APC学习记录

文章目录

  • APC概念
  • APC插入、执行过程逆向分析
    • 插入过程
    • 执行过程
    • 总结
  • 代码演示
  • 参考资料

APC概念

APC全称叫做异步过程调用,英文名是 Asynchronous Procedure Call,在进行系统调用、线程切换、中断、异常时会进行触发执行的一段代码,其中主要分为内核APC用户APC,故名思意内核APC在执行时APC的代码在内核,用户APC在执行时代码在用户层。

APC是依赖于线程的,所以在线程的KTHREAD中可以找到关于APC的所有相关信息

APC插入、执行过程逆向分析

插入过程

因为APC的插入会调用KeInsertQueueApc,我们逆向分析这个函数即可
APC学习记录_第1张图片APC学习记录_第2张图片插入过程很简单,KeInsertQueueApc其实是在判断APC队列是否禁用或APC是否插入,对应的WRK代码如下
APC学习记录_第3张图片继续跟入KiInsertQueueApc,其中也是根据各种APC模式来进行插入位置的选择
APC学习记录_第4张图片
对应的WRK代码如下
APC学习记录_第5张图片
这里就不继续往下面跟了,感兴趣可以仔细阅读WRK的代码和注释

执行过程

APC执行调用的是KiDeliverApc函数,其中会先执行KernelRoutine中的代码,如果NormalRoutine不为空,则调用KiInitializeUserApc对用户APC进行初始化操作

APC学习记录_第6张图片KiInitializeUserApc通过KeContextFromKframes将KTRAP_FRAME保存一份,以便后续返回使用
APC学习记录_第7张图片修改EIP,使其跳转到三环的KeUserApcDispatcher,执行用户的APC代码
APC学习记录_第8张图片
以上分析对应的WRK代码如下,也可以看出先执行KernelRoutine,后执行NormalRoutine
APC学习记录_第9张图片

总结

插入过程主要是根据参数决定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;
}

驱动代码中主要有两个新鲜的函数PsWrapApcWow64ThreadKeAlertThread

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
火哥视频

你可能感兴趣的:(windows内核,学习,驱动开发,安全,系统安全,windows)