?題 :? 【技術(shù)專(zhuān)題】軟件漏洞分析入門(mén)_2_初級(jí)棧溢出A_初識(shí)數(shù)組越界
作?者
:? failwest
時(shí)?間
:? 2007 - 12 - 13 , 10 : 27
鏈?接 :? http : //bbs.pediy.com/showthread.php?t=56479

2_ 初級(jí)棧溢出_A

To?be?the?apostrophe?which?changed?“Impossible”?into?“I’m?possible”
——?failwest

今夜月明星稀

本想來(lái)點(diǎn)大道理申明下研究思路啥的,看到大家的熱情期待,稍微調(diào)整一下講課的順序。從今天開(kāi)始,將用
3 4 次給大家做一下棧溢出的掃盲。

棧溢出的文章網(wǎng)上還是有不少的(其實(shí)優(yōu)秀的也就兩三篇),原理也不難,讀過(guò)基本上就能夠明白是怎么回事。本次講解將主要集中在動(dòng)手調(diào)試方面,更加著重實(shí)踐。

經(jīng)過(guò)這
3 4 次的棧溢出掃盲,我們的目標(biāo)是:

領(lǐng)會(huì)棧溢出攻擊的基本原理
能夠動(dòng)手調(diào)試簡(jiǎn)易的棧溢出漏洞程序,并能夠利用漏洞執(zhí)行任意代碼(最簡(jiǎn)易的shellcode)

最主要的目的其實(shí)是激發(fā)大家的學(xué)習(xí)興趣——寡人求學(xué)若干年,深知沒(méi)有興趣是決計(jì)沒(méi)有辦法學(xué)出名堂來(lái)的。

本節(jié)課的基本功要求是:會(huì)C語(yǔ)言就行(大概能編水仙花數(shù)的水平)

我會(huì)盡量用最最傻瓜的文字來(lái)闡述這些內(nèi)存中的二進(jìn)制概念。為了避免一開(kāi)始涉及太多枯燥的基礎(chǔ)知識(shí)讓您失去了興趣,我并不提倡從匯編和寄存器開(kāi)始,也不想用函數(shù)和棧開(kāi)頭。我準(zhǔn)備用一個(gè)自己設(shè)計(jì)的小例子開(kāi)始講解,之后我會(huì)以這個(gè)例子為基礎(chǔ),逐步加碼,讓它變得越來(lái)越像真實(shí)的漏洞攻擊。

您需要的就是每天晚上看一篇帖子,然后用十分鐘時(shí)間照貓畫(huà)虎的在編譯器里把例子跟著走一遍,堅(jiān)持一個(gè)星期之后您就會(huì)發(fā)現(xiàn)世界真奇妙了。

不懂匯編不是拒絕這門(mén)迷人技術(shù)的理由——今天的課程就不涉及匯編——并且以后遇到會(huì)隨時(shí)講解滴

所以如果你懂C語(yǔ)言的話(huà),不許不學(xué),不許說(shuō)學(xué)不會(huì),也不許說(shuō)難,哈哈

開(kāi)場(chǎng)白多說(shuō)了幾句,下面是正題。今天我們來(lái)一起研究一段暴簡(jiǎn)單無(wú)比的C語(yǔ)言小程序,看看編程中如果不小心出現(xiàn)數(shù)組越界將會(huì)出現(xiàn)哪些問(wèn)題,直到這個(gè)單元結(jié)束您能夠用這些數(shù)組越界漏洞控制遠(yuǎn)程主機(jī)。


#include? < stdio . h >
#define? PASSWORD? "1234567"
int? verify_password? ( char? * password )
{
??
int? authenticated ;
??
char? buffer [ 8 ];?? //?add?local?buff?to?be?overflowed
??
authenticated = strcmp ( password , PASSWORD );
??
strcpy ( buffer , password );?? //over?flowed?here!??
??
return? authenticated ;
}
main ()
{
??
int? valid_flag = 0 ;
??
char? password [ 1024 ];
??
while ( 1 )
??{
????
printf ( "please?input?password:???????" );
????
scanf ( "%s" , password );
????
valid_flag? =? verify_password ( password );
????
if ( valid_flag )
????{
??????
printf ( "incorrect?password!\n\n" );
????}
????
else
????
{
??????
printf ( "Congratulation!?You?have?passed?the?verification!\n" );
??????
break ;
????}
??}
}


對(duì)于這幾行亂簡(jiǎn)單無(wú)比的程序,我還是稍作解釋。
程序運(yùn)行后將提示輸入密碼
用戶(hù)輸入的密碼將被程序與宏定義中的“
1234567 ”比較
密碼錯(cuò)誤,提示驗(yàn)證錯(cuò)誤,并提示用戶(hù)重新輸入
密碼正確,提示正確,程序退出(真夠傻瓜的語(yǔ)言)

所謂的漏洞在于verify_password()函數(shù)中的strcpy
( buffer , password ) 調(diào)用。
由于程序?qū)延脩?hù)輸入的字符串原封不動(dòng)的復(fù)制到verify_password函數(shù)的局部數(shù)組
char? buffer [ 8 ] 中,但用戶(hù)的字符串可能大于 8 個(gè)字符。當(dāng)用戶(hù)輸入大于 8 個(gè)字符的緩沖區(qū)尺寸時(shí),緩沖區(qū)就會(huì)被撐暴——即所謂的緩沖區(qū)溢出漏洞。

緩沖區(qū)給撐暴了又怎么樣?大不了程序崩潰么,有什么了不起!

此話(huà)不然,如果只是導(dǎo)致程序崩潰就不用我在這里浪費(fèi)大家時(shí)間了。根據(jù)緩沖區(qū)溢出發(fā)生的具體情況,巧妙的填充緩沖區(qū)不但可以避免崩潰,還能影響到程序的執(zhí)行流程,甚至讓程序去執(zhí)行緩沖區(qū)里的代碼。

今天我們先玩一個(gè)最簡(jiǎn)單的。函數(shù)verify_password()里邊申請(qǐng)了兩個(gè)局部變量
int? authenticated ;
char? buffer [ 8 ];?

當(dāng)verify_password被調(diào)用時(shí),系統(tǒng)會(huì)給它分配一片連續(xù)的內(nèi)存空間,這兩個(gè)變量就分布在那里(實(shí)際上就叫函數(shù)棧幀,我們后面會(huì)詳細(xì)講解),如下圖



變量和變量緊緊的挨著。為什么緊挨著?當(dāng)然不是他倆關(guān)系好,省空間啊,好傻瓜的問(wèn)題,笑:)

用戶(hù)輸入的字符串將拷貝進(jìn)buffer
[ 8 ] ,從示意圖中可以看到,如果我們輸入的字符超過(guò) 7 個(gè)(注意有串截?cái)喾菜阋粋€(gè)),那么超出的部分將破壞掉與它緊鄰著的authenticated變量的內(nèi)容!

在復(fù)習(xí)一下程序,authenticated變量實(shí)際上是一個(gè)標(biāo)志變量,其值將決定著程序進(jìn)入錯(cuò)誤重輸?shù)牧鞒蹋ǚ?/font> 0 )還是密碼正確的流程( 0 )。

下面是比較有趣的部分:
當(dāng)密碼不是宏定義的
1234567 時(shí),字符串比較將返回 1 - 1 (這里只討論 1 ,結(jié)尾的時(shí)候會(huì)談下 - 1 的情況)
由于intel是所謂的大頂機(jī),其實(shí)就是內(nèi)存中的數(shù)據(jù)按照
4 字節(jié)(DWORD)逆序存儲(chǔ),所以authenticated為 1 時(shí),內(nèi)存中存的是 0x01000000
如果我們輸入包含 8 個(gè)字符的錯(cuò)誤密碼,如“qqqqqqqq”,那么字符串截?cái)喾?/font> 0x00 將寫(xiě)入authenticated變量
這溢出數(shù)組的一個(gè)字節(jié)
0x00 將恰好把逆序存放的authenticated變量改為 0x00000000
函數(shù)返回,main函數(shù)中一看authenticated是
0 ,就會(huì)歡天喜地的告訴你,oh?yeah?密碼正確!這樣,我們就用錯(cuò)誤的密碼得到了正確密碼的運(yùn)行效果

下面用
5 分鐘實(shí)驗(yàn)一下這里的分析吧。將代碼用VC6 .0 編譯鏈接,生成可執(zhí)行文件。注意,是VC6 .0 或者更早的編譯器,不是 7.0 ,不是 8.0 ,不是 .? net,不是VS2003,不是VS2005。為什么,其實(shí)不是高級(jí)的編譯器不能搞,是比較難搞,它們有特殊的GS編譯選項(xiàng),為了不給咱們掃盲班增加負(fù)擔(dān),所以暫時(shí)飄過(guò),用 6.0

??按照程序的設(shè)計(jì)思路,只有輸入了正確的密碼”
1234567 ”之后才能通過(guò)驗(yàn)證。程序運(yùn)行情況如下:

?
????
??要是輸入幾十個(gè)字符的長(zhǎng)串,應(yīng)該會(huì)崩潰。多少個(gè)字符會(huì)崩潰?為什么?賣(mài)個(gè)關(guān)子,下節(jié)課慢慢講。現(xiàn)在來(lái)個(gè)
8 個(gè)字符的密碼試下:?
?


注意為什么
01234567 不行?因?yàn)樽址笮〉谋容^是按字典序來(lái)的,所以這個(gè)串小于“ 1234567 ”,authenticated的值是 - 1 ,在內(nèi)存里將按照補(bǔ)碼存負(fù)數(shù),所以實(shí)際村的不是 0x01000000 而是 0xffffffff 。那么字符串截?cái)嗪蠓?/font> 0x00 淹沒(méi)后,變成 0x00ffffff ,還是非 0 ,所以沒(méi)有進(jìn)入正確分支。

總結(jié)一下,由于編程的粗心,有可能造成程序中出現(xiàn)緩沖區(qū)溢出的缺陷。

這種缺陷大多數(shù)情況下會(huì)導(dǎo)致崩潰,但是結(jié)合內(nèi)存中的具體情況,如果精心構(gòu)造緩沖區(qū)的話(huà),是有可能讓程序作出設(shè)計(jì)人員根本意向不到的事情的

本節(jié)只是用一個(gè)字節(jié)淹沒(méi)了鄰接變量,導(dǎo)致了程序進(jìn)入密碼正確的處理流程,使設(shè)計(jì)的驗(yàn)證功能失效。

其實(shí)作為cracker,大家可能會(huì)說(shuō)這有什么難的,我可以說(shuō)出一堆方法做到這一點(diǎn):
直接查看PE,找出宏定義中的密碼值,得到正確密碼
反匯編PE,找到爆破點(diǎn)
, JZ?JNZ的或者TEST?EAX , EAX變XOR?EAX , EAX的在分支處改它一個(gè)字節(jié)
……

但是今天介紹的這種方法與crack的方法有一個(gè)非常重要的區(qū)別,非常非常重要~~

就是~~~我們是在程序允許的情況下,用合法的輸入數(shù)據(jù)(對(duì)于程序來(lái)說(shuō))得到了非法的執(zhí)行效果(對(duì)于程序員來(lái)說(shuō))——這是hack與crack之間的一個(gè)重要區(qū)別,因?yàn)榇蠖鄶?shù)情況下hack是沒(méi)有辦法直接修改PE的,他們只能通過(guò)影響輸入來(lái)影響程序的流程,這將使hack受到很多限制,從某種程度上講也更加困難。這個(gè)區(qū)別將在后面幾講中得到深化,并被我不斷強(qiáng)調(diào)。

好了,今天的掃盲課程暫時(shí)結(jié)束,作為棧溢出的開(kāi)場(chǎng)白,希望這個(gè)自制的漏洞程序能夠給您帶來(lái)一點(diǎn)點(diǎn)幫助。

順便預(yù)告一下下一講的內(nèi)容:

初級(jí)溢出B:將講述函數(shù)調(diào)用時(shí)怎樣和系統(tǒng)棧配合的,然后在本講的基礎(chǔ)上淹沒(méi)棧幀寄存器,直接改變程序流程

初級(jí)溢出C:手把手的教你寫(xiě)一段超簡(jiǎn)單的shellcode(可執(zhí)行的機(jī)器代碼),并把這段代碼做為密碼輸入,最后引導(dǎo)程序跳去執(zhí)行這段代碼

下次再見(jiàn):)