分享
 
 
 

再谈Windows NT/2000内部数据结构

王朝vc·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

再谈Windows NT/2000内部数据结构

WebCrazy(tsu00@263.net)

注:本文最初见于www.nsfocus.com

现在我们结合Regmon(http://www.sysinternals.com/)在NT中的实现方法再来谈谈Windows NT/2000内部数据结构。

Regmon是监视应用程序访问系统注册表的实用程序。大家都知道在应用程序中使用注册表一般都调用WinAPI Regxxx,而Regxxx最终会调用Native API Zwxxx!(参阅Windows NT/2000 DDK Documentation)。Regmon正是通过改变这些例程以达到监视注册表的目的。Zwxxx的实现方式如下:

mov eax, ServiceId

lea edx, ParameterTable

int 2eh

ret ParamTableBytes

这就是所说的NT System Services,是不是与Linux有点相似(只不过Linux使用的是80h中断而已,它也有ServiceID,如fork系统调用ServiceID为2)。

System Services在DDK Documentation是如下定义的:

The set of native, user-mode routines exported by the executive for use only by protected subsystems. Each system service has a name of the form TwoLettersXxxYyy where:

TwoLetters is the prefix for all system services.

Xxx is usually a verb, describing the operation of a given service.

Yyy is generally the object type the service operates on.

System Services在系统中由两部分组成,一部分由win32k.sys导出,另一部分由ntoskrnl.exe提供服务。前者主要完成NT中win32、Posix与Os/2等子系统(subsystems)与内核的通信,仅能由用户态的应用程序调用,如user32!WaitMessage等。由于Regmon只涉及后者,所以本文将对其进行讨论,以下所有关于System Service的讨论均适合两者!

上次(Nsfocus Magazine 10)我曾经提及KeServiceDescriptorTable,也说过它的结构如下:

struct _ServiceDescriptorEntry {

unsigned int *ServiceTableBase;

unsigned int *ServiceCounterTableBase;

unsigned int NumberOfServices;

unsigned char *ParamTableBase;

}ServiceDescriptorTableEntry

ntoskrnl.exe导出全局变量KeServiceDescriptorTable指向ServiceDescriptorTableEntry(由win32k.sys导出的System Services也有自己的ServiceDescriptorTable,在Win2000 Server中其Service ID从1000h始,由KeServiceDescriptorTable以下偏移50h处指向,其结构与ntosrknl.exe导出的基本一致,本文不作讨论,SoftICE中的ntcall命令在特定情况下可以列出所有的System Service)。

下面我们先用SoftICE 4.05 For Windows NT/2000来分析分析x86平台Windows 2000 Server Build 2195的情况(以下仅摘录部分,不同版本不同时刻可能得到的数据未必一样)

:dd KeServiceDescriptorTable l 4*4

//如果看win32k.sys导出的表请使用dd KeServiceDescriptorTable+50 l 4*4

//即将KeServiceDescriptorTable向下偏移50h处下同

0008:8046AB80 804704D8 00000000 000000F8 804708BC ..G...........G.

| |_ServiceTableBase值 | | |_ParamTableBase值

| |_似乎总为0 |

|_KeServiceDescriptorTable地址 |_NumberOfService

:dd @KeServiceDescriptorTable l byte(@(KeServiceDescriptorTable+08))*4

// dd ServiceDescriptorTableEntry->ServiceTableBase l NumberOfService*4

0008:804704D8 804AB3BF 804AE86B 804BDEF3 8050B034 ..J.k.J...K.4.P.

|

|_ServiceID=0的System Service入口地址(依次类推)

0008:804704E8 804C11F4 80459214 8050C2FF 8050C33F ..L...E...P.?.P.

0008:804704F8 804B581C 80508874 8049860A 804FC7E2 .XK.t.P...I...O.

0008:80470508 804955F7 8049C8A6 80448472 804A8D50 .UI...I.r.D.P.J.

0008:80470518 804B6BFB 804F0CEF 804FCB95 8040189A .kK...O...O...@.

0008:80470528 804D06CB 80418F66 804F69D4 8049E0CC ..M.f.A..iO...I.

...(略)

:db @(KeServiceDescriptorTable+0c) l byte(@(KeServiceDescriptorTable+08))

// db ServiceDescriptorTableEntry->ParamTableBase

0008:804708BC 18 20 2C 2C 40 2C 40 44-0C 18 18 08 04 04 0C 10 . ,,@,@D........

|

|_ServiceID=0的System Service参数个数*4(即参数个数为18h/4=6)

0008:804708CC 18 08 08 0C 08 08 04 04-04 0C 04 20 08 0C 14 0C ........... ....

...(略)

要获得哪个应用程序对系统注册表有过操作,只要在对其有操作的System Service中注入自己的代码,也就是改变这些System Service的执行流程,先执行自己的代码(Regmon中用于记录供GUI部分使用),接着返回至原先处继续执行即可。通过以上分析,我们知道只要修改ServiceTableBase到ServiceTableBase+NumberOfService*4范围的数据就可以改变System Service的执行流程,而只要知道System Service的ServiceID就可以改变这一System Service入口地址在这一区域的位置,那么又如何得到System Service的Service ID呢!我们可以随便以ZwOpenKey作个例子:

:u ZwOpenKey

ntoskrnl!ZwOpenKey

0008:80400E2A B867000000 MOV EAX,00000067

| |_ServiceID

|_机器码(其中第二字节即ZwOpenKey线性地址加一处就是ServiceID)

0008:80400E2F 8D542404 LEA EDX,[ESP+04]

0008:80400E33 CD2E INT 2E

0008:80400E35 C20C00 RET 000C

这样只要知道Zwxxx例程名(即System Service在内存中的线性地址),是不是就可以实现我们的目的了呢?来看看Regmon的具体实现代码吧:

.

.

.

// 保存ZwOpenKey原先入口,在HookRegOpenKey中使用

RealRegOpenKey = SYSCALL( ZwOpenKey );

// 修改ZwOpenKey流程,指向新的入口,即调用ZwOpenKey时转向执行HookRegOpenKey

SYSCALL( ZwOpenKey ) = (PVOID) HookRegOpenKey;

.

.

.

SYSCALL在intel平台是如下定义的:

#define SYSCALL(_function) ServiceTable->ServiceTable[ *(PULONG)((PUCHAR)_function+1)]

ServiceTable->ServiceTable就是我们上面所述的 ServiceDescriptorTableEntry->ServiceTableBase(为了便于描述)。_function+1即ServiceID所在地址。整个表达式即取得_function对应的System Service的入口地址在线性内存中的位置。其它定义请参阅Regsys.c与Regsys.h!

可以使用SoftICE对比一下Regsys.sys装载前后ServiceTable中System Service入口地址的变化,加深对System Service拦截的理解.

好了现在我们知道Regmon的基本实现方法了(当然真正要实现此功能还要考虑很多问题,如保护态应用程序与内核驱动程序之间的通信、线程同步等等)。

让我们再来看看KeServiceDescriptorTable的另一个应用吧!如果我们重新分配段内存池,构造自己的ServiceTable与ParamTable数组(必须复制系统原有的System Services,否则...),然后修改结构中ServiceTableBase与ParamTableBase,使其指向自己的ServiceTable与ParamTable,再修改一下NumberOfServices,是不是可以增加自个儿的System Service呢!如果你有兴趣的话可以参阅<<UNDOCUMENTED NT>>。这书我也没见过,只知道网上它名声在外。哦,还要感谢James Shatlyk给我提供随书配套例子代码。如果您见过此书(不知道有没有Chinese 版,E文也可),能不能与我联系联系?

谈完System Service后,再让我们来看看Regmon是如何在Driver中取得系统进程名的。

首先谈谈KTEB(Kernel Thread Environment Block)与KPEB(Kernel Process Environment Block),与TEB(其实应该是User-TEB)一样,KPEB/KTEB则纪录着系统内核进程/线程信息。要了解KTEB、KPEB,首先要知道如何得到当前进程/线程中它们的基址,可以先看看Native API IoGetCurrentProcess。在Windows 2000 DDK Document中它是如下定义的:

PEPROCESS IoGetCurrentProcess();

使用IDA Pro或SoftICE,可知其在ntoskrnl.exe仅是由几条汇编指令实现的:

mov eax,fs:[00000124]

mov eax,[eax+00000044] //NT 4.0以下这个值应为[eax+40]

ret

这个Native API很有典型性,它的第一条指令取得当前线程的KTEB,而整个API刚好将相对于KTEB始68(即16进制44)字节处取得当前进程的KPEB返回给使用者。你可以使用SoftICE验证一下。

让我们再来看看其具体是如何实现的:

//----------------------------------------------------------------------

//

// GetProcessNameOffset

//

// In an effort to remain version-independent, rather than using a

// hard-coded into the KPEB (Kernel Process Environment Block), we

// scan the KPEB looking for the name, which should match that

// of the GUI process

//

//----------------------------------------------------------------------

ULONG GetProcessNameOffset()

{

PEPROCESS curproc;

int i;

DbgPrint(("GetProcessNameOffset\n"));

curproc = PsGetCurrentProcess();

//

// Scan for 12KB, hopping the KPEB never grows that big!

//

for( i = 0; i < 3*PAGE_SIZE; i++ ) {

if( !strncmp( SYSNAME, (PCHAR) curproc + i, strlen(SYSNAME) )) {

return i;

}

}

//

// Name not found - oh, well

//

return 0;

}

//----------------------------------------------------------------------

//

// GetProcess

//

// Uses undocumented data structure offsets to obtain the name of the

// currently executing process.

//

//----------------------------------------------------------------------

FILTERSTATUS GetProcess( PCHAR Name )

{

PEPROCESS curproc;

char *nameptr;

ULONG i;

//

// We only try and get the name if we located the name offset

//

if( ProcessNameOffset ) {

curproc = PsGetCurrentProcess();

nameptr = (PCHAR) curproc + ProcessNameOffset;

strncpy( Name, nameptr, 16 );

} else {

strcpy( Name, "???");

}

.

.

.

}

这段代码从Regmon中NT Driver部分摘录,详细可参阅Regsys.c。

这两函数主要功能是取得进程名称,供程序使用。大家都知道在Driver部分不能简单的调用WIN32 API,而NT执行体提供的NtQuerySystemInformation主要针对所有进程、线程或其他NT内部信息等,所以我们必须寻找其它方法(一般方法是跟踪相应的Win32 API用Debugger对其进行艰苦但充满挑战充满乐趣的逆向工程,然后找出其在NT执行体中的具体实现过程,你也可以使用此方法对本文所提及的进行验证)。

Regmon中这两个函数通过查找KPEB取得进程名,GetProcessNameOffset主要是调用PsGetCurrentProcess取得KPEB基址,然后搜索KPEB,得到ProcessName相对KPEB的偏移量,存放在全局变量ProcessNameOffset中。在NT/2000 DDK中如下定义PsGetCurrentProcess:

#define PsGetCurrentProcess() IoGetCurrentProcess()

而IoGetCurrentProcess已经在前面讨论过了。

作者在3页内存区域(x86中一页为4k)查找,从程序中注释可知他也不知道是否会超出此范围,还有程序段中SYSNAME被定义为system,因为调用Driver中DriverEntry入口正是由system进程调度(GetProcessNameOffset在DriverEntry中调用)。你也可以使用SoftICE查出特定Windows NT/2000版本中ProcessNameOffset的值。在x86平台Windows 2000 Server Build 2195中它为1fch(NT 4.0与3.51中为1dch),然后根据这个值找几个进程核对核对。

GetProcess将当前进程的KPEB基址加上ProcessNameOffset值取得当前进程(Regmon中即调用操作Registry的Native API进程)的名称。

至于KPEB/KTEB等的具体结构,各字节的具体含义,由于其所谓的Undocument,我查MSDN,到各新闻组,追踪NT内核,也没找到其中的一小部分,这也是我着手写此篇的用意,希望懂得的高手,朋友能互相交流交流,还有本文有误之处,还望您能指出并与我说说,谢谢!

参考资料:

1.Regmon 4.22源代码

2.Windows 2000 DDK Documentation

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有