淺談C#緩沖區(qū)溢出的秘密
開(kāi)場(chǎng)白
各位朋友們,當(dāng)你們看到網(wǎng)上傳播關(guān)于微軟windows、IE對(duì)黑客利用“緩沖區(qū)溢出”、0day漏洞攻擊的新聞,是否有過(guò)自己也想試試身手,可惜無(wú)從下手的感慨?本文將完全使用C#語(yǔ)言,探索那些不為人知的秘密。
1.本文講述在C#中利用堆棧緩沖區(qū)溢出動(dòng)態(tài)修改內(nèi)存,達(dá)到改變應(yīng)用程序執(zhí)行流程的目的。
2.如果你是高手,請(qǐng)指出本文的不足。
3.為了讓本文通俗易懂,代碼將極盡精簡(jiǎn)。
現(xiàn)在開(kāi)始
我們知道,當(dāng)數(shù)組下標(biāo)越界時(shí),.NET會(huì)自動(dòng)拋出StackOverflowException,這樣便讓我們可以安全的讀寫(xiě)內(nèi)存,那么我們有沒(méi)有逾越這個(gè)自動(dòng)檢測(cè)的屏障,達(dá)到我們非常操作的目的呢?答案是有的,而且我們可以修改一些關(guān)鍵變量如if、switch的判斷值,for循環(huán)變量i值,甚至方法返回值,當(dāng)然理論上還可以注入代碼、轉(zhuǎn)移代碼執(zhí)行區(qū)塊,前提是必須在unsafe代碼里。
方法在被調(diào)用時(shí),系統(tǒng)會(huì)進(jìn)行以下幾項(xiàng)操作:將該方法入棧、參數(shù)入棧、返回地址入棧、控制代碼區(qū)入棧(EIP入棧)。我們想要訪問(wèn)方法的棧內(nèi)地址,常規(guī)的托管代碼是不行的,只能使用unsafe代碼,但也并不是說(shuō)你非要精通C/C++語(yǔ)言和指針操作,本文的例子都非常簡(jiǎn)單,完全可以將指針就認(rèn)為是簡(jiǎn)版C#數(shù)組。
改變臨時(shí)變量的值
先給出一段代碼,然后再詳細(xì)解釋原理。
- static unsafe void Main(string[] args)
- {
- //在棧上申請(qǐng)一個(gè)只能保存一個(gè)int32的內(nèi)存段
- int* p = stackalloc int[1];
- for (var i = 0; i < 30; i++)
- {
- System.Threading.Thread.Sleep(200);
- Console.WriteLine("{0}\n", i);
- p[i] = 0;
- }
- Console.ReadLine();
- }
這是一個(gè)既簡(jiǎn)單,但是對(duì)于從沒(méi)有嘗試這樣寫(xiě)過(guò)代碼的開(kāi)發(fā)者來(lái)說(shuō),又頗耐人尋味,C#(包括C/C++)不會(huì)去檢查指針p的偏移量是否越界,那么這段代碼將會(huì)順利編譯并運(yùn)行,那么for循環(huán)會(huì)順利執(zhí)行30次嗎?還是......
呵呵..大家稍等...
結(jié)論是,這將是一個(gè)死循環(huán),因?yàn)閜不斷的遞增1偏移,并將附近的內(nèi)存的值全改為0,而局部變量i是靠p最近的變量,所有當(dāng)p[i]的偏移地址等于i的地址時(shí),代碼p[i]=0就等價(jià)于i=0,實(shí)際上我在測(cè)試中i=6的時(shí)候i的值就被覆蓋為0了,我在代碼中添加了Thread.Sleep(200)和Console.WriteLine("{0}\n", i)就是讓大家能更直觀的看到程序的執(zhí)行過(guò)程,當(dāng)然這里也可以改為p[i]=1,p[i]=2等數(shù)字
搜索內(nèi)存值并修改
還是先給出代碼
- static unsafe void Main(string[] args)
- {
- Console.WriteLine(Change_Result());
- Console.ReadLine();
- }
- static unsafe int Change_Result()
- {
- int i = 0;
- //變量result,默認(rèn)的返回值
- int result = 123;
- //申請(qǐng)一段棧內(nèi)存,大小可隨意設(shè)置
- int* p = stackalloc int[1];
- //從當(dāng)前棧地址開(kāi)始向下查找與函數(shù)返回值相匹配的地址,一旦匹配則修改為10000
- while (true)
- {
- if (p[++i] == 123)
- {
- p[i] = 10000;
- break;
- }
- };
- return result;
- }
變量result作為方法的返回值默認(rèn)為123,并且沒(méi)有任何顯式修改其值的代碼,關(guān)鍵在這里
- while (true)
- {
- if (p[++i] == 123)
- {
- p[i] = 10000;
- break;
- }
- }
這段代碼找到值為123的內(nèi)存地址(也就可能是變量result的地址),然后將其值修改為10000,當(dāng)然,函數(shù)返回值就肯定不會(huì)再是原先的123咯
這就是經(jīng)典的StackOverFlow的兩個(gè)例子,希望通俗易懂能讓大家所接受,另外緩沖區(qū)溢出并不只是改變內(nèi)存的值,在高手的手里,他還可以執(zhí)行任意代碼,因?yàn)榉椒▓?zhí)行的時(shí)候總會(huì)有一個(gè)指針指向方法即將執(zhí)行的下一條指令,如果控制了這個(gè)指針,就控制了進(jìn)程。
原文鏈接:http://www.cnblogs.com/bidaas2002/archive/2010/12/27/1917562.html
【編輯推薦】