Group of Software Security In Progress

GoSSIP @ LoCCS.Shanghai Jiao Tong University

Different Is Good: Detecting the Use of Uninitialized Variables Through Differential Replay

作者:Mengchen Cao, Xiantong Hou, Tao Wang, Hunter Qu, Yajin Zhou, Xiaolong Bai, Fuwei Wang

单位:Orion Security Lab, Alibaba Group, School of Cyber Science and Technology, Zhejiang University

会议:CCS 2019

出处:http://malgenomeproject.org/papers/ccs19_timeplayer.pdf


Abstract

使用未初始化变量会带来一系列的问题,内核中使用未初始化变量可能导致内核信息泄露,从而会导致本地提权.但其检测一直是一个难题,尤其是针对闭源系统。目前最好的检测方式为 Google P0 团队的 Bochspwn Reloaded,虽然发现众多,但其检测精度不够,漏报较多。作者利用差分重放发现内核信息泄露漏洞,共发现windows kernel的信息泄漏漏洞34个。

简要概括:作者使用两台实例,进行差分(分配堆栈时填充特殊值(poison))、确定性(控制程序初始状态、输入完全一样)重放。当检测到内存访问发生差异时可以认为使用了未初始化变量。

作者的贡献:

  • 使用差分重放的技术,能够检测windows内核中的未初始化变量的使用,且不需要源码。
  • 使用符号化污点分析技术,能够定位未初始化变量的source,并设计了两个优化措施
  • 应用在windows 10和windows 7上,7个月发现了34个漏洞,其中17个已经被Microsoft确认为0 day。

Key words

uninitialized variables、Kernel、closed source、Differential Replay、symbolic taint analysis

Background

未初始化变量

为什么使用未初始化变量会带来很大的危害?

kernel使用了KASLR,如果一块内存中存放着kernel object的地址,使用那块内存的变量未进行初始化而泄漏到用户空间,将损害KASLR的安全性。攻击者可以根据泄漏的信息进行提权

为什么检测未初始化变量很困难?

静态分析:

  • 现代编译器(gcc clang visual studio)具备简单的检测功能。但大多局限于一个函数,且对于数组、指针、循环、结构体、union等无能为力。
  • 需要源码

动态分析:

  • 首先使用未初始化变量这类内存泄漏问题通常不会导致crash或很明显的影响。

  • 一种naive的思路是:进行全系统的动态污点分析,在分配栈或堆时设置source,赋值时去除污点标记,如果程序使用了一块tainted的内存,则可以确定使用了未初始化变量。

  • 但进行全系统的污点分析会带来极大的overhead
  • 目前最好的工具Bochspwn Reloaded仅对特定类型指令进行污点传播,从而降低overhead,但导致了大量的漏报。
  • 其他内存检测工具如MemorySanitizer等,需要基于源码插桩,且仅适用于用户态

作者的方案是通过差分重放来检测未初始化变量的使用,overhead比上述动态分析方案大幅度降低。

确定性记录和重放框架PANDA

PANDA是一个基于QEMU开源的动态分析平台,可以进行确定性记录和重放。

对于一个程序/系统而言,初始状态相同,运行时输入的内容、时间相同,中断、DMA相同,则多次运行的各个状态应该相同。

输入(IN)、硬件中断、DMA事件(Direct Memory Access,直接内存存取)称为不确定性事件

PANDA在

  • record时,先记录下程序的初始状态,当不确定性事件发生时,PANDA记录下其发生的时刻和内容。

  • replay时,恢复初始状态,并在对应的时刻利用之前的记录处理不确定性事件。
  • 使得这些不确定性事件确定化,故称为确定性重放

image-20191217150734653

通过PANDA的record和replay我们能做些什么?

  • 控制变量(各个状态、不确定性事件)、对比实验。
  • 本文的工作中作者修改了堆栈分配时的状态(poison,使用0xaa填充),通过确定性重放,对比后续内存操作的状态,发现差异则能确定使用了未初始化变量。

为什么对比后续内存操作的状态,发现差异就则确定使用了未初始化变量

正常初始化了的变量———————-在两次重放中可以视为一常量————两次重放结果相同

未初始化变量(被0xaa填充)———值由poison决定(0xaa)———————结果存在差异

未初始化变量,要么别用;用了,改变了程序的状态,如果被拷贝到用户空间了,就必然会被发现。

污点分析

静态分析:

image-20191217160433058

A Motivating Example

作者用一个内核信息泄漏漏洞(CVE-2018- 8408)作为例子,展示目前工具的局限性,以及作者工具发现该漏洞的过程。

image-20191218001809312

截取了三条关键的汇编指令。

  • 第一条汇编(黄色),分配栈上变量,未进行初始化
  • 第二条(蓝色),将其中4个字节存入ecx寄存器
  • 第三条(紫色),将ecx的内容存入eax+4的位置,而此时eax指向的是用户空间

为什么不能被Bochspwn发现?

前文中提到Bochspwn为了避免过高的overhead,仅对特定种类的指令进行污点传播,(其对memory to memory的指令进行污点传播,而不对memory to reg的指令进行污点传播),所以污点无法进入ecx,从而无法被Bochspwn发现。

作者的系统是如何发现这个漏洞的?

  • 首先通过确定性重放,poisoned kernel stack,执行到0x83e24501 mov dword ptr [eax + 4], ecx时,对比地址0x021afad8发现差异:
  • image-20191218001741684
  • 接下来利用符号化污点分析技术,定位未初始化变量的source(变量是分配在哪的):
    • 利用stack trace记录的信息,往前500个栈帧开始dump trace
    • 再对trace进行符号化污点分析
    • 遇到sub esp, eax、函数ExAllocatePoolWithTag等栈、堆分配指令时,新建一个符号并打上taint标记
    • 遍历指令,传递污点标记。遇到被赋值操作,则去除对应符号的污点标记。
    • 污点分析到0x83e24501 mov dword ptr [eax + 4], ecx时,查看ecx对应的符号即可知道其是在哪里被分配的。

image-20191217165414072

System Overview

image-20191217165625437

差分重放:

  1. 使用全系统模拟器记录内核和用户态的运行
  2. 重放
  3. 对比检测到差异

符号化污点分析:

  1. 在检测到差异的指令回退500个栈帧,利用得到的trace,开始符号化污点分析
  2. 进行污点传播
  3. 到达检测到差异的指令,确定泄漏内存被分配的位置

Key Technique 1: 差分重放

记录程序的执行

为什么选择PANDA?

  • PANDA基于QEMU,是非侵入式的,不同于PIN、Valgrind,不会改变程序的行为(如内存分配)
  • 需要追踪的数据可能会跨越内核态和用户态,因此需要全系统模拟器。
  • PANDA支持系统级的确定性重放

Poisoning Memory

进行重放时,会启动两个实例。一个不会修改任何内存,另一个会在堆栈分配的时候毒化内存。

  • 栈帧的创建
    • sub esp, xxx
  • 堆内存的分配
    • 通过调用ExAllocatePoolWithTag函数分配内存

使用0xaa填充poisoned memory

Comparing Replay Instances

检查点

检查内存读写指令,如mov qword ptr [rsi], rax,此时会检查两个实例中寄存器rsi的内容,以及[rsi]的内容。作者实现的系统只考虑向用户空间地址写的情况。

比较差异

两个instances之间使用一块共享内存,作者使用生产者消费者模型进行检查点信息的比较。正常的实例作为生产者,运行并将检查点信息存入共享内存中;poisoned实例作为消费者,运行并从共享内存中取检查点信息来比较。

并行重放

作者实现的系统允许将一次执行记录切成多片并发执行,从而提高效率,但这样会带来漏报(内存泄漏所发生的指令与分配的指令不在同一个切片上的情况)

Key Technique 2: 符号化污点分析

准备traces

符号化污点分析是在程序执行的trace上进行的。在重放时并不会边执行边收集trace,而是在发现一个differential point时,回溯一定数量的指令,再次执行并记录trace。对于每条执行的指令,都会记录下其PC、SP、通用寄存器的值、访问的内存。

符号化污点分析

作者将堆栈上分配的内存定义为source,在差分重放中发现差异的那条指令作为sink。借助于VEX IR

污点传播如 Figure 5所示。

优化

作者提出了两个方案来提高符号化污点分析的效率:选择执行、符号表达式封装。

选择执行

如果一条指令不涉及污点内存或寄存器操作,则可以跳过(native执行),直接到下一条操作污点的指令。

符号表达式封装

随着符号表达式越来越复杂,使用新符号表达式带来的时间、空间上的开销也越来越大。

作者在符号表达式的长度和深度上设置threshold,当超过threshold时,便会使用一个新的符号来表示。当然,会记录他们之间的关系。

image-20191217195150802

Evaluation

有效性

在7个月的时间内,发现了34个windows 内核信息泄漏的漏洞,17个已申报CVE

image-20191217195830828

与Bochspwn对比

使用了56个Bochspwn提供的公开的POC,在旧版本windows上测试,Bockspwn发现了67个漏洞,作者的系统除了发现了那67个之外,还发现了15个漏洞,共85个。

效率

作者将自己的系统与动态污点分析做对比。

作者的系统花费47个小时发现了34个漏洞,动态污点分析花费了66个小时发现了7个漏洞。

image-20191217202326744

性能

Differential replay

比普通的重放慢22-24倍,作者提出可以通过并行重放来提高速度。

image-20191217203041543

symbolic taint analysis

当检测到一个differential point时,需要回溯栈帧并生成trace。

image-20191217203217630

总结

作者通过差分重放和符号化污点分析,设计了一套适用于闭源kernel的、用于检测未初始化变量使用及定位未初始化变量分配位置的系统,并应用在了windows 7和windows 10kernel上,并发现了34个新的漏洞。

评价

作者通过差分重放来发现漏洞,并通过符号化污点分析来定位泄漏内存的位置。

差分重放通过控制变量、设置对照实验、对比发现问题,思路值得学习。

符号化污点分析,仅对生成的trace进行分析,并通过选择执行、符号表达式封装提高性能。

这套系统还是需要靠输入触发漏洞,可以跟fuzzer相结合。

资料

污点分析:https://www.cs.cmu.edu/~ckaestne/17313/2018/20181023-taint-analysis.pdf