악성코드가 빈번히 사용하는 Process Hollowing (a.k.a. RunPE) 기법의
초기 구간(Stub → Target Image Unmapping → Memory Overwrite → Context 재설정)에서
보여지는 프로세스 메모리 오염 패턴, 레지스터 전이, 스택 프레임 변형, 제어 흐름 재배치(Thread Rebase) 과정을 작성함.
리버싱 기록
1. 개요 — Process Hollowing의 핵심 구조
Process Hollowing은 정상 프로세스를 생성한 뒤, 해당 프로세스의
Image Section을 Unmap/Overwrite하여 공격자 임의 코드(일반적으로 언패킹된 PE)를
삽입하고, 이어서 Thread Context(EIP/RIP) 를 새 EntryPoint로 재설정하여
실행 흐름을 완전히 대체하는 기법이다.
분석 관점에서 특징적인 포인트는 다음과 같다:
- Suspended 상태의 프로세스 생성 (
CreateProcess+CREATE_SUSPENDED) NtUnmapViewOfSection호출 후 ImageBase 제거- Private Memory에 공격자 이미지 재구성
- PEB.ImageBaseAddress / RTL_USER_PROCESS_PARAMETERS 불일치
SetThreadContext기반 Thread PC register 재배치- Import Table 재배치 또는 동적 API 해석 루틴 삽입
- 스택 프레임/호출 규약이 정상 프로세스와 불일치
이러한 패턴들은 메모리 포렌식에서 명확한 지표(IOC) 를 생성한다.
2. 초기 Stub 코드 패턴 (무해화된 예시)
프로세스 생성 및 Unmap 루틴의 대표적인 패턴은 다음과 같다:
// 패턴
BOOL Demo_ProcessHollowingStub(LPCSTR hostPath, LPVOID payloadBase, DWORD payloadSize) {
STARTUPINFOA si = {0};
PROCESS_INFORMATION pi = {0};
si.cb = sizeof(si);
// 1) Suspended 상태로 정상 프로세스 생성
if (!CreateProcessA(hostPath, NULL, NULL, NULL, FALSE,
CREATE_SUSPENDED, NULL, NULL, &si, &pi))
return FALSE;
// 2) Target 프로세스 ImageBase 조회 (PEB 읽기)
CONTEXT ctx;
ZeroMemory(&ctx, sizeof(ctx));
ctx.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL;
if (!GetThreadContext(pi.hThread, &ctx))
return FALSE;
// 3) PEB 구조에서 ImageBaseAddress 획득 (패턴 단순화)
DWORD pebAddr = (DWORD)ctx.Ebx; // 32bit PEB 포인터
DWORD imageBase = 0;
ReadProcessMemory(pi.hProcess, (LPCVOID)(pebAddr + 0x08), &imageBase, sizeof(DWORD), NULL);
// 4) 원본 이미지 제거
NtUnmapViewOfSection(pi.hProcess, (PVOID)imageBase);
// 5) 공격자 Payload를 대상 프로세스 메모리에 매핑
LPVOID remoteBase = VirtualAllocEx(pi.hProcess, (LPVOID)imageBase,
payloadSize, MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
// (이하: 무해화 처리된 흐름 — 실제 PE Rebuild/ImportFix 없음)
WriteProcessMemory(pi.hProcess, remoteBase, payloadBase, payloadSize, NULL);
return TRUE;
}
관찰 포인트
- EBX 레지스터 사용 → x86에서 PEB 포인터
- Remote 프로세스의 ImageBase를 제거함으로써
정상 PE 구조가 사라지고 “Unbacked Private Memory”만 존재 - RWX 메모리 영역 증가
3. Thread Context 재설정 (RIP/EIP Rebase)
Image Overwrite 이후에는 새 이미지의 EntryPoint로 제어 흐름을 재설정한다.
공격자 코드는 이를 위해 EIP/RIP 수정을 수행한다.
예제 코드
// 패턴
BOOL Demo_RebaseThreadContext(HANDLE hThread, DWORD newEntry) {
CONTEXT ctx;
ZeroMemory(&ctx, sizeof(ctx));
ctx.ContextFlags = CONTEXT_CONTROL;
if (!GetThreadContext(hThread, &ctx))
return FALSE;
ctx.Eip = newEntry; // x86 기준
return SetThreadContext(hThread, &ctx);
}
4. Assembly 관점 — Hollowing Stub의 전형적인 흐름
다음은 프로세스 언매핑 및 EntryPoint 재배치를 수행하는
“교육용 패턴화”된 디스어셈블리다:
; --- PEB에서 ImageBase 추출 ---
mov eax, [ebx+08h] ; PEB.ImageBaseAddress
push eax
push [edi] ; hProcess
call dword ptr [NtUnmapViewOfSection]
; --- 새 Payload 매핑 ---
push PAGE_EXECUTE_READWRITE
push MEM_COMMIT
push payload_size
push eax ; desired base (기존 ImageBase)
push hProcess
call dword ptr [VirtualAllocEx]
; --- EntryPoint 재설정 ---
mov ecx, new_entry
mov [ctx+0B8h], ecx ; CONTEXT.Eip 수정
push ctx
push hThread
call dword ptr [SetThreadContext]
5. 레지스터·메모리·스택 변화 상세 분석
| 시점 | 레지스터 / 메모리 상태 | 의미 |
|---|---|---|
| 스레드 생성 직후 | EIP = 정상 EntryPoint, EBX = PEB | 정상 프로세스 초기 상태 |
| NtUnmapViewOfSection 후 | ImageBase 메모리 Unmapped | PE Header/Sections 제거됨 |
| VirtualAllocEx 이후 | RemoteBase = Private Memory | 공격자 이미지가 위치할 공간 확보 |
| WriteProcessMemory 후 | Private Memory에 새 이미지 반영 | PE 전체가 디스크 없이 메모리 상 재구성 |
| SetThreadContext 후 | EIP = 새 EntryPoint | 정상 실행 흐름 완전 대체 |
6. PE 구조 관점의 무결성 파괴
리버싱 핵심원리 기준으로 보면 Process Hollowing은 PE 구조의
다음 요소들이 비정상적으로 변형된다:
(1) PEB.ImageBaseAddress 값과 실제 매핑 주소 불일치
- 진짜 매핑된 이미지가 Private Memory로 바뀌었으나
PEB는 갱신되지 않거나 부분적으로만 반영됨
(2) Section Alignment 및 Header 구조 붕괴
- 디스크 기반 백업 없음
.text,.rdata경계와 실제 메모리 경계 불일치
(3) Import Table 재배치 및 AddressOfFunctions 파편화
- IAT 재해석 루틴 또는 동적 API 로더가 등장하는 경우 많음
7. 동적 API 호출 및 Anti-Analysis 결합 패턴
Process Hollowing과 결합되는 고전적 로더 패턴들:
- API 해시 기반 Resolver
- Kernel32 LoadLibrary/GetProcAddress Stub 삽입
- TEB/PEB 직접 참조로 Anti-Hook 우회
- Inline Anti-Debugging(NtQueryInformationProcess)
- RDTSC / QueryPerformanceCounter 기반 Timing Check
- IsDebuggerPresent 플래그 조작
이들은 모두 Hollowing 단계 직후 등장하기 쉬운
Payload 초기화 루틴의 공통 특징이다.
8. 메모리 포렌식에서의 IOC
Process Hollowing은 다음 IOC를 거의 항상 남긴다:
(1) Private Memory에 PE Header 특유의 “MZ / PE” 시그니처 존재
(2) 메모리맵에서 동일 주소대역이 “Unbacked” 상태로 표시
(3) Module 리스트(PEB Loader Entries)와 실행중 EntryPoint의 불일치
(4) RWX 메모리 증가 + Image 타입 섹션 사라짐
(5) Call Stack Reconstruction 실패 (Return Address가 모듈 내부가 아님)
9. 결론
Process Hollowing은 사용자 공간 프로세스 구조(PEB, VAD, Section Mapping)를 직접 변형해
정상 실행 흐름을 완전히 대체하는 공격 기법이다.
그러나 PE 구조/메모리 보호 속성/Thread Context/스택 프레임 관찰을 기반으로
꾸준히 정형화된 TTP를 보여주기 때문에
디지털 포렌식·Incident Response 환경에서 충분히 탐지 가능하다.
📍 Written by Code & Compass