有人的地方就有江湖,有江湖的地方就有爭斗,有爭斗就有攻防。人類爭斗最初是利用拳腳,冷兵器時代是刀槍,熱兵器時代是槍炮,而在計算機平臺上,人們的武器換成了——代碼。
注:本文注重漏洞攻防的思路對抗過程,因此并未完全按照時間先后順序描述防護措施。
00二進制漏洞
二進制漏洞是可執行文件(PE、ELF文件等)因編碼時考慮不周,造成的軟件執行了非預期的功能。二進制漏洞早期主要以棧溢出為主,那時候操作系統和軟件廠商還沒有相應的安全意識,漏洞利用在當時來說可謂是如入無人之境。
要理解棧溢出,首先要掌握C語言中函數的調用過程:
C語言中調用一個函數,在編譯完成后執行的是匯編語句Call指令。Call指令會執行兩個操作:
(1)將Call指令之后的下一條指令入棧;
(2)跳轉到函數地址。
函數開始執行時,主要工作為保存本函數會修改的寄存器值和申請局部變量空間:
函數執行結束時,主要工作為:
(1)將函數返回值入eax
(2)恢復本函數調用前的寄存器值
(3)釋放局部變量空間
(4)調用ret指令跳轉到函數調用結束后的下一條指令(返回地址)
備注:這里我們順帶就能理解為什么說局部變量的生存周期隨著函數調用結束而結束。
函數調用過程中棧的空間分布如下:
棧溢出指的是局部變量在使用過程中,由于代碼編寫考慮不當,造成了其大小超出了其本身的空間,覆蓋掉了前棧幀EBP和返回地址等。由于返回地址不對,函數調用結束后跳轉到了不可預期的地址,造成了程序崩潰。
早期的棧溢出漏洞利用就是將函數的返回地址覆蓋成一個可預期的地址,從而控制程序執行流程觸發shellcode。漏洞發生時,能控制的數據(包含shellcode)在局部變量中,局部變量又存在于棧上面,因此要想執行shellcode必須將程序執行流程跳轉到棧上。
shellcode存好了,返回地址也可控,如果將返回地址改寫為shellcode地址就OK了,可偏偏棧的地址在不同環境中是不固定的。
這時候有聰明的程序員發現了一條妙計,棧地址不固定,但是程序地址是固定的啊。通過在程序代碼中搜索jmp esp指令地址,將返回地址改成jmp esp的地址,就可以實現控制程序執行流程跳轉到棧上執行shellcode!
至此,各種棧溢出漏洞利用粉墨登場。瀏覽網頁、看個視頻、聽個音樂、看個文檔甚至聊個QQ都可能被別有用心的人控制電腦。等到MS08-067遠程漏洞被曝出后,只要電腦聯網,你什么都不干都會被人控制,這簡直是喪心病狂。微軟你出來,我保證不打死你!
01 第一回合GS
守方:你不是要覆蓋返回地址嗎?我在函數執行一開始先往棧上保存個數據,等函數返回之前先檢查這個數據,要是不一致那一定是被覆蓋了,我就不返回了!
這一技術可以在編譯器選項的設置為啟用GS選項,而保存的這個數據稱之為“Security Cookie”。IDA可以清楚的看到啟用GS選項之后的反匯編代碼,函數開始執行時:
在函數返回前,調用Check_Security_Cookie檢查棧是否被覆蓋
校驗函數代碼
一旦校驗不通過,函數跳到相應的處理過程,不再返回因此shellcode也就無法獲得執行機會。
攻方:你以為我只有覆蓋返回地址這一招嗎?你忘記了還有異常處理SEH鏈也在棧上嗎?我可以通過覆蓋SEH鏈為jmp esp的地址,之后觸發異常跳轉到jmp esp執行shellcode。
本回合總結:
有關異常處理SEH鏈的技術可以參考http://blog.csdn.net/hustd10/article/details/51167971這邊文章,這里不再敘述。有一點得說明一下,利用覆蓋SEH鏈的方法同樣是覆蓋了??臻g,因此必須滿足:在函數檢查Security_Cookie之前就觸發異常,具體原因讀者可以自行思考。為了能夠觸發異常,這里有兩個思路可以參考:
(1)盡可能多的覆蓋??臻g。輸入的數據如果足夠大,保證覆蓋掉SEH鏈后繼續覆蓋超出??臻g,OK,觸發異常。
(2)通過控制數據,在函數觸發漏洞之后到返回之前的代碼中觸發異常。
由于GS選項是軟件編譯之時加入的,使得之前的版本無法添加此安全特性,尤其是在很多用戶不喜歡更新軟件的現實環境中。就算新版,也有可以通過覆蓋SEH鏈來實現漏洞利用,所以這一次防守短期內基本沒有達到預期目的。
02 第二回合SafeSEH
守方:兵來將擋,水來土屯。既然可以覆蓋SEH鏈實現執行shellcode,那我就改進下溢出處理的機制。
在程序編譯的時候,就將所有的異常處理函數進行注冊。凡是執行過程中觸發異常后,都要經過一個檢驗函數,檢查SEH鏈指向的地址是否在注冊的列表中。如果不在,那就不去執行,嗯,這個機制就叫SafeSEH吧。
攻方大神:別慌,我們先來分析下他的檢驗函數的邏輯。一袋煙功夫之后,嗯,這邏輯有點怪。阻止執行的情況只有在SEH鏈指向模塊(exe、dll)地址的情況下,如果SEH鏈指向的地址不在這些模塊中,那就可以執行了?
攻方A:他這是弄啥咧,只要不在注冊的列表中,就不執行,多簡單的邏輯。我覺得這可能是個陰謀!
攻方B:愛咋咋地,搞起!
攻方C:這么說在程序中非模塊的數據空間找到jmp esp就行了唄,比方說nls后綴的資源文件等。
攻方D:說是這么說,第一回合我們就損失了小部分戰場,要是有軟件在數據空間找不到jmp esp呢?
攻方E:管不了那么多了,再說了不是還有瀏覽器這類支持JS腳本的軟件嗎?那申請點堆空間自己寫個jmp esp不就得了。
攻方大神:傻啊,都能寫jmp esp了,直接把shellcode寫進去不得了,腦殘。
攻方:干杯!
本回合總結:
(1)對于不支持JS等腳本的軟件,在數據空間中尋找jmp esp地址,覆蓋SEH鏈指向此地址。
(2)對于支持JS等腳本的軟件,直接通過腳本申請堆空間寫入shellcode,覆蓋SEH鏈指向堆上的shellcode地址。
03 第三回合DEP
守方:攻方太狡猾了,簡直是欺人太甚??磥硇〈蛐◆[是不行了,是你們逼我的。大招伺候:數據執行保護(DEP)。
攻方A(一臉懵逼):數據執行保護是個啥?
攻方大神(深深的吸了一口煙):就是堆和棧只有讀寫權限,沒有執行權限了。
攻方B:啥玩意啊,虛頭巴腦的,直接叫“堆棧不可執行”不就得了。
攻方大神:照你這么說,以后新聞聯播中的“與會雙方深入交換了意見”就得說成“與會雙方大吵一架”。
攻方B:……
攻方大神:這個有點麻煩啊,容我三思!
……
守方:我得意的笑。
很長一段時間后……
攻方A:既然堆棧都不能執行了,我們就從程序自身的代碼去拼湊shellcode。
攻方B:你干脆讓所有程序員給你留一個后門得了。
攻方大神:還別說,這句話提醒了我,我們不去湊shellcode,這不現實。shellcode還在堆棧中,如果可以從程序自身的代碼去湊到執行VirtualProtect將shellcode所在內存屬性添加上可執行權限就可以了。
若干天后……
攻方全體:這個技術好,大部分軟件可以通過拼湊代碼片段執行VirtualProtect,將函數返回值或者SEH鏈覆蓋成代碼片段的起始地址,又可以愉快的玩耍了。
守方:
本回合總結:
這種利用程序自身的代碼碎片繞過DEP的技術稱之為ROP。有關ROP的資料可以參考http://rickgray.me/2014/08/26/bypass-dep-with-rop-study.html。
ROP技術是通過拼湊代碼碎片執行API,在最開始沒有相應輔助工具的時候,構建ROP鏈是耗時耗力的。隨著研究人員的增多,相應的輔助工具也被開發出來,ROP鏈的構建已經相對容易了。
04 第四回合ASLR
守方:本以為堆棧不可執行就世界太平了,沒想到這幫小子居然還可以這么玩。老虎不發威,你當我是病貓啊。
依我看,ROP技術的前提是代碼片段的地址固定,這樣才能知道往函數返回值或者SEH鏈中填寫哪個地址。這次再放一個大招,地址空間布局隨機化(ASLR),我就不信了。不是要構建ROP么,我讓exe、dll的地址全都隨機,有本事來戰啊。
攻方A:給跪了,膜拜。
攻方B:洗洗睡了。
攻方C:收拾東西,擺地攤手機貼膜去。
……
攻方大神:別慌,我們不是還有一些不能利用的漏洞嗎?如果這些漏洞能夠泄露出模塊地址,兩個漏洞結合起來,不是還有一線生機嗎?
攻方A:聽上去有點道理。
若干天后
攻方A:這個方法很不通用啊,而且泄露地址的漏洞得不能讓程序崩潰才能有機會觸發后面的可執行漏洞。
攻方B:洗洗睡了。
攻方C:收拾東西,擺地攤手機貼膜去。
……
攻方大神:你們夠了!再不行,我們暴力把程序空間占滿,全鋪上shellcode,只要跳轉地址沒落在已有模塊中,落在我們的空間中不就可以執行了shellcode了么?
攻方B:這樣不好吧,把內存全占滿了,系統都卡的不行了。另外這樣也繞不過DEP吧?
攻方大神:你有更好的辦法嗎?
……
本回合總結:
ASLR+DEP的雙重防護使得大多數軟件的漏洞只能造成崩潰,無法穩定利用。將程序空間占滿的技術,稱之為堆噴射(Heap Spraying),這種技術只能應用在可以執行JS等腳本的軟件上,如瀏覽器等。
堆噴射通過大面積的申請內存空間并構造適當的數據,一旦EIP指向這片空間,就可以執行shellcode。堆噴射已經是不得已而為之,有時候會造成系統卡一段時間,容易被發現;另一點,如果EIP恰好指向shellcode中間部分就會造成漏洞利用失敗,因此不能保證100%成功。
堆噴射只是繞過了ASLR,無法繞過DEP,對于ASLR+DEP的雙重防護依然是無效的。
堆噴射技術學習可以參考http://blog.chinaunix.net/uid-24917554-id-3492618.html這篇文章。
05 第五回合Flash
守方:世界上沒有什么問題是一個大招不能解決的,如果有,那就兩個。
攻方A:哎,路子越來越窄,日子難過啊。
攻方B:這JS也不好用,不但卡,還經常掛,成功率受影響啊,要是有別的腳本語言就好了。
攻方大神(眼前一亮):別的腳本語言沒有,Flash不是可以自己編寫AS代碼嗎?而且Flash在Winodws、Linux和Android上都有應用!
攻方B:不知道Flash好用不好用,試試看吧。
一段時間后……
攻方全體:Flash好人啊,上帝關上門,感謝Flash給我們開了一扇窗。
本回合總結:
ASLR+DEP的防護,使得漏洞利用穩定成功率,越來越多的研究者從瀏覽器+JS轉向了瀏覽器+Flash。剛開始的Flash漏洞利用和JS一樣也是不穩定,隨著研究的深入,Flash漏洞利用越來越成熟。
由于二進制漏洞破壞了程序執行流程,因此如果執行了shellcode之后不做其他處理,軟件會崩潰掉。通常的做法是shellcode調用ExitProcess退出進程,這樣會造成軟件打開了閃退掉。而Flash作為瀏覽器插件的存在,居然發展出很多不卡不閃不掛的漏洞,Flash漏洞利用研究如星火燎原般熾熱。
06 第六回合 混亂
攻方:整形溢出、類型混淆、雙重釋放、UAF,看我萬箭齊發!
守方:無力吐槽了,不怕神一樣的對手,就怕豬一樣的隊友,Adobe,你看著辦!
Adobe:我補,我再補,我繼續補,我接著補,我……
攻方:整形溢出、類型混淆、雙重釋放、UAF,萬箭齊發!
Adobe:來人,護駕!
吃瓜群眾:Adobe你行不行啊?
蘋果:大家放棄Flash吧!
FireFox:+1
Amazon:+2
Chrome:+3
FaceBook:你們。。。我的游戲,我的收入!
微軟:+10086
FaceBook:算了,我還是轉HTML5吧。
攻方:別這樣。。。我剛精通AS代碼。
安全廠商:推薦用戶卸載Flash!
吃瓜群眾:卸載?網頁上全是框框,視頻看不了,頁游玩不了….
Adobe:不是國軍無能,是共軍太狡猾!
全體:滾!
07 后記
雖然Flash的漏洞使得其他廠商有點無奈,但是微軟并沒有停止防守的腳步。微軟在Win 8.1 Update 3以及Win 10中啟用了一種抵御內存泄露攻擊的新機制,即Control Flow Guard(CFG)——控制流防護。這項技術是為了彌補此前不完美的保護機制,例如地址空間布局隨機化(ASLR)導致了堆噴射的發展,而數據執行保護(DEP)造成了漏洞利用代碼中返回導向編程(ROP)技術的發展。
有關CFG控制流保護的分析可以參考文章http://www.freebuf.com/articles/security-management/58373.html。
有盾就有矛,有守就有攻。繞過CFG的研究也在不斷的進行中,其中的一些技術可以參考綠盟科技的《分析及防護:Win10 執行流保護繞過問題》,地址http://blog.nsfocus.net/win10-cfg-bypass/,以及XCon2016的議題《JIT噴射技術不死—利用WARP Shader JIT噴射繞過控制流保護(CFG)》。
這是一場沒有硝煙的戰爭,這也是一場沒有盡頭的戰爭。曾經office默認執行宏造成了宏病毒的泛濫,之后微軟將Office文檔的宏改為手動啟用,宏病毒銷聲匿跡,二進制文檔漏洞開始興起。隨著ASLR+DEP防護的加強,Office還額外增加了一些防護手段,二進制文檔漏洞難以為繼,曾經的宏病毒搖身一變又殺了回來。當然,宏依然是不能夠自動執行,但是可以通過文檔內容,誘使人點擊執行。
攻也好,防也好,技術的背后是人。當技術手段的發展受到制約,利用人自身弱點的社會工程學就會興起。你躲得了初一,躲得過十五嗎?