内核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,记录自己是否被插入过

通过插入带有KernelRoutineNormalRoutine的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;
}
posted @ 2024-05-18 17:27  MuRKuo  阅读(14)  评论(0编辑  收藏  举报