PWN ARM 环境配置

Posted on May 25, 2021

主要使用的调试方法为:qemu + gdb-multiarch,再交叉编译得到有符号的 libc。

qemu 安装

直接通过 apt 安装并不是一个很好的选择,因为版本往往不是最新的,而且对调试的支持并不是很好(内存分析的比较一般),我选择通过手动编译安装来安装。

首先下载源码,在官网可以直接下载,我下载了最新版本,即

wget https://download.qemu.org/qemu-6.0.0.tar.xz

下好之后解压,进入目录,编译安装,建议在其中建立一个 build 文件夹,因为会编译出来一堆东西。

mkdir build
cd build
../configure --gdb=/usr/bin/gdb --enable-debug --prefix=/usr/local

这里的参数,通过 –gdb 设置 gdb 路径,其实不需要显式设置这个;用 –enable-debug 开启调试功能;用 –prefix 设置安装的路径,我这里就直接安装到 /usr/local 中了。默认情况下会编译所有架构的虚拟机,正是我们需要的。

我在 configure 的时候碰到了 ERROR: Cannot find Ninja,也就是缺少 ninja。解决这个问题,不建议直接通过包管理器安装,因为包管理器中的版本往往较低,可能还是无法满足需求,可以直接源码安装

git clone https://github.com/ninja-build/ninja.git
cd ninja
./configure.py --bootstrap
sudo cp ninja /usr/bin/

configure 好了之后,就可以 make 了

make -j$(nproc)

这里加 -j 参数是指定编译使用的线程数,应为要编译大量架构的虚拟机,编译一次的时间会比较长,建议开到最大($(nproc) = 机器逻辑处理器数)。

make 好了后就可以安装

sudo make install

这样就安装好了 qemu,也自带了许多 libc,在 /usr/ 目录下。

通过 gdb 调试

以一个 arm32 动态链接程序为例

qemu-arm -g 2222 -L /usr/arm-linux-gnueabihf ./vuln

使用 -g 参数指定 gdb 远程调试的端口,-L 指定动态运行库,./vuln 指定要调试的二进制程序。

按下回车后就会发现卡在那了,这是正常的,然后新开一个终端,使用 gdb-multiarch 进行远程调试

gdb-multiarch ./vuln -q

如果没有 gdb-multiarch 的话,包管理器装一下就可以了。

然后在 gdb 里面输入

target remote :2222

就可以 attach 上去了,输入 vmmap 可以获得程序的基址

大概就是这样一个效果,通过手动编译的 qemu 起程序 gdb 就可以分析出各段了,不再是恼人的 [explored] 了。有了程序的基地址,就可以下断点了。

交叉编译 libc

其实到上面为止,已经可以调试了,但是 qemu 自带的 libc 是没有符号的,而且版本往往也不是我们想要的,这个时候就需要手动编译一个 libc 了。x86 机器想要编译 arm 的程序,就需要使用交叉编译。交叉编译具体是什么这里不再多说了,反正有了这一套工具链,我们就可以编译出想要的平台的二进制文件了,arm 架构下比较常用的工具链是由 linaro 维护的,可以到官网下载需要的工具链,实例中的程序需要的是 arm-linux-gnueabihf,所以我们就下载对应的工具链即可,关于版本不是特别重要,我下了最新版也编译成功了。由于我的宿主机是 x86_64,所以下载如下的工具链

wget http://releases.linaro.org/components/toolchain/binaries/latest-7/arm-linux-gnueabihf/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz

下载下来后解压可以得到工具链,解压到你喜欢的文件夹里面就可以了,然后最好把 bin 的路径加到环境变量里面,这样之后就可以直接调用编译器了。

编译 libc 和普通编译一样,只是 configure 的时候要注意一下 CC 和 host 的配置,我是这样配置的

CC=arm-linux-gnueabihf-gcc CFLAGS="-g -g3 -ggdb -gdwarf-4 -Og -Wno-error" ../configure \
--prefix=/your/path/to/install --host=arm-linux --target=arm-linux

然后

make -j$(nproc)
make install

就可以了。

在通过 qemu 启动的时候通过 -L 设置程序使用的运行库为我们编译出来的 libc,就可以带符号调试了

就可以通过 pwndbg 看堆的情况了。

当然由于各种各样的原因,您可能无法实现上面的效果,建议看下面的问题解决。如果按照下面的做法做了还是解决不了,欢迎联系笔者一起排查。

一些问题的解决

使用 qemu 起程序的时候提示 error while loading shared libraries

比较可能的原因是 libc 的安装目录中的文件被破坏了。

此工具链编译出来的 libc 似乎是把绝对地址写死的,也就是编译出来的 libc 必须在 config 的时候 –prefix 参数指定的安装目录下有一份完整的副本才能工作

什么意思呢,就是根据你自己的意愿,你可以把 lib 文件夹拷贝到任何一个文件夹里面,然后在这个文件夹里面用

qemu-xxx -L ./ elf

启动 elf 是可行的,但是由于在运行时 lib 里面的文件会引用别的文件,被引用的文件都是以绝对路径寻址的,也就是会引用 prefix 指定的目录里面的文件,如果这个文件夹中的文件被删掉了,自然就会出现找不到的错误。

太长不看版:prefix 指定的目录里的文件不要动,mv 这里面的文件的时候都换成 cp

明明用了自己手动编译的 libc,还是没有符号,看不了堆之类的

这里 gdb 对符号的读取似乎有一定问题,有可能需要把 lib 文件拷贝到被调试的二进制文件的目录下才能正确读取符号。也就是保证用

qemu-xxx -g xxxx -L ./ elf

启动。笔者经过测试发现这样 gdb 是可以读取出符号的。

更好地解决

大家应该都不愿意做一道题就拷贝一次 lib,毕竟 lib 挺大的,我尝试了一下,软链接也是可以的。也就是把拷贝变成软链接。

ln -s /path/to/lib ./lib

比如我这里建立好的效果就是QQ截图20210810161424.png

相比之下不怎么好的解决方法

如果硬要用 prefix 的目录,gdb 自己又没法找到符号的话,也可以自己加,使用 gdb 的 add-symbol-file 指令即可,也就是

pwndbg> add-symbol-file /path/to/libc libc_base

但是这里的 libc_base 要自己找,比较麻烦,所以不推荐这种方式,让 gdb 自己来肯定更方便一点。

总结一下上面两个问题

其实都是 libc 所在的位置的问题。第一个问题是不能移动 lib 文件夹,不然 qemu 找不到动态库,第二个问题则是必须移动 lib 文件夹,不然 gdb 找不到文件。综合一下,最好的解决方案就是建立符号链接,直接在当前目录建立一个链接到 prefix 中的 lib 的符号链接,就可以了。

明明用了自己编译的 libc,也用软链接,但是还是没符号

首先检查自己编译的 libc 是否有符号,可以用 IDA 分析,看看能不能找到 main_arena 这个全局变量(在 __malloc_hook 上方不远处),找不到的话就说明是没符号的。如果用了上面的方法还是不行的话基本上就是因为编译出来的 libc 实际是没符号的。这可能是 config 时出错等问题造成的,建议重新 config,make clean 后再重新编译(事实上 clean 非常慢,可以直接 rm 掉整个源码文件夹,从头重新来过)。