admin管理员组文章数量:1631139
一. 远程线程注入原理
前面几篇文章介绍了《Windows注入与拦截(2) – 使用注册表方式完成DLL注入》,《Windows注入与拦截(3) – 使用钩子方式完成DLL注入》这2种注入方式。“注册表注入方式”由于不能精确指定需要注入的进程,而且只能注入到GUI程序中,灵活性较差;“钩子注入方式”虽然能够精确指定注入的线程,但只能针对特定类型的消息进行Hook,从而间接的实现注入,对于类似windows服务类的程序就束手无策了。
本文介绍的“远程线程的注入方式”是在实际中使用最为广泛的一种注入方式,它即可以精确指定需要注入的进程,又可以注入到非GUI程序中。
远程线程注入方式使用的关键API为CreateRemoteThread,原型如下:
HANDLE WINAPI CreateRemoteThread(
_In_ HANDLE hProcess,
_In_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ SIZE_T dwStackSize,
_In_ LPTHREAD_START_ROUTINE lpStartAddress,
_In_ LPVOID lpParameter,
_In_ DWORD dwCreationFlags,
_Out_ LPDWORD lpThreadId
);
CreateRemoteThread
的参数和我们平时创建本地线程使用的CreateThread
的参数类似,新增了hProcess
句柄参数用于指定在哪个进程创建远程线程,通过OpenProcess
可以获取到进程句柄,但需要注意权限问题。
通过远程线程方式实现DLL注入主要是在lpStartAddress
和lpParameter
这2个参数上面做文章。
lpStartAddress
参数和CreateThread
函数的一样,都是线程的处理过程函数,函数原型如下:
DWORD WINAPI ThreadProc(LPVOID lpParameter);
我们知道加载DLL使用的API是LoadLibraryA
或LoadLibraryW
,原型如下:
HMODULE WINAPI LoadLibrary(LPCTSTR lpFileName);
对比LoadLibrary
和线程处理函数(LPTHREAD_START_ROUTINE
)的原型,我们发现两者的函数的原型基本相同。虽然不是完全相同,但都是接收一个指针参数,而且都是返回一个值,并且调用约定也都是WINAPI
。
我们完全可以利用下它们之间的相似性,把线程处理函数的地址设为LoadLibraryA
或LoadLibraryW
,类似下面这样:
HANDLE hThread = CreateRemoteThread(hProcessRemote,
NULL,
0,
LoadLibraryA,
"C:\\InjectDll.dll",
0,
NULL
);
当CreateRemoteThread
创建的新线程在远程进程地址空间中被创建的时候,就会立即调用LoadLibraryA
函数,并传入DLL路径的地址作为其参数。 (~~ 这一句话才是关键 ~~)
二. 需要注意的问题
按照上面的介绍的方法很容易就能实现远程线程注入,实际上也的确是很容易实现,只是还有3个地方需要注意:LoadLibrary函数地址、DLL路径字符串地址、取消注入。
2.1 LoadLibrary函数地址问题
我们不能向上面的代码那样直接把LoadLibraryA
或LoadLibraryW
作为第4个参数传给CreateRemoteThread
函数。这其中涉及模块的导入段等问题,如果在调用CreateRemoteThread
时直接引用LoadLibraryA
,则该引用会被解析为我们被注入DLL的导入段中的LoadLibraryA
转换函数的地址,如果把这个转换函数的地址作为远程线程的起始地址传入,其结果很可能是访问违规。
为了强制代码略过转换函数并直接调用LoadLibraryA
函数,我们必须通过GetProcAddress来得到LoadLibraryA
的确切地址。如:
HMODULE hKernel32 = GetModuleHandle(TEXT("kernel32.dll"));
LPVOID pLoadLibraryAAddr = (LPVOID)GetProcAddress(hKernel32, "LoadLibraryA");
2.2 DLL路径字符串地址问题
DLL路径字符串"C:\\InjectDll.dll"
的内存地址位于调用进程的地址空间中,并不位于被注入的进程的地址空间中。所以当LoadLibraryA
用这个地址去被注入进程的地址空间中访问的时候,DLL路径的字符串并不在那里,这很有可能导致远程线程访问违规。
如果对进程的地址空间不了解,可以参考:Windows内存体系系列文章。
为了解决这个问题,我们需要把DLL的路径字符串存储到被注入进程的地址空间中。Windows提供的VirtualAllocEx
函数可以实现在其他进程的地址空间中分配内存块。实现过程大致如下:
hTargeProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessID); // dwProcessID为被注入目标进程的进程ID
if (!hTargeProcess) {
return;
}
SIZE_T dllPathSize = strlen(pszDllPath); // pszDllPath存储了DLL的路径
pVM4DllPath = VirtualAllocEx(hTargeProcess, NULL, dllPathSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!pVM4DllPath) {
return;
}
if (!WriteProcessMemory(hTargeProcess, pVM4DllPath, pszDllPath, dllPathSize, NULL)) {
return;
}
2.3 取消注入问题
取消注入就是将DLL从目标进程卸载,我们知道,卸载DLL所用的API是FreeLibrary
,但我们不能直接调用这个函数,因为直接调用的话是在我们的进程中卸载DLL,而不是目标进程中卸载,很显然这样达不到卸载的目的。我们需要和加载DLL时一样,将FreeLibrary
的地址作为第4个参数传给CreateRemoteThread
函数,但同样需要通过GetProcAddress
来得到FreeLibrary
的确切地址:
PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "FreeLibrary");
if(pfnThreadRtn) {
hThread = CreateRemoteThread(hProcess, NULL, 0, pfnThreadRtn, me.modBaseAddr, 0, NULL);
}
三. 实例
InjectDllByRemoteThread
和EjectDllByRemoteThread
两个函数使用远程线程的方式分别实现了注入和取消注入的功能。
3.1 InjectDllByRemoteThread
BOOL InjectDllByRemoteThread(DWORD dwProcessID, const char* pszDllPath) {
BOOL bRet = FALSE;
const DWORD dwThreadSize = 50 * 1024;
HANDLE hTargeProcess = NULL;
HANDLE hRemoteThread = NULL;
PVOID pVM4LoadLibrary = NULL;
PVOID pVM4DllPath = NULL;
__try {
if (!EnablePrivilege(SE_DEBUG_NAME, TRUE)) {
__leave;
}
hTargeProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessID);
if (!hTargeProcess) {
__leave;
}
SIZE_T dllPathSize = strlen(pszDllPath);
pVM4DllPath = VirtualAllocEx(hTargeProcess, NULL, dllPathSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!pVM4DllPath) {
__leave;
}
if (!WriteProcessMemory(hTargeProcess, pVM4DllPath, pszDllPath, dllPathSize, NULL)) {
__leave;
}
HMODULE hKernel32 = GetModuleHandle(TEXT("kernel32.dll"));
LPVOID pLoadLibraryAAddr = (LPVOID)GetProcAddress(hKernel32, "LoadLibraryA");
hRemoteThread = CreateRemoteThread(hTargeProcess, NULL, 0, (DWORD(WINAPI *)(LPVOID))pLoadLibraryAAddr, pVM4DllPath, 0, NULL);
if (!hRemoteThread) {
__leave;
}
WaitForSingleObject(hRemoteThread, INFINITE);
DWORD dwExitCode = 0;
BOOL B = GetExitCodeThread(hRemoteThread, &dwExitCode);
bRet = TRUE;
}
__finally {
if (hTargeProcess && pVM4DllPath) {
VirtualFreeEx(hTargeProcess, pVM4DllPath, dwThreadSize, MEM_RELEASE);
}
if (hRemoteThread) {
CloseHandle(hRemoteThread);
}
if (hTargeProcess) {
CloseHandle(hTargeProcess);
}
}
return bRet;
}
3.2 EjectDllByRemoteThread
BOOL EjectDllByRemoteThread(DWORD dwProcessID, LPCWSTR pszDllPath) {
BOOL bOk = FALSE;
HANDLE hTHSnapshot = NULL;
HANDLE hProcess = NULL, hThread = NULL;
__try {
hTHSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessID);
if(hTHSnapshot == INVALID_HANDLE_VALUE) {
__leave;
}
MODULEENTRY32W me = {sizeof(me)};
BOOL bFound = FALSE;
BOOL bMoreMods = Module32FirstW(hTHSnapshot, &me);
for(; bMoreMods; bMoreMods = Module32NextW(hTHSnapshot, &me)) {
bFound = (_wcsicmp(me.szModule, pszDllPath) == 0) ||
(_wcsicmp(me.szExePath, pszDllPath) == 0);
if(bFound) break;
}
if(!bFound) {
__leave;
}
hProcess = OpenProcess(
PROCESS_QUERY_INFORMATION |
PROCESS_CREATE_THREAD |
PROCESS_VM_OPERATION, // For CreateRemoteThread
FALSE, dwProcessID);
if(hProcess == NULL) {
__leave;
}
PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)
GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "FreeLibrary");
if(pfnThreadRtn == NULL) {
__leave;
}
hThread = CreateRemoteThread(hProcess, NULL, 0, pfnThreadRtn, me.modBaseAddr, 0, NULL);
if(hThread == NULL) {
__leave;
}
WaitForSingleObject(hThread, INFINITE);
bOk = TRUE;
}
__finally {
if(hTHSnapshot != NULL) {
CloseHandle(hTHSnapshot);
}
if(hThread != NULL) {
CloseHandle(hThread);
}
if(hProcess != NULL) {
CloseHandle(hProcess);
}
}
return(bOk);
}
3.3 DllMain
使用远程线程的方式进行DLL注入,我们一般在DllMain的DLL_PROCESS_ATTACH
条件分支开始业务逻辑(通常会另外创建一个子线程,将业务逻辑放到子线程中处理),DLL_PROCESS_DETACH
条件分支出结束业务逻辑。
BOOL APIENTRY DllMain(HMODULE hModule, DWORD fdwReason, LPVOID lpReserved) {
HANDLE hThread = NULL;
switch(fdwReason) {
case DLL_PROCESS_ATTACH:
{
g_hDllModule = hModule;
// 使用注册表方式和CreateRemoteThread方式注入时,一般在此处创建线程
//
hThread = (HANDLE)_beginthreadex(NULL, 0, PluginProc, NULL, 0, NULL);
if (hThread) {
CloseHandle(hThread); // 关闭句柄,防止句柄泄漏
}
break;
}
case DLL_THREAD_ATTACH:
{
break;
}
case DLL_THREAD_DETACH:
{
break;
}
case DLL_PROCESS_DETACH:
{
// 结束业务逻辑
// ......
break;
}
}
return TRUE;
}
完整的示例代码下载地址: https://download.csdn/download/china_jeffery/10323108
版权声明:本文标题:Windows注入与拦截(4) -- 使用远程线程方式完成DLL注入 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.elefans.com/dianzi/1729087663a1185797.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论