Fireshell CTF 2019 Writeup

#はじめに
よくCTFのWriteupって、あたかも最初から答えが分かっているかのように進めるじゃないですか？
「ここに〇〇がある。これを××するとFlag入手。」
みたいな。

自分みたいなgdbもロクに使いこなせない人間からすると、特にReversingとかPwnはヒドくて
「まずは静的解析で眺める。ここが怪しそうだ。なのでデバッグでbreakpointをセット。〇〇のようだ。書き換えてFlag入手。」
っていやいや。そもそもどう眺めるの？なんでそこが怪しいの？書き換えのやり方は？みたいな人も多くいると思うんですよ。

なので極力この解答に至るまでのプロセスも残しつつWriteup書いていきます。
もちろんCTF初心者への道しるべとなれば幸いですけど、どちらかというと自分用にこういう手段もあるよ、という引き出しを増やすためのメモが本来の目的です。ご了承を。

あと実際に手を動かしたい人もいると思うので問題も残しておきます。

なお、自分のCTF能力はゴミなので上級者はCTFtime[^1]に載ってるWriteupを見た方が早いです。


##babycryptoweb
与えられたURLに飛ぶと以下のソースコードが載った画面のみが表示されます。

```php:babycryptoweb.php
<?php

$code = '$kkk=5;$s="e1iwZaNolJeuqWiUp6pmo2iZlKKulJqjmKeupalmnmWjVrI=";$s=base64_decode($s);$res="";for($i=0,$j=strlen($s);$i<$j;$i++){$ch=substr($s,$i,1);$kch=substr($kkk,($i%strlen($kkk))-1,1);$ch=chr(ord($ch)+ord($kch));$res.=$ch;};echo $res;';
    
if (isset($_GET['p']) && isset($_GET['b']) && strlen($_GET['b']) === 1 && is_numeric($_GET['p']) && (int) $_GET['p'] < strlen($code)) {
    $p = (int) $_GET['p'];
    $code[$p] = $_GET['b'];
    eval($code);
} else {
    show_source(__FILE__);
}

?>
```
PHPは何も知識がないのでissetとか_GETとか関数名はググりました(ここでは割愛)。まずはif文の解析です。
「isset(\$GET['p']) === 1」$\rightarrow$ pには何かしらセットされている
「isset(\$GET['b']) === is_numeric(\$GET['p'])」 $\rightarrow$ pは数字であり、bには何かしらセットされている[^2]
「strlen(\$GET['b']) === (int) \$GET['p'] < strlen(\$code))」$\rightarrow$ pにセットされている値はcodeの長さ(235)よりも小さく、bの長さは1。[^3]

ここまでで、例えば「(与えられたURL)?p=84&b=w」などが条件をクリアすることになります。
さて、if文内の処理を見ます。この例だと
\$p = 84
\$code[84] = 'w'
とした(つまり84文字目をwに変えた)のち\$codeの文字列をコードだとみなしてevalで実行していることになります。要は、\$code内の1文字を何かしら変えればFlagが得られるんじゃないか、ということです。

次は\$code内の解析です。見にくいので少し手を加えました。

```php:babycrypto_code.php
<?php
$kkk = 5;
$s = "e1iwZaNolJeuqWiUp6pmo2iZlKKulJqjmKeupalmnmWjVrI=";
$s = base64_decode($s);
$res = "";
for ($i = 0, $j = strlen($s); $i < $j; $i++) {
    $ch = substr($s, $i, 1);  // $chは文字列$sのi番目
    $kch = substr($kkk, ($i % strlen($kkk)) - 1, 1);  // strlen($kkk) = 1なので$kchは常に5
    $ch = chr(ord($ch) + ord($kch));  // ASCII codeで数字に変換して足し, 元に戻す
    $res .= $ch;  // C言語でいう+=に相当
};
echo $res;
?>
```

1文字変える前にまずは挙動を確認してみます。
4行目でbase64デコードしています。ここで\$sの値を覗いてみても、それらしい文字列は得られません。

```
// 4,5行目の間に「echo $s;」を追加
$ php ./babycrypto_code.php
{X?e?h????h???f?h????????????f?e?V?
```
さて、ordとかchrが出てきているのでASCIIが絡んでいるだろうと踏んで話を進めます(ここは経験で導くしかない？)。まずはこの\$sのそれぞれについてASCIIで数字に変換した結果を見てみます。

```
// 7,8行目の間に「echo ord($ch), "\n";」を追加
$ php ./babycrypto_code.php
123
88
176
101
…
```
9行目では、これらの数字に文字「5」に相当するASCII Code(53)が足され、また文字に戻されていることになります。ここまでで挙動の確認は終了です。

さて、Flagの形式はF#{~}なので(うまいことこの数字に持っていけないかなぁと願いつつ)、まずはこれをASCII Codeに戻してみます(いずれも10進表記)[^4]。
・F $\rightarrow$ 70
・\# $\rightarrow$ 35
・{ $\rightarrow$ 123
これらと上の数字(123, 88, 176, …)、53を見比べて上手く法則を見つけられればめでたく終了。足すのではなく引けばいいってことです。

```
// 9行目の"+"を"-"に変更
$ php ./babycrypto_code.php
F#{0n3_byt3_ru1n3d_my_encrypt1i0n!}
```


## Alphabet
If you know your keyboard, you know the flag.
とのリード文と一つの.txtファイルがあります[^5]。眺めると0~9, a~fの文字列と、時々半角スペース。
さて、いきなり答えを言ってしまうとアレなので色々と試行錯誤のお時間です。

・テキストエディタ(Windowsならメモ帳とか)の検索機能で"F#"が紛れてないかチェック$\rightarrow$ヒットせず

・スペースを全て改行に変換し、少し見やすくする$\rightarrow$32文字列と64文字列が入り乱れてる。他にはなさそう
$\rightarrow$ピアノの鍵盤っぽくない？キーボードと少しかぶるか？$\rightarrow$ここから進まず

・「キーボード 16進 32桁」でググる$\rightarrow$[ここ](http://qpon.quu.cc/pc/cord.htm)の下の方にバイナリイメージなるものが。
$\rightarrow$実際は違うって言われているしダメ元でやってみる$\rightarrow$ダメ

・1つ目の「72dfcfb0c470ac255cde83fb8fe38de8a128188e03ea5ba5b2a93adbea1062fa」をテキストエディタで検索してみる
$\rightarrow$6個ヒットするし、完全ランダム生成のUUIDじゃなさそう。

・思い切って「72dfcfb0c470ac255cde83fb8fe38de8a128188e03ea5ba5b2a93adbea1062fa 」をググる。$\rightarrow$Hashやらmd5やらの文字
$\rightarrow$この文字列を[CrackStation](https://crackstation.net)ってサイトでデコードしてみる$\rightarrow$文字LをSHA256でハッシュ化したものと判明


64桁の方はSHA256によるハッシュ値のようです。同じく32桁の方をデコードしてみると、こちらはMD5によるある1文字のハッシュとわかります。
流石に4.9MBもあるファイル全てを1行ずつCrackStationでデコードしてテキストエディタで置換して、なんてことをするのはしんどいので、ある程度あたりをつけます。

1. 文字"{"をSHA256, MD5でハッシュ化

F#{~}となる文字列がどこかにあるはずです。なのでまずは "{" 1文字だけ書かれたファイルを作り(hoge.txtとします)

```
$ shasum -a 256 hoge.txt
021fb596db81e6d02bf3d2586ee3981fe519f275c0ac9ca76bbcf2ebb4097d96

$ md5sum hoge.txt
f95b70fdc3088560732a5ac135644506

// shasumがないと言われたらmacOSでは
// $ brew install md5sha1sum
// として再度試す
```
でハッシュ値を得ます。これで検索をかけると前者は1件だけヒット。後者は見つかりません。
(見にくいので画像クリックして拡大してください)

![pic1.png](https://qiita-image-store.s3.amazonaws.com/0/334637/2a4336bd-e57b-d07b-6345-3ca783f485fd.png)
念の為、その上2つ分を変換してみましょう
"F"のSHA256ハッシュ値$\rightarrow$「f67ab10ad4e4c53121b6a5fe4da9c10ddee905b978d3788d2723d7bfacbe28a9」
"#"のSHA256ハッシュ値$\rightarrow$「334359b90efed75da5f0ada1d5e6b256f4a6bd0aee7eb39c0f90182a021ffc8b」

となり大丈夫そうです。

あとは、"{"以下、CrackStationで根気よくデコードしていきましょう。
![pic2.png](https://qiita-image-store.s3.amazonaws.com/0/334637/e74a82aa-5aef-4535-4483-840f56802548.png)

F#{Y3aH_Y0u_kN0w_mD5_4Nd_Sh4256}です。


#おわりに
時間があればReversingとPwnの解析もしたいと思います。

長文ですがここまで読んでくれてありがとうございます。


[^1]: https://ctftime.org/event/727/tasks/
[^2]: もちろんpが数字ではなく、bには何もセットされていない可能性もありますが、そうすると次の(int) _GET('p')という型変換がエラーを起こします。
[^3]: pにセットされている値はcodeよりも長く(e.g. \$GET['p']=1000)bの長さが0という可能性は、if文内の処理で変数\$p=1000とするので次の\$code[1000]が参照できなくなり矛盾します。
[^4]: CTFは試行錯誤あるのみです。ASCIIでダメなら別の手段を考えるだけです。
[^5]: https://github.com/taiyaki8926/Fireshell