[태그:] 스텔스 인젝션

  • 🧩 Windows Thread Hiding 기반 스텔스형 인젝션

    이번 내용은 스텔스 인젝션 기법 중 비교적 고급 형태인
    “Thread Hiding + APC 기반 코드 실행 흐름 감추기” 패턴을 분석한 내용임.

    리버싱 기록


    1. 개요 — Thread Hiding 기법 개념

    일부 샘플은 기본적인 CreateRemoteThread 사용을 피하고,
    이미 존재하는 타깃 프로세스의 스레드에
    **APC(Asynchronous Procedure Call)**를 큐잉하거나
    Thread State를 Suspended → Running으로 미세 조작하여
    코드 실행 흐름을 감춘다.

    핵심 목표:

    • 스레드 시작 주소(Start Address)로 드러나는 인젝션 흔적 최소화
    • 디버거·EDR의 스레드 생성 이벤트 탐지 회피
    • APC 스케줄 타이밍을 이용해 호출 스택을 은폐

    2. 개념적 인젝션 흐름도

    [1] 타깃 프로세스 열기
        ↓
    [2] 메모리 공간 확보(VirtualAllocEx)
        ↓
    [3] Payload 쓰기
        ↓
    [4] 타깃의 기존 스레드 핸들 획득(Thread32First/Next)
        ↓
    [5] APC 큐잉(QueueUserAPC)
        ↓
    [6] 스레드가 Alertable 상태로 진입할 때 실행
    

    3. PE 구조 및 API 동적 해석(Export Directory 스캔 기반)

    악성코드는 보통 API 문자열을 직접 포함하지 않는다.
    다음은 C로 작성된 예시로, Export Table을 순회하여
    함수 해시를 비교해 주소를 찾는 루틴의 형태를 보여준다.

    // 예시 — Export 스캔 기반 동적 API 검색
    void* ResolveAPI(void* moduleBase, unsigned int targetHash) {
        IMAGE_DOS_HEADER* dos = (IMAGE_DOS_HEADER*)moduleBase;
        IMAGE_NT_HEADERS* nt = (IMAGE_NT_HEADERS*)((BYTE*)moduleBase + dos->e_lfanew);
    
        IMAGE_EXPORT_DIRECTORY* exp = 
            (IMAGE_EXPORT_DIRECTORY*)((BYTE*)moduleBase +
            nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
    
        DWORD* nameRVA = (DWORD*)((BYTE*)moduleBase + exp->AddressOfNames);
        WORD* ordinal = (WORD*)((BYTE*)moduleBase + exp->AddressOfNameOrdinals);
        DWORD* funcRVA = (DWORD*)((BYTE*)moduleBase + exp->AddressOfFunctions);
    
        for (DWORD i = 0; i < exp->NumberOfNames; i++) {
            char* name = (char*)moduleBase + nameRVA[i];
            unsigned int h = HashFunctionConcept(name);   // 해시
            if (h == targetHash) {
                void* fn = (BYTE*)moduleBase + funcRVA[ordinal[i]];
                return fn;
            }
        }
        return NULL;
    }
    

    4. APC 기반 코드 실행 디스어셈블리 패턴 분석

    다음은 APC를 통해 실행되는 함수의
    x86 디스어셈블리 분석.

    ; 예시 — APC로 호출된 함수의 프롤로그 패턴
    APC_PAYLOAD:
        push ebp
        mov  ebp, esp
        sub  esp, 0x20              ; 로컬 버퍼 확보
    
        mov  eax, [ebp+0x08]        ; APC 전달 파라미터
        xor  ecx, ecx               ; 레지스터 초기화
        mov  [ebp-0x04], eax        ; Stack frame 내부에 저장
    
        ; --- 메모리 조작 또는 스텔스 로직 (개념) ---
        ; 예: 특정 메모리 페이지의 플래그 확인
        mov  edx, [eax]             ; (메모리 참조)
        test edx, edx
        jz   SHORT .exit_path
    
        ; 추가 연산 또는 분기
        add  edx, 0x10
        mov  [ebp-0x08], edx
    
    .exit_path:
        mov  esp, ebp
        pop  ebp
        ret  4
    

    ▶ 스택 프레임 구성 해석

    위치설명
    [ebp+8]APC 함수의 첫 번째 파라미터
    [ebp-4]임시 저장용 로컬 변수
    [ebp-8]분기 계산용 로컬 버퍼

    ▶ 제어 흐름 분석 포인트

    • jz SHORT .exit_path조건부로 스텔스 로직을 건너뛰어 분석을 방해할 수 있음
    • 분석 시 조건을 역방향으로 추적해 실제 실행 경로를 확인해야 함
    • EAX/EDX의 실제 사용 패턴이 payload 성격을 결정

    5. Anti-Debugging 연계 패턴

    APC 기반 인젝션은 종종 디버거 탐지와 함께 사용된다.

    x86 코드:

    mov eax, fs:[0x30]           ; PEB
    mov al,  [eax+0x02]          ; BeingDebugged
    test al, al
    jnz  SHORT DEBUG_EXIT        ; 디버거 있으면 다른 경로
    
    ; 타이밍 체크 기반 안티 디버깅
    rdtsc
    mov ebx, eax
    rdtsc
    sub eax, ebx
    cmp eax, 0x00001000          ; 임계값 기준
    jg SHORT DEBUG_EXIT
    
    ; 정상이면 계속
    jmp CONTINUE_EXECUTION
    

    특징

    • PEB 플래그 확인
    • RDTSC 사이클 차이로 single-step, breakpoints, VM 속도 저하 감지
    • APC 페이로드가 디버거 우회 경로로만 실행되도록 조건 분기 구성 가능

    6. Thread Hiding 구현 개념

    아래는 C 언어 예시이다.

    // 개념 예시 — Thread Hide
    void HideThreadContext(HANDLE hThread) {
    
        // 1) 스레드 컨텍스트 가져오기
        CONTEXT ctx = {0};
        ctx.ContextFlags = CONTEXT_FULL;
        GetThreadContext(hThread, &ctx);
    
        // 2) 스택 포인터 및 명령 포인터 조정(개념적)
        ctx.Esp -= 0x200;      // 새로운 스택 영역
        ctx.Eip = (DWORD)APC_PAYLOAD;  // 페이로드 엔트리
    
        // 3) 조작된 컨텍스트 반영
        SetThreadContext(hThread, &ctx);
    }
    

    이 과정이 실제로는 다음과 같은 점에서 탐지 회피에 기여한다:

    • 스레드 시작 주소(Start Address)와 실행 위치가 불일치
    • 스택 프레임이 정상 API 호출 스택 형태를 갖추지 않음
    • 스레드가 Alertable 상태 전환 시에만 비정상 코드가 실행됨

    7. Call Stack 흐름 분석 기법

    APC 기반 페이로드는 Call Stack이 다음처럼 구성된다:

    [ ReturnAddress to ntdll!KiUserApcDispatcher ]
    [ APC_PAYLOAD arguments ]
    [ ... local stack frame ... ]
    

    분석 포인트:

    1. ReturnAddress가 항상 KiUserApcDispatcher 근처
    2. 정상 스레드의 Call Stack 패턴과 불일치
    3. 페이로드 진입 전에 호출 규약이 명확하지 않아
      stdcall/cdecl 구분이 모호한 형태로 나타남
    4. 스택 복구(mov esp, ebp; pop ebp)가 제대로 맞지 않으면
      다른 APC와 충돌 위험

    8. 암호화된 페이로드 복원(개념)

    대부분의 APC 페이로드는 압축·암호화되어 있으며,
    복원 루틴은 다음과 같은 패턴을 갖는다.

    // 예시
    void DecryptConcept(BYTE* buf, size_t len, BYTE key) {
        for (size_t i = 0; i < len; i++) {
            buf[i] ^= key;         // 단순 XOR 개념
            buf[i] = rol(buf[i], 3); // ROL 기반 변형
        }
    }
    

    디스어셈블리 특징:

    movzx eax, byte ptr [ecx]
    xor   al, dl
    rol   al, 3
    mov   [ecx], al
    inc   ecx
    cmp   ecx, edi
    jb    SHORT LOOP
    

    ✨ 마무리 한 줄

    APC 기반 스레드 하이딩은 “스레드 생성 이벤트 없이 코드 실행 흐름을 전환하는” 특성 덕분에
    정적·동적 분석 모두에서 탐지 난도가 높다.
    분석가는 스레드 컨텍스트, 콜스택 패턴, Export 기반 API 해석 루틴을
    정밀하게 추적해야 전체 흐름을 복원할 수 있다.


    📍 Written by Code & Compass