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();