|
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
| |