聊聊游戏辅助那些事上部第五篇——如何隐藏一个DLL模块?
隐藏DLL第一个方法是断链,这个比较简单:
VOID HideModule(HMODULE hLibModule) { PPEB_LDR_DATA pLdr = NULL; PLDR_MODULE FirstModule = NULL; PLDR_MODULE GurrentModule = NULL; __try { __asm { mov esi, fs:[0x30] mov esi, [esi + 0x0C] mov pLdr, esi }
FirstModule = (PLDR_MODULE)(pLdr->InLoadOrderModuleList.Flink); GurrentModule = FirstModule; while (!(GurrentModule->BaseAddress == hLibModule)) { GurrentModule = (PLDR_MODULE)(GurrentModule->InLoadOrderModuleList.Blink); if (GurrentModule == FirstModule) break; } if (GurrentModule->BaseAddress != hLibModule) return;
// // Dll解除链接 // ((PLDR_MODULE)(GurrentModule->InLoadOrderModuleList.Flink))->InLoadOrderModuleList.Blink = GurrentModule->InLoadOrderModuleList.Blink; ((PLDR_MODULE)(GurrentModule->InLoadOrderModuleList.Blink))->InLoadOrderModuleList.Flink = GurrentModule->InLoadOrderModuleList.Flink;
memset(GurrentModule->FullDllName.Buffer, 0, GurrentModule->FullDllName.Length); memset(GurrentModule, 0, sizeof(PLDR_MODULE));
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)hLibModule; PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(LPBYTE(hLibModule) + dosHeader->e_lfanew);
if ((dosHeader->e_magic == IMAGE_DOS_SIGNATURE) && (ntHeaders->Signature == IMAGE_NT_SIGNATURE)) { // // 这里可能引起写保护异常,需要用 VirtualProtect 解除写保护。 // memset(dosHeader, 0, sizeof(*dosHeader)); memset(ntHeaders, 0, sizeof(*ntHeaders)); } }
__except (EXCEPTION_EXECUTE_HANDLER) { return; } }
源码包中,HideDll.cpp 中保留了相关源码,虽然没有在程序中用到。 缺点是,只是常规的搜索方法中找不到相关的DLL模块,比如用 CreateToolhelp32Snapshot 查找模块。或者用NT函数。但用下面的源码就可以找到断链的DLL模块:
MEMORY_BASIC_INFORMATION mbi_thunk; PVOID AllocationBase = NULL; TCHAR FilePath[MAX_PATH]; for (LPSTR Addr = (LPSTR)0x00000000; ::VirtualQueryEx(hProcess, Addr, &mbi_thunk, sizeof(mbi_thunk)); Addr = LPSTR(mbi_thunk.BaseAddress) + mbi_thunk.RegionSize) { if ((mbi_thunk.AllocationBase > AllocationBase) && (GetMappedFileName(hProcess, mbi_thunk.BaseAddress, FilePath, _countof(FilePath)) > 0)) { AllocationBase = mbi_thunk.AllocationBase; KdPrint((_T("MODULE:%x, %s\r\n"), AllocationBase, FilePath)); } }
原形毕露了吧,哈哈。
第二个方法直接加载到内存。源码是一个宿主LoadDll.dll, 和一个要加载的寄生Inject.dll。Inject.dll加载为LoadDll.dll的一个资源 IDR_INJECT。LoadDll.dll 在加载完 Inject.dll 后主动 FreeLibrary 自己。
有几点需要注意: 1、VS2012以上版本编译的寄生DLL需要添加:项目属性->C/C++->命令行的其它选项中添加:/Zc:threadSafeInit- ,否则会产生莫名其妙的错误,分析是执行TLS时出错,暂时没有找到完美的解决方法。 2、dll_dllmain.cpp 的包含目录添加 $(VCInstallDir)crt\src\vcruntime,测试通过的是VS2015版本运行库。 3、dll_dllmain.cpp 的预处理器添加 _VCRT_BUILD 4、项目属性->链接器->高级的入口点设置为:_DllMainCRTStartup
几个重点函数:
DLL入口函数:汇编的工作是加载完成后直接调用FreeLibrary
extern "C" __declspec(naked) __declspec(dllexport) BOOL WINAPI _DllMainCRTStartup( HINSTANCE const instance, DWORD const reason, LPVOID const reserved ) { // // 建立堆栈 // _asm push ebp _asm mov ebp, esp if (reason == DLL_PROCESS_ATTACH) { // The /GS security cookie must be initialized before any exception // handling targeting the current image is registered. No function // using exception handling can be called in the current image until // after this call: __security_init_cookie(); }
if (dllmain_dispatch(instance, reason, reserved)) { if (reason == DLL_PROCESS_ATTACH) { _asm mov esp, ebp _asm pop ebp _asm pop eax // 返回地址 _asm add esp, 0x0C // 调用参数堆栈 _asm push DWORD PTR[esp - 0x0C] // FreeLibrary 的参数 instance 压栈 _asm push eax // 返回地址 压栈 _asm jmp FreeLibrary // 转去调用 FreeLibrary }
_asm mov eax, 1 } else { _asm xor eax, eax }
_asm mov esp, ebp _asm pop ebp _asm ret 0x0C }
// // 设置加载次数,静态加载的DLL的加载次数是 -1, FreeLibrary 不能释放DLL // void PreprocessUnloadDll(HMODULE hLibModule) { PPEB_LDR_DATA pLdr = NULL; PLDR_MODULE FirstModule = NULL; PLDR_MODULE GurrentModule = NULL; __try { __asm { mov esi, fs:[0x30] mov esi, [esi + 0x0C] mov pLdr, esi }
FirstModule = (PLDR_MODULE)(pLdr->InLoadOrderModuleList.Flink); GurrentModule = FirstModule; while (!(GurrentModule->BaseAddress == hLibModule)) { GurrentModule = (PLDR_MODULE)(GurrentModule->InLoadOrderModuleList.Blink); if (GurrentModule == FirstModule) { return; } }
// // 设置 LDRP_PROCESS_ATTACH_CALLED // GurrentModule->Flags |= LDRP_PROCESS_ATTACH_CALLED;
// // 设置 // int oldLoadCount = GurrentModule->LoadCount; GurrentModule->LoadCount = 1; return; }
__except (EXCEPTION_EXECUTE_HANDLER) { return; } }
// // 装载寄生DLL, GetClassInfoEx, RegisterClassEx 在这儿可不是为了创建窗口,只是为了保存一下加载到内存的DLL模块。我想来想去好像是这对哥们最造合这个工作 // HMODULE LoadInjectModule(HMODULE hModule) { KdFunctionLog(); HMODULE memdll = NULL; HRSRC hRsrc = FindResource(hModule, MAKEINTRESOURCE(IDR_INJECT), _T("DLL")); if (hRsrc) { HGLOBAL hGlobal = ::LoadResource(hModule, hRsrc); if (hGlobal) { LPCVOID pData = ::LockResource(hGlobal);
WCHAR ProcessMappedFileNameW[MAX_PATH]; size_t nLength = LoadString(hModule, IDR_INJECT, ProcessMappedFileNameW, sizeof(ProcessMappedFileNameW)); if (nLength < 1) { wcscpy_s(ProcessMappedFileNameW, L"LOADDLL_UNNAMED"); nLength = 15; }
// // 其实这步可以省略,GetClassInfoEx, RegisterClassEx 使用的名字空间限于进程内。 // swprintf_s(ProcessMappedFileNameW + nLength, sizeof(ProcessMappedFileNameW)/sizeof(ProcessMappedFileNameW[0]) - nLength, _T("#%04X"), GetCurrentProcessId());
WNDCLASSEX wndcls = { sizeof(WNDCLASSEX) }; if (GetClassInfoEx((HINSTANCE)GetModuleHandle(NULL), ProcessMappedFileNameW, &wndcls)) { memdll = (HMODULE)wndcls.lpfnWndProc; if (memdll) { KdPrint((_T("DLL 再次加载 : %x\r\n"), memdll)); } } else { memdll = CustomLoadLibrary(pData, SizeofResource(hModule, hRsrc)); LPSTR ThisModuleNameA = (LPSTR)CustomGetProcAddress(memdll, "ThisModuleFileNameA"); if (ThisModuleNameA) { GetThisModuleFileNameA(ThisModuleNameA, MAX_PATH); }
if (memdll) { wndcls.hInstance = (HINSTANCE)GetModuleHandle(NULL); wndcls.lpfnWndProc = (WNDPROC)memdll; wndcls.lpszClassName = ProcessMappedFileNameW; }
ATOM w = RegisterClassEx(&wndcls);
KdPrint((_T("DLL 首次加载 : %x\r\n"), memdll)); } UnlockResource(hRsrc); } } return memdll; }
MemoryModule.cpp 是网上找到的内存加载DLL源码修改而成,我把它改成的模板函数,支持多种加载形式。
本系列的第六篇估计要夭折了,本来计划以发布一个辅助模板作为上部的结尾,但是现在看来技术上还不够成熟。不过想一试半成品的可以给我留言,需要的人多我也不介意发出来的。或者QQ: 1872681795。 调试工具包几个月前准备发,后来发现有些保护又有了针对性的改进,另外也测试了TXP下的DXNXF,所以就拖到现在才发。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
上传的附件: