KosenXm4sCTF Writeup（Part1）

#1. はじめに
2020/12/24(木) 09:00 JST — 2020/12/25(金) 21:00:00 JST で、KosenXm4sCTF に参加し、2327 点（得点を得た 100 チーム中 6 位）でした。
![Top 10 Users.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429434/45991005-7477-01cd-fefc-8a8e820fa8e8.png)
9 月上旬にメンタルが死亡し、10 月中旬くらいからは参加登録した半数以上の大会で「問題を回収するだけ」ような状況でしたが、Twitter で「初心者向け CTF がある」旨の報を知り、本格的な復活の狼煙を上げるため参加することにしました。
取り急ぎ、Welcome と Crypto と Web の Writeup を公開します。
#2. Writeup
##2-0. Welcome!! (500点)
>Discordサーバーに参加してない方はしましょう！大事なお知らせが #announcement に流れてくるかもしれません！

Welcome は点数の減衰なしというところが素敵です！（大会とdiscordに）参加することに意義がある、というメッセージと受け止めました。 実際、Discord に参加して #announcement を確認するとフラグが得られました。
**xm4s{welcome_to_xm4s_ctf!hava_fun!}**

##2-1. advanced_caesar (Cryoto, 94点; beginner) 
>超すごい暗号作った！ｗ

```encrypted_flag.txt
xn4u{fejyhzwyjazwzqkszurwhyqaop}
```
いろいろ試してみると、「n 字目（0オリジン）が n 文字分 rot する」というルールでした。
以下のソルバーで flag を得られます。

```solve.py
enc = "xn4u{fejyhzwyjazwzqkszurwhyqaop}"
flag = ""
i = 0
for c in enc:
  if ord(c) in range(0x61, 0x7b):
    dec = chr((ord(c) - 0x61 - i) % 26 + 0x61)
    i = i + 1
  else:
    dec = c
  flag = flag + dec
print(flag)
```

**xm4s{caesarnoyomikatagawakarann}**

##2-2. do_you_know_RSA? (Cryoto, 78点; easy) 
>RSA暗号機を作ってみたよ！ えーと、、、公開鍵を公開して、、、秘密鍵を隠して、、、、 、、あれ？この情報って表示していいんだっけ？

```param.txt
N = 872466878637044085809546928077402525188932163354013071311247
p = 1202641222143185422372899516011
E = 65537
crypted message is 736752258923832368359268459529023058483734448152703465168101
```
N,e,c の三点セットに加え、P が判明しているので RSA の原理が判っていれば何も考えず容易に解けてしまいます。

```solve.py
from Crypto.Util.number import *
N = 872466878637044085809546928077402525188932163354013071311247
p = 1202641222143185422372899516011
E = 65537
ct = 736752258923832368359268459529023058483734448152703465168101

q = N // p
phi = (p-1) * (q-1)
d = inverse(E, phi)
print(long_to_bytes(pow(ct, d, N)).decode())
```
**xm4s{dont_leak_p_and_q!!}**

##2-3. bad_hash (Cryoto, 97点; medium) 
>二人でいれば衝突なんて怖くないっ！
>
>nc [以下略]

```hash.py
#!/bin/python3

def hash(base):
    xor_sum = 0
    mod_sum = 0
    for c in base.encode():
        xor_sum ^= c
        mod_sum += c
        mod_sum %= 100
   
    return (xor_sum, mod_sum)

with open("./password.txt") as f:
    answer = f.read()

ans_x, ans_m = hash(answer)
print(f'ans_x {ans_x}, ans_m {ans_m}')

user_input = input()
if 10 <= len(user_input):
    print("too long...")

inp_x, inp_m = hash(user_input)
print(f'inp_x {inp_x}, inp_m {inp_m}')

if ans_x == inp_x and ans_m == inp_m:
    print("You hava a password!!")
    with open('./flag.txt') as f:
        print(f.read())
```

あまり頭を使わず解きました。百発百中のソルバではなく、そこそこの確率で短時間で答えを出すソルバを使う（失敗したらリトライ）という発想です。具体的には、nc でサーバに繋いで ans_x と ans_m を入手、 入力容易な 2 ～ 6 文字から所定のハッシュを取得できなければリトライ、という考え方です。

```solve.py
import itertools

def solver(x,m):
  for L in range(2,6):
    for c in itertools.combinations(range(0x20,0x7f), L):
      xs = 0
      ms = 0
      for i in range(L):
        xs ^= c[i]
        ms += c[i]
        ms %= 100
      if xs == x and ms == m:
        ret = ''.join(chr(c[i]) for i in range(L))
        return ret
  return "failed"

def hash(base):
    xor_sum = 0
    mod_sum = 0
    for c in base.encode():
        xor_sum ^= c
        mod_sum += c
        mod_sum %= 100
    return (xor_sum, mod_sum)

x = input("x=")
m = input("m=") 

res = solver(int(x),int(m))
print(res)

```
サーバに繋いだら
ans_x 88, ans_m 36
を得ました。これを上の solver に食わせると 3 文字の文字列
　　$HJZ$
を得たので（一発で成功したのはラッキー）、これをサーバ上に入力するとフラグが得られました。
**xm4s{xor_and_modsum!double_hash!!}**
2020/12/27加筆：サーバプログラムをよく読むとpasswordはランダム生成ではなくファイルからの読み込みですから、失敗したらやり直しではなく失敗したら要ソルバ修正ですね。

##2-4. do_you_like_CBC? (Cryoto, 152点; medium) 
>メリークリスマス！これを読んでるあなたに、暗号化ソースコードのクリスマスプレゼント！

```enc.py
import math
import base64

# flag = "xm4s{this_is_dummy_flag}"
with open("./flag.txt", "r") as f:
    flag = f.read()

key = "paswd"
blocksize = len(key)
initial_vector = "abcde"

# 足りない分は'#'で埋める
if len(flag)%blocksize != 0:
    flag+= '#' * (blocksize - len(flag)%blocksize)

print(f"flag lenght: {len(flag)}")
print(f"block size: {blocksize}")

encrypted_flag = ""
last_enc = initial_vector

for i in range(0,len(flag),blocksize):
    asciicode = [ord(j) for j in flag[i:i+blocksize]]
    chain = [asciicode[j] ^ ord(last_enc[j]) for j in range(blocksize)]
    enc = [chain[j] ^ ord(key[j]) for j in range(blocksize)]
    enc = ''.join([chr(j) for j in enc])
    encrypted_flag += enc
    last_enc = enc

# 不可視文字だと扱いにくいのでbase64する
encrypted_flag = base64.b64encode(encrypted_flag.encode())

print("encrypted(your flag):",encrypted_flag)
```

``` encrypted_flag.txt
flag lenght: 30
block size: 5
encrypted(your flag): b'aW4kYHpQUDt+dUVuC0tSamoWX0RjexFBT307HzwI'
```

プログラムを読み解くと、タイトル通り「CBC モード」で暗号化がなされていることが判ります。すなわち、ブロックサイズ毎、前から順に「前ブロックのの暗号化結果（初回のみ初期ベクトル）と XOR し、さらにそれを key と XOR 」で暗号化しているのです。
key は提供されたコード内に明示されているので、特にトリックを使う必要もなく、復号プログラムを書いて実行すれば OK です。

```solve.py
import base64

encb64 = b'aW4kYHpQUDt+dUVuC0tSamoWX0RjexFBT307HzwI'
encrypted_flag = base64.b64decode(encb64)

key = "paswd"
blocksize = len(key)

flag = ""
last_enc = b"abcde"

for i in range(0,len(encrypted_flag),blocksize):
    val = encrypted_flag[i:i+blocksize]
    chain = [val[j] ^ last_enc[j] for j in range(blocksize)]
    dec = [chain[j] ^ ord(key[j]) for j in range(blocksize)]
    dec = ''.join([chr(j) for j in dec])
    flag += dec
    last_enc = val

print(flag)
```
上のソルバを実行し、余分な「#」を外せばフラグを得られます。
**xm4s{I_like_CBC_encryption!}**

##2-5. decryptor (Cryoto, 187点; medium) 
>「なんでも復号くん」を開発しました！
>
> nc [以下略]

``` server.py
#!/usr/local/bin/python3
from Crypto.Util.number import *
from base64 import b64encode, b64decode

with open("./primes.txt", "rb") as f:
	p, q = map(int, f.read().split())

assert p > 2 ** 1023
assert q > 2 ** 1023

N = p * q
e = 65537
d = pow(e, -1, (p-1) * (q-1))

with open("./flag.txt", "rb") as f:
	flag = f.read()

def encrypt(m):
	return long_to_bytes(pow(bytes_to_long(m), e, N))

def decrypt(c):
	return long_to_bytes(pow(bytes_to_long(c), d, N))

def main():
	flag_c = encrypt(flag)
	print(f"(N, e) = ({N}, {e})")
	print(f"encrypted FLAG is {b64encode(flag_c).decode()}")
	print()
	print("Decode your ciphertext!")
	c = input("base64: ").encode()
	m = decrypt(b64decode(c))
	if m != flag:
		print(f"Your plaintext is {b64encode(m).decode()}")
	else:
		print("I don't decrypt FLAG!")


main()
```

サーバに繋ぐと、公開鍵（N,e）と暗号文（Base64エンコード済）を提示されます。
そして、暗号文の入力を促されます。入力すると復号してくれるのですが、最初に提示された暗号文と同じ値を入れると弾かれます。
暗号化は RSA で行われていますので、平文（フラグ）を m 、暗号文を c とすると、e は奇数ですから、N-c = pow((N-m), e, N) が成り立ちます。つまり、暗号文として N-c を入力すれば、N-m が返ってくるので、フラグをゲットできてしまいます。
サーバから提示された暗号文 c を、以下のソルバで N-c に変換します。

``` solve1.py
from Crypto.Util.number import *
from base64 import b64encode, b64decode

e = 65537
N = int(input("N:"))
enc = input("encrypted FLAG:")
enc = b64decode(enc)
m_enc = long_to_bytes(N - bytes_to_long(enc))

print(b64encode(m_enc))
```
この値をサーバに入力し、帰って平文（N-m）を以下のソルバに入れ、フラグ（m）を求めます。

```solve2.py
m_dec = input("decrypted m_FLAG:")
m_dec = b64decode(m_dec)
dec = long_to_bytes(N - bytes_to_long(m_dec))
print(dec.decode())
```
この問題は1st bloodをいただきました。というのも、この問題が公開された直後のタイミングで着手できたからです。
**xm4s{p1ain_RSA_1s_w3ak}**

##2-6. bad_path (Web, 52点; beginner) 
>Hello Worldを何回もすると成長できるらしいので、そのためにサイトを作りました。

```index.php
<?php

$content = "";
if (isset($_GET["ext"])) {
	$content = file_get_contents("resource/" . $_GET["ext"]);
}

?>

<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>HelloWorld</title>
	<style type="text/css">
		pre {
		  margin: 1em 0;
		  padding: 1em;
		  background: #25292f;
		  color: #fff;
		  white-space: pre-wrap;
		}
	</style>
</head>
<body>
	<h1>Hello World!</h1>
	<form>
		<select name="ext">
			<option value="hello.js">JavaScript</option>
			<option value="hello.py">Python</option>
			<option value="hello.rs">Rust</option>
			<option value="hello.c">C</option>
		</select>
		<input type="submit" value="View">
	</form>
	<pre><?php echo htmlspecialchars($content, ENT_QUOTES) ?></pre>
</body>
</html>
```
index.phpのソースから、directory traversal を狙えそうなので、「index.php? 」の後のクエリ文字列を「ext=../../flag.txt」としたらフラグをゲットできました。
**xm4s{H3110_H3110_CTF3r}**

##2-7. bad_path (Web, 319点; medium) 
>学生服には，主にセーラータイプとブレザータイプが存在します． あなたはどちらが好きですか？

Chrome の デバッグ機能を使ってエンジンに相当する SailorOrBlazor.dll をダウンロードし、dnspy で逆コンパイルします。すると、以下のロジックからフラグが得られそうです。

```.cs
private void Judge()
{
	byte[] bytes = Encoding.ASCII.GetBytes(this._answer);
	byte[] array = new byte[bytes.Length];
	for (int i = 0; i < bytes.Length; i++)
	{
		int num = (int)bytes[i];
		byte[] array2 = array;
		int num2 = i;
		int num3 = num;
		int num4 = i % 4;
		int num5;
		if (num4 != 0)
		{
			if (num4 != 1)
			{
				num5 = 32;
			}
			else
			{
				num5 = 18;
			}
		}
		else
		{
			num5 = 36;
		}
		array2[num2] = (byte)(num3 ^ num5);
	}
	string result;
	if (Encoding.ASCII.GetString(array) == "\\\u007f\u0014S_#\u007fL\u0015y\u0013\u007fF~\u0014Z\u0014`\u007fTL&N\u007fWs\u0011L\u0014`]")
	{
		result = "Correct!";
	}
	else
	{
		result = "Incorrect, try again.";
	}
	this._result = result;
	this._answer = "";
}
```

このコードをもとに、pythonで以下のソルバを使ってフラグを得ました。

```solve.py
enc = b"\\\x7f\x14S_#\x7fL\x15y\x13\x7fF~\x14Z\x14`\x7fTL&N\x7fWs\x11L\x14`]"
dec = ""
for i in range(len(enc)):
  key = 36
  if i % 4 != 0:
    if i % 4 != 1:
      key = 32
    else:
      key = 18
  dec = dec + chr(key ^ enc[i])
print(dec)
```
**xm4s{1_l1k3_bl4z0r_th4n_sa1l0r}**
個人的には、xm4s{1_l1k3_bl4z0r_4s_much_4s_sa1l0r} なのですけど。

以下、次号とします。