baby_栈溢出
根据题目我们可以知悉这是一道简单的栈溢出题,那么我们就需要知道什么是栈溢出,我们又应该如何处理这道题呢?
预备知识
一、栈溢出攻击原理
缓冲区溢出的简单介绍
缓冲区溢出:简单地说,缓冲区溢出就是超长的数据向小缓冲区复制,导致数据超出了小缓冲区,导致缓冲区其他的数据遭到破坏,这就是缓冲区溢出。而栈溢出是缓冲区溢出的一种,也是最常见的。只不过栈溢出发生在栈,堆溢出发生在堆。
栈的简单介绍
栈(stack),它是一种运算受限的线性表,其限制是仅允许在表的一端进行插入和删除运算。人们把此端称为栈顶,栈顶的第一个元素被称为栈顶元素,相对地,把另一端称为栈底。向一个栈插入新元素又称为进栈或入栈,它是把该元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称为出栈或退栈,它是把栈顶元素删除掉,使其下面的相邻元素成为新的栈顶元素。
形象点说,就像洗碗,我们洗好的碗摞在一起,新洗好放在最上面是进栈,而我们把最上面的那个碗叫栈顶元素,当我们拿走最上面那个碗时叫出栈。也就是由于栈的插入和删除运算仅在栈顶一端进行,所以后进栈的元素必定先出栈。
栈溢出攻击
因为栈空间内保存了函数的返回地址。该地址保存了函数调用结束后后续执行的指令的位置。而如果有人恶意修改了这个返回地址,并使该返回地址指向了一个新的代码位置,程序便能从其它位置继续执行。
栈溢出攻击呢,就是向缓冲器填入过多的数据,超出边界,导致数据外溢。 同时利用缓冲器溢出改写数据、改变程序执行流程。 执行shellcode。
尤其是当函数内的一个数组缓冲区接受用户输入的时候,一旦程序代码未对输入的长度进行合法性检查的话,缓冲区溢出便有可能触发!比如下边的一个简单的函数。
1 | void fun(unsigned char *data) |
这是个典型的栈溢出代码。在使用不安全的strcpy库函数时,系统会盲目地将data的全部数据拷贝到buffer指向的内存区域。buffer的长度是有限的,一旦data的数据长度超过BUF_LEN,便会产生缓冲区溢出。
由于栈是低地址方向增长的,因此局部数组buffer的指针在缓冲区的下方。当把data的数据拷贝到buffer内时,超过缓冲区区域的高地址部分数据会“淹没”原本的其他栈帧数据。
如果在data本身的数据内就保存了一系列的指令的二进制代码,一旦栈溢出修改了函数的返回地址,并将该地址指向这段二进制代码的其实位置,那么就完成了基本的溢出攻击行为。
通过计算返回地址内存区域相对于buffer的偏移,并在对应位置构造新的地址指向buffer内部二进制代码的其实位置,便能执行用户的自定义代码!这段既是代码又是数据的二进制数据被称为shellcode,因为攻击者希望通过这段代码打开系统的shell,以执行任意的操作系统命令。
一些基本内容
- ESP:栈指针寄存器,存放一个指针,该指针永远指向系统栈最上面的栈帧的栈顶
- EBP:基址指针寄存器,该指针永远指向系统栈最上面的栈帧的底部
- 函数栈帧:ESP 和 EBP 之间内存空间为当前栈帧
在函数栈帧中一般包含以下几种信息:
局部变量:为函数举报变量开辟的内存空间
栈帧状态值:保存前栈帧的顶部和底部(实际上只保存前栈帧的底部,前栈帧的顶部可以通过堆栈平衡得到)
函数返回地址:保存当前函数调用前的“断点”信息,也就是函数调用前的指令位置
gdb调试
gdb是做题的有利工具,我们可以通过help查看gdb指令的种类
再具体地通过例如help stack查看有关stack的指令
一些指令:
内部指令
这道题的具体分析
这道题先在虚拟机中用gdb pwn2打开直接输入checksec,查看它的保护机制
可以看到没啥特别的保护机制开着,接着我们把它丢到ida64里面查看一下源代码
发现这就是一个很简单的栈溢出,读入0x200字节的buf,然后就直接return 0了。
双击buf进入main函数对应的栈中
这里呢我们可以直接看到它的偏移是0x1再加上rbp的0x8(32位程序是ebp加4),当然还有其他寻找偏移的方法比如pattern生成随机数,找出溢出点等。
我们还可以看到在getshell函数在这是个后门函数有system(‘/bin/sh’)
那我就只需要将该程序栈溢出后写入后门函数就可以get shell了!
而后门函数的地址我们可以直接通过objdump pwn2反汇编直接找到getshell函数的地址,也可以在ida中直接寻找
在这其中我们可以在ret处下断点来查看它的返回地址是否被改变
然后编写脚本