首页 -> 安全研究

安全研究

绿盟月刊
绿盟安全月刊->第10期->技术专题
期刊号: 类型: 关键词:
Windows NT/2000内部数据结构探究

作者:WebCrazy (tsu00@263.net)
出处:http://www.nsfocus.com
日期:2000-06-14


    WINDOWS系统隐含了不少内部数据结构,其记录着与系统相关的所有重要信息如线程、
进程、内核调用等等,具体如Windows NT/2000模块ntoskernl.exe中的NtBuildNumber与KeServiceDescriptorTable等(用SoftICE或Visual Studio所带的Dependency Walker之类的可以看到),前者只是指出当前Windows的Build号(如SoftICE下可用dw命令查出我的机器中为0893h 即十进制2195);后者是指向如下数据结构的指针:
    struct _ServiceDescriptorEntry {
    unsigned int *ServiceTableBase;
    unsigned int *ServiceCounterTableBase;
    unsigned int NumberOfServices;
    unsigned char *ParamTableBase;
     }ServiceDescriptorTableEntry
其典型应用为Mark Russinovich与Bryce Cogswell的Regmon,具体可以参阅
www.sysinternals.com.    

    本文仅在Intel i386的Windows 2000 Server(Build 2195)中对
    TEB(Thread Environment Block)作初步介绍.

    TEB在Windows 9x系列中称为TIB(Thread Information Block),她纪录着线程的重要信息,每一个线程对应一个TEB结构。其格式如下(摘自Matt Pietrek的Under the Hood专栏-MSJ 1996):

    typedef struct _TIB
    {
        PEXCEPTION_REGISTRATION_RECORD pvExcept;
        // 00h Head of exception record list
        PVOID   pvStackUserTop;     // 04h Top of user stack
        PVOID   pvStackUserBase;    // 08h Base of user stack

        union                       // 0Ch (NT/Win95 differences)
        {
            struct  // Win95 fields
                {
                    WORD    pvTDB;         // 0Ch TDB
                    WORD    pvThunkSS;     
                // 0Eh SS selector used for thunking to 16 bits
                    DWORD   unknown1;      // 10h
                } WIN95;

            struct  // WinNT fields
                {
                    PVOID SubSystemTib;     // 0Ch
                    ULONG FiberData;        // 10h
                } WINNT;
        } TIB_UNION1;

        PVOID   pvArbitrary;        
        // 14h Available for application use
        struct _tib *ptibSelf;      
        // 18h Linear address of TIB structure

        union                       // 1Ch (NT/Win95 differences)
        {
                struct  // Win95 fields
                {
                    WORD    TIBFlags;           // 1Ch
                    WORD    Win16MutexCount;    // 1Eh
                    DWORD   DebugContext;       // 20h
                    DWORD   pCurrentPriority;   // 24h
                    DWORD   pvQueue;            
                    // 28h Message Queue selector
                } WIN95;

                struct  // WinNT fields
                {
                    DWORD unknown1;             // 1Ch
                    DWORD processID;            // 20h
                    DWORD threadID;             // 24h
                    DWORD unknown2;             // 28h
                } WINNT;
        } TIB_UNION2;

        PVOID*  pvTLSArray;         // 2Ch Thread Local Storage array

        union                       // 30h (NT/Win95 differences)
        {
            struct  // Win95 fields
            {
                PVOID*  pProcess;     
                // 30h Pointer to owning process database
            } WIN95;
        } TIB_UNION3;
    
    } TIB, *PTIB;

  在Windows 2000 DDK中定义为:
    typedef struct _NT_TIB {
        struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList;
        PVOID StackBase;
        PVOID StackLimit;
        PVOID SubSystemTib;
        union {
               PVOID FiberData;
               ULONG Version;
            };
            PVOID ArbitraryUserPointer;
                struct _NT_TIB *Self;
        } NT_TIB;

    庆幸的是,Windows在调入进程,创建线程时,操作系统均会为每个线程分配TEB,而
    且都将FS段选择器(i386)指向当前线程的TEB数据(单CPU机器在任何时刻系统中只有
    一条线程在执行),这就为我们提供了存取TEB数据的途径。实际上Windows都是通过
    这种方法来为你的应用程序提供信息的,让我们来看一个例子吧!大家都知道用
    GetCurrentThreadID API来获得当前线程ID的,其在Kernel32.dll是如下实现的:

    GetCurrentThreadID:
            mov eax, FS:[00000018]  
            ; 18h Linear address of TIB structure(TIB结构线性地址)
            mov eax, [eax+24]       ; 24h ThreadID
            ret                     ; 将EAX中的值返回给调用者

    由于TEB结构过于庞大,我现在只来谈谈偏移量为00h的
struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList,并结合CIH 1.3源码来说说它具体用处。ExceptionList主要用于处理SEH(Structured Exception Handling)的。如果你连C语言中新增的_try,_except与_finally也不熟悉的话,建议请先看看
Jeffery Richter的<<Advanced Windows NT>>或之类的。

  首先让我们来看看_EXCEPTION_REGISTRATION_RECORD结构,在
CRT(C++ RunTime library)源码中它如下定义:

   // Exsup.INC ---Microsoft Visual C++ CRT 源文件

    _EXCEPTION_REGISTRATION struc
        prev                dd      ?
        handler             dd      ?
    _EXCEPTION_REGISTRATION ends

    其中prev是指向前一_EXCEPTION_REGISTRATION的指针,形成一链状结构,这样才会
在EXCPT.H中有EXCEPTION_CONTINUE_SEARCH这样的定义(参阅<<Advanced Windows NT>>);
handler指向异常处理代码。

    CIH正是利用了这一机制,将handler指向它自己程序中。在它入口处有如下代码:

    .
        .
        .
                                                                                 
    ; *********************************************************                      
    ; *             Ring3 Virus Game Initial Program          *                      
    ; *********************************************************                      
                                                                                 
    MyVirusStart:                                              
    ; Ring3代码入口点               
               push    ebp                                              
                                                                                 
    ; *************************************                                          
    ; * Let's Modify Structured Exception *                                          
    ; * Handing, Prevent Exception Error  *                                          
    ; * Occurrence, Especially in NT.     *                                          
    ; *************************************                                          
                                                                                 
               lea     eax, [esp-04h*2]                   
                       ;在栈中分配8字节存放_EXCEPTION_REGISTRATION结构
                     ;相当于C中基于栈的数据,即局部变量(C编译器中完成)
                       ;这样EAX即指向_EXCEPTION_REGISTRATION的指针,但此时
              ;_EXCEPTION_REGISTRATION结构未初始化
                       ;具体实现机制可翻阅编译原理书籍和Matt Pietrek大师文章
                                  
                                                                                 
               xor     ebx, ebx                           ;0->EBX        
               xchg    eax, fs:[ebx]                      ;FS:[0]<->EAX
                       ;此时EAX存放的是原来异常处理代码,FS:[0]指向TEB中
            ;ExceptionList(FS指向TEB,ExceptionList偏移为0,即FS:[0])
                                                                                 
               call    @0                                               
    @0:                                                                              
               pop     ebx
                          ;此三行计算代码入口,此时ebx就是@0的地址              
               lea     ecx, StopToRunVirusCode-@0[ebx]
                          ;将ecx指向自己内部代码处
               push    ecx
                       ;填充_EXCEPTION_REGISTRATION结构的handler
            ;在发生异常时,操作系统会自动调用,此时为CIH代码
                                                                   
               push    eax
                       ;EAX为原来异常处理代码
                       ;填充_EXCEPTION_REGISTRATION结构的prev

        .
        .
        .
                                                                         
    这其后CIH调用int 3使系统发生异常,仍能进入自已的代码,这可从CIH源代码中的如
    下注释得到证实:

    ; *************************************                                          
    ; * Generate Exception to Get Ring0   *                                          
    ; *************************************                                          
                                                                                 
                        int     HookExceptionNumber     ; GenerateException      
  HookExceptionNumber定义为3,此段代码会产生异常,具体请参阅CIH源代码。

    因为如上代码比较抽象,我特意将它稍加修改,以便于理解
    (PE格式可直接在Windows下执行):

    // TestCIH.C 有任何问题联系tsu00@263.net
    
    #include <windows.h>
    #include <stdio.h>


    EXCEPTION_DISPOSITION __cdecl _except_handler(  //异常处理程序段               
        struct _EXCEPTION_RECORD *ExceptionRecord,
        void * EstablisherFrame,
        struct _CONTEXT *ContextRecord,
        void * DispatcherContext )
    {
        printf( "CIH Run Here...\n" );
        exit(0);                                 
        //由于堆栈已被程序打乱,有兴趣的可以自己将它恢复,
        //这儿我只简单的退出
    }

    void main(void)
     {
          _asm
            {
                        push    ebp                                              
                                                                                 
                        mov     eax, esp
                        sub     eax, 8          
                        //这两行相当于lea  eax, [esp-04h*2]                                                           
                        xor     ebx, ebx                                         
                        xchg    eax, fs:[ebx]                                    
                                                                                 
                        call    next
          next:                                                                              
                        pop     ebx                     
                        //这三行在这没实在意义,只是为了与CIH对比
                                                                                 
                        lea     ecx, _except_handler  
                        //将_except_handler设为异常处理入口
                        push    ecx                                              
                                                                                 
                        push    eax                                              
                                                                                 
            }
          _asm
            {
                        mov     eax,0
                        mov     [eax],0        
                        //发生STATUS_ACCESS_VIOLATION异常
                        //让操作系统调用_except_handler
            }     
      }

    _except_handler回调函数原形可参阅EXCPT.H 
    在main函数中第一个_asm段与前面讨论的CIH代码基本一致,而第二个_asm段则试图
    写系统保留内存地址,发生异常。
    使用Visual C++如下编译:
        c:>Cl  testCIH.c
        c:>testCIH
           CIH Run Here...
    在Windows 2000中,运行此段代码时,出现异常后操作系统将控制权交给
    _except_handler执行,这样CIH代码在NT/2000环境下在系统修改被其保护的内存地
    址时(IDT区域),不至于出现非法操作等提示,以达到保护自己的目的!
    
    我总觉得了解系统安全,首先必须对这个系统有足够的了解,就像了解CIH病毒一样,
    而目前国内在这方面的资料可真谓少之又少,本文仅在这方面说出我自己的一些切身
    实践,错误之处,在所难免。如果您有任何发现,如果您对这方面有比较有兴趣,
    请联系tsu00@263.net.

    最后很感谢绿盟高手的指点与帮助!


    参考文献:

      1.Jeffery Richter <<Advanced Windows NT>>
      2.Matt Pietrek
        <<A Crash Course on the Depths of Win32 Structured Exception Handling>>
      3.CIH 1.3源代码
版权所有,未经许可,不得转载