更新日時で差をつけろ

むしろ差をつけられている

高専セキュコンに出た #SCKOSEN

10/21-22の高専セキュコンに初参加しました。@yfba_, @Akashi_SNさん, @Snow_poijioさんと、チームBiPhoneで出ました。
2位です。惜しい!
でもとても楽しかった!

問題一覧です。下の2つはKoH(King of the Hill)。

Cryptoは1問も解いてません...RSAこわい...(((
ずっとzipのフォーマットを読んだりzipコマンドを調べたりしていました。

自分が解いた、計1300点の問題の考え方(writeup)をのこしておきます。直感で通したものが多いので。

Misc

準備体操〜

  • 「君(脆弱性)の名は」時代遅れなようで、とてもナウいKRACKsの話題。
  • 諜報機関は基本?」開始前の講義で耳にしました。CIA。

Network200「ファイル送信pcap」

チームの人に投げられた問題。
pcapをWiresharkで開き、「ファイル>オブジェクトをエクスポート」でLenna.pnglock.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

時刻の前に書いてある defNstor の部分が異なります。なんか怪しい。
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がでます。
f:id:sei0okun:20171022201145p:plain

SCKOSEN{44203240442235}

Web200「Web2」

f:id:sei0okun:20171022201458p:plain

0x52 0x54alertr tなので、その辺りから適当に埋めていく。
callの場所は自信がなかったけど何度か入れ替えて通した。

Network100「寝坊気味のコンピュータ」

.pcapngWiresharkで開くと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がうらやましい。 f:id:sei0okun:20171022052032p:plain
なんだこれは...たまげたなぁ...
しかし0x4000付近に flag.zip という名前があります。もしかしてここがディレクトリエントリ?
与えられたstructに照らしてみてもピッタリハマります。サイズは0xfe71a = 1042202, オフセットは1533クラスタ * 4096B = 0x5fd000です。

4: zipを探す
さっき binwalk した時にzipがあったのを思い出して、zipのシグネチャで検索します(50 b4 03 04)。
zipのフォーマットを調べながら他のシグネチャも見つけました。でもバラバラ
このとき私は「シグネチャあったし0000で埋まったところは消しても大丈夫!」と考えて00を消すスクリプトを書いたりしました...アホすぎる...


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 があります。
いらすとや。
f:id:sei0okun:20171022145533p:plain

↑これは嘘です。ごめんなさい。めっちゃ時間かかりました。あと私はinsecureのプロではありません。

KoH

え? (insecureすごい)

お疲れ様でした!来年も出たい(あれば)
かなり読みづらい文章が出来上がってしまった...