根据网上的教程,写了一个注入器的实现:
但是我发现注入后无法卸载,经过一系列的排除法验证后,最终确定了原因。
先上代码,动态库DllMain中使用线程的正确姿势:
#include
std::atomic_bool g_run_falg_ = true;
DWORD WINAPI ThreadProc(LPVOID lpThreadParameter) {
while (g_run_falg_) {
OutputDebugStringW(L"injector thread run");
std::this_thread::sleep_for(std::chrono::seconds(1));
}
OutputDebugStringW(L"injector thread exit");
return 0;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH: {
OutputDebugStringW(L"injector DLL_PROCESS_ATTACH");
DWORD dwThreadId;
// 使用windows API来创建线程,std::thread 会卡死,原因不明。
HANDLE hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, &dwThreadId);
std::this_thread::sleep_for(std::chrono::seconds(1));
CloseHandle(hThread);
break;
}
case DLL_THREAD_ATTACH:
OutputDebugStringW(L"injector DLL_THREAD_ATTACH");
break;
case DLL_THREAD_DETACH:
OutputDebugStringW(L"injector DLL_THREAD_DETACH");
break;
case DLL_PROCESS_DETACH: {
OutputDebugStringW(L"injector DLL_PROCESS_DETACH");
g_run_falg_ = false;
// 最坏的情况是,1秒后线程退出。需要等等线程完全释放后退出,否则微信崩溃
std::this_thread::sleep_for(std::chrono::seconds(1));
break;
}
}
return TRUE;
}
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
) {
switch (ul_reason_for_call) {
// 注意,默认的case不带break,会一直往下执行。其次break需要用{}包起来,否则作用域就是switch了
case DLL_PROCESS_ATTACH: {
OutputDebugStringW(L"injector DLL_PROCESS_ATTACH");
break;
}
case DLL_THREAD_ATTACH: {
OutputDebugStringW(L"injector DLL_THREAD_ATTACH");
break;
}
case DLL_THREAD_DETACH: {
OutputDebugStringW(L"injector DLL_THREAD_DETACH");
break;
}
case DLL_PROCESS_DETACH: {
OutputDebugStringW(L"injector DLL_PROCESS_DETACH");
break;
}
}
return TRUE;
}
使用od加载微信。然后点击注入,查看OD(附加后点击e,可查看加载的库)中动态库是否已成功加载。如下图,没有问题,已成功加载。
步骤4中,我点击了卸载,故输出了卸载的2个log,刷新一下OD,发现该动态库已经不在了。
通过上面的验证,我们初步明白了注入和卸载的执行顺序。有时候,我们可能会在DLL中启动Socket监听,故需要新启动一个线程去做这件事情,我使用了标准线程库std::thread,代码如下:
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH: {
OutputDebugStringW(L"injector DLL_PROCESS_ATTACH");
// 这里创建线程,模拟Socket监听线程
std::thread t([]() {
while (g_run_falg_) {
std::this_thread::sleep_for(std::chrono::seconds(1));
OutputDebugStringW(L"injector thread run");
}
OutputDebugStringW(L"injector thread exit");
});
t.detach();
break;
}
// ...
}
此时,我通过注入器再注入后,发现无法再卸载,Log也只有DLL_PROCESS_ATTACH,DLL_THREAD_ATTACH没有执行,说明被卡住了。后经过搜索验证,更换了std::thread后解决,具体见:正确的用法。
动态库DllMain入口中,线程正确启动和退出的姿势如下:
#include
std::atomic_bool g_run_falg_ = true;
DWORD WINAPI ThreadProc(LPVOID lpThreadParameter) {
while (g_run_falg_) {
OutputDebugStringW(L"injector thread run");
std::this_thread::sleep_for(std::chrono::seconds(1));
}
OutputDebugStringW(L"injector thread exit");
return 0;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH: {
OutputDebugStringW(L"injector DLL_PROCESS_ATTACH");
/*std::thread t([]() {
while (g_run_falg_) {
std::this_thread::sleep_for(std::chrono::seconds(1));
OutputDebugStringW(L"injector thread run");
}
OutputDebugStringW(L"injector thread exit");
});
t.detach();*/
DWORD dwThreadId;
// 使用windows API来创建线程,std::thread 会卡死,原因不明。
HANDLE hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, &dwThreadId);
std::this_thread::sleep_for(std::chrono::seconds(1));
CloseHandle(hThread);
break;
}
case DLL_THREAD_ATTACH:
OutputDebugStringW(L"injector DLL_THREAD_ATTACH");
break;
case DLL_THREAD_DETACH:
OutputDebugStringW(L"injector DLL_THREAD_DETACH");
break;
case DLL_PROCESS_DETACH: {
OutputDebugStringW(L"injector DLL_PROCESS_DETACH");
g_run_falg_ = false;
// 最坏的情况是,1秒后线程退出。需要等等线程完全释放后退出,否则微信崩溃
std::this_thread::sleep_for(std::chrono::seconds(1));
break;
}
}
return TRUE;
}
#ifndef _INJECT_TOOLS_4B2B684A_9CA6_4A9E_B7C0_9EC427D54FD7_
#define _INJECT_TOOLS_4B2B684A_9CA6_4A9E_B7C0_9EC427D54FD7_
#include
namespace core {
/** @fn ProcessNameFindPID
* @brief 通过进程名获取进程ID
* @param ProcessName:进程名字,带.exe
* @return
*/
DWORD ProcessNameFindPID(const char* processName) noexcept;
/** @fn getFileName
* @brief 从路径中截取文件名
* @param filePath:完整文件路径名
* @return
*/
std::wstring getFileName(const std::wstring& filePath);
/** @fn StartProcess
* @brief 启动进程
* @param filePath:进程完整路径
* firstWindowName:判断成功的窗口名,微信为WeChatLoginWndForPC
* @return
*/
void StartProcess(std::wstring filePath, std::wstring firstWindowName = L"WeChatLoginWndForPC");
/** @fn InjectDll
* @brief 注入dll,对应的动态库入口DllMain会执行一次DLL_PROCESS_ATTACH,请在这里挂载Hook等初始化动作。
* @param exeName:进程名
* targetDllFilePath:要注入动态库的路径
* @return
*/
void InjectDll(const std::string& exeName, const std::wstring& targetDllFilePath);
/** @fn UnloadDll
* @brief 卸载DLL,对应的动态库入口DllMain会执行一次DLL_PROCESS_DETACH,请在这里释放Hook、线程等动作。
* PS:如果有残留线程,则将无法卸载DLL!微信关闭后,也将无法正确退出,从而变成僵尸进程。
* @param exeName:要卸载动态库的目标进程
* targetDllFilename:要卸载动态库的名字
* @return
*/
void UnloadDll(const std::string& exeName, const std::wstring& targetDllFilePath);
/** @fn CheckIsInject
* @brief 检测DLL是否已经注入
* @param dwProcessid:进程ID
* targetDllFilePath:文件路径名
* @return
*/
bool CheckIsInject(DWORD processid, const std::wstring& targetDllFilePath);
}
#endif//_INJECT_TOOLS_4B2B684A_9CA6_4A9E_B7C0_9EC427D54FD7_
#include "stdafx.h"
#include "inject_tools.h"
#include
#include
#include
#include
#include
#pragma comment(lib,"advapi32")
namespace core {
DWORD ProcessNameFindPID(const char* ProcessName) noexcept {
PROCESSENTRY32 pe32 = {
0 };
pe32.dwSize = sizeof(PROCESSENTRY32);
HANDLE hProcess = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
if (Process32First(hProcess, &pe32) == TRUE) {
do {
USES_CONVERSION;
if (strcmp(ProcessName, W2A(pe32.szExeFile)) == 0) {
return pe32.th32ProcessID;
}
} while (Process32Next(hProcess, &pe32));
}
return 0;
}
void StartProcess(std::wstring filePath, std::wstring firstWindowName) {
if (!nbase::FilePathIsExist(filePath, false)) {
throw std::exception("exe is not exist.");
}
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
si.dwFlags = STARTF_USESHOWWINDOW; // 指定wShowWindow成员有效
si.wShowWindow = TRUE; // 此成员设为TRUE的话则显示新建进程的主窗口,为FALSE的话则不显示
if (!CreateProcess(filePath.c_str(), NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
throw std::exception("create wechat process error, regedit may be changed!");
}
if (!firstWindowName.empty()) {
HWND hwnd = nullptr;
int i = 0;
while (nullptr == hwnd) {
hwnd = FindWindow(firstWindowName.c_str(), NULL);
++i;
if (i >= 3)
break;
Sleep(500);
}
if (nullptr == hwnd) {
throw std::exception("not find the window,please check it");
}
}
}
void InjectDll(const std::string& exeName, const std::wstring& targetDllFilePath) {
//获取微信Pid
DWORD dwPid = ProcessNameFindPID(exeName.c_str());
if (dwPid == 0) {
throw std::exception("not find process.");
}
std::wstring fileName = getFileName(targetDllFilePath);
//检测dll是否已经注入
if (!CheckIsInject(dwPid, targetDllFilePath)) {
//打开进程
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
if (hProcess == nullptr) {
throw std::exception(nbase::UTF16ToUTF8(L"进程打开失败").c_str());
}
// 在微信进程中申请内存
// 1参数进程句柄,2分配空间位置为空就随机,3一个路径地址,4权限,5可读可写
LPVOID pAddress = VirtualAllocEx(hProcess, NULL, MAX_PATH, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (pAddress == nullptr) {
throw std::exception(nbase::UTF16ToUTF8(L"内存分配失败").c_str());
}
//写入dll路径到微信进程
const auto path = nbase::UTF16ToUTF8(targetDllFilePath);
if (WriteProcessMemory(hProcess, pAddress, path.c_str(), path.length(), NULL) == 0) {
throw std::exception(nbase::UTF16ToUTF8(L"路径写入失败").c_str());
}
//获取LoadLibraryA函数地址
FARPROC pLoadLibraryAddress = GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryA");
if (pLoadLibraryAddress == NULL) {
throw std::exception(nbase::UTF16ToUTF8(L"获取LoadLibraryA函数地址失败").c_str());
}
// 注入成功后开线程操作
// CreateRemoteThread第四个参数需要拿自己的地址用 GetModuleHandle 获取Kernel32基址
HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pLoadLibraryAddress, pAddress, 0, NULL);
if (hRemoteThread == NULL) {
throw std::exception(nbase::UTF16ToUTF8(L"远程线程注入失败").c_str());
}
CloseHandle(hRemoteThread);
CloseHandle(hProcess);
}
}
std::wstring getFileName(const std::wstring& filePath) {
const auto index = filePath.find_last_of(L"\\");
if (index == std::wstring::npos) {
throw std::exception("invalid filePath");
}
std::wstring fileName = filePath.substr(index + 1, filePath.length() - index - 1);
return std::move(fileName);
}
bool CheckIsInject(DWORD processid, const std::wstring& targetDllFilePath) {
HANDLE hModuleSnap = INVALID_HANDLE_VALUE;
//初始化模块信息结构体
MODULEENTRY32 me32 = {
sizeof(MODULEENTRY32) };
//创建模块快照 1 快照类型 2 进程ID
hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, processid);
//如果句柄无效就返回false
if (hModuleSnap == INVALID_HANDLE_VALUE) {
throw std::exception(nbase::UTF16ToUTF8(L"创建模块快照失败").c_str());
}
//通过模块快照句柄获取第一个模块的信息
if (!Module32First(hModuleSnap, &me32)) {
//获取失败则关闭句柄
CloseHandle(hModuleSnap);
throw std::exception(nbase::UTF16ToUTF8(L"获取第一个模块的信息失败").c_str());
}
std::wstring fileName = getFileName(targetDllFilePath);
do {
if (std::wstring(me32.szModule) == fileName) {
return true;
}
} while (Module32Next(hModuleSnap, &me32));
return false;
}
void UnloadDll(const std::string& exeName, const std::wstring& targetDllFilePath) {
//获取微信Pid
DWORD dwPid = ProcessNameFindPID(exeName.c_str());
if (dwPid == 0) {
throw std::exception(nbase::UTF16ToUTF8(L"找不到进程").c_str());
}
std::wstring fileName = getFileName(targetDllFilePath);
//遍历模块
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPid);
MODULEENTRY32 ME32 = {
0 };
ME32.dwSize = sizeof(MODULEENTRY32);
BOOL isNext = Module32First(hSnap, &ME32);
BOOL flag = FALSE;
while (isNext) {
USES_CONVERSION;
if (strcmp(W2A(ME32.szModule), nbase::UTF16ToUTF8(fileName).c_str()) == 0) {
flag = TRUE;
break;
}
isNext = Module32Next(hSnap, &ME32);
}
CloseHandle(hSnap);
if (flag == FALSE) {
throw std::exception(nbase::UTF16ToUTF8(L"找不到目标模块").c_str());
}
//打开目标进程
HANDLE hPro = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
//获取FreeLibrary函数地址
FARPROC pFun = GetProcAddress(GetModuleHandleA("kernel32.dll"), "FreeLibrary");
//创建远程线程执行FreeLibrary
HANDLE hThread = CreateRemoteThread(hPro, NULL, 0, (LPTHREAD_START_ROUTINE)pFun, ME32.modBaseAddr, 0, NULL);
if (!hThread) {
throw std::exception(nbase::UTF16ToUTF8(L"创建远程线程失败").c_str());
}
if (WaitForSingleObject(hThread, 2 * 1000) == WAIT_TIMEOUT) {
CloseHandle(hThread);
CloseHandle(hPro);
throw std::exception(nbase::UTF16ToUTF8(L"远程线程执行超时,动态库或许有残留线程没退出!").c_str());
}
CloseHandle(hThread);
CloseHandle(hPro);
}
}
相关代码我已经放在了 https://github.com/xmcy0011/study-trace