使用QEMU chroot进行固件本地调试

QEMU是开发者在调试一些不同架构的程序时经常使用的虚拟机软件。它有两种运行模式,全系统模拟(System mode)和单程序运行(User mode)。System mode和开发者平常用的VMWare一样,模拟整个系统从加载器开始的启动和运行。在设备逆向过程中,如果仅仅是为了运行开发者提取出文件系统中的某一个程序,那就可以使用QEMU的user mode来简化整个操作流程,同时能够方便的利用 QEMU 自带的GDB服务来进行调试,免去搭建环境的烦恼。

但是,单单在命令行中调用“qemu-arm myprogram”往往没有那么简单,因为动态链接的程序都会依赖几个动态链接库。虽然可以传入 -L 参数,或者通过指定环境变量QEMU_LD_PREFIX解决,但这种方式不但不优雅,还会造成重复性的工作——每个程序依赖的库不同,因此每次都要选择不同的目录。而且使用这种方式启动的程序,所运行的程序文件夹(CWD)与原来不同,很可能访问不了程序中硬编码的一些文件的绝对路径,造成程序出错。

因此最简单直接的方法还是使用chroot配合QEMU,来完全模拟程序的文件系统环境,以固件的根目录作为chroot的根目录,程序也能够自动加载到它所需要的libc与其他各种函数库。绿盟的小伙伴在看雪上做过一次分享,我听过后以为很容易上手,但是操作时踩到了一些坑。这里总结一下整个流程,顺便讲解一下其中的原理。

编译安装

Ubuntu自带的 QEMU 版本有点老(我的18.04 LTS 附带的 QEMU 2.11),在调试时会遇到类似下面的报错:

老版本QEMU不能够很好的处理与调试相关的ptrace系统调用,我们需要从官方的最新版源代码编译安装QEMU。

依赖的安装可以参考官方教程。安装好依赖后从git获取最新的源码,并使用以下参数指定编译的QEMU采用静态链接,最后进行编译。我在这里指定prefix目录为当前目录下的 staging,自己操作时可以随意更改。

然后 staging/user-static 目录下就是我们编译好的核武器了。

安装binfmt

binfmt(Binary Format)是一个内核模块,它的用处如它的名字,通过二进制文件头来识别它的格式,从而指定用哪个解释器去启动——可以理解为二进制文件的hashbang(用处类似于在Python文件的第一行写上“#!/usr/bin/env python”)。有了它我们就可以像启动原生ELF一样启动一个ARM或其他任何QEMU支持的程序了。

安装这个包会依赖安装系统软件源中的qemu-user。我们用不到它,但装这个包的意义在于它包含了几个自动向内核注册QEMU binfmt的脚本,这样我们就不需要再手动指定我们的ARM可执行文件需要哪个路径下的QEMU来执行,非常方便。安装成功后在命令行中执行“update-binfmts –display”。

图 1 update-binfmts输出

我们此时可以测试一下,临时将环境变量QEMU_LD_PREFIX设置为我们要chroot进去的根目录,然后运行ARM设备中提取出的ELF可执行文件,如果不报错就可以了。图中a.out是我编译的arm64的hello world,这个程序可在我的测试设备上正常运行。

图 2 运行示例程序 Hello World

复制QEMU程序

hello world可以运行之后,我们还需要准备一下我们的rootfs目录。

将第一步编译目录中的“staging/user-static/qemu-aarch64”复制到“update-binfmts”中显示的对应位置(/usr/bin/qemu-aarch64),如果必要的话,将这里的aarch64替换成你所要运行的程序架构。注意必须是相同位置!当我们启动为ARM或其他架构编译的应用程序时,系统会调用binfmts识别它的类型并调用之前注册的interpreter(如/usr/bin/qemu-aarch64)来“翻译”启动。在chroot下,依然会从这个路径中寻找。因此如果chroot后这个路径下找不到QEMU,启动任何程序都会报错No such file or directory。这个报错会有很多歧义,因此一定要自己确认一下QEMU确实在rootfs的“/usr/bin”目录中。

运行

到这里,我们就可以像在虚拟机中一样,通过shell运行这个chroot中的所有程序了!

总结 Xxx not found 相关的问题

当我运行一个命令时,

报一个错——

我的第一反应肯定是怀疑自己。程序名称输错了?但又不对,我怎么可能这么蠢呢?一路摸爬滚打下来,我发现最蠢的还是这句模糊的报错信息。它会有很多歧义。

 

根据我的经验,这个报错会有以下四种原因。

  • 最容易想到的:要运行的命令不存在。
  • 动态链接器不存在,如下例,运行IDA的远程调试器。

运行objdump可以看到它需要哪个解释器来读取它。一般都是ld-xxxx.so

如果ld找不到的话,这程序能运行的概率就很小了。

  • QEMU解释器没找到。如果我们注册了binfmt却没有将qemu拷贝到“rootfs/usr/bin“中,chroot时也会报一样的错误——文件没找到。如果没有踩过这个坑,大概会很久找不出原因吧。
  • 动态链接库没找到。这种情况比较显而易见,因为他会告诉你哪个库没找到。

 

以后拿到一个新的固件包,只需要解压到一个文件夹里,把对应架构的qemu拷贝进去,直接运行命令chroot即可。遇到需要调试的程序,我们通过运行“qemu-aarch64 -g 2331 /path/to/binary”指定-g参数开启调试选项,也可以声明一个环境变量QEMU_GDB=2331,带上这个环境变量所启动的程序,都会自动开启GDB端口并等待调试器attach,调试起来是不是很方便呢?

 

参考资料:

 

发表评论