
Kernel LPE exploit for CVE-2023-6931 on Xiaomi Pad 6 (pipa), chaining a perf read_size overflow to root and disabling SELinux via data-only netlink_sock sk_destruct corruption.
最终在设备上使用的那一版 exploit,单独整理在此目录。
设备 Xiaomi Pad 6 / pipa / SM8250
系统 HyperOS OS2.0.20.0.UMZCNXM
内核 4.19.157-perf-g240e1d0a5f52
起点 adb shell(shell 域)
结果 一次性 uid0 + 全局 SELinux permissive(重启失效)
见 exploit.c 文件头,摘要如下。
1) perf group read_size overflow -> 越界 u64 自增原语
2) 自增 victim netlink_sock->sk_destruct -> netlink_sock_destruct_work(+0x28e8)
3) close(victim) -> sk_destruct(work) -> container_of(work, netlink_sock, 0x440)
-> fake = victim-0x440 -> sk_free(fake) -> __sk_destruct(&fake->sk_rcu)
-> fake->sk_destruct @ fake+704 = pivot(mov sp, x19)
4) 栈迁移到 fake 内的 skb 载荷(pt_regs1 @ +960,pt_regs2 @ +1120)
5) restore_tramp -> eret EL1h -> commit_creds(&init_cred)
6) restore_no_kpti -> eret EL0 -> post_root
关 SELinux 走纯数据改写:fake+0x248 指向 selinux_state,__sk_destruct 的 atomic_dec 把该字最低位段里的 enforcing 从 1 减成 0。
./build.sh # 输出到本目录 exploit
CC=aarch64-linux-gnu-gcc ./build.sh /tmp/exploit
驱动脚本 .project/scripts/drive_selinux_off.sh 使用的命令行:
adb push exploit_selinux8 /data/local/tmp/exploit_selinux8
adb shell 'chmod 755 /data/local/tmp/exploit_selinux8'
adb shell 'cd /data/local/tmp && setsid nohup /data/local/tmp/exploit_selinux8 \
--selinux-off --layout-interleave --tsec-sid 1 --alarm-us 5000000 \
> /data/local/tmp/run.out 2>&1 < /dev/null &'
参数含义:
--selinux-off 关 SELinux(纯数据改写路径),同时带 cred 伪造
--layout-interleave 分配序列交错排布,让 OOB 自增落在目标对象的 sk_destruct 上
--tsec-sid 1 伪造 cred 的安全上下文 sid/osid(取不到真实域 sid 时用 1)
--alarm-us 5000000 自唤醒 ITIMER_REAL,卡住的尝试 5 秒后退出
运行前提与限制:
shell 域可达 perf_event 与 netlink_route,应用域不行,所以必须从 adb shell 起;perf_event_mlock_kb = 516,同一个 boot 里不要重复运行,上次实例不退出就不会释放事件,重跑只会建到 4000 到 6000 个;kill -9 清掉旧实例再重来,否则按每次开机一次有效尝试计;pause(),root 与 permissive 仅对本次启动有效。--edl、--edl2),用于从 root 窗口确定性进入 EDL,本目录只收录最终使用的那一版。这个目录只放第一阶段。整条链实际上是两段,衔接点是第一阶段留下的 root 命令通道。
第一阶段 exploit_selinux8 内核提权 + 关 SELinux,然后停在 root 命令通道
产物:uid0 + permissive,通道 = FIFO /data/local/tmp/root_in|root_out
(另有抽象 unix socket rootshell / rootshell2,以及 --root-client PORT 模式)
第二阶段 stage2-frida/ 通过上面那条通道以 root 起 frida-server,注入 Settings
产物:应用进程内构造并发出的绑定请求(TEE 签名)
第一阶段结束时会把 ROOTSHELL_OK 写进 /data/local/tmp/root_proof.txt 作为可执行命令的凭据。之后宿主机的任何 root 动作都是往 FIFO 里写一行命令,例如第二阶段启动 frida-server 的那一条:
adb shell "echo 'killall -9 frida-server 2>/dev/null; rm -f /data/local/tmp/frida.log; \
setsid runcon u:r:shell:s0 /data/local/tmp/frida-server16 -D \
</dev/null >/data/local/tmp/frida.log 2>&1 & echo FRIDA_STARTED' > /data/local/tmp/root_in"
runcon u:r:shell:s0 不能省:SELinux 关掉之后 frida-server 若跑在 kernel 域,拿不到 Android Context,PackageManager 会 NPE。
外部依赖(未随本目录附带):frida-server 16.7.19(54 MB,放在 .project/tools/ext/frida/fs16,sha256 前缀 4eebf1fbc66ff54aba9a9124)与对应 python 客户端(.project/tools/ext/fridavenv16,frida 16.7.19)。用 16.x 而不是 17.x,是因为 17 的 agent 里没有 Java bridge。
完整调用链由 .project/scripts/drive_selinux_off.sh 编排:命中 root 窗口后第一个动作就是 bash .project/scripts/exp_forge_send.sh,拿到 code 0 即停止循环。
exploit_selinux8 是交付原件,校验和见 SHA256SUMS。用本目录源码重建得到的是另一个二进制:
本目录源码重建 1,853,744 B f8bb900bf4e197190d56bfc1437bcc9a025e49640a7fa2a78ff0f7fb0a6e7939
原始源码重建 1,853,752 B 96c7c3637682a2d5869ab4b6a732688a252f17f5e604b1695bc8fcc022113b3f
实际运行的那份 1,853,720 B 279837d2ee028fbbb520120180156446ad8dc3286b723e28de300b544be2a21a
差异已经逐符号定位,来源只有一处:命令行参数表里多了两个模式。
--edl 帧2 调 __qcom_scm_set_dload_mode(dev, 1)
--edl2 帧2 直接写 dload cookie
也就是说,交付的那份是加 EDL 模式之前编出来的版本,当前源码是加过之后的(.project/work/exploit_edl1、exploit_edl2 由它编成)。EDL 分支只在传 --edl 或 --edl2 时生效,edl_mode 默认 0,payload 构造里唯一相关的一行是 edl_mode ? 1ULL : rt(OFF_INIT_CRED),所以用最终那条 --selinux-off 命令行时两者行为一致。差异属于功能增量,不是修复或行为改变,二进制按原件附带,源码保留完整版。
用 .project/tools/strip_dev_comments.py 处理,删除 124 个开发过程注释块(版本迭代标记如 v96、v127,以及中文调试口吻如"旧值/实测/重跑/跳过"),保留 313 处结构性注释。预处理结果对比只差空行,代码 token 完全一致。运行日志里仍带有 vNNN: 前缀的打印文本,因为它们属于程序输出而非注释。
idk fr what files are really needed, if you want to you might very need a LLM to understand the whole exp and write yours.
| 文件 | 说明 |
|---|
exploit.c | 源码。已去掉开发过程注释,保留文件头链说明、偏移表、结构体布局等结构性注释 |
text_syms.h | 从设备 kallsyms 转储生成的 .text 符号表,编译必需 |
ro_syms.h | 同上,只读数据符号表,编译必需 |
exploit_selinux8 | 实际在设备上运行的二进制,与驱动脚本使用的那份一致 |
build.sh | 构建脚本 |
SHA256SUMS | 上述文件的校验和 |
| 文件 | 说明 |
|---|
exp_forge_send.sh | 推送并启动 frida-server 16.7.19、确保 Settings 在运行、取 pid、跑 agent |
frida_run.py | frida 客户端封装,支持 FRIDA_PID 直连与 FRIDA_SPAWN 自启(绕开 am/pm) |
forge_send.js | 在 Settings 进程内复制 getNonce、按旧字段集合签 applyBind 并发出 |
| 比对项 | 结果 |
|---|
| 函数大小不同的符号 | 只有 main(shipped 17,744 B,重建 18,004 B,差 260 B ≈ 65 条指令) |
| 其余 1,503 个函数 | 大小完全一致,无增减 |
| 只在重建里出现的符号 | edl_mode 一个 |
| 命令行参数表 | shipped 85 个,重建 87 个,多出的正是 --edl 与 --edl2 |
整段 .text 指令数 | 126,533 与 126,597,差 64 条,与 main 的差别相符 |