물마중

[리버스_0x02] 함께 공부하는 Reversing_3 본문

Reverse Engineering/Reversing 이론 설명

[리버스_0x02] 함께 공부하는 Reversing_3

zweistar2 2011. 1. 12. 14:13
작성자: Dear. Tom
편집자: 엔시스(sis@sis.pe.kr)
출   처: 보안인닷컴 팀 블로그[http://boanin.tistory.com]



1. Abstract 


 
이번 시간에 함께 분석할 프로그램은 1강에서 쓰인 ReverseMe.exe 파일이다.
어떤 일에든 그렇지만, 리버싱에서도 문제를 해결하기 위한 다양한 방법들이 존재할 수 있다.
그래서 이번에는 이전에 풀이한 방법보다 더 나은 방법으로 문제를 해결해 보겠다.
 
1번과 똑같은 프로그램이지만, 우리는 프로그램이 정확히 무엇을 원하는지는 파악하지 않았다.
(처음에는 그저 실패 메시지로 가는 구문을 주먹구구식으로 막았을 뿐이지, 진정한 올바른 방법은 아니다.)
이번 강의를 통해 그 부분을 집중적으로 파헤쳐볼 것이다.
 
 

2. tools and target
 
- Tool : OllyDbg
- Package : 이 문제의 해답인 Keyfile.data도 포함되어 있지만,
                 스스로 해결해 보는 것이 리버싱을 익히는데 가장 좋은 방법일 것이다.
 
지피지기면 백전백승이란 말도 있듯이, 목표물을 정확히 파악하는 것은 어떤 일 보다도 선행되어야 할 일이다.
하지만, 이번에 분석할 프로그램은 이전에 이미 다루어본 프로그램이므로 다시 언급하지는 않도록 하겠다.
프로그램의 전반적인 흐름이 기억이 나지 않는다면 이전 강의를 다시 한번 볼 것을 추천한다.

 

3. the use of plugins

 
"Analyse this!" 이 플러그인은 Code Segment에 Mark되지 않은 것도 분석할 수 있게 해준다.
Olly에서 플러그인을 적용하기 위해서는 압축을 푼 다음에 Plugin 폴더에 넣어주기만 하면 된다.
그리고 오른쪽 버튼을 클릭하면, 플러그인이 제대로 적용되었음을 확인 할 수 있다.
 


 
 

이 플러그인을 실행하고 나면, 프로그램 하단에 11개의 알려진 함수가 존재한다고 분석결과를 알려준다.

 
 
4. Solving the reverseMe

 
* 이 문제를 해결하기 위해서는 bit와 Byte, 그리고 16진수에 관한 지식이 있다면 좀 더 쉽게 접근하실 수 있을 것입니다.
 
 
우선 F8을 눌러가며, 코드를 한줄씩 실행해 본다.
 
 
The CreateFile function creates, opens, or truncates a file, pipe, communications resource, disk device, or console. It returns a handle that can be used to access the object. It can also open and return a handle to a directory.


1번 문제를 풀었던 기억을 되살려 보면, 이 부분에서 에러 메시지가 뜬다는 것을 알 수 있다.
프로그램에서는 Keyfile.dat라는 것을 열고자 한다.
 
문제 프로그램(reverseMe)과 같은 폴더에 메모장을 이용하여 "Keyfile.dat" 라는 이름의 빈파일을 생성해 본다.
지금은 아무런 내용도 입력하지 않겠지만, 아마도 나중에 이 파일이 다시 필요할 것이다.
 
계속 실행해보자.
파일을 정상적으로 찾았음을 알 수 있다.

 
 
 
만약 찾지 않았다면, EAX=FFFFFFFFh 이었을 것이다.
이로써, 첫번째 관문을 넘길 수 있을 것이다.
 
이제 프로그램에서는 "Keyfile.dat" 파일을 읽는다.    
 
 
 
ReadFile()에서는 40211A에 위치한 버퍼의 402173에서 부터 46h(70d) Byte를 읽는다.
 

The ReadFile function reads data from a file, starting at the position indicated by the file pointer. After the read operation has been completed, the file pointer is adjusted by the number of bytes actually read, unless the file handle is created with the overlapped attribute. If the file handle is created for overlapped input and output (I/O), the application must adjust the position of the file pointer after the read operation.

BOOL ReadFile(

    HANDLE  hFile, // handle of file to read
    LPVOID  lpBuffer, // address of buffer that receives data 
    DWORD  nNumberOfBytesToRead, // number of bytes to read
    LPDWORD  lpNumberOfBytesRead, // address of number of bytes read
    LPOVERLAPPED  lpOverlapped  // address of structure for data
   );

 
 
004010AE | TEST EAX, EAX
 
- TEST A, B
- A와 B를 &(ampersand) 연산을 수행하되, 그 결과를 저장하지는 않는다.
- 대신에 Flag만 변화시킨다.
- 연산후의 결과값이 0일 경우, ZF(Zero Flag)가 1로 설정되고, 그렇지 않다면 0으로 설정된다.
 
004010B0 | JNZ SHORT reverseM.004010B4
004010B0 | JMP SHORT reverseM.004010F7
 
파일을 성공적으로 열었다면 EAX에 0이 들어가므로, ZF가 1로 설정되므로 실패 메시지를 가볍게 뛰어 넘을 수 있다.
현재 EAX에는 파일을 성공적으로 열었다면 1, 그렇지 않다면 0으로 설정 되어 있을 것이다.
다행히 우리는 이전에 파일을 만들었기 때문에 첫번째 관문을 통과할 수 있다.
 
하지만, 파일 열기에는 성공했지만, 우리가 만든 파일은 빈 파일이다.
 
 
004010B4 | XOR EBX, EBX
004010B6 | XOR ESI, ESI
 
같은 레지스터끼리의 xor연산은 0으로 설정하기 위해서이다.
이 연산은 나중을 위해 초기화 하는 과정이다.
 
ex) EBX = 00110101
 
        00110101
   xor 00110101
   -------------
        00000000
 
004010B8 | CMP DWORLD PTR DS:[402173], 10 
 
10과 비교하는데, 10은 무엇이고, [402173]은 또 무엇일까?
 
  • 모든 값은 16진수를 의미한다.
  • 그러므로, 10h == 16d 이며,
  • [....]는 일종의 pointer를 의미하며, 주소값이 가지고 있는 실제 값을 말한다.
  • 그러므로, ReadFile로 읽어들인 숫자가 16개 보다 작으면 실패 메시지로 간다.
 
Jump 구문에서 Sign Flag를 간단히 0으로 만들어주고, 이 부분을 일단 넘겨보자.
 
 
 
 
루프 구문이 나온다. 다음줄을 읽어보고 어떤일이 발생할지 생각해보자. 
프로그램은 AL의 [EBX+40211A]에 있는 값으로 이동한다.
 
이 루틴이 시작될 때, ebx는 xor연산으로 0으로 설정되어있다.
 
 
 
 
ReadFile 함수에 의해 [40211A]에 있는 값, 00을 읽어온다.
 
AL과 0을 비교하여, 같으면 esi >= 8 비교구문으로 점프하는데, 이때 값이 8보다 작으면 실패 메시지로 간다.
=> 이로써 우리는 성공 메시지로 가기 위해서는 esi 값이 8보다 크거나 같아야 한다는 사실을 알 수 있다.
 
ESI 레지스터는 주로 반복문에서 counter를 제어하는데 쓰인다.
for(i=0; i<n; i++) 에서 변수 i의 역할을 한다고 생각하면 쉽게 이해할 수 있을 것이다.

그러나 esi는 시작할 때 xor연산으로 초기화 시켜놓았기 때문에 0이다.  
그래서 지금 이 구문을 건너 뛸 수는 없지만, je 구문에서 ZF를 0으로 set해주고 일단 넘어가보도록 하자.
 
 
 
 
00401B | CMP AL, 47
 
al(읽어온 첫 바이트)과 47h을 비교한다.
16진수인 47은 패키지에 포함된 아스키 코드표파일을 참조하거나, 크래커 툴을 이용하면 이 값이 무엇을 의미하는지 알 수 있다. 바로 G(대문자 gee ;-))이다.
 
 
004010CF | INC ESI
 
만약, 바이트가 의미하는 바가 G가 아니라면, INC ESI 구문을 그냥 뛰어 넘어 버리게 된다.
(앞에서 성공 메시지로 가려면 ESI가 8보다 크거나 같아야 하는데, 여기서 ESI값이 증가하지 않는다면 당연히 실패 메시지로 갈 수 밖에 없겠죠?)

그러므로 G가 모두 8개가 있어야 한다는 사실을 쉽게 유추해 볼 수 있다!
 
 
004010D0 | INC EBX
 
EBX는 1씩 증가하면서 파일에서 읽어올 위치를 다음 Byte로 이동한다.
F8을 눌러가며 계속 진행해보자.
 
 
004010D6 | JL SHORT reverseM.004010F7
 
반복문이 모두 끝나고, JL 구문에서 8보다 작다면 실패메시지로 이동하고, 그렇지 않으면 성공 메시지로 간다는 것을 알 수 있다.
 
 
이제 다시 이전에 만들었던 빈파일을 열어보자.
우리가 성공 메시지로 가기 위해서는 이 파일에 모두 16개의 문자를 넣어야 하는데, 적어도 첫번째부터 8번째까지는 G로 채워져야 한다.
 
[KeyFile.dat]
 
GGGGGGGG00000000
 
이제 재시작을 해보자. 
 
 

 5. testing the keyfile


1. 바이트를 얼마나 읽나?  

  • 16d(== 10h) bytes
  • 이 말은 곧, KeyFile에 16개의 문자가 들어가 있어야 함을 나타낸다. 

2. 몇개의 G를 찾나? 

  • 8개

 

6. conclusing

이제 프로그램을 실행해도 더이상 실패 메시지가 뜨지 않는 것을 확인 할 수 있다.
이번장을 학습하면서 라이센스를 요구하는 프로그램들을 어떻게 크랙하는지 간단한 예제를 통해 학습할 수 있었다. 하지만, 이 것을 보고 나쁜 생각을 하는 분들이 없기를 바라며 이번 강의를 마친다.