picoCTF 2021 [Forensics] 分野の writeup
2021年3月16日~3月30日(日本時間では3月17日~3月31日)に開催された中高生向けのCTF大会、picoCTFの[Forensics]分野のwriteupです。
その他のジャンルについてはこちらを参照
information
Files can always be changed in a secret way. Can you find the flag? cat.jpg
cat.jpg
が配布されます。
exiftoolで見てあげると
$ exiftool cat.jpg ExifTool Version Number : 10.40 File Name : cat.jpg Directory : . File Size : 858 kB File Modification Date/Time : 2021:03:18 22:01:46+00:00 File Access Date/Time : 2021:03:18 22:07:09+00:00 File Inode Change Date/Time : 2021:03:18 22:07:06+00:00 File Permissions : rw-r--r-- File Type : JPEG File Type Extension : jpg MIME Type : image/jpeg JFIF Version : 1.02 Resolution Unit : None X Resolution : 1 Y Resolution : 1 Current IPTC Digest : 7a78f3d9cfb1ce42ab5a3aa30573d617 Copyright Notice : PicoCTF Application Record Version : 4 XMP Toolkit : Image::ExifTool 10.80 License : cGljb0NURnt0aGVfbTN0YWRhdGFfMXNfbW9kaWZpZWR9 Rights : PicoCTF Image Width : 2560 Image Height : 1598 Encoding Process : Baseline DCT, Huffman coding Bits Per Sample : 8 Color Components : 3 Y Cb Cr Sub Sampling : YCbCr4:2:0 (2 2) Image Size : 2560x1598 Megapixels : 4.1
ぱっと見flagはなさそう。Photoshop使ってるっぽかったのでLicenseも自然だよなーと思ってたんだけど、普通ライセンスって小文字オンリーとか大文字オンリーとかハイフン入ってたりしないっけ?と思ってbase64 decodeしてみたらflagだった。
$ echo "cGljb0NURnt0aGVfbTN0YWRhdGFfMXNfbW9kaWZpZWR9" | base64 -d picoCTF{the_m3tadata_1s_modified}
Weird File
What could go wrong if we let Word documents run programs? (aka "in-the-clear"). Download file.
weird.docm
という docファイルが配布されます。中身はtableで、coolとかstuffとか書かれている。
wordで開く時にマクロがあるぞと警告されたので、とりあえずマクロを確認してみると3つほど。そのうちのrubypythonというのを編集画面で開いてみると、flagのbase64 encodeされたのが書いてありました。
$ echo "cGljb0NURnttNGNyMHNfcl9kNG5nM3IwdXN9" | base64 -d picoCTF{m4cr0s_r_d4ng3r0us}
Matryoshka doll
Matryoshka dolls are a set of wooden dolls of decreasing size placed one inside another. What's the final one? Image: this
かわいいマトリョーシカの画像 dolls.jpg
が配布されます。
問題文から、分解したり解凍していったら良さそう。
$ file dolls.jpg dolls.jpg: PNG image data, 594 x 1104, 8-bit/color RGBA, non-interlaced
foremost
で分解してみます。
$ foremost dolls.jpg Processing: dolls.jpg |foundat=base_images/2_c.jpgUT *| $ tree output/ output/ ├── audit.txt ├── png │ └── 00000000.png └── zip └── 00000532.zip 2 directories, 3 files
zipファイルが作成されたみたい。
$ unzip output/zip/00000532.zip Archive: output/zip/00000532.zip inflating: base_images/2_c.jpg
またjpeg。これかなり繰り返しそうなので、コードを書いた。
#!/bin/bash foremost -d dolls.jpg for i in {1..10} ; do unzip output/zip/*.zip mv output output_${i} foremost -d base_images/$((${i}+1))_c.jpg done
最後エラー処理をしていなかったのでプロセスがハングしてしまったが、flag.txt
を抜き出せた様子。
$ ./solve.sh foremost: /usr/local/etc/foremost.conf: No such file or directory Processing: dolls.jpg |foundat=base_images/2_c.jpgUT *| Archive: output/zip/00000532.zip inflating: base_images/2_c.jpg foremost: /usr/local/etc/foremost.conf: No such file or directory Processing: base_images/2_c.jpg ...(中略) |foundat=flag.txtUT *| Archive: output/zip/00000155.zip inflating: flag.txt
見てみると、ただのテキストではなさそう
が、読めるのでそのままflagを写経したら通りました🙌
tunn3l v1s10n
We found this file. Recover the flag.
tunn3l_v1s10n
という謎ファイルが配布されます。
$ file tunn3l_v1s10n tunn3l_v1s10n: data $ ls -l tunn3l_v1s10n -rw-r--r--@ 1 user staff 2893454 3 19 17:25 tunn3l_v1s10n
ただのdataファイルなのに2.9Mもある。困った。困ったときの strings
コマンドでは何もでてこなかった。
foremostでも何も出てこない。
バイナリエディタで開いてみると
BM
から始まっている。
List of file signatures - Wikipedia こちらの、ファイル冒頭のマジックナンバーリストによると、このファイルはビットマップファイルの可能性が高い。
このあたりを参考に、何処が壊れているのかチェックする。pngだとpngchekがあるから、bmpcheckみたいなツールもないかな?
まずはファイルヘッダ。
- [0] bfType 2byte unsigned int ファイルタイプ 'BM'
- [2] bfSize 4byte unsigned long ファイルサイズ[byte] -> 0x2C268E
- [6] bfReserved1 2byte unsigned int 予約領域1 常に0
- [8] bfReserved2 2byte unsigned int 予約領域2 常に0
- [10] bfOffBits 4byte unsigned long ファイル先頭から画像データまでのオフセット[byte]
あ!bfOffBits
のところがBAD
(hex)ってなってる!ここは正しい値がまだわからないなぁ。
更に、次の情報ヘッダ(Bitmap Information Header)の先頭
- [14] biSize 4byte unsigned long 情報ヘッダサイズ[byte] 40
もBAD
ってなってる。これは直しやすそう。40 = 0x28 なので BA D0 00 00
-> 28 00 00 00
- [18] biWidth 4byte long 画像の幅[ピクセル] -> 0x46E = 1134
- [22] biHeight 4byte long 画像の高さ[ピクセル] -> 0x132 = 306
- [26] biPlanes 2byte unsigned int プレーン数 常に1
- [28] biBitCount 2byte unsigned int 色ビット数[bit] 1,4,8,(16),24,32 0x18 -> 24
- [30] biCompression 4byte unsigned long 圧縮形式 0,1,2,3 -> 0
- [34] biSizeImage 4byte unsigned long 画像データサイズ[byte] -> 0x2C2658 = 2893400
- [38] biXPixPerMeter 4byte long 水平解像度[dot/m] 0の場合もある -> 0x1625 = 5669
- [42] biYPixPerMeter 4byte long 垂直解像度[dot/m] 0の場合もある -> 0x1625 = 5669
- [46] biClrUsed 4byte unsigned long 格納パレット数[使用色数] 0の場合もある -> 0
- [50] biCirImportant 4byte unsigned long 重要色数 0の場合もある -> 0
このあと、54から画像データが始まっているようなので、最初にわからなかった bfOffBits
は54 = 0x36かな?
画像出たよ!
notflag{sorry}だって🥺
あとは、ファイルサイズに対して画像のサイズが小さすぎる気がする。そういえば不自然に横長だ。
いつぞやのSECCONの問題に、画像のサイズを拡張すると続きの絵が出てくるのがあったな。
ということで、binary editorで高さを適当に変えてみます。
おや!やっぱり隠れていた絵があるみたい。ちょっと広がった👍
大きくしすぎるとデータが読めなくて開けなくなるので、少しずつ高さを上げていって…
flagが出た!
Wireshark doo dooo do doo...
Can you find the flag? shark1.pcapng.
パケットキャプチャファイル、shark1.pcapng
が配布されます。
File > Export Object > HTTP... でexportすると、下記の怪しいファイルを発見。
Gur synt vf cvpbPGS{c33xno00_1_f33_h_qrnqorrs}
これはちょっとした換字暗号っぽいな?ROT13でした。
The flag is picoCTF{p33kab00_1_s33_u_deadbeef}
MacroHard WeakEdge
I've hidden a flag in this file. Can you find it? Forensics is fun.pptm
Forensics is fun.pptm
が配布されます。
$ file Forensics\ is\ fun.pptm Forensics is fun.pptm: Microsoft PowerPoint 2007+
古い形式のPowerPointらしい。
wordのときのようにマクロが設定されていたので覗いてみたけど空振り。
他、ノートを見たり色を変えてみて白で文字が書いてないか見てみたり、スライドやノートのマスタを見てみたりしたけど見つからず。
そういえばmicrosoft系のファイルは unzip
してあげると良いと言うのをctf4gで教わったのでした。
$ unzip Forensics\ is\ fun.pptm Archive: Forensics is fun.pptm inflating: [Content_Types].xml inflating: _rels/.rels inflating: ppt/presentation.xml inflating: ppt/slides/_rels/slide46.xml.rels inflating: ppt/slides/slide1.xml inflating: ppt/slides/slide2.xml (中略) inflating: docProps/core.xml inflating: docProps/app.xml inflating: ppt/slideMasters/hidden
ん、最後に怪しいファイルを発見。開いてみると、スペースで分断されたbase64っぽい文字列が。スペースを消してbase64 decodeしてみます。
$ echo "ZmxhZzogcGljb0NURntEMWRfdV9rbjB3X3BwdHNfcl96MXA1fQ" | base64 -D flag: picoCTF{D1d_u_kn0w_ppts_r_z1p5
最後 }
が無かったけど、補足してあげたら通った。
Trivial Flag Transfer Protocol
Figure out how they moved the flag.
tftp.pcapng
が配布されます。
wiresharkで開いてみると、今度はTFTP通信が多いようなので、
File > Export Object > TFTP... でextractするとこんなファイルがでてきました。
$ tree . ├── instructions.txt ├── picture1.bmp ├── plan └── program.deb
debian packageがあるようなので分解して中身覗いてみると、steghide
という実行ファイルがいるみたい。これは steghide を使うということ?そういえばbmpファイルが入ってる。
$ steghide extract -sf picture1.bmp
passphrase無しで復号を試みたけどだめでした。passphraseが何処かにあるのかなぁ。
元のpacketをもう一度眺めてみると、もう一枚画像がやり取りされているみたい。
3790 67.595239703 10.10.10.11 10.10.10.12 TFTP 63 Read Request, File: picture2.bmp, Transfer type: octet
このファイルがめちゃめちゃ大きいらしく、ずーっと通信が続いて最後は
134864 102.054588630 10.10.10.11 10.10.10.12 TFTP 60 Acknowledgement, Block: 65535
end of fileの通信無しで終わってしまっている。だからextractできなかったのかな?いや、この数値は0xFFFFだからカンストしてしまったのか…。ここから先の通信も続きっぽいな。
146679 105.164950267 10.10.10.12 10.10.10.11 TFTP 100 Data Packet, Block: 5907 (last)
ここで終了っぽい。そしてその次に3枚目。
146683 111.171248607 10.10.10.11 10.10.10.12 TFTP 63 Read Request, File: picture3.bmp, Transfer type: octet
152412 112.708052683 10.10.10.12 10.10.10.11 TFTP 252 Data Packet, Block: 2865 (last)
ここで無事終わっているみたい。これは何でextractされなかったんだろう?
念の為、もう一度TFTP通信のextractをしてみたところ、下に searching の文字と進捗バーが…。
2個目が大きすぎて抽出対象のサーチを待ちきれてなかったんだ。
今度はしっかり待って、全てのファイルを抽出してもらいましいた。
しかしファイル数が増えて解析対象が増えただけで、どうして良いものか…。
配布された.deb
ファイルを覗いてみると、その中にsteghideツールのセットが置いてあるのでこれを使ってみるのかな?でもpassphraseがわからないぞ?
ひとまず自分でいつも使っているsteghideをpassphrase無しで実行してみた。
$ steghide extract -sf picture1.bmp -xf output.txt $ steghide extract -sf picture1.bmp -xf output.txt
うーん。何も出てこない。
ここで、抽出したテキストファイル、planにかかれていた文字列をROT13で復号してみると、なんと意味のある言葉になった。
VHFRQGURCEBTENZNAQUVQVGJVGU-QHRQVYVTRAPR.PURPXBHGGURCUBGBF -> ROT13 IUSEDTHEPROGRAMANDHIDITWITH-DUEDILIGENCE.CHECKOUTTHEPHOTOS
I used the program and hid it with - due diligence. check out the photos
due diligence は相当注意してってことっぽい。
同じく instructions.txt
はROT13すると
TFTPDOESNTENCRYPTOURTRAFFICSOWEMUSTDISGUISEOURFLAGTRANSFER.FIGUREOUTAWAYTOHIDETHEFLAGANDIWILLCHECKBACKFORTHEPLAN
tftp doesn't encrypt our traffic so we must disguise our flag transfer. figure out a way to hide the flag and i will check back for the plan
あ、見る順番間違えたかな。
passphraseはもらえず、steghideを使うっぽいことが改めてわかった。
passphraseがわからずめっちゃ時間かかったけど、もう一度planを読み直すと
IUSEDTHEPROGRAMANDHIDITWITH-DUEDILIGENCE.
おや…、もしかして… DUEDILIGENCE
がpassphraseなのでは…
(ここ思いつくのに数日かかった。子供のお迎えに走って向かってる最中に気づいた。)
$ steghide extract -sf picture3.bmp -p DUEDILIGENCE -xf output.txt wrote extracted data to "output.txt". $ cat output.txt picoCTF{h1dd3n_1n_pLa1n_51GHT_18375919}
🙌
Wireshark twoo twooo two twoo...
Can you find the flag? shark2.pcapng.
shark2.pcapng
が配布されます。
oneのときと同じように
File > Export Object > HTTP... でextractすると、ファイルが沢山!
flag
というファイルもたくさんあって、それぞれflag formatに準ずるflagが書いてあるんだけど、いくつか試したところハズレっぽい。他の方法で見る必要がありそう。
なんか名前も似てるしと、picoCTF 2019 [Forensics] shark on wire 2 (300pt) みたいな方法で解くやつだったら大変だなー…。と思いつつ、やってみた。
wiresharkでパケットを開き、File > Export Packet Dissections > As Plain Text... で、wiresharkから全部テキスト形式で抽出。 -> all_traffics.txt
そういえば、DNS問い合わせの時に、
No such name A BN9PyNZN.reddshrimpandherring.com SOA a.gtld-servers.net
みたいな応答が多い気がする。そもそも BN9PyNZN
って何だ。
この形式で検索してみると、どうも base64の一部っぽい文字列
.reddshrimp関連のドメイン
.com みたいなレコードの問い合わせをしまくっているようだ。
ドメインのバリエーションは6個くらいあったけど、全部同じ内容で問い合わせしているっぽいので一つに絞って抽出してみた。
grep -E "No such name A (.+)\.reddshrimpandherring\.com .*$" all_traffics.txt > grep.txt
255件ヒット。
抽出された行の例
1606 9.199964 8.8.8.8 192.168.38.104 DNS 166 Standard query response 0xccfe No such name A 9NzCwWxd.reddshrimpandherring.com SOA a.gtld-servers.net 1614 9.236640 8.8.8.8 192.168.38.104 DNS 166 Standard query response 0x233e No such name A m/TqO+IW.reddshrimpandherring.com SOA a.gtld-servers.net 1627 9.306399 8.8.8.8 192.168.38.104 DNS 166 Standard query response 0x7487 No such name A o2ZJtOyF.reddshrimpandherring.com SOA a.gtld-servers.net 1634 9.388061 18.217.1.57 192.168.38.104 DNS 166 Standard query response 0xdf26 No such name A cGljb0NU.reddshrimpandherring.com SOA a.gtld-servers.net 1640 9.506471 8.8.8.8 192.168.38.104 DNS 166 Standard query response 0x8872 No such name A um0kpvjf.reddshrimpandherring.com SOA a.gtld-servers.net
おや、問い合わせ先が 8.8.8.8
じゃないものがあるな。
最初にでてきた不明な問い合わせ先のレコードのサブドメイン cGljb0NU
をbase64 decode してみると
$ echo "cGljb0NU" | base64 -D picoCT
来たねこれ。
という事で、ここから本物のDNS問い合わせっぽいものを抜いて(下記は正規表現で使ったもの。全部テキストエディタでやった…)
^.*8.8.8.8.*$\n (空に置換)
サブドメインだけ抽出して
^.*No such name A (.+)\.reddshrimpandherring\.com .*$\n ($1 に置換)
base64 decode!
$ echo "cGljb0NURntkbnNfM3hmMWxfZnR3X2RlYWRiZWVmfQ==" | base64 -D picoCTF{dns_3xf1l_ftw_deadbeef}
Hoooooooooooo!
Disk, disk, sleuth!
Use
srch_strings
from the sleuthkit and some terminal-fu to find a flag in this disk image: dds1-alpine.flag.img.gz
dds1-alpine.flag.img.gz
圧縮されたimage fileが配布されます。
$ file dds1-alpine.flag.img.gz dds1-alpine.flag.img.gz: gzip compressed data, was "dds1-alpine.flag.img", last modified: Tue Mar 16 00:19:44 2021, from Unix, original size 134217728 $ gzip -d dds1-alpine.flag.img.gz $ file dds1-alpine.flag.img dds1-alpine.flag.img: DOS/MBR boot sector; partition 1 : ID=0x83, active, start-CHS (0x0,32,33), end-CHS (0x10,81,1), startsector 2048, 260096 sectors
このimageをmountして中を覗いてみよう。
パーティションのoffsetを確認します。参考: Raw Disk Image file を Ubuntu でマウントする方法 | 穀風
$ parted dds1-alpine.flag.img GNU Parted 3.2 Using /root/ctf/pico/dds1-alpine.flag.img Welcome to GNU Parted! Type 'help' to view a list of commands. (parted) u Unit? [compact]? B (parted) print Model: (file) Disk /root/ctf/pico/dds1-alpine.flag.img: 134217728B Sector size (logical/physical): 512B/512B Partition Table: msdos Disk Flags: Number Start End Size Type File system Flags 1 1048576B 134217727B 133169152B primary ext3 boot (parted) q
マウント。
$ mount -t ext4 -o loop,rw,offset=1048576 dds1-alpine.flag.img /tmp
mountできたので、中を覗いてみいます。
$ grep pico -r ./ ./boot/System.map-virt:ffffffff81399ccf t pirq_pico_get ./boot/System.map-virt:ffffffff81399cee t pirq_pico_set ./boot/System.map-virt:ffffffff820adb46 t pico_router_probe ./boot/syslinux.cfg: SAY picoCTF{f0r3ns1c4t0r_n30phyt3_a69a712c}
flagいた!
Disk, disk, sleuth! II
All we know is the file with the flag is named
down-at-the-bottom.txt
... Disk image: dds2-alpine.flag.img.gz
さっきと同様、圧縮されたimageファイル dds2-alpine.flag.img.gz
が配布されます。
回答して中身を確認。
$ gzip -d dds2-alpine.flag.img.gz $ file dds2-alpine.flag.img dds2-alpine.flag.img: DOS/MBR boot sector; partition 1 : ID=0x83, active, start-CHS (0x0,32,33), end-CHS (0x10,81,1), startsector 2048, 260096 sectors
パーティションのoffset確認
parted dds2-alpine.flag.img GNU Parted 3.2 Using /root/ctf/pico/dds2-alpine.flag.img Welcome to GNU Parted! Type 'help' to view a list of commands. (parted) u Unit? [compact]? B (parted) print Model: (file) Disk /root/ctf/pico/dds2-alpine.flag.img: 134217728B Sector size (logical/physical): 512B/512B Partition Table: msdos Disk Flags: Number Start End Size Type File system Flags 1 1048576B 134217727B 133169152B primary ext3 boot (parted) q
ここもさっきと同じっぽい。
マウント。
$ mount -t ext4 -o loop,rw,offset=1048576 dds2-alpine.flag.img /tmp
さっきと同じくgrepしてみる
$ grep pico -r ./ ./boot/System.map-virt:ffffffff81399ccf t pirq_pico_get ./boot/System.map-virt:ffffffff81399cee t pirq_pico_set ./boot/System.map-virt:ffffffff820adb46 t pico_router_probe
まぁ全く同じではでてこないか。問題文より、flagのあるfile名は down-at-the-bottom.txt
とのことなので、ファイルを検索してみます。
$ find -name "down-at-the-bottom.txt" ./root/down-at-the-bottom.txt
あった。
$ cat ./root/down-at-the-bottom.txt _ _ _ _ _ _ _ _ _ _ _ _ _ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ ( p ) ( i ) ( c ) ( o ) ( C ) ( T ) ( F ) ( { ) ( f ) ( 0 ) ( r ) ( 3 ) ( n ) \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ _ _ _ _ _ _ _ _ _ _ _ _ _ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ ( s ) ( 1 ) ( c ) ( 4 ) ( t ) ( 0 ) ( r ) ( _ ) ( n ) ( 0 ) ( v ) ( 1 ) ( c ) \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ _ _ _ _ _ _ _ _ _ _ _ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ ( 3 ) ( _ ) ( 8 ) ( 2 ) ( 4 ) ( 8 ) ( 9 ) ( d ) ( b ) ( f ) ( } ) \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/
でた。
Surfing the Waves
While you're going through the FBI's servers, you stumble across their incredible taste in music. One main.wav you found is particularly interesting, see if you can find the flag!
main.wav
が配布されます。
$ file main.wav main.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 2736 Hz
音声ファイルといえば、いつぞやのCTFで Audacity
というのを入れていたので使ってみる。普通に聞いてみると短すぎてよくわからないので、かなりゆっくりめに設定して聞いてみると「カタカタカタ」みたいなタイプ音っぽいのが聞こえる。
とても拡大してみると、なんだかパルス波っぽい。
どんな値が入ってるのかな?と思ったので、データを取り出してみることに。
更に、こんなwriteupを見つけたので、抽出コードもお借りしてみた。
CTFtime.org / Affinity CTF 2019 - Quals / XY_FUN / Writeup
import struct with open('main.wav', 'rb') as f: data = f.read()[44:] raw_bytes = [struct.unpack('h', data[i : i+2])[0] for i in range(0,len(data), 2)] print(raw_bytes)
実行結果
$ python solve.py [2003, 2509, 2006, 1505, 2000, 8502, 4505, 3508, 4505, 2505, 4508, 2004, 2004, 8503, 4002, 2009, 4006, 5507, 4006, 8000, 2001, 8503, 4008, 3508, 4006, 8002, 4502, 4002, 2004, 1009, 4505, 1002, 4501, 5503, 4504, 3004, 4001, 5000, 4009, 8504, 4009, 8008, 2502, 2508, 1001, 6004, 4007, 5509, 4004, 7501, 4508, 1000, 4002, 8502, 4500, 2006, 4505, 3008, 2007, 1007, 4009, 8008, 4506, 3502, 4001, 7505, 4506, 1003, 4503, 5504, 2001, 1000, 4001, 1501, 4501, 2504, 2008, 1001, 4006, 8004, 4503, 1000, 1004, 6008, 4000, 4008, 4509, 2006, 4001, 8509, 4009, 7507, 2000, 1000, 4500, 2502, 4006, 2504, 4005, 5500, 4509, 1008, 4506, 5507, 2000, 8003, 4006, 5502, 4009, 8506, 2001, 8003, 4508, 4505, 4001, 1504, 4509, 4007, 4005, 4005, 4003, 5507, 4006, 7006, 4007, 3506, 2004, 1008, 4004, 5500, 4002, 7502, 4507, 1007, ...
なんか500の倍数付近の値が多いっぽい!
さっきのwriteup通り、LとRに分けてplotしてみた。
整然としている。
なんか横に8桁ずつ並びそうだったので、点があるところと無いところで0,1になおしてみる。
00110101 00110010 00110011 00111111 00010111 00010111 00011011 00111011 00110011 10100111 10110001 00100111 00101010 00110111 00100010 00100110
ascii変換してもflagになりそうにないなぁ。このグラフは暫く眺めてガチャガチャ試してみたけど方針転換。
データの出力をもう一度よく見てみると、
範囲: 1000 - 8500 -> (8500-1000)//500 = 15 16 段階
16ってほら、hexとか色々応用できそうだよね、という事で、どういう出力になっているのか統計をとってみる。
import struct import binascii with open('main.wav', 'rb') as f: data = f.read()[44:] raw_bytes = [struct.unpack('h', data[i:i+2])[0] for i in range(0,len(data), 2)] hist = {} for r in raw_bytes: i = r // 500 - 2 if r not in hist.keys(): hist[r] = hex(i)[2:] print(hist)
※最初はhistgram出してたのだけど内容が変わったので全然histgramじゃないのにhist
って変数名になっている。
実行結果
$ python solve.py {2003: '2', 2509: '3', 2006: '2', 1505: '1', 2000: '2', 8502: 'f', 4505: '7', 3508: '5', 2505: '3', 4508: '7', 2004: '2', 8503: 'f', 4002: '6', 2009: '2', 4006: '6', 5507: '9', 8000: 'e', 2001: '2', 4008: '6', 8002: 'e', 4502: '7', 1009: '0', 1002: '0', 4501: '7', 5503: '9', 4504: '7', 3004: '4', 4001: '6', 5000: '8', 4009: '6', 8504: 'f', 8008: 'e', 2502: '3', 2508: '3', 1001: '0', 6004: 'a', 4007: '6', 5509: '9', 4004: '6', 7501: 'd', 1000: '0', 4500: '7', 3008: '4', 2007: '2', 1007: '0', 4506: '7', 3502: '5', 7505: 'd', 1003: '0', 4503: '7', 5504: '9', 1501: '1', 2504: '3', 2008: '2', 8004: 'e', 1004: '0', 6008: 'a', 4000: '6', 4509: '7', 8509: 'f', 7507: 'd', 4005: '6', 5500: '9', 1008: '0', 8003: 'e', 5502: '9', 8506: 'f', 1504: '1', 4003: '6', 7006: 'c', 3506: '5', 7502: 'd', 4507: '7', 3003: '4', 3006: '4', 3501: '5', 7503: 'd', 1005: '0', 5505: '9', 8001: 'e', 1503: '1', 2500: '3', 5002: '8', 3507: '5', 5006: '8', 7005: 'c', 6000: 'a', 1507: '1', 8006: 'e', 8508: 'f', 7500: 'd', 5506: '9', 2005: '2', 2002: '2', 1500: '1', 6009: 'a', 6003: 'a', 3009: '4', 3509: '5', 5008: '8', 3500: '5', 1502: '1', 3505: '5', 8507: 'f', 1006: '0', 5501: '9', 7002: 'c', 1508: '1', 6005: 'a', 2506: '3', 8500: 'f', 8005: 'e', 3000: '4', 8007: 'e', 3001: '4', 7506: 'd', 8009: 'e', 6007: 'a', 7000: 'c', 8505: 'f', 2501: '3', 3504: '5', 5007: '8', 6001: 'a', 5003: '8', 7508: 'd', 3005: '4', 1506: '1', 7008: 'c', 5004: '8', 5508: '9', 7001: 'c', 3007: '4', 5009: '8', 7003: 'c', 8501: 'f', 5005: '8', 5001: '8', 3002: '4', 2507: '3', 3503: '5', 2503: '3', 1509: '1', 6002: 'a', 6501: 'b', 7504: 'd', 7509: 'd', 6507: 'b', 6502: 'b', 7004: 'c', 6500: 'b', 6505: 'b', 6504: 'b', 7007: 'c', 6503: 'b', 6508: 'b', 7009: 'c', 6006: 'a'}
16個あるのでせっかくだから16進数で出してみた。
これつなげたらflagにならないかな?
# 上記のhistを出すコードに追記 flag = "" for r in raw_bytes: flag += hist[r] #print(flag) print(binascii.unhexlify(''.join(flag)).decode())
実行結果
#!/usr/bin/env python3 import numpy as np from scipy.io.wavfile import write from binascii import hexlify from random import random with open('generate_wav.py', 'rb') as f: content = f.read() f.close() # Convert this program into an array of hex values hex_stuff = (list(hexlify(content).decode("utf-8"))) # Loop through the each character, and convert the hex a-f characters to 10-15 for i in range(len(hex_stuff)): if hex_stuff[i] == 'a': hex_stuff[i] = 10 elif hex_stuff[i] == 'b': hex_stuff[i] = 11 elif hex_stuff[i] == 'c': hex_stuff[i] = 12 elif hex_stuff[i] == 'd': hex_stuff[i] = 13 elif hex_stuff[i] == 'e': hex_stuff[i] = 14 elif hex_stuff[i] == 'f': hex_stuff[i] = 15 # To make the program actually audible, 100 hertz is added from the beginning, then the number is multiplied by # 500 hertz # Plus a cheeky random amount of noise hex_stuff[i] = 1000 + int(hex_stuff[i]) * 500 + (10 * random()) def sound_generation(name, rand_hex): # The hex array is converted to a 16 bit integer array scaled = np.int16(np.array(hex_stuff)) # Sci Pi then writes the numpy array into a wav file write(name, len(hex_stuff), scaled) randomness = rand_hex # Pump up the music! # print("Generating main.wav...") # sound_generation('main.wav') # print("Generation complete!") # Your ears have been blessed # picoCTF{mU21C_1s_1337_b040e2da}
まさかflag(に付随する文章)こんなに長いと思わんかった…。
情報を圧縮する方法ばかりに目が向いていて、この方法は全然思いつかなかった。16段階あるな?以外に何かヒントあったのだろうか…。
Milkslap
🥛
この牛乳にサイトへのリンクが貼ってあり、ミルクをかけられる男性が。
マウスカーソルの位置によってgifのコマが変わるっぽく、ミルをかけ始めからかけ終わるまでが見れる。
とっっっっっても縦長の画像を、どうやらカーソルの場所に合わせてoffsetを変えてみせることでgifっぽく表示しているみたい。
この画像をDLしてきて解析してみることに。
stego-toolkit立ち上げて、上から順に試していきます。
stegoveritasを試してみると、めっちゃ時間がかかった上でめっちゃ色々解析結果のoutput fileをくれました。実行中の出力サンプル
$ stegoveritas concat_v.png stegoveritas concat_v.png Running Module: SVImage +---------------------------+------+ | Image Format | Mode | +---------------------------+------+ | Portable network graphics | RGB | +---------------------------+------+ +----------+------------------+------------------------------------------------------------------------------------------------+-----------+ | Offset | Carved/Extracted | Description | File Name | +----------+------------------+------------------------------------------------------------------------------------------------+-----------+ | 0x699677 | Carved | LZMA compressed data, properties: 0xB6, dictionary size: 0 bytes, uncompressed size: 108 bytes | 699677.7z | | 0x699677 | Extracted | LZMA compressed data, properties: 0xB6, dictionary size: 0 bytes, uncompressed size: 108 bytes | 699677 | | 0x699857 | Carved | LZMA compressed data, properties: 0xB6, dictionary size: 0 bytes, uncompressed size: 108 bytes | 699857.7z | | 0x699857 | Extracted | LZMA compressed data, properties: 0xB6, dictionary size: 0 bytes, uncompressed size: 108 bytes | 699857 | | 0x699a37 | Carved | LZMA compressed data, properties: 0xB6, dictionary size: 0 bytes, uncompressed size: 108 bytes | 699A37.7z | | 0x699a37 | Extracted | LZMA compressed data, properties: 0xB6, dictionary size: 0 bytes, uncompressed size: 108 bytes | 699A37 | | 0x699fd7 | Carved | LZMA compressed data, properties: 0xB6, dictionary size: 0 bytes, uncompressed size: 108 bytes | 699FD7.7z | | 0x699fd7 | Extracted | LZMA compressed data, properties: 0xB6, dictionary size: 0 bytes, uncompressed size: 108 bytes | 699FD7 | | 0x69a1b7 | Carved | LZMA compressed data, properties: 0xB6, dictionary size: 0 bytes, uncompressed size: 108 bytes | 69A1B7.7z | | 0x69a1b7 | Extracted | LZMA compressed data, properties: 0xB6, dictionary size: 0 bytes, uncompressed size: 108 bytes | 69A1B7 | | 0x69a397 | Carved | LZMA compressed data, properties: 0xB6, dictionary size: 0 bytes, uncompressed size: 108 bytes | 69A397.7z | | 0x69a397 | Extracted | LZMA compressed data, properties: 0xB6, dictionary size: 0 bytes, uncompressed size: 108 bytes | 69A397 | | 0x74067b | Carved | LZMA compressed data, properties: 0xB6, dictionary size: 0 bytes, uncompressed size: 108 bytes | 74067B.7z | | 0x74067b | Extracted | LZMA compressed data, properties: 0xB6, dictionary size: 0 bytes, uncompressed size: 108 bytes | 74067B | | 0x74085b | Carved | LZMA compressed data, properties: 0xB6, dictionary size: 0 bytes, uncompressed size: 108 bytes | 74085B.7z | | 0x74085b | Extracted | LZMA compressed data, properties: 0xB6, dictionary size: 0 bytes, uncompressed size: 108 bytes | 74085B | | 0x740a3b | Carved | LZMA compressed data, properties: 0xB6, dictionary size: 0 bytes, uncompressed size: 108 bytes | 740A3B.7z | | 0x740a3b | Extracted | LZMA compressed data, properties: 0xB6, dictionary size: 0 bytes, uncompressed size: 108 bytes | 740A3B | | 0x740c1b | Carved | LZMA compressed data, properties: 0xB6, dictionary size: 0 bytes, uncompressed size: 108 bytes | 740C1B.7z | | 0x740c1b | Extracted | LZMA compressed data, properties: 0xB6, dictionary size: 0 bytes, uncompressed size: 108 bytes | 740C1B | | 0x740dfb | Carved | LZMA compressed data, properties: 0xB6, dictionary size: 0 bytes, uncompressed size: 108 bytes | 740DFB.7z | | 0x740dfb | Extracted | LZMA compressed data, properties: 0xB6, dictionary size: 0 bytes, uncompressed size: 108 bytes | 740DFB | | 0x740fdb | Carved | LZMA compressed data, properties: 0xB6, dictionary size: 0 bytes, uncompressed size: 108 bytes | 740FDB.7z | | 0x740fdb | Extracted | LZMA compressed data, properties: 0xB6, dictionary size: 0 bytes, uncompressed size: 108 bytes | 740FDB | | 0x7411bb | Carved | LZMA compressed data, properties: 0xB6, dictionary size: 0 bytes, uncompressed size: 108 bytes | 7411BB.7z | | 0x7411bb | Extracted | LZMA compressed data, properties: 0xB6, dictionary size: 0 bytes, uncompressed size: 108 bytes | 7411BB | +----------+------------------+------------------------------------------------------------------------------------------------+-----------+ +----------+------------------+------------------------------------------------------------------------------------------------------+-----------+ ...(中略) Found something worth keeping! ASCII text
おうおう?何か見つけたとな?ここまで解析に小一時間かかっていた気がしますが、この状態でハングした疑惑。
まだ解析は続いている(orハングしてる)みたいだけど、一旦ここで出力結果を確認してみたところ、
$ grep picoCTF -r ./results/* Binary file ./results/keepers/1616196387.6978421 matches
何やらマッチするのがいるらしい。
$ strings results/keepers/1616196387.6978421 picoCTF{imag3_m4n1pul4t10n_sl4p5}
🙌
もう少しシュッと解ける想定解がありそうだけど、stegoveritasで解けました。
Very very very Hidden
Finding a flag may take many steps, but if you look diligently it won't be long until you find the light at the end of the tunnel. Just remember, sometimes you find the hidden treasure, but sometimes you find only a hidden map to the treasure. try_me.pcap
try_me.pcap
が配布されます。
今回はヒントも思わせぶり。
I believe you found something, but are there any more subtle hints as random queries?
The flag will only be found once you reverse the hidden message.
wiresharkでパケットを開いてみます。
いくつか前の問題と同じように、一旦DNSのプロトルの通信だけにフィルタしてみます。
他、http通信で画像をやり取りしているみたいなので、http通信の中身を抜いてみたり。
こんなduck.png
とevil_duck.png
を入手したけど、手持ちのツールではなんの情報も得られなかった。
http通信で絞ると、最後の方に powershell.org
に通信している。RequestとResponse。
[Request] GET / HTTP/1.1\r\n [Expert Info (Chat/Sequence): GET / HTTP/1.1\r\n] [GET / HTTP/1.1\r\n] [Severity level: Chat] [Group: Sequence] Request Method: GET Request URI: / Request Version: HTTP/1.1 Host: powershell.org\r\n
[Response] HTTP/1.1 301 Moved Permanently\r\n [Expert Info (Chat/Sequence): HTTP/1.1 301 Moved Permanently\r\n] [HTTP/1.1 301 Moved Permanently\r\n] [Severity level: Chat] [Group: Sequence] Response Version: HTTP/1.1 Status Code: 301 [Status Code Description: Moved Permanently] Response Phrase: Moved Permanently Report-To: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report?s=ELOu0tEaWLx24EBcHYKNZW9phLwKCnd76o%2BK46lfooraLRhijEOd3eTMTm1dzBpmOUQwc7r%2BOJrCW4zCEoV77N16I0Sh35UF01rZGxmsJg%3D%3D"}],"group":"cf-nel","max_age":604800}\r\n
Responseの方に入ってた Report-To
を URL Decode してみたけど有益な情報にはならず。
他、dnsとhttp以外にも、下記のプロトコルの通信が。
- MDNS
- NBNS
- LLMNR
- ARP
- SSDP
- TCP
- UDP
MDNS, NBNS, LLMNRの通信にも powershell
を示唆する情報がちらほら。
ヒントにクエリ、というのがあったので、クエリを送っているプロトコルを眺めてみます。
MDNSのクエリは宛先が wpad.local or powreshell.local。
うーん、よくわからないけどpowershellのツールを探して試す感じかな?
というところで競技終了。powershell環境全く整えていなかったので、時間かかりそうと思ったのもあり後回しにしていました。
Ubuntuにもpowershellをinstallできるとのことで、この機会にinstallして使ってみることにしました。下記の記事を見ながら ubuntu 20.04 のVMにpowershellをinstall。
参考: Ubuntu20.04にPowerShellをインストールして使用する方法 - Tutorial Crawler
めっちゃ簡単に入った。こんなに簡単なら競技中にもトライすればよかった。食わず嫌いしすぎた。
githubで PNG powershell
みたいな感じで探したところ、一番上にこのツールが出てきた。
このツール、埋め込むだけだなー、と思ってもうしばらくネットの海をさまよってみると
なんかさっきのツールで埋め込んだデータを取り出すためのツールっぽいぞ…!
うん?見たことあるアイコン…。@imurasheen さんのリポジトリじゃん₍₍ (ง ˙ω˙)ว ⁾⁾
powershellを入れただけの環境では動作しなかったので、試行錯誤の結果、Ubuntuで動かすには libc6-dev
and libgdiplus
が必要らしい。インストールします。
参考: How do you use System.Drawing in .NET Core? - Scott Hanselman's Blog
$ sudo apt update -y $ sudo apt install -y libc6-dev $ sudo apt install -y libgdiplus
Extractを試みます。
> Import-Module ./Extract-Invoke-PSImage.ps1 > Extract-Invoke-PSImage -Image ../evil_duck.png -Out ../result.ps1 [Oneliner to extract embedded payload] sal a New-Object;Add-Type -AssemblyName "System.Drawing";$g=a System.Drawing.Bitmap("/home/kusuwada/workspace/pico2021/evil_duck.png");$o=a Byte[] 1490837;(0..811)|%{foreach($x in(0..1222)){$p=$g.GetPixel($x,$_);$o[$_*1223+$x]=([math]::Floor(($p.B-band15)*16)-bor($p.G-band15))}};$g.Dispose();[System.Text.Encoding]::ASCII.GetString($o[0..1490831])|Out-File $Out [First 50 characters of extracted payload] $out = "flag.txt" $enc = [system.Text.Encoding]::
おおっ!なんかそれっぽい出力が出たぞ!早速 result.ps1
を実行してみます。
> ./result.ps1 ParserError: /.../result.ps1:16 Line | 16 | ???8?????D??P???s7`A?H???? ??E?????1A?}?9?4s | ~ | Unexpected token '}' in expression or statement.
うーん、できない。中身を見てみよう。
$out = "flag.txt" $enc = [system.Text.Encoding]::UTF8 $string1 = "HEYWherE(IS_tNE)50uP?^DId_YOu(]E@t*mY_3RD()B2g3l?" $string2 = "8,:8+14>Fx0l+$*KjVD>[o*.;+1|*[n&2G^201l&,Mv+_'T_B" $data1 = $enc.GetBytes($string1) $bytes = $enc.GetBytes($string2) for($i=0; $i -lt $bytes.count ; $i++) { $bytes[$i] = $bytes[$i] -bxor $data1[$i] } [System.IO.File]::WriteAllBytes("$out", $bytes) ??????I?4I?1?-3Xc?????????E??\???q1dM?A??????G?????;C?x ?>?4x???T?0]CXs?P??H?????D57??~??$?_?<9??????M?:B?5?#3Xi ???8?????D??P???s7`A?H??????E?????1A?}?9?4s ...(略)
この最後の書き出しの後でエラーになってるっぽい。
が、その前の string1
と string2
を xor してるっぽい処理、これだけでflagになりそうな予感がする。ここだけを抜き出して実行してみると
$out = "flag.txt" $enc = [system.Text.Encoding]::UTF8 $string1 = "HEYWherE(IS_tNE)50uP?^DId_YOu(]E@t*mY_3RD()B2g3l?" $string2 = "8,:8+14>Fx0l+$*KjVD>[o*.;+1|*[n&2G^201l&,Mv+_'T_B" $data1 = $enc.GetBytes($string1) $bytes = $enc.GetBytes($string2) for($i=0; $i -lt $bytes.count ; $i++) { $bytes[$i] = $bytes[$i] -bxor $data1[$i] } [System.IO.File]::WriteAllBytes("$out", $bytes)
実行
> ./solve.ps1
$ cat flag.txt picoCTF{n1c3_job_f1nd1ng_th3_s3cr3t_in_the_im@g3}
やったー!フラグゲット٩(๑❛ᴗ❛๑)尸
powershellをUbuntu 20.04 に入れるまでは順調だったのだけど、そこから解くためのツールを探したり、ツールを動かすのに必要なライブラリを探したりするのに時間がかかった。
冒頭で「競技中やればよかった!」と言っていたけど、その後結構ハマったので powershell初心者としてはやはり手を出さないのが正解だったかな。