Skip to content

Black Sand Beacon — a lightweight, memory-resident micro beacon or implant for the LazyOwn RedTeam Framework — is the first offensive platform to deliver true native BOF (Beacon Object File) support for Linux. Inspired by the Windows-based Black Basalt Beacon, it enables red teams to write position-independent, fileless post-exploitation modules

License

Notifications You must be signed in to change notification settings

grisuno/blacksandbeacon

🌋 Black Sand Beacon

image

Black Sand Beacon — a lightweight, memory-resident micro beacon or implant for the LazyOwn RedTeam Framework — is the first offensive platform to deliver true native BOF (Beacon Object File) support for Linux. Inspired by the Windows-based Black Basalt Beacon, it enables red teams to write position-independent, fileless post-exploitation modules in pure C—no assembly required. Unlike traditional Linux payloads that rely on shellcode, interpreted scripts, or full binary deployment, Black Sand executes relocatable ELF objects directly in memory, resolving symbols and applying relocations on-the-fly. This breakthrough brings the agility, safety, and developer experience of Cobalt Strike–style BOFs to Linux for the very first time.

Linux ELF BOF Loader - First Native C Implementation

Background: After implementing COFFLoader3 (Windows COFF with DJB2 hashing) and BlackBasalt C2, this represents the first fully native C implementation of a Cobalt Strike-style beacon for Linux with complete ELF BOF support.

Key Features

  • ✅ Native C (no Go/Rust runtime)
  • ✅ Full ELF relocation support (R_X86_64_64/32/PC32/PLT32)
  • ✅ Automatic trampoline generation for long jumps
  • ✅ System V ABI compliant (variadic function support)
  • ✅ Beacon API compatible (BeaconPrintf, BeaconOutput)
  • ✅ AES-256-CFB encrypted C2 communication

Technical Innovations

  1. Trampoline Cache System: Solves Linux address space challenges
  2. Stack Alignment: Proper handling of movaps/variadic functions
  3. Symbol Resolution: Dynamic symbol table with fallback

Credits

  • Inspired by TrustedSec's COFFLoader (Windows)
  • Built upon experience with COFFLoader3 and BlackBasalt beacon

Comparison

Feature This Project Sliver Mythic CrossC2
Language C (native) Go Rust Closed
Open Source
Standalone Needs CS

📱 System Architecture

Black Sand Beacon is a modular C2 agent with a layered architecture consisting of:

  • Beacon Core: Main orchestrator implementing the command polling loop
  • Cryptography Layer: Dual encryption (TLS + AES-256 CFB) with Base64 encoding
  • Execution Engines: Shell command executor and in-memory ELF loader for BOFs
  • Data Processing: JSON serialization using cJSON library
  • Network Layer: HTTPS communication via libcurl with malleable C2 profiles
image

🚨 Beacon Core Loop

The main execution loop resides in main() at beacon3.c 637-808 The beacon operates in a continuous 6-second polling cycle:

image

🚀 Initialization

beacon3.c 638-648 :

🔑 Generates AES key from hex string KEY_HEX

Constructs C2 URL using C2_URL, MALEABLE, and CLIENT_ID constants Initializes random seed for User-Agent rotation Command Retrieval beacon3.c 650-693 :

image

📥 Issues HTTPS GET request to C2 server

Base64 decodes response Extracts IV from first 16 bytes AES-256 CFB decrypts command payload Command Execution beacon3.c 695-716 :

📟 Shell commands: Execute via exec_cmd() using popen()

BOF commands: Download ELF via download_bof(), execute via RunELF() Result Transmission beacon3.c 726-796 :

{ } Constructs JSON with system metadata and command output

AES-256 CFB encrypts with random IV Base64 encodes payload POSTs to C2 server

🇨➁ Malleable C2 Profile

URL construction uses configurable components beacon3.c 32-34 :

  • C2_URL: Base server address
  • MALEABLE: URI path pattern (/pleasesubscribe/v1/users/)
  • CLIENT_ID: Beacon identifier (linux)

📡 C2 Communication Protocol

This document describes the HTTPS-based command and control communication protocol used by the Black Sand Beacon to communicate with the LazyOwn RedTeam Framework C2 server. It covers the transport layer, encryption mechanisms, message encoding, request/response patterns, and malleable C2 profile configuration.

For information about command execution after receiving C2 instructions, see Command Execution. For details on the cryptographic implementations, see Encryption Protocol.

🗂️ Protocol Overview

The Black Sand Beacon implements a pull-based C2 protocol over HTTPS with dual-layer encryption. The beacon continuously polls the C2 server at regular intervals to retrieve commands, executes them, and transmits results back to the server. All communications use TLS for transport security plus an additional application-level AES-256 CFB encryption layer.

✈️ Transport Layer

🌍 HTTPS Configuration

The beacon uses libcurl for all HTTP/HTTPS communications. The https_request function handles both command retrieval (GET) and result transmission (POST).

CURL Configuration Details:

The https_request function beacon3.c 154-240 configures the following critical options:

🛡️ SSL Verification Disabled

beacon3.c 175-176 : Both CURLOPT_SSL_VERIFYPEER and CURLOPT_SSL_VERIFYHOST are set to 0L to allow connections to C2 servers with self-signed certificates Timeout Settings beacon3.c 177-178 : 10-second overall timeout, 5-second connection timeout User-Agent Randomization beacon3.c 174 : Randomly selects from USER_AGENTS array to vary traffic fingerprint Memory Callback beacon3.c 180-181 : Uses WriteMemoryCallback to accumulate response data in a dynamically allocated buffer Sources: beacon3.c 154-240

beacon3.c 136-151

🔗 URL Construction and Malleable Profiles

The beacon constructs C2 URLs using configurable components to enable traffic blending:

Malleable C2 Configuration:

Constant Default Value Purpose Location C2_URL "https://10.10.14.57:4444" Base C2 server address beacon3.c 32 MALEABLE "/pleasesubscribe/v1/users/" URI path for traffic blending beacon3.c 34 CLIENT_ID "linux" Client identifier beacon3.c 33 The full URL is constructed at initialization: full_url = C2_URL + MALEABLE + CLIENT_ID beacon3.c 646-647

🕷️ User-Agent Rotation:

The beacon randomly selects from 4 User-Agent strings beacon3.c 37-42 on each request beacon3.c 174 :

  • Chrome on Linux x86_64
  • Firefox on Ubuntu
  • Chrome on Android mobile
  • Chrome on Windows 10 Sources: beacon3.c 32-42

beacon3.c 646-647

beacon3.c 174

🪦 Encryption Layer

AES-256 CFB Implementation The beacon implements a custom AES-256 Cipher Feedback (CFB) mode for application-level encryption. This provides an additional security layer beyond TLS, protecting payloads even if TLS is compromised.

CFB Mode Implementation Details:

Both aes256_cfb_encrypt beacon3.c 283-309 and aes256_cfb_decrypt beacon3.c 311-338 follow the CFB algorithm:

➡️ Initialization

beacon3.c 285-289

beacon3.c 313-317 : Initialize AES context with 256-bit key, copy IV to working buffer Block Processing beacon3.c 291-306

beacon3.c 319-334 : Encrypt IV buffer using AES-ECB: AES_ECB_encrypt(&ctx, encrypted_iv) beacon3.c 294

beacon3.c 322 XOR result with plaintext/ciphertext block beacon3.c 296-298

beacon3.c 324-326 Update IV buffer with output ciphertext beacon3.c 300-304

beacon3.c 328-332 Output beacon3.c 307-308

beacon3.c 335-337 : Return encrypted/decrypted buffer Sources: beacon3.c 283-338

aes.c 239-242

aes.c 490-494

🗝️ Key Management

The AES-256 key is hardcoded in the beacon binary as a hexadecimal string and converted to binary at runtime:

// Key initialization in main() const char* KEY_HEX = "88a41baa358a779c346d3ea784bc03f50900141bb58435f4c50864c82ff624ff"; unsigned char AES_KEY[32]; for (int i = 0; i < 32; i++) { sscanf(KEY_HEX + i * 2, "%2hhx", &AES_KEY[i]); }

🐚 IV Generation:

Outbound Messages: Random 16-byte IV generated using OpenSSL's RAND_bytes(iv_out, 16) beacon3.c 763 Inbound Messages: IV extracted from first 16 bytes of encrypted payload beacon3.c 675-676 Sources: beacon3.c 640-644

beacon3.c 763

beacon3.c 675-676

Message Format Inbound Message Structure (Commands) Commands received from the C2 server follow this structure:

🏗️ Processing Pipeline:

Receive beacon3.c 656 : GET request to full_url returns Base64-encoded payload Decode beacon3.c 667 : base64_decode(b64_resp, &enc_len) → encrypted bytes Extract IV beacon3.c 675-676 : First 16 bytes are the IV, remainder is ciphertext Decrypt beacon3.c 678 : aes256_cfb_decrypt(AES_KEY, iv, ciphertext, enc_len - 16, &plain_len) → plaintext command Command Types:

⚡ Command Pattern Action Handler Location

bof: Download and execute BOF from URL beacon3.c 698-712 Any other string Execute as shell command via popen() beacon3.c 714 Sources: beacon3.c 656-693

beacon3.c 698-716

beacon3.c 243-280

beacon3.c 311-338

📊 Outbound Message Structure (Results)

Results transmitted to the C2 server are JSON objects encrypted and encoded:

image

JSON Structure:

The beacon constructs a JSON object using the cJSON library beacon3.c 734-744 :

{
  "output": "<command/BOF output>",
  "client": "linux",
  "command": "<executed command>",
  "pid": 1234,
  "hostname": "<system hostname>",
  "ips": "<comma-separated local IPs>",
  "user": "<username>",
  "discovered_ips": "",
  "result_portscan": null,
  "result_pwd": "<current working directory>"
}
image

📩 Encryption and Transmission:

Serialize beacon3.c 746 : cJSON_PrintUnformatted(root) → JSON string Generate IV beacon3.c 763 : RAND_bytes(iv_out, 16) → random IV Encrypt beacon3.c 765 : aes256_cfb_encrypt(AES_KEY, iv_out, json_str, strlen(json_str), &encrypted_len) → ciphertext Concatenate beacon3.c 768-770 : IV || Ciphertext → combined buffer Encode beacon3.c 771 : base64_encode(full_enc, 16 + encrypted_len) → Base64 string Transmit beacon3.c 777 : https_request(full_url, "POST", b64_resp) → send to C2 Sources: beacon3.c 726-796

beacon3.c 243-258

beacon3.c 283-309

image

💬 Request/Response Flow

Main Communication Loop The beacon operates in a continuous polling loop with a fixed interval:

🧙🏼‍♂️ BOF Concept

A Beacon Object File (BOF) is a compiled ELF relocatable object file (.o) that contains custom functionality to be executed by the beacon. BOFs enable modular capability extension without modifying the core beacon executable.

image

⚙️ ELF Loading Mechanism

The RunELF function at beacon3.c 361-532 implements a complete in-memory ELF loader that parses relocatable object files and prepares them for execution.

image

🗂️ Symbol Resolution

BOFs reference external functions (libc, beacon API) that must be resolved at load time. The beacon maintains a symbol resolver table that maps symbol names to function pointers.

🧠 Symbol Resolver Table

The g_external_symbols[] array at beacon3.c 76-90 defines all externally visible symbols:

👉 Symbol Name Pointer Variable Purpose

  • printf g_printf_ptr Standard output (debugging)
  • strlen g_strlen_ptr String length calculation
  • memcpy g_memcpy_ptr Memory copy operations
  • memset g_memset_ptr Memory initialization
  • BeaconPrintf g_BeaconPrintf_ptr Formatted output to beacon
  • BeaconOutput g_BeaconOutput_ptr Raw output to beacon
  • dlsym g_dlsym_ptr Dynamic symbol lookup
  • dlopen g_dlopen_ptr Dynamic library loading

🧮 Symbol Resolution Algorithm

Sources: beacon3.c 461-470

image

🧩 The resolution process at

beacon3.c 461-470 first checks if a symbol is defined within the BOF itself (local symbol), then falls back to the external symbol table. Unresolved symbols are logged but do not halt execution.

📐 Relocation Processing

After loading sections into memory, the loader must patch code and data references to point to their actual runtime addresses. This is accomplished by processing SHT_RELA sections.

🧬 Supported Relocation Types

Type Value Description Implementation

  • R_X86_64_64 1 64-bit absolute address *loc = symbol_addr + addend
  • R_X86_64_PC32 2 32-bit PC-relative offset *loc = symbol_addr + addend - loc
  • R_X86_64_PLT32 4 32-bit PLT-relative offset Same as PC32
  • R_X86_64_32 10 32-bit absolute address *loc = (uint32_t)(symbol_addr + addend)

👨🏻‍💻 Execution Model

Once the ELF is loaded and relocated, the beacon locates the go function and executes it in an isolated stack frame.

e = ∑∞ⁿ⁼⁰ ¹ₙ Entry Point Discovery

The loader searches the symbol table for a function named go:

image

🚩 Overview

The ELF loader is implemented in the RunELF function, which performs the following operations:

image
  • ELF validation - Verifies ELF magic bytes and architecture
  • Section parsing - Locates symbol tables, string tables, and loadable sections
  • Memory mapping - Allocates RWX memory regions for code and data sections
  • Symbol resolution - Resolves external symbols using dlsym and a predefined symbol table
  • Relocation processing - Applies ELF relocations to resolve references
  • Function lookup - Locates the entry point function by name
  • Execution - Invokes the BOF using a stack-aligned assembly wrapper
image image

🔷 Gopher Protocol C2

Black Sand Beacon now includes full support for the Gopher protocol as a command and control (C2) channel. The Gopher protocol (RFC 1436) is a lightweight, simple communication protocol designed in the early 1990s that offers unique advantages for modern offensive operations.​

Why Gopher?

Evasion: Extremely rare protocol in modern networks, making detection by traditional IDS/IPS systems difficult​

Simplicity: Minimalist protocol that generates less network noise than HTTP/HTTPS​

Low profile: Most security tools do not inspect Gopher traffic​

Flexible: Supports transmission of text, binaries, and encrypted commands​

Legacy advantage: Many firewalls and monitoring solutions lack proper Gopher protocol inspection​

Features

✅ AES-256-CFB encryption for all communications

✅ Base64-encoded command and result transmission

✅ Support for remote BOF (Beacon Object Files) downloads

✅ Automatic logging in CSV format

✅ Multi-client session management

✅ Customizable port (default 7070, standard 70)

✅ Simple selector-based routing

Installation

Requirements

pip3 install cryptography

Directory Structure

c2_gopher/
├── c2_gopher.py          # Main C2 server
├── beacon_gopher         # Compiled beacon binary
├── sessions/
│   ├── logs/            # Client session logs (CSV)
│   └── uploads/         # BOF files for download

Quick Start

  1. Generate AES Key
python3 -c "import os; print(os.urandom(32).hex())"

Update the AES_KEY variable in both c2_gopher.py and recompile beacon_gopher with the new key.

  1. Start the C2 Server
python3 c2_gopher.py

The server will listen on gopher://0.0.0.0:7070/ by default.

  1. Deploy the Beacon
./beacon_gopher

The beacon will start checking for commands from the C2 server.

  1. Issue Commands In the C2 server terminal, you'll be prompted to enter commands:
Client ID: linux
Command: whoami
[+] Command 'whoami' queued for linux

Protocol Flow

Command Retrieval (Beacon → C2) Beacon connects to C2 on port 7070

Sends selector: /pleasesubscribe/v1/users/<client_id>

C2 responds with AES-encrypted command (Base64)

Beacon decrypts and executes command

Result Submission (Beacon → C2) Beacon encrypts execution result with AES

Encodes as Base64

Sends selector: /report/<base64_payload>

C2 decrypts, parses JSON, and logs to CSV

BOF Download (Beacon → C2) Beacon requests: /bof/

C2 responds with Base64-encoded BOF

Beacon decodes and executes in memory

🔗 Links (Because Sharing Is Power)

Python Shell Script Flask License: GPL v3

ko-fi

About

Black Sand Beacon — a lightweight, memory-resident micro beacon or implant for the LazyOwn RedTeam Framework — is the first offensive platform to deliver true native BOF (Beacon Object File) support for Linux. Inspired by the Windows-based Black Basalt Beacon, it enables red teams to write position-independent, fileless post-exploitation modules

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

No packages published