tl-wdr5620固件提取

tl-wdr5620固件提取

最近分析了一下tl-wdr5620的路由器,该路由器使用的是VxWorks系统,目前很多设备上都使用的是这种系统。然而针对该系统上可能出现什么类型的漏洞,以及怎么利用发现的漏洞,目前都很少有深入分析的文章。而且凡是使用了该系统的设备,基本上都没有爆出来过漏洞,包括我了解的某些tplink路由器、水星路由器等。所以在整个过程中感觉收获最大的也就是固件的提取了,记录一下。

路由器信息

固件版本:2.5.76

硬件版本:v4.0

在官网上没有找到该版本对应的固件,目前官网上只有v3版本及以前的固件。先下载3.0的看一下固件长什么样!

binwalk一下发现是一堆压缩的数据:

这是典型的VxWorks固件,整个固件是一个可执行的程序,包含了程序运行所需要个各种图标、html文件等各种资源。

一般提取固件主要有三种方式:

  • 从官网下载升级包
  • 通过串口调试获取shell,通过dump内存或是ftp等方式获取固件
  • 直接从flash里面读固件

一般在官网无法下载固件的情况下,会先尝试从串口中获取固件,因为这种方式不会对电路板造成什么损害,最多只需要通过焊接把针脚固定在调试口处。而如果通过串口也无法获取固件的情况下,就比如之前分析的小米路由器,串口只能读数据不能发数据,就只能通过flash来直接提取固件了(然而小米路由器把flash等器件也给保护了起来)。

尝试从uart获取shell

拆开路由器看是否存在uart调试口

存在四个并排的小孔,猜测就是uart口,首先确定这四个口对应对应的是vcc/nx/rx/gnd中的哪一个。

通过通断性测试,发现从上往下数第二个口为GND口。具体怎么做的呢?(这在上一篇小米路由器从开始到放弃中也有提到过)
将黑色表笔接到IoT设备板子上的电路接口上固定不动,红色表笔依次在四个接口上进行通断测试,如果万用表有蜂鸣声,我们就可以判断此接口是GND接口。使用万用表,将黑色的线插入COM口,红色的线插入V/Ω口,调整旋钮到测量二极管通断处。接着把黑色指针放到供电口,也就是供电线与路由器相接的地方,把红色指针放到从上往下的第二个口。这时发现万用表发出了蜂鸣声,放到其他的口却没有,说明第二个口就是GND

已经知道gnd口后,接下来还需要确定tx、rx以及vcc的位置

在通电的情况下短接任意两个口,如果机器重启,那么可以确定这两个口一个是vcc一个是gnd,这里已知了gnd,所以只需要尝试三次即可确定那个口是vcc。通过实验发现从上往下数第一个口为vcc。

第三和第四个口就为tx、rx了,具体对应的是哪个现在还无法知道!如何确定呢?连上USB2TTL试一下即可,如果没有输出,调换一下RX/TX的顺序后有了输出,那么就可以确定RX和TX的位置了!

最终得到的顺序从上至下为:
VCC->GND->RX->TX

最后弄好的样子是这样:

获取固件

获取到shell之后,发现这不是平常遇见的linux下的shell,help一下发现够执行下面的一些命令

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
# help

------------------------------------------------------------------------
Version = 2.0.
Task name = tCmdTask, pri = 8, stack size = 10240, max command entry num = 64.

command description
--------------------------------------------------------------------
? print all commands.
help print all commands.
mem show memory part, pools, and dump memory.
task show task info and manage system task.
tftp download or upload files by tftp protocol.
ifconfig Interface config, please reference to -help command.
route Show route table, delete/add special route entry.
arp Show all arp entries, delete/add special arp entry.
net Show net runtimes.
track Show conntrack runtimes, modify conntrack environments.
register register show.
flash Print flash layout, read/write/erase specify flash.
fs display file system status.
port manage all udp/tcp packet ports.
packet Show cblk or mblk chain.
mcb Show mcb pools or blocks.
bridge Show all bridge stations.
nat Show nat runtimes.
cloudClient cloudClient request.
system reboot, reset or firmware.
devinfo show device info.
wioctl Wlan command line utility.
iwpriv MTK Wlan command line utility.
cloudBrd proxy to connect cloud server.
--------------------------------------------------------------------

flash的布局如下:

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
# flash -layout

Version: 2.0
Name: FlashIo
Total Size(K): 2048
Erase Sector Size(K): 4
Block Num: 2.
==================================================
Flash Layout:
|------------------------| 0x00000000(0.0K)
| |
| |
|FACUBOOT(60.0K) |
| |
| |
|------------------------| 0x0000f000(60.0K)
|PROFILE(2.0K) |
|------------------------| 0x0000f800(62.0K)
|RADIO(2.0K) |
|------------------------| 0x00010000(64.0K)
| |
| |
|CONFIG(20.0K) |
| |
| |
|------------------------| 0x00015000(84.0K)
|TPHEAD(0.5K) |
|------------------------| 0x00015200(84.5K)
| |
| |
|FIRMWARE(1963.5K) |
| |
| |
|------------------------| 0x00200000(2048.0K)

除了flash,我们还能执行mem命令,该命令可以dump内存信息:

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
# mem -show [sys | dada | object]
# mem -dump start size
# mem -md start value
#
# -show Displays alloced/free mempry blocks and size.
# -dump dump specify memory block.
# -md copy specify memory block to specify address.
#
# start Start address of specify memory block, in hex.
# size Size of specify memory block, in hex.
# value value in UINT32 format.
# object end object, e.g:eth 0.
#
# Example:
# mem -dump 80010000 1000 .... Show memory block start at 0x80010000 which size of 4k.
# mem -show eth 1 .... Show netpool of end object eth1.

# mem -dump 80001000 1000
80001000: 00 B0 0D 3C 64 00 AD 35 - 00 00 AE 8D 00 00 00 00 ...<d..5 ........
80001010: FC FF 01 3C FC FF 21 34 - 24 70 C1 01 01 00 01 3C ...<..!4 $p.....<
80001020: 25 70 C1 01 00 00 AE AD - 00 00 00 00 00 B0 0D 3C %p...... .......<
80001030: 00 06 AD 35 00 00 AE 8D - 00 00 00 00 00 C0 01 3C ...5.... .......<
80001040: 25 70 C1 01 00 00 AE AD - 00 00 00 00 00 B0 0D 3C %p...... .......<
80001050: 04 06 AD 35 00 00 AE 8D - 00 00 00 00 0F 00 CE 35 ...5.... .......5
80001060: 00 00 AE AD 00 00 00 00 - 00 B0 0D 3C 40 06 AD 35 ........ ...<@..5
80001070: 00 C0 0E 3C 00 00 AE AD - 00 00 00 00 00 B0 0D 3C ...<.... .......<
80001080: 44 06 AD 35 0F 00 0E 24 - 00 00 AE AD 00 00 00 00 D..5...$ ........

先利用脚本提取前0xf000大小的内存块试一试,因为在路由器启动的时候是从bc015200处开始执行的(这个在路由器启动时输出的信息里面可以看到),说明固件被加载到了0xbc015200处的位置,0x15200刚好对应到flash中的固件的起始地址,所以猜测整个flash应该是被加载到了0xbc000000处。

在dump的时候依次最多能dump的内存大小为0x4000,而固件的大小远比这个大得多,我们不可能手动的一次一次来输入dump命令吧。所以这里还有三个问题需要解决:

  • 如何保存dump指令输出的内容
  • 如何自动的输入dump命令
  • 如何将保存的内容转换为二进制固件

保存dump指令输出

我采用的是xshell连接的串口,在xshell中可以通过log的方式来记录终端输出的内容,只需要在文件-->属性中配置一下即可:

配置好了之后,每次连上串口的时候都会提示日志保存的文件名,所有终端上输出的内容都会记录到日志文件里面,这样就能保存dump指令的输出了!

自动输入dump指令

要dump的数据很多,我们不可能手动的输入所有的dump指令,有什么办法能自动输入呢?要是xshell能帮我们输入命令就好了!查了一下发现还真有,xshell支持运行我们写的脚本(包括python、VBScript等),对于python脚本中的xsh.Screen.Sendxsh.Screen.WaitForString分别可以用来向终端发送命令和等待终端特定的输出,perfect!

最后写好的自动dump内存的脚本如下(从xshell中运行):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import time
def Main():
start=0x0
length=0x200000-0x15200
base=0xbc000000+0x15200
flag=1
while flag:
if length > 0x4000:
shell = "mem -dump "+hex(base+start)+" "+hex(0x4000)
elif length > 0:
shell = "mem -dump "+hex(base+start)+" "+hex(length)
flag=0
else:
break
xsh.Screen.Send(shell+'\n')
start = start+0x4000
length=length-0x4000
xsh.Screen.WaitForString("\n# ")
time.sleep(0.5)

转换为二进制固件

固件信息现在是以字符的形式保存在日志文件中的,需要把它抓换成二进制文件,转换起来也很容易,因为输出的内存信息有固定的格式,直接写脚本转就行了。

转换的脚本如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
file = open("tl-wdr5620-0x15200-0x20000.log",'r')
file_o = open('tl-wdr5620-0x15200-0x20000.bin','wb')
line_start = len('BC000000: ')
line_end = line_start+len('FF 00 00 10 00 00 00 00 - FD 00 00 10 00 00 00 00')
while 1:
lines = file.readlines(10000)
if not lines:
break
for line in lines:
if line.startswith('BC'):
line = line[line_start:line_end]
line = line.replace(' - ',' ').split(' ')
tmp = ''
for i in line:
tmp += chr(int(i,16))
# print line
file_o.write(tmp)
else:
pass
file.close()
file_o.close()

转换后得到的bin文件经过binwalk识别后发现和v3版本的固件是一样的,全是压缩包。

参考链接

https://bbs.pediy.com/thread-230095.htm

https://www.secpulse.com/archives/75636.html

https://www.anquanke.com/vul/id/1454474

https://www.secpulse.com/archives/75635.html