• 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
    #include
    
    void haha()
    {
        printf("APC被执行了!\n");
    }
    
    int main()
    {
        printf("pid:%d 函数地址:%x \n", GetCurrentThreadId(), haha);
        while (1)
        {
            SleepEx(30000,FALSE);
            printf("qqqqqqqqqqqq\n");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    驱动头文件“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
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    驱动代码

    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    驱动代码中主要有两个新鲜的函数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
    火哥视频

  • 相关阅读:
    Python之“诗词大会”游戏
    使用canvas实现时间轴上滑块的各种常用操作(仅供参考)
    【HarmonyOS应用开发】三方库(二十)
    iOS系统暗黑模式
    AT89S51编辑和烧录软件过程
    基于Javaee的影视创作论坛的设计与实现(源码开放)
    MATLAB程序设计与应用 3.2 矩阵变换
    kafka安装流程
    大端模式与小端模式
    【加载数据--自定义自己的Dataset类】
  • 原文地址:https://blog.csdn.net/qq_45844442/article/details/134089592