NT 下动态切换进程分析笔记

Linux 进程与线程概念解析 线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者.传统的Unix也支持线程的概念,但是在一个进程(process)中只允许有一个线程,这样多线程就意味着多进程.现在,多线程技术已经被许多操作系统所支持,包括Windows/NT、Linux。 进程:进程是一个具有一定独立功能的程序的一次运行活动,同时也是资源分配的最小单元; 阅读详情
Author: sinister
Email:   sinister@whitecell.org
Homepage: http://www.whitecell.org
Date:   2005-11-16


2005-2-22

  在应用层想要动态嵌入其他进程内存空间,我们可以调用 CreateRemoteThread()
等相关函数来实现。那么在内核态想要动态嵌入其他进程内存空间又如何做呢?这时
我们需要调用一个未公开的内核函数 KeAttachProcess(),利用这个函数我们可以在
内核态实现将代码插入到其他进程地址空间中。下面是一段演示代码。


NTSTATUS PsLookupProcessByProcessId( IN ULONG ulProcId, OUT PEPROCESS * pEProcess);
VOID KeAttachProcess ( IN PEPROCESS pEProcess );
VOID KeDetachProcess ( VOID );


NTSTATUS KeSwitchProcess(ULONG uPID)
{
      PEPROCESS   EProcess;
      LPTSTR     lpCurProc;
      DWORD     dwCR3;
      NTSTATUS   ntStatus;
     
      ntStatus = PsLookupProcessByProcessId(uPID, &EProcess);

      if (!NT_SUCCESS( ntStatus ))
      {

           DbgPrint("PsLookupProcessByProcessId()/n");
          return ntStatus;
      }

    KeAttachProcess(EProcess);
      __asm{
        mov eax,cr3
        mov dwCR3,eax
      }

    lpCurProc = (LPTSTR)EProcess;
    lpCurProc = lpCurProc + ProcessNameOffset;
    DbgPrint("CR3 = %x, PROCESS = PROCESS NAME: %s, PROCESS ID: %d, PROCESS ADDRESS %x:/n",
          dwCR3,
          lpCurProc,
          uPID,
          EProcess
          );

    KeDetachProcess();

    return STATUS_SUCCESS;

}

NTSTATUS KeEnumProcess(VOID)
{
  ULONG nextoffset;
  NTSTATUS rc;
  ULONG ReturnLength = 0;
  SYSTEM_PROCESSES *curr = NULL;
  PVOID SystemInformationBuffer = ExAllocatePool(PagedPool, 255*sizeof(SYSTEM_PROCESSES));
 

  rc = ZwQuerySystemInformation(5, SystemInformationBuffer, 255*sizeof(SYSTEM_PROCESSES), &ReturnLength );

  if(NT_SUCCESS(rc))
  {
       curr = (SYSTEM_PROCESSES *)SystemInformationBuffer;
 
    while(curr)
    {

      rc = SwitchProcess(curr->ProcessId);
      if (!NT_SUCCESS( rc ))
        {
          DbgPrint("AttachProcess() to failed!/n");
        }

      nextoffset = curr->NextEntryDelta;
        if (nextoffset) {
         curr = (SYSTEM_PROCESSES *)((PCHAR)curr + nextoffset);
        }
        else curr = NULL;
    }
  }

  ExFreePool(SystemInformationBuffer);
  return rc;
}


  为了调试方便,我枚举系统中所有进程,并依次调用 KeAttachProcess(),KeDetachP
rocess() 函数动态嵌入和退出,通过打印 CR3 的值我们可以看到确实是实现了动态嵌
入的目的,在这里我们并不想介绍嵌入函数的用法,而是想要了解动态切换进程内存空
间的实现方法。首先我们来分析一下 KeAttachProcess() 函数的流程,当调用此函数时,
首先从 KPCR 结构中得到当前线程 ETHREAD 结构,并提升当前 IRQL 为 DISPATCH_LEVEL
(防止在进程切换中被其他软件列程所中断),并从形参中取出要切换进程的 EPROCESS
结构与当前进程 EPROCESS 结构进行比较,如果是当前进程的话,则降低当前 IRQL 为
初始值,函数返回。如果要进行切换的进程不是当前进程的话,先要判断当前线程是否
处于 APC 与 DPC 活动状态(ETHREAD->ApcStateIndex,KPCR->DpcRoutineActive),
如果是则不允许动态切换进程,跳转到蓝屏处,系统崩溃。(微软规定在进行动态切换
时不允许 APC ,DPC 列程运行,否则将系统崩溃,这应该不是必须的)如果不是则从形
参中取当前线程(ETHREAD)、要切换的进程(EPROCESS)初始的 IRQL 值和当前线程的
APC 保存状态 (ETHREAD->SavedApcState) 做为参数,来调用 KiAttachProcess()函
数完成具体的切换。


  从上面可以看出 KeAttachProcess() 函数只是进行了一些相关参数的设置和系统环
境的判断后简单的调用了 KiAttachProcess() 函数,那么我们需要对 KiAttachProcess()
流程做一下分析。当调用此函数时先把要切换的进程的内核堆栈数加一(EPROCESS->Stack
Count),并从形参中取得当前线程保存 APC 状态的位置,(ETHREAD->SaveApcState)
得到当前线程 APC 状态(ETHREAD->ApcState),来调用 KiMoveApcState() 函数,将当
前线程APC 状态 (ETHREAD->ApcState) 复制到当前线程 SavedApcState 处保存。然后
将当前线程内核 APC 的 Progress 状态、内核模式 APC 的 Pending 状态、用户模式
APC 的 Pending 态均设置为 FALSE(ETHREAD->KernelApcInProgress、ETHREAD->Kernel
ApcPending、ETHREAD->UserApcPending),并初始化当前线程内核模式与用户模式 APC
状态链(ETHREAD->>ApcState.ApcListHead[KernelMode]、ETHREAD->ApcState.ApcList
Head[UserMode]),然后将当前线程所在进程(ETHREAD->ApcState->EPROCESS)设置为
要切换的进程(EPROCESS),比较当前线程保存 APC 状态(ETHREAD->SavedApcState)
是否与形参中(SavedApcState)要输出的保存 APC 态相等,如相等则需要将当前线程
APC 状态与保存 APC 状态(ETHREAD->ApcState、ETHREAD->SavedApcState)分别赋与
当前线程的 ApcStatePointer[0] 与 ApcStatePointer[1],(ApcStatePointer 是
KAPC_STATE 结构数组而 KAPC_STATE 又是由 LIST_ENTRY 链表描述的)然后在设置当前
线程 APC 状态索引为 1(ETHREAD->ApcStateIndex)。这里需要重点讲解一下,上述基
本都是处理与 APC 相关的操作,(APC 即“异步过程调用” )为什么要做这些操作呢?
在进程调度中怎么没有看到相关的操作?那是因为进行强行切换时需要中断当前线程运
行切换到新的进程中,这种切换又跟时钟中断所产生的进程调度不一样,时钟中断产生
的进程调度会根据时间片与线程优先级等让被中断的线程 APC 重新得以运行,而这种强
行进程切换如果不调用退出函数,是不会让原线程 APC 运行的,所以首先要保留当前线
程的 APC 状态,状态保存后为了不让当前 APC 状态影响要切换的新进程,则需要把相关
APC 位清0。为了更加明确,我这里配合 KeDetachProcess() 函数深入讲解一下,当调用
KeDetachProcess() 函数退出时,首先得到当前线程所指象的进程(ETHREAD->ApcState-
>EPROCESS),这里要注意,当前运行环境是已被嵌入的新进程,所以得到的是被嵌入的
进程,退出函数的下一步就是调用KiMoveApcState 来恢复 SavedApcState 到当前线程
APC 状态(ETHREAD->ApcState),
这时的 ETHREAD->ApcState->EPROCESS 是原始进程和刚才第一次调用得到的被嵌入的新
进程是两个 EPROCESS 结构地址了,待做一些相关操作后调用 KiAttachProcess() 来切
换回到原始进程。在段落开头时讲过 KiAttachProcess() 调用会先判断要进行嵌入的是
否为同一进程,如果是则退出,经过上述对线程相关的 APC 操作后,现在是完全不同的
两个进程,所以可以顺利的切换回原始进程。想象一下我们经常通过一个线程(ETHREAD)
的偏移 0x44 处得到它所属的进程(EPROCESS)而这个进程正是在当前线程的 APC 状态
中的(ETHREAD->ApcState->EPROCESS)通过上面的分析,可以看出微软把对 APC 的处
理与ETHREAD,EPROCESS 结构之间的连接安排的非常巧妙,充分考虑了各种环境应用。
好了,当相关 APC 设置完成后,我们需要比较要切换的进程是否在内存中?(EPROCESS
->State)(因为有可能进程在等待或挂起时相关页面已交换到硬盘页面文件中)如果不
存在则不能马上进行切换,要将当前线程等待链(ETHREAD->WaitListEntry)插入到要
切换进程的就绪链中(EPROCESS->ReadyListHead),并设置当前线程为就绪状态(ETHR
EAD->State)和当前线程链所指向的进程就绪队列(ETHREAD->ProcessReadyQueue)为
TRUE。然后判断要切换的进程状态(EPROCESS->State)是否已经交换出当前内存,如果
为其他状态则直接调用 KiSwapThread() 函数来完成切换。如果已交换出当前内存状态
则将要切换的进程状态(EPROCESS->State)设置为 Transition 并将要切换进程的交换
链(EPROCESS->SwapListEntry)插入到全局进程输入交换链 KiProcessInSwapListHead
中继续设置全局交换事件状态(KiSwapEvent.Header.SignalState),判断全局交换事
件等待链头(KiSwapEvent.Header.WaitListHead)是否为空如果不为空则需要调用 KiW
aitTest() 函数来测试全局交换事件 (KiSwapEvent)余额。最后调用 KiSwapThread()
来完成最终切换并跳转到函数结束处。如果存在当前内存中,则遍历要切换进程的就绪
链(EPROCESS->ReadyListHead),如果要切换进程的下一节点没有就绪,则从线程等待
链里得到一个线程(ETHREAD)并移除刚刚得到的要切换进程的节点,设置得到的线程链
所指向的进程就绪队列(ETHREAD->ProcessReadyQueue)为 FALSE,然后调用 KiReady
Thread() 来就绪得到的线程(ETHREAD),并得到要切换进程就绪链的下一节点,重复
上述步骤直到要切换进程的下一节点就绪,这时调用 KiSwapProcess() 函数来完成最终
的切换工作,切换完成后从形参中得到初始 IRQL 值,调用 KfLowerIrql() 来降低当前
IRQL 为初始值,函数结束。这里可能会有疑问,既然当前进程在内存中直接调用 KiSw
apProcess() 来切换不就行了?为什么还需要做那么多的设置?那是因为必须是有一个
线程在你要切换进程的就绪队列中,这个时候才可以实现强行切换。


这里对最终切换进行一个分析,上面提到了两种切换方式,一种是 KiSwapThread(),
在这份笔记里我不打算记录它,我想在下一份笔记里将线程优先级分析等结合在一起来
讲。这份笔记里仅仅记录对 KiSwapProcess() 的分析,其实 KiSwapProcess()和《NT
内核的进程调度分析笔记》中写到的 SwapContext() 函数大致一样,而且还比他简单了
许多,因为要被嵌入的进程许多环境和参数都已设置好了。函数实现就是,先得到要切换
进程的 LDT 描述符(EPROCESS->LdtDescriptor)与页目录表(EPROCESS->DirectoryTa
bleBase),判断要切换的进程 LDT 不为空,如果不为空则从 KPCR 中取得 KGDT 的位
置,并从 KGDT 中索引到 LDT,把要切换的进程(EPROCESS->LdtDescriptor)中的 LDT
赋与 KPCR 中的LDT。再得到 KPCR 中 IDT的位置,把当前进程(EPROCESS->Int21Descr
iptor)中的 INT 21中断赋与 KPCR 中 KIDT中的相应位置,使当前进程可以调用 INT
21。最后跳转到调用 LLDT 使当前所有设置生效。(按理说 NT 内核中 32 位应用程序
是不使用 LDT 的但为什么在线程切换中会有设置LDT的部分呢?这是为了向下兼容 16
位的应用程序,当调度到一个 16 位的应用程时则会特意为它分配 LDT 并且使 IDT 中的
INT 21有效,玩过 DOS 的人都知道 INT 21 是 DOS 下的系统调用,可以试着运行一个
16 位的 DOS 程序,然后观察下 IDT 表就会发现,原来没有用到的 INT 21 会被设置成
一个 16 位的 TrapGate),否则用要切换进程的页目录来刷新 KTSS 中的 CR3 值与当前
CR3 值,并用要切换进程的 IOPM 来填充 KTSS 中的 IOPM。完成切换,函数返回。


:u KeAttachProcess l 200
ntoskrnl!KeAttachProcess
0008:8042BE1A PUSH     EBP
0008:8042BE1B MOV     EBP,ESP
0008:8042BE1D PUSH     ECX
0008:8042BE1E PUSH     ESI
0008:8042BE1F MOV     EAX,FS:[00000124]  
0008:8042BE25 MOV     ESI,EAX   注释:ESI = 当前线程(ETHREAD)
0008:8042BE27 CALL     [HAL!KeRaiseIrqlToDpcLevel]
0008:8042BE2D MOV     ECX,[EBP+08] 注释:ECX = 要切换进程的(EPROCESS)
0008:8042BE30 MOV     [EBP-04],AL   注释:保存初始 IRQL 值到临时变量
0008:8042BE33 CMP     [ESI+44],ECX
0008:8042BE36 JNZ     8042BE46
0008:8042BE38 MOV     CL,AL  
0008:8042BE3A CALL   80403FD0 注释:降低当前 IRQL(CL=初始IRQL值)
0008:8042BE3F POP     ESI
0008:8042BE40 LEAVE
0008:8042BE41 RET     0004   注释:KeAttachProcess() 函数调用完毕,返回。

从 KPCR 结构中得到当前线程 ETHREAD 结构,并提升当前 IRQL 为 DISPATCH_LEVEL 并
从形参中取出要切换进程的 EPROCESS 结构与当前进程 EPROCESS 结构进行比较,如果
要进行切换的进程不是当前进程的话,则跳转到具体切换地址进行处理。如果是当前进
程的话,则降低当前 IRQL 为初始值,函数返回。

0008:8042BE44 JMP     8042BE3F

跳转函数完成地址。

0008:8042BE46 CMP     BYTE PTR [ESI+00000159],00
0008:8042BE4D JNZ     8042BE6C

判断如果当前线程(ETHREAD->ApcStateIndex)处于 APC 运行状态,则不允许动态切换
进程,跳转到蓝屏处,系统崩溃。

0008:8042BE4F MOV     EAX,FS:[0000080C]
0008:8042BE55 TEST     EAX,EAX
0008:8042BE57 JNZ     8042BE6C

判断如果当前 有 DCP 列程运行 (KPCR->DpcRoutineActive) 则不允许动态切换进程,
跳转到蓝屏处,系统崩溃。

0008:8042BE59 LEA     EAX,[ESI+00000140] 注释:当前线程的 APC 状态(ETHREAD->SavedApcState)
0008:8042BE5F PUSH     EAX
0008:8042BE60 PUSH     DWORD PTR [EBP-04] 注释:初始的 IRQL 值
0008:8042BE63 PUSH     ECX           注释:要切换的进程(EPROCESS)
0008:8042BE64 PUSH     ESI           注释:当前线程(ETHREAD)
0008:8042BE65 CALL   8042C2F2       注释:调用 KiAttachProcess() 函数

如果当前没有 APC,DPC 等调用处理,则取当前线程(ETHREAD)、要切换的进程(EPROC
ESS)初始的 IRQL 值和当前线程的 APC 保存状态 (ETHREAD->SavedApcState) 做为参
数,来调用KiAttachProcess() 函数完成具体的切换。

0008:8042BE6A JMP     8042BE3F

进程切换完成后,直接跳转函数完成地址。

0008:8042BE6C MOV     EAX,FS:[0000080C]
0008:8042BE72 PUSH     EAX
0008:8042BE73 MOVZX   EAX,BYTE PTR [ESI+00000159]
0008:8042BE7A PUSH     EAX
0008:8042BE7B PUSH     DWORD PTR [ESI+44]
0008:8042BE7E PUSH     ECX
0008:8042BE7F PUSH     05
0008:8042BE81 CALL     ntoskrnl!KeBugCheckEx

调用 KeBugCheckEx() 蓝屏,系统崩溃,并显示错误信息(当前进程,要切换到的目标进
程、APC,DPC 状态等)


_______________________________________________________________________________________



下面是 KiAttachProcess() 实现细节

:u 8042c2f2 l 200

0008:8042C2F2 PUSH     EBP
0008:8042C2F3 MOV     EBP,ESP
0008:8042C2F5 PUSH     EBX
0008:8042C2F6 PUSH     ESI
0008:8042C2F7 MOV     ESI,[EBP+08]     注释:ESI = 当前线程(ETHREAD)
0008:8042C2FA PUSH     EDI
0008:8042C2FB PUSH     DWORD PTR [EBP+14] 注释:形参 SavedApcState
0008:8042C2FE MOV     EDI,[EBP+0C]     注释:EDI = 要切换进程的(EPROCESS)
0008:8042C301 LEA     EBX,[ESI+34]     注释:EBX = 当前线程APC状态(ETHREAD->ApcState)

0008:8042C304 INC     WORD PTR [EDI+60]
0008:8042C308 PUSH     EBX
0008:8042C309 CALL   8042C3FC       注释:调用 KiMoveApcState() 函数

把要切换的进程(EPROCESS->StackCount)的内核堆栈数加一,并从形参中取得当前线程
(ETHREAD->SaveApcState)保存APC状态的位置,得到当前线程(ETHREAD->ApcState)
APC 状态,来调用 KiMoveApcState() 函数。


0008:8042C30E AND     BYTE PTR [ESI+48],00
0008:8042C312 AND     BYTE PTR [ESI+49],00
0008:8042C316 AND     BYTE PTR [ESI+4A],00
0008:8042C31A LEA     EAX,[ESI+3C]      
0008:8042C31D MOV     [ESI+40],EAX
0008:8042C320 PUSH     01
0008:8042C322 MOV     [EAX],EAX
0008:8042C324 LEA     EAX,[ESI+00000140] 注释:EAX = 当前线程(ETHREAD->SavedApcState)保存 ACP 状态
0008:8042C32A CMP     [EBP+14],EAX 注释:[EBP+14] = 形参 SavedApcState。
0008:8042C32D MOV     [ESI+38],EBX
0008:8042C330 MOV     [EBX],EBX
0008:8042C332 MOV     [ESI+44],EDI
0008:8042C335 POP     EDX      
0008:8042C336 JNZ     8042C34A

将当前线程(ETHREAD->KernelApcInProgress、ETHREAD->KernelApcPending、ETHREAD->
UserApcPending)内核 APC 的 Progress 状态、内核模式 APC 的 Pending 状态、用户
模式 APC 的 Pending 态均设置为 FALSE,并初始化当前线程ETHREAD->>ApcState.ApcL
istHead[KernelMode] ETHREAD->>ApcState.ApcListHead[UserMode])内核模式与用户模
式 APC 状态链,然后将当前线程(ETHREAD->ApcState->EPROCESS)所在进程设置为要切
换的进程(EPROCESS),比较当前线程(ETHREAD->SavedApcState)保存 APC 状态是否
与形参中(SavedApcState)要输出的保存 APC 态相等,如果不相等则直接跳转到比较
当前进程(EPROCESS->State)状态处。

0008:8042C338 MOV     [ESI+0000012C],EAX
0008:8042C33E MOV     [ESI+00000130],EBX
0008:8042C344 MOV     [ESI+00000159],DL 注释:EDX = 1

如果当前线程(ETHREAD->SavedApcState)保存 APC 状态与形参中(SavedApcState)要
输出的保存 APC 状态相等则将当前线程(ETHREAD->ApcState、ETHREAD->SavedApcState)
APC 状态与保存 APC 状态分别赋与当前线程的 ApcStatePointer[0] 与 ApcStatePointer
[1],(ApcStatePointer 是 KAPC_STATE 结构数组而 KAPC_STATE 又是由 LIST_ENTRY 链
表描述的)然后在设置当前线程(ETHREAD->ApcStateIndex)APC 状态索引为 1。

0008:8042C34A CMP     BYTE PTR [EDI+65],00 注释:要切换的进程(EPROCESS->State)状态
0008:8042C34E JNZ     8042C391

比较要切换的进程(EPROCESS->State)是否在内存中?如果不存在则直接跳转到设置当前
线程(ETHREAD->State)状态与切换线程处执行。

0008:8042C350 LEA     ESI,[EDI+40] 注释:ESI = 要切换进程(EPROCESS->ReadyListHead)的就绪链表
0008:8042C353 MOV     EAX,[ESI]
0008:8042C355 CMP     EAX,ESI
0008:8042C357 JZ     8042C374
0008:8042C359 MOV     EDX,[EAX]
0008:8042C35B LEA     ECX,[EAX-5C] 注释:ECX = 等待线程(ETHREAD)链
0008:8042C35E MOV     EAX,[EAX+04]
0008:8042C361 MOV     [EAX],EDX
0008:8042C363 MOV     [EDX+04],EAX
0008:8042C366 AND     BYTE PTR [ECX+0000011D],00 注释:线程链所指向的进程就绪队列(ETHREAD->ProcessReadyQueue)
0008:8042C36D CALL   8043150B   注释: 调用 KiReadyThread() 函数
0008:8042C372 JMP     8042C353    
0008:8042C374 MOV     EAX,[EBP+14]       注释:EAX = 形参 SavedApcState
0008:8042C377 PUSH     DWORD PTR [EAX+10]   注释:当前线程(ETHREAD->SavedApcState->EPROCESS)所在进程
0008:8042C37A PUSH     EDI             注释:要切换的进程
0008:8042C37B CALL   8040438C   注释:调用 KiSwapProcess() 函数
0008:8042C380 MOV     CL,[EBP+10]
0008:8042C383 CALL   80403FD0   注释:调用 KfLowerIrql() 函数


如果要切换的进程(EPROCESS)在内存中则遍历要切换进程(EPROCESS->ReadyListHead)
的就绪链,(ReadyListHead 是 LIST_ENTRY 结构),如果要切换进程的下一节点没有就
绪,则从线程等待链里得到一个线程(ETHREAD)并移除刚刚得到的要切换进程的节点,
设置得到的线程链所指向的进程就绪队列(ETHREAD->ProcessReadyQueue)为 FALSE,
然后调用 KiReadyThread() 来就绪得到的线程(ETHREAD),并得到要切换进程就绪链的
下一节点,重复上述步骤直到要切换进程的下一节点就绪,这时调用 KiSwapProcess()
函数来完成最终的切换工作,切换完成后从形参中得到初始 IRQL 值,调用 KfLowerIr
ql() 来降低当前 IRQL 为初始值。


0008:8042C388 POP     EDI
0008:8042C389 POP     ESI
0008:8042C38A POP     EBX
0008:8042C38B POP     EBP
0008:8042C38C RET     0010

函数结束,返回。

0008:8042C38F JMP     8042C388

跳转到函数结束。

0008:8042C391 LEA     EAX,[EDI+40] 注释:EAX = 要切换进程(EPROCESS->ReadyListHead)的就绪链表
0008:8042C394 MOV     [ESI+2D],DL 注释:[ESI+2D] = 当前线程(ETHREAD->State)状态
0008:8042C397 MOV     [ESI+0000011D],DL 注释:当前线程(ETHREAD->ProcessReadyQueue)链所指向的进程就绪队列
0008:8042C39D LEA     ECX,[ESI+5C] 注释:ECX = 当前线程(ETHREAD->WaitListEntry)等待链
0008:8042C3A0 MOV     EBX,[EAX+04]
0008:8042C3A3 MOV     [ECX],EAX
0008:8042C3A5 MOV     [ESI+60],EBX
0008:8042C3A8 MOV     [EBX],ECX
0008:8042C3AA MOV     [EAX+04],ECX
0008:8042C3AD CMP     [EDI+65],DL 注释:要切换的进程(EPROCESS->State)状态
0008:8042C3B0 JNZ     8042C3EE
0008:8042C3B2 MOV     BYTE PTR [EDI+65],02
0008:8042C3B6 MOV     ECX,[80482714]
0008:8042C3BC LEA     EAX,[EDI+48] 注释:EAX = 要切换进程(EPROCESS->SwapListEntry)的交换链
0008:8042C3BF MOV     [EDI+4C],ECX
0008:8042C3C2 MOV     DWORD PTR [EAX],80482710 注释:80482710 = 全局进程输入交换链(KiProcessInSwapListHead)
0008:8042C3C8 MOV     [ECX],EAX
0008:8042C3CA CMP     DWORD PTR [80482C98],80482C98 注释:80482C98 = 全局交换事件等待链(KiSwapEvent.Header.WaitListHead)
0008:8042C3D4 MOV     [80482714],EAX
0008:8042C3D9 MOV     [80482C94],EDX 注释:[80482C94] 全局交换事件状态(KiSwapEvent.Header.SignalState)
0008:8042C3DF JZ     8042C3EE
0008:8042C3E1 PUSH     0A
0008:8042C3E3 MOV     ECX,80482C90 注释:80482C90 = 全局交换事件(KiSwapEvent)
0008:8042C3E8 POP     EDX
0008:8042C3E9 CALL   80432BF1   注释:调用 KiWaitTest() 函数
0008:8042C3EE MOV     AL,[EBP+10]
0008:8042C3F1 MOV     [ESI+54],AL 注释:[ESI+54] = 当前线程(ETHREAD->WaitIrql)等待状态的 IRQL
0008:8042C3F4 CALL   80404080   注释:调用 KiSwapThread() 函数
0008:8042C3F9 JMP     8042C388  
0008:8042C3FB INT     3

如果要切换的进程(EPROCESS)不在内存中则将当前线程(ETHREAD->WaitListEntry)等
待链插入到要切换进程(EPROCESS->ReadyListHead)的就绪链表中,并设置当前线程
(ETHREAD->State)为就绪状态和当前线程(ETHREAD->ProcessReadyQueue)链所指向的
进程就绪队列为 TRUE。然后判断要切换的进程(EPROCESS->State)状态是否已经交换出
当前内存,如果为其他状态则直接跳转到 KiSwapThread()函数来切换当前线程。如果已
交换出当前内存状态则将要切换的进程(EPROCESS->State)状态设置为Transition 并将
要切换进程(EPROCESS->SwapListEntry)的交换链(SwapListEntry 是 LIST_ENTRY 结
构)插入到全局进程输入交换链 KiProcessInSwapListHead 中 (KiProcessInSwapList
Head 是 LIST_ENTRY 结构),继续设置全局交换事件状态(KiSwapEvent.Header.Signal
State),判断全局交换事件等待链头(KiSwapEvent.Header.WaitListHead)是否为空如
果不为空则需要调用 KiWaitTest() 函数来测试全局交换事件 (KiSwapEvent)余额。最
后调用 KiSwapThread() 来完成线程切换并跳转到函数结束处。


_______________________________________________________________________________________



下面是 KiMoveApcState() 函数实现细节


0008:8042C3FC MOV     EDX,[ESP+04]
0008:8042C400 MOV     EAX,[ESP+08]
0008:8042C404 PUSH     ESI
0008:8042C405 PUSH     EDI
0008:8042C406 PUSH     06
0008:8042C408 MOV     ESI,EDX   注释:EDX,ESI = 当前线程(ETHREAD->ApcState)
0008:8042C40A POP     ECX
0008:8042C40B MOV     EDI,EAX   注释:EAX,EDI = 当前线程(ETHREAD->SavedApcState)
0008:8042C40D REPZ MOVSD

当前线程的 ETHREAD->SavedApcState 与 ETHREAD->ApcState 都是 KAPC_STATE 结构,而
KAPC_STATE 结构又是一个双项链表 LIST_ENTRY 结构(LIST_ENTRY 结构在 DDK 头文件中
有定义)构成的数组 ApcListHead[2] ,数组织分别标识处了内核模式(ApcListHead[KernelMode]
)与用户模式(ApcListHead[UserMode])的 APC 链。以上代码是将 ETHREAD->ApcState 复制
到 ETHREAD->SavedApcState 中去。

0008:8042C40F MOV     ESI,[EDX]
0008:8042C411 CMP     ESI,EDX  

0008:8042C413 JNZ     8042C431  

判断当前线程(ETHREAD->ApcState[KernelMode])内核模式的 APC 链是否为空,如果不为
空的话则跳转到保存内核模式 APC 双项链 ApcState[KernelMode] 到 SavedApcState[KernelMode]
中。

0008:8042C415 MOV     [EAX+04],EAX
0008:8042C418 MOV     [EAX],EAX

如果当前线程(ETHREAD->ApcState[KernelMode])内核模式的 APC 链为空,则初始化当
前线程(ETHREAD->SavedApcState[KernelMode])的 SavedApcState 链表保存内核模式的
APC 状态。

0008:8042C41A MOV     ESI,[EDX+08]
0008:8042C41D LEA     ECX,[EDX+08]
0008:8042C420 CMP     ESI,ECX
0008:8042C422 JNZ     8042C440    

继续比较当前线程(ETHREAD->ApcState[UserMode])用户模式的 APC 链是否为空,如果
不为空的话则跳转到保存用户模式 APC 双项链 ApcState[UserMode] 到 SavedApcState
[UserMode]中。

0008:8042C424 LEA     ECX,[EAX+08]
0008:8042C427 MOV     [EAX+0C],ECX
0008:8042C42A MOV     [ECX],ECX

如果当前线程(ETHREAD->ApcState[UserMode])用户模式的 APC 链为空,则初始化当前
线程(ETHREAD->SavedApcState[UserMode])的 SavedApcState 链表保存用户模式的 APC 状态。

0008:8042C42C POP     EDI
0008:8042C42D POP     ESI
0008:8042C42E RET     0008

函数结束,返回。

0008:8042C431 MOV     ECX,[EDX+04]
0008:8042C434 MOV     [EAX],ESI
0008:8042C436 MOV     [EAX+04],ECX
0008:8042C439 MOV     [ESI+04],EAX
0008:8042C43C MOV     [ECX],EAX
0008:8042C43E JMP     8042C41A    

设置内核模式 APC 双项链 ApcState[KernelMode].FLink 与 ApcState[KernelMode].BLink
到 SavedApcState[KernelMode].FLink 和 SavedApcState[KernelMode].BLink 中。然后
跳转到继续比较当前线程(ETHREAD->ApcState[UserMode])用户模式的 APC 链是否为空
处。


0008:8042C440 MOV     EDX,[EDX+0C]
0008:8042C443 LEA     ECX,[EAX+08]
0008:8042C446 MOV     [EAX+0C],EDX
0008:8042C449 MOV     [ECX],ESI
0008:8042C44B MOV     [ESI+04],ECX
0008:8042C44E MOV     [EDX],ECX
0008:8042C450 JMP     8042C42C

设置用户模式 APC 双项链 ApcState[UserMode].FLink 与 ApcState[UserMode].BLink
到 SavedApcState[UserMode].FLink 和 SavedApcState[UserMode].BLink 中。然后跳转
到函数结束处。



_______________________________________________________________________________________



下面是 KiSwapProcess 函数实现细节

:u 8040438c l 100

0008:8040438C MOV     EDX,[ESP+04]
0008:80404390 TEST     WORD PTR [EDX+20],FFFF 注释: EPROCESS->LdtDescriptor
0008:80404396 JNZ     804043BC
0008:80404398 XOR     EAX,EAX
0008:8040439A LLDT     AX
0008:8040439D MOV     ECX,[FFDFF040] 注释: ECX = KPCR->KTSS
0008:804043A3 XOR     EAX,EAX
0008:804043A5 MOV     GS,AX
0008:804043A8 MOV     EAX,[EDX+18]   注释:EAX = EPROCESS->DirectoryTableBase
0008:804043AB MOV     [ECX+1C],EAX
0008:804043AE MOV     CR3,EAX
0008:804043B1 MOV     AX,[EDX+30]
0008:804043B5 MOV     [ECX+66],AX
0008:804043B9 RET     0008

得到要切换进程的 LDT 描述符(EPROCESS->LdtDescriptor)与页目录表(EPROCESS->
DirectoryTableBase),判断要切换的进程 LDT 不为空,则跳转到设置 INT 21 处继续
运行,否则用要切换进程的页目录来刷新 KTSS 中的 CR3 值与当前 CR3 值,并用要切
换进程的 IOPM 来填充 KTSS 中的 IOPM。完成切换,函数返回。


0008:804043BC MOV     ECX,[FFDFF03C] 注释:ECX = KPCR->KGDT
0008:804043C2 MOV     EAX,[EDX+20]
0008:804043C5 MOV     [ECX+48],EAX
0008:804043C8 MOV     EAX,[EDX+24]
0008:804043CB MOV     [ECX+4C],EAX
0008:804043CE MOV     ECX,[FFDFF038] 注释:ECX = KPCR->KIDT
0008:804043D4 MOV     EAX,[EDX+28]
0008:804043D7 MOV     [ECX+00000108],EAX
0008:804043DD MOV     EAX,[EDX+2C]
0008:804043E0 MOV     [ECX+0000010C],EAX
0008:804043E6 MOV     EAX,00000048
0008:804043EB JMP     8040439A

从 KPCR 中取得 KGDT 的位置,并从 KGDT 中索引到 LDT,把当前进程(EPROCESS)中的
LDT 赋与 KPCR 中的 LDT。再得到 KPCR 中 KIDT 的位置,把当前进程(EPROCESS)中的
INT 21 中断赋与 KPCR 中 KIDT 中的相应位置,并跳转到设置 LDT 处使当前进程可以使
用 INT 21 。




笔记是今年春节利用放假时间写的,当时分析到一半才发现原来 WIN2K 源代码中已
经包含了此部分,无奈已经把汇编进行了简单的注释,索性就这样写下去。错误之处再
所难免,还望得到您的指正。


参考资源: Windows 2000 源代码
感谢 FlashSky,SoBeIt 与我探讨。
Windows NT 2000内核编程:Native API参考大全 Windows NT和2000操作系统提供了一套丰富的Native API,为系统编程和应用开发提供了强大的支持。这些API允许开发者直接与操作系统的底层进行通信,执行各种高级任务,如进程管理、内存分配、文件系统操作、网络通信、设备驱动开发等。本章将概述Native API的基本概念,介绍其在Windows系统中的核心作用,并为后续章节中详细介绍各个API的应用和优化打下基础。通过本章,读者可以了解到系统API的起源、架构和它们在现代操作系统中的重要性,从而为深入学习各个高级主题打下坚实的基础。 阅读详情

相关推荐

过hs等保护

可完美下断,驱动开发方面值得学习的不错的资料

过TP保护与解除游戏驱动保护

过TP保护与解除游戏驱动保护

whl0071的专栏 2181

【SOC估计】基于EKF与二阶RC等效电路模型的锂电池SOC在线估计算法实现与仿真分析

本文详细介绍了基于扩展卡尔曼滤波(EKF)与二阶RC等效电路模型的锂电池SOC在线估计算法。通过Matlab仿真分析,展示了该算法如何实现高精度SOC估计,相比传统方法显著提升估计精度与鲁棒性,适用于电动汽车、储能系统等关键领域。

1105

Detect-KeAttachProcess - 实时检测内核模块注入攻击

Detect-KeAttachProcess - 实时检测内核模块注入攻击 Detect-KeAttachProcess 是一个开源的 Windows 防火墙驱动程序,用于实时检测和阻止通过 KeAttachProcess API 发起的内核模块注入攻击。 什么是 Detect-KeAttachProcess? Detect-KeAttachProcess 是一款轻量级的防火墙驱动程序,它监控系统...

gitblog_00001的博客 557

Viobot使用教程总贴

使用教程总贴

weixin_66666206的博客 1411

Unity3D Pico VR 手势交互实战:基于MRTK3实现抓握与射线控制

本文针对Pico VR原生手势交互功能不足的问题,提供了基于MRTK3实现高级手势交互的实战解决方案。通过将MRTK3成熟的交互框架与Pico的手势追踪能力相结合,详细讲解了从环境配置、SDK导入、核心适配到真机调试的全流程,帮助开发者高效实现抓握与射线控制等复杂交互,有效提升Pico VR应用的开发效率与体验。

weixin_29011667的博客 391

[Windows 驱动开发] 清内存强杀进程

原理 Attach 到进程进行内存清零.这里提供了两种方法.原理是一样 KeAttachProcess方法 与 KeStackAttachProcess方法. 其中第一种属于旧方法了.根据MSDN所说API已经升级为了KeStackAttachProcess #include <ntifs.h> NTKERNELAPI UCHAR* PsGetProcessImageFileName(IN PEPROCESS Process); //未公开的进行导出即可 NTKERNELAPI VOID

LYSM 1727

Unity如何实现3D物体拆解组装

最近有一个需求,是做一个发动机的拆卸和安装功能,其实是一个很简单的功能,但是其中有一个点我觉的非常有意思,就是拖拽组装时,物体如何精准拖到目标位置,思路有了,但是我一直找不到实现方式,早晨刷牙时无意间想到了叉乘,我才有了解决方案。就凭这一次的灵光乍现,也很有必要写篇博客来记录一下。首先,我们先来看下我们要实现的效果,就是对一个发动机(任意多组件物体,如:车)的360°预览查看,手动拖拽拆解,手动拖拽组装。360°旋转视角远近拉伸视角点击拖拽拆卸拖拽组装。

TxNet 1万+

APC的内部实现

APC的内部实现 今天心情如同死去的病人的心电图一般,直线一条。所以没啥好说的。 天津泰达拿了亚军,还是挺给力的,下赛季去球场看亚冠,多少年没去现场看球了,上次去还是泰达球场的落成仪式,泰达2:1墨尔本,虽然中国足球没什么好看的,但是球场是一个比较便宜

求索 3493

2024年11月架构设计师案例分析真题回顾,附参考答案、解析及所涉知识点(一)

软考高级系统架构设计师考试包含三个科目:信息系统综合知识、案例分析和系统架构设计论文。考试形式为机考。本文主要回顾2024年下半年(2024-11-10)系统架构设计师考试上午第二科案例分析的题目,同时附带参考解答、解析和所涉知识点。

一几文的编程学习笔记。 2420

QT 实现QStackedWidget切换页面右移动画

首先截取当前界面的图片,渲染到一个QLabel上,然后设置QPropertyAnimation动画,动画的作用对象就是这个QLabel,不断的改变geometry;当动画结束的时候,隐藏并释放这个QLabel。以下是一个QStackedWidget,放了两个QPushButton在上面,点击切换不同的界面。为了方便查看动画特效,设置了每个界面的背景图片。

希望能帮助大家,希望大家多多支持,你们的支持是我前进的动力。 883

读《毛德操:关于进程挂靠》

WINDOWS 可以创建远程线程,但是linux却不可以,为何?windows是基于对象的,是异步的,在很多情况下都没有进程上下文,试想一下,windows实际 上就像一个超大容器,里面有形形色色各种对象,进程是对象,线程是对象,中断也是对象,文件是对象...,内核管理着这些对象,通过管理它们为用户提供服 务,而如果对象不交互就不需要管理,系统也就没有存在的意义了,实际上系统基于对象实现就是为了提

TCP/IP 5660

【AI大模型应用开发】【LlamaIndex教程】2. 存储模块:如何在 LlamaIndex 中使用自定义的向量数据库?(附代码)

前面文章两行代码就实现了文档的切分和向量化存储以及持久化存储。如果我们想用自定义的向量化数据库呢?

同学小张的博客 2119

NT 内核的进程调度分析笔记

文章作者:sinister信息来源:白细胞Author: sinisterEmail:   sinister@whitecell.orgHomepage:http://www.whitecell.org Date:   2005-11-162005-2-15众所周知 nt kernel 是多任务抢占试方式运行的,在非 SMP 系统上,每个进程分配特定的CPU 时间片来达到执行目的,这样看上去就象多

404

Flutter SDK的删除与重装:一步步教你如何轻松搞定

在Flutter开发过程中,我们可能会遇到一些需要重新安装Flutter SDK的情况,例如升级Flutter版本、解决依赖冲突等。本文将详细介绍如何删除并重新安装Flutter SDK,让你轻松应对各种问题。

理论都是虚的,代码才是王道。 2247

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Windows NT 内核基本结构

Windows NT 内核基本结构和特征 尽管大部分人每天在Windows系统上工作学习娱乐,但是对于其内核结构很多人仍然是不了解的。一方面是由于其内核源代码不开源,另一方面则是由于相关资料的奇缺。我大二基本上花了一学期来学习和探究NT内核。虽然不敢说自己对其有很深入的了解,但至少其基本结构还算是清楚的。但是我毕竟才学有限,肯定会有不完善的地方,还请...

weixin_33754065的博客 1830

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

Windows NT 与 Linux 内核对比:从 3 大核心子系统看进程与内存管理差异

正如Linux内核维护者Greg Kroah-Hartman所说:"内核不是魔法,它只是一段精心编写的代码——但正是这段代码决定了整个数字世界的运行方式。微软首席架构师David Cutler的设计初衷是:"我们需要一个既能保证商业可靠性,又能适应多种硬件平台的现代内核"。Torvalds曾直言:"微内核只是理论家的玩具,真正的系统需要的是效率"。Windows NT内核(以下简称NT内核)与Linux内核代表了商业闭源与开源社区两种技术路线的巅峰之作,它们的差异从最基础的架构设计就已注定。

weixin_30363981的博客 408

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

Windows Research Kernel (WRK) 调试技巧:如何使用WinDbg分析NT内核源代码的完整指南

Windows Research Kernel (WRK) 是微软官方发布的Windows NT内核源代码研究项目,为操作系统研究人员和内核开发者提供了深入了解Windows内核架构的绝佳机会。本文将为您详细介绍如何利用WinDbg调试工具来分析和调试WRK内核源代码,帮助您快速掌握Windows内核调试的核心技巧。💡 ## 为什么选择Windows Research Kernel进行内核研究

gitblog_00009的博客 551

NT操作系统的Rootkit技术初探

1 NT操作系统的Rootkit技术初探   目前偶对Win

goooooooooo的专栏 827

使用动态优先权的进程调度算法的模拟_操作系统功能进程管理

进程•进程是正在执行的程序。•由可执行程序、其数据和堆栈、其程序计数器(PC)、堆栈计数器(SP)和运行所需的其他信息。•进程控制是多任务O/S的重要组成部分–这包括分配内存、cpu时间、I/O设备等•现代O/S将进程进一步分解为线程,是一个进程中较小的单独可执行部分。这使得CPU使用更有效。•有关进程的信息存储在进程表中(O/S数据结构)过程状态•一个过程可以是三种主要“状态”之一:就...

weixin_39854951的博客 291

[转]Windows NT 内核基本结构

转自:http://spinlock.blog.51cto.com/607469/174696作者:kevx尽管大部分人每天在Windows系统上工作学习娱乐,但是对于其内核结构很多人仍然是不了解的。一方面是由于其内核源代码不开源,另一方面则是由于相关资料的奇缺。我大二基本上花了一学期来学习和探究NT内核。虽然不敢说自己对其有很深入的了解,但至少其基本结构还算是清楚的。但是我毕竟才学有限,肯定会...

weixin_30275415的博客 714

第三章 进程

第三章 进程

weixin_53254469的博客 2451

【荐】NT内核基本结构和特征

 Windows NT内核基本结构和特征      尽管大部分人每天在Windows系统上工作学习娱乐,但是对于其内核结构很多人仍然是不了解的。一方面是由于其内核源代码不开源,另一方面则是由于相关资料的奇缺。我大二基本上花了一学期来学习和探究NT内核。虽然不敢说自己对其有很深入的了解,但至少其基本结构还算是清楚的。但是我毕竟才学有限,肯定会有不完善的地方,还请各位不吝赐教。目前微软所有的主

613

Windows NT 内核基本结构和特征

Windows NT 内核基本结构和特征 尽管大部分人每天在Windows系统上工作学习娱乐,但是对于其内核结构很多人仍然是不了解的。一方面是由于其内核源代码不开源,另一方面则是由于相关资料的奇缺。我大二基本上花了一学期来学习和探究NT内核。虽然不敢说自己对其有很深入的了解,但至少其基本结构还算是清楚的。但是我毕竟才学有限,肯定会有不完善的地方,还请各位不吝赐教。 目前微软所有的主流操作系

autumn20080101的专栏 1862

进程与线程的关系

进程是系统分配资源的单位,每一个进程对应与一个活动的程序,当进程激活时,操作系统就将系统的资源包括内存、I/O和CPU等分配给它,使它执行。 线程是CPU分配时间的单位,每一个线程对应于它在进程中的一个函数,也就是内存中的代码段,多个线程执行时CPU会根据它们的优先级分配时间,使它们完成自己的功能。 一般来说,进程中至少一个线程,一个主线程和其他线程组成一个

西昆仑 2464

如何: 在Windows2000中动态禁用/启用Ctrl-Alt-Delete

概要此文章的信息来自优快云论坛VC/MFC版的讨论 在NT/2000中怎么禁用Ctrl+Alt+Delete?(不能用gina,键盘驱动) 在Windows2000中Ctrl-Alt-Delete组合键的处理如下: Winlogon初始化的时候,在系统中注册了CTRL+ALT+DEL Secure Attention Sequence(SAS)热键,并且在WinSta0

aasa2的专栏 1088
上一篇: Delphi下构建无导入表程序-使用:hash获取API,k32Base等
下一篇: Process Initiation
iiprogram
博客等级 码龄25年 1068粉丝 1515原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值