AI 辅助项目。 本研究、漏洞利用开发及文档均借助 AI 辅助完成,使用了 GLM-5.3 和 DeepSeek V4.1 Flash 模型。
针对 Amazon Fire 7 第 9 代(mustang,MT8163,Mali-T720) 最终固件——Fire OS 7.3.3.1,PS7331.4463N,内核 4.9.117(构建于 2025-05-03,SPL 2024-08-01)——的 root 漏洞利用研究。
目标:LineageOS。该设备的引导加载程序路径已失效(bootrom 已修补——仅能通过 CMD 短接进入 preloader),因此唯一剩余的途径是软件内核漏洞利用。
nix-shell -p android-tools --run './run.sh' # add -p zig too if no zig
成功后:```
/data/metrics/su id # run a command as root
/data/metrics/su # interactive root shell
reclaim 大约每 3 次启动成功 1 次,失败会导致平板 panic/重启;
run.sh 只是等待重启并重试。SELinux 在漏洞利用过程中被强制设为 Permissive,因此 root 是仅运行时有效——重启会恢复原厂状态,你需要重新运行 run.sh。
预编译的 st3 和 su(armv7 静态)已提交,因此运行无需工具链。如果你有 zig,./run.sh --build 会从 poc/*.c 重新构建它们。
GhostLock(见下文)已搁置:MTK 的 BUG_ON rtmutex 变体 + shell 无法泄露任何内核地址 = 在此构建上架构性死路(sessions 2-4)。 kbase JIT UAF 被重新诊断(destroy-worker 的“无条件 panic”其实是 JIT_FREE 解引用,日志因 adbd 在 panic 中途死亡而丢失),stage 2 现在已由 oracle 证实——见 SESSION 5 部分。
rtmutex remove_waiter() futex-PI 栈 UAF(NebuSec 于 2026-07 披露,修复 3bfdc63936dd
于 2026-04 合入)。受影响范围 2.6.39–7.1 → 我们的 4.9.117(2025 年 5 月)受影响。
在我们的确切构建上验证:
CONFIG_FUTEX=y,rtmutex 已编译进内核,bug 原样存在:
rtmutex.c:1108-1111 使用了 current->pi_lock/current->pi_blocked_on(应为
waiter->task);有 bug 的调用点 rtmutex.c:1723(rt_mutex_start_proxy_lock 错误路径)WAIT_REQUEUE_PI/CMP_REQUEUE_PI),无需设备节点,
没有任何受 SELinux 限制的东西——kbase 路径的致命障碍在这里不存在sched_setattr → __sched_setscheduler → rt_mutex_adjust_pi(p) 位于
sched/core.c:4706——解引用过期的 pi_blocked_on ✓exp32/main.crt_waiter 帧偏移 vs do_sys_select fd_set 区域——反汇编
我们的 vmlinux(do_sys_select stack_fds vs futex_wait_requeue_pi 帧),将
STAMP_NFDS/STAMP_WAITER_OFF 暴露为可调参数Failed critical init step 3/dev/mali0 全局读写 + SELinux gpu_device,kbase r26p0-01rel0selroot 2 包链:清零 selinux_state.enforcing,重写
假条目为 。,SELinux Permissive。mustang,Fire OS 7.3.3.1 PS7331.4463N/00315758630404.9.117-g08fe75b-dirty,构建于 Sat May 3 01:25:15 UTC 2025(Linaro GCC 6.3-2017.05)/dev/kb、/dev/dkb(Amazon 内核备份分区)root:drmrpc 0660——已锁定mali_kbase r26p0-01rel0(Midgard,Mali-T720),在 NVD 受影响范围 r4p0–r31p0 内mali_kbase_mem.c:2721 kbase_jit_destroy_worker 释放区域,从不清理 kctx->jit_alloc[id]mali_kbase_softjobs.c:1270 kbase_jit_free_finish 解引用过期的 jit_alloc[ids[j]]mali_kbase_mem.c:3138 kbase_jit_backing_lost → destroy 路径(在 reclaim 期间触发)0xc0008000 VA / 0x40080000 PA)ARM_SW_DOMAIN_PAN → ret2usr 可行;CONFIG_PANIC_ON_OOPS=y(失败尝试 = 重启)SLAB_FREELIST_RANDOM/HARDENED,无 CONFIG_USER_NS/USERFAULTFD/NF_TABLESCONFIG_MODULES=y,无 STATIC_USERMODEHELPER → modprobe_path 覆写 = root_IOC_TYPE 0x80)MEM_ALLOC 联合体为 32 字节(in 有 4 × u64,包括 extent)BASE_MEM_PROT_GPU_RD|WR(位 2|3),而非旧版 R|Wmmap(fd, offset=3<<12, PROT_NONE)JOB_SUBMIT 步长必须等于 sizeof(base_jd_atom_v2) = 48(base_jd_prio/base_jd_dep_type 是 u8 typedef)MEM_JIT_INIT(nr 14,v2 结构),分配/释放是软作业,通过 JOB_SUBMIT
(BASE_JD_REQ_SOFT_JIT_ALLOC=0x209,...FREE=0x20a;jc=用户指针,=计数)Panic 发生在驱逐本身期间(evictable_reclaim_scan_objects → backing_lost →
destroy worker)——悬空引用(jit_alloc[]、驱逐列表)在我们提交 JIT_FREE 之前就被遍历了。Stage 2 必须赢得竞态:在压力仍在运行时,用我们自己的 MEM_ALLOC 喷射重新分配已释放的 kbase_va_region。
poc/stage2.c — stage-2 漏洞利用(模式:step/uaf/spstep/spfree/spray/keys)
— spray 700 = 完整 oracle 运行;存活并暂停(kill 以清理)poc/mustang_jit_uaf.c — stage-1 PoC(模式:jit N / control N / pressure N)poc/build.sh — zig 交叉构建(静态 musl armv7)kernel/vmlinux — 从确切 OTA 构建恢复的符号(vmlinux-to-elf)kernel/config-* — 来自运行设备的 转储nix-shell -p zig --run 'zig cc -target arm-linux-musleabihf -static -O2 -o juaf poc/mustang_jit_uaf.c' adb push juaf /data/local/tmp/juaf && adb shell chmod 755 /data/local/tmp/juaf adb shell /data/local/tmp/juaf jit 700 # full trigger (~reboots device) adb shell /data/local/tmp/juaf control 700 # no-JIT control adb shell /data/local/tmp/juaf pressure 700 # raw memory-pressure control
## 参考资料
- GHSL-2022-054 安全公告:https://securitylab.github.com/advisories/GHSL-2022-054_Arm_Mali/
- Mo 的 Pixel 6 漏洞利用文章:https://github.blog/2023-01-23-pwning-the-all-google-phone-with-a-non-google-bug/
- Fire HD 10 (trona) 先例,同一漏洞家族:ericpardee.github.io/fire-hd-ownership
- Amazon 开源门户:amazon.com gp/help/customer/display.html nodeId=200203720
- XDA 解锁帖(对此硬件版本已失效):xdaforums.com/t/fire-7-2019-mustang-unbrick-downgrade-unlock-root.3944365/
## 会话 3 附录(深度系统调用标记调查)
已测量的复制源深度(相对于系统调用入口 sp0 的绝对值;等待者范围为 -0x1d8..-0x1a8):
- sendto sockaddr @ -0xc4 | process_vm iov @ -0x104 | recvmsg iov @ -0x11c
- sendmsg iov @ -0x12c | recvmmsg iov @ -0x154 | select fds @ -0x174
- pselect6 fds @ -0x19c | **sendmmsg iov @ -0x1bc(最佳 — 距 waiter+0x00 仅差 0x1c)**
- poll entries @ -0x3e0(完全在下方;错误一侧)
本次会话已排除:
- io_submit 链太浅(约 -0x130)| semtimedop:CONFIG_SYSVIPC=n(桩)
- configfs 已挂载但零子系统注册(无 mkdir 目标)
- /sys/kernel/debug、/config:shell 被 SELinux 拒绝
- /proc/sys/kernel:getdents 可用(列出 29 个条目),仅 pid_max 可打开;
kptr_restrict/hotplug/hostname/domainname 读取全部被拒绝
- 写入值始终为 waiter+0(内核栈地址,可执行,shellcode 位于
+0x1c):rb_link_node *link = node,insert_color 写入 parent-color — 没有
可控值变体,除非进行树字段标记(间隙 -0x1d8..-0x1bc)
- 双重解引用分派字段读取 *(waiter+0)=1,*(waiter+4/+8)=0 — BLX 1/0
(nf_hooks、net_families、inet[6]_protos、seq_file->op 全部失效)
- timer_list.function@+0xc 和 work_struct.func@+0xc 将读取 *(waiter+0xc) =
pi_tree 自指针 = 可执行的 waiter+0xc — 但没有路径将 waiter+0 排入
timer/work(链接损坏 / 无间接队列来源)
- 树根非零(sysctl 处理程序槽)= 通过 .text 作为 rb-tree 的确定性指针追踪;
终止于零字 — 可离线模拟,但落入有用的可写槽不太可能
会话 4 的剩余线索:
1. ioctl 深层路径:dev_ioctl ifreq 复制(40B 用户数据)— 测量
SyS_ioctl→sock_ioctl→dev_ioctl 链深度与 -0x1d8 的对比
2. 任何比 sendmmsg 深 0x1c 的其他复制(尚未发现)
3. 如果标记面搜寻失败:重新考虑遍历链式构造或
搜寻尚未枚举的可写零调用槽类
## 会话 4 — 两大突破
### 1. MTK 的 rtmutex_common.h 就是整个谜团
MTK 将上游的 NULL 安全 rt_mutex_top_waiter 替换为:```c
w = rb_entry(lock->waiters_leftmost, struct rt_mutex_waiter, tree_entry);
BUG_ON(w->lock != lock); // compiled to: ldr sb,[lock+8]; ldr r3,[sb+0x1c]; cmp; bne→udf#0x12
NO NULL CHECK + BUG_ON。每个全零 anchor 都会在 *(NULL+0x1c) 处死亡;垃圾 anchor 会在 udf 处死亡。遍历要求:lock->waiters_leftmost (lock+8) 必须指向一个伪造的 waiter W(可写),且 W->lock (+0x1c) == lock。
遍历流程已完整映射(rt_mutex_adjust_prio_chain @ 0xc0189a58):
/proc/self/task//stat 字段 28(kstkesp)从 SHELL 上下文返回 syscall 阻塞线程的真实内核 SP(已验证:观察到非零值)。
遍历确定性地触发(死锁探针:每次都崩溃,干净代码)。 写入无法落地,因为一个 3 重内核加固巧合:
stamp 窗口(waiter+0x1c..0x5b)是唯一可控且内容已知的 内存,但它的地址正是我们需要的未知量。自引用构造 都需要将内核地址作为常量 stamp — 没有泄漏就是循环依赖。
他们的 5.4 内核有 UPSTREAM rtmutex_top_waiter(NULL 安全:if (!leftmost) return NULL)— 空树 anchor 存活,他们的写入落在 null_fops
(在他们的内核上可写)。即使他们也卡在 dispatch("ioctl reboot")。
Mustang 的 4.9.117 MTK 树有 BUG_ON 变体 — Fire OS 8 平板
(GhostLock-5.10)成功是因为他们的 5.10 内核是 upstream 风格。
从 shell 域测试并确认死路:
结论:mustang 上的 GhostLock 需要此内核不向 shell 域暴露的 内核地址泄露。没有它就无法构造自引用伪造 lock。
(a) 启动确定性磨:重启 → 通过崩溃 oracle 校准栈地址 (约 20-30 次重启),验证可复现性。希望渺茫 — 启动后期 线程栈分配不太可能稳定。 (b) 带着积累的资产 PIVOT 回 kbase CVE-2022-38181:精确构建 vmlinux + 完整源码 + 工具链 + O_SYNC 跟踪纪律 + 深入的 4.9 知识。原始阻塞(JIT 驱逐期间 destroy-worker panic) 是 spray 时序问题,现在理解得更好了。 (c) 停在诚实的约 45%:触发已证明,遍历已映射到指令, 写入被 MTK BUG_ON + 无泄漏阻塞。
推荐:(b) — kbase bug 在此精确源码中已验证存在, 有可用的触发,其阻塞是机械性的,而非架构性的。
step 模式(alloc id=1 → DONT_NEED → 700MB 压力 → MEM_QUERY,无 free)
存活:query=-1(区域被 destroy worker 释放,rbtree 干净)。
worker 路径与合法的压力下 JIT_FREE 流程逐字节相同。
Session-1 的崩溃始终是 JIT_FREE 悬空解引用;其日志行
丢失是因为 panic 在 flush 中途杀死了 adbd。用 uaf
模式又验证了两次(裸 free → panic,相同的日志截断)。GHSL-2022-054 流程在此构建上完全可用。
kbase_jit_free(kctx, reg) @ 0xc058495c,带完全可控的伪造 reg:
reg->cpu_alloc NULL → backed size 0 → trim 块跳过(0xc0584978)kctx+0x147dd(字节)+ kctx+0x147de+bin_id(字节)mark_reclaim(reg->gpu_alloc) @ 0xc059b158:链
K=*(gpu_alloc+0x38) → *(K+0x1429c)==0 跳过 mm-atomics →
atomic_sub nents@K+0x141c8,D=*(K+4) → atomic_sub nents@D+0x538。
nents=0 时所有写入都是 no-op 存储(相同值的 strex)。reg->flags |= 0x100000(写入伪造对象,良性)list_add(gpu_alloc->evict_node, &kctx->evict_list):evict_list 头
@ kctx+0x1427c;写入 gpu_alloc+0x18/0x1c(必须可写)r3=*(reg+0x3c) prev, r2=*(reg+0x38) next
→ *(next+4)=prev; *(prev+0)=next — 两个任意 write-what-where,
然后将 reg+0x38 重新链接到 jit_pool_head @ kctx+0x148e89 个候选;S=0xc118b7ec(xfrm 数据,在此设备上休眠):
*(S+8)=0(nents),*(S+0x18)=S+0x18(空 evict_node → 无 WARN),
K=*(S+0x38)=0xc118b820 → *(K+0x1429c)=0,K/D+0x141c8/+0x538 全在
可写数据中。Oracle 目标已布置:init_uts_ns.name.nodename=0xc110d561
("(none)",可通过 uname 读取),scratch P=0xc118bd58(xfrm 零)。
避免 S=0xc111cba4(tracepoint 相邻)。CONFIG_DEBUG_RODATA=y → 所有写入
目标必须在 .data/.bss 中(bss 0xc11d9000-0xc12d9000)。
add_key(CONFIG_KEYS=y):shell 被 SELinux 拒绝。死路。setxattr 值缓冲区:kvmalloc(96)+copy_from_user 发生在
SELinux 检查之前 → 即使调用失败,alloc 舞蹈也是 SELinux 免疫的;
瞬态(syscall 结束时释放),字节在 +4..95 处持续(freelist 指针
覆盖 +0..3 = rblink,kbase_jit_free 不使用)spray 700 运行 2026-09-11:压力期间 spray 了 5895 个 region,对悬空
id=1 的 JIT_FREE 在回收的 region 上完成,然后
JIT_ALLOC(0x40, bin 0) 遍历 jit_pool_head 并返回 sprayed region
#4251 的 VA (0x142701000) — 正是悬空指针消耗的那个 region。
之后进程 kill:kctx 拆除干净,无崩溃。Stage 2 完成:
确定性 UAF 重定向,带受控对象类型 + 内容。
Region 类型回收提供合法舞蹈存活,但 jit_node 被 INIT 为自引用 → 无 unlink 原语。需要在 +0x38/+0x3c 处有原始字节:
*(N+4)=P,P=用户态 shellcode 页(无 PAN!)—
候选:const fops 是 .rodata(DEBUG_RODATA)→ 目标 .data 中的非 const
函数指针,或 binfmt formats 列表头,或 sysctl proc_handler
(验证表可写性)。回退:通过字节链式
写入 modprobe_path(值必须是可写地址 — 使用指针形状的目标)poc/stage3.c):
kern_table[pid_max].proc_handler @ 0xc1113f40(可写
.data,通过字符串指针扫描 + handler == proc_dointvec_minmax 验证)b +8 跳过;W2 在 P = handler 字段写入 N)read /proc/sys/kernel/pid_max
(可从 shell 读取);在自己的任务上下文中运行 → creds 应用于我们inotify_handle_event kmalloc name_len+0x1d,
name 字节(完全可控,无 NUL/斜杠约束)在 event+0x1c;
name_len=60 → kmalloc-96;排队 → 持有;4 个实例 → 每次
rename 4 个事件;shell 下 SELinux-OK。伪造重新设计为无 NUL:cpu_alloc
指向 S(nents@S+8 = 0 → 与 NULL 相同语义)| 变体 | 结果 |
|---|---|
| 风暴期间并发 rename sprayer | rename 停滞(journal/GFP_NOFS)→ 总共 128 → 垃圾解引用 |
| 预排空 12K 事件 + 压力 + 小尾随 | 在解引用处崩溃 |
| + 驱逐时 kill 子进程(10ms 轮询) |
工作假设:风暴垃圾竞争 — 在 destroy worker 释放槽位(风暴中途)与 child-kill/安静之间,残余回收 活动用非 payload 字节占据了受害者 slab 的唯一空闲 槽位。Region spray(stage 2)获胜是因为它在风暴期间 持续分配;rename 不能。
iso 模式):相同时序,尾随用 commit-0 REGION +
stage-2 池重用 oracle → ORACLE 命中
→ 时序/可达性没问题;事件是问题所在current->mm 是内核线程的 → 优雅的
"将伪造对象的指针指向我们自己的用户态 mmap" 设计(无 PAN!)
非确定性地 FAULTS。5/5 崩溃,其他方面完美的伪造对象。K=*(S+0x38) 可读,*(K+0x1429c)==0 在运行时
(跳过 mm-atomics),K+0x141c8 可写,D=*(K+4) → D+0x538
可写,nents *(S+8) 最好为 0。9 个离线 S 候选已
针对 FILE 字节验证 — 运行时漂移(xfrm/tracepoint init)
使它们未经验证。错误的链 = 崩溃 = 重启(约 3 分钟周期)。A. Physmap-spray 伪造(ret2dir,经典 arm32 无 PAN):spray 约 450MB 用户页,每页包含为 ONE 猜测地址 G 烘焙的伪造模式 (G&0xfff = 0x141 用于无 NUL 名称字节;S=G;K=G-0x141b4 使 K+4 落在页内;K+0x141c8/+0x1429c → G+0x10/+0xd4 页内;D=G+0x300; 杂散 sub-0 存储命中随机映射 RAM - nents=0 时无害)。 Spray 兼作驱逐压力(脏匿名 = 不可驱逐 → 只需额外约 100-200MB)。几率 ≈ 45%(页命中)× 约 50%(外来 K+0x1429c 字为零... 如果按上述布局 K+0x1429c 保持在页内, 几率 = 仅页命中)。错过 = 崩溃 = 重启,重试。 B. 暴力破解 9 个静态 S 候选(xfrm 0xc118b7ec 优先, tracepoint 相邻 0xc111cba4 第二...):每个候选 1 次重启, 先良性 oracle payload,命中后武器化。 C. 基于 pagemap 的精确 G(死路:PFN 被掩码)— 不要重访。
/tmp/opencode/ksrc2/kernel/mediatek/mt8163/4.9/(arch/arm 包括
mustang.dtsi、mm/、fs/eventpoll.c、fs/notify)来自 ksrc/platform.tar。
发现:
physmap_retouch()(在 trailing/deref 之前
将所有 spray 页面重新缺页调入)Regions reclaim:3/3。Events:0/~12 次尝试,包括 28K 次独占 分配,具有先发优势和构造正确的 fake。如果 events 以 p_hit(G)≈0.35 进行 reclaim,五次 pmap 未命中 ≈ 11.6% — 可能但现在 不太可能(约 10-15%)。要么 events 在结构上无法占据此槽位 (原因未知 — 相同缓存、相同上下文、相同时机),要么我们的 G 猜测 系统性地未命中(highmem 边界偏移、分配器放置)。
vmalloc=496M slub_max_order=0 slub_debug=O loglevel=8 initcall_debug
Regions 占据 victim 槽位 3/3;events 0/13。相同缓存(在 源码 + disasm 层面证明),相同进程上下文,相同固定 cpu,相同 kill 后时机,数千次分配具有独占先发优势。 机制未知。剩余嫌疑:分配速率/频率 与 partial-list 轮换的相关性(region ioctls 相隔约 1ms vs event 重命名相隔约 100µs — 相反方向?),或 SLUB freelist 在 slub_max_order=0 下的排序细节有利于……不清楚。
[+] uname.nodename="X?lhost" (was "localhost") <<< ORACLE HIT
**完整的原始字节链在真实设备上生效**:事件回收已释放的区域槽位 → kbase_jit_free 解引用我们的伪造对象(S=来自 kbm 喷洒的 physmap 页,G=0xc154b2a4)→ unlink 执行我们的两次写入。
已用良性载荷(nodename 写入)反复验证。
### 会话中的发现链
1. kbm 页是 GFP_HIGHUSER → HIGHMEM,对 physmap 不可见
(mali_kbase_mem_pool.c:164!)——通过 zonelist 溢出修复:260 × 2MB
喷洒 > highmem-free → 盈余落入 ZONE_NORMAL(physmap)
2. 最初的武器尝试崩溃:W1 目标 N+4 是一个 USER 页——
unlink 在 kworker 上下文中运行(无 mm)→ 缺页。通过将 ring-0
shellcode 烘焙进 physmap 模式中 page+0x600 处修复(arm32 非 LPAE 上
直接映射 RWX)——内核驻留代码,无需 ret2usr
3. ctl_table 偏移错误:proc_handler 位于 entry+0x14,而非 +0x18(
最初的扫描是对的;我的定义写错了)——当时写的是 extra1
4. 发现并回退了过压回归:kid 预算 20×100MB +
尾部 10s 破坏了回收;可用的配置是 2 kids/200MB +
5s/+3200 尾部(回退后良性命中 1/1)
5. **diag2:W2 → &pid_max 全局变量(0xc1114d7c)→ 读取返回我们的值
(-1055861411 = 0xc110d55d 作为 int32)——写入 + 回读已证明**
### 剩余的谜团(距离破解只差一个实验)
使用正确 handler 地址(0xc1113f3c)的 diag1:W1 触发(nodename
改变),W2 必定已执行(下一条指令)——然而 pid_max 读取
仍返回干净的值 → 我们写入的 handler 字段并非 inode 实际分发
所经过的那个。diag3(已排队;需要一次命中启动):W2 →
entry->data 字段(0xc1113f2c)指向 nodename——如果读取随后
显示 nodename 字节作为 int,则我们的 entry 是活的,只是 handler
偏移不知何故错了;如果不受影响,则 inode 使用的是影子表
副本,我们需要找到那个活的。
### 命中率的现实
每次启动如同抛硬币(约 25-40%),呈聚集性;连续几次安全未命中/崩溃启动
是正常的。大约每 3-4 次启动中有 1 次命中。保持可用
配置完全不变(2 kids,5s 尾部,260 区域 kbm,G=0xc154b2a4)。
### Session-9 TODO
1. 在一次命中启动上完成 diag3(反复重启直到 "W1 FIRED")
2. 如果 entry 是活的:经验性地重新检查 handler 偏移(将
proc_dostring 的地址作为 handler 写入,通过…… N 必须等于一个有用的
值——改用 unlink 写入 entry->data 并转向:例如,
data=selinux_enforcing-相邻……)
3. 如果是影子表:定位那个活的——kallsyms 没有数据符号;
候选方案:扫描 /proc/sys 行为,或通过 .data 中的头部列表模式
找到第二个 ctl_table 区域(0x20 步长的条目,
handler=proc_dointvec_minmax 且 maxlen=4——枚举全部并
逐个 diag 写入)
4. 完全避开分发的替代目标类别:从 shell 可达路径调用的
.data 函数指针(需要审计)
5. 写入原语本身已完成——任何可靠的内核地址
目标现在都足以获取 root
## SESSION 9 — nf-WEAPON:reclaim+unlink+G-hit 已在武器中证明;只剩 hook 遍历
### 新的触发设计(完全取代 sysctl-handler 路径)
用户的想法翻译到内核内存:没有 SUID 文件(系统是
dm-verity 只读;原语写入内核 RAM)。取而代之:**伪造 netfilter
hook**。此内核有 Android 通用的新
nf_hook_entries API 回移,但实现为链表(通过
nf_hook_slow + 辅助函数 0xc09897d4 的反汇编验证):
- `__ip_local_out(net, sk, skb)` 从
**[net+0x58c]** 加载 entries CELL,存入 state+0x1c,调用 nf_hook_slow
- 遍历:`entry = *cell`;while(entry){ if (state->[4] <= entry->[0x20])
call entry->[0xc](entry->[0x14], skb, state); entry = entry->[0]; }
——即 **fn@entry+0x0c,priv@entry+0x14,priority@entry+0x20,
next@entry+0x00**;state+4 = INT_MIN 阈值(始终通过)
- init_net = **0xc1104548**(CONFIG_NET_NS=n → sock_net() 内联
该常量;6678 个 movw/movt 引用,直方图冠军;由
nf_hook_slow 自身的字面量交叉确认)。目标:**[init_net+0x58c] = 0xc1104ad4**
- LOCAL_OUT hook 在发送者的进程上下文中运行 → 我们 hookfn 的
commit_creds(prepare_kernel_cred(0)) 将发送数据包的进程提权。
触发 = sendto(127.0.0.1:9) UDP。
### nf 模式布局(poc/stage3.c,模式 `nf 200`)
- kbm 模式页(页相对,单一事实来源——旧
武器有三个现已修复的 bug:proc_handler@+0x14 而非 +0x18;W1
目标必须是内核内存(kworker 上下文,无 mm);烘焙代码位于
page+0x600 与 entry G+0x600=page+0x8a4 不匹配):
- +0x2ac/+0x2bc/+0x2c0/+0x2dc/+0x3a8:伪造 phy-alloc 链(未变)
- +0x600:nf_code hookfn(标记存储 + prepare_kernel_cred +
commit_creds + 返回 NF_ACCEPT(1))
- +0x700:伪造 entry {next=0, fn=PM_PAGE+0x600, priv=0, prio=0x100}
- +0x740:cell → PM_PAGE+0x700
- 载荷:N = PM_PAGE+0x740 (0xc154b740),P = 0xc1104ad4
→ W1:*(cell+4)=P(落入我们的页),W2:*(init_net+0x58c)=cell
### 自诊断插桩(kbm_scan_for)
保留 kbm CPU 映射;释放后我们扫描每个喷洒页
寻找一个已知字:
- 在 page+0x744 处 scan(HOOKS_PTR_ADDR) → 证明 reclaim + unlink 并揭示
哪个物理页支撑 G 的猜测
- 在 page+0x7f0 处 scan(0x600d600d) → 证明 hookfn 已执行
(nf_code 将此标记作为其第 2 个动作写入)
### 关键的那次运行(2026-09-11,session 9 后期)```
[+] W1 CONFIRMED: region 135 page+0x185000 <- 0xc1104ad4 (reclaim + G hit!)
[+] nf trigger done, uid=2000 euid=2000
完整武器链在实机启动中成功触发:事件回收了槽位,伪造代码运行,unlink 执行,G 猜测页(0xc154b000)确实属于我们(区域 135 页 389)。W2 = *(0xc1104ad4)=cell 是相邻指令——它必定已执行。然而 UDP sendto 并未让我们获得 root → 失败发生在 hook 路径内部:遍历语义、[state+0x1c] 数据传递、优先级比较,或入口字段。 (已添加用于区分 hookfn 是否运行的标记实验;仅在会话结束前发生崩溃启动——尚无干净数据。)
pmap 200)是环境健全性检查(热态时 4/4 命中;冷态时崩溃未命中)nf 200,直到出现 W1-CONFIRMED 行,然后读取 MARKER 行:
a. 标记存在,uid!=0 → shellcode 的凭据失败(检查 prepare_kernel_cred/commit_creds 地址;blx 编码)
b. 标记不存在 → 遍历从未调用我们:用第二个标记验证,该标记由……下一步诊断:仅写入标记并返回 1 的 hookfn(无凭据)——如果仍然不存在:
ls -la; 保留标记文件poc/stage3.c 模式:pin/root/drain{,2,3,4,5}/iso——完整武器 + oracle + 隔离测试框架,O_SYNC 崩溃点取证init_net:0xc1104548 是 __stack_chk_guard(movw/movt 直方图被栈金丝雀加载污染——2025 年整个 nf 计划都建立在此之上)。真正的 init_net = 0xc1185040(已确认:ip_send_skb(net,...) 使用此字面量调用;约 994 处引用全部在网络栈中)。IPv4 LOCAL_OUT cell = init_net+0x58c = 0xc11855cc。nf_iterate 将 [init_net+0x58c] 视为 nf_hook_ops 指针本身——它直接从该值读取 fn@+0xc, priv@+0x14, prio@+0x20。会话 9 的伪造入口位于 PM_PAGE+0x700,cell 指向它(cell 处未使用的 next/fn 字段 → fn=0 → 崩溃)。伪造入口必须位于 (0xc154b740)。修复后,钩子调用得到证实( 模式 = SAFE_FN 干净地处理所有 200 次 sendto)。commit_creds(prepare_kernel_cred(0)) / override_creds(&init_cred) 可获得 uid 0,但会落入 kernel SELinux SID,此 Fire OS 策略不允许其写入 /data 或 /sys/fs/selinux/enforce(已验证:EACCES)。真正的 init SID(7)也被拒绝(伪造 struct cred 测试)。enforcing_setup 是 __init(已释放 → 崩溃)。mark_reclaim 的 atomic_sub 需要 nents=1,这会破坏 shrink_cpu_mapping 的提前退出。
制胜点: 利用进程保留了 kbm CPU 映射,因此伪造的 nf 入口可以在数据包之间就地重写:
selroot 模式:入口 = {fn=0xc01d503c (mov r3,#0;str r3,[r0];bx lr), priv=0xc1213ea8 (selinux_state.enforcing)}。*(enforcing)=0 → SELinux 宽容模式。kbm_cpu[reg]+off 将入口重写为 {fn=commit_creds, priv=&init_cred}。commit_creds(&init_cred) → uid 0,且 SELinux 为宽容模式 → 可用的 root,全部在一次回收中完成,无需链式操作。[+] W1 CONFIRMED: region 67 page+0xad000 <- 0xc11855cc (reclaim + G hit!) [*] sendto 0 -> -1 errno=1 uid=0 euid=0 [+] nf trigger done, uid=0 euid=0 <<< HOOK RAN - commit_creds OK [+] ROOT: uid=0 euid=0 [+] setenforce write=1 [+] su copied bytes=236220
- `getenforce` → **Permissive**;暂停的 st3 为 `Uid: 0 0 0 0`,
`CapEff: 3fffffffff`。
- `/data` 以 **nosuid** 挂载,因此 setuid 的 `su` 无法工作。一个微小的
`rootshell`(发送 UDP → 对自身调用 `commit_creds` → `execl sh`)可提供
交互式 root shell:`uid=0(root) context=u:r:kernel:s0`。
- Root 可以读写 `/dev/block/by-name/*`(`dd if=boot ...` 可行)。
### Stage-3 代码状态(`poc/stage3.c`)
- 模式:`nf <mb> [probe|uprobe|oc|chain|fc <sid>|notrig|selroot]`
- `selroot` 是有效的武器。关键静态变量:`init_net=0xc1185040`、
`HOOKS_PTR_ADDR=0xc11855cc`、`ENFORCING_ADDR=0xc1213ea8`、
`ZERO_GADGET=0xc01d503c`、`commit_creds=0xc014993c`、
`init_cred=0xc1114f54`。
- 利用后直接使用系统调用(不使用 `system()`);保持 kctx 存活
(`pause()`)以避免拆除时崩溃。
### 剩余部分(Stage 4/5)
- 重启后的持久化(verity / boot 镜像 / recovery),因为 cell
劫持 + permissive SELinux 仅在运行时有效,重新运行漏洞利用需要
约 1/3 概率的回收硬币翻转。
- `su` 需要一个非 nosuid 的 home(`/system`)或一个能重新触发的启动器。
## SESSION 11 — 持久化侦察(Track B + Track A)与 RE 交接
目标是持久化 root。划定了两条路线:
- **Track B**:禁用验证启动(dm-verity / SELinux),以便可以修补 `/system`。
- **Track A**:在启动时重新运行漏洞利用。
两者都归结为同一个障碍:**让 LK 将设备视为 `eng`/`unlocked`。**
### 验证启动事实(确切构建)
- Bootloader 已锁定,AVB `green`,`ro.boot.unlocked_kernel=false`,`ro.boot.secure_cpu=1`,
`rpmb_state=1`。Bootrom 已修补(无 BROM);preloader 仅通过 CMD 短接。
- `/system` 由 **Android dm-verity 从 lk 构建的内核 cmdline** 挂载:
`root=/dev/dm-0 dm="system none ro,0 1 android-verity PARTUUID=b6404ef3-… "`,
`veritykeyid=id:f3530e18f64d11fc25eb2dd762979f078de990bf`,`androidboot.veritymode=eio`,
`skip_initramfs`(system-as-root)。`dm-0` = 名为 `system` 的 verity 设备;`dm-1` = `/vendor`。
- LK:Amazon **UFBL**,`ro.boot.lk_version=0x0006`,构建 `0db73c9-20231025_030009`;
preloader `pl_version=0x000a`,构建 `80c6fcb-20230523_065640`。`/dev/block/by-name/lk` = mmcblk0p5(1 MB)。
- 完整 GPT(16 个分区,无 `persist`/`seccfg`/`nvram`/`protect`/`para`):
`proinfo` p0、`PMT` p1、`kb` p2、`dkb` p3、`lk` p4、`tee1` p5、`tee2` p6、`metadata` p7、
`MISC` p8、`reserved` p9、`boot` p10、`recovery` p11、`system` p12、`vendor` p13、
`cache` p14、`userdata` p15。eMMC boot0(1 MB)= preloader(`EMMC_BOOT` 魔数);
boot1(4 MB)= IDME 存储。
### LK(UFBL)静态发现
`lk.img` 头:`88 16 88 58 | 00052974 | "LK"`;ARM 向量表位于 0x200,其余为 Thumb-2,
位置无关/已重定位(字面量池使用 `ldr+add pc`,因此朴素的基址相对反汇编会失败)。
相关字符串(文件偏移):`amzn_image_verify`、`amzn_verify_unlock`、`amzn_verify_code_internal`、
`unlock_code`、`unlock code error`、`unlock failed`、`$Common Kernel Signing Engineering CA0`、
`seccfg`、`para`、`ENV_v1`、`LK_ENV`、`Kfos_flags`/`Kdev_flags`/`Kusr_flags`/`Kunlock_code`/`Kunlock_version`、
`FOS_FLAGS_{NONE,ADB_ON,ADB_ROOT,CONSOLE_ON,RAMDUMP_ON,VERBOSITY_ON,ADB_AUTH_DISABLE,FORCE_DM_VERITY,DM_VERITY_OFF,BOOT_DEXOPT}`、
`[DM-VERITY] verify for system(root) is enabled`、`[DM-VERITY] verify off by fos_flags`、
`[DM-VERITY] disabled by fos_flags on eng devices or unlocked device`、
`[SELINUX] set to permissive mode by dev_flags`、`androidboot.prod=1|0`、`androidboot.unlocked_kernel=%s`。
**结论:LK 将 `fos_flags`/`dev_flags` 的安全效果限制在 eng/unlocked 上。**
### IDME 存储(eMMC **boot1**)——可写、持久、由 LK 和 Android 读取
- 魔数 `beefdeed` + `"2.1\0"` + count(0x19=25) 位于 0x0;条目从 0x10 开始。
- 条目格式:`char name[16]; u32 size; u32 type(=1); u32 magic(=0x124); u8 data[size] (pad4)`。
- 条目偏移(原始):`board_id@0x10 serial@0x3c mac_addr@0x68 mac_sec@0x94 bt_mac_addr@0xd0
bt_mfg@0xfc product_name@0x198 productid@0x1d4 productid2@0x210 region@0x24c bootmode@0x26c
postmode@0x28c bootcount@0x2ac manufacturing@0x2d0 unlock_code@0x4ec sensorcal@0x908 alscal@0x9c4
KB@0xa00 DKB@0x1e1c device_type_id@0x2238 dev_flags@0x2274 fos_flags@0x2298 usr_flags@0x22bc
wifi_mfg@0x22e0 unlock_version@0x26fc`。值为 ASCII(标志为 **十六进制字符串**)。
- 运行时读取:`/proc/idme/<name>`(只读)。上次启动的值被缓存;对 boot1 的写入在
下次启动时生效。写入路径需要清除 `/sys/block/mmcblk0boot1/force_ro`(root)。
- **确认 LK 读取 boot1**:更改 `serial` 会在下次启动时改变 `ro.boot.serialno`。
但 LK **将 serial 截断为 16 字节**,并忽略了 `fos_flags=0x80`、`dev_flags=0xff`、
全 1 等——verity/selinux/`prod` 未变。因此通过 serial 注入 cmdline 失败。
### Android 侧 IDME 标志的消费者
- `/init.fosflags.sh`(服务 `fosflags`,`u:r:fosflags:s0`):`FOS_FLAGS_ADB_ON=0x1`、
`CONSOLE_ON=0x4`、`RAMDUMP_ON=0x8`、`VERBOSITY_ON=0x10`、`ADB_AUTH_DISABLE=0x20`、
`BOOT_DEXOPT=0x100`。已验证:设置标志会生效(`sys.usb=adb`、`noadbauth=1`)。
- **adbd**(未剥离的 ARM ET_EXEC;`.text` VA 0x8160 / 文件 0x160;fileoff = VA-0x8000):
- `amzn_is_root_allowed` @0x2d5b8 = `amzn_is_dev_unlocked() && (fos_flags & 0x2)`
- `amzn_is_adb_auth_disable_allowed` @0x2d5e8 = `fos_flags & 0x20`(无门控)
- `amzn_is_dev_unlocked` @0x2d5fc = `/proc/cmdline` 包含 `androidboot.prod=0` **或**
`androidboot.unlocked_kernel=true`
- `fos_read_debug_flags` @0x2d724 读取 `/proc/idme/<name>` 并解析 **十六进制**
- `restart_root_service` @0xcb74 / `restart_unroot_service` @0xcc64
- 字符串:`amzn_fos: ADB: Auto-root succeeded`、`… eng_device=%d`、`… unlocked_kernel=%d`、
`adbd cannot run as root in production builds`、`ro.debuggable`
- `adb root` → "cannot run as root in production builds"(`ro.debuggable=0`)——因此即使
满足了自动 root 门控,AOSP 生产检查也会门控该命令路径。
### 为什么 Session-10 的 root 不持久
- SELinux permissive + cell 劫持 + root 仅在运行时有效。
- `/data/metrics` 是一个 **vpartition**:`/system/bin/vpartition.sh` 在每次启动时将
`/data/vp/metrics.img`(ext4,非 nosuid/noexec)挂载到 `/data/metrics`;写入那里的 `su`
**不会**在重启后存活。(这也是为什么 setuid 的 `su` 给出了 uid 0 但 **零 caps**。)
### Track A(启动时重新利用)——受阻
- 没有 init `.rc` 触发器执行可控代码(导入全部已验证;`persist.*` 触发器仅
`start` 固定服务;`/system`/`/vendor` 中的脚本)。
- Root 服务读取 `/data` 配置但从不从中执行(`perfmonitord`、`amazonfiled`、
`vpartition.sh`、`kisd`、……)。
- 唯一的启动执行器 = 一个 **app**,但漏洞利用的暂停占用为 **VmRSS 534 MB**
(`kbm` 喷射)→ lmkd 会杀死它;加上丢失回收会 panic(`PANIC_ON_OOPS`)→ bootloop。
- **adbd 自动 root** 存在,但受 LK 构建的 cmdline 门控(`prod=0`/`unlocked_kernel=true`)。
### 结论 / 下一个目标(选择:Track B RE)
一切都取决于让 LK 报告 `eng`/`unlocked`。可及范围:
`androidboot.prod=1|0` 和 `androidboot.unlocked_kernel=false` 由 LK 设置。逆向 LK 以找到:
1. 它在哪里读取 `fos_flags`/`dev_flags`/`usr_flags`(`K*` 条目)以及确切的门控;
2. `prod`/`unlocked` 的确定方式(IDME 条目?buildvariant?`amzn_verify_unlock` 结果?);
3. `amzn_verify_unlock`(libtomcrypt RSA 验证)以寻找绕过或弱 unlock_code/version 路径;
4. `seccfg`/`para`/`ENV_v1`(LK_ENV) 存储(不在任何已转储分区中——可能受 tee 保护);
5. preloader(`boot0`、`EMMC_BOOT`)以寻找 bug。
如果其中任何一项让我们设置 eng/unlocked(持久地,通过 boot1 或原始分区写入),那么
`FOS_FLAGS_DM_VERITY_OFF` 会禁用 system(root) verity,并且 `/system` 可以被持久地修补。
### 产物(来自本次会话)
`/tmp/opencode/mustang-dumps/`(主机重启时可能被清除):`lk.img`、`boot1.img`(原始)、
`boot.img`、`MISC.img`、`metadata*.img`、`pmt.img`、`mbr.img`、`kb.img`、`dkb.img`、`reserved.img`、
`cache.img`、`boot0.img`、`boot1.img`、`adbd.bin`、`perfmonitord.bin`、`amazonfiled.bin`。
辅助工具:`tools/findinitnet.py`、`findgadget*.py`、`findstores.py`、`adbd_sym.py`(在 /tmp 中);
仓库中有 `run.sh`、`poc/stage3.c`(`selroot`)、`poc/su.c`、`rootcmd.sh`。
### 便捷命令```
# IDME read
/data/metrics/su sh -p -c 'for f in fos_flags dev_flags usr_flags serial region device_type_id unlock_version; do echo -n "$f="; cat /proc/idme/$f; echo; done'
# write boot1 (root; su lives only until reboot -> re-run run.sh first)
/data/metrics/su sh -p -c 'echo 0 > /sys/block/mmcblk0boot1/force_ro; dd if=/data/local/tmp/boot1.img of=/dev/block/mmcblk0boot1 bs=4096 count=4; sync; echo 1 > /sys/block/mmcblk0boot1/force_ro'
# dump a partition to host
adb exec-out '/data/metrics/su dd if=/dev/block/by-name/lk bs=4096 2>/dev/null' > lk.img
目标:让 LK 将设备视为 eng/unlocked,或找到一个 preloader/LK 漏洞,
以便持久性地禁用 verity/SELinux。结果:端到端地逆向了相关的
LK 代码路径;该翻转无法通过可用的存储达成。
没有设备变砖;唯一一次 boot1 实验已恢复至原始状态。
lk.img 以一个极小的 ARM 存根开头(文件偏移 0x200)。位于 0x224 的重定位器
从 0x200 复制到一个字面量目标地址,并跳转到一个字面量入口点:
因此对于偏移 >= 0x200 的部分,运行时地址 = 0xFF400000 + 文件偏移。
存根之后的所有内容均为 Thumb-2,位置无关。字符串通过
ldr rT,[pc,#imm](T1 偏移 = imm8*4;ldr.w 偏移 = imm12)后接
add rT, pc 构建;目标为 (add+4) + *pool。一个能够穿越
ARM 存根和字面量池的稳健扫描器已添加为 tools/lk_xref.py(处理 16 位和
32 位形式,每 2 字节扫描一次)。以下所有偏移均为文件偏移;
加上 0xFF400000 即为运行时地址。
0x20b4 读取 unlock_code IDME 项(0x400 字节,本机上全为零)
并运行 amzn_verify_unlock(0x222c -> 0x20f0)。该函数驱动
libtomcrypt(数十条 /features/libtomcrypt/src/pk/asn1/der/... 路径和
RSA 验证),且镜像中嵌入了证书材料:
Sunnyvale / Amazon Lab126 / "$Common Kernel Signing Engineering CA0" 位于
0x317d9+,以及诊断信息
Image FAILED AUTHENTICATION on PRODUCTION device(0x3166e)、
Authentication failed on engineering device with production certificate(0x316a0)、
Image FAILED AUTHENTICATION on ENGINEERING device(0x31703)、
Image AUTHENTICATED with PRODUCTION certificate(0x31736)。
不存在空码 / 长度 / 版本捷径:verify(zeros) != 0,因此
(已在 中确认)。翻转
或 需要要么一个有效的
Amazon 签名的 (私钥不可用),要么验证器中的代码执行漏洞。在
0x20b4/0x222c/0x20f0 中静态分析未发现任何可利用之处(边界/大小)。
=>
安全标志通过 0x57c 处的 getter 读取。实证测试:```
dd if=/dev/block/mmcblk0boot1 ... ; reboot /proc/idme/fos_flags -> 00000080 (persisted, Android sees it) ro.boot.veritymode -> eio (unchanged!) root=/dev/dm-0 dm="system none ro,0 1 android-verity PARTUUID=..." (unchanged) androidboot.prod=1 / secure_cpu=1 / buildvariant=user (unchanged)
`fos_flags=0x80` 是 `FOS_FLAGS_DM_VERITY_OFF`;如果 getter 返回了它,解码后的门控本会关闭
verity。但它没有。该门控是活跃的,而非死代码:它的一次性缓存哨兵值在镜像中为 `-1`
(`*(u32*)0x50c74 == 0xffffffff`),因此该函数确实执行了
`check_flag("fos_flags",0x80)` 路径并得到了 0。因此该 getter(至少在
verity 保护时)**并未**读取 boot1 IDME 项。
另一个候选存储是 **LK env**,从一个字面名为
`"para"` 的分区加载(loader 0x12fd4,magic `ENV_v1`,checksum @0x3ffc)。LK 自己的
分区表(0x4fcc0..0x50340)列出了 preloader/proinfo/nvram/protect1/
protect2/persist/seccfg/secro/**para**/logo/custom/expdb/tee1/tee2/metadata/
system/cache/userdata —— 但该平板实际的 GPT **只有 16 个条目**,全部
类型为 `af3dc60f838472478e793d69d8477de4`:```
#0 proinfo 0x400 #1 PMT 0x1c00 #2 kb 0x4000 #3 dkb 0x4800
#4 lk 0x5000 #5 tee1 0x5800 #6 tee2 0x8000 #7 metadata 0xa800
#8 MISC 0x1e400 #9 reserved 0x1e800 #10 boot 0x22800 #11 recovery 0x2a800
#12 system 0x34800 #13 vendor 0x644000 #14 cache 0x6b4800 #15 userdata 0x7ae800
本产品上没有 para、seccfg、nvram、protect 或 persist 分区(且 PMT/pmt.img 转储全为零)。因此 LK 环境为空,Kfos_flags/Kdev_flags 键从不存在,所有 fos_flags/dev_flags 检查都解析为 0——与 IDME 项包含什么内容无关。boot1 IDME 项由 Android 消费(/init.fosflags.sh、adbd、/proc/idme/*),但不被 LK 的安全门消费。
unlocked_kernel 需要 Amazon 签名的 unlock_code(RSA/libtomcrypt,内嵌 CA)。无法离线伪造;未发现验证器漏洞。硬阻止。DM_VERITY_OFF / selinux=permissive 标志从 LK 环境(para/ENV_v1)消费,而该环境在此 GPT 上不存在。IDME fos_flags 经验上被 LK 忽略(0x80 已持久化,verity 仍为 eio)。硬阻止,除非修改分区表。fos_flags=0x80,也只会设置 androidboot.veritymode=disabled 和一个非 dm-0 的 root=;它不会解锁,且 SELinux 仍需要来自同一缺失环境的 dev_flags 才能转为 permissive。para/ENV_v1 存储:在 userdata 之后的空闲空间(userdata 结束于 LBA 0x3a3dfde;磁盘 = 30535680 扇区)添加一个名为 para 的 GPT 条目(主 GPT + 备份 GPT 都必须更新),然后构造一个包含 fos_flags=0x80 和 dev_flags=0x40 的环境(校验和在 +0x3ffc = 对 0x3ffc 的字节求和)。这是关闭 verity 的唯一剩余途径。风险:损坏主/备份 GPT 可能导致变砖;且未证明 verity 门实际读取 para(仅证明它不是 boot1 IDME)。boot0/EMMC_BOOT)漏洞:本次会话未逆向。在存在原始副本和恢复路径之前,禁止写入 boot0。tools/lk_xref.py —— 与基址无关的 LK 字符串交叉引用解析器。/tmp/opencode/mustang-dumps/lk.img、boot1.img(原始)、boot0.img、mbr.img(GPT)、pmt.img(全零)。/tmp/opencode/s12/boot1_f80.img;设备已恢复到原始 boot1(已验证 /proc/idme/fos_flags -> 0)。./run.sh 重新武装)```./run.sh --no-build
/data/metrics/su /system/bin/sh -p -c 'cat /proc/cmdline'
for f in fos_flags dev_flags usr_flags unlock_version serial; do cat /proc/idme/$f; echo; done
## 会话 13 — 预加载器终究可达(`1949:20ff` = MTK 预加载器,HID 传输)
在关机并插入 USB 时,平板电脑枚举为 **`1949:20ff`**
(`Lab126`)——*不是* Android,也*不是* `0e8d:0003` bootrom。描述符:```
bInterfaceClass 3 (HID), iConfiguration "HID", iInterface "HID Interface"
HID report descriptor = 05 01 09 00 a1 01 c0 (empty collection!)
EP 0x81 IN interrupt 4 bytes, bInterval 4
EP 0x01 OUT interrupt 4 bytes, bInterval 4
iSerial = GCC0X90805310009 (the IDME serial)
识别。 0x20FF 在 mtkclient 的 config/usb_ids.py 中被列为 "MTK Preloader"(位于 MediaTek VID 0x0e8d 下:0xe8d:{0x0003 Brom, 0x2000/0x2001/0x20ff/0x3000 Preloader})。Amazon 保留了 preloader PID,将 VID 改为 0x1949,并将其呈现为一对带有虚拟报告描述符的 HID 端点。因此这是 MediaTek preloader / USBDL 模式,一个位于 LK 之下的阶段——在此处通过关机 + 插入来进入,而非通过 CMD 短接。
描述符字符串 "HID"/"HID Interface" 不存在于 lk.img、boot0.img、boot.img 或其他转储中,即该模式由我们尚未转储的组件(bootrom/TEE)产生,或在运行时组装。
为何重要。 aftv2-tools 所使用的 Amazon preloader 通过这一确切的字节流暴露了内置的、无需 Download-Agent 的命令:```
handshake : host A0 0A 50 05 -> dev 5F F5 AF FA
0xD1 read32 (addr, n_words) : echo cmd/addr/n, 00 00, nu32, 00 00
0xD4 write32(addr, words[]) : echo cmd/addr/n, 00 00, nu32, 00 00
`aftv2-tools/read_mmc.py` 使用 `read32`/`write32` 来操作 MSDC 控制器
(MT8173 上的基地址为 `0x11230000`;MT8163 需另行确认),并在没有 DA 的情况下
读写 **原始 eMMC 块**,因此不会受到 AVB/verity 的阻碍。如果 mustang 的 preloader
接受 0xD1/0xD4,那就是一条通往持久解锁(修补 `boot` / `lk`)的直接路径,
独立于 RSA 解锁码和缺失的 LK 环境变量。
### 新增工具(需要 root;先对 USB 节点执行 chmod)```
lsusb -d 1949:20ff # note Bus/Dev, e.g. Bus 001 Device 003
sudo chmod 666 /dev/bus/usb/001/003
# 1) does it answer the MTK handshake? (no DA, no flash access)
nix-shell -p python3Packages.pyusb --run \
'python3 tools/mtk_preloader_hid.py handshake'
# 2) read-only arbitrary memory read
nix-shell -p python3Packages.pyusb --run \
'python3 tools/mtk_preloader_hid.py read32 0x00100000 4'
tools/probe_preloader.py 是最小化的仅握手探测脚本;
tools/mtk_preloader_hid.py 是完整的传输实现(handshake/read32/
write32;write32 受保护,在 eMMC 寄存器映射确认之前不应使用)。
read_mmc),然后从 preloader 修补 boot.img/lk 并重启。amzn_verify_unlock 或 LK 环境变量门控。futex.c:1975 传入 this->rt_waiter,
在 futex.c:2880 的 futex_wait_requeue_pi 中声明)→ waiter 通过 arm32 select(nr 142)fd_sets 在其自身已释放的栈帧上打标记DEBUG_RT_MUTEXES 关闭
→ 紧凑的 48 字节 rt_mutex_waiter(tree_entry@0, pi_tree_entry@0xc, task@0x18,
lock@0x1c, prio@0x20, deadline@0x28)modprobe_path @ 0xc111488c(字符串在
vmlinux 中自定位;KALLSYMS_ALL 关闭,因此数据符号需要此技巧)→ 未知 binfmt exec → root
脚本(setenforce 0,禁用 OTA,su)refs/ 中的参考:NebuSec/CyberMeowfia(原始),GhostLock-5.10(Fire OS 8 移植,
src/exp32/ 中的完整 32 位 ARM 触发),ghostlock-...-4.19-k40(Qualcomm 4.19 Android 移植)commit_creds(&init_cred)uid=0nr_extresinfo->gpu_alloc_addr 写入(你必须
预先分配并传入的 GPU VA)| 可驱逐对象 | 压力 | 结果 |
|---|
| 无 | 900 MB | 存活 |
| 无 | 1300 MB | panic(系统 lowmem bug——无关) |
| 普通区域 + DONT_NEED | 700 MB | 存活 |
| JIT 区域 + DONT_NEED | 700 MB | 在 reclaim 路径中 panic |
/proc/config.gzksrc/ — Amazon OSS 源码(platform.tar + 提取的 midgard-r26p0 树)/tmp/opencode/mustang_ota.bin(sha256 6068515a… 与 fireos-archive 匹配)
以及 2.2 GB 内核源码 tarball 保存在 ~/Desktop/amazon-mustang/ksrc/kernel/mediatek/mt8163/4.9/drivers/misc/mediatek/gpu/gpu_mali/mali_midgard/midgard-r26p0/| 在解引用处崩溃 |
| + cpu0 固定生命周期(drain3) | 在解引用处崩溃 |
| 分步压力(drain4 v1) | 子进程退出时释放内存 → 无驱逐(验证合法路径) |
| 运行 | 结果 |
|---|
| pmap G=c2a412a4 400MB | 在 deref 处崩溃 |
| pmap G=c2f4b2a4 480MB | 崩溃;+0 次 kill 后重命名(480MB 使 fs 窒息) |
| iso2(spray + regions + oracle) | REGION 命中 — spray 不会破坏 reclaim |
| mix v1(并发 events+regions) | 崩溃;混杂(自旋 event 线程) |
| pmap G=c2a7d2a4 350MB + retouch | 崩溃;+4452 次重命名 OK |
| mix2(顺序:2s events 然后 regions) | 崩溃;+7126 次重命名(28K event 分配),2715 个 regions |
PM_PAGE+NF_CELL_OFFprobekernel_x_end 以上为 XN:arch/arm/mm/mmu.c 的 map_lowmem() 将内核文本以下的 lowram 映射为 MT_MEMORY_RWX,但 kernel_x_end 以上的所有内容为 MT_MEMORY_RW → PMD_SECT_XN(第 509 行)。位于 0xc154b600 的固化 shellcode 会预取中止。Payload 必须是真正的内核函数指针,而非 physmap 中的代码。| 字面量(文件偏移) | 值 | 含义 |
|---|
| 0x270 | 0xFF4002F8 | str r4,[r6] 暂存区 |
| 0x274 | 0xFF40027C | 目标地址(基址+0x27C) |
| 0x278 | 0xFF54A440 | 复制结束(含 BSS) |
| 0x27C | 0xFF400484 | 入口点 |
| 偏移 | 函数 |
|---|
0xdf7c | is_secure_or_prod() -> byte[ [[g]+0 ] + 0x163 ];g = 全局变量 @0x52838。本机上为 1。 |
0x20b4 | verify_stored_unlock() = memset(buf,0,0x100);通过 0x57c 读取 IDME/env unlock_code(0x100);bl 0x222c;返回 (verify==0)。 |
0x222c / 0x20f0 | amzn_verify_unlock(code,len) — libtomcrypt RSA/PKCS#1 验证(见下文)。 |
0xda3e | is_unlocked() = is_secure_or_prod() && verify_stored_unlock()。 |
0x29a28 | is_verity_disabled() = (fos_flags & 0x80) && !(is_secure_or_prod() && verify_stored_unlock());缓存在全局变量 @0x50c74 中。 |
0x29974 | SELinux cmdline 构建器:dev_flags & 0x20 -> androidboot.selinux=enforce,dev_flags & 0x40 -> ...=permissive(各自受 byte[+0x162] 门控)。 |
0x118xx/0x11bxx | 内核 cmdline 构建器(unlocked_kernel、prod=1/0、verifiedbootstate、rpmb_state、secure_cpu、版本、root=)。 |
0x27af8 | UART 门控:fos_flags & 0x4 -> printk.disable_uart=0,否则 =1。 |
0x12fd4 | LK 环境变量加载器:分区 "para",0x4000 字节,魔数 ENV_v1,校验和 = 0x3ffc 范围内字节之和与 @0x3ffc 处的字比较。 |
0x1efd0 | 按名称查找分区(用于 "para"、"boot" 等)。 |
0x57c | 通过回调槽 @0x58218 的 getter 分发器;槽 @0x58200..0x5821c 从 0x5a8-0x734 处的表注册。 |
0x2a19c | fastboot oem unlock:bl 0x222c(code,len);成功后通过 0x408 写入 unlock_code(0x100)。 |
unlocked_kernel=false/proc/cmdlineandroidboot.unlocked_kernelandroidboot.produnlock_code