Skip to main content

Emulite

PyPI Python

Emulite is a Python framework for emulating Android and iOS native binaries. It combines detailed logging, configurable device profiles, strong typing, and explicit failures to provide a direct path from a target binary to a working proof of concept.

Emulite is alpha software, so breaking API changes may occur before 1.0.0. It was inspired by Unidbg and Chomper.

Requirements

  • Python 3.10+
  • Unicorn 2.1.4+
  • Capstone 5+
  • LIEF 0.14+
  • Cryptography 42+

Installation

From PyPI:

pip install emulite

From source:

git clone --recurse-submodules https://github.com/retrowave3/emulite.git
cd emulite
pip install -e .

Examples

Real application examples: emulite-examples

Android Usage

Use AndroidEmulator32 or AndroidEmulator64 based on the target architecture. A default Android rootfs is included. Pass a path as the first argument to use a custom rootfs.

Every hook returns a HookHandle; call unhook() when the hook is no longer needed. Replacement, trace, and memory callbacks use explicit action enums, so callback behavior is visible in both code and type checkers.

import logging

from emulite import AndroidEmulator64, AndroidEmulatorBase, JniHandler, JniValue
from emulite import MemoryAccess, MemoryHookAction, ReplacementAction, TraceAction, TraceInfo
from emulite.android.java.lang.reflect.java_method import JavaMethod


class CustomJniHandler(JniHandler):
    def call_static_method(self, method: JavaMethod, args: list[object]) -> JniValue:
        if method.java_class.name == "com/example/Device" and method.name == "getValue":
            return "value"
        return super().call_static_method(method, args)


def before_memcpy(emu: AndroidEmulatorBase) -> ReplacementAction:
    print("memcpy size:", emu.get_argument(2))
    return ReplacementAction.CALL_ORIGINAL


def after_memcpy(emu: AndroidEmulatorBase) -> None:
    print("memcpy returned:", hex(emu.get_return_value()))


def replace_time(_emu: AndroidEmulatorBase) -> int:
    return 150000


def on_instruction(_emu: AndroidEmulatorBase, info: TraceInfo) -> TraceAction:
    print(info.format())
    return TraceAction.CONTINUE


def on_memory(_emu: AndroidEmulatorBase, access: MemoryAccess, address: int, size: int, value: int) -> MemoryHookAction:
    print(access.name.lower(), hex(address), size, hex(value))
    return MemoryHookAction.CONTINUE


logging.basicConfig(level=logging.DEBUG, format="%(levelname)s %(name)s: %(message)s")
emu = AndroidEmulator64(jni_handler=CustomJniHandler())
module = emu.load_library("path/to/libnative.so")
module.call_jni_onload()

native = emu.java_class("com.example.Native")
memcpy_hook = module.hook_import("memcpy", before_memcpy, after_memcpy)
time_hook = emu.intercept("time", replace_time)
trace = module.trace(on_instruction)

jni_result = native.call_static("getValue", "(I)I", 123)

buffer = emu.allocate_string("testing")
print(buffer.read_cstr())

watchpoint = emu.watchpoint(buffer, on_memory, length=32)
emu.map_file("/data/local/tmp/config.json", "config.json")
native_result = module.call_symbol("exported_symbol", buffer)
runtime_image = module.dump()

watchpoint.unhook()
trace.unhook()
time_hook.unhook()
memcpy_hook.unhook()
emu.free(buffer)
emu.close()

iOS Usage

IOSApplication accepts an unpacked .app or an .ipa, creates its profile, and uses the bundled rootfs. Mach-O symbols retain their leading underscore.

import logging

from emulite import IOSApplication, IOSEmulatorBase, MemoryAccess, MemoryHookAction
from emulite import TraceAction, TraceInfo


def replace_time(_emu: IOSEmulatorBase) -> int:
    return 1_700_000_000


def on_instruction(_emu: IOSEmulatorBase, info: TraceInfo) -> TraceAction:
    print(info.format())
    return TraceAction.CONTINUE


def on_memory(_emu: IOSEmulatorBase, access: MemoryAccess, address: int, size: int, value: int) -> MemoryHookAction:
    print(access.name.lower(), hex(address), size, hex(value))
    return MemoryHookAction.CONTINUE


logging.basicConfig(level=logging.DEBUG, format="%(levelname)s %(name)s: %(message)s")
app = IOSApplication.load("path/to/Example.app")
emu = app.emulator
module = app.module

time_hook = emu.intercept("_time", replace_time)
trace = module.trace(on_instruction)
payload = emu.allocate_string("payload")
watchpoint = emu.watchpoint(payload, on_memory, length=8)
emu.map_file("/tmp/config.json", "config.json")
native_result = module.call_symbol("_sign", payload)

signer = app.objc.require_class("Signer")
instance_hook = app.objc.add_interceptor("+[Signer sharedInstance]", lambda _emu: signer.call_method("alloc"))
value_hook = app.objc.add_interceptor("-[Signer value]", lambda _emu: 42)
instance = signer.call_method("sharedInstance")
objc_result = instance.call_method("value")
instance_class_name = instance.class_name
state = instance.get_variable("_state")
class_header = signer.dump()
status = app.run()
runtime_image = module.dump()

watchpoint.unhook()
trace.unhook()
value_hook.unhook()
instance_hook.unhook()
time_hook.unhook()
emu.free(payload)
app.close()

For standalone targets, use IOSEmulator64.load_library() for a dylib or load_framework() for a framework.

Android and iOS share the same module, pointer, allocation, file, interception, tracing, watchpoint, and lifecycle primitives. String and byte arguments are borrowed for one call; use allocate_string() or allocate_bytes() when native code retains the pointer. module.call_address(offset, ...) uses a module-relative offset; emulator.call(address, ...) uses an absolute guest address.

Crash Details

EmulatorCrashed retains a readable message and exposes pc, location, and an immutable frames tuple when that information can be collected:

from emulite import EmulatorCrashed

try:
    module.call_symbol("run")
except EmulatorCrashed as crash:
    print(crash.location)
    for frame in crash.frames:
        print(frame.format())

Virtual Filesystem Providers

Providers receive mount-relative POSIX paths:

import errno

from emulite import AndroidEmulator64
from emulite.filesystem import FileIO, FileStat, OpenFlag, RegularFileIO


class MemoryProvider:
    def __init__(self, data: bytes):
        self.data = bytearray(data)

    def open(self, path: str, flags: OpenFlag) -> FileIO | int:
        if path != "/message.txt":
            return -errno.ENOENT
        return RegularFileIO(path, self.data, writable=False, oflags=flags)

    def stat(self, path: str) -> FileStat | None:
        if path == "/":
            return FileStat.for_directory()
        if path == "/message.txt":
            return FileStat.for_file(len(self.data))
        return None

    def listdir(self, path: str) -> list[str] | None:
        return ["message.txt"] if path == "/" else None


emu = AndroidEmulator64()
provider = MemoryProvider(b"hello from Python")
emu.mount("/virtual", provider)
contents = emu.read_file("/virtual/message.txt")
emu.unmount("/virtual", provider)
emu.close()

Guest Snapshots

checkpoint = emu.snapshot()
first = module.call_symbol("exported_symbol")
emu.restore(checkpoint)
second = module.call_symbol("exported_symbol")

Snapshots restore CPU and guest memory on the emulator that created them. They do not restore Python objects, hooks, or open files.

Project Status

Supported Platforms

Platform ARM32 ARM64
Android ✅ AndroidEmulator32 ✅ AndroidEmulator64
iOS — ✅ IOSEmulator64

Supported Engines

Backend Engine Status
Unicorn ✅
Dynarmic —
Apple Silicon Hypervisor —
Linux KVM Hypervisor —

✅ Supported · ⏳ Planned · — Not planned

Resources

Metadata

Release files for emulite 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for emulite 0.3.0
File Size Uploaded
emulite-0.3.0.tar.gz 90.0 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for emulite 0.3.0
File Interpreter ABI Platform
emulite-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 180.9 MB

Release files / emulite-0.3.0.tar.gz

Download URL emulite-0.3.0.tar.gz
Size 90.0 MB
Tags Source
SHA-256 checksum
How to use checksums
122825344caad28ac645165bb0257608be531ea2b3c2c06678d95c7882d3436e
BLAKE2b-256 checksum
How to use checksums
1062da63915c22c2fa7b719776e9da62dd409e4dd79e909f1108377f07b42287
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.

Transparency log

Release files / emulite-0.3.0-py3-none-any.whl

Download URL emulite-0.3.0-py3-none-any.whl
Size 90.9 MB
Tags Python 3
SHA-256 checksum
How to use checksums
9a641656987a84468984cccd3613b796b2e98cbf4ac1bfc2e52fd30be1a8ebd3
BLAKE2b-256 checksum
How to use checksums
1af0db922eba9253691019441ce66ac148044210c92a3c54d85797d4b6b4d6f4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.

Transparency log

Release history Release notifications | RSS feed

0.4.0

2 release files

This release

0.3.0 This release

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page