ふるつき

v(*'='*)v かに

SECCON Beginners CTF 2019 writeup

チームzer0ptsでSECCON Beginnners CTF 2019に参加し、5477点を獲得して1位でした。初心者としては上の方にいることが確認できてよかったと思います。チームメイトが優秀だったので、私は易しい問題ばかり解いていました。

[Crypto 115pts(192 solves)] [warmup]So Tired

最強の暗号を作りました。 暗号よくわからないけどきっと大丈夫!

File: so_tired.tar.gz

yoshikingが取り組んでいましたが、なぜか解けないようだったので代わりにやりました。ダウンロードしたファイルをみると、base64エンコードされていることがわかるので、base64 -d encrypted.txtとして復号すると表示不能文字の列になりました。file コマンドに食べさせてやるとzlibで圧縮されていることがわかりました。zlib-flate --uncompress で伸長またbase64文字列になったので、この時点で次のスクリプトを書きました。

from base64 import b64decode
from zlib import decompress

data = open("encrypted.txt", "rb").read()

i = 0
while True:
    try:
        if i % 2 == 0:
            data = b64decode(data)
        else:
            data = decompress(data)
        i += 1
    except:
        print(data)
        break

フラグは ctf4b{very_l0ng_l0ng_BASE64_3nc0ding} でした。zlibどこいった

[Crypto 223pts(96 solves)] Party

Let's 暗号パーティ

File: party.tar.gz

encrypt.pyをみると秘密分散法っぽいコードになっていることがわかります。ただし、剰余をとっていません。

from flag import FLAG
from Crypto.Util.number import bytes_to_long, getRandomInteger, getPrime


def f(x, coeff):
    y = 0
    for i in range(len(coeff)):
        y += coeff[i] * pow(x, i)
    return y


N = 512
M = 3
secret = bytes_to_long(FLAG)
assert(secret < 2**N)

coeff = [secret] + [getRandomInteger(N) for i in range(M-1)]
party = [getRandomInteger(N) for i in range(M)]

val = map(lambda x: f(x, coeff), party)
output = list(zip(party, val))
print(output)

剰余をとっていない場合にはラグランジュ補完によって各項を求めるのはできなさそうなので普通にz3で解きました。

from z3 import *

M = 3


def f(x, coeff):
    y = 0
    for i in range(len(coeff)):
        y += coeff[i] * pow(x, i)
    return y


s = Solver()
coeff = [Int("x:{}".format(i)) for i in range(M)]
for c in coeff:
    s.add(0 < c)
    s.add(c < (1 << 512))

encrypted = eval(open("encrypted", "r").read())
for x in encrypted:
    s.add(f(x[0], coeff) == x[1])

r = s.check()
if r == sat:
    m = s.model()
    print(m)
else:
    print(r)
    exit()

結果を見ると多項式の係数は↓で、x0を文字列に直してやるとctf4b{just_d0ing_sh4mir} とフラグが得られました。

[x:2 = 8559415203809303629563171044315478022492879973152936590413420646926860552595649298493153041683835412421908115002277197166850496088216040975415228249635834,
 x:1 = 6759741750199108721817212574266152064959437506612887142001761070682826541920627672362291016337903640265385249474489124882116454124173716091800442011015857,
 x:0 = 175721217420600153444809007773872697631803507409137493048703574941320093728]

[Reversing 57pts(414 solves)] [warmup]seccompare

https://score.beginners.seccon.jp/files/seccompare_44d43f6a4d247e65c712d7379157d6a9.tar.gz

IDAとかで開くと 74,66,34,62,7B,35,74,72,31,6E,67,73,5F,31,73,5F,6E,30,74,5F,65,6E,30,75,67,68,7D というバイト列がみえるのでえいって戻すと tf4b{5tr1ngs_1s_n0t_en0ugh} になっているのでどこかでこぼした cを補完して勝ち

[Reversing 186pts(120 solves)] Leakage

https://score.beginners.seccon.jp/files/leakage_80a8c3c2bd63254a033ea21093944b1e.tar.gz

IDAで見た感じそんなに複雑そうではないcrackmeだったので、angrに投げたら通りました。

import angr, claripy

p = angr.Project("leakage")


flag = claripy.BVS("sim_flag", 8 * 0x22)

state = p.factory.entry_state(args=[p.filename, flag])

for c in flag.chop(8):
    state.add_constraints(c >= " ")
    state.add_constraints(c <= "~")

sim = p.factory.simulation_manager(state)
sim.explore(find=0x4006AE, avoid=[0x4006BC])
print(sim.run())

import code

code.interact(local=locals())

フラグは ctf4b{le4k1ng_th3_f1ag_0ne_by_0ne}だったので、本当はちゃんと解析する問題だったのだと思います。

[Reversing 293pts (62 solves)]Linear Operation

https://score.beginners.seccon.jp/files/linear_operation_a45530bbfc995ac99f30e026276674aa.tar.gz

こちらも普通のcrackmeのようでしたが、IDAでみてみると無駄に長くて規則的な処理をしていたので、問題名と合わせてもangrの領域だと判断して投げました。

import angr, claripy

p = angr.Project("linear_operation")


flag = claripy.BVS("sim_flag", 8 * 0x63)

state = p.factory.entry_state(stdin=flag)

for c in flag.chop(8):
    state.add_constraints(c >= " ")
    state.add_constraints(c <= "~")

sim = p.factory.simulation_manager(state)
sim.explore(find=0x40CF78, avoid=[0x40CF86])
print(sim.run())

import code

code.interact(local=locals())

フラグはctf4b{5ymbol1c_3xecuti0n_1s_3ffect1ve_4ga1nst_l1n34r_0p3r4ti0n}でした。こちらは想定解法だったようです。