|
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------------------------------------//
/* & | |