高専セキュコンに出た #SCKOSEN
10/21-22の高専セキュコンに初参加しました。@yfba_, @Akashi_SNさん, @Snow_poijioさんと、チームBiPhoneで出ました。
2位です。惜しい!
でもとても楽しかった!
問題一覧です。下の2つはKoH(King of the Hill)。BiPhoneが説いた問題#SCKOSEN pic.twitter.com/PonsKyIT8L
— Akashi_SN (@Akashi_SN) 2017年10月22日
Cryptoは1問も解いてません...RSAこわい...(((
ずっとzipのフォーマットを読んだりzipコマンドを調べたりしていました。
自分が解いた、計1300点の問題の考え方(writeup)をのこしておきます。直感で通したものが多いので。
Misc
準備体操〜
Network200「ファイル送信pcap」
チームの人に投げられた問題。
pcapをWiresharkで開き、「ファイル>オブジェクトをエクスポート」でLenna.png
とlock.zip
を保存します。
身体が unzip
を求めますが、パスワードがかかっています。
zipinfo
で中身を見てみると...Lenna.png
がありますね。 既知平文攻撃 が使えそうです。
$ zipinfo lock.zip Archive: lock.zip Zip file size: 474291 bytes, number of entries: 2 -rwxrwx--- 3.0 unx 473831 BX defN 13-Oct-04 07:42 Lenna.png -rwxrwx--- 3.0 unx 17 TX stor 17-Oct-21 01:51 flag.txt 2 files, 473848 bytes uncompressed, 473923 bytes compressed: 0.0%
pkcrack
で簡単に...
$ zip -0 Lenna.zip Lenna.png $ pkcrack -C lock.zip -c Lenna.png -p Lenna.png -P Lenna.zip -d output.zip
...しかしこれは失敗します。簡単じゃないですね。
10分ぐらい考えて、ある違いに気づきました。実際の競技の場面だと観察力がブーストされる気がする。
$ zipinfo Lenna.zip -rwxrwx--- 3.0 unx 473831 bx stor 17-Oct-21 13:23 Lenna.png $ zipinfo lock.zip -rwxrwx--- 3.0 unx 473831 BX defN 13-Oct-04 07:42 Lenna.png
時刻の前に書いてある defN
と stor
の部分が異なります。なんか怪しい。
zipinfo
でググッてみるとこのような記述を発見しました。
Finally, the sixth field indicates the compression method and possible sub-method used. There are six methods known at present: storing (no compression), reducing, shrinking, imploding, tokenizing (never publicly released), and deflating. In addition, thereare four levels of reducing (1 through 4); four types of imploding (4K or 8K sliding dictionary, and 2 or 3 Shannon-Fano trees); and four levels of deflating (superfast, fast, normal, maximum compression). zipinfo represents these methods and their sub-methods as follows: stor; re:1, re:2, etc.; shrk; i4:2, i8:3, etc.; tokn; and defS, defF, defN, and defX.
要約すると、「6列目には圧縮方式が書いてあるよ, storが無圧縮, def*はdeflateのことだよ」ということです。
pkcrack
がうまくクラックできないのもこれが原因ではないかと考え、次は圧縮アルゴリズムを変えて
$ zip Lenna.zip Lenna.png (-0を除いた)
とすればpkcrack
できました。SCKOSEN{k_p_t_a}
「lock.zipの圧縮率が0%だから -0 をつけて...」としたのがアダになりました。
Web200「Web1」
___ = window; __ = document; s = "shift"; m = ["addEventListener", "DOMContentLoaded", "createElement", "div", "textContent", "deobfuscate js and find the key", "body", "appendChild", "length", "join", "forEach", "parseInt", "toString", "toUpperCase"]; __[m[s]()](m[s](), () => { _ = __[m[s]()](m[s]()); _[m[s]()] = m[s](); __[m[s]()][m[s]()](_); (() => { __ = [18234125, 323835316891, 11523, 907531478812, 744387234, 44203240442235, 844002446169231, 4601, ]; ____ = { _: m[s](), ___: m[s]() }; ____.__ = `____` [ [____._] ]; __[m[s]()]((n, _) => { __[_] = `${___[m[s|s]](n,_+1)[m[s^s^1]](0O22<<1)[m[-~-~s]]()}{${n}}`; if (__[_][____["_"]] > 4) __[_] = Array(__[_][____["_"]])[____["___"]]("_"); }); console.log(__); })(); }, false);
コードがブラウザのコンソールの履歴に残ってましたw
この難読化されたjsを読み解く問題です。うまい具合に実行すれば一瞬なのかもしれませんが、手動置換で泥臭く解きました。
a["b"]()
がa.b()
と同じというところがポイント。
let ax = (() => { __ = [18234125, 323835316891, 11523, 907531478812, 744387234, 44203240442235, 844002446169231, 4601, ]; ____ = { _: "length", ___: "join", __: "_", }; __.forEach((n, idx) => { __[idx] = `${parseInt(n,idx+1).toString(36).toUpperCase()}{${n}}`; if (__[idx][3] > 4) __[idx] = Array(__[idx][3]).join("_"); }); return __; });
このように整形したあとax()
するとFLAGがでます。
SCKOSEN{44203240442235}
Web200「Web2」
0x52 0x54
はalert
のr t
なので、その辺りから適当に埋めていく。
call
の場所は自信がなかったけど何度か入れ替えて通した。
Network100「寝坊気味のコンピュータ」
.pcapng
をWiresharkで開くとWake On LANのパケットがいくつか入っている。
MACアドレスの部分にASCIIコードでFLAGが入っています。
SCKOSEN{wake_on_lan_is_alarm}
Binary500「OreNoFS」
2日目の朝4時に解いてチームメイトとウェーイした。魔剤を1本消費しました。面白い問題。pwnとか数学力の暴力よりもバイナリ読む方が好きかな〜
AllocationTable(AT)のそれぞれの値がどういう意味を表しているのか結構悩みました。ソートしたりいろいろ...
簡潔にまとめると、ファイルシステムにはAT(2B * 8192 = 4クラスタ)、ディレクトリエントリ(ATの次のクラスタに32byte)、データ(たくさん)がこの順番で入っています。データの入ったクラスタをATを使ってうまく並び替えて1つのファイルにするとFLAGが出ます。
1: binwalk (-Me) raw.dmg
しましたが当然 flag.png
は取り出せませんでした。
とりあえず zipアーカイブ と __MACOSX
があることがわかる。
2: raw.dmg
からOreNoFSのパーティションを取り出す(疲れからか.dmg
を気づかずダメージダメージと読んでいましたw)
問題分にもあるとおり、GPTなどを取り除かなければなりません。
StalkR's Blog: Defcon 18 CTF quals writeup - Forensics 100
The Sleuth Kit
あ!ctf4bでやったところだ! の mmls
というコマンドでパーティションの範囲を確かめ、dd
で取り出します。
いろいろ使えるdd便利。
$ mmls raw.dmg GUID Partition Table (EFI) Offset Sector: 0 Units are in 512-byte sectors Slot Start End Length Description 000: Meta 0000000000 0000000000 0000000001 Safety Table 001: ------- 0000000000 0000002047 0000002048 Unallocated 002: Meta 0000000001 0000000001 0000000001 GPT Header 003: Meta 0000000002 0000000033 0000000032 Partition Table 004: 000 0000002048 0000061439 0000059392 disk image <= これを取る! 005: ------- 0000061440 0000062539 0000001100 Unallocated $ dd if=raw.dmg of=ore.bin bs=512 skip=2048 count=59392
これで ore.bin
にOreNoFSの中身が入ります。
3: どこから読むんだ?
バイナリエディタで ore.bin
を開いてみます。ちなみに私が使ったのは hexed.it とVinarise(Vimプラグイン)です。
前者はオンラインですがかゆいところに手が届いて便利です。このときだけBzやStirlingがあるWindowsがうらやましい。
なんだこれは...たまげたなぁ...
しかし0x4000
付近に flag.zip
という名前があります。もしかしてここがディレクトリエントリ?
与えられたstructに照らしてみてもピッタリハマります。サイズは0xfe71a = 1042202
, オフセットは1533クラスタ * 4096B = 0x5fd000
です。
4: zipを探す
さっき binwalk
した時にzipがあったのを思い出して、zipのシグネチャで検索します(50 b4 03 04
)。
zipのフォーマットを調べながら他のシグネチャも見つけました。でもバラバラ
このとき私は「シグネチャあったし0000で埋まったところは消しても大丈夫!」と考えて00を消すスクリプトを書いたりしました...アホすぎる...
大川隆法「スゥ…俺謹製ファイルシステムです」 #SCKOSEN
— 青い辞書 (@sei0o) 2017年10月21日
gzipをかけてみたり(???)しながら2時間ほど経って、zipのデータがファイル内に散らばっていることに気づきました。binvis.ioで見ると一目瞭然。
5: クラスタにファイルを分ける
スクリプトは途中の出力を確認しつつ書きました。やっぱりPythonよりRubyだよね!
pwntoolsの代替が見つからないが...
io = File.open "ore.bin" # ore.bin == partition # AT(4096B * 4) buf = "x" * 4096 * 4 v = io.read 4096*4, buf File.open "results/at.bin", "wb" do |f| # ATの情報を別ファイルで管理 f.write v end buf = "x" * 4096 (7424-4).times do |i| # (clusters in ore.bin) / 4096 - 4(AT) # 中のクラスタは4096Bごとに切って保存 v = io.read 4096, buf break unless buf File.open "results/#{i+4}.bin", "wb" do |f| f.write v end end
6: クラスタを並び替えて結合
io = File.open "results/at.bin" buf = "xx" nxt = [] # arr = [] (4096 * 4 / 2).times do |i| # ATの情報を取得(自分の次のATはどこか?) v = io.read 2, buf hx = v.unpack("H*")[0] nxt[i] = (hx[2] + hx[3] + hx[0] + hx[1]).hex arr << [i, (hx[2] + hx[3] + hx[0] + hx[1]).hex] end # File.open "results/atresult.txt", "wb" do |f| # ATの情報を見て次のクラスタをまとめる # arr.each do |(i, v)| # next if v == 0 || v == 65535 # # f.write "Chunk #{i}: #{v}\n" # end # end cur = 1533 File.open "results/zipped.zip", "wb" do |f| # ATの情報をもとにクラスタを組み上げ、zipにする loop do f.write File.binread("results/#{cur}.bin") cur = nxt[cur] break if cur == 0 || cur == 65535 ## FFFFや0000は使えない end end
7: unzip results/zipped.zip
すると中に flag.png
があります。
いらすとや。
↑これは嘘です。ごめんなさい。めっちゃ時間かかりました。あと私はinsecureのプロではありません。ひらめいた
— 青い辞書 (@sei0o) 2017年10月21日
やるだけじゃん!w
KoH
え? (insecureすごい)
お疲れ様でした!来年も出たい(あれば)
かなり読みづらい文章が出来上がってしまった...