mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 10:19:23 +02:00
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:
parent
e66bbc5231
commit
4b641143ec
74 changed files with 10883 additions and 318 deletions
|
@ -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"
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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 → Source-Sink</b>. Their options
|
||||
are accessible via <b>Edit → 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 → 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>
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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.");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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.");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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).");
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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<>();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,25 +156,14 @@ 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
|
||||
/*
|
||||
* {@link program.model.listing.CodeUnit}
|
||||
*/
|
||||
String comment = (String) value;
|
||||
getProgram().getListing().setComment(addr, CodeUnit.PLATE_COMMENT, comment);
|
||||
|
@ -174,7 +174,8 @@ public class SarifController implements ObjectSelectedListener<Map<String, Objec
|
|||
break;
|
||||
case "bookmark":
|
||||
String bookmark = (String) value;
|
||||
getProgram().getBookmarkManager().setBookmark(addr, "Sarif", result.getRuleId(), bookmark);
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
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);
|
||||
|
|
69
Ghidra/Features/Sarif/src/main/java/sarif/SarifService.java
Normal file
69
Ghidra/Features/Sarif/src/main/java/sarif/SarifService.java
Normal 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();
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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() }));
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -13,8 +13,7 @@
|
|||
* 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;
|
||||
|
||||
|
@ -26,12 +25,14 @@ 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -19,10 +19,12 @@ import sarif.handlers.SarifResultHandler;
|
|||
|
||||
public class SarifKindResultHandler extends SarifResultHandler {
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return "Kind";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String parse() {
|
||||
if (result.getKind() != null) {
|
||||
return result.getKind().toString();
|
||||
|
|
|
@ -19,10 +19,12 @@ import sarif.handlers.SarifResultHandler;
|
|||
|
||||
public class SarifLevelResultHandler extends SarifResultHandler {
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return "Level";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String parse() {
|
||||
if (result.getLevel() != null) {
|
||||
return result.getLevel().toString();
|
||||
|
|
|
@ -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;
|
||||
|
@ -37,10 +31,12 @@ import sarif.view.SarifResultsTableProvider;
|
|||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -31,18 +27,20 @@ import sarif.model.SarifDataFrame;
|
|||
|
||||
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());
|
||||
|
@ -69,7 +67,7 @@ public class SarifPropertyResultHandler extends SarifResultHandler {
|
|||
}
|
||||
break;
|
||||
case "listing":
|
||||
controller.handleListingAction(result, splits[1], additional.get(key));
|
||||
controller.handleListingAction(run, result, splits[1], additional.get(key));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,10 +19,12 @@ import sarif.handlers.SarifResultHandler;
|
|||
|
||||
public class SarifRuleIdResultHandler extends SarifResultHandler {
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return "RuleId";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String parse() {
|
||||
return result.getRuleId();
|
||||
}
|
||||
|
|
|
@ -19,10 +19,12 @@ import sarif.handlers.SarifResultHandler;
|
|||
|
||||
public class SarifToolResultHandler extends SarifResultHandler {
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return "Tool";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String parse() {
|
||||
return run.getTool().getDriver().getName();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -69,26 +57,30 @@ 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);
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +90,8 @@ public class SarifReturnTypeResultHandler extends SarifResultHandler {
|
|||
try {
|
||||
func.setReturnType(parseDataType(type), func.getSignatureSource());
|
||||
return true;
|
||||
} catch (InvalidInputException e) {
|
||||
}
|
||||
catch (InvalidInputException e) {
|
||||
throw new RuntimeException("Error setting return type for " + func);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
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;
|
||||
|
|
|
@ -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;
|
||||
|
@ -77,12 +81,13 @@ public class SarifDataFrame {
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,15 +25,15 @@ 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;
|
||||
|
||||
/**
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue