
콘솔 생성 없이 Cobalt Strike Beacon에서 PE를 실행하는 BOF
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 │ │
│ └────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
printf/WriteConsole 후크를 통해 출력을 캡처합니다BOF의 동작은 Additionals postex -> RunPe Config에서 편집할 수 있습니다.

| Method | 설명 |
|---|---|
None | 직접 API 호출 |
Draugr | 스택 스푸핑된 API 호출 |
Regwait | RegisterWaitForSingleObject 콜백 |
Timer | 타이머 큐 콜백 |
| Method | 설명 |
|---|---|
Heap | Draugr와 함께 RtlCreateHeap을 통한 프라이빗 힙 |
VirtualAlloc | Draugr와 함께 NtAllocateVirtualMemory |
Module stomping | 합법적인 DLL .text 섹션을 덮어씀 |
| Option | 설명 |
|---|---|
AllocRWX | RWX로 할당 (RW→RX 전환 대신) |
UnhookNtdll | ntdll.dll .text를 디스크의 새 복사본으로 교체 |
Timeout | 실행 제한 시간(밀리초) (0 = 무기한) |
StompModule | 모듈 스토밍용 DLL 경로 (예: chakra.dll) |
| Option | 설명 |
|---|---|
ModuleName | 시작 주소용 합법적인 모듈 (예: Kernel32.dll) |
ProcedureName | 모듈 내 함수 이름 (예: BaseThreadInitThunk) |
Offset | 함수 시작 지점으로부터의 오프셋 |
모든 PE 출력은 IAT 후크를 통해 beacon 콘솔로 리디렉션됩니다. 콘솔 창이나 명명된 파이프는 생성되지 않습니다.
| 후킹된 함수 | 대상 |
|---|---|
GetCommandLineA/W | 스푸핑된 인자 반환 |
__getmainargs / __wgetmainargs | CRT 인자 초기화 |
printf / wprintf | BeaconPrintf 리디렉션 |
WriteConsoleA/W | BeaconPrintf 리디렉션 |
__stdio_common_vfprintf | UCRT 출력 함수 |
ExitProcess / exit | ExitThread로 변환 |
| 기법 | 우회 대상 |
|---|---|
| 간접 시스템 콜(Indirect Syscalls) | 사용자랜드 API 후크 (EDR/AV) |
| Draugr 스택 스푸핑 | 호출 스택 검사 |
| 스레드 시작 스푸핑 | 스레드 시작 주소 분석 |
| 모듈 스토밍 | 백업되지 않은 메모리 탐지 |
| 프라이빗 힙 할당 | VirtualAlloc 모니터링 |
| Ntdll 언후킹 | 디스크의 ntdll로 메모리 내 ntdll 덮어쓰기 |
| IAT 후킹 (파이프 없음) | 명명된 파이프 모니터링 |
NtGetContextThread / NtSetContextThread:
메모리 작업:
DONT_RESOLVE_DLL_REFERENCES 플래그로 DLL이 로드됨 (메모리 할당자가 모듈 스토밍인 경우)Cobalt Strike → Script Manager → Load → BOF_RunPe.cna
beacon> runpe /path/to/binary.exe --arg1 value1

beacon> help runpe

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 지원 없음 |
| 커널 가시성 | 스레드 생성이 커널 콜백에 노출됨 |
| .NET | 관리되는 실행 파일은 지원되지 않음 |
Windows Native API Programming - Pavel Yosifovich 저Windows Internals, Part 1 - Pavel Yosifovich 저Windows Internals, Part 2 - Andrea Allievi 저