首页 -> 安全研究

安全研究

绿盟月刊
绿盟安全月刊->第12期->技术专题
期刊号: 类型: 关键词:
浅析Windows NT/2000环境切换

作者:WebCrazy(tsu00@263.net)
日期:2000-08-15

本文假设您已经了解Windows NT/2000系统体系,对Windows NT/2000内部KPEB/KTEB等数据结构与内核工作方式已有一定的概念,对80x86保护模式,Intel/AT&T格式汇编语言有过学习,能熟练使用SoftICE for Windows NT,且曾经接触过Microsoft Visual Studio及其附带工具,翻阅过Linux内核代码,如果您对这些方面不甚了解,请自行参阅相关书籍。

    环境切换(Context Switch)牵涉到很多方面的内容,本文仅对与其有关的几个数据进行详细的讨论,并给出取得这些数据的部分程序段,还列出Windows 2000的少量环境切换代码。另外文中讨论的系统内部数据均未来自Microsoft官方文档,在Windows NT/2000的下个版本甚至目前各版本间均会有差别,所以我尽量详细的将文中所涉及的软硬件列于下面,所有因硬件体系、软件版本不同等因素引起的差异,请自行根据您的情况予以调整。

    ⊙ x86平台单处理机Windows 2000 Server Build 2195
    ⊙ Numega SoftICE 4.05 for Windows NT/2000 Build 334
    ⊙ Linux 2.0.30内核
    ⊙ Datarescue IDA 4.0.4.362
    ⊙ Microsoft Visual Studio 6.0 SP3
    ⊙ Windows 2000 DDK

    80x86产生的环境切换有以下几种可能:
    1.当前任务执行一个FAR CALL或JMP指令,而选择器指向一个TSS描述符或一个任务门。
    2.当前任务执行IRET指令返回先前任务,IRET只在EFLAGS寄存器中的NT位置1时产生切换。
    3.发生一个中断或异常情况,并且IDT项是个任务门。

    Linux内核中有如下代码:

    /*
       /usr/src/linux/include/asm-i386/system.h
       仅列出单处理器实现代码
    */

    #define switch_to(prev,next) do { \
    __asm__("movl %2,"SYMBOL_NAME_STR(current_set)"\n\t" \
        "ljmp %0\n\t" \
        "cmpl %1,"SYMBOL_NAME_STR(last_task_used_math)"\n\t" \
        "jne 1f\n\t" \
        "clts\n" \
        "1:" \
        : /* no outputs */ \
        :"m" (*(((char *)&next->tss.tr)-4)), \
         "r" (prev), "r" (next)); \
        /* Now maybe reload the debug registers */ \
                .
                .
                .
         } \
    } while (0)

    这段代码使用了上面讨论的第一种情况。

    众所周知,Linux是个开放源代码的操作系统,而Microsoft则没如此“大方”,但我们仍能对其进行些逆向工程,可喜的是网上目前已经有很多人对此有过研究,现摘录Mark Russinovich部分成果(http://www.sysinternals.com/tips.htm):

    //
    // NT's main
    // NTOSKRNL main
    //
    int main( boot parameters )
       {
         //
         // Fire up NT!
         //
         KiSystemStartup();    
         return 0;
       }


    从中可看出ntoskrnl.exe(PE格式,可方便的使用反汇编工具进行分析)是NT OSLOADER真正调用内核的开始,其对文件对象(File)、作业对象(Job)、进程对象(Process)、线程对象(Thread)、纤程对象(Fiber)、文件映射对象(FileMapping)、事件对象(Event)、互斥对象(Mutex)、信号对象(Semaphore)等许多内核对象进行管理,其也负责线程调度,内存管理,进程间通信等所有操作系统功能,让它们协调工作,我们要讨论的线程切换代码也在此模块中。

    用IDA等对ntoskrnl.exe进行反汇编所得的结果,其分析的工作量恐怕大家都是可想而知的。在我们讨论Windows 2000环境切换详细代码前,还是先让我们看看以下几个重要的与环境切换有关的系统数据:

    1 进程Context
      进程Context是指80x86在保护模式下内存分页机制中当前进程的页目录所在的物理地址,其存放在系统CR3寄存器中,在Windows 2000中所处的位置为KPEB偏移后18h处,看看SoftICE的输出结果吧(限于篇幅,我对输出结果进行了删减,但仍对重要数据进行注解,应注意的是与您当前运行的程序等系统环境密切相关,随机性很强,下同):

    :cpu                            //显示当前cpu的寄存器值

    Processor 00 Registers
    ----------------------
    CS:EIP=0008:80069582  SS:ESP=0010:8046FD98
    EAX=8046BDF0  EBX=FFDFF000  ECX=FFDFF878  EDX=0000BA5A
    ESI=8046BDF0  EDI=8046BB60  EBP=FFDFF800  EFL=00000213
    DS=0023  ES=0023  FS=0030  GS=0000

    CR0=8000003B PE MP TS ET NE PG
    CR2=76EE18EC
    CR3=00030000
           |
           |_当前进程的CR3
    CR4=000002D1 VME PSE MCE PGE
       .
       .
       .

    :proc idle

     Process     KPEB      PID  Threads  Pri  User Time  Krnl Time  Status
     *Idle       8046BB60    0        1    0   00000000   0000BA5A  Running
       |           |
       |           |_Idle进程的KPEB
       |_系统中当前进程(SoftICE中用不同颜色突出,且前面有个*)

    :dd 8046bb60+18 l 4   //dd Idle's KPEB+18h
    0010:8046BB78 00030000  00000000  00000000  00000000      ................
                    |
                    |_Idle进程Context
    :addr
    CR3       LDT Base:Limit  KPEB Addr PID   Name
    00030000                  FE4E1C60  0008  System
    02D59000                  FF8E6540  0090  smss
    01D41000                  FF8E17E0  00AC  csrss
    00686000                  FE51BAE0  00C0  winlogon
    0095D000                  FF8A7AE0  00DC  services
    0276E000                  FF8A5D60  00E8  lsass
    00394000                  FF881020  0180  svchost
    02CAE000                  FF884020  01A4  SPOOLSV
    00882000                  FF85B560  01D0  msdtc
    02993000                  FF83F020  0238  svchost
    00D2F000                  FF83D760  024C  llssrv
    0063A000                  FF837860  0274  regsvc
    02EFA000                  FF6ECD60  0318  dfssvc
    00A5E000                  FF823A20  0328  inetinfo
    03612000                  FF6AF860  0384  explorer
    003A2000                  FF68E460  03B4  internat
    003A7000                  FF68CD60  0130  OSA
    008C1000                  FF6769A0  03E8  svchost
    01BAA000                  FF65A020  01C0  cmd
    00822000                  FF86C960  038C  conime
    03362000                  FF6B3540  0388  notepad
   *00030000                  8046BB60  0000  Idle
     |
     |__当前进程Context,是不是与上一命令输出结果一致。

    可以用同样的方法进行再次进行验证。

    2.Context Switches Times 线程已被操作系统调度次数
    当每次操作系统调用线程时,都会将这个值加一的。Visual Studio所附的工具Spy++,在Thread窗口中Thread Properties中Context Switches指出系统中该线程已调度的次数(Spy++只有在Windows NT/2000中运行时才会显示出这个值,9x中则没有)。Switch Times在系统中所处的位置在KTEB的偏移4ch处。
    
    :thread idle
    TID   Krnl TEB  StackBtm  StkTop    StackPtr  User TEB  Process(Id)
    0000  8046BDF0  8046D040  80470040  8046FD90  00000000  Idle(00)
    :dd 8046bdf0+4c l 4
    0010:8046BE3C 0000E778  00000000  00000002  00000000      x...............
                     |
                     |_指出当前Idle线程已经被系统调用0E778h(十进制59256)次了。用e命令改改再看看Spy++的输出结果!

    3.线程所属进程的KPEB与进程名  //分别位于KTEB+44h与KPEB+1fch处
      具体见我在《再谈Windows NT/2000内部数据结构》(nsfocus Magazine 11)一文所述。

    应该指出的是,以上讨论只是针对Windows 2000 Server Build 2195的,如果您的系统不是的话或想知道如何得到这些值的具体位置,请参阅我以下的叙述的方法:

    通过找突破口,正像上面我所描述的Context Switches Times在Spy++中显示的一样,然后可以用逆向工程法,我也是用这个方法来取得这个具体位置的。

    举个例子吧!我曾经对SoftICE for NT中的addr命令输出结果(包含进程Context)来自何处感到困惹,也曾经在国外的一些著名的新闻组中提问过,不过至今仍没人应答(可能是我的英文水平太差,人家看不懂什么意思吧!)

    addr命令输出结果见上。

    后来无奈之下我还是想到CR3(存放进程页目录物理地址的寄存器)应与特定的进程有关,其应该存放在KPEB结构中(实际上的确是这样的)。而如果真是这样的话,不是只要枚举(Enum)出系统中所有的KPEB,则能得到所有的CR3值(当然前提是找出其相对KPEB的偏移值),相应的使用我在《再谈Windows NT/2000内部数据结构》(nsfocus Magazine 11)的方法就可以取出所有进程的进程名了吗?(PID也是一样的)。

    我在通过分析PSAPI.DLL中枚举系统进程的函数后(EnumProcesses等),发现系统启动后的第一个进程system的KPEB是存放在ntoskrnl.exe导出的PsInitialSystemProcess指出的地址处的,而系统中各个KPEB由一链表联结着,至于链表的定义在Windows 2000 DDK中的ntdef.h中如下定义的:

    typedef struct _LIST_ENTRY {
       struct _LIST_ENTRY *Flink;
       struct _LIST_ENTRY *Blink;
    } LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

    有了KPEB,跟踪相应的代码(这段反汇编代码我将在下面列出),就能找出偏移地址18h处的CR3值。

    以上操作,在Windows 9x中可由Kernel32.dll中序号为1的Undocumented函数(NONAME,Softice Export列表中显示为ORD_0001)实现,但因为Windows 9x与NT/2000的内核的不同,Softice for 9x与NT的addr命令输出的格式也完全不同。至于在Windows NT/2000中不知道是否有现成的函数可以得到结果,至少现在我也没找到,这可是题外话。

    因为上面的叙述还是比较抽象,我还是将SoftICE的输出结果列于此,更利于理解:

    :dd PsInitialSystemProcess l 4   
    0008:8046A844 FE4E1C60  E1000968  00000000  00000000      `.N.h...........
                     |
                     |_System进程的KPEB

    :dd @PsInitialSystemProcess+18 l 4
    0008:FE4E1C78 00030000  00000000  00000000  00000000      ................
                     |
                     |_System进程的Context

    :dd @PsInitialSystemProcess+9c l 4   //9ch是PID相对KPEB的位置
    0008:FE4E1CFC 00000008  FF8E65E0  8046A180  00000000      .....e....F.....
                     |
                     |_System进程PID

    :dd @PsInitialSystemProcess+1fc l 10  //1fch是Process Name相对KPEB的位置
    0008:FE4E1E5C 74737953  00006D65  00000000  00000000      System..........
                                                                 |
                                          System进程Process Name_|
    :? @(@PsInitialSystemProcess+a0)-a0  //计算System指向的下一个进程的KPEB,0a0h是链状结构相对KPEB的偏移
    FF8E6540  4287522112 (-7445184)  "
版权所有,未经许可,不得转载