AndKittyInjector

repository·main·Indexed 43 days ago

https://github.com/mjx0/andkittyinjector

A ptrace-based Android shared-library injector that supports PID or package targeting. It features multiple injection strategies including process launching, watching, and breakpoint-triggered injection (via --bp-ld or --bp-sym). Advanced capabilities include memfd-backed dlopen to bypass SELinux, library hiding from solist/sonext, and the ability to be embedded as a C++ library using the KittyInjector class and KittyMemoryMgr.

Tokens
4.4K
Snippets
8
Records
14
Agent score
57%

What's inside AndKittyInjector

  1. Inject into an app at launch or via breakpoints

    main

    When injecting into apps that use emulation (cross-ISA via libhoudini or libndk_translation), you may need to use specific timing or breakpoint strategies to ensure successful injection.

    Common Scenarios:

    1. Wait for launch: Use --watch to wait for the process to appear.
    2. Inject on first dlopen: Use --launch --bp-ld to inject as soon as the first native/emulated dlopen occurs.
    3. Inject on specific symbol: Use --launch --bp-sym <binary> <symbol> to trigger injection at a specific code location.
    4. Delayed injection: Use --delay <micros> to provide a buffer after launch.
    # Inject as soon as the first native/emulated dlopen call happens after launch.
    ./AndKittyInjector --package com.target.package --libs /data/local/tmp/libtest.so --launch --bp-ld
    
    # Inject on a breakpoint at a specific symbol in a specific binary.
    ./AndKittyInjector --package com.target.package --libs /data/local/tmp/libtest.so \
        --launch --bp-sym /libc.so malloc
  2. Implement JNI_OnLoad for injected libraries

    main

    When writing a library intended to be injected by AndKittyInjector, follow these rules to ensure stability:

    1. Do NOT start threads in the library constructor.
    2. Use JNI_OnLoad for initialization logic.
    3. Verify the injector key: The injector passes a specific key (1337) to JNI_OnLoad. Check this key to confirm the call originated from the injector before proceeding with sensitive initialization.

    Example implementation:

    extern "C" jint JNIEXPORT JNI_OnLoad(JavaVM* vm, void *key)
    {
        // key 1337 is passed by the injector to confirm this call came from it.
        if (key != (void*)1337)
            return JNI_VERSION_1_6;
    
        KITTY_LOGI("JNI_OnLoad called by injector.");
    
        JNIEnv *env = nullptr;
        if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) == JNI_OK)
        {
            KITTY_LOGI("JavaEnv: %p.", env);
            // ...
        }
    
        std::thread(thread_function).detach();
    
        return JNI_VERSION_1_6;
    }
  3. Compile AndKittyInjector from source

    main

    To build the project, you need the Android NDK, CMake, and MAKE installed and available in your system's PATH. You must also set the NDK_HOME environment variable to point to your NDK folder.

    Build Steps:

    1. Clone the repository recursively (to include KittyMemoryEx as a submodule).
    2. Navigate to the source directory.
    3. Run the build script.
    git clone --recursive https://github.com/MJx0/AndKittyInjector.git
    cd AndKittyInjector/AndKittyInjector
    ./build.sh
  4. Use the AndKittyInjector CLI

    main

    AndKittyInjector is a ptrace-based Android shared-library injector. You can target a process using either its PID or its package name. You must provide one or more library paths to be injected via the --libs flag.

    Requirements:

    • The binary must be pushed to Android and have execution permissions (chmod +x).
    • Libraries should be located in /data (e.g., /data/local/tmp/).

    Basic Usage Pattern: ./AndKittyInjector [--pid <id> | --package <name>] --libs <path1> <path2> ... [options]

    # Inject into an already-running process by PID.
    ./AndKittyInjector --pid 12345 --libs /data/local/tmp/libtest.so
    
    # Inject into an already-running process by package name.
    ./AndKittyInjector --package com.target.package --libs /data/local/tmp/libtest.so
  5. Advanced injection: Hiding and Unloading

    main

    For stealthier or cleaner injections, use the following options:

    • --hide: This option makes the injected library harder to detect. It removes the library from the soinfo list (solist/sonext), remaps the library to anonymous memory, and randomizes the ELF header.
    • --free: This option instructs the injector to unload the library automatically after its entry point (e.g., JNI_OnLoad) has finished executing. This is useful for one-time setup tasks that do not need to persist in the process memory.
  6. How process monitoring modes work

    main

    AndKittyInjector provides two primary ways to handle process lifecycle during injection:

    1. Direct Injection: If you provide a --pid or --package without monitoring flags, the tool attempts to find the existing process and inject immediately (after an optional --delay).
    2. Monitoring Modes:
      • --launch: The tool will attempt to launch the specified --package using Android's Activity Manager and then inject.
      • --watch: The tool monitors the system for the appearance of the specified --package. Once the process starts, it uses inotify to watch the /proc/<pid> directory and triggers injection as soon as the process initializes (skipping fd and timerslack events to ensure injection happens at the right moment during linker initialization).

    Note: You cannot use --pid simultaneously with --launch or --watch.

  7. How breakpoint-based injection works

    main

    You can delay injection until a specific event occurs in the target process using the breakpoint (--bp) flags:

    • --bp-ld: Triggers injection immediately after the first native or emulated dlopen call is detected.
    • --bp-sym <binary> <symbol>: Sets a breakpoint on a specific symbol within a specific binary (e.g., /system/lib64/libc.so malloc). The injector waits for this breakpoint to be hit before proceeding with the library injection.

    When using breakpoint modes, the tool effectively pauses the target's execution flow at the desired point to ensure the injected libraries are loaded at a specific stage of the application lifecycle.

  8. Use AndKittyInjector CLI to inject libraries

    main

    AndKittyInjector is a command-line tool used to inject one or more shared libraries into an Android process. You can target a process either by its PID or by its package name. The tool supports various injection modes, including launching a new process, watching for a process to start, or triggering injection based on specific breakpoints.

    # Example: Inject libraries into a running process by PID
    ./AndKittyInjector --pid 1234 --libs /path/to/lib1.so /path/to/lib2.so
    
    # Example: Inject libraries into a package by watching for its start
    ./AndKittyInjector --package com.example.app --libs /path/to/lib1.so --watch
    
    # Example: Inject using a breakpoint on a specific symbol
    ./AndKittyInjector --package com.example.app --libs /path/to/lib1.so --bp-sym /system/lib64/libc.so malloc
  9. Embed AndKittyInjector as a library

    main

    You can integrate KittyInjector directly into your own C++ tools instead of using the CLI. This requires manual management of the KittyMemoryMgr and the injection lifecycle.

    Workflow:

    1. Initialize KittyMemoryMgr and its trace component.
    2. seize() or attach() to the target PID.
    3. Initialize the memory manager with kMgr.initialize(...).
    4. Initialize the KittyInjector instance with the manager and a configuration object.
    5. Validate the ELF file using injector.validateElf(...).
    6. Call injector.inject(...) to perform the injection.
    7. detach() the trace when finished.

    Note: inject_elf_config_t provides beforeEntryPoint and afterEntryPoint callbacks which are invoked around the remote JNI_OnLoad call.

    #include "Injector/KittyInjector.hpp"
    
    inject_elf_config_t cfg{};
    // init config
    
    KittyMemoryMgr kMgr;
    // manual early init of kMgr.trace to stop process then take our time to kMgr.initialize(...)
    kMgr.trace = KittyTraceMgr(targetPid, 0, true);
    kMgr.trace.seize();      // or .attach() on older SDKs
    kMgr.trace.stop();
    kMgr.initialize(targetPid, EK_MEM_OP_SYSCALL, true);
    
    KittyInjector injector;
    injector.init(&kMgr, cfg);
    
    bool needsEmulation = false;
    if (!injector.validateElf("/data/local/tmp/libtest.so", nullptr, &needsEmulation))
    {
        // Failed to validate lib
    }
    
    inject_elf_info_t result = injector.inject("/data/local/tmp/libtest.so");
    if (result.is_valid())
    {
        // result.dl_handle, result.soinfo, result.pJvm, result.pJNI_OnLoad are populated
    }
    
    kMgr.trace.detach();
  10. Configure injection settings with inject_elf_config_t

    main

    The inject_elf_config_t struct defines the behavior and parameters for the injection process. Use this struct to control how the injector targets processes, handles memory, and manages lifecycle callbacks.

    Key Configuration Fields

    FieldTypeDescription
    packagestd::stringThe target package name to watch or launch.
    sdkintTarget SDK level.
    rtdl_flagsintFlags passed to the linker (e.g., RTLD_LOCAL, RTLD_NOW). Defaults to RTLD_LOCAL | RTLD_NOW.
    delayintDelay in milliseconds before injection.
    timeoutintRemote-call timeout.
    watchboolIf true, waits for the target package to appear.
    launchboolIf true, launches the target app.
    seizeboolIf true, intercepts the process.
    bpboolIf true, uses a breakpoint for injection.
    memfdboolIf true, uses memfd_create for fileless injection.
    freeboolIf true, frees the library after injection.
    hideboolIf true, attempts to hide the library from /proc/pid/maps and the linker.
    bp_argsstd::vector<std::string>Arguments for the breakpoint mechanism.
    beforeEntryPointstd::functionCallback executed before the library's entry point. Receives inject_elf_info_t.
    afterEntryPointstd::functionCallback executed after the library's entry point. Receives inject_elf_info_t.
  11. AndKittyInjector CLI Reference

    main

    The following flags are available for the AndKittyInjector command-line tool:

    FlagDescription
    --pid <id>Target process ID. Mutually exclusive with --package. Cannot be used with --launch or --watch.
    --package <name>Target package name. If --pid is used instead, the package name is auto-resolved.
    --libs <paths>...Required. One or more library paths to be injected in order.
    --launchLaunch the process, then inject.
    --watchWait for the process to start, then inject.
    --bp-ldInject after the first native/emulated dlopen breakpoint hit.
    --bp-sym <binary> <sym>Inject after the first breakpoint hit on <sym> inside <binary> (e.g., /libc.so malloc).
    --delay <micros>Delay injection by the specified number of microseconds.
    --timeout <ms>Timeout for ptrace remote calls in milliseconds.
    --memfdUse memfd-backed dlopen to bypass SELinux path restrictions.
    --freeUnload the library after its entry point returns.
    --hideRemove the lib's soinfo from solist/sonext, remap segments to anonymous memory, and randomize ELF header.
    Usage: AndKittyInjector [--help] [--version] (--pid <id> | --package <name>) --libs <paths>...
                             [--launch | --watch] [--bp-ld | --bp-sym <binary> <symbol>]
                             [--delay <micros>] [--timeout <ms>] [--memfd] [--free] [--hide]
  12. Initialize and use the KittyInjector class

    main

    The KittyInjector class is the primary interface for managing library injection. To use it, you must first initialize it with a KittyMemoryMgr instance and an inject_elf_config_t configuration object.

    Workflow

    1. Initialize: Call init(kmgr, cfg) to set up the memory manager and injection parameters.
    2. Inject: Call inject(elfPath) providing the path to the .so file you wish to inject. This returns an inject_elf_info_t object containing metadata about the injected library.

    Important Methods

    • bool init(KittyMemoryMgr *kmgr, const inject_elf_config_t &cfg): Initializes the injector. Returns true on success.
    • inject_elf_info_t inject(const std::string &elfPath): Performs the injection of the specified ELF file into the target process.
    • KittyMemoryMgr *KMgr(): Returns the pointer to the internal memory manager.
    // Example initialization and injection
    KittyMemoryMgr kmgr;
    inject_elf_config_t cfg;
    cfg.package = "com.example.targetapp";
    cfg.watch = true;
    cfg.hide = true;
    
    KittyInjector injector;
    if (injector.init(&kmgr, cfg)) {
        inject_elf_info_t info = injector.inject("/data/local/tmp/libinjected.so");
        if (info.is_valid()) {
            // Injection successful
        }
    }