好奇心の足跡

飽きっぽくすぐ他のことをしてしまうので、忘れないため・形にして頭に残すための備忘録。

picoCTF2021 [Forengics] writeup

picoCTF 2021 [Forensics] 分野の writeup

2021年3月16日~3月30日(日本時間では3月17日~3月31日)に開催された中高生向けのCTF大会、picoCTFの[Forensics]分野のwriteupです。
その他のジャンルについてはこちらを参照

tech.kusuwada.com

information

Files can always be changed in a secret way. Can you find the flag? cat.jpg

cat.jpgが配布されます。

f:id:kusuwada:20210331134635j:plain:w300

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されたのが書いてありました。

f:id:kusuwada:20210331131606p:plain

$ 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 が配布されます。

f:id:kusuwada:20210331131731j:plain:h300

問題文から、分解したり解凍していったら良さそう。

$ 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                

見てみると、ただのテキストではなさそう

f:id:kusuwada:20210331131809p:plain

が、読めるのでそのまま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でも何も出てこない。

バイナリエディタで開いてみると

f:id:kusuwada:20210331131855p:plain

BMから始まっている。

List of file signatures - Wikipedia こちらの、ファイル冒頭のマジックナンバーリストによると、このファイルはビットマップファイルの可能性が高い。

Bitmapファイルフォーマット

このあたりを参考に、何処が壊れているのかチェックする。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かな?

f:id:kusuwada:20210331132740p:plain

画像出たよ!
notflag{sorry}だって🥺

あとは、ファイルサイズに対して画像のサイズが小さすぎる気がする。そういえば不自然に横長だ。
いつぞやのSECCONの問題に、画像のサイズを拡張すると続きの絵が出てくるのがあったな。
ということで、binary editorで高さを適当に変えてみます。

f:id:kusuwada:20210331132859p:plain

おや!やっぱり隠れていた絵があるみたい。ちょっと広がった👍
大きくしすぎるとデータが読めなくて開けなくなるので、少しずつ高さを上げていって…

f:id:kusuwada:20210331132919p:plain

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個目が大きすぎて抽出対象のサーチを待ちきれてなかったんだ。
今度はしっかり待って、全てのファイルを抽出してもらいましいた。

f:id:kusuwada:20210331133223p:plain

しかしファイル数が増えて解析対象が増えただけで、どうして良いものか…。 配布された.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
(空に置換)

f:id:kusuwada:20210331133513p:plain

サブドメインだけ抽出して

^.*No such name A (.+)\.reddshrimpandherring\.com .*$\n
($1 に置換)

f:id:kusuwada:20210331133605p:plain

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 というのを入れていたので使ってみる。普通に聞いてみると短すぎてよくわからないので、かなりゆっくりめに設定して聞いてみると「カタカタカタ」みたいなタイプ音っぽいのが聞こえる。

とても拡大してみると、なんだかパルス波っぽい。

f:id:kusuwada:20210331133718p:plain

どんな値が入ってるのかな?と思ったので、データを取り出してみることに。
更に、こんな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してみた。

f:id:kusuwada:20210331133826p:plain

整然としている。
なんか横に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

🥛

この牛乳にサイトへのリンクが貼ってあり、ミルクをかけられる男性が。

f:id:kusuwada:20210331133949p:plain

マウスカーソルの位置によって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のプロトルの通信だけにフィルタしてみます。

f:id:kusuwada:20210518062258p:plain

他、http通信で画像をやり取りしているみたいなので、http通信の中身を抜いてみたり。

f:id:kusuwada:20210518062318p:plain:w300 f:id:kusuwada:20210518062329p:plain:w300

こんなduck.pngevil_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 みたいな感じで探したところ、一番上にこのツールが出てきた。

github.com

このツール、埋め込むだけだなー、と思ってもうしばらくネットの海をさまよってみると

github.com

なんかさっきのツールで埋め込んだデータを取り出すためのツールっぽいぞ…!
うん?見たことあるアイコン…。@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
...(略)

この最後の書き出しの後でエラーになってるっぽい。
が、その前の string1string2 を 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初心者としてはやはり手を出さないのが正解だったかな。