gbl_root_canoe隐藏BL状态原理分析"

admin 2026-10-22 08:26:56 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

cover_image

gbl\_root\_canoe隐藏BL状态原理分析

原创

非虫
非虫

软件安全与逆向分析

2026年6月11日 18:31
湖北

在小说阅读器读本章

去阅读

gbl\_root\_canoe隐藏BL状态原理分析

高通骁龙8Gen5(内部代号 Sun/Canoe)平台的Android设备获取Root权限需要先解锁Bootloader(BL)。解锁BL后,TEE(Trusted Execution Environment)会将设备永久标记为不可信状态,导致银行类应用、支付应用及完整性检测机制(SafetyNet/Play Integrity)识别到设备已解锁而拒绝运行。

gbl\_root\_canoe实现BL解锁与TEE状态隐藏、在获得Root权限的前提下,利用GBL(Generic Bootloader)加载机制中的一个行为漏洞,将经过精准二进制修补的ABL(Android Bootloader,即高通UEFI启动应用)注入efisp分区,使设备在启动链的早期就切换到「假回锁」状态,让TEE、内核及Android框架层均认为BL处于已锁状态,从而绕过完整性检测,同时保留通过SuperFastboot进行刷写的能力。


高通ABL启动链基础

在骁龙8Gen5平台上,设备上电后的启动顺序大致如下:

PBL(片上只读)
  └──> XBL / SBL(一次性可编程,高度受保护)
         └──> ABL(efisp 分区,UEFI 应用层)
                └──> Linux Kernel

ABL是整个链条中唯一同时满足「可被刷写替换」与「可访问 Verified Boot 决策逻辑」两个条件的组件。高通将 ABL 以 UEFI Firmware Volume(FV)格式打包,内部包含若干 PE32 格式的 EFI 应用,其中最核心的是LinuxLoader.efi,负责执行 AVB(Android Verified Boot)校验、决定 BootState,并最终引导内核。

ABL的验证由 GBL 负责。GBL 会从 efisp 分区读取并校验 ABL。这里存在一个关键行为:GBL 读取的分区名是通过一个可写的 UTF-16 字符串 \"efisp\" 指定的。这个字符串位于 ABL 二进制的数据段,可以在 PC 端修改——这就是本方案漏洞利用的起点。


efisp分区名替换

patch_abl_gbl是整个方案中最简洁的一步:

int32_tpatch_abl_gbl(char* buffer, int32_t size) {
char target[]      = { 'e',0, 'f',0, 'i',0, 's',0, 'p',0 };
char replacement[] = { 'n',0, 'u',0, 'l',0, 'l',0, 's',0 };
int32_t target_len = sizeof(target);
for (int32_t i = 0; i < size - target_len; ++i) {
if (memcmp(buffer + i, target, target_len) == 0) {
memcpy(buffer + i, replacement, target_len);
return0;
        }
    }
return-1;
}

这段代码在ABL二进制中扫描 UTF-16 编码的字符串\"efisp\",将其就地替换为\"nulls\"。

作用:修补后的ABL被刷入efisp分区。当这个被修改过的 ABL 在运行时试图按照原来的逻辑从 efisp 分区加载「下一个 EFI」时,它会因找不到名为\"nulls\" 的分区而静默跳过,从而阻止原始 ABL被递归再次加载,避免了加载链的无限循环。

这一补丁之所以可行,根本原因在于GBL对ABL的完整性校验发生在加载阶段,而不是在ABL运行期间对其自身数据段进行持续校验。一旦GBL完成校验并将控制权移交给被修改的 ABL,后者就能自由运行,包括引导一个与原始执行路径完全不同的自定义EFI载荷。


ABL 二进制的提取:从镜像到 PE32

在对ABL进行修补之前,需要从abl.img(或运行中设备的 abl_a/abl_b 分区)中提取出可操作的 PE32 二进制。这项工作由extractfv完成。

extractfv 实现了一个递归多层扫描器,逻辑如下:

abl.img(原始 flash 镜像)
  └── deep_scan()
        ├── 扫描 MZ 头 → 提取 PE32 文件
        ├── 扫描 BMP 图片(可选)
        ├── 扫描 LZMA 压缩块(0x5D 0x00 0x00 魔数)→ 解压后递归 deep_scan()
        └── 扫描 EFI Firmware Volume(_FVH 签名)→ 递归进入 FV 解析

其中最关键的是 FV 解析路径。高通 ABL 将核心 LinuxLoader.efi 封装在一个标准 UEFI Firmware Volume 内,该 FV 内部包含若干 FFS(Firmware File System)文件,PE32 节(EFI\_SECTION\_PE32)可能直接存在,也可能经过 LZMA 压缩封装在 EFI\_SECTION\_COMPRESSION 中。

extractfv 能够处理所有这些变体:

  • try_lzma_decompress()

:使用 liblzma 解压 LZMA 压缩块
* calc_pe_real_size()

:通过解析 PE 头部的节表精确计算真实文件大小,避免提取多余数据
* parse_pe_info()

:识别 ARM64 架构与 EFI\_APP/EFI\_DRIVER 子系统,用于过滤

默认模式下,extractfv 提取最大的 PE32 文件,输出为 LinuxLoader.efi,这就是后续 ABL 修补的输入。


修补AVB验证绕过

提取出LinuxLoader.efi后,patch_abl工具对其进行一系列精确的二进制修补。所有修补逻辑都在 PatchBuffer()中协调执行,目标是VerifiedBoot.c中的LoadImageAndAuthVB2 函数的编译产物。

整个修补分为以下几个层次。

启动状态锚点定位(补丁3)

int16_t Original[] = {
-1, 0x00, 0x00, 0x34, 0x28, 0x00, 0x80, 0x52,
0x06, 0x00, 0x00, 0x14, 0xE8, -1, 0x40, 0xF9,
0x08, 0x01, 0x40, 0x39, 0x1F, 0x01, 0x00, 0x71,
0xE8, 0x07, 0x9F, 0x1A, 0x08, 0x79, 0x1F, 0x53
};

这是一个包含 8 条 ARM64 指令的字节序列,对应源码中以下逻辑的编译产物:

// VerifiedBoot.c 约 1887 行
if (AllowVerificationError) {
    BootState = ORANGE;  // CBZ + MOVZ #0x28
} else {
    BootState = GREEN;   // B + (其他赋值)
}

模式中的 -1 表示该字节是通配符(随编译版本变化的寄存器编号)。匹配到这个模式后:

  1. 从第一个字节的低 5 位提取锁状态寄存器编号(lock_register_num)
  2. 记录该模式在文件中的偏移(anchor_offset)
  3. 对匹配到的 CBZ Wn, <label> 指令进行修补:将其目标寄存器替换为 WZR(寄存器 31,零寄存器),使分支永远不成立,从而跳过 ORANGE 路径

这一步是后续所有数据流追踪的基础锚点。

反向数据流追踪(补丁4)

定位到锚点后,find_ldrB_instructio_reverse 从锚点地址向前反向扫描,追踪 lock_register_num 寄存器值的来源:

// 从锚点向前追踪,最多 8 次寄存器-栈溢出弹跳
while (now_offset >= 0) {
    DecodedInst d = decode_at(buffer, now_offset);

if (d.type == INST_PACIASP) break; // 遇到函数边界,停止

// 处理 64 位栈溢出:LDR Xt,[SP,#imm] → 追踪对应的 STR
if (d.type == INST_LDR_X_IMM && d.rn == 31 && d.rt == current_target) { ... }

// 处理字节级栈溢出:LDRB Wt,[SP,#imm] → 追踪对应的 STRB
if (d.type == INST_LDRB_IMM && d.rn == 31 && d.rt == current_target) { ... }

// 找到真正的数据来源:LDRB Wt,[Xn,#off](从全局变量加载)
if (d.type == INST_LDRB_IMM && d.rt == current_target && d.rn != 31) {
// 找到了!触发 source_callback
    }
}

找到源头 LDRB Wt, [Xn, #off](即 AllowVerificationError = IsUnlocked())后,source_callback 将其改写为:

write_instr(buffer, now_offset, encode_movz_w(current_target, 1));
// 原: LDRB Wt, [Xn, #off]   ← 读取 IsUnlocked() 返回值
// 改: MOV Wt, #1             ← 硬编码 AllowVerificationError = TRUE

这一修改的深层含义:AllowVerificationError = TRUE 不仅跳过了 AVB 校验失败时的拒绝启动逻辑,还阻止了 UpdateRollbackIndex 标志被置位。由于回滚版本更新(TZ_UPDATE_ROLLBACK_VERSION)和 TZ 软熔断(TZ_BLOW_SW_FUSE_ID)都只在 UpdateRollbackIndex == TRUE 时执行,这一补丁同时防止了 TZ 对设备的永久性标记。

正向数据流追踪(补丁5)

找到源头后,track_forward_patch_strb 从该源指令向后正向扫描,追踪寄存器值的消费点。追踪引擎使用 LocSet 数据结构维护一个「活跃数据位置集合」,处理寄存器间拷贝、32/64 位栈溢出与重载:

// LocSet 追踪的操作类型:
// - LOC_REG:   当前在某个寄存器中
// - LOC_STK64: 已溢出到栈上(64 位)
// - LOC_STK8:  已溢出到栈上(8 位)

// 当发现 STRB Wt, [Xn, #imm],且 Wt 在 LocSet 中时:
write_instr(buffer, off, strb_with_reg(d.raw, 31));
// 原: STRB Wt, [Xbase, #off]   ← 写入 Data.IsUnlocked = AllowVerificationError
// 改: STRB WZR, [Xbase, #off]  ← 强制写入 0

Data.IsUnlocked 是 KMRotAndBootState 结构体的成员,该结构体通过 KeyMasterSetRotAndBootState() 传递给 TEE。TEE 用 IsUnlocked 的值参与 ROT(Root of Trust)摘要计算:

RotDigest = SHA256(PublicKey || IsUnlocked)

如果IsUnlocked = 1 到达 TEE,TEE 的 ROT 与出厂预期不符,会拒绝释放 FBE(File-Based Encryption)数据加密密钥,导致用户数据完全不可访问。补丁 5 强制写入 0,确保 TEE 收到的 ROT 信息与未解锁状态一致,数据分区正常解密。

cmdline状态字符串修补(补丁2)

int32_tpatch_adrl_unlocked_to_locked(char* buffer, int32_t size, uint64_t load_base) {
// 在二进制中定位 ADRP+ADD 指令对序列
// 第一对加载 \"unlocked\" 字符串地址
// 第二对加载 \"locked\" 字符串地址
// 第三对加载 \"androidboot.vbmeta.device_state\" 字符串(确认上下文)

// 将第一对 ADRP+ADD 的目标寄存器改为第二对的寄存器
// 效果:两者都指向 \"locked\"
uint32_t new_adrp = adrp_with_rd(b0.raw, xa);
uint32_t new_add  = add_with_reg(b1.raw, xa);
    write_instr(buffer, i,     new_adrp);
    write_instr(buffer, i + 4, new_add);
}

这一补丁修改内核启动参数中 androidboot.vbmeta.device_state 的值,从 unlocked 改为 locked。Android 框架层(如 Google Play Services、SafetyNet/Play Integrity)通过读取这个属性判断 BL 状态,如果不修改该字符串,即便 TEE 认为设备已锁,Android 层面仍会探测到解锁状态,银行类应用等将无法运行。

定位方式完全依赖编译器保留的字符串:通过 ADRP+ADD 指令对(高通 AARCH64 ABI 下加载页对齐全局地址的标准方式)来找到加载目标字符串的代码位置,无需知道任何固定偏移。


OPPO/OnePlus警告屏去除定制补丁

针对OPPO/一加设备,patch_warning负责去除开机时的「橙色状态」警告界面:

boolpatch_warning(char* buffer, int32_t size, int32_t global_var_offset) {
// 1. 通过字符串 \"Orange State\
\" 定位警告代码
int32_t warn_off = find_warning_offset(buffer, size, 0);

// 2. 向上搜索控制是否显示警告的 CBZ 指令
for (int32_t i = warn_off - 4; i >= max_search_range; i -= 4) {
if (d.type == INST_CBZ_W) {
// 3. 反向追踪该 CBZ 的判断值来源
            find_ldrB_instructio_reverse(buffer, size, i, d.rt, &offset, empty_source_callback);

// 4. 确认来源是锁状态全局变量(与前面记录的 global_var_offset 匹配)
if (offset != global_var_offset) continue;

// 5. 将 CBZ Wt 改为 CBZ WZR(永远不成立,跳过警告显示)
            write_instr(buffer, i, change_rt(&d, 31));
        }
    }
}

关键点在于第4步的确认:只对来源是「锁状态全局变量」的 CBZ 进行修改,不盲目 NOP 掉所有 CBZ,从而避免破坏其他功能逻辑。


ARM64 指令引擎

上述所有修补逻辑都依赖一个轻量级的ARM64指令解码与编码引擎,支持 20+ 种指令类型:

指令类别 覆盖类型
内存访问 LDRB/STRB(立即数/前索引/后索引)、LDR/STR(32/64 位)
寄存器操作 MOV X/W、MOVZ W
地址计算 ADRP、ADD X(含 LSL#12)
条件分支 CBZ/CBNZ(32/64 位)、B.cond
无条件跳转 B、BL
特殊指令 PACIASP(函数边界标记)、NOP、RET

所有指令使用统一的 DecodedInst 结构表示,解码采用优先级表(priority table)驱动,避免编码空间重叠带来的歧义。编码辅助函数支持原地修改:

uint32_tadrp_with_rd(uint32_t raw, uint8_t new_rd);  // 替换 ADRP 的目标寄存器
uint32_tadd_with_reg(uint32_t raw, uint8_t new_reg);  // 替换 ADD 的 Rd 和 Rn
uint32_tstrb_with_reg(uint32_t raw, uint8_t new_rt);  // 替换 STRB 的 Rt
uint32_tencode_movz_w(uint8_t rd, uint16_t imm16);    // 构造 MOVZ Wd, #imm16
uint32_tchange_rt(DecodedInst* inst, uint8_t new_rt); // 通用 Rt 替换
uint32_tchange_to_b(uint32_t raw);                    // CBZ/CBNZ 转为无条件 B

自定义SuperFastboot

修补后的ABL在efisp分区加载自身时不再递归加载原始efisp,而是执行项目自带的 LinuxLoader.efi(即 SuperFastboot 载荷)。

ELF注入机制

elf_inject工具将修补后的 ABL.efi 嵌入 loader.elf(SuperFastboot 的未链接 ELF 二进制)中:

// loader.elf 中预留了一个占位符节区
__attribute__((section(\".abl_placeholder\"), used))
unsignedint dist_ABL_efi_len = 16;
__attribute__((section(\".abl_placeholder\"), used))
unsignedchar dist_ABL_efi[] = \"ABL_PLACEHOLDER!\";

elf_inject 扫描 ELF 的节表,找到包含 \"ABL_PLACEHOLDER!\" 的节区,将其替换为实际的 ABL.efi 数据,同时正确更新节大小、程序头(Phdr)的 FileSiz/MemSiz 字段以及节表偏移(e_shoff),确保输出的 ELF 结构合法。

运行时启动逻辑

LinuxLoader.efi的LinuxLoaderEntry 启动入口实现了以下决策树:

LinuxLoaderEntry()
  ├── ReadAllowUnlockValue() → 检测 OEM Unlock 开关
  │     └── 若未开启(或读取失败)→ 直接加载内嵌的 ABL.efi,普通启动
  ├── WaitForVolumeDownKey(3000ms) → 等待音量减键
  │     ├── 按下音量减 → 进入 SuperFastboot 模式
  │     ├── 按下音量加 → 进入 Recovery(扩展功能)
  │     └── 超时无输入 → 加载内嵌 ABL.efi,普通启动
  └── FastbootInitialize() → 启动 Fastboot 服务

BootEfiImage() 通过标准的 gBS->LoadImage() + gBS->StartImage() UEFI 调用链式启动内嵌的 ABL.efi。这意味着整个引导链实际上是:

GBL → 修补后的 ABL(SuperFastboot 壳)
         └──[用户无操作]──> 原始 ABL.efi(内嵌于壳中,继续正常引导)
         └──[按音量减]────> Fastboot 服务(SuperFastboot 模式)

SuperFastboot 的关键能力

SuperFastboot 基于高通 QcomModulePkg 的 FastbootLib,在假回锁状态下提供了原生 Fastboot 所不具备的能力:

  • fastboot flashing unlock

/ unlock_critical:BL 解锁操作,不触发数据清除
* fastboot flashing lock

:重新锁定 BL,触发数据清除
* fastboot flash <partition> <file>

:全分区刷写
* fastboot boot xxx.efi

:临时启动任意 EFI 文件(无需刷写)

fastboot flashing unlock 不触发数据清除的原因:高通 ABL 在处理解锁命令时会检查当前 BootState。由于此时 ABL 已被修补(BootState=GREEN,IsUnlocked=0),设备表现为已锁状态,执行解锁操作时不满足触发数据清除的条件判断,因此跳过了清除流程。


工具链与构建流程

整个方案由多个独立工具组成,通过 make 驱动的构建系统统一编排:

gbl_root_canoe/
├── submodules/
│   ├── ablfvextractor/   extractfv — ABL 固件卷解包工具(C,依赖 liblzma)
│   ├── patcher/          patch_abl — ABL 二进制修补工具(C,无依赖)
│   ├── elflinker/        elf_inject — ELF 载荷注入工具(C)
│   └── uefi/edk2/        LinuxLoader.efi — SuperFastboot 载荷(UEFI/EDK2)
└── targets/
    ├── toolkit_linux/    Linux PC 工具包构建目标
    ├── toolkit_windows/  Windows PC 工具包构建目标(MinGW 交叉编译)
    └── magisk_module/    Magisk 模块构建目标(Android NDK 交叉编译)

使用方法

在PC电脑上依次执行:

1. extractfv abl.img -o ./build/
        → 提取 LinuxLoader.efi(ABL 核心 PE32)

2. patch_abl LinuxLoader.efi ABL.efi
        → 执行所有补丁,输出修补后的 ABL.efi

3. elf_inject loader.elf ABL.efi ABL_with_superfastboot.efi
        → 将修补后的 ABL 嵌入 SuperFastboot 壳,输出最终文件

4. fastboot flash efisp ABL_with_superfastboot.efi
        → 刷入 efisp 分区

Magisk模块版本将以上步骤封装为设备端脚本,在已Root的设备上通过Root管理器(KernelSU/Magisk/APatch)直接执行,免去PC操作。


整体效果

方案在几个层面保证了跨设备、跨 ABL 版本的鲁棒性:

字符串锚定而非地址硬编码 所有代码位置均通过编译器保留的字符串常量(\"unlocked\"、\"locked\"、\"androidboot.vbmeta.device_state\"、\"Orange State\ \"、\"Your device has been unlocked\")动态定位。字符串在 ABL 重编译后地址会变化,但内容不会变化,ADRP+ADD 寻址模式也不会变化。

指令模式匹配而非固定字节序列 启动状态锚点(Original[])使用带通配符的模式而非精确字节序列,通配符覆盖随编译器寄存器分配变化的字段(低 5 位寄存器编号),只匹配控制流结构固定的部分。

数据流追踪适应编译器优化 补丁 4/5 的反向/正向追踪引擎能够跟踪寄存器值经过多层栈溢出(STR Xt,[SP,#imm]/LDR Xt,[SP,#imm])和字节溢出(STRB Wt,[SP,#imm]/LDRB Wt,[SP,#imm])之后的最终位置,最多支持 8 次「弹跳」,应对编译器激进优化下的寄存器重用和溢出重排。

函数边界保护 所有追踪都以 PACIASP(ARM 指针认证指令,同时也是高通 ABL 中标记函数入口的约定)作为硬性停止条件,防止追踪越过函数边界误改其他函数的代码。

分级失败处理 补丁分为「关键」(失败则中止并返回 false)、「重要但非关键」(失败打印警告继续)、「可选」(失败静默忽略)三个级别。这确保了在 cmdline 字符串补丁失败的 ABL 版本上,设备仍能正常启动,只是状态字符串暴露,不会因补丁失败导致砖机。

经过以上所有补丁和载荷注入后,设备的状态:

项目 状态
TEE BootState GREEN(已锁,TEE 正常释放 FBE 密钥)
androidboot.vbmeta.device_state locked
用户数据 完整可访问
SafetyNet / Play Integrity 基础级别通过(取决于设备其他因素)
Fastboot 能力 完整,含解锁/锁定/刷写/临时启动
OTA 兼容性 需 OTA 前降级 ABL 版本,或模块脚本自动处理
BL 解锁状态 维持未解锁(物理意义上),或可通过 SuperFastboot 二次解锁

本方案的本质是:在 GBL 校验之后、内核启动之前的 UEFI 空间内,通过精确的二进制修补让 ABL 的安全状态决策输出与未解锁状态保持一致,从而在不触碰 xBL 及 TEE 的前提下,实现了对 AVB 和设备状态上报机制的完全控制。

当然,这种补丁方式已经有了更优雅的升级版本,基于GBL启动链Hook技术勾住每一个需要补丁的地方,动态补丁兼容性更好。

具体细节与使用方法点击阅读原文下载:高通8Gen5的gbl解锁隐藏技术实现原理分析.pdf


gbl_root_canoe隐藏BL状态原理分析" 网络安全文章

gbl_root_canoe隐藏BL状态原理分析"

该文档分析了高通骁龙8Gen5平台下gbl_root_canoe工具隐藏Bootloader解锁状态的原理。通过修改ABL二进制文件,替换efisp分区名为nulls,并修补AVB验证逻辑,使TEE和系统认为设备处于锁定状态,从而绕过完整性检测。关键步骤包括定位启动状态锚点、反向追踪数据流强制AllowVerificationError为TRUE、正向追踪修改IsUnlocked值为0,以及修补内核启动参数字符串。方案利用GBL校验漏洞在启动早期实现假回锁,保留刷写能力的同时规避安全检测。
实战sql盲注小技巧" 网络安全文章

实战sql盲注小技巧"

文档介绍在SQLServer盲注场景中,当目标存在杀软、EDR防护且仅DNS出网时,通过VBS脚本将命令执行结果转为十六进制格式,分段嵌入DNS请求外带数据。提供完整操作流程:使用sqlmap的–os-shell功能写入并执行脚本,配合Python工具解密DNS日志重组回显。关键发现包括利用DNS协议绕过网络限制,可操作建议涉及修改DNSlog平台地址与Cookie配置以实现自动化解密。
EasyShellExtensions脚本菜单—用户使用手册" 网络安全文章

EasyShellExtensions脚本菜单—用户使用手册"

本文是EasyShellExtensions脚本菜单的用户手册,详细介绍了该渗透测试工具的功能特性、部署使用方法、内置插件集配置以及自定义JSON插件编写指南。文档重点说明了脚本菜单的树形操作界面、内存执行技术、Windows/Linux/macOS多平台支持,以及从CobaltStrikeCNA迁移的注意事项,为红队操作人员提供了完整的操作指引和实战经验分享。
评论:0   参与:  0