首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
逆向工程
发新帖
1
18
[原创]今天学点不一样的打破Linux Bsp安全启动链,自己控制安全启动链与用RP2350 固化CVE-2022-38694注入实践
发表于: 2026-8-6 00:53
2376
[原创]今天学点不一样的打破Linux Bsp安全启动链,自己控制安全启动链与用RP2350 固化CVE-2022-38694注入实践
LeoChen..
3
2026-8-6 00:53
2376
--- ## 免责声明 本文基于公开资料、开源项目和个人研究过程整理,只用于科研、教学、逆向工程学习和安全研究。 本文不提供任何可直接用于商业设备的破解文件、私有签名文件、厂商密钥、完整固件包或现成绕过成品。文中涉及的 FDL1、FDL2、SPL Loader、U-Boot、boot、system 等内容,均以分析方法和研究思路为主,具体样本需要读者在自己的研究环境中自行获取、自行分析、自行验证。 不同平台、不同 SoC、不同固件版本的启动链和校验逻辑可能完全不同,本文内容不能直接套用到所有设备。请在合法合规、授权和自有设备范围内进行研究,因错误操作导致设备损坏、数据丢失或其他后果,需要由操作者自行承担。 --- ## 0. 项目简介 这个项目的目标,是把 Unisoc / Spreadtrum 平台上原本依赖 PC 工具完成的 BootROM 下载流程,固化到 RP2350 微控制器中。 正常情况下,我们需要在 PC 上使用 `spd_dump` 一类工具,手动发送 FDL1、FDL2,再进入后续刷写或启动流程。本项目做的是:把前置逆向得到的修改版加载链,内嵌到 RP2350 工程里,让 RP2350 作为 USB Host,在目标设备进入 BootROM 模式后自动完成握手、发送和注入。 项目地址: ```text https://github.com/LeoChen-CoreMind/spd_flasher ``` 本文主要记录两部分内容: 1. **逆向分析部分**:如何分析 UDX710 上 FDL1、FDL2、SPL Loader、U-Boot 和 system 层的限制; 2. **项目实现部分**:如何把已经处理好的启动链固化到 RP2350 中自动发送。 文章重点放在逆向过程和最终落地流程。项目实现细节不在正文展开,读者需要复现时直接查看源码。 --- ## 1. 整体流程 完整流程可以概括为: ```text 查看标准刷机链 -> 理解 BootROM / FDL1 / FDL2 / BSP 多层校验 -> 分析 UDX710 的 FDL1 校验 -> 分析 UDX710 的 FDL2 校验 -> 分析 SPL Loader 启动地址和加载关系 -> 分析 U-Boot 验证链 -> system 层去云控 / 去策略回收 -> 得到修改后的启动链 -> 将 SPL Loader 作为 FDL 数据写入 0x28007000 -> 再写入 0x3F28 的 exec stub -> RP2350 固化发送整个流程 ``` 这里要先明确一点:RP2350 只是把已经研究清楚的流程自动化重放。真正让设备能启动的关键,在前面的逆向和魔改。 --- ## 2. 逆向过程开始前:每一层分别负责什么 在进入具体分析前,需要先把 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。 当前项目利用的关键点是: ```text 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 层继续分析的必要性。 --- ## 3. 从标准刷机链开始 图 1 来源于 `TomKing062/spreadtrum_flash` 项目,展示了 `spd_dump` 的典型使用方式: ```text spd_dump --wait 300 fdl /path/to/fdl1 fdl1_addr fdl /path/to/fdl2 fdl2_addr exec ```  这条命令对应的流程是: ```text BootROM -> 下载 FDL1 到 fdl1_addr -> 执行 FDL1 -> FDL1 下载 FDL2 到 fdl2_addr -> 执行 FDL2 -> FDL2 进入刷写、读取、启动或其他命令逻辑 ``` 它说明了一个关键事实: > Unisoc 的刷机流程不是一次性把文件写进去,而是分阶段加载、分阶段校验、分阶段交接控制权。 因此,后面的分析不能只盯着一个文件,而要沿着整条链路逐层追。 --- ## 4. 刷机前为什么要处理多层校验 在标准流程里,各阶段并不是无条件信任下一阶段。常见校验关系可以抽象成: ```text BootROM 校验 FDL1 -> FDL1 校验 FDL2 -> FDL2 校验刷入文件 -> 刷入文件还可能带 BSP 签名校验 ``` 如果这些校验没有处理,即使 USB 通信和 FDL 下载都正常,仍然可能出现: - BootROM 不接受 FDL1; - FDL1 不接受 FDL2; - FDL2 不接受待刷入镜像; - BSP 签名不通过; - 文件写入成功但启动链失败; - 进入系统后被云控或联网策略重新限制。 所以这不是单点 patch,而是一条多级处理链: ```text FDL1 -> FDL2 -> SPL Loader -> U-Boot -> system ``` --- ## 5. UDX710 的分析原则 公开项目和 Wiki 只能提供思路,不能直接套用到 UDX710。 UDX710 的启动流程、内存布局、分区组织、厂商改动和验证链实现都可能不同。本文采用的原则是: ```text 借鉴公开资料的定位方法, 但所有 patch 点、函数地址、文件偏移、镜像顺序和验证结果, 都以 UDX710 自身样本的逆向结果为准。 ``` 公开资料里最值得借鉴的是分析路线: - 从 FDL1 的执行路径看它如何接收和校验 FDL2; - 从 FDL2 / U-Boot 中找 `do_cboot()`、`set_lock_status()`、`get_lock_status()`; - 通过 trustos、teecfg、sml 等镜像还原启动链; - 区分下载阶段 patch 和启动阶段 patch; - 区分 SPL 路线和 FDL1 路线。 --- ## 6. FDL1 校验定位:从 FDL2 文件头 `DHTB` 入手 ### 6.1 观察 FDL2 文件头 首先打开 `udx710_fdl2`,可以看到文件开头是 ASCII 字符串: ```text DHTB ```  对应十六进制字节是: ```text 44 48 54 42 ``` 这个文件头就是定位 FDL1 校验逻辑的第一个线索。 ### 6.2 `DHTB` 魔数为什么是 `1112819780` 在反编译结果里,文件头不一定以 `"DHTB"` 字符串出现,更多时候会变成一个整数常量。 `DHTB` 的 ASCII 字节为: ```text D = 0x44 H = 0x48 T = 0x54 B = 0x42 ``` 按 **32 位小端整数** 解释: ```text magic = 'D' + ('H' << 8) + ('T' << 16) + ('B' << 24) = 0x44 + (0x48 << 8) + (0x54 << 16) + (0x42 << 24) = 68 + 72 * 256 + 84 * 65536 + 66 * 16777216 = 1112819780 ``` 也就是: ```text 0x42544844 == 1112819780 ``` 所以在反编译结果中看到 `1112819780` 时,它很可能就是在检查 FDL2 文件头的 `DHTB`。 这一步的意义是:**把文件格式特征转换成可搜索的反编译常量。** ### 6.3 在 FDL1 中搜索魔数引用 拿到 `1112819780` 后,在 FDL1 反编译结果中搜索该常量,可以定位到图 3 所示位置:  图中核心判断逻辑为: ```c if ( *a2 != 1112819780 ) return 1LL; ``` 这段代码说明: - `a2` 指向待解析的数据结构或缓冲区; - `*a2` 取的是缓冲区开头的 32 位字段; - 如果开头字段不是 `1112819780`,也就是不是 `DHTB`,函数直接返回失败。 到这里,FDL1 对 FDL2 的识别入口已经缩小到一个很小的范围。 不过,这里通常只是头部检查,不一定是真正的签名或完整性校验。真正决定 FDL2 是否能继续执行的逻辑,还要继续沿调用链追下去。 ### 6.4 顺着调用链追到真正失败分支 继续从图 3 的函数往下追踪,可以定位到图 4 所示位置:  图 4 中的核心逻辑可以简化成: ```c v0 = MEMORY[0x28017DAC]; nullsub_1(); if ( (unsigned int)sub_2800FD2C(671117312LL, MEMORY[0x28017DAC], 0LL, 0LL, 2LL) ) { while ( 1 ) ; } ((void (*)(void))(unsigned int)(v0 + 512))(); return 0LL; ``` 这一段比前面的 `DHTB` 头部判断更关键: - `sub_2800FD2C(...)` 更像是真正的校验函数; - 返回值为非零时进入失败分支; - 失败分支是一个死循环: ```c while ( 1 ) ; ``` 如果校验通过,代码会继续执行: ```c ((void (*)(void))(unsigned int)(v0 + 512))(); ``` 也就是跳到被加载镜像的后续入口位置。 因此这里的控制流含义很明确: ```text 校验成功 -> 跳转执行 FDL2 入口 校验失败 -> 进入 while(1) 死循环 ``` ### 6.5 FDL1 修改思路 FDL1 的处理目标不是破坏整个加载流程,而是让失败分支不再阻断后续执行。 结合图 4,阻断点集中在校验失败后的死循环: ```c while ( 1 ) ; ``` 该死循环本质上是失败陷阱。处理方式就是让程序不要停在这里,而是继续走到后面的执行路径。 实际处理时,可以将失败分支对应位置改成空操作,或调整分支逻辑,使其不再落入死循环。这样 FDL1 对 FDL2 的阻断被解除,FDL1 阶段完成。 本节完整定位链如下: ```text FDL2 文件头看到 DHTB -> DHTB 按 32 位小端转换为 1112819780 -> 在 FDL1 中搜索 1112819780 -> 命中 FDL2 头部检查函数 -> 顺着调用链继续追踪 -> 找到真正校验函数 sub_2800FD2C(...) -> 发现失败后进入 while(1) 死循环 -> 处理失败陷阱,使流程继续进入 FDL2 入口 ``` 这一节体现的是逆向分析中最重要的方法:**先从文件格式特征入手,再用常量引用反推处理函数,最后顺着调用链找到真正影响控制流的分支。** --- ## 7. FDL2 刷入校验定位:从加载地址和分区字符串入手 FDL1 处理完成后,下一步就是分析 FDL2。这里的目标不是让 FDL2 能运行,而是让它在刷入目标镜像时不再被校验逻辑阻断。 ### 7.1 通过刷机命令确认 FDL2 加载地址 在 PC 侧验证时,可以通过如下 `spd_dump` 命令观察 FDL1、FDL2 的加载关系: ```powershell spd_dump --wait 600 exec_addr 0x3f28 fdl .\data\udx710_fdl1 0x28007000 fdl .\data\udx710_fdl2 0x9efffe00 exec ``` 这条命令里最关键的是第二个 FDL 地址: ```text 0x9efffe00 ``` 它就是当前样本中 FDL2 的加载地址。后面在 IDA / Ghidra 里分析 `udx710_fdl2` 时,需要用这个地址作为镜像基址进行加载。基址设置正确后,反编译中的全局变量、函数交叉引用和字符串引用才更容易对上。 这一步的意义是: ```text 从刷机命令拿到 FDL2 运行地址 -> 用 0x9efffe00 作为 FDL2 镜像基址 -> 让反编译结果更接近真实运行状态 ``` ### 7.2 通过分区字符串定位处理函数 加载 FDL2 后,可以从字符串入手搜索待刷入分区名。 图 5 中可以看到,函数里出现了多个和刷写目标相关的字符串: ```text 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 中可以看到,函数进入后会先调用: ```c sub_9F05EC28(a1, a2); ``` 后面继续出现多次和刷写相关的调用以及错误返回,例如: ```c return sub_9F01E448("flash error"); ``` 这说明当前函数已经不是单纯的字符串分类函数,而是进入了真正刷入流程附近。 这一段的分析链可以总结为: ```text 用 0x9efffe00 加载 FDL2 -> 搜索 splloader / sml / uboot / boot / nr_modem 等分区字符串 -> 找到按分区名分发的函数 -> 顺着调用关系追到刷入处理函数 -> 观察 flash error 等错误路径 -> 找到影响刷入成功与否的关键分支 ``` ### 7.4 FDL2 修改思路 FDL2 这一层的处理目标,是让刷入流程不要因为分区检查、镜像检查或签名检查而中断。 从图 6 的位置继续分析,可以看到真正影响刷入结果的关键调用和错误路径。处理时的原则和 FDL1 类似:不要破坏整个刷写流程,而是处理会导致失败返回的阻断点。 在当前教学里,这一段只讲基本原理: ```text 定位 FDL2 加载地址 -> 设置正确镜像基址 -> 搜索待刷入分区字符串 -> 找到分区分发函数 -> 追到真正刷入函数 -> 处理失败分支 ``` 实践中,这一步远比一句“找到后 NOP”复杂。不同版本 FDL2 的结构、函数内联、字符串引用、错误路径、BSP 签名检查和真实刷写调用都可能不同。真正操作时需要花很多时间反复验证,不是靠一个固定偏移就能通用解决。 本文在这里给出的是最基本的分析思路:**先用加载地址还原运行视角,再用分区字符串定位业务入口,最后顺着调用链找到真正影响刷入结果的分支。** --- ## 8. 后续逆向主线 FDL1 和 FDL2 的下载、刷写路径处理完成后,还要继续分析 SPL Loader、U-Boot 和 system 层。 ### 8.1 SPL Loader 接下来需要处理 SPL Loader。相比 FDL1 和 FDL2,这一层更加关键,因为它已经进入正式启动链,后面会继续影响 `sml`、`trustos`、`uboot` 等镜像能否被加载和执行。 SPL Loader 的整体思路和 FDL1 大同小异:还是先通过魔术值、镜像头、字符串或已知结构定位到校验函数,再顺着调用链找到真正阻断启动的失败分支。 不同点在于:SPL Loader 后面要处理的不再只是一个 FDL2,而是多个启动镜像。它通常会继续校验: ```text sml trustos uboot / LK ``` 所以和前面的 FDL1 相比,SPL Loader 里会多出好几个校验点。这里不能只处理一个分支,而是要把相关校验路径逐一确认、逐一处理。 图 7 就是 SPL Loader 中比较典型的一段多重校验逻辑:  图中可以看到,多次出现类似的调用结构: ```c if ( (unsigned int)sub_2802FC2C(...) ) { while ( 1 ) ; } ``` 这和前面 FDL1 的失败逻辑很像: ```text 校验函数返回失败 -> 进入 while(1) -> 启动链停止 ``` 区别是,SPL Loader 里这类检查不止一处。它可能分别对应不同镜像、不同加载地址或不同启动阶段。例如图中能看到多次对 `sub_2802FC2C(...)` 的调用,并且参数不同,这说明它不是一次简单校验,而是在按不同镜像或不同区域做连续检查。 因此 SPL Loader 这一层的处理方法可以概括成: ```text 通过魔术值 / 镜像头 / 字符串定位校验入口 -> 找到实际校验函数 -> 统计所有调用点 -> 区分每个调用点对应的镜像或区域 -> 找出失败后进入 while(1) 的分支 -> 逐一处理这些失败陷阱 -> 确认后续能继续加载 sml / trustos / uboot ``` 这里要注意,SPL Loader 是整条链里非常关键的一层。FDL1 和 FDL2 更多是在下载、刷写阶段起作用;SPL Loader 则会直接影响正式启动链。如果只处理 FDL1 / FDL2,而 SPL Loader 对后续镜像的校验仍然存在,设备仍然会在启动阶段卡死。 当前项目利用的关键点就是: ```text 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 中可以看到一个很典型的函数:  这个函数里出现了: ```text 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 中可以看到明显的校验日志: ```text sprd_verify_table_2048 verify failed: len = %d verify failed: table not match ``` 这个函数的返回值很关键: ```c return 0LL; // 失败 return 1LL; // 成功 ``` 图 10:  图 10 中可以看到: ```text sprd_verify_cert pss cert type: %d invalid cert type %d!!! ``` 这说明它和证书类型、证书结构或签名证书验证有关。 结合图 9 和图 10,可以把 U-Boot 里的 BSP 校验链简化成: ```text boot / recovery 镜像进入校验入口 -> 校验 table -> 校验证书 / cert -> 任一失败则返回失败 -> 上层阻断 boot 启动 ``` #### 8.2.3 Patch 原理:让校验函数固定返回成功 这里的处理思路和前面的 FDL1、FDL2、SPL Loader 一样:不要大范围破坏启动流程,只处理最终决定“校验成功 / 失败”的返回点。 在 AArch64 调用约定里,函数返回值放在 `W0 / X0` 中: ```text W0 = 0 通常表示失败 W0 = 1 通常表示成功 ``` 因此,对于图 9 和图 10 这两个最终校验函数,可以把函数入口改成固定返回成功: ```asm MOV W0, #1 RET ``` 含义是: ```text 进入校验函数 -> 直接把返回值设置为 1 -> 立即返回上层 -> 上层认为 BSP / boot 校验成功 ``` AArch64 小端字节形式为: ```text MOV W0, #1 ; 20 00 80 52 RET ; C0 03 5F D6 ``` 也就是: ```text 20 00 80 52 C0 03 5F D6 ``` 这样处理后,上层调用者拿到的结果始终是“校验成功”,非签名 `boot` 就不会再因为这两个 BSP 校验函数返回失败而被阻断。 #### 8.2.4 这里只讲基本原理 这一层实践起来同样很复杂。实际分析时,需要确认: - 图 9、图 10 是否确实是当前样本最终生效的校验函数; - 上层是否还存在其他二次判断; - patch 后是否影响 `recovery`、`nr_agdsp` 等其他镜像; - 返回值语义是否在当前样本中确实是 `1 = 成功`; - 是否还有 AVB / rollback / slot 等后续启动限制。 所以本文这里只介绍基本思路: ```text 搜索 boot 字符串 -> 过滤定位 boot / recovery 校验入口 -> 顺调用链追到 table / cert 校验函数 -> 确认返回值语义 -> 将关键校验函数改成 MOV W0, #1; RET -> 让上层永远收到校验成功 ``` 真正落地时,需要结合当前样本反复验证,不能只靠这一段文字机械套用。 ### 8.3 system 去云控 进入 system 后,还可能存在: - 云控策略; - 联网校验; - 开机自动恢复; - 远程配置下发; - 设备状态回收。 所以 system 去云控是启动链打通后的最后一环。 --- ## 9. 从逆向到落地:完整固化开机流程 恭喜看到这里。到这一步,前面的 FDL1、FDL2、SPL Loader、U-Boot 校验链已经基本讲完,但真正能稳定开机,还差最后一段落地流程。 前面已经提供了足够多的定位思路: ```text 通过魔数定位 FDL1 校验 -> 通过加载地址和分区字符串定位 FDL2 刷写校验 -> 通过 SPL Loader 多重校验处理 sml / trustos / uboot -> 通过 boot 字符串定位 U-Boot / BSP 校验 -> 通过返回值 patch 让关键校验函数固定成功 ``` 接下来,`boot` 和 `system` 的修改就需要继续沿着同样的方法深入研究。这里不能再用一两句话概括成固定答案,因为不同版本的 `boot`、`vendor_boot`、`system`、厂商服务、云控策略都可能不一样。 可以把最后阶段理解成: ```text 修改 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` 都处理完成后,需要把这些镜像刷回目标设备: ```text 魔改 uboot 魔改 boot 魔改 system ``` 刷入后要确认: - U-Boot 不再阻断非签名 boot; - boot 能正常挂载 system; - system 不再被云控或策略回收; - 重启后状态仍然保持。 ### 9.3 擦除 `splloader` 分区 接下来要擦除目标设备上的 `splloader` 分区。 这样做的目的,是让设备上电后进入 BootROM 下载模式,然后由外部 RP2350 接管后续发送流程。 流程可以理解成: ```text 擦除 splloader -> 设备无法从原始 splloader 正常启动 -> 上电进入 BootROM 下载路径 -> RP2350 作为 USB Host 自动发送魔改 splloader ``` ### 9.4 将魔改 `splloader` 放入源码并编译 RP2350 固件 魔改后的 `splloader` 需要放入 RP2350 工程中,作为上电后自动发送的数据。具体如何转换、如何嵌入、如何编译,读者可以直接看项目源码。 最终编译得到 RP2350 固件: ```text spd_flasher.uf2 spd_flasher.bin ``` ### 9.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 自动完成发送流程。 最终效果就是: ```text 目标设备上电 -> 进入 BootROM -> RP2350 自动握手 -> RP2350 发送魔改 splloader -> RP2350 发送 exec stub -> 目标进入修改后的启动链 -> 实现 CVE-2022-38694 固化开机 ``` 到这里,整个研究链才从“逆向分析”真正落地成“硬件固化启动”。 --- ## 10. 项目实现 本文重点是逆向思路和整体落地流程,项目源码部分不在这里展开。 RP2350 固化发送、USB Host、BSL/HDLC 封包、状态机、数据数组生成、编译烧录等实现细节,已经放在项目源码中。需要复现或二次开发的读者直接看仓库即可: ```text https://github.com/LeoChen-CoreMind/spd_flasher ``` --- ## 11. 结语 本案例的核心价值不在于某一个固定 patch,而在于完整的分析方法: ```text 从标准刷机链入手 -> 分清 BootROM / FDL1 / FDL2 / SPL / U-Boot / system 的职责 -> 用文件格式特征反推校验函数 -> 顺着调用链找到真正阻断控制流的位置 -> 处理失败分支 -> 把验证过的启动链固化到 RP2350 ``` 以 FDL1 为例,`DHTB` 文件头提供了一个非常清晰的入口。将它转换成小端整数 `1112819780` 后,就能在 FDL1 中定位头部检查函数,再继续追踪到真正的校验函数和失败死循环。 FDL2、SPL Loader、U-Boot 和 system 层也是同样的分析思路:不要直接套别人的地址和 patch,而是先找到当前机型自己的验证链,再做针对化处理。
回复或点赞可查看完整内容
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
最后于
2026-8-6 02:39 被LeoChen..编辑 ,原因:
#调试逆向
#系统底层
收藏
・
1
点赞
・
18
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
XxingGoD
你的分享对大家帮助很大,非常感谢!
2026-9-13 23:49
git_51951meggadf3df
非常支持你的观点!
2026-9-10 15:29
漫雾.
为你点赞!
2026-8-28 12:20
mb_lthgjpwj
为你点赞!
2026-8-21 07:35
zhuzhu_biu
这个讨论对我很有帮助,谢谢!
2026-8-19 20:41
kxliping
谢谢你的细致分析,受益匪浅!
2026-8-14 18:41
mb_wsoacceo
为你点赞!
2026-8-11 01:30
mb_qtvylfog
感谢你的贡献,论坛因你而更加精彩!
2026-8-10 11:29
mb_xaadptkn
非常支持你的观点!
2026-8-8 20:49
mb_wpitiize
期待更多优质内容的分享,论坛有你更精彩!
2026-8-7 14:50
ONewTach
感谢你的积极参与,期待更多精彩内容!
2026-8-6 09:07
mb_ddggyduk
期待更多优质内容的分享,论坛有你更精彩!
2026-8-6 08:22
mb_lmhtajpz
感谢你的积极参与,期待更多精彩内容!
2026-8-6 07:00
mb_hzwxkcae
期待更多优质内容的分享,论坛有你更精彩!
2026-8-6 02:57
mb_ebtyejup
感谢你分享这么好的资源!
2026-8-6 02:06
mb_rjztytzi
这个讨论对我很有帮助,谢谢!
2026-8-6 01:51
Zy143L
谢谢你的细致分析,受益匪浅!
2026-8-6 01:08
wx_0.0_522
你的帖子非常有用,感谢分享!
2026-8-6 01:06
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
3
)
LeoChen..
雪 币:
3294
活跃值:
(1014)
能力值:
( LV8,RANK:130 )
在线值:
发帖
7
回帖
7
粉丝
61
关注
私信
LeoChen..
3
2
楼
感觉嵌入式都没啥人玩
2026-8-6 17:49
0
简单的简单
雪 币:
10834
活跃值:
(9146)
能力值:
( LV8,RANK:130 )
在线值:
发帖
18
回帖
200
粉丝
189
关注
私信
简单的简单
1
3
楼
666
2026-8-14 14:57
0
道友请留步
雪 币:
138
能力值:
( LV1,RANK:0 )
在线值:
发帖
1
回帖
45
粉丝
0
关注
私信
道友请留步
4
楼
1111111111111111111
2026-8-19 11:22
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
LeoChen..
3
7
发帖
7
回帖
130
RANK
关注
私信
他的文章
[原创]Android 某6零免费版 DexVMP 恢复工作流框架:还原成可运行的正常程序,仅供学习研究
3859
[原创]今天学点不一样的打破Linux Bsp安全启动链,自己控制安全启动链与用RP2350 固化CVE-2022-38694注入实践
2376
[原创]ELF GOT Hook 实战
17009
[原创]Linux arm64 内核Hook实现与校验绕过
21841
[原创]开源分享|从零自研 ARM64 虚拟机保护引擎(VMP),2.0 版本已理论覆盖全部 A64 基础指令
26888
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部