邪恶八进制信息安全团队技术讨论组's Archiver

EvilOctal 2005-6-27 02:56

[转载]Windows NT/2000内部数据结构探究

文章作者:WebCrazy([email]tsu00@263.net[/email])
信息来源:本文最初见于[url]www.nsfocus.com[/url]

  WINDOWS系统隐含了不少内部数据结构,其记录着与系统相关的所有重要信息如线程、进程、内核调用等等,具体如Windows NT/2000模块ntoskrnl.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,具体可以参阅[url]www.sysinternals.com.[/url]
   本文仅在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这样的定义(参阅&t;<Advanced Windows NT>>);handler指向异常处理代码。

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

     .
     .
     .

   ; *********************************************************
   ; * Ring3 Virus Game Initial Program *
   ; *********************************************************

   MyVirusStart: ; Ring3代码入口点
   push ebp

   ; *************************************
   ; * Let&#39;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 有任何问题联系[email]tsu00@263.net[/email]

   #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病毒一样,而目前国内在这方面的资料可真谓少之又少,本文仅在这方面说出我自己的一些切身实践,错误之处,在所难免。如果您有任何发现,如果您对这方面有比较有兴趣,请联系[email]tsu00@263.net[/email].

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

   参考文献:

      1.Jeffrey Richter <<Advanced Windows NT>>
      2.Matt Pietrek <<A Crash Course on the Depths of Win32 Structured Exception Handling>>
      3.CIH 1.3源代码

页: [1]
© 1999-2008 EvilOctal Security Team