
1.了解unidbg_trace,精准控制追踪,提升逆向分析效率
2.配置 mcp 服务,利用AI辅助逆向,加速关键算法分析
3.unidbg 之补 jni,模拟各类Java层调用
1.教程Demo
2.IDEA
3.IDA
4.Cursor
1. 基础用法
在调用目标函数前,只需简单地调用 emulator.traceCode() 即可开启对后续所有指令的追踪。
2. 约束追踪范围
通常我们只关心目标 SO 的执行流,而非 libc 等系统库的内部执行。traceCode 的重载方法 traceCode(long begin, long end) 允许我们精确设定追踪的内存地址范围。
3. 精确控制追踪时机
4. 预估追踪耗时
等待一个未知的 Trace 过程是痛苦的。我们可以通过一个轻量级的 CodeHook 快速统计指令总数,从而预估总耗时。这基本不含反汇编和寄存器打印的开销,所以速度极快。
PS::在较好的测试条件下,unidbg 每分钟约可追踪 40 万行执行流。你可以根据统计出的总行数来判断是泡杯咖啡等几分钟,还是需要去睡一觉等几个小时。
5. 限定特定函数
结合断点,可以实现对单个函数内部的精确追踪。
6.函数调用追踪 (debugger.traceFunctionCall) 详解
traceFunctionCall 是 unidbg 0.9.6 版本后新增的强大功能,用于追踪函数粒度的调用关系,对理清高层逻辑和对抗 OLLVM 等混淆有奇效。
1. 基础用法
它属于 Debugger 的一部分,使用时需要先 attach() 到模拟器。
2. 构建函数调用树
通过获取栈回溯深度,我们可以格式化输出,形成一个清晰的函数调用树,这对于分析复杂的调用逻辑极其有用。
首先修改一下源码,路径:unidbg-api\src\main\java\com\github\unidbg\unwind
添加以下方法:
调用实例:
完整代码:
MCP概念:
MCP(Model Context Protocol,模型上下文协议)是什么?
MCP是一种标准化协议,旨在为人工智能模型(如大语言模型)与外部工具、数据源之间的交互提供统一接口。(为 AI 装上了手脚)
MCP是AI智能体与外部工具的"USB接口",定义了AI模型与外部工具(如API、数据库、文档编辑器等)的交互标准,开发者无需为每个工具单独开发适配代码。
MCP协议旨在实现大型语言模型(LLM)与外部数据源和工具之间的无缝集成,通过提供标准化的接口,使AI应用程序能够安全、可控地与本地或远程资源进行交互。

项目地址
要求:
1.IDA Pro 8.3 以上,最好是 9
2.python3.11 或更高版本,使用idapyswitch切换到Python 版本
3.支持 Mcp 的客户端,这里以 Cursor 为例
环境配置:
Ps: 如果你现有的 python 版本低于十一,可以单独下载一个版本然后安装到 ida 的目录下

接着打开 idapyswitch 切换 3.11 的版本

然后 cmd 窗口打开新安装的 python.exe 再运行 pip 命令
接下来,再运行命令:
这步如果报错,可以打开 11 版本 python 目录下的 Scripts 文件夹,找到 ida-pro-mcp.exe
Cmd 窗口运行:
最后运行:

接下来打开 cursor,发现 ida_pro_mcp 已经亮绿灯了,说明 mcp 服务正常了。如果没有你就复制 ida-pro-mcp --config 输出的那串 json 到 mcp.json


最后在 IDA 中将 server 端开启即可。

定义:
“Unidbg 补环境”是指在使用 Unidbg 模拟执行 Android 原生 (Native) 代码时,为了使程序能够正常运行并获得与真实设备一致的结果,对 Unidbg 模拟器环境中的缺失或不完善部分进行补充和调整的过程
JNI (Java Native Interface) 补环境是 Unidbg 应用中的核心环节。当 Unidbg 模拟执行的原生库(. So 文件)尝试通过 JNI 调用 Java 层的代码时,Unidbg 必须能够提供这些 Java 方法的模拟实现。如果 Unidbg 缺少某个方法的实现,或者默认实现不符合预期,程序就会抛出 UnsupportedOperationException 异常,导致模拟中断。因此,JNI 补环境的目的就是拦截这些 JNI 调用,并提供一个符合目标 SO 文件逻辑预期的返回值或行为,从而“欺骗”SO 文件,使其认为自己运行在真实的 Android 环境中。(本质就是缺啥补啥)
这是最主要的 Java 层补环境方式。通过创建一个继承自com.github.unidbg.linux.android.dvm.AbstractJni 的自定义类,并重写其关键方法,可以模拟任意 JNI 调用。
具体步骤:
方法实现示例:
public DvmObject<?> newObjectV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
switch (signature) {
case "java/util/HashMap->
}
```
- 模拟字段访问 (Get/Set<Type>Field) :
处理复杂的数据结构(如结构体、数组)是 JNI 补环境的难点和重点。
1. 数组 byte[]、String[]、 int[]
2.结构体 (Struct)
3.Map 等集合对象
在本次课程中,我们系统性地学习了 Unidbg 逆向分析中的两项核心技术和 mcp 的配置与辅助分析:
本节完整代码:
百度云
阿里云
哔哩哔哩
教程开源地址
PS:解压密码都是52pj,阿里云由于不能分享压缩包,所以下载exe文件,双击自解压
白龙unidbg教程
| Trace 类型 |
描述 |
| 函数 Trace |
包括无差别的函数 Trace、导出函数 Trace、库函数 Trace、系统调用 Trace 等,用于分析算法执行流、应对 OLLVM 混淆与动态跳转。 |
| 基本块 Trace |
也叫 Block Trace,用于控制流分析与反混淆。 |
| 汇编 Trace |
包括指令级 Trace、特定指令 Trace 等,尤其适用于分析自定义算法、花指令;它也是最重要的 Trace 类型,基于它可实现其他类型 Trace。 |
| 维度 |
表现 |
| 易用性 |
必须掌握 Unidbg 的使用,但一旦会用,就能得到目标函数完整执行的 Trace。开启指令追踪十分简单,只需 emulator.traceCode(begin, end) 一句码。 对比:Frida Stalker、IDA Instruction trace 的启动更复杂。 |
| 效率 |
速度中等:测试环境下约可达 40w 行执行流/分钟(2400w/小时)。 简单样本约 10 分钟即可完成 Trace,中等样本则需 2 小时,复杂、混淆严重的样本可能长达 12 小时甚至更多。 与 IDA trace 相比快很多,但比 Stalker 等动态重编译方慢。 实际速度会因范围过滤等处理而折减至理论值的 1/4 或更低。 |
| 稳定性 |
稳定性好:基于 Unicorn,引发崩溃或异常 Trace 的情况较少。 对比:IDA Trace 容易中断或导致应用崩溃,Stalker 对某些指令支持有 Bug。 |
| 兼容性 |
支持 ARM32 与 ARM64。 比多数指令追踪方案(如 Stalker 不完全支持 ARM32)更全面。 相比 IDA Trace 对 X86、MIPS 等架构的支持仍有不足。 |
| 展示效果 |
信息维度丰富:包括时间、绝对地址、模块、相对偏移、机器码、汇编代码、执行前后寄存器变化等。 信息处理也很出色,细节优化多。 |
emulator.traceCode();
boolean result = security.callStaticJniMethodBoolean(emulator, "check", "123456");
emulator.traceCode();
boolean result = security.callStaticJniMethodBoolean(emulator, "check", "123456");
Module module = dm.getModule();
emulator.traceCode(module.base, module.base + module.size);
Module module = dm.getModule();
emulator.traceCode(module.base, module.base + module.size);
memory.addModuleListener(new ModuleListener() {
@Override
public void onLoaded(Emulator<?> emulator, Module module) {
if("lib52pojie.so".equals(module.name)){
emulator.traceCode(module.base, module.base + module.size);
}
}
});
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/zj/wuaipojie/util/lib52pojie.so"), true);
memory.addModuleListener(new ModuleListener() {
@Override
public void onLoaded(Emulator<?> emulator, Module module) {
if("lib52pojie.so".equals(module.name)){
emulator.traceCode(module.base, module.base + module.size);
}
}
});
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/zj/wuaipojie/util/lib52pojie.so"), true);
private long instructionCount = 0;
emulator.getBackend().hook_add_new(new CodeHook() {
@Override
public void hook(Backend backend, long address, int size, Object user) {
instructionCount++;
}
@Override
public void onAttach(UnHook unHook) {}
@Override
public void detach() {}
}, module.base, module.base + module.size, null);
System.out.println("总共执行ARM指令数: " + test.instructionCount);
private long instructionCount = 0;
emulator.getBackend().hook_add_new(new CodeHook() {
@Override
public void hook(Backend backend, long address, int size, Object user) {
instructionCount++;
}
@Override
public void onAttach(UnHook unHook) {}
@Override
public void detach() {}
}, module.base, module.base + module.size, null);
System.out.println("总共执行ARM指令数: " + test.instructionCount);
long callAddr = module.base + 0xE53C;
final TraceHook[] traceHook = new TraceHook[1];
emulator.attach().addBreakPoint(callAddr, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
traceHook[0] = emulator.traceCode(module.base, module.base + module.size);
return true;
}
});
emulator.attach().addBreakPoint(callAddr + 4, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
if (traceHook[0] != null) {
traceHook[0].stopTrace();
}
return true;
}
});
long callAddr = module.base + 0xE53C;
final TraceHook[] traceHook = new TraceHook[1];
emulator.attach().addBreakPoint(callAddr, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
traceHook[0] = emulator.traceCode(module.base, module.base + module.size);
return true;
}
});
emulator.attach().addBreakPoint(callAddr + 4, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
if (traceHook[0] != null) {
traceHook[0].stopTrace();
}
return true;
}
});
Debugger debugger = emulator.attach();
debugger.traceFunctionCall(module, new FunctionCallListener() {
@Override
public void onCall(Emulator<?> emulator, long callerAddress, long functionAddress) {
System.out.println("onCall: " + UnidbgPointer.pointer(emulator, callerAddress) + " -> " + UnidbgPointer.pointer(emulator, functionAddress));
}
@Override
public void postCall(Emulator<?> emulator, long callerAddress, long functionAddress, Number[] args) {
}
});
Debugger debugger = emulator.attach();
debugger.traceFunctionCall(module, new FunctionCallListener() {
@Override
public void onCall(Emulator<?> emulator, long callerAddress, long functionAddress) {
System.out.println("onCall: " + UnidbgPointer.pointer(emulator, callerAddress) + " -> " + UnidbgPointer.pointer(emulator, functionAddress));
}
@Override
public void postCall(Emulator<?> emulator, long callerAddress, long functionAddress, Number[] args) {
}
});
/**
* 将给定的内存地址格式化为包含详细信息、可读的字符串。
* 这个方法会尝试解析地址,并提供尽可能多的上下文信息,如模块名、函数名(符号)、偏移量等。
*
* @param address 要格式化的绝对内存地址。
* @return 一个包含地址详细信息的格式化字符串。
*/
public String formatAddressDetails(long address) {
Module module = emulator.getMemory().findModuleByAddress(address);
if (module != null) {
Symbol symbol = module.findClosestSymbolByAddress(address, true);
if (symbol != null) {
GccDemangler demangler = DemanglerFactory.createDemangler();
String demangledName = demangler.demangle(symbol.getName());
long offset = address - symbol.getAddress();
return String.format("[%s] %s + 0x%x (at 0x%x)", module.name, demangledName, offset, address);
} else {
long offset = address - module.base;
return String.format("[%s] sub_%x (at 0x%x)", module.name, offset, address);
}
}
MemRegion region = emulator.getSvcMemory().findRegion(address);
if (region != null) {
return String.format("[%s] (at 0x%x)", region.getName(), address);
}
return String.format("[unknown] (at 0x%x)", address);
}
/**
* 将给定的内存地址格式化为包含详细信息、可读的字符串。
* 这个方法会尝试解析地址,并提供尽可能多的上下文信息,如模块名、函数名(符号)、偏移量等。
*
* @param address 要格式化的绝对内存地址。
* @return 一个包含地址详细信息的格式化字符串。
*/
public String formatAddressDetails(long address) {
Module module = emulator.getMemory().findModuleByAddress(address);
if (module != null) {
Symbol symbol = module.findClosestSymbolByAddress(address, true);
if (symbol != null) {
GccDemangler demangler = DemanglerFactory.createDemangler();
String demangledName = demangler.demangle(symbol.getName());
long offset = address - symbol.getAddress();
return String.format("[%s] %s + 0x%x (at 0x%x)", module.name, demangledName, offset, address);
} else {
long offset = address - module.base;
return String.format("[%s] sub_%x (at 0x%x)", module.name, offset, address);
}
}
MemRegion region = emulator.getSvcMemory().findRegion(address);
if (region != null) {
return String.format("[%s] (at 0x%x)", region.getName(), address);
}
return String.format("[unknown] (at 0x%x)", address);
}
Debugger debugger = emulator.attach();
System.out.println("函数调用关系追踪器已附加,结果将输出到日志文件。");
debugger.traceFunctionCall(null, new FunctionCallListener() {
private int depth = 0;
private String getPrefix(int currentDepth) {
if (currentDepth <= 0) {
return "";
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < currentDepth - 1; i++) {
sb.append("│ ");
}
sb.append("├─ ");
return sb.toString();
}
@Override
public void onCall(Emulator<?> emulator, long callerAddress, long functionAddress) {
String prefix = getPrefix(depth + 1);
String details = emulator.getUnwinder().formatAddressDetails(functionAddress);
traceStream.printf("%sCALL -> %s%n", prefix, details);
depth++;
}
@Override
public void postCall(Emulator<?> emulator, long callerAddress, long functionAddress, Number[] args) {
depth--;
String prefix = getPrefix(depth + 1);
String details = emulator.getUnwinder().formatAddressDetails(functionAddress);
Backend backend = emulator.getBackend();
Number retVal = emulator.is64Bit() ? backend.reg_read(Arm64Const.UC_ARM64_REG_X0) : backend.reg_read(ArmConst.UC_ARM_REG_R0);
long retValLong = retVal.longValue();
String retValFormatted = String.format("0x%x", retValLong);
UnidbgPointer pointer = UnidbgPointer.pointer(emulator, retValLong);
if (pointer != null) {
String cstring = safeReadCString(pointer);
if (isPrintable(cstring)) {
retValFormatted += String.format(" -> \"%s\"", cstring);
}
}
traceStream.printf("%sRET <- %s, ret=%s%n", prefix, details, retValFormatted);
}
});
Debugger debugger = emulator.attach();
System.out.println("函数调用关系追踪器已附加,结果将输出到日志文件。");
debugger.traceFunctionCall(null, new FunctionCallListener() {
private int depth = 0;
private String getPrefix(int currentDepth) {
if (currentDepth <= 0) {
return "";
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < currentDepth - 1; i++) {
sb.append("│ ");
}
sb.append("├─ ");
return sb.toString();
}
@Override
public void onCall(Emulator<?> emulator, long callerAddress, long functionAddress) {
String prefix = getPrefix(depth + 1);
String details = emulator.getUnwinder().formatAddressDetails(functionAddress);
traceStream.printf("%sCALL -> %s%n", prefix, details);
depth++;
}
@Override
public void postCall(Emulator<?> emulator, long callerAddress, long functionAddress, Number[] args) {
depth--;
String prefix = getPrefix(depth + 1);
String details = emulator.getUnwinder().formatAddressDetails(functionAddress);
Backend backend = emulator.getBackend();
Number retVal = emulator.is64Bit() ? backend.reg_read(Arm64Const.UC_ARM64_REG_X0) : backend.reg_read(ArmConst.UC_ARM_REG_R0);
long retValLong = retVal.longValue();
String retValFormatted = String.format("0x%x", retValLong);
UnidbgPointer pointer = UnidbgPointer.pointer(emulator, retValLong);
if (pointer != null) {
String cstring = safeReadCString(pointer);
if (isPrintable(cstring)) {
retValFormatted += String.format(" -> \"%s\"", cstring);
}
}
traceStream.printf("%sRET <- %s, ret=%s%n", prefix, details, retValFormatted);
}
});
package com.example.ndkdemo;
import com.github.unidbg.*;
import com.github.unidbg.arm.backend.Backend;
import com.github.unidbg.arm.backend.CodeHook;
import com.github.unidbg.arm.backend.UnHook;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.arm.context.Arm64RegisterContext;
import com.github.unidbg.debugger.BreakPointCallback;
import com.github.unidbg.debugger.Debugger;
import com.github.unidbg.debugger.FunctionCallListener;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmClass;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.pointer.UnidbgPointer;
import unicorn.Arm64Const;
import unicorn.ArmConst;
import java.io.*;
public class MainActivity {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
private final DvmClass security;
private final boolean logging;
private long instructionCount = 0;
MainActivity(boolean logging) {
this.logging = logging;
emulator = AndroidEmulatorBuilder.for64Bit()
.setProcessName("com.example.ndkdemo")
.addBackendFactory(new Unicorn2Factory(true))
.build();
final Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM();
vm.setVerbose(logging);
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/zj/wuaipojie/util/libndkdemo.so"), false);
dm.callJNI_OnLoad(emulator);
module = dm.getModule();
security = vm.resolveClass("com/example/ndkdemo/MainActivity");
emulator.getBackend().hook_add_new(new CodeHook() {
@Override
public void hook(Backend backend, long address, int size, Object user) {
instructionCount++;
}
@Override
public void onAttach(UnHook unHook) {}
@Override
public void detach() {}
}, module.base, module.base + module.size, null);
attachTraceAndInspectX0(module.base + 0x15F8);
}
/**
* 【模块化功能】: 附加一个函数调用追踪器 (traceFunctionCall)。
* 此功能会监听所有的函数调用,并以树状结构打印出调用关系。
* @param traceStream 日志输出流
*/
public void attachFunctionCallTracer(final PrintStream traceStream) {
Debugger debugger = emulator.attach();
System.out.println("函数调用关系追踪器已附加,结果将输出到日志文件。");
debugger.traceFunctionCall(null, new FunctionCallListener() {
private int depth = 0;
private String getPrefix(int currentDepth) {
if (currentDepth <= 0) {
return "";
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < currentDepth - 1; i++) {
sb.append("│ ");
}
sb.append("├─ ");
return sb.toString();
}
@Override
public void onCall(Emulator<?> emulator, long callerAddress, long functionAddress) {
String prefix = getPrefix(depth + 1);
String details = emulator.getUnwinder().formatAddressDetails(functionAddress);
traceStream.printf("%sCALL -> %s%n", prefix, details);
depth++;
}
@Override
public void postCall(Emulator<?> emulator, long callerAddress, long functionAddress, Number[] args) {
depth--;
String prefix = getPrefix(depth + 1);
String details = emulator.getUnwinder().formatAddressDetails(functionAddress);
Backend backend = emulator.getBackend();
Number retVal = emulator.is64Bit() ? backend.reg_read(Arm64Const.UC_ARM64_REG_X0) : backend.reg_read(ArmConst.UC_ARM_REG_R0);
long retValLong = retVal.longValue();
String retValFormatted = String.format("0x%x", retValLong);
UnidbgPointer pointer = UnidbgPointer.pointer(emulator, retValLong);
if (pointer != null) {
String cstring = safeReadCString(pointer);
if (isPrintable(cstring)) {
retValFormatted += String.format(" -> \"%s\"", cstring);
}
}
traceStream.printf("%sRET <- %s, ret=%s%n", prefix, details, retValFormatted);
}
});
}
/**
* 在指定地址设置断点,实现对一个函数调用的追踪,并在函数返回后检查 x0 寄存器的内容。
*
* @param callAddress 函数调用指令(例如 BL, B)的绝对地址
*/
private void attachTraceAndInspectX0(long callAddress) {
final TraceHook[] traceHook = new TraceHook[1];
emulator.attach().addBreakPoint(callAddress, (emu, address) -> {
traceHook[0] = emu.traceCode(module.base, module.base + module.size);
return true;
});
long returnAddress = callAddress + 4;
emulator.attach().addBreakPoint(returnAddress, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emu, long address) {
if (traceHook[0] != null) {
traceHook[0].stopTrace();
System.out.println();
}
Arm64RegisterContext ctx = emu.getContext();
long x0 = ctx.getXLong(0);
System.out.printf("[+] 检查地址: 0x%x, x0寄存器值: 0x%x\n", address, x0);
UnidbgPointer pointer = UnidbgPointer.pointer(emu, x0);
if (pointer == null) {
System.out.println("[-] x0的值不是一个有效的指针或指向未映射的内存");
return true;
}
String cstring = safeReadCString(pointer);
if (cstring != null && isPrintable(cstring)) {
System.out.println("[+] x0指向的字符串: " + cstring);
} else {
System.out.println("[-] x0指向的内容不是一个可打印的字符串");
}
int dumpSize = 256;
byte[] data = pointer.getByteArray(0, dumpSize);
System.out.println("[+] x0指向内存的HexDump (前" + dumpSize + "字节):");
System.out.println(prettyHexDump(data, x0));
System.out.println("--- x0寄存器检查结束 ---\n");
return true;
}
});
}
private static String safeReadCString(UnidbgPointer p) {
try {
return p.getString(0);
} catch (Exception e) {
return null;
}
}
private static boolean isPrintable(String s) {
if (s == null || s.isEmpty()) {
return false;
}
int printableChars = 0;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if ((c >= 32 && c <= 126) || Character.isWhitespace(c)) {
printableChars++;
}
}
return s.length() >= 2 && (double) printableChars / s.length() > 0.8;
}
private static String prettyHexDump(byte[] data, long baseAddr) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < data.length; i += 16) {
sb.append(String.format("%016x: ", baseAddr + i));
StringBuilder hexPart = new StringBuilder();
StringBuilder asciiPart = new StringBuilder();
for (int j = 0; j < 16; j++) {
if (i + j < data.length) {
byte b = data[i + j];
hexPart.append(String.format("%02x ", b));
char c = (b >= 32 && b <= 126) ? (char) b : '.';
asciiPart.append(c);
} else {
hexPart.append(" ");
}
if (j == 7) {
hexPart.append(" ");
}
}
sb.append(hexPart).append(" |").append(asciiPart).append("|\n");
}
return sb.toString();
}
private void crack() {
emulator.traceCode();
boolean result = security.callStaticJniMethodBoolean(emulator, "check", "1234567");
System.out.println("函数调用结束,返回结果: " + result);
}
void destroy() {
try {
emulator.close();
if (logging) {
System.out.println("模拟器已成功关闭");
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
MainActivity test = new MainActivity(false);
String traceFile = "unidbg-android/src/test/resources/traceFunctions.txt";
try (PrintStream traceStream = new PrintStream(new FileOutputStream(traceFile), true)) {
test.attachFunctionCallTracer(traceStream);
test.crack();
} catch (IOException e) {
e.printStackTrace();
} finally {
test.destroy();
System.out.println("总共执行ARM指令数: " + test.instructionCount);
}
}
}
package com.example.ndkdemo;
import com.github.unidbg.*;
import com.github.unidbg.arm.backend.Backend;
import com.github.unidbg.arm.backend.CodeHook;
import com.github.unidbg.arm.backend.UnHook;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.arm.context.Arm64RegisterContext;
import com.github.unidbg.debugger.BreakPointCallback;
import com.github.unidbg.debugger.Debugger;
import com.github.unidbg.debugger.FunctionCallListener;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmClass;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.pointer.UnidbgPointer;
import unicorn.Arm64Const;
import unicorn.ArmConst;
import java.io.*;
public class MainActivity {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
private final DvmClass security;
private final boolean logging;
private long instructionCount = 0;
MainActivity(boolean logging) {
this.logging = logging;
emulator = AndroidEmulatorBuilder.for64Bit()
.setProcessName("com.example.ndkdemo")
.addBackendFactory(new Unicorn2Factory(true))
.build();
final Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM();
vm.setVerbose(logging);
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/zj/wuaipojie/util/libndkdemo.so"), false);
dm.callJNI_OnLoad(emulator);
module = dm.getModule();
security = vm.resolveClass("com/example/ndkdemo/MainActivity");
emulator.getBackend().hook_add_new(new CodeHook() {
@Override
public void hook(Backend backend, long address, int size, Object user) {
instructionCount++;
}
@Override
public void onAttach(UnHook unHook) {}
@Override
public void detach() {}
}, module.base, module.base + module.size, null);
attachTraceAndInspectX0(module.base + 0x15F8);
}
/**
* 【模块化功能】: 附加一个函数调用追踪器 (traceFunctionCall)。
* 此功能会监听所有的函数调用,并以树状结构打印出调用关系。
* @param traceStream 日志输出流
*/
public void attachFunctionCallTracer(final PrintStream traceStream) {
Debugger debugger = emulator.attach();
System.out.println("函数调用关系追踪器已附加,结果将输出到日志文件。");
debugger.traceFunctionCall(null, new FunctionCallListener() {
private int depth = 0;
private String getPrefix(int currentDepth) {
if (currentDepth <= 0) {
return "";
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < currentDepth - 1; i++) {
sb.append("│ ");
}
sb.append("├─ ");
return sb.toString();
}
@Override
public void onCall(Emulator<?> emulator, long callerAddress, long functionAddress) {
String prefix = getPrefix(depth + 1);
String details = emulator.getUnwinder().formatAddressDetails(functionAddress);
traceStream.printf("%sCALL -> %s%n", prefix, details);
depth++;
}
@Override
public void postCall(Emulator<?> emulator, long callerAddress, long functionAddress, Number[] args) {
depth--;
String prefix = getPrefix(depth + 1);
String details = emulator.getUnwinder().formatAddressDetails(functionAddress);
Backend backend = emulator.getBackend();
Number retVal = emulator.is64Bit() ? backend.reg_read(Arm64Const.UC_ARM64_REG_X0) : backend.reg_read(ArmConst.UC_ARM_REG_R0);
long retValLong = retVal.longValue();
String retValFormatted = String.format("0x%x", retValLong);
UnidbgPointer pointer = UnidbgPointer.pointer(emulator, retValLong);
if (pointer != null) {
String cstring = safeReadCString(pointer);
if (isPrintable(cstring)) {
retValFormatted += String.format(" -> \"%s\"", cstring);
}
}
traceStream.printf("%sRET <- %s, ret=%s%n", prefix, details, retValFormatted);
}
});
}
/**
* 在指定地址设置断点,实现对一个函数调用的追踪,并在函数返回后检查 x0 寄存器的内容。
*
* @param callAddress 函数调用指令(例如 BL, B)的绝对地址
*/
private void attachTraceAndInspectX0(long callAddress) {
final TraceHook[] traceHook = new TraceHook[1];
emulator.attach().addBreakPoint(callAddress, (emu, address) -> {
traceHook[0] = emu.traceCode(module.base, module.base + module.size);
return true;
});
long returnAddress = callAddress + 4;
emulator.attach().addBreakPoint(returnAddress, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emu, long address) {
if (traceHook[0] != null) {
traceHook[0].stopTrace();
System.out.println();
}
Arm64RegisterContext ctx = emu.getContext();
long x0 = ctx.getXLong(0);
System.out.printf("[+] 检查地址: 0x%x, x0寄存器值: 0x%x\n", address, x0);
UnidbgPointer pointer = UnidbgPointer.pointer(emu, x0);
if (pointer == null) {
System.out.println("[-] x0的值不是一个有效的指针或指向未映射的内存");
return true;
}
String cstring = safeReadCString(pointer);
if (cstring != null && isPrintable(cstring)) {
System.out.println("[+] x0指向的字符串: " + cstring);
} else {
System.out.println("[-] x0指向的内容不是一个可打印的字符串");
}
int dumpSize = 256;
byte[] data = pointer.getByteArray(0, dumpSize);
System.out.println("[+] x0指向内存的HexDump (前" + dumpSize + "字节):");
System.out.println(prettyHexDump(data, x0));
System.out.println("--- x0寄存器检查结束 ---\n");
return true;
}
});
}
private static String safeReadCString(UnidbgPointer p) {
try {
return p.getString(0);
} catch (Exception e) {
return null;
}
}
private static boolean isPrintable(String s) {
if (s == null || s.isEmpty()) {
return false;
}
int printableChars = 0;
传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!