KPCR
来源:互联网 发布:pid补偿网络 编辑:程序博客网 时间:2024/06/07 06:16
由于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的基地址:
- /*
- * AUTHOR : 莫灰灰
- * BLOG : http://blog.csdn.net/hu3167343
- * DESCRIBE : KPCR->KdVersionBlock->PsLoadedModuleList->ntoskrnl base address
- */
- #include "ntddk.h"
- NTKERNELAPI VOID KeSetSystemAffinityThread ( KAFFINITY Affinity );
- NTKERNELAPI VOID KeRevertToUserAffinityThread ( VOID );
- VOID
- DriverUnload(IN PDRIVER_OBJECT pDriverObj)
- {
- KdPrint(("Unloaded Success\r\n"));
- return;
- }
- NTSTATUS
- DriverEntry(IN PDRIVER_OBJECT pDriverObj, IN PUNICODE_STRING pRegistryString)
- {
- NTSTATUS status = STATUS_SUCCESS;
- ULONG FSAddr;
- pDriverObj->DriverUnload = DriverUnload;
- KeSetSystemAffinityThread(1); //使当前线程运行在第一个处理器上
- __asm{
- push eax
- mov eax, fs:[0x34] ;得到KdVersionBlock的地址
- add eax,18h ;得到指向PsLoadedModuleList的地址
- mov eax,[eax] ;得到PsLoadedModuleList的地址
- mov eax,[eax] ;取出PsLoadedModuleList里面的内容, 即KLDR_DATA_TABLE_ENTRY结构
- mov eax,[eax+18h] ;取出DllBase, 即ntoskrnl.exe的基地址
- mov FSAddr, eax
- pop eax
- }
- KeRevertToUserAffinityThread();//恢复线程运行的处理器
- KdPrint(("0x%08X\n", FSAddr));
- return STATUS_SUCCESS;
- }
在和进程、线程相关的数据结构中,进程控制块和线程控制块是关键,通过这两个数据结构可以按图索骥找到其余的有关数据结构。Windows提供了一系列的获取进程控制块或者线程控制块的函数,下边将对这些函数实现进行剖析。
首先Windows内核提供了一个函数KeGetCurrentThread函数,用来或者当前线程的KTHREAD结构指针,这个函数的实现方式将在后文中介绍,windows其他获取控制块的函数都是以此函数为基础进行封装的。
下面将就几者之间的关系进行介绍。
1.KeGetCurrentThread – - > PsGetCurrentThread
后者只是前者定义的一个宏。并加上了强制类型转换,因为ETHREAD的一个成员是KTHREAD结构,所以两者的首地址相同。
#define PsGetCurrentThread() ((PETHREAD)KeGetCurrentThread())
2.PsGetCurrentThread – - >IoGetCurrentProcess
IoGetCurrentProcess是以PsGetCurrentThread为基础,封装的函数。
PEPROCESS STDCALLIoGetCurrentProcess(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 STDCALLKeGetCurrentProcess(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.cppint 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_TIBTib;/* 00 */ struct _KPCR*Self;/* 1C */ struct _KPRCB*Prcb;/* 20 */ KIRQLIrql;/* 24 */ ULONGIRR;/* 28 */ ULONGIrrActive;/* 2C */ ULONGIDR;/* 30 */ PVOIDKdVersionBlock;/* 34 */ PUSHORTIDT;/* 38 */ PUSHORTGDT;/* 3C */ struct _KTSS*TSS;/* 40 */ USHORTMajorVersion;/* 44 */ USHORTMinorVersion;/* 46 */ KAFFINITYSetMember;/* 48 */ ULONGStallScaleFactor;/* 4C */ UCHARDebugActive;/* 50 */ UCHARProcessorNumber;/* 51 */ UCHARReserved;/* 52 */ UCHARL2CacheAssociativity;/* 53 */ ULONGVdmAlert;/* 54 */ ULONGKernelReserved[14];/* 58 */ ULONGL2CacheSize;/* 90 */ ULONGHalReserved[16];/* 94 */ ULONGInterruptMode;/* D4 */ UCHARKernelReserved2[0x48];/* D8 */ KPRCBPrcbData;/* 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函数。
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;}
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;}
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;}
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;}
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
- KPCR
- KPCR
- ffdff000 处的结构 KPCR
- 从KPCR中获取内核基地址
- 内核程序中通过KPCR获取内核模块基址
- 内核态fs寄存器指向结构KPCR,方便反汇编
- win7x64下的kpcr结构和kprcb结构
- How to Defeat Windows 8 ASLR in Getting the Address of KPCR
- maven的archetype:create用不起
- ContextLoaderListener作用详解
- Java并发编程:volatile关键字解析
- POJ 2377 Bad Cowtractors(最大生成树)
- POJ 1775 (ZOJ 2358) Sum of Factorials
- KPCR
- JSTL
- 不相交集合求并的路径压缩
- 搭建集群---网络化知识
- mac osx使用80端口(使用nginx解决)
- 冒泡排序
- hdu2222 Keywords Search (AC自动机)
- 今日学习札记——C++Primer补充2(11.8)
- [转]非常牛叉的楼主,自己的问题其实就是答案--用springmvc上传文件时报The current request is not a multipart request异常