はじめに
先日、人生初のSECCONに参加しました。
特に勉強もしてなかったので、学校の友人とチームを組んで軽いのりで始めたものの、1問しか解けず圧倒的惨敗でした。仲間の1人は開始2時間でリタイアしました。
反省の意を込めて、開始5分で爆速でできた問題と、その他残りの23時間55分かけても解けなかった問題たちの解法を、強者のWriteupを見ながらまとめていきます。
Writeup
Unzip (Forensics)
これが唯一解けた問題ですね。
与えられたzipを解答すると、makefile.sh
とflag.zip
が入っています。flag.zip
はパスワードがかかっていて開きません。
まずはmakefile.sh
を見てみましょう。
echo 'SECCON{'`cat key`'}' > flag.txt
zip -e --password=`perl -e "print time()"` flag.zip flag.txt
1行目で「flag.txt」にflagを書き込み、2行目で現在時刻のUNIX timeをパスワードとして、zipファイル化していることがわかります。
$ zipinfo -v flag.zip
Archive: flag.zip
There is no zipfile comment.
End-of-central-directory record:
-------------------------------
Zip archive file size: 225 (00000000000000E1h)
Actual end-cent-dir record offset: 203 (00000000000000CBh)
Expected end-cent-dir record offset: 203 (00000000000000CBh)
(based on the length of the central directory and its expected offset)
This zipfile constitutes the sole disk of a single-part archive; its
central directory contains 1 entry.
The central directory is 78 (000000000000004Eh) bytes long,
and its (expected) offset in bytes from the beginning of the zipfile
is 125 (000000000000007Dh).
Central directory entry #1:
---------------------------
flag.txt
offset of local header from start of archive: 0
(0000000000000000h) bytes
file system or operating system of origin: Unix
version of encoding software: 3.0
minimum file system compatibility required: MS-DOS, OS/2 or NT FAT
minimum software version required to extract: 2.0
compression method: deflated
compression sub-type (deflation): normal
file security status: encrypted
extended local header: yes
file last modified on (DOS date/time): 2018 Oct 27 00:10:42
file last modified on (UT extra field modtime): 2018 Oct 27 00:10:41 local
file last modified on (UT extra field modtime): 2018 Oct 26 15:10:41 UTC
32-bit CRC value (hex): 571b1069
compressed size: 43 bytes
uncompressed size: 32 bytes
length of filename: 8 characters
length of extra field: 24 bytes
length of file comment: 0 characters
disk number on which file begins: disk 1
apparent file type: text
Unix file attributes (100644 octal): -rw-r--r--
MS-DOS file attributes (00 hex): none
The central-directory extra field contains:
- A subfield with ID 0x5455 (universal time) and 5 data bytes.
The local extra field has UTC/GMT modification/access times.
- A subfield with ID 0x7875 (Unix UID/GID (any size)) and 11 data bytes:
01 04 f6 01 00 00 04 14 00 00 00.
There is no file comment.
UNIX time で以上の時間は1540566641なので、これを入力すると、、、。
```bash
$ unzip
Archive: flag.zip
[flag.zip] flag.txt password: 1540566641
replace flag.txt? [y]es, [n]o, [A]ll, [N]one, [r]ename: n
解けました。
SECCON{We1c0me_2_SECCONCTF2o18}
QR checker (Forensics)
次はQRコードに関する問題です。以下のサイトに、QRコードを読み込ませるためのwebサイトと、ソースコードが公開されています。
#!/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:
image = image.resize((sz, sz))
result= zbarlight.scan_codes('qrcode', image)
if result == None:
break
if 1 < len(result):
break
codes.add(result[0])
for c in sorted(list(codes)):
print(c.decode())
if 1 < len(codes):
print("SECCON{" + open("flag").read().rstrip() + "}")
except:
pass
print('</pre>')
print('</body></html>')
ソースコードを見た感じ、1つのQR画像を500x500 250x250 100x100 50x50とリサイズし、そこから2つ以上のデータが読み込まれた場合のみflagが表示されるようになっています。
リサイズにより画像が荒くなったり潰れたりすることを利用しろということでしょうか。。。
とりあえず、ローカル環境で再現できるようセットアップを行います。
!/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:
image = image.resize((sz, sz))
result= zbarlight.scan_codes('qrcode', image)
print(result)
if result == None:
break
if 1 < len(result):
break
codes.add(result[0])
print(codes)
for c in sorted(list(codes)):
print(c.decode())
print(len(codes))
if 1 < len(codes):
print("SECCON{" + open("flag").read().rstrip() + "}")
except:
pass
print('</pre>')
print('</body></html>')
まずはデバックできるように、print(c.decode())
を追加しました。しかし、実行してみるとzbarlight
がないと怒られます。zbarlight
をインストールしましょう。
$ brew install zbar
$ export LDFLAGS="-L$(brew --prefix zbar)/lib"
$ export CFLAGS="-I$(brew --prefix zbar)/include"
$ pip install zbarlight
ここから、cgiサーバーを立ち上げて、ローカル環境で実行できるようにします。
具体的には、cgi-binフォルダを作成し、先程のファイルをその中に移動します。そして、以下のコマンドで簡易サーバを立ち上げます。
$ python -m http.server --cgi
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
127.0.0.1 - - [01/Nov/2018 16:59:45] "GET /cgi-bin/qr.python HTTP/1.1" 200 -
http://localhost:8000/cgi-bin/ファイル名 にアクセスするとローカル環境で動かせる事がわかります。
ローカル環境のセットアップはこれで完了です。
ここから、サイズを変えたり、QRコードを重ねたりいろいろしたのですが間に合わなかったです、、、。
ということで、こちらの方のWriteupを拝見したところ"気まぐれコード"と呼ばれる、千人に一人、一万人に一人はフィッシングサイトに誘導されてしまうようなQRコードの存在を知りました。
上記のサイトに有るように、QRコードにおける誤り訂正符号部分が白と認識されるか黒と認識されるかで読み込まれるサイトが変わるような仕様になっています。
実際に、以下のように塗りつぶし、
これをローカル環境で読み込むと、
このように50x50にリサイズされた時、labがlobとして認識されることがわかりました。
これを実際のサイトで読み込ませると、、、。
SECCON{50d7bc7542b5837a7c5b94cf2446b848}
flagが獲得できました。
明日以降も随時更新してきます!