Skip to content

Commit

Permalink
Big refactor incl Ghidra 11.1 support (#78)
Browse files Browse the repository at this point in the history
* Big Refactor (MVP, but not cleaned up yet)

* Use extra method for checking credentials instead of relying on health endpoint

* Use dedicated endpoint for checking validity of credentials

* Implement binary uploading

* Make health check more resilient

* Cleanup, minor fixes and support for Ghidra 11.1

* Use Ghidra 11.1 in CI

---------

Co-authored-by: Florian Magin <fmagin@users.noreply.github.com>
  • Loading branch information
fmagin and fmagin authored Jun 11, 2024
1 parent 6cadeee commit c8cbab0
Show file tree
Hide file tree
Showing 55 changed files with 2,187 additions and 1,674 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ jobs:
# Root directory for doing Ghidra work (building, etc.)
root: ["/tmp/ghidra"]
# Ghidra build version(s)
version: [10.4]
version: [11.1]
include:
- version: 10.4
release_url: "https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_10.4_build"
filename: "ghidra_10.4_PUBLIC_20230928.zip"
directory: "ghidra_10.4_PUBLIC"
- version: 11.1
release_url: "https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_11.1_build"
filename: "ghidra_11.1_PUBLIC_20240607.zip"
directory: "ghidra_11.1_PUBLIC"

steps:
- uses: actions/checkout@v3
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ jobs:
# Repository name ("reait-ghidra")
name: ["${{github.event.repository.name}}"]
# Ghidra build version(s)
version: [10.4]
version: [11.1]
include:
- version: 10.4
release_url: "https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_10.4_build"
filename: "ghidra_10.4_PUBLIC_20230928.zip"
directory: "ghidra_10.4_PUBLIC"
- version: 11.1
release_url: "https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_11.1_build"
filename: "ghidra_11.1_PUBLIC_20240607.zip"
directory: "ghidra_11.1_PUBLIC"

steps:
- uses: actions/checkout@v3
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ The RevEng.AI Toolkit allows you to interact with our API from within Ghidra. Th
## Key Features

* Upload the current binary for analysis
* Rename a function based on similar functions from our dataset
* Ask for a human readable function explanation
* Automatically rename all functions above a confidence threshold
* Show similar functions and their names for one selected function

## Installation

Expand Down
10 changes: 10 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,17 @@ dependencies {
// this extension is built.
implementation 'com.moandjiezana.toml:toml4j:0.7.2'
implementation 'org.json:json:20230618'
implementation "com.google.guava:guava:33.2.0-jre"
}

// Exclude additional files from the built extension
// Ex: buildExtension.exclude '.idea/**'


sourceSets {
main {
java {
srcDir 'ghidra_scripts'
}
}
}
90 changes: 90 additions & 0 deletions ghidra_scripts/RevEngAutoRenamePostScript.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import ai.reveng.toolkit.ghidra.core.services.api.GhidraRevengService;
import ai.reveng.toolkit.ghidra.core.services.api.types.AnalysisStatus;
import ai.reveng.toolkit.ghidra.core.services.api.types.ApiInfo;
import ai.reveng.toolkit.ghidra.core.services.api.types.BinaryID;
import ghidra.app.script.GhidraScript;
import ghidra.program.model.listing.CircularDependencyException;
import ghidra.program.model.symbol.Namespace;
import ghidra.program.model.symbol.SourceType;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.InvalidInputException;

public class RevEngAutoRenamePostScript extends GhidraScript {
@Override
protected void run() throws Exception {
// Services are not available in headless mode, so we have to instantiate it ourself
var ghidraRevengService = new GhidraRevengService(ApiInfo.fromConfig());
ghidraRevengService.upload(currentProgram);
var binID = ghidraRevengService.analyse(currentProgram);
// Wait for analysis to finish
waitForAnalysis(ghidraRevengService, binID);

var revengMatchNamespace = currentProgram.getSymbolTable().getOrCreateNameSpace(
currentProgram.getGlobalNamespace(),
"RevEng",
SourceType.ANALYSIS
);
// Fetch Function matches
ghidraRevengService.getSimilarFunctions(currentProgram, 1, 0.05).forEach(
(function, matches) -> {
var bestMatch = matches.get(0);
Namespace libraryNamespace = null;
try {
libraryNamespace = currentProgram.getSymbolTable().getOrCreateNameSpace(
revengMatchNamespace,
bestMatch.nearest_neighbor_binary_name(),
SourceType.ANALYSIS);
} catch (DuplicateNameException e) {
throw new RuntimeException(e);
} catch (InvalidInputException e) {
throw new RuntimeException(e);
}
try {
function.getSymbol().setNameAndNamespace(
bestMatch.nearest_neighbor_function_name(),
libraryNamespace,
SourceType.ANALYSIS
);
println("Renamed " + function.getName() + " to " + bestMatch.nearest_neighbor_function_name() + " from " + bestMatch.nearest_neighbor_binary_name() + " with confidence " + bestMatch.confidence());
} catch (DuplicateNameException e) {
throw new RuntimeException(e);
} catch (InvalidInputException e) {
throw new RuntimeException(e);
} catch (CircularDependencyException e) {
throw new RuntimeException(e);
}

}

);


}


private void waitForAnalysis(GhidraRevengService ghidraRevengService, BinaryID binID) throws InterruptedException {
var analysisComplete = false;
while (!analysisComplete) {
Thread.sleep(5000);
switch (ghidraRevengService.status(binID)) {
case Complete:
println("Analysis finished successfully");
analysisComplete = true;
break;
case Error:
println("Analysis failed");
analysisComplete = true;
break;
case Processing:
println("Analysis still running");
break;
case Queued:
println("Analysis queued");
break;
default:
println("Unknown status");
break;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@

import ai.reveng.toolkit.ghidra.ReaiPluginPackage;
import ai.reveng.toolkit.ghidra.FunctionExplanation.actions.AskForFunctionExplanationAction;
import ai.reveng.toolkit.ghidra.core.services.api.ApiService;
import ai.reveng.toolkit.ghidra.core.services.logging.ReaiLoggingService;
import ai.reveng.toolkit.ghidra.core.services.api.TypedApiInterface;import ai.reveng.toolkit.ghidra.core.services.logging.ReaiLoggingService;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.ProgramPlugin;
import ghidra.app.services.ProgramManager;
Expand All @@ -19,10 +18,10 @@
@PluginInfo(
status = PluginStatus.STABLE,
packageName = ReaiPluginPackage.NAME,
category = PluginCategoryNames.DECOMPILER,
category = PluginCategoryNames.COMMON,
shortDescription = "Provide Function Explanation using AI",
description = "Provides support for annotating functions in the decompiler view with human read comments on what the function does",
servicesRequired = { ApiService.class, ProgramManager.class, ReaiLoggingService.class }
servicesRequired = { TypedApiInterface.class, ProgramManager.class, ReaiLoggingService.class }
)
//@formatter:on
public class FunctionExplanationPlugin extends ProgramPlugin {
Expand All @@ -45,7 +44,5 @@ private void setupActions() {
@Override
public void init() {
super.init();

tool.getService(ApiService.class);
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package ai.reveng.toolkit.ghidra.FunctionExplanation.actions;

import ai.reveng.toolkit.ghidra.ReaiPluginPackage;
import ai.reveng.toolkit.ghidra.core.services.api.ApiResponse;
import ai.reveng.toolkit.ghidra.core.services.api.ApiService;
import ai.reveng.toolkit.ghidra.core.services.api.GhidraRevengService;
import ai.reveng.toolkit.ghidra.core.services.logging.ReaiLoggingService;
import docking.ActionContext;
import docking.action.DockingAction;
Expand All @@ -18,11 +17,13 @@
import ghidra.program.model.listing.Program;
import ghidra.util.Msg;

import javax.help.UnsupportedOperationException;

public class AskForFunctionExplanationAction extends DockingAction {

private PluginTool tool;
private Function fau;
private ApiService apiService;
private GhidraRevengService apiService;
private ReaiLoggingService loggingService;

public AskForFunctionExplanationAction(PluginTool tool) {
Expand Down Expand Up @@ -88,19 +89,19 @@ public void actionPerformed(ActionContext context) {

ClangTokenGroup decompiledFunction = results.getCCodeMarkup();

apiService = tool.getService(ApiService.class);
ApiResponse res = apiService.explain(decompiledFunction.toString());

if (res.getJsonObject().has("error")) {
loggingService.error("Error with function explaination: " + res.getJsonObject().get("error").toString());
Msg.showError(this, null, "", "Error getting function explaination: " + res.getJsonObject().get("error"));
return;
}

int transactionID = currentProgram.startTransaction("Set function pre-comment based on RevEng.ai description");
String fComment = String.format("RevEng.AI Autogenerated\n\n%s", res.getJsonObject().getString("explanation"));
fau.setComment(fComment);
currentProgram.endTransaction(transactionID, true);
loggingService.info(fComment);
apiService = tool.getService(GhidraRevengService.class);
// Object res = apiService.explain(decompiledFunction.toString());
throw new UnsupportedOperationException("FunctionExplaination not implemented yet");
// if (res.getJsonObject().has("error")) {
// loggingService.error("Error with function explaination: " + res.getJsonObject().get("error").toString());
// Msg.showError(this, null, "", "Error getting function explaination: " + res.getJsonObject().get("error"));
// return;
// }
//
// int transactionID = currentProgram.startTransaction("Set function pre-comment based on RevEng.ai description");
// String fComment = String.format("RevEng.AI Autogenerated\n\n%s", res.getJsonObject().getString("explanation"));
// fau.setComment(fComment);
// currentProgram.endTransaction(transactionID, true);
// loggingService.info(fComment);
}
}
2 changes: 2 additions & 0 deletions src/main/java/ai/reveng/toolkit/ghidra/ReaiPluginPackage.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public class ReaiPluginPackage extends PluginPackage {
public static final String OPTION_KEY_MODEL = PREFIX + "Model";
public static final String OPTION_KEY_BINID = PREFIX + "Binary ID";

public static final Integer INVALID_BINARY_ID = -1;

/**
* Create a Top Level Plugin Package that uses the RevEng.AI logo
*/
Expand Down
Loading

0 comments on commit c8cbab0

Please sign in to comment.