导语:利用WinDbg及wscript.exe分析JavaScript脚本
攻击者经常使用JavaScript来编写恶意软件,因为它系统默认解析,很少被禁用。我们之前关于.NET分析的文章引起了很多关于如何使用WinDBG来分析.js文件的兴趣。在这篇文章中,我们使用WinDBG及64位版本的wscript.exe来分析JavaScript。强烈建议先阅读我们以前的文章。
WINDOWS系统上的对象加载
JavaScript通常需要加载外部对象来获取Windows解释器默认不包含的附加功能。这个可以通过使用ActiveObject()的API(用来加载ActiveX对象)或WScript.CreateObject()的API(用来加载COM对象)。这两个API的后台机制大同小异——加载一个外部库来启用对新对象的访问。下面是两个调用的例子:
New ActiveXObject("Shell.Application"); WScript.CreateObject("Wscript.Shell");
第一点是了解哪些库是在这两个对象之后。此信息存储在注册表中,首先,我们需要在以下注册表名称中获取与对象名称关联的CLSID:HKEY_CLASSES_ROOT\OBJECT_NAME\CLSID. 。
以下是Shell.Application对象名称的示例:
这表明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信息>) 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结构的指针。微软的文档描述比在这些情况下,结构位于偏移0x18。
案例研究#2:WScript Shell对象
我们来看一下第二个例子
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:Usersuser> 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"
这些参数是两个字符串而不是一个结构,可以无偏移地检索。
案例研究#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))
该脚本执行两种不同类型的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],rdi [...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,并将base64编码调用的解码版本的notepad.exe:
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。在许多情况下,WinDBG可能会因为了解单个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)