Skip to content

Commit 4be8c30

Browse files
committed
feat(PRO-1733): allow attaching to matching analyses by hash only
Enforce boundaries checks at function level
1 parent cb77ba8 commit 4be8c30

6 files changed

Lines changed: 18 additions & 77 deletions

File tree

src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/autounstrip/AutoUnstripDialog.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import ai.reveng.toolkit.ghidra.core.services.api.GhidraRevengService;
55
import ai.reveng.toolkit.ghidra.core.services.api.types.AnalysisID;
66
import ai.reveng.toolkit.ghidra.core.services.api.types.AutoUnstripResponse;
7+
import ai.reveng.toolkit.ghidra.core.services.api.types.FunctionID;
78
import ai.reveng.toolkit.ghidra.core.types.ProgramWithBinaryID;
89
import ai.reveng.toolkit.ghidra.plugins.ReaiPluginPackage;
910
import ghidra.framework.plugintool.PluginTool;
@@ -107,6 +108,9 @@ private void importFunctionNames(AutoUnstripResponse autoUnstripResponse) {
107108
// Retrieve the mangled names map once outside the transaction
108109
var mangledNameMapOpt = revengService.getFunctionMangledNamesMap(program);
109110

111+
// Retrieve the function ID map once outside the transaction
112+
var functionMap = revengService.getFunctionMap(program);
113+
110114
program.withTransaction("Apply Auto-Unstrip Function Names", () -> {
111115
try {
112116
var revengMatchNamespace = program.getSymbolTable().getOrCreateNameSpace(
@@ -121,6 +125,7 @@ private void importFunctionNames(AutoUnstripResponse autoUnstripResponse) {
121125

122126
var revEngMangledName = match.suggested_name();
123127
var revEngDemangledName = match.suggested_demangled_name();
128+
var functionID = functionMap.get(new FunctionID(match.function_id().value()));
124129

125130
if (
126131
func != null &&
@@ -132,6 +137,8 @@ private void importFunctionNames(AutoUnstripResponse autoUnstripResponse) {
132137
// Only accept valid names (no spaces)
133138
!revEngMangledName.contains(" ") &&
134139
!revEngDemangledName.contains(" ")
140+
// Only rename if the function ID is known (boundaries matched)
141+
&& functionID != null
135142
) {
136143
try {
137144
// Capture original name before renaming

src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/functionmatching/AbstractFunctionMatchingDialog.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ protected void processFunctionMatchingResults(FunctionMatchingBatchResponse resp
135135
Function localFunction = functionMap.get(new FunctionID(matchResult.getFunctionId()));
136136

137137
if (localFunction == null) {
138-
// If we can't find the local function, skip this match
138+
// If we can't find the local function, skip this match (boundaries do not match the remote ones)
139139
return;
140140
}
141141

src/main/java/ai/reveng/toolkit/ghidra/binarysimilarity/ui/recentanalyses/RecentAnalysesTableModel.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,6 @@ protected void doLoad(Accumulator<LegacyAnalysisResult> accumulator, TaskMonitor
4848
return;
4949
}
5050

51-
// Filter out analyses where the function boundaries hash does not match our program
52-
var functionBoundariesHash = functionBoundariesService.getFunctionBoundariesHash();
53-
if (!result.function_boundaries_hash().equals(functionBoundariesHash)) {
54-
loggingService.info(
55-
"[RevEng] Skipping analysis for " + result.binary_id() + " as function boundaries hash does" +
56-
" not match. Expected " + functionBoundariesHash + " but got " +
57-
result.function_boundaries_hash());
58-
return;
59-
}
60-
6151
accumulator.add(result);
6252
}
6353
);

src/main/java/ai/reveng/toolkit/ghidra/core/services/api/GhidraRevengService.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ private void loadFunctionInfo(Program program, BinaryID binID) throws ApiExcepti
188188
StringPropertyMap finalMangledNameMap = mangledNameMap;
189189

190190
AtomicInteger ghidraRenamedFunctions = new AtomicInteger();
191+
AtomicInteger ghidraBoundariesMatchedFunction = new AtomicInteger();
191192
functionInfo.forEach(
192193
info -> {
193194
var oFunc = getFunctionFor(info, program);
@@ -215,9 +216,11 @@ private void loadFunctionInfo(Program program, BinaryID binID) throws ApiExcepti
215216
}
216217

217218
var funcSize = func.getBody().getNumAddresses();
219+
218220
// For unclear reasons the func size is off by one
219221
if (funcSize - 1 != info.functionSize() && funcSize != info.functionSize()){
220222
Msg.warn(this, "Function size mismatch for function %s: %d vs %d".formatted(ghidraMangledName, funcSize, info.functionSize()));
223+
return;
221224
}
222225

223226
// Source types:
@@ -238,6 +241,8 @@ private void loadFunctionInfo(Program program, BinaryID binID) throws ApiExcepti
238241
}
239242
finalFunctionIDMap.add(func.getEntryPoint(), info.functionID().value());
240243
finalMangledNameMap.add(func.getEntryPoint(), revEngMangledName);
244+
245+
ghidraBoundariesMatchedFunction.getAndIncrement();
241246
}
242247
);
243248

@@ -256,14 +261,14 @@ private void loadFunctionInfo(Program program, BinaryID binID) throws ApiExcepti
256261
program.endTransaction(transactionID, true);
257262

258263
// Print summary
259-
Msg.debug(
260-
this,
261-
"Loaded %d functions from RevEng.AI, renamed %d, Ghidra has %d functions".formatted(
264+
Msg.showInfo(this, null, ReaiPluginPackage.WINDOW_PREFIX + "Function loading summary",
265+
("Found %d functions from RevEng.AI. Renamed %d. Your local Ghidra instance has %d/%d matching function " +
266+
"boundaries. For better results, please start a new analysis from this plugin.").formatted(
262267
functionInfo.size(),
263268
ghidraRenamedFunctions.get(),
269+
ghidraBoundariesMatchedFunction.get(),
264270
ghidraFunctionCount.get()
265-
)
266-
);
271+
));
267272
}
268273

269274
/**

src/main/java/ai/reveng/toolkit/ghidra/core/services/function/export/ExportFunctionBoundariesService.java

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,4 @@ public interface ExportFunctionBoundariesService {
2929
* @return
3030
*/
3131
public JSONArray getFunctionsArray();
32-
33-
/**
34-
* Return a hash of the function boundaries for change detection
35-
* Note that this algorithm must match that used on the API server side!
36-
* @return
37-
*/
38-
public String getFunctionBoundariesHash();
3932
}

src/main/java/ai/reveng/toolkit/ghidra/core/services/function/export/ExportFunctionBoundariesServiceImpl.java

Lines changed: 0 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -80,58 +80,4 @@ public JSONArray getFunctionsArray() {
8080
}
8181
return fArray;
8282
}
83-
84-
@Override
85-
public String getFunctionBoundariesHash() {
86-
if (!isReady)
87-
init();
88-
89-
// Collect all function boundaries into a list
90-
List<JSONObject> boundaries = new ArrayList<>();
91-
for (Function f : fm.getFunctions(true)) {
92-
boundaries.add(getFunctionAt(f.getEntryPoint()));
93-
}
94-
95-
// Sort the boundaries by start address (convert hex string to long for proper sorting)
96-
boundaries.sort(Comparator.comparingLong(b -> Long.parseUnsignedLong(
97-
b.getString("start_addr").substring(2), 16)));
98-
99-
// Create a formatted string representation of the boundaries
100-
StringBuilder boundariesStr = new StringBuilder();
101-
for (int i = 0; i < boundaries.size(); i++) {
102-
JSONObject b = boundaries.get(i);
103-
if (i > 0) {
104-
boundariesStr.append(",");
105-
}
106-
107-
// Convert hex addresses to integer representation
108-
String startAddrHex = b.getString("start_addr");
109-
String endAddrHex = b.getString("end_addr");
110-
long startAddrInt = Long.parseUnsignedLong(startAddrHex.substring(2), 16);
111-
long endAddrInt = Long.parseUnsignedLong(endAddrHex.substring(2), 16);
112-
113-
boundariesStr.append(startAddrInt)
114-
.append("-")
115-
.append(endAddrInt);
116-
}
117-
118-
// Generate SHA-256 hash of the boundaries string
119-
try {
120-
MessageDigest digest = MessageDigest.getInstance("SHA-256");
121-
byte[] hashBytes = digest.digest(boundariesStr.toString().getBytes());
122-
123-
// Convert to hexadecimal string
124-
StringBuilder hexString = new StringBuilder();
125-
for (byte b : hashBytes) {
126-
String hex = Integer.toHexString(0xff & b);
127-
if (hex.length() == 1) {
128-
hexString.append('0');
129-
}
130-
hexString.append(hex);
131-
}
132-
return hexString.toString();
133-
} catch (NoSuchAlgorithmException e) {
134-
throw new RuntimeException("SHA-256 algorithm not available", e);
135-
}
136-
}
13783
}

0 commit comments

Comments
 (0)