本文翻译自 http://www.devttys0.com/2012/03/emulating-nvram-in-qemu/
能够在Qemu中模拟嵌入式应用程序非常有用,但并非没有陷阱。我遇到的最常见的问题可能是的二进制程序试图从NVRAM中读取配置数据。由于二进制文件在Qemu中运行,而不是在目标设备上运行,因此显然没有要读取的NVRAM。
嵌入式应用程序通常通过共享库与NVRAM交互。该库又与包含设备当前配置设置的MTD分区接口交互。如果没有NVRAM配置数据,许多程序将无法正常运行,需要我们拦截NVRAM库调用并返回有效数据,以便在Qemu中正确执行应用程序。
这是从固件更新映像中提取的Web服务器,该二进制程序拒绝在Qemu下启动:
看起来httpd无法启动,因为它不知道要绑定到哪个IP地址。IP无法通过命令行参数设置,因此必须从其他位置获取此数据。让我们启动IDA并开始破解吧!
快速浏览httpd的主要功能可以发现罪魁祸首。httpd服务器尝试通过调用nvram_get来获取IP地址和协议设置。如果这些调用失败,则会打印出我们在上面看到的错误消息:
nvram_get函数是从共享库中导入的,使用LD_PRELOAD可以很容易地进行拦截:
但是,在我们开始拦截函数调用之前,我们需要了解有关nvram_get的更多信息。它似乎只接受一个参数,该参数是一个字符串(特别是上面的“lan_ipaddr”和“lan_proto”),但是它返回什么类型的数据?
从前面的反汇编中可以看出,nvram_get(“ lan_proto”)的返回值保存在寄存器R5中。后来,使用strcmp将R5指向的数据与字符串“ static”和“ dhcp”进行比较:
同样,nvram(“ lan_ipaddr”)的返回值保存在寄存器R6中,该值稍后作为第一个参数传递给inet_aton:
因此,nvram_get的参数是键值对中的键字符串并返回相应的值字符串。我们可以使用以下代码轻松模拟此功能以及一些虚拟配置数据:
#include
#include
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的目录:
eve@eve:~$ arm-linux-gcc -shared nvram.c -o nvram.so
eve@eve:~$ cp nvram.so squashfs-root/nvram.so
现在,我们将尝试再次在Qemu中运行httpd,这一次在LD_PRELOAD环境变量中指定nvram.so文件的路径:
eve@eve:~/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
#include
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
eve@eve:~/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错误消息已消失,服务器似乎正在运行。让我们测试一哈:
成功!httpd现在可以与IDA的调试器进行一对一的交互了。