0x00 UART简介:
什么是UART?
通用异步接收器/发送器(Universal Asynchronous Receiver/Transmitter),通常称为UART,是一种广泛应用于嵌入式领域的串行、异步、全双工通信协议。
Uart在嵌入式开发中有很广泛的应用,其中之一就是开发板的固件烧录功能。
在常见的一些IOT设备中,它的作用主要是厂商预留的一些调试接口,一般可以通过跳线的方式连接这些uart串口,实现IOT设备的日志输出,固件升级降级等功能,有些还会开放根目录与u-boot的访问权限。
什么是u-boot
U-Boot是一款开源的引导加载程序(Bootloader),可用于嵌入式系统中。它由德国的DENX公司开发,支持多种处理器架构,包括ARM,MIPS,PowerPC等。
由于其对各种架构处理器的支持,可裁剪等特点,是目前嵌入式领域应用最广泛的boot loader。
u-boot支持使用命令行操作,一般来说,未裁剪的u-boot有很多操作flash与内存的指令,允许用户读取或者写入内存或Flash,此外,u-boot还支持通过tftp启动代码的功能。
本文中就用到了一些u-boot指令用来提取固件。
0x01 水星MW325R固件提取
用USB-TTL模块连接水星路由器上的uart接口,由于板子上的串口并没有标出哪个是TX,RX,GND,所以这里我们用万用表来根据电器特性找出哪个是TX,RX。
根据电气特性,GND和3V3都比较好判断,电压表调到Beep档,将电源的地引脚与其中的几个引脚相连,阻值为0或者导通状态即可判断GND,RX是接收引脚,对地电压为0,TX是输出引脚,对地电压 一般为3.3V,还有一个3V3引脚,这里就是通过经验判断了,通常,大部分厂商在设计uart原理图的时候,它的引脚分布一般都是
VCC 或者 VCC
TX GND
RX TX
GND RX
这样或者类似的顺序,TX和RX往往都是挨在一起,所以一般只要确定了GND和RX,就能试出来TX引脚是哪个。
最后我测出图中这里灰色是3V3,红色是GND,黄色是TX,橙色是RX
然后用下面这张图的方式将它和TTL模块上的引脚连接在一起。3V3可以不连
进入MobaXterm,选择Serial连接,串口号COM7,波特率57600,
上电之后查看串口的输出发现设备会自动跳转到u-boot命令行
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.
packet packet show.
flash Print flash layout, read/write/erase specify flash.
port manage all udp/tcp packet ports.
mcb Show mcb pools or blocks.
bridge Show all bridge stations.
nat Show nat runtimes.
system reboot, reset or firmware.
wioctl Wlan command line utility.
iwpriv MTK Wlan command line utility.
wpkt MTK WiFi Tx mem pool monitor utility.
--------------------------------------------------------------------
#
输入help,根据输出结果可以看到这是一个阉割版的u-boot命令行
如果有md命令,则可以用md显示出内存的数据从而提取出固件来
但是这里没有md命令,我们可以用flash 和mem -dunmp来获取固件
先用flash -layout
获取flash 表的偏移量,得到固件位置位于0xd000
到0xfa000
这一区间
# flash -layout
Version: 2.0
Name: FlashIo
Total Size(K): 1024
Erase Sector Size(K): 4
Block Num: 6.
==================================================
Flash Layout:
|------------------------| 0x00000000(0K)
| |
| |
|BOOTIMG(52K) |
| |
| |
|------------------------| 0x0000d000(52K)
| |
| |
|FIRMWARE(948K) |
| |
| |
|------------------------| 0x000fa000(1000K)
|CONFIG(8K) |
|------------------------| 0x000fc000(1008K)
|EXPLOG(8K) |
|------------------------| 0x000fe000(1016K)
|PROFILE(4K) |
|------------------------| 0x000ff000(1020K)
|RADIO(4K) |
|------------------------| 0x00100000(1024K)
#
然后再内存中找一片空白的区域作为缓冲区
mem -show
得到内存的排布表
# mem -show
FREE LIST:
num addr size
---- ---------- ----------
1 0x801f57a0 15984
2 0x807f3810 22304
3 0x807f3690 96
4 0x801fb980 48
5 0x8017a820 503344
6 0x806eb0c0 176
7 0x805d0520 16
8 0x8046eac0 80
9 0x801f56a0 80
SUMMARY:
status bytes blocks avg block max block
------ ---------- --------- ---------- ----------
current
free 542128 9 60236 503344
alloc 6296064 935 6733 -
cumulative
alloc 20584576 18500 1112 -
#
这里我们看到0x801f57a0这里有一个大小为15984 B的内存块,我们先用mem -dump查看这一块的内存内容。
# mem -dump 0x801f57a0 100
801F57A0: 50 57 1F 80 38 1F 00 80 - 18 38 7F 80 00 00 00 00 PW..8... ......
801F57B0: 00 00 00 00 00 01 00 00 - 00 00 00 00 00 00 00 00 ........ ........
801F57C0: C3 42 C3 83 F4 9B 14 80 - 00 FE 7F 80 00 00 00 00 .B...... ......
801F57D0: 00 00 00 00 00 C3 C3 C3 - 00 00 00 00 82 B2 C1 C3 ........ ........
801F57E0: C3 C3 5A CB F0 57 1F 80 - F8 57 1F 80 83 C3 C3 C3 ..Z..W.. .W......
801F57F0: C1 C2 C3 83 D3 93 C3 C2 - C3 C3 C3 C3 C3 CB D1 C1 ........ ........
801F5800: A0 57 1F 80 08 1F 00 00 - C3 C3 43 C3 4B C3 D3 C3 .W...... ..C.K...
801F5810: 77 6C 61 6E 49 6E 69 74 - 00 EE EE EE EE EE EE EE wlanInit ........
801F5820: EE EE EE EE EE EE EE EE - EE EE EE EE EE EE EE EE ........ ........
801F5830: EE EE EE EE EE EE EE EE - EE EE EE EE EE EE EE EE ........ ........
801F5840: EE EE EE EE EE EE EE EE - EE EE EE EE EE EE EE EE ........ ........
801F5850: EE EE EE EE EE EE EE EE - EE EE EE EE EE EE EE EE ........ ........
801F5860: EE EE EE EE EE EE EE EE - EE EE EE EE EE EE EE EE ........ ........
801F5870: EE EE EE EE EE EE EE EE - EE EE EE EE EE EE EE EE ........ ........
801F5880: EE EE EE EE EE EE EE EE - EE EE EE EE EE EE EE EE ........ ........
801F5890: EE EE EE EE EE EE EE EE - EE EE EE EE EE EE EE EE ........ ........
#
这里我们看到从0x801F57A0一直到0x801F5810都存有内容,但是从0x801F5820开始就没有了,为了防止我们覆盖掉的内容可能会影响到板子正常运行,我们可以用801F5820这个位置开始作为缓冲区
即缓冲区的地址为0x801f5820。
然后用flash -read 0xd000 3000 0x801f5820
将flash中的固件内容写入内存,这里一次只读取3000个字节的数据
这里flash命令的用法为
# flash -layout
# flash -erase off len
# flash -read off len buffer
# flash -write off len buffer
# off Offset of flash. #flash rom的位置
# len Length of specify flash segment. #需要读取或者写入的长度
# buffer Buffer write from or read to. #需要读取或写入的内存位置
执行flash -read 0xd000 3000 0x801f5820
拷贝到内存
再mem -dump 0x801f5820 3000
让串口打印3k的数据
这里我们先取100看看\
# mem -dump 0x801f5820 100
801F5820: 49 4D 47 30 00 0E D0 80 - 03 25 14 04 00 00 00 01 IMG0.... .%......
801F5830: 5A 02 02 04 00 00 00 00 - 00 00 00 00 00 00 00 00 Z....... ........
801F5840: 00 00 00 01 00 00 00 00 - 00 00 00 00 00 00 00 00 ........ ........
801F5850: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ........ ........
801F5860: 00 0A 30 00 00 04 95 AD - 00 00 00 00 00 00 00 00 ..0..... ........
801F5870: 00 00 00 80 00 0A 26 16 - 00 00 00 00 00 00 00 00 ......&. ........
801F5880: 00 0E D0 00 00 00 00 80 - 00 00 00 00 00 00 00 00 ........ ........
801F5890: 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ........ ........
801F58A0: 6E 00 00 80 00 A0 C9 14 - 00 00 00 00 00 00 00 2C n....... .......,
801F58B0: 00 F3 BC CB 28 1B 3F 3F - 89 C4 BF C5 EA A4 68 59 ....(.?? ......hY
801F58C0: AA F1 81 51 49 C9 AE 4F - 65 EE 47 2D 5F 98 E4 48 ...QI..O e.G-_..H
801F58D0: AC 1F 0C 1D 03 8A 2D 6C - F0 47 18 86 0F 78 D6 32 ......-l .G...x.2
801F58E0: 2A 7A 0C 5A ED B4 BA 17 - 81 12 BF A7 C3 CF 49 3F *z.Z.... ......I?
801F58F0: 67 86 A4 EC 46 A2 CF C0 - 67 6C 35 EE 33 B0 20 F9 g...F... gl5.3. .
801F5900: 69 AF 57 61 7E 85 B3 41 - 76 FA DA 71 74 18 66 68 i.Wa~..A v..qt.fh
801F5910: 96 A2 7E 5A C5 76 8D 6C - 41 F5 59 35 B0 48 D8 FE ..~Z.v.l A.Y5.H..
#
这里可以看到固件的开头IMG0,不过之前看到固件在flash中的大小为948KB,如果像我们这样一次3k 3k的读,至少要来回读取948K/3K = 316次才行,还得将读取的内容转换为16进制字节流格式写入文件,这显然人工提取速度实在太慢了。这里我们就编写一个Python脚本来读取。先用serial库创建一个Serial对象
serial.Serial(serial_port, baud_rate, timeout=2) as ser:
然后用while循环判断是否读取完毕,用ser.write(command)发送flash -read指令和mem -dump指令
再用ser.readline()接收mem -dump输出的字符流。
用re库创建一个对象来对字符串进行匹配,因为我们接收到的字符串是这个样子的
801F5910: 96 A2 7E 5A C5 76 8D 6C - 41 F5 59 35 B0 48 D8 FE ..~Z.v.l A.Y5.H..
所以对应的正则表达式为:
match = re.search('([0-9A-F]{2}\s*[0-9A-F]{2}\s*[0-9A-F]{2}\s*[0-9A-F]{2}\s*[0-9A-F]{2}\s*[0-9A-F]{2}\s*[0-9A-F]{2}\s*[0-9A-F]{2})\s*-\s*([0-9A-F]{2}\s*[0-9A-F]{2}\s*[0-9A-F]{2}\s*[0-9A-F]{2}\s*[0-9A-F]{2}\s*[0-9A-F]{2}\s*[0-9A-F]{2}\s*[0-9A-F]{2})', line)
这个正则表达式将实际内容匹配为两个部分,中间被“-”截断分隔开了,分别用match.group(1).replace(' ', '')和match.group(2).replace(' ', '')获取并且过滤掉空格,最后将它们以十六进制比特流的格式写入文件中,然后完成这一轮循环,current_address 增加chunk_size,再发送command和command2继续读取并且写入,直到读取到目标地址为结束。
以下是完整的py脚本:
import serial
import time
import re
def dump_memory_test(start_address, end_address, chunk_size, serial_port,
baud_rate, output_file,buffer):
with serial.Serial(serial_port, baud_rate, timeout=2) as ser:
with open(output_file, 'wb') as f:
current_address = start_address
while current_address < end_address:
#command = f"md {current_address:X} {chunk_size //4:X}\n".encode('utf-8')
command = f"flash -read {current_address:X} {chunk_size} {buffer}\n".encode('utf-8')
command2= f"mem -dump {buffer} {chunk_size}\n".encode('utf-8')
ser.write(command)
print(command)
time.sleep(1)
ser.write(command2)
print(command2)
time.sleep(0.1)
raw_output = b''
while True:
line = ser.readline().decode('utf-8').strip()
print(line)
if line == '':
break
#match = re.search(r'^[0-9a-fA-F]{8}: ([0-9a-fA-F]{8} - [0-9a-fA-F]{8}', line)
match = re.search('([0-9A-F]{2}\s*[0-9A-F]{2}\s*[0-9A-F]{2}\s*[0-9A-F]{2}\s*[0-9A-F]{2}\s*[0-9A-F]{2}\s*[0-9A-F]{2}\s*[0-9A-F]{2})\s*-\s*([0-9A-F]{2}\s*[0-9A-F]{2}\s*[0-9A-F]{2}\s*[0-9A-F]{2}\s*[0-9A-F]{2}\s*[0-9A-F]{2}\s*[0-9A-F]{2}\s*[0-9A-F]{2})', line)
#match = re.search(r'(?<=\s{2})((?:[0-9A-F]{2}\s){8})(?=\s-\s)', line)
if match:
hex_data = match.group(1).replace(' ', '')
raw_output += bytes.fromhex(hex_data)
hex_data = match.group(2).replace(' ', '')
raw_output += bytes.fromhex(hex_data)
#print(raw_output)
if raw_output:
f.write(raw_output)
print(f"读取地址: 0x{current_address:X}, 数据保存至 {output_file}")
current_address += chunk_size
if __name__ == '__main__':
start_address = int(input("请输入起始地址(16进制):"), 16)
end_address = int(input("请输入结束地址(16进制):"), 16)
chunk_size = int(input("请输入块大小(字节):"))
serial_port = input("请输入串口号(例如COM3):")
baud_rate = int(input("请输入波特率(例如115200):"))
output_file = input("请输入输出文件名(例如md.bin):")
buffer = input("请输入缓冲区位置:")
dump_memory_test(start_address, end_address, chunk_size, serial_port, baud_rate, output_file,buffer)
尝试运行该脚本,用秒表掐一下时间,在我的电脑上,如果每次读取1000B的数据,读取一次要8秒钟,948K全部读完就是948 X 8 =7584 秒,也就是126分钟
如果每次读3000的数据,读取一次需要16秒,算下来需要84分钟才能读取完毕,这么长的时间跟波特率有关系,因为这个板子定义的波特率只有57600,本身传输速度有限,再加上mem -dump需要一定的时间,所以脚本里加了几秒的延时。
真的跑了好久好久,才得到我们要的固件md.bin,把它给binwalk解一下看看
得到了一堆文件,由于这是vxworks的固件,没有文件名,不过文件内容是正常的。
0x02 友华PT926G 固件提取
拆开PT926G的外壳,可以看到电路板上存在两个uart调试接口,用杜邦线焊接其中的一组,然后把它连接到USB-TTL模块上
打开MobaXterm,串口号选择COM7,波特率选择115200。这里的波特率是用XCom一步一步试出来的,因为波特率不正确的情况下,显示的内容是一团乱码,反复调整波特率,直到输出显示正常,即可得到板子的波特率为115200
成功连接uart命令行接收到调试信息
命令行输出了一长串系统启动的指令,停在最后提示要输入用户名和密码验证登录
获取密码的方式有两种,一种是连接上该路由器的网口,然后访问
http://192.168.1.1:8080/cgi-bin/cgic_systeminfo.cgi
就能获取到该路由器的密码,另一种方式就是直接看路由器外壳底部的贴纸,上面会记录该路由器的默认密码
输入useradmin回车,fnrx6回车,成功进入路由器的Busybox界面
这里可以直接看到路由器的根目录,尝试了几个指令,发现有cp ,tar,mv等常用的文件拷贝指令,同时PT926G还有一个USB接口,插入U盘后系统还会自动挂载U盘到/mnt/usb 下,这样我们就可以尝试把根目录全部拷贝到U盘中。
这里我准备了一个足够大的U盘,插入路由器
一开始我想的是先用tar -cvf 打包根目录下所有文件,再cp到mnt/usb中,结果发现除了tmp和mnt目录,其他目录都只有只读权限
#ls
bin etc image mnt overlay sbin tmp usr
dev home lib opt proc sys userfs var
#
#touch 1.c
touch: 1.c: Read-only file system
#
然后尝试在tmp目录下拷贝所有文件,结果拷贝到etc文件的时候就提示没有足够的空间了
#cd tmp
#tar -cvf a.tar.gz /
...
etc/province/config_AH_926.xml
tar: write error: No space left on device
#cp: write error: No space left on device
cp: write error: No space left on device
cp: write error: No space left on device
cp: write error: No space left on device
cp: write error: No space left on device
cp: write error: No space left on device
cp: write error: No space left on device
cp: write error: No space left on device
cp: write error: No space left on device
cp: write error: No space left on device
看来只能在u盘里面tar,用tar直接在/mnt/usb/目录下生成tar 文件,一开始我想的是直接打包根目录,就输入了
tar -cvf a.tar.gz /
结果前面打包正常,但是打包到mnt的时候,我发现它把mnt/usb里面的文件,也就是我U盘里本来就存在的文件也进行了打包,由于里面还有其他的大文件,结果就是卡在了一个大文件那里
最后没办法了,只能按文件夹一个一个打包忽略掉mnt文件夹
cd /mnt/usb1.2
tar -cvf bin.tar.gz /bin
tar -cvf etc.tar.gz /etc
tar -cvf image.tar.gz /image
tar -cvf overlay.tar.gz /overlay
tar -cvf sbin.tar.gz /sbin
tar -cvf tmp.tar.gz /tmp
tar -cvf usr.tar.gz /usr
tar -cvf dev.tar.gz /dev
tar -cvf home.tar.gz /home
tar -cvf lib.tar.gz /lib
tar -cvf opt.tar.gz /opt
tar -cvf proc.tar.gz /proc
tar -cvf sys.tar.gz /sys
tar -cvf userfs.tar.gz /userfs
tar -cvf var.tar.gz /var
最后就得到了这些压缩包,将他们全部解压缩,就能拿到所有的固件内容了