Online interactive sandbox for DFIR/SOC investigations. Fast malware analysis and cybersecurity threat detection.
Walkthroughs, tutorials, guides, and tips. This story will teach you how to do something new or how to do something better.
作为恶意软件研究人员,您经常会遇到复杂、严重混淆的样本。我们今天处理的恶意软件 GuLoader 就是一个典型的例子。
看看这段伪代码,它是通过反编译其汇编代码生成的——它很难看且不可读。
面对这样的难题,你可能会不知所措。你应该从哪里开始呢?您如何处理该样本的分析?让我们分解一下。
那么,在本文中,我们将使用 GuLoader 作为参考,探讨反混淆此类代码的策略。您将了解:
本文基于 ANY.RUN 之前发布的GuLoader 恶意软件分析。请访问我们的博客,找到我们将分析的示例,以及解压说明和 Ghidra 脚本,该脚本部分自动化了我们从现在开始将要介绍的大部分内容。
本文重点关注静态分析。但如果您想动态分析 GuLoader 样本,则可以使用ANY.RUN云恶意软件沙箱。
使用您的企业电子邮件申请企业计划的14 天免费试用。充分利用 VM 实例的延长生命周期、无限任务以及 Windows 版本 7 至 11。
动态分析可让您通过将恶意软件的行为与其技术设置联系起来,了解恶意软件在现实世界中的工作原理。这是一种检查其在不同系统设置中的操作并收集 IOC 的方法。
事不宜迟,让我们开始吧。
解压样本后,我们开始手动分析 shellcode,并很快意识到它已被混淆。研究代码使我们能够将 GuLoader 使用的混淆技术分为几类:
现在是停下来分析正在使用的混淆方法并制定反混淆策略的好时机。
例如,在我们的例子中,大多数技术引入的代码不会改变最终的执行结果。因此,我们通常可以安全地“NOP”它们以提高可读性。但请谨慎行事——正如我们很快就会发现的那样,并非所有混淆的代码都与程序的操作无关。
现在,让我们单独检查这些混淆技术,看看如何击败它们。
代码中散布着许多 XMM 指令。这些看起来是无序的,增加了分析过程的复杂性。我们从解压的 shellcode 的第一个字节就遇到了它们。
请注意,由于缺乏默认支持,许多仿真引擎都会遇到困难。我们对 Angr、Triton 和 Ghidra 的嵌入式引擎进行了测试,结果都没有达到要求。
在 GuLoader 的例子中,XMM 指令实际上并不影响代码的预期行为。您会在许多恶意软件中遇到类似的混淆方法。因此,我们可以安全地将所有 XMM 指令替换为“NOP”,如下表所示:
以下是 Ghidra 反汇编程序中的结果:
无条件 JMP 指令将代码分割成更小的块。此方法经常用于避免防病毒软件和其他安全工具的检测。此外,它还可能使分析师的工作变得更加耗时和令人沮丧,因为他们必须在这些块之间跳转,尤其是在处理大量代码时。 GuLoader 和其他恶意软件通常采用这种技术。
这种混淆方法很容易被破解。反编译代码中的反汇编器通常会成功连接这些块,从而提高代码可读性,即使存在这些无条件跳转也是如此。因此,我们可以保留小块原样,而不需要合并它们。
垃圾汇编指令通常作为额外的混淆层发挥作用。这些指令不执行任何有形功能,通常保持寄存器值、执行流程或内存不变。
您也会在 GuLoader 中遇到这些。
留意不执行任何操作的指令(“NOP”、“FNOP”)以及移位或循环零位的指令(“SHL reg, 0”;“ROL reg, 0”)。还存在其他无影响的指令,例如“OR reg, 0”、“XOR reg, 0”、“CLD”、“WAIT”。
处理虚假比较指令比仅仅用“NOP”替换垃圾指令更具挑战性。我们无法删除所有比较指令,因为有些比较指令对于正确的代码功能是必需的。解决这个问题的一种方法是“标记”我们遇到的所有比较指令。如果没有找到使用比较结果的指令,则可以安全地执行 NOP。如果我们发现条件跳转或类似的情况,我们会取消比较标记以避免删除。
下表显示了一个示例,其中除“CMP EDX,0x0”之外的所有比较指令都选择性地替换为 NOP:
GuLoader 还采用了使用假“PUSHAD”指令以及匹配的“POPAD”指令的混淆策略。它们可以临时修改寄存器值,但会被“POPAD”恢复原始寄存器值而无效。
我们的研究表明 GuLoader 中的所有“PUSHAD”指令都是无关的。因此,我们通过用 NOP 替换“PUSHAD”、“POPAD”和中间指令来解决这个问题:
然而,并非GuLoader中的所有“POPAD”指令都是垃圾指令。我们对那些没有相应“PUSHAD”的部分保持不变。
另一种与前一种类似的混淆技术是使用假的“PUSH”指令。这些指令将一个值压入堆栈,然后立即将其弹出。
一个示例是包含“PUSH SS”指令,其后可能是修改寄存器或存储器位置的指令。然而,随后的“POP SS”将堆栈指针恢复到其初始值。
击败假 PUSH 指令类似于假 PUSHAD 的过程,但保持非推送寄存器不变至关重要。
不透明谓词是总是返回 true 或 false 的条件语句,但它们很难分析或预测。这些可以在 GuLoader 的代码中找到,并且使逻辑理解变得复杂。
例如,诸如“MOV BL,0xB6”和“CMP BL,0xB6”之类的一对指令后面可以跟有诸如“JNZ ADDR”之类的条件跳转。比较总是返回 false,因为比较的值等于移动的值,使得跳转不必要且令人困惑。
由于需要“预测”跳转条件,克服不透明谓词似乎具有挑战性。然而,我们的情况更加简单,因为所有不透明谓词都落在“PUSHAD”和“POPAD”块内。因此,我们只需将这些指令之间的所有谓词替换为 NOP。
混淆算术表达式是 GuLoader 使用的更有趣的技术之一。它们使得理解实际执行的操作变得更加困难。这些表达式包含算术运算,例如加法或减法。有时,它们与其他混淆混合在一起,例如虚假比较、不透明谓词和垃圾指令。
一个示例是将常量值移入寄存器并执行算术运算:
另一个例子是将常量值压入堆栈并在内存上执行计算:
为了反混淆 GuLoader 的算术表达式,我们采用了类似于处理假比较的方法。我们标记第二个参数是标量值的所有“MOV”指令,以及参数是标量的所有“PUSH”指令。当遇到算术运算时,我们更新第一条指令中的常量值并用 NOP 替换当前值。这样,第一条指令就有了结果,后续的算术指令被 NOP 代替。
以下是优化“MOV”和“PUSH”操作的示例:
请谨慎对待操作数的大小。在操作过程中保持正确的尺寸至关重要。
在ANY.RUN专家的这篇文章中,我们使用 GuLoader 作为现实示例来强调典型的反混淆策略。我们首先分析代码,然后对类似的混淆策略进行分组,最后想出独特的方法来解决每个问题。
但重要的是要了解这些技术并不是一刀切的解决方案。每个恶意软件样本可能会呈现其独特的混淆策略,需要独特的对策。
我们的讨论强调了将反混淆等复杂任务分解为清晰、可管理步骤的重要性。请记住,成功反混淆任何恶意软件都需要详细分析、发现混淆模式并创建优化策略。