@SmashStack
2017-03-21T15:12:35.000000Z
字数 3719
阅读 1197
Binary
这篇论文发表在 RAID 2011 中,介绍了通过两层解码的方式对传统 ROP
流量进行打包, 生成简短多变
并且完全由可打印字符串
构成的新型攻击流量。
作者
- Kangjie Lu @ Singapore Management University
- Dabi Zou @ Singapore Management University
- Weiping Wen @ Peking University
- Debin Gao @ Singapore Management University
ROP (Return-Oriented Programming) 是一种通过 Stack Overflow 对函数返回地址进行劫持的攻击方式。攻击者控制堆栈调用以劫持程序控制流并执行针对性的机器语言指令序列(称为Gadgets),一般而言,每一段 Gadget 以 Ret 指令结尾,但是在某些特殊情况下,也可以以 Jump 或者 Call 指令结尾。 在过去的研究中已经证明了 ROP 足够让攻击者执行任意代码。 同时因为 ROP 不需要代码注入,也使的现在很多安全防御措施(如 NX 可写段不可执行)失去的效用。
但是,ROP 的攻击效用是否真的如传闻中说的那么神通广大呢?事实上,在现实生活中,我们的攻击流量面领着这样三个甚至更多的问题:静态分析、流量过滤、特征分析。在接下来的部分中,作者着重讨论了:
并由此引出了他们所提出的 两层编码
方式。
介于很多实用的 Gadget 的地址都不全是可打印字符 [0x20, 0x7e],作者首先提出了一种单层编码方式。这种编码方式通过加法实现任意地址的 Gadget,逻辑简单清晰,美中不足在于其会导致攻击流量冗长,使得适用范围大大受限。
这种编码方式的核心思想在于通过加法运算在 fake stack 上进行真正的 ROP 栈布局,随后将 esp 指向 fake stack。具体流程图如下:
接下来,我们通过例子来了解这个编码方式。下图是 subroutine 的具体实现。
首先在 Gadget 1
中,程序将 fake stack 的起始地址 addr 放入 edx,随后 Gadget 2
和 Gadget 3
分别将 0x2d30466c、0x50294030 放入到 ecx 和 eax 中。 Gadget 4
实现了两个寄存器的加法并存入 eax。在 Gadget 5
中, eax 与 edx 的值进行了交换,而随后的 Gadget 6
则将加法得出的不可打印的 Gadget Address (0x7d59869c) 放入 eax+0x1c 的内存上。 Gadget 7
实现了 eax 加 4,而 Gadget 8
就是 Gadget 5
,同样实现了两个寄存器的交换。
如此反复,将 fake stack 上填满了我们需要的不可打印的 Gadget Address 以后, 通过 xchg esp, eax
实现 Stack Pivot。最后进行正常的 ROP。
然而,这样的方法存在着两个致命的缺点:
于是就有了接下来的考虑。
刚才我们已经提到了,单层编码导致 Payload 冗长,如果我们能够将数据与执行的 Gadget 分离,真正实现以上流程图的循环方式,那么我们生成的 Payload 的长度将大大减小。
可不幸的是,能够实现 ROP 循环的 Conditional Jump 的 Gadget 很稀少特殊,更难被一个 Printable Address 表示。为此,作者们设计了一种 两层编码方式 (Two-layer packer
)。
如上图所示,两层编码需要经过两次解码获得原始的 ROP Payload。在第一层编码中, dec1 通过类似于单层编码的方式对 enc1 (dec2) 进行编码,使其成为能够构成 Conditional Jump 的循环 ROP;而在第二层编码中, dec2 通过循环的方式将 enc2 编码,获得原始的 ROP Payload。 因为dec2 是通过编码生成的,所以它不需要是 Printable 的,因而解决了 Conditional Jump 的 Gadget 无法被使用的问题。
dec1 分为 Initializing 和 Decoding enc1 (dec2) 两个部分
Initializing
初始化的目的是为了将 enc1 (dec2) 的起始地址放入一个可写的数据段,这样做可以简化获取 enc1 (dec2) 数据的过程(如上图)。
Decoding
Decoding 的流程是将 3 个可打印数值弹出到寄存器中,并通过 (op1 - op2) xor op3
计算出 enc1 (dec2) 所需要的 Gadget Address。如上图所示, Decoding 分为三个步骤:
类似于 dec1, dec2 也分为 Initializing 和 Decoding enc1 (dec2) 两个部分。但是值得一提的是,介于 dec2 使用了循环的流程,它要更加复杂一些
Initializing
在这里,我们需要初始化 3 个不同的 addr:
Decoding
在这里 Arithmetic Operations 只使用了两个值 ((op1 << 1) + 1) xor op2
去计算新的地址。
另一方面,我们同样需要一个标志符表征 Decoding 的终结,用于结束 Decoding Loop。在文中,作者选择了 0x7e7e7e7e
放置于 enc2 的结尾作为终结符。下图详细展示了通过 Gadget 实现 Conditional Jump 的方式。
在上图 (b) 中详细阐述了 Conditional Jump 的流程:
neg eax
: eax = 0 && CF = 0sbb eax, eax
: eax = 0 && CF = 0inc eax
: eax = 1 && CF = 0neg eax
: eax = 0xfffffff && CF = 1neg eax
: eax != 0 && CF = 1sbb eax, eax
: eax = 0xffffffff && CF = 1inc eax
: eax = 0 && CF = 1neg eax
: eax = 0 && CF = 0
pop ecx (pop offset => ecx)
and eax, ecx
add eax, [addr3]
xchg esp, eax
通过以上的流程,便可以通过 ROP 实现 Loop 编码。
作者对 Windows 7 & Windows XP 上对 Winamp v5.572 和 RM Downloader v3.1.3 的 ROP 攻击流量进行了 Pack,得到了如下的试验结果
可以看见,这个编码流程还是很具有可行的。
作者为我们展现了通过对 ROP 攻击流量进行打包,从而绕过多种限制和查杀手段。从整体上来看,作者提出的两层解码可以有效地绕过可见字符串的过滤,但是即使这样,使用条件也受到了 ASLR 的限制(用于 dec1 的固定 Gadget 会不断变化,导致部分 Gadget Address 不可打印)。另一方面,特征明显的问题却没有被多样性所解决,因为 dec1 部分的 Gadget Address 是不会变化的,这样敏感的头部信息可以作为有效的特征码。
但不得不说,对流量混淆方面该编码方式有着不错的成效。从另外一个方面提出了对 ROP 流量特征查杀也有着先进之处。
PRO
:
CON
: