Tiny CTF
チビ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])])