From 011bc48f76461e582bfc1659780028af80d58d96 Mon Sep 17 00:00:00 2001 From: DrFrugal Date: Sun, 11 Feb 2024 20:20:19 +0100 Subject: [PATCH] introduced method to get functions by tags instead of just by name regex method for setting tags by name regex new script for importing calling conventions by offset refactoring script names to be more consistent --- BulkUtils.java | 62 +++++++++++++++++++ ...tFunctionsBulk.java => ExportFuncBulk.java | 14 ++--- ImportCallConvBulk.java | 47 ++++++++++++++ ...onventionBulk.java => SetCallConvBulk.java | 10 ++- SetFuncParamByIndexBulk.java | 4 +- SetFuncTagsBulk.java | 37 +++++++++++ SetReturnTypeBulk.java | 4 +- 7 files changed, 158 insertions(+), 20 deletions(-) rename ExportFunctionsBulk.java => ExportFuncBulk.java (79%) create mode 100644 ImportCallConvBulk.java rename SetCallingConventionBulk.java => SetCallConvBulk.java (84%) create mode 100644 SetFuncTagsBulk.java diff --git a/BulkUtils.java b/BulkUtils.java index 2673195..f8e71ef 100644 --- a/BulkUtils.java +++ b/BulkUtils.java @@ -2,6 +2,7 @@ import java.awt.BorderLayout; import java.io.File; import java.util.ArrayList; +import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Set; @@ -27,6 +28,7 @@ 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.program.model.listing.FunctionTag; import ghidra.util.data.DataTypeParser.AllowedDataTypes; import ghidra.util.exception.CancelledException; @@ -109,6 +111,33 @@ public class BulkUtils { return pattern; } + static List getFunctions(GhidraScript gs) throws CancelledException{ + List modeChoices = Arrays.asList("Name Regex", "Tags"); + String modeSelection = gs.askChoice("Selection Mode", "How do you want to select functions?", modeChoices, modeChoices.get(0)); + switch(modeSelection) { + default: + case "Name Regex": + Pattern pattern = askForRegex(gs, "entire function name"); + return getFunctionsByRegex(gs, pattern); + case "Tags": + String[] tags = gs.askString("Tags", "select for (comma separated):").split(","); + String tagModeSelection; + switch(tags.length) { + case 0: + System.out.println("Abort: no tags provided"); + throw new CancelledException(); + case 1: + tagModeSelection = "AND"; + break; + default: + List tagModeChoices = Arrays.asList("AND", "OR"); + tagModeSelection = gs.askChoice("Tag Mode", "Behavior for multiple tags", tagModeChoices, tagModeChoices.get(0)); + break; + } + return getFunctionsByTags(gs, tagModeSelection == "AND", tags); + } + } + static List getFunctionsByRegex(GhidraScript gs, Pattern pattern) { List functions = new ArrayList<>(); FunctionIterator iterator = gs.getCurrentProgram().getFunctionManager().getFunctions(true); @@ -121,6 +150,39 @@ public class BulkUtils { return functions; } + static List getFunctionsByTags(GhidraScript gs, boolean useAND, String... searchTags){ + List searchTagsList = Arrays.asList(searchTags); + List functions = new ArrayList<>(); + FunctionIterator iterator = gs.getCurrentProgram().getFunctionManager().getFunctions(true); + boolean matches; + while (iterator.hasNext() && !gs.getMonitor().isCancelled()) { + Function function = iterator.next(); + Set functionTags = function.getTags(); + if(functionTags.size() == 0) continue; // functions without tags are irrelevant + if(useAND && functionTags.size() < searchTags.length) continue; // search mode is AND + function has fewer tags as we are searching for + matches = useAND ? true : false; + boolean tagContained; + for(FunctionTag functionTag : functionTags) + { + tagContained = searchTagsList.contains(functionTag.getName()); + if(useAND) + { + if(!tagContained) { + matches = false; + break; + } + } else if(tagContained) { + matches = true; + break; + } + } + if(matches){ + functions.add(function); + } + } + return functions; + } + static int removeFunctionsByCallingConvention(List functions, String callingConvention) { int cnt = 0; Iterator iterator = functions.iterator(); diff --git a/ExportFunctionsBulk.java b/ExportFuncBulk.java similarity index 79% rename from ExportFunctionsBulk.java rename to ExportFuncBulk.java index d537784..2a09a26 100644 --- a/ExportFunctionsBulk.java +++ b/ExportFuncBulk.java @@ -1,5 +1,5 @@ //TODO write a description for this script -//@author +//@author DrFrugal //@category Export //@keybinding //@menupath @@ -9,7 +9,6 @@ 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; @@ -17,14 +16,13 @@ import ghidra.app.decompiler.DecompileResults; import ghidra.app.script.GhidraScript; import ghidra.program.model.listing.Function; -public class ExportFunctionsBulk extends GhidraScript { +public class ExportFuncBulk extends GhidraScript { @Override public void run() throws Exception { - Pattern pattern = BulkUtils.askForRegex(this, "entire function name"); - List functions = BulkUtils.getFunctionsByRegex(this, pattern); - if(functions.size() == 0) { + List matchingFunctions = BulkUtils.getFunctions(this); + if(matchingFunctions.size() == 0) { println("Abort: no functions match the provided regex"); return; } @@ -45,14 +43,14 @@ public class ExportFunctionsBulk extends GhidraScript { PrintWriter decompWriter = new PrintWriter(outputFile); int decompileTimeout = 10; // seconds - functions.forEach(function -> { + matchingFunctions.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()); + println(matchingFunctions.size() + " matching function(s) exported to " + outputFile.getAbsolutePath()); } } diff --git a/ImportCallConvBulk.java b/ImportCallConvBulk.java new file mode 100644 index 0000000..8e494ee --- /dev/null +++ b/ImportCallConvBulk.java @@ -0,0 +1,47 @@ +//TODO write a description for this script +//@author DrFrugal +//@category Functions +//@keybinding +//@menupath +//@toolbar bomb + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; + +import ghidra.app.script.GhidraScript; +import ghidra.program.model.listing.Function; + +public class ImportCallConvBulk extends GhidraScript { + + public void run() throws Exception { + + String ln; + String[] shards; + Function func; + File f = askFile("Import Calling Conventions", "OK"); + BufferedReader br = new BufferedReader(new FileReader(f)); + while((ln = br.readLine()) != null) { + ln = ln.trim(); + if(ln == "") continue; // skip empty line + shards = ln.split(" "); // first shard is address, second shard is calling convention + if(shards.length != 2) continue; // malformed line + func = getFunctionAt(toAddr(shards[0])); + if(func == null) continue; // unable to find a function at this offset + switch(shards[1]) { + case "__cdecl": + case "__fastcall": + case "__stdcall": + case "__thiscall": + func.setCallingConvention(shards[1]); + break; + default: + func.setCallingConvention("unknown"); + func.addTag(shards[1]); + break; + } + } + br.close(); + } + +} diff --git a/SetCallingConventionBulk.java b/SetCallConvBulk.java similarity index 84% rename from SetCallingConventionBulk.java rename to SetCallConvBulk.java index 8edc8da..4ee2c12 100644 --- a/SetCallingConventionBulk.java +++ b/SetCallConvBulk.java @@ -1,25 +1,23 @@ //TODO write a description for this script -//@author -//@category _NEW_ +//@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.listing.Function; -public class SetCallingConventionBulk extends GhidraScript { +public class SetCallConvBulk extends GhidraScript { public void run() throws Exception { String callingConvention = BulkUtils.askForCallingConvention(this); - Pattern pattern = BulkUtils.askForRegex(this, "entire function name"); - List matchingFunctions = BulkUtils.getFunctionsByRegex(this, pattern); + List matchingFunctions = BulkUtils.getFunctions(this); 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 diff --git a/SetFuncParamByIndexBulk.java b/SetFuncParamByIndexBulk.java index 38909b7..65d4800 100644 --- a/SetFuncParamByIndexBulk.java +++ b/SetFuncParamByIndexBulk.java @@ -7,7 +7,6 @@ import java.util.List; -import java.util.regex.Pattern; import ghidra.app.script.GhidraScript; import ghidra.program.model.data.DataType; @@ -21,8 +20,7 @@ public class SetFuncParamByIndexBulk extends GhidraScript { public void run() throws Exception { - Pattern pattern = BulkUtils.askForRegex(this, "entire function name"); - List matchingFunctions = BulkUtils.getFunctionsByRegex(this, pattern); + List matchingFunctions = BulkUtils.getFunctions(this); boolean confirmed = BulkUtils.showFunctionConfirmationDialog(matchingFunctions, "Confirm " + matchingFunctions.size() + " matches", -1, "Please confirm these matches before we proceed."); if (!confirmed) { println("Abort: negative confirmation"); diff --git a/SetFuncTagsBulk.java b/SetFuncTagsBulk.java new file mode 100644 index 0000000..47c490d --- /dev/null +++ b/SetFuncTagsBulk.java @@ -0,0 +1,37 @@ +//TODO write a description for this script +//@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.listing.Function; + +public class SetFuncTagsBulk extends GhidraScript { + + public void run() throws Exception { + String tagsInput = askString("Tags", "Add (comma separated):").trim(); + if(tagsInput.length() == 0) { + println("Abort: no tags provided"); + return; + } + String[] tags = tagsInput.split(","); + Pattern pattern = BulkUtils.askForRegex(this, "entire function name"); + List matchingFunctions = BulkUtils.getFunctionsByRegex(this, pattern); + boolean confirmed = BulkUtils.showFunctionConfirmationDialog(matchingFunctions, "Confirm " + matchingFunctions.size() + " matches", -1, null); + if (!confirmed) { + println("Abort: negative confirmation"); + return; + } + for(Function function : matchingFunctions) { + for(String tag : tags) { + function.addTag(tag); + } + } + } + +} diff --git a/SetReturnTypeBulk.java b/SetReturnTypeBulk.java index c67ebf6..6c29220 100644 --- a/SetReturnTypeBulk.java +++ b/SetReturnTypeBulk.java @@ -8,7 +8,6 @@ import java.util.List; -import java.util.regex.Pattern; import ghidra.app.script.GhidraScript; import ghidra.program.model.data.DataType; @@ -25,8 +24,7 @@ public class SetReturnTypeBulk extends GhidraScript { println("Abort: no type selected"); return; } - Pattern pattern = BulkUtils.askForRegex(this, "entire function name"); - List matchingFunctions = BulkUtils.getFunctionsByRegex(this, pattern); + List matchingFunctions = BulkUtils.getFunctions(this); int numOfTypeAlreadyCorrect = BulkUtils.removeFunctionsByDataType(matchingFunctions, selectedDataType); int numOfMatches = matchingFunctions.size();