RP2350 硬件固化 CVE‑2022‑38694:突破 Unisoc BSP 安全启动链
LeoChen.. 2026-09-04 17:59 上海

看雪论坛作者ID:LeoChen..
00

项目简介

这个项目的目标,是把 Unisoc / Spreadtrum 平台上原本依赖 PC 工具完成的 BootROM 下载流程,固化到 RP2350 微控制器中。
正常情况下,我们需要在 PC 上使用 spd_dump 一类工具,手动发送 FDL1、FDL2,再进入后续刷写或启动流程。本项目做的是:把前置逆向得到的修改版加载链,内嵌到 RP2350 工程里,让 RP2350 作为 USB Host,在目标设备进入 BootROM 模式后自动完成握手、发送和注入。
项目地址:
https://github.com/LeoChen-CoreMind/spd_flasher本文主要记录两部分内容:
- 逆向分析部分
如何分析 UDX710 上 FDL1、FDL2、SPL Loader、U-Boot 和 system 层的限制;
- 项目实现部分
如何把已经处理好的启动链固化到 RP2350 中自动发送。
文章重点放在逆向过程和最终落地流程。项目实现细节不在正文展开,读者需要复现时直接查看源码。
01

整体流程

完整流程可以概括为:
查看标准刷机链
-> 理解 BootROM / FDL1 / FDL2 / BSP 多层校验
-> 分析 UDX710 的 FDL1 校验
-> 分析 UDX710 的 FDL2 校验
-> 分析 SPL Loader 启动地址和加载关系
-> 分析 U-Boot 验证链
-> system 层去云控 / 去策略回收
-> 得到修改后的启动链
-> 将 SPL Loader 作为 FDL 数据写入 0x28007000
-> 再写入 0x3F28 的 exec stub
-> RP2350 固化发送整个流程这里要先明确一点:RP2350 只是把已经研究清楚的流程自动化重放。真正让设备能启动的关键,在前面的逆向和魔改。
02

逆向过程开始前:每一层分别负责什么

在进入具体分析前,需要先把 Unisoc 刷机链里的几个角色分清楚。
2.1 BootROM
BootROM 是芯片上电后最早执行的固化代码。它负责:
初始化最基本的下载环境;
通过 USB 暴露 BootROM 下载接口;
接收 FDL1;
校验并执行 FDL1。
BootROM 这一层通常是我们和设备建立通信的入口。
2.2 FDL1
FDL1 是 BootROM 下载并执行的第一阶段加载器。它负责:
承接 BootROM;
初始化更完整的下载环境;
接收 FDL2;
校验 FDL2;
通过后跳转执行 FDL2。
本文最先分析的就是 FDL1 对 FDL2 的校验。
2.3 FDL2
FDL2 是第二阶段下载器,功能比 FDL1 更完整。它通常负责:
分区读写;
刷入文件;
校验待刷入镜像;
处理 BSP 签名;
进入启动或下载命令处理逻辑。
如果 FDL2 这一层没有处理好,即使 FDL1 已经通过,后续刷入文件仍然可能失败。
2.4 SPL Loader
SPL Loader 是 BootROM 后正式启动链中的早期加载器。它负责:
初始化启动环境;
加载 trustos、teecfg、sml 等镜像;
继续进入后续 bootloader。
当前项目利用的关键点是:
FDL1 下载地址 == SPL Loader 实际启动地址也就是说,可以把修改后的 SPL Loader 作为 FDL 数据写入目标地址。
2.5 U-Boot / LK
U-Boot 或 LK 这一层通常负责:
bootloader lock 状态;
Verified Boot 状态;
do_cboot()一类启动函数;
slot / rollback / AVB / VAB 判断;
启动参数和分区检查。
很多时候,前面 FDL1、FDL2、SPL 都处理完了,最后仍然卡在 U-Boot 的验证链。
2.6 system 层
进入 system 后,还可能存在:
云控策略;
联网校验;
开机自动恢复;
远程配置下发;
设备状态回收。
所以 system 去云控是启动链打通后的最后一环。
2.7 启动链图示
为了让读者更直观看到 SPL Loader 后面还会继续校验 sml、trustos、uboot,这里放一张启动链示意图:

这张图根据公开资料整理:TheGammaSqueeze/UnisocBypass README 中明确提到,Unisoc 的 UMS512 安全启动链使用 DHTB + SIMGHDR 格式,SPL 会对它加载的 sml、trustos、uboot 做 RSA-2048 签名校验,任一检查失败会进入无限循环。这个结论可以支撑本文后面对 SPL Loader、U-Boot 和 system 层继续分析的必要性。
03

从标准刷机链开始

图 1 来源于 TomKing062/spreadtrum_flash 项目,展示了 spd_dump 的典型使用方式:
spd_dump --wait 300 fdl /path/to/fdl1 fdl1_addr fdl /path/to/fdl2 fdl2_addr exec
这条命令对应的流程是:
BootROM
-> 下载 FDL1 到 fdl1_addr
-> 执行 FDL1
-> FDL1 下载 FDL2 到 fdl2_addr
-> 执行 FDL2
-> FDL2 进入刷写、读取、启动或其他命令逻辑它说明了一个关键事实:
Unisoc 的刷机流程不是一次性把文件写进去,而是分阶段加载、分阶段校验、分阶段交接控制权。
因此,后面的分析不能只盯着一个文件,而要沿着整条链路逐层追。
04

刷机前为什么要处理多层校验

在标准流程里,各阶段并不是无条件信任下一阶段。常见校验关系可以抽象成:
BootROM 校验 FDL1
-> FDL1 校验 FDL2
-> FDL2 校验刷入文件
-> 刷入文件还可能带 BSP 签名校验如果这些校验没有处理,即使 USB 通信和 FDL 下载都正常,仍然可能出现:
BootROM 不接受 FDL1;
FDL1 不接受 FDL2;
FDL2 不接受待刷入镜像;
BSP 签名不通过;
文件写入成功但启动链失败;
进入系统后被云控或联网策略重新限制。
所以这不是单点 patch,而是一条多级处理链:
FDL1
-> FDL2
-> SPL Loader
-> U-Boot
-> system05

UDX710 的分析原则

公开项目和 Wiki 只能提供思路,不能直接套用到 UDX710。
UDX710 的启动流程、内存布局、分区组织、厂商改动和验证链实现都可能不同。
本文采用的原则是:
借鉴公开资料的定位方法,
但所有 patch 点、函数地址、文件偏移、镜像顺序和验证结果,
都以 UDX710 自身样本的逆向结果为准。公开资料里最值得借鉴的是分析路线:
从 FDL1 的执行路径看它如何接收和校验 FDL2;
从 FDL2 / U-Boot 中找
do_cboot()、set_lock_status()、get_lock_status();通过 trustos、teecfg、sml 等镜像还原启动链;
区分下载阶段 patch 和启动阶段 patch;
区分 SPL 路线和 FDL1 路线。
06

FDL1 校验定位:从 FDL2 文件头 DHTB入手

6.1 观察 FDL2 文件头
首先打开 udx710_fdl2,可以看到文件开头是 ASCII 字符串:
DHTB
对应十六进制字节是:
44 48 54 42这个文件头就是定位 FDL1 校验逻辑的第一个线索。
6.2 DHTB 魔数为什么是 1112819780
在反编译结果里,文件头不一定以 "DHTB" 字符串出现,更多时候会变成一个整数常量。
DHTB 的 ASCII 字节为:
D = 0x44
H = 0x48
T = 0x54
B = 0x42按 32 位小端整数 解释:
magic = 'D' + ('H' << 8) + ('T' << 16) + ('B' << 24)
= 0x44 + (0x48 << 8) + (0x54 << 16) + (0x42 << 24)
= 68 + 72 * 256 + 84 * 65536 + 66 * 16777216
= 1112819780也就是:
0x42544844 == 1112819780所以在反编译结果中看到 1112819780 时,它很可能就是在检查 FDL2 文件头的 DHTB。
这一步的意义是:把文件格式特征转换成可搜索的反编译常量。
6.3 在 FDL1 中搜索魔数引用
拿到 1112819780 后,在 FDL1 反编译结果中搜索该常量,可以定位到图 3 所示位置:

图中核心判断逻辑为:
if ( *a2 != 1112819780 )
return 1LL;这段代码说明:
a2指向待解析的数据结构或缓冲区;
*a2取的是缓冲区开头的 32 位字段;
如果开头字段不是
1112819780,也就是不是DHTB,函数直接返回失败。
到这里,FDL1 对 FDL2 的识别入口已经缩小到一个很小的范围。
不过,这里通常只是头部检查,不一定是真正的签名或完整性校验。真正决定 FDL2 是否能继续执行的逻辑,还要继续沿调用链追下去。
6.4 顺着调用链追到真正失败分支
继续从图 3 的函数往下追踪,可以定位到图 4 所示位置:

图 4 中的核心逻辑可以简化成:
v0 = MEMORY[0x28017DAC];
nullsub_1();
if ( (unsignedint)sub_2800FD2C(671117312LL, MEMORY[0x28017DAC], 0LL, 0LL, 2LL) )
{
while ( 1 )
;
}
((void (*)(void))(unsignedint)(v0 + 512))();
return0LL;这一段比前面的 DHTB 头部判断更关键:
sub_2800FD2C(...)更像是真正的校验函数;
返回值为非零时进入失败分支;
失败分支是一个死循环:
while ( 1 )
;如果校验通过,代码会继续执行:
((void (*)(void))(unsigned int)(v0 + 512))();也就是跳到被加载镜像的后续入口位置。
因此这里的控制流含义很明确:
校验成功 -> 跳转执行 FDL2 入口
校验失败 -> 进入 while(1) 死循环6.5 FDL1 修改思路
FDL1 的处理目标不是破坏整个加载流程,而是让失败分支不再阻断后续执行。
结合图 4,阻断点集中在校验失败后的死循环:
while ( 1 )
;该死循环本质上是失败陷阱。处理方式就是让程序不要停在这里,而是继续走到后面的执行路径。
实际处理时,可以将失败分支对应位置改成空操作,或调整分支逻辑,使其不再落入死循环。这样 FDL1 对 FDL2 的阻断被解除,FDL1 阶段完成。
本节完整定位链如下:
FDL2 文件头看到 DHTB
-> DHTB 按 32 位小端转换为 1112819780
-> 在 FDL1 中搜索 1112819780
-> 命中 FDL2 头部检查函数
-> 顺着调用链继续追踪
-> 找到真正校验函数 sub_2800FD2C(...)
-> 发现失败后进入 while(1) 死循环
-> 处理失败陷阱,使流程继续进入 FDL2 入口这一节体现的是逆向分析中最重要的方法:先从文件格式特征入手,再用常量引用反推处理函数,最后顺着调用链找到真正影响控制流的分支。
07

FDL2 刷入校验定位:从加载地址和分区字符串入手

FDL1 处理完成后,下一步就是分析 FDL2。这里的目标不是让 FDL2 能运行,而是让它在刷入目标镜像时不再被校验逻辑阻断。
7.1 通过刷机命令确认 FDL2 加载地址
在 PC 侧验证时,可以通过如下 spd_dump 命令观察 FDL1、FDL2 的加载关系:
spd_dump --wait 600 exec_addr 0x3f28 fdl .\data\udx710_fdl1 0x28007000 fdl .\data\udx710_fdl2 0x9efffe00 exec这条命令里最关键的是第二个 FDL 地址:
0x9efffe00它就是当前样本中 FDL2 的加载地址。后面在 IDA / Ghidra 里分析 udx710_fdl2 时,需要用这个地址作为镜像基址进行加载。基址设置正确后,反编译中的全局变量、函数交叉引用和字符串引用才更容易对上。
这一步的意义是:
从刷机命令拿到 FDL2 运行地址
-> 用 0x9efffe00 作为 FDL2 镜像基址
-> 让反编译结果更接近真实运行状态7.2 通过分区字符串定位处理函数
加载 FDL2 后,可以从字符串入手搜索待刷入分区名。
图 5 中可以看到,函数里出现了多个和刷写目标相关的字符串:
splloader
sml
uboot
boot
nr_agdsp
nr_modem
nr_v3phy
nr_nophy
nr_nrdsp1
recovery
nr_nrdsp2
这些字符串很关键,因为 FDL2 在刷写不同镜像时,需要先判断当前传入的分区或镜像名属于哪一类,再进入对应的处理路径。
从图 5 的逻辑可以看出:
splloader会被归到一类;
sml、
uboot会被归到另一类;boot、
nr_modem、recovery等分区会被归到 FDL2 处理路径;最后统一跳到后面的处理函数。
因此,搜索字符串不是为了看字符串本身,而是为了找到 FDL2 中“按分区名分发刷写逻辑”的入口。
7.3 定位真正刷入函数
顺着图 5 的函数继续追,可以定位到图 6 所示位置。

图 6 中可以看到,函数进入后会先调用:
sub_9F05EC28(a1, a2);后面继续出现多次和刷写相关的调用以及错误返回,例如:
return sub_9F01E448("flash error");这说明当前函数已经不是单纯的字符串分类函数,而是进入了真正刷入流程附近。
这一段的分析链可以总结为:
用 0x9efffe00 加载 FDL2
-> 搜索 splloader / sml / uboot / boot / nr_modem 等分区字符串
-> 找到按分区名分发的函数
-> 顺着调用关系追到刷入处理函数
-> 观察 flash error 等错误路径
-> 找到影响刷入成功与否的关键分支7.4 FDL2 修改思路
FDL2 这一层的处理目标,是让刷入流程不要因为分区检查、镜像检查或签名检查而中断。
从图 6 的位置继续分析,可以看到真正影响刷入结果的关键调用和错误路径。处理时的原则和 FDL1 类似:不要破坏整个刷写流程,而是处理会导致失败返回的阻断点。
在当前教学里,这一段只讲基本原理:
定位 FDL2 加载地址
-> 设置正确镜像基址
-> 搜索待刷入分区字符串
-> 找到分区分发函数
-> 追到真正刷入函数
-> 处理失败分支实践中,这一步远比一句“找到后 NOP”复杂。不同版本 FDL2 的结构、函数内联、字符串引用、错误路径、BSP 签名检查和真实刷写调用都可能不同。真正操作时需要花很多时间反复验证,不是靠一个固定偏移就能通用解决。
本文在这里给出的是最基本的分析思路:先用加载地址还原运行视角,再用分区字符串定位业务入口,最后顺着调用链找到真正影响刷入结果的分支。
08

后续逆向主线

FDL1 和 FDL2 的下载、刷写路径处理完成后,还要继续分析 SPL Loader、U-Boot 和 system 层。
8.1 SPL Loader
接下来需要处理 SPL Loader。相比 FDL1 和 FDL2,这一层更加关键,因为它已经进入正式启动链,后面会继续影响 sml、trustos、uboot 等镜像能否被加载和执行。
SPL Loader 的整体思路和 FDL1 大同小异:还是先通过魔术值、镜像头、字符串或已知结构定位到校验函数,再顺着调用链找到真正阻断启动的失败分支。
不同点在于:SPL Loader 后面要处理的不再只是一个 FDL2,而是多个启动镜像。它通常会继续校验:
sml
trustos
uboot / LK所以和前面的 FDL1 相比,SPL Loader 里会多出好几个校验点。这里不能只处理一个分支,而是要把相关校验路径逐一确认、逐一处理。
图 7 就是 SPL Loader 中比较典型的一段多重校验逻辑:

图中可以看到,多次出现类似的调用结构:
if ( (unsigned int)sub_2802FC2C(...) )
{
while ( 1 )
;
}这和前面 FDL1 的失败逻辑很像:
校验函数返回失败
-> 进入 while(1)
-> 启动链停止区别是,SPL Loader 里这类检查不止一处。它可能分别对应不同镜像、不同加载地址或不同启动阶段。例如图中能看到多次对 sub_2802FC2C(...) 的调用,并且参数不同,这说明它不是一次简单校验,而是在按不同镜像或不同区域做连续检查。
因此 SPL Loader 这一层的处理方法可以概括成:
通过魔术值 / 镜像头 / 字符串定位校验入口
-> 找到实际校验函数
-> 统计所有调用点
-> 区分每个调用点对应的镜像或区域
-> 找出失败后进入 while(1) 的分支
-> 逐一处理这些失败陷阱
-> 确认后续能继续加载 sml / trustos / uboot这里要注意,SPL Loader 是整条链里非常关键的一层。FDL1 和 FDL2 更多是在下载、刷写阶段起作用;SPL Loader 则会直接影响正式启动链。如果只处理 FDL1 / FDL2,而 SPL Loader 对后续镜像的校验仍然存在,设备仍然会在启动阶段卡死。
当前项目利用的关键点就是:
FDL1 下载地址 == SPL Loader 实际启动地址也就是说,我们最后把修改后的 SPL Loader 当作 FDL 数据写入 0x28007000,前提就是这个 SPL Loader 自身已经经过处理,能够继续放行后面的 sml、trustos 和 uboot。
本节只讲基本原理。实际分析时,SPL Loader 的校验点数量、函数内联情况、镜像加载顺序、参数含义和失败路径都要逐一核对。不能看到一个 while(1) 就认为全部完成,必须把相关校验调用全部追完。
8.2 U-Boot
众所周知,从上面的启动流程可以看出:SPL Loader 校验通过后,会继续启动 U-Boot / LK;U-Boot 再继续处理 boot、recovery 等启动镜像的校验和加载。
所以在 SPL Loader 之后,还需要继续看 U-Boot 这一层。
U-Boot / LK 通常决定:
bootloader lock 状态;
Verified Boot 状态;
do_cboot()启动路径;
boot_env_check
AVB / VAB;
slot 选择;
rollback 检查;
下载模式回退逻辑。
这一层处理完成后,启动链才算真正从 BootROM、FDL、SPL 走到了系统启动前的关键位置。
8.2.1 通过 boot 字符串过滤定位
这里的分析入口依然是字符串。
因为目标是启动非签名 boot,所以可以在 U-Boot 里大量搜索 boot 相关字符串,然后结合上下文过滤无关结果。只看字符串本身没有意义,重点是找到“哪些函数在根据分区名决定是否需要校验”。
图 8 中可以看到一个很典型的函数:

这个函数里出现了:
orca_cpe_boot_verify
boot
recovery
nr_agdsp
the cpe img do not need to verify从逻辑上看,它会判断当前镜像名:
如果是
boot或recovery,进入校验路径;如果是
nr_agdsp,进入另一类校验路径;其他情况则可能打印“不需要校验”。
因此图 8 这个函数可以理解为 U-Boot 里和 BSP / boot 镜像校验相关的分发入口。
8.2.2 最终定位到两个 BSP 校验函数
继续顺着图 8 的调用关系往下追,最终定位到图 9 和图 10 这两个关键函数。
图 9:
图 9 中可以看到明显的校验日志:
sprd_verify_table_2048
verify failed: len = %d
verify failed: tablenotmatch这个函数的返回值很关键:
return 0LL; // 失败
return 1LL; // 成功图 10:

图 10 中可以看到:
sprd_verify_cert
pss cert type: %d
invalid cert type %d!!!这说明它和证书类型、证书结构或签名证书验证有关。
结合图 9 和图 10,可以把 U-Boot 里的 BSP 校验链简化成:
boot / recovery 镜像进入校验入口
-> 校验 table
-> 校验证书 / cert
-> 任一失败则返回失败
-> 上层阻断 boot 启动8.2.3 Patch 原理:让校验函数固定返回成功
这里的处理思路和前面的 FDL1、FDL2、SPL Loader 一样:不要大范围破坏启动流程,只处理最终决定“校验成功 / 失败”的返回点。
在 AArch64 调用约定里,函数返回值放在 W0 / X0 中:
W0 = 0 通常表示失败
W0 = 1 通常表示成功因此,对于图 9 和图 10 这两个最终校验函数,可以把函数入口改成固定返回成功:
MOV W0, #1
RET含义是:
进入校验函数
-> 直接把返回值设置为 1
-> 立即返回上层
-> 上层认为 BSP / boot 校验成功AArch64 小端字节形式为:
MOV W0,#1 ; 20 00 80 52
RET ; C0 035F D6也就是:
20008052 C0 035F D6这样处理后,上层调用者拿到的结果始终是“校验成功”,非签名 boot 就不会再因为这两个 BSP 校验函数返回失败而被阻断。
8.2.4 这里只讲基本原理
这一层实践起来同样很复杂。实际分析时,需要确认:
图 9、图 10 是否确实是当前样本最终生效的校验函数;
上层是否还存在其他二次判断;
patch 后是否影响
recovery、nr_agdsp等其他镜像;返回值语义是否在当前样本中确实是
1 = 成功;是否还有 AVB / rollback / slot 等后续启动限制。
所以本文这里只介绍基本思路:
搜索 boot 字符串
-> 过滤定位 boot / recovery 校验入口
-> 顺调用链追到 table / cert 校验函数
-> 确认返回值语义
-> 将关键校验函数改成 MOV W0, #1; RET
-> 让上层永远收到校验成功真正落地时,需要结合当前样本反复验证,不能只靠这一段文字机械套用。
8.3 system 去云控
进入 system 后,还可能存在:
云控策略;
联网校验;
开机自动恢复;
远程配置下发;
设备状态回收。
所以 system 去云控是启动链打通后的最后一环。
09

从逆向到落地:完整固化开机流程

恭喜看到这里。到这一步,前面的 FDL1、FDL2、SPL Loader、U-Boot 校验链已经基本讲完,但真正能稳定开机,还差最后一段落地流程。
前面已经提供了足够多的定位思路:
通过魔数定位 FDL1 校验
-> 通过加载地址和分区字符串定位 FDL2 刷写校验
-> 通过 SPL Loader 多重校验处理 sml / trustos / uboot
-> 通过 boot 字符串定位 U-Boot / BSP 校验
-> 通过返回值 patch 让关键校验函数固定成功接下来,boot 和 system 的修改就需要继续沿着同样的方法深入研究。这里不能再用一两句话概括成固定答案,因为不同版本的 boot、vendor_boot、system、厂商服务、云控策略都可能不一样。
可以把最后阶段理解成:
修改 boot
-> 让非签名 system 能正常启动
-> 修改 system
-> 去掉云控 / 联网策略 / 开机回收
-> 刷入魔改后的 uboot、boot、system
-> 擦除 splloader 分区
-> 将魔改后的 splloader 放入 RP2350 源码
-> 编译 RP2350 固件
-> 烧录 RP2350
-> 焊接到目标 USB 口
-> 上电自动完成 CVE-2022-38694 固化开机9.1 修改 boot 与 system
boot 这一层通常包含 kernel、ramdisk、启动参数和早期 init 逻辑。想让非签名 system 正常启动,需要继续分析:
ramdisk 中的 init 脚本;
fstab / dm-verity / AVB 相关配置;
启动参数;
vendor_boot 与 boot 的配合关系;
system 挂载失败后的回退逻辑。
system 层则重点看:
云控服务;
联网校验;
开机恢复策略;
远程配置下发;
设备状态回收;
与 bootloader lock 状态相关的用户态服务。
这一部分没有通用偏移,必须按当前固件自己分析。
9.2 刷入魔改后的镜像
当 uboot、boot、system 都处理完成后,需要把这些镜像刷回目标设备:
魔改 uboot
魔改 boot
魔改 system刷入后要确认:
U-Boot 不再阻断非签名 boot;
boot 能正常挂载 system;
system 不再被云控或策略回收;
重启后状态仍然保持。
9.3 擦除 splloader 分区
接下来要擦除目标设备上的 splloader 分区。
这样做的目的,是让设备上电后进入 BootROM 下载模式,然后由外部 RP2350 接管后续发送流程。
流程可以理解成:
擦除 splloader
-> 设备无法从原始 splloader 正常启动
-> 上电进入 BootROM 下载路径
-> RP2350 作为 USB Host 自动发送魔改 splloader9.4 将魔改 splloader 放入源码并编译 RP2350 固件
魔改后的 splloader 需要放入 RP2350 工程中,作为上电后自动发送的数据。具体如何转换、如何嵌入、如何编译,读者可以直接看项目源码。
最终编译得到 RP2350 固件:
spd_flasher.uf2
spd_flasher.bin9.5 烧录 RP2350 并焊接到 USB 口
最后将编译好的固件烧录到 RP2350,并把 RP2350 的 USB Host 线路接到目标设备 USB 口。
实际落地时要保证:
RP2350 处于 USB Host 模式;
D+ / D- 连接正确;
GND 共地;
目标设备供电稳定;
RP2350 上电时机和目标设备上电时机匹配。
实际焊接效果如下。图中可以看到目标板 USB 口附近的 D+ / D- 引出点:
![图11:目标板 USB D+ / D- 引出点]
RP2350 模块烧录固件后,固定到目标板上,并将 USB D+ / D-、供电和 GND 接入目标板:
![图12:RP2350 固定并接入目标 USB 线路]
这里的核心不是单纯“飞线”,而是把前面逆向得到的魔改 splloader 固化到 RP2350 中,让目标设备每次上电后都能由 RP2350 自动完成发送流程。
最终效果就是:
目标设备上电
-> 进入 BootROM
-> RP2350 自动握手
-> RP2350 发送魔改 splloader
-> RP2350 发送 exec stub
-> 目标进入修改后的启动链
-> 实现 CVE-2022-38694 固化开机到这里,整个研究链才从“逆向分析”真正落地成“硬件固化启动”。
10

项目实现

本文重点是逆向思路和整体落地流程,项目源码部分不在这里展开。
RP2350 固化发送、USB Host、BSL/HDLC 封包、状态机、数据数组生成、编译烧录等实现细节,已经放在项目源码中。需要复现或二次开发的读者直接看仓库即可:
https://github.com/LeoChen-CoreMind/spd_flasher11

结语

本案例的核心价值不在于某一个固定 patch,而在于完整的分析方法:
从标准刷机链入手
-> 分清 BootROM / FDL1 / FDL2 / SPL / U-Boot / system 的职责
-> 用文件格式特征反推校验函数
-> 顺着调用链找到真正阻断控制流的位置
-> 处理失败分支
-> 把验证过的启动链固化到 RP2350以 FDL1 为例,DHTB 文件头提供了一个非常清晰的入口。将它转换成小端整数 1112819780 后,就能在 FDL1 中定位头部检查函数,再继续追踪到真正的校验函数和失败死循环。
FDL2、SPL Loader、U-Boot 和 system 层也是同样的分析思路:不要直接套别人的地址和 patch,而是先找到当前机型自己的验证链,再做针对化处理。
免责声明
本文基于公开资料、开源项目和个人研究过程整理,只用于科研、教学、逆向工程学习和安全研究。
本文不提供任何可直接用于商业设备的破解文件、私有签名文件、厂商密钥、完整固件包或现成绕过成品。文中涉及的 FDL1、FDL2、SPL Loader、U-Boot、boot、system 等内容,均以分析方法和研究思路为主,具体样本需要读者在自己的研究环境中自行获取、自行分析、自行验证。
不同平台、不同 SoC、不同固件版本的启动链和校验逻辑可能完全不同,本文内容不能直接套用到所有设备。请在合法合规、授权和自有设备范围内进行研究,因错误操作导致设备损坏、数据丢失或其他后果,需要由操作者自行承担。
看雪ID:LeoChen..
*本文为看雪论坛精华文章,由 LeoChen.. 原创,转载请注明来自看雪社区


球分享

球点赞

球在看

点击阅读原文查看更多

