上一篇:妙用Regsvr32命令修复系统故障 >>
再谈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
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))
// dd 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呢!如果你有兴趣的话可以参阅<>。这书我也没见过,只知道网上它名声在外。哦,还要感谢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
()
下一篇:全面了解Windows系统鲜为人知的宝藏 >>
相关文章:
- · XP 注册表键值的双重作用
- · 10种常见计算机无法启动故障解决方法
- · 下载隐藏链接的网络资源
- · 磁盘坏道修复
- · 常见电子书格式及其反编译思路
- · Gmail变成blog和变成HTML空间
- · Java入门需掌握的30个基本概念
- · 几个DNS欺骗的利用原理
- · 虚拟路由器简介
- · 随心订制linux透明防火墙
- · 硬盘数据丢失后的解决方法
- · FlashGet最新绝密曝光
- · Transact-SQL语句总汇
- · 防范非法用户入侵系统秘籍
- · 磁盘碎片分析报告巧利用
- · 带你亲手打造QQ破解器
- · 在Ultraedit中用宏实现将选中的代码设置为注释
- · 画图程序制作头像图片全过程
- · 欺骗黑客的一种有效方法
- · 电子邮件头全揭密
- · 格式化硬盘数据删不净 信息露不停
- · 禁用QQ的办法
- · 双系统的安装和启动原理
- · DEBUG命令大全
- · Linux中的一些常用操作方法
- · 提高系统内存效能的五大技巧
- · 自动完成密码查看攻略
- · DOS常用命令详解
- · 端口的基本概念
- · 诊断排除基本的 TCP/IP 问题
- · TCP/IP协议的简单说明
- · 选择局域网中的通信协议
- · IP网络路由技术
- · 双网卡负载均衡及路径切换配置
- · SNIFFER的含义和工作原理
- · 一山还比一山高!破解“隐身”的几大绝招
- · Windows Xp系统密码遗忘解决办法
- · 建立注册DLL和反注册DLL文件的快捷方式
