z.b.Azy
驱动牛犊
驱动牛犊
  • 注册日期2006-03-11
  • 最后登录2013-04-29
  • 粉丝0
  • 关注0
  • 积分263分
  • 威望95点
  • 贡献值0点
  • 好评度91点
  • 原创分2分
  • 专家分0分
阅读:2972回复:4

NTLDR Reverse Engineering

楼主#
更多 发布于:2007-09-19 14:24
NTLDR Reverse Engineering
原始地址:http://www.reteam.org/board/index.php?showtopic=351&st=0&p=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/Documentation/Hardware/Machines/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反汇编的参考。
z.b.Azy
驱动牛犊
驱动牛犊
  • 注册日期2006-03-11
  • 最后登录2013-04-29
  • 粉丝0
  • 关注0
  • 积分263分
  • 威望95点
  • 贡献值0点
  • 好评度91点
  • 原创分2分
  • 专家分0分
沙发#
发布于:2007-09-19 14:28
相关的东西reteam.org上面有一些,希望对大家有帮助
poize
驱动牛犊
驱动牛犊
  • 注册日期2005-08-17
  • 最后登录2013-09-13
  • 粉丝0
  • 关注0
  • 积分2分
  • 威望32点
  • 贡献值0点
  • 好评度29点
  • 原创分0分
  • 专家分0分
板凳#
发布于:2007-09-26 18:01
好的,能不能再多介绍点reverse的好站,这些站肯定有不少文档可以用来学习
chinaruto
驱动牛犊
驱动牛犊
  • 注册日期2007-09-19
  • 最后登录2008-01-26
  • 粉丝0
  • 关注0
  • 积分210分
  • 威望22点
  • 贡献值0点
  • 好评度21点
  • 原创分0分
  • 专家分0分
地板#
发布于:2007-10-29 09:52
顶。。。好东西
Rinrin
驱动牛犊
驱动牛犊
  • 注册日期2005-07-26
  • 最后登录2011-06-06
  • 粉丝0
  • 关注0
  • 积分0分
  • 威望3点
  • 贡献值0点
  • 好评度2点
  • 原创分0分
  • 专家分0分
地下室#
发布于:2007-10-29 10:44
这篇翻译的不太好,请大家见谅了
游客

返回顶部