[태그:] vm난독화

  • 🔍 Anti-Debugging, VM 난독화 구조, CrackMe 예시

    리버싱 기록


    📌 1. Anti-Debugging 심화 분석

    기본적인 IsDebuggerPresent 호출을 넘어서, 실제 크랙미나 악성코드에서 자주 사용하는 안티 디버깅 기법 정리.

    🧩 (1) PEB 직접 접근 기반 Anti-Debugging

    PEB 구조체 내부의 BeingDebugged 플래그를 직접 읽어서 디버거 여부 확인하는 방식.

    #include <stdio.h>
    
    int main() {
        unsigned char *peb = (unsigned char*)__readfsdword(0x30);
        unsigned char BeingDebugged = peb[2]; // PEB + 0x2
    
        if (BeingDebugged)
            printf("Debugger detected via PEB check!\n");
        else
            printf("No debugger detected.\n");
    }
    

    요약

    • __readfsdword(0x30) → x86 환경에서 PEB 주소 얻는 방식
    • peb[2] = BeingDebugged 플래그
    • 값이 1이면 디버거 존재

    ASM 흐름

    mov     eax, fs:[30h]     
    mov     al, [eax+2]       
    test    al, al
    jnz     debugger_found
    

    분석 관점

    • [eax+2] 값을 0으로 패치하는 방식
    • 또는 jnz → jz 반전

    🧩 (2) NtQueryInformationProcess 기반 탐지

    Native API를 이용해 디버그 플래그 확인하는 방식.

    #include <Windows.h>
    #include <winternl.h>
    
    typedef NTSTATUS (WINAPI *PFN)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);
    
    int main() {
        PFN NtQueryInformationProcess =
            (PFN)GetProcAddress(GetModuleHandleA("ntdll"), "NtQueryInformationProcess");
    
        DWORD flags = 0;
        NtQueryInformationProcess(GetCurrentProcess(),
                                  0x1f,
                                  &flags, sizeof(flags), NULL);
    
        if (flags == 0)
            printf("Debugger detected (DebugFlags == 0)\n");
        else
            printf("No debugger detected.\n");
    }
    

    요약

    • ProcessDebugFlags = 0 → 디버거 존재
    • 값을 강제로 1로 패치하는 방식

    🧩 (3) Timing 기반 Anti-Debugging

    #include <Windows.h>
    
    int main() {
        DWORD t1 = GetTickCount();
        for (volatile int i = 0; i < 10000000; i++);
        DWORD t2 = GetTickCount();
    
        if ((t2 - t1) > 50)
            printf("Debugger likely detected.\n");
        else
            printf("Normal execution.\n");
    }
    

    요약

    • 디버깅 시 루프 실행 시간 증가 → threshold 이상이면 탐지
    • 타이밍 비교문 패치 방식

    📌 2. VM 기반 난독화 구조 분석

    코드를 직접 실행하지 않고 VM 명령 집합(bytecode)으로 변환하여 핸들러 위에서 실행하는 방식. 난이도 높은 난독화 기법.

    🧩 (1) VM Dispatcher 구조

    VM_Dispatch:
        movzx   eax, byte [esi]
        inc     esi
        jmp     [HandlerTable + eax*4]
    

    요약

    • esi = VM 바이트코드 포인터
    • opcode = [esi]
    • HandlerTable[opcode] → 해당 연산 핸들러로 점프

    🧩 (2) VM Handler 예시

    Handler_ADD:
        mov     edx, [stack_top]
        mov     ecx, [stack_top - 4]
        add     ecx, edx
        mov     [stack_top - 4], ecx
        sub     stack_top, 4
        jmp     VM_Dispatch
    

    요약

    • 스택 상단 두 값 pop → add 연산
    • 결과를 push하는 구조
    • Dispatcher로 복귀

    분석 관점

    • 핸들러 기능을 테이블화하여 의미 매핑
    • 바이트코드를 순차 분석하여 원래 로직 복원

    📌 3. CrackMe 예시

    🔍 (1) Anti-Debugging 우회

    CALL    IsDebuggerPresent
    TEST    EAX, EAX
    JNZ     fail
    

    요약

    • EAX != 0 → 디버거 탐지
    • JNZ → JZ 패치 방식

    🔍 (2) Serial 검증 루틴 분석

    xor     eax, eax
    mov     ecx, [input]
    
    xor_loop:
        mov     dl, [ecx]
        test    dl, dl
        jz      check_end
        xor     al, dl
        inc     ecx
        jmp     xor_loop
    
    check_end:
        cmp     al, 3Ch
        je      correct
        jmp     fail
    

    요약

    • 문자 반복 XOR 누적 방식
    • 결과값 == 0x3C → correct

    ✔ 예시 정답

    ABCDEF<
    

    정리:

    • Anti-Debugging은 API 검사 → PEB → 타이밍 검사 순으로 고도화
    • VM 난독화는 Dispatcher/Handler 구조 파악이 핵심
    • CrackMe는 작성자의 의도 파악 중심 분석

    📍Written by Code & Compass