#OPPO A11 (SDM665) 引导漏洞挖掘与完整复现

liang 发布于 6 小时前 3 次阅读


作者:黎昂(不是菜鸡呀) · 时间:2026
参考方法论:h4ckm310n《解锁Vivo Y31S》(https://h4ckm310n.com/?p=106) —— USB 抓包 + 逆向 + 字节级复现


摘要

本工具的目标只有一件事:把一颗未经官方签名的 programmer 引导镜像,通过 9008 模式注入 OPPO A11 (高通 SDM665) 的 PBL,让设备进入 Firehose

整个研究等价于"把 UnlockTool 解锁一条手机的路,用公开的抓包 + 逆向 + 字节级还原三步复刻一遍",与你参考的 h4ckm310n 解锁 Vivo 是同一套方法论,只不过我们把目标放到了更底层的引导固件上。

涉及的技术要点:
1. Qualcomm PBL / Sahara(9008)引导协议
2. 对 PBL 状态机的一个 漏洞利用:连续 13 9A 9A 9A × 95
3. Firehose XML 协议 + UFS 配置
4. 一个踩了大半天的字节级大坑:镜像某 4 字节损坏导致 PBL 静默拒绝


1. 背景与动机

1.1 为什么不能直接刷引导

OPPO A11(SDM665)要引导系统、要解锁,绕不开官方/厂商工具(UnlockTool 等)。正规路径里,programmer(引导加载器)是带签名校验的,PBL 校验不过就拒绝跳转。

1.2 思路

直接使用工具的流量做黑盒复现
- 让工具(UnlockTool)执行一次"进 Firehose";
- 在 USB 层抓到它和设备之间"发出去 / 收回来"的每一个字节;
- 把这些流量在我们自己的脚本里重放,逐字节一比一还原。

这与 h4ckm310n 用 pyusb 模拟 fastboot 对 Vivo 发包是同一个道理——只不过他复现的是 fastboot 命令,我们复现的是 Sahara/PBL 引导协议

1.3 抓包的三个角色

角色 作用
手机(真机) 真正的受害者,跑的是高通 9008 引导
UnlockTool.exe(逆向样本) 我们抓包观察的"正版行为"
监听层(Frida hook + com0com 虚拟串口 tap) 把 UnlockTool↔手机之间所有字节镜像一份

我们最终拿到了 614,491 字节的真机双向流量(tap_dev.bin 设备侧 + tap_un.bin 工具侧),后面的所有结论都从这份黄金数据里反推出来。


2. 目标与漏洞面:Qualcomm PBL / Sahara

2.1 9008 = EDL(Emergency Download Mode)

SDM665 长按【音量+】+【音量-】插入 USB 会进入 9008。此时 PBL 跑一个叫 Sahara 的协议,等待上位机发 programmer。

2.2 Sahara 报文(64 位模式)

Sahara 每个包首 8 字节固定为 cmd(4) + length(4)(小端)。本主角:

含义 命令码 说明
HELLO_REQ(设备→上位机) 0x01 48 字节:01 00 00 00 30 00 00 00 02 00 00 00 01 00 00 00 …,内含 max_cmd_len=0x400
HELLO_RSP(上位机→设备) 0x02 48 字节,保留字段必须全 0
READ_DATA(64bit) 0x12 24 字节:12 00 00 00 20 00 00 00 + image_id(8) + offset(8) + length(8)
END_TRANSFER 0x04 04 00 00 00 10 00 00 00 / image_id(4) / status(4)status=0x0 表示接受
DONE_REQ / DONE_RSP 0x05 / 0x06 上位机确认“传完了”,PBL 回 DONE_RSP 后跳转

2.3 Firehose(programmer 起来之后)

进入 Firehose 后双方用 XML 通信:
- <nop value="ping"/> → 探活
- <configure MemoryName="UFS" MaxPayloadSizeToTargetInBytes="16384"/> → 配置 UFS
- 实测 UFS / sector=4096 / maxpayload=16384

3. 漏洞挖掘过程(挖的过程)

3.1 第一步:抓黄金数据

在 UnlockTool 真正解锁一台真机时,用 Frida hook + 串口 tap 把双向流量全量镜像下来(约 61 万字节),得到两份文件:
- tap_dev.bin(设备→工具)
- tap_un.bin(工具→设备)

这两份是我们做一切验证的唯一裁判

3.2 第二步:逆向状态机

UnlockTool.exe 做静态分析,定位到 偏移 0x15726B0 附近的“复位/握手”逻辑,确认了它的行为:先用一个 magic 序列冲击 PBL 的 Sahara“未签名镜像校验”开关,再按正常 Sahara 握手上传 programmer。

3.3 第三步:还原漏洞利用(EXPLOIT)

从黄金数据里数出,UnlockTool 在握手之前会精准地发 95 次 4 字节:

13 9A 9A 9A
│     └── 填充 0x9A 9A 9A
└── Sahara CMD = 0x13

原理推测:这 95 个“几乎非法”的 Sahara 包把 PBL 命令解析循环撞成一个越界/覆盖状态,最终把内部 已握手 / 已验签 两个标志位覆盖清零,使 PBL 误以为“这个引导已被验证过”,于是后面未签名的 programmer 也能被接受并跳转。

这一步正是脚本里看到的紫色「漏洞利用 / PBL EXPLOIT」横幅 + 独立进度条

3.4 第四步:还原 READ_DATA 时序

PBL 按程序头表顺序一个段一个段地要数据(64 位版请求里给 image_id/offset/length)。还原出 umi_elf_1.elf 的请求序列:

0x0 → 0x40(0x3B8) → 0x1000 → 0x2000(0xCD8) → 0x5AB70 → 0x5BB70(0xEEC) → 0x3000 …
… → 0x906BC(0x1000)
= 127 块,合计 497,532 字节

注意它不是顺序读文件:中途跳到 0x5AB700x5BB70 这种“程序头里登记的段地址”,再跳回 0x3000 继续。实现时必须按请求里给的 offset 从文件取,不能想当然顺序发。

3.5 第五步:判断成功 / 换镜像

  • 传完一个镜像,设备回 END_TRANSFER status=0x0(接受)或 !=0(拒绝)。
  • 工具按 DevPrg trinket(探测 6 块) → umi_elf_1(真 programmer) → umi_elf_2(备用) 逐个试。
  • 第一个 DevPrg(= umi_elf_0,599,436 字节)被请求 6 块就停——这是正常的“探测相”,UnlockTool 抓包里第一段也同样是 6 块,然后再发 HELLO_RSP 换第二个镜像 umi_elf_1

3.6 第六步:进 Firehose + UFS

umi_elf_1 被接受 → 上位机发 DONE_REQ → PBL 回 DONE_RSP → 跳转 → 设备起来发 XML → ping OK → configure UFS OK。

4. 踩过的坑(这些坑最值钱)

坑 1:波特率太低,设备会被复位

默认 115200 太慢,引导要几十秒,PBL 等不耐烦就复位(电脑“叮咚”一声 = USB 重枚举)。改成 3000000。若走 USB 通道则每个 4096 字节块只需 ~48ms。

坑 2:换镜像时绝对不要发 RESET_STATE_MACHINE

早期每次换镜像都调 cmd_reset_state_machine()这是错的:UnlockTool 抓包里从没有这条命令,它的切换就是“直接再发一个 HELLO_RSP”。一复位就把设备打回 9008,后面全错。

坑 3:HELLO_REQ 心跳 ≠ 失败

设备会周期性地发 HELLO_REQ(心跳)。传完后第一个读到的若是 HELLO_REQ,不代表失败。一开始“看到第一个包是 HELLO_REQ 就判被拒、换镜像”是误判。正确做法:跳过心跳、继续多读几轮,直到拿到 END_TRANSFER 或 XML。

坑 4:每个 READ_DATA 响应必须“一次性写完”

Sahara 64 位模式的数据段是裸字节(无每包头)。若分 512 字节小块发,后续请求会错位、PBL 会一直等。必须 write(整块 4096/0x1000)

坑 5:HELLO_RSP 的保留字段必须全 0

48 字节里除了版本/命令,其余保留字段填非 0,PBL 会拒绝/解析错。

坑 6:magic 次数必须精确 95

少了状态机没被冲垮;多了可能把 PBL 撞进更错的状态。要和 UnlockTool 抓出来的 95 一致(默认 --retries 95)。

坑 7(最隐蔽):镜像被提取时坏了 4 个字节

症状:设备把 127 块 READ_DATA 全部照常要完(请求序列与 UnlockTool 抓包逐条一致),然后死活不回 END_TRANSFER(表现为“无回应”或复位成 HELLO_REQ)。

定位方法(字节级黄金对比):
1. 从 tap_dev.bin 抽出全部 156 个 READ_DATA 请求
2. 取 umi_elf_1 相(#6 起:0x0/0x40/0x1000/0x2000/0x5AB70/0x5BB70),按顺序把每个请求对应的文件块拼起来 = 497,532 字节
3. 与 tap_un.bin 里 UnlockTool 实际发送的同一段数据逐字节比对;
4. 全程只差 4 个字节

位置 我们的 umi_elf_1.elf UnlockTool 实际发送
文件偏移 0x48B5E 0F 77 B5 AC 00 14 71 00

根因:PBL 对整段做完整性校验,这 4 字节错了→校验不过→拒绝跳转但不报错(静默)。把 0x48B5E 改成 00 14 71 00 后,设备立刻 END_TRANSFER status=0x0 → Firehose → UFS 配置成功。

教训:拿到任何 vendor 提取的镜像,都要用官方流量做字节级对照,而不是只看“能不能下载”。edl_a11.py 已内置自动修正(KNOWN_FIXES,先备份 .orig)。

坑 8:镜像身份

  • DevPrg_Oppo_A11X_SDM665.trinket.elfumi_elf_0.elf 是同一个文件(sha256 相同)。
  • 第一相被请求 6 块就停是正常探测,不是失败——不要因此改逻辑。

5. 完整复现过程

5.1 准备(目标电脑,Windows 64 位)

  • 整个 oppo665new 文件夹拷贝过去(自带便携 Python 3.11,依赖已装,无需装 Python)。
  • 新电脑需先装一次 Qualcomm 9008 串口驱动(装过就一直生效)。

5.2 让手机进 9008

  1. 手机拔掉数据线、关机;
  2. 按住【音量+】和【音量-】不要松;
  3. 插入 USB;
  4. 设备管理器出现 Qualcomm HS-USB QDLoader 9008 (COMx) 即成功。

5.3 跑复现

双击 启动.bat,或命令行:

.\py\python.exe edl_a11.py boot

回车通过免责声明后,依次看到 5 步:

步骤 1/5  环境与端口         (找到 COMx)
步骤 2/5  进入 EDL / Sahara 握手
步骤 3/5  漏洞利用 · PBL EXPLOIT [危险]   ← 紫色横幅 + 独立进度条 (95×13 9A 9A 9A)
步骤 4/5  引导镜像发送        ← 进度条 [####] 50.2% 292KB/581.7KB PBL 已读 4 块
步骤 5/5  Firehose 初始化

5.4 成功判据

sahara | [END] image_id=0xD status=0x0 (SAHARA_STATUS_SUCCESS)
================================================================
  完成!  设备已在 Firehose   [UFS 已配置: UFS / 扇区 4096 / 最大包 16384]
================================================================

5.5 可选:自己再抓一遍做对照

想自己验证“字节级复现”,按同一方法论再做一次抓包:
1. 用 Frida hook + com0com 虚拟串口把 UnlockTool↔手机流量镜像成 tap_dev.bin / tap_un.bin
2. 抽出 READ_DATA 请求,按相重组出 497,532 字节;
3. 与 umi_elf_1.elf 逐字节对比,应只有 0x48B5E 这 4 字节差异(工具会自动改回);其余 0 差异

5.6 命令行参数

.\py\python.exe edl_a11.py boot --com COM16       只认这个串口
.\py\python.exe edl_a11.py boot --loader xxx.elf  指定 programmer
.\py\python.exe edl_a11.py boot --no-exploit      跳过 95 次 magic(不推荐)

6. 结论

  • 高通 PBL/Sahara 状态机的确存在可被 13 9A 9A 9A × 95 冲垮的校验缺陷,使未签名 programmer 也能被接受并跳转。
  • 抓包 + 字节级对照是复现一切厂商引导行为的可靠手段(与 h4ckm310n 解 Vivo 同源)。
  • 本工具把“挖洞结果”固化成:彩色分步 + 漏洞利用标注 + 引导进度条 + 自动修正关键 4 字节 + UFS 配置,拷到任意 64 位 Windows 电脑双击即用。
  • 最深的坑不是‘不会写代码’,而是‘镜像里 4 个字节错了’——这类静默失败必须靠黄金数据逐字节对比才能定位。

7. 参考

  • h4ckm310n《解锁Vivo Y31S》—— USB 抓包 + pyusb 复现:https://h4ckm310n.com/?p=106
  • 真实抓包文件(研究期生成,可复核):tap_dev.bin / tap_un.bin
此作者没有提供个人介绍。
最后更新于 2026-09-12