天天看点

基于QEMU的NVRAM仿真

本文翻译自 http://www.devttys0.com/2012/03/emulating-nvram-in-qemu/

能够在Qemu中模拟嵌入式应用程序非常有用,但并非没有陷阱。我遇到的最常见的问题可能是的二进制程序试图从NVRAM中读取配置数据。由于二进制文件在Qemu中运行,而不是在目标设备上运行,因此显然没有要读取的NVRAM。

嵌入式应用程序通常通过共享库与NVRAM交互。该库又与包含设备当前配置设置的MTD分区接口交互。如果没有NVRAM配置数据,许多程序将无法正常运行,需要我们拦截NVRAM库调用并返回有效数据,以便在Qemu中正确执行应用程序。

这是从固件更新映像中提取的Web服务器,该二进制程序拒绝在Qemu下启动:

基于QEMU的NVRAM仿真

看起来httpd无法启动,因为它不知道要绑定到哪个IP地址。IP无法通过命令行参数设置,因此必须从其他位置获取此数据。让我们启动IDA并开始破解吧!

快速浏览httpd的主要功能可以发现罪魁祸首。httpd服务器尝试通过调用nvram_get来获取IP地址和协议设置。如果这些调用失败,则会打印出我们在上面看到的错误消息:

基于QEMU的NVRAM仿真

nvram_get函数是从共享库中导入的,使用LD_PRELOAD可以很容易地进行拦截:

基于QEMU的NVRAM仿真

但是,在我们开始拦截函数调用之前,我们需要了解有关nvram_get的更多信息。它似乎只接受一个参数,该参数是一个字符串(特别是上面的“lan_ipaddr”和“lan_proto”),但是它返回什么类型的数据?

从前面的反汇编中可以看出,nvram_get(“ lan_proto”)的返回值保存在寄存器R5中。后来,使用strcmp将R5指向的数据与字符串“ static”和“ dhcp”进行比较:

基于QEMU的NVRAM仿真

同样,nvram(“ lan_ipaddr”)的返回值保存在寄存器R6中,该值稍后作为第一个参数传递给inet_aton:

基于QEMU的NVRAM仿真

因此,nvram_get的参数是键值对中的键字符串并返回相应的值字符串。我们可以使用以下代码轻松模拟此功能以及一些虚拟配置数据:

#include <stdio.h>
#include <string.h>

char *nvram_get(char *key)
{
        char *value = NULL;

        if(strcmp(key, "lan_ipaddr") == 0)
        {
                value = strdup("127.0.0.1");
        }

        if(strcmp(key, "lan_proto") == 0)
        {
                value = strdup("static");
        }

        printf("nvram_get(%s) == %s\n", key, value);
        return value;
}
           

我们需要将此代码作为共享库进行交叉编译,并将其复制到squashfs-root目录中,这个目录是我们运行qemu的目录:

[email protected]:~$ arm-linux-gcc -shared nvram.c -o nvram.so
[email protected]:~$ cp nvram.so squashfs-root/nvram.so
           

现在,我们将尝试再次在Qemu中运行httpd,这一次在LD_PRELOAD环境变量中指定nvram.so文件的路径:

[email protected]:~/squashfs-root$ sudo chroot . ./qemu-arm -E LD_PRELOAD="/nvram.so" usr/sbin/httpd 
usr/sbin/httpd: relocation error: /nvram.so: symbol __register_frame_info, version GLIBC_2.0 not defined in file libgcc_s.so.1 with link time reference
           

看起来nvram.so文件期望引用__register_frame_info函数,该函数符号在目标系统的libgcc_s.so库中不存在。发生这种情况是因为我们用来构建nvram.so的工具链与供应商用来为目标系统构建固件的工具链不同。我们希望__register_frame_info存在,而他们却没有。

由于供应商未为其系统发布GPL代码,因此我们不能简单地使用其工具链来重新构建nvram.so。我们可以向公司发起GPL请求,但是有一种更简单(更快!)的方法。我们只需要在nvram.c中为__register_frame_info符号添加占位符定义(函数声明):

#include <stdio.h>
#include <string.h>

void __register_frame_info(void) { }
void __deregister_frame_info(void) { }
void __unregister_frame_info(void) { }

char *nvram_get(char *key)
{
        char *value = NULL;

        if(strcmp(key, "lan_ipaddr") == 0)
        {
                value = strdup("127.0.0.1");
        }

        if(strcmp(key, "lan_proto") == 0)
        {
                value = strdup("static");
        }

        printf("nvram_get(%s) == %s\n", key, value);
        return value;
}
           

然后重新构建nvram.so,再次尝试运行httpd

[email protected]:~/squashfs-root$ sudo chroot . ./qemu-arm -E LD_PRELOAD="/nvram.so" usr/sbin/httpd 
httpd server started at port 80 (delay 0 second) 
nvram_get(lan_ipaddr) == 127.0.0.1
nvram_get(lan_proto) == static
           

我们已成功拦截nvram_get调用,httpd错误消息已消失,服务器似乎正在运行。让我们测试一哈:

基于QEMU的NVRAM仿真

成功!httpd现在可以与IDA的调试器进行一对一的交互了。

继续阅读