Skip to content
This repository was archived by the owner on Aug 12, 2025. It is now read-only.

Exploit Details

Ryan Miceli edited this page Mar 3, 2025 · 6 revisions

This page contains information on the various parts of the Bad Update exploit and what each stage is/does. For additional information on the Xbox 360 security system or how I found and wrote the exploit please see the following blog posts:

The Bad Update exploit is broken up into 4 parts or "stages" which are chained together. Each stage is described in detail below.

Stage 1: Game Exploit

The exploit chain starts with obtaining ROP execution via a save game exploit for one of the support games. The details of the game save exploit are not documented here. For additional information on the game save exploits see the following pages:

Once the game save exploit gains control of the instruction pointer it will jump to a small ROP chain embedded in the game save file. This ROP chain performs the following actions:

  1. Copies some static data to a R/W section of memory to be used as a faux data segment by stage 2.
  2. Creates a symlink to mount the USB stick device path to "PAYLOAD:" for accessing additional exploit files.
  3. Allocates memory to hold the 2nd stage ROP chain and reads the contents of the "BadUpdateExploit-2ndStage.bin" file into memory.
  4. Jumps to the 2nd stage ROP chain.

The data contained in the faux data segment used by the 2nd stage ROP chain is defined in the "BadUpdateExploit_Data.asm" file. The memory addresses for this data are pre-calculated with the EXPLOIT_DATA macro defined in "BadUpdateExploit_Data_h.asm" file and baked into the 2nd stage ROP chain. Therefore, it's not possible to mix a 2nd stage binaries for different game targets.

The location for the faux data segment can be any R/W memory region large enough to fit it with a static address. The "BINKDATA" segment is a great location for this data if the target game links to the bink libraries.

Stage 2: Kernel Mode Code Execution

The 2nd stage is a very large ROP chain that performs an attack on encrypted memory marked as RX to obtain 64kb of arbitrary kernel mode code execution. The details for this attack are described in detail here. A brief overview of the steps the ROP chain performs are as follows:

  1. Loading the boot animation executable from "Flash:\bootanim.xex" and reading 16 bytes of plaintext data at the target memory address 0x98030000.
  2. Reading and obtaining the ciphertext data for the "BadUpdateExploit-3rdStage.bin" file.
  3. Loading the boot animation executable in a loop until a whitening value collision is hit.
  4. Writing the ciphertext for the 3rd stage code to memory, flushing cache, and jumping execution to the 3rd stage.

This ROP chain is extremely large and complex, coming in at around ~28,000 links in the chain. Most of the chain is written in macros that are split across several different files. The loop portion of the attack is written in such a way that an embedded "mini ROP chain" will be setup with pre-calculated memory addresses and then copied to 2 distinct scratch buffers that are referred to as the "primary" and "secondary" overwrite loops.

Each overwrite loop performs the same operations:

  1. Load the boot animation executable and check for a whitening value collision.
  2. If no collision is found unload the boot animation, copy "clean" ROP gadget data for the opposite overwrite loop buffer, and pivot to it.
  3. If a whitening value collision is found break the loop and write the 3rd stage data to memory.

Since looping in ROP is difficult I made a single ROP chain that is the loop body, and have two instances of the chain that ping-pong back and forth until the loop condition is satisfied.

Important

If you modify any of the data in the faux data segment used by the 2nd stage ROP chain you'll need to rebuild the game save portion of the exploit as the data segment data is embedded into it.

Stage 3: Hypervisor Code Execution

The 3rd stage is C/assembly code that performs the attack on the bootloader update XKE payload to get hypervisor code execution. The details of this attack are described in detail here. A brief overview of the steps performed in this stage are as follows:

  1. Reads in various files used during this stage: update_data.bin, xke_update.bin, BadUpdateExploit-4thStage.bin.
  2. Obtains ciphertext for the expected data in the LZX decoder context header (used to identify when a whitening value collision occurs), and ciphertext for the malicious dec_output_buffer pointer (set to point into the last code segment of hypervisor memory).
  3. Runs the attack and payload threads to carry out the attack.

Attack Thread:

  1. Loops infinitely and checks if the ciphertext for the LZX decoder context header matches the ciphertext generated in step 2 above, which indicates a whitening value collision has been hit.
  2. If a collision is hit the thread enters a secondary loop that writes the ciphertext for the malicious dec_output_buffer pointer into the LZX decoder context structure and flushes cache for 100000 iterations.
  3. Repeats the process indefinitely.

This loop is implemented in hand written assembly to maximize the performance of the operations performed. Timing in this attack is critical and the machine code emitted by the compiler was not satisfactory. All of the data used for this loop is preloaded into registers so that no additional memory fetches need to be performed in the loop body. After each memory read or write cache must be flushed so the CPU thread doesn't fetch stale data and any data sitting in CPU cache is flushed to main memory.

Payload Thread:

  1. Puts pressure on CPU L2 cache by locking half of the available space (512KB) and half of the pathways (4 pathways), effectively reducing the L2 cache by 50%.
  2. In a loop: run the bootloader update XKE payload and check if the ciphertext for the last hypervisor code segment has changed. This check is performed in two distinct locations to detect when the last block (block #14) of the update file has been decompressed as it's the only block with a size smaller than 0x8000 bytes. This is effectively a side channel to determine when the race condition is won and block 14 gets written, which is explained in detail here.
  3. Once block 14 has overwritten the last code segment of the hypervisor we now have a single byte arbitrary write primitive from hypervisor mode that can be used by executing a specific syscall. Using this write primitive I patch the hypervisor syscall table to overwrite the entry for HvxPostOutput to point to the instruction pattern mtctr %r4 | bctr which turns HvxPostOutput into an arbitrary code execution primitive.
  4. By placing the 64-bit real mode address of the memory allocation containing the stage 4 code into r4 and calling HvxPostOutput the stage 4 code will execute in hypervisor context. This code will fix the memory corruption done to the hypervisor by the block 14 overwrite, and patch out the RSA signature checks on executable files before returning back to the stage 3 code.
  5. Finally, we'll launch an unsigned default.xex file in the "BadUpdatePayload" folder on the USB stick.

Once the exploit process is complete the memory corruption done to the hypervisor will be restored into a clean working state with the exception of the patch made to the HvxPostOutput syscall to run arbitrary code in hypervisor mode. This patch will persist for the remainder of the time the console is powered on and can be used any homebrew executable after the exploit is complete to run arbitrary code in hypervisor mode.

Due to the nature of the memory writes and cache hammering that happens in stage 3 there's a ~70% chance that it will fail and cause the console to hang. The crashes can be due to the memory corruption attacks happening at the wrong time and causing the hypervisor to crash, and also due to the thrashing of the CPU L2 cache. For whatever reason the CPU does not like to run for extended periods of time with that much L2 cache locked off. However, the locking of the L2 cache is critical to the success of the race condition memory writes.

I tried for a few days to get the C code for stage 3 to compile under GCC in one shot (and even wrote a custom linker for it). However, GCC is incapable of producing working machine code that can interface with the Xbox 360 OS for several different reasons. Therefore, while the C code does accurately represent the operations performed in stage 3 it needs to be compiled in chunks and have the machine code fixed up by hand. This is how the "BadUpdateExploit-Stage3.asm" file was constructed. This is a very bad approach to solving the issue but I've wasted enough hours of my life messing around with GCC and couldn't justify wasting any more time on it.

Stage 4: Patching/Fixing the Hypervisor

Stage 4 is assembly code that runs in hypervisor mode after stage 3 successfully achieves hypervisor mode code execution. This stage is responsible for undoing all of the memory corruption done to the hypervisor to achieve code execution, and patch out the RSA signature checks on executable files in both the hypervisor and kernel (there's 2 checks that must be patched).

Important

This stage links in clean hypervisor data for the last segment that block 14 overwrites during the exploit process. To target another hypervisor version you'll need to link in the data for the last 64KB segment in the target hypervisor image.

Post Exploit

After the exploit successfully runs the hypervisor will be patched to allow booting unsigned executables, and provide an entry point for running arbitrary code in hypervisor mode. What this exploit does not do is patch in all the quality of life things that you get from other exploits such as:

  • Removing signature checks on DLC content.
  • Removing additional security checks on executable files.
  • Allowing dev encrypted executables to run.
  • Etc.

My only goal with this exploit was to achieve hypervisor code execution, anything thereafter is left for someone else to implement/develop and should be kept separate from this repository. For anyone who wants to develop such things here's some additional information that will help you.

Boot Animation Cleanup

The boot animation dll that's loaded into memory by stage 2 to house the stage 3 code is still loaded into memory when the exploit process finishes. It's highly recommended to anyone developing a post-exploit executable to unload the boot animation dll as it could cause an address conflict with another system module/game dll.

Hypervisor Code Execution

By executing the HvxPostOutput syscall you can run arbitrary code in hypervisor mode. The following C code demonstrates how to do this:

extern "C" ULONG MmGetPhysicalAddress(PVOID pAddress);

// Note: r5+ can be used as additional argument registers to your code.
ULONG __declspec(naked) HvxPostOutputExploit(ULONG r3, ULONGLONG shellCodeAddress)
{
    _asm
    {
        li      r0, 0xD
        sc
        blr
    }
}

bool ReadShellCodeFile(LPCSTR psFilePath, BYTE** ppShellCodeBuffer, DWORD* pdwShellCodeBufferSize)
{
    DWORD BytesRead = 0;

    // Open the update file for reading.
    HANDLE hFile = CreateFile(psFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
    {
        DbgPrint("Failed to open shell code file\n");
        return false;
    }

    // Get the size of the file.
    DWORD dwFileSize = GetFileSize(hFile, NULL);
    DWORD dwBufferSize = (dwFileSize + 0x7F) & ~0x7F;

    // Allocate memory for the clean update data buffer.
    BYTE* pBuffer = (BYTE*)XPhysicalAlloc(dwBufferSize, MAXULONG_PTR, 0x10000, PAGE_READWRITE | PAGE_NOCACHE | MEM_LARGE_PAGES);
    if (pBuffer == NULL)
    {
        DbgPrint("Failed to allocate memory for shell code data\n");
	return false;
    }

    memset(pBuffer, 0, dwBufferSize);

    // Read the file into memory.
    if (ReadFile(hFile, pBuffer, dwFileSize, &BytesRead, NULL) == false || BytesRead != dwFileSize)
    {
        DbgPrint("Failed to read shell code file\n");
        return false;
    }

    // Update the pointers for the update data.
    *ppShellCodeBuffer = pBuffer;
    *pdwShellCodeBufferSize = dwBufferSize;

    CloseHandle(hFile);
    return true;
}

...

BYTE* pShellCodeData = NULL;
DWORD ShellCodeDataSize = 0;

// Read the exploit shell code.
if (ReadShellCodeFile("game:\\your_hv_code.bin", &pShellCodeData, &ShellCodeDataSize) == false)
{
	return;
}

// Get the full physical address of the shell code buffer.
ULONGLONG ShellCodePhys = 0x8000000000000000 | MmGetPhysicalAddress(pShellCodeData);

// Execute code in hypervisor mode.
HvxPostOutputExploit(0, ShellCodePhys);

This can be used to run code that does additional patching of the hypervisor post-exploit. Keep in mind you'll need to be mindful of caching (RMCI) when touching code in encrypted memory.

Clone this wiki locally