mirror of
https://github.com/advanced-threat-research/GhidraScripts
synced 2026-06-08 13:03:27 +00:00
765 lines
26 KiB
Java
765 lines
26 KiB
Java
//Gets all functions within the program and allows one to see the complexity depth of each function. 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
|
|
//@author Max 'Libra' Kersten for Trellix
|
|
//@category
|
|
//@keybinding
|
|
//@menupath
|
|
//@toolbar
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.IOException;
|
|
import java.io.InputStreamReader;
|
|
import java.io.OutputStream;
|
|
import java.net.HttpURLConnection;
|
|
import java.net.URI;
|
|
import java.net.URL;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.concurrent.ExecutorService;
|
|
import java.util.concurrent.Executors;
|
|
|
|
import com.google.gson.Gson;
|
|
import com.google.gson.JsonSyntaxException;
|
|
|
|
import ghidra.app.decompiler.DecompInterface;
|
|
import ghidra.app.decompiler.DecompileResults;
|
|
import ghidra.app.decompiler.DecompiledFunction;
|
|
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
|
|
import ghidra.app.script.GhidraScript;
|
|
import ghidra.graph.GDirectedGraph;
|
|
import ghidra.graph.GraphAlgorithms;
|
|
import ghidra.graph.GraphFactory;
|
|
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.listing.Variable;
|
|
import ghidra.program.model.symbol.SourceType;
|
|
import ghidra.util.SystemUtilities;
|
|
import ghidra.util.exception.CancelledException;
|
|
|
|
public class GhidrAI extends GhidraScript {
|
|
|
|
/*
|
|
* The number of seconds the decompiler will run before timing out
|
|
*/
|
|
private final int DECOMPILER_TIMEOUT = 600;
|
|
|
|
/*
|
|
* The number of threads via which the AI LLM provider is contacted. This
|
|
* decreases the wait time on the LLM as multiple functions are handled at the
|
|
* same time. If your LLM cannot handle multiple connections, adjust this value.
|
|
*/
|
|
private final int THREAD_COUNT = 10;
|
|
|
|
/*
|
|
* The number of milliseconds to wait before the HTTP request times out
|
|
*/
|
|
private final int POST_REQUEST_TIMEOUT = 120_000;
|
|
|
|
/*
|
|
* The HTTP URL to the LLM API
|
|
*/
|
|
private String API_URL;
|
|
|
|
/*
|
|
* True if functions should be renamed, false if not. Only functions starting
|
|
* with FUN_ are renamed if this is true.
|
|
*/
|
|
private boolean RENAME_FUNCTION;
|
|
|
|
/*
|
|
* True if variables within functions should be renamed
|
|
*/
|
|
private boolean RENAME_VARIABLES;
|
|
|
|
/*
|
|
* The Google JSON handling library which is included in Ghidra, used to convert
|
|
* the JSON response from the LLM proxy
|
|
*/
|
|
private Gson gson;
|
|
|
|
/*
|
|
* Ghidra's automatic analysis manager, used to check if one or more analyzers
|
|
* are still running
|
|
*/
|
|
private AutoAnalysisManager autoAnalysisManager;
|
|
|
|
@Override
|
|
protected void run() throws Exception {
|
|
/*
|
|
* Required input:
|
|
*
|
|
* - AI API URL
|
|
*
|
|
* - Rename function based on suggestion: yes/no
|
|
*
|
|
* - Rename variables based on suggestions: yes/no
|
|
*/
|
|
|
|
// Get all provided values
|
|
API_URL = askString("AI API URL", "The URL where the AI's API is accessible at");
|
|
RENAME_FUNCTION = askYesNo("", "Rename the function based on the AI's suggestion");
|
|
RENAME_VARIABLES = askYesNo("", "Rename variables based on the AI's suggestions");
|
|
|
|
// Create a string to provide the feedback to the user
|
|
String inputFeedback = "Received input:\n";
|
|
inputFeedback += "\tAI URL: " + API_URL + "\n";
|
|
inputFeedback += "\tRename functions: " + RENAME_FUNCTION + "\n";
|
|
inputFeedback += "\tRename variables: " + RENAME_VARIABLES;
|
|
|
|
// Log the input feedback
|
|
println(inputFeedback);
|
|
|
|
// Now that all checks are done, the variables in the class are initialised
|
|
gson = new Gson();
|
|
autoAnalysisManager = AutoAnalysisManager.getAnalysisManager(currentProgram);
|
|
DecompInterface decompiler = new DecompInterface();
|
|
decompiler.openProgram(currentProgram);
|
|
|
|
/*
|
|
* Notify the user of the current activity. Generating the complexity graph
|
|
* takes a bit, but each step is relatively small. As such, it is summarised as
|
|
* a single-step task to the user
|
|
*/
|
|
monitor.initialize(1, "Generating the complexity graph for the current program");
|
|
|
|
// Generate the complexity graph
|
|
GDirectedGraph<CodeBlockVertex, CodeBlockEdge> callGraph = createCallGraph();
|
|
Map<CodeBlockVertex, Integer> complexityDepth = GraphAlgorithms.getComplexityDepth(callGraph);
|
|
List<List<Function>> partition = createFunctionList(complexityDepth);
|
|
|
|
// Remove the empty levels from the list
|
|
partition = cleanAndReverseList(partition);
|
|
|
|
int currentFunctionCount = 0;
|
|
int totalFunctionCount = 0;
|
|
for (List<Function> list : partition) {
|
|
totalFunctionCount += list.size();
|
|
}
|
|
|
|
// Increment the monitor, marking this task as complete
|
|
monitor.increment();
|
|
|
|
/*
|
|
* To iterate over the list from start to end (rather than end to start), you
|
|
* need to reverse the list. This can be done using
|
|
* Collections.reverse(partition).
|
|
*/
|
|
|
|
/*
|
|
* Declare the executor service variable. It is instantiated per level within
|
|
* the partition.
|
|
*/
|
|
ExecutorService executor;
|
|
|
|
/*
|
|
* Iterate backwards over the list, handling the functions with the least
|
|
* complexity depth first
|
|
*/
|
|
for (int i = 0; i < partition.size(); i++) {
|
|
// Get the functions for the complexity level
|
|
List<Function> list = partition.get(i);
|
|
|
|
if (list.isEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Create the monitor message, taking the potential plurality of the function
|
|
* count into account
|
|
*/
|
|
String listSize = "Iterating over " + list.size() + " function";
|
|
if (list.size() > 1) {
|
|
listSize += "s";
|
|
}
|
|
String monitorMessage = listSize + " in level " + i + "/" + partition.size();
|
|
|
|
/*
|
|
* Initialise the monitor with the message. Each thread will call back to the
|
|
* thread safe increment method
|
|
*/
|
|
monitor.initialize(list.size(), monitorMessage);
|
|
|
|
/*
|
|
* The number of threads is equal to the globally set maximum number of thread
|
|
* counts unless the size of the list is less than that.
|
|
*/
|
|
if (list.size() > THREAD_COUNT) {
|
|
executor = Executors.newFixedThreadPool(THREAD_COUNT);
|
|
} else {
|
|
executor = Executors.newFixedThreadPool(list.size());
|
|
}
|
|
|
|
// Iterate over each function within this complexity level
|
|
for (Function function : list) {
|
|
// Exclude thunk and external functions
|
|
if (function.isThunk() || function.isExternal()) {
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Set the current value for the current function, so we can keep the analyst
|
|
* updated based on the number of functions (as only the current and total
|
|
* number of levels in the graph is shown via the monitor message
|
|
*/
|
|
currentFunctionCount++;
|
|
/*
|
|
* Create a worker to schedule in the thread pool. The worker will connect with
|
|
* the LLM and use the response to modify the given function based on the
|
|
* predefined settings with regards to renaming.
|
|
*/
|
|
FunctionWorker worker = new FunctionWorker(decompiler, function, totalFunctionCount,
|
|
currentFunctionCount);
|
|
// Schedule the worker in the executor
|
|
executor.execute(worker);
|
|
}
|
|
|
|
// Shut the executor down, meaning no new workers can be added
|
|
executor.shutdown();
|
|
|
|
// Wait until all workers have completed their work
|
|
while (executor.isTerminated() == false) {
|
|
// If the user cancels the script, cancel the execution
|
|
if (monitor.isCancelled()) {
|
|
throw new CancelledException();
|
|
}
|
|
/*
|
|
* If the workers are running and the script isn't cancelled by the user, sleep
|
|
* for one second to avoid using CPU cycles and check again after the sleep
|
|
* finishes.
|
|
*/
|
|
Thread.sleep(1000);
|
|
}
|
|
/*
|
|
* Before moving on to the next level, we wait for any ongoing automatic
|
|
* analysis to complete
|
|
*/
|
|
waitUntilAutoAnalysisCompletes();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A function to remove empty list entries from the list of lists. The functions
|
|
* within each list within the given list are also iterated over, excluding
|
|
* external and thunk functions, as these will not be handled afterwards
|
|
*
|
|
* @param list the list which contains the levels, where each level is a list of
|
|
* functions
|
|
* @return a list with the levels, where each level contains 1 or more
|
|
* functions, and all included functions are non-thunk and non-external
|
|
*/
|
|
private List<List<Function>> cleanAndReverseList(List<List<Function>> list) {
|
|
// The output variable
|
|
List<List<Function>> output = new ArrayList<>();
|
|
|
|
// Iterate backwards over the list
|
|
for (int i = list.size(); i-- > 0;) {
|
|
// Get the current level
|
|
List<Function> level = list.get(i);
|
|
// Skip empty levels
|
|
if (level.isEmpty()) {
|
|
continue;
|
|
}
|
|
// Create a new list of functions
|
|
List<Function> functions = new ArrayList<>();
|
|
// Iterate over the functions within the level
|
|
for (Function f : level) {
|
|
// Exclude external and thunk functions
|
|
if (f.isExternal() || f.isThunk()) {
|
|
continue;
|
|
}
|
|
// Add non-thunk and non-external functions to the list
|
|
functions.add(f);
|
|
}
|
|
// Add the filtered level to the output variable
|
|
output.add(functions);
|
|
}
|
|
|
|
/*
|
|
* Return the list with the levels, in the original order with the applied
|
|
* filters
|
|
*/
|
|
return output;
|
|
}
|
|
|
|
/**
|
|
* Prints the given message via the
|
|
* {@link ghidra.app.script.GhidraScript#println(String)} method, depending on
|
|
* the running mode and boolean.
|
|
*
|
|
* @param message the message to print
|
|
* @param printHeadless true if the message should be printed when executing
|
|
* headless, false if not
|
|
*/
|
|
private void log(String message, boolean printHeadless) {
|
|
if (SystemUtilities.isInHeadlessMode()) {
|
|
if (printHeadless == false) {
|
|
return;
|
|
}
|
|
}
|
|
println(message);
|
|
}
|
|
|
|
/**
|
|
* This method only returns once the auto analysis manager is done analyzing. If
|
|
* the user cancels, a cancelled exception is thrown.
|
|
*
|
|
* @throws InterruptedException
|
|
* @throws CancelledException
|
|
*/
|
|
private void waitUntilAutoAnalysisCompletes() throws InterruptedException, CancelledException {
|
|
while (autoAnalysisManager.isAnalyzing()) {
|
|
if (monitor.isCancelled()) {
|
|
throw new CancelledException();
|
|
}
|
|
Thread.sleep(1000); // Sleep to avoid consuming extra CPU cycles in-between checks
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A thread safe method to increment the monitor
|
|
*/
|
|
private synchronized void incrementMonitorThreadSafe() {
|
|
// Does not check for cancellation
|
|
monitor.incrementProgress();
|
|
}
|
|
|
|
/**
|
|
* A thread safe method to verify if a method's name is unique.
|
|
*
|
|
* @param functionName The function name to verify
|
|
* @return true if the name is unique, false if not
|
|
*/
|
|
private synchronized boolean isUniqueFunctionName(String functionName) {
|
|
for (Function function : currentProgram.getFunctionManager().getFunctionsNoStubs(currentProgram.getMinAddress(),
|
|
true)) {
|
|
if (function.getName().equalsIgnoreCase(functionName)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns a unique function name. If the name is not unique, an underscore is
|
|
* appended
|
|
*
|
|
* @param functionName the name to check
|
|
* @return the unique name
|
|
*/
|
|
private synchronized String getUniqueFunctionName(String functionName) {
|
|
if (isUniqueFunctionName(functionName)) {
|
|
return functionName;
|
|
}
|
|
|
|
functionName += "_";
|
|
return getUniqueFunctionName(functionName);
|
|
}
|
|
|
|
/**
|
|
* The class where the threaded execution to handle a given function is handled
|
|
*/
|
|
private class FunctionWorker implements Runnable {
|
|
|
|
private DecompInterface decompiler;
|
|
private Function function;
|
|
int totalFunctionCount;
|
|
int currentFunctionCount;
|
|
|
|
public FunctionWorker(DecompInterface decompiler, Function function, int totalFunctionCount,
|
|
int currentFunctionCount) {
|
|
this.decompiler = decompiler;
|
|
this.function = function;
|
|
this.totalFunctionCount = totalFunctionCount;
|
|
this.currentFunctionCount = currentFunctionCount;
|
|
}
|
|
|
|
/**
|
|
* The run method of the thread, not to be confused with the script's main run
|
|
* method!
|
|
*/
|
|
@Override
|
|
public void run() {
|
|
// Decompile the given function
|
|
DecompileResults results = decompiler.decompileFunction(function, DECOMPILER_TIMEOUT, monitor);
|
|
// Get the decompiled function
|
|
DecompiledFunction dFunction = results.getDecompiledFunction();
|
|
// Get the pseudo-C representation of the decompiled function
|
|
String code = dFunction.getC();
|
|
// Get the plate comment, if present
|
|
String tempComment = getPlateComment(function.getEntryPoint());
|
|
// If the comment is non-null, it exists
|
|
if (tempComment != null) {
|
|
/*
|
|
* Remove the comment from the pseudo-C representation to avoid overloading the
|
|
* LLM's context window
|
|
*/
|
|
code = code.replace(tempComment, "");
|
|
}
|
|
|
|
// Get the function's variables
|
|
Variable[] variables = function.getAllVariables();
|
|
|
|
// Contact the LLM and store the JSON-based response
|
|
JsonResponse response = contactLLM(code);
|
|
// If the response is null, an error occurred
|
|
if (response == null) {
|
|
// Print an error if there is an error
|
|
printerr(
|
|
"The LLM's return value cannot be parsed as the format is invalid or the LLM did not respond at all");
|
|
// Return from the thread
|
|
return;
|
|
}
|
|
|
|
// Declare and initialize the comment
|
|
String comment = "";
|
|
|
|
// Store the old function name
|
|
String oldFunctionName = function.getName();
|
|
// Get the new function name from the LLM response
|
|
String newFunctionName = response.getFunctionName();
|
|
// If the new name is present and is not an empty or whitespace-only string
|
|
if (newFunctionName != null && newFunctionName.isBlank() == false) {
|
|
// If functions should be renamed
|
|
if (RENAME_FUNCTION) {
|
|
// Only rename functions with default names
|
|
if (function.getName().toLowerCase().startsWith("fun_")) {
|
|
try {
|
|
// Set the new name
|
|
function.setName(getUniqueFunctionName(newFunctionName), SourceType.IMPORTED);
|
|
// Log the rename to non-headless instances
|
|
log(oldFunctionName + " -> " + newFunctionName, false);
|
|
// Add the name change to the comment
|
|
comment += "Changed " + oldFunctionName + " into " + newFunctionName + "\n\n";
|
|
} catch (Exception ex) {
|
|
printerr("Function renaming failed:\n" + ex.toString());
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
* If the function should not be renamed, or if the function name does not start
|
|
* with fun_, the suggested name is still added to the comment
|
|
*/
|
|
if (RENAME_FUNCTION == false || function.getName().toLowerCase().startsWith("fun_") == false) {
|
|
comment += "AI suggested function name: " + newFunctionName + "\n\n";
|
|
}
|
|
}
|
|
|
|
// If one or more variables are present in the function and LLM response
|
|
if (variables.length > 0 && response.getVariableNames() != null) {
|
|
// Set the next part of the comment
|
|
comment += "Variables:\n";
|
|
// Iterate over all variables
|
|
for (Variable variable : variables) {
|
|
// Get the old name
|
|
String oldVariableName = variable.getName();
|
|
// Get the new name
|
|
String newVariableName = response.getVariableNames().get(oldVariableName);
|
|
// If the new name is non-null and is not an empty or whitespace-only string
|
|
if (newVariableName != null && newVariableName.isBlank() == false) {
|
|
// If variables should be renamed
|
|
if (RENAME_VARIABLES) {
|
|
try {
|
|
// Set the name
|
|
variable.setName(newVariableName, SourceType.IMPORTED);
|
|
} catch (Exception ex) {
|
|
printerr("Variable renaming failed:\n" + ex.toString());
|
|
}
|
|
}
|
|
// Add the renaming to the comment
|
|
comment += "\t\tAI variable name: \"" + oldVariableName + "\" -> \"" + newVariableName + "\"\n";
|
|
}
|
|
}
|
|
// Log the variables in Ghidra, when running non-headless
|
|
log("Variables for " + function.getName() + " renamed", false);
|
|
comment += "\n";
|
|
}
|
|
|
|
// Get the summary from the LLM
|
|
String summary = response.getSummary();
|
|
// If the summary is non-null and is not an empty or whitespace-only string
|
|
if (summary != null && summary.isBlank() == false) {
|
|
// Add the summary to the comment
|
|
comment += "AI function summary: " + summary;
|
|
}
|
|
|
|
// If the comment is not whitespace-only or empty
|
|
if (comment.isBlank() == false) {
|
|
// Set the comment to the function
|
|
setComment(function, comment, false);
|
|
// Log the summary addition for non-headless instances
|
|
log("Summary for " + function.getName() + " added", false);
|
|
}
|
|
|
|
/*
|
|
* This message is always logged (when running headless and non-headless) to
|
|
* give an indication of the progress
|
|
*/
|
|
log("Completed " + function.getName() + " (" + currentFunctionCount + "/" + totalFunctionCount + ")", true);
|
|
// Increment the monitor to update the progress in a thread safe manner
|
|
incrementMonitorThreadSafe();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send the HTTP POST request to LLM
|
|
*
|
|
* @param body the body of comma separated strings to check
|
|
* @return the JSON response from the server
|
|
* @throws IOException
|
|
*/
|
|
private String sendPostRequest(Map<String, String> headers, String body) throws IOException {
|
|
URL url = URI.create(API_URL).toURL();
|
|
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
|
connection.setRequestMethod("POST");
|
|
|
|
for (Map.Entry<String, String> entry : headers.entrySet()) {
|
|
connection.setRequestProperty(entry.getKey(), entry.getValue());
|
|
}
|
|
|
|
connection.setConnectTimeout(POST_REQUEST_TIMEOUT);
|
|
connection.setReadTimeout(POST_REQUEST_TIMEOUT);
|
|
|
|
connection.setDoOutput(true);
|
|
try (OutputStream outputStream = connection.getOutputStream()) {
|
|
byte[] rawBody = body.getBytes("utf-8");
|
|
outputStream.write(rawBody, 0, rawBody.length);
|
|
}
|
|
|
|
// TODO use response code to potentially throw an exception
|
|
int responseCode = connection.getResponseCode();
|
|
|
|
try (BufferedReader bufferedReader = new BufferedReader(
|
|
new InputStreamReader(connection.getInputStream(), "utf-8"))) {
|
|
StringBuilder response = new StringBuilder();
|
|
String line = null;
|
|
while ((line = bufferedReader.readLine()) != null) {
|
|
response.append(line.trim());
|
|
}
|
|
return response.toString();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prompt the LLM by providing the pseudo-C code as the argument
|
|
*
|
|
* @param code the function's pseudo-C code
|
|
* @return the LLM's JSON response
|
|
* @throws IOException
|
|
*/
|
|
public String promptLlmApi(String code) throws IOException {
|
|
Map<String, String> headers = new HashMap<>();
|
|
headers.put("Content-Type", "application/json");
|
|
headers.put("Accept", "application/json");
|
|
|
|
String prompt = "For the following decompiled code from Ghidra, suggest a new name for the function, summarise the function, and suggest new names for each of the variables in the function. State the old name of the variable and the new name. The response should be a JSON object with \"functionName\" as the key for the suggested function name, the key \"summary\" should contain the function's summary, and a nested JSON object named \"variableNames\" with the old variable names as keys, where the value of each key is the newly suggested name. The response should only the the requested JSON object, nothing else.\n\n"
|
|
+ code;
|
|
|
|
return sendPostRequest(headers, prompt);
|
|
}
|
|
|
|
/**
|
|
* Contact the LLM with the pseudo-C code and prompt
|
|
*
|
|
* @param code the function's pseudo-C code
|
|
* @return a Java object in which the LLM's JSON response is already parsed
|
|
*/
|
|
private JsonResponse contactLLM(String code) {
|
|
try {
|
|
String response = promptLlmApi(code);
|
|
|
|
if (response == null) {
|
|
// return null;
|
|
}
|
|
JsonResponse jsonResponse = gson.fromJson(response, JsonResponse.class);
|
|
return jsonResponse;
|
|
} catch (IOException ex) {
|
|
printerr(ex.toString());
|
|
return null;
|
|
} catch (JsonSyntaxException ex) {
|
|
printerr(ex.toString());
|
|
return null;
|
|
} catch (Exception ex) {
|
|
printerr(ex.toString());
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper function to set a comment at a given function, with the indication if
|
|
* this comment should be at the top or bottom of any already existing function
|
|
* comment at this function
|
|
*
|
|
* @param function the function to set a comment at
|
|
* @param comment the comment to set at the given function
|
|
* @param pushToTop true if the function's existing comment (if any) should be
|
|
* put below this comment, false if not
|
|
*/
|
|
private void setComment(Function function, String comment, boolean pushToTop) {
|
|
// Get the old comment, which is null if no comment is present
|
|
String oldComment = function.getComment();
|
|
// Check for the existence of the old comment
|
|
if (oldComment != null) {
|
|
// If it is present, trim the comment to avoid redundant whitespace
|
|
oldComment = oldComment.trim();
|
|
// If the string, post trimming, is not empty nor blank (which also checks if it
|
|
// is empty)
|
|
if (oldComment.isEmpty() == false && oldComment.isBlank() == false) {
|
|
// If the comment should be at the top, place it at the top
|
|
if (pushToTop) {
|
|
comment += "\n\n" + oldComment;
|
|
} else { // Else put it at the bottom
|
|
comment = oldComment += "\n\n" + comment;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the comment, which contains the prior comment at the correct placement
|
|
* with regards to the given boolean if it existed
|
|
*/
|
|
function.setComment(comment);
|
|
}
|
|
|
|
/**
|
|
* The class used to store the HTTP LLM API response in
|
|
*/
|
|
private class JsonResponse {
|
|
private String functionName;
|
|
private String summary;
|
|
private Map<String, String> variableNames;
|
|
|
|
@SuppressWarnings("unused")
|
|
public JsonResponse() {
|
|
|
|
}
|
|
|
|
/**
|
|
* Gets the AI generated function name
|
|
*
|
|
* @return the AI generated function name
|
|
*/
|
|
public String getFunctionName() {
|
|
return functionName;
|
|
}
|
|
|
|
/**
|
|
* Gets the AI generated summary
|
|
*
|
|
* @return the AI generated summary
|
|
*/
|
|
public String getSummary() {
|
|
return summary;
|
|
}
|
|
|
|
/**
|
|
* Gets the mapping of the variable names (as keys) and the AI generated
|
|
* variable names (as a value, one for each key)
|
|
*
|
|
* @return gets the AI generated variable names for the current variable names
|
|
*/
|
|
public Map<String, String> getVariableNames() {
|
|
return variableNames;
|
|
}
|
|
}
|
|
|
|
// From here onwards, the code is from the Ghidra source code
|
|
|
|
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;
|
|
}
|
|
}
|