一、windows下的內存保護機制
0x00、二進制漏洞
二進制漏洞是可執行文件(PE、ELF文件等)因編碼時考慮不周,造成的軟件執行了非預期的功能。二進制漏洞早期主要以棧溢出為主。
我們都知道在C語言中調用一個函數,在編譯后執行的是CALL指令,CALL指令會執行兩個操作:
(1)、將CALL指令之后下一條指令入棧。
(2)、跳轉到函數地址。
函數在開始執行時主要工作為保存該函數會修改的寄存器的值和申請局部變量
空間,而在函數執行結束時主要的工作為:
(1)、將函數返回值入eax
(2)、恢復本函數調用前的寄存器值
(3)、釋放局部變量空間
(4)、調用ret指令跳轉到函數調用結束后的下一條指令(返回地址)
棧溢出指的是局部變量在使用過程中,由於代碼編寫考慮不當,造成了其大小超出了其本身的空間,覆蓋掉了前棧幀EBP和返回地址等。由於返回地址不對,函數調用結束后跳轉到了不可預期的地址,造成了程序崩潰。
早期的棧溢出漏洞利用就是將函數的返回地址覆蓋成一個可預期的地址,從而控制程序執行流程觸發shellcode。漏洞發生時,能控制的數據(包含shellcode)在局部變量中,局部變量又存在於棧上面,因此要想執行shellcode必須將程序執行流程跳轉到棧上。
shellcode存好了,返回地址也可控,如果將返回地址改寫為shellcode地址就OK了,可偏偏棧的地址在不同環境中是不固定的。
這時候有聰明的程序員發現了一條妙計,棧地址不固定,但是程序地址是固定的。通過在程序代碼中搜索jmp esp指令地址,將返回地址改成jmp esp的地址,就可以實現控制程序執行流程跳轉到棧上執行shellcode。
附上一張函數調用過程中的棧空間分布圖:
0x01、啟用GS選項
啟用GS選項是在編輯器中可以啟用的一項選擇。
啟用GS選項之后,會在函數執行一開始先往棧上保存一個數據,等函數返回時候檢查這個數據,若不一致則為被覆蓋,這樣就跳轉進入相應的處理過程,不再返回,因此shellcode也就無法被執行,這個值被稱為“Security cookie”。
感興趣的同學可以編寫一個DEMO啟用GS后編譯,使用IDA便可以看軟件到調用Check_Security_Cookie()
檢查棧是否被覆蓋。
可是他們忽略了異常處理SEH鏈也在棧上因此可以覆蓋SEH鏈為jmp esp的地址,之后觸發異常跳轉到esp執行shellcode。
有關SEH鏈的的技術可以參考這里。
0x02、SafeSEH
SafeSEH是在程序編譯的時候,就將所有的異常處理函數進行注冊。凡是執行過程中觸發異常后,都要經過一個檢驗函數,檢查SEH鏈指向的地址是否在注冊的列表中。
可是再檢驗函數的邏輯中阻止執行的情況只有在SEH鏈指向模塊(exe、dll)地址的情況下,如果SEH鏈指向的地址不在這些模塊中,那就可以執行了。因此在程序中非模塊的數據空間找到jmp esp,比方說nls后綴的資源文件等。或者是在支持js腳本的軟件中(瀏覽器等),通過腳本申請堆空間寫入shellcode。
0x03、DEP
數據執行保護(DEP)指的是堆和棧只有讀寫權限沒有執行權限。
對抗DEP的方式是將shellcode寫入堆棧中,從程序自身的代碼去湊到執行VirtualProtect()
將shellcode所在內存屬性添加上可執行權限,將函數返回值或者SEH鏈覆蓋成代碼片段的起始地址。這種利用程序自身碎片繞過DEP的方式被稱作ROP,關於ROP的技術細節可以參考這篇文章。
ROP技術是通過拼湊代碼碎片執行API,在最開始沒有相應輔助工具的時候,構建ROP鏈是耗時耗力的。隨着研究人員的增多,相應的輔助工具也被開發出來,ROP鏈的構建已經相對容易了。
0x04、ASLR
ROP技術的前提是代碼片段的地址固定,這樣才能知道往函數返回值或者SEH鏈中填寫哪個地址。因此地址空間布局隨機化(ASLR)應運而上,ALSR即是讓exe、dll的地址全都隨機。
對抗ASLR的方式是暴力把程序空間占滿,全鋪上shellcode,只要跳轉地址沒落在已有模塊中,落在我們的空間中即可以執行了shellcode,但是這樣做無法繞過DEP,這種將程序空間全部占滿鋪上shellcode的技術被稱為堆噴射技術,堆噴射技術只能對抗ASLR,缺無法對抗ASLR+DEP的雙重防護。
ASLR+DEP的雙重防護使得大多數軟件的漏洞只能造成崩潰,無法穩定利用。將程序空間占滿的技術,稱之為堆噴射(Heap Spraying),這種技術只能應用在可以執行JS等腳本的軟件上,如瀏覽器等。
堆噴射通過大面積的申請內存空間並構造適當的數據,一旦EIP指向這片空間,就可以執行shellcode。堆噴射已經是不得已而為之,有時候會造成系統卡一段時間,容易被發現;另一點,如果EIP恰好指向shellcode中間部分就會造成漏洞利用失敗,因此不能保證100%成功。
關於堆噴射技術可以參考這篇文章。
因為ASLR+DEP的雙重防護使得PC上的軟件若存在漏洞也無法穩定利用,因為safeSEH技術的存在大多數的軟件安全研究者都轉向了瀏覽器+JS,可是因為JS的效率問題,JS也漸漸的使用變少,此時FLASH的AS腳本漸漸進入了人們的視野,而且FLASH不僅僅在windows上,包括Linux和Andriod均可以使用。因此很多的軟件安全研究人員轉向了瀏覽器+AS。
由於二進制漏洞破壞了程序執行流程,因此如果執行了shellcode之后不做其他處理,軟件會崩潰掉。通常的做法是shellcode調用ExitProcess退出進程,這樣會造成軟件打開了閃退掉。而Flash作為瀏覽器插件的存在,居然發展出很多不卡不閃不掛的漏洞,Flash漏洞利用研究如星火燎原般熾熱。因此大多數的瀏覽器漸漸的不再支持FLASH插件,HTML5動畫技術也漸漸的發展起來。
0x05、CFG
雖然FlASH的漏洞使得其他廠商有點無奈,但是微軟並沒有停止防守的腳步。微軟在Win 8.1 Update 3以及Win 10中啟用了一種抵御內存泄露攻擊的新機制,即Control Flow Guard(CFG)——控制流防護。這項技術是為了彌補此前不完美的保護機制,例如地址空間布局隨機化(ASLR)導致了堆噴射的發展,而數據執行保護(DEP)造成了漏洞利用代碼中返回導向編程(ROP)技術的發展。
有關CFG控制流保護的分析可以參考這里。
繞過CFG的研究也在不斷的進行比如這篇文章以及XCon2016的議題《JIT噴射技術不死—利用WARP Shader JIT噴射繞過控制流保護(CFG)》。
二、LINUX下的內存防護機制
0x00、checksec
checksec是一個shell編寫的腳本軟件源碼參見這里。
checksec 用來檢查可執行文件屬性,例如PIE, RELRO, PaX, Canaries, ASLR, Fortify Source等等屬性。
一般來說,如果是學習二進制漏洞利用的朋友,建議大家使用gdb里peda插件里自帶的checksec功能,如下:
0x01、CANNARY
CANNARY(棧溢出保護)是一種緩沖區溢出攻擊緩解手段當。
啟用棧保護后,函數開始執行的時候會先往棧里插入cookie信息,當函數真正返回的時候會驗證cookie信息是否合法,如果不合法就停止程序運行。攻擊者在覆蓋返回地址的時候往往也會將cookie信息給覆蓋掉,導致棧保護檢查失敗而阻止shellcode的執行。在linux中我們將cookie信息稱為canary。
gcc在4.2版本中添加了-fstack-protector和-fstack-protector-all編譯參數以支持棧保護功能,4.9新增了-fstack-protector-strong編譯參數讓保護的范圍更廣。
這種方法很像windows下的啟用GS選項
0x02、FORTIFY
這個網上的信息很少因此舉個例子來描述:
1 void fun(char *s) { 2 char buf[0x100]; 3 strcpy(buf, s); 4 /* Don't allow gcc to optimise away the buf */ 5 asm volatile("" :: "m" (buf)); 6 }
用包含參數-U_FORTIFY_SOURCE編譯
1 08048450 <fun>: 2 push %ebp ; 3 mov %esp,%ebp 4 5 sub $0x118,%esp ; 將0x118存儲到棧上 6 mov 0x8(%ebp),%eax ; 將目標參數載入eax 7 mov %eax,0x4(%esp) ; 保存目標參數 8 lea -0x108(%ebp),%eax ; 數組buf 9 mov %eax,(%esp) ; 保存 10 call 8048320 <strcpy@plt> 11 12 leave ; 13 ret
用包含參數-D_FORTIFY_SOURCE=2編譯
1 08048470 <fun>: 2 push %ebp ; 3 mov %esp,%ebp 4 5 sub $0x118,%esp ; 6 movl $0x100,0x8(%esp) ; 把0x100當作目標參數保存 7 mov 0x8(%ebp),%eax ; 8 mov %eax,0x4(%esp) ; 9 lea -0x108(%ebp),%eax ; 10 mov %eax,(%esp) ; 11 call 8048370 <__strcpy_chk@plt> 12 13 leave ; 14 ret
我們可以看到gcc生成了一些附加代碼,通過對數組大小的判斷替換strcpy, memcpy, memset等函數名,達到防止緩沖區溢出的作用。
0x03、NX
NX即No-eXecute(不可執行)的意思,NX的基本原理是將數據所在內存頁標識為不可執行,當程序溢出成功轉入shellcode時,程序會嘗試在數據頁面上執行指令,此時CPU就會拋出異常,而不是去執行惡意指令。和windows下的DEP原理相同。不再贅述。
關於NX的繞過方式在linux叫做Ret2libc,與WINDOWS下的ROP大相徑庭。
既然注入Shellcode無法執行,進程和動態庫的代碼段怎么也要執行吧,具有可執行屬性,那攻擊者能否利用進程空間現有的代碼段進行攻擊,答案是肯定的。
linux下shellcode的功能是通過execute執行/bin/sh,那么系統函數庫(Linux稱為glibc)有個system函數,它就是通過/bin/sh命令去執行一個用戶執行命令或者腳本,我們完全可以利用system來實現Shellcode的功能。EIP一旦改寫成system函數地址后,那執行system函數時,它需要獲取參數。而根據Linux X86 32位函數調用約定,參數是壓到棧上的。噢,棧空間完全由我們控制了,所以控制system的函數不是一件難事情。
這種攻擊方法稱之為ret2libc,即return-to-libc,返回到系統庫函數執行 的攻擊方法。
有關ret2libc的攻擊方式可以參考這里。
0x04、PIE
一般情況下NX和地址空間分布隨機化會同時工作。在linux下內存空間隨機化被稱作PIE。
內存地址隨機化機制,有以下三種情況
0 - 表示關閉進程地址空間隨機化。 1 - 表示將mmap的基址,stack和vdso頁面隨機化。 2 - 表示在1的基礎上增加棧(heap)的隨機化。
可以防范基於Ret2libc方式的針對DEP的攻擊。ASLR和DEP配合使用,能有效阻止攻擊者在堆棧上運行惡意代碼。
Built as PIE:位置獨立的可執行區域(position-independent executables)。這樣使得在利用緩沖溢出和移動操作系統中存在的其他內存崩潰缺陷時采用面向返回的編程(return-oriented programming)方法變得難得多。
0x05、RELRO
在Linux系統安全領域數據可以寫的存儲區就會是攻擊的目標,尤其是存儲函數指針的區域. 所以在安全防護的角度來說盡量減少可寫的存儲區域對安全會有極大的好處.
GCC, GNU linker以及Glibc-dynamic linker一起配合實現了一種叫做relro的技術: read only relocation.大概實現就是由linker指定binary的一塊經過dynamic linker處理過 relocation之后的區域為只讀.
RELRO設置符號重定向表格為只讀或在程序啟動時就解析並綁定所有動態符號,從而減少對GOT(Global Offset Table)攻擊。
有關RELRO的技術細節點擊這里。
有關GOT攻擊的技術原理參考這里。
三、小結
這是一場沒有硝煙的戰爭,這也是一場沒有盡頭的戰爭。曾經office默認執行宏造成了宏病毒的泛濫,之后微軟將Office文檔的宏改為手動啟用,宏病毒銷聲匿跡,二進制文檔漏洞開始興起。隨着ASLR+DEP防護的加強,Office還額外增加了一些防護手段,二進制文檔漏洞難以為繼,曾經的宏病毒搖身一變又殺了回來。當然,宏依然是不能夠自動執行,但是可以通過文檔內容,誘使人點擊執行。
攻也好,防也好,技術的背后是人。當技術手段的發展受到制約,利用人自身弱點的社會工程學就會興起。
所以安全的最終還是關於主要人員的安全意識。