KPCR

转载 2015年11月08日 11:05:12

由于Windows需要支持多个CPU, 因此Windows内核中为此定义了一套以处理器控制区(Processor Control Region)即KPCR为枢纽的数据结构, 使每个CPU都有个KPCR. 其中KPCR这个结构中有一个域KPRCB(Kernel Processor Control Block)结构, 这个结构扩展了KPCR. 这两个结构用来保存与线程切换相关的全局信息. 

通常fs段寄存器在内核模式下指向KPCR, 用户模式下指向TEB.

KPCR结构如下:


其中比较重要的是KdVersionBlock这个指针, 它指向一个DBGKD_GET_VERSION64这个结构.

这个结构体里面包含了一些重要信息。如:PsLoadedModuleList ,它是Windows加载的所有内核模块构成的链表的表头。



由此可以看到,两个处理器对应的KPCR结构是有区别的,只有第一个处理器的KPCR域KdVersionBlock才指向DBGKD_GET_VERSION64这个结构。


下面我们通过具体的代码举个例子,通过KPCR枚举到Ntoskrnl的基地址:



  1. /* 
  2. * AUTHOR      : 莫灰灰 
  3. * BLOG        : http://blog.csdn.net/hu3167343 
  4. * DESCRIBE    : KPCR->KdVersionBlock->PsLoadedModuleList->ntoskrnl base address  
  5. */  
  6.   
  7. #include "ntddk.h"  
  8.   
  9. NTKERNELAPI VOID KeSetSystemAffinityThread ( KAFFINITY Affinity );  
  10. NTKERNELAPI VOID KeRevertToUserAffinityThread ( VOID );  
  11.   
  12. VOID  
  13. DriverUnload(IN PDRIVER_OBJECT pDriverObj)  
  14. {      
  15.     KdPrint(("Unloaded Success\r\n"));  
  16.     return;  
  17. }  
  18.   
  19. NTSTATUS  
  20. DriverEntry(IN PDRIVER_OBJECT pDriverObj, IN PUNICODE_STRING pRegistryString)  
  21. {  
  22.     NTSTATUS        status = STATUS_SUCCESS;  
  23.     ULONG FSAddr;  
  24.   
  25.     pDriverObj->DriverUnload = DriverUnload;  
  26.   
  27.     KeSetSystemAffinityThread(1); //使当前线程运行在第一个处理器上  
  28.     __asm{  
  29.             push eax  
  30.             mov    eax, fs:[0x34]    ;得到KdVersionBlock的地址  
  31.             add  eax,18h        ;得到指向PsLoadedModuleList的地址   
  32.             mov  eax,[eax]        ;得到PsLoadedModuleList的地址   
  33.             mov  eax,[eax]        ;取出PsLoadedModuleList里面的内容, 即KLDR_DATA_TABLE_ENTRY结构  
  34.             mov  eax,[eax+18h]    ;取出DllBase, 即ntoskrnl.exe的基地址  
  35.             mov FSAddr, eax  
  36.             pop eax  
  37.     }  
  38.     KeRevertToUserAffinityThread();//恢复线程运行的处理器  
  39.   
  40.     KdPrint(("0x%08X\n", FSAddr));  
  41.     return STATUS_SUCCESS;  


在和进程、线程相关的数据结构中,进程控制块和线程控制块是关键,通过这两个数据结构可以按图索骥找到其余的有关数据结构。Windows提供了一系列的获取进程控制块或者线程控制块的函数,下边将对这些函数实现进行剖析。

首先Windows内核提供了一个函数KeGetCurrentThread函数,用来或者当前线程的KTHREAD结构指针,这个函数的实现方式将在后文中介绍,windows其他获取控制块的函数都是以此函数为基础进行封装的。


下面将就几者之间的关系进行介绍。

1.KeGetCurrentThread – - > PsGetCurrentThread

后者只是前者定义的一个宏。并加上了强制类型转换,因为ETHREAD的一个成员是KTHREAD结构,所以两者的首地址相同。

#define PsGetCurrentThread() ((PETHREAD)KeGetCurrentThread())

2.PsGetCurrentThread – - >IoGetCurrentProcess

IoGetCurrentProcess是以PsGetCurrentThread为基础,封装的函数。

PEPROCESS STDCALL
IoGetCurrentProcess(VOID)
{
  if(PsGetCurrentThread() == NULL ||
    PsGetCurrentThread()->Tcb.ApcState.Process = NULL)
  {
    return (psInitialSystemProcess);
  }
  else
  {
    return (PEPROCESS)(PsGetCurrentThread()->Tcb.ApcState.Process);
  }
}

就这个函数,书中有这样一段描述:

只要当前线程不是内核线程,其KTHREAD结构中成分ApcState内部的指针Process就指向当前进程的EPROCESS结构,KTHREAD结构中本来就有个指针也叫Process(这个指针我没有观察到。。),这诗歌KPROCESS指针,那为什么要用ApcState内部的指针Process呢?这是因为考虑到进程“挂靠”。后面读者将会看到,一个进程可能会“挂靠”到另一个进程上,以访问所挂靠进程的用户空间。这样,在“常态”下IoGetCurrentProcess所返回的是当前线程所属进程的EPROCESS结构指针,而在挂靠状态下则所返回的是当前线程所挂靠进程的EPROCESS结构指针。

3. IoGetCurrentProcess – - > PsGetCurrentProcess

后者是前者定义的一个宏。

#defind PsGetCurrentProcess IoGetCurrentProcess

4.PsGetCurrentProcess – - > KeGetCurrentProcess

KeGetCurrentProcess是以PsGetCurrentProcess为基础封装的函数,返回值为KPROCESS指针。

PKROCESS STDCALL
KeGetCurrentProcess(VOID)
{
  return (&(PsGetCurrentProcess()->Pcb));
}

由以上可以看出,无论是*GetCurrentThread还是*GetCurrentProcess都是获取ETHREAD、KTHREAD或者EPROCESS、KPROCESS结构。而KTHREAD(KPROCESS)有是ETHREAD(EPROCESS)结构中的第一个元素,所以两者的地址是一样的。所以从Thread到Process的过度关键是IoGetCurrentProcess函数,其核心也是通过ETHREAD结构中成员变量得到其所对应(或者挂靠)的进程。

通过分析IoGetCurrentProcess函数可以看到,ETHREAD.Tcb.ApcState.Process指向了该线程对应(或者挂靠)的进程。其实ETHREAD.Tcb指向的就是该线程的KTHREAD结构。

那么以上函数引用的实质,就是几个数据结构之间的指向关系。

下边将对如下的程序,对这个过程使用WinDbg进行验证。

//int-3.cpp
int main()
{
  __asm
  {
    int 3;
  }
  return 0;
}

1.首先查看当前进程信息。

从图上我们可以看到当前进程的EPROCESS(KPROCESS) 地址为0x81DFA088。其中一个线程的ETHREAD(KTHREAD)地址为0x81DFA4E0。

上边提到了, ETHREAD.Tcb.ApcState.Process其实就是KTHREAD.ApcState.Process。为了节省一个图,验证时使用后者。

2.查看KTHREAD中的ApcState成员的数据类型和地址。

有上图可以看到,KTHREAD中的ApcState成员,在偏移为0×034处,数据类型为_KAPC_STATE。

3.查看ApcState中的成员Process的值。


从上图可以看到,Process的值为0x81DFA088,和第一步中查看到的KPROCESS的地址是一样的,至此验证完成。

下边将介绍内核如何获得当前运行线程的KTHREAD结构的。

Windows内核中,为每个CPU分配了一套“处理器控制区(Processor Control Region)”,简称KPCR结构。该结构中存放着与线程切换相关的全局信息,包括所需的寄存器和其他的数据结构。KPCR数据结构的定义如下:

typedef struct _KPCR {
  KPCR_TIB	Tib;					/* 00 */
  struct _KPCR	*Self;				/* 1C */
  struct _KPRCB	*Prcb;				/* 20 */
  KIRQL	Irql;						/* 24 */
  ULONG	IRR;						/* 28 */
  ULONG	IrrActive;					/* 2C */
  ULONG	IDR;						/* 30 */
  PVOID	KdVersionBlock;				/* 34 */
  PUSHORT	IDT;						/* 38 */
  PUSHORT	GDT;						/* 3C */
  struct _KTSS	*TSS;				/* 40 */
  USHORT	MajorVersion;				/* 44 */
  USHORT	MinorVersion;				/* 46 */
  KAFFINITY	SetMember;				/* 48 */
  ULONG	StallScaleFactor;			/* 4C */
  UCHAR	DebugActive;				/* 50 */
  UCHAR	ProcessorNumber;			/* 51 */
  UCHAR	Reserved;					/* 52 */
  UCHAR	L2CacheAssociativity;		/* 53 */
  ULONG	VdmAlert;					/* 54 */
  ULONG	KernelReserved[14];			/* 58 */
  ULONG	L2CacheSize;				/* 90 */
  ULONG	HalReserved[16];			/* 94 */
  ULONG	InterruptMode;				/* D4 */
  UCHAR	KernelReserved2[0x48];		/* D8 */
  KPRCB	PrcbData;					/* 120 */
} KPCR, *PKPCR;

其中的第三个变量是指向一个“处理器控制块”,即KPRCB数据结构,这个数据结构内容很多,下边只列举一部分。

/* ProcessoR Control Block */
typedef struct _KPRCB {
  USHORT MinorVersion;
  USHORT MajorVersion;
  struct _KTHREAD *CurrentThread;
  struct _KTHREAD *NextThread;
  struct _KTHREAD *IdleThread;
  . . . . . .
  UCHAR CpuType;
  UCHAR CpuID;
  USHORT CpuStep;
  KPROCESSOR_STATE ProcessorState;
  . . . . . .
  PVOID LockQueue[33];	// Used for Queued Spinlocks
  struct _KTHREAD *NpxThread;
  ULONG InterruptCount;
  ULONG KernelTime;
  ULONG UserTime;
  . . . . . .
  struct _KEVENT *DpcEvent;
  UCHAR ThreadDpcEnable;
  BOOLEAN QuantumEnd;
  . . . . . .
  LONG MmPageReadCount;
  LONG MmPageReadIoCount;
  LONG MmCacheReadCount;
  LONG MmCacheIoCount;
  LONG MmDirtyPagesWriteCount;
  . . . . . .
  FX_SAVE_AREA NpxSaveArea;
  PROCESSOR_POWER_STATE PowerState;
} KPRCB, *PKPRCB;

这个结构中的第三个成员变量是KTHREAD类型的CurrentThread变量,该变量指向了当前线程的KTHREAD数据结构。上文提到的KeGetCurrentThread函数就是从此处获得的当前线程的KTHREAD结构指针。

KeGetCurrentThread函数的实现过程,书中有这样的描述(还没有动手实践):

当CPU运行于内核中时,段寄存器FS(所选择的段描述块)总是指向该CPU的KPCR结构的起点,而KPCR中位移为0x1c的字段为Self,指向其所在的KPCR结构的起点。找到CPU的KPCR结构,就可以找到其KPCRB结构,并进而找到其CurrentThread了。

下面依然接着上边的程序进行调试验证。

1.查看当前系统中KPCR的个数。

2.查看系统中的KPRCB结果的位置。

上边得到的是KPRCB结构的地址是0xffdff120,那么KPCR结构的地址是0xffdff120 – 0×120,这种方法是在参考文献[1]中得到的。是否可以直接获得KPCR的地址,我还不知道。

3.从KPRCB结构中查看CurrentThread的值。

从上图中看到了CurrentThread的值是0x81dfa4e0,与本文中的第一张Windbg调试图对比,可以看到的得到的结果是一致的!



PsActiveProcessHead的定义:

在windows系统中,所有的活动进程都是连在一起的,构成一个双链表,表头是全局变量PsActiveProcessHead,当一个进程被创建时,其ActiveProcessList域将被作为节点加入到此链表中;当进程被删除时,则从此链表中移除,如果windows需要枚举所有的进程,直接操纵此链表即可。



方法一:从KdInitSystem函数地址处硬编码搜索
方法二:从System进程(pid=4)的PEPROCESS地址获取
方法三:从ntoskrnl.exe的导出变量PsInitialSystemProcess中获取
方法四:从KPCR中获取
方法五:调用NtSystemDebugControl函数获取

注:操作系统 Windows XP SP3

方法一:
系统内核变量KdDebuggerDataBlock是一个KDDEBUGGER_DATA64类型的结构体,结构成员PsActiveProcessHead正是我们所找的地址。可在WinDDK的\inc\api\WDBGEXTS.H文件中查看到此结构的定义。

KdInitSystem函数中引用了KdDebuggerDataBlock,而ntoskrnl.exe的导出函数KdEnableDebugger调用了KdInitSystem函数。
名称:  方法1找KdInitSystem.jpg查看次数: 61文件大小:  134.7 KB
名称:  方法1找到KdDebuggerDataBlock.jpg查看次数: 61文件大小:  85.3 KB

代码:
ULONG FindPsActiveProcessHead1()
{
  //1.从KdEnableDebugger地址找到KdInitSystem地址
  //nt!KdEnableDebugger   804f7810
  
  //804f7837 6a00            push    0
  //804f7839 6a00            push    0
  //804f783b c605ecab558001  mov     byte ptr [nt!PoHiberInProgress (8055abec)],1
  //804f7842 e8f7951600      call    nt!KdInitSystem (80660e3e)
  //804f7847 e8649a1600      call    nt!KdpRestoreAllBreakpoints (806612b0)

  ULONG i;
  PCALL_CODE pCall;
  PUCHAR pKdInitSystem=NULL;
  PUCHAR p=(PUCHAR)GetExportFuncAddress(L"KdEnableDebugger");
  KdPrint(("KdEnableDebugger地址=%x\n",p));
  if (!p)
  {
    KdPrint(("获取KdEnableDebugger地址失败\n"));
    return 0;
  }

  for (i=0;i<100;i++,p++)
  {
    if ((*p==0x6a)&&
      (*(p+1)==0x00)&&
      (*(p+2)==0x6a)&&
      (*(p+3)==0x00)&&
      (*(p+4)==0xc6)&&
      (*(p+5)==0x05)&&
      (*(p+0xb)==0xe8)&&
      (*(p+0x10)==0xe8)  )
    {
      pCall=(PCALL_CODE)(p+0xb);
      pKdInitSystem=p+0xb+pCall->address+5;
      KdPrint(("KdInitSystem地址=%x\n",pKdInitSystem));
      break;
    }
  }
  
  if (!pKdInitSystem)
  {
    KdPrint(("获取KdInitSystem地址失败\n"));
    return 0;
  }

  //2.从KdInitSystem地址找到KdDebuggerDataBlock地址
  //nt!KdInitSystem 80660e3e

  //80660e8e 6890020000      push    290h
  //80660e93 68606b5480      push    offset nt!KdDebuggerDataBlock (80546b60)
  //80660e98 be74926780      mov     esi,offset nt!KdpDebuggerDataListHead (80679274)

  p=pKdInitSystem;
  for (i=0;i<100;i++,p++)
  {
    if ((*p==0x68)&&
      (*(p+5)==0x68)&&
      (*(p+0xA)==0xbe))
    {
      pCall=(PCALL_CODE)(p+5);
      KdPrint(("KdDebuggerDataBlock地址=%x\n",pCall->address));
      KdPrint(("PsActiveProcessHead地址=%x\n",((PKDDEBUGGER_DATA64)pCall->address)->PsActiveProcessHead));
      return ((PKDDEBUGGER_DATA64)pCall->address)->PsActiveProcessHead;
    }
  }
  KdPrint(("获取KdDebuggerDataBlock地址失败\n"));
  return 0;
}
方法二:PsActiveProcessHead是活动进程链表头,理论上是第二个进程的EPROCESS结构成员ActiveProcessLinks的Blink,最后一个进程的EPROCESS结构成员ActiveProcessLinks的Flink。第二个进程即System进程,进程ID等于4。

代码:
NTSTATUS FindPsActiveProcessHead(ULONG *pPsActiveProcessHead)
{
  PEPROCESS process;
  PLIST_ENTRY pList=NULL;
  NTSTATUS status=PsLookupProcessByProcessId((HANDLE)4,&process);
  if (!NT_SUCCESS(status))
  {
    KdPrint(("获取process失败\n"));
    return status;
  }
  //xp _EPROCESS +0x088 ActiveProcessLinks : _LIST_ENTRY
  pList=(PLIST_ENTRY)((PUCHAR)process+0x88);
  KdPrint(("PsActiveProcessHead地址=%x\n",pList->Blink));
  *pPsActiveProcessHead=(ULONG)pList->Blink;
  ObDereferenceObject(process);
  return status;
}
方法三:ntoskrnl.exe导出了一个类型为PEPROCESS结构的变量PsInitialSystemProcess,它指向system进程(PID=4)的EPROCESS。这个方法与上一个方法类似。使用PsInitialSystemProcess在WinDDK中编译的话有链接ntoskrnl.lib。
名称:  PsInitialSystemProcess.jpg查看次数: 60文件大小:  161.8 KB

代码:
ULONG FindPsActiveProcessHead3()
{
  ULONG addr=*(PULONG)PsInitialSystemProcess;
  //xp _EPROCESS +0x088 ActiveProcessLinks : _LIST_ENTRY
  PLIST_ENTRY pList=(PLIST_ENTRY)(addr+0x88);
  KdPrint(("PsActiveProcessHead地址=%x\n",pList->Blink));
  return (ULONG)pList->Blink;
}
方法四:每个CPU都有个KPCR结构,第一个KPCR结构的地址是固定的0xffdff000。KPCR结构偏移0x034位置的结构成员KdVersionBlock是一个DBGKD_GET_VERSION64类型的指针。此结构同样在WDBGEXTS.H中有定义。DBGKD_GET_VERSION64结构成员DebuggerDataList其实是KdpDebuggerDataListHead。而KdpDebuggerDataListHead.Flink=KdpDebuggerDataListHead.Blink=KdDebuggerDataBlock。
名称:  KPCR1.jpg查看次数: 41文件大小:  198.5 KB
名称:  KPCR2.jpg查看次数: 40文件大小:  139.1 KB
名称:  KPCR3.jpg查看次数: 40文件大小:  125.6 KB

代码:
ULONG FindPsActiveProcessHead4()
{
  PLIST_ENTRY pList;
  PKDDEBUGGER_DATA64 pKdDebuggerData;
  PDBGKD_GET_VERSION64 pKdVersionBlock=(PDBGKD_GET_VERSION64)(*(PULONG)(0xffdff000+0x34));
  KdPrint(("获取到DebuggerDataList地址=%x\n",pKdVersionBlock->DebuggerDataList));
  pList=(PLIST_ENTRY)pKdVersionBlock->DebuggerDataList;
  KdPrint(("pList->Flink=%x,pList->Blink地址=%x\n",pList->Flink,pList->Blink));
  pKdDebuggerData=(PKDDEBUGGER_DATA64)pList->Flink;
  KdPrint(("PsActiveProcessHead地址=%x\n",pKdDebuggerData->PsActiveProcessHead));
  return pKdDebuggerData->PsActiveProcessHead;
}
方法五:调用SSDT表中的NtSystemDebugControl函数。


代码:
ULONG FindPsActiveProcessHead5()
{
  PLIST_ENTRY pList;
  DBGKD_GET_VERSION64 KdVersionBlock;
  PKDDEBUGGER_DATA64 pKdDebuggerData;
  ZwSystemDebugControl NtSystemDebugControl;
  
  NtSystemDebugControl=(ZwSystemDebugControl)GetSSDTAddrFromIndex(255);
  KdPrint(("NtSystemDebugControl函数地址=%x\n",NtSystemDebugControl));
  
  NtSystemDebugControl(SysDbgSysGetVersion,NULL,0,&KdVersionBlock,sizeof(DBGKD_GET_VERSION64),NULL);
  KdPrint(("DebuggerDataList=%x\n",KdVersionBlock.DebuggerDataList));
  
  pList=(PLIST_ENTRY)KdVersionBlock.DebuggerDataList;
  KdPrint(("获取到KdDebuggerDataBlock地址=%x\n",pList->Flink));

  pKdDebuggerData=(PKDDEBUGGER_DATA64)pList->Flink;
  KdPrint(("PsActiveProcessHead地址=%x\n",pKdDebuggerData->PsActiveProcessHead));

  return pKdDebuggerData->PsActiveProcessHead;
}

KPCR

由于Windows需要支持多个CPU, 因此Windows内核中为此定义了一套以处理器控制区(Processor Control Region)即KPCR为枢纽的数据结构, 使每个CPU都有个KPCR...

win7x64下的kpcr结构和kprcb结构

//翻网页看全是x86的,然后我就自己来找x64的 //下面都是windbg调的 nt!_KPCR    +0x000 NtTib            : _NT_TIB    +0...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:KPCR
举报原因:
原因补充:

(最多只允许输入30个字)