1. 第一章:从一块手机的上电说起
    1. 1.1 高通启动链全景
    2. 1.2 ARM 特权级与启动链的映射
    3. 1.3 安全存储:RPMB
  2. 第二章:ABL——承上启下的关键组件
    1. 2.1 ABL 的源码从哪来
    2. 2.2 ABL 包含了什么
    3. 2.3 ABL 的完整启动流程
    4. 2.4 OEM 的定制层
    5. 2.5 UEFI 环境:所有 EFI 共享的运行时世界
    6. EFI 之间的加载关系
    7. 2.6 UEFI Protocol:模块间的"接口契约"
    8. Protocol 是什么
    9. QCOM_VERIFIEDBOOT_PROTOCOL:高通的 Verified Boot 接口
  3. 第三章:GBL——新架构与新漏洞
    1. 3.1 什么是 GBL
    2. 3.2 受影响平台:仅 SM8850 (Snapdragon 8 Elite Gen 5)
    3. 3.3 致命缺陷:两层验证的断层
    4. 3.4 第二个缺陷:VerifiedBoot Protocol 的暴露
    5. 3.5 完整的利用链:三阶段攻击
    6. 阶段一:SELinux 绕过(Fastboot 命令行注入)
    7. 阶段二:提权写入 efisp(MQSAS Binder 服务)
    8. 阶段三:EFI 执行 + VBRwDeviceState 改写 RPMB
  4. 第四章:gbl_root_canoe 的核心思路
    1. 4.1 不是破解签名,而是搭便车
    2. 4.2 制作的到底是什么
    3. 4.3 为什么要"套娃":内嵌 ABL
    4. 4.4 为什么 Patch 1 要替换 "efisp" 字符串
    5. 4.5 完整的执行流
  5. 第五章:五阶段补丁的技术细节
    1. 5.1 工具链概览
    2. 5.2 自定义 ARM64 指令解码器
    3. 5.3 五个补丁阶段
    4. Patch 1:禁用 GBL 加载
    5. Patch 2:ADRL 指针重定向
    6. Patch 3:启动状态字节码修补
    7. Patch 4:反向指令追踪 + 源替换
    8. Patch 5:正向数据流追踪 + 汇补丁
    9. 5.4 数据流分析系统
  6. 第六章:构建系统与部署
    1. 6.1 项目精简了什么
    2. 6.2 三种构建模式
    3. 6.3 构建流程 (make build)
    4. 6.4 AUTO_PATCH_ABL 模式
    5. 6.5 部署
  7. 第七章:生态全景与安全启示
    1. 7.1 同一漏洞的不同利用方案
    2. 7.2 传统解锁 vs GBL 漏洞
    3. 7.3 漏洞的根源
    4. 7.4 受影响的范围
    5. 7.5 写在最后
  8. 参考资料
    1. 官方文档
    2. 项目与仓库
    3. 社区讨论与新闻报道

本文的主要内容由 GLM-5.1 整理撰写,人工仅在几个关键利用点给以引导并进行核对,不保证所有细节的准确性,如有错误欢迎在评论区指出。

文章基于对 gbl_root_canoe 项目的源码分析,结合 Qualcomm 官方 ABL 源码、XDA 社区讨论、以及多方技术资料,完整剖析这个项目的工作原理。该漏洞由 Xiaomi ShadowBlade 安全实验室发现并通过协调披露报告给 Qualcomm。

第一章:从一块手机的上电说起

当你按下手机电源键的那一刻,在屏幕亮起之前,一条精密的信任链已经在运转。这条链的设计目标只有一个:确保设备上运行的每一行代码都是被授权的

1.1 高通启动链全景

Qualcomm Snapdragon 芯片的启动过程是一条严格的自上而下的签名验证链:

PBL (Primary Boot Loader)
  │ 位置:芯片内部 Mask ROM,出厂烧死,绝对不可修改
  │ 职责:读取 eFuse 中的 OEM 公钥哈希,验证 XBL 签名
  │ 信任等级:信任根,整个安全体系的基石
  ↓ 签名验证 ✗ 失败 → 拒绝启动
XBL_SEC (eXtensible Boot Loader - Secure)
  │ 位置:xbl_sec 分区
  │ 签名:Qualcomm + OEM 双重 RSA 签名
  │ 职责:初始化 TrustZone (EL3),配置安全监控器
  ↓ 签名验证
XBL Core
  │ 位置:xbl / xbl_a / xbl_b 分区
  │ 签名:Qualcomm + OEM 双重签名
  │ 职责:初始化 DDR、电源管理,建立 UEFI DXE 环境
  ↓ 签名验证
ABL (Android Boot Loader)
  │ 位置:abl / abl_a / abl_b 分区
  │ 签名:签名策略因 OEM 配置而异
  │ 职责:Verified Boot、加载 boot.img、启动 Linux Kernel
  ↓
boot.img → Linux Kernel → Android

1.2 ARM 特权级与启动链的映射

ARM 架构定义了四个特权级(Exception Level),高通将启动链的每个阶段映射到不同的 EL:

EL3 ─ Secure Monitor / TrustZone
      ↑ PBL、XBL_SEC 运行在此
      ↑ 控制 RPMB 访问,持有 RPMB 密钥副本,计算 HMAC 签名
      ↑ 处理所有 SMC (Secure Monitor Call) 请求
      │
EL2 ─ Hypervisor (Gunyah)
      ↑ 虚拟化隔离
      │
EL1 ─ OS Kernel / ABL
      ↑ ABL 在此运行
      ↑ 可以通过 SMC 请求 EL3 的服务
      │
EL0 ─ 用户空间 (Android Apps)

关键点:ABL 运行在 EL1,这是一个高特权级但不是最高。它可以通过 SMC #0 指令向 EL3 的 TrustZone 发起请求——比如读写 RPMB 中的设备锁定状态。

1.3 安全存储:RPMB

设备是否"解锁"(bootloader unlocked)的状态存储在 RPMB(Replay Protected Memory Block)中。RPMB 是 UFS/eMMC 存储控制器中的一个特殊硬件区域——与普通存储分区不同,RPMB 的每次写入操作都需要 256 位 HMAC-SHA256 认证。

RPMB 认证密钥在设备制造阶段被同时写入存储控制器的安全区域和 TEE(QTEE)中——双方各持有一份密钥副本。当需要写入 RPMB 时,QTEE 使用其持有的密钥计算 HMAC-SHA256 签名,将数据与签名通过安全通道提交给存储控制器;存储控制器使用其内部存储的同一密钥独立计算并验证 HMAC,比对一致后才执行写入。

写入 RPMB 的唯一合法路径:
  EL1 (ABL) → SMC → EL3 (TrustZone/QTEE)
    → QTEE 使用其持有的 RPMB 密钥计算 HMAC-SHA256 签名
    → 将数据与签名提交给存储控制器
    → 存储控制器使用其内部密钥独立验证 HMAC → 比对通过 → 写入 RPMB

Linux Kernel 无法直接写 RPMB,TrustZone 会拒绝未授权的 SMC 请求。

这意味着:在正常情况下,设备解锁状态是不可篡改的——除非你能说服 TrustZone 帮你改。


第二章:ABL——承上启下的关键组件

ABL 是整条信任链中最复杂、定制化程度最高的组件,也是 gbl_root_canoe 项目的核心研究对象。

2.1 ABL 的源码从哪来

ABL 的源码是公开的。Qualcomm 在 CodeLinaro 平台上发布了基于 EDK2 (TianoCore) 的 ABL 源码包——QcomModulePkg

https://git.codelinaro.org/clo/la/abl/tianocore/edk2/
  分支: uefi.lnx.5.0.r49-rel
  目录: QcomModulePkg/

gbl_root_canoe 项目中的 edk2/QcomModulePkg/ 就是基于这个上游源码修改而来的。

2.2 ABL 包含了什么

上游 QcomModulePkg 是一个完整的启动加载方案,包含了从设备初始化到启动 Linux 的全部过程:

QcomModulePkg/
├── Application/LinuxLoader/         ← ABL 主入口
│   └── LinuxLoader.c (982 行)
├── Library/
│   ├── BootLib/                     ← 核心启动逻辑 (30+ 文件)
│   │   ├── BootLinux.c              加载 boot.img
│   │   ├── UpdateDeviceTree.c       更新 Device Tree
│   │   ├── UpdateCmdLine.c          构造 kernel cmdline
│   │   ├── DeviceInfo.c             设备信息
│   │   └── ...
│   ├── avb/                         ← Android Verified Boot 完整实现
│   │   ├── VerifiedBoot.c           AVB 主逻辑
│   │   ├── libavb/                  Google 的 libavb 库
│   │   └── ...
│   ├── FastbootLib/                 ← Fastboot 协议实现
│   │   ├── FastbootMain.c           主循环
│   │   ├── FastbootCmds.c           命令处理 (3664 行)
│   │   └── FastbootSSL/             RSA 签名验证 (BoringSSL)
│   ├── OpenDice/                    ← Open-DICE + BoringSSL (500+ 文件)
│   ├── aes/                         ← AES-GCM 加密
│   ├── lz4/                         ← LZ4 压缩
│   └── zlib/                        ← zlib 解压
├── Include/
│   ├── Library/                     (32 个头文件)
│   └── Protocol/                    (30+ 个 UEFI Protocol 定义)
└── build.config.msm.*               (50+ 个板级配置)

2.3 ABL 的完整启动流程

LinuxLoaderEntry()                          // 入口
  │
  ├── 硬件/环境初始化
  │   ├── GetRebootReason()                 // 判断重启原因
  │   ├── 设备信息初始化
  │   └── 显示初始化
  │
  ├── 分区选择
  │   ├── A/B slot 选择
  │   └── recovery / fastboot / 正常启动
  │
  ├── Verified Boot (Library/avb/)
  │   ├── 读取 vbmeta 分区
  │   ├── RSA 签名验证
  │   ├── boot.img 完整性校验
  │   └── 设备锁定状态检查
  │
  ├── 加载 boot.img
  │   ├── 从 boot_a / boot_b 分区读取
  │   ├── 解压 (LZ4 / gzip / lzma)
  │   └── 解析 kernel / ramdisk / dtb
  │
  ├── Device Tree 更新 (Library/BootLib/)
  │   ├── 注入 cmdline 参数
  │   │   ├── androidboot.verifiedbootstate=green/orange/red
  │   │   ├── androidboot.vbmeta.device_state=locked/unlocked
  │   │   └── dm-verity 参数
  │   ├── 添加内存映射
  │   └── 添加 display 信息
  │
  └── 跳转执行 Linux Kernel
      └── kernel(entry_point)(dtb, ...)     // ABL 的终点

2.4 OEM 的定制层

虽然 Qualcomm 提供了 ABL 的基础源码,但各 OEM 会在此基础上添加大量定制代码:

  • Xiaomi:添加 Mi-Token Protocol 调用(RSA 服务器签名验证解锁)、自定义 Fastboot 命令
  • Samsung:KNOX 计数器、自定义 Download 模式 (Odin);此外在更底层的 PBL/XBL 阶段额外部署了 SSBK(Samsung Secure Boot Key)验证和 Knox Verified Boot (KVB)
  • OnePlus:自定义解锁流程

这些 OEM 的定制代码不开源,只存在于各厂商编译好的 ABL 二进制中。这也就是为什么 gbl_root_canoe 选择二进制补丁而非源码修改——OEM 的定制层无法从源码层面获取。

2.5 UEFI 环境:所有 EFI 共享的运行时世界

要理解后续的漏洞机制,必须先理解 UEFI 环境的本质——它不是操作系统,也不是裸金属,而是一个单地址空间、共享内存、协作式的固件运行环境。

XBL 本身就是一个基于 EDK2 的完整 UEFI 固件,在运行 DXE 阶段时建立了一个 UEFI 环境,包含:

  • Boot Services (gBS):内存分配、Protocol 管理、事件定时器、镜像加载等
  • Runtime Services (gRT):变量存取、重启、时间(OS 启动后仍然可用)
  • Protocol 数据库:全局可见的接口注册表(类似 COM 组件注册表)
  • 内存池:所有 EFI 共享同一块物理内存

EFI 之间的加载关系

UEFI 中一个 EFI 加载另一个 EFI 的方式是 LoadImage + StartImage

EFI A (如 ABL, LinuxLoader.efi)
  运行中...
  │
  ├── gBS->LoadImage(..., buffer, size, &child_handle)
  │     ├── 分配内存,解析 PE/COFF 头
  │     ├── 重定位,加载到内存
  │     └── 返回 child_handle (还没执行)
  │
  ├── gBS->StartImage(child_handle, NULL, NULL)
  │     ├── 调用 EFI B 的入口函数
  │     │  ┌─────────────────────────────────────┐
  │     │  │  EFI B (如 GBL / 自制 EFI)           │
  │     │  │  运行中...                           │
  │     │  │  可以访问 gBS, gRT, 所有 Protocol    │
  │     │  │  可以再 LoadImage + StartImage       │
  │     │  │  return EFI_SUCCESS;  ←── 执行完毕   │
  │     │  └─────────────────────────────────────┘
  │     └── StartImage 返回 (控制权回到 EFI A)
  │
  └── EFI A 继续执行...

关键点

  1. 父 EFI 不会退出StartImage 本质上是函数调用。EFI A 的栈帧、全局变量、已分配的内存都还在。
  2. 共享内存空间:EFI A 和 EFI B 在同一个物理地址空间运行,没有内存隔离
  3. 共享 Protocol 数据库:EFI B 可以通过 gBS->LocateProtocol() 找到任何已注册的 Protocol——不管是谁注册的。
  4. 同步调用StartImage 不会返回,直到 EFI B 的入口函数返回或调用 gBS->Exit()

这意味着:当 ABL 通过 LoadImage + StartImage 加载 efisp 中的自制 GBL 时,自制 GBL 继承了整个 UEFI 环境——XBL 注册的所有 Protocol、ABL 分配的内存、全部的 Boot Services。它和 ABL 处于完全相同的特权级,没有任何隔离。

2.6 UEFI Protocol:模块间的"接口契约"

Protocol 是什么

UEFI Protocol 是模块间通信的标准机制。本质上就是一个 C 结构体,里面放了一组函数指针,通过 GUID 标识,注册到 Boot Services 的全局数据库中。

注册方:  gBS->InstallProtocolInterface(&handle, &guid, ..., &protocol_struct)
调用方:  gBS->LocateProtocol(&guid, NULL, (VOID **)&protocol_struct)
调用:    protocol_struct->某个函数(...)

UEFI 规范定义了数百个标准 Protocol(块 I/O、网络、USB、显示等)。芯片厂商和 OEM 可以注册自定义 Protocol 来暴露硬件特有的功能。

QCOM_VERIFIEDBOOT_PROTOCOL:高通的 Verified Boot 接口

这个 Protocol 的头文件在 ABL 公版源码中可以找到:QcomModulePkg/Include/Protocol/EFIVerifiedBoot.h

// GUID: 8e5eff91-21b6-47d3-af2b-c15a01e020ec
struct _QCOM_VERIFIEDBOOT_PROTOCOL {
    UINT64 Revision;

    // 读/写设备锁定状态(RPMB 中的 DevInfo)
    QCOM_VB_RW_DEVICE_STATE   VBRwDeviceState;

    // 初始化 Verified Boot
    QCOM_VB_DEVICE_INIT       VBDeviceInit;

    // 发送 Root of Trust 给 Keymaster
    QCOM_VB_SEND_ROT          VBSendRot;

    // 通知 TrustZone 启动完成(milestone)
    QCOM_VB_SEND_MILESTONE    VBSendMilestone;

    // 验证分区镜像签名
    QCOM_VB_VERIFY_IMAGE      VBVerifyImage;

    // 重置 Keymaster 状态
    QCOM_VB_RESET_STATE       VBDeviceResetState;

    // 查询设备是否安全
    QCOM_VB_IS_DEVICE_SECURE  VBIsDeviceSecure;

    // 获取当前启动状态 (GREEN/ORANGE/YELLOW/RED)
    QCOM_VB_GET_BOOT_STATE    VBGetBootState;

    // 获取证书指纹
    QCOM_VB_GET_CERT_FINGERPRINT VBGetCertFingerPrint;

    // 查询 Keymaster 是否启用
    QCOM_VB_IS_KEYMASTER_ENABLED VBIsKeymasterEnabled;
};

定义在 ABL 源码中,但实现在 XBL 中。 XBL 初始化时通过 gBS->InstallProtocolInterface() 将这个 Protocol 注册到全局数据库,函数指针指向 XBL 内部的代码——这些代码最终通过 SMC (Secure Monitor Call) 陷入 EL3 的 TrustZone 来执行实际操作(如读写 RPMB)。

注册方: XBL (EL3/EL2 初始化时)
  InstallProtocolInterface(QCOM_VERIFIEDBOOT_PROTOCOL)
    ├── VBRwDeviceState → 内部执行 SMC → TrustZone 读写 RPMB
    ├── VBVerifyImage   → 内部执行 SMC → TrustZone 验证签名
    └── ...

调用方: ABL (EL1, 启动流程中)
  LocateProtocol(QCOM_VERIFIEDBOOT_PROTOCOL)  → 找到函数指针表
  → VBRwDeviceState(READ_CONFIG, ...)         → 读取设备锁定状态
  → VBVerifyImage(...)                        → 验证 boot.img 签名

漏洞的关键:正常流程中只有 ABL 自己会调用这个 Protocol。但由于 UEFI 环境没有进程隔离,任何在 EL1 运行的代码——包括通过 efisp 加载的未签名 EFI——都可以通过同样的 gBS->LocateProtocol() 找到它,然后调用 VBRwDeviceState(WRITE_CONFIG, ...) 改写 RPMB 中的设备锁定状态。Protocol 本身没有做调用者身份校验,因为 UEFI 的设计假设所有已加载的 EFI 都是可信的。


第三章:GBL——新架构与新漏洞

在 Snapdragon 8 Elite Gen 5 平台上,高通引入了 GBL (Generic Boot Loader) 架构。

3.1 什么是 GBL

GBL (Generic Bootloader) 是 Google 主导的新一代通用引导加载程序,用 Rust 编写,目标是替代各厂商各自实现的高度碎片化的 bootloader,统一 Android 生态的引导流程。

根据 Google 官方文档,GBL 包含以下组件:

  • 核心 Android 启动逻辑:主程序循环、启动模式检测、内核加载
  • Fastboot:通信协议和设备刷写工具
  • Vendor 扩展:厂商可通过自定义 UEFI Protocol 扩展 GBL 功能
  • UEFI Protocol 处理器:实现必需的 UEFI 协议(块 I/O、内存分配、随机数生成等)
  • Android 专用 UEFI Protocol:AVB、Fastboot、Slot 选择、OS 配置等

GBL 被编译为一个独立的 UEFI 应用程序(约 2MB),存储在 FAT 分区中,路径为 /EFI/BOOT/BOOTAA64.EFI。官方规范要求设备创建两个 8MB 的 FAT 分区 android_esp_aandroid_esp_b 用于存放 GBL。

从 Android 16 开始,Google 强烈推荐所有 ARM-64 设备部署 GBL。

3.2 受影响平台:仅 SM8850 (Snapdragon 8 Elite Gen 5)

需要明确的是,这个漏洞目前仅影响 Snapdragon 8 Elite Gen 5 (SM8850) 平台

SM8850 是首个在 ABL 中实现 GBL 加载机制的高通 SoC。在 ABL 的初始化代码中,新增了对 efisp(或 efisp_aefisp_b)分区的扫描逻辑:

// ABL 中的伪代码 (通过逆向 OEM 固件获得)
PartiSelectFilter Filter;
Filter.PartitionLabel = L"efisp"; // 同时匹配 efisp_a, efisp_b
// ...
if (found) {
    LoadGenericBootloader(HandleInfoList[0].Handle);
}

大多数厂商当前的 efisp 分区是空的——ABL 找不到 GBL 就跳过这一步,正常启动。这反而说明 GBL 机制在 SM8850 上是新增的,但签名验证没有跟上。

SM8850 启动链:
  XBL → ABL → 扫描 efisp 分区
                   ├── 找到 EFI → 尝试加载(漏洞所在)
                   └── 未找到 → 正常启动 boot.img → Linux

3.3 致命缺陷:两层验证的断层

GBL 之所以可以被未验证地加载,是两套独立验证系统之间的断层造成的。

Qualcomm 设备上存在两套独立的签名验证机制:

验证机制覆盖范围验证方式
Qualcomm Secure Boot(私有)PBL → XBL_SEC → XBL → ABLELF 哈希段 + X.509 证书链,根植于 eFuse
UEFI Secure Boot(标准)UEFI 环境中加载的所有 EFI 应用PK/KEK/db 密钥层级 + PE/COFF Authenticode 签名

断层就在这里

  1. Qualcomm 的私有验证链到 ABL 为止。ABL 之后的 GBL 是通过 UEFI 的 gBS->LoadImage() 加载的,走的是 UEFI 路径,不是 ELF 哈希段验证路径。因此 GBL 不在 Qualcomm 私有验证链的保护范围内
  2. UEFI Secure Boot 本应保护这个加载路径,但实际处于失效状态。OEM 出厂设备未配置 UEFI Platform Key (PK)。当 PK 为空时,EDK2 固件将 SetupMode 设为 1、SecureBoot 设为 0;而 DxeImageVerificationHandler 在检查 SecureBoot 变量时,若发现其未启用(值不为 1),直接返回 EFI_SUCCESS 跳过所有签名验证(但仍会检查 DBX 黑名单,拒绝已知的恶意哈希)。由于攻击者的自定义 EFI 不在 DBX 中,实际效果等同于无验证通过。

用公开 PoC 的作者的话说:

"Qualcomm uses its own verification instead of UEFI secure boot. But GBL is unsigned."

这句话描述的是架构层面的缝隙:Qualcomm 用自己的验证而非 UEFI Secure Boot,而 GBL 恰好不在 Qualcomm 验证链的覆盖范围内。OEM 不配置 PK 则让 UEFI Secure Boot 这道防线也形同虚设。

正常预期:
  ABL 扫描 efisp → 发现 EFI → UEFI Secure Boot 验证签名 → ✗ 非官方 → 拒绝
  或
  ABL 扫描 efisp → 发现 EFI → Qualcomm 验证链覆盖 → ✗ 未签名 → 拒绝

实际行为:
  Qualcomm 验证链: 仅覆盖到 ABL,GBL 不在保护范围内  → ✗ 不适用
  UEFI Secure Boot: PK 未配置 → Setup Mode (SecureBoot=0) → 跳过签名验证 → ✓ 直接加载

结果:任何人只要能写入 efisp 分区,ABL 就会不经验证地加载并执行其中的代码。

3.4 第二个缺陷:VerifiedBoot Protocol 的暴露

XBL 在初始化阶段通过 UEFI Boot Services 注册了 QCOM_VERIFIEDBOOT_PROTOCOL,提供 VBRwDeviceState() 接口。任何在 EL1 运行的代码都可以调用它:

// 定位 Protocol
gBS->LocateProtocol(&gEfiQcomVerifiedBootProtocolGuid, NULL, &VbProtocol);

// 读取设备状态 (从 RPMB,通过 SMC 陷入 TrustZone)
VbProtocol->VBRwDeviceState(VbProtocol, READ_CONFIG, Buf, sizeof(DevInfo));

// 改写设备状态 (写入 RPMB,TrustZone 在 pre-boot 阶段允许)
VbProtocol->VBRwDeviceState(VbProtocol, WRITE_CONFIG, Buf, sizeof(DevInfo));

在 ABL 的 pre-boot 阶段,TrustZone 允许 VerifiedBoot Protocol 通过 SMC 读写 RPMB 中的设备状态。这意味着一个在 EL1 执行的未签名 EFI 可以直接改写设备的锁定状态

3.5 完整的利用链:三阶段攻击

利用这个漏洞并不只是"写个文件到 efisp"这么简单。在以 Xiaomi 为例的真实攻击场景中,完整的利用链包含三个阶段:

阶段一:SELinux 绕过(Fastboot 命令行注入)

adb reboot bootloader
fastboot oem set-gpu-preemption-value 0 androidboot.selinux=permissive
fastboot continue

fastboot oem set-gpu-preemption-value 是一个 GPU 调度配置命令——它将用户输入拼接到 msm_kgsl.preempt_enable= 字符串上,写入 UEFI 变量 GpuConfiguration,最终注入内核命令行。但它没有对额外参数做输入过滤。通过在参数末尾追加 androidboot.selinux=permissive,可以让内核以 SELinux Permissive 模式启动——此时所有 SELinux 策略变为仅告警不阻止。

阶段二:提权写入 efisp(MQSAS Binder 服务)

# 推送自制 EFI 到设备
adb push gbl_efi_unlock.efi /data/local/tmp

# 通过 Xiaomi MQSAS 服务的 IMQSNative Binder 接口以 root 权限执行 dd
adb shell service call miui.mqsas.IMQSNative 21 \
  i32 1 s16 "dd" i32 1 \
  s16 'if=/data/local/tmp/gbl_efi_unlock.efi of=/dev/block/by-name/efisp' \
  s16 '/data/mqsas/log.txt' i32 60

miui.mqsas.IMQSNative 是 Xiaomi HyperOS 的一个系统级诊断服务,以 UID 0 (root) 运行。其内部方法 #21 可以执行任意 shell 命令。由于 SELinux 已处于 Permissive 模式,Binder IPC 调用不会被阻止。攻击者利用这个服务以 root 权限执行 dd,将自制 EFI 写入 efisp 分区。

阶段三:EFI 执行 + VBRwDeviceState 改写 RPMB

重启后,ABL 从 efisp 加载自制的未签名 EFI。该 EFI 定位 QCOM_VERIFIEDBOOT_PROTOCOL,调用 VBRwDeviceState(WRITE_CONFIG) 改写 RPMB 中的设备锁定状态——永久解锁 bootloader

完整攻击链:
  fastboot 注入 cmdline → SELinux Permissive
    → MQSAS Binder 服务以 root 执行 dd → 写入 efisp
      → 重启 → ABL 加载未签名 EFI(Setup Mode 跳过验证)
        → EFI 调用 VBRwDeviceState(WRITE_CONFIG) → SMC → TrustZone 写 RPMB
          → bootloader 永久解锁

前提条件:这个完整链需要三个条件同时满足——(1) 设备使用 Qualcomm ABL 且未额外部署防御层(Samsung 虽也使用 Qualcomm ABL,但通过 Odin/Download 模式 + SELinux Enforcing + SSBK/KVB + XBL_SEC/TME(Trust Management Engine)多层防御阻断了利用路径)、(2) 存在可利用的写入路径(如 MQSAS 服务 + SELinux 绕过)、(3) 设备固件尚未应用 Qualcomm 的修复补丁。


第四章:gbl_root_canoe 的核心思路

理解了漏洞原理后,我们来看 gbl_root_canoe 是如何工程化地利用这个漏洞的。

4.1 不是破解签名,而是搭便车

首先要明确一点:这个项目没有破解任何签名验证

PBL → XBL_SEC → XBL → ABL 这条链上的签名验证都是完好的。项目做的是利用 ABL → GBL 这一步的签名验证缺失,在 efisp 分区放入自制的 EFI 程序。

4.2 制作的到底是什么

gbl_root_canoe 编译出来的产物——ABL_with_superfastboot.efi——不是 ABL,而是 GBL

这个自制 GBL 的逻辑非常简单:

自制 GBL 启动后:
  1. 等待 5 秒,检测音量键
     ├── 音量下 → 进入 Fastboot 模式 (SuperFastboot)
     ├── 音量上 → 进入 Recovery
     └── 超时   → 加载内嵌的修补后 ABL
  2. 修补后的 ABL 启动,绕过安全验证,加载 boot.img

4.3 为什么要"套娃":内嵌 ABL

为什么不直接在 GBL 里改写 RPMB 就完了?因为单纯的解锁会导致各种副作用(TEE 假死等)。更好的方案是:

让设备以为自己是"锁定"的,但实际上验证已被绕过。

这需要在 ABL 的二进制层面做精确的指令修补——而这只能针对 OEM 编译好的特定 ABL 二进制来做。所以项目的做法是:

  1. 从设备提取原始 ABL 二进制
  2. 对其做精确的 5 个补丁
  3. 将补丁后的 ABL 嵌入自制的 GBL 中
  4. 将 GBL 写入 efisp 分区

4.4 为什么 Patch 1 要替换 "efisp" 字符串

这是整个方案中最精妙的细节。

如果不做这个补丁,会发生无限循环

ABL (原始, abl 分区)
  → 扫描 efisp → 加载自制 GBL
    → GBL 加载内嵌的原始 ABL (未修补)
      → 原始 ABL 也扫描 efisp → 又加载 GBL
        → GBL 又加载 ABL → 又扫描 efisp → ...无限循环

Patch 1 将内嵌 ABL 二进制中的 UTF-16LE 字符串 "efisp" 替换为 "nulls"

// patchlib.h: patch_abl_gbl()
CHAR8 target[]     = { 'e',0, 'f',0, 'i',0, 's',0, 'p',0 };
CHAR8 replacement[] = { 'n',0, 'u',0, 'l',0, 'l',0, 's',0 };

修补后的 ABL 找不到名为 nulls 的分区,就跳过 GBL 加载步骤,直接走正常的 boot.img 启动路径。断开循环,一次加载。

4.5 完整的执行流

设备上电
  ↓
PBL → XBL_SEC → XBL (全部签名验证通过)
  ↓
ABL (原始, abl 分区, OEM 签名)
  → 扫描 GPT → 发现 efisp 分区
  → LoadImage() → PK 未配置 → Setup Mode (SecureBoot=0) → 跳过签名验证
  ↓
自制 GBL (ABL_with_superfastboot.efi, 写入 efisp 分区)
  → 等待 5 秒按键
  → 超时 → LoadIntegratedEfi()
  ↓
内嵌的修补后 ABL (编译时通过 ABL.h 嵌入)
  → Patch 1: "efisp" → "nulls"         不再扫描 efisp,断开循环
  → Patch 2: ADRL 指针重定向             unlocked→locked 指针
  → Patch 3: 启动状态字节码修补
  → Patch 4: LDRB → MOV #1             使锁定检查跳过,走解锁路径
  → Patch 5: STRB → WZR                使状态写入无效
  ↓
  → 加载 boot.img → Linux Kernel
  → 系统以为设备是 "locked",但安全验证已被绕过

第五章:五阶段补丁的技术细节

gbl_root_canoe 最核心的技术含量在 tools/patchlib.h 中——一个 569 行的 ARM64 二进制补丁引擎。

5.1 工具链概览

文件行数语言功能
extractfv.py198Python从 abl.img 提取 LinuxLoader.efi
patch_abl.c35C补丁器入口
patchlib.h569C5 阶段补丁引擎核心
arm64_inst_decoder.h323C自定义 ARM64 指令解码器
types.hCUEFI/POSIX 双模式抽象层

5.2 自定义 ARM64 指令解码器

要对 ABL 二进制做精确的指令级补丁,首先需要能"读懂" ARM64 指令。项目实现了一个轻量级的 ARM64 指令解码器 (arm64_inst_decoder.h),支持:

  • Load/Store: LDRB, STRB, LDR, STR(各种寻址模式)
  • Move: MOV (ORR form), MOVZ
  • 地址生成: ADRP, ADD
  • 控制流: CBZ, CBNZ, B, BL, RET, NOP
  • 比较: CMP, CSET
  • 位操作: UBFM

5.3 五个补丁阶段

每个补丁可以通过 DISABLE_PATCH_N 宏独立控制:

Patch 1:禁用 GBL 加载

目标: UTF-16LE 字符串 "efisp" → "nulls"
原理: 简单的字节替换,让修补后的 ABL 找不到 efisp 分区
难度: ★☆☆☆☆

Patch 2:ADRL 指针重定向

目标: 扫描 ADRP+ADD 指令三元组
      找到指向 "unlocked"/"locked"/"androidboot.vbmeta.device_state" 的指针
      将 "unlocked" 指针重定向到 "locked"
原理: ARM64 中字符串引用通过 ADRP+ADD 实现(页对齐地址+偏移)
      通过模式匹配找到这些指令,修改 ADD 的偏移量
效果: 系统在查询设备状态时,永远读到 "locked"
难度: ★★★☆☆

Patch 3:启动状态字节码修补

目标: 通过字节码模式定位启动验证状态检查,修改条件跳转
原理: 固定的字节序列模式匹配,定位后修改分支指令
难度: ★★☆☆☆

Patch 4:反向指令追踪 + 源替换

目标: 从锚点指令反向追踪 LDRB 指令链,将源替换为 MOV Wn, #1
原理:
  1. 从已知锚点开始
  2. 反向扫描 LDRB 指令(支持栈弹跳:寄存器 → 栈 → 寄存器)
  3. 找到读取锁定状态的 LDRB 指令
  4. 将其替换为 MOV Wn, #1(硬编码为"已解锁")
效果: 使 ABL 跳过锁定检查分支,走解锁路径;结合 Patch 2/3/5 实现表面显示锁定但实际绕过验证
难度: ★★★★☆

Patch 5:正向数据流追踪 + 汇补丁

目标: 追踪寄存器/栈的数据流传播,定位最终的 STRB 写入点
      将源寄存器替换为 WZR(零寄存器)
原理:
  1. 实现了编译器级别的数据流分析
  2. 追踪三种位置:LOC_REG (寄存器)、LOC_STK64 (64位栈槽)、LOC_STK8 (8位栈槽)
  3. 支持 spill/reload 追踪、寄存器传播
  4. 最多追踪 256 个活跃位置
  5. 找到最终写入设备状态的 STRB 指令
  6. 将源操作数替换为 WZR,使写入值恒为 0
效果: 即使代码试图写入"已解锁"状态,实际写入的也是 0(锁定)
难度: ★★★★★

5.4 数据流分析系统

Patch 5 中的 DataLoc 实现了真正的编译器级数据流分析:

typedef enum { LOC_REG, LOC_STK64, LOC_STK8 } DataLocType;

typedef struct {
    DataLocType type;
    int         val;    // 寄存器号或栈偏移
} DataLoc;

这允许补丁引擎精确追踪一个值从加载、经过中间运算、到最终存储的完整路径——就像一个简化版的编译器后端。


第六章:构建系统与部署

6.1 项目精简了什么

gbl_root_canoe 从 Qualcomm 上游 QcomModulePkg 中删除了大量不需要的组件:

删除的组件原因
Library/avb/ (AVB 签名验证)真正的启动由内嵌 ABL 完成
Library/OpenDice/ (500+ 文件)不需要密码学证书链
Library/BootLib/ (30 文件)不需要加载 boot.img
Library/aes/, Library/lz4/, Library/zlib/不需要压缩/加密
FastbootSSL/ (BoringSSL)不需要 SSL 验证

新增的代码才是项目的核心:

新增功能
WaitForVolumeDownKey()5 秒按键等待
BootEfiImage()加载 EFI 到内存并执行
LoadIntegratedEfi()加载内嵌的修补后 ABL
ScanAndFindPe32() 系列从 abl 分区提取 PE32
#include "ABL.h"编译时嵌入的 ABL 二进制
#include "patchlib.h"运行时动态补丁(AUTO_PATCH_ABL 模式)

6.2 三种构建模式

make 目标产物策略
buildABL_with_superfastboot.efi预修补 ABL → 嵌入 GBL → 写入 efisp
build_genericgeneric_superfastboot.efiGBL 运行时从 abl 分区读取并动态修补
build_superfbonlysuperfastboot.efi纯 Fastboot 工具,无内嵌 ABL

6.3 构建流程 (make build)

1. clean                                    清理旧产物
   ↓
2. extractfv.py ← images/abl.img           从原始 ABL 提取 LinuxLoader.efi
   ↓
3. mv → ABL_original.efi                   保留备份
   ↓
4. gcc → 编译 patch_abl.c                  编译补丁器
   ↓
5. patch_abl → ABL.efi                     执行 5 阶段二进制补丁
   ↓
6. xxd -i → ABL.h                          将补丁后的 ABL 转为 C 头文件
   ↓
7. cp Conf/ → edk2/Conf/                   配置 EDK2 构建环境
   ↓
8. EDK2 build (CLANG/LLD, AARCH64)          交叉编译自制 GBL
   ↓
9. 输出: dist/ABL_with_superfastboot.efi

6.4 AUTO_PATCH_ABL 模式

generic_superfastboot.efi 使用了一种更聪明的策略——不在编译时嵌入 ABL,而是运行时从设备的 abl_a / abl_b 分区动态读取:

STATIC VOID LoadIntegratedEfi(VOID) {
#ifndef AUTO_PATCH_ABL
    // 非动态模式:启动编译时嵌入的 ABL
    BootEfiImage(dist_ABL_efi, dist_ABL_efi_len);
#else
    CHAR8* buffer;
    UINT32 size;
    // 1. 从设备 abl 分区读取原始 ABL
    LoadAblFromPartition(&buffer, &size);
    // 2. 在内存中执行同样的 5 个补丁
    if (!PatchBuffer(buffer, size)) {
        FreePool(buffer);
        return;
    }
    // 3. 直接启动修补后的 ABL
    BootEfiImage(buffer, size);
#endif
}

这个模式的好处是不需要预先提取设备的 ABL 固件,一个 GBL 镜像可以适配多台设备。

6.5 部署

最终产物需要写入设备的 efisp 分区。写入方式取决于设备状态:

  • 已解锁设备:可通过 fastboot flash efisp ABL_with_superfastboot.efi 直接写入
  • 未解锁设备(实际攻击场景):需要通过完整的利用链获得写入权限(如 MQSAS 提权 + dd 写入),或通过 EDL (9008) 模式写入
# 已解锁设备的简单部署
fastboot flash efisp ABL_with_superfastboot.efi

# 未解锁设备需通过利用链提权后使用 dd 写入
dd if=ABL_with_superfastboot.efi of=/dev/block/by-name/efisp

重启后,ABL 扫描到 efisp 分区中的自制 GBL,不验证签名直接加载——信任链在此断裂。


第七章:生态全景与安全启示

7.1 同一漏洞的不同利用方案

GBL 漏洞在被公开后出现了多种利用方案,工程化程度差异巨大:

方案来源做法工程质量
gbl_root_canoeLittlenine (XDA)自定义 ARM64 解码器 + 5 阶段数据流分析补丁 + 完整 EDK2 构建管线★★★★★
VbRwStateApp.c PoCkasnria00160 行 C,直接调用 VBRwDeviceState(WRITE_CONFIG)★★☆☆☆
tryigit.dev 文章安全博客Fastboot cmdline 注入 + Binder IPC + GBL 加载的完整技术分析★★★☆☆
发现归属:此漏洞由 Xiaomi ShadowBlade 安全实验室通过协调披露发现并报告给 Qualcomm。公开 PoC 由独立研究者 kasnria001 于 2026 年 3 月 4 日发布在 GitHub。Littlenine 在 XDA 指出公开 PoC "只写入两个寄存器,没有退出函数,会导致 TEE 损坏和指纹校准丢失",而他的方案通过精确的指令级补丁避免了这些问题。

7.2 传统解锁 vs GBL 漏洞

以 Xiaomi 为例,传统的 Bootloader 解锁需要经过严格的服务器验证:

传统 Xiaomi 解锁:
  fastboot getvar token
    → ABL 调用 XBL 中的 Mi-Token Protocol
    → 生成 token (随机数 + 设备代号 + 序列号)
    → base64 编码
  → 发送到 Xiaomi 服务器
    → 服务器用私钥签名
    → 返回 RSA 签名 (256 字节)
  → fastboot oem unlock <签名>
    → ABL 调用 Mi-Token 验证签名
    → 使用 XBL 中硬编码的 Xiaomi 公钥
    → RSA 验证通过 → 写入 RPMB 解锁状态

  结论: 没有 Xiaomi 的私钥,无法伪造签名,无法解锁

GBL 漏洞解锁:
  写入自制 EFI 到 efisp 分区
    → ABL 加载并执行(不验证签名)
    → EFI 直接调用 VerifiedBoot Protocol
    → VBRwDeviceState(WRITE_CONFIG) → SMC → TrustZone 写 RPMB
    → 解锁

  结论: 完全绕过 Xiaomi 的 RSA 签名验证,不需要服务器授权

7.3 漏洞的根源

这个漏洞不是某一行代码的 bug,而是架构演进中的安全断层

  1. Qualcomm 私有验证链到 ABL 为止,GBL 不在保护范围内:PBL → XBL → ABL 的签名验证使用 Qualcomm 自有的 ELF 哈希段 + X.509 证书链机制,根植于 eFuse。但 GBL 是通过 UEFI LoadImage() 路径加载的,不在 ELF 验证域内。新增加载阶段的设计者假设 UEFI Secure Boot 会兜底,但实际上——
  2. UEFI Secure Boot 因 PK 未配置而形同虚设:OEM 出厂设备未配置 Platform Key,导致 SecureBoot UEFI 变量为 0 或不存在。EDK2 的 DxeImageVerificationHandler 在检查该变量时,若发现其未启用(值不为 1),直接返回 EFI_SUCCESS 跳过所有签名验证(但仍会检查 DBX 黑名单,不过攻击者的自定义 EFI 不在 DBX 中,实际效果等同于无验证通过)——GBL 正好落入两套验证系统之间的缝隙:Qualcomm 的不覆盖它,UEFI 的被跳过了。
  3. VerifiedBoot Protocol 在 pre-boot 阶段缺乏调用者鉴权:XBL 注册的 QCOM_VERIFIEDBOOT_PROTOCOL 提供了直接操作 RPMB 的能力,但在 pre-boot 阶段对调用者没有做身份校验。UEFI 的设计假设所有已加载的 EFI 都是可信的——但前提已经被第 1、2 点打破。
  4. OEM 写入路径存在缝隙:即使 SELinux 在 Enforcing 模式下会阻止写入 efisp 分区,但通过 Fastboot OEM 命令注入(如 fastboot oem set-gpu-preemption-value 的参数未过滤)可以绕过 SELinux,再通过厂商特权服务(如 Xiaomi 的 MQSAS IMQSNative Binder 服务)获得 root 级别的写入权限。

7.4 受影响的范围

  • 受影响平台仅 Snapdragon 8 Elite Gen 5 (SM8850)——这是首个在 ABL 中引入 efisp 分区扫描和 GBL 加载机制的 SoC
  • 已确认受影响的设备(多个独立新闻源验证):

    • Xiaomi 17 / 17 Pro / 17 Pro Max / 17 Ultra
    • POCO F8 Ultra
    • Redmi K90 Pro Max
    • OnePlus 15
    • OnePlus Ace 6T / Redmagic 11 系列 / Nubia Z80 Ultra
  • Samsung 不受影响:Samsung Galaxy 系列即使使用同款 Snapdragon 8 Elite Gen 5 芯片,也不受此漏洞影响。Samsung Snapdragon 设备实际上仍然使用 Qualcomm ABL 引导链,但在此基础上增加了 Samsung 自有的 S-Boot 安全层。虽然 Samsung 设备也存在类似的 uefi_a/uefi_b 分区(相当于 efisp),但 Samsung 用 Odin/Download 模式替代了标准 Fastboot(set-gpu-preemption-value 注入无法使用),SELinux 始终 Enforcing 阻止写入,且 bootloader 锁定通过 SSBK (Samsung Secure Boot Key)Knox Verified Boot (KVB) 在硬件层面强制执行
  • 修复状态:Qualcomm 于 2026 年 3 月初向 OEM 厂商提供了修复补丁。Qualcomm 在 2026 年 3 月 14 日的官方声明中确认:

    "Regarding their GBL-related research, fixes were made available to our customers in early March 2026. We encourage end users to apply security updates as they become available from device makers."

    漏洞的原始发现者为 Xiaomi ShadowBlade 安全实验室,Qualcomm 在同一份声明中对他们使用了协调披露(Coordinated Disclosure)的做法表示了赞赏。据报道 Xiaomi HyperOS 3.0.304.0 版本包含针对该漏洞链的修复

7.5 写在最后

gbl_root_canoe 是一份高质量的安全研究工程。它的价值不仅在于利用了漏洞,更在于展示了如何:

  • 用自定义的 ARM64 指令解码器实现精确的二进制分析
  • 用编译器级的数据流追踪实现指令级补丁
  • 将攻击者控制的二进制无缝嵌入正常的启动链
  • 通过 EDK2 构建系统产出可与 OEM 固件协同工作的 UEFI 应用

理解这个项目,就是理解了现代 Android 设备启动安全的攻击面,以及"信任链"这个概念在实践中有多脆弱——一条链的强度取决于最薄弱的环节,而在这个案例中,那个环节就是"ABL 加载 efisp 时不验证签名"这一个设计决策


参考资料

官方文档

项目与仓库

社区讨论与新闻报道