|
推荐
楼主 |
发表于 2006-10-16 20:19:13
|
只看该作者
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 编辑 ] |
|