Skip to content

Native Emulator

Domain: native-emulator

In-process, dependency-free self-built ARM64 interpreter for emulating Android .so libraries: load a shared object, register mock Java methods, and invoke exported or Java_* JNI functions to recover signing/crypto algorithms — no device, JVM, or Frida. Sessions are isolated and explicitly managed (create → … → destroy) with idle auto-expiry. libapp.so (Flutter Dart AOT) is not executable here and routes to the Dart layer.

Profiles

  • workflow
  • full

Typical scenarios

  • Recover native/JNI signing and crypto algorithms
  • Extract and load arm64-v8a .so from an APK
  • Instruction-trace an obfuscated native function
  • Mock the Java world via declarative callbacks

Common combinations

  • native-emulator + binary-instrument
  • native-emulator + dart-inspector

Full tool list (54)

ToolDescription
nemu_capabilitiesReport native-emulator backend availability, supported features, and explicit ISA/SIMD gaps. Unsupported opcodes fail loudly instead of being reported as emulated.
nemu_create_sessionCreate an isolated ARM64 emulator session and return its sessionId. Each session owns its own CPU registers, guest stack, and JNI object table, so concurrent analyses never interfere. Destroy it with nemu_destroy_session when done; idle sessions auto-expire. Pass files to populate the virtual device filesystem (path→base64 content) so native code can fopen/fread assets like jiagu_config directly from the emulated FS.
nemu_destroy_sessionDestroy an emulator session and free its memory (mapped library, stack, JNI tables).
nemu_list_sessionsList active emulator sessions with their creation and last-use timestamps.
nemu_session_infoInspect one emulator session without executing native code. Returns timestamps, exported symbols, unresolved imports, constructor faults, and active session count.
nemu_load_libraryLoad an AArch64 ELF shared object (.so) from a filesystem path into a session, mapping its segments and resolving exported symbols. Prerequisite for list_symbols / call_symbol / call_jni_export.
nemu_load_library_chainLoad a chain of dependent libraries into a session, resolving inter-library imports. Pass dependency .so paths as dependencyPaths (loaded first in order), then the primary .so path. Each dependency exports are visible to the primary and later dependencies. Use this for FFmpeg-style multi-library loads where libijkplayer.so calls exports from libijkffmpeg.so and libijksdl.so.
nemu_inspect_importsInspect an AArch64 ELF .so before emulation and list imported symbols from dynamic relocations, including GOT offsets and whether each import is backed by the built-in bionic stubs. Use this to diagnose PLT/GOT NULL indirect-call failures without writing ad-hoc readelf/Capstone scripts.
nemu_dump_gotDump the PLT trampoline → GOT → symbol mapping for an AArch64 ELF shared object. Scans .text for the 4-instruction trampoline pattern (adrp x16 → ldr x17 → add x17,x16,x17 → br x17) used by obfuscated SO files and cross-references each slot against dynamic relocations to resolve the callee name. Use this when you need to know what "bl 0xACD0" actually calls without manual readelf + Python scripting.
nemu_extract_apk_libsList the loadable arm64-v8a native libraries (.so) packaged inside an APK, with their byte sizes. Use nemu_load_apk_library to load one. Note: libapp.so (Flutter Dart AOT) is listed but is not executable here — route it to the Dart layer.
nemu_load_apk_libraryExtract a specific arm64-v8a .so from an APK by name and load it into a session in one step (no temp files). Pair with nemu_extract_apk_libs to discover library names.
nemu_list_symbolsList the exported function symbols of the loaded library — the names callable via call_symbol / call_jni_export.
nemu_call_symbolInvoke an exported function by name following AArch64 AAPCS (integer args in x0..x7, result in x0). Auto-detects JNI signatures. Set injectJni=false to force raw arguments. Set debug=true for auto-NOP on NULL indirect calls + auto TLS prep.
nemu_call_jni_exportInvoke an exported Java_* JNI function. Injects the guest JNIEnv* and thiz, then the Java arguments. Returns x0 — an int/jboolean directly, or a jobject/jbyteArray/jstring handle to resolve via read_byte_array. The main entry point for reversing a native signing/crypto routine.
nemu_call_addressCall a function at an arbitrary guest address (e.g. a native method registered via RegisterNatives). Uses AArch64 AAPCS with args in x0..x7; returns x0. Set injectJni=true to prepend guest JNIEnv* as x0 + thiz=0 as x1 (standard JNI method convention).
nemu_setup_java_mockRegister a mock Java method for JNI callbacks. returnInt/returnString/returnBytes for single constant; returnMap (JSON) for per-key dispatch: first Java arg matched as key→{type,value}. Single-constant is fallback for unmatched keys.
nemu_setup_java_fieldRegister a mock Java field the emulated native code reads back via JNI (GetFieldID/GetStaticFieldID + Get<Type>Field). Declaratively specify the value with valueInt, valueString, or valueBytes (base64) — the 'Java world' constant a native routine folds into its result. No code is executed.
nemu_setup_java_mocksBatch-register multiple Java method mocks in one call. Each entry in the array has the same fields as nemu_setup_java_mock: className, methodName, signature, plus one return value (returnInt, returnString, returnBytes, returnObject, returnArray, or returnMap). Use this to define the full mock chain without rebuilding jshook.
nemu_new_byte_arrayWrap base64 bytes as a JNI jbyteArray handle to pass as an argument into call_jni_export (e.g. the plaintext a signing routine consumes). Returns the handle.
nemu_read_byte_arrayResolve a jbyteArray handle (e.g. a native call's return value) back to its bytes, returned as base64 plus length.
nemu_create_jni_handleCreate a mock JNI object handle pre-populated with controlled data. Use BEFORE calling JNI functions to seed the handle table so that GetStringUTFChars / GetObjectArrayElement / GetIntField return expected values. Returns the handle id to pass as an argument to nemu_call_address or nemu_call_symbol.
nemu_traceInvoke an exported symbol while recording every instruction executed (pc, opcode, step), optionally snapshotting named registers per step. Bounded by maxSteps. Use to follow the control flow / algorithm of an obfuscated native function.
nemu_set_pac_keyConfigure the ARMv8.3 Pointer Authentication key set used by PACIA/PACIB/AUTIA/AUTIB instructions in this emulator session. Set a 128-bit key (32 hex chars) by key slot (ia/ib/da/db) to match keys dumped from a real device via Frida, so AUTIA can verify and strip real-hardware PAC signatures.
nemu_disassembleDisassemble instructions. Two modes: (1) single-instruction — pass opcode (a number, 0x hex string, or hex bytes) and optional pc; no session needed. (2) batch — pass sessionId, vaddr, and count; reads count 4-byte words from guest memory starting at vaddr and returns a {pc, opcode, asm}[] list. Batch supports fixed-width ISAs only (arm64/aarch64, riscv32/riscv64, mips/mips32/mipsel); x86/x64 are rejected. A local lightweight decoder for trace readability, including common SSE/AVX/AVX2/AVX-512 EVEX, RISC-V, and MIPS instructions.
nemu_alloc_memoryAllocate raw guest memory (NOT a JNI handle — a real char* address). Optionally fill with initial data via fillBytes (base64). Returns the guest address to pass as an integer arg to call_symbol. Use at the start of a session to stage encrypted blobs for a native decrypt/signing routine, then read the output with nemu_read_memory.
nemu_read_memoryRead raw bytes from guest memory at a given address. Returns a bounded preview by default; set includeDataBase64=true for full base64 within the configured cap.
nemu_write_memoryWrite raw bytes into guest memory at a given address via base64 data. Use to update an input buffer between call_symbol invocations without re-allocating, or to patch code/data in place.
nemu_write_regionsWrite multiple memory regions in a single call. Accepts an array of {address, dataBase64} objects. Essential for atomic code patching: apply all patches in one call to avoid intermediate corrupt states.
nemu_prepare_tlsMap the TPIDR_EL0 (thread-pointer) TLS block so its memory is accessible for pre-population via nemu_write_regions. Returns the TLS base address. Use this before writing data to TLS offsets (e.g. frame-table pointer at +0x1768) that native code reads via mrs xN, tpidr_el0; ldr xM, [xN, #large_offset].
nemu_session_loadLoad a JSON-serialised array of tool calls and execute them sequentially to set up a session. Each entry is {tool, args}. Supported tools: alloc_memory, write_regions, call_address, call_symbol, prepare_tls, setup_java_mocks, map_memory, bind_host_fn. Use this to replay a debug session from a saved JSON plan without repeating ~20 manual MCP calls.
nemu_bind_host_fnRegister a JavaScript host function at a specific guest address, overriding any existing stub. The function receives guest registers (ctx.x(0)..x(7)), can read/write guest memory (ctx.read/ctx.write), and returns a BigInt value placed in x0. Use to mock custom shell imports at their resolved GOT addresses.
nemu_bind_all_importsBatch-bind host functions to ALL resolved import stubs in the GOT. Reads the GOT table (0x74000 range), finds every unique resolved address, and binds the given JS function body to each. Call after load_library to mock every unresolved shell import at once.
nemu_mem_shadowAdd a shadow memory overlay at a specific address. Reads from shadow take priority over underlying memory — use to provide mock data at addresses that would otherwise crash (e.g. address 0 where SO ELF header resides). Does NOT modify the underlying SO mapping.
nemu_create_vtableCreate a C++ vtable-backed object in guest memory. Allocates a vtable with numSlots entries (each pointing to a return-0 host stub) and an object that points to it. Use when native code does direct vtable dispatch (BLR X8 through [obj+offset]) — common in obfuscated SO files calling virtual methods on C++ objects. Returns {objectAddr, vtableAddr} for use with nemu_call_address/nemu_call_symbol.
nemu_set_vtable_slotOverride a specific vtable slot with a custom host function. The slot at vtableAddr + slotIndex*8 is rewritten to point to a stub executing fnBody (JS, with ctx.x/ctx.writeU64/ctx.persistReg etc.). Use to mock specific C++ virtual methods after creating a vtable with nemu_create_vtable.
nemu_set_registersSet arbitrary CPU registers by index. Pass an object mapping register number to value (e.g. {0: 0x60000000, 10: 0, 11: 0x55150}). Supports x0-x30 and floating-point d0-d31. Use to fix up loop variables or inject context pointers before/after host function calls.
nemu_jni_diagRead the JNI diagnostic log for a session. Tracks every JNI function call (FindClass, GetMethodID, CallIntMethod, etc.) and unimplemented stub invocations. Use after nemu_call_symbol or nemu_trace to see what Java methods the native code tried to call. Actions: "read" (default) reads and clears the log; "snapshot" reads without clearing; "clear" clears without returning.
nemu_jni_handlesList all JNI object handles allocated in a session, with their kind and summary. Handles are opaque IDs (jclass, jstring, jbyteArray, jobject) that native code passes around. Use to verify mock setups and debug handle leaks. Optionally filter by kind (e.g. "class", "string", "bytes", "method", "field", "auto-object", "mock-int", "mock-string", "mock-boolean", "objarray") or by specific handle number.
nemu_get_jni_stubGet the guest stub address for a JNI table index. Pass a specific index to look up one entry (returns 0 + bound=false if the index was never bound), or omit to return all bound index→stubAddress mappings. Use to read stub addresses from a session so they can be written into SO caches or external tooling that expects specific JNI function addresses (especially the extended indices 280-336 used by obfuscation VM dispatch bridges).
nemu_dlsym_diagRead the dlsym resolution log from the current session. Tracks every symbol lookup the emulated code requested via dlsym() — essential for discovering which VM handler names an obfuscated dispatch engine tries to resolve. Actions: read (default, reads+clears), snapshot (read-only), clear.
nemu_vm_state_dumpDump LiteVM state from guest memory at specified base addresses. Reads ctx (32×64-bit), table (32×64-bit), and optional output buffer. Returns structured hex values suitable for comparison with Python LiteVM dumps. Use after nemu_call_symbol to inspect native VM execution results.
nemu_vm_state_loadLoad VM state into guest memory. Takes ctx values and table values as hex strings and writes them at the specified base addresses. Use to bridge Python LiteVM state into native VM: run Python vm.run(), dump ctx/table as hex, then load into nemu guest memory before calling bb2i34u32clsb.
nemu_vm_state_compareCompare native VM state (read from guest memory) against an expected state (e.g. Python LiteVM dump). For each of ctx, table, and output, reports whether they match and lists the first mismatches. Use to cross-validate native VM execution against the known-good Python implementation.
nemu_mem_mapMap a memory region in guest address space. Use to extend the mapped area for output buffers or scratch data that would otherwise cause unmapped-memory faults. Idempotent — safe to call on already-mapped regions.
nemu_bytecode_decodeDecode a u32 LiteVM bytecode word into its opcode fields: group (G0-G7), sub-opcode, a1 register index, fl field index, imm signed offset, and validity. Matches the Python LiteVM Opcode.is_valid_opcode() semantics. No session needed — pure computation. Use to understand what a native bytecode word means without external scripts.
nemu_bytecode_scanScan a guest memory region and decode all valid LiteVM bytecode words. Reads count u32 words starting at address, decodes each one, and returns only the valid opcodes with their offsets. Much faster than manual decode+filter — one call to survey an entire bytecode table.
nemu_pointer_chainWalk a chain of pointers in guest memory. Starting from base, reads a u64 pointer, then follows it to the next address, repeating up to maxDepth times. At each hop, shows the address, the pointer value, and the first 32 bytes of data there. Essential for understanding CreateLitevm's x24 table indirection structure.
nemu_data_dumpRead a guest memory region and format it as a structured table of u32 or u64 values. Each row shows offset, hex value, ASCII preview, and optional annotations. Auto-classifies each word as pointer, bytecode, ASCII, or raw data. Pointers are resolved to show target data when possible.
nemu_dump_frameRead and decode a CreateLitevm frame structure from guest memory. Parses the 256-byte frame fields: chain pointer, bytecode count, frame data, and sub-function flags. Essential for understanding the VM dispatch state at any point during execution.
nemu_patch_applyApply multiple memory patches in a single call. Each patch is {address, dataBase64, writeProtect?}. Faster than repeated nemu_write_memory calls — essential for atomic code patches that must be applied together to avoid intermediate corrupt states.
nemu_regs_saveSave a named snapshot of current GPR registers (x0-x30, sp). Returns a snapshot id usable with nemu_regs_restore. The snapshot persists until the session is destroyed or the name is overwritten. Use to preserve registers before calling an obfuscated function that corrupts callee-saved state.
nemu_regs_restoreRestore GPR registers from a previously-saved snapshot (created by nemu_regs_save). Partially restores: only registers that were saved are written back. Use after an obfuscated function call to recover decode/context registers.
nemu_scan_memoryScan emulated memory for a byte pattern (like Volatility). Searches a guest address range for an exact byte match using Boyer-Moore-Horspool. Returns a list of matched addresses. Skips unmapped regions silently — use nemu_mem_map to extend the scan range if needed.
nemu_xor_regionXOR a region of emulated memory with a single-byte key. Returns the XOR result as base64. Use for quick decryption testing — XOR a buffer with a candidate key byte and inspect the preview without modifying guest state. Set dryRun=false to write the XOR result back into guest memory.

Released under AGPL-3.0-only