无忧启动论坛

标题: NTLDR研究专贴 [打印本页]

作者: Rinrin    时间: 2006-10-16 20:18
标题: NTLDR研究专贴
本帖及跟帖将探讨NTLDR的内部细节
将运用反汇编手段对NTLD进行研究分析

如果各位对本主题感兴趣,欢迎跟帖探讨
假如回复内容比较空洞,我还是希望你不要回复了,那样没有什么意义,也影响帖子质量

研究对象:NTLDR
版本:Windows Server 2003 SP1 Check Build
获得方法:
下载SP1补丁包http://www.microsoft.com/downloa ... &displaylang=en
用WinRAR提取如下文件:
ntldr
ntdetect.com
osloader.exe
startrom.com
调试信息文件获取方法:
安装Windbg
在命令行上输入类似如下的命令:
symchk -v osloader.exe
即可在\Windows\Symbols目录下获得osloader.pdb文件
反汇编工具及获得方法:
IDA Pro 5.0
可以去“看雪论坛”寻找
当前状态:
分析osloader.exe
作者: Rinrin    时间: 2006-10-16 20:19
NTLDR Reverse Engineering
原始地址:http://www.reteam.org/board/inde ... 1302&#entry1302
翻译:Rinrin

    ntldr分为两部分,一个16位flat模式的二进制文件(类似于一个.COM文件)和一个32位的PE文件,它们担负了大部分装载任务。我发现可以用一种简单的办法来把ntldr分开,从而用IDA分别研究这两部分。我们可以使用一个16进制编辑器,在ntldr中搜索"MZ"或者"PE"这两个特征字符串,从"MZ"开始到文件结束作为一个文件。我把它命名为osloader.exe,因为这个文件的信息头里指出它的原始文件名就是osloader.exe。

    通常情况下,你可以忽略那些16进制代码。基本上它的作用就是为32位的osloader.exe设置执行环境,并且实现了一些保护模式下的调用(实际上是对实模式BIOS接口的包装)。因为这时候还没有任何驱动,osloader.exe就用这些功能来实现它的I/O操作。设置GDT和IDT的操作也在这些代码里。。。嗯,以后有时间我可能会去看一看这些代码的。

    上面提到的I/O函数在osloader.exe里是通过一个指针数组来访问的。AndreaGeddon在他的文章(Understanding Win2k Sources - Part 1 by AndreaGeddon)里提到这个指针在BootContextRecord的第二个双字处了解这些函数的定义对逆向osloader.exe很有好处,根据我收集的资料,这个函数表不完全的定义如下:

typedef struct _IO_FUNCTIONS
{
   DWORD SystemReset;
   DWORD DiskServices;
   DWORD KeybGetChar;
   DWORD GetTimeOfDay;
   DWORD ExecuteBootSector;
   DWORD ExecuteNtdetect;
   void (__cdecl *VideoServices) (DWORD function, DWORD data);
   void (__cdecl *GetDateTime) (DWORD *time, DWORD *date);
   DWORD SerialServices;
   DWORD GetMicrosecondMetric;
   DWORD LoadVgaTextModeChars;
   void (__cdecl *GetSystemMemoryMap) (PSYSTEM_MEMORY_MAP pSystemMemoryMap);
   DWORD ExtDiskServices;
   DWORD GetBootCdromStatus;
   DWORD ExtDiskGetDriveParams;
   DWORD PxenvApiServices;
   DWORD ApmInitialize; /* 这个函数即没有参数也没有返回值。
                           奇怪的是,虽然没有返回任何信息,
                           它也对高级电源管理的版本和接口
                           作了简单的测试。*/
   // ... (?)
} IO_FUNCTIONS, *PIO_FUNCTIONS;

    就如你所看到的,我只是给出了它们的名字来表明它们的用处,并没有研究它们的参数和实际的功能。
    用IDA来分析osloader.exe,当IDA的分析停止时,你就可以看到代码的入口点了。AndreaGeddon的文章里把它叫作NtProcessStartup,让我们把它重命名吧。它的唯一参数arg_0是指向BootContextRecord结构体的指针,我们把它重命名成pBootContext。
    我们跳过第一个调用,因为它只是为局部变量分配栈空间。第二个调用叫做DoGlobalInitialization(参考AndreaGeddon的文章而来),它也只有一个参数pBootContext,在这个函数里,分配了一个全局指针指向I/O函数表。所以你需要识别出它。看看DoGlobalInitialization的反汇编代码片断:

.text:0040125C DoGlobalInitialization proc near      ; CODE XREF: NtProcessStartup+1Ep
.text:0040125C
.text:0040125C pBootContext    = dword ptr  8
.text:0040125C
.text:0040125C                 mov     edi, edi
.text:0040125E                 push    ebp
.text:0040125F                 mov     ebp, esp
.text:00401261                 push    esi
.text:00401262                 mov     esi, [ebp+pBootContext]
.text:00401265                 mov     eax, [esi+BOOT_CONTEXT.LoaderImageBase]
.text:00401268                 mov     loaderImageBase, eax
.text:0040126D                 mov     eax, [esi+BOOT_CONTEXT.LoaderExportTableVa]
.text:00401270                 push    esi
.text:00401271                 mov     loaderExportTableVa, eax
.text:00401276                 call    InitializeMemory
.text:0040127B                 test    eax, eax
.text:0040127D                 jz      short @@1
.text:0040127F                 push    eax
.text:00401280                 push    offset aInitializememo; "InitializeMemory failed %lx\n"
.text:00401285                 call    printf_output
.text:0040128A                 pop     ecx
.text:0040128B                 pop     ecx
.text:0040128C
.text:0040128C infinite_loop:                        ; CODE XREF: DoGlobalInitialization:infinite_loopj
.text:0040128C                 jmp     short infinite_loop
.text:0040128E; ---------------------------------------------------------------------------
.text:0040128E
.text:0040128E @@1:                                  ; CODE XREF: DoGlobalInitialization+21j
.text:0040128E                 mov     eax, [esi+BOOT_CONTEXT.pIoFunctions]
.text:00401291                 mov     pIoFunctions, eax
.text:00401296                 mov     ecx, [esi+BOOT_CONTEXT.IsEisaSystem]
.text:00401299                 push    0000007Fh
.text:0040129B                 push    00000000h      ; /* SET CURSOR POSITION - Row = 0x7F, Column = 0x00 */
.text:0040129D                 mov     isEisaSystem, ecx
.text:004012A3                 call    [eax+IO_FUNCTIONS.VideoServices]

    上面的代码中,对于pIoFunctions->VideoServices的调用是通过寄存器间接寻址完成的,寄存器的值被传给全局指针pIoFunctions,你应该能轻松看出这些的。
    在IDA中创建一个结构体定义IO_FUNCTIONS,它应该像下面这样:
IO_FUNCTIONS    struc; (sizeof=0x4C)
00000000 SystemReset     dd ?
00000004 DiskServices    dd ?
00000008 KeybGetChar     dd ?
0000000C GetTimeOfDay    dd ?
00000010 ExecuteBootSector dd ?
00000014 ExecuteNtdetect dd ?
00000018 VideoServices   dd ?
0000001C GetDateTime     dd ?
00000020 SerialServices  dd ?
00000024 GetMicrosecondMetric dd ?
00000028 LoadVgaTextModeChars dd ?
0000002C GetSystemMemoryMap dd ?
00000030 ExtDiskServices dd ?
00000034 GetBootCdromStatus dd ?
00000038 ExtDiskGetDriveParams dd ?
0000003C PxenvApiServices dd ?
00000040 ApmInitialize   dd ?
00000044 field_44        dd ? // 未知
00000048 field_48        dd ? // 未知
0000004C IO_FUNCTIONS    ends

    现在所有对于pIoFunctions的交叉引用,都加上IO_FUNCTIONS的偏移定义。例如下面的代码:
.text:0040656D                 mov     eax, pIoFunctions
.text:00406572                 call    dword ptr [eax+1Ch]
    变成了:
.text:0040656D                 mov     eax, pIoFunctions
.text:00406572                 call    [eax+IO_FUNCTIONS.GetDateTime]
    希望你也能辨认出I/O函数调用的名称,这非常有用。
    事实上osloader.exe中还有另外一张更重要的函数表,不过它已经被很好地文档化了,你只需要稍待片刻就能看到。
    OK,接着上次我们说到的函数表,它之所以存在是因为bootloader的代码不仅仅是为Intel-x86体系的计算机所写,也可以用于RISC体系的机器。RISC计算机的固件会帮助bootloader实现启动过程。这些固件遵循ARC(高级RISC计算)规范,通过一个函数向量表向bootloader提供了接口,bootloader通过接口实现启动和启动配置。
    这样的固件在基于Intel-x86的机器上是不存在的,所以osloader.exe自己实现了这张向量表。ARC规范详细地描述了它。所以我们可以通过规范文档来了解每一个函数,它们的参数,返回值和作用。这非常简单:),我们要做的只是定位这张表。
    规范文档在此->http://www.netbsd.org/Documentat ... es/ARC/riscspec.pdf
    要定位这张向量表,让我们回到NtProcessStartup里,找到紧接着DoGlobalInitialization的调用。这个函数初始化了固件调用向量表。我把它命名为InitArcFirmwareVectors。下面是我的反汇编结果,可以看到只有osloader使用的向量被实现了:

.text:0040757D InitArcFirmwareVectors proc near      ; CODE XREF: NtProcessStartup+24p
.text:0040757D                 mov     edi, edi
.text:0040757F                 push    edi
.text:00407580                 push    25h
.text:00407582                 pop     ecx
.text:00407583                 mov     eax, offset UnimplementedFirmwareVector
.text:00407588                 mov     edi, offset ArcFirmwareVectors
.text:0040758D                 rep stosd
.text:0040758F                 mov     eax, offset ArcFwRestartReboot
.text:00407594                 mov     ArcFirmwareVectors.Close, offset ArcFwClose
.text:0040759E                 mov     ArcFirmwareVectors.Open, offset ArcFwOpen
.text:004075A8                 mov     ArcFirmwareVectors.GetMemoryDescriptor, offset ArcFwGetMemoryDescriptor
.text:004075B2                 mov     ArcFirmwareVectors.Seek, offset ArcFwSeek
.text:004075BC                 mov     ArcFirmwareVectors.Read, offset ArcFwRead
.text:004075C6                 mov     ArcFirmwareVectors.GetReadStatus, offset ArcFwGetReadStatus
.text:004075D0                 mov     ArcFirmwareVectors.Write, offset ArcFwWrite
.text:004075DA                 mov     ArcFirmwareVectors.GetFileInformation, offset ArcFwGetFileInformation
.text:004075E4                 mov     ArcFirmwareVectors.GetTime, offset ArcFwGetTime
.text:004075EE                 mov     ArcFirmwareVectors.GetRelativeTime, offset ArcFwGetRelativeTime
.text:004075F8                 mov     ArcFirmwareVectors.GetPeer, offset ArcFwGetPeer
.text:00407602                 mov     ArcFirmwareVectors.GetChild, offset ArcFwGetChild
.text:0040760C                 mov     ArcFirmwareVectors.GetParent, offset ArcFwGetParent
.text:00407616                 mov     ArcFirmwareVectors.GetComponent, offset ArcFwGetComponent
.text:00407620                 mov     ArcFirmwareVectors.GetConfigurationData, offset ArcFwGetConfigurationData
.text:0040762A                 mov     ArcFirmwareVectors.GetEnvironmentVariable, offset ArcFwGetEnvironmentVariable
.text:00407634                 mov     ArcFirmwareVectors.Restart, eax
.text:00407639                 mov     ArcFirmwareVectors.Reboot, eax
.text:0040763E                 pop     edi
.text:0040763F                 retn    4
.text:0040763F InitArcFirmwareVectors endp

    根据上面的汇编代码片断你应该能自己推断出ArcFirmwareVectors结构体的成员。在IDA里定义如下结构体:

00000000 ARC_FIRMWARE_VECTORS struc; (sizeof=0x94)
00000000 Load            dd ?
00000004 Invoke          dd ?
00000008 Execute         dd ?
0000000C Halt            dd ?
00000010 PowerDown       dd ?
00000014 Restart         dd ?
00000018 Reboot          dd ?
0000001C EnterInteractiveMode dd ?
00000020 ReturnFromMain  dd ?
00000024 GetPeer         dd ?
00000028 GetChild        dd ?
0000002C GetParent       dd ?
00000030 GetConfigurationData dd ?
00000034 AddChild        dd ?
00000038 DeleteComponent dd ?
0000003C GetComponent    dd ?
00000040 SaveConfiguration dd ?
00000044 GetSystemId     dd ?
00000048 GetMemoryDescriptor dd ?
0000004C Signal          dd ?
00000050 GetTime         dd ?
00000054 GetRelativeTime dd ?
00000058 GetDirectoryEntry dd ?
0000005C Open            dd ?
00000060 Close           dd ?
00000064 Read            dd ?
00000068 GetReadStatus   dd ?
0000006C Write           dd ?
00000070 Seek            dd ?
00000074 Mount           dd ?
00000078 GetEnvironmentVariable dd ?
0000007C SetEnvironmentVariable dd ?
00000080 GetFileInformation dd ?
00000084 SetFileInformation dd ?
00000088 FlushAllCaches  dd ?
0000008C TestUnicodeCharacter dd ?
00000090 GetDisplayStatus dd ?
00000094 ARC_FIRMWARE_VECTORS ends

然后用这个定义创建ArcFirmwareVectors结构体,下面是我的:

.data:00468340 ArcFirmwareVectors ARC_FIRMWARE_VECTORS <?>

如果你观察ArcFirmwareVectors的交叉引用会发现有一个全局指针指向它,我把它叫做pArcFirmwareVectors。下面是我的:

.data:00436070 pArcFirmwareVectors dd offset ArcFirmwareVectors

就像我们对pIoFunctions指针做的那样,我们查看所有对pArcFirmwareVectors指针的引用,加上ARC_FIRMWARE_VECTORS的偏移定义,下面是例子:

.text:0041D07F                 mov     eax, pArcFirmwareVectors
.text:0041D084                 call    dword ptr [eax+54h]

变成了:

.text:0041D07F                 mov     eax, pArcFirmwareVectors
.text:0041D084                 call    [eax+ARC_FIRMWARE_VECTORS.GetRelativeTime]

ARC规范描述了所有的函数,据我所知,osloader.exe中的实现是遵循ARC规范的。
=======================================
我刚刚才知道ddk中的ntldr_dbg是调试版本,那么它的代码里应该有很多调试信息,我以后将用它的反汇编作为对原始ntldr反汇编的参考。

[ 本帖最后由 Rinrin 于 2006-10-16 08:24 PM 编辑 ]
作者: Rinrin    时间: 2006-10-18 21:48
分析了BlPrint函数
该函数是osloader.exe中的输出函数
原型为
void BlPrint( char * String,int Value );
String支持的%替换符有如下几种:
%c
%d
%ld
%lu
%lx
%s
%u
%w
%wS
%wZ
%x
最终都会通过ARC的Write()调用来实现向屏幕的输出。
作者: asbai    时间: 2006-10-19 22:32
支持,推荐同时参考 ReactOS 的相关源码。ReactOS 是从驱动层完全兼容 Win2K 的开源系统。设计架构最大限度的模仿了 NT 系列。应该有一定的参考价值。
作者: Rinrin    时间: 2006-10-19 23:36
原帖由 asbai 于 2006-10-19 10:32 PM 发表
支持,推荐同时参考 ReactOS 的相关源码。ReactOS 是从驱动层完全兼容 Win2K 的开源系统。设计架构最大限度的模仿了 NT 系列。应该有一定的参考价值。

确实
我已经找来了TinyKrnl和leaked nt4 source,相信应该有参考价值。
作者: Rinrin    时间: 2006-11-4 11:24
BOOTFONT.BIN文件格式
作者:Rinrin
初始版本:2006.11.4

    BOOTFONT.BIN是NTLDR和SETUPLDR.BIN使用的字体文件,它提供了Windows系统启动阶段其他语言(非英语)的字体点阵。本文针对的BOOTFONT.BIN为中文版本Windows XP With SP2所附带(Windows 2000/XP/2003使用的字库文件完全相同),其他语言的应该类似。
    用Ultraedit-32打开BOOTFONT.BIN,可以发现头四个字节为"MdeT",为什么要用这么奇怪的魔数呢?答案在泄漏的windows nt4源代码中。在bootfont.h中对这个魔数进行了定义:
    +------------------------------------------+
    | // Define signature value.               |
    | //                                       |
    | #define BOOTFONTBIN_SIGNATURE 0x5465644d |
    +------------------------------------------+
    0x5465644d即为"TedM",Windows最开始的本地化版本是日文版,因此由日本的开发人员做了最初的本地化工作,可以从ntos\boot\bootfont\readme.txt中看出来:
    +-----------------------------------------------------------------------+
    | The jpn directory contains the original fntjapan.h that was once      |
    | compiled into the x86 boot loaders, and a program I wrote to generate |
    | bootfont.bin for Japan from it.                                       |
    |                                                                       |
    | - tedm, 7/11/95, Tokyo                                                |
    +-----------------------------------------------------------------------+
    0x04-0x07为语言代码,对于中文则为0x804。
    0x08-0x0b为BOOTFONT.BIN支持的单字节字符个数(NumSbcsChars)。
    0x0c-0x0f为BOOTFONT.BIN支持的双字节字符个数(NumDbcsChars)。
    0x10-0x13为单字节字符点阵在文件中的偏移(SbcsOffset)。
    0x14-0x17为双字节字符点阵在文件中的偏移(DbcsOffset)。
    0x18-0x1b为所有单字节字符点阵数据的总长度(SbcsEntriesTotalSize)。
    0x1c-0x1f为所有双字节字符点阵数据的总长度(DbcsEntriesTotalSize)。
    0x20-0x2b为一个数组,存放了DBCS的前导字节范围,最后以"\0\0"结尾,MAX_DBCS_RANGE的值当前为5(DbcsLeadTable[(MAX_DBCS_RANGE+1)*2])。
    0x2c表示字符的高度(CharacterImageHeight)。
    0x2d表示字符的TopPad(CharacterTopPad)。
    0x2e表示字符的BottomPad(CharacterBottomPad)。
    0x2f表示单字节字符的宽度(CharacterImageSbcsWidth)。
    0x30表示双字节字符的宽度(CharacterImageDbcsWidth)。

    以上为BOOTFONT.BIN的头部,一般来说,接下来就是单字节字符的点阵了。根据头部SbcsOffset的值,找到点阵的开始位置。每一个单字节字符需要19个字节,第一个字节为ASCII码,之后的16个字节即是点阵数据(根据字符的大小8x16,16个字节正好放下),最后两个字节为该字符的Unicode(小端)编码;对于双字节字符点阵,每个字符需要36个字节,头两个字节为GB2312编码,之后32个字节为点阵数据,最后两个字节为该字符的Unicode(小端)编码。

    参考文献:
        The leaked windows nt4 source code

-=[End Of File]=-
作者: lasti    时间: 2006-11-4 17:01
好厉害啊,用IDA,我就是想学这种东西啊
作者: xiangsui    时间: 2006-11-4 17:46
我正想研究这个..哈哈,,,,,,,,,,,,,,,,,,兄台辛苦了 ``
作者: 七猫    时间: 2007-9-17 18:16
http://bbs.wwwfox.net/viewthread.php?tid=113224&extra=page%3D2
里面有NT4的这部分代码下载
作者: screen98    时间: 2007-9-26 11:09
提示: 作者被禁止或删除 内容自动屏蔽
作者: yce    时间: 2007-9-26 11:28
好久没有看到楼主的最新研究成果了
作者: xingxing168    时间: 2009-2-11 23:30
支持楼主,希望加快进度。
作者: haijie1223    时间: 2011-12-1 11:10
留意一下。。。。。。
作者: GD210    时间: 2011-12-6 15:29
这个比较深了。想知道研究出来有哪些实际用途?
作者: 2012rospartner    时间: 2014-8-1 10:47
楼主手上有osloader.pdb吗?现在按照你的方法,怎么下载不了了啊。ms不提供下载了。




欢迎光临 无忧启动论坛 (http://bbs.wuyou.net/) Powered by Discuz! X3.3