缓冲区溢出原理:
就像老师讲的倒啤酒的例子,向一个杯子里灌啤酒,杯子装不下了,啤酒就溢出了。在计算机内存中,当某个数据,超过了处理程序限制的范围时,该数据就会造成程序的执行溢出(overflow)。
但计算机是怎样存储数据的,为什么可能造成程序限制的范围呢?进程是如何组织内存中的数据的?
所以我觉得在掌握缓冲区溢出基础之前,应对内存中数据存储方式有所了解。
从内存存储分配的角度看,缓冲区就是一段连续分配的内存空间。在程序的函数调用中,缓冲区一般通过堆栈来实现。堆栈是一个后进先出的队列,它的生长方向与内存的生长方向正好相反,正因为这一特点使得溢出攻击可能实现。我通过一个例子谈谈我对堆栈存贮的理解:
假设程序中有一个函数overflow(buf),当程序调用这个函数时,计算机会将调用此函数的一些地址和参数压入堆栈中,其顺序为:buf,overflow函数返回地址(以下简称ret),EBP寄存器指向的地址(以下简称EBP),如果函数中有局部变量,接下来会在堆栈中再分配这些局部变量空间。当函数调用结束时,局部变量空间将被丢弃(但不会清空),然后弹出EBP恢复调用函数前堆栈帧的指向,最后弹出返回地址(ret)到EIP继续执行下一步主程序。
总结一下:
调用函数时堆栈中数据存储为(栈顶->栈底):局部变量->EBP->ret->buf。
可以看到,当程序如果使用局部变量时,其长度大于预分配给局部变量空间的时候,多余长度的字符将按照内存存贮方向填充,而前面提到堆栈的生长方向与内存生长方向相反,于是EBP,ret就可能被多余的字符所覆盖,在函数返回时ret已不是原来的值,而是一个错误的值,这个值被弹入EIP后,cpu可不管它是不是原来的地址,就照EIP指向的地址去执行程序,可想而知这个错误地址指向的内存地方不是正确的主程序下一步语句,错误也就出现了,就是所说的溢出了,呵呵。
如果我们精心设计一个这种多余的字符,使得它所覆盖的ret地址能够指向我们自己编写的shellcode并加以执行,也就达到了我们利用缓冲区溢出漏洞的目的。
了解了溢出产生的原因,当我们遇到存在这种漏洞的程序并打算加以利用时,就要准确确定这个溢出点,也就是需要填充多少字符刚好能够覆盖我们需要的ret。
由于水平有限,还不足以分析存在缓冲区漏洞的较大程序(惭愧,惭愧),因此参考教学视频写了一个模拟的小例子,练习一下中安网培课程中介绍的利用报错对话框来确定溢出点的方法。
第一步:
test1.c
#include
#include
int overflow(char *buf)
{
char output[8];
strcpy(output,buf);
printf("buf的地址是:%p\n",buf);
printf("output内容为:%s\n",output);
return 0;
}
int main()
{
char buf[26];
for(int j=0;j<26;j )
{
buf[j]='A';
}
overflow(buf);
return 0;
}

