BOF_RunPE
BOF RunPE는 Cobalt Strike용 Beacon Object File로, 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 후크를 통해 출력을 캡처합니다
- 다중 할당 방식: Heap, VirtualAlloc, Module Stomping
- 프록시 로딩: Timer Queue, RegisterWait 또는 직접 호출
- Ntdll 언후킹: 선택적으로 디스크에서 새 복사본 사용
- RWX : 선택적으로 RWX 메모리 할당
- 스레드 시작 스푸핑: RIP 하이재킹과 함께 합법적인 시작 주소 사용
구성 옵션
BOF의 동작은 Additionals postex -> RunPe Config에서 편집할 수 있습니다.

프록시 방식
| Method | 설명 |
|---|
None | 직접 API 호출 |
Draugr | 스택 스푸핑된 API 호출 |
Regwait | RegisterWaitForSingleObject 콜백 |
|
할당 방식
| Method | 설명 |
|---|
Heap | Draugr와 함께 RtlCreateHeap을 통한 프라이빗 힙 |
VirtualAlloc | Draugr와 함께 NtAllocateVirtualMemory |
Module stomping | 합법적인 DLL .text 섹션을 덮어씀 |
일반 옵션
| Option | 설명 |
|---|
AllocRWX | RWX로 할당 (RW→RX 전환 대신) |
스레드 스푸핑
| Option | 설명 |
|---|
ModuleName | 시작 주소용 합법적인 모듈 (예: Kernel32.dll) |
ProcedureName | 모듈 내 함수 이름 (예: BaseThreadInitThunk) |
|
출력 캡처
모든 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
Aggressor 명령
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 | CET(Control-flow Enforcement Technology)가 합성 스택 프레임을 차단할 수 있음 |
| x64 전용 | x86/WoW64 지원 없음 |
| 커널 가시성 |
크레딧 / 개발에 사용된 리소스
저장소/블로그 포스트
도서
Windows Native API Programming - Pavel Yosifovich 저
Windows Internals, Part 1 - Pavel Yosifovich 저
Windows Internals, Part 2 - Andrea Allievi 저