作者简介 /Profile/
朱文哲,平安科技银河实验室高级安全研究员,Blackhat Asia 2019 演讲者,专注于工业物联网设备及软件的漏洞挖掘。曾发现多款知名品牌工业物联网设备及软件的安全漏洞。
0x01 前言
本文仅对TP-Link XDR-5430-V2路由器研究的过程及发现进行一些记录,部分描述可能会存在错误,在后续研究更深入后会进行修正,截止目前该设备的研究尚未有明显的突破。
0x02 焊接UART串口
和往常一样在拿到设备后首先第一件要干的事情,就是拆开设备寻找UART或其他调试接口。在拆卸设备后,可以在主板正面看到很清晰的4个UART PIN,在PIN的边上也对每个PIN的用途进行了标注。
UART串口焊接后的效果如下图所示。
此时我们就可以通过串口客户端来获取设备的串口信息,设备的引导日志如下所示:
Format: Log Type - Time(microsec) - Message - Optional Info
Log Type: B - Since Boot(Power On Reset), D - Delta, S - Statistic
S - QC_IMAGE_VERSION_STRING=BOOT.BF.3.3.1.1-00041
S - IMAGE_VARIANT_STRING=MAABANBZA
S - OEM_IMAGE_VERSION_STRING=WINCE-SDC-101
S - Boot Config, 0x000002c1
B - 127 - PBL, Start
B - 1559 - bootable_media_detect_entry, Start
B - 1736 - bootable_media_detect_success, Start
B - 1740 - elf_loader_entry, Start
B - 2736 - auth_hash_seg_entry, Start
B - 3109 - auth_hash_seg_exit, Start
B - 147859 - elf_segs_hash_verify_entry, Start
B - 202709 - PBL, End
B - 165432 - SBL1, Start
B - 226157 - GCC [RstStat:0x10, RstDbg:0x600000] WDog Stat : 0x4
B - 235338 - clock_init, Start
D - 7228 - clock_init, Delta
B - 243603 - boot_flash_init, Start
S - SBL last page flash: 38.
S - SBL last page flash: 1.
S - SBL last page flash: 0.
S - SBL last page flash: 0.
S - First couple:0x26:0x1.
S - Second couple:0x0:0x0.
D - 17110 - boot_flash_init, Delta
B - 262879 - boot_config_data_table_init, Start
D - 0 - boot_config_data_table_init, Delta - (0 Bytes)
B - 274744 - Boot Setting : 0x00000618
B - 277062 - CDT version:2,Platform ID:8,Major ID:4,Minor ID:0,Subtype:1
B - 288194 - sbl1_ddr_set_params, Start
B - 294020 - Pre_DDR_clock_init, Start
B - 295118 - Pre_DDR_clock_init, End
B - 845978 - Image Load, Start
D - 438590 - QSEE Image Loaded, Delta - (383613 Bytes)
B - 1285453 - QSEE Execution, Start
D - 61 - QSEE Execution, Delta
B - 1292102 - SBL1, End
D - 1126853 - SBL1, Delta
S - Flash Throughput, 4732 KB/s (383613 Bytes, 81066 us)
S - DDR Frequency, 800 MHz
S - Core 0 Frequency, 800 MHz
U-Boot 2016.01 (May 26 2021 - 22:32:57 +0800)
DRAM: smem ram ptable found: ver: 1 len: 4
256 MiB
Using default environment
In: serial@78AF000
Out: serial@78AF000
Err: serial@78AF000
machid: 8040001
No ART partition found
SPI_ADDR_LEN=3
SF: Detected XM25QU128C with page size 256 Bytes, erase size 4 KiB, total 16 MiB
input <abort key> to stop autoboot in 500 ms
device 0 offset 0xe0000, size 0xd0
SF: 208 bytes @ 0xe0000 Read: OK
verifying uboot partition...
copy the full partition to ram ...
device 0 offset 0xc0000, size 0x20000
SF: 131072 bytes @ 0xc0000 Read: OK
ok
verifying kernel and romfs partition...
copy the full partition to ram ...
device 0 offset 0xe0200, size 0xb9fe00
SF: 12189184 bytes @ 0xe0200 Read: OK
ok
device 0 offset 0xe0000, size 0x200
SF: 512 bytes @ 0xe0000 Read: OK
device 0 offset 0xc0000, size 0x20000
SF: 131072 bytes @ 0xc0000 Read: OK
U-Boot 2016.01 (May 26 2021 - 22:44:05 +0800)
DRAM: smem ram ptable found: ver: 1 len: 4
256 MiB
Using default environment
In: serial@78AF000
Out: serial@78AF000
Err: serial@78AF000
machid: 8040001
No ART partition found
SPI_ADDR_LEN=3
SF: Detected XM25QU128C with page size 256 Bytes, erase size 4 KiB, total 16 MiB
device 0 offset 0xe0000, size 0x200
SF: 512 bytes @ 0xe0000 Read: OK
set_fs_bootargs flash_type = 6
SPI_ADDR_LEN=3
SF: Detected XM25QU128C with page size 256 Bytes, erase size 4 KiB, total 16 MiB
device 0 offset 0xe0200, size 0x300000
SF: 3145728 bytes @ 0xe0200 Read: OK
## Loading kernel from FIT Image at 44000000 ...
Using '[email protected]' configuration
Trying 'kernel@1' kernel subimage
Description: ARM OpenWrt Linux-4.4.60
Type: Kernel Image
Compression: lzma compressed
Data Start: 0x440000e4
Data Size: 2265062 Bytes = 2.2 MiB
Architecture: ARM
OS: Linux
Load Address: 0x41208000
Entry Point: 0x41208000
Hash algo: crc32
Hash value: e147d172
Hash algo: sha1
Hash value: 9da1dce4bf2675cc6d62d7d23eb3482f38bfc60d
## Loading fdt from FIT Image at 44000000 ...
Using '[email protected]' configuration
Trying '[email protected]' fdt subimage
Description: ARM OpenWrt qcom-ipq50xx-mpxx device tree blob
Type: Flat Device Tree
Compression: uncompressed
Data Start: 0x4422920c
Data Size: 54170 Bytes = 52.9 KiB
Architecture: ARM
Hash algo: crc32
Hash value: d5137844
Hash algo: sha1
Hash value: ab96aac48f897beb852b9960e577904023881b28
Booting using the fdt blob at 0x4422920c
Uncompressing Kernel Image ... OK
Loading Device Tree to 497f1000, end 49801399 ... OK
Using machid 0x8040001 from environment
Starting kernel ...
- preinit -
- regular preinit -
switching to jffs2
tmp/virtual_art.bin
- init -
Please press Enter to activate this console. dev.nss.general.redirect = 1
00.00.23.782318 MCSD plugin info : Initializing plugin manager
mcastds: Entering evloopRun
checkOrResetFactoryInfo(913). Factory Info verified
<ucconv> usrconfPathPrepare(37). /tmp/etc/config doesn't exit, Now create it.
00.00.24.673944 MCSD netd ERR : netdBridgeLinkNLCB Read error, Create it again
deviceInfoInit(785). softver:1.0.15 Build 210526 Rel.80830n
br_scan_ifaces(47). Scanning bridge netif...
event_sock_enable(1018). enabled netlink socket at 23 with own pid 433
lanv6LocalIpv6: fe80::36f7:16ff:fe9f:bfbf
[wireless]ioctlCmdSetAlmac[791]Set almac 34:f7:16:9f:bf:bf
...
...
...
不同于早期的D-Link及小米路由器,在终端输入回车后提示需要输入账号密码才能够登陆路由器的终端shell,此时就需要去分析设备的固件来分析此处的账号密码。
0x03 固件提取及分析
在设备主板上,我们可以看到一枚型号为XMC25QU128CH10的flash芯片,我们可以使用热风枪将该芯片拆卸后,使用专用的编程器对Flash中的数据进行读写。
使用专用工具导出该FLASH芯片内的数据如下图所示。
使用Binwalk对固件文件进行分析的结果如下图所示,我们可以在固件中找到squashfs文件系统。
使用binwalk自动解压后即可查看到具体的设备文件。
在passwd文件中发现了root账号的密码hash,但遗憾的是无法通过在线平台对该hash进行破解。
考虑到我们可以通过编程器直接修改已拆卸FLASH芯片中的内容,下一步的计划就是直接重新打包一个修改过密码的rootfs到flash中尝试绕过路由器自带的系统升级校验。
0x04 尝试重打包固件
首先我们需要修改原始rootfs的passwd文件,将hash替换后在进行重打包为squashfs文件。
# 使用mksquashfs命令将修改后的rootfs打包为hacked.squashfs文件
mksquashfs squashfs-root hacked.squashfs -b 256k -comp xz
使用python脚本重打包
# !/usr/bin/env python3
# coding=utf-8
import struct
squashfs_base_offset = 0x324594
squashfs_end_offset = 0xC80000
src_file_path = "./TP_XDR_5430_XMC25QU128CH10_20210628_104736.BIN"
dst_file_path = "./TP_XDR_5430_XMC25QU128CH10_20210628_104736_harked.BIN"
squashfs_file_path = "./hacked.squashfs"
src_file_data = open(src_file_path, 'rb').read()
squashfs_file_data = open(squashfs_file_path, 'rb').read()
squashfs_file_data += (squashfs_end_offset - squashfs_base_offset - len(squashfs_file_data)) * b'xff'
new_dst_file_data = src_file_data[:squashfs_base_offset] + squashfs_file_data + src_file_data[squashfs_end_offset:]
new_dst_file = open(dst_file_path, 'wb')
new_dst_file.write(new_dst_file_data)
new_dst_file.close()
将重打包后的固件重新写入FLASH芯片,装回设备上开机后发现,TP-Link的bootloader会对rootfs进行校验,直接修改rootfs的方法无效。
Recover mode的界面如下。
尝试了一下在Revocer界面直接更新打过patch的固件升级包,则会提示如下的RSA签名校验错误信息,可见TP-Link在这一步也做了签名校验。
目前想到的方法就是分析bootloader代码,找到校验的逻辑后patch掉。
0x05 bootloader分析
从最初的引导日志中可以发现firmware check failed报错是在第一个u-boot中,因此我们就需要先从固件中提取第一个u-boot。继续对binwalk分析出的几个gzip包解压进行字符串搜索后,最终定位到第一个u-boot就在偏移为0x69000处。
解压后发现uboot是一个纯二进制文件,没有使用任何封装。在Ghidra中加载该文件进行分析。
直接查看uboot头部的代码可以发现在0x00000004处PC寄存器指向了0x4a920000附近,然后又由于uboot头部代码存储的一般是中断向量表,因此大概率0x4a920000就是这个uboot的加载地址。
重新使用0x4a920000加载后进行分析,在0x4a952e3c处发现了目标关键字的引用。
在0x4a925000处发现了用于校验固件的函数。
在0x4a9451ac函数中找到校验逻辑,发现有一个循环会逐个对kernel和rootfs等进行校验,随即进行patch。
打完patch后使用如下gzip命令压缩。
# 一定要使用 -n 去除header中的文件名
gzip -n uboot_1_harked.bin
使用python脚本再次重打包
# !/usr/bin/env python3
# coding=utf-8
import struct
squashfs_base_offset = 0x324594
squashfs_end_offset = 0xC80000
uboot_1_start_offset = 0x69000
uboot_1_end_offset = 0xA0000
src_file_path = "./TP_XDR_5430_XMC25QU128CH10_20210628_104736.BIN"
dst_file_path = "./TP_XDR_5430_XMC25QU128CH10_20210628_104736_harked.BIN"
squashfs_file_path = "./hacked.squashfs"
uboot_1_file_path = "./uboot_1_harked.bin.gz"
src_file_data = open(src_file_path, 'rb').read()
squashfs_file_data = open(squashfs_file_path, 'rb').read()
squashfs_file_data += (squashfs_end_offset - squashfs_base_offset - len(squashfs_file_data)) * b'xff'
new_dst_file_data = src_file_data[:squashfs_base_offset] + squashfs_file_data + src_file_data[squashfs_end_offset:]
uboot_1_size_offset_in_elf = 0x31000 + 0x3e0
uboot_1_size_in_firmware = struct.unpack("<Q", src_file_data[uboot_1_size_offset_in_elf: uboot_1_size_offset_in_elf + 8])[0]
print("uboot_1_size_in_firmware: {}".format(uboot_1_size_in_firmware))
# uboot_1
uboot_1_file_data = open(uboot_1_file_path, 'rb').read()
uboot_1_file_length = len(uboot_1_file_data)
print("len(uboot_1_file_data): {}".format(len(uboot_1_file_data)))
# Padding b'xff'
uboot_1_file_data += (uboot_1_end_offset - uboot_1_start_offset - len(uboot_1_file_data)) * b'xff'
print("len(uboot_1_file_data): {}".format(len(uboot_1_file_data)))
new_dst_file_data_2 = new_dst_file_data[:uboot_1_start_offset] + uboot_1_file_data + new_dst_file_data[uboot_1_end_offset:]
new_dst_file = open(dst_file_path, 'wb')
new_dst_file.write(new_dst_file_data_2)
new_dst_file.close()
这次重新打包后又遇到了新的问题,引导程序报错。
针对这个情况,有两种可能:
一种是bootloader没有能够自动识别到gzip的文件长度导致解析错误;
另一种可能是gzip的不同版本之间压缩算法存在区别,之前在研究D-Link路由器时遇到过类似的问题,原因是xz压缩库版本不兼容导致的。
针对第一种情况,由于gzip的header自身是不带文件长度信息的,因此在bootloader中必然会有某些地方用于存储gzip的长度信息。在对固件中第二个elf文件进行提取分析时发现,uboot实际是由第二个elf文件加载的。
第二个ELF中的Program Header中可以找到uboot.bin.gz相关的文件大小等各类信息。
从上图中可以得知,gzip文件的大小是写在第二个ELF的Header信息中的,不过当手动修改ELF Header中的gzip文件大小后烧录Flash引导会导致新的报错,BootLoader在做ELF Header校验时出错(详见第6章节高通Secure boot描述)。
此时还有另一个方法,就是使用高压缩比的gzip,然后按照原来的FLASH ROM特征,使用x00填充gzip文件,使gzip文件满足Header中的长度要求,后续的FLASH段内容以xFF填充。
在进行了如下操作后烧录至FLASH引导后的日志如下所示,从日志中看至少校验和解压部分应该顺利完成了,但是在Core 0 Frequency, 800MHz后没有能够跳转到我们修改后的uboot执行,这个可能就是压缩算法的差异导致解压后的二进制文件异常。
然而在尝试了几个版本,甚至是直接使用qemu调用路由器rootfs中的gzip命令进行压缩都会报同样的错误,那么下一步只能想办法通过模拟执行去分析TP-LInk到底是如何去解压gzip文件的。
0x06 使用Qiling框架模拟执行bootloader
提取Bootloader
通过引导日志我们可以发现TP-Link的bootloader至少有三层,第一层负责引导第二层的uboot,第二层的uboot会对第三层的uboot及rootfs进行校验,确认数据完整性后再移交给第三层的uboot进行系统引导。
那么在进行模拟执行之前,我们首选需要解决的一个问题是,把第一层的bootloader提取出来。再来看一下之前binwalk的信息,我们可以发现flash的头部是一个elf文件。
直接使用readelf命令可以看到elf header的信息,通过该信息,我们就可以准确提取该elf文件。
将提取后的文件拖到ida中直接分析(懒得提取也可以直接把整个flash备份拖到ida)。在0x070057d2处可以看到对应的报错日志代码。
使用Qiling框架进行模拟执行
那么接下来的目的就是通过模拟执行,想办法让程序执行到解压gzip的代码段。先使用如下python脚本,尝试进行模拟执行。
# !/usr/bin/env python3
# coding=utf-8
import os as oos
import sys
import shutil
from qiling import *
from qiling.const import QL_VERBOSE
def memory_fix(ql, access, addr, size, value):
ql.log.info("[_] Mapping " + str(size) + " bytes at " + hex(addr) + " | access: " + str(access) + " | value: " + str(value))
print(
"[_] Mapping " + str(size) + " bytes at " + hex(addr) + " | access: " + str(access) + " | value: " + str(value))
ql.mem.map(addr//4096*4096, 4096*4096)
packer = ql.pack32
if ql.archbit == 64:
packer = ql.pack64
ql.mem.write(addr, packer(value)) # memory packing is OS dependant
return
def my_sandbox(path, rootfs):
ql = Qiling(path,
rootfs=rootfs,
log_file='./xdr5430-qiling.log',
verbose=QL_VERBOSE.DEBUG,
# verbose=QL_VERBOSE.DISASM,
)
# 发现没有mapped的数据就进行map
ql.hook_mem_unmapped(memory_fix)
ql.run()
if __name__ == "__main__":
running_env_path = "./running_env/"
if oos.path.exists(running_env_path):
shutil.rmtree(running_env_path)
oos.mkdir(running_env_path)
else:
oos.mkdir(running_env_path)
shutil.copy("TP_XDR_5430_harked.BIN", running_env_path)
main_app_path = "{}/TP_XDR_5430_harked.BIN".format(running_env_path)
my_sandbox(path=[main_app_path], rootfs=running_env_path)
直接运行时会出现很多unmapped内存地址以及各类奇怪的报错。
由于此时我们模拟的是bootloader,因此相当于是在内核态进行执行,很多内存地址其实是一些特殊的寄存器地址或是外部设备地址。
通过启动日志我们可以得知这款路由器使用的处理器是qcom-ipq50xx-mpxx系列,在网上查阅了一些资料后得知具体的型号是高通IPQ5018 Cortex-A53。
在Binwalk分析的FLASH的结果中我们可以看到几个Flattened device tree文件,但是binwalk并没有能够自动提取这几个FDT文件,FDT文件相关的资料。
我们可以使用如下命令手工对FDT文件进行提取并将提取后的dtb文件转成dts文件。
dd if=TP_XDR_5430_XMC25QU128CH10_20210628_104736.BIN of=0xE0200.dtb bs=1 skip=918016 count=2376596
# 提取完整的 ARM OpenWrt FIT (Flattened Image Tree)
dtc -I dtb -O dts 0xE0200.dtb >0xE0200.dts
# 提取 ARM OpenWrt qcom-ipq50xx-mpxx device tree blob Qualcomm Technologies, Inc. IPQ5018/AP-MP03.1
dd if=TP_XDR_5430_XMC25QU128CH10_20210628_104736.BIN of=0x30940C.dtb bs=1 skip=3183628 count=54170
dtc -I dtb -O dts 0x30940C.dtb >0x30940C.dts
# 提取 ARM OpenWrt qcom-ipq50xx-mpxx device tree blob Qualcomm Technologies, Inc. IPQ5018/AP-MP02.1
dd if=TP_XDR_5430_XMC25QU128CH10_20210628_104736.BIN of=0x3168B4.dtb bs=1 skip=3238068 count=55681
dtc -I dtb -O dts 0x3168B4.dtb >0x3168B4.dts
转换完成的ARM OpenWrt FIT (Flattened Image Tree)文件如下所示,这个文件中包含了3个image,分别是Linux kernel和两个fdt文件, 而这2个fdt文件就是前面我们通过dd命令提取的0x30940C.dts和0x3168B4.dts。
在dts文件中,我们可以看到CPU相关的一些如UART设备相关的内存基地址。
通过分析高通CPU的dts文件可以在一定程度上帮助我们分析及模拟执行bootloader的代码,然而在实际模拟中我们还是会遇到很多涉及到主板外部设备的内存地址,这时只能通过在网上搜索近似型号处理器相关的dts描述文件进行查看。最终在相似型号的描述文件中找到了0x4a3000地址应该是高通的sleep counter。
此时我们可以通过在python脚本中追加如下代码ql.mem.map(0x004a3000, 0x00001000)手动map这个内存地址。同时我们也可以通过debug调试来查访问目标unmapped内存的代码位置。
同样如果我们想要查看是哪边对这个内存地址进行了读写操作也可以使用Qiling的hook api进行判断。
接下来,就可以从elf文件的entry_point处0x07000FB0结合模拟执行开始逐步分析。此时我们的线索就是如下所示设备正常情况下的引导日志,通过模拟执行时输出的文本可以初步判断我们执行的情况。
Format: Log Type - Time(microsec) - Message - Optional Info
Log Type: B - Since Boot(Power On Reset), D - Delta, S - Statistic
S - QC_IMAGE_VERSION_STRING=BOOT.BF.3.3.1.1-00041
S - IMAGE_VARIANT_STRING=MAABANBZA
S - OEM_IMAGE_VERSION_STRING=WINCE-SDC-101
S - Boot Config, 0x000002c1
B - 127 - PBL, Start
B - 1559 - bootable_media_detect_entry, Start
B - 1736 - bootable_media_detect_success, Start
B - 1740 - elf_loader_entry, Start
B - 2736 - auth_hash_seg_entry, Start
B - 3109 - auth_hash_seg_exit, Start
B - 147859 - elf_segs_hash_verify_entry, Start
B - 202709 - PBL, End
B - 165432 - SBL1, Start
B - 226157 - GCC [RstStat:0x10, RstDbg:0x600000] WDog Stat : 0x4
B - 235338 - clock_init, Start
D - 7228 - clock_init, Delta
B - 243603 - boot_flash_init, Start
S - SBL last page flash: 38.
S - SBL last page flash: 1.
S - SBL last page flash: 0.
S - SBL last page flash: 0.
S - First couple:0x26:0x1.
S - Second couple:0x0:0x0.
D - 17110 - boot_flash_init, Delta
B - 262879 - boot_config_data_table_init, Start
D - 0 - boot_config_data_table_init, Delta - (0 Bytes)
B - 274744 - Boot Setting : 0x00000618
B - 277062 - CDT version:2,Platform ID:8,Major ID:4,Minor ID:0,Subtype:1
B - 288194 - sbl1_ddr_set_params, Start
B - 294020 - Pre_DDR_clock_init, Start
B - 295118 - Pre_DDR_clock_init, End
B - 845978 - Image Load, Start
D - 438590 - QSEE Image Loaded, Delta - (383613 Bytes)
B - 1285453 - QSEE Execution, Start
D - 61 - QSEE Execution, Delta
B - 1292102 - SBL1, End
D - 1126853 - SBL1, Delta
S - Flash Throughput, 4732 KB/s (383613 Bytes, 81066 us)
S - DDR Frequency, 800 MHz
S - Core 0 Frequency, 800 MHz
2016.01 (May 26 2021 - 22:32:57 +0800)
DRAM: smem ram ptable found: ver: 1 len: 4
256 MiB
Using default environment
In: serial@78AF000
Out: serial@78AF000
Err: serial@78AF000
machid: 8040001
为了模拟执行输出日志,我们首先需要解决一个问题,就是如何让模拟执行输出日志。因为这些日志实际是通过读写uart接口来实现的,因此我们可以通过定位到具体的打印函数,进行hook来直接输出写入到uart的内容。
通过分析打印文本的交叉引用可以很轻松的定位到0x07009EF0及0x07009C14处的两个打印相关的函数。
相关hook代码如下:
def hook_print_07009CF8(ql, *args, **kw):
print_string = ql.mem.string(ql.reg.r0)
print("Hook print(0x07009CF8): {}".format(print_string))
return
ql.hook_address(hook_print_07009CF8, 0x07009CF8)
Hook后就可以在模拟执行时查看如下图所示的uart输出内容了。
在模拟过程中还会遇到各种如下所示的逻辑问题,从代码来看如果0xE1140处内存数据不满足条件就会一直死循环。
此时我们就需要分析整体代码的逻辑分支手动对一些外部设备内存地址进行赋值。
ql.mem.map(0x000E1000, 0x00010000)
ql.mem.write(0xE1140, ql.pack32(0x02000000)) # use at 0x7004977
如果长时间程序没有任何反应也没有报错,还可以把verbose级别调到QL_VERBOSE.DISASM,这样就可以查看是否哪里陷入了死循环等情况,会遇到很多如下图所示的情况需要定位并对内存进行处理。
通过hook unmapped内存,我们可以进一步识别出memcpy(0x070117CC)及内存初始化函数0x07000EAC,在上述两个函数下hook可以帮助我们更好的理解代码逻辑。
通过分析交叉引用结合Qiling调试分析,大致确认了需要执行到的解压gzip函数的调用栈情况如下:
0x07012030->0x07007AD8->0x07007B2C->0x070098A4 ->0x07007EB4 ->0x07008254 ->0x070085A8 ->0x07005640(zlib_decompress)
接下来就是需要逐步分析业务逻辑和处理各种内存异常。
在漫长的分析、调试和内存patch后,最终在0x70120E6处遇到了一个无法处理的问题。
通过调试分析sub_7011FE4得知该函数的参数r0实际是程序自身ELF文件加载时的第一个参数,然而ELF加载时的代码在SOC芯片中,我们无法获取,因此在无法知晓这部分代码实际输入的情况下也就很难进行处理,此外这部分代码也会影响后续的业务处理逻辑,强行进行patch绕过这段代码也会对实际分析产生影响。
通过Qiling模拟执行,去分析bootloader的话,会遇到很多复杂的问题,需要投入大量的时候去分析和处理,目前时间有限,只能暂时放一下,等后续有时间时再进一步分析该bootloader的相关代码。
0x07 总结
通过对TP-Link XDR-5430-V2路由器进行研究后,对高通的固件格式和引导流程有了一定得了解,下图为高通Secure boot的描述。
从描述中看,Secure boot会首先加载Primary BootLoader (PBL) 进行预处理后加载Secondary BootLoader (SBL) 对下一层bootloader进行校验并最终跳转执行。
如下图所示,高通信任链中会对所有ELF image Header及segment内容进行校验。
按照高通的信任链,我们通过修改rootfs和uboot的方法应该是无法成功的,但是在实际测试中我们在未修改ELF Header的情况下修改gzip信息会报zlib报错,这应该代表已经通过了高通的信任链校验,这个显然并不科学,其中的一种可能就是TP-LInk 并未严格按照高通的信任链去开发,具体原因如何,只能等后续有时间的情况下进一步的分析来确认。
在这次研究过程中,使用Qiling框架模拟Bootloader也面临了很多的挑战,Qiling框架虽然有模拟执行的能力,但框架自身并没有模拟外部设备(时钟、FLASH存储等)的功能。
此外分析bootloader的难度和复杂度也较高,需要投入大量的时间和精力去研究处理不同CPU及外部设备之间是如何工作的,在缺乏描述文件的情况下这部分的分析会很困难。相信这些问题在积累分析各类bootloader的经验后,处理起来应该会相对更轻松一些。
0x08 引用
- Qcom IPQ40xx Device Tree Overview
- 高通入门缩写词意思 – 知乎
- Msm8998 Cold Boot Flow – Evsio0n
- 高通平台Android源码bootloader分析之sbl1(一) | Andy.Lee’s Blog
- Analysis of Qualcomm Secure Boot Chains
银河实验室
#PSRC 2021年度评选# 四大奖项正在评选中,丰厚奖金等你来拿!
往期回顾
技术
技术
技术
技术
长按识别二维码关注我们
微信号:PSRC_Team
球分享
球点赞
球在看
原文始发于微信公众号(平安集团安全应急响应中心):TP-Link XDR-5430-V2 研究分享 – 第一章