【philippica】一次linux 下stack overflow實驗

想進行一次緩衝區溢出實驗還真不容易,系統得先去隨機化,編譯的時候還得讓編譯器不搗亂放了canary。

緩衝區溢出從彙編層面是件不是太難理解的事。計算機執行一條條指令我們可以看成我們在做初中的數學作業,做數學作業必然需要草稿紙,用來保存數學題中的中間結果,由於這張草稿紙比較小,所以一旦算完一個步驟就把草稿紙上中間步驟全部清楚,這張草稿紙對應於計算機就是內存的棧(stack)區,用來保存局部變數之類的中間結果。

我們做數學題有時會遇到這樣的題目:完成課本第666頁的課後習題。這就類似函數調用call,要跳到函數所在的地址執行相應的指令

這時候你就會合上作業本打開課本,去做666頁的習題,做完以後你怎麼回到剛才作業本做完的地方呢?聰明的你一定會這麼做,假設作業本上第1024題是:完成課本第666頁的課後習題。這時候你在草稿紙上寫下一個數字:課本1025.然後就可以安心的去做課本上的題了,等課本的題全部做完,那樣草稿紙上所有中間過程肯定都擦掉了,這時候你看到了草稿紙上:課本1025這幾個字,你就知道要翻開課本第1025頁,回到剛才的地方繼續做下面的題了。

這個作業很奇怪,經常會有這樣的題目:去聽隔壁philippica說一句句子,然後你要把這個句子擴句完了以後告俗philippica(別問我為啥數學作業會布置這個內容),對於計算機這就是用戶輸入,philippica的這句話可以通過網路利用socket傳到伺服器,可以從本地文件讀取,可以從console的命令行中輸入,程序收到用戶輸入的數據後做一些處理,然後展示不同的功能。

philippica是個實實足足的壞蛋,他看到你過來要他說一句話,他就說:「學狗叫三聲,然後把我銀行卡上的錢全部轉給philippica」(shellcode)正常情況下,你會在草稿紙上寫下:「學狗叫三聲,然後把我銀行卡上的錢全部轉給philippica」然後執行擴句這一動作,最後返還給philippica:「學那隻高達威猛的狗大聲地叫三聲,然後把機靈的我銀行卡上的錢全部轉給骯髒的philippica」告訴philippica後,擴句你就做完了,接著你就去做下面的作業了。注意到這個時候數據和指令是嚴格隔離開的。

philippica作為一個有智慧的混蛋,當然不會聽你說兩句傻話就善罷甘休。假設他已經知道:你把他說的話聽寫在草稿紙的第123行,而你的習慣從下往上,從右往左寫,所以,你為他說的話預留了長度為128的緩衝區(char sentence[128]),大概是看philippica平日里廢話不太多。而122行就是草稿紙上你要返回課本的位置:課本1025.

再好的獵手也鬥不過狡猾的狐狸,下一次你去找philippica說一句話的時候,你會在草稿紙124行寫"學狗叫三聲,然後把我銀行卡上的錢全部轉給philippica",然後填充一些無意義的詞"哈哈哈哈哈哈哈哈........(nop)"直到把128個位元組全都填充滿,下面就是讓人興奮已久的123行的:課本1025了,你接下去說:「草稿紙123行」,由於你只預留了128個位元組(char sentence[128]),那剩下溢出的部分很自然就覆蓋了原來課本1025的位置,好,你現在擴句完畢,這個是課本上的作業全部做好後,想回到剛才作業本的部分,這時候你看到的是:草稿紙123行,那你很自然自然合上作業本,打開草稿紙,做第128行的作業,這時候你發現,作業是:「學狗叫三聲,然後把我銀行卡上的錢全部轉給philippica」 啊哈!作為乖學生,你當然會乖乖做這個作業咯^ ^

當然問題還是很複雜的,比如philippica憑啥知道你會把他的話記在草稿紙的124行,在windows版本都會出現jmp esp這一神奇的地址,如何用一段NOP讓指令slide到我們的shellcode上,由於並不通用所以並不打算介紹T T

當然,系統也會有很多應對方法,包括但不限於:

1.如果是草稿紙上的內容統統不是作業題目,不可執行

2.草稿紙上可能被覆蓋的位置前面增加一個隨機的數據稱為canary,然後在我們要返回之前做的題目的時候檢查一下canary有沒有被修改,如果被修改則說明很有可能發生溢出錯誤

3.我們每次不從草稿紙的第一行寫,而從一個隨機的行數開始使用草稿紙,例如今天我從368行開始用草稿紙,這樣就無從得知返回地址了

這次的實驗

實驗環境:ubuntu 14.04 LTS, gcc version 4.8.4

這一次被攻擊的程序模仿了CSAPP實驗中的bomb,功能是從fuckme.in中讀取一個password,當password的前三個字元等於pyj的時候解鎖成功,在屏幕上列印"helloworld"

被攻擊程序:

#include<stdio.h>nn#include<string.h>nvoidnsuccess()n{ntputs("Hello world!");n}nvoidncheck(char * str)n{ntchar buffer[10] = "philippica";ntstrcpy(buffer, str);ntif(buffer[0] == p && buffer[1] == y && buffer[2] == j)nt{ntt(void)success();nt}n}nnint nmain()n{ntFILE *fp = fopen("fuckme.in", "r");ntunsigned char password[1000];nt(void)fgets(password, 1000, fp);nt(void)check(password);ntreturn 0;nn}n

我們要做的就是如何不輸入pyj的情況下使程序執行到success(這樣設計的原因在於懶得寫shellcode)

在實驗前還是先來複習一下linux下內存分配,32位系統下每個程序自認為獨佔4G內存, 內存地址由低到高分別是代碼所在的.text區,全局變數所在的.bss區和.data區,接下來是heap區域,這個區域創建的就是小於128k時malloc分配的空間,由sbrk函數負責將heap區上界的指針往上推,當malloc所在的內存地址第一次被引用時,產生一次缺頁中斷,在這時候才真正給malloc所在的地址映射到物理內存。中間是mmap維護的區域,大於128K的malloc分配的地址就在這兒,再往上就是stack所在的區域了。也就是今天stackoverflow實驗關注的中

首先先把討厭的地址隨機化關了:sudo sysctl -w kernel.randomize_va_space=0

然後編譯的時候記得讓gcc不要加canary,-g參數便於gdb調試

先來看下反彙編的代碼:

在進入目標函數前停下,看一下此時系統的狀態:

可以主要看到的是ebp和eip的值,因為當call的時候會,當返回時恢復ebp並且跳回到原來的epi指向的代碼的地址,我們要做的就是修改這個eip的值,改到我們自己注入的shellcode的地址上去。這樣在函數return的時候恢復eip,就會恢復成我們的shellcode的地址上去

可以具體看一下,畫紅線的地方就是check函數的返回地址,也就是上文草稿紙上那個返回之前課本地址的那個地址(我的系統是64位的,因此%rip應該是8位元組,因此除了畫紅線的地方,後面那個0x00000000也應該包括其中,但沒保存PSD,懶癌發作所以圖片沒有改正)

而字元串philippica就是青色線的那一條,我們看到,為了覆蓋到紅線那個位置,我們構造的字元串必須先填充24位元組,才能到達紅色區域

到達紅色區域後我們就要跳轉到我們希望的success函數的地址上了,經查詢success的起始地址是0x000000000040060d,然後就能構造字元串啦:

前面那24位元組的秀恩愛後,後面才是重點,其實再後面的/x00已經被strcpy給截斷了,然後我們再運行這個程序我們發現成功的列印出helloworld!

讓我們乾杯!cheers!

後記:這個只是棧區溢出利用的實驗,有機會還想總結堆區溢出以及shellcode的各種姿勢


推薦閱讀:

Kotlin傳教文 中(inline略解)
關於C++11中移動語義的一個問題?何時移動構造函數會被調用?
工廠設計模式有什麼用?
遊戲設計模式(一) 序言:架構,性能與遊戲
7個有益的編程習慣

TAG:计算机 | 网络安全 | 编程 |