VxWorks固件加载地址分析方法研究

IoT 2个月前 admin
107 0 0
本文主要是通过固件格式分析和固件特征识别的方法,识别VxWorks加载地址。
经过观察研究,现有的路由器设备主要分为Linux系统、RTOS系统,VxWorks属RTOS系统的一种,而VxWorks系统除了用于嵌入式等设备,还常被用于低端路由器。VxWorks的路由器设备通常升级固件包不超过3MB,这样是为了减少固件的大小,在廉价、存储空间小的路由器设备上也可以进行存储和运行。总体来看,VxWorks路由器设备成本较低,价格相对于同级别的 squashfs 文件系统的路由器要便宜很多,而 squashfs 则会占用更多的存储空间,通常非增量式升级固件包都大于3MB。
本文主要基于以下路由器设备固件进行研究,它们都使用VxWorks系统,总共28个固件:
* TP-Link:tplink_wdr5610_v1、tplink_wdr5620_v2、tplink_wdr5640_v1、tplink_wdr5650、tplink_wdr5660、tplink_wdr5670_v1、tplink_wdr7660、tplink_wr541gqos v4、tplink_wr842nv3、tplink_wr886n_v7
* 水星:shuixing_d12_v2、shuixing_d12a_v1、shuixing_d12b_v1、shuixing_d19_v1、shuixing_d121g_v1、shuixing_d126_v2、shuixing_d128_v1、shuixing_d196g_v1、shuixing_m6g_v3、shuixing_m9g_v1、shuixing_mw300r_v15、shuixing_mw305r_v7、shuixing_mw313r_v4、shuixing_mw315r_v1、shuixing_mw325r_v3、shuixing_mw450r_v4
* 其他VxWorks系统路由器固件:ap1900dg、FWB200 V3.0_3.0.1_Build_20191227_Rel.44604
这里的整体思路是,首先找出固件中的某些关键特征,然后通过多个样本实验,去推断加载地址,尽管这些关键特征可能只限于这些路由器设备的固件而不是所有的VxWorks嵌入式设备,所以本文只限于对以上路由器设备固件的VxWorks分析方法的总结。
VxWorks固件加载地址分析方法研究
在观察不同固件,从多个固件的大小、文本内容、段、关键字符串等特征观察后,得出以下结论:
* 在这28个固件里面,文件大小分为三个区间:1MB大小以下、1MB至2MB之间、2MB及以上。
 其中1MB文件包含MINIFS文件系统,可以搜索到“MINIFS”字符串,并且MINIFS存储了一些明文路径信息,算是一个固件特征。样例如下,红框内是MINIFS所包含的文件名称和文件大小等信息:
VxWorks固件加载地址分析方法研究
此MINIFS开头为“MINIFS”字符串,用“0”字符补齐16个长度,然后是一个四字节类型,暂时不知道其用途。紧接着下一个四字节存储文件大小,然后下一个四字节存储一个偏移量,最后是文件名称字符串,用“0”字符补齐0x50个长度。那么可以分析得到 C 结构如体下:
struct file_info{
 int size;
 int offset;
 char filename[0x50];
}

struct MINIFS{
 char magic[16];
 int flag;
 struct file_info[FILECOUNT];
}
样本如下图,红色框部分为magic字段占用16字节,紧接着的绿色框是不知用途的四字节,然后是上面结构体的file_info的结构体数组,file_info包含文件大小、文件偏移、文件名称字符串,有FILECOUNT个这样的结构体:VxWorks固件加载地址分析方法研究
  • 对于这种格式的MINIFS,现有工具可以对其进行分析和解包,工具链接:

    https://github.com/H4lo/MINIFS-Decompression

  • 1MB至2MB之间,以及2MB以上的固件同样包含“MINIFS”字符串,但不再具有明文路径特征,样例如下,红框内不再含有文件名称等信息:

VxWorks固件加载地址分析方法研究
此MINIFS开头为“MINIFS”字符串,用“0”字符补齐16个长度,然后是一个四字节类型,暂时不知道其用途。紧接着下一个四字节存储文件大小,然后下一个四字节存储一个偏移量,最后是文件名称字符串,用“0”字符补齐0x50个长度。那么可以分析得到 C 结构如体下:
struct file_info{
 int size;
 int offset;
 char filename[0x50];
}

struct MINIFS{
 char magic[16];
 int flag;
 struct file_info[FILECOUNT];
}
样本如下图,红色框部分为magic字段占用16字节,紧接着的绿色框是不知用途的四字节,然后是上面结构体的file_info的结构体数组,file_info包含文件大小、文件偏移、文件名称字符串,有FILECOUNT个这样的结构体:VxWorks固件加载地址分析方法研究
  • 对于这种格式的MINIFS,现有工具可以对其进行分析和解包,工具链接:

    https://github.com/H4lo/MINIFS-Decompression

  • 1MB至2MB之间,以及2MB以上的固件同样包含“MINIFS”字符串,但不再具有明文路径特征,样例如下,红框内不再含有文件名称等信息:

VxWorks固件加载地址分析方法研究
  • 这里的文件看起来像是进行了加密,那么便无法再通过上面的工具进行解包了。

  • 通过分析,在这28个固件中,ARM架构的VxWorks固件通常具有外部符号表,可以通过IDA导入signature文件对VxWorks文件做符号恢复,具体恢复方法网上有很多资料,可参考:

    http://tttang.com/archive/1418/

    符号恢复不在本文描述范围内,这里不再赘述。而MIPS架构固件则不存在任何符号信息。

    如下图是ARM架构的VxWorks文件,可以在binwalk提取出来的文件中,使用 grep -r 找到符号信息:

VxWorks固件加载地址分析方法研究下图则是MIPS架构的VxWorks文件,不存在任何符号信息:VxWorks固件加载地址分析方法研究
  • 在部分固件中,字符串“MyFirmware”可作为加载地址的关键指纹,但这并不通用。具体方法是:在固件中搜索字符串“MyFirmware”,然后往上查找一个段开始的位置,这很好辨认,一般段开始位置之前是上一个段使用0xFF或者0x00进行填充。找到段开始位置之后往后数0x18个字节,这两个四字节值即是VxWorks系统的加载地址。样例如下:

VxWorks固件加载地址分析方法研究
经过尝试后,发现1MB以下的固件不包含“MyFirmware”字符串,所以无法通过这种方式确定加载地址。当不存在“MyFirmware”字符串特征时,可以通过分析uboot信息来获取加载地址。
VxWorks固件加载地址分析方法研究
uboot通常包含较少的启动信息,但对于分析加载地址也已经够用了。设备在加电启动之后会执行uboot初始化内存等操作,然后再交给VxWorks运行整个系统,那么就可以对uboot信息进行分析,确定uboot给出的系统加载地址。
要知道uboot也可以是一个单独的二进制程序,只是在制作固件时把uboot和系统拼接在了一起,那么uboot也有其加载地址。所以我们在提取出uboot对其分析时,首先需要对uboot的加载地址进行分析。
在部分固件中,找到一个关键指纹“u-boot image”字符串,在这个字符串位置减去16个字节,会有两个相邻的四字节值,通过分析多个具备此特征的固件后,确定这个就是uboot的加载地址,这有两个例子。
示例一:通过上述的分析方法,找到“u-boot image”字符串,减去16字节,即红框中的两个值,那么此样例固件中的uboot加载地址即是0x80200000:
VxWorks固件加载地址分析方法研究
然后通过此地址作为加载地址后,在IDA中修复uboot一些函数,从字符串引用正常情况来看,确定加载地址是正确的。如果加载地址不对,字符串引用不正确会影响IDA的反编译结果,导致代码难以分析,这其实很好辨认。下图即是正确的加载地址解析得到的结果,明显代码逻辑是通顺的:
VxWorks固件加载地址分析方法研究
示例二:还是通过上述的方法,找到“u-boot image”字符串后,得到uboot加载地址为0x80010000:
VxWorks固件加载地址分析方法研究
下图是示例二使用此地址后,在IDA中的解析情况,依然正确的识别了字符串引用,地址无法解析是因为这个程序有其他段的地址,而我这里没有设置其他的内存段:
VxWorks固件加载地址分析方法研究
现在有了uboot程序,可以对其进行逆向分析,从而得到VxWorks系统的加载地址。
在上面的示例一中,需要得到一个字符串, 也就是strtol 的参数一才能得到镜像加载地址,这就需要对uboot程序进行逆向分析,而这个strtol函数也是通过逆向分析得到的结果重新命名的。在示例二中,看似好像加载地址为0x8100000,但尝试后发现并不是此地址,所以也是需要对uboot进行进一步逆向才能得到结果。
另外,在分析示例一中的汇编代码时有一个小插曲,发现MIPS指令不是定长的四字节,具体如下:
VxWorks固件加载地址分析方法研究
对于上述情况,具体的情况是这样的:这个uboot程序由mips16e进行编译,16位程序会带有部分的32位指令,所以看起来就是有一些硬编码是两个字节,而有一些是四个字节。而MIPS16E汇编不再用于Linux程序中,或许已经被大部分厂商弃用了:
VxWorks固件加载地址分析方法研究
在CPU中,会把16位指令翻译32位来执行:
VxWorks固件加载地址分析方法研究
或者可以参考以下链接内容:
http://t.zoukankan.com/ldxsuanfa-p-10557851.html
https://gcc.gnu.org/onlinedocs/gcc/MIPS-Function-Attributes.html
虽然说不影响逆向分析,但由于这种情况的存在,capstone和unicorn是无法反编译和模拟的,就意味着uboot无法进行仿真,这也为后面的仿真调试工作带来困难。
此外以下介绍一种手动识别VxWorks固件加载地址的方法,并将上面的内容进行总结,编写出自动化脚本。

VxWorks固件加载地址分析方法研究
通过上面的一些特征字符串识别加载地址的方法以外,如果在固件中找不到上述的任何的特征字符串,那么就需要我们手动逆向分析,这里介绍一种比较通用的方法,主要针对于VxWorks系统,而非uboot。
此方法主要是通过MIPS汇编特性:当调用函数时通过gp指针来间接寻址,也就是通过一张全局的函数表来寻址。那么只要找到这个全局的表,然后在IDA中将其转换为四字节的值,即可得到一些特征,并且由于RTOS这类固件无法地址随机化的特性,可以直接猜测得到加载地址。具体过程如下:
  1. binwalk解包固件,得到VxWorks系统

  2. 把文件放入到IDA中,按照binwalk -Y结果设置IDA的CPU等参数,加载过程中IDA会要求输入固件加载地址,可以暂时忽略掉,直接加载即可

  3. 加载完成后,在开始位置处按c键转换为代码(这时候IDA可能会自动识别到一些其他函数),如下例:

    VxWorks固件加载地址分析方法研究

  4. 分析上图汇编代码,得到一条指令:li $gp, 0x802DD1C0,这就是向gp指针赋值的语句,从这条指令我们得到一个结论:也就是说全局的这张函数表,它的地址大于0x80000000

  5. 然后通过对MIPS程序分析的一些经验,在整个系统中找到全局表大概的位置,找到一些类似表的数据:

    VxWorks固件加载地址分析方法研究

  6. 然后把这些数据转换为四字节数据:

    VxWorks固件加载地址分析方法研究

  7. 有了这些数据之后就更加确定加载地址大于0x80000000。这里给出一些MIPS架构的VxWorks常用的加载基址,这是我尝试多个样例后得出的结论,一般MIPS架构的加载地址会大于0x80000000,而ARM架构的一般会大于0x40000000。MIPS架构常用的加载地址一般有:0x80001000、0x80010000、0x80008000、0x80200000;ARM架构常用的加载地址为 0x40205000。

  8. 经过尝试过MIPS架构的一些加载地址后,当前固件的加载地址是0x80010000,然后在IDA加载时进行设置,最后再去找到全局表,依次再转换为四字节数据,这时候就可以看到IDA会自动识别函数了:

    VxWorks固件加载地址分析方法研究

  9. 那么如何判断是否是正确的加载地址呢?可以通过字符串引用地址的方式来检查加载地址是否正确,如果设置了正确的加载地址,在字符串引用时,引用的指针所指向的字符串是完整的。如果是错误的加载地址,那么指针指向的字符串可能会指向某个字符串的中间位置,并不是完整的字符串。也可以在某些函数中查看函数逻辑是否通顺,最好的就是找到一些格式化字符串,然后查看其参数位置是否对应得上,如下例:

    VxWorks固件加载地址分析方法研究

当然,如果自己通过gp指针推算出加载地址的取值区间,也可以按照0x1000的内存对齐去一个一个地址尝试,这算是一种比较笨但很通用的加载地址分析方法,只要将全局函数表恢复出来,离修复成功也就不远了。

VxWorks固件加载地址分析方法研究
由以上文件格式总结出固件加载地址的分析方法(由于固件厂商只有水星和TP-Link,并且只有路由器设备,没有其他类型如机器人、机械臂等固件,所以得出的结论局限性比较大):
  1. 通过搜索“MyFirmware”、“img addr”等字符串来定位加载地址,具体分析方法可以参考之前的描述

  2. 如果无法找到关键字符串,那么就需要分析uboot文件。在整个固件中查找uboot信息,比如“u-boot image”等字符串获取uboot加载地址,然后通过逆向分析uboot文件得到VxWorks加载地址

  3. 通用的人工分析方法,通过分析VxWorks入口的gp指针定位全局函数表位置,搭配使用字符串定位的方法来分析固件加载地址

VxWorks固件加载地址分析方法研究
为了批量识别VxWorks固件的加载地址,这里写了一个识别固件加载地址的demo代码。为了快速的编写出工具,只为了实现功能,不考虑执行速度和其他因素,决定使用python作为开发语言。总结上述的加载地址分析方法,编写代码如下:
import re,sys,os
import struct
import json

def u32(data):
    '''Big endian'''
    return struct.unpack(">I",data)[0]

def read_file(path):
    fd = open(path,"rb")
    if(not fd):
        print('[-] Open target firmware failed!')
        exit(-1)
    content = fd.read()
    return content

def find_img_addr(path):
    content = read_file(path)
    img_addr_str_offset = content.find(b"img addr")
    if img_addr_str_offset == -1:
        # print('[-] Can't find "img addr" string!')
        return 0
    addr_str_offset = img_addr_str_offset + len('img addr: ')
    count = addr_str_offset

    while 1:
        if content[count] == 0:
            break
        elif content[count] == 10:
            break
        else:
            count+=1
    addr = content[addr_str_offset: count]
    if addr:
        # print(hex(eval(addr)))
        return eval(addr)
    else:
        print('[-] Find address failed!')
        return 0

def find_myfirmware_addr(path):
    content = read_file(path)
    myfirmware_str_offset = content.find(b"MyFirmware")
    if myfirmware_str_offset == -1:
        # print('[-] Can't find "MyFirmware" string!')
        return 0
    addr_str_offset = myfirmware_str_offset - 0xc0 + 0x18
    addr = u32(content[addr_str_offset: addr_str_offset + 4])
    if addr:
        # print(hex(addr))
        return addr
    else:
        print('[-] Find address failed!')
        return 0

def find_u_boot_image_addr(path):
    content = read_file(path)
    u_boot_image_addr = content.find(b"u-boot image")
    if u_boot_image_addr == -1:
        return 0
    addr_str_offset = u_boot_image_addr - 0x10
    addr = u32(content[addr_str_offset: addr_str_offset + 4])
    if addr:
        return addr
    else:
        print('[-] Find address failed!')
        return 0


def main():
    dirpath = "/path/to/vxworks/"

    result = {
        "file""",
        "myfirmware_str_addr""",
        "img_addr_addr""",
        "uboot_addr""",
    }

    filelist = os.listdir(dirpath)
    for i in filelist:
        filename = os.path.join(dirpath,i)
        result["file"] = i
        result["img_addr_addr"] = hex(find_img_addr(filename))
        result["myfirmware_str_addr"] = hex(find_myfirmware_addr(filename))
        result["uboot_addr"] = hex(find_u_boot_image_addr(filename))

        r = json.dumps(result)
        print(r)


if __name__ == "__main__":
    main()
运行结果如下图,如果没有找到VxWorks固件加载地址,那么可以使用uboot加载地址然后去逆向分析uboot,这样看来基本上都找到了可入手点:
VxWorks固件加载地址分析方法研究从上面结果来看,仍有一个固件无法通过上述方法找到任何特征,通过人工分析也是没有找到任何的可分析的特征,对此我们也在持续研究中。

原文始发于微信公众号(山石网科安全技术研究院):VxWorks固件加载地址分析方法研究

版权声明:admin 发表于 2022年12月16日 上午11:05。
转载请注明:VxWorks固件加载地址分析方法研究 | CTF导航

相关文章

暂无评论

暂无评论...