frida-rust

repository·main·Indexed 43 days ago

https://github.com/frida/frida-rust

Rust bindings for the Frida instrumentation toolkit. It enables high-performance instrumentation logic, including the ability to precompile JavaScript scripts to bytecode, inject library blobs or files into running processes via PID, and utilize Frida-Gum for low-level operations like hooking libc functions, generating backtraces with Backtracer, and inspecting disassembled instructions using the Insn struct.

Tokens
21.8K
Snippets
69
Records
100
Agent score
54%

What's inside frida-rust

  1. Precompile JavaScript scripts to bytecode

    main

    You can precompile JavaScript scripts into runtime-specific bytecode to improve loading performance or to ship scripts as binary artifacts. This is achieved using a two-step process:

    1. Compile: Use Session::compile_script to transform a JavaScript string into bytecode.
    2. Load: Use Session::create_script_from_bytes to load that bytecode back into a session for execution.

    Important: Runtime Compatibility Bytecode is runtime-specific. If you compile a script using ScriptRuntime::QJS, the resulting bytecode can only be loaded by a session using the QJS runtime. A blob produced for QJS will not work under V8 or other runtimes.

    // Conceptual workflow based on the example:
    
    // 1. Compile the script to bytecode
    let bytecode = session.compile_script("console.log('Hello!');", ScriptRuntime::QJS)?;
    
    // 2. (Optional) Save bytecode to a file
    // std::fs::write("compiled.bin", &bytecode)?;
    
    // 3. Load the bytecode back from bytes
    let script = session.create_script_from_bytes(bytecode, ScriptRuntime::QJS)?;
    
    // 4. Run the script
    script.load()?;
  2. Inject a library file into a process

    main

    This example demonstrates how to build and use a tool to inject a shared library (.so file) into a running process identified by its PID.

    To use this example, you must first build both the library you wish to inject and the injection executable itself using cargo.

    # 1. Enter the example directory
    cd examples/core/inject_lib_file/
    
    # 2. Build the library and the executable
    cargo build --release --lib
    cargo build --release
    
    # 3. Execute the injector with <PID> and <LIB_FILE_PATH>
    ../../../target/release/inject_lib_file <PID> <LIB_FILE_PATH>
  3. Install frida-rust

    main

    To use frida-rust, you must first have the Frida devkits (specifically frida-gum and frida-core) available on your system. You can either build Frida from source or download the devkits for your specific platform.

    For Crate Installation (Standard Usage)

    If you are using frida-rust as a dependency in your own Rust projects, ensure the frida-gum and frida-core devkits are located in a directory included in your rustc-link-search path (for example, /usr/local/include and /usr/local/lib on Unix-like systems).

    For Local Development

    If you are developing frida-rust itself, follow these steps:

    1. Move the frida-gum devkit into the frida-gum-sys directory.
    2. Move the frida-core devkit into the frida-sys directory.
    3. Run cargo build from the repository root.
  4. Install frida-gum Rust bindings

    main

    To use frida-gum in a Rust project, you must have the underlying Frida Gum C library available on your system. You have two primary methods for installation:

    Method 1: Manual Installation

    1. Build Frida from source or download the latest supported package.
    2. Install the header file frida-gum.h into a directory your Rust compiler can find (e.g., /usr/local/include).
    3. Install the static library libfrida-gum.a into a directory your Rust compiler can find (e.g., /usr/local/lib).

    Method 2: Auto-download

    You can use the auto-download feature provided by the crate to automatically fetch and install the necessary Frida components during the build process.

  5. How Interceptor, Listener, and InvocationContext work together

    main

    The Interceptor is the central engine used to modify or observe function execution. It provides two primary modes of operation:

    1. Replacement Mode (replace, replace_fast): You provide a new function address. The engine redirects the original function to your replacement. This is used for changing behavior. You get the original_function address back to allow calling the original logic.
    2. Observation Mode (attach, attach_instruction): You provide a listener. The engine triggers your listener when the address is hit, but does not necessarily redirect the control flow in the same way as replacement. This returns a Listener object which acts as a handle for detaching.

    When using observation mode with the invocation-listener feature, the InvocationContext provides the metadata about the specific call being observed (like arguments and return values).

  6. How Stalker works for code tracing

    main

    Stalker is a code tracing engine that allows for fine-grained instrumentation of thread execution. It works by using a Transformer to modify basic blocks of code as they are executed.

    Key concepts include:

    • Stalker: The main engine instance. It can be initialized via new() or new_with_params() (on supported architectures like aarch64 or x86_64).
    • Transformer: A callback-based mechanism used to decide which instructions to keep or modify during execution.
    • EventSink: (Requires event-sink feature) An interface to receive and process events generated by the Stalker.
    • Trust Threshold: A mechanism to optimize performance. You can set how many times code must execute before Stalker assumes it won't mutate itself using set_trust_threshold(threshold). A threshold of -1 means no trust (slowest), 0 means immediate trust, and N means trust after N executions.
  7. Configure X86InstructionWriter target CPU and ABI

    main

    Before writing instructions that depend on specific calling conventions or register layouts, you must configure the writer's target environment using set_target_cpu and set_target_abi.

    • set_target_cpu(cpu_type: GumCpuType): Sets the target CPU architecture (e.g., IA32, AMD64).
    • set_target_abi(abi_type: GumAbiType): Sets the target ABI (e.g., Unix or Windows). This is critical for put_call_* methods as it determines how arguments are laid out in registers or on the stack.

    After configuration, you can use get_cpu_register_for_nth_argument(n: u32) to retrieve the register used for the $n$-th argument under the current configuration.

  8. Compile JavaScript to bytecode using compile_script

    main

    The compile_script method allows you to pre-compile JavaScript source code into a bytecode blob. This is useful for performance or for sending pre-compiled code to a target.

    Important Note on Runtimes: Bytecode is runtime-specific. A blob produced for the QJS runtime will not work under V8, and vice-versa. You must ensure that the ScriptRuntime specified in your ScriptOption matches the runtime used when you eventually load the bytes via create_script_from_bytes.

    As of Frida 16.x, the default runtime is QJS if not otherwise specified in the ScriptOption object.

    // Compiling source to bytes
    let mut option = ScriptOption::default();
    let bytecode = session.compile_script("console.log('hello');", &mut option)?;
    // 'bytecode' can now be stored or sent to another session
  9. Use Scope to control process enumeration metadata

    main

    When calling enumerate_processes_with_options, you can specify a Scope to control how much metadata Frida collects for each process. This affects the performance and the amount of information available in the returned Process objects.

    • Scope::Minimal: The default. Only populates the process name and pid.
    • Scope::Metadata: Includes lightweight extra metadata where the host platform provides it cheaply.
    • Scope::Full: The most detailed. Populates Process::get_parameters with information like ppid, path, user, and started time.
    // Enumerate processes with full metadata
    let processes = device.enumerate_processes_with_options(Scope::Full);
    
    for process in processes {
        let params = process.get_parameters();
        // Access ppid, path, etc.
    }
  10. Generate backtraces with Backtracer

    main

    The Backtracer struct provides methods to walk the stack and retrieve return addresses. It supports two modes of operation:

    1. Accurate: Provides a precise backtrace. This is more reliable but may be slower or more resource-intensive.
    2. Fuzzy: Provides a faster, heuristic-based backtrace that might be less precise.

    You can generate backtraces for the current execution context or for a specific GumCpuContext or signal context (ucontext_t). All methods return a Vec<usize> containing the list of return addresses.