使用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的其他用途和能力。