공부/리버싱핵심원리

10장 함수 호출 규약 (Calling Convention)

함수 호출 규약 (Calling Convention)에 관하여 알아보자. (학교 수업시간에 간단하게 듣기는 했다.)

 

1. 함수 호출 규약

Calling Convention은 '함수를 호출할 때 파라미터를 어떤 식으로 전달하는가?' 에 대한 일종의 약속이다.

우린 이미 함수를 호출하기 전에 파라미터를 스택을 통해 전달한다는 것을 알고있다.

스택이란, 프로세스에서 정의된 메모리 공간이며 주소가 줄어드는 방향으로 자라난다.

 

PE헤더에 그 크기가 명시되어있다. 즉 프로세스가 실행될 때 스택 메모리의 크기가 결정된다.

(malloc, new 같은 동적메모리는 영역이 다르다. 그래서 꼭 free를 해주어야 한다.)

 

함수가 실행완료 되더라도 스택내의 파라미터는 그대로 둔다.

스택에 저장된 값은 임시로 사용하는 값이기에 더 이상 사용하지 않는다고 하더라도 값을 지우거나 하면 cpu 자원을 불필요하게 소모하게 된다.

다음 번에 스택에 다른 값을 입력할 때 저절로 덮어쓰기도 하고, 스택메모리는 이미 고정되어 있기 때문에 메로리를 해제할 수도 없어서 굳이 할 필요도 없다.

 

함수가 실행 완료되면 ESP는 함수 호출 전으로 복원되어야 한다.
그래야 참조 가능한 스택 크기가 줄어들지 않는다. (ESP : Stack Poitner)

스택 메모리는 고정되어있고, ESP로 스택의 현재 위치를 가리킨다. 만약 ESP가 스택의 끝을 가리킨다면 더 이상 스택을 사용할 수가 없다. 함수 호출 후에 ESP를 어떻게 정리하는지에 대한 약속이 바로 Calling Convention이다.

 

사실 이 말만 들으면 잘 이해가 가지 않는다. 그러므로 세 가지의 호출 규약을 알아보자.

 

  • cdecl
  • stdcall
  • fastcall

주요한 함수 호출 규약은 위와 같다.

디버깅에서는 cdecl과 stdcall의 차이를 확실히 알아야 한다. (어떤 방식이든지 파라미터를 스택을 통해 전달한다는 기본개념은 동일하다.)

 


2. cdecl

cdecl 방식은 주로 C언어에서 사용된다. Caller에서 스택을 정리하는 특징을 가지고 있다.

#include <stdio.h>

int add(int a, int b){
	return (a+b);
}

int main(int argc, char* argv[]){
	return add(1, 2);
}

이런 코드가 있다고 가정하고, X64DBG로 뜯어보자.

add() 함수의 파라미터 1, 2를 역순으로 스택에 입력하고, ADD ESP, 8로 스택을 정리함을 알 수 있다.

숫자 두 개가 총 8바이트이므로 파라미터에 집어넣었을 때는 ESP의 값이 8만큼 작아지게 되었기 때문이다.

 

이렇게 Caller에서 자신이 스택에 입력한 함수 파라미터를 직접 정리하는 방식이 cdecl 방식이다.

 


3. stdcall

stdcall 방식은 Win32 API에서 사용되며, callee에서 스택을 정리한다. 

C언어는 기본적으로 cdecl 방식이라 stdcall 방식으로 사용할거면 키워드 _stdcall 을 붙여주면 된다.

#include <stdio.h>

int _stdcall add(int a, int b){
	return (a+b);
}

int main(int argc, char* argv[]){
	return add(1,2);
}

cdecl 방식과 달리 callee에서 직접 ret 8를 통해 파라미터를 정리하고 있다.

조금만 더 자세히 알아보자. ret 8을 하는 이유가 무엇일까?

 

ret 8의 의미는 ret + pop8 이다. 즉 caller로 돌아가고, ESP를 8만큼 증가시켜주는 것이다.

여기서 8인 이유는 당연히 두 정수의 크기가 총 8바이트이기 때문일 것이다.

 

이렇게 Callee 함수 내에서 스택을 정리하는 방식이 stdcall 방식이다. 장점은 호출되는 함수(Callee) 내부에 스택정리 코드가 존재하므로, 함수를 호출할 때 마다 ADD ESP, 8 과 같은 명령을 써줘야 하는 cdecl 방식과 비교해서는 코드 크기가 줄어들게 된다.

 

Win32 API는 C로 작성된 라이브러리이지만 기본 cdecl 방식이 아닌 stdcall 방식을 사용한다. 다른 언어들과의 호환성을 맞추기 위해서라고 한다.

 


4. fastcall

fastcall 방식은 기본적으로 stdcall 방식과 같다.

함수에 전달하는 파라미터 일부(거의 2개까지)를 스택 메모리가 아닌 레지스터를 이용하여 전달한다는 것이 특징이다.

 

만약 어떤 함수의 파라미터가 4개라면 각각 ECX, EDX를 이용한다고 한다.

fastcall의 장점은 말그대로 더 빠른 함수호출이 가능하다. ( 레지스터에 있으니까 !!!!!!)

호출 자체는 빠르지만 ECX, EDX 레지스터를 관리하기 위한 추가 오버헤드가 필요할 수 있다.

 

ECX, EDX 내용이 중요해서 백업을 해야하는 경우, 또는 프로그램이 복잡해 다시 어딘가에 저장해야 하는 경우 모두 오버헤드가 필요할 것 같긴 하다.

'공부 > 리버싱핵심원리' 카테고리의 다른 글

13장 PE File Format  (0) 2022.03.05
11장 Lena's Reversing for Newbies  (0) 2022.01.03
9장 Process Explorer  (0) 2021.08.21
8장 abex' crackme #2 (abexcm2) - Retry  (0) 2021.05.21
8장 abex' crackme #2 (abexcm2)  (1) 2021.01.23