現在,Microsoft Office のバイナリファイルの解析を行っているのですが,MS Excel のオートシェイプ部分が結構トリッキーな形になっていたのでメモとして残しておきます.
MS Excel バイナリファイルのデータ構造
MS Excel のバイナリファイルは,セルの内容やフォントなどの情報毎に「レコード」を作成し,レコードの集合体と言う形になっています.それぞれのレコードは,以下のようにヘッダ部とデータ部に分かれます.
Offset | Size (Bytes) | Contents |
---|---|---|
0 | 2 | レコード番号 |
2 | 2 | データ長 |
4 | variable | データ |
MS Excel には数多くの種類のレコードが存在するのですが,今回問題となったのは MsoDrawing (0xEC) と呼ばれるレコードです.
MsoDrawing のデータ構造
MsoDrawing レコードは,MS Excel においてオートシェイプ(図形)に関する情報を保持するためのレコードです.Microsoft Excel の仕様書では以下のように書かれています.
This record contains a drawing object provided by the Microsoft Office Drawing tool. For more information on this file format, see Microsoft Office Drawing Binary File Format specification.
Record Data ― BIFF8
Offset Field Name Size Contents 4 rgMSODrawing var Microsoft Office Drawing data
何のことやら分からない説明ですね.実は,MS Excel のオートシェイプのデータ構造は MS PowerPoint から流用しているらしく*1,MsoDrawing レコードのデータ部には MS PowerPoint で使用されているレコードが格納されています.
MS PowerPoint も MS Excel と同様にレコード単位での管理を採用していて,各レコードのフォーマットは以下のようになります.
Offset | Size (Bytes) | Contents |
---|---|---|
0 | 2 | バージョン (4bit) + インスタンス (12bit) |
2 | 2 | レコードタイプ |
4 | 4 | データ長 |
8 | variable | データ |
断片化されて格納されている MsoDrawing
さて,問題の発端となったのは「MS Excel の MsoDrawing レコードのデータサイズとそのデータに格納されている(PowerPoint の)レコードに記述されているデータサイズが合わない」と言うものでした.
以下は,2 つのオートシェイプ(長方形と三角形)と一つのグラフを記述した MS Excel ファイルを BiffView++ と言うツールで閲覧したときの MsoDrawing レコードの結果です.
BIFF MsoDrawing (0xEC) 0x3906 0x837 0F 00 02 F0 B6 10 00 00 ...
0x837 がこのレコードのデータサイズです(10 進数に直すと 2,103Byte).これに対して,データ部に記載されている(MS PowerPoint の)レコードのデータサイズ (4--7Byte) を見ると 0x000010B6 となっています(10 進数に直すと 4,278Byte).ここから,このレコードだけではデータが足りない事が分かります.そこで,このレコードの周辺を観察してみたのですが,そうすると面白い事が分かりました.
BIFF MsoDrawing (0xEC) 0x3906 0x837 0F 00 02 F0 B6 10 00 00 ... BIFF Obj (0x5D) 0x4141 0x1A 15 00 12 00 02 00 23 00 ... BIFF MsoDrawing (0xEC) 0x415F 0x08 00 00 0D F0 00 00 00 00 ... BIFF TxO (0x1B6) 0x416B 0x12 12 02 00 00 00 00 00 00 ... BIFF MsoDrawing (0xEC) 0x4181 0x805 0F 00 04 F0 05 08 00 00 ... BIFF Obj (0x5D) 0x498A 0x1A 15 00 12 00 1E 00 24 00 ... BIFF MsoDrawing (0xEC) 0x49A8 0x08 00 00 0D F0 00 00 00 00 ... BIFF TxO (0x1B6) 0x49B4 0x12 12 02 00 00 00 00 00 00 ... BIFF MsoDrawing (0xEC) 0x49CA 0x72 0F 00 04 F0 6A 00 00 00 ... BIFF Obj (0x5D) 0x4A40 0x1A 15 00 12 00 05 00 25 00 ... BIFF BOF Chart (0x809) 0x4A5E 0x10 00 06 20 00 AA 1F CD 07 .. ...
問題となる MsoDrawing 周辺は上記のようになっているのですが,これらの MsoDrawing レコードのデータサイズを全て合計すると,0x10BE (0x837 + 0x08 + 0x805 + 0x08 + 0x72) となります(10 進数に直すと,4,286Byte).MS PowerPoint のレコードのヘッダサイズが 8Byte,レコードに記載されていたデータサイズが 4,278Byte なので合計値と一致します.すなわち,MsoDrawing レコードを扱うときはまず「MsoDrawing レコードのみを集めて(Obj, TxO レコードを取り除いて),それらを一つのバイト列に結合し直す」必要があることが分かります.
蛇足
ゴミのように見える Obj,TxO レコードの直前の(MsoDrawing レコードのデータ部の)バイト列を見ると,それぞれ <00 00 11 F0 00 00 00 00>,<00 00 0D F0 00 00 00 00> とデータサイズが 0Byte の不思議な(MS PowerPoint)レコードが出現している事が分かります.Office Drawing の仕様書(オートシェイプ専用の仕様書)によるとこれらのレコードには以下のような記述があります.
Record Name | Type | Version | Instance | Contents |
---|---|---|---|---|
msofbtClientTextbox | F00D | - | host-defined | the text in the textbox, in a host-defined format |
msofbtClientData | F011 | - | host-defined | host-specific data |
これらのレコードは,オートシェイプを使う環境依存のデータレコード,およびテキストボックスレコードを表しています.すなわち,MS Excel においては環境依存のデータレコードに関しては,ヘッダだけ記載してデータ自体は欄外に記載すると言うトリッキーな方法を採用しているようです.
ちなみに,オートシェイプの表示位置の座標を表すレコード (msofbtClientAnchor) も環境依存なのですが,このレコードに関してはデータも一緒に記載されています.この辺りの統一性の無さは,イライラさせられる部分です.
MsoDrawing のレコード番号が MsoDrawing とは限らない
解析をしていて発見したもう一つの問題です.こちらも発端はデータサイズが合わないと言うものでした.以下は,解析に使用したサンプルファイルの MsoDrawing レコード周辺を BiffView++ を使用して解析した結果(の一部)です.これを見ると,途中でレコードの様相が変わってきている事が分かります.
BIFF MsoDrawing (0xEC) 0x16339 0xD4 0F 00 02 F0 94 8A 00 00 ... BIFF Obj (0x5D) 0x16411 0x1A 15 00 12 00 01 00 01 00 ... BIFF MsoDrawing (0xEC) 0x1642F 0x72 0F 00 04 F0 72 00 00 00 ... BIFF Obj (0x5D) 0x164A5 0x1A 15 00 12 00 06 00 02 00 ... BIFF MsoDrawing (0xEC) 0x164C3 0x08 00 00 0D F0 00 00 00 00 ... BIFF TxO (0x1B6) 0x164CF 0x12 14 02 00 00 00 00 00 00 ... BIFF Continue (0x3C) 0x164E5 0x13 01 09 59 F4 66 42 30 8A ... BIFF Continue (0x3C) 0x164FC 0x10 00 00 0B 00 53 00 0E 00 ... BIFF MsoDrawing (0xEC) 0x16510 0x84 0F 00 04 F0 7C 00 00 00 ... BIFF Obj (0x5D) 0x16598 0x1A 15 00 12 00 01 00 03 00 ... ... BIFF MsoDrawing (0xEC) 0x192E7 0x08 00 00 0D F0 00 00 00 00 ... BIFF TxO (0x1B6) 0x192F3 0x12 12 02 00 00 00 00 00 00 ... BIFF Continue (0x3C) 0x19309 0x09 01 66 8A 08 54 42 7D 86 ... BIFF Continue (0x3C) 0x19316 0x10 00 00 0B 00 28 00 0E 00 ... BIFF Continue (0x3C) 0x1932A 0x7E 0F 00 04 F0 76 00 00 00 ... BIFF Obj (0x5D) 0x193AC 0x1A 15 00 12 00 01 00 41 00 ... BIFF Continue (0x3C) 0x193CA 0x72 0F 00 04 F0 72 00 00 00 ... BIFF Obj (0x5D) 0x19440 0x1A 15 00 12 00 06 00 42 00 ... BIFF Continue (0x3C) 0x1945E 0x08 00 00 0D F0 00 00 00 00 ... ...
MS Excel の 1 レコードは 8,228Byte 以内と言う制限があるので,その制限を超えるデータを扱う場合やその他レコード毎の固有の問題でレコードを分割する必要がある場合には,Continue と言うレコードを使用して直前のレコードに情報を付け足します.MS Excel の仕様書によると「TxO レコードは最大で 2 つの Continue レコードを持つことがある」そうなので,上記の例では,TxO レコードの直後の 2 つの Continue レコードは分かりますが,それ以外の Continue レコード(Obj レコードの直後と TxO の 3 個目の Continue レコード)が不明です.
これらの不明な Continue レコードのデータ部の最初の 8Byte (e.g., 0F 00 04 F0 76 00 00 00) をよく見ると,MS PowerPoint のレコードのヘッダである事が分かります.結局,「本来 MsoDrawing レコードであるべきレコードが Continue レコードとして格納されている」ようです.最初に説明した問題はともかく,こちらに関しては MS Excel のバグだと思うのですが・・・よく問題なく動いてるなぁと思いました.
しかし,BiffView++ は便利ですね.当初,バイナリファイルの解析にかなり手こずっていたのですが,このツールのおかげでかなり効率良く進められるようになりました.
*1:正確には,オートシェイプには Office Drawing と呼ばれる専用の仕様書を用意して,統一化を図っている模様.