Q. This program can detect debuggers. Find out the name of the debugger detecting function the program uses.


그림 1

최초 프로그램을 실행해보면, 정상이라는 글이 일정 시간간격으로 출력되는 것을 볼 수있다.



 그러나, 올리디버거로(또는 다른 디버깅 툴) 실행시,

그림 2

이러한 메인 루틴을 지나게 되면


그림 3

디버깅 당하고 있음을 출력하는 문자열을 볼 수 있다.


메인 루틴(그림 2)을 살펴 보면 문제에서 요구하는 답을 직관적으로 (IsDebuggerPresent) 알 수 있으며, 일정 시간을 두면서 문자열을 출력하는 이유 또한 알 수 있다.(Sleep함수)

보이는 바와 같이 IsDebuggerPresent함수는 Kernerl32.dll에 속해 있고, 디버깅을 당하고 있을시 non-zero, 디버깅을 당하고 있지 않다면 zero를 리턴한다. 

MSDN참조 : http://msdn.microsoft.com/en-us/library/windows/desktop/ms680345(v=vs.85).aspx


문제에 대한 답은 찾았으나, 디버깅을 하면서도 정상 이라는 문자열을 출력해보고 싶었다.

가장 쉽게 처음 생각한 방법은 IsDebuggerPresent함수를 지나 후에 있는 

TEST eax,eax 를 mov eax, 0으로 바꾸어보는 것이였다. 리턴 값을 조정해주면 내부가 어떻든간에

쉽게 해결될꺼라고 생각했는데, 정말 멍청한 생각이였음.

test 명령어는 두 값을 비교하여 플래그를 수정한다.

TEST 명령어는 피연산자들을 AND연산하여 즉, 두 피연산자중 하나라도 0이면 ZF = 1로 셋팅된다.

따라서 test eax, eax라는 것은 eax가 0인지 아닌지를 판별하는 명령어를 의미하게 된다.

CMP명렁어와 다른점은 두 피연산자들의 뺄셈연산을하여 같은지 다른지를 비교하는 것으로 차이점이 있다.

test명령어 아래에 나타나는 JE명령어는 이 ZF가 1이면 점프분기하는 조건분기 명령어중 하나이다.

따라서 단순 mov eax, 0으로 바꾸게 된다면 ZF값을 수정해 주지 못함으로써 아래의 JE조건분기를 수행할 수 없어서

프로그램이 뻗게되었다.

다음 방법으로 생각 한 것은 IsDebuggerPresent함수 내부에서 리턴 하기 전에 리턴 값을 0으로 수정해보기로 하였다.

함수 내부로 진입해 점프를 두 번 수행 하고 나니


그림 4

이 세개의 명령어 수행후 리턴을 하게 되었다. 이때 eax값은 7efde000, 따라서 7efde002 메모리번지에 있는 값으로 셋팅된 후 리턴 하게 된다는 것을 알게 되었고, 이 메모리 번지로 찾아갔다.


그림 5

BeingDebugged = TRUE로 되어있는 것을 보면 이 값을 리턴되었을때 디버깅이 되고있음을 의미한다는 것을 확실히 확신할 수 있었지만, 이 값을 변경 할 수 없었으며,

반대의 의미를 리턴하는 값을 아래에서 찾을 수 없었다.(찾게 된다면 74f33785의 movzx문을 수정해볼 생각이였다.)

왜 변경 할 수 없었는지에 대한 의문.... 잘 모르겠다.

kernerl32.dll에 있기 때문이거나, dll에 있는 부분이여서 동적으로 할당 되는 부분을 수정할 수 없기 때문이 아닌가 하고 생각만 해봄...ㅠㅠ


결국 IsDebuggerPresent함수 내부를 수정하는 것도 실패하고, 남은 방법은 

메인 루틴에서 test명령어 이후에 나타나는 je명령어를 jmp로 수정하는 방법밖에 떠오르지 않아서, 수정해보았더니

디버깅 중에도 정상으로 출력하는 것을 볼 수 있었다. 



결국 돌아돌아서 별 거 아닌 패치를 했지만, IsDebuggerPresent함수 내부를 한번 들여다 본 것과

단순 레지스터들의 값들뿐만아니라 여러 플래그들도 같이 둘러봐야 된다는 교훈을 얻게된 것같다.





CPU Register란?

: CPU내부에 존재하는 다목적 공간

  RAM은 물리적 제약에 의해 비교적 저속이나,

  Register는 CPU와 한 몸이기 때문에 고속이다.

  ( Register >> Cache >> RAM >> HDD ) //컴구조 시간에 배운 기억이;;


레지스터의 종류는 많지만, 우선은 기본적으로 알아야할 레지스터부터 알아본다.

*추후 control, memory, debug Register들의 공부도 필요하게 될 것같다.


Basic Program execution Register  - General Purpose Registers ( 32bit * 8개 ) //범용레지스터

                                                 - Segment Registers ( 16bit * 6개 )

                                                 - Program Status Control Registers ( 32bit * 1개 )

                                                 - Instruction Pointer ( 32bit * 1개 ) 


먼저 General Purpose Registers에 대해 알아보면,

각각의 크기는 32bit ( 4 byte )이며, 보통의 용도는 상수/주소 등을 저장하는데 쓴다.

특정 어셈블리 명령어에서 특정레지스터를 조작하기도하며, 특정레지스터는 특수용도로 사용된다.


그림과 같이 범용레지스터는

EAX ( Accumulator Operands and result data )

EBX ( Pointer to data in the DS Segment )

ECX ( Counter for String and Loop Operations )

EDX ( I/O Pointer )

EBP ( Pointer to data on the Stack )

ESI ( Source Pointer for String Operations )

EDI ( Destination Pointer for string Operations )

ESP ( Stack Pointer ) 로 구성이 되어있다.

그림에서 반이 나누어진 것을 볼수 있는데,

현재 사용되고있는 32bit기준으로 레지스터가 확장되었기 때문이다.

예전에는 16bit를 사용 하였기때문에 레지스터의 용량 또한 16bit였고, 여기서

확장된(Extend)의미를 붙여 각 레지스터에 E가 붙게 되었다.

새로 레지스터를 만들지 않고 확장시킨이유는 예전과 호환을 위해서라고 한다.


각 범용레지스터의 사용 용도는

EAX ~ EBX레지스터들은 주로 산술연산 (add, sub, xor, or 등) 명령어에서 상수/변수 값의 저장용도로

많이 사용되며 특별히 EAX와 ECX는 다른 특정 용도가 있다.

eax -> 함수 리턴값에 사용             ecx-> 반복loop 카운트수 저장

ESP는 스택 메모리 주소를 저장하고 있어서, 스택의 PUSH, POP에 따라 값이 변한다.

EBP는 함수 호출시 그 순간의 ESP를 저장하여 리턴 직전에 ESP값을 되돌려서 Stack이 깨지지않게 하는 역할을 한다.

나중에 나오지만 이것을 Stack Frame 기법이라고 한다.

ESI와 EDI는 특정 명령어들과 함께 주로 메모리 복사에 사용된다.



출처 : 리버싱 핵심원리 (이승원 저)



같은 네트워크망에 있다면 아이피 형식은 ex) 192.168.0.x 이런식으로 비슷하기 마련이다.

보통은 1은 게이트웨이로 쓰이며 이후 255까지 많은 접속 컴퓨터및 통신기기가 있을 수 있다.

먼저 기본으로 자신의 ip는 ifconfig 명령어를 통해 ip address 및 게이트웨이주소 맥주소 등을 확인해 볼수 있다.

그리고 ping 명령어를 통해 해당 아이피주소에 통신기기의 응답을 받을 수 있다.

예를 들어 ping 192.168.0.2 ~ 192.168.0.255 까지 각 아이피 주소에 대해 접속한 기기를 검색해 볼수 있는데,

하나하나 다 하다보면 시간이 너무 오래걸릴 것이므로....

간단한 프로그래밍을 통해 스스로 핑을 확인해 볼 수 있다.


#include <stdio.h>

int main()

{

      int i = 0;

      char order[100];

      for( i = 0; i <= 255; i++)

     {

            sprintf( order, "ping -c 192.168.0.%d |grep from", i );

            system( order );

     }


     printf("Search is finished\n");

  

     return 0;

}


위의 예제를 보면 0부터 255까지 i가 증가하면서  ping 명령어를 찍어보고 있고, 255까지 서치가 끝나면 서치가 끝났다는

printf문을 끝으로 프로그램이 종료된다.

ping명령어의 옵션으로 -c를 붙인 것은 해당 아이피의 응답을 한 번만 듣겠다는 옵션이고,

|grep from은 from이 포함된 결과만 보게 해주는 구문이다. 왜 그렇게 했냐면

응답이 있는 아이피 어드레스만이 from이 포함된 출력을 주고,(응답이 없을 경우 from이 포함되어있지않은 결과가나옴)

우리가 얻고 싶은 아이피 어드레스는 응답이 있는 것이므로 쓸데 없는 결과를 삭제한다고 볼 수 있다.

참조 : 해커스쿨 (http://www.hackerschool.org/)

처음으로 블로그에 올리는 글이고, 아직 학부생이라 허접한 내용이지만 하나씩 하나씩 글쓰는 재미를 얻어가야겠다;;;



  1. 형이다 2013.10.08 23:12

    소스코드로도 핑을 날릴수 있었구나?ㅋㅋㅋㅋ
    도스창에 다가만 날리는줄 알았더니ㅋㅋㅋ

  2. JSBach Bach 2013.10.08 23:41 신고

    system 함수로 문자열 저장해서 넣으면 되더라구 :)

+ Recent posts