概述:
前文介绍了ROP的基本原理,但前面的方法有一些局限性,一旦目标程序调用的函数较少,或者使用动态编译,就会导致我们可以利用的gadget变少,从而无法达到利用效果。为了解决这种问题,我们可以选择使用ROP的方式,到动态链接库里面寻找gadget。即ret2libc。
静态链接和动态链接:
链接:程序经过预处理,编译,汇编,链接之后可以生成可执行文件,链接可以将多个汇编之后的程序拼在一起。也可以链接函数库,库是一种软件组件技术,库里面封装了数据和函数,比如常用的printf,get函数。链接包含函数库,可以方便代码的复用,避免重复造轮子。
静态链接:
前面的所讲的ret2syscall所利用的二进制程序就是经过静态链接得到的。静态链接就是将整个库直接链接到程序中,一般这样的程序占用空间会比较大,并且会有很多不会用到的函数。
动态链接:
随着系统中可执行文件的增加,静态链接所带来的磁盘和内存空间浪费问题愈发严重。例如大部分可执行文件都需要glibc,那么在静态链接的时候就需要把libc.a和编写的代码链接进去,单个libc.a的大小为5M左右,那么1000个就是5G。如果两个静态链接的可执行文件都包含testLib.o,那么在装载入内存时,两个相同的库也会被装载进去,造成内存空间的浪费。静态链接另一个明显的缺点就是,如果对标准函数哪怕做了一点很微小的改动,都需要重新编译整个源文件,使得开发和维护很艰难。
如果不把系统库和自己编写的代码链接到一个可执行文件,而是分割成两个独立的模块,等到程序真正运行时,再把这两个模块进行链接,就可以节省硬盘空间,并且内存中的一个系统库可以被多个程序共同使用,还节省了物理内存空间。这种在运行或加载时,在内存中完成链接的过程叫做动态链接,这些用于动态链接的系统库称为共享库,或者共享对象,整个过程由动态链接器完成。
Linux下动态库文件的文件名形如 libxxx.so,其中so是 Shared Object 的缩写,即可以共享的目标文件。
PLT&GOT表以及延迟绑定机制:
参考资料:《程序员的自我修养-链接、装载与库》
利用方式:
下面我们将使用三个例题从易到难来讲解ret2libc的利用方式:
难度一:
例题初探:
程序中存在system函数和/bin/sh字符串,但与ret2text不同的是,此时的system函数的参数并不是/bin/sh,而是一个奇怪字符:
并且/bin/sh所在的位置为:
如果我们单纯的将返回地址覆盖为system的地址,程序就会执行system(“shell!?”),但是shell!?并不是一个系统命令,此时程序执行会产生错误,就相当于我们直接在命令行敲shell!?,系统会提示找不到命令,但如果敲/bin/sh就会返回一个真正的shell。如果我们要想利用system函数并且让程序返回一个shell,那么我们就必须要让system函数的参数变为/bin/sh。
那么如何让system的参数变成/bin/sh?
首先回顾一下汇编调用过程:汇编调用函数过程中会首先将参数压栈,然后返回地址压栈,然后是ebp的地址。
当程序调用system函数时,会自动去寻找栈底即ebp指向的位置,然后将ebp+8字节的位置的数据当作函数的参数,所以如果我们想将/bin/sh作为system函数的参数,就可以在栈溢出的时候,先修改eip为system函数的地址,然后填充4个字节的垃圾数据,再将/bin/sh的地址写入栈上,这样调用system函数的时候,就可以将/bin/sh作为参数,然后返回一个shell。
注意: 为什么是在eip(即system函数地址)后面覆盖4个字节垃圾数据而不是前面提到的8个字节,这是因为当我们调用system函数的时候,在system函数中会首先执行push ebp指令,将4字节的ebp地址压入栈中,而此时的栈底距离我们的参数/bin/sh正好8字节,所以我们应该填充4字节垃圾数据。
完整利用过程:
首先checksec:
32位程序,无canary,无pie。
32位ida pro打开:
明显的栈溢出漏洞。
存在system函数,地址为:
/bin/sh地址为:
然后动态调试确定栈溢出大小:
脚本如下:
from pwn import *
sh = process('./ret2libc1')
binsh_addr = 0x8048720
system_plt = 0x08048460
payload