先上源码:
#include "Inject_Main.h" #include "resource.h" #include <Windows.h> #include <TlHelp32.h> #include <string> #include <TCHAR.H> using namespace std; /// <summary> /// 通过进程名称获取该进程句柄 /// </summary> /// <param name="processName"></param> /// <returns>成功返回 DWORD,失败返回 0</returns> DWORD GetProcessByName(CONST TCHAR* processName) { // 获取到整个系统的进程 HANDLE processALL = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); // 定义一个容器,该容器用来接收,进程信息 PROCESSENTRY32W processInfo = { 0 }; processInfo.dwSize = sizeof(PROCESSENTRY32W); // 根据进程名称,循环判断是否是指定的进程 do { if (_tcscmp(processInfo.szExeFile, processName) == 0) { // 释放进程快照,防止内存泄露 CloseHandle(processALL); // 如果是返回指定进程句柄 return processInfo.th32ProcessID; } // 一个迭代函数 } while (Process32Next(processALL, &processInfo)); // 释放进程快照,防止内存泄露 CloseHandle(processALL); return 0; } /// <summary> /// 获取指定 DLL 的内存地址 /// </summary> /// <param name="pid"></param> /// <param name="moduleName"></param> /// <returns></returns> HMODULE GetProcessModuleHandle(DWORD pid, CONST TCHAR* moduleName) { MODULEENTRY32 moduleEntry; HANDLE handle = NULL; handle = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid); if (!handle) { CloseHandle(handle); return NULL; } ZeroMemory(&moduleEntry, sizeof(MODULEENTRY32)); moduleEntry.dwSize = sizeof(MODULEENTRY32); if (!Module32First(handle, &moduleEntry)) { CloseHandle(handle); return NULL; } do { if (_tcscmp(moduleEntry.szModule, moduleName) == 0) { // 释放进程快照,防止内存泄露 CloseHandle(handle); return moduleEntry.hModule; } } while (Module32Next(handle, &moduleEntry)); CloseHandle(handle); return 0; } /// <summary> /// 把指定DLL注入到指定进程中 /// </summary> /// <param name="processName">processName 进程名称</param> /// <param name="dllPath">dllPath dll路径</param> void InjectDll(const wchar_t* processName, const char* dllPath) { // 获取指定进程的句柄 DWORD dword = GetProcessByName(processName); if (dword == 0) { MessageBox(NULL, TEXT("没有找到指定进程"), TEXT("错误"), 0); return; } // 打开指定进程 HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dword); if (hProcess == NULL) { MessageBox(NULL, TEXT("指定进程打开失败"), TEXT("错误"), 0); return; } /* 在指定进程的地址,开辟一块内存空间,用来保存 DLL的路径信息 LPVOID VirtualAllocEx( [in] HANDLE hProcess, 在那个进程中开辟内存 [in, optional] LPVOID lpAddress, 开辟内存的起始地址 (NULL,不需要控制起始位置) [in] SIZE_T dwSize, 开辟内存的大小(当前保存的内容是 DLL的路径) [in] DWORD flAllocationType, 内存分配的类型。(开辟内存) [in] DWORD flProtect,设置内存的权限 (可读可写) ); */ LPVOID DLLAddress = VirtualAllocEx(hProcess, NULL, strlen(dllPath), MEM_COMMIT, PAGE_READWRITE); /* 把DLL的路径,写入到刚开辟出来的内存中 BOOL WriteProcessMemory( [in] HANDLE hProcess, // 指定的进程 [in] LPVOID lpBaseAddress, // DLL路径字符串,写入的基址 [in] LPCVOID lpBuffer, // DLL路径字符串,的指针 [in] SIZE_T nSize, // 需要写入内存的字节长度 [out] SIZE_T *lpNumberOfBytesWritten // [out] 返回一个指针,不需要,NULL ); */ if (WriteProcessMemory(hProcess, DLLAddress, dllPath, strlen(dllPath), NULL) == 0) { MessageBox(NULL, TEXT("路径写入失败"), TEXT("错误"), 0); return; } // 获取 Kernel32.dll 这个模块 HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); // 在 Kernel32.dll 模块中找到 LoadLibrary 这个函数的内存地址 LPVOID loadADD = GetProcAddress(k32, "LoadLibraryA"); /* 在指定进程中,创建一个线程 并通过这个线程,调用 LoadLibrary 函数 通过 LoadLibrary 函数,把 DLL 载入到目标进程中 HANDLE CreateRemoteThread( [in] HANDLE hProcess, // 指定进程 [in] LPSECURITY_ATTRIBUTES lpThreadAttributes, // 设置线程安全属性,表示线程是否可以继承,NULL就够了 [in] SIZE_T dwStackSize, // 堆栈的初始大小,0 表示使用可执行文件的默认大小 [in] LPTHREAD_START_ROUTINE lpStartAddress, // 远程进程中,需要执行的那个函数的指针 [in] LPVOID lpParameter, // 目前进程中 DLL路径的指针 [in] DWORD dwCreationFlags, // 0 线程在创建后立即运行。 [out] LPDWORD lpThreadId // [out] 当前不需要这个返回值 ); */ HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)loadADD, DLLAddress, 0, NULL); // 释放指定的模块 CloseHandle(hThread); CloseHandle(hProcess); } /// <summary> /// 把指定进程中的DLL卸载掉 /// </summary> /// <param name="processName"></param> /// <param name="dllPath"></param> void UnInjectDll(const wchar_t* processName) { // 通过进程名称获取该进程句柄 DWORD dword = GetProcessByName(processName); if (dword == 0) { MessageBox(NULL, TEXT("没有找到指定进程"), TEXT("错误"), 0); return; } // 获取指定进程中指定模块的内存地址 HMODULE hmodule = GetProcessModuleHandle(dword, L"WX_Read_Write.dll"); // 打开指定进程 HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dword); if (hProcess == NULL) { MessageBox(NULL, TEXT("指定进程打开失败"), TEXT("错误"), 0); return; } // 获取 Kernel32.dll 这个模块 HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); // 在 Kernel32.dll 模块中找到 LoadLibrary 这个函数的内存地址 LPVOID loadADD = GetProcAddress(k32, "FreeLibrary"); HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)loadADD, (LPVOID)hmodule, 0, NULL); // 释放指定的模块 CloseHandle(hThread); CloseHandle(hProcess); } /// <summary> /// /// </summary> /// <param name="hwndDlg"></param> /// <param name="uMsg"></param> /// <param name="wParam"></param> /// <param name="lParam"></param> /// <returns></returns> INT_PTR CALLBACK dialogProc(_In_ HWND hwndDlg, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam) { wchar_t processName[100] = L"WeChat.exe"; char dllPath[400] = { "C://Users//qiaoas//documents//visual studio 2015//Projects//ConsoleApplication1//Debug//WX_Read_Write.dll" }; switch (uMsg) { case WM_INITDIALOG: break; case WM_CLOSE: EndDialog(hwndDlg, 0); // 关闭窗体 break; case WM_COMMAND: /*GetDlgItemText(hwndDlg, Text_ProcessName, processName, sizeof(processName)); GetDlgItemText(hwndDlg, Text_DLLPath, (LPWSTR)dllPath, sizeof(dllPath));*/ if (wParam == Btn_Inject_DLL) { if (sizeof(processName) == 0) { MessageBox(NULL, TEXT("进程名称不能为空"), TEXT("错误"), 0); } if (sizeof(dllPath) == 0) { MessageBox(NULL, TEXT("DLL路径不能为空"), TEXT("错误"), 0); } InjectDll(processName, dllPath); // 注入DLL } if (wParam == Btn_unInject_DLL) { UnInjectDll(processName); // 卸载DLL } break; default: break; } return FALSE; } /// <summary> /// 初始化 /// </summary> /// <param name="hInstance"></param> /// <param name="hPrevInstance"></param> /// <param name="lpCmdLine"></param> /// <param name="nCmdShow"></param> /// <returns></returns> int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, &dialogProc); return 0; }
初学C++,代码可能有些地方写的不够好,但是注入卸载是完全没问题的。
注入逻辑解释:
使用
CreateRemoteThread
函数可以为目标进程创建一个新的线程。在一个进程为另一个进程创建的线程就是远程线程。
使用
LoadLibrary
函数把指定的DLL加载到进程中因此就可以在创建远程线程的同时调用
LoadLibrary
函数,把指定的DLL
加载到目标进程中。
为什么创建远程线程的时候调用 LoadLibrary 函数就能把 DLL 注入到目标进程中?
LoadLibrary
函数是 Kernel32.dll 中的一个成员Kernel32.dll
这个DLL是创建进程必须的一个DLL,并且所有进程在内存中指向的 Kernel32.dll 是同一个地址LoadLibrary
函数的地址就够了为什么要在目标进程中开辟一块内存,再把DLL路径写入到块内存中?
LoadLibrary
函数需要一个参数,就是DLL的路径DLL
的路径直接写入到目标进程中。VirtualAllocEx
函数,在目标进程中开辟一块空间,用来存放DLL路径WriteProcessMemory
函数,把DLL的路径写入进去GetModuleHandle
获取 Kernel32.dll 模块GetProcAddress
获取 LoadLibraryA 函数在内存中的地址CreateRemoteThread
创建远程线程,并调用 LoadLibraryA 函数LoadLibrary
、LoadLibraryA
、LoadLibraryW
这三者的区别。
LoadLibrary
是一个宏,可以根据字符集的不同,自动决定是使用 LoadLibraryA 还是 LoadLibraryW
LoadLibrary 宏定义的源码:
WINBASEAPI _Ret_maybenull_ HMODULE WINAPI LoadLibraryA( _In_ LPCSTR lpLibFileName ); WINBASEAPI _Ret_maybenull_ HMODULE WINAPI LoadLibraryW( _In_ LPCWSTR lpLibFileName ); #ifdef UNICODE #define LoadLibrary LoadLibraryW #else #define LoadLibrary LoadLibraryA #endif // !UNICODE
卸载逻辑:
使用 CreateRemoteThread 函数创建一个远程线程
调用 FreeLibrary 函数,卸载DLL
FreeLibrary 函数在 Kernel32.dll 模块中,逻辑同上
FreeLibrary 函数需要 DLL 的内存地址
遍历进程快照可以获取到指定模块的内存地址
卸载和注入的思路都是一样的
确认DLL是否注入到目标进程中:
方式一:使用 procexp
方式二:Cheat Engine
确认 Kernel32.dll 中的 FreeLibrary 和 LoadLibraryA 在多个进程中是否指向同一块内存地址:
可以通过CE查看多个进程中 Kernel32.dll 的内存地址是否相同
再通过 Kernel32.dll 中函数的内存地址,确认 FreeLibrary 和 LoadLibraryA 这两个函数