지금까지 포스팅한 내용들을 잘 보면 리버싱에 기초되는 내용들이 많다. Function procedure call 부분부터 Assembly Language에 대한부분도 있고, OS에 대한 내용, Virtual Memory에 대한 내용도 있다. 이 많은 내용을 종합적으로 하여 Reversing을 어떤 식으로 하는지 예시를 통해 포스팅해보려고 한다.


단순 Assembly Language만으로는 불가능하다. 가령, mov %ebp, %esp라는 instruction을 봤을 때 %esp값을 %ebp에 복사하라는 것은 모두가 알지만, 왜 그런 일을 하는지 "분석"하는 것은 쉽지 않다. 이번 포스팅에서 예시로 분석할 함수는 NTDLL.dll의 함수 중 하나이다. RtlInitializeGenericTable이라는 함수를 분석해볼 생각이다. 왜 이것을 분석하냐? 이 함수의 이름으로 보아 GenericTable을 초기화한다는 것 같은데, 분명 GenericTable은 구조체로 되어있을 것인데, 이 GenericTable structure의 구조체가 어떻게 생겼는지 알아야 하기 때문이다. 그 구조체를 알기 위해서 어떤 함수가 젤 효율적일까? 이름으로 보아 "RtlInitializeGenericTable 함수가 그것일 것이다" 라고 추측한 것이다. 그래!! 추측으로부터 시작해보자. 


NTDLL.dll의 export function list를 보면 Rtl로 시작하는 함수들을 많이 볼 수 있다. 앞으로 Rtl 그룹함수라고 편하게 이야기하겠다.


이 Rtl 그룹함수들은 Undocumented API이다. 즉, 설명이 안 되어있다. 이것을 알 수 있는 방법.. Reversing이다. 그 중에서 어떤 함수부터 시작할까? 
RtlInitializeGenericTable이다. 왜냐? 이것이 GenericTable API에서 사용하는 여러 구조체 중에서 가장 Root 구조체를 잘 설명해줄 것이라고 추측하기 때문이다. 이름으로부터 그런 추측을 한 것이다.

우선 이 함수의 Disassembled Code를 봐볼까?


뭔가 짧은 것이 기분이 좋다. 우선 가장 위의 3줄을 보자.

mov edi, edi
push ebp
mov ebp, esp

두번째 세번째 줄은 일반적으로 함수가 시작할 때 Function Procedure Call을 이해한 사람이라면 이해할 수 있는 내용이다. 아래의 링크를 확인해보라.

http://operatingsystems.tistory.com/entry/SP-Procedure-Call


그런데 가장 첫번째 줄, mov edi, edi 이거는 왜 할까? edi를 edi에 복사한다. 의미가 없다. 어짜피 같은 값이다. 이것을 하는 이유는 hot-patch point를 만들기 위해서 이다. 아래의 링크를 참고 하자.

http://blogs.msdn.com/b/oldnewthing/archive/2011/09/21/10214405.aspx


즉, Run-Time Library와 같이 실행 시간 중에 무언가 함수 흐름을 변경하려면 jmp instruction을 어딘가 껴넣어야 하는데, 그것을 가장 처음 부분에 끼워넣을 것이고 그것을 넣기 위한 공간을 확보해둔 것이다. 일반적으로 jmp instruction은 최대 2 바이트 차지하는데 그 2 바이트를 위한 NOP함수인 것이다. 즉, 바뀌어도 상관없는 공간 2 바이트를 만들어둔 것이다. 


pop ebp

retn 0014h

이 라인은 함수를 종료하고 caller function으로 돌아갈 때 사용한다. retn 0014h를 조금 더 자세히 보면 RtlInitializeGenericTable 함수에서 몇 개의 파라미터를 사용하는지 추측할 수 있다. 5개 일 것이다. 0x14는 10진수로 20이고 포인터는 4바이트이기 때문이다. 물론 character형이 parameter로 올 수도 있고, 4바이트가 아닌 다른 형이 올 수도 있다. (아래 내용을 더 읽어보면 알겠지만 5개가 맞다.)


이제 나머지 라인들을 살펴보자.아, 본격적으로 살펴보기 전에 한 가지 또 가정을 해보자. RtlInitializeGenericTable 이 함수가 뭘 하는 함수일까? GenericTable을 초기화하는 함수일 것이다. 그렇다면 이 함수의 parameter 중 첫번째 parameter는 무엇일까? GenericTable의 root data structure일 것이다. 그리고 이 함수의 Return Address 또한 GenericTable의 root data structure일 것이다.


이렇게 가정하면 RtlInitializeGenericTable 함수와 GenericTable의 root data structure(이하 GnrTbl)는 아래와 같은 모양일 것이다.

조금 더 Assembly Code를 분석해보면...

mov eax,[ebp+08h]

첫번째 파라미터를 %eax에 놓는다. 

xor edx,edx

lea ecx,[eax+04h]

%edx에 0을 놓고 %ecx에 GnrTbl의 두번째 member Value를 놓는다.


지금까지의 내용을 C코드로 작성하면 대략 아래와 같을 것이다.



mov ecx,[ebp+0Ch]

mov [eax+18h],ecx

%ecx가 param2로 업데이트되고 GnrTbl의 7번째 member로 초기화한다.


mov ecx,[ebp+10h]

mov [eax+1Ch],ecx

%ecx가 param3로 업데이트되고 GnrTbl의 8번째 member로 초기화한다.


mov ecx,[ebp+14h]

mov [eax+20h],ecx

%ecx가 param4로 업데이트되고 GnrTbl의 9번째 member로 초기화한다.


mov ecx,[ebp+18h]

%ecx가 param5로 업데이트되고 ...


mov [eax+14h],edx

mov [eax+10h],edx

GnrTbl의 5번째와 6번째 member를 0으로 초기화 한다.


mov [eax+24h],ecx

GnrTbl의 10번째 member가 param5로 업데이트 된다.


pop ebp

retn 0014h

함수를 종료하고 caller 함수로 돌아간다. 


지금까지의 분석한 내용을 바탕으로 위의 C코드를 완성해보자.


이 모든 초기화 과정을 %eax를 통해 진행되는 것을 보니 분명 이 함수는 return 값을 가질 것이다 마지막에 return GnrTbl과 같이 무언가가 있지 않을까?


지금까지 RtlInitializeGenericTable를 분석해봤다. 이번 포스팅을 통해서 우리는... 리버싱을 하기 위해서는 초반에는 어느정도의 추측을 가정하고 들어간다는 것을 알 수 있다. 함수명을 통해서 함수의 행위를 추측하고, 어떤 파라미터를 사용할지 추측한다. 그리고 어떤 구조체를 사용할 지도 예측해야 한다. 이것을 바탕으로 instruction을 하나하나 분석한다. 이렇게 모인 내용들을 바탕으로 우리는 퍼즐을 맞춰야 한다.


리버싱은 어느 정도의 가정을 바탕으로 들어가야 된다는 것이 이번 포스팅의 핵심이다. 자 다음 포스팅은 Rtl 그룹함수의 다른 함수를 분석해볼 생각이다. 앞으로 몇개의 포스팅은 이번 포스팅과 연결될 것 같다. 



Posted by 빛나유
,