栈基础

栈的结构

关于栈的定义,在指南中有这么部分的描述:栈空间是计算机内存中确定了的内存区域,也有着一些指针指向相应的内存地址,在x96架构下这个指针位于ESP寄存器,而在x86-64下位于RSP寄存器,

而在计算机底层,栈的主要的用途是:(1):存储局部变量,(2):执行CALL指令调用函数的时候,保存函数地址以便函数结束时正确返回,(3):传递函数参数

栈的主要有两种操作,push 和 pop

1
2
push 用于压栈,将数据压入栈中(栈顶) // 这个过程中需要对ESP/RSP/SP/进行+4/+8的操作,因为只有这样才能使得压进栈的参数正确入栈
pop 与push相反,用于出栈,将数据从栈顶移除 // 这个过程中需要对EBP/RBP/BP/进行-4/-8的操作,因为只有这样才能使得参数正确出栈

程序的栈是从进程地址空间的高地址向低地址增长的

那么是怎么确定栈的呢?

答:栈是由无数个栈帧组成,

那么怎么确定一个栈帧呢?

答:两种寄存器,esp(rsp) 和 ebp(rbp) ,esp(rsp)代表了栈顶,而 ebp(rbp) 代表了栈底,二者中间的内存就构造起了一个栈帧

那么程序是怎么执行的呢?执行的过程是怎么控制的呢?

答:程序的执行是由一个寄存器eip(rip)控制的,当eip(rip)指向哪条汇编就会执行这条汇编,那么只要我们控制eip也就相当于控制了程序的执行过程,那么这个我们也称呼其为 控制程序执行流

ok,现在是栈的基础部分:寄存器(这里只说通用寄存器),和传参;

寄存器和传参

i386(32位)系统中存在以下的(通用)寄存器

1
2
3
4
5
6
7
8
eax: 通常用来执行加法,函数调用的返回值一般也放在这里面
ebx: 数据存取
ecx: 通常用来作为计数器,比如for循环
edx: 读写I/O端口时,edx用来存放端口号
esp: 栈顶指针,指向栈的顶部
ebp: 栈底指针,指向栈的底部,通常用ebp+偏移量的形式来定位函数存放在栈中的局部变量
esi: 字符串操作时,用于存放数据源的地址
edi: 字符串操作时,用于存放目的地址的,和esi两个经常搭配一起使用,执行字符串的复制等操作

x64(64位)

因为是64位系统,所以为了兼顾使用,就选择i386的版本进行更新拓展,使i386的版本的寄存器变成了x64的低32位,
所以寄存器相关效果和x86没什么区别
但是名字出现了区别:现在使r开头而不是e开头,其余的都不变,
同时新增了8个新的通用寄存器

1
r8~r15

至于传参顺序就一句话,32位使用栈传参 ,按照顺序一个个传入对应的函数,比如:

1
2
3
存在函数read(int fd, void *buf, size_t count)
那么可以看到有3个参数:fd *buf count 可以看到有三个参数,这个时候我们需要传入三个参数,从而使用read这个函数,就需要,把fd *buf count这个三个参数一个个摆好,payload = p32(read) + p32(fd) + p32(*buf) + p32(count)
这样就调用了read函数

至于64位就不一样,这个是先使用寄存器传参,然后再用栈传参

这六个寄存器分别是:

1
RDI, RSI, RDX, RCX, R8, R9

因此,如果想调用read函数

1
payload  = p64(read) + p64(rdi) + p64(fd) + p64(rsi) + p64(*buf) + p64(rdx) + p64(count)

ret2系列

ret2text

我们把函数存在的地方叫做text段,

什么是栈溢出?

栈溢出指的是程序向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数,因而导致与其相邻的栈中的变量的值被改变

栈的结构如下(以32位为例 64位一样的)

1
2
3
4
5
6
7
| esp |
+-----+
| | ---->缓冲区
+-----+
| ebp | ---->存放着上一个函数的ebp(old_ebp)
+-----+
| retn| ----> 存放着函数调用的返回地址

那么我们栈溢出使得我们可以修改ebp 和 retn里面的数据,那么此时我们就可以使控制程序的程序执行流

好,那让我说说为什么可以控制程序执行流(控制eip)

ret 的汇编其实是 pop eip 就是将栈顶的值传递给eip,同时esp+4(将这个值删掉),那么当执行到ret 的时候,esp在retn这里,那么就会把retn里面的值弹给eip,那么eip就会跳到那个内容里面继续执行,那么这个时候我们修改这个值不就可以控制eip跳转到我们想要其执行的地方嘛!

这就是ret2text,跳转回text段上执行恶意代码

SROP

sigreturn是一个系统调用,在类 unix 系统发生 signal 的时候会被间接地调用

signal 机制

signal 是一种类 unix 系统中进程之间相互传递信息的一种方法,也就是所谓的信号软中断,进程之间可以通过系统调用 kill 来发送软中断信号

在发生signal的时候,会进入内核态,会将进程挂起,内核会为该进程保存相应的上下文,主要是将所有寄存器压入栈中,以及压入 signal 信息,以及指向 sigreturn 的系统调用地址

而这个函数的作用有且只有一个,那就是pop ,将栈上的东西依次弹给寄存器,(注:所有寄存器)