使用“远程线程注入” 打造DLL进程注入器
何为DLL注入?
DLL注入技术,通俗来讲是向一个正在运行的进程插入/注入代码的过程。注入的代码以动态链接库(DLL)的形式存在。至于注入的代码是而已代码还是正常代码,那就全看开发人员的目的。
DLL注入游戏外挂方面用途
据我所知 "DLL注入" 这项技术,在游戏外挂方面使用非常频繁。每个Windows程序都有独立的4GB虚拟空间,程序之间内存数据相互隔离互不影响,这就导致要想修改游戏的数据就非常困难。
但有"DLL注入"这项技术的存在,就变得简单了许多。只需要把操作游戏数据的代码写进 DLL文件,再把DLL文件注入到游戏。在让DLL文件中的代码来修改游戏数据,这一切都显得非常顺其自然。
DLL远程注入原理
知其然而不知其所以然,我们要知其然更要知其所以然。首先了解"DLL远程注入"的本质及原理。了解了"DLL远程注入"的本质及原理在来写代码,那么就如鱼得水信手拈来了。废话不多说先来看一张图。
分为四个步骤
因为每个进程的数据都是相互隔离的,所以要想运行的进程B加载外部 DLL 文件。
1.首先打开进程B,获取到进程B的实例句柄。
2.然后再进程B中给进程B申请一块内存空间,这一块空间主要用于存储DLL文件的路径。
3.再进程B中申请空间成功后,就把DLL的路径写入到这一块区域。
4.最后给进程B创建一个线程,用于加载DLL文件。
OpenProcess函数原型
函数原型
HANDLE OpenProcess(
DWORD dwDesiredAccess,//进程访问权限
BOOL bInheritHandle,//表示所得到的进程句柄是否可以被继承
DWORD dwProcessId //要打开进程的PID
);
函数返回值:如成功,返回值为指定进程的句
函数功能:OpenProcess 函数用来打开一个已存在的进程对象,并返回进程的句柄。
VirtualAllocEx函数原型
LPVOID VirtualAllocEx(
HANDLE hProcess,//申请内存所在的进程句柄。
LPVOID lpAddress,//留页面的内存地址;一般用NULL自动分配 。
SIZE_T dwSize,//欲分配的内存大小,
DWORD flAllocationType,//为特定的页面区域分配内存
DWORD flProtect//指定访问的权限
);
函数返回值:执行成功就返回分配内存的首地址,不成功就是NULL
函数功能:在指定进程的中申请内存区域,
WriteProcessMemory函数原型
BOOL WriteProcessMemory(
HANDLE hProcess, //欲写的内存空间
LPVOID lpBaseAddress, //空间所在地址
LPCVOID lpBuffer,//欲写入的内容
SIZE_T nSize, //欲写入的大小
SIZE_T *lpNumberOfBytesWritten //一般为NULL
);
函数返回值:非零值代表写入成功。
函数功能:在指定进程的指定地址,写入数据。
CreateRemoteThread函数原型
HANDLE CreateRemoteThread(
HANDLE hProcess, //线程所属进程的进程句柄.
LPSECURITY_ATTRIBUTES lpThreadAttributes,//安全属性.一般为NULL
SIZE_T dwStackSize,//线程栈初始大小,以字节为单位, 一般为0
LPTHREAD_START_ROUTINE lpStartAddress,//该线程的线程函数的起始地址.
LPVOID lpParameter,//传给线程函数的参数.
DWORD dwCreationFlags,//线程的创建后状态
LPDWORD lpThreadId//向所创建线程ID的指针
);
函数返回值:如果调用成功,返回新线程句柄.
函数功能:在指定进程的中创建一个线程。
程序核心代码
BOOL CInjectToolDlg::injectModuleInto(DWORD dwProcessID)
{
if (GetCurrentProcessId() == dwProcessID)//如果注入的进程ID 等于 自身ID 返回
return FALSE;
//1.试图打开目标进程
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,
FALSE, dwProcessID);
if (hProcess == NULL)
return FALSE;
//2.在目标进程中申请空间,
int cbSize = (wcslen(m_szDllName) + 1);
LPVOID lpRemoteDLLName = ::VirtualAllocEx(hProcess, NULL, cbSize, MEM_COMMIT, PAGE_READWRITE);
//3.再申请的空间中写入dll路径
::WriteProcessMemory(hProcess, lpRemoteDLLName, m_szDllName, cbSize, NULL);
HMODULE hModule = GetModuleHandle(L"kernel32.dll");
DWORD pfnStartRoutine = (DWORD)GetProcAddress(hModule, "LoadLibraryW");
//4.启动远程线程
HANDLE hRemoteThrad = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pfnStartRoutine, lpRemoteDLLName, 0, NULL);
if (hRemoteThrad == NULL)
{
CloseHandle(hProcess);
return false;
}
//等待目标线程运行结束,即LoadLibraryA函数返回
WaitForSingleObject(hRemoteThrad, INFINITE);
::CloseHandle(hRemoteThrad);
CloseHandle(hProcess);
return TRUE;
}
上述的核心代码是封装在一个函数中的,不知道你们有没有发现。核心代码中出现俩个没给你们介绍的Api函数 分别是GetModuleHandle和GetProcAddress。
GetModuleHandle是用于获取模块实例句柄的,而 GetProcAddress函数 则是从模块中获取到某个函数的指针地址。
通过GetProcAddress函数可以取得LoadLibraryW函数的地址。kernel32.dll是最重要的Win32系统模块,它总被映射到每个进程相同的地址中,这样一来,LoadLibraryW函数在任何进程的地址空间中的地址都是相同的。这保证了向CreateRemoteThread函数传递的指针有效。
然后再把再B进程中写入DLL路径的内存地址,作为线程函数参数传入。当远程线程运行以后,它传递的这个参数给线程函数(这里是LoadLibraryW)。这就使得线程B成功加载指定的DLL文件。