首页 | 安全文章 | 安全工具 | Exploits | 本站原创 | 关于我们 | 网站地图 | 安全论坛
  当前位置:主页>安全文章>文章资料>网络安全>文章内容
NT下动态切换进程分析笔记
来源:http://www.whitecell.org 作者:sinister 发布时间:2005-11-28  

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

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 与我探讨。



WSS(Whitecell Security Systems),一个非营利性民间技术组织,致力于各种系统安全技术的研究。坚持传统的hacker精神,追求技术的精纯。
WSS 主页:http://www.whitecell.org/
WSS 论坛:http://www.whitecell.org/forums/



 
[推荐] [评论(0条)] [返回顶部] [打印本页] [关闭窗口]  
匿名评论
评论内容:(不能超过250字,需审核后才会公布,请自觉遵守互联网相关政策法规。
 §最新评论:
  热点文章
·一句话木马
·samcrypt.lib简介
·教你轻松查看QQ空间加密后的好友
·web sniffer 在线嗅探/online ht
·SPIKE与Peach Fuzzer相关知识
·asp,php,aspx一句话集合
·Cisco PIX525 配置备忘
·用Iptables+Fedora做ADSL 路由器
·检查 Web 应用安全的几款开源免
·Md5(base64)加密与解密实战
·风险评估中的渗透测试
·QQ2009正式版SP4文本信息和文件
  相关文章
·NT内核的进程调度分析笔记
·基于DoS攻击的随机数据包标记源
·kernel inline hook 绕过vice检
·AIX内核的虚拟文件系统框架
·使用omniORBpy开发简单CORBA程序
·ASP安全配置不完全手册
·谁更安全?黑客眼中的 防火墙与
·从漏洞及攻击分析到NIDS规则设计
·安全成交换机的基本功能
·PHP漏洞中的战争
·简评中小企业网络安全市场
·从后台得到webshell技巧大汇总
  推荐广告
CopyRight © 2002-2022 VFocuS.Net All Rights Reserved