代码仓库 :<450K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6^5K9h3q4G2K9$3q4F1k6$3S2#2j5W2)9J5c8Y4m8@1k6h3S2G2L8$3E0W2M7W2)9J5y4X3N6@1i4K6y4n7i4@1g2r3i4@1u0o6i4K6R3^5c8#2m8x3i4K6u0V1x3W2)9J5k6e0m8Q4c8f1k6Q4b7V1y4Q4z5o6V1`.
环境基线:Xiaomi M2102K1AC / Android 13 (API 33) / Kernel 5.4.233 / APatch v0.12.2 (KernelPatch 框架)
代码共 ~1800 行 KPM + ~3000 行 Python host。文中代码片段可直接复用。
起点是看雪 thread-290718 第 7.2 节提出的"方案 C"骨架:
查名片,找老巢:读取目标 Java 方法 ArtMethod 结构体中的 entry_point_from_quick_compiled_code_ 指针。
布设隐形陷阱:不对这个指针做任何修改。直接拿着真实的指令地址,呼叫内核 KPM 在这里拉起 UXN 高压电网。
这句话给出了方向但没落地。本文记录把它做成生产级 框架(能在 aweme 这种 7000+ VMA、高密度的商用 APP 上稳定跑)的全过程,重点是可复现的工程细节 :
读完应该能复现整个框架或独立实现同样的机制。
目标效果的一组数据:
以 stealth 为唯一设计目标的 ARM64 Android hook 框架,三条硬红线:
ARM64 的页表项(PTE)是 64 位,相关的几个位:
UXN = 1 时,EL0(用户态)从该页取指令 → Instruction Abort → 进入内核 do_mem_abort。数据读写不受影响 。
关键:只翻这 1 个 bit,物理页帧、其他权限、属性都不变 。对反作弊读页内容完全透明 —— 他读的还是原字节。
这是 7.2 方案的物理基础。
APatch 把 Linus 的 kernel 补过一个 patch 后,提供自定义内核模块 (KPM) 运行环境:
我们的 KPM 主入口如下:
用户态工具 ptehook_ctl 负责:
Shellcode 和 DBI 重编译代码需要放在目标进程可执行 的内存里。普通 mmap 出来的内存:
要规避这些,必须绕过 VMA 子系统,直接在进程页表里插 PTE 。
pte_template 参数很关键 —— 是从邻居 VMA 抓来的 PTE 作为模板:
为什么这样做?因为 PTE 里的很多属性(MAIR index、NS、SH 等)来自 CPU 内存模型,直接瞎填会得到 uncached 或错误 shareability 的页。从邻居继承 保证我们的 ghost 页和 libart r-xp 在 cache/coherence 行为上一致。
早期版本有个实际上机才发现的 bug:
在 aweme 这种密集进程里,num_pages=8(DBI 最坏展开)时,单页空洞不够。安装第 2 页 PTE 时撞到相邻 VMA → -EEXIST → 回滚 → -ENOSPC。
修复:
do_mem_abort 是 ARM64 内核处理用户态 fault 的入口。我们用 KernelPatch 的 hook_wrap3 把它 wrap 住:
UXN 是按页 生效的。目标方法 entry_point 在某个 4KB 页上,但整页的所有函数 调用都会触发 fault。比如 art_quick_to_interpreter_bridge 和 art_quick_imt_conflict_trampoline 可能在同一页。不 hook 这些 helper 的时候,他们的调用也会被我们的 UXN 挡住,必须能正确 fallthrough 到原逻辑。
DBI 的任务:把整页代码重编译一份到 ghost,所有 PC-relative 指令修复成能从 ghost 执行 。
ADRP 是 PC-relative 的,在 ghost 位置执行时计算结果会错。解法是换成绝对地址加载:
B.cond 的 imm19 只够 ±1MB 范围。ADRP 修复过的代码在 ghost 里比原地址偏移大,原 B.cond 可能超范围。同时如果目标在同一 hooked 页 ,最好跳到 ghost 里对应位置(而不是原 VA → UXN 反弹)—— 这就是我们最重要的 bugfix(见第 7 节):
整个项目最深的一个坑。第一次崩的时候是 hook aweme 的 MSManager.frameSign,app 跑 5-20s 必崩:
第一步:反汇编崩点
libart.so 里 artInvokeInterfaceTrampoline+0xF8 反汇编:
X24 本应是个 mirror::Object*,但拿到了 0xaa1103e0 —— 完全不像 ART heap 指针 (ART 用 TBI tag 0xb4 或 low-ram 0x70 开头)。
第二步:向上追 X24
X24 从 [X20] 读出来,X20 源自 X0。
看崩点 X20 = 0x787b81a058 。这是 libart.so 里 cmp x0, x17 这条指令所在的 VA(offset 0x58)。也就是说 X0 入 BL 时是 libart 代码段 VA ,根本不是对象。
第三步:看调用点
bl artInvokeInterfaceTrampoline 在 art_quick_imt_conflict_trampoline 里:
按 ART 约定,X17 在整个 trampoline 函数体内都是 IMT key 。走到 mov x0, x17 时把 key 放 X0,BL 交给 artInvokeInterfaceTrampoline。
第四步:X17 什么时候不对的?
崩溃时 X17 = 0x787bd3336c = artInvokeInterfaceTrampoline 函数入口。这是 BL 展开成 MOVZ/MOVK X17 + BLR X17 之后的值,已经是 BL 本身污染的 。X0 的值在 BL 之前就确定了。
真正要找的是 mov x0, x17 执行那一瞬间 X17 是什么 。
X0 at crash = 0x787b81a058 = libart cmp x0, x17 指令地址。这不是巧合 。X17 在 mov x0, x17 执行时就已经是 0x787b81a058 了。
第五步:dump ghost 字节
重现崩溃,保留 KPM 里的 ghost 物理页,通过 ghost-read 命令读出 8KB:
对照 offset_map 定位 b #-16 这条循环回跳指令在 ghost 里变成了什么:
装上:X17 = 0x73e761a058 (= target page + offset 0x58 = libart 里 cmp x0, x17 的 VA)。
就是这里 !b #-16 回跳到 0x21a058。target VA 和 ghost VA 距离 218MB > B 的 ±128MB 范围,DBI 展开成 MOVZ X17 + BR X17。展开过程把 X17 写成了跳转目标的绝对 VA 。
BR 后 CPU 跳到原 VA 0x21a058,触发 UXN 反弹 → Pass 3 → ghost 对应位置继续跑。但 X17 已经被污染 。后续 cmp x0, x17 / cbz x0 / mov x0, x17 全部用脏值,X0 拿到 0x787b81a058,传给 artInvokeInterfaceTrampoline,崩在 +248。
ARM AAPCS64 规定 X16/X17 是 IPR scratch ,函数调用过程中可以被 linker 存根污染。但 ART trampolines 不遵守 AAPCS :它们用 X17 传 IMT key 贯穿整个函数体。DBI 只看指令语义不看 ART 调用约定,就用 X17 做了远跳 scratch。
彻底方案:intra-page 分支的目标翻译到 ghost VA,绝不用 far-jump 展开 。
实现要点在 6.5 节的 recomp_bcond 里看得见:前向分支因为 offset_map 还没填,先 emit placeholder,加到 pending 队列,主循环结束后回填。
完整修复涉及 4 个 recomp 函数(B, B.cond, CBZ, TBZ)。recomp_b 的片段:
修复前:aweme 5-20s 必崩。
修复后:
267,735 次 Pass 3 命中,aweme 零崩溃 。平均 22k 命中/秒吞吐。
DBI 修好后,又发现一个新问题:jit_watch 动态切换 hook 会导致内核 panic / 设备 reboot 。
jit_watch 是后台 Python 线程,轮询 ArtMethod.entry_point。ART 会在运行中把方法从 Nterp 升级到 JIT,entry_point 随之变化 —— 我们的 hook 钉在旧 ep 就失效。watcher 检测到变化会:
aweme 高 Pass 3 流量下 (22k/s),这个切换导致内核 panic。
uxn_unhook 做这些事:
tlbi vmalle1is 只保证未来 的取指走 PTE(此时 PTE 已恢复原 libart 页)。但CPU 当前正在执行的指令序列是从 I-cache 里来的 :
此时 ghost_free 把物理页还给 buddy allocator 。Allocator 转手给下一个申请者,写入新数据。I-cache miss 时再从物理页取 → 拿到垃圾指令 → 用户态 UDF 或内核 panic 。
原理 :
on_each_cpu 通过 kallsyms_lookup_name("on_each_cpu") 在 KPM 初始化时拿到:
10 轮快速 rehook 压测(每 0.6s 改 ArtMethod+0x18,watcher 每次都检测到并重装):
设备 uptime 10+ 分钟无重启,aweme 存活。修复前同样负载必崩。
DEX 文件里方法用 method_idx 索引。ART 加载 DEX 后,每个方法对应一个 ArtMethod 结构体,在 [anon:dalvik-LinearAlloc] VMA 里。设备侧 C 程序用 process_vm_readv 扫 LinearAlloc,按 pattern 过滤:
aweme 的 LinearAlloc ~100MB,扫描 0.17s。
scanner 多候选时需要消歧。比如 method_idx=33853 在 aweme 里返回 5 个候选 —— 因为有多个 class 碰巧都有第 33853 个方法。消歧两层过滤:
Python 侧用 _ep_offset() / _af_offset() 取代所有硬编码:
device_scanner.c 也加了 CLI flag:
Python 侧根据 get_offsets() 构造这些 flag:
hide-vma 之类依赖 linked-list VMA 的命令在 maple tree 内核下直接拒绝:
Makefile.planc 关键行:
KPM unload 时会触发 cleanup_all_state,自动清所有目标进程的 ghost PTE + 物理页:
启动后输出:
另一个终端:
last_far=0x73e761a050 指向 art_quick_imt_conflict_trampoline 入口 —— 说明 aweme 的 IMT 分发密集命中这一页,DBI fallthrough 正常。
aweme 的坑:MSManager.frameSign 只是 wrapper ,实际签名由 ms.bd.c.f2.frameSign 执行。java_hook_all 装在 MSManager 上大概率 on_call 不触发。
解法 1:ms.bd.c.g2$a 接口有多个实现类,DEX parse 时找出所有实现类,都 hook:
解法 2:native hook libmetasec_ml.so 的 JNI_OnLoad 里 RegisterNatives 表:
Python 封装返回结构化数据:
Python 自动 chunk:
29 个测试覆盖:
ART 13 Nterp 对"未 JIT 的纯 Java 方法"有极深的 fast path,Java-to-Java 调用不读 entry_point 。尝试过:
只能 wait_jit=True 等 ART 自然 JIT(通常 10 次调用左右)。对 trivial 方法(比如 return 0)ART 拒绝 JIT,这种方法目前抓不到。
hide-vma / hide-range 依赖 linked-list VMA,kernel 6.1+ 改成 maple tree 后这两个命令会拒绝执行。核心 hook 路径(UXN / DBI / ghost)不受影响 ,因为 vm_area_struct 的 vm_start/vm_end 偏移一直稳定。
7.2 trap 模式下不支持。需要 LSPlant 风格的 ArtMethod 克隆(在 ART heap 里 alloc 新 ArtMethod、注册到 class_table、WriteBarrier),跨进程实现 ≈ 9 天工作量,ROI 不够。
按优先级:
不做的:
这个项目走下来最大的感受是 —— "方案 C"骨架简单,工程细节全在坑里 。
几个坑的代价:
每一个坑都是 ART / Linux kernel / ARM64 语义交互的细节 。特别想强调 DBI 的 X17 clobber —— 写 DBI 是有 textbook 的(怎么重编译 PC-relative),但没人告诉你 ART trampoline 的寄存器约定不遵守 AAPCS 。X17 在 AAPCS 里可以随便 clobber,但在 ART 的 imt_conflict_trampoline 里它是神圣的 IMT key 载体。这种隐含约定只能靠实战踩出来。
ptehook 不是 Frida 替代品 ,它解决的是 Frida 解决不了的一个特定问题 —— stealth 。如果要做的反作弊对抗需要零 .so 注入 / 零字节修改 / 零 ptrace 痕迹,这个框架的机制就是为你设计的。如果需要 JS 热更 / 完整 Java onEnter+onLeave / spawn 注入,老老实实用 Frida 。
感谢 7.2 节那篇启蒙文章。没有"查名片、布陷阱"这个框架性思路,我不会往这个方向做。
代码仓库:<1e4K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6^5K9h3q4G2K9$3q4F1k6$3S2#2j5W2)9J5c8Y4m8@1k6h3S2G2L8$3E0W2M7W2)9J5y4X3N6@1i4K6y4n7i4@1g2r3i4@1u0o6i4K6R3^5c8#2m8x3i4K6u0V1x3W2)9J5k6e0m8Q4c8f1k6Q4b7V1y4Q4z5o6W2Q4c8e0y4Q4z5o6m8Q4z5o6u0Q4c8e0k6Q4b7f1y4Q4b7e0u0Q4c8e0S2Q4b7V1k6Q4z5p5g2Q4c8e0g2Q4z5f1u0Q4z5f1g2Q4c8e0g2Q4b7U0S2Q4z5e0k6Q4c8e0c8Q4b7V1q4Q4b7e0c8Q4c8e0k6Q4b7U0g2Q4z5o6q4Q4c8f1k6Q4b7V1y4Q4z5p5y4Q4c8e0c8Q4b7U0W2Q4z5f1k6Q4c8e0k6Q4b7f1y4Q4b7e0u0Q4c8e0S2Q4b7V1k6Q4z5p5f1`. issue / PR 修 bug。
指标
数值
aweme Pass 3 fault 处理
267,735 次 / 30 秒 / 0 崩溃
KPM 代码规模
~1800 行 .c
目标进程 /proc/maps 里的痕迹
0 个新 VMA
目标进程 TracerPid
0
ArtMethod 字节修改
0(默认模式)
单元测试
29 个全过
检测手段
我们能否过
扫 /proc/pid/maps
✅ 看不到
扫 TracerPid / ptrace_scope
✅
dl_iterate_phdr 遍历 .so
✅
CRC / MD5 of .text pages
✅ 字节未改
扫 ArtMethod.entry_point 是否落在合法 oat 段
✅ 字段未改
扫 ArtMethod.access_flags ACC_NATIVE bit 变化
✅
/proc/self/pagemap PFN 对比(用户态)
✅ 只翻 PTE 里 1 个 bit,PFN 不变
Kernel 驱动扫 PTE.UXN
❌ 能看到(但反作弊一般没这权限)
时序检测(fault 后 μs 级延迟)
⚠️ 首次取指有延迟,稳态影响小
维度
Frida
LSPlant
wxshadow+rustFrida
ptehook
注入 .so 到目标
✅ libfrida-agent
✅ Zygisk
✅ libagent
❌
ptrace 目标
✅
❌
✅ spawn
❌
改 .text 字节
✅ inline
✅ inline
✅ (藏 shadow 页)
❌
改 ArtMethod 字节
—
✅ entry_point
—
❌ (默认)
/proc/maps 可见
✅
✅
✅
❌
JS 脚本热更
✅ QuickJS
❌
✅ QuickJS
❌
Java onEnter+onLeave
✅
✅
✅
⚠️ Native 支持
改 Java 入参
✅
✅
✅
❌
Nterp-tier 方法
✅
✅
✅
⚠️ 需 wait_jit
Spawn 注入
✅
✅
✅
❌ 只能 attach
pagemap PFN 一致
❌
❌
❌
✅
坑
发现代价
修法
DBI X17 clobber
3 次尝试 + dump ghost 字节
intra-page ghost VA + pending backpatch
Ghost UAF
1 次 kernel reboot + 思考
on_each_cpu + ic ialluis 广播
KPM 卸载不清 PTE
多轮孤儿 PTE 占 gap
cleanup_all_state 兜底
Scanner 早期 cluster 误匹配
多次上机尝试
retry + access_flags 双层过滤
ART Nterp 短路
ART 源码翻查
确认无解,走 wait_jit
想了解
读这个
UXN 陷阱怎么工作
test_kmod/ptehook_planc_v2.c::before_do_mem_abort
Ghost 如何分配
test_kmod/ghost_mm.c::ghost_alloc / find_hole_near
DBI 重编译每类指令
test_kmod/dbi_kern.c::recomp_*
DBI intra-page 回填
test_kmod/dbi_kern.c::dbi_recompile_page (末尾循环)
Ghost UAF 修复
test_kmod/ghost_mm.c::ghost_free_drain_ipi
shellcode 生成
pte_hookctl/shellcode.py::java_uxn_filter
Java hook 安装流程
pte_hookctl/ptehook/session.py::_install_java
ArtMethod 扫描消歧
pte_hookctl/ptehook/session.py::_resolve_artmethod
JIT 漂移监测
pte_hookctl/ptehook/session.py::_jit_watch_loop
KPM 卸载清理
test_kmod/ptehook_planc_v2.c::cleanup_all_state
跨 Android 版本偏移
pte_hookctl/art_offsets.py::_OFFSETS_BY_API
跨 kernel 版本探测
test_kmod/ptehook_planc_v2.c::probe_kern_layout
指令
mask
opcode pattern
NOP
0xFFFFFFFF
0xD503201F
B imm26
0xFC000000
0x14000000
BL imm26
0xFC000000
0x94000000
B.cond imm19
0xFF000010
0x54000000
CBZ/CBNZ imm19
0x7E000000
0x34000000
TBZ/TBNZ imm14
0x7E000000
0x36000000
ADRP imm21
0x9F000000
0x90000000
ADR imm21
0x9F000000
0x10000000
LDR (lit) Wt
0xFF000000
0x18000000
LDR (lit) Xt
0xFF000000
0x58000000
LDRSW (lit)
0xFF000000
0x98000000
PRFM (lit)
0xFF000000
0xD8000000
LDR (lit) S/D/Q
0x3F000000
0x1C000000
BR Xn
0xFFFFFC1F
0xD61F0000 | (Rn<<5)
BLR Xn
0xFFFFFC1F
0xD63F0000 | (Rn<<5)
RET
0xFFFFFFFF
0xD65F03C0
BTI jc
0xFFFFFFFF
0xD50324DF
MOVZ Xd, imm16, LSL (hw*16)
0xD2800000 | (hw<<21) | (imm<<5) | Rd
MOVK Xd, imm16, LSL (hw*16)
0xF2800000 | (hw<<21) | (imm<<5) | Rd
bit 63 : NSE (非安全扩展)
bit 62 -61 : 保留 / IGNORED
bit 60 -59 : Contiguous / DBM
bit 58 -55 : 软件位(内核可用)
bit 54 : UXN (Unprivileged eXecute Never) ← ★我们的陷阱开关
bit 53 : PXN (Privileged eXecute Never)
bit 52 : Contiguous
bit 51 -50 : 保留
bit 50 : GP (Guarded Page, ARMv8.5 BTI)
bit 49 -12 : 物理页帧号 PFN
bit 11 -10 : NG, AF
bit 9 -8 : SH (Shareability)
bit 7 -6 : AP (Access Permission)
bit 7 = AP[2 ]: 0 = RW, 1 = RO
bit 6 = AP[1 ]: 0 = kernel only, 1 = user+kernel
bit 5 : NS
bit 4 -2 : Attr Index
bit 1 -0 : Block / Page type
#include <compat.h>
#include <kallsyms.h>
#include <hook.h>
#include "dbi_kern.h"
#include "ghost_mm.h"
typedef int (*access_process_vm_t ) (struct task_struct *, unsigned long ,
void *, int , unsigned int ) ;
typedef void *(*get_task_mm_t )(struct task_struct *);
typedef void (*mmput_t ) (struct mm_struct *) ;
typedef int (*apply_to_page_range_t ) (struct mm_struct *mm, unsigned long addr,
unsigned long size, pte_fn_t fn, void *data) ;
typedef unsigned long (*get_free_pages_t ) (unsigned int , unsigned int ) ;
typedef void (*free_pages_t ) (unsigned long , unsigned int ) ;
typedef void *(*find_vma_t )(struct mm_struct *, unsigned long );
typedef void (*on_each_cpu_t ) (void (*fn)(void *), void *arg, int wait) ;
static access_process_vm_t fn_access_process_vm;
static get_task_mm_t fn_get_task_mm;
static mmput_t fn_mmput;
static apply_to_page_range_t fn_apply_to_page_range;
static get_free_pages_t fn_get_free_pages;
static free_pages_t fn_free_pages;
static find_vma_t fn_find_vma;
static const int64_t *ptr_physvirt_offset;
static on_each_cpu_t fn_on_each_cpu;
static void *addr_do_mem_abort;
static long planc2_init (const char *args, const char *event, void *__user r)
{
fn_access_process_vm = (access_process_vm_t )kallsyms_lookup_name("access_process_vm" );
fn_get_task_mm = (get_task_mm_t )kallsyms_lookup_name("get_task_mm" );
fn_mmput = (mmput_t )kallsyms_lookup_name("mmput" );
fn_apply_to_page_range = (apply_to_page_range_t )kallsyms_lookup_name("apply_to_page_range" );
fn_get_free_pages = (get_free_pages_t )kallsyms_lookup_name("__get_free_pages" );
fn_free_pages = (free_pages_t )kallsyms_lookup_name("free_pages" );
fn_find_vma = (find_vma_t )kallsyms_lookup_name("find_vma" );
ptr_physvirt_offset = (const int64_t *)kallsyms_lookup_name("physvirt_offset" );
fn_on_each_cpu = (on_each_cpu_t )kallsyms_lookup_name("on_each_cpu" );
addr_do_mem_abort = (void *)kallsyms_lookup_name("do_mem_abort" );
struct ghost_mm_syms gsyms = {
.get_free_pages = fn_get_free_pages,
.free_pages = fn_free_pages,
.find_vma = fn_find_vma,
.apply_to_page_range = fn_apply_to_page_range,
.physvirt_offset_p = ptr_physvirt_offset,
.on_each_cpu = fn_on_each_cpu,
};
ghost_mm_init(&gsyms);
return 0 ;
}
static long planc2_ctl0 (const char *args, char *__user out_msg, int outlen)
{
static char buf[4096 ];
int off = 0 ;
const char *p = args;
if (str_starts_with(p, "uxn-hook" )) {
int pid = parse_num(&p);
unsigned long target = parse_num(&p);
unsigned long replace = parse_num(&p);
off = cmd_uxn_hook(pid, target, replace, buf, sizeof (buf));
}
else if (str_starts_with(p, "uxn-unhook" )) {
}
else if (str_starts_with(p, "ghost-alloc-at" )) {
}
else if (str_starts_with(p, "proc-read" )) {
}
compat_copy_to_user(out_msg, buf, off < outlen ? off + 1 : outlen);
return 0 ;
}
KPM_INIT(planc2_init);
KPM_CTL0(planc2_ctl0);
KPM_EXIT(planc2_exit);
struct ghost_page {
struct task_struct *task ;
struct mm_struct *mm ;
unsigned long vaddr;
unsigned long kaddr;
unsigned long pfn;
uint64_t installed_pte;
int order;
unsigned long alloc_size;
int installed;
};
struct install_ctx {
uint64_t pte_val;
int written;
};
static int install_pte_cb (void *pte_ptr, unsigned long addr, void *data)
{
struct install_ctx *c = (struct install_ctx *)data;
uint64_t *p = (uint64_t *)pte_ptr;
if (*p != 0 && (*p & PTE_VALID)) return -EEXIST;
*p = c->pte_val;
c->written = 1 ;
return 0 ;
}
int ghost_alloc (struct task_struct *task, struct mm_struct *mm,
unsigned long near, unsigned long range,
uint64_t pte_template, int num_pages,
struct ghost_page *out)
{
unsigned long kva;
unsigned long vaddr;
uint64_t pa_base, new_pte;
int order = pages_to_order(num_pages);
int i, ret;
kva = g_syms.get_free_pages(GFP_KERNEL | GFP_ZERO, order);
if (!kva) return -ENOMEM;
pa_base = (uint64_t )((int64_t )kva + *g_syms.physvirt_offset_p);
vaddr = find_hole_near(mm, near, range, 1 << order);
if (!vaddr) { g_syms.free_pages(kva, order); return -ENOSPC; }
for (i = 0 ; i < (1 << order); i++) {
uint64_t page_pa = pa_base + (uint64_t )i * 0x1000 ;
struct install_ctx ictx ;
new_pte = (pte_template & ~ARM64_PFN_MASK) | (page_pa & ARM64_PFN_MASK);
new_pte |= PTE_VALID | PTE_TYPE_PAGE | PTE_AF;
new_pte &= ~PTE_UXN;
new_pte &= ~(1UL << 50 );
new_pte &= ~(1UL << 7 );
ictx.pte_val = new_pte;
ictx.written = 0 ;
ret = g_syms.apply_to_page_range(mm, vaddr + i * 0x1000 , 0x1000 ,
install_pte_cb, &ictx);
if (ret || !ictx.written) {
int j;
for (j = 0 ; j < i; j++) {
struct clear_ctx cctx = { .cleared = 0 };
g_syms.apply_to_page_range(mm, vaddr + j * 0x1000 , 0x1000 ,
clear_pte_cb, &cctx);
}
g_syms.free_pages(kva, order);
return -EFAULT;
}
}
asm volatile (
"dsb ishst\n\t"
"tlbi vmalle1is\n\t"
"dsb ish\n\t"
"isb\n\t"
::: "memory"
) ;
out->task = task;
out->mm = mm;
out->vaddr = vaddr;
out->kaddr = kva;
out->pfn = pa_base >> 12 ;
out->installed_pte = new_pte;
out->order = order;
out->alloc_size = (1UL << order) * 0x1000 ;
out->installed = 1 ;
return 0 ;
}
pop.mode = 0 ;
pop.out_val = &tpte;
fn_apply_to_page_range(mm, libart_page, 0x1000 , pte_op_cb, &pop);
if (gap_end - gap_start >= 0x1000 ) {
}
static unsigned long find_hole_near (struct mm_struct *mm, unsigned long near,
unsigned long range, int num_pages)
{
unsigned long need = (unsigned long )num_pages * 0x1000 ;
if (gap_end - gap_start >= need) {
}
}
struct uxn_hook_slot {
int used;
int pid;
unsigned long target_addr;
unsigned long target_page;
unsigned long replace_addr;
uint64_t saved_pte;
struct dbi_page_ctx dbi ;
struct ghost_page gp ;
unsigned long fault_hits;
unsigned long pass3_hits;
unsigned long last_pass3_far;
unsigned long last_pass3_new_pc;
};
#define UXN_HOOK_MAX 16
static struct uxn_hook_slot uxn_hooks [UXN_HOOK_MAX ];
static void before_do_mem_abort (hook_fargs3_t *fargs, void *udata)
{
unsigned long far = fargs->arg0;
unsigned long esr = fargs->arg1;
void *regs_vp = (void *)fargs->arg2;
unsigned int ec, ifsc;
unsigned long *pc_ptr;
uint64_t new_pc;
int i, cur_pid;
ec = (esr >> 26 ) & 0x3F ;
if (ec != 0x20 ) return ;
ifsc = esr & 0x3F ;
if (ifsc < 0x0C || ifsc > 0x0F ) return ;
pc_ptr = (unsigned long *)((char *)regs_vp + 0x100 );
cur_pid = fn_task_pid_nr_ns ? fn_task_pid_nr_ns(current, 0 , 0 ) : 0 ;
for (i = 0 ; i < UXN_HOOK_MAX; i++) {
struct uxn_hook_slot *s = &uxn_hooks[i];
if (!s->used) continue ;
if (cur_pid && s->pid != cur_pid) continue ;
if ((far & ~3UL ) == (s->target_addr & ~3UL )) {
*pc_ptr = s->replace_addr;
fargs->skip_origin = 1 ;
s->fault_hits++;
return ;
}
}
for (i = 0 ; i < UXN_HOOK_MAX; i++) {
struct uxn_hook_slot *s = &uxn_hooks[i];
if (!s->used) continue ;
if (cur_pid && s->pid != cur_pid) continue ;
if (s->target_page != (far & ~0xFFFU L)) continue ;
new_pc = dbi_target_to_ghost_pc(&s->dbi, far);
if (new_pc) {
*pc_ptr = new_pc;
fargs->skip_origin = 1 ;
s->fault_hits++;
s->pass3_hits++;
s->last_pass3_far = far;
s->last_pass3_new_pc = new_pc;
return ;
}
}
}
if (!g_hook_installed) {
hook_wrap3(addr_do_mem_abort, before_do_mem_abort, after_do_mem_abort, 0 );
g_hook_installed = 1 ;
}
static int cmd_uxn_hook (int pid, unsigned long target, unsigned long replace,
char *buf, int bsize)
{
struct task_struct *task ;
struct mm_struct *mm ;
struct uxn_hook_slot *s ;
unsigned long target_page = target & ~0xFFFU L;
struct pte_op pop ;
uint64_t tpte;
int slot, ghost_pages, r;
reap_dead_uxn_slots();
if (find_uxn_slot_by_target(pid, target) >= 0 ) return fail("already hooked" );
slot = find_uxn_slot();
s = &uxn_hooks[slot];
task = find_task(pid);
mm = fn_get_task_mm(task);
r = fn_access_process_vm(task, target_page, s->orig_buf,
DBI_TARGET_SIZE, FOLL_FORCE);
pop.mode = 0 ;
pop.out_val = &tpte;
fn_apply_to_page_range(mm, target_page, 0x1000 , pte_op_cb, &pop);
s->dbi.target_page = target_page;
s->dbi.ghost_page = 0 ;
s->dbi.orig = s->orig_buf;
s->dbi.ghost = s->ghost_buf;
s->dbi.ghost_capacity = DBI_GHOST_MAX_INSNS;
dbi_recompile_page(&s->dbi);
ghost_pages = (s->dbi.ghost_count * 4 + 0xFFF ) >> 12 ;
r = ghost_alloc(task, mm, target_page, 512UL << 20 , tpte,
ghost_pages, &s->gp);
if (r == -ENOSPC) {
r = ghost_alloc(task, mm, target_page, 8UL << 30 , tpte,
ghost_pages, &s->gp);
}
s->dbi.ghost_page = s->gp.vaddr;
dbi_recompile_page(&s->dbi);
ghost_write(&s->gp, 0 , s->ghost_buf, s->dbi.ghost_count * 4 );
ghost_sync_icache(&s->gp);
s->used = 1 ;
s->pid = pid;
s->target_addr = target;
s->target_page = target_page;
s->replace_addr = replace;
s->saved_pte = tpte;
s->fault_hits = s->pass3_hits = 0 ;
asm volatile ("dmb ish" ::: "memory" ) ;
if (!g_hook_installed) {
hook_wrap3(addr_do_mem_abort, before_do_mem_abort, after_do_mem_abort, 0 );
g_hook_installed = 1 ;
}
pop.mode = 1 ;
fn_apply_to_page_range(mm, target_page, 0x1000 , pte_op_cb, &pop);
asm volatile (
"dsb ishst\n\t"
"tlbi vmalle1is\n\t"
"dsb ish\n\t"
"isb\n\t"
::: "memory"
) ;
fn_mmput(mm);
return ok("slot=%d ghost=0x%lx backup=0x%lx" , slot, s->gp.vaddr,
dbi_target_to_ghost_pc(&s->dbi, target));
}
#define DBI_TARGET_SIZE 4096
#define DBI_TARGET_INSNS 1024
#define DBI_GHOST_MAX_INSNS 8192
struct dbi_page_ctx {
uint64_t target_page;
uint64_t ghost_page;
const uint32_t *orig;
uint32_t *ghost;
int ghost_capacity;
int ghost_count;
uint16_t offset_map[DBI_TARGET_INSNS];
struct dbi_pending_branch pending [512];
int n_pending;
int fixed, expanded, passthrough, failed, intra_page_fixed;
};
struct dbi_pending_branch {
int ghost_idx;
uint32_t enc_template;
uint16_t target_tidx;
uint8_t kind;
};
static int recomp_b (struct dbi_page_ctx *ctx, uint32_t insn, uint64_t orig_pc) ;
static int recomp_bl (struct dbi_page_ctx *ctx, uint32_t insn, uint64_t orig_pc) ;
static int recomp_bcond (struct dbi_page_ctx *ctx, uint32_t insn, uint64_t orig_pc) ;
static int recomp_cbz (struct dbi_page_ctx *ctx, uint32_t insn, uint64_t orig_pc) ;
static int recomp_tbz (struct dbi_page_ctx *ctx, uint32_t insn, uint64_t orig_pc) ;
static int recomp_adrp (struct dbi_page_ctx *ctx, uint32_t insn, uint64_t orig_pc) ;
static int recomp_adr (struct dbi_page_ctx *ctx, uint32_t insn, uint64_t orig_pc) ;
static int recomp_ldr_lit (struct dbi_page_ctx *ctx, uint32_t insn, uint64_t orig_pc,
int variant) ;
static int recomp_ldr_vec_lit (struct dbi_page_ctx *ctx, uint32_t insn, uint64_t orig_pc) ;
int dbi_recompile_page (struct dbi_page_ctx *ctx)
{
int i;
ctx->ghost_count = 0 ;
ctx->fixed = ctx->expanded = ctx->passthrough = ctx->failed = 0 ;
ctx->intra_page_fixed = 0 ;
ctx->n_pending = 0 ;
for (i = 0 ; i < DBI_TARGET_INSNS; i++) {
uint32_t insn = ctx->orig[i];
uint64_t orig_pc = ctx->target_page + (uint64_t )(i * 4 );
int prev_ghost_idx = ctx->ghost_count;
int rc = 0 ;
ctx->offset_map[i] = (uint16_t )prev_ghost_idx;
if (insn == 0xD503201F ) { ctx->passthrough++; rc = emit(ctx, insn); }
else if ((insn & 0xFC000000 ) == 0x14000000 ) rc = recomp_b(ctx, insn, orig_pc);
else if ((insn & 0xFC000000 ) == 0x94000000 ) rc = recomp_bl(ctx, insn, orig_pc);
else if ((insn & 0xFF000010 ) == 0x54000000 ) rc = recomp_bcond(ctx, insn, orig_pc);
else if ((insn & 0x7E000000 ) == 0x34000000 ) rc = recomp_cbz(ctx, insn, orig_pc);
else if ((insn & 0x7E000000 ) == 0x36000000 ) rc = recomp_tbz(ctx, insn, orig_pc);
else if ((insn & 0x9F000000 ) == 0x90000000 ) rc = recomp_adrp(ctx, insn, orig_pc);
else if ((insn & 0x9F000000 ) == 0x10000000 ) rc = recomp_adr(ctx, insn, orig_pc);
else if ((insn & 0xFF000000 ) == 0x18000000 ) rc = recomp_ldr_lit(ctx, insn, orig_pc, 0 );
else if ((insn & 0xFF000000 ) == 0x58000000 ) rc = recomp_ldr_lit(ctx, insn, orig_pc, 1 );
else if ((insn & 0xFF000000 ) == 0x98000000 ) rc = recomp_ldr_lit(ctx, insn, orig_pc, 2 );
else if ((insn & 0xFF000000 ) == 0xD8000000 ) { ctx->expanded++; rc = emit(ctx, 0xD503201F ); }
else if ((insn & 0x3F000000 ) == 0x1C000000 ) rc = recomp_ldr_vec_lit(ctx, insn, orig_pc);
else { ctx->passthrough++; rc = emit(ctx, insn); }
if (rc < 0 ) {
ctx->failed++;
ctx->ghost_count = prev_ghost_idx;
emit(ctx, 0xD503201F );
}
}
for (int p = 0 ; p < ctx->n_pending; p++) {
struct dbi_pending_branch *b = &ctx->pending[p];
uint64_t target_ghost = ctx->ghost_page + (uint64_t )ctx->offset_map[b->target_tidx] * 4 ;
uint64_t patch_pc = ctx->ghost_page + (uint64_t )b->ghost_idx * 4 ;
int64_t delta = (int64_t )(target_ghost - patch_pc);
uint32_t enc;
if (b->kind == 0 || b->kind == 1 ) {
int64_t imm19 = delta / 4 ;
enc = b->enc_template | (((uint32_t )imm19 & 0x7FFFFU ) << 5 );
} else if (b->kind == 2 ) {
int64_t imm14 = delta / 4 ;
enc = b->enc_template | (((uint32_t )imm14 & 0x3FFFU ) << 5 );
} else {
int64_t imm26 = delta / 4 ;
enc = b->enc_template | ((uint32_t )imm26 & 0x03FFFFFFU );
}
ctx->ghost[b->ghost_idx] = enc;
}
return 0 ;
}
uint64_t dbi_target_to_ghost_pc (const struct dbi_page_ctx *ctx,
uint64_t target_pc)
{
if (target_pc < ctx->target_page) return 0 ;
uint64_t off = target_pc - ctx->target_page;
if (off >= DBI_TARGET_SIZE) return 0 ;
unsigned idx = (unsigned )(off >> 2 );
return ctx->ghost_page + (uint64_t )ctx->offset_map[idx] * 4 ;
}
static int recomp_adrp (struct dbi_page_ctx *ctx, uint32_t insn, uint64_t orig_pc)
{
uint32_t rd = insn & 0x1FU ;
uint64_t immlo = (insn >> 29 ) & 0x3U ;
uint64_t immhi = (insn >> 5 ) & 0x7FFFFU ;
int64_t imm21 = sign_extend64((immhi << 2 ) | immlo, 21 );
uint64_t target = (orig_pc & ~0xFFFU LL) + ((uint64_t )imm21 << 12 );
ctx->expanded++;
return emit_load_addr(ctx, rd, target);
}
static int emit_load_addr (struct dbi_page_ctx *ctx, uint32_t rd, uint64_t addr)
{
uint32_t movs[4 ];
int n = emit_mov_imm64(rd, addr, movs, 4 );
if (n < 0 ) return -1 ;
for (int i = 0 ; i < n; i++) {
if (emit(ctx, movs[i]) < 0 ) return -1 ;
}
return 0 ;
}
static int emit_mov_imm64 (uint32_t rd, uint64_t imm, uint32_t *out, int max_insns)
{
int n = 0 , first = 1 ;
for (int shift = 0 ; shift < 64 ; shift += 16 ) {
uint16_t chunk = (uint16_t )((imm >> shift) & 0xFFFFU );
uint32_t hw = (uint32_t )(shift / 16 );
if (chunk == 0 ) {
if (!first) continue ;
if (imm != 0 ) continue ;
}
if (n >= max_insns) return -1 ;
if (first) {
out[n++] = 0xD2800000U | (hw << 21 ) | ((uint32_t )chunk << 5 ) | (rd & 0x1FU );
first = 0 ;
} else {
out[n++] = 0xF2800000U | (hw << 21 ) | ((uint32_t )chunk << 5 ) | (rd & 0x1FU );
}
}
if (first) {
out[n++] = 0xD2800000U | (rd & 0x1FU );
}
return n;
}
[培训]《冰与火的战歌:Windows内核攻防实战》!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。