APC_LEVEL相关内容整理

本文探讨了WDF框架中线程挂起的原理及其预防措施,通过分析KeEnterCriticalRegion函数的作用,解释了如何阻止线程在处理请求时被挂起,避免系统死锁。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在看WDF框架的时候,看到一段代码注释:

NTSTATUS
__fastcall
FxPkgIo::DispatchStep2(
    __inout  MdIrp       Irp,
    __in_opt FxIoInCallerContext* IoInCallerCtx,
    __in_opt FxIoQueue*  Queue
    )
{       
    //
    // The request inserted into the queue can be retrieved and processed 
    // by an arbitrary thread. So we need to make sure that such a thread doesn't
    // get suspended and deadlock the driver and potentially the system by 
    // entering critical region.The KeEnterCriticalRegion temporarily disables 
    // the delivery of normal kernel APCs used to suspend a thread. 
    // Kernel APCs queued to this thread will get executed when we leave the 
    // critical region. 
    //
    if (Mx::MxGetCurrentIrql() <= APC_LEVEL) {
        Mx::MxEnterCriticalRegion();
        inCriticalRegion = TRUE;
    }

WDF框架使用进入临界区的方法,使得当前线程无法被挂起。对此,我难以理解。翻了一下<Windows内核情景分析>5.14节关于KeSuspendThread的注释:

            if (!Thread->SuspendApc.Inserted)
            {
                /* Not inserted, insert it */
				//创建线程时由KeInitializeThread设置APC为PiSuspendThreadNormalRoutine
                Thread->SuspendApc.Inserted = TRUE;
                KiInsertQueueApc(&Thread->SuspendApc, IO_NO_INCREMENT);
            }

而Thread->SuspendApc在线程初始化时,设置为:

KeInitializeApc(&Thread->SuspendApc,
					Thread,
					OriginalApcEnvironment,
					PsSuspendThreadKernelRoutine,PiSuspendThreadRundownRoutine,
					PiSuspendThreadNormalRoutine,KernelMode,NULL);

PiSuspendThreadNormalRoutine内部仅仅是KeWaitForSingleObject。因此当IRQL退到APC_LEVEL并执行先前插入的APC时,会使得线程的执行被阻塞。

    了解了线程挂起的背景后,再来看看如何阻止线程被挂起:

1).

#define KeEnterCriticalRegion()                                             \  
{                                                                           \  
    PKTHREAD _Thread = KeGetCurrentThread();                                \  
    ASSERT(_Thread == KeGetCurrentThread());                                \  
    ASSERT((_Thread->KernelApcDisable <= 0) &&                              \  
           (_Thread->KernelApcDisable != -32768));                          \  
    _Thread->KernelApcDisable--;                                            \  
}  

上面ASSERT语句反映了Thread->KernelApcDisable的值<=0。当线程进入临界区后,执行完_Thread->KernelApcDisable--;Thread->KernelApcDisable的值最多等于-1,不可能等于0.

2).当IRQL降到DISPATCH_LEVEL以下时,OS会调用KiDeliverApc,来执行APC。然而,执行APC必须满足一定的条件Thread->KernelApcDisable==0:

while (!IsListEmpty(&Thread->ApcState.ApcListHead[KernelMode]))
    {
        /* Lock the APC Queue */
        KiAcquireApcLockAtApcLevel(Thread, &ApcLock);

        /* Check if the list became empty now */
        if (IsListEmpty(&Thread->ApcState.ApcListHead[KernelMode]))
        {
            /* It is, release the lock and break out */
            KiReleaseApcLock(&ApcLock);
            break;
        }

        /* Get the next Entry */
        ApcListEntry = Thread->ApcState.ApcListHead[KernelMode].Flink;
        Apc = CONTAINING_RECORD(ApcListEntry, KAPC, ApcListEntry);

        NormalRoutine = Apc->NormalRoutine; //PiSuspendThreadNormalRoutine
        KernelRoutine = Apc->KernelRoutine;
        NormalContext = Apc->NormalContext; //NULL
        SystemArgument1 = Apc->SystemArgument1;
        SystemArgument2 = Apc->SystemArgument2;

        /* Special APC */
        if (!NormalRoutine)
        {
		...
        }
        else
        {
            /* Normal Kernel APC, make sure it's safe to deliver */
            if ((Thread->ApcState.KernelApcInProgress) ||
                (Thread->KernelApcDisable)) //<----------------Thread->KernelApcDisable==0,才会跳过这个if语句
            {
                /* Release lock and return */
                KiReleaseApcLock(&ApcLock);
                goto Quickie;
            }

            /* Dequeue the APC */
            RemoveEntryList(ApcListEntry);
            Apc->Inserted = FALSE;

            /* Go back to APC_LEVEL */
            KiReleaseApcLock(&ApcLock);

            /* Call the Kernel APC */
            KernelRoutine(Apc,
                          &NormalRoutine,
                          &NormalContext,
                          &SystemArgument1,
                          &SystemArgument2);

            ...
        }
    }

Quickie:
}

但是由于1)的作用,破坏了APC执行的条件,因此APC无法执行,进一步说,线程无法调用KeWaitforSingleObject而一直无法被挂起。

    另外,以前看代码时,忽略了一个细节:当IRQL从APC_LEVEL将到PASSIVE_LEVEL时,如果有APC请求,OS会短暂的提升IRQL并执行APC:

VOID  
NTAPI  
KiCheckForKernelApcDelivery(VOID)  
{  
    KIRQL OldIrql;  
    if (KeGetCurrentIrql() == PASSIVE_LEVEL)  
    {  
        KeRaiseIrql(APC_LEVEL, &OldIrql);  
        KiDeliverApc(KernelMode, 0, 0);  
        KeLowerIrql(PASSIVE_LEVEL);  
    }  
    else  
    {  
        KeGetCurrentThread()->ApcState.KernelApcPending = TRUE;  
        HalRequestSoftwareInterrupt(APC_LEVEL);  
    }  
}  

总结一下:

Thread有3种方式进入APC_LEVEL:

  1. Call KeAcquireFastMutex
  2. Delivery a APC
  3. KeRaiseIrql (一般不用)等。

当使用Fast Mutex进入APC_LEVEL后,对于其它线程,若要获取此Mutex,则会被设置为等待状态。对于线程自己而言,微软说:

Code paths that are protected by a fast mutex run at IRQL=APC_LEVEL, thus disabling delivery of all APCs and preventing the thread from suspension.

即:阻止响应任何APC,而且线程不能被挂起(suspend),为什么不能被挂起?因为操作系统实现线程挂起的方式,就是Delivery APC,在APC的回调函数里面等待一个信号量(这个是我查阅WRK中找到的答案)。由于运行在APC_LEVEL(KeAcquireFastMutex的作用)上,会disabling delivery of all APCs(不响应同一IRQL级别发出的投递APC请求)。

参考: IRQL中断请求级别及APC_LEVEL讨论

ReactOS分析windows APC机制

一:SSDT表的hook检测和恢复 ~!~~~ 二:IDT表的hook检测和恢复 ~~~~~~(idt多处理器的恢复没处理,自己机器是单核的,没得搞,不过多核的列举可以) 三:系统加载驱动模块的检测 通过使用一个全局hash表(以DRIVEROBJECT为对象)来使用以下的方法来存储得到的结果,最终显示出来 1.常规的ZwQuerySystemInformation来列举 2通过打开驱动对象目录来列举 3搜索内核空间匹配驱动的特征来列举(这个功能里面我自己的主机一运行就死机,别的机器都没事,手动设置热键来蓝屏都不行,没dump没法分析,哎,郁闷) 4从本驱动的Modulelist开始遍历来列举驱动 四:进程的列举和进程所加载的dll检测 采用以下方法来列举进程: 1ZwQuerySystemInformation参数SystemProcessesAndThreadsInformation来枚举 2进程EPROCESS 结构的Activelist遍历来枚举 3通过解析句柄表来枚举进程 4通过Handletablelisthead枚举进程 5进程创建时都会向csrss来注册,从这个进程里面句柄表来枚举进程 6通过自身进程的HANDLETABLE来枚举进程 7通过EPROCESS的SessionProcessLinks来枚举进程 8通过EPROCESS ---VM---WorkingSetExpansionLinks获取进程 9暴力搜索内存MmSystemRangeStart以上查找PROCESS对象 进程操作: 进程的唤醒和暂停通过获取PsSuspendProcess和PsResumeProcess来操作的 进程结束通过进程空间清0和插入apc。 采用以下方法查找DLL: 1遍历VAD来查找dll 2挂靠到对应的进程查找InLoadOrderLinks来枚举dll 3暴力搜索对应进程空间查找pe特征来枚举dll DLL的操作: Dll的卸载是通过MmUnmapViewOfSection和MmmapViewOfSection(从sdt表中相应函数搜索到的)来实现的(本来想直接清0 dll空间,有时行有时不行)(只要将这个进程的ntdll卸载了,进程就结束了,一个好的杀进程的办法撒,绿色环保无污染),注入dll使用的是插入apc实现的。(注入的dll必须是realse版的。Debug版会出现***错误,全局dll注入貌似也是)插入apc效果不是很好,要有线程有告警状态才执行。 五:线程信息的检测 遍历ThreadList来枚举线程 线程的暂停和唤醒都是通过反汇编获取PsResumeThread和PsSuspendThread直接从r3传来ETHREAD来操作的,通过插入APC来结束线程 六:shadow sdt表的hook检测与恢复 没有采用pdb来解决函数名问题,直接写入xp和03的shandow表函数名(主要是自己的网不稳定,连windbg有时都连不上微软) 七:系统所有的过滤驱动的检测 查看各device下是否挂接有驱动之类的,可直接卸载 八:系统常用回调历程的检测和清除 只检查了PsSetLoadImageNotifyRoutine PsSetCreateThreadNotifyRoutine PsSetCreateProcessNotifyRoutine CmRegisterCallback这几个,至于那个什么shutdown回调不知道是啥玩意,就没搞了,有知道的顺便告诉我下撒,谢谢 九:文件系统fat和ntfs的分发函数检测 直接反汇编fat和ntfs的DriverEntry得到对应的填充分发的偏移,然后和当前已经运行的文件系统的分发相比是否被hook,并附带恢复 十:文件查看功能 自己解析ntfs和fat的结构,来实现列举文件和直接写磁盘删除。附带有普通的删除和发生IRP来删除。不过这里面有点问题,ntfs删除有时把目录给搞坏了,大家凑合着吧, Ntfs网上删除这些操作的代码不多,就是sudami大大的利用ntfs-3g来实现的,看了下,太多了,充满了结构。然后自己对照着系统删除文件时目录的变化来自己实现的。只处理了$BITMAP对应的位清除,父目录的对应文件的索引项的覆盖,删除文件对应的filerecord清0. 另外偷懒时间都没处理,呵呵,y的,一个破时间都都搞好几个字节移来移去的。 十一:常用内核模块钩子的检测和恢复 这里只检测了主要的内核模块nkrnlpa**.exe的.win32k.sys,hal.dll,比对它们的原始文件从而查找eat inline hook,iat hook ,和inline hook。Inline是从TEXT段开始一段位置开始比较的。(有点慢貌似,等待显示扫描完成就好了) 十二:应用层进程所加载dll的钩子 应用层钩子检测和内核模块钩子检测原理一样,不过为了能读写别的进程的空间,并没有使用openprocess去打开进程,而是通过KiattachProcess挂靠到当前进程,然后通过在r0直接读写进程空间的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值