Iriton's log

Background: Calling Convention 본문

Pwnable/Study

Background: Calling Convention

Iriton 2024. 4. 1. 21:41

*본 포스트는 Dreamhack - Systemhacking Lecture 을 참고하여 작성되었습니다.

함수 호출 규약

함수의 호출 및 반환에 대한 약속을 말한다.

 

한 함수에서 다른 함수를 호출할 때, 프로그램의 실행 흐름은 다른 함수로 이동한다. 그리고 호출한 함수가 반환하면 다시 원래의 함수로 돌아와서 기존의 실행 흐름을 이어나간다. 따라서 함수를 호출할 때는 반환된 이후를 위해 호출자(Caller)의 상태(Stack frame) 및 반환 주소(Return Address)를 저장해야 한다. 또한, 호출자는 피호출자가 요구하는 인자를 전달해줘야 하며, 피호출자의 실행이 종료될 때는 반환 값을 전달받아야 한다.

 

함수 호출 규약을 적용하는 것은 일반적으로 컴파일러의 몫이다. 컴파일러가 호출 규약에 맞게 코드를 컴파일한다. 호출 규약은 여러 가지가 있는데 프로그래머가 코드에 명시하지 않는다면 컴파일러는 지원하는 호출 규약 중에서 CPU의 아키텍처에 적합한 것을 선택한다.

 

예를 들어 x86 아키텍처는 레지스터를 통해 피호출자의 인자를 전달하기에는 레지스터 수가 적으므로 스택으로 인자를 전달하는 규약을 사용한다. 반대로 x86-64 아키턱체에서는 레지스터가 많아서 적은 수의 인자는 레지스터만 사용해서 인자를 전달하고 인자가 너무 많을 때만 스택을 사용한다.

 

CPU의 아키텍처가 같아도 컴파일러가 다르면 적용하는 호출 규약이 다를 수 있다. C언어를 컴파일 할 때, 윈도우에서는 MSVC를 리눅스에서는 gcc를 많이 사용한다. 이 둘은 같은 아키텍처에 대해서도 다른 호출 규약을 적용한다.

 

x86-64 호출 규약: SYSV

리눅스는 SYSTEM V(SYSV) Application Binary Interface(ABI)를 기반으로 만들어졌다. 이는 ELF 포맷, 링킹 방법, 함수 호출 규약 등의 내용을 담고 있다. file 명령어를 이용하여 바이너리 정보를 살펴보면 아래와 같이 SYSV 문자열이 포함된 것을 확인할 수 있다.

SYSV에서 정의한 함수 호출 규약은 다음의 특징은 가진다.

  1. 6개의 인자를 RDI, RSI, RDX, RCX, R8, R9에 순서대로 저장하여 전달한다. 더 많은 인자를 사용해야 할 때는 스택을 추가로 이용한다.
  2. Caller에서 인자 전달에 사용된 스택을 정리한다.
  3. 함수의 반환 값은 RAX로 전달한다.

 

gdb 동적 분석

// Name: sysv.c
// Compile: gcc -fno-asynchronous-unwind-tables  -masm=intel \\
//         -fno-omit-frame-pointer -S sysv.c -fno-pic -O0

#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(); }
더보기

📍 gcc -fno-asynchronous-unwind-tables -masm=intel -fno-omit-frame-pointer -S sysv.c -fno-pic -O0

 

  1. -fno-asynchronous-unwind-tables : 비동기 언와인드 테이블을 생성하지 않도록 지정한다. 이 테이블은 실행 중인 프로그램의 스택 추적 정보를 저장하는 데 사용되며, 프로그램의 디버깅 및 예외 처리에 도움이 된다.
  2. -masm=intel : 인라인 어셈블리 및 어셈블리 소스 코드를 생성할 때 Intel 문법을 사용하도록 지정한다. Intel 문법은 AT$T 문법과 구문이나 표기법이 다르다.
  3. -fno-omit-frame-pointer : 컴파일러에게 프레임 포인터를 생략하지 않도록 지정한다.
  4. -S : 소스 코드를 어셈블리 코드로만 변환하고 컴파일 하지 않도록 지정한다.
  5. -fno-pic : Position Independent Code(PIC)를 생성하지 않도록 지정한다. 코드가 실행될 때 주소가 상대적으로 이동하지 않게 한다.
  6. -O0 : 최적화를 비활성화한다. 디버깅 용이성을 높일 수 있다.

 

인자 전달

 

gdb -q sysv로 디버깅 해준 뒤

caller 함수에 bp를 걸어서 r(run)으로 bp 전까지 실행시킨다.

DISASM을 보면 6개의 인자를 레지스터에 설정하고 있으며 caller+8에서는 7번째 인자를 스택으로 전달하는 걸 볼 수 있다.

 

callee 함수를 호출하기 전까지 실행하고 레지스터와 스택을 확인해 보기 위해서 calle를 호출하는 부분을 파악하고 해당 부분에 중단점을 설정한다.

 

si(step instruction) 명령어로 callee 내부로 들어갔다.

x/4gx $rsp 명령어로 스택을 확인해 보면 0x00005555555551bc가 반환 주소로 저장되어 있다.

 

해당 주소는 callee 호출 다음 명령어 주소란 걸 알 수 있다.

callee에서 반환되었을 때 이 주소를 꺼내어 원래의 실행 흐름으로 돌아갈 수 있다.

 

더보기

📍 메모리 조회 명령어

x : examine의 약어로 메모리 조회 명령 중 하나
/4gx : 형식 지정자. 8바이트씩 총 4개의 메모리 주소 출력하라는 의미

$rsp : 현재 스택 포인터. 현재 스택 프레임의 가장 위쪽 

/10i : 뒤에 나오는 메모리 주소부터 instruction 10개를 읽어오는 것을 의미

 

3. 스택 프레임 저장

x/9i $rsp 명령어로 callee 함수의 도입부를 살펴보면

push rbp를 통해 호출자 caller()의 rbp를 저장한다.

그리고 rbp를 rsp로 옮긴다. rbp는 스택 프레임의 가장 낮은 주소를 가리키는 포인터이고 rsp는 스택 프레임의 가장 높은 포인트이기 때문에 현재 스텍프레임(callee)에서 rsp는 caller의 rbp가 되어야 하는 것이다. 새로운 스택프레임이 생성되는 과정이라 보면 된다.

 

si를 두 번 해서 push rbp를 실행하고 스택을 확인해 보면 rbp 값이 저장된 것을 확인할 수 있다.

 

4. 스택 프레임 할당

mov rbp,rsp로 rbp와 rsp가 같은 주소를 가리키게 한다.

 

5. 반환값 전달

d 명령어는 현재 스택 프레임에서 지역 변수들의 정보를 보여준다.

아무것도 뜨지 않는 것을 보아 현재 스택 프레임에서는 지역변수를 사용하지 않은 것을 알 수 있다.

#여기서 하나 알 수 있는 건 아까 mov rbp, rsp 다음에 rsp 값을 조정하지 않았다.(rsp 값을 빼서 rbp와 rsp의 사이 공간을 새로운 스택 프레임으로 할당하는 것을 말한다.) 이는 지역변수를 사용하지 않으므로 새로운 스택 프레임을 만들지 않는다는 걸 말한다.

 

pop rbp 명령어를 실행하는 callee+90에 bp를 걸어두고 r 명령어로 실행한다. si를 두 번 실행하면 pop rbp로 돌아갈 함수의 스택 프레임을 다시 가져오고 ret으로 이전 함수로 돌아간다.

 

 

rbp와 rip 값을 확인하면 이전에 저장해둔 sfp과 반환 주소가 설정된 걸 확인할 수 있다.

 

 

x86 함수 호출 규약

함수호출규약 사용 컴파일러 인자 전달 방식 스택 정리 적용
stdcall MSVC Stack Callee WINAPI
cdecl GCC, MSVC Stack Caller 일반 함수
fastcall MSVC ECX, EDX Callee 최적화된 함수
thiscall MSVC ECX(인스턴스), Stack(인자) Callee 클래스의 함수

 

x86-64 함수 호출 규약

함수호출규약 사용 컴파일러 인자 전달 방식 스택 정리 적용
MS ABI MSVC RCX, RDX, R8, R9 Caller 일반 함수, Windows Syscall
System ABI GCC RDI, RSI, RDX, RCX, R8, R9, XMM0–7 Caller 일반 함수

'Pwnable > Study' 카테고리의 다른 글

Exploit Tech: Return Address Overwrite  (0) 2024.04.09
Memory Corruption: Stack Buffer Overflow  (0) 2024.04.01
Exploit Tech: Shellcode  (0) 2024.03.27
Tool: gdb & pwntools  (0) 2024.03.20
x86 Assembly  (0) 2024.03.19
Comments