栈溢出原理:
栈溢出漏洞是利用各种不安全的,不限制输入长度的输入方法来导致缓冲区溢出的漏洞,根据溢出发生的位置,可以分成栈溢出和堆溢出,其中由于栈上保存着局部变量和一些状态信息(寄存器的值、返回地址等),通过溢出可以任意修改栈上的值,也可以通过溢出来覆写返回地址从而执行任意地址的代码,利用方法包括shellcode注入,ret2libc、ROP等。为了应对这样的漏洞,也发展出了很多防守方法,例如Stack Canaries。
想要学会栈溢出,首先要了解函数调用栈。
函数调用栈是一块连续的用来保存函数运行状态的内存区域,调用函数和被调用函数根据调用关系堆叠起来,从内存的高地址向低地址增长。
x86与x86-64的情况略有不同,首先看x86的情况
调用函数时,首先会存储当前的ebp,将其压入栈中,然后更新esp为ebp,开辟出新的栈空间。最后按照从右至左的顺序依次将所有参数入栈。压栈结束后call调用函数。调用后同样先将ebp保存,压入栈中,然后更新esp为当前ebp。开始执行函数功能,函数返回时则相反,通过leave指令将esp恢复为当前的ebp,并从栈中将调用者的ebp弹出,最后ret指令弹出返回地址作为eip,程序回到调用函数中,最后抬高esp清理被调用函数的参数,一次函数的调用过程就结束了
以一个简单的程序作为例子
int func(int arg1,int arg2,int arg3,int arg4,int arg5,int arg6,int arg 7,int arg8)
{
int c1 = arg1 + 1;
int loc8 = arg8 + 8;
return loc1 +loc8;
}
int main()
{
return func(11,22,33,44,55,66,77,88);
}
使用gcc -m32命令编译后,用gdb调试查看具体过程
pwndbg> disassemble main
Dump of assembler code for function main:
0x565561b5 <+0>: push ebp #将栈底ebp压栈(esp-=4)
0x565561b6 <+1>: mov ebp,esp #更新ebp为当前栈顶esp
0x565561b8 <+3>: call 0x565561dc <__x86.get_pc_thunk.ax>
0x565561bd <+8>: add eax,0x2e43
0x565561c2 <+13>: push 0x58 #将arg8压栈(esp-=4)
0x565561c4 <+15>: push 0x4d
0x565561c6 <+17>: push 0x42
0x565561c8 <+19>: push 0x37
0x565561ca <+21>: push 0x2c
0x565561cc <+23>: push 0x21
0x565561ce <+25>: push 0x16
0x565561d0 <+27>: push 0xb
0x565561d2 <+29>: call 0x56556189
0x565561d7 <+34>: add esp,0x20
0x565561da <+37>: leave
0x565561db <+38>: ret
End of assembler dump.
pwndbg> disassemble func
Dump of assembler code for function func:
0x56556189 <+0>: push ebp 将栈底ebp压栈
0x5655618a <+1>: mov ebp,esp 更新ebp为当前栈顶
0x5655618c <+3>: sub esp,0x10 为局部变量开辟栈空间
0x5655618f <+6>: call 0x565561dc <__x86.get_pc_thunk.ax>
0x56556194 <+11>: add eax,0x2e6c
0x56556199 <+16>: mov eax,DWORD PTR [ebp+0x8]
0x5655619c <+19>: add eax,0x1
0x5655619f <+22>: mov DWORD PTR [ebp-0x4],eax
0x565561a2 <+25>: mov eax,DWORD PTR [ebp+0x24]
0x565561a5 <+28>: add eax,0x8
0x565561a8 <+31>: mov DWORD PTR [ebp-0x8],eax
0x565561ab <+34>: mov edx,DWORD PTR [ebp-0x4]
0x565561ae <+37>: mov eax,DWORD PTR [ebp-0x8]
0x565561b1 <+40>: add eax,edx
0x565561b3 <+42>: leave
0x565561b4 <+43>: ret
End of assembler dump.
64位的程序,略有不同的地方在于,传递的前六个参数(从左向右的六个)会使用寄存器进行,剩下的参数会和32位相同,从右至左依次入栈。除此之外,调用函数时,rsp不会下移开辟空间,同时rsp下的128个字节会作被用来保存临时数据。
导致栈溢出漏洞的函数
在语言设计之初,因为很多原因,导致出现了一些存在漏洞的函数,如果不经注意,很容易就会导致溢出。
这样的函数大致分为两类,第一类为输入读取函数,例如scanf。
char buf[10];
scanf("%s",buf);
这样的使用,因为没有限制scanf的读取长度,很容易超出buf的存储限度导致溢出。
但有时即使限制了长度也会存在溢出问题,比如下面的写法
sacnf("%10s",buf)
这样的写法看起来很安全,但依然存在溢出问题,因为scanf会在输入的字符串结尾自动添加一个结束符,从而再次出现溢出的危险
第二类危险的函数是strcpy,strcat,sprintf等拷贝函数
char srcbuf[20];
char destbuf[10];
read(0,srcbuf,19);
strcpy(destbuf,srcbuf);
此时read函数读取是十分安全的,但是由于没有考虑到destbuf的长度小于srcbuf,所以也会造成溢出问题。
Comments NOTHING