bof-launcher

repository·main·Indexed 43 days ago

https://github.com/the-z-labs/bof-launcher

A high-performance, cross-platform library for managing and executing Beacon Object Files (BOFs) in-memory. It features stealth capabilities like sleep-masking and supports Windows (x86/x64) and Linux (x86, x64, ARMv6+, AArch64). The library provides a C API for BOF lifecycle management, execution routines (including async threads and processes), and memory masking. It serves as the foundation for the z-beac0n adversary simulation toolkit.

Tokens
4.7K
Snippets
10
Records
14
Agent score
55%

What's inside bof-launcher

  1. Overview of bof-launcher, z-beac0n, and Z-Labs BOFs

    main

    The bof-launcher repository consists of three main components:

    1. bof-launcher library: A standalone programming library (implemented in Zig and C) for in-memory management of Beacon Object Files (BOFs). It handles loading, tracking, execution, and masking. It supports Windows (x86/x64) and Linux (x86, x64, ARMv6+, AArch64).
    2. z-beac0n: A custom-written stage-1 (pre-C2) adversary simulation toolkit that utilizes the bof-launcher library. It is designed for a small footprint, stealth, and modularity.
    3. Z-Labs BOFs collection: A collection of OS-specific (Linux/Windows) and cross-platform BOFs designed for red team engagements.

    Developers can use the bof-launcher library to execute BOFs that adhere to standard Windows or Linux BOF templates.

  2. How the z-beac0n implant architecture works

    main

    The z-beac0n implant is a modular adversary simulation tool built using the bof-launcher library. It relies on a core component called BOF0 (the heart of the implant) which manages the entire lifecycle.

    Key Components:

    • BOF0 (Core BOF): The first BOF executed via bofRun(). It manages command processing, fetching new BOFs, and executing them.
    • C2 Channel (API-style BOF): A specialized BOF that implements the communication protocol (functions like netInit, netConnect, netExchange). Because this is a BOF, the communication protocol can be swapped at runtime by fetching a different C2 BOF.
    • bof-launcher: The underlying library providing BOF management, execution, and in-memory evasion (masking).
    • Additional BOFs: Arbitrary tasks launched by BOF0 using routines like bofObjectRun or async variants.
  3. Integrate BOF Runner with Rust

    main

    BOF Runner is a Rust-based CLI tool designed to load and execute Beacon Object Files (BOFs) on Windows (x64). It functions by linking against the static libraries produced by the bof-launcher repository.

    Note: The current version of this utility does not support passing arguments to the BOF; it only supports executing the BOF file itself.

  4. Build all components using Zig

    main

    To build the bof-launcher library, example programs, and all included BOFs, you must use the Zig programming language.

    1. Download Zig (version 0.15.2 recommended) and add it to your PATH.
    2. Run the build command from the repository root.

    Build artifacts are located in zig-out/bin and zig-out/lib.

    Build Commands

    • Standard build: zig build (builds all components for all supported platforms).
    • Debug build: zig build -Doptimize=Debug (builds BOFs as debuggable, standalone executables).
    • Test build: zig build test (builds and runs test BOFs).
    • Cross-architecture testing: Use QEMU with the following command to run tests on foreign CPU architectures: zig build test -fqemu --glibc-runtimes /usr
    zig build
    
    # For debuggable standalone executables
    zig build -Doptimize=Debug
    
    # To run tests
    zig build test
    
    # To run tests on foreign architectures using QEMU
    zig build test -fqemu --glibc-runtimes /usr
  5. Build and run the BOF Runner utility

    main

    Prerequisites

    • Windows 10/11 (x64)
    • Latest stable Rust toolchain

    Build Steps

    1. Navigate to the bof_runner directory.
    2. Run the cargo build command:
      cargo build --release
    3. The resulting executable will be located at target/release/bof_runner.exe.

    Execution

    Run the utility by passing the path to the BOF file as a single argument:

    bof_runner.exe <bof-filename>
    # Build the utility
    cargo build --release
    
    # Execute a BOF
    bof_runner.exe <bof-filename>
  6. Set up the BOF-stager C2 server

    main

    The BOF-stager C2 server is a simple HTTP-based solution that serves BOFs as post-exploitation modules.

    1. Configure the backend: Edit utils/serve_bofs.py to set your desired host and port within the app.run() call.
    2. Prepare the directory structure: Create a doc_root/bofs/ directory and copy the server script into doc_root/.
    3. Deploy BOFs: Copy your compiled BOF files (e.g., .o files) into doc_root/bofs/.
    4. Start the server: Run the Python script from the doc_root directory.
    # Configure host/port in utils/serve_bofs.py first
    
    mkdir -p doc_root/bofs/
    cp bof-launcher/utils/serve_bofs.py doc_root/
    
    # Copy a BOF to the bofs directory
    cp bof-launcher/zig-out/bin/uname.elf.x64.o doc_root/bofs/
    
    cd doc_root/
    python serve_bofs.py
  7. Integrate bof-launcher with a Go application

    main

    You can use bof-launcher within a Go application by leveraging CGO to interface with the C headers and static library.

    To successfully build the Go application, you must provide the bof_launcher_api.h header and the corresponding static library (e.g., libbof_launcher_lin_x64.a) in a local directory accessible to the Go compiler.

    Required Directory Structure

    Ensure your project follows this structure so CGO can locate the necessary files:

    bofsFromGo/
    ├── cgo.go
    ├── lib/
    │   ├── bof_launcher_api.h
    │   └── libbof_launcher_lin_x64.a
    └── uname.elf.x64.o

    Note: bof_launcher_api.h and libbof_launcher_lin_x64.a must be sourced directly from the bof-launcher repository.

    # Build the Go application
    go build -o bofsFromGo ./cgo.go
    
    # Run the application with a BOF object file
    ./bofsFromGo uname.elf.x64.o
  8. Build and run the BOF-stager client

    main

    The client is written in Zig and communicates with the C2 server via HTTP.

    1. Configure the client: In src/main.zig, define the c2_host (e.g., 127.0.0.1:8000), the c2_endpoint (e.g., /endpoint), and the jitter value.
    2. Build: Use zig build to compile the project.
    3. Execute: Run the resulting binary from zig-out/bin/.
    # Configuration snippet from src/main.zig
    const c2_host = "127.0.0.1:8000";
    const c2_endpoint = "/endpoint";
    const jitter = 3;
    
    # Build and run
    zig build
    ./zig-out/bin/bof-stager_lin_x64
  9. Set up the BOF Runner project structure

    main

    To use BOF Runner, you must first build the bof-launcher libraries and then organize your Rust project directory as follows. The static library from the bof-launcher build process must be placed in a lib/ directory within the bof_runner folder.

    Required Directory Structure:

    bof_runner/
    ├── src/
    │   └── main.rs          # Main application logic
    ├── lib/
    │   └── bof_launcher_win_x64.lib
    ├── Cargo.toml           # Rust project configuration
    └── README.md            # This file
    bof_runner/
    ├── src/
    │   └── main.rs
    ├── lib/
    │   └── bof_launcher_win_x64.lib
    ├── Cargo.toml
    └── README.md
  10. Deploy z-beac0n for testing and experimentation

    main

    To run the z-beac0n implant locally for testing, follow these steps:

    1. Build all components: Use the Zig build system from the repository root.
    2. Prepare the C2 server: Copy the compiled BOF binaries for all supported architectures into the implant directory so the server can serve them.
    3. Launch the C2 server: Run the Python C2 script (defaults to 127.0.0.1:8000).
    4. Launch the Operator Console: Run the Python console script to interact with the implant via the server's endpoints.
    5. Run the implant: Execute the compiled binary (ELF, PE, or shellcode) on the target machine.

    C2 Server Endpoints:

    • /tasking: Used by the operator to issue tasks (POST) and retrieve output (GET).
    • /heartbeat: Used by the implant to query for tasks (GET) and send output (POST).
    # 1. Build components
    zig build
    
    # 2. Prepare C2 server binaries
    cd examples/implant
    cp -r ../../zig-out/bin/bofs/ ./
    
    # 3. Launch server
    python z-beac0n-C2.py
    
    # 4. Launch console (in another terminal)
    python z-beac0n-console.py
    
    # 5. Run implant (example for Linux ELF)
    ./zig-out/bin/z-beac0n_lin_x64.elf
  11. Task a client to run a BOF via HTTP

    main

    You can task a client to execute a specific BOF by sending a POST request to the C2 server's tasking endpoint. The request must include a JSON payload specifying the BOF name and the header format.

    Payload Keys:

    • name: The identifier for the BOF (e.g., bof:uname).
    • header: The header type, such as inline:z.
    curl -H 'Content-Type: application/json' \
         -d '{ "name" : "bof:uname", "header" : "inline:z" }' \
         http://127.0.0.1:8000/tasking
  12. bof-launcher C API Reference

    main

    The bof-launcher library exposes a C API for managing BOF lifecycles, memory masking, and execution.

    Initialization and Cleanup

    • int bofLauncherInit(void): Initializes the library.
    • void bofLauncherRelease(void): Releases library resources.

    Memory Masking

    • int bofMemoryMaskKey(const unsigned char* key, int key_len): Sets a key for memory masking.
    • int bofMemoryMaskWin32ApiCall(const char* win32_api_name, int masking_enabled): Enables/disables masking for specific Win32 API calls.

    Object Management

    • int bofObjectInitFromMemory(const unsigned char* file_data_ptr, int file_data_len, BofObjectHandle* out_bof_handle): Loads a BOF from memory.
    • void bofObjectRelease(BofObjectHandle bof_handle): Releases a specific BOF object.
    • int bofObjectIsValid(BofObjectHandle bof_handle): Checks if a handle is valid.
    • void* bofObjectGetProcAddress(BofObjectHandle bof_handle, const char* name): Retrieves a procedure address from the BOF.

    Execution

    • int bofRun(const unsigned char* file_data_ptr, int file_data_len): Runs a BOF directly from memory.
    • int bofObjectRun(BofObjectHandle bof_handle, unsigned char* arg_data_ptr, int arg_data_len, BofContext** out_context): Runs a loaded BOF object with arguments.
    • int bofObjectRunAsyncThread(...): Runs a BOF in a separate thread.
    • int bofObjectRunAsyncProcess(...): Runs a BOF in a separate sacrificial process.

    Context and Arguments

    • void bofContextRelease(BofContext* context): Releases execution context.
    • int bofContextIsRunning(BofContext* context): Checks if the context is still active.
    • void bofContextWait(BofContext* context): Waits for context completion.
    • unsigned char bofContextGetExitCode(BofContext* context): Gets the exit code.
    • const char* bofContextGetOutput(BofContext* context, int* out_output_len): Retrieves output from the BOF.
    • int bofArgsInit(BofArgs** out_args): Initializes an argument buffer.
    • int bofArgsAdd(BofArgs* args, unsigned char* arg, int arg_len): Adds arguments to the buffer.
    • void bofArgsEnd(BofArgs* args): Finalizes the argument buffer.
    int bofLauncherInit(void);
    void bofLauncherRelease(void);
    
    int bofMemoryMaskKey(const unsigned char* key, int key_len);
    int bofMemoryMaskWin32ApiCall(const char* win32_api_name, int masking_enabled);
    
    int bofObjectInitFromMemory(const unsigned char* file_data_ptr, int file_data_len, BofObjectHandle* out_bof_handle);
    
    void bofObjectRelease(BofObjectHandle bof_handle);
    int bofObjectIsValid(BofObjectHandle bof_handle);
    
    void* bofObjectGetProcAddress(BofObjectHandle bof_handle, const char* name);
    
    int bofRun(const unsigned char* file_data_ptr, int file_data_len);
    int bofObjectRun(BofObjectHandle bof_handle,
                 unsigned char* arg_data_ptr,
                 int arg_data_len,
                 BofContext** out_context);
    int bofObjectRunAsyncThread(BofObjectHandle bof_handle,
                 unsigned char* arg_data_ptr,
                 int arg_data_len,
                 BofCompletionCallback completion_cb,
                 void* completion_cb_context,
                 BofContext** out_context);
    int bofObjectRunAsyncProcess(BofObjectHandle bof_handle,
                 unsigned char* arg_data_ptr,
                 int arg_data_len,
                 BofCompletionCallback completion_cb,
                 void* completion_cb_context,
                 BofContext** out_context);
    
    void bofContextRelease(BofContext* context);
    int bofContextIsRunning(BofContext* context);
    void bofContextWait(BofContext* context);
    unsigned char bofContextGetExitCode(BofContext* context);
    const char* bofContextGetOutput(BofContext* context, int* out_output_len);
    BofObjectHandle bofContextGetObjectHandle(BofContext* context);
    
    int bofArgsInit(BofArgs** out_args);
    void bofArgsRelease(BofArgs* args);
    int bofArgsAdd(BofArgs* args, unsigned char* arg, int arg_len);
    void bofArgsBegin(BofArgs* args);
    void bofArgsEnd(BofArgs* args);
    const char* bofArgsGetBuffer(BofArgs* args);
    int bofArgsGetBufferSize(BofArgs* args);