Difference between revisions of "Boot Process"
(→Overview) |
Mborgerson (talk | contribs) |
||
Line 79: | Line 79: | ||
</pre> | </pre> | ||
− | === RC4 Decryption of the 2BL === | + | === MCPX 1.0: RC4 Decryption of the 2BL === |
− | + | Version 1.0 of the ROM uses RC4 to decrypt the 2BL. | |
− | ==== Stage 1 ==== | + | ==== Stage 1: Key Scheduling ==== |
− | + | The [https://en.wikipedia.org/wiki/RC4#Key-scheduling_algorithm_.28KSA.29 RC4 Key-Scheduling Algorithm] is used to initialize the RC4 “S” array, first initializing the identity permutation (writing 1, 2, ..., 255 to 0x8f000 to 0x850FF), then processed in a way similar to the PRGA to mix in the key. | |
<pre> | <pre> | ||
− | + | uint8_t *s = (uint8_t *)0x8f000; | |
− | + | uint32_t i; | |
− | + | for (i = 0; i <= 255; i++) { | |
− | + | s[i] = i; | |
− | |||
} | } | ||
− | |||
− | = | + | uint8_t *key = (uint8_t *)0xffffffa5; /* ROM offset 0x1a5. */ |
+ | uint8_t j, t; | ||
− | + | /* It is unclear why values s[0x100..0x101] are being set to 0. They are | |
+ | * not modified by the code, but later these will be be used as the initial | ||
+ | * i, j values in the PRGA. | ||
+ | */ | ||
+ | s[0x100] = 0x00; | ||
+ | s[0x101] = 0x00; | ||
− | + | for (i = 0, j = 0; i <= 255; i++) { | |
− | + | j = j + s[i] + key[i%16]; | |
− | |||
− | |||
− | |||
− | + | /* Swap s[i] and s[j] */ | |
− | + | t = s[i]; | |
− | + | s[i] = s[j]; | |
− | + | s[j] = t; | |
− | |||
− | |||
} | } | ||
</pre> | </pre> | ||
− | ==== Stage | + | ==== Stage 2: PRGA ==== |
− | + | The [https://en.wikipedia.org/wiki/RC4#Pseudo-random_generation_algorithm_.28PRGA.29 RC4 Pseudo-random generation algorithm (PRGA)] is then used to decrypt the 2BL from 0xFFFF9E00, storing the decrypted 2BL at 0x00090000. It is 24KiB in size. | |
<pre> | <pre> | ||
− | + | uint8_t *encrypted = (uint8_t*)0xFFFF9E00; /* 2bl */ | |
− | + | uint8_t *decrypted = (uint8_t*)0x90000; /* Decrypted 2bl Destination */ | |
− | + | uint32_t pos; | |
− | |||
− | + | /* As noted above, s[0x100..0x101] were set to 0 earlier, but have not been | |
+ | * modified since. The RC4 algorithm defines i and j both to be set to 0 | ||
+ | * before PRGA begins. */ | ||
+ | i = s[0x100]; | ||
+ | j = s[0x101]; | ||
− | i = | + | for (pos = 0; pos < 0x6000; pos++) { |
− | j = | + | /* Update i, j. */ |
+ | i = (i + 1) & 0xff; | ||
+ | j += s[i]; | ||
− | + | /* Swap s[i] and s[j]. */ | |
− | + | t = s[i]; | |
+ | s[i] = s[j]; | ||
+ | s[j] = t; | ||
− | + | /* Decrypt message and write output. */ | |
− | + | decrypted[pos] = encrypted[pos] ^ s[ s[i] + s[j] ]; | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
} | } | ||
</pre> | </pre> | ||
− | ==== Stage | + | ==== Stage 3: Verification ==== |
− | + | Now that the Second-Stage Bootloader has been loaded, a quick sanity-check is performed: a “magic” signature is verified. If the signature doesn’t match, control goes to the error handler. If the signature does match, the code will jump to the 2bl entry point, which is given by the first dword of the decrypted 2bl. | |
<pre> | <pre> | ||
− | + | if (get_memory_dword(0x95FE4) == MAGIC_NUMBER) { | |
− | + | eip = get_memory_dword(0x900000); | |
− | + | } else { | |
− | + | // Else, things have gone wrong | |
− | + | eip = 0xFFFFFF94; | |
− | |||
− | |||
} | } | ||
</pre> | </pre> | ||
Line 166: | Line 161: | ||
The RC4 algorithm was included as part of MCPX 1.0 and seems to work fine with BIOS versions 3944, 4034, and 4134. | The RC4 algorithm was included as part of MCPX 1.0 and seems to work fine with BIOS versions 3944, 4034, and 4134. | ||
+ | |||
+ | === MCPX 1.1: TEA Decryption of the 2BL === | ||
+ | |||
+ | {{FIXME}} | ||
== 2BL == | == 2BL == | ||
Line 396: | Line 395: | ||
* [http://hackspot.net/XboxBlog/?p=1 Understanding the Xbox boot process] | * [http://hackspot.net/XboxBlog/?p=1 Understanding the Xbox boot process] | ||
− | * [https://mborgerson.com/deconstructing-the-xbox-boot-rom Deconstructing the Boot ROM] | + | * [https://mborgerson.com/deconstructing-the-xbox-boot-rom Deconstructing the Boot ROM] |
Revision as of 17:51, 29 May 2017
Contents
Overview
The Xbox has a 256 KB ROM containing the startup animation and sound, as well as the Xbox kernel, which contains a stripped down version of the Windows 2000 (NT 5.0) microkernel, the HAL, filesystems, as well as HD and DVD drivers.
When the Xbox is turned on, the software in ROM is decompressed into RAM, and the kernel initializes the hardware. Because there are no audio or video drivers in the kernel, the startup code plays the animation and sound by accessing the registers of the hardware directly. As soon as the Xbox logo is on the display, the kernel unlocks the hard disk and checks whether there is a valid game medium in the DVD drive. If not, the file xboxdash.xbe gets loaded from partition #3. In either case, the Microsoft logo is shown below the Xbox logo and the executable is started. If an error occurs (no/wrong hard disk, wrong signature, ...), the boot loader shows an error screen and halts.
MCPX
Certain things are still missing, for example, getting the CPU to 32 bit protected mode and enabling caching.[FIXME]
Xcodes
The xcode interpreter is common through both versions of the MCPX ROM. The high level interpretation of the MCPX ROM might look like this:
void xcode_interpreter() { int run_xcodes = 1; uint32_t eip = 0xff000080; // Not really EIP. This is just a pointer to the next xcode uint32_t result, scratch = 0; while (run_xcodes) { opcode = get_memory_byte(eip); operand_1 = get_memory_dword(eip+1); operand_2 = get_memory_dword(eip+5); if (opcode == 0x07) { opcode = operand_1; operand_1 = operand_2; operand_2 = result; } switch (opcode) { case 0x02: result = get_memory_dword(operand_1 & 0x0fffffff); break; case 0x03: set_memory_dword(operand_1) = operand_2; break; case 0x06: result = (result & operand_1) | operand_2; break; case 0x04: if (operand_1 == 0x80000880) { operand_2 &= 0xfffffffd; } outl(operand_1, 0xcf8); outl(operand_2, 0xcfc); break; case 0x05: outl(operand_1, 0xcf8); result = inl(0xcfc); break; case 0x08: if (result != operand_1) { eip += operand_2; } break; case 0x09: eip += operand_2; break; case 0x10: scratch = (scratch & operand_1) | operand_2; result = scratch; break; case 0x11: outb(operand_2, operand_1); break; case 0x12: result = inb(operand_1); break; case 0xee: run_xcodes = 0; default: break; } eip += 9; } }
MCPX 1.0: RC4 Decryption of the 2BL
Version 1.0 of the ROM uses RC4 to decrypt the 2BL.
Stage 1: Key Scheduling
The RC4 Key-Scheduling Algorithm is used to initialize the RC4 “S” array, first initializing the identity permutation (writing 1, 2, ..., 255 to 0x8f000 to 0x850FF), then processed in a way similar to the PRGA to mix in the key.
uint8_t *s = (uint8_t *)0x8f000; uint32_t i; for (i = 0; i <= 255; i++) { s[i] = i; } uint8_t *key = (uint8_t *)0xffffffa5; /* ROM offset 0x1a5. */ uint8_t j, t; /* It is unclear why values s[0x100..0x101] are being set to 0. They are * not modified by the code, but later these will be be used as the initial * i, j values in the PRGA. */ s[0x100] = 0x00; s[0x101] = 0x00; for (i = 0, j = 0; i <= 255; i++) { j = j + s[i] + key[i%16]; /* Swap s[i] and s[j] */ t = s[i]; s[i] = s[j]; s[j] = t; }
Stage 2: PRGA
The RC4 Pseudo-random generation algorithm (PRGA) is then used to decrypt the 2BL from 0xFFFF9E00, storing the decrypted 2BL at 0x00090000. It is 24KiB in size.
uint8_t *encrypted = (uint8_t*)0xFFFF9E00; /* 2bl */ uint8_t *decrypted = (uint8_t*)0x90000; /* Decrypted 2bl Destination */ uint32_t pos; /* As noted above, s[0x100..0x101] were set to 0 earlier, but have not been * modified since. The RC4 algorithm defines i and j both to be set to 0 * before PRGA begins. */ i = s[0x100]; j = s[0x101]; for (pos = 0; pos < 0x6000; pos++) { /* Update i, j. */ i = (i + 1) & 0xff; j += s[i]; /* Swap s[i] and s[j]. */ t = s[i]; s[i] = s[j]; s[j] = t; /* Decrypt message and write output. */ decrypted[pos] = encrypted[pos] ^ s[ s[i] + s[j] ]; }
Stage 3: Verification
Now that the Second-Stage Bootloader has been loaded, a quick sanity-check is performed: a “magic” signature is verified. If the signature doesn’t match, control goes to the error handler. If the signature does match, the code will jump to the 2bl entry point, which is given by the first dword of the decrypted 2bl.
if (get_memory_dword(0x95FE4) == MAGIC_NUMBER) { eip = get_memory_dword(0x900000); } else { // Else, things have gone wrong eip = 0xFFFFFF94; }
Notes
The RC4 algorithm was included as part of MCPX 1.0 and seems to work fine with BIOS versions 3944, 4034, and 4134.
MCPX 1.1: TEA Decryption of the 2BL
[FIXME]
2BL
Certain parts are still missing
MTRR Setup
First, the cache is disabled.[FIXME]
Then, the MTRR (Memory Type Range Register) will be setup (using wrmsr
) in the following way:
MTRR (ecx) | High value (edx) | Low value (eax) | Notes |
---|---|---|---|
0x200 | 0x00000000 | 0x00000006 | |
0x201 | 0x0000000F | 0xFC000800 | (For 64 MiB RAM BIOS) |
0xF8000800 | (For 128 MiB RAM BIOS) | ||
0x202 | 0x00000000 | 0xFFF80005 | |
0x203 | 0x0000000F | 0xFFF80800 | |
0x204 | 0x00000000 | 0x00000000 | Clear all unused MTRR |
... | |||
0x20F | 0x00000000 | 0x00000000 | |
0x2FF | 0x00000000 | 0x00000800 |
Once the MTRR have been written, the cache is enabled.[FIXME]
Register setup
Now the 2BL will set up the segment registers[FIXME] and stack:
Register | Value | Notes |
---|---|---|
ds | 0x0010 | Data segment[citation needed] |
es | 0x0010 | |
ss | 0x0010 | |
esp | 0x00400000 | |
fs | 0x0000 | |
gs | 0x0000 |
Self-copy
Now the 2BL copies itself (24 kiB) from 0x00900000 to memory address 0x00400000.
Paging
Now a PDE is prepared at address 0x0000F000:
Offset in PDE | Value | Notes |
---|---|---|
0x000 | 0x000000E3 | Identity maps the first 256MiB of RAM: 0x00000000 and 0x80000000 will both map to physical page 0 0xE3: Flags: * 0x80: 4 MiB page * 0x40: Marked as previously written (Dirty) * 0x20: Marked as previously accessed * 0x02: Read/Write * 0x01: Present |
0x800 | 0x000000E3 | |
0x004 | 0x004000E3 | |
0x804 | 0x004000E3 | |
... | ||
0x8FC | 0x0FC000E3 | |
0x0FC | 0x0FC000E3 | |
0x900 | 0x00000000 | Unmapping the rest of the pages |
0x100 | 0x00000000 | |
... | ||
0xFFC | 0x00000000 | |
0x7FC | 0x00000000 | |
0xC00 | 0x0000F063 | Maps the PDE (4 kiB page) to address 0xC0000000 0x63: Flags: * 0x40: Marked as previously written (Dirty) * 0x20: Marked as previously accessed * 0x02: Read/Write * 0x01: Present |
0xFFC | 0xFFC000E3 | Identity maps the upper portion of the Flash (4 MiB page) to address 0xFFC00000 0xE3: Flags: * 0x80: 4 MiB page * 0x40: Marked as previously written (Dirty) * 0x20: Marked as previously accessed * 0x02: Read/Write * 0x01: Present |
0xFD0 | 0xFD0000FB | Maps 16 MiB for the GPU control registers 0xFB: Flags: * 0x80: 4 MiB page * 0x40: Marked as previously written (Dirty) * 0x20: Marked as previously accessed * 0x10: Cache disabled * 0x08: Write-Through caching * 0x02: Read/Write * 0x01: Present |
0xFD4 | 0xFD4000FB | |
0xFD8 | 0xFD8000FB | |
0xFDC | 0xFDC000FB |
After setting up the PDE, the PAT is set up using wrmsr
: [FIXME]
CR4 is touched [FIXME]
CR3 is touched [FIXME]
Now paging is activated by enabling the PG and WP bits in CR0.
Additionally, the same or
instruction is used to enable the NE bit in cr0.
2BL main
esp is now also reloaded to point at the relocated address. It will be set to 0x80400000 (absolute value, independent of previous esp value).
The 2BL will now call
into the relocated 2BL code somewhere near 0x00400000.
Disabling of the MCPX ROM
out32(0xCF8, 0x80000880); out8(0xCFC, 0x02);
SMC handling
The SMC has a watchdog functionality which must be turned off. This is done by querying the SMC registers 0x1C - 0x1F. If all of them are 0x00 the 2BL will shutdown the system[FIXME]. If this is not the case, the bootloader calculates the watchdog challenge response and sends it to SMC registers 0x20 and 0x21.
Additionally, the 2BL will set SMC register 0x01 to 0 (which resets the cursor position for reading the SMC revision information).
Enable IDE and NIC
out32(0xCF8, 0x8000088C); out32(0xCFC, 0x40000000);
Memory cleanup
The 2BL fills memory with 0xCC from 0x80090000 to 0x80095FFF. These are the 24 kiB where the 2BL was stored previously.
Setup RAM timing
Not described yet, this is complicated[FIXME]. This got a lot more complicated when Microsoft started using different RAM sometime after Hardware Revision 1.6 was already out.
Weird stuff 2
This does some PCI config, use unknown[FIXME].
out32(0xCF8, 0x80000854); out32(0xCFC, in32(0xCFC) | 0x88000000); out32(0xCF8, 0x80000064); out32(0xCFC, in32(0xCFC) | 0x88000000); out32(0xCF8, 0x8000006C); uint32_t tmp = in32(0xCFC); out32(0xCFC, tmp & 0xFFFFFFFE); out32(0xCFC, tmp); out32(0xCF8, 0x80000080); out32(0xCFC, 0x00000100);
Weird stuff 3
[FIXME]
out32(0xCF8, 0x80000808); uint8_t mcpx_revision = in8(0xCFC); if (mcpx_revision >= 0xD1) { out32(0xCF8, 0x800008C8); out32(0xCFC, 0x00008F00); }
Loading the kernel
Kernel-copy
The Kernel is now copied into RAM.
Kernel decryption
The 2BL will copy the kernel decryption key (16 bytes) from offset 32 of an array of 3 keys:
Offset | Use |
---|---|
0 | EEPROM key |
16 | Certificate key |
32 | Kernel key |
The Kernel is then decrypted in-place using RC4.
Kernel decompression
The Kernel is decompressed directly to 0x80010000 where it will reside until a full system shutdown.
Running the kernel
The xboxkrnl.exe header at 0x8001000 is checked[FIXME]. If it is invalid, [FIXME]. If it is valid, the kernel entry point is looked up from the PE optional header. The hardcoded image base of 0x8001000 is added to the entry point. The entry-point is now being called. Argumnts are passed on the stack, from right to left. The first argument is a commandline string loaded from memory address 0x80400000. It is an empty string for retail BIOS[FIXME]. A pointer to the previously mentioned array of 3 keys is passed as the second argument.