我是大家的老朋友CPU阿甘, 每天你一開(kāi)機(jī),我就忙得不亦樂(lè)乎,從內(nèi)存中讀取一條條的指令,挨個(gè)執(zhí)行。
最早的時(shí)候我認(rèn)為程序都是順序執(zhí)行的,后來(lái)發(fā)現(xiàn)并不是這樣,經(jīng)常會(huì)出現(xiàn)一條跳轉(zhuǎn)指令,讓我到另外一個(gè)內(nèi)存地址處去下一條指令去執(zhí)行。
時(shí)間久了我就明白這是人類代碼中的if ... else ,或者for ,while等循環(huán)導(dǎo)致的。
這樣跳來(lái)跳去,讓我覺(jué)得有點(diǎn)頭暈,不過(guò)沒(méi)有辦法,這是人類做出的規(guī)定。
后來(lái)我發(fā)現(xiàn),有些指令經(jīng)常會(huì)出現(xiàn)重復(fù),尤其是下面這幾個(gè):
pushl %ebp
movl %esp %ebp
call xxxx
ret
正當(dāng)我疑惑的時(shí)候,內(nèi)存炫耀地說(shuō):這些指令是為了函數(shù)調(diào)用,建立棧幀所所必需的啊。
“函數(shù)調(diào)用?這是什么鬼?”
“函數(shù)調(diào)用你都不知道? 我告訴你吧,現(xiàn)在的計(jì)算機(jī)語(yǔ)言,甭管你是面向?qū)ο筮€是函數(shù)式、動(dòng)態(tài)還是靜態(tài)、解釋還是編譯,只要想在我們馮諾依曼體系結(jié)構(gòu)下運(yùn)行,最終都得變成順序、循環(huán)、分支,以及函數(shù)調(diào)用!”
內(nèi)存說(shuō)著給我舉了一個(gè)例子:
這個(gè)例子非常簡(jiǎn)單,一看就明白。
“但是棧幀是什么?”
“阿甘你知道棧是什么意思吧?”
“不就是一個(gè)先進(jìn)后出的數(shù)據(jù)結(jié)構(gòu)嗎?”
“對(duì),通俗來(lái)說(shuō):一個(gè)棧幀就是這個(gè)棧中的一個(gè)元素,表示了一個(gè)函數(shù)在運(yùn)行時(shí)的結(jié)構(gòu)。” 內(nèi)存繼續(xù)給我科普:
“你這種畫(huà)法好古怪,怎么倒過(guò)來(lái)了,棧底在上方,棧頂反而在下方!”
“這也是人類規(guī)定的,一個(gè)進(jìn)程的虛擬內(nèi)存中有個(gè)區(qū)域,就是棧,這個(gè)棧就是從高地址向低地址發(fā)展的啊?!?/p>
“奧,原來(lái)我執(zhí)行的代碼在一個(gè)叫做代碼區(qū)的地方存放著啊,執(zhí)行的時(shí)候會(huì)操作你的棧,對(duì)不對(duì)?”
“沒(méi)錯(cuò),我再給你看看那個(gè)棧幀的內(nèi)部結(jié)構(gòu)吧!”
這張圖看起來(lái)很復(fù)雜,但是和代碼一對(duì)應(yīng),還是比較清楚的。
我心中模擬了一下這個(gè)執(zhí)行過(guò)程,hello()函數(shù)正在被執(zhí)行,當(dāng)要調(diào)用add函數(shù)的時(shí)候,需要準(zhǔn)備參數(shù),即x = 10, y=20 。
還要記錄下返回地址,即printf(....)這個(gè)指令在內(nèi)存的地址。當(dāng)add函數(shù)調(diào)用完成以后,就可以返回到這里執(zhí)行了。
真正開(kāi)始執(zhí)行add函數(shù)的時(shí)候,也需要給它建立一個(gè)棧幀(其中要記錄下上個(gè)函數(shù)棧幀的開(kāi)始地址),還有這個(gè)函數(shù)的參數(shù),在棧幀也會(huì)分配內(nèi)存空間,例如sum, buf等。
等到執(zhí)行結(jié)束,add函數(shù)的棧幀就廢棄了(相當(dāng)于從棧中彈出),找到返回地址,繼續(xù)執(zhí)行printf指令。
hello函數(shù)執(zhí)行完畢,也會(huì)廢棄掉,回到上一個(gè)函數(shù)的棧幀,繼續(xù)執(zhí)行,如此持續(xù)下去....
我對(duì)內(nèi)存說(shuō):“明白了,我已經(jīng)迫不及待地想執(zhí)行一下這個(gè)函數(shù),看看效果了?!?/p>
內(nèi)存說(shuō):“真的明白了?正好,操作系統(tǒng)老大已經(jīng)發(fā)出指令,讓我們運(yùn)行了,開(kāi)始吧!”
建立hello函數(shù)的棧幀,調(diào)用add函數(shù),建立add棧幀,執(zhí)行add函數(shù)的代碼, 一切都很順利。
add函數(shù)中調(diào)用了scanf ,要求用戶輸入一些數(shù)據(jù),人類是超級(jí)慢的,我耐心等待。
用戶輸入了8個(gè)字符A,我把他們都放到了buf所在的內(nèi)存中:
但是人類還在輸入,接下里是一些很奇怪的數(shù)據(jù),其長(zhǎng)度遠(yuǎn)遠(yuǎn)超過(guò)了char buf[8]中的8個(gè)字節(jié)。
可是我還得把數(shù)據(jù)給放到內(nèi)存中啊,于是函數(shù)棧幀就變成了這個(gè)樣子。
(注:用戶輸入的數(shù)據(jù)是從低地址向高地址存放的。)
我覺(jué)得特別古怪的是,這個(gè)返回地址也被沖掉了,被改寫(xiě)了。
這個(gè)用戶到底要干啥?
add函數(shù)執(zhí)行完畢,要返回到hello函數(shù)了, 我明明知道返回地址已經(jīng)被改掉, 可是我沒(méi)有選擇,還得把那個(gè)新的(用戶輸入的)返回地址給取出來(lái), 老老實(shí)實(shí)地去那個(gè)地址取出下一條指令去執(zhí)行。
完了,這根本就不是原來(lái)的prinf函數(shù),而是一段惡意代碼的入口!
與此同時(shí)....
黑客三兄弟中的老三大叫: 大哥二哥,我的這次緩沖區(qū)溢出攻擊實(shí)驗(yàn)成功了!
“不錯(cuò)啊,你是怎么搞的?” 老大問(wèn)道。
“正如二哥說(shuō)的,那個(gè)scanf函數(shù)沒(méi)有邊界檢查,我成功地把代碼注入到了棧幀中,并且修改了返回地址!于是程序就跳到我指定的地方執(zhí)行了?!?/p>
聯(lián)系客服