一个通俗易懂的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]//地址里的值指向下一个

扫描二维码关注公众号,回复: 1936351 查看本文章

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函数了。当你把上面一段代码转成硬编码,之后就可以嵌入其他进程中运行了。






猜你喜欢

转载自blog.csdn.net/u011569253/article/details/80529936