/*
rtti-reader - The Sims Online MSVC RTTI Class Hierarchy Extractor
rtti-reader.cpp - Copyright (c) 2012 Niotso Project
Author(s): Fatbag
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//For information about MSVC RTTI, read:
//
//
//For information about the Windows PE header, read:
//
#include
#include
#include
#include
#include
#include
#ifndef read_int32
#define read_uint32(x) (unsigned)(((x)[0]<<(8*0)) | ((x)[1]<<(8*1)) | ((x)[2]<<(8*2)) | ((x)[3]<<(8*3)))
#define read_uint16(x) (unsigned)(((x)[0]<<(8*0)) | ((x)[1]<<(8*1)))
#endif
#ifndef write_int32
#define write_uint32(dest, src) do { \
(dest)[0] = ((src)&0x000000FF)>>(8*0); \
(dest)[1] = ((src)&0x0000FF00)>>(8*1); \
(dest)[2] = ((src)&0x00FF0000)>>(8*2); \
(dest)[3] = ((src)&0xFF000000)>>(8*3); \
} while(0)
#endif
static void Shutdown_M(const char * Message);
struct Segment {
size_t size, offset;
Segment() : size(0) {}
};
struct ByteReaderContext {
size_t start, position, end;
bool seek(size_t pos){
if(pos > end)
return false;
position = pos;
return true;
}
bool skip(int pos = 1){
if(position + pos > end)
return false;
position += pos;
return true;
}
};
struct PaddingTest {
uint32_t A;
uint32_t B;
};
template
struct RTTIVector {
size_t Count, SizeAllocated;
T * Buffer;
void init(){
Count = 0, SizeAllocated = sizeof(T);
if(!(Buffer = (T*) malloc(sizeof(T))))
Shutdown_M("Failed to allocate memory");
}
T& add(){
if((Count+1)*sizeof(T) > SizeAllocated){
void * ptr;
if(SizeAllocated > SIZE_MAX/2 || !(ptr = (T*) realloc(Buffer, SizeAllocated<<=1)))
Shutdown_M("Failed to allocate memory");
Buffer = (T *) ptr;
}
return Buffer[Count++];
}
};
struct RTTITypeDescriptor {
struct {
uint32_t Address;
uint32_t VTableAddress;
uint32_t Reserved;
} Fields;
char * Name;
char * UnmangledName;
};
struct RTTIBaseClassDescriptor {
struct {
uint32_t Address;
uint32_t TypeDescriptorAddress;
uint32_t BaseClassCount;
uint32_t MemberOffset;
uint32_t COLAddressOffset;
uint32_t VTableOffset;
uint32_t Attributes;
} Fields;
RTTITypeDescriptor TD;
};
struct RTTIClassHierarchyDescriptor {
struct {
uint32_t Address;
uint32_t Reserved;
uint32_t Attributes;
uint32_t BaseClassCount;
uint32_t BaseClassListAddress;
} Fields;
RTTIVector BCDL;
};
struct RTTICompleteObjectLocator {
struct {
uint32_t Address;
uint32_t Reserved;
uint32_t Offset;
uint32_t CDOffset;
uint32_t TypeDescriptorAddress;
uint32_t ClassDescriptorAddress;
} Fields;
uint32_t VTableAddress;
};
struct RTTIClass {
RTTIVector COLL;
RTTITypeDescriptor TD;
RTTIClassHierarchyDescriptor CHD;
void init(){
COLL.init();
CHD.BCDL.init();
}
bool DependsOn(const RTTIClass& X) const {
for(uint32_t i=1; i(Aptr);
const RTTIClass& B = *reinterpret_cast(Bptr);
if(A.DependsOn(B)) return 1; //If A depends on B, A > B
if(B.DependsOn(A)) return -1; //If B depends on A, B > A
return strcmp(A.TD.UnmangledName, B.TD.UnmangledName);
}
};
struct PEFile {
static PEFile * ptr;
FILE * hFile;
uint8_t * Data;
Segment rdata, data;
ByteReaderContext brc;
PEFile(const char * filename) : Data(NULL) {
PEFile::ptr = this;
hFile = fopen(filename, "rb");
if(!hFile)
Shutdown_M("The specified input file does not exist or could not be opened for reading");
fseek(hFile, 0, SEEK_END);
size_t FileSize = ftell(hFile);
if(FileSize < 64)
Shutdown_M("Not a valid Windows PE file");
fseek(hFile, 0, SEEK_SET);
Data = (uint8_t*) malloc(FileSize);
if(!Data)
Shutdown_M("Failed to allocate memory");
if(fread(Data, 1, FileSize, hFile) != FileSize)
Shutdown_M("Failed to read input file");
fclose(hFile);
brc.start = brc.position = 0;
brc.end = FileSize;
}
~PEFile(){
if(hFile)
fclose(hFile);
free(Data);
}
inline bool seek(size_t pos, int offset = 0){
return brc.seek(pos + offset);
}
inline bool skip(size_t pos = 1, int offset = 0){
return brc.skip(pos + offset);
}
int nextchar(){
if(!brc.skip())
return EOF;
return Data[brc.position-1];
}
void lookat(Segment& segment){
brc.start = brc.position = segment.offset;
brc.end = segment.offset + segment.size;
}
uint32_t read32(){
return brc.skip(4) ? read_uint32(Data+brc.position-4) : -1;
}
uint16_t read16(){
return brc.skip(2) ? read_uint16(Data+brc.position-2) : -1;
}
size_t strlen(){
size_t i = (size_t)-1;
int byte;
do {
byte = nextchar();
if(byte == EOF)
return -1;
i++;
} while(byte);
skip(-(int)i-1); //Seek back
return i;
}
bool strcpy(char * dest){
int i = 0;
do {
int byte = nextchar();
if(byte == EOF)
return false;
*dest = (char) byte;
i--;
} while(*dest++);
skip(i); //Seek back
return true;
}
int strcmp(const char * data){
int i = 0;
int byte;
do {
byte = nextchar();
if(byte == EOF)
return -1;
i--;
} while(*data++ && (char)byte == *(data-1));
skip(i); //Seek back
return byte - *(data-1);
}
enum { Parse_QuestionMark = 1};
bool memfind(const char * data, size_t size, int MemParse = 0){
size_t i = 0;
do {
int byte = nextchar();
if(byte == EOF)
return false;
else if((char)byte != data[i] && (!MemParse || data[i] != '?')){
skip(-(int)i);
i = 0;
} else i++;
} while(i brc.end - brc.position)
return false;
uint32_t *field = reinterpret_cast(ptr);
*field = brc.position; //The Address field always comes first
do {
ptr += padding; count -= padding;
field = reinterpret_cast(ptr);
*field = read32();
} while(count);
return true;
}
template
inline bool Fill(T& context) {
return GenericFill(reinterpret_cast(&context.Fields), sizeof(context.Fields));
}
};
PEFile * PEFile::ptr;
static void Shutdown_M(const char * Message){
fprintf(stderr, "rtti-reader: error: %s.\n", Message);
if(PEFile::ptr)
PEFile::ptr->~PEFile();
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[]){
unsigned i;
const char * InFile, * BaseName;
if(argc != 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")){
printf("Usage: rtti-reader infile\n"
"Extract class information from an EXE or DLL using MSVC RTTI.\n"
"\n"
"Report bugs to .\n"
"rtti-reader is maintained by the Niotso project.\n"
"Home page: \n");
return 0;
}
InFile = argv[1];
int slash;
for(i=0, slash=-1; InFile[i]; i++)
if(InFile[i] == '/' || InFile[i] == '\\') slash = i;
BaseName = InFile + slash + 1;
PEFile DLL(InFile);
if(DLL.read16() != 0x5A4D) //"MZ"
Shutdown_M("Not a valid Windows PE file");
DLL.seek(60);
DLL.seek(DLL.read32(), 6); unsigned SegmentCount = DLL.read16();
DLL.skip(12); unsigned OptionalHeaderSize = DLL.read16();
DLL.skip(30); unsigned ImageBase = DLL.read32();
DLL.skip(OptionalHeaderSize, -32);
for(i=0; i UINT_MAX-DLL.rdata.offset || DLL.rdata.size+DLL.rdata.offset > DLL.brc.end)
Shutdown_M(".rdata segment is invalid");
else if(DLL.data.size > UINT_MAX-DLL.data.offset || DLL.data.size+DLL.data.offset > DLL.brc.end)
Shutdown_M(".data segment is invalid");
printf("\n****\n** [ 1 of 2] RTTI Report for %s\n****\n", BaseName);
RTTIVector RCL;
RCL.init();
DLL.lookat(DLL.data);
unsigned TotalClassCount = 0;
while(DLL.skip(8) && DLL.memfind(".?AV", 4, PEFile::Parse_QuestionMark)){
TotalClassCount++;
size_t length = DLL.strlen();
if(length == (unsigned)-1)
Shutdown_M("Unexpectedly reached end of binary");
size_t TDAddress = DLL.brc.position + ImageBase - 8, datapos = DLL.brc.position + length + 1;
DLL.lookat(DLL.rdata);
RTTIClass * RCPointer = NULL;
for(size_t rdatapos = DLL.brc.position + 12;
DLL.seek(rdatapos) && DLL.find32(TDAddress); rdatapos += 4, DLL.lookat(DLL.rdata)){
//Find all complete object locators that belong to this class
rdatapos = DLL.brc.position;
if(!DLL.skip(4))
continue;
size_t CDAddress = DLL.read32() - ImageBase;
if(CDAddress < DLL.brc.start || CDAddress > DLL.brc.end-4)
continue; //This was a base class descriptor
//Add this COL to our respective RTTIClass
bool newclass = false;
if(RCPointer == NULL){
//This is a new class; add it to the RCL
newclass = true;
RTTIClass& RC = RCL.add();
RCPointer = &RC;
RC.init();
}
RTTIClass& RC = *RCPointer;
RTTICompleteObjectLocator& COL = RC.COLL.add();
DLL.seek(rdatapos,-12);
size_t COLAddress = DLL.brc.position + ImageBase;
if(!DLL.Fill(COL))
Shutdown_M("Unexpectedly reached end of binary");
DLL.lookat(DLL.rdata);
COL.VTableAddress = (DLL.find32(COLAddress)) ? DLL.brc.position + ImageBase + 4 : (uint32_t)-1;
if(newclass){
if(!DLL.seek(COL.Fields.ClassDescriptorAddress - ImageBase))
Shutdown_M("Unexpectedly reached end of binary");
RTTIClassHierarchyDescriptor& CHD = RC.CHD;
if(!DLL.Fill(CHD))
Shutdown_M("Unexpectedly reached end of binary");
if(!DLL.seek(CHD.Fields.BaseClassListAddress - ImageBase))
Shutdown_M("Unexpectedly reached end of binary");
size_t bcdlpos;
for(i=0, bcdlpos = DLL.brc.position; i= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') || c == '_')
? c : ' ';
}
BCD.TD.UnmangledName[length-6] = '\0';
if(newclass){
newclass = false;
memcpy(&RC.TD, &BCD.TD, sizeof(RTTITypeDescriptor));
}
}
}
}
DLL.lookat(DLL.data);
DLL.seek(datapos);
}
for (i=0; i 1){
//The first BCD always refers to the class itself, e.g. class A "depends on class A".
printf(" : %s", RC.CHD.BCDL.Buffer[1].TD.UnmangledName);
for(uint32_t j=2; j