Calling Convention (함수 호출 규약)

0. 들어가며

  • 함수 호출 규약 : 함수의 호출 및 반환에 대한 약속
  • 함수 호출 시 프로그램의 실행 흐름이 다른 함수로 이동
  • 이 때, 기존 함수로 돌아오기위해 호출 방식에 대한 약속을 규정

1. 함수 호출 규약

  • 함수를 호출할 때는
    • 반환된 이후를 위한 호출자의 StackFrame 및 반환 주소 저장
    • 피호출자가 요구하는 인자 전달
  • 피호출자의 실행 종료 시 반환 값 전달
  • 함수 호출 규약을 적용하는 것은 일반적으로 컴파일러

2. 함수 호출 규약의 종류

  • CPU 아키텍처와 컴파일러 종류에 따라 함수 호출 규약도 달라짐
  • x86(32비트) 아키텍처
    • 레지스터를 통해 피호출자의 인자를 전달하기에는 레지스터의 수가 적어 스택을 이용하는 함수 호출 규약 사용
    • cdecl
    • stdcall
    • fastcall
  • x86-64
    • 레지스터가 많으므로 적은 수의 인자는 레지스터를, 인자가 많을 경우는 스택을 사용
    • SYSV
    • MSCV

3. x86호출 규약: cdecl

  • cdecl 호출 규약은 c/c++ 함수에서 기본적으로 사용되는 호출 규약
  • x86 아키텍처는 레지스터의 수가 적어 스택을 통해 인자 전달
  • 인자를 전달하기 위해 사용한 스택을 호출자가 정리하는 것이 cdecl 특징
  • 스택을 통해 인자 전달 시, 마지막 인자부터 스택에 PUSH
int sum(int a, int b)
{
	return a + b;
}

int main(int argc, char* argv[])
{
	sum(5, 4);
	return 0;
}
  • 위 C코드를 빌드한 후 main함수 확인
00401068  |. 6A 04          PUSH 4
0040106A  |. 6A 05          PUSH 5
0040106C  |. E8 94FFFFFF    CALL Consolas.00401005
00401071  |. 83C4 08        ADD ESP,8
  • sum의 마지막 인자인 4부터 stack에 PUSH
  • sum 함수 호출 이후 ADD ESP, 8을 통해 스택에 올라간 인자를 호출자가 정리
  • cdecl은 이처럼 호출자가 피호출자의 인자를 정리

4. x86호출 규약: stdcall

  • stdcall 방식은 Win32 API에서 사용
  • 피호출자가 스택을 정리
  • Win32 API에서는 가변 인수 함수가 없어 매개변수의 개수가 고정적 → 피호출자가 스택을 정리하는 것이 효율적
int __stdcall sum(int a, int b)
{
	return a + b;
}

int main(int argc, char* argv[])
{
	sum(5, 4);
	return 0;
}
  • cdecl의 코드와 동일한 동작을 하는 stdcall 코드
00401068  |. 6A 04          PUSH 4
0040106A  |. 6A 05          PUSH 5
0040106C  |. E8 9EFFFFFF    CALL Consolas.0040100F
  • 해당 코드의 main문 일부
  • cdecl과 동일하게 stack에 인자를 PUSH
  • 하지만 stack을 호출자인 main에서 정리하는 것이 아닌 sum함수에서 정리
00401020 >/> 55              PUSH EBP
00401021  |. 8BEC            MOV EBP,ESP
..
00401038  |. 8B45 08         MOV EAX,DWORD PTR SS:[EBP+8]
0040103B  |. 0345 0C         ADD EAX,DWORD PTR SS:[EBP+C]
..
00401041  |. 8BE5            MOV ESP,EBP
00401043  |. 5D              POP EBP
00401044  |. C2 0800         RETN 8
  • sum함수의 일부
  • 함수의 마지막 부분의 RETN 8을 통해 함수 내 스택을 정리
  • 반환을 한 후 ESP를 8만큼 증가시킴

5. x86호출 규약: fastcall

  • 스택이 아닌 가까운 레지스터를 사용하여 호출 속도가 빠름
  • 호출된 함수 내에서 사용된 스택은 정리
  • 인자 전달을 위해 스택을 사용하지 않고 레지스터를 이용하므로 따로 정리를 하지 않음
int __fastcall sum(int a, int b)
{
	return a + b;
}

int main(int argc, char* argv[])
{
	sum(5, 4);
	return 0;
}
  • cdecl, stdcall과 같은 기능을 하는 fastcall 코드
00401068  |. BA 04000000     MOV EDX,4
0040106D  |. B9 05000000     MOV ECX,5
00401072  |. E8 98FFFFFF     CALL Consolas.0040100F
  • main문의 일부
  • EDX와 ECX 레지스터를 사용하여 함수의 인자를 전달
  • cdecl, stdcall과 동일하게 마지막 인자부터 전달
00401020 >|> 55              PUSH EBP
00401021  |. 8BEC            MOV EBP,ESP
..
0040103A  |. 8955 F8         MOV DWORD PTR SS:[EBP-8],EDX
0040103D  |. 894D FC         MOV DWORD PTR SS:[EBP-4],ECX
00401040  |. 8B45 FC         MOV EAX,DWORD PTR SS:[EBP-4]
00401043  |. 0345 F8         ADD EAX,DWORD PTR SS:[EBP-8]
..
00401049  |. 8BE5            MOV ESP,EBP
0040104B  |. 5D              POP EBP
0040104C  |. C3              RETN
  • EDX의 레지스터의 값을 EBP-8에 넣고
  • ECX의 레지스터의 값을 EBP-4에 넣음
  • EAX 레지스터를 통해 두 전달 받은 인자를 더하는 연산
  • 레지스터를 이용하여 호출 속도가 빠름

5. x86-64호출 규약: SYSV

  • 64비트 리눅스에서 사용하는 함수 호출 규약
  • SYSV에서 정의한 함수 호출 규약
    1. 6개의 인자를 RDI, RSI, RDX, RCX, R8, R9에 순서대로 저장하여 전달, 더 많은 인자는 스택을 추가로 이용
    2. 호출자가 인자 전달에 사용된 스택을 정리
    3. 함수의 반환 값은 RAX로 전달
#define ull unsigned long long

ull callee(ull a1, int a2, int a3, int a4, int a5, int a6, int a7) {
  ull ret = a1 + a2 + a3 + a4 + a5 + a6 + a7;
  return ret;
}

void caller() { callee(123456789123456789, 2, 3, 4, 5, 6, 7); }

int main() { caller(); }
  • 스택 사용을 확인하기 위해 7개의 인자 함수를 사용

  • 함수 호출 직전의 레지스터와 스택의 상태
  • RDI, RSI, RDX, RCX, R8, R9과 RSP에 순서대로 인자가 저장되어있음
  • 32비트와 다르게 인자 순서대로 저장
  • 함수 종료 시 반환값은 RAX을 통해 전달하며
  • SYSV의 스택 정리는 호출자가 정리

6. 마치며

  • 위에서 소개한 함수 호출 규약 이외에도 다양한 아키텍처와 컴파일러에 의해 다양한 함수 호출 규약 존재
  • 64비트 Window의 경우 MSCV의 함수 호출 규약으로 레지스터 RCX, RDX, R8, R9을 사용하여 호출하며 호출자가 스택을 정리
  • 어셈블리어 분석을 하면서 함수 호출 규약을 볼 경우가 많으므로 기억해놓기

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

리버싱 / 악성코드 분석을 위한 Windows 10 Defender 비활성화  (0) 2023.07.11
PE File Format Advance  (1) 2023.07.08
스택프레임  (0) 2023.07.05
PE파일  (1) 2023.07.03
어셈블리어  (0) 2023.07.02