内核APC执行过程
3.内核APC执行过程
说明
未文档化但是导出,所以需要提前声明
具体看下面的代码中的struct.h
KeInitializeApc参数
//初始化apc函数
VOID KeInitializeApc(
__out PRKAPC Apc,//使用`PKAPC pKapc`初始化
__in PRKTHREAD Thread,//内核中填当前线程即可
__in KAPC_ENVIRONMENT Environment,//OriginalApcEnvironment
__in PKKERNEL_ROUTINE KernelRoutine,//填回调函数(插入apc后需要执行什么函数)
__in_opt PKRUNDOWN_ROUTINE RundownRoutine,//NULL
__in_opt PKNORMAL_ROUTINE NormalRoutine,//这个不填NULL的话就是普通内核apc
__in_opt KPROCESSOR_MODE ApcMode,//KernelMode内核模式
__in_opt PVOID NormalContext//NULL
);
理论依据如下
kd> dt _kapc
ntdll!_KAPC
+0x000 Type : UChar//apc类型
+0x001 SpareByte0 : UChar//保留
+0x002 Size : UChar//apc结构大小
+0x003 SpareByte1 : UChar//保留
+0x004 SpareLong0 : Uint4B//保留
+0x008 Thread : Ptr32 _KTHREAD//当前apc的结构是挂在那个线程上的
+0x00c ApcListEntry : _LIST_ENTRY//和KAPC_STATE串联,减去0x0c才可以得到kapc
+0x014 KernelRoutine : Ptr32 void //内核函数
+0x018 RundownRoutine : Ptr32 void //特殊的函数
+0x01c NormalRoutine : Ptr32 void //正常的apc的函数表,如果是用户态apc必须写,内核apc可写可不写
+0x020 NormalContext : Ptr32 Void//正常的apc的函数表对应的参数1
+0x024 SystemArgument1 : Ptr32 Void//正常的apc的函数表对应的参数2
+0x028 SystemArgument2 : Ptr32 Void//正常的apc的函数表对应的参数3
+0x02c ApcStateIndex : Char//和线程中的ApcStateIndex没有关系
+0x02d ApcMode : Char//模式,用户apc节点还是用户apc节点,0代表内核,1代表用户
+0x02e Inserted : UChar//插入完是1,记录自己是否被插入过
通过插入带有KernelRoutine
和NormalRoutine
的apc然后执行对应函数的代码
特殊内核apc
struct.h
未文档化的函数的声明
#pragma once
#include <ntifs.h>
//定义和原型
//内核中最常用第一个,其他几个基本用不到,因为无论怎么样,线程最终都会回归原始的环境
typedef enum _KAPC_ENVIRONMENT {
OriginalApcEnvironment,
AttachedApcEnvironment,
CurrentApcEnvironment,
InsertApcEnvironment
} KAPC_ENVIRONMENT;
typedef VOID(*PKNORMAL_ROUTINE) (
IN PVOID NormalContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
);
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 VOID(*PKRUNDOWN_ROUTINE) (
IN struct _KAPC* Apc
);
//初始化apc函数
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
);
//插入apc函数
BOOLEAN KeInsertQueueApc(
__inout PRKAPC Apc,
__in_opt PVOID SystemArgument1,
__in_opt PVOID SystemArgument2,
__in KPRIORITY Increment
);
main.c
#include <ntifs.h>
#include "struct.h"
//普通apc
VOID NormalRoutineFunc(
IN PVOID NormalContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
)
{
}
//特殊apc
VOID KernelAPCRoutineFunc(
IN struct _KAPC* Apc,
IN OUT PKNORMAL_ROUTINE* NormalRoutine,
IN OUT PVOID* NormalContext,
IN OUT PVOID* SystemArgument1,
IN OUT PVOID* SystemArgument2
)
{
//打印一句话然后释放内存
DbgPrint("KernelAPCRoutineFunc insert\r\n");
ExFreePool(Apc);
}
VOID Unload(PDRIVER_OBJECT pDriver)
{
DbgPrint("unload\r\n");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg)
{
//定义一个apc申请内存,因为要进到dpc等级上,所以不能分页
//不同版本的vs的`KAPC`结构体会有变化,所以需要增加一些大小
PKAPC pKapc = ExAllocatePool(NonPagedPool, sizeof(KAPC)+0x100);
//清空内存
memset(pKapc, 0, sizeof(KAPC) + 0x100);
//初始化apc
//插入当前线程
KeInitializeApc(pKapc, PsGetCurrentThread(), OriginalApcEnvironment, KernelAPCRoutineFunc, NULL, NULL, KernelMode, NULL);
//插入apc
BOOLEAN isRet = KeInsertQueueApc(pKapc, NULL, NULL,0);
//如果插入失败,释放内存
if (!isRet)
{
ExFreePool(pKapc);
}
pDriver->DriverUnload = Unload;
//DbgBreakPoint();
DbgPrint("TEST_Entry\r\n");
return STATUS_SUCCESS;
}
普通内核apc
普通内核apc和上面的特殊内核apc唯一的区别就是在倒数第三个参数那里添上了NormalRoutineFunc
函数
然后下面写了一下NormalRoutineFunc
函数的代码
//初始化apc
KeInitializeApc(pKapc, PsGetCurrentThread(), OriginalApcEnvironment, KernelAPCRoutineFunc, NULL, NormalRoutineFunc, KernelMode, NULL
NormalRoutineFunc
代码
//普通apc
VOID NormalRoutineFunc(
IN PVOID NormalContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
)
{
//打印一句话
DbgPrint("NormalRoutineFunc insert\r\n");
}
struct.h同上面的一样
main.c
#include <ntifs.h>
#include "struct.h"
//普通apc
VOID NormalRoutineFunc(
IN PVOID NormalContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
)
{
//打印一句话
DbgPrint("NormalRoutineFunc insert\r\n");
}
//特殊apc
VOID KernelAPCRoutineFunc(
IN struct _KAPC* Apc,
IN OUT PKNORMAL_ROUTINE* NormalRoutine,
IN OUT PVOID* NormalContext,
IN OUT PVOID* SystemArgument1,
IN OUT PVOID* SystemArgument2
)
{
//打印一句话然后释放内存
DbgPrint("KernelAPCRoutineFunc insert\r\n");
ExFreePool(Apc);
}
VOID Unload(PDRIVER_OBJECT pDriver)
{
DbgPrint("unload\r\n");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg)
{
//定义一个apc申请内存,因为要进到dpc等级上,所以不能分页
//不同版本的vs的`KAPC`结构体会有变化,所以需要增加一些大小
PKAPC pKapc = ExAllocatePool(NonPagedPool, sizeof(KAPC)+0x100);
//清空内存
memset(pKapc, 0, sizeof(KAPC) + 0x100);
//初始化apc
//插入当前线程
KeInitializeApc(pKapc, PsGetCurrentThread(), OriginalApcEnvironment, KernelAPCRoutineFunc, NULL, NormalRoutineFunc, KernelMode, NULL);
//插入apc
BOOLEAN isRet = KeInsertQueueApc(pKapc, NULL, NULL,0);
//如果插入失败,释放内存
if (!isRet)
{
ExFreePool(pKapc);
}
pDriver->DriverUnload = Unload;
//DbgBreakPoint();
DbgPrint("TEST_Entry\r\n");
return STATUS_SUCCESS;
}
可以在Windbg上看到执行顺序
KernelAPCRoutineFunc insert
NormalRoutineFunc insert
TEST_Entry
实验1
我们在apc初始化这里传进去一个参数1
//插入当前线程
KeInitializeApc(pKapc, PsGetCurrentThread(), OriginalApcEnvironment, KernelAPCRoutineFunc, NULL, NormalRoutineFunc, KernelMode, (PVOID)1);
然后在这两个回调函数这里都插上断点
//普通apc
VOID NormalRoutineFunc(
IN PVOID NormalContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
)
{
DbgBreakPoint();
//打印一句话
DbgPrint("NormalRoutineFunc insert\r\n");
}
//特殊apc
VOID KernelAPCRoutineFunc(
IN struct _KAPC* Apc,
IN OUT PKNORMAL_ROUTINE* NormalRoutine,
IN OUT PVOID* NormalContext,
IN OUT PVOID* SystemArgument1,
IN OUT PVOID* SystemArgument2
)
{
DbgBreakPoint();
//打印一句话然后释放内存
DbgPrint("KernelAPCRoutineFunc insert\r\n");
ExFreePool(Apc);
}
可以看到先断到KernelRoutine
上,然后我们看一下传进来的参数
可以看到1
已经传进来了而且是个二级指针
接下来我们继续执行,直到断在NormalRoutine
函数上
可以看到里面直接存放我们传进去的参数是个1级指针
实验2
接下来我们在KernelAPCRoutineFunc
函数中更改一下NormalContext
也就是我们传进来的参数的值
//特殊apc
VOID KernelAPCRoutineFunc(
IN struct _KAPC* Apc,
IN OUT PKNORMAL_ROUTINE* NormalRoutine,
IN OUT PVOID* NormalContext,
IN OUT PVOID* SystemArgument1,
IN OUT PVOID* SystemArgument2
)
{
//更改传进来的参数的值
*(PULONG)NormalContext = 100;
DbgBreakPoint();
//打印一句话然后释放内存
DbgPrint("KernelAPCRoutineFunc insert\r\n");
ExFreePool(Apc);
}
可以看到KernelAPCRoutineFunc
中的NormalContext
值已经变成了0x64
也就是100
接下来继续执行
可以看到在NormalRoutineFunc
函数中的NormalContext
参数也改成了100
实验3
新增一个这个函数
VOID OurFunc(
IN PVOID NormalContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
)
{
DbgBreakPoint();
//打印一句话
DbgPrint("OurFunc insert\r\n");
}
然后改一下KernelAPCRoutineFunc
函数
//特殊apc
VOID KernelAPCRoutineFunc(
IN struct _KAPC* Apc,
IN OUT PKNORMAL_ROUTINE* NormalRoutine,
IN OUT PVOID* NormalContext,
IN OUT PVOID* SystemArgument1,
IN OUT PVOID* SystemArgument2
)
{
//让他来到我们自定的`NormalRoutine`中
*NormalRoutine = OurFunc;
*(PULONG)NormalContext = 100;
DbgBreakPoint();
//打印一句话然后释放内存
DbgPrint("KernelAPCRoutineFunc insert\r\n");
ExFreePool(Apc);
}
可以看到在执行后KernelAPCRoutineFunc
后,来到了我们自定义的回调函数这里
而不是按照初始化apc中规定的来到NormalRoutineFunc
了
实验4 在其他进程的线程中创建apc
火哥做这个实验的时候失败了,但是我成功了,不知道为什么
#include <ntifs.h>
#include "struct.h"
//普通apc
VOID NormalRoutineFunc(
IN PVOID NormalContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
)
{
DbgBreakPoint();
//打印一句话
DbgPrint("NormalRoutineFunc insert\r\n");
}
//普通apc
VOID OurFunc(
IN PVOID NormalContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
)
{
DbgBreakPoint();
//打印一句话
DbgPrint("----target2:%d---\r\n", PsGetCurrentProcessId());
DbgPrint("OurFunc insert\r\n");
}
//特殊apc
VOID KernelAPCRoutineFunc(
IN struct _KAPC* Apc,
IN OUT PKNORMAL_ROUTINE* NormalRoutine,
IN OUT PVOID* NormalContext,
IN OUT PVOID* SystemArgument1,
IN OUT PVOID* SystemArgument2
)
{
//让他来到我们自定`NormalRoutine`中
*NormalRoutine = OurFunc;
*(PULONG)NormalContext = 100;
DbgBreakPoint();
DbgPrint("----target1:%d---\r\n", PsGetCurrentProcessId());
//打印一句话然后释放内存
DbgPrint("KernelAPCRoutineFunc insert\r\n");
ExFreePool(Apc);
}
VOID Unload(PDRIVER_OBJECT pDriver)
{
DbgPrint("unload\r\n");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg)
{
//定义一个apc申请内存,因为要进到dpc等级上,所以不能分页
//不同版本的vs的`KAPC`结构体会有变化,所以需要增加一些大小
PKAPC pKapc = ExAllocatePool(NonPagedPool, sizeof(KAPC)+0x100);
//清空内存
memset(pKapc, 0, sizeof(KAPC) + 0x100);
//插入外部线程apc
PETHREAD eThread = NULL;
PsLookupThreadByThreadId(1804, &eThread);
if (!eThread)
{
DbgPrint("获取线程失败\r\n");
ExFreePool(pKapc);
return STATUS_UNSUCCESSFUL;
}
DbgPrint("----main:%d---\r\n", PsGetCurrentProcessId());
//初始化apc
//插入当前线程
KeInitializeApc(pKapc, eThread, OriginalApcEnvironment, KernelAPCRoutineFunc, NULL, NormalRoutineFunc, KernelMode, (PVOID)1);
//插入apc
BOOLEAN isRet = KeInsertQueueApc(pKapc, NULL, NULL,0);
//如果插入失败,释放内存
if (!isRet)
{
ExFreePool(pKapc);
}
pDriver->DriverUnload = Unload;
//DbgBreakPoint();
DbgPrint("TEST_Entry\r\n");
return STATUS_SUCCESS;
}
可以很明显的看到当前的上下文环境已经切换成了指定进程的id了
不过这个代码太乱了,精简一下
#include <ntifs.h>
#include "struct.h"
//普通apc
VOID NormalRoutineFunc(
IN PVOID NormalContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
)
{
}
//特殊apc
VOID KernelAPCRoutineFunc(
IN struct _KAPC* Apc,
IN OUT PKNORMAL_ROUTINE* NormalRoutine,
IN OUT PVOID* NormalContext,
IN OUT PVOID* SystemArgument1,
IN OUT PVOID* SystemArgument2
)
{
DbgPrint("----target:%d---\r\n", PsGetCurrentProcessId());
//打印一句话然后释放内存
DbgPrint("KernelAPCRoutineFunc insert\r\n");
ExFreePool(Apc);
}
VOID Unload(PDRIVER_OBJECT pDriver)
{
DbgPrint("unload\r\n");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg)
{
//定义一个apc申请内存,因为要进到dpc等级上,所以不能分页
//不同版本的vs的`KAPC`结构体会有变化,所以需要增加一些大小
PKAPC pKapc = ExAllocatePool(NonPagedPool, sizeof(KAPC)+0x100);
//清空内存
memset(pKapc, 0, sizeof(KAPC) + 0x100);
//插入外部线程apc
PETHREAD eThread = NULL;
PsLookupThreadByThreadId(1804, &eThread);
if (!eThread)
{
DbgPrint("获取线程失败\r\n");
ExFreePool(pKapc);
return STATUS_UNSUCCESSFUL;
}
DbgPrint("----main:%d---\r\n", PsGetCurrentProcessId());
//初始化apc
//插入当前线程
KeInitializeApc(pKapc, eThread, OriginalApcEnvironment, KernelAPCRoutineFunc, NULL, NormalRoutineFunc, KernelMode, NULL);
//插入apc
BOOLEAN isRet = KeInsertQueueApc(pKapc, NULL, NULL,0);
//如果插入失败,释放内存
if (!isRet)
{
ExFreePool(pKapc);
}
pDriver->DriverUnload = Unload;
//DbgBreakPoint();
DbgPrint("TEST_Entry\r\n");
return STATUS_SUCCESS;
}
想再精简可以把用户apc去掉
实验5 事件等待
添加事件等待,必须等apc中的事件执行完才继续执行
也就是等待事件
#include <ntifs.h>
#include "struct.h"
//普通apc
VOID NormalRoutineFunc(
IN PVOID NormalContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
)
{
//打印一句话
DbgPrint("NormalRoutineFunc insert\r\n");
//发送事件,后面那个`Wait`标志位代表是否一直等待接收到事件,一般不用
KeSetEvent(SystemArgument1, 0, FALSE);
}
//特殊apc
VOID KernelAPCRoutineFunc(
IN struct _KAPC* Apc,
IN OUT PKNORMAL_ROUTINE* NormalRoutine,
IN OUT PVOID* NormalContext,
IN OUT PVOID* SystemArgument1,
IN OUT PVOID* SystemArgument2
)
{
DbgPrint("----target1:%d---\r\n", PsGetCurrentProcessId());
//打印一句话然后释放内存
DbgPrint("KernelAPCRoutineFunc insert\r\n");
ExFreePool(Apc);
}
VOID Unload(PDRIVER_OBJECT pDriver)
{
DbgPrint("unload\r\n");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg)
{
//定义一个apc申请内存,因为要进到dpc等级上,所以不能分页
//不同版本的vs的`KAPC`结构体会有变化,所以需要增加一些大小
PKAPC pKapc = ExAllocatePool(NonPagedPool, sizeof(KAPC) + 0x100);
//清空内存
memset(pKapc, 0, sizeof(KAPC) + 0x100);
//定义事件
PKEVENT pEvent = ExAllocatePool(NonPagedPool, sizeof(KEVENT));
memset(pEvent, 0, sizeof(KEVENT));
//事件初始化
//在内核中有两种事件,一个是同步事件,一个是通知事件,0是同步事件,1是通知事件
//NotificationEvent,通知事件,是通知到所有,假如有十个事件在等待,那么这十个都会接收到通知
//SynchronizationEvent同步事件,多个等待只会让一个抢到,其余没有抢到的依然在等待
//初始化的时候填FLASE没有信号
KeInitializeEvent(pEvent, SynchronizationEvent, FALSE);
//插入外部线程apc
PETHREAD eThread = NULL;
PsLookupThreadByThreadId(2864, &eThread);
if (!eThread)
{
DbgPrint("获取线程失败\r\n");
ExFreePool(pKapc);
ExFreePool(pEvent);
return STATUS_UNSUCCESSFUL;
}
DbgPrint("----main:%d---\r\n", PsGetCurrentProcessId());
//初始化apc
//插入当前线程
KeInitializeApc(pKapc, eThread, OriginalApcEnvironment, KernelAPCRoutineFunc, NULL, NormalRoutineFunc, KernelMode, (PVOID)1);
//插入apc,填入事件`pEvent`
BOOLEAN isRet = KeInsertQueueApc(pKapc, pEvent, NULL, 0);
//如果插入失败,释放内存
if (!isRet)
{
ExFreePool(pKapc);
ExFreePool(pEvent);
return STATUS_UNSUCCESSFUL;
}
else
{
//等待这个事件,内核等待,是否允许唤醒,没有时间限制
KeWaitForSingleObject(pEvent, Executive, KernelMode, FALSE, NULL);
DbgPrint("-------waitOver------\r\n");
ExFreePool(pEvent);
}
pDriver->DriverUnload = Unload;
//DbgBreakPoint();
DbgPrint("TEST_Entry\r\n");
return STATUS_SUCCESS;
}