
CFFフォントに飛び込み、偶然にもとある脱獄(ジェイルブレイク)からCFFパース処理におけるbof(バッファオーバーフロー)について学ぶ。ただの楽しみだ。
CFFフォントに飛び込み、偶然にもあるjailbreakからCFFパース処理におけるbofについて学ぶ。ただの楽しみだ。
それで……CFFって一体何だ? 私の知識では、これはファイルフォーマットの一種だ。だが、Wikipediaが何と言っているか見てみよう:「CFFは、FontSetとして知られる単一のユニットに複数のフォントをまとめて保存するためのコンテナとして機能する。」(https://docs.fileformat.com/font/cff/) つまり、基本的にはフォントをまとめて保存できる。いいだろう。で、次はどうする? まあ、ファイルフォーマットである以上、仕様があるはずだ。確かに仕様はある。しかし、それは親切ではない。なぜなら60ページもあるからだ。私はその形式を学ぶ気はない。だが、star-masterのgithubリポジトリ(たぶんjailbreak 2.0のソースコード)のexploitを使って、その形式について学ぶことはできる。
それでは…作者が提供する cff.py スクリプトを使うことから始め、また hxd エディタで out.cff(標準的な単純な .cff ファイル)を開く。
ファイルをパーサーに与えると、次のような出力が得られることが分かる。

つまり、CFFの定義をすでに導き出せたので、かなり進歩したと言える。ヘッダーに関するメタデータがあり、それがおそらくtffバージョンを示し、ヘッダーサイズ、そして何であれabsoffsizeを示すという結論に達したからだ。
それで、今のところは |メジャーバージョン(1バイト)|マイナーバージョン(1バイト)|ヘッダーサイズ(1バイト) | ヘッダーabsoffsize(1バイト) |
さらに進んで、この .cff ファイルで使用されているフォントを取得する関数がある。下のとおりだ。

では、その目的が「どのフォントが使われているかを読み取ること」だとどこで分かるのか? ツールを実行すると、cmd で次の結果が得られた。

これが、ファイルのメタデータの直後に続くバイト列であることが分かる。

この関数の分析には後で戻るが、今のところファイル形式は次のように推測できる。
|メジャーバージョン(1バイト)|マイナーバージョン(1バイト)|ヘッダーサイズ(1バイト) | ヘッダーabsoffsize(1バイト) | ABCDEF+パック内のフォント|
さらに、スクリプト内で「string」と呼ばれるものを検索していることが分かる:

なぜかって? パック内で使われているフォントに関する情報を収集したいからだと思う。ドキュメントによれば、「Name INDEX に現れる FontName と CIDFontName の文字列を除き、FontSet 内のさまざまなフォントで使われるすべての文字列は、1つの INDEX 構造体にまとめられ、文字列識別子(SID)と呼ばれる2バイトの符号なし整数で参照されます。標準文字列として知られるこれらの文字列は、ISOAdobe および Expert 文字セットで使用されるすべての名前を記述します」。とにかく、ここで興味深い点は、string に到達するために約41バイトをスキップしたことだ。


次に、.cff ファイル内にあるフォントに関する情報を取得する。

どうやってそんなことをするのかって? そう、いわゆる top dict データを収集する。それが何だって? ドキュメントから理解できた限りでは、特定の情報を含む Python の dict で、特定の方法でエンコードされている。

偶然にも、デコードアルゴリズムに従うと:

strings データ型を逆参照してフォントに関する情報を得る。つまり、topdict には単にいくつかのインデックスが含まれており、それが後で strings データ型でフォント情報を取得するために使われる、という結論になる。
それで、今のところファイルの定義は依然として次のようになる。
|メジャーバージョン(1バイト)|マイナーバージョン(1バイト)|ヘッダーサイズ(1バイト) | ヘッダーabsoffsize(1バイト) | ABCDEF+パック内のフォント|既知の41バイト|フォントに関する25バイトの情報|
いいね。で、次は何が起こる?
パーサースクリプトを調べると、charstring_off と private_off を取得し、charstring_off の位置に移動してさらに何かを読み取ることが分かる。

しかし、それは全体像を理解するのにどう役立つのか? というわけで、唐突にこれを締めくくる。基本的に、通常の cff と破損した cff の2つのファイルの差分を取った。

左が破損した .cff ファイルで、右が正常な .cff ファイルだ。実行結果を調べると

最初にパースを実行すると、count、offsize、offbase などのすべてが、何らかのデリミタまでの単なるオフセットであることが分かる。どのデリミタか? 正確にはフォント名だ。つまり、('offbase', 8L) にあるように、ファイルの先頭から、cff ファイル内に存在するフォントの文字列に最初に遭遇するまでのオフセットだ。

「2回目のパース実行」でも見られるように

hexビューアの0x6cオフセットに、悪意のあるデータの始まりである \x0e\0xe\0xe\x0e が見られる。
結論として、.cff ファイル形式の一般的なフォーマットは次のようになる。
|メジャーバージョン(1バイト)|マイナーバージョン(1バイト)|ヘッダーサイズ(1バイト) | ヘッダーabsoffsize(1バイト) | ABCDEF+パック内のフォント|既知の41バイト|フォントに関する25バイトの情報|
そしてカスタムファイル形式(ユーザーコンテンツ付き)
|メジャーバージョン(1バイト)|マイナーバージョン(1バイト)|ヘッダーサイズ(1バイト) | ヘッダーabsoffsize(1バイト) | ABCDEF+パック内のフォント|不明な41バイト|フォントに関する25バイトの情報| 4バイト(count) | 4バイト(offsize) | 未解明の9バイト| ユーザーコンテンツ|