mirror of
https://github.com/advanced-threat-research/GhidraScripts
synced 2026-06-08 13:03:27 +00:00
232 lines
7.9 KiB
Java
232 lines
7.9 KiB
Java
//Colourises the complexity of a function where it is called in the disassembly listing. Light red is not complex, dark red is complex.
|
|
//@author Max 'Libra' Kersten for Trellix. The graph related code has been taken from and inspired by Ghidra's base: https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/module/ComplexityDepthModularizationCmd.java#L43
|
|
//@category
|
|
//@keybinding
|
|
//@menupath
|
|
//@toolbar
|
|
|
|
import java.awt.Color;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
import ghidra.app.decompiler.DecompInterface;
|
|
import ghidra.app.script.GhidraScript;
|
|
import ghidra.app.util.viewer.listingpanel.PropertyBasedBackgroundColorModel;
|
|
import ghidra.graph.GDirectedGraph;
|
|
import ghidra.graph.GraphAlgorithms;
|
|
import ghidra.graph.GraphFactory;
|
|
import ghidra.program.database.IntRangeMap;
|
|
import ghidra.program.model.address.Address;
|
|
import ghidra.program.model.block.BasicBlockModel;
|
|
import ghidra.program.model.block.CodeBlock;
|
|
import ghidra.program.model.block.CodeBlockIterator;
|
|
import ghidra.program.model.block.CodeBlockReference;
|
|
import ghidra.program.model.block.CodeBlockReferenceIterator;
|
|
import ghidra.program.model.block.graph.CodeBlockEdge;
|
|
import ghidra.program.model.block.graph.CodeBlockVertex;
|
|
import ghidra.program.model.listing.Function;
|
|
import ghidra.program.model.symbol.Reference;
|
|
import ghidra.program.model.symbol.ReferenceIterator;
|
|
import ghidra.util.exception.CancelledException;
|
|
import ghidra.util.exception.DuplicateNameException;
|
|
|
|
public class ColouriseByComplexity extends GhidraScript {
|
|
|
|
@Override
|
|
protected void run() throws Exception {
|
|
// Initialise the decompiler
|
|
DecompInterface decompiler = new DecompInterface();
|
|
// Open the current program
|
|
decompiler.openProgram(currentProgram);
|
|
|
|
// Get the call graph
|
|
GDirectedGraph<CodeBlockVertex, CodeBlockEdge> callGraph = createCallGraph();
|
|
// Obtain the complexity depth graph
|
|
Map<CodeBlockVertex, Integer> complexityDepth = GraphAlgorithms.getComplexityDepth(callGraph);
|
|
// Get the list of all levels within the graph
|
|
List<List<Function>> partition = createFunctionList(complexityDepth);
|
|
|
|
// Remove the empty levels from the list
|
|
partition = cleanList(partition);
|
|
|
|
// Iterate over the levels
|
|
for (int i = 0; partition.size() > i; i++) {
|
|
// Get the current list (or level, if you will)
|
|
List<Function> list = partition.get(i);
|
|
|
|
// Iterate over the functions within the current list
|
|
for (Function function : list) {
|
|
// Exclude thunk and external functions
|
|
if (function.isThunk() || function.isExternal()) {
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Store the number of levels as a double, required to perform arithmetics that
|
|
* return a double
|
|
*/
|
|
double size = partition.size();
|
|
|
|
// Get the current value, incremented by one since i is zero-based
|
|
double currentValue = i + 1;
|
|
|
|
/*
|
|
* Calculate the percentage of this level is relatively to the rest all levels
|
|
*/
|
|
double percentage = currentValue / size;
|
|
|
|
/*
|
|
* One minus the percentage, since the first entry in the partition list, has
|
|
* the highest complexity depth
|
|
*/
|
|
double red = (1 - percentage) * 255;
|
|
|
|
// Get all references to the function
|
|
ReferenceIterator referenceIterator = currentProgram.getReferenceManager()
|
|
.getReferencesTo(function.getEntryPoint());
|
|
|
|
// Iterate over all references
|
|
for (Reference ref : referenceIterator) {
|
|
// Create the colour
|
|
Color color = new Color((int) red, 0, 0);
|
|
// Set the background colour in the listing for the reference
|
|
setBackgroundColor(ref.getFromAddress(), ref.getFromAddress(), color);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Takes a list of lists and returns a list of lists where none of the lists is
|
|
* empty
|
|
*
|
|
* @param list the list to clean
|
|
* @return the same list but without any empty lists within
|
|
*/
|
|
private List<List<Function>> cleanList(List<List<Function>> list) {
|
|
List<List<Function>> output = new ArrayList<>();
|
|
|
|
for (List<Function> item : list) {
|
|
if (item.isEmpty()) {
|
|
continue;
|
|
}
|
|
output.add(item);
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
// Code below is taken from the Ghidra source code
|
|
|
|
public void setBackgroundColor(Address min, Address max, Color c) {
|
|
IntRangeMap map = getColorRangeMap(true);
|
|
if (map != null) {
|
|
map.setValue(min, max, c.getRGB());
|
|
}
|
|
}
|
|
|
|
private IntRangeMap getColorRangeMap(boolean create) {
|
|
IntRangeMap map = currentProgram.getIntRangeMap(PropertyBasedBackgroundColorModel.COLOR_PROPERTY_NAME);
|
|
if (map == null && create) {
|
|
try {
|
|
map = currentProgram.createIntRangeMap(PropertyBasedBackgroundColorModel.COLOR_PROPERTY_NAME);
|
|
} catch (DuplicateNameException e) {
|
|
// can't happen since we just checked for it!
|
|
}
|
|
}
|
|
return map;
|
|
}
|
|
|
|
private int getMaxLevel(Map<CodeBlockVertex, Integer> levelMap) {
|
|
int maxLevel = -1;
|
|
for (Integer level : levelMap.values()) {
|
|
if (level > maxLevel) {
|
|
maxLevel = level;
|
|
}
|
|
}
|
|
return maxLevel;
|
|
}
|
|
|
|
private Function getFunctionFromCodeBlockVertex(CodeBlockVertex vertex) {
|
|
Address startAddress = vertex.getCodeBlock().getFirstStartAddress();
|
|
Function function = getFunctionAt(startAddress);
|
|
return function;
|
|
}
|
|
|
|
private List<List<Function>> createFunctionList(Map<CodeBlockVertex, Integer> levelMap) {
|
|
List<List<Function>> levelList = new ArrayList<>();
|
|
int maxLevel = getMaxLevel(levelMap);
|
|
for (int i = 0; i <= maxLevel; i++) {
|
|
levelList.add(new ArrayList<Function>());
|
|
}
|
|
for (CodeBlockVertex vertex : levelMap.keySet()) {
|
|
int reverseLevel = maxLevel - levelMap.get(vertex);
|
|
Function function = getFunctionFromCodeBlockVertex(vertex);
|
|
if (function != null) {
|
|
levelList.get(reverseLevel).add(function);
|
|
}
|
|
}
|
|
return levelList;
|
|
}
|
|
|
|
protected GDirectedGraph<CodeBlockVertex, CodeBlockEdge> createCallGraph() throws CancelledException {
|
|
|
|
Map<CodeBlock, CodeBlockVertex> instanceMap = new HashMap<>();
|
|
GDirectedGraph<CodeBlockVertex, CodeBlockEdge> graph = GraphFactory.createDirectedGraph();
|
|
|
|
CodeBlockIterator codeBlocks = new BasicBlockModel(currentProgram, true).getCodeBlocks(monitor);
|
|
while (codeBlocks.hasNext()) {
|
|
CodeBlock block = codeBlocks.next();
|
|
|
|
CodeBlockVertex fromVertex = instanceMap.get(block);
|
|
if (fromVertex == null) {
|
|
fromVertex = new CodeBlockVertex(block);
|
|
instanceMap.put(block, fromVertex);
|
|
graph.addVertex(fromVertex);
|
|
}
|
|
|
|
// destinations section
|
|
addEdgesForDestinations(graph, fromVertex, block, instanceMap);
|
|
}
|
|
return graph;
|
|
}
|
|
|
|
private void addEdgesForDestinations(GDirectedGraph<CodeBlockVertex, CodeBlockEdge> graph,
|
|
CodeBlockVertex fromVertex, CodeBlock sourceBlock, Map<CodeBlock, CodeBlockVertex> instanceMap)
|
|
throws CancelledException {
|
|
|
|
CodeBlockReferenceIterator iterator = sourceBlock.getDestinations(monitor);
|
|
while (iterator.hasNext()) {
|
|
monitor.checkCancelled();
|
|
|
|
CodeBlockReference destination = iterator.next();
|
|
CodeBlock targetBlock = getDestinationBlock(destination);
|
|
if (targetBlock == null) {
|
|
continue; // no block found
|
|
}
|
|
|
|
CodeBlockVertex targetVertex = instanceMap.get(targetBlock);
|
|
if (targetVertex == null) {
|
|
targetVertex = new CodeBlockVertex(targetBlock);
|
|
instanceMap.put(targetBlock, targetVertex);
|
|
}
|
|
|
|
graph.addVertex(targetVertex);
|
|
graph.addEdge(new CodeBlockEdge(fromVertex, targetVertex));
|
|
}
|
|
}
|
|
|
|
private CodeBlock getDestinationBlock(CodeBlockReference destination) throws CancelledException {
|
|
|
|
Address targetAddress = destination.getDestinationAddress();
|
|
CodeBlock targetBlock = new BasicBlockModel(currentProgram, true).getFirstCodeBlockContaining(targetAddress,
|
|
monitor);
|
|
if (targetBlock == null) {
|
|
return null; // no code found for call; external?
|
|
}
|
|
|
|
return targetBlock;
|
|
}
|
|
} |