Microsoft Excel バイナリファイル解析(Office Drawing 部分)

現在,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 と呼ばれる専用の仕様書を用意して,統一化を図っている模様.