首页
社区
课程
招聘
1
[翻译]Ghidra基于脚本的恶意软件分析
发表于: 2025-3-7 18:57 1955

[翻译]Ghidra基于脚本的恶意软件分析

2025-3-7 18:57
1955

(译者注:本文是对国外逆向工程师David Álvarez Pérez 所著《Ghidra Software Reverse Engineering for Beginners》第六章《Scripting Malware Analysis》主要内容的翻译,版权归原作者所有,翻译如有错误,还请各位读者指正。为熟悉Ghidra操作,文中部分截图是译者自己截图,与原文有些许差异,如需查看原文可点击文末链接)

1. 使用 Ghidra 脚本 API

Ghidra 脚本 API 分为 Flat API(ghidra.app.decompiler.flatapi)和其他更复杂的函数(http://ghidra.re/ghidra_docs/api/overview-tree.html)。

Flat API 是 Ghidra API 的简化版本,它允许你执行以下操作:

  • 处理内存地址:addEntryPoint、addInstructionXref、createAddressSet、getAddressFactory 和 removeEntryPoint。
  • 执行代码分析:analyze、analyzeAll、analyzeChanges、analyzeAll 和 analyzeChanges。
  • 清除代码列表:clearListing。
  • 声明数据:createAsciiString、createAsciiString、createBookmark、createByte、createChar、createData、createDouble、createDWord、createDwords、createEquate、createUnicodeString、removeData、removeDataAt、removeEquate、removeEquate 和 removeEquates。
  • 内存地址获取数据:getInt、getByte、getBytes、getShort、getLong、getFloat、getDouble、getDataAfter、getDataAt、getDataBefore、getLastData、getDataContaining、getUndefinedDataAfter、getUndefinedDataAt、getUndefinedDataBefore、getMemoryBlock、getMemoryBlocks 和 getFirstData。
  • 处理引用:createExternalReference、createStackReference、getReference、getReferencesFrom、getReferencesTo 和 setReferencePrimary。
  • 处理数据类型:createFloat、createQWord、createWord、getDataTypes 和 openDataTypeArchive。
  • 为某些内存地址设置值:setByte、setBytes、setDouble、setFloat、setInt、setLong 和 setShort。
  • 创建片段:getFragment、createFragment、createFunction、createLabel、createMemoryBlock、createMemoryReference、createSymbol、getSymbol、getSymbols、getSymbolAfter、getSymbolAt、getSymbolBefore、getSymbols 和 getBookmarks。
  • 汇编字节:disassemble。
  • 处理事务:end 和 start。
  • 查找值:find、findBytes、findPascalStrings 和 findStrings。
  • 函数级别操作:getGlobalFunctions、getFirstFunction、getFunction、getFunctionAfter、getFunctionAt、getFunctionBefore、getFunctionContaining 和 getLastFunction。
  • 在程序级别操作:getCurrentProgram、saveProgram、set 和 getProgramFile。
  • 在指令级别操作:getFirstInstruction、getInstructionAfter、getInstructionAt、getInstructionBefore、getInstructionContaining 和 getLastInstruction。
  • 处理等价值:getEquate 和 getEquates。
  • 删除某些内容:removeBookmark、removeFunction、removeFunctionAt、removeInstruction、removeInstructionAt、removeMemoryBlock、removeReference 和 removeSymbol。
  • 处理注释:setEOLComment、setPlateComment、setPostComment、setPreComment、getPlateComment、getPostComment、getPreComment、getEOLComment 和 toAddr。
  • 反编译字节:FlatDecompilerAPI、decompile 和 getDecompiler。
  • 其他杂项函数:getMonitor、getNamespace 和 getProjectRootFolder。

这个参考可以帮助你在开始使用 Ghidra 脚本时识别你需要的函数,并在文档中查找其原型。

2. 使用 Java 编写脚本

正如你在前一章中所了解的,Alina 恶意软件包含注入到 explorer.exe 进程中的 shellcode。如果你想对 shellcode 中的 Kernel32 API 函数调用进行去混淆,那么你需要识别 call 指令。你还需要过滤函数,以筛选你需要的内容,最后,当然,你需要执行去混淆操作:

1
2
3
4
5
6
7
8
9
10
11
12
Function fn = getFunctionAt(currentAddress);
Instruction i = getInstructionAt(currentAddress);
while (getFunctionContaining(i.getAddress()) == fn) {
    String nem = i.getMnemonicString();
    if (nem.equals("CALL")) {
        Object[] target_address = i.getOpObjects(0);
        if (target_address[0].toString().equals("EBP")) {
            // Do your deobfuscation here.
        }
    }
    i = i.getNext();
}

让我逐行解释这段代码的工作原理:

  1. 它获取包含当前地址(选中地址)的函数(第 01 行)。
  2. 还获取当前地址的指令(第 02 行)。
  3. 执行一个从当前指令到函数末尾的循环(第 03 行)。
  4. 获取指令的助记符(第 04 行)。
  5. 检查助记符是否对应于 CALL 指令,这是我们感兴趣的指令类型(第 05 行)。
  6. 还检索指令的操作数(第 06 行)。
  7. 由于混淆的调用是相对于存在哈希表的 EBP 地址的,我们检查 EBP 是否是一个操作数(第 07 行)。
  8. 去混淆例程必须在这一行实现(第 08 行)。
  9. 检索下一条指令(第 11 行)。

在本节中,你学习了如何使用 Ghidra API 以 Java 语言实现脚本。在下一节中,你将学习如何使用 Python 做同样的事情,我们将在 Ghidra 脚本的背景下比较这两种语言

3. 使用 Python 编写脚本

如果我们使用 Python 重写去混淆代码框架,它看起来如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
fn = getFunctionAt(currentAddress)
i = getInstructionAt(currentAddress)
 
while getFunctionContaining(i.getAddress()) == fn:
    nem = i.getMnemonicString()
     
    if nem == "CALL":
        target_address = i.getOpObjects(0)
         
        if target_address[0].toString() == 'EBP':
            # Do your deobfuscation here.
     
    i = i.getNext()

正如你所看到的,它与 Java 类似,因此不需要额外的解释。

要开发 Ghidra 脚本,不需要记住所有函数。唯一重要的是要清楚你想做什么,并找到必要的资源,例如文档,以定位正确的 API 函数。

Python 是一种非常棒的语言,拥有一个开发库和工具的出色社区。如果你想快速编写代码,Python 是一个很好的选择。不幸的是,Ghidra 并没有集成纯 Python 实现。Ghidra 主要用 Java 实现,然后通过 Jython 移植到 Python。

理论上,你可以随意选择使用 Python 或 Java,但实际上,Jython 存在一些问题:

  • Jython 依赖于 Python 2.x,而 Python 2.x 已被弃用。
  • 有时,某些功能在 Java 中按预期工作,但在 Jython 中不起作用。以下是一些示例:

https://github.com/NationalSecurityAgency/ghidra/issues/1890

https://github.com/NationalSecurityAgency/ghidra/issues/1608

鉴于上述提到的种种因素,选择哪种语言来编写脚本取决于你:是选择更稳定的 Java,还是选择更快速但可能不太稳定的 Python。 不妨亲自评估一下这两种方案,然后做出最适合你的选择!

4. 使用脚本对恶意软件样本进行去混淆

在上一章中,我们展示了 Alina 如何将 shellcode 注入到 explorer.exe 进程中。我们通过简单地读取字符串来分析这一点,这是一种快速、实用的方法,但我们可以更准确地进行分析。让我们看看一些 shellcode 的细节。

4.1 Delta Offset

在注入代码时,代码被放置在一个在开发时未知的位置。因此,无法使用绝对地址访问数据;相反,必须通过相对位置访问。shellcode 在运行时检索当前地址。换句话说,它试图获取 EIP 寄存器的值。

在 x86 架构(32 位)中,EIP 寄存器的目的是指向要执行的下一条指令;因此,它控制程序的执行流程。它决定了要执行的下一条指令。

但是,由于 EIP 寄存器是隐式控制的(通过控制转移指令、中断和异常),它不能直接访问,因此恶意软件通过执行以下技术来检索它:

执行一个指向 5 字节外地址的 CALL 指令。因此,CALL 指令会执行两个更改:

  • 它将返回地址(下一条指令的地址)压入堆栈,即 0x004f6105:

Figure 6.1 – The CALL instruction pushes the return address onto the stack

  • 它将控制权转移到目标地址:

Figure 6.2 – The CALL instruction transfers the flow to the target address

然后,它通过 POP EBP 恢复存储在堆栈中的地址。该指令执行以下操作:

  • 它移除最后压入堆栈的值:

Figure 6.3 – The POP instruction removes the latest value pushed onto the stack

  • 它将值存储在目标寄存器中,本例中为 EBP:

Figure 6.4 – The POP instruction stores the removed stack value into the targeting EBP register

最后,它从 EBP 寄存器中减去 0x5,以获取存储在 EBP 中的 EIP 值(这是我们在执行 CALL 指令时的 EIP 值,而不是当前的 EIP 值):

Figure 6.5 – The SUB instruction subtracts 5 units from the EBP register

通过使用这种技巧,恶意软件开发者可以利用 EBP 寄存器(shellcode 的起始位置)加上一个偏移量来引用数据值。 使用这种技术,生成的代码与位置无关;无论你将 shellcode 放在哪个位置,它都能正常工作。

你可以在下面的代码片段中验证这一点:

Figure 6.6 – Delta offset stored in the EBP register for position-independent code

这种技巧通常被称为 Delta Offset。在本例中,它用于计算 API 哈希码表的位置,该表位于相对于 shellcode 起始地址的 0x5e2 偏移处:

Figure 6.7 – Storing the base address of the API hash table

之后,一个函数负责将 Kernel32 API 函数的哈希值替换为函数地址,从而允许你从程序中调用它。

一旦替换完成,许多调用都是通过这个哈希表的偏移量完成的,该哈希表现在已转换为 API 地址表:

Figure 6.8 – Calling resolved API functions via relative offsets

正如你所看到的,反汇编显示了指向 EBP 相对偏移量的 CALL 指令。更希望看到的是被调用函数的名称。我们的目标是改进反汇编以显示函数名称,但作为第一步,在下一节中,你将学习如何将 API 哈希值替换为相应的 API 函数地址。


[注意]看雪招聘,专注安全领域的专业人才平台!

收藏
免费 1
支持
分享
赞赏记录
参与人
雪币
留言
时间
pexillove
你的帖子非常有用,感谢分享!
2025-3-7 19:23
最新回复 (0)
游客
登录 | 注册 方可回帖
返回

账号登录
验证码登录

忘记密码?
没有账号?立即免费注册