콜링 컨벤션(Calling Conventions)이란 함수호출규약으로, 함수를 호출할 때 사용하는 정해진 규약입니다.
시스템/컴파일러마다 정해진 규약이 다르고 인자 전달 순서나 전달 위치, 또 사용한 스택을 어디서 정리하는지 정하기 때문에 매우 중요합니다.
리버싱에서는 콜링컨벤션에 대해 알아야 함수 인자 추적이 가능하고, 포너블에서는 스택 정리 방식에 따라 삽입하는 payload 구조가 달라지기 때문에 더 중요합니다.
해당 포스팅에 나오는 실습을 따라해보고 싶으신 분들은 다음과 같은 명령어로 32비트 컴파일 환경을 설치해주시기 바랍니다.
sudo apt update
sudo apt install gcc-multilib g++-multilib
먼저 콜링컨벤션에 알아보기 전에 기본 용어를 정리하고 가보겠습니다.
Caller : 호출한 함수 (보통 main이나 다른 함수)
Callee : 호출당한 함수(sub_401000)
스택 정리 : 함수 호출 후 스택 포인터(esp, rsp)를 원래대로 복구하는 작업
콜링 컨벤션의 종류는 다음과 같습니다.
| 규약 | 인자 전달 방식 | 스택 정리 주체 | 플랫폼/OS |
| cdecl | 스택(오른쪽 -> 왼쪽) | Caller | C언어(대부분의 윈도우 리눅스) |
| stdcall | 스택(오른쪽 -> 왼쪽) | Callee | WinAPI 기본 |
| fastcall | 일부 레지스터, 일부 스택 | Callee | Microsoft Visual C++(MSCV) |
위의 3가지 규약은 32비트에서 사용합니다.
보통 스택 정리 주체를 확인하면 어떤 규약을 사용했는지 알 수 있습니다.
| 규약 | 인자 전달 방식 | 스택 정리 주체 | 플랫폼/OS |
| MS x64 | RCX,RDX,R8,R9 (이외에는 스택) | caller | Windows x64 |
| System V x64(SYSV) | RDI,RSI,RDX,RCX,R8,R9 | caller | Linux x64 |
위의 2가지 규약은 64비트에서 사용합니다.
64비트의 콜링컨벤션은 플랫폼이 정해져 있기 때문에 구분하는데 어려움은 없습니다.
(*저번 포스팅에서 작성했던 shellcode는 sysv를 사용하여 작성했습니다)
오늘 포스팅에서는 정해진 콜링 컨벤션 중 32비트에 해당하는 cdecl, stdcall에 대해 알아보겠습니다.
(fastcall은 거의 사용 x)
1.cdecl
cdecl은 C declaration의 약자로 c언어 함수 호출 규약에서 가장 기본이 되는 함수입니다.
함수 호출 시 인자를 스택에 오른쪽 -> 왼쪽 순서로 push 하고, 호출한 쪽 (caller)에서 스택을 정리하는 방식입니다.
이해를 위해 간단한 코드를 작성한 후 컴파일해 보겠습니다.
int add(int a, int b) {
return a + b;
}
int main() {
int result = add(1, 2);
return 0;
}
add라는 함수를 작성 후, result에 add(1,2)를 저장하는 간단한 코드입니다.
해당 코드를 add.c로 저장후 32비트 방식으로 컴파일 해보겠습니다. 컴파일 명령어는 다음과 같습니다.
gcc -m32 -S -masm=intel -o add.s add.c
-m32는 32비트로 컴파일
-S는 어셈블리 파일을 생성
-masm=intel 형식은 어셈블리어를 저희가 보기 편한 intel형식으로 저장해줍니다(기본값은 nasm)

다음과 같이 명령어를 입력하고, add.s.를 열어보겠습니다.


왼쪽은 add부분의 어셈블리어고 오른쪽은 main함수의 어셈블리어입니다.
.cfi지시어는 Call Fram Information으로 실행에 영향을 주지 않으면서 디버깅을 도와주는 명령어이므로 신경쓸 필요 없습니다.
먼저 add부분의 어셈블리어부터 살펴보겠습니다.
push ebp
mov ebp, esp //스택에 푸쉬
mov edx, DWORD PTR 8[ebp] //edx에 첫번째 인자 a
mov eax, DWORD PTR 12[ebp] //eax에 두번째 인자 b
add eax, edx //eax+ebx를 eax에 저장
pop ebp //ebp값을 복구하고
ret //ret로 복귀 (eax값이 리턴값)
함수 스택을 정리하는 부분이 없이 바로 ret을 해주고 있습니다.
다음 main함수의 어셈블리어부분을 살펴보겠습니다.
push ebp
mov ebp, esp
sub esp, 16 //스택 프레임 생성
push 2 //b
push 1 //a (2와 1을 스택프레임에 push)
call add
add esp, 8 //스택 정리(인자 2개 int형 4바이트*2 = 8)
mov DWORD PTR -4[ebp], eax
mov eax, 0
leave
ret
살펴봐야하는 부분이 2군데 있습니다.
먼저 인자를 전달하는 부분입니다.
push 2
push 1
저희가 작성했던 c언어 코드에서는 add(1,2)로 1이 먼저 들어갔지만 함수 호출에서는 오른쪽 인자부터 push하기때문에 push 2다음 push 1이 나오는 부분입니다.
add esp, 8
위 명령어는는 스택을 정리하는 명령어입니다.
스택은 아래로 쌓이기때문에 인자 두개를 push하면서 esp가 -8만큼 내려갔고, 스택을 정리하기 위해 add esp, 8을 통해 스택을 복구해주었습니다.
call add한 뒤 위와같은 add로 스택을 정리하는 건 cdecl방식의 특징입니다.
2.stdcall
stdcall은 C함수 호출 시 cdecl과 마찬가지로 인자를 오른쪽에서 왼쪽으로 스택에 push하고, cdecl과 다르게 callee가 스택을 정리하는 호출 규약입니다.
Windows API(MessageBoxA, CreateFileA)등에서 사용합니다.
조금 헷갈릴 수 있는 부분들을 정리해보자면 우리가 일반적인 C언어 코드를 작성하면 기본적으로 cdecl 방식이 사용되어서 컴파일 됩니다. 하지만 코드안에서 API를 호출하는 부분이 있을경우, Windows API함수들은 stdcall방식으로 지정되어 있으므로 API를 호출하는 부분에서는 stdcall방식에 맞게 호출합니다(컴파일러가 알아서 규약을 맞춰줌)
코드를 작성할 때 있어서 호출방식을 신경쓸 필요는 없지만, payload 작성할 때나 후킹시 스택 계산과 같은 부분에서 중요합니다.
아까의 add.c 파일을 stdcall방식으로 컴파일해보겠습니다.
c언어에서는 기본적으로 cdecl방식을 사용하므로 코드 수정이 필요합니다.

위와 같이 __atribute__((stdcall))을 추가해주면 됩니다.
gcc -m32 -masm=intel -S -o add_stdcall.s add.c
마찬가지로 컴파일한 뒤 add_stdcall.s에 저장했습니다.


왼쪽은 add함수부분 어셈블리어, 오른쪽은 main함수 부분입니다.
add함수부분부터 살펴보겠습니다.
push ebp
mov ebp, esp //스택프레임 설정
mov edx, DWORD PTR [ebp+8]
mov eax, DWORD PTR [ebp+12]
add eax, edx
pop ebp
ret 8 //바뀐 부분
아까 cdecl에서 ret 부분이 ret 8로 바뀌었습니다.
ret 8은 기능적으로
ret
add esp,8
위의 2줄의 명령어와 기능적으로 완전히 동일하게 작동합니다.
add esp,8은 아까 스택을 정리하는 명령어라고 설명을 했고, stdcall은 cdecl과 다르게 호출당한 함수에서 스택을 정리하는 것을 어셈코드를 통해 확인할 수 있었습니다.
push ebp
mov ebp, esp
sub esp, 16
push 2
push 1
call add //원래 뒤에 있던 add esp, 8 부분이 사라짐
mov DWORD PTR -4[ebp], eax
mov eax, 0
leave
ret
아까 cdecl과 마찬가지로 오른쪽부터 왼쪽으로 스택에 push하는 부분을 확인할 수 있고, cdecl과 다르게 call add뒤에 바로 리턴값을 저장하는 mov명령어가 나오는 것을 확인할 수 있습니다.
이상으로 콜링컨벤션 중 32비트에 해당하는 cdecl과 stdcall에 대해 살펴보았습니다.
시험기간이 끝나자마자 포스팅을 한건데도 불구하고 좀 오랜만에 공부하는 느낌이었는데, 처음에 복잡해보였던 내용과 다르게 생각보다 어렵진 않았던 것 같습니다.
다음 포스팅에는 64비트 콜링컨벤션과 32비트 콜링컨벤션의 가장 큰 차이점과 64비트 콜링컨벤션에 대해 알아보겠습니다.
또 제가 이번주 토요일 핵시움 예선을 해보는데, 처음 나가보는 대회인지라 준비하는 과정과 공부내용도 담을 수 있으면 담아보겠습니다!
이상으로 포스팅을 마치겠습니다. 읽어주셔서 감사합니다~

'취약점분석 > Reversing' 카테고리의 다른 글
| [Reversing] Hooking - 1 (DLL,IAT 이론) (2) | 2025.07.05 |
|---|---|
| [Reversing]콜링 컨벤션(Calling convention) -2 (64 bits) (1) | 2025.06.27 |
| [Reversing] 어셈블리어 실전 구조 분석 (if문과 반복문) (1) | 2025.06.13 |
| [Reversing] 어셈블리어 기본 구조 (2) | 2025.06.13 |
| [Reversing] 바이러스의 보안 모듈 우회 방법 (0) | 2025.06.11 |