ntldr 逆向工程

  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+1E
.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_loop
.text:0040128C                 jmp     short infinite_loop
.text:0040128E; ---------------------------------------------------------------------------
.text:0040128E
.text:0040128E @@1:                                  ; CODE XREF: DoGlobalInitialization+21
.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/Documentation/Hardware/Machines/ARC/riscspec.pdf。
    要定位这张向量表,让我们回到NtProcessStartup里,找到紧接着DoGlobalInitialization的调用。这个函数初始化了固 件调用向量表。我把它命名为InitArcFirmwareVectors。下面是我的反汇编结果,可以看到只有osloader使用的向量被实现了:

.text:0040757D InitArcFirmwareVectors proc near      ; CODE XREF: NtProcessStartup+24
.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
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
NTLDR文件是win nt/win200/WinXP的引导文件,当此文件丢失时启动系统会提示"NTLDR is missing..."并要求按任意键重新启动,不能正确进入系统 。所以应该在系统正常的时候给予备份。 NTLDR文件是做什么的?我们如何来修复NTLDR文件类型的故障呢? NTLDR文件的是一个隐藏的,只读的系统文件,位置在系统盘的根目录,用来装载操作系统。 一般情况系统的引导过程是这样的: 代码 1、电源自检程序开始运行 2、主引导记录被装入内存,并且程序开始执行 3、活动分区的引导扇区被装入内存 4、NTLDR从引导扇区被装入并初始化 5、将处理器的实模式改为32位平滑内存模式 6、NTLDR开始运行适当的小文件系统驱动程序。 小文件系统驱动程序是建立在NTLDR内部的,它能读FAT或NTFS。 7、NTLDR读boot.ini文件 8、NTLDR装载所选操作系统 *如果NT/XP被选择,, NTLDR运行Ntdetect.com 对于其他的操作系统, NTLDR装载并运行Bootsect.dos然后向它传递控制。 windows NT过程结束。 9.Ntdetect.com 搜索计算机硬件并将列表传送给NTLDR,以便将这些信息写进HKE Y_LOCAL_MACHINEHARDWARE中。 10.然后NTLDR装载Ntoskrnl.exe,Hal.dll和系统信息集合。 11.Ntldr搜索系统信息集合,并装载设备驱动配置以便设备在启动时开始工作 12.Ntldr把控制权交给Ntoskrnl.exe,这时,启动程序结束,装载阶段开始 当此文件丢失时,我们可以从安装光盘上进行提取,方法是: 1、进入系统故障恢复控制台。 2、转到C盘。 3、输入"copy X:\I386\NTLDR c:\"(说明:X为光驱盘符)并回车, 如果系统提示要否覆盖则按下"Y",之后输入exit命令退出控制台重新启动即可。 症状 当试图在一台运行 Microsoft Windows 95、Microsoft Windows 98 或 Microsoft Windows Millennium Edition (Me) 的计算机上安装 Windows XP 或者升级到 Windows XP 时,可能在安装过程中的第一次重新启动后接收到下面的错误信息: NTLDR is missing Press any key to restart 此行为只有当 Windows 95、Windows 98 或者 Windows Me 安装于使用 FAT32 文件系统的大容量驱动器之上时才会出现。 原因 如果克隆现有的 Windows 95、Windows 98 或 Windows Me 的安装,然后应用于与克隆副本的源驱动器有不同布局的驱动器上,则可能发生此行为。 一种可能的情况如下所示:正在一个 4 GB 的驱动器上运行 Windows 98。在升级后,例如升级到一块 30 GB 的硬盘后,使用第三方磁盘映像实用程序对 Windows 98 安装进行镜像操作并应用镜像到新驱动器上。在晚些时候,接着在 Windows 98 的克隆映像上安装 Windows XP 以升级到 Windows XP。 要发生此行为,则下面的条件必须存在: ? 系统/启动分区用 FAT32 文件系统格式化。 ? 计算机通过使用 INT-13 扩展(大于 7.8 GB 且分区表内有 0C 的 System-ID 类型的分区)启动。 ? 由于克隆过程,造成了 FAT32 BIOS Parameter Block (BPB) 中的头(边)值与物理驱动器的布局不匹配。 Windows 95、Windows 98 或者 Windows Me 启动代码忽略 BPB 中的头值,并且即使在值无效时仍启动程序。但是,Windows 2000 和 Windows XP 中的启动代码需要这个值,如果此值无效则启动过程不会成功。 解决方案 若要解决此问题,请改正 FAT32 BPB 中的无效头(边)值以使得 Windows XP 启动过程得以继续。更新此字段最简单的方法是通过使用下面的过程重写 Windows 95、Windows 98 或者 Windows Me 的启动代码: 代码 一. 使用包含 Sys.com 文件(默认情况下包含此文件)的 Windows 95、Windows 98 或者 Windows Me 启动盘重新启动计算机。 二. 在系统驱动器的根目录中制作 msdos.sys 文件的备份副本。为此,请从命令提示处键入下面的命令: attrib -h -r -s c:\msdos.sys rename msdos.sys *.sys 三. 在命令提示符

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值