什么是ShellCode?
不依赖环境,在任何地方都能执行的机器码(硬编码)。
ShellCode的编写原则
- 不能有全局变量。
- 不能使用常量字符串、
- 不能使用系统调用。
- 不能嵌套调用其他函数。
ShellCode的问题
由于第 3 点,所以不能直接使用函数,因为直接使用函数在编译时会产生导入表。而将代码复制到其他进程中,其他进程没有需要的导入表,所以会出问题。
那么该怎么解决这个问题呢?
可以不依赖任何导入表得到进程的TEB,其次,通过TEB找到PEB,再找到 3 个链表。
对链表进行遍历,找到需要的模块,如Kernel32.dll、User32.dll等。
找到模块了还不够,因为还需要找到函数在哪里。通过PE相关的知识(知道导出表的结构)遍历模块文件,找到需要的函数地址。
代码
#include <iostream>
#include <Windows.h>
//定义函数指针
typedef int (WINAPI* PMESSAGEBOX)(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);
typedef FARPROC(WINAPI* PGETPROCADDRESS)(HMODULE hModule, LPCSTR lpProcName);
typedef HMODULE(WINAPI* PLOADLIBRARY)(LPCTSTR lpFileName);
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING;
typedef struct _PEB_LDR_DATA {
DWORD Length;
BYTE Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA;
typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks; //代表按加载顺序构成的模块链表
LIST_ENTRY InMemoryOrderLinks; //代表按内存顺序构成的模块链表
LIST_ENTRY InInitializationOrderLinks; //代表按初始化顺序构成的模块链表
PVOID DllBase; //该模块的基地址
PVOID EntryPoint; //该模块的入口
ULONG SizeOfImage; //该模块的映像大小
UNICODE_STRING FullDllName; //包含路径的模块名称
UNICODE_STRING BaseDllName; //不包含路径的模块名称
ULONG Flags;
SHORT LoadCount; //该模块的引用计数
SHORT TlsIndex;
HANDLE SectionHandle;
ULONG CheckSum;
ULONG TimeDataStamp;
} LDR_DATA_TABLE_ENTRY;
void ShellCode()
{
LDR_DATA_TABLE_ENTRY* pPld = NULL, * pBeg = NULL;
PGETPROCADDRESS pGetProcAddress = NULL;
PMESSAGEBOX pMessageBox = NULL;
PLOADLIBRARY pLoadLibrary = NULL;
WORD* pFirst = NULL, * pLast = NULL;
DWORD ret = 0, i = 0;
DWORD dwKernelBase = 0;
//定义自身要使用的DLL、函数
char szKernel32[] = { 'K',0,'E',0,'R',0,'N',0,'E',0,'L',0,'3',0,'2',0,'.',0,'D',0,'L',0,'L',0,0,0 };
char szUser32[] = { 'u','s','e','r','3','2','.','d','l','l',0 };
char szGetProcAddress[] = { 'G','e','t','P','r','o','c','A','d','d','r','e','s','s',0 };
char szLoadLibrary[] = { 'L','o','a','d','L','i','b','r','a','r','y','A',0 };
char szMessageBox[] = { 'M','e','s','s','a','g','e','B','o','x','A',0 };
//获取链表 TEB->PEB->_PEB_LDR_DATA->_LDR_DATA_TABLE_ENTRY
__asm
{
mov eax,fs:[0x30] //PEB
mov eax,[eax+0x0c] //PEB->Ldr
add eax,0x0c //_PEB_LDR_DATA->InLoadOrderModuleList
mov pBeg,eax
mov eax,[eax]
mov pPld,eax
}
//遍历寻找 Kernel32.dll
while (pPld!=pBeg)
{
pLast = (WORD*)pPld->BaseDllName.Buffer;
pFirst = (WORD*)szKernel32;
while (*pFirst && *pFirst == *pLast)
pFirst++, pLast++;
if (*pFirst == *pLast)
{
dwKernelBase = (DWORD)pPld->DllBase;
break;
}
pPld = (LDR_DATA_TABLE_ENTRY*)pPld->InLoadOrderLinks.Flink;
}
//遍历 Kernel32.dll 的导出表 找到 GetProcAddress 函数的地址
IMAGE_DOS_HEADER* pIDH = (IMAGE_DOS_HEADER*)dwKernelBase;
IMAGE_NT_HEADERS* pINGS = (IMAGE_NT_HEADERS*)((DWORD)dwKernelBase + pIDH->e_lfanew);
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* pAddOfNames_Raw = (DWORD*)((DWORD)dwKernelBase + pIED->AddressOfNames);
DWORD dwCnt = 0;
char* pFinded = NULL, * pSrc = szGetProcAddress;
for (; dwCnt < pIED->NumberOfNames; dwCnt++)
{
pFinded = (char*)((DWORD)dwKernelBase + pAddOfNames_Raw[dwCnt]);
while (*pFinded && *pFinded == *pSrc)
pFinded++, pSrc++;
if (*pFinded == *pSrc)
{
pGetProcAddress = (PGETPROCADDRESS)((DWORD)dwKernelBase + pAddOfFun_Raw[pAddOfOrd_Raw[dwCnt]]);
break;
}
pSrc = szGetProcAddress;
}
//有了 GetProcAddress 就可以得到任何的API函数了
pLoadLibrary = (PLOADLIBRARY)pGetProcAddress((HMODULE)dwKernelBase, szLoadLibrary);
pMessageBox = (PMESSAGEBOX)pGetProcAddress(pLoadLibrary((LPCTSTR)szUser32), szMessageBox);
//使用函数
char szTitle[] = { 'T','e','s','t',0 };
char szContent[] = { 'S','h','e','l','l','C','o','d','e','T','e','s','t',0 };
pMessageBox(NULL, (LPCTSTR)szContent, (LPCTSTR)szTitle, 0);
}
int main()
{
std::cout << "按任意键继续..." << std::endl;
std::cin.get();
ShellCode();
return 0;
}