關於內存溢出

時間: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的野指針都是指向未知的內存空間,對這樣的指針進行操作很有可能訪問或改寫未知的內存區域,也就可能引起緩衝區溢出問題。要杜絕這樣的情況。


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