GP-339 Added RecoverClassesFromRTTI script and related classes.

This commit is contained in:
ghidra007
2021-05-18 18:53:05 -04:00
parent 3cc2bc5b06
commit 3d052cde11
16 changed files with 18037 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,201 @@
/* ###
* 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.
*/
//Script to graph class hierarchies given metadata found in class structure description that
// was applied using the ExtractClassInfoFromRTTIScript.
//@category C++
import java.util.*;
import ghidra.app.script.GhidraScript;
import ghidra.app.services.GraphDisplayBroker;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.data.*;
import ghidra.service.graph.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
public class GraphClassesScript extends GhidraScript {
List<Structure> classStructures = new ArrayList<Structure>();
@Override
public void run() throws Exception {
if (currentProgram == null) {
println("There is no open program");
return;
}
DataTypeManager dataTypeManager = currentProgram.getDataTypeManager();
String path = new String("ClassDataTypes");
CategoryPath dataTypePath = new CategoryPath(CategoryPath.ROOT, path);
Category category = dataTypeManager.getCategory(dataTypePath);
if (category == null) {
println(
"/ClassDataTypes folder does not exist so there is no class data to process. Please run the ExtractClassInfoFromRTTIScript to generate the necessary information needed to run this script.");
return;
}
Category[] subCategories = category.getCategories();
getClassStructures(subCategories);
AttributedGraph graph = createGraph();
if (graph.getVertexCount() == 0) {
println(
"There was no metadata in the class structures so a graph could not be created. Please run the ExtractClassInfoFromRTTIScript to generate the necessary information needed to run this script.");
}
else {
showGraph(graph);
}
}
private void getClassStructures(Category[] categories) throws CancelledException {
for (Category category : categories) {
monitor.checkCanceled();
DataType[] dataTypes = category.getDataTypes();
for (DataType dataType : dataTypes) {
monitor.checkCanceled();
if (dataType.getName().equals(category.getName()) &&
dataType instanceof Structure) {
// if the data type name is the same as the folder name then
// it is the main class structure
Structure classStructure = (Structure) dataType;
if (!classStructures.contains(classStructure)) {
classStructures.add(classStructure);
}
}
}
Category[] subcategories = category.getCategories();
if (subcategories.length > 0) {
getClassStructures(subcategories);
}
}
}
private AttributedGraph createGraph() throws CancelledException {
AttributedGraph g = new AttributedGraph();
Iterator<Structure> classStructuresIterator = classStructures.iterator();
while (classStructuresIterator.hasNext()) {
monitor.checkCanceled();
Structure classStructure = classStructuresIterator.next();
String description = classStructure.getDescription();
String mainClassName = getClassName(description);
if (mainClassName == null) {
continue;
}
AttributedVertex classVertex = g.addVertex(mainClassName);
int numParents = 0;
while (description.contains(":")) {
numParents++;
int indexOfColon = description.indexOf(":", 0);
description = description.substring(indexOfColon + 1);
int endOfBlock = description.indexOf(":", 0);
if (endOfBlock == -1) {
endOfBlock = description.length();
}
String parentName = description.substring(0, endOfBlock);
description = description.substring(endOfBlock);
boolean isVirtualParent = false;
if (parentName.contains("virtual")) {
isVirtualParent = true;
}
parentName = parentName.replace("virtual", "");
parentName = parentName.replace(" ", "");
AttributedVertex parentVertex = g.addVertex(parentName);
AttributedEdge edge = g.addEdge(parentVertex, classVertex);
if (isVirtualParent) {
edge.setAttribute("Color", "Orange");
}
// else leave it default lime green
}
// no parent = blue vertex
if (numParents == 0) {
classVertex.setAttribute("Color", "Blue");
}
// single parent = green vertex
else if (numParents == 1) {
classVertex.setAttribute("Color", "Green");
}
// multiple parents = red vertex
else {
classVertex.setAttribute("Color", "Red");
}
}
return g;
}
private void showGraph(AttributedGraph graph) throws Exception {
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);
display.setGraph(graph, "test graph", false, TaskMonitor.DUMMY);
}
private String getClassName(String description) {
// parse description for class hierarchy
if (!description.startsWith("class")) {
return null;
}
// skip "class " to get overall class
description = description.substring(6);
int indexOfColon = description.indexOf(":", 0);
String mainClassName;
if (indexOfColon == -1) {
mainClassName = description;
}
else {
mainClassName = description.substring(0, indexOfColon - 1);
}
mainClassName = mainClassName.replace(" ", "");
return mainClassName;
}
}
@@ -0,0 +1,84 @@
/* ###
* 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.
*/
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.util.exception.CancelledException;
public class SearchForImageBaseOffsets extends GhidraScript {
@Override
public void run() throws Exception {
if (currentProgram == null) {
println("No open program");
return;
}
if (currentProgram.getMemory().isBigEndian()) {
println("This script only looks for little endian image base offsets");
return;
}
Address imageBase = currentProgram.getImageBase();
long currentAddressOffset = currentAddress.getOffset();
long imageBaseOffset = imageBase.getOffset();
long currentAddressIbo = imageBaseOffset ^ currentAddressOffset;
byte searchBytes[] = createLittleEndianByteArray(currentAddressIbo, 8);
println("searching for possible ibo64 references to " + currentAddress.toString() + " ...");
searchForByteArray(searchBytes);
searchBytes = createLittleEndianByteArray(currentAddressIbo, 4);
println("searching for possible ibo32 references to " + currentAddress.toString() + " ...");
searchForByteArray(searchBytes);
}
/**
* Method to create a byte array out of the given long value
* @param value the given value
* @param numBytes the number of bytes from the low end of the value to copy into the array
* @return the little endian byte array for the given value
* @throws CancelledException if cancelled
*/
private byte[] createLittleEndianByteArray(long value, int numBytes)
throws CancelledException {
byte byteArray[] = new byte[numBytes];
for (int i = 0; i < numBytes; i++) {
monitor.checkCanceled();
byteArray[i] = (byte) (value >> (8 * i) & 0xff);
}
return byteArray;
}
private void searchForByteArray(byte[] byteArray) throws CancelledException {
Address start = currentProgram.getMinAddress();
Address found = find(start, byteArray);
while (found != null) {
monitor.checkCanceled();
println(found.toString());
start = found.add(1);
found = find(start, byteArray);
}
}
}
@@ -0,0 +1,191 @@
/* ###
* 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.
*/
//Script to update the given class's virtual functions' function signature data types and
// the given class's vfunction structure field name for any differing functions in
// the class virtual function table(s). To run, put the cursor on any of the desired class's
// virtual functions or at the top a class vftable. The script will not work if the <class>_vftable
// structure is not applied to the vftable using the ExtractClassInfoFromRTTIScript.
//@category C++
import java.util.*;
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateNameException;
public class UpdateClassFunctionDataScript extends GhidraScript {
@Override
public void run() throws Exception {
if (currentProgram == null) {
println("There is no open program");
return;
}
Function function = getFunctionContaining(currentAddress);
if (function != null) {
Namespace parentNamespace = function.getParentNamespace();
Parameter thisParam = function.getParameter(0);
if (thisParam.getName().equals("this")) {
DataType dataType = thisParam.getDataType();
if (dataType instanceof Pointer) {
Pointer pointer = (Pointer) dataType;
DataType baseDataType = pointer.getDataType();
if (baseDataType.getName().equals(parentNamespace.getName())) {
// call update
println("updating class " + parentNamespace.getName());
updateClassFunctionDataTypes(parentNamespace);
return;
}
}
}
}
Symbol primarySymbol = currentProgram.getSymbolTable().getPrimarySymbol(currentAddress);
if (primarySymbol.getName().equals("vftable") ||
primarySymbol.getName().substring(1).startsWith("vftable")) {
updateClassFunctionDataTypes(primarySymbol.getParentNamespace());
return;
}
}
private void updateClassFunctionDataTypes(Namespace classNamespace)
throws CancelledException, DuplicateNameException, DataTypeDependencyException {
List<Symbol> classVftableSymbols = getClassVftableSymbols(classNamespace);
Iterator<Symbol> vftableIterator = classVftableSymbols.iterator();
while (vftableIterator.hasNext()) {
monitor.checkCanceled();
Symbol vftableSymbol = vftableIterator.next();
Address vftableAddress = vftableSymbol.getAddress();
Data data = getDataAt(vftableAddress);
if (data == null) {
continue;
}
DataType baseDataType = data.getBaseDataType();
if (!(baseDataType instanceof Structure)) {
continue;
}
Structure vfunctionStructure = (Structure) baseDataType;
Category category = getDataTypeCategory(vfunctionStructure);
if (category == null) {
continue;
}
// check that the structure name starts with <classname>_vtable and that it is in
// the dt folder with name <classname>
if (category.getName().equals(classNamespace.getName()) &&
vfunctionStructure.getName().startsWith(classNamespace.getName() + "_vftable")) {
println(
"Updating vfunction signature data types and (if necessary) vtable structure for vftable at address " +
vftableAddress.toString());
updateVfunctionDataTypes(data, vfunctionStructure, vftableAddress);
}
}
}
/**
* Method to find any function signatures in the given vfunction structure that have changed
* and update the function signature data types
* @throws DuplicateNameException
* @throws DataTypeDependencyException
*/
private void updateVfunctionDataTypes(Data structureAtAddress, Structure vfunctionStructure,
Address vftableAddress) throws DuplicateNameException, DataTypeDependencyException {
DataTypeManager dtMan = currentProgram.getDataTypeManager();
int numVfunctions = structureAtAddress.getNumComponents();
for (int i = 0; i < numVfunctions; i++) {
Data dataComponent = structureAtAddress.getComponent(i);
Reference[] referencesFrom = dataComponent.getReferencesFrom();
if (referencesFrom.length != 1) {
continue;
}
Address functionAddress = referencesFrom[0].getToAddress();
Function vfunction = getFunctionAt(functionAddress);
if (vfunction == null) {
continue;
}
FunctionDefinitionDataType functionSignatureDataType =
(FunctionDefinitionDataType) vfunction.getSignature();
DataTypeComponent structureComponent = vfunctionStructure.getComponent(i);
DataType componentDataType = structureComponent.getDataType();
if (!(componentDataType instanceof Pointer)) {
continue;
}
Pointer pointer = (Pointer) componentDataType;
DataType pointedToDataType = pointer.getDataType();
if (functionSignatureDataType.equals(pointedToDataType)) {
continue;
}
// update data type with new new signature
dtMan.replaceDataType(pointedToDataType, functionSignatureDataType, true);
if (!structureComponent.getFieldName().equals(vfunction.getName())) {
structureComponent.setFieldName(vfunction.getName());
}
}
}
private Category getDataTypeCategory(DataType dataType) {
DataTypeManager dataTypeManager = currentProgram.getDataTypeManager();
CategoryPath originalPath = dataType.getCategoryPath();
Category category = dataTypeManager.getCategory(originalPath);
return category;
}
private List<Symbol> getClassVftableSymbols(Namespace classNamespace)
throws CancelledException {
SymbolTable symbolTable = currentProgram.getSymbolTable();
List<Symbol> vftableSymbols = new ArrayList<Symbol>();
SymbolIterator symbols = symbolTable.getSymbols(classNamespace);
while (symbols.hasNext()) {
monitor.checkCanceled();
Symbol symbol = symbols.next();
if (symbol.getName().equals("vftable") ||
symbol.getName().substring(1).startsWith("vftable")) {
vftableSymbols.add(symbol);
}
}
return vftableSymbols;
}
}
@@ -0,0 +1,280 @@
/* ###
* 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.
*/
import ghidra.app.decompiler.*;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.util.OptionsService;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.ParameterDefinition;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.model.pcode.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
public class DecompilerScriptUtils {
private Program program;
private PluginTool tool;
private TaskMonitor monitor;
private DecompInterface decompInterface;
DecompilerScriptUtils(Program program, PluginTool tool, TaskMonitor monitor) {
this.program = program;
this.monitor = monitor;
this.tool = tool;
decompInterface = setupDecompilerInterface();
}
/**
* Method to setup the decompiler interface for the given program
* @return the interface to the decompiler
*/
public DecompInterface setupDecompilerInterface() {
decompInterface = new DecompInterface();
DecompileOptions options;
options = new DecompileOptions();
OptionsService service = tool.getService(OptionsService.class);
if (service != null) {
ToolOptions opt = service.getOptions("Decompiler");
options.grabFromToolAndProgram(null, opt, program);
}
decompInterface.setOptions(options);
decompInterface.toggleCCode(true);
decompInterface.toggleSyntaxTree(true);
decompInterface.setSimplificationStyle("decompile");
if (!decompInterface.openProgram(program)) {
return null;
}
return decompInterface;
}
public DecompInterface getDecompilerInterface() {
return decompInterface;
}
/**
* Method to decompile the given function and return the function's HighFunction
* @param function the given function
* @return the HighFunction for the given function or null if there are issues decompiling the function
*/
public HighFunction getHighFunction(Function function) {
DecompileResults res = decompInterface.decompileFunction(function,
decompInterface.getOptions().getDefaultTimeout(), null);
decompInterface.flushCache();
return res.getHighFunction();
}
/**
* Method to get the decompiler version of the given function's return type (which is not always
* the same as the listing version)
* @param function the given function
* @return the decompiler version of the given function's return type (which is not always the
* same as the listing version)
*/
public DataType getDecompilerReturnType(Function function) {
DecompileResults decompRes = decompInterface.decompileFunction(function,
decompInterface.getOptions().getDefaultTimeout(), monitor);
//If can't decompile, return null
if (decompRes == null || decompRes.getHighFunction() == null ||
decompRes.getHighFunction().getFunctionPrototype() == null) {
return null;
}
return decompRes.getHighFunction().getFunctionPrototype().getReturnType();
}
/**
* Method to retrieve the function signature string from the decompiler function prototype. NOTE:
* if there is a this param, it will not be included.
* @param function the given function
* @param includeReturn if true, include the return type in the signature string
* @return the function signature string
* @throws CancelledException if cancelled
*/
public String getFunctionSignatureString(Function function, boolean includeReturn)
throws CancelledException {
if (function == null) {
return null;
}
StringBuffer stringBuffer = new StringBuffer();
DecompileResults decompRes = decompInterface.decompileFunction(function,
decompInterface.getOptions().getDefaultTimeout(), monitor);
//If can't decompile, show the listing version of the function signature
if (decompRes == null || decompRes.getHighFunction() == null ||
decompRes.getHighFunction().getFunctionPrototype() == null) {
return null;
}
HighFunction highFunction = decompRes.getHighFunction();
FunctionPrototype functionPrototype = highFunction.getFunctionPrototype();
if (includeReturn) {
stringBuffer.append(functionPrototype.getReturnType().getDisplayName() + " ");
}
stringBuffer.append(function.getName() + "(");
ParameterDefinition[] parameterDefinitions = functionPrototype.getParameterDefinitions();
if (parameterDefinitions == null) {
stringBuffer.append(");");
}
else {
int paramCount = parameterDefinitions.length;
for (int i = 0; i < parameterDefinitions.length; i++) {
monitor.checkCanceled();
ParameterDefinition param = parameterDefinitions[i];
if (param.getName().equals("this")) {
continue;
}
stringBuffer.append(param.getDataType().getDisplayName() + " " + param.getName());
if (i == paramCount) {
stringBuffer.append(");");
}
else {
stringBuffer.append(", ");
}
}
}
return stringBuffer.toString();
}
/**
* Get the parameters from the decompiler for the given function
* @param function the given function
* @return the decompiler parameters for the given function
*/
public ParameterDefinition[] getParametersFromDecompiler(Function function) {
DecompileResults decompRes = decompInterface.decompileFunction(function,
decompInterface.getOptions().getDefaultTimeout(), monitor);
if (decompRes == null || decompRes.getHighFunction() == null) {
return null;
}
return decompRes.getHighFunction().getFunctionPrototype().getParameterDefinitions();
}
/**
* Method to dispose the decompiler interface
*/
public void disposeDecompilerInterface() {
decompInterface.closeProgram();
decompInterface.dispose();
}
public Address getAssignedAddressFromPcode(Varnode storedValue) {
long addressOffset;
if (storedValue.isConstant()) {
addressOffset = storedValue.getOffset();
Address possibleAddress = toAddr(addressOffset);
if (possibleAddress == null || !program.getMemory().contains(possibleAddress)) {
return null;
}
return possibleAddress;
}
PcodeOp valuePcodeOp = storedValue.getDef();
if (valuePcodeOp == null) {
return null;
}
if (valuePcodeOp.getOpcode() == PcodeOp.CAST || valuePcodeOp.getOpcode() == PcodeOp.COPY) {
Varnode constantVarnode = valuePcodeOp.getInput(0);
return getAssignedAddressFromPcode(constantVarnode);
}
else if (valuePcodeOp.getOpcode() != PcodeOp.PTRSUB) {
return null;
}
// don't need to check isConst bc always is
Varnode constantVarnode = valuePcodeOp.getInput(1);
addressOffset = constantVarnode.getOffset();
Address possibleAddress = toAddr(addressOffset);
if (possibleAddress == null || !program.getMemory().contains(possibleAddress)) {
return null;
}
return possibleAddress;
}
/**
* Method to get the called address from the given CALL pcodeOp's input Varnode
* @param pcodeOpInput the Varnode from a CALL pcodeOp input
* @return the calledAddress
*/
public Address getCalledAddressFromCallingPcodeOp(Varnode pcodeOpInput) {
PcodeOp def = pcodeOpInput.getDef();
if (def == null) {
return null;
}
Varnode defInput = def.getInput(1);
if (defInput == null) {
return null;
}
Address defInputAddress = defInput.getAddress();
if (defInputAddress == null) {
return null;
}
long offset = defInputAddress.getOffset();
Address calledAddress = program.getMinAddress().getNewAddress(offset);
return calledAddress;
}
/**
* Returns a new address with the specified offset in the default address space.
* @param offset the offset for the new address
* @return a new address with the specified offset in the default address space
*/
public final Address toAddr(long offset) {
return program.getAddressFactory().getDefaultAddressSpace().getAddress(offset);
}
}
@@ -0,0 +1,381 @@
/* ###
* 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.
*/
import java.util.*;
import ghidra.program.model.data.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
public class EditStructureUtils {
EditStructureUtils() {
}
/**
* Method to determine if the given containing struct has components at the given offset
* that are either the same, all undefined1's or equivalently sized undefineds as the components
* in the given possible newInternalStruct
* @param containingStruct the given structure to check
* @param offset the offset at the containing struct to check for equivalent components
* @param newInternalStruct the possible new internal struct to replace at the given offset in the
* given containing struct
* @param monitor task monitor
* @return true if components in the containing struct are replaceable with the possible new
* internal struct, false otherwise
* @throws CancelledException if cancelled
*/
public boolean hasReplaceableComponentsAtOffset(Structure containingStruct, int offset,
Structure newInternalStruct, TaskMonitor monitor) throws CancelledException {
DataTypeComponent[] newStructComponents = newInternalStruct.getComponents();
for (DataTypeComponent newStructComponent : newStructComponents) {
monitor.checkCanceled();
int structOffset = newStructComponent.getOffset();
DataTypeComponent currentComponentAtOffset =
containingStruct.getComponentAt(offset + structOffset);
DataType newStructCompDt = newStructComponent.getDataType();
DataType containingComDt = currentComponentAtOffset.getDataType();
// if component dts are equal continue
if (newStructCompDt.equals(containingComDt)) {
continue;
}
// if containing is all undefined1s at equivalent location then continue
if (hasEnoughUndefined1sAtOffset(containingStruct, offset + structOffset,
newStructCompDt.getLength(), monitor)) {
continue;
}
// otherwise if lengths not equal then return false
if (newStructCompDt.getLength() != containingComDt.getLength()) {
return false;
}
// component lengths are equal if it gets here
// if containing is not undefined then return false because the components would be
// incompatible types
if (!containingComDt.getName().startsWith("undefined")) {
return false;
}
}
return true;
}
/**
* Method to determine if there are at least the given length of undefined size 1 components at the given offset in the given structure
* @param structure the given structure
* @param offset the given offset
* @param length the length of undefine size 1 components to check for starting at given offset
* @param monitor task monitor
* @return true if there are at least length undefined size 1 components at the given offset in the given structure
* @throws CancelledException if cancelled
*/
public boolean hasEnoughUndefined1sAtOffset(Structure structure, int offset, int length,
TaskMonitor monitor) throws CancelledException {
if (structure.getLength() < offset + length) {
return false;
}
for (int i = offset; i < offset + length; i++) {
monitor.checkCanceled();
DataTypeComponent component = structure.getComponentAt(i);
if (component == null) {
return false;
}
DataType dataType = component.getDataType();
if (dataType.getName().equals("undefined") && dataType.getLength() == 1) {
continue;
}
return false;
}
return true;
}
/**
* Method to check that all components starting with the one at the given offset and encompassing
* all up to the given length from the given offset are an undefined data type. If so, clear them all and return true.
* If not, do nothing and return false.
* @param structure the given structure
* @param offset the given offset in the structure
* @param length the total length from the offset to hopefully clear
* @param monitor task monitor
* @return true if successfully cleared from offset to offset+length, false otherwise
* @throws CancelledException if cancelled
*/
public boolean clearLengthAtOffset(Structure structure, int offset, int length,
TaskMonitor monitor) throws CancelledException {
if (structure.getLength() < offset + length) {
return false;
}
List<Integer> offsetsToClear = new ArrayList<Integer>();
int endOfClear = offset + length;
while (offset < endOfClear) {
monitor.checkCanceled();
DataTypeComponent component = structure.getComponentAt(offset);
DataType dataType = component.getDataType();
// return false if it would clear too much
if (offset + dataType.getLength() > endOfClear) {
return false;
}
offsetsToClear.add(offset);
offset += dataType.getLength();
continue;
}
if (offsetsToClear.isEmpty()) {
return false;
}
Iterator<Integer> offsetIterator = offsetsToClear.iterator();
while (offsetIterator.hasNext()) {
Integer componentOffset = offsetIterator.next();
// need to get ordinal from component using offset because after clearing
// component size > 1, the index changes
DataTypeComponent component = structure.getComponentAt(componentOffset);
int index = component.getOrdinal();
structure.clearComponent(index);
}
return true;
}
/**
* Method to determine if data type is an undefined size 1 data type
* @param dataType the given data type
* @return true if given data type is undefined size 1, false otherwise
*/
public boolean isUndefined1(DataType dataType) {
if (isUndefined(dataType) && dataType.getLength() == 1) {
return true;
}
return false;
}
/**
* Method to determine if data type is an undefined data type of any size
* @param dataType the given data type
* @return true if given data type is undefined of any size, false otherwise
*/
public boolean isUndefined(DataType dataType) {
if (dataType.getName().contains("undefined")) {
return true;
}
return false;
}
/**
* Method to determine if there are at least the given length of undefined (any size) components at the given offset in the given structure
* @param structure the given structure
* @param offset the given offset
* @param length the total length of undefined components to check for starting at given offset
* @param monitor task monitor
* @return true if there are at least total length of undefined components at the given offset in the given structure
* @throws CancelledException if cancelled
*/
public boolean hasEnoughUndefinedsOfAnyLengthAtOffset(Structure structure, int offset,
int length, TaskMonitor monitor) throws CancelledException {
if (structure.getLength() < offset + length) {
return false;
}
int endOfRange = offset + length;
while (offset < endOfRange) {
monitor.checkCanceled();
DataTypeComponent component = structure.getComponentAt(offset);
DataType dataType = component.getDataType();
if (isUndefined(dataType)) {
offset += dataType.getLength();
if (offset > endOfRange) {
return false;
}
continue;
}
return false;
}
return true;
}
/**
* Method to add a field to the given structure. If the structure already has data
* at the given offset, don't replace. If there is undefined data there then replace
* it with the data type. If the structure empty, insert the data type at the given offset.
* If the structure is not big enough and not-empty, grow it so there is room to replace.
* @param structure the given structure
* @param offset the offset to add a field
* @param dataType the data type to add to the field at the given offset
* @param fieldName the name of field
* @param monitor task monitor
* @return the updated structure data type
* @throws IllegalArgumentException if issue inserting data type into structure
* @throws CancelledException if cancelled
*/
public Structure addDataTypeToStructure(Structure structure, int offset,
DataType dataType, String fieldName, TaskMonitor monitor)
throws CancelledException, IllegalArgumentException {
int dataTypeLength = dataType.getLength();
int endOfDataTypeInStruct = offset + dataTypeLength;
int roomForData = structure.getLength() - endOfDataTypeInStruct;
// if structure isn't defined insert
if (structure.isNotYetDefined()) {
structure.insertAtOffset(offset, dataType, dataTypeLength, fieldName, null);
return structure;
}
// if not enough room, grow the structure
if (roomForData < 0) {
structure.growStructure(0 - roomForData);
}
// else replace only if data already there are enough undefined data types at
// that offset to fit the new data type
if (hasEnoughUndefined1sAtOffset(structure, offset, dataTypeLength, monitor)) {
structure.replaceAtOffset(offset, dataType, dataTypeLength, fieldName, null);
}
return structure;
}
/**
* Method to determine if the given structure has room at the given offset to have a component of the given length added to it
* @param structureDataType the given structure
* @param offset the offset to check for available room
* @param lengthToAdd the length of bytes wanted to add at the offset
* @param monitor task monitor
* @return true if the given structure has room at the given offset to have a component of the given length added to it
* @throws CancelledException if cancelled
*/
public boolean canAdd(Structure structureDataType, int offset, int lengthToAdd,
TaskMonitor monitor)
throws CancelledException {
// not big enough so return true so it can be grown
DataTypeComponent component = structureDataType.getComponentAt(offset);
if (component == null) {
return true;
}
// no matter what size, if the data type at the offset is defined, return false
// so it is not replaced
if (!component.getDataType().getName().equals("undefined")) {
return false;
}
// if structure isn't big enough but what is there is all undefined
// return true to grow it
int structLen = structureDataType.getLength();
int spaceAvailable = structLen - (offset + lengthToAdd);
if (spaceAvailable < 0) {
int overflow = 0 - spaceAvailable;
return hasEnoughUndefined1sAtOffset(structureDataType, offset, structLen - overflow,
monitor);
}
// if structure is big enough and there is room at the offset return true
return hasEnoughUndefined1sAtOffset(structureDataType, offset, lengthToAdd, monitor);
}
/**
* Method to retrieve the number of undefined size 1 components in the given structure before the given offset
* @param structure the given structure
* @param offset the given offset
* @param monitor task monitor
* @return the number of undefined size 1 components in the given structure before the given offset
* @throws CancelledException if cancelled
*/
public int getNumberOfUndefinedsBeforeOffset(Structure structure, int offset,
TaskMonitor monitor) throws CancelledException {
int numUndefineds = 0;
int index = offset - 1;
while (index >= 0) {
monitor.checkCanceled();
DataTypeComponent component = structure.getComponentAt(index);
if (component.getDataType().getName().equals("undefined") &&
component.getLength() == 1) {
index--;
numUndefineds++;
}
else {
return numUndefineds;
}
}
return numUndefineds;
}
/**
* Method to retrieve the number of undefined size 1 components starting at the given offset in the given structure
* @param structure the given structure
* @param offset the given offset
* @param monitor task monitor
* @return the number of undefined size 1 components starting at the given offset in the given structure
* @throws CancelledException if cancelled
*/
public int getNumberOfUndefinedsStartingAtOffset(Structure structure, int offset,
TaskMonitor monitor) throws CancelledException {
int numUndefineds = 0;
int index = offset;
while (index < structure.getLength()) {
monitor.checkCanceled();
DataTypeComponent component = structure.getComponentAt(index);
if (component.getDataType().getName().equals("undefined") &&
component.getLength() == 1) {
index++;
numUndefineds++;
}
else {
return numUndefineds;
}
}
return numUndefineds;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,292 @@
/* ###
* 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.
*/
import java.util.*;
import ghidra.app.util.NamespaceUtils;
import ghidra.framework.options.Options;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.flatapi.FlatProgramAPI;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.Namespace;
import ghidra.program.model.symbol.SymbolType;
import ghidra.program.util.ProgramLocation;
import ghidra.util.Msg;
import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
public class RTTIClassRecoverer extends RecoveredClassUtils {
boolean programHasRTTIApplied = false;
String ghidraVersion;
Program program;
TaskMonitor monitor;
RTTIClassRecoverer(Program program, ProgramLocation location, PluginTool tool,
FlatProgramAPI api, boolean createBookmarks, boolean useShortTemplates,
boolean nameVfunctions,
TaskMonitor monitor) {
super(program, location, tool, api, createBookmarks, useShortTemplates, nameVfunctions,
monitor);
this.program = program;
this.monitor = monitor;
this.location = location;
this.tool = tool;
this.api = api;
this.createBookmarks = createBookmarks;
this.useShortTemplates = useShortTemplates;
this.nameVfunctions = nameVfunctions;
ghidraVersion = getVersionOfGhidra();
}
public DecompilerScriptUtils getDecompilerUtils() {
return decompilerUtils;
}
public int getDefaultPointerSize() {
return defaultPointerSize;
}
public DataTypeManager getDataTypeManager() {
return dataTypeManager;
}
public boolean containsRTTI() throws CancelledException {
return true;
}
public boolean isValidProgramType() {
return true;
}
public boolean isValidProgramSize() {
if (defaultPointerSize != 4 && defaultPointerSize != 8) {
return false;
}
return true;
}
/**
* 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
*/
public String getVersionOfGhidra() {
Options options = program.getOptions("Program Information");
Object ghidraVersionObject = options.getObject("Created With Ghidra Version", null);
return ghidraVersionObject.toString();
}
public void fixUpProgram() {
return;
}
public List<RecoveredClass> createRecoveredClasses() {
return new ArrayList<RecoveredClass>();
}
/**
* Method to promote the namespace is a class namespace.
* @param vftableNamespace the namespace for the vftable
* @return true if namespace is (now) a class namespace or false if it could not be promoted.
*/
public Namespace promoteToClassNamespace(Namespace vftableNamespace) {
try {
Namespace newClass = NamespaceUtils.convertNamespaceToClass(vftableNamespace);
SymbolType symbolType = newClass.getSymbol().getSymbolType();
if (symbolType == SymbolType.CLASS) {
return newClass;
}
Msg.debug(this,
"Could not promote " + vftableNamespace.getName() + " to a class namespace");
return null;
}
catch (InvalidInputException e) {
Msg.debug(this, "Could not promote " + vftableNamespace.getName() +
" to a class namespace because " + e.getMessage());
return null;
}
}
/**
* Method to iterate over all the RecoveredClass objects and see if there is an existing class structure data type already
* if so, add it to the RecoveredClass object
* @param recoveredClasses List of RecoveredClass objects
* @throws CancelledException when cancelled
*/
public void retrieveExistingClassStructures(List<RecoveredClass> recoveredClasses)
throws CancelledException {
Iterator<RecoveredClass> recoveredClassIterator = recoveredClasses.iterator();
while (recoveredClassIterator.hasNext()) {
monitor.checkCanceled();
RecoveredClass recoveredClass = recoveredClassIterator.next();
// if class is non-virtual have to search for an existing class datatype
if (!recoveredClass.hasVftable()) {
DataType[] possibleExistingClassStructures =
extraUtils.getDataTypes(recoveredClass.getName());
if (possibleExistingClassStructures.length == 0) {
continue;
}
for (int i = 0; i < possibleExistingClassStructures.length; i++) {
monitor.checkCanceled();
if (!(possibleExistingClassStructures[i] instanceof Structure)) {
continue;
}
if (possibleExistingClassStructures[i].isNotYetDefined()) {
continue;
}
Structure existingClassStructure =
(Structure) possibleExistingClassStructures[i];
recoveredClass.addExistingClassStructure(existingClassStructure);
break;
}
}
//Iterate over constructor/destructor functions
List<Function> constructorOrDestructorFunctions =
recoveredClass.getConstructorOrDestructorFunctions();
Iterator<Function> constDestIterator = constructorOrDestructorFunctions.iterator();
while (constDestIterator.hasNext()) {
monitor.checkCanceled();
Function constDestFunction = constDestIterator.next();
Namespace parentNamespace = constDestFunction.getParentNamespace();
if (!parentNamespace.equals(recoveredClass.getClassNamespace())) {
continue;
}
if (recoveredClass.hasExistingClassStructure()) {
continue;
}
int parameterCount = constDestFunction.getParameterCount();
if (parameterCount == 0) {
continue;
}
DataType dataType = constDestFunction.getParameter(0).getDataType();
CategoryPath dataTypePath = dataType.getDataTypePath().getCategoryPath();
if (!(dataType instanceof Pointer)) {
continue;
}
String dataTypeName = dataType.getName();
dataTypeName = dataTypeName.replace(" *", "");
if (!dataTypeName.equals(recoveredClass.getName())) {
continue;
}
Structure existingClassStructure =
(Structure) dataTypeManager.getDataType(dataTypePath, dataTypeName);
if (!existingClassStructure.isNotYetDefined()) {
recoveredClass.addExistingClassStructure(existingClassStructure);
break;
}
}
}
}
/**
* Method to get class data information from destructors if a class has no constructors
* @param recoveredClasses list of classes
* @throws CancelledException if cancelled
* @throws InvalidInputException if issues setting function return
* @throws DuplicateNameException if try to create same symbol name already in namespace
* @throws CircularDependencyException if parent namespace is descendent of given namespace
*/
public void figureOutClassDataMembers(List<RecoveredClass> recoveredClasses)
throws CancelledException, DuplicateNameException, InvalidInputException,
CircularDependencyException {
Iterator<RecoveredClass> classIterator = recoveredClasses.iterator();
while (classIterator.hasNext()) {
monitor.checkCanceled();
RecoveredClass recoveredClass = classIterator.next();
// we can only figure out structure info for functions with vftable since that is
// what we use to determine which variable is being used to store the class structure
if (!recoveredClass.hasVftable()) {
continue;
}
// if the class already has an existing class structure from pdb then no need to process
if (recoveredClass.hasExistingClassStructure()) {
continue;
}
List<Function> memberFunctionsToProcess = new ArrayList<Function>();
memberFunctionsToProcess.addAll(recoveredClass.getConstructorList());
memberFunctionsToProcess.addAll(recoveredClass.getDestructorList());
memberFunctionsToProcess.addAll(recoveredClass.getIndeterminateList());
memberFunctionsToProcess.addAll(recoveredClass.getInlinedConstructorList());
Iterator<Function> memberFunctionIterator = memberFunctionsToProcess.iterator();
while (memberFunctionIterator.hasNext()) {
monitor.checkCanceled();
Function memberFunction = memberFunctionIterator.next();
if (getVftableReferences(memberFunction) == null) {
continue;
}
// skip if other classes contain this function as an inline inlined destructor or
// inlined indeterminate
if (isInlineDestructorOrIndeterminateInAnyClass(memberFunction)) {
continue;
}
gatherClassMemberDataInfoForFunction(recoveredClass, memberFunction);
}
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,68 @@
/* ###
* 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.
*/
// Script to fix up Windows RTTI vtables and structures
//@category C++
import ghidra.app.cmd.data.rtti.RttiUtil;
import ghidra.app.plugin.prototype.MicrosoftCodeAnalyzerPlugin.PEUtil;
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
public class IdPeRttiScript extends GhidraScript {
@Override
public void run() throws Exception {
if (currentProgram == null) {
if (!isRunningHeadless()) {
println("There is no open program.");
return;
}
currentProgram.setTemporary(true);
return;
}
boolean isPe = PEUtil.isVisualStudioOrClangPe(currentProgram);
if (!isPe) {
if (!isRunningHeadless()) {
println("The current program is not a Visual Studio or Clang PE program.");
return;
}
currentProgram.setTemporary(true);
return;
}
Address commonVfTableAddress = RttiUtil.findTypeInfoVftableAddress(currentProgram, monitor);
if (commonVfTableAddress == null) {
if (!isRunningHeadless()) {
println("The current program does not appear to contain RTTI.");
return;
}
currentProgram.setTemporary(true);
return;
}
if (!isRunningHeadless()) {
println("The current program is a Visual Studio PE or Clang that contains RTTI.");
return;
}
currentProgram.setTemporary(false);
}
}
@@ -0,0 +1,36 @@
/* ###
* 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.
*/
// Script to fix up Windows RTTI vtables and structures
//@category C++
import ghidra.app.plugin.prototype.MicrosoftCodeAnalyzerPlugin.RttiAnalyzer;
import ghidra.app.script.GhidraScript;
import ghidra.app.services.Analyzer;
import ghidra.app.util.importer.MessageLog;
public class RunRttiAnalyzerScript extends GhidraScript {
@Override
public void run() throws Exception {
runRTTIAnalyzer();
}
private void runRTTIAnalyzer() throws Exception {
Analyzer analyzer = new RttiAnalyzer();
analyzer.added(currentProgram, currentProgram.getAddressFactory().getAddressSet(), monitor,
new MessageLog());
}
}