본문 바로가기

High Level Technique/Reversing

Anti Debugging - Dynamic

Anti Debugging - Dynamic


Dynamic Anti Debugging은 프로그램의 코드를 트레이싱 하지 못하도록 지속적으로 방해하는 기법입니다.

내부 코드와 데이터를 리버싱으로부터 감추고 보호하는 것을 말합니다. PE Protector에서 많이 사용되고 원본 프로그램의 핵심 알고리즘을 보호하기 위해서 사용됩니다.

OEP로 가지 못하도록 방해합니다.





예외


안티 디버깅에서 예외를 이용하는 방법이 많이 나옵니다. 정상적으로 실행된 프로세스에서 예외가 발생하면 SEH 메커니즘에 의해 OS에서 예외를 받아서 프로세스에 등록된 SEH를 호출합니다.

하지만 디버깅을 하는 경우 프로세스에서 예외가 발생하면 디버거에서 예외를 담당합니다. 이러한 방식을 이용해서 정상 실행되는 경우와 디버깅 상태에서 실행되는 경우를 판별해서 서로 다른 동작을 할 수가 있습니다.



SEH


SEH  예외는 http://kblab.tistory.com/302 에서 확인 할 수 있습니다.


EXCEPTION_BREAKPOINT


가장 대표적인 예외입니다. BreakPoint 명령어를 이용해서 예외를 발생시키면 일반 실행인 경우 등록된 SEH가 자동 실행되지만 디버깅 실행의 경우 실행이 멈춰지고 디버거에게 넘어갑니다.

예외 처리기에 EIP 값을 변경하는 코드가 들어있는데 디버거 옵션에서 디버깅 중이라도 관련 예외를 OS에게 넘겨서 SEH가 자동 실행되로록 만들 수 있습니다.

하지만 내부에서 Static Anti Debugging 기법을 사용한다면 디버깅 여부를 쉽게 판별해 낼 수 있습니다. 또한 EIP가 어떻게 변조될지 모르기 때문에 들어가봐야 합니다.


핵심원리의 실습예제인 DynAD_SEH.exe를 가지고 분석해 보겠습니다.



401024 주소에서 INT 3 이라는 명령어가 들어있습니다. 이것을 이용한 안티 디버깅 방법입니다. 

흐름을 살펴보면 아래와 같습니다.


디버깅 중: INT3 예외발생 - SE Handler 접근 불가 - 다른 곳으로 JMP

정상실행 중: INT3 예외발생 - SE Handler 접근 - 정상코드로 이동



디버깅 중일 때와 정살 실행될 경우의 루틴입니다.



40102C의 ESP+C의 값은 CONTEXT *pContext 구조체 포인터를 나타냅니다. 예외가 발생한 thread의 context 구조체 입니다.

401036의 EAX+B8의 값은 pContext -> EIP 멤버를 가리킵니다. 따라서 401036 주소의 MOV 명령은 예외가 발생한 thread의 context 구조체의 EIP 값을 401040으로 변경하는 것입니다. 그 후에 예외 처리기는 ExceptionContinueExecution(0x0)을 리턴합니다.


우회 방법



프로세스에서 발생하는 INT 3 예외를 무시하고 자체 SEH에서 처리되도록 옵션을 위와 같이 설정해 주면 됩니다.










SetUnhandledExceptionFilter()


프로세스에 예외가 발생했을 때 만약 SEH에서 예외가 처리되지 않았거나 등록된 SEH가 없는 경우에는 kernel32.UnhandledExceptionFilter()가 호출됩니다.

이 함수 내부에서 Top Level Exception Filter 또는 Last Exception Filter 라고 불리는 시스템의 마지막 예외 처리기를 실행시킵니다.


보통 "작동이 중지되었습니다." 와 같은 에러 메시지를 띄운 후 프로세스를 종료시킵니다.


kernel32.UnhandledExceptionFilter() 내부에서 static 안티 디버깅 기법인 NtQueryInformationProcess(Process DebugPort)을 호출해서 디버깅 여부를 판별합니다.



Top Level Exceptino Filter(401000)과 kernel32.UnhandledExceptinoFilter()에 BreakPoint를 걸어둡니다.


401045 주소에서 401000 주소의 함수는 Top Level Exceptino filter로 등록됩니다. 



401050에서 EAX의 값을 0으로 초기화 시키고 401052 주소에서 0의 주소에 값을 넣을려고 하기 때문에 Invalid Memory Access Violation 예외가 발생합니다.


이 부분은 앞에서도 DebugPort 부분을 찾지 못했었습니다. 나중에 더 깊게 분석해 보고 수정하도록 하겠습니다.












NtSetInformationThread


NtSetInformationThread 기법은 thread를 디버거로 부터 감추는 방법이다. 




이 API는 thread의 우선순위를 설정하는 것이지만 Windows 2000부터 안티 디버깅만을 목적으로 업그레이드 되었습니다.


여기서 살펴볼 것은 ThreadInformationClass 값인데 enum으로 다음을 살펴보도록 하죠.





ThreadHideFromDebugger 라는 멤버가 존재하는데 이것을 인자로 넣으면 프로그래머가 만든 thread를 디버거로부터 감출 수 있습니다.





ntdll.dll 에서 NtSetInformationThread()의 포인터를 구한뒤에 NtSetInformationThread()의 첫 번재 인자로 Thread Handle을 전달하고 두 번째 인자로 0x11을 넘겨줍니다.

GetCurrentThread()로 현재 쓰레드를 넘겨줘도 되고 보호하고 싶은 쓰레드만 넘겨줘도 됩니다.


디버거 위에서 프로그램이 돌아가는 경우 NtSetInformationThread()에서 디버거에게 이벤트를 보내지 않도록 합니다.








Timing Check


코드를 한 줄씩 트레이싱 하다보면 프로그램이 실행되는데 많은 시간이 걸리게 됩니다. 바로 이 시간을 이용해서 디버깅 여부를 판단하는 기법입니다.



시간 측정 방법


시간 측정에는 Counter based method와 Time based method가 있습니다. CPU의 카운터를 이용하는 것과 실제 시스템 시간을 이용하는 방법입니다.


Counter base method

RDTSC, kernel32.QueryPerformanceCounter, kernel32.NtQueryPerformanceCounter


Time based method

timeGetTime()

_ftime()




RDTSC


실습예제 DynAD_RDTSC.exe를 분석해 보도록 하겠습니다.



우회 방법


1. 해당 부분을 트레이싱 하지 않고 적절한 곳에 BreakPoint를 걸어 Run하는 방법


2. RDTSC의 결과 값을 조작 EDX, EAX의 값을 바꾸거나 CMP 구문을 변경하면 된다.


3. 커널 드라이버를 이용하여 RDTSC 명령 무력화 (Olly Advanced PlugIn 이용)




















Trap Flag


EFLAGS 레지스터의 Index 8번 비트 입니다.


OllyDBG에서 T라고 적혀있는 곳이 TF 입니다.




Single Step


TF 값을 1로 하면 CPU는 Single Step 모드로 변경되고, CPU는 Single Step 모드에서 EXCEPTION_SINGLE_STEP 예외를 발생 시킵니다. 이후에 Trap Flag는 0으로 세팅됩니다.

EXCEPTION_SINGLE_STEP은 SEH 기법과 결합하여 디버거를 탐지하기 위한 안티 디버깅 기법에 사용됩니다.


실습예제인 DynAD_SingleStep.exe를 분석해 보도록 하겠습니다.



PUSHFD 명령어를 통해서 EFLAGS 레지스터 값을 스택에 저장합니다.

OR 연산을 통해서 TF를 1로 세팅합니다.

POPFD를 통해서 변경된 TF 값을 EFLAGS에 저장합니다.


일반실행의 경우 401036 주소를 실행시키지만 디버깅의 경우 40102E를 실행시킵니다. 그래서 EAX에 -1이 들어가게 되고 존재하지 않는 주소로 JMP를 하게 됩니다.



EFLAGS를 보면 216으로 되어있습니다. POPFD까지 실행시키면 아래와 같이 TF가 1로 세팅되고 EFLAGS의 값이 316으로 변경됩니다.



그리고 Step Over를 하게되면 아래와 같이 Single step event가 발생하게 됩니다.



이 상태가 된 후에 TF의 값을 확인하면 0으로 세팅되어 있고 EFLAGS의 값도 다시 216으로 변경됩니다.



우회 방법


EXCEPTION_SINGLE_STEP 예외를 프로세스에서 직접 처리하도록 OllyDBG에서 옵션을 설정합니다.



401036 주소에 BreakPoint를 걸어 두고 재실행 시키면 해당 주소에 멈추게 됩니다.



그 후 트레이싱을 진행하면 됩니다.





INT 2D


INT 2D는 커널 모드에서 작동하는 BreakPoint 예외를 방생시키는 명령어 이지만 유저 모드에서도 예외를 발생시킵니다.

디버거에서 실행할 경우에는 예외를 발생시키지 않고 넘어갑니다. 


INT 2D는 다음 명령어의 첫 바이트는 무시되고 그 다음 바이트 부터 새로운 명령어로 인식하여 실행됩니다. 


실습예제인 DynAD_INT2D.exe를 분석해 보도록 하겠습니다.


40101E 주소에 INT 2D가 존재합니다. 해당 주소에  BreakPoint를 걸고 이동해 보도록 하겠습니다.



401021 주소에 BreakPoint를 걸어두고 INT 2D 명령을 실행시킵니다.


그러면 401020의 NOP의 위치로 이동해야 하지만 1바이트가 무시되기 때문에 401021 주소로 이동하게 됩니다.



우회 방법


INT 2D 명령어까지 실행을 시킨 후 Single Step 예외를 무시하도록 OllyDBG에 옵션을 설정해 줍니다. 



40102A 주소로 이동해야 하기 때문에 이 주소에 BreakPoint를 걸어두고 TF의 값을 1로 세팅하고 진행해 보도록 하겠습니다.



처음 진행한것과는 다르게 401021 주소로 이동하지 않고 401020으로 이동했습니다. 예외가 발생하면 SEH로 이동해야 하는데 이동하지도 않았고, TF의 값도 1로 그대로 입니다.


이러한 이유는 INT 2D가 kernel  명령어이기 때문에 유저모드 디버거에서는 정상적인 명령으로 인식되지 않기 때문이빈다. 그래서 401020의 NOP에서 멈추게 된 것입니다.


이 상태에서 Step Over를 하게 되면 SEH로 이동하게 됩니다.



정상적으로 SEH 루틴에 들어온 것을 볼 수 있고 TF 값도 0으로 세팅된 것을 볼 수 있습니다.















0xCC Detection


일반적으로 디버깅을 할 때 Software BreakPoint를 걸어서 분석을 합니다. BreakPoint는 0xCC인데, 이 값을 정확히 발견할 수 있다면 디버깅 여부를 판별할 수 있습니다.

단순히 0xCC라는 값을 찾기만 한다면 문제가 발생합니다. Opcode로 사용될 수도 있지만 Displacement, immediate, 데이터, 주소 등으로 사용될 수 있기 때문입니다.




API BreakPoint


프로그램을 분석을 하다보면 특정 API에 BreakPoint를 걸어두고 분석을 많이 합니다. 안티 디버깅 기법 중에 API에 설치된 BreakPoint를 확인해서 현재 디버깅을 당하는지 판단하는 방법이 있습니다. API 시작 부분에  BreakPoint를 설치하므로 첫 바이트가 0xCC 인지 검사해서 디버깅 중인지 판단할 수 있습니다.



우회 방법


API에 BreakPoint를 설치할 때 코드 첫바이트를 피해서 BreakPoint를 설치합니다. 아니면 Hardware BreakPoint를 설치합니다.





Checksum 비교


Software BreakPoint를 확인하는 방법은 특정 코드 영역의 Checksum 값을 비교하는 것입니다.


어느 임의의 영역 A~B 사이의 checksum 값이 0x11111111 이라고 할 때 BreakPoint를 걸어두게 되면 0xCC로 변경되기 때문에 checksum 값도 바뀌게 됩니다.



DynAD_Checksum.exe를 분석해 보도록 하겠습니다.



위 구간이 checksum을 구하는 루틴입니다. 해당 위치에서 checksum을 구한 후 401035 주소에서 값을 비교하여 디버깅 중인지 판단합니다.



우회방법


1. 해당 영역에 BreakPoint를 걸지 않을 것.


2. CRC 비교 구문을 패치하는 것. 비교만 하고 JMP를 정상적인 위치로 조작한다면 값이 달라도 디버깅을 진행 할 수 있습니다.



'High Level Technique > Reversing' 카테고리의 다른 글

Service Debugging  (0) 2016.08.09
Advanced Anti Debugging  (0) 2016.08.09
Anti Debugging - Static  (0) 2016.08.07
Anti Debugging  (0) 2016.08.05
IA32 Instruction  (1) 2016.08.04