Cobalt Strike Beacon console showing dprintf output from the BOF Cocktails OpenProcessToken hook

BOF Cocktails in Cobalt Strike: Instrumenting BOFs with BEACON_INLINE_EXECUTE and Crystal Palace

Original text: “BOF Cocktails in Cobalt Strike”Rasta Mouse, rastamouse.me (05 Jun 2026). Code blocks and the screenshot below are reproduced verbatim from the source with attribution.

Executive Summary

Post-exploitation Beacon Object Files (BOFs) historically inherited their evasion posture from whatever agent or loader executed them. If the loader took care of unhooking, masking, or telemetry suppression, the BOF benefited; if it didn’t, every OpenProcessToken, NtReadVirtualMemory, or RegOpenKeyEx the BOF made was a clean shot for EDR sensors. Rasta Mouse’s BOF Cocktails pattern fixed that by merging tradecraft directly into BOFs at build time — but in Cobalt Strike it still required ugly Aggressor hacks like alias_clear to intercept execution.

Cobalt Strike 4.13 closes the gap with a first-class Aggressor hook: BEACON_INLINE_EXECUTE. The hook fires whenever Beacon is asked to run a BOF and hands the operator the raw BOF bytes, expecting modified bytes back. This post walks through using that hook to drive a Crystal Palace specification file, merging a custom OpenProcessToken stub and the libtcg helper library into every BOF transparently — no per-command Aggressor wrapper required.

Background: Why Postex BOFs Needed Their Own Evasion

An earlier post on rastamouse.me introduced BOF Cocktails as an execution pattern that bakes tradecraft into a BOF rather than relying on the agent that runs it. The motivation is simple: postex BOFs are loaded into Beacon’s process and call Windows APIs directly. If Beacon has unhooked NTDLL, suppressed ETW, or masked its heap, those mitigations only protect Beacon’s own code paths — the moment a BOF resolves and calls ADVAPI32!OpenProcessToken through Beacon’s import resolver, it cuts straight through any user-mode mitigation the loader put in place.

The Cocktails approach solves this by post-processing the BOF’s COFF object: redirecting selected import references to local thunk functions, merging in a small instrumentation library, and emitting a new COFF that Beacon can load. Conceptually it’s the BOF analogue of compile-time API hooking. The earlier post covers the problem space in detail — this one focuses on the new Cobalt Strike integration point.

The New Hook: BEACON_INLINE_EXECUTE

Until Cobalt Strike 4.13, there was no supported way for an Aggressor script to intercept a BOF on its way into Beacon. Operators who wanted to apply Cocktails had to override individual BOF-backed commands by clearing the existing alias with alias_clear and re-registering their own wrapper — one wrapper per command, easy to forget, brittle when scripts loaded in a different order.

The CS 4.13 release party webinar announced a new Aggressor hook named BEACON_INLINE_EXECUTE. The hook fires every time Beacon is instructed to execute a BOF and gives the operator a chance to process the BOF before it goes on the wire. The contract is straightforward:

  • $1 — the original BOF as a raw byte string.
  • Return value — the modified BOF, also as raw bytes. Beacon executes whatever the hook returns.

That’s enough surface area to push every BOF through Crystal Palace and merge in whatever tradecraft the engagement calls for, transparently to the operator typing inline-execute, execute-assembly wrappers, or any of the dozens of community BOF-backed commands.

Wiring Crystal Palace into the Hook

The Aggressor side is small: load the Crystal Palace JAR, parse a spec file, wrap the incoming BOF bytes in a Capability object, run the spec against it, and return the transformed bytes. A minimal cocktail.cna:

import crystalpalace.spec.* from: crystalpalace.jar;
import java.util.HashMap;

# $1 - raw BOF bytes
set BEACON_INLINE_EXECUTE
{
    local( '$bof $spec_path $spec $capability $final' );

    $bof = $1;

    $spec_path  = script_resource( "cocktail.spec" );
    $spec       = [ LinkSpec Parse: $spec_path ];
    $capability = [ Capability Parse: cast( $bof, 'b' ) ];
    $final      = [ $spec run: $capability, [ new HashMap ] ];

    return $final;
}

Two things to note. First, script_resource() resolves the spec path relative to the script’s own directory — convenient for keeping the cocktail self-contained. Second, the spec runs with a fresh empty HashMap for variables; in real engagements that map is a natural place to hand the spec environment-specific knobs (which hooks to apply, which library variant to merge, per-target tweaks).

The Spec File: cocktail.spec

Crystal Palace specs are declarative pipelines. They take an object, transform it through a sequence of stages, and emit a result. The spec below has two architecture branches (x86 and x64) that do the same thing: pull the incoming BOF as a COFF object, optimise it, load the architecture-specific hooks object, merge the two, mix in the libtcg helper library, attach a thunk that retargets every call to ADVAPI32$OpenProcessToken through a local __OpenProcessToken (or _OpenProcessToken on x64), and export the merged BOF.

x86:
    push $OBJECT              # grab the bof
        make coff +optimize   # turn it into a coff-exporter object
    
    load "bin/hooks.x86.o"    # grab the hooks
        merge                 # merge with the bof

    mergelib "libtcg.x86.zip" # merge the tcg library
    
    attach "ADVAPI32$OpenProcessToken" "__OpenProcessToken"  # add a hook

    export  # export the merged bof


x64:
    push $OBJECT
        make coff +optimize
    
    load "bin/hooks.x64.o"
        merge

    mergelib "libtcg.x64.zip"
    
    attach "ADVAPI32$OpenProcessToken" "_OpenProcessToken"

    export

The attach directive is the core of the Cocktail: it rewrites every reference to a Beacon-resolved import (ADVAPI32$OpenProcessToken) so it points at a symbol that lives inside the merged hooks object instead. The hook function is then free to log, inspect, or alter parameters before chaining to the real Beacon-resolved import. Crystal Palace handles the COFF relocation bookkeeping — the operator just lists which APIs to intercept and which local thunks to swap in.

Note the x86 vs x64 symbol naming: Microsoft’s 32-bit C compiler decorates __cdecl exports with a leading underscore (so the C function _OpenProcessToken exports as __OpenProcessToken), while x64 has no name mangling for extern "C" — the symbol stays _OpenProcessToken as written. The Crystal Palace spec just has to reflect what the linker actually produced on each side.

The Hook Source: hooks.c

The hooks object is an ordinary BOF/COFF source file. It exports the thunk that Crystal Palace will attach in place of the original import. The example simply prints the parameters before forwarding the call through to Beacon’s resolved ADVAPI32$OpenProcessToken — trivial as instrumentation, but it demonstrates the full round-trip:

#include <windows.h>
#include "tcg.h"

DECLSPEC_IMPORT BOOL WINAPI ADVAPI32$OpenProcessToken( HANDLE, DWORD, PHANDLE );

BOOL WINAPI _OpenProcessToken( HANDLE ProcessHandle, DWORD DesiredAccess, PHANDLE TokenHandle )
{
    dprintf( "[*] _OpenProcessToken\n" );
    dprintf( "  -> ProcessHandle: 0x%p\n", ProcessHandle );
    dprintf( "  -> DesiredAccess: %d\n", DesiredAccess );

    return ADVAPI32$OpenProcessToken( ProcessHandle, DesiredAccess, TokenHandle );
}

A few things are doing real work here despite the short listing. The DECLSPEC_IMPORT declaration tells the linker the resolved symbol comes from outside the object — Beacon’s import resolver fills it in at load time. dprintf is provided by libtcg and writes to the Beacon console, which is where the screenshot below comes from. And because the hook simply forwards to ADVAPI32$OpenProcessToken, the original BOF semantics are preserved — the only change is that a log line gets emitted on every call.

Swapping dprintf for real evasion is straightforward: validate the ProcessHandle argument, replace requested access masks, redirect to an alternative token, or drop the call entirely on conditions you don’t want telemetry for. The same pattern applies to any import you care about — NtOpenProcess, NtReadVirtualMemory, registry hives, the lot.

Observed Output

Cobalt Strike Beacon console showing dprintf output from the BOF Cocktails OpenProcessToken hook
Cobalt Strike Beacon console output produced by the instrumented _OpenProcessToken hook — every BOF call to OpenProcessToken now prints the handle and desired-access mask before forwarding to the real Beacon import. Source: original article.

The original article closes with thanks to the Cobalt Strike engineering team for adding the hook — a fair note. Operators have wanted this intercept point for years; the alternatives all involved either fighting the Aggressor command system or patching Beacon at runtime, neither of which is OPSEC-friendly.

Key Takeaways

  • BEACON_INLINE_EXECUTE is a real intercept point. Every BOF Beacon would otherwise execute first passes through the hook, which receives raw BOF bytes and returns modified bytes.
  • No more alias_clear per command. The hook is global — one cocktail script applies to every BOF-backed command across every script you’ve loaded.
  • The Aggressor side is ~10 lines. Load the Crystal Palace JAR, parse a spec, wrap the bytes in a Capability, run the spec, return the result.
  • The heavy lifting lives in Crystal Palace spec files. Declarative pipelines per architecture: load BOF, merge hooks object, merge support library (libtcg), attach selected imports to local thunks, export.
  • Hook functions are ordinary BOF C. DECLSPEC_IMPORT the real import, do whatever you want, optionally forward. The same shape works for any Win32/NT API.
  • Architecture matters. 32-bit MSVC decorates __cdecl exports with a leading underscore; x64 doesn’t. The spec’s attach target must match the symbol the linker produced for each arch.
  • Tradecraft is now part of the BOF, not the loader. Evasion follows the BOF wherever it runs, regardless of how Beacon itself is hardened.

Defensive Recommendations

  • Don’t rely on inline IAT/EAT inspection of Beacon’s process to spot BOF behaviour. The merged hooks live inside the COFF Beacon just loaded — there is no PE import table to inspect for the substituted symbol.
  • Hunt at the API call site, not the import resolution layer. ETW Threat Intelligence (kernel-emitted) and well-placed minifilter / Object Manager callbacks will still see OpenProcessToken regardless of how the user-mode shim was built. NtOpenProcessToken-level kernel events are the floor that BOF Cocktails cannot evade.
  • Profile Beacon-resident anonymous executable memory. Loaded BOFs land in RWX or RX private allocations Beacon manages; correlate those allocations with sudden bursts of sensitive Win32 calls from the same thread as a hunting heuristic.
  • Watch for libtcg-style helper signatures in memory. Cocktails routinely merge in support libraries (dprintf, output buffering, format helpers). Memory scanners that match known helper code stubs catch a wide variety of merged BOFs cheaply.
  • Stack-walk on sensitive token / process / memory APIs. If OpenProcessToken is called from anonymous executable memory rather than a backed module, treat it as suspect regardless of which process is calling.
  • Audit Cobalt Strike profiles and Aggressor scripts on red-team infrastructure you have visibility into. The presence of a BEACON_INLINE_EXECUTE hook handler is itself a strong tradecraft fingerprint.
  • For threat-hunting / detection engineering teams: read the original BOF Cocktails post linked above. The conceptual model — compile-time merge of evasion into postex tooling — will spread beyond Cobalt Strike.

Conclusion

Cobalt Strike 4.13’s BEACON_INLINE_EXECUTE hook is a small API change with outsized OPSEC consequences. Paired with Crystal Palace’s declarative COFF-merging pipeline, it lets operators bind tradecraft to every postex BOF transparently — turning what used to be a per-command Aggressor hack into a single global cocktail that travels with the toolset. For defenders, the practical implication is that user-mode import hooking is now even less reliable as a detection surface against Beacon-loaded BOFs; the durable signal lives below the user/kernel boundary at ETW-TI and at object-manager / minifilter callbacks.

Original text: “BOF Cocktails in Cobalt Strike” by Rasta Mouse at rastamouse.me.

Comments are closed.