0x00 TP-Link wr886nv7-V1.1.0 路由器分析 - 固件初步分析

固件提取

系统固件通常会以一定的格式封装在固件升级包中。为了提取系统固件可以先使用Binwalk对wr886nv7.bin进行初步分析。

从上图可以看到,除了一个比较明显的uImage header头以外这个升级包中还有一大堆的LZMA数据信息。通过使用binwalk或直接使用dd命令能够对升级包中的uimage镜像进行提取。

1
2
# count长度应该是header + lzma包的长度
dd if=wr886nv7.bin of=uboot_0x366C.img bs=1 skip=13932 count=20852

使用binwalk继续分析uboot的镜像,可以发现这个uboot镜像的Data部分使用了LZMA进行压缩。

解压uboot的LZMA数据

1
dd if=uboot_new.img of=uboot.lzma bs=1 skip=64

由于uboot image中缺乏符号表且大小只有52K不到,因此此处暂时不对其进行进一步的分析。

VxWorks 系统固件分析

在wr886nv7.bin中除了uImage外,还有一个2M多的有趣的lzma压缩数据。

从升级包中提取该0xA200压缩数据并解压。

1
2
3
4
# count 是0xc317e - 0xa200
dd if=wr886nv7.bin of=A200.lzma bs=1 skip=41472 count=757630
# 使用lzma进行解压
lzma -d A200.lzma

使用Binwalk查看解压出的数据后发现这个文件很可能就是路由器所运行的系统固件,VxWorks系统版本5.5.1.

固件加载地址分析

在对0xA200地址之前的一些数据进行分析后可以看到一个疑似uimage header的数据段,其中有两处地址指向了0x80001000,这个地址也和很多同类路由器设备的固件加载地址相同,因此我们可以尝试使用该地址作为固件加载地址进行分析。

使用IDA加载固件进行分析

在使用IDA加载前,首先我们需要确定CPU架构及加载地址。CPU架构信息已经在升级包中的uimage header中进行了定义,同时我们也可以使用binwalk -Y命令进行识别。

加载地址的话,在前面的分析中已经初步可以判定为0x80001000,下一步就是使用IDA对固件进行加载分析了。在IDA中加载VxWorks固件。

在IDA中填写固件加载地址信息0x80001000

成功加载固件后的IDA界面大致如下图所示,可以看到默认加载的情况下还是有大量的函数。

尝试修复函数名

通过初步对加载的VxWorks固件进行分析后发现,这个固件中并没有编入符号表,这对后续研究产生了很大的影响。再次对固件升级包中的内容进行分析后发现,有一个格式为data的文件很有趣。

1
2
3
4
5
# 使用binwalk -e 默认提取升级包中的文件。
binwalk -e wr886nv7.bin
# 进入目录后查找,是否存在包含VxWorks关键函数名的文件。
cd _wr886nv6-20180123.bin.extracted
grep -r bzero ./

在对C4FAB的内容进行查看后发现有惊喜,文件中保存的数据很像独立的符号表,其中找到了大量的VxWorks关键函数名。

在对这个文件进行了初步的分析后发现这个文件还是有很明显的特征的。

根据这个独立符号文件的特征,我们可以编写对应的ida_python脚本来对固件进行修复。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# coding=utf-8
import idc
import idaapi
import idautils
sym_file = open("PATH OF SYM FILE", 'rb').read()


table_data = sym_file[0x08:0x9f80]
print(table_data[-8:].encode('hex'))
string_table = sym_file[0x9f80:]


def get_string(offset):
string = ""
while True:
if string_table[offset] != '\x00':
string += string_table[offset]
offset += 1
else:
break
return string


def get_sym_data():
sym_data = []
for offset in range(0, len(table_data), 8):
table = table_data[offset: offset + 8]
flag = table[0]
# print('flag: %s' % flag)
string_offset = int(table[1:4].encode('hex'), 16)
# print('string_offset: %s' % string_offset)
string = get_string(string_offset)
# print('string: %s' % string)
target_address = int(table[-4:].encode('hex'), 16)
# print('target_address: %s' % hex(target_address))
sym_data.append([flag, string, target_address])

return sym_data

def fix_idb(sym_data):
for sym in sym_data:
flag, string, target_address = sym
idc.MakeName(target_address, string)
if flag == '\x54':
print("Start fix Function %s at %s" % (string, hex(target_address)))
idc.MakeCode(target_address)
idc.MakeFunction(target_address, idc.BADADDR)
# print('flag: %s' % flag)
# print('string: %s' % string)
# print('target_address: %s' % hex(target_address))




if __name__ == '__main__':
sym_data = get_sym_data()
fix_idb(sym_data)

脚本运行后固件看起来就清晰了很多。

至此,固件分析的预处理工作就成功完成了,此时固件中依然还有很多很函数无法被正确被识别,还需要后续的一些操作进行手动修复。基本修复后的IDA界面会像下图所示, 具体的修复方法将会在后续的文档中进行说明。

根据固件及设备分析后修复的segments信息