[RTOS] 基于VxWorks的TP-Link路由器固件的通用解压与修复思路
0x00 固件来源
宿舍有台自用的TP-LINK TL-WDR7660
,搭载的是VxWorks(一种RTOS),和一般品牌路由器固件差别挺大的
但是搜索了一圈发现不管是新版还是旧版固件解压和提取都有一定的套路,只不过网上大部分分析比较倾向于某个特例
- 先去官网下个最新版固件:
- 下完解压会得到类似这样一个
bin
文件:
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进制编辑器打开手动定位到文件结束的位置:
感觉确实不太对,可能是超了,于是一直上溯到这:
感觉这里才是真正的文件尾部,于是将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小端序的架构
确定主程序入口
这一步在研究的时候花了不少功夫,对比了几个不同型号旧固件之后总结出入口地址存放的大致规律:
- 首先从主程序偏移往前找,在这个例子里面就是
0x10400
往前 - 在这个范围内搜索如下字符串:
MyFirmware
- 从字符串的偏移往上一点会发现两个重复的地址,猜测这就是主程序的加载地址和入口
- 接下来在IDA中测试一下这个地址是否正确,如果地址正确的话识别出来的函数数量会比较多,大概有几千个(注意有些地方由于执行流的原因没有被识别成函数,需要手动设置一下)
0x02 符号修复
外部符号文件
此时主程序的函数列表还是一堆sub_xxx
,得先找到外部符号存放的位置
- 先用
binwalk -e
将固件中所有的文件全部提取出来 - 尝试在在提取出来的目录中搜索包含关键函数名的文件(这里bzero是一个常用函数)
➜ _wdr7660gv1-cn-up_2019-08-30_10.37.02.bin.extracted grep -r bzero .
匹配到二进制文件 ./15CBBA
- 尝试16进制下打开这个文件观察结构
前面是一堆8字节的符号信息:
后面是符号对应的字符串:
脚本提取修复
网上找到了现成的脚本(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)
修复完大概就是下面这个效果:
0x03 后话
实测不同型号固件虽然修复效果不尽相同,但大致思路都是一样的,欢迎纠正和改进