
BOF to run PE in Cobalt Strike Beacon without console creation
BOF RunPE is a Beacon Object File for Cobalt Strike that executes PE files entirely in-memory within the beacon process. Unlike traditional fork&run, no child process is spawned, no console is created, and no pipe is used - all output is captured via IAT hooking and redirected to the beacon console.
Architecture: x64 only
┌──────────────────────────────────────────────────────────────┐
│ Cobalt Strike Beacon │
│ (Current Process) │
└────────────────────────┬─────────────────────────────────────┘
│
│ beacon_inline_execute()
│
▼
┌──────────────────────────────────────────────────────────────┐
│ BOF RunPE │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ VxTable + Draugr Initialization │ │
│ │ (Syscall Resolution + Stack Spoofing) │ │
│ └──────────────────────┬─────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────▼─────────────────────────────────┐ │
│ │ PE Mapping │ │
│ │ - Section Copy - IAT Patching (with hooks) │ │
│ │ - Relocations - Memory Protection │ │
│ └──────────────────────┬─────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────▼─────────────────────────────────┐ │
│ │ Thread Execution │ │
│ │ - Spoofed Start Address │ │
│ │ - RIP Hijacking to Entry Point │ │
│ │ - Output Redirection via Hooks │ │
│ └────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
printf/WriteConsole hooksThe behaviorus of BOF can be edited in Additionals postex -> RunPe Config

| Method | Description |
|---|---|
None | Direct API calls |
Draugr | Stack spoofed API calls |
Regwait | RegisterWaitForSingleObject callback |
Timer | Timer Queue callback |
| Method | Description |
|---|---|
Heap | Private heap via RtlCreateHeap with Draugr |
VirtualAlloc | NtAllocateVirtualMemory with Draugr |
Module stomping | Overwrites legitimate DLL .text section |
| Option | Description |
|---|---|
AllocRWX | Allocate as RWX (vs RW→RX transition) |
UnhookNtdll | Replace ntdll.dll .text with fresh copy from disk |
Timeout | Execution timeout in milliseconds (0 = infinite) |
StompModule | DLL path for module stomping (e.g., chakra.dll) |
| Option | Description |
|---|---|
ModuleName | Legitimate module for start address (e.g., Kernel32.dll) |
ProcedureName | Function name within module (e.g., BaseThreadInitThunk) |
Offset | Offset from function start |
All PE output is redirected to the beacon console via IAT hooks. No console window or named pipe is created.
| Hooked Function | Target |
|---|---|
GetCommandLineA/W | Returns spoofed arguments |
__getmainargs / __wgetmainargs | CRT argument initialization |
printf / wprintf | BeaconPrintf redirection |
WriteConsoleA/W | BeaconPrintf redirection |
__stdio_common_vfprintf | UCRT print functions |
ExitProcess / exit | Converted to ExitThread |
| Technique | Bypasses |
|---|---|
| Indirect Syscalls | Userland API hooks (EDR/AV) |
| Draugr Stack Spoofing | Call stack inspection |
| Thread Start Spoofing | Thread start address analysis |
| Module Stomping | Unbacked memory detection |
| Private Heap Allocation | VirtualAlloc monitoring |
| Ntdll Unhooking | Overwrite in memory ntdll with Ntdll on a disk |
| IAT Hooking (no pipes) | Named pipe monitoring |
NtGetContextThread / NtSetContextThread:
Memory Operations:
DONT_RESOLVE_DLL_REFERENCES (if memory allocator is module stomping)Cobalt Strike → Script Manager → Load → BOF_RunPe.cna
beacon> runpe /path/to/binary.exe --arg1 value1

beacon> help runpe

Requires GCC 13 (mingw-w64). Use provided Dockerfile:
sudo docker build -t ubuntu-gcc-13 .
sudo docker run --rm -it -v "$PWD":/work -w /work ubuntu-gcc-13:latest make
Output: Bin/runpe.o
| Limitation | Description |
|---|---|
| CET | Control-flow Enforcement Technology may block synthetic stack frames |
| x64 Only | No x86/WoW64 support |
| Kernel Visibility | Thread creation visible to kernel callbacks |
| .NET | Managed executables not supported |
Windows Native API Programming by Pavel YosifovichWindows Internals, Part 1 by Pavel YosifovichWindows Internals, Part 2 by Andrea Allievi