한국어

tc_backup

원본 영문 레퍼런스 파일
Windows Anti-Debug Reference.pdf
아래 내용의 pdf 파일
windows_anti-debug_reference_darkhi.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

barosd123

2009.12.20
19:41:25
(*.186.38.194)
이런 좋은 글에 댓글 하나 없다니.. 원래 영문 문서이긴 해도 잘 보겠습니다.~

barosd123

2009.12.20
19:49:44
(*.186.38.194)

"비밀글입니다."

:

엔신

2009.12.20
20:27:54
(*.131.117.149)
상관없습니다 ^^

행인

2010.03.16
17:21:43
(*.183.45.63)
감사합니다. ^^ 잘받아갈게요
영어못해서 번역본 찾고있었는데 번역은 아니더라도
좋습니다. 잘볼게요

엔신

2010.03.16
23:08:31
(*.176.43.83)
네 도움 되셨으면 좋겠습니다. ㅎㅎ

pyoung

2010.07.29
15:34:54
(*.230.65.160)
감사합니다 공부에 많은 도움이 되었습니다.

엔신

2010.09.10
14:26:27
(*.102.117.181)
새로운 자료도 많이 올려야 되는데
요즘은 공부했던 것도 다 잊어먹고 있네요 ㅎㅎ
List of Articles
번호 제목 글쓴이 날짜 조회 수sort
260 [VBA] 엑셀 시트명 가져오기 엔신 2013-01-22 132706
259 Security Nessus 설치, 사용법 file 엔신 2008-12-11 93609
258 DBMS MySQL 설치/사용시 나는 에러 유형별 대처방법 엔신 2008-02-24 63588
257 CCNA 14.[EIGRP] file 엔신 2008-05-26 56002
256 [APC3.0] AhnLab Policy Center 3.0 관련 모듈 목록 엔신 2013-03-21 53313
255 [VBA] 파워포인트 그림 리사이즈, 위치 고정 엔신 2013-02-21 51700
254 freeware hex editor 엔신 2013-01-22 51205
253 Security base64 code 표 file 엔신 2009-01-20 44585
252 Programming COleDateTime 클래스 엔신 2009-11-23 43264
251 CCNA ACL(Access List) 설정하기 file 엔신 2008-05-28 42592
250 Linux 삭제 파일 복구하기 debugfs 엔신 2008-02-23 41813
249 WideCap을 통한 Proxy 이용 file 엔신 2013-04-20 39810
248 Windows 64비트 운영체제에서 아크로뱃 설치시 adobepdf.dll 파일이 없다고 할 때 [5] file 엔신 2007-07-25 38418
247 RCE [MUP] UPX & Stolen Bytes [2] file 엔신 2009-02-17 37482
246 CCNA Frame-relay(프레임릴레이) multipoint 방식 풀메쉬 토플로지 구현 file 엔신 2008-06-09 36781
245 Security SSH Port Forwarding(SSH Tunneling) [2] file 엔신 2009-05-29 36557
244 Linux configure: error: No curses/termcap library found 엔신 2008-02-24 35375
243 CCNA 서브넷팅 : 가장효율적으로 IP를 할당할수 있는 방법!!(VLSM) 엔신 2008-05-12 33979
242 Windows VMware VMwareDnD 폴더 엔신 2009-03-07 33881
» RCE Windows Anti-Debug Reference [7] file 엔신 2009-06-10 33612