GP-3443: couple more

GP-3443: clean-up & comment
GP-3443: more help edits; fix for QueryResult error
GP-3443: actions refactor
GP-3443: record fix + html fixes
GP-3443: misc
GP-3443: fixes for help
GP-3443: more post-review
GP-3443: refactor on queries + other stuff
GP-3443: test improvement
GP-3443: post-review more easy
GP-3443: post-review easy fixes
GP-3443: imports
GP-3443: formatting fixes
GP-3443: better test
GP-3443: more test improvemnts
GP-3443: fix for fields, more test stuff
GP-3443: prelims for testing
GP-3443: more work on edge cases
GP-3443: fix for locals fixed
GP-3443: fix for locals
GP-3443: by symbol fix
GP-3443: by symbol fix
GP-3443: minor ergonomics
GP-3443: simpler query
GP-3443: fix for structs
GP-3443: fix for clear
GP-3443: thunks working
GP-3443: more deps
GP-3443: dependencies fix
GP-3443: gates
GP-3443: imports organized
GP-3443: oops
GP-3443: oops
GP-3443: pretty substantial refactor
GP-3443: marks now location-specific
GP-3443: better clear
GP-3443: fairly major logic change
GP-3443: (better) functioning plugin
GP-3443: better docs
GP-3443: script
GP-3443: script
GP-3443: bueno
GP-3443: manual rebase
This commit is contained in:
d-millar 2024-12-11 14:50:56 -05:00
parent e66bbc5231
commit 4b641143ec
74 changed files with 10883 additions and 318 deletions

View file

@ -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"
}

View file

@ -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|

File diff suppressed because it is too large Load diff

View file

@ -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<Function> getFunctionSet() {
Set<Function> toProcess = new HashSet<Function>();
toProcess.add(getFunctionContaining(currentAddress));
return toProcess;
}
}

View file

@ -53,4 +53,10 @@
target="help/topics/DecompilerTextFinderPlugin/Decompiler_Text_Finder.html" />
</tocref>
<tocref id="Decompiler">
<tocdef id="DecompilerTaint"
text="Decompiler Taint"
target="help/topics/DecompilerTaint/DecompilerTaint.html" />
</tocref>
</tocroot>

View file

@ -0,0 +1,329 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Decompiler Window</title>
<link rel="stylesheet" type="text/css" href="help/shared/DefaultStyle.css">
<link rel="stylesheet" type="text/css" href="../../shared/languages.css">
<meta name="generator" content="DocBook XSL Stylesheets V1.79.1">
<link rel="home" href="Decompiler.html" title="Decompiler">
<link rel="up" href="Decompiler.html" title="Decompiler">
<link rel="prev" href="DecompilerOptions.html" title="Decompiler Options">
</head>
<body><div class="chapter">
<div class="titlepage"><div><div><h1 class="title">
<a name="DecompilerTaint"></a>Decompiler Taint Operations</h1></div></div></div>
<p>
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.
</p>
<p>
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.
</p>
<p>
The first two steps are activated in the menus under <b>Tools &rarr; Source-Sink</b>. Their options
are accessible via <b>Edit &rarr; Tool Options</b> under <b>Options/Decompiler/Taint</b>. 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.
</p>
<div class="section">
<div class="titlepage"><div><div><h2 class="title" style="clear: both">
<a name="InitActions"></a>Initialization Actions (Tools &rarr; Source-Sink)</h2></div></div></div>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="DeleteIndex"></a>Delete Facts and Index</h3></div></div></div>
<p>
Deletes any pre-existing data (facts and database) from the directories specified under
Taint Options.
</p>
</div>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="ExportFacts"></a>Export PCode Facts</h3></div></div></div>
<p>
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.)
</p>
</div>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="InitializeIndex"></a>Initialize Program Index</h3></div></div></div>
<p>
Converts the directory of PCode facts into an indexed database for future queries.
</p>
</div>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="ReexportFacts"></a>Re-export Function Facts</h3></div></div></div>
<p>
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.)
</p>
</div>
<div class="section">
<div class="titlepage"><div><div><h2 class="title" style="clear: both">
<a name="TaintOptions"></a>Tool Options (Options/Decompiler/Taint)</h2></div></div></div>
<p>
These options govern the locations for various elements of the taint engine.
</p>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="TaintExecutionEngine"></a>Directories.Engine</h3></div></div></div>
<p>
Path to the executable responsible for the taint engine logic.
</p>
</div>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="TaintFactDirectory"></a>Directories.Facts</h3></div></div></div>
<p>
Directory where PCode facts will be written.
</p>
</div>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="TaintOutputDirectory"></a>Directories.Output</h3></div></div></div>
<p>
Directory where database index and queries will be written.
</p>
</div>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="TaintQueryFile"></a>Query.Current Query</h3></div></div></div>
<p>
The file containing the most recent query.
</p>
</div>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="TaintIndexDatabase"></a>Query.Index</h3></div></div></div>
<p>
Name of the file that contains the database produced by the "Index" operation.
</p>
</div>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="TaintTokenColor"></a>Highlight Color</h3></div></div></div>
<p>
The color used for taint highlights in the decompiler.
</p>
</div>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="TaintTokenStyle"></a>Highlight Style</h3></div></div></div>
<p>
"All", "Labels", or "Default".
</p>
</div>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="TaintOutputFormat"></a>Output Format</h3></div></div></div>
<p>
Currently always "sarif+all".
</p>
</div>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="TaintAllAccess"></a>Use all access paths</h3></div></div></div>
<p>
Use all access paths for sink/source variables.
</p>
</div>
<div class="section">
<div class="titlepage"><div><div><h2 class="title" style="clear: both">
<a name="DecompilerMenuActions"></a>Decompiler Pop-up Menu and Keyboard Actions</h2></div></div></div>
<p>
The following actions appear in the <b>Taint</b> 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.
</p>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="TaintSource"></a>Source</h3></div></div></div>
<p>
Mark the selected varnode as a taint source.
</p>
</div>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="TaintSourceSymbol"></a>Source (Symbol)</h3></div></div></div>
<p>
Mark the symbol associated with the selected varnode as a taint source.
</p>
</div>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="TaintSink"></a>Sink</h3></div></div></div>
<p>
Mark the selected varnode as a taint sink (i.e. the endpoint).
</p>
</div>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="TaintSinkSymbol"></a>Sink (Symbol)</h3></div></div></div>
<p>
Mark the symbol associated with the selected varnode as a taint sink.
</p>
</div>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="TaintGate"></a>Gate</h3></div></div></div>
<p>
Mark the selected varnode as a gate (i.e. taint will not pass through this node).
</p>
</div>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="TaintClear"></a>Clear</h3></div></div></div>
<p>
Remove the source, sink, and gate markers, and existing taint.
</p>
</div>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="TaintSliceTree"></a>Slice Tree</h3></div></div></div>
<p>
Launch a call-tree style viewer for the slices for the selected token.
</p>
</div>
<div class="section">
<div class="titlepage"><div><div><h2 class="title" style="clear: both">
<a name="DecompilerToolbarActions"></a>Decompiler Toolbar Actions</h2></div></div></div>
<p>
These actions apply after the source and sinks have been chosen.
</p>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="TaintQuery"></a>Run taint query</h3></div></div></div>
<p>
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.
</p>
</div>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="TaintDefaultQuery"></a>Run default taint query</h3></div></div></div>
<p>
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.)
</p>
</div>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="TaintCustomQuery"></a>Run custom taint query</h3></div></div></div>
<p>
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.
</p>
</div>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="TaintLoadSarif"></a>Load SARIF file</h3></div></div></div>
<p>
Loads a raw SARIF file into the results table.
</p>
</div>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="TaintShowLabels"></a>Show label table</h3></div></div></div>
<p>
Displays the current set of source, sink, and gate markers.
</p>
</div>
<div class="section">
<div class="titlepage"><div><div><h2 class="title" style="clear: both">
<a name="ResultMenuActions"></a>Results Pop-up Menu</h2></div></div></div>
<p>
These actions appear in the context menu of the Query Results table and transfer the selected results to the decompiler/disassembly.
</p>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="TaintAddToProgram"></a>Add To Program</h3></div></div></div>
<p>
Applies SARIF results to the current progam generically, based on the current set of handlers.
</p>
</div>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="TaintApplyByVarnode"></a>Apply taint</h3></div></div></div>
<p>
Highlight the varnodes which have been tainted.
</p>
</div>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="TaintClearResults"></a>Clear taint</h3></div></div></div>
<p>
Clear taint matching the selected rows from the Decompiler listing.
</p>
</div>
<div class="sect2">
<div class="titlepage"><div><div><h3 class="title">
<a name="TaintSelection"></a>Make Selection</h3></div></div></div>
<p>
Create a selection from the selected addresses.
</p>
</div>
</div>
</dl></div>
</div>
</div></body>
</html>

View file

@ -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<TaintLabel> sources = new HashSet<>();
private Set<TaintLabel> sinks = new HashSet<>();
private Set<TaintLabel> gates = new HashSet<>();
// Sets used for highlighting.
private AddressSet taintAddressSet = new AddressSet();
private Map<Address, Set<TaintQueryResult>> 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<String> param_list, Path engine, File indexDBFile,
String index_directory);
@Override
public abstract void buildIndex(List<String> 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<TaintLabel> 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<TaintLabel> marks = getTaintLabels(mtype);
return updateMarks(labelToToggle, marks);
}
private TaintLabel updateMarks(TaintLabel tlabel, Set<TaintLabel> 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.
*
* <p>
* The artifacts (e.g., sources) that are used in the datalog query are those
* selected by the user via the menu.
*
* <p>
* @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<String> param_list = new ArrayList<String>();
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<Address, Set<TaintQueryResult>> vmap) {
taintVarnodeMap = vmap;
}
@Override
public Map<Address, Set<TaintQueryResult>> 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;
}
}

View file

@ -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<String> param_list = new ArrayList<String>();
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();
}
}

View file

@ -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<String, Color> 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();
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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.
* <p>
* <ul><li>
* Index 0: Green
* </li><li>
* Index colors.length / 2: Yellow
* </li><li>
* Index colors.length: Red
* </li></ul>
* <p>
* <ul><li>
* Red: 1.0,0.0,0.0
* </li><li>
* Green: 0.0, 1.0, 0.0
* </li><li>
* Yellow: 1.0, 1.0, 0.0
* </li></ul>
*/
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;
}
}
}
}

View file

@ -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<String> sourceAddresses = new HashSet<>();
private Set<String> sinkAddresses = new HashSet<>();
private Set<String> gateAddresses = new HashSet<>();
// These icon property names go in your Theme properties files in the <home>/.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<ClangLine> 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<String> 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();
}
}

View file

@ -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;
}
}

View file

@ -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<Varnode> varnodes;
private Varnode specialVn;
private PcodeOp specialOp;
private Color hlColor;
private Color specialHlColor;
TaintHighlightColorProvider(DecompilerPanel panel, Set<Varnode> 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;
}
}

View file

@ -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;
}
}

View file

@ -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:
* <ul><li>
* Whether the taint label is active or inactive.
* </li><li>
* The "label" associated with the source, sink, or gate.
* </li></ul>
*/
public class TaintLabelsDataFrame implements ObjectSelectedListener<Map<String, Object>> {
private List<String> 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<Map<String, Object>> 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<String> 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<String, Object> 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<String, Object> row_data = tableResults.get(row);
row_data.put("Selected", value);
}
public void toggleSelected(int row) {
Map<String, Object> 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<String, Object> map : tableResults) {
aset.add((Address) map.get("Address"));
}
}
return aset;
}
public void dumpTableToDebug() {
for (Map<String, Object> row : tableResults) {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, Object> 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<String, Object> row) {
if (row != null && row.containsKey("Address")) {
List<Address> addr_list = new ArrayList<Address>();
addr_list.add((Address) row.get("Address"));
Msg.info(this, "Making selection, " + row.get("Address"));
this.plugin.makeSelection(addr_list);
}
}
public List<Map<String, Object>> getData() {
return this.tableResults;
}
}

View file

@ -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<String> sColumns; // Used for the TableColumnDescriptor
public TaintLabelsTableModelFactory(List<String> 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<Map<String, Object>> {
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<String, Object> 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<Map<String, Object>> 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<String, Object> result : df.getData()) {
Msg.info(this, "Loading: " + result.get("Taint Label Object"));
if (monitor.isCancelled()) {
monitor.clearCancelled();
break;
}
accumulator.add(result);
}
}
@Override
protected TableColumnDescriptor<Map<String, Object>> createTableColumnDescriptor() {
TableColumnDescriptor<Map<String, Object>> 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<Map<String, Object>, Object, Object> {
private String columnName;
public Column(String name) {
columnName = name;
}
@Override
public String getColumnName() {
return columnName;
}
@Override
public Object getValue(Map<String, Object> rowObject, Settings settings, Object data,
ServiceProvider sp) throws IllegalArgumentException {
return rowObject.get(getColumnName());
}
}
public class HighVariableColumn
extends AbstractDynamicTableColumn<Map<String, Object>, HighVariable, Object> {
private String columnName;
public HighVariableColumn(String name) {
columnName = name;
}
@Override
public String getColumnName() {
return columnName;
}
@Override
public HighVariable getValue(Map<String, Object> rowObject, Settings settings,
Object data, ServiceProvider sp) throws IllegalArgumentException {
return (HighVariable) rowObject.get(getColumnName());
}
}
public class TaintLabelColumn
extends AbstractDynamicTableColumn<Map<String, Object>, TaintLabel, Object> {
private String columnName;
public TaintLabelColumn(String name) {
columnName = name;
}
@Override
public String getColumnName() {
return columnName;
}
@Override
public TaintLabel getValue(Map<String, Object> rowObject, Settings settings,
Object data, ServiceProvider sp) throws IllegalArgumentException {
return (TaintLabel) rowObject.get(getColumnName());
}
}
public class StringColumn
extends AbstractDynamicTableColumn<Map<String, Object>, String, Object> {
private String name;
public StringColumn(String name) {
this.name = name;
}
@Override
public String getColumnName() {
return this.name;
}
@Override
public String getValue(Map<String, Object> 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<Map<String, Object>, Boolean, Object> {
private String name;
public BooleanColumn(String name) {
this.name = name;
}
@Override
public String getColumnName() {
return this.name;
}
@Override
public Boolean getValue(Map<String, Object> rowObject, Settings settings, Object data,
ServiceProvider sp) throws IllegalArgumentException {
return (Boolean) rowObject.get(this.name);
}
}
public class AddressColumn
extends AbstractDynamicTableColumn<Map<String, Object>, Address, Object> {
private String name;
public AddressColumn(String name) {
this.name = name;
}
@Override
public String getColumnName() {
return this.name;
}
@Override
public Address getValue(Map<String, Object> rowObject, Settings settings, Object data,
ServiceProvider sp) throws IllegalArgumentException {
return (Address) rowObject.get(this.name);
}
}
public class IntegerColumn
extends AbstractDynamicTableColumn<Map<String, Object>, Integer, Object> {
private String name;
public IntegerColumn(String name) {
this.name = name;
}
@Override
public String getColumnName() {
return this.name;
}
@Override
public Integer getValue(Map<String, Object> rowObject, Settings settings, Object data,
ServiceProvider sp) throws IllegalArgumentException {
Object o = rowObject.get(this.name);
if (o == null) {
return -1;
}
return (Integer) o;
}
}
}
}

View file

@ -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<Map<String, Object>> 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);
}
}

View file

@ -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.
*
* <p>
* 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);
}
}

View file

@ -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<String, TaintSliceTreeProvider> 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<String, TaintSliceTreeProvider> 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<TaintSliceTreeProvider> 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.
*
* <p>
* 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:
* <ul><li>
* the highlight service has been established.
* </li><li>
* the highlighter instance has been instantiated and added to the decompHighlighters map.
* </li></ul>
*
* @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<Address> 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<Address, Set<TaintQueryResult>> vmap, boolean clear) {
if (clear) {
taintProvider.clearTaint();
}
state.setTaintVarnodeMap(vmap);
taintProvider.setTaint();
}
@Override
public AddressSet getAddressSet() {
return state.getTaintAddressSet();
}
@Override
public Map<Address, Set<TaintQueryResult>> getVarnodeMap() {
return state.getTaintVarnodeMap();
}
}

View file

@ -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<String, Color> cachedHighlightsByToken;
// Use the string and not high variable to match on the string shown in the
// decomp.
private Map<Address, TaintHighlight> 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.
* <p>
* 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<Address, Set<TaintQueryResult>> 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<TaintQueryResult> 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<TaintQueryResult> 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<TaintQueryResult> 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.
* <p>
* 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;
}
}

View file

@ -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<String> labels, boolean functionLevelResult) {
public TaintQueryResult(Map<String, Object> result) {
this((String) result.get("name"),
(String) result.get("location"),
(Address) result.get("Address"),
(Address) result.get("entry"),
new ArrayList<String>(),
(Address) result.get("Address") == null);
String value = (String) result.get("value");
addLabel(value);
}
public TaintQueryResult(Map<String, Object> 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<String>(),
(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);
}
}

View file

@ -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;
}
}

View file

@ -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
* <p>
* {@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<Address, Set<TaintQueryResult>> getVarnodeMap();
/**
* Set taint using varnode map
*
* @param vmap tainted addresses
* @param clear before setting
*/
public void setVarnodeMap(Map<Address, Set<TaintQueryResult>> vmap, boolean clear);
/**
* Clear existing taint
*/
public void clearTaint();
}

View file

@ -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<TaintLabel> 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<Address, Set<TaintQueryResult>> vmap);
public Map<Address, Set<TaintQueryResult>> getTaintVarnodeMap();
public void buildIndex(List<String> 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<ClangNode> 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;
}
}

View file

@ -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.
*
* <p>
* The VertexHighlighter applies a different color to each token. All of the highlighted tokens are tainted.
*
* <p>
* There are two determinations a Highlighter must make:
*
* <ol><li>
* Does the token match an element returned from a Source-Sink query that should be highlighted.
* </li><li>
* 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.
* </li></ol>
* Making BOTH of the above determinations require access to the query data.
*
* <p>
* 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<String, Color> 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();
}
}

View file

@ -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.
*
* <p>
* 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);
}

View file

@ -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.");
}
}
}

View file

@ -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");
}
}

View file

@ -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.
*
* <p>
* 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());
}
}

View file

@ -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.
*
* <p>
* This action will allow PREVIOUSLY generated query output (possibly generated externally) to be read
* in for the program binary.
*
* <p>
* 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.");
}
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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));
}
}

View file

@ -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());
}
}

View file

@ -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());
}
}

View file

@ -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:
* <ul><li>
* An input parameter,
* </li><li>
* A stack variable,
* </li><li>
* A variable associated with a register, or
* </li><li>
* A "dynamic" variable.
* </li></ul>
*/
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);
}
}
}

View file

@ -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:
* <ul><li>
* An input parameter,
* </li><li>
* A stack variable,
* </li><li>
* A variable associated with a register, or
* </li><li>
* A "dynamic" variable.
* </li></ul>
*/
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());
}
}

View file

@ -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:
* <ul><li>
* An input parameter,
* </li><li>
* A stack variable,
* </li><li>
* A variable associated with a register, or
* </li><li>
* A "dynamic" variable.
* </li></ul>
*/
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());
}
}

View file

@ -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<String> 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<String> 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).");
}
}

View file

@ -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<String, Object> map) {
this.df = dframe;
this.controller = df.getController();
this.run = run;
this.result = result;
List<Map<String, Object>> 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<CodeFlow> codeFlows = result.getCodeFlows();
if (codeFlows == null) {
return;
}
int path_id = 1;
int path_index = 0;
for (CodeFlow cf : codeFlows) {
List<ThreadFlow> threadFlows = cf.getThreadFlows();
if (threadFlows == null) {
continue;
}
for (ThreadFlow tf : threadFlows) {
List<ThreadFlowLocation> 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<String, Object> 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);
}
}

View file

@ -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<String, Object> additional = properties.getAdditionalProperties();
if (additional != null) {
for (Entry<String, Object> 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));
}
}
}
}

View file

@ -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<String, Object> 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<Location> locs = result.getLocations();
if (locs != null) {
map.put("Locations", locs);
populate(map, locs);
}
PropertyBag properties = result.getProperties();
if (properties != null) {
Map<String, Object> additionalProperties = properties.getAdditionalProperties();
if (additionalProperties != null) {
for (Entry<String, Object> 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<String, Object> map, List<Location> 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<Address, Set<TaintQueryResult>> map = new HashMap<>();
for (int row : selected) {
Map<String, Object> 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<Address, Set<TaintQueryResult>> map,
Map<String, Object> r) {
Address faddr = (Address) r.get("entry");
Set<TaintQueryResult> vset = getSet(map, faddr);
vset.add(new TaintQueryResult(r));
}
private void getTaintedInstruction(Map<Address, Set<TaintQueryResult>> map,
Map<String, Object> r) {
Address faddr = (Address) r.get("entry");
String fqname = (String) r.get("location");
Set<TaintQueryResult> 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<TaintQueryResult> getSet(Map<Address, Set<TaintQueryResult>> map,
Address faddr) {
Set<TaintQueryResult> vset = map.get(faddr);
if (vset == null) {
vset = new HashSet<TaintQueryResult>();
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<Address, Set<TaintQueryResult>> map = service.getVarnodeMap();
Map<Address, Set<TaintQueryResult>> mapX = new HashMap<>();
for (Entry<Address, Set<TaintQueryResult>> entry : map.entrySet()) {
Set<TaintQueryResult> entryX = new HashSet<>();
entryX.addAll(entry.getValue());
mapX.put(entry.getKey(), entryX);
}
for (int row : selected) {
Map<String, Object> 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<Address, Set<TaintQueryResult>> map,
Map<String, Object> r) {
Address faddr = (Address) r.get("entry");
Set<TaintQueryResult> vset = getSet(map, faddr);
vset.remove(new TaintQueryResult(r));
}
private void removeTaintedInstruction(Map<Address, Set<TaintQueryResult>> map,
Map<String, Object> r) {
Address faddr = (Address) r.get("entry");
String fqname = (String) r.get("location");
Set<TaintQueryResult> 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<TaintQueryResult> getSet(Map<Address, Set<TaintQueryResult>> map, Address faddr) {
Set<TaintQueryResult> vset = map.get(faddr);
if (vset == null) {
vset = new HashSet<TaintQueryResult>();
map.put(faddr, vset);
}
return vset;
}
}

View file

@ -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<GTreeNode> 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;
}
}

View file

@ -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.
*
* <p>
* 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<GTreeNode> generateChildren(TaskMonitor monitor) throws CancelledException {
FunctionSignatureFieldLocation location =
new FunctionSignatureFieldLocation(program, functionAddress);
Set<Address> addresses = ReferenceUtils.getReferenceAddresses(location, monitor);
LazyMap<Function, List<GTreeNode>> 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<GTreeNode> 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;
}
}

View file

@ -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;
}
}

View file

@ -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<GTreeNode> generateChildren(TaskMonitor monitor) throws CancelledException {
return new ArrayList<>();
}
}

View file

@ -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);
}
}

View file

@ -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<GTreeNode> generateChildren(TaskMonitor monitor) throws CancelledException {
AddressSetView functionBody = function.getBody();
Address entryPoint = function.getEntryPoint();
Set<Reference> references = getReferencesFrom(program, functionBody, monitor);
LazyMap<Function, List<GTreeNode>> 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<GTreeNode> children =
nodesByFunction.values()
.stream()
.flatMap(list -> list.stream())
.collect(Collectors.toList());
Collections.sort(children, new CallNodeComparator());
return children;
}
private void createNode(LazyMap<Function, List<GTreeNode>> 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;
}
}

View file

@ -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;
}
}

View file

@ -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.
*
* <p>
* 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<Reference> getReferencesFrom(Program program, AddressSetView addresses,
TaskMonitor monitor) throws CancelledException {
Set<Reference> 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<Function, List<GTreeNode>> nodesByFunction,
SliceNode node) {
Function function = node.getRemoteFunction();
List<GTreeNode> 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<GTreeNode> {
@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;
}
}

View file

@ -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<ClangToken> tokenIterator;
private Run run;
private Address functionAddr;
private String[] functionLabels = { "0x10021f0", "0x1003e21", "0x10021f0" };
private Map<String, ClangToken> 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<Address, Set<TaintQueryResult>> map = new HashMap<>();
for (Map<String, Object> result : df.getTableResults()) {
processResult(map, result);
}
taintService.setVarnodeMap(map, true);
validateResult(token, map);
}
private void processResult(Map<Address, Set<TaintQueryResult>> map, Map<String, Object> 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<Address, Set<TaintQueryResult>> map) {
Set<TaintQueryResult> 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<Address, Set<TaintQueryResult>> map,
Map<String, Object> result) {
Address faddr = (Address) result.get("entry");
Set<TaintQueryResult> vset = getSet(map, faddr);
vset.add(new TaintQueryResult(result));
}
private void getTaintedInstruction(Map<Address, Set<TaintQueryResult>> map,
Map<String, Object> result) {
Address faddr = (Address) result.get("entry");
String fqname = (String) result.get("location");
Set<TaintQueryResult> 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<TaintQueryResult> getSet(Map<Address, Set<TaintQueryResult>> map, Address faddr) {
Set<TaintQueryResult> vset = map.get(faddr);
if (vset == null) {
vset = new HashSet<TaintQueryResult>();
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;
}
}
}

View file

@ -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<Map<String, Objec
public Set<ImageArtifactDisplay> artifacts = new HashSet<>();
public Set<GraphDisplay> graphs = new HashSet<>();
private Class<? extends SarifGraphRunHandler> defaultGraphHandler = SarifGraphRunHandler.class;
private boolean useOverlays;
public Set<SarifResultHandler> getSarifResultHandlers() {
Set<SarifResultHandler> set = new HashSet<>();
set.addAll(ClassSearcher.getInstances(SarifResultHandler.class));
@ -107,7 +111,8 @@ public class SarifController implements ObjectSelectedListener<Map<String, Objec
public void showTable(String logName, SarifSchema210 sarif) {
SarifDataFrame df = new SarifDataFrame(sarif, this, false);
SarifResultsTableProvider provider = new SarifResultsTableProvider(logName, this.plugin, this, df);
SarifResultsTableProvider provider =
new SarifResultsTableProvider(logName, getPlugin(), this, df);
provider.filterTable.addSelectionListener(this);
provider.addToTool();
provider.setVisible(true);
@ -118,8 +123,9 @@ public class SarifController implements ObjectSelectedListener<Map<String, Objec
}
public void showImage(String key, BufferedImage img) {
if (plugin.displayArtifacts()) {
ImageArtifactDisplay display = new ImageArtifactDisplay(plugin.getTool(), key, "Sarif Parse", img);
if (getPlugin().displayArtifacts()) {
ImageArtifactDisplay display =
new ImageArtifactDisplay(getPlugin().getTool(), key, "Sarif Parse", img);
display.setVisible(true);
artifacts.add(display);
}
@ -127,17 +133,22 @@ public class SarifController implements ObjectSelectedListener<Map<String, Objec
public void showGraph(AttributedGraph graph) {
try {
GraphDisplayBroker service = this.plugin.getTool().getService(GraphDisplayBroker.class);
boolean append = plugin.appendToGraph();
PluginTool tool = this.getPlugin().getTool();
GraphDisplayBroker service = tool.getService(GraphDisplayBroker.class);
boolean append = getPlugin().appendToGraph();
GraphDisplay display = service.getDefaultGraphDisplay(append, null);
GraphDisplayOptions graphOptions = new GraphDisplayOptions(new EmptyGraphType());
graphOptions.setMaxNodeCount(plugin.getGraphSize());
graphOptions.setMaxNodeCount(getPlugin().getGraphSize());
if (plugin.displayGraphs()) {
if (getPlugin().displayGraphs()) {
display.setGraph(graph, graphOptions, graph.getDescription(), append, null);
SarifGraphDisplayListener listener =
new SarifGraphDisplayListener(this, display, graph);
display.setGraphDisplayListener(listener);
graphs.add(display);
}
} catch (GraphException | CancelledException e) {
}
catch (GraphException | CancelledException e) {
Msg.error(this, "showGraph failed " + e.getMessage());
}
}
@ -145,37 +156,27 @@ public class SarifController implements ObjectSelectedListener<Map<String, Objec
/**
* If a results has "listing/<something>" 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<Address> addrs = getListingAddresses(result);
public void handleListingAction(Run run, Result result, String key, Object value) {
List<Address> 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<Map<String, Objec
/**
* Get listing addresses associated with a result
*
* @param result
* @return
*/
public List<Address> getListingAddresses(Result result) {
public List<Address> getListingAddresses(Run run, Result result) {
List<Address> addrs = new ArrayList<>();
if (result.getLocations() != null && result.getLocations().size() > 0) {
List<Location> 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<Map<String, Objec
return addrs;
}
public Address locationToAddress(Location location) {
return SarifUtils.locationToAddress(location, program);
public Address locationToAddress(Run run, Location loc) {
return SarifUtils.locationToAddress(loc, program, useOverlays);
}
/**
* Pull the text information from a State object
* @param stateKey
* @return The text value or empty string if key not found.
*/
public String getStateText(State state, String stateKey) {
String result = "";
Map<String, MultiformatMessageString> state_mappings = state.getAdditionalProperties();
for (Map.Entry<String, MultiformatMessageString> 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<Map<String, Objec
if (row != null) {
if (row.containsKey("CodeFlows")) {
for (List<Address> flow : (List<List<Address>>) row.get("CodeFlows")) {
this.plugin.makeSelection(flow);
this.getPlugin().makeSelection(flow);
}
}
if (row.containsKey("Graphs")) {
@ -244,7 +261,30 @@ public class SarifController implements ObjectSelectedListener<Map<String, Objec
public void setProgram(Program program) {
this.program = program;
this.bookmarkManager = program.getBookmarkManager();
bookmarkManager.defineType("Sarif", ResourceManager.loadImage("images/peach_16.png"), Color.pink, 0);
bookmarkManager.defineType("Sarif", SarifPlugin.SARIF_ICON, Color.pink, 0);
}
public SarifPlugin getPlugin() {
return plugin;
}
public void setSelection(Set<AttributedVertex> vertices) {
for (SarifResultsTableProvider provider : providers) {
provider.setSelection(vertices);
}
}
public Class<? extends SarifGraphRunHandler> getDefaultGraphHander() {
return defaultGraphHandler;
}
@SuppressWarnings("unchecked")
public void setDefaultGraphHander(Class<? extends SarifGraphRunHandler> clazz) {
defaultGraphHandler = clazz;
}
public void setUseOverlays(boolean useOverlays) {
this.useOverlays = useOverlays;
}
}

View file

@ -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<Address, Set<AttributedVertex>> 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<AttributedVertex> 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<AttributedVertex> vertices = getVertices(location.getAddress());
if (vertices != null) {
graphDisplay.selectVertices(vertices, EventTrigger.INTERNAL_ONLY);
}
}
}
}
@Override
public void selectionChanged(Set<AttributedVertex> 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<AttributedVertex> getVertices(Address address) {
return map.get(address);
}
@Override
protected Set<AttributedVertex> getVertices(AddressSetView addrSet) {
if (addrSet.isEmpty()) {
return Collections.emptySet();
}
Set<AttributedVertex> vertices = new HashSet<>();
for (Entry<Address, Set<AttributedVertex>> entry : map.entrySet()) {
if (addrSet.contains(entry.getKey())) {
for (AttributedVertex v : entry.getValue()) {
vertices.add(v);
}
}
}
return vertices;
}
@Override
protected AddressSet getAddresses(Set<AttributedVertex> vertices) {
AddressSet addrSet = new AddressSet();
Collection<Set<AttributedVertex>> values = map.values();
for (Set<AttributedVertex> 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);
}
}

View file

@ -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
*/
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<Program, SarifController> 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);

View file

@ -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
* <p>
* {@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();
}

View file

@ -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<com.contrastsecurity.sarif.Address> addresses;
private static Map<String, Long> nameToOffset = new HashMap<>();
private static Map<String, LogicalLocation[]> nodeLocs = new HashMap<>();
private static Map<String, String> edgeSrcs = new HashMap<>();
private static Map<String, String> edgeDsts = new HashMap<>();
private static Map<String, String> 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<String, Object> result, Program program,
AddressSet set) throws AddressOverflowException {
public static AddressSet getLocations(Map<String, Object> 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<LogicalLocation> 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<Address> extractFQNameAddrPair(Program program, String fqname) {
List<Address> addr_pair = new ArrayList<Address>();
String[] parts = fqname.split("@");
if (parts.length > 1) {
String[] subparts = parts[1].split(":");
// subparts: <FN ADDR> , <INSN ADDR>, ???
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<ReportingDescriptor> view = new ArrayList<>(taxonomy.getTaxa());
return view.get(taxa.getIndex().intValue());
}
public static ToolComponent getTaxonomy(ReportingDescriptorReference taxa,
Set<ToolComponent> taxonomies) {
public static ToolComponent getTaxonomy(ReportingDescriptorReference taxa, Set<ToolComponent> taxonomies) {
Object idx = taxa.getToolComponent().getIndex();
if (idx == null) {
List<ToolComponent> view = new ArrayList<>(taxonomies);
@ -202,4 +349,94 @@ public class SarifUtils {
return names;
}
public static LogicalLocation getLogicalLocation(Run run, Location loc) {
Set<LogicalLocation> llocset = loc.getLogicalLocations();
if (llocset == null) {
return null;
}
Iterator<LogicalLocation> 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<LogicalLocation> runLocs = run.getLogicalLocations();
if (runLocs != null) {
llocs = new LogicalLocation[runLocs.size()];
runLocs.toArray(llocs);
}
Set<Graph> rgraphs = run.getGraphs();
for (Graph rg : rgraphs) {
Set<Edge> 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<Node> nodes = rg.getNodes();
for (Node n : nodes) {
String id = n.getId();
Location loc = n.getLocation();
if (loc != null) {
Set<LogicalLocation> 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);
}
}

View file

@ -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<String, Object> map) {
this.df = df;
public void handle(SarifDataFrame dframe, Run run, Result result, Map<String, Object> 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() }));

View file

@ -29,12 +29,12 @@ abstract public class SarifRunHandler implements ExtensionPoint {
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();

View file

@ -13,25 +13,26 @@
* 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<Address> listingAddresses = controller.getListingAddresses(result);
List<Address> listingAddresses = controller.getListingAddresses(run, result);
return listingAddresses.isEmpty() ? null : listingAddresses.get(0);
}

View file

@ -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<Map<String, List<Address>>> parse() {
List<Map<String, List<Address>>> res = new ArrayList<>();
@Override
public List<List<Address>> parse() {
List<List<Address>> res = new ArrayList<>();
List<CodeFlow> codeFlows = result.getCodeFlows();
if (codeFlows != null) {
for (CodeFlow f : codeFlows) {
Map<String, List<Address>> map = new HashMap<>();
parseCodeFlow(f, map);
res.add(map);
parseCodeFlow(f, res);
}
}
return res;
}
private void parseCodeFlow(CodeFlow f, Map<String, List<Address>> map) {
private void parseCodeFlow(CodeFlow f, List<List<Address>> list) {
for (ThreadFlow t : f.getThreadFlows()) {
List<Address> addrs = new ArrayList<Address>();
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);
}
}

View file

@ -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<AttributedGraph> parse() {
List<AttributedGraph> res = new ArrayList<AttributedGraph>();
Set<Graph> 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<String, AttributedVertex> nodeMap = new HashMap<String, AttributedVertex>();
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<String, Object> additional = properties.getAdditionalProperties();
if (additional != null) {
for (Entry<String, Object> 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()));

View file

@ -17,12 +17,14 @@ 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();

View file

@ -17,12 +17,14 @@ 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();

View file

@ -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,12 +29,14 @@ 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<String, Object> map) {
this.df = df;
this.controller = df.getController();
@ -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");
}
}

View file

@ -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";
}
@Override
public List<Address> parse() {
return controller.getListingAddresses(result);
return controller.getListingAddresses(run, result);
}
@Override
public void handle(SarifDataFrame df, Run run, Result result, Map<String, Object> map) {
this.controller = df.getController();
List<SarifColumnKey> columns = df.getColumns();
public void handle(SarifDataFrame dframe, Run run, Result result, Map<String, Object> map) {
this.controller = dframe.getController();
List<SarifColumnKey> columns = dframe.getColumns();
List<String> 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();

View file

@ -17,12 +17,14 @@ 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();
}

View file

@ -17,12 +17,14 @@ 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();
}

View file

@ -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,7 +28,7 @@ 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() {
@ -69,37 +57,42 @@ public class SarifReturnTypeResultHandler extends SarifResultHandler {
}
@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;
@ -107,31 +100,31 @@ public class SarifReturnTypeResultHandler extends SarifResultHandler {
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;
}

View file

@ -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<AttributedGraph> parse() {
List<AttributedGraph> res = new ArrayList<>();
Set<Graph> 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<String, AttributedVertex> nodeMap = new HashMap<String, AttributedVertex>();
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<String, Object> additional = properties.getAdditionalProperties();
if (additional != null) {
for (Entry<String, Object> 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;

View file

@ -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<String, ReportingDescriptorReference> 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<String, Boolean> entry : programMgr.getKeys().entrySet()) {
columns.add(new SarifColumnKey(entry.getKey(), entry.getValue()));
}
SarifUtils.validateRun(run);
for (Result result : run.getResults()) {
compileTaxaMap(run, result);
Map<String, Object> 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<Artifact> 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;
}
}

View file

@ -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());
@ -82,6 +82,7 @@ public class SarifResultsTableProvider extends ComponentProvider {
closeComponent();
}
@Override
public void closeComponent() {
super.closeComponent();
getController().removeProvider(this);
@ -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<SarifResultHandler> resultHandlers = controller.getSarifResultHandlers();
List<SarifColumnKey> columns = model.getDataFrame().getColumns();
@ -117,7 +118,6 @@ public class SarifResultsTableProvider extends ComponentProvider {
}
}
public int getIndex(String key) {
List<SarifColumnKey> columns = model.getDataFrame().getColumns();
for (SarifColumnKey c : columns) {
@ -140,4 +140,29 @@ public class SarifResultsTableProvider extends ComponentProvider {
return controller;
}
public SarifDataFrame getDataFrame() {
return model.getDataFrame();
}
public void setSelection(Set<AttributedVertex> vertices) {
for (AttributedVertex vertex : vertices) {
Map<String, String> 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<String, Object> rowObject = model.getRowObject(i);
String objName = (String) rowObject.get("name");
if (name.equals(objName)) {
filterTable.getTable().selectRow(i);
filterTable.getTable().scrollToSelectedRow();
}
}
}
}
}
}
}