SECCON 2019 Online CTF に参加した (Beeeeeeeeeer, Sandstorm, repair, PPKeyboard)
2019-10-20
SECCON 2019 Online CTFにp3r0zとして参加しました。2131点で42位でした。
僕はBeeeeeeeeeerとSandstormとrepairとPPKeyboardを解いたので、その解法を簡単に書きたいと思います。
Beeeeeeeeeer
問題ファイルをダウンロードしてみると、中身はシェルスクリプトのようです。 整形してみても、コマンド置換を使ったり$'string'
を使ったりして難読化しています。 良い解き方が思い浮かばなかったので、set -x
をして地道に1ステップずつ追っていきました。部分的にset -x
を検出してexitするコードや、後半の方でshutdown
を呼び出すコードが含まれているので注意しましょう(僕はdockerを使って実行していたので問題ありませんでした)。
最終的に要点だけ抜き出すとこんな感じになります。
# 1つ目のscriptのなかでexport
export S1=hogefuga
# 2つ目のscriptのなかでexport
export n=3
# 3つ目のscriptのなかで実行
read _____ # bash
echo SECCON{$S1$n$_____}
# => SECCON{hogefuga3bash}
Sandstorm
Sandstormは画像見たらAdamと書いてあって、exif見たらインターレース方式にAdam7と書いてあったのでWikipediaを参考に画像を走査してみたら8×8のパターンで抽出したときにQRコードが出てきた。https://t.co/n3ZpseGnLwhttps://t.co/EeIKqUvobM
— Yuki Igarashi (@bonprosoft)
問題ファイルを落とすとこんな感じです。
exifを見てみましょう。
ExifTool Version Number : 11.70
File Name : sandstorm.png
File Size : 62 kB
File Modification Date/Time : 2019:10:19 17:30:43+09:00
File Access Date/Time : 2019:10:20 19:40:51+09:00
File Inode Change Date/Time : 2019:10:19 18:03:54+09:00
File Permissions : rw-r--r--
File Type : PNG
File Type Extension : png
MIME Type : image/png
Image Width : 584
Image Height : 328
Bit Depth : 8
Color Type : RGB with Alpha
Compression : Deflate/Inflate
Filter : Adaptive
Interlace : Adam7 Interlace
Background Color : 255 255 255
Image Size : 584x328
Megapixels : 0.192
InterlaceとしてAdam7が使われています。画像中でAdamとか書いてあるので、きっとインターレースに何か鍵があるのでしょう。
とりあえずインターレースで読み込む順番を再現してみます。Adam 7アルゴリズムはWikipediaを見るととても簡単に実装できそうです。
adam7.py
|
|
実行してみるとこんな感じです。
明らかに8×8で読み込んだとき(一番左上の画像)にQRコードのような模様が出ていて怪しいので、8×8で読み込むピクセルだけを集めた画像を作ります。
というわけで答えはSECCON{p0nlMpzlCQ5AHol6}
です
repair
repairを解くときに使ったスクリプトをアップしておきますhttps://t.co/F5qjsTSka5
— Yuki Igarashi (@bonprosoft)
最初愛のこもった手打ちRIFFヘッダーをつけようとしたけど、コーデックもわからないし途中でやめて、PyAVを使ってmoviチャンクに含まれる各00dcをフレームとして突っ込んだ。ちなみにコーデックはcinepakでいけた。
960×540という名前のついたよくわからないファイルが渡されます。 とりあえずstringsにかけてみると、Lavf58.28.100といった単語が出てくるので、おそらく動画や音声のファイルかなという推測します。 他にも、JUNK
やLIST
といったキーワードを調べていくと、これはどうやらRIFF形式のファイルだということがわかります。 RIFFについては調べると詳しい情報がたくさん出てきます。
- https://docs.microsoft.com/en-us/windows/win32/directshow/avi-riff-file-reference
- http://hp.vector.co.jp/authors/VA030681/VBTips/AVI.htm
- http://eternalwindows.jp/winmm/mminput/mminput04.html
- https://makiuchi-d.github.io/mksoft/doc/avifileformat.html
- https://books-nekoya.jp/Programming/RIFF/riff.html まずはそのままVLCなどで再生してみようとしましたが、もちろん再生できない。バイナリを眺めると、どうやら動画のヘッダー部分が欠落しているようです。 つまり動画の正しいヘッダーを付け足して、動画の中身を閲覧することができれば答えに近づけそうです。
さて動画のヘッダーを作るには、前述のWebサイトの情報から、フレームのサイズやフレーム数、コーデックなどの情報が必要になります。フレームサイズはファイル名から960×540であると推測します。フレーム数も、動画ファイル後半にあるidx1
セクション(動画のインデックスセクション)から257であることがわかりました。問題はコーデックです。
最初はいくつか手打ちでRIFFヘッダーを作って試しましたが、自分のヘッダーが間違っていて再生できないのか、あるいはコーデックが間違っていて再生できないのかわからないので、最終的にffmpegのAPIを使って、半分自動でコーデックを求めて、映像を画像に落とし込むスクリプトを書きました。
このスクリプトのfind_available_codecs
関数を呼び出すと以下の結果が得られます。
['qpeg', 'smc', 'tiertexseqvideo', 'bintext', 'kgv1', 'cavs', 'wnv1', 'tmv', 'gdv', 'arbc', 'idf', 'rscc', 'asv2', 'msvideo1', 'amv', 'tscc
2', 'mpegvideo', 'ultimotion', 'mxpeg', 'vb', 'h261', 'mpeg1video', 'dfa', 'fic', 'xbin', 'flic', 'eatgv', 'sp5x', 'lscr', 'dirac', 'mvc1',
'fraps', 'cinepak', 'mpeg2video']
結構いっぱいあるやん…。とりあえずそれぞれのコーデックでデコードするプログラムを回して(前述のプログラムのmain
関数を参照)画像をそれぞれ見たら、cinepak
のときに正しくデコードできていました。
PPKeyboard
PE(64bit)とpcapが渡されます。とりあえずpcapを開いてみると、どうやらUSBの通信のようです。
PEも解析してみます。.rdata
セクションを見てみると、DDJ-XP1
という文字が見つかりました。どうやらDJ用途のMIDIコントローラーのようです。 また関数呼び出しを見てみると、WINMM.dllに含まれるmidiInOpen
やmidiInStart
などを呼び出している箇所があります。このことから、どうやらこのpcapはこのプログラムとMIDIコントローラーの通信をキャプチャしたものではないかと考えます。
MIDIコントローラーと通信を行うプログラムの解説記事を探してみると、MIDIキーボードとの通信は以下のレイアウトの構造体で行うようです。
typedef struct
{
uint8_t header;
uint8_t byte1;
uint8_t byte2;
uint8_t byte3;
} midiPacket;
// 特にNoteOn(押した状態)を通知する場合は…
typedef struct
{
uint8_t header; // 0x9
uint8_t byte1; // 0x9n, n=channel number [0x0,0xF]
uint8_t noteNumber; // pitch [0, 127]
uint8_t velocity; // velocity [0, 127]
} midiPacketNoteOn;
capからUSB URBのペイロードを抜き出してみると次のようなデータが得られます。
0997047f
09970400
0999087f
09990800
0997067f
09970600
0999057f
09990500
...
眺めてみると、前述のNoteOnのパケットと一致するので、これはMIDIコントローラーからの通信と考えて間違いなさそうです。
PE側の解析に戻りましょう。winmmを使ってMIDIコントローラーから信号を受信するには、 midiInOpen
関数を呼び出すときの第3引数にハンドラーを指定します[参考]。ここに指定している関数(0x0140001070番地から始まる関数)の中身をPythonコードに落とし込んでみると以下のようになります(参考: MidiInProc callback function – MSDN)。
MidiInProc.py
|
|
この処理にあうように、ペイロードを加工&バイトオーダーを入れ替えて渡してあげると、以下の出力が得られます。
0x48 0x65 0x79 0x20 0x67 0x75 0x79 0x73 0x21 0x20 0x46 0x4c 0x41 0x47 0x20 0x69 0x73 0x20 0x53 0x45 0x43 0x43 0x4f 0x4e 0x7b 0x33 0x6e 0x37 0x33 0x72 0x33 0x64 0x5f 0x66 0x72 0x30 0x6d 0x5f 0x37 0x68 0x33 0x5f 0x70 0x33 0x72 0x66 0x30 0x72 0x6d 0x34 0x6e 0x63 0x33 0x5f 0x70 0x34 0x64 0x5f 0x6b 0x33 0x79 0x62 0x30 0x34 0x72 0x64 0x7d
あとは普通にデコードしてあげると以下のようになります。
Hey guys! FLAG is SECCON{3n73r3d_fr0m_7h3_p3rf0rm4nc3_p4d_k3yb04rd}
感想
repairで土曜日の夜を溶かしてしまいました(最初のほうで手打ちRIFFと格闘していたら3時間くらい時間を溶かしました、しかも再生できない)。 その後に作り始めた自動化のスクリプトは1時間ほどで作ることができて、さらにPyAVを使うとヘッダーの知識不要がほぼ不要になったので、やはり長いもの(ffmpeg)には巻かれるべきだなと思いました。
あと今回もpwnを解くことができませんでした。復習してちゃんと解けるようになりたいです(毎回言ってる気がする)。