ret2text
栈溢出
说栈溢出之前先说一下几个重要的寄存器(32位下的)
- ebp 指向栈底的指针
- esp 指向栈顶的指针
- eip 用来存储CPU要读取指令的地址,CPU通过EIP寄存器读取即将要执行的指令(指令指针寄存器)下面简称IP
这题是最简单的栈溢出。
那既然说到栈溢出那么就要先了解栈溢出是什么?怎么溢出的?也就是栈溢出的原理。
说到栈溢出那么就要先找到C语言的函数调用栈。
- 函数调用栈是指程序运行时内存一段连续的区域用来保存函数运行时的状态信息,包括函数参数与局部变量等称之为“栈”是因为发生函数调用时,调用函数(caller)的状态被保存在栈内,被调用函数(callee)的状态被压入调用栈的栈顶
- 在函数调用结束时,栈顶的函数(callee)状态被弹出,栈顶恢复到调用函数(caller)的状态
- 函数调用栈在内存中从高地址向低地址生长,所以栈顶对应的内存地址在压栈时变小,退栈时变大
这是什么意思呢?看起来很复杂,其实很简单。就是父函数调用一个子函数,函数调用栈会先保存父函数的状态,然后将子函数压入到栈中。
当然这其中也包含着很多的细节,我们具体来分析一下。
首先就是我们栈帧的结构
- previous stack frame pointer
- 这个表示的是之前的栈帧
- arguments
-
函数状态主要涉及三个寄存器 —— esp,ebp,eip。esp 用来存储函数调用栈的栈顶地址,在压栈和退栈时发生变化。ebp 用来存储当前函数状态的基地址,在函数运行时不变,可以用来索引确定函数参数或局部变量的位置。eip 用来存储即将执行的程序指令的地址,cpu 依照 eip 的存储内容读取指令并执行,eip 随之指向相邻的下一条指令,如此反复,程序就得以连续执行指令。
-
下面让我们来看看发生函数调用时,栈顶函数状态以及上述寄存器的变化。变化的核心任务是将调用函数(caller)的状态保存起来,同时创建被调用函数(callee)的状态。
-
首先将被调用函数(callee)的参数按照逆序依次压入栈内。如果被调用函数(callee)不需要参数,则没有这一步骤。这些参数仍会保存在调用函数(caller)的函数状态内,之后压入栈内的数据都会作为被调用函数(callee)的函数状态来保存。
-
在参数的存放位置上x86和amd64有区别。
- x86
- 使用栈来传递参数
- 使用 eax 存放返回值
- amd64
- 前6个参数依次存放于 rdi、rsi、rdx、rcx、r8、r9 寄存器中
- 第7个以后的参数存放于栈中
- x86
-
- return address
- 这就是栈溢出最重要的一个地方,返回地址。因为在底层调用一个函数是使用call指令来调用的,call指令会保存当前IP中的值到栈中,也就是return address;之后由指令代码给出的目的地址段内偏移量送入IP,从而实现过程调用(简单理解就是跳转到子函数的地址执行)。
4.stack frame pointer
- 再将当前的ebp 寄存器的值(也就是调用函数的基地址)压入栈内,并将 ebp 寄存器的值更新为当前栈顶的地址。这样调用函数(caller)的 ebp(基地址)信息得以保存。同时,ebp 被更新为被调用函数(callee)的基地址。这样就会将栈顶和栈底指向被调用函数了。
5.callee saved registers
- 被调用者保存寄存器也叫非易失性寄存器,在程序调用过程中,这些寄存器中的值需要被保存,不能被覆盖;当某个程序调用这些寄存器,被调用寄存器会先保存这些值然后再进行调用,且在调用结束后恢复被调用之前的值;
6.local variables
- 再之后是将被调用函数(callee)的局部变量等数据压入栈内。
介绍完背景知识,就可以继续回归栈溢出攻击的主题了。当函数正在执行内部指令的过程中我们无法拿到程序的控制权,只有在发生函数调用或者结束函数调用时,程序的控制权会在函数状态之间发生跳转,这时才可以通过修改函数状态来实现攻击。而控制程序执行指令最关键的寄存器就是 eip,所以我们的目标就是让 eip 载入攻击指令的地址。
先来看看函数调用结束时,如果要让 eip 指向攻击指令,需要哪些准备?首先,在退栈过程中,返回地址会被传给 eip,所以我们只需要让溢出数据用攻击指令的地址来覆盖返回地址就可以了。其次,我们可以在溢出数据内包含一段攻击指令,也可以在内存其他位置寻找可用的攻击指令。
从上图可以看到,我们输入了超长的字符导致溢出覆盖了原有的栈帧结构,这就是我们可以利用的点。
先将程序放入IDA中静态分析一下;反汇编后可以看到程序有个函数叫vulnerable(),这就是漏洞利用的地方;因为其他的函数都没有可以让我们输入的地方。
进入后可以发现这里面有着一个gets函数,向一个8字节的buffer读入数据;gets函数是一个危险的函数,一般就是利用的点,因为gets函数不会限制输入的长度,我们可以无限制的输入内容。
在程序的函数中我们可以发现一个get_shell函数,点进去会发现里面有一句system("/bin/sh")
;那么我们是不是可以控制eip里面的值指向这句话的地址呢?
我们可以在vulnerable()中的gets函数写入payload来进行控制eip,那么在上面的函数调用栈中可以控制eip的就只有return address可以;那么我们只需要溢出到return address然后修改其中的值就可以控制程序执行流了。
那么需要溢出多少呢?其实IDA已经给我们了;我们可以回头看在vulnerable()函数中IDA写了个注释[ebp - 10h]
,因为是16进制的,转化过来就是16;那我们所输入的内容和ebp有着16个字节的距离,再加上ebp本身的4个字节,那么我们只需要溢出20个字节就到了return address的位置。然后只需要再找到get_shell()的地址就可以构造出payload了。
直接在IDA中点击get_shell函数就可以找到get_shell()的地址。
具体payload构造:
import pwn
io = pwn.process('./ret2text') #打开本地程序
payload = b'A' * 16 + b'B' * 4 + pwn.p32(0x8048522)
io.recvline() #接受一行输出
io.sendline(payload) #发送一行输入
io.interactive() #交互模式
成功getshell。
标签:ret2text,eip,函数,函数调用,地址,调用函数,寄存器 From: https://www.cnblogs.com/qianyuzz/p/17397541.html