本文最后更新于:1 小时前
IQOO Z9 Turbo+ / VIVO 系设备 REC 检测 - 去除 Root 标识
来源:IDA Pro7.7分析
设备:IQOO Z9 Turbo+(蓝厂 VIVO 系设备)
目标:去除 REC(恢复模式)中的 Root 检测标识,恢复未 Root 状态
核心原理:修改 backup 分区 NV 数据结构中的 is_root 字段,并同步更新 MD5 校验值
本文由openclaw整理生成
一、背景与原理概述
1.1 存储分区分析
VIVO/IQOO 设备中,Root 标识可能存储在以下位置:
| 分区/路径 |
说明 |
是否直接存储 root 标识 |
/dev/block/by-name/nvram |
NVRAM 原始分区 |
❓ 未确定 |
/dev/block/by-name/nvdata |
NVRAM 数据分区 |
❓ 未确定 |
/dev/block/by-name/nvcfg |
NVRAM 配置分区 |
❓ 未确定 |
/dev/block/by-name/persist |
persist 分区(ext4) |
⚠️ 可能相关 |
/dev/block/by-name/backup |
备份分区 |
✅ 已确认为主要存储位置 |
/data/vendor/engineermode/nvram_item |
明文备份文件 |
记录日志,非 root 标识存储 |
/persist/opporeserve/iqoo_nv.bin |
IQOO 定制 NV 文件 |
⚠️ 可能相关 |
/persist/security/root_flag |
Root 标志文件 |
⚠️ 可能相关 |
1.2 核心发现
经过代码分析,确认:
- Root 标识
is_root 存储在 backup 分区的 NV 数据结构中
- 该数据结构同时被备份到明文文件
/data/vendor/engineermode/nvram_item
- MD5 校验保护:前 524 字节的 MD5 值存储在偏移 524
539(0x20C0x21B)
- 修改后必须重新计算并写入 MD5,否则系统校验失败,可能拒绝启动或使用默认值
二、关键函数分析
以下函数来自逆向工程(ARM64 伪代码):
2.1 sub_2B69B0 — 从 backup 读取 NV 数据
1 2 3 4 5 6 7 8 9 10 11 12
| 功能:从 backup 分区读取 IQOO NV 数据 路径尝试顺序: 1. /dev/block/by-name/backup 2. /dev/block/bootdevice/by-name/backup 3. /dev/block/platform/bootdevice/by-name/backup
读取参数: - 块偏移:a2 << 12(即 a2 * 4096) - 每次读取 4 块,每块 0x200 = 512 字节,共 2048 字节 - 有效 NV 数据大小:0x7AC = 1964 字节
返回值:成功返回 1,失败返回 0
|
2.2 sub_2B6210 — 写入 backup 分区
1 2 3 4 5 6 7 8
| 功能:向 backup 分区写入 NV 数据(与 sub_2B69B0 对称) 写入参数:同读取 写入模式:O_RDWR (1052674) 写入后调用 fdatasync() 强制刷盘
特殊处理: - 写入前计算 MD5(第 0~523 字节,共 524 字节) - 将 MD5 结果写入偏移 524~539
|
2.3 sub_2B5290 — NV 数据写入 nvram_item 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 功能:将 NV 数据同时写入明文备份文件 文件路径:/data/vendor/engineermode/nvram_item
写入内容(完整 NV 结构体解析): - offset 0 (4B): gsensor_calibration_flag - offset 4 (4B): master_clear_flag - offset 8 (4B): master_clear_result - offset 12 (4B): adb_flag - offset 16 (16B): iqoo_sv (字符串) - offset 27*4 (4B): ★ is_root ★ ← 核心目标字段 - offset 28*4 (4B): is_bksn_ok - offset 29*4 (4B): is_first_boot ... - offset 540 (16B): MD5 校验值(低 8 字节) - offset 524 (16B): MD5 校验值(高 8 字节)
注意:写入 nvram_item 仅作日志记录,不影响实际使用。 但 backup 分区才是系统真正读取的存储位置。
|
2.4 sub_2B6BB0 — NV 数据 MD5 校验
1 2 3 4 5 6 7 8
| 功能:验证 NV 数据结构完整性 校验算法: 1. 取偏移 0~523 共 524 字节 2. 计算 MD5(结果为 16 字节 = 128bit) 3. 前 8 字节存入偏移 524 4. 后 8 字节存入偏移 532
返回值:校验通过返回 1,失败返回 0
|
三、NV 数据结构速查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 整体结构大小:0x7AC = 1964 字节
偏移 大小 字段名 ───────────────────────────────────────────── 0x000 4B gsensor_calibration_flag 0x004 4B master_clear_flag 0x008 4B master_clear_result 0x00C 4B adb_flag 0x010 16B iqoo_sv (版本字符串) ... 0x06C ★ 4B is_root ← 核心修改目标! 0x00000000 = 未 Root 0x00000001 = 已 Root ... 0x20C MD5[0:8] (前8字节) 0x214 MD5[8:16] (后8字节) ─────────────────────────────────────────────
|
关键偏移计算:is_root 在 offset 27,27 × 4 = 0x6C(108 字节)
四、修改方法(手动 Hex 编辑)
⚠️ 操作有风险,请提前备份原始文件!
4.1 工具准备
| 工具 |
说明 |
| HxD |
Windows 平台推荐,免费十六进制编辑器 |
| 010 Editor |
专业级,支持模板 |
| Android ADB |
用于刷入修改后的镜像 |
4.2 获取 backup 分区镜像
1 2 3 4 5 6 7 8 9 10
| adb shell su dd if=/dev/block/by-name/backup of=/sdcard/backup.img
dd if=/dev/block/bootdevice/by-name/backup of=/sdcard/backup.img
adb pull /sdcard/backup.img C:\backup\backup.img
|
4.3 定位并修改 is_root 字段
① 备份原文件
1
| 将 backup.img 复制一份为 backup_original.img
|
② 打开 HxD,跳转到偏移 0x6C
HxD 操作:
- 菜单 → 搜索 → 跳转到 → 输入
6C → 选择”十六进制”
- 或按
Ctrl+G 输入 6C
③ 确认当前值
- 当前 Root:
01 00 00 00(小端序,值为 1)
- 目标值:
00 00 00 00(小端序,值为 0)
④ 修改
选中 4 个字节,直接输入 00 00 00 00,保存文件。
4.4 重新计算 MD5
① 提取前 524 字节用于计算
在 HxD 中:
- 选中偏移 0x000 ~ 0x20B(共 524 字节)
- 右键 → 复制选块 → 另存为
chunk_524.bin
② 计算 MD5(Windows certutil)
1 2
| certutil -hashfile chunk_524.bin MD5
|
输出示例:
1 2 3
| MD5 哈希(适合 chunk_524.bin): a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6 CertUtil: -hashfile 命令成功完成。
|
③ 将 MD5 写入偏移 0x20C
在 HxD 中:
Ctrl+G → 输入 20C → 跳转
- 将 32 位十六进制字符转换为字节写入(共 16 字节)
例如:a1 b2 c3 d4 e5 f6 a7 b8 c9 d0 e1 f2 a3 b4 c5 d6
| 偏移 |
写入值 |
| 0x20C |
a1 |
| 0x20D |
b2 |
| 0x20E |
c3 |
| 0x20F |
d4 |
| 0x210 |
e5 |
| 0x211 |
f6 |
| 0x212 |
a7 |
| 0x213 |
b8 |
| 0x214 |
c9 |
| 0x215 |
d0 |
| 0x216 |
e1 |
| 0x217 |
f2 |
| 0x218 |
a3 |
| 0x219 |
b4 |
| 0x21A |
c5 |
| 0x21B |
d6 |
④ 保存修改后的文件
4.5 刷入设备
1 2 3 4 5 6 7 8 9 10
| adb push backup_modified.img /sdcard/backup.img
adb shell su dd if=/sdcard/backup.img of=/dev/block/by-name/backup
sync
|
五、修改方法(Python 自动化脚本)
✅ 推荐使用此方法,自动处理 MD5 重新计算
5.1 修改脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
| """ IQOO Z9 Turbo+ / VIVO 系设备 backup 分区 is_root 字段修改工具
功能: 1. 读取 backup.img 2. 将 is_root 字段(偏移 108)置为 0 3. 重新计算前 524 字节的 MD5 4. 将新 MD5 写入偏移 524~539 5. 输出修改后的镜像
用法: python modify_backup.py backup.img backup_modified.img """
import hashlib import sys import os
IS_ROOT_OFFSET = 108
MD5_RANGE = 524
MD5_OFFSET = 524
def modify_backup_img(input_file: str, output_file: str) -> bool: """ 修改 backup.img 的 is_root 字段并更新 MD5
参数: input_file : 原始 backup.img 路径 output_file : 修改后输出路径
返回: True 成功,False 失败 """
with open(input_file, 'rb') as f: data = bytearray(f.read())
print(f"[+] 文件大小: {len(data)} 字节")
if len(data) < MD5_RANGE + 16: print(f"[✗] 文件太小 ({len(data)} 字节),不是有效的 backup 镜像") return False
original_is_root = int.from_bytes( data[IS_ROOT_OFFSET:IS_ROOT_OFFSET + 4], 'little' ) print(f"[*] 原始 is_root 值: {original_is_root} " f"({'已 Root' if original_is_root else '未 Root'})")
if original_is_root == 0: print("[*] is_root 已经是 0,无需修改") else: data[IS_ROOT_OFFSET:IS_ROOT_OFFSET + 4] = b'\x00\x00\x00\x00' print(f"[+] 已将 is_root 置为 0")
chunk_to_hash = bytes(data[:MD5_RANGE]) new_md5 = hashlib.md5(chunk_to_hash).digest()
print(f"[*] 新 MD5 (16 字节): {new_md5.hex()}")
data[MD5_OFFSET:MD5_OFFSET + 16] = new_md5 print(f"[+] 新 MD5 已写入偏移 0x{MD5_OFFSET:X} ~ 0x{MD5_OFFSET + 15:X}")
recalc_md5 = hashlib.md5(bytes(data[:MD5_RANGE])).digest() if recalc_md5 == new_md5: print("[✓] MD5 验证通过") else: print("[✗] MD5 验证失败!") return False
with open(output_file, 'wb') as f: f.write(data)
size_kb = os.path.getsize(output_file) / 1024 print(f"[✓] 已保存修改后的镜像: {output_file} ({size_kb:.1f} KB)")
return True
def verify_backup(img_path: str) -> dict: """ 验证 backup.img 的 is_root 状态和 MD5
返回: dict,包含 is_root 值、MD5 状态等 """ with open(img_path, 'rb') as f: data = f.read()
if len(data) < MD5_RANGE + 16: return {"valid": False, "error": "文件太小"}
is_root = int.from_bytes(data[IS_ROOT_OFFSET:IS_ROOT_OFFSET + 4], 'little')
stored_md5 = data[MD5_OFFSET:MD5_OFFSET + 16] calc_md5 = hashlib.md5(data[:MD5_RANGE]).digest()
return { "valid": True, "is_root": is_root, "is_root_str": "已 Root" if is_root else "未 Root", "md5_match": stored_md5 == calc_md5, "stored_md5": stored_md5.hex(), "calc_md5": calc_md5.hex(), "file_size": len(data), }
def main(): if len(sys.argv) < 2: print("用法:") print(" 验证: python modify_backup.py verify backup.img") print(" 修改: python modify_backup.py backup.img output.img") sys.exit(1)
if sys.argv[1] == "verify": if len(sys.argv) < 3: print("用法: python modify_backup.py verify backup.img") sys.exit(1) result = verify_backup(sys.argv[2]) print("=" * 45) print(f" 文件: {sys.argv[2]}") print(f" 大小: {result.get('file_size', 'N/A')} 字节") print(f" is_root: {result.get('is_root_str', 'N/A')}") print(f" MD5 校验: {'✓ 通过' if result.get('md5_match') else '✗ 失败'}") print(f" 存储 MD5: {result.get('stored_md5', 'N/A')}") print(f" 计算 MD5: {result.get('calc_md5', 'N/A')}") print("=" * 45) return
if len(sys.argv) < 3: print("用法: python modify_backup.py backup.img output.img") sys.exit(1)
input_file = sys.argv[1] output_file = sys.argv[2]
if not os.path.exists(input_file): print(f"[✗] 文件不存在: {input_file}") sys.exit(1)
print("=" * 45) print(" IQOO Z9 Turbo+ / VIVO Root 标识修改工具") print("=" * 45)
success = modify_backup_img(input_file, output_file)
if success: print("\n[*] 下一步:") print(" 1. 将 backup_modified.img 推送到设备") print(" 2. 在 root shell 中执行:") print(" dd if=/sdcard/backup_modified.img of=/dev/block/by-name/backup") print(" 3. 重启设备验证") else: sys.exit(1)
if __name__ == "__main__": main()
|
5.2 使用说明
1 2 3 4 5 6 7 8 9 10 11 12 13
| python modify_backup.py verify backup.img
python modify_backup.py backup.img backup_fixed.img
adb push backup_fixed.img /sdcard/ adb shell su dd if=/sdcard/backup_fixed.img of=/dev/block/by-name/backup sync reboot
|
六、刷入后的验证
6.1 验证 MD5 匹配
1 2 3 4 5 6
| adb push modify_backup.py /sdcard/ adb shell su cd /sdcard python3 modify_backup.py verify backup_fixed.img
|
6.2 检查 root 属性
- 若
is_root 字段修改成功且 MD5 校验通过,Root 检测应失效。
6.3 刷入后系统无反应的应对
若 MD5 校验不通过导致系统无法启动:
- 方案 A:在 recovery 或 fastboot 模式刷入原版 backup 镜像恢复
- 方案 B:将 MD5 字段全部写 0,让系统使用默认值(
is_root=0)1 2
| data[524:540] = b'\x00' * 16
|
七、其他可能涉及的存储位置
根据分析,以下路径也可能存储 root 相关标志,建议同步检查:
| 路径 |
说明 |
/persist/security/root_flag |
直接以文件名存储 root 标志 |
/persist/opporeserve/iqoo_nv.bin |
IQOO 定制 NV 文件 |
/persist/vivo/emergent_mode |
应急模式标志 |
/data/vendor/engineermode/nvram_item |
明文备份(仅记录,不影响校验) |
八、注意事项与风险提示
⚠️ 重要声明:
- 操作前务必备份原始 backup.img
- backup 分区同时存储了大量校准数据(gsensor、gyro、光感等),修改后可能影响校准状态
- MD5 校验失败时系统会使用内存中的默认值,短期内可能正常,但不稳定
- 若设备已加密或使用动态分区,部分路径可能不存在
- 修改系统存储涉及安全机制,仅供学习研究使用