阅读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中存储的描述符表基址找到相应的描述符:
找到索引为6的描述符:
82409393`6c003748
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