#tmctf の Analysis Others 100 の壊れたPDFファイルを復元する問題に再挑戦してみた。

TMCTF 2015 Writeup - たいしょー(miettal)の日記 http://miettal.hatenablog.com/entry/2015/10/06/111214 にあったように、 PDF Stream Dumper http://sandsprite.com/blogs/index.php?uid=7&pid=57 を試してみる(wineでインストールしようとして、MSVBVM60.DLL がないというエラーになったので、 http://wiki.winehq.org/winetricks で winetricks vb6run)。 で、PDF Stream Dumperで開いてみると、XMPメタデータが見つかり、そのRDF中にBase64エンコードされたサムネイル画像があり、復号保存してみるとフラグが書いてあった。

ただ、問題の説明文からすると、本来想定していた解き方は、サムネイルを見る方法ではなく、PDFファイルを修復する方法だと思われるので、そっちも挑戦してみる。

チームメンバーに教えてもらった「手書きPDF入門」 http://www.kobu.com/docs/pdf/pdfxhand.htm というドキュメントを読みながら、PDFファイルの中身を観てみる。ファイル末尾に start xref 32548 となっているが、実際にxrefが始まる位置は 32548 (0x7f24) ではなく 32375 (0x7e77) で、途中で173バイトほど消えていそう。 またxrefの実体は以下のようになっている、
------------------------
xref
0 27
0000000000 65535 f 
0000032443 00000 n 
0000020577 00000 n 
0000000162 00000 n 
0000020414 00000 n 
0000000022 00000 n 
0000000144 00000 n 
0000000369 00000 n 
0000000434 00000 n 
0000000778 00000 n 
0000000797 00000 n 
0000000929 00000 n 
0000005475 00000 n 
0000005496 00000 n 
0000017694 00000 n 
0000018280 00000 n 
0000018326 00000 n 
0000017716 00000 n 
0000017979 00000 n 
0000017998 00000 n 
0000018261 00000 n 
0000020497 00000 n 
0000032245 00000 n 
0000032267 00000 n 
0000032292 00000 n 
0000032343 00000 n 
0000032401 00000 n
------------------------

これを実際の位置と比較してみると、以下のようになり、オブジェクト14以降が173バイトずつずれており、オブジェクト13 から173バイト削除されてしまっているように見える。
------------------------
オブジェクト01の開始位置は 0000032443 = 0x7ebb とあるが、実際の開始位置は 32443 - 173 = 32270 = 0x7e0e
オブジェクト02の開始位置は 0000020577 = 0x5061 とあるが、実際の開始位置は 20577 - 173 = 20404 = 0x4fb4
オブジェクト03の開始位置は 0000000162 = 0x00a2 でOK
オブジェクト04の開始位置は 0000020414 = 0x4fbe とあるが、実際の開始位置は 20414 - 173 = 20241 = 0x4f11
オブジェクト05の開始位置は 0000000022 = 0x0016 でOK
オブジェクト06の開始位置は 0000000144 = 0x0090 でOK
オブジェクト07の開始位置は 0000000369 = 0x0171 でOK
オブジェクト08の開始位置は 0000000434 = 0x01b2 でOK
オブジェクト09の開始位置は 0000000778 = 0x030a でOK
オブジェクト10の開始位置は 0000000797 = 0x031d でOK
オブジェクト11の開始位置は 0000000929 = 0x03a1 でOK
オブジェクト12の開始位置は 0000005475 = 0x1563 でOK
オブジェクト13の開始位置は 0000005496 = 0x1578 でOK
オブジェクト14の開始位置は 0000017694 = 0x451e とあるが、実際の開始位置は 17694 - 173 = 17521 = 0x4471
オブジェクト15の開始位置は 0000018280 = 0x4768 とあるが、実際の開始位置は 18280 - 173 = 18107 = 0x46bb 
オブジェクト16の開始位置は 0000018326 = 0x4796 とあるが、実際の開始位置は 18326 - 173 = 18153 = 0x46e9 
オブジェクト17の開始位置は 0000017716 = 0x4534 とあるが、実際の開始位置は 17716 - 173 = 17543 = 0x4487
オブジェクト18の開始位置は 0000017979 = 0x463b とあるが、実際の開始位置は 17979 - 173 = 17806 = 0x458e
オブジェクト19の開始位置は 0000017998 = 0x464e とあるが、実際の開始位置は 17998 - 173 = 17825 = 0x45a1
オブジェクト20の開始位置は 0000018261 = 0x4755 とあるが、実際の開始位置は 18261 - 173 = 18088 = 0x46a8
オブジェクト21の開始位置は 0000020497 = 0x5011 とあるが、実際の開始位置は 20497 - 173 = 20324 = 0x4f64
オブジェクト22の開始位置は 0000032245 = 0x7df5 とあるが、実際の開始位置は 32245 - 173 = 32072 = 0x7d48
オブジェクト23の開始位置は 0000032267 = 0x7e0b とあるが、実際の開始位置は 32267 - 173 = 32094 = 0x7d5e
オブジェクト24の開始位置は 0000032292 = 0x7e24 とあるが、実際の開始位置は 32292 - 173 = 32119 = 0x7d77
オブジェクト25の開始位置は 0000032343 = 0x7e57 とあるが、実際の開始位置は 32343 - 173 = 32170 = 0x7daa
オブジェクト26の開始位置は 0000032401 = 0x7e91 とあるが、実際の開始位置は 32401 - 173 = 32228 = 0x7de4
------------------------

オブジェクト13,14の中身は以下の通り。
------------------------
13 0 obj
stream
〜11991バイトのバイナリデータ〜
endstream
endobj
14 0 obj
11991
endobj
------------------------

オブジェクト13が参照されている箇所を探すと、オブジェクト10で以下のように参照されていて、PDFの命令はよく分からないけれど、オブジェクト11と13が画像として使われているっぽい。
------------------------
10 0 obj
<< /ProcSet [ /PDF /ImageB /ImageC /ImageI ] /ExtGState << /Gs1 15 0 R >>
/XObject << /Im1 11 0 R /Im2 13 0 R >> >>
endobj
------------------------

オブジェクト11,12は以下の通りなので、同様のプロパティ情報(?)を173バイト分でっちあげてオブジェクト13に復元すればよさげ。
------------------------
11 0 obj
<< /Length 12 0 R /Type /XObject /Subtype /Image /Width 305 /Height 50 /ColorSpace
16 0 R /Intent /Perceptual /SMask 17 0 R /BitsPerComponent 8 /Filter /FlateDecode
>>
stream
〜4344バイトのバイナリデータ〜
endstream
endobj
12 0 obj
4344
endobj
------------------------

まず、オブジェクトの11のストリーム部分は4344バイトのデータで、それが書かれているオブジェクト12を参照している。オブジェクト13についても数えてみると11991バイトのデータで、それがオブジェクト14に書かれているので、Lengthについてはそれを参照すれば良さそう。

次に、オブジェクト11はFlateDecodeということでzlibで圧縮されている。 オブジェクト13についても同様にzlibで展開できたので、末尾の /Filter /FlateDecode もそのまま持ってくる。 なお、展開結果は61,000バイトのデータとなった。

次にSMaskに着目すると、オブジェクト11ではオブジェクト17をSMaskとして使っているが、オブジェクト17と同様のデータでどこからも参照されていないオブジェクトとしてオブジェクト19があったので、これだろう。 というか、中身を確認すると、オブジェクト17とオブジェクト19は完全に同一のデータなのだけれど。

オブジェクト19のデータを確認すると以下のようになっており、ソフトマスクと画像本体は多分同じサイズだろうから、WidthとHeightはやはり305と50だと推測できる。
------------------------
19 0 obj
<< /Length 20 0 R /Type /XObject /Subtype /Image /Width 305 /Height 50 /ColorSpace
/DeviceGray /BitsPerComponent 8 /Filter /FlateDecode >>
stream
〜20バイトのバイナリデータ〜
endstream
endobj
20 0 obj
90
endobj
------------------------

オブジェクト13のストリームを展開したデータは61,000バイトだったので、これを305x50で割ると4になり、1ピクセル4バイトの画像データのよう。 最初RGBAと思って変換したけど、ダメでCMYKと思って表示してみると、フラグの描かれた画像が得られた。 具体的には展開結果のデータを obj13.cmyk として、ImageMagickで convert -size 305x50 -depth 8 obj13.cmyk obj13.png として変換した。

これで解答は得られたけれど、PDFファイルとして復元するには、これまでの情報を総合して、以下の内容で改行コードを含めて173バイトちょうどになるので、これをオブジェクト13に挿入すれば良い。
------------------------
<< /Length 14 0 R /Type /XObject /Subtype /Image /Width 305 /Height 50 /ColorSpace
/DeviceCMYK /Intent /Perceptual /SMask 19 0 R /BitsPerComponent 8 /Filter /FlateDecode
>>
------------------------