Group of Software Security In Progress

GoSSIP @ LoCCS.Shanghai Jiao Tong University

Deep Packer Inspection: A Longitudinal Study of the Complexity of Run-Time Packers

今天要介绍的一篇论文SoK: Deep Packer Inspection: A Longitudinal Study of the Complexity of Run-Time Packers 来自SP’15,这篇论文是一篇总结性质的论文 (SoK:State of Knowledge),对当前计算机程序的加壳保护技术 (Packer) 进行了系统和深入的分析讨论。

作者单位:DeustoTech, University of Deusto, Spain; Eurecom Sophia Antipolis, France

论文下载:http://www.s3.eurecom.fr/docs/oakland15_packing.pdf

1 简介

本文深入探讨了当前在PC平台上广为使用的加壳技术,其贡献在于:a) 提出了一种Packer Taxonomy,从壳的结构和复杂性上评估不同壳的强度; b) 广泛研究了大量的壳,包括一些地下流传的加壳技术。 作者指出对抗当前加壳技术的一些朴素的认识包括:

  1. 在某个时刻,壳会把整个原始代码段都完整释放到内存中
  2. 对于使用多层加壳技术的样本,每一层都会逐级解码下一级的代码,直至最后一层壳将原始代码释放出
  3. 壳的代码和原始代码的执行泾渭分明(存在某一个时刻,壳代码会将控制权转移给原始代码)
  4. 壳代码与原始代码总是运行在同一个进程间,不存在IPC通讯过程

作者指出,上述这些认识事实上低估了壳的复杂性,使得基于这些认识的一些方法在处理复杂壳时无能为力。作者提出了如下三个问题:

  1. 有多少已知的壳是符合上述朴素认识的(即,可以被一些已有的unpacking技术方法所处理)?
  2. 目前观察到的最复杂的壳是什么情况?
  3. 有多少Malware families使用了此类(最复杂)的packing技术

为了回答上述问题,作者开发了一系列动态分析工具来研究加壳技术的各类动态运行时信息,进而揭示了许多更为本质的壳的特征。更为重要的是,这些研究又引出了新的一类问题:壳的发展极为迅速,早年的一些经典强壳(Armadilllo、Asprotect、Yoda等)实际上虽然是地下流传,但也广为人知且被深入剖析,然而实际上更多私有的加壳保护技术仍然尚未被深入研究。作者试图去了解:

  1. 有多少并未广泛流行的壳被使用
  2. 这些地下流行的壳和主流公开发售的加壳保护技术相比复杂度如何
  3. (终极问题)在过去这些年,packing界的landscape到底发生了什么样的变化,壳演化得更为复杂了么?是否有更为多样化的packer出现?

2 A Packer Taxonomy

作者提出了对壳的分类学,具体地,从如下几个方面来进行衡量:

A 加壳层数

传统的对于壳的研究将加壳后的程序执行过程分为不同的阶段,一般认为,壳本身作为一个routine,会先于原始代码执行,然后通过一个所谓的“tail jump”转移指令回到真实的OEP。这种分析方法甚至还被一些论文正规化定义,提出了execution phases和code wave等概念,然而,类似VMP和Themida这一类vm based code protection技术的出现改变了对这种分层的认识。因此本文作者提出了一种保守的定义:在程序最初执行前,所有内存中代码属于第L_0层,当一块内存被修改并执行,该内存所属的层数,要由所有修改它的代码中,最大的L_i决定(即该内存属于L_{i+1})层。

B 并行Packers

很多壳会启动多个processes来脱壳,典型的例子包括由一个process释放出一个executable然后执行。衡量此类壳的复杂度的一个关键点在于这些进程之间是否存在通讯,通讯的情况如何

C 转移模型

在壳的分层模式下,不同层之间的转移模型也是衡量壳复杂度的一个关键点,这里作者定义了前向(从高层layer往低层layer)和后向(从低层layer往高层layer)的转移,通常情况下多见的是简单的一次转移(壳往真实OEP转移)。而对于有N层layer的程序,大家一般认为运行时只会线性地发生N-1次转移,这是不对的,壳往往会在多层之间跳转来跳转去

D 壳的隔离

一些简单的壳代码和原始代码之间“井水不犯河水”,程序运行时首先进入壳的routine,然后通过一条tail transition instruction进入到原始代码中来。这种简单的模式现在被很多复杂的壳所抛弃(在unpack.cn上,大家把现有的方式叫做“壳中有肉,肉中有壳”)

E 代码的Frame

通常情况下,我们观察到的壳的行为是写一段内存,然后执行该段内存。作者把这种写入然后执行的内存块称为一个Frame;在作者的分类学中,如果一个layer只被完整写入一次,然后后续执行,这种layer可被称为single-frame layer,然而现实中很多layer往往被多个不同的routine写入然后才能完整执行

F 代码可见性

对于分析者而言,dump 内存获取原始代码是一种常见的分析手法,因此越来越多的壳会留意如何防止内存dump攻击,简单的壳往往存在某个时刻所有的原始代码都存在于内存之中,而更为复杂老练的壳则会逐渐地将要执行到的代码释放出来,因此只有在特定的时刻dump才能获取最多的代码,更有甚者会在运行后将释放出来执行过的原始代码再次加壳写回,杜绝了任何时刻出现全部完整代码的可能。

G 代码转移技巧

通过异常触发控制流转移,通过hooking进行控制权转移以及通过代码插桩内联的方式来进行转移,这是常见的一些高级控制转移技术。

H 壳的粒度

壳会按照不同的粒度释放原始代码,包括:以内存页为单位,以一个函数为单位,以基本块为单位,以指令为单位

3 壳的复杂度分类

Type I

UPX为代表,只有一个简单的unpacking routine,一开始就会执行,然后释放全部原始代码

Type II

壳包含多层,每一层顺序释放下一层代码,直至最后一层去释放原始代码

Type III

类似Type II,只是不按层的顺序来依次释放,执行过程包含更为复杂的回路,例如先执行层2,然后执行层4,层4释放出层5的部分代码,然后回到层2,再进行一些层5的修复和反调试,最后进入层5

Type IV

壳可能是单层也可能是多层,但壳的代码有一些会和原始的代码混在一起,这些混在原始代码中的壳代码负责进行一些完整性校验和反调试工作。然而,这种壳依然会把原有的代码全部释放到内存中去

Type V

壳的代码和原始代码交织在一起,原始代码被分为多个frames,每一帧被执行时壳才会去释放它,因此只有整个程序被完整执行过,该程序的snapshot才是比较完整的(动态执行很难覆盖全部代码,因此脱壳很难做到完备)

Type VI

最复杂的壳:每次只释放出原始程序的一个片段进行执行(vm based protection)!

4 分析技术

A Execution Tracing

基于 TEMU 的 framework,适用于监控多进程类型的 packer,同时方便监控各类 API 调用(特别是 memory un-mapping 和 memory deallocation 相关)

B 系统事件收集

关注堆、栈、内存 module 等的情况,关注 IAT——特别是被抹去 IAT 的情况,通过对 trace 的分析来跟踪

C 后期处理分析

为内存页面做标记,根据前述的状态转移和页面被写后执行等情况来判定某一内存页面的属性。

D 图形化

虽然是Coarse-grained 图形化,举个例子:下面是一个非常酷炫的为Obsidium 1.2, a Type-IV packer生成的图

5 案例分析

UPolyX 0.4: A Type-III packer

基于 UPX 改造的壳,原始代码变成了两层,在运行时会在两层之间切换。 然而存在明显的 tail jump;unpacking routine 没有修改原始代码的逻辑;寻找 OEP 十分容易; 因此作者认为这是一个 Type III 的 packer

ACProtect 1.09: A Type-IV packer

作者发现这个 packer 有 216 层(??!!),但是原始代码在第二层就释放了。 使用了 IAT hooking;

作者认为这是个标准的 Type-IV packer,protected code 和 packer code 交织执行。其特点包括:原始代码不在最后一层释放,程序最后执行的代码也不是原始代码;入口点混淆技术

Armadillo 8.0: A Type-VI packer

号称是最强类型的 packer,双进程保护,父进程调试子进程,子进程产生一个异常,由父进 程捕获,父进程为子进程写入代码。Armadillo 每次只写入一部分可执行代码。

6 LONGITUDINAL STUDY OF THE COMPLEXITY OF RUN-TIME PACKERS

论文最后放了很多图,讨论了整个加壳界的一些统计情况~大家可以去看看论文