ROP与ROP的实践

 

Return-oriented programming(ROP)是一种很常见的攻击技术,是一种具有图灵完备性的超级攻击方式。

何为ROP

rop面向返回的编程。上文我们讲到了ret2plt,rop就是ret2plt在64位的升级版本。rop也可以用来做32位的题,是一种相当上位的攻击技术。可以替代我们之前讲的几乎所有溢出利用。可以绕过aslr与dep(NX)。

我所理解的的ROP就是有去有回,需要多次劫持程序流的攻击。

广义的ROP包括了上文所讲到的任何返回式的攻击方式,不过我们今天要讲的是狭义的ROP


背景

传参方式的改变

在64位程序中,calling conventions规定参数依靠寄存器传递,前面6个参数依次以rdi, rsi, rdx, rcx, r8和r9寄存来传递,后面的参数则用栈来传递。这样子我们之前的那些依靠栈来传递参数的方法似乎就难以使用。

天然零化

64位相较32位对于32位可以掌控更多(40亿倍,大约16EB)的内存。而我们显然用不到这么多内存(笔者连加一条8g内存都心痛),在linux中规定只使用后48位的内存,而前面的那些就全部置零,这些零字节会截断字符串,形成了天然的防护。


原理

linux x64采用了寄存器传参,因此难以使用我们平时使用的栈传参。

参数 寄存器
1 RDI
2 RSI
3 RDX
4 RCX
5 R8
6 R9

由于我们不能直接控制寄存器。

我们上文讲到的ret2plt利用所谓的PPR结构来在libc或者其他的可执行块里找到一些片段来调用。

现在我们也用这种方法来进行攻击。将我们需要执行的指令连成ROP链来进行ROP攻击。


攻击步骤

如何构建ROP链

因为我们的攻击的最终目的一定是获得shell,所以我们最终需要让程序执行system("\bin\sh")这条指令。那就需要覆盖程序返回地址到我们想要执行的地方。

buffer|canary|saved fame pointer|saved returned address

那么我们需要在栈里面写入”/bin/sh\0”。因为栈的内容是我们可以控制的,所以可以写入。那么我们需要找到例如pop rdi这样的语句。我们可以使用诸如ROPgadget这样的工具,具体用法请看官方文档。

但是有一点需要注意,我们找到的语句必须包含一个返回ret(retq),不然会顺着语句一路执行下去。

当然,我们有些时候会碰到程序过于小,导致根本没有rop片段可以利用,例如上次笔者碰见的一个500b的题,那我们需要利用libc的gadget,但是我们一般情况下不知道题目的libc,所以需要不断leak出服务器端的libc。

如果对于libc的地址,aslr等不是很清楚的话,建议再读一读elf的内容。

ROP的功能

rop看似复杂,限制很多。但是总可以从程序中找到一枝半叶,而组成极其强大的功能。rop真的可以为所欲为。

甚至,高手的rop可以实现循环、分支、条件等等逻辑。有人指出rop具有图灵完备性。


局限

  • 技巧性有点强。
  • 需要其他的栈溢出或者注入来做铺垫。