做者:蒸米@阿里聚安全html
ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术能够用来绕过现代操做系统的各类通用防护(好比内存不可执行和代码签名等)。虽然如今你们都在用64位的操做系统,可是想要扎实的学好ROP仍是得从基础的x86系统开始,但看官请不要着急,在随后的教程中咱们还会带来linux_x64以及android (arm)方面的ROP利用方法,欢迎你们继续学习。linux
小编备注:文中涉及代码可在文章最后的github连接找到。android
比较常见的程序流劫持就是栈溢出,格式化字符串攻击和堆溢出了。经过程序流劫持,攻击者能够控制PC指针从而执行目标代码。为了应对这种攻击,系统防护者也提出了各类防护方法,最多见的方法有DEP(堆栈不可执行),ASLR(内存地址随机化),Stack Protector(栈保护)等。可是若是上来就部署所有的防护,初学者可能会以为无从下手,因此咱们先从最简单的没有任何保护的程序开始,随后再一步步增长各类防护措施,接着再学习绕过的方法,按部就班。git
首先来看这个有明显缓冲区溢出的程序:程序员
这里咱们用github
#bash gcc -fno-stack-protector -z execstack -o level1 level1.c
这个命令编译程序。-fno-stack-protector和-z execstack这两个参数会分别关掉DEP和Stack Protector。同时咱们在shell中执行:shell
这几个指令。执行完后咱们就关掉整个linux系统的ASLR保护。编程
接下来咱们开始对目标程序进行分析。首先咱们先来肯定溢出点的位置,这里我推荐使用pattern.py这个脚原本进行计算。咱们使用以下命令:安全
来生成一串测试用的150个字节的字符串:bash
随后咱们使用 gdb ./level1
调试程序
咱们能够获得内存出错的地址为0x37654136。随后咱们使用命令:
就能够很是容易的计算出PC返回值的覆盖点为140个字节。咱们只要构造一个”A”*140+ret
字符串,就可让pc执行ret地址上的代码了。
接下来咱们须要一段shellcode,能够用msf生成,或者本身反编译一下。
这里咱们使用一段最简单的执行execve ("/bin/sh")
命令的语句做为shellcode。溢出点有了,shellcode有了,下一步就是控制PC跳转到shellcode的地址上:
[shellcode][“AAAAAAAAAAAAAA”….][ret] ^------------------------------------------------|
对初学者来讲这个shellcode地址的位置实际上是一个坑。由于正常的思惟是使用gdb调试目标程序,而后查看内存来肯定shellcode的位置。但当你真的执行exp的时候你会发现shellcode压根就不在这个地址上!这是为何呢?缘由是gdb的调试环境会影响buf在内存中的位置,虽然咱们关闭了ASLR,但这只能保证buf的地址在gdb的调试环境中不变,但当咱们直接执行./level1的时候,buf的位置会固定在别的地址上。怎么解决这个问题呢?
最简单的方法就是开启core dump这个功能。
开启以后,当出现内存错误的时候,系统会生成一个core dump文件在tmp目录下。而后咱们再用gdb查看这个core文件就能够获取到buf真正的地址了。
由于溢出点是140个字节,再加上4个字节的ret地址,咱们能够计算出buffer的地址为$esp-144
。经过gdb的命令 “x/10s $esp-144”
,咱们能够获得buf的地址为0xbffff290。
OK,如今溢出点,shellcode和返回值地址都有了,能够开始写exp了。写exp的话,我强烈推荐pwntools这个工具,由于它能够很是方便的作到本地调试和远程攻击的转换。本地测试成功后只须要简单的修改一条语句就能够立刻进行远程攻击。
最终本地测试代码以下:
执行exp:
接下来咱们把这个目标程序做为一个服务绑定到服务器的某个端口上,这里咱们可使用socat这个工具来完成,命令以下:
随后这个程序的IO就被重定向到10001这个端口上了,而且可使用 nc 127.0.0.1 10001来访问咱们的目标程序服务了。
由于如今目标程序是跑在socat的环境中,exp脚本除了要把p = process('./level1')
换成p = remote('127.0.0.1',10001)
以外,ret的地址还会发生改变。解决方法仍是采用生成core dump的方案,而后用gdb调试core文件获取返回地址。而后咱们就可使用exp进行远程溢出啦!
如今咱们把DEP打开,依然关闭stack protector和ASLR。编译方法以下:
这时候咱们若是使用level1的exp来进行测试的话,系统会拒绝执行咱们的shellcode。若是你经过sudo cat /proc/[pid]/maps
查看,你会发现level1的stack是rwx的,可是level2的stack倒是rw的。
level1: bffdf000-c0000000 rw-p 00000000 00:00 0 [stack] level2: bffdf000-c0000000 rwxp 00000000 00:00 0 [stack]
那么如何执行shellcode呢?咱们知道level2调用了libc.so,而且libc.so里保存了大量可利用的函数,咱们若是可让程序执行system(“/bin/sh”)
的话,也能够获取到shell。既然思路有了,那么接下来的问题就是如何获得system()这个函数的地址以及”/bin/sh”这个字符串的地址。
若是关掉了ASLR的话,system()函数在内存中的地址是不会变化的,而且libc.so中也包含”/bin/sh”这个字符串,而且这个字符串的地址也是固定的。那么接下来咱们就来找一下这个函数的地址。这时候咱们可使用gdb进行调试。而后经过print和find命令来查找system和”/bin/sh”字符串的地址。
咱们首先在main函数上下一个断点,而后执行程序,这样的话程序会加载libc.so
到内存中,而后咱们就能够经过print system
这个命令来获取system函数在内存中的位置,随后咱们能够经过print __libc_start_main
这个命令来获取libc.so
在内存中的起始位置,接下来咱们能够经过find命令来查找/bin/sh
这个字符串。这样咱们就获得了system的地址0xb7e5f460
以及/bin/sh
的地址0xb7f81ff8
。下面咱们开始写exp:
要注意的是system()后面跟的是执行完system函数后要返回地址,接下来才是”/bin/sh”
字符串的地址。由于咱们执行完后也不打算干别的什么事,因此咱们就随便写了一个0xdeadbeef做为返回地址。下面咱们测试一下exp:
OK。测试成功。
接下来咱们打开ASLR保护。
如今咱们再回头测试一下level2的exp,发现已经很差用了。
若是你经过sudo cat /proc/[pid]/maps
或者ldd查看,你会发现level2的libc.so地址每次都是变化的。
那么如何解决地址随机化的问题呢?思路是:咱们须要先泄漏出libc.so某些函数在内存中的地址,而后再利用泄漏出的函数地址根据偏移量计算出system()函数和/bin/sh字符串在内存中的地址,而后再执行咱们的ret2libc的shellcode。既然栈,libc,heap的地址都是随机的。咱们怎么才能泄露出libc.so的地址呢?方法仍是有的,由于程序自己在内存中的地址并非随机的,如图所示:
Linux内存随机化分布图
因此咱们只要把返回值设置到程序自己就可执行咱们指望的指令了。首先咱们利用objdump来查看能够利用的plt函数和函数对应的got表:
咱们发现除了程序自己的实现的函数以外,咱们还可使用read@plt()
和write@plt()
函数。但由于程序自己并无调用system()
函数,因此咱们并不能直接调用system()
来获取shell。但其实咱们有write@plt()
函数就够了,由于咱们能够经过write@plt ()
函数把write()
函数在内存中的地址也就是write.got
给打印出来。
既然write()
函数实现是在libc.so
当中,那咱们调用的write@plt()
函数为何也能实现write()
功能呢? 这是由于linux采用了延时绑定技术,当咱们调用write@plit()
的时候,系统会将真正的write()
函数地址link到got表的write.got
中,而后write@plit()
会根据write.got
跳转到真正的write()
函数上去。(若是仍是搞不清楚的话,推荐阅读《程序员的自我修养 - 连接、装载与库》这本书)
由于system()
函数和write()
在libc.so
中的offset(相对地址)
是不变的,因此若是咱们获得了write()的地址而且拥有目标服务器上的libc.so
就能够计算出system()
在内存中的地址了。
而后咱们再将pc指针return回vulnerable_function()
函数,就能够进行ret2libc溢出攻击,而且这一次咱们知道了system()
在内存中的地址,就能够调用system()
函数来获取咱们的shell了。
使用ldd命令能够查看目标程序调用的so库。随后咱们把libc.so
拷贝到当前目录,由于咱们的exp须要这个so文件来计算相对地址:
最后exp以下:
接着咱们使用socat把level2绑定到10003端口:
最后执行咱们的exp:
本章简单介绍了ROP攻击的基本原理,因为篇幅缘由,咱们会在随后的文章中会介绍更多的攻击技巧:如何利用工具寻找gadgets,如何在不知道对方libc.so版本的状况下计算offset;如何绕过Stack Protector等。欢迎你们到时继续学习。另外本文提到的全部源代码和工具均可以从个人github下载:https://github.com/zhengmin1989/ROP_STEP_BY_STEP
The geometry of innocent flesh on the bone: return-into-libc without function calls (on the x86)
picoCTF 2013: https://github.com/picoCTF/2013-Problems
Smashing The Stack For Fun And Profit: http://phrack.org/issues/49/14.html
程序员的自我修养
ROP轻松谈
做者:蒸米@阿里聚安全,更多技术文章,请访问阿里聚安全博客