APC的本质

 Windows内核分析索引目录:https://www.cnblogs.com/onetrainee/p/11675224.html

APC的本质

一、对于线程关闭问题的启发

  线程,本身占据CPU,对CPU有直接控制权。

  这就存在一个问题,如果一个线程不想关闭自己,则外界是无法干涉它的。

  因此,线程需要自己杀死自己。

  但是线程本身是代码,其并不知道何时才需要杀死自己,这时需要我们另外提供代码,让其杀死自己。

  因此,线程的操作机制就是:定时检查是否有另外执行的代码,然后去执行。该代码(函数),就是APC。

 

二、APC介绍

  1. APC,即Asynchronous procedure call,异步程序调用。

    我们理解“异步”这个词,线程本身的代码是“同步”的,在此之外可以认为是异步。

  2. _KAPC_STATE 结构体(具体作用右侧已经标记出来)

    其在 _KTHREAD + 0x34 的位置。

    kd> dt _KAPC_STATE
      ntdll!_KAPC_STATE
       +0x000 ApcListHead      : [2] _LIST_ENTRY  // APC队列,两个双向链表,分别指向用户与内核结构的APC
       +0x010 Process          : Ptr32 _KPROCESS // 线程所属的进程或者挂靠的进程
       +0x014 KernelApcInProgress : UChar  //  表示当前内核中的APC程序是否正在执行
       +0x015 KernelApcPending : UChar  // 表示APC队列中是否有内核APC函数,如果有为1,否则为0.
       +0x016 UserApcPending   : UChar   // 表示APC队列中是否用用户APC函数,如果有为1,否则为0.

  3. _KAPC 结构体

     kd> dt _KAPC
       ntdll!_KAPC
       +0x000 Type             : Int2B
       +0x002 Size             : Int2B
       +0x004 Spare0           : Uint4B
       +0x008 Thread           : Ptr32 _KTHREAD
       +0x00c ApcListEntry     : _LIST_ENTRY
       +0x014 KernelRoutine    : Ptr32     void
       +0x018 RundownRoutine   : Ptr32     void
       +0x01c NormalRoutine    : Ptr32     void
       +0x020 NormalContext    : Ptr32 Void
       +0x024 SystemArgument1  : Ptr32 Void
       +0x028 SystemArgument2  : Ptr32 Void
       +0x02c ApcStateIndex    : Char
       +0x02d ApcMode          : Char
       +0x02e Inserted         : UChar
    在 _KAPC_STATE中前两个双向链表指向的就是一个个 _KAPC函数成员, 通过+0x01c NormalRoutine,可以查看代码(注意并不是指向函数地址)

三、什么时候执行APC函数

  1. 先介绍两个函数

    a. KiServiceExit函数:这个函数是系统调用异常中断的必经之路。

    b. KiDeliverApc函数: 负责执行APC函数。

  2. 在执行 KiServiceExit函数时,其会从线程结构体中拿出 _KAPC_STATE.KernelApcPending是否为零。

   如果不为零,则会调用KiDeliverApc去执行APC函数,当执行完一次后再跳转回来进行遍历。

    

 

 

 

四、KiServiceExit2部分源码解读:

在WindowsXp 专业版的 ntoskrnl.exe 中并未搜索到 KiServiceExit 函数;在有关快速调用的代码中查看到 KiServiceExit2 (ntkrnlpa.exe)

.text:004667F0                 cli
.text:004667F1                 test    dword ptr [ebp+70h], 20000h
.text:004667F8                 jnz     short loc_466800 ; 获取Kthread
.text:004667FA                 test    byte ptr [ebp+6Ch], 1
.text:004667FE                 jz      short loc_466834
.text:00466800
.text:00466800 loc_466800:                             ; CODE XREF: _KiServiceExit2+8↑j
.text:00466800                                         ; _KiServiceExit2+41↓j
.text:00466800                 mov     ebx, ds:0FFDFF124h ; 获取Kthread
.text:00466806                 mov     byte ptr [ebx+2Eh], 0
.text:0046680A                 cmp     byte ptr [ebx+4Ah], 0 ; 判断是否有用户APC请求
.text:0046680E                 jz      short loc_466834 ; 如果有用户APC请求会走这里
.text:00466810                 mov     ebx, ebp
.text:00466812                 mov     ecx, 1          ; NewIrql
.text:00466817                 call    ds:__imp_@KfRaiseIrql@4 ; KfRaiseIrql(x)
.text:0046681D                 push    eax
.text:0046681E                 sti
.text:0046681F                 push    ebx
.text:00466820                 push    0
.text:00466822                 push    1
.text:00466824                 call    _KiDeliverApc@12 ; 该函数实现对APC的处理
.text:00466829                 pop     ecx             ; NewIrql
.text:0046682A                 call    ds:__imp_@KfLowerIrql@4 ; KfLowerIrql(x)
.text:00466830                 cli
.text:00466831                 jmp     short loc_466800 ; 获取Kthread

 

posted @ 2019-11-03 09:31  OneTrainee  阅读(1281)  评论(0编辑  收藏  举报