
(내용이 정확하지 않을 수 있습니다! 틀린 부분이 있다면 댓글을 통해 알려주세요.)
계속 스택 볼 때 뭔가 헷갈려서, 이 참에 확실하게 정리하고 가려 한다.
일단 스택 프레임에 대한 이해가 필요한데, 링크로 대체한다.
- 이 문서에서의 SFP = *rbp이며 RET = *(rbp-0x8)입니다. (ret이랑 구분)
오늘 준비한 간단한 코드이다. add()의 return값 + 1을 출력해준다. gcc 7.5.0로 컴파일했다.
//gcc -o hello hello.c
#include <stdio.h>
int add(){
int b = 3;
int c = 5;
return b + c;
}
int main(){
int a = 1;
printf("%d", add() + 1);
}
Function Prologue
gef➤ disas main
0x0000555555400666 <+0>: push rbp
0x0000555555400667 <+1>: mov rbp,rsp
0x000055555540066a <+4>: sub rsp,0x10
0x000055555540066e <+8>: mov DWORD PTR [rbp-0x4],0x1
0x0000555555400675 <+15>: mov eax,0x0
0x000055555540067a <+20>: call 0x55555540064a <add>
0x000055555540067f <+25>: add eax,0x1
0x0000555555400682 <+28>: mov esi,eax
0x0000555555400684 <+30>: lea rdi,[rip+0x99] # 0x555555400724
0x000055555540068b <+37>: mov eax,0x0
0x0000555555400690 <+42>: call 0x555555400520 <printf@plt>
0x0000555555400695 <+47>: mov eax,0x0
0x000055555540069a <+52>: leave
0x000055555540069b <+53>: ret
call 0x55555540064a <add>
add를 call하는 부분에서 멈춰보자.
call은 두 가지 역할을 수행한다고 한다.
- RSP = RSP - 8로 만들고, 이 위치에 다음 명령어의 주소를 저장한다.
- add의 주소로 jmp한다.
$rsp : 0x00007fffffffde80 → 0x00007fffffffdf70 → 0x0000000000000001
$rbp : 0x00007fffffffde90 → 0x00005555554006a0 → <__libc_csu_init+0> push r15
$rip : 0x000055555540067a → <main+20> call 0x55555540064a <add>
$rsp : 0x00007fffffffde78 → 0x000055555540067f → <main+25> add eax, 0x1
$rbp : 0x00007fffffffde90 → 0x00005555554006a0 → <__libc_csu_init+0> push r15
$rip : 0x000055555540064a → <add+0> push rbp
보면 rsp가 8 빠지고 그 위치에 0x000055555540067f가 저장된 것을 볼 수 있다. 이게 그 유명한 RET인듯 하다
그리고 rip가 add로 들어왔다.
gef➤ disas add
0x000055555540064a <+0>: push rbp
0x000055555540064b <+1>: mov rbp,rsp
0x000055555540064e <+4>: mov DWORD PTR [rbp-0x8],0x3
0x0000555555400655 <+11>: mov DWORD PTR [rbp-0x4],0x5
0x000055555540065c <+18>: mov edx,DWORD PTR [rbp-0x8]
0x000055555540065f <+21>: mov eax,DWORD PTR [rbp-0x4]
0x0000555555400662 <+24>: add eax,edx
0x0000555555400664 <+26>: pop rbp
0x0000555555400665 <+27>: ret
push rbp
다음으로 push rbp에서 멈춰보자.
$rsp : 0x00007fffffffde78 → 0x000055555540067f → <main+25> add eax, 0x1
$rbp : 0x00007fffffffde90 → 0x00005555554006a0 → <__libc_csu_init+0> push r15
$rsp : 0x00007fffffffde70 → 0x00007fffffffde90 → 0x00005555554006a0 → <__libc_csu_init+0> push r15
$rbp : 0x00007fffffffde90 → 0x00005555554006a0 → <__libc_csu_init+0> push r15
rsp가 또 8 빠지고 rbp가 저장되었다.
여기서 추측할 수 있는 두 가지 :
1: push <somthin> → rsp를 8 빼고 그 위치에 somthin을 저장한다. (64bit만 그런 듯 하다.)
2: push rbp는 기존 스택 프레임의 rbp(SFP)를 push하고, 다시말해 rbp에 SFP가 저장된다
mov rbp, rsp
$rsp : 0x00007fffffffde70 → 0x00007fffffffde90 → 0x00005555554006a0 → <__libc_csu_init+0> push r15
$rbp : 0x00007fffffffde90 → 0x00005555554006a0 → <__libc_csu_init+0> push r15
Before
$rsp : 0x00007fffffffde70 → 0x00007fffffffde90 → 0x00005555554006a0 → <__libc_csu_init+0> push r15
$rbp : 0x00007fffffffde70 → 0x00007fffffffde90 → 0x00005555554006a0 → <__libc_csu_init+0> push r15
After
rsp의 값을 rbp에 넣어준다. 이로써 rsp와 rbp는 같은 위치(rsp)를 가리킨다.

+ 스택에 값 넣는것도 구경하세요 ㅎㅎ
이제 스택에 값이 들어가는 것도 보자.
0x000055555540064e <+4>: mov DWORD PTR [rbp-0x8],0x3
0x0000555555400655 <+11>: mov DWORD PTR [rbp-0x4],0x5
gef➤ x/30gx $rbp-0x8
0x7fffffffde68: 0x0000000500000003 0x00007fffffffde90
0x7fffffffde78: 0x000055555540067f 0x00007fffffffdf70
그냥 이렇게 값 넣어준다.
add+18~24는 그냥 계산이다.
0x000055555540065c <+18>: mov edx,DWORD PTR [rbp-0x8]
0x000055555540065f <+21>: mov eax,DWORD PTR [rbp-0x4]
0x0000555555400662 <+24>: add eax,edx
0x0000555555400664 <+26>: pop rbp
0x0000555555400665 <+27>: ret
Function Epilogue
나는 함수 에필로그를 보고싶다.
0x0000555555400664 <+26>: pop rbp
0x0000555555400665 <+27>: ret
따라서 이 부분에 집중해보자.
pop rbp
$rsp : 0x00007fffffffde70 → 0x00007fffffffde90 → 0x00005555554006a0 → <__libc_csu_init+0> push r15
$rbp : 0x00007fffffffde70 → 0x00007fffffffde90 → 0x00005555554006a0 → <__libc_csu_init+0> push r15
$rsp : 0x00007fffffffde78 → 0x000055555540067f → <main+25> add eax, 0x1
$rbp : 0x00007fffffffde90 → 0x00005555554006a0 → <__libc_csu_init+0> push r15
보자. rbp에 rsp에 있던 값(SFP)이 들어왔다. 이후 rsp가 +8되면서 RET를 가리키는 것도 볼 수 있다. 이로써 add 스택이 정리가 된 것 같다.
(pop <somthin> : somthin에 rsp의 값을 저장하고 rsp + 8)
ret
ret는 두 가지 명령어로 이루어져있다.
pop rip
jmp rip
$rsp : 0x00007fffffffde78 → 0x000055555540067f → <main+25> add eax, 0x1
$rbp : 0x00007fffffffde90 → 0x00005555554006a0 → <__libc_csu_init+0> push r15
$rip : 0x0000555555400665 → <add+27> ret
Before
$rsp : 0x00007fffffffde80 → 0x00007fffffffdf70 → 0x0000000000000001
$rbp : 0x00007fffffffde90 → 0x00005555554006a0 → <__libc_csu_init+0> push r15
$rip : 0x000055555540067f → <main+25> add eax, 0x1
After
따라서 rip에 rsp의 값(RET)을 넣어주고 그 값으로 뛰는 것을 볼 수 있다. pop이니 rsp는 +8될 것이다.

번외 : main의 Epilogue
마지막으로 main의 에필로그만 보자.
얘는 leave가 있다.
0x55555540069a <main+52> leave
0x55555540069b <main+53> ret
leave
leave는 다음 두 명령어로 구성된다.
mov rsp, rbp
pop rbp
$rsp : 0x00007fffffffde80 → 0x00007fffffffdf70 → 0x0000000000000001
$rbp : 0x00007fffffffde90 → 0x00005555554006a0 → <__libc_csu_init+0> push r15
Before
$rsp : 0x00007fffffffde90 → 0x00005555554006a0 → <__libc_csu_init+0> push r15
$rbp : 0x00007fffffffde90 → 0x00005555554006a0 → <__libc_csu_init+0> push r15
mov rsp, rbp
$rsp : 0x00007fffffffde98 → 0x00007ffff7a03bf7 → <__libc_start_main+231> mov edi, eax
$rbp : 0x00005555554006a0 → <__libc_csu_init+0> push r15
pop rbp (rbp에 rsp가 가리키는 값을 넣고, rsp를 +8하라는 뜻)
이로써 rbp는 main 스택 프레임의 SFP가 가리키는 위치 (main 스택 프레임 아래 프레임의 SFP 위치)로 가고, rsp는 RET을 가리키게 된다.
ret
이 상태에서 ret을 하면.. rip가 rsp에 있는 값, 즉 RET이 가리키는 위치(__libc_start_main 어딘가)로 뛰게 된다.
Reference : https://ls-toast.tistory.com/10