深入理解PsGetCurrentProcess获取当前进程的过程

阅读WRK 源代码时,我们会频繁地看到许多有关当前线程或进程的基本操作。内核层函数KeGetCurrentThread 是一个重要的函数,它返回当前处理器上正在运行的线程的KTHREAD 结构指针。通过此结构信息,可以进一步得到KPROCESS、ETHREAD 和EPROCESS 结构。在WRK 中,KeGetCurrentThread 是这样实现的:

FORCEINLINE  

struct _KTHREAD *  

NTAPI KeGetCurrentThread (VOID)  

{  

#if (_MSC_FULL_VER >= 13012035)  

return (struct _KTHREAD *) (ULONG_PTR) __readfsdword (FIELD_OFFSET  

(KPCR, PrcbData.CurrentThread));  

#else  

__asm { mov eax, fs:[0] KPCR.PrcbData.CurrentThread }  

#endif  

}

WRK 本身带的编译器(tools\x86\cl.exe)版本(13.10.4035)大于13012035,所以,上面的代码取第一个条件分支,它展开来实际上只是一条指令。在Intel x86 处理器的Windows 系统中,fs 寄存器指向一块被称为处理器控制区(PCR,Processor Control Region)的内存,其数据类型为KPCR。KPCR 有一个类型为KPRCB 的数据成员PrcbData,这是当前处理器的控制块(Processor Control Block),其中包含了指向当前线程的KTHREAD结构的指针。因此,上面的KeGetCurrentThread 代码实际上是一条访问fs 寄存器加上特定偏移的指令。

获得了当前线程的KTHREAD 结构指针以后,便可以很方便地获得ETHREAD 结构的指针,以及进程KPROCESS 或EPROCESS 结构的指针。在执行体层上获得当前线程和进程的函数分别是PsGetCurrentThread 和PsGetCurrentProcess,代码如下:

#define _PsGetCurrentProcess() (CONTAINING_RECORD \  

(((KeGetCurrentThread())->ApcState.Process),EPROCESS,Pcb))  

#define _PsGetCurrentThread() ((PETHREAD)KeGetCurrentThread())  

PEPROCESS  

PsGetCurrentProcess(  

VOID  

)  

{  

return _PsGetCurrentProcess();  

}  

PETHREAD  

PsGetCurrentThread(  

VOID  

)  

{  

return _PsGetCurrentThread();  

}

我们看到,_PsGetCurrentProcess 宏从当前线程KTHREAD 结构的ApcState 成员中获得当前线程所属进程的KPROCESS 结构。这里之所以从ApcState 成员中获得进程结构指针,而不是从KTHREAD 的Process 域或ETHREAD 的ThreadsProcess 域获取进程结构指针,是因为即使当前线程附载到其他的进程中(通过KeAttachProcess 函数),或者又回到原先的进程中(通过KeDetachProcess 函数),这种做法也总是能够获得正确的当前进程结构的指针。

对PsGetCurrentProcess和PsGetCurrentThread进行反汇编:

kd> uf PsGetCurrentProcess
nt!PsGetCurrentProcess:
8289723c 64a124010000    mov     eax,dword ptr fs:[00000124h]
82897242 8b4050          mov     eax,dword ptr [eax+50h]
82897245 c3              ret
kd> uf PsGetCurrentThread
nt!PsGetCurrentThread:
828493f1 64a124010000    mov     eax,dword ptr fs:[00000124h]
828493f7 c3              ret

从上面可以看出,fs:[00000124h] 这里存放的是当前线程的KTHREAD,有了这个结构那么当前运行的进程,线程信息就都可以取到了。

那么FS作为段寄存器指向的是什么呢,我取了内核态和用户态FS的值,在内核态FS=0x30, 在用户态FS=0x3B。实际上如果你去看wrk中SwapContext 和 KiSystemServices的代码就会发现,FS 只会是前面提到的两个固定值。

下面我们找到FS段寄存器指向的段基地址,在此之前希望大家先看看这篇:GDT(全局描述符表)和LDT(局部描述符表)

kd> .formats 0x30
Evaluate expression:
  Hex:     00000030
  Decimal: 48
  Octal:   00000000060
  Binary:  00000000 00000000 00000000 00110000
  Chars:   …0
  Time:    Thu Jan 01 08:00:48 1970
  Float:   low 6.72623e-044 high 0
  Double:  2.37152e-322

TI为0 ,选择子是在GDT选择。

index为6,GDT的表项编号。

根据在GDTR中存储的描述符表基址找到相应的描述符:

image

找到索引为6的描述符:

82409393`6c003748

名称:  222.jpg
查看次数: 129
文件大小:  44.3 KB

kd> .formats 82409393
  Binary:  10000010 01000000 10010011 10010011
kd> .formats 6c003748
  Binary:  01101100 00000000 00110111 01001000

所以得到段基地址为:

10000010 10010011 01101100 00000000

转换成十六进制:0x82936c00。这是关于运行态最重要的信息PCR的地址。

kd> dt _kpcr 0x82936c00
ntdll!_KPCR

+0x120 PrcbData         : _KPRCB

kd> dt _KPRCB 0x82936c00+0x120
ntdll!_KPRCB
   +0x000 MinorVersion     : 1
   +0x002 MajorVersion     : 1
   +0x004 CurrentThread    : 0x84b50388 _KTHREAD

这下应该明白了:

kd> uf PsGetCurrentThread
nt!PsGetCurrentThread:
828493f1 64a124010000    mov     eax,dword ptr fs:[00000124h]
828493f7 c3              ret

这里的偏移为什么是00000124h。

kd> .thread
Implicit thread is now 84b50388

当前线程就是这样得到的。

kd> dt _kthread 84b50388
ntdll!_KTHREAD

   +0x040 ApcState         : _KAPC_STATE

kd> dt _KAPC_STATE 84b50388+0x40
ntdll!_KAPC_STATE
   +0x000 ApcListHead      : [2] _LIST_ENTRY [ 0x84b503c8 – 0x84b503c8 ]
   +0x010 Process          : 0x85da3030 _KPROCESS
   +0x014 KernelApcInProgress : 0 ”
   +0x015 KernelApcPending : 0 ”
   +0x016 UserApcPending   : 0 ”

这下应该明白了:

kd> uf PsGetCurrentProcess
nt!PsGetCurrentProcess:
8289723c 64a124010000    mov     eax,dword ptr fs:[00000124h]
82897242 8b4050          mov     eax,dword ptr [eax+50h]
82897245 c3              ret

第二句偏移为什么是0x50h。

kd> .process
Implicit process is now 85da3030

当前进程就是这样得到的。

总结一下PsGetCurrentProcess的整个流程:

KPCR->PrcbData->CurrentThread->ApcState->Process

本文链接:http://www.alonemonkey.com/psgetcurrentprocess-flow-path.html