栈溢出漏洞介绍与利用

##简介
栈溢出,0day漏洞中最简单的一种漏洞。相比起其它漏洞,栈溢出最容易被出现,也最容易利用。是一种非常常见的漏洞。一般如果服务器程序出现了栈溢出的漏洞,那么这台服务器距离被pwn也不远了。

##原理
栈溢出的主要产生原因是因为程序对用户输入字符串的长度检查不严格,或者有逻辑漏洞,或者一些菜鸟根本没有进行长度检查而产生的漏洞。相信新手程序员一般都写过这样的代码。

1
2
char dest[10];
scanf("%s",dest);

千万不要把这样的代码放到服务器上去供别的人访问!!!这两行代码首先声明了一个局部变量,然后用scanf函数读入字符串。但是这个程序没有限制字符串长度的地方。如果用户输入的字符串长度大于10个字符,那么就会产生溢出,而函数的局部变量是储存在栈中的,这种错误就是栈溢出。

通常产生栈溢出之后程序运行结果是无法预计的,最有可能就是崩溃退出。但是到了黑客手上,结果就是被人控制电脑。

##利用
既然有栈溢出的漏洞,那么黑客是如何利用的呢?这首先需要介绍一下栈的结构(不会的复习汇编去)
这是一个典型的存在栈溢出的函数

1
2
3
4
5
6
void s(void)
{

char dest[10];
scanf("%s",dest);
return 0;
}

编译之后用ida打开,看看栈中是怎么样的。
这是一个普通的栈应该有的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-0000000C dest            db ? ;
-0000000b db ? ;
-0000000a db ? ;
-00000009 db ? ;
-00000008 db ? ;
-00000007 db ? ;
-00000006 db ? ;
-00000005 db ? ;
-00000004 db ? ;
-00000003 db ? ;
-00000002 db ? ;
-00000001 db ? ;
+00000000 s db 4 dup(?)
+00000004 r db 4 dup(?)
+00000008
+00000008 ; end of stack variables

dest就是刚才scanf读入字符串的存放位置。下面的”s”代表ebp寄存器的位置,然后”r”表示的就是函数的返回位置,也就是call命令压栈的调用函数的下一句代码的地址。如果这个函数还有参数的话,应该会在函数返回值的高位,也就是更靠近栈底的位置。

那么如果我输入的字符串过长,比如“aaaaaaaaaaaaa”+“bbbb”+“cccc”这么长的字符串会怎么样呢? 现在栈中的数据会是这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-0000000C dest            db 'a' ;
-0000000b db 'a' ;
-0000000a db 'a' ;
-00000009 db 'a' ;
-00000008 db 'a' ;
-00000007 db 'a' ;
-00000006 db 'a' ;
-00000005 db 'a' ;
-00000004 db 'a' ;
-00000003 db 'a' ;
-00000002 db 'a' ;
-00000001 db 'a' ;
+00000000 s db 4 dup("bbbb")

+00000004 r db 4 dup("cccc")
+00000008
+00000008 ; end of stack variables

可以看到原来应该是函数返回地址的地方被我们覆盖了。现在函数返回的时候eip指针的值变成了”cccc”或者说是”0x61616161”这个位置。当然如果这里不是代码段的话程序就崩溃了。但是如果我们将输入的”cccc”变成我们进行精心计算好的地址,那么我们就达成了漏洞利用最重要的一部,控制eip。然后你的程序就会被黑客任意的跳转,以此为跳板,进而控制电脑。

##危害以及防御
当然,现在的程序员已经很少再犯这么容易发现的错误了。但是因为各种逻辑上的不严谨,依然会产生许许多多的漏洞。通常会把栈溢出作为一次完整漏洞攻击的第一个环节,也可以说是入口。再结合ret to libc或者是rop gadget以及一些其它的技巧,进而可以控制计算机,获得root权限的许多的危害。

至于防御的方法:
1.停止使用scanf或者gets等不安全的函数,改为scanf_sgets_s等可以进行长度检查的函数,并且一定要注意读入字符串长度的检查。
2.尽量的将字符串的数据使用堆来储存。
3.多多debug,尽量发现所有潜在的逻辑漏洞。