GIAC Reverse Engineering Malware (GREM) 3日目 Malicious Web and Document Filesのメモ。
JavaScriptの難読化
JavaScriptはタプルの最後の要素のみ使う
以下のJavaScriptコードは変数hogeにfugaのみが格納される。
hoge = ("hello", "world", "fuga");
以下のコードは上記のタプルの性質を利用して変数cにdocumentを格納する
a = ("dummy", "dummy2", "doc");
b = ("dummy3", "dummy4", "ument");
c = a + b;
JavaScript ワンライナー構文
以下のJavaScriptコードは変数hogeにevalが格納される。
hoge = (100>1?"ev"+"al":"pri"+"nt");
上記のコードは以下のように分解できる
- 条件 : 100>1 (100は1より大きいか?)
- 真ならば"ev"+"al" (eval)が返される
- 偽ならば"pri"+"nt" (print)が返される
?
以前が条件式を表し、真ならば :
(コロン)の左側の値が、偽ならば :
の右側の値が返される。
getElementByIdを利用してHTMLタグに埋め込んだ値を取り出す
以下のJavaScriptコードはabcというHTML要素からurrryyyyという値を取り出して、変数fugaに格納する。
<html>
<body>
<input type='hidden' id='abc' value='urrryyyy'>
<script>
fuga = document.getElementById('abc').value;
document.write(fuga);
</script>
</body>
</html>
上記のようにHTMLタグの中に悪意のあるコードを紛れ込ませることにより、後述するSpiderMonkeyなどのJavaScriptインタープリタによる解析を阻害することができる。(SpiderMonkeyでJavaScriptを実行する際はHTMLタグを除去しなければならない。上記の例だとHTMLタグを除去するとurrryyyyという値を取り出すことができない。)
またgetElementByIdを使わずともdivタグを使うことでHTMLの要素を取り出すことができる。
<div id='defg' value='gwaaa'>contents</div>
<script>
fuga = defg;
</script>
JavaScriptのarugments.callee
arguments.calleeを用いると関数は自身のボディを参照することができる。例えば関数がarguments.callee.toStringという命令を呼び出すと、関数のボディをテキスト文字列として受け取ることができる。
arguments.calleeを利用することにより、関数は自身のコードが改竄されたか検知することができる。事前にarguments.calleeで自身のコードサイズやハッシュ値やチェックサムを取得しておき、これらの値を比較することでコードに変更が施されたか確認できる。またarguments.calleeを利用して自身のボディを暗号化処理や難読化処理の復号鍵とすることもできる。
arguments.calleeによるアンチ・デバッグが施された関数を解析する場合、改行を挟まずにコードにブレークポイントをセットできるデバッガ (Internet Explorer)を用いるか、SpiderMonkeyなどのJavaScriptインタープリタを用いる。
JavaScriptの解析
Notepad++のJSToolプラグインを使ってJavaScriptコードを見やすく整える (Beautify)
Notepad++のPluginsメニューより、JSTool > JSMinを選択して余計なコメントなどを削除する。
同じくJSTool > JSFormatを選択して改行やインデントを挿入してコードを見やすくする。
留意点としてJSFormatを実行するとHTMLタグに余計な空白が挿入されてしまう。
<html>
が< html >
となる</html>
が< / html >
となる
代替手段としてCyberChefのJavaScript BeautifyやREMnuxのjs-beautifyコマンドがある。
SpiderMonkey
SpiderMonkeyはJavaScriptのインタープリタ。SpiderMonkeyでJavaScriptを実行するにはJavaScriptコードからHTMLタグを除去しなければならない。
REMnuxでは以下のコマンドでSpiderMonkeyを実行できる。
js -f malicious.js
SpiderMonkeyを使ってJavaScriptを実行しようとするとオブジェクト未定義によるエラーに遭遇することが度々ある。REMnuxではJavaScriptの主要オブジェクトは/usr/share/remnux/objects.js
に定義されている。
以下は/usr/share/remnux/objects.js
の一部抜粋。eval関数を実行する前に関数に渡された値をprintするようにeval関数を再定義している。
original_eval = eval;
eval = function(input_string) {
print(input_string);
original_eval(input_string);
}
オブジェクト未定義エラーに遭遇した場合は以下のようにして定義ファイルを読み込ませた上でJavaScriptを実行する。(必要に応じて定義ファイルを編集すること)
js -f /usr/share/remnux/objects.js -f malicious.js
box-js
box-jsはJavaScriptのエミュレーター。box-jsはHTMLタグを除去しなくても実行できる。
REMnuxでは以下のコマンドで実行できる。
box-js malicious.js
Internet ExplorerによるJavaScriptのデバッグ
Firefox、 Chrome、Internet Explorerなどの主要ブラウザにはデバッガモードが存在するが、Internet Explorerの特筆すべき点として、コード行の途中でもブレークポイントをセットできる点が挙げられる。大抵のデバッガは行の先頭にしかブレークポイントをセットできない。
PDF の解析
PDFはディレクティブによってオブジェクトを定義しており、ディレクティブのキーワードにはASCII文字が使われている。そのためstringsなどによってある程度の静的解析が可能。
以下は攻撃者がPDFに悪意のあるペイロードを埋め込む際に使用されるディレクティブ・キーワード:
- /JS、/JavaScript、/XFA : 埋め込まれたJavaScriptを実行する
- /RichMedia : 埋め込まれたFlashプログラムを実行する
- /Launch、/EmbeddedFiles : 外部プログラムまたは埋め込まれたプログラムを実行する
- /URI、/SubmitForm : ウェブサイトと通信する
pdfid.pyでPDFファイルの概要を取得する
pdfid.pyを実行して、対象のPDFに上述したような危険なディレクティブが含まれていないか確認する。
REMnuxでは以下のコマンドで実行できる。
pdfid.py malicious.pdf
pdf-parser.pyで不審なディレクティブやオブジェクトを精査する
pdfid.pyで不審なディレクティブやオブジェクトの当たりをつけたら、pdf-parser.pyで精査する。
以下にコマンドの一例を挙げる。
JavaScriptというキーワードを検索するpdf-parser.py malicious.pdf --search JavaScript
任意のオブジェクトを確認するpdf-parser.py malicious.pdf --object <object ID>
任意のオブジェクトをデコードして抽出するpdf-parser.py malicious.pdf --object <object ID> --filter --raw --dump <output file>
Microsoft Office ドキュメントの解析
Microsoft Office ドキュメント・フォーマット
- レガシー・フォーマットとしてOLE2がある
- より新しいフォーマットとしてXML形式のOOXMLがある
- OOXMLはドキュメントのコンテンツを含む複数の関連ファイルをZIP形式で保持している
- どちらのフォーマットもマクロを使用できるが、XML形式のファイルでマクロを実行するにはファイル名の拡張子が「m」で終わらなければならない (.docm .xlsm . pptm .dotm)
- OOXML形式のドキュメントにマクロが含まれている場合、マクロはOLE2形式のファイル (ファイル名: vbaProject.bin)としてZIPに保持される
- Composite Document File V2はOLE2と同義
VBA (Visual Basic for Applications) マクロ
- VBAマクロを介してシステムに外部ファイルをダウンロードさせたり、任意のファイルを実行させることができる
- 大抵の不正マクロはユーザーがドキュメントを開いてマクロの実行を許可すると自動実行されるようにプログラムされている (Auto_Open、Workbook_Open)
- Officeファイルがパスワード保護されている場合でもマクロのコード自体は暗号化されておらず、ツールによって抽出可能
Microsoft Office でVBAマクロの抽出が可能
- 対象のドキュメントをMicrosoft Officeで開いて、VIEWタブよりMacrosボタンをクリックするとマクロを有効化しなくてもマクロのコードを閲覧できる
- LibreOfficeでもマクロの閲覧が可能
- ドキュメントがパスワード保護されていた場合、正しいパスワードを入力しないとドキュメントを開けないので、上記の方法は使えない
olevba.pyによるマクロの解析
olevbaについてはこちらの記事でも少し取り上げている。olevba.pyはドキュメントに埋め込まれているマクロを抽出・解析して解析結果のサマリーを出力してくれる。REMnuxでは以下のコマンドで実行できる。
olevba.py malicious.docm
oledump.pyによるマクロの解析
oledump.pyは特にOLE2形式のファイルを解析する際に有用。REMnuxでは以下のコマンドで実行できる。
oledump.py malicious.docm
oledump.pyは対象のファイルの中にマクロを見つけるとアイテム番号の横に「M」というラベルをつける。
oledump.pyを使ってマクロを抽出するには以下のようにする。
oledump.py -s <item number> -v malicious.docm
SRP ストリームについて
- SRPには旧バージョンのマクロの情報がキャッシュされている
- 元からあるドキュメントに別の新しいマクロを埋め込むとSRPにキャッシュされる
- SRPキャッシュを参照することで解析対象のドキュメントの旧バージョンの情報を得ることができる
- oledump.pyではSRPは「_SRP_0」といった名前で表示される
- oledump.pyを使ってSRPを抽出できる
p-codeについて
マクロがOfficeドキュメントに埋め込まれるとp-codeというバイトコードにコンパイルされる。マクロ実行時に実際に実行されているのはp-codeである。つまりp-codeが残っていればマクロのソースコードを除去してもマクロの実行には差し支えない。
攻撃者はp-codeを残してマクロのソースコードをドキュメントから除去することで (あるいはソースコードをオリジナルのものから変更することで) olevba.pyやoledump.pyによる解析を阻害できる。これらのツールは解析の際にマクロのソースコードを参照するため、ソースコードを除去されるとマクロを解析できない。
上述したようなテクニックをVBA Stompingと呼ぶ。
pcodedmp.pyを使えばp-codeのみのドキュメントを解析することができる。コマンドは以下の通り。
pcodedmp.py -d malicious.doc
RTF (Rich Text File)の解析
RTFはMicrosoft Wordを始めとする文書ファイルのフォーマットである。マクロはサポートしていないが、OLE1オブジェクトやそのほかのバイナリ・ペイロードを埋め込むことができる。
RTFの構造
\
で始まる制御文によってアプリケーションがどのように文書をフォーマットして出力するべきかを指定する{
}
によってグルーピングする- グループの中にほかのグループを含めることができる (ネストができる)
- オブジェクトやそのほかのバイナリ・ペイロードを16進数形式にシリアライズして埋め込むことができる。その際に使用される制御文は
\objdata
rtfdump.pyによる解析
RTFの解析にはrtfdump.pyを用いる。REMnuxでは以下のコマンドで実行できる。
rtfdump.py malicious_rtf.doc
オプション無しで実行するとそれぞれの{ }
グループの情報を1グループに付き1行ずつ表示する。
特定のグループのみ調べたい場合は以下のようにする。
rtfdump.py -s <item number> malicious_rtf.doc
rtfdump.pyを使ってRTFに不審なペイロードが埋め込まれていないか確認する場合は以下の点に注視すること。
- h= の値に注目して異様に大きな16進数形式のデータがないか確認する (バイナリ・ペイロードは16進数形式にシリアライズ化して埋め込まれるため)
- 長大な16進数データが有り、且つ深い階層にネストされているグループがないか確認する
- Level 0の階層がないか確認する。Level 0はLevel 1から始まる一連のグループの外側にグループが存在していることを示している。(overlay)
16進数形式にシリアライズされたデータをデコードして抽出するには以下のようにする。
rtfdump.py -s <item number> -H -d malicious_rtf.doc > dumpfile.bin
XORSearchによるシェルコードの解析
XORSearchは主にXORエンコードされたファイルの解析に使用されるツールだが -W オプション付きで実行するとシェルコードのパターン検索をしてくれる。
xorsearch -W -d 3 suspected_shellcode.bin
誤検知を避けるため、-W オプションを使うときは -d 3 というパラメーターと併せて使うのが推奨される。(-d 3 はROT変換を無効化するためのオプション)
GetEIPについて
シェルコードが実行されると、シェルコードはまず自身がロードされているアドレス位置を確認しようとする。シェルコードが自身のローカル・データや変数にアクセスするには、自身がどこのメモリ領域にロードされているか知る必要があるためである。自身のアドレス位置を確認するためシェルコードはEIPの値を取得しようとする。この行為をGetEIPと呼ぶ。EIPは直接アクセスすることができないが、以下のような方法でEIPの値を取得することができる。
CALLの直後にPOPする
012345: CALL 012349
012349: POP EAX
CALL命令はまず最初にEIPをスタックに保存する。CALLの直後にスタックの値をPOPすることでEIPを取得することができる。
以下は別のパターン。
012345: JMP SHORT 012357
012349: POP ESI
012353: JMP SHORT 012361
012357: CALL 012349
012361: ADD ESI, 9
やっていることは先に示した例と同じだが、POP ESIをCALL命令よりも上の行に持ってくることで、単純な静的検知の回避を試みている。
シェルコードがGetFileSizeを呼び出す理由
- ドキュメント・ファイルにシェルコードが埋め込まれている場合、シェルコードは関連するデータをドキュメントから読み込むためにドキュメント・ファイルのハンドルを取得する必要がある
- シェルコードはまずシステム上のファイル・ハンドルを列挙する
- GetFileSizeでファイル・ハンドルが指しているファイルのサイズを取得し、ドキュメント・ファイルのサイズと比較することで、そのハンドルが対象のドキュメント・ファイルのハンドルかどうか確認する
- 両者のファイル・サイズが一致すれば、そのハンドルからドキュメント・ファイルにアクセスして必要なデータを読み込む