diff --git a/Ghidra/Features/DecompilerDependent/build.gradle b/Ghidra/Features/DecompilerDependent/build.gradle index 9fab3b88c9..10e0ab6951 100644 --- a/Ghidra/Features/DecompilerDependent/build.gradle +++ b/Ghidra/Features/DecompilerDependent/build.gradle @@ -26,6 +26,8 @@ eclipse.project.name = 'Features DecompilerDependent' dependencies { api project(':Base') api project(':Decompiler') + api project(':Sarif') + api "com.contrastsecurity.sarif:java-sarif-2.1-modified" } diff --git a/Ghidra/Features/DecompilerDependent/certification.manifest b/Ghidra/Features/DecompilerDependent/certification.manifest index 12dbefcacd..7bada2609a 100644 --- a/Ghidra/Features/DecompilerDependent/certification.manifest +++ b/Ghidra/Features/DecompilerDependent/certification.manifest @@ -4,6 +4,7 @@ README.md||GHIDRA||||END| data/ExtensionPoint.manifest||GHIDRA||||END| data/decompiler.dependent.theme.properties||GHIDRA||||END| src/main/help/help/TOC_Source.xml||GHIDRA||||END| +src/main/help/help/topics/DecompilerTaint/DecompilerTaint.html||GHIDRA||||END| src/main/help/help/topics/DecompilerTextFinderPlugin/Decompiler_Text_Finder.html||GHIDRA||||END| src/main/help/help/topics/DecompilerTextFinderPlugin/images/DecompilerTextFinderDialog.png||GHIDRA||||END| src/main/help/help/topics/DecompilerTextFinderPlugin/images/DecompilerTextFinderResultsTable.png||GHIDRA||||END| diff --git a/Ghidra/Features/DecompilerDependent/ghidra_scripts/ExportPCodeForCTADL.java b/Ghidra/Features/DecompilerDependent/ghidra_scripts/ExportPCodeForCTADL.java new file mode 100644 index 0000000000..7abaefbb37 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/ghidra_scripts/ExportPCodeForCTADL.java @@ -0,0 +1,1178 @@ +/* ### + * 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. + */ +//Decompile the function at the cursor and its callees, then output facts files corresponding to the pcodes +//@category PCode + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import ghidra.app.decompiler.DecompInterface; +import ghidra.app.decompiler.DecompileOptions; +import ghidra.app.decompiler.DecompileResults; +import ghidra.app.decompiler.parallel.DecompileConfigurer; +import ghidra.app.decompiler.parallel.DecompilerCallback; +import ghidra.app.decompiler.parallel.ParallelDecompiler; +import ghidra.app.script.GhidraScript; +import ghidra.framework.plugintool.PluginTool; +import ghidra.graph.GDirectedGraph; +import ghidra.graph.GEdge; +import ghidra.graph.GVertex; +import ghidra.graph.GraphFactory; +import ghidra.graph.algo.DepthFirstSorter; +import ghidra.program.database.symbol.FunctionSymbol; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressFactory; +import ghidra.program.model.data.AbstractFloatDataType; +import ghidra.program.model.data.AbstractIntegerDataType; +import ghidra.program.model.data.Array; +import ghidra.program.model.data.BooleanDataType; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.DataTypeComponent; +import ghidra.program.model.data.FunctionDefinition; +import ghidra.program.model.data.ParameterDefinition; +import ghidra.program.model.data.Pointer; +import ghidra.program.model.data.Structure; +import ghidra.program.model.data.TypeDef; +import ghidra.program.model.data.Union; +import ghidra.program.model.listing.Data; +import ghidra.program.model.listing.DataIterator; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.MemoryAccessException; +import ghidra.program.model.pcode.FunctionPrototype; +import ghidra.program.model.pcode.HighConstant; +import ghidra.program.model.pcode.HighFunction; +import ghidra.program.model.pcode.HighGlobal; +import ghidra.program.model.pcode.HighLocal; +import ghidra.program.model.pcode.HighOther; +import ghidra.program.model.pcode.HighSymbol; +import ghidra.program.model.pcode.HighVariable; +import ghidra.program.model.pcode.PcodeBlock; +import ghidra.program.model.pcode.PcodeBlockBasic; +import ghidra.program.model.pcode.PcodeOp; +import ghidra.program.model.pcode.PcodeOpAST; +import ghidra.program.model.pcode.SequenceNumber; +import ghidra.program.model.pcode.Varnode; +import ghidra.program.model.pcode.VarnodeAST; +import ghidra.program.model.symbol.ExternalReference; +import ghidra.program.model.symbol.Reference; +import ghidra.program.model.symbol.Symbol; +import ghidra.program.model.symbol.SymbolIterator; +import ghidra.program.model.symbol.SymbolTable; +import ghidra.program.model.symbol.ThunkReference; +import ghidra.util.task.TaskMonitor; + +class PcodeBlockBasicVertex implements GVertex { + private PcodeBlock bb; + + public PcodeBlockBasicVertex(PcodeBlock bb) { + this.bb = bb; + } + + public PcodeBlockBasic bb() { + return (PcodeBlockBasic) bb; + } +} + +class PcodeBlockBasicEdge implements GEdge { + private PcodeBlockBasicVertex start; + private PcodeBlockBasicVertex end; + + public PcodeBlockBasicEdge(PcodeBlockBasicVertex start, PcodeBlockBasicVertex end) { + this.start = start; + this.end = end; + } + + public PcodeBlockBasicVertex getStart() { + return start; + } + + public PcodeBlockBasicVertex getEnd() { + return end; + } +} + +class DecompilerConfigurer implements DecompileConfigurer { + + DecompInterface ifc; + + DecompilerConfigurer(Program p) { + } + + @Override + public void configure(DecompInterface decompiler) { + this.ifc = decompiler; + DecompileOptions options = new DecompileOptions(); + decompiler.setOptions(options); + decompiler.setSimplificationStyle("decompile"); // can also use "normalize" but that won't generate + // HighVariables + } + + DecompInterface getInteface() { + return ifc; + } + +} + +enum PredicateFile { + LANGUAGE("CTADLLanguage"), HFUNC_LOCAL_EP("HFUNC_LOCAL_EP"), HFUNC_FUNC("HFUNC_FUNC"), HFUNC_TOSTR("HFUNC_TOSTR"), + HFUNC_PROTO("HFUNC_PROTO"), HFUNC_ISEP("HFUNC_ISEP"), HFUNC_ISEXT("HFUNC_ISEXT"), HFUNC_CSPEC("HFUNC_CSPEC"), + HFUNC_EP("HFUNC_EP"), HFUNC_LANG("HFUNC_LANG"), HFUNC_NAME("HFUNC_NAME"), HVAR_NAME("HVAR_NAME"), + HVAR_SIZE("HVAR_SIZE"), HVAR_CLASS("HVAR_CLASS"), HVAR_SCOPE("HVAR_SCOPE"), HVAR_TYPE("HVAR_TYPE"), + HVAR_REPRESENTATIVE("HVAR_REPRESENTATIVE"), PCODE_TOSTR("PCODE_TOSTR"), PCODE_MNEMONIC("PCODE_MNEMONIC"), + PCODE_OPCODE("PCODE_OPCODE"), PCODE_PARENT("PCODE_PARENT"), PCODE_TARGET("PCODE_TARGET"), + PCODE_INPUT_COUNT("PCODE_INPUT_COUNT"), PCODE_INPUT("PCODE_INPUT"), PCODE_OUTPUT("PCODE_OUTPUT"), + PCODE_NEXT("PCODE_NEXT"), PCODE_TIME("PCODE_TIME"), PCODE_INDEX("PCODE_INDEX"), VNODE_ADDRESS("VNODE_ADDRESS"), + VNODE_IS_ADDRESS("VNODE_IS_ADDRESS"), VNODE_IS_ADDRTIED("VNODE_IS_ADDRTIED"), VNODE_PC_ADDRESS("VNODE_PC_ADDRESS"), + VNODE_DESC("VNODE_DESC"), VNODE_OFFSET("VNODE_OFFSET"), VNODE_OFFSET_N("VNODE_OFFSET_N"), VNODE_SIZE("VNODE_SIZE"), + VNODE_NAME("VNODE_NAME"), VNODE_SPACE("VNODE_SPACE"), VNODE_TOSTR("VNODE_TOSTR"), VNODE_HVAR("VNODE_HVAR"), + VNODE_DEF("VNODE_DEF"), VNODE_HFUNC("VNODE_HFUNC"), TYPE_NAME("TYPE_NAME"), TYPE_LENGTH("TYPE_LENGTH"), + TYPE_POINTER("TYPE_POINTER"), TYPE_POINTER_BASE("TYPE_POINTER_BASE"), TYPE_ARRAY("TYPE_ARRAY"), + TYPE_ARRAY_BASE("TYPE_ARRAY_BASE"), TYPE_ARRAY_N("TYPE_ARRAY_N"), + TYPE_ARRAY_ELEMENT_LENGTH("TYPE_ARRAY_ELEMENT_LENGTH"), TYPE_STRUCT("TYPE_STRUCT"), + TYPE_STRUCT_FIELD("TYPE_STRUCT_FIELD"), TYPE_STRUCT_OFFSET("TYPE_STRUCT_OFFSET"), + TYPE_STRUCT_OFFSET_N("TYPE_STRUCT_OFFSET_N"), TYPE_STRUCT_FIELD_NAME("TYPE_STRUCT_FIELD_NAME"), + TYPE_STRUCT_FIELD_NAME_BY_OFFSET("TYPE_STRUCT_FIELD_NAME_BY_OFFSET"), + TYPE_STRUCT_FIELD_COUNT("TYPE_STRUCT_FIELD_COUNT"), TYPE_UNION("TYPE_UNION"), TYPE_UNION_FIELD("TYPE_UNION_FIELD"), + TYPE_UNION_OFFSET("TYPE_UNION_OFFSET"), TYPE_UNION_OFFSET_N("TYPE_UNION_OFFSET_N"), + TYPE_UNION_FIELD_NAME("TYPE_UNION_FIELD_NAME"), TYPE_UNION_FIELD_NAME_BY_OFFSET("TYPE_UNION_FIELD_NAME_BY_OFFSET"), + TYPE_UNION_FIELD_COUNT("TYPE_UNION_FIELD_COUNT"), TYPE_FUNC("TYPE_FUNC"), TYPE_FUNC_RET("TYPE_FUNC_RET"), + TYPE_FUNC_VARARGS("TYPE_FUNC_VARARGS"), TYPE_FUNC_PARAM_COUNT("TYPE_FUNC_PARAM_COUNT"), + TYPE_FUNC_PARAM("TYPE_FUNC_PARAM"), TYPE_BOOLEAN("TYPE_BOOLEAN"), TYPE_INTEGER("TYPE_INTEGER"), + TYPE_FLOAT("TYPE_FLOAT"), TYPE_ENUM("TYPE_ENUM"), BB_IN("BB_IN"), BB_LAST("BB_LAST"), BB_OUT("BB_OUT"), + BB_FOUT("BB_FOUT"), BB_TOUT("BB_TOUT"), BB_FIRST("BB_FIRST"), BB_HFUNC("BB_HFUNC"), BB_START("BB_START"), + PROTO_IS_CONSTRUCTOR("PROTO_IS_CONSTRUCTOR"), PROTO_IS_DESTRUCTOR("PROTO_IS_DESTRUCTOR"), + PROTO_IS_VARARG("PROTO_IS_VARARG"), PROTO_IS_INLINE("PROTO_IS_INLINE"), PROTO_IS_VOID("PROTO_IS_VOID"), + PROTO_HAS_THIS("PROTO_HAS_THIS"), PROTO_CALLING_CONVENTION("PROTO_CALLING_CONVENTION"), + PROTO_RETTYPE("PROTO_RETTYPE"), PROTO_PARAMETER("PROTO_PARAMETER"), PROTO_PARAMETER_COUNT("PROTO_PARAMETER_COUNT"), + PROTO_PARAMETER_DATATYPE("PROTO_PARAMETER_DATATYPE"), SYMBOL_HVAR("SYMBOL_HVAR"), SYMBOL_HFUNC("SYMBOL_HFUNC"), + DATA_STRING("DATA_STRING"), VTABLE("VTABLE"), SYMBOL_NAME("SYMBOL_NAME"), PROGRAM_FILE("PROGRAM_FILE"); + + private final String name; + + PredicateFile(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } + + public Writer getWriter(File directory, String suffix, boolean append) throws IOException { + File factsFile = new File(directory, name + suffix); + if (!append) + touch(factsFile); + return new BufferedWriter(new FileWriter(factsFile, true)); + } + + public static void touch(File file) throws IOException { + file.delete(); + file.createNewFile(); + } +} + +class Database { + private static final char DBSEP = '\t'; + private static final char EOL = '\n'; + + protected final Map>> contents; + protected boolean everCleared; + + public Database() { + contents = new EnumMap<>(PredicateFile.class); + for (PredicateFile predicateFile : EnumSet.allOf(PredicateFile.class)) + contents.put(predicateFile, new HashSet>()); + // contents.put(predicateFile, new ArrayList>()); + everCleared = false; + } + + public void writeFile(Collection> contents, PredicateFile predicateFile, String directory) + throws IOException { + Writer w = predicateFile.getWriter(new File(directory), ".facts", everCleared); + for (List record : contents) { + writeRecord(w, record); + } + w.flush(); + w.close(); + } + + public void writeFacts(String directory, boolean writeAll) throws IOException { + final int k = Runtime.getRuntime().availableProcessors() * 2 - 1; + ExecutorService es = Executors.newFixedThreadPool(k); + for (PredicateFile predicateFile : EnumSet.allOf(PredicateFile.class)) { + if (!writeAll && predicateFile.name().startsWith("TYPE_")) { + continue; + } + es.execute(() -> { + try { + writeFile(this.contents.get(predicateFile), predicateFile, directory); + } catch (Exception e) { + // System.out.println(e); + ghidra.util.Msg.info(this, e); + for (java.lang.StackTraceElement st : e.getStackTrace()) { + ghidra.util.Msg.info(this, st); + } + } + }); + } + + es.shutdown(); + try { + while (!es.awaitTermination(30, TimeUnit.SECONDS)) + ; + } catch (Exception e) { + // System.out.println(e); + ghidra.util.Msg.info(this, e); + for (java.lang.StackTraceElement st : e.getStackTrace()) { + ghidra.util.Msg.info(this, st); + } + } + + this.clear(writeAll); + } + + private void writeRecord(Writer writer, List record) throws IOException { + boolean first = true; + for (String col : record) { + if (!first) { + writer.write(DBSEP); + } + writeColumn(writer, col); + first = false; + } + writer.write(EOL); + } + + private void writeColumn(Writer writer, String column) throws IOException { + // Quote some special characters. + final char QUOTE = '\"'; + final char SLASH = '\\'; + final char TABCH = '\t'; + for (int i = 0; i < column.length(); i++) { + char c = column.charAt(i); + switch (c) { + case QUOTE: + writer.write("'"); + break; + case SLASH: + writer.write('\\'); + writer.write('\\'); + break; + case EOL: + writer.write('\\'); + writer.write('n'); + break; + case TABCH: + writer.write('\\'); + writer.write('t'); + break; + default: + writer.write(c); + } + } + } + + public void add(PredicateFile predicateFile, String... args) { + this.contents.get(predicateFile).add(Arrays.asList(args)); + } + + public void merge(Database other) { + for (Map.Entry>> entry : other.contents.entrySet()) { + this.contents.get(entry.getKey()).addAll(entry.getValue()); + } + + } + + public void clear(boolean clearAll) { + for (PredicateFile predicateFile : EnumSet.allOf(PredicateFile.class)) { + if (!clearAll && predicateFile.name().startsWith("TYPE_")) { + continue; + } + contents.put(predicateFile, new HashSet>()); + } + everCleared = true; + } +} + +class HighFunctionExporter { + private final Database db = new Database(); + private final Set types = new HashSet(); + private Set varnodes = new HashSet(); + private final HashMap componentPredicates = new HashMap(); + private Map extraGlobals = new HashMap(); + private final Writer debug; + private final String directory; + private final Set
vtables = new HashSet
(); + + String SEP = ":"; + String TAB = "\t"; + String QUOTE = "\""; + + public HighFunctionExporter(String directory) throws IOException { + this.directory = directory; + for (PredicateFile pf : PredicateFile.values()) { + componentPredicates.put(pf.toString(), pf); + } + debug = new BufferedWriter(new FileWriter(new File(directory, "PcodeOps.facts"))); + } + + public void writeFacts(boolean writeAll) throws IOException { + db.writeFacts(directory, writeAll); + } + + public void writeDebug() throws IOException { + debug.close(); + } + + public Database getDatabase() { + return db; + } + + public void processFunction(DecompileResults res, Function f, DecompInterface decompiler) { + // System.out.println("processing " + f.getName() + SEP + f.getEntryPoint()); + varnodes = new HashSet(); + extraGlobals = new HashMap(); + +// TODO: This should only be done once if dumping the entire program + SymbolIterator externalSymbols = f.getProgram().getSymbolTable().getSymbols(f.getName()); + while (externalSymbols.hasNext()) { + Symbol next = externalSymbols.next(); + if (!next.isExternal()) { + Address address = next.getAddress(); + export(PredicateFile.HFUNC_LOCAL_EP, addressString(f.getEntryPoint()), addressString(address)); + } else if (next instanceof FunctionSymbol fsym) { + Reference[] references = fsym.getReferences(); + for (Reference r : references) { + if (r instanceof ThunkReference || r instanceof ExternalReference) { + Address address = r.getFromAddress(); + if (address != null) { + export(PredicateFile.HFUNC_LOCAL_EP, addressString(f.getEntryPoint()), + addressString(address)); + } + } + } + } + } + HighFunction high = getHighFunction(res, f, decompiler); + debug("Starting function " + high.getFunction().getName()); + + // we want to make sure the pcode indexes are in source order. + // we accomplish this by sorting the basic blocks topologically. + GDirectedGraph g = GraphFactory.createDirectedGraph(); + for (PcodeBlockBasic bb : high.getBasicBlocks()) { + PcodeBlockBasicVertex v = new PcodeBlockBasicVertex(bb); + g.addVertex(v); + for (int i = 0; i < bb.getInSize(); i++) { + PcodeBlockBasicVertex vIn = new PcodeBlockBasicVertex(bb.getIn(i)); + g.addVertex(vIn); + g.addEdge(new PcodeBlockBasicEdge(vIn, v)); + } + for (int i = 0; i < bb.getOutSize(); i++) { + PcodeBlockBasicVertex vOut = new PcodeBlockBasicVertex(bb.getOut(i)); + g.addVertex(vOut); + g.addEdge(new PcodeBlockBasicEdge(v, vOut)); + } + } + + HashSet set = new HashSet(); + HashSet bbset = new HashSet(); + int index = 0; + for (PcodeBlockBasicVertex v : DepthFirstSorter.preOrder(g)) { + PcodeBlockBasic bb = v.bb(); + if (bbset.contains(bb.getIndex())) { + continue; + } + bbset.add(bb.getIndex()); + debug("Starting basic block " + bb.getIndex()); + Iterator opiter = bb.getIterator(); // high.getPcodeOps(); + while (opiter.hasNext()) { + PcodeOp op = opiter.next(); + if (op != null) { + set.add(op); + exportPcode(high, index++, op); + } + } + debug("End basic block " + bb.getIndex()); + } + + exportPcodeOpSequence(high, set); + exportHighFunction(high); + debug("End function " + high.getFunction().getName()); + } + + private BigInteger readInteger(Program program, Address addr, int size) { + //AddressFactory addrFactory = program.getAddressFactory(); + //int spaceID = addr.getAddressSpace().getSpaceID(); + try { + byte[] dest = new byte[size]; + program.getMemory().getBytes(addr, dest, 0, size); + if (!program.getLanguage().isBigEndian()) { + reverseBytes(dest); + } + return new BigInteger(dest); + } catch (MemoryAccessException e) { + debug("MemoryAccessException, skipping: " + e); + return BigInteger.ZERO; + } + } + + private void reverseBytes(byte[] arr) { + for (int i = 0; i < arr.length / 2; i++) { + byte tmp = arr[i]; + arr[i] = arr[arr.length - i - 1]; + arr[arr.length - i - 1] = tmp; + } + } + + public void processVTable(Program program, Symbol sym) { + StringBuilder className = new StringBuilder(); + for (String s : sym.getPath()) { + className.append("::" + s); + } + Address addr = sym.getAddress(); + if (program.getMemory().getBlock(addr).isExternalBlock()) + return; + // SymbolTable symbolTable = program.getSymbolTable(); + AddressFactory addrFactory = program.getAddressFactory(); + int ptrBytes = program.getAddressFactory().getDefaultAddressSpace().getSize() / 8; + + // Skip first two addresses to get to function pointer table + // BigInteger classOffset = readInteger(program, addr, ptrBytes); + addr = addr.add(ptrBytes); + // BigInteger typeInfo = readInteger(program, addr, ptrBytes); + addr = addr.add(ptrBytes); + Address tableAddr = addr; + + // Reads function pointer table + int funOffset = 0; + BigInteger funPtrInt; + Address funPtrAddr; + while (true) { + // Breaks if we hit another vtable + if (vtables.contains(addr)) + break; + + // read next function pointer + funPtrInt = readInteger(program, addr, ptrBytes); + funPtrAddr = addrFactory.getAddress(addrFactory.getDefaultAddressSpace().getSpaceID(), + funPtrInt.longValue()); + + // The table ends when one of three things is true: + // 1. Reading a zero + if (funPtrInt == BigInteger.ZERO) + break; + + // 2. If there are defined symbols, we are out of the vtable, so break + // if (symbolTable.getSymbols(funPtrAddr).length > 0) + // break; + + // emit fact + export(PredicateFile.VTABLE, className.toString(), addressString(tableAddr), + Integer.toString(funOffset * ptrBytes), funPtrInt.toString()); + // + // 3. if next block is different from ours, break + if (program.getMemory().getBlock(funPtrAddr) == null + || program.getMemory().getBlock(funPtrAddr.add(ptrBytes)) == null) + break; + if (!program.getMemory().getBlock(funPtrAddr) + .equals(program.getMemory().getBlock(funPtrAddr.add(ptrBytes)))) + break; + + addr = addr.add(ptrBytes); + funOffset++; + } + } + + public void processVTables(Program program) { + SymbolIterator vtableSymbols = program.getSymbolTable().getSymbols("vtable"); + while (vtableSymbols.hasNext()) { + Symbol sym = vtableSymbols.next(); + processVTable(program, sym); + } + } + +// private void initializeSet(SymbolTable table) { +// vtables.clear(); +// SymbolIterator iter = table.getSymbols("vtable"); +// while (iter.hasNext()) { +// Symbol sym2 = iter.next(); +// vtables.add(sym2.getAddress()); +// } +// } + + private HighFunction getHighFunction(DecompileResults res, Function func, DecompInterface decompiler) { + HighFunction high = res.getHighFunction(); + if (high == null) { + high = new HighFunction(func, decompiler.getLanguage(), decompiler.getCompilerSpec(), + decompiler.getDataTypeManager()); + int default_extrapop = decompiler.getCompilerSpec().getDefaultCallingConvention().getExtrapop(); + high.grabFromFunction(default_extrapop, false, false); + String id = funcID(func); + export(PredicateFile.HFUNC_ISEXT, id); + } + return high; + } + + private String addressString(Address addr) { + return Long.toString(addr.getOffset()); + } + + private void export(PredicateFile pfile, String key) { + db.add(pfile, key); + } + + private void export(PredicateFile pfile, String key, String value) { + db.add(pfile, key, value); + } + + @SuppressWarnings("unused") + private void export(PredicateFile pfile, String key, String val1, String val2, String val3) { + db.add(pfile, key, val1, val2, val3); + } + + private void exportL(PredicateFile pfile, String key, long value) { + db.add(pfile, key, Long.toString(value)); + } + + private void exportN(PredicateFile label, String key, int index, String value) { + if (value.equals("")) { + db.add(label, key, Integer.toString(index)); + } else { + db.add(label, key, Integer.toString(index), value); + } + } + + private void exportNL(PredicateFile label, String key, int index, long value) { + db.add(label, key, Integer.toString(index), Long.toString(value)); + } + + private void exportPcode(HighFunction hfn, int index, PcodeOp op) { + debug("<" + pcodeID(hfn, op) + "> [index: " + Integer.toString(index) + "]: " + op.toString()); + SequenceNumber sn = op.getSeqnum(); + String outstr = op.toString(); + String id = pcodeID(hfn, op); + if (sn != null) { + outstr = sn.getTarget() + SEP + String.valueOf(index) + SEP + sn.getTime(); + export(PredicateFile.PCODE_TIME, id, Integer.toString(sn.getTime())); + } + export(PredicateFile.PCODE_INDEX, id, String.valueOf(index)); + export(PredicateFile.PCODE_TOSTR, id, funcID(hfn.getFunction()) + SEP + outstr); + export(PredicateFile.PCODE_MNEMONIC, id, op.getMnemonic()); + export(PredicateFile.PCODE_OPCODE, id, Integer.toString(op.getOpcode())); + export(PredicateFile.PCODE_PARENT, id, bbID(hfn, op.getParent())); + export(PredicateFile.PCODE_TARGET, id, addressString(op.getSeqnum().getTarget())); + exportN(PredicateFile.PCODE_INPUT_COUNT, id, op.getNumInputs(), ""); + for (int i = 0; i < op.getNumInputs(); ++i) { + VarnodeAST vni = (VarnodeAST) op.getInput(i); + if (vni != null) { + // OK, this is a little weird, but PTRSUBs with first arg == 0 + // are (usually) global variables at address == second arg + if (op.getMnemonic().equals("PTRSUB") && (i == 0)) { + if (vni.getAddress().getOffset() == 0) { + VarnodeAST next = (VarnodeAST) op.getInput(1); + HighVariable high = next.getHigh(); + if (high != null) { + extraGlobals.put(high, next); + } + } + } + exportN(PredicateFile.PCODE_INPUT, id, i, vnodeID(hfn, vni)); + exportVarnode(hfn, vni); + debug(" input " + i + ": " + vnodeID(hfn, vni)); + } + } + VarnodeAST vno = (VarnodeAST) op.getOutput(); + if (vno != null) { + export(PredicateFile.PCODE_OUTPUT, id, vnodeID(hfn, vno)); + exportVarnode(hfn, vno); + debug(" output: " + vnodeID(hfn, vno)); + } + } + + private void exportVarnode(HighFunction hfn, VarnodeAST vn) { + String id = vnodeID(hfn, vn); + if (!varnodes.add(id)) + return; + export(PredicateFile.VNODE_ADDRESS, id, addressString(vn.getAddress())); + if (vn.isAddress()) { + export(PredicateFile.VNODE_IS_ADDRESS, id); + } + if (vn.isAddrTied()) { + export(PredicateFile.VNODE_IS_ADDRTIED, id); + } + export(PredicateFile.VNODE_PC_ADDRESS, id, addressString(vn.getPCAddress())); + export(PredicateFile.VNODE_DESC, id, vn.toString()); + export(PredicateFile.VNODE_NAME, id, "vn" + String.valueOf(vn.getUniqueId())); + long offset = vn.getOffset(); + export(PredicateFile.VNODE_OFFSET, id, Long.toHexString(offset)); + // if (offset < Long.MAX_VALUE && offset > Long.MIN_VALUE) { + exportL(PredicateFile.VNODE_OFFSET_N, id, offset); + // } + export(PredicateFile.VNODE_SIZE, id, Integer.toString(vn.getSize())); + export(PredicateFile.VNODE_SPACE, id, vn.getAddress().getAddressSpace().getName()); + export(PredicateFile.VNODE_HFUNC, id, hfuncID(hfn)); + HighVariable hv = vn.getHigh(); + if (hv == null) { + // export("VNODE_TOSTR", id, funcID(hfn.getFunction())+SEP+vn.toString()); + export(PredicateFile.VNODE_TOSTR, id, + funcID(hfn.getFunction()) + SEP + vn.getPCAddress().toString() + SEP + vn.toString()); + } else { + if (hv instanceof HighConstant && hv.getDataType() instanceof Pointer) { + if (offset != 0) { + extraGlobals.put(hv, vn); + } + } + // export("VNODE_TOSTR", id, funcID(hfn.getFunction())+hvarName(hfn,hv)); + export(PredicateFile.VNODE_TOSTR, id, + funcID(hfn.getFunction()) + SEP + vn.getPCAddress().toString() + SEP + hvarName(hfn, hv)); + export(PredicateFile.VNODE_HVAR, id, hvarID(hfn, hv)); + exportHighVariable(hfn, hv, true); + } + if (vn.getDef() != null) { + export(PredicateFile.VNODE_DEF, id, pcodeID(hfn, vn.getDef())); + } + } + + private void exportHighVariable(HighFunction hfn, HighVariable hv, boolean dontDescend) { + String id = hvarID(hfn, hv); + export(PredicateFile.HVAR_NAME, id, hvarName(hfn, hv)); + export(PredicateFile.HVAR_SIZE, id, Integer.toString(hv.getSize())); + if (hv instanceof HighGlobal) { + export(PredicateFile.HVAR_CLASS, id, "global"); + } + if (hv instanceof HighLocal) { + export(PredicateFile.HVAR_CLASS, id, "local"); + Address pcAddress = ((HighLocal) hv).getPCAddress(); + if (pcAddress != null) { + export(PredicateFile.HVAR_SCOPE, id, addressString(pcAddress)); + } + } + if (hv instanceof HighConstant) { + export(PredicateFile.HVAR_CLASS, id, "constant"); + } + if (hv instanceof HighOther) { + export(PredicateFile.HVAR_CLASS, id, "other"); + } + DataType dataType = hv.getDataType(); + if (dataType != null) { + export(PredicateFile.HVAR_TYPE, id, dtID(dataType)); + exportType(dataType); + } + if (hv.getSymbol() != null) { + String hsid = hsID(hfn, hv.getSymbol()); + export(PredicateFile.SYMBOL_HVAR, hsid, hvarID(hfn, hv)); + } + if (!dontDescend) { + VarnodeAST representative = (VarnodeAST) hv.getRepresentative(); + if (representative != null) { + export(PredicateFile.HVAR_REPRESENTATIVE, id, vnodeID(hfn, representative)); + exportVarnode(hfn, representative); + } + Varnode[] instances = hv.getInstances(); + for (Varnode varnode : instances) { + exportVarnode(hfn, (VarnodeAST) varnode); + } + } + } + + private void exportType(DataType dataType) { + String id = dtID(dataType); + if (!types.add(id)) + return; + + export(PredicateFile.TYPE_NAME, id, id); + exportN(PredicateFile.TYPE_LENGTH, id, dataType.getLength(), ""); + while (dataType instanceof TypeDef) { + TypeDef typedef = (TypeDef) dataType; + dataType = typedef.getBaseDataType(); + } + if (dataType instanceof Pointer) { + export(PredicateFile.TYPE_POINTER, id); + DataType baseType = ((Pointer) dataType).getDataType(); + if (baseType != null) { + export(PredicateFile.TYPE_POINTER_BASE, id, dtID(baseType)); + exportType(baseType); + } + // else { + // System.err.println("TEST"); + // } + } + if (dataType instanceof Array) { + export(PredicateFile.TYPE_ARRAY, id); + Array arr = (Array) dataType; + export(PredicateFile.TYPE_ARRAY_BASE, id, dtID(arr.getDataType())); + exportN(PredicateFile.TYPE_ARRAY_N, id, arr.getNumElements(), ""); + exportN(PredicateFile.TYPE_ARRAY_ELEMENT_LENGTH, id, arr.getElementLength(), ""); + exportType(arr.getDataType()); + } + if (dataType instanceof Structure) { + export(PredicateFile.TYPE_STRUCT, id); + Structure struct = (Structure) dataType; + exportN(PredicateFile.TYPE_STRUCT_FIELD_COUNT, id, struct.getNumComponents(), ""); + for (int i = 0; i < struct.getNumComponents(); i++) { + DataTypeComponent dtc = struct.getComponent(i); + exportComponent("TYPE_STRUCT", id, i, dtc); + } + } + if (dataType instanceof Union) { + export(PredicateFile.TYPE_UNION, id); + Union union = (Union) dataType; + exportN(PredicateFile.TYPE_UNION_FIELD_COUNT, id, union.getNumComponents(), ""); + for (int i = 0; i < union.getNumComponents(); i++) { + DataTypeComponent dtc = union.getComponent(i); + exportComponent("TYPE_UNION", id, i, dtc); + } + } + if (dataType instanceof FunctionDefinition) { + export(PredicateFile.TYPE_FUNC, id); + FunctionDefinition fd = (FunctionDefinition) dataType; + export(PredicateFile.TYPE_FUNC_RET, id, fd.getReturnType().toString()); + exportType(fd.getReturnType()); + if (fd.hasVarArgs()) { + export(PredicateFile.TYPE_FUNC_VARARGS, id); + } + ParameterDefinition[] arguments = fd.getArguments(); + exportN(PredicateFile.TYPE_FUNC_PARAM_COUNT, id, arguments.length, ""); + for (int i = 0; i < arguments.length; i++) { + exportN(PredicateFile.TYPE_FUNC_PARAM, id, i, arguments[i].toString()); + exportType(arguments[i].getDataType()); + } + } + if (dataType instanceof BooleanDataType) { + export(PredicateFile.TYPE_BOOLEAN, id); + } + if (dataType instanceof AbstractIntegerDataType) { + export(PredicateFile.TYPE_INTEGER, id); + } + if (dataType instanceof AbstractFloatDataType) { + export(PredicateFile.TYPE_FLOAT, id); + } + if (dataType instanceof Enum) { + export(PredicateFile.TYPE_ENUM, id); + } + } + + private void exportComponent(String label, String id, int i, DataTypeComponent dtc) { + String dtcid = dtID(dtc.getDataType()); + exportN(componentPredicates.get(label + "_FIELD"), id, i, dtcid); + exportNL(componentPredicates.get(label + "_OFFSET"), id, i, dtc.getOffset()); + exportNL(componentPredicates.get(label + "_OFFSET_N"), id, i, dtc.getOffset()); + if (dtc.getFieldName() != null && !dtc.getFieldName().isEmpty()) { + exportN(componentPredicates.get(label + "_FIELD_NAME"), id, i, dtc.getFieldName()); + exportN(componentPredicates.get(label + "_FIELD_NAME_BY_OFFSET"), id, dtc.getOffset(), dtc.getFieldName()); + } + exportType(dtc.getDataType()); + } + + boolean isString(String mnemonic) { + if (mnemonic.equals(new String("ds")) || mnemonic.equals(new String("unicode")) + || mnemonic.equals(new String("p_unicode")) || mnemonic.equals(new String("p_string")) + || mnemonic.equals(new String("p_string255")) || mnemonic.equals(new String("mbcs"))) { + return true; + } + return false; + } + + private void exportHighFunction(HighFunction hfn) { + Function f = hfn.getFunction(); + + for (PcodeBlockBasic bb : hfn.getBasicBlocks()) { + String bbid = bbID(hfn, bb); + export(PredicateFile.BB_HFUNC, bbid, hfuncID(hfn)); + if (bb.getStart() != null) { + export(PredicateFile.BB_START, bbid, addressString(bb.getStart())); + } + for (int i = 0; i < bb.getInSize(); i++) { + export(PredicateFile.BB_IN, bbid, bbID(hfn, bb.getIn(i))); + } + for (int i = 0; i < bb.getOutSize(); i++) { + export(PredicateFile.BB_OUT, bbid, bbID(hfn, bb.getOut(i))); + } + if (bb.getOutSize() > 1) { + export(PredicateFile.BB_TOUT, bbid, bbID(hfn, bb.getTrueOut())); + export(PredicateFile.BB_FOUT, bbid, bbID(hfn, bb.getFalseOut())); + } + } + String id = hfuncID(hfn); + export(PredicateFile.HFUNC_NAME, id, hfn.getFunction().getName()); + export(PredicateFile.HFUNC_FUNC, id, funcID(hfn.getFunction())); + export(PredicateFile.HFUNC_TOSTR, id, funcID(hfn.getFunction())); + export(PredicateFile.HFUNC_CSPEC, id, hfn.getCompilerSpec().toString()); + export(PredicateFile.HFUNC_LANG, id, hfn.getLanguage().toString()); + export(PredicateFile.HFUNC_EP, id, addressString(hfn.getFunction().getEntryPoint())); + FunctionPrototype proto = hfn.getFunctionPrototype(); + if (proto != null) { + export(PredicateFile.HFUNC_PROTO, id, funcID(hfn.getFunction())); + exportPrototype(hfn, proto, funcID(hfn.getFunction())); + } + Function thunk = f.getThunkedFunction(false); + if (thunk != null && thunk.isExternal()) { + export(PredicateFile.HFUNC_ISEXT, id); + } + export(PredicateFile.HFUNC_ISEP, id); + + Iterator symbols = hfn.getLocalSymbolMap().getSymbols(); + while (symbols.hasNext()) { + HighSymbol hs = symbols.next(); + String hsid = hsID(hfn, hs); + export(PredicateFile.SYMBOL_HFUNC, hsid, id); + export(PredicateFile.SYMBOL_NAME, hsid, hs.getName()); + HighVariable hv = hs.getHighVariable(); + if (hv != null) { + export(PredicateFile.SYMBOL_HVAR, hsid, hvarID(hfn, hv)); + exportHighVariable(hfn, hv, false); + } + } + } + + public void exportDefinedData(Program p) { + DataIterator dataIter = p.getListing().getDefinedData(p.getMinAddress(), true); + for (Data d : dataIter) { + if (!isString(d.getMnemonicString())) + continue; + Address addr = d.getAddress(); + export(PredicateFile.DATA_STRING, addressString(addr), d.getValue().toString()); + } + } + + private void exportPrototype(HighFunction hfn, FunctionPrototype proto, String id) { + exportN(PredicateFile.PROTO_PARAMETER_COUNT, id, proto.getNumParams(), ""); + for (int i = 0; i < proto.getNumParams(); i++) { + HighSymbol param = proto.getParam(i); + String hsid = hsID(hfn, param); + HighVariable pvar = param.getHighVariable(); + if (pvar != null) { + exportHighVariable(hfn, pvar, true); + export(PredicateFile.SYMBOL_HVAR, hsid, hvarID(hfn, pvar)); + VarnodeAST rep = (VarnodeAST) pvar.getRepresentative(); + export(PredicateFile.HVAR_REPRESENTATIVE, hvarID(hfn, pvar), vnodeID(hfn, rep)); + exportVarnode(hfn, rep); + } else { + // fake out a HighVar + String vnid = hsid + SEP + "0"; + export(PredicateFile.HVAR_NAME, hsid, param.getName()); + export(PredicateFile.SYMBOL_HVAR, hsid, hsid); + export(PredicateFile.SYMBOL_NAME, hsid, param.getName()); + export(PredicateFile.HVAR_REPRESENTATIVE, hsid, vnid); + export(PredicateFile.VNODE_HVAR, vnid, hsid); + export(PredicateFile.VNODE_HFUNC, vnid, hfuncID(param.getHighFunction())); + export(PredicateFile.VNODE_SPACE, vnid, "fake"); + export(PredicateFile.VNODE_NAME, vnid, param.getName() + SEP + "0"); + } + exportN(PredicateFile.PROTO_PARAMETER, id, i, hsid); + export(PredicateFile.PROTO_PARAMETER_DATATYPE, hsid, dtID(param.getDataType())); + exportType(param.getDataType()); + } + /* + * ParameterDefinition[] parameterDefinitions = proto.getParameterDefinitions(); + * if (parameterDefinitions != null) { for (ParameterDefinition def : + * parameterDefinitions) { exportN("PROTO_PARAMETER_DEFINITION", id, + * def.getOrdinal(), def.getName()); export("PROTO_PARAMETER_DATATYPE", + * def.getName(), dtID(def.getDataType())); exportType(def.getDataType()); } } + */ + DataType returnType = proto.getReturnType(); + if (returnType != null) { + export(PredicateFile.PROTO_RETTYPE, id, dtID(returnType)); + exportType(returnType); + } + export(PredicateFile.PROTO_CALLING_CONVENTION, id, hfn.getFunction().getCallingConventionName()); + if (proto.hasNoReturn()) + export(PredicateFile.PROTO_IS_VOID, id); + if (proto.hasThisPointer()) + export(PredicateFile.PROTO_HAS_THIS, id); + if (proto.isVarArg()) + export(PredicateFile.PROTO_IS_VARARG, id); + if (proto.isInline()) + export(PredicateFile.PROTO_IS_INLINE, id); + if (proto.isConstructor()) + export(PredicateFile.PROTO_IS_CONSTRUCTOR, id); + if (proto.isDestructor()) + export(PredicateFile.PROTO_IS_DESTRUCTOR, id); + } + + private void exportPcodeOpSequence(HighFunction hfn, HashSet set) { + Iterator opiter = hfn.getPcodeOps(); + HashSet seenParents = new HashSet(); + HashMap first = new HashMap(); + HashMap last = new HashMap(); + while (opiter.hasNext()) { + PcodeOpAST op = opiter.next(); + PcodeBlockBasic parent = op.getParent(); + if (seenParents.contains(parent)) { + continue; + } + Iterator iterator = parent.getIterator(); + PcodeOp prev = null; + PcodeOp next = null; + while (iterator.hasNext()) { + next = iterator.next(); + if (prev == null && set.contains(next)) { + first.put(parent, next); + } + if (prev != null && set.contains(prev) && set.contains(next)) { + export(PredicateFile.PCODE_NEXT, pcodeID(hfn, prev), pcodeID(hfn, next)); + } + prev = next; + } + if (next != null && set.contains(next)) { + last.put(parent, next); + } + seenParents.add(parent); + } + for (PcodeBlock block : first.keySet()) { + PcodeOpAST ast = (PcodeOpAST) first.get((block)); + export(PredicateFile.BB_FIRST, bbID(hfn, block), pcodeID(hfn, ast)); + } + for (PcodeBlock block : last.keySet()) { + export(PredicateFile.BB_LAST, bbID(hfn, block), pcodeID(hfn, last.get(block))); + } + } + + private String pcodeID(HighFunction hfn, PcodeOp op) { + SequenceNumber sn = op.getSeqnum(); + if (sn != null) { + return hfuncID(hfn) + SEP + sn.getTarget() + SEP + sn.getTime(); + } + return hfuncID(hfn) + SEP + "NO_SEQNUM" + SEP + op.toString(); + } + + private String vnodeID(HighFunction hfn, VarnodeAST vn) { + HighVariable hv = vn.getHigh(); + if (hv == null) { + return hfuncID(hfn) + SEP + Integer.toString(vn.getUniqueId()); + } else { + return hfuncID(hfn) + SEP + hvarName(hfn, hv) + SEP + Integer.toString(vn.getUniqueId()); + } + } + + private String hvarID(HighFunction hfn, HighVariable hv) { + return hfuncID(hfn) + SEP + hvarName(hfn, hv); + } + + private String hvarName(HighFunction hf, HighVariable hv) { + if (hv.getName() == null || hv.getName().equals("UNNAMED")) { + SymbolTable symbolTable = hf.getFunction().getProgram().getSymbolTable(); + Varnode rep = hv.getRepresentative(); + Address addr = rep.getAddress(); + if (extraGlobals.containsKey(hv)) { + VarnodeAST vn = extraGlobals.get(hv); + addr = addr.getNewAddress(vn.getOffset()); + } + Symbol symbol = symbolTable.getPrimarySymbol(addr); + if (symbol == null) { + return addr.toString(); + } + export(PredicateFile.HVAR_CLASS, hfuncID(hf) + SEP + symbol.getName(), "global"); + return symbol.getName(); + } + return hv.getName(); + } + + private String hsID(HighFunction hfn, HighSymbol hs) { + return hfuncID(hfn) + SEP + hs.getName(); + } + + public String funcID(Function fn) { + return fn.getName(true) + "@" + fn.getEntryPoint(); + } + + public String hfuncID(HighFunction fn) { + Function f = fn.getFunction(); + return funcID(f); + } + + private String bbID(HighFunction hfn, PcodeBlock bb) { + if (bb.getStart() != null) { + return hfuncID(hfn) + SEP + bb.hashCode(); + } + return hfuncID(hfn) + SEP + "unknown block"; + } + + private String dtID(DataType dt) { + if (dt.getName() != null) { + return dt.getName().replaceAll(" ", ""); + } + return dt.toString(); + } + + private void debug(String s) { + try { + this.debug.write(s); + this.debug.write('\n'); + } catch (IOException e) { + throw new RuntimeException(e.toString()); + } + // db.add(PredicateFile.PcodeOps, s); + } +} + +//class ResultWriter implements Runnable { +// BlockingQueue q = new ArrayBlockingQueue<>(50); +// +// HighFunctionExporter ex; +// DecompilerConfigurer configurer; +// +// boolean shutDown = false; +// +// private TaskMonitor monitor; +// +// public ResultWriter(HighFunctionExporter ex, DecompilerConfigurer configurer, TaskMonitor tMonitor) { +// this.ex = ex; +// this.configurer = configurer; +// this.monitor = tMonitor; +// } +// +// BlockingQueue getQueue() { +// return q; +// } +// +// @Override +// public void run() { +// try { +// int count = 0; +// while(!(shutDown && q.isEmpty())){ +// monitor.checkCancelled(); +// DecompileResults results = q.take(); +// +// ex.processFunction(results, results.getFunction(), configurer.getInteface()); +// +// count++; +// if (count > 50) { +// ex.writeFacts(); +// count = 0; +// } +// } +// } catch (InterruptedException | IOException | CancelledException e) {} +// } +// +// public void done() { +// shutDown = true; +// } +//} + +public class ExportPCodeForCTADL extends GhidraScript { + + File outputDirectory; + boolean DEBUG = false; + + @Override + protected void run() throws Exception { + + String[] args = getScriptArgs(); + PluginTool tool = state.getTool(); + if (tool == null) { + println("Script is not running in GUI"); + } + if (args.length >= 1) { + outputDirectory = new File(args[0]); + } else { + outputDirectory = askDirectory("Select Directory for Results", "OK"); + } + + // System.setProperty("cpu.core.override", "10"); + // String cpuOverrideString = System.getProperty("cpu.core.override"); + long startTime = System.nanoTime(); + // GThreadPool threadPool = GThreadPool.getSharedThreadPool("Parallel + // Decompiler"); + + HighFunctionExporter ex = new HighFunctionExporter(outputDirectory.getAbsolutePath()); + ex.getDatabase().add(PredicateFile.LANGUAGE, "PCODE"); + ex.getDatabase().add(PredicateFile.PROGRAM_FILE, getProgramFile().toString()); + + DecompilerConfigurer configurer = new DecompilerConfigurer(currentProgram); + + DecompilerCallback callback = new DecompilerCallback(currentProgram, + configurer) { + int count = 0; + + @Override + // This could be done better, when results are available, other decompiler + // results will stall. + public synchronized DecompileResults process(DecompileResults results, TaskMonitor tMonitor) + throws Exception { + // This routine could pass the results to another thread's queue to add to the + // DB + // then there is only one DB writer and no synchronization needed. Decompiler + // threads can go as fast as they can without waiting for writing. + // With the chunking method, all decompiler threads would stall until the write + // was finished and a new chunk of functions could be gotten. + ex.processFunction(results, results.getFunction(), configurer.getInteface()); + count++; + if (count > 20) { + ex.writeFacts(false); + count = 0; + } + return null; + } + }; + + Set toProcess = new HashSet(); + currentProgram.getFunctionManager().getFunctions(true).forEach(f -> { + toProcess.add(f); + }); + currentProgram.getFunctionManager().getExternalFunctions().forEach(f -> { + toProcess.add(f); + }); + List toProcessList = new ArrayList(toProcess); + + Collections.sort(toProcessList, + (o1, o2) -> -Long.compare(o1.getBody().getNumAddresses(), o2.getBody().getNumAddresses())); + + monitor.initialize(toProcess.size()); + + // dump defined data once + ex.exportDefinedData(currentProgram); + ex.writeFacts(true); + + ParallelDecompiler.decompileFunctions(callback, toProcessList, monitor); + + // System.out.println("Done producing"); + + ex.processVTables(currentProgram); + // waiting until the end to write all datatypes TYPE_ may not save much time. + // It could save time if the structures are complicated, so constantly + // traversing + // structure references all over again every 50 functions. Might not be worth + // it. + ex.writeFacts(true); + ex.writeDebug(); + + long endTime = System.nanoTime(); + long duration = (endTime - startTime) / 1000000; + println("total duration: " + Long.toString(duration)); + } + +} diff --git a/Ghidra/Features/DecompilerDependent/ghidra_scripts/ExportPCodeForSingleFunction.java b/Ghidra/Features/DecompilerDependent/ghidra_scripts/ExportPCodeForSingleFunction.java new file mode 100644 index 0000000000..00a8ebf2b4 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/ghidra_scripts/ExportPCodeForSingleFunction.java @@ -0,0 +1,32 @@ +/* ### + * 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. + */ +//Decompile the function at the cursor and its callees, then output facts files corresponding to the pcodes +//@category PCode + +import java.util.HashSet; +import java.util.Set; + +import ghidra.program.model.listing.Function; + +public class ExportPCodeForSingleFunction extends ExportPCodeForCTADL { + + protected Set getFunctionSet() { + Set toProcess = new HashSet(); + toProcess.add(getFunctionContaining(currentAddress)); + return toProcess; + } + +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/help/help/TOC_Source.xml b/Ghidra/Features/DecompilerDependent/src/main/help/help/TOC_Source.xml index 3ee1228742..8c3f6040dd 100644 --- a/Ghidra/Features/DecompilerDependent/src/main/help/help/TOC_Source.xml +++ b/Ghidra/Features/DecompilerDependent/src/main/help/help/TOC_Source.xml @@ -53,4 +53,10 @@ target="help/topics/DecompilerTextFinderPlugin/Decompiler_Text_Finder.html" /> + + + + diff --git a/Ghidra/Features/DecompilerDependent/src/main/help/help/topics/DecompilerTaint/DecompilerTaint.html b/Ghidra/Features/DecompilerDependent/src/main/help/help/topics/DecompilerTaint/DecompilerTaint.html new file mode 100644 index 0000000000..3868a263b4 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/help/help/topics/DecompilerTaint/DecompilerTaint.html @@ -0,0 +1,329 @@ + + + +Decompiler Window + + + + + + + +
+

+Decompiler Taint Operations

+ +

+ Taint-tracking is the ability to capture the flow of data through a program by following transfers + from one variable to another. Often, this involves specifying where the data originates (a source) + and which endpoints are of interest (sinks). In Ghidra, taint-tracking leverages external engines + which rely on the SSA-nature of Ghidra's PCode to describe varnode-to-varnode flows. +

+

+ Taint-tracking is a four-step process. First, the PCode underlying the target program must be exported + to a directory for subsequent "indexing". Second, the program is indexed creating an index database. + These are one-time actions and need only be re-executed when you change programs or modify the target + program's PCode in a substantive way. Given a database, any number of queries may be processed using + the index. Step three, making a query generally includes marking sources and sinks, and then executing + the query. Last, the results of the query may be selectively re-applied as markup on the decompilation/disassembly. +

+

+ The first two steps are activated in the menus under Tools → Source-Sink. Their options + are accessible via Edit → Tool Options under Options/Decompiler/Taint. The third step is controlled by + the pop-up menus (or associated keyboard actions) and the toolbar items within the Decompiler window. + Pop-up menus on the results table control how the results are applied. + +

+ +
+

+ Initialization Actions (Tools → Source-Sink)

+ +
+

+ Delete Facts and Index

+

+ Deletes any pre-existing data (facts and database) from the directories specified under + Taint Options. +

+
+ +
+

+ Export PCode Facts

+

+ Run an engine-specific ghidra script to export the PCode for the current program as a set + of ASCII fact files to be consumed by the engine. (For CTADL, our default engine, this + script is ExportPCodeForCTADL.java.) +

+
+ +
+

+ Initialize Program Index

+

+ Converts the directory of PCode facts into an indexed database for future queries. +

+
+ +
+

+ Re-export Function Facts

+

+ Updates the existing fact set for the current function, which may be useful if the + decompilation has been improved during the course of analysis. (Does require re-indexing + the database.) +

+
+ +
+

+ Tool Options (Options/Decompiler/Taint)

+ +

+ These options govern the locations for various elements of the taint engine. +

+ +
+

+ Directories.Engine

+

+ Path to the executable responsible for the taint engine logic. +

+
+ +
+

+ Directories.Facts

+

+ Directory where PCode facts will be written. +

+
+ +
+

+ Directories.Output

+

+ Directory where database index and queries will be written. +

+
+ +
+

+ Query.Current Query

+

+ The file containing the most recent query. +

+
+ +
+

+ Query.Index

+

+ Name of the file that contains the database produced by the "Index" operation. +

+
+ +
+

+ Highlight Color

+

+ The color used for taint highlights in the decompiler. +

+
+ +
+

+ Highlight Style

+

+ "All", "Labels", or "Default". +

+
+ +
+

+ Output Format

+

+ Currently always "sarif+all". +

+
+ +
+

+ Use all access paths

+

+ Use all access paths for sink/source variables. +

+
+ +
+

+ Decompiler Pop-up Menu and Keyboard Actions

+ +

+ The following actions appear in the Taint sub-menu of the Decompiler's context menu, + accessed by right-clicking a token. The pop-up menu is context sensitive and + the type of token in particular determines what actions are available. + The token clicked provides a local context for the action and may be used to pinpoint the exact + variable or operation affected. +

+ +
+

+ Source

+

+ Mark the selected varnode as a taint source. +

+
+ +
+

+ Source (Symbol)

+

+ Mark the symbol associated with the selected varnode as a taint source. +

+
+ +
+

+ Sink

+

+ Mark the selected varnode as a taint sink (i.e. the endpoint). +

+
+ +
+

+ Sink (Symbol)

+

+ Mark the symbol associated with the selected varnode as a taint sink. +

+
+ +
+

+ Gate

+

+ Mark the selected varnode as a gate (i.e. taint will not pass through this node). +

+
+ +
+

+ Clear

+

+ Remove the source, sink, and gate markers, and existing taint. +

+
+ +
+

+ Slice Tree

+

+ Launch a call-tree style viewer for the slices for the selected token. +

+
+ +
+

+ Decompiler Toolbar Actions

+ +

+ These actions apply after the source and sinks have been chosen. +

+ +
+

+ Run taint query

+

+ Uses the defined source, sinks, and gates to compose and execute a query. Input + may include parameters, stack variables, variables associated with registers, or + "dynamic" variables. Queries require an index database generated from PCode. +

+
+ +
+

+ Run default taint query

+

+ Use pre-defined sources and sinks to execute the engine's default query. + (Ignores the sources and sinks specified by the user and tries to apply whatever + the engine considers the de-facto set of sources/sinks - which may be undefined + for a given target.) +

+
+ +
+

+ Run custom taint query

+

+ Executes the query referenced in option without rebuilding it based on sources, sinks, etc. + Unedited, this will re-execute the last query, but the file can be modified by hand to reflect + any query you're interested in. +

+
+ +
+

+ Load SARIF file

+

+ Loads a raw SARIF file into the results table. +

+
+ +
+

+ Show label table

+

+ Displays the current set of source, sink, and gate markers. +

+
+ +
+

+ Results Pop-up Menu

+ +

+ These actions appear in the context menu of the Query Results table and transfer the selected results to the decompiler/disassembly. +

+ +
+

+ Add To Program

+

+ Applies SARIF results to the current progam generically, based on the current set of handlers. +

+
+ +
+

+ Apply taint

+

+ Highlight the varnodes which have been tainted. +

+
+ +
+

+ Clear taint

+

+ Clear taint matching the selected rows from the Decompiler listing. +

+
+ +
+

+ Make Selection

+

+ Create a selection from the selected addresses. +

+
+ +
+ +
+
+ + +
+ diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/AbstractTaintState.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/AbstractTaintState.java new file mode 100644 index 0000000000..15ed6489aa --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/AbstractTaintState.java @@ -0,0 +1,476 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint; + +import java.io.*; +import java.lang.ProcessBuilder.Redirect; +import java.nio.file.Path; +import java.util.*; + +import com.contrastsecurity.sarif.SarifSchema210; +import com.google.gson.JsonSyntaxException; + +import docking.widgets.filechooser.GhidraFileChooser; +import docking.widgets.filechooser.GhidraFileChooserMode; +import ghidra.app.decompiler.ClangToken; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSet; +import ghidra.program.model.listing.Program; +import ghidra.program.model.pcode.HighVariable; +import ghidra.program.model.pcode.PcodeException; +import ghidra.util.Msg; +import sarif.SarifService; + +/** + * Container for all the decompiler elements the users "selects" via the menu. + * This data is used to build queries. + */ +public abstract class AbstractTaintState implements TaintState { + + public static String ENGINE_NAME = ""; + + private Set sources = new HashSet<>(); + private Set sinks = new HashSet<>(); + private Set gates = new HashSet<>(); + + // Sets used for highlighting. + private AddressSet taintAddressSet = new AddressSet(); + private Map> taintVarnodeMap = new HashMap<>(); + + /// private QueryDataFrame currentQueryData; + private SarifSchema210 currentQueryData; + + protected TaintOptions taintOptions; + private TaintPlugin plugin; + + private boolean cancellation; + + public AbstractTaintState(TaintPlugin plugin) { + this.plugin = plugin; + } + + public abstract void buildQuery(List param_list, Path engine, File indexDBFile, + String index_directory); + + @Override + public abstract void buildIndex(List param_list, String engine_path, String facts_path, + String index_path); + + protected abstract void writeRule(PrintWriter writer, TaintLabel mark, boolean isSource); + + protected abstract void writeGate(PrintWriter writer, TaintLabel mark); + + @Override + public boolean wasCancelled() { + return this.cancellation; + } + + @Override + public void setCancellation(boolean status) { + this.cancellation = status; + } + + @Override + public Set getTaintLabels(MarkType mtype) { + return switch (mtype) { + case SOURCE -> sources; + case SINK -> sinks; + case GATE -> gates; + default -> new HashSet<>(); + }; + } + + @Override + public TaintLabel toggleMark(MarkType mtype, ClangToken token) throws PcodeException { + TaintLabel labelToToggle = new TaintLabel(mtype, token); + + Msg.info(this, "labelToToggle: " + labelToToggle); + Set marks = getTaintLabels(mtype); + + return updateMarks(labelToToggle, marks); + } + + private TaintLabel updateMarks(TaintLabel tlabel, Set marks) { + for (TaintLabel existingLabel : marks) { + if (existingLabel.equals(tlabel)) { + existingLabel.toggle(); + plugin.getTool().contextChanged(plugin.getDecompilerProvider()); + return existingLabel; + } + } + + marks.add(tlabel); + plugin.getTool().contextChanged(plugin.getDecompilerProvider()); + return tlabel; + } + + /** + * Predicate indicating the presence of one or more sources in the source set; + * this is used to determine state validity. + * + * The source set MUST BE NON-EMPTY for a query to be executed. + */ + @Override + public boolean isValid() { + return activeSources() || activeSinks(); + } + + private boolean activeSinks() { + for (TaintLabel label : sinks) { + if (label.isActive()) + return true; + } + return false; + } + + private boolean activeSources() { + for (TaintLabel label : sources) { + if (label.isActive()) + return true; + } + return false; + } + + /** + * For the label table it doesn't matter which are active or inactive. We want + * to see all of them and the button should be active when we have any in these + * sets. + */ + @Override + public boolean hasMarks() { + return !sources.isEmpty() || !sinks.isEmpty() || !gates.isEmpty(); + } + + /** + * Write the datalog query file that the engine will use to generate results. + * + *

+ * The artifacts (e.g., sources) that are used in the datalog query are those + * selected by the user via the menu. + * + *

+ * @param queryTextFile - file containing the query + * @return success + * @throws Exception - on write + */ + public boolean writeQueryFile(File queryTextFile) throws Exception { + + PrintWriter writer = new PrintWriter(queryTextFile); + writer.println("#include \"pcode/taintquery.dl\""); + + for (TaintLabel mark : sources) { + if (mark.isActive()) { + writeRule(writer, mark, true); + } + } + + writer.println(""); + + for (TaintLabel mark : sinks) { + if (mark.isActive()) { + // CAREFUL note the "false" + writeRule(writer, mark, false); + } + } + + if (!gates.isEmpty()) { + writer.println(""); + for (TaintLabel mark : gates) { + if (mark.isActive()) { + writeGate(writer, mark); + } + } + } + + writer.flush(); + writer.close(); + + plugin.consoleMessage("Wrote Query File: " + queryTextFile); + + return true; + } + + /** + * Build the query string, save it to a file the users selects, and run the + * engine using the index and the query that is saved to the file. + */ + @Override + public boolean queryIndex(Program program, PluginTool tool, QueryType queryType) { + + if (queryType.equals(QueryType.SRCSINK) && !isValid()) { + Msg.showWarn(this, tool.getActiveWindow(), getName() + " Query Warning", + getName() + " query cannot be performed because there are no sources or sinks."); + return false; + } + + List param_list = new ArrayList(); + File queryFile = null; + taintOptions = plugin.getOptions(); + + try { + + // Make sure we can access and execute the engine binary. + Path engine = Path.of(taintOptions.getTaintEnginePath()); + File engine_file = engine.toFile(); + + if (!engine_file.exists() || !engine_file.canExecute()) { + plugin.consoleMessage("The " + getName() + " binary (" + + engine_file.getCanonicalPath() + ") cannot be found or executed."); + engine_file = getFilePath(taintOptions.getTaintEnginePath(), + "Select the " + getName() + " binary"); + if (engine_file == null) { + plugin.consoleMessage( + "No " + getName() + " engine has been specified; exiting query function."); + return false; + } + } + + plugin.consoleMessage("Using " + getName() + " binary: " + engine_file.toString()); + + Path index_directory = Path.of(taintOptions.getTaintOutputDirectory()); + Path indexDBPath = Path.of(taintOptions.getTaintOutputDirectory(), + taintOptions.getTaintIndexDBName(program.getName())); + + File indexDBFile = indexDBPath.toFile(); + plugin.consoleMessage("Attempting to use index: " + indexDBFile.toString()); + + if (!indexDBFile.exists()) { + plugin.consoleMessage("The index database for the binary named: " + + program.getName() + " does not exist; create it first."); + return false; + } + + plugin.consoleMessage("Using index database: " + indexDBFile); + + switch (queryType) { + case SRCSINK: + // Generate a datalog query file based on the selected source, sink, etc. data. + // This file can be overwritten + Path queryPath = Path.of(taintOptions.getTaintOutputDirectory(), + taintOptions.getTaintQueryDLName()); + queryFile = queryPath.toFile(); + writeQueryFile(queryFile); + plugin.consoleMessage("The datalog query file: " + queryFile.toString() + + " has been written and can be referenced later if needed."); + break; + case DEFAULT: + plugin.consoleMessage("Performing default query."); + break; + case CUSTOM: + plugin.consoleMessage("Performing custom query."); + break; + default: + plugin.consoleMessage("Unknown query type."); + } + + buildQuery(param_list, engine, indexDBFile, index_directory.toString()); + + if (queryType.equals(QueryType.SRCSINK) || queryType.equals(QueryType.CUSTOM)) { + // The datalog that specifies the query. + if (queryType.equals(QueryType.CUSTOM)) { + Path queryPath = Path.of(taintOptions.getTaintOutputDirectory(), + taintOptions.getTaintQueryDLName()); + queryFile = queryPath.toFile(); + } + param_list.add(queryFile.getAbsolutePath()); + } + + Msg.info(this, "Query Param List: " + param_list.toString()); + try { + ProcessBuilder pb = new ProcessBuilder(param_list); + pb.directory(new File(taintOptions.getTaintOutputDirectory())); + pb.redirectError(Redirect.INHERIT); + Process p = pb.start(); + + switch (taintOptions.getTaintOutputForm()) { + case "sarif+all": + readQueryResultsIntoDataFrame(program, p.getInputStream()); + break; + default: + } + // We wait for the process to finish after starting to read the input stream, + // otherwise waitFor() might wait for a running process trying to write to + // a filled output buffer. This causes waitFor() to wait indefinitely. + p.waitFor(); + + } + catch (InterruptedException e) { + Msg.error(this, e.getMessage()); + return false; + } + + } + catch (Exception e) { + Msg.error(this, "Problems running query: " + e); + return false; + } + return true; + } + + /** + * @param is the input stream (SARIF json) from the process builder that runs the engine + */ + private void readQueryResultsIntoDataFrame(Program program, InputStream is) { + + StringBuilder sb = new StringBuilder(); + String line = null; + taintAddressSet.clear(); + taintVarnodeMap.clear(); + + try { + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is)); + + while ((line = bufferedReader.readLine()) != null) { + sb.append(line); + } + bufferedReader.close(); + } + catch (IOException e) { + plugin.consoleMessage("IO Error Reading Query Results from Process: " + e.getMessage()); + } + + try { + currentQueryData = plugin.getSarifService().readSarif(sb.toString()); + } + catch (JsonSyntaxException e) { + plugin.consoleMessage( + "Error in JSON in Sarif Output from " + getName() + ": " + e.getMessage()); + e.printStackTrace(); + } + catch (IOException e) { + plugin.consoleMessage( + "IO Exception Parsing JSON Sarif Output from " + getName() + ": " + e.getMessage()); + e.printStackTrace(); + } + } + + private File getFilePath(String initial_directory, String title) { + + GhidraFileChooser chooser = new GhidraFileChooser(null); + chooser.setCurrentDirectory(new File(initial_directory)); + chooser.setFileSelectionMode(GhidraFileChooserMode.FILES_ONLY); + chooser.setTitle(title); + File selectedFile = chooser.getSelectedFile(); + if (selectedFile != null) { + return selectedFile; + } + + return selectedFile; + } + + /** + * Read and parse a file that has Sarif JSON in it and set the addresses in the + * listing that are tainted so they are highlighted. + * + * @param sarifFile a file that contains SARIF JSON data. + */ + @Override + public void loadTaintData(Program program, File sarifFile) { + + try { + //SarifTaintGraphRunHandler.setEnabled(true); + SarifService sarifService = plugin.getSarifService(); + SarifSchema210 sarif_data = sarifService.readSarif(sarifFile); + sarifService.showSarif(sarifFile.getName(), sarif_data); + } + catch (JsonSyntaxException e) { + plugin.consoleMessage( + "Syntax error in JSON taint data " + getName() + ": " + e.getMessage()); + e.printStackTrace(); + } + catch (IOException e) { + plugin.consoleMessage( + "IO Exception parsing in JSON taint data " + getName() + ": " + e.getMessage()); + e.printStackTrace(); + } + } + + @Override + public void setTaintAddressSet(AddressSet aset) { + taintAddressSet = aset; + } + + @Override + public AddressSet getTaintAddressSet() { + return taintAddressSet; + } + + @Override + public void augmentAddressSet(ClangToken token) { + Address addr = token.getMinAddress(); + if (addr != null) { + taintAddressSet.add(addr); + } + } + + @Override + public void setTaintVarnodeMap(Map> vmap) { + taintVarnodeMap = vmap; + } + + @Override + public Map> getTaintVarnodeMap() { + return taintVarnodeMap; + } + + @Override + public void clearTaint() { + Msg.info(this, "TaintState: clearTaint() - clearing address set"); + taintAddressSet.clear(); + taintVarnodeMap.clear(); + } + + @Override + public void clearMarkers() { + sources.clear(); + sinks.clear(); + gates.clear(); + } + + @Override + public boolean isSink(HighVariable hvar) { + for (TaintLabel mark : sinks) { + if (mark.getHighVariable() != null && mark.getHighVariable().equals(hvar)) { + return true; + } + } + return false; + } + + @Override + public SarifSchema210 getData() { + if (currentQueryData == null) { + Msg.warn(this, "attempt to retrieve a sarif data frame that is null."); + } + return currentQueryData; + } + + @Override + public void clearData() { + currentQueryData = null; + } + + @Override + public TaintOptions getOptions() { + return plugin.getOptions(); + } + + public String getName() { + return ENGINE_NAME; + } + +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/CreateTargetIndexTask.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/CreateTargetIndexTask.java new file mode 100644 index 0000000000..0a5d58867e --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/CreateTargetIndexTask.java @@ -0,0 +1,169 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint; + +import java.io.File; +import java.io.IOException; +import java.lang.ProcessBuilder.Redirect; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import docking.widgets.filechooser.GhidraFileChooser; +import docking.widgets.filechooser.GhidraFileChooserMode; +import ghidra.app.services.ConsoleService; +import ghidra.framework.options.ToolOptions; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Program; +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.Task; +import ghidra.util.task.TaskMonitor; + +public class CreateTargetIndexTask extends Task { + + private TaintPlugin plugin; + private Program program; + + public CreateTargetIndexTask(TaintPlugin plugin, Program program) { + super("Create Target Index Action", true, true, false, false); + this.plugin = plugin; + this.program = program; + } + + private File getFilePath(String initial_directory, String title) { + + GhidraFileChooser chooser = new GhidraFileChooser(null); + chooser.setCurrentDirectory(new File(initial_directory)); + chooser.setFileSelectionMode(GhidraFileChooserMode.FILES_ONLY); + chooser.setTitle(title); + File selectedFile = chooser.getSelectedFile(); + if (selectedFile != null) { + return selectedFile; + } + + return selectedFile; + } + + private String getDirectoryPath(String path, String title) { + + GhidraFileChooser chooser = new GhidraFileChooser(null); + chooser.setCurrentDirectory(new File(path)); + chooser.setFileSelectionMode(GhidraFileChooserMode.DIRECTORIES_ONLY); + chooser.setTitle(title); + File selectedDir = chooser.getCurrentDirectory(); + if (selectedDir != null && !chooser.wasCancelled()) { + return selectedDir.getAbsolutePath(); + } + return null; + } + + private boolean indexProgram(String engine_path, String facts_path, String index_directory) { + + boolean rvalue = true; + + List param_list = new ArrayList(); + plugin.getTaintState().buildIndex(param_list, engine_path, facts_path, index_directory); + Msg.info(this, "Index Param List: " + param_list.toString()); + + try { + ProcessBuilder pb = new ProcessBuilder(param_list); + pb.directory(new File(facts_path)); + pb.redirectError(Redirect.INHERIT); + Process p = pb.start(); + p.waitFor(); + + } + catch (IOException | InterruptedException e) { + Msg.error(this, "Problems running index: " + e); + rvalue = false; + } + + return rvalue; + } + + @Override + public void run(TaskMonitor monitor) throws CancelledException { + + monitor.initialize(program.getFunctionManager().getFunctionCount()); + PluginTool tool = plugin.getTool(); + ConsoleService consoleService = tool.getService(ConsoleService.class); + + ToolOptions options = tool.getOptions("Decompiler"); + + // This will pull all the Taint options default set in the plugin. These could also be set in Ghidra configuration files. + String enginePathName = options.getString(TaintOptions.OP_KEY_TAINT_ENGINE_PATH, + "/home/user/workspace/engine_binary"); + String factsDirectory = + options.getString(TaintOptions.OP_KEY_TAINT_FACTS_DIR, "/tmp/export"); + String indexDirectory = + options.getString(TaintOptions.OP_KEY_TAINT_OUTPUT_DIR, "/tmp/output"); + String indexDBName = options.getString(TaintOptions.OP_KEY_TAINT_DB, "ctadlir.db"); + + // builds a custom db name with the string of the binary embedded in it for better identification. + indexDBName = TaintOptions.makeDBName(indexDBName, program.getName()); + + Path enginePath = Path.of(enginePathName); + File engineFile = enginePath.toFile(); + + if (!engineFile.exists() || !engineFile.canExecute()) { + Msg.info(this, "The engine binary (" + engineFile.getAbsolutePath() + + ") cannot be found or executed."); + engineFile = getFilePath(enginePathName, "Select the engine binary"); + } + + consoleService.addMessage("Create Index", "using engine at: " + enginePath.toString()); + + Path factsPath = Path.of(factsDirectory); + + if (!factsPath.toFile().exists() || !factsPath.toFile().isDirectory()) { + Msg.info(this, "Facts Path: " + factsPath.toString() + " does not exist."); + factsDirectory = getDirectoryPath(factsDirectory, + "Select full path to the directory containing the FACTS files"); + if (factsDirectory == null) { + Msg.info(this, "User cancelled operation; existing script."); + return; + } + Msg.info(this, "Using .facts files in: " + factsDirectory); + options.setString(TaintOptions.OP_KEY_TAINT_FACTS_DIR, factsDirectory); + } + else { + factsDirectory = factsPath.toString(); + } + + consoleService.addMessage("Create Index", "using facts path: " + factsDirectory); + Path indexPath0 = Path.of(indexDirectory); + Path indexPath = Path.of(indexDirectory, indexDBName); + + if (!indexPath0.toFile().exists() || !indexPath0.toFile().isDirectory()) { + // the index has already been build. Use it? + Msg.info(this, "Index Path: " + indexPath.toString() + " does not exist."); + indexDirectory = getDirectoryPath(indexDirectory, + "Select full path to the directory to containthe INDEX file"); + indexPath = Path.of(indexDirectory, indexDBName); + options.setString(TaintOptions.OP_KEY_TAINT_OUTPUT_DIR, indexDirectory); + } + + consoleService.addMessage("Create Index", "using index path: " + indexDirectory); + Msg.info(this, "Engine Path: " + engineFile.toString()); + Msg.info(this, "Facts Path: " + factsDirectory); + Msg.info(this, "Index Path: " + indexDirectory); + + boolean success = indexProgram(engineFile.toString(), factsDirectory, indexDirectory); + consoleService.addMessage("Create Index", "indexing status: " + success); + monitor.clearCancelled(); + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/LabelHighlighterMatcher.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/LabelHighlighterMatcher.java new file mode 100644 index 0000000000..9776e31a2a --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/LabelHighlighterMatcher.java @@ -0,0 +1,61 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint; + +import java.awt.Color; +import java.util.HashMap; +import java.util.Map; + +import ghidra.app.decompiler.CTokenHighlightMatcher; +import ghidra.app.decompiler.ClangToken; + +public class LabelHighlighterMatcher implements CTokenHighlightMatcher { + + TaintCTokenHighlighterPalette palette; + TaintProvider taintProvider; + + // stores previously established colors for consistency. + // the key should be some unique String that is WHAT you are using to + // define consistent highlighting. + Map cachedHighlights; + + final int nextColorIndex; + + public LabelHighlighterMatcher(TaintProvider taintProvider, TaintCTokenHighlighterPalette palette) { + this.taintProvider = taintProvider; + this.palette = palette; + this.nextColorIndex = 0; + this.cachedHighlights = new HashMap<>(); + } + + /** + * The basic method clients must implement to determine if a token should be + * highlighted. Returning a non-null Color will trigger the given token to be + * highlighted. + * + * @param token the token + * @return the highlight color or null + */ + @Override + public Color getTokenHighlight(ClangToken token) { + return null; + } + + public void clearCache() { + cachedHighlights.clear(); + } + +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/PurgeIndexTask.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/PurgeIndexTask.java new file mode 100644 index 0000000000..ebeb8f4b3e --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/PurgeIndexTask.java @@ -0,0 +1,78 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint; + +import java.io.File; +import java.nio.file.Path; + +import ghidra.app.services.ConsoleService; +import ghidra.framework.options.ToolOptions; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Program; +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.Task; +import ghidra.util.task.TaskMonitor; + +public class PurgeIndexTask extends Task { + + private TaintPlugin plugin; + private Program program; + + public PurgeIndexTask(TaintPlugin plugin, Program program) { + super("Purge Index Action", true, true, false, false); + this.plugin = plugin; + this.program = program; + } + + @Override + public void run(TaskMonitor monitor) throws CancelledException { + + PluginTool tool = plugin.getTool(); + ConsoleService consoleService = tool.getService(ConsoleService.class); + + ToolOptions options = tool.getOptions("Decompiler"); + + // This will pull all the Taint options default set in the plugin. These could also be set in Ghidra configuration files. + String facts_directory = + options.getString(TaintOptions.OP_KEY_TAINT_FACTS_DIR, "/tmp/export"); + String index_directory = + options.getString(TaintOptions.OP_KEY_TAINT_OUTPUT_DIR, "/tmp/output"); + String index_db_name = options.getString(TaintOptions.OP_KEY_TAINT_DB, "ctadlir.db"); + + // builds a custom db name with the string of the binary embedded in it for better identification. + index_db_name = TaintOptions.makeDBName(index_db_name, program.getName()); + + Path facts_path = Path.of(facts_directory); + File facts = facts_path.toFile(); + if (facts.exists() && facts.isDirectory()) { + Msg.info(this, "Deleting contents: " + facts_path.toString()); + File[] files = facts.listFiles(); + for (File f : files) { + f.delete(); + } + } + consoleService.addMessage("Purge Index", "using facts path: " + facts_path); + + Path index_path = Path.of(index_directory, index_db_name); + File index = index_path.toFile(); + if (index.exists() && !index.isDirectory()) { + Msg.info(this, "Deleting index: " + index_path.toString()); + index.delete(); + } + consoleService.addMessage("Purge Index", "using index path: " + index_path); + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/RunPCodeExportScriptTask.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/RunPCodeExportScriptTask.java new file mode 100644 index 0000000000..dd1136fd09 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/RunPCodeExportScriptTask.java @@ -0,0 +1,106 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint; + +import java.io.File; +import java.nio.file.Path; + +import docking.widgets.filechooser.GhidraFileChooser; +import docking.widgets.filechooser.GhidraFileChooserMode; +import ghidra.app.script.GhidraScript; +import ghidra.app.script.GhidraState; +import ghidra.app.services.ConsoleService; +import ghidra.framework.options.ToolOptions; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Program; +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.Task; +import ghidra.util.task.TaskMonitor; + +public class RunPCodeExportScriptTask extends Task { + + private PluginTool tool; + private GhidraScript script; + private GhidraState currentState; + private ConsoleService console; + private String scriptName; + + public RunPCodeExportScriptTask(PluginTool tool, GhidraScript script, GhidraState currentState, + ConsoleService console) { + super(script.getSourceFile().getName(), true, false, false); + this.tool = tool; + this.script = script; + this.scriptName = script.getSourceFile().getName(); + this.console = console; + this.currentState = currentState; + } + + @Override + public void run(TaskMonitor monitor) { + try { + Thread.currentThread().setName(scriptName); + + ToolOptions options = tool.getOptions("Decompiler"); + String facts_directory = + options.getString(TaintOptions.OP_KEY_TAINT_FACTS_DIR, "/tmp/export"); + Path facts_path = Path.of(facts_directory); + if (!facts_path.toFile().exists() || !facts_path.toFile().isDirectory()) { + Msg.info(this, "Facts Path: " + facts_path.toString() + " does not exists."); + facts_directory = getDirectoryPath(facts_directory, + "Select full path to the directory containing the facts files"); + if (facts_directory == null) { + Msg.info(this, "User cancelled operation; existing script."); + return; + } + Msg.info(this, "Using .facts files in: " + facts_directory); + options.setString(TaintOptions.OP_KEY_TAINT_FACTS_DIR, facts_directory); + } + script.setScriptArgs(new String[] { facts_directory }); + + console.addMessage(scriptName, "Running..."); + script.execute(currentState, monitor, console.getStdOut()); + console.addMessage(scriptName, "Finished!"); + } + catch (CancelledException e) { + console.addErrorMessage(scriptName, "User cancelled script."); + } + catch (Exception e) { + if (!monitor.isCancelled()) { + Msg.showError(this, null, getTaskTitle(), "Error running script: " + scriptName + + "\n" + e.getClass().getName() + ": " + e.getMessage(), e); + console.addErrorMessage("", "Error running script: " + scriptName); + console.addException(scriptName, e); + } + } + } + + private String getDirectoryPath(String path, String title) { + GhidraFileChooser chooser = new GhidraFileChooser(null); + chooser.setCurrentDirectory(new File(path)); + chooser.setFileSelectionMode(GhidraFileChooserMode.DIRECTORIES_ONLY); + chooser.setTitle(title); + File selectedDir = chooser.getSelectedFile(); + if (selectedDir != null && !chooser.wasCancelled()) { + return selectedDir.getAbsolutePath(); + } + return null; + } + + public Program getProgram() { + return script.getCurrentProgram(); + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintCTokenHighlighterPalette.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintCTokenHighlighterPalette.java new file mode 100644 index 0000000000..b8c0c40029 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintCTokenHighlighterPalette.java @@ -0,0 +1,105 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint; + +import java.awt.Color; + +public class TaintCTokenHighlighterPalette { + + private Color uninitializedColor; + + // The awt.Color class uses sRGB colors. + private Color[] colors; + + public TaintCTokenHighlighterPalette(int sz) { + // Using the constructor with ints. + uninitializedColor = new Color(192, 192, 192); + colors = new Color[sz]; + setGYRColorRange(); + } + + public int getSize() { + if (colors == null) { + return 0; + } + return colors.length; + } + + public Color getDefaultColor() { + return uninitializedColor; + } + + /** + * The method that calls this will need to transform the decimal value into an integer in the appropriate range. + * That will be based on the sz parameter supplied to this constructor. + * @param i - index + * @return color + */ + public Color getColor(int i) { + if (i < 0 || i >= colors.length) { + // this will be a good way to detect errors. + // if we want high and low to be represented by colors[0] and colors[colors.length-1] change this. + return uninitializedColor; + } + return colors[i]; + } + + /** + * Establish the indexed color range; this is done 1 time. + *

+ *

  • + * Index 0: Green + *
  • + * Index colors.length / 2: Yellow + *
  • + * Index colors.length: Red + *
+ *

+ *

  • + * Red: 1.0,0.0,0.0 + *
  • + * Green: 0.0, 1.0, 0.0 + *
  • + * Yellow: 1.0, 1.0, 0.0 + *
+ */ + private void setGYRColorRange() { + + float red = 0.0f; + float green = 1.0f; + float blue = 0.0f; + + // since we are stepping through 2 colors, we double the rate of the step + float step = (1.0f / (colors.length - 1)) * 10.0f; + + // red stays constant; green grows from 0.0 -> 1.0; + for (int i = 0; i < colors.length; ++i) { + + colors[i] = new Color(red, green, blue); + if (green == 1.0 && red < 1.0) { + red += step; + if (red > 1.0f) + red = 1.0f; + } + else { + // initially, green increases and the others stay constant. + green -= step; + if (green < 0.0) + green = 0.0f; + } + } + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintDecompilerMarginProvider.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintDecompilerMarginProvider.java new file mode 100644 index 0000000000..98690229b6 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintDecompilerMarginProvider.java @@ -0,0 +1,168 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint; + +import java.awt.*; +import java.math.BigInteger; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.swing.Icon; +import javax.swing.JPanel; + +import docking.widgets.fieldpanel.LayoutModel; +import docking.widgets.fieldpanel.listener.IndexMapper; +import docking.widgets.fieldpanel.listener.LayoutModelListener; +import generic.theme.GIcon; +import ghidra.app.decompiler.ClangLine; +import ghidra.app.decompiler.component.margin.DecompilerMarginProvider; +import ghidra.app.decompiler.component.margin.LayoutPixelIndexMap; +import ghidra.program.model.listing.Program; + +@SuppressWarnings("serial") +public class TaintDecompilerMarginProvider extends JPanel + implements DecompilerMarginProvider, LayoutModelListener { + + // TODO: Extend the ClangLine class and include an equals and hashCode method so it + // works properly with sets. This will be better than strings because it could + // include line number and deconflict when there are two IDENTICAL lines in the source + // code. + + private LayoutModel model; + private LayoutPixelIndexMap pixmap; + + private final TaintPlugin plugin; + + // NOTE: ClangLine doesn't have an equals or hashCode method, so we use strings. + private Set sourceAddresses = new HashSet<>(); + private Set sinkAddresses = new HashSet<>(); + private Set gateAddresses = new HashSet<>(); + + // These icon property names go in your Theme properties files in the /.ghidra directory tree + // The format: icon.decompiler.taint.source = /path/to/the/icon.png + + private Icon sourceIcon = new GIcon("icon.plugin.scriptmanager.run"); + private Icon sinkIcon = new GIcon("icon.stop"); + private Icon gateIcon = new GIcon("icon.debugger.breakpoint.set"); + + public TaintDecompilerMarginProvider(TaintPlugin plugin) { + this.plugin = plugin; + setPreferredSize(new Dimension(16, 0)); + } + + @Override + public void setProgram(Program program, LayoutModel model, LayoutPixelIndexMap pixmap) { + setLayoutManager(model); + this.pixmap = pixmap; + repaint(); + } + + public void functionChanged() { + repaint(); + } + + private void setLayoutManager(LayoutModel model) { + if (this.model == model) { + return; + } + if (this.model != null) { + this.model.removeLayoutModelListener(this); + } + this.model = model; + if (this.model != null) { + this.model.addLayoutModelListener(this); + } + } + + @Override + public Component getComponent() { + return this; + } + + @Override + public void modelSizeChanged(IndexMapper indexMapper) { + repaint(); + } + + @Override + public void dataChanged(BigInteger start, BigInteger end) { + repaint(); + } + + @Override + public void paint(Graphics g) { + super.paint(g); + if (plugin.getDecompilerProvider() == null) { + return; + } + Rectangle visible = getVisibleRect(); + BigInteger startIdx = pixmap.getIndex(visible.y); + BigInteger endIdx = pixmap.getIndex(visible.y + visible.height); + + List lines = plugin.getDecompilerProvider().getDecompilerPanel().getLines(); + for (BigInteger index = startIdx; index.compareTo(endIdx) <= 0; index = + index.add(BigInteger.ONE)) { + + int i = index.intValue(); + if (i >= lines.size()) { + continue; + } + + ClangLine line = lines.get(i); + if (sourceAddresses.contains(line.toString())) { + sourceIcon.paintIcon(this, g, 0, pixmap.getPixel(index)); + } + + if (sinkAddresses.contains(line.toString())) { + sinkIcon.paintIcon(this, g, 0, pixmap.getPixel(index)); + } + + if (gateAddresses.contains(line.toString())) { + gateIcon.paintIcon(this, g, 0, pixmap.getPixel(index)); + } + } + } + + /** + * @param label - SOURCE, SINK, etc. + */ + public void toggleIcon(TaintLabel label) { + Set addresses = switch (label.getType()) { + case SOURCE -> sourceAddresses; + case SINK -> sinkAddresses; + case GATE -> gateAddresses; + default -> null; + }; + if (addresses != null) { + String cline = label.getClangLine().toString(); + if (label.isActive()) { + addresses.add(cline); + } + else { + addresses.remove(cline); + } + repaint(); + } + } + + public void clearIcons() { + sourceAddresses.clear(); + sinkAddresses.clear(); + gateAddresses.clear(); + } + +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintHighlight.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintHighlight.java new file mode 100644 index 0000000000..cef3544732 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintHighlight.java @@ -0,0 +1,60 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint; + +import java.awt.Color; + +import generic.theme.GColor; + +public enum TaintHighlight { + + SINK("SINK", -3, new GColor("color.bg.decompiler.highlights.sink")), + SOURCE("SOURCE", -2, new GColor("color.bg.decompiler.highlights.source")), + SINKSOURCE("SINK, SOURCE", -1, new GColor("color.bg.decompiler.highlights.sinksource")), + SOURCESINK("SOURCE, SINK", -1, new GColor("color.bg.decompiler.highlights.sinksource")), + OTHER("0", 0, new GColor("color.bg.decompiler.highlights.path")); + + private final String tag; + private final int priority; + private final Color color; + + private TaintHighlight(String tag, int priority, Color color) { + this.tag = tag; + this.priority = priority; + this.color = color; + } + + public String getTag() { + return tag; + } + + public int getPriority() { + return priority; + } + + public Color getColor() { + return color; + } + + public static TaintHighlight byLabel(String label) { + for (TaintHighlight v : TaintHighlight.values()) { + if (v.getTag().equals(label)) { + return v; + } + } + return OTHER; + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintHighlightColorProvider.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintHighlightColorProvider.java new file mode 100644 index 0000000000..190a5eb914 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintHighlightColorProvider.java @@ -0,0 +1,71 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint; + +import java.awt.Color; +import java.util.Set; + +import ghidra.app.decompiler.ClangToken; +import ghidra.app.decompiler.component.*; +import ghidra.program.model.pcode.PcodeOp; +import ghidra.program.model.pcode.Varnode; + +/** + * A class to provide a color for highlight a variable using one of the 'slice' actions + */ +public class TaintHighlightColorProvider implements ColorProvider { + + private Set varnodes; + private Varnode specialVn; + private PcodeOp specialOp; + private Color hlColor; + private Color specialHlColor; + + TaintHighlightColorProvider(DecompilerPanel panel, Set varnodes, Varnode specialVn, + PcodeOp specialOp) { + this.varnodes = varnodes; + this.specialVn = specialVn; + this.specialOp = specialOp; + + hlColor = panel.getCurrentVariableHighlightColor(); + specialHlColor = panel.getSpecialHighlightColor(); + } + + @Override + public Color getColor(ClangToken token) { + + Varnode vn = DecompilerUtils.getVarnodeRef(token); + if (vn == null) { + return null; + } + + Color c = null; + if (varnodes.contains(vn)) { + c = hlColor; + } + + if (specialOp == null) { + return c; + } + + // look for specific varnode to label with special color + if (vn == specialVn && token.getPcodeOp() == specialOp) { + c = specialHlColor; + } + + return c; + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintLabel.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintLabel.java new file mode 100644 index 0000000000..25a3be343a --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintLabel.java @@ -0,0 +1,214 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint; + +import ghidra.app.decompiler.*; +import ghidra.app.plugin.core.decompiler.taint.TaintState.MarkType; +import ghidra.program.model.address.Address; +import ghidra.program.model.pcode.*; + +public class TaintLabel { + + private MarkType mtype; + private ClangToken token; + + private String fname; + private HighFunction hfun; + private HighVariable hvar; + private boolean active; + private String label; + private boolean isGlobal = false; + private boolean bySymbol = false; + + // TODO: This is not a good identifier since it could change during re work! + private Address addr; + private ClangLine clangLine; + + public TaintLabel(MarkType mtype, ClangToken token) throws PcodeException { + HighVariable highVar = token.getHighVariable(); + if (highVar == null) { + hfun = token.getClangFunction().getHighFunction(); + } + else { + hfun = highVar.getHighFunction(); + HighSymbol symbol = highVar.getSymbol(); + if (symbol != null) { + isGlobal = symbol.isGlobal(); + } + } + + Varnode exactSpot = token.getVarnode(); + if (exactSpot != null) { // The user pointed at a particular usage, not just the vardecl + highVar = hfun.splitOutMergeGroup(exactSpot.getHigh(), exactSpot); + } + + String fn = token instanceof ClangFuncNameToken ftoken ? ftoken.getText() + : hfun.getFunction().getName(); + PcodeOp pcodeOp = token.getPcodeOp(); + Address target = pcodeOp == null ? null : pcodeOp.getSeqnum().getTarget(); + + this.mtype = mtype; + this.token = token; + this.fname = fn; + this.hvar = highVar; + this.active = true; + this.addr = target; + this.clangLine = token.getLineParent(); + + // Initial label is one of SOURCE, SINK, or GATE + this.label = mtype.toString(); + } + + public ClangLine getClangLine() { + return this.clangLine; + } + + public void setClangLine(ClangLine clangLine) { + this.clangLine = clangLine; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getLabel() { + return label; + } + + public MarkType getType() { + return this.mtype; + } + + public ClangToken getToken() { + return this.token; + } + + public HighFunction getHighFunction() { + return this.hfun; + } + + public String getFunctionName() { + return this.fname; + } + + public HighVariable getHighVariable() { + return this.hvar; + } + + public Address getAddress() { + return addr; + } + + @Override + public String toString() { + String result = mtype.toString() + " "; + + if (isActive()) { + result += "[ACTIVE]: "; + } + else { + result += "[INACTIVE]: "; + } + + result = result + fname; + if (this.hvar != null) { + result += ", " + this.hvar.toString(); + } + + if (this.clangLine != null) { + result += ", " + this.clangLine.toString(); + } + + return result; + } + + public void deactivate() { + active = false; + } + + public void activate() { + active = true; + } + + public void toggle() { + active = !active; + } + + public boolean isActive() { + return active; + } + + public boolean isGlobal() { + return isGlobal; + } + + public boolean bySymbol() { + return bySymbol; + } + + public void setBySymbol(boolean bySymbol) { + this.bySymbol = bySymbol; + } + + public boolean hasHighVar() { + return this.hvar != null; + } + + /** + * {@inheritDoc} + * + * hashCode that ignores the boolean active status. + */ + @Override + public int hashCode() { + int prime = 31; + int result = 1; + result = prime * result + mtype.hashCode(); + result = prime * result + fname.hashCode(); + if (hvar != null) { + result = prime * result + hvar.hashCode(); + } + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null) { + return false; + } + if (getClass() != o.getClass()) { + return false; + } + TaintLabel other = (TaintLabel) o; + if (this.mtype != other.mtype) { + return false; + } + if (!this.fname.equals(other.fname)) { + return false; + } + if (this.hvar != other.hvar) { + return false; + } + return true; + } + +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintLabelsDataFrame.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintLabelsDataFrame.java new file mode 100644 index 0000000000..fc1e9dd91b --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintLabelsDataFrame.java @@ -0,0 +1,161 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint; + +import java.util.*; + +import docking.widgets.table.ObjectSelectedListener; +import ghidra.app.plugin.core.decompiler.taint.TaintState.MarkType; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSet; +import ghidra.program.model.pcode.HighVariable; +import ghidra.util.Msg; + +/** + * The data that populates the TaintHighlight table. This data is all the active and inactive taint labels. + * The following items should be editable: + *
  • + * Whether the taint label is active or inactive. + *
  • + * The "label" associated with the source, sink, or gate. + *
+ */ +public class TaintLabelsDataFrame implements ObjectSelectedListener> { + + private List columns; + + // Each item in the list is a row, the rows are maps from column names -> data. + // NOTE: These results need to transfer back to the TaintState. + public List> tableResults; + + private TaintPlugin plugin; + + /** + * Sarif Data is associated with a plugin and a program. + * + * @param plugin - plugin + */ + public TaintLabelsDataFrame(TaintPlugin plugin) { + + this.plugin = plugin; + + columns = new ArrayList<>(); + tableResults = new ArrayList<>(); + + columns.add("Selected"); + columns.add("Address"); + columns.add("Label"); + columns.add("Name"); + columns.add("Category"); + columns.add("Function Address"); + columns.add("Taint Label Object"); + + Msg.info(this, "Created TaintLabelsDataFrame"); + } + + public List getColumnHeaders() { + return columns; + } + + public void loadData() { + tableResults = new ArrayList<>(); + Msg.info(this, "Loading TaintLabelsDataFrame"); + + for (MarkType category : new MarkType[] { MarkType.SOURCE, MarkType.SINK, MarkType.GATE }) { + + // loading data from TaintState which should be the start state of this table. + for (TaintLabel taint_label : plugin.getTaintState().getTaintLabels(category)) { + + Map row = new HashMap<>(); + HighVariable hv = taint_label.getHighVariable(); + + if (hv == null) { + row.put("Name", taint_label.getFunctionName()); + row.put("Function Address", null); + row.put("Address", null); + } + else { + row.put("Name", hv.getName()); + row.put("Function Address", hv.getHighFunction().getFunction().getEntryPoint()); + Address addr = hv.getSymbol() == null ? null : hv.getSymbol().getPCAddress(); + row.put("Address", addr); + } + + row.put("Label", taint_label.getLabel()); + row.put("Taint Label Object", taint_label); + row.put("Category", category); + row.put("Selected", taint_label.isActive()); + + Msg.info(this, "Row loaded: " + taint_label.toString()); + tableResults.add(row); + } + } + } + + public void setSelected(int row, Boolean value) { + Map row_data = tableResults.get(row); + row_data.put("Selected", value); + } + + public void toggleSelected(int row) { + Map rowData = tableResults.get(row); + Boolean status = (Boolean) rowData.get("Selected"); + rowData.put("Selected", !status); + } + + public void setLabel(int row, String label) { + tableResults.get(row).put("Label", label); + Msg.info(this, "New label value: " + tableResults.get(row).get("Label")); + } + + public AddressSet getTaintAddressSet() { + AddressSet aset = new AddressSet(); + + if (tableResults != null && tableResults.size() > 0) { + for (Map map : tableResults) { + aset.add((Address) map.get("Address")); + } + } + return aset; + } + + public void dumpTableToDebug() { + for (Map row : tableResults) { + StringBuilder sb = new StringBuilder(); + for (Map.Entry entry : row.entrySet()) { + sb.append(String.format("(%s,%s), ", entry.getKey(), entry.getValue())); + } + Msg.info(this, sb.toString()); + } + } + + /** + * @param row This is ALL the data in the row we can use. + */ + @Override + public void objectSelected(Map row) { + if (row != null && row.containsKey("Address")) { + List
addr_list = new ArrayList
(); + addr_list.add((Address) row.get("Address")); + Msg.info(this, "Making selection, " + row.get("Address")); + this.plugin.makeSelection(addr_list); + } + } + + public List> getData() { + return this.tableResults; + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintLabelsTableModelFactory.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintLabelsTableModelFactory.java new file mode 100644 index 0000000000..d0d9fb5060 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintLabelsTableModelFactory.java @@ -0,0 +1,316 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint; + +import java.util.List; +import java.util.Map; + +import docking.widgets.table.AbstractDynamicTableColumn; +import docking.widgets.table.TableColumnDescriptor; +import ghidra.docking.settings.Settings; +import ghidra.framework.plugintool.ServiceProvider; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Program; +import ghidra.program.model.pcode.HighVariable; +import ghidra.util.Msg; +import ghidra.util.datastruct.Accumulator; +import ghidra.util.exception.CancelledException; +import ghidra.util.table.AddressBasedTableModel; +import ghidra.util.task.TaskMonitor; + +public class TaintLabelsTableModelFactory { + + private List sColumns; // Used for the TableColumnDescriptor + + public TaintLabelsTableModelFactory(List cols) { + sColumns = cols; + } + + public TaintLabelsTableModel createModel(String description, TaintPlugin plugin, + Program program, TaintLabelsDataFrame df, TaintLabelsTableProvider provider) { + return new TaintLabelsTableModel(description, plugin, program, df, provider); + } + + public class TaintLabelsTableModel extends AddressBasedTableModel> { + private static final long serialVersionUID = 1L; + private TaintLabelsDataFrame df; + private TaintLabelsTableProvider provider; + private TaintPlugin plugin; + + public TaintLabelsTableModel(String description, TaintPlugin plugin, Program program, + TaintLabelsDataFrame df, TaintLabelsTableProvider provider) { + super(description, plugin.getTool(), program, null); + this.df = df; + this.provider = provider; + this.plugin = plugin; + } + + @Override + public boolean isCellEditable(int row, int col) { + String colName = this.getColumnName(col); + if (colName == "Selected" || colName == "Label") + return true; + return false; + } + + @Override + public void setValueAt(Object obj, int row, int col) { + + String colName = this.getColumnName(col); + + Msg.info(this, "Set (" + row + "," + col + ") with colName: " + colName + " Value: " + + obj.toString()); + + // The table retains instances of the column -> data mappings when it accumulates. + Map mapping = provider.filterTable.getRowObject(row); + TaintLabel tlabel = (TaintLabel) mapping.get("Taint Label Object"); + + switch (colName) { + + case "Selected" -> { + boolean selected = (boolean) mapping.get(colName); + mapping.put(colName, !selected); + // TODO This should just change the instance that is the same as what is in State... + tlabel.toggle(); + plugin.toggleMarginIcon(tlabel); + } + case "Label" -> { + String newLabel = (String) obj; + mapping.put(colName, newLabel); + tlabel.setLabel(newLabel); + } + default -> { + Msg.warn(this, "Unable to set value at "+colName); + } + } + } + + public TaintLabelsDataFrame getDataFrame() { + return this.df; + } + + @Override + public Address getAddress(int row) { + return (Address) this.getRowObject(row).get("Address"); + } + + @Override + protected void doLoad(Accumulator> accumulator, TaskMonitor monitor) + throws CancelledException { + + Msg.info(this, "doLoad attempting to load the table."); + + // tableResults is a list of Maps; each map is a row in the table. + for (Map result : df.getData()) { + + Msg.info(this, "Loading: " + result.get("Taint Label Object")); + + if (monitor.isCancelled()) { + monitor.clearCancelled(); + break; + } + + accumulator.add(result); + } + } + + @Override + protected TableColumnDescriptor> createTableColumnDescriptor() { + + TableColumnDescriptor> descriptor = new TableColumnDescriptor<>(); + + for (String columnName : sColumns) { + + switch (columnName) { + case "Address": + case "Function Address": + descriptor.addVisibleColumn(new AddressColumn(columnName)); + break; + case "Category": + case "Name": + descriptor.addVisibleColumn(new StringColumn(columnName)); + break; + case "Selected": + descriptor.addVisibleColumn(new BooleanColumn(columnName)); + break; + case "Taint Label Object": + descriptor.addHiddenColumn(new TaintLabelColumn(columnName)); + break; + default: + descriptor.addVisibleColumn(new Column(columnName)); + break; + } + } + return descriptor; + } + + public class Column + extends AbstractDynamicTableColumn, Object, Object> { + private String columnName; + + public Column(String name) { + columnName = name; + } + + @Override + public String getColumnName() { + return columnName; + } + + @Override + public Object getValue(Map rowObject, Settings settings, Object data, + ServiceProvider sp) throws IllegalArgumentException { + return rowObject.get(getColumnName()); + } + + } + + public class HighVariableColumn + extends AbstractDynamicTableColumn, HighVariable, Object> { + private String columnName; + + public HighVariableColumn(String name) { + columnName = name; + } + + @Override + public String getColumnName() { + return columnName; + } + + @Override + public HighVariable getValue(Map rowObject, Settings settings, + Object data, ServiceProvider sp) throws IllegalArgumentException { + return (HighVariable) rowObject.get(getColumnName()); + } + + } + + public class TaintLabelColumn + extends AbstractDynamicTableColumn, TaintLabel, Object> { + private String columnName; + + public TaintLabelColumn(String name) { + columnName = name; + } + + @Override + public String getColumnName() { + return columnName; + } + + @Override + public TaintLabel getValue(Map rowObject, Settings settings, + Object data, ServiceProvider sp) throws IllegalArgumentException { + return (TaintLabel) rowObject.get(getColumnName()); + } + } + + public class StringColumn + extends AbstractDynamicTableColumn, String, Object> { + private String name; + + public StringColumn(String name) { + this.name = name; + } + + @Override + public String getColumnName() { + return this.name; + } + + @Override + public String getValue(Map rowObject, Settings settings, Object data, + ServiceProvider sp) throws IllegalArgumentException { + + // pull out of the table row, the data associated with this COLUMN. + Object o = rowObject.get(this.name); + if (o == null) { + return "NULL"; + } + return o.toString(); + } + + } + + public class BooleanColumn + extends AbstractDynamicTableColumn, Boolean, Object> { + private String name; + + public BooleanColumn(String name) { + this.name = name; + } + + @Override + public String getColumnName() { + return this.name; + } + + @Override + public Boolean getValue(Map rowObject, Settings settings, Object data, + ServiceProvider sp) throws IllegalArgumentException { + return (Boolean) rowObject.get(this.name); + } + + } + + public class AddressColumn + extends AbstractDynamicTableColumn, Address, Object> { + private String name; + + public AddressColumn(String name) { + this.name = name; + } + + @Override + public String getColumnName() { + return this.name; + } + + @Override + public Address getValue(Map rowObject, Settings settings, Object data, + ServiceProvider sp) throws IllegalArgumentException { + return (Address) rowObject.get(this.name); + } + + } + + public class IntegerColumn + extends AbstractDynamicTableColumn, Integer, Object> { + private String name; + + public IntegerColumn(String name) { + this.name = name; + } + + @Override + public String getColumnName() { + return this.name; + } + + @Override + public Integer getValue(Map rowObject, Settings settings, Object data, + ServiceProvider sp) throws IllegalArgumentException { + Object o = rowObject.get(this.name); + if (o == null) { + return -1; + } + return (Integer) o; + } + + } + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintLabelsTableProvider.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintLabelsTableProvider.java new file mode 100644 index 0000000000..e4a58b9ec7 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintLabelsTableProvider.java @@ -0,0 +1,216 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint; + +import java.awt.BorderLayout; +import java.util.Map; + +import javax.swing.*; + +import docking.ActionContext; +import docking.action.DockingAction; +import docking.action.ToolBarData; +import generic.theme.GIcon; +import ghidra.app.plugin.core.decompiler.taint.TaintLabelsTableModelFactory.TaintLabelsTableModel; +import ghidra.app.plugin.core.decompiler.taint.TaintState.QueryType; +import ghidra.app.plugin.core.decompiler.taint.sarif.SarifTaintGraphRunHandler; +import ghidra.framework.plugintool.ComponentProviderAdapter; +import ghidra.program.model.listing.Program; +import ghidra.util.Msg; +import ghidra.util.table.GhidraFilterTable; +import ghidra.util.table.GhidraTable; +import ghidra.util.table.actions.MakeProgramSelectionAction; +import ghidra.util.task.Task; +import ghidra.util.task.TaskMonitor; +import sarif.SarifService; + +/** + * Show the SARIF result as a table and build possible actions on the table + */ +public class TaintLabelsTableProvider extends ComponentProviderAdapter { + + private TaintPlugin plugin; + private Program program; + private JComponent mainPanel; + + private GhidraTable gtable; + + public GhidraFilterTable> filterTable; + private TaintLabelsTableModel model; + + // TODO: Put these in the Taint Options Manager. + private static String clearTaintTagsIconString = "icon.clear"; + private static Icon clearTaintTagsIcon = new GIcon(clearTaintTagsIconString); + private static String executeTaintQueryIconString = "icon.graph.default.display.program.graph"; + private static Icon executeTaintQueryIcon = new GIcon(executeTaintQueryIconString); + + public TaintLabelsTableProvider(String description, TaintPlugin plugin, + TaintLabelsDataFrame df) { + + super(plugin.getTool(), description, plugin.getName()); + this.plugin = plugin; + this.program = plugin.getCurrentProgram(); + + TaintLabelsTableModelFactory factory = + new TaintLabelsTableModelFactory(df.getColumnHeaders()); + + this.model = + factory.createModel("Source-Sink Query Results Table", plugin, program, df, this); + this.mainPanel = buildPanel(); + + filterTable.addSelectionListener(df); + filterTable.getTable().getSelectionModel().addListSelectionListener(e -> { + Msg.info(this, "list selection listener triggered."); + plugin.getTool().contextChanged(this); + }); + + createActions(); + } + + private JComponent buildPanel() { + filterTable = new GhidraFilterTable<>(this.model); + GhidraTable table = filterTable.getTable(); + table.installNavigation(plugin.getTool()); + table.setName("DataTable"); + + model.addTableModelListener(e -> { + Msg.info(this, "TableModelListener fired"); + int rowCount = model.getRowCount(); + int unfilteredCount = model.getUnfilteredRowCount(); + model.getDataFrame().dumpTableToDebug(); + + setSubTitle("" + rowCount + " items" + + (rowCount != unfilteredCount ? " (of " + unfilteredCount + ")" : "")); + filterTable.repaint(); + }); + + JPanel panel = new JPanel(new BorderLayout()); + panel.add(filterTable, BorderLayout.CENTER); + + return panel; + } + + public void reloadModel() { + model.reload(); + } + + @Override + public JComponent getComponent() { + return mainPanel; + } + + public GhidraTable getTable() { + return gtable; + } + + /** + * Add actions to various table features. + */ + public void createActions() { + + // Provides the icon in the toolbar that makes a selection based on what you have in the table. + DockingAction selectionAction = + new MakeProgramSelectionAction(plugin, filterTable.getTable()); + + DockingAction clearTaintMarksAction = + new DockingAction("Clear All Taint Marks", plugin.getName()) { + + @Override + public void actionPerformed(ActionContext context) { + // empty out the marker sets. + plugin.getTaintState().clearMarkers(); + // clear the markers in the decompiler window. + plugin.clearIcons(); + + // load empty marker set and then reload the table. + model.getDataFrame().loadData(); + model.reload(); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + return plugin.getTaintState().hasMarks(); + } + }; + + clearTaintMarksAction.setToolBarData(new ToolBarData(clearTaintTagsIcon)); + + DockingAction queryAction = + new DockingAction("Execute Source-Sink Query", plugin.getName()) { + @Override + public void actionPerformed(ActionContext context) { + Msg.info(this, "Execute Source-Sink Query from Taint Labels Table"); + + Program currentProgram = plugin.getCurrentProgram(); + if (currentProgram == null) + return; + + TaintState state = plugin.getTaintState(); + + Task queryTask = new Task("Source-Sink Query Task", true, true, true, true) { + @Override + public void run(TaskMonitor monitor) { + state.setCancellation(false); + monitor.initialize(program.getFunctionManager().getFunctionCount()); + // query index NOT the default query; use table data. + boolean successful = + state.queryIndex(currentProgram, tool, QueryType.SRCSINK); + state.setCancellation(!successful || monitor.isCancelled()); + monitor.clearCancelled(); + } + }; + + // This task will block -- see params above. + // The blocking is necessary because of the table provider we create below. + // It is problematic to do GUI stuff in the thread. + // We still get a progress bar and option to cancel. + // 1. Query Index. + tool.execute(queryTask); + + if (!state.wasCancelled()) { + // 2. Show Table. + SarifService sarifService = plugin.getSarifService(); + sarifService.getController() + .setDefaultGraphHander(SarifTaintGraphRunHandler.class); + sarifService.showSarif("query", state.getData()); + + // 3. Set Initial Highlights + plugin.consoleMessage("executing query..."); + TaintProvider provider = plugin.getProvider(); + provider.setTaint(); + plugin.consoleMessage("query complete"); + state.setCancellation(false); + + } + else { + plugin.consoleMessage("Source-Sink query was cancelled."); + } + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + // TODO make this smarter. + return true; + } + }; + + queryAction.setToolBarData(new ToolBarData(executeTaintQueryIcon)); + + addLocalAction(selectionAction); + addLocalAction(clearTaintMarksAction); + addLocalAction(queryAction); + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintOptions.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintOptions.java new file mode 100644 index 0000000000..d9f3b26f6c --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintOptions.java @@ -0,0 +1,280 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint; + +import java.awt.Color; + +import generic.theme.GColor; +import ghidra.app.plugin.core.decompiler.taint.TaintPlugin.Highlighter; +import ghidra.app.util.HelpTopics; +import ghidra.framework.options.ToolOptions; +import ghidra.framework.plugintool.Plugin; +import ghidra.program.model.listing.Program; +import ghidra.util.HelpLocation; + +/** + * Taint information is used in the Decompiler Window. + */ +public class TaintOptions { + + // ResourceManager may be able to pull these from a configuration. + + // Option key strings for various directory and file paths. + /* full path to where the GhidraScript puts the facts. */ + public final static String OP_KEY_TAINT_FACTS_DIR = "Taint.Directories.Facts"; + /* full path to where all query databases and other output lives -- not engine; not facts. */ + public final static String OP_KEY_TAINT_OUTPUT_DIR = "Taint.Directories.Output"; + /* full path to where the engine executable lives. */ + public final static String OP_KEY_TAINT_ENGINE_PATH = "Taint.Directories.Engine"; + + /* The default name of the text file containing the query. */ + public final static String OP_KEY_TAINT_QUERY = "Taint.Query.Current Query"; + /* The default name of the index database file. */ + public final static String OP_KEY_TAINT_DB = "Taint.Query.Index"; + + public final static String OP_KEY_TAINT_QUERY_OUTPUT_FORM = "Taint.Output Format"; + /* Color used in the decompiler to highlight taint. */ + public final static String TAINT_HIGHLIGHT = "Taint.Highlight Color"; + /* How to apply highlight taint. */ + public final static String TAINT_HIGHLIGHT_STYLE = "Taint.Highlight Style"; + public final static String TAINT_ALL_ACCESS = "Taint.Use all access paths"; + private final static Boolean TAINT_ALL_ACCESS_PATHS = true; + + public final static String DEFAULT_TAINT_ENGINE_PATH = ""; + public final static String DEFAULT_TAINT_FACTS_DIR = ""; + public final static String DEFAULT_TAINT_OUTPUT_DIR = ""; + + /* this is the text code that contains the datalog query the plugin writes. */ + public final static String DEFAULT_TAINT_QUERY = "taintquery.dl"; + public final static String DEFAULT_TAINT_DB = "ctadlir.db"; + public final static String DEFAULT_TAINT_OUTPUT_FORM = "sarif+all"; + + public final static Boolean DEFAULT_GET_PATHS = true; + + private final static GColor TAINT_HIGHLIGHT_COLOR = + new GColor("color.bg.listing.highlighter.default"); + private final static Highlighter TAINT_HIGHLIGHT_STYLE_DEFAULT = Highlighter.DEFAULT; + + private String taintEngine; + private String taintFactsDir; + private String taintOutputDir; + + private String taintQuery; + private String taintDB; + + private String taintQueryOutputForm; + + private Highlighter taintHighlightStyle; + private Color taintHighlightColor; + private Boolean taintUseAllAccess; + + private TaintProvider taintProvider; + + public static String makeDBName(String base, String binary_name) { + StringBuilder sb = new StringBuilder(); + String[] parts = base.split("\\."); + for (int i = 0; i < parts.length; ++i) { + if (i > 0) { + sb.append("."); + } + + if (i == 2) { + sb.append(binary_name); + sb.append("."); + } + + sb.append(parts[i]); + } + + return sb.toString(); + } + + public TaintOptions(TaintProvider provider) { + taintProvider = provider; + + taintEngine = DEFAULT_TAINT_ENGINE_PATH; + taintFactsDir = DEFAULT_TAINT_FACTS_DIR; + taintOutputDir = DEFAULT_TAINT_OUTPUT_DIR; + taintQuery = DEFAULT_TAINT_QUERY; + taintDB = DEFAULT_TAINT_DB; + taintQueryOutputForm = DEFAULT_TAINT_OUTPUT_FORM; + taintUseAllAccess = TAINT_ALL_ACCESS_PATHS; + + } + + /** + * This registers all the decompiler tool options with ghidra, and has the side + * effect of pulling all the current values for the options if they exist + * + * @param ownerPlugin the plugin to which the options should be registered + * @param opt the options object to register with + * @param program the program + */ + public void registerOptions(Plugin ownerPlugin, ToolOptions opt, Program program) { + + opt.registerOption(OP_KEY_TAINT_QUERY_OUTPUT_FORM, DEFAULT_TAINT_OUTPUT_FORM, + new HelpLocation(HelpTopics.DECOMPILER, "Taint Output Type"), + "The type of Source-Sink query output (e.g., sarif, summary, text"); + + opt.registerOption(OP_KEY_TAINT_ENGINE_PATH, DEFAULT_TAINT_ENGINE_PATH, + new HelpLocation(HelpTopics.DECOMPILER, "Taint Engine Directory"), + "Base path to external taint engine (Source-Sink executable)."); + + opt.registerOption(OP_KEY_TAINT_FACTS_DIR, DEFAULT_TAINT_FACTS_DIR, + new HelpLocation(HelpTopics.DECOMPILER, "Taint Facts Directory"), + "Base Path to facts directory"); + + opt.registerOption(OP_KEY_TAINT_OUTPUT_DIR, DEFAULT_TAINT_OUTPUT_DIR, + new HelpLocation(HelpTopics.DECOMPILER, "Taint Output Directory"), + "Base Path to output directory"); + + opt.registerOption(OP_KEY_TAINT_QUERY, DEFAULT_TAINT_QUERY, + new HelpLocation(HelpTopics.DECOMPILER, "TaintQuery"), + "File where the query text that Ghidra produces is written."); + + opt.registerOption(OP_KEY_TAINT_DB, DEFAULT_TAINT_DB, + new HelpLocation(HelpTopics.DECOMPILER, "Taint Database"), + "File where the index is written for the binary."); + + opt.registerThemeColorBinding(TAINT_HIGHLIGHT, TAINT_HIGHLIGHT_COLOR.getId(), + new HelpLocation(HelpTopics.DECOMPILER, "TaintTokenColor"), + "Color used for highlighting tainted variables."); + + opt.registerOption(TAINT_ALL_ACCESS, TAINT_ALL_ACCESS_PATHS, + new HelpLocation(HelpTopics.DECOMPILER, "TaintAllAccess"), "Use all access paths."); + + grabFromToolAndProgram(ownerPlugin, opt, program); + } + + /** + * Grab all the decompiler options from various sources within a specific tool + * and program and cache them in this object. + * + *

+ * NOTE: Overrides the defaults. + * + * @param ownerPlugin the plugin that owns the "tool options" for the decompiler + * @param opt the Options object that contains the "tool options" + * specific to the decompiler + * @param program the program whose "program options" are relevant to the + * decompiler + */ + public void grabFromToolAndProgram(Plugin ownerPlugin, ToolOptions opt, Program program) { + + taintEngine = opt.getString(OP_KEY_TAINT_ENGINE_PATH, ""); + taintFactsDir = opt.getString(OP_KEY_TAINT_FACTS_DIR, ""); + taintQuery = opt.getString(OP_KEY_TAINT_QUERY, ""); + // taintQueryResultsFile = opt.getString(OP_KEY_TAINT_QUERY_RESULTS, ""); + taintOutputDir = opt.getString(OP_KEY_TAINT_OUTPUT_DIR, ""); + taintDB = opt.getString(OP_KEY_TAINT_DB, ""); + + taintQueryOutputForm = opt.getString(OP_KEY_TAINT_QUERY_OUTPUT_FORM, ""); + + taintHighlightStyle = opt.getEnum(TAINT_HIGHLIGHT_STYLE, TAINT_HIGHLIGHT_STYLE_DEFAULT); + taintHighlightColor = opt.getColor(TAINT_HIGHLIGHT, TAINT_HIGHLIGHT_COLOR); + taintUseAllAccess = opt.getBoolean(TAINT_ALL_ACCESS, TAINT_ALL_ACCESS_PATHS); + + } + + public String getTaintOutputForm() { + return taintQueryOutputForm; + } + + public String getTaintEnginePath() { + return taintEngine; + } + + public String getTaintFactsDirectory() { + return taintFactsDir; + } + + public String getTaintOutputDirectory() { + return taintOutputDir; + } + + public String getTaintQueryDLName() { + return taintQuery; + } + + public String getTaintQueryDBName() { + return taintDB; + } + + public String getTaintQueryDBName(String name) { + return makeDBName(taintDB, name); + } + + public String getTaintIndexDBName() { + return taintDB; + } + + public String getTaintIndexDBName(String name) { + return makeDBName(taintDB, name); + } + + public Color getTaintHighlightColor() { + return taintHighlightColor; + } + + public Highlighter getTaintHighlightStyle() { + return taintHighlightStyle; + } + + public Boolean getTaintUseAllAccess() { + return taintUseAllAccess; + } + + public void setTaintOutputForm(String form) { + this.taintQueryOutputForm = form; + taintProvider.setOption(OP_KEY_TAINT_QUERY_OUTPUT_FORM, form); + } + + public void setTaintFactsDirectory(String path) { + this.taintFactsDir = path; + taintProvider.setOption(DEFAULT_TAINT_FACTS_DIR, path); + } + + public void setTaintOutputDirectory(String path) { + this.taintOutputDir = path; + taintProvider.setOption(OP_KEY_TAINT_OUTPUT_DIR, path); + } + + public void setTaintQueryName(String filename) { + this.taintQuery = filename; + taintProvider.setOption(OP_KEY_TAINT_QUERY, filename); + } + + public void setTaintIndexDBName(String filename) { + this.taintDB = filename; + taintProvider.setOption(OP_KEY_TAINT_DB, filename); + } + + public void setTaintHighlightColor(Color color) { + this.taintHighlightColor = color; + taintProvider.setColor(TAINT_HIGHLIGHT, color); + } + + public void setTaintHighlightStyle(Highlighter style) { + this.taintHighlightStyle = style; + taintProvider.setOption(TAINT_HIGHLIGHT_STYLE, style.name()); + taintProvider.changeHighlighter(style); + } + + public void setTaintAllAccess(Boolean allAccess) { + this.taintUseAllAccess = allAccess; + taintProvider.setAllAccess(TAINT_ALL_ACCESS, allAccess); + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintPlugin.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintPlugin.java new file mode 100644 index 0000000000..6fb111c88f --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintPlugin.java @@ -0,0 +1,659 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint; + +import java.awt.Color; +import java.util.*; + +import javax.swing.Icon; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import docking.ActionContext; +import docking.action.DockingAction; +import docking.action.MenuData; +import generic.theme.GIcon; +import ghidra.app.CorePluginPackage; +import ghidra.app.decompiler.*; +import ghidra.app.events.*; +import ghidra.app.plugin.PluginCategoryNames; +import ghidra.app.plugin.ProgramPlugin; +import ghidra.app.plugin.core.decompile.DecompilerProvider; +import ghidra.app.plugin.core.decompiler.taint.TaintState.MarkType; +import ghidra.app.script.GhidraScript; +import ghidra.app.script.GhidraState; +import ghidra.app.services.ConsoleService; +import ghidra.framework.plugintool.*; +import ghidra.framework.plugintool.util.PluginStatus; +import ghidra.program.database.SpecExtension; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSet; +import ghidra.program.model.listing.*; +import ghidra.program.model.pcode.*; +import ghidra.program.model.symbol.Reference; +import ghidra.program.model.symbol.ReferenceManager; +import ghidra.program.util.ProgramLocation; +import ghidra.util.Msg; +import resources.Icons; +import sarif.SarifService; + +/** + * Plugin for tracking taint through the decompiler. + */ +//@formatter:off +@PluginInfo( + status = PluginStatus.UNSTABLE, + packageName = CorePluginPackage.NAME, + category = PluginCategoryNames.ANALYSIS, + shortDescription = "DecompilerTaint", + description = "Plugin for tracking taint through the decompiler", + servicesProvided = { TaintService.class }, + servicesRequired = { + DecompilerHighlightService.class, + DecompilerMarginService.class, + ConsoleService.class, + SarifService.class + }, + eventsConsumed = { + ProgramActivatedPluginEvent.class, ProgramOpenedPluginEvent.class, + ProgramLocationPluginEvent.class, ProgramSelectionPluginEvent.class, + ProgramClosedPluginEvent.class + }) +//@formatter:on + +public class TaintPlugin extends ProgramPlugin implements TaintService { + + public final static String HELP_LOCATION = "DecompilerTaint"; + + private Function currentFunction; + private DecompilerMarginService marginService; + private ConsoleService consoleService; + private SarifService sarifService; + + // Source-Sink Specific. + private TaintProvider taintProvider; + private TaintDecompilerMarginProvider taintDecompMarginProvider; + + public static enum Highlighter { + ALL("Taint Variables"), LABELS("Taint Labels"), DEFAULT("Default"); + + private String name; + + private Highlighter(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + } + + // shift over to multiple highlighters + private DecompilerHighlightService highlightService; + + private TaintState state; + + // Taint Tree Provider Stuff + static final String SHOW_TAINT_TREE_ACTION_NAME = "Taint Slice Tree"; + public static final Icon PROVIDER_ICON = Icons.ARROW_DOWN_RIGHT_ICON; + public static final Icon FUNCTION_ICON = new GIcon("icon.plugin.calltree.function"); + public static final Icon RECURSIVE_ICON = new GIcon("icon.plugin.calltree.recursive"); + public static final Icon TAINT_TREE_ICON = + new GIcon("icon.plugin.functiongraph.layout.nested.code"); + + // You may want MANY slice tree gui elements to explore different slices within a program. + // This list should keep track of them all. + + private Map taintTreeProviders = new HashMap<>(); + + static final Logger log = LogManager.getLogger(TaintPlugin.class); + + public TaintPlugin(PluginTool tool) { + super(tool); + state = TaintState.newInstance(this); + taintProvider = new TaintProvider(this); + taintDecompMarginProvider = new TaintDecompilerMarginProvider(this); + createActions(); + + // No PRIMARY NEEDED. + } + + public void showOrCreateNewSliceTree(Program program, ClangToken tokenAtCursor, + HighVariable highVariable) { + + Msg.info(this, "showOrCreateNewSliceTree"); + + if (program == null) { + return; + } + + String treeProviderKey = + highVariable != null ? highVariable.toString() : tokenAtCursor.toString(); + + // We will have a taint tree for each variable we are interested in. + TaintSliceTreeProvider provider = taintTreeProviders.get(treeProviderKey); + if (provider != null) { + // Show a previous composed provider. + tool.showComponentProvider(provider, true); + return; + } + + // did not find a provider for the key. + + if (highVariable == null) { + // just use the tokenAtCursor (must be a function??) + createAndShowProvider(tokenAtCursor); + return; + } + + createAndShowProvider(highVariable); + } + + // Taint Tree + private void createAndShowProvider(ClangToken token) { + TaintSliceTreeProvider provider = new TaintSliceTreeProvider(this, false); + taintTreeProviders.put(token.toString(), provider); + tool.showComponentProvider(provider, true); + } + + // Taint Tree + private void createAndShowProvider(HighVariable highVar) { + TaintSliceTreeProvider provider = new TaintSliceTreeProvider(this, false); + taintTreeProviders.put(highVar.toString(), provider); + provider.initialize(currentProgram, currentLocation); + tool.showComponentProvider(provider, true); + } + + // Taint Tree + public ProgramLocation getCurrentLocation() { + return currentLocation; + } + + // Taint Tree + public void removeProvider(TaintSliceTreeProvider provider) { + + for (Map.Entry mapping : taintTreeProviders.entrySet()) { + if (provider == mapping.getValue()) { + taintTreeProviders.remove(mapping.getKey()); + tool.removeComponentProvider(mapping.getValue()); + mapping.getValue().dispose(); + return; + } + } + + } + + // Taint Tree + @Override + protected void programDeactivated(Program program) { + for (TaintSliceTreeProvider provider : taintTreeProviders.values()) { + provider.programDeactivated(program); + } + } + + // Taint Tree + @Override + protected void programClosed(Program program) { + for (TaintSliceTreeProvider provider : taintTreeProviders.values()) { + provider.programClosed(program); + } + } + + // Taint Tree + public Function getFunction(ProgramLocation location) { + FunctionManager functionManager = currentProgram.getFunctionManager(); + Address address = location.getAddress(); + Function function = functionManager.getFunctionContaining(address); + function = resolveFunction(function, address); + return function; + } + + public Function getCurrentFunction() { + return currentFunction; + } + + /** + * Apparently, we create fake function markup for external functions. Thus, there is no + * real function at that address and our plugin has to do some work to find out where + * we 'hang' references to the external function, which is itself a Function. These + * fake function will usually just be a pointer to another function. + * + * @param function the function to resolve; if it is not null, then it will be used + * @param address the address for which to find a function + * @return either the given function if non-null, or a function being referenced from the + * given address. + */ + Function resolveFunction(Function function, Address address) { + if (function != null) { + return function; + } + + // maybe we point to another function? + FunctionManager functionManager = currentProgram.getFunctionManager(); + ReferenceManager referenceManager = currentProgram.getReferenceManager(); + Reference[] references = referenceManager.getReferencesFrom(address); + for (Reference reference : references) { + Address toAddress = reference.getToAddress(); + Function toFunction = functionManager.getFunctionAt(toAddress); + if (toFunction != null) { + return toFunction; + } + } + + return null; + } + + @Override + protected void dispose() { + List copy = new ArrayList<>(taintTreeProviders.values()); + for (TaintSliceTreeProvider provider : copy) { + removeProvider(provider); + } + + } + + @Override + protected void locationChanged(ProgramLocation loc) { + for (TaintSliceTreeProvider provider : taintTreeProviders.values()) { + provider.setLocation(loc); + } + } + + @Override + protected void programActivated(Program program) { + currentProgram = program; + for (TaintSliceTreeProvider provider : taintTreeProviders.values()) { + provider.programActivated(program); + } + } + + @Override + public void init() { + // DO NOTHING + } + + /* + * 1. Run the pcode extracter. + * 2. Run the indexer. + * 3. Run import a SarifFile and pop the table. + */ + private void createActions() { + + TaintPlugin plugin = this; + + DockingAction exportAllAction = new DockingAction("ExportFacts", HELP_LOCATION) { + + @Override + public void actionPerformed(ActionContext context) { + GhidraState ghidraState = new GhidraState(tool, null, currentProgram, + currentLocation, currentHighlight, currentHighlight); + GhidraScript exportScript = state.getExportScript(consoleService, false); + RunPCodeExportScriptTask export_task = + new RunPCodeExportScriptTask(tool, exportScript, ghidraState, consoleService); + tool.execute(export_task); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + return plugin.getCurrentProgram() != null; + } + + }; + + exportAllAction.setMenuBarData( + new MenuData(new String[] { "Tools", "Source-Sink", "Export PCode Facts" })); + + DockingAction saveTableDataAction = new DockingAction("InitializeIndex", HELP_LOCATION) { + @Override + public void actionPerformed(ActionContext context) { + CreateTargetIndexTask index_task = + new CreateTargetIndexTask(plugin, plugin.getCurrentProgram()); + tool.execute(index_task); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + return plugin.getCurrentProgram() != null; + } + }; + + saveTableDataAction.setMenuBarData( + new MenuData(new String[] { "Tools", "Source-Sink", "Initialize Program Index" })); + + DockingAction deleteFactsAndIndex = new DockingAction("DeleteIndex", HELP_LOCATION) { + @Override + public void actionPerformed(ActionContext context) { + PurgeIndexTask index_task = new PurgeIndexTask(plugin, plugin.getCurrentProgram()); + tool.execute(index_task); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + return plugin.getCurrentProgram() != null; + } + }; + + deleteFactsAndIndex.setMenuBarData( + new MenuData(new String[] { "Tools", "Source-Sink", "Delete Facts and Index" })); + + DockingAction exportFuncAction = new DockingAction("ReexportFacts", HELP_LOCATION) { + + @Override + public void actionPerformed(ActionContext context) { + GhidraState ghidraState = new GhidraState(tool, null, currentProgram, + currentLocation, currentHighlight, currentHighlight); + GhidraScript exportScript = state.getExportScript(consoleService, true); + RunPCodeExportScriptTask export_task = + new RunPCodeExportScriptTask(tool, exportScript, ghidraState, consoleService); + tool.execute(export_task); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + return plugin.getCurrentProgram() != null; + } + + }; + + exportFuncAction.setMenuBarData( + new MenuData(new String[] { "Tools", "Source-Sink", "Re-export Function Facts" })); + + tool.addAction(deleteFactsAndIndex); + tool.addAction(exportAllAction); + tool.addAction(exportFuncAction); + tool.addAction(saveTableDataAction); + } + + public TaintState getTaintState() { + return state; + } + + public TaintProvider getProvider() { + return taintProvider; + } + + public TaintOptions getOptions() { + return taintProvider.getOptions(); + } + + @Override + public Program getCurrentProgram() { + return currentProgram; + } + + @Override + public void processEvent(PluginEvent event) { + super.processEvent(event); + + //Msg.info(this, "TaintPlugin -> processEvent: " + event.toString() ); + + if (event instanceof ProgramClosedPluginEvent) { + Program program = ((ProgramClosedPluginEvent) event).getProgram(); + if (currentProgram != null && currentProgram.equals(program)) { + currentProgram = null; + taintProvider.doSetProgram(null); + } + return; + } + + if (taintProvider == null) { + return; + } + + if (event instanceof ProgramActivatedPluginEvent) { + currentProgram = ((ProgramActivatedPluginEvent) event).getActiveProgram(); + taintProvider.doSetProgram(currentProgram); + if (currentProgram != null) { + SpecExtension.registerOptions(currentProgram); + } + + } + else if (event instanceof ProgramLocationPluginEvent) { + + // user changed their location in the program; this may be a function change. + + taintProvider.contextChanged(); + ProgramLocation location = ((ProgramLocationPluginEvent) event).getLocation(); + Address address = location.getAddress(); + + if (address.isExternalAddress()) { + // ignore external functions when it comes to taint. + return; + } + + if (currentProgram != null) { + // The user loaded a program for analysis. + Listing listing = currentProgram.getListing(); + Function f = listing.getFunctionContaining(address); + // We are in function f + if (currentFunction == null || !currentFunction.equals(f)) { + // In the PAST we were in a function and the program location moved us into a new function. + String cfun = "NULL"; + String nfun = "NULL"; + + if (currentFunction != null) { + cfun = currentFunction.getEntryPoint().toString(); + } + + if (f != null) { + nfun = f.getEntryPoint().toString(); + } + + Msg.info(this, "Changed from function: " + cfun + " to function " + nfun); + currentFunction = f; + taintDecompMarginProvider.functionChanged(); + taintProvider.setTaint(); + } + } + } + } + + private class VertexHighlighter implements CTokenHighlightMatcher { + + @Override + public Color getTokenHighlight(ClangToken token) { + if (currentFunction == null || token == null) { + //log.info("Highlighter> currentFunction == null || token == null"); + return null; + } + + HighFunction highFunction = token.getClangFunction().getHighFunction(); + if (highFunction == null) { + return null; + } + + if (!currentFunction.getEntryPoint() + .equals(highFunction.getFunction().getEntryPoint())) { + return null; + } + + if (taintProvider.matchOn(token)) { + log.info("Highlighter> MATCHED Token: '{}'", token.getText()); + state.augmentAddressSet(token); + return taintProvider.getHighlightColor(token); + } + + return null; + } + + } + + private class TaintLabelHighlighter implements CTokenHighlightMatcher { + + @Override + public Color getTokenHighlight(ClangToken token) { + if (currentFunction == null || token == null) { + //log.info("Highlighter> currentFunction == null || token == null"); + return null; + } + + assert (currentFunction.getEntryPoint() + .equals( + token.getClangFunction().getHighFunction().getFunction().getEntryPoint())); + + if (taintProvider.matchOn(token)) { + log.info("Highlighter> MATCHED Token: '{}'", token.getText()); + return taintProvider.getHighlightColor(token); + } + + return null; + } + + } + + /** + * The concrete highlighter instances created by Ghidra are ClangDecompilerHighlighters. This class applyHighlights and clearHighlights + * using our installed matcher. We are currently caching the query items and the colors that are being applied to maintain consistency in the matcher. There + * is no way to reach in to the matcher to clear that cache; this may be useful. This needs some thought. One may to do this is to create a completely new highlighter + * with a new matcher. This seems like a bad solution. The matching itself is done in the TaintProvider which uses TaintState to maintain the current + * list of ClangTokens we want to match on based on the query results and filter. + * + *

+ * We create a map of highlighters that can be changed via the gui. This provides different strategies for a user to highlight the taint results. + */ + private void initHighlighters() { + // ability to highlight (with many different colors) the source in the decompiler. + highlightService = tool.getService(DecompilerHighlightService.class); + + // Start with the ALL highlighter + CTokenHighlightMatcher matcher = new VertexHighlighter(); + taintProvider.setHighlighter(highlightService, matcher); + } + + /** + * Change the highlighter (token matcher and colors used) to the designated highlighter IF: + *

  • + * the highlight service has been established. + *
  • + * the highlighter instance has been instantiated and added to the decompHighlighters map. + *
+ * + * @param hl - highlighter + */ + public void changeHighlighter(Highlighter hl) { + if (highlightService == null) { + // if not setup, ignore the change request. + return; + } + + CTokenHighlightMatcher matcher = + hl.equals(Highlighter.LABELS) ? new TaintLabelHighlighter() : new VertexHighlighter(); + taintProvider.setHighlighter(highlightService, matcher); + } + + /** + * Gets several services and sets instance variables to those services. + * + * @return The DecompilerMarginService with a TaintDecompilerMarginProvider + */ + public DecompilerProvider getDecompilerProvider() { + + if (marginService == null) { + // ability to add custom margins to the decompiler view + marginService = tool.getService(DecompilerMarginService.class); + marginService.addMarginProvider(taintDecompMarginProvider); + } + + if (highlightService == null) { + initHighlighters(); + } + + if (consoleService == null) { + consoleService = tool.getService(ConsoleService.class); + } + + return (DecompilerProvider) marginService; + } + + public void toggleIcon(MarkType mtype, ClangToken token, boolean bySymbol) { + TaintLabel label; + try { + label = state.toggleMark(mtype, token); + label.setBySymbol(bySymbol); + } + catch (PcodeException e) { + e.printStackTrace(); + return; + } + taintDecompMarginProvider.toggleIcon(label); // mtype, label.isActive()); + Msg.info(this, "Mark Toggle: " + label.toString()); + consoleMessage("Mark Toggle: " + label.toString()); + } + + public void clearIcons() { + taintDecompMarginProvider.clearIcons(); + } + + public void toggleMarginIcon(TaintLabel label) { + taintDecompMarginProvider.toggleIcon(label); + } + + @Override + public void clearTaint() { + taintProvider.clearTaint(); + } + + public void consoleMessage(String msg) { + consoleService.addMessage(this.getName(), msg); + } + + public void makeSelection(List
addrs) { + AddressSet selection = new AddressSet(); + for (Address addr : addrs) { + if (addr == null) + continue; + selection.add(addr); + } + this.setSelection(selection); + } + + public SarifService getSarifService() { + if (sarifService == null) { + sarifService = tool.getService(SarifService.class); + } + return sarifService; + } + + @Override + public void setAddressSet(AddressSet set, boolean clear) { + if (clear) { + taintProvider.clearTaint(); + } + state.setTaintAddressSet(set); + taintProvider.setTaint(); + } + + @Override + public void setVarnodeMap(Map> vmap, boolean clear) { + if (clear) { + taintProvider.clearTaint(); + } + state.setTaintVarnodeMap(vmap); + taintProvider.setTaint(); + } + + @Override + public AddressSet getAddressSet() { + return state.getTaintAddressSet(); + } + + @Override + public Map> getVarnodeMap() { + return state.getTaintVarnodeMap(); + } + +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintProvider.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintProvider.java new file mode 100644 index 0000000000..ce63ee2033 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintProvider.java @@ -0,0 +1,473 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint; + +import java.awt.Color; +import java.util.*; + +import javax.swing.Icon; +import javax.swing.JComponent; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import docking.ActionContext; +import docking.ComponentProvider; +import docking.action.*; +import generic.theme.GIcon; +import ghidra.GhidraOptions; +import ghidra.app.decompiler.*; +import ghidra.app.nav.Navigatable; +import ghidra.app.plugin.core.decompile.DecompilerActionContext; +import ghidra.app.plugin.core.decompile.DecompilerProvider; +import ghidra.app.plugin.core.decompiler.taint.TaintPlugin.Highlighter; +import ghidra.app.plugin.core.decompiler.taint.actions.*; +import ghidra.app.services.CodeViewerService; +import ghidra.framework.options.OptionsChangeListener; +import ghidra.framework.options.ToolOptions; +import ghidra.framework.plugintool.ComponentProviderAdapter; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSet; +import ghidra.program.model.listing.Program; +import ghidra.program.model.pcode.HighFunction; +import ghidra.program.util.ProgramSelection; +import ghidra.util.Msg; +import ghidra.util.Swing; + +public class TaintProvider extends ComponentProviderAdapter implements OptionsChangeListener { + + private static final Logger log = LogManager.getLogger(TaintProvider.class); + + private static final String OPTIONS_TITLE = "Decompiler"; + + private TaintPlugin plugin; + private TaintOptions taintOptions; + private Program program; + + private DecompilerProvider decompilerProvider; + private Navigatable navigatable; + + private TaintState state; + + private DecompilerHighlighter highlighter; + + private Boolean allAccess; + + private TaintCTokenHighlighterPalette highlightPalette; + private int paletteIndex; + + private int matchCount = 0; + + // Use the string and not high token to match on the string shown in the decomp. + private Map cachedHighlightsByToken; + + // Use the string and not high variable to match on the string shown in the + // decomp. + private Map cachedHighlightByAddress; + + private static String showTaintLabelEditTableIcoString = "icon.dialog.error.expandable.stack"; + private static Icon showTaintLabelEditTableIcon = new GIcon(showTaintLabelEditTableIcoString); + + public TaintProvider(TaintPlugin plugin) { + super(plugin.getTool(), "TaintProvider", plugin.getName(), DecompilerActionContext.class); + this.plugin = plugin; + this.taintOptions = new TaintOptions(this); + this.state = plugin.getTaintState(); + this.cachedHighlightsByToken = new HashMap<>(); + this.cachedHighlightByAddress = new HashMap<>(); + this.highlightPalette = new TaintCTokenHighlighterPalette(256); + this.paletteIndex = 0; + initializeDecompilerOptions(); + } + + public TaintOptions getOptions() { + return taintOptions; + } + + @Override + public JComponent getComponent() { + decompilerProvider = plugin.getDecompilerProvider(); + return decompilerProvider.getComponent(); + } + + private void createActions(ComponentProvider provider, boolean isConnected) { + String variableGroup = "2 - Variable Group"; + int subGroupPosition = 0; // reset for the next group + + // These actions are only available in the drop-down window + + TaintSourceAction taintSourceAction = new TaintSourceAction(plugin, state); + setGroupInfo(taintSourceAction, variableGroup, subGroupPosition++); + + TaintSourceBySymbolAction taintSourceBySymbolAction = + new TaintSourceBySymbolAction(plugin, state); + setGroupInfo(taintSourceBySymbolAction, variableGroup, subGroupPosition++); + + TaintSinkAction taintSinkAction = new TaintSinkAction(plugin, state); + setGroupInfo(taintSinkAction, variableGroup, subGroupPosition++); + + TaintSinkBySymbolAction taintSinkBySymbolAction = + new TaintSinkBySymbolAction(plugin, state); + setGroupInfo(taintSinkBySymbolAction, variableGroup, subGroupPosition++); + + TaintGateAction taintGateAction = new TaintGateAction(plugin, state); + setGroupInfo(taintGateAction, variableGroup, subGroupPosition++); + + TaintClearAction taintClearAction = new TaintClearAction(plugin, state); + setGroupInfo(taintClearAction, variableGroup, subGroupPosition++); + + // These actions have an icon and a drop-down menu option in the decompiler window. + TaintQueryAction taintQueryAction = new TaintQueryAction(plugin, state); + TaintQueryDefaultAction taintQueryDefaultAction = + new TaintQueryDefaultAction(plugin, state); + TaintQueryCustomAction taintQueryCustomAction = new TaintQueryCustomAction(plugin, state); + TaintLoadAction taintLoadAction = new TaintLoadAction(plugin, state); + + TaintSliceTreeAction taintSliceTreeAction = new TaintSliceTreeAction(plugin, state); + + DockingAction taintLabelTableAction = new DockingAction("TaintShowLabels", TaintPlugin.HELP_LOCATION) { + + @Override + public void actionPerformed(ActionContext context) { + + TaintLabelsDataFrame df = new TaintLabelsDataFrame(plugin); + df.loadData(); + + TaintLabelsTableProvider table_provider = + new TaintLabelsTableProvider(getName(), plugin, df); + table_provider.addToTool(); + table_provider.setVisible(true); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + return state.hasMarks(); + } + + }; + + taintLabelTableAction + .setMenuBarData( + new MenuData(new String[] { "Source-Sink", taintLabelTableAction.getName() })); + taintLabelTableAction.setToolBarData(new ToolBarData(showTaintLabelEditTableIcon)); + + provider.addLocalAction(taintSliceTreeAction); + provider.addLocalAction(taintLabelTableAction); + provider.addLocalAction(taintSourceAction); + provider.addLocalAction(taintSourceBySymbolAction); + provider.addLocalAction(taintSinkAction); + provider.addLocalAction(taintSinkBySymbolAction); + provider.addLocalAction(taintGateAction); + provider.addLocalAction(taintQueryAction); + provider.addLocalAction(taintQueryDefaultAction); + provider.addLocalAction(taintQueryCustomAction); + provider.addLocalAction(taintLoadAction); + provider.addLocalAction(taintClearAction); + } + + /** + * Sets the group and subgroup information for the given action. + */ + private void setGroupInfo(DockingAction action, String group, int subGroupPosition) { + MenuData popupMenuData = action.getPopupMenuData(); + popupMenuData.setMenuGroup(group); + popupMenuData.setMenuSubGroup(Integer.toString(subGroupPosition)); + } + + @Override + public void componentShown() { + if (program != null) { + ToolOptions opt = tool.getOptions(OPTIONS_TITLE); + taintOptions.grabFromToolAndProgram(plugin, opt, program); + } + } + + /* + * Sets the current program and adds/removes itself as a domainObjectListener + * + * @param newProgram the new program or null to clear out the current program. + */ + public void doSetProgram(Program newProgram) { + program = newProgram; + if (program != null) { + ToolOptions opt = tool.getOptions(OPTIONS_TITLE); + taintOptions.grabFromToolAndProgram(plugin, opt, program); + } + } + + private void initializeDecompilerOptions() { + ToolOptions opt = tool.getOptions(OPTIONS_TITLE); + taintOptions.registerOptions(plugin, opt, program); + + opt.addOptionsChangeListener(this); + + ToolOptions codeBrowserOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS); + codeBrowserOptions.addOptionsChangeListener(this); + } + + @Override + public void optionsChanged(ToolOptions options, String optionName, Object oldValue, + Object newValue) { + if (options.getName().equals(OPTIONS_TITLE) || + options.getName().equals(GhidraOptions.CATEGORY_BROWSER_FIELDS)) { + doRefresh(); + } + } + + private void doRefresh() { + ToolOptions opt = tool.getOptions(OPTIONS_TITLE); + taintOptions.grabFromToolAndProgram(plugin, opt, program); + } + + public void programClosed(Program closedProgram) { + program = null; + } + + @Override + public void contextChanged() { + if (decompilerProvider == null) { + decompilerProvider = plugin.getDecompilerProvider(); + createActions(decompilerProvider, true); + } + tool.contextChanged(decompilerProvider); + } + + /** + * This is called every time we CHANGE FUNCTIONS and have a new decompilation. + *

+ * TODO: We could limit our taint addresses to those in this function...? TODO: + * We should reset the palette cache to start coloring from the start. + */ + public void setTaint() { + if (navigatable == null) { + navigatable = tool.getService(CodeViewerService.class).getNavigatable(); + } + + AddressSet taintAddressSet = state.getTaintAddressSet(); + Msg.info(this, "setTaint(): " + taintAddressSet.toString()); + + // sets the selection in the LISTING? + // TODO: should we not set select and only highlight in the decompilation. + Swing.runIfSwingOrRunLater(() -> { + navigatable.setSelection(new ProgramSelection(taintAddressSet)); + }); + + // Ditch the previous token string to highlight map, so we can restart. + highlighter.clearHighlights(); + + if (!taintOptions.getTaintHighlightStyle().equals(Highlighter.LABELS)) { + this.paletteIndex = 0; + } + + // apply highlights to the decompiler window. + highlighter.applyHighlights(); + } + + public boolean matchOn(ClangToken token) { + + Map> taintVarnodeMap = state.getTaintVarnodeMap(); + + if (taintVarnodeMap == null || taintVarnodeMap.isEmpty() || + token instanceof ClangBreak || + token instanceof ClangTypeToken || + token instanceof ClangSyntaxToken || + token instanceof ClangCommentToken) { + return false; + } + + HighFunction hf = token.getClangFunction().getHighFunction(); + + if (hf == null) { + log.info("\tHighlighter> HighFunction null -- not associated with a function."); + return false; + } + + Address tokenFuncEntryAddr = hf.getFunction().getEntryPoint(); + + // Just the tainted elements that are in this function. + Set funcTaintSet = taintVarnodeMap.get(tokenFuncEntryAddr); + if (funcTaintSet == null || funcTaintSet.isEmpty()) { + return false; + } + + if (token instanceof ClangVariableToken vtoken) { + + if (matchNodeHighVariable(vtoken, hf, funcTaintSet)) { + matchCount++; + return true; + } + + } + else if (token instanceof ClangFieldToken ftoken) { + + if (matchNodeHighVariable(ftoken, hf, funcTaintSet)) { + matchCount++; + return true; + } + + } + else if (token instanceof ClangFuncNameToken fntoken) { + + if (matchNodeFuncName(fntoken.getText(), tokenFuncEntryAddr, funcTaintSet)) { + matchCount++; + return true; + } + } + + return false; + } + + private boolean matchNodeHighVariable(ClangToken token, HighFunction hf, + Set taintSet) { + for (TaintQueryResult taintedVarnode : taintSet) { + addHighlightColor(taintedVarnode); + String match = taintedVarnode.matches(token); + if (match != null) { + log.info("\t\tHighlighter> LOC Match on {}", match); + return true; + } + } + return false; + } + + private boolean matchNodeFuncName(String funcName, Address faddr, + Set taintSet) { + for (TaintQueryResult taintedVarnode : taintSet) { + if (taintedVarnode.matchesFunction(funcName, faddr)) { + log.info("\t\tHighlighter> FUN LOC Match on {} at addr: {}", funcName, faddr); + return true; + } + } + return false; + } + + public void setHighlighter(DecompilerHighlightService highlightService, + CTokenHighlightMatcher matcher) { + if (highlighter != null) { + highlighter.dispose(); + } + DecompilerHighlighter dhl = highlightService.createHighlighter(matcher); + this.highlighter = dhl; + } + + public void clearTaint() { + Msg.info(this, + "TaintProvider: clearTaint() - state clearTaint() and highligher apply highlights."); + matchCount = 0; + state.clearTaint(); + highlighter.clearHighlights(); + cachedHighlightByAddress.clear(); + cachedHighlightsByToken.clear(); + highlighter.applyHighlights(); + } + + /** + * Applies highlights to the tainted labels. + */ + public void repaint() { + highlighter.applyHighlights(); + } + + public void setOption(String option, String path) { + ToolOptions opt = tool.getOptions(OPTIONS_TITLE); + opt.setString(option, path); + } + + public void setOption(String name, Boolean option) { + ToolOptions opt = tool.getOptions(OPTIONS_TITLE); + opt.setBoolean(name, option); + } + + public void setColor(String option, Color color) { + ToolOptions opt = tool.getOptions(OPTIONS_TITLE); + opt.setColor(option, color); + } + + public Color getDefaultHighlightColor() { + return highlightPalette.getDefaultColor(); + } + + /** + * Returns the currently cached color for a ClangToken, or takes the next color + * from the color palette and assigns it to this token. + *

+ * NOTE: token is assumed to not be null + * NOTE 2: this highlights individual variables NOT labels on taint; that should be something we need to do. + * + * @param token - the token we wish to highlight. + * @return the color currently assigned to this specific token. + */ + public Color getHighlightColor(ClangToken token) { + Color hl = null; + + TaintOptions options = getOptions(); + Highlighter style = options.getTaintHighlightStyle(); + if (style.equals(Highlighter.LABELS)) { + Address addr = token.getMinAddress(); + if (addr != null) { + TaintHighlight tl = cachedHighlightByAddress.get(addr); + return tl == null ? null : tl.getColor(); + } + return null; + } + hl = this.cachedHighlightsByToken.get(token.toString()); + + if (hl == null) { + // Color has not been cached, so get a new color. + hl = this.highlightPalette.getColor(this.paletteIndex); + this.cachedHighlightsByToken.put(token.toString(), hl); + this.paletteIndex = (this.paletteIndex + 10) % this.highlightPalette.getSize(); + } + + return hl; + } + + public void addHighlightColor(TaintQueryResult result) { + Address addr = result.getInsnAddr(); + String label = result.getLabel(); + TaintHighlight labelHighlight = TaintHighlight.byLabel(label); + TaintHighlight addrHighlight = cachedHighlightByAddress.get(addr); + + if (addrHighlight == null) { + addrHighlight = labelHighlight; + this.cachedHighlightByAddress.put(addr, addrHighlight); + } + else { + if (!labelHighlight.equals(addrHighlight)) { + if (labelHighlight.getPriority() > addrHighlight.getPriority()) { + this.cachedHighlightByAddress.put(addr, labelHighlight); + } + } + } + } + + public void changeHighlighter(Highlighter hl) { + plugin.changeHighlighter(hl); + } + + public boolean isAllAccess() { + return allAccess; + } + + public void setAllAccess(String taintAllAccess, Boolean allAccess) { + this.allAccess = allAccess; + } + + public int getTokenCount() { + return matchCount; + } + +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintQueryResult.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintQueryResult.java new file mode 100644 index 0000000000..623bf9c620 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintQueryResult.java @@ -0,0 +1,135 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint; + +import java.util.*; + +import com.contrastsecurity.sarif.LogicalLocation; +import com.contrastsecurity.sarif.Run; + +import ghidra.app.decompiler.*; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Function; +import ghidra.program.model.pcode.*; +import sarif.SarifUtils; + +public record TaintQueryResult(String name,String fqname, Address iaddr, Address faddr, List labels, boolean functionLevelResult) { + + public TaintQueryResult(Map result) { + this((String) result.get("name"), + (String) result.get("location"), + (Address) result.get("Address"), + (Address) result.get("entry"), + new ArrayList(), + (Address) result.get("Address") == null); + String value = (String) result.get("value"); + addLabel(value); + } + + public TaintQueryResult(Map result, Run run, LogicalLocation ll) { + this( + SarifUtils.extractDisplayName(fqnFromLoc(run, ll)), + fqnFromLoc(run, ll).getFullyQualifiedName(), + (Address) result.get("Address"), + (Address) result.get("entry"), + new ArrayList(), + (Address) result.get("Address") == null); + String value = (String) result.get("value"); + addLabel(value); + } + + private static LogicalLocation fqnFromLoc(Run run, LogicalLocation ll) { + String fqn = ll.getFullyQualifiedName(); + if (fqn == null) { + ll = SarifUtils.getLogicalLocation(run, ll.getIndex()); + } + return ll; + } + + public String getLabel() { + return this.labels.get(0); + } + + public String getQualifiedName() { + return fqname; + } + + public Address getInsnAddr() { + return iaddr; + } + + public void addLabel(String label) { + labels.add(label); + } + + public boolean hasLabel(String label) { + return labels.contains(label); + } + + public String matches(ClangToken token) { + String text = token.getText(); + Address vaddr = token.getMinAddress(); + HighVariable hv = token.getHighVariable(); + ClangToken hvToken = token; + if (hv == null && token instanceof ClangFieldToken ftoken) { + ClangVariableToken vtoken = TaintState.getParentToken(ftoken); + if (vtoken != null) { + hv = vtoken.getHighVariable(); + hvToken = vtoken; + } + } + if (hv == null) { + return null; + } + HighFunction hf = hv.getHighFunction(); + String hvName = TaintState.hvarName(hvToken); + + // Weed-out check + if (!fqname.contains(hvName) && !fqname.contains(text)) { + return null; + } + Function function = hf.getFunction(); + Varnode vn = token.getVarnode(); + boolean functionLevelToken = function.isThunk() || (vn == null); + if (functionLevelToken || functionLevelResult) { + if (!faddr.equals(function.getEntryPoint())) { + return null; + } + } + else { + // if neither are function-level, the addresses must match + if (!iaddr.equals(vaddr)) { + return null; + } + } + if (hvName.startsWith(":")) { // fqname is FUN@FUN:name:vname + if (fqname.endsWith(hvName) || fqname.endsWith(text)) { + return hvName; + } + } + else { // fqname is FUN@FUN:vname:id + if (fqname.contains(":" + hvName + ":") || fqname.contains(":" + text + ":")) { + return hvName; + } + } + return null; + } + + public boolean matchesFunction(String fname, Address entry_address) { + return name.startsWith(fname) && iaddr.equals(entry_address); + } + +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintRule.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintRule.java new file mode 100644 index 0000000000..103c53ac17 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintRule.java @@ -0,0 +1,60 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint; + +public enum TaintRule { + + UNKNOWN("UNKNOWN"), + SOURCE("Source"), + SINK("Sink"), + GATE("Gate"), + INSN("Instruction"), + VERTEX("Vertex"), + PATH("Path"); + + private String name; + + private TaintRule(String name) { + this.name = name; + } + + public static TaintRule fromRuleId(String ruleId) { + if (ruleId.contains("C0003")) { + return SOURCE; + } + else if (ruleId.contains("C0001")) { + return PATH; + } + else if (ruleId.contains("C0004")) { + return SINK; + } + else if (ruleId.contains("C0002")) { + return INSN; + } + else if (ruleId.contains("C0005")) { + return VERTEX; + } + else { + return UNKNOWN; + } + } + + @Override + public String toString() { + return this.name; + } + +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintService.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintService.java new file mode 100644 index 0000000000..ce1c0054c3 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintService.java @@ -0,0 +1,67 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint; + +import java.util.Map; +import java.util.Set; + +import ghidra.framework.plugintool.ServiceInfo; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSet; +import ghidra.util.Swing; + +/** + * The TaintService provides a general service for retrieving or setting taint from an external engine + *

+ * {@link Swing#runLater(Runnable)} call, which will prevent any deadlock issues. + */ +@ServiceInfo(defaultProvider = TaintPlugin.class, description = "supply taint") +public interface TaintService { + + /** + * Get tainted address set + * @return addresses + */ + public AddressSet getAddressSet(); + + /** + * Set taint using address set + * + * @param set tainted addresses + * @param clear before setting + */ + public void setAddressSet(AddressSet set, boolean clear); + + /** + * Get tainted varnode map + * @return address-to-result map + */ + public Map> getVarnodeMap(); + + /** + * Set taint using varnode map + * + * @param vmap tainted addresses + * @param clear before setting + */ + public void setVarnodeMap(Map> vmap, boolean clear); + + /** + * Clear existing taint + */ + public void clearTaint(); + +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintSliceTreeProvider.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintSliceTreeProvider.java new file mode 100644 index 0000000000..69baf015ba --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintSliceTreeProvider.java @@ -0,0 +1,1104 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint; + +import java.awt.*; +import java.awt.event.*; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.swing.*; +import javax.swing.tree.TreePath; + +import docking.*; +import docking.action.DockingAction; +import docking.action.ToggleDockingAction; +import docking.action.builder.ActionBuilder; +import docking.action.builder.ToggleActionBuilder; +import docking.resources.icons.NumberIcon; +import docking.widgets.dialogs.NumberInputDialog; +import docking.widgets.label.GLabel; +import docking.widgets.tree.*; +import docking.widgets.tree.support.GTreeSelectionEvent.EventOrigin; +import docking.widgets.tree.support.GTreeSelectionListener; +import docking.widgets.tree.tasks.GTreeExpandAllTask; +import generic.theme.GIcon; +import ghidra.app.events.ProgramLocationPluginEvent; +import ghidra.app.events.ProgramSelectionPluginEvent; +import ghidra.app.plugin.core.decompiler.taint.slicetree.*; +import ghidra.app.services.GoToService; +import ghidra.framework.model.*; +import ghidra.framework.plugintool.ComponentProviderAdapter; +import ghidra.framework.preferences.Preferences; +import ghidra.program.database.symbol.FunctionSymbol; +import ghidra.program.model.address.*; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.Symbol; +import ghidra.program.util.*; +import ghidra.util.HTMLUtilities; +import ghidra.util.HelpLocation; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.SwingUpdateManager; +import ghidra.util.task.TaskMonitor; +import resources.Icons; + +public class TaintSliceTreeProvider extends ComponentProviderAdapter + implements DomainObjectListener { + + static final String EXPAND_ACTION_NAME = "Fully Expand Selected Nodes"; + static final String TITLE = "Taint Slice Tree"; + + private static final Icon EMPTY_ICON = Icons.EMPTY_ICON; + private static final Icon EXPAND_ICON = Icons.EXPAND_ALL_ICON; + private static final Icon COLLAPSE_ICON = Icons.COLLAPSE_ALL_ICON; + + private static Icon REFRESH_ICON = new GIcon("icon.plugin.calltree.refresh"); + private static Icon REFRESH_NOT_NEEDED_ICON = + new GIcon("icon.plugin.calltree.refresh.not.needed"); + + public static Icon HIGH_VARIABLE_ICON = new GIcon("icon.debugger.provider.stack"); + public static Icon HIGH_FUNCTION_ICON = new GIcon("icon.plugin.navigation.function"); + public static Icon IN_TAINT_ICON = new GIcon("icon.up"); + public static Icon OUT_TAINT_ICON = new GIcon("icon.down"); + public static Icon TAINT_ICON = new GIcon("icon.version.tracking.package"); + + private static final String RECURSE_DEPTH_PROPERTY_NAME = "call.tree.recurse.depth"; + private static final String DEFAULT_RECURSE_DEPTH = "5"; + + private final TaintPlugin plugin; + + private JComponent component; + private JSplitPane splitPane; + private GTree inTree; + private GTree outTree; + private boolean isPrimary; + private enum Condition {IN, OUT, EITHER} + + private SwingUpdateManager reloadUpdateManager = new SwingUpdateManager(500, () -> doUpdate()); + + private Program currentProgram; + private Function currentFunction; + private ToggleDockingAction filterDuplicates; + private ToggleDockingAction navigationOutgoingAction; + private ToggleDockingAction navigationIncomingAction; + private DockingAction refreshAction; + + private boolean isFiringNavigationEvent; + + /** + * A variable used to restrict open-ended operations, like expanding all nodes or filtering. + */ + private AtomicInteger recurseDepth = new AtomicInteger(); + private NumberIcon recurseIcon; + + public TaintSliceTreeProvider(TaintPlugin plugin, boolean isPrimary) { + super(plugin.getTool(), TITLE, plugin.getName()); + this.plugin = plugin; + this.isPrimary = isPrimary; + + component = buildComponent(); + + // try to give the trees a suitable amount of space by default + component.setPreferredSize(new Dimension(800, 400)); + + setWindowMenuGroup(TITLE); + // This puts it into the bottom of the main window. + // setDefaultWindowPosition(WindowPosition.BOTTOM); + // This puts it into ITS OWN window. + setDefaultWindowPosition(WindowPosition.WINDOW); + + setIcon(TaintPlugin.PROVIDER_ICON); + setHelpLocation(new HelpLocation(plugin.getName(), "Taint_Slice_Tree_Plugin")); + + addToTool(); + loadRecurseDepthPreference(); + createActions(); + } + + private void createActions() { + + String expandMenu = Integer.toString(1); + String selectionMenuGroup = Integer.toString(2); + String goToMenu = Integer.toString(3); + + String homeToolbarGroup = Integer.toString(1); + String filterOptionsToolbarGroup = Integer.toString(2); + String navigationOptionsToolbarGroup = Integer.toString(3); + String ownerName = plugin.getName(); + + //@formatter:off + + // + // fully expand + // + new ActionBuilder(EXPAND_ACTION_NAME, ownerName) + //.description("Make program selection of function starts from selected rows") + .helpLocation(new HelpLocation(ownerName, "Call_Tree_Context_Action_Expand_Nodes")) + //.toolBarIcon(SELECT_FUNCTIONS_ICON) + .popupMenuIcon(EXPAND_ICON) + .popupMenuPath("Expand Nodes to Depth Limit") + .popupMenuGroup(expandMenu) + .popupWhen(c -> isValidContext(c, Condition.EITHER)) + .enabledWhen(c -> isValidContext(c, Condition.EITHER)) + .onAction(c -> expandToDepth(c)) + .buildAndInstallLocal(this) + ; + + // + // collapse all + // + new ActionBuilder("Collapse All", ownerName) + .helpLocation(new HelpLocation(ownerName, "Call_Tree_Context_Action_Collapse_Nodes")) + .popupMenuIcon(COLLAPSE_ICON) + .popupMenuPath("Collapse All Nodes") + .popupMenuGroup(expandMenu) + .popupWhen(c -> getSelectedPaths(c) != null) + .enabledWhen(c -> isValidContextSel(c, Condition.EITHER)) + .onAction(c -> collapseAll(c)) + .buildAndInstallLocal(this) + ; + + // + // goto + // + new ActionBuilder("Go To Destination", ownerName) + .helpLocation(new HelpLocation(ownerName, "Call_Tree_Context_Action_Goto_Destination")) + .popupMenuPath("Go To Call Destination") + .popupMenuGroup(goToMenu) + .popupWhen(c -> addToPopupNotRoot(c)) + .enabledWhen(c -> isValidContextSel(c, Condition.OUT)) + .onAction(c -> gotToDest(c)) + .buildAndInstallLocal(this) + ; + + new ActionBuilder("Go To Source", ownerName) + .helpLocation(new HelpLocation(ownerName, "Call_Tree_Context_Action_Goto_Source")) + .popupMenuPath("Go To Call Source") + .popupMenuGroup(goToMenu) + .popupWhen(c -> addToPopupNotRoot(c)) + .enabledWhen(c -> isValidContextSel(c, Condition.EITHER)) + .onAction(c -> gotToSrc(c)) + .buildAndInstallLocal(this) + ; + + // + // recurse depth + // + new ActionBuilder("Recurse Depth", ownerName) + .description("Recurse Depth

Limits the depth to " + "which recursing tree operations" + + "
will go. Example operations include Expand All and filtering") + .helpLocation(new HelpLocation(ownerName, "Call_Tree_Action_Recurse_Depth")) + .toolBarGroup(filterOptionsToolbarGroup, "2") + .toolBarIcon(new NumberIcon(recurseDepth.get())) + .onAction(c -> recurseDepth(c)) + .buildAndInstallLocal(this) + ; + + // + // selection actions + // + new ActionBuilder("Select Call Source", ownerName) + .helpLocation(new HelpLocation(ownerName, "Call_Tree_Context_Action_Select_Source")) + .popupMenuIcon(new GIcon("icon.plugin.calltree.filter.select.source")) + .popupMenuPath("Select Call Source") + .popupMenuGroup(selectionMenuGroup) + .popupWhen(c -> addToPopupMult(c)) + .enabledWhen(c -> isValidContextMult(c, Condition.EITHER)) + .onAction(c -> makeSelectionFromPaths(c, true)) + .buildAndInstallLocal(this) + ; + + new ActionBuilder("Select Call Destination", ownerName) + .helpLocation(new HelpLocation(ownerName, "Call_Tree_Context_Action_Select_Destination")) + .popupMenuIcon(new GIcon("icon.plugin.calltree.filter.select.source")) + .popupMenuPath("Select Call Destination") + .popupMenuGroup(selectionMenuGroup) + .popupWhen(c -> addToPopupMult(c)) + .enabledWhen(c -> isValidContextMult(c, Condition.OUT)) + .onAction(c -> makeSelectionFromPaths(c, false)) + .buildAndInstallLocal(this) + ; + + // + // home button + // + new ActionBuilder("Home", ownerName) + .helpLocation(new HelpLocation(ownerName, "Call_Tree_Action_Home")) + .toolBarGroup(homeToolbarGroup) + .toolBarIcon(Icons.HOME_ICON) + .enabledWhen(c -> currentFunction != null) + .onAction(c -> home(c)) + .buildAndInstallLocal(this) + ; + + refreshAction = new ActionBuilder("Refresh", ownerName) + .description("Push at any time to refresh the current trees.
This is highlighted when the data may be stale.
") + .helpLocation(new HelpLocation(ownerName, "Call_Tree_Action_Refresh")) + .toolBarGroup(homeToolbarGroup) + .toolBarIcon(REFRESH_NOT_NEEDED_ICON) + .enabledWhen(c -> true) + .onAction(c -> reloadUpdateManager.updateNow()) + .buildAndInstallLocal(this) + ; + + // + // filter duplicates action + // + filterDuplicates = new ToggleActionBuilder("Filter Duplicates", ownerName) + .description("Push at any time to refresh the current trees.
This is highlighted when the data may be stale.
") + .helpLocation(new HelpLocation(ownerName, "Call_Tree_Action_Filter")) + .toolBarGroup(filterOptionsToolbarGroup, "1") + .toolBarIcon(new GIcon("icon.plugin.calltree.filter.duplicates")) + .selected(true) + .onAction(c -> doUpdate()) + .buildAndInstallLocal(this) + ; + + // + // navigate outgoing nodes on selection + // + navigationOutgoingAction = new ToggleActionBuilder("Navigate Outgoing Nodes", ownerName) + .description("Outgoing Navigation

Toggled on triggers node selections
in the tree to navigate the Listing to
the source location of the call") + .helpLocation(new HelpLocation(ownerName, "Call_Tree_Action_Navigation")) + .toolBarGroup(navigationOptionsToolbarGroup, "1") + .toolBarIcon(Icons.NAVIGATE_ON_OUTGOING_EVENT_ICON) + .selected(true) + .onAction(c -> { + //IGNORE + }) + .buildAndInstallLocal(this) + ; + + // + // navigate incoming nodes on selection + // + navigationIncomingAction = new ToggleActionBuilder("Navigate Incoming Location Changes", ownerName) + .description(HTMLUtilities.toHTML("Incoming Navigation" + + "

Toggle On - change the displayed " + + "function on Listing navigation events" + + "
Toggled Off - don't change the displayed function on Listing navigation events")) + .helpLocation(new HelpLocation(ownerName, "Call_Tree_Action_Incoming_Navigation")) + .toolBarGroup(navigationOptionsToolbarGroup, "2") + .toolBarIcon(Icons.NAVIGATE_ON_INCOMING_EVENT_ICON) + .selected(isPrimary) + .onAction(c -> { + if (navigationIncomingAction.isSelected()) { + setLocation(plugin.getCurrentLocation()); + }}) + .buildAndInstallLocal(this) + ; + + //@formatter:on + } + + + private boolean isValidSelection(ActionContext context) { + Object contextObject = context.getContextObject(); + GTree gTree = (GTree) contextObject; + if (gTree.getSelectionPaths().length != 1) { + return false; + } + + TreePath path = gTree.getSelectionPath(); + if (path == null) { + return false; + } + + GTreeNode node = (GTreeNode) path.getLastPathComponent(); + return (node instanceof SliceNode); + } + + private boolean isValidContext(ActionContext context, Condition cond) { + Object contextObject = context.getContextObject(); + if (!(contextObject instanceof GTree)) { + return false; + } + if (contextObject == inTree && cond == Condition.OUT) { + return false; + } + if (contextObject == outTree && cond == Condition.IN) { + return false; + } + + return true; + } + + private boolean isValidContextSel(ActionContext context, Condition cond) { + return isValidContext(context, cond) && isValidSelection(context); + } + + private boolean isValidContextMult(ActionContext context, Condition cond) { + if (!isValidContext(context, cond)) { + return false; + } + if (currentFunction == null) { + return false; + } + + Object contextObject = context.getContextObject(); + GTree gTree = (GTree) contextObject; + TreePath[] selectionPaths = gTree.getSelectionPaths(); + return selectionPaths.length > 0; + } + + private TreePath[] getSelectedPaths(ActionContext context) { + Object contextObject = context.getContextObject(); + if (!(contextObject instanceof GTree)) { + return null; + } + + GTree gTree = (GTree) contextObject; + TreePath[] selectionPaths = gTree.getSelectionPaths(); + if (selectionPaths.length == 0) { + return null; + } + return selectionPaths; + } + + private boolean addToPopupNotRoot(ActionContext context) { + TreePath[] selectionPaths = getSelectedPaths(context); + if (selectionPaths == null) { + return false; + } + for (TreePath path : selectionPaths) { + GTreeNode node = (GTreeNode) path.getLastPathComponent(); + if (node.isRoot()) { + return false; + } + } + return true; + } + + private boolean addToPopupMult(ActionContext context) { + if (!addToPopupNotRoot(context)) { + return false; + } + return isValidContextMult(context, Condition.OUT); + } + + + private void expandToDepth(ActionContext context) { + Object contextObject = context.getContextObject(); + GTree gTree = (GTree) contextObject; + TreePath[] paths = gTree.getSelectionPaths(); + for (TreePath treePath : paths) { + GTreeNode node = (GTreeNode) treePath.getLastPathComponent(); + gTree.runTask(new ExpandToDepthTask(gTree, node, recurseDepth.get())); + } + } + + private void collapseAll(ActionContext context) { + Object contextObject = context.getContextObject(); + GTree gTree = (GTree) contextObject; + GTreeNode rootNode = gTree.getViewRoot(); + List children = rootNode.getChildren(); + for (GTreeNode child : children) { + gTree.collapseAll(child); + } + } + + private void gotToDest(ActionContext context) { + Object contextObject = context.getContextObject(); + GTree gTree = (GTree) contextObject; + TreePath path = gTree.getSelectionPath(); + SliceNode node = (SliceNode) path.getLastPathComponent(); + goTo(node.getLocation()); + } + + private void gotToSrc(ActionContext context) { + Object contextObject = context.getContextObject(); + GTree gTree = (GTree) contextObject; + TreePath path = gTree.getSelectionPath(); + SliceNode node = (SliceNode) path.getLastPathComponent(); + goTo(new ProgramLocation(currentProgram, node.getSourceAddress())); + } + + private void recurseDepth(ActionContext context) { + NumberInputDialog dialog = + new NumberInputDialog("", "", recurseDepth.get(), 0, Integer.MAX_VALUE, false); + if (!dialog.show()) { + return; + } + + int newValue = dialog.getValue(); + setRecurseDepth(newValue); + } + + private void home(ActionContext context) { + FunctionSignatureFieldLocation location = new FunctionSignatureFieldLocation( + currentProgram, currentFunction.getEntryPoint()); + goTo(location); + } + + private void makeSelectionFromPaths(ActionContext context, boolean selectSource) { + GTree gTree = (GTree) context.getContextObject(); + TreePath[] paths = gTree.getSelectionPaths(); + AddressSet set = new AddressSet(); + for (TreePath path : paths) { + SliceNode sliceNode = (SliceNode) path.getLastPathComponent(); + Address address = null; + if (selectSource) { + address = sliceNode.getSourceAddress(); + } + else { + address = sliceNode.getLocation().getAddress(); + } + set.addRange(address, address); + } + + ProgramSelection selection = new ProgramSelection(set); + tool.firePluginEvent( + new ProgramSelectionPluginEvent(plugin.getName(), selection, currentProgram)); + } + + private void goTo(ProgramLocation location) { + isFiringNavigationEvent = true; + GoToService goToService = tool.getService(GoToService.class); + if (goToService != null) { + goToService.goTo(location); + isFiringNavigationEvent = false; + return; + } + + // no goto service...navigate the old fashioned way (this doesn't have history) + plugin.firePluginEvent(new ProgramLocationPluginEvent(getName(), location, currentProgram)); + isFiringNavigationEvent = false; + } + + @Override + public ActionContext getActionContext(MouseEvent e) { + if (e == null) { + return new DefaultActionContext(this, getActiveComponent()); + } + + Object source = e.getSource(); + if (source instanceof JTree) { + JTree jTree = (JTree) source; + GTree gTree = inTree; + if (outTree.isMyJTree(jTree)) { + gTree = outTree; + } + return new DefaultActionContext(this, gTree); + } + + return null; + } + + private Component getActiveComponent() { + KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); + Component focusOwner = manager.getFocusOwner(); + if (focusOwner == null) { + return component; + } + + if (SwingUtilities.isDescendingFrom(focusOwner, outTree)) { + return outTree; + } + if (SwingUtilities.isDescendingFrom(focusOwner, inTree)) { + return inTree; + } + return component; + } + + private JComponent buildComponent() { + JPanel container = new JPanel(new BorderLayout()); + + splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); + + inTree = createTree(); + outTree = createTree(); + + GTreeSelectionListener treeSelectionListener = e -> { + if (e.getEventOrigin() != EventOrigin.USER_GENERATED) { + return; + } + + if (!navigationOutgoingAction.isSelected()) { + return; + } + + if (currentFunction == null) { + return; + } + + TreePath path = e.getPath(); + if (path == null) { + return; + } + + SliceNode node = (SliceNode) path.getLastPathComponent(); + Address sourceAddress = node.getSourceAddress(); + goTo(new ProgramLocation(currentProgram, sourceAddress)); + }; + + outTree.addGTreeSelectionListener(treeSelectionListener); + inTree.addGTreeSelectionListener(treeSelectionListener); + + GTreeSelectionListener contextSelectionListener = e -> notifyContextChanged(); + inTree.addGTreeSelectionListener(contextSelectionListener); + outTree.addGTreeSelectionListener(contextSelectionListener); + + splitPane.setLeftComponent(createTreePanel(true, inTree)); + splitPane.setRightComponent(createTreePanel(false, outTree)); + splitPane.addHierarchyListener(new HierarchyListener() { + @Override + public void hierarchyChanged(HierarchyEvent e) { + long changeFlags = e.getChangeFlags(); + if (HierarchyEvent.DISPLAYABILITY_CHANGED == (changeFlags & + HierarchyEvent.DISPLAYABILITY_CHANGED)) { + + // check for the first time we are put together + if (splitPane.isDisplayable()) { + SwingUtilities.invokeLater(() -> splitPane.setDividerLocation(.5)); + splitPane.removeHierarchyListener(this); + } + } + } + }); + + container.add(splitPane, BorderLayout.CENTER); + + return container; + } + + private JPanel createTreePanel(boolean isIncoming, GTree tree) { + JPanel panel = new JPanel(new BorderLayout()); + + panel.add( + new GLabel( + isIncoming ? "Backward Taint Tree (in nodes)" : "Forward Taint Tree (out nodes)"), + BorderLayout.NORTH); + panel.add(tree, BorderLayout.CENTER); + + return panel; + } + + private GTree createTree() { + GTree tree = new GTree(new EmptyRootNode()); + tree.setPaintHandlesForLeafNodes(false); + return tree; + } + + @Override + public JComponent getComponent() { + return component; + } + + @Override + public void componentShown() { + reload(); + } + + @Override + public void componentHidden() { + if (!isPrimary) { + plugin.removeProvider(this); + } + } + + public void reload() { + setLocation(plugin.getCurrentLocation()); + } + + public void dispose() { + reloadUpdateManager.dispose(); + inTree.dispose(); + outTree.dispose(); + if (currentProgram != null) { + currentProgram.removeListener(this); + currentProgram = null; + } + + refreshAction.dispose(); + filterDuplicates.dispose(); + navigationOutgoingAction.dispose(); + navigationIncomingAction.dispose(); + } + + public void setLocation(ProgramLocation location) { + if (isFiringNavigationEvent) { + return; + } + + if (!followLocationChanges()) { + return; + } + + if (!isVisible()) { + return; + } + + doSetLocation(location); + } + + private void doSetLocation(ProgramLocation location) { + if (location == null) { + setFunction(null); + return; + } + + if (currentProgram == null) { + // This can happen when we were created to lock onto one location and then the program + // for that location was closed. Then, the user pressed the button to track location + // changes, which means we will get here while setting the location, but our program + // will have been null'ed out. + currentProgram = plugin.getCurrentProgram(); + currentProgram.addListener(this); + } + + // TODO Don't need the function - need the High Variable / High Function. + Function function = plugin.getFunction(location); + setFunction(function); + } + + private void setFunction(Function function) { + if (function != null && function.equals(currentFunction)) { + return; + } + + doSetFunction(function); + } + + private void doSetFunction(Function function) { + currentFunction = function; + notifyContextChanged(); + if (currentFunction == null) { + clearTrees(); + return; + } + + // NB: This will clear out the trees and set a pending node because the "update" may take a while. + // in our case I don't think that is the case, but we'll keep this. + resetTrees(); + updateTitle(); + + // This is where the real update and tree building happens. + // this actually calls this classes doUpdate method. + reloadUpdateManager.update(); + } + + private void notifyContextChanged() { + tool.contextChanged(this); + } + + private void clearTrees() { + if (inTree.getModelRoot() instanceof EmptyRootNode) { + // already empty + return; + } + + updateTitle(); + inTree.setRootNode(new EmptyRootNode()); + outTree.setRootNode(new EmptyRootNode()); + } + + /** + * NB: I think this is the real start of building the trees. + */ + private void resetTrees() { + inTree.setRootNode(new PendingRootNode()); + outTree.setRootNode(new PendingRootNode()); + } + + /** + * NB: This is key as well. + * + * this is called in the doSetFunctio nmethod + */ + private void doUpdate() { + updateIncomingReferences(currentFunction); + updateOutgoingReferences(currentFunction); + setStale(false); + } + + private void updateIncomingReferences(Function function) { + GTreeNode rootNode = null; + if (function == null) { + // no function, just do an empty node. + rootNode = new EmptyRootNode(); + } + else { + // The root node in the left panel. + rootNode = new InSliceRootNode(currentProgram, function, function.getEntryPoint(), + filterDuplicates.isSelected(), recurseDepth); + } + + // this does the recusing? + inTree.setRootNode(rootNode); + } + + private void updateOutgoingReferences(Function function) { + GTreeNode rootNode = null; + if (function == null) { + rootNode = new EmptyRootNode(); + } + else { + rootNode = new OutSliceRootNode(currentProgram, function, function.getEntryPoint(), + filterDuplicates.isSelected(), recurseDepth); + } + + outTree.setRootNode(rootNode); + } + + private void updateTitle() { + String title = TITLE; + String subTitle = ""; + if (currentFunction != null) { + // TODO Don't need function name but some high variable string. + String programName = + (currentProgram != null) ? currentProgram.getDomainFile().getName() : ""; + title = TITLE + ": " + currentFunction.getName(); + subTitle = " (" + programName + ")"; + } + + setTitle(title); + setSubTitle(subTitle); + } + + public void initialize(Program program, ProgramLocation location) { + if (program == null) { // no program open + setLocation(null); + return; + } + + currentProgram = program; + currentProgram.addListener(this); + doSetLocation(location); + } + + public void programActivated(Program program) { + if (!followLocationChanges() && currentProgram != null) { + return; // don't respond; just keep our data + } + + currentProgram = program; + currentProgram.addListener(this); + setLocation(plugin.getCurrentLocation()); + } + + public void programDeactivated(Program program) { + if (!followLocationChanges()) { + return; // don't respond; just keep our data + } + + clearState(); + } + + public void programClosed(Program program) { + if (program != currentProgram) { + return; // not my program + } + + program.removeListener(this); + clearState(); + + currentProgram = null; + } + + private void clearState() { + inTree.cancelWork(); + outTree.cancelWork(); + currentFunction = null; + reloadUpdateManager.update(); + } + + public boolean isShowingLocation(ProgramLocation location) { + if (currentFunction == null) { + return false; + } + + AddressSetView body = currentFunction.getBody(); + return body.contains(location.getAddress()); + } + + private boolean followLocationChanges() { + return navigationIncomingAction.isSelected(); + } + + @Override + public void domainObjectChanged(DomainObjectChangedEvent event) { + if (!isVisible()) { + return; + } + + if (isEmpty()) { + return; // nothing to update + } + + if (event.contains(DomainObjectEvent.RESTORED)) { + setStale(true); + return; + } + + for (int i = 0; i < event.numRecords(); i++) { + DomainObjectChangeRecord domainObjectRecord = event.getChangeRecord(i); + ProgramEvent eventType = (ProgramEvent) domainObjectRecord.getEventType(); + + switch (eventType) { + case MEMORY_BLOCK_MOVED: + case MEMORY_BLOCK_REMOVED: + case SYMBOL_ADDED: + case SYMBOL_REMOVED: + case REFERENCE_ADDED: + case REFERENCE_REMOVED: + setStale(true); + break; + case SYMBOL_RENAMED: + Symbol symbol = (Symbol) ((ProgramChangeRecord) domainObjectRecord).getObject(); + if (!(symbol instanceof FunctionSymbol)) { + break; + } + + FunctionSymbol functionSymbol = (FunctionSymbol) symbol; + Function function = (Function) functionSymbol.getObject(); + if (updateRootNodes(function)) { + return; // the entire tree will be rebuilt + } + + inTree.runTask(new UpdateFunctionNodeTask(inTree, function)); + outTree.runTask(new UpdateFunctionNodeTask(outTree, function)); + break; + default: + break; + } + } + } + + private boolean isEmpty() { + GTreeNode rootNode = inTree.getModelRoot(); + return rootNode instanceof EmptyRootNode; + } + + private boolean updateRootNodes(Function function) { + GTreeNode root = inTree.getModelRoot(); + // root might be a "PendingRootNode" + //TODO do we need to use a PendingRootNode? + if (root instanceof SliceNode) { + SliceNode sliceNode = (SliceNode) root; + Function nodeFunction = sliceNode.getRemoteFunction(); + if (nodeFunction.equals(function)) { + reloadUpdateManager.update(); + return true; + } + } + + return false; + } + + private class UpdateFunctionNodeTask extends GTreeTask { + + private Function function; + + protected UpdateFunctionNodeTask(GTree tree, Function function) { + super(tree); + this.function = function; + } + + @Override + public void run(TaskMonitor monitor) throws CancelledException { + SliceNode rootNode = (SliceNode) tree.getModelRoot(); + List children = rootNode.getChildren(); + for (GTreeNode node : children) { + updateFunction((SliceNode) node); + } + } + + private void updateFunction(SliceNode node) { + if (!node.isLoaded()) { + // children not loaded, don't force a load by asking for them + return; + } + + // first, if the given node represents the function we have, then we don't need to + // go any further + if (function.equals(node.getRemoteFunction())) { + GTreeNode parent = node.getParent(); + parent.removeNode(node); + parent.addNode(node.recreate()); + return; + } + + List children = node.getChildren(); + for (GTreeNode child : children) { + updateFunction((SliceNode) child); + } + } + } + + private void setStale(boolean stale) { + if (stale) { + refreshAction.getToolBarData().setIcon(REFRESH_ICON); + } + else { + refreshAction.getToolBarData().setIcon(REFRESH_NOT_NEEDED_ICON); + } + } + +//================================================================================================== +// Service-like Methods +//================================================================================================== + + public void setRecurseDepth(int depth) { + if (depth < 1) { + return; // always have at least one level showing + } + + if (recurseDepth.get() == depth) { + return; + } + + this.recurseDepth.set(depth); + this.recurseIcon.setNumber(depth); + + removeFilterCache(); + inTree.refilterLater(); + outTree.refilterLater(); + + saveRecurseDepth(); + } + + /** + * The nodes will cache filtered values, which are restricted by the recurse depth. We + * have to remove this cache to get the nodes to reload children to the new depth. + */ + private void removeFilterCache() { + // + // I don't like this, BTW. The problem with this approach is that you lose any loading + // you have done. Normally this is not that big of a problem. However, if the loading + // takes a long time, then you lose some work. + // + GTreeNode rootNode = inTree.getModelRoot(); + rootNode.removeAll(); + rootNode = outTree.getModelRoot(); + rootNode.removeAll(); + } + + private void saveRecurseDepth() { + Preferences.setProperty(RECURSE_DEPTH_PROPERTY_NAME, Integer.toString(recurseDepth.get())); + Preferences.store(); + } + + private void loadRecurseDepthPreference() { + String value = Preferences.getProperty(RECURSE_DEPTH_PROPERTY_NAME, DEFAULT_RECURSE_DEPTH); + int intValue; + try { + intValue = Integer.parseInt(value); + } + catch (NumberFormatException nfe) { + intValue = Integer.parseInt(DEFAULT_RECURSE_DEPTH); + } + + recurseDepth.set(intValue); + } + + public int getRecurseDepth() { + return recurseDepth.get(); + } + + public void setIncomingFilter(String text) { + inTree.setFilterText(text); + } + + public void setOutgoingFilter(String text) { + outTree.setFilterText(text); + } + +//================================================================================================== +// Inner Classes +//================================================================================================== + + private class ExpandToDepthTask extends GTreeExpandAllTask { + + private int maxDepth; + + public ExpandToDepthTask(GTree tree, GTreeNode node, int maxDepth) { + super(tree, node); + + // base max depth upon you starting location + TreePath treePath = node.getTreePath(); + int startDepth = treePath.getPathCount(); + this.maxDepth = maxDepth + startDepth - 1; // -1--don't count the root node in the depth + } + + @Override + protected void expandNode(GTreeNode node, boolean force, TaskMonitor monitor) + throws CancelledException { + TreePath treePath = node.getTreePath(); + Object[] path = treePath.getPath(); + if (path.length > maxDepth) { + return; + } + + if (!force && !node.isAutoExpandPermitted()) { + return; + } + + SliceNode sliceNode = (SliceNode) node; + if (sliceNode.functionIsInPath()) { + return; // this path hit a function that is already in the path + } + + super.expandNode(node, false, monitor); + } + } + + private class PendingRootNode extends GTreeNode { + + @Override + public Icon getIcon(boolean expanded) { + return TaintPlugin.FUNCTION_ICON; + } + + @Override + public String getName() { + return "Pending..."; + } + + @Override + public String getToolTip() { + return null; + } + + @Override + public boolean isLeaf() { + return true; + } + } + + private class EmptyRootNode extends GTreeNode { + + @Override + public Icon getIcon(boolean expanded) { + return EMPTY_ICON; + } + + @Override + public String getName() { + return "No Function"; + } + + @Override + public String getToolTip() { + return null; + } + + @Override + public boolean isLeaf() { + return true; + } + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintState.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintState.java new file mode 100644 index 0000000000..828ba22c17 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/TaintState.java @@ -0,0 +1,156 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint; + +import java.io.File; +import java.util.*; + +import com.contrastsecurity.sarif.SarifSchema210; + +import ghidra.app.decompiler.*; +import ghidra.app.plugin.core.decompiler.taint.ctadl.TaintStateCTADL; +import ghidra.app.script.GhidraScript; +import ghidra.app.services.ConsoleService; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSet; +import ghidra.program.model.listing.Program; +import ghidra.program.model.pcode.*; +import ghidra.program.model.symbol.Symbol; +import ghidra.program.model.symbol.SymbolTable; + +/** + * The interface for the methods that collect desired taint information from the decompiler window and store them + * for construction of queries and indexing. + */ +public interface TaintState { + + public enum MarkType { + SOURCE, SINK, GATE + } + + public enum QueryType { + SRCSINK, DEFAULT, CUSTOM + } + + public static TaintState newInstance(TaintPlugin plugin) { + return new TaintStateCTADL(plugin); + } + + /** + * Perform a Source-Sink query on the index database. + * + * @param program the program whose pcode is being queried. + * @param tool - + * @param queryType true -> perform the default query (i.e., do not build the query from the selected source) + * @return success + */ + public boolean queryIndex(Program program, PluginTool tool, QueryType queryType); + + public TaintLabel toggleMark(MarkType mtype, ClangToken token) throws PcodeException; + + public Set getTaintLabels(MarkType mtype); + + public boolean isValid(); + + public AddressSet getTaintAddressSet(); + + public void setTaintAddressSet(AddressSet aset); + + public void augmentAddressSet(ClangToken token); + + public void clearTaint(); + + public boolean isSink(HighVariable hvar); + + public void clearMarkers(); + + public void loadTaintData(Program program, File sarif_file); + + public SarifSchema210 getData(); + + public void clearData(); + + public TaintOptions getOptions(); + + // predicate that indicates there are sources, sinks, or gates. + public boolean hasMarks(); + + public boolean wasCancelled(); + + public void setCancellation(boolean status); + + public void setTaintVarnodeMap(Map> vmap); + + public Map> getTaintVarnodeMap(); + + public void buildIndex(List param_list, String engine_path, String facts_path, + String index_directory); + + public GhidraScript getExportScript(ConsoleService console, boolean perFunction); + + public static String hvarName(ClangToken token) { + HighVariable hv = token.getHighVariable(); + HighFunction hf = + (hv == null) ? token.getClangFunction().getHighFunction() : hv.getHighFunction(); + if (hv == null || hv.getName() == null || hv.getName().equals("UNNAMED")) { + SymbolTable symbolTable = hf.getFunction().getProgram().getSymbolTable(); + Varnode rep = hv.getRepresentative(); + Address addr = rep.getAddress(); + Symbol symbol = symbolTable.getPrimarySymbol(addr); + if (symbol == null) { + if (hv instanceof HighLocal) { + return addr.toString(); + } + return token.getText(); + } + return symbol.getName(); + } + return hv.getName(); + } + + public static ClangVariableToken getParentToken(ClangFieldToken token) { + ClangTokenGroup group = (ClangTokenGroup) token.Parent(); + Iterator iterator = group.iterator(); + while (iterator.hasNext()) { + ClangNode next = iterator.next(); + if (next instanceof ClangVariableToken vtoken) { + HighVariable highVariable = vtoken.getHighVariable(); + if (highVariable == null || highVariable instanceof HighConstant) { + continue; + } + return vtoken; + } + } + return null; + } + + public static boolean isActualParam(ClangToken token) { + PcodeOp pcodeOp = token.getPcodeOp(); + if (pcodeOp != null) { + String mnemonic = pcodeOp.getMnemonic(); + if (mnemonic.contains("CALL")) { + for (Varnode input : pcodeOp.getInputs()) { + if (input.equals(token.getVarnode())) { + return true; + } + } + } + } + return false; + } + +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/VertexHighlighterMatcher.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/VertexHighlighterMatcher.java new file mode 100644 index 0000000000..c096a24df0 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/VertexHighlighterMatcher.java @@ -0,0 +1,81 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint; + +import java.awt.Color; +import java.util.HashMap; +import java.util.Map; + +import ghidra.app.decompiler.CTokenHighlightMatcher; +import ghidra.app.decompiler.ClangToken; + +/** + * A highlighter maps highlight colors to ClangTokens in the decompilation. Which colors to use for each token is determined + * by some strategy. + * + *

+ * The VertexHighlighter applies a different color to each token. All of the highlighted tokens are tainted. + * + *

+ * There are two determinations a Highlighter must make: + * + *

  1. + * Does the token match an element returned from a Source-Sink query that should be highlighted. + *
  2. + * If a match is made, what color to apply to the token; the CONSISTENCY of a highlight color is determined by some + * characteristic of the returned query, e.g., a specific location, a specific variable name, a specific taint label. + *
+ * Making BOTH of the above determinations require access to the query data. + * + *

+ * The TaintProvider is a good place to determine whether there is a match; however, it should return the TaintLabelId instance + * that captures critical information to make a color determination. + */ +public class VertexHighlighterMatcher implements CTokenHighlightMatcher { + + TaintCTokenHighlighterPalette palette; + TaintProvider taintProvider; + + Map cachedHighlights; + + int nextColorIndex; + + public VertexHighlighterMatcher(TaintProvider taintProvider, TaintCTokenHighlighterPalette palette) { + this.taintProvider = taintProvider; + this.palette = palette; + this.nextColorIndex = 0; + this.cachedHighlights = new HashMap<>(); + } + + /** + * The basic method clients must implement to determine if a token should be + * highlighted. Returning a non-null Color will trigger the given token to be + * highlighted. + * + * @param token the token + * @return the highlight color or null + */ + @Override + public Color getTokenHighlight(ClangToken token) { + + return null; + } + + public void clearCache() { + cachedHighlights.clear(); + } + +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintAbstractDecompilerAction.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintAbstractDecompilerAction.java new file mode 100644 index 0000000000..63051e16a5 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintAbstractDecompilerAction.java @@ -0,0 +1,191 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint.actions; + +import docking.ActionContext; +import docking.action.DockingAction; +import docking.action.KeyBindingType; +import ghidra.app.decompiler.*; +import ghidra.app.decompiler.component.DecompilerUtils; +import ghidra.app.plugin.core.decompile.DecompilePlugin; +import ghidra.app.plugin.core.decompile.DecompilerActionContext; +import ghidra.app.util.datatype.DataTypeSelectionDialog; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.Address; +import ghidra.program.model.data.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.pcode.*; +import ghidra.program.model.symbol.*; +import ghidra.util.UndefinedFunction; +import ghidra.util.data.DataTypeParser.AllowedDataTypes; + +/** + * User action(s) associated with the Source-Sink Taint Menu. This menu is brought up by Right-Clicking + * elements in the Decompiler window. + * + *

+ * This is implemented by the remaining actions in this package. + */ +public abstract class TaintAbstractDecompilerAction extends DockingAction { + + /** + * Get the structure/union associated with a field token + * @param tok is the token representing a field + * @return the structure/union which contains this field + */ + public static Composite getCompositeDataType(ClangToken tok) { + // We already know tok is a ClangFieldToken + ClangFieldToken fieldtok = (ClangFieldToken) tok; + DataType dt = fieldtok.getDataType(); + + if (dt == null) { + return null; + } + + while (dt instanceof TypeDef) { + dt = ((TypeDef) dt).getBaseDataType(); + } + + if (dt instanceof Composite) { + return (Composite) dt; + } + + return null; + } + + /** + * Compare the given HighFunction's idea of the prototype with the Function's idea. + * Return true if there is a difference. If a specific symbol is being changed, + * it can be passed in to check whether or not the prototype is being affected. + * @param highSymbol (if not null) is the symbol being modified + * @param hfunction is the given HighFunction + * @return true if there is a difference (and a full commit is required) + */ + protected static boolean checkFullCommit(HighSymbol highSymbol, HighFunction hfunction) { + if (highSymbol != null && !highSymbol.isParameter()) { + return false; + } + Function function = hfunction.getFunction(); + Parameter[] parameters = function.getParameters(); + LocalSymbolMap localSymbolMap = hfunction.getLocalSymbolMap(); + int numParams = localSymbolMap.getNumParams(); + if (numParams != parameters.length) { + return true; + } + + for (int i = 0; i < numParams; i++) { + HighSymbol param = localSymbolMap.getParamSymbol(i); + if (param.getCategoryIndex() != i) { + return true; + } + VariableStorage storage = param.getStorage(); + // Don't compare using the equals method so that DynamicVariableStorage can match + if (0 != storage.compareTo(parameters[i].getVariableStorage())) { + return true; + } + } + + return false; + } + + protected static DataType chooseDataType(PluginTool tool, Program program, + DataType currentDataType) { + DataTypeManager dataTypeManager = program.getDataTypeManager(); + DataTypeSelectionDialog chooserDialog = new DataTypeSelectionDialog(tool, dataTypeManager, + Integer.MAX_VALUE, AllowedDataTypes.FIXED_LENGTH); + chooserDialog.setInitialDataType(currentDataType); + tool.showDialog(chooserDialog); + return chooserDialog.getUserChosenDataType(); + } + + public TaintAbstractDecompilerAction(String name) { + super(name, DecompilePlugin.class.getSimpleName()); + } + + public TaintAbstractDecompilerAction(String name, KeyBindingType kbType) { + super(name, DecompilePlugin.class.getSimpleName(), kbType); + } + + /** + * Can only be used within the Decompiler? + */ + @Override + public boolean isValidContext(ActionContext context) { + return context instanceof DecompilerActionContext; + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + DecompilerActionContext decompilerContext = (DecompilerActionContext) context; + return decompilerContext.checkActionEnablement(() -> { + return isEnabledForDecompilerContext(decompilerContext); + }); + } + + @Override + public void actionPerformed(ActionContext context) { + DecompilerActionContext decompilerContext = (DecompilerActionContext) context; + decompilerContext.performAction(() -> { + decompilerActionPerformed(decompilerContext); + }); + } + + protected Symbol getSymbol(DecompilerActionContext context) { + + // prefer the decompiler's function reference over the program location's address + Function function = getFunction(context); + if (function != null && !(function instanceof UndefinedFunction)) { + return function.getSymbol(); + } + + Program program = context.getProgram(); + SymbolTable symbolTable = program.getSymbolTable(); + Address address = context.getAddress(); + if (address == null) { + return null; + } + return symbolTable.getPrimarySymbol(address); + } + + /** + * Get the function corresponding to the specified decompiler context. + * + * @param context decompiler action context + * @return the function associated with the current context token or null if none identified. + */ + protected Function getFunction(DecompilerActionContext context) { + ClangToken token = context.getTokenAtCursor(); + + Function f = null; + if (token instanceof ClangFuncNameToken) { + f = DecompilerUtils.getFunction(context.getProgram(), (ClangFuncNameToken) token); + } + else { + HighSymbol highSymbol = token.getHighSymbol(context.getHighFunction()); + if (highSymbol instanceof HighFunctionShellSymbol) { + f = (Function) highSymbol.getSymbol().getObject(); + } + } + while (f != null && f.isThunk() && f.getSymbol().getSource() == SourceType.DEFAULT) { + f = f.getThunkedFunction(false); + } + return f; + } + + protected abstract boolean isEnabledForDecompilerContext(DecompilerActionContext context); + + protected abstract void decompilerActionPerformed(DecompilerActionContext context); +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintAbstractQueryAction.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintAbstractQueryAction.java new file mode 100644 index 0000000000..64a2ea4eec --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintAbstractQueryAction.java @@ -0,0 +1,105 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint.actions; + +import javax.swing.Icon; + +import docking.action.MenuData; +import ghidra.app.plugin.core.decompile.DecompilerActionContext; +import ghidra.app.plugin.core.decompiler.taint.*; +import ghidra.app.plugin.core.decompiler.taint.TaintState.QueryType; +import ghidra.app.plugin.core.decompiler.taint.sarif.SarifTaintGraphRunHandler; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Program; +import ghidra.util.HelpLocation; +import ghidra.util.task.Task; +import ghidra.util.task.TaskMonitor; +import sarif.SarifService; + +/** + * Action triggered from a specific token in the decompiler window to mark a variable as a source or + * sink and generate the requisite query. This can be an input parameter, a stack variable, a + * variable associated with a register, or a "dynamic" variable. + */ +public abstract class TaintAbstractQueryAction extends TaintAbstractDecompilerAction { + + protected TaintPlugin plugin; + protected TaintState state; + protected String desc; + + protected String executeTaintQueryIconString; + protected Icon executeTaintQueryIcon; + protected QueryType queryType; + + public TaintAbstractQueryAction(TaintPlugin plugin, TaintState state, String desc, String cmd) { + super(cmd); + + setHelpLocation(new HelpLocation(TaintPlugin.HELP_LOCATION, "Taint"+desc)); + setMenuBarData(new MenuData(new String[] { "Source-Sink", getName() })); + + this.plugin = plugin; + this.state = state; + this.desc = desc; + } + + /* + * We can only perform a query if we have an index database for this program and we have selected a sink. + */ + @Override + protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) { + return true; + } + + @Override + protected void decompilerActionPerformed(DecompilerActionContext context) { + Program program = context.getProgram(); + PluginTool tool = context.getTool(); + + Task defaultQueryTask = new Task("Source-Sink Query Task", true, true, true, true) { + @Override + public void run(TaskMonitor monitor) { + state.setCancellation(false); + monitor.initialize(program.getFunctionManager().getFunctionCount()); + state.queryIndex(program, tool, queryType); + state.setCancellation(monitor.isCancelled()); + monitor.clearCancelled(); + } + }; + + // This task will block -- see params above. + // The blocking is necessary because of the table provider we create below. + // It is problematic to do GUI stuff in the thread. + // We still get a progress bar and option to cancel. + tool.execute(defaultQueryTask); + + if (!state.wasCancelled()) { + SarifService sarifService = plugin.getSarifService(); + sarifService.getController().setDefaultGraphHander(SarifTaintGraphRunHandler.class); + sarifService.showSarif(desc, state.getData()); + + plugin.consoleMessage("executing query..."); + TaintProvider provider = plugin.getProvider(); + provider.setTaint(); + + plugin.consoleMessage("query complete"); + state.setCancellation(false); + } + else { + plugin.consoleMessage("Source-Sink query was cancelled."); + + } + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintClearAction.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintClearAction.java new file mode 100644 index 0000000000..1342792aaa --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintClearAction.java @@ -0,0 +1,60 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint.actions; + +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; + +import docking.action.KeyBindingData; +import docking.action.MenuData; +import ghidra.app.plugin.core.decompile.DecompilerActionContext; +import ghidra.app.plugin.core.decompiler.taint.TaintPlugin; +import ghidra.app.plugin.core.decompiler.taint.TaintState; +import ghidra.app.util.HelpTopics; +import ghidra.util.HelpLocation; + +/** + * Action triggered from a specific token in the decompiler window to mark a variable as a source or + * sink and generate the requisite query. This can be an input parameter, a stack variable, a + * variable associated with a register, or a "dynamic" variable. + */ +public class TaintClearAction extends TaintAbstractDecompilerAction { + + private TaintPlugin plugin; + private TaintState state; + + public TaintClearAction(TaintPlugin plugin, TaintState state) { + super("Clear Markers"); + setHelpLocation(new HelpLocation(TaintPlugin.HELP_LOCATION, "TaintClear")); + setPopupMenuData(new MenuData(new String[] { "Taint", "Clear" }, "Decompile")); + setKeyBindingData(new KeyBindingData(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK)); + this.plugin = plugin; + this.state = state; + } + + @Override + protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) { + return true; + } + + @Override + protected void decompilerActionPerformed(DecompilerActionContext context) { + state.clearMarkers(); + plugin.clearIcons(); + plugin.clearTaint(); + plugin.consoleMessage("taint cleared"); + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintGateAction.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintGateAction.java new file mode 100644 index 0000000000..7e413ac8d9 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintGateAction.java @@ -0,0 +1,89 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint.actions; + +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; + +import docking.action.KeyBindingData; +import docking.action.MenuData; +import ghidra.app.decompiler.*; +import ghidra.app.plugin.core.decompile.DecompilerActionContext; +import ghidra.app.plugin.core.decompiler.taint.TaintPlugin; +import ghidra.app.plugin.core.decompiler.taint.TaintState; +import ghidra.app.plugin.core.decompiler.taint.TaintState.MarkType; +import ghidra.app.util.HelpTopics; +import ghidra.program.model.listing.Function; +import ghidra.util.HelpLocation; +import ghidra.util.UndefinedFunction; + +/** + * Gate: A location / function that "sanitizes" a tainted variable. + * + *

+ * Action triggered from a specific token in the decompiler window to mark a variable as a + * source or sink and generate the requisite query. This can be an input parameter, + * a stack variable, a variable associated with a register, or a "dynamic" variable. + */ +public class TaintGateAction extends TaintAbstractDecompilerAction { + + private TaintPlugin plugin; + private MarkType mtype; + + public TaintGateAction(TaintPlugin plugin, TaintState state) { + super("Mark Gate"); + setHelpLocation(new HelpLocation(TaintPlugin.HELP_LOCATION, "TaintGate")); + setPopupMenuData(new MenuData(new String[] { "Taint", "Gate" }, "Decompile")); + setKeyBindingData(new KeyBindingData(KeyEvent.VK_S, InputEvent.ALT_DOWN_MASK)); + this.plugin = plugin; + this.mtype = MarkType.GATE; + } + + protected void mark(ClangToken token) { + plugin.toggleIcon(mtype, token, false); + } + + @Override + protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) { + Function function = context.getFunction(); + if (function == null || function instanceof UndefinedFunction) { + return false; + } + + ClangToken tokenAtCursor = context.getTokenAtCursor(); + if (tokenAtCursor == null) { + return false; + } + if (tokenAtCursor instanceof ClangFieldToken) { + return false; + } + if (tokenAtCursor.Parent() instanceof ClangReturnType) { + return false; + } + if (tokenAtCursor instanceof ClangFuncNameToken) { + return true; + } + if (!tokenAtCursor.isVariableRef()) { + return false; + } + return true; + } + + @Override + protected void decompilerActionPerformed(DecompilerActionContext context) { + mark(context.getTokenAtCursor()); + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintLoadAction.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintLoadAction.java new file mode 100644 index 0000000000..10c5d4a641 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintLoadAction.java @@ -0,0 +1,99 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint.actions; + +import java.io.File; + +import javax.swing.Icon; + +import docking.action.MenuData; +import docking.action.ToolBarData; +import docking.widgets.filechooser.GhidraFileChooser; +import docking.widgets.filechooser.GhidraFileChooserMode; +import generic.theme.GIcon; +import ghidra.app.plugin.core.decompile.DecompilerActionContext; +import ghidra.app.plugin.core.decompiler.taint.TaintPlugin; +import ghidra.app.plugin.core.decompiler.taint.TaintState; +import ghidra.app.util.HelpTopics; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Program; +import ghidra.util.HelpLocation; + +/** + * Normal workflow is for a QUERY to be executed which is immediately followed by ingesting the results for use. + * + *

+ * This action will allow PREVIOUSLY generated query output (possibly generated externally) to be read + * in for the program binary. + * + *

+ * Action triggered from a specific token in the decompiler window to mark a variable as a source or + * sink and generate the requisite query. This can be an input parameter, a stack variable, a + * variable associated with a register, or a "dynamic" variable. + */ +public class TaintLoadAction extends TaintAbstractDecompilerAction { + + private TaintPlugin plugin; + private TaintState state; + + private static String loadSarifFileIconString = "icon.fsbrowser.file.extension.obj"; + private static Icon loadSarifFileIcon = new GIcon(loadSarifFileIconString); + + public TaintLoadAction(TaintPlugin plugin, TaintState state) { + super("Load SARIF file"); + setHelpLocation(new HelpLocation(TaintPlugin.HELP_LOCATION, "TaintLoadSarif")); + + setMenuBarData(new MenuData(new String[] { "Source-Sink", getName() })); + setToolBarData(new ToolBarData(loadSarifFileIcon)); + + this.plugin = plugin; + this.state = state; + } + + @Override + protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) { + // this should always be enabled; we do not need any sources or sinks designated. + return true; + } + + @Override + protected void decompilerActionPerformed(DecompilerActionContext context) { + Program program = context.getProgram(); + PluginTool tool = context.getTool(); + + // Objectives: + // Pop a file dialog to select the SARIF file. + + // need to pop-up a file chooser dialog. + GhidraFileChooser file_chooser = new GhidraFileChooser(tool.getToolFrame()); + file_chooser.setCurrentDirectory(new File(state.getOptions().getTaintEnginePath())); + file_chooser.setFileSelectionMode(GhidraFileChooserMode.FILES_ONLY); + file_chooser.setTitle("Select a Source-Sink Query Results SARIF File"); + + File sarifFile = file_chooser.getSelectedFile(true); + + if (sarifFile != null && sarifFile.canRead()) { + plugin.consoleMessage( + String.format("Setting feature file: %s\n", sarifFile.getAbsolutePath())); + state.loadTaintData(program, sarifFile); + plugin.consoleMessage("external query results loaded."); + } + else { + plugin.consoleMessage("No sarif file specified, or file does not exist."); + + } + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintQueryAction.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintQueryAction.java new file mode 100644 index 0000000000..ba5e46e45b --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintQueryAction.java @@ -0,0 +1,45 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint.actions; + +import java.awt.event.KeyEvent; + +import docking.action.KeyBindingData; +import docking.action.ToolBarData; +import generic.theme.GIcon; +import ghidra.app.plugin.core.decompile.DecompilerActionContext; +import ghidra.app.plugin.core.decompiler.taint.TaintPlugin; +import ghidra.app.plugin.core.decompiler.taint.TaintState; +import ghidra.app.plugin.core.decompiler.taint.TaintState.QueryType; + +public class TaintQueryAction extends TaintAbstractQueryAction { + + public TaintQueryAction(TaintPlugin plugin, TaintState state) { + super(plugin, state, "Query", "Run taint query"); + executeTaintQueryIconString = "icon.graph.default.display.program.graph"; + executeTaintQueryIcon = new GIcon(executeTaintQueryIconString); + queryType = QueryType.SRCSINK; + + setToolBarData(new ToolBarData(executeTaintQueryIcon)); + setKeyBindingData(new KeyBindingData(KeyEvent.VK_Q, 0)); + } + + @Override + protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) { + return state.isValid(); + } + +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintQueryCustomAction.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintQueryCustomAction.java new file mode 100644 index 0000000000..27f1d8fe44 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintQueryCustomAction.java @@ -0,0 +1,29 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint.actions; + +import ghidra.app.plugin.core.decompiler.taint.TaintPlugin; +import ghidra.app.plugin.core.decompiler.taint.TaintState; +import ghidra.app.plugin.core.decompiler.taint.TaintState.QueryType; + +public class TaintQueryCustomAction extends TaintAbstractQueryAction { + + public TaintQueryCustomAction(TaintPlugin plugin, TaintState state) { + super(plugin, state, "CustomQuery", "Run custom taint query"); + queryType = QueryType.CUSTOM; + } + +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintQueryDefaultAction.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintQueryDefaultAction.java new file mode 100644 index 0000000000..d5922f28a1 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintQueryDefaultAction.java @@ -0,0 +1,35 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint.actions; + +import docking.action.ToolBarData; +import generic.theme.GIcon; +import ghidra.app.plugin.core.decompiler.taint.TaintPlugin; +import ghidra.app.plugin.core.decompiler.taint.TaintState; +import ghidra.app.plugin.core.decompiler.taint.TaintState.QueryType; + +public class TaintQueryDefaultAction extends TaintAbstractQueryAction { + + public TaintQueryDefaultAction(TaintPlugin plugin, TaintState state) { + super(plugin, state, "DefaultQuery", "Run default taint query"); + executeTaintQueryIconString = "icon.version.tracking.markup.status.conflict"; + executeTaintQueryIcon = new GIcon(executeTaintQueryIconString); + queryType = QueryType.DEFAULT; + + setToolBarData(new ToolBarData(executeTaintQueryIcon)); + } + +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintSinkAction.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintSinkAction.java new file mode 100644 index 0000000000..9bf5cf30fd --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintSinkAction.java @@ -0,0 +1,90 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint.actions; + +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; + +import docking.action.KeyBindingData; +import docking.action.MenuData; +import ghidra.app.decompiler.*; +import ghidra.app.plugin.core.decompile.DecompilerActionContext; +import ghidra.app.plugin.core.decompiler.taint.TaintPlugin; +import ghidra.app.plugin.core.decompiler.taint.TaintState; +import ghidra.app.plugin.core.decompiler.taint.TaintState.MarkType; +import ghidra.app.util.HelpTopics; +import ghidra.program.model.listing.Function; +import ghidra.util.HelpLocation; +import ghidra.util.UndefinedFunction; + +/** + * Action triggered from a specific token in the decompiler window to mark a variable as a + * source or sink and generate the requisite query. This can be an input parameter, + * a stack variable, a variable associated with a register, or a "dynamic" variable. + */ +public class TaintSinkAction extends TaintAbstractDecompilerAction { + + private TaintPlugin plugin; + private MarkType mtype; + + public TaintSinkAction(TaintPlugin plugin, TaintState state) { + super("Mark Sink"); + setHelpLocation(new HelpLocation(TaintPlugin.HELP_LOCATION, "TaintSink")); + setPopupMenuData(new MenuData(new String[] { "Taint", "Sink" }, "Decompile")); + setKeyBindingData(new KeyBindingData(KeyEvent.VK_S, InputEvent.SHIFT_DOWN_MASK)); + this.plugin = plugin; + this.mtype = MarkType.SINK; + } + + protected void mark(ClangToken token) { + plugin.toggleIcon(mtype, token, false); + } + + /** + * Designate an item in the decompiler window as a sink. This can only be done if you are + * on a HighSymbol + */ + @Override + protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) { + Function function = context.getFunction(); + if (function == null || function instanceof UndefinedFunction) { + return false; + } + + ClangToken tokenAtCursor = context.getTokenAtCursor(); + if (tokenAtCursor == null) { + return false; + } + if (tokenAtCursor instanceof ClangFieldToken) { + return false; + } + if (tokenAtCursor.Parent() instanceof ClangReturnType) { + return false; + } + if (tokenAtCursor instanceof ClangFuncNameToken) { + return true; + } + if (!tokenAtCursor.isVariableRef()) { + return false; + } + return true; + } + + @Override + protected void decompilerActionPerformed(DecompilerActionContext context) { + mark(context.getTokenAtCursor()); + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintSinkBySymbolAction.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintSinkBySymbolAction.java new file mode 100644 index 0000000000..fcd539987a --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintSinkBySymbolAction.java @@ -0,0 +1,85 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint.actions; + +import docking.action.MenuData; +import ghidra.app.decompiler.*; +import ghidra.app.plugin.core.decompile.DecompilerActionContext; +import ghidra.app.plugin.core.decompiler.taint.TaintPlugin; +import ghidra.app.plugin.core.decompiler.taint.TaintState; +import ghidra.app.plugin.core.decompiler.taint.TaintState.MarkType; +import ghidra.app.util.HelpTopics; +import ghidra.program.model.listing.Function; +import ghidra.util.HelpLocation; +import ghidra.util.UndefinedFunction; + +/** + * Action triggered from a specific token in the decompiler window to mark a symbol as a + * source or sink and generate the requisite query. This can be an input parameter, + * a stack variable, a variable associated with a register, or a "dynamic" variable. + */ +public class TaintSinkBySymbolAction extends TaintAbstractDecompilerAction { + + private TaintPlugin plugin; + private MarkType mtype; + + public TaintSinkBySymbolAction(TaintPlugin plugin, TaintState state) { + super("Mark Sink (Symbol)"); + setHelpLocation(new HelpLocation(TaintPlugin.HELP_LOCATION, "TaintSinkSymbol")); + setPopupMenuData(new MenuData(new String[] { "Taint", "Sink (Symbol)" }, "Decompile")); + this.plugin = plugin; + this.mtype = MarkType.SINK; + } + + protected void mark(ClangToken token) { + plugin.toggleIcon(mtype, token, false); + } + + /** + * Designate an item in the decompiler window as a sink. This can only be done if you are + * on a HighSymbol + */ + @Override + protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) { + Function function = context.getFunction(); + if (function == null || function instanceof UndefinedFunction) { + return false; + } + + ClangToken tokenAtCursor = context.getTokenAtCursor(); + if (tokenAtCursor == null) { + return false; + } + if (tokenAtCursor instanceof ClangFieldToken) { + return false; + } + if (tokenAtCursor.Parent() instanceof ClangReturnType) { + return false; + } + if (tokenAtCursor instanceof ClangFuncNameToken) { + return true; + } + if (!tokenAtCursor.isVariableRef()) { + return false; + } + return true; + } + + @Override + protected void decompilerActionPerformed(DecompilerActionContext context) { + mark(context.getTokenAtCursor()); + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintSliceTreeAction.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintSliceTreeAction.java new file mode 100644 index 0000000000..c7c632472e --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintSliceTreeAction.java @@ -0,0 +1,83 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint.actions; + +import docking.action.MenuData; +import ghidra.app.decompiler.ClangToken; +import ghidra.app.plugin.core.decompile.DecompilerActionContext; +import ghidra.app.plugin.core.decompiler.taint.TaintPlugin; +import ghidra.app.plugin.core.decompiler.taint.TaintState; +import ghidra.app.util.HelpTopics; +import ghidra.program.model.listing.Program; +import ghidra.program.model.pcode.HighVariable; +import ghidra.util.HelpLocation; +import ghidra.util.Msg; + +/** + * Triggered by right-click on a token in the decompiler window. + * + * Action triggered from a specific token in the decompiler window to mark a variable as a + * source or sink and generate the requisite query. Legal tokens to select include: + *

  • + * An input parameter, + *
  • + * A stack variable, + *
  • + * A variable associated with a register, or + *
  • + * A "dynamic" variable. + *
+ */ +public class TaintSliceTreeAction extends TaintAbstractDecompilerAction { + + private TaintPlugin plugin; + + public TaintSliceTreeAction(TaintPlugin plugin, TaintState state) { + super("Show Slice Tree"); + setHelpLocation(new HelpLocation(TaintPlugin.HELP_LOCATION, "TaintSliceTree")); + setPopupMenuData(new MenuData(new String[] { "Taint", "Slice Tree" }, "Decompile")); + setDescription( + "Shows the Taint Slice Trees window for the item under the cursor in the decompilation window. The new window will not change along with the Listing cursor."); + this.plugin = plugin; + } + + @Override + protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) { + return true; + } + + @Override + protected void decompilerActionPerformed(DecompilerActionContext context) { + Msg.info(this, "TaintSliceTreeAction action performed: " + context.toString()); + Program program = context.getProgram(); + if (program == null) { + return; + } + ClangToken tokenAtCursor = context.getTokenAtCursor(); + HighVariable highVariable = tokenAtCursor.getHighVariable(); + String hv = highVariable == null ? "HV NULL" : highVariable.toString(); + + Msg.info(this, "TaintSliceTreeAction action performed.\n" + + "\tProgram: " + program.toString() + "\n" + + "\tClangToken: " + tokenAtCursor.toString() + "\n" + + "\tHighVariable: " + hv); + + if (highVariable != null) { + // TODO This will need the sarif dataframe with only the path information. + plugin.showOrCreateNewSliceTree(program, tokenAtCursor, highVariable); + } + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintSourceAction.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintSourceAction.java new file mode 100644 index 0000000000..55dd7a3471 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintSourceAction.java @@ -0,0 +1,97 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint.actions; + +import java.awt.event.KeyEvent; + +import docking.action.KeyBindingData; +import docking.action.MenuData; +import ghidra.app.decompiler.*; +import ghidra.app.plugin.core.decompile.DecompilerActionContext; +import ghidra.app.plugin.core.decompiler.taint.TaintPlugin; +import ghidra.app.plugin.core.decompiler.taint.TaintState; +import ghidra.app.plugin.core.decompiler.taint.TaintState.MarkType; +import ghidra.app.util.HelpTopics; +import ghidra.program.model.listing.Function; +import ghidra.util.HelpLocation; +import ghidra.util.UndefinedFunction; + +/** + * Triggered by right-click on a token in the decompiler window. + * + * Action triggered from a specific token in the decompiler window to mark a variable as a + * source or sink and generate the requisite query. Legal tokens to select include: + *
  • + * An input parameter, + *
  • + * A stack variable, + *
  • + * A variable associated with a register, or + *
  • + * A "dynamic" variable. + *
+ */ +public class TaintSourceAction extends TaintAbstractDecompilerAction { + + private TaintPlugin plugin; + private MarkType mtype; + + public TaintSourceAction(TaintPlugin plugin, TaintState state) { + super("Mark Source"); + setHelpLocation(new HelpLocation(TaintPlugin.HELP_LOCATION, "TaintSource")); + // Taint Menu -> Source sub item. + setPopupMenuData(new MenuData(new String[] { "Taint", "Source" }, "Decompile")); + // Key Binding Capital S + setKeyBindingData(new KeyBindingData(KeyEvent.VK_S, 0)); + this.plugin = plugin; + this.mtype = MarkType.SOURCE; + } + + protected void mark(ClangToken token) { + plugin.toggleIcon(mtype, token, false); + } + + @Override + protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) { + Function function = context.getFunction(); + if (function == null || function instanceof UndefinedFunction) { + return false; + } + + ClangToken tokenAtCursor = context.getTokenAtCursor(); + if (tokenAtCursor == null) { + return false; + } + if (tokenAtCursor instanceof ClangFieldToken) { + return true; + } + if (tokenAtCursor.Parent() instanceof ClangReturnType) { + return false; + } + if (tokenAtCursor instanceof ClangFuncNameToken) { + return true; + } + if (!tokenAtCursor.isVariableRef()) { + return false; + } + return true; + } + + @Override + protected void decompilerActionPerformed(DecompilerActionContext context) { + mark(context.getTokenAtCursor()); + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintSourceBySymbolAction.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintSourceBySymbolAction.java new file mode 100644 index 0000000000..8072edb417 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/actions/TaintSourceBySymbolAction.java @@ -0,0 +1,92 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint.actions; + +import docking.action.MenuData; +import ghidra.app.decompiler.*; +import ghidra.app.plugin.core.decompile.DecompilerActionContext; +import ghidra.app.plugin.core.decompiler.taint.TaintPlugin; +import ghidra.app.plugin.core.decompiler.taint.TaintState; +import ghidra.app.plugin.core.decompiler.taint.TaintState.MarkType; +import ghidra.app.util.HelpTopics; +import ghidra.program.model.listing.Function; +import ghidra.util.HelpLocation; +import ghidra.util.UndefinedFunction; + +/** + * Triggered by right-click on a token in the decompiler window. + * + * Action triggered from a specific token in the decompiler window to mark a symbol as a + * source or sink and generate the requisite query. Legal tokens to select include: + *
  • + * An input parameter, + *
  • + * A stack variable, + *
  • + * A variable associated with a register, or + *
  • + * A "dynamic" variable. + *
+ */ +public class TaintSourceBySymbolAction extends TaintAbstractDecompilerAction { + + private TaintPlugin plugin; + private MarkType mtype; + + public TaintSourceBySymbolAction(TaintPlugin plugin, TaintState state) { + super("Mark Source (Symbol)"); + setHelpLocation(new HelpLocation(TaintPlugin.HELP_LOCATION, "TaintSourceSymbol")); + // Taint Menu -> Source sub item. + setPopupMenuData(new MenuData(new String[] { "Taint", "Source (Symbol)" }, "Decompile")); + this.plugin = plugin; + this.mtype = MarkType.SOURCE; + } + + protected void mark(ClangToken token) { + plugin.toggleIcon(mtype, token, true); + } + + @Override + protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) { + Function function = context.getFunction(); + if (function == null || function instanceof UndefinedFunction) { + return false; + } + + ClangToken tokenAtCursor = context.getTokenAtCursor(); + if (tokenAtCursor == null) { + return false; + } + if (tokenAtCursor instanceof ClangFieldToken) { + return true; + } + if (tokenAtCursor.Parent() instanceof ClangReturnType) { + return false; + } + if (tokenAtCursor instanceof ClangFuncNameToken) { + return true; + } + if (!tokenAtCursor.isVariableRef()) { + return false; + } + return true; + } + + @Override + protected void decompilerActionPerformed(DecompilerActionContext context) { + mark(context.getTokenAtCursor()); + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/ctadl/TaintStateCTADL.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/ctadl/TaintStateCTADL.java new file mode 100644 index 0000000000..2da0418ad7 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/ctadl/TaintStateCTADL.java @@ -0,0 +1,182 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint.ctadl; + +import java.io.File; +import java.io.PrintWriter; +import java.nio.file.Path; +import java.util.List; + +import generic.jar.ResourceFile; +import ghidra.app.decompiler.*; +import ghidra.app.plugin.core.decompiler.taint.*; +import ghidra.app.plugin.core.osgi.BundleHost; +import ghidra.app.script.*; +import ghidra.app.services.ConsoleService; +import ghidra.program.model.address.Address; +import ghidra.program.model.pcode.HighParam; +import ghidra.program.model.pcode.HighVariable; + +/** + * Container for all the decompiler elements the users "selects" via the menu. + * This data is used to build queries. + */ +public class TaintStateCTADL extends AbstractTaintState { + + public TaintStateCTADL(TaintPlugin plugin) { + super(plugin); + ENGINE_NAME = "ctadl"; + } + + @Override + public void buildQuery(List paramList, Path engine, File indexDBFile, + String indexDirectory) { + paramList.add(engine.toString()); + paramList.add("--directory"); + paramList.add(indexDirectory); + paramList.add("query"); + paramList.add("-j8"); + paramList.add("--format=" + taintOptions.getTaintOutputForm()); + } + + @Override + public void buildIndex(List paramList, String engine_path, String facts_path, + String indexDirectory) { + paramList.add(engine_path); + paramList.add("--directory"); + paramList.add(indexDirectory); + paramList.add("index"); + paramList.add("-j8"); + paramList.add("-f"); + paramList.add(facts_path); + } + + @Override + public GhidraScript getExportScript(ConsoleService console, boolean perFunction) { + String scriptName = getScriptName(perFunction); + BundleHost bundleHost = GhidraScriptUtil.acquireBundleHostReference(); + for (ResourceFile dir : bundleHost.getBundleFiles()) { + if (dir.isDirectory()) { + ResourceFile scriptFile = new ResourceFile(dir, scriptName); + if (scriptFile.exists()) { + GhidraScriptProvider provider = GhidraScriptUtil.getProvider(scriptFile); + try { + return provider.getScriptInstance(scriptFile, console.getStdErr()); + } + catch (GhidraScriptLoadException e) { + console.addErrorMessage("", "Unable to load script: " + scriptName); + console.addErrorMessage("", " detail: " + e.getMessage()); + } + } + } + } + throw new IllegalArgumentException("Script does not exist: " + scriptName); + } + + protected String getScriptName(boolean perFunction) { + return perFunction ? "ExportPCodeForSingleFunction.java" : "ExportPCodeForCTADL.java"; + } + + /* + * NOTE: This is the only method used now for Sources and Sinks. + */ + @Override + protected void writeRule(PrintWriter writer, TaintLabel mark, boolean isSource) { + Boolean allAccess = taintOptions.getTaintUseAllAccess(); + String method = isSource ? "TaintSource" : "LeakingSink"; + Address addr = mark.getAddress(); + + if (mark.getFunctionName() == null) { + return; + } + + ClangToken token = mark.getToken(); + if (token instanceof ClangFuncNameToken) { + + writer.println(method + "Vertex(\"" + mark.getLabel() + "\", vn, p) :-"); + writer.println("\tHFUNC_NAME(f, \"" + mark.getFunctionName() + "\"),"); + writer.println("\tCFunction_FormalParam(f, n, vn),"); + writer.println("\tCReturnParameter(n),"); + writer.println("\tVertex(vn, p)."); + + } + else { + + HighVariable hv = mark.getHighVariable(); + if (hv == null && token instanceof ClangFieldToken ftoken) { + ClangVariableToken vtoken = TaintState.getParentToken(ftoken); + if (vtoken != null) { + hv = vtoken.getHighVariable(); + token = vtoken; + } + } + writer.println(method + "Vertex(\"" + mark.getLabel() + "\", vn, p) :-"); + if (!mark.isGlobal()) { + writer.println("\tHFUNC_NAME(m, \"" + mark.getFunctionName() + "\"),"); + writer.println("\tCVar_InFunction(vn, m),"); + } + if (addr != null && addr.getOffset() != 0 && !mark.bySymbol()) { + if (!TaintState.isActualParam(token) && !(hv instanceof HighParam)) { + writer.println("\tVNODE_PC_ADDRESS(vn, " + addr.getOffset() + "),"); + } + } + if (mark.bySymbol()) { + writer.println("\tSYMBOL_NAME(sym, \"" + token.getText() + "\"),"); + writer.println("\tSYMBOL_HVAR(sym, hv),"); + writer.println("\tVNODE_HVAR(vn, hv),"); + } + else if (hv != null) { + writer.println("\tCVar_SourceInfo(vn, SOURCE_INFO_NAME_KEY, \"" + + TaintState.hvarName(token) + "\"),"); + } + else { + writer.println("\tCVar_Name(vn, \"" + token.getText() + "\"),"); + } + if (!allAccess) { + writer.println("\tp = \"\","); + } + writer.println("\tVertex(vn, p)."); + + } + } + + @Override + public void writeGate(PrintWriter writer, TaintLabel mark) { + Boolean allAccess = taintOptions.getTaintUseAllAccess(); + String method = "TaintSanitizeAll"; + Address addr = mark.getAddress(); + + if (mark.getFunctionName() == null) { + return; + } + + writer.println(method + "Vertex(vn, p) :-"); + if (!mark.isGlobal()) { + writer.println("\tHFUNC_NAME(m, \"" + mark.getFunctionName().toString() + "\"),"); + writer.println("\tCVar_InFunction(vn, m),"); + } + if (addr != null && addr.getOffset() != 0) { + writer.println("\tVNODE_PC_ADDRESS(vn, " + addr.getOffset() + "),"); + } + writer.println("\tCVar_SourceInfo(vn, SOURCE_INFO_NAME_KEY, \"" + + TaintState.hvarName(mark.getToken()) + "\"),"); + if (!allAccess) { + writer.println("\tp = \"\","); + } + writer.println("\tVertex(vn, p)."); + } + +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/sarif/SarifTaintCodeFlowResultHandler.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/sarif/SarifTaintCodeFlowResultHandler.java new file mode 100644 index 0000000000..7f5c4a16b1 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/sarif/SarifTaintCodeFlowResultHandler.java @@ -0,0 +1,156 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint.sarif; + +import java.util.*; + +import com.contrastsecurity.sarif.*; +import com.contrastsecurity.sarif.Result.Kind; +import com.contrastsecurity.sarif.Result.Level; + +import ghidra.app.plugin.core.decompiler.taint.AbstractTaintState; +import ghidra.app.plugin.core.decompiler.taint.TaintRule; +import ghidra.program.model.address.Address; +import sarif.SarifUtils; +import sarif.handlers.SarifResultHandler; +import sarif.model.SarifDataFrame; + +public class SarifTaintCodeFlowResultHandler extends SarifResultHandler { + + @Override + public String getKey() { + return "Address"; + } + + @Override + public boolean isEnabled(SarifDataFrame dframe) { + return dframe.getToolID().equals(AbstractTaintState.ENGINE_NAME); + } + + @Override + public void handle(SarifDataFrame dframe, Run run, Result result, Map map) { + this.df = dframe; + this.controller = df.getController(); + + this.run = run; + this.result = result; + + List> tableResults = df.getTableResults(); + String ruleId = result.getRuleId(); + if (ruleId == null || !ruleId.equals("C0001")) { + return; + } + String type = TaintRule.fromRuleId(ruleId).toString(); + // TODO: this is a bit weak + String label = "UNSPECIFIED"; + Message msg = result.getMessage(); + String[] parts = msg.getText().split(":"); + if (parts.length > 1) { + label = parts[1].strip(); + } + String comment = result.getMessage().getText(); + + List codeFlows = result.getCodeFlows(); + if (codeFlows == null) { + return; + } + int path_id = 1; + int path_index = 0; + for (CodeFlow cf : codeFlows) { + List threadFlows = cf.getThreadFlows(); + if (threadFlows == null) { + continue; + } + for (ThreadFlow tf : threadFlows) { + List threadFlowLocations = tf.getLocations(); + path_index = 1; + for (ThreadFlowLocation tfl : threadFlowLocations) { + map.put("Message", result.getMessage().getText()); + Kind kind = result.getKind(); + map.put("Kind", kind == null ? "None" : kind.toString()); + Level level = result.getLevel(); + if (level != null) { + map.put("Level", level.toString()); + } + map.put("RuleId", result.getRuleId()); + map.put("type", type); + map.put("value", label); + map.put("comment", comment); + map.put("pathID", path_id); + map.put("index", path_index); + populate(map, tfl, path_index); + if (path_index > 1) { + tableResults.add(map); + } + map = new HashMap<>(); + path_index++; + } + } + path_id++; + } + } + + @Override + protected Object parse() { + // UNUSED + return null; + } + + private void populate(Map map, ThreadFlowLocation tfl, int path_index) { + // For Source-Sink, these are the nodes. First is the Source, last is the Sink. + Location loc = tfl.getLocation(); + LogicalLocation ll = SarifUtils.getLogicalLocation(run, loc); + String name = ll.getName(); + String fqname = ll.getFullyQualifiedName(); + String displayName = SarifUtils.extractDisplayName(ll); + map.put("originalName", name); + map.put("name", displayName); + map.put("location", fqname); + map.put("function", SarifUtils.extractFQNameFunction(fqname)); + + Address faddr = SarifUtils.extractFunctionEntryAddr(controller.getProgram(), fqname); + if (faddr != null && faddr.getOffset() >= 0) { + map.put("entry", faddr); + map.put("Address", faddr); + } + + String kind = ll.getKind(); + String operation = ""; + + switch (kind) { + case "variable": + map.put("Address", + SarifUtils.extractFQNameAddrPair(controller.getProgram(), fqname).get(1)); + kind = path_index == 1 ? "path source" : "path sink"; + operation = path_index == 1 ? "Source" : "Sink"; + break; + + case "instruction": + // instruction address. + map.put("Address", + SarifUtils.extractFQNameAddrPair(controller.getProgram(), fqname).get(1)); + operation = controller.getStateText(tfl.getState(), "assignment"); + kind = "path node"; + break; + + default: + System.err.println(String.format("Path Kind: '%s' is unknown", kind)); + } + map.put("kind", kind); + map.put("operation", operation); + } + +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/sarif/SarifTaintGraphRunHandler.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/sarif/SarifTaintGraphRunHandler.java new file mode 100644 index 0000000000..3edc4cd401 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/sarif/SarifTaintGraphRunHandler.java @@ -0,0 +1,79 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint.sarif; + +import java.util.Map; +import java.util.Map.Entry; + +import com.contrastsecurity.sarif.*; + +import ghidra.app.plugin.core.decompiler.taint.TaintService; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.Address; +import ghidra.service.graph.AttributedVertex; +import sarif.SarifUtils; +import sarif.handlers.run.SarifGraphRunHandler; + +public class SarifTaintGraphRunHandler extends SarifGraphRunHandler { + + private TaintService service; + + @Override + protected void populateVertex(Node n, AttributedVertex vertex) { + if (service == null) { + PluginTool tool = controller.getPlugin().getTool(); + service = tool.getService(TaintService.class); + } + Address addr = controller.locationToAddress(run, n.getLocation()); + vertex.setName(addr.toString()); + String text = n.getLabel().getText(); + PropertyBag properties = n.getProperties(); + if (properties != null) { + Map additional = properties.getAdditionalProperties(); + if (additional != null) { + for (Entry entry : additional.entrySet()) { + vertex.setAttribute(entry.getKey(), entry.getValue().toString()); + } + } + } + vertex.setAttribute("Label", text); + vertex.setAttribute("Address", addr.toString(true)); + LogicalLocation ll = SarifUtils.getLogicalLocation(run, n.getLocation()); + if (ll != null) { + String name = ll.getName(); + String fqname = ll.getFullyQualifiedName(); + String displayName = SarifUtils.extractDisplayName(ll); + vertex.setAttribute("originalName", name); + vertex.setAttribute("name", displayName); + if (name != null) { + vertex.setName(displayName); + } + addr = SarifUtils.getLocAddress(controller.getProgram(), fqname); + if (addr != null) { + vertex.setAttribute("Address", addr.toString(true)); + } + + vertex.setAttribute("location", fqname); + vertex.setAttribute("kind", ll.getKind()); + vertex.setAttribute("function", SarifUtils.extractFQNameFunction(fqname)); + Address faddr = SarifUtils.extractFunctionEntryAddr(controller.getProgram(), fqname); + if (faddr != null && faddr.getOffset() >= 0) { + vertex.setAttribute("func_addr", faddr.toString(true)); + } + } + } + +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/sarif/SarifTaintResultHandler.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/sarif/SarifTaintResultHandler.java new file mode 100644 index 0000000000..138e344b02 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/sarif/SarifTaintResultHandler.java @@ -0,0 +1,366 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint.sarif; + +import java.util.*; +import java.util.Map.Entry; + +import com.contrastsecurity.sarif.*; + +import docking.ActionContext; +import docking.action.*; +import ghidra.app.plugin.core.decompiler.taint.*; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressRange; +import ghidra.program.model.address.AddressSet; +import ghidra.program.util.ProgramTask; +import ghidra.util.task.TaskLauncher; +import ghidra.util.task.TaskMonitor; +import resources.Icons; +import sarif.SarifUtils; +import sarif.handlers.SarifResultHandler; +import sarif.model.SarifDataFrame; +import sarif.view.SarifResultsTableProvider; + +public class SarifTaintResultHandler extends SarifResultHandler { + + @Override + public String getKey() { + return "Address"; + } + + @Override + public boolean isEnabled(SarifDataFrame dframe) { + return dframe.getToolID().equals(AbstractTaintState.ENGINE_NAME); + } + + @Override + public void handle(SarifDataFrame dframe, Run run, Result result, Map map) { + this.df = dframe; + this.controller = df.getController(); + + this.run = run; + this.result = result; + + String ruleId = result.getRuleId(); + if (ruleId == null || ruleId.equals("C0001")) { + return; + } + map.put("type", TaintRule.fromRuleId(ruleId)); + // TODO: this is a bit weak + String label = "UNSPECIFIED"; + Message msg = result.getMessage(); + String[] parts = msg.getText().split(":"); + if (parts.length > 1) { + label = parts[1].strip(); + } + map.put("value", label); + map.put("comment", result.getMessage().getText()); + + List locs = result.getLocations(); + if (locs != null) { + map.put("Locations", locs); + populate(map, locs); + } + + PropertyBag properties = result.getProperties(); + if (properties != null) { + Map additionalProperties = properties.getAdditionalProperties(); + if (additionalProperties != null) { + for (Entry entry : additionalProperties.entrySet()) { + map.put(entry.getKey(), entry.getValue()); + } + } + } + } + + @Override + protected Object parse() { + // UNUSED + return null; + } + + @Override + public String getActionName() { + return "Apply taint"; + } + + @Override + public ProgramTask getTask(SarifResultsTableProvider prov) { + return new ApplyTaintViaVarnodesTask(prov); + } + + private void populate(Map map, List locs) { + Location loc = locs.get(0); + LogicalLocation ll = SarifUtils.getLogicalLocation(run, loc); + if (ll != null) { + String name = ll.getName(); + String fqname = ll.getFullyQualifiedName(); + String displayName = SarifUtils.extractDisplayName(ll); + map.put("originalName", name); + map.put("name", displayName); + Address faddr = SarifUtils.extractFunctionEntryAddr(controller.getProgram(), fqname); + if (faddr != null && faddr.getOffset() >= 0) { + map.put("entry", faddr); + map.put("Address", faddr); + } + Address addr = SarifUtils.getLocAddress(controller.getProgram(), fqname); + if (addr != null) { + map.put("Address", addr); + + } + map.put("location", fqname); + map.put("kind", ll.getKind()); + map.put("function", SarifUtils.extractFQNameFunction(fqname)); + } + } + + @Override + public DockingAction createAction(SarifResultsTableProvider prov) { + this.provider = prov; + this.isEnabled = isEnabled(provider.getDataFrame()); + + DockingAction byVarnode = new DockingAction(getActionName(), getKey()) { + @Override + public void actionPerformed(ActionContext context) { + ProgramTask task = getTask(provider); + TaskLauncher.launch(task); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + return isEnabled; + } + + @Override + public boolean isAddToPopup(ActionContext context) { + return isEnabled; + } + }; + byVarnode.setPopupMenuData(new MenuData(new String[] { getActionName() })); + provider.addLocalAction(byVarnode); + + DockingAction applyAll = new DockingAction("Apply all", getKey()) { + @Override + public void actionPerformed(ActionContext context) { + provider.filterTable.getTable().selectAll(); + TaskLauncher.launch(new ApplyTaintViaVarnodesTask(provider)); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + return isEnabled; + } + + @Override + public boolean isAddToPopup(ActionContext context) { + return isEnabled; + } + }; + applyAll.setDescription("Apply all"); + applyAll.setToolBarData(new ToolBarData(Icons.EXPAND_ALL_ICON)); + provider.addLocalAction(applyAll); + + DockingAction clearTaint = new DockingAction("Clear taint", getKey()) { + @Override + public void actionPerformed(ActionContext context) { + TaskLauncher.launch(new ClearTaintTask(provider)); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + return isEnabled; + } + + @Override + public boolean isAddToPopup(ActionContext context) { + return isEnabled; + } + }; + clearTaint.setPopupMenuData(new MenuData(new String[] { "Clear taint" })); + + return clearTaint; + } + + private class ApplyTaintViaVarnodesTask extends ProgramTask { + + private SarifResultsTableProvider tableProvider; + + protected ApplyTaintViaVarnodesTask(SarifResultsTableProvider provider) { + super(provider.getController().getProgram(), "ApplyTaintViaVarnodesTask", true, true, + true); + this.tableProvider = provider; + } + + @Override + protected void doRun(TaskMonitor monitor) { + int[] selected = tableProvider.filterTable.getTable().getSelectedRows(); + Map> map = new HashMap<>(); + for (int row : selected) { + Map r = tableProvider.getRow(row); + String kind = (String) r.get("kind"); + if (kind.equals("instruction") || kind.startsWith("path ")) { + getTaintedInstruction(map, r); + } + if (kind.equals("variable")) { + getTaintedVariable(map, r); + } + } + + PluginTool tool = tableProvider.getController().getPlugin().getTool(); + TaintService service = tool.getService(TaintService.class); + if (service != null) { + service.setVarnodeMap(map, true); + } + } + + private void getTaintedVariable(Map> map, + Map r) { + Address faddr = (Address) r.get("entry"); + Set vset = getSet(map, faddr); + vset.add(new TaintQueryResult(r)); + } + + private void getTaintedInstruction(Map> map, + Map r) { + Address faddr = (Address) r.get("entry"); + String fqname = (String) r.get("location"); + Set vset = getSet(map, faddr); + String edgeId = SarifUtils.getEdge(fqname); + if (edgeId != null) { + String srcId = SarifUtils.getEdgeSource(edgeId); + LogicalLocation[] srcNodes = SarifUtils.getNodeLocs(srcId); + for (LogicalLocation lloc : srcNodes) { + vset.add(new TaintQueryResult(r, run, lloc)); + } + String dstId = SarifUtils.getEdgeDest(edgeId); + LogicalLocation[] dstNodes = SarifUtils.getNodeLocs(dstId); + for (LogicalLocation lloc : dstNodes) { + vset.add(new TaintQueryResult(r, run, lloc)); + } + } + } + + private Set getSet(Map> map, + Address faddr) { + Set vset = map.get(faddr); + if (vset == null) { + vset = new HashSet(); + map.put(faddr, vset); + } + return vset; + } + + } + + private class ClearTaintTask extends ProgramTask { + + private SarifResultsTableProvider tableProvider; + + protected ClearTaintTask(SarifResultsTableProvider provider) { + super(provider.getController().getProgram(), "ClearTaintTask", true, true, true); + this.tableProvider = provider; + } + + @Override + protected void doRun(TaskMonitor monitor) { + int rowCount = tableProvider.filterTable.getTable().getRowCount(); + int[] selected = tableProvider.filterTable.getTable().getSelectedRows(); + PluginTool tool = tableProvider.getController().getPlugin().getTool(); + TaintService service = tool.getService(TaintService.class); + if (service == null) { + return; + } + if (selected.length == 0 || selected.length == rowCount) { + service.clearTaint(); + return; + } + + AddressSet set = service.getAddressSet(); + AddressSet setX = new AddressSet(); + for (AddressRange range : set.getAddressRanges()) { + setX.add(range); + } + Map> map = service.getVarnodeMap(); + Map> mapX = new HashMap<>(); + for (Entry> entry : map.entrySet()) { + Set entryX = new HashSet<>(); + entryX.addAll(entry.getValue()); + mapX.put(entry.getKey(), entryX); + } + for (int row : selected) { + Map r = tableProvider.getRow(row); + String kind = (String) r.get("kind"); + if (kind.equals("instruction") || kind.startsWith("path ")) { + removeTaintedInstruction(map, r); + } + if (kind.equals("variable")) { + removeTaintedVariable(map, r); + } + + Address addr = (Address) r.get("Address"); + if (addr != null) { + set.delete(addr, addr); + } + } + + service.setVarnodeMap(map, false); + service.setAddressSet(set, false); + } + + } + + private void removeTaintedVariable(Map> map, + Map r) { + Address faddr = (Address) r.get("entry"); + Set vset = getSet(map, faddr); + vset.remove(new TaintQueryResult(r)); + } + + private void removeTaintedInstruction(Map> map, + Map r) { + Address faddr = (Address) r.get("entry"); + String fqname = (String) r.get("location"); + Set vset = getSet(map, faddr); + String edgeId = SarifUtils.getEdge(fqname); + if (edgeId != null) { + String srcId = SarifUtils.getEdgeSource(edgeId); + LogicalLocation[] srcNodes = SarifUtils.getNodeLocs(srcId); + for (LogicalLocation lloc : srcNodes) { + TaintQueryResult res = new TaintQueryResult(r, run, lloc); + vset.remove(res); + } + String dstId = SarifUtils.getEdgeDest(edgeId); + LogicalLocation[] dstNodes = SarifUtils.getNodeLocs(dstId); + for (LogicalLocation lloc : dstNodes) { + TaintQueryResult res = new TaintQueryResult(r, run, lloc); + vset.remove(res); + } + map.put(faddr, vset); + } + } + + private Set getSet(Map> map, Address faddr) { + Set vset = map.get(faddr); + if (vset == null) { + vset = new HashSet(); + map.put(faddr, vset); + } + return vset; + } + +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/slicetree/ExternalSliceNode.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/slicetree/ExternalSliceNode.java new file mode 100644 index 0000000000..554a2bc9e7 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/slicetree/ExternalSliceNode.java @@ -0,0 +1,103 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint.slicetree; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.swing.Icon; + +import docking.widgets.tree.GTreeNode; +import generic.theme.GIcon; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Function; +import ghidra.program.util.FunctionSignatureFieldLocation; +import ghidra.program.util.ProgramLocation; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; +import resources.MultiIcon; +import resources.icons.TranslateIcon; + +public class ExternalSliceNode extends SliceNode { + + // A small orange box that is open. + private static final Icon EXTERNAL_ICON = new GIcon("icon.plugin.calltree.node.external"); + private final Icon EXTERNAL_FUNCTION_ICON; + private final Icon baseIcon; + + private final Function function; + private final Address sourceAddress; + private final String name; + + ExternalSliceNode(Function function, Address sourceAddress, Icon baseIcon) { + super(new AtomicInteger(0)); // can't recurse + this.function = function; + this.sourceAddress = sourceAddress; + this.name = function.getName(); + this.baseIcon = baseIcon; + + MultiIcon outgoingFunctionIcon = new MultiIcon(EXTERNAL_ICON, false, 32, 16); + TranslateIcon translateIcon = new TranslateIcon(baseIcon, 16, 0); + outgoingFunctionIcon.addIcon(translateIcon); + EXTERNAL_FUNCTION_ICON = outgoingFunctionIcon; + } + + @Override + public SliceNode recreate() { + return new ExternalSliceNode(function, sourceAddress, baseIcon); + } + + @Override + public Function getRemoteFunction() { + return function; + } + + @Override + public ProgramLocation getLocation() { + return new FunctionSignatureFieldLocation(function.getProgram(), function.getEntryPoint()); + } + + @Override + public Address getSourceAddress() { + return sourceAddress; + } + + @Override + public List generateChildren(TaskMonitor monitor) throws CancelledException { + return new ArrayList<>(); + } + + @Override + public Icon getIcon(boolean expanded) { + return EXTERNAL_FUNCTION_ICON; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getToolTip() { + return "External Call - called from " + sourceAddress; + } + + @Override + public boolean isLeaf() { + return true; + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/slicetree/InSliceNode.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/slicetree/InSliceNode.java new file mode 100644 index 0000000000..8e7021fdeb --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/slicetree/InSliceNode.java @@ -0,0 +1,153 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint.slicetree; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +import javax.swing.Icon; + +import org.apache.commons.collections4.map.LazyMap; + +import docking.widgets.tree.GTreeNode; +import ghidra.app.plugin.core.decompiler.taint.TaintSliceTreeProvider; +import ghidra.app.plugin.core.navigation.locationreferences.ReferenceUtils; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.*; +import ghidra.program.util.FunctionSignatureFieldLocation; +import ghidra.program.util.ProgramLocation; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; +import resources.MultiIcon; +import resources.icons.TranslateIcon; + +/** + * These are nodes that are in the left tree and below the InSliceRootNode. That is a little deceptive; see below. + * + *

+ * A location in the call tree that is ABOVE or has an in-path to base node (node of interest / root). + * e.g., the top in-node is the program entry point in many cases. + * + */ +public class InSliceNode extends SliceNode { + + private Icon INCOMING_FUNCTION_ICON; + + private Icon icon = null; + private final Address functionAddress; + protected final Program program; + protected final Function function; + protected String name; + protected final boolean filterDuplicates; + private final Address sourceAddress; + + InSliceNode(Program program, Function function, Address sourceAddress, + boolean filterDuplicates, AtomicInteger filterDepth) { + super(filterDepth); + this.program = program; + this.function = function; + this.name = function.getName(); + this.sourceAddress = sourceAddress; + this.filterDuplicates = filterDuplicates; + this.functionAddress = function.getEntryPoint(); + + MultiIcon incomingFunctionIcon = + new MultiIcon(TaintSliceTreeProvider.IN_TAINT_ICON, false, 32, 16); + TranslateIcon translateIcon = + new TranslateIcon(TaintSliceTreeProvider.HIGH_FUNCTION_ICON, 16, 0); + incomingFunctionIcon.addIcon(translateIcon); + INCOMING_FUNCTION_ICON = incomingFunctionIcon; + + setAllowsDuplicates(!filterDuplicates); + } + + @Override + public SliceNode recreate() { + return new InSliceNode(program, function, sourceAddress, filterDuplicates, + filterDepth); + } + + @Override + public Function getRemoteFunction() { + return function; + } + + @Override + public ProgramLocation getLocation() { + return new FunctionSignatureFieldLocation(function.getProgram(), function.getEntryPoint()); + } + + @Override + public List generateChildren(TaskMonitor monitor) throws CancelledException { + + FunctionSignatureFieldLocation location = + new FunctionSignatureFieldLocation(program, functionAddress); + + Set

addresses = ReferenceUtils.getReferenceAddresses(location, monitor); + LazyMap> nodesByFunction = + LazyMap.lazyMap(new HashMap<>(), k -> new ArrayList<>()); + FunctionManager functionManager = program.getFunctionManager(); + for (Address fromAddress : addresses) { + monitor.checkCancelled(); + Function callerFunction = functionManager.getFunctionContaining(fromAddress); + if (callerFunction == null) { + continue; + } + + InSliceNode node = new InSliceNode(program, callerFunction, fromAddress, + filterDuplicates, filterDepth); + addNode(nodesByFunction, node); + } + + List children = + nodesByFunction.values() + .stream() + .flatMap(list -> list.stream()) + .collect(Collectors.toList()); + Collections.sort(children, new CallNodeComparator()); + + return children; + } + + @Override + public Address getSourceAddress() { + return sourceAddress; + } + + @Override + public Icon getIcon(boolean expanded) { + if (icon == null) { + icon = INCOMING_FUNCTION_ICON; + } + return icon; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getToolTip() { + return null; + } + + @Override + public boolean isLeaf() { + return false; + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/slicetree/InSliceRootNode.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/slicetree/InSliceRootNode.java new file mode 100644 index 0000000000..0dea01ea2e --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/slicetree/InSliceRootNode.java @@ -0,0 +1,56 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint.slicetree; + +import java.util.concurrent.atomic.AtomicInteger; + +import javax.swing.Icon; + +import ghidra.app.plugin.core.decompiler.taint.TaintSliceTreeProvider; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; + +/** + * For this plugin there are two trees side by side. + * This tree is on the left. This node is the "root" or at the top of the tree. All nodes that flow from it + * are actually HIGHER up in the call stack to get to this node. They are nodes that CALL INTO this node via + * some call path. + */ +public class InSliceRootNode extends InSliceNode { + + public InSliceRootNode(Program program, Function function, Address sourceAddress, + boolean filterDuplicates, AtomicInteger filterDepth) { + super(program, function, sourceAddress, filterDuplicates, filterDepth); + name = function.getName(); + } + + @Override + public SliceNode recreate() { + return new InSliceRootNode(program, function, getSourceAddress(), filterDuplicates, + filterDepth); + } + + @Override + public Icon getIcon(boolean expanded) { + return TaintSliceTreeProvider.TAINT_ICON; + } + + @Override + public String getName() { + return "Backward Taint from " + name; + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/slicetree/LeafSliceNode.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/slicetree/LeafSliceNode.java new file mode 100644 index 0000000000..7abb7a9673 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/slicetree/LeafSliceNode.java @@ -0,0 +1,116 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint.slicetree; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.swing.Icon; + +import docking.widgets.tree.GTreeNode; +import generic.theme.GIcon; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.*; +import ghidra.program.util.ProgramLocation; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +public class LeafSliceNode extends SliceNode { + + // A stop sign symbol without the word stop. + private static final Icon ICON = new GIcon("icon.plugin.calltree.node.dead.end"); + + private final Reference reference; + private String name; + + private final Program program; + + LeafSliceNode(Program program, Reference reference) { + // Leaf node is the 0 level, OR cannot expand. + super(new AtomicInteger(0)); + this.program = program; + this.reference = reference; + } + + @Override + public SliceNode recreate() { + return new LeafSliceNode(program, reference); + } + + @Override + public Function getRemoteFunction() { + return null; // no function--dead end + } + + /** + * The address from which this leaf node comes. + */ + @Override + public Address getSourceAddress() { + return reference.getFromAddress(); + } + + @Override + public Icon getIcon(boolean expanded) { + return ICON; + } + + /** + * Name of the symbol associated with the to address or the to address as a string + * in cases where there is no symbol. + */ + @Override + public String getName() { + if (name == null) { + Address toAddress = reference.getToAddress(); + SymbolTable symbolTable = program.getSymbolTable(); + Symbol symbol = symbolTable.getPrimarySymbol(toAddress); + if (symbol != null) { + name = symbol.getName(); + } + else { + name = toAddress.toString(); + } + } + return name; + } + + @Override + public String getToolTip() { + return "Called from " + reference.getFromAddress(); + } + + @Override + public boolean isLeaf() { + return true; + } + + @Override + public ProgramLocation getLocation() { + return new ProgramLocation(program, reference.getToAddress()); + } + + /** + * There are no children + */ + @Override + public List generateChildren(TaskMonitor monitor) throws CancelledException { + return new ArrayList<>(); + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/slicetree/OutFunctionCallNode.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/slicetree/OutFunctionCallNode.java new file mode 100644 index 0000000000..27d62e531a --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/slicetree/OutFunctionCallNode.java @@ -0,0 +1,39 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint.slicetree; + +import java.util.concurrent.atomic.AtomicInteger; + +import ghidra.app.plugin.core.decompiler.taint.TaintSliceTreeProvider; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; + +public class OutFunctionCallNode extends OutSliceNode { + + OutFunctionCallNode(Program program, Function function, Address sourceAddress, + boolean filterDuplicates, AtomicInteger filterDepth) { + super(program, function, sourceAddress, TaintSliceTreeProvider.HIGH_VARIABLE_ICON, + filterDuplicates, + filterDepth); + } + + @Override + public SliceNode recreate() { + return new OutFunctionCallNode(program, function, getSourceAddress(), + filterDuplicates, filterDepth); + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/slicetree/OutSliceNode.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/slicetree/OutSliceNode.java new file mode 100644 index 0000000000..b61c0fa3aa --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/slicetree/OutSliceNode.java @@ -0,0 +1,248 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint.slicetree; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +import javax.swing.Icon; +import javax.swing.tree.TreePath; + +import org.apache.commons.collections4.map.LazyMap; + +import docking.widgets.tree.GTreeNode; +import ghidra.app.plugin.core.decompiler.taint.TaintPlugin; +import ghidra.app.plugin.core.decompiler.taint.TaintSliceTreeProvider; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.listing.*; +import ghidra.program.model.symbol.*; +import ghidra.program.util.FunctionSignatureFieldLocation; +import ghidra.program.util.ProgramLocation; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; +import resources.MultiIcon; +import resources.icons.TranslateIcon; + +public abstract class OutSliceNode extends SliceNode { + + private final Icon OUTGOING_FUNCTION_ICON; + + private Icon icon = null; + protected final Program program; + protected final Function function; + protected String name; + private final Address sourceAddress; + protected final boolean filterDuplicates; + private final Icon baseIcon; + + OutSliceNode(Program program, Function function, Address sourceAddress, Icon baseIcon, + boolean filterDuplicates, AtomicInteger filterDepth) { + super(filterDepth); + this.program = program; + this.function = function; + this.name = function.getName(); + this.sourceAddress = sourceAddress; + this.baseIcon = baseIcon; + this.filterDuplicates = filterDuplicates; + + MultiIcon outgoingFunctionIcon = + new MultiIcon(TaintSliceTreeProvider.OUT_TAINT_ICON, false, 32, 16); + TranslateIcon translateIcon = new TranslateIcon(baseIcon, 16, 0); + outgoingFunctionIcon.addIcon(translateIcon); + OUTGOING_FUNCTION_ICON = outgoingFunctionIcon; + + setAllowsDuplicates(!filterDuplicates); + } + + @Override + public Function getRemoteFunction() { + return function; + } + + @Override + public List generateChildren(TaskMonitor monitor) throws CancelledException { + AddressSetView functionBody = function.getBody(); + Address entryPoint = function.getEntryPoint(); + Set references = getReferencesFrom(program, functionBody, monitor); + LazyMap> nodesByFunction = + LazyMap.lazyMap(new HashMap<>(), k -> new ArrayList<>()); + FunctionManager functionManager = program.getFunctionManager(); + for (Reference reference : references) { + monitor.checkCancelled(); + Address toAddress = reference.getToAddress(); + if (toAddress.equals(entryPoint)) { + continue; + } + + Function calledFunction = functionManager.getFunctionAt(toAddress); + createNode(nodesByFunction, reference, calledFunction); + } + + List children = + nodesByFunction.values() + .stream() + .flatMap(list -> list.stream()) + .collect(Collectors.toList()); + Collections.sort(children, new CallNodeComparator()); + + return children; + } + + private void createNode(LazyMap> nodes, Reference reference, + Function calledFunction) { + if (calledFunction != null) { + if (isExternalCall(calledFunction)) { + SliceNode node = + new ExternalSliceNode(calledFunction, reference.getFromAddress(), baseIcon); + node.setAllowsDuplicates(!filterDuplicates); + addNode(nodes, node); + } + else { + addNode(nodes, new OutFunctionCallNode(program, calledFunction, + reference.getFromAddress(), filterDuplicates, filterDepth)); + } + } + else if (isCallReference(reference)) { + + Function externalFunction = getExternalFunctionTempHackWorkaround(reference); + if (externalFunction != null) { + SliceNode node = + new ExternalSliceNode(externalFunction, reference.getFromAddress(), baseIcon); + node.setAllowsDuplicates(!filterDuplicates); + addNode(nodes, node); + } + else { + // we have a call reference, but no function + SliceNode node = new LeafSliceNode(program, reference); + node.setAllowsDuplicates(!filterDuplicates); + addNode(nodes, node); + } + } + } + + private Function getExternalFunctionTempHackWorkaround(Reference reference) { + Address toAddress = reference.getToAddress(); + Listing listing = program.getListing(); + Data data = listing.getDataAt(toAddress); + if (data == null) { + return null; + } + + if (!data.isPointer()) { + return null; + } + + Reference primaryReference = data.getPrimaryReference(0); // not sure why 0 + if (primaryReference.isExternalReference()) { + FunctionManager functionManager = program.getFunctionManager(); + return functionManager.getFunctionAt(primaryReference.getToAddress()); + } + return null; + } + + private boolean isExternalCall(Function calledFunction) { + return calledFunction.isExternal(); + } + + private boolean isCallReference(Reference reference) { + RefType type = reference.getReferenceType(); + if (type.isCall()) { + return true; + } + + if (type.isWrite()) { + return false; + } + + Listing listing = program.getListing(); + Instruction instruction = listing.getInstructionAt(reference.getFromAddress()); + if (instruction == null || !instruction.getFlowType().isCall()) { + return false; + } + + if (listing.getFunctionAt(reference.getToAddress()) != null) { + return true; + } + + Data data = listing.getDataAt(reference.getToAddress()); + if (data == null) { + return false; + } + + Reference ref = data.getPrimaryReference(0); + if (ref == null || !ref.isExternalReference()) { + return false; + } + + Symbol extSym = program.getSymbolTable().getPrimarySymbol(ref.getToAddress()); + SymbolType symbolType = extSym.getSymbolType(); + if (symbolType == SymbolType.FUNCTION) { + return true; + } + return false; + } + + @Override + public Address getSourceAddress() { + return sourceAddress; + } + + @Override + public ProgramLocation getLocation() { + return new FunctionSignatureFieldLocation(function.getProgram(), function.getEntryPoint()); + } + + @Override + public Icon getIcon(boolean expanded) { + if (icon == null) { + icon = OUTGOING_FUNCTION_ICON; + if (functionIsInPath()) { + icon = TaintPlugin.RECURSIVE_ICON; + } + } + return icon; + } + + @Override + public boolean functionIsInPath() { + TreePath path = getTreePath(); + Object[] pathComponents = path.getPath(); + for (Object pathComponent : pathComponents) { + OutSliceNode node = (OutSliceNode) pathComponent; + if (node != this && node.function.equals(function)) { + return true; + } + } + return false; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getToolTip() { + return "Called from " + sourceAddress; + } + + @Override + public boolean isLeaf() { + return false; + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/slicetree/OutSliceRootNode.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/slicetree/OutSliceRootNode.java new file mode 100644 index 0000000000..922ab8227a --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/slicetree/OutSliceRootNode.java @@ -0,0 +1,68 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint.slicetree; + +import java.util.concurrent.atomic.AtomicInteger; + +import javax.swing.Icon; + +import ghidra.app.plugin.core.decompiler.taint.TaintSliceTreeProvider; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; + +/** + * For this plugin there are two trees side by side. + * This tree is on the right. This node is the "root" or at the top of that tree. All nodes that flow from it + * are actually LOWER in the call stack. They are nodes that ARE CALLED OUT OF this node via + * some call path. + + */ +public class OutSliceRootNode extends OutSliceNode { + + public OutSliceRootNode(Program program, Function function, Address sourceAddress, + boolean filterDuplicates, AtomicInteger filterDepth) { + super(program, function, sourceAddress, TaintSliceTreeProvider.HIGH_VARIABLE_ICON, + filterDuplicates, + filterDepth); + } + + @Override + public SliceNode recreate() { + return new OutSliceRootNode(program, function, getSourceAddress(), filterDuplicates, + filterDepth); + } + + @Override + public Icon getIcon(boolean expanded) { + return TaintSliceTreeProvider.TAINT_ICON; + } + + @Override + public String getName() { + return "Forward Taint from " + name; + } + + @Override + public boolean isLeaf() { + return false; + } + + @Override + public String getToolTip() { + return null; + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/slicetree/SliceNode.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/slicetree/SliceNode.java new file mode 100644 index 0000000000..91ee8a8f49 --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/slicetree/SliceNode.java @@ -0,0 +1,200 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint.slicetree; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.swing.tree.TreePath; + +import org.apache.commons.collections4.map.LazyMap; + +import docking.widgets.tree.GTreeNode; +import docking.widgets.tree.GTreeSlowLoadingNode; +import ghidra.program.model.address.*; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.Reference; +import ghidra.program.model.symbol.ReferenceManager; +import ghidra.program.util.ProgramLocation; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * Abstract base for all nodes associated with our slice tree. + * + *

+ * This likely DOES NOT need to be a threaded loading subtree implementer. + * extends GTreeLazyNode would probably be better. + */ +public abstract class SliceNode extends GTreeSlowLoadingNode { + + private boolean allowDuplicates; + protected AtomicInteger filterDepth; + private int depth = -1; + + /** Used to signal that this node has been marked for replacement */ + protected boolean invalid = false; + + public SliceNode(AtomicInteger filterDepth) { + this.filterDepth = filterDepth; + } + + /** + * Returns this node's remote function, where remote is the source function for + * an incoming call or a destination function for an outgoing call. May return + * null for nodes that do not have functions. + * @return the function or null + */ + public abstract Function getRemoteFunction(); + + /** + * Returns a location that represents the caller of the callee. + * @return the location + */ + public abstract ProgramLocation getLocation(); + + /** + * Returns the address that for the caller of the callee. + * @return the address + */ + public abstract Address getSourceAddress(); + + /** + * Called when this node needs to be reconstructed due to external changes, such as when + * functions are renamed. + * + * @return a new node that is the same type as 'this' node. + */ + public abstract SliceNode recreate(); + + protected Set getReferencesFrom(Program program, AddressSetView addresses, + TaskMonitor monitor) throws CancelledException { + Set set = new HashSet<>(); + ReferenceManager referenceManager = program.getReferenceManager(); + AddressIterator addressIterator = addresses.getAddresses(true); + while (addressIterator.hasNext()) { + monitor.checkCancelled(); + Address address = addressIterator.next(); + Reference[] referencesFrom = referenceManager.getReferencesFrom(address); + if (referencesFrom != null) { + for (Reference reference : referencesFrom) { + set.add(reference); + } + } + } + return set; + } + + /** + * True allows this node to contains children with the same name + * + * @param allowDuplicates true to allow duplicate nodes + */ + protected void setAllowsDuplicates(boolean allowDuplicates) { + this.allowDuplicates = allowDuplicates; + } + + protected void addNode(LazyMap> nodesByFunction, + SliceNode node) { + + Function function = node.getRemoteFunction(); + List nodes = nodesByFunction.get(function); + if (nodes.contains(node)) { + return; // never add equal() nodes + } + + if (allowDuplicates) { + nodes.add(node); // ok to add multiple nodes for this function with different addresses + } + + if (nodes.isEmpty()) { + nodes.add(node); // no duplicates allow; only add if this is the only node + return; + } + + } + + protected class CallNodeComparator implements Comparator { + @Override + public int compare(GTreeNode o1, GTreeNode o2) { + return ((SliceNode) o1).getSourceAddress() + .compareTo(((SliceNode) o2).getSourceAddress()); + } + } + + @Override + public int loadAll(TaskMonitor monitor) throws CancelledException { + if (depth() > filterDepth.get()) { + return 1; + } + return super.loadAll(monitor); + } + + private int depth() { + if (depth < 0) { + TreePath treePath = getTreePath(); + Object[] path = treePath.getPath(); + depth = path.length; + } + return depth; + } + + public boolean functionIsInPath() { + TreePath path = getTreePath(); + Object[] pathComponents = path.getPath(); + for (Object pathComponent : pathComponents) { + SliceNode node = (SliceNode) pathComponent; + Function nodeFunction = node.getRemoteFunction(); + Function myFunction = getRemoteFunction(); + if (node != this && nodeFunction.equals(myFunction)) { + return true; + } + } + return false; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + + SliceNode other = (SliceNode) obj; + if (!Objects.equals(getSourceAddress(), other.getSourceAddress())) { + return false; + } + return Objects.equals(getRemoteFunction(), other.getRemoteFunction()); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + Function function = getRemoteFunction(); + result = prime * result + ((function == null) ? 0 : function.hashCode()); + Address sourceAddress = getSourceAddress(); + result = prime * result + ((sourceAddress == null) ? 0 : sourceAddress.hashCode()); + return result; + } + +} diff --git a/Ghidra/Features/DecompilerDependent/src/test/java/ghidra/app/plugin/core/decompiler/taint/DecompilerTaintTest.java b/Ghidra/Features/DecompilerDependent/src/test/java/ghidra/app/plugin/core/decompiler/taint/DecompilerTaintTest.java new file mode 100644 index 0000000000..3f3a556d5f --- /dev/null +++ b/Ghidra/Features/DecompilerDependent/src/test/java/ghidra/app/plugin/core/decompiler/taint/DecompilerTaintTest.java @@ -0,0 +1,353 @@ +/* ### + * 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. + */ +package ghidra.app.plugin.core.decompiler.taint; + +import static org.junit.Assert.*; + +import java.io.File; +import java.lang.Exception; +import java.util.*; + +import org.junit.*; +import org.junit.experimental.categories.Category; + +import com.contrastsecurity.sarif.*; + +import generic.jar.ResourceFile; +import generic.test.category.NightlyCategory; +import ghidra.app.decompiler.*; +import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; +import ghidra.app.plugin.core.decompile.DecompilePlugin; +import ghidra.app.plugin.core.decompile.DecompilerProvider; +import ghidra.app.plugin.core.decompiler.taint.TaintState.MarkType; +import ghidra.app.plugin.core.decompiler.taint.TaintState.QueryType; +import ghidra.app.plugin.core.decompiler.taint.sarif.SarifTaintGraphRunHandler; +import ghidra.app.services.CodeViewerService; +import ghidra.framework.Application; +import ghidra.framework.options.ToolOptions; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressFormatException; +import ghidra.program.model.listing.Program; +import ghidra.program.model.pcode.*; +import ghidra.program.util.ProgramLocation; +import ghidra.test.*; +import ghidra.util.task.*; +import sarif.*; +import sarif.model.SarifDataFrame; + +@Category(NightlyCategory.class) +public class DecompilerTaintTest extends AbstractGhidraHeadedIntegrationTest { + + private static final String CTADL = "/usr/bin/ctadl"; + private static final String TMP = "/tmp"; + + private TestEnv env; + private File script; + private Program program; + private PluginTool tool; + + private CodeBrowserPlugin browserService; + private SarifPlugin sarifService; + private TaintPlugin taintService; + + private DecompilerProvider decompilerProvider; + + private Iterator tokenIterator; + private Run run; + private Address functionAddr; + + private String[] functionLabels = { "0x10021f0", "0x1003e21", "0x10021f0" }; + private Map tokenMap = new HashMap<>(); + //@formatter:off + private String[][] testTargets = {{ + "param_1", "param_1:010021fc", + "AVar1", "AVar1:01002292", + "local_50", "local_50:0100226a","local_50:01002270", "local_50:01002283", + "hCursor:01002270", + "_DAT_01005b28:01002243", "_DAT_01005b28:01002270", + "DAT_01005b30:010021fc", "DAT_01005b30:01002243", "DAT_01005b30:0100226a", + "DAT_01005b24:0100230f", "DAT_01005b24:01002365", + }, { + "pHVar1", "pHVar1:01003e36", "pHVar1:01003f8d", + }, { + "pHVar1", "pHVar1:01003e36", "pHVar1:01003f8d", + }}; + private int testIndex = 0; + private int[] testSizes = { + 10,11, 10,11, + 3,3, 3,3, + 21,3, 21,2, 21,2, 21,0, + 21,2, + 0,5, 0,1, + 0,8, 0,8, 0,0, + 0,9, 0,2, + + 12,12, 12,10, 12,4, + + 11,11, 11,0, 11,11, + }; + //@formatter:on + + @Before + public void setUp() throws Exception { + env = new TestEnv(); + ResourceFile resourceFile = + Application.getModuleFile("DecompilerDependent", + "ghidra_scripts/ExportPCodeForCTADL.java"); + script = resourceFile.getFile(true); + + program = env.getProgram("Winmine__XP.exe.gzf"); + tool = env.launchDefaultTool(program); + tool.addPlugin(DecompilePlugin.class.getName()); + tool.addPlugin(SarifPlugin.class.getName()); + tool.addPlugin(TaintPlugin.class.getName()); + showProvider(tool, "Decompiler"); + + ToolOptions options = tool.getOptions("Decompiler"); + options.setString(TaintOptions.OP_KEY_TAINT_ENGINE_PATH, CTADL); + options.setString(TaintOptions.OP_KEY_TAINT_FACTS_DIR, TMP); + options.setString(TaintOptions.OP_KEY_TAINT_OUTPUT_DIR, TMP); + + initServices(); + initDatabase(); + } + + @After + public void tearDown() throws Exception { + env.dispose(); + } + + // NB: This test is VERY slow. I do not recommend running it on a regular basis. + // Each of the 22 paired examples above takes close to a minute to run, so... + @Ignore + @Test + public void testWinmine() throws Exception { + int nf = 0; + for (String f : functionLabels) { + decompilerProvider = taintService.getDecompilerProvider(); + decompilerProvider.goTo(program, + new ProgramLocation(program, program.getMinAddress().getAddress(f))); + goTo(program, f); + waitForSwing(); + //System.err.println("TESTING: "+browserService.getCurrentLocation()); + + try { + functionAddr = program.getMinAddress().getAddress(f); + } + catch (AddressFormatException e) { + e.printStackTrace(); + } + + for (int i = 0; i < testTargets[nf].length; i++) { + ClangToken token = tokenMap.get(testTargets[nf][i]); + if (token != null) { + processToken(token, true); + processToken(token, false); + } + else { + System.err.println("NULL for " + testTargets[nf][i]); + } + } + nf++; + } + } + + private void processToken(ClangToken token, boolean bySymbol) throws Exception { + TaintState taintState = taintService.getTaintState(); + taintState.clearMarkers(); + taintService.clearIcons(); + taintService.clearTaint(); + taintService.toggleIcon(MarkType.SOURCE, token, bySymbol); + + taintState.clearData(); + taintState.queryIndex(program, tool, QueryType.SRCSINK); + SarifSchema210 data; + while ((data = taintState.getData()) == null) { + Thread.sleep(100); + } + SarifDataFrame df = new SarifDataFrame(data, sarifService.getController(), false); + + this.run = taintState.getData().getRuns().get(0); + Map> map = new HashMap<>(); + for (Map result : df.getTableResults()) { + processResult(map, result); + } + taintService.setVarnodeMap(map, true); + validateResult(token, map); + } + + private void processResult(Map> map, Map result) + throws Exception { + String kind = (String) result.get("kind"); + if (kind.equals("instruction") || kind.startsWith("path ")) { + getTaintedInstruction(map, result); + } + if (kind.equals("variable")) { + getTaintedVariable(map, result); + } + } + + private void validateResult(ClangToken token, Map> map) { + Set set = map.get(functionAddr); + //System.err.println("VALIDATE: "+functionAddr); + if (set != null) { + int sz = taintService.getProvider().getTokenCount(); + assertEquals(testSizes[testIndex], sz); + //System.err.println(testSizes[testIndex] + " vs " + sz); + } + //else { + // System.err.println("NULL for "+functionAddr); + //} + testIndex++; + } + + private void getTaintedVariable(Map> map, + Map result) { + Address faddr = (Address) result.get("entry"); + Set vset = getSet(map, faddr); + vset.add(new TaintQueryResult(result)); + } + + private void getTaintedInstruction(Map> map, + Map result) { + Address faddr = (Address) result.get("entry"); + String fqname = (String) result.get("location"); + Set vset = getSet(map, faddr); + String edgeId = SarifUtils.getEdge(fqname); + if (edgeId != null) { + String srcId = SarifUtils.getEdgeSource(edgeId); + LogicalLocation[] srcNodes = SarifUtils.getNodeLocs(srcId); + for (LogicalLocation lloc : srcNodes) { + vset.add(new TaintQueryResult(result, run, lloc)); + } + String dstId = SarifUtils.getEdgeDest(edgeId); + LogicalLocation[] dstNodes = SarifUtils.getNodeLocs(dstId); + for (LogicalLocation lloc : dstNodes) { + vset.add(new TaintQueryResult(result, run, lloc)); + } + } + } + + private Set getSet(Map> map, Address faddr) { + Set vset = map.get(faddr); + if (vset == null) { + vset = new HashSet(); + map.put(faddr, vset); + } + return vset; + } + + private void initServices() { + CodeViewerService viewer = tool.getService(CodeViewerService.class); + if (viewer instanceof CodeBrowserPlugin cb) { + this.browserService = cb; + } + SarifService sarif = tool.getService(SarifService.class); + if (sarif instanceof SarifPlugin sp) { + this.sarifService = sp; + } + TaintService taint = tool.getService(TaintService.class); + if (taint instanceof TaintPlugin tp) { + this.taintService = tp; + } + sarifService.getController().setDefaultGraphHander(SarifTaintGraphRunHandler.class); + } + + private void initDatabase() throws Exception { + ScriptTaskListener scriptId = env.runScript(script); + waitForScriptCompletion(scriptId, 65000); + program.flushEvents(); + waitForSwing(); + + CreateTargetIndexTask indexTask = + new CreateTargetIndexTask(taintService, taintService.getCurrentProgram()); + TaskBusyListener listener = new TaskBusyListener(); + indexTask.addTaskListener(listener); + new TaskLauncher(indexTask, tool.getActiveWindow()); + waitForBusyTool(tool); +// while (listener.executing) { +// Thread.sleep(100); +// } + + for (String f : functionLabels) { + decompilerProvider = taintService.getDecompilerProvider(); + decompilerProvider.goTo(program, + new ProgramLocation(program, program.getMinAddress().getAddress(f))); + goTo(program, f); + waitForSwing(); + //System.err.println("INIT: "+browserService.getCurrentLocation()); + + ClangToken tokenAtCursor = decompilerProvider.getDecompilerPanel().getTokenAtCursor(); + ClangFunction clangFunction = tokenAtCursor.getClangFunction(); + tokenIterator = clangFunction.tokenIterator(true); + while (tokenIterator.hasNext()) { + ClangToken next = tokenIterator.next(); + if (next instanceof ClangVariableToken || + next instanceof ClangFieldToken || + next instanceof ClangFuncNameToken) { + if (next instanceof ClangVariableToken vtoken) { + Varnode vn = vtoken.getVarnode(); + if (vn != null) { + HighVariable high = vn.getHigh(); + if (high instanceof HighConstant) { + continue; + } + } + } + String key = next.getText(); + if (next.getPcodeOp() != null) { + key += ":" + next.getPcodeOp().getSeqnum().getTarget(); + } + tokenMap.put(key, next); + } + } + } + } + + private void goTo(Program prog, String addr) { + runSwing(() -> { + try { + Address min = prog.getMinAddress(); + functionAddr = min.getAddress(addr); + browserService.getNavigatable() + .goTo(prog, new ProgramLocation(prog, functionAddr)); + } + catch (AddressFormatException e) { + e.printStackTrace(); + } + }); + } + + private class TaskBusyListener implements TaskListener { + + public boolean executing = true; + + TaskBusyListener() { + executing = true; + } + + @Override + public void taskCompleted(Task t) { + executing = false; + } + + @Override + public void taskCancelled(Task t) { + executing = false; + } + } +} diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/SarifController.java b/Ghidra/Features/Sarif/src/main/java/sarif/SarifController.java index 57be6346e3..7b81443a4e 100644 --- a/Ghidra/Features/Sarif/src/main/java/sarif/SarifController.java +++ b/Ghidra/Features/Sarif/src/main/java/sarif/SarifController.java @@ -25,6 +25,7 @@ import docking.widgets.table.ObjectSelectedListener; import ghidra.app.plugin.core.colorizer.ColorizingService; import ghidra.app.services.GraphDisplayBroker; import ghidra.app.util.importer.MessageLog; +import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSetView; import ghidra.program.model.listing.*; @@ -33,9 +34,9 @@ import ghidra.util.Msg; import ghidra.util.classfinder.ClassSearcher; import ghidra.util.exception.CancelledException; import ghidra.util.exception.GraphException; -import resources.ResourceManager; import sarif.handlers.SarifResultHandler; import sarif.handlers.SarifRunHandler; +import sarif.handlers.run.SarifGraphRunHandler; import sarif.managers.ProgramSarifMgr; import sarif.model.SarifDataFrame; import sarif.view.ImageArtifactDisplay; @@ -57,6 +58,9 @@ public class SarifController implements ObjectSelectedListener artifacts = new HashSet<>(); public Set graphs = new HashSet<>(); + private Class defaultGraphHandler = SarifGraphRunHandler.class; + private boolean useOverlays; + public Set getSarifResultHandlers() { Set set = new HashSet<>(); set.addAll(ClassSearcher.getInstances(SarifResultHandler.class)); @@ -107,7 +111,8 @@ public class SarifController implements ObjectSelectedListener" in a SARIF result, this handles * defining our custom API for those - * - * @param log - * @param result - * @param key - * @param value */ - public void handleListingAction(Result result, String key, Object value) { - List

addrs = getListingAddresses(result); + public void handleListingAction(Run run, Result result, String key, Object value) { + List
addrs = getListingAddresses(run, result); for (Address addr : addrs) { switch (key) { - case "comment": - /* @formatter:off - * docs/GhidraAPI_javadoc/api/constant-values.html#ghidra.program.model.listing.CodeUnit - * EOL_COMMENT 0 - * PRE_COMMENT 1 - * POST_COMMENT 2 - * PLATE_COMMENT 3 - * REPEATABLE_COMMENT 4 - * @formatter:on - */ - String comment = (String) value; - getProgram().getListing().setComment(addr, CodeUnit.PLATE_COMMENT, comment); - break; - case "highlight": - Color color = Color.decode((String) value); - coloringService.setBackgroundColor(addr, addr, color); - break; - case "bookmark": - String bookmark = (String) value; - getProgram().getBookmarkManager().setBookmark(addr, "Sarif", result.getRuleId(), bookmark); - break; + case "comment": + /* + * {@link program.model.listing.CodeUnit} + */ + String comment = (String) value; + getProgram().getListing().setComment(addr, CodeUnit.PLATE_COMMENT, comment); + break; + case "highlight": + Color color = Color.decode((String) value); + coloringService.setBackgroundColor(addr, addr, color); + break; + case "bookmark": + String bookmark = (String) value; + getProgram().getBookmarkManager() + .setBookmark(addr, "Sarif", result.getRuleId(), bookmark); + break; } } } @@ -190,16 +191,13 @@ public class SarifController implements ObjectSelectedListener getListingAddresses(Result result) { + public List
getListingAddresses(Run run, Result result) { List
addrs = new ArrayList<>(); if (result.getLocations() != null && result.getLocations().size() > 0) { List locations = result.getLocations(); for (Location loc : locations) { - Address addr = locationToAddress(loc); + Address addr = locationToAddress(run, loc); if (addr != null) { addrs.add(addr); } @@ -208,8 +206,27 @@ public class SarifController implements ObjectSelectedListener state_mappings = state.getAdditionalProperties(); + + for (Map.Entry pair : state_mappings.entrySet()) { + if (pair.getKey().equalsIgnoreCase(stateKey)) { + result = pair.getValue().getText(); + break; + } + } + return result; } @SuppressWarnings("unchecked") @@ -218,7 +235,7 @@ public class SarifController implements ObjectSelectedListener flow : (List>) row.get("CodeFlows")) { - this.plugin.makeSelection(flow); + this.getPlugin().makeSelection(flow); } } if (row.containsKey("Graphs")) { @@ -244,7 +261,30 @@ public class SarifController implements ObjectSelectedListener vertices) { + for (SarifResultsTableProvider provider : providers) { + provider.setSelection(vertices); + } + } + + public Class getDefaultGraphHander() { + return defaultGraphHandler; + } + + @SuppressWarnings("unchecked") + public void setDefaultGraphHander(Class clazz) { + defaultGraphHandler = clazz; + } + + public void setUseOverlays(boolean useOverlays) { + this.useOverlays = useOverlays; } } diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/SarifGraphDisplayListener.java b/Ghidra/Features/Sarif/src/main/java/sarif/SarifGraphDisplayListener.java new file mode 100644 index 0000000000..d20395d9d8 --- /dev/null +++ b/Ghidra/Features/Sarif/src/main/java/sarif/SarifGraphDisplayListener.java @@ -0,0 +1,145 @@ +/* ### + * 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. + */ +package sarif; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import docking.widgets.EventTrigger; +import ghidra.app.events.ProgramLocationPluginEvent; +import ghidra.app.plugin.core.graph.AddressBasedGraphDisplayListener; +import ghidra.framework.plugintool.PluginEvent; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSet; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.util.ProgramLocation; +import ghidra.service.graph.AttributedGraph; +import ghidra.service.graph.AttributedVertex; +import ghidra.service.graph.GraphDisplay; +import ghidra.service.graph.GraphDisplayListener; + +/** + * {@link GraphDisplayListener} that handle events back and from from program + * graphs. + */ +public class SarifGraphDisplayListener extends AddressBasedGraphDisplayListener { + + private Map> map = new HashMap<>(); + private SarifController controller; + private AttributedGraph graph; + + public SarifGraphDisplayListener(SarifController controller, GraphDisplay display, AttributedGraph graph) { + super(controller.getPlugin().getTool(), controller.getProgram(), display); + this.controller = controller; + this.graph = graph; + for (AttributedVertex vertex : graph.vertexSet()) { + String addrStr = vertex.getAttribute("Address"); + if (addrStr != null) { + Address address = program.getAddressFactory().getAddress(addrStr); + Set set = map.get(address); + if (set == null) { + set = new HashSet<>(); + } + set.add(vertex); + map.put(address, set); + } + } + } + + @Override + public void eventSent(PluginEvent event) { + super.eventSent(event); + if (event instanceof ProgramLocationPluginEvent) { + ProgramLocationPluginEvent ev = (ProgramLocationPluginEvent) event; + if (program.equals(ev.getProgram())) { + ProgramLocation location = ev.getLocation(); + Set vertices = getVertices(location.getAddress()); + if (vertices != null) { + graphDisplay.selectVertices(vertices, EventTrigger.INTERNAL_ONLY); + } + } + } + } + + @Override + public void selectionChanged(Set vertices) { + super.selectionChanged(vertices); + controller.setSelection(vertices); + } + + @Override + public Address getAddress(AttributedVertex vertex) { + String addrStr = vertex.getAttribute("Address"); + Address address = program.getAddressFactory().getAddress(addrStr); + return address; + } + + protected Set getVertices(Address address) { + return map.get(address); + } + + @Override + protected Set getVertices(AddressSetView addrSet) { + if (addrSet.isEmpty()) { + return Collections.emptySet(); + } + + Set vertices = new HashSet<>(); + for (Entry> entry : map.entrySet()) { + if (addrSet.contains(entry.getKey())) { + for (AttributedVertex v : entry.getValue()) { + vertices.add(v); + } + } + } + return vertices; + } + + @Override + protected AddressSet getAddresses(Set vertices) { + + AddressSet addrSet = new AddressSet(); + Collection> values = map.values(); + for (Set set : values) { + for (AttributedVertex vertex : vertices) { + if (set.contains(vertex)) { + String addrStr = vertex.getAttribute("Address"); + Address address = program.getAddressFactory().getAddress(addrStr); + addrSet.add(address); + } + } + } + return addrSet; + } + + protected boolean isValidAddress(Address addr) { + if (addr == null || program == null) { + return false; + } + return program.getMemory().contains(addr) || addr.isExternalAddress(); + } + + @Override + public GraphDisplayListener cloneWith(GraphDisplay newGraphDisplay) { + return new SarifGraphDisplayListener(controller, newGraphDisplay, graph); + } + +} diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/SarifPlugin.java b/Ghidra/Features/Sarif/src/main/java/sarif/SarifPlugin.java index 407a929ff5..45bfe3a3a4 100644 --- a/Ghidra/Features/Sarif/src/main/java/sarif/SarifPlugin.java +++ b/Ghidra/Features/Sarif/src/main/java/sarif/SarifPlugin.java @@ -4,9 +4,9 @@ * 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. @@ -27,6 +27,7 @@ import com.google.gson.JsonSyntaxException; import docking.action.builder.ActionBuilder; import docking.tool.ToolConstants; import docking.widgets.filechooser.GhidraFileChooser; +import generic.theme.GIcon; import ghidra.MiscellaneousPluginPackage; import ghidra.app.events.*; import ghidra.app.plugin.PluginCategoryNames; @@ -40,7 +41,6 @@ import ghidra.program.model.listing.Program; import ghidra.util.HelpLocation; import ghidra.util.Msg; import ghidra.util.bean.opteditor.OptionsVetoException; -import resources.ResourceManager; import sarif.io.SarifGsonIO; import sarif.io.SarifIO; @@ -50,16 +50,17 @@ import sarif.io.SarifIO; packageName = MiscellaneousPluginPackage.NAME, category = PluginCategoryNames.ANALYSIS, shortDescription = "Sarif Plugin.", - description = "SARIF parsing and visualization plugin." + description = "SARIF parsing and visualization plugin.", + servicesProvided = { SarifService.class } ) //@formatter:on /** - * A {@link ProgramPlugin} for reading in sarif files + * A {@link ProgramPlugin} for reading in sarif files */ -public class SarifPlugin extends ProgramPlugin implements OptionsChangeListener { +public class SarifPlugin extends ProgramPlugin implements SarifService, OptionsChangeListener { public static final String NAME = "Sarif"; - public static final Icon SARIF_ICON = ResourceManager.loadImage("images/peach_16.png"); + public static final Icon SARIF_ICON = new GIcon("icon.plugin.bookmark.type.note"); private Map sarifControllers; private SarifIO io; @@ -128,6 +129,29 @@ public class SarifPlugin extends ProgramPlugin implements OptionsChangeListener } } + @Override + public SarifSchema210 readSarif(File sarifFile) throws JsonSyntaxException, IOException { + return io.readSarif(sarifFile); + } + + @Override + public SarifSchema210 readSarif(String sarif) throws JsonSyntaxException, IOException { + return io.readSarif(sarif); + } + + public SarifController getController() { + currentProgram = getCurrentProgram(); + if (currentProgram != null) { + if (!sarifControllers.containsKey(currentProgram)) { + SarifController controller = new SarifController(currentProgram, this); + sarifControllers.put(currentProgram, controller); + } + return sarifControllers.get(currentProgram); + } + Msg.showError(this, tool.getActiveWindow(), "File parse error", "No current program"); + return null; + } + /** * Ultimately both selections end up calling this to actually show something on * the Ghidra gui @@ -136,17 +160,16 @@ public class SarifPlugin extends ProgramPlugin implements OptionsChangeListener * @param sarif */ public void showSarif(String logName, SarifSchema210 sarif) { - currentProgram = getCurrentProgram(); - if (currentProgram != null) { - if (!sarifControllers.containsKey(currentProgram)) { - SarifController controller = new SarifController(currentProgram, this); - sarifControllers.put(currentProgram, controller); - } - SarifController currentController = sarifControllers.get(currentProgram); - if (currentController != null) { + SarifController currentController = getController(); + if (currentController != null) { + if (sarif != null) { currentController.showTable(logName, sarif); - return; } + else { + Msg.showError(this, tool.getActiveWindow(), "File parse error", + "No SARIF generated - check directories"); + } + return; } Msg.showError(this, tool.getActiveWindow(), "File parse error", "No current program"); } @@ -162,7 +185,7 @@ public class SarifPlugin extends ProgramPlugin implements OptionsChangeListener private void createActions() { //@formatter:off new ActionBuilder("Read", getName()) - .menuPath("Sarif", "Read File") + .menuPath("Tools", "Sarif", "Read File") .menuGroup("sarif", "1") .helpLocation(new HelpLocation("Sarif", "Using_SARIF_Files")) .enabledWhen(ctx -> getCurrentProgram() != null) @@ -187,7 +210,8 @@ public class SarifPlugin extends ProgramPlugin implements OptionsChangeListener @Override public void optionsChanged(ToolOptions options, String optionName, Object oldValue, - Object newValue) throws OptionsVetoException { + Object newValue) + throws OptionsVetoException { Options sarifOptions = options.getOptions(NAME); loadOptions(sarifOptions); diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/SarifService.java b/Ghidra/Features/Sarif/src/main/java/sarif/SarifService.java new file mode 100644 index 0000000000..e8f6582bea --- /dev/null +++ b/Ghidra/Features/Sarif/src/main/java/sarif/SarifService.java @@ -0,0 +1,69 @@ +/* ### + * 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. + */ +package sarif; + +import java.io.File; +import java.io.IOException; + +import com.contrastsecurity.sarif.SarifSchema210; +import com.google.gson.JsonSyntaxException; + +import ghidra.framework.plugintool.ServiceInfo; +import ghidra.util.Swing; + +/** + * The SarifService provides a general service for plugins to load and display sarif files + *

+ * {@link Swing#runLater(Runnable)} call, which will prevent any deadlock issues. + */ +@ServiceInfo(defaultProvider = SarifPlugin.class, description = "load SARIF") +public interface SarifService { + + /** + * Attempts to read a SARIF file + * + * @param sarif file + * @throws IOException + * @throws JsonSyntaxException + * @see #readSarif(sarifFile) + */ + public SarifSchema210 readSarif(File sarifFile) throws JsonSyntaxException, IOException; + + /** + * Attempts to read a SARIF blob + * + * @param sarif string + * @throws IOException + * @throws JsonSyntaxException + * @see #readSarif(sarif) + */ + public SarifSchema210 readSarif(String sarif) throws JsonSyntaxException, IOException; + + /** + * Attempts to load a SARIF file + * + * @param logName tracks errors + * @param sarif base object + * @see #showSarif(logName, sarif) + */ + public void showSarif(String logName, SarifSchema210 sarif); + + /** + * Retrieve the current controller + */ + public SarifController getController(); + +} diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/SarifUtils.java b/Ghidra/Features/Sarif/src/main/java/sarif/SarifUtils.java index 0d298747ca..38b618bb33 100644 --- a/Ghidra/Features/Sarif/src/main/java/sarif/SarifUtils.java +++ b/Ghidra/Features/Sarif/src/main/java/sarif/SarifUtils.java @@ -4,9 +4,9 @@ * 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. @@ -16,19 +16,47 @@ package sarif; import java.io.ByteArrayInputStream; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; import org.bouncycastle.util.encoders.Base64; -import com.contrastsecurity.sarif.*; +import com.contrastsecurity.sarif.Artifact; +import com.contrastsecurity.sarif.ArtifactContent; +import com.contrastsecurity.sarif.ArtifactLocation; +import com.contrastsecurity.sarif.Edge; +import com.contrastsecurity.sarif.Graph; +import com.contrastsecurity.sarif.Location; +import com.contrastsecurity.sarif.LogicalLocation; +import com.contrastsecurity.sarif.Node; +import com.contrastsecurity.sarif.PhysicalLocation; +import com.contrastsecurity.sarif.ReportingDescriptor; +import com.contrastsecurity.sarif.ReportingDescriptorReference; +import com.contrastsecurity.sarif.Run; +import com.contrastsecurity.sarif.ToolComponent; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import ghidra.framework.store.LockException; -import ghidra.program.model.address.*; import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressFactory; +import ghidra.program.model.address.AddressFormatException; +import ghidra.program.model.address.AddressOverflowException; +import ghidra.program.model.address.AddressRange; +import ghidra.program.model.address.AddressRangeImpl; +import ghidra.program.model.address.AddressRangeIterator; +import ghidra.program.model.address.AddressSet; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.address.OverlayAddressSpace; +import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Program; import ghidra.util.InvalidNameException; +import ghidra.util.Msg; import ghidra.util.exception.DuplicateNameException; public class SarifUtils { @@ -39,6 +67,15 @@ public class SarifUtils { // addresses // artifactLocation/uri <= the overlayED space name (typically OTHER) + private static Run currentRun = null; + private static LogicalLocation[] llocs; + private static List addresses; + private static Map nameToOffset = new HashMap<>(); + private static Map nodeLocs = new HashMap<>(); + private static Map edgeSrcs = new HashMap<>(); + private static Map edgeDsts = new HashMap<>(); + private static Map edgeDescs = new HashMap<>(); + public static JsonArray setLocations(Address min, Address max) { AddressSet set = new AddressSet(min, max); return setLocations(set); @@ -78,8 +115,8 @@ public class SarifUtils { } @SuppressWarnings("unchecked") - public static AddressSet getLocations(Map result, Program program, - AddressSet set) throws AddressOverflowException { + public static AddressSet getLocations(Map result, Program program, AddressSet set) + throws AddressOverflowException { if (set == null) { set = new AddressSet(); } @@ -96,21 +133,23 @@ public class SarifUtils { return set; } - public static AddressRange locationToRange(Location location, Program program) - throws AddressOverflowException { + public static AddressRange locationToRange(Location location, Program program) throws AddressOverflowException { PhysicalLocation physicalLocation = location.getPhysicalLocation(); long len = physicalLocation.getAddress().getLength(); - Address addr = locationToAddress(location, program); + Address addr = locationToAddress(location, program, true); return addr == null ? null : new AddressRangeImpl(addr, len); } - public static Address locationToAddress(Location location, Program program) { + public static Address locationToAddress(Location location, Program program, boolean useOverlays) { + Long addr = -1L; + PhysicalLocation physicalLocation = location.getPhysicalLocation(); if (location.getPhysicalLocation() != null) { + addr = physicalLocation.getAddress().getAbsoluteAddress(); + } + if (addr >= 0) { AddressFactory af = program.getAddressFactory(); AddressSpace base = af.getDefaultAddressSpace(); - PhysicalLocation physicalLocation = location.getPhysicalLocation(); - Long addr = physicalLocation.getAddress().getAbsoluteAddress(); String fqn = physicalLocation.getAddress().getFullyQualifiedName(); if (fqn == null) { return longToAddress(base, addr); @@ -129,17 +168,53 @@ public class SarifUtils { String uri = artifact.getUri(); base = program.getAddressFactory().getAddressSpace(uri); if (base == null) { + if (!useOverlays) { + return longToAddress(af.getDefaultAddressSpace(), addr); + } try { base = program.createOverlaySpace(fqn, base); - } - catch (IllegalStateException | DuplicateNameException | InvalidNameException - | LockException e) { + } catch (IllegalStateException | DuplicateNameException | InvalidNameException | LockException e) { throw new RuntimeException("Attempt to create " + fqn + " failed!"); } } AddressSpace space = getAddressSpace(program, fqn, base); return longToAddress(space, addr); } + if (location.getLogicalLocations() != null) { + Set logicalLocations = location.getLogicalLocations(); + for (LogicalLocation logLoc : logicalLocations) { + if (logLoc.getKind() == null) { + logLoc = llocs[logLoc.getIndex().intValue()]; + } + switch (logLoc.getKind()) { + case "function": + String fname = logLoc.getName(); + for (Function func : program.getFunctionManager().getFunctions(true)) { + if (fname.equals(func.getName())) { + return func.getEntryPoint(); + } + } + break; + + case "member": + // From sarif, we need to extract 2 addrs out of members. + // The first address is the function entry point. + return extractFQNameAddrPair(program, logLoc.getFullyQualifiedName()).get(0); + + case "variable": + // From sarif, we need to extract an addr and a var name. + // e.g., __buf + // return the address in the FQN + return extractFunctionEntryAddr(program, logLoc.getFullyQualifiedName()); + + case "instruction": + break; + + default: + Msg.error(program, "Unknown logical location to handle: " + logLoc.toString()); + } + } + } return null; } @@ -150,9 +225,7 @@ public class SarifUtils { } try { space = program.createOverlaySpace(fqn, base); - } - catch (IllegalStateException | DuplicateNameException | InvalidNameException - | LockException e) { + } catch (IllegalStateException | DuplicateNameException | InvalidNameException | LockException e) { throw new RuntimeException("Attempt to create " + fqn + " failed!"); } return space; @@ -169,14 +242,88 @@ public class SarifUtils { return new ByteArrayInputStream(decoded); } - public static ReportingDescriptor getTaxaValue(ReportingDescriptorReference taxa, - ToolComponent taxonomy) { + public static Address extractFunctionEntryAddr(Program program, String fqname) { + String addr = null; + if (fqname.contains("!")) { + fqname = fqname.substring(0, fqname.indexOf("!")); + } + String[] parts = fqname.split("@"); + if (parts.length > 1) { + String[] subparts = parts[1].split(":"); + if (subparts[0].equals("EXTERNAL")) { + try { + addr = subparts[1]; + return program.getAddressFactory().getAddressSpace(subparts[0]).getAddress(addr); + } catch (AddressFormatException e) { + e.printStackTrace(); + } + } + addr = subparts[0]; + } + return program.getAddressFactory().getAddress(addr); + } + + /** + * @param fqname + * @return + */ + public static List

extractFQNameAddrPair(Program program, String fqname) { + List
addr_pair = new ArrayList
(); + String[] parts = fqname.split("@"); + + if (parts.length > 1) { + String[] subparts = parts[1].split(":"); + // subparts: , , ??? + if (subparts.length > 1) { + // This is the function entry point address. + Address faddress = program.getAddressFactory().getAddress(subparts[0]); + addr_pair.add(faddress); + + // This is the insn address. + Address iaddress = program.getAddressFactory().getAddress(subparts[1]); + addr_pair.add(iaddress != null ? iaddress : faddress); + } else { + if (parts[1].contains("!")) { + subparts = parts[1].split("!"); + } + Address faddress = program.getAddressFactory().getAddress(subparts[0]); + addr_pair.add(faddress); + // This is the insn address. + addr_pair.add(faddress); + } + } + + // could return an empty list. + // could return a non-empty list with null in it. + return addr_pair; + } + + public static String extractFQNameFunction(String fqname) { + String fname = "UNKNOWN"; + String[] parts = fqname.split("@"); + if (parts.length > 0) { + fname = parts[0]; + } + return fname; + } + + public static String extractDisplayName(LogicalLocation ll) { + String name = ll.getName(); + String fqname = ll.getFullyQualifiedName(); + if (name != null && name.startsWith("vn")) { + name = fqname.split("@")[0] + ":" + fqname.split(":")[1]; + } else { + name = fqname.split("@")[0] + ":" + name; + } + return name; + } + + public static ReportingDescriptor getTaxaValue(ReportingDescriptorReference taxa, ToolComponent taxonomy) { List view = new ArrayList<>(taxonomy.getTaxa()); return view.get(taxa.getIndex().intValue()); } - public static ToolComponent getTaxonomy(ReportingDescriptorReference taxa, - Set taxonomies) { + public static ToolComponent getTaxonomy(ReportingDescriptorReference taxa, Set taxonomies) { Object idx = taxa.getToolComponent().getIndex(); if (idx == null) { List view = new ArrayList<>(taxonomies); @@ -202,4 +349,94 @@ public class SarifUtils { return names; } + public static LogicalLocation getLogicalLocation(Run run, Location loc) { + Set llocset = loc.getLogicalLocations(); + if (llocset == null) { + return null; + } + Iterator it = llocset.iterator(); + if (it.hasNext()) { + LogicalLocation next = it.next(); + Long index = next.getIndex(); + if (index != null && llocs != null) { + return llocs[index.intValue()]; + } + return next; + } + return null; + } + + public static LogicalLocation getLogicalLocation(Run run, Long index) { + return llocs[index.intValue()]; + } + + public static void validateRun(Run run) { + if (!run.equals(currentRun) || llocs == null) { + initRun(run); + } + } + + private static void initRun(Run run) { + currentRun = run; + addresses = run.getAddresses(); + for (com.contrastsecurity.sarif.Address sarifAddr : addresses) { + Long offset = sarifAddr.getAbsoluteAddress(); + String fqname = sarifAddr.getFullyQualifiedName(); + nameToOffset.put(fqname, offset); + } + Set runLocs = run.getLogicalLocations(); + if (runLocs != null) { + llocs = new LogicalLocation[runLocs.size()]; + runLocs.toArray(llocs); + } + Set rgraphs = run.getGraphs(); + for (Graph rg : rgraphs) { + Set edges = rg.getEdges(); + for (Edge e : edges) { + String id = e.getId(); + String src = e.getSourceNodeId(); + String dst = e.getTargetNodeId(); + String desc = e.getLabel().getText(); + edgeSrcs.put(id, src); + edgeDsts.put(id, dst); + edgeDescs.put(desc, id); + } + Set nodes = rg.getNodes(); + for (Node n : nodes) { + String id = n.getId(); + Location loc = n.getLocation(); + if (loc != null) { + Set logicalLocations = loc.getLogicalLocations(); + LogicalLocation[] llocs = new LogicalLocation[logicalLocations.size()]; + logicalLocations.toArray(llocs); + nodeLocs.put(id, llocs); + } + } + } + } + + public static String getEdge(String fqname) { + return edgeDescs.get(fqname); + } + + public static String getEdgeSource(String edgeId) { + return edgeSrcs.get(edgeId); + } + + public static String getEdgeDest(String edgeId) { + return edgeDsts.get(edgeId); + } + + public static Address getLocAddress(Program program, String fqname) { + Long offset = nameToOffset.get(fqname); + if (offset == null) { + return null; + } + return program.getAddressFactory().getDefaultAddressSpace().getAddress(offset); + } + + public static LogicalLocation[] getNodeLocs(String id) { + return nodeLocs.get(id); + } + } diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/handlers/SarifResultHandler.java b/Ghidra/Features/Sarif/src/main/java/sarif/handlers/SarifResultHandler.java index b2728b9313..cdac346a52 100644 --- a/Ghidra/Features/Sarif/src/main/java/sarif/handlers/SarifResultHandler.java +++ b/Ghidra/Features/Sarif/src/main/java/sarif/handlers/SarifResultHandler.java @@ -4,9 +4,9 @@ * 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. @@ -41,15 +41,16 @@ abstract public class SarifResultHandler implements ExtensionPoint { protected Run run; protected Result result; protected SarifResultsTableProvider provider; + protected boolean isEnabled; public abstract String getKey(); - public boolean isEnabled() { + public boolean isEnabled(SarifDataFrame dframe) { return true; } - public void handle(SarifDataFrame df, Run run, Result result, Map map) { - this.df = df; + public void handle(SarifDataFrame dframe, Run run, Result result, Map map) { + this.df = dframe; this.controller = df.getController(); this.run = run; this.result = result; @@ -77,12 +78,14 @@ abstract public class SarifResultHandler implements ExtensionPoint { return additionalProperties.get(key); } - public ProgramTask getTask(SarifResultsTableProvider provider) { + public ProgramTask getTask(SarifResultsTableProvider tableProvider) { return null; } - public DockingAction createAction(SarifResultsTableProvider provider) { - this.provider = provider; + public DockingAction createAction(SarifResultsTableProvider tableProvider) { + this.provider = tableProvider; + this.isEnabled = isEnabled(provider.getDataFrame()); + DockingAction rightClick = new DockingAction(getActionName(), getKey()) { @Override public void actionPerformed(ActionContext context) { @@ -92,12 +95,12 @@ abstract public class SarifResultHandler implements ExtensionPoint { @Override public boolean isEnabledForContext(ActionContext context) { - return true; + return isEnabled; } @Override public boolean isAddToPopup(ActionContext context) { - return true; + return isEnabled; } }; rightClick.setPopupMenuData(new MenuData(new String[] { getActionName() })); diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/handlers/SarifRunHandler.java b/Ghidra/Features/Sarif/src/main/java/sarif/handlers/SarifRunHandler.java index 5d8775435d..e99db13b4b 100644 --- a/Ghidra/Features/Sarif/src/main/java/sarif/handlers/SarifRunHandler.java +++ b/Ghidra/Features/Sarif/src/main/java/sarif/handlers/SarifRunHandler.java @@ -4,9 +4,9 @@ * 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. @@ -21,25 +21,25 @@ import ghidra.util.classfinder.ExtensionPoint; import sarif.SarifController; import sarif.model.SarifDataFrame; -abstract public class SarifRunHandler implements ExtensionPoint { - +abstract public class SarifRunHandler implements ExtensionPoint { + protected SarifDataFrame df; protected SarifController controller; protected Run run; public abstract String getKey(); - public boolean isEnabled() { + public boolean isEnabled(SarifDataFrame dframe) { return true; } - - public void handle(SarifDataFrame df, Run run) { - this.df = df; + + public void handle(SarifDataFrame dframe, Run run) { + this.df = dframe; this.controller = df.getController(); this.run = run; parse(); } - + protected abstract Object parse(); } diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifAddressResultHandler.java b/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifAddressResultHandler.java index d61b18fda8..2f0872ae0e 100644 --- a/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifAddressResultHandler.java +++ b/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifAddressResultHandler.java @@ -4,35 +4,36 @@ * 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. */ -package sarif.handlers.result -; +package sarif.handlers.result; import java.util.List; import ghidra.program.model.address.Address; import sarif.handlers.SarifResultHandler; -public class SarifAddressResultHandler extends SarifResultHandler { - +public class SarifAddressResultHandler extends SarifResultHandler { + // If we can parse a listing Address we can make the table navigate there when // selected - + + @Override public String getKey() { return "Address"; } + @Override public Address parse() { - List
listingAddresses = controller.getListingAddresses(result); + List
listingAddresses = controller.getListingAddresses(run, result); return listingAddresses.isEmpty() ? null : listingAddresses.get(0); } - + } diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifCodeFlowResultHandler.java b/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifCodeFlowResultHandler.java index 0fd1f6973f..57fcb55186 100644 --- a/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifCodeFlowResultHandler.java +++ b/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifCodeFlowResultHandler.java @@ -4,9 +4,9 @@ * 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. @@ -16,43 +16,39 @@ package sarif.handlers.result; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import com.contrastsecurity.sarif.CodeFlow; -import com.contrastsecurity.sarif.ThreadFlow; -import com.contrastsecurity.sarif.ThreadFlowLocation; +import com.contrastsecurity.sarif.*; import ghidra.program.model.address.Address; import sarif.handlers.SarifResultHandler; public class SarifCodeFlowResultHandler extends SarifResultHandler { + @Override public String getKey() { return "CodeFlows"; } - public List>> parse() { - List>> res = new ArrayList<>(); + @Override + public List> parse() { + List> res = new ArrayList<>(); List codeFlows = result.getCodeFlows(); if (codeFlows != null) { for (CodeFlow f : codeFlows) { - Map> map = new HashMap<>(); - parseCodeFlow(f, map); - res.add(map); + parseCodeFlow(f, res); } } return res; } - private void parseCodeFlow(CodeFlow f, Map> map) { + private void parseCodeFlow(CodeFlow f, List> list) { for (ThreadFlow t : f.getThreadFlows()) { List
addrs = new ArrayList
(); for (ThreadFlowLocation loc : t.getLocations()) { - addrs.add(controller.locationToAddress(loc.getLocation())); + addrs.add(controller.locationToAddress(run, loc.getLocation())); } - map.put(t.getId(), addrs); + list.add(addrs); } } diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifGraphResultHandler.java b/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifGraphResultHandler.java index 068caaffc5..3760a3960a 100644 --- a/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifGraphResultHandler.java +++ b/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifGraphResultHandler.java @@ -4,9 +4,9 @@ * 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. @@ -15,27 +15,23 @@ */ package sarif.handlers.result; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; +import java.util.Map.Entry; -import com.contrastsecurity.sarif.Edge; -import com.contrastsecurity.sarif.Graph; -import com.contrastsecurity.sarif.Node; +import com.contrastsecurity.sarif.*; -import ghidra.service.graph.AttributedGraph; -import ghidra.service.graph.AttributedVertex; -import ghidra.service.graph.EmptyGraphType; +import ghidra.program.model.address.Address; +import ghidra.service.graph.*; import sarif.handlers.SarifResultHandler; public class SarifGraphResultHandler extends SarifResultHandler { + @Override public String getKey() { return "Graphs"; } + @Override public List parse() { List res = new ArrayList(); Set graphs = result.getGraphs(); @@ -48,12 +44,25 @@ public class SarifGraphResultHandler extends SarifResultHandler { } private AttributedGraph parseGraph(Graph g) { - AttributedGraph graph = new AttributedGraph(controller.getProgram().getDescription(), new EmptyGraphType()); + AttributedGraph graph = + new AttributedGraph(controller.getProgram().getDescription(), new EmptyGraphType()); Map nodeMap = new HashMap(); for (Node n : g.getNodes()) { - // AttributedVertex node = graph.addVertex(n.getId(), n.getLabel().getText()); - // node. - nodeMap.put(n.getId(), graph.addVertex(n.getId(), n.getLabel().getText())); + Address addr = controller.locationToAddress(run, n.getLocation()); + String text = n.getLabel().getText(); + AttributedVertex vertex = graph.addVertex(n.getId(), addr.toString()); + PropertyBag properties = n.getProperties(); + if (properties != null) { + Map additional = properties.getAdditionalProperties(); + if (additional != null) { + for (Entry entry : additional.entrySet()) { + vertex.setAttribute(entry.getKey(), entry.getValue().toString()); + } + } + } + vertex.setAttribute("Label", text); + vertex.setAttribute("Address", addr.toString(true)); + nodeMap.put(n.getId(), vertex); } for (Edge e : g.getEdges()) { graph.addEdge(nodeMap.get(e.getSourceNodeId()), nodeMap.get(e.getTargetNodeId())); diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifKindResultHandler.java b/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifKindResultHandler.java index 9fdc36f987..bd803a4fae 100644 --- a/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifKindResultHandler.java +++ b/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifKindResultHandler.java @@ -4,9 +4,9 @@ * 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. @@ -17,17 +17,19 @@ package sarif.handlers.result; import sarif.handlers.SarifResultHandler; -public class SarifKindResultHandler extends SarifResultHandler { - +public class SarifKindResultHandler extends SarifResultHandler { + + @Override public String getKey() { return "Kind"; } + @Override public String parse() { if (result.getKind() != null) { return result.getKind().toString(); } return "none"; } - + } diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifLevelResultHandler.java b/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifLevelResultHandler.java index 4a1637e7fb..8e60195fa8 100644 --- a/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifLevelResultHandler.java +++ b/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifLevelResultHandler.java @@ -4,9 +4,9 @@ * 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. @@ -17,17 +17,19 @@ package sarif.handlers.result; import sarif.handlers.SarifResultHandler; -public class SarifLevelResultHandler extends SarifResultHandler { - +public class SarifLevelResultHandler extends SarifResultHandler { + + @Override public String getKey() { return "Level"; } + @Override public String parse() { if (result.getLevel() != null) { return result.getLevel().toString(); } return "none"; } - + } diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifProgramResultHandler.java b/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifProgramResultHandler.java index 2591fb200c..0292c3c22f 100644 --- a/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifProgramResultHandler.java +++ b/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifProgramResultHandler.java @@ -4,9 +4,9 @@ * 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. @@ -16,16 +16,10 @@ package sarif.handlers.result; import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import com.contrastsecurity.sarif.Location; -import com.contrastsecurity.sarif.PropertyBag; -import com.contrastsecurity.sarif.Result; -import com.contrastsecurity.sarif.Run; +import com.contrastsecurity.sarif.*; import ghidra.program.util.ProgramTask; import ghidra.util.task.TaskMonitor; @@ -35,16 +29,18 @@ import sarif.managers.ProgramSarifMgr; import sarif.model.SarifDataFrame; import sarif.view.SarifResultsTableProvider; -public class SarifProgramResultHandler extends SarifResultHandler { - +public class SarifProgramResultHandler extends SarifResultHandler { + + @Override public String getKey() { return "Message"; } + @Override public void handle(SarifDataFrame df, Run run, Result result, Map map) { this.df = df; this.controller = df.getController(); - + this.run = run; this.result = result; map.put(getKey(), result.getMessage().getText()); @@ -62,13 +58,13 @@ public class SarifProgramResultHandler extends SarifResultHandler { } } } - + @Override protected Object parse() { // UNUSED return null; } - + @Override public String getActionName() { return "Add To Program"; @@ -78,7 +74,7 @@ public class SarifProgramResultHandler extends SarifResultHandler { public ProgramTask getTask(SarifResultsTableProvider provider) { return new CommitToProgramTask(provider); } - + private class CommitToProgramTask extends ProgramTask { private SarifResultsTableProvider provider; @@ -90,7 +86,7 @@ public class SarifProgramResultHandler extends SarifResultHandler { this.programMgr = provider.getController().getProgramSarifMgr(); programMgr.addManagers(); } - + protected void doRun(TaskMonitor monitor) { int[] selected = provider.filterTable.getTable().getSelectedRows(); Map>> results = new HashMap<>(); @@ -106,7 +102,8 @@ public class SarifProgramResultHandler extends SarifResultHandler { } try { programMgr.readResults(monitor, (SarifProgramOptions) null, results); - } catch (IOException e) { + } + catch (IOException e) { throw new RuntimeException("Read failed"); } } diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifPropertyResultHandler.java b/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifPropertyResultHandler.java index 6ea8f9bc0e..96a600a17d 100644 --- a/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifPropertyResultHandler.java +++ b/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifPropertyResultHandler.java @@ -4,9 +4,9 @@ * 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. @@ -15,13 +15,9 @@ */ package sarif.handlers.result; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; -import com.contrastsecurity.sarif.PropertyBag; -import com.contrastsecurity.sarif.Result; -import com.contrastsecurity.sarif.Run; +import com.contrastsecurity.sarif.*; import db.Transaction; import ghidra.program.model.address.Address; @@ -29,20 +25,22 @@ import sarif.handlers.SarifResultHandler; import sarif.model.SarifColumnKey; import sarif.model.SarifDataFrame; -public class SarifPropertyResultHandler extends SarifResultHandler { - +public class SarifPropertyResultHandler extends SarifResultHandler { + + @Override public String getKey() { return "Property"; } - public List
parse() { - return controller.getListingAddresses(result); - } - @Override - public void handle(SarifDataFrame df, Run run, Result result, Map map) { - this.controller = df.getController(); - List columns = df.getColumns(); + public List
parse() { + return controller.getListingAddresses(run, result); + } + + @Override + public void handle(SarifDataFrame dframe, Run run, Result result, Map map) { + this.controller = dframe.getController(); + List columns = dframe.getColumns(); List columnNames = new ArrayList<>(); for (SarifColumnKey c : columns) { columnNames.add(c.getName()); @@ -59,18 +57,18 @@ public class SarifPropertyResultHandler extends SarifResultHandler { for (String key : additional.keySet()) { String[] splits = key.split("/"); switch (splits[0]) { - case "viewer": - switch (splits[1]) { - case "table": - if (!columnNames.contains(splits[2])) { - columns.add(new SarifColumnKey(splits[2], false)); + case "viewer": + switch (splits[1]) { + case "table": + if (!columnNames.contains(splits[2])) { + columns.add(new SarifColumnKey(splits[2], false)); + } + map.put(splits[2], additional.get(key)); } - map.put(splits[2], additional.get(key)); - } - break; - case "listing": - controller.handleListingAction(result, splits[1], additional.get(key)); - break; + break; + case "listing": + controller.handleListingAction(run, result, splits[1], additional.get(key)); + break; } } t.commit(); diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifRuleIdResultHandler.java b/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifRuleIdResultHandler.java index af8c9bbc0e..f129cbaae3 100644 --- a/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifRuleIdResultHandler.java +++ b/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifRuleIdResultHandler.java @@ -4,9 +4,9 @@ * 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. @@ -17,14 +17,16 @@ package sarif.handlers.result; import sarif.handlers.SarifResultHandler; -public class SarifRuleIdResultHandler extends SarifResultHandler { - +public class SarifRuleIdResultHandler extends SarifResultHandler { + + @Override public String getKey() { return "RuleId"; } + @Override public String parse() { return result.getRuleId(); } - + } diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifToolResultHandler.java b/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifToolResultHandler.java index 867c750af4..7ca0f4185f 100644 --- a/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifToolResultHandler.java +++ b/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/SarifToolResultHandler.java @@ -4,9 +4,9 @@ * 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. @@ -17,14 +17,16 @@ package sarif.handlers.result; import sarif.handlers.SarifResultHandler; -public class SarifToolResultHandler extends SarifResultHandler { - +public class SarifToolResultHandler extends SarifResultHandler { + + @Override public String getKey() { return "Tool"; } + @Override public String parse() { return run.getTool().getDriver().getName(); } - + } diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/sample/SarifReturnTypeResultHandler.java b/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/sample/SarifReturnTypeResultHandler.java index 6c66e75baf..6697a3f69d 100644 --- a/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/sample/SarifReturnTypeResultHandler.java +++ b/Ghidra/Features/Sarif/src/main/java/sarif/handlers/result/sample/SarifReturnTypeResultHandler.java @@ -4,9 +4,9 @@ * 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. @@ -18,21 +18,9 @@ package sarif.handlers.result.sample; import java.util.ArrayList; import java.util.List; -import com.contrastsecurity.sarif.ReportingDescriptor; -import com.contrastsecurity.sarif.ReportingDescriptorReference; -import com.contrastsecurity.sarif.ToolComponent; +import com.contrastsecurity.sarif.*; -import ghidra.program.model.data.BooleanDataType; -import ghidra.program.model.data.CharDataType; -import ghidra.program.model.data.DataType; -import ghidra.program.model.data.DoubleDataType; -import ghidra.program.model.data.IntegerDataType; -import ghidra.program.model.data.LongDataType; -import ghidra.program.model.data.LongDoubleDataType; -import ghidra.program.model.data.PointerDataType; -import ghidra.program.model.data.UnsignedIntegerDataType; -import ghidra.program.model.data.UnsignedLongDataType; -import ghidra.program.model.data.VoidDataType; +import ghidra.program.model.data.*; import ghidra.program.model.listing.Function; import ghidra.program.util.ProgramTask; import ghidra.util.exception.InvalidInputException; @@ -40,8 +28,8 @@ import ghidra.util.task.TaskMonitor; import sarif.handlers.SarifResultHandler; import sarif.view.SarifResultsTableProvider; -public class SarifReturnTypeResultHandler extends SarifResultHandler { - +public class SarifReturnTypeResultHandler extends SarifResultHandler { + @Override public String getKey() { return "return type"; @@ -62,76 +50,81 @@ public class SarifReturnTypeResultHandler extends SarifResultHandler { } return null; } - + @Override public String getActionName() { return "Commit"; } @Override - public ProgramTask getTask(SarifResultsTableProvider provider) { - return new ReturnTypeTaxonomyTask(provider); + public ProgramTask getTask(SarifResultsTableProvider tableProvider) { + return new ReturnTypeTaxonomyTask(tableProvider); } - + private class ReturnTypeTaxonomyTask extends ProgramTask { - private SarifResultsTableProvider provider; + private SarifResultsTableProvider tableProvider; protected ReturnTypeTaxonomyTask(SarifResultsTableProvider provider) { - super(provider.getController().getProgram(), "ReturnTypeTaxonomyTask", true, true, true); - this.provider = provider; + super(provider.getController().getProgram(), "ReturnTypeTaxonomyTask", true, true, + true); + this.tableProvider = provider; } - + + @Override protected void doRun(TaskMonitor monitor) { - int col = provider.getIndex("return type"); - int[] selected = provider.filterTable.getTable().getSelectedRows(); + int col = tableProvider.getIndex("return type"); + int[] selected = tableProvider.filterTable.getTable().getSelectedRows(); for (int row : selected) { - Function func = provider.getController().getProgram().getFunctionManager() - .getFunctionContaining(provider.model.getAddress(row)); - String value = (String) provider.getValue(row, col); + Function func = tableProvider.getController() + .getProgram() + .getFunctionManager() + .getFunctionContaining(tableProvider.model.getAddress(row)); + String value = (String) tableProvider.getValue(row, col); setReturnType(func, value); } } - - private boolean setReturnType(Function func, String type) { + + private boolean setReturnType(Function func, String type) { if (type != null) { try { func.setReturnType(parseDataType(type), func.getSignatureSource()); return true; - } catch (InvalidInputException e) { - throw new RuntimeException("Error setting return type for "+func); + } + catch (InvalidInputException e) { + throw new RuntimeException("Error setting return type for " + func); } } return false; } - + private DataType parseDataType(String datatype) { switch (datatype) { - case "int": - return new IntegerDataType(); - case "uint": - case "__ssize_t": - return new UnsignedIntegerDataType(); - case "bool": - return new BooleanDataType(); - case "char": - return new CharDataType(); - case "char *": - case "FILE *": - case "void *": - case "whcar_t *": - case "tm *": - return new PointerDataType(); - case "void": - return new VoidDataType(); - case "double": - return new DoubleDataType(); - case "long": - return new LongDataType(); - case "longdouble": - return new LongDoubleDataType(); - case "ulong": - return new UnsignedLongDataType(); + case "int": + return new IntegerDataType(); + case "uint": + case "__ssize_t": + return new UnsignedIntegerDataType(); + case "bool": + return new BooleanDataType(); + case "char": + return new CharDataType(); + case "char *": + case "FILE *": + case "void *": + case "whcar_t *": + case "tm *": + return new PointerDataType(); + case "void": + return new VoidDataType(); + case "double": + return new DoubleDataType(); + case "long": + return new LongDataType(); + case "longdouble": + return new LongDoubleDataType(); + case "ulong": + return new UnsignedLongDataType(); } return null; } diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/handlers/run/SarifGraphRunHandler.java b/Ghidra/Features/Sarif/src/main/java/sarif/handlers/run/SarifGraphRunHandler.java index b2289176de..4087e3050e 100644 --- a/Ghidra/Features/Sarif/src/main/java/sarif/handlers/run/SarifGraphRunHandler.java +++ b/Ghidra/Features/Sarif/src/main/java/sarif/handlers/run/SarifGraphRunHandler.java @@ -4,9 +4,9 @@ * 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. @@ -15,20 +15,13 @@ */ package sarif.handlers.run; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; +import java.util.Map.Entry; -import com.contrastsecurity.sarif.Edge; -import com.contrastsecurity.sarif.Graph; -import com.contrastsecurity.sarif.Node; -import com.contrastsecurity.sarif.Run; +import com.contrastsecurity.sarif.*; -import ghidra.service.graph.AttributedGraph; -import ghidra.service.graph.AttributedVertex; -import ghidra.service.graph.EmptyGraphType; +import ghidra.program.model.address.Address; +import ghidra.service.graph.*; import sarif.handlers.SarifRunHandler; import sarif.model.SarifDataFrame; @@ -39,21 +32,30 @@ public class SarifGraphRunHandler extends SarifRunHandler { return "graphs"; } + @Override + public boolean isEnabled(SarifDataFrame dframe) { + return dframe.getController().getDefaultGraphHander().equals(getClass()); + } + @Override public List parse() { List res = new ArrayList<>(); Set graphs = run.getGraphs(); if (graphs != null) { for (Graph g : graphs) { - String description = g.getDescription() == null ? controller.getProgram().getDescription() - : g.getDescription().getText(); + String description = + g.getDescription() == null ? controller.getProgram().getDescription() + : g.getDescription().getText(); AttributedGraph graph = new AttributedGraph(description, new EmptyGraphType()); Map nodeMap = new HashMap(); for (Node n : g.getNodes()) { - nodeMap.put(n.getId(), graph.addVertex(n.getId(), n.getLabel().getText())); + AttributedVertex vertex = graph.addVertex(n.getId(), n.getId()); + populateVertex(n, vertex); + nodeMap.put(n.getId(), vertex); } for (Edge e : g.getEdges()) { - graph.addEdge(nodeMap.get(e.getSourceNodeId()), nodeMap.get(e.getTargetNodeId())); + graph.addEdge(nodeMap.get(e.getSourceNodeId()), + nodeMap.get(e.getTargetNodeId())); } res.add(graph); } @@ -61,6 +63,23 @@ public class SarifGraphRunHandler extends SarifRunHandler { return res; } + protected void populateVertex(Node n, AttributedVertex vertex) { + Address addr = controller.locationToAddress(run, n.getLocation()); + vertex.setName(addr.toString()); + String text = n.getLabel().getText(); + PropertyBag properties = n.getProperties(); + if (properties != null) { + Map additional = properties.getAdditionalProperties(); + if (additional != null) { + for (Entry entry : additional.entrySet()) { + vertex.setAttribute(entry.getKey(), entry.getValue().toString()); + } + } + } + vertex.setAttribute("Label", text); + vertex.setAttribute("Address", addr.toString(true)); + } + @Override public void handle(SarifDataFrame df, Run run) { this.df = df; diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/model/SarifDataFrame.java b/Ghidra/Features/Sarif/src/main/java/sarif/model/SarifDataFrame.java index 047fff984b..c61c27420c 100644 --- a/Ghidra/Features/Sarif/src/main/java/sarif/model/SarifDataFrame.java +++ b/Ghidra/Features/Sarif/src/main/java/sarif/model/SarifDataFrame.java @@ -20,18 +20,20 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.Map.Entry; +import java.util.Set; import com.contrastsecurity.sarif.Artifact; import com.contrastsecurity.sarif.ReportingDescriptorReference; import com.contrastsecurity.sarif.Result; import com.contrastsecurity.sarif.Run; import com.contrastsecurity.sarif.SarifSchema210; +import com.contrastsecurity.sarif.Tool; import com.contrastsecurity.sarif.ToolComponent; import com.contrastsecurity.sarif.ToolComponentReference; import sarif.SarifController; +import sarif.SarifUtils; import sarif.handlers.SarifResultHandler; import sarif.handlers.SarifRunHandler; import sarif.managers.ProgramSarifMgr; @@ -49,6 +51,8 @@ public class SarifDataFrame { private Map taxaMap; private String sourceLanguage; private String compiler; + private String toolID; + private String version; public SarifDataFrame(SarifSchema210 sarifLog, SarifController controller, boolean parseHeaderOnly) { this.controller = controller; @@ -70,19 +74,20 @@ public class SarifDataFrame { continue; } compileComponentMap(run); - for (String name :getComponentMap().keySet()) { + for (String name : getComponentMap().keySet()) { columns.add(new SarifColumnKey(name, false)); } ProgramSarifMgr programMgr = controller.getProgramSarifMgr(); for (Entry entry : programMgr.getKeys().entrySet()) { columns.add(new SarifColumnKey(entry.getKey(), entry.getValue())); } + SarifUtils.validateRun(run); for (Result result : run.getResults()) { compileTaxaMap(run, result); Map curTableResult = new HashMap<>(); for (SarifResultHandler handler : resultHandlers) { - if (handler.isEnabled()) { + if (handler.isEnabled(this)) { handler.handle(this, run, result, curTableResult); } } @@ -96,7 +101,7 @@ public class SarifDataFrame { list.add(curTableResult); } for (SarifRunHandler handler : controller.getSarifRunHandlers()) { - if (handler.isEnabled()) { + if (handler.isEnabled(this)) { handler.handle(this, run); } } @@ -104,6 +109,12 @@ public class SarifDataFrame { } private void parseHeader(Run run) { + Tool tool = run.getTool(); + if (tool != null) { + ToolComponent driver = tool.getDriver(); + toolID = driver.getName(); + version = driver.getVersion(); + } Set artifacts = run.getArtifacts(); if (artifacts == null) { return; @@ -179,5 +190,12 @@ public class SarifDataFrame { public String getCompiler() { return compiler; } -} + public String getToolID() { + return toolID; + } + + public String getVersion() { + return version; + } +} diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/view/SarifResultsTableProvider.java b/Ghidra/Features/Sarif/src/main/java/sarif/view/SarifResultsTableProvider.java index c96d500f20..3b8b62881d 100644 --- a/Ghidra/Features/Sarif/src/main/java/sarif/view/SarifResultsTableProvider.java +++ b/Ghidra/Features/Sarif/src/main/java/sarif/view/SarifResultsTableProvider.java @@ -4,9 +4,9 @@ * 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. @@ -16,10 +16,7 @@ package sarif.view; import java.awt.BorderLayout; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import javax.swing.JComponent; import javax.swing.JPanel; @@ -28,22 +25,22 @@ import docking.ComponentProvider; import docking.action.DockingAction; import ghidra.app.services.GoToService; import ghidra.framework.plugintool.Plugin; +import ghidra.program.model.address.Address; import ghidra.program.model.listing.Program; +import ghidra.service.graph.AttributedVertex; import ghidra.util.table.GhidraFilterTable; import ghidra.util.table.GhidraTable; import ghidra.util.table.actions.MakeProgramSelectionAction; import sarif.SarifController; import sarif.handlers.SarifResultHandler; -import sarif.model.SarifColumnKey; -import sarif.model.SarifDataFrame; -import sarif.model.SarifResultsTableModelFactory; +import sarif.model.*; import sarif.model.SarifResultsTableModelFactory.SarifResultsTableModel; /** * Show the SARIF result as a table and build possible actions on the table * */ -public class SarifResultsTableProvider extends ComponentProvider { +public class SarifResultsTableProvider extends ComponentProvider { private JComponent component; public SarifResultsTableModel model; @@ -52,7 +49,8 @@ public class SarifResultsTableProvider extends ComponentProvider { private Plugin plugin; private SarifController controller; - public SarifResultsTableProvider(String description, Plugin plugin, SarifController controller, SarifDataFrame df) { + public SarifResultsTableProvider(String description, Plugin plugin, SarifController controller, + SarifDataFrame df) { super(plugin.getTool(), controller.getProgram().getName(), plugin.getName()); this.plugin = plugin; this.controller = controller; @@ -60,7 +58,9 @@ public class SarifResultsTableProvider extends ComponentProvider { SarifResultsTableModelFactory factory = new SarifResultsTableModelFactory(df.getColumns()); this.model = factory.createModel(description, plugin.getTool(), program, df); this.component = buildPanel(); - filterTable.getTable().getSelectionModel().addListSelectionListener(e -> plugin.getTool().contextChanged(this)); + filterTable.getTable() + .getSelectionModel() + .addListSelectionListener(e -> plugin.getTool().contextChanged(this)); this.createActions(); this.setTransient(); } @@ -68,7 +68,7 @@ public class SarifResultsTableProvider extends ComponentProvider { private JComponent buildPanel() { JPanel panel = new JPanel(new BorderLayout()); filterTable = new GhidraFilterTable<>(this.model); - GhidraTable table = (GhidraTable) filterTable.getTable(); + GhidraTable table = filterTable.getTable(); GoToService goToService = this.getTool().getService(GoToService.class); table.installNavigation(plugin.getTool(), goToService.getDefaultNavigatable()); @@ -76,17 +76,18 @@ public class SarifResultsTableProvider extends ComponentProvider { panel.add(filterTable); return panel; } - + public void dispose() { filterTable.dispose(); closeComponent(); } + @Override public void closeComponent() { super.closeComponent(); getController().removeProvider(this); } - + @Override public JComponent getComponent() { return component; @@ -99,8 +100,8 @@ public class SarifResultsTableProvider extends ComponentProvider { * can be performed */ public void createActions() { - DockingAction selectionAction = new MakeProgramSelectionAction(this.plugin, - (GhidraTable) filterTable.getTable()); + DockingAction selectionAction = + new MakeProgramSelectionAction(this.plugin, filterTable.getTable()); this.addLocalAction(selectionAction); Set resultHandlers = controller.getSarifResultHandlers(); List columns = model.getDataFrame().getColumns(); @@ -113,11 +114,10 @@ public class SarifResultsTableProvider extends ComponentProvider { if (handler.getActionName() != null) { this.addLocalAction(handler.createAction(this)); } - } + } } } - - + public int getIndex(String key) { List columns = model.getDataFrame().getColumns(); for (SarifColumnKey c : columns) { @@ -135,9 +135,34 @@ public class SarifResultsTableProvider extends ComponentProvider { public Map getRow(int x) { return model.getRowObject(x); } - + public SarifController getController() { return controller; } + public SarifDataFrame getDataFrame() { + return model.getDataFrame(); + } + + public void setSelection(Set vertices) { + for (AttributedVertex vertex : vertices) { + Map attributes = vertex.getAttributes(); + if (attributes.containsKey("Address")) { + String addrStr = attributes.get("Address"); + String name = attributes.get("name"); + for (int i = 0; i < model.getRowCount(); i++) { + Address address = model.getAddress(i); + if (address != null && address.toString(true).equals(addrStr)) { + Map rowObject = model.getRowObject(i); + String objName = (String) rowObject.get("name"); + if (name.equals(objName)) { + filterTable.getTable().selectRow(i); + filterTable.getTable().scrollToSelectedRow(); + } + } + } + } + } + } + }