www.xbdev.net xbdev - software development
Friday October 10, 2008
home | about | contact | Donations

     
 
XBE - XBOX Executable Structure     
 

XBE File Format...

by bkenwright@xbdev.net

 

So you want to know how it ticks, whats inside the xbe file....how to create your own XBOX executable do you?  Well it can't be more simple...well as long as you stick with me, I'm sure you'll pick it up really easy.

 

 

Following on...feedback is always appreciated.....also donations to xbdev are always welcome - as it at least pays for the web server :)

 

The best way I think of doing this, is to get yourself an xbe, you can use any xbe you find.  There is a tool that will rip an xbe and contents of a game cd you have, then you can get the xbe from it.  Or compile an xbe using the OpenXDK?...its entirely upto you.

 

Where going to explore the xbe file format using C code...which means we'll read in the file using fopen(..), fread(..) etc, and examine the data that we read in...byte by byte.

 

 

 

 

As the first few hundred bytes will only define the details of the xbe, things like offsets to the code, the entry point to the program...when it was compiled etc.  We'll expand on this simple diagram later....just remember that all our xbe is, is code and data....its got a nice wrapper around it, which is the header...and may be broken up into sections...which we'll dig out of this xbe.

 

Some interesting things we do discover from the xbe as well, is the internal kernel bios api...and how they are used in the code.

 

code:  main.cpp

//Program Entry Point

void main(int argc, char* argv[])

{

 

      }

 

This is our starting point...I think its better to use, 'argv and argc' as we can later on pass the xbe filename in to our program, and possibly some additional information if we need to.

 

What where going to do, is write our output to a text file...we'll call it output.txt for now...we could change this later on, and pass an output filename.  So lets add our output code:

 

 

code: main.cpp

#include <stdio.h>                     // Need this for fopen(..) fwrite(..) etc

 

// We use this simple function to write data to our file.

void fileout(char *str)

{

      FILE *fp;

      fp=fopen("output.txt", "a+");    // a+ -> if file doesn't exist create it, else

      fprintf(fp, "%s", str);          //       append to the end of the file

      fclose(fp);

}// End fileout(..)

 

//Program Entry Point

void main(int argc, char* argv[])

{

      fileout("start of our xbe exploration");

      }// End main(..)

 

 

There you go!  Run that little program up, and we can output information using 'fileout(..)' whenever we want :)  So now if you run that code you should get a txt file called 'output.txt' in the same directory as your exe that you compiled.  Open it up in notepad, and we find!..."start of our xbe exploration".

 

I was originally just going to allocate a big area of memory....a couple of megs and just read in the whole xbe into it.  But a better idea is to write a small function to see how big our xbe is...it really makes our code much more flexible, and its not to complex.

 

code: main.cpp

#include <stdio.h>                     // Need this for fopen(..) fwrite(..) etc

 

// We use this simple function to write data to our file.

void fileout(char *str)

{

      FILE *fp;

      fp=fopen("output.txt", "a+");   // a+ -> if file doesn't exist create it, else

      fprintf(fp, "%s", str);         //       append to the end of the file

      fclose(fp);

}// End fileout(..)

 

long FileSize( char * szFileName )

{

      FILE *file = fopen(szFileName, "rb"); // Open the file

      fseek(file, 0, SEEK_END);             // Seek to the end

      long file_size = ftell(file);         // Get the current position

      fclose(file);

     

      return file_size;                     // We have the file size, so return it

}// End FileSize(..)

 

//Program Entry Point

void main(int argc, char* argv[])

{

      char szFileName[] = "default.xbe";    // Our input xbe

      char buf[500];                        // Large temp char buffer

 

      long iFileSize = FileSize(szFileName);

 

      sprintf(buf, "xbe filesize: %d bytes", iFileSize);

      fileout(buf);

}// End main(..)

 

Notice I've hard coded the filename of the xbe...its usually called 'default.xbe' anyhow...and you need to keep it in the directory with the exe.

 

xbe filesize: 1633220 bytes

 

Thats the output I get if I run the code on an xbe I had laying around on my xbox :).....its not much yet...but our journey has to start at the beginning.  If you right click on the xbe in windows, and look at the details...which are the actual file size in bytes...you'll see its the same as what is written to your txt file.  Where going slowly, so you can take it all in bit by bit.  Now go and get yourself a coffee, like I'm going to...before we start onto the next part...reading some bytes from the xbe.

 

Lets allocate memory for our whole xbe, and read it in...the whole thing...and we can write code to pick on it.

 

code: main.cpp

#include <stdio.h>                     // Need this for fopen(..) fwrite(..) etc

 

// We use this simple function to write data to our file.

void fileout(char *str)

{

...

}// End fileout(..)

 

long FileSize( char * szFileName )

{

...

}// End FileSize(..)

 

 

//Program Entry Point

void main(int argc, char* argv[])

{

      char szFileName[] = "default.xbe";    // Our input xbe

      char buf[500];                        // Large temp char buffer

 

      long iFileSize = FileSize(szFileName);

 

      sprintf(buf, "File: %s - xbe filesize: %d bytes\n", szFileName, iFileSize);

      fileout(buf);

 

      // Lets allocate enough memory for the whole xbe and read it all in

      unsigned char * pXBE = new unsigned char[iFileSize];

 

      // Open our xbe file

      FILE* fp = fopen( szFileName, "r" );

      fseek(fp, 0, SEEK_SET);

 

      // Read all the contents into our allocated memory

      /*

      do

      {

            fread(pXBE, 1, 1, fp);

      } while (!feof(fp));

      */

     

      // For some reason I had a problem with the above do/while loop..as it

      // didn't seem to read in all the data?  ..hmmm or was aligned wrong

      // so for a quick fix, I chose this instead :)

      fread(pXBE, iFileSize, 1, fp);

 

      // Close our file.

      fclose( fp );

 

 

      // Write out the first 3 char's of the xbe to see what they are

      sprintf(buf, "pXBE[0..3] = %c%c%c%c", pXBE[0], pXBE[1], pXBE[2], pXBE[3] );

      fileout(buf);

 

      // Remember, before exiting the program, release the memory we allcoated for

      // the xbe data we read in

      delete[] pXBE;

 

}// End main(..)

 

 

And of course the file output is:

 

XBEFile: default.xbe - xbe filesize: 1633220 bytes
pXBE[0..3] = XBEH

 

Now the first 4 bytes of the xbe are known as the signature - they are always 'XBEH'....you can use this to test if you have a valid xbe on your hands if you add in debugging code.

 

Next 0x100 or 256 bytes are an authentication signature, which is used by MS to make sure that the game was authorised by them.  This is one of the main reasons you need a mod chip to run compiled xbe's - as when the xbe boots up, it checks this signature to make sure its a valid xbe, if so it does some other checks and runs.  Of course we don't need to worry about this, so we will can just think of this as all 0's and it will still work :)

 

code E : main.cpp

 

 

#include <stdio.h>                     // Need this for fopen(..) fwrite(..) etc

 

// We use this simple function to write data to our file.

void fileout(char *str)

{

...

}// End fileout(..)

 

long FileSize( char * szFileName )

{

...

}// End FileSize(..)

 

 

//Program Entry Point

void main(int argc, char* argv[])

{

      char szFileName[] = "default.xbe";    // Our input xbe

      char buf[500];                        // Large temp char buffer

 

      long iFileSize = FileSize(szFileName);

 

      sprintf(buf, "File: %s - xbe filesize: %d bytes\n\n", szFileName, iFileSize);

      fileout(buf);

 

      // Lets allocate enough memory for the whole xbe and read it all in

      unsigned char * pXBE = new unsigned char[iFileSize];

 

      // Open our xbe file

      FILE* fp = fopen( szFileName, "r" );

      fseek(fp, 0, SEEK_SET);

 

      // Read all the contents into our allocated memory

      fread(pXBE, iFileSize, 1, fp);

 

      // Close our file.

      fclose( fp );

 

      int iOffset = 0;

      //------------------------Our XBE Analysis Code-------------------------//

 

      // Write out the first 3 char's of the xbe to see what they are

      sprintf(buf, "Sig (0x4) pXBE[0..3] = %c%c%c%c\n",

                pXBE[iOffset+0], pXBE[iOffset+1], pXBE[iOffset+2], pXBE[iOffset+3] );

      fileout(buf);

      iOffset += 4; // Move along 4 bytes.

 

      // Skip the 0x100 bytes that are the authentication signature

      fileout("Authentication Signature (0x100)\n");

      iOffset +=0x100;

 

      // Lets take a looksy at what the next piece of juicy info is...the base

      // address

      // We need this next line, as if we just use pXBE[iOffset] it will give us

      // a byte...big endian...so our 0x0...if we have 0x00100000...so if we

      // cast the pointer to an unsigned int, we get a 4 bytes...which is what we want

      unsigned int * ptr = (unsigned int*)&pXBE[iOffset];

      sprintf(buf, "Base Address (0x4) :  0x%08X\n", *ptr);

      fileout(buf);

      iOffset += 0x4;

 

      // Goodbye

 

      // Remember, before exiting the program, release the memory we allcoated for

      // the xbe data we read in

      delete[] pXBE;

 

}// End main(..)

 

 

And our output is as follows:

 

File: default.xbe - xbe filesize: 1633220 bytes

Sig (0x4) pXBE[0..3] = XBEH
Authentication Signature (0x100)
Base Address (0x4) : 0x00010000

 

The base address is the memory location our xbe will be placed at when the xbe runs.  Well thats what the xbe wants, it puts this information here to request it...it usually is given what it wants to keep it happy.

Now just remember that this offset base address is used as a reference later on, so when it says offset 100 bytes for some code..it means from this base address.  I'll point this out to you later on so you don't forget.

 

So skipping ahead, I've put a function together which will write the values for all the parts of the xbe main header to the file for us, so we can take a looksy at them...here is the code and output text file data:

 

code F: main.cpp

 

 

#include <stdio.h>                     // Need this for fopen(..) fwrite(..) etc

 

// We use this simple function to write data to our file.

void fileout(char *str)

{

    ...

}// End fileout(..)

 

long FileSize( char * szFileName )

    ...

}// End FileSize(..)

 

 

void ProcessXBE( unsigned char *pXBE, long iSizeXBE )

{

      char buf[500];                        // Large temp char buffer

      int iOffset = 0;

      unsigned int * ptr = (unsigned int*)pXBE;

 

      // Write out the first 3 char's of the xbe to see what they are

      sprintf(buf, "Sig (0x4) pXBE[0..3] = %c%c%c%c\n",

                pXBE[0], pXBE[1], pXBE[2], pXBE[3] );

      fileout(buf);

 

      // Skip the 0x100 bytes that are the authentication signature

      fileout("Authentication Signature (0x100) (skipped)\n");

       //Remember 256 bytes is 64 dwords, and the 1 dword which is the XBEH at the start

      ptr +=0x41;

 

      sprintf(buf, "Base Address                        (0x4) :  0x%08X\n", *ptr++);

      fileout(buf);

 

      sprintf(buf, "Size of Headers                     (0x4) :  0x%08X\n", *ptr++);

      fileout(buf);

     

      sprintf(buf, "Size of Image                       (0x4) :  0x%08X\n", *ptr++);

      fileout(buf);

 

      sprintf(buf, "Size of Image Header                (0x4) :  0x%08X\n", *ptr++);

      fileout(buf);

     

      sprintf(buf, "Time&Date Stamp                     (0x4) :  0x%08X\n", *ptr++);

      fileout(buf);

 

      sprintf(buf, "Certificate Address                 (0x4) :  0x%08X\n", *ptr++);

      fileout(buf);

 

      sprintf(buf, "Number of Sections                  (0x4) :  0x%08X\n", *ptr++);

      fileout(buf);

     

      sprintf(buf, "Section Headers Address             (0x4) :  0x%08X\n", *ptr++);

      fileout(buf);

 

      sprintf(buf, "Initialization Flags                (0x4) :  0x%08X\n", *ptr++);

      fileout(buf);

 

      sprintf(buf, "Entry Point                         (0x4) :  0x%08X\n", *ptr++);

      fileout(buf);

 

      sprintf(buf, "TLS Address                         (0x4) :  0x%08X\n", *ptr++);

      fileout(buf);

 

      sprintf(buf, "PE Stack Commit                     (0x4) :  0x%08X\n", *ptr++);

      fileout(buf);

     

      sprintf(buf, "PE Heap Reserve                     (0x4) :  0x%08X\n", *ptr++);

      fileout(buf);

     

      sprintf(buf, "PE Heap Commit                      (0x4) :  0x%08X\n", *ptr++);

      fileout(buf);

 

      sprintf(buf, "PE Base Address                     (0x4) :  0x%08X\n", *ptr++);

      fileout(buf);

 

      sprintf(buf, "PE Size of Image                    (0x4) :  0x%08X\n", *ptr++);

      fileout(buf);

     

      sprintf(buf, "PE Checksum                         (0x4) :  0x%08X\n", *ptr++);

      fileout(buf);

 

      sprintf(buf, "PE TimeDate                         (0x4) :  0x%08X\n", *ptr++);

      fileout(buf);

     

      sprintf(buf, "Debug PathName Address              (0x4) :  0x%08X\n", *ptr++);

      fileout(buf);

 

      sprintf(buf, "Debug FileName Address              (0x4) :  0x%08X\n", *ptr++);

      fileout(buf);

 

      sprintf(buf, "Debug Unicode FileName Address      (0x4) :  0x%08X\n", *ptr++);

      fileout(buf);

 

      sprintf(buf, "Kernel Image Thunk Address          (0x4) :  0x%08X\n", *ptr++);

      fileout(buf);

     

      sprintf(buf, "Non-Kernel Import Directory Address (0x4) :  0x%08X\n", *ptr++);

      fileout(buf);

     

      sprintf(buf, "Number of Library Versions          (0x4) :  0x%08X\n", *ptr++);

      fileout(buf);

     

      sprintf(buf, "Library Versions Address            (0x4) :  0x%08X\n", *ptr++);

      fileout(buf);

     

      sprintf(buf, "Kernel Library Version Address      (0x4) :  0x%08X\n", *ptr++);

      fileout(buf);

     

      sprintf(buf, "XAPI Library Version Address        (0x4) :  0x%08X\n", *ptr++);

      fileout(buf);

 

      sprintf(buf, "Logo Bitmap Address                 (0x4) :  0x%08X\n", *ptr++);

      fileout(buf);

 

      sprintf(buf, "Logo Bitmap Size                    (0x4) :  0x%08X\n", *ptr++);

      fileout(buf);

     

 

}// End ProcessXBE(..)

 

//Program Entry Point

void main(int argc, char* argv[])

{

      char szFileName[] = "default.xbe";    // Our input xbe

      char buf[500];                        // Large temp char buffer

 

      long iFileSize = FileSize(szFileName);

 

      sprintf(buf, "File: %s - xbe filesize: %d bytes\n\n", szFileName, iFileSize);

      fileout(buf);

 

      // Lets allocate enough memory for the whole xbe and read it all in

      unsigned char * pXBE = new unsigned char[iFileSize];

 

      // Open our xbe file

      FILE* fp = fopen( szFileName, "r" );

      fseek(fp, 0, SEEK_SET);

 

      // Read all the contents into our allocated memory

      fread(pXBE, iFileSize, 1, fp);

 

      // Close our file.

      fclose( fp );

 

      //------------------------Our XBE Analysis Code-------------------------//

 

      ProcessXBE( pXBE, iFileSize );

     

      //------------------------End of XBE Analysis---------------------------//

 

      // Goodbye

 

      // Remember, before exiting the program, release the memory we allcoated for

      // the xbe data we read in

      delete[] pXBE;

 

}// End main(..)

 

 

And here is our output data from the txt file:

 

File: default.xbe - xbe filesize: 1633220 bytes

Sig (0x4) pXBE[0..3] = XBEH
Authentication Signature (0x100) (skipped)
Base Address (0x4) : 0x00010000
Size of Headers (0x4) : 0x00002000
Size of Image (0x4) : 0x0019E240
Size of Image Header (0x4) : 0x00000178
Time&Date Stamp (0x4) : 0x3D5CE207
Certificate Address (0x4) : 0x00010178
Number of Sections (0x4) : 0x00000018
Section Headers Address (0x4) : 0x00010364

Initialization Flags (0x4) : 0x0000000C
Entry Point (0x4) : 0xA8F93C11
TLS Address (0x4) : 0x0001D8EC
PE Stack Commit (0x4) : 0x00010000
PE Heap Reserve (0x4) : 0x00100000
PE Heap Commit (0x4) : 0x00001000
PE Base Address (0x4) : 0x00011B40
PE Size of Image (0x4) : 0x0015A960
PE Checksum (0x4) : 0x0016A024
PE TimeDate (0x4) : 0x3D5CE202
Debug PathName Address (0x4) : 0x00010A7C
Debug FileName Address (0x4) : 0x00010AA0
Debug Unicode FileName Address (0x4) : 0x00010A58
Kernel Image Thunk Address (0x4) : 0x5B6C60B6
Non-Kernel Import Directory Address (0x4) : 0x00000000
Number of Library Versions (0x4) : 0x0000000B
Library Versions Address (0x4) : 0x000109A8
Kernel Library Version Address (0x4) : 0x000109E8
XAPI Library Version Address (0x4) : 0x000109A8
Logo Bitmap Address (0x4) : 0x00010AB4
Logo Bitmap Size (0x4) : 0x000002B2

Now I've made the most important parts bold so you can see.  I'll explain why I've made them bold here - I made the XBEH bold so we know we have an xbox executable file...I just like seeing the xbe..hehe....makes me know I'm getting it right.  Next the base address, which is 0x10000....we use this a lot through out our other couple of headers and so we try to keep it at the back of our mind.

Then we have Sections!  Take a deep breath, there usually isn't 0x18 sections..hehe...just must be this xbe I'm working with...I would usually expect 3-4 sections...this is a big xbe.  The sections are where our code and data is held.  You could take each code section...write it to a raw binary file...and use a disassembler to look at the actual code.  We'll do more on that later though.

 

Entry point - this should have a name which is self explanatory.  Its the starting point of our code!  Yup...its all processed loaded into memory and your xbox is ready to start, so it jumps to this point in memory, which hopefully is where one of your sections will have been loaded.

 

Then finally we have the Thunk's.  What on earth is a thunk :-/  Well when I first started reading about xbe and exe formats....these thinks really stumbled me... they don't tell you much.  But all it is, is an array of pointers.  Each pointer points to a kernel bios function.  As when your xbe is loaded, these values are filled in...and when our program wants to do things like read from the disk or get the tick count of the cpu, which are the responsiblity of the cpu...then the function is mapped to there.  As there are 256 functions which are put there.  An example of some of the bios function calls are as follows:

 

...

MmAllocateContiguousMemory  (165)

MmAllocateContiguousMemoryEx (166)

MmAllocateSystemMemory (167)

MmClaimGpuInstanceMemory (168)

MmCreateKernelStack (169)

      ...

 

Where the number to the right, is the offset value, e.g. 165.  This value is usually added to (0x80000000) then inserted into the array....on loading, its value is changed for the actual memory offset to the bios function.

 

 

Now to keep things tidy...and to allow us to build our code larger, I thought I'd make it a little more structured.  So I created a structure for the Image Header...and called it stImageHeader....I can then load it in one go, and reference any part of it when I need.  Here is the slightly changed code.

 

 

code G: main.cpp

 

#include <stdio.h>                     // Need this for fopen(..) fwrite(..) etc

#include <string.h>                    // memcpy(..)

 

// We use this simple function to write data to our file.

void fileout(char *str)

{

      ...

}// End fileout(..)

 

long FileSize( char * szFileName )

{

      ...

}// End FileSize(..)

 

 

// Remember that we use 'pragma pack(1)' so our data in the stucture is packed nice

// and tight using a 1 byte alignment.

#pragma pack(1)

struct stImageHeader

{

      char          sig[4];

      unsigned char auth_sig[0x100];

      unsigned int  base_address;

      unsigned int  size_header;

      unsigned int  size_image;

      unsigned int  size_image_header;

      unsigned int  time_date;

      unsigned int  cert_addr;

      unsigned int  num_sections;

      unsigned int  sect_addr;

      unsigned int  init_flags;

#define MountUtilityDrive    0x00000001

#define FormatUtilityDrive   0x00000002

#define Limit64Megabytes     0x00000004

#define DontSetupHarddisk    0x00000008

      unsigned int  entry_point;

#define XOR_ENTRY_DEBUG      0x94859D4B

#define XOR_ENTRY_RETAIL     0xA8FC57AB

      unsigned int  tls_addr;

      unsigned int  pls_stack_commit;

      unsigned int  pe_heap_reserv;

      unsigned int  pe_heap_commit;

      unsigned int  pe_base_addr;

      unsigned int  pe_size_image;

      unsigned int  pe_checksum;

      unsigned int  pe_timedata;

      unsigned int  pathname_addr;

      unsigned int  filename_addr;

      unsigned int  unicode_filename_addr;

      unsigned int  kernel_thunk_addr;

#define XOR_KERNEL_DEBUG     0xEFB1F152

#define XOR_KERNEL_RETAIL    0x5B6D40B6

      unsigned int  non_kernel_dir_addr;

      unsigned int  num_lib_versions;

      unsigned int  lib_vers_addr;

      unsigned int  kernel_lib_vers_addr;

      unsigned int  xapi_lib_vers_addr;

      unsigned int  logo_bitmap_addr;

      unsigned int  logo_bitmap_size;

};

#pragma pack()

 

 

 

void ProcessXBE( unsigned char *pXBE, long iSizeXBE )

{

 

      stImageHeader IH;

      memcpy( &IH, pXBE, sizeof(stImageHeader) );

 

      // Large temp char buffer