DLL进程注入之CreateRemoteThread()
Dll注入原理
Dll注入一般指向一个正在运行的程序注入一个线程,注入的线程运行我们的代码,而我们的代码就以DLL(动态链接库)的形式存在。但是程序不会平白无故的就加载我们的dll,这时候就需要使用我们强大的Windows API了,它为我们提供了大量的函数来附加和操纵其他进程。
API中的所有函数都包含于DLL文件之中。其中,最重要的是“Kernel32.dll”(包含管理内存,进程和线程相关的函数),“User32.dll”(大部分是用户接口函数),和“GDI32.dll”(绘制图形和显示文本相关的函数)。。
Dll注入代码实现
DLL的注入方法有很多,这里介绍一种最经典的注入方法,远程进程注入。
这种方法灵活性高,同时要求掌握的知识也很多,其核心思想就是在目标进程中开启一个线程调用LoadLibrary函数来加载我们想要注入的dll.大致流程
首先是OpenProcess函数
HANDLE OpenProcess(
DWORD dwDesiredAccess, //渴望得到的访问权限(标志)
BOOL bInheritHandle, // 是否继承句柄
DWORD dwProcessId// 进程标示符
);
获得返回句柄之后再用VirtualAllocEx函数,在目标进程中开辟一块内存存放我们的dll的路径。
LPVOID VirtualAllocEx(
HANDLE hProcess, //目标进程句柄
LPVOID lpAddress, //保留页面的内存地址,一般用NULL自动分配
SIZE_T dwSize, //欲分配的内存大小,字节单位
DWORD flAllocationType, //为特定的页面区域分配内存中或磁盘
的页面文件中的物理存储
DWORD flProtect //受保护状态
);
之后使用WriteProcessMemory函数向目标内存写入dll地址
BOOL WINAPI WriteProcessMemory(
_In_ HANDLE hProcess, //由OpenProcess返回的进程句柄。
_In_ LPVOID lpBaseAddress, //要写的内存首地址
_In_ LPCVOID lpBuffer, //指向要写的数据的指针。
_In_ SIZE_T nSize, //要写入的字节数。
_Out_ SIZE_T *lpNumberOfBytesWritten //返回值。返回实际写入的字节
);
用GetProcAddress函数获得LoadLibraryW函数的起始地址。LoadLibraryW函数位于Kernel32.dll中,再用CreateRemoteThread函数让目标进程执行LoadLibraryW来加载被注入的dll。函数结束将返回载入dll后的模块句柄。
注意:这里的LoadLibrary函数在底层实际调用有两种可能,如果目标程序使用的是ANSI编码方式,LoadLibrary实际调用的是LoadLibraryA,其参数字符串应当是ANSI编码;
如果目标程序使用的是Unicode编码方式,LoadLibrary实际调用的是LoadLibraryW,其参数字符串应当是Unicode编码。
这使得注入过程变得很麻烦,为了减少复杂性,不妨直接使用LoadLibraryA或LoadLibraryW而不是用LoadLibrary函数来避免这一麻烦。另外,即使使用的是LoadLibraryA,LoadLibraryA也会将传入的ANSI编码的字符串参数转换成Unicode编码后再调用LoadLibraryW。综上,不妨一致使用LoadLibraryW函数,并且字符串用Unicode编码即可。
HMODULE hModule = GetModuleHandle(L"kernel32.dll");
if (!hModule)
{
printf("GetModuleHandle Error !\n");
GetLastError();
CloseHandle(hProcess);
return FALSE;
}
// #6.获取LoadLibraryA 函数地址
LPTHREAD_START_ROUTINE dwLoadAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "LoadLibraryW");
if (!dwLoadAddr)
{
printf("GetProcAddress Error !\n");
GetLastError();
CloseHandle(hProcess);
CloseHandle(hModule);
return FALSE;
}
完整代码
bool Inject(DWORD dwId, WCHAR* szPath)//参数1:目标进程PID 参数2:DLL路径
{
//一、在目标进程中申请一个空间
/*
【1.1 获取目标进程句柄】
参数1:想要拥有的进程权限(本例为所有能获得的权限)
参数2:表示所得到的进程句柄是否可以被继承
参数3:被打开进程的PID
返回值:指定进程的句柄
*/
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwId);
/*
【1.2 在目标进程的内存里开辟空间】
参数1:目标进程句柄
参数2:保留页面的内存地址,一般用NULL自动分配
参数3:欲分配的内存大小,字节单位
参数4:MEM_COMMIT:为特定的页面区域分配内存中或磁盘的页面文件中的物理存储
参数5:PAGE_READWRITE 区域可被应用程序读写
返回值:执行成功就返回分配内存的首地址,不成功就是NULL
*/
LPVOID pRemoteAddress = VirtualAllocEx(
hProcess,
NULL,
wcslen(szPath) * 2,
MEM_COMMIT,
PAGE_READWRITE
);
//二、 把dll的路径写入到目标进程的内存空间中
DWORD dwWriteSize = 0;
/*
【写一段数据到刚才给指定进程所开辟的内存空间里】
参数1:OpenProcess返回的进程句柄
参数2:准备写入的内存首地址
参数3:指向要写的数据的指针(准备写入的东西)
参数4:要写入的字节数(东西的长度+0/)
参数5: 返回值。返回实际写入的字节
*/
BOOL bRet = WriteProcessMemory(hProcess, pRemoteAddress, szPath, wcslen(szPath)*2, NULL);
//三、 创建一个远程线程,让目标进程调用LoadLibrary
/*
参数1:该远程线程所属进程的进程句柄
参数2:一个指向 SECURITY_ATTRIBUTES 结构的指针, 该结构指定了线程的安全属性
参数3:线程栈初始大小,以字节为单位,如果该值设为0,那么使用系统默认大小
参数4:在远程进程的地址空间中,该线程的线程函数的起始地址(也就是这个线程具体要干的活儿)
参数5:传给线程函数的参数(刚才在内存里开辟的空间里面写入的东西)
参数6:控制线程创建的标志。0(NULL)表示该线程在创建后立即运行
参数7:指向接收线程标识符的变量的指针。如果此参数为NULL,则不返回线程标识符
返回值:如果函数成功,则返回值是新线程的句柄。如果函数失败,则返回值为NULL
*/
// #5.获取模块地址
HMODULE hModule = GetModuleHandle(L"kernel32.dll");
if (!hModule)
{
printf("GetModuleHandle Error !\n");
GetLastError();
CloseHandle(hProcess);
return FALSE;
}
// #6.获取LoadLibraryA 函数地址
LPTHREAD_START_ROUTINE dwLoadAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "LoadLibraryW");
if (!dwLoadAddr)
{
printf("GetProcAddress Error !\n");
GetLastError();
CloseHandle(hProcess);
CloseHandle(hModule);
return FALSE;
}
// //7.创建远程线程,加载dll
HANDLE hThread = CreateRemoteThread(
hProcess,
NULL,
0,
(LPTHREAD_START_ROUTINE)dwLoadAddr,
pRemoteAddress,
NULL,
NULL
);
//WaitForSingleObject(hThread, -1); //当句柄所指的线程有信号的时候,才会返回
///*
//四、 【释放申请的虚拟内存空间】
//参数1:目标进程的句柄。该句柄必须拥有 PROCESS_VM_OPERATION 权限
//参数2:指向要释放的虚拟内存空间首地址的指针
//参数3:虚拟内存空间的字节数
//参数4:MEM_DECOMMIT仅标示内存空间不可用,内存页还将存在。
// MEM_RELEASE这种方式很彻底,完全回收。
//*/
//VirtualFreeEx(hProcess, pRemoteAddress, 1, MEM_DECOMMIT);
return 0;
}
DWORD GetPid(WCHAR* szName)
{
HANDLE hprocessSnap = NULL;
PROCESSENTRY32 pe32 = { 0 };
hprocessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
/*if (hprocessSnap == (HANDLE)-1) { return 0; }*/
pe32.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hprocessSnap, &pe32))
{
do {
if (!wcscmp(szName, pe32.szExeFile))
return (int)pe32.th32ProcessID;
} while (Process32Next(hprocessSnap, &pe32));
}
else
CloseHandle(hprocessSnap);
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
wchar_t wStr[] = L"C:\\Release\\Dnew.dll";
DWORD dwId = 0;
DWORD dwPid = 0;
WCHAR S[] = L"sublime_text.exe";
DWORD SS = GetPid(S);
printf("目标窗口的进程PID为:%d\n", SS);
//参数1:目标进程的PID
//参数2:想要注入DLL的路径
Inject(SS, wStr);
system("pause");
return 0;
}
这里用sublime来测试,随便打开个文档,
之后再运行我们的注入程序
可以看到文档处多了个弹框
而且查看进程,也成功加载了我们的DLL(在这里只是弹窗)
附赠测试dll代码
#include <windows.h>
#include <pch.h>
DWORD WINAPI runBot(LPVOID lpParam) {
// 此处可以写具体的bot代码
return 1;
}
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);
CreateThread(NULL, 0, &runBot, NULL, 0, NULL);
break;
}
return TRUE;
}