整数溢出

来源:互联网 发布:java图解教程 pdf 编辑:程序博客网 时间:2024/05/16 09:08

什么情况下会出现整数溢出呢?

由于整数在内存里面保存在一个固定长度 (在本章中使用32位)的空间内,它能存储的最大值就是固定的,当尝试去存储一个数,而这个数又大于这个固定的最大值时,将会导致整数溢出。

举个例子,有两个无符号的整数,num1和num2,两个数都是32位长,首先赋值给num1 一个32位整数的最大值,num2被赋值为1。然后让num1和num2相加,然后存储结果到第3个无符号32位的整数num3,代码如下:

  1. num1 = 0xFFFFFFFF;  
  2. num2 = 0x00000001;  
  3. num3 = num1 + num2; 

很显然,num1的值是:11111111 11111111 11111111 11111111;

num2的值是:00000000 00000000 00000000 00000001;

两者相加,得到结果为:00000000 00000000 00000000 00000000。因此,num3中的值是0,发生了整数溢出。

此时,如果一个整数用来计算一些敏感数值,如缓冲区大小或数组索引,就会产生潜在的危险。

不过,并不是所有的整数溢出都可以被利用,毕竟,整数溢出并没有改写额外的内存;但是,在有些情况下,整数溢出将会导致"不能确定的行为",由于整数溢出出现之后,很难被被立即察觉,比较难用一个有效的办法去判断是否出现或者可能出现整数溢出。

就发现的难度而言,和缓冲区溢出相比,整数溢出更加难被发现。因此,即使是审核过的代码,有时候也难以幸免。

综上所述,一言以蔽之,整数溢出是尝试存储一个很大的数到一个变量中,由于这个变量能够存储的数值范围太小,不足以存储那个很大的数,造成溢出。下面用最简单的程序来说明这个问题(使用的环境是DEV C++,用户也可以使用TurboC,稍有不同):

P02_08.c

  1. #include <stdio.h>   
  2. int main(void)  
  3. {   
  4.     int l;   
  5.     short s;   
  6.     char c;   
  7.     l = 0xdeadbeef;   
  8.     s = l;   
  9.     c = l;   
  10.     printf("l=0x%x(%d bytes)/n", l, sizeof(l));   
  11.     printf("s=0x%x(%d bytes)/n", s, sizeof(s));   
  12.     printf("c=0x%x(%d bytes)/n", c, sizeof(c));   
  13.     return 0;   
  14. }  

生成可执行文件,运行,显示:

 

如前所述,整数溢出并不像普通的漏洞类型,一般不会允许直接改写内存。但是一个精巧的设计可以直接改变程序的控制流程,程序员此时很难有办法在进程里面检查计算发生后的结果,带给用户的感觉就是:计算结果和正确结果之间,有一定的偏差。此种攻击一般的方法是:攻击者强迫一个变量包含错误的值,从而在后面的代码中出现问题。看下面的例子:

P02_09.c

  1. #include <stdio.h>   
  2. #include <string.h>   
  3. int main(int argc, char *argv[])  
  4. {   
  5.     unsigned short s;   
  6.     int i;   
  7.     char buf[100];   
  8.     i = atoi(argv[1]);   
  9.     s = i;   
  10.     if(s >= 100)  
  11. {  
  12.          printf("拷贝字节数太大,请退出!/n");   
  13.          return -1;   
  14.     }   
  15.     memcpy(buf, argv[2], i);   
  16.     buf[i] = '/0';   
  17.     printf("成功拷贝%d个字节/n", i);   
  18.     return 0;   
  19. }  

该程序的作用是将argv[2]的内容拷贝到buf中,由argv[1]指定拷贝的字节数,在程序中,进行了相对严格的大小检查:如果argv[1]的值大于等于buf数组的大小(100),则不进行拷贝。

生成可执行文件,运行:
 显示:
 完全正常。运行:
 
显示  该程序看似正常,但是输入:
 程序显示:
 

该程序中,程序从命令行参数中得到一个整数值存放在整形变量i当中,然后这个值被赋予unsigned short类型的整数s,由于s在内存中是用16位进行存储,16位能够存储的最大数是:

  1. 11111111 11111111 

即:十进制的65535,因此,unsigned short存储的范围是0 - 65535,如果这个值大于unsigned short类型所能够存储的值65535,它将被截断。因此,输入65536,系统会将其认为0。因为65536的二进制是:

  1. 1 00000000 00000000 

系统只取后面16位进行存储。

实际上,整数溢出的危害还在于能够产生"并发攻击",类似于医学中的"并发症"。以上面的例子为例,程序绕过代码中的大小判断部分的边界检测,又可以导致缓冲区溢出,只要使用一般的栈溢出技术就能够利用这个溢出程序。

下面的例子列举了另外几个出现问题的运算:

P02_10.c

  1. #include <stdio.h>   
  2. int main(int argc, char *argv[])  
  3. {   
  4.     int n1 = 0x7fffffff;  
  5.     int n2 = 0x40000000;  
  6.     int n6 = 0x8fffffff;  
  7.     printf("%d(0x%x)+1=%d(0x%x)/n", n1, n1, n1+1, n1+1);       
  8.     printf("%d(0x%x)+%d(0x%x)=%d(0x%x)/n", 
    n2, n2, n2, n2, n2+n2, n2+n2);  
  9.     printf("%d(0x%x)*4=%d(0x%x)/n", n2, n2, n2*4, n2*4);   
  10.     printf("%d(0x%x)-%d(0x%x)=%d(0x%x)/n", 
    n2, n2, n6, n6, n2-n6, n2-n6);   
  11.     return 0;   
  12. }  

生成可执行文件,运行,得到如下结果:

 

以上显示的基本上是可以被攻击者利用的一些运算。很显然,这些不正常的结果都是由于整数溢出引起的,读者可以自己分析其原理。

整数溢出还有可能在动态分配内存时被利用。看如下代码:

P02_11.c

  1. #include <stdio.h>   
  2. #include <stdlib.h>   
  3. int* arraycpy(int *array, int len)  
  4. {   
  5.     int *newarray, i;   
  6.     newarray = malloc(len*sizeof(int));   
  7.     printf("为newarray成功分配%d字节内存/n",len*sizeof(int));  
  8.     if(newarray == NULL)  
  9. {   
  10.         return -1;   
  11.     }   
  12.     printf("循环运行次数:%d(0x%x)/n",len,len);  
  13.     for(i = 0; i < len; i++)  
  14. {  
  15.         newarray[i] = array[i];   
  16.     }   
  17.     return newarray;   
  18. }  
  19. int main(int argc, char *argv[]){   
  20.     int array[] = {1,2,3,4,5};      
  21.     arraycpy(array,atoi(argv[1]));  
  22.     return 0;   
  23. }  

该代码将array拷贝到newarray中,生成exe文件,运行:  显示:
 看似没有问题。但是如果输入下面的命令:
 我们知道,1073741824的16进制是0x40000000,从前一个例子可以看出,0x40000000*4=0x0。因此,屏幕上显示:
 很显然,这个看起来没有问题的函数,可能出现在没有为newarray分配内存的情况下,却向其里面拷贝数组元素,循环的次数还非常多,严重时造成系统崩溃。攻击者通过选择一个合适的值给len可以使得循环反复执行导致缓冲区溢出。

还有一种情况,通过改写malloc的控制结构,也能够在正常的函数运行的过程中插入其他可执行恶意代码。

P02_12.c

  1. #include <stdio.h>   
  2. #include <stdlib.h>   
  3. int catstring(char *buf1, char *buf2, int len1, int len2)  
  4. {   
  5.     char mybuf[256];   
  6.     if((len1 + len2) > 256)  
  7. {  
  8.              printf("超出mybuf容纳范围!/n");  
  9.              return -1;   
  10.     }   
  11.     memcpy(mybuf, buf1, len1);  
  12.     memcpy(mybuf + len1, buf2, len2);       
  13.     printf("复制%d+%d=%d个字节到mybuf!/n",len1,len2,len1+len2);  
  14.     return 0;   
  15. }  
  16. int main(int argc, char *argv[])  
  17. {          
  18.     catstring(argv[1],argv[2],atoi(argv[3]),atoi(argv[4]));  
  19.     return 0;   

该例子看起来无懈可击,并且进行了len1和len2相加之后的检查,输入:

 

显示:

 

输入:

 

显示:

 

这看起来正常,但是如果输入:

 

2147483647的16进制为0x7FFFFFFF。该运行结果为:

 


程序崩溃。根本不会显示:"超出mybuf容纳范围!"。是什么原因?请读者自己分析。

整数溢出是非常危险的,部分原因是因为它在发生后不可能被发现,也就是说,一个整数溢出发生了,应用程序并不知道它的计算是错误的。因此应用程序在假定它是正确的情况下,会继续运行下去。在安全的系统中,这种结果具有巨大的危害,有时甚至能够造成系统崩溃。

解决整数溢出的方案,主要是编程之前必须进行详细的预测,多考虑一些问题, 在编程时将各种问题考虑到并且进行相应的处理。如:

充分考虑各种数据的取值范围,使用合适的数据类型;

尽量不要在不同范围的数据类型之间进行赋值;等等。

 

原创粉丝点击