DLL注入
DLL 注入(Dynamic-link Library Injection)是一种在 Windows 操作系统中将一个外部 DLL(动态链接库)文件加载到另一个进程的地址空间的技术。通过 DLL 注入,可以在目标进程中执行来自外部 DLL 的代码。这种技术有许多合法和非法的用途。(你好,以上来自ChatGPT-4)
技术是无罪的,DLL注入有很多用途…
●修复BUG
●安全研究:安全研究人员可以利用 DLL 注入技术来研究目标程序的漏洞,以便评估潜在的安全风险,并为这些漏洞提供补丁。
●拓展功能,无需修改源代码,就可以为现有程序添加新功能或修改现有功能。
非法或恶意目的:
●制作游戏外挂(挂狗sm)
●制作和传播恶意软件:黑客可以利用 DLL 注入将恶意代码注入目标程序中,从而实现对目标系统的控制、数据窃取或其他恶意行为。
●破解软件
只有了解其原理,才能攻与防,本文只是较浅的谈及DLL注入技术。(大佬可以关闭页面了)
大致了解原理
DLL被加载到进程后会自动运行DIIMain()函数,用户可以把想执行的代码放到DIIMain()函数,每当加载DLL时,添加的代码就会自然而然得到执行。利用该特性可修复程序Bug,或向程序添加新功能。
从根本来说,DLL注入技术要求目标进程的一个线程调用LoadLibrary函数来载入我们想要的DLL,然后把你想注入的代码写在DllMain()函数内,这样注入后就会被调用。
使用远程线程(CreateRemoteThread)来注入DLL
本方法来自《Windows核心编程》 ,这种方法提供了最高的灵活性。为什么要使用远程线程来注入呢?因为在大多数Windows函数只允许一个进程对它自己进行操作。这样可以防止一个进程破坏另一个进程。
通过限制进程之间的直接交互,Windows 操作系统可以提高系统的稳定性和安全性。每个进程在其自己的地址空间中运行,彼此隔离。
因此这种方法要求我们在目标进程中创建一个新的线程。由于这个线程是我们自己创建的,我们可以对它执行的代码加以控制。幸运的是,Windows提供了CreateRemoteThread函数,它使得在另一个进程中创建线程变得非常容易。
怎么在另一个进程中创建一个线程?
CreateRemoteThread函数
CreateThread&&CreateRemoteThread
HANDLE CreateRemoteThread(
[in] HANDLE hProcess, //要在其中创建线程的进程句柄
[in] LPSECURITY_ATTRIBUTES lpThreadAttributes, //该结构决定新线程的安全属性。通常情况下,设置为 NULL 即可。
[in] SIZE_T dwStackSize, //堆栈的初始大小(以字节为单位)。 系统将此值舍入到最近的页面。 如果此参数为 0 (零) ,则新线程将使用可执行文件的默认大小。
[in] LPTHREAD_START_ROUTINE lpStartAddress, //指向由线程执行的 LPTHREAD_START_ROUTINE 类型的应用程序定义函数的指针,表示远程进程中线程的起始地址。 函数必须存在于远程进程中
[in] LPVOID lpParameter, //指向要传递给线程函数的变量的指针。
[in] DWORD dwCreationFlags, //一般填0,代表创建后,线程会立刻运行
[out] LPDWORD lpThreadId //指向接收线程标识符的变量的指针。如果此参数为 NULL,则不返回线程标识符。
);
相比于CreateThread函数来说,CreateRemoteThread就多了一个参数hProcess,hProcess用来表示新创建的线程归哪个进程所有。好,如何在另一个进程中创建线程的这个问题解决了。
怎么才能让创建的线程加载我们的目标dll呢?
LoadLibrary函数
其实没有LoadLibrary函数,只有LoadLibraryA和LoadLibraryW(后者路径就要写成L"xxx")两个函数(A中指的是ANSI,W指的是Wide character即宽字节),根据你的DLL名选择正确的函数。
好,那我们能直接在CreateRemoteThread函数的第四个参数直接写上LoadlibraryW(L"xxx.dll")呗,其实这是错误的!!!
《Windows核心编程》中介绍了这样写法的两个错误!
错误一
如果直接引用LoadLibrary类的函数,该引用会被解析成我们模块的导入段(IAT)中的LoadLibrary转换函数的地址。如果把这个转换函数的地址作为远程线程的起始地址传入,那么天知道远程线程会执行什么代码。所以为了强制代码略过转化函数并直接调用LoadLibrary函数,我们必须通过调用GetProcAddress来获取Loadlibrary的确切地址。简而言之,IAT的条目(即指向 LoadLibrary 函数的指针)位于你的模块(本地进程)的地址空间中,而不是目标进程的地址空间。
但其实我有一个疑惑,就是LoadLibrary不是系统函数吗,它在kernel32.dll中,按道理它在所有进程中都具有相同的地址,所以可能直接引用也能跑,我有空写一个试试。
错误二
与DLL路径字符串有关,“xxx.dll”位于当前进程的地址空间内,把这个地址传给新创建的远程线程,然后再传给LoadLibraryW。但是当LoadLibraryW去访问这个地址的时候,DLL的路径字符串不在那里,就有可能引发访问违规,然后终止远程进程。那这样的话,我们就不是远程注入了,我们就是远程终止进程了😅…
在默认情况下,一个进程不能直接访问另一个进程的地址空间。进程的地址空间是相互隔离的,这有助于提高系统的安全性和稳定性。当然,Windows其实也提供了一些函数使得一个进程可以访问另一个进程的地址空间,比如ReadProcessMemory 和 WriteProcessMemory。不然你的调试器是怎么做到的呢?
正确思路
Windows为我们提供了VirtualAllocEx函数,这个函数可以让一个进程在另一个进程的空间地址中分配一块内存,而VirtualFreeEx函数则可以让我们释放掉这块内存。然后使用WriteProcessMemory函数,从而将当前进程中的字符串的地址空间复制到远程进程的地址空间去。
总结过程
1.通过VirtualAllocEx函数在远程进程的地址空间中分配一块内存。
2.用WriteProcessMemory函数把DLL的路径名复制到前面一步所分配的内存中去。
3.使用GetProcAddress函数来得到LoadLibraryW函数在(Kernel32.dll)的实际地址。
4.使用CreateRemoteThread函数在远程进程中创建一个线程,让新线程调用正确的LoadlibraryW函数,参数则是第1步分配的内存地址。
编写代码(32位)
对于已被系统服务或进程调用起来的系统DLL,LoadLibrary和GetModuleHandle效果是一样的。所以本代码使用GetModuleHandle。
#include<windows.h>
#include<tchar.h>
BOOL InjectDll(DWORD dwPID, LPCTSTR szDllPath) {
HANDLE hProcess = NULL, hThread = NULL;
HMODULE hMod = NULL;
LPVOID pRemoteBuf = NULL;
DWORD dwBufSize = (DWORD)(_tcslen(szDllPath) + 1) * sizeof(TCHAR);//计算dll路径名的Size
LPTHREAD_START_ROUTINE pThreadProc;
//使用dwPID获取目标进程的句柄(可以使用process Explorer查看PID)
if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID))) {
_tprintf(L"OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError());//GetLastError(),返回调用线程最近的错误代码值,这个还是很常见的。
return FALSE;
}
pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);//在远程进程的内存中分配之前计算好大小的内存。
WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);//将你写的dll的路径写到分配的内存中去
hMod = GetModuleHandle(L"kernel32.dll");
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");//直接获取LoadLibrary函数在Kernel32.dll的地址空间
//在远程进程中运行线程
hThread = CreateRemoteThread(
hProcess,//目标进程句柄
NULL,
0,
pThreadProc,//简而言之,就是线程函数地址
pRemoteBuf,//线程函数参数地址
0,
NULL
);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}
int _tmain(int argc,TCHAR *argv[]) {
if (argc != 3) {
_tprintf(L"Usage: %s pid dll_path\n", argv[0]);
return 1;
}
if (InjectDll((DWORD)_tstol(argv[1]), argv[2]))// 前一个参数是进程PID ,使用_tstol将字符转成数字
_tprintf(L"InjectDll(\"%s\") success!!!\n", argv[2]);
else
_tprintf(L"InjectDll(\"%s\") failed!!!\n", argv[2]);
return 0;
}
要注入的DLL文件
功能比较简单,就是弹出弹窗,然后创建一个线程,将指定网址的index.html保存到本地。
#include<windows.h>
#include<tchar.h>
#pragma comment(lib,"urlmon.lib")
#define DEF_URL (L"https://www.baidu.com")
#define DEF_FILE_NAME (L"index.html")
HMODULE g_hMod = NULL;
DWORD WINAPI ThreadProc(LPVOID lParam) {
MessageBoxA(NULL, "ThreadProc started", "Injected!!!", MB_OK);
TCHAR szPath[_MAX_PATH] = { 0, };
if (!GetModuleFileName(g_hMod, szPath, MAX_PATH))
return FALSE;
TCHAR* p = _tcsrchr(szPath, '\\');
if (!p) {
return FALSE;
}
_tcscpy_s(p + 1, _MAX_PATH, DEF_FILE_NAME);
URLDownloadToFile(NULL, DEF_URL, szPath, 0, NULL);
return 0;
}
BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD fdwReason, LPVOID lpvReserved) {
HANDLE hThread = NULL;
g_hMod = (HMODULE)hinstDll;
switch (fdwReason) {
case DLL_PROCESS_ATTACH:
MessageBoxA(NULL, "hacked by Fup1p1", "Injected!!!", MB_OK );
OutputDebugString(L"myhack.dll Injection!!!");
hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
CloseHandle(hThread);
break;
}
return TRUE;
}
被注入的程序
很简单,就是弹出一个弹窗。
#include <windows.h>
#include <stdio.h>
int main()
{
MessageBoxA(NULL, "test", "test123", S_OK);
system("pause");
}
效果
注入后就可以发现,程序的dll中突然插入了hackby.dll文件
并且,我们写在dll中的代码也成功被执行。
然后百度的index.html也成功下载到了本地
评论区