KOSENセキュリティコンテスト2017に出場して3位だった話

KOSENセキュリティコンテストに金沢高専チーム SandBoxで出場しました。
3300点で3位でした。1位取らないと担任に煽られるので悲しい。明日学校に行きたくない。

10/23 14:20追記
煽られました。
担任「こんなんで勝てないんなら、大学編入した後こういう大会で1位になるのは2-3年は無理やな」

というわけでWriteUpを書きます。

00 Sample 100 サンプル

これはサンプル問題である。今回出題される問題は、全て答えとなる「フラグ」が含まれている。フラグは、必ずSCKOSEN{hoge}の形式になっている。この問題のフラグはSCKOSEN{Let's enjoy}である。入力しろ。

問題文が命令口調。
SCKOSEN{Let's enjoy}

01 Binary 100 フラグを答えろ

正しいフラグを書けば正しいかどうかを判定してくれる、便利なアプリを開発した。フラグを調べて入力せよ。
(添付: a.out)

問題文が命令口調。 とりあえずfileします。

file a.out
a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=d4f77b6523796400a4367a33eec053d50ec2761b, not stripped

なるほど。とりあえずバイナリエディタで開く。

f:id:sakura_lov:20171023002654p:plain

適当に整形して
SCKOSEN{h1dden_f1ag}

02 Binary 100 ファイル名を探せ

フラグは簡単だ、ファイル名に隠した。
(添付: q)

問題文(ry とりあえずfileします。

file q
q: gzip compressed data, last modified: Thu Oct 19 04:49:39 2017, from Unix

もしかしてこの時期から準備ですか。大変ですね。展開します。

mv q q.gz
gzip -d q

またqができました。

file q
q: POSIX tar archive (GNU)

>||tar -xvf q||<で失敗してるっぽいので

strings q -n 10 | grep "SCKOSEN"
q/SCKOSEN{ki_ha_mori_ni_kakuse}

SCKOSEN{ki_ha_mori_ni_kakuse}

03 Binary 200 ボスを倒せ

とあるゲームを見つけたのだが、ボスがあまりに強すぎて一切倒せそうにない。どうにか倒す方法はないだろうか。
(nc hogehoge@hoge.com 114514)

とりあえず接続してみる

nc hogehoge@hoge.com 114514

Input your name: Player's HP: 768
Boss's HP: 12345678
Next round=>
Round 1/10
	Player's Attack!
	Boss's HP: 12345667 (-11)
	Boss's Attack!
	Player's HP: 602 (-166)
Next round=>
Round 2/10
	Player's Attack!
	Boss's HP: 12345658 (-9)
	Boss's Attack!
	Player's HP: 427 (-175)
Next round=>
Round 3/10
	Player's Attack!
	Boss's HP: 12345649 (-9)
	Boss's Attack!
	Player's HP: 252 (-175)
Next round=>
Round 4/10
	Player's Attack!
	Boss's HP: 12345639 (-10)
	Boss's Attack!
	Player's HP: 120 (-132)
Next round=>
Round 5/10
	Player's Attack!
	Boss's HP: 12345630 (-9)
	Boss's Attack!
	Player's HP: 0 (-121)
You lose

なんて理不尽な。Input your name:に適当にでかい値入れればバッファオーバーフロー的なにかでうまくいくんじゃないかと考え、
適当に大きい数字を入れるとBoss's HPがとても小さくなって嬉しい!(睡眠不足なので考えるのがめんどくさかった)
ボスを倒すと Flag is SCKOSEN{buffer_over_flow!}と出力されるので

SCKOSEN{buffer_over_flow!}

04 Binary 500 OreNoFS

このファイル'raw.dmg'には、どうも独自のファイルシステムが構築されているらしい。
このファイルシステムに格納されたデータは1つ、そのデータを復元しよう。
このファイルシステムは、クラスタ単位で管理されており、1クラスタ=4096byteであることは分かっている。また、AllocationTableというFATファイルシステムでいうFAT領域とディレクトリエントリ、データ格納領域に分けられているようだ。また、 GUID Partition Table (GPT)でフォーマットされているので、その分はクラスタの管理外である点に注意しなければならない。ATは2byte*8192、ディレクトリエントリは32byteで下記の構造体で定義されているらしい。

```
struct directory_entry {
unsigned char magic;(1byte)
char filename[8];
char extension[3];
unsigned int size;(4byte)
unsigned long offset_of_cluster;(2byte)
char attribute[12];
unsigned long reserved; (2byte)
};
```

(添付 raw.dmg)

強実装には競技プログラミングスキルで戦う。
適当にバイナリを読んでいくと、

AllocationTable(FAT領域)の始点 - 1048576
2byte(ビッグエンディアン)(確か)次のデータが格納されているクラスタ番号が記録されてる

ディレクトリエントリの始点 - 1064960
unsigned char magic - 0f
char filename[8] - flag.zip (ファイル名が書いてある)
char extension[3] - 00 00 00 (これは何かわからん)
unsigned int size (4byte) - 1042202 B (ファイルの大きさ)
unsigned long offset_of_cluster - 1533 (これ、何か最初わからなかったんだけど、AllocationTableのファイルの始まりを示すクラスタ番号が書かれているレコードを示している。)
1 クラスタ 4096 B

こんな感じのことがわかる。
なので適当にくっつけてワイワイやってくれるやつをJavaで書いた。なぜJavaなのか。それは弊高専の公用プログラミング言語だからである。(は?)
クソプログラムがこちら。(寝起きクオリティです。目が潰れます。)

public class OreNoFS {
	public static void main(String[] args) {
		File f = new File("../../Desktop/raw.dmg");
		File o = new File("../../Desktop/flags.zip");
		try {

			InputStream fr = new FileInputStream(f);
			BufferedInputStream br = new BufferedInputStream(fr);
			byte[] b = new byte[br.available()];
			br.read(b);
			System.out.println(b.length);

			int start = 1134592;
			ArrayList<ArrayList<Byte>> list = new ArrayList<>();
			
			for(int i = start; i < b.length; i += 4096) {
				byte[] bb = new byte[4096];
				ArrayList<Byte> li = new ArrayList<>();
				if(i + 4096 > b.length) break;
				for(int j = 0; j < 4096; j++) {
					li.add(b[i + j]);
				}
				list.add(li);
			}
			
			int atStart = 1048576;
			int[] at = new int[8192];
			for(int i = 0; i < 8192; i++) {
				int index = i * 2 + atStart;
				
				at[i] = Byte.toUnsignedInt(b[index]) + Byte.toUnsignedInt(b[index + 1]) * 256;
			}
			HashMap<Integer,ArrayList<Byte>> map = new HashMap<>();
			ArrayList<Integer> indexList         = new ArrayList<>();
			for(int i = 1533; i < at.length; ) {
				if(at[i] == 0 || at[i] == 65535 || at[i] == 65527) break;
				
				
				System.out.println(at[i]);
				
				int index = i - 21;
				i = at[i];
				ArrayList<Byte> bl = new ArrayList<>();
				for(int j = 0; j < 4096; j++) {
					bl.add(b[start + index * 4096 + j]);
				}
				
				map.put(at[i],bl);
				indexList.add(at[i]);
			}
			//Collections.sort(indexList);
			ArrayList<ArrayList<Integer>> pk = new ArrayList<>();
			for(int i = 0; i < indexList.size(); i++) {
				ArrayList<Integer> ll = new ArrayList<>();
				ArrayList<Byte> bbl = map.get(indexList.get(i));
				for(int j = 0; j < bbl.size(); j++) {
					ll.add(Byte.toUnsignedInt(bbl.get(j)));
				}
				for(int j = 0; j < ll.size(); j++) {
					//System.out.println(ll.get(j));
				}
				pk.add(ll);
			}
			for(int i = 0; i < indexList.size(); i++) {
				System.out.println(indexList.get(i));
			}
			FileOutputStream os = new FileOutputStream(o);
			BufferedOutputStream bo = new BufferedOutputStream(os);
			for(int i = 0; i < pk.size(); i++) {
				for(int j = 0; j < pk.get(i).size(); j++) {
					bo.write(pk.get(i).get(j));
				}
			}
			bo.flush();
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

これをやるといい感じにファイルができるんだけど、どうやら末尾が出ていない。寝不足(寝起き)なので末尾が出ていないバグプログラムを書いてしまった。
どうやら末尾が正しく出てないようなので、ATを参照して末尾のクラスタの始点アドレスを計算して適当にバイナリエディタで切り取り貼り付け。
zipファイルの完成。
中身はこれ。
f:id:sakura_lov:20171023010648p:plain

SCKOSEN{do_not_be_evil_do_the_right_thing}

その通りですね。

05 Crypto 100 簡単な符号化2

ファイルからフラグを探せ

こんな感じのファイルがもらえる。
f:id:sakura_lov:20171023011202p:plain
どう見てもBase64ですね。
Decodeすると
f:id:sakura_lov:20171023011424p:plain
こんな感じ。 先頭が 4B 50 04 03で始まるので多分zipを2byteごとに交換していったやつだと思うのでプログラムを書いて出力
結果はdocファイルみたいな感じでした。
f:id:sakura_lov:20171023011747p:plain

SCKOSEN{TEXT_BUT_NOT_PLAIN}

06 Crypto 100 解凍して解答せよ

ファイルからフラグを読み取れ!

好きですこの問題タイトル。
解凍すると中身はxor.pngとmask.png
もう答えですね。しかも薄目で見るとフラグが見えます。
合成すると見えるぞ答えが!(スペシャねこまんまなやつを使うと良いです。)

SCKOSEN{simple_visual_cryptography}

07 Crypto 100 簡単な符号化

U0NLT1NFTntiYXNlNjRfaXNfdmVyeV9lYXN5fQ

すごくBASE64ですね。デコード。

SCKOSEN{base64_is_very_easy}

08 Crypto 200 WeakRSA1

How can you break RSA encryption?

なんか完璧そうな感じに見えた。こういう時はとりあえずfermat法で素因数分解

SCKOSEN{Cl0s3_9r1m3_1s_n0t_s4fe}

09 Crypto 200 WeakRSA2

Same modulo, different key.
That means...?

問題文、答え言ってますね。
Common Modulus Attackです。
ちなみに途中まで数値が間違ったものだったので無限に時間を溶かしました。
質問投げたチームの人ありがとう。

SCKOSEN{Comm0n_Modu1us_d1ff3rent_pubkey_1s_n0t_s4fe}

10 Crypto 400 Homomorphic Encryption

解けませんでした!英語ができない高専生です!

11 Misc 100 君(脆弱性)の名は

名前は何か。 SCKOSEN{___}の形で答えよ。
CVE-2017-13077
CVE-2017-13078
CVE-2017-13079
CVE-2017-13080
CVE-2017-13081
CVE-2017-13082
CVE-2017-13084
CVE-2017-13086
CVE-2017-13087
CVE-2017-13088

2017年でめっちゃ連続でCVE番号ある感じ。これは普通の脆弱性BoFとかそういうの)ではないですね。最近といえばKRACKsですね。
打ったら出ました。

SCKOSEN{KRACKs}

12 Misc 100 便利なプロトコル

DHCPは便利なプロトコルであるが、IP対応機器に対してしかIPアドレスを配布できない問題がある。1998年、この問題を克服した画期的なプロトコルが提案された。
その提案のタイトルをSCKOSEN{___}の形で答えよ。
なお、スペースはアンダースコア(_)に置きかえよ

キーワードは 「プロトコル」、「画期的」、「1998年」、「提案」、「DHCP」ですね。
提案とプロトコルからRFCが連想でき、画期的、1998年、RFCからジョークRFCが連想できます。

RFC 2322 - Management of IP numbers by peg-dhcp ですね。 洗濯バサミ〜〜

SCKOSEN{Management_of_IP_numbers_by_peg-dhcp}

13 Misc 100 諜報機関は基本?

情報セキュリティの三要素、SCKOSEN{___}の形で答えよ。

試合開始前のビデオで飽きるほど言ってましたね。
「機密性」(Confidentiality)、「完全性」(Integrity)、「可用性」(Availability)です。
情報セキュリティスペシャリスト試験ぶりに思い出しました。

SCKOSEN{CIA}

14 Misc 100 素数を数えろ

7桁で最大の素数をSCKOSEN{___}の形で答えよ。

素数はググろう。

SCKOSEN{9999991}

15 Network 100 寝坊気味のコンピュータ

ここにある通信をキャプチャしたファイルがある。この中からフラグを見つけ出せ!
(添付 pcapng)

とりあえずstrings

strings problem.pcapng
SCKOSESCKOSESCKOSESCKOSESCKOSESCKOSESCKOSESCKOSESCKOSESCKOSESCKOSESCKOSESCKOSESCKOSESCKOSESCKOSE
N{wakeN{wakeN{wakeN{wakeN{wakeN{wakeN{wakeN{wakeN{wakeN{wakeN{wakeN{wakeN{wakeN{wakeN{wakeN{wake
_on_la_on_la_on_la_on_la_on_la_on_la_on_la_on_la_on_la_on_la_on_la_on_la_on_la_on_la_on_la_on_la
n_is_an_is_an_is_an_is_an_is_an_is_an_is_an_is_an_is_an_is_an_is_an_is_an_is_an_is_an_is_an_is_a
1~_z
lerm}

ちなみにpcapファイルを読むと、WoLパケットの送り先のMACアドレスがフラグでした。
私はいつも寝坊気味です。

SCKOSEN{wake_on_lan_is_alerm}

16 Network 100 ログインしたいんだ!

ここにある通信をキャプチャしたファイルがある。この中から、フラグを見つけ出せ。
(添付 pcapファイル)

pcapファイルを開くと、f:id:sakura_lov:20171023091300p:plain
こんなのが見える。Base64っぽいのでデコードすると、

admin:SCKOSEN{basic_is_unsecure}


SCKOSEN{basic_is_unsecure}

17 Network 200「ファイル送信pcap」

(問題文略 添付 pcapファイル)

ファイルを抽出すると、lock.zip っていうzipファイルとLenna.pngとかいう画像が与えられる。
lock.zipはパスワードがかかっていてひらけないが、stringsで文字列を見ていると、中にLenna.pngが入っていることがわかる。
既知平文攻撃が使えるのでpkcrackで開けると

SCKOSEN{k_p_t_a}

18 Web 100 ログインせよ

adminとしてログインせよ
(添付 webサーバのURL)

SQLインジェクションで叩くだけでした。

SCKOSEN{sug0-i_ta-n0sh1-}

19 Web 100 灯台下暗し

パスワードを奪取せよ
(添付 webサーバのURL)

「とうだい」ってワードを聞くと心が痛む。
添付のURLにアクセスすると、Hintとしてこんな感じのやつが貼られていた。

$try = false;
  $login = false;

 if( isset($_POST[‘name’]) && isset($_POST[‘password’])){
    $name = htmlspecialchars($_POST[‘name’], ENT_QUOTES);
    $password = htmlspecialchars($_POST[‘password’], ENT_QUOTES);

   $db = new SQLite3(‘data.db’);
    
   $stmt = $db->prepare(“SELECT name FROM users WHERE name = ? AND password = ?“);
    $stmt->bindValue(1, $name, SQLITE3_TEXT);
    $stmt->bindValue(2, $password, SQLITE3_TEXT);

   $rows = $stmt->execute();
    $row = $rows->fetchArray();

   $try = true;
    $login = $rows && $row[‘name’];
    $db->close();
  }

$db = new SQLite3('data.db');
ってところが肝で、[添付のURLのアドレス]/data.db
とやると、SQLiteのデータベースファイルが落ちてくる。嬉しい。

strings data.db
SQLite format 3
Qtableusersusers
CREATE TABLE users(name, password)
EadminSCKOSEN{database_bukkonuki!}

SCKOSEN{database_bukkonuki!}

20 Web 200 Web1

(問題文略)

こんな感じの辛そうなJSがあった。

___ = 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(__[_][____["_"]])[____["___"]]("_");
            }


        });
    })();
}, false);

なので

___ = 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){
              console.log(__[_]);
              __[_] = Array(__[_][____["_"]])[____["___"]]("_");
              console.log(__[_]);
            }


        });
    })();
}, false);

って感じでconsole.logを使って吐いてくれるようにすると、

SCKOSEN{44203240442235}

21 Web 200 Web2

f:id:sakura_lov:20171023092658p:plain

こんな感じのやつがある。めんどくさそうなので、2行目はどう見ても自明に文字になる感じのやつ 0x~~~とか 0o~~~が入ると思うのと、
最後は(1)ってことだけを伝えて後輩に適当に組み合わせておいてと言っておく。

SCKOSEN{4c0bf259050d08b8982b6ae43ad0f12be030f191}