SECCON予選2018 writeup

# 成績

一人チームtakubokudori 570点　134位
SECCON2回目です．

解けた問題

- Unzip
- Runme
- QRChecker
- History

# Unzip(101点)
今回のウェルカム問題
パスワードの掛かったzipファイルと，暗号化用プログラムが提供される．

```shell
echo 'SECCON{'`cat key`'}' > flag.txt
zip -e --password=`perl -e "print time()"` flag.zip flag.txt
```

フラグであるkeyファイルを読み出し，SECCON{keyファイルの中身}をflag.txtに格納．
そしてzipファイルをパスワードで暗号化し，flag.zipに格納していることがわかる．
そのパスワードはperlプログラムで生成しているが，print time()と書いてあるので，perlを使ったことがなくても時間をパスワードにしているらしいことがわかる．
ヒントが見つからなかったが，まあさすがに最近作ったものであろうと予測してブルートフォースしたら，1540566641がパスワードであった．

```python
import zipfile

s=False

with zipfile.ZipFile('flag.zip') as pass_zip:
    for i in range(1540000000,1540622539+1):
        print(i)
        try:
            pass_zip.extract('flag.txt','~/Desktop',pwd=str(i).encode())
            s=True
            break
        except:
            s=False
            pass

print(s)
```

ちなみにこの時間は，flag.zipが作られた時間(2018/10/27 0:10:41)である．
なぜか知らないが，Windows10の右クリックプロパティで見ると0:10:42になっている．謎．
ちゃんとflag.zipのタイムスタンプを見ていればブルートフォースをせずとも解けたのである．こういうのに気付けるようになりたい・・・

最初はシェルスクリプトでやろうと思ったがうまくいかなかったのでpythonに切り替えた．
この問題で1時間消費．

# Runme(102点)
10分くらいで解けた．Cutter disassemblerの効力を知った回．IDAが買えない貧乏人には非常にうれしい．たまにおかしくなるが．
main関数から，自作関数を追っていくと，似た関数が出てくる．
ただひたすらに関数を掘っていくと1文字ずつそのフラグが露わになる．
途中でC:\Temp\....exeみたいなものも見えるが，何を意味しているのかは分からなかった．
SECCON{Runn1n6_P47h}

# QRChecker(222点)
qr.cgiを見ると，画像を500,250,100,50の正方形にリサイズしながらQRコードを読み取っていくプログラムのようである．

```python
#!/usr/bin/env python3
import sys, io, cgi, os
from PIL import Image
import zbarlight
print("Content-Type: text/html")
print("")
codes = set()
sizes = [500, 250, 100, 50]
print('<html><body>')
print('<form action="' + os.path.basename(__file__) + '" method="post" enctype="multipart/form-data">')
print('<input type="file" name="uploadFile"/>')
print('<input type="submit" value="submit"/>')
print('</form>')
print('<pre>')
try:
	form = cgi.FieldStorage()
	data = form["uploadFile"].file.read(1024 * 256)
	image= Image.open(io.BytesIO(data)) # アップされた画像を開く
	for sz in sizes: # [500, 250, 100, 50]にリサイズ
		image = image.resize((sz, sz)) # 画像をリサイズ
		result= zbarlight.scan_codes('qrcode', image)
		if result == None: # 読み取り失敗
			break
		if 1 < len(result): # 2つ以上同時に読み取った
			break
		codes.add(result[0]) # 重複していないなら値を格納
	for c in sorted(list(codes)):
		print(c.decode()) # 読み取ったコード一覧を列挙
	if 1 < len(codes): # 2つ以上，別々に読み取ったのならフラグを表示
		print("SECCON{" + open("flag").read().rstrip() + "}")
except:
	pass
print('</pre>')
print('</body></html>')
```

こんな感じである．ファイルをアップロードすると，サイズを変えて4回QRを読み取るが，途中で読み取りに失敗するか，2つ以上同時に読み取った場合は4回読み取っていなくとも中断する．

そして，4回の読み取りの中で，2回別のQRを読み取れれば，codesに値が2個格納されて

```python
if 1 < len(codes):
```

の条件を満たし，晴れてフラグが手に入る．
4回の読み取りでは，画像のサイズが変化する．
つまり，画像のサイズ変化によって読み取れるQRコードが変化すればよい．
そして，最低2個異なるQRが読めれば勝ちなので，500と250で別々のコードを読ませれば良い．
さて，QRコードを変える方法はいくつか存在する．
最近神戸大学が，[白黒QRの一部箇所の色を灰色にすることで確率によって遷移するQRが変化してしまうもの](https://news.yahoo.co.jp/byline/moriimasakatsu/20180629-00087130/)を開発した．
初めはこれかと思って試してみたが，そもそも確率で変わるのはQRコードリーダで読み込んだ時の明るさとか，そんなノイズがあって初めて確率が関わるので，結局投げても毎回同じ結果しか返ってこなかった．
それ以外にも，大きいQRコードの一部に小さいQRを埋め込むといった例が存在している．

結局のところ，サイズの変化によって，一方のQRコードが読み取れなくなり，同時にもう一方のQRコードが読み取れるようになればよさそうである．QRコードが読み取れなくなる場合は，QRにノイズが走っているとか，小さすぎるとか，そういう理由が考えられる．
つまり，アップロードする原画像には**読み取れるが，あと少し小さくなると読み取れなくなるQRコード**と，**ノイズが走っていて読み取れないが，あと少し小さくなるとそのノイズが無視できるほど小さくなる(消失する)ので読み取れるQRコード**があれば，うまくいきそうである．
で，数時間かけてできたものがこちらになります．

![js3.png](https://qiita-image-store.s3.amazonaws.com/0/240552/2065775a-9186-8861-0741-3817d221baf7.png)

500の時は左上の小さいQRコードは読める．しかし，右下の大きいQRコードは1pxの極細の赤線が走っており，読み取れない．
250になると，小さくなりすぎて左上のQRコードが読み取れなくなる．しかし，右下のQRコードは読み取りを邪魔していた赤線が消失し，読み取れるようになる．
結果，500のときと250のときで別のQRコードが読み取られ，無事フラグが手に入る．
ちなみに，アップロードするサイトページはSelf-XSS可能である，セッションとか機微な情報はそもそも持っていないが，**無闇に知らないQRコードをアップロードしないようにしよう**

# History(145点)
終了1時間前ぐらいに解けたが意味が分からない発狂しそう
とりあえず「strings --encoding=l J」で可読文字列を抽出，そしてsortとuniqを駆使して重複行を削除して目grepしていたら「2018}.txt」の文字列を発見．もしやと思って「{」を探してみると「CON{.txt」を発見．
txtファイルを取り出せばよいのかと思って「grep "\.txt$"」で検索をかけてみると，末尾あたりに
SEC.txt
CON{.txt
F0r.txt
....
2018}.txt
のようなものが見つかり，すべて結合してフラグを投げたら弾かれた．
よくわからないtktksecとか，そんなのを除外したりしていたらなんか通った．