简介
Elastic 安全实验室分析通过我们的威胁搜寻管道和遥测队列发现的各种恶意软件。我们最近遇到了一个名为 DOUBLELOADER 的新恶意软件家族,与 RHADAMANTHYS 信息窃取程序同时出现。DOUBLELOADER 的一个有趣属性是它受到开源混淆器ALCATRAZ的保护,该混淆器于 2023 年首次发布。虽然该项目起源于游戏黑客社区,但它也出现在电子犯罪领域,并被用于有针对性的入侵。
这篇文章的目的是介绍 ALCATRAZ 采用的各种混淆技术,同时强调恶意软件分析师对抗这些技术的方法。这些技术包括控制流平坦化、指令变异、常量展开、LEA 常量隐藏、反反汇编技巧和入口点混淆。
关键要点
- 开源混淆器 ALCATRAZ 被发现出现在与 RHADAMANTHYS 感染同时部署的新恶意软件中
- 控制流平坦化等混淆技术继续成为分析师的障碍
- 通过了解混淆技术及其应对方法,组织可以提高有效分类和分析受保护二进制文件的能力。
- Elastic 安全实验室发布了用于对受 ALCATRAZ 保护的二进制文件进行反混淆的工具,并随此文章发布
双装载机
从去年 12 月开始,我们的团队发现了一种通用后门恶意软件与RHADAMANTHYS窃取程序感染相结合。根据PDB路径,该恶意软件自称为DOUBLELOADER。
该恶意软件利用NtOpenProcess
、 NtWriteVirtualMemory
、 NtCreateThreadEx
等系统调用在 Windows 桌面/文件管理器 ( explorer.exe
) 中启动未支持的代码。该恶意软件收集主机信息,请求自身的更新版本,并开始向二进制文件中存储的硬编码 IP( 185.147.125.81
)发出信标。
DOUBLELOADER 样本包含一个具有可执行权限的非标准部分( .0Dev
),这是作者根据二进制混淆工具ALCATRAZ
句柄留下的工具标记。
诸如 ALCATRAZ 之类的混淆器最终会增加对恶意软件进行分类的复杂性。其主要目标是通过不同的技术来阻碍二进制分析工具并增加逆向工程过程的时间;例如隐藏控制流或使反编译难以跟踪。下面是 DOUBLELOADER 内部一个函数的混淆控制流的示例。
该文章的其余部分将重点介绍 ALCATRAZ 使用的各种混淆技术。我们将使用 DOUBLELOADER 的第一阶段以及基本代码示例来突出 ALCATRAZ 的功能。
恶魔岛
恶魔岛概览
Alcatraz 是一款开源混淆器,最初于 2023 年 1 月发布。虽然该项目在游戏黑客社区中被认可为学习混淆技术的基础工具,但也观察到它被电子犯罪和APT 组织滥用。
Alcatraz 的代码库包含 5 主要功能,这些功能以标准代码混淆技术为中心,并增强了混淆入口点的功能。其工作流程遵循标准bin2bin
格式,这意味着用户提供一个编译后的二进制文件,然后在转换后,他们将收到一个新的编译后的二进制文件。这种方法对于游戏黑客/恶意软件开发者来说特别有吸引力,因为它易于使用,只需很少的努力并且不需要在源代码级别进行修改。
开发人员可以选择混淆所有或特定的功能,也可以选择对每个功能应用哪种混淆技术。编译后,将生成文件,并在文件名末尾附加字符串( obf
)。
ALCATRAZ 中的混淆技术
以下部分将介绍 ALCATRAZ 实施的各种混淆技术。
入口点混淆
处理混淆的入口点就像在家庭公路旅行开始时轮胎漏气一样。这个想法的核心是混淆分析师和二进制工具,因为无法直接明确程序从哪里开始,从而在分析过程的一开始就造成混乱。
以下是 IDA Pro 中非混淆程序的干净入口点( 0x140001368
)的视图。
通过启用入口点混淆,ALCATRAZ 会移动入口点,然后包含带有算法的附加代码来计算程序的新入口点。下面是混淆入口点的反编译视图的片段。
由于 ALCATRAZ 是一个开源混淆器,我们可以找到自定义入口点代码来查看计算是如何执行的,或者逆转我们自己的混淆示例。在我们的反编译中,我们可以看到该算法使用了 PE 头中的几个字段,例如Size of the Stack Commit
、 Time Date Stamp
以及.0dev
部分的前四个字节。这些字段经过解析,然后通过按位运算(例如右旋转(ROR)和异或(XOR))来计算入口点。
下面是 IDA Python 脚本(附录 A)的示例输出,该脚本解析 PE 并找到真正的入口点,并使用未混淆的样本确认原始起点( 0x140001368
)。
防拆卸
恶意软件开发人员和混淆器使用反反汇编技巧来混淆或破坏反汇编程序,以使静态分析变得更加困难。这些技术利用线性扫描和递归反汇编过程中的弱点,阻止干净的代码重建,然后迫使分析师手动或自动修复底层指令。
ALCATRAZ 实现了这种技术的一种形式,即通过在前面添加短跳转指令( 0xEB
)来修改以0xFF
字节开头的任何指令。0xFF
字节可以表示处理调用、间接跳转、堆栈推送等多个有效指令的开始。通过在前面添加短跳转0xEB
,可以有效地跳转到下一个字节0xFF
。虽然它并不复杂,但损坏已经造成,拆卸也需要某种干预。
为了修复这种特定的技术,可以通过用 NOP 替换每个出现的0xEB
字节来修补文件。修补后,代码恢复到更干净的状态,从而可以正确反汇编以下call
指令。
指令变异
混淆器使用的一种常见技术是指令变异,其中指令以保留其原始行为的方式进行转换,但使代码更难理解。Tigress或Perses等框架是围绕指令变异进行混淆研究的很好例子。
下面是 ALCATRAZ 实现的该技术的一个示例,其中两个寄存器之间的任何加法都会被改变,但其语义等价性保持不变。简单的add
指令转换为 5 不同的指令( push
、 not
、 sub
、 pop
、 sub
)。
为了纠正这个问题,我们可以使用模式匹配来一起找到这些 5 指令,反汇编这些字节来找出涉及哪些寄存器,然后使用 Keystone 等汇编器来生成正确的相应字节。
不断展开
这种混淆技术在整个 DOUBLELOADER 样本中普遍存在,并且是各种形式的恶意软件中广泛使用的方法。这里的概念侧重于逆转编译过程;混淆器不是优化编译时已知的计算,而是“展开”这些常量,使反汇编和反编译变得复杂和混乱。下面是该技术的一个简单示例,其中已知常数( 46
)被分解为两个数学运算。
在 DOUBLELOADER 中,每当将立即值移动到寄存器中时,我们就会遇到这种技术。这些立即数被替换为多个按位运算,从而掩盖了这些常量值,从而破坏了任何上下文和分析师的流程。例如,在下面左侧的反汇编中,在地址 ( 0x18016CD93
) 处有一个 EAX 值的比较指令。通过查看前面的说明,由于多个模糊的按位计算,EAX 值应该是什么并不明显或不清楚。如果我们调试程序,我们可以看到 EAX 值被设置为0
。
为了清除这种混淆技术,我们可以用我们自己的示例来确认其行为,我们可以使用以下源代码并查看如何应用转换。
#include <iostream>
int add(int a, int b)
{
return a + b;
}
int main()
{
int c;
c = add(1, 2);
printf("Meow %d",c);
return 0;
}
编译后,我们可以查看左侧干净版本中main
函数的反汇编,并看到这两个常量( 2,1
)被移入 EDX 和 ECX 寄存器。右边是变换后的版本,两个常量隐藏在新添加的指令之中。
通过使用模式匹配技术,我们可以查找这些指令序列,模拟指令执行各种计算以恢复原始值,然后用 NOP 修补剩余的字节以确保程序仍能运行。
LEA 混淆
与前面讨论的技术类似,LEA(加载有效地址)混淆专注于模糊与 LEA 指令相关的立即值。减法算术计算将直接跟在 LEA 指令之后,以计算出原始预期值。虽然这看起来像是一个小小的改变,但它可能会对打破字符串和数据的交叉引用产生重大影响——这对于有效的二进制分析至关重要。
下面是 DOUBLELOADER 中这种技术的一个示例,其中 RAX 寄存器值通过加载初始值( 0x1F4DFCF4F
),然后减去( 0x74D983C7
)的模式进行伪装,从而得到一个新的计算值( 0x180064B88
)。
如果我们转到样本中的该地址,我们将进入只读数据部分,在那里我们可以找到引用的字符串bad array new length
。
为了纠正这种技术,我们可以使用模式匹配来找到这些特定的指令,执行计算,然后重新构建一个新的 LEA 指令。在 64 位模式下,LEA 使用 RIP 相对寻址,因此地址是根据当前指令指针 (RIP) 计算的。最终,我们得到一条如下所示的新指令: lea rax, [rip - 0xFF827]
。
以下是生成最终指令的步骤:
有了这些信息,我们可以使用 IDA Python 来修补所有这些模式,下面是修复 LEA 指令的示例。
控制流混淆
控制流平坦化是一种强大的混淆技术,它通过消除条件分支和循环等传统结构来破坏程序控制流的传统结构。相反,它使用集中调度程序重构执行,根据状态变量确定下一个要执行的基本块,这使得分析和反编译变得更加困难。下面是一个简单的图表,表示非扁平化和扁平化控制流之间的区别。
我们的团队已经在各种恶意软件(例如DOORME)中观察到了这种技术,在这种情况下,扁平化控制流是 ALCATRAZ 混淆器的主要功能之一,这并不奇怪。为了解决反平坦化问题,我们专注于使用安全研究员 Boris Batteux 编写的 IDA 插件D810来建立工具。
我们将从前面的示例程序开始,使用常见的_security_init_cookie
函数来检测缓冲区溢出。下面是非混淆形式的cookie初始化函数的控制流程图。从图中我们可以看到,有六个基本块、两个条件分支,我们可以轻松地跟踪执行流程。
如果我们采用相同的功能并应用 ALCATRAZ 的控制流展平功能,程序的控制流看起来会有很大不同,其中包含 22 基本块、 8 条件分支和一个新的调度程序。在下图中,彩色填充的块表示非混淆版本中的先前基本块,其余白色块表示用于调度和控制执行的添加的混淆器代码。
如果我们看一下反编译结果,就会发现函数现在被分解为while
循环内的不同部分,其中新的state
变量用于引导程序以及混淆后的残余内容(包括popf/pushf
指令)。
为了清理此功能,D810 采用了两个不同的规则( UnflattenerFakeJump
、 FixPredecessorOfConditionalJumpBlock
),应用微码转换来改进反编译。
2025-04-03 15:44:50,182 - D810 - INFO - Starting decompilation of function at 0x140025098
2025-04-03 15:44:50,334 - D810 - INFO - glbopt finished for function at 0x140025098
2025-04-03 15:44:50,334 - D810 - INFO - BlkRule 'UnflattenerFakeJump' has been used 1 times for a total of 3 patches
2025-04-03 15:44:50,334 - D810 - INFO - BlkRule 'FixPredecessorOfConditionalJumpBlock' has been used 1 times for a total of 2 patches
当我们刷新反编译器时,控制流平坦化被删除,伪代码也被清理。
虽然这是一个很好的例子,但修复控制流混淆通常是一个依赖于功能的手动且及时的过程。在下一节中,我们将总结一些学到的技术并将其应用到 DOUBLELOADER。
清理 DOUBLELOADER 函数
处理恶意软件中的混淆时面临的挑战之一并不是单个的混淆技术,而是技术的分层。此外,对于 DOUBLELOADER 来说,大量代码被放置在边界不明确的函数块中,这给分析带来了很大挑战。在本节中,我们将通过一个实际示例展示受 ALCATRAZ 保护的 DOUBLELOADER 函数的清理过程。
在Start
导出处启动后,第一个调用之一转到loc_18016C6D9
。这似乎是一个更大函数的入口,但是由于0x18016C8C1
处未定义的指令,IDA 无法正确创建函数。
如果我们滚动到这个地址,我们可以看到第一次中断是由于我们之前在博客文章中看到过的短跳转反汇编技术( EB FF
)。
修复附近出现的相同技术 6 后,我们可以回到起始地址 ( 0x18016C6D9
) 并使用 MakeFunction 功能。虽然该函数可以反编译,但它仍然被严重混淆,这对于任何分析来说都不理想。
回到反汇编代码,我们可以看到下面这个函数中使用的 LEA 混淆技术,其中字符串常量”Error”
现在使用之前的解决方案恢复。
下面的另一个示例展示了LoadIcon
调用的模糊参数的转换,其中lpIconName
参数被清理为0x7f00
( IDI_APPLICATION
)。
现在反编译已经得到改进,我们可以通过使用 D810 插件删除控制流混淆来完成清理。下面是展示前后效果的演示。
本节介绍了清理受 ALCATRAZ 保护的恶意混淆函数的真实场景。虽然恶意软件分析报告通常会显示最终结果,但通常会花费大量时间来消除混淆并修复二进制文件,以便可以对其进行正确的分析。
IDA Python脚本
我们的团队正在发布一系列概念验证IDA Python 脚本,用于处理 ALCATRAZ 混淆器强加的默认混淆技术。这些旨在作为处理这些技术时的基本示例,并应用于研究目的。不幸的是,处理混淆问题没有灵丹妙药,但掌握一些例子和一般策略对于解决未来类似的挑战会很有价值。
雅拉
Elastic Security 已创建 YARA 规则来识别此活动。
观察结果
本研究讨论了以下可观察的结果。
可观测 | 类型 | 名称 | 参考 |
---|---|---|---|
3050c464360ba7004d60f3ea7ebdf85d9a778d931fbf1041fa5867b930e1f7fd | SHA256 | DoubleLo.dll | 双装载机 |
参考资料
上述研究参考了以下内容: