The second (and the last so far) article about Master Boot Record and Windows boot process describes what the code stored in this special sector actually does.
MBR Boot Code
The boot code is executed in the real mode of the processor which implies non-flat addressing. Every address consists of two parts: segment and offset. Both parts are two byte long. Physical address corresponding to a given segment:offset pair can be calcualted by the following formula:
PhysicalAddress = Segment * 16 + Offset
Since the sizes of both parts allow to address 65536 segments, each 64 KB in length, and since the fact that the formula suggests 16 bytes long segments, the individual segments of the address space actually overlap with their neighbors. For a given physical address there must exist multiple segment:offset pairs that map to it. For example, “virtual” addresses of 0x0060:0
, 0x0006:0x5A0
and 0x0000:0x600
map to the physical address 0x600
.
Code Relocation
The BIOS loads the Master Boot Record to physical address 0x7C00
and jumps to the first byte of the sector. The main task of the boot code is to load the first sector of the active partition (Volume Boot Record) to 0x7C00
and execute it. The same physical address for the MBR and the VBR makes the initial boot process more transparent; boot code in the VBR might safely think that it is the first code executed after initialization performed by the BIOS (which can be ture since the VBR might be the first sector of the disk. Such disks are called dynamic disks). In case of extended partitions, VBR code may behave similarly to the MBR one – it just finds bootable logical partition, loads its first sector and executes it. Theoreticaly, such a chain might be very long because each of its links can assume to be the first one.
However, the need of loading the VBR into the same address as the MBR forces the initial boot code to copy the whole sector to another location before doing anything else. The code performs this within its first block of instrluctions, displayed in Listing 1. It copies the whole Master Boot Record to buffer starting at physical address 0x600.
At first, the code fills segment registers with zeroes and sets the stack pointer SP
to 0x7C00. This is OK since the stack grows to lower addresses, hence it cannot override the boot code starting at the same address.
Listing 1: The first instructions of the boot code
CopyTo600h: 0x7C00: xor ax, ax 0x7C02: mov ss, ax 0x7C04: mov sp, 7C00h 0x7C07: mov es, ax 0x7C09: mov ds, ax 0x7C0B: mov si, 7C00h 0x7C0E: mov di, 600h 0x7C11: mov cx, 200h 0x7C14: cld 0x7C15: rep movsb 0x7C17: push ax 0x7C18: push InitBootPartitionSearch 0x7C1B: retf InitBootPartitionSearch:
The MOVSB
instruction is used to copy 512 bytes from 0x0000:0x7C00
to 0x0000:0x600
. Then, the code jumps to 0x0000:0x61C
, which is just behind the RETF
instruction shown in Listing 1. After the jump, the boot code can now start to care about its main task – loading and executing Volume Boot Record of the active partition.
Active Partition Recognition and Error Message Printing
As shown in Listing 2, the boot code searches for active partition in a straightforward manner. At first, it sets the BP
register to point to the boot flags field of the first partition entry. Then, it determines value of the most signifficant bit (MSB) of the field. If the bit is set, the code assumes the partition is active, and jumps to the CheckLBAExtension
label. That effectively stops the search operation. If the MSB is not set and the boot flags field is nonzero, the code treats the partition entry as invalid, uses the InvalitPartitionTableError
label to display appropriate error message and stops the processor. Zero value of the field means inactive partition; the code advances to the next entry by increasing the BP
register.
Listing 2: Searching for active partition
InitBootPartitionSearch: 0x061C: sti 0x061D: mov cx, 4 0x0620: mov bp, 7BEh SearchPartitionTable: 0x0623: cmp byte ptr [bp+0], 0 0x0627: jl short CheckLBAExtension 0x0629: jnz InvalitPartitionTableError 0x062D: add bp, 10h 0x0630: loop SearchPartitionTable 0x0632: int 18h
In case no active partition is found, which is indicated by the fact the CX
register reaching zero so the LOOP
instruction does not jump, the interrupt 0x18 is invoked. This causes the processor to either stop or reset.
Error messages are printed through the service 0xE of the interrupt 0x10. The service displays character stored in the AL
register to screen. Error messages are null terminated strings. The print operation, represented by the PrintErrorMessageCharacter
loop, continues until the null character is reached. Position in the string is marked by the DS:SI
register pair. When the end of the message is found, the boot code starts to execute the HTL
instruction in an infinite loop. The code is shown in Listing 3.
Listing 3: Displaying error messages
PrintErrorMessage: 0x073E: xor ah, ah 0x0740: add ax, 700h 0x0743: mov si, ax PrintErrorMessageCharacter: 0x0745: lodsb 0x0746: cmp al, 0 0x0748: jz short Halt 0x074A: mov bx, 7 0x074D: mov ah, 0Eh 0x074F: int 10h 0x0751: jmp short PrintErrorMessageCharacter Halt: 0x0753: hlt 0x0754: jmp short Halt
To display any error message and halt the processor, the boot code just needs to set the AL
register to “point” to the message string (the “pointer” is actually an offset relative to the current address of the boot code – 0x0000:0x600
), and jump to PrintErrorMessage
label. This scenario holds for every error message, including report of invalid partition table (see Listing 4).
Listing 4: Printing error message for invalid partition entry
InvalitPartitionTableError: 0x073B: mov al, 63h PrintErrorMessage: ... aInvalidPartitionTable: 0x0763: db 'Invalid partition table',0
Reading the Volume Boot Record
As mentioned above, there exist at least two methods how to read data from a hard drive: via CHS address and through the LBA extension. The LBA extension was added later and it is a good practice to check whether it is supported by the hard drive. The CHS method is always present, however, its addressing capabilities are very limited, as discussed previously.
The code related to reading of the hard drive starts at the CheckLBAExtension
label (Listing 5). At first, presence of the LBA extension is checked. The code uses service 0x41 of the interrupt 0x13. The BIOS returns information about available extensions in the CX
register. If bit 0 of the register is set, the LBA extension is present. The boot code saves the state of the extension to [BP+0x10]
. As can be seen in Listing 5, the code also sets [BP+0x11]
to 5. It is the number of attempts the it attempts to read Volume Boot Record of the active partition.
Listing 5: Checking for presence of LBA extension
CheckLBAExtension: 0x0634: mov [bp+0], dl 0x0637: push bp 0x0638: mov byte ptr [bp+11h], 5 0x063C: mov byte ptr [bp+10h], 0 0x0640: mov ah, 41h 0x0642: mov bx, 55AAh 0x0645: int 13h 0x0647: pop bp 0x0648: jb short ReadVBR 0x064A: cmp bx, 0AA55h 0x064E: jnz short ReadVBR 0x0650: test cx, 1 0x0654: jz short ReadVBR 0x0656: inc byte ptr [bp+10h] ReadVBR:
If the LBA extension check fails (BIOS returns an error), the boot code assumes the LBA extension is not present, and continues the execution.
After the extension check, the boot code attempts to read the Volume Boot Record of the active partition (see Listing 6). It selects the read method according to the value stored at [BP+0x10]
. In case of the CHS mode, the CHS address of the Volume Boot Record is loaded from the partition entry, and the value in the boot flags field is used to identify the hard drive (0x80 menas the first hard drive). When the LBA method is selected, LBA address of the VBR is loaded from the partition entry. The boot code always stores the data of the VBR at 0x7C00.
Listing 6: Reading the Volume Boot Record
ReadVBR: 0x0659: pushad 0x065B: cmp byte ptr [bp+10h], 0 0x065F: jz short ReadVBRCHS ReadVBRLBA: 0x0661: push large 0 ; LBA address (high 32 bits) 0x0667: push large dword ptr [bp+8] ; LBA address (low 32 bits) 0x066B: push 0 ; Memory address (segment) 0x066E: push 7C00h ; Memory address (offset) 0x0671: push 1 ; Number of sectors 0x0674: push 10h ; Extended structure size 0x0677: mov ah, 42h 0x0679: mov dl, [bp+0] 0x067C: mov si, sp 0x067E: int 13h 0x0680: lahf 0x0681: add sp, 10h 0x0684: sahf 0x0685: jmp short CheckSuccess ReadVBRCHS: 0x0687: mov ax, 201h 0x068A: mov bx, 7C00h 0x068D: mov dl, [bp+0] 0x0690: mov dh, [bp+1] 0x0693: mov cl, [bp+2] 0x0696: mov ch, [bp+3] 0x0699: int 13h CheckSuccess:
After the read operation is performed, time comes to check whether it succeeded. The operation is performed by the code displayed in Listing 7. It starts at the CheckSuccess
label. If a failure is detected, the boot code resets the hard drive (see the ResetDiskSystem
label) and makes another attempt to read the VBR. Value at [BP+0x11]
is decremented after each unsuccessful attempt. When the value reaches zero, the boot code gives up and uses the ErrorLoadingOperatingSystem
label to display appropriate error message and halt the processor.
Listing 7: Checking success of the read operation
CheckSuccess: 0x069B: popad 0x069D: jnb short KeyboardInit 0x069F: dec byte ptr [bp+11h] 0x06A2: jnz short ResetDiskSystem 0x06A4: cmp byte ptr [bp+0], 80h 0x06A8: jz ErrorLoadingOperatingSystem 0x06AC: mov dl, 80h 0x06AE: jmp short CheckLBAExtension ResetDiskSystem: 0x06B0: push bp 0x06B1: xor ah, ah 0x06B3: mov dl, [bp+0] 0x06B6: int 13h 0x06B8: pop bp 0x06B9: jmp short ReadVBR VBRReadSuccess: 0x06BB: cmp ds:7DFEh, 0AA55h 0x06C1: jnz short MBRSignatureInvalid KeyboardInit: ... MBRSignatureInvalid: 0x0731: mov al, 9Ah 0x0734: jmp short PrintErrorMessage ErrorLoadingOperatingSystem: 0x0736: mov al, 7Bh 0x0739: jmp short PrintErrorMessage ... 0x077B: db 'Error loading operating system', 0 0x079A: db 'Missing operating system', 0
If the read operation succeeds, the boot code reaches the KeyboardInit
label. And if the Volume Boot Record contains signature 0xAA55 at its end, the main task of the MBR is now complete; the first sector of the active partition is stored in memory at address 0x0000:0x7C00
. Before executing it, an attempt is made to perform two optional steps: keyboard initialization and VBR content hashing.
Keyboard Initialization
The 8042 keyboard controller is a device that, apart from keyboard services, offers features none would have expected. It can be commanded to reset the processor immediately. And it can enable the famous A20 line that allows real mode code to address physical memory above 1 megabyte. Because the operating system usually wants to boot successfully and not to keep reseting the processor in an infinite loop, the reset ability of the controller is just an interesting feature, at least from this point of view. Situation is different with the A20 line.
The keyboard initialization code is shon in Listing 8. The boot code first checks whether the keyboard is able to receive commands. This is the task for the WaitKeyboardReady
function, shown in Listing 9. If the function returns nonzero value, the controller cannot receive commands at the moment. In such a case, the boot code skips the whole initialization and attempts to find Trusted Platform Module (TPM) or a device with similar capabilities.
Keyboard initialization code works with two I/O ports related to the 8042 controller: port 64h, called a command port, and port 60h, a data port. The communication protocol is the following:
- Send a command to the command port.
- Wait until the controller is ready to receive data for the command.
- Send command data to the data port. This step is ommited in case the command does not require any data to be sent.
- Wait until the keyboard controller processes the command.
The boot code sends command 0xD1 with data byte 11011111b. The command enables keyboard interrupt and the A20 line. Since this time, when a key is pressed or released, the keyboard controller generates an interrupt to report the event. Additionaly, it is possible to address physical memory above 1 megabyte.
Listing 8: Keyboard initialization
KeyboardInit: 0x06C3: push word ptr [bp+0] 0x06C6: call WaitKeyboardReady 0x06C9: jnz short CheckTCGStatus 0x06CB: cli 0x06CC: mov al, D1h 0x06CE: out 64h, al 0x06D0: call WaitKeyboardReady 0x06D3: mov al, 11011111b 0x06D5: out 60h, al 0x06D7: call WaitKeyboardReady 0x06DA: mov al, FFb 0x06DC: out 64h, al 0x06DE: call WaitKeyboardReady 0x06E1: sti CheckTCGStatus:
To determine whether the keyboard controller is ready to receive commands, the WaitKeyboardReady
routine repeatedly checks keyboard status. This is done by reading a byte from the port 0x64 which is, in the read case, sometimes called a keyboard status. If the bit 1 is set, the controller’s input buffer is full, hence the device cannot accept and process any commands right now. Otherwise, the controller is ready to receive commands and data.
Listing 9: Waiting for the controller
WaitKeyboardReady proc near 0x0756: sub cx, cx ReadKeyboardStatus: 0x0758: in al, 64h 0x075A: jmp short $+2 0x075C: and al, 2 0x075E: loopne ReadKeyboardStatus 0x0760: and al, 2 0x0762: retn
Trusted Platform Module
The code that works with TPM (or another device which supports TCG interface) is quite straightforward. It is displayed in Listing 10. At first, it checks whether a device capable of cryptographics services, is available. If so, the code determines whether the device is able to hash a given area of physical memory. If yes, a hash of the Volume Boot Record is computed and probably stored inside the cryptographic device. Communication with the device is performed via the interrupt 0x1A.
Listing 10: Hashing of the Volume Boot Record
ReadTCGStatus: 0x06E2: mov ax, 0BB00h 0x06E5: int 1Ah CheckStatus: 0x06E7: and eax, eax 0x06EA: jnz short ExecuteVBR 0x06EC: cmp ebx, 'APCT' 0x06F3: jnz short ExecuteVBR 0x06F5: cmp cx, 102h 0x06F9: jb short ExecuteVBR HashVBR: 0x06FB: push large 0BB07h 0x0701: push large 200h 0x0707: push large 0 0x070D: push ebx 0x070F: push ebx 0x0711: push ebp 0x0713: push large 0 0x0719: push large 7C00h 0x071F: popad 0x0721: push 0 0x0724: pop es 0x0725: int 1Ah ExecuteVBR: 0x0727: pop dx 0x0728: xor dh, dh 0x072A: jmp far ptr 0000:7C00h
When the code finishes with the cryptography, it finally jumps to address 0x7C00 in order to execute the VOlume Boot Record.
Links
- An Examination of the Windows Vista MBR
- Display a character on the screen (INT 0x10)
- Check LBA Extension (INT 0x13)
- Read Data From Had Drive Through CHS Mode (INT 0x13)
- Read Data From Had Drive Through LBA Extension (INT 0x13)
- Reset Disk System (INT 0x13)
- TCG PC Client Specific Implementation Specification For Conventional BIOS