使用“远程线程注入” 打造DLL进程注入器

使用“远程线程注入” 打造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文件。

发布于 2021-02-23 17:37