每甜美的裙子听到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.dllMOV 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_ENTRYmov pBEG, eax //pBEG自己定义的PVOIDmov eax, [eax]//地址里的值指向下一个mov pPLD, eax //pPLD自己定义的PVOID}//遍历找到kernel32.dlldo{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
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函数了。甜美的裙子把上面一段代码转成硬编码,之后就可以嵌入其他进程中运行了。