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

本文详细介绍了CodeReason框架如何通过深度语义分析技术解析x86/ARM原生代码,实现字符串反混淆和指令优化,展示了在Flame恶意软件分析中的实际应用案例。

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

你是否曾想对原生模式程序进行查询,找出将特定值写入寄存器的程序位置?是否希望自动化解密被混淆的字符串?

逆向工程的挑战

逆向工程原生程序需要从底层理解其语义,直到浮现出高级功能图景。系统化理解原生程序面临的挑战在于:这种理解必须扩展到程序使用的每条指令。分析必须知道哪些指令对内存调用和寄存器产生何种影响。

我们为DARPA网络快速追踪项目开发的CodeReason机器码分析框架应运而生。该框架支持分析x86和ARM原生代码的语义,其核心价值在于能够查询原生代码对程序整体状态的影响机制,这得益于其对原生指令的深度语义理解。

构建语义分析系统

构建这种语义理解体系既耗时又昂贵。现有系统要么门槛过高,要么无法精确满足需求,或缺少语义简化和优化功能。而语义简化能将复杂的优化转化为易于理解的简单表达式。以下通过Flame恶意软件案例说明其应用价值。

Flame恶意软件字符串解密实战

当Flame恶意软件曝光时,其部分二进制文件被上传至malware.lu。该恶意程序将混淆字符串存储在全局数据结构中,结构如下:

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

我们使用CodeReason开发了C语言字符串解密器。原始解密逻辑分三步:

  1. 第一函数检查hasDeobfuscated字段:若为0则返回字符串首指针;否则调用第二函数并将该字段置零。
  2. 第二函数遍历字符串数组,对每个字符调用第三函数获取解密因子进行减法运算:
1
2
3
4
5
6
7
8
9
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;
  }
}
  1. 第三函数由单基本块构成,包含lea/add/imul/shr/xor等指令序列。

CodeReason的核心优势

通过CodeReason的语义分析系统,我们可以捕获该代码块并获取’eax’寄存器的运算方程,从而在解密器中复现get_decrypt_modifier的功能。虽然可将其反编译为C代码,但我们更关注代码对’eax’的影响而非高级语言表示。

使用CodeReason交互式分析获得如下语义表达式:

1
EAX = Xor32[ Xor32[ Shr32[ Xor32[ Shr32[ Mul32[ Add32[ REGREAD(EAX), I:U32(0xb) ],...]]]]]

据此可准确实现C语言版本:

1
2
3
unsigned char get_decrypt_modifier_f(unsigned int a) {
  return Xor32(Xor32(Shr32(...Mul32(Add32(a,0xb),Add32(a,0x11))...));
}

高级应用场景

  1. IDA插件集成:正在开发IDA插件支持直接查询
  2. 竞态条件发现:该解密器存在线程安全问题,多线程同时解密会永久破坏字符串
  3. 动态字符串构造优化:能将通过算术运算动态构建字符串的指令序列优化为直接内存写入:
1
2
MEMWRITE[esp] = '.dll'
MEMWRITE[esp-4] = 'nlan'

技术展望

将机器码转化为可优化和理解的形式具有强大潜力,特别是当这种能力通过编程库提供时。CodeReason不仅能提取字符串混淆函数的语义实现自动解密,还能将混淆代码简化为直接表达解密字符串的形式。我们将在后续文章中介绍更多应用案例。

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