/* ### * IP: GHIDRA * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //Queries all functions in the current selection (or all functions in the current program if //the current selection is null) against all functions in a user-selected program. //@category BSim import java.util.*; import org.apache.commons.collections4.IteratorUtils; import generic.lsh.vector.*; import ghidra.app.decompiler.DecompileException; import ghidra.app.script.GhidraScript; import ghidra.app.services.FunctionComparisonService; import ghidra.app.tablechooser.*; import ghidra.features.base.codecompare.model.MatchedFunctionComparisonModel; import ghidra.features.bsim.query.*; import ghidra.features.bsim.query.client.Configuration; import ghidra.features.bsim.query.description.FunctionDescription; import ghidra.program.model.address.Address; import ghidra.program.model.listing.*; //TODO: docs public class LocalBSimQueryScript extends GhidraScript { //functions with self significance below this bound will be skipped private static final double SELF_SIGNIFICANCE_BOUND = 15.0; //bsim database template determining the signature settings private static final String TEMPLATE_NAME = "medium_nosize"; //these are analogous to the bounds in a bsim query private static final double MATCH_SIMILARITY_LOWER_BOUND = 0.0; private static final double MATCH_CONFIDENCE_LOWER_BOUND = 0.0; private static final int MATCHES_PER_FUNCTION = 10; //decrease this if you only want to see matches that aren't exact //for instance, when looking for changes between two versions of a program private static final double MATCH_SIMILARITY_UPPER_BOUND = 1.0; private TableChooserDialog tableDialog; @Override protected void run() throws Exception { if (isRunningHeadless()) { popup("This script cannot be run headlessly."); return; } if (currentProgram == null) { popup("This script requires a program to be open in the tool"); return; } Set sourceFuncs = new HashSet<>(); if (currentSelection == null) { IteratorUtils.forEach(currentProgram.getFunctionManager().getFunctions(true), x -> sourceFuncs.add(x)); } else { IteratorUtils.forEach( currentProgram.getFunctionManager().getFunctionsOverlapping(currentSelection), x -> sourceFuncs.add(x)); } if (sourceFuncs.isEmpty()) { this.popup("No non-stub functions to query!"); return; } Program targetProgram = askProgram("Select Target Program"); if (targetProgram == null) { return; } try { List localMatches = null; //use special optimized method when the target program is the same as the current program //in that case, a given function might be in both the source and target sets //but we only want to generate signatures for it once if (currentProgram.getUniqueProgramID() == targetProgram.getUniqueProgramID()) { localMatches = getMatchesCurrentProgram(sourceFuncs); } else { //in this case there is no overlap between the source and target functions localMatches = getMatchesTwoPrograms(sourceFuncs, currentProgram, targetProgram); } if (localMatches.isEmpty()) { popup("No matches meeting criteria."); return; } Collections.sort(localMatches); initializeTable(currentProgram, targetProgram); //again, use an optimized method for the special case when target program is the same //as the current program if (currentProgram.getUniqueProgramID() == targetProgram.getUniqueProgramID()) { addMatchesOneProgram(localMatches, sourceFuncs); } else { addMatchesTwoPrograms(localMatches); } } finally { targetProgram.release(this); } } /** * Iterate through the list of sorted matches, adding the top MATCHES_PER_FUNCTION elements * to the table for each source function. * @param localMatches matches in decreasing order of confidence */ private void addMatchesTwoPrograms(List localMatches) { Map matchCounts = new HashMap<>(); for (LocalBSimMatch match : localMatches) { int count = matchCounts.getOrDefault(match.getSourceFunc(), 0); if (count >= MATCHES_PER_FUNCTION) { continue; } tableDialog.add(match); matchCounts.put(match.getSourceFunc(), count + 1); } } /** * Iterate through the list of sorted matches, adding the top MATCHES_PER_FUNCTION elements * to the table for each function ins {@code sourceFuncSet}. * * By construction, the matches in this list have the "source" function before the "target" * function (in address order). This is an optimization to prevent essentially the same * data from appearing in the list twice (since the BSim similarity and confidence operations * are commutative). So, for each match, we need to check whether the source or the * target are in {@code sourceFuncSet}. * * @param localMatches matches in decreasing order of confidence * @param sourceFuncSet source functions */ private void addMatchesOneProgram(List localMatches, Set sourceFuncSet) { Map matchCounts = new HashMap<>(); for (LocalBSimMatch match : localMatches) { Function leftFunc = match.getSourceFunc(); int leftCount = matchCounts.getOrDefault(leftFunc, 0); if (sourceFuncSet.contains(leftFunc) && leftCount < MATCHES_PER_FUNCTION) { tableDialog.add(match); matchCounts.put(leftFunc, leftCount + 1); } Function rightFunc = match.getTargetFunc(); int rightCount = matchCounts.getOrDefault(rightFunc, 0); if (sourceFuncSet.contains(rightFunc) && rightCount < MATCHES_PER_FUNCTION) { LocalBSimMatch switched = new LocalBSimMatch(rightFunc, leftFunc, match.getSimilarity(), match.getSignificance()); tableDialog.add(switched); matchCounts.put(rightFunc, rightCount + 1); } } } private List getMatchesCurrentProgram(Set funcs) throws LSHException, DecompileException { List bsimMatches = new ArrayList<>(); LSHVectorFactory vectorFactory = getVectorFactory(); //generate the signatures for *all* functions in the program... FunctionManager fman = currentProgram.getFunctionManager(); Iterator iter = fman.getFunctions(true); GenSignatures gensig = generateSignatures(currentProgram, iter, fman.getFunctionCount(), vectorFactory); //...but use sourceFuncAddrs to ensure that source functions are in the //funcs set Set sourceFuncAddrs = new HashSet<>(); for (Function func : funcs) { sourceFuncAddrs.add(func.getEntryPoint().getOffset()); } Iterator sourceDescripts = gensig.getDescriptionManager().listAllFunctions(); VectorCompare vecCompare = new VectorCompare(); while (sourceDescripts.hasNext()) { FunctionDescription srcDesc = sourceDescripts.next(); //skip if not in selection if (!sourceFuncAddrs.contains(srcDesc.getAddress())) { continue; } //skip if self-significance too small LSHVector srcVector = srcDesc.getSignatureRecord().getLSHVector(); if (vectorFactory.getSelfSignificance(srcVector) <= SELF_SIGNIFICANCE_BOUND) { continue; } Iterator targetDescripts = gensig.getDescriptionManager().listAllFunctions(); Function srcFunc = getFunction(currentProgram, srcDesc.getAddress()); while (targetDescripts.hasNext()) { //skip if target before srcFunc in address order //AND target is one of the source functions (i.e., in funcs) FunctionDescription targetDesc = targetDescripts.next(); long targetAddress = targetDesc.getAddress(); if (sourceFuncAddrs.contains(targetAddress) && targetAddress <= srcDesc.getAddress()) { continue; } //skip if self-significance too small LSHVector targetVector = targetDesc.getSignatureRecord().getLSHVector(); if (vectorFactory.getSelfSignificance(targetVector) <= SELF_SIGNIFICANCE_BOUND) { continue; } double sim = srcVector.compare(targetVector, vecCompare); double sig = vectorFactory.calculateSignificance(vecCompare); if (sig >= MATCH_CONFIDENCE_LOWER_BOUND && MATCH_SIMILARITY_LOWER_BOUND <= sim && sim <= MATCH_SIMILARITY_UPPER_BOUND) { Function targetFunc = getFunction(currentProgram, targetDesc.getAddress()); bsimMatches.add(new LocalBSimMatch(srcFunc, targetFunc, sim, sig)); } } } return bsimMatches; } private List getMatchesTwoPrograms(Set srcFuncs, Program sourceProgram, Program targetProgram) throws LSHException, DecompileException { List bsimMatches = new ArrayList<>(); LSHVectorFactory vectorFactory = getVectorFactory(); GenSignatures srcSigs = generateSignatures(sourceProgram, srcFuncs.iterator(), srcFuncs.size(), vectorFactory); FunctionManager targetFuncMan = targetProgram.getFunctionManager(); Iterator targetFuncIter = targetFuncMan.getFunctions(true); GenSignatures targetSigs = generateSignatures(targetProgram, targetFuncIter, targetFuncMan.getFunctionCount(), vectorFactory); Iterator sourceDescripts = srcSigs.getDescriptionManager().listAllFunctions(); VectorCompare vecCompare = new VectorCompare(); while (sourceDescripts.hasNext()) { FunctionDescription srcDesc = sourceDescripts.next(); //skip if self-significance too small LSHVector srcVector = srcDesc.getSignatureRecord().getLSHVector(); if (vectorFactory.getSelfSignificance(srcVector) <= SELF_SIGNIFICANCE_BOUND) { continue; } Iterator targetDescripts = targetSigs.getDescriptionManager().listAllFunctions(); Function srcFunc = getFunction(sourceProgram, srcDesc.getAddress()); while (targetDescripts.hasNext()) { FunctionDescription targetDesc = targetDescripts.next(); //skip if self-significance too small LSHVector targetVector = targetDesc.getSignatureRecord().getLSHVector(); if (vectorFactory.getSelfSignificance(targetVector) <= SELF_SIGNIFICANCE_BOUND) { continue; } double sim = srcVector.compare(targetVector, vecCompare); double sig = vectorFactory.calculateSignificance(vecCompare); if (sig >= MATCH_CONFIDENCE_LOWER_BOUND && MATCH_SIMILARITY_LOWER_BOUND <= sim && sim <= MATCH_SIMILARITY_UPPER_BOUND) { Function targetFunc = getFunction(targetProgram, targetDesc.getAddress()); bsimMatches.add(new LocalBSimMatch(srcFunc, targetFunc, sim, sig)); } } } return bsimMatches; } private Function getFunction(Program program, long offset) { Address addr = program.getAddressFactory().getDefaultAddressSpace().getAddress(offset); return program.getFunctionManager().getFunctionAt(addr); } private LSHVectorFactory getVectorFactory() throws LSHException { LSHVectorFactory vectorFactory = FunctionDatabase.generateLSHVectorFactory(); Configuration config = FunctionDatabase.loadConfigurationTemplate(TEMPLATE_NAME); vectorFactory.set(config.weightfactory, config.idflookup, config.info.settings); return vectorFactory; } private GenSignatures generateSignatures(Program program, Iterator funcs, int count, LSHVectorFactory vectorFactory) throws LSHException, DecompileException { GenSignatures gensig = new GenSignatures(false); gensig.setVectorFactory(vectorFactory); gensig.openProgram(program, null, null, null, null, null); gensig.scanFunctions(funcs, count, monitor); return gensig; } class LocalBSimMatch implements Comparable, AddressableRowObject { private Function sourceFunc; private Function targetFunc; private double similarity; private double significance; public LocalBSimMatch(Function sourceFunc, Function targetFunc, double sim, double signif) { this.sourceFunc = sourceFunc; this.targetFunc = targetFunc; this.similarity = sim; this.significance = signif; } public Function getSourceFunc() { return sourceFunc; } public Function getTargetFunc() { return targetFunc; } public double getSimilarity() { return similarity; } public double getSignificance() { return significance; } public Program getSourceProgram() { return sourceFunc.getProgram(); } public Program getTargetProgram() { return targetFunc.getProgram(); } @Override public int compareTo(LocalBSimQueryScript.LocalBSimMatch o) { return -Double.compare(significance, o.significance); } @Override public Address getAddress() { return sourceFunc.getEntryPoint(); } } /**************************************************************************************** * table stuff ****************************************************************************************/ class CompareMatchesExecutor implements TableChooserExecutor { private FunctionComparisonService compareService; public CompareMatchesExecutor() { compareService = state.getTool().getService(FunctionComparisonService.class); } @Override public String getButtonName() { return "Compare Selected Matches"; } @Override public boolean execute(AddressableRowObject rowObject) { LocalBSimMatch match = (LocalBSimMatch) rowObject; compareService.createComparison(match.getSourceFunc(), match.getTargetFunc()); return false; } } private void initializeTable(Program sourceProgram, Program targetProgram) { StringBuilder titleBuilder = new StringBuilder("Local BSim Matches: "); titleBuilder.append(sourceProgram.getDomainFile().getPathname()); titleBuilder.append(" -> "); titleBuilder.append(targetProgram.getDomainFile().getPathname()); tableDialog = createTableChooserDialog(titleBuilder.toString(), new CompareMatchesExecutor()); configureTableColumns(tableDialog); tableDialog.setMinimumSize(800, 400); tableDialog.show(); tableDialog.setMessage(null); } private void configureTableColumns(TableChooserDialog dialog) { ColumnDisplay simColumn = new AbstractComparableColumnDisplay() { @Override public Double getColumnValue(AddressableRowObject rowObject) { return ((LocalBSimMatch) rowObject).getSimilarity(); } @Override public String getColumnName() { return "Similarity"; } }; ColumnDisplay sigColumn = new AbstractComparableColumnDisplay() { @Override public Double getColumnValue(AddressableRowObject rowObject) { return ((LocalBSimMatch) rowObject).getSignificance(); } @Override public String getColumnName() { return "Significance"; } }; StringColumnDisplay sourceFuncColumn = new StringColumnDisplay() { @Override public String getColumnValue(AddressableRowObject rowObject) { return ((LocalBSimMatch) rowObject).getSourceFunc().getName(true); } @Override public String getColumnName() { return "Source Function"; } }; StringColumnDisplay targetFuncColumn = new StringColumnDisplay() { @Override public String getColumnValue(AddressableRowObject rowObject) { return ((LocalBSimMatch) rowObject).getTargetFunc().getName(true); } @Override public String getColumnName() { return "Target Function"; } }; dialog.addCustomColumn(simColumn); dialog.addCustomColumn(sigColumn); dialog.addCustomColumn(sourceFuncColumn); dialog.addCustomColumn(targetFuncColumn); } }