Loading... 主要使用的调试方法为:qemu + gdb-multiarch,再交叉编译得到有符号的 libc。 ### qemu 安装 直接通过 apt 安装并不是一个很好的选择,因为版本往往不是最新的,而且对调试的支持并不是很好(内存分析的比较一般),我选择通过手动编译安装来安装。 首先下载源码,在[官网](https://www.qemu.org/)可以直接下载,我下载了最新版本,即 ``` 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 可以获得程序的基址 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/05/1779591138.png "></div> 大概就是这样一个效果,通过手动编译的 qemu 起程序 gdb 就可以分析出各段了,不再是恼人的 [explored] 了。有了程序的基地址,就可以下断点了。 ### 交叉编译 libc 其实到上面为止,已经可以调试了,但是 qemu 自带的 libc 是没有符号的,而且版本往往也不是我们想要的,这个时候就需要手动编译一个 libc 了。x86 机器想要编译 arm 的程序,就需要使用交叉编译。交叉编译具体是什么这里不再多说了,反正有了这一套工具链,我们就可以编译出想要的平台的二进制文件了,arm 架构下比较常用的工具链是由 linaro 维护的,可以到[官网](http://releases.linaro.org/components/toolchain/binaries/)下载需要的工具链,实例中的程序需要的是 arm-linux-gnueabihf,所以我们就下载对应的工具链即可,关于版本不是特别重要,我下了最新版也编译成功了。由于我的宿主机是 x86_64,所以下载如下的工具链 ```shell 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,就可以带符号调试了 <div style="text-align:center"><img src="https://www.cjovi.icu/usr/uploads/2021/05/149583752.png "></div> 就可以通过 pwndbg 看堆的情况了。 当然由于各种各样的原因,您可能无法实现上面的效果,建议看下面的问题解决。如果按照下面的做法做了还是解决不了,欢迎联系笔者一起排查。 ### 一些问题的解决 #### 使用 qemu 起程序的时候提示 `error while loading shared libraries` 比较可能的原因是 libc 的安装目录中的文件被破坏了。 此工具链编译出来的 libc 似乎是把绝对地址写死的,也就是编译出来的 libc **必须在 config 的时候 --prefix 参数指定的安装目录下有一份完整的副本才能工作**。 什么意思呢,就是根据你自己的意愿,你可以把 lib 文件夹**拷贝**到任何一个文件夹里面,然后在这个文件夹里面用 ```shell qemu-xxx -L ./ elf ``` 启动 elf 是可行的,但是由于在运行时 lib 里面的文件会引用别的文件,被引用的文件都是以绝对路径寻址的,也就是会引用 prefix 指定的目录里面的文件,如果这个文件夹中的文件被删掉了,自然就会出现找不到的错误。 **太长不看版:prefix 指定的目录里的文件不要动,mv 这里面的文件的时候都换成 cp** #### 明明用了自己手动编译的 libc,还是没有符号,看不了堆之类的 这里 gdb 对符号的读取似乎有一定问题,有可能需要把 lib 文件拷贝到被调试的二进制文件的目录下才能正确读取符号。也就是保证用 ```shell qemu-xxx -g xxxx -L ./ elf ``` 启动。笔者经过测试发现这样 gdb 是可以读取出符号的。 ##### 更好地解决 大家应该都不愿意做一道题就拷贝一次 lib,毕竟 lib 挺大的,我尝试了一下,软链接也是可以的。也就是把拷贝变成软链接。 ```shell ln -s /path/to/lib ./lib ``` 比如我这里建立好的效果就是 ##### 相比之下不怎么好的解决方法 如果硬要用 prefix 的目录,gdb 自己又没法找到符号的话,也可以自己加,使用 gdb 的 `add-symbol-file` 指令即可,也就是 ```shell 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 掉整个源码文件夹,从头重新来过)。 最后修改:2021 年 11 月 10 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 0 如果觉得我的文章对你有用,那听听上面我喜欢的歌吧