这篇鸽了挺久的,补一下吧
简单介绍ROP
首先先来说下什么是ROP
ROP是Return Oriented Programming 的缩写
翻译过来就是面向返回的编程
你可能会问,我们不是利用栈溢出漏洞么,怎么又扯到编程了?
其实ROP就是另外一种意义上的编程,其核心在于利用了指令集中的 ret 指令,改变了指令流的执行顺序。ROP 攻击一般得满足如下条件
程序存在溢出,并且可以控制返回地址。
可以找到满足条件的 gadgets 以及相应 gadgets 的地址。
这里的gadgets是类似下面的代码片段
这里用32位的程序作为示例,所以我们先讲32位的ROP,然后再讲64位的ROP,两者其实相差不大
这些gadgets一般遵循以下的形式
1 | xxx |
例如1
2pop ebp
ret
1 | int 80h |
反正就是一堆指令后面跟着ret
但是比较常见和常用的就是1
2pop xxx
ret
这一类的gadget
但是其实在32位的ROP中是比较少用gadget的
这里又扯一下函数参数的传递方式
32位
我们举一个例子吧,在上一章的示例程序中也可以找到对应的代码
1
read(0,buf,0x100)
这一句代码,对应的汇编是
1
2
3
4
5.text:08048484 push 100h ; nbytes
.text:08048489 lea eax, [ebp+buf]
.text:0804848C push eax ; buf
.text:0804848D push 0 ; fd
.text:0804848F call _read可以看到参数是从右到左入栈,先push 0x100,再push buf,最后push 0
所以例如
1
my_fun(0,1,2,3,4,5,6)
对应的汇编就是
1
2
3
4
5
6
7
8push 6
push 5
push 4
push 3
push 2
push 1
push 0
call my_fun64位
同样是那一行代码
1
read(0,buf,0x100)
对应的64位汇编是
1
2
3
4
5lea rax, [rbp+buf]
mov edx, 100h ; nbytes
mov rsi, rax ; buf
mov edi, 0 ; fd
call _read而
1
my_fun(0,1,2,3,4,5,6)
就变成
1
2
3
4
5
6
7
8push 6
mov r9d, 5
mov r8d, 4
mov ecx, 3
mov edx, 2
mov esi, 1
mov edi, 0
call my_fun可以看到函数的前6个参数都会放到寄存器里面,从左到右对应的是
1
rdi, rsi, rdx, rcx, r8d, r9d
如果还有更多参数的话,就会通过栈来传递
32位程序实战
接下来结合实例来讲一下吧
还是用上一章那个程序
假如现在我们不直接跳到backdoor函数,而是通过ROP来调用
1 | system("/bin/sh") |
布置好的栈如下
对应的payload是
1 | from pwn import * |
接下来讲解一下为什么这样布置栈
我们在0x8048499 处下一个断点
用
1 | x /10xw $esp |
查看栈的情况
1 | 0x08048340 -> system |
你可能想问,这里为什么突然多了retaddr这个东西了呢?
我们来回顾一下正常调用1
system("/bin/sh")
对应的汇编是
1 | push binsh_addr |
关键就在1
call system
这句汇编其实等价于1
2push eip+5
jmp system
因为call指令一波都是5个字节的长度,所以这里保存的是下一条指令的地址
而我们栈溢出控制rip,其实就相等于
1 | jmp system |
少了保存地址,所以我们要填一个返回地址给它
但是调用system(“/bin/sh”)之后就get shell了,返回地址是什么其实没有什么所谓,所以填0xdeadbeef也行
接下来加大一点难度
假设程序里面没有/bin/sh这个字符串,我们要调用read读/bin/sh到bss段
布置好的栈如下
payload如下
1 | from pwn import * |
这里如果要调试的话,最好把1
sleep(1)
换成1
raw_input()
这样比较好调
这里1
pop3ret
作用是改变栈,让栈指针指向system
64位程序实战
64位的其实和32位的大同小异,区别就在于传递参数那里
这里给下示例程序
然后给下payload,因为差不多,所以就不详细解释了
1 | from pwn import * |
但是因为程序没有1
pop rdx
这个gadget,所以在这个程序比较难控制第三个参数,也就比较难调用1
read(0,buf,0x100)
比较难不代表不行,之后会介绍一个万能gadget,能够控制rdx的