语义分析原生程序与CodeReason
你是否曾想对原生模式程序进行查询,询问哪些程序位置会将特定值写入寄存器?是否曾希望自动反混淆被混淆的字符串?
逆向工程原生程序涉及在低层次理解其语义,直到功能的高层次图景浮现。原则性理解原生模式程序的一个挑战是,这种理解必须扩展到程序使用的每一条指令。你的分析必须知道哪些指令对内存调用和寄存器有什么影响。
我们想介绍CodeReason,这是我们为DARPA Cyber Fast Track制作的机器代码分析框架。CodeReason提供了一个分析原生x86和ARM代码语义的框架。我们喜欢CodeReason,因为它为我们提供了一个平台,可以查询原生代码对整体程序状态的影响。CodeReason通过深度语义理解原生指令来实现这一点。
构建这种语义理解既耗时又昂贵。现有系统存在高入门门槛、无法精确满足需求,或未对其语义应用简化和优化。我们想这样做,因为这些简化可以将复杂的优化减少为易于理解的简单表达式。为了说明这一点,我们将给出一个使用CodeReason的示例。
简化Flame
当Flame恶意软件被揭露时,其部分二进制文件被发布到malware.lu。它们的整体方案是将混淆字符串存储在全局数据的结构中。结构如下:
|
|
每个结构末尾都有可变长度数据,其中7字节数据显然未使用。
这里有两个有趣的事情。首先,我使用CodeReason用C语言编写了一个字符串反混淆器。原始程序逻辑分三步执行字符串反混淆。
第一个函数检查hasDeobfuscated字段,如果为零,则返回指向字符串第一个元素的指针。如果字段不为零,它将调用第二个函数,然后将hasDeobfuscated设置为零。
第二个函数将迭代“string”数组中的每个字符。在每个字符处,它将调用第三个函数,然后从字符串数组中的字符减去第三个函数返回的值,将结果写回数组。因此,它看起来像:
|
|
那么第三个函数‘get_decrypt_modifier’呢?这个函数只有一个基本块,看起来像:
|
|
拥有原生代码语义理解系统的一个优势是,我可以捕获这个块并将其输入CodeReason,让它告诉我‘eax’的方程是什么。这将告诉我这个块“返回”给调用者的内容,并让我在我的反混淆器中捕获get_decrypt_modifier的语义。
也可以将这个片段反编译为C,但我真正关心的是代码对‘eax’的影响,而不是像C反编译器视图中的代码“看起来”那样高层次的东西。C反编译器也使用语义翻译器,但然后通过尝试翻译为C来代理该翻译的结果。CodeReason让我们跳过最后一步,只考虑语义,这有时可能更强大。
使用CodeReason
从CodeReason获取这个看起来像:
|
|
这很酷,因为如果我实现Xor32、Mul32、Add32和Shr32的函数,我在C中有这个函数,像这样:
|
|
这也很酷,因为它有效。
|
|
我们正在将CodeReason扩展为IDA插件,允许我们直接从IDA进行这些查询,这应该非常酷!
第二个有趣的事情是,这个字符串反混淆器有一个竞态条件。如果两个线程同时尝试反混淆同一个线程,它们将永远损坏字符串。如果你试图用混淆字符串做重要的事情,这可能会很糟糕,因为它会导致将错误数据传递给系统服务或其他东西,这可能产生非常坏的影响。
我使用CodeReason攻击了像这样实现的字符串混淆:
|
|
其中原生指令序列会将非字符串立即值转换为字符串值(通过巧妙使用二进制补码算术的语义),然后以正确的顺序将它们推入堆栈,从而每次反混淆代码运行时动态构建字符串。CodeReason能够查看这一点,并使用一个非常简单的针孔优化器,将代码转换为字符串立即值的内存写入序列,如:
|
|
结论
将机器代码置于可以优化和理解的形式可能非常强大!特别是当这可以从程序库中获得时。使用CodeReason,我们能够提取字符串混淆函数的语义,并自动实现字符串反混淆器。此外,我们能够将混淆代码简化为表达反混淆字符串值本身的形式。我们计划在未来的博客文章中介绍CodeReason的其他用途和能力。