372 lines
12 KiB
Java
372 lines
12 KiB
Java
/* ###
|
|
* 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.
|
|
*/
|
|
import java.io.IOException;
|
|
import java.util.*;
|
|
|
|
import ghidra.app.script.GhidraScript;
|
|
import ghidra.feature.vt.api.db.VTSessionDB;
|
|
import ghidra.feature.vt.api.main.*;
|
|
import ghidra.framework.model.DomainFile;
|
|
import ghidra.framework.model.DomainObject;
|
|
import ghidra.program.model.address.Address;
|
|
import ghidra.program.model.listing.*;
|
|
import ghidra.util.exception.CancelledException;
|
|
import ghidra.util.exception.VersionException;
|
|
|
|
public class EvaluateVTMatch extends GhidraScript {
|
|
// NOTE: this script is very rudimentary and makes a lot of assumptions,
|
|
// such as unique symbol names in source and destination, that the
|
|
// same-named symbols are actual matches, etc.
|
|
//
|
|
// Perhaps it should be based on manual matches as ground-truth.
|
|
private static class MyFunction {
|
|
public String name;
|
|
public int srchits;
|
|
public int desthits;
|
|
public boolean matched;
|
|
public boolean mismatched;
|
|
public boolean srcexists;
|
|
public boolean destexists;
|
|
public boolean srcbody;
|
|
public boolean destbody;
|
|
|
|
public MyFunction(String nm) {
|
|
name = nm;
|
|
srchits = 0;
|
|
desthits = 0;
|
|
matched = false;
|
|
mismatched = false;
|
|
srcexists = false;
|
|
destexists = false;
|
|
srcbody = false;
|
|
destbody = false;
|
|
}
|
|
}
|
|
|
|
private static class VTScorer {
|
|
private Program sourceProg;
|
|
private Program destProg;
|
|
private TreeMap<String, MyFunction> nameset;
|
|
private FunctionManager sourceFuncMgr;
|
|
private FunctionManager destFuncMgr;
|
|
private int totalmatches;
|
|
private int possiblematches;
|
|
private int emptybodymatches; // A pair (with same name) neither of which have a body, not considered a possiblematch
|
|
private int othersrcfuncs;
|
|
private int otherdestfuncs;
|
|
private int conflicts;
|
|
private int matchdiscovered;
|
|
private int mismatch;
|
|
private double falsenegative;
|
|
private double falsepositive;
|
|
private List<String> mismatchList;
|
|
private int mismatchCountDown;
|
|
|
|
public VTScorer(Program src,Program dest) {
|
|
sourceProg = src;
|
|
destProg = dest;
|
|
nameset = new TreeMap<String,MyFunction>();
|
|
sourceFuncMgr = sourceProg.getFunctionManager();
|
|
destFuncMgr = destProg.getFunctionManager();
|
|
totalmatches = 0;
|
|
possiblematches = 0;
|
|
emptybodymatches = 0;
|
|
othersrcfuncs = 0;
|
|
otherdestfuncs = 0;
|
|
conflicts = 0;
|
|
matchdiscovered = 0;
|
|
mismatch = 0;
|
|
mismatchList = new ArrayList<String>();
|
|
mismatchCountDown = 100;
|
|
}
|
|
|
|
private static String getName(Function func) {
|
|
String name = func.getName();
|
|
int pos = name.indexOf("@@GLIB");
|
|
if (pos >= 0) {
|
|
name = name.substring(0, pos);
|
|
}
|
|
return name;
|
|
}
|
|
|
|
/**
|
|
* For every function in a manager, make sure a record exists in -mymap-
|
|
* @param funcMgr is the manager to iterate over
|
|
* @param mymap is the map to add records to
|
|
* @param isSrc is true if the srcexists field should be set to true, otherwise destexists is set
|
|
*/
|
|
private static void tagFunctionNames(FunctionManager funcMgr,Listing listing,
|
|
TreeMap<String, MyFunction> mymap, boolean isSrc) {
|
|
FunctionIterator functions = funcMgr.getFunctions(true);
|
|
while (functions.hasNext()) {
|
|
Function next = functions.next();
|
|
if (next.isThunk()) continue;
|
|
CodeUnit cu = listing.getCodeUnitAt(next.getEntryPoint());
|
|
boolean hasbody = false;
|
|
if ((cu != null) && (cu instanceof Instruction))
|
|
hasbody = true;
|
|
String funcName = getName(next);
|
|
MyFunction myRec = mymap.get(funcName);
|
|
if (myRec == null) {
|
|
myRec = new MyFunction(funcName);
|
|
mymap.put(myRec.name, myRec);
|
|
}
|
|
if (isSrc) {
|
|
myRec.srcexists = true;
|
|
myRec.srcbody = hasbody;
|
|
}
|
|
else {
|
|
myRec.destexists = true;
|
|
myRec.destbody = hasbody;
|
|
}
|
|
}
|
|
// Make sure external functions have an entry
|
|
functions = funcMgr.getExternalFunctions();
|
|
while(functions.hasNext()) {
|
|
Function next = functions.next();
|
|
String funcName = getName(next);
|
|
MyFunction myRec = mymap.get(funcName);
|
|
if (myRec == null) {
|
|
myRec = new MyFunction(funcName);
|
|
mymap.put(myRec.name, myRec);
|
|
}
|
|
if (isSrc) {
|
|
myRec.srcexists = true;
|
|
myRec.srcbody = false;
|
|
}
|
|
else {
|
|
myRec.destexists = true;
|
|
myRec.destbody = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void tag() {
|
|
tagFunctionNames(sourceFuncMgr, sourceProg.getListing(), nameset, true);
|
|
tagFunctionNames(destFuncMgr, destProg.getListing(), nameset, false);
|
|
}
|
|
|
|
public void registerMatch(Address srcAddr,Address destAddr) {
|
|
Function sourceFunc = sourceFuncMgr.getFunctionAt(srcAddr);
|
|
if (sourceFunc == null) {
|
|
return;
|
|
}
|
|
Function destFunc = destFuncMgr.getFunctionAt(destAddr);
|
|
if (destFunc == null) {
|
|
return;
|
|
}
|
|
totalmatches += 1; // We have a match between functions
|
|
|
|
String srcName = getName(sourceFunc);
|
|
String destName = getName(destFunc);
|
|
MyFunction srcRec = nameset.get(srcName);
|
|
MyFunction destRec = nameset.get(destName);
|
|
srcRec.srchits += 1;
|
|
destRec.desthits += 1;
|
|
if (srcRec == destRec) {
|
|
srcRec.matched = true;
|
|
}
|
|
else {
|
|
srcRec.mismatched = true;
|
|
if (mismatchCountDown > 0) {
|
|
mismatchList.add("Mismatch: " + srcName + " - " + destName);
|
|
mismatchCountDown -= 1;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
public void calcStats() {
|
|
Iterator<MyFunction> iterator2 = nameset.values().iterator();
|
|
while (iterator2.hasNext()) {
|
|
MyFunction myfunc = iterator2.next();
|
|
if (myfunc.srcexists && myfunc.destexists) {
|
|
possiblematches += 1;
|
|
if (!myfunc.srcbody || !myfunc.destbody)
|
|
emptybodymatches += 1; // One side or other does not have a body
|
|
}
|
|
else if (myfunc.srcexists) {
|
|
othersrcfuncs += 1;
|
|
}
|
|
else if (myfunc.destexists) {
|
|
otherdestfuncs += 1;
|
|
}
|
|
if (myfunc.mismatched)
|
|
mismatch += 1;
|
|
if (myfunc.matched) {
|
|
if ((myfunc.srchits == 1) && (myfunc.desthits == 1)) {
|
|
matchdiscovered += 1;
|
|
}
|
|
else {
|
|
conflicts += 1; // Match confused by conflicts
|
|
}
|
|
}
|
|
}
|
|
falsepositive = (double) mismatch / (double) possiblematches; // Functions in source that were mismatched with dest
|
|
falsenegative =
|
|
(double) (possiblematches - matchdiscovered) / (double) possiblematches;
|
|
}
|
|
|
|
public void reportResults(GhidraScript script,String msg) {
|
|
script.println(msg);
|
|
script.println(" False positive = " + Double.toString(falsepositive));
|
|
script.println(" False negative = " + Double.toString(falsenegative));
|
|
script.println(" Total reported matches = " + Integer.toString(totalmatches));
|
|
script.println(" Possible valid matches = " + Integer.toString(possiblematches));
|
|
script.println(" Empty body matches = " + Integer.toString(emptybodymatches));
|
|
script.println(" Non-conflicting valid matches = " + Integer.toString(matchdiscovered));
|
|
script.println(" Mismatches = " + Integer.toString(mismatch));
|
|
script.println(" Conflicting valid matches = " + Integer.toString(conflicts));
|
|
script.println(" Source unmatchable functions = " + Integer.toString(othersrcfuncs));
|
|
script.println(" Destination unmatchable functions = " + Integer.toString(otherdestfuncs));
|
|
}
|
|
|
|
public void reportFalseNegatives(GhidraScript script,boolean sourceSide,int max) {
|
|
Iterator<MyFunction> iterator2 = nameset.values().iterator();
|
|
int count = 0;
|
|
while (iterator2.hasNext()) {
|
|
MyFunction myfunc = iterator2.next();
|
|
if ((!myfunc.srcexists)||(!myfunc.destexists)) continue; // Make sure both sides have named function
|
|
if ((!myfunc.srcbody)||(!myfunc.destbody)) continue; // Make sure both sides have a body
|
|
if (!myfunc.matched) { // If the function pair was not matched
|
|
if (sourceSide && (myfunc.srchits==0)) {
|
|
script.println("Source miss: "+myfunc.name);
|
|
count += 1;
|
|
}
|
|
else if ((!sourceSide)&&(myfunc.desthits==0)) {
|
|
script.println("Dest miss: "+myfunc.name);
|
|
count += 1;
|
|
}
|
|
if (count >= max) return;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void reportUnmatchable(GhidraScript script,boolean sourceSide,int max) {
|
|
Iterator<MyFunction> iterator2 = nameset.values().iterator();
|
|
int count = 0;
|
|
while (iterator2.hasNext()) {
|
|
MyFunction myfunc = iterator2.next();
|
|
if (sourceSide) {
|
|
if (myfunc.srcexists && !myfunc.destexists) {
|
|
script.println("Source unmatchable: "+myfunc.name);
|
|
count += 1;
|
|
}
|
|
}
|
|
else {
|
|
if (myfunc.destexists && !myfunc.srcexists) {
|
|
script.println("Dest unmatchable: "+myfunc.name);
|
|
count += 1;
|
|
}
|
|
if (count >= max) break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void reportMismatches(GhidraScript script) {
|
|
for (String line : mismatchList) {
|
|
script.println(line);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void run() throws Exception {
|
|
DomainFile vtFile = askDomainFile("Select VT Session");
|
|
openVTSessionAndDoWork(vtFile);
|
|
}
|
|
|
|
private void openVTSessionAndDoWork(DomainFile domainFile) {
|
|
|
|
DomainObject vtDomainObject = null;
|
|
try {
|
|
vtDomainObject = domainFile.getDomainObject(this, false, false, monitor);
|
|
doWork((VTSessionDB) vtDomainObject);
|
|
}
|
|
catch (VersionException e) {
|
|
e.printStackTrace();
|
|
}
|
|
catch (CancelledException e) {
|
|
e.printStackTrace();
|
|
}
|
|
catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
finally {
|
|
if (vtDomainObject != null) {
|
|
vtDomainObject.release(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void doWork(VTSessionDB session) {
|
|
println("Working on session: " + session);
|
|
|
|
List<VTMatchSet> matchSets = session.getMatchSets();
|
|
Iterator<VTMatchSet> iterator = matchSets.iterator();
|
|
while (iterator.hasNext()) {
|
|
evaluateMatchSet(iterator.next());
|
|
}
|
|
evaluateAccepted(session);
|
|
}
|
|
|
|
private void evaluateAccepted(VTSessionDB session) {
|
|
VTScorer scorer = new VTScorer(session.getSourceProgram(),session.getDestinationProgram());
|
|
scorer.tag();
|
|
|
|
VTAssociationManager associationManager = session.getAssociationManager();
|
|
List<VTAssociation> associations = associationManager.getAssociations();
|
|
for (VTAssociation association : associations) {
|
|
if (association.getStatus() == VTAssociationStatus.ACCEPTED) {
|
|
Address srcAddr = association.getSourceAddress();
|
|
Address destAddr = association.getDestinationAddress();
|
|
scorer.registerMatch(srcAddr, destAddr);
|
|
}
|
|
}
|
|
|
|
scorer.calcStats();
|
|
scorer.reportResults(this,"OVERALL ACCEPTED RESULTS:");
|
|
// scorer.reportFalseNegatives(this, true,20);
|
|
// scorer.reportUnmatchable(this, true, 20);
|
|
scorer.reportMismatches(this);
|
|
}
|
|
|
|
private void evaluateMatchSet(VTMatchSet matchset) {
|
|
Collection<VTMatch> matches = matchset.getMatches();
|
|
if (matches.isEmpty()) {
|
|
println("Empty matchset - " + matchset.getProgramCorrelatorInfo().getName() + " (" +
|
|
matchset.getID() + ")");
|
|
return;
|
|
}
|
|
VTSession vtsession = matchset.getSession();
|
|
VTScorer scorer = new VTScorer(vtsession.getSourceProgram(),vtsession.getDestinationProgram());
|
|
scorer.tag();
|
|
|
|
Iterator<VTMatch> iterator = matches.iterator();
|
|
while (iterator.hasNext()) {
|
|
VTMatch next = iterator.next();
|
|
VTAssociation association = next.getAssociation();
|
|
Address srcAddr = association.getSourceAddress();
|
|
Address destAddr = association.getDestinationAddress();
|
|
scorer.registerMatch(srcAddr, destAddr);
|
|
}
|
|
scorer.calcStats();
|
|
scorer.reportResults(this,matchset.getProgramCorrelatorInfo().getName() + " (" + matchset.getID() +
|
|
"):");
|
|
}
|
|
|
|
}
|