'NtQueryInformationProcess'에 해당되는 글 1건

  1. 2011.05.10 [Tip and Tech] Anti-debugging Techniques (2)

이 글은 Symantec 사의 홈페이지에 게시된 article을 바탕으로 정리한 내용으로 원문은 아래 링크를 참고하기 바랍니다.

http://www.symantec.com/connect/articles/windows-anti-debug-reference

Anti-debugging이란 프로그램이 정상적으로 실행된 것이 아니라 디버거에 의해 실행되고 있음을 감지하는 기술을 말하며 보통 리버싱을 통한 분석 과정을 방해하거나 상용의 패커나 프로텍터 등에서 사용된다.


○ 메모리 값을 이용한 Anti-debugging

① kernel32!IsDebuggerPresent API 이용

kernel32!IsDebuggerPresent API는 해당 프로세스가 디버깅을 당하고 있으면 1을 리턴하며 그렇지 않을 경우 0을 리턴한다. 간단히 해당 API 호출을 통해 디버깅 유무를 감지할 수 있지만 그만큼 회피도 쉬우며 대부분의 디버거들은 쉽게 회피가 가능하다. 참고로 IsDebuggerPresent는 PEB 구조체의 BeingDebugged 값을 참조하여 리턴값을 결정한다.

- Example

 call IsDebuggerPresent  // IsDebuggerPresent call
 test eax, eax                // return값 비교
 jne @DebuggerDetected // return값에 따라 분

② PEB의 IsDebugged값 참조
IsDebuggerPresent 참조하는 값과 동일한 값으로 API 호출을 통해서가 아닌 직접 PEB 구조체의 +2바이트에 존재하는 BeingDebugged  접근하여 해당 플래그 값을 읽어와 현재 디버거에 의해 실행중인지 검사한다.(디버거에 의해 실행 중일때 값이 설정된다.)
- Example

 mov eax, fs:[30h]           // PEB 구조체에 접근
 mov eax, byte [eax+2]    // BeingDebugged  값을 eax에 저장
 test eax, eax                  // BeingDebugged  값 검사
 jne @DebuggerDetected  // 검사 결과에 따라 분기

③ PEB의 NtGlobalFlags값 참조
NtGlobalFlags는 PEB+0x68에 위치한 값으로 디버거에 의해 실행될 때와 그렇지 않을 경우 다른 값을 갖는다.  NtGlobalFlags에 설정가능한 플래그의 값은 아래 링크를 참조하기 바란다. http://msdn.microsoft.com/en-us/library/ff549596%28VS.85%29.aspx

디버거가 겂을 경우 NtGlobalFlags 값은 0으로 설정되며, 디버거에 의해 실행시 0x70으로 설정된다. 즉, FLG_HEAP_ENABLE_TAIL_CHECK, FLG_HEAP_ENABLE_FREE_CHECK and FLG_HEAP_VALIDATE_PARAMETERS 세 개의 플래그가 설정되는 것이다.
- Example

 mov eax, fs:[30h]          // PEB의 주소를 eax에 저장
 mov eax, [eax+68h]       // NtGlobalFlags 값을 eax에 저장
  and eax, 0x70               // NtGlobalFlags 값이 0x70인지 비교
  test eax, eax               
  jne @DebuggerDetected  // 비교 결과에 따라 분기

④ Heap flags 참조
PEB 구조체의 +0x18에 위치한 ProcessHeap 포인터를 참조하여 ProcessHeap 구조체의 Flags와 ForceFlags 두 플래그를 참조하여 디버깅을 탐지할 수 있다. 아래 그림과 같이 Flags는 0x0c에 ForceFlags는 0x010에 위치한다. Flags 플래그는 디버깅 중일 경우 0x50000062으로 설정되며 그렇지 않을 경우 0x2로 설정되며, ForceFlags의 경우 디버깅 중일 경우 0x40000060으로 설정되며 그렇지 않을 경우 0으로 설정된다. 각 플래그 값은 다음과 같다.

- Flags 플래그

HEAP_GROWABLE (2)
HEAP_TAIL_CHECKING_ENABLED (0x20)

HEAP_FREE_CHECKING_ENABLED (0x40)

HEAP_SKIP_VALIDATION_CHECKS (0x10000000)

HEAP_VALIDATE_PARAMETERS_ENABLED(0x40000000)

- ForceFlags

HEAP_TAIL_CHECKING_ENABLED (0x20)

HEAP_FREE_CHECKING_ENABLED (0x40)

HEAP_VALIDATE_PARAMETERS_ENABLED(0x40000000)

- Example 1 (Flags 플래그 값을 검사)

mov eax, fs:[30h]      // PEB의 주소를 eax에 저장
mov eax, [eax+18h]  // ProcessHeap 주소를 eax에 저장
mov eax, [eax+0ch]  // Flags 플래그 값을 eax에 저장
dec eax
dec eax
jne being_debugged  // eax의 값이 2인지 검사

- Example 2 (ForceFlags 플래그 값을 검사)

mov eax, fs:[30h]     // PEB의 주소를 eax에 저장
mov eax, [eax+18h]  // ProcessHeap 주소를 eax에 저장
cmp [eax+10h], 0     // ForceFlags 플래그를 0과 비교하여 분기
jne being_debugged

출처 : 스토리가 있는 리버싱(http://sharku.egloos.com/302589)

⑤ Vista anti-debug

비스타의 경우 프로세스가 디버깅 중이라면 0xBFC에 위치한 메인 쓰레드의 TEB가 system dll을 참조하는 unicode string 값을 가르키는 포인터를 갖고 있지만, 프로세스가 디버깅중이지 않을 경우 포인터는 NULL 값을 갖는다.

- Example

 call GetVersion
 cmp al, 6                    // 윈도우 버젼이 Vista인지 검사(6.0일시에 Vista)
 jne @NotVista
 push offset _seh
 push dword fs:[0]
 mov fs:[0], esp
 mov eax, fs:[18h]       // TEB의 주소를 eax에 저장
 add eax, 0BFCh          // TEB + 0xBFC로 오프셋 이동
 mov ebx, [eax]          // unicode string을 가르키는 포인터
 test ebx, ebx              // null인지 검사
 je @DebuggerNotFound
 sub ebx, eax ; the unicode string follows the
 sub ebx, 4 ; pointer
 jne @DebuggerNotFound ;debugger detected if it reaches this point

 

○ 시스템의 차이점을 비교(?)

① NtQueryInformationProcess

ntdll!NtQueryInformationProcess 함수는 ZwQueryInformationProcess system call의 wrapper 함수이다. 해당 함수의 syntax는 다음과 같다.

 NTSTATUS WINAPI NtQueryInformationProcess(

__in HANDLE ProcessHandle,

__in PROCESSINFOCLASS ProcessInformationClass,

__out PVOID ProcessInformation,

__in ULONG ProcessInformationLength,

__out_opt PULONG ReturnLength);

ProcessInformationClass 값을 7(ProcessDebugPort)로 설정한 뒤 해당 함수 호출시, 프로세스가 디버깅 중이라면 시스템은 ProcessInformation 값을 -1로 설정한다. 프로세스가 디버깅 중일 경우 NtQueryInformationProcess 파라미터 중 ProcessDebugPort 값을 7로 설정하게 된다. ProcessInformationClass 함수에 대한 자세한 설명은 MSDN을 참고하기 바란다.

이 anti-debugging을 회피하는 방법은 프로그램을 모니터링 하다가 ProcessInformation값을 조작(-1이 리턴될 경우 0으로 변경)하여 회피하거나, ZwNtQueryInformationProcess 함수를 후킹하는 것이다. NtQueryInformationProcess를 사용하는 안티 디버깅 기법의 예로 CheckRemoteDebuggerPresent, UnhandledExceptionFilter가 있다.

- Example

 push 0

push 4

push offset isdebugged

push 7 // ProcessInformationClass 값을 7로 설정

push -1

call NtQueryInformationProcess // NtQueryInformationProcess 호출후 리턴값 검사

test eax, eax

jne @ExitError

② kernel32!CheckRemoteDebuggerPresent
CheckRemoteDebuggerPresent 함수는 아래와 같은 syntax를 갖는다.

 BOOL WINAPI CheckRemoteDebuggerPresent(

__in HANDLE hProcess,

__inout PBOOL pbDebuggerPresent);

해당 함수는 핸들로 넘긴 프로세스가 디버깅 중일 경우 "1"을 리턴한다. 내부적으로는 위에서 설명한 NtQueryInformationProcess 함수를 이용하여 유무를 점검한다.

- Example

 push offset isdebugged

push -1

call CheckRemoteDebuggerPresent //

test eax, eax

jne @DebuggerDetected

③ UnhandledExceptionFilter
윈도우 SP2 이상, 윈도우 2003, 윈도우 비스타 등의 OS에서 exception이 발생할 경우 OS는 다음과 같은 과정으로 해당 exception을 처리한다.

1. 프로세스마다 존재하는 VEH(Vectored Exception Handler)에게 제어권을 넘긴다.

2. exception이 처리되지 않을 경우 FS[0]가 가르키는 쓰레드마다 존재하는 top SEH handler로 제어권을 넘긴다. SEH는 chain 형식으로 구성되어 있으며, 이전 chain에서 처리되지 않을 경우 다음으로 넘긴다.

3. 그래도 처리가 되지 않을 경우 final SEH handler가 kernel32!UnhandledExceptionFilter를 호출한다. UnhandledExceptionFilter 함수가 프로세스가 디버깅 중인지 아닌지를 검사하는 매개체가 된다.

4. 프로세스가 디버깅 중일 경우 종료되며, 그렇지 않을 경우 사용자가 정의한 함수(user-defined filter function)가 호출된다.

- Example

 push @not_debugged

call SetUnhandledExceptionFilter

xor eax, eax

mov eax, dword [eax] // 0값을 참조하게 되므로 exception 발생, 디버깅 중이라면 프로그램 종료

...

@not_debugged:

process the exception

continue the execution

④ NtSetInformationThread

ntdll!NtSetInformationThread 함수는 ZwSetInformationThread system call의 wrapper 함수이며, syntax는 다음과 같다.

 NTSTATUS ZwSetInformationThread(

__in HANDLE ThreadHandle,

__in THREADINFOCLASS ThreadInformationClass,

__in PVOID ThreadInformation,

__in ULONG ThreadInformationLength);

ThreadInformationClass를 0x11(ThreadHideFromDebugger)로 설정후 호출시 해당 쓰레드는 디버거로 부터 떨어지게(detached)된다. ZwQueryInformationProcess 함수와 마찬가지로 회피를 위해서는 ZwSetInformationThread 호출전에 해당 파라미터를 변경하거나, 커널 드라이버를 이용하여 후킹을 해야한다.

- Example

 push 0

push 0

push 11h // ThreadHideFromDebugger 값을 0x11로 설정

push -2

call NtSetInformationThread // 디버깅 중일 겨우 쓰레드는 떨어짐

⑤ kernel32!CloseHandle and NtClose

최종적으로 ZwClose를 호출하는 API인 CloseHandle등을 이용한 디버깅 탐지 기법으로 프로세스가 디버깅 중이라면 잘못된 핸들을 인수로 준 뒤 ZwClose 호출시 STATUS_INVALID_HANDLE (0xC0000008)exception을 생성한다.

- Example

push offset @not_debugged
push dword fs:[0]
mov fs:[0], esp
push 1234h ; // invalid handle
call CloseHandle // CloseHandle 호출, 프로세스가 디버깅 중일시 실패

;...
@not_debugged:
;...

⑥ Self-debugging

프로세스는 자기 자신을 스스로 디버깅 함으로서 디버깅 여부를 감지할 수 있다. 예를 들어 새로운 프로세스를 생성한 뒤에 부모 프로세스에서 kernel32!DebugActiveProcess(pid)를 호출한다.

DebugActiveProcess가 호출되어 디버깅 기능이 활성화 되면 자식 프로세스에는 ntdll!DbgUiDebugActiveProcess가 호출되게 되고 이것은 결국 ZwDebugActiveProcess 함수를 syscall 하게 된다. 만약 프로세스가 디버깅중이라면 ZwDebugActiveProcess 함수는 실패하게 된다. toolhelp32 APIs를 이용하여 부모 프로세스의 PID( PROCESSENTRY32 구조체의 th32ParentProcessID 필드)를 가져올 수 있으며, 해당 값을 참고하여 디버깅 중인 부모 프로세스를 종료하게 된다.

⑦ Kernel-mode timers

kernel32!QueryPerformanceCounter는 매우 효율적인 anti-debug 방법이다. 해당 함수는 ntdll!NtQueryPerformanceCounter 호출하며, ZwQueryPerformanceCounter syscall을 wrap하고 있다. QueryPerformanceCounter를 이용하여 성능 카운터 값을 읽어와 일정 시간보다 클 경우 현재 프로세스가 디버깅 중인 것으로 판단할 수 있다. 디버깅 중이면 Step-by-Step 방식으로 진행할 것이므로 아무래도 성능 카운터의 값이 클 것이다.

⑧ User-mode timers

kernel32!GetTickCount 함수는 시스템이 시작된 이후의 시간을 millisecond 단위로 반환한다. 정상적으로 프로그램이 실행될 경우와 디버깅 중일 경우에는 시간차가 있을 것이므로 그점을 이용하여 디버깅 중인지 판단한다.

⑨ kernel32!OutputDebugStringA

매우 오래된 anti-debug 방법으로 유효한 ASCII 스트링을 매개변수로 하여 OutputDebugStringA를 호출하였을 경우 프로그램이 디버깅 중이라면 파라미터로 전달한 스트링의 주소값을 반환하게 되며, 정상적으로 실행중일시에는 1을 반환하게 된다.

- Example

xor eax, eax
push offset szHello // 파라미터로 ASCII string 전달

call OutputDebugStringA // OutputDebugStringA 호출
cmp eax, 1 // 결과값이 1인지 비교
jne @DebuggerDetected

⑩ Ctrl- C

콘솔 프로그램이 디버깅 중이라면 Ctrl-C 시그널은 EXECEPTION_CTL_C exception을 발생시킨다. 반면에 정상적으로 프로그램이 실행중이라면 바로 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

① Rouge Int3

고전적이며 대부분의 디버거에서 속지않는 anti-debug 방법으로, 정상적인 명령어 구문속에 "INT3" opcode를 삽입하는 방법이다. 프로그램이 디버깅중이 아니라면 INT3 명령 실행시 protection(보호), execution(실행), continue(재개)는 exception handler가 제어하게 될 것이다. INT3 명령은 디버거에서 software breakpoint 설정시 사용하는 명령이다. 따라서 INT3을 명령 중간에 삽입하여 디버거에게 software breakpoint가 설정된 것으로 속이는 방법이다. (이후의 설명에 대한 부분과 예제는 잘 이해가 가지 않는다. 내용을 아시는 분은 가르침 부탁드립니다.)

- Example

push offset @handler

push dword fs:[0]

mov fs:[0], esp

;...

db 0CCh

;if fall here, debugged

;...

@handler:

;continue execution

;...

② "Ice Breakpoint

소위 "Ice Breakpoint"라고 불리는 방법으로 intel의 문서화되지 않은 opcode은 0xF1을 이용하는 방법이다. 0xF1은 추적 프로그램(tracing program)을 감지할 때 사용한다.

0xF1 실행시 SINGLE_STEP exception을 발생시킨다. 따라서 이미 프로그램이 추적(trace) 중이라면(디버깅 중이라면) 디버거는 Flag Register의 SingleStep bit를 설정하고 실행되는 정상적인 실행이라 판단한다. (참고 : Trap Flag는 software exception을 발생시키는 flag로 Trap flag가 설정되면 exception이 발생하고 제어권이 SEH로 넘어간다.)

관련된 exception handler는 실행되지 않을 것이며, 프로그램은 기대한 것과 다르게 실행될 것이다.

해당 anti-debug를 회피하는 방법은 간단하다. single-stepping 명령을 건너뛰고 실행하게 한다.

- Example

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

③ Interrupt 2Dh

2Dh 인터럽트 실행시 프로그램이 디버깅 중이지 않을 경우 breakpoint exception을 발생시킨다. 프로그램이 디버깅 중이고 trace flag가 set되지 않은 상태로 실행될시에 exception이 발생하지 않으며 정상적으로 실행이 된다. 프로그램이 디버깅 중이며 trace flag가 set되어 있을 경우에는 바로 다음 명령은 실행되지 않는다.

- Example

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

④ Timestamp counters

매우 정밀한 카운터로 컴퓨터가 실행된 이후로 현재까지의 CPU cycle의 횟수를 "RDTSC"(Read Time Stamp Counter) 명령을 통해 알 수 있다. 고전적인 anti-debug 방법들은 프로그램의 time deltas를 측정하는 방법을 사용하며 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
@debugged:
...
@not_debugged:
...
handler:
mov ecx, [esp+0Ch]
add dword ptr [ecx+0B8h], 2 ;skip div
xor eax, eax
ret

⑤ Popf and trap flag

Flags Register에 존재하는 trap flag는 프로그램의 추적을 제어한다. 해당 flag가 set 되어 있을 경우 해당 명령 실행시 SINGLE_STEP exception을 발생시킨다. trap flag는 추적을 방해하는 용도로 조작될 수 있다.

- Example

pushf // 모든 flag를 push한다.
mov dword [esp], 0x100 // trap flag 값을 설정
popf // 스택에 저장했던 flag 값들을 다시 pop

프로그램이 추적(디버깅) 중이라면 위의 예제는 실제로 flag register에 영향을 미치지 않을 것이고 디버거는 exception을 발생시킬 것이다. 만약 디버깅중이 아니라면 exception handler는 실행되지 않을 것이다.

⑥ Stack Segment register

매우 오래된 anti-debug 방법으로 MarCrypt라는 패커에서 사용되었다. 이 방법은 다음과 같은 명령 순서를 통해 디버깅 여부를 탐지한다.

push ss
pop ss
pushf
nop

디버거를 통해 한 단계씩(step-by-step) 명령 실행시에 pop ss 명령을 실행하게 되면 pushf는 건너띄고 nop로 넘어가게 된다. 이런 특징을 이용하여 디버깅 여부를 탐지하게 되며 MarCrypt는 다음과 같은 방법을 사용하고 있다.

- Example

push ss

pop ss //step-by-step으로 실행시 바로 뒤의 pushf에서 멈추지 않고 pop eax에서 멈춤

pushf

pop eax // TF 값을 eax에 저장(?)

and eax, 0x100

or eax, eax

jnz @debugged // TF 값이 설정되어 있을시에 프로그램 종료

⑦ Debug registers manipulation

Debug register(DR0 ~ DR7)은 하드웨어 브레이크 포인트를 설정할 때 사용된다. debug register가 설정되어 있을 경우 디버깅 중 인것으로 판단할 수 있으며, 디버깅 방해를 위해 debug register 값을 해제해 버리거나 특정 값으로 설정 후 추후에 확인을 통해 현재 디버깅 상태인지 검사할 수 있다. tElock 같은 패커는 debug register를 통해 디버깅 중인지 감지 및 방해한다.

사용자 모드에서 "mov drx, ..." 명령을 통해 debug register를 설정할 수 없다. 다른 방법으로는

- exception 발생시 thread context가 변경될 것이고(exception 발생시의 CPU register를 갖고 있다.), 새로운 context를 가지고 실행을 재개할 것이다.

- 다른 방법은 NtGetContextThread, NtSetContextThread syscall(kernel32의 GetThreadContext, SetThreadContext)을 이용하는 것이다.

push offset handler
push dword ptr fs:[0]
mov fs:[0],esp
xor eax, eax
div eax ; // 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 // DR0 register 초기화
mov dword ptr [ecx+08h], 0 // DR1 register 초기화
mov dword ptr [ecx+0Ch], 0 // DR2 register 초기화
mov dword ptr [ecx+10h], 0 // DR3 register 초기화

mov dword ptr [ecx+14h], 0 // DR6 register 초기화
mov dword ptr [ecx+18h], 0 // DR7 register 초기화
xor eax, eax
ret

⑧ Context modification

debug register 변경과 같이 context 변경은 프로그램의 실행 흐름을 변경할 수 있으며, 디버깅을 방해할 수 있다.

NtContinue는 현재 쓰레드에 새로운 context를 로드할 수 있다.(예를 들어 NtContinue는 exception handler manager로 사용될 수 있다.)


○ Uncategorized anti-debug

① TLS-callback

최근 몇년전만 해도 잘 알려지지 않은 anti-debug 방법으로, 이 방법은 PE loader에게 프로그램의 첫번째 엔트리 포인트를 Thread Local Storage 엔트리(PE구조에서 OptionalHeader에 존재하는 IMAGE_DATA_DIRECTORY 구조체의 10번째 항목)를 가르키게 한다. 그렇게 함으로써 프로그램의 EP(entry-point)가 먼저 실행되는 것이 아니라 TLS entry가 먼저 실행되며 은밀하게 anti-debug를 수행할 수 있다.

이 방법은 널리 사용되지는 않으며, 디버거의 plugins에 의해 TLS를 알지 못하더라도 쉽게 잡아낼 수 있다.

② CC scanning

패커에 의해서 주로 사용되는 CC-scanning loop는 디버거에 의해서 설정된 software breakpoint를 탐지하는데 사용된다. 이런 탐지 기법 회피를 위해서는 hardware breakpoint를 같이 사용하거나, custom type의 software breakpoint 를 사용하는 것이다. CLI(0xFA)는 고전적인 방법은 INT3 opcode를 대체할 좋은 방법이다. 단, CLI는 ring 3에서 실행시 1 byte의 공간만 차지하게 되며 privileged instruction exception을 발생시킨다.

③ Entrypoint RVA set to 0

일부 패킹된 파일은 entry point RVA를 0으로 설정해 버린다. 그것은 프로그램은 "MZ ..." 부터 실행될 것이고 이것은 "dec ebx / pop edx" opcode를 의미한다.

이것은 anti-debug trick은 아니지만 entry-point에 breakpoint를 설정하고 싶을 경우에는 매우 성가실 수 있다.

당신이 정지된(suspended) 프로세스를 생성한 뒤에 RVA 0를 INT3로 설정한다면 "MZ.."의 'M' 값을 변경하게 된다. 이후 프로세스가 resume 될때에 ntdll에 의해 PE 포멧을 검사하게 될 것이고 위와 같은 경우 "INVALID_IMAGE_FORMAT" exception을 발생시킬 것이다.

 

○ 결 론

간단한 anti-debug trick 때문에 한참을 헤맨 적이 많아 자료를 찾던 도중에 symantec 홈페이지에 유용한 내용의 글이 있어 남기게 되었다. 물론 아직 모든 내용이 이해가 가지는 않는다. 이렇게 기록해 놓고 차후에 찾아보면서 해당 trick들을 하나씩 자세히 알아갈 날이 오겠지.

 


                                                                                                    +---------------------------------+
                                                                                                    | Infinite Flow..                              |
                                                                                                    | mail : reverseinsight@gmail.com    |
                                                                                                    | Blog : http://sinun.tistory.com       |
                                                                                                    | twitter : @unpacker                      |
                                                                                                    | CISSP                                        |
                                                                                                    +----------------------------------+

저작자 표시 비영리 변경 금지
신고
Posted by By. PHR34K