๐Ÿงฉ 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

MalClown์—์„œ ๋” ์•Œ์•„๋ณด๊ธฐ

์ง€๊ธˆ ๊ตฌ๋…ํ•˜์—ฌ ๊ณ„์† ์ฝ๊ณ  ์ „์ฒด ์•„์นด์ด๋ธŒ์— ์•ก์„ธ์Šคํ•˜์„ธ์š”.

๊ณ„์† ์ฝ๊ธฐ