Reverse Engineering RET Homepage RET Members Reverse Engineering Projects Reverse Engineering Papers Reversing Challenges Reverser Tools RET Re-Search Engine Reverse Engineering Forum Reverse Engineering Links

Go Back   Reverse Engineering Team Board > Reverse Engineering Board > Reverse Code Engineering
FAQ Members List Calendar Search Today's Posts Mark Forums Read

Reply
 
Thread Tools Display Modes
  #1  
Old 03-04-2006, 10:46 PM
hasper hasper is offline
Junior Member
 
Join Date: Mar 2006
Posts: 1
Default

I'm diving into NTLDR for the first time, trying to understand how it handles hiberfil.sys. I've done a lot of reverse engineering of binaries, but haven't dove into this area before. Any chance that someone with some NTLDR experience can show me the ropes on that binary? I typically use IDA 4.8 for my static analysis... haven't played with 16 bit code before. Thanks in advance.
Reply With Quote
  #2  
Old 03-05-2006, 08:29 PM
rwid rwid is offline
Member
 
Join Date: Jun 2005
Location: Sydney, Australia
Posts: 41
Default

hi hasper,

Give me a few days and i'll post what I know about ntldr and the approach i've used to disassemble/reverse it.
Reply With Quote
  #3  
Old 03-20-2006, 01:58 AM
rwid rwid is offline
Member
 
Join Date: Jun 2005
Location: Sydney, Australia
Posts: 41
Default

Oops did I say a few days? I meant a few weeks

I don't have time to post it all right now, but I'll make a start...


Okay the ntldr binary I'm looking at comes from XP with SP2 installed.
Md5 checksum: 9EC920F4179D45AF3A6638A083D39C85

According to the internal version number of osloader.exe (the embedded PE file in ntldr) its version is "5.1.2600.2180 (xpsp_sp2_rtm.040803-2158)". Just be aware that things might be a little different in other versions of the file.

"Understanding Win2k Sources - Part 1 by AndreaGeddon" gives a good overview of ntldr's execution flow and code layout, even though it deals with Win2k's code and this is XP SP2. I'll just divulge some of the implementation details that I have reversed and found to be helpful.

There are two parts to ntldr. A 16-bit flat binary image, much like a .COM file, and a 32-bit PE file image, which contains the bulk of the loader responsibility. I found it easier to extract the PE file from ntldr and study the two parts independently in IDA. Using a hex editor, search for the 'MZ' or 'PE' signature in ntldr, and from the 'MZ' signature cut & paste the remainder of ntldr to a separate file (this is the osloader.exe PE image). I save it as 'osloader.exe' because the internal name value in the PE file's version information states this is its original name.

For the most part, you can ignore the 16-bit code. Essentially its task is to set up the execution environment in which the 32-bit osloader.exe resides and executes. It does however implement a set of protected mode functions that wrap around real mode code to interface with the BIOS. Osloader.exe uses these functions to perform its I/O, as there are no drivers up and running at this point. The protected mode GDT and IDT tables are originally located here too... mmm I guess maybe the 16-bit code is worth a look after all.

The I/O functions mentioned above are accessed by osloader.exe through a pointer to a table of pointers to these functions. This pointer is the 2nd DWORD in the 'BootContextRecord' structure AndreaGeddon mentions in his paper. Knowing these function definitions is of course helpful in further reversing osloader.exe code. From my knowledge gathered so far, here is an incomplete C definition of that I/O function table:

Code:
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; /* This function has no parameters and no return
 * * * * * * * * * * * * * *value. It performs a quick check for some Advanced
 * * * * * * * * * * * * * *Power Management versions and interfaces, although
 * * * * * * * * * * * * * *no feedback is given. Weird. */
 * *// ... (?)
} IO_FUNCTIONS, *PIO_FUNCTIONS;
As you can see, I gave them names that basically describe what they are used for, though I haven't bothered to research the exact parameters and behaviours of all these functions.

Load osloader.exe into IDA, wait for it to idle, and then take a look at the entry point code. This is the function 'NtProcessStartup' AndreaGeddon's paper refers to. So rename the entry point. The sole argument to NtProcessStartup is 'arg_0', which is the pointer to the 'BootContextRecord' structure. So rename 'arg_0' to something like 'pBootContext'.

Skip the very first call made in 'NtProcessStartup' because it just makes an allocation on the stack for local variables. The second call is made to the 'DoGlobalInitialization' function (from here on, just look at AndreaGeddon's paper for these names). DoGlobalInitialization too has just one argument - 'pBootContext'. It is in this function where the pointer to the I/O function table is assigned to a global pointer, with which osloader.exe code accesses those functions. So you need to identify the pointer. Take a look at the following DoGlobalInitialization snippet from my disassembly:

Code:
.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]
The call above to pIoFunctions->VideoServices is made through an intermediary register (eax in this case) which was used to assign the value of pBootContext->pIoFunctions to the global pointer 'pIoFunctions'. So you can easily identify the global pointer 'pIoFunctions' here.

Create a structure definition for IO_FUNCTIONS in IDA. It should look like this:

Code:
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 ? // Unknown functionality.
00000048 field_48 * * * *dd ? // Unknown functionality.
0000004C IO_FUNCTIONS * *ends
Now for all cross-references IDA has for the pIoFunctions pointer, assign the structure offset in IO_FUNCTION to the calls made through pIoFunction (they will actually be made through an intermediary register). For example, the following code:

Code:
.text:0040656D * * * * * * * * mov * * eax, pIoFunctions
.text:00406572 * * * * * * * * call * *dword ptr [eax+1Ch]
will become:

Code:
.text:0040656D * * * * * * * * mov * * eax, pIoFunctions
.text:00406572 * * * * * * * * call * *[eax+IO_FUNCTIONS.GetDateTime]
So hopefully now you have identified all of these I/O function calls made by the osloader.exe code. It's something helpful.



Actually there is another function table used by osloader.exe which is more useful and MUCH better documented... for now you'll just have to wait and see...

...to be continued...
Reply With Quote
  #4  
Old 03-21-2006, 10:36 AM
rwid rwid is offline
Member
 
Join Date: Jun 2005
Location: Sydney, Australia
Posts: 41
Default

Okay about this other function table I mentioned. It exists due to the fact that the boot loader code was written not only for Intel x86-based architectures, but also for RISC-based architectures. Firmware exists in these RISC-based architectures that will aid the boot loader during the boot process. The firmware adheres to the ARC (Advanced RISC Computing) specification, which among other things defines a firmware function vector table that the firmware exposes to the boot loader. The boot loader uses these functions to perform typically required operations during booting and boot configuring.

However, this firmware does not exist in the Intel x86-based architectures. Therefore osloader.exe implements the firmware function vectors in its own code. The ARC specification documentation fully describes these function vectors, so we can use the documentation to identify each of the function vectors, their parameters, return values, and their functionality. It's that easy. All that is needed is to locate the firmware function vector table, and then we can identify the calls made to these vectors.

Here is the specification --> ARC Specification Documentation


...to be continued... again...
Reply With Quote
  #5  
Old 03-21-2006, 10:53 PM
rwid rwid is offline
Member
 
Join Date: Jun 2005
Location: Sydney, Australia
Posts: 41
Default

To locate the firmware function vector table, go back to NtProcessStartup again and look at the call immediately following the call to DoGlobalInitialization. This function initializes the firmware function vector table. I gave it the name 'InitArcFirmwareVectors'. Here is my disassembly of InitArcFirmwareVectors. You can see here that only the vectors used by osloader.exe are actually implemented:

Code:
.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
From this disassembly you should be able to recognize the offset to the ArcFirmwareVectors structure in your own disassembly. Create the following structure definition in IDA:

Code:
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
Then use this structure definition to create the ArcFirmwareVectors structure. Here's mine:

Code:
.data:00468340 ArcFirmwareVectors ARC_FIRMWARE_VECTORS <?>
If you take a look at the cross-references to ArcFirmwareVectors you will see a global pointer that holds the offset to this structure. Calls to the firmware function vectors are made through this pointer. I call it 'pArcFirmwareVectors'. Here's mine:

Code:
.data:00436070 pArcFirmwareVectors dd offset ArcFirmwareVectors
Just like we did for the pIoFunctions pointer, we look to all the cross-references IDA has for the pArcFirmwareVectors pointer, and assign the structure offset in ARC_FIRMWARE_VECTORS to the calls made through pArcFirmwareVectors. An example again, the following code:

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

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

The ARC documentation describes everything about these functions, and as far as I'm aware, osloader.exe's implementation complies with the specification.

Now there is a big load off your back while reversing the code.


mmm I'm trying to think what other info might be useful for reversing ntldr... (still thinking)
Reply With Quote
  #6  
Old 03-28-2006, 08:45 PM
rwid rwid is offline
Member
 
Join Date: Jun 2005
Location: Sydney, Australia
Posts: 41
Default

I've just realized the debug version of ntldr 'ntldr_dbg' can be found in the xp ddk. It's easier to follow since there are various debug strings referenced in the code. I'm now using it as a cross reference for the original ntldr disassembly.
Reply With Quote
  #7  
Old 03-31-2006, 09:02 AM
Devine9 Devine9 is offline
Administrator
 
Join Date: Dec 2002
Posts: 180
Default

Thanks a lot for these posts rwid.
This has become a great thread.

-DR
Reply With Quote
  #8  
Old 04-16-2006, 04:40 AM
Ar-ras Ar-ras is offline
Junior Member
 
Join Date: Apr 2006
Posts: 2
Default

I would like to analyze the source of windows too
When I downloaded it, It was when it was published..
Now I can't find the source code anywhere
Is it possible that somebody uploads it on rapidshare or something like this?

Thx Ar-ras
Reply With Quote
  #9  
Old 11-02-2006, 06:22 AM
Pinczakko Pinczakko is offline
Member
 
Join Date: Aug 2004
Location: Taka Bonerate National Park, Indonesia
Posts: 7
Default

Hi rwid,

Can you post the dump of the reverse-engineered bootloader (precisely the bootsector code) code prior to execution of NTLDR in segment 2000h?

I've been reversing my system (WinXP SP2) and I'm stuck in the HDD sector calculation routine (needed to load the remaining NTLDR to segment 2000h in real mode). Hopefully finished tonight

Wish me luck .

I need your findings for comparison purposes .

Anyway, thx for your very helpful posting in this thread.
Reply With Quote
  #10  
Old 11-02-2006, 07:16 AM
rwid rwid is offline
Member
 
Join Date: Jun 2005
Location: Sydney, Australia
Posts: 41
Default

Quote:
Originally posted by Pinczakko@Nov 2 2006, 08:22 PM
Hi rwid,

Can you post the dump of the reverse-engineered bootloader (precisely the bootsector code) code prior to execution of NTLDR in segment 2000h?

I've been reversing my system (WinXP SP2) and I'm stuck in the HDD sector calculation routine (needed to load the remaining NTLDR to segment 2000h in real mode). Hopefully finished tonight*

Wish me luck* .

I need your findings for comparison purposes* .

Anyway, thx for your very helpful posting in this thread.
[snapback]1679[/snapback]

Hi dude, see the attachment at the end of this post...

The document contains a disassembly of my bootsector and ntfs bootloader sectors, along with some comments. Hope it helps.

btw these threads are old... i haven't worked on this stuff for ages now (thought no one cared :P) oh well
Reply With Quote
Reply


Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Forum Jump





Powered by vBulletin® Version 3.6.4
Copyright ©2000 - 2019, Jelsoft Enterprises Ltd.