這篇文章主要介紹緩沖區(qū)溢出的一些問(wèn)題及應(yīng)對(duì)策略石洗。
什么是緩沖區(qū)溢出紧显?大意就是為某特定的數(shù)據(jù)結(jié)構(gòu)分配內(nèi)存,然后修改內(nèi)存時(shí)沒(méi)有控制好截止條件涉兽,使得修改了邊界之外的內(nèi)存篙程;比如在棧上分配的內(nèi)存數(shù)組int iData[100],然后修改數(shù)組元素:
for (int i = 0; i <= 100; ++i) {//todo}拥诡,如果在32位機(jī)器上氮发,可能分配多于100*sizeof(int)個(gè)字節(jié)的空間,然后iData[0]在低地址仇祭,iData[1]....在高地址颈畸,理論上這里修改iData[100]可能不會(huì)影響其他變量或調(diào)用函數(shù)時(shí)需要保存的參數(shù)嚣艇;再比如
char szToBuf[1024];
char szFromBuf[2048];
memcpy(szFromBuf, szToBuf, strlen(szFromBuf))就非郴可能改寫ebp,返回地址等引起嚴(yán)重的問(wèn)題贰谣。這些都會(huì)因?yàn)榫幋a問(wèn)題或者不完全了解底層的實(shí)現(xiàn)迁霎,導(dǎo)致出現(xiàn)緩沖區(qū)溢出,進(jìn)而引起可被利用的漏洞秘豹。以下列舉比較隱晦的可能導(dǎo)致緩沖區(qū)溢出的情況:
1由整數(shù)漏洞引起的問(wèn)題昌粤,如溢出,截?cái)嗥喾罚[式轉(zhuǎn)換等袱讹;以下分別列舉相應(yīng)的例子:
整數(shù)溢出:一般指的是有符號(hào)整數(shù)溢出,結(jié)果值不能用該類型表示時(shí)就會(huì)發(fā)生椒丧,這種情況是未定義的行為救巷,如int16_t i = INT16_MAX;i ++久橙;那么在內(nèi)存中原來(lái)i的位模式是011111***11111管怠,然后加1后的補(bǔ)碼為1000***0000是最小的負(fù)數(shù)了缸榄,那么有下面的代碼:
int16_t i = 0;
while (i <= INT16_MAX)
{
??? iData[i] = 100;
i ++;
}
那么這里就會(huì)發(fā)生溢出了她肯,非法訪問(wèn)了iData[0]前面的內(nèi)存,這段代碼也是無(wú)限循環(huán)康嘉,這個(gè)例子有點(diǎn)特殊籽前。
截?cái)啵罕热?2的int,賦值給16位的int16_t肄梨,那么就取低16位挠锥,這里截?cái)嗪蟮奈荒J讲蛔儯还苜x值給有符號(hào)的還是無(wú)符號(hào)的16位的類型粱侣,只是告訴編譯器如何解釋這些位模式蓖宦。
這里列舉無(wú)符號(hào)的截?cái)啵?/p>
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]);
}
這段代碼的意思是以兩個(gè)串的長(zhǎng)度加1尔店,開辟總的內(nèi)存空間主慰,然后拷貝兩個(gè)串至pBuff中,外加個(gè)空白符結(jié)尾该肴。這里的問(wèn)題是當(dāng)strlen(argv[1]) + strlen(argv[2])的和超過(guò)unsigned short int所能表示的最大值時(shí)藐不,發(fā)生截?cái)啵詈骾Len的值是小于min(strlen(argv[1], strlen(argv[2])))的涎嚼,那么在拷貝時(shí)就會(huì)發(fā)生緩沖區(qū)溢出挑秉。
再比如大學(xué)寫的有問(wèn)題的二分查找,有這么一行:
int iMid = (iLeft + iRight) / 2立哑;而這可能因?yàn)橐昳Left和iRight的類型而定,如果都為無(wú)符號(hào)類型铛绰,那么可能兩個(gè)數(shù)的和導(dǎo)致回繞捂掰,使得(iLeft + iRight)/2后的值小于min(iLeft若皱,iRight),那么就取不到[iLeft尘颓,iRight]索引處的數(shù)據(jù)了走触;正確的寫法應(yīng)該是:int iMid = iLeft + (iRight-iLeft) / 2;如果為有符號(hào)疤苹,可能導(dǎo)致溢出互广,兩個(gè)數(shù)的和為負(fù)數(shù)了,導(dǎo)致引用錯(cuò)誤的內(nèi)存地址卧土;
有/無(wú)符號(hào)的隱式轉(zhuǎn)換:如把無(wú)符號(hào)賦值給有符號(hào)惫皱,有符號(hào)賦值給無(wú)符號(hào),假設(shè)類型大小一致的情況下[大小不一致需要擴(kuò)展]尤莺,轉(zhuǎn)換是位模式不變旅敷,變的是解釋這些位的方式颤霎,值可能會(huì)改變媳谁。比如:
求和:
int computeSum(int iArray[],unsigned int iLen)
{
?????? int iSum = 0;
for (int i = 0; i <= iLen -1; ++i)
?????? {
???????????? //TODO
?????? }
????? return iSum;
}
如果這樣調(diào)用computeSum(***, 0)友酱,因?yàn)閕Len為無(wú)符號(hào)晴音,那么0-1為-1,有符號(hào)轉(zhuǎn)換成無(wú)符號(hào)為INT32_MAX缔杉,就出現(xiàn)了非法內(nèi)存訪問(wèn)锤躁;
其他情況:如沒(méi)有完全檢測(cè)邊界值:
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不小心是負(fù)數(shù)的話,代碼塊pValue[iPos]被編譯器轉(zhuǎn)換為如下形式:((char*)pValue + sizeof(int) * iPos)或详,那么寫入就會(huì)發(fā)生在pValue地址前面而引起一些問(wèn)題系羞;
像很多C字符串庫(kù)函數(shù),比如strcpy霸琴,sprintf等都沒(méi)有控制要操作的字節(jié)數(shù)椒振,可能會(huì)導(dǎo)致多拷貝字節(jié),而緩沖區(qū)溢出往往導(dǎo)致棧溢出沈贝,覆蓋重要的ebp和函數(shù)返回地址杠人,和其他變量,破壞棧宋下。
下篇會(huì)介紹下網(wǎng)絡(luò)編程方面的知識(shí)嗡善。