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

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

    Regmon是监视应用程序访问系统注册表的实用程序。大家都知道在应用程序中使用注册
表一般都调用WinAPI Regxxx,而Regxxx最终会调用Native API Zwxxx!(参阅Windows NT/20
00 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 TwoLettersX
xxYyy 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导出,另一部分由ntosk
rnl.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指向ServiceDescriptorTableEn
try(由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 Se
rver 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 Serv
ice入口地址在这一区域的位置,那么又如何得到System Service的Service ID呢!我们可以
随便以ZwOpenKey作个例子:

    :u ZwOpenKey
    ntoskrnl!ZwOpenKey
    0008:80400E2A B867000000 MOV EAX,00000067
                      |                 |_ServiceID
                      |_机器码(其中第二字节即ZwOpenKey线性地址加一处就是ServiceI
D)
    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)_fu
nction+1)]
    ServiceTable->ServiceTable就是我们上面所述的 ServiceDescriptorTableEntry->Se
rviceTableBase(为了便于描述)。_function+1即ServiceID所在地址。整个表达式即取得_f
unction对应的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给我提供随书配套例子代码。如果您见过此书(不知道有没有Ch
inese 版,E文也可),能不能与我联系联系?

    谈完System Service后,再让我们来看看Regmon是如何在Driver中取得系统进程名的。
首先谈谈KTEB(Kernel Thread Environment Block)与KPEB(Kernel Process Environment B
lock),与TEB(其实应该是User-TEB)一样,KPEB/KTEB则纪录着系统内核进程/线程信息。要
了解KTEB、KPEB,首先要知道如何得到当前进程/线程中它们的基址,可以先看看Native AP
I 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主要是调用PsGe
tCurrentProcess取得KPEB基址,然后搜索KPEB,得到ProcessName相对KPEB的偏移量,存放
在全局变量ProcessNameOffset中。在NT/2000 DDK中如下定义PsGetCurrentProcess:
    #define PsGetCurrentProcess() IoGetCurrentProcess()
而IoGetCurrentProcess已经在前面讨论过了。
    作者在3页内存区域(x86中一页为4k)查找,从程序中注释可知他也不知道是否会超出此
范围,还有程序段中SYSNAME被定义为system,因为调用Driver中DriverEntry入口正是由sy
stem进程调度(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,我查MSD
N,到各新闻组,追踪NT内核,也没找到其中的一小部分,这也是我着手写此篇的用意,希望
懂得的高手,朋友能互相交流交流,还有本文有误之处,还望您能指出并与我说说,谢谢!

参考资料:
    1.Regmon 4.22源代码
    2.Windows 2000 DDK Documentation

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第一章 Win32 API概论…………………………………………………………………………1 1.1 为什么使用Win32 API …………………………………………………………………1 1.2 Win32 API简介 …………………………………………………………………………1 1.3 综述………………………………………………………………………………………11 第二章 窗口管理函数(Windows Control Function) ……………………………………13 2.1 易用特性函数(Accessibility Features)…………………………………………13 2.2 按钮函数(Button)……………………………………………………………………20 2.3 插入标记(^)函数(Caret)…………………………………………………………21 2.4 组合框函数(Combo box) ……………………………………………………………24 2.5 通用对话框函数(Common Dialog Box) ……………………………………………25 2.6 标函数(Cursor)………………………………………………………………………36 2.7 对话框函数(Dialog Box)……………………………………………………………40 2.8 编辑控制函数(Edit Control)………………………………………………………54 2.9 图标函数(Icon)………………………………………………………………………54 2.10 键盘加速器函数(Keyboard Accelerator)……………………………………… 61 2.11 键盘输入函数(Keyboard InPut) …………………………………………………63 2.12 列表框函数(List box) ……………………………………………………………75 2.13 菜单函数(Menu) ……………………………………………………………………76 2.14 消息和消息队列函数(Message and Message Queue)……………………………90 2.15 鼠标输入函数(Mouse Input) ……………………………………………………100 2.16 多文档接口函数(Multiple Document Interface) ……………………………103 2.17 资源函数(Resource)………………………………………………………………105 2.18 滚动条函数(Scroll Bar)…………………………………………………………113 2.19 窗口函数(Window)…………………………………………………………………119 2.20 窗口类函数(Window Class)………………………………………………………144 2.21 窗口过程函数(Window Procedure)………………………………………………150 2.22 窗口属性函数(Window Property) ………………………………………………152 第三章 图形设备接口函数(Graphic Device Interface Function) …………………155 3.1 位图函数(Bitmap) …………………………………………………………………155 3.2 笔刷函数(Brush)……………………………………………………………………171 3.3 剪切函数(Clipping) ………………………………………………………………176 3.4 颜色函数(Color)……………………………………………………………………179 3.5 坐标空间与变换函数(Coordinate Space Transformation)……………………186 3.6 设备环境函数(Device Context) …………………………………………………195 3.7 填充形态函数(Filled shape) ……………………………………………………211 3.8 字体和正文函数(Font and Text)…………………………………………………215 3.9 ICM 2.0函数 …………………………………………………………………………238 3.10 线段和曲线函数(Line and Curve)………………………………………………295 3.11 图元文件函数(Metafile)……………………………………………
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值