APC学习记录

APC概念

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

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

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<stdio.h>
#include<windows.h>

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<ntifs.h>

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<ntifs.h>
#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:将在 64 位操作系统上运行的 32 位应用程序线程上的 APC 包装成适用于 Wow64 环境的形式,这样驱动就可以直接在64位下进行插入,如果读者想修改成32位可以把这个函数删除并将eThread + 0x4c改为eThread + 0x3c

KeAlertThread:可以立即执行我们插入的APC函数

参考资料

https://www.cnblogs.com/sanyimitian/p/14219541.html
https://blog.csdn.net/hongduilanjun/article/details/126850904
火哥视频

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值