允许您模拟 Android 原生库,以及实验性的 iOS 模拟。
这是一个教育项目,用于更深入地了解 ELF/MachO 文件格式和 ARM 汇编。
使用风险自负!
unidbg 支持 模型上下文协议 (MCP) 进行 AI 辅助调试。当调试器激活时,在控制台输入 mcp 即可启动 MCP 服务器,AI 工具(例如 Cursor)可以连接该服务器。
unidbg MCP 有两种运行模式:
模式 1:断点调试 —— 附加调试器并运行你的代码。当命中断点时,Breaker.debug() 会暂停模拟器——在控制台输入 mcp 启动 MCP 服务器,让 AI 协助分析。所有调试工具均可用(寄存器、内存、反汇编、单步执行、跟踪等)。恢复执行后,若再次命中断点,调试器会再次暂停。当执行完成且未命中断点时,进程退出,MCP 关闭。
Debugger debugger = emulator.attach();
debugger.addBreakPoint(address);
// run your emulation logic — debugger pauses when breakpoint is hit
模式 2:自定义工具(可重复) —— 使用 McpToolkit 注册自定义工具,让 AI 使用不同参数重新运行目标函数。原生库仅加载一次;每次执行后进程保持存活,MCP 继续保持活动状态以便下一次运行。
McpToolkit toolkit = new McpToolkit();
toolkit.addTool(new McpTool() {
@Override public String name() { return "encrypt"; }
@Override public String description() { return "Run encryption"; }
@Override public String[] paramNames() { return new String[]{"input"}; }
@Override public void execute(String[] params) {
String input = params.length > 0 ? params[0] : "default";
// call encryption with input
}
});
toolkit.run(emulator.attach());
当调试器中断时,在控制台输入 mcp(或 mcp 9239 指定端口)。然后在 Cursor MCP 设置中添加:
{
"mcpServers": {
"unidbg-mcp-server": {
"url": "http://localhost:9239/sse"
}
}
}
状态与信息
寄存器与反汇编
内存
断点与执行
跟踪
| 工具 | 描述 |
|---|---|
trace_code | 跟踪指令及其寄存器读写值(regs_read、prev_write) |
trace_read / trace_write | 跟踪指定地址范围内的内存读/写 |
函数调用
| 工具 | 描述 |
|---|---|
call_function | 按地址调用原生函数,支持类型化参数(十六进制、字符串、字节、null)。返回值带符号解析和内存预览 |
call_symbol | 按模块 + 符号名称调用导出函数,例如 libc.so + malloc |
仅限 iOS(当 Family=iOS 时可用)
| 工具 | 描述 |
|---|---|
inspect_objc_msg |
使用 McpToolkit 注册自定义工具,每个工具实现 McpTool 接口。这用简洁且自包含的工具类取代了手动 if-else 分发。此时原生库已完全加载(JNI_OnLoad / 入口点已执行),因此每个工具 execute() 内的代码就是要分析的目标函数逻辑。AI 可以在触发自定义工具之前设置断点和跟踪,然后检查不同输入下的执行结果,而无需重启进程。
Android 示例 —— 参见 Utilities64.java,这是一个带自定义 MCP 工具的 Android JNI 示例:
DalvikModule dm = vm.loadLibrary(new File("libtmessages.29.so"), true);
dm.callJNI_OnLoad(emulator);
cUtilities = vm.resolveClass("org/telegram/messenger/Utilities");
McpToolkit toolkit = new McpToolkit();
toolkit.addTool(new McpTool() {
@Override public String name() { return "aesCbc"; }
@Override public String description() { return "Run AES-CBC encryption on input data"; }
@Override public String[] paramNames() { return new String[]{"input"}; }
@Override public void execute(String[] params) {
byte[] input = params.length > 0 ? params[0].getBytes() : new byte[16];
aesCbcEncryptionByteArray(input);
}
});
toolkit.addTool(new McpTool() {
@Override public String name() { return "aesCtr"; }
@Override public String description() { return "Run AES-CTR decryption on input data"; }
@Override public String[] paramNames() { return new String[]{"input"}; }
@Override public void execute(String[] params) {
byte[] input = params.length > 0 ? params[0].getBytes() : new byte[16];
aesCtrDecryptionByteArray(input);
}
});
toolkit.addTool(new McpTool() {
@Override public String name() { return "pbkdf2"; }
@Override public String description() { return "Run PBKDF2 key derivation"; }
@Override public String[] paramNames() { return new String[]{"password", "iterations"}; }
@Override public void execute(String[] params) {
String password = params.length > 0 ? params[0] : "123456";
int iterations = params.length > 1 ? Integer.parseInt(params[1]) : 100000;
pbkdf2(password.getBytes(), iterations);
}
});
toolkit.run(emulator.attach());
iOS 示例 —— 参见 IpaLoaderTest.java,这是一个带自定义 MCP 工具的 iOS IPA 加载示例:
IpaLoader ipaLoader = new IpaLoader64(ipa, new File("target/rootfs/ipa"));
LoadedIpa loader = ipaLoader.load(this);
emulator = loader.getEmulator();
loader.callEntry();
module = loader.getExecutable();
McpToolkit toolkit = new McpToolkit();
toolkit.addTool(new McpTool() {
@Override public String name() { return "dumpClass"; }
@Override public String description() { return "Dump an ObjC class definition by name"; }
@Override public String[] paramNames() { return new String[]{"className"}; }
@Override public void execute(String[] params) {
String className = params.length > 0 ? params[0] : "AppDelegate";
IClassDumper classDumper = ClassDumper.getInstance(emulator);
System.out.println("dumpClass(" + className + "):\n" + classDumper.dumpClass(className));
}
});
toolkit.addTool(new McpTool() {
@Override public String name() { return "readVersion"; }
@Override public String description() { return "Read the TelegramCoreVersionString from the executable"; }
@Override public void execute(String[] params) {
Symbol sym = module.findSymbolByName("_TelegramCoreVersionString");
if (sym != null) {
Pointer pointer = UnidbgPointer.pointer(emulator, sym.getAddress());
if (pointer != null) {
System.out.println("_TelegramCoreVersionString=" + pointer.getString(0));
}
}
}
});
toolkit.run(emulator.attach());
一旦 MCP 服务器启动,AI 就可以通过 MCP 调用这些工具,以自定义参数运行模拟、设置断点、跟踪执行并检查结果——全程无需重启进程。
底层 API:你还可以直接使用
Debugger.addMcpTool()+Debugger.run(DebugRunnable)进行全面控制。McpToolkit是更高级的封装,消除了 if-else 分发。
跟踪 guest 侧内存分配(mmap/munmap/brk),以检测模拟原生代码中的泄漏。使用 try-with-resources——跟踪在创建时开始,并在关闭时自动打印泄漏报告。
try (MemoryTracker tracker = emulator.traceMemoryLeaks()) {
module.callFunction(emulator, "targetFunction", arg1, arg2);
}
每个泄漏的内存块都包含 guest ARM 回溯(模块+偏移量+符号)和宿主 Java 堆栈跟踪。示例输出:
=== Memory Leak Report ===
Tracking duration: 42ms
Total allocations: 5
Total deallocations: 3
Leaked blocks: 2
Total leaked size: 32768 bytes (32.0 KB)
--- Leak #1 ---
Address: 0x40001000, Size: 16384 (16.0 KB), Perms: rw-
Guest Backtrace:
#0 0x40123456 libexample.so+0x3456 (malloc+0x12)
#1 0x40124000 libexample.so+0x4000 (doSomething+0x48)
Host Stack Trace:
com.github.unidbg.linux.AndroidElfLoader.mmap2(AndroidElfLoader.java:785)
...
你也可以在关闭之前以编程方式访问该报告:
try (MemoryTracker tracker = emulator.traceMemoryLeaks()) {
module.callFunction(emulator, "targetFunction", arg1, arg2);
List<AllocationRecord> leaks = tracker.getLeaks();
assert leaks.isEmpty() : "Memory leak detected!";
}
一个线程安全的对象池,用于在多线程间复用模拟器实例,避免重复初始化的开销。
public class MyWorker implements Worker {
private final AndroidEmulator emulator;
public MyWorker() {
emulator = AndroidEmulatorBuilder.for64Bit().build();
// load .so, call JNI_OnLoad, etc.
}
@Override
public void destroy() {
emulator.close();
}
public byte[] doWork(byte[] input) {
// call native methods and return the result
}
}
// Create a worker pool (max = CPU cores, lazy-initialized)
WorkerPool pool = WorkerPoolFactory.create(MyWorker::new);
// Or specify max workers explicitly
// WorkerPool pool = WorkerPoolFactory.create(MyWorker::new, 4);
// Optional: customize idle timeout (default 10 minutes, minimum 1 minute)
pool.setIdleTimeout(30); // idle workers destroyed after 30 minutes
// Optional: customize minimum kept-alive workers (default 1, minimum 1)
pool.setMinIdle(2); // always keep at least 2 workers alive
// Optional: pre-create workers eagerly (default 0, fully lazy)
pool.setInitialSize(4); // eagerly create 4 workers on startup
// Concurrent invocation from multiple threads
ExecutorService executor = Executors.newFixedThreadPool(100);
for (int i = 0; i < 100; i++) {
executor.submit(() -> {
try (WorkerLoan<MyWorker> loan = pool.borrow(1, TimeUnit.MINUTES)) {
if (loan != null) {
byte[] result = loan.get().doWork(input);
}
} // worker is automatically returned to the pool
});
}
executor.shutdown();
executor.awaitTermination(10, TimeUnit.MINUTES);
pool.close(); // destroy all workers and release resources
完整示例参见 TTEncryptWorker.java。
src/test 目录下的简单测试:

更多测试:
| 工具 | 描述 |
|---|
check_connection | 模拟器状态:Family、架构、后端能力、isRunning、已加载模块 |
list_modules / get_module_info | 列出已加载模块,获取包括导出符号数量和依赖项在内的详细信息 |
list_exports | 列出模块的导出/动态符号,支持可选过滤和 C++ 符号还原 |
find_symbol | 按名称查找符号,或在地址处查找最近的符号 |
get_threads | 列出模拟器中的所有线程/任务 |
| 工具 | 描述 |
|---|
get_registers / get_register / set_register | 读写 CPU 寄存器 |
disassemble | 对指定地址的指令进行反汇编(分支目标自动标注符号名称) |
assemble | 将指令文本汇编为机器码 |
get_callstack | 获取当前调用堆栈(回溯) |
| 工具 | 描述 |
|---|
read_memory / write_memory | 读写原始内存字节 |
read_string / read_std_string | 读取 C 字符串或 C++ std::string(带 SSO 检测) |
read_pointer | 读取带符号解析的指针链 |
read_typed | 以类型化值读取内存(int8–int64、float、double、pointer) |
search_memory | 在内存中搜索字节模式,支持范围/权限过滤 |
list_memory_map | 列出所有内存映射及其权限 |
allocate_memory / free_memory / list_allocations | 分配(malloc/mmap)可附带初始数据,释放并跟踪内存块 |
patch | 将汇编后的指令写入内存 |
| 工具 | 描述 |
|---|
add_breakpoint / add_breakpoint_by_symbol / add_breakpoint_by_offset | 按地址、符号或模块+偏移量添加断点 |
remove_breakpoint / list_breakpoints | 移除或列出断点(含反汇编) |
continue_execution | 恢复执行。使用 poll_events 等待 breakpoint_hit 或 execution_completed |
step_over / step_into / step_out | 单步跳过、单步进入(N 条指令)或单步跳出函数 |
next_block | 在下一个基本块处中断(仅限 Unicorn) |
step_until_mnemonic | 在下一个匹配助记符的指令处中断,例如 bl、ret(仅限 Unicorn) |
poll_events | 轮询 breakpoint_hit、execution_completed、trace 事件 |
检查 objc_msgSend 调用:显示接收者类名和选择器,例如 -[NSString length] |
get_objc_class_name | 获取给定地址处对象的 ObjC 类名(纯内存解析,不改变状态) |
dump_objc_class | 导出 ObjC 类定义(属性、方法、协议、实例变量) |
dump_gpb_protobuf | 以 .proto 格式导出 GPB protobuf 消息结构(仅限 64 位) |