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
This commit is contained in:
DrFrugal 2024-02-11 20:20:19 +01:00
parent 62bfbbb16d
commit 011bc48f76
7 changed files with 158 additions and 20 deletions

View File

@ -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<Function> getFunctions(GhidraScript gs) throws CancelledException{
List<String> 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<String> 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<Function> getFunctionsByRegex(GhidraScript gs, Pattern pattern) {
List<Function> functions = new ArrayList<>();
FunctionIterator iterator = gs.getCurrentProgram().getFunctionManager().getFunctions(true);
@ -121,6 +150,39 @@ public class BulkUtils {
return functions;
}
static List<Function> getFunctionsByTags(GhidraScript gs, boolean useAND, String... searchTags){
List<String> searchTagsList = Arrays.asList(searchTags);
List<Function> functions = new ArrayList<>();
FunctionIterator iterator = gs.getCurrentProgram().getFunctionManager().getFunctions(true);
boolean matches;
while (iterator.hasNext() && !gs.getMonitor().isCancelled()) {
Function function = iterator.next();
Set<FunctionTag> 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<Function> functions, String callingConvention) {
int cnt = 0;
Iterator<Function> iterator = functions.iterator();

View File

@ -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<Function> functions = BulkUtils.getFunctionsByRegex(this, pattern);
if(functions.size() == 0) {
List<Function> 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());
}
}

47
ImportCallConvBulk.java Normal file
View File

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

View File

@ -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<Function> matchingFunctions = BulkUtils.getFunctionsByRegex(this, pattern);
List<Function> 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

View File

@ -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<Function> matchingFunctions = BulkUtils.getFunctionsByRegex(this, pattern);
List<Function> 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");

37
SetFuncTagsBulk.java Normal file
View File

@ -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<Function> 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);
}
}
}
}

View File

@ -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<Function> matchingFunctions = BulkUtils.getFunctionsByRegex(this, pattern);
List<Function> matchingFunctions = BulkUtils.getFunctions(this);
int numOfTypeAlreadyCorrect = BulkUtils.removeFunctionsByDataType(matchingFunctions, selectedDataType);
int numOfMatches = matchingFunctions.size();