--[ 1.0 目錄
在這篇文章中我將會講述2種由于不安全編程引發(fā)的問題,導(dǎo)致一些惡意的用戶修改受影響的進(jìn)程
改變程序執(zhí)行流程.這2種類型的問題都是由于某一程序變量包含一個不可預(yù)料的值,因此這種類型
的問題不同于那些程序內(nèi)存被改寫的問題,比如:緩沖區(qū)溢出,格式化溢出.所有文章中給出的程序例
子都是用C語言編寫,所以需要讀者熟悉C語言.一些整數(shù)在內(nèi)存中存儲方法的知識也是會有所幫助
的,但不是全部.
----[ 1.1 什么是整數(shù)?
一個整數(shù), 在計(jì)算范圍內(nèi), 是一個變量可能是一個沒有小數(shù)部分的實(shí)數(shù)的.在系統(tǒng)上被編譯處理后,
整型和指針的尺寸一般是相同的(比如: 在32位的系統(tǒng)中,例如i386, 一個整數(shù)是32字節(jié)長,在64位的
系統(tǒng)中,例如SPARC,一個整數(shù)是64字節(jié)長).然而一些編譯器不使整型和指針為同樣尺寸 ,所以為了
通俗易懂,所有這里談到的例子是在32位的系統(tǒng)環(huán)境和32位的整數(shù),長度和指針.
整數(shù),如同所有的變量只是內(nèi)存的一個區(qū)域, 當(dāng)我們談及關(guān)于整數(shù),我們通常用10進(jìn)制來表示它們.
換句話說也就是人們經(jīng)常使用的一種編碼方式.計(jì)算機(jī)是基于數(shù)字的,不能直接處理10進(jìn)制,所以在
計(jì)算機(jī)中整數(shù)是以2進(jìn)制的方式存儲的.2進(jìn)制是另一種編碼方式,它們只有2個數(shù)字,1和0,與之不同
的10進(jìn)制是用10個數(shù)字來表示的.2進(jìn)制和10進(jìn)制,16進(jìn)制是廣泛的被使用的在電腦中能夠很簡單的
轉(zhuǎn)換2進(jìn)制和16進(jìn)制.
因?yàn)榇鎯ω?fù)數(shù)通常是必要的,這樣就需要一種機(jī)制僅僅用位來代表負(fù)數(shù),這種方法已經(jīng)完成了,通過
一個變量的最高為來決定正負(fù).如果最高位置1,這個變量就被解釋為負(fù)數(shù); 如果置0,這個變量就解釋
為整數(shù).這會導(dǎo)致一些混淆,這可以說明一些符號類型問題的概念,因?yàn)椴皇撬械淖兞慷际怯蟹?/p>
之分的,意思就是說并不是所有的類型都需要使用MSB來區(qū)分正負(fù).這些變量被定義為無符號,它只能
被賦予正數(shù)值.如果變量可正可負(fù),可以被稱做是無正負(fù)的,
整數(shù)溢出
。----[ 1.2 什么是整數(shù)溢出?
既然一個整數(shù)是一個固定的長度 (在本篇文章中使用32位),它能存儲的最大值是固定的,當(dāng)
嘗試去存儲一個大于這個固定的最大值時,將會導(dǎo)致一個整數(shù)溢出.在ISO C99的標(biāo)準(zhǔn)中講
到整數(shù)溢出將會導(dǎo)致"不能確定的行為",也就是說編譯器遵從了這個的規(guī)則,那就是完全忽略
溢出而退出這個程序.很多編譯器似乎忽略了這個溢出,結(jié)果是一個意想不到的錯誤值被存儲.
----[ 1.3 為什么那是危險(xiǎn)的?
整數(shù)溢出是不能被立即察覺,因此沒有辦法去用一個應(yīng)用程序來判斷先前計(jì)算的結(jié)果是否實(shí)
際上也是正確的.如果是用來計(jì)算緩沖區(qū)的大小或者計(jì)算數(shù)組索引排列的距離,這會變的危險(xiǎn).
當(dāng)然很多整數(shù)溢出并不是都是可利用的,因?yàn)椴]有直接改寫內(nèi)存,但是有時,他們可導(dǎo)致其他
類型的bugs,緩沖區(qū)溢出等.而且,整數(shù)溢出很難被發(fā)現(xiàn),因此,就算是審核過的代碼也會產(chǎn)生意外。
--[ 2.0 整數(shù)溢出
所以當(dāng)一個整數(shù)溢出已經(jīng)發(fā)生時會發(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."
譯者注:
大致的意思是:
涉及到無符號操作數(shù)計(jì)算的時候從不會溢出,因?yàn)榻Y(jié)果不能被無符號類型表示的時候,
就會對比該類型能表示的最大值還大的數(shù)求余.這樣就能用該結(jié)果來表示這種類型了.
NB:取模的運(yùn)算方法是2個數(shù)相除取余數(shù)的值
例子:
10 modulo 5 = 0
11 modulo 5 = 1
所以在減輕體重法里面,一個大數(shù)被和(最大的int值 + 1)取模,在C語言中,取模操作的符號是%.
這里有一個字節(jié)是多余的,可能是一個很好的象征性例子證明我們說的"導(dǎo)致不確定的行為".
我們有2個無符號的整數(shù),a和b, 2個數(shù)都是32位字節(jié)長,我們賦值給a 一個32為整數(shù)的最大值,
b被賦值為1.然后我們讓a和b相加然后存儲結(jié)果到第3個無符號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)嗟揭粋范圍,
通常用一個變量來存儲這個結(jié)果。這個經(jīng)常被稱作一個"環(huán)繞"(譯者注:類似成語中"否極泰來"的
意思,在這篇文章中我們理解為一個正數(shù)大到了極點(diǎn)就會變成負(fù)數(shù),負(fù)數(shù)小到了極點(diǎn)就會變成正數(shù)),
作為這里的結(jié)果,就出現(xiàn)了環(huán)繞到0.
----[ 2.1 Widthness 溢出
所以整數(shù)溢出是嘗試存儲一個大數(shù)到一個變量中,由于這個變量太小不足以存儲該大數(shù)導(dǎo)致的結(jié)
果.用最簡單的例子來說明這個問題,存儲一個大變量到一個小變量中去:
/* 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)我們把一個大的變量放入一個小變量的存儲區(qū)域中,結(jié)果是只保留小變量能夠存儲的位,而其他的位
都被截短了.
有必要在這里提及整數(shù)進(jìn)位.當(dāng)一個計(jì)算包含大小不同的操作數(shù)時,通過計(jì)算較小的操作數(shù)會被進(jìn)位到
較大的操作數(shù).如果結(jié)果將被存儲在一個較小的變量里,這個結(jié)果將會被重新減小,直到較小的操作數(shù)
可以容納.
這個例子里:
int i;
short s;
s = i;
這里計(jì)算結(jié)果將被賦給一個不同尺寸的操作數(shù),將發(fā)生的是變量s被提升為一個整型(32位),然后整數(shù)i的
內(nèi)容被拷貝給新的提升后的s,接著,提升后的變量內(nèi)容為了能存在s里面被降低回16位.如果超過了s能
存儲的最大值降位將導(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)迫一個變量包含錯誤的值,從而在后面的代碼中出現(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)造可能從來不會在真實(shí)的代碼里面出現(xiàn).這里只是一個簡單的例子,讓我們看看執(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ù)中得到一個整數(shù)值存放在整形變量i當(dāng)中,當(dāng)這個值被賦予unsigned short類型
的整數(shù)s,如果這個值大于unsigned short類型所能夠存儲的將被截短.(比如 這個值大于65535,
unsigned short存儲的范圍是0 - 65535),因次,可能繞過代碼中的[w1]部分的邊界檢測,導(dǎo)致緩沖
區(qū)溢出.只要使用一般的棧溢出技術(shù)就能夠溢出利用這個程序.
[1] [2] 下一頁