Library / SDK
sledgeh4w/chomper avatar
sledgeh4w/chomper

Chomper: emulating iOS crypto routines with Unicorn instead of an iPhone

A lightweight emulation framework for emulating cryptographic algorithms in iOS executables and libraries.

635 stars152 forksPythonMIT

At a glance

What is it?
Chomper is a lightweight Python emulation framework built on Unicorn that runs iOS executables and Mach-O libraries under an emulated ARM64 CPU, with an Objective-C runtime and partial Android support. It exists so that a cryptographic routine can be exercised on a workstation, without a device and without running the full app.
Who is it for?
Adopt Chomper when you need to exercise a cryptographic routine from an iOS binary or Mach-O library on a workstation, and you are prepared to supply the matching rootfs system libraries yourself. Do not adopt it to run an entire app, since the feature list is limited to ELF and Mach-O emulation and a subset of iOS system libraries, and treat the Objective-C runtime as partial rather than complete.
Can I use it commercially?
Yes. MIT 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 11 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

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

Editorial analysis

The problem Chomper solves: calling one function, not running an app

Chomper is a lightweight emulation framework based on Unicorn. It is mainly used to emulate security algorithms in iOS executables and libraries, and it also provides limited support for Android native libraries.

Read that scope statement carefully, because it defines the tool. This is not an iOS simulator and not an app analysis sandbox. It is a way to load a binary into an emulated CPU and call a function in it.

The workflow in the documentation is characteristic. A module is loaded, a string is created in the emulated memory, a buffer is allocated for the output, and then a specific address is called with those arguments:

python
from chomper import Chomper
from chomper.const import ARCH_ARM64, OS_IOS

# For iOS, system libraries will be automatically loaded from `rootfs_path`
emu = Chomper(
    arch=ARCH_ARM64,
    os_type=OS_IOS,
    rootfs_path="rootfs/ios",
)

# Load program
discover = emu.load_module("samples/ios/com.xingin.discover/8.74/discover")

Then the call itself:

python
emu.call_address(discover.base + 0x324EF10, input_str, input_len, result_buf, buf_size, result_len_ptr)

result_len = emu.read_u64(result_len_ptr)
result = emu.read_bytes(result_buf, result_len)

The address is an offset from the module base, which is the detail that tells you how this is meant to be used. You are not discovering the function, you are calling a function whose location you already know, typically from static analysis of the binary. The emulation gives you a way to run it and observe the output without a device.

That is the whole value proposition: the analysis work happens statically, and the execution happens in the emulator.

The topic list reflects the domain, with reverse-engineering, macho, objc, elf, ios, android and unicorn among the tags, and the repository's own description calls it an emulation framework for emulating cryptographic algorithms.

The rootfs is a separate repository you must clone

Installation has three parts, and the third one catches people out.

The stable release comes from PyPI:

bash
$ pip install chomper

The latest version comes from GitHub:

bash
$ pip install git+https://github.com/sledgeh4w/chomper.git

And the rootfs is a separate repository:

bash
$ git clone https://github.com/sledgeh4w/rootfs.git

The rootfs is not optional. The Chomper constructor takes a rootfs_path, and for iOS the comment in the example says plainly that system libraries will be automatically loaded from rootfs_path. The path in the example is rootfs/ios, and the Android example uses rootfs/android.

So the framework needs a filesystem tree of the target platform's system libraries to resolve imports against. When you emulate an iOS binary, it links against the frameworks and libraries of that OS version, and the emulator has to supply them.

The feature list mentions support for a subset of iOS system libraries from iOS 14.4.0, which tells you two things at once. It tells you which OS version the bundled libraries come from, and it tells you the support is a subset rather than the whole set. If the binary you are analysing imports something the rootfs does not carry, the module will fail to load.

That is the main practical constraint on the tool, and it is why the rootfs is versioned separately from the framework. The .gitmodules file at the repository root and the rootfs entry among the top level directories show that the rootfs is wired into the project as a submodule or vendored path rather than shipped inside the package.

The requirements are modest otherwise: Python 3.9 or later and Unicorn 2.0.0 or later.

The Objective-C runtime, and the autorelease pool

Chomper does more than call C functions. It ships an Objective-C runtime, reached through the ObjcRuntime class, and the example is short enough to read in one go.

python
from chomper import Chomper
from chomper.const import ARCH_ARM64, OS_IOS
from chomper.objc import ObjcRuntime

emu = Chomper(
    arch=ARCH_ARM64,
    os_type=OS_IOS,
    rootfs_path="rootfs/ios",
)

objc = ObjcRuntime(emu)

emu.load_module("samples/ios/cn.com.scal.sichuanair/zsch")

Once the runtime is attached, the API is object oriented. You look up a class by name, create an NSString, call a method on the class object, and convert the result back to a C string:

python
# Use this context manager to ensure that Objective-C objects can be automatically released
with objc.autorelease_pool():
    # Find class
    zsch_rsa_class = objc.find_class("ZSCHRSA")

    # Create NSString object
    input_str = objc.create_ns_string("Mocha")

    # Call Objective-C method
    req_sign = zsch_rsa_class.call_method("getReqSign:", input_str)

    # Convert NSString object to C string
    result_ptr = req_sign.call_method("UTF8String")
    result = emu.read_string(result_ptr)

The class name in that example is the giveaway about the use case. ZSCHRSA is the RSA signing class inside an airline application, and the method takes an NSString and returns a signed value. Calling it directly from an emulated Objective-C runtime means you get the same signing behaviour the app would produce, which is exactly what you need when you are trying to reproduce a signature or understand what it depends on.

One detail deserves attention because it is a correctness issue rather than a style one. The example uses an autorelease_pool context manager, and the comment explains its purpose: to ensure that Objective-C objects can be automatically released.

Without that, the emulated objects accumulate. In a real Objective-C runtime, memory management is a language feature with a well known pattern of pool scopes. Here, the pool is provided by the emulator, and if you write a loop that calls thousands of methods, you need the pool to be released or you will hold on to every object you created.

Android support exists, but it is explicitly limited

The Android path follows the same shape, with one structural difference that shows up in the example.

python
from chomper import Chomper
from chomper.const import ARCH_ARM64, OS_ANDROID

emu = Chomper(
    arch=ARCH_ARM64,
    os_type=OS_ANDROID,
    rootfs_path="rootfs/android",
)

# Load dependency libraries
emu.load_module("rootfs/android/system/lib64/libz.so")

libszstone = emu.load_module("samples/android/com.shizhuang.duapp/libszstone.so")

For Android you load the dependency libraries yourself from the rootfs, starting with libz.so from system/lib64. For iOS the system libraries are loaded automatically from rootfs_path, so the Android flow has that extra step because the target here is a single shared library rather than an application with a full framework stack.

Then the call, with the address offset from the library base:

python
s = "Mocha"

input_str = emu.create_string(s)
input_len = len(s)
result_buf = emu.create_buffer(1024)

result_len = emu.call_address(libszstone.base + 0x2F1C8, input_str, input_len, result_buf)
result = emu.read_bytes(result_buf, result_len)

The library named here, libszstone.so from a Chinese application, is a case where the routine of interest lives in a native library loaded by a Java application rather than in the app itself.

The framework's own wording is the important part here. It provides limited support for Android native libraries, and the feature list names ELF and Mach-O binaries as the covered formats. ELF covers Android, and Mach-O covers Apple platforms, so both binaries are in scope, but the surrounding platform support is not symmetric: iOS has the Objective-C runtime and automatic system library loading, Android has manual dependency loading and no equivalent runtime layer described in the documentation.

For a first project, the iOS path is the better documented one.

The dependency stack tells you what this project sits on

The pyproject.toml lists four runtime dependencies, and each one plays a distinct role in what the framework does.

pyelftools at 0.29 or later handles ELF parsing, and lief at 0.16.0 or later handles binary format work more broadly. unicorn at 2.0.0 or later is the emulation engine the framework is built on. capstone at 5.0.0.post1 or later is the disassembler, which is what makes instruction level tooling possible.

That capstone dependency is worth pausing on, because it is not in the minimal list of the other three. The examples directory includes a file named trace_inst.py, and a disassembly library sitting in the runtime dependencies suggests the framework can decode instructions rather than only execute them, which is what you need if you want to hook a function or understand what the emulated code is doing at a given point.

The examples directory also holds emulate.py, hook.py and objc.py alongside the crypto subdirectory. The presence of a hook example matches the general shape of these frameworks, where the ability to intercept a call is as important as the ability to make one.

The build is a plain setuptools build with a dynamic version taken from chomper.__version__, and the project supports Python 3.9 through 3.14. The type checking configuration is strict in a few respects, including strict_equality and warn_redundant_casts, and the black configuration excludes a plist17lib directory, which is a vendored library for reading property lists that the framework needs for iOS.

The repository also carries a CHANGES.md, a tox.ini, a .flake8 configuration and a pre-commit configuration, which is the standard set of quality gates for a Python library of this size.

What this framework is for, and where it stops

It is worth being direct about the boundary, because emulation frameworks invite overreach.

Chomper is for executing a known function in a known binary. The offset in the example, 0x324EF10 inside a specific application binary, is the point. You have already located the routine, probably with a disassembler or a symbol from a class dump, and you want to run it to see what it produces under an input you control.

It is not for running an application end to end. There is no UI, no application lifecycle, no network stack, no file system beyond what the rootfs provides. If you need to watch an app run, you want an instrumented device or a full system emulator, and Chomper will not get you there.

It is also not a static analysis tool. Static analysis is upstream of this workflow: Chomper consumes the answer about where the function is and gives you a way to call it.

The examples directory makes the intended use explicit. The crypto subdirectory holds emulation code for specific cryptographic algorithms, which is the clearest statement of what people actually use this for. Reproducing a request signature, confirming that an algorithm behaves as the static analysis suggested, or capturing the exact bytes a library produces for a given input are all tasks where emulation beats running the app, because you control the arguments and can loop over inputs quickly.

The cost of the approach is the setup. You need the binary, you need the right rootfs for the platform and version, and you need to know the address. The payoff is that the loop from input to output becomes a script you can run a thousand times, which is something you cannot do with a device that is busy serving an actual user.

The releases show steady work: v0.3.9 on 2026-10-13, v0.3.8 on 2026-04-21 and v0.3.7 on 2026-01-02, with the last push to the repository on 2026-10-13. The version numbers are still below 1.0, and the licence is MIT.

Editorial conclusion

Adopt Chomper when you need to exercise a cryptographic routine from an iOS binary or Mach-O library on a workstation, and you are prepared to supply the matching rootfs system libraries yourself. Do not adopt it to run an entire app, since the feature list is limited to ELF and Mach-O emulation and a subset of iOS system libraries, and treat the Objective-C runtime as partial rather than complete. Before your first run, clone the rootfs repository and point rootfs_path at the matching ios or android directory, and check the examples/crypto directory, which contains emulation code for specific algorithms you can adapt.

Frequently asked questions

What is Chomper and what does it do?

It is a lightweight Python emulation framework based on Unicorn, mainly used to emulate security algorithms in iOS executables and libraries, with limited support for Android native libraries. It emulates ELF and Mach-O binaries and supports a subset of iOS system libraries from iOS 14.4.0.

What do I need to install to use Chomper?

Python 3.9 or later and Unicorn 2.0.0 or later, with Chomper installed from PyPI or from GitHub. You also need to clone the separate rootfs repository, since the system libraries are loaded from the rootfs_path you pass to the Chomper constructor.

How do I call a function inside an emulated iOS binary?

Load the module with emu.load_module, create the input string and output buffer with emu.create_string and emu.create_buffer, then call emu.call_address with the address as the module base plus the function offset, and read the result back with emu.read_bytes or emu.read_string.

Does Chomper support Objective-C?

Yes, through the ObjcRuntime class from chomper.objc. You can find a class by name, create an NSString, call a method, and convert the result with UTF8String. The examples wrap calls in an autorelease_pool context manager so objects can be released.

How complete is the Android support in Chomper?

The project describes it as limited support for Android native libraries. Unlike the iOS flow, dependency libraries are loaded explicitly from the rootfs, for example rootfs/android/system/lib64/libz.so, and no Objective-C equivalent layer is described for Android.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. sledgeh4w/chomper on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/sledgeh4w-chomper.svg)](https://hysenlabs.com/projects/sledgeh4w-chomper)

Was this useful?