initial commit
This commit is contained in:
parent
35a1b96c9b
commit
62bfbbb16d
|
@ -24,3 +24,5 @@
|
|||
hs_err_pid*
|
||||
replay_pid*
|
||||
|
||||
.project
|
||||
.classpath
|
|
@ -0,0 +1,181 @@
|
|||
|
||||
import java.awt.BorderLayout;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.JTextArea;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
import javax.swing.table.TableColumn;
|
||||
import javax.swing.table.TableRowSorter;
|
||||
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.app.util.datatype.DataTypeSelectionDialog;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.data.DataTypeManager;
|
||||
import ghidra.program.model.lang.CompilerSpec;
|
||||
import ghidra.program.model.lang.PrototypeModel;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.program.model.listing.FunctionIterator;
|
||||
import ghidra.util.data.DataTypeParser.AllowedDataTypes;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
public class BulkUtils {
|
||||
|
||||
private static final Set<String> RESERVED_C_KEYWORDS = Set.of(
|
||||
"auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else",
|
||||
"enum", "extern", "float", "for", "goto", "if", "inline", "int", "long", "register",
|
||||
"restrict", "return", "short", "signed", "sizeof", "static", "struct", "switch",
|
||||
"typedef", "union", "unsigned", "void", "volatile", "while", "_Alignas", "_Alignof",
|
||||
"_Atomic", "_Bool", "_Complex", "_Generic", "_Imaginary", "_Noreturn", "_Static_assert",
|
||||
"_Thread_local"
|
||||
);
|
||||
private static final Pattern C_PARAM_REGEX = Pattern.compile("[a-zA-Z_][a-zA-Z\\\\d_]*");
|
||||
|
||||
static File askForOutputFile(String suggestedFileName) {
|
||||
JFileChooser fileChooser = new JFileChooser();
|
||||
if(suggestedFileName != null) {
|
||||
fileChooser.setSelectedFile(new File(suggestedFileName));
|
||||
}
|
||||
int fileChooserResult = fileChooser.showSaveDialog(null);
|
||||
if(fileChooserResult == JFileChooser.APPROVE_OPTION) {
|
||||
File outputFile = fileChooser.getSelectedFile();
|
||||
if(outputFile.exists()) {
|
||||
String question = "This file already exists, do you want to overwrite it?";
|
||||
int confirmOverwriteResult = JOptionPane.showConfirmDialog(null, question, null, JOptionPane.YES_NO_OPTION);
|
||||
if(confirmOverwriteResult != JOptionPane.YES_OPTION) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return outputFile;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static boolean isValidCParameterName(String parameterName) {
|
||||
if(parameterName == null || parameterName.isEmpty()) return false;
|
||||
if(RESERVED_C_KEYWORDS.contains(parameterName)) return false;
|
||||
return C_PARAM_REGEX.matcher(parameterName).matches();
|
||||
}
|
||||
|
||||
static DataType askForDataType(GhidraScript gs, String title) {
|
||||
DataTypeManager dataTypeManager = gs.getCurrentProgram().getDataTypeManager();
|
||||
PluginTool tool = gs.getState().getTool();
|
||||
DataTypeSelectionDialog typeDialog = new DataTypeSelectionDialog(tool, dataTypeManager, -1, AllowedDataTypes.FIXED_LENGTH);
|
||||
if(title != null) {
|
||||
typeDialog.setTitle(title);
|
||||
}
|
||||
tool.showDialog(typeDialog);
|
||||
return typeDialog.getUserChosenDataType();
|
||||
}
|
||||
|
||||
static void printDoneMessage(GhidraScript gs, int success, int fail) {
|
||||
gs.println("Done: successful: " + success + " | failed: " + fail);
|
||||
}
|
||||
|
||||
static String askForCallingConvention(GhidraScript gs) throws CancelledException {
|
||||
CompilerSpec compilerSpec = gs.getCurrentProgram().getCompilerSpec();
|
||||
PrototypeModel[] callingConventions = compilerSpec.getCallingConventions();
|
||||
List<String> choices = new ArrayList<>();
|
||||
choices.add(Function.DEFAULT_CALLING_CONVENTION_STRING);
|
||||
choices.add(Function.UNKNOWN_CALLING_CONVENTION_STRING);
|
||||
for(PrototypeModel callingConvention : callingConventions) {
|
||||
choices.add(callingConvention.getName());
|
||||
}
|
||||
return gs.askChoice(null, "Calling convention:", choices, choices.get(0));
|
||||
}
|
||||
|
||||
static Integer askForInteger(GhidraScript gs, String whatFor) throws CancelledException {
|
||||
String input = gs.askString(null, whatFor);
|
||||
try {
|
||||
return Integer.parseInt(input, 10);
|
||||
} catch(NumberFormatException nfe) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
static Pattern askForRegex(GhidraScript gs, String whatToMatch) throws CancelledException {
|
||||
String regex = gs.askString("Enter Regex", "Pattern to match " + whatToMatch + ":");
|
||||
Pattern pattern = Pattern.compile(regex);
|
||||
return pattern;
|
||||
}
|
||||
|
||||
static List<Function> getFunctionsByRegex(GhidraScript gs, Pattern pattern) {
|
||||
List<Function> functions = new ArrayList<>();
|
||||
FunctionIterator iterator = gs.getCurrentProgram().getFunctionManager().getFunctions(true);
|
||||
while (iterator.hasNext() && !gs.getMonitor().isCancelled()) {
|
||||
Function function = iterator.next();
|
||||
if (pattern.matcher(function.getName()).matches()) {
|
||||
functions.add(function);
|
||||
}
|
||||
}
|
||||
return functions;
|
||||
}
|
||||
|
||||
static int removeFunctionsByCallingConvention(List<Function> functions, String callingConvention) {
|
||||
int cnt = 0;
|
||||
Iterator<Function> iterator = functions.iterator();
|
||||
while(iterator.hasNext()) {
|
||||
Function function = iterator.next();
|
||||
if(function.getCallingConventionName().equals(callingConvention)) {
|
||||
iterator.remove();
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
return cnt;
|
||||
}
|
||||
|
||||
static int removeFunctionsByDataType(List<Function> functions, DataType dataType) {
|
||||
int cnt = 0;
|
||||
Iterator<Function> iterator = functions.iterator();
|
||||
while(iterator.hasNext()) {
|
||||
Function function = iterator.next();
|
||||
if(function.getReturnType().equals(dataType)) {
|
||||
iterator.remove();
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
return cnt;
|
||||
}
|
||||
|
||||
static boolean showFunctionConfirmationDialog(List<Function> functions, String title, int skipped, String skippedString) {
|
||||
DefaultTableModel tableModel = new DefaultTableModel(new String[] {"Name", "Entry Point"}, 0);
|
||||
functions.forEach(function -> {
|
||||
tableModel.addRow(new String[] {function.getName(), function.getEntryPoint().toString().toUpperCase()});
|
||||
});
|
||||
TableRowSorter<DefaultTableModel> sorter = new TableRowSorter<>(tableModel);
|
||||
sorter.setSortsOnUpdates(true);
|
||||
sorter.toggleSortOrder(0); // Sort by first column (Name)
|
||||
JTable table = new JTable(tableModel);
|
||||
table.setRowSorter(sorter);
|
||||
TableColumn entryPointColumn = table.getColumnModel().getColumn(1);
|
||||
int fixedWidthEntryPoint = 130;
|
||||
entryPointColumn.setMinWidth(fixedWidthEntryPoint);
|
||||
entryPointColumn.setMaxWidth(fixedWidthEntryPoint);
|
||||
entryPointColumn.setPreferredWidth(fixedWidthEntryPoint);
|
||||
JScrollPane scrollPane = new JScrollPane(table);
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
if(skippedString != null) {
|
||||
JTextArea skippedText = new JTextArea(String.format(skippedString, skipped));
|
||||
skippedText.setEditable(false);
|
||||
panel.add(skippedText, BorderLayout.NORTH);
|
||||
}
|
||||
panel.add(scrollPane, BorderLayout.CENTER);
|
||||
Object[] options = { "OK", "Cancel" };
|
||||
JOptionPane optionPane = new JOptionPane(panel, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION, null, options, options[1]); // select cancel initially
|
||||
JDialog confirmDialog = optionPane.createDialog(null, title);
|
||||
confirmDialog.setResizable(true);
|
||||
confirmDialog.setVisible(true);
|
||||
return optionPane.getValue() == options[0];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
//TODO write a description for this script
|
||||
//@author
|
||||
//@category Export
|
||||
//@keybinding
|
||||
//@menupath
|
||||
//@toolbar bomb
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import ghidra.app.decompiler.DecompInterface;
|
||||
import ghidra.app.decompiler.DecompileOptions;
|
||||
import ghidra.app.decompiler.DecompileResults;
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.program.model.listing.Function;
|
||||
|
||||
public class ExportFunctionsBulk extends GhidraScript {
|
||||
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
|
||||
Pattern pattern = BulkUtils.askForRegex(this, "entire function name");
|
||||
List<Function> functions = BulkUtils.getFunctionsByRegex(this, pattern);
|
||||
if(functions.size() == 0) {
|
||||
println("Abort: no functions match the provided regex");
|
||||
return;
|
||||
}
|
||||
|
||||
String programName = currentProgram.getName();
|
||||
DecompInterface decompiler = new DecompInterface();
|
||||
DecompileOptions options = new DecompileOptions();
|
||||
options.setMaxWidth(1024); // easier parsing if everything is in one line
|
||||
decompiler.setOptions(options);
|
||||
decompiler.openProgram(currentProgram);
|
||||
|
||||
String fileName = programName + ".c";
|
||||
File outputFile = BulkUtils.askForOutputFile(fileName);
|
||||
if(outputFile == null){
|
||||
println("Abort: export dialog has been cancelled");
|
||||
return;
|
||||
}
|
||||
|
||||
PrintWriter decompWriter = new PrintWriter(outputFile);
|
||||
int decompileTimeout = 10; // seconds
|
||||
functions.forEach(function -> {
|
||||
decompWriter.write("// ADDRESS - 0x" + function.getEntryPoint().toString().toUpperCase() + "\n");
|
||||
DecompileResults decompileResults = decompiler.decompileFunction(function, decompileTimeout, null);
|
||||
String decompiledCode = decompileResults.getDecompiledFunction().getC();
|
||||
decompWriter.println(decompiledCode.trim() + "\n\n");
|
||||
});
|
||||
decompWriter.close();
|
||||
println(functions.size() + " matching function(s) exported to " + outputFile.getAbsolutePath());
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
//TODO write a description for this script
|
||||
//@author
|
||||
//@category _NEW_
|
||||
//@keybinding
|
||||
//@menupath
|
||||
//@toolbar bomb
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.program.model.listing.Function;
|
||||
|
||||
public class SetCallingConventionBulk extends GhidraScript {
|
||||
|
||||
public void run() throws Exception {
|
||||
|
||||
String callingConvention = BulkUtils.askForCallingConvention(this);
|
||||
|
||||
Pattern pattern = BulkUtils.askForRegex(this, "entire function name");
|
||||
List<Function> matchingFunctions = BulkUtils.getFunctionsByRegex(this, pattern);
|
||||
int skipped = BulkUtils.removeFunctionsByCallingConvention(matchingFunctions, callingConvention);
|
||||
int numOfMatches = matchingFunctions.size();
|
||||
// only abort if there are no matches AND no matches have been skipped, so the users understands why there are no matches
|
||||
if(numOfMatches == 0 && skipped == 0){
|
||||
println("Abort: no matches found");
|
||||
return;
|
||||
}
|
||||
|
||||
String title = "Set calling convention of " + matchingFunctions.size() + " function(s) to " + callingConvention;
|
||||
boolean confirmed = BulkUtils.showFunctionConfirmationDialog(matchingFunctions, title, skipped, "Number of matches skipped (calling convention already matches): %d");
|
||||
if (!confirmed) {
|
||||
println("Abort: negative confirmation");
|
||||
return;
|
||||
}
|
||||
|
||||
int success = 0;
|
||||
int fail = 0;
|
||||
for (Function func : matchingFunctions) {
|
||||
try {
|
||||
func.setCallingConvention(callingConvention);
|
||||
success++;
|
||||
} catch (Exception e) {
|
||||
println("Failed to update function " + func.getName() + " at " + func.getEntryPoint() + " - " + e.getMessage());
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
BulkUtils.printDoneMessage(this, success, fail);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
//Sets function parameters matching a provided regex at a provided index.
|
||||
//@author DrFrugal
|
||||
//@category Functions
|
||||
//@keybinding
|
||||
//@menupath
|
||||
//@toolbar bomb
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.program.model.listing.Parameter;
|
||||
import ghidra.program.model.symbol.SourceType;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
import ghidra.util.exception.InvalidInputException;
|
||||
|
||||
public class SetFuncParamByIndexBulk extends GhidraScript {
|
||||
|
||||
public void run() throws Exception {
|
||||
|
||||
Pattern pattern = BulkUtils.askForRegex(this, "entire function name");
|
||||
List<Function> matchingFunctions = BulkUtils.getFunctionsByRegex(this, pattern);
|
||||
boolean confirmed = BulkUtils.showFunctionConfirmationDialog(matchingFunctions, "Confirm " + matchingFunctions.size() + " matches", -1, "Please confirm these matches before we proceed.");
|
||||
if (!confirmed) {
|
||||
println("Abort: negative confirmation");
|
||||
return;
|
||||
}
|
||||
|
||||
Integer paramIndex = BulkUtils.askForInteger(this, "Parameter index:");
|
||||
if(paramIndex == null) {
|
||||
println("Abort: index couldn't be parsed");
|
||||
return;
|
||||
}
|
||||
|
||||
DataType paramType = BulkUtils.askForDataType(this, "Parameter type");
|
||||
if (paramType == null) {
|
||||
println("Abort: no type selected");
|
||||
return;
|
||||
}
|
||||
|
||||
String paramName = askString(null, "Parameter name:");
|
||||
if(!BulkUtils.isValidCParameterName(paramName)) {
|
||||
println("Abort: invalid C parameter name");
|
||||
return;
|
||||
}
|
||||
|
||||
int success = 0;
|
||||
int fail = 0;
|
||||
for(Function function : matchingFunctions) {
|
||||
Parameter param = function.getParameter(paramIndex);
|
||||
if(param == null) {
|
||||
println("Unable to retrieve parameter in " + function.getName());
|
||||
fail++;
|
||||
continue;
|
||||
}
|
||||
if(param.isAutoParameter()) {
|
||||
println("Skipping auto parameter in " + function.getName());
|
||||
fail++;
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
param.setName(paramName, SourceType.USER_DEFINED);
|
||||
success++;
|
||||
} catch (DuplicateNameException | InvalidInputException e) {
|
||||
printerr("Failed setting parameter name in " + function.getName());
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
BulkUtils.printDoneMessage(this, success, fail);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
//Select a fixed-size data type, and then provide regex to select functions whose return type should be set to it.
|
||||
//Results of that regex will be shown in a dialog for confirmation.
|
||||
//@author DrFrugal
|
||||
//@category Functions
|
||||
//@keybinding
|
||||
//@menupath
|
||||
//@toolbar bomb
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.program.model.symbol.SourceType;
|
||||
|
||||
public class SetReturnTypeBulk extends GhidraScript {
|
||||
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
|
||||
DataType selectedDataType = BulkUtils.askForDataType(this, "Return type");
|
||||
if (selectedDataType == null) {
|
||||
println("Abort: no type selected");
|
||||
return;
|
||||
}
|
||||
Pattern pattern = BulkUtils.askForRegex(this, "entire function name");
|
||||
List<Function> matchingFunctions = BulkUtils.getFunctionsByRegex(this, pattern);
|
||||
int numOfTypeAlreadyCorrect = BulkUtils.removeFunctionsByDataType(matchingFunctions, selectedDataType);
|
||||
|
||||
int numOfMatches = matchingFunctions.size();
|
||||
// only abort if there are no matches AND no matches have been skipped, so the users understands why there are no matches
|
||||
if(numOfMatches == 0 && numOfTypeAlreadyCorrect == 0){
|
||||
println("Abort: no matches found");
|
||||
return;
|
||||
}
|
||||
String title = "Set return type of " + matchingFunctions.size() + " function(s) to " + selectedDataType.getSourceArchive().getName() + selectedDataType.getPathName();
|
||||
boolean confirmed = BulkUtils.showFunctionConfirmationDialog(matchingFunctions, title, numOfTypeAlreadyCorrect, "Number of matches skipped (return type already matches): %d");
|
||||
if (!confirmed) {
|
||||
println("Abort: negative confirmation");
|
||||
return;
|
||||
}
|
||||
|
||||
int success = 0;
|
||||
int fail = 0;
|
||||
for (Function func : matchingFunctions) {
|
||||
try {
|
||||
func.setReturnType(selectedDataType, SourceType.USER_DEFINED);
|
||||
success++;
|
||||
} catch (Exception e) {
|
||||
println("Failed to update function " + func.getName() + " at " + func.getEntryPoint() + " - " + e.getMessage());
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
BulkUtils.printDoneMessage(this, success, fail);
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue