壳的编写(3)-- 编写壳(Stub)部分(2)
2016-01-04 16:41
537 查看
接:壳的编写(2)-- 编写壳(Stub)部分(1) http://blog.csdn.net/obuyiseng/article/details/50456807
1)通过FS得到TEB的地址
2)TEB偏移0x30处指向的是PEB指针
3)PEB偏移0x0C处指向PEB_LDR_DATA结构指针
4)PEB_LDR_DATA偏移0x1C处是InInitializationOrderModuleLis(模块初始化链表的头指针)
5)InInitializationOrderModuleLis中按顺序存在着此进程的初始化模块信息,在NT5.x内核中,第一个节点为ntdll.dll的基址,第二个节点为Kernel32.dll的基址; 在NT6.1内核中,第二个节点为KernelBase.dll的基址(包含着Kernel32.dll的大部分实现,其中就有GetProcAddress函数)
为此我们需要在Stub.h中定义两个函数,一个用于获取Kernel32.dll基址,一个用于获取GetProcAddress基址,代码如下:
注:可参考http://blog.csdn.net/obuyiseng/article/details/50456090
此时我们就能够获取到Kernel32.dll的基址,那么就可以通过遍历Kernel32.dll的导出表获取到GetProcAddress函数的基址了。
在Stub.cpp文件中获取GetProcAddress的基址代码如下:
在Stub.cpp中实现InitializationAPI函数,并初始化要使用的API函数。由于KernelBase.dll中没有导出LoadLibrary函数,所以为了兼容性考虑,我们在加载DLL的时候使用LoadLibraryExW。
为了退出进程时的兼容性,在StubEntryPoint中添加退出进程的兼容代码,总体如下:
5、动态加载API--获取到GetProcAddress
由于我们程序到时会丢弃掉自己的IAT和导入表信息,这样就不能够直接调用API,因此,我们需要使用动态加载API方法。我们需要获取到GetProcAddress函数的地址,而该函数是从系统文件Kernel32.dll中导出的,所有的运行的程序都会加载该动态链接库。那么,我们获取到Kernel32.dll加载基址,就可以获取GetProcAddress函数了。要想获取到Kernel32基址,我们可以使用TEB的信息找到Kernel32.dll的加载基址:1)通过FS得到TEB的地址
2)TEB偏移0x30处指向的是PEB指针
3)PEB偏移0x0C处指向PEB_LDR_DATA结构指针
4)PEB_LDR_DATA偏移0x1C处是InInitializationOrderModuleLis(模块初始化链表的头指针)
5)InInitializationOrderModuleLis中按顺序存在着此进程的初始化模块信息,在NT5.x内核中,第一个节点为ntdll.dll的基址,第二个节点为Kernel32.dll的基址; 在NT6.1内核中,第二个节点为KernelBase.dll的基址(包含着Kernel32.dll的大部分实现,其中就有GetProcAddress函数)
为此我们需要在Stub.h中定义两个函数,一个用于获取Kernel32.dll基址,一个用于获取GetProcAddress基址,代码如下:
extern DWORD GetKernel32Base(); // 获取Kernel32.dll的模块基址 extern DWORD GetGPAFunAddr(); // 获取GetProcAddress的函数地址在Stub.cpp中获取Kernel32.dll的代码如下:
DWORD GetKernel32Base() { DWORD dwKernel32Addr = 0; __asm { push eax mov eax,dword ptr fs:[0x30] // eax = PEB的地址 mov eax,[eax+0x0C] // eax = 指向PEB_LDR_DATA结构的指针 mov eax,[eax+0x1C] // eax = 模块初始化链表的头指针InInitializationOrderModuleList mov eax,[eax] // eax = 列表中的第二个条目 mov eax,[eax+0x08] // eax = 获取到的Kernel32.dll基址(Win7下获取的是KernelBase.dll的基址) mov dwKernel32Addr,eax pop eax } return dwKernel32Addr; }
注:可参考http://blog.csdn.net/obuyiseng/article/details/50456090
此时我们就能够获取到Kernel32.dll的基址,那么就可以通过遍历Kernel32.dll的导出表获取到GetProcAddress函数的基址了。
在Stub.cpp文件中获取GetProcAddress的基址代码如下:
DWORD GetGPAFunAddr() { DWORD dwAddrBase = GetKernel32Base(); // 1. 获取DOS头、NT头 PIMAGE_DOS_HEADER pDos_Header; PIMAGE_NT_HEADERS pNt_Header; pDos_Header = (PIMAGE_DOS_HEADER)dwAddrBase; pNt_Header = (PIMAGE_NT_HEADERS)(dwAddrBase + pDos_Header->e_lfanew); // 2. 获取导出表项 PIMAGE_DATA_DIRECTORY pDataDir; PIMAGE_EXPORT_DIRECTORY pExport; pDataDir = pNt_Header->OptionalHeader.DataDirectory; pDataDir = &pDataDir[IMAGE_DIRECTORY_ENTRY_EXPORT]; pExport = (PIMAGE_EXPORT_DIRECTORY)(dwAddrBase + pDataDir->VirtualAddress); // 3、获取导出表的必要信息 DWORD dwModOffset = pExport->Name; // 模块的名称 DWORD dwFunCount = pExport->NumberOfFunctions; // 导出函数的数量 DWORD dwNameCount = pExport->NumberOfNames; // 导出名称的数量 PDWORD pEAT = (PDWORD)(dwAddrBase + pExport->AddressOfFunctions); // 获取地址表的RVA PDWORD pENT = (PDWORD)(dwAddrBase + pExport->AddressOfNames); // 获取名称表的RVA PWORD pEIT = (PWORD)(dwAddrBase + pExport->AddressOfNameOrdinals); //获取索引表的RVA // 4、获取GetProAddress函数的地址 for (DWORD i = 0; i < dwFunCount; i++) { if (!pEAT[i]) { continue; } // 4.1 获取序号 DWORD dwID = pExport->Base + i; // 4.2 变量EIT 从中获取到 GetProcAddress的地址 for (DWORD dwIdx = 0; dwIdx < dwNameCount; dwIdx++) { // 序号表中的元素的值 对应着函数地址表的位置 if (pEIT[dwIdx] == i) { //根据序号获取到名称表中的名字 DWORD dwNameOffset = pENT[dwIdx]; char * pFunName = (char*)(dwAddrBase + dwNameOffset); //判断是否是GetProcAddress函数 if (!strcmp(pFunName, "GetProcAddress")) { // 获取EAT的地址 并将GetProcAddress地址返回 DWORD dwFunAddrOffset = pEAT[i]; return dwAddrBase + dwFunAddrOffset; } } } } return -1; }
6、动态加载API--获取到其他用到的API
在Stub.h中定义相关的函数和函数指针,如下:extern bool InitializationAPI(); // 初始化各个API // 基础API定义声明 typedef DWORD (WINAPI *LPGETPROCADDRESS)(HMODULE,LPCSTR); // GetProcAddress typedef HMODULE (WINAPI *LPLOADLIBRARYEX)(LPCTSTR,HANDLE,DWORD); // LoadLibaryEx extern LPGETPROCADDRESS g_funGetProcAddress; extern LPLOADLIBRARYEX g_funLoadLibraryEx; // 其他API定义声明 typedef VOID (WINAPI *LPEXITPROCESS)(UINT); // ExitProcess typedef int (WINAPI *LPMESSAGEBOX)(HWND,LPCTSTR,LPCTSTR,UINT); // MessageBox typedef HMODULE (WINAPI *LPGETMODULEHANDLE)(LPCWSTR); // GetModuleHandle typedef BOOL (WINAPI *LPVIRTUALPROTECT)(LPVOID,SIZE_T,DWORD,PDWORD); // VirtualProtect extern LPEXITPROCESS g_funExitProcess; extern LPMESSAGEBOX g_funMessageBox; extern LPGETMODULEHANDLE g_funGetModuleHandle; extern LPVIRTUALPROTECT g_funVirtualProtect;
在Stub.cpp中实现InitializationAPI函数,并初始化要使用的API函数。由于KernelBase.dll中没有导出LoadLibrary函数,所以为了兼容性考虑,我们在加载DLL的时候使用LoadLibraryExW。
LPGETPROCADDRESS g_funGetProcAddress = nullptr; LPLOADLIBRARYEX g_funLoadLibraryEx = nullptr; LPEXITPROCESS g_funExitProcess = nullptr; LPMESSAGEBOX g_funMessageBox = nullptr; LPGETMODULEHANDLE g_funGetModuleHandle = nullptr; LPVIRTUALPROTECT g_funVirtualProtect = nullptr; bool InitializationAPI() { HMODULE hModule; // 1. 初始化基础API 这里使用的是LoadLibraryExW g_funGetProcAddress = (LPGETPROCADDRESS)GetGPAFunAddr(); g_funLoadLibraryEx = (LPLOADLIBRARYEX)g_funGetProcAddress((HMODULE)GetKernel32Base(), "LoadLibraryExW"); // 2. 初始化其他API hModule = NULL; if (!(hModule = g_funLoadLibraryEx(L"kernel32.dll", NULL, NULL))) return false; g_funExitProcess = (LPEXITPROCESS)g_funGetProcAddress(hModule, "ExitProcess"); hModule = NULL; if (!(hModule = g_funLoadLibraryEx(L"user32.dll", NULL, NULL))) return false; g_funMessageBox = (LPMESSAGEBOX)g_funGetProcAddress(hModule, "MessageBoxW"); hModule = NULL; if (!(hModule = g_funLoadLibraryEx(L"kernel32.dll", NULL, NULL))) return false; g_funGetModuleHandle = (LPGETMODULEHANDLE)g_funGetProcAddress(hModule, "GetModuleHandleW"); hModule = NULL; if (!(hModule = g_funLoadLibraryEx(L"kernel32.dll", NULL, NULL))) return false; g_funVirtualProtect = (LPVIRTUALPROTECT)g_funGetProcAddress(hModule, "VirtualProtect"); return true; }
7、添加解密函数
在Stub.h中定义一个加密函数extern void Decrypt(); // 解密函数在Stub.cpp中实现该函数
void Decrypt() { // 在导出的全局变量中读取需解密区域的起始于结束VA PBYTE lpStart = g_stcParam.lpStartVA; PBYTE lpEnd = g_stcParam.lpEndVA; // 循环解密 while (lpStart < lpEnd) { *lpStart ^= 0x15; lpStart++; } }
8、完善start函数
我们先初始化GLOBAL_PARAM结构,并在Start函数中调用InitializationAPI()初始化所有API ,调动Decrypt()进行解密,并跳转到被加壳程序的原始OEP。extern __declspec(dllexport) GLOBAL_PARAM g_stcParam={0}; void start() { // 1. 初始化所有API if (!InitializationAPI()) return; // 2. 解密宿主程序 Decrypt(); // 3. 跳转到OEP __asm jmp g_stcParam.dwOEP; }
为了退出进程时的兼容性,在StubEntryPoint中添加退出进程的兼容代码,总体如下:
void __declspec(naked) StubEntryPoint() { __asm sub esp, 0x50; // 抬高栈顶,提高兼容性 start(); // 执行壳的主体部分 __asm add esp, 0x50; // 平衡堆栈 // 主动调用ExitProcess函数退出进程可以解决一些兼容性问题 if (g_funExitProcess) { g_funExitProcess(0); } __asm retn; }
相关文章推荐
- 大型网站的负载均衡器、db proxy和db
- android 一个 textview 设置不同的字体大小和颜色
- C# 判断socket断开
- lightning mdb 源代码分析(4)—MVCC/COW
- c中static静态变量与全局变量的区别
- MFC使用滚动条显示大图片
- 全国Uber优步司机奖励政策 (12月28日-1月3日)
- tornado 学习笔记17 HTTPServerRequest分析
- spring定时任务Quartz
- js上下滚动表格
- JS----几种过滤重复的数组的方法
- Java——抽象类
- 当listview的item为相对布局时item高度被挤压解决
- APP留存率
- 数据库第五章理论总结
- 第十个项目
- lightning mdb 源代码分析(4)&mdash;MVCC/COW
- linux中添加开机自启服务的方法
- 织梦翻页分页css代码案例分享(转载)
- 使用Metronic开发的后台管理系统的基础架构(bootstrap 3.3.5)