关于内存溢出

时间:2014.07.03

地点:基地

-----------------------------------------------------------------------------

一、什么是内存溢出

  在内存空间中,当要表示的数据超出计算机为该数据分配的空间范围时或者访问超出为数据分配的空间的内存范围时,或者为数据分配好了给定大小的内存,而我们对超出该部分内存部分进行操作时就会产生内存溢出。操作系统中50%的安全漏洞都是由内存溢出引起的。内存溢出是C/C++语言的固有缺陷,因为它们既不检查数组边界,也不检查类型的可靠性。

  假设我们的代码申请了X字节的内存缓冲区,随后向其复制超过X字节的数据,那么多出来的字节就会溢出原本为之分配的内存区。而C/C++编译器开辟的内存缓冲区常常邻近很重要的数据结构,这样很容易被恶意攻击者刻意覆盖原本安全可信的数据,从而让机器变为他们肆意攻击的肉鸡。

-----------------------------------------------------------------------------

二、防范措施——针对库函数

  举例来说,C语言中的字符串由于没有采用相应的安全措施,为此很容易发生内存泄露问题。比如strcpy、strcat等函数在执行时不会对缓冲区的大小进行检查,如此很容易引发安全问题:

const int kMAX_DATA_LENGTH=32;
void DataCopy(char* src_data)
{
  char dest_data[kMAX_DATA_LENGTH];
  strcpy(dest_data,src_data);
  //......
}
上述代码是危险代码,输入数据源src_data的长度不超过规定的长度,那么代码表现很正常,但因为strcpy()不会检查数据源长度,在执行复制操作时能让它停下来的只有字符串中的结束符 ’\0' ,只有碰到字符串结束符时字符串复制才会停止,如果没碰到这个结束符时,它就一个字节一个字节地复制src_data内容,在填满32字节预设空间后,溢出的字符部分就会去覆盖缓冲区后面的内存区域,打个比方来说,我们虽然为这个存储区划分了一块区域,但这就像国家领土的划分,划界是划了,但人还是可以通过的,但这是很危险的。在这里若是溢出的数据恰好覆盖了后面的DataCopy函数的返回地址,那么在函数调用完毕后,程序就转入攻击者设定的返回地址中去了,乖乖地进入了预先设定好的陷阱,执行黑客们的代码去了。

  为了避免这种情况,我们在处理来自用户的数据时应该处处留意,如果一个函数的数据来源不可靠,又要用到内存缓冲区,就必须提供警惕,我们应该获知内存缓冲区的总长度,并检测内存缓冲区,将上述代码改善如下:

const int kMAX_DATA_LENGTH=32;
void DataCopy(char* src_data,DWORD data_len)
{
  char dest_data[kMAX_DATA_LENGTH];
  if(data_len<kMAX_DATA_LENGTH
    strcpy(dest_data,src_data);
  dest_data[data_len]='\0';
  //......
}
在这里,我们首先要获得源数据src_data的长度,保证数据长度不大于缓冲区长度32,;其次,要保证参数传来的数据长度真实有效,可以向缓冲区后一个位置处写入数据,因为,当缓冲区溢出时,一旦向其写入常量值,代码就会出错,与其落入阴谋家的陷阱还不如及时终止程序运行。这种方法只是有效降低了内存溢出的危害,无法从根本上避免对内存溢出的攻击。我们在使用诸如strcpy 、strcat、gets等函数时,得从源代码开始提高警惕,追踪传入数据流向,即便是使用所谓的相对安全可靠的改良版本N-Versions(strncpy和strncat)也不可轻信。

-----------------------------------------------------------------------------

三、防范措施——针对数组

  当我们访问边界数据时同样肯能引起缓冲区溢出。比如:

const int kDataLength=16;
int data[16]={1,2,3,4,5,0,7,8,4,0,3,0,3,2,5,0};
void PrintData()
{
  for(int i=0;data[i]!=0&&i<kDataLength;i++)
    cout<<data[i]<<endl;
}
在这里,但i等于16时,还是会访问data[16],于是已经越界访问到非法区域了,于是会引起缓冲区溢出,正确的做法是:不要将索引号i和数据本身data[i]的判断放在一起。而是将判断条件分开为两句,如下:

const int kDataLength=16;
int data[16]={1,2,3,4,5,0,7,8,4,0,3,0,3,2,5,0};
void PrintData()
{
  for(int i=0;i<kDataLength;i++;
   {
      if(data[i]!=0)
            cout<<data[i]<<endl;;
    }
}
类似的,我们在访问未初始化的指针或失效指针时,因为未初始化指针和失效为置nullptr的野指针都是指向未知的内存空间,对这样的指针进行操作很有可能访问或改写未知的内存区域,也就可能引起缓冲区溢出问题。要杜绝这样的情况。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章