チーム「卍最強卍」でOptimisticと2人で参加して62位でした。問題をそれなりに残しておくつもりだったけど結構解いちゃってごめん。久々の割には善戦したんじゃなかろうか?
熱海とポルトガルで検索すると姉妹都市らしい。ほぼ決め打ちでやったらあたってウケた。記念碑をつけて適当にググると
写真が貼られたサイト
が見つかる。
TsukuCTF25{2014/06/06}
曲がってるエレベーターは維持の難しさなどからあまりないことは一般に知られているのはその通り。検索すると、さすがの三菱なんですね(
サイト
)。
写真を見る感じ、美術館系っぽい?きがする。と思ったら、
全設置箇所まとめのページ
を発見。目grepすると、神奈川県のランドマークプラザっぽい。
TsukuCTF25{tokyo-esca.com}
を投げるがincorrect。あれ?
調べ直して、
TsukuCTF25{yokohama-landmark.jp}
を投げてcorrect。
情報がなさすぎる... ので、頑張って点字を解読する。
とーきょーと きゅーせき まさかどづか
東京都旧跡将門塚!!!!!ということで、あとは調べるだけ。
TsukuCTF25{35.6873_139.7627}
写真右端にOTi... Fried Chikenという看板が見えるのでググる。一発で見つかっていい感じ(
link
)。ジャカルタのチェーン店らしい。
長めの道で、奥が多分T字路になっているっぽいので、それを念頭に探す。日本人のCTFなので、おそらく都市部かつ大通りだろうという予想。
めちゃくちゃ頑張って探すと、見つかる
。
TsukuCTF25{-7.3189_110.4971}
。
わからないのでほぼ丸投げ。ある程度調べてもらったやつのリンクを眺めながらそれっぽそうなのを適当に投げていたらあたった(あの?)
あー、これなんだっけ。と言いながらAI丸投げ 。
...
しかし、これは機能しません。実際には、JSONパーサーを使用して配列を作成する場合、配列のlength
は常に非負になります。
実際の解法は、配列のプロトタイプを操作してlength
プロパティをオーバーライドすることですが、JSONパースだけではこれは不可能です。
あ、待って!JSONで有効ではない構文を使用してJSON.parse
を失敗させ、ソースコードを確認できるエラーを発生させる必要があります。
いや、実際の解法はJSON.parse
が配列以外のものを返すようにすることです!
...
勝手に自分で解決してた。
❯ curl -X POST -d 'array={"length":-1}' http://challs.tsukuctf.org:28888
TsukuCTF25{l4n_l1n_lun_l4n_l0n}%
完全に専門外なので予定はなかったけど、MMRZの作問分があるはずなので一個ぐらいは解きたいなぁのきもちで取り組む。
ヴィジュネル暗号の実装らしい。tsukuctfの部分が
変わらない
ということは、その部分がおなじになるように設定されている、ということ。
\(p\)
と
\(p+k\)
を26で割ったあまりが等しくないといけないので、
\(p, k < 26\)
に注意すると
\(k=0\)
しかありえない。つまり、暗号の
tsukuctf
の部分は
a
が連続しているはず。あと、keyの文字数が足りなくなった時点で暗号文をkeyとして再利用している。これに
a
が連続する部分があるから、その部分は平文が残っているはず。
...ん?
tsukuctf
も、
a
が連続する部分も8文字なんだよなぁ。これはつまり、周期が8であることを示唆している?じゃないと暗号文が
...aaaaaa aa tsukuctf...
にはならないはず。実際に前から8文字で区切っていくとそこが境目になるし、これで決め打って良さそう。はじめの8文字を除いてkeyと暗号文がわかったことになるので、デコーダを書く。
ciphertext="ayb wpg uujmz pwom jaaaaaa aa tsukuctf, hj vynj? mml ogyt re ozbiymvrosf bfq nvjwsum mbmm ef ntq gudwy fxdzyqyc, yeh sfypf usyv nl imy kcxbyl ecxvboap, epa 'avb' wxxw unyfnpzklrq."
import string
cipher_only_acsii=[]
for c in ciphertext:
if c in string.ascii_lowercase:
cipher_only_acsii.append(c)
key = cipher_only_acsii[0:8]
def f(c, k):
base = ord('a')
ci = ord(c) - base
ki = ord(k) - base
p = (ci-ki+26)%26
return chr((base+p))
idx = 0
ans=[]
for c in ciphertext[10:]:
if c not in string.ascii_lowercase:
ans.append(c)
continue
p = f(c, key[idx])
ans.append(p)
key[idx] = c
idx+=1
idx%=8
print(''.join(ans))
"""
❯ python3 dec.py
joy this problem or tsukuctf, or both? the flag is concatenate the seventh word in the first sentence, the third word in the second sentence, and 'fun' with underscores.
"""
ということで、flagは
TsukuCTF25{tsukuctf_is_fun}
。
tsukuctf
の部分が変わらないなぁじゃないよ。絶対作るの大変だったでしょこれ。
Xor shiftの部分だけ取り出して適当なseedで試してみると、どうも280回で一周回るっぽい?で、300回の試行があるので
- 280回に渡って任意の手を出し続けて、その結果から順序を取り出す
- 十分に溜まったら、それに対応する手を出し続ける
をすればよい。
久々にpwntoolsを使ったので仕様に頭を悩ませつつ、AIに助けてもらいつつ、なんとか書き上げる。
trace = []
idx = 0
WIN = b"Tsukushi: You win!\n"
DRAW = b"Tsukushi: Draw! ...But If you wanna get the flag, you have to win 294 rounds in a row.\n"
ENDGAME = b"Tsukushi: You won 294 times in a row?! That's incredible!\n"
def janken_gen() -> int:
global idx
if len(trace) < 280:
return 0
else:
res = (trace[idx]+1)%3
idx += 1
idx %= 280
return res
from pwn import *
#io = process(['uv', 'run', 'server.py'], env={'FLAG':'STATUS_OK'})
io = remote('challs.tsukuctf.org', 30057)
while True:
isEnd = False
while True:
print(io.recvuntil(b"Rock, Paper, Scissors... Go! (Rock: 0, Paper: 1, Scissors: 2): "))
p = janken_gen()
io.send(str(p)+'\n')
if len(trace) < 280:
# 結果をもとに相手の手を復元
result = io.recvline()
if result == WIN:
trace.append(2)
elif result == DRAW:
trace.append(0)
break
else:
trace.append(1)
break
else:
io.recvline()
result = io.recvline()
if result == ENDGAME:
isEnd = True
break
if isEnd:
break
print(io.recvall())
❯ uv run exploit.py
...
[+] Receiving all data: Done (95B)
[*] Closed connection to challs.tsukuctf.org port 30057
b'Tsukushi: So, here is the flag. TsukuCTF25{4_xor5h1ft_15_only_45_good_45_1t5_5h1ft_p4r4m3t3r5}\n'
(xortsukushift)