使用CodeReason进行原生程序语义分析 - 揭秘二进制逆向工程利器

本文详细介绍了CodeReason框架如何通过深度语义分析技术解析x86/ARM原生代码,实现寄存器状态追踪、字符串反混淆等逆向工程核心功能,并展示了其在Flame恶意软件分析中的实战应用。

原生程序的语义分析:CodeReason技术解析

你是否曾想对原生模式程序发起查询,询问哪些代码位置会向特定寄存器写入特定值?是否希望自动化解混淆字符串的操作?

逆向工程的语义挑战

逆向工程原生程序需要在低层次理解其语义,直到浮现出高层次功能图景。系统化理解原生程序的一个关键挑战在于:这种理解必须覆盖程序使用的每一条指令。分析工具需要明确知道哪些指令会对内存调用和寄存器产生何种影响。

我们向您介绍CodeReason——这个为DARPA网络快速追踪项目开发的机器码分析框架。CodeReason提供了分析x86和ARM原生代码语义的框架,其价值在于能让我们平台化地查询原生代码对整体程序状态的影响,这得益于它对原生指令的深度语义理解。

构建语义理解的挑战

构建这种语义理解既耗时又昂贵。现有系统要么门槛过高,要么无法精确满足需求,或者没有对其语义进行简化和优化。而我们正需要这些简化——它们能将复杂的优化转化为易于理解的简单表达式。为说明这点,我们将展示一个CodeReason的实际应用案例。

Flame恶意软件分析实战

当Flame恶意软件曝光时,其部分二进制文件被上传到malware.lu。这些样本采用全局数据结构存储混淆字符串,结构如下:

1
2
3
4
5
6
struct ObfuscatedString {
  char padding[7];
  char hasDeobfuscated;
  short stringLen;
  char string[];
};

每个结构体末尾包含可变长度数据,其中7字节似乎未被使用。

技术亮点一:字符串反混淆

我们使用CodeReason编写了C语言字符串解混淆器。原始程序逻辑分三步进行字符串解混淆:

  1. 首函数检查hasDeobfuscated字段:若为零,返回字符串首元素指针;若非零则调用第二函数,并将hasDeobfuscated置零
  2. 次函数遍历’string’数组每个字符,调用第三函数获取值后从字符中减去该值,结果写回数组:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void inplace_buffer_decrypt(unsigned char *buf, int len) {
  int counted = 0;
  while( counted < len ) {
    unsigned char *cur = buf + counted;
    unsigned char newChar = get_decrypt_modifier_f(counted);
    *cur -= newChar;
    ++counted;
  }
  return;
}
  1. 关键函数get_decrypt_modifier仅包含一个基本块:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
lea ecx, [eax+11h]
add eax, 0Bh
imul ecx, eax
mov edx, ecx
shr edx, 8
mov eax, edx
xor eax, ecx
shr eax, 10h
xor eax, edx
xor eax, ecx
retn

CodeReason的优势在于:我们可以捕获该代码块并获取’eax’的方程式,从而理解该块的"返回"行为,在解混淆器中复现get_decrypt_modifier的语义。

语义分析 vs 反编译

虽然可以反编译此代码片段为C,但我们更关注代码对’eax’的影响,而非高级的C代码表现形式。CodeReason让我们能跳过最后一步,直接分析语义,这种方式往往更强大。

CodeReason实战演示

通过CodeReason获取语义表达如下:

1
2
$ ./bin/VEEShell -a X86 -f ../tests/testSkyWipe.bin
EAX = Xor32[ Xor32[ Shr32[ Xor32[ Shr32[ Mul32[ Add32[ REGREAD(EAX), I:U32(0xb) ], Add32[ REGREAD(EAX), I:U32(0x11) ] ], I:U8(0x8) ], Mul32[ Add32[ REGREAD(EAX), I:U32(0xb) ], Add32[ REGREAD(EAX), I:U32(0x11) ] ] ], I:U8(0x10) ], Shr32[ Mul32[ Add32[ REGREAD(EAX), I:U32(0xb) ], Add32[ REGREAD(EAX), I:U32(0x11) ] ], I:U8(0x8) ] ], Mul32[ Add32[ REGREAD(EAX), I:U32(0xb) ], Add32[ REGREAD(EAX), I:U32(0x11) ] ] ]

基于此,我们实现了等效的C函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
unsigned char get_decrypt_modifier_f(unsigned int a) {
  return Xor32(
    Xor32(
      Shr32(
        Xor32(
          Shr32(
            Mul32(
              Add32( a, 0xb),
              Add32( a, 0x11) ),
            0x8 ),
          Mul32(
            Add32( a, 0xb ),
            Add32( a, 0x11 ) ) ),
        0x10 ),
      Shr32(
        Mul32(
          Add32( a, 0xb ),
          Add32( a, 0x11 ) ),
        0x8 ) ),
    Mul32(
      Add32( a, 0xb ),
      Add32( a, 0x11 ) ) );
}

实际运行效果验证了其正确性:

1
2
C:\code\tmp>skywiper_string_decrypt.exe
CreateToolhelp32Snapshot

我们正在将CodeReason扩展为IDA插件,届时可直接在IDA中进行这些查询!

技术亮点二:竞态条件发现

这个字符串解混淆器存在竞态条件:若两个线程同时解混淆同一字符串,将导致字符串永久损坏。当处理关键混淆字符串时,这可能导致向系统服务传递错误数据,造成严重后果。

高级混淆破解案例

我们还用CodeReason破解了如下形式的字符串混淆:

1
2
3
4
xor eax eax
push eax
sub eax, 0x21ece84
push eax

通过巧妙利用二进制补码运算语义,这些指令序列将非字符串立即数转化为字符串值,并按正确顺序压栈,实现动态构建字符串。CodeReason配合简单窥孔优化器,能将其转换为字符串立即数的内存写入形式:

1
2
MEMWRITE[esp] = '.dll'
MEMWRITE[esp-4] = 'nlan'

结论

将机器码转化为可优化和理解的形式具有强大潜力!特别是当这种能力以编程库形式提供时。通过CodeReason,我们能够:

  1. 提取字符串混淆函数的语义
  2. 自动实现字符串解混淆器
  3. 将混淆代码简化为直接表达解混淆字符串的形式

我们将在后续博文中介绍CodeReason的更多应用场景和能力。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计