Reversing/Reversing2017. 7. 2. 15:03

함수 호출 규약 (Calling Convention)


함수 호출 규약은 함수를 호출할 때 파라미터를 어떤 식으로 전달하는가?’에 대한 일종의 약속이다. 이전까지의 포스팅에서도 확인했듯이 함수를 호출하기 전에 파라미터를 스택을 통해서 전달한다는 것을 알고 있다. 스택이란 프로세스에서 정의된 메모리 공간이며, ‘스택은 거꾸로 자란다는 말대로 스택에 큰 주소부터 작은 주소를 향해 자라난다. 또한 PE 헤더에 스택의 크기가 명시되어있다. , 프로세스가 실행될 때 스택 메모리의 크기가 결정된다는 뜻이다. 이는 동적 할당에 사용되는 Heap 메모리 공간과는 다르다.


그렇다면 함수가 실행이 완료되었을 때 스택에 있는 파라미터는 어떻게 할까? 정답은 그대로 놔둔다이다. 스택에 저장된 값은 임시로 사용하는 값이기 때문에 더 이상 사용하지 않는다 하더라도 값을 지우거나 하면 불필요하게 CPU의 자원을 소모하는 것이기 때문이다. 어차피 다음 번에 스택에 다른 값을 입력하면 저절로 덮어쓰는 데다가 스택 메모리는 이미 고정되어 있기 때문에 메모리를 해제(Free)시킬 수도 없고 할 필요도 없다.

=> 함수 실행 후 스택에 남은 파라미터는 지우지 않고 그대로 놔둔다.

 

함수가 실행이 완료되었을 때 ESP, 즉 스택 포인터는 함수 호출 전으로 복원되어야 한다. 그래야만 참조 가능한 스택의 크기가 줄어들지 않기 때문이다. 스택 메모리는 고정되어 있고 ESP로 스택의 현재 위치를 가리키는데, 만약 ESP가 스택의 끝을 가리킨다면 더 이상 스택을 사용할 수 없다. 함수 호출 후에 ESP를 어떻게 정리하는지에 대한 약속이 바로 함수 호출 규약이다.

주요 함수 호출 규약은 아래와 같다.


1.     Cdecl

2.     Stdcall

3.     Fastcall


애플리케이션 디버깅에선 cdecl stdcall의 차의점을 확실히 알아야 한다. 어떤 방식이든 파라미터를 스택을 통해 전달한다는 기본 개념은 동일하다.

 

Cdecl (C Declaration)

Cdecl 방식은 주로 C언어에서 사용되는 방식이고, Caller, 즉 함수 호출자가 스택을 정리하는 특징을 가지고 있다.


위에서 Main으로 표시해둔 부분을 잘 보자. 파라미터를 401000 주소에 위치한 함수에 전달한 후 ESP 8을 더해서 파라미터 부분을 정리하고 있다. 이렇게 Caller(이 예제에서는 main)가 스택을 정리하는 방식이 cdecl 방식이다.

Cdecl 방식은 printf 함수와 같이 파라미터의 길이가 변해도 전달할 수 있다는 장점이 있다. 가변 길이 파라미터를 전달하는 것은 다른 함수 호출 규약 방식(정확히 말하면 피호출자가 정리하는 규약)에서는 구현이 어렵다. 호출자 함수에서는 가변 인자의 스택의 크기를 알고 있지만 피호출자 함수에서는 가변 인자의 스택의 크기를 알 수 없기 때문이다.

 

Stdcall

Stdcall Win32 API에서 사용되며, Callee에서 스택을 정리하는 특징을 가진다. C언어는 기본적으로 cdecl 방식이라고 했는데, stdcall 방식으로 컴파일 하고 싶을때는 int _stdcall name(int a)와 같이 함수명 앞에 ‘_stdcall’ 키워드를 붙여주면 된다.


401010 메모리 주소에 있는 것이 main함수이다. 위에서 cdecl 방식이 main에서 스택을 정리했었는데, 여기서는 ESP 8을 더하는 명령이 생략되고 401000에 있는 함수에서 RETN 8(RETN POP 8바이트 라는 뜻이다) 명령으로 8바이트만큼 ESP를 증가시키고 있다. 이와 같이 Callee가 스택을 정리해주는 방식이 stdcall 방식이다.

Stdcall 방식은 피호출자 함수 내부에 스택 정리 코드가 존재하므로 함수를 호출할 때마다 ADD ESP, @ 명령을 써줘야 하는 cdecl 방식에 비해 코드의 크기가 작아진다. Win32 API C언어로 된 라이브러리이지만 기본 cdecl 방식이 아닌 stdcall 방식을 사용한다. 이는 C 이외의 다른 언어에서 API를 직접 호출할 때 호환성을 좋게 하기 위한 것이다.

 

Fastcall

기본적으로 stdcall 방식과 같지만 함수에 전달하는 파라미터의 일부(2개까지, 일반적으로 파라미터 맨 앞의 2)를 스택 메모리가 아닌 ECX, EDX 레지스터를 이용해 전달한다. 이는 좀 더 빠른 함수 호출을 가능하게 하지만 ECX, EDX 레지스터를 관리하는 추가적인 오버헤드가 필요할 수 있다. 함수 호출 전에 ECX, EDX에 중요한 값이 저장되어 있다면 백업을 해야한다. 또한 함수의 내용이 복잡하면 ECX EDX 레지스터를 다른 용도로 사용해야 할 필요가 있는데, 이럴 때도 다른 어딘가에 백업을 해야한다.

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

Lena's Reversing for Newbies #1  (0) 2017.07.03
Abex’ Crackme #2  (0) 2017.06.30
Stack Frame  (0) 2017.06.27
Abex' Crackme #1  (0) 2017.06.27
OllyDbg Commands & Assembly Basic & etc  (0) 2017.06.27
Posted by BinZIP