티스토리 뷰

이 블로그는 제가 공부한 것을 바탕으로 정리 목적으로 사용되고 있습니다.

작성 내용중 부족한 부분이나 잘못된 부분을 지적해주시면 감사하겠습니다 (꾸벅)


함수의 호출 규약에 대해서 알아보려 한다.


리버싱을 하기 위해 디버기를 켜고 바이너리를 올려 놓았다. 제일 먼저 해야 할 일이 무엇일까?

해야 할 일은 바로 지금 보이는 이 함수의 역할은 무엇이고 파라미터는 이러한 구조로 넘어가는 구나!!!

를 알아야 될거 같다.


즉 각 함수의 역할을 파악하는 것이다.

코드의 목적을 알아낸다면 리버스 엔지니어링 작업의 50%를 달성한 것이나 다름없다고 한다.

함수 호출 규약에는 여러 가지 방식이 있다.

대표적으로 __cdcel, __stdcall, __fastcall, __thiscall 이렇게 네가지가 있다.

여기서 우리가 확인할 것은 디스어셈블된 코드를 보고 이것이 어떤 콜링 컨베션에 해당하는지 파악하는 것이다.

이를 확인하는 목적은 리버스 엔지니어링을 할 때 call문을 보고 이 함수의 인자가 몇 개이고 어떤 용도로 쓰이는지 분석하기 위해서 이다.



먼저 __cdcel에 대해 알아보자.

int __cdecl sum (int a, int b){ 

     int c = a+ b; 

     return c; 

} 

int main (int argc, char* argv[]) { 

     sum (1,2 ); 

     return 0; 

} 

sum : 

push ebp 

mov ebp, esp 

push ecx 

mov eax, [ebp+arg_0] 

add eax, [ebp+arg_4] 

mov [ebp+var_4], eax 

mov eax, [ebp+var_4] 

mov esp, ebp 

pop ebp 

retn 


main : 

push 2 

push 1 

call calling.00401000 

add esp,8 


함수 본체 말고 call calling.00401000이라 되어 있는 함수를 호출하는 곳을 살펴보는 것이 함수 역분석의 1 과제다. 항상 call 문의 다음 줄을 

살펴서 스택을 정리하는 곳이 있는지 체크해야 한다. 코드처럼 add esp, 8 같이 스택을 보정하는 코드가 등장한다면 그것은 __cdecl 방식의 함수라 생각하면 된다. 

(__cdecl 방식은 함수 밖에서 스택을 보정한다.) 

그리고 해당 스택의 크기를 파라미터의 개수까지 확인할 있다. 인자는 4바이트씩 계산이 되므로 스택을 8바이트까지 끌어올린다는 

점에서 파라미터가 2개인 함수라는 까지 파악할 있다. 


__stdcall

int __stdcall sum(int a, int b){ 

     int c = a+ b; 

     return c; 

} 


int main (int argc, char* argv[]) { 

     sum (1,2 ); 

     return 0; 

} 


sum : 

push     ebp 

mov      ebp, esp 

push     ecx 

mov      eax, [ebp+arg_0] 

add       eax, [ebp+arg_4] 

mov      [ebp+var_4], eax 

mov      eax, [ebp+var_4] 

mov      esp, ebp 

pop      ebp 

retn      8 


main : 

push 2 

push 1 

call calling.00401000 


__cdecl에서의 어셈블리 코드와 다르게 add esp, 8 보이지 않는다는 것을 있다. 

이것은 main() 안에서 sum() 사용한 어떤한 스택 처리도 없다는 이야기이다. 

대신 sum() 본체의 후반부의 리턴 부분에 그냥 retn 아닌 retn 8 했다는 사실을 있다 

경우에는 함수 안에서 스택을 처리한다는 것을 있다. 

그래서 8바이트의 스택 보정과 파라미터가 2개라는 판단은 함수 내부에서 확인해야 한다 

대표적으로 Win32 API __stdcall 방식을 이용한다 


__fastcall


int __fastcall sum(int a, int b){ 

     int c = a+ b; 

     return c; 

} 


int main (int argc, char* argv[]) { 

     sum (1,2 ); 

     return 0; 

} 


sum : 

push     ebp 

mov      ebp, esp 

sub       esp, 0ch 

mov      [ebp+var_C], edx 

mov      [ebp+var_8], ecx 

mov      eax, [ebp+arg_8] 

add       eax, [ebp+arg_C] 

mov      [ebp+var_4], eax 

mov      eax, [ebp+var_4] 

mov      esp, ebp 

pop      ebp 

retn      8 


main : 

push     ebp 

push     ebp, esp 

mov      edx, 2 

mov      ecx, 1 

call       sub_00401000 

xor       eax, eax 

pop      ebp 

retn 


sub esp, 0ch 스택 공간을 확보하고 edx레지스터를 사용한 것을 있다. 

함수의 파라미터가 2 이하일 경우, 인자를 push 넣지 않고, ecx edx 레지스터를 이용한다. 

메모리를 이용하는 것보다 레지스터를 사용하는 것이 속도가 훨씬 빠르다 

따라서 리버스 엔지니어링 함수 호출 전에 edx ecx 레지스터에 값을 넣는 것이 보이면 __firstcall 규약의 함수라고 생각할 있다. 


__thiscall


Class CTemp{

public :

     int MemberFunc(int a, int b);

};



mov     eax, dword ptr [ebp-14h]

push    eax

mov     edx, dword ptr [ebp-10h]

push    edx

lea       ecx, [ebp-4]

call      402000


__thiscall 주로 C++ 클래스에서 이용되는 방법이다. 함수의 특징으로는 현재 객체의 포인터를 ecx 전달하는 것이다. 같은 구조를 설명하자면 클래스에 대한 내용을 아는 것이 좀더 효과적일 듯하다. 클래스는 객체지향 프로그래밍의 개념이며, 하나의 클래스만 정의하면 얼마든지 언제든지 여러 개의 독립적인 개체를 만들 있다따라서 모양은 완전히 동일한 클래스더라도 실제 오브젝트 입장에서 생각해 보면 클래스는 서로 다른 메모리 번지에 존재하게 된다. 그리고 그것을 각자 구분하기 위해서는 현재 자신이 어떤 객체를 이용하고 있는지 구분해줄 값이 필요하다. 그것이 C++에서는 this포인터이다. ecx 전달되는 값이 this 포인터가 된다__thiscall 인자 전달 방법이나 스택 처리방법은 __stdcall이랑 동일하다


<공부 출처 : 코드 재창조의 미학 리버스 엔지니어링 바이블 ; 저자 :강병탁>

'Reversing > Reverse Engineering' 카테고리의 다른 글

abex’ crackme #1  (0) 2015.12.26
OllyDbg를 이용한 스택 공부하기  (0) 2015.12.23
PACKING  (0) 2015.12.21
레지스터, 단지 변수이다.  (0) 2015.08.05
어셈블리어 기본 명령어  (0) 2015.08.04
댓글