步骤:
(1)用VirtualAllocEx函数在远程进程的地址空间中分配一块内存
(2)用WriteProcessMemory把函数DLL的路径名字复制到第一步分配的内存中
(3)用GetProcAddress函数来得到LoadLibraryW或LoadLibraryA函数在Kernel32.dll中的实际地址
解释下为什么可以这么做:
*为什么可以直接用GetProcAddress获取LoadLibraryW在Kernel32.dll的地址然后传给CreateRemoteThread中,因为Kernel32.dll在所有进程中的基地址都是一样的。
*既然如此那为什么不直接传递LoadLibrary给CreateRemoteThread还需要通过GetProcAddress获取地址呢?因为我们自己的模块会包含一个导入段,改段由一系列转换函数组成,这些转换函数用来跳转到导入函数。因此代码调用LoadLibrary时链接器会生成一个调用,来调用我们模块中的导入段中的一个转换函数,这个转换函数会跳转到实际的函数中。
(4)用CreateRemoteThread在远程进程中创建一个线程,让新线程调用正确的LoadLibrary函数并在参数中传入第一步分配的内存地址,这个时候,DLL以及被注入到远程进程的地址空间中,DLL的DllMain函数会受到DLL_PROCESS_ATTACH通知并且可以执行我们想要执行的代码,当DllMain返回的时候,远程线程会从LoadLibraryW/A调用返回到BaseThreadStart函数,然后该函数调用ExitThread离开线程
(5)用VirtualFreeEx来释放第一步申请的内存
(6)用GetProcAddress获取FreeLibrary函数在Kernel32.dll中的实际地址
(7)用CreateRemoteThread函数在远程线程中创建一个线程,该线程调用FreeLibrary函数并传入远程DLL的HMODULE
给出的例子有两部分:一个是要注入的DLL,成为InjLKDLL,一个是我们自己的程序用于注入该DLL,为InjDll.
先给出InjLKDLL的代码:
#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容
// Windows 头文件
#include <windows.h>
#include <string>
#include <sstream>
#include <fstream>
#include <Windows.h>
#include <TlHelp32.h>
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
MessageBoxA(NULL, "DLL Attached!\n", "Game Hacking", MB_OK | MB_TOPMOST);
//我们要处理的逻辑
DWORD dwCurProcessId = GetCurrentProcessId();
char szProcessName[MAX_PATH];
GetModuleFileNameA(NULL, szProcessName, MAX_PATH);
std::string strProcessName = szProcessName;
SIZE_T index = strProcessName.find_last_of('\\');
std::string strExeName = strProcessName.substr(index + 1);
char outFileName[MAX_PATH] = { 0 };
sprintf_s(outFileName, "D:\\%s-%lu.txt", strExeName.c_str(), dwCurProcessId);
std::wofstream outFile;
outFile.open(outFileName, std::ios::out);
if (outFile.good())
{
outFile.imbue(std::locale("chs"));
outFile << L"注入成功:" << dwCurProcessId<< std::endl;
HANDLE hthSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwCurProcessId);
if (hthSnapshot == INVALID_HANDLE_VALUE)
{
outFile << L"读取模块失败" << std::endl;
break;
}
MODULEENTRY32 me = { sizeof(me) };
BOOL bFound = FALSE;
BOOL bMoreMods = Module32FirstW(hthSnapshot, &me);
WCHAR szContext[MAX_PATH * 2] = { 0 };
for (; bMoreMods; bMoreMods = Module32NextW(hthSnapshot, &me))
{
wsprintf(szContext, L"0x%016x\t%s", me.modBaseAddr, me.szExePath);
outFile << szContext << std::endl;
}
CloseHandle(hthSnapshot);
outFile.close();
}
break;
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
InjDll代码实现如下:
#include <iostream>
#include <fstream>
#include <Windows.h>
#include <TlHelp32.h>
BOOL InjectDLL(DWORD dwProcessId, PCWSTR pszDllFile);
BOOL EjectDLL(DWORD dwProcessId, PCWSTR pszDllFile);
int main()
{
WCHAR szDllFile[MAX_PATH] = L"D:\\VSWorkSpace\\InjDll\\x64\\Release\\InjLKDLL.dll";
DWORD dwProcessId = 0;
do
{
std::cout << "输入要注入的进程号:" << std::endl;
std::cin >> dwProcessId;
if (dwProcessId > 0)
{
if (InjectDLL(dwProcessId, szDllFile))
{
std::cout << "注入成功" << std::endl;
if (EjectDLL(dwProcessId, szDllFile))
std::cout << "卸载成功" << std::endl;
else
std::cout << "卸载失败" << std::endl;
}
else
{
std::cout << "注入失败" << std::endl;
}
}
else
break;
} while (1);
std::cout << "Hello World!\n";
system("pause");
return 1;
}
BOOL InjectDLL(DWORD dwProcessId, PCWSTR pszDllFile)
{
BOOL bOk = FALSE;
HANDLE hProcess = NULL, hThread = NULL;
PWSTR pszDllFileRemote = NULL;
HANDLE hthSnapshot = NULL;
do
{
hProcess = OpenProcess(
PROCESS_ALL_ACCESS,
FALSE,
dwProcessId);
if (hProcess == NULL) break;
//计算DLL路径需要的大小
int cch = 1 + lstrlenW(pszDllFile);
int cb = cch * sizeof(wchar_t);
pszDllFileRemote = (PWSTR)VirtualAllocEx(
hProcess,
NULL,
cb,
MEM_COMMIT,
PAGE_READWRITE);
if (pszDllFileRemote == NULL) break;
if (!WriteProcessMemory(
hProcess,
pszDllFileRemote,
(PVOID)pszDllFile,
cb,
NULL)) break;
PTHREAD_START_ROUTINE pfnThreadRtn =
(PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
if (pfnThreadRtn == NULL) break;
char pfnThreadRtnAddre[100];
sprintf_s(pfnThreadRtnAddre, "0x%016x", pfnThreadRtn, 100);
std::cout << pfnThreadRtnAddre << std::endl;
hThread = CreateRemoteThread(
hProcess,
NULL,
0,
pfnThreadRtn,
pszDllFileRemote,
0,
NULL);
std::cout << "GetLastErr:" << GetLastError() << std::endl;
if (hThread == NULL) break;
WaitForSingleObject(hThread, INFINITE);
bOk = TRUE;
} while (0);
if (hthSnapshot != NULL) CloseHandle(hthSnapshot);
if (pszDllFileRemote != NULL)
VirtualFreeEx(hProcess, pszDllFileRemote, 0, MEM_RELEASE);
if (hThread != NULL)
CloseHandle(hThread);
if (hProcess != NULL)
CloseHandle(hProcess);
return (bOk);
}
BOOL EjectDLL(DWORD dwProcessId, PCWSTR pszDllFile)
{
BOOL bOk = FALSE;
HANDLE hthSnapshot = NULL;
HANDLE hProcess = NULL, hThread = NULL;
do
{
//查找我们注入的模块
hthSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessId);
if (hthSnapshot == INVALID_HANDLE_VALUE) return FALSE;
MODULEENTRY32 me = { sizeof(me) };
BOOL bFound = FALSE;
BOOL bMoreMods = Module32FirstW(hthSnapshot, &me);
for (; bMoreMods; bMoreMods = Module32NextW(hthSnapshot, &me))
{
bFound = (_wcsicmp(me.szModule, pszDllFile) == 0) ||
(_wcsicmp(me.szExePath, pszDllFile) == 0);
//std::wcout.imbue(std::locale("chs"));
//std::wcout << me.szExePath << std::endl;
if (bFound) break;
}
if (!bFound) break;
hProcess = OpenProcess(
PROCESS_ALL_ACCESS,
FALSE,
dwProcessId);
if (hProcess == NULL) break;
PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)
GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "FreeLibrary");
if (pfnThreadRtn == NULL) break;
hThread = CreateRemoteThread(
hProcess,
NULL,
0,
pfnThreadRtn,
me.modBaseAddr,
0,
NULL);
if (hThread == NULL) break;
WaitForSingleObject(hThread, INFINITE);
bOk = TRUE;
} while (0);
if (hthSnapshot != NULL) CloseHandle(hthSnapshot);
if (hThread != NULL) CloseHandle(hThread);
if (hProcess != NULL) CloseHandle(hProcess);
return (bOk);
}
几个疑问:
(1)有些进程会注入失败,不知道什么原因
答:可能是权限不够
(2)有些进程注入成功,但是不加载DLL,我在win10上个微信注入就会遇到这种情况
答:因为微信是32位的,注入的进程也需要时32位的,设置为x86即可
(3)CreateToolhelp32Snapshot好像并不能枚举进程中所有的模块,对微信进程枚举只有四五个作用,很诡异。
答案:因为调用该函数的进程与目标进程的位数不一样,目标进程可能是32位,但是调用进程是64位就出现这个问题