본문 바로가기

Reversing

04. 함수 호출 규약

04. 함수 호출 규약


일단 함수 호출 규약에는 3가지 방식이 있다.

시작하기에 앞서 함수 호출 규약을 알아보기위해 사용한 코드이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
 
int __cdecl test1(int a1, int a2, int a3) {
    int result = a1 + a2 + a3;
    return result;
}
 
int __stdcall test2(int a1, int a2, int a3) {
    int result = a1 + a2 + a3;
    return result;
}
 
int __fastcall test3(int a1, int a2, int a3) {
    int result = a1 + a2 + a3;
    return result;
}
 
int main(void)
{
    test1(123);
    test2(123);
    test3(123);
 
    return 0;
}
cs


1. __cdecl


기본적으로 사용되는 호출 규약으로서 호출자가 스택을 정리한다.

인자는 오른쪽에서 왼쪽으로 전달되며 호출자가 피호출자를 호출할 때 전달되는 

인자의 개수를 알기때문에 가변 인수 함수를 만들 수 있다.


그럼 코드 부분에서 __cdecl은 어느 부분일까?


1
2
3
4
int __cdecl test1(int a1, int a2, int a3) {
    int result = a1 + a2 + a3;
    return result;
}
cs

바로 이 부분이다. 이렇게만 봐서는 뭐가 어떻게 스택을 정리하고 인자는 어떻게 가는거고 알수가 없다.
어셈블리 코드로 보도록하자.




test1의 어셈블리코드이다. 하나하나뜯어서 살펴보면


push 3 -> test1의 제일 오른쪽 인자로 전달된 3을 push한다.

push 2 -> test1의 중간 인자로 전달된 2를 push한다.

push 1 -> test1의 제일 왼쪽 인자로 전달된 1을 push한다.

여기서 __cdecl의 인자가 오른쪽에서 왼쪽으로 전달된다는 걸 알 수 있다.


call test1 (0D2104Bh) -> 전달받은 인자들로 test1을 호출한다.


add esp,0Ch -> 중요한 부분인데, 스택을 호출자가 정리하는 부분이다. 만약 피호출자, 즉 함수가 스택을 정리했다면

스택을 정리하는 이 부분이 함수내에서 나타나야한다.


2. __stdcall


Win32 API에서 사용하며 피호출자가 스택을 정리한다.

인자는 __cdecl과 마찬가지로 오른쪽에서 왼쪽으로 전달되고

매개변수의 개수가 고정적이어서 호출자쪽에서 스택을 정리하는것 보다는

피호출자쪽에서 스택을 정리하는게 더 효율적이다.


코드에서의 __stdcall 부분은



1
2
3
4
int __stdcall test2(int a1, int a2, int a3) {
    int result = a1 + a2 + a3;
    return result;
}
cs
\
이 부분이다.

그럼 어셈코드로 살펴보도록 하자.


__cdecl과 달라진점이 보일것이다. 나머지 부분은 대부분 비슷하다.


push 3

push 2

push 1

식으로 인자를 오른쪽에서 왼쪽으로 전달하고 있고


call test2 (0D21343h)

인자 전달 후에 함수를 호출한다.


그런데 __cdecl에서는 보이던


add esp,0Ch


이런 비스무리한 코드조차 보이지가 않는다.


그 이유는 __stdcall은 스택을 피호출자, 즉 메인함수 혹은 이 함수를 쓰는 쪽에서 정리하고

__cdecl은 스택을 함수 자기 자신이 정리하기 때문에 스택을 정리하는 저 코드는

위 어셈 코드에서는 찾아볼 수 없고

test2 함수 자체를 어셈 코드로 나타내면 그 곳에서 찾아볼 수 있을것이다.

(위의 어셈 코드는 main함수 내의 test2 함수이다.)


3. __fastcall


__fastcall? 빠르게 호출한단다.

실제로 이 __fastcall 호출 규약은 스택이 아니라 가까운 레지스터를 사용해서 호출 속도가 빠르다.

피호출자가 스택을 정리하게끔 되어있지만 스택이 아니라 가까운 레지스터를 사용한다.

즉, 스택을 사용하지를 않으므로 정리할 필요가 없어서 정리를 하지않는다.


코드에서 __fastcall 부분은



1
2
3
4
int __fastcall test3(int a1, int a2, int a3) {
    int result = a1 + a2 + a3;
    return result;
}
cs

이 부분이다.

이것 또한 어셈코드로 살펴보도록 하자.


흠칫 봤을때는 __stdcall과 별 다른게 없어보인다.

하지만, __fastcall의 특징은 스택이 아니라 가까운 레지스터를 사용하는 것이다.


push 3 -> 3을 push한다.

mov edx,2 -> edx에 중간 인자 값인 2를 mov 즉, 저장한다.

mov ecx,1 -> ecx에 제일 왼쪽 인자 값인 1을 mov 즉, 저장한다.


이렇게 레지스터를 사용해서 인자 값을 저장한다.


call test3 (0D21127h) -> 위의 인자 값들을 가지고 test3 함수를 호출한다.


레지스터를 사용했기 때문에 딱히 어느 부분에서 스택을 정리하지 않는다.


이상 함수 호출 규약에 대해 정리했다.



세개의 함수 호출 규약에 대한 어셈코드를 합쳐서 올린다.

이 어셈코드를 보고 각기 다른점을 알아챈다면 함수 호출 규약을 어느정도는 안다고 말할 수 있지않을까.


반응형

'Reversing' 카테고리의 다른 글

06. gdb 명령어 정리  (0) 2017.09.29
05. 스택, 스택프레임  (0) 2017.08.04
03. 패킹 & 언패킹  (0) 2017.05.26
02. IAT & EAT  (0) 2017.05.24
01. PE File Format  (0) 2017.05.24