LLVM-powered deobfuscation of virtualized binaries
这项工作是在 Thalium 进行的实习期间完成的,主题是虚拟化二进制文件的去混淆。
背景
混淆是故意使代码难以理解的过程,以阻碍其分析。它通常用于恶意软件中,以隐藏恶意意图并避免检测。
如今存在多种二进制混淆策略,包括:
-
移除注释/符号 -
添加不透明谓词(基于常量条件的分支) -
控制流扁平化 -
虚拟化
虚拟化是当今最流行和最有效1的混淆形式之一。
包括Tigress[1]、Themida[2]和VMProtect[3]在内的多种混淆器提供虚拟化。由于其强大和混淆器的高可用性,虚拟化不幸地被威胁行为者使用,并在众多恶意软件中发现(来源:MITRE[4])。
以下是虚拟化混淆二进制文件架构的粗略概述:
在虚拟化二进制文件中,原始程序被编码为一系列_虚拟指令_。混淆程序将包含这些_虚拟指令_以及一个_解释器_。_解释器_负责执行虚拟指令。它通常包含一些独特的组件:
-
一个负责上下文切换和初始化虚拟 CPU 的_VM 入口_; -
一个同样用于上下文切换的_VM 出口_; -
一个_调度器_,它在_虚拟程序计数器_(VPC)处检索_虚拟指令_,然后将控制流发送到给定_虚拟指令_的适当_处理程序_; -
处理程序,它们编码不同_虚拟指令_的语义。
虚拟指令可以是任何东西,从逻辑操作(AND,XOR)、控制流操作(JUMP,BRANCH)到更复杂的操作(见 Schloegel等人Loki: 加固代码混淆以抵御自动化攻击 (usenix.org[5]))。
去虚拟化策略
手动分析
如果你曾经被引导手动逆向工程一个虚拟机(一个受虚拟化保护的程序),你会知道这通常是一个漫长而复杂的任务。如果你没有,你可以查看我们的另一篇文章kaleidoscope[6]并自己尝试一下!
去虚拟化的直观方法通常是识别虚拟机上下文(例如,虚拟程序计数器和其他虚拟寄存器的位置),逆向工程每个处理程序,然后为该虚拟机创建一个自定义反汇编器。然后,我们可以反汇编虚拟指令并逆向工程原始程序。
这非常繁琐,主要是因为虚拟机中使用的架构可能在每个混淆程序中变化。此外,虚拟化二进制文件可能会进一步混淆,使得识别和逆向工程处理程序变得困难。例如,一种常见的加固策略是加密虚拟指令。
自动分析
Yadegari _等人_在他们的论文自动去混淆可执行代码的通用方法 (ieee.org[7]) 中提出了一种不同的方法,使用动态污点分析来重建原始程序的控制流图(CFG)。Jonathan Salwan _等人_在符号去混淆:从虚拟化代码回到原始 (shell-storm.org[8]) 中扩展了这一策略,通过利用符号执行和编译器优化来重建去虚拟化程序。
我们的去虚拟化方法深受这些基于污点的方法的启发。考虑到实习的时间有限,我们稍微简化了这一方法。我们还想尝试去除符号执行组件,因为它 notoriously 慢。
对于我们的去虚拟化,我们对混淆函数执行动态污点分析,标记其参数。然后,每当我们在控制流图中遇到污点条件指令(分支、cmove
等)时,我们就会拆分混淆二进制文件的执行轨迹。最后,我们将这些块组合起来重建控制流图。
这种方法并不是最稳健的,但对于我们测试的 Tigress 混淆二进制文件效果良好(这将在_结果_部分进一步详细说明)。
示例
解释我们的方法的最佳方式是通过一个示例。
让我们考虑一个简单的程序:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
int counter(intinput) {
int i = 0;
while (i < input) {
i += 1;
}
return i;
}
其控制流图(CFG)如下所示:
该程序的虚拟化版本可能如下所示:
上图中包含了虚拟机的主要元素。在这里,虚拟指令(“操作码”)仅仅是处理程序的颜色。
请注意,混淆该程序的过程增加了额外的基本块(带阴影的部分)。这些包括修改虚拟程序计数器(VPC)和调度程序的操作。
污点分析
对上述混淆程序进行污点分析显示,红色块(分支指令)是唯一的污点指令(它是唯一与用户输入交互的指令)。
这是一种非常简化的视图:在更复杂的程序中,污点会在执行过程中传播。
执行轨迹
然后,我们收集了一个随机输入(在这个例子中我们选择了2
)的混淆程序的执行轨迹:
这些指令都是连续的,箭头只是为了使轨迹适合屏幕。
请注意,轨迹依赖于输入。如果我们选择输入-1
,轨迹将不包含绿色块。
拆分轨迹
在这个阶段,我们可以在污点控制流图指令(我们确定为红色块)上拆分轨迹。
拆分后,我们的块看起来像这样:
如您所见,第二组和第三组块是相同的。因此,我们可以将它们合并。使用完整报告中详细说明的各种启发式方法,我们能够合并和拆分块以创建最终的控制流图:
简化控制流图
此时,控制流图仍然相当复杂,包含许多无用的指令。去混淆过程的下一步是使用编译器优化来简化代码。
使用 LLVM
正如我们刚才看到的,基于污点的去虚拟化依赖于编译器优化来简化重建的控制流图并去混淆程序。我们决定使用 LLVM 的先进优化。
我们没有仅仅在去混淆管道的最后整合 LLVM,而是决定在 LLVM IR 中执行所有分析。我们受到另一种去混淆工具的启发:_Saturn_[9],它使用 LLVM IR 作为中间表示来去混淆不透明谓词。
首先,这使我们能够在去混淆管道的最开始进行优化。这一轮初步优化使我们能够去除简单的混淆过程,从而加快后续分析的速度。
另一个重要的考虑是,使用 IR 我们能够轻松创建多架构工具。撰写本文时,我们能够去虚拟化amd64
和aarch64
二进制文件。我们正在添加对其 32 位对应物的支持。
最后但同样重要的是,使用 LLVM 的 IR 使我们能够访问一整套工具。LLVM 的生态系统不仅包括 LLVM 的分析和优化过程,还包括第三方工具,如符号执行引擎KLEE[10]。值得注意的是,这也反过来适用:我们将能够通过我们的分析模块为 LLVM 的生态系统做出贡献。例如,在这次实习期间,我们创建了一个用于 LLVM IR(版本 17)的动态污点分析引擎。
提升
一个重要的步骤是_提升_:将机器指令翻译成另一种表示(在这种情况下是 LLVM IR)的过程。
为此,我们使用了 Trail of Bits 的_remill_[11]库。使用_remill_证明是棘手的,但我们从_Saturn_的作者那里获得了一些有用的建议,详细说明了提升完整控制流图的策略,以及自定义 LLVM 过程和简化生成的 LLVM IR 的技巧。
提升二进制文件是实习期间一个庞大而复杂的部分。
结果
在这次实习期间,我们能够在使用 Tigress 混淆器混淆的二进制文件(包括amd64
和aarch64
)上使用我们的去虚拟化工具。
值得注意的是,我们能够快速部分去混淆第一个Tigress 挑战[12]。
例如,在我们的机器上(操作系统:Linux - Fedora 40,CPU:6 核 Intel Core i5-8500,内存:15.44 GiB),我们在不到一秒的时间内部分去混淆了challenge0000-binary1
。作为参考,_Triton_声称在 9.20 秒内完全去混淆相同的二进制文件。
这是混淆的amd64
challenge0000-binary1
:
这是去混淆的 LLVM IR 部分结果(132 行 LLVM IR):
这是在使用llc
编译结果后再用 IDA 反编译的 C 代码片段:
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line
v16_0 = (input & 0x222C2AFC) - 0x14582014;
v16_1 = input + 0x1DF2339F * v16_0 + 0x22D2B77C;
V16_2 = (input & 0x140538E4) - 0x5F1E4CE7;
v16_3 =
input + (v16_1 >> ((((unsigned __int8)((input & 0xE4) + 0x19) >> 3) & 7u) - 1) >> 1);
if ((v16_0 ^ v16_1) == V16_2 * v16_3) {
_mermaid_missing_block();
JUMPOUT(0x2B2LL);
}
在 C 代码片段中,您可以看到对一个缺失块的调用,因为我们没有访问该执行路径。如果我们选择了不同的输入,我们可以探索这个分支(但不能另一个)。为了实现更好的覆盖率,我们可以使用符号执行来找到允许我们访问各种执行路径的输入。
限制
尽管我们在速度和模块化方面取得了令人鼓舞的结果,但这次实习的范围仍然相当有限:
-
我们仅考虑了没有调用的纯函数的去混淆; -
我们只去混淆了单一的执行路径; -
我们的去混淆器在某些循环中遇到困难。
完整报告
如果您觉得这项工作有趣,我们决定将完整的实习报告公开发布在这里[13]。该报告包含额外的技术细节、对限制的讨论以及我们的未来计划。
参考资料
Tigress:https://tigress.wtf/
[2]Themida:https://www.oreans.com/Themida.php
[3]VMProtect:https://vmpsoft.com/
[4]来源:MITRE:https://attack.mitre.org/techniques/T1027/002/
[5]usenix.org:https://www.usenix.org/conference/usenixsecurity22/presentation/schloegel
[6]kaleidoscope:https://blog.thalium.re/posts/ecw-2023-kaleidoscope-write-up/
[7]ieee.org:https://ieeexplore.ieee.org/document/7163054
[8]shell-storm.org:https://shell-storm.org/talks/DIMVA2018-deobfuscation-salwan-bardin-potet.pdf
[9]Saturn:https://arxiv.org/pdf/1909.01752
[10]KLEE:https://klee-se.org/
[11]remill:https://github.com/lifting-bits/remill
[12]Tigress 挑战:https://tigress.cs.arizona.edu/challenges.html
[13]在这里:https://blog.thalium.re/posts/misc/llvm-powered-devirtualization/internship-report.pdf
原文始发于微信公众号(securitainment):基于 LLVM 的虚拟化二进制文件去混淆
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论