Back

[IoT] 基于VxWorks的TP-Link路由器固件的通用解压与修复思路

0x00 固件来源

宿舍有台自用的TP-LINK TL-WDR7660,搭载的是VxWorks(一种RTOS),和一般品牌路由器固件差别挺大的

但是搜索了一圈发现不管是新版还是旧版固件解压和提取都有一定的套路,只不过网上大部分分析都是基于某个特例(说白了都是互抄)

  1. 先去官网下个最新版固件:

2021-08-31T04:27:55.png

  1. 下完解压会得到类似这样一个bin文件:

2021-08-31T04:29:16.png

0x01 固件提取

binwalk分析

直接binwalk跑一下会得到类似以下结果:

➜  TL-WDR7660 binwalk wdr7660gv1.bin

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
512           0x200           uImage header, header size: 64 bytes, header CRC: 0xDEFB3DA, created: 2018-09-05 07:32:57, image size: 48928 bytes, Data Address: 0x41C00000, Entry Point: 0x41C00000, data CRC: 0x2A36A3AD, OS: Firmware, CPU: ARM, image type: Standalone Program, compression type: lzma, image name: "U-Boot 2014.04-rc1-gdbb6e75-dirt]"
576           0x240           LZMA compressed data, properties: 0x5D, dictionary size: 67108864 bytes, uncompressed size: -1 bytes
66560         0x10400         LZMA compressed data, properties: 0x6E, dictionary size: 8388608 bytes, uncompressed size: 3869672 bytes
1422400       0x15B440        LZMA compressed data, properties: 0x5A, dictionary size: 8388608 bytes, uncompressed size: 7170 bytes
1423926       0x15BA36        LZMA compressed data, properties: 0x5A, dictionary size: 8388608 bytes, uncompressed size: 200 bytes
1424153       0x15BB19        LZMA compressed data, properties: 0x5A, dictionary size: 8388608 bytes, uncompressed size: 240 bytes
1424474       0x15BC5A        LZMA compressed data, properties: 0x5A, dictionary size: 8388608 bytes, uncompressed size: 14388 bytes
1426445       0x15C40D        LZMA compressed data, properties: 0x5A, dictionary size: 8388608 bytes, uncompressed size: 493 bytes
1426896       0x15C5D0        LZMA compressed data, properties: 0x5A, dictionary size: 8388608 bytes, uncompressed size: 2823 bytes
1428410       0x15CBBA        LZMA compressed data, properties: 0x5A, dictionary size: 8388608 bytes, uncompressed size: 334633 bytes
1532705       0x176321        LZMA compressed data, properties: 0x5A, dictionary size: 8388608 bytes, uncompressed size: 1111 bytes
1533610       0x1766AA        LZMA compressed data, properties: 0x5A, dictionary size: 8388608 bytes, uncompressed size: 1356 bytes
1534009       0x176839        LZMA compressed data, properties: 0x5A, dictionary size: 8388608 bytes, uncompressed size: 384 bytes
1534244       0x176924        LZMA compressed data, properties: 0x5A, dictionary size: 8388608 bytes, uncompressed size: 1535 bytes
1534616       0x176A98        LZMA compressed data, properties: 0x5A, dictionary size: 8388608 bytes, uncompressed size: 607 bytes
 
此处省略n行...

可以看到基本是由一个uImage header和一堆LZMA格式压缩的数据所得,从uImage header解析结果不难看出这是ARM架构的设备。同时可以看到一个Entry Point: 0x41C00000,但是要注意一下,这个Entry Point通常来说是uBoot程序的入口,而不是我们要分析的主程序的入口!

提取uBoot

notice: 一般不对其做进一步分析,只演示如何提取

uBoot通常由uImage header和紧随其后的一块LZMA compressed data组成,先要将他们提取出来:

skip为起始位置,count为总大小(uImage header+LZMA compressed data = 66560 - 512
dd if=wdr7660gv1.bin of=uboot.raw bs=1 skip=512 count=66048
➜  test binwalk uboot.raw 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             uImage header, header size: 64 bytes, header CRC: 0xDEFB3DA, created: 2018-09-05 07:32:57, image size: 48928 bytes, Data Address: 0x41C00000, Entry Point: 0x41C00000, data CRC: 0x2A36A3AD, OS: Firmware, CPU: ARM, image type: Standalone Program, compression type: lzma, image name: "U-Boot 2014.04-rc1-gdbb6e75-dirt]"
64            0x40            LZMA compressed data, properties: 0x5D, dictionary size: 67108864 bytes, uncompressed size: -1 bytes

由于缺少符号等原因这里大小才64K左右,暂时不分析

提取主程序

0x10400偏移也就是uImage header之后的第二块LZMA compressed data的位置存放了1.3M左右特别大的数据,一般来说这也是主程序所在,将其用同样的方法提取出来...

一开始我用的是:

count = 1422400 - 66560
dd if=wdr7660gv1.bin of=data_0x10400.lzma bs=1 skip=66560 count=1355840

但是在解压的时候提示压缩数据已损坏,初步判断可能是文件尾部的位置不正确,尝试用16进制编辑器打开手动定位到文件结束的位置:

2021-08-31T05:40:48.png

感觉确实不太对,可能是超了,于是一直上溯到这:

2021-08-31T05:42:10.png

感觉这里才是真正的文件尾部,于是将count修改为0x15a477 - 66560大小后再次提取:

dd if=wdr7660gv1.bin of=data_0x10400.lzma bs=1 skip=66560 count=1351799

提取完毕尝试解压:

lzma -d ./data_0x10400.lzma

得到data_0x10400二进制文件再丢进binwalk分析一下:

➜  test binwalk ./data_0x10400 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
91549         0x1659D         Certificate in DER format (x509 v3), header length: 4, sequence length: 5384
484797        0x765BD         PARity archive data - file number 24064
676065        0xA50E1         Certificate in DER format (x509 v3), header length: 4, sequence length: 1280
727381        0xB1955         Certificate in DER format (x509 v3), header length: 4, sequence length: 1280
1142637       0x116F6D        Certificate in DER format (x509 v3), header length: 4, sequence length: 5404
1685641       0x19B889        Certificate in DER format (x509 v3), header length: 4, sequence length: 5520
2051245       0x1F4CAD        Certificate in DER format (x509 v3), header length: 4, sequence length: 4640
2315613       0x23555D        Certificate in DER format (x509 v3), header length: 4, sequence length: 4640
2728393       0x29A1C9        Certificate in DER format (x509 v3), header length: 4, sequence length: 1280
3093449       0x2F33C9        Certificate in DER format (x509 v3), header length: 4, sequence length: 5588
3150693       0x301365        Certificate in DER format (x509 v3), header length: 4, sequence length: 1284
3152765       0x301B7D        Certificate in DER format (x509 v3), header length: 4, sequence length: 1300
3174925       0x30720D        Copyright string: "Copyright (c) 1983, 1988, 1993"
3178484       0x307FF4        Copyright string: "Copyright 1984-2002 Wind River Systems, Inc."
3178712       0x3080D8        VxWorks operating system version "5.5.1" , compiled: "Aug 30 2019, 10:21:15"
3242522       0x317A1A        Neighborly text, "NeighborReq)ble; 1:reload with bs enable)"
3243000       0x317BF8        Neighborly text, "NeighborReq ignore. dbglvl to Default."
3249474       0x319542        Neighborly text, "neighbor[%d]: %d"
3249603       0x3195C3        Neighborly text, "neighbor info of %02X:%02X:%02X:%02X:%02X:%02X"
3406512       0x33FAB0        Neighborly text, "neighbor report frameort response is meaningless"
3406563       0x33FAE3        Neighborly text, "neighbor report response is meaninglessd "
3406733       0x33FB8D        Neighborly text, "neighbor report frame failed:%02x) not support rrm"
3409384       0x3405E8        Neighborly text, "Neighbor RSP"
3475008       0x350640        Neighborly text, "Neighbor Response Frame/common/wss.c:%d assert binfo->apchanrpt_opclass_num <= IEEE80211_RRM_NUM_CHANRPT_MAXfailed"
3536416       0x35F620        Unix path: /etc/wireless/mediatek/MT7626_EEPROM.bin
3566196       0x366A74        Copyright string: "Copyright(C) 2001-2011 by TP-LINK TECHNOLOGIES CO., LTD."
3592779       0x36D24B        Neighborly text, "neighbor(%s)fault router list contains a non-linklocal address(%s)"
3610436       0x371744        VxWorks WIND kernel version "2.6"
3648445       0x37ABBD        StuffIt Deluxe Segment (data): fDomain
3648461       0x37ABCD        StuffIt Deluxe Segment (data): fPort
3665020       0x37EC7C        HTML document header
3665085       0x37ECBD        HTML document footer
3688396       0x3847CC        PEM certificate
3688452       0x384804        PEM RSA private key
3698052       0x386D84        Base64 standard index table
3707967       0x38943F        Neighborly text, "NeighborReq"
3728344       0x38E3D8        SHA256 hash constants, little endian
3745540       0x392704        Neighborly text, "NeighborReqSanityureReq"
3745715       0x3927B3        Neighborly text, "NeighborReph"
3765236       0x3973F4        XML document, version: "1.0"
3784928       0x39C0E0        SHA256 hash constants, little endian
3796045       0x39EC4D        StuffIt Deluxe Segment (data): f
3796076       0x39EC6C        StuffIt Deluxe Segment (data): fError
3796157       0x39ECBD        StuffIt Deluxe Segment (data): f
3823380       0x3A5714        XML document, version: "1.0"
3829860       0x3A7064        Unix path: /etc/Wireless/RT2860/RT2860_2G.dat
3847576       0x3AB598        CRC32 polynomial table, little endian

进一步确定了的这是ARM小端序的架构

确定主程序入口

这一步在研究的时候花了不少功夫,对比了几个不同型号旧固件之后总结出入口地址存放的大致规律:

  1. 首先从主程序偏移往前找,在这个例子里面就是0x10400往前
  2. 在这个范围内搜索如下字符串:MyFirmware
  3. 从字符串的偏移往上一点会发现两个重复的地址,猜测这就是主程序的加载地址和入口

2021-08-31T05:58:44.png

  1. 接下来在IDA中测试一下这个地址是否正确,如果地址正确的话识别出来的函数数量会比较多,大概有几千个(注意有些地方由于执行流的原因没有被识别成函数,需要手动设置一下)

2021-08-31T06:02:20.png

2021-08-31T06:03:48.png

2021-08-31T06:07:15.png

0x02 符号修复

外部符号文件

此时主程序的函数列表还是一堆sub_xxx,得先找到外部符号存放的位置

  1. 先用binwalk -e将固件中所有的文件全部提取出来
  2. 尝试在在提取出来的目录中搜索包含关键函数名的文件(这里bzero是一个常用函数)
➜  _wdr7660gv1-cn-up_2019-08-30_10.37.02.bin.extracted grep -r bzero .
匹配到二进制文件 ./15CBBA
  1. 尝试16进制下打开这个文件观察结构

前面是一堆8字节的符号信息:

2021-08-31T06:24:51.png

后面是符号对应的字符串:

2021-08-31T06:27:19.png

脚本提取修复

网上找到了现成的脚本(python2的,可以考虑移植一下?),效果还不错,注意修改以下几个变量:

symfile_path: 刚刚搜索到的符号文件的路径

symbols_table_start : 符号表起始偏移,从16进制编辑器看出来是8(前8字节作用不清楚,也不重要)

strings_table_start : 字符串起始偏移,也是从16进制编辑器看出来

import idautils
import idc
import idaapi

symfile_path = './sym_table'    
symbols_table_start = 8
strings_table_start = 0x1a728

with open(symfile_path, 'rb') as f:
    symfile_contents = f.read()

symbols_table = symfile_contents[symbols_table_start:strings_table_start]
strings_table = symfile_contents[strings_table_start:]

def get_string_by_offset(offset):
    index = 0
    while True:
        if strings_table[offset+index] != '\x00':
            index += 1
        else:
            break
    return strings_table[offset:offset+index]


def get_symbols_metadata():
    symbols = []
    for offset in xrange(0, len(symbols_table),8):
        symbol_item = symbols_table[offset:offset+8]
        flag = symbol_item[0]
        string_offset = int(symbol_item[1:4].encode('hex'), 16)
        string_name = get_string_by_offset(string_offset)
        target_address = int(symbol_item[-4:].encode('hex'), 16)
        symbols.append((flag, string_name, target_address))
    return symbols


def add_symbols(symbols_meta_data):
    for flag, string_name, target_address in symbols_meta_data:
        idc.MakeName(target_address, string_name)
        if flag == '\x54':
            idc.MakeCode(target_address)
            idc.MakeFunction(target_address)


if __name__ == "__main__":
    symbols_metadata = get_symbols_metadata()
    add_symbols(symbols_metadata)

修复完大概就是下面这个效果:

2021-08-31T06:32:02.png

0x03 后话

实测不同型号固件虽然修复效果不尽相同,但大致思路都是一样的,欢迎纠正和改进

Submit