Created
April 13, 2026 15:40
-
-
Save nstarke/a8ef4edcc461e3c949d5884f2d666278 to your computer and use it in GitHub Desktop.
HarusHeadless - Haruspex Ghidra Script for Headless Mode
This file contains hidden or 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
| /* | |
| * HarusHeadless.java - Extract pseudocode from Ghidra's decompiler | |
| * Copyright (c) 2022-2026 Marco Ivaldi <raptor@0xdeadbeef.info> | |
| * | |
| * Headless-mode update: | |
| * - accepts output directory via scriptArgs[0] | |
| * - still falls back to askString() in GUI mode | |
| */ | |
| // This script extracts all pseudocode generated by the Ghidra decompiler | |
| // in a format that should be suitable to be imported into an IDE, such as | |
| // VS Code, or parsed by static analysis tools, such as Semgrep. | |
| // @author Marco Ivaldi <raptor@0xdeadbeef.info> | |
| // @category VulnDev | |
| // @keybinding H | |
| // @menupath Tools.Haruspex | |
| // @toolbar | |
| import java.util.List; | |
| import java.util.ArrayList; | |
| import java.io.File; | |
| import java.io.FileWriter; | |
| import java.io.PrintWriter; | |
| import ghidra.app.script.GhidraScript; | |
| import ghidra.program.model.symbol.*; | |
| import ghidra.program.model.listing.*; | |
| import ghidra.app.decompiler.DecompInterface; | |
| import ghidra.app.decompiler.DecompileOptions; | |
| import ghidra.app.decompiler.DecompileResults; | |
| public class HarusHeadless extends GhidraScript | |
| { | |
| List<Function> functions; | |
| DecompileOptions options; | |
| DecompInterface decomp; | |
| String outputPath = "/tmp/haruspex.out"; | |
| String baseOutputPath = outputPath; | |
| static final int TIMEOUT = 60; | |
| @Override | |
| public void run() throws Exception | |
| { | |
| printf("\nHaruspex.java - Extract pseudocode from Ghidra's decompiler\n"); | |
| printf("Copyright (c) 2022-2026 Marco Ivaldi <raptor@0xdeadbeef.info>\n\n"); | |
| resolveOutputPath(); | |
| outputPath = new File(baseOutputPath, currentProgram.getName()).getAbsolutePath(); | |
| File outDir = new File(outputPath); | |
| if (!outDir.exists() && !outDir.mkdirs()) { | |
| printf("Could not create output directory \"%s\", exiting.\n\n", outputPath); | |
| return; | |
| } | |
| if (!outDir.isDirectory()) { | |
| printf("Output path \"%s\" is not a directory, exiting.\n\n", outputPath); | |
| return; | |
| } | |
| functions = new ArrayList<>(); | |
| getAllFunctions(); | |
| decomp = new DecompInterface(); | |
| options = new DecompileOptions(); | |
| decomp.setOptions(options); | |
| decomp.toggleCCode(true); | |
| decomp.toggleSyntaxTree(true); | |
| decomp.setSimplificationStyle("decompile"); | |
| if (!decomp.openProgram(currentProgram)) { | |
| printf("Could not initialize the decompiler, exiting.\n\n"); | |
| return; | |
| } | |
| printf("Saving pseudocode to: %s\n", outputPath); | |
| printf("Extracting pseudocode from %d functions...\n\n", functions.size()); | |
| functions.forEach(f -> extractPseudoCode(f)); | |
| } | |
| private void resolveOutputPath() | |
| { | |
| String[] args = getScriptArgs(); | |
| if (args != null && args.length > 0 && args[0] != null && !args[0].trim().isEmpty()) { | |
| baseOutputPath = args[0].trim(); | |
| printf("Using base output directory from scriptArgs: \"%s\"\n", baseOutputPath); | |
| return; | |
| } | |
| try { | |
| baseOutputPath = askString("Output directory path", "Enter the base path of the output directory:"); | |
| } | |
| catch (Exception e) { | |
| printf("Output directory not supplied, using default base path \"%s\".\n", baseOutputPath); | |
| } | |
| } | |
| // collect all Function objects into a global ArrayList | |
| public void getAllFunctions() | |
| { | |
| SymbolTable st = currentProgram.getSymbolTable(); | |
| SymbolIterator si = st.getSymbolIterator(); | |
| while (si.hasNext()) { | |
| Symbol s = si.next(); | |
| if (s.getSymbolType() == SymbolType.FUNCTION && !s.isExternal()) { | |
| Function fun = getFunctionAt(s.getAddress()); | |
| if (fun != null && !fun.isThunk()) { | |
| functions.add(fun); | |
| } | |
| } | |
| } | |
| } | |
| // extract the pseudocode of a function | |
| public void extractPseudoCode(Function func) | |
| { | |
| DecompileResults res = decomp.decompileFunction(func, TIMEOUT, monitor); | |
| if (res.getDecompiledFunction() != null) { | |
| String fileName = func.getName() + "@" + func.getEntryPoint() + ".c"; | |
| saveToFile(outputPath, fileName, res.getDecompiledFunction().getC()); | |
| } | |
| else { | |
| printf("Can't decompile %s\n\n", func.getName()); | |
| } | |
| } | |
| // save results to file | |
| public void saveToFile(String path, String name, String output) | |
| { | |
| File outFile = new File(path, name); | |
| try (FileWriter fw = new FileWriter(outFile); | |
| PrintWriter pw = new PrintWriter(fw)) { | |
| pw.write(output); | |
| } | |
| catch (Exception e) { | |
| printf("Cannot write to output file \"%s\".\n\n", outFile.getAbsolutePath()); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment