gets引发的血案

来源:互联网 发布:淘宝千牛怎么同意退款 编辑:程序博客网 时间:2024/04/30 08:42

转自:http://hi.baidu.com/david_jlu/blog/item/3f742b1b74284a1a8618bf80.html


   /* DO NOT USE THIS FUNCTION!! There is no limit on how much it will read. */
下面让我们浏览一下gets的源码:

 1 char * 2 gets(char *str){ 3         char *cp; 4         int c; 5         if ((stdin->flags & __SRD) == 0) 6                 return NULL; 7         for (c = 0, cp = str; c != '\n'; cp++) { 8                 if ((c = getchar()) == EOF) { 9                         stdin->flags |= __SERR;10                         return NULL;11                  }12                  *cp = (char)c;13          }14          *--cp = '\0';15         return str;16 }一直读到‘\n’结束,看上去似乎没问题,但忽略了str的长度,如果输入的串超过str边界咋办?所以要避免使用这个函数,取而代之用fget就可以有效的检查有没有越界: 1 char * 2 fgets(char *as, int n, FILE *f){ 3         int c; 4         char *s=as; 5          c = EOF; 6         while(n>1 && (c=getc(f))!=EOF){ 7                  *s++=c; 8                  --n; 9                 if(c=='\n') break;10          }11         if(c==EOF && s==as12                          || ferror(f)) return NULL;13         if(n) *s='\0';14         return as;15 } 这是一个很典型的问题──缓冲区溢出(Buffer Overflow)。1988年11月,许多组织不得不因为“Morris 蠕虫”而切断 Internet 连接,“Morris 蠕虫”使得整个Internet的10%崩溃。2001年7月,一个名为“Code Red”的蠕虫病毒最终导致了全球运行微软的IIS Web Server的300000多台计算机受到攻击。2003年1月,“Slammer”蠕虫利用Microsoft SQL Server 2000中的一个缺陷,使得南韩和日本的部分Internet 崩溃,中断了芬兰的电话服务,并且使得美国航空订票系统、信用卡网络和自动出纳机运行缓慢。所有这些攻击都利用了缓冲区溢出的程序缺陷。为什么缓冲区溢出危害这么大呢?下面举一个例子:1 void print_input(int a,int b) {2         char str[3];3          gets(str);4          puts(str);5 }6 int main(int argc, char * argv[]) {7          print_input(1,2);8         return 0;9 }
编译以后(gcc加-g)用gdb调试,设断点在gets后,直接continue到断点,看看当前状态

(gdb) bt
#0 print_input (a=1, b=2) at study.c:5
#1 0x080483e7 in main () at study.c:9
(gdb) p &a
$3 = (int *) 0xbfed95c0
(gdb) p &b
$4 = (int *) 0xbfed95c4
(gdb) p &str
$5 = (char (*)[3]) 0xbfed95b5
(gdb) x/3b 0xbfed95b5
0xbfed95b5:     0x41    0x42    0x00      #这里的‘1’和‘2’是我执行时输入的str

这里能看出栈区是从高地址向低地址延伸的,即高地址为栈底低地址为栈顶,当前内存状态:

0xbfed95b5 0xbfed95b6 0xbfed95b7 ………… 0xbfed95c0 0xbfed95c4
      str[0]            str[1]             str[3]                             a                b

中间还有8个字节,是什么呢?如果学过编译编译应该知道,肯定会有返回地址的,要不然执行
完print_input怎么返回main呢?!不信看看:

(gdb) x/4b 0xbfed95bc
0xbfed95bc:     0xe7    0x83    0x04    0x08        #这个就是返回地址0x080483e7
(gdb) x/4b 0xbfed95b8
0xbfed95b8:     0xd8    0x95    0xed    0xbf
(gdb) disassemble main                                    #对main函数反汇编
Dump of assembler code for function main:
0x080483c2 <main+0>:    lea    0x4(%esp),%ecx
0x080483c6 <main+4>:    and    $0xfffffff0,%esp
0x080483c9 <main+7>:    pushl 0xfffffffc(%ecx)
0x080483cc <main+10>:   push   %ebp
0x080483cd <main+11>:   mov    %esp,%ebp
0x080483cf <main+13>:   push   %ecx
0x080483d0 <main+14>:   sub    $0x14,%esp
0x080483d3 <main+17>:   movl   $0x2,0x4(%esp)
0x080483db <main+25>:   movl   $0x1,(%esp)
0x080483e2 <main+32>:   call   0x80483a4 <print_input>
0x080483e7 <main+37>:   mov    $0x0,%eax                            #函数执行完应该返回到这
0x080483ec <main+42>:   add    $0x14,%esp
0x080483ef <main+45>:   pop    %ecx
0x080483f0 <main+46>:   pop    %ebp
0x080483f1 <main+47>:   lea    0xfffffffc(%ecx),%esp
0x080483f4 <main+50>:   ret    
End of assembler dump.

从上面可以看出,a变量左边紧挨着那个就是返回地址,即call 0x80483a4 <print_input>
的下一条指令,那还有一个空缺是什么呢?0xbfed95b8对应是堆栈指针sp,这个可有可无。
当前内存状态图:

0xbfed95b5 0xbfed95b6 0xbfed95b7 0xbfed95b8 0xbfed95bc 0xbfed95c0 0xbfed95c4
      str[0]            str[1]             str[3]          %sp          ret地址            a                 b

现在的问题出在,gets根本不检查边界,倘若输入一个很长的串就会覆盖%sp和返回地址,
这就意味着cracker能够改写返回地址,当print_input完成时,它将返回──不过不是返回到
main函数 ,而是返回到cracker想要执行的恶意代码。

附:C语言中的危险函数
函数严重性解决方案gets最危险使用 fgets(buf, size, stdin)。这几乎总是一个大问题!strcpy很危险改为使用 strncpy。strcat很危险改为使用 strncat。sprintf很危险改为使用 snprintf,或者使用精度说明符。scanf很危险使用精度说明符,或自己进行解析。sscanf很危险使用精度说明符,或自己进行解析。fscanf很危险使用精度说明符,或自己进行解析。vfscanf很危险使用精度说明符,或自己进行解析。vsprintf很危险改为使用 vsnprintf,或者使用精度说明符。vscanf很危险使用精度说明符,或自己进行解析。vsscanf很危险使用精度说明符,或自己进行解析。streadd很危险确保分配的目的地参数大小是源参数大小的四倍。strecpy很危险确保分配的目的地参数大小是源参数大小的四倍。strtrns危险手工检查来查看目的地大小是否至少与源字符串相等。realpath很危险(或稍小,取决于实现)分配缓冲区大小为 MAXPATHLEN。同样,手工检查参数以确保输入参数不超过 MAXPATHLEN。syslog很危险(或稍小,取决于实现)在将字符串输入传递给该函数之前,将所有字符串输入截成合理的大小。getopt很危险(或稍小,取决于实现)在将字符串输入传递给该函数之前,将所有字符串输入截成合理的大小。getopt_long很危险(或稍小,取决于实现)在将字符串输入传递给该函数之前,将所有字符串输入截成合理的大小。getpass很危险(或稍小,取决于实现)在将字符串输入传递给该函数之前,将所有字符串输入截成合理的大小。getchar中等危险如果在循环中使用该函数,确保检查缓冲区边界。fgetc中等危险如果在循环中使用该函数,确保检查缓冲区边界。getc中等危险如果在循环中使用该函数,确保检查缓冲区边界。read中等危险如果在循环中使用该函数,确保检查缓冲区边界。bcopy低危险确保缓冲区大小与它所说的一样大。fgets低危险确保缓冲区大小与它所说的一样大。memcpy低危险确保缓冲区大小与它所说的一样大。snprintf低危险确保缓冲区大小与它所说的一样大。strccpy低危险确保缓冲区大小与它所说的一样大。strcadd低危险确保缓冲区大小与它所说的一样大。strncpy低危险确保缓冲区大小与它所说的一样大。vsnprintf低危险确保缓冲区大小与它所说的一样大。

原创粉丝点击