본문 바로가기

SYSTEM HACKING

[SWING] Pwnable 02 - 함수 호출

함수 호출

함수 호출 규약은 함수의 호출과 반환에 대한 약속이다.

함수를 호출하거나 반환할 때 프로그램의 실행 흐름은 다른 함수로 이동한다.

함수를 호출할 때 호출자(Caller)의 상태(Stack Frame) 및 반환 주소(Return Address)를 저장하는데,

이는 함수가 호출한 함수가 반환되고 이동했던 실행 흐름이 다시 원래의 함수로 돌아올 때를 위함이다.

호출자(Caller)는 피호출자(Callee)가 요구하는 인자를 전달해주고, 피호출자의 실행이 종료될 때 반환 값을 전달받는다.

 

컴파일러가 작성된 코드를 컴파일할 때, 코드에 명시된 혹은 CPU 아키텍처에 적합하며 지원되는 함수 호출 규약을 적용한다.

 

아키텍처

x86 (32bit) 아키텍처: 레지스터가 적다 -> 스택으로 피호출자의 인자를 전달

x86-64 아키텍처: 레지스터가 많다 -> 인자가 너무 많을 때만 스택 사용

 

C언어 컴파일러와 호출 규약

윈도우: MSVC & MS x64

리눅스: gcc & SYSTEM V

 

x86 호출 규약

stdcall

fastcall

thiscall

cdecl

 

 

x86-64 호출 규약

MS ABI의 Calling Convention

System V AMD64 ABI의 Calling Convention

리눅스는 SYStem V Application Binary Interface를 기반으로 만들어진다.

SYSV ABI는 ELF 포맷, 링킹 방법, 함수 호출 규약 등의 내용을 담고 있다.

 

 

1. 인자 6개를 RDI, RSI, RDX, RCX, R8, R9에 순서대로 저장하여 전달한다. 인자가 더 많을 때는 스택을 추가로 이용한다.

2. 호출자에서 인자 전달에 사용된 스택을 정리한다.

3. 함수의 반환 값은 RAX로 전달된다.

 

컴파일

 

1. 인자 전달

 

 

gdb로 sysv 로드

중단점 설정하여 caller 함수 실행

 

 

caller+10 ~ caller+37: 6개의 인자를 각각의 레지스터에 설정

caller+8: 7번째 인자 7을 스택으로 전달

 

callee 함수 호출 전까지 실행하고 레지스터와 스택을 보자.

caller()의 디스어셈블된 코드에서 callee()를 호출하는 부분을 파악해 중단점 설정

프로그램을 실행하다가 callee() 호출 직전에 멈춤

 

 

callee(123456789, 2, 3, 4, 5, 6, 7) 함수 호출

-> rdi, rsi, rdx, rcx, r8, r9, rsp 에 인자들이 순서대로 설정

 

2. 반환 주소 저장

 

si > 한 단계 더 실행

call  실행 후 스택에 저장된 반환 주소 0x5555555551bc = callee 호출 다음 명령어의 주소

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

 

 

3. 스택 프레임 저장

 

callee 함수의 도입부(Prologue)

push rbp > caller() rbp(=SFP, 스택 프레임의 가장 낮은 주소를 가리키는 포인터) 저장

callee에서 반환될때 SFP 꺼내어 caller의 스택 프레임으로 돌아갈 수 있음

 

si > push rbp 실행

rbp 0x00007fffffffe050 저장됨

 

 

 

 

 

4. 스택 프레임 할당

 

mov rbp, rsp > rbp와 rsp가 같은 주소를 가리키게 함

그리고 rsp 값을 빼면 rbp와 rsp 사이 공간을 새로운 스택 프레인으로 할당 but callee 함수는 지역 변수를 사용하지 않으므로 새로운 스택 프레임을 만들지 않음

 

 

5. 반환값 전달

 

덧셈 연산을 마치고 종결부(Epilogue)에 도달하면 반환값을 rax에 옮김

반환 직전에 rax 출력 > 전달한 인자 7개의 합 = 123456789123456816

 

 

6. 반환

 

저장해뒀던 스택 프레임과 반환 주소를 꺼내며 반환

callee 함수가 스택 프레임을 만들지 않았기에 pop rbp > 스택 프레임 꺼냄

(but 일반적으론 leave로 스택 프레임 꺼냄)

 

ret > 호출자로 복귀

SFP = rbp

반환 주소 = rip