Windows 2000 Kernel Source Code Analysis-Part I

http://blog.chinaunix.net/uid-20517852-id-1936379.html
Windows 2000 Kernel Source Code Analysis-Part I

这是我的关于Windows 2000内核细节系列文章中的第一篇。我会涉及到已泄漏的Windows 2000内核源代码。显然,我不会在文章中直接写出相关的内核代码,但我会清楚地给出相关话题的参考文件,所以如果你有了这些源代码就会很容易地找到这些文件并理解它。

那么,首先如果你有了源代码固然是好事,如果你没有,通过阅读本文同样可以对Windows 2000内核有一个大致的了解。其次,你必须对X86体系结构有所了解,其实我不会讨论像IDT这样的硬件细节知识,所以赶快拿起INTEL手册开始研究吧!最后,我假设你知道什么是文件系统,什么是调度器等等。现在我们开始吧。

建议阅读以下一些关于内核的书籍:
• The Windows 2000 Device Driver Book - Art Baker, Jerry Lozano
• Inside Windows 2000 - Russinovich, Solomon (sysinternals)
• Windows driver model - Oney
• Windows NT Native Api - Gary Nebbett
• Undocumented Windows NT - Dabak, Phadke, Borate
• Windows NT File SYstem Internals - Nagar
• Windows NT Device Driver Development - Viscarola

Windows 源代码泄漏是哪年哪月的事GOOGLE一下到处都是,据说对此事负有直接责任的是一个微软的合作伙伴。但是,泄漏的详细情况我仍然不是很清楚。

我们从以下问题开始讨论吧:
从哪里能弄到这些源代码?你必须自己解决这个问题!可以在网络上搜索,现在有很多网站提供下载,显然这是非法的。但是,微软好像也没因此而起诉谁!!嘿嘿!
这些代码有几个版本?虽然网上有许多骗人的声称是源代码,但是确实有两个版本:一个是Windows NT 4.0的源代码,另一个是Windows 2000 SP1的。这里我们讨论的是Windows 2000,但是如果你有NT4的源代码更好。实际上这篇文章所讨论的引导过程使用的是NT4.0的源代码。很明显,对于引导过程来说基本的内容是相同的,所以对于Windows XP和Windows Server 2003也是一样的。
它们是完整的源代码吗?严格意义上它们并不完整。其中包含内核,用户空间DLLs,甚至有单人纸牌游戏的代码!最可惜的是没有NTFS的源代码,它由相关的驱动程序管理,而且在两个版本中都没有给出。但这些源代码仍然有很多有用的东西,在后面就会看到这一点。也没有包含整个GDI,但是我们所感兴趣的部分(内核)是完整的。
那么我能重新编译它吗?这些源代码缺少一些定义和其他的文件,而内核的重编译需要IFS包,但是所有的用户模式代码不能重新编译。在写这篇文章的时候我不知道如何重新编译Windows 内核或与此类似的东西。如果你知道或你自己重编译成功了请你告诉我。
这些源代码有什么用?对此我不能说的太多。它们主要对于驱动程序开发人员或Windows 的竞争者有用。这些源代码是微软WISE(Windows Interface Source Environment,Windows 接口源环境)的一部分,这个项目的目的是帮助UNIX和Macintosh系统开发者将程序整合到Windows中。
源代码泄漏对于系统安全构成威胁这个说法对吗?虽然有些人对这个话题表现出狂热的兴趣,但是他们肯定是错的。在我写本文期间分析家们并没有说内核源代码中有任何致命的Bugs。唯一的Bug是来自于IE5.0,是关于处理位图时发现内存泄漏。
这些代码是用什么语言写的,如何编译的?内核几乎全部用C(不是C++)写成,有些与硬件关系密切的部分使用了汇编。用户模式部分使用C++编写。显然,内核的设计使用了面向对象思想。这些代码使用Visual Studio的内部版本而不是其他商业版本。
这些代码的广泛传播对黑客,程序员以及最终用户带来了什么改变吗?没有什么改变。驱动程序开发者已经对Windows内核有了足够的了解(比如,System Internals,OSR,NTDEV等等)。这些源代码确实可以丰富现存的关于内核的文档,但对那些研究内核并对内核作逆向工程的人并没有什么大的帮助,因为他们所需要的那些东西已经通过内核逆向工程得到了。并不是所有人都知道微软公司其实已经为每个Windows模块提供了调试符号文件,所以像下面的一段反汇编代码:
mov [0x11223344], eax
push 0x22334455
call 0x77889900

有了调试符号后就会像下面这样:
mov [_TickCount], eax
push _dwSeconds
call _GetTime

所以一旦解决了符号名字,那么把汇编代码转换成C代码就很容易了。以上代码与相关的C代码对于阅读来说没有什么不同:
TickCount = ...blahblah...;
GetTime(dwSeconds);
怎么样?Windows本来就是开放源代码的,你只是需要知道如何阅读汇编代码罢了。比如Matt Pietrek用伪代码重写了许多Windows 9X内核。我们唯一知道的关于Windows内核源代码泄露后有所进展的是那些Windows 的竞争者,其它Windows源代码泄漏所带来的影响已经没有什么报道了。

现在我们已经对Windows源代码有了一般性的认识,该是讨论一些细节的时候了!

如果不想在源代码的海洋中迷失的话,你应该在脑海里有一幅windows系统主要组件的结构图。首先我们看看下面三个重要的目录:
\bsc
\private
\public

第一个目录包含了搜索引擎的索引数据,最后一个目录是SDK和OAK,我们对这两个目录都没什么兴趣。我们感兴趣的是\private目录,它是所有代码的基础。在NT4 源代码中只给了\private目录。现在你会看到这个目录很大!所以我们不会对所有的目录都给出注释。下面是一些重要组件的目录:
模块: ntoskrnl.exe
位置: \private\ntos
描述: Windows内核, 与Linux的bzImage相当
模块: ntdll.dll
位置: \private\ntos\dll
描述: 从用户模式转换到内核模式的大门(系统调用)
模块: kernel32.dll
位置: \private\windows\base\client
描述: Windows内核的用户模式部分
模块: user32.dll
位置: \private\ntos\w32\ntuser\client
描述: 各种组件, 比如窗口创建和文本处理等
模块: advapi32.dll
位置: \private\windows\screg\winreg
描述: 注册表 APIs

这些是主要组件,但我们所关心的90%是内核部分。在\private\windows\shell目录下你可以发现注册表管理器、任务管理器、游戏和其他应用程序。也有一些其他组件的源代码,比如comdlg32等等。真正缺少的是前面提到的:
NTFS.sys——NTFS驱动程序
gdi32.dll——图形函数库

在Windows 2000源代码中引导启动代码没有给出,但是在NT4 中给了,它所在的目录是:
\private\ntos\boot
你可以发现引导扇区加载器、内核加载器、硬件侦测器和安装程序加载器,每个组件都有自己的目录。引导扇区加载器与特定文件系统相关,尤其是NTFS,所以你可以在这里发现NTFS分区的相关数据。如果你对NTFS感兴趣这里也有日志文件管理函数。回到Windows 2000代码中,可以发现有关网络部分的代码:
\private\inet
它是IE(mshtml)、urlmon、wininet的一部分。

好了,现在我们已经对源代码结构有了大致的了解,如果你所关心的问题在这里我没有提及,可以使用grep工具自己搜索。

从引导开始


到接触代码的时候了!嗯!我们从哪里开始呢?从引导开始?好的!前面我们提到\private\ntos\boot目录,该目录下每个组件都有自己的子目录而引导扇区加载器是在\bootcode子目录下。我们知道开始点是在\mbr下,即 主引导记录。它是在开机BIOS代码执行后操作系统所执行的第一段代码(文件名是x86mboot.asm)。正如上面提到的它是每个PC机的标准代码。这段代码读出位于主引导记录末尾的分区表,找到标志为可引导的分区,把它的引导扇区拷贝到内存中并执行。主引导记录的结构是:
+----------+ -- MBR --
| 引导代码 | 可执行代码
| 分区1      | 分区表
| 分区2      |
| 分区3      |
| 分区4      |
+------------+ -- END MBR --
| 分区1      | 分区
|                |
.                .
.                .
所以引导代码只是重定位到地址 0000:0600,跳到这里从分区表中读取可引导分区的入口,把引导分区拷贝到内存的标准引导地址(0000:7C00),最后执行它。因此就好像BIOS自己直接引导了这个分区。嗯,注意在x86mboot.asm文件的第48行,以及以后的代码,该代码使用了一个远跳转指令自动重定位到地址 0000:0600,但值得注意的是它用了硬编码,如下:

db OEAh
db ...blabla...

这些硬编码的位是JMP 0000:0600指令,它的地址在编译期间决定下来。以后发现可引导分区后还会发现一些硬编码的地方。这些代码还会使处理器跳到0000:7C00来引导新的扇区。此时,代码有了些变化。因为我们为每一个系统所支持的文件系统提供不同的代码:
\etfs Electronic Tariff Filing System
\fat fat32
\hpfs Pinball File System (high performance file system, os2)
\ntfs nt native file system
\ofs surprise! Void directory!

然而只有FAT32和NTFS是系统真正所支持的,把Windows NT 系统安装到FAT32分区上并不是件好事。现在我们主要关心NTFS的引导分区加载器上,其他引导分区工作方式相同。

这段代码(ntfsboot.asm)的主要任务是读ntldr文件,把它映射到地址2000:0000并开始执行它。注意,现在操作系统依然在实模式下,所以所有代码都是16位的。但是这段代码太大了,第一个扇区的512字节容不下它,实际上BIOS把可引导磁盘(磁道0,磁头0,扇区1)的第一个物理扇区映射到内存中物理地址为7C00处。然而现在BIOS并没有真的把可引导分区的第一个扇区映射到物理内存中,而是由MBR映射的。为什么这段代码没有把多于512字节的ntfsboot全部映射呢?显然是为了与其他系统兼容。所以被映射了的ntfsboot现在马上要做的就是把剩余的代码映射到内存。其实,它先从第一个扇区读代码,一直到bootsector代码部分,并把它重定向到地址0D00:0000,所以内存中该地址处是bootsector代码和后面扇区中的必要代码部分。一旦这些代码映射好了,它就跳到0D00:0200处,这是已从磁盘读入的第二个扇区中的代码(第一个扇区的代码已经执行过了,所以它没什么用处了)。这段代码在物理地址D200处,它离地址2000还远着呢,所以没有任何干扰问题。这里我们再次看到新的重定位跳转代码(165行):

push seg
push offset
ret

呵呵,这里好像使用远跳转会有些问题!这并不重要,只是好奇罢了。现在主引导程序转移到了第二个扇区。在执行第二个扇区代码之前我们可以看到一些数据,如"55AA",这代表第一个扇区的末尾处。主引导程序读NTFS文件(你可以看到几个读NTFS文件的函数),它把执行权返回给ntldr的内存映像,上面我们已经提到了它位于2000:0000处。与其他文件系统相关的引导扇区加载器的工作原理也是一样的,唯一不一样的是读ntldr的代码部分,它因文件系统的不同而不同。现在我们要转到ntldr代码部分了。注意,到现在为止还没有做任何的初始化,像换页机制,保护模式的切换等等。我们仍处于实模式,所以ntldr开始执行16位代码,然后切换到保护模式后才开始执行32位代码。好了,现在我们要看一下这个文件:

\ntos\boot\startup\i386\su.asm

记住我们仍处于NT 4的源代码中。第一行代码是 jmp RealStart。在这行代码以及整个例程中我们看到一些与FAT文件系统有关的代码。如果系统是从FAT32引导的,那么仍然需要处理读ntldr文件并把它映射到内存再去执行它。这里我们关心的是NTFS,所以忽略FAT32的问题。RealStart例程为了把执行权交给SuMain需要准备栈和设置段寄存器。SuMain位于:

\ntos\boot\startup\i386\main.c

在这里我们终于看到C代码了!但是一定要记住su.asm文件,因为它导出了保护模式启用函数,一会儿我们会看到。这个例程初始化视频子系统(display.c中的InitializeVideoSubSystem函数),如果系统从软盘引导则关掉软盘驱动器(su.asm中的TurnMotorOff函数),做其他一些初始化工作,如计算操作系统加载器所需的内存量,之后我们进入核心地带:32位模式切换。

这段代码启动了A20线(a20.asm中的EnableA20函数),重定位保护模式中使用的IDT和GDT结构。现在该做保护模式切换了(su.asm中的EnableProtectPaging函数)。注意,首次执行这段代码时还没有启用换页功能,其实加载器的启动代码还没有给出PDE和PTE的有效描述符。换页功能的启用是在操作系统加载器的开始代码处,一会我们就会看到。

特别地这段代码在FS段寄存器中为进程控制块PCR——内核的一个基本数据结构——设置选择符,因此我们希望代码立即将执行权传递给ntoskrnl.exe模块。除此之外,已经为IDT和GDT设置了内存区域,而将LDT清零。Windows NT实际上没有使用LDT。因此保护模式的代码是:

mov eax, c30
... (首先避免换页)
or eax,PROT_MODE
mov cr0,eax

但是并没有完全切换到32位模式,因为现在需要设置一些段寄存器,数据结构以及TSS选择符。切换到保护模式之后代码返回到SuMain继续执行。SuMain调用RelocateLoaderSections函数计算操作系统加载器入口点的地址。它是一个有效的COFF PE格式的文件并嵌入到ntldr中,所以我们认为它是Windows执行的第一个真正的进程。一旦找到入口点后TransferToLoader函数使用刚刚计算出的地址把执行权传给它。所以现在我们转向目录:

\ntos\boot\lib\i386

这里有许多将要执行的文件。尤其是文件:

entry.c

它是上面提到的PE文件的入口点,由NtProcessStartup函数识别。现在我们分析这个文件,我们会看到第一个调用的函数是DoGlobalInitialization。也能看到调用InitializeMemorySubsytem函数。看起来很有意思!(作者按:许多函数作为BootContextRecord结构体的参数传递过去,这个结构是在bootx86.h中声明的_BOOT_CONTEXT结构)。再来看看memory.c文件中的InitializeMemorySubsystem函数。在这个文件中我们也能找到内存映射(映像和相关的堆栈组件)。这个函数循环设置所有的由BootContextRecord加载的内存描述符。每个内存描述符由两个域组成:BlockBase和BlockSize。它们描述了内存区域的起始地址和大小。所以

BootContext->MemoryDescriptorList

是一个用于描述所有内存块的内存描述符的数组。记住,现在我们在保护模式中但是没有换页机制。所以while语句为所有已知的内存块准备内存地址(页界限)。加载器不使用16m以上的内存(为了避免与ISA总线数据传输相互干扰),所以所有16M以上的内存都标识为MemorySetDescriptorRegion。一旦代码退出了while循环所有物理内存都有了描述符(由MemAllocDescriptor和MempSetDescriptorRegion函数设置),而描述符数组由arcemul.c中定义的MDArray[]维护。这些"宏"描述符使物理内存能够按需描述,所以在while循环以后会有处理第一个1MB内存的描述符的代码。其实,所有内存对于加载器来说都是有用的,例如中断向量区,系统堆等等。注意,第一个1MB的虚拟内存与第一个1MB的物理内存是对应的,这允许操作系统加载器继续执行第一个1MB以下的代码并把内核映射到相应的内存处。这时我们再次看到MempAllocDescriptor函数,它初始化MDArray数组,获得1Mb以下的内存区子描述符。当所有描述符都建立好后我们就到了MempTurnOnPaging函数。这个函数遍历MDArray数组,这样它就能利用刚计算出来的所需内存所创建的PDE/PTE入口来调用MempSetupPaging函数。全局变量PDE代表PDE,HalPT代表PTE。遍历完内存描述符,正确设置了PDE /PTE 后,调用MempTurnOnPaging函数:

mov eax,PDE
mov cr3,eax
mov eax,cr0
or eax,CR0_PG
mov cr0,eax

这样就开启了换页机制。这是自系统加载以后第一次开启换页机制。指向PDE数组的指针被放到页目录基地址寄存器(CR3)中,并开启了CR0寄存器的PG位启动换页机制。之后我们回到InitializeMemorySubsystem函数,它调用MempCopyGdt把GDT和IDT转移到新的内存区域处。好了现在这个函数结束了,我们可以回到DoGlobalInitialization函数了。我们看到

其他一些初始化处理代码,最后调用InitializeMemoryDescriptor函数,可以在注释中看到这时InitializeMemorySubsystem函数的第二步。首先建立PDE/PTE开启换页机制,现在该函数回到MDArray数组并为所有标记为"reserved"的描述符分配内存。现在我们完成了DoGlobalInitialization函数的分析。再回到NtProcessStartup函数。这里有其他一些初始化函数,找到引导分区,初始化系统内存和I/O系统,然后调用BlStartup函数。之后是下面这段代码:

// we should never get here!
do {
GET_KEY();
} while ( 1 );

这表示ntldr在BlStartup函数中结束了它的全部工作,它位于initx86.c文件,目录是:

\ntos\boot\bldr\i386

这个函数是做什么的?它负责打开驱动和读boot.ini文件(所有可引导入口都在该文件中定义)。这些可引导入口以传统的选择菜单的形式出现在屏幕上,因此一旦选择了一个引导入口,操作系统就通过 磁盘/分区/路径的形式引导,然后到了BlOsLoader函数,它位于:

\ntos\boot\bldr\osloader.c

我们可以看到这个函数之前定义了名为"ntoskrnl.exe"和"hal.dll"的文件,这些组件是之后将要加载的。这段代码注释得很好,所以很容易理解它是做什么的:它打开引导文件和系统分区,打开输入/输出控制台,用BlMemoryInitalize初始化内存,位于:

\ntos\boot\lib

的blmemory.c文件,可以发现栈,堆和内存分配列表。此时,一个唯一的内存描述符分配给了相应的操作系统加载器,其实除了它之外没有加载其他程序。因此该函数搜索操作系统加载器内存描述符之后的第一个描述符,并在尽可能高的地址处为它分配堆栈。内存初始化之后还有其他的初始化工作(I/O和其他资源)。所以我们看到处理位于boot.ini文件的引导参数,其实内核和硬件抽象层与默认的有所不同。所以现在建立内核和硬件抽象层组件的路径以及系统储巢。第一个加载的是ntoskrnl.exe,它由BlLoadImage函数加载到内存中。加载器分析文件系统的类型并把最终的参数传递给内核。现在轮到hal.dll了,它也是由BlLoadImage函数加载。记住,这两个PE COFF文件实际上位于:

\ntos\boot\lib\peldr.c

它不是一个完全的PE文件加载器,在这段代码之后实际使用的是BlScanImportDescriptorTable函数来加载动态链接库并进行引入/导出绑定。这时该到加载驱动程序的时候了。为了加载驱动程序,操作系统加载器必须参考系统储巢。什么是储巢?储巢是存储于注册表的系统信息文件。特别是在

\windows\config\system

下的储巢文件。它存储了所有硬件设置和相应驱动器的信息。一旦加载了驱动程序,就调用BlSetupForNt函数:它做一些硬件初始化,例如ABIOS的重定位,TSS重定位等等。最后加载器完成了它的工作,之后我们到了这行:

(SystemEntry)(BlLoaderBlock);

这行代码调用ntoskrnl模块的入口点。从现在开始我们就要转到Windows 2000源代码了(我们现在仍在NT4中!)。

内核的入口点位于:

\win2k\private\ntos\ke\i386\newsysbg.asm

该函数是KiSystemStartup。它带有一个参数,就是上面提到的BlLoaderBlock。

本系列文章的第一部分就到这里了。我们已经看到了系统的初始化部分,即引导部分。我们到了内核,所以下一部分将要讲述的是内核初始化和其他有趣的话题。

下一章再见!

Windows 2000,原名Windows NT 5.0。它结合了Windows 98和Windows NT 4.0的很多优良的功能/性能与一身,超越了Windows NT的原来含义。   Windows 2000系列分成四个产品:Windows 2000 Professional, Windows 2000 Server, Windows 2000 Advanced Server, Windows 2000 Datacenter Server。 Windows 2000 Professional 是一个商业用户的桌面操作系统,也适合移动用户,是Windows NT Workstation 4.0的升级。Windows 2000 Server和Advanced Server分别是Windows NT Server 4.0及其企业版的升级产品。Windows 2000 Datacenter Server是一个新的品种,主要通过OEM的方式销售,是,支持32个以上的CPU和64GB的内存,以及4个节点的集群服务。 Windows 2000平台包括了Windows 2000 Professional 和Windows 2000 Server前后台的集成,下面仅从五个方面简要地介绍一下它的新特性和新功能。   一、活动目录   Windows 2000 Server在Windows NT Server 4.0的基础上,进一步发展了“活动目录(Active Directory)”。活动目录是从一个数据存储开始的。它采用了类似Exchange Server的数据存储,称为:Extensible Storage Service (ESS)。其特点是不需要事先定义数据库的参数,可以做到动态地增长,性能非常优良。这个数据存储之上已建立索引的,可以方便快速地搜索和定位。活动目录的分区是“域(Domain)”,一个域可以存储上百万的对象。域之间还有层次关系,可以建立域树和域森林,无限地扩展。   在数据存储之上,微软建立了一个对象模型,以构成活动目录。这一对象模型对LDAP有纯粹的支持,还可以管理和修改Schema。Schema包括了在活动目录中的计算机、用户和打印机等所有对象的定义,其本身也是活动目录的内容之一,在整个域森林中是唯一的。通过修改Schema的工具,用户或开发人员可以自己定义特殊的类和属性,来创建所需要的对象和对象属性。   活动目录包括两个方面:一个目录和与目录相关的服务。目录是存储各种对象的一个物理上的容器;而目录服务是使目录中所有信息和资源发挥作用的服务。活动目录是一个分布式的目录服务。信息可以分散在多台不同的计算机上,保证快速访问和容错;同时不管用户从何处访问或信息处在何处,都对用户提供统一的视图。 活动目录充分体现了微软产品的“ICE”,即集成性(Integration),深入性(Comprehensive),和易用性(Ease of Use)等优点。活动目录是一个完全可扩展,可伸缩的目录服务,既能满足商业ISP的需要,又能满足企业内部网和外联网的需要 最近在网上游荡的时候发现msdos和windows 2000的原代码 ,不敢独享,所以分享给大家
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值