Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
amazon-mustang-hack — 针对 Amazon Fire 7(Fire OS 7.3.3.1)的内核漏洞利用研究,通过 Mali kbase JIT 释放后使用漏洞 CVE-2022-38181 实现临时 root,并采用 modprobe_path 覆写链。 | Kitploit
工具/GitHubGitHub/artur9010/amazon-mustang-hack
Android安全权限提升漏洞分析漏洞利用逆向工程移动安全论文与研究Payload 开发二进制利用
GitHubartur9010/amazon-mustang-hack

amazon-mustang-hack

针对 Amazon Fire 7(Fire OS 7.3.3.1)的内核漏洞利用研究,通过 Mali kbase JIT 释放后使用漏洞 CVE-2022-38181 实现临时 root,并采用 modprobe_path 覆写链。

13小时58分前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享
查看仓库网站

AI 辅助项目。 本研究、漏洞利用开发及文档均借助 AI 辅助完成,使用了 GLM-5.3 和 DeepSeek V4.1 Flash 模型。

amazon-mustang-hack

针对 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),因此唯一剩余的途径是软件内核漏洞利用。

快速开始```

one-shot: build, run the exploit (retries across the probabilistic reclaim),

install a setuid-root su and verify it as an unprivileged user

nix-shell -p android-tools --run './run.sh' # add -p zig too if no zig

root@kitploit:~
成功后:```
/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 重新构建它们。

以下内容只是模型工作日志,没有人类输入。

主要目标(自 session 5 起):kbase CVE-2022-38181 — stage 2 已证实

GhostLock(见下文)已搁置:MTK 的 BUG_ON rtmutex 变体 + shell 无法泄露任何内核地址 = 在此构建上架构性死路(sessions 2-4)。 kbase JIT UAF 被重新诊断(destroy-worker 的“无条件 panic”其实是 JIT_FREE 解引用,日志因 adbd 在 panic 中途死亡而丢失),stage 2 现在已由 oracle 证实——见 SESSION 5 部分。

已搁置:GhostLock,CVE-2026-43499

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 错误路径)
  • 触发面 = 纯 futex 系统调用(WAIT_REQUEUE_PI/CMP_REQUEUE_PI),无需设备节点, 没有任何受 SELinux 限制的东西——kbase 路径的致命障碍在这里不存在
  • 消费者:sched_setattr → __sched_setscheduler → rt_mutex_adjust_pi(p) 位于 sched/core.c:4706——解引用过期的 pi_blocked_on ✓

TODO(移植计划)

  1. 编写触发(3 线程 requeue-PI 死锁,核心 0-3)——移植 exp32/main.c
  2. 标记几何:rt_waiter 帧偏移 vs do_sys_select fd_set 区域——反汇编 我们的 vmlinux(do_sys_select stack_fds vs futex_wait_requeue_pi 帧),将 STAMP_NFDS/STAMP_WAITER_OFF 暴露为可调参数
  3. 针对 arm32 48 字节 waiter 的 fake-writer 编码 → “写 V 到 ADDR”槽
  4. 2 个槽 → modprobe_path,触发,root 脚本
  5. 如果 select-stamp 无法触及的备选方案:setsockopt(MCAST_JOIN_SOURCE_GROUP) 标记

状态

  • Bootrom(amonet 硬件方法)——此设备上已修补,死路
  • mtk-su (CVE-2020-0069)——已修补,Failed critical init step 3
  • 攻击面调查——/dev/mali0 全局读写 + SELinux gpu_device,kbase r26p0-01rel0
  • CVE-2022-38181 在确切构建源码中确认;stage-1 触发有效
  • CVE-2026-43499 (GhostLock) 已验证但受阻:MTK BUG_ON rtmutex 变体 + shell 无法泄露内核地址(sessions 2-4)
  • CVE-2022-38181 stage 2 已证实(session 5):destroy-worker panic 是 误诊;UAF 重定向到喷射区域,oracle 验证
  • Stage 1:触发 + 标记 + 消费者(崩溃 = 链已激活)
  • Stage 2(kbase 路径):UAF 重定向到喷射区域——已证实 session 5
  • Stage 2b:原始字节槽控制(xattr 标记搅动)→ unlink 写
  • Stage 3:任意内核函数调用 → ROOT(session 10)——nf LOCAL_OUT 钩子劫持,selroot 2 包链:清零 selinux_state.enforcing,重写 假条目为 。,SELinux Permissive。

关键发现

设备 / 固件

  • 型号 KFMUWI,设备 mustang,Fire OS 7.3.3.1 PS7331.4463N/0031575863040
  • 内核 4.9.117-g08fe75b-dirty,构建于 Sat May 3 01:25:15 UTC 2025(Linaro GCC 6.3-2017.05)
  • Amazon 在 2025 年 5 月静默重新发布了 7.3.3.1(新的增量,相同版本字符串)
  • 2020 年后的 Bootrom 修订版:eMMC CMD 短接到 GND 仅给出 preloader(已修补)
  • /dev/kb、/dev/dkb(Amazon 内核备份分区)root:drmrpc 0660——已锁定

为什么 CVE-2022-38181 适用

  • 驱动:mali_kbase r26p0-01rel0(Midgard,Mali-T720),在 NVD 受影响范围 r4p0–r31p0 内
  • Amazon 2025 年 5 月的重新构建原样发布了 2018 年的 bug——没有 backport
  • 确切漏洞代码,源码验证:
    • 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 期间触发)

利用环境(全部从实时配置转储 + OTA vmlinux 验证)

  • armv7 32 位,非 LPAE → 无 KASLR(内核位于固定 0xc0008000 VA / 0x40080000 PA)
  • 无 ARM_SW_DOMAIN_PAN → ret2usr 可行;CONFIG_PANIC_ON_OOPS=y(失败尝试 = 重启)
  • 无 SLAB_FREELIST_RANDOM/HARDENED,无 CONFIG_USER_NS/USERFAULTFD/NF_TABLES
  • CONFIG_MODULES=y,无 STATIC_USERMODEHELPER → modprobe_path 覆写 = root
  • 1 GB RAM → 直接 reclaim(驱逐所需)轻易可达;压力 >~1 GB 会自行导致内核 panic(无关的 lowmem/OOM bug)——保持喷射 ≤ 900 MB,使用 ~700 MB

PoC 开发中遇到的 UAPI 怪癖(r26p0,_IOC_TYPE 0x80)

  • MEM_ALLOC 联合体为 32 字节(in 有 4 × u64,包括 extent)
  • flags 必须包含 BASE_MEM_PROT_GPU_RD|WR(位 2|3),而非旧版 R|W
  • 任何分配前必须 mmap tracking-page:mmap(fd, offset=3<<12, PROT_NONE)
  • JOB_SUBMIT 步长必须等于 sizeof(base_jd_atom_v2) = 48(base_jd_prio/base_jd_dep_type 是 u8 typedef)
  • JIT:MEM_JIT_INIT(nr 14,v2 结构),分配/释放是软作业,通过 JOB_SUBMIT (BASE_JD_REQ_SOFT_JIT_ALLOC=0x209,...FREE=0x20a;jc=用户指针,=计数)

Stage-1 差分(证明 bug 触发)

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

root@kitploit:~
## 参考资料

- 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):

  • 9b44-9b54:重试头部;pi_blocked_on==NULL → 干净退出 ret 0
  • 9abc-9adc:orig_waiter==NULL → 跳过 pi_waiters 检查(adjust_pi 总是传 NULL)
  • 9b10-9b28:优先级检查(prio==task->prio + MIN → 退出 9b58)
  • 9b2c-9b38:trylock(lock+0) — ticket;失败 → 带计数器 bail 的重试循环(9a90-9aa8,限制 @ *(0xc11189c8))
  • 9ba4-9bc0:死锁检查
  • 9bcc-9bd8:THE BUG_ON(leftmost→W→W->lock==lock 否则死亡)
  • 9bdc-9c04:出队(残留树 RB_CLEAR_NODE'd = EMPTY → 安全跳过),prio/deadline 写入
  • 9c04 bl:rt_mutex_enqueue → *link = waiter+0 于 lock+4 ← 写入发生
  • 9c3c+:owner==NULL → 干净退出路径

2. 内核栈地址泄漏(破坏无地址要求)

/proc/self/task//stat 字段 28(kstkesp)从 SHELL 上下文返回 syscall 阻塞线程的真实内核 SP(已验证:观察到非零值)。

  • waiter 阻塞在 read(blocking_pipe) → stat → kstkesp
  • 栈基址 = kstkesp & ~0x1fff(arm32 THREAD_SIZE=8192)
  • rt_waiter 绝对地址 = base + 固定偏移(可计算:sp0 = base+0x2000-0x48 pt_regs;waiter = sp0-0x1d8)
  • 所有自引用 stamp 值都变得可计算!

完整自洽 stamp(泄漏之后):

  • L = waiter+0x24(窗口内的伪造 lock — 全部 4 个字可控)
  • iov[0].base(waiter+0x1c lock)= L
  • iov[0].len(waiter+0x20 prio)= 1(≠139)
  • W = waiter+0x1c;stamp *(W+0x1c) = *(waiter+0x38) = L(BUG_ON 通过)
  • lock+0(waiter+0x24)= 0;lock+4(waiter+0x28)= 0(写入落在此处); lock+8(waiter+0x2c)= W;lock+0xc(waiter+0x30)= 0(owner NULL)

Oracle 状态

  • 遍历期间崩溃 = 遍历已运行(死锁探针:确定性崩溃,干净代码)
  • 干净遍历 + 无写入 = trylock 失败重试 bailout(kptr anchor:运行时字非零)
  • 在污染修复之后,一切现在都是确定性的。

下次会话 TODO

  1. 实现泄漏:waiter 阻塞在 pipe 上,主线程读取 stat,计算基址
  2. Stamp 自洽窗口,触发遍历 → 无崩溃完成 = 写入已证明
  3. 武器化:写入总是落在 lock+4(rb_link_node)— lock 必须位于 窗口内(唯一完全可控的内存),因此目标选择研究: 要么找到窗口内被调用槽位的技巧,要么两阶段构造。

SESSION 4 最终状态 — 墙(精确刻画)

完整图景

遍历确定性地触发(死锁探针:每次都崩溃,干净代码)。 写入无法落地,因为一个 3 重内核加固巧合:

  1. MTK rtmutex BUG_ON 变体:lock+8(leftmost)必须指向 W,且 *(W+0x1c)==lock。全零/垃圾 anchor 都会死亡。不存在静态自引用 模式(扫描 6571 个候选,0 命中)。运行时指针未知。
  2. 从 shell 无内核地址泄露:
    • arm32 上的 kstkesp = USER SP(task_pt_regs->ARM_sp)— 不是内核栈。死路。
    • dmesg/pstore/pagetypeinfo/kallsyms/stack — 全部被拒绝。
    • 运行时 kptr_restrict=1(fops anchor 字在运行时也非零 — kptr anchor 运行通过 trylock 失败重试 bailout 退出,而非 trylock 成功)
  3. rodata 中的 fops 表:trylock strex 中止(session-3 扫描崩溃)。

stamp 窗口(waiter+0x1c..0x5b)是唯一可控且内容已知的 内存,但它的地址正是我们需要的未知量。自引用构造 都需要将内核地址作为常量 stamp — 没有泄漏就是循环依赖。

gitchw 对比(为什么他们的 ARM32 写入成功,我们的还不能)

他们的 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 风格。

Session-4 已验证事实

  • 遍历重试循环有计数器 bailout(限制 @ *(0xc11189c8));在运行时非零 anchor 字上 trylock 失败 → 干净重试 bailout 退出(kptr anchor 运行)
  • 无重新入队路径(9ce4,完整遍历)也在 9d64 解引用 leftmost — 无逃逸
  • RB_CLEAR_NODE 自指针作为残留存在于窗口中(waiter+0 和 +0xc 包含它们自己的地址),但没有检查比较以 避免 stamp 已知地址的方式使用它们
  • 真实 mutex 候选(chain mutex 有活跃 waiter = BUG_ON 会通过)— 但 &chain_mutex 是堆地址,没有泄漏无法到达

下次会话选项(排序)

  1. logcat 内核指针搜寻:Amazon HAL/守护进程很啰嗦;任何记录的 内核指针(即使是过期的)都能解锁构造。测试成本低。
  2. 此构建上的 /proc/net %pK 行为:某些 4.9 树在 /proc/net/tcp,udp,unix 中为无特权读者打印未哈希指针。实时测试。
  3. 悬空 pi_blocked_on 上的线程退出路径(一次性,不同解引用)。
  4. 用积累的 4.9 知识重新审视搁置的 kbase JIT bug。

SESSION 4 附录 — 泄漏搜寻:已穷尽(定论)

从 shell 域测试并确认死路:

  • /proc/net/{tcp,unix,packet,netlink,ptype}:%pK 哈希为 00000000(kptr_restrict=1)
  • /proc/timer_list:可读但指针 %pK 清零(符号可见,无地址)
  • logcat:Amazon/wpa 输出中无内核指针
  • kstkesp(stat f28):arm32 上是 USER SP(task_pt_regs->ARM_sp)
  • MTK 节点(/proc/ged, mtk_cmdq_debug, mtktz, ptp, chip, aed, driver/*):全部 SELinux 拒绝
  • /proc/{iomem,vmstat,kmsg,keys,crypto,slabs...}:拒绝
  • /sys/kernel/notes:拒绝
  • CONFIG_VECTORS_BASE=0xffff0000(高向量 — NULL+0x1c 故障)
  • CONFIG_KUSER_HELPERS=y(kuser 在 0xffff0000,不是第 0 页)

结论: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 在此精确源码中已验证存在, 有可用的触发,其阻塞是机械性的,而非架构性的。

SESSION 5 — STAGE 2 已证明(执行选项 b)

重新诊断:"无条件 destroy panic" 从未存在

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 流程在此构建上完全可用。

完整原语清单(精确 vmlinux 反汇编)

kbase_jit_free(kctx, reg) @ 0xc058495c,带完全可控的伪造 reg:

  • reg->cpu_alloc NULL → backed size 0 → trim 块跳过(0xc0584978)
  • bin 递减: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(写入伪造对象,良性)
  • shrink_cpu_mapping 在 new==old 时提前退出(nents=0 → return)
  • list_add(gpu_alloc->evict_node, &kctx->evict_list):evict_list 头 @ kctx+0x1427c;写入 gpu_alloc+0x18/0x1c(必须可写)
  • WARN 路径(0xc0584bd4)非致命(无 panic_on_warn)且继续
  • UNLINK @ 0xc0584b08/b0c:r3=*(reg+0x3c) prev, r2=*(reg+0x38) next → *(next+4)=prev; *(prev+0)=next — 两个任意 write-what-where, 然后将 reg+0x38 重新链接到 jit_pool_head @ kctx+0x148e8

静态伪造 gpu_alloc 链(离线 vmlinux 扫描,/tmp/opencode/scan_s.py)

9 个候选;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)。

Spray 工程(什么有效,什么无效)

  • 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 不使用)
  • kbase_va_region 本身:kzalloc(72) → kmalloc-96! MEM_ALLOC(va=0x40, commit=0x10) 只将 region 放入 kmalloc-96(phy alloc → 384)→ 确定性回收类型。真实 region 受害者使 kbase_jit_free 通过完全合法状态完成(空 jit_node → 自解链)。
  • 压力后顺序 spray:总是错过 — worker 在压力中途将槽位释放到 部分 slab(SLUB:释放到非活动 slab ≠ cpu freelist);压力下我们的 alloc 失败 → 净量为零 → 无轮换
  • 固定 sprayer + 上限:仍然错过(512 上限在驱逐前耗尽; worker 可能在任何 cpu 上运行)
  • 赢家:commit_pages=0 spray — 无物理页 → MEM_ALLOC 在整个 压力风暴中成功 → 约 6000 净分配 → 部分列表 轮换有保证。8 线程(2/cpu,cpu 0-3 硬编码 — /proc/cpuinfo 对 shell 过滤为 1 核,使用 Cpus_allowed_list)+ 压力子进程 固定到 cpu0 + join 后 16-alloc 保留批次。
  • query(jit_va) 陷阱:回收后,spray region 在 自定义 zone 中重用已释放的 VA → query=0 有歧义(live-original vs spray-covering-VA)

ORACLE 命中 — 机器验证的重定向

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 重定向,带受控对象类型 + 内容。

Stage 3 计划(原始字节 unlink)

Region 类型回收提供合法舞蹈存活,但 jit_node 被 INIT 为自引用 → 无 unlink 原语。需要在 +0x38/+0x3c 处有原始字节:

  1. xattr stamping:交替 region-alloc 突发(净量 → slab 轮换)与 xattr 风暴(stamp 每个头槽位,字节在 free 后 持续)→ 安静窗口 → 解引用
  2. 或固定 sendmsg cmsg(optmem_max=10240 → 约 106 × 96B 持有)
  3. 然后:W1 *(N+4)=P,P=用户态 shellcode 页(无 PAN!)— 候选:const fops 是 .rodata(DEBUG_RODATA)→ 目标 .data 中的非 const 函数指针,或 binfmt formats 列表头,或 sysctl proc_handler (验证表可写性)。回退:通过字节链式 写入 modprobe_path(值必须是可写地址 — 使用指针形状的目标)
  4. 无 KASLR + 精确 vmlinux:prepare_kernel_cred 0xc0149e3c, commit_creds 0xc014993c

SESSION 5B — STAGE 3:武器已构建,回收竞争尚未赢得

已完成

  • Stage-3 武器完成并布置(poc/stage3.c):
    • 目标:kern_table[pid_max].proc_handler @ 0xc1113f40(可写 .data,通过字符串指针扫描 + handler == proc_dointvec_minmax 验证)
    • N = shellcode 入口 0x11111112(mmap 0x11111000;W1 覆盖 entry+4, 被 b +8 跳过;W2 在 P = handler 字段写入 N)
    • arm32 ring0 shellcode 手工编码:prepare_kernel_cred(0) + commit_creds + ret 0;触发 = read /proc/sys/kernel/pid_max (可从 shell 读取);在自己的任务上下文中运行 → creds 应用于我们
    • 良性 oracle 模式写入 uts nodename(0xc110d561,非对齐 ok)
  • Spray 原语选择:
    • NETLINK_USERSOCK sendmsg 固定(msg_control kmalloc-96 拷贝,阻塞时 持有,从不解析):SELinux 拒绝(socket create EACCES)
    • unix/UDP sendmsg:cmsg 解析污染 payload 字节 ✗
    • inotify 事件: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 相同语义)
  • 机械链端到端验证(drain4,无驱逐运行): 20K 排空事件 + 13.5K 多 cpu 尾随 rename + JIT_FREE + oracle + pause,全部干净。O_SYNC 日志(/data/local/tmp/s3.log)在 panic 后存活 — 精确崩溃点取证。

对已释放槽位的回收尝试(目前全部错过)

变体结果
风暴期间并发 rename sprayerrename 停滞(journal/GFP_NOFS)→ 总共 128 → 垃圾解引用
预排空 12K 事件 + 压力 + 小尾随在解引用处崩溃
+ 驱逐时 kill 子进程(10ms 轮询)

工作假设:风暴垃圾竞争 — 在 destroy worker 释放槽位(风暴中途)与 child-kill/安静之间,残余回收 活动用非 payload 字节占据了受害者 slab 的唯一空闲 槽位。Region spray(stage 2)获胜是因为它在风暴期间 持续分配;rename 不能。

遇到的坑

  • spray 切换 bug:rename 源必须是 payload 名称(之前是临时 名称 → 2 波后 ENOENT → 只产生了 512 个事件)
  • 设备主机名是 "localhost"/变化 — oracle 比较前后
  • 暂停的 st3 + pkill → 设备 WEDGE(33K 事件拆除?!)— 只能通过 重启杀死暂停的进程;本会话第二次硬 wedge
  • /proc/cpuinfo 对 shell 显示 1 个 cpu;使用 Cpus_allowed_list

下一步(排序)

  1. drain4 @ 500MB(驱逐阈值已在那里确认),1ms 轮询,即时 kill,4-cpu 尾随 × 3200 — 缩小风暴窗口
  2. xattr-stamp 搅动(setxattr alloc-copy 发生在 SELinux 检查之前 — SELinux 免疫)与风暴 + region 轮换并发,以 stamp 结束
  3. 接受 region 回收(已证明)+ 在 region 受害者状态上寻找第二阶段 原语(双重 jit_free 分析目前为负)

SESSION 5C — 阻塞,精确刻画

本会话的经验结果

  • drain4@500(1ms 轮询,即时 kill,+4.5K rename):仍在解引用处崩溃
  • 尾随与 kill 重叠(drain5):更早崩溃(kill 恢复风暴期间的 rename 命中系统级故障)— 重叠放弃
  • 隔离实验(iso 模式):相同时序,尾随用 commit-0 REGION + stage-2 池重用 oracle → ORACLE 命中 → 时序/可达性没问题;事件是问题所在
  • mask 循环事件(MOVED_TO/CREATE/DELETE 轮换以击败 inotify_merge):仍在解引用处崩溃
  • CONFIG_MEMCG=n → 事件和 region 共享一个 kmalloc-96(memcg 理论 死亡);inotify_merge 也比较名称(merge 理论死亡 — 我们的 切换名称从未合并;事件一直排队并持有)
  • /proc/slabinfo 不存在;/proc/self/pagemap 可读但 PFN 清零 (4.0 后掩码,无 CAP_SYS_ADMIN)

实际阻塞(两部分,均已证明)

  1. 软作业完成在 kbase 作业调度器 WORKER 上下文中运行 (jd_run_atom ← js dispatch,mali_kbase_jd.c:81-112/677),不是内联在 submit ioctl 中 → current->mm 是内核线程的 → 优雅的 "将伪造对象的指针指向我们自己的用户态 mmap" 设计(无 PAN!) 非确定性地 FAULTS。5/5 崩溃,其他方面完美的伪造对象。
  2. 因此 gpu_alloc 必须指向具有可存活 运行时链的内核内存: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 分钟周期)。

Session-6 计划(均已完全指定)

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 被掩码)— 不要重访。

SESSION 5D — physmap 伪造已构建;G 扫描 0/3;混杂因素已消除

本会话确立(全部二进制/设备验证)

  • 缓存身份确认相同:region = kmem_cache_alloc_trace( kmalloc_caches[7], GFP|0x8000, 0x48) [kbase_alloc_free_region 反汇编]; event = __kmalloc(89, GFP) → kmalloc-96。事件和 region 可以共享 受害者的缓存。(MEMCG 关闭;单一缓存集。)
  • inotify 队列:≥5000 事件持有,无溢出,无合并崩溃 (qmeas 1000 & 5000 运行)— 事件 spray 持续
  • iso2(physmap spray + region 尾随 + oracle):命中 — physmap spray 不破坏 region 回收;机制健全
  • 事件 vs region 回收:region 3/3(iso, iso2, stage-2),事件 0/10 但 3 次 pmap 运行可由 G 错过解释,每次几率 0.3-0.45 (P(3 次错过|事件有效) ≈ 0.2-0.3 — 非决定性)
  • mix 模式混杂:480MB spray 使 kill 后 rename 窒息(+0); 350MB OK(+4452);自旋失败 rename 线程也干扰 region 回收(mix 崩溃,iso2 干净)
  • query_commit 在 EINVAL 时也返回 -1 — 已插桩;观察到 EINVAL = rbtree 查找未命中 = 真正释放 ✓(非假阳性)

当前 pmap 设计(在 stage3.c 中,模式 pmap/mix/iso2)

  • G 烘焙到每个 sprayed 页(偏移 0x2a4):nents@+0x2ac=0, evict self@+0x2bc/0x2c0,K=G+0x100@+0x2dc,D=G+0x200@+0x3a8; K+0x141c8/-0x1429c 落在约 20 页之上(sub-0 存储无害 / 读取必须 为 0 或有效)。PM_SPRAY_MB 350,G 扫描已尝试:c2a412a4, c2f4b2a4, c2a7d2a4 — 全部在解引用处崩溃
  • physmap 范围合理性:RAM 1GB → physmap 约 0xc0000000-0xc3fffffff; MTK carveout(GPU/M4U/secure)可能占据块 — G 地雷

Session-6 TODO(排序)

  1. 提取完整内核源码(2.2GB tarball 在 ~/Desktop/amazon-mustang/ — platform.tar):获取 arch/arm + mm/ + drivers/of + MTK 保留映射 → 计算 carveout 映射 → 将 G 定位到已验证 RAM 的 physmap 子范围;同时验证 kmalloc 缓存几何(ARCH_KMALLOC_MINALIGN!)和 0x8000 GFP 位
  2. 用位置信息指导的猜测进行 G 扫描(多次重启,变化 spray 大小以去相关)
  3. 如果 G 扫描穷尽:重新考虑多 G payload 或来自 运行时合理静态的 S 候选(uts 相邻指针字段失败:NULL-K)

SESSION 6 — zram 发现、源码提取、事件问题仍未解决### 现已提取完整内核源码

/tmp/opencode/ksrc2/kernel/mediatek/mt8163/4.9/(arch/arm 包括 mustang.dtsi、mm/、fs/eventpoll.c、fs/notify)来自 ksrc/platform.tar。 发现:

  • 0x8000 GFP 位 = ___GFP_ZERO(只是 kzalloc;无缓存拆分)
  • kmalloc-96 是真正的 96 字节缓存(kmalloc 缓存上没有 HWCACHE_ALIGN)
  • mustang.dtsi:内存节点 0x40000000/512MB — 由 preloader 扩展 (设备显示 MemTotal 977MB);CONFIG_VMSPLIT_3G,HIGHMEM=y
  • zram0 活跃(SwapCached > 0) → “dirty anon = unevictable” 是 错误的:physmap spray 在压力下被换出 → G 别名失效 → 在 kill 后添加了 physmap_retouch()(在 trailing/deref 之前 将所有 spray 页面重新缺页调入)

本次会话运行(全部 O_SYNC 记录,约 6 次重启)

关于 events 的裁决(贝叶斯,诚实)

Regions reclaim:3/3。Events:0/~12 次尝试,包括 28K 次独占 分配,具有先发优势和构造正确的 fake。如果 events 以 p_hit(G)≈0.35 进行 reclaim,五次 pmap 未命中 ≈ 11.6% — 可能但现在 不太可能(约 10-15%)。要么 events 在结构上无法占据此槽位 (原因未知 — 相同缓存、相同上下文、相同时机),要么我们的 G 猜测 系统性地未命中(highmem 边界偏移、分配器放置)。

Session-7 决策树

  1. 先确定 G(廉价,无 exploit):临时插桩 ISO2 流程 — region trailing + oracle — 但将 PAYLOAD event 设为 physmap fake,并检查在扫描范围内是否有任何 G 产生 nodename 变化且没有 regions 竞争(纯 pmap,G 扫描 ~0xc1500000-0xc2a00000 lowmem 中心,每次重启 1 个 G,4-5 次重启)
  2. 如果 G 扫描穷尽 → events 宣告死亡 → 寻找从 shell 可达的 替代原始字节 kmalloc-96 分配器(审计:seq_file、 tty ldisc、fdtable、sk_filter(被阻止:code-field 在 +0x38)、 netlink nlmsg(skb ✗)、keys(被拒绝))— 或重新审视两阶段 region 原语(目前分析为负面)
  3. 考虑稍后通过 root 解锁 UART/ramoops;不要追逐

SESSION 7 — cmdline 重磅消息;event 之谜现在被精确界定

mustang_defconfig CONFIG_CMDLINE(放置的 ground truth):

vmalloc=496M slub_max_order=0 slub_debug=O loglevel=8 initcall_debug

  1. vmalloc=496M → physmap = 0xc0008000..~0xc2080000 仅此(RAM 的低 520MB) — 所有先前的 G 猜测(0xc2a4xxxx+)都在 VMALLOC 空间中。 来自 sessions 5D/6 的每个“G-miss”结论都无效;崩溃 解释仍然成立,但扫描瞄准了错误的内存映射。
  2. slub_max_order=0:所有 slab 页面 order-0
  3. KMALLOC_MIN_SIZE = ARCH_KMALLOC_MINALIGN = 64(L1_CACHE_SHIFT 6)→ kmalloc_index() 对 96/192 的特殊处理被禁用 → region kzalloc(0x48=72) → caches[7];event __kmalloc(89) → caches[7] (disasm + include/linux/slab.h:287 已验证)— 两者都在合并的 128 字节 “kmalloc-128/96” 缓存中。缓存身份:再次确认相等。
  4. zram 活跃 → 用 kbm_spray 替换 anon physmap spray:160 × 2MB kbase MEM_ALLOC regions(GFP_KERNEL → ZONE_NORMAL → 仅 lowmem, 固定 → 免疫 zram),模式通过 CPU mmap 写入 — 正确范围, ~65% lowmem 覆盖率

运行(O_SYNC 记录)

  • kbm + G=0xc16412a4:在 deref 处崩溃(+4831 次重命名)
  • kbm + G=0xc1c4b2a4 @200MB:无驱逐(query=16 — 压力 校准随固定 spray 变化;合法空闲,存活)
  • kbm + G=0xc1c4b2a4 @400MB:驱逐 ✓,+4053 次重命名,在 deref 处崩溃
  • 有效范围 G 记录:0/2。如果 events 在 ~65% 覆盖率下工作: P(2 次未命中) ≈ 12%。问题仍然开放但比以往更窄。

问题,最终形式

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 下的排序细节有利于……不清楚。

Session-8 TODO

  1. 插桩级实验:两个 event payload 变体,具有 不同的 N/P 值交替(两个名称集)— 如果 nodename 曾经改变,最后一个赢家被识别;用 kbm spray 扫描 G 在 [0xc1200000..0xc2000000],3-4 次重启预算
  2. 如果仍然 0/N:放弃 events。替代方案排名: a. 通过 F_SETPIPE_SZ 的 pipe_buf 数组(4096 → 1 buf?不 — 16 bufs = kcalloc(16, 28)=448→512 ✗) — 死路 b. 审计 fs/notify + fs 以寻找其他携带名称/数据的 kmalloc-128 对象(fanotify events?mq 关闭;fanotify 需要 groups...) c. seq_file 缓冲区(kmalloc(PAGE_SIZE) ✗) d. sock filters(code-field 在 +0x38 冲突 ✗) e. 接受 region 类型 reclaim + 链接第二个 bug/技术
  3. 重新审视为什么 regions 获胜 — 也许通过多个 jit ids 插桩: N 个悬空槽位,每个槽位的 region-vs-event 竞争,oracle 检测哪个 spray 占据了哪个槽位 → 机制的统计指纹

SESSION 8 — 在设备上实现任意写;dispatch 之谜遗留

里程碑```

[+] uname.nodename="X?lhost" (was "localhost") <<< ORACLE HIT

root@kitploit:~
**完整的原始字节链在真实设备上生效**:事件回收已释放的区域槽位 → 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 是否运行的标记实验;仅在会话结束前发生崩溃启动——尚无干净数据。)

命中率 / 启动状态经验(来之不易)

  • 可用配置(请勿改动):2 个子进程 × 100MB 阶梯压力,尾部 100×50ms/+3200 次重命名,260×2MB kbm 喷射,G=0xc154b2a4,预排空约 5000 次重命名
  • 过度压力(20 个子进程 / 10 秒尾部)会破坏回收——已回退
  • 启动稳定期很重要:在 boot_completed 后立即启动的运行会与系统启动分配竞争 → 冷启动连续失败;启动后等待 60-90 秒再运行
  • “W1 未触发”(nodename 检查)对 nf payload 毫无意义——W1 写入我们的页面;应改用 kbm_scan_for
  • 在 free 时崩溃的启动 ≈ 垃圾槽位或 G 未命中转换;良性对照(pmap 200)是环境健全性检查(热态时 4/4 命中;冷态时崩溃未命中)
  • /data/local/tmp/.w* 目录在运行开始时会被清理(累积的目录会降低回收质量)

会话 10 待办事项(决策树,按顺序)

  1. 在延迟稳定的启动上运行 nf 200,直到出现 W1-CONFIRMED 行,然后读取 MARKER 行: a. 标记存在,uid!=0 → shellcode 的凭据失败(检查 prepare_kernel_cred/commit_creds 地址;blx 编码) b. 标记不存在 → 遍历从未调用我们:用第二个标记验证,该标记由……下一步诊断:仅写入标记并返回 1 的 hookfn(无凭据)——如果仍然不存在:
    • 在触发前通过 CPU 映射转储我们实际的入口字节(它们是我们可读的!)
    • 检查 [init_net+0x58c] 是否真的被查询:改为写入以破坏可观察的内容(例如将其指向一个 cell,其 *cell = 入口,fn = 内核函数如 kfree → 触发时立即崩溃 = 字段确实被查询)
    • 重新验证 0x58c 偏移:也许 hooks_ipv4[NF_INET_LOCAL_OUT] 位于不同的索引(NF_INET_POST_ROUTING=4?)
  2. 如果遍历调用了我们:修复凭据 → root → 然后用户的计划: setenforce 0; cp /system/bin/sh /data/local/tmp/su; chown root; chmod 6755; 验证 ls -la; 保留标记文件
  3. 持久化(root 后):通过 /dev/block/by-name/boot 修补启动镜像
    • 禁用 dm-verity,或 Magisk 风格;仅 /data 上的 su 在重启后是 shell 域中的 uid0(SELinux 再次强制)——setenforce 0 仅限运行时
  4. 清理注意事项:暂停的 st3 进程具有损坏的 kctx 状态——只能通过重启终止;nf 劫持会破坏所有流量的 LOCAL_OUT 钩子——root 后重启以恢复

今晚新增资产

  • poc/stage3.c 模式:pin/root/drain{,2,3,4,5}/iso——完整武器 + oracle + 隔离测试框架,O_SYNC 崩溃点取证
  • 用户态伪造基础设施(ufake_prep)——保留,但仅在找到上下文内联解引用路径时可用
  • tools/:kdis/scan_s/resolve/dumpb/findsysctl 离线 vmlinux 分析

会话 10 — 已获得 ROOT(2026-09-11)

阻塞 nf 武器的三个 bug,全部已修复

  1. 错误的 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。
  2. 双重解引用 bug: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)。

SELinux 之墙与双包绕过

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)}。
  • 数据包 1:*(enforcing)=0 → SELinux 宽容模式。
  • 通过 kbm_cpu[reg]+off 将入口重写为 {fn=commit_creds, priv=&init_cred}。
  • 数据包 2:在发送者的任务中执行 commit_creds(&init_cred) → uid 0,且 SELinux 为宽容模式 → 可用的 root,全部在一次回收中完成,无需链式操作。

已在设备上验证(2026-09-11)```

[+] 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

root@kitploit:~
- `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

会话 12 — LK 回复:eng/unlocked 门控是真实存在的,且不存在未签名的标志存储

目标:让 LK 将设备视为 eng/unlocked,或找到一个 preloader/LK 漏洞, 以便持久性地禁用 verity/SELinux。结果:端到端地逆向了相关的 LK 代码路径;该翻转无法通过可用的存储达成。 没有设备变砖;唯一一次 boot1 实验已恢复至原始状态。

LK 是 Thumb-2 PIC,重定位至基址 0xFF400000

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 即为运行时地址。

解码后的控制流(偏移 -> 含义)

解锁码由 Amazon-RSA 签名 — 不可伪造

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 中静态分析未发现任何可利用之处(边界/大小)。 =>

verity/SELinux 标志并非来自一个存在的存储

安全标志通过 0x57c 处的 getter 读取。实证测试:```

boot1 IDME item fos_flags data (offset 0x22B4, 8 bytes) set to "00000080"

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)

root@kitploit:~
`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 的安全门消费。

结论——为什么持久化 eng/unlock 被阻止

  1. unlocked_kernel 需要 Amazon 签名的 unlock_code(RSA/libtomcrypt,内嵌 CA)。无法离线伪造;未发现验证器漏洞。硬阻止。
  2. DM_VERITY_OFF / selinux=permissive 标志从 LK 环境(para/ENV_v1)消费,而该环境在此 GPT 上不存在。IDME fos_flags 经验上被 LK 忽略(0x80 已持久化,verity 仍为 eio)。硬阻止,除非修改分区表。
  3. 即使成功设置 fos_flags=0x80,也只会设置 androidboot.veritymode=disabled 和一个非 dm-0 的 root=;它不会解锁,且 SELinux 仍需要来自同一缺失环境的 dev_flags 才能转为 permissive。
  4. 因此 Track A(启动时重新利用)仍与 SESSION 11 中完全一样被阻止:其唯一的解锁路径是同一个 LK 门。

剩余途径(未来,风险更高;未尝试)

  • 合成 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)。
  • Preloader(boot0/EMMC_BOOT)漏洞:本次会话未逆向。在存在原始副本和恢复路径之前,禁止写入 boot0。
  • 验证器研究:工程证书路径(0x316a0/0x31703)只有在设备身份被接受为“engineering”且代码由工程密钥签名时才可达;没有可用的私钥。

产物 / 可复现性

  • 新增工具:tools/lk_xref.py —— 与基址无关的 LK 字符串交叉引用解析器。
  • 使用的转储:/tmp/opencode/mustang-dumps/lk.img、boot1.img(原始)、boot0.img、mbr.img(GPT)、pmt.img(全零)。
  • boot1 实验镜像(fos_flags=0x80)保留在 /tmp/opencode/s12/boot1_f80.img;设备已恢复到原始 boot1(已验证 /proc/idme/fos_flags -> 0)。

便捷命令(需要 root;用 ./run.sh 重新武装)```

re-arm runtime root (~1/3 per boot)

./run.sh --no-build

confirm LK's decisions without a UART

/data/metrics/su /system/bin/sh -p -c 'cat /proc/cmdline'

watch: root=/dev/dm-0 dm="system ... android-verity ..." (verity on)

androidboot.veritymode=eio ; androidboot.selinux=enforce ; prod=1

IDME read (Android copy; NOT what LK's gates use)

for f in fos_flags dev_flags usr_flags unlock_version serial; do cat /proc/idme/$f; echo; done

root@kitploit:~
## 会话 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

root@kitploit:~
`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 寄存器映射确认之前不应使用)。

状态 / 后续步骤

  • 未确认:mustang 的 preloader 是否实际实现了 0xD1/0xD4 (由握手探测决定)。如果实现了,aftv2 的 eMMC 读写 路径很可能可以直接移植。
  • 接下来:找到 MT8163 MSDC 基址(内核 DT 或 preloader),转储一个分区 (read_mmc),然后从 preloader 修补 boot.img/lk 并重启。
  • 这是比 SESSION 12 中所有内容都更底层的引导阶段,因此它不 依赖于 amzn_verify_unlock 或 LK 环境变量门控。
  • 在协议和 eMMC 布局确认之前,不要对其运行 SP Flash Tool / mtkclient 写入操作。
下载工具
  • 代理 waiter 位于 waiter 线程自己的栈上(futex.c:1975 传入 this->rt_waiter, 在 futex.c:2880 的 futex_wait_requeue_pi 中声明)→ waiter 通过 arm32 select(nr 142)fd_sets 在其自身已释放的栈帧上打标记
  • 利用环境:无 KASLR(固定基址 0xc0008000),无 PAN,DEBUG_RT_MUTEXES 关闭 → 紧凑的 48 字节 rt_mutex_waiter(tree_entry@0, pi_tree_entry@0xc, task@0x18, lock@0x1c, prio@0x20, deadline@0x28)
  • 最小链:2 个写槽 → 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=0
  • [_] Stage 4:root 脚本(su,permissive,OTA 关闭)+ 持久化——已获得 root; 持久化受阻(见 SESSION 11):LK 将 verity-off/SELinux-permissive 限制在 eng/unlocked,启动时重新利用没有可行的执行器。下一步:逆向 LK/amzn_verify_unlock。
  • Stage 5:自定义 OS 启动链
  • nr_extres
  • JIT 分配结果由内核通过 info->gpu_alloc_addr 写入(你必须 预先分配并传入的 GPU VA)
  • 可驱逐对象压力结果
    无900 MB存活
    无1300 MBpanic(系统 lowmem bug——无关)
    普通区域 + DONT_NEED700 MB存活
    JIT 区域 + DONT_NEED700 MB在 reclaim 路径中 panic
    /proc/config.gz
  • ksrc/ — Amazon OSS 源码(platform.tar + 提取的 midgard-r26p0 树)
  • OTA:/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
    cell 地址
    PM_PAGE+NF_CELL_OFF
    probe
  • Physmap 直接映射在 kernel_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 中的代码。
  • 字面量(文件偏移)值含义
    0x2700xFF4002F8str r4,[r6] 暂存区
    0x2740xFF40027C目标地址(基址+0x27C)
    0x2780xFF54A440复制结束(含 BSS)
    0x27C0xFF400484入口点
    偏移函数
    0xdf7cis_secure_or_prod() -> byte[ [[g]+0 ] + 0x163 ];g = 全局变量 @0x52838。本机上为 1。
    0x20b4verify_stored_unlock() = memset(buf,0,0x100);通过 0x57c 读取 IDME/env unlock_code(0x100);bl 0x222c;返回 (verify==0)。
    0x222c / 0x20f0amzn_verify_unlock(code,len) — libtomcrypt RSA/PKCS#1 验证(见下文)。
    0xda3eis_unlocked() = is_secure_or_prod() && verify_stored_unlock()。
    0x29a28is_verity_disabled() = (fos_flags & 0x80) && !(is_secure_or_prod() && verify_stored_unlock());缓存在全局变量 @0x50c74 中。
    0x29974SELinux 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=)。
    0x27af8UART 门控:fos_flags & 0x4 -> printk.disable_uart=0,否则 =1。
    0x12fd4LK 环境变量加载器:分区 "para",0x4000 字节,魔数 ENV_v1,校验和 = 0x3ffc 范围内字节之和与 @0x3ffc 处的字比较。
    0x1efd0按名称查找分区(用于 "para"、"boot" 等)。
    0x57c通过回调槽 @0x58218 的 getter 分发器;槽 @0x58200..0x5821c 从 0x5a8-0x734 处的表注册。
    0x2a19cfastboot oem unlock:bl 0x222c(code,len);成功后通过 0x408 写入 unlock_code(0x100)。
    unlocked_kernel=false
    /proc/cmdline
    androidboot.unlocked_kernel
    androidboot.prod
    unlock_code
    通过文档化路径进行 eng/unlocked 翻转在密码学上是不可行的。