Win32:远程线程注入

一、普通的远程线程注入(CreateRemoteThread+LoadLibrary)

  • 原理
    因为KERNEL32.DLL在每个进程中都属于最先加载的模块,所以每个进程的KERNEL32.DLL模块基址都相同。据此,可以通过CreateRemoteThread这个API在目标进程中调用KERNEL32.DLL模块的LoadLibrary函数,使目标进程加载我们的DLL文件,从而达到注入的目的。
  • 实现过程中的坑
    (1) 确保当前进程和目标进程位数一致,才能以同地址远程调用目标进程LoadLibrary(即注入器与被注入进程需位数相同)
    (2)进程只能加载位数相同的DLL模块(即64位进程只能加载64位的DLL)
    (3)创建远程线程后,通过GetExitCodeThread这个API可以得到远程线程的退出码,而又因为远程线程所执行的函数是LoadLibrary,所以这个退出码就是目标进程中调用LoadLibrary函数的返回值,据此可以得到DLL在目标进程中被载入的地址,如果返回0则表示载入失败。
  • 弊端
    调用了太多的系统API,很多地方都可能被检测,并且在目标进程的模块列表中可以看到被注入的DLL模块。高危API动作有
    OpenProcess、VirtualAllocEx、WriteProcessMemory、CreateRemoteThread、LoadLibrary 等
  • 代码
    //************************************
    // Method:     RemoteThreadInject_General 
    // Description:
    // Parameter:  const char * destProcess - 被注入的目标进程
    // Parameter:  const char * DllPath - 要注入的模块路径
    // Returns:    BOOL - 
    //************************************
    BOOL Injector::RemoteThreadInject_General(const wchar_t* destProcessName, const char* DllPath) {
    	Log::INFO(HString::Format("开始远程线程注入(普通版),模块文件路径[%s]", DllPath));
    	//打开目标进程
    	HANDLE hDestProcess;
    	if (ProcessTool::OpenProcessByName(destProcessName, hDestProcess) == FALSE) { return FALSE; }
    	//合法性检查
    	BOOL is32BitProcess = TRUE;
    	IsWow64Process(hDestProcess, &is32BitProcess);
    #ifdef _WIN64
    	if (is32BitProcess == TRUE) { Log::Error("远程线程注入(普通版)失败!因为当前进程是64位,而目标进程是32位。"); return FALSE; }//确保当前进程和目标进程位数一致,才能以同地址远程调用目标进程LoadLibrary()
    #else
    	if (is32BitProcess == FALSE) { Log::Error("远程线程注入(普通版)失败!因为当前进程是32位,而目标进程是64位。"); return FALSE; }//确保当前进程和目标进程位数一致,才能以同地址远程调用目标进程LoadLibrary()
    #endif
    	PE pe;
    	if (pe.LoadFileBuffer(DllPath) == FALSE) { return FALSE; }
    	if (pe.AnalyzePE_ByFileBuffer(pe.pFileBuffer) == FALSE) { return FALSE; }
    	if (pe.IsPE64 != (!is32BitProcess)) { Log::Error("远程线程注入(普通版)失败!因为目标模块的位数与目标进程的位数不一致。"); return FALSE; } //进程只能加载位数相同的DLL模块
    
    	//为DLL名称字符串申请足够大的空间(100字节),将字符串写入目标进程
    	LPVOID remoteAddr = VirtualAllocEx(hDestProcess, NULL, 100, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    	if (remoteAddr == NULL) { Log::Error(HString::Format("远程线程注入(普通版)失败!因为向目标进程申请空间失败,lastErrorCode=%d", GetLastError())); return FALSE; }
    	if (WriteProcessMemory(hDestProcess, (LPVOID)remoteAddr, (LPCVOID)DllPath, 100, OUT NULL) == 0) { Log::Error(HString::Format("远程线程注入(普通版)失败!因为向目标进程写入DLL名称字符串失败,lastErrorCode=%d", GetLastError())); return FALSE; }
    
    	//获取本进程中LoadLibraryA()函数地址(KERNEL32.DLL在每个进程中都属于最先加载的模块,所以每个进程的KERNEL32.DLL模块基址都相同)
    	LPTHREAD_START_ROUTINE pLoadLibrary = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("KERNEL32.DLL")), "LoadLibraryA");
    	if (pLoadLibrary == NULL) { Log::Error(HString::Format("远程线程注入(普通版)失败!因为获取本进程LoadLibraryA()函数地址失败,lastErrorCode=%d", GetLastError())); return FALSE; }
    
    	//创建远程线程,调用目标进程内的LoadLibrary函数已加载目标DLL模块
    	DWORD remoteThreadID = 0;
    	HANDLE hRemoteThread = CreateRemoteThread(hDestProcess, NULL, 0, pLoadLibrary, (LPVOID)remoteAddr, 0, OUT & remoteThreadID);
    	if (hRemoteThread == NULL) { Log::Error(HString::Format("远程线程注入(普通版)失败!因为创建远程线程失败,lastErrorCode=%d", GetLastError())); return FALSE; }
    
    	//等待远程线程结束
    	WaitForSingleObject(hRemoteThread, INFINITE);
    	DWORD far  exitCode;
    	GetExitCodeThread(hRemoteThread, OUT & exitCode);//线程退出码,即LoadLibrary函数返回值,即注入的模块基址
    	if (exitCode == 0) { Log::Error(HString::Format("远程线程注入(普通版)失败!因为目标进程LoadLibrary返回的模块基址为0,可能是因为DLL文件路径有误。")); return FALSE; CloseHandle(hRemoteThread); return FALSE; }
    
    	Log::SUCCESS(HString::Format("完成远程线程注入(普通版)!远程线程退出码(即LoadLibrary返回的模块基址) = %llX(仅4字节) ", exitCode));
    	return TRUE;
    }
    

二、模块隐藏的远程线程注入

发布了56 篇原创文章 · 获赞 5 · 访问量 7435

猜你喜欢

转载自blog.csdn.net/forchoosen/article/details/103847016