1. 程式人生 > >一個通俗易懂的ShellCode例子

一個通俗易懂的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函數了。當你把上面一段程式碼轉成硬編碼,之後就可以嵌入其他程序中運行了。