unidbg

zhkl0228Apache-2.0活跃维护

用 Java 实现跨平台 CPU 模拟,可直接加载并运行 Android 原生 so 库,也支持实验性 iOS 模拟,常用于逆向分析与脱壳调试。

Allows you to emulate an Android native library, and an experimental iOS emulation

v0.9.9发布于 2026-03-05 05:00 UTC5,313star +6 / 7 天1,200fork同步于 2026-11-11 05:34 UTC

怎么上手

由 README 提炼,执行前请确认
README 未给出安装命令
硬件要求按 README 说明

README 未说明具体显存或内存要求;加速后端需 Apple M1 hypervisor 或树莓派 B4 上的 Linux KVM,其余场景走 unicorn/dynarmic 软件模拟。

支持 Apple Silicon
下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

模拟执行 Android 原生库并实验性模拟 iOS 的逆向分析框架

用 Java 实现跨平台 CPU 模拟,可直接加载并运行 Android 原生 so 库,也支持实验性 iOS 模拟,常用于逆向分析与脱壳调试。

核心能力

  • 模拟 JNI_OnLoad 与 JavaVM/JNIEnv 调用
  • 支持 ARM32/ARM64 及 Dobby、xHook 内联与导入 hook
  • 内置 unicorn 控制台调试器、gdb stub 与指令/内存追踪
  • 支持 dynarmic、Apple M1 hypervisor、Linux KVM 加速后端
  • 通过 MCP 服务器让 AI 工具断点调试与调用自定义工具

容易踩的坑

README 未说明依赖版本与构建方式,需自行参考项目源码;iOS 模拟需准备 IPA 与 rootfs 目录;MCP 默认端口为 9239,需在 AI 工具中手动配置连接。

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

5 个版本v0.9.9
  1. v0.9.9最新正式版2026-03-05 · 2026-03-05 05:00 UTC

    Release 0.9.9

  2. v0.9.82024-09-05 · 2024-09-05 06:49 UTC
    查看更新说明

    Release 0.9.8

  3. v0.9.72022-07-30 · 2022-07-30 11:22 UTC
    查看更新说明

    Release 0.9.7

  4. v0.9.62022-01-31 · 2022-01-31 09:41 UTC
    查看更新说明

    Release 0.9.6

  5. v0.9.52021-09-21 · 2021-09-21 12:12 UTC
    查看更新说明

    Release 0.9.5

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    unidbg

    Allows you to emulate an Android native library, and an experimental iOS emulation.

    This is an educational project to learn more about the ELF/MachO file format and ARM assembly.

    Use it at your own risk !

    Features

    • Support MCP (Model Context Protocol) for AI-assisted debugging with Cursor and other AI tools.
    • Emulation of the JNI Invocation API so JNI_OnLoad can be called.
    • Support JavaVM, JNIEnv.
    • Emulation of syscalls instruction.
    • Support ARM32 and ARM64.
    • Inline hook, thanks to Dobby.
    • Android import hook, thanks to xHook.
    • iOS fishhook and substrate and whale hook.
    • unicorn backend support simple console debugger, gdb stub, instruction trace, memory read/write trace.
    • Support iOS objc and swift runtime.
    • Support dynarmic fast backend.
    • Support Apple M1 hypervisor, the fastest ARM64 backend.
    • Support Linux KVM backend with Raspberry Pi B4.
    • Memory leak detection for emulated native code with guest backtrace and host stack trace.

    MCP Debugger (AI Integration)

    unidbg supports Model Context Protocol (MCP) for AI-assisted debugging. When the debugger is active, type mcp in the console to start an MCP server that AI tools (e.g. Cursor) can connect to.

    Quick Start

    unidbg MCP has two operating modes:

    Mode 1: Breakpoint Debug — Attach the debugger and run your code. When a breakpoint is hit, Breaker.debug() pauses the emulator — type mcp in the console to start MCP server and let AI assist with analysis. All debugging tools are available (registers, memory, disassembly, stepping, tracing, etc). After resuming, if another breakpoint is hit the debugger pauses again. Once execution completes without hitting a breakpoint, the process exits and MCP shuts down.

    Debugger debugger = emulator.attach();
    debugger.addBreakPoint(address);
    // run your emulation logic — debugger pauses when breakpoint is hit
    

    Mode 2: Custom Tools (Repeatable) — Use McpToolkit to register custom tools and let AI re-run target functions with different parameters. The native library is loaded once; after each execution the process stays alive and MCP remains active for the next run.

    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());
    

    When the debugger breaks, type mcp (or mcp 9239 to specify port) in the console. Then add to Cursor MCP settings:

    {
      "mcpServers": {
        "unidbg-mcp-server": {
          "url": "http://localhost:9239/sse"
        }
      }
    }
    

    Available MCP Tools

    Status & Info

    ToolDescription
    check_connectionEmulator status: Family, architecture, backend capabilities, isRunning, loaded modules
    list_modules / get_module_infoList loaded modules, get detail including exported symbol count and dependencies
    list_exportsList exported/dynamic symbols of a module with optional filter and C++ demangling
    find_symbolFind symbol by name or find nearest symbol at address
    get_threadsList all threads/tasks in the emulator

    Registers & Disassembly

    ToolDescription
    get_registers / get_register / set_registerRead/write CPU registers
    disassembleDisassemble instructions at address (branch targets auto-annotated with symbol names)
    assembleAssemble instruction text to machine code
    get_callstackGet current call stack (backtrace)

    Memory

    ToolDescription
    read_memory / write_memoryRead/write raw memory bytes
    read_string / read_std_stringRead C string or C++ std::string (with SSO detection)
    read_pointerRead pointer chain with symbol resolution
    read_typedRead memory as typed values (int8–int64, float, double, pointer)
    search_memorySearch memory for byte patterns with scope/permission filters
    list_memory_mapList all memory mappings with permissions
    allocate_memory / free_memory / list_allocationsAllocate (malloc/mmap) with optional initial data, free, and track memory blocks
    patchWrite assembled instructions to memory

    Breakpoints & Execution

    ToolDescription
    add_breakpoint / add_breakpoint_by_symbol / add_breakpoint_by_offsetAdd breakpoints by address, symbol, or module+offset
    remove_breakpoint / list_breakpointsRemove or list breakpoints (with disassembly)
    continue_executionResume execution. Use poll_events to wait for breakpoint_hit or execution_completed
    step_over / step_into / step_outStep over, into (N instructions), or out of function
    next_blockBreak at next basic block (Unicorn only)
    step_until_mnemonicBreak at next instruction matching mnemonic, e.g. bl, ret (Unicorn only)
    poll_eventsPoll for breakpoint_hit, execution_completed, trace events

    Tracing

    ToolDescription
    trace_codeTrace instructions with register read/write values (regs_read, prev_write)
    trace_read / trace_writeTrace memory reads/writes in address range

    Function Calls

    ToolDescription
    call_functionCall native function by address with typed arguments (hex, string, bytes, null). Returns value with symbol resolution and memory preview
    call_symbolCall exported function by module + symbol name, e.g. libc.so + malloc

    iOS Only (available when Family=iOS)

    ToolDescription
    inspect_objc_msgInspect objc_msgSend call: show receiver class name and selector, e.g. -[NSString length]
    get_objc_class_nameGet ObjC class name of an object at a given address (pure memory parsing, no state change)
    dump_objc_classDump ObjC class definition (properties, methods, protocols, ivars)
    dump_gpb_protobufDump GPB protobuf message schema as .proto format (64-bit only)

    Custom MCP Tools

    Use McpToolkit to register custom tools, each implementing the McpTool interface. This replaces manual if-else dispatch with clean, self-contained tool classes. By this point the native library is fully loaded (JNI_OnLoad / entry point already executed), so the code inside each tool's execute() is the target function logic to analyze. AI can set breakpoints and traces before triggering a custom tool, then inspect execution results across different inputs without restarting the process.

    Android Example — See Utilities64.java for an Android JNI example with custom MCP tools:

    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 Example — See IpaLoaderTest.java for an iOS IPA loading example with custom MCP tools:

    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());
    

    Once the MCP server is started, AI can call these tools via MCP to run emulations with custom parameters, set breakpoints, trace execution, and inspect results — all without restarting the process.

    Low-level API: You can also use Debugger.addMcpTool() + Debugger.run(DebugRunnable) directly for full control. McpToolkit is a higher-level wrapper that eliminates if-else dispatch.

    Memory Leak Detection

    Track guest-side memory allocations (mmap/munmap/brk) to detect leaks in emulated native code. Use try-with-resources — tracking starts on creation, and the leak report is printed automatically on close.

    try (MemoryTracker tracker = emulator.traceMemoryLeaks()) {
        module.callFunction(emulator, "targetFunction", arg1, arg2);
    }
    

    Each leaked block includes guest ARM backtrace (module+offset+symbol) and host Java stack trace. Sample output:

    === 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)
      ...
    

    You can also access the report programmatically before close:

    try (MemoryTracker tracker = emulator.traceMemoryLeaks()) {
        module.callFunction(emulator, "targetFunction", arg1, arg2);
        List<AllocationRecord> leaks = tracker.getLeaks();
        assert leaks.isEmpty() : "Memory leak detected!";
    }
    

    Worker Pool

    A thread-safe object pool for reusing emulator instances across multiple threads, avoiding the overhead of repeated initialization.

    • Lazy initialization — Workers are created on-demand only when the pool is empty, not upfront.
    • Max limit — Total alive workers (borrowed + idle) never exceeds the configured maximum.
    • Idle cleanup — Workers idle for longer than the timeout (default 10 minutes) are automatically destroyed by the management thread.
    • Min idle — A minimum number of workers (default 1) is always kept alive, even when idle.

    1. Implement a Worker

    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
        }
    }
    

    2. Create Pool, Borrow, and Close

    // 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
    

    See TTEncryptWorker.java for a complete example.

    Examples

    Simple tests under src/test directory:





    More tests:

    License

    Thanks

    Stargazers over time

    Stargazers over time