Skip to content

Instantly share code, notes, and snippets.

@jocopa3
Last active October 3, 2024 14:51
Show Gist options
  • Save jocopa3/7ac6842f9d625a0d326602e108013354 to your computer and use it in GitHub Desktop.
Save jocopa3/7ac6842f9d625a0d326602e108013354 to your computer and use it in GitHub Desktop.
/*
This exports vtable entry symbols to a file.
Run this in the IDA Script Command window (File->Script Command...)
Each vtable segment in the output file is separated by an empty line.
*/
#include <idc.idc>
static getPointerAddress(loc)
{
auto ptrLoc, ptrSize;
ptrSize = ItemSize(loc);
auto i = 0, mul = 0x1;
while(i < ptrSize)
{
ptrLoc = ptrLoc + Byte(loc+i)*mul;
mul = mul * 0x100;
i = i + 1;
}
return(ptrLoc-1);
}
static dumpVtables(fileName)
{
SetStatus(IDA_STATUS_WORK);
Message("Saving vtable info to file...\n");
// Open file with write permissions
auto file = fopen(fileName, "w");
auto seg, loc, ptrLoc;
auto entries = 0, name;
// Get the segment storing vtables
seg = FirstSeg();
while(SegName(seg) != ".data.rel.ro" && seg != BADADDR)
{
seg = NextSeg(seg);
}
// Check if segment is valid address
if(seg == BADADDR)
{
fclose(file);
Message("...Failed to dump vtable info: couldn't find the vtable segment\n");
return;
}
// Loop over the segment and dump vtable info and entries
loc = SegStart(seg);
while(loc < SegEnd(seg))
{
// Get the name of the address
name = Name(loc);
// May need to change in the future
// This label marks the end of most of the game's vtables
if(name == "utf8proc_properties")
{
break;
}
// Demangle the name to make it readable
name = Demangle(name, 0);
// Filter out typeinfo tables
if(substr(name, 1, 9) == "typeinfo")
{
loc = loc + ItemSize(loc);
continue;
}
// Get just the name of the VTable
name = substr(name, 12, strlen(name));
// Makeshift filter; don't care about std, boost, or pplx vtables
if(substr(name, 0, 5) == "std::"
|| substr(name, 0, 7) == "boost::"
|| substr(name, 0, 6) == "pplx::")
{
loc = loc + ItemSize(loc);
continue;
}
if(strlen(name) != 0)
{
// Save Vtable name
if(entries != 0)
{
fprintf(file, "\n");
}
fprintf(file, "%s\n", name);
entries = 0;
} else
{
// Get the pointer address
ptrLoc = getPointerAddress(loc);
// Get the name of the function at the pointer location
name = GetFunctionName(ptrLoc);
if(strlen(name) != 0)
{
// Save mangled function name
fprintf(file, "%s\n", name);
entries = entries + 1;
}
}
loc = loc + ItemSize(loc);
}
fclose(file);
Message("...Done!\n\n");
}
static main()
{
SetStatus(IDA_STATUS_WAITING);
auto promptResult = AskYN(1, "Would you like to save vtable info to a file?");
if(promptResult == 1)
{
auto file = AskFile(1, "*.txt", "Save Vtable Info to File");
if(strlen(file) > 0)
{
dumpVtables(file);
}
}
SetStatus(IDA_STATUS_READY);
}
/*
This imports vtable entry symbols from a file.
This script requires you run ClassInformer first.
Run this in the IDA Script Command window (File->Script Command...)
WARNING: this script is very slow! I recommend you import only vtables
you need, as importing all vtables can take as long as an hour! I am
looking into ways to improve performance, but for now this script is slow.
Each new vtable segment in the input file must have a single empty line
between it and the previous vtable segment.
There are quite a few bugs currently. For example, some vtables like
the BrewingStandBlockEntity are split into two vtables of the same
name by class informer, which throws off this script. I am currently
trying to fix the issues I find.
*/
#include <idc.idc>
static getPointerAddress(loc)
{
auto ptrLoc, ptrSize;
ptrSize = ItemSize(loc);
auto i = 0, mul = 0x1;
while(i < ptrSize)
{
ptrLoc = ptrLoc + Byte(loc+i)*mul;
mul = mul * 0x100;
i = i + 1;
}
return(ptrLoc);
}
// Names vtables using the input file
static nameTables(fileName)
{
SetStatus(IDA_STATUS_WORK);
auto file = fopen(fileName, "r");
auto seg, segLoc, loc, ptrLoc;
auto entries = 0, name, line;
// Get the segment storing vtables
seg = FirstSeg();
while(SegName(seg) != ".data" && seg != BADADDR)
{
seg = NextSeg(seg);
}
// Check if segment is valid address
if(seg == BADADDR)
{
fclose(file);
Message("...Failed to import vtable info: couldn't find the data segment\n");
return;
}
segLoc = SegStart(seg);
// Get the first vtable name
line = readstr(file);
line = substr(line, 0, strlen(line)-1);
// If the vtable struct already exists, delete it
auto structID = GetStrucIdByName(line);
if (structID != -1)
{
Message("Deleted old vtable struct\n");
DelStruc(structID);
}
// Create the struct to import vtable names into
structID = AddStrucEx(-1, line, 0);
// Search for the vtable address
loc = FindText(segLoc, SEARCH_DOWN, 0, 0, " "+line+":");
Message("%s: 0x%x\n", line, loc);
// Skip the first entry in the vtable as it doesn't exist in the Windows version
line = readstr(file);
loc = loc + ItemSize(loc);
line = readstr(file);
auto ind = 0;
while(line != -1)
{
line = substr(line, 0, strlen(line)-1);
if(line == "")
{
line = readstr(file);
line = substr(line, 0, strlen(line)-1);
// If the vtable struct already exists, delete it
structID = GetStrucIdByName(line);
if (structID != -1)
{
DelStruc(structID);
}
// Create the struct to import vtable names into
structID = AddStrucEx(-1, line, 0);
// Search for the vtable address
loc = FindText(segLoc, SEARCH_DOWN, 0, 0, " "+line+":");
// If the vtable can't be found, skip its entries
if(loc == -1) {
Message("%s: not found\n", line);
while(line != "\n")
{
line = readstr(file);
}
continue;
}
Message("%s: 0x%x\n", line, loc);
// Prepare to read the next vtable
line = readstr(file);
loc = loc + ItemSize(loc);
line = readstr(file);
line = substr(line, 0, strlen(line)-1);
ind = 0;
}
// Construct the vtable pointer (There's probably a better way to do this)
ptrLoc = getPointerAddress(loc);
//line = substr(line, 0, strlen(line)-1);
name = Name(ptrLoc);
//Message("[%d] 0x%x: %s : %s\n", ind, ptrLoc, line, name);
if(substr(name, 0, 7) == "nullsub")
{
// Nullsub detected
//MakeComm(loc, Demangle(line, 0));
} else if(CommentEx(ptrLoc, 0) == "reused")
{
// Multi-purpose subroutine detected
//MakeComm(loc, Demangle(line, 0));
}else if(substr(name, 0, 1) == "_" && name != line)
{
// Same function has two different names
// Mark the function as being multi-purpose
MakeComm(ptrLoc, "reused");
MakeNameEx(ptrLoc, "", SN_NOWARN);
//MakeComm(loc, Demangle(line, 0));
} else
{
// Function only has one name and can be safely renamed
MakeNameEx(ptrLoc, line, SN_NOWARN);
}
// Comment the vtable entry with the demangled function name
MakeComm(loc, Demangle(line, 0));
// Add a member of type offset to the vtable struct using its mangled name
while (AddStrucMember(structID, line, ind*8, FF_0OFF, ptrLoc, 8) == STRUC_ERROR_MEMBER_NAME)
{
line = line + "_";
}
loc = loc + ItemSize(loc);
line = readstr(file);
ind = ind + 1;
}
fclose(file);
}
static main()
{
SetStatus(IDA_STATUS_WAITING);
auto file = AskFile(0, "*.txt", "Import Vtable Info File");
// Check if the user hasn't cancelled the file prompt
if(strlen(file) > 0)
{
nameTables(file);
}
SetStatus(IDA_STATUS_READY);
}
@lukeeey
Copy link

lukeeey commented May 9, 2018

Does this work in newer versions @jocopa3? I see no empty lines in the output file

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment