一個通俗易懂的ShellCode例子
每當你聽到ShellCode一定會想到病毒與安全。其實ShellCode並沒有你想想中的那麼難,它有一個特點就是把它嵌入到任何程序中都能夠執行。是不是感覺很牛逼。但是我們分析一下什麼程式碼能夠不依賴任何環境呢?首先這段程式碼不能夠有常量區、靜態區資料。也就是說不能夠有全域性變數。還有不能有類似char str[]={"hello word"};這樣的資料,因為這樣的資料在常量區。同時不能夠有系統呼叫和函式呼叫。當你程式碼包含以上這些條件。那麼恭喜你已經完成了一個ShellCode。下面我們就簡單分析一個windows下彈Messagebox這段程式碼如何書寫。
首先需要獲取到KERNEL32.DLL的基地址。我們可以用以下兩種方法,其原理都是一樣的,我希望你和我一樣想知道為什麼這麼寫,那麼我們來探究為什麼這樣寫。
_asm { MOV EAX, DWORD PTR FS : [0x30]//; 獲取PEB基址 MOV EAX, DWORD PTR DS : [EAX + 0xC]//; 獲取PEB_LDR_DATA結構指標 MOV ESI, DWORD PTR DS : [EAX + 0x1C]//; 獲取InInitializationOrderModuleList成員指標 LODS DWORD PTR DS : [ESI]//; 把ESI地址裡的值給EAX,同時ESI自己加4,相當於獲取下一個節點 MOV EBX, DWORD PTR DS : [EAX + 8]//; 取其基地址,該結構當前包含的是kernel32.dll MOV dwKernelBase, EBX }
_asm { mov eax, DWORD PTR FS:[0x30]//+0x030 ProcessEnvironmentBlock : Ptr32 _PEB* mov eax, DWORD PTR DS:[eax + 0x0c]// +0x00c Ldr : Ptr32 _PEB_LDR_DATA * mov eax, DWORD PTR DS:[eax + 0x1c]// +0x01c InInitializationOrderModuleList : _LIST_ENTRY mov pBEG, eax //pBEG自己定義的PVOID mov eax, [eax]//地址裡的值指向下一個 mov pPLD, eax //pPLD自己定義的PVOID } //遍歷找到kernel32.dll do { PVOID BaseAddress = (PVOID)*((PDWORD)((DWORD)pPLD + 0x08)); PVOID FullDllName = (PVOID)*((PDWORD)((DWORD)pPLD + 0x20)); WCHAR* szname = (WCHAR*)FullDllName; pLast = (WORD *)FullDllName; pFirst = (WORD *)szKernel32; while (*pFirst && *pFirst == *pLast) { pFirst++; pLast++; } if (*pFirst == *pLast) { dwKernelBase = (DWORD)BaseAddress; break; } pPLD = (PVOID)*((PDWORD)pPLD); } while (pPLD != pBEG);
要想理解上面的程式碼就要知道FS:[0] 相當於基地址為當前執行緒的執行緒環境塊(TEB),所以該段也被稱為TEB段。下面就是TEB的結構體
/*
cefclient!_TEB
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : Ptr32 Void
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : Ptr32 Void
+0x02c ThreadLocalStoragePointer : Ptr32 Void
+0x030 ProcessEnvironmentBlock : Ptr32 _PEB//程序環境塊 PEB
+0x034 LastErrorValue : Uint4B
+0x038 CountOfOwnedCriticalSections : Uint4B
+0x03c CsrClientThread : Ptr32 Void
+0x040 Win32ThreadInfo : Ptr32 Void
*/
我們看到了PEB在偏移0x30的位置所以你很好理解 mov eax, DWORD PTR FS:[0x30]這句彙編了吧。
/*cefclient!_PEB
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar
+0x003 BitField : UChar
+0x003 ImageUsesLargePages : Pos 0, 1 Bit
+0x003 IsProtectedProcess : Pos 1, 1 Bit
+0x003 IsImageDynamicallyRelocated : Pos 2, 1 Bit
+0x003 SkipPatchingUser32Forwarders : Pos 3, 1 Bit
+0x003 IsPackagedProcess : Pos 4, 1 Bit
+0x003 IsAppContainer : Pos 5, 1 Bit
+0x003 IsProtectedProcessLight : Pos 6, 1 Bit
+0x003 SpareBits : Pos 7, 1 Bit
+0x004 Mutant : Ptr32 Void
+0x008 ImageBaseAddress : Ptr32 Void
+0x00c Ldr : Ptr32 _PEB_LDR_DATA //程序載入的模組連結串列Ldr
+0x010 ProcessParameters : Ptr32 _RTL_USER_PROCESS_PARAMETERS
+0x014 SubSystemData : Ptr32 Void
+0x018 ProcessHeap : Ptr32 Void
+0x01c FastPebLock : Ptr32 _RTL_CRITICAL_SECTION
+0x020 AtlThunkSListPtr : Ptr32 Void
+0x024 IFEOKey : Ptr32 Void
+0x028 CrossProcessFlags : Uint4B
*/
這下我們理解了mov eax, DWORD PTR DS:[eax + 0x0c]// +0x00c Ldr : Ptr32 _PEB_LDR_DATA *
/*cefclient!_PEB_LDR_DATA
+0x000 Length : Uint4B
+0x004 Initialized : UChar
+0x008 SsHandle : Ptr32 Void
+0x00c InLoadOrderModuleList : _LIST_ENTRY
+0x014 InMemoryOrderModuleList : _LIST_ENTRY
+0x01c InInitializationOrderModuleList : _LIST_ENTRY//獲取初始化順序模組連結串列
+0x024 EntryInProgress : Ptr32 Void
+0x028 ShutdownInProgress : UChar
+0x02c ShutdownThreadId : Ptr32 Void
*/
對應mov eax, DWORD PTR DS:[eax + 0x1c]// +0x01c InInitializationOrderModuleList : _LIST_ENTRY
下面我們來看看
mov pBEG, eax //首先用pBEG儲存第一個連結串列的地址
mov eax, [eax]//地址裡的值指向下一個
mov pPLD, eax//pPLD儲存下一個指向的地址
這是一張我在網上找到的圖 感覺很形象理解上面的結構體
但是我們是在第三個list做的選著下一個節點。所以這個圖有一些問題,但是原理是一樣的。是不是知道我們如何找到KERNEL32.DLL的基地址了。我推薦使用第二種方法查詢基地址。第一中在win7以上系統才可以。第二種通過對比字串確定基地址更為準確一些。
我們已經找到了KERNEL32.DLL的基地址了,下面如何找到GetProcAddress函式地址了。這需要你對PE檔案有一些瞭解。知道匯出表在那個位置。程式碼如下
IMAGE_DOS_HEADER *pIDH = (IMAGE_DOS_HEADER*)dwKernelBase;//獲取基地址DOS頭
IMAGE_NT_HEADERS *pINGS = (IMAGE_NT_HEADERS*)((DWORD)dwKernelBase + pIDH->e_lfanew);//找到NT頭
IMAGE_EXPORT_DIRECTORY *pIED = (IMAGE_EXPORT_DIRECTORY*)((DWORD)dwKernelBase + pINGS->OptionalHeader.DataDirectory[0].VirtualAddress);//找到匯出表位置
DWORD *pAddOffun_Raw = (DWORD*)((DWORD)dwKernelBase + pIED->AddressOfFunctions);//匯出表對應的三個地址,他們之間多關係我就不講了自己檢視匯出表
WORD *pAddOfOrd_Raw = (WORD*)((DWORD)dwKernelBase + pIED->AddressOfNameOrdinals);
DWORD *pAddOfofNames_Raw = (DWORD*)((DWORD)dwKernelBase + pIED->AddressOfNames);
下面我們要獲取具體GetProcAddress函式地址
// PE(匯出表)->找匯出函式
for (;dwCnt<pIED->NumberOfNames;dwCnt++)
{
pFinded = (char*)((DWORD)dwKernelBase + pAddOfofNames_Raw[dwCnt]);//名稱表對應多名稱地址
while (*pFinded && *pFinded == *pSrc)//對比GetProcAddress字串
{
pFinded++;
pSrc++;
}
if (*pFinded == *pSrc)//對比成功
{
pGetProcAddress = (PGETPROCADDRESS)((DWORD)dwKernelBase + pAddOffun_Raw[pAddOfOrd_Raw[dwCnt]]);//名稱表對應序號表地址裡的內容就是對應多函式地址
break;
}
pSrc = szGetProcAddr;
}
這樣我們就找到了GetProcAddress函式地址,有了這個函式地址我們就可以獲取任意我們想載入的函數了
//獲取其他函式地址
pLoadLibrary = (PLOADLIBRARY)pGetProcAddress((HMODULE)dwKernelBase, szLoadLibrary);
pMessageBox = (PMESSAGEBOX)pGetProcAddress((HMODULE)pLoadLibrary((LPCTSTR)szUser32), szMessageBox);
char strtest[] = { 'S','h','e','l','l','C','o','d','e',0 };
char strContent[] = { 'l','i','u','g','x',0 };
pMessageBox(NULL, (LPCTSTR)strtest, (LPCTSTR)strContent, 0);
這樣我們呼叫成功MessageBox函數了。當你把上面一段程式碼轉成硬編碼,之後就可以嵌入其他程序中運行了。