BOF_RunPE
BOF RunPE 是一个用于 Cobalt Strike 的 Beacon 对象文件,可在 beacon 进程内完全在内存中执行 PE 文件。与传统的 fork&run 不同,不会生成子进程,不会创建控制台,也不会使用管道——所有输出均通过 IAT 挂钩捕获并重定向到 beacon 控制台。
架构: 仅 x64
概述
┌──────────────────────────────────────────────────────────────┐
│ 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 │ │
│ └────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
主要特性
- 无进程创建:PE 在 beacon 进程内运行
- 无控制台/管道:通过
printf/WriteConsole 挂钩捕获输出
- 多种分配方法:堆、VirtualAlloc、模块踩踏
- 代理加载:定时器队列、RegisterWait 或直接调用
- Ntdll 取消挂钩:可选择从磁盘获取新副本
- RWX:可选择以 RWX 分配内存
- 线程起始地址欺骗:使用合法起始地址并通过 RIP 劫持
配置选项
BOF 的行为可以在 Additionals postex -> RunPe Config 中编辑

代理方法
| 方法 | 描述 |
|---|
None | 直接 API 调用 |
Draugr | 栈欺骗 API 调用 |
Regwait | RegisterWaitForSingleObject 回调 |
|
分配方法
| 方法 | 描述 |
|---|
Heap | 通过 Draugr 使用 RtlCreateHeap 的私有堆 |
VirtualAlloc | 通过 Draugr 使用 NtAllocateVirtualMemory |
Module stomping | 覆盖合法 DLL 的 .text 节区 |
通用选项
| 选项 | 描述 |
|---|
AllocRWX | 以 RWX 分配(相对于 RW→RX 转换) |
线程欺骗
| 选项 | 描述 |
|---|
ModuleName | 用于起始地址的合法模块(例如 Kernel32.dll) |
ProcedureName | 模块内的函数名(例如 BaseThreadInitThunk) |
Offset |
输出捕获
所有 PE 输出通过 IAT 挂钩重定向到 beacon 控制台。不会创建控制台窗口或命名管道。
规避技术
检测向量
内核遥测 (ETW-TI)
NtGetContextThread / NtSetContextThread:
内存操作:
- NtAllocateMemory 分配,可设置为 RWX(取决于配置)
- NtProtectVirtualMemory 转换(RW → RX)
- 堆区域中的可执行内存可疑(取决于配置)
- 通过节区哈希不匹配可检测模块踩踏(取决于配置)
行为指标
- 创建挂起的线程,获取上下文然后更改 RIP 的值
- 堆内存标记为可执行(如果内存分配器是堆)
- 使用
DONT_RESOLVE_DLL_REFERENCES 加载 DLL(如果内存分配器是模块踩踏)
- Ntdll .text 节区被修改(如果启用取消挂钩)
使用方法
加载脚本
Cobalt Strike → Script Manager → Load → BOF_RunPe.cna
攻击者命令
beacon> runpe /path/to/binary.exe --arg1 value1


编译
需要 GCC 13 (mingw-w64)。使用提供的 Dockerfile:
sudo docker build -t ubuntu-gcc-13 .
sudo docker run --rm -it -v "$PWD":/work -w /work ubuntu-gcc-13:latest make
输出:Bin/runpe.o
限制
| 限制 | 描述 |
|---|
| CET | 控制流强制技术可能会阻止合成栈帧 |
| 仅 x64 | 不支持 x86/WoW64 |
| 内核可见性 | 线程创建对内核回调可见 |
| .NET | 不支持托管可执行文件 |
致谢 / 开发使用的资源
仓库/博客文章
书籍
Windows Native API Programming by Pavel Yosifovich
Windows Internals, Part 1 by Pavel Yosifovich
Windows Internals, Part 2 by Andrea Allievi