⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄

📁 兼容内核漫谈 适合想将Windows上的程序移植到其它平台上的朋友研究查看
💻
📖 第 1 页 / 共 4 页
字号:
       }
      
       /* Dequeue the APC */
       RemoveEntryList(ApcListEntry);
       Apc->Inserted = FALSE;
      
       /* Go back to APC_LEVEL */
       KeReleaseSpinLock(&Thread->ApcQueueLock, OldIrql);
      
       /* Call the Kernel APC */
       DPRINT("Delivering a Normal APC: %x\n", Apc);
       KernelRoutine(Apc,
                      &NormalRoutine,
                      &NormalContext,
                      &SystemArgument1,
                      &SystemArgument2);
      
       /* If There still is a Normal Routine, then we need to call this at PASSIVE_LEVEL */
       if (NormalRoutine != NULL) {
        
         /* At Passive Level, this APC can be prempted by a Special APC */
         Thread->ApcState.KernelApcInProgress = TRUE;
         KeLowerIrql(PASSIVE_LEVEL);
        
         /* Call and Raise IRQ back to APC_LEVEL */
         DPRINT("Calling the Normal Routine for a Normal APC: %x\n", Apc);
         NormalRoutine(&NormalContext, &SystemArgument1, &SystemArgument2);
         KeRaiseIrql(APC_LEVEL, &OldIrql);
      }

      /* Raise IRQL and Lock again */
      KeAcquireSpinLock(&Thread->ApcQueueLock, &OldIrql);
      Thread->ApcState.KernelApcInProgress = FALSE;
    }
  }  //end while[/code]


    参数DeliveryMode表示需要“投递”哪一种APC,可以是UserMode,也可以是KernelMode。不过,KernelMode确实表示只要求执行内核APC,而UserMode却表示在执行内核APC之外再执行用户APC。这里所谓“执行内核APC”是执行内核APC队列中的所有请求,而“执行用户APC”却只是执行用户APC队列中的一项。
    所以首先检查内核模式APC队列,只要非空就通过一个while循环处理其所有的APC请求。队列中的每一项(如果队列非空的话)、即每一个APC请求都是KAPC结构,结构中有三个函数指针,但是这里只涉及其中的两个。一个是NormalRoutine,若为非0就是指向一个实质性的内核APC函数。另一个是KernelRoutine,指向一个辅助性的内核APC函数,这个指针不会是0,否则这个KAPC结构就不会在队列中了(注意KernelRoutine与内核模式NormalRoutine的区别)。NormalRoutine为0是一种特殊的情况,在这种情况下KernelRoutine所指的内核函数无条件地得到调用。但是,如果NormalRoutine非0,那么首先得到调用的是KernelRoutine,而指针NormalRoutine的地址是作为参数传下去的。KernelRoutine的执行有可能改变这个指针的值。这样,如果执行KernelRoutine以后NormalRoutine仍为非0,那就说明需要加以执行,所以通过这个函数指针予以调用。不过,内核APC函数的执行是在PASSIVE_LEVEL级别上执行的,所以对NormalRoutine的调用前有KeLowerIrql()、后有KeRaiseIrql(),前者将CPU的运行级别调整为PASSIVE_LEVEL,后者则将其恢复为APC_LEVEL。
    执行完内核APC队列中的所有请求以后,如果调用参数DeliveryMode为UserMode的话,就轮到用户APC了。我们继续往下看:

[code][KiDeliverApc()]

  /* Now we do the User APCs */
  if ((!IsListEmpty(&Thread->ApcState.ApcListHead[UserMode])) &&
      (DeliveryMode == UserMode) && (Thread->ApcState.UserApcPending == TRUE)) {
   
    /* It's not pending anymore */
    Thread->ApcState.UserApcPending = FALSE;

    /* Get the APC Object */
    ApcListEntry = Thread->ApcState.ApcListHead[UserMode].Flink;
    Apc = CONTAINING_RECORD(ApcListEntry, KAPC, ApcListEntry);
   
    /* Save Parameters so that it's safe to free the Object in Kernel Routine*/
    NormalRoutine   = Apc->NormalRoutine;
    KernelRoutine   = Apc->KernelRoutine;
    NormalContext   = Apc->NormalContext;
    SystemArgument1 = Apc->SystemArgument1;
    SystemArgument2 = Apc->SystemArgument2;
   
    /* Remove the APC from Queue, restore IRQL and call the APC */
    RemoveEntryList(ApcListEntry);
    Apc->Inserted = FALSE;
   
    KeReleaseSpinLock(&Thread->ApcQueueLock, OldIrql);
    DPRINT("Calling the Kernel Routine for for a User APC: %x\n", Apc);
    KernelRoutine(Apc,
                  &NormalRoutine,
                  &NormalContext,
                  &SystemArgument1,
                  &SystemArgument2);

    if (NormalRoutine == NULL) {
      /* Check if more User APCs are Pending */
      KeTestAlertThread(UserMode);
    }else {
      /* Set up the Trap Frame and prepare for Execution in NTDLL.DLL */
      DPRINT("Delivering a User APC: %x\n", Apc);
      KiInitializeUserApc(Reserved,
                        TrapFrame,
                        NormalRoutine,
                        NormalContext,
                        SystemArgument1,
                        SystemArgument2);
    }

  } else {
       
    /* Go back to APC_LEVEL */
    KeReleaseSpinLock(&Thread->ApcQueueLock, OldIrql);
  }
}[/code]

    当然,执行用户APC是有条件的。首先自然是用户APC队列非空,同时调用参数DeliveryMode必须是UserMode;并且ApcState中的UserApcPending为TRUE,表示队列中的请求确实是要求尽快加以执行的。
    读者也许已经注意到,比之内核APC队列,对用户APC队列的处理有个显著的不同,那就是对用户APC队列并不是通过一个while循环处理队列中的所有请求,而是每次进入KiDeliverApc()只处理用户APC队列中的第一个请求。同样,这里也是只涉及两个函数指针,即NormalRoutine和KernelRoutine,也是先执行KernelRoutine,并且KernelRoutine可以对指针NormalRoutine作出修正。但是再往下就不同了。
    首先,如果执行完KernelRoutine(所指的函数)以后指针NormalRoutine为0,这里要执行KeTestAlertThread()。这又是跟设备驱动有关的事(Windows术语中的Alert相当于Linux术语中的“唤醒”),我们在这里暂不关心。
    反之,如果指针NormalRoutine仍为非0,那么这里执行的是KiInitializeUserApc(),而不是直接调用NormalRoutine所指的函数,因为NormalRoutine所指的函数是在用户空间,要等CPU回到用户空间才能执行,这里只是为其作好安排和准备。

[code][KiDeliverApc() > KiInitializeUserApc()]

VOID
STDCALL
KiInitializeUserApc(IN PVOID Reserved,
                    IN PKTRAP_FRAME TrapFrame,
                    IN PKNORMAL_ROUTINE NormalRoutine,
                    IN PVOID NormalContext,
                    IN PVOID SystemArgument1,
                    IN PVOID SystemArgument2) 
{
    PCONTEXT Context;
    PULONG Esp;

    . . . . . .
    /*
     * Save the thread's current context (in other words the registers
     * that will be restored when it returns to user mode) so the
     * APC dispatcher can restore them later
     */
    Context = (PCONTEXT)(((PUCHAR)TrapFrame->Esp) - sizeof(CONTEXT));
    RtlZeroMemory(Context, sizeof(CONTEXT));
    Context->ContextFlags = CONTEXT_FULL;
    Context->SegGs = TrapFrame->Gs;
    Context->SegFs = TrapFrame->Fs;
    Context->SegEs = TrapFrame->Es;
    Context->SegDs = TrapFrame->Ds;
    Context->Edi = TrapFrame->Edi;
    Context->Esi = TrapFrame->Esi;
    Context->Ebx = TrapFrame->Ebx;
    Context->Edx = TrapFrame->Edx;
    Context->Ecx = TrapFrame->Ecx;
    Context->Eax = TrapFrame->Eax;
    Context->Ebp = TrapFrame->Ebp;
    Context->Eip = TrapFrame->Eip;
    Context->SegCs = TrapFrame->Cs;
    Context->EFlags = TrapFrame->Eflags;
    Context->Esp = TrapFrame->Esp;
    Context->SegSs = TrapFrame->Ss;
      
    /*
     * Setup the trap frame so the thread will start executing at the
     * APC Dispatcher when it returns to user-mode
     */
    Esp = (PULONG)(((PUCHAR)TrapFrame->Esp) -
                         (sizeof(CONTEXT) + (6 * sizeof(ULONG))));
    Esp[0] = 0xdeadbeef;
    Esp[1] = (ULONG)NormalRoutine;
    Esp[2] = (ULONG)NormalContext;
    Esp[3] = (ULONG)SystemArgument1;
    Esp[4] = (ULONG)SystemArgument2;
    Esp[5] = (ULONG)Context;
    TrapFrame->Eip = (ULONG)LdrpGetSystemDllApcDispatcher();
    TrapFrame->Esp = (ULONG)Esp;
}[/code]

    这个函数的名字取得不好,很容易让人把它跟前面的KeInitializeApc()相连系,实际上却完全是两码事。参数TrapFrame是由KiDeliverApc()传下来的一个指针,指向用户空间堆栈上的“中断现场”。这里要做的事情就是在原有现场的基础上“注水”,伪造出一个新的现场,使得CPU返回用户空间时误认为中断(或系统调用)发生于进入APC函数的前夕,从而转向APC函数。
    怎么伪造呢?首先使用户空间的堆栈指针Esp下移一个CONTEXT数据结构的大小,外加6个32位整数的位置(注意堆栈是由上向下伸展的)。换言之就是在用户空间堆栈上扩充出一个CONTEXT数据结构和6个32位整数。注意,TrapFrame是在系统空间堆栈上,而TrapFrame->Esp的值是用户空间的堆栈指针,所指向的是用户空间堆栈。所以这里扩充的是用户空间堆栈。这样,原先的用户堆栈下方是CONTEXT数据结构Context,再往下就是那6个32位整数。然后把TrapFrame的内容保存在这个CONTEXT数据结构中,并设置好6个32位整数,那是要作为调用参数传递的。接着就把保存在TrapFrame中的Eip映像改成指向用户空间的一个特殊函数,具体的地址通过LdrpGetSystemDllApcDispatcher()获取。这样,当CPU返回到用户空间时,就会从这个特殊函数“继续”执行。当然,也要调整TrapFrame中的用户空间堆栈指针Esp。
    LdrpGetSystemDllApcDispatcher()只是返回一个(内核)全局量SystemDllApcDispatcher的值,这个值是个函数指针,指向ntdll.dll中的一个函数,是在映射ntdll.dll映像时设置好的。

[code]PVOID LdrpGetSystemDllApcDispatcher(VOID)
{
   return(SystemDllApcDispatcher);
}[/code]

    与全局变量SystemDllApcDispatcher相似的函数指针有:
    ● SystemDllEntryPoint,指向LdrInitializeThunk()。

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -