TP-LINK VxWorks firmware analysis

Table of Contents

Next: , Up: (dir)   [Contents]

TP-LINK VxWorks firmware analysis

This manual is for program, version version.


Next: , Previous: , Up: Top   [Contents]

1 Author

Author: XiaoLan Lee

Blog: http://blog.leexiaolan.tk/


Next: , Previous: , Up: Top   [Contents]

2 Analysis 1

TP-LINK VxWorks系统路由器固件升级文件格式(一)

前段时间,TL-R478型号的路由器固件升级文件引起了我的注意,生产商是TP-LINK。其固件升级文件格式,与该生产商其它产品已知的格式完全不同。

使用二进制编辑器打开固件,在文件偏移0x5c处,有这样的一段文字:

fwup-ptn hw-info base 0x01000 size 0x00105
fwup-ptn product-info base 0x01105 size 0x0021d
fwup-ptn partition-table base 0x00800 size 0x00800
fwup-ptn default-config base 0x01322 size 0x00968
fwup-ptn profile base 0x01c8a size 0x006da
fwup-ptn soft-version base 0x02364 size 0x00015
fwup-ptn os-image base 0x02379 size 0x152901
fwup-ptn web-res base 0x154c7a size 0x3f4d2
fwup-ptn appdist-db base 0x19414c size 0x00da4

Google "fwup-ptn",没有找到有用信息。

看来只能自行分析。不难发现,每一行都以"\t\r\n"分割,整段文字以"\x00"结束。仔细观察后可以发现, 每行都由六个空格分割的字符串构成,我们可以看成三个键值对。当我们按每行base的值从大到小排列, 可以得出这样的结论:每行的base值加上size值,就等于下一行的base值。于是可以猜测,上面文字每行代表一个分区(partition), fwup-ptn的值是分区的名字,base代表分区内容在文件中的偏移量,size由字面意思即知是大小的意思。简单计算就可以得知base所代表的偏移量是从上面这段文字开始计算的。 于是,很容易写出一个程序,抽取出固件升级文件中的各个分区的内容,源程序在此,其中的unpack.py就是此用途。

unpack.py TL-WVR300v1.bin

执行后,将在当前目录下,生成按分区名命名的文件。os-image和web-res将会是后面我们感兴趣的分区文件。

通过在固件文件中发现的字符串“VxWorks5.5.1”,猜测其系统为VxWorks。另外,发现TL-WVR300也是使用这样的固件升级文件格式。

对偏移从0x00-0x5c的文件头,将在后续的文章中进行分析。


Next: , Previous: , Up: Top   [Contents]

3 Analysis 2

TP-LINK VxWorks系统路由器固件升级文件格式(二)

在TP-LINK VxWorks系统路由器固件升级文件格式(一)中,我们从固件升级文件中提取出了各个分区文件, 其中包含名为web-res的分区文件,不过其中的内容还是无法理解。所以这次将对web-res分区文件内容深入探究。

首先使用binwalk 对其进行扫描,没有发现任何文件特征,binwalk无能为力。

01 tplink-vxworks-based-firmware.git$ ls -l web-res
02 -rw------- 1 root root 522361 Jan 26 13:54 web-res
03 tplink-vxworks-based-firmware.git$ hd web-res | head
04 00000000  00 07 f8 70 00 00 00 00  00 5d 00 80 00 00 91 36  |...p.....].....6|
05 00000010  30 00 00 00 00 00 00 27  95 e8 30 10 07 00 06 10  |0......'..0.....|
06 00000020  58 12 85 15 4c b3 bb a9  7a f1 0e 92 a9 25 17 fb  |X...L...z....%..|
07 00000030  3a 61 ca cb e0 47 a2 fa  e0 20 29 c1 73 1c 6c 47  |:a...G... ).s.lG|
08 00000040  e9 58 aa 08 cf 7b a7 b9  df 45 e3 31 a9 9e c5 85  |.X...{...E.1....|
09 00000050  98 99 2d 7c de 3b be 5e  dd 0d 74 54 c1 63 5c 06  |..-|.;.^..tT.c\.|
10 00000060  34 49 3f 0e 23 84 9d 6c  0e 13 47 c5 10 12 e1 57  |4I?.#..l..G....W|
11 00000070  23 bc 9d cc 0d de d6 41  4a 65 b6 a7 f8 d5 54 af  |#......AJe....T.|
12 00000080  1f 76 35 9d 4b 00 cc d4  fb 42 18 2c 6a 96 2d e5  |.v5.K....B.,j.-.|
13 00000090  08 59 1f bb cc 71 fa 26  cb d0 d8 86 88 6e af 8c  |.Y...q.&.....n..|

仔细观察后发现,如果将文件的头四个字节当做大端的32位整型来看,0x0007f870 == 522352,同文件大小522361相比,仅仅相差9。 于是可以假定其值代表后面数据的大小,文件由9个字节的文件头和实际数据组成。但后面数据的格式还是未知。

从名字web-res来看,里面存储的内容应该是一些web相关的资源文件,html,css和js等,这些文件都是纯文本。 但数据完全看不出任何合理的字符串,估计是被加密或压缩过的。加密的可能性一般不大,可以先考虑压缩。 数据的第一个字节0x5D看起来像默认属性的lzma压缩流,用lzma解压来碰碰运气吧。

01 tplink-vxworks-based-firmware.git$ dd if=web-res of=web-res.dat.lzma bs=1 skip=9
02 522352+0 records in
03 522352+0 records out
04 522352 bytes (522 kB) copied, 0.901332 s, 580 kB/s
05 tplink-vxworks-based-firmware.git$ lzmainfo web-res.dat.lzma
06
07 web-res.dat.lzma
08 Uncompressed size:             3 MB (3159697 bytes)
09 Dictionary size:               0 MB (2^15 bytes)
10 Literal context bits (lc):     3
11 Literal pos bits (lp):         0
12 Number of pos bits (pb):       2
13
14 tplink-vxworks-based-firmware.git$ lzma -d -v web-res.dat.lzma
15 web-res.dat.lzma (1/1)
16  99.9 %     510.1 KiB / 3,085.6 KiB = 0.165
17 lzma: web-res.dat.lzma: Compressed data is corrupt
18 99.9 %     510.1 KiB / 3,085.6 KiB = 0.165

尝试用lzma命令行工具解压失败了,但看起来99.9%的数据已经解压成功了,深入检查后发现, 原来是正常的压缩数据后,还有部分尾数据,不属于lzma压缩流,所以导致lzma命令失败。 知道了原因就很容易解决啦。使用python的lzma包来解压并忽略尾数据,成功解压。 源代码decompress-web-res.py

01 tplink-vxworks-based-firmware.git$ ./decompress-web-res.py web-res
02 Decompressed data length: 3159697
03 Unused data length: 1
04 Decompressed data saved to "web-res.decompressed".
05 tplink-vxworks-based-firmware.git$ hd web-res.decompressed | head
06 00000000  4f 57 4f 57 4f 57 4f 57  4f 57 4f 57 4f 57 4f 57  |OWOWOWOWOWOWOWOW|
07 *
08 00000020  00 00 00 01 00 00 00 f8  2f 72 63 5f 66 69 6c 65  |......../rc_file|
09 00000030  73 79 73 2f 64 6f 63 2f  64 79 6e 61 66 6f 72 6d  |sys/doc/dynaform|
10 00000040  2f 63 6f 6d 6d 6f 6e 2e  6a 73 00 00 00 00 00 00  |/common.js......|
11 00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
12 00000060  00 00 00 00 00 00 00 00  00 00 bb 07 00 00 49 c8  |..............I.|
13 00000070  00 00 00 00 2f 72 63 5f  66 69 6c 65 73 79 73 2f  |..../rc_filesys/|
14 00000080  64 6f 63 2f 64 79 6e 61  66 6f 72 6d 2f 6d 61 69  |doc/dynaform/mai|
15 00000090  6e 2e 63 73 73 00 00 00  00 00 00 00 00 00 00 00  |n.css...........|

解压后的文件中,已经可以看到有意义的字符串了。 开头部分的字符串看起来像是文件名,如何从中提取这些文件呢?文件开头的“OWOWOWOW...” 像是magic string,google之,发现这篇文章Mystery File System和其UPDATE #3中引用的Solving a Little Mystery。 综合上述两篇文章中的信息, web-res.decompressed中的文件系统(姑且称之为ow2fs,相对Mystery File System中的owfs而言)的header和entry可以如此定义(大端字节序):

01  struct ow2fs_header{
02   char magic[32];          // 'OWOWOWOWOW...'
03   uint32_t unknown;
04   uint32_t file_count;
05 }
06  struct ow2fs_entry{
07   char file_path[0x40];
08   uint32_t file_size;
09   uint32_t file_offset;
10   uint32_t unknown;
11
   }

ow2fs与owfs相比,有如下差异:

magic字符串为大写“OW”,而非小写。
header少了一个uint32_t的字段。
entry中的文件名变成了文件路径,可能包含文件夹,最大长度变大成64。
entry中多了一个uint32_t的未知字段,其值均为0。

知道了ow2fs的结构,我们就可以写段程序,提取其中的所有文件了, 源代码unow2fs

01 tplink-vxworks-based-firmware.git$ ./unow2fs.py web-res.decompressed out | head
02 Extract 248 files into out...
03 /rc_filesys/doc/dynaform/common.js (47879) at 0x49c8...Successful
04 /rc_filesys/doc/dynaform/main.css (25599) at 0x104d0...Successful
05 /rc_filesys/doc/dynaform/menu.js (13551) at 0x168d0...Successful
06 /rc_filesys/doc/dynaform/extra.js (57444) at 0x19dc0...Successful
07 /rc_filesys/doc/frames/product.htm (881) at 0x27e24...Successful
08 /rc_filesys/doc/localization/str_menu.js (2238) at 0x28198...Successful
09 /rc_filesys/doc/localization/char_set.js (0) at 0x28a58...Successful
10 /rc_filesys/doc/dynaform/tab.js (15495) at 0x28a58...Successful
11 /rc_filesys/doc/dynaform/bwCtrlVerify.js (59979) at 0x2c6e0...Successful

查看这些提取的js,css和htm文件,都完好无损。至此,web-res的结构我们已经完全分析清楚了。


Previous: , Up: Top   [Contents]

4 Analysis 3