--[ 1.0 目錄
在這篇文章中我將會(huì)講述2種由于不安全編程引發(fā)的問題,導(dǎo)致一些惡意的用戶修改受影響的進(jìn)程
改變程序執(zhí)行流程.這2種類型的問題都是由于某一程序變量包含一個(gè)不可預(yù)料的值,因此這種類型
的問題不同于那些程序內(nèi)存被改寫的問題,比如:緩沖區(qū)溢出,格式化溢出.所有文章中給出的程序例
子都是用C語言編寫,所以需要讀者熟悉C語言.一些整數(shù)在內(nèi)存中存儲(chǔ)方法的知識(shí)也是會(huì)有所幫助
的,但不是全部.
----[ 1.1 什么是整數(shù)?
一個(gè)整數(shù), 在計(jì)算范圍內(nèi), 是一個(gè)變量可能是一個(gè)沒有小數(shù)部分的實(shí)數(shù)的.在系統(tǒng)上被編譯處理后,
整型和指針的尺寸一般是相同的(比如: 在32位的系統(tǒng)中,例如i386, 一個(gè)整數(shù)是32字節(jié)長,在64位的
系統(tǒng)中,例如SPARC,一個(gè)整數(shù)是64字節(jié)長).然而一些編譯器不使整型和指針為同樣尺寸 ,所以為了
通俗易懂,所有這里談到的例子是在32位的系統(tǒng)環(huán)境和32位的整數(shù),長度和指針.
整數(shù),如同所有的變量只是內(nèi)存的一個(gè)區(qū)域, 當(dāng)我們談及關(guān)于整數(shù),我們通常用10進(jìn)制來表示它們.
換句話說也就是人們經(jīng)常使用的一種編碼方式.計(jì)算機(jī)是基于數(shù)字的,不能直接處理10進(jìn)制,所以在
計(jì)算機(jī)中整數(shù)是以2進(jìn)制的方式存儲(chǔ)的.2進(jìn)制是另一種編碼方式,它們只有2個(gè)數(shù)字,1和0,與之不同
的10進(jìn)制是用10個(gè)數(shù)字來表示的.2進(jìn)制和10進(jìn)制,16進(jìn)制是廣泛的被使用的在電腦中能夠很簡單的
轉(zhuǎn)換2進(jìn)制和16進(jìn)制.
因?yàn)榇鎯?chǔ)負(fù)數(shù)通常是必要的,這樣就需要一種機(jī)制僅僅用位來代表負(fù)數(shù),這種方法已經(jīng)完成了,通過
一個(gè)變量的最高為來決定正負(fù).如果最高位置1,這個(gè)變量就被解釋為負(fù)數(shù); 如果置0,這個(gè)變量就解釋
為整數(shù).這會(huì)導(dǎo)致一些混淆,這可以說明一些符號(hào)類型問題的概念,因?yàn)椴皇撬械淖兞慷际怯蟹?hào)
之分的,意思就是說并不是所有的類型都需要使用MSB來區(qū)分正負(fù).這些變量被定義為無符號(hào),它只能
被賦予正數(shù)值.如果變量可正可負(fù),可以被稱做是無正負(fù)的,
整數(shù)溢出
。----[ 1.2 什么是整數(shù)溢出?
既然一個(gè)整數(shù)是一個(gè)固定的長度 (在本篇文章中使用32位),它能存儲(chǔ)的最大值是固定的,當(dāng)
嘗試去存儲(chǔ)一個(gè)大于這個(gè)固定的最大值時(shí),將會(huì)導(dǎo)致一個(gè)整數(shù)溢出.在ISO C99的標(biāo)準(zhǔn)中講
到整數(shù)溢出將會(huì)導(dǎo)致"不能確定的行為",也就是說編譯器遵從了這個(gè)的規(guī)則,那就是完全忽略
溢出而退出這個(gè)程序.很多編譯器似乎忽略了這個(gè)溢出,結(jié)果是一個(gè)意想不到的錯(cuò)誤值被存儲(chǔ).
----[ 1.3 為什么那是危險(xiǎn)的?
整數(shù)溢出是不能被立即察覺,因此沒有辦法去用一個(gè)應(yīng)用程序來判斷先前計(jì)算的結(jié)果是否實(shí)
際上也是正確的.如果是用來計(jì)算緩沖區(qū)的大小或者計(jì)算數(shù)組索引排列的距離,這會(huì)變的危險(xiǎn).
當(dāng)然很多整數(shù)溢出并不是都是可利用的,因?yàn)椴]有直接改寫內(nèi)存,但是有時(shí),他們可導(dǎo)致其他
類型的bugs,緩沖區(qū)溢出等.而且,整數(shù)溢出很難被發(fā)現(xiàn),因此,就算是審核過的代碼也會(huì)產(chǎn)生意外。
--[ 2.0 整數(shù)溢出
所以當(dāng)一個(gè)整數(shù)溢出已經(jīng)發(fā)生時(shí)會(huì)發(fā)生什么呢? ISO C99 是這樣說的:
"A computation involving unsigned operands can never overflow,
because a result that cannot be represented by the resulting unsigned
integer type is reduced modulo the number that is one greater than
the largest value that can be represented by the resulting type."
譯者注:
大致的意思是:
涉及到無符號(hào)操作數(shù)計(jì)算的時(shí)候從不會(huì)溢出,因?yàn)榻Y(jié)果不能被無符號(hào)類型表示的時(shí)候,
就會(huì)對(duì)比該類型能表示的最大值還大的數(shù)求余.這樣就能用該結(jié)果來表示這種類型了.
NB:取模的運(yùn)算方法是2個(gè)數(shù)相除取余數(shù)的值
例子:
10 modulo 5 = 0
11 modulo 5 = 1
所以在減輕體重法里面,一個(gè)大數(shù)被和(最大的int值 + 1)取模,在C語言中,取模操作的符號(hào)是%.
這里有一個(gè)字節(jié)是多余的,可能是一個(gè)很好的象征性例子證明我們說的"導(dǎo)致不確定的行為".
我們有2個(gè)無符號(hào)的整數(shù),a和b, 2個(gè)數(shù)都是32位字節(jié)長,我們賦值給a 一個(gè)32為整數(shù)的最大值,
b被賦值為1.然后我們讓a和b相加然后存儲(chǔ)結(jié)果到第3個(gè)無符號(hào)32位的整數(shù)r:
a = 0xffffffffff
b = 0x1
r = a + b
現(xiàn)在,當(dāng)相加起來的結(jié)果不能用32位的的值來表示,結(jié)果,為了和ISO 標(biāo)準(zhǔn)一致,被和0x100000000
取模.
r = (0xffffffff + 0x1) % 0x100000000
r = (0x100000000) % 0x100000000 = 0
減輕體重法的取模算法只能計(jì)算低于32位的計(jì)算結(jié)果,所以整數(shù)溢出導(dǎo)致結(jié)果被截?cái)嗟揭粋(gè)范圍,
通常用一個(gè)變量來存儲(chǔ)這個(gè)結(jié)果。這個(gè)經(jīng)常被稱作一個(gè)"環(huán)繞"(譯者注:類似成語中"否極泰來"的
意思,在這篇文章中我們理解為一個(gè)正數(shù)大到了極點(diǎn)就會(huì)變成負(fù)數(shù),負(fù)數(shù)小到了極點(diǎn)就會(huì)變成正數(shù)),
作為這里的結(jié)果,就出現(xiàn)了環(huán)繞到0.
----[ 2.1 Widthness 溢出
所以整數(shù)溢出是嘗試存儲(chǔ)一個(gè)大數(shù)到一個(gè)變量中,由于這個(gè)變量太小不足以存儲(chǔ)該大數(shù)導(dǎo)致的結(jié)
果.用最簡單的例子來說明這個(gè)問題,存儲(chǔ)一個(gè)大變量到一個(gè)小變量中去:
/* ex1.c - loss of precision */
#include
int main(void){
int l;
short s;
char c;
l = 0xdeadbeef;
s = l;
c = l;
printf("l = 0x%x (%d bits)\\n", l, sizeof(l) * 8);
printf("s = 0x%x (%d bits)\\n", s, sizeof(s) * 8);
printf("c = 0x%x (%d bits)\\n", c, sizeof(c) * 8);
return 0;
} /* EOF */
讓我們看看執(zhí)行結(jié)果
nova:signed {48} ./ex1
l = 0xdeadbeef (32 bits)
s = 0xffffbeef (16 bits)
c = 0xffffffef (8 bits)
當(dāng)我們把一個(gè)大的變量放入一個(gè)小變量的存儲(chǔ)區(qū)域中,結(jié)果是只保留小變量能夠存儲(chǔ)的位,而其他的位
都被截短了.
有必要在這里提及整數(shù)進(jìn)位.當(dāng)一個(gè)計(jì)算包含大小不同的操作數(shù)時(shí),通過計(jì)算較小的操作數(shù)會(huì)被進(jìn)位到
較大的操作數(shù).如果結(jié)果將被存儲(chǔ)在一個(gè)較小的變量里,這個(gè)結(jié)果將會(huì)被重新減小,直到較小的操作數(shù)
可以容納.
這個(gè)例子里:
int i;
short s;
s = i;
這里計(jì)算結(jié)果將被賦給一個(gè)不同尺寸的操作數(shù),將發(fā)生的是變量s被提升為一個(gè)整型(32位),然后整數(shù)i的
內(nèi)容被拷貝給新的提升后的s,接著,提升后的變量內(nèi)容為了能存在s里面被降低回16位.如果超過了s能
存儲(chǔ)的最大值降位將導(dǎo)致結(jié)果被截?cái)?.
------[ 2.1.1 Exploiting
整數(shù)溢出并不像普通的漏洞類型, 它們不允許直接的改寫內(nèi)存或者直接改變程序的控制流程.而是更加精巧.
程序的所有者面臨的事實(shí)是沒有辦法在進(jìn)程里面檢查計(jì)算發(fā)生后的結(jié)果,所以有可能計(jì)算結(jié)果和正確
結(jié)果之間有一定的偏差.就因?yàn)檫@樣,大多數(shù)的整數(shù)溢出不能被利用,即使這樣,在一些情況下,我們還是有可能
強(qiáng)迫一個(gè)變量包含錯(cuò)誤的值,從而在后面的代碼中出現(xiàn)問題.
由于這些漏洞的精巧,導(dǎo)致有大量的地方能被利用,所以我就不嘗試覆蓋到所有能被利用的環(huán)境.相反
,我將提供一些能被利用的情況,希望讀者能自己來探索.我將提供一些能被利用的情況的例子.
Example 1:
/* width1.c - exploiting a trivial widthness bug */
#include
#include
int main(int argc, char *argv[]){
unsigned short s;
int i;
char buf[80];
if(argc < 3){
return -1;
}
i = atoi(argv[1]);
s = i;
if(s >= 80){ /* [w1] */
printf("Oh no you don\'t!\\n");
return -1;
}
printf("s = %d\\n", s);
memcpy(buf, argv[2], i);
buf[i] = \'\\0\';
printf("%s\\n", buf);
return 0;
}
然而像這種構(gòu)造可能從來不會(huì)在真實(shí)的代碼里面出現(xiàn).這里只是一個(gè)簡單的例子,讓我們看看執(zhí)行后
的輸出:
nova:signed {100} ./width1 5 hello
s = 5
hello
nova:signed {101} ./width1 80 hello
Oh no you don\'t!
nova:signed {102} ./width1 65536 hello
s = 0
Segmentation fault (core dumped)
程序從命令行參數(shù)中得到一個(gè)整數(shù)值存放在整形變量i當(dāng)中,當(dāng)這個(gè)值被賦予unsigned short類型
的整數(shù)s,如果這個(gè)值大于unsigned short類型所能夠存儲(chǔ)的將被截短.(比如 這個(gè)值大于65535,
unsigned short存儲(chǔ)的范圍是0 - 65535),因次,可能繞過代碼中的[w1]部分的邊界檢測(cè),導(dǎo)致緩沖
區(qū)溢出.只要使用一般的棧溢出技術(shù)就能夠溢出利用這個(gè)程序.
[1] [2] 下一頁