チビCTF

前回チビCTFやりました。分科会の時間19:00 - 21:20くらいの長さでやりました。

問題

問題全体

結果(分科会終了時)

終了後の結果

優勝者はリモートから参戦したcookiesさんでした。分科会参加者としては、akouryyくんが一位でした。

なお、分科会終了後liesegang氏が全完しました。

Writeup

想定解をまとめておきます。

Flag test, Well Known, Substitution

点数取ってね枠。

Too weak

RSAだが鍵が短いので因数分解できる。sageに投げる(ここ、なんでこんなに早く因数分解できるか興味があって調べたので、あとで時間が余れば二体ふるい法の話します)。

Lambda Lambda Lambda

ラムダ計算で色々計算しようという問題。個人的にはこの問題が一番難しいと思っていたが(めんどくさいフェーズを含むという点もあるので)、解く人が多くて、難易度設定は分からないなぁという感じだった。

1問目

まだ+が使えるので書けばいい。

lambda x: x + 1

2問目

これ当初builtin functions消したつもりが消えてなくてΩ\ζ°)チーンだった。想定解は、

lambda x: x * (x >= 0) - x * (x < 0)

3問目

ここから+のような記号も使えなくしたので、題意としては、lambda計算だけでといて欲しいという問題。

[Third Challenge]
Can you calculate the f(x, y) = xor(x, y) without ^?
(type(x) == bool and type(y) == bool)
The code will be executed by
   eval('(' + code + ')(x)(y)(True)(False))'
provided that...
 TRUE = lambda t: lambda f: t
 FALSE = lambda t:lambda f: f
  x in [TRUE, FALSE] and y in [TRUE, FALSE] == True
Beaware that in the scope of your code there is no TRUE/FALSE, so you cannot use
TRUE/FALSE value in the server code(please write yourself

によって送ったコードが実行されるので、Church Bool値におけるxorを計算できるlambda式を送り込めという問題。

想定解は、

lambda a: lambda b: a(b(lambda t: lambda f: f)(lambda t: lambda f: t))(b(lambda
t: lambda f: t)(lambda t: lambda f: f))

4問目

最後はYコンビネータを用いて、階乗を計算する問題。Pythonの評価戦略に気をつける必要があるが、η変換で対応する。

Y = "lambda f: (lambda x: (lambda z: f(x(x))(z)))((lambda x: (lambda z:
f(x(x))(z))))"
TRUE   = "lambda t: lambda f: t"
FALSE  = "lambda t: lambda f: f"
SUCC   = "lambda n: lambda s: lambda z: s(n(s)(z))"
ZERO   = "lambda s: lambda z: z"
MUL    = "lambda n: lambda m: lambda s: n(m(s))"
ISZERO = "lambda n: n(lambda b: FALSE)(TRUE)"
MKPAIR = "lambda x: lambda y: lambda f: f(x, y)"
FST    = "lambda p: p(lambda x, y: x)"
SND    = "lambda p: p(lambda x, y: y)"
NEXT   = "lambda p: MKPAIR(SUCC(FST(p)))(FST(p))"
PRED   = "lambda n: SND(n(NEXT)(MKPAIR(ZERO)(ZERO)))"
MINUS  = "lambda m: lambda n: n(PRED(m))"
ONE    = "SUCC(ZERO)"
XOR = "lambda a: lambda b: a(b(FALSE)(TRUE))(b(TRUE)(FALSE))"

q1 = "lambda x: x+ 1"
q2 = "lambda x: x * (x >= 0) - x * (x < 0)"
q3 = XOR.replace("TRUE", TRUE).replace("FALSE", FALSE)

FACTGEN   = "lambda f: lambda n: ISZERO(n)(ONE)(MUL(n)(lambda z:
f(PRED(n))(z)))"

FACT = "(%s)(%s)" % (Y, FACTGEN)

FACT = FACT.replace("ONE", "(" + ONE + ")")
FACT = FACT.replace("MINUS", "(" + MINUS + ")")
FACT = FACT.replace("PRED", "(" + PRED + ")")
FACT = FACT.replace("NEXT", "(" + NEXT + ")")
FACT = FACT.replace("SND", "(" + SND + ")")
FACT = FACT.replace("FST", "(" + FST + ")")
FACT = FACT.replace("MKPAIR", "(" + MKPAIR+ ")")
FACT = FACT.replace("ISZERO", "(" + ISZERO+ ")")
FACT = FACT.replace("MUL", "(" + MUL + ")")
FACT = FACT.replace("ZERO", "(" + ZERO+ ")")
FACT = FACT.replace("SUCC", "(" + SUCC + ")")
FACT = FACT.replace("TRUE", "(" + TRUE + ")")
FACT = FACT.replace("FALSE", "(" + FALSE + ")")
q4 = FACT
print('\n'.join([q1,q2,q3,q4]))

Hack The Starry

Pwn的なタグをつけたが、Pwn要素はあまりない。 Starryのオンライン言語処理系のようなPython実装がサーバーで動いている。この時、stackの一番下から一つ上のあたりにフラグが置いてある。 

イメージ

普通にpushをしてしまうと当然フラグは見えなくなってしまうが、どうしよう、という問題。

この問題、バックエンドで動いているプログラムがPythonで、ソース公開してしまうか迷ったが、まぁ2時間半しかないしな、という気持ちで公開した。

このプログラムを見ると、swap/rotateを行う実装が明らかにやばいことがわかる

def swap():
    esp = stack[0]
    stack[esp], stack[(esp + 1) % len(stack)] =\
    stack[(esp + 1) % len(stack)],  stack[esp]

def rotate():
    esp = stack[0]
    stack[esp], stack[(esp + 1) % len(stack)], stack[(esp + 2) % len(stack)] =\
    stack[(esp + 1) % len(stack)],  stack[(esp + 2) % len(stack)], stack[esp]

espが一番下のとき(N-1とすると)、swapをすると、0番地とN-1番地のスワップが発生し、結果として、espとして使っているstackの一番上の値を書き換えることが可能になる。これがわかればあとは頑張ってespをフラグの文字列が始まるところへ合わせればよく、想定解は次のようなStarryのプログラムを送り込むこと。

  +    +    +    +    +
            + +  *               +  *         + +  *         +* * + + +  + . . .
                 . . . . . . . . . . . . . . . .
-% nc moratorium08.tech 1337
*** Starry Sky Machine ***
Please paste your beautiful code!

  +    +    +    +    +
            + +  *               +  *         + +  *         +* * + + +  + . . .
                 . . . . . . . . . . . . . . . .
---------------STDOUT---------------
TSG{St4rRYsKY}
------------STDOUT[END]-------------

Good Bye

やっていることは

code = ""
code += swap()
code += pop()
code += pop()
code += pop()
code += pop()
code += pushx(100)
code += dup()
code += mul()
code += mulx(10)
code += subx(20)
code += dup()
code += dup()
code += dup()
code += swap()
code += print_ascii()
code += print_ascii()
code += print_ascii()
code += print_ascii()
code += print_ascii()
code += print_ascii()
code += print_ascii()
code += print_ascii()
code += print_ascii()
code += print_ascii()
code += print_ascii()
code += print_ascii()
code += print_ascii()
code += print_ascii()
code += print_ascii()
code += print_ascii()
code += print_ascii()
code += print_ascii()
code += print_ascii()

(mulxやsubxはみやすさのためでStarryの機能ではない、内部でpush + mulに置き換えている)

Oracle from the Sun

予定では、独自暗号的なのを実装しようと思っていたもののうまく作れず問題が自明になってしまい悲しい気持ちになった結果作られたPadding Oracle Attackする問題。

設定としてはやや不自然でまぁPadding Oracle Attackしてくださいという感じなので、Padding Oracle Attackをすれば解けるはず。鯖が落ちまくって申し訳ない・・・。

とりあえず、adminとしてznF4eyji5noT0Ge4TitnYsTH9yly0C2MKhVVwnf5i/M=を投げると、ivがもらえるので、あとは頑張って復元をする。

概要は第6回資料をみてほしい。

以下スクリプト

# coding:utf-8
from __future__ import division, print_function
from Crypto.Cipher import AES
import base64 as b64
from pwn import remote

host = "13.113.155.57"
port = 3500
r = remote(host, port)

def decrypt_ok(s):
    r.recvuntil("> ")
    r.sendline("2")
    r.recvuntil("name?\n")
    r.sendline("admin")
    r.recvuntil("secret))\n")
    r.sendline(b64.b64encode(s))
    ret = r.recvline()
    #print(ret[0] == "D")
    if ret[0] == "D":
        return False
    else:
        print(True)
        return True

s = "znF4eyji5noT0Ge4TitnYsTH9yly0C2MKhVVwnf5i/M="

encrypted = b64.b64decode(s)

def seq2str(seq):
    return ''.join(map(chr, seq))

def set_iv_seq(seq, p, e, cnt):
    for i in range(cnt - 1):
        idx = 15 - i
        seq[idx] = cnt ^ ord(p[idx]) ^ ord(e[idx])


blocks = [b64.b64decode("6d4wVKCTk8UaxjwRbjQelw==")]
ret = ""
for i in range(len(encrypted) // 16):
    blocks.append(encrypted[16 * i : 16*(i+1)])

for k in range(1, len(blocks)):
    iv_seq = [0 for i in range(16)]
    plains = ["\x00" for i in range(16)]
    for i in range(16):
        for j in range(256):
            set_iv_seq(iv_seq, plains, blocks[k-1], i + 1)
            iv = seq2str(iv_seq)
            data = iv + blocks[k]
            if decrypt_ok(data):
                plains[15 - i] = chr(iv_seq[15 - i] ^ ord(blocks[k-1][15-i]) ^
(i + 1))
                break
            else:
                iv_seq[15 - i] += 1

    ret += ''.join(plains)
print(ret[:-ord(ret[-1])])