题目
要想实现ret2libc,有以下几个必要条件:
- 存在溢出,且溢出范围足够大,可以覆盖到main函数的返回地址,还可以覆盖更远的区域。
- 存在类似于puts,write这样的打印函数。可以被利用,劫持程序的执行流程后,执行puts,write这样的函数打印一些已经执行过的函数的真实地址,以便我们寻找libc的基地址。
另外这类题目往往还有以下的特点,暗示我们要可能要使用ret2libc的方法: - 开启了NX保护,即数据段不可执行。同时栈也是不可执行的。因此就别想通过写入shellcode再ret2shellcode这样的方法拿shell。
- 程序本身也没有像system(“/bin/sh”)这样直接的后门函数,因此我们也不要想着直接ret2text这么直接。
- 程序中可能既没有system函数,又没有”/bin/sh”字符串,需要我们在libc库中寻找。
解题思路
我们的目标是拿到shell,换言之就是,劫持二进制可执行文件的执行流程,让程序执行system(“/bin/sh”)。拆分这个目标,可以分为以下两个步骤:
- 找到system()函数和/bin/sh字符串在libc中的地址。
- 劫持程序的执行流程,让程序执行system(“/bin/sh”)。
实现第二步不难,只要精巧合理地构造溢出,把main函数的返回地址覆盖为system()函数的地址,并合理实现传参即可。关键在于如何找到system()函数和”/bin/sh”字符串的地址。这两个关键地址都在libc库中,这就是这类题型被叫做ret2libc的原因。那么如何寻找libc中的system()函数和”/bin/sh”字符串呢?这里需要用到以下公式:函数的真实地址 = 基地址 + 偏移地址
要牢牢记住我们的目标:找到system()函数和”/bin/sh”字符串的真实地址。下面我们对这个公式做一个解释:
偏移地址:libc是Linux新系统下的C函数库,其中就会有system()函数、”/bin/sh”字符串,而libc库中存放的就是这些函数的偏移地址。换句话说,只要确定了libc库的版本,就可以确定其中system()函数、”/bin/sh”字符串的偏移地址。解题核心在于如何确定libc版本,本文介绍过程将忽略这个问题,打本地直接确定为本地的libc版本即可。
基地址:每次运行程序加载函数时,函数的基地址都会发生改变。这是一种地址随机化的保护机制,导致函数的真实地址每次运行都是不一样的。然而,哪怕每次运行时函数的真实地址一直在变,最后三位确始终相同。可以根据这最后三位是什么确定这个函数的偏移地址,从而反向推断出libc的版本(此处需要用到工具LibcSearcher库,本文忽略这个步骤)。那么如何求基地址呢?如果我们可以知道一个函数的真实地址,用公式:
这次运行程序的基地址 = 这次运行得到的某个函数func的真实地址 - 函数func的偏移地址
即可求出这次运行的基地址。
这回问题又发生了转化:如何找到某个函数func的真实地址呢?
像puts(),write()这样的函数可以打印内容,我们可以直接利用这些打印函数,打印出某个函数的真实地址(即got表中存放的地址)。某个函数又指哪个函数呢?由于Linux的延迟绑定机制,我们必须选择一个main函数中已经执行过的函数(这样才能保证该函数在got表的地址可以被找到),选哪个都可以,当然也可以直接选puts和write,毕竟题目中像puts和write往往会直接出现在main函数中。
总结一下上面这段话,我们可以通过构造payload让程序执行puts(puts@got)或者write(1,write@got, 读取的字节数)打印puts函数/write函数的真实地址。
整体思路总结(关键):
首先寻找一个函数的真实地址,以puts为例。构造合理的payload1,劫持程序的执行流程,使得程序执行puts(puts@got)打印得到puts函数的真实地址,并重新回到main函数开始的位置。
找到puts函数的真实地址后,根据其最后三位,可以判断出libc库的版本(本文忽略)。
根据libc库的版本可以很容易的确定puts函数的偏移地址。
计算基地址。基地址 = puts函数的真实地址 - puts函数的偏移地址。
根据libc函数的版本,很容易确定system函数和”/bin/sh”字符串在libc库中的偏移地址。
根据 真实地址 = 基地址 + 偏移地址 计算出system函数和”/bin/sh”字符串的真实地址。
再次构造合理的payload2,劫持程序的执行流程,劫持到system(“/bin/sh”)的真实地址,从而拿到shell。
以上取自pwn入门:基本栈溢出之ret2libc详解(以32位+64位程序为例)-CSDN博客
64位脚本
1 | from pwn import * |
32位脚本
1 | from pwn import * |
关于本文
由 GuQing 撰写,采用 CC BY-NC 4.0 许可协议。