Win32API之实现远程线程注入(九)
什么是注入
注入是一种在不知情或未经许可的情况下向其他进程中注入模块并试图执行它们的技术
常见的注入方式有:远程线程注入、APC注入、消息钩子注入、注册表注入、导入表注入、输入法注入等等
什么是远程线程注入
远程线程注入是一种技术,可以将一个动态链接库(DLL)注入到另一个进程的地址空间中,并在该进程中创建一个远程线程来执行该 DLL 中的代码。这种技术通常用于恶意软件攻击,也可以用于调试和监视进程。远程线程注入涉及到许多操作系统底层的概念,包括内存映射、线程创建、函数调用等等。为了成功进行远程线程注入,攻击者需要克服许多障碍,例如安全软件、权限限制、代码签名等等
CreateRemoteThreaad
函数描述
CreateRemoteThread函数用于在另一个进程的地址空间中创建一个新线程。CreateRemoteThread函数通常用于远程线程注入和Hook技术等高级应用场景。其原理是通过在目标进程中分配内存,将需要执行的代码和参数写入该内存,然后在该内存中创建新线程并启动执行
其函数原型如下:
HANDLE CreateRemoteThread(
HANDLE hProcess, //目标进程的句柄
LPSECURITY_ATTRIBUTES lpThreadAttributes, //线程安全描述符
SIZE_T dwStackSize, //线程的初始堆栈大小,通常为0表示使用默认值
LPTHREAD_START_ROUTINE lpStartAddress, //新线程的入口点,即线程执行的第一个函数
LPVOID lpParameter, //传递给线程函数的参数
DWORD dwCreationFlags, //线程创建的标志,例如是否立即运行等
LPDWORD lpThreadId //返回新线程的ID
);
注意:
lpStartAddress
参数表示目标进程所拥有的线程函数,而不是CreateRemoteThread函数所在进程的线程函数
使用实例
如下代码用于表示被远程注入的目标进程,首先通过调试进程获取到线程函数的地址为0x00981f70
,然后运行此段代码
include <iostream>
include <windows.h>
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
for (int i = 0; i < 5; i++)
{
printf("线程正在执行!\n");
}
return 0;
}
int main()
{
HANDLE hThread = CreateThread(NULL, 0, ThreadProc, 0, 0, NULL);
getchar();
CloseHandle(hThread);
}
如下代码用于实现远程线程注入,运行后会发现目标进程的线程函数会再次执行
include <iostream>
include <windows.h>
include <TlHelp32.h>
include <string>
// 远程线程注入函数
BOOL MyCreateRemoteThread(DWORD ProcessID, DWORD AddrThreadProc) {
HANDLE hProcess;
HANDLE hThread;
DWORD ThreadID;
// 打开目标进程
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessID);
if (hProcess == NULL)
{
OutputDebugString("OpenProcess Error!\n");
return FALSE;
}
// 在目标进程中创建新的远程线程
hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)AddrThreadProc, NULL, 0, &ThreadID);
if (hThread == NULL)
{
OutputDebugString("CreateRemoteThread Error!\n");
return FALSE;
}
// 关闭进程和线程句柄
CloseHandle(hThread);
CloseHandle(hProcess);
return true;
}
// 通过进程名称获取进程ID的函数
DWORD GetProcessIdByName(const std::wstring& name) {
DWORD pid = 0;
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snap != INVALID_HANDLE_VALUE) {
PROCESSENTRY32W entry = { sizeof(entry) };
// 遍历进程快照
if (Process32FirstW(snap, &entry)) {
do {
// 检查进程名称是否匹配
if (std::wstring(entry.szExeFile) == name) {
pid = entry.th32ProcessID;
break;
}
} while (Process32NextW(snap, &entry));
}
// 关闭快照句柄
CloseHandle(snap);
}
return pid;
}
int main()
{
// 通过进程名获取进程ID,并打印
std::wstring process_name = L"test2.exe";
DWORD pid = GetProcessIdByName(process_name);
if (pid != 0) {
printf("Process ID of %ls: %lu\n", process_name.c_str(), pid);
}
else {
printf("Failed to get process ID of %ls\n", process_name.c_str());
}
// 在获取到的进程ID中执行远程线程注入
MyCreateRemoteThread(pid, 0x00981f70);
return 0;
}
远程线程注入的思路
通过上述CreateRemoteThread
的远程线程实例可知, 若要实现远程线程注入, 只能限制于目标进程的线程函数, 而不能执行自己的代码
若想目标进程执行自己的代码,可以利用Dll注入来实现。首先了解ThreadProc
线程函数的语法格式, 此函数需要一个四字节的参数以及四字节的返回值, 也就是说, 只要有符合这种格式的函数,那么它就能代替线程函数并传递给CreateRemoteThread
函数调用
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
return 0;
}
比如LoadLibrary
函数, 此函数与线程函数的格式一样,都是四字节的参数和返回值。只要将CreateRemoteThread
函数的线程函数地址替换成LoadLibrary
的函数地址, 以及传递dll的路径作为LoadLibrary
函数的参数, 在dll中写入我们自己的代码,这样就能实现完整的远程线程注入
HINSTANCE LoadLibrary(
LPCTSTR lpLibFileName;
)
总结以上几点, 远程注入的流程如下所示:
- 在目标进程分配内存空间, 用于存储dll的路径
- 获取
LoadLibrary
函数的地址 - 创建远程线程, 并执行
LoadLibrary
函数
远程线程注入实例
代码
如下代码是DLL文件,要注意一点,若目标进程是32位的,那么创建的dll也应该是32位的, 将要执行的代码写在DLL_PROCESS_ATTACH
处, 即dll被加载后会自动执行这段代码
//Mydll.dll
include "pch.h"
include <stdio.h>
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
for (int i = 0; i < 5; i++)
{
Sleep(1000);
printf("远程线程注入成功!\n");
}
return 0;
}
<br>
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
如下代码是被远程注入的进程B
//进程B
include <iostream>
include <windows.h>
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
for (int i = 0; i < 5; i++)
{
printf("线程正在执行!\n");
}
return 0;
}
int main()
{
HANDLE hThread = CreateThread(NULL, 0, ThreadProc, 0, 0, NULL);
getchar();
CloseHandle(hThread);
}
如下代码用于实现远程线程注入的进程A。
关于Dll文件的路径问题, Dll的路径若要使用相对路径, 那么dll文件就必须放在进程B(目标进程)的工作目录下, 而不是进程A的工作目录
//进程A
include <iostream>
include <windows.h>
include <TlHelp32.h>
include <string>
//获取进程ID的函数
DWORD GetProcessIdByName(const std::wstring& name) {
DWORD pid = 0;
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snap != INVALID_HANDLE_VALUE) {
PROCESSENTRY32W entry = { sizeof(entry) };
if (Process32FirstW(snap, &entry)) {
do {
if (std::wstring(entry.szExeFile) == name) {
pid = entry.th32ProcessID;
break;
}
} while (Process32NextW(snap, &entry));
}
CloseHandle(snap);
}
return pid;
}
BOOL RemoteInject(DWORD pid, char* dllPath)
{
DWORD DllNameLength = strlen(dllPath);
//1 获取目的进程句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
//2 为目的进程分配内存,用于存放Loadlibrary传入的参数,即dll的路径
VOID* paraAddr = VirtualAllocEx(hProcess, NULL, DllNameLength + 1, MEM_COMMIT, PAGE_READWRITE);
//3 将DLL的路径写到目标进程的内存
if (!WriteProcessMemory(hProcess, paraAddr, dllPath, DllNameLength + 1, NULL))
{
printf("写入内存失败!\n");
return false;
}
//4 获取loadlibrary函数的地址
HINSTANCE LibHandle = LoadLibrary("kernel32");
DWORD ProcAdd = (DWORD)GetProcAddress(LibHandle, "LoadLibraryA");
if (!ProcAdd)
{
printf("获取LoadLibraryA失败!\n");
return false;
}
//5 创建远程线程
DWORD threadid = 0;
HANDLE hRemoteThread = INVALID_HANDLE_VALUE;
hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)ProcAdd, paraAddr, 0, &threadid);
if (NULL == hRemoteThread)
{
printf("目标进程中创建线程失败!\n");
CloseHandle(hProcess);
return FALSE;
}
WaitForSingleObject(hRemoteThread, INFINITE);
//释放句柄
CloseHandle(hRemoteThread);
CloseHandle(hProcess);
}
int main(int argc, char*argv[])
{
//获取进程ID
std::wstring process_name = L"test2.exe";
DWORD ProcessID = GetProcessIdByName(process_name);
//远程线程注入
RemoteInject(ProcessID, (char*)"E:\\Mydll.dll");
return 0;
}
执行结果
将生成的dll文件(MyDll.dll)放到E盘目录, 先运行进程B的代码, 随后运行进程A的代码, 可以发现dll文件的线程代码在进程A执行了