일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 26 | 27 | 28 |
29 | 30 | 31 |
- BOJ Python
- 자료구조 복습
- Javascript
- 드림핵
- 백준
- 숙명여자대학교 정보보안동아리
- 웹페이지 만들기
- 숙명여자대학교 정보보안 동아리
- The Loard of BOF
- 기계학습
- Python
- BOJ
- Sookmyung Information Security Study
- c++
- hackctf
- HTML
- CSS
- 생활코딩
- 머신러닝
- WarGame
- XSS Game
- lob
- siss
- hackerrank
- PHP 웹페이지 만들기
- 파이썬
- C언어
- SWEA
- 풀이
- c
- Today
- Total
혜랑's STORY
[System Hacking STAGE 4] Background: Calling Convention 본문
[System Hacking STAGE 4] Background: Calling Convention
hyerang0125 2022. 1. 26. 13:08함수 호출 규약
함수 호출 규약은 함수의 호출 및 반환에 대한 약속이다. 한 함수에서 다른 함수를 호출할 때, 프로그램의 실행 흐름은 다른 함수로 이동한다. 그리고 호출한 함수가 반환하면, 다시 원래의 함수로 돌아와서 기존의 실행 흐름을 이어나간다. 그러므로 함수를 호출할 때는 반환된 이후를 위해 호출자(caller)의 상태(Stack frame) 및 반환 주소(Return Address)를 저장해야 한다. 또한, 호출자는 피호출자(Callee)가 요구하는 인자를 전달해줘야 하며, 피호출자의 실행이 조요될 때는 반환 값을 전달 받아야 한다.
함수 호출 규약을 적용하는 것은 일반적으로 컴파일러의 몫이다. 그러나 컴파일러의 도움 없이 어셈블리 코드를 작성하려 하거나, 또는 어셈블리로 작성된 코드를 읽고자 한다면 함수 호출 규약을 알아야 할 필요가 있다. 시스템 해킹에서는 이 둘 모두 필수적인 기술이다.
x86호출 규약: cdecl
x86아키텍처는 레지스터의 수가 적으므로, 스택을 통해 인자를 전달한다. 또한, 인자를 전달하기 위해 사용한 스택을 호출자가 정리하는 특징이 있다. 스택을 통해 인자를 전달할 때는, 마지막 인자부터 첫 번째 인자까지 거꾸로 스택에 push한다.
// Name: cdecl.c
// Compile: gcc -fno-asynchronous-unwind-tables -nostdlib -masm=intel \
// -fomit-frame-pointer -S cdecl.c -w -m32 -fno-pic -O0
void __attribute__((cdecl)) callee(int a1, int a2){ // cdecl로 호출
}
void caller(){
callee(1, 2);
}
어셈블리어로 컴파일한 후 확인해보자.
각 의미는 이렇게 볼 수 있다.
; Name: cdecl.s
.file "cdecl.c"
.intel_syntax noprefix
.text
.globl callee
.type callee, @function
callee:
nop
ret ; 스택을 정리하지 않고 리턴합니다.
.size callee, .-callee
.globl caller
.type caller, @function
caller:
push 2 ; 2를 스택에 저장하여 callee의 인자로 전달합니다.
push 1 ; 1를 스택에 저장하여 callee의 인자로 전달합니다.
call callee
add esp, 8 ; 스택을 정리합니다. (push를 2번하였기 때문에 8byte만큼 esp가 증가되어 있습니다.)
nop
ret
.size caller, .-caller
.ident "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
.section .note.GNU-stack,"",@progbits
x86-64호출 규약: SYSV
리눅스는 SYSTEM V(SYSV) Application Binary Interface(ABI)를 기반으로 만들어졌다. SYSV ABI는 ELF 포맷, 링킹 방법, 함수 호출 규약 등의 내용을 담고 있다. file 명령어를 이용하여 바이너리의 정보를 살펴보면, 아래와 같이 SYSV 문자열이 포함된 것을 확인할 수 있다.
SYSV에서 정의한 함수 호출 규약에는 다음 특징이 있다.
- 6개의 인자를 RDI, RSI, RDX, RCX, R8, R9에 순서대로 저장하여 전달한다. 더 많은 인자를 사용해야 할 때는 스택을 추가로 이용한다.
- Caller에서 인자 전달에 사용된 스택을 정리한다.
- 함수의 반환 값은 RAW로 전달한다.
// 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
int 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(); }
SYSV 상세 분석
SYSV는 앞으로 자주 접할 호출 규약이므로, 실행하며 gdb로 자세히 분석해보자. 먼저, sysv.c를 컴파일한다.
1. 인자 전달
gdb로 sysv를 로드한 후 중단점을 설정하여 caller 함수까지 실행한다.
context의 DISASM을 보면, caller+6부터 caller+33까지 6개의 인자를 각각의 레지스터에 설정하고 있으며, caller+4에서는 7번째 인자인 7을 스택으로 전달하고 있다.
callee 함수를 호출하기 전까지 실행하고, 레지스터와 스택을 확인해보자.
소스 코드에서 callee(123456789123456789, 2, 3, 4, 5, 6, 7)로 함수를 호출했는데, 인자들이 순서대로 rdi, rsi, rdx, rcx, r8, r9 그리고 [rsp]에 설정되어 있는 것을 확인할 수 있다.
2. 반환 주소 저장
si 명령어로 한 단계 더 실행시킨다.
call이 실행되고 스택을 확인해보면 0x555555554682가 반환 주소로 저장되어 있다. gdb로 확인해보면 0x555555554682는 callee 호출 다음 명령어의 주소이다. callee에서 반환됐을 때, 이 주소를 꺼내어 원래의 실행 흐름으로 돌아갈 수 있다.
3. 스택 프레임 저장
x/5i $rip 명령어로 callee 함수의 도입부(Prologue)를 살펴보면, 가장 먼저 push rbp를 통해 호출자의 rbp를 저장하고 있다. rbp가 스택프레임의 가장 낮은 주소를 가리키는 포인터이므로, 이를 Stack Frame Pointer(SFP)라고도 부른다. callee에서 반환될 때, SFP를 꺼내어 caller의 스택 프레임으로 돌아갈 수 있다.
si로 push rbp를 실행하고, 스택을 확인해보면 rbp값인 0x00007fffffffdf80가 저장된 것을 확인할 수 있다.
4. 스택 프레임 할당
이제 mov rbp, rsp로 rbp와 rsp가 같은 주소를 가리키게 한다. 바로 다음에 rsp의 값을 빼게 되면, rbp와 rsp의 사이 공간을 새로운 스택 프레임으로 할당하는 것이지만, callee 함수는 지역 변수를 사용하지 않으므로, 새로운 스택 프레임을 만들지 않는다.
si로 실행하고, 레지스터를 보면 이 둘이 같은 주소를 가리키는 것을 확인하 수 있다.
5. 반환값 전달
덧셈 연산을 모두 마치고, 함수의 종결부(Epilogue)에 도달하면, 반환값을 rax에 옮긴다. 반환 직전에 rax를 출력하면 전달한 7개 인자의 합인 1234567891234567816을 확인할 수 있다.
6. 반환
반환은 저장해뒀던 스택 프레임과 반환 주소를 꺼내면서 이루어진다. 여기서는 callee 함수가 스택 프레임을 만들지 않았기 때문에, pop rbp로 스택 프레임을 꺼낼 수 있지만, 일반적으로 leave로 스택 프레임을 꺼낸다.
스택 프레임을 꺼낸 뒤에는, ret로 호출자로 복귀한다. 앞에서 저장해뒀던 sfp로 rbp가 반환 주소로 rip가 설정된 것을 확인할 수 있다.
요약
x86 함수 호출 규약
x86-64 함수 호출 규약
'무지성 공부방 > Dreamhack SystemHacking' 카테고리의 다른 글
[System Hacking STAGE 4] Exploit Tech: Return Address Overwrite (0) | 2022.01.26 |
---|---|
[System Hacking STAGE 4] Memory Corruption: Stack Buffer Overflow (0) | 2022.01.26 |
[System Hacking STAGE 3] Tool: pwntools (0) | 2022.01.26 |
[System Hacking STAGE 3] Tool: gdb (0) | 2022.01.26 |
[System Hacking STAGE 2] shell_basic (0) | 2022.01.21 |