안티 디버깅(Anti-debugging, 디버거 탐지) – GetTickCount (시간차)

 

How to debugging detect?

프로그램 역분석, 즉 프로그램 디버깅 방법을 배웠다면, 해당 프로그램의 코드를 그대로 보는 것과 같아서, 숙달되면 프로그램의 모든 코드를 손쉽게 확인할 수 있다. 이를 통해 개발자가 원하지 않았던 방식으로 처리 흐름을 바꾸거나 데이터를 바꿔 치기 하는 등 악용할 수 있는 소지가 많다.

이를 방지하기 위해 개발된 기술이 바로 안티 디버깅으로, 말 그대로 디버깅 방지를 위한 기술들이라고 할 수 있다. 디버깅 자체를 막는 기술은 많이 개발되었으나, 이에 맞춰 안티-안티 디버깅 역시 함께 발전하면서 서로 뚫고 막고를 반복하고 있는 상황이다.

그리고 안티 디버깅 방법들은 대부분 이미 여러 포럼에서 공개해 놓은 상태이다. 아래는 시만텍에서 공개한 안티 디버깅 코드 참조용 내용이니 학습 전에 먼저 들러보기 바란다.

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

이외에도 많은 사이트에서 안티 디버깅과 관련된 참조 문서들을 많이 확인할 수 있는 만큼 안티 디버깅은 프로그램 보안 기술 중 기본 중 기본이라 할 수 있다(실제 대부분의 안티 디버깅 역시 방어 방법이 이미 나와 있다).

여기서는 안티 디버깅 중 유용한 몇 가지 방법에 대해 소개하여, 여러분이 안티 디버깅이라는 기술의 시각을 넓힐 수 있는 기본 지식을 다질 수 있도록 원리와 기법을 여러분들에게 소개하는 데 목적을 두고 진행하도록 하겠다.

디버거 탐지는 사용자가 프로그램을 분석하기 위해서 디버깅을 진행하는지에 대해 탐지하여 분석을 할 수 없도록 프로그램을 오동작하도록 하거나 중지, 함정에 빠뜨리는 방법이다.

이렇게 컴파일한 기계어인 프로그램의 보호가 필요한 이유는 3부에서 배웠듯이 역분석을 진행하면, 해당 프로그램의 동작 상황을 소설책을 읽어 내려가듯 분석하게 되면, 원하는 문장을 고쳐, 처리 흐름을 바꿔놓거나, 해당 프로그램의 목적과 반대로 동작하도록 수정할 수도 있다.

따라서 디버깅을 진행하는 프로그램에 대한 제재를 진행해야 하는데, 이를 실행하기 위해 프로그램 자체에 디버깅을 방지하는 보호 코드를 삽입하여야 한다. 이러한 안티 디버깅 코드들은 유명 개발 커뮤니티에도 다수 공개되어 있으므로, 전체적인 내용을 진행하는 것에는 큰 의미 없으므로, 여기서 에서는 몇 가지 개념 이해를 돕기에 유용한 분석을 진행해보도록 하겠다.

 

GetTickCount (시간차)

CPU는 매우 고속으로 명령들을 처리한다. 그에 반해 프로그램 디버깅을 진행하고 있다면, 각 명령어의 실행시간은 매우 느려지게 되고 심지어 브레이크 포인트를 설정으로 인해 실행시간을 멈출 수도 있다. 이러한 시간적 차이를 비교하여 프로그램이 디버깅 중이라는 것 감지하도록 만든 것으로 내부 함수인 GetTickCount를 이용하는 경우가 많다.

우리는 2부에서 퀀텀에 대해 배운 적이 있다. CPU의 단위인 Hz는 1초에 처리한 명령 개수다. 1Hz는 1개의 명령(어셈블리 한줄)을 처리하는데 1초가 걸린다는 것과 같다. 현재 우리가 사용하는 CPU들은 GHz로, 1초에 엄청나게 많은 명령을 처리할 수 있다. 따라서 GetTickCount를 통해서, 밀리 세컨드(milliseconds, ms) 단위로 측정을 진행해 프로그램이 명령 실행 간격이 1ms 이상 차이가 난다면, 디버깅 중임을 의심할 수 있다. 즉 퀀텀을 염두에 둔다고 해도 해당 처리 시간 이내 충분히 완료할 수 있기 때문에, 탐지 오류은 크지 않고, 코드 역시 간단한 효과적인 안티 디버깅 방법이라 할 수 있다.

gettickcount.cpp

#include
<stdio.h>#include
<windows.h>void main()

{

while(true)

{

Sleep(1000);

// 기준 값을 생성한다.

DWORD beginTime = GetTickCount();

// 생성한 기준값과 현재 값이 맞는지 확인한다 만약 브레이크포인트등을 설정하여 기존 시간과 다를 경우 임의의 주소에서 종료한다.

// begintime과 검사하는 코드 사이에 다른 안티 디버깅 코드나 실행코드, 정크 코드등을 추가 하면 더욱 효과적이다. 여기서는 정크 코드를 추가하였다. (3장 패킹에서 다룬다.)

__asm{PUSH ECX}

__asm{MOV ECX, 3}

__asm{PUSH EAX}

__asm{XOR EAX, EAX}

__asm{ADD EAX, 10}

__asm{XCHG EAX, ECX}

__asm{POP EAX}

__asm{OR EAX, ECX}

__asm{POP ECX}

// 초기 체크한 시간과 맞는지 검사한다.

if(GetTickCount() != beginTime)

{

printf(“Debugging Detected\n”);

// 임의의 주소에서 종료

exit( 0x1ED0BE05 );

}

else

{

printf(“Safe\n”);

}

}

return;

}

[예제] GetTickCount를 이용한 안티 디버깅 탐지

이 코드는 단독으로 사용하는 것보다 다른 안티 디버깅과 함께 사용하게 되면 더욱 효과적으로 사용될 수 있다. 위 코드를 컴파일하고 이 역시 Ollydbg를 이용하여 분석을 진행해보도록 하자.

앞서 진행한 main()과 같이 main 구분에 브레이크 포인트를 설정하자.


[그림] main()에 브레이크 포인트를 설정하자

이 후 프로그램 실행하면, main 함수 처리시 프로그램이 일시 정지하게 된다. 그 후 Step into(F7키)를 눌러 우리가 확인하고자 하는 코드를 확인하자.


[그림] Ollydbg로 확인한 정크 코드와 타임 확인 위치

그럼 GetTickCount 함수가 어떤 역할을 하는지 Win32 Programmers Reference를 이용해 확인해 보자.

다운로드 위치 http://www.phatcode.net/downloads.php?id=238&action=get&file=win32hlp.zip

이는 Ollydbg의 Help
Select/Open API Help File 메뉴를 이용해서도 사용할 수 있다.


[그림] API별 도움말을 확인할 수 있다

GetTickCount는 앞서 애기한 것과 같이 컴퓨터가 부팅된 이후부터 현재까지의 시간을 밀리 세컨드 단위로 표시해 준다. 이를 이용해 기존 시간과 현재 시간을 비교하여 디버깅 유무를 파악하게 된다. 그럼 해당 코드를 Ollydbg를 이용하여 확인해 보자.

…실행 초기 코드 생략

003613B8 3BF4 CMP ESI,ESP

003613BA E8 6DFDFFFF CALL GetTickC.0036112C

003613BF 8BF4 MOV ESI,ESP

003613C1 FF15 98813600 CALL DWORD PTR DS:[<&KERNEL32.GetTickCount>] ; kernel32.GetTickCount 기준 값을 생성

003613C7 3BF4 CMP ESI,ESP

003613C9 E8 5EFDFFFF CALL GetTickC.0036112C

003613CE 8945 F8 MOV DWORD PTR SS:[EBP-8],EAX

// 분석을 어렵게 만들기 위해 삽입한 정크 코드 일부이다.

003613D1 51 PUSH ECX

003613D2 B9 03000000 MOV ECX,3

003613D7 50 PUSH EAX

003613D8 33C0 XOR EAX,EAX

003613DA 83C0 0A ADD EAX,0A

003613DD 91 XCHG EAX,ECX

003613DE 58 POP EAX ; GetTickC.003619FF

003613DF 0BC1 OR EAX,ECX

003613E1 59 POP ECX ; GetTickC.003619FF

003613E2 8BF4 MOV ESI,ESP

003613E4 FF15 98813600 CALL DWORD PTR DS:[<&KERNEL32.GetTickCount>] ; kernel32.GetTickCount 비교값 생성

003613EA 3BF4 CMP ESI,ESP

003613EC E8 3BFDFFFF CALL GetTickC.0036112C 비교값과 기준값을 비교한다.

003613F1 3B45 F8 CMP EAX,DWORD PTR SS:[EBP-8]

003613F4 74 2D JE SHORT GetTickC.00361423 디버깅 유무를 판단하는 분기점

003613F6 8BF4 MOV ESI,ESP

003613F8 68 44573600 PUSH GetTickC.00365744 ; ASCII “Debugging Detected\n”

003613FD FF15 A8823600 CALL DWORD PTR DS:[<&MSVCR100D.printf>] ; MSVCR100.printf

00361403 83C4 04 ADD ESP,4

00361406 3BF4 CMP ESI,ESP

00361408 E8 1FFDFFFF CALL GetTickC.0036112C

0036140D 8BF4 MOV ESI,ESP

0036140F 68 05BED01E PUSH 1ED0BE05

00361414 FF15 AC823600 CALL DWORD PTR DS:[<&MSVCR100D.exit>] ; MSVCR100.exit

[실습] GetTickCount 메인 코드 분석 내용

이를 우회하는 방법도 간단히 만들 수 있다. 필자는 CMP 비교 구분인 EAX 레지스터와 [EBP-8] 주소의 값을 같은 값을 비교하도록 적용하여 처리 결과가 같은 값이 나오도록 변경하였다. 이외에도 안티 디버깅 탐지 코드를 전부 NOP 구분으로 수정하거나, 처리 결과를 무조건 0으로 반환하는 등, 많은 방법을 이용하여 안티 디버깅을 우회할 수 있다.


[그림] 같은 값을 비교하여 항상 같은 결과를 나오도록 한다

Facebook Comments

Leave A Reply

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.