shadow 是一个 jemalloc 堆漏洞利用框架。它的设计目标是独立于使用 jemalloc 作为堆分配器的目标应用程序(无论是 Android 的 libc、Firefox、FreeBSD 的 libc、独立 jemalloc,还是其他任何实现)。当前版本(2.0)已在以下目标上进行了广泛测试:
除了工具的源代码之外,此仓库还包含关于设置 Android 用户态调试环境以使用 shadow 的文档、利用 shadow 进行的 Android jemalloc 结构快速概览,以及关于 double free、未对齐 free 和任意 free 漏洞在 Android 的 jemalloc 上如何表现的一些说明。
当你首次发出 jemalloc 相关命令时,shadow 会解析所有它知道的 jemalloc 元数据,并将其保存至一个 Python pickle 文件。后续的命令将使用此 pickle 文件,而不必再次从内存中解析元数据,以提升速度。
当你知道 jemalloc 元数据的状态已发生变化时(例如,当你进行了一些分配或触发了垃圾回收),请使用 jeparse 命令重新解析元数据并重新创建 pickle 文件。
第一步是在你的主机上安装 pyrsistence。
在已 root 的设备上执行以下操作:
host$ adb shell
phone$ su root
phone# ps -e
从 ps 的输出中选择一个进程,例如 com.google.process.gapps:
...
u0_a19 4679 3214 1668980 69216 SyS_epoll_ 7fa5f41430 S com.google.process.gapps
...
phone$ cd /data/local/tmp
phone$ ./gdbserver64 :5039 --attach 4679
你可以在“bin”目录中找到 ARM32 和 ARM64 的 GDB 服务器二进制文件。或者,如果你不信任我们,请执行:
host$ git clone http://android.googlesource.com/toolchain/gdb
host$ cd ./gdb/gdb-7.11
host$ mkdir build64; cd build64
host$ ../configure --program-prefix=aarch64-eabi-linux- --target=aarch64-eabi-linux --disable-werror
host$ make
host$ sudo make install
然后在主机上执行:
host$ adb forward tcp:5039 tcp:5039
host$ aarch64-eabi-linux-gdb
(gdb) target remote :5039
(gdb) source /dir/with/shadow/gdb_driver.py
(gdb) jeparse -c /dir/with/shadow/cfg/android7_64.cfg
(gdb) jeruns -c
有时如果你花费太长时间才发出 target remote :5039 命令,GDB 服务器可能会停止监听。因此,如果你在发出 jeparse 命令时看到奇怪的错误,请从头开始重新操作。
shadow 的 Windows/Firefox 版本已在以下环境中测试通过:
注意:如果你使用的 Firefox 版本低于 36.0,请使用 mozjs 分支!
首先,你需要使用 Mozilla 的符号服务器 设置 WinDBG。你还需要安装 pykd。然后将你从 GitHub 克隆的 shadow 目录复制到某个路径(例如 C:\tmp\)。
你还可以在文件“windbg-init.cmd”中找到示例 WinDBG 初始化脚本。将其放置到 C:\tmp\ 中,并使用 windbg.exe -c "$$>< C:\tmp\windbg-init.cmd" 启动 WinDBG。
最后,在 WinDBG 中发出以下命令:
!load pykd.pyd
!py c:\\tmp\\shadow\\pykd_driver help
[shadow] De Mysteriis Dom jemalloc
[shadow] shadow v2.0
[shadow] Firefox v56.0a1 (x86-64)
[shadow] jemalloc 相关命令:
[shadow] jechunks : 转储所有可用 chunk 的信息
[shadow] jearenas : 转储 jemalloc arena 的信息
[shadow] jerun [-m] <address> : 转储单个 run 的信息
[shadow] -m : 将内容预览映射到元数据
[shadow] jeruns [-cs] : 转储 jemalloc run 的信息
[shadow] -c : 仅当前 run
[shadow] -s <size class> : 仅指定大小类的 run
[shadow] jebins : 转储 jemalloc bin 的信息
[shadow] jebininfo : 转储 bin 大小的信息
[shadow] jesize2bin : 将大小转换为 bin 索引
[shadow] jeregions <size class> : 转储包含给定大小类 region 的所有 run
[shadow] jesearch [-cs] <hex> : 搜索堆中以寻找给定的十六进制双字
[shadow] -c : 仅当前 run
[shadow] -s <size class> : 仅指定大小的 region
[shadow] jeinfo <address> : 显示给定地址的所有可用细节
[shadow] jedump [path] : 将堆快照存储到当前工作目录或指定路径
[shadow] jestore [path] : jedump 的别名
[shadow] jetcaches : 转储所有 tcache 的信息
[shadow] jetcache [-bs] <tid> : 转储单个 tcache 的信息
[shadow] -b <bin index> : 仅指定 bin 索引的信息
[shadow] -s <size class> : 仅指定大小类的信息
[shadow] jeparse [-crv] : 从内存解析 jemalloc 结构
[shadow] -c <config file> : jemalloc 目标配置文件
[shadow] -r : 读取内容预览
[shadow] -v : 生成 debug.log
[shadow] Firefox 专用(仅 pykd)命令:
[shadow] nursery : 显示 SpiderMonkey GC nursery 的信息
[shadow] symbol [-vjdx] <size> : 显示所有具有给定大小的 Firefox 符号
[shadow] -v : 仅包含虚函数表的类符号
[shadow] -j : 仅 SpiderMonkey 的符号
[shadow] -d : 仅 DOM 符号
[shadow] -x : 仅非 SpiderMonkey 的符号
[shadow] pa <address> [<length>] : 修改 ArrayObject 的长度(默认新长度为 0x666)
[shadow] Android 专用命令:
[shadow] jefreecheck [-bm] : 显示可以传递给 free() 的地址
[shadow] -b <bin index> : 显示将释放到指定 bin 索引 tcache bin 的地址
[shadow] -m <name> : 仅搜索指定模块
[shadow] 通用命令:
[shadow] jeversion : 输出版本号
[shadow] jehelp : 显示本帮助信息
如果你没有看到上述帮助信息,那就说明你做错了 ;)
注意:此功能目前仅适用于 Firefox/Windows!
symbol 命令允许你搜索特定大小的 SpiderMonkey 和 DOM 类(以及结构体)。这在尝试利用释放后使用漏洞,或希望定位有趣的受害对象以进行覆盖/破坏时非常有用。
在 shadow 的主目录中,你可以找到两个小的 PDB 解析实用程序:symhex.py 和 pdbdy.py(速度更快)。在“xul.pdb”上运行它们,以生成 shadow 在“pdb”目录中期望的 Python pickle 文件(作为“pdb/xul-VERSION.pdb.pkl”)。在运行它们之前,请确保你已经注册了“msdia90.dll”;例如,在 Windows 8.1 x86-64 上,你可以使用管理员提示符执行以下操作:
regsvr32 "c:\Program Files (x86)\Common Files\Microsoft Shared\VC\msdia90.dll"
你还需要 Python 的“comtypes”模块;安装 pip,然后执行 pip install comtypes。
为了获取“xul.pdb”,你需要使用 Mozilla 的符号服务器 设置 WinDBG。
unmask_jemalloc 最初被重新设计为模块化结构,以支持所有三种主流调试器和平台(WinDBG、GDB 和 LLDB)。当添加了仅限 Firefox/Windows/WinDBG 的功能后,该工具被重命名为 shadow。
以下是新设计的概述(箭头表示“导入”)。目标是显而易见地将所有调试器相关的代码放在 *_driver 和 *_engine 模块中。
---------------------------------------------------------------------------------------
调试器所需的前端(胶水层)