整数溢出的概念
1、整数及整数溢出
关于整数的概念,应该说我们在上中学的时候就学过了。这里我们需要了解的是:整数分为无符号和有符号两类,其中有负符号整数最高位为 1,正整数最高位为 0,无符号整数无此限制;此外,常见的整数类型有 8 位(布尔、单字节字符等)、16 位(短整型、Unicode等)、32 位(整型、长整型)以及 64 位(__int64)等等。对于本文来说,了解这些就基本足够了。
关于整数溢出,简而言之,就是往存储整数的内存单位中存放的数据大于该内存单位所能存储的最大值,从而导致了溢出。归根到底,造成整数溢出漏洞的根本原因还是编程人员由于自身疏忽而对整数进行了错误操作引起的。
2、导致漏洞的几种整数误操作
一般说来,主要有三类整数操作可以导致安全性漏洞,下面列出每类的典型例子:
2.1.无符号整数的下溢和上溢
无符号整数的下溢问题是由于无符号整数不能识别负数所导致的。示例代码如下:
BOOL fun(size_tcbSize)
{
if(cbSize> 1024)
rerurn FALSE;
char *pBuf = new char[cbSize – 1];
//未对 new 的返回直进行检查
memset(pBuf, 0x90,cbSize – 1);
……
return TRUE;
}
在上面代码中,在调用 new 分配内存后,程序未对调用结果的正确性进行检测。如果cbSize 为 0 的话,则 cbSize – 1 为-1。但是 Memset 中第 3 个参数本身是无符号数,因此会将-1 视为正的 0xffffffff,函数执行之后程序当然就只有崩溃了。无符号整数的上溢问题也不难理解,示例代码如下:
BOOL fun(char *s1,size_t len1, char *s2,size_t len2)
{
if(len1 + len2 + 1 > 1024)
return FALSE;
charpBuf = new char[len1 + len2 + 1];
if(buf == NULL)
return FALSE;
memcpy(buf, s1, len1);
memcpy(buf + len1, s2, len2); //可能造成程序崩溃
……
return TRUE;
}
本例子中代码看起来没什么问题,该检测的地方也都检测了。但这段代码却可能出现整数上溢问题,len1 和 len2 都是无符号整数,如果 len1 = 8,len2 = 0xffffffff,由于加操作的限制,8+0xffffffff+1 产生的结果是 8。也就是说,new 操作只分配 8 字节的内存,而后面却要进行长达 0xffffffff 的串拷贝工作,结果肯定也是程序崩溃。
2.2.符号的问题
符号问题可以是多种多样的,但有几点是应该注意的:有符号整数之间的比较;有符号整数的运算;无符号整数和有符号整数的对比。这里举一个典型的例子:
intcopy_something(char *buf,intlen)
{
charszBuf[800];
if(len>sizeof(szBuf)) /* [1] */
{
return -1;
}
return memcpy(szBuf,buf,len); /* [2] */
}
上面代码的问题在于 memcpy 使用无符号整数作为 len 参数,但是在之前的数据边界检测使用了有符号整数。假设提供一个负数的 len,这样可以绕过[1]的检测,但是这个值同样被使用在[2]的 memcpy 函数的参数里面,len 可能被转换成一个非常大的正整数,导致 kbuf缓冲区后面的数据被重写,进而使得程序崩溃。
2.3.截断的问题
截断问题主要发生在大范围整数(如 32 位)拷贝给小范围整数(如 16 位)的时候可能产生的。同样来看一个例子:
BOOL fun(byte *name, DWORD cbBuf)
{
unsigned short cbCalculatedBufSize = cbBuf;
byte *buf = new byte[cbCalculatedBufSize];
if (buf == NULL)
return FALSE;
memcpy(buf, name,cbBuf);
……
return TRUE;
}
如果 cbBuf 是 0x00010020,又会如何呢?cbCalculatedBufSize 只有 0x20,因为只从0x00010020 复制了低 16 位。因此,仅分配了 0x20 个字节,并且 0x00010020字节复制到新分配的目标缓冲区中。如果整数溢出发生,之后的所有相关操作的结果都将发生变化。与缓冲区溢出不同的是,整数溢出发生时不会马上发生异常,即使程序执行结果与预期的不同,也很不容易发现问题所在。前面提到,整数溢出在很多时候会导致缓冲区溢出漏洞的发生,包括堆栈溢出和堆溢出。但并不是所有由整数溢出导致的缓冲区溢出都是可以利用的。相反,大多数情况是不能利用的,原因就在于其中涉及到诸如近乎 4G这样的大内存操作,会发生段错误。