Cisco Japan Blog
Share

WinDBG と JavaScript の分析


2017年8月17日


はじめに

マルウェアでは、Windows 上で悪意のあるコードを実行するために JavaScript が多用されています。その理由は、JavaScript が強力で、ネイティブで利用でき、大半のシステムで無効化されていないためです。前回の .NET 分析の記事popup_iconでは、WinDBG を使用して .js ファイルを分析する方法を中心に解説しました。今回の記事では、64 ビット版の wscript.exe を使用した JavaScript を WinDBG により分析する方法について説明します。ここでは、前回の記事popup_iconを先に読むことを強くお勧めします。

Windows システムにおけるオブジェクトの読み込み

Windows インタープリタにデフォルトで含まれていない JavaScript の追加機能にアクセスする際は、外部オブジェクトを読み込む必要があります。その際は、ActiveXObject() API または WScript.CreateObject() API を使用します。前者は ActiveX オブジェクトを読み込み、後者は COM オブジェクトを読み込みます。これら 2 つの API の仕組みは同じです。つまり、外部ライブラリを読み込むことで、新しいオブジェクトへのアクセスを可能にします。次に 2 つの例をご紹介します。

new ActiveXObject("Shell.Application");
WScript.CreateObject("Wscript.Shell");

最初のポイントは、これら 2 つのオブジェクトの背後にあるライブラリを理解することです。この情報はレジストリから掘り出せます。最初に、次のレジストリ名から、オブジェクト名に関連付けられた CLSID を取得する必要があります。 HKEY_CLASSES_ROOT\OBJECT_NAME\CLSID

Shell.Application object name オブジェクト名の例:

CLSID は {13709620-C279-11CE-A49E-444553540000} であることが分かります。この情報を基に、HKEY_CLASSES_ROOT\CLSID\{THE_CLSID} にあるオブジェクトの DLL パスを取得できます。

今回のケースでは、Shell.Application オブジェクトがあるライブラリは shell32.dll です。この情報を基に、WinDBG を使用してオブジェクトのロードと実行を分析します。

WINDBG の分析

JavaScript の分析では、wscript.exe のバイナリをデバッグします。これは次のコマンドにより実行できます。

"C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\windbg.exe"
C:\Windows\System32\wscript.exe c:\Users\User\to_be_analysed.js

分析手法はしばしば同じで、以下のものを使用します。

  • オブジェクト ライブラリがロードされたときのブレークポイント
  • 対象機能の識別とブレークポイント
  • 関数の引数を取得する

ケース スタディ その 1:ActiveX オブジェクト

以下のコードについて考えてみましょう。

var oShell = new ActiveXObject("Shell.Application");
var commandtoRun = "calc.exe";
oShell.ShellExecute(commandtoRun,"","","","1");

最初のタスクは、レジストリ内で「Shell.Application」ライブラリ オブジェクトを見つけることです。

c:\Users\user> script.py Shell.Application
Object Name: Shell.Application
CLSID: {13709620-C279-11CE-A49E-444553540000}
Description: Shell Automation Service
dll: %SystemRoot%\system32\shell32.dll

これは、shell32.dll を解析する必要があることを示しています。このスクリプトを実行し、ライブラリがロードされたときにブレークポイントを導入します。

0:000> sxe ld shell32 ; g
ModLoad: 00007fff`c6af0000 00007fff`c7f27000   C:\WINDOWS\System32\SHELL32.dll
ntdll!NtMapViewOfSection+0x14:
00007fff`c8e658a4 c3              ret
The next step is to identify the ShellExecute function:
0:000> x shell32!ShellExecute

残念ながら、関数名は JavaScript とライブラリで異なります。ただし、次の正規表現を使って検索できます。

0:000> x shell32!ShellExecute*
00007fff`c6b13dd0 SHELL32!ShellExecuteExW (void)
00007fff`c6b13e44 SHELL32!ShellExecuteNormal (void)
00007fff`c6cb1630 SHELL32!ShellExecuteExA (<no parameter info>)
00007fff`c6fa8d58 SHELL32!ShellExecuteRegApp (<no parameter info>)
00007fff`c6bef560 SHELL32!ShellExecuteW (<no parameter info>)
00007fff`c6cb15a0 SHELL32!ShellExecuteA (<no parameter info>)
00007fff`c6fa9058 SHELL32!ShellExecuteRunApp (<no parameter info>)

ここでは、ShellExecuteNormal にブレークポイントを導入します。

0:000> bp shell32!ShellExecuteNormal
0:000> g
Breakpoint 0 hit
SHELL32!ShellExecuteNormal:
00007fff`c6b13e44 48895c2408      mov     qword ptr [rsp+8],rbx 
ss:00000029`cb56c7a0=00000029cb56cc90

RCX のレジスタを介して、引数を直接取得できます。

0:000> r $t1=poi(rcx+0x18);du $t1
000001ee`350d055c  "calc.exe"

一見すると、0x18 のオフセットがある理由は不明確です。この理由は、ShellExecuteNormal()に渡される引数が SHELLEXECUTEINFO 構造体popup_iconへのポインタであることに起因します。Microsoft 社のドキュメンテーションによると、これらの場合に構造体がオフセット 0x18 で配置されます。

ケース スタディ その 2:WScript シェル オブジェクト

2 番目の例を考えてみましょう。

var shell = WScript.CreateObject("Wscript.Shell");
var command = "calc.exe";
shell.Run(command, true, false);

前述のタスクと同様に、最初のタスクは、Wscript.Shell が含まれるライブラリを見つけることです。

c:\Users\user> script.py Wscript.Shell
Object Name: Wscript.Shell
CLSID: {72C24DD5-D70A-438B-8A42-98424B88AFB8}
Description: Windows Script Host Shell Object
dll: C:\Windows\System32\wshom.ocx

関数名を識別しましょう。

0:000> sxe ld wshom
0:000> g
ModLoad: 00007fff`b5630000 00007fff`b5657000   C:\Windows\System32\wshom.ocx
ntdll!NtMapViewOfSection+0x14:
00007fff`c8e658a4 c3              ret
0:000> x wshom!*Run*
00007fff`b5640930 wshom!CUnknown::InnerUnknown::`vftable' = <no type information>
00007fff`b563d530 wshom!CUnknown::InnerUnknown::QueryInterface (<no parameter info>)
00007fff`b5648084 wshom!_IMPORT_DESCRIPTOR_ScrRun = <no type information>
00007fff`b563d570 wshom!CUnknown::InnerUnknown::Release (<no parameter info>)
00007fff`b5643d30 wshom!ScrRun_NULL_THUNK_DATA = <no type information>
00007fff`b563bbb0 wshom!CWshShell::Run (<no parameter info>)
00007fff`b5631000 wshom!CUnknown::InnerUnknown::AddRef (<no parameter info>)
00007fff`b5644518 wshom!LIBID_IWshRuntimeLibrary = <no type information>)

この関数は wshom!CWshShell::Run です。これをブレークポイントとして引数を確認できます。

0:000> bp wshom!CWshShell::Run
0:000> g
Breakpoint 0 hit
wshom!CWshShell::Run:
00007fff`b563bbb0 48895c2408      mov     qword ptr [rsp+8],rbx ss:00000020`7ccfd520=0000013f3d650420
0:000> du rdx
0000013f`3d65055c  "calc.exe"

前述のケース スタディとは対照的に、引数は文字列であり構造体ではないため、引数を取り出すために必要なオフセットはありません

ケーススタディ その 3:WScript XMLHTTP オブジェクト

このケース スタディのソース コードは次のとおりです。

var httpStream = WScript.CreateObject("MSXML2.XMLHTTP");
httpStream.open("GET", 'http://blog.talosintelligence.com');
httpStream.send();

MSXML2.XMLHTTP のオブジェクトに関連付けられたライブラリ:

c:\Users\user> script.py MSXML2.XMLHTTP
Object Name: MSXML2.XMLHTTP
CLSID: {F6D90F16-9C73-11D3-B32E-00C04F990BB4}
Description: XML HTTP
dll: %SystemRoot%\System32\msxml3.dll

上記と同じ手法を使用できます。

0:000> sxe ld msxml3
0:000> g
ModLoad: 00007fff`8dc40000 00007fff`8de68000   C:\WINDOWS\System32\msxml3.dll
ntdll!NtMapViewOfSection+0x14:
00007fff`c8e658a4 c3              ret

今回は正規表現を使用して、「Open」という単語を含むすべての API でブレークポイントを追加します。

0:000> bm msxml3!*Open*
1: 00007fff`8dc43030 @!"msxml3!ErrorHelper::CHTMLWindow2::open"
breakpoint 1 redefined
1: 00007fff`8dc43030 @!"msxml3!FakeHTMLDoc::open"
2: 00007fff`8dd4c5fc @!"msxml3!HTTPStream::OpenRequest"
3: 00007fff`8dcaa407 @!"msxml3!_imp_load_CertOpenStore"
breakpoint 1 redefined
1: 00007fff`8dc43030 @!"msxml3!ErrorHelper::CHTMLWindow2::get_opener"
4: 00007fff`8dc48eb4 @!"msxml3!ContentModel::openGroup"
5: 00007fff`8dd4cb00 @!"msxml3!HTTPStream::deferedOpen"
breakpoint 1 redefined
1: 00007fff`8dc43030 @!"msxml3!ErrorHelper::CHTMLDocument2::open"
breakpoint 1 redefined
1: 00007fff`8dc43030 @!"msxml3!ErrorHelper::CHTMLWindow2::put_opener"
6: 00007fff`8dd4a050 @!"msxml3!URLMONRequest::open"
7: 00007fff`8dc8f4d0 @!"msxml3!FileStream::deferedOpen"
8: 00007fff`8dd34e80 @!"msxml3!XMLHttp::open"
9: 00007fff`8dc597e0 @!"msxml3!URLMONStream::deferedOpen"
10: 00007fff`8dc70ddc @!"msxml3!NamespaceMgr::popEntry"
11: 00007fff`8dcaa3bf @!"msxml3!_imp_load_WinHttpOpen"
12: 00007fff`8dcaa3e3 @!"msxml3!_imp_load_WinHttpOpenRequest"
13: 00007fff`8dd47340 @!"msxml3!HTTPRequest::open"
14: 00007fff`8dd47660 @!"msxml3!HTTPRequest::openWithCredentials"
15: 00007fff`8dc8f37c @!"msxml3!FileStream::open"
16: 00007fff`8dd4c128 @!"msxml3!URLStream::OpenPreloadResource"
17: 00007fff`8dd4b410 @!"msxml3!URLRequest::open"
0:000> g
Breakpoint 8 hit
msxml3!XMLHttp::open:
00007fff`8dd34e80 488bc4          mov     rax,rsp

実際に使用されている API は XMLHttp::open() で、これから次の引数を得られます。

0:000> du rdx
00000173`311a0568  "GET"
0:000> du r8
00000173`311a0578  "http://blog.talosintelligence.co"
00000173`311a05b8  "m"

これらの引数は構造体ではなく 2 つの文字列であり、オフセットなしで取得できます。

ケーススタディ その 4:eval() 関数

eval() 関数は、マルウェアのコード実行を難読化するために多用されています。これは JavaScript のネイティブ関数であり、外部ライブラリを必要としません。次に eval() の実例を示します。

var test = "var oShell = new ActiveXObject(\"Shell.Application\");var commandtoRun = \"notepad.exe\"; oShell.ShellExecute(commandtoRun,\"\",\"\",\"\",\"1\");"
eval(test)

var encoded = "dmFyIG9TaGVsbCA9IG5ldyBBY3RpdmVYT2JqZWN0KCJTaGVsbC5BcHBsaWNhdGlvbiIpO3ZhciBjb21tYW5kdG9SdW4gPSAiY2FsYy5leGUiOyBvU2hlbGwuU2hlbGxFeGVjdXRlKGNvbW1hbmR0b1J1biwiIiwiIiwiIiwiMSIpOwo="
eval(Base64.decode(encoded))

このスクリプトでは、2 種類の方法で eval() 関数を呼び出します。前者の方法では、直接実行する文字列を含みます(calc.exe の実行)。後者には、実行するコードを生成するためのコマンド(base64 でエンコードされた notepad.exe の実行コード)が含まれます。

eval() 関数自体は、script.dll ライブラリ(bp jscript!JsEval)にあります。関数は jscript!COleScript::Compile API を使用して、eval() から実行される次の JavaScript コードを生成します。

0:000> sxe ld jscript;g
ModLoad: 00007fff`9e650000 00007fff`9e70c000   C:\Windows\System32\jscript.dll
ntdll!NtMapViewOfSection+0x14:
00007fff`c8e658a4 c3              ret
0:000> bp jscript!JsEval
0:000> g
Breakpoint 0 hit
jscript!JsEval:
00007fff`9e681960 488bc4          mov     rax,rsp
0:000> u rip L50
jscript!JsEval:
00007fff`9e681960 488bc4          mov     rax,rsp
00007fff`9e681963 48895810        mov     qword ptr [rax+10h],rbx
00007fff`9e681967 48897018        mov     qword ptr [rax+18h],rsi
00007fff`9e68196b 48897820        mov     qword ptr [rax+20h],rd
[...redacted…]
00007fff`9e681a81 488364242000    and     qword ptr [rsp+20h],0
00007fff`9e681a87 e80c3cfdff      call    jscript!COleScript::Compile
00007fff`9e681a8c 89455f          mov     dword ptr [rbp+5Fh],eax
00007fff`9e681a8f 8bf8            mov     edi,eax
00007fff`9e681a91 85c0            test    eax,eax
00007fff`9e681a93 7923            jns     jscript!JsEval+0x158 (00007fff`9e681ab8)

jscript!COleScript::Compile でブレークポイントを導入します。calc.exe を呼び出す文字列の例と、notepad.exe を呼び出す 文字列の例の両方を取得します。前者はエンコードされておらず、後者は base64 でエンコードされています。

0:000> bp jscript!COleScript::Compile "r $t1 = poi(rdx+0x10);r $t2 = poi($t1+0x8);du $t2;g";g
jscript!COleScript::Compile:
00007fff`9e715698 4053            push    rbx
0:000> g
0000019b`d23f6408  "var oShell = new ActiveXObject(""
0000019b`d23f6448  "Shell.Application");var commandt"
0000019b`d23f6488  "oRun = "calc.exe"; oShell.ShellE"
0000019b`d23f64c8  "xecute(commandtoRun,"","","","1""
0000019b`d23f6508  ");."
80070002 The system cannot find the file specified.
0000019b`d473a1b0  "var oShell = new ActiveXObject(""
0000019b`d473a1f0  "Shell.Application");var commandt"
0000019b`d473a230  "oRun = "notepad.exe"; oShell.She"
0000019b`d473a270  "llExecute(commandtoRun,"","","","
0000019b`d473a2b0  ""1");"
ntdll!NtTerminateProcess+0x14:
00007fff`c8e65924 c3              ret

まとめ

WinDBG は .NET ファイルの分析だけでなく、wscript.exe による JavaScript の実行ファイルを分析するのにも役立つ非常に強力なツールです。単一の JavaScript ファイルの機能を分析する用途では、多くの場合に WinDBG はオーバースペックかもしれません。逆に複雑な JavaScript ファイルでは、機能を全体的に別の視点から把握し、分析が容易になります。

付録

オブジェクト名からライブラリを取得する Python スクリプト

from _winreg import *
import sys

try:
  objectName = sys.argv[1]
except:
  sys.exit(1)

try:
  hReg = ConnectRegistry(None,HKEY_CLASSES_ROOT)
  hCLSIDKey = OpenKey(hReg, objectName+"\CLSID")
  CLSID=QueryValue(hCLSIDKey, "")
  if CLSID:
    hKey = OpenKey(hReg, "CLSID\\"+CLSID)
    description = QueryValue(hKey, "")
    hKey = OpenKey(hReg, "CLSID\\"+CLSID+"\\InProcServer32")
    dll = QueryValueEx(hKey, "")[0]
    print "Object Name: "+objectName
    print "CLSID: "+CLSID
    print "Description: "+description
    print "dll: "+dll
  else:
    print "No CLSID"
except:
  print "Error"
  sys.exit(2)

 

本稿は 2017年8月9日に Talos Grouppopup_icon のブログに投稿された「WinDBG and JavaScript Analysispopup_icon」の抄訳です。

 

Tags:
コメントを書く