Library / SDK
zhkl0228/unidbg avatar
zhkl0228/unidbg

unidbg: Emulating Android and iOS Native Libraries in Java

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

5,211 stars1,187 forksJavaApache-2.0

At a glance

What is it?
unidbg is a Java framework for loading and executing ARM32 and ARM64 native code from Android .so files and, experimentally, iOS binaries, without a device or a full system emulator. It is an educational project with a wide backend matrix, an MCP debugger for AI tools, and a clear set of constraints around what it does not emulate.
Who is it for?
unidbg fits engineers who need to call a specific exported function inside an Android or iOS native library from a JVM test harness, or who want instruction-level tracing without a physical device. It does not fit anyone looking for a full Android system emulator or a supported commercial tool: the README calls it educational and says to use it at your own risk.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 8 days ago.
What is it written in?
Mainly Java, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

What unidbg actually replaces

The problem unidbg addresses is narrow and concrete: you have a compiled native library, typically an Android .so, and you want to call one of its exported functions from Java without booting an emulator image or attaching a physical device. The README frames the project as a way to emulate an Android native library, with iOS emulation described as experimental. It also states plainly that this is an educational project to learn more about the ELF/MachO file format and ARM assembly, and that you use it at your own risk. That framing matters when you decide whether it belongs in a build pipeline.

The audience is therefore engineers doing reverse engineering, malware analysis, protocol research, or automated testing of a native routine that would otherwise require a rooted device or a full AVD. If your goal is to run an Android app end to end, including the framework, the graphics stack and the system services, unidbg is the wrong layer. It emulates the native code and the JNI surface around it, not the operating system.

JNI emulation and the backend matrix

The core mechanism is a JNI Invocation API emulation, which is what allows JNI_OnLoad to be called so the library initialises the way it would inside a real process. unidbg provides JavaVM and JNIEnv emulation on top of that, plus emulation of syscall instructions, so a library that issues system calls does not simply fault. The README lists ARM32 and ARM64 as the supported instruction sets.

Execution is delegated to one of several backends, and the choice is the main performance and compatibility decision. The unicorn backend (a fork maintained under the same author) supports a simple console debugger, a gdb stub, instruction trace, and memory read/write trace. dynarmic is described as a fast backend. The Apple M1 hypervisor is described as the fastest ARM64 backend, and there is Linux KVM support with Raspberry Pi B4. Those last two are hardware-assisted paths, so they are only available on the matching host. Several debugger features are backend-specific: the README marks next_block, step_until_mnemonic, and the trace tools as Unicorn only, which means a faster backend can cost you debugging granularity.

Hooking is handled by external libraries rather than reimplemented. Inline hook comes from Dobby, Android import hook from xHook, and the iOS side uses fishhook, substrate and whale. The README also lists iOS objc and swift runtime support, and memory leak detection for emulated native code that reports a guest backtrace alongside a host stack trace. That last feature is unusual for this class of tool and is aimed at people debugging their own native code rather than only inspecting someone else's.

Running it: attach, break, then type mcp

The README's worked example is the MCP debugger, and it is the most concrete usage path in the material. You attach a debugger to the emulator and set a breakpoint by address:

Debugger debugger = emulator.attach(); debugger.addBreakPoint(address);

When the breakpoint is hit, Breaker.debug() pauses the emulator. At that point you type mcp in the console to start the MCP server, optionally with a port such as mcp 9239. The client side is a JSON block added to Cursor's MCP settings, pointing at http://localhost:9239/sse under an mcpServers entry named unidbg-mcp-server.

The README describes two operating modes. In breakpoint debug mode, the emulator pauses at each breakpoint, MCP stays available while paused, and once execution completes without hitting a breakpoint the process exits and MCP shuts down. In the custom tools mode, you register tools through McpToolkit by implementing the McpTool interface with name(), description(), paramNames() and execute(String[] params), then call toolkit.run(emulator.attach()). Here the native library is loaded once and the process stays alive after each execution, so an AI client can re-run a target function with different parameters without paying the load cost again. The README notes that by the time a tool's execute() runs, JNI_OnLoad or the entry point has already executed, so the code inside execute() is the target logic itself.

The tool surface exposed over MCP is broad and worth reading as a capability list: register read and write, disassemble and assemble, callstack, memory read and write, read_string and read_std_string with SSO detection, read_pointer with symbol resolution, typed reads, memory search with scope and permission filters, memory map listing, allocation tracking, patching with assembled instructions, breakpoints by address, symbol or module offset, stepping, polling for breakpoint_hit and execution_completed, code and memory tracing, and call_function or call_symbol with typed arguments. The iOS-only tools include inspect_objc_msg, get_objc_class_name, dump_objc_class and dump_gpb_protobuf for GPB protobuf schemas on 64-bit.

Where unidbg stops being the right tool

The most honest limitation is stated by the project itself: iOS emulation is experimental, and the whole thing is an educational project offered at your own risk. Treat that as a compatibility contract rather than modesty. A native library that depends on a syscall, a kernel behaviour or a hardware feature outside the emulated set will not work, and the failure may present as a hang or a corrupted register rather than a clean error.

The backend split is the second constraint. Hardware-assisted paths (Apple M1 hypervisor, Linux KVM) are tied to specific hosts, so a test suite that runs fast on an M1 laptop may not run at all in a Linux CI container without falling back to unicorn or dynarmic. And since next_block, step_until_mnemonic and the tracing tools are Unicorn only, moving to a faster backend to make a test suite tolerable can remove the exact instrumentation you built the harness to get. There is no indication in the material of a compatibility shim that hides this from you.

Finally, the MCP integration is a debugging aid, not a headless automation API. In breakpoint debug mode the process exits when execution completes without hitting a breakpoint, and MCP shuts down with it. If your goal is unattended batch analysis, the custom tools mode is the one to read carefully, and you should confirm from the source how the toolkit behaves under repeated invocation before designing around it.

How it differs from QEMU user-mode emulation

The obvious alternative for running foreign-architecture native code is QEMU in user mode, which emulates the CPU and translates Linux system calls so an ARM binary runs on an x86 host. The difference in approach is where the boundary sits. QEMU user-mode presents a process-level illusion: the binary believes it is running on Linux, and you interact with it through its own entry point and standard I/O. unidbg inverts that. It runs inside your JVM, exposes the library through an emulated JNIEnv, and lets you call a chosen exported function directly with typed arguments rather than driving a whole process.

That inversion is why unidbg can offer call_symbol with a module and symbol name, breakpoints by module offset, and ObjC-specific inspection tools. It is also why it cannot help you if the code you care about only makes sense inside a running Android app with a live binder connection and a real Activity. QEMU user-mode is the better fit for whole-binary execution and for anything that expects a POSIX environment; unidbg is the better fit when you want to reach into one function and watch registers and memory as it runs. Neither is a substitute for a full system emulator, and unidbg does not claim to be one.

Maintenance, releases and licence surface

The release history in the material is uneven. v0.9.7 landed in July 2022, v0.9.8 in September 2024, and v0.9.9 in March 2026, with the last push to master in September 2026. That cadence suggests a project that moves in bursts rather than on a schedule, which matters if you are pinning a version for a long-lived analysis pipeline. Plan to track the master branch if you depend on recent MCP tool additions, because the release tags lag behind the documented feature set.

unidbg itself is Apache-2.0, which is permissive and includes a patent grant. The complication is the bundled hooking libraries: Dobby, xHook, fishhook and whale are separate projects with their own licences, and the README credits them by name. If you redistribute a build that includes them, check each one rather than assuming Apache-2.0 covers the whole artefact. This is a packaging question, not a legal opinion, and it is worth resolving before the code ships inside anything customer-facing.

Editorial conclusion

unidbg fits engineers who need to call a specific exported function inside an Android or iOS native library from a JVM test harness, or who want instruction-level tracing without a physical device. It does not fit anyone looking for a full Android system emulator or a supported commercial tool: the README calls it educational and says to use it at your own risk. Before adopting, verify that your target library's architecture and syscall usage are covered by your chosen backend, and check the Apache-2.0 licence plus the separate licences of the bundled hooking libraries (Dobby, xHook, fishhook, whale) against your distribution model.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. README
  4. Releases
  5. zhkl0228/unidbg on GitHub
Community notes

Community notes