chomper

repository·main·Indexed 20 days ago

https://github.com/sledgeh4w/chomper

A lightweight emulation framework built on Unicorn for emulating security algorithms within iOS and Android native binaries. It supports ARM and ARM64 architectures, providing tools for loading modules, calling functions by symbol or address, and managing memory. It includes specialized support for Objective-C runtime interaction in iOS emulation via the ObjcRuntime class and provides comprehensive memory read/write helpers and hooking mechanisms.

Tokens
9.8K
Snippets
31
Records
44
Agent score
70%

What's inside chomper

  1. Work with Objective-C in iOS emulation

    main

    For interacting with Objective-C objects, use the ObjcRuntime class, passing your Chomper instance to it.

    Key workflows:

    • Memory Management: Wrap Objective-C operations in objc.autorelease_pool() to ensure objects are automatically released.
    • Class Discovery: Use find_class(name) to locate an Objective-C class.
    • Object Creation: Use create_ns_string(string) to create an NSString object.
    • Method Invocation: Use class_instance.call_method(selector, *args) to call methods.
    • Data Conversion: Use call_method("UTF8String") on an NSString object to get a C string pointer, then use emu.read_string(ptr) to read it into 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("examples/binaries/ios/cn.com.scal.sichuanair/zsch")
    
    # Use autorelease_pool for automatic memory management
    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 and read it
        result_ptr = req_sign.call_method("UTF8String")
        result = emu.read_string(result_ptr)
  2. Emulate iOS executables

    main

    To emulate an iOS binary, initialize the Chomper instance with ARCH_ARM64 and OS_IOS. You must provide the rootfs_path pointing to the iOS directory in your cloned rootfs repository so that system libraries can be automatically loaded.

    Use load_module to load the binary, create_string or create_buffer to prepare memory for arguments, and call_address to execute a specific function address. Use read_u64 or read_bytes to retrieve results from memory.

    from chomper import Chomper
    from chomper.const import ARCH_ARM64, OS_IOS
    
    # Initialize with rootfs_path for automatic system library loading
    emu = Chomper(
        arch=ARCH_ARM64,
        os_type=OS_IOS,
        rootfs_path="rootfs/ios",
    )
    
    # Load the target binary
    discover = emu.load_module("examples/binaries/ios/com.xingin.discover/8.74/discover")
    
    s = "Mocha"
    
    # Prepare arguments in emulated memory
    input_str = emu.create_string(s)
    input_len = len(s)
    result_buf = emu.create_buffer(120)
    buf_size = 120
    result_len_ptr = emu.create_buffer(8)
    
    # Execute function at specific address
    emu.call_address(discover.base + 0x324EF10, input_str, input_len, result_buf, buf_size, result_len_ptr)
    
    # Read results
    result_len = emu.read_u64(result_len_ptr)
    result = emu.read_bytes(result_buf, result_len)
  3. Install Chomper

    main

    You can install the stable version of Chomper from PyPI or the latest version directly from GitHub.

    Requirements:

    • Python 3.9+
    • Unicorn 2.0.0+

    Additionally, you must clone the rootfs repository to provide the necessary system libraries for iOS or Android emulation.

    # Install stable version
    $ pip install chomper
    
    # Or install latest from GitHub
    $ pip install git+https://github.com/sledgeh4w/chomper.git
    
    # Clone rootfs (required for system libraries)
    $ git clone https://github.com/sledgeh4w/rootfs.git
  4. Emulate Android native libraries

    main

    To emulate Android native libraries, initialize Chomper with OS_ANDROID and the appropriate rootfs_path. Unlike iOS, you may need to manually load dependency libraries using load_module before loading your target library.

    Arguments and results are handled similarly to the iOS workflow using create_string, create_buffer, and call_address.

    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 manually
    emu.load_module("rootfs/android/system/lib64/libz.so")
    
    # Load target library
    libszstone = emu.load_module("examples/binaries/android/com.shizhuang.duapp/libszstone.so")
    
    s = "Mocha"
    
    input_str = emu.create_string(s)
    input_len = len(s)
    result_buf = emu.create_buffer(1024)
    
    # Call function and get result length
    result_len = emu.call_address(libszstone.base + 0x2F1C8, input_str, input_len, result_buf)
    result = emu.read_bytes(result_buf, result_len)
  5. Initialize the Chomper emulator

    main

    The Chomper class is the main entrypoint for the emulation framework. It initializes the Unicorn engine (for CPU emulation) and Capstone (for disassembly). It supports ARM and ARM64 architectures and can be configured for Android or iOS environments.

    Key Arguments:

    • arch: Architecture to emulate (const.ARCH_ARM or const.ARCH_ARM64). Defaults to const.ARCH_ARM64.
    • mode: Emulation mode (const.MODE_ARM or const.MODE_THUMB). Defaults to const.MODE_ARM.
    • os_type: Target OS (const.OS_ANDROID or const.OS_IOS). Defaults to const.OS_ANDROID.
    • rootfs_path: Path to the root filesystem (required for iOS emulation to load system libraries).
    • trace_inst: If True, prints a log for every instruction executed (slows down emulation).
    • trace_symbol_calls: If True, prints a log when any symbol is called.
    • trace_inst_callback: A custom callback function for instruction tracing.
    from chomper import Chomper, const
    
    emu = Chomper(
        arch=const.ARCH_ARM64,
        mode=const.MODE_ARM,
        os_type=const.OS_IOS,
        rootfs_path='/path/to/ios/rootfs'
    )
  6. Interact with Objective-C objects using ObjcObject

    main

    The ObjcObject class wraps Objective-C object instances. You can use it to inspect an object's class, access its instance variables (ivars), and call methods on it.

    Key capabilities:

    • Inspect Class: Use .class_ to get the ObjcClass or .class_name for the string name.
    • Access Variables: Use .get_ivar(ivar) or .get_variable(var_name) to retrieve data.
    • Call Methods: Use .call_method(sel, *args) to invoke Objective-C selectors.
    • String Representation: Calling str(obj) returns the object's description via its UTF8String or returns `
  7. Convert bytes to float or double

    main

    Use bytes_to_float to interpret a byte sequence as a floating-point number. The function automatically determines if the input should be treated as a single-precision float (4 bytes) or a double-precision float (8 bytes) based on the length of the data provided.

    from chomper.utils import bytes_to_float, LITTLE_ENDIAN
    
    # Assuming data is 4 bytes for single precision
    data = b'\x00\x00\x80\x3f'
    value = bytes_to_float(data, endian=LITTLE_ENDIAN)
  8. Convert ctypes Structures to and from bytes

    main

    Chomper provides utilities to bridge the gap between raw bytes and ctypes.Structure objects, which is useful for parsing memory layouts.

    • struct_to_bytes(st: Structure): Serializes a ctypes.Structure instance into its raw byte representation.
    • bytes_to_struct(data: bytes, struct_class: Type[StructureT]): Deserializes a byte sequence into a new instance of the provided ctypes.Structure class.
    • read_struct(emu: Chomper, address: int, struct_class: Type[StructureT]): A high-level helper that reads a specific amount of bytes from a Chomper emulator instance at a given address and converts them directly into a ctypes.Structure.
    import ctypes
    from chomper.utils import bytes_to_struct, read_struct
    
    class MyStruct(ctypes.Structure):
        _fields_ = [("a", ctypes.c_int32), ("b", ctypes.c_int32)]
    
    # From raw bytes
    data = b'\x01\x00\x00\x00\x02\x00\x00\x00'
    struct_inst = bytes_to_struct(data, MyStruct)
    
    # From an emulator (Chomper instance)
    # struct_inst = read_struct(emu, 0x1000, MyStruct)
  9. Find Objective-C classes and protocols with ObjcRuntime

    main

    Use find_class(name) to retrieve an ObjcClass instance by its string name. If the class does not exist, it raises a ValueError. Use find_protocol(name) to retrieve an ObjcProtocol instance. If the protocol does not exist, it raises a ValueError.

    # Find a class
    objc_class = runtime.find_class("NSObject")
    
    # Find a protocol
    objc_protocol = runtime.find_protocol("NSCopying")
  10. Convert integers to bytes

    main

    Use int_to_bytes to convert an integer into a byte representation. You can specify the length, whether the integer is signed, and the endianness.

    from chomper.utils import int_to_bytes, LITTLE_ENDIAN
    
    # Convert 255 to 4 bytes, little-endian
    bytes_val = int_to_bytes(255, length=4, signed=False, endian=LITTLE_ENDIAN)
  11. Manage emulated file system paths and working directory

    main

    The PosixOs interface allows managing the emulated environment's filesystem state:

    • Working Directory: Use get_working_dir() to retrieve the current path and set_working_dir(path) to change it. Paths must be absolute (starting with /).
    • Path Forwarding: Use forward_path(src_path, dst_path) to map an emulated path to a real host path. This allows emulated programs to access specific host files via a virtual path.
    • Symbolic Links: Use set_symbolic_link(src_path, dst_path) to create virtual symlinks within the emulated filesystem.
    • Real Path Mapping: The system uses rootfs_path to resolve emulated absolute paths to actual files on the host machine.
  12. Safely join paths within a directory

    main

    The safe_join(directory: str, *paths: str) function joins multiple path components to a base directory while ensuring the resulting absolute path does not escape the base directory (preventing directory traversal attacks). If the resulting path is outside the base, it returns None.

    from chomper.utils import safe_join
    
    # Success
    path = safe_join("/tmp/base", "subdir", "file.txt")
    # Returns "/tmp/base/subdir/file.txt"
    
    # Failure (attempts to escape)
    path = safe_join("/tmp/base", "..", "etc", "passwd")
    # Returns None