Group of Software Security In Progress

GoSSIP @ LoCCS.Shanghai Jiao Tong University

OmniUnpack: Fast, Generic, and Safe Unpacking of Malware

论文下载

摘要

恶意程序会使用加壳技术来轻易逃避基于签名的杀毒软件检测。因此最新的杀毒软件通过动静态结合的分析技术来恢复加壳软件的内容,但目前来看这样的技术并不很有效,因此作者提出了一个新技术叫OmniUnpack,当恶意程序脱壳后,实时监控程序执行,他能够直接将脱壳后执行的程序送给杀毒软件检测。并能够处理已知和未知的加壳算法并仅仅引入很小的性能损耗(至多11%)。

背景

加壳程序通常是通过解压或解密流程,将真正的恶意payload释放到内存中执行。通常这个unpack的步骤可以是一步,或多步来完成,即每次unpack出一部分代码。对于杀毒软件来说,必须等到程序开始执行以后,也就是恶意程序释放了真正的恶意payload后才能开始扫描代码的工作。

作者罗列了之前的工作,包括静态解密加壳算法(受限于已知壳),基于模拟器执行的通用脱壳(受限于模拟器环境与真机差异),基于单步调试(时间损耗)。而OmniUnpack的思想是监控程序执行,跟踪被写以及被写后执行的内存页面,一旦程序调用了潜在的危险系统调用时,OmniUnpack就调用杀毒软件来扫描被写的内存页面,如果不报毒,则继续执行。OmniUnpack修改少量的操作系统而不使用调试、模拟器等来防止程序自我保护技巧,OmniUnpack实施在内存页面级别的跟踪而不是指令级别,而且只扫描新生成的代码,因此性能损耗也非常低。

系统综述

因为内存页面级别的跟踪会导致大量的误报,也就是说内存页面被写后执行不代表真的脱壳代码已经生成,例如分步脱壳的中间过程,以及同一个页面被多次写和执行,这些都并不意味着真正脱壳的完成,因此OmniUnpack是等到内存页面被写和执行,并且调用了危险的系统调用时,才认为是脱壳步骤完成,才调用杀毒软件来扫描代码。危险的系统调用:例如Windows上的创建和写注册表等这类能够修改OS状态的都算。另外整个扫描过程是随着程序的执行而持续进行的,原先的单次扫描可能会因为多步脱壳解密而漏掉真正的恶意代码,持续的扫描让恶意软件延迟释放恶意负载也毫无作用,而这样做的前提是OmniUnpack只有很小的时间性能损耗。

OmniUnpack算法

记录被写的内存页面 W,和被写后执行的内存页面 WX(显然 WX 是 W 的子集),直到有危险系统调用在 WX 中被执行,则去扫描之前所有的 W,检测出恶意就返回了,没有就继续,并且将 WX 和 W 置空。

实现

在Intel IA-32处理器的WinXP中将OmniUnpack作为内存驱动。借鉴了OllyBone这个OD插件,这个插件采用类似PaX PAGEEXEC的方法来允许页面级别的BreakOnExecute,用户空间的杀毒软件是基于开源的ClamAV。

内存页面监控是利用了硬件提供的内存保护机制,被写后执行的行为可以通过拦截违反W⊕X策略的异常处理来检测。当然这个W⊕X策略只是为了拦截异常而设置,原始的策略被保留和恢复当需要保证被监控的程序继续正常执行下去。许多硬件架构(IA-64,SunSparc,Alpha等)支持内存页面的RWX策略设置,但像(IA-32)这种并不支持,不过可以用PaX PAGEEXE项目提到的方式,来软件模拟NX策略的内存页面管理。有危险系统调用就进行代码扫描,用户空间的杀毒软件通过共享内存方式得到加壳程序内存,而杀毒软件的签名必须是满足两个条件:

  • 基于原始恶意程序而非脱壳步骤和加壳后的
  • 基于调用危险系统调用前的恶意代码片段特征
  • 基于恶意代码从unpack自身到调用危险系统调用之间,并没有二次修改或擦除之前已经unpack的代码。

实验

  • 实验环境是个QEMU的虚拟机,里面是XP SP2。
  • 对比PolyUnpack(作者自己实现的,因为原版有版权问题)和ClamAV unpacker,速度上秒杀PolyUnpack,5秒对比100多秒,准确率上秒杀ClamAV,80%对于15%。
  • 通过执行恶意程序到最终的unpack步骤完成,OmniUnpack大概要比PolyUnpack快上20多倍,而只比ClamAV慢五倍,这里是只算上第一次到达unpack步骤的时间。
  • 网上找了20个不同的加壳程序去加密一个样本,ClamAV脱了其中五个壳并检测出3个,可能因为脱壳程序有bug的原因,而OmniUnpack检测出其中的16个,没有检测出的Armadillo和CExe的原因是这两个壳解密了一个可执行文件并执行,OmniUnpack没有去跟踪新创建的进程。nPack解密可执行文件并执行后就crash了,teLock貌似是因为有QEMU不支持的指令,作者认为这个问题可以在真机上被克服。
  • 作者最后用普通压缩程序加壳做性能检测发现只会增加11%以下的性能损耗。

相关工作

值得一提的是PolyUnpack,貌似是使用调试器执行加壳程序,一旦发现有没有出现在原始静态反汇编中的指令序列时,就认为有壳,但他受限于静态反汇编自身的短板,而且调试器引入的性能损耗也很大。