mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 02:09:44 +02:00

Adds support for simple DWARF5 debug info. Split .dwo not supported. Uses bookmarks to tag problematic issues instead of log messages during import.
1608 lines
54 KiB
Java
1608 lines
54 KiB
Java
/* ###
|
|
* IP: GHIDRA
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
// PROTOTYPE Script to recover class information using RTTI structures.
|
|
// Note: This script does not compile on versions of Ghidra prior to 9.2 or in 9.2.1.
|
|
// This script uses information found in RTTI structures to figure out
|
|
// class hierarchy, inheritance types, constructors and destructors, class data types, and more.
|
|
// If program has pdb information, it uses it to help fill in class structures with known names
|
|
// and types for class member data.
|
|
// If program does not have pdb, or incomplete pdb data types, it uses the decompiler pcode store
|
|
// information to fill in class structures with assumed data types for class member data. These
|
|
// data types are not always complete and may be filled in with undefined place holders of
|
|
// the same size as the associated data.
|
|
// There are many options that can be changed by editing flags at the top of the script.
|
|
// There are options to find missing functions that have not been disassembled, print or output
|
|
// class information in various formats, and to graph the class hierarchies.
|
|
// To best see the results of this script, look in the SymbolTree in the Classes folder. If a class
|
|
// has RTTI, you will see the class functions, vftable(s), and more there. Click on class functions
|
|
// and look at them in the decompiler to see how the class data types have improved the decompiler
|
|
// results. To see class data definitions, either hover on the this param in the decompiler or look
|
|
// in the DataTypeManager/<program_name>/ClassDataTypes/<class_name> folder.
|
|
// NOTE: As this is a prototype script, the location, names, and design of data types created by
|
|
// this script and default vfunctions named by this script are likely to change in the future
|
|
// once an official design for Object Oriented representation is determined.
|
|
// NOTE: Windows class recovery is more complete and tested than Gcc class recovery, which is still
|
|
// in early stages of development. Gcc class data types are only recovered for classes without
|
|
// virtual inheritance but if the program contains DWARF, there will be some amount of data recovered
|
|
// by the DWARF analyzer.
|
|
// NOTE: For likely the best results, run this script on freshly analyzed programs. No testing has been
|
|
// done on user marked-up programs.
|
|
// NOTE: After running this script if you edit function signatures in the listing for a particular
|
|
// class and wish to update the corresponding class data function definition data types (vftable
|
|
// structure field names, ...) then you can run the ApplyClassFunctionSignatureUpdatesScript.java
|
|
// to have it do so for you. See that script's description for more info.
|
|
// Conversely, if you update a particular class's function definitions in the data type manager and
|
|
// wish to have related function signatures in the listing updated, as well as other data types that
|
|
// are related, then run the ApplyClassFunctionDefinitionsUpdatesScript.java to do so. See that script's
|
|
// description for more info. At some point, the Ghidra API will be updated to do the updates
|
|
// automatically instead of needing the mentioned scripts to do so.
|
|
|
|
//@category C++
|
|
|
|
import java.io.File;
|
|
import java.io.PrintWriter;
|
|
import java.util.*;
|
|
import java.util.stream.Collectors;
|
|
|
|
import classrecovery.*;
|
|
import generic.theme.GThemeDefaults.Colors.Palette;
|
|
import ghidra.app.decompiler.DecompInterface;
|
|
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
|
|
import ghidra.app.plugin.core.analysis.DecompilerFunctionAnalyzer;
|
|
import ghidra.app.script.GhidraScript;
|
|
import ghidra.app.services.Analyzer;
|
|
import ghidra.app.services.GraphDisplayBroker;
|
|
import ghidra.app.util.NamespaceUtils;
|
|
import ghidra.app.util.bin.format.dwarf.DWARFFunctionImporter;
|
|
import ghidra.app.util.bin.format.dwarf.DWARFProgram;
|
|
import ghidra.app.util.bin.format.dwarf.sectionprovider.DWARFSectionProvider;
|
|
import ghidra.app.util.bin.format.dwarf.sectionprovider.DWARFSectionProviderFactory;
|
|
import ghidra.app.util.bin.format.pdb.PdbParserConstants;
|
|
import ghidra.app.util.importer.MessageLog;
|
|
import ghidra.app.util.opinion.PeLoader;
|
|
import ghidra.framework.options.Options;
|
|
import ghidra.framework.plugintool.PluginTool;
|
|
import ghidra.program.model.address.*;
|
|
import ghidra.program.model.data.*;
|
|
import ghidra.program.model.listing.*;
|
|
import ghidra.program.model.mem.MemoryBlock;
|
|
import ghidra.program.model.reloc.Relocation;
|
|
import ghidra.program.model.reloc.RelocationTable;
|
|
import ghidra.program.model.symbol.Symbol;
|
|
import ghidra.program.util.GhidraProgramUtilities;
|
|
import ghidra.service.graph.*;
|
|
import ghidra.util.exception.CancelledException;
|
|
import ghidra.util.exception.GraphException;
|
|
import ghidra.util.task.TaskMonitor;
|
|
|
|
public class RecoverClassesFromRTTIScript extends GhidraScript {
|
|
|
|
public static final String RTTI_FOUND_OPTION = "RTTI Found";
|
|
|
|
// print c-like class definitions to the console
|
|
private static final boolean PRINT_CLASS_DEFINITIONS = false;
|
|
|
|
// print class information (parent class, children classes, vfunctions, member data) to console
|
|
private static final boolean PRINT_CLASS_INFO = false;
|
|
|
|
// print class parent(s) and children classes to console
|
|
private static final boolean PRINT_CLASS_PARENTS_AND_CHILDREN = false;
|
|
|
|
// print one line hierarchy for each class with no children (child : parent : grandparent: ...)
|
|
// if multiple inheritance print multiple parents on new line directly below end of their child
|
|
private static final boolean PRINT_CLASS_HIERARCHIES = false;
|
|
|
|
// these do the same as above but print to a file
|
|
private static final boolean OUTPUT_CLASS_DEFINITIONS = false;
|
|
private static final boolean OUTPUT_CLASS_INFO = false;
|
|
private static final boolean OUTPUT_CLASS_PARENTS_AND_CHILDREN = false;
|
|
private static final boolean OUTPUT_SIMPLE_CLASS_HIERARCHIES = false;
|
|
|
|
// print counts of found items
|
|
private static final boolean PRINT_COUNTS = true;
|
|
|
|
// recommended to find missing RTTI structure info in older programs and to find any missing
|
|
// potential constructor destructor functions because analysis did not make them functions yet.
|
|
// They are either undefined bytes or code that is not in a function.
|
|
private static final boolean FIXUP_PROGRAM = true;
|
|
|
|
// bookmark all constructor/destructor functions figured out by this script
|
|
private static final boolean BOOKMARK_FOUND_FUNCTIONS = true;
|
|
|
|
// show a graph of class hierarchies after script is complete
|
|
// no parent = blue vertex
|
|
// single parent = green vertex
|
|
// multiple parents = red vertex
|
|
// edge between child and parent is orange if child inherits the parent virtually
|
|
// edge between child and parent is lime green if child inherits the parent non-virtually
|
|
private static final boolean GRAPH_CLASS_HIERARCHIES = false;
|
|
private static final String NO_INHERITANCE = "No Inheritance";
|
|
private static final String SINGLE_INHERITANCE = "Single Inheritance";
|
|
private static final String MULTIPLE_INHERITANCE = "Multiple Inheritance";
|
|
private static final String VIRTUAL_INHERITANCE = "Virtual Inheritance";
|
|
private static final String NON_VIRTUAL_INHERITANCE = "Non-virtual Inheritance";
|
|
|
|
// show shortened class template names in class structure field names
|
|
private static final boolean USE_SHORT_TEMPLATE_NAMES_IN_STRUCTURE_FIELDS = true;
|
|
|
|
private static final String CLASS_DATA_STRUCT_NAME = "_data";
|
|
|
|
private static final String CONSTRUCTOR_BOOKMARK = "CONSTRUCTOR";
|
|
private static final String DESTRUCTOR_BOOKMARK = "DESTRUCTOR";
|
|
|
|
private static final String INDETERMINATE_BOOKMARK = "INDETERMINATE";
|
|
boolean programHasRTTIApplied = false;
|
|
boolean hasDebugSymbols = false;
|
|
String ghidraVersion = null;
|
|
|
|
DecompilerScriptUtils decompilerUtils;
|
|
DataTypeManager dataTypeManager;
|
|
|
|
int defaultPointerSize;
|
|
|
|
RTTIClassRecoverer recoverClassesFromRTTI;
|
|
|
|
boolean nameVfunctions = false;
|
|
|
|
AnalysisMode analysisMode = AnalysisMode.SUSPENDED;
|
|
|
|
@Override
|
|
public void run() throws Exception {
|
|
|
|
String errorMsg = validate();
|
|
|
|
if (!errorMsg.isEmpty()) {
|
|
println(errorMsg);
|
|
return;
|
|
}
|
|
|
|
if (!isGcc() && isWindows()) {
|
|
|
|
if (!isRttiAnalyzed()) {
|
|
println("Running the RTTIAnalyzer...");
|
|
analysisMode = AnalysisMode.ENABLED;
|
|
runScript("RunRttiAnalyzerScript.java");
|
|
analysisMode = AnalysisMode.SUSPENDED;
|
|
|
|
if (!isRttiAnalyzed()) {
|
|
println("The RTTI Analyzer did not complete successfully.");
|
|
return;
|
|
}
|
|
|
|
if (!hasRtti()) {
|
|
println("This program does not contain RTTI.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
hasDebugSymbols = isPDBLoadedInProgram();
|
|
nameVfunctions = !hasDebugSymbols;
|
|
recoverClassesFromRTTI =
|
|
new RTTIWindowsClassRecoverer(currentProgram, currentLocation, state.getTool(),
|
|
this, BOOKMARK_FOUND_FUNCTIONS, USE_SHORT_TEMPLATE_NAMES_IN_STRUCTURE_FIELDS,
|
|
nameVfunctions, hasDebugSymbols, monitor);
|
|
}
|
|
else if (isPE() && isGcc()) {
|
|
|
|
println("Program is a gcc compiled PE.");
|
|
|
|
boolean runGcc;
|
|
if (isRunningHeadless()) {
|
|
runGcc = true;
|
|
}
|
|
else {
|
|
runGcc = askYesNo("Gcc Class Recovery Still Under Development",
|
|
"I understand that Gcc class recovery is still under development and my " +
|
|
"results will be incomplete but want to run this anyway.");
|
|
}
|
|
if (!runGcc) {
|
|
return;
|
|
}
|
|
//run fixup old elf relocations script
|
|
runScript("FixElfExternalOffsetDataRelocationScript.java");
|
|
recoverClassesFromRTTI =
|
|
new RTTIGccClassRecoverer(currentProgram, currentLocation, state.getTool(), this,
|
|
BOOKMARK_FOUND_FUNCTIONS, USE_SHORT_TEMPLATE_NAMES_IN_STRUCTURE_FIELDS,
|
|
nameVfunctions, hasDebugSymbols, monitor);
|
|
}
|
|
else if (isGcc()) {
|
|
|
|
boolean runGcc;
|
|
|
|
if (isRunningHeadless()) {
|
|
runGcc = true;
|
|
}
|
|
else {
|
|
runGcc = askYesNo("Gcc Class Recovery Still Under Development",
|
|
"I understand that Gcc class recovery is still under development and my results will be incomplete but want to run this anyway.");
|
|
}
|
|
|
|
if (!runGcc) {
|
|
return;
|
|
}
|
|
|
|
//run fixup old elf relocations script
|
|
runScript("FixElfExternalOffsetDataRelocationScript.java");
|
|
|
|
hasDebugSymbols = isDwarfLoadedInProgram();
|
|
if (hasDwarf() && !hasDebugSymbols) {
|
|
println(
|
|
"The program contains DWARF but the DWARF analyzer has not been run. Please " +
|
|
"run the DWARF analyzer to get best results from this script.");
|
|
return;
|
|
}
|
|
nameVfunctions = !hasDebugSymbols;
|
|
recoverClassesFromRTTI =
|
|
new RTTIGccClassRecoverer(currentProgram, currentLocation, state.getTool(), this,
|
|
BOOKMARK_FOUND_FUNCTIONS, USE_SHORT_TEMPLATE_NAMES_IN_STRUCTURE_FIELDS,
|
|
nameVfunctions, hasDebugSymbols, monitor);
|
|
}
|
|
else {
|
|
println("This script will not work on this program type");
|
|
return;
|
|
}
|
|
|
|
if (!recoverClassesFromRTTI.containsRTTI()) {
|
|
println(
|
|
"This program does not appear to contain any processed RTTI information. Either " +
|
|
"it does not contain any or the RTTI Analyzer was not run.");
|
|
return;
|
|
}
|
|
|
|
if (!recoverClassesFromRTTI.isValidProgramType()) {
|
|
println("This script will not work on this program type");
|
|
return;
|
|
}
|
|
|
|
if (!recoverClassesFromRTTI.isValidProgramSize()) {
|
|
println("This program is not a valid program address size.");
|
|
return;
|
|
}
|
|
|
|
decompilerUtils = recoverClassesFromRTTI.getDecompilerUtils();
|
|
DecompInterface decompInterface = decompilerUtils.getDecompilerInterface();
|
|
|
|
if (decompInterface.getProgram() == null) {
|
|
println("The decompiler interface cannot open the current program: " +
|
|
decompInterface.getLastMessage());
|
|
return;
|
|
}
|
|
|
|
defaultPointerSize = recoverClassesFromRTTI.getDefaultPointerSize();
|
|
dataTypeManager = recoverClassesFromRTTI.getDataTypeManager();
|
|
|
|
PrintWriter out = null;
|
|
|
|
boolean doOutput = shouldDoOutput();
|
|
|
|
if (doOutput) {
|
|
File outputFile = askFile("Choose Output File", "Please choose an output file");
|
|
out = new PrintWriter(outputFile);
|
|
}
|
|
|
|
currentProgram.setPreferredRootNamespaceCategoryPath(
|
|
"/" + RecoveredClassHelper.DTM_CLASS_DATA_FOLDER_NAME);
|
|
|
|
if (FIXUP_PROGRAM) {
|
|
println("Checking for missing RTTI information and undefined constructor/destructor " +
|
|
"functions and creating if possible " + "to find entry point...");
|
|
AddressSetView beforeScriptChanges = currentProgram.getChanges().getAddressSet();
|
|
|
|
analysisMode = AnalysisMode.ENABLED;
|
|
|
|
recoverClassesFromRTTI.fixUpProgram();
|
|
analyzeProgramChanges(beforeScriptChanges);
|
|
|
|
analysisMode = AnalysisMode.SUSPENDED;
|
|
}
|
|
|
|
println("Recovering classes using RTTI...");
|
|
List<RecoveredClass> recoveredClasses = recoverClassesFromRTTI.createRecoveredClasses();
|
|
|
|
if (recoveredClasses == null) {
|
|
println("Error recovering classes");
|
|
return;
|
|
}
|
|
|
|
if (recoveredClasses.isEmpty()) {
|
|
println("No new classes recovered.");
|
|
if (doOutput) {
|
|
out.close();
|
|
}
|
|
return;
|
|
}
|
|
|
|
println("Identified " + recoveredClasses.size() + " classes to process and " +
|
|
getNumberOfConstructorsOrDestructors(recoveredClasses) +
|
|
" class member functions to assign.");
|
|
|
|
if (!hasDebugSymbols) {
|
|
|
|
if (BOOKMARK_FOUND_FUNCTIONS) {
|
|
bookmarkFunctions(recoveredClasses);
|
|
println("See Bookmark Manager for a list of functions by type.");
|
|
}
|
|
}
|
|
|
|
callOptionalOutputMethods(recoveredClasses, out);
|
|
|
|
if (doOutput) {
|
|
out.close();
|
|
}
|
|
|
|
if (GRAPH_CLASS_HIERARCHIES) {
|
|
AttributedGraph graph = createGraph(recoveredClasses);
|
|
showGraph(graph);
|
|
}
|
|
|
|
decompilerUtils.disposeDecompilerInterface();
|
|
|
|
}
|
|
|
|
private boolean hasDwarf() {
|
|
if (DWARFProgram.isDWARF(currentProgram)) {
|
|
DWARFSectionProvider dsp =
|
|
DWARFSectionProviderFactory.createSectionProviderFor(currentProgram, monitor);
|
|
if (dsp == null) {
|
|
return false;
|
|
}
|
|
dsp.close();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Method to determine if pdb info has been applied to the program
|
|
* @return true if pdb info has been applied to program
|
|
*/
|
|
private boolean isPDBLoadedInProgram() {
|
|
Options options = currentProgram.getOptions(Program.PROGRAM_INFO);
|
|
return options.getBoolean(PdbParserConstants.PDB_LOADED, false);
|
|
}
|
|
|
|
private boolean isDwarfLoadedInProgram() {
|
|
|
|
Options options = currentProgram.getOptions(Program.PROGRAM_INFO);
|
|
|
|
return (DWARFFunctionImporter.hasDWARFProgModule(currentProgram,
|
|
DWARFProgram.DWARF_ROOT_NAME) || options.getBoolean("DWARF Loaded", false));
|
|
}
|
|
|
|
public String validate() throws CancelledException {
|
|
|
|
if (currentProgram == null) {
|
|
return ("There is no open program");
|
|
}
|
|
|
|
if (!GhidraProgramUtilities.isAnalyzed(currentProgram)) {
|
|
return ("The program has not been analyzed. Please run auto-analysis and make sure " +
|
|
"the RTTI analzer is one of the analyzers enabled.");
|
|
}
|
|
|
|
if (isRttiAnalyzed() && !hasRtti()) {
|
|
return ("This program does not contain RTTI.");
|
|
}
|
|
|
|
CategoryPath path =
|
|
new CategoryPath(CategoryPath.ROOT, RecoveredClassHelper.DTM_CLASS_DATA_FOLDER_NAME);
|
|
if (currentProgram.getDataTypeManager().containsCategory(path)) {
|
|
return ("This script has already been run on this program");
|
|
}
|
|
|
|
if (!checkGhidraVersion()) {
|
|
return ("This script only works with Ghidra version 9.2, 9.2.2 and later. It does" +
|
|
" not work on Ghidra 9.2.1 or on versions prior to 9.2");
|
|
}
|
|
|
|
if (!isGcc() && !isWindows()) {
|
|
return ("This script only handles Windows PE and Gcc programs");
|
|
}
|
|
|
|
defaultPointerSize = currentProgram.getDefaultPointerSize();
|
|
if (defaultPointerSize != 4 && defaultPointerSize != 8) {
|
|
return ("This script only works on 32 or 64 bit programs");
|
|
}
|
|
|
|
// check that gcc loader or mingw analyzer has fixed the relocations correctly
|
|
if (isGcc()) {
|
|
|
|
// first check that there is even rtti by searching the special string in memory
|
|
if (!isStringInProgramMemory("class_type_info")) {
|
|
return ("This program does not contain RTTI.");
|
|
}
|
|
|
|
// then check to see if the special typeinfo namespace is in external space
|
|
// if so then relocations are present and have not been fixed up because when fixed up
|
|
// the namespace gets moved to inside program space
|
|
if (isExternalNamespace("__cxxabiv1::__class_type_info")) {
|
|
return ("This program's relocations were not correctly fixed so the script cannot " +
|
|
"continue. If this program is mingw this is a known issue and " +
|
|
"will be fixed in a later release. For all other gcc programs please " +
|
|
"contact the Ghidra team so this issue can be fixed.");
|
|
}
|
|
|
|
if (hasUnhandledRelocations()) {
|
|
return ("This program has unhandled elf relocations so cannot continue. Please " +
|
|
"contact the Ghidra team for assistance.");
|
|
}
|
|
}
|
|
|
|
return new String();
|
|
|
|
}
|
|
|
|
private boolean hasUnhandledRelocations() throws CancelledException {
|
|
|
|
RelocationTable relocationTable = currentProgram.getRelocationTable();
|
|
|
|
Iterator<Relocation> relocations = relocationTable.getRelocations();
|
|
|
|
while (relocations.hasNext()) {
|
|
monitor.checkCancelled();
|
|
Relocation r = relocations.next();
|
|
|
|
if (r.getSymbolName().contains("class_type_info") &&
|
|
(r.getStatus() != Relocation.Status.APPLIED &&
|
|
r.getStatus() != Relocation.Status.APPLIED_OTHER &&
|
|
r.getStatus() != Relocation.Status.SKIPPED)) {
|
|
return true;
|
|
}
|
|
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private void analyzeProgramChanges(AddressSetView beforeChanges) throws Exception {
|
|
|
|
AddressSetView addressSet = currentProgram.getChanges().getAddressSet();
|
|
addressSet = addressSet.subtract(beforeChanges);
|
|
if (!addressSet.isEmpty()) {
|
|
println("analyzing program changes ...");
|
|
setAnalysisOption(currentProgram, "Decompiler Parameter ID", "true");
|
|
analyzeChanges(currentProgram);
|
|
addressSet = currentProgram.getChanges().getAddressSet();
|
|
analyzeChangesWithDecompilerFunctionAnalyzer(
|
|
currentProgram.getChanges().getAddressSet());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public AnalysisMode getScriptAnalysisMode() {
|
|
|
|
return analysisMode;
|
|
|
|
}
|
|
|
|
@Override
|
|
public void analyzeChanges(Program program) {
|
|
AutoAnalysisManager mgr = AutoAnalysisManager.getAnalysisManager(program);
|
|
mgr.startAnalysis(monitor, false);
|
|
}
|
|
|
|
/**
|
|
* Method to create a class hierarchy graph where the parents are at the top of the graph and
|
|
* the children at the bottom. Classes with no parents have blue nodes. Classes with a single
|
|
* parent have green nodes. Classes with multiple parents have red nodes. Classes with virtual
|
|
* inheritance have orange edges between parent and child. Classes with non-virtual inheritance
|
|
* have lime green edges between parent and child.
|
|
* @param recoveredClasses the list of classes
|
|
* @return a hierarchy graph for the given list of classes
|
|
* @throws CancelledException if cancelled
|
|
*/
|
|
private AttributedGraph createGraph(List<RecoveredClass> recoveredClasses)
|
|
throws CancelledException {
|
|
|
|
GraphType graphType =
|
|
new GraphTypeBuilder("Class Hierarchy Graph").vertexType(NO_INHERITANCE)
|
|
.vertexType(SINGLE_INHERITANCE)
|
|
.vertexType(MULTIPLE_INHERITANCE)
|
|
.edgeType(NON_VIRTUAL_INHERITANCE)
|
|
.edgeType(VIRTUAL_INHERITANCE)
|
|
.build();
|
|
|
|
AttributedGraph g = new AttributedGraph("Recovered Classes Graph", graphType);
|
|
|
|
for (RecoveredClass recoveredClass : recoveredClasses) {
|
|
monitor.checkCancelled();
|
|
|
|
AttributedVertex classVertex =
|
|
g.addVertex(recoveredClass.getClassPath().getPath(), recoveredClass.getName());
|
|
|
|
Map<RecoveredClass, List<RecoveredClass>> classHierarchyMap =
|
|
recoveredClass.getClassHierarchyMap();
|
|
|
|
// no parent = blue vertex
|
|
if (classHierarchyMap.isEmpty()) {
|
|
classVertex.setVertexType(NO_INHERITANCE);
|
|
classVertex.setDescription(recoveredClass.getClassPath().getPath());
|
|
continue;
|
|
}
|
|
|
|
Set<RecoveredClass> parents = classHierarchyMap.keySet();
|
|
|
|
// single parent = green vertex
|
|
if (parents.size() == 1) {
|
|
classVertex.setVertexType(SINGLE_INHERITANCE);
|
|
}
|
|
// multiple parents = red vertex
|
|
else {
|
|
classVertex.setVertexType(MULTIPLE_INHERITANCE);
|
|
}
|
|
|
|
classVertex.setDescription(recoveredClass.getClassPath().getPath());
|
|
|
|
Map<RecoveredClass, Boolean> parentToBaseTypeMap =
|
|
recoveredClass.getParentToBaseTypeMap();
|
|
|
|
for (RecoveredClass parent : parents) {
|
|
monitor.checkCancelled();
|
|
AttributedVertex parentVertex =
|
|
g.addVertex(parent.getClassPath().getPath(), parent.getName());
|
|
|
|
parentVertex.setDescription(parent.getClassPath().getPath());
|
|
|
|
AttributedEdge edge = g.addEdge(parentVertex, classVertex);
|
|
|
|
Boolean isVirtualParent = parentToBaseTypeMap.get(parent);
|
|
if (isVirtualParent == null) {
|
|
continue;
|
|
}
|
|
|
|
// edge between child and parent is orange if child inherits the parent virtually
|
|
if (isVirtualParent) {
|
|
edge.setEdgeType(VIRTUAL_INHERITANCE);
|
|
}
|
|
else {
|
|
edge.setEdgeType(NON_VIRTUAL_INHERITANCE);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return g;
|
|
}
|
|
|
|
/**
|
|
* Method to display the given graph
|
|
* @param graph the given graph
|
|
* @throws GraphException if the graph service cannot get the graph display
|
|
* @throws CancelledException if drawing the graph is cancelled
|
|
*/
|
|
private void showGraph(AttributedGraph graph) throws GraphException, CancelledException {
|
|
|
|
GraphDisplay display;
|
|
PluginTool tool = state.getTool();
|
|
GraphDisplayBroker broker = tool.getService(GraphDisplayBroker.class);
|
|
GraphDisplayProvider service = broker.getGraphDisplayProvider("Default Graph Display");
|
|
display = service.getGraphDisplay(false, TaskMonitor.DUMMY);
|
|
|
|
GraphDisplayOptions graphOptions = new GraphDisplayOptionsBuilder(graph.getGraphType())
|
|
.vertex(NO_INHERITANCE, VertexShape.RECTANGLE, Palette.BLUE)
|
|
.vertex(SINGLE_INHERITANCE, VertexShape.RECTANGLE, Palette.GREEN)
|
|
.vertex(MULTIPLE_INHERITANCE, VertexShape.RECTANGLE, Palette.RED)
|
|
.edge(NON_VIRTUAL_INHERITANCE, Palette.LIME)
|
|
.edge(VIRTUAL_INHERITANCE, Palette.ORANGE)
|
|
.defaultVertexColor(Palette.PURPLE)
|
|
.defaultEdgeColor(Palette.PURPLE)
|
|
.defaultLayoutAlgorithm("Compact Hierarchical")
|
|
.build();
|
|
|
|
display.setGraph(graph, graphOptions, "Recovered Classes Graph", false, TaskMonitor.DUMMY);
|
|
}
|
|
|
|
/**
|
|
* Method to determine, based on the script's output settings, if the script will do any output
|
|
* @return true if the script will do output, false otherwise
|
|
*/
|
|
private boolean shouldDoOutput() {
|
|
|
|
boolean doOutput = OUTPUT_CLASS_DEFINITIONS || OUTPUT_CLASS_INFO ||
|
|
OUTPUT_CLASS_PARENTS_AND_CHILDREN || OUTPUT_SIMPLE_CLASS_HIERARCHIES;
|
|
|
|
return doOutput;
|
|
}
|
|
|
|
private void printClassHierarchyLists(List<RecoveredClass> recoveredClasses)
|
|
throws CancelledException {
|
|
|
|
for (RecoveredClass recoveredClass : recoveredClasses) {
|
|
monitor.checkCancelled();
|
|
|
|
List<RecoveredClass> classHierarchyList = recoveredClass.getClassHierarchy();
|
|
for (RecoveredClass currentClass : classHierarchyList) {
|
|
monitor.checkCancelled();
|
|
println(currentClass.getName());
|
|
}
|
|
|
|
println("***");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Script works on versions of ghidra including and after 9.2 except for 9.2.1 because a method
|
|
* was accidentally removed from FillOutStructureCmd that is needed
|
|
* @return true if script will work and false otherwise
|
|
*/
|
|
private boolean checkGhidraVersion() {
|
|
|
|
ghidraVersion = getVersionOfGhidra();
|
|
|
|
if (ghidraVersion.compareTo("9.3") <= 0 && !ghidraVersion.equals("9.2.1")) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Method to check if executable format is PE
|
|
*/
|
|
private boolean isPE() {
|
|
|
|
if (!PeLoader.PE_NAME.equals(currentProgram.getExecutableFormat())) {
|
|
return false;
|
|
}
|
|
return true;
|
|
|
|
}
|
|
|
|
/**
|
|
* Method to set the global variable isGcc
|
|
*/
|
|
private boolean isGcc() {
|
|
|
|
boolean isGcc;
|
|
|
|
boolean isCompilerSpecGcc = currentProgram.getCompilerSpec()
|
|
.getCompilerSpecID()
|
|
.getIdAsString()
|
|
.equalsIgnoreCase("gcc");
|
|
if (isCompilerSpecGcc) {
|
|
return true;
|
|
}
|
|
|
|
String compiler = currentProgram.getCompiler();
|
|
if (compiler != null && compiler.contains("gcc")) {
|
|
return true;
|
|
}
|
|
|
|
MemoryBlock commentBlock = currentProgram.getMemory().getBlock(".comment");
|
|
if (commentBlock == null) {
|
|
return false;
|
|
}
|
|
|
|
if (!commentBlock.isInitialized()) {
|
|
return false;
|
|
}
|
|
|
|
// check memory bytes in block for GCC: bytes
|
|
byte[] gccBytes = { (byte) 0x47, (byte) 0x43, (byte) 0x43, (byte) 0x3a };
|
|
byte[] maskBytes = { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
|
|
|
|
Address found = currentProgram.getMemory()
|
|
.findBytes(commentBlock.getStart(), commentBlock.getEnd(), gccBytes, maskBytes,
|
|
true, monitor);
|
|
if (found == null) {
|
|
isGcc = false;
|
|
}
|
|
else {
|
|
isGcc = true;
|
|
}
|
|
|
|
return isGcc;
|
|
}
|
|
|
|
/**
|
|
* Method to set the global variable isWindows
|
|
*/
|
|
private boolean isWindows() {
|
|
|
|
String compilerID =
|
|
currentProgram.getCompilerSpec().getCompilerSpecID().getIdAsString().toLowerCase();
|
|
boolean isWindows = compilerID.contains("windows");
|
|
return isWindows;
|
|
}
|
|
|
|
/**
|
|
* Method to determine if somehow the constructor list and destructor list for a class contain
|
|
* overlapping functions
|
|
* @param recoveredClass the given class
|
|
* @return true if there is a discrepancy in the constructor/destructor lists
|
|
*/
|
|
private boolean hasConstructorDestructorDiscrepancy(RecoveredClass recoveredClass) {
|
|
|
|
List<Function> allClassConstructors =
|
|
recoverClassesFromRTTI.getAllClassConstructors(recoveredClass);
|
|
List<Function> allClassDestructors =
|
|
recoverClassesFromRTTI.getAllClassDestructors(recoveredClass);
|
|
|
|
List<Function> commonFunctions1 = allClassConstructors.stream()
|
|
.distinct()
|
|
.filter(allClassDestructors::contains)
|
|
.collect(Collectors.toList());
|
|
|
|
List<Function> commonFunctions2 = allClassDestructors.stream()
|
|
.distinct()
|
|
.filter(allClassConstructors::contains)
|
|
.collect(Collectors.toList());
|
|
|
|
if (commonFunctions1.isEmpty() && commonFunctions2.isEmpty()) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Method to analyze the program changes with the decompiler parameter ID analyzer
|
|
* @param set the set of addresses to analyze
|
|
* @throws Exception if the analyzer throws an exception
|
|
*/
|
|
private void analyzeChangesWithDecompilerFunctionAnalyzer(AddressSetView set) throws Exception {
|
|
|
|
Analyzer analyzer = new DecompilerFunctionAnalyzer();
|
|
analyzer.added(currentProgram, set, monitor, new MessageLog());
|
|
}
|
|
|
|
/**
|
|
* If program has the "RTTI Found" option at all it means RTTI analyzer has been run and
|
|
* does not need to be run again. If program does not have the option it means either analyzer
|
|
* has not been run or it has been run by an older version (<10.3) so we don't know and should
|
|
* rerun to make sure.
|
|
* @return true if RTTI analyzer has definitely been run, false otherwise
|
|
*/
|
|
private boolean isRttiAnalyzed() {
|
|
Options programOptions = currentProgram.getOptions(Program.PROGRAM_INFO);
|
|
Boolean rttiAnalyzed = (Boolean) programOptions.getObject(RTTI_FOUND_OPTION, null);
|
|
if (rttiAnalyzed == null) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Method to check to see if current program has RTTI based on option setting
|
|
* @return true if program contains RTTI and false if not
|
|
*/
|
|
private boolean hasRtti() {
|
|
Options programOptions = currentProgram.getOptions(Program.PROGRAM_INFO);
|
|
return programOptions.getBoolean(RTTI_FOUND_OPTION, false);
|
|
}
|
|
|
|
/**
|
|
* Get the version of Ghidra that was used to analyze this program
|
|
* @return a string containing the version number of Ghidra used to analyze the current program
|
|
*/
|
|
private String getVersionOfGhidra() {
|
|
|
|
Options options = currentProgram.getOptions("Program Information");
|
|
return options.getString("Created With Ghidra Version", null);
|
|
}
|
|
|
|
/**
|
|
* Method to bookmark all of the constructor/destructor/indeterminate functions
|
|
* @param recoveredClasses List of classes
|
|
* @throws CancelledException if cancelled
|
|
*/
|
|
private void bookmarkFunctions(List<RecoveredClass> recoveredClasses)
|
|
throws CancelledException {
|
|
bookmarkConstructors(recoveredClasses);
|
|
bookmarkDestructors(recoveredClasses);
|
|
bookmarkRemainingIndeterminateConstructorsAndDestructors(recoveredClasses);
|
|
}
|
|
|
|
/**
|
|
* Method to print class hierarchy of the form child : parent: grandparent : etc...
|
|
* @param stringBuffer the buffer to add the newly created string to
|
|
* @param recoveredClass the current class to process
|
|
* @throws CancelledException when cancelled
|
|
*/
|
|
private StringBuffer getSimpleClassHierarchyString(StringBuffer stringBuffer,
|
|
RecoveredClass recoveredClass) throws CancelledException {
|
|
|
|
// print class
|
|
stringBuffer.append(recoveredClass.getName());
|
|
|
|
if (!recoveredClass.hasParentClass()) {
|
|
return stringBuffer;
|
|
}
|
|
|
|
// if class has parents, print parents in order
|
|
Map<RecoveredClass, List<RecoveredClass>> classHierarchyMap =
|
|
recoveredClass.getClassHierarchyMap();
|
|
|
|
List<RecoveredClass> parents = new ArrayList<>(classHierarchyMap.keySet());
|
|
|
|
// if single inheritance - simple linear case
|
|
if (recoveredClass.hasSingleInheritance()) {
|
|
stringBuffer.append(" : ");
|
|
getSimpleClassHierarchyString(stringBuffer, parents.get(0));
|
|
}
|
|
// otherwise have to split into various lines for the multiple parents
|
|
else {
|
|
stringBuffer.append(" : ");
|
|
int lastColon = stringBuffer.lastIndexOf(":");
|
|
for (RecoveredClass parentClass : classHierarchyMap.keySet()) {
|
|
monitor.checkCancelled();
|
|
if (classHierarchyMap.size() == 1) {
|
|
//stringBuffer.append(" : ");
|
|
getSimpleClassHierarchyString(stringBuffer, parentClass);
|
|
continue;
|
|
}
|
|
|
|
stringBuffer.append("\r\n");
|
|
|
|
//int lastColon = stringBuffer.lastIndexOf(":");
|
|
for (int i = 0; i <= lastColon; i++) {
|
|
monitor.checkCancelled();
|
|
stringBuffer.append(" ");
|
|
}
|
|
getSimpleClassHierarchyString(stringBuffer, parentClass);
|
|
}
|
|
|
|
}
|
|
|
|
return stringBuffer;
|
|
|
|
}
|
|
|
|
/**
|
|
* Method to retrieve the AddressSet of the current program's initialized memory
|
|
* @return the AddressSet of the current program's initialized memory
|
|
* @throws CancelledException if cancelled
|
|
*/
|
|
private AddressSet getInitializedMemory() throws CancelledException {
|
|
|
|
AddressSet dataAddresses = new AddressSet();
|
|
MemoryBlock[] blocks = currentProgram.getMemory().getBlocks();
|
|
|
|
for (MemoryBlock block : blocks) {
|
|
monitor.checkCancelled();
|
|
|
|
if (block.isInitialized()) {
|
|
dataAddresses.add(block.getStart(), block.getEnd());
|
|
}
|
|
}
|
|
return dataAddresses;
|
|
}
|
|
|
|
/**
|
|
* Method to bookmark found constructor functions
|
|
* @param recoveredClasses List of classes
|
|
* @throws CancelledException if cancelled
|
|
*/
|
|
private void bookmarkConstructors(List<RecoveredClass> recoveredClasses)
|
|
throws CancelledException {
|
|
|
|
for (RecoveredClass recoveredClass : recoveredClasses) {
|
|
monitor.checkCancelled();
|
|
bookmarkFunctionsOnList(recoveredClass.getConstructorList(), CONSTRUCTOR_BOOKMARK);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Method to bookmark found constructor functions
|
|
* @param recoveredClasses List of classes
|
|
* @throws CancelledException if cancelled
|
|
*/
|
|
private void bookmarkDestructors(List<RecoveredClass> recoveredClasses)
|
|
throws CancelledException {
|
|
|
|
for (RecoveredClass recoveredClass : recoveredClasses) {
|
|
monitor.checkCancelled();
|
|
bookmarkFunctionsOnList(recoveredClass.getDestructorList(), DESTRUCTOR_BOOKMARK);
|
|
bookmarkFunctionsOnList(recoveredClass.getNonThisDestructors(), DESTRUCTOR_BOOKMARK);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Method to bookmark indeterminate constructor/destructor functions
|
|
* @param recoveredClasses List of classes
|
|
* @throws CancelledException if cancelled
|
|
*/
|
|
private void bookmarkRemainingIndeterminateConstructorsAndDestructors(
|
|
List<RecoveredClass> recoveredClasses) throws CancelledException {
|
|
|
|
for (RecoveredClass recoveredClass : recoveredClasses) {
|
|
monitor.checkCancelled();
|
|
bookmarkFunctionsOnList(recoveredClass.getIndeterminateList(), INDETERMINATE_BOOKMARK);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Method to add/append analysis bookmarks with the given comment to the given list of functions
|
|
* @param functions List of functions
|
|
* @param comment the bookmark comment to add
|
|
* @throws CancelledException when script is cancelled
|
|
*/
|
|
private void bookmarkFunctionsOnList(List<Function> functions, String comment)
|
|
throws CancelledException {
|
|
|
|
if (functions.size() == 0) {
|
|
return;
|
|
}
|
|
|
|
for (Function function : functions) {
|
|
monitor.checkCancelled();
|
|
Address address = function.getEntryPoint();
|
|
recoverClassesFromRTTI.bookmarkAddress(address, comment);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Method to optionally print to console or output to file various types of class information
|
|
* depending on the options set at top of script
|
|
* @param recoveredClasses List of classes
|
|
* @param out output mechanism
|
|
* @throws CancelledException if cancelled
|
|
*/
|
|
private void callOptionalOutputMethods(List<RecoveredClass> recoveredClasses, PrintWriter out)
|
|
throws CancelledException {
|
|
|
|
if (PRINT_COUNTS) {
|
|
printCounts(recoveredClasses);
|
|
}
|
|
|
|
if (PRINT_CLASS_HIERARCHIES) {
|
|
printClassHierarchiesFromLowestChildren(recoveredClasses);
|
|
}
|
|
|
|
if (PRINT_CLASS_DEFINITIONS) {
|
|
printClassDefinitions(recoveredClasses);
|
|
}
|
|
|
|
if (PRINT_CLASS_PARENTS_AND_CHILDREN) {
|
|
printClassesParentsAndChilren(recoveredClasses);
|
|
}
|
|
|
|
if (PRINT_CLASS_INFO) {
|
|
printClassInfo(recoveredClasses);
|
|
}
|
|
|
|
if (OUTPUT_CLASS_DEFINITIONS) {
|
|
outputClassDefinitions(recoveredClasses, out);
|
|
}
|
|
if (OUTPUT_CLASS_INFO) {
|
|
outputClassInfo(recoveredClasses, out);
|
|
}
|
|
|
|
if (OUTPUT_CLASS_PARENTS_AND_CHILDREN) {
|
|
outputClassParentsAndChildren(recoveredClasses, out);
|
|
}
|
|
if (OUTPUT_SIMPLE_CLASS_HIERARCHIES) {
|
|
outputSimpleClassHierarchies(recoveredClasses, out);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Method to print class definitions given information discovered about each class.
|
|
* Start with top parents and recurse over their children
|
|
* @param recoveredClasses List of classes
|
|
* @throws CancelledException when cancelled
|
|
*/
|
|
private void printClassDefinitions(List<RecoveredClass> recoveredClasses)
|
|
throws CancelledException {
|
|
for (RecoveredClass recoveredClass : recoveredClasses) {
|
|
monitor.checkCancelled();
|
|
if (!recoveredClass.hasParentClass()) {
|
|
println(createClassDefinitionString(recoveredClass).toString());
|
|
}
|
|
}
|
|
}
|
|
|
|
private void outputClassDefinitions(List<RecoveredClass> recoveredClasses, PrintWriter out)
|
|
throws CancelledException {
|
|
for (RecoveredClass recoveredClass : recoveredClasses) {
|
|
monitor.checkCancelled();
|
|
if (!recoveredClass.hasParentClass()) {
|
|
out.append(createClassDefinitionString(recoveredClass));
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Method to print class info for each of the classes on the given list, starting with classes with no parents then
|
|
* recursively printing infor for their child classes
|
|
* @param recoveredClasses the list of classes
|
|
* @throws CancelledException if cancelled
|
|
*/
|
|
private void printClassInfo(List<RecoveredClass> recoveredClasses) throws CancelledException {
|
|
for (RecoveredClass recoveredClass : recoveredClasses) {
|
|
monitor.checkCancelled();
|
|
if (!recoveredClass.hasParentClass()) {
|
|
println(createClassInfoString(recoveredClass).toString());
|
|
}
|
|
}
|
|
}
|
|
|
|
private void printClassParents(List<RecoveredClass> recoveredClasses)
|
|
throws CancelledException {
|
|
for (RecoveredClass recoveredClass : recoveredClasses) {
|
|
monitor.checkCancelled();
|
|
String printString = new String("\n" + recoveredClass.getName() + "\n");
|
|
if (recoveredClass.hasParentClass()) {
|
|
List<RecoveredClass> parentList = recoveredClass.getParentList();
|
|
for (RecoveredClass parent : parentList) {
|
|
monitor.checkCancelled();
|
|
printString = printString.concat("\t" + parent.getName() + "\n");
|
|
}
|
|
}
|
|
println(printString);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Method to print class hierarchies for the given list of classes starting with the lowest
|
|
* child classes in each family of classes
|
|
* @param recoveredClasses the list of classes
|
|
* @throws CancelledException if cancelled
|
|
*/
|
|
private void printClassHierarchiesFromLowestChildren(List<RecoveredClass> recoveredClasses)
|
|
throws CancelledException {
|
|
|
|
StringBuffer wholeBuffer = new StringBuffer();
|
|
wholeBuffer.append("\r\n");
|
|
for (RecoveredClass recoveredClass : recoveredClasses) {
|
|
monitor.checkCancelled();
|
|
if (!recoveredClass.hasChildClass()) {
|
|
|
|
StringBuffer stringBuffer = new StringBuffer();
|
|
|
|
wholeBuffer.append(
|
|
getSimpleClassHierarchyString(stringBuffer, recoveredClass).toString() +
|
|
"\r\n\r\n");
|
|
}
|
|
}
|
|
println(wholeBuffer.toString());
|
|
|
|
}
|
|
|
|
/**
|
|
* Method to output simple class hierarchies for the given classes to the given output writer
|
|
* @param recoveredClasses the list of classes
|
|
* @param out the output writer
|
|
* @throws CancelledException if cancelled
|
|
*/
|
|
private void outputSimpleClassHierarchies(List<RecoveredClass> recoveredClasses,
|
|
PrintWriter out) throws CancelledException {
|
|
|
|
StringBuffer wholeBuffer = new StringBuffer();
|
|
wholeBuffer.append("\r\n");
|
|
|
|
for (RecoveredClass recoveredClass : recoveredClasses) {
|
|
monitor.checkCancelled();
|
|
if (!recoveredClass.hasChildClass()) {
|
|
StringBuffer stringBuffer = new StringBuffer();
|
|
wholeBuffer.append(
|
|
getSimpleClassHierarchyString(stringBuffer, recoveredClass).toString() +
|
|
"\r\n");
|
|
}
|
|
}
|
|
out.append(wholeBuffer);
|
|
}
|
|
|
|
/**
|
|
* Method to output class info for the given list of classes
|
|
* @param recoveredClasses the list of classes
|
|
* @param out the output writer
|
|
* @throws CancelledException if cancelled
|
|
*/
|
|
private void outputClassInfo(List<RecoveredClass> recoveredClasses, PrintWriter out)
|
|
throws CancelledException {
|
|
|
|
for (RecoveredClass recoveredClass : recoveredClasses) {
|
|
monitor.checkCancelled();
|
|
if (!recoveredClass.hasParentClass()) {
|
|
out.append(createClassInfoString(recoveredClass).toString());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Method to print counts of various class items for the given classes, such as number of
|
|
* constructors, destructors, etc...
|
|
* @param recoveredClasses list of classes
|
|
* @throws CancelledException if cancelled
|
|
*/
|
|
private void printCounts(List<RecoveredClass> recoveredClasses) throws CancelledException {
|
|
|
|
println("Total number of constructors: " +
|
|
recoverClassesFromRTTI.getNumberOfConstructors(recoveredClasses));
|
|
println("Total number of inlined constructors: " +
|
|
getNumberOfInlinedConstructors(recoveredClasses));
|
|
println("Total number of destructors: " +
|
|
recoverClassesFromRTTI.getNumberOfDestructors(recoveredClasses));
|
|
println("Total number of inlined destructors: " +
|
|
recoverClassesFromRTTI.getNumberOfInlineDestructors(recoveredClasses));
|
|
println("Total number of virtual functions: " +
|
|
recoverClassesFromRTTI.getNumberOfVirtualFunctions(recoveredClasses));
|
|
println("Total number of virtual functions that are deleting destructors: " +
|
|
recoverClassesFromRTTI.getNumberOfDeletingDestructors(recoveredClasses));
|
|
|
|
println("Total number of virtual functions that are clone functions: " +
|
|
recoverClassesFromRTTI.getNumberOfCloneFunctions(recoveredClasses));
|
|
|
|
println("Total number of virtual functions that are vbase_destructors: " +
|
|
recoverClassesFromRTTI.getNumberOfVBaseFunctions(recoveredClasses));
|
|
|
|
List<Function> remainingIndeterminates =
|
|
recoverClassesFromRTTI.getRemainingIndeterminates(recoveredClasses);
|
|
println("Total number of indetermined constructor/destructors: " +
|
|
remainingIndeterminates.size());
|
|
|
|
println("Total fixed incorrect FID functions: " +
|
|
recoverClassesFromRTTI.getBadFIDFunctions().size());
|
|
println("Total resolved functions that had multiple FID possiblities: " +
|
|
recoverClassesFromRTTI.getResolvedFIDFunctions().size());
|
|
println("Total fixed functions that had incorrect data types due to incorrect FID: " +
|
|
recoverClassesFromRTTI.getFixedFIDFunctions().size());
|
|
|
|
}
|
|
|
|
/**
|
|
* Method to get the total number of
|
|
* @param recoveredClasses list of classes
|
|
* @return the total number of constructors and destructors in the given list of classes
|
|
* @throws CancelledException if cancelled
|
|
*/
|
|
private int getNumberOfConstructorsOrDestructors(List<RecoveredClass> recoveredClasses)
|
|
throws CancelledException {
|
|
|
|
int total = 0;
|
|
for (RecoveredClass recoveredClass : recoveredClasses) {
|
|
monitor.checkCancelled();
|
|
List<Function> constructorList = recoveredClass.getConstructorOrDestructorFunctions();
|
|
total += constructorList.size();
|
|
}
|
|
return total;
|
|
}
|
|
|
|
/**
|
|
* Method to get the total number of inlined constructors in the given list of classes
|
|
* @param recoveredClasses list of classes
|
|
* @return total number of inlined constructors in the given list of classes
|
|
* @throws CancelledException if cancelled
|
|
*/
|
|
private int getNumberOfInlinedConstructors(List<RecoveredClass> recoveredClasses)
|
|
throws CancelledException {
|
|
|
|
int total = 0;
|
|
for (RecoveredClass recoveredClass : recoveredClasses) {
|
|
monitor.checkCancelled();
|
|
List<Function> inlineList = recoveredClass.getInlinedConstructorList();
|
|
total += inlineList.size();
|
|
}
|
|
return total;
|
|
}
|
|
|
|
/**
|
|
* Method to print the given list of addresses
|
|
* @param addresses the list of addresses to print
|
|
* @throws CancelledException if cancelled
|
|
*/
|
|
private void printAddresses(List<Address> addresses) throws CancelledException {
|
|
for (Address element : addresses) {
|
|
monitor.checkCancelled();
|
|
println(element.toString());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Method to output the class, it's parents and it's children for each of the listed classes
|
|
* @param recoveredClasses the given classes
|
|
* @param out the output writer
|
|
* @throws CancelledException if cancelled
|
|
*/
|
|
private void outputClassParentsAndChildren(List<RecoveredClass> recoveredClasses,
|
|
PrintWriter out) throws CancelledException {
|
|
|
|
for (RecoveredClass recoveredClass : recoveredClasses) {
|
|
monitor.checkCancelled();
|
|
out.append(printClassParentsandChildren(recoveredClass));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Method to print the class, it's parents and it's children for each of the listed classes
|
|
* @param recoveredClasses the given classes
|
|
* @throws CancelledException if cancelled
|
|
*/
|
|
private void printClassesParentsAndChilren(List<RecoveredClass> recoveredClasses)
|
|
throws CancelledException {
|
|
|
|
for (RecoveredClass recoveredClass : recoveredClasses) {
|
|
monitor.checkCancelled();
|
|
println(printClassParentsandChildren(recoveredClass).toString());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Method to get formatted string containing the given class, it's parents and it's children
|
|
* @param recoveredClass the given classes
|
|
* @return StringBuffer containing the formatted string containing the given class, it's parents
|
|
* and it's children
|
|
* @throws CancelledException if cancelled
|
|
*/
|
|
private StringBuffer printClassParentsandChildren(RecoveredClass recoveredClass)
|
|
throws CancelledException {
|
|
StringBuffer stringBuffer = new StringBuffer();
|
|
stringBuffer.append("\r\n");
|
|
stringBuffer.append("\r\n");
|
|
|
|
stringBuffer.append("*** Recovered Class: " + recoveredClass.getName() + " ***\r\n");
|
|
stringBuffer.append("\r\n");
|
|
|
|
// print parent classes
|
|
stringBuffer.append("parent class(es):\r\n");
|
|
if (recoveredClass.hasParentClass()) {
|
|
Set<RecoveredClass> keySet = recoveredClass.getClassHierarchyMap().keySet();
|
|
for (RecoveredClass parent : keySet) {
|
|
monitor.checkCancelled();
|
|
stringBuffer.append("\t" + parent.getName() + "\r\n");
|
|
}
|
|
}
|
|
|
|
// print child classes
|
|
stringBuffer.append("\r\n");
|
|
stringBuffer.append("child class(es):\r\n");
|
|
if (recoveredClass.hasChildClass()) {
|
|
List<RecoveredClass> childClasses = recoveredClass.getChildClasses();
|
|
for (RecoveredClass element : childClasses) {
|
|
monitor.checkCancelled();
|
|
stringBuffer.append("\t" + element.getName() + "\r\n");
|
|
}
|
|
|
|
}
|
|
return stringBuffer;
|
|
|
|
}
|
|
|
|
/**
|
|
* Method to create a string containing class info for the given class including parents,
|
|
* children, constructors, destructors inlined constructors, inlined destructors, member
|
|
* functions, member data and the same info for each child class
|
|
* @param recoveredClass the given class
|
|
* @return string buffer containing class info for the given class
|
|
* @throws CancelledException if cancelled
|
|
*/
|
|
private StringBuffer createClassInfoString(RecoveredClass recoveredClass)
|
|
throws CancelledException {
|
|
|
|
StringBuffer stringBuffer = new StringBuffer();
|
|
stringBuffer.append("\r\n");
|
|
stringBuffer.append("\r\n");
|
|
|
|
stringBuffer.append("*** Recovered Class: " + recoveredClass.getName() + " ***\r\n");
|
|
stringBuffer.append("\r\n");
|
|
|
|
// print parent classes
|
|
stringBuffer.append("parent class(es):\r\n");
|
|
if (recoveredClass.hasParentClass()) {
|
|
|
|
// use this to get direct parents
|
|
Map<RecoveredClass, List<RecoveredClass>> classHierarchyMap =
|
|
recoveredClass.getClassHierarchyMap();
|
|
Set<RecoveredClass> directParents = classHierarchyMap.keySet();
|
|
|
|
// use this to get correct parent order and to get the type of parent
|
|
Map<RecoveredClass, Boolean> parentToBaseTypeMap =
|
|
recoveredClass.getParentToBaseTypeMap();
|
|
Set<RecoveredClass> ancestors = parentToBaseTypeMap.keySet();
|
|
for (RecoveredClass ancestor : ancestors) {
|
|
monitor.checkCancelled();
|
|
if (directParents.contains(ancestor)) {
|
|
|
|
Boolean isVirtualParent = parentToBaseTypeMap.get(ancestor);
|
|
if (isVirtualParent != null && isVirtualParent) {
|
|
stringBuffer.append("\t virtual " + ancestor.getName() + "\r\n");
|
|
}
|
|
else {
|
|
stringBuffer.append("\t" + ancestor.getName() + "\r\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
stringBuffer.append("\tNone\r\n");
|
|
}
|
|
|
|
// print child classes
|
|
stringBuffer.append("\r\n");
|
|
stringBuffer.append("child class(es):\r\n");
|
|
if (recoveredClass.hasChildClass()) {
|
|
List<RecoveredClass> childClasses = recoveredClass.getChildClasses();
|
|
for (RecoveredClass element : childClasses) {
|
|
monitor.checkCancelled();
|
|
stringBuffer.append("\t" + element.getName() + "\r\n");
|
|
}
|
|
|
|
}
|
|
stringBuffer.append("\r\n");
|
|
|
|
// print constructors
|
|
stringBuffer.append("constructor(s):\r\n");
|
|
List<Function> constructorList = recoveredClass.getConstructorList();
|
|
for (Function constructorFunction : constructorList) {
|
|
monitor.checkCancelled();
|
|
stringBuffer.append("\t" + constructorFunction.getName() + " " +
|
|
constructorFunction.getEntryPoint().toString() + "\r\n");
|
|
}
|
|
stringBuffer.append("\r\n");
|
|
|
|
// print inlined constructors
|
|
List<Function> inlinedConstructorList = recoveredClass.getInlinedConstructorList();
|
|
if (inlinedConstructorList.size() > 0) {
|
|
stringBuffer.append("inlined constructor(s):\r\n");
|
|
for (Function inlinedConstructorFunction : inlinedConstructorList) {
|
|
monitor.checkCancelled();
|
|
stringBuffer.append("\t" + inlinedConstructorFunction.getName() + " " +
|
|
inlinedConstructorFunction.getEntryPoint().toString() + "\r\n");
|
|
}
|
|
}
|
|
stringBuffer.append("\r\n");
|
|
|
|
// print destructors
|
|
stringBuffer.append("destructor(s):\r\n");
|
|
List<Function> destructorList = recoveredClass.getDestructorList();
|
|
for (Function destructorFunction : destructorList) {
|
|
monitor.checkCancelled();
|
|
stringBuffer.append("\t" + destructorFunction.getName() + " " +
|
|
destructorFunction.getEntryPoint().toString() + "\r\n");
|
|
}
|
|
stringBuffer.append("\r\n");
|
|
|
|
// print inlined destructors
|
|
List<Function> inlinedDestructorList = recoveredClass.getInlinedDestructorList();
|
|
if (inlinedDestructorList.size() > 0) {
|
|
stringBuffer.append("inlined destructor(s):\r\n");
|
|
for (Function inlinedDestructorFunction : inlinedDestructorList) {
|
|
monitor.checkCancelled();
|
|
stringBuffer.append("\t" + inlinedDestructorFunction.getName() + " " +
|
|
inlinedDestructorFunction.getEntryPoint().toString() + "\r\n");
|
|
}
|
|
}
|
|
|
|
// print const/dest that couldn't be classified correctly
|
|
List<Function> indeterminateList = recoveredClass.getIndeterminateList();
|
|
if (indeterminateList.size() > 0) {
|
|
stringBuffer.append("\r\nindeterminate constructor(s) or destructor(s):\r\n");
|
|
for (Function indeterminateFunction : indeterminateList) {
|
|
monitor.checkCancelled();
|
|
stringBuffer.append("\t" + indeterminateFunction.getName() + " " +
|
|
indeterminateFunction.getEntryPoint().toString() + "\r\n");
|
|
}
|
|
stringBuffer.append("\r\n");
|
|
}
|
|
stringBuffer.append("\r\n");
|
|
|
|
// print virtual function signatures
|
|
stringBuffer.append("member function(s):\r\n");
|
|
List<Function> virtualFunctions = recoveredClass.getAllVirtualFunctions();
|
|
for (Function vfunction : virtualFunctions) {
|
|
monitor.checkCancelled();
|
|
stringBuffer.append(
|
|
"\t" + vfunction.getName() + " " + vfunction.getEntryPoint().toString() + "\r\n");
|
|
}
|
|
stringBuffer.append("\r\n");
|
|
|
|
// print class member data
|
|
stringBuffer.append("member data:\r\n");
|
|
|
|
DataType classMemberDataType = dataTypeManager.getDataType(recoveredClass.getClassPath(),
|
|
recoveredClass.getName() + CLASS_DATA_STRUCT_NAME);
|
|
|
|
if (classMemberDataType != null && classMemberDataType instanceof Structure) {
|
|
|
|
Structure memberDataStructure = (Structure) classMemberDataType;
|
|
int numDefinedComponents = memberDataStructure.getNumDefinedComponents();
|
|
|
|
DataTypeComponent[] definedComponents = memberDataStructure.getDefinedComponents();
|
|
for (int i = 0; i < numDefinedComponents; i++) {
|
|
monitor.checkCancelled();
|
|
|
|
stringBuffer.append("\t" + definedComponents[i].getDataType() + " " +
|
|
definedComponents[i].getFieldName() + "\r\n");
|
|
}
|
|
}
|
|
stringBuffer.append("\r\n");
|
|
|
|
// Then recursively process the child classes
|
|
if (recoveredClass.hasChildClass()) {
|
|
List<RecoveredClass> childClasses = recoveredClass.getChildClasses();
|
|
for (RecoveredClass element : childClasses) {
|
|
monitor.checkCancelled();
|
|
stringBuffer.append(createClassInfoString(element));
|
|
}
|
|
}
|
|
|
|
return stringBuffer;
|
|
}
|
|
|
|
/**
|
|
* Method to get the function signature string, from the decompiler if possible, otherwise from
|
|
* the listing
|
|
* @param function the given function
|
|
* @param includeReturn if true, include the return type in the string
|
|
* @return the function signature string
|
|
* @throws CancelledException if cancelled
|
|
*/
|
|
private String getFunctionSignatureString(Function function, boolean includeReturn)
|
|
throws CancelledException {
|
|
|
|
if (function == null) {
|
|
return "";
|
|
}
|
|
|
|
StringBuffer stringBuffer = new StringBuffer();
|
|
stringBuffer.append("\t");
|
|
|
|
String functionSignatureString =
|
|
decompilerUtils.getFunctionSignatureString(function, includeReturn);
|
|
|
|
if (functionSignatureString != null) {
|
|
stringBuffer = stringBuffer.append(functionSignatureString.toString());
|
|
return stringBuffer.toString();
|
|
}
|
|
|
|
// if can't get it from decompiler then use the listing one
|
|
if (includeReturn) {
|
|
stringBuffer.append(function.getReturnType().getDisplayName() + " ");
|
|
}
|
|
|
|
stringBuffer.append(function.getName() + "(");
|
|
int paramCount = function.getParameterCount();
|
|
int autoParamCount = function.getAutoParameterCount();
|
|
if (paramCount - autoParamCount <= 0) {
|
|
stringBuffer.append(");");
|
|
}
|
|
else {
|
|
for (int i = autoParamCount - 1; i < paramCount; i++) {
|
|
monitor.checkCancelled();
|
|
Parameter param = function.getParameter(i);
|
|
stringBuffer.append(param.getDataType().getDisplayName() + " " + param.getName());
|
|
if (i == paramCount) {
|
|
stringBuffer.append(");");
|
|
}
|
|
else {
|
|
stringBuffer.append(", ");
|
|
}
|
|
}
|
|
}
|
|
|
|
return stringBuffer.toString();
|
|
}
|
|
|
|
/**
|
|
* Method to create a string containing a C++-like representation of the given class
|
|
* @param recoveredClass the given class
|
|
* @return string containing a C++-like representation of the given class
|
|
* @throws CancelledException if cancelled
|
|
*/
|
|
private StringBuffer createClassDefinitionString(RecoveredClass recoveredClass)
|
|
throws CancelledException {
|
|
|
|
StringBuffer stringBuffer = new StringBuffer();
|
|
stringBuffer.append("\r\n\r\n");
|
|
|
|
stringBuffer.append(recoverClassesFromRTTI.createParentStringBuffer(recoveredClass));
|
|
|
|
stringBuffer.append("\r\n{\r\n");
|
|
|
|
// print constructor signature(s)
|
|
stringBuffer.append("constructor(s):\r\n");
|
|
List<Function> constructorList = recoveredClass.getConstructorList();
|
|
for (Function constructorFunction : constructorList) {
|
|
monitor.checkCancelled();
|
|
String functionSignatureString = getFunctionSignatureString(constructorFunction, true);
|
|
|
|
stringBuffer.append(functionSignatureString);
|
|
stringBuffer.append("\r\n");
|
|
}
|
|
|
|
// print destructor signature
|
|
stringBuffer.append("\r\ndestructor(s):\r\n");
|
|
List<Function> destructorList = recoveredClass.getDestructorList();
|
|
for (Function destructorFunction : destructorList) {
|
|
monitor.checkCancelled();
|
|
String functionSignatureString = getFunctionSignatureString(destructorFunction, true);
|
|
stringBuffer.append(functionSignatureString);
|
|
stringBuffer.append("\r\n");
|
|
}
|
|
|
|
// print const/dest that couldn't be classified correctly
|
|
List<Function> indeterminateList = recoveredClass.getIndeterminateList();
|
|
if (indeterminateList.size() > 0) {
|
|
stringBuffer.append("\r\nindeterminate constructor or destructor function(s):\r\n");
|
|
for (Function indeterminateFunction : indeterminateList) {
|
|
monitor.checkCancelled();
|
|
String functionSignatureString =
|
|
getFunctionSignatureString(indeterminateFunction, true);
|
|
stringBuffer.append(functionSignatureString);
|
|
stringBuffer.append("\r\n");
|
|
}
|
|
}
|
|
|
|
// print virtual function signature(s)
|
|
stringBuffer.append("\r\nmember function(s):\r\n");
|
|
List<Function> virtualFunctions = recoveredClass.getAllVirtualFunctions();
|
|
for (Function vfunction : virtualFunctions) {
|
|
monitor.checkCancelled();
|
|
String functionSignatureString = getFunctionSignatureString(vfunction, true);
|
|
stringBuffer.append(functionSignatureString);
|
|
stringBuffer.append("\r\n");
|
|
}
|
|
|
|
stringBuffer.append("\r\n");
|
|
stringBuffer.append("member data: \r\n");
|
|
|
|
// print class member data items
|
|
DataType classMemberDataType = dataTypeManager.getDataType(recoveredClass.getClassPath(),
|
|
recoveredClass.getName() + CLASS_DATA_STRUCT_NAME);
|
|
|
|
if (classMemberDataType != null && classMemberDataType instanceof Structure) {
|
|
|
|
Structure memberDataStructure = (Structure) classMemberDataType;
|
|
int numDefinedComponents = memberDataStructure.getNumDefinedComponents();
|
|
|
|
DataTypeComponent[] definedComponents = memberDataStructure.getDefinedComponents();
|
|
for (int i = 0; i < numDefinedComponents; i++) {
|
|
monitor.checkCancelled();
|
|
stringBuffer.append("\t" + definedComponents[i].getDataType() + " " +
|
|
definedComponents[i].getFieldName() + "\r\n");
|
|
}
|
|
}
|
|
stringBuffer.append("};\r\n");
|
|
|
|
// Then recursively process the child classes
|
|
if (recoveredClass.hasChildClass()) {
|
|
List<RecoveredClass> childClasses = recoveredClass.getChildClasses();
|
|
for (RecoveredClass element : childClasses) {
|
|
monitor.checkCancelled();
|
|
stringBuffer.append(createClassDefinitionString(element));
|
|
}
|
|
}
|
|
|
|
return stringBuffer;
|
|
}
|
|
|
|
private boolean isStringInProgramMemory(String string) {
|
|
|
|
byte[] byteArrray = string.getBytes();
|
|
|
|
Address findBytes = currentProgram.getMemory()
|
|
.findBytes(currentProgram.getMinAddress(), byteArrray, null, true, monitor);
|
|
if (findBytes != null) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private boolean isExternalNamespace(String path) throws CancelledException {
|
|
|
|
List<Symbol> symbols = NamespaceUtils.getSymbols(path, currentProgram, true);
|
|
|
|
for (Symbol symbol : symbols) {
|
|
monitor.checkCancelled();
|
|
if (symbol.isExternal() && symbol.getSymbolType().isNamespace()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
}
|