ARM64 函数级 VM 执行与指令跟踪框架。把任意 native 函数放进 Unicorn 引擎中执行,暴露每条指令、外部调用、SVC、内存访问的全部细节。 支持多进程,多线程,多并发,多个函数同时追踪,测试抖音,美团,等大型app均无崩溃情况。
概要
ARM64 函数级 VM 执行与指令跟踪框架。把任意 native 函数放进 Unicorn 引擎中执行,暴露每条指令、外部调用、SVC、内存访问的全部细节。 支持多进程,多线程,多并发,多个函数同时追踪,测试抖音,美团,等大型app均无崩溃情况。
README
AndroidPrybar
ARM64 函数级 VM 执行与指令跟踪框架。把任意 native 函数放进 Unicorn 引擎中执行,暴露每条指令、外部调用、SVC、内存访问的全部细节。 支持多进程,多线程,多并发,多个函数同时追踪,测试抖音,美团,等大型app均无崩溃情况。
本工程已包含预编译的 libtrace.so、头文件和 JNI 示例。克隆后直接编译运行即可查看结果。
libtrace.so 在哪里
预编译动态库:app/src/main/jniLibs/arm64-v8a/libtrace.so
对外头文件:app/src/main/cpp/include/ARM64Emulator.h
快速上手
trace() — 最简路径,一键出日志,一般用这个就够了,第二个参数是指定一个路径,不要指定名字,它支持多线程调用的,你用的时候,trace这个包装函数会返回一个同等功能的函数指针,你直接用inlinehook或者无痕hook等手段替换到原来地址,等app自己调用,或者你来传参调用都可以
#include "ARM64Emulator.h"
// 本地文件输出(LZ4 压缩,per-thread 自动分 .lz4 文件)
auto fn = (int(*)(int))trace((void*)target_func, "/data/data/pkg/trace_dir");
fn(123); // 调用 → 自动写 trace 日志
freeTrace((uint64_t)fn); // 用完释放,也可以不用释放,没必要
// 输出文件在 trace_dir/ 下,用 trace_receiver.py decode *.lz4 还原为文本
// TCP 远程输出(LZ4 压缩,实时传到 PC,适合超大 trace),在使用的时候adb forward该端口即可
auto fn2 = (int(*)(int))trace((void*)target_func, "tcp:9876");
// ↑ 阻塞等待 PC 连接,PC 端运行: python trace_receiver.py receive,这个的具体使用写在最末尾
fn2(123);
freeTrace((uint64_t)fn2);
vc_make_handle — 带回调的 VM 执行
vm_context* ctx = nullptr;
auto fn = (int(*)(int, int))vc_make_handle((void*)target_func, &ctx);
// 注册 hook
vc_hook_h hh;
vc_hook_add(ctx, &hh, VC_HOOK_EXTERNAL_JUMP, (void*)my_jump_cb, nullptr, 0, 0);
int result = fn(1, 2); // 在 VM 中执行
vc_free(ctx);
replace_trace() — inline hook 式 trace(一般用不到)
replace_trace((void*)func_addr, "/data/data/pkg/trace_dir");
// 之后所有对 func_addr 的调用都自动走 VM trace
restore_function((void*)func_addr); // 恢复原函数
Hook 类型与回调
每种 hook 类型有对应的回调签名:
// 拦截外部函数调用(最常用)
void my_jump_cb(vm_context* ctx, uint64_t address,
const char* symbol_name, vc_event_action* action, void* ud) {
if (symbol_name && strcmp(symbol_name, "getuid") == 0) {
uint64_t fake_uid = 0;
vc_reg_write(ctx, VC_REG_X0, &fake_uid);
*action = VC_ACTION_SKIP; // 跳过真实调用
}
}
// SVC 系统调用监控
void my_svc_cb(vm_context* ctx, uint64_t address, uint32_t syscall_nr, void* ud) {
LOGD("SVC #%u @ 0x%lx", syscall_nr, address);
}
// 基本块
void my_block_cb(vm_context* ctx, uint64_t address, uint32_t size, void* ud) {
// 每个基本块入口触发
}
// 内存读写
void my_mem_cb(vm_context* ctx, vc_mem_type type,
uint64_t address, int size, int64_t value, void* ud) {
if (type == VC_MEM_WRITE) {
LOGD("MEM WRITE @ 0x%lx size=%d val=0x%lx", address, size, value);
}
}
Hook 类型速查
| 类型 | 回调签名 | 用途 |
|---|---|---|
VC_HOOK_BLOCK |
vc_cb_hookcode_t |
每个基本块入口 |
VC_HOOK_CODE |
vc_cb_hookcode_t |
每条指令(慢 ~10x) |
VC_HOOK_INTR |
vc_cb_hookintr_t |
中断(含 SVC) |
VC_HOOK_MEM_READ |
vc_cb_hookmem_t |
内存读 |
VC_HOOK_MEM_WRITE |
vc_cb_hookmem_t |
内存写 |
VC_HOOK_MEM_READ_AFTER |
vc_cb_hookmem_t |
内存读后(有值) |
VC_HOOK_SVC |
vc_cb_hooksvc_t |
SVC 系统调用 |
VC_HOOK_EXTERNAL_JUMP |
vc_cb_hookjump_t |
外部函数调用 |
寄存器读写
uint64_t pc, x0;
vc_reg_read(ctx, VC_REG_PC, &pc);
vc_reg_read(ctx, VC_REG_X0, &x0);
uint64_t fake_ret = 0;
vc_reg_write(ctx, VC_REG_X0, &fake_ret);
// 批量读
uint64_t x0_val, x1_val, sp_val;
vc_reg regs[] = { VC_REG_X0, VC_REG_X1, VC_REG_SP };
void* vals[] = { &x0_val, &x1_val, &sp_val };
vc_reg_read_batch(ctx, regs, vals, 3);
// SIMD/FP 寄存器(128 位)
__uint128_t q0;
vc_reg_read(ctx, VC_REG_Q0, &q0);
跳转控制
默认行为
| SO 类型 | 默认行为 |
|---|---|
| 目标 SO | VM 内执行 |
| 其他用户 SO | VM 内执行 |
| 系统库(libc 等) | 跳出到 host |
blacklist — 强制指定 SO 跳出到 host
// 某些 SO 不需要 trace,让它们在 host 上跑更快
const char* blacklist[] = { "libutils.so", "libcrypto.so", nullptr };
vc_set_jump_blacklist(blacklist, nullptr, 0);
// 也可以用地址范围
uint64_t ranges[][2] = { { base, base + 0x200000 } };
vc_set_jump_blacklist(nullptr, ranges, 1);
// 清除
vc_clear_jump_blacklist();
全局开关
// 只模拟目标 SO 本身,其他所有用户库都跳出到 host
vc_set_external_jump_enabled(false);
单步与受控执行
所有单步/断点 API 在回调中调用。
// 单步 — 执行 N 条指令后暂停,再次触发回调链
vc_single_step(ctx, 1); // 执行 1 条后暂停
vc_single_step(ctx, 100); // 执行 100 条后暂停
// 回调中不调用 → 恢复正常执行
// 设置停止地址(临时断点),到达后自动清除
vc_set_until(ctx, target_addr);
vc_set_until(ctx, 0); // 手动清除
类 LLDB 调试器示例
void debugger_cb(vm_context* ctx, uint64_t addr, uint32_t size, void* ud) {
uint64_t pc;
vc_reg_read(ctx, VC_REG_PC, &pc);
// 反汇编当前指令
vc_insn insn;
if (vc_disasm(pc, 1, &insn) > 0) {
LOGD("0x%lx: %s %s", insn.address, insn.mnemonic, insn.op_str);
}
// 打印寄存器
uint64_t x0, x1, sp;
vc_reg_read(ctx, VC_REG_X0, &x0);
vc_reg_read(ctx, VC_REG_X1, &x1);
vc_reg_read(ctx, VC_REG_SP, &sp);
LOGD(" X0=0x%lx X1=0x%lx SP=0x%lx", x0, x1, sp);
vc_single_step(ctx, 1); // 继续单步
}
反汇编 API
// 不需要 vm_context(identity mapping)
vc_insn insns[10];
int count = vc_disasm(address, 10, insns);
for (int i = 0; i < count; i++) {
LOGD("0x%lx: [%08x] %s %s",
insns[i].address, insns[i].bytes,
insns[i].mnemonic, insns[i].op_str);
}
VM 控制
// 停止 VM
vc_emu_stop(ctx);
// CPU 状态快照
vc_cpu_context* snap = nullptr;
vc_context_save(ctx, &snap);
// ... 执行一些操作 ...
vc_context_restore(ctx, snap);
vc_context_free(snap);
// 符号查询
const char* sym = vc_lookup_symbol(ctx, address);
内存监控
// 内置监控(每个 ctx 各只能添加一次)
trace_read(ctx, 0, 0); // 全范围读监控
trace_write(ctx, 0, 0); // 全范围写监控
// 自定义 watchpoint
void mem_watch(vm_context* ctx, vc_mem_type type,
uint64_t address, int size, int64_t value, void* ud) {
uint64_t pc;
vc_reg_read(ctx, VC_REG_PC, &pc);
LOGD("WATCHPOINT: [0x%lx] written by PC=0x%lx val=0x%lx", address, pc, value);
}
vc_hook_h hh;
vc_hook_add(ctx, &hh, VC_HOOK_MEM_WRITE, (void*)mem_watch, nullptr,
watch_addr, watch_addr + 8);
实战示例:绕过环境检测
void anti_detect(vm_context* ctx, uint64_t addr,
const char* sym, vc_event_action* action, void* ud) {
if (!sym) return;
if (strcmp(sym, "access") == 0) {
uint64_t path_ptr;
vc_reg_read(ctx, VC_REG_X0, &path_ptr);
const char* path = (const char*)path_ptr;
if (strstr(path, "su") || strstr(path, "magisk")) {
uint64_t ret = (uint64_t)-1;
vc_reg_write(ctx, VC_REG_X0, &ret);
*action = VC_ACTION_SKIP;
}
}
else if (strcmp(sym, "getuid") == 0) {
uint64_t ret = 10000;
vc_reg_write(ctx, VC_REG_X0, &ret);
*action = VC_ACTION_SKIP;
}
else if (strcmp(sym, "exit") == 0 || strcmp(sym, "abort") == 0) {
*action = VC_ACTION_SKIP;
}
}
vm_context* ctx = nullptr;
auto fn = (int(*)())vc_make_handle((void*)target, &ctx);
vc_hook_h hh;
vc_hook_add(ctx, &hh, VC_HOOK_EXTERNAL_JUMP, (void*)anti_detect, nullptr, 0, 0);
fn();
vc_free(ctx);
trace 输出格式
trace 输出为 LZ4 压缩格式(.lz4 文件),用 trace_receiver.py decode 还原为文本。
还原后每行一条指令:
so+0xOFFSET 0xPC: mnemonic operands reg_reads => reg_writes mem_r[0xADDR] / mem_w[0xADDR]=VAL
- str:“…” — 访问地址是 C 字符串时完整输出
- ->“…” — 加载的值是字符串指针时输出指向的字符串
- [CALL] func(args) — 外部函数调用
- >>> JNIEnv->Method() — JNI 调用
API 速查表
| API | 用途 |
|---|---|
trace(func, path) |
快速 trace(path 为目录 → 本地 .lz4,"tcp:PORT" → 远程) |
trace(func, path, &ctx) |
带 ctx 的 trace |
freeTrace(wrapper) |
释放 trace 句柄 |
replace_trace(func, path) |
全局替换式 trace |
restore_function(func) |
恢复被替换的函数 |
vc_make_handle(func, &ctx) |
创建裸 VM 句柄 |
vc_free(ctx) |
释放 VM 上下文 |
vc_hook_add(ctx, &hh, type, cb, ud, begin, end) |
注册 hook |
vc_hook_del(ctx, hh) |
删除 hook |
vc_reg_read / vc_reg_write |
寄存器读写 |
vc_reg_read_batch / vc_reg_write_batch |
批量读写 |
vc_emu_stop(ctx) |
停止 VM |
vc_single_step(ctx, count) |
执行 N 条后暂停 |
vc_set_until(ctx, addr) |
设置临时断点 |
vc_disasm(addr, count, out) |
反汇编 |
vc_context_save / restore / free |
CPU 快照 |
vc_lookup_symbol(ctx, addr) |
地址查符号 |
vc_set_jump_blacklist(names, ranges, n) |
设置跳转黑名单 |
vc_clear_jump_blacklist() |
清除黑名单 |
vc_set_external_jump_enabled(enabled) |
全局跳转开关 |
trace_read / trace_write |
内存监控 |
性能参考
| 模式 | 速度 | 适用场景 |
|---|---|---|
| vc_make_handle(默认) | ~2-5x 慢 | 功能验证、外部调用监控 |
| trace() 指令级 | ~50-100x 慢 | 详细分析、逆向工程 |
| vc_make_handle + CODE | ~10-20x 慢 | 自定义逐指令监控 |
| vc_single_step(ctx, 1) | ~100-200x 慢 | 精确调试 |
Demo 工程说明
点击界面上的 Run Native Demo 后会按顺序运行:
- 自定义回调示例 —
vc_make_handle+ 事件回调 + 伪造返回值 - JNI trace 示例 —
trace()在 Android 环境中跟踪 JNI 调用 - 原子指令测试 — LDXR/STXR/CAS/SWP/LDADD + std::atomic 正确性验证
- 普通 trace 示例 — 最小接入路径
运行后在 filesDir 下生成 LZ4 日志文件,界面输出中显示完整路径。用 python tools/trace_receiver.py decode *.lz4 还原为纯文本。
项目结构
AndroidPrybar/
|-- TraceDemo/ ← Android 接入示例工程
| `-- app/src/main/
| |-- cpp/include/ARM64Emulator.h ← API 头文件
| |-- cpp/native-lib.cpp ← Demo 示例代码
| `-- jniLibs/arm64-v8a/libtrace.so ← 预编译库
|-- tools/
| |-- trace_receiver.py ← TCP 接收 + LZ4 解码工具
| `-- build_calltree.py ← trace → 函数调用树/调用图
|-- .claude/skills/unicorn-trace/ ← 附带的 Claude Code 用法 skill
`-- README.md
附带工具
tools/trace_receiver.py — LZ4 解码 + TCP 接收
trace 输出统一为 LZ4 压缩格式(.lz4 文件),压缩比约 7-8x。本工具负责解码和 TCP 接收。
解码本地 .lz4 文件
手机上 trace 产生的文件是 .lz4 格式,先 adb pull 到 PC,再用 decode 还原为可读文本:
# 从手机拉取 trace 目录
adb pull /data/data/com.xxx/trace_dir/ .
# 解码
python tools/trace_receiver.py decode trace.lz4 # → trace.log
python tools/trace_receiver.py decode *.lz4 # 批量解码所有 .lz4
python tools/trace_receiver.py decode trace.lz4 --stdout # 输出到 stdout(可 pipe 给 grep 等)
python tools/trace_receiver.py decode trace.lz4 -o out.log # 指定输出文件名
TCP 远程接收(适合超大 trace)
trace 量很大时(几百 MB ~ 几 GB),不适合写手机存储,用 TCP 实时传到 PC:
# 1. PC 端先启动接收(自动 adb forward,等待设备连接)
python tools/trace_receiver.py receive
# 2. App 中调用 trace(func, "tcp:9876") → 设备阻塞等待 PC 连接
# 连接建立后 trace 数据实时流式传输到 PC
# freeTrace() 后自动结束
# 自定义选项
python tools/trace_receiver.py receive -p 12345 -o my.log # 自定义端口和输出前缀
python tools/trace_receiver.py receive --no-adb --host 192.168.1.x # WiFi 直连(不走 USB)
python tools/trace_receiver.py receive --stdout # 输出到 stdout
多线程输出
trace 支持多线程并发调用,每个线程的 trace 数据带有 tid 标识:
- 本地文件模式:每个线程自动生成独立文件,如
func_tid1234_0.lz4、func_tid1234_1.lz4(同一线程多次调用自动递增序号) - TCP 模式:所有线程数据通过同一 socket 传输,接收端自动按 tid 分文件,如
trace_tid1234_0.log、trace_tid5678_0.log。同一 tid 多次接收时序号自动递增,不会覆盖已有文件 - decode 多线程 .lz4:如果一个
.lz4文件中包含多个 tid 的数据,decode 会自动拆分为_tid{N}.log
接收/解码时终端会实时显示各线程数据量:
[*] frames=42 total=3.2MB ratio=7.6x speed=12.5MB/s [t1234:2.1M t5678:1.1M]
tools/build_calltree.py — trace → 函数调用树
从 trace 重建函数调用树/调用图(谁调了谁、各函数调用次数)。脚本侧建树,部分/被打断的 trace 也能建;新引擎的多线程输出(每线程一文件的目录)传目录即自动每线程一棵树。
python tools/build_calltree.py [so名] [入口偏移hex] [最大行数]
# 输出: _calltree.txt(调用树) + _callgraph.txt(调用图/计数)
.claude/skills/unicorn-trace/ — Claude Code 用法 skill
本仓库内置一个 Claude Code skill:unicorn-trace。用 Claude Code 打开本仓库干活时,它会自动带上 libtrace 的完整用法,AI 直接知道怎么调 trace() / vc_make_handle / 各类 hook,不用每次解释。
SKILL.md:精简速查(两个入口、API 速查表、Hook 类型、实战示例索引、trace 格式、性能、限制)。GUIDE.md:完整指南(完整 API + 实战示例 + trace 格式)。- 想在别的项目用:把
.claude/skills/unicorn-trace/复制到那个项目的.claude/skills/或用户级~/.claude/skills/。
交流群 / 联系方式
欢迎大家扫码进群,一起学习和交流 Android Native / Trace / VM 相关内容。
个人的碎碎念念
这个工具断断续续写了两年。当时公开好用的 trace 工具不多,相关思路也少有人分享,很多东西只能自己一点点摸索、测试、推倒重来,光是一个支持自动传参的 JIT 就折腾了很久。
现在把它分享出来,用得上就拿去用,随便改、随便抄,不用署名。如果它帮到了你,欢迎进群交流,也算是给我一点继续维护的动力。
推奨ツール
別のキーワードを試すか、フィルタを外してください。
インストール
npx skillfish add gitkittys/androidprybar