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具有图灵完备性。
局限
- 技巧性有点强。
- 需要其他的栈溢出或者注入来做铺垫。