www.xbdev.net xbdev - software development
Thursday January 8, 2009
home | about | contact | Donations

     
 

XBOX Programming

More than just a hobby...

 

XBOX - XBE to ASM and Hex using C and NASM

 

We take an xbe...any xbe...from the xbox hard drive...or extract one from a game cd...we'll then go about converting it to a nasm file, which we can use nasm to rebuild into the original xbe.  Of course the nasm file will contain disassembled asm values....header data the works....so we really really get a feel for what makes up an xbe.

 

How does NASM help us?...what is NASM?   where can you get NASM?...hmmm

 

Well NASM stands for the Netwide Assembler, as is an 80x86 open source assembler...but its much much more.  Its open source, so you can download it free and see how it works if you like... its powerful, and can assemble intel pentium 3 and 4 opcodes.  But best of all it allows us to create simple hex/asm files...which we can assemble nice and easily to produce binary output... but for use the binary output will be an xbe.

 

So take an example....open notepad and enter these lines:

 

db 'XBE'

db 0x10

dd 0x59

 

 

Then save it as...hmmm...lets call it test.asm...you can call it anything you want...whacky.txt if you like, nasm will still work with it.  We then go to the command line, and enter:

 

C:> nasm.exe test.asm -o test.txt

 

And that will produce our output file.  The -o is so we can name our output file...if you just put 'nasm.exe test.asm'....it will produce an output file called 'test' with no extension.  Go on now...open your output file test.txt in notepad...see what it looks like..heheh

 

'XBE Y '

 

Well its not much to look at...but if you open it up in a hex editor you'll see that every byte that we've specified in our nasm input file has been put into our output file...we can also mix in 80x86 asm code....comments....so much we can do....and then just assemble it to produce our output.

 

Where is this taking us?  Well we will write some C code which will take our xbe, and convert it to a nasm asm file...which when we assemble it with nasm will reproduce the original xbe.  But while producing the asm file with our C code...we'll be putting information in our output...what each byte means...and possibly disassembling some of the binary code as well :)

 

 

First things first...we'll write a small C code that will take an input xbe file...and give us a nasm asm file....which we can convert back to an xbe using nasm :)

 

Here is our code to start with:

 

code A: main.cpp
 

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

 

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

void abc(char *str)

{

      FILE *fp;

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

      fprintf(fp, "%s", str);              //       else 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, "; File: %s - xbe filesize: %d bytes\n\n", szFileName, iFileSize);

      abc(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" );

 

      // Seek to the start of the xbe - as our getfilesize(.) function has left it

      // at the end...so we need this.

      fseek(fp, 0, SEEK_SET);

 

      // Read all the contents into our allocated memory

      fread(pXBE, iFileSize, 1, fp);

 

      // Close our file.

      fclose( fp );

 

      //--------------------------Output XBE Hex to file-----------------------//

 

      abc("db\t");

      for(int icount=0; icount<iFileSize; icount++)

      {

            // Create our output in a format that nasm will like, e.g. "db  0x20"

            sprintf(buf, "0x%02X", pXBE[icount] );

            // Output to file

            abc(buf);

 

            // Every 0x10 bytes, do a new line

            // Added the icount!=0 so that we dont' get a byte db 0x56 etc on its

            // own line at the start.

            if( ((icount % 0x10) == 0) && (icount!=0))

                  abc("\ndb\t");

            else

                  abc(", ");

      }//End for loop

     

      //------------------------End of outputing to file ----------------------//

 

      // Goodbye

 

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

      // the xbe data we read in

      delete[] pXBE; 

      }// End main(..)

 

It looks like a lot of C code, but there really isn't that much....I've put loads of comments in it, and tried to lay it out in an easy format.  So lets run this baby up with a trial xbe and have a look at some of the output shall we!  Oh yeah...if your using a large xbe...megs...it may take a while to output the text file...as where not really interested in optimisation here..heheh...but be patient.  The nasm build on the other hand only takes a few seconds.

 

; File: default.xbe - xbe filesize: 1633220 bytes

 

db    0x58, 0x42, 0x45, 0x48, 0xFC, 0x55, 0x80, 0xD4, 0xFD, 0x4F, 0x11, 0xF7, 0x2B, 0xCE, 0x74, 0x27, 0xA8

db    0xE7, 0xD4, 0x94, 0x5D, 0x39, 0x20, 0xE7, 0x8E, 0x80, 0x77, 0xC4, 0x3F, 0x45, 0xD0, 0xC2, 0x39

db    0x52, 0x5D, 0x37, 0xD4, 0xDC, 0xE2, 0x8F, 0xF7, 0x08, 0x31, 0x69, 0x36, 0x67, 0x5D, 0xC3, 0x20

db    0x2F, 0x43, 0xEF, 0xFD, 0x3E, 0x3F, 0xA7, 0xC1, 0x85, 0xCB, 0x7F, 0x1F, 0x7B, 0x39, 0x33, 0xE4

db    0xB0, 0x81, 0x3C, 0x71, 0xF8, 0x5B, 0x5E, 0xDC, 0x9A, 0x8A, 0x57, 0x1C, 0xF7, 0x8E, 0xAB, 0x7F

db    0x84, 0x8A, 0x35, 0xE5, 0xA6, 0xB0, 0xF2, 0x45, 0x93, 0x70, 0xAF, 0xBC, 0xD3, 0x95, 0x16, 0xA9

db    0x47, 0xCA, 0x72, 0xEE, 0x68, 0xCC, 0x59, 0xF3, 0xB4, 0x49, 0xB1, 0x75, 0xDF, 0x44, 0x71, 0x5C

db    0x1D, 0x1B, 0x0E, 0xA7, 0xCB, 0x60, 0x9A, 0x17, 0x0B, 0xF1, 0x44, 0x2B, 0x58, 0x2C, 0x18, 0xF2

      db    0x58, 0xDD, 0x7A, 0xC4, 0xDA, 0xD2, 0xA9, 0x59, 0xC3, 0x64, 0x85, 0xBD, 0xDC, 0xA8, 0xD1, 0xF5

 

It may not look like much at the moment...but it will get there...so power up nasm and give it a test.... type in those magic lines "nasm output.txt -o default.xbe"....whoooshh...and there you have it....our original xbe is built from all thos hex values :)

 

We'll go about making our C code a bit smarter now, and make it output additional information for us...like the first 4 byte values are the char values 'X','B','E','H'....so we can have our C code do " db 'XBEH' on the first line.   And bit by bit disassemble the xbe into understandable information.

 

Also the great thing about learning this way is...that once you understand how an xbe is put together...what each byte is for...and which bytes are important...you can build xbox executables that are totally custom....just put in the asm code that you want and assemble it...then run it on the xbox and thats it.  Or progress further and create an exe to xbe converter program :)

 

A good idea at this point is to look over the xbe specification a few times, and try and understand it.  You don't have to worry to much, as I'll be taking you through it bit by bit....but its still worth having it close by so that you can look things up from time to time....and who knows...might might make a mistake :-/

 

Just so we can expand our code more and more and keep it tidy...I've split up the code we had above into some sub functions....so its easier to work with....small little pieces are better than big clumsy ones :)

 

code B: main.cpp

 

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

 

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

void abc(char *str)

{

      ...

}// End fileout(..)

 

long FileSize( char * szFileName )

{

      ...

}// End FileSize(..)

 

void ReadInXBE( unsigned char ** pXBE, char * szFileName )

{

      long iFileSize = FileSize(szFileName);

 

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

      *pXBE = new unsigned char[iFileSize];

 

      // Open our xbe file

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

 

      // Seek to the start of the xbe - as our getfilesize(.) function has left it

      // at the end...so we need this.

      fseek(fp, 0, SEEK_SET);

 

      // Read all the contents into our allocated memory

      fread(*pXBE, iFileSize, 1, fp);

 

      // Close our file.

      fclose( fp );

 

}// End ReadInXBE(..)

 

 

// We can use this function to either output all the file in hex, or if there

// are parts of the file that we are unsure about, then we can output that section

// in hex keeping the file integrity...or possibly output code sections in hex.

void OutRawHex( unsigned char * pXBE, int iFrom, int iTo )

{

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

      //--------------------------Output XBE Hex to file-----------------------//

     

      abc("db\t");

      for(int icount=iFrom; icount< iTo-1; icount++)

      {

            // Create our output in a format that nasm will like, e.g. "db  0x20"

            sprintf(buf, "0x%02X", pXBE[icount] );

            // Output to file

            abc(buf);

 

            // Every 0x10 bytes, do a new line

            if( (icount % 0x10) == 0 )

                  abc("\ndb\t");

            else

                  abc(", ");

      }//End for loop

 

      // Added this line, and made the above loop finish at iTo-1 instead of

      // iTo, so that our hex output doesn't finish with a comman (i.e. ,)

      sprintf(buf, " 0x%02X", pXBE[iTo-1] );

      abc(buf);

     

      //------------------------End of outputing to file ----------------------//

 

}// End OutRawHex(..)

 

 

/***************************************************************************************/

/*                                                                                     */

//------------------------------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);

      abc(buf);

 

      unsigned char * pXBE = NULL;

      ReadInXBE( &pXBE, szFileName );

 

 

      abc("db 'XBEH'\n");

 

      // Start at 4, as XBEH is 4 bytes..but arrays and all computers start at 0...so in

      // effect we have 0,1,2,3 then we start

      OutRawHex(pXBE, 4, iFileSize );

 

 

      // Goodbye

 

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

      // the xbe data we read in

      delete[] pXBE;

 

}// End main(..)

 

And the output isn't much different from the previous one...except I've not output the first 4 bytes as char as they should be :)

 

; File: default.xbe - xbe filesize: 1633220 bytes

 

db    'XBEH'

db    0x48, 0xFC, 0x55, 0x80, 0xD4, 0xFD, 0x4F, 0x11, 0xF7, 0x2B, 0xCE, 0x74, 0x27, 0xA8

db    0xE7, 0xD4, 0x94, 0x5D, 0x39, 0x20, 0xE7, 0x8E, 0x80, 0x77, 0xC4, 0x3F, 0x45, 0xD0, 0xC2, 0x39

db    0x52, 0x5D, 0x37, 0xD4, 0xDC, 0xE2, 0x8F, 0xF7, 0x08, 0x31, 0x69, 0x36, 0x67, 0x5D, 0xC3, 0x20

db    0x2F, 0x43, 0xEF, 0xFD, 0x3E, 0x3F, 0xA7, 0xC1, 0x85, 0xCB, 0x7F, 0x1F, 0x7B, 0x39, 0x33, 0xE4

db    0xB0, 0x81, 0x3C, 0x71, 0xF8, 0x5B, 0x5E, 0xDC, 0x9A, 0x8A, 0x57, 0x1C, 0xF7, 0x8E, 0xAB, 0x7F

 etc etc.....

 

Hmmmm...so what next....just sitting here, thinking of the best way to go about this....could get messy if we do it the wrong way :)  

 

 

IDEAS*

Well at various stages in my code development, I'm tempted to add all sorts of sweet improvements which will make the code much cooler and much more useful - but at a cost, which includes more complexity, and the possibility that it will be more inflexible to some unknowns.

Let me give you some ideas of where you could add some add-ons...which I won't implement, as I want to keep the code as simple as I can. 

\ Hidden File Offsets - There is nothing stopping you putting file offsets all the way through your file...e.g ";<0x0010020>" on the far right of your text output file.  This offset as long as you have the ; in front of it will be ignored by nasm.  But also, if you do a GUI interface later on, you could have the GUI loader look for this offset and use it in some way to get some further information from the xbe.

\ Progress Feedback.  Now I'm working with quite a big xbe, so it takes a few minutes to process the data...so it might be better off to possibly use printf(..) to output some data to the dos console to show whats happening...and when we go into one of those big loops....do a possible %10, %20...feedback of how far along we are.

 

What do we have now... I've wrote a nice piece of code...which a few helper functions which makes it possible now for us to hopefully output and analyse all the header data.... the code seemed to grow quiet fast, but I kept thinking ahead...as we have quick a few different headers after the main ImageHeader at the start...so putting together some nice output functions which we can call over and over again makes the code easier to the eyes I think.  At this point, all I've done is processed the first 0x178 bytes...which is the first Header data at the start of the xbe.

 

This tells us all the other offsets to all our other juicy data that we'll be extracting soon.  I've not put many comments into the header outputs yet....things like where it has the address of the file location...we could put a comment line and write what is written at that address - just so the asm file looks more understandable.  We'll have a few cups of coffee later on and try and add those lines in :)

 

 

code C: main.cpp

 

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

 

/***************************************************************************************/

//-------------------------------stImageHeader Structure-------------------------------//

// 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()

 

/***************************************************************************************/

 

 

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

void abc(char *str)

{

      ...

}// End fileout(..)

 

long FileSize( char * szFileName )

{

      ...

}// End FileSize(..)

 

void ReadInXBE( unsigned char ** pXBE, char * szFileName )

{

      ...

}// End ReadInXBE(..)

 

 

// We can use this function to either output all the file in hex, or if there

// are parts of the file that we are unsure about, then we can output that section

// in hex keeping the file integrity...or possibly output code sections in hex.

void OutRawHex( unsigned char * pXBE, int iFrom, int iTo )

{

      ...

}// End OutRawHex(..)

 

// iNum      -> How many char to output...e.g. db 'XBE' is 3 char

// pData     -> Pointer to the character data

// szComment -> Should speak for itself, if we want a comment on the end

void OutCharData(int iNum, char* pData, char* szComment=0  )

{

      ...

}// End OutCharData(..)

 

void OutWordData(int iNum, unsigned short* pData, char* szComment=0  )

{

      ...

}// End OutCharData(..)

 

void OutDwordData(int iNum, unsigned int* pData, char* szComment=0  )

{

      ...

}// End OutCharData(..)

 

// I added this function, so I could put comments into the output file, with

// the same indentation spaces...instead of using sprintf(..) and output etc

// over and over agian :)

void OutComment(char * szComment)

{

      ...

}// End OutComment(..)

 

 

#define DATA_TYPE_CHAR  0

#define DATA_TYPE_WORD  1

#define DATA_TYPE_DWORD 2

void OutData( unsigned int iType, void * pData, char* szComment=0, int iNum=1 )

{

      char buf[500];

 

      switch( iType )

      {

      case 0:

            OutCharData(iNum, (char*)pData, szComment);

            break;

      case 1:

            OutWordData(iNum, (unsigned short*)pData, szComment);

            break;

      case 2:

            OutDwordData(iNum, (unsigned int*)pData, szComment);

            break;

      // possible implement a default - just incase

      }// End switch(..)

 

}// End OutData(..)

 

// Output the image header information

void OutImageHeader(unsigned char * pXBE, stImageHeader * pImageHeader)

{

      char buf[500];

 

      abc(";---------------------------ImageHeader-----------------------------;\n\n");

      abc("StartImageHeader:\n");

      OutData( DATA_TYPE_CHAR,  (void*)pImageHeader->sig, "Signature", 4);

 

      // The authentication signature can just be replaced by a load of zero's

      sprintf(buf, "db\tresb  0x100\n"); abc(buf);

 

      OutData( DATA_TYPE_DWORD, (void*)&pImageHeader->base_address,   "BaseAddress");

      OutData( DATA_TYPE_DWORD, (void*)&pImageHeader->size_header,    "SizeHeader");

      OutData( DATA_TYPE_DWORD, (void*)&pImageHeader->size_image,     "SizeImage");

      OutData( DATA_TYPE_DWORD, (void*)&pImageHeader->size_image_header, "SizeImageHeader");

      OutData( DATA_TYPE_DWORD, (void*)&pImageHeader->time_date,      "Time&Date");

      OutData( DATA_TYPE_DWORD, (void*)&pImageHeader->cert_addr,      "Cert Header Addr");

      OutData( DATA_TYPE_DWORD, (void*)&pImageHeader->num_sections,   "Num Sections");

      OutData( DATA_TYPE_DWORD, (void*)&pImageHeader->sect_addr,      "Section Header Addr");

      OutData( DATA_TYPE_DWORD, (void*)&pImageHeader->init_flags,     "Initialisation Flags");

      OutData( DATA_TYPE_DWORD, (void*)&pImageHeader->entry_point,    "*Program Entry Point*");

 

      sprintf(buf, "XOR with our Release Key");

      OutComment(buf);

      sprintf(buf, "0x%08X ^ 0x%08X = 0x%08X", XOR_ENTRY_RETAIL,

                                                 pImageHeader->entry_point,

                                                                   pImageHeader->entry_point ^ XOR_ENTRY_RETAIL );

      OutComment(buf);

      OutComment("****Important**** - this is our entry point for our code\n");

 

 

      OutData( DATA_TYPE_DWORD, (void*)&pImageHeader->tls_addr,       "TLS Header Addr");

      OutData( DATA_TYPE_DWORD, (void*)&pImageHeader->pls_stack_commit, "PLS Stack Commit");

      OutData( DATA_TYPE_DWORD, (void*)&pImageHeader->pe_heap_reserv, "PE Heap Reserve");

      OutData( DATA_TYPE_DWORD, (void*)&pImageHeader->pe_heap_commit, "Pe Heap Commit");

      OutData( DATA_TYPE_DWORD, (void*)&pImageHeader->pe_base_addr,   "PE Base Addr");

      OutData( DATA_TYPE_DWORD, (void*)&pImageHeader->pe_size_image,  "PE Size Image");

      OutData( DATA_TYPE_DWORD, (void*)&pImageHeader->pe_checksum,    "PE CheckSum");

      OutData( DATA_TYPE_DWORD, (void*)&pImageHeader->pe_timedata,    "PE Time&Data");

      OutData( DATA_TYPE_DWORD, (void*)&pImageHeader->pathname_addr,  "PathName Addr");

      OutData( DATA_TYPE_DWORD, (void*)&pImageHeader->filename_addr,  "FileName Addr");

      OutData( DATA_TYPE_DWORD, (void*)&pImageHeader->unicode_filename_addr, "Unicode FileName Addr");

      OutData( DATA_TYPE_DWORD, (void*)&pImageHeader->kernel_thunk_addr, "Kernel Thunk Addr");

      OutData( DATA_TYPE_DWORD, (void*)&pImageHeader->non_kernel_dir_addr, "Non Kernel Dir Addr");

      OutData( DATA_TYPE_DWORD, (void*)&pImageHeader->num_lib_versions, "Num Library Headers");

      OutData( DATA_TYPE_DWORD, (void*)&pImageHeader->lib_vers_addr, "Library Headers Addr");

      OutData( DATA_TYPE_DWORD, (void*)&pImageHeader->kernel_lib_vers_addr, "Kernel Lib Addr");

      OutData( DATA_TYPE_DWORD, (void*)&pImageHeader->xapi_lib_vers_addr, "XAPI Lib Addr");

      OutData( DATA_TYPE_DWORD, (void*)&pImageHeader->logo_bitmap_addr, "Logo Bitmap Addr");

      OutData( DATA_TYPE_DWORD, (void*)&pImageHeader->logo_bitmap_size, "Logo Bitmap Size");

 

}// End OutImageHeader(..)

 

 

// Now all the xbe checking and outputting goes in here, as its bad to have all your

// code in the main function

void ProcessXBE( unsigned char * pXBE , long iFileSize)

{

      // Output the first part of the file - which is ImageHeader

      stImageHeader * pImageHeader = (stImageHeader*)pXBE;

      OutImageHeader( pXBE,  pImageHeader);

 

      // Display the rest of the file as hex

      OutRawHex(pXBE, sizeof(stImageHeader), iFileSize );

}// End ProcessXBE(..)

/***************************************************************************************/

/*                                                                                     */

//------------------------------Program Entry Point------------------------------------//

/*      &