阅读:3037回复:4
NTLDR Reverse Engineering
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反汇编的参考。 |
|
沙发#
发布于:2007-09-19 14:28
相关的东西reteam.org上面有一些,希望对大家有帮助
|
|
板凳#
发布于:2007-09-26 18:01
好的,能不能再多介绍点reverse的好站,这些站肯定有不少文档可以用来学习
|
|
地板#
发布于:2007-10-29 09:52
顶。。。好东西
|
|
地下室#
发布于:2007-10-29 10:44
这篇翻译的不太好,请大家见谅了
|
|