APC全称叫做异步过程调用,英文名是 Asynchronous Procedure Call
,在进行系统调用、线程切换、中断、异常时会进行触发执行的一段代码,其中主要分为内核APC
与用户APC
,故名思意内核APC在执行时APC的代码在内核,用户APC在执行时代码在用户层。
APC是依赖于线程的,所以在线程的KTHREAD中可以找到关于APC的所有相关信息
因为APC的插入会调用KeInsertQueueApc,我们逆向分析这个函数即可
插入过程很简单,KeInsertQueueApc其实是在判断APC队列是否禁用或APC是否插入,对应的WRK代码如下
继续跟入KiInsertQueueApc,其中也是根据各种APC模式来进行插入位置的选择
对应的WRK代码如下
这里就不继续往下面跟了,感兴趣可以仔细阅读WRK的代码和注释
APC执行调用的是KiDeliverApc函数,其中会先执行KernelRoutine中的代码,如果NormalRoutine不为空,则调用KiInitializeUserApc对用户APC进行初始化操作
KiInitializeUserApc通过KeContextFromKframes将KTRAP_FRAME保存一份,以便后续返回使用
修改EIP,使其跳转到三环的KeUserApcDispatcher,执行用户的APC代码
以上分析对应的WRK代码如下,也可以看出先执行KernelRoutine,后执行NormalRoutine
插入过程
主要是根据参数决定APC插入链表的位置
执行过程
主要是先执行参数的KernelRoutine的代码,如果有NormalRoutine则跳到三环去遍历执行再回到内核,以此往复将链表中的所有APC执行完毕
用户层被插入代码
#include
#include
void haha()
{
printf("APC被执行了!\n");
}
int main()
{
printf("pid:%d 函数地址:%x \n", GetCurrentThreadId(), haha);
while (1)
{
SleepEx(30000,FALSE);
printf("qqqqqqqqqqqq\n");
}
}
驱动头文件“test.h”
#pragma once
#include
typedef
VOID
(*PKNORMAL_ROUTINE) (
IN PVOID NormalContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
);
typedef
VOID
(*PKRUNDOWN_ROUTINE) (
IN struct _KAPC* Apc
);
typedef
VOID
(*PKKERNEL_ROUTINE) (
IN struct _KAPC* Apc,
IN OUT PKNORMAL_ROUTINE* NormalRoutine,
IN OUT PVOID* NormalContext,
IN OUT PVOID* SystemArgument1,
IN OUT PVOID* SystemArgument2
);
typedef enum _KAPC_ENVIRONMENT {
OriginalApcEnvironment,
AttachedApcEnvironment,
CurrentApcEnvironment,
InsertApcEnvironment
} KAPC_ENVIRONMENT;
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
);
BOOLEAN KeInsertQueueApc(
__inout PRKAPC Apc,
__in_opt PVOID SystemArgument1,
__in_opt PVOID SystemArgument2,
__in KPRIORITY Increment
);
BOOLEAN
KeAlertThread(
__inout PKTHREAD Thread,
__in KPROCESSOR_MODE AlertMode
);
驱动代码
#include
#include"test.h"
VOID DriverUnload(_In_ struct _DRIVER_OBJECT* DriverObject)
{
DbgPrint("--------------DRIVER_UNLOAD-----------------");
}
VOID kernelRoutineFunc(
IN struct _KAPC* Apc,
IN OUT PKNORMAL_ROUTINE* NormalRoutine,
IN OUT PVOID* NormalContext,
IN OUT PVOID* SystemArgument1,
IN OUT PVOID* SystemArgument2
)
{
DbgPrintEx(77, 0, "[db]:---------kernelRoutineFunc pid = %d--------------\r\n", PsGetCurrentProcessId());
DbgPrintEx(77, 0, "[db]:kernelRoutineFunc\r\n");
ULONG64 addr = 0x401000;
PsWrapApcWow64Thread(NULL, &addr);
*NormalRoutine = addr;
ExFreePool(Apc);
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath)
{
PKAPC pApc = ExAllocatePool(NonPagedPool, sizeof(KAPC));
memset(pApc, 0, sizeof(KAPC));
PETHREAD eThread = NULL;
PsLookupThreadByThreadId(2632, &eThread);
KeInitializeApc(pApc, eThread, OriginalApcEnvironment,
kernelRoutineFunc, NULL, 0x401000, UserMode, (PVOID)1);
DbgBreakPoint();
*(PCHAR)((PCHAR)eThread + 0x4c) |= 0x20;
BOOLEAN is = KeInsertQueueApc(pApc, eThread, NULL, 0);
if (!is)
{
ExFreePool(pApc);
}
KeAlertThread(eThread, UserMode);
pDriverObject->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
驱动代码中主要有两个新鲜的函数PsWrapApcWow64Thread
与KeAlertThread
PsWrapApcWow64Thread
:为了将32位的地址进行转换到64位进行使用,这样驱动就可以直接在64位下进行插入,如果读者想修改成32位可以把这个函数删除并将eThread + 0x4c改为eThread + 0x3c
KeAlertThread
:可以立即执行我们插入的APC函数
https://www.cnblogs.com/sanyimitian/p/14219541.html
https://blog.csdn.net/hongduilanjun/article/details/126850904
火哥视频