Created
January 27, 2024 06:16
-
-
Save moyix/b364e21ea0ffdcd5a5a4d2fb52f4afba to your computer and use it in GitHub Desktop.
Ghidra scripts to produce JSON files with decompilation / disassembly for each function in an binary
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.io.File; | |
import java.io.FileWriter; | |
import java.io.IOException; | |
import java.util.HashMap; | |
import org.apache.logging.log4j.LogManager; | |
import org.apache.logging.log4j.Logger; | |
import ghidra.app.script.GhidraScript; | |
import ghidra.app.decompiler.DecompInterface; | |
import ghidra.app.decompiler.DecompileResults; | |
import com.google.gson.*; | |
public class DecompileToJson extends GhidraScript { | |
private static Logger log; | |
public DecompileToJson() { | |
log = LogManager.getLogger(DecompileToJson.class); | |
} | |
public void export(String filename) { | |
Gson gson = new GsonBuilder().setPrettyPrinting().create(); | |
File outputFile = new File(filename); | |
HashMap<String, String> json_dict = new HashMap<String, String>(); | |
DecompInterface ifc = new DecompInterface(); | |
ifc.openProgram(currentProgram); | |
for (var func : currentProgram.getListing().getFunctions(Boolean.TRUE)) { | |
DecompileResults res = ifc.decompileFunction(func,0,monitor); | |
if (!res.decompileCompleted()) { | |
System.err.println(res.getErrorMessage()); | |
continue; | |
} | |
String code = res.getDecompiledFunction().getC(); | |
json_dict.put(func.getName(), code); | |
System.out.println(func.getName()); | |
} | |
// Convert the HashMap to JSON | |
String json = gson.toJson(json_dict); | |
// Write JSON to file | |
try (FileWriter writer = new FileWriter(outputFile)) { | |
writer.write(json); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
@Override | |
public void run() throws Exception { | |
String[] args = getScriptArgs(); | |
export(args[0]); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.io.File; | |
import java.io.FileWriter; | |
import java.io.IOException; | |
import java.util.List; | |
import java.util.HashMap; | |
import org.apache.logging.log4j.LogManager; | |
import org.apache.logging.log4j.Logger; | |
import ghidra.app.script.GhidraScript; | |
import ghidra.app.util.EolComments; | |
import ghidra.program.model.listing.CodeUnit; | |
import ghidra.program.model.listing.CodeUnitFormat; | |
import ghidra.program.model.listing.CodeUnitFormatOptions; | |
import ghidra.program.model.listing.CodeUnitIterator; | |
import ghidra.program.model.listing.Data; | |
import ghidra.program.model.listing.Instruction; | |
import ghidra.program.model.listing.Listing; | |
import ghidra.program.model.listing.Variable; | |
import ghidra.program.model.mem.MemoryAccessException; | |
import ghidra.program.model.symbol.Symbol; | |
import ghidra.app.util.template.TemplateSimplifier; | |
import ghidra.app.util.viewer.field.EolExtraCommentsOption; | |
import com.google.gson.*; | |
public class DisassembleToJson extends GhidraScript { | |
private static Logger log; | |
public DisassembleToJson() { | |
log = LogManager.getLogger(DisassembleToJson.class); | |
} | |
private String getBytes(CodeUnit cu) { | |
StringBuffer bytesbuf = new StringBuffer(); | |
try { | |
byte[] bytes; | |
if (cu instanceof Instruction instr) { | |
bytes = instr.getParsedBytes(); | |
} | |
else { | |
bytes = cu.getBytes(); | |
} | |
for (int i = 0; i < bytes.length; ++i) { | |
if (bytes[i] >= 0x00 && bytes[i] <= 0x0F) { | |
bytesbuf.append("0"); | |
} | |
bytesbuf.append(Integer.toHexString(bytes[i] & 0xff)); | |
} | |
} | |
catch (MemoryAccessException e) { | |
return ""; | |
} | |
return bytesbuf.toString(); | |
} | |
private String getOperands(CodeUnit cu, CodeUnitFormat cuFormat) { | |
StringBuffer buffy = new StringBuffer(); | |
if (cu instanceof Instruction) { | |
Instruction inst = (Instruction) cu; | |
int opCnt = inst.getNumOperands(); | |
String firstSeparator = ((Instruction) cu).getSeparator(0); | |
String[] opSeparators = new String[opCnt]; | |
String[] opReps = new String[opCnt]; | |
for (int i = 0; i < opCnt; ++i) { | |
opReps[i] = cuFormat.getOperandRepresentationString(cu, i); | |
opSeparators[i] = ((Instruction) cu).getSeparator(i + 1); | |
if (opSeparators[i] == null) { | |
opSeparators[i] = ""; | |
} | |
} | |
if (firstSeparator != null) { | |
buffy.append(firstSeparator); | |
} | |
for (int i = 0; i < opCnt; ++i) { | |
buffy.append(opReps[i]); | |
buffy.append(opSeparators[i]); | |
} | |
} | |
else if (cu instanceof Data) { | |
Data data = (Data) cu; | |
String opRep = cuFormat.getDataValueRepresentationString(data); | |
buffy.append(opRep); | |
} | |
return buffy.toString(); | |
} | |
private String getVariableSorageString(Variable var) { | |
String offsetStr; | |
if (var.isStackVariable()) { | |
int offset = var.getStackOffset(); | |
offsetStr = | |
(offset >= 0 ? " 0x" + Integer.toHexString(offset) : "-0x" + Integer.toHexString(-offset)); | |
} | |
else if (var.isRegisterVariable()) { | |
offsetStr = var.getRegister().getName(); | |
} | |
else { | |
offsetStr = var.getVariableStorage().toString(); | |
} | |
return offsetStr; | |
} | |
public void export(String filename) { | |
Gson gson = new GsonBuilder().setPrettyPrinting().create(); | |
File outputFile = new File(filename); | |
HashMap<String, String> json_dict = new HashMap<String, String>(); | |
// Formatter for disassembled code | |
TemplateSimplifier simplifier = new TemplateSimplifier(); | |
simplifier.setEnabled(false); | |
CodeUnitFormatOptions formatOptions = new CodeUnitFormatOptions( | |
CodeUnitFormatOptions.ShowBlockName.NEVER, | |
CodeUnitFormatOptions.ShowNamespace.NON_LOCAL, | |
null, | |
true, // doRegVariableMarkup | |
true, // doStackVariableMarkup | |
true, // includeInferredVariableMarkup | |
true, // alwaysShowPrimaryReference | |
true, // includeScalarReferenceAdjustment | |
true, // showLibraryInNamespace | |
true, // followReferencedPointers | |
simplifier | |
); | |
var cuf = new CodeUnitFormat(formatOptions); | |
// Annoying thing: for some reason catch blocks are not considered part of | |
// a function (listing.getFunctionContaining() returns null), so we'll just | |
// keep track of the last function we saw and assume that the next code unit | |
// belongs to the same function if it's not in a function. | |
ghidra.program.model.listing.Function lastFunc = null; | |
CodeUnitIterator cuIterator = currentProgram.getListing().getCodeUnits(true); | |
Listing listing = currentProgram.getListing(); | |
for (var cu : cuIterator) { | |
var currentAddress = cu.getMinAddress(); | |
var func = listing.getFunctionContaining(currentAddress); | |
if (func == null) { | |
if (cu instanceof Instruction && lastFunc != null) { | |
log.warn(String.format( | |
"Instruction at [%s] not in a function; assuming it's part of %s", | |
currentAddress, lastFunc | |
)); | |
func = lastFunc; | |
} | |
else { | |
// System.err.printf( | |
// "[%s] Note: skipping CU of type %s\n", | |
// currentAddress, cu.getClass().getName() | |
// ); | |
continue; | |
} | |
} | |
lastFunc = func; | |
boolean isFunctionEntryPoint = func.getEntryPoint().equals(currentAddress); | |
// Get the code for this function so far, or create a new one | |
String code = json_dict.getOrDefault(func.getName(), ""); | |
// If this is the first code unit in the function, add preamble | |
if (isFunctionEntryPoint) { | |
log.info(String.format( | |
"Processing: %s [%s,%s]", | |
func.getName(), | |
func.getBody().getMinAddress(), | |
func.getBody().getMaxAddress() | |
)); | |
// Add comment with function signature | |
code += String.format("; %s\n", func.getPrototypeString(true, true)); | |
// Add comment with parameters | |
code += "; Parameters:\n"; | |
for (var v : func.getParameters()) { | |
code += String.format("; %-14s %-14s %s\n", | |
v.getName(), v.getDataType().getDisplayName(), getVariableSorageString(v) | |
); | |
} | |
// Add comment with local variables on the stack | |
code += "; Stack variables:\n"; | |
for (var v : func.getLocalVariables()) { | |
code += String.format("; %-14s %-14s %s\n", | |
v.getName(), v.getDataType().getDisplayName(), getVariableSorageString(v) | |
); | |
} | |
} | |
String addrString = cu.getAddressString(true, false); | |
// Any pre-comment | |
String preComment = cu.getComment(CodeUnit.PRE_COMMENT); | |
if (preComment != null) { | |
code += String.format("%-16s %-16s ; %s\n", | |
"", | |
"", | |
preComment | |
); | |
} | |
// Primary symbol name (either function name or label) | |
Symbol primarySymbol = cu.getPrimarySymbol(); | |
if (primarySymbol != null) { | |
code += String.format("%-16s %-16s %s:\n", "", "", primarySymbol.getName()); | |
} | |
// Disassembly | |
code += String.format("%-16s %-16s %-11s %-40s", | |
addrString, | |
getBytes(cu), | |
cu.getMnemonicString(), | |
getOperands(cu, cuf), | |
cuf.getRepresentationString(cu, true) | |
); | |
// EOL comments | |
EolExtraCommentsOption eolOption = new EolExtraCommentsOption(); | |
EolComments eolComments = new EolComments(cu, true, 6 /* arbitrary */, eolOption); | |
List<String> comments = eolComments.getComments(); | |
if (comments.size() > 0) { | |
code += " ; " + String.join(", ", comments); | |
} | |
// Trim trailing whitespace | |
code = code.stripTrailing(); | |
code += "\n"; | |
// Any post-comment | |
String postComment = cu.getComment(CodeUnit.POST_COMMENT); | |
if (postComment != null) { | |
code += String.format("%-16s %-16s ; %s\n", | |
"", | |
"", | |
postComment | |
); | |
} | |
// Update the code for this function | |
json_dict.put(func.getName(), code); | |
} | |
// Convert the HashMap to JSON | |
String json = gson.toJson(json_dict); | |
// Write JSON to file | |
try (FileWriter writer = new FileWriter(outputFile)) { | |
writer.write(json); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
@Override | |
public void run() throws Exception { | |
String[] args = getScriptArgs(); | |
export(args[0]); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment