这篇文章主要介绍缓冲区溢出的一些问题及应对策略。
什么是缓冲区溢出?大意就是为某特定的数据结构分配内存,然后修改内存时没有控制好截止条件,使得修改了边界之外的内存;比如在栈上分配的内存数组int iData[100],然后修改数组元素:
for (int i = 0; i <= 100; ++i) {//todo},如果在32位机器上,可能分配多于100*sizeof(int)个字节的空间,然后iData[0]在低地址,iData[1]....在高地址,理论上这里修改iData[100]可能不会影响其他变量或调用函数时需要保存的参数;再比如
char szToBuf[1024];
char szFromBuf[2048];
memcpy(szFromBuf, szToBuf, strlen(szFromBuf))就非常可能改写ebp,返回地址等引起严重的问题。这些都会因为编码问题或者不完全了解底层的实现,导致出现缓冲区溢出,进而引起可被利用的漏洞。以下列举比较隐晦的可能导致缓冲区溢出的情况:
1由整数漏洞引起的问题,如溢出,截断,隐式转换等;以下分别列举相应的例子:
整数溢出:一般指的是有符号整数溢出,结果值不能用该类型表示时就会发生,这种情况是未定义的行为,如int16_t i = INT16_MAX;i ++;那么在内存中原来i的位模式是011111***11111,然后加1后的补码为1000***0000是最小的负数了,那么有下面的代码:
int16_t i = 0;
while (i <= INT16_MAX)
{
iData[i] = 100;
i ++;
}
那么这里就会发生溢出了,非法访问了iData[0]前面的内存,这段代码也是无限循环,这个例子有点特殊。
截断:比如32的int,赋值给16位的int16_t,那么就取低16位,这里截断后的位模式不变,不管赋值给有符号的还是无符号的16位的类型,只是告诉编译器如何解释这些位模式。
这里列举无符号的截断,如
int main(int argc, char * argv[])
{
unsigned short int iLen = 0;
iLen = strlen(argv[1]) + strlen(argv[2]) + 1;
char * pBuff = (char *)malloc(iLen);
if (pBuff == NULL) { return -1; }
strcpy(pBuff, argv[1]);
strcat(pBuff, argv[2]);
}
这段代码的意思是以两个串的长度加1,开辟总的内存空间,然后拷贝两个串至pBuff中,外加个空白符结尾。这里的问题是当strlen(argv[1]) + strlen(argv[2])的和超过unsigned short int所能表示的最大值时,发生截断,最后iLen的值是小于min(strlen(argv[1], strlen(argv[2])))的,那么在拷贝时就会发生缓冲区溢出。
再比如大学写的有问题的二分查找,有这么一行:
int iMid = (iLeft + iRight) / 2;而这可能因为视iLeft和iRight的类型而定,如果都为无符号类型,那么可能两个数的和导致回绕,使得(iLeft + iRight)/2后的值小于min(iLeft,iRight),那么就取不到[iLeft,iRight]索引处的数据了;正确的写法应该是:int iMid = iLeft + (iRight-iLeft) / 2;如果为有符号,可能导致溢出,两个数的和为负数了,导致引用错误的内存地址;
有/无符号的隐式转换:如把无符号赋值给有符号,有符号赋值给无符号,假设类型大小一致的情况下[大小不一致需要扩展],转换是位模式不变,变的是解释这些位的方式,值可能会改变。比如:
求和:
int computeSum(int iArray[],unsigned int iLen)
{
int iSum = 0;
for (int i = 0; i <= iLen -1; ++i)
{
//TODO
}
return iSum;
}
如果这样调用computeSum(***, 0),因为iLen为无符号,那么0-1为-1,有符号转换成无符号为INT32_MAX,就出现了非法内存访问;
其他情况:如没有完全检测边界值:
int * pValue = new[10];
int insertValue(int iPos, int iValue)
{
if (iPos > 9) //should be if (iPos > 9 || iPos < 0)
{
return -1;
}
pValue[iPos] = iValue;
return 0;
}
如果iPos不小心是负数的话,代码块pValue[iPos]被编译器转换为如下形式:((char*)pValue + sizeof(int) * iPos),那么写入就会发生在pValue地址前面而引起一些问题;
像很多C字符串库函数,比如strcpy,sprintf等都没有控制要操作的字节数,可能会导致多拷贝字节,而缓冲区溢出往往导致栈溢出,覆盖重要的ebp和函数返回地址,和其他变量,破坏栈。
下篇会介绍下网络编程方面的知识。