什么是“不透明谓词”?

逆向工程 混淆
2021-06-21 02:09:24

在混淆论文中多次看到不透明谓词的术语据我所知,它指的是很难以自动方式评估的谓词。将其放置在程序的关键点 ( jmp, test, ...) 可能会误导自动工具对程序的分析。

我的定义不够精确,而且我不知道如何估计这样一个谓词不透明度(它的效率)。那么,有人可以给出一个正确的定义,或者一些例子吗?

4个回答

该线程中已有的答案很好。简而言之,不透明谓词是“如果程序分析不够复杂,程序分析可能会遗漏的东西”。Denis 的示例基于常量传播的逆,并用作反校验和机制。Joxean 的SetErrorMode示例是用于动态反仿真的基于环境的不透明谓词。Ange的两个回答也是动态反仿真;基于环境,基于不常见的平台功能。Ange 的另一个例子更像是通过间接寻址的反反汇编技巧。

在学术文献中,不透明谓词被称为总是在一个方向上执行的分支,该分支为程序的创建者已知,而分析器先验未知。这个定义中故意省略了不透明谓词的“硬度”的概念。学术谓词通常基于数论结构、别名关系、递归数据结构;基本上是程序分析研究人员普遍理解的任何会导致程序分析工具出现问题的东西。

我最喜欢的研究员 Mila Dalla Preda 已经表明,抽象解释器打破给定类别的不透明谓词的能力与该域相对于谓词测试的属性的“完整性”有关。她通过使用基于 mod-k 的不透明谓词进行演示,并针对常见的变换器(加法、乘法等)引出了 mod-k 的完整域系列(即不会产生抽象精度损失)。然后,她探索了使用诸如完整性细化等晦涩的理论构造来自动构造一个域以打破特定类别的谓词。有关更多详细信息,请参阅本文

一个不透明的谓词是模糊化的条件,即,遵循的条件操作,将会使分析困难,而且在某些情况下是不可能的代码,直到实际执行,直到该条件被评估。

这用于破坏静态分析(结果不可预测)或仿真(区分真机和仿真环境)。

它们可以依赖于执行条件、CPU 特性、API 调用以及是否记录在案。

例子

初始值

由于寄存器的值在进程启动时既不是空也不是完全随机的,因此可以依赖它们来创建看起来随机但实际上是确定性的测试:

例子:

<EntryPoint>:
   jnz <InvalidPath>
   <ValidPath>
  1. 标志寄存器总是246在入口点
    • ZF 总是设置
  2. 因此,jnz永远不会被采取。

校验和

  1. 计算某段代码或数据的校验和
    • 最好是在运行之前不存在的东西
  2. expected_result ^ jump_target
  3. 盲目地用结果跳到某处

因此,可能无法提前知道接下来的指令是什么。

数学函数

  1. 在 FPU 中实现渐近函数
    • 与标准指令相比,FPU 更有可能不受支持或被错误地模拟/分析
  2. 实施足够的迭代以保证结果
    • 多次迭代可能会使模拟器用完周期
  3. 在测试中使用最终结果,作为跳转目标等......

谓词不一定很难评估。不透明谓词是程序员预先知道结果并且不能静态解析(例如由编译器)而必须动态解析的条件。

几年前我在恶意软件中注意到的一个例子:

  SetErrorMode(100);
  if ( SetErrorMode(1024) == 100 )
    // Valid Path
  else
    // Invalid Path

如果不执行程序(或不知道 Win32 API 的SetErrorMode工作原理),就无法确定程序将采用哪条路径。然而,当SetErrorMode返回最后一个前一个代码集时,程序员在执行这段代码之前就知道第一个路径是唯一有效的路径。

它也可以在恶意混淆的 JavaScript 代码中找到:

001     vz = 1;
002     var1 = 49;
003     var2 = var1;
004     if (var1 == var2){
005       document.location = "http://path.to.malicious.website";
006     }

在这里,您看到第 001-004 行尝试声明变量和条件,这毫无意义,并提出更改混淆代码哈希并使自动分析复杂化。

ps 这是非常简单的例子,关于 JS 中不透明谓词的更复杂的例子,请参阅 DefenceCode博客文章