Windows Anti-Debug Reference

Posted at 2009/06/10 03:15 // in RCE // by 엔신
원본 영문 레퍼런스 파일

아래 내용의 pdf 파일


Windows Anti-Debug Reference

Anti-debugging and anti-tracing techniques

시작 전에 먼저 말씀해드립니다. 영문 레퍼런스의 번역본이 아닙니다. 제가 보면서 공부하기 위해 한글로 이해하는 대로 적기도 하고, 부족한 부분은 추가로 설명을 달았습니다. 정석으로 보고 싶으신 분은 영어로된 원본을 보시길 추천합니다.

아래부터는 편의상 말은 짧게 하겠습니다.


목차
Exploiting memory discrepancies
    1 kernel32!IsDebuggerPresent
    2 PEB!IsDebugged
    3 PEB!NtGlobalFlags
    4 Heap flags
    5 Vista anti-debug (no name)

Exploiting system discrepancies
    1 NtQueryInformationProcess
    2 kernel32!CheckRemoteDebuggerPresent
    3 UnhandledExceptionFilter
    4 NtSetInformationThread
    5 kernel32!CloseHandle and NtClose
    6 Self-debugging
    7 Kernel-mode timers
    8 User-mode timers
    9 kernel32!OutputDebugStringA
    10 Ctrl-C
    
CPU anti-debug
    1 Rogue Int3
    2 "Ice" Breakpoint
    3 Interrupt 2Dh
    4 Timestamp counters
    5 Popf and the trap flag
    6 Stack Segment register
    7 Debug registers manipulation
    8 Context modification
    
Uncategorized anti-debug
    1 TLS-callback
    2 CC scanning
    3 EntryPoint RVA set to 0

Data reference
    CONTEXT structure for IA32 processors
    Process Environment Block structure (from The Wine Project)
    Thread Environment Block structure (from The Wine Project)
    NtGlobalFlags


시작에 앞서
윈도우에는 PEB와 TEB라 불리는 특수 구조체가 프로세스별로 존재.PEB는 유저 모드 프로세스당 하나, TEB는 유저 모드 스레드당 하나씩 존재.



Exploiting memory discrepancies
아래는 메모리와 관련된 Anti-Debug 기법

1 kernel32!IsDebuggerPresent

    Example:
    call IsDebuggerPresent
    test eax, eax
    jne @DebuggerDetected
    ...
위와 같은 형태로 사용이 되는 가장 기본적인 예제입니다. 이미 다들 알고 있을 것으로 예상이 됩니다. IsDebuggerPresent 함수는 디버그 중이면 1을 리턴. 그렇지 않다면 0을 리턴. IsDebuggerPresent 함수는 PEB!BeingDebugged에서 byte 크기인 flag의 값을 read한다.    (PEB 구조체 참조. 영문서에도 있고, 하단을 참조하여도 됨)

IsDebuggerPresent 함수 구조
    Example:
    mov eax, large fs:18h        ; TEB 구조체 위치
    mov eax, [eax+30h]        ; TEB를 기준으로 PEB 구조체 위치
    movzx eax, byte ptr [eax+2]        ; PEB!BeingDebugged flag
    retn


2 PEB!IsDebugged

    Example:
    mov eax, fs:[30h]        // PEB 구조체 위치
    mov eax, byte [eax+2]
    test eax, eax
    jne @DebuggerDetected
    ...
딱 보면 kernel32!IsDebuggerPresent와 같은 것을 알 수 있는데 함수 형태로 호출하는 것이 하는게 아니라 직접 PEB에 접근하는 것을 차이점으로 둘 수 있다.


3 PEB!NtGlobalFlags

Example:
mov eax, fs:[30h]        ; PEB 구조체 위치
mov eax, [eax+68h]      ; PEB+068 uint32 NtGlobalFlag 위치(dword)
and eax, 0x70
test eax, eax
jne @DebuggerDetected
...
NtGlobalFlag는 PEB 구조체 안에 존재하는 한 항목이다.(PEB 구조체 참조)  디버깅 중에는 0x70, 아니라면 0x0의 값이 세팅되어진다. 디버깅과 관련있는 세팅에 사용되는 상수는 아래의 것이 존재한다.

    FLG_HEAP_ENABLE_TAIL_CHECK (0x10) // 해제된 heap 메모리 영역의 buffer overruns을 검사하며, allocation된 끝에 짧은 패턴을 삽입한다.
    FLG_HEAP_ENABLE_FREE_CHECK (0x20) // 해제된 heap 메모리 영역의 유효성 검사
    FLG_HEAP_VALIDATE_PARAMETERS (0x40) // heap API가 호출되어질 때마다 몇가지 측면을 검사(파라미터)

다 합치면 0x70. 더 자세히 또는 그 외의 사용되는 상수에 대해 알고자 한다면 아래 링크를 참조.
http://technet.microsoft.com/en-us/library/cc779664.aspx


4 Heap flags

    Example:
    mov eax, fs:[30h]        ; PEB 구조체 위치
    mov eax, [eax+18h]        ; process heap
    mov eax, [eax+10h]        ; heap flags
    test eax, eax
    jne @DebuggerDetected
    ...
위의 NtGlobalFlag의 플래그값이 활성화되면서 Heap Flags도 활성화 되어 진다.
먼저 헷갈리지 않게 여기서 설명할 heap flags 항목은 2가지가 존재한다.
Flags와 ForceFlags로 각각 모두 4byte의 Uint4B형이다.

PEB.ProcessHeap.ForceFlags
PEB 구조체의 0x18에 위치하는 ProcessHeap 핸들로부터 0x10에 위치한 ForceFlags 값에 대한 내용이다. 프로세스의 첫 번째 heap이 생성 될 때 0x0으로 설정한다. 하지만 디버깅중이라면 0x40000060으로 설정이 된다.(Flags & 0x6001007D)


PEB.ProcessHeap.Flags
Flags 역시 ForceFlags와 같이 ProcessHeap 핸들의 0x0c 위치에 존재한다. 디버깅중이지 않는다면 0x2(HEAP_GROWABLE)로 설정이 되고, 디버깅중이라면 0x50000062으로 설정된다.
(NtGlobalFlags에 따라)
– HEAP_TAIL_CHECKING_ENABLED (0x20)
– HEAP_FREE_CHECKING_ENABLED (0x40)

우회(bypass)하는 방법
첫 번째-
    디버그하지 않은 상태로 프로세스를 생성한다.
    디버거를 attach시킨다.
두 번째-
    레지스트리 추가.
    "HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options" Create a subkey (not value)


5 Vista anti-debug (no name)

    Example:
    call GetVersion
    cmp al, 6
    jne @NotVista
    push offset _seh
    push dword fs:[0]
    mov fs:[0], esp
    mov eax, fs:[18h] ; teb
    add eax, 0BFCh
    mov ebx, [eax] ; pointer to a unicode string
    test ebx, ebx ; (ntdll.dll, gdi32.dll,...)
    je @DebuggerNotFound
    sub ebx, eax ; the unicode string follows the
    sub ebx, 4 ; pointer
    jne @DebuggerNotFound
    ;debugger detected if it reaches this point
    ;...
이 방법은 아직 명명되지 않았으나, 가칭 vista anti-debug라고 불린다.(Vista 32bit, SP0, English ver에서 테스트되어짐)
TEB+0xBFC 위치를 따라가게 되는데, 그 곳이 가리키고 있는 주소를 참조하는데 참조하는 곳에는 unicode 문자열로 system dll이 존재한다.(ntdll.dll, gdi32.dll,...)
만일 디버깅중이 아니라면 NULL 포인터가 있게 되고, 그러므로 문자열은 존재하지 않는다.


Exploiting system discrepancies
시스템(함수, syscall)과 관련된 안티 디버깅 기법이다.

1 NtQueryInformationProcess

사용되는 함수 타입 설명
NTSYSAPI NTSTATUS NTAPI NtQueryInformationProcess(
    IN HANDLE ProcessHandle,
    IN PROCESS_INFORMATION_CLASS ProcessInformationClass,
    OUT PVOID ProcessInformation,
    IN ULONG ProcessInformationLength,
    OUT PULONG ReturnLength
);
NtQueryInformationProcess는 ZwQueryInformationProcess를 감싸고 있는 함수이다.
매개변수 중 ProcessInformationClass는 7이 설정되는데 ProcessDebugPort 상수를 뜻하고,  ProcessInformation에 값을 저장받게 되는데 -1(0xFFFFFFFF)인 경우 프로세스가 디버깅 중인 상태이다. 그렇지 않을 때에는 0이 설정된다.
이 것은 매우 강력한 안티디버그이다. 그러나 프로그램을 추적하는 경우(디버깅), syscall returns할 때 ProcessInformation을 수정하여 교묘히 회피할 수 있다.(-1을 0으로 설정)
다른 방법은 ZwQueryInformationProcess를 hook하는 방법이 있다.
NtQueryInformationProcess를 사용하는 안티 디버깅 기법의 예로 CheckRemoteDebuggerPresent, UnhandledExceptionFilter가 있다.

    Example:
    push 0
    push 4
    push offset isdebugged
    push 7 ;ProcessDebugPort
    push -1
    call NtQueryInformationProcess
    test eax, eax
    jne @ExitError
위에서 다 설명하였으니 어셈 코드를 직접 보세요.


2 kernel32!CheckRemoteDebuggerPresent

    Example:
    push offset isdebugged
    push -1
    call CheckRemoteDebuggerPresent
    test eax, eax
    jne @DebuggerDetected
    ...
위에서 잠깐 말이 나온 CheckRemoteDebuggerPresent 함수에 대한 것이다.
매개변수로 process handle과 DWORD pointer가 사용된다. DWORD값으로 1이 설정된 경우 프로세스는 디버그 상태이다.
내부적 작동은 위에서 언급한 NtQueryInformationProcess으로 이루어져있다. 그러므로 우회하는 방법은 동일하게 ProcessInformation를 0으로 수정하면 된다.


3 UnhandledExceptionFilter

예외가 발생하면 Windows XP SP2, 2003, Vista OS에서 예외를 처리하는 일반적인 방법이다.

만약 프로세스 당 Vectored Exception Handler를 통과 처리한 경우(???)
처리되지 않은 예외가 스레드당 최상위 SEH Handler로 통과 처리 하고, 포인터가 FS:[0] 에서 그 예외가 생성되는 스레드인 경우.
만약 어떤 경우를 제외하고 이전 핸들러에 의해 처리되지 않았을 때 마지막 SEH Handler(set by the system)는 kernel32!UnhandledExceptionFilter를 호출한다.
 이 함수는 프로세스가 디버깅중이거나 디버깅 중이 아닌 여부에 따라 무엇을 해야 하는지 결정하게 된다.
만약 디버깅 중이라면, 프로그램은 종료 될 것이다(terminated)
(Advenced Windows Debugging 책에도 Exception dispatching logic이라고 하여 나온 순서도에서도 나온다. 정상적인 경우 디버깅중에는 절대 UnhandledExceptionFilter에 접근할 수 없다. 잘 이해가 안될 것 같아서 한 줄 더 적자면, 프로그램에서 무조건 Exception을 발생하는 코드가 존재한다. 그러고 UnhandlerExceptionFilter에 실제 코드를 넣어두고 호출하게끔 하였다고 하자. 그러면 디버깅중에는 절대 UnhandledExceptionFilter에 접근할 수 없으므로 원래의 소스 코드에는 접근할 수 없게 된다. 그것을 우회하기 위해 디버깅중이 아닌 것처럼 속여 UnhandledExceptionFilter를 호출하여 원래의 코드에 도달하기 위한 방법이다.)

kernel32!CheckRemoteDebuggerPresent와 마찬가지로 UnhandledExceptionFilter도 ntdll!NtQueryInformationProcess를 사용하여 디버깅 중인지 판단하게 된다.
해결 방법은 DebugPort를 해결하는 방법과 동일하다.(타고 들어가서 NtQueryInformationProcess의 ProcessInformation를 0으로 수정하기)

어셈 코드로 보면 아래와 같다. 더 추가하자면, setunhandledExceptionFilter 함수를 호출하면 아래의 .exception_filter로 jmp하게 되고 디버깅중일테니 -1을 리턴하게 된다. 그렇지만 리턴값 있는 있는 eax는 0으로 초기화하고, 그 안에 0을 넣어주고 있다.(디버깅중이지 않은 상태의 값)

;set the exception filter
    push    .exception_filter
    call    [SetUnhandledExceptionFilter]
    mov     [.original_filter],eax
    ;throw an exception
    xor     eax,eax
    mov     dword [eax],0
    ;restore exception filter
    push    dword [.original_filter]
    call    [SetUnhandledExceptionFilter]
    
.exception_filter:
    ;EAX = ExceptionInfo.ContextRecord
    mov     eax,[esp+4]
    mov     eax,[eax+4]
    ;set return EIP upon return
    add     dword [eax+0xb8],6
    ;return EXCEPTION_CONTINUE_EXECUTION
    mov     eax,0xffffffff        ; -1을 의미하며 디버깅 중인 상태
    retn

다른 예제이다. 아마도 위 예제가 더 보기 쉬울 것 같다. 위 예제를 이해했다면 아래 예제도 이해될 것이다

    Example:
    push @not_debugged
    call SetUnhandledExceptionFilter
    xor eax, eax
    mov eax, dword [eax] ; trigger exception
    ;program terminated if debugged
    ;...
    @not_debugged:
    ;process the exception
    ;continue the execution
    ;...
추가 설명 :
SetUnhandledExceptionFilter 함수 호출한 다음 access violation을 발생시킴.
디버깅중이라면 예외가 발생하여 종료되는 쪽으로 흐를 것이고, 디버깅 중이 아니라면 계속 진행될 것이다.


4 NtSetInformationThread

ntdll!NtSetInformationThread는 ZwSetInformationThread 시스템 함수를 감싸고 있다.

아래는 함수 타입 설명이다.

NTSYSAPI NTSTATUS NTAPI NtSetInformationThread(
    IN HANDLE ThreadHandle,
    IN THREAD_INFORMATION_CLASS ThreadInformationClass,
    IN PVOID ThreadInformation,
    IN ULONG ThreadInformationLength
);
ThreadInformationClass에 0x11((ThreadHideFromDebugger 상수)이 설정된 경우 스레드는 디버거로부터 분리될 것이다.

    Example:
    push 0
    push 0
    push 11h ;ThreadHideFromDebugger
    push -2
    call NtSetInformationThread
    ;thread detached if debugged
    ;...

ZwQueryInformationProcess와 마찬가지로 이 안티디버그를 우회할려면  ZwSetInformationThread의 매개변수를 호출되기 전에 수정하여야 한다.
해결책이 안나왔는데 ThreadInformationClass를 0x11말고 0같은거 주면 될려나?...


5 kernel32!CloseHandle and NtClose

사용자는 API를 만들어 ZwClose 함수를 syscall하여 CloseHandle을 통하여 간접적으로 디버거를 감지하는데 사용할 수 있다. 프로세스가 디버깅중에 ZwClose함수가 호출되면 유효하지 않은 핸들로 STATUS_INVALID_HANDLE (0xC0000008) exception을 생성하게 될 것이다.

이 안티디버그 역시 모든 안티디버그와 마찬가지로 커널(따라서 syscall 이용)로부터 직접적으로 사용할 수 있는 정보를 의존하게 된다.

CloseHandle 안티디버그를 무시하거나 우회하는 유일한 방법은 ring3에서 syscall 데이터를 함수가 호출되기 전에 수정하거나, 또는 커널을 후킹하는 방법이 있다.

이 안티디버그는 비록 매우 강력하지만, 널리 사용되는 악성코드 프로그램에서는 잘 보이지 않는다.(이용되지 않는다?)

    Example:
    push offset @not_debugged
    push dword fs:[0]
    mov fs:[0], esp
    push 1234h        ; invalid handle(유효하지 않은 핸들)
    call CloseHandle
    ; if fall here, process is debugged
    ;...
    @not_debugged:
    ;...


6 Self-debugging

프로세스는 스스로 디버그하려고 하여 디버깅중인지 디버깅중인지 감지(detect)할 수 있다.
새 프로세스를 생성하여(인스턴스생성) kernel32!DebugActiveProcess(pid)함수를 호출하여 디버거를 부모 프로세스로 올려놓을 수 있다.

DebugActiveProcess가 호출되어 디버거가 Attach되면 Child Process(원래의 프로세스를 의미)에는 ntdll!DbgUiDebugActiveProcess가 호출되게 되고 이 것은 ZwDebugActiveProcess를 syscall하게 될 것이다.
만약 프로세스가 디버깅중이라면, syscall(ZwDebugActiveProcess)은 실패한다. toolhelp32 APIs를 참고하여 부모 프로세스의 PID를 가져오는 과정을 수행할 수 있다.
(PROCESSENTRY32 구조체의 th32ParentProcessID 필드)

요약하자면, 프로세스 스스로 디버거를 물리고, 디버거가 물려있을 경우에는 함수 호출이 실패하므로, 실패했을 경우 toolhelp32 API를 참조해서 이미 물려있는 디버거를 terminated시키게 된다.


7 Kernel-mode timers

kernel32!QueryPerformanceCounter는 효율적인 안티디버그이다. 이 API는 ntdll!NtQueryPerformanceCounter를 호출하고, 그 안에는 ZwQueryPerformanceCounter(syscall)을 감싸고 있다.
이 함수는 안티디버그에 이용할수 있는 시간 함수이다. 최소 1ms단위이며, 디버깅을 Step by Step으로 하게 되면 프로그램 가동 시간이 늦어지게 된다.
그러므로 일정 시간 이상의 시간을 리턴할 경우 디버깅중이라는 것으로 간주할 수 있게 된다. 그럴 경우 종료시키도록 하겠지.

8 User-mode timers

kernel32!GetTickCount는 시스템이 시작된 후 얼마의 시간이 경과했는지를 milliseconds 단위로 반환한다. 역시 위와 동일하게 시간 차이를 비교하여 디버깅중인지 판단한다.
흥미로운 점은 커널을 사용하여 관련된 일을 수행하지 않는다. 주소공간을 user-mode 프로세스 카운터와 매핑한다.

8GB의 user-mode 공간에 값이 반환되어진다.

d[0x7FFE0000] * d[0x7FFE0004] / (2^24)
3FFE0005FFF80000 / 16777216 = 2D92BFBA0.63D7BF9A8

이 의미를 잘 모르겠......


9 kernel32!OutputDebugStringA

이 함수는 비쥬얼스튜디오에서 코딩시 디버그 창에 문자열을 출력하기 위한 함수이다. 특정 라인이 실행되었을 경우 콘솔창에 메시지를 출력하여 확인하는 것이 아니라, 디버그 창에 문자열을 출력하게 되므로 디버깅 용도로 사용 하며, 일반 화면에서는 보이지 않게 된다.

이것은 안티디버그의 진짜 원조(시초?, 정석?)이다.ReCrypt v0.80로 된 패킹된 파일안에서 발생하였다.
이 트릭은 유효하지 않은 ASCII 문자열의 구성으로 OutputDebugStringA 함수 호출하면서 발생한다.

만약 프로그램이 디버거의 제어하에 실행되는 경우, return값은 문자열의 주소를 매개변수로 전달하게 된다. 정상적인 조건에서 반환 값은 1이어야 한다.

    Example:
    xor eax, eax
    push offset szHello
    call OutputDebugStringA
    cmp eax, 1
    jne @DebuggerDetected
    ...
    szHello db "%s%s", 0

따로 설명은 없는데 OllyDbg의 버그(?)라는 소리를 들은 것 같다.


10 Ctrl-C

콘솔 프로그램을 디버깅중 일 때, Ctrl+C 키 신호(signal)가 발생하면 EXCEPTION_CTL_C exception이 던져질 것이다.(throw), 또는 발생. 반면에 프로그램이 디버깅중이지 않다면 signal handler로 바로 호출된다.

    Example:
    push offset exhandler
    push 1
    call RtlAddVectoredExceptionHandler
    push 1
    push sighandler
    call SetConsoleCtrlHandler
    push 0
    push CTRL_C_EVENT
    call GenerateConsoleCtrlEvent
    push 10000
    call Sleep
    push 0
    call ExitProcess
    exhandler:
    ;check if EXCEPTION_CTL_C, if it is,
    ;debugger detected, should exit process
    ;...
    sighandler:
    ;continue
    ;...


CPU anti-debug
CPU와 관련된 안티디버그

1 Rogue Int3

이것은 약한 디버거를 속이기 위한 고전적인 안티디버그이다. 정상적인 코드의 중간에 INT3 Opcode가 삽입되어져 있는 것으로 구성되어 있다.
INT3가 실행되어졌을 때, 만약 프로그램이 디버깅중이지 않다면, protection(보호)와 execution(실행), continue(재개)를 exception handler가 제어를 하게 될 것이다.
INT3 명령을 사용하면 software breackpoint를 설정하게 되고, INT3 Opcode는 디버거에 삽입한 breakpoint 중 하나라고 믿게 만드는 트릭이다.
마찬가지로 적어두면 INT3는 0xCD, 0x03으로 인코딩할 수 있다.
(위 내용은 뭔가 잘못된거 같다. 아래 예제에도 0xCC이고, Opcode 찾아보니 0xCC가 INT3이고, 0xCD는 INT Ib, 0x03은 ADD Gv,Ev 명령어이다.)

    Example:
    push offset @handler
    push dword fs:[0]
    mov fs:[0], esp
    ;...
    db 0CCh
    ;if fall here, debugged
    ;...
    @handler:
    ;continue execution
    ;...


2 "Ice" Breakpoint

소위 "Ice breakpoint" 라고 불리는 인텔(80386+)의 undocumented 명령으로 opcode는 0xF1이다. 그것을 추적 프로그램(tracing program)을 탐지하는데 사용한다.

이 명령을 실행하면 SINGLE_STEP exception이 생성된다. 그러므로 만약 프로그램이 이미 추적(디버깅)되고 있다면, 명령의 실행과 Flags Register에 SingleStep bit(Flags Register의 TF:Trap flag, 또는 tracing flag)가 설정(set)되고, 디버거는 일반적인 exception이 생성되었다고 생각할 것이다. 또한 연관된 exception handler가 실행되지 않을 것이다. 그리고 예상대로 계속 실행되지 않는다.

이 트랙의 무시는 간단하다:
single-stepping 명령을 건너 띄고 실행되게 한다(ignore Exception에 추가). 이 예외가 생성되었다면, 그 이후 프로그램은 추적되지 않고, 디버거는 exception handler에게 pass 제어를 보낸 것으로 이해한다.

    Example:
    push offset @handler
    push dword fs:[0]
    mov fs:[0], esp
    ;...
    db 0F1h
    ;if fall here, traced
    ;...
    @handler:
    ;continue execution
    ;...


3 Interrupt 2Dh

인터럽트가 실행될 때 프로그램이 디버깅중이지 않다면 breakpoint exception이 일어날 것이다. 만약 프로그램들이 디버깅 중이고 명령이 trace flag와 같이 실행되지 않았다면 예외는 생성되지 않고 정상적으로 실행되어질 것이다.
프로그램이 디버깅 중이고 명령이 추적되고 있다면, 다음 Byte는 생략되어지고 계속 실행 될 것이다.
따라서 INT 2Dh를 사용하는 것은 강력한 안티디버그와 anti-tracer(추적) 메커니즘로 사용될 수 있다.

    Example:
    push offset @handler
    push dword fs:[0]
    mov fs:[0], esp
    ;...
    db 02Dh
    mov eax, 1 ;anti-tracing
    ;...
    @handler:
    ;continue execution
    ;...

좀 더 쉽게 적자면
CD 2D(INT 2D)
33C0(XOR EAX,EAX)
83C0 02(ADD EAX,2)
원래 코드가 위와 같은 경우
C083 C002... 와 같은 코드로 변경되어 버린다.(33은 생략되어짐. 즉 Opcode를 정상적으로 인 인식할 수 없어져 계속 진행될 수 없어진다)

여기서 주의 할 점은 TF가 set되어지고 안되어지고에 따라 INT 2D는 다르게 실행된다.
TF가 set 되면 위의 설명처럼 다음 Byte는 생략되어지고 명령을 인식하지만, TF가 set되어져 있지 않다면 다음 Byte가 아닌 다음 명령 자체를 생략(건너뛰고)하고 실행하게 된다.


4 Timestamp counters

정밀도가 높은 카운터이고, 이 machine이 켜진 이후 cpu사이클이 실행되어진 현재의 횟수를 RDTSC 명령으로 검색할 수 있다.

오래된 안티디버그는 델타 시간(time deltas라는데 뭔지 모르겠음) 측정으로 이루어져 프로그램의 주요지점에 exception handler 이내 주위에 구성되어 있다.

만약 delta가 너무 큰 경우, 그 프로그램은 디버거의 제어하에 실행된다.(디버거에서 예외 처리를 하고, 다시 디버거를 제어할 수 있게 하는 긴 작업이다.)

    Example:
    push offset handler
    push dword ptr fs:[0]
    mov fs:[0],esp
    rdtsc
    push eax
    xor eax, eax
    div eax ;trigger exception
    rdtsc
    sub eax, [esp] ;ticks delta
    add esp, 4
    pop fs:[0]
    add esp, 4
    cmp eax, 10000h ;threshold
    jb @not_debugged ; Jb - 왼쪽 인자의 값이 오른쪽 인자의 값보다 작으면 점프(부호없는)
    @debugged:
    ...
    @not_debugged:
    ...
    handler:
    mov ecx, [esp+0Ch]
    add dword ptr [ecx+0B8h], 2 ;skip div
    xor eax, eax
    ret


5 Popf and the trap flag

trap flag는 Falgs Register안에 위치하고, 프로그램의 추적을 제어한다. 만약 이 플래그가 설정되어 있는 경우, 명령을 실행할 때 SINGLE_STEP exception이 발생하게 될 것이다. Trap Flag는 추적을 막아내기 위해 조절할 수 있다. 예를 들어, 명령의 순서를 trap flag에 설정할 수 있다.

    pushf (PUSHFD)        ; 모든 Flags Register의 플래그 값을 push
    mov dword [esp], 0x100        ; TF를 설정함(그 외 다른 플래그는 0으로)
    popf (POPFD)            ; 스택에 저장된 Flags Register 값을 pop 한다

만약 프로그램이 추적되고 있다면, 이 것은 flags register에 진짜로 영향을 미칠 것이고, 디버거는 정규(정상)적인 추적이라 믿게 되어 exception을 처리할 것이다. 만약 디버깅중이지 않는다면 예외 처리기는 실행되지 않을 것이다. anti-tracer 트릭을 우회하기 위해서는 pushf 명령을 건너뛸 필요가 있다.


6 Stack Segment register

이것은 아주 오리지날 anti-tracer이다. MarCrypt 패커에서 발생되었다.
난 이 것이 널리 알려져 있지 않고, 사용은 말할 것도 없다는 것을 믿는다.
이것은 명령어의 순서를 넘어 추적의 구성이다.

    push ss
    pop ss
    pushf
    nop

pop ss가 추적될 때, 다음 명령이 실행되어질 것이나 디버거는 브레이크에 걸리지 않는다. 따라서 그 다음 명령을 멈출 것이다.(NOP의 경우)
쉽게 설명하면 Step by Step으로 pop ss 를 실행하면 pushf를 건너띄고(생략되어짐) nop으로 넘어가게 된다.
MarCrypt는 다음과 같이 anti-debug를 사용한다.

    push ss
    ; junk
    pop ss
    pushf
    ; junk
    pop eax
    and eax, 0x100
    or eax, eax ; eax에는 0x100
    jnz @debugged ; 조건에 부합하여 점프하게 됨
    ; carry on normal execution

이 트릭은 만약 디버거가 명령어의 순서를 통해 추적이 되고 있다면, popf는 맹목적으로(절대적으로) 실행되어질 것이고, 그리고 디버거는 stack에 값을 push할때 trap flag를 unset 하지 못할 수 있게 된다. protection에 대하여trap flag를 check(확인)하고 그것을 발견하면 프로그램을 종료한다.
회피하는 한 한가지 간단한 방법은 popf에 breackpoint를 걸고(음... 하나하나 다 걸어야되나? 아니면 conditional breakpoint로도 될려나?) 프로그램을 실행하는 것이다.(TF 플래그의 사용을 피하기 위해)


7 Debug registers manipulation

Debug Register는(DR0에서 DR7까지) hardware breakpoint를 설정하기 위해 사용된다.
보호하거나 hardware breakpoint를 설정하고 감지하도록 조작할 수 있다.(따라서 그것은 디버깅되고 있다.)
다시 설정(reset)하거나 특정 값을 설정하여 나중에 검사를 수행하는데 사용할 수 있다.
tElock 같은 packer를 만들어 리버스 엔지니어들이 debug register의 사용을 방지한다.
사용자 모드 관점으로부터, debug register를 설정(set)하지 못하게 'mov drx, ...' 명령어을 privileged 하였다.(나쁜 개발자들...ㅜㅜ)

다른 방법이 존재한다 :
- exception이 생성되어지고, thread context가 수정되어지고(그것은 CPU Register가 포함되어지고 exception은 던져지게 된다.추가:Exception 발생할때 Context 레코드 떠올리면 될듯) 그리고 new context와 함께 정상적으로 실행을 재개할 것이다.
- 다른 방법은 NtGetContextThread와 NtSetContextThread syscall을 사용하면 된다.(Kernel32에 있는 GetThreadContext와 SetThreadContext도 사용 가능)

대부분의 protector가 "비공식적인" 첫번째 방법을 사용한다.
(이 말은 익셉션을 발생시켜서 설정해놓은 hardware breakpoint를 Context 레코드의 수정하여 초기화 시킨다는 뜻)

    Example:
    push offset handler
    push dword ptr fs:[0]
    mov fs:[0],esp
    xor eax, eax
    div eax ;generate exception
    pop fs:[0]
    add esp, 4
    ;continue execution
    ;...
    handler:
    mov ecx, [esp+0Ch] ;skip div
    add dword ptr [ecx+0B8h], 2 ;skip div
    mov dword ptr [ecx+04h], 0 ;clean dr0
    mov dword ptr [ecx+08h], 0 ;clean dr1
    mov dword ptr [ecx+0Ch], 0 ;clean dr2
    mov dword ptr [ecx+10h], 0 ;clean dr3
    mov dword ptr [ecx+14h], 0 ;clean dr6
    mov dword ptr [ecx+18h], 0 ;clean dr7
    xor eax, eax
    ret


8 Context modification

debug register 조작과 마찬가지로, context를 수정하고 사용하여 프로그램의 실행 흐름
을 파격적인 길로 가게 할 수 있다. 디버거에 혼란을 쉽게 줄 수 있다.


Uncategorized anti-debug

1 TLS-callback

이 anti-debug는 몇 년 전만해도 이렇게 잘 알려져 있지 않았다. 그것이 있는 PE Loader는 프로그램의 Thread Local Storage entry에 참조되어진 first entry point를 가리키는 것으로 구성된다.(TLS는 PE 구조에서 OptionalHeader에 존재하는 IMAGE_DATA_DIRECTORY 구조체의 10번째 항목을 의미한다.구조체 총 15개로 되어 있고, null 영역까지 하면 16개로 보는 것이 맞다. 또한 0번부터 시작하므로 9번의 위치이다)

이렇게 함으로서 프로그램의 entry-point는 첫번째로 실행되지 않는다. TLS entry를 수행하여 anti-debug 체크를 stealthy하게 한다.

참고로 이 기법은 광범위하게 사용되지는 않는다.
디버거(OllyDbg 포함)의 custom patcher tool의 plugin에 의해 TLS를 알지 못하여도, 쉽게 잡아낼 수 있다.(걸러낼 수 있다)


2 CC scanning

일반적인 protection 기능 사용에 의해 packers는 CC-scanning loop와, 디버거에 의해 설정된 software breakpoint를 탐지하는게 목표이다.

만약 당신이 그런 종류의 문제를 피하려고, 당신은 hardware breakpoint 또는 custom type의 softworae breakpoint를 사용할 수 있다.

CLI(0xFA)는 고전적인 INT3 opcode를 대체할 수 있는 좋은 후보이다. 이 명령은 작업을 위해 다음과 같은 요구사항을 갖고 있다.

만약 ring3 프로그램에 의해 실행되었다면, 1Byte의 공간만 차지하며 Privileged instruction Exception을 발생한다.
이건 ring3에선 대책이 없.....(아니구나 명령어 없애버려!)


3 EntryPoint RVA set to 0

일부 패킹된 파일은 RVA가 0으로 설정이 되어 있고, 'MZ'는 실행을 시작하는 것을 의미하고, 'dec ebx / pop edx'에 해당한다.
M은 0x4D로 이걸 opCode로 보면 DEC EBP가 되고, Z는 0x5A, Opcode로는 POP EDX가 된다.(정확히는 POP RDX)

이것은 anti-debug 트릭이 아니다. 하지만 만약 당신이 software breakpoint를 entry-point에 사용하여 break걸고 싶다면 성가시게 할 수 있다.

만약 당신이 정지된 프로세스를 생성하고 RVA 0(MZ 시그너처의 M 부분)에 INT3를 설정할 수 있다면 Magic MZ value(시그너쳐)('M')의 일부를 삭제할 수 있다. 그 magic이 프로세스가 생성되고 나서 확인할 수 있고, 하지만 ntdll은 프로세스를 재개하는 과정에서 다시 확인될 것이다.(entry-point에 도달할수 있는 가능성) 그 경우에는 INVALID_IMAGE_FORMAT Exception이 발생한다.

만약 당신이 당신 소유의 tracing이나 디버그툴을 만든다면 당신은 hardward breakpoint를 이 문제를 피하기위해 사용하게 하길 원할 것이다.


Data reference

                             CONTEXT structure for IA32 processors
struct CONTEXT_IA32
{
    // ContextFlags must be set to the appropriate CONTEXT_* flag
    // before calling (Set|Get)ThreadContext
    DWORD ContextFlags;
    // CONTEXT_DEBUG_REGISTERS (not included in CONTEXT_FULL)
    DWORD Dr0; // 04h
    DWORD Dr1; // 08h
    DWORD Dr2; // 0Ch
    DWORD Dr3; // 10h
    DWORD Dr6; // 14h
    DWORD Dr7; // 18h
    // CONTEXT_FLOATING_POINT
    FLOATING_SAVE_AREA FloatSave;
    // CONTEXT_SEGMENTS
    DWORD SegGs; // 88h
    DWORD SegFs; // 90h
    DWORD SegEs; // 94h
    DWORD SegDs; // 98h
    // CONTEXT_INTEGER
    DWORD Edi; // 9Ch
    DWORD Esi; // A0h
    DWORD Ebx; // A4h
    DWORD Edx; // A8h
    DWORD Ecx; // ACh
    DWORD Eax; // B0h
    // CONTEXT_CONTROL
    DWORD Ebp; // B4h
    DWORD Eip; // B8h
    DWORD SegCs; // BCh (must be sanitized)
    DWORD EFlags; // C0h
    DWORD Esp; // C4h
    DWORD SegSs; // C8h
    // CONTEXT_EXTENDED_REGISTERS (processor-specific)
    BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
};
             Process Environment Block structure (from The Wine Project)
struct PEB
{
    BOOLEAN InheritedAddressSpace; // 00
    BOOLEAN ReadImageFileExecOptions; // 01
    BOOLEAN BeingDebugged; // 02
    BOOLEAN SpareBool; // 03
    HANDLE Mutant; // 04
    HMODULE ImageBaseAddress; // 08
    PPEB_LDR_DATA LdrData; // 0c
    RTL_UPROCESS_PARAMETERS *ProcessParameters; // 10
    PVOID SubSystemData; // 14
    HANDLE ProcessHeap; // 18
    PRTL_CRITICAL_SECTION FastPebLock; // 1c
    PVOID /*PPEBLOCKROUTI*/ FastPebLockRoutine; // 20
    PVOID /*PPEBLOCKROUTI*/ FastPebUnlockRoutine; // 24
    ULONG EnvironmentUpdateCount; // 28
    PVOID KernelCallbackTable; // 2c
    PVOID EventLogSection; // 30
    PVOID EventLog; // 34
    PVOID /*PPEB_FREE_BLO*/ FreeList; // 38
    ULONG TlsExpansionCounter; // 3c
    PRTL_BITMAP TlsBitmap; // 40
    ULONG TlsBitmapBits[2]; // 44
    PVOID ReadOnlySharedMemoryBase; // 4c
    PVOID ReadOnlySharedMemoryHeap; // 50
    PVOID *ReadOnlyStaticServerData; // 54
    PVOID AnsiCodePageData; // 58
    PVOID OemCodePageData; // 5c
    PVOID UnicodeCaseTableData; // 60
    ULONG NumberOfProcessors; // 64
    ULONG NtGlobalFlag; // 68
    BYTE Spare2[4]; // 6c
    LARGE_INTEGER CriticalSectionTimeout; // 70
    ULONG HeapSegmentReserve; // 78
    ULONG HeapSegmentCommit; // 7c
    ULONG HeapDeCommitTotalFreeTh; // 80
    ULONG HeapDeCommitFreeBlockTh; // 84
    ULONG NumberOfHeaps; // 88
    ULONG MaximumNumberOfHeaps; // 8c
    PVOID *ProcessHeaps; // 90
    PVOID GdiSharedHandleTable; // 94
    PVOID ProcessStarterHelper; // 98
    PVOID GdiDCAttributeList; // 9c
    PVOID LoaderLock; // a0
    ULONG OSMajorVersion; // a4
    ULONG OSMinorVersion; // a8
    ULONG OSBuildNumber; // ac
    ULONG OSPlatformId; // b0
    ULONG ImageSubSystem; // b4
    ULONG ImageSubSystemMajorVersion; // b8
    ULONG ImageSubSystemMinorVersion; // bc
    ULONG ImageProcessAffinityMask; // c0
    ULONG GdiHandleBuffer[34]; // c4
    ULONG PostProcessInitRoutine; // 14c
    PRTL_BITMAP TlsExpansionBitmap; // 150
    ULONG TlsExpansionBitmapBits[32]; // 154
    ULONG SessionId; // 1d4
};
               Thread Environment Block structure (from The Wine Project)
struct TEB
{
    NT_TIB Tib; // 000 Info block
    PVOID EnvironmentPointer; // 01c
    CLIENT_ID ClientId; // 020 PID,TID
    PVOID ActiveRpcHandle; // 028
    PVOID ThreadLocalStoragePointer; // 02c
    PEB *Peb; // 030
    DWORD LastErrorValue; // 034
    ULONG CountOfOwnedCriticalSections; // 038
    PVOID CsrClientThread; // 03c
    PVOID Win32ThreadInfo; // 040
    ULONG Win32ClientInfo[0x1f]; // 044
    PVOID WOW32Reserved; // 0c0
    ULONG CurrentLocale; // 0c4
    ULONG FpSoftwareStatusRegister; // 0c8
    PVOID SystemReserved1[54]; // 0cc
    PVOID Spare1; // 1a4
    LONG ExceptionCode; // 1a8
    BYTE SpareBytes1[40]; // 1ac
    PVOID SystemReserved2[10]; // 1d4
    DWORD num_async_io; // 1fc
    ULONG_PTR dpmi_vif; // 200
    DWORD vm86_pending; // 204
    DWORD pad6[309]; // 208
    ULONG gdiRgn; // 6dc
    ULONG gdiPen; // 6e0
    ULONG gdiBrush; // 6e4
    CLIENT_ID RealClientId; // 6e8
    HANDLE GdiCachedProcessHandle; // 6f0
    ULONG GdiClientPID; // 6f4
    ULONG GdiClientTID; // 6f8
    PVOID GdiThreadLocaleInfo; // 6fc
    PVOID UserReserved[5]; // 700
    PVOID glDispachTable[280]; // 714
    ULONG glReserved1[26]; // b74
    PVOID glReserved2; // bdc
    PVOID glSectionInfo; // be0
    PVOID glSection; // be4
    PVOID glTable; // be8
    PVOID glCurrentRC; // bec
    PVOID glContext; // bf0
    ULONG LastStatusValue; // bf4
    UNICODE_STRING StaticUnicodeString; // bf8
    WCHAR StaticUnicodeBuffer[261]; // c00
    PVOID DeallocationStack; // e0c
    PVOID TlsSlots[64]; // e10
    LIST_ENTRY TlsLinks; // f10
    PVOID Vdm; // f18
    PVOID ReservedForNtRpc; // f1c
    PVOID DbgSsReserved[2]; // f20
    ULONG HardErrorDisabled; // f28
    PVOID Instrumentation[16]; // f2c
    PVOID WinSockData; // f6c
    ULONG GdiBatchCount; // f70
    ULONG Spare2; // f74
    ULONG Spare3; // f78
    ULONG Spare4; // f7c
    PVOID ReservedForOle; // f80
    ULONG WaitingOnLoaderLock; // f84
    PVOID Reserved5[3]; // f88
    PVOID *TlsExpansionSlots; // f94
};
                                     NtGlobalFlags
FLG_STOP_ON_EXCEPTION 0x00000001
FLG_SHOW_LDR_SNAPS 0x00000002
FLG_DEBUG_INITIAL_COMMAND 0x00000004
FLG_STOP_ON_HUNG_GUI 0x00000008
FLG_HEAP_ENABLE_TAIL_CHECK 0x00000010
FLG_HEAP_ENABLE_FREE_CHECK 0x00000020
FLG_HEAP_VALIDATE_PARAMETERS 0x00000040
FLG_HEAP_VALIDATE_ALL 0x00000080
FLG_POOL_ENABLE_TAIL_CHECK 0x00000100
FLG_POOL_ENABLE_FREE_CHECK 0x00000200
FLG_POOL_ENABLE_TAGGING 0x00000400
FLG_HEAP_ENABLE_TAGGING 0x00000800
FLG_USER_STACK_TRACE_DB 0x00001000
FLG_KERNEL_STACK_TRACE_DB 0x00002000
FLG_MAINTAIN_OBJECT_TYPELIST 0x00004000
FLG_HEAP_ENABLE_TAG_BY_DLL 0x00008000
FLG_IGNORE_DEBUG_PRIV 0x00010000
FLG_ENABLE_CSRDEBUG 0x00020000
FLG_ENABLE_KDEBUG_SYMBOL_LOAD 0x00040000
FLG_DISABLE_PAGE_KERNEL_STACKS 0x00080000
FLG_HEAP_ENABLE_CALL_TRACING 0x00100000
FLG_HEAP_DISABLE_COALESCING 0x00200000
FLG_VALID_BITS 0x003FFFFF
FLG_ENABLE_CLOSE_EXCEPTION 0x00400000
FLG_ENABLE_EXCEPTION_LOGGING 0x00800000
FLG_ENABLE_HANDLE_TYPE_TAGGING 0x01000000
FLG_HEAP_PAGE_ALLOCS 0x02000000
FLG_DEBUG_WINLOGON 0x04000000
FLG_ENABLE_DBGPRINT_BUFFERING 0x08000000
FLG_EARLY_CRITICAL_SECTION_EVT 0x10000000
FLG_DISABLE_DLL_VERIFICATION 0x80000000

이올린에 북마크하기
2009/06/10 03:15 2009/06/10 03:15

스택(Stack)과 포인터(Pointer) 간단한 설명

Posted at 2009/04/17 13:14 // in RCE // by 엔신
//////////////////////////////////////////////////////
#include <stdio.h>
int main()
{
    int a = 10, b = 20;
    int *pa;
    pa=&a;
    printf("%d\n%d\n", a, *pa);
    printf("%d\n%d\n", pa, &pa);
    return 0;
}
//////////////////////////////////////////////////////

c로 된 간단한 소스를 기준으로 설명을 하겠습니다. c언어를 배우게 되면서 많은 분들이 pointer를 배우다 좌절하는 경우가 많은데 그러한 이유는 아마도 메모리 구조를 이해하지 못하고 있기 때문에 일어나는 문제 같습니다. 메모리 구조를 이해하게 된다면 pointer는 아주 당연하게 받아들일 수 있습니다.

스택(stack), 힙(heap), Data, 코드(Code)라는 메모리 영역에 대해서 들어보신 적이 없다면, adioshun님의 글을 먼저 읽어 보시기 바랍니다.

지역변수인 a와 b와 pa는 stack에 선언이 됩니다. 전역변수의 경우에는 데이터 영역에 선언이 된다는 것은 이제 기본적으로 알고 있을 것이라 생각이 됩니다.

그러면 위와 같은 프로그램이 실행이 되게 되면 메모리 구조에서는 어떻게 나타날지 stack의 모습을 그려봤습니다.

사용자 삽입 이미지
그림의 두 스택은 같은 내용입니다. 다만 왼쪽의 내용을 보기 편하게 구분하기 위하여 오른쪽에 하나 더 그려 색깔을 칠해놓은 것입니다.

x86 cpu는 32비트입니다. 32비트는 4Byte를 의미하고 메모리 공간의 주소도 16진수로 최대 FFFF FFFF로 나타낼 수 있습니다.(이론상)주소값이 4Byte 단위 이므로 해당 주소값의 데이터 저장 공간도 4Byte로 최대 FFFF FFFF까지 저장이 가능해집니다.(int의 경우이고 long이나 double의 경우에는 8byte로 두 공간을 사용하게 됩니다)

그 중 특정 부분을 Stack의 공간으로 설정하여 사용하게 됩니다. 그러므로 현재 위의 그림에서는 0012F** 지점이 스택의 영역임을 전제하에 설명합니다. 실제 내부적으로는 주소값은 달라질 수 있지만 위와 같은 형은 그대로 적용이 됩니다.

위 그림에서는 보기 편하게 4byte 단위로 표현이 되고 있습니다. 실제로도 보통 4byte 단위로 처리가 됩니다. 주소가 거꾸로 나와 있다고 잘못된게 아니냐고 하시는 분이 계실지 모르겠는데, Intel CPU는 Full Descending Stack으로 큰 주소에서 작은 주소로 데이터가 쌓이게 됩니다.

RET Address :
Return Address를 의미하며, 테스트에 사용한 c 프로그램을 호출하기 이전에도 무언가 내부적으로 함수가 사용되고 있었고, 그러던 중 프로그램을 호출하게 된 것이므로 프로그램이 종료되면 다시 해당 프로그램을 호출했던 지점으로 돌아가서 기존에 내부적으로 돌아가던 곳으로 돌아가 계속 실행을 해야 합니다. 그러기 위해 돌아갈 곳의 주소를 기억해놓게 되는 주소입니다.

SFP(Stack Frame Pointer) :
스택 포인터 프레임를 의미합니다. 스택은 고정적이지 않고 항상 유동적으로 변하는 저장 공간입니다. 그러므로 기준점(EBP)을 세워서 스택의 위치를 계산해서 해당 번지에 있는 값이나 주소를 찾아야 하는데, 만일 스택의 시작을 기준으로 한다면 처음에는 스택에 쌓인 데이터가 별로 없을 것이므로 시작과 쌓인 위치로 가서 값을 구하는데 무리가 없을 것입니다. 하지만 점점 스택에 데이터가 쌓이게 되면 시작 위치와는 점점 거리가 멀어지게 되므로 데이터의 값을 구하거나 저장하는데 있어서 상당한 오버헤드가 발생하게 됩니다. 그래서 사용하게 되는 것이 SPF입니다.
SPF는 기준점을 함수의 호출이 된 지점으로부터 계산하게 되는 것입니다. 해당 함수 내에서 사용하는 데이터들은 근처에 쌓여 있을 것이므로 값을 구하거나 저장하는데 있어서 오버헤드가 적어지게 됩니다.
예를 들면 main함수를 돌다가 func이라는 함수를 호출하게 되면 기존 main함수의 기준점(EBP) 백업해놓고 새로운 기준점을 세워야 합니다. 그러기 위해 main함수의 기준점(EBP)을 주소를 스택에 저장하게 됩니다.(즉 포인터처럼 주소값이 저장되게 됩니다.)

즉, 현재 main 함수는 SPF에서부터 시작입니다.(함수 시작이 될땐 반드시 이전 함수의 기준점(EBP)를 백업하므로)

그리고 int a, b; 가 되어 있으므로 가장 먼저 지역 변수를 사용하기 위해 스택에 공간을 할당하게 됩니다. int형 변수가 총 3개이므로 4 byte * 3 하여 공간이 할당되어지게 됩니다. 그 후 데이터가 할당되어진 공간에 들어가게 되는데 int는 4byte의 자료형이고 스택에서 기본적으로 4byte단위로 계산이 되기 때문에 위에처럼 쌓이게 됩니다. 참고할 점은 int a, b; 가 있으면 right->left의 순서로 스택에 쌓이므로 b가 먼저 쌓이고 a의 데이터가 쌓이게 됩니다.

그리하여 메모리 주소 0012FFE8에는 0x14(10진수 20), 0012FFE4에는 0x0a(10진수 10)가 들어가서 위의 그림과 같이 되게 됩니다.

int *pa; 는 포인터입니다. 포인터는 주소값을 저장할 수 있는 공간입니다. 주소값 체계는 4byte(32비트)로 구성이 되어 있기 때문에 포인터의 저장 공간 역시 4byte가 되어지게 됩니다. 프로그램 실행 초기 단계에서 이미 할당해 놓았기 때문에 0012FFE0의 메모리 주소가 포인터 변수의 주소가 됩니다.

그리고 포인터 변수 pa에 변수 a의 &(주소)를 =(대입)시키게 됩니다. 위 그림에서 보다시피 a의  시작 주소는 0012FFE4가 이므로 포인터 변수 pa의 공간은 4byte이므로 주소체계인 4byte의 크기와 동일하기에 손실이나 변화 없이 삽입이 가능합니다.

참고할 점은 long *paa; 라는 변수가 있다면 해당 포인터 변수는 8byte가 아닌 4byte의 공간을 갖게 되고 long형 데이터가 존재하는 변수의 시작 위치를 기억하게 할 수 있게 되는 것입니다.

추가적으로 파란색으로 하이라이트된 부분은 아직 할당되어 쓰이고 있지 않은 공간을 의미합니다. 해당 영역에는 0이 들어가 있을 수 있고 그 외의 값이 들어가 있을 수 있습니다. 하지만 그러한 값은 쓰레기 값으로 현재에는 할당되어진 값이 아니기에 필요하지 않은 값들입니다.(이전에 해당 주소가 사용된 흔적의 값일뿐입니다.)

이러한 내용이 이해가 된다면 포인터의 원리를 이해할 수 있게 될 것입니다. 말을 잘 못 하고 글을 잘 못쓰기에 의도하는 바가 제대로 표현이 안됐을 수 있지만, 포인터와 스택을 이해하시는데 도움 되시는 분 계셨으면 좋겠습니다.

이올린에 북마크하기
2009/04/17 13:14 2009/04/17 13:14

[Unpack] MEW 11 1.2 -> NorthFox/HCC

Posted at 2009/03/20 18:34 // in RCE // by 엔신
사용자 삽입 이미지

MEW 11 SE 1.2 Packer로 패킹한 후 언패킹하여 보았다.
확인하여 보니 Entry Point가 첫 번째 Section이 아님을 알 수 있다.
디버깅을 하여 보면 아래에서 처음 시작하게 된다.

0047C526 >-E9 293CF8FF      JMP unpacked.00400154

가 나오고 해당 영역으로 점프해서 확인하여 보면 패킹된 데이터를 복호화하는 함수들이 나타나게 된다.

00400154   BE 1CA04500      MOV ESI,unpacked.0045A01C
00400159   8BDE             MOV EBX,ESI
0040015B   AD               LODS DWORD PTR DS:[ESI]
0040015C   AD               LODS DWORD PTR DS:[ESI]
0040015D   50               PUSH EAX
0040015E   AD               LODS DWORD PTR DS:[ESI]
0040015F   97               XCHG EAX,EDI
00400160   B2 80            MOV DL,80
00400162   A4               MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]
00400163   B6 80            MOV DH,80
00400165   FF13             CALL DWORD PTR DS:[EBX]
00400167  ^73 F9            JNB SHORT unpacked.00400162
00400169   33C9             XOR ECX,ECX
0040016B   FF13             CALL DWORD PTR DS:[EBX]
0040016D   73 16            JNB SHORT unpacked.00400185
........................... 생략 ...................................
004001F1   91               XCHG EAX,ECX
004001F2   40               INC EAX
004001F3   50               PUSH EAX
004001F4   55               PUSH EBP
004001F5   FF53 F4          CALL DWORD PTR DS:[EBX-C]
004001F8   AB               STOS DWORD PTR ES:[EDI]
004001F9   85C0             TEST EAX,EAX
004001FB  ^75 E5            JNZ SHORT unpacked.004001E2
004001FD   C3               RETN
004001FE   0000             ADD BYTE PTR DS:[EAX],AL
00400200   0000             ADD BYTE PTR DS:[EAX],AL
00400202   0000             ADD BYTE PTR DS:[EAX],AL
00400204   0000             ADD BYTE PTR DS:[EAX],AL

중간 복호화하는 데이터를 확인하여 보고 밑으로 더 내리다보면 결국에는 RETN이 나오게 된다.
해당 영역에 브레이크 포인트를 걸고 Run 한다.
잠시 후 브레이크가 걸리고 Step by로 RETN을 하면 OEP가 나오게 된다.

사용자 삽입 이미지

그러나 아직 Analyse Code가 이루어지지 않았다.
Analysis -> Remove analysis from module 메뉴를 이용하면 정상적인 코드 확인이 가능해진다.

사용자 삽입 이미지

OEP가 45834라는 것을 확인하였으니 IAT를 찾아야 한다.
Cntl + B 키를 이용하여 Serach for Binary를 이용하여 FF25를 검색한다.

사용자 삽입 이미지

Search를 통해 찾은 내용을 Dump 윈도우에서 Long->Address 타입으로 보게 되면 위와 같이 확인하기 쉽게 IAT를 볼 수 있다.

4486C4(끝주소) - 448104(시작주소) = 5C0(Size)

사용자 삽입 이미지

OllyDump나 ImpREC의 Dump 기능을 이용하여 일단 메모리에 로드된 데이터를 Dump하고 ImpREC 툴을 사용하여 IAT를 수정하고 Fix Dump하여 주면 정상적으로 Unpacking 완료이다.

이올린에 북마크하기
2009/03/20 18:34 2009/03/20 18:34

[Unpack] USSR_0.31

Posted at 2009/03/17 16:22 // in RCE // by 엔신
사용자 삽입 이미지
USSR_0.31은 시그네쳐 기반의 패킹 분석툴로는 확인이 안될 가능성이 높다. 그러나 Entry Point가 첫 Section이 아니라는 점과 Name의 .USSR를 통하여 패킹되어 있음을 확인할 수 있다.

먼저 Olly Plugin 중 PhantOm 을 사용하여 hide from PEB 옵션을 사용하여 PEB 관련 디버그 flag의 작동을 막도록 하자.(그렇지 않으면 제대로 언팩할수 없을테니!)

사용자 삽입 이미지

플러그인을 설정한 다음 처음 로드시 확인할 수 있는 소스이다.

0046B000 > E9 1B000000      JMP UnPackMe.0046B020
0046B005   B8 1CF76265      MOV EAX,6562F71C
0046B00A   B1 F0            MOV CL,0F0
0046B00C   35 CBAD852E      XOR EAX,2E85ADCB
0046B011   55               PUSH EBP
0046B012   C685 6C5A3B3B 90 MOV BYTE PTR SS:[EBP+3B3B5A6C],90
0046B019   EE               OUT DX,AL                                ; I/O command
0046B01A   DB               ???                                      ; Unknown command
0046B01B   A4               MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]
0046B01C   BF 5FAC6F56      MOV EDI,566FAC5F
0046B021   E8 5B000000      CALL UnPackMe.0046B081
0046B026   77 4A            JA SHORT UnPackMe.0046B072
0046B028   3F               AAS
0046B029   3B1D ADE37971    CMP EBX,DWORD PTR DS:[7179E3AD]

먼저 Import 되는 함수들을 찾을 수 있다.
Olly의 Memory 윈도우를 확인하여 보면 imports Contain을 확인할 수 있다. 해당 영역을 Dump CPU하여 Dump창으로 Follow하고 460000 영역을 Long Address 타입으로 변경하여 확인하면 쉽게 Import Table을 찾을 수 있다.

사용자 삽입 이미지

IAT를 먼저 찾았으므로 기억하기 쉽게 메모해서 시작 지점과 Size를 계산해 놓는다.
Start Offset : 460818 - 40000(ImageBase값) = 60818
Size : 60F28(끝) - 60814(시작) = 714

IAT를 찾았으므로 이번엔 소스를 찾아보자.

사용자 삽입 이미지

Memory 윈도우에서 401000 영역 전체에 대하여 Access Break Point를 걸고 Run(F9)하면 위와 같은 화면에서 Exception이 발생할 수 있다. 아래와 같은 지점에서 익셉션을 처리하고 진행하면 Break가 걸리게 된다.

사용자 삽입 이미지

0046BC9D   3006             XOR BYTE PTR DS:[ESI],AL
0046BC9F   8A06             MOV AL,BYTE PTR DS:[ESI]
0046BCA1   46               INC ESI                                  ; UnPackMe.004271B0
0046BCA2   81FE AF724200    CMP ESI,UnPackMe.004272AF
0046BCA8  ^7C F3            JL SHORT UnPackMe.0046BC9D

위 소스를 보면 XOR를 연산 수행을 계속 반족적으로 하게 됨을 확인할 수 있다. 브레이크가 걸린 지점이 004271B0이라는 것을 확인할 수 있고, Dump 영역이라는 것을 알 수 있다. F7키나 F8키를 사용하여 Step 단위로 디버깅하여 보면 덤프 영역의 값이 바뀌는 것을 확인할 수 있다. 일일히 반복이 끝날때까지 Step으로 확인할 수 없으므로 반복이 벗어나는 지점에 브레이크를 걸고 Run(F9)하여 복호화가 끝난 상태로 만든다.

여전히 Disassemble 창에서 확인하여도 제대로 된 코드가 보이지 않으므로 Analyse Code 메뉴를 통해 분석하여 보면 아래와 같은 정상적인 코드 확인이 가능해진다. 확인하면 OEP는 4271B0이라는 것을 알 수 있다.

사용자 삽입 이미지

OEP와 IAT를 찾았으므로 OllyDump나 ImpREC 툴의 Dump 기능을 이용하여 Dump를 할 수 있으나, ImpREC 툴을 이용하여 Dump를 뜨는 것을 더 추천한다.
확인 결과 예제에 사용된 Unpackme 파일을 OllyDump를 이용하여 Dump를 하였더니 PE FileHeader의 Machine의 값 2Byte가 00 00 으로 수정되어 버렸다. 원래의 값은 4C 01(0x14C:i386또는 IA32를 의미함)이다. 32비트 기반의 프로그램은 모두 해당 Machine의 값을 가지고 있어야 한다. 그러나 실제 Unpackme 파일을 Hex Editer로 확인한 결과 4C 01로 되어 있지만 Olly로 디버깅중인 상태에는 00 00 으로 되어 있었다. 아무래도 그 영향으로 인하여 OllyDump는 메모리에 올라와 있는 00 00 이라는 Machine의 값을 그대로 저장한 것이고, ImpREC의 툴은 정상적인 4C 01로 저장하게끔 만들어져 있는 것 같다.

그러므로 ImpREC 툴에서 오른쪽 버튼을 눌러 Advanced Commands -> Select Code Section(s) 메뉴를 선택하고 바로 Dump한다.

그리고 OEP와 입력되어 있는 IAT을 기반으로 Fix Dump하게 되면 정상적으로 Dump가 되며 언팩킹이 완료가 된다.

사용자 삽입 이미지

Hex Workshop의 Compare 기능을 이용하여 비교해 본 결과 딱 2Byte만 다른 것을 확인할 수 있었다.

사용자 삽입 이미지

또한 디버깅 중인 상태에서는 Machine의 값이 00 00 인 것을 확인할 수 있었다.
실제 패킹된 원본 파일은 4C 01로 정상적인 IA32의 값을 나타내고 있다.
메모리에 로드되면서 왜 바뀐지는 알 수 없으나, 항상 PE 구조를 염두하며 자동화 툴은 전적으로 신뢰하지 말고 Unpacking에 임하기를 바란다.
이올린에 북마크하기
2009/03/17 16:22 2009/03/17 16:22

[Unpack] VCrypt0[1].9b

Posted at 2009/03/14 14:06 // in RCE // by 엔신
0046B000 > 8BC4             MOV EAX,ESP
0046B002   FF30             PUSH DWORD PTR DS:[EAX]
0046B004   8368 FC 4F       SUB DWORD PTR DS:[EAX-4],4F
0046B008   68 B0714200      PUSH UnPackMe.004271B0
0046B00D   FF50 FC          CALL DWORD PTR DS:[EAX-4]
0046B010   83C4 08          ADD ESP,8
0046B013   FF70 F8          PUSH DWORD PTR DS:[EAX-8]
0046B016   B3 99            MOV BL,99
0046B018   B8 00104000      MOV EAX,UnPackMe.00401000
0046B01D   3D 00A24400      CMP EAX,UnPackMe.0044A200
0046B022   74 07            JE SHORT UnPackMe.0046B02B
0046B024   3018             XOR BYTE PTR DS:[EAX],BL
0046B026   D0C3             ROL BL,1
0046B028   40               INC EAX
0046B029  ^EB F2            JMP SHORT UnPackMe.0046B01D
0046B02B   B3 F8            MOV BL,0F8
0046B02D   B8 00704500      MOV EAX,UnPackMe.00457000
0046B032   3D 00C84500      CMP EAX,UnPackMe.0045C800
0046B037   74 07            JE SHORT UnPackMe.0046B040
0046B039   3018             XOR BYTE PTR DS:[EAX],BL
0046B03B   D0C3             ROL BL,1
0046B03D   40               INC EAX
0046B03E  ^EB F2            JMP SHORT UnPackMe.0046B032
0046B040   E8 00000000      CALL UnPackMe.0046B045
0046B045   C3               RETN

PUSH를 하면 ESP가 변경되게 된다. 그러면 ESP를 Dump창으로 옮겨서 최상위 4Byte에 하드웨어 엑세스 브레이크 포인트를 Dword로 건다.
그리고 Run(F9)를 몇차례 하면 아래와 같이 나온다.

004271B0   . 55             PUSH EBP
004271B1   . 8BEC           MOV EBP,ESP
004271B3   ? 6A FF          PUSH -1
004271B5   ? 68 600E4500    PUSH UnPackMe.00450E60
004271BA     68             DB 68                                    ;  CHAR 'h'
004271BB     C8             DB C8
004271BC     92             DB 92
004271BD     42             DB 42                                    ;  CHAR 'B'
004271BE     00             DB 00
004271BF     64             DB 64                                    ;  CHAR 'd'
004271C0     A1             DB A1
004271C1     00             DB 00
004271C2     00             DB 00

함수 프롤로그 부근에 도달하였으면 Analysis -> Analyse Code 메뉴를 실행하면 정상적인 코드로 보이게 된다.

004271B0   . 55             PUSH EBP
004271B1   . 8BEC           MOV EBP,ESP
004271B3   . 6A FF          PUSH -1
004271B5   . 68 600E4500    PUSH UnPackMe.00450E60
004271BA   . 68 C8924200    PUSH UnPackMe.004292C8                   ;  SE handler installation
004271BF   . 64:A1 00000000 MOV EAX,DWORD PTR FS:[0]
004271C5   . 50             PUSH EAX
004271C6   . 64:8925 000000>MOV DWORD PTR FS:[0],ESP
004271CD   . 83C4 A8        ADD ESP,-58
004271D0   . 53             PUSH EBX
004271D1   . 56             PUSH ESI
004271D2   . 57             PUSH EDI
004271D3   . 8965 E8        MOV DWORD PTR SS:[EBP-18],ESP
004271D6   . FF15 DC0A4600  CALL DWORD PTR DS:[<&KERNEL32.GetVersion>;  kernel32.GetVersion

이곳이 OEP이다.
ImpREC 툴을 사용하여 OEP에 4271B0 - 40000(ImageBase값) = 271B0를 적어준다.

IAT를 찾기 위하여 자동화 툴을 사용하지 않고 수동으로 찾는다

For Search Binary String을 통해 FF25를 검색한다.

00435CC0   $-FF25 88094600  JMP DWORD PTR DS:[<&KERNEL32.RtlUnwind>]         ;  ntdll.RtlUnwind
00435CC6   $-FF25 000C4600  JMP DWORD PTR DS:[<&USER32.ReuseDDElParam>]      ;  USER32.ReuseDDElParam
00435CCC   $-FF25 040C4600  JMP DWORD PTR DS:[<&USER32.UnpackDDElParam>]     ;  USER32.UnpackDDElParam
00435CD2   $-FF25 D00E4600  JMP DWORD PTR DS:[<&comdlg32.ChooseFontA>]       ;  comdlg32.ChooseFontA
00435CD8   $-FF25 D80E4600  JMP DWORD PTR DS:[<&comdlg32.ChooseColorA>]      ;  comdlg32.ChooseColorA
00435CDE   $-FF25 D40E4600  JMP DWORD PTR DS:[<&comdlg32.PrintDlgA>]         ;  comdlg32.PrintDlgA
00435CE4   $-FF25 CC0E4600  JMP DWORD PTR DS:[<&comdlg32.GetSaveFileNameA>]  ;  comdlg32.GetSaveFileNameA
00435CEA   $-FF25 C80E4600  JMP DWORD PTR DS:[<&comdlg32.GetOpenFileNameA>]  ;  comdlg32.GetOpenFileNameA
00435CF0   $-FF25 C40E4600  JMP DWORD PTR DS:[<&comdlg32.GetFileTitleA>]     ;  comdlg32.GetFileTitleA
00435CF6   $-FF25 C00E4600  JMP DWORD PTR DS:[<&comdlg32.ReplaceTextA>]      ;  comdlg32.ReplaceTextA
00435CFC   $-FF25 BC0E4600  JMP DWORD PTR DS:[<&comdlg32.FindTextA>]         ;  comdlg32.FindTextA
00435D02   $-FF25 B80E4600  JMP DWORD PTR DS:[<&comdlg32.CommDlgExtendedErro>;  comdlg32.CommDlgExtendedError
00435D08   $-FF25 B00E4600  JMP DWORD PTR DS:[<&WINSPOOL.ClosePrinter>]      ;  WINSPOOL.ClosePrinter
00435D0E   $-FF25 940E4600  JMP DWORD PTR DS:[<&WINSPOOL.EndDocPrinter>]     ;  WINSPOOL.EndDocPrinter
00435D14   $-FF25 AC0E4600  JMP DWORD PTR DS:[<&WINSPOOL.StartPagePrinter>]  ;  WINSPOOL.StartPagePrinter

위와 같은 부분을 만날수 있게 되고
Follow in Dump Memory Address 를 하게 되면 덤프 창에서 IAT를 찾을 수 있다. 이때 Dump 창을 Long의 Address 형식으로 보면 보기 수월하다.

00460818 >77F56C17  ADVAPI32.RegCloseKey
0046081C >77F57842  ADVAPI32.RegOpenKeyExA
00460820 >77F5E9E4  ADVAPI32.RegCreateKeyExA
00460824 >77F5EAD7  ADVAPI32.RegSetValueExA
00460828 >77F57AAB  ADVAPI32.RegQueryValueExA
0046082C  00000000
00460830 >5C8265CF  COMCTL32.InitCommonControls
00460834 >5C8303D8  COMCTL32.ImageList_Destroy
00460838  00000000
0046083C >77E26AA1  GDI32.GetClipBox
00460840 >77E290DD  GDI32.ExcludeClipRect
00460844 >77E26A56  GDI32.IntersectClipRect
00460848 >77E2A20A  GDI32.MoveToEx

이런 식으로 덤프창에 보이게 된다.
60818이 시작주소이고
60F2B(마지막주소)-60818(시작주소) = 713 라는 Size값을 확인할 수 있다.

그러나 IAT를 찾을 필요가 없을 수도 있다. OllyDump만으로 하여도 이미 IAT가 정상적으로 설정이 된 경우일 수 있기 때문이다.

이올린에 북마크하기
2009/03/14 14:06 2009/03/14 14:06

[MUP] UPX & Stolen Bytes

Posted at 2009/02/17 00:23 // in RCE // by 엔신
# Unpacking에 사용된 파일은 심플스(http://simples.co.kr)의 unpackme 파일임을 밝혀둡니다.
# 그림이 잘 보이지 않을 경우, 클릭하여 보시면 원래의 사이즈로 볼 수 있습니다.

사용자 삽입 이미지

PEiD라는 PE Header 분석 프로그램을 이용하여 확인하여 보면 UPX 0.89.6 - 1.02 / 1.05 - 2.90 -> Markus & Laszlo [Overlay] 라는 Packing 정보를 확인할 수 있습니다. 한가지 참고할 점은 분석 프로그램을 전적으로 신뢰하지 않는 것이 좋습니다. 시그너쳐를 조작하게 되면 잘못된 정보를 나타내기도 하며, 그럴 경우에 분석을 오히려 더 어렵게 할 수 있습니다.

UPX의 경우 압축을 목적으로 하는 Packer이며 Unpacker가 존재하며 http://upx.sourceforge.net 에서 받을 수 있습니다.

사용자 삽입 이미지

위 그림은 대상 프로그램을 처음 불러왔을 때의 모습입니다.

PUSHAD
PUSHAD는 모든 Register를 Stack에 PUSH하는 명령어입니다. 오른쪽 아래의 Stack Window를 자세히 살펴 보면 알 수 있습니다.

PUSHAD를 Step into(F7)나 Step over(F8)를 이용하여 실행한 다음 Stack Window에서는 Breakpoint를 걸 수 없기 때문에 Dump Window를 통해 Stack을 보게 한 다음 Stack의 가장 위에 Breakpoint를 겁니다. ESP에서 Follow in Dump를 하면 간단하게 이동이 가능합니다. Dword가 4Byte를 뜻하며, 메모리 주소값은 4Byte로 표현이 됩니다.

Breakpoint를 걸었으면 Run(F9)하여 진행을 하여 보면 아래 위치에서 Break가 걸리게 됩니다.

사용자 삽입 이미지

POPAD
Stack에 백업되었던 모든 Register의 값들을 원래대로 Register에 복구 시킵니다.

POPAD 명령을 수행 한 다음에 Register는 원래대로 복구가 됩니다. 복구가 되는 과정에서 ESP의 위치를 Access를 하게 되는데 이때 Break가 걸리게 됩니다. POPAD를 조금 더 내려가면 JMP가 보이게 되는데 그 곳이 JMP를 하게 되면 OEP로 진입을 하게 됩니다. 그러나 Highlight된 부분의 Stolen Byte가 문제가 됩니다.
OEP로 진입한 다음 Dump를 하게 되면 위의 내용이 손실되게 됩니다. 위의 내용은 실제 소스에서 사용되는 매개변수인데, 매개변수가 손실되게 되면 함수를 호출시에 문제가 발생하게 됩니다.

StolenByte
Stolen Bytes는 패커가 위치를 이동시킨 코드로써 보호된 프로그램의 코드의 윗부분(보통은 엔트리 포인트의 몇 개의 명령어)입니다. 이 부분의 명령어는 이동된 곳 이나 할당 받은 메모리 공간에서 실행됩니다.
보호된 프로세스의 메모리가 덤프 되었을 때 Stolen Bytes 를 복구 하지 못한다면 덤프된 실행파일은 작동하지 않습니다.

6A 00
68 00204000
68 12204000

라는 Stolen Byte를 메모장에 열어서 복사하고 OEP로 진입합니다.

사용자 삽입 이미지

OEP로 진입하게 되면 위와 같은 내용이 나옵니다. 여기서 Dump를 하여야 하는데 좀 전의 Stolen Byte는 다른 영역(Packer의 영역)에 존재하므로 OEP를 100C(400000은 ImageBase이므로 생략)로 하여 Dump를 하게 되면 조금 전의 Stolen Byte로 되어진 MessageBoxA 함수의 매개변수가 손실되게 됩니다.

다행히 100C의 위에는 NOP Code로 비어진 공간이 존재하여 해당 영역에 Ctrl + E 키를 사용하거나 Binary Edit로 복사하여 놓은 Stolen Byte를 삽입합니다.

사용자 삽입 이미지

예제로 된 파일이라 NOP Code로 된 사이즈는 알맞게 Stolen Byte를 삽입하게 되어 있습니다.
그러나 실제 Solen Byte가 예제처럼 딱 맞는 경우가 항상이라고 생각하시면 안됩니다. 때에 따라서는 삽입할 수 있는 공간이 넉넉할 수 있지만, 그렇지 못할 경우에는 다른 남는 NOP Code가 존재하는 공간을 찾아 삽입하고 JMP 명령어를 사용하여 원래 실행되어야 하는 OEP로 오게 하여야 정상적으로 프로그램이 실행이 가능해집니다. 이제 Stolen Byte를 복구하였으므로 Dump를 하여야 합니다.

사용자 삽입 이미지

OllyDump Plugin을 사용하여 Dump를 하여야 하는데, 확인하여야 하는 부분은 Highlight된 Modify와 Rebuild Import입니다.

Modify는 Dump하기 이전의 Entry Point(최초 프로그램 시작점. Packer의 영역)를 원래의 OEP(Original Entry Point)로 수정하는 것입니다. OllyDump가 자동으로 잡는 값은 현재 Break가 걸린 위치이므로 100C라고 나오게 됩니다. 그러나 Stolen Byte를 삽입하였고 그 시작점이 1000이 되었습니다.(해당 위치에 ImageBase 4000000를 더하면 됩니다) 그러므로 우리는 1000이라고 값을 입력합니다

두번째로 Rebuild Import는 IAT(Import Address Table)을 OllyDump가 자동으로 찾는 옵션이지만, 자동으로 찾는 경우가 드물기 때문에 체크해제하고 수동으로 IAT를 수정하여 주어야 합니다. 찾기 이전에 Dump를 하여 임의의 파일명으로 저장을 합니다. 저 같은 경우에는 unpackme_05.exe 파일이라는 원본 파일명 뒤에 _dump를 붙여 unpackme_05_dump.exe 파일로 저장하였습니다.

IAT를 찾는 방법은 Disassembler Window에서 Ctrl + B키를 이용하여 FF25라는 Hex 값을 Search하여 찾을 수도 있고, OEP의 주변을 직접 에서 마우스 스크롤을 통하여 찾아보는 방법도 있습니다. 특별한 방법이 존재하는 것은 개인의 역량에 따라 찾는 속도가 차이가 나게 됩니다. 분석을 많이 해본 사람은 경험을 통하여 좀 더 빨리 찾게 됩니다.

사용자 삽입 이미지

저 같은 경우는 간단한 예제 파일이기 때문에 마우스 스크롤을 통하여 IAT를 찾았고, 사용되는 함수가 Kernel32.dll의 CreateFileA, GetFileSize, ExitProcess와 USER32.dll의 MessageBoxA 라는 API 함수가 사용된 것을 확인할 수 있었습니다.
403054의 주소값이 IAT라는 것을 알았기 때문에 Dump Window에서 G to Expression(Ctrl + G키)하여 403052을 입력합니다. 그 후 보기 쉽게 하기 위하여 Dump Window에서 오른쪽 버튼을 누르고 Long -> Address를 선택하면 위의 그림처럼 나타나게 됩니다. 원래대로 돌리려면 Hex -> Hex/Ascii를 선택하시면 됩니다.

Dump Window에서 마우스 스크롤을 통하여 다른 API 함수가 사용되는지 확인하여야 하며 중간에 IAT의 끝은 00000000입니다. 그렇다고 403060이 끝은 아닙니다. 위의 그림처럼 IAT 사이사이에 NULL로 채워진 경우에는 자동화 툴을 사용하다보면 다음에 나오는 IAT(위의 그림에서는 MessageBoxA)까지 인식하지 못하는 경우가 발생할 수 있습니다. 그러므로 자동화 툴보다는 직접 확인하는 것이 좋습니다.

위의 결과로 00403054에서 00403067까지가 IAT라는 것을 알았고, 사이즈를 계산하여 보면 00403067 - 00403054 = 13이 나오게 되며 주소값의 경우엔 Hex(16진수) 값이며 10진수 값이 아닙니다.

IAT의 시작주소가 3054라는 것과 Size가 13이라는 것을 알았으므로 IAT Rebuild 툴을 사용하여 수정하여야 합니다. 아직 디버거를 종료하면 안됩니다.

사용자 삽입 이미지

ImportREC(Import REConstructor) 툴을 사용하여 아직 디버깅중인 파일을 Attach시켜주고, OEP인 1000값을 입력하고 IAT AutoSearch 버튼을 누르면 그림처럼 IAT의 RVA(Relative Virtual Address)와 Size를  자동으로 계산하여 주고 Get Imports 버튼을 누르면 해당 위치에서 찾은 API 함수와 dll 파일을 결과물로 나타내어 줍니다.

그러나 우리가 찾은 IAT의 시작 주소는 3054이고 사이즈는 13이라는 것과 차이가 있습니다.

사용자 삽입 이미지

위 그림을 통하여 다시 확인하여 보면 자동으로 찾았을때는 노란색 범위만큼을 뜻하고, 우리가 실제 찾은 사이즈는 연두색 부분만큼입니다. 실제로 필요한 사이즈는 연두색 부분이지만, 노란색 부분만큼 사용한다고 하여도 NULL로된 부분이기 때문에 프로그램 실행에는 아무 문제가 없습니다.

하지만, 자동화 툴에 사용하기 이전에 제대로 원리를 알고 사용하길 바랍니다.

사용자 삽입 이미지

이제 IAT Rebuilding을 하기 위해 Fix Dump 버튼을 누르면 파일 열기 창이 나오고 미리 Dump 해놓은 unpackme_05_dump.exe 파일을 선택하고 열기를 누르면 원본 파일 경로에 unpackme_05_dump_.exe 라는 파일이 생기게 됩니다.

사용자 삽입 이미지

확인하여 보면 Packing되었던 파일보다 Unpacking된 파일의 용량이 더 증가한 것을 볼 수 있습니다. 이는 UPX Packer는 압축을 주 목적으로 하는 Packer이기 때문입니다. 또한 Unpacking을 하게 되면 메모리의 기본 단위(Page)의 사이즈와 파일의 기본 단위(Block)의 사이즈가 차이를 보여 용량이 더 증가하게 됩니다.

Unpacking된 파일을 실행하여 보면 Packing되었던 파일처럼 정상적으로 실행이 됩니다. 그러나 운영체제가 바뀌게 되면(XP에서 Vista, 또는 XP에서 Win98와 같은 형식의 다른 OS인 경우) 정상적으로 실행이 되지 않을 수 있습니다.

이올린에 북마크하기
2009/02/17 00:23 2009/02/17 00:23

vmware에서 Kernel Debugging 하기

Posted at 2009/01/31 21:45 // in RCE // by 엔신
1.vmware에서 Serial Port 추가.
  use named pipe 선택
  \\.\pipe\com_1 입력

2.c드라이브에 boot.ini 파일에 추가
   multi(0)disk(0)rdisk(0)partition(1)\WINNT="Microsoft Windows 2000 Server Debug" /fastdetect /debugport=COM1 /baudrate=115200

3.부팅시에 해당 모드 선택하고 windbg에서 Kernel Debug 모드 선택하고 Baud Rate:115200, Port:com1 입력하면 연결

이올린에 북마크하기
2009/01/31 21:45 2009/01/31 21:45