GP-6129: post-review(2)

GP-6129: post-review
GP-6129: post-review
GP-6129: typo
GP-6129: first bits
GP-6129: first resultsGP-6129: more or less functional sarif tableGP-6129: pre-refactorGP-6129: tagging taint with opGP-6129: fix for composed libsGP-6129: taint action workingGP-6129: fqname errorGP-6129: error handlingGP-6129: error handling
This commit is contained in:
d-millar
2025-11-24 15:11:54 -05:00
parent 4c7ea237c3
commit cba3d5a963
23 changed files with 883 additions and 68 deletions

View File

@@ -0,0 +1,453 @@
/* ###
* 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.debug.taint;
import java.io.*;
import java.util.*;
import java.util.Map.Entry;
import com.google.gson.*;
import com.google.gson.stream.JsonWriter;
import docking.action.builder.ActionBuilder;
import ghidra.app.context.ProgramLocationActionContext;
import ghidra.app.decompiler.ClangToken;
import ghidra.app.decompiler.location.DefaultDecompilerLocation;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.service.breakpoint.PlaceEmuBreakpointActionItem;
import ghidra.app.plugin.core.decompiler.taint.*;
import ghidra.app.script.AskDialog;
import ghidra.app.script.GhidraScript;
import ghidra.app.services.ConsoleService;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.exec.PcodeProgram;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.*;
import ghidra.program.model.pcode.*;
import ghidra.program.util.*;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.trace.model.property.*;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import sarif.SarifService;
import sarif.export.SarifWriterTask;
import sarif.export.WrappedLogicalLocation;
import sarif.managers.SarifMgr;
/**
* Container for all the decompiler elements the users "selects" via the menu.
* This data is used to build queries.
*/
public class EmulatorTaintState extends AbstractTaintState {
private DebuggerTraceManagerService traceManager;
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
private static final String SARIF_URL =
"https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0.json";
private static final String SARIF_VERSION = "2.1.0";
private int llIndex = 0;
private Map<String, WrappedLogicalLocation> logicalLocations = new HashMap<>();
//private Set<VariableRef> annotationSet = new HashSet<>();
private Map<String, Map<Integer, String>> registerNames = new HashMap<>();
private Object lastValue;
public record KTV(String key, String type, String value, String displayName) {}
public interface SetTaintAction {
String NAME = "Set Taint";
String DESCRIPTION = "Set taint for given varnode";
String GROUP = DebuggerResources.GROUP_GENERAL;
String HELP_ANCHOR = "set_taint";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.toolBarGroup(GROUP)
.menuGroup(GROUP)
.popupMenuGroup(GROUP)
.popupMenuPath(NAME)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
public EmulatorTaintState(TaintPlugin plugin) {
super(plugin);
ENGINE_NAME = "emulator";
plugin.setTaintState(this);
SetTaintAction.builder(plugin)
.withContext(ProgramLocationActionContext.class)
.onAction(this::setTaint)
.buildAndInstall(plugin.getTool());
}
private void setTaint(ProgramLocationActionContext context) {
Program currentProgram = plugin.getCurrentProgram();
Address addr = context.getAddress();
FunctionManager functionManager = currentProgram.getFunctionManager();
Function f = functionManager.getFunctionContaining(addr);
ProgramLocation location = context.getLocation();
int row = location.getRow();
int offset = location.getCharOffset();
String tokenId = null;
if (location instanceof PcodeFieldLocation pfl) {
List<String> pcodeStrings = pfl.getPcodeStrings();
String test = pcodeStrings.get(row);
int lastSpace = test.lastIndexOf(" ");
int index = offset > lastSpace ? 1 : 0;
Instruction inst = currentProgram.getListing().getInstructionContaining(addr);
PcodeOp[] pcode = inst.getPcode();
PcodeOp op = pcode[row];
if (index >= op.getNumInputs()) {
index--;
}
Varnode vn = op.getInput(index);
tokenId = vn2oper(vn);
sources.add(new TaintLabel(MarkType.SOURCE, f.getName(), addr, vn.getAddress()));
}
else if (location instanceof OperandFieldLocation ofl) {
Address refAddress = ofl.getRefAddress();
sources.add(new TaintLabel(MarkType.SOURCE, f.getName(), addr, refAddress));
if (refAddress == null) {
tokenId = ofl.getOperandRepresentation();
}
else {
tokenId = addr2oper(refAddress, refAddress.getSize() / 8);
}
}
else if (location instanceof DefaultDecompilerLocation ddl) {
ClangToken token = ddl.getToken();
plugin.toggleIcon(MarkType.SOURCE, token, false);
return; // taint is set via the token
}
else {
AskDialog<String> dialog = new AskDialog<>("Emulator Taint",
"Varnode address", AskDialog.STRING, lastValue);
if (dialog.isCanceled()) {
return;
}
tokenId = dialog.getValueAsString();
}
setTaint(MarkType.SOURCE, f, addr, tokenId);
}
private boolean init() {
PluginTool tool = plugin.getTool();
traceManager = tool.getService(DebuggerTraceManagerService.class);
current = traceManager.getCurrent();
return current.getTrace() != null;
}
/**
* 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 (!init()) {
return false;
}
taintOptions = plugin.getOptions();
TraceAddressPropertyManager mgr = current.getTrace().getAddressPropertyManager();
TracePropertyMap<String> propertyMap = mgr.getPropertyMap("Taint", String.class);
readQueryResultsIntoDataFrame(program, propertyMap);
return true;
}
@Override
public TaintLabel toggleMark(MarkType mtype, ClangToken token) throws PcodeException {
TaintLabel mark = super.toggleMark(mtype, token);
setTaint(mtype, mark);
return mark;
}
private void setTaint(MarkType source, Function f, Address target, String tokenId) {
if (!init()) {
return;
}
String taint = "%s = taint_arr(%s);".formatted(tokenId, tokenId);
String sleigh = """
%s
emu_exec_decoded();
""".formatted(taint);
injectTaint(target, sleigh);
}
public void setTaint(MarkType type, TaintLabel mark) {
if (!init()) {
return;
}
Address target = mark.getAddress();
String opnd = vn2oper(mark.getVnode());
String taint = "%s = taint_arr(%s);".formatted(opnd, opnd);
String sleigh = """
%s
emu_exec_decoded();
""".formatted(taint);
injectTaint(target, sleigh);
}
private void injectTaint(Address target, String sleigh) {
PlaceEmuBreakpointActionItem item = new PlaceEmuBreakpointActionItem(current.getTrace(),
current.getSnap(), target, 1, Set.of(TraceBreakpointKind.SW_EXECUTE),
sleigh);
item.execute();
}
public PcodeProgram rebase(Address target, PcodeProgram orig) {
List<PcodeOp> origCode = orig.getCode();
List<PcodeOp> code = new ArrayList<>();
for (PcodeOp op : origCode) {
code.add(new PcodeOp(target, op.getSeqnum().getTime(), op.getOpcode(), op.getInputs(),
op.getOutput()));
}
return new PcodeProgram(orig, code);
}
private Writer getWriter() {
return new StringWriter(10000);
}
protected void readQueryResultsIntoDataFrame(Program program,
TracePropertyMap<String> propertyMap) {
taintAddressSet.clear();
taintVarnodeMap.clear();
logicalLocations.clear();
llIndex = 0;
try {
Writer baseWriter = getWriter();
JsonWriter writer = new JsonWriter(baseWriter);
writer.setIndent(" ");
Gson gson = new GsonBuilder().setPrettyPrinting()
.excludeFieldsWithoutExposeAnnotation()
.serializeNulls()
.disableHtmlEscaping()
.create();
JsonObject sarif = new JsonObject();
JsonArray results = new JsonArray();
JsonArray llocs = new JsonArray();
writeSarifHeader(program, sarif, results, llocs);
registerNames = generateRegisterMap(program);
AddressSetView addressSetView =
propertyMap.getAddressSetView(Lifespan.toNow(current.getSnap()));
for (AddressRange addressRange : addressSetView) {
TracePropertyMapSpace<String> space =
propertyMap.getPropertyMapSpace(addressRange.getAddressSpace(), false);
Collection<Entry<TraceAddressSnapRange, String>> entries =
space.getEntries(Lifespan.toNow(current.getSnap()), addressRange);
for (Entry<TraceAddressSnapRange, String> entry : entries) {
writeResult(program.getFunctionManager(), results, llocs, entry);
}
}
monitor.setMessage("Results written...exporting to JSON");
gson.toJson(sarif, writer);
monitor.setMessage("JSON completed");
StringWriter w = (StringWriter) baseWriter;
StringBuffer sb = w.getBuffer();
SarifService sarifService = plugin.getSarifService();
SarifMgr.getColumnKeys().put("displayName", true);
currentQueryData = sarifService.readSarif(sb.toString());
}
catch (IOException e) {
Msg.error(this, e.getMessage());
}
}
private Map<String, Map<Integer, String>> generateRegisterMap(Program program) {
Language language = program.getLanguage();
for (Register r : language.getRegisters()) {
Map<Integer, String> sizeMap = registerNames.computeIfAbsent(r.getAddress().toString(),
a -> new HashMap<Integer, String>());
sizeMap.put(r.getBitLength(), r.getName());
if (r.equals(r.getBaseRegister())) {
sizeMap.put(0, r.getName());
}
}
return registerNames;
}
private void writeSarifHeader(Program program, JsonObject sarif, JsonArray results,
JsonArray llocs) {
sarif.addProperty("$schema", SARIF_URL);
sarif.addProperty("version", SARIF_VERSION);
sarif.add("properties", new JsonObject());
JsonArray runs = new JsonArray();
sarif.add("runs", runs);
JsonObject run = new JsonObject();
runs.add(run);
writeToolInfo(program, run);
run.add("results", results);
run.add("logicalLocations", llocs);
}
private void writeToolInfo(Program program, JsonObject run) {
JsonObject tool = new JsonObject();
run.add("tool", tool);
JsonObject driver = new JsonObject();
tool.add("driver", driver);
driver.addProperty("name", "emulator");
driver.addProperty("version", "0.1");
driver.addProperty("informationUri", "https://github.com/NationalSecurityAgency/ghidra");
JsonArray artifacts = new JsonArray();
run.add("artifacts", artifacts);
JsonObject artifact = new JsonObject();
artifacts.add(artifact);
JsonObject location = new JsonObject();
artifact.add("location", location);
location.addProperty("uri", program.getExecutablePath());
JsonObject properties = new JsonObject();
artifact.add("properties", properties);
JsonObject additionalProperties = new JsonObject();
properties.add("additionalProperties", additionalProperties);
additionalProperties.addProperty("imageBase", program.getImageBase().toString());
artifact.addProperty("sourceLanguage", program.getLanguageID().getIdAsString());
JsonObject description = new JsonObject();
artifact.add("description", description);
description.addProperty("text", program.getMetadata().get("Compiler ID"));
}
private void writeResult(FunctionManager fmgr, JsonArray results, JsonArray llocs,
Entry<TraceAddressSnapRange, String> entry) {
try {
SarifWriterTask task;
SarifLogicalLocationWriter writer = new SarifLogicalLocationWriter(entry, fmgr);
Address address = writer.getAddress();
if (address != null) {
taintAddressSet.add(address);
}
WrappedLogicalLocation wll = writer.getLogicalLocation();
String llkey = wll.getLogicalLocation().getFullyQualfiedName();
if (!logicalLocations.containsKey(llkey)) {
wll.setIndex(llIndex++);
logicalLocations.put(llkey, wll);
task = new SarifWriterTask("taint", writer, llocs);
task.monitoredRun(monitor);
}
wll = logicalLocations.get(llkey);
String displayName = generateDisplayName(writer.getKey(), writer.getType());
KTV ktv = new KTV(writer.getKey(), writer.getType(), writer.getValue(), displayName);
SarifKeyValueWriter kvwriter = new SarifKeyValueWriter(ktv, wll);
task = new SarifWriterTask("taint", kvwriter, results);
task.monitoredRun(monitor);
}
catch (IOException e) {
Msg.error(this, e.getMessage());
}
}
private String generateDisplayName(String key, String type) {
String displayName = key;
String searchKey = key.startsWith("ram@") ? key.substring(4) : key;
if (registerNames.containsKey(searchKey)) {
Map<Integer, String> sizeMap = registerNames.get(searchKey);
Integer size = switch (type) {
case "(int64)" -> 64;
case "(int32)" -> 32;
case "(int16)" -> 16;
case "(int8)" -> 8;
default -> 0;
};
String res = sizeMap.get(size);
if (res == null) {
res = sizeMap.get(0);
}
displayName = key.startsWith("ram@") ? "ram@" + res : res;
}
return displayName;
}
private String vn2oper(Varnode vn) {
Address vnAddr = vn.getAddress();
return addr2oper(vnAddr, vn.getSize());
}
private String addr2oper(Address addr, int size) {
AddressSpace space = addr.getAddressSpace();
return "*[%s]:%d 0x%s:%d".formatted(space.getName(), size,
addr.toString(false), addr.getSize() / 8);
}
@Override
public String getQueryName() {
return "emulator";
}
@Override
public GhidraScript getExportScript(ConsoleService console, boolean perFunction) {
return null;
}
@Override
public void buildQuery(List<String> paramList, String enginePath, File indexDBFile,
String indexDirectory) {
//UNNEEDED
}
@Override
public void buildIndex(List<String> paramList, String enginePath, String factsPath,
String indexDirectory) {
//UNNEEDED
}
@Override
protected void writeHeader(PrintWriter writer) {
//UNNEEDED
}
@Override
protected void writeRule(PrintWriter writer, TaintLabel mark, boolean isSource) {
//UNNEEDED
}
@Override
protected void writeGate(PrintWriter writer, TaintLabel mark) {
//UNNEEDED
}
@Override
protected void writeFooter(PrintWriter writer) {
//UNNEEDED
}
}

View File

@@ -0,0 +1,41 @@
/* ###
* 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.debug.taint;
import ghidra.app.plugin.core.debug.taint.EmulatorTaintState.KTV;
import ghidra.program.model.data.ISF.AbstractIsfWriter.Exclude;
import ghidra.program.model.data.ISF.IsfObject;
public class ExtKeyValue implements IsfObject {
String name;
String displayName;
String type;
String value;
String taintLabels;
@Exclude
private int index;
public ExtKeyValue(KTV ktv) {
this.name = ktv.key();
this.displayName = ktv.displayName();
this.type = "Instruction";
this.value = ktv.value();
this.taintLabels = "[" + ktv.value() + "]";
}
}

View File

@@ -0,0 +1,50 @@
/* ###
* 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.debug.taint;
import java.io.IOException;
import ghidra.app.plugin.core.debug.taint.EmulatorTaintState.KTV;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import sarif.export.*;
public class SarifKeyValueWriter extends AbstractExtWriter {
private ExtKeyValue isf;
private WrappedLogicalLocation wll;
public SarifKeyValueWriter(KTV ktv, WrappedLogicalLocation wll)
throws IOException {
super(null);
this.isf = new ExtKeyValue(ktv);
this.wll = wll;
}
@Override
protected void genRoot(TaskMonitor monitor) throws CancelledException, IOException {
genData(monitor);
root.add("structuredObject", objects);
}
private void genData(TaskMonitor monitor) {
ExtLogicalLocation lloc = wll.getLogicalLocation();
SarifObject sarif = new SarifObject(lloc.getDecoratedName(), "VALUE", lloc, getTree(isf),
wll.getAddress(), wll.getIndex());
objects.add(getTree(sarif));
}
}

View File

@@ -0,0 +1,102 @@
/* ###
* 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.debug.taint;
import java.io.IOException;
import java.util.Map.Entry;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressFormatException;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.FunctionManager;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import sarif.export.*;
public class SarifLogicalLocationWriter extends AbstractExtWriter {
private WrappedLogicalLocation lloc;
private String key;
private String type;
private String value;
private Address addr;
public SarifLogicalLocationWriter(Entry<TraceAddressSnapRange, String> entry,
FunctionManager fmgr)
throws IOException {
super(null);
Function f = null;
String location = "UNKNOWN";
Address min = entry.getKey().getX1();
key = min.toString(true);
type = min.getAddressSpace().getName();
value = entry.getValue();
if (value.contains("@")) {
String[] split = value.split("@");
value = split[0];
String[] vSplit = split[1].split(",");
try {
String seq = vSplit[1].substring(3).trim() + ":" + vSplit[2].trim();
addr = min.getAddress(vSplit[1].trim());
f = fmgr.getFunctionContaining(addr);
if (f != null) {
location = f.getName() + ":" + key;
location += "@" + f.getEntryPoint();
location += ":" + seq;
}
}
catch (AddressFormatException e) {
e.printStackTrace();
}
}
ExtLogicalLocation ext = new ExtLogicalLocation(key, f, location, "");
lloc = new WrappedLogicalLocation(ext, addr);
}
@Override
protected void genRoot(TaskMonitor monitor) throws CancelledException, IOException {
genData(monitor);
root.add("logicalLocation", objects);
}
private void genData(TaskMonitor monitor) {
objects.add(getTree(lloc.getLogicalLocation()));
}
public WrappedLogicalLocation getLogicalLocation() {
return lloc;
}
public Address getAddress() {
return addr;
}
public String getKey() {
return key;
}
public String getValue() {
return value;
}
public String getType() {
return type;
}
}

View File

@@ -98,6 +98,11 @@ public enum TaintPcodeArithmetic implements PcodeArithmetic<TaintVec> {
* carries. All others, we assume every byte could be tainted by any other byte in the vector,
* so we union and broadcast.
*/
@Override
public TaintVec unaryOp(PcodeOp op, TaintVec in1) {
return PcodeArithmetic.super.unaryOp(op, in1).withOp(op);
}
@Override
public TaintVec unaryOp(int opcode, int sizeout, int sizein1, TaintVec in1) {
return switch (opcode) {
@@ -128,35 +133,20 @@ public enum TaintPcodeArithmetic implements PcodeArithmetic<TaintVec> {
switch (op.getOpcode()) {
case PcodeOp.INT_XOR, PcodeOp.INT_SUB, PcodeOp.BOOL_XOR -> {
if (Objects.equals(op.getInput(0), op.getInput(1))) {
return fromConst(0, op.getOutput().getSize());
return fromConst(0, op.getOutput().getSize()); // NB: withOp unneeded, as this essentially removes taint
}
}
}
return PcodeArithmetic.super.binaryOp(op, in1, in2);
}
/**
* {@inheritDoc}
*
* <p>
* For bitwise operations, we pair-wise union corresponding elements of the two input taint
* vectors. For integer add and subtract, we do the same, but account for the carry bits
* possibly cascading into bytes of higher significance. For {@link PcodeOp#PIECE}, we perform
* the analog as on concrete state, since the operand sizes are constant. For all others, we
* must consider that every output byte is potentially affected by any or all bytes of both
* input operands. Thus, we union and broadcast.
*/
@Override
public TaintVec binaryOp(int opcode, int sizeout, int sizein1, TaintVec in1,
int sizein2, TaintVec in2) {
return switch (opcode) {
int sizein2 = op.getInput(1).getSize();
int sizeout = op.getOutput().getSize();
return switch (op.getOpcode()) {
case PcodeOp.BOOL_AND, PcodeOp.BOOL_OR, PcodeOp.BOOL_XOR, PcodeOp.INT_AND, //
PcodeOp.INT_OR, PcodeOp.INT_XOR -> {
yield in1.zipUnion(in2);
yield in1.zipUnion(in2).withOp(op);
}
case PcodeOp.INT_ADD, PcodeOp.INT_SUB -> {
TaintVec temp = in1.zipUnion(in2);
yield temp.setCascade(endian.isBigEndian());
yield temp.setCascade(endian.isBigEndian()).withOp(op);
}
case PcodeOp.INT_SLESS, PcodeOp.INT_SLESSEQUAL, //
PcodeOp.INT_LESS, PcodeOp.INT_LESSEQUAL, //
@@ -164,30 +154,42 @@ public enum TaintPcodeArithmetic implements PcodeArithmetic<TaintVec> {
PcodeOp.FLOAT_LESS, PcodeOp.FLOAT_LESSEQUAL, //
PcodeOp.FLOAT_EQUAL, PcodeOp.FLOAT_NOTEQUAL -> {
TaintSet temp = in1.union().union(in2.union());
yield TaintVec.copies(temp, sizeout);
yield TaintVec.copies(temp, sizeout).withOp(op);
}
case PcodeOp.PIECE -> {
TaintVec temp = in1.extended(sizeout, endian.isBigEndian(), false);
temp.setShifted(endian.isBigEndian() ? -sizein2 : sizein2, ShiftMode.UNBOUNDED);
yield temp.set(endian.isBigEndian() ? sizeout - sizein2 : 0, in2);
yield temp.set(endian.isBigEndian() ? sizeout - sizein2 : 0, in2).withOp(op);
}
default -> {
TaintVec temp = in1.zipUnion(in2);
yield temp.setCopies(temp.union());
TaintVec temp = in1.zipUnion(in2).truncated(sizeout, endian.isBigEndian());
yield temp.setCopies(temp.union()).withOp(op);
}
};
}
@Override
public TaintVec binaryOp(int opcode, int sizeout, int sizein1, TaintVec in1,
int sizein2, TaintVec in2) {
throw new RuntimeException("Not supported");
}
/**
* {@inheritDoc}
*
* <p>
* Here we handle indirect taint for indirect writes
*/
@Override
public TaintVec modBeforeStore(PcodeOp op, AddressSpace space, TaintVec inOffset,
TaintVec inValue) {
return inValue.tagIndirectWrite(inOffset).withOp(op);
}
@Override
public TaintVec modBeforeStore(int sizeinOffset, AddressSpace space, TaintVec inOffset,
int sizeinValue, TaintVec inValue) {
return inValue.tagIndirectWrite(inOffset);
throw new RuntimeException("Not supported");
}
/**
@@ -196,10 +198,16 @@ public enum TaintPcodeArithmetic implements PcodeArithmetic<TaintVec> {
* <p>
* Here we handle indirect taint for indirect reads
*/
@Override
public TaintVec modAfterLoad(PcodeOp op, AddressSpace space, TaintVec inOffset,
TaintVec inValue) {
return inValue.tagIndirectRead(inOffset).withOp(op);
}
@Override
public TaintVec modAfterLoad(int sizeinOffset, AddressSpace space, TaintVec inOffset,
int sizeinValue, TaintVec inValue) {
return inValue.tagIndirectRead(inOffset);
throw new RuntimeException("Not supported");
}
/**

View File

@@ -19,7 +19,11 @@ import java.util.Set;
import org.apache.commons.lang3.tuple.Pair;
import ghidra.pcode.emu.DefaultPcodeThread.PcodeThreadExecutor;
import ghidra.pcode.exec.AnnotatedPcodeUseropLibrary;
import ghidra.pcode.exec.PcodeExecutor;
import ghidra.program.model.address.Address;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.taint.model.*;
import ghidra.trace.model.time.schedule.TraceSchedule;
@@ -59,8 +63,15 @@ public class TaintPcodeUseropLibrary extends AnnotatedPcodeUseropLibrary<Pair<by
* @return the same value, with the generated taint unioned in
*/
@PcodeUserop
public Pair<byte[], TaintVec> taint_var(Pair<byte[], TaintVec> in) {
return Pair.of(in.getLeft(), in.getRight().eachUnion(nextVar()));
public Pair<byte[], TaintVec> taint_var(Pair<byte[], TaintVec> in, @OpOp PcodeOp op,
@OpExecutor PcodeExecutor executor) {
if (executor instanceof PcodeThreadExecutor te) {
Address counter = te.getThread().getCounter();
op = new PcodeOp(counter, op.getSeqnum().getTime(), op.getOpcode(), op.getInputs(),
op.getOutput());
}
return Pair.of(in.getLeft(), in.getRight().eachUnion(nextVar()).withOp(op));
}
/**
@@ -73,12 +84,22 @@ public class TaintPcodeUseropLibrary extends AnnotatedPcodeUseropLibrary<Pair<by
* [arr_0_0][arr_0_1]...[arr_0_7].
*
* @param in the input value
* @param op the taint source
* @param executor the current executor
* @return the same value, with the generated taint unioned in
*/
@PcodeUserop
public Pair<byte[], TaintVec> taint_arr(Pair<byte[], TaintVec> in) {
public Pair<byte[], TaintVec> taint_arr(Pair<byte[], TaintVec> in, @OpOp PcodeOp op,
@OpExecutor PcodeExecutor executor) {
if (executor instanceof PcodeThreadExecutor te) {
Address counter = te.getThread().getCounter();
op = new PcodeOp(counter, op.getSeqnum().getTime(), op.getOpcode(), op.getInputs(),
op.getOutput());
}
TaintVec taint = in.getRight();
taint = taint.zipUnion(TaintVec.array(nextArrName(), 0, taint.length));
taint = taint.zipUnion(TaintVec.array(nextArrName(), 0, taint.length)).withOp(op);
return Pair.of(in.getLeft(), taint);
}
}

View File

@@ -78,6 +78,10 @@ public class TaintPieceHandler extends AbstractPropertyBasedPieceHandler<byte[],
@Override
protected void decodeFrom(PcodeExecutorStatePiece<byte[], TaintVec> piece, AddressSetView limit,
AddressRange range, String propertyValue) {
if (propertyValue.contains("@")) {
// FIXME: WOuld be nice to actually decode the p-code op
propertyValue = propertyValue.substring(0, propertyValue.indexOf("@"));
}
TaintVec vec = TaintVec.copies(TaintSet.parse(propertyValue), (int) range.getLength());
if (limit.contains(range.getMaxAddress(), range.getMaxAddress())) {
piece.setVarInternal(range.getAddressSpace(), range.getMinAddress().getOffset(),
@@ -115,7 +119,11 @@ public class TaintPieceHandler extends AbstractPropertyBasedPieceHandler<byte[],
property.clear(new AddressRangeImpl(address, address));
}
else {
property.put(address, s.toString());
String desc = s.toString();
if (value.getOriginatingOp() != null) {
desc += "@" + value.getOriginatingOp().getSeqnum().toString();
}
property.put(address, desc);
}
}
}

View File

@@ -21,6 +21,7 @@ import java.util.Map.Entry;
import ghidra.pcode.exec.PcodeStateCallbacks;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Register;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.taint.model.TaintSet;
import ghidra.taint.model.TaintVec;
import ghidra.util.MathUtilities;
@@ -39,6 +40,7 @@ public class TaintSpace {
protected final TaintPcodeExecutorStatePiece piece;
// TODO: There must be a better way. Similar to SemisparseByteArray?
protected final NavigableMap<Long, TaintSet> taints = new TreeMap<>(Long::compareUnsigned);
protected final NavigableMap<Long, PcodeOp> ops = new TreeMap<>(Long::compareUnsigned);
public TaintSpace(AddressSpace space, TaintPcodeExecutorStatePiece piece) {
this.space = space;
@@ -59,6 +61,7 @@ public class TaintSpace {
* @param cb callbacks to receive emulation events
*/
public void set(long offset, TaintVec val, PcodeStateCallbacks cb) {
ops.put(offset, val.getOriginatingOp());
for (int i = 0; i < val.length; i++) {
TaintSet s = val.get(i);
/*
@@ -148,7 +151,8 @@ public class TaintSpace {
while (taints.get(end) != null) {
end++;
}
TaintVec vec = new TaintVec(MathUtilities.unsignedMin(1024, end - offset));
PcodeOp pcodeOp = ops.get(offset); // Needed here to generate the TaintVec
TaintVec vec = new TaintVec(MathUtilities.unsignedMin(1024, end - offset), pcodeOp);
getInto(offset, vec, PcodeStateCallbacks.NONE);
return Map.entry(offset, vec);
}

View File

@@ -20,6 +20,8 @@ import java.util.function.BinaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import ghidra.program.model.pcode.PcodeOp;
/**
* A mutable, but fixed-size, buffer of taint sets
*
@@ -33,8 +35,15 @@ import java.util.stream.Stream;
*/
public class TaintVec {
public static TaintVec of(TaintSet... taints) {
return new TaintVec(taints);
/**
* Create a vector of taint sets
*
* @param op the originating p-code op
* @param taints the taint set
* @return the new vector
*/
public static TaintVec of(PcodeOp op, TaintSet... taints) {
return new TaintVec(taints, op);
}
/**
@@ -79,11 +88,13 @@ public class TaintVec {
private TaintSet[] sets;
private List<TaintSet> setsView;
public final int length;
private final PcodeOp originatingOp;
private TaintVec(TaintSet[] sets) {
private TaintVec(TaintSet[] sets, PcodeOp op) {
this.sets = sets;
this.setsView = Collections.unmodifiableList(Arrays.asList(sets));
this.length = sets.length;
this.originatingOp = op;
}
/**
@@ -92,7 +103,17 @@ public class TaintVec {
* @param length the length
*/
public TaintVec(int length) {
this(new TaintSet[length]);
this(new TaintSet[length], null);
}
/**
* Create a new uninitialized taint vector of the given length
*
* @param length the length
* @param op the originating op
*/
public TaintVec(int length, PcodeOp op) {
this(new TaintSet[length], op);
}
@Override
@@ -573,4 +594,22 @@ public class TaintVec {
}
return vec;
}
/**
* @return the originating op
*/
public PcodeOp getOriginatingOp() {
return originatingOp;
}
/**
* Supply the originating op
*
* @param the originating op
* @return the tagged TaintVec
*
*/
public TaintVec withOp(PcodeOp op) {
return new TaintVec(sets, op);
}
}

View File

@@ -29,6 +29,7 @@ public class TaintLabel {
private HighFunction hfun;
private HighVariable hvar;
private Varnode vnode;
private Address vnAddr;
private boolean active;
private String label;
private boolean isGlobal = false;
@@ -40,7 +41,7 @@ public class TaintLabel {
private int size = 0;
public TaintLabel(MarkType mtype, ClangToken token) throws PcodeException {
HighVariable highVar = token.getHighVariable();
if (highVar == null) {
hfun = token.getClangFunction().getHighFunction();
@@ -54,22 +55,24 @@ public class TaintLabel {
}
this.vnode = token.getVarnode();
if (vnode != null) { // The user pointed at a particular usage, not just the vardecl
String fn = token instanceof ClangFuncNameToken ftoken ? ftoken.getText()
: hfun.getFunction().getName();
PcodeOp pcodeOp = token.getPcodeOp();
Address target =
pcodeOp == null ? hfun.getFunction().getEntryPoint() : pcodeOp.getSeqnum().getTarget();
if (vnode == null && pcodeOp != null) {
vnode = pcodeOp.getOutput();
}
if (vnode != null) {
vnAddr = vnode.getAddress();
HighVariable high = vnode.getHigh();
if (high instanceof HighLocal) {
highVar = hfun.splitOutMergeGroup(high, vnode);
}
}
String fn = token instanceof ClangFuncNameToken ftoken ? ftoken.getText()
: hfun.getFunction().getName();
PcodeOp pcodeOp = token.getPcodeOp();
Address target = pcodeOp == null ? hfun.getFunction().getEntryPoint() : pcodeOp.getSeqnum().getTarget();
if (vnode == null && pcodeOp != null) {
vnode = pcodeOp.getOutput();
highVar = vnode.getHigh();
}
this.mtype = mtype;
this.token = token;
this.fname = fn;
@@ -85,6 +88,41 @@ public class TaintLabel {
this.label = mtype.toString();
}
public TaintLabel(MarkType mtype, ClangToken token, HighVariable hvar,
String fn, Address target) {
this.mtype = mtype;
this.token = token;
if (token != null) {
this.clangLine = token.getLineParent();
}
this.hvar = hvar;
if (hvar == null) {
if (token != null) {
hfun = token.getClangFunction().getHighFunction();
}
}
else {
size = hvar.getSize();
hfun = hvar.getHighFunction();
HighSymbol symbol = hvar.getSymbol();
if (symbol != null) {
isGlobal = symbol.isGlobal();
}
}
this.fname = fn;
this.addr = target;
active = true;
// Initial label is one of SOURCE, SINK, or GATE
label = mtype.toString();
}
public TaintLabel(MarkType mtype, String fn, Address target, Address refAddress) {
this(mtype, null, null, fn, target);
vnAddr = refAddress;
}
public ClangLine getClangLine() {
return this.clangLine;
}
@@ -155,7 +193,7 @@ public class TaintLabel {
public void activate() {
active = true;
}
public int getSize() {
return size;
}
@@ -231,12 +269,12 @@ public class TaintLabel {
}
return true;
}
public Address getVarnodeAddress() {
if (vnode != null) {
return vnode.getAddress();
}
return null;
return vnAddr;
}
public Varnode getVnode() {
return vnode;
}
}

View File

@@ -220,6 +220,9 @@ public class SarifTaintResultHandler extends SarifResultHandler {
for (int row : selected) {
Map<String, Object> r = tableProvider.getRow(row);
String kind = (String) r.get("kind");
if (kind == null) {
continue;
}
if (kind.equals("member") || kind.startsWith("path ")) {
getTaintedInstruction(map, r);
}

View File

@@ -268,7 +268,7 @@ public class SarifUtils {
}
addr = subparts[0];
}
return program.getAddressFactory().getAddress(addr);
return addr == null ? null : program.getAddressFactory().getAddress(addr);
}
public static List<Address> extractFQNameAddrPair(Program program, String fqname) {

View File

@@ -31,7 +31,7 @@ public class ExtLogicalLocation implements IsfObject {
this.kind = "variable";
this.decoratedName = op;
this.fullyQualifiedName = location + ":" + name;
this.uri = function.getProgram().getExecutablePath();
this.uri = function == null ? "UNKNOWN" : function.getProgram().getExecutablePath();
}
public String getName() {

View File

@@ -30,6 +30,7 @@ import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.PrototypeModel;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
import ghidra.program.model.symbol.*;
@@ -167,7 +168,7 @@ public interface EmuSyscallLibrary<T> extends PcodeUseropLibrary<T> {
@Override
public void execute(PcodeExecutor<T> executor, PcodeUseropLibrary<T> library,
Varnode outVar, List<Varnode> inVars) {
PcodeOp op, Varnode outVar, List<Varnode> inVars) {
syslib.syscall(executor, library);
}

View File

@@ -31,6 +31,7 @@ import ghidra.pcode.memstate.MemoryBank;
import ghidra.pcode.memstate.MemoryState;
import ghidra.program.model.address.Address;
import ghidra.program.model.lang.*;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
import ghidra.util.Msg;
@@ -121,7 +122,7 @@ public class ModifiedPcodeThread<T> extends DefaultPcodeThread<T> {
@Override
public void execute(PcodeExecutor<T> executor, PcodeUseropLibrary<T> library,
Varnode outVar, List<Varnode> inVars) {
PcodeOp op, Varnode outVar, List<Varnode> inVars) {
behavior.evaluate(emulate, outVar, inVars.toArray(Varnode[]::new));
}

View File

@@ -67,7 +67,8 @@ public abstract class AuxPcodeEmulator<U> extends AbstractPcodeMachine<Pair<byte
@Override
protected PcodeUseropLibrary<Pair<byte[], U>> createThreadStubLibrary() {
return getPartsFactory().createLocalUseropStub(this);
return super.createThreadStubLibrary()
.compose(getPartsFactory().createLocalUseropStub(this));
}
@Override

View File

@@ -102,7 +102,7 @@ public class JitDataFlowUseropLibrary implements PcodeUseropLibrary<JitVal> {
@Override
public void execute(PcodeExecutor<JitVal> executor, PcodeUseropLibrary<JitVal> library,
Varnode outVar, List<Varnode> inVars) {
PcodeOp op, Varnode outVar, List<Varnode> inVars) {
throw new AssertionError();
}

View File

@@ -79,7 +79,7 @@ public class DecoderUseropLibrary extends AnnotatedPcodeUseropLibrary<Object> {
@Override
public void execute(PcodeExecutor<Object> executor, PcodeUseropLibrary<Object> library,
Varnode outVar, List<Varnode> inVars) {
PcodeOp op, Varnode outVar, List<Varnode> inVars) {
throw new AssertionError();
}

View File

@@ -29,6 +29,7 @@ import org.apache.commons.lang3.reflect.TypeUtils;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
import utilities.util.AnnotationUtilities;
@@ -92,6 +93,17 @@ public abstract class AnnotatedPcodeUseropLibrary<T> implements PcodeUseropLibra
void setPos(AnnotatedPcodeUseropDefinition<?> opdef, int pos) {
opdef.posOut = pos;
}
},
OP(OpOp.class, PcodeOp.class) {
@Override
int getPos(AnnotatedPcodeUseropDefinition<?> opdef) {
return opdef.posOp;
}
@Override
void setPos(AnnotatedPcodeUseropDefinition<?> opdef, int pos) {
opdef.posOp = pos;
}
};
static boolean processParameter(AnnotatedPcodeUseropDefinition<?> opdef, Type declClsOpType,
@@ -243,6 +255,7 @@ public abstract class AnnotatedPcodeUseropLibrary<T> implements PcodeUseropLibra
private int posState = -1;
private int posLib = -1;
private int posOut = -1;
private int posOp = -1;
public AnnotatedPcodeUseropDefinition(AnnotatedPcodeUseropLibrary<T> library, Type opType,
Lookup lookup, Method method, PcodeUserop annot) {
@@ -290,7 +303,7 @@ public abstract class AnnotatedPcodeUseropLibrary<T> implements PcodeUseropLibra
@Override
public void execute(PcodeExecutor<T> executor, PcodeUseropLibrary<T> library,
Varnode outVar, List<Varnode> inVars) {
PcodeOp op, Varnode outVar, List<Varnode> inVars) {
validateInputs(inVars);
PcodeExecutorStatePiece<T, T> state = executor.getState();
@@ -308,6 +321,9 @@ public abstract class AnnotatedPcodeUseropLibrary<T> implements PcodeUseropLibra
if (posOut != -1) {
args.set(posOut, outVar);
}
if (posOp != -1) {
args.set(posOp, op);
}
placeInputs(executor, args, inVars);
try {
@@ -792,6 +808,17 @@ public abstract class AnnotatedPcodeUseropLibrary<T> implements PcodeUseropLibra
public @interface OpOutput {
}
/**
* An annotation to receive the CALLOTHER p-code op into a parameter
*
* <p>
* The annotated parameter must have type {@link PcodeOp}).
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface OpOp {
}
protected Map<String, PcodeUseropDefinition<T>> ops = new HashMap<>();
private Map<String, PcodeUseropDefinition<T>> unmodifiableOps =
Collections.unmodifiableMap(ops);

View File

@@ -131,8 +131,25 @@ public interface PcodeUseropLibrary<T> {
* @param inVars the input varnodes as ordered in the source.
* @see AnnotatedPcodeUseropLibrary.AnnotatedPcodeUseropDefinition
*/
void execute(PcodeExecutor<T> executor, PcodeUseropLibrary<T> library, Varnode outVar,
List<Varnode> inVars);
default void execute(PcodeExecutor<T> executor, PcodeUseropLibrary<T> library,
Varnode outVar, List<Varnode> inVars) {
execute(executor, library, null, outVar, inVars);
}
/**
* Invoke/execute the userop.
*
* @param executor the executor invoking this userop.
* @param library the complete library for this execution. Note the library may have been
* composed from more than the one defining this userop.
* @param op the CALLOTHER p-code op
* @param outVar if invoked as an rval, the destination varnode for the userop's output.
* Otherwise, {@code null}.
* @param inVars the input varnodes as ordered in the source.
* @see AnnotatedPcodeUseropLibrary.AnnotatedPcodeUseropDefinition
*/
void execute(PcodeExecutor<T> executor, PcodeUseropLibrary<T> library, PcodeOp op,
Varnode outVar, List<Varnode> inVars);
/**
* Invoke/execute the raw userop.
@@ -147,7 +164,7 @@ public interface PcodeUseropLibrary<T> {
* @param op the {@link PcodeOp#CALLOTHER} op
*/
default void execute(PcodeExecutor<T> executor, PcodeUseropLibrary<T> library, PcodeOp op) {
execute(executor, library, op.getOutput(),
execute(executor, library, op, op.getOutput(),
Arrays.asList(op.getInputs()).subList(1, op.getNumInputs()));
}

View File

@@ -20,6 +20,7 @@ import java.util.*;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
/**
@@ -170,7 +171,7 @@ public class SleighPcodeUseropDefinition<T> implements PcodeUseropDefinition<T>
@Override
public void execute(PcodeExecutor<T> executor, PcodeUseropLibrary<T> library,
Varnode outArg, List<Varnode> inArgs) {
PcodeOp op, Varnode outArg, List<Varnode> inArgs) {
PcodeProgram program = programFor(outArg, inArgs, library);
executor.execute(program, library);
}

View File

@@ -1149,7 +1149,7 @@ emulator:</p>
<ul>
<li>For memory state: <a
href="../../../Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/TaintFieldFactory.java">TaintFieldFactory</a></li>
<li>For regsiter state: <a
<li>For register state: <a
href="../../../Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/TaintDebuggerRegisterColumnFactory.java">TaintDebuggerRegisterColumnFactory</a></li>
</ul>
<p>Anything more than that would require completely custom providers,

View File

@@ -889,6 +889,6 @@ Since string-based serialization may be a common case, we may eventually provide
For now, we refer you to the implementations for the Taint-augmented emulator:
* For memory state: [TaintFieldFactory](../../../Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/TaintFieldFactory.java)
* For regsiter state: [TaintDebuggerRegisterColumnFactory](../../../Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/TaintDebuggerRegisterColumnFactory.java)
* For register state: [TaintDebuggerRegisterColumnFactory](../../../Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/TaintDebuggerRegisterColumnFactory.java)
Anything more than that would require completely custom providers, plugins, etc.