使用CreateRemoteThread卸载注入DLL时触发Access violation问题排查
DLL注入后调用函数再卸载时目标进程崩溃的问题
我尝试将DLL干净注入前台窗口进程,调用DLL内的简单函数后再卸载,但卸载时目标进程会崩溃。
注入器代码
HWND fgwnd = GetForegroundWindow(); DWORD cur_thread = GetCurrentThreadId(); DWORD fg_pid = 0; DWORD fg_thread = GetWindowThreadProcessId(fgwnd, &fg_pid); BOOL res = 0; const char* inj_path = "C:\\Users\\pc\\source\\repos\\hothook\\x64\\Debug\\fground_injector.dll"; // 获取目标进程句柄 HANDLE victim = OpenProcess(PROCESS_ALL_ACCESS, FALSE, fg_pid); // 获取kernel32.dll中LoadLibraryA和FreeLibrary的地址 PVOID llib = (LPVOID)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryA"); PVOID flib = (LPVOID)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "FreeLibrary"); // 在目标进程中分配内存 LPVOID inj_path_victim = (LPVOID)VirtualAllocEx(victim, NULL, strlen(inj_path)+1, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); // 将DLL路径写入目标进程内存 SIZE_T written; res = WriteProcessMemory(victim, inj_path_victim, inj_path, strlen(inj_path)+1, &written); // 创建远程线程注入DLL HANDLE inj_llib_thread = CreateRemoteThread( /*I*/ victim, // 目标进程句柄 /*I*/ NULL, // 线程安全属性 /*I*/ 0, // 初始栈大小(默认) /*I*/ (LPTHREAD_START_ROUTINE)llib, // 线程入口(LoadLibraryA) /*I*/ inj_path_victim, // 传入参数(DLL路径) /*I*/ 0, // 立即启动线程 /*O|O*/ NULL); // 不获取线程ID // 等待注入完成 res = WaitForSingleObject(inj_llib_thread, INFINITE); // 获取DLL加载后的基地址 DWORD llib_exit; res = GetExitCodeThread(inj_llib_thread, &llib_exit); // 释放目标进程中分配的内存 res = VirtualFreeEx(victim, inj_path_victim, 0, MEM_RELEASE); // 调用注入DLL中的函数 HMODULE fg_inj = LoadLibraryA(inj_path); PVOID inj_t_proc = (LPVOID)GetProcAddress(fg_inj, "injectThread"); // 创建远程线程调用函数 HANDLE inj_thread = CreateRemoteThread( /*I*/ victim, // 目标进程句柄 /*I*/ NULL, // 线程安全属性 /*I*/ 0, // 初始栈大小(默认) /*I*/ (LPTHREAD_START_ROUTINE)inj_t_proc, // 线程入口(injectThread) /*I*/ NULL, // 无参数 /*I*/ 0, // 立即启动线程 /*O|O*/ NULL); // 不获取线程ID // 等待函数执行完成 res = WaitForSingleObject(inj_thread, INFINITE); // 创建远程线程卸载DLL HANDLE inj_flib_thread = CreateRemoteThread( /*I*/ victim, // 目标进程句柄 /*I*/ NULL, // 线程安全属性 /*I*/ 0, // 初始栈大小(默认) /*I*/ (LPTHREAD_START_ROUTINE)flib, // 线程入口(FreeLibrary) /*I*/ (LPVOID)llib_exit, // 传入参数(DLL基地址) /*I*/ 0, // 立即启动线程 /*O|O*/ NULL); // 不获取线程ID // 等待卸载完成 res = WaitForSingleObject(inj_flib_thread, INFINITE); DWORD flib_exit; res = GetExitCodeThread(inj_flib_thread, &flib_exit); // 释放宿主进程中加载的DLL(即使循环调用100次也无法卸载) FreeLibrary(fg_inj); // 关闭所有句柄 CloseHandle(victim); CloseHandle(inj_llib_thread); CloseHandle(inj_thread); CloseHandle(inj_flib_thread);
注入DLL代码
BOOL APIENTRY DllMain(HMODULE hm, DWORD call_reas, LPVOID reserved) { switch(call_reas) { case DLL_PROCESS_ATTACH: { DisableThreadLibraryCalls(hm); // 试过注释掉这行,崩溃情况依旧 break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; case DLL_PROCESS_DETACH: break; } return TRUE; } DWORD injectThread(LPVOID p) { //AllowSetForegroundWindow(ASFW_ANY); // 原本要实现让宿主进程获取前台窗口权限,但注释后还是崩溃 return 0; }
崩溃详情
崩溃发生在创建FreeLibrary远程线程之后,异常信息如下:
Exception thrown at 0x00007FFE598C7170 in maudswch.exe: 0xC0000005: Access violation executing location 0x00007FFE598C7170. 00007ffe598c7170() kernel32.dll!00007ffe9ac47034() ntdll.dll!00007ffe9c682651() Not Flagged 50864 0 Main Thread Main Thread win32u.dll!00007ffe99e51104 Not Flagged 47548 0 Worker Thread ntdll.dll thread ntdll.dll!00007ffe9c6d0794 Not Flagged 41096 0 Worker Thread ntdll.dll thread ntdll.dll!00007ffe9c6d0794 Not Flagged 43456 0 Worker Thread ntdll.dll thread ntdll.dll!00007ffe9c6d0794 Not Flagged > 29820 0 Worker Thread Win64 Thread 00007ffe598c7170
测试情况与补充信息
- 多个目标进程(CMD、记事本、自定义GUI程序)测试结果一致:DLL成功注入,函数能执行,但卸载时必崩溃。
- FreeLibrary远程线程完成耗时极长,有时1.5秒甚至7秒。
- 调试发现:宿主进程和目标进程中DLL的基地址、函数地址完全一致,排除地址截断导致崩溃的可能。
问题分析与解决方案
核心问题
崩溃的根源在于:你通过远程线程调用injectThread(属于待卸载的DLL),同时又在外部启动另一个远程线程调用FreeLibrary卸载该DLL。当FreeLibrary执行时,DLL的代码段会被系统标记为可回收,而此时injectThread的线程可能还在执行返回逻辑,访问已经被释放的内存,直接触发访问违例。
另外,宿主进程中FreeLibrary无法卸载DLL,是因为GetProcAddress会增加DLL的引用计数,单次调用无法清零。
修复步骤
让DLL内部完成卸载逻辑
修改DLL的injectThread,在业务逻辑执行完成后,主动调用FreeLibraryAndExitThread,确保线程在DLL卸载前安全退出:DWORD injectThread(LPVOID p) { // 执行你的业务逻辑,比如原本的AllowSetForegroundWindow(ASFW_ANY); // 获取当前DLL的模块句柄 HMODULE hModule = NULL; GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCSTR)injectThread, &hModule); // 卸载DLL并退出线程 FreeLibraryAndExitThread(hModule, 0); return 0; // 此行不会被执行,仅用于编译通过 }移除外部的FreeLibrary远程线程
注入器中无需再创建卸载DLL的远程线程,只需等待injectThread的远程线程完成即可——线程会自行处理DLL卸载。修复宿主进程DLL无法卸载的问题
循环调用FreeLibrary直到返回FALSE,确保引用计数清零:while (FreeLibrary(fg_inj)) {}规避ASLR风险(可选但推荐)
不要依赖宿主与目标进程DLL基地址一致的巧合,通过DLL内部获取模块句柄的方式,能兼容ASLR地址随机化的场景。
修复后的注入器关键代码片段
// ...(注入DLL的逻辑保持不变) // 调用注入DLL中的函数 HMODULE fg_inj = LoadLibraryA(inj_path); PVOID inj_t_proc = (LPVOID)GetProcAddress(fg_inj, "injectThread"); HANDLE inj_thread = CreateRemoteThread(victim, NULL, 0, (LPTHREAD_START_ROUTINE)inj_t_proc, NULL, 0, NULL); // 等待线程完成(线程内部会卸载DLL) res = WaitForSingleObject(inj_thread, INFINITE); // 释放宿主进程中的DLL while (FreeLibrary(fg_inj)) {} // 关闭句柄 CloseHandle(victim); CloseHandle(inj_llib_thread); CloseHandle(inj_thread); // 移除原有的inj_flib_thread相关代码
内容的提问来源于stack exchange,提问作者ScienceDiscoverer
相关产品推荐
相关产品推荐

