
原因:Office文書を開くと、FLTLDR.EXEが起動され、この脆弱性を含む埋め込みEPSファイルのレンダリングに使用される。このファイルはPostScript言語で書かれており、攻撃者は"save-restore"操作を利用してこれを悪用できる。その本質はUAF(Use-After-Free)脆弱性である。 ユーザーが不正な形式のグラフィック画像を含むファイルを開いた場合、または不正な形式のグラフィック画像をOfficeファイルに挿入した場合に、この脆弱性が悪用される可能性がある。
影響を受けるバージョン:Microsoft Office 2010 Service Pack 2、Microsoft Office 2013 Service Pack 1、Microsoft Office 2016
POC:kcufId's Github
筆者はネット上で長い間探し回ったが、EPSIMP32.FLTを含むOfficeインストールパッケージは見つからなかった。幸いにもkcufId氏が、EPSファイルをロードするためのLoadEps.exeを提供してくれた。kcufId氏に感謝する。
LoadEps.exeはまずEPSIMP32.FLTをロードする:

その後、ImportGrを呼び出してEPSファイルのロードを開始する:

ここで直接F7でステップインすると、EPSIMP32.FLT内に設定したブレークポイントで正常に停止できる。
本題に入る前に、まずPostScriptオブジェクトの構造について説明する。
// PostScript Object
struct PostScript object
{
dword type;
dword attr;
dword value1;
dword value2; // if array, point to userdict where store the array object
}ps_obj;
このうち、異なるtypeに対応する値は以下の通り:
0x0 nulltype
0x3 integertype
0x5 realtype
0x8 booleantype
0x10 operatortype
0x20 marktype
0x40 savetype
0x300 nametype
0x500 stringtype
0x900 filetype
0x30000 arraytype
0x0B0000 packedarraytype
0x70000 packedarraytype
0x110000 dicttype
0x210000 gstatetype
文字列を例として、その格納構造を説明する。forall関数にブレークポイントを設定すれば、文字列がどのように処理されるかをさらに確認できる(forall関数の特定方法は、https://paper.seebug.org/368/ を参照のこと)。

図中の1はps_objに対応しており、そのvalue2項目はインデックスリスト内の対応する項目(図中の2)を指す。インデックス項目はサイズ0x30の構造体を指し、この構造体の0x24位置にはサイズ0x28の構造体(図中の5)へのポインタのポインタが保存され、0x2C位置には文字列サイズ(図中の3)が保存される。図中の5の構造体の0x4位置には、この構造体がインデックスリスト内の対応する項目のアドレス(すなわち図中の4の0x01DB5E94)が格納され、0x20位置は文字列の最終的な格納場所(図中の6)を指し、0x24位置は実際に占有するメモリサイズ、すなわち文字列サイズ+1となる。
サイズ0x30の構造体:
+0x0 dword
+0x4 dword
+0x8 dword
+0xc dword
+0x10 dword
+0x14 dword
+0x18 dword
+0x1c dword
+0x20 dword
+0x24 dword pp_struct //指向大小为0x28结构的指针的指针
+0x28 dword
+0x2c dword size //字符串实际大小
サイズ0x28の構造体(配列の場合、この構造体のサイズは0x2Cであり、0x28位置は配列の要素を指す。各要素はps_objである):
+0x0 dword
+0x4 dword //存储该结构于索引列表中对应项的地址
+0x8 dword
+0xc dword
+0x10 dword
+0x14 dword
+0x18 dword
+0x1c dword
+0x20 dword ptr_object //指向字符串最终存储位置
+0x24 dword size //实际所占内存大小,字符串实际大小+1
脆弱性の1回目のトリガー:

まずVM状態をl62変数に保存し、その後l63変数内の各文字に対してl61→l59→l56の処理プロセスを呼び出す。l62 restoreは以前の状態を復元し、これによって/l62 save def ステートメント以降でl63が確保したメモリ領域は解放され、ダングリングポインタとなる。

l95-l99変数は後続のフローを決定し、その値はすべて0(すなわち32ビット)である:

脆弱性の2回目のトリガー。まず0x27サイズ(実際には0x28を占有する)のメモリ領域を確保してl63を格納する:

その後、l62 restoreが以前の状態を復元し、l63が確保したメモリ領域が解放されてダングリングポインタとなる。続いてl100を実行すると、先ほどl63が占有していたメモリ領域がl102(すなわちl136)文字列の0x28構造体の格納に使用される(これがl63が0x27サイズのメモリ領域を確保した理由を説明している):

それぞれ、この構造体の0x4、0x20、0x24位置の値を取得する:

最後にl136文字列の内容を変更する(図には変更箇所の一部のみが示されている):

これらの変更内容は巧妙に構築されており、3回目の脆弱性トリガーの際に使用される。
脆弱性の3回目のトリガー。0x37個の要素を含む配列を確保し、その後、ループが0x34番目の要素に達した時点でl62 restoreを実行する:

restoreの実行後、配列の0x30構造体はl193文字列の内容で上書きされる:

これにより、最後(0x36)のforallプロセスで実行されるオブジェクトが上図の0x30構造体となり、その0x36番目の要素を取得すると、2回目の脆弱性トリガー時に巧妙に構築された文字列に到達する:

そして取得された配列要素はサイズ4の配列であり、その配列の先頭要素は開始アドレスが0、サイズが0x7FFFFFFFの文字列である:

この配列はl159変数に格納され、その先頭要素、すなわち開始アドレスが0、サイズが0x7FFFFFFFの文字列はl201変数に格納される。その後、l201変数を通じて任意のアドレスの値を取得できるようになる。
kernel32.dllのベースアドレスを取得:


これにより、l314変数内にはEPSIMP32.FLTのベースアドレスが格納される。

注:search コマンドの構文は以下の通り:

指定したgadgetの検索:


file型の構造体を構築:
l199 l201 get_dword
/l487 exch def
l487 l201 get_dword
/l488 exch def
l488 36 my_add l201 get_dword
/l489 exch def
l489 l201 get_dword
/l490 exch def
l490 32 my_add l201 get_dword
/l491 exch def
l199 l491 l201 put_data_to_array
l199 12 my_sub 2304 l201 put_data_to_array

l492アドレス(l491+0x32)に構築データを書き込む:
l492 0 l201 put_data_to_array %% 0x00 0
l492 4 my_add l375 l201 put_data_to_array %% 0x04 Address of <5E C3>
l492 8 my_add l373 l201 put_data_to_array %% 0x08 Address of <94 00 00 00 00 5E C3>
l492 12 my_add l377 l201 put_data_to_array %% 0x0C Address of <C2 0C 00>
l492 16 my_add l370 l201 put_data_to_array %% 0x10 Address of VirtualProtect()
l492 20 my_add 0 l201 put_data_to_array %% 0x14 0
l492 24 my_add 0 l201 put_data_to_array %% 0x18 0
l492 28 my_add 0 l201 put_data_to_array %% 0x1C 0
l492 32 my_add l368 l201 put_data_to_array %% 0x20 Address of Shellcode
l492 36 my_add l368 l201 put_data_to_array %% 0x24 Address of Shellcode——lpAddress
l492 40 my_add l349 l201 put_data_to_array %% 0x28 Size of Shellcode——dwSize
l492 44 my_add 64 l201 put_data_to_array %% 0x2C PAGE_EXECUTE_READWRITE——flNewProtect
l492 48 my_add l493 l201 put_data_to_array %% 0x30 lpflOldProtect
最後に、closefile命令を実行するとShellcodeへジャンプする:

EPSエクスプロイトスクリプトは\word\mediaディレクトリ内にあり、解凍すれば確認できる。この脆弱性のエクスプロイトサンプルはShellcode部分を除き、その他はほぼ同一であるため、Patchword組織のあるサンプルを例に分析する。
ファイル名:Cyber_Secure_Pakistan.docx
MD5:DD89BBB916A2C909630EC78CBB0E13E5
Shellcodeへジャンプし、スタックを復元する:

メモリを確保する:

関数呼び出しアドレスを取得する:

デバッグ中、環境問題が原因でCreateToolhelp32Snapshot関数呼び出しアドレスの取得に失敗した可能性がある:

アドレスを手動で入力し、Wordを開いて分析を続行する。プロセスを列挙してWINWORD.exeを探す:

C:\ProgramData\Microsoft\DeviceSyncディレクトリ内にMSBuild.exeという名前のプログラムを作成する:

ファイルの内容を書き込む。その内容はEPSスクリプトのpayload_32変数内に格納されている:


vmtools.dllファイルを作成する:

ファイルの内容を書き込む。その内容はEPSスクリプトのpayload_32_f2変数内に格納されている:


VMwareCplLauncher.exeファイルを作成する:

その内容はEPSスクリプトのpayload_32_f1変数内に格納されている:

このファイルはVmware署名を持つホワイトリストファイルである:

以下の内容をexplorer.exeに注入する:

その機能はVMwareCplLauncher.exeプロセスの作成である:

以降のプロセスは360のこのレポートで言及されており、本稿ではその分析部分には触れない:

興味のある読者は、このレポートをさらに読み進めることができる。
注:この脆弱性のエクスプロイトサンプルは基本的に類似しており、異なるのは最後のMSBuild.exeペイロードである。これはEPSスクリプトのpayload_32変数内に格納されており、直接ダンプしてDOSファイルヘッダを補完した後、IDAにドラッグして分析できる。