Standalone macOS POC for loading and executing BOFs.
cd poc/bof-runner
GOWORK=off go build -o bof-runner ../bof-runner <bof.dylib|bof.bin> [args-json]All BOFs must export the standard entry point:
void go(char* args, int args_len, char* output);| Extension | Mode | Description |
|---|---|---|
.dylib / .so |
dlopen/dlsym | Shared library loaded by dyld. Supports libc calls (snprintf, gethostname, etc). |
.bin |
mmap shellcode | Raw .text bytes mmap'd as RWX. Self-contained code only — no external symbol references. |
Both modes include signal-based crash recovery (SIGSEGV, SIGBUS, SIGILL, SIGABRT).
./build-test-bof.shProduces four test BOFs in test-bofs/:
| BOF | Description |
|---|---|
hostname.dylib |
System hostname |
sysinfo.dylib |
Kernel/OS info via uname() |
whoami.dylib |
UID, username, home dir, shell |
echo.dylib |
Echoes back the args JSON |
# Run a test BOF
./bof-runner test-bofs/hostname.dylib
# Pass arguments
./bof-runner test-bofs/echo.dylib '{"hello":"world"}'
# Compile and run an existing BOF from Bridge/bofs/src/
clang -shared -fPIC -Os -o test.dylib ../../Bridge/bofs/src/sysinfo.c
./bof-runner test.dylib#include <stdio.h>
#include <string.h>
void go(char* args, int len, char* output) {
snprintf(output, 4096, "Hello from BOF! Args: %.*s", len, args);
}Compile:
clang -shared -fPIC -Os -o mybof.dylib mybof.c
./bof-runner mybof.dylibExisting BOFs (TrustedSec SA, etc.) are typically Windows COFF objects that use the Beacon API. bof-runner expects macOS dylibs with a different output model:
| Aspect | TrustedSec / community BOFs | bof-runner |
|---|---|---|
| Format | COFF object files (.o) |
.dylib or .bin shellcode |
| Platform | Windows x86/x64 | macOS arm64/x86_64 |
| Output | BeaconOutput(), BeaconPrintf() |
Write directly to output buffer |
| Args | BeaconDataParse() |
Raw args pointer + len |
You cannot load a Windows .o file directly. Conversion requires source and one of:
1. Source port (recommended) — Replace BeaconOutput/BeaconPrintf with writes to the output buffer, replace BeaconDataParse with manual parsing of args, and swap Windows APIs for POSIX equivalents. Then compile as a dylib.
2. Beacon compatibility shim — Implement BeaconOutput, BeaconPrintf, and BeaconDataParse to write into the output buffer. Link the BOF source against this shim. You still need to port any Windows-specific logic to POSIX.
3. TrustedSec ELFLoader — ELFLoader loads ELF object files on Linux/macOS with Beacon compatibility. It is a different loader; BOFs must be recompiled as ELF for the target platform.
This mirrors the BOF execution path from scout-m:
- dylib mode:
dlopen()loads the library,dlsym()resolves thegoentry point, then calls it with args and an output buffer. dyld handles all symbol resolution (libc, system frameworks, etc). - shellcode mode:
mmap()allocates RWX memory (withMAP_JITon Apple Silicon), copies the raw bytes in, flushes the icache, and calls the memory as a function pointer. On ARM64 macOS this requirespthread_jit_write_protect_np()to toggle between writable and executable states.
The dylib mode is what works for real BOFs since they almost always call libc. The raw shellcode path from the scout only works for fully self-contained position-independent code with no external references.