Merge remote-tracking branch 'origin/patch'

This commit is contained in:
Ryan Kurtz
2025-12-16 13:38:26 -05:00
62 changed files with 10695 additions and 1 deletions

View File

@@ -0,0 +1,4 @@
MODULE FILE LICENSE: lib/lisa-analyses-0.1.jar MIT
MODULE FILE LICENSE: lib/lisa-program-0.1.jar MIT
MODULE FILE LICENSE: lib/lisa-sdk-0.1.jar MIT
MODULE FILE LICENSE: lib/joda-time-2.14.0.jar Apache License 2.0

View File

@@ -0,0 +1,57 @@
/* ###
* 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.
*/
apply from: "$rootProject.projectDir/gradle/distributableGhidraExtension.gradle"
//apply from: "$rootProject.projectDir/gradle/distributableGhidraModule.gradle"
apply from: "$rootProject.projectDir/gradle/javaProject.gradle"
apply from: "$rootProject.projectDir/gradle/helpProject.gradle"
apply from: "$rootProject.projectDir/gradle/javaTestProject.gradle"
apply plugin: 'eclipse'
eclipse.project.name = 'Xtra Lisa'
dependencies {
api project(':Base')
api project(':Decompiler')
api project(':DecompilerDependent')
api("io.github.lisa-analyzer:lisa-analyses:0.1") {
exclude group: 'org.apache.commons'
exclude group: 'org.apache.logging.log4j'
exclude group: 'com.fasterxml.jackson.core'
exclude group: 'org.graphstream'
exclude group: 'org.reflections'
}
api("io.github.lisa-analyzer:lisa-program:0.1") {
exclude group: 'org.apache.commons'
exclude group: 'org.apache.logging.log4j'
exclude group: 'com.fasterxml.jackson.core'
exclude group: 'org.graphstream'
exclude group: 'org.reflections'
}
api("io.github.lisa-analyzer:lisa-sdk:0.1") {
exclude group: 'org.apache.commons'
exclude group: 'org.apache.logging.log4j'
exclude group: 'com.fasterxml.jackson.core'
exclude group: 'org.graphstream'
exclude group: 'org.reflections'
}
// Joda is needed at run time
api "joda-time:joda-time:2.14.0"
}

View File

@@ -0,0 +1,8 @@
##VERSION: 2.0
##MODULE IP: Apache License 2.0
##MODULE IP: MIT
Module.manifest||GHIDRA||||END|
extension.properties||GHIDRA||||END|
src/main/help/help/TOC_Source.xml||GHIDRA||||END|
src/main/help/help/topics/LisaPlugin/LisaPlugin.html||GHIDRA||||END|
src/main/java/ghidra/lisa/pcode/analyses/README.md||GHIDRA||||END|

View File

@@ -0,0 +1,5 @@
name=Lisa
description=Abstract interpretation engine based on the Universita Ca Foscari's Library for Static Analysis (LiSA)
author=Ghidra Team
createdOn=9/9/2025
version=@extversion@

View File

@@ -0,0 +1,116 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//Template for running analyses using Lisa (does very little, as is)
//@category PCode
import java.util.*;
import ghidra.app.script.GhidraScript;
import ghidra.lisa.pcode.PcodeFrontend;
import ghidra.lisa.pcode.analyses.PcodeByteBasedConstantPropagation;
import ghidra.lisa.pcode.locations.PcodeLocation;
import ghidra.util.Msg;
import it.unive.lisa.*;
import it.unive.lisa.analysis.*;
import it.unive.lisa.analysis.nonrelational.value.ValueEnvironment;
import it.unive.lisa.interprocedural.InterproceduralAnalysis;
import it.unive.lisa.program.Program;
import it.unive.lisa.program.cfg.CFG;
import it.unive.lisa.program.cfg.statement.Statement;
import it.unive.lisa.symbolic.value.Identifier;
import it.unive.lisa.util.representation.StructuredRepresentation;
public class LisaLaunchScript extends GhidraScript {
@Override
public void run() throws Exception {
if (isRunningHeadless()) {
popup("Script is not running in GUI");
return;
}
PcodeFrontend frontend = new PcodeFrontend();
Program p = frontend.doWork(currentProgram.getListing(), currentAddress);
DefaultConfiguration conf = new DefaultConfiguration();
conf.serializeResults = true;
conf.abstractState = DefaultConfiguration.simpleState(
DefaultConfiguration.defaultHeapDomain(),
//new DefiniteDataflowDomain<>(new ConstantPropagation()),
new ValueEnvironment<>(
new PcodeByteBasedConstantPropagation(currentProgram.getLanguage())),
DefaultConfiguration.defaultTypeDomain());
LiSA lisa = new LiSA(conf);
LiSAReport report = lisa.run(p);
InterproceduralAnalysis<?> interproceduralAnalysis =
report.getConfiguration().interproceduralAnalysis;
Collection<CFG> ep = p.getEntryPoints();
for (CFG cfg : ep) {
Collection<?> results = interproceduralAnalysis.getAnalysisResultsOf(cfg);
Iterator<?> iterator = results.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
if (next instanceof AnalyzedCFG<?> acfg) {
processCFG(cfg, acfg);
}
}
}
}
private void processCFG(CFG cfg, AnalyzedCFG<?> acfg) {
if (!cfg.getNodes().isEmpty()) {
for (Statement st : cfg.getNodes()) {
AnalysisState<?> s = acfg.getAnalysisStateAfter(st);
AbstractState<?> abs = s.getState();
if (abs instanceof SimpleAbstractState sas) {
processState(sas, st);
}
}
}
}
@SuppressWarnings("rawtypes")
private void processState(SimpleAbstractState sas, Statement st) {
ValueEnvironment<?> valueState =
(ValueEnvironment<?>) sas.getValueState();
//DefiniteDataflowDomain valueState = (DefiniteDataflowDomain) sas.getValueState();
Map<Identifier, ?> function = valueState.function;
if (function != null) {
for (Object key : function.keySet()) {
Object val = valueState.function.get(key);
exampleAnalysis(st, key, val);
}
}
}
private void exampleAnalysis(Statement st, Object key, Object val) {
if (val instanceof PcodeByteBasedConstantPropagation icp) {
StructuredRepresentation representation =
icp.representation();
String rep = representation.toString();
if (!rep.contains("TOP")) {
PcodeLocation loc =
(PcodeLocation) st.getLocation();
Msg.info(this, loc.getCodeLocation() +
" ==> " + key + ":" + representation);
}
}
}
}

View File

@@ -0,0 +1,457 @@
/* ###
* 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.
*/
//Uses overriding references and the LiSA constant propagator to resolve system calls
//@category Analysis
import java.io.*;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.Predicate;
import generic.jar.ResourceFile;
import ghidra.app.cmd.function.ApplyFunctionDataTypesCmd;
import ghidra.app.cmd.memory.AddUninitializedMemoryBlockCmd;
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
import ghidra.app.script.GhidraScript;
import ghidra.app.services.DataTypeManagerService;
import ghidra.app.util.opinion.ElfLoader;
import ghidra.framework.Application;
import ghidra.lisa.pcode.PcodeFrontend;
import ghidra.lisa.pcode.analyses.PcodeByteBasedConstantPropagation;
import ghidra.lisa.pcode.locations.PcodeLocation;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.SpaceNames;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.symbol.*;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import it.unive.lisa.*;
import it.unive.lisa.analysis.*;
import it.unive.lisa.analysis.nonrelational.value.ValueEnvironment;
import it.unive.lisa.interprocedural.InterproceduralAnalysis;
import it.unive.lisa.program.cfg.CFG;
import it.unive.lisa.program.cfg.statement.Statement;
import it.unive.lisa.symbolic.value.Identifier;
/**
* This script will resolve system calls for x86 or x64 Linux binaries.
* It assumes that in the x64 case, the syscall native instruction is used to make system calls,
* and in the x86 case, system calls are made via an indirect call to GS:[0x10].
* It should be straightforward to modify this script for other cases.
*/
public class Lisa_ResolveX86orX64LinuxSyscallsScript extends GhidraScript {
//disassembles to "CALL dword ptr GS:[0x10]"
private static final byte[] x86_bytes = { 0x65, -1, 0x15, 0x10, 0x00, 0x00, 0x00 };
private static final String X86 = "x86";
private static final String SYSCALL_SPACE_NAME = "syscall";
private static final int SYSCALL_SPACE_LENGTH = 0x10000;
//this is the name of the userop (aka CALLOTHER) in the pcode translation of the
//native "syscall" instruction
private static final String SYSCALL_X64_CALLOTHER = "syscall";
//a set of names of all syscalls that do not return
private static final Set<String> noreturnSyscalls = Set.of("exit", "exit_group");
//tests whether an instruction is making a system call
private Predicate<Instruction> tester;
//register holding the syscall number
private String syscallRegister;
//datatype archive containing signature of system calls
private String datatypeArchiveName;
//file containing map from syscall numbers to syscall names
//note that different architectures can have different system call numbers, even
//if they're both Linux...
private String syscallFileName;
//the type of overriding reference to apply
private RefType overrideType;
//the calling convention to use for system calls (must be defined in the appropriate .cspec file)
private String callingConvention;
private InterproceduralAnalysis<?> ipa;
private PcodeFrontend frontend;
private LiSA lisa;
private Map<Address, CFG> targets = new HashMap<>();
@Override
protected void run() throws Exception {
if (!(currentProgram.getExecutableFormat().equals(ElfLoader.ELF_NAME) &&
currentProgram.getLanguage().getProcessor().toString().equals(X86))) {
popup("This script is intended for x86 or x64 Linux files");
return;
}
//determine whether the executable is 32 or 64 bit and set fields appropriately
int size = currentProgram.getLanguage().getLanguageDescription().getSize();
if (size == 64) {
tester = Lisa_ResolveX86orX64LinuxSyscallsScript::checkX64Instruction;
syscallRegister = "RAX";
datatypeArchiveName = "generic_clib_64";
syscallFileName = "x64_linux_syscall_numbers";
overrideType = RefType.CALLOTHER_OVERRIDE_CALL;
callingConvention = "syscall";
}
else {
tester = Lisa_ResolveX86orX64LinuxSyscallsScript::checkX86Instruction;
syscallRegister = "EAX";
datatypeArchiveName = "generic_clib";
syscallFileName = "x86_linux_syscall_numbers";
overrideType = RefType.CALL_OVERRIDE_UNCONDITIONAL;
callingConvention = "syscall";
}
//get the space where the system calls live.
//If it doesn't exist, create it.
AddressSpace syscallSpace =
currentProgram.getAddressFactory().getAddressSpace(SYSCALL_SPACE_NAME);
if (syscallSpace == null) {
//don't muck with address spaces if you don't have exclusive access to the program.
if (!currentProgram.hasExclusiveAccess()) {
popup("Must have exclusive access to " + currentProgram.getName() +
" to run this script");
return;
}
Address startAddr = currentProgram.getAddressFactory()
.getAddressSpace(SpaceNames.OTHER_SPACE_NAME)
.getAddress(0x0L);
AddUninitializedMemoryBlockCmd cmd = new AddUninitializedMemoryBlockCmd(
SYSCALL_SPACE_NAME, null, this.getClass().getName(), startAddr,
SYSCALL_SPACE_LENGTH, true, true, true, false, true);
if (!cmd.applyTo(currentProgram)) {
popup("Failed to create " + SYSCALL_SPACE_NAME);
return;
}
syscallSpace = currentProgram.getAddressFactory().getAddressSpace(SYSCALL_SPACE_NAME);
}
else {
printf("AddressSpace %s found, continuing...\n", SYSCALL_SPACE_NAME);
}
//get all of the functions that contain system calls
//note that this will not find system call instructions that are not in defined functions
Map<Function, Set<Address>> funcsToCalls = getSyscallsInFunctions(currentProgram, monitor);
if (funcsToCalls.isEmpty()) {
popup("No system calls found (within defined functions)");
return;
}
//get the system call number at each callsite of a system call.
//note that this is not guaranteed to succeed at a given system call call site -
//it might be hard (or impossible) to determine a specific constant
Map<Address, Long> addressesToSyscalls =
resolveConstants(funcsToCalls, currentProgram, monitor);
if (addressesToSyscalls.isEmpty()) {
popup("Couldn't resolve any syscall constants");
return;
}
//get the map from system call numbers to system call names
//you might have to create this yourself!
Map<Long, String> syscallNumbersToNames = getSyscallNumberMap();
//at each system call call site where a constant could be determined, create
//the system call (if not already created), then add the appropriate overriding reference
//use syscallNumbersToNames to name the created functions
//if there's not a name corresponding to the constant use a default
for (Entry<Address, Long> entry : addressesToSyscalls.entrySet()) {
Address callSite = entry.getKey();
Long offset = entry.getValue();
printerr(callSite + ":" + Long.toHexString(offset));
Address callTarget = syscallSpace.getAddress(offset);
Function callee = currentProgram.getFunctionManager().getFunctionAt(callTarget);
if (callee == null) {
String funcName = "syscall_" + String.format("%08X", offset);
if (syscallNumbersToNames.get(offset) != null) {
funcName = syscallNumbersToNames.get(offset);
}
callee = createFunction(callTarget, funcName);
if (callee == null) {
continue;
}
callee.setCallingConvention(callingConvention);
//check if the function name is one of the non-returning syscalls
if (noreturnSyscalls.contains(funcName)) {
callee.setNoReturn(true);
}
}
Reference ref = currentProgram.getReferenceManager()
.addMemoryReference(callSite, callTarget, overrideType, SourceType.USER_DEFINED,
Reference.MNEMONIC);
//overriding references must be primary to be active
currentProgram.getReferenceManager().setPrimary(ref, true);
}
//finally, open the appropriate data type archive and apply its function data types
//to the new system call space, so that the system calls have the correct signatures
AutoAnalysisManager mgr = AutoAnalysisManager.getAnalysisManager(currentProgram);
DataTypeManagerService service = mgr.getDataTypeManagerService();
List<DataTypeManager> dataTypeManagers = new ArrayList<>();
dataTypeManagers.add(service.openDataTypeArchive(datatypeArchiveName));
dataTypeManagers.add(currentProgram.getDataTypeManager());
ApplyFunctionDataTypesCmd cmd = new ApplyFunctionDataTypesCmd(dataTypeManagers,
new AddressSet(syscallSpace.getMinAddress(), syscallSpace.getMaxAddress()),
SourceType.USER_DEFINED, false, false);
cmd.applyTo(currentProgram);
}
private Map<Long, String> getSyscallNumberMap() {
Map<Long, String> syscallMap = new HashMap<>();
ResourceFile rFile = Application.findDataFileInAnyModule(syscallFileName);
if (rFile == null) {
popup("Error opening syscall number file, using default names");
return syscallMap;
}
try (FileReader fReader = new FileReader(rFile.getFile(false));
BufferedReader bReader = new BufferedReader(fReader)) {
String line = null;
while ((line = bReader.readLine()) != null) {
//lines starting with # are comments
if (!line.startsWith("#")) {
String[] parts = line.trim().split(" ");
Long number = Long.parseLong(parts[0]);
syscallMap.put(number, parts[1]);
}
}
}
catch (IOException e) {
Msg.showError(this, null, "Error reading syscall map file", e.getMessage(), e);
}
return syscallMap;
}
/**
* Scans through all of the functions defined in {@code program} and returns
* a map which takes a function to the set of address in its body which contain
* system calls
* @param program program containing functions
* @param tMonitor monitor
* @return map function -> addresses in function containing syscalls
* @throws CancelledException if the user cancels
*/
private Map<Function, Set<Address>> getSyscallsInFunctions(Program program,
TaskMonitor tMonitor) throws CancelledException {
Map<Function, Set<Address>> funcsToCalls = new HashMap<>();
for (Function func : program.getFunctionManager().getFunctionsNoStubs(true)) {
tMonitor.checkCancelled();
for (Instruction inst : program.getListing().getInstructions(func.getBody(), true)) {
if (tester.test(inst)) {
Set<Address> callSites = funcsToCalls.get(func);
if (callSites == null) {
callSites = new HashSet<>();
funcsToCalls.put(func, callSites);
}
callSites.add(inst.getAddress());
}
}
}
return funcsToCalls;
}
/**
* Uses the LiSA constant propagator to attempt to determine the constant value in
* the syscall register at each system call instruction
*
* @param funcsToCalls map from functions containing syscalls to address in each function of
* the system call
* @param program containing the functions
* @return map from addresses of system calls to system call numbers
* @throws CancelledException if the user cancels
*/
private Map<Address, Long> resolveConstants(Map<Function, Set<Address>> funcsToCalls,
Program program, TaskMonitor tMonitor) throws CancelledException {
initLisa();
Set<CFG> cfgs = new HashSet<>();
for (Function func : funcsToCalls.keySet()) {
CFG cfg = frontend.visitFunction(func, func.getEntryPoint());
cfgs.add(cfg);
}
it.unive.lisa.program.Program p = frontend.getProgram();
Collection<CFG> baseline = p.getAllCFGs();
for (CFG cfg : cfgs) {
if (baseline.contains(cfg)) {
p.addEntryPoint(cfg);
}
}
LiSAReport report = lisa.run(p);
ipa = report.getConfiguration().interproceduralAnalysis;
storeResults(frontend.getProgram(), ipa);
Map<Address, Long> addressesToSyscalls = new HashMap<>();
Register syscallReg = program.getLanguage().getRegister(syscallRegister);
for (Function func : funcsToCalls.keySet()) {
for (Address callSite : funcsToCalls.get(func)) {
long val = getRegisterValue(callSite, syscallReg);
if (val == -1) {
//createBookmark(callSite, "System Call",
// "Couldn't resolve value of " + syscallReg);
//printf("Couldn't resolve value of " + syscallReg + " at " + callSite + "\n");
continue;
}
addressesToSyscalls.put(callSite, val);
}
}
return addressesToSyscalls;
}
private long getRegisterValue(Address callSite, Register syscallReg) {
Set<Statement> set = frontend.getStatement(callSite);
if (set == null) {
printerr("Null set for " + callSite);
return -1;
}
String offset = syscallReg.getAddress().toString();
Function f = currentProgram.getListing().getFunctionContaining(callSite);
CFG cfg = targets.get(f.getEntryPoint());
if (cfg == null) {
printerr("Null cfg for " + callSite);
return -1;
}
Collection<?> results = ipa.getAnalysisResultsOf(cfg);
Statement st = null;
for (Statement obj : set) {
PcodeLocation loc = (PcodeLocation) obj.getLocation();
if (loc.op.getOpcode() >= PcodeOp.CALL && loc.op.getOpcode() <= PcodeOp.CALLOTHER) {
st = obj;
}
}
if (st == null) {
printerr("Null statement for " + callSite);
return -1;
}
Iterator<?> iterator = results.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
if (next instanceof AnalyzedCFG<?> acfg) {
AnalysisState<?> state1;
try {
state1 = acfg.getAnalysisStateBefore(st);
AbstractState<?> state2 = state1.getState();
if (state2 instanceof SimpleAbstractState sas) {
ValueEnvironment<?> valueState = (ValueEnvironment<?>) sas.getValueState();
Map<Identifier, ?> function = valueState.function;
if (function != null) {
for (Object key : function.keySet()) {
Object val = valueState.function.get(key);
if (val instanceof PcodeByteBasedConstantPropagation icp) {
String keyStr = key.toString();
if (keyStr.equals(offset)) {
String valstr = icp.representation().toString();
if (!valstr.contains("#TOP#")) {
return Long.parseLong(valstr);
}
}
}
}
}
}
}
catch (Exception e) {
printerr(e.getMessage());
}
}
}
return -1;
}
private void initLisa() {
frontend = new PcodeFrontend();
DefaultConfiguration conf = new DefaultConfiguration();
conf.serializeResults = true;
conf.abstractState = DefaultConfiguration.simpleState(
DefaultConfiguration.defaultHeapDomain(),
new ValueEnvironment<>(
new PcodeByteBasedConstantPropagation(currentProgram.getLanguage())),
DefaultConfiguration.defaultTypeDomain());
conf.serializeResults = false;
lisa = new LiSA(conf);
}
private void storeResults(it.unive.lisa.program.Program p,
InterproceduralAnalysis<?> interproceduralAnalysis) {
Collection<CFG> ep = p.getEntryPoints();
for (CFG cfg : ep) {
Collection<Statement> entrypoints = cfg.getEntrypoints();
for (Statement st : entrypoints) {
PcodeLocation loc = (PcodeLocation) st.getLocation();
Address target = loc.op.getSeqnum().getTarget();
Function f = currentProgram.getListing().getFunctionContaining(target);
targets.put(f.getEntryPoint(), cfg);
}
}
}
/**
* Checks whether an x86 native instruction is a system call
* @param inst instruction to check
* @return true precisely when the instruction is a system call
*/
private static boolean checkX86Instruction(Instruction inst) {
try {
return Arrays.equals(x86_bytes, inst.getBytes());
}
catch (MemoryAccessException e) {
Msg.info(Lisa_ResolveX86orX64LinuxSyscallsScript.class,
"MemoryAccessException at " + inst.getAddress().toString());
return false;
}
}
/**
* Checks whether an x64 instruction is a system call
* @param inst instruction to check
* @return true precisely when the instruction is a system call
*/
private static boolean checkX64Instruction(Instruction inst) {
boolean retVal = false;
for (PcodeOp op : inst.getPcode()) {
if (op.getOpcode() == PcodeOp.CALLOTHER) {
int index = (int) op.getInput(0).getOffset();
if (inst.getProgram()
.getLanguage()
.getUserDefinedOpName(index)
.equals(SYSCALL_X64_CALLOTHER)) {
retVal = true;
}
}
}
return retVal;
}
}

View File

@@ -0,0 +1,19 @@
<?xml version='1.0' encoding='ISO-8859-1'?>
<!-- See Base's TOC_Source.xml for help -->
<tocroot>
<tocref id="Ghidra Functionality">
<tocdef id="Lisa Plugin" text="Lisa Plugin"
target="help/topics/LisaPlugin/LisaPlugin.html">
<tocdef id="add_cfgs" text="Add CFGs"
target="help/topics/LisaPlugin/LisaPlugin.html#add_cfgs" />
<tocdef id="clear_cfgs" text="Clear CFGs"
target="help/topics/LisaPlugin/LisaPlugin.html#clear_cfgs" />
<tocdef id="set_taint" text="Taint"
target="help/topics/LisaPlugin/LisaPlugin.html#set_taint" />
</tocdef>
</tocref>
</tocroot>

View File

@@ -0,0 +1,134 @@
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
<HTML>
<HEAD>
<META name="generator" content=
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
<TITLE>Abstract Interpretation: LiSA</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
<LINK rel="stylesheet" type="text/css" href="help/shared/DefaultStyle.css">
</HEAD>
<BODY lang="EN-US">
<H1><A name="LisaPlugin"></A>Abstract Interpretation: LiSA</H1>
<P>The Lisa Plugin uses the "Library for Static Analysis", <B>LiSA</B>, developed and maintained
by the Software and System Verification (SSV) group at Universita Ca' Foscari in Venice, Italy,
(lisa-analyzer.github.io), to implement and run static analyzers based on the theory of Abstract Interpretation.
</P>
<H2>Setup</H2>
<P>If MavenCentral is accessible, the gradle build command will download the requisite LiSA libraries.
Otherwise, you should install them and add them to the relevant build paths. Add the Lisa and
Taint plugins to your project in the usual way. (The various analyses are started using the
"default taint query" button in Decompiler.)
</P>
<H2>Options</H2>
<P>The analysis to run is chosen via the Edit &rarr; ToolOptions, which gives you a default category
<B>Abstract Interpretation</B> and two subcategories <B>Abstract Interpretation/Domains</B> and
<B>Abstract Interpretation/Output</B>. <B>Domains</b> is the most important of these, as it provides the list of available analyses.
</P>
<P>
<B>Domains</B> has three options, corresponding to the choice of <B>Heap</B>, <B>Type</B>, and <B>Value</B> domains,
the last being the option you are most likely to want to change.
</P>
<UL>
<LI><B>Heap<A name="domain_heap"></A></B>: the abstraction used to model the target program's heap.</LI>
<LI><B>Type<A name="domain_type"></A></B>: the abstraction used to model types.</LI>
<LI><B>Value<A name="domain_value"></A></B>: the abstraction used to model values. &larr; </LI>
</UL>
<P>
The symbolic state of an analysis is the combination of these domains.
</P>
<P>
<B>Output</B> has several options (not well-tested), which correspond to fields in the LiSAConfiguration class.
</P>
<UL>
<LI><B>WorkDir<A name="work_dir"></A></B>: the working directory to be used for output if <B>SerializeResults</B> is selected.</LI>
<LI><B>GraphFormat<A name="graph_format"></A></B>: graph format if graph output is desired.</LI>
<LI><B>SerializeResults<A name="serialize"></A></B>: causes the analysis results to be dumped as JSON.</LI>
</UL>
<P>
<B>Abstract Interpretation</B> (the base option set) has additional execution-related options, associated again with LiSAConfiguration.
</P>
<UL>
<LI><B>CallGraph<A name="call_graph"></A></B>: "Class Hierarchy" or "Rapid Type" analysis of calls.</LI>
<LI><B>DescendingPhase<A name="descending_phase"></A></B>: the descending phase applied by the fixpoint algorithm.</LI>
<LI><B>Interprocedural<A name="interprocedural"></A></B>: type of interprocedural analysis desired.</LI>
<LI><B>OpenCallPolicy<A name="open_call_policy"></A></B>: how to treat unresolved calls.</LI>
<LI><B>OptimizeResults<A name="optimize"></A></B>: whether to optimize fixpoint execution.</LI>
<LI><B>PostState<A name="post_state"></A></B>: evaluate state post- or pre-statement.</LI>
<LI><B>CallDepth<A name="call_depth"></A></B>: cfg-computation depth (-1 == unlimited).</LI>
<LI><B>Threshhold<A name="threshhold"></A></B>: applied to either widening or glb.</LI>
</UL>
<P>
For the most part, these options may be left to their defaults. However, specific analyses may benefit from different choices.
</P>
<H2>Actions</H2>
<H3><A name="add_cfgs">Add CFGs</A></H3>
<P>By default, an analysis uses the current function and its callees as the basis for the analysis. Under certain circumstances,
you may want to add additional functions to the base. This action generates control-flow graphs for the current function and its
descendants without invoking a particular analysis.
</P>
<H3><A name="clear_cfgs">Clear CFGs</A></H3>
<P>Successive analyses will add to the set of control-flow graphs under consideration without clearing the previous results.
To clear previous CFGs, you must explicitly do so. This action removes all active CFGs.
</P>
<H3><A name="set_taint">Set Taint</A></H3>
<P>For the "Taint" and "ThreeLevelTaint" analyses, you will need to specify sources of taint. There are two ways to do this.
From the decompiler, you may use the normal taint analysis functions (or this action) to specify <B>SOURCES</B> of taint.
(<B>SINKS</B> and <B>GATES</B> may also be used, but be aware they currently correspond only to "clean" annotations in LiSA.)
Alternatively, from the disassembly view, you may use this action on a line of PCode to mark an input to the op, or on a line
of disassembly to get a dialog for entering the varnode ID by hand. The varnode should be specified using its address, e.g.
"register:00000000" for RAX.
</P>
<P>Using either method may be imprecise. In particular, the analysis currently uses the low PCode, so tokens selected in the
decompiler (high PCode) may or may not have an equivalent. Similarly, varnodes marked in the disassembly are inputs only.
Marking an input as a source does not mark it as tainted in the analysis' state. In most cases, the resulting output will
be tainted, but not future references to the input, e.g. "(unique:5) INT_ADD (register:4), 0x12" taints future references
to (unique:5) but not to (register:4).
</P>
<H2>Current Analyses (Value Domains)</H2>
<UL>
<LI><B>Numeric: Constant Propagation</B>: overflow-insensitive basic constant propagation.</LI>
<LI><B>Numeric: Interval</B>: approximating numeric values as the minimum interval containing them.</LI>
<LI><B>Numeric: Non-redundant Power Sets Of Intervals</B>: approximating numeric values as a non-redundant set of intervals.</LI>
<LI><B>Numeric: Parity</B>: tracking whether numeric values are even or odd.</LI>
<LI><B>Numeric: Pentagon</B>: capturing properties of the form of x in [a, b] &and; x &lt; y.</LI>
<LI><B>Numeric: Sign</B>: tracking zero, strictly positive and strictly negative values.</LI>
<LI><B>Numeric: Upper Bound</B>: capturing upper bounds on a variable.</LI>
<LI><B>Dataflow: Available Expressions</B>: expressions stored in some variable.</LI>
<LI><B>Dataflow: Constant Propagation</B>: overflow-insensitive basic constant propagation.</LI>
<LI><B>Dataflow: Reaching Definitions</B>: instruction whose targets reach the given one without an intervening assignment.</LI>
<LI><B>Dataflow: Liveness</B>: variables that are live at each point in the program.
NB: typically uses the BackwardModularWorstCase interprocedural analysis.</LI>
<LI><B>Dataflow: Taint</B>: two levels of taintedness - clean and tainted. See above re setting taint.</LI>
<LI><B>Dataflow: Three-level Taint</B>: three levels of taintedness - clean, tainted, and top. See above re setting taint.</LI>
<LI><B>Non-interference</B>: type-system based implementation of non-interference analysis.</LI>
<LI><B>Stability</B>: per-variable numerical trends.</LI>
</UL>
<P>Note: with the exception of reaching, liveness, and non-interference domains, these domains have been extended (to a greater or lesser extent)
to handle p-code. Modifications may not be in sync with the latest domain definitions in the original LiSA analysis archive.
</P>
<H2>Results</H2>
<P>Results are passed, by default, via SARIF to a results table. The functionality of the table is described in more detail
in <A href="help/topics/DecompilerTaint/DecompilerTaint.html#ResultMenuActions">Decompiler Taint Operations</A>. In general, you'll want to enable the "name", "type", "value",
and "displayName" columns, as well as "Address" and possibly "location".
</P>
</BODY>
</HTML>

View File

@@ -0,0 +1,39 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.lisa.gui;
import ghidra.lisa.gui.LisaTaintState.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 taintLabels;
@Exclude
private int index;
public ExtKeyValue(KTV ktv) {
this.name = ktv.key();
this.displayName = ktv.displayName();
this.type = ktv.type();
this.taintLabels = "[" + ktv.value() + "]";
}
}

View File

@@ -0,0 +1,635 @@
/* ###
* 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.lisa.gui;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.Plugin;
import ghidra.lisa.pcode.analyses.*;
import ghidra.program.model.listing.Program;
import ghidra.util.HelpLocation;
import it.unive.lisa.DefaultConfiguration;
import it.unive.lisa.analysis.dataflow.*;
import it.unive.lisa.analysis.heap.*;
import it.unive.lisa.analysis.heap.pointbased.FieldSensitivePointBasedHeap;
import it.unive.lisa.analysis.heap.pointbased.PointBasedHeap;
import it.unive.lisa.analysis.nonInterference.NonInterference;
import it.unive.lisa.analysis.nonrelational.inference.InferenceSystem;
import it.unive.lisa.analysis.nonrelational.value.TypeEnvironment;
import it.unive.lisa.analysis.nonrelational.value.ValueEnvironment;
import it.unive.lisa.analysis.type.TypeDomain;
import it.unive.lisa.analysis.types.InferredTypes;
import it.unive.lisa.analysis.types.StaticTypes;
import it.unive.lisa.analysis.value.ValueDomain;
import it.unive.lisa.conf.LiSAConfiguration.DescendingPhaseType;
import it.unive.lisa.conf.LiSAConfiguration.GraphType;
import it.unive.lisa.interprocedural.*;
import it.unive.lisa.interprocedural.callgraph.*;
import it.unive.lisa.interprocedural.context.*;
/**
* Parameters used to control LiSA
*/
public class LisaOptions {
// ResourceManager may be able to pull these from a configuration.
public final static String OP_KEY_LISA_ANALYSIS_HEAP = "Domain.Heap";
public final static String OP_KEY_LISA_ANALYSIS_TYPE = "Domain.Type";
public final static String OP_KEY_LISA_ANALYSIS_VALUE = "Domain.Value";
public final static String OP_KEY_LISA_ANALYSIS_CALL_GRAPH = "CallGraph";
public final static String OP_KEY_LISA_ANALYSIS_DESC = "DescendingPhase";
public final static String OP_KEY_LISA_ANALYSIS_INTER = "Interprocedural";
public final static String OP_KEY_LISA_ANALYSIS_CALL = "OpenCallPolicy";
public final static String OP_KEY_LISA_ANALYSIS_OPT = "OptimizeResults";
public final static String OP_KEY_LISA_ANALYSIS_POST = "Post-State (vs Pre-)";
public final static String OP_KEY_LISA_ANALYSIS_SHOW_TOP = "Display 'top' values";
public final static String OP_KEY_LISA_ANALYSIS_SHOW_UNIQUE = "Display 'unique' values";
public final static String OP_KEY_LISA_ANALYSIS_CALL_DEPTH = "Compute CFGs to Depth";
public final static String OP_KEY_LISA_ANALYSIS_THRESH = "Threshhold";
public final static String OP_KEY_LISA_ANALYSIS_GRAPH = "Output.GraphFormat";
public final static String OP_KEY_LISA_ANALYSIS_OUTDIR = "Output.WorkDir";
public final static String OP_KEY_LISA_ANALYSIS_SERIAL = "Output.SerializeResults";
public final static String DEFAULT_LISA_ANALYSIS_HEAP = HeapDomainOption.DEFAULT.optionString;
public final static String DEFAULT_LISA_ANALYSIS_TYPE = TypeDomainOption.DEFAULT.optionString;
public final static String DEFAULT_LISA_ANALYSIS_VALUE = ValueDomainOption.DEFAULT.optionString;
public final static String DEFAULT_LISA_ANALYSIS_CALL_GRAPH =
CallGraphOption.RTA.optionString;
public final static String DEFAULT_LISA_ANALYSIS_DESC =
InterproceduralOption.DEFAULT.optionString;
public final static String DEFAULT_LISA_ANALYSIS_INTER =
InterproceduralOption.DEFAULT.optionString;
public final static String DEFAULT_LISA_ANALYSIS_CALL = CallOption.DEFAULT.optionString;
public final static boolean DEFAULT_LISA_ANALYSIS_OPT = false;
public final static boolean DEFAULT_LISA_ANALYSIS_POST = false;
public final static boolean DEFAULT_LISA_ANALYSIS_SHOW_TOP = false;
public final static boolean DEFAULT_LISA_ANALYSIS_SHOW_UNIQUE = false;
public final static int DEFAULT_LISA_ANALYSIS_CALL_DEPTH = 0;
public final static int DEFAULT_LISA_ANALYSIS_THRESH = 5;
public final static String DEFAULT_LISA_ANALYSIS_OUTDIR = "";
public final static String DEFAULT_LISA_ANALYSIS_GRAPH = GraphOption.DEFAULT.optionString;
public final static boolean DEFAULT_LISA_ANALYSIS_SERIAL = false;
public final static String DEFAULT_LISA_TOP_REPRESENTATION = "#TOP#";
private static String suppress; // Suppressed in results (typically #TOP#)
private HeapDomainOption heapDomainOption;
private TypeDomainOption typeDomainOption;
private ValueDomainOption valueDomainOption;
private InterproceduralOption interproceduralOption;
private DescendingPhaseOption descendingPhaseOption;
private CallOption callOption;
private GraphOption graphOption;
private CallGraphOption callGraphOption;
private int cfgDepth;
private static int threshhold;
private String outputDir;
private boolean serialize;
private boolean optimize;
private boolean postState;
private boolean showTop;
private boolean showUnique;
public static enum HeapDomainOption {
HEAP_MONOLITHIC("Monolithic"),
HEAP_POINTBASED("PointBased"),
HEAP_FIELDSENSITIVE("FieldSensitivePointBased"),
HEAP_TYPEBASED("TypeBased"),
DEFAULT("DEFAULT(Monolithic)");
private String optionString;
private HeapDomainOption(String optString) {
this.optionString = optString;
}
@Override
public String toString() {
return optionString;
}
@SuppressWarnings("rawtypes")
public HeapDomain getDomain() {
return switch (this) {
case HEAP_MONOLITHIC -> new MonolithicHeap();
case HEAP_POINTBASED -> new PointBasedHeap();
case HEAP_FIELDSENSITIVE -> new FieldSensitivePointBasedHeap();
case HEAP_TYPEBASED -> new TypeBasedHeap();
default -> DefaultConfiguration.defaultHeapDomain();
};
}
}
public static enum TypeDomainOption {
TYPE_INFERRED("Inferred"),
TYPE_STATIC("Static"),
DEFAULT("DEFAULT(Inferred)");
private String optionString;
private TypeDomainOption(String optString) {
this.optionString = optString;
}
@Override
public String toString() {
return optionString;
}
@SuppressWarnings("rawtypes")
public TypeDomain getDomain() {
return switch (this) {
case TYPE_INFERRED -> new TypeEnvironment<>(new InferredTypes());
case TYPE_STATIC -> new TypeEnvironment<>(new StaticTypes());
default -> DefaultConfiguration.defaultTypeDomain();
};
}
}
public static enum ValueDomainOption {
VALUE_CONSTPROP("Numeric: ConstantPropagation"),
VALUE_INTERVAL("Numeric: Interval"),
VALUE_POWERSET("Numeric: NonRedundantPowersetOfInterval"),
VALUE_PARITY("Numeric: Parity"),
VALUE_PENTAGON("Numeric: Pentagon"),
VALUE_SIGN("Numeric: Sign"),
VALUE_UPPERBOUND("Numeric: UpperBound"),
DDATA_AVAILABLE("Dataflow: AvailableExpressions"),
DDATA_CONSTPROP("Dataflow: ConstantPropagation"),
PDATA_REACHING("Dataflow: ReachingDefinitions"),
PDATA_LIVENESS("Dataflow: Liveness"),
VALUE_TAINT("Dataflow: Taint"),
VALUE_TAINT3L("Dataflow: ThreeLevelTaint"),
NONINTERFERENCE("NonInterference"),
STABILITY("Stability"),
// VALUE_TREND("Trend"),
// VALUE_WHOLE("WholeValueAnalysis"),
DEFAULT("DEFAULT(Interval)");
private String optionString;
private ValueDomainOption(String optString) {
this.optionString = optString;
}
@Override
public String toString() {
return optionString;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public ValueDomain getDomain(Program program) {
suppress = DEFAULT_LISA_TOP_REPRESENTATION;
if (this == VALUE_INTERVAL || this == VALUE_POWERSET) {
suppress = "[-Inf, +Inf]";
}
if (this == VALUE_UPPERBOUND) {
suppress = "{}";
}
if (this == STABILITY) {
suppress = "=";
}
if (this == NONINTERFERENCE) {
suppress = "HL";
}
if (this == VALUE_TAINT || this == VALUE_TAINT3L) {
suppress = "_";
}
return switch (this) {
case VALUE_CONSTPROP -> new ValueEnvironment<>(
new PcodeByteBasedConstantPropagation(program.getLanguage()));
case VALUE_INTERVAL -> new ValueEnvironment<>(new PcodeInterval());
case VALUE_PARITY -> new ValueEnvironment<>(new PcodeParity());
case VALUE_PENTAGON -> new PcodePentagon();
case VALUE_POWERSET -> new ValueEnvironment<>(
new PcodeNonRedundantPowersetOfInterval());
case VALUE_SIGN -> new ValueEnvironment<>(new PcodeSign());
case VALUE_TAINT -> new ValueEnvironment<>(new PcodeTaint(false));
case VALUE_TAINT3L -> new ValueEnvironment<>(new PcodeThreeLevelTaint());
//case VALUE_TREND -> new ValueEnvironment<>(Trend.TOP);
case VALUE_UPPERBOUND -> new ValueEnvironment<>(new PcodeUpperBounds());
case DDATA_AVAILABLE -> new DefiniteDataflowDomain<>(new AvailableExpressions());
case DDATA_CONSTPROP -> new DefiniteDataflowDomain<>(
new PcodeDataflowConstantPropagation(program.getLanguage()));
case PDATA_REACHING -> new PossibleDataflowDomain<>(new ReachingDefinitions());
case PDATA_LIVENESS -> new PossibleDataflowDomain<>(new Liveness());
case NONINTERFERENCE -> new InferenceSystem<>(new NonInterference());
case STABILITY -> new PcodeStability(
new ValueEnvironment<>(new PcodeInterval()).top());
default -> new ValueEnvironment<>(new PcodeInterval());
};
}
}
public static enum InterproceduralOption {
CONTEXT("ContextBased"),
FULLSTACK("ContextBased(FullStackToken)"),
KDEPTH("ContextBased(KDepthToken)"),
LASTCALL("ContextBased(LastCallToken)"),
INSENSITIVE("ContextBased(ContextInsensitiveToken)"),
BACKWARDS("BackwardModularWorstCaseAnalysis"),
DEFAULT("ModularWorstCaseAnalysis");
private String optionString;
private InterproceduralOption(String optString) {
this.optionString = optString;
}
@Override
public String toString() {
return optionString;
}
@SuppressWarnings("rawtypes")
public InterproceduralAnalysis<?> getAnalysis() {
return switch (this) {
case CONTEXT -> new ContextBasedAnalysis<>();
case FULLSTACK -> new ContextBasedAnalysis<>(FullStackToken.getSingleton());
case KDEPTH -> new ContextBasedAnalysis<>(KDepthToken.getSingleton(threshhold));
case LASTCALL -> new ContextBasedAnalysis<>(LastCallToken.getSingleton());
case INSENSITIVE -> new ContextBasedAnalysis<>(
ContextInsensitiveToken.getSingleton());
case BACKWARDS -> new BackwardModularWorstCaseAnalysis();
default -> new ModularWorstCaseAnalysis<>();
};
}
}
public static enum DescendingPhaseOption {
NARROWING("Narrowing"),
GLB("GLB k-times"),
DEFAULT("None");
private String optionString;
private DescendingPhaseOption(String optString) {
this.optionString = optString;
}
@Override
public String toString() {
return optionString;
}
public DescendingPhaseType getType() {
return switch (this) {
case NARROWING -> DescendingPhaseType.NARROWING;
case GLB -> DescendingPhaseType.GLB;
default -> DescendingPhaseType.NONE;
};
}
}
public static enum CallOption {
RETURNTOP("ReturnTop"),
DEFAULT("WorstCase");
private String optionString;
private CallOption(String optString) {
this.optionString = optString;
}
@Override
public String toString() {
return optionString;
}
public OpenCallPolicy getPolicy() {
return switch (this) {
case RETURNTOP -> ReturnTopPolicy.INSTANCE;
default -> WorstCasePolicy.INSTANCE;
};
}
}
public static enum GraphOption {
HTML("HTML"),
HTMLSUB("HTML w/ subnodes"),
DOT("Dot"),
GRAPHML("GraphML"),
GRAPHMLSUB("GraphML w/ subnodes"),
DEFAULT("None");
private String optionString;
private GraphOption(String optString) {
this.optionString = optString;
}
@Override
public String toString() {
return optionString;
}
public GraphType getType() {
return switch (this) {
case HTML -> GraphType.HTML;
case HTMLSUB -> GraphType.HTML_WITH_SUBNODES;
case DOT -> GraphType.DOT;
case GRAPHML -> GraphType.GRAPHML;
case GRAPHMLSUB -> GraphType.GRAPHML_WITH_SUBNODES;
default -> GraphType.NONE;
};
}
}
public static enum CallGraphOption {
CHA("Call Hierarchy Analysis"),
RTA("Rapid Type Analysis");
private String optionString;
private CallGraphOption(String optString) {
this.optionString = optString;
}
@Override
public String toString() {
return optionString;
}
public CallGraph getCallGraph() {
return switch (this) {
case CHA -> new CHACallGraph();
default -> new RTACallGraph();
};
}
}
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();
}
/**
* 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_LISA_ANALYSIS_HEAP, HeapDomainOption.DEFAULT,
new HelpLocation(ownerPlugin.getName(), "domain_heap"),
"Domain for the heap");
opt.registerOption(OP_KEY_LISA_ANALYSIS_TYPE, TypeDomainOption.DEFAULT,
new HelpLocation(ownerPlugin.getName(), "domain_type"),
"Domain for types");
opt.registerOption(OP_KEY_LISA_ANALYSIS_VALUE, ValueDomainOption.DEFAULT,
new HelpLocation(ownerPlugin.getName(), "domain_value"),
"Domain for values");
opt.registerOption(OP_KEY_LISA_ANALYSIS_INTER, InterproceduralOption.DEFAULT,
new HelpLocation(ownerPlugin.getName(), "interprocedural"),
"Interprocedural analysis");
opt.registerOption(OP_KEY_LISA_ANALYSIS_DESC, DescendingPhaseOption.DEFAULT,
new HelpLocation(ownerPlugin.getName(), "descending_phase"),
"Descending phase");
opt.registerOption(OP_KEY_LISA_ANALYSIS_CALL, CallOption.DEFAULT,
new HelpLocation(ownerPlugin.getName(), "open_call_policy"),
"Open call policy");
opt.registerOption(OP_KEY_LISA_ANALYSIS_POST, DEFAULT_LISA_ANALYSIS_POST,
new HelpLocation(ownerPlugin.getName(), "post_state"),
"Evaluate state post- or pre-statement");
opt.registerOption(OP_KEY_LISA_ANALYSIS_CALL_DEPTH, DEFAULT_LISA_ANALYSIS_CALL_DEPTH,
new HelpLocation(ownerPlugin.getName(), "call_depth"),
"Depth for CFG computation");
opt.registerOption(OP_KEY_LISA_ANALYSIS_THRESH, DEFAULT_LISA_ANALYSIS_THRESH,
new HelpLocation(ownerPlugin.getName(), "threshhold"),
"Threshhold (GLB or k-depth)");
opt.registerOption(OP_KEY_LISA_ANALYSIS_OUTDIR, DEFAULT_LISA_ANALYSIS_OUTDIR,
new HelpLocation(ownerPlugin.getName(), "work_dir"),
"Output directory");
opt.registerOption(OP_KEY_LISA_ANALYSIS_SERIAL, DEFAULT_LISA_ANALYSIS_SERIAL,
new HelpLocation(ownerPlugin.getName(), "serialize"),
"Serialize results");
opt.registerOption(OP_KEY_LISA_ANALYSIS_OPT, DEFAULT_LISA_ANALYSIS_OPT,
new HelpLocation(ownerPlugin.getName(), "optimize"),
"Optimize results");
opt.registerOption(OP_KEY_LISA_ANALYSIS_GRAPH, GraphOption.DEFAULT,
new HelpLocation(ownerPlugin.getName(), "graph_format"),
"Graph type");
opt.registerOption(OP_KEY_LISA_ANALYSIS_CALL_GRAPH, CallGraphOption.RTA,
new HelpLocation(ownerPlugin.getName(), "call_graph"),
"Call graph input");
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) {
heapDomainOption = opt.getEnum(OP_KEY_LISA_ANALYSIS_HEAP, HeapDomainOption.DEFAULT);
typeDomainOption = opt.getEnum(OP_KEY_LISA_ANALYSIS_TYPE, TypeDomainOption.DEFAULT);
valueDomainOption = opt.getEnum(OP_KEY_LISA_ANALYSIS_VALUE, ValueDomainOption.DEFAULT);
interproceduralOption =
opt.getEnum(OP_KEY_LISA_ANALYSIS_INTER, InterproceduralOption.DEFAULT);
descendingPhaseOption =
opt.getEnum(OP_KEY_LISA_ANALYSIS_DESC, DescendingPhaseOption.DEFAULT);
callOption = opt.getEnum(OP_KEY_LISA_ANALYSIS_CALL, CallOption.DEFAULT);
graphOption = opt.getEnum(OP_KEY_LISA_ANALYSIS_GRAPH, GraphOption.DEFAULT);
callGraphOption = opt.getEnum(OP_KEY_LISA_ANALYSIS_CALL_GRAPH, CallGraphOption.RTA);
postState = opt.getBoolean(OP_KEY_LISA_ANALYSIS_POST, DEFAULT_LISA_ANALYSIS_POST);
showTop = opt.getBoolean(OP_KEY_LISA_ANALYSIS_SHOW_TOP, DEFAULT_LISA_ANALYSIS_SHOW_TOP);
showUnique =
opt.getBoolean(OP_KEY_LISA_ANALYSIS_SHOW_UNIQUE, DEFAULT_LISA_ANALYSIS_SHOW_UNIQUE);
cfgDepth = opt.getInt(OP_KEY_LISA_ANALYSIS_CALL_DEPTH, DEFAULT_LISA_ANALYSIS_CALL_DEPTH);
threshhold = opt.getInt(OP_KEY_LISA_ANALYSIS_THRESH, DEFAULT_LISA_ANALYSIS_THRESH);
outputDir = opt.getString(OP_KEY_LISA_ANALYSIS_OUTDIR, DEFAULT_LISA_ANALYSIS_OUTDIR);
serialize = opt.getBoolean(OP_KEY_LISA_ANALYSIS_SERIAL, DEFAULT_LISA_ANALYSIS_SERIAL);
optimize = opt.getBoolean(OP_KEY_LISA_ANALYSIS_OPT, DEFAULT_LISA_ANALYSIS_OPT);
}
public HeapDomainOption getHeapOption() {
return heapDomainOption;
}
public void setHeapDomain(HeapDomainOption option) {
this.heapDomainOption = option;
}
public TypeDomainOption getTypeOption() {
return typeDomainOption;
}
public void setTypeDomain(TypeDomainOption option) {
this.typeDomainOption = option;
}
public ValueDomainOption getValueOption() {
return valueDomainOption;
}
public void setValueDomain(ValueDomainOption option) {
this.valueDomainOption = option;
}
public InterproceduralOption getInterproceduralOption() {
return interproceduralOption;
}
public void setInterproceduralOption(InterproceduralOption option) {
this.interproceduralOption = option;
}
public DescendingPhaseOption getDescendingPhaseOption() {
return descendingPhaseOption;
}
public void setDescendingPhaseOption(DescendingPhaseOption option) {
this.descendingPhaseOption = option;
}
public CallOption getCallOption() {
return callOption;
}
public void setCallOption(CallOption option) {
this.callOption = option;
}
public int getCfgDepth() {
return cfgDepth;
}
public void setCfgDepth(int cfgDepth) {
this.cfgDepth = cfgDepth;
}
public int getThreshhold() {
return threshhold;
}
public void setThreshhold(int threshhold) {
LisaOptions.threshhold = threshhold;
}
public String getOutputDir() {
return outputDir;
}
public void setOutputDir(String outputDir) {
this.outputDir = outputDir;
}
public boolean isSerialize() {
return serialize;
}
public void setSerialize(boolean serialize) {
this.serialize = serialize;
}
public boolean isOptimize() {
return optimize;
}
public void setOptimize(boolean optimize) {
this.optimize = optimize;
}
public boolean isPostState() {
return postState;
}
public void setPostState(boolean postState) {
this.postState = postState;
}
public boolean isShowTop() {
return showTop;
}
public void setShowTop(boolean showTop) {
this.showTop = showTop;
}
public boolean isShowUnique() {
return showUnique;
}
public void setShowUnique(boolean showUnique) {
this.showUnique = showUnique;
}
public GraphOption getGraphOption() {
return graphOption;
}
public void setGraphOption(GraphOption graphOption) {
this.graphOption = graphOption;
}
public CallGraphOption getCallGraphOption() {
return callGraphOption;
}
public void setCallGraphOption(CallGraphOption callGraphOption) {
this.callGraphOption = callGraphOption;
}
public static String getTopValue() {
return suppress;
}
}

View File

@@ -0,0 +1,484 @@
/* ###
* 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.lisa.gui;
import java.util.*;
import db.Transaction;
import docking.action.builder.ActionBuilder;
import ghidra.GhidraOptions;
import ghidra.app.CorePluginPackage;
import ghidra.app.context.ProgramLocationActionContext;
import ghidra.app.decompiler.ClangToken;
import ghidra.app.decompiler.location.DefaultDecompilerLocation;
import ghidra.app.events.*;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.ProgramPlugin;
import ghidra.app.plugin.core.decompiler.absint.AbstractInterpretationService;
import ghidra.app.plugin.core.decompiler.taint.*;
import ghidra.app.plugin.core.decompiler.taint.TaintState.MarkType;
import ghidra.app.script.AskDialog;
import ghidra.framework.options.OptionsChangeListener;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.lisa.gui.LisaOptions.*;
import ghidra.lisa.pcode.PcodeFrontend;
import ghidra.program.database.SpecExtension;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
import ghidra.program.util.PcodeFieldLocation;
import ghidra.program.util.ProgramLocation;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.DummyCancellableTaskMonitor;
import ghidra.util.task.TaskMonitor;
import it.unive.lisa.*;
import it.unive.lisa.interprocedural.InterproceduralAnalysis;
import it.unive.lisa.program.cfg.CFG;
import it.unive.lisa.program.cfg.statement.Statement;
/**
* Plugin for tracking taint through the decompiler.
*/
//@formatter:off
@PluginInfo(
status = PluginStatus.UNSTABLE,
packageName = CorePluginPackage.NAME,
category = PluginCategoryNames.ANALYSIS,
shortDescription = "LisaTest",
description = "Plugin for abstract interpretation analysis via LiSA",
servicesProvided = { AbstractInterpretationService.class },
servicesRequired = {
TaintService.class,
},
eventsConsumed = {
ProgramActivatedPluginEvent.class, ProgramOpenedPluginEvent.class,
ProgramLocationPluginEvent.class, ProgramSelectionPluginEvent.class,
ProgramClosedPluginEvent.class
})
//@formatter:on
public class LisaPlugin extends ProgramPlugin implements OptionsChangeListener, AbstractInterpretationService {
private static final String OPTIONS_TITLE = "Abstract Interpretation";
public HeapDomainOption heapOption = HeapDomainOption.DEFAULT;
public TypeDomainOption typeOption = TypeDomainOption.DEFAULT;
public ValueDomainOption valueOption = ValueDomainOption.DEFAULT;
private Function currentFunction;
private InterproceduralAnalysis<?> ipa;
private PcodeFrontend frontend;
private LiSA lisa;
private LisaOptions options;
private TaintPlugin taintPlugin;
private LisaTaintState taintState;
private TaskMonitor monitor = TaskMonitor.DUMMY;
private String lastValue = "";
public LisaPlugin(PluginTool tool) {
super(tool);
setOptions(new LisaOptions());
createActions();
}
public Function getCurrentFunction() {
return currentFunction;
}
@Override
protected void programActivated(Program program) {
currentProgram = program;
initOptions();
}
public interface AddCfgsAction {
String NAME = "Add CFG";
String DESCRIPTION = "Compute called CFGs prior to analysis";
String HELP_ANCHOR = "add_cfgs";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.popupMenuPath("Abstract Interpretation", NAME)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
public interface ClearCfgsAction {
String NAME = "Clear CFGs";
String DESCRIPTION = "Clear CFGs prior to analysis";
String HELP_ANCHOR = "clear_cfgs";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.popupMenuPath("Abstract Interpretation", NAME)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
public interface SetTaintAction {
String NAME = "Set Taint";
String DESCRIPTION = "Set taint for given varnode";
String HELP_ANCHOR = "set_taint";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.popupMenuPath("Abstract Interpretation", NAME)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
private void createActions() {
AddCfgsAction.builder(this)
.withContext(ProgramLocationActionContext.class)
.onAction(this::addCfgs)
.buildAndInstall(tool);
ClearCfgsAction.builder(this)
.withContext(ProgramLocationActionContext.class)
.onAction(this::clearCfgs)
.buildAndInstall(tool);
SetTaintAction.builder(this)
.withContext(ProgramLocationActionContext.class)
.onAction(this::setTaint)
.buildAndInstall(tool);
}
@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 closedEvent) {
Program program = closedEvent.getProgram();
if (currentProgram != null && currentProgram.equals(program)) {
currentProgram = null;
}
return;
}
if (event instanceof ProgramActivatedPluginEvent activatedEvent) {
currentProgram = activatedEvent.getActiveProgram();
if (currentProgram != null) {
SpecExtension.registerOptions(currentProgram);
}
}
else if (event instanceof ProgramLocationPluginEvent locEvent) {
// user changed their location in the program; this may be a function change.
ProgramLocation location = locEvent.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;
}
}
}
}
private void addCfgs(ProgramLocationActionContext context) {
Set<CFG> cfgs = new HashSet<>();
Address addr = context.getAddress();
FunctionManager functionManager = currentProgram.getFunctionManager();
Function f = functionManager.getFunctionContaining(addr);
addCfg(cfgs, f, true);
}
private void addCfg(Set<CFG> cfgs, Function f, boolean recurse) {
if (frontend == null) {
initProgram();
}
int depth = recurse ? getOptions().getCfgDepth() : 0;
addFunction(cfgs, f);
addCalledFunctions(cfgs, f, depth);
it.unive.lisa.program.Program p = frontend.getProgram();
Collection<CFG> baseline = p.getAllCFGs();
for (CFG g : cfgs) {
if (baseline.contains(g)) {
p.addEntryPoint(g);
}
}
}
private void addFunction(Set<CFG> cfgs, Function f) {
if (frontend.hasProcessed(f) || monitor.isCancelled()) {
return;
}
Msg.info(this, "Adding "+f);
CFG cfg = frontend.visitFunction(f, f.getEntryPoint());
cfgs.add(cfg);
}
private void addCalledFunctions(Set<CFG> cfgs, Function f, int depth) {
if (depth == 0 || frontend.hasProcessed(f) || monitor.isCancelled()) {
return;
}
Set<Function> calledFunctions = f.getCalledFunctions(new DummyCancellableTaskMonitor());
for (Function func : calledFunctions) {
Address entryPoint = func.getEntryPoint();
if (entryPoint.getAddressSpace().equals(f.getEntryPoint().getAddressSpace())) {
addFunction(cfgs, func);
addCalledFunctions(cfgs, func, depth-1);
}
}
}
private void clearCfgs(ProgramLocationActionContext context) {
initProgram();
frontend.clearTargets();
}
private void setTaint(ProgramLocationActionContext context) {
if (!checkTaintState()) {
return;
}
taintState.clearAnnotations();
BookmarkManager bookmarkManager = currentProgram.getBookmarkManager();
try (Transaction tx = currentProgram.openTransaction("clear bookmark")) {
bookmarkManager.removeBookmarks(BookmarkType.INFO, "Taint Source", TaskMonitor.DUMMY);
}
catch (CancelledException e) {
throw new AssertionError("Unreachable code");
}
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 = vn.getAddress().toString();
}
else if (location instanceof DefaultDecompilerLocation ddl) {
ClangToken token = ddl.getToken();
taintPlugin.toggleIcon(MarkType.SOURCE, token, false);
return; // taint is set via the token
}
else {
AskDialog<String> dialog = new AskDialog<>("Abstract Interpretation Taint", "Varnode address", AskDialog.STRING, lastValue);
if (dialog.isCanceled()) {
return;
}
tokenId = dialog.getValueAsString();
}
try (Transaction tx = currentProgram.openTransaction("set bookmark")) {
bookmarkManager.setBookmark(addr, BookmarkType.INFO, "Taint Source", tokenId);
}
taintState.setTaint(MarkType.SOURCE, f, addr, tokenId);
}
private boolean checkTaintState() {
if (taintState == null) {
// ability to add custom margins to the decompiler view
TaintService service = tool.getService(TaintService.class);
if (service instanceof TaintPlugin taint) {
this.taintPlugin = taint;
TaintState state = taint.getTaintState();
if (state instanceof LisaTaintState ts) {
this.taintState = ts;
return true;
}
}
}
else {
return true;
}
return false;
}
public Map<Function, Collection<?>> performAnalysis(TaskMonitor tm) {
this.monitor = tm;
if (currentFunction == null) {
Msg.error(this, "Not currently in a function");
return null;
}
try {
addCfg(new HashSet<>(), currentFunction, true);
initLisa();
it.unive.lisa.program.Program p = frontend.getProgram();
Map<Function, Collection<?>> combined = new HashMap<>();
if (monitor.isCancelled()) {
return combined;
}
LiSAReport report = lisa.run(p);
ipa = report.getConfiguration().interproceduralAnalysis;
FunctionManager functionManager = currentProgram.getFunctionManager();
Map<Address, CFG> targets = frontend.getTargets();
for (Address entry : targets.keySet()) {
if (monitor.isCancelled()) {
break;
}
Function f = functionManager.getFunctionAt(entry);
CFG cfg = targets.get(entry);
Collection<?> res = ipa.getAnalysisResultsOf(cfg);
combined.put(f, res);
}
return combined;
} catch (AnalysisException e) {
Msg.error(this, e.getMessage());
}
return null;
}
public Collection<Statement> getStatements(Function f) {
addCfg(new HashSet<>(), f, false);
CFG cfg = frontend.getTarget(f.getEntryPoint());
return cfg.getNodes();
}
private void initProgram() {
frontend = new PcodeFrontend();
}
@SuppressWarnings("unchecked")
private void initLisa() {
LisaOptions opt = getOptions();
heapOption = opt.getHeapOption();
typeOption = opt.getTypeOption();
valueOption = opt.getValueOption();
DefaultConfiguration conf = new DefaultConfiguration();
conf.abstractState = DefaultConfiguration.simpleState(
heapOption.getDomain(),
valueOption.getDomain(currentProgram),
typeOption.getDomain());
conf.interproceduralAnalysis = opt.getInterproceduralOption().getAnalysis();
conf.descendingPhaseType = opt.getDescendingPhaseOption().getType();
conf.openCallPolicy = opt.getCallOption().getPolicy();
conf.analysisGraphs = opt.getGraphOption().getType();
conf.callGraph = opt.getCallGraphOption().getCallGraph();
conf.serializeResults = opt.isSerialize();
conf.optimize = opt.isOptimize();
String outputDir = opt.getOutputDir();
if (!outputDir.equals(LisaOptions.DEFAULT_LISA_ANALYSIS_OUTDIR)) {
conf.workdir = outputDir;
}
lisa = new LiSA(conf);
}
private void initOptions() {
ToolOptions opt = tool.getOptions(OPTIONS_TITLE);
getOptions().registerOptions(this, opt, currentProgram);
opt.addOptionsChangeListener(this);
ToolOptions codeBrowserOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS);
codeBrowserOptions.addOptionsChangeListener(this);
}
@Override
public void optionsChanged(ToolOptions opts, String optionName, Object oldValue,
Object newValue) {
if (opts.getName().equals(OPTIONS_TITLE) ||
opts.getName().equals(GhidraOptions.CATEGORY_BROWSER_FIELDS)) {
doRefresh();
}
}
private void doRefresh() {
ToolOptions opt = tool.getOptions(OPTIONS_TITLE);
getOptions().grabFromToolAndProgram(this, opt, currentProgram);
}
@Override
public String getActiveQueryName() {
String queryName = valueOption.toString();
if (!heapOption.equals(HeapDomainOption.DEFAULT)) {
queryName += ":"+heapOption.toString();
}
if (!typeOption.equals(TypeDomainOption.DEFAULT)) {
queryName += ":"+typeOption.toString();
}
if (currentFunction != null) {
queryName += " @ " + currentFunction;
}
return queryName;
}
public LisaOptions getOptions() {
return options;
}
public void setOptions(LisaOptions options) {
this.options = options;
}
}

View File

@@ -0,0 +1,555 @@
/* ###
* 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.lisa.gui;
import java.io.*;
import java.util.*;
import org.apache.commons.lang3.StringUtils;
import com.google.gson.*;
import com.google.gson.stream.JsonWriter;
import ghidra.app.plugin.core.decompiler.absint.AbstractInterpretationService;
import ghidra.app.plugin.core.decompiler.taint.*;
import ghidra.app.script.GhidraScript;
import ghidra.app.services.ConsoleService;
import ghidra.framework.plugintool.PluginTool;
import ghidra.lisa.pcode.analyses.*;
import ghidra.lisa.pcode.locations.InstLocation;
import ghidra.lisa.pcode.locations.PcodeLocation;
import ghidra.program.model.address.Address;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.util.Msg;
import it.unive.lisa.analysis.*;
import it.unive.lisa.analysis.dataflow.*;
import it.unive.lisa.analysis.nonrelational.inference.InferenceSystem;
import it.unive.lisa.analysis.nonrelational.value.TypeEnvironment;
import it.unive.lisa.analysis.nonrelational.value.ValueEnvironment;
import it.unive.lisa.analysis.value.ValueDomain;
import it.unive.lisa.outputs.serializableGraph.*;
import it.unive.lisa.program.annotations.Annotation;
import it.unive.lisa.program.annotations.Annotations;
import it.unive.lisa.program.cfg.statement.*;
import it.unive.lisa.symbolic.value.Identifier;
import it.unive.lisa.util.representation.*;
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 LisaTaintState extends AbstractTaintState {
private LisaPlugin lisa;
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 Map<String, WrappedLogicalLocation> logicalLocations = new HashMap<>();
private Set<VariableRef> annotationSet = new HashSet<>();
private int llIndex = 0;
private boolean suppressTop = true;
private boolean suppressUnique = true;
private Map<String, Map<Integer, String>> registerNames = new HashMap<>();
public record KTV(String key, String type, String value, String displayName) {}
public LisaTaintState(TaintPlugin plugin) {
super(plugin);
ENGINE_NAME = "lisa";
}
private boolean init() {
if (lisa == null) {
AbstractInterpretationService service =
plugin.getTool().getService(AbstractInterpretationService.class);
if (service instanceof LisaPlugin lisaService) {
this.lisa = lisaService;
}
else {
return false;
}
}
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 (!init()) {
return false;
}
taintOptions = plugin.getOptions();
for (TaintLabel mark : getTaintLabels(MarkType.SOURCE)) {
if (mark.isActive()) {
Function f = mark.getHighFunction().getFunction();
Address target = mark.getAddress();
String tokenId = mark.getVarnodeAddress().toString();
setTaint(MarkType.SOURCE, f, target, tokenId);
}
}
for (TaintLabel mark : getTaintLabels(MarkType.SINK)) {
if (mark.isActive()) {
Function f = mark.getHighFunction().getFunction();
Address target = mark.getAddress();
String tokenId = mark.getVarnodeAddress().toString();
setTaint(MarkType.SINK, f, target, tokenId);
}
}
for (TaintLabel mark : getTaintLabels(MarkType.GATE)) {
if (mark.isActive()) {
Function f = mark.getHighFunction().getFunction();
Address target = mark.getAddress();
String tokenId = mark.getVarnodeAddress().toString();
setTaint(MarkType.GATE, f, target, tokenId);
}
}
Map<Function, Collection<?>> results = lisa.performAnalysis(monitor);
if (results != null) {
readQueryResultsIntoDataFrame(program, results);
}
return results != null;
}
public void clearAnnotations() {
for (VariableRef var : annotationSet) {
Collection<Annotation> annotations = var.getAnnotations().getAnnotations();
annotations.clear();
}
}
public void setTaint(MarkType type, Function f, Address target, String tokenId) {
if (!init()) {
return;
}
for (Statement st : lisa.getStatements(f)) {
Address stAddr = null;
if (st.getLocation() instanceof PcodeLocation loc) {
stAddr = loc.getAddress();
}
else if (st.getLocation() instanceof InstLocation loc) {
stAddr = loc.getAddress();
}
if (stAddr.getOffset() >= target.getOffset()) {
if (st instanceof Expression x) {
if (markExpression(type, x, tokenId, stAddr)) {
break;
}
}
}
}
}
private boolean markExpression(MarkType type, Expression x, String tokenId, Address target) {
boolean ret = false;
if (x instanceof VariableRef vref) {
String id = vref.toString();
if (id.equals(tokenId)) {
Annotations annotations = vref.getAnnotations();
Annotation ann = type == MarkType.SOURCE ? new Annotation("Tainted@" + target)
: new Annotation("Clean@" + target);
annotations.addAnnotation(ann);
annotationSet.add(vref);
return true;
}
}
if (x instanceof NaryExpression nx) {
Expression[] subx = nx.getSubExpressions();
for (Expression sx : subx) {
ret |= markExpression(type, sx, tokenId, target);
}
}
return ret;
}
private Writer getWriter() {
return new StringWriter(10000);
}
protected void readQueryResultsIntoDataFrame(Program program,
Map<Function, Collection<?>> res) {
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);
LisaOptions options = lisa.getOptions();
suppressTop = !options.isShowTop();
suppressUnique = !options.isShowUnique();
registerNames = generateRegisterMap(program);
for (Function f : res.keySet()) {
Collection<?> c = res.get(f);
Iterator<?> iteratorC = c.iterator();
while (iteratorC.hasNext() && !monitor.isCancelled()) {
Object next = iteratorC.next();
if (next instanceof AnalyzedCFG<?> acfg) {
writeResults(program, f, results, llocs, acfg);
}
}
}
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", "lisa");
driver.addProperty("version", "0.1");
driver.addProperty("informationUri", "https://github.com/lisa-analyzer");
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 writeResults(Program program, Function f, JsonArray results, JsonArray llocs,
AnalyzedCFG<?> acfg) {
try {
for (Statement st : lisa.getStatements(f)) {
AbstractState<?> state =
lisa.getOptions().isPostState() ? acfg.getAnalysisStateAfter(st).getState()
: acfg.getAnalysisStateBefore(st).getState();
if (state instanceof SimpleAbstractState sas) {
processSimpleState(results, llocs, f, st, sas);
}
if (monitor.isCancelled()) {
break;
}
}
}
catch (Exception e) {
Msg.error(this, e.getMessage());
}
}
@SuppressWarnings("rawtypes")
private void processSimpleState(JsonArray results, JsonArray llocs, Function f, Statement st,
SimpleAbstractState sas) {
Map<Identifier, ?> tfunction = getTypeMap(sas);
ValueDomain valueState = sas.getValueState();
if (valueState instanceof PcodeStability stab) {
valueState = stab.getTrends();
}
if (valueState instanceof PcodePentagon pent) {
valueState = pent.getIntervals();
}
if (valueState instanceof ValueEnvironment ve) {
@SuppressWarnings("unchecked")
Map<Identifier, ?> vfunction = ve.function;
if (vfunction == null) {
return;
}
for (Object key : vfunction.keySet()) {
if (vfunction.get(key) instanceof StructuredObject vso) {
KTV ktv = repToStrings(key.toString(), vso.representation(), tfunction);
processKeyValue(results, llocs, f, st, ktv);
}
}
}
if (valueState instanceof InferenceSystem is) {
@SuppressWarnings("unchecked")
Map<Identifier, ?> vfunction = is.function;
if (vfunction == null) {
return;
}
for (Object key : vfunction.keySet()) {
if (vfunction.get(key) instanceof StructuredObject vso) {
KTV ktv = repToStrings(key.toString(), vso.representation(), tfunction);
processKeyValue(results, llocs, f, st, ktv);
}
}
}
if (valueState instanceof DefiniteDataflowDomain ddd) {
for (Object object : ddd.getDataflowElements()) {
StructuredRepresentation rep = null;
if (object instanceof PcodeDataflowConstantPropagation cp) {
rep = cp.representation();
}
if (object instanceof AvailableExpressions ae) {
rep = ae.representation();
}
KTV ktv = repToStrings(null, rep, tfunction);
processKeyValue(results, llocs, f, st, ktv);
}
}
if (valueState instanceof PossibleDataflowDomain pdd) {
for (Object object : pdd.getDataflowElements()) {
StructuredRepresentation rep = null;
if (object instanceof ReachingDefinitions rd) {
rep = rd.representation();
}
if (object instanceof Liveness lv) {
rep = lv.representation();
}
KTV ktv = repToStrings(null, rep, tfunction);
processKeyValue(results, llocs, f, st, ktv);
}
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private Map<Identifier, ?> getTypeMap(SimpleAbstractState sas) {
if (sas.getTypeState() instanceof TypeEnvironment te) {
return te.function;
}
return null;
}
private KTV repToStrings(String key, StructuredRepresentation rep, Map<Identifier, ?> typeMap) {
String val = "";
if (rep instanceof StringRepresentation srep) {
String[] split = srep.toString().split(" ");
if (key == null) {
key = split.length == 2 ? split[1] : split[0];
}
val = srep.toString();
}
if (rep instanceof SetRepresentation srep) {
SerializableValue sval = srep.toSerializableValue();
if (sval instanceof SerializableArray sarr) {
List<SerializableValue> elements = sarr.getElements();
val = ((SerializableString) elements.get(0)).getValue();
if (elements.size() > 1) {
Msg.error(this, "Unexpected result: " + sval);
}
}
}
if (rep instanceof ListRepresentation lrep) {
SerializableValue sval = lrep.toSerializableValue();
if (sval instanceof SerializableArray sarr) {
List<SerializableValue> elements = sarr.getElements();
if (key == null) {
key = ((SerializableString) elements.get(0)).getValue();
}
val = ((SerializableString) elements.get(1)).getValue();
if (elements.size() > 2) {
Msg.error(this, "Unexpected result: " + sval);
}
}
}
if (StringUtils.isNumeric(val)) {
val = Long.toHexString(Long.parseLong(val));
}
String type = "";
for (Identifier k : typeMap.keySet()) {
if (k.toString().equals(key)) {
type = typeMap.get(k).toString();
break;
}
}
String displayName = generateDisplayName(key, type);
return new KTV(key, type, val, displayName);
}
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 void processKeyValue(JsonArray results, JsonArray llocs, Function f, Statement st,
KTV ktv) {
boolean isTop = ktv.value.contains(LisaOptions.getTopValue());
boolean isUnique = ktv.key.contains("unique:");
if (!(isTop && suppressTop) && !(isUnique && suppressUnique)) {
writeResult(ktv, f, st, llocs, results);
if (st.getLocation() instanceof PcodeLocation ploc) {
taintAddressSet.add(ploc.getAddress());
}
if (st.getLocation() instanceof InstLocation iloc) {
taintAddressSet.add(iloc.getAddress());
}
}
}
private void writeResult(KTV ktv, Function f, Statement st, JsonArray llocs,
JsonArray results) {
try {
SarifWriterTask task;
SarifLogicalLocationWriter locWriter = new SarifLogicalLocationWriter(ktv.key, f, st);
WrappedLogicalLocation wll = locWriter.getLogicalLocation();
String llkey = wll.getLogicalLocation().getFullyQualfiedName();
if (!logicalLocations.containsKey(llkey)) {
wll.setIndex(llIndex++);
logicalLocations.put(llkey, wll);
task = new SarifWriterTask(lisa.valueOption.toString(), locWriter, llocs);
task.monitoredRun(monitor);
}
wll = logicalLocations.get(llkey);
SarifKeyValueWriter writer = new SarifKeyValueWriter(ktv, wll);
task = new SarifWriterTask(lisa.valueOption.toString(), writer, results);
task.monitoredRun(monitor);
}
catch (IOException e) {
Msg.error(this, e.getMessage());
}
}
@Override
public String getQueryName() {
return lisa.getActiveQueryName();
}
@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
}
public void setSuppressTop(boolean suppressTop) {
this.suppressTop = suppressTop;
}
public void setSuppressUnique(boolean suppressUnique) {
this.suppressUnique = suppressUnique;
}
}

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.lisa.gui;
import java.io.IOException;
import ghidra.lisa.gui.LisaTaintState.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,76 @@
/* ###
* 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.lisa.gui;
import java.io.IOException;
import ghidra.lisa.pcode.locations.InstLocation;
import ghidra.lisa.pcode.locations.PcodeLocation;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function;
import ghidra.program.model.pcode.SequenceNumber;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import it.unive.lisa.program.cfg.statement.Statement;
import sarif.export.*;
public class SarifLogicalLocationWriter extends AbstractExtWriter {
private PcodeLocation location;
private WrappedLogicalLocation lloc;
public SarifLogicalLocationWriter(String key, Function f, Statement statement)
throws IOException {
super(null);
Address addr;
String loc, op;
if (statement.getLocation() instanceof PcodeLocation ploc) {
this.location = ploc;
SequenceNumber seqnum = location.op.getSeqnum();
String seq = seqnum.getTarget() + ":" + seqnum.getTime();
loc = f.getName();
loc += "@" + f.getEntryPoint();
loc += ":" + seq;
addr = location.getAddress();
op = seq + " " + location.op.toString();
}
else {
loc = f.getName();
InstLocation instLoc = (InstLocation) statement.getLocation();
addr = instLoc.getAddress();
loc += "@" + addr;
op = instLoc.toString();
}
ExtLogicalLocation ext = new ExtLogicalLocation(key, f, loc, op);
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;
}
}

View File

@@ -0,0 +1,87 @@
/* ###
* 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.lisa.pcode;
import java.util.*;
import ghidra.lisa.pcode.WorkItem.PredType;
import it.unive.lisa.program.cfg.CFG;
import it.unive.lisa.program.cfg.controlFlow.ControlFlowStructure;
import it.unive.lisa.program.cfg.edge.Edge;
import it.unive.lisa.program.cfg.statement.Statement;
import it.unive.lisa.util.datastructures.graph.code.NodeList;
public class PcodeBranch extends ControlFlowStructure {
private Statement branch;
private Statement fallThrough;
protected PcodeBranch(NodeList<CFG, Statement, Edge> cfgMatrix, Statement condition) {
super(cfgMatrix, condition, null);
}
@Override
protected Collection<Statement> bodyStatements() {
Collection<Statement> all = new HashSet<>(getTrueBranch());
all.addAll(getFalseBranch());
return all;
}
private Collection<Statement> getFalseBranch() {
if (fallThrough == null) {
return Set.of();
}
return new HashSet<>(cfgMatrix.followersOf(fallThrough));
}
private Collection<Statement> getTrueBranch() {
if (branch == null) {
return Set.of();
}
return new HashSet<>(cfgMatrix.followersOf(branch));
}
@Override
public boolean contains(Statement st) {
return bodyStatements().contains(st);
}
@Override
public void simplify() {
// Nothing required here
}
@Override
public String toString() {
return "if-then-else[" + getCondition() + "]";
}
@Override
public Collection<Statement> getTargetedStatements() {
return bodyStatements();
}
public void addStatement(Statement st, PredType type) {
if (type.equals(PredType.TRUE)) {
branch = st;
}
else {
fallThrough = st;
setFirstFollower(st);
}
}
}

View File

@@ -0,0 +1,391 @@
/* ###
* 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.lisa.pcode;
import java.util.*;
import org.apache.commons.lang3.tuple.Pair;
import ghidra.lisa.pcode.WorkItem.PredType;
import ghidra.lisa.pcode.contexts.*;
import ghidra.lisa.pcode.expressions.*;
import ghidra.lisa.pcode.statements.PcodeNop;
import ghidra.program.model.listing.Listing;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.SequenceNumber;
import it.unive.lisa.program.annotations.Annotations;
import it.unive.lisa.program.cfg.*;
import it.unive.lisa.program.cfg.controlFlow.ControlFlowStructure;
import it.unive.lisa.program.cfg.edge.Edge;
import it.unive.lisa.program.cfg.edge.SequentialEdge;
import it.unive.lisa.program.cfg.statement.*;
import it.unive.lisa.program.cfg.statement.comparison.Equal;
import it.unive.lisa.program.cfg.statement.literal.*;
import it.unive.lisa.program.type.BoolType;
import it.unive.lisa.type.Type;
import it.unive.lisa.type.Untyped;
import it.unive.lisa.util.datastructures.graph.code.NodeList;
/**
* An {@link PcodeCodeMemberVisitor} that will parse the pcode of an function
*
*/
public class PcodeCodeMemberVisitor {
private final NodeList<CFG, Statement, Edge> list;
private final Collection<Statement> entrypoints;
private final Collection<ControlFlowStructure> cfs;
private final Map<String, Pair<VariableRef, Annotations>> visibleIds;
private final CFG cfg;
private final CodeMemberDescriptor descriptor;
private Listing listing;
private final Collection<String> visited;
private final Map<SequenceNumber, Statement> visitedPcode;
private Stack<WorkItem> workItems;
private UnitContext currentUnit;
private Map<String, PcodeBranch> flows;
private int varCount = 0;
/**
* Builds the visitor of an IMP method or constructor.
*
* @param descriptor the descriptor of the method or constructor
* @param listing the program listing
*/
PcodeCodeMemberVisitor(CodeMemberDescriptor descriptor, Listing listing) {
this.descriptor = descriptor;
this.listing = listing;
list = new NodeList<>(new SequentialEdge());
entrypoints = new HashSet<>();
visited = new HashSet<>();
visitedPcode = new HashMap<>();
cfs = new LinkedList<>();
// side effects on entrypoints and matrix will affect the cfg
cfg = new CFG(descriptor, entrypoints, list);
this.flows = new HashMap<>();
visibleIds = new HashMap<>();
for (VariableTableEntry par : descriptor.getVariables()) {
visibleIds.put(par.getName(), Pair.of(par.createReference(cfg), par.getAnnotations()));
}
}
/**
* Visits the code of a {@link UnitContext} representing the code block of
* a method or constructor.
*
* @param ctx the block context
*
* @return the {@link CFG} built from the block
*/
CFG visitCodeMember(UnitContext ctx) {
this.currentUnit = ctx;
InstructionContext entry = ctx.entry();
if (entry == null) {
throw new RuntimeException("No entry for " + ctx.function());
}
while (entry.getPcodeOps().isEmpty()) {
entry = entry.next();
}
visited.clear();
visitedPcode.clear();
workItems = new Stack<>();
workItems.add(new WorkItem(null, entry.getPcodeOp(0)));
while (!workItems.isEmpty()) {
processWorkItem(workItems.pop());
}
visitBlock(entry);
cfs.forEach(cf -> cfg.addControlFlowStructure(cf));
Ret ret = new Ret(cfg, descriptor.getLocation());
if (cfg.getNodesCount() == 0) {
// empty method, so the ret is also the entrypoint
list.addNode(ret);
entrypoints.add(ret);
}
else {
// every non-throwing instruction that does not have a follower
// is ending the method
Collection<Statement> preExits = new LinkedList<>();
for (Statement st : list.getNodes())
if (!st.stopsExecution() && list.followersOf(st).isEmpty())
preExits.add(st);
list.addNode(ret);
for (Statement st : preExits)
list.addEdge(new SequentialEdge(st, ret));
for (VariableTableEntry vte : descriptor.getVariables())
if (preExits.contains(vte.getScopeEnd()))
vte.setScopeEnd(ret);
}
cfg.simplify();
return cfg;
}
public void visitBlock(InstructionContext entry) {
if (entry == null) {
return;
}
while (entry.getPcodeOps().isEmpty()) {
entry = entry.next();
}
visited.clear();
visitedPcode.clear();
workItems = new Stack<>();
workItems.add(new WorkItem(null, entry.getPcodeOp(0)));
while (!workItems.isEmpty()) {
processWorkItem(workItems.pop());
}
}
private void processWorkItem(WorkItem item) {
StatementContext ctx = item.getContext();
Statement st = visitPcodeOp(ctx);
if (st == null) {
return;
}
if (visited.contains(item.getKey())) {
return;
}
Statement pred = item.getPred();
boolean entrypoint = pred == null;
cfg.addNode(st, entrypoint);
if (!entrypoint) {
Edge e = item.computeBranch(st);
if (e != null) {
cfg.addEdge(e);
}
PredType type = item.getType();
if (!type.equals(PredType.SEQ)) {
String loc = pred.getLocation().getCodeLocation();
PcodeBranch flow = flows.get(loc);
if (flow == null) {
flow = new PcodeBranch(cfg.getNodeList(), pred);
cfg.addControlFlowStructure(flow);
}
flow.addStatement(st, type);
flows.put(loc, flow);
}
}
else {
entrypoints.add(st);
}
if (st instanceof Ret || st instanceof Return) {
return;
}
List<StatementContext> branches = ctx.branch(this.listing, this.currentUnit);
for (StatementContext branch : branches) {
WorkItem n = new WorkItem(st, branch);
if (ctx.isConditional()) {
n.setType(true);
}
workItems.add(n);
}
StatementContext next = ctx.next(this.listing);
if (next != null) {
WorkItem n = new WorkItem(st, next);
if (ctx.isBranch()) {
if (ctx.isConditional()) {
n.setType(false);
workItems.add(n);
}
}
else {
workItems.add(n);
}
}
visited.add(item.getKey());
}
public Statement visitPcodeOp(StatementContext ctx) {
SequenceNumber key = ctx.getOp().getSeqnum();
if (visitedPcode.containsKey(key)) {
return visitedPcode.get(key);
}
Statement st;
if (ctx.isRet()) {
if (ctx.expression() != null) {
st = new Return(cfg, ctx.location(), visitExpression(ctx));
}
else {
st = new Ret(cfg, ctx.location());
}
}
else if (ctx.isBranch()) {
if (ctx.isConditional()) {
st = visitCondition(ctx.condition());
}
else {
st = new PcodeNop(cfg, ctx.location()); // Treating these as a NOP
}
}
else if (ctx.expression() != null) {
st = visitExpression(ctx);
}
else
throw new IllegalArgumentException(
"Statement '" + ctx.toString() + "' cannot be parsed");
visitedPcode.put(key, st);
return st;
}
public Statement visitCondition(
ConditionContext ctx) {
VarnodeContext expression = ctx.expression();
CodeLocation loc = ctx.location();
if (expression == null) {
return new NoOp(cfg, loc);
}
//return visitVarnode(ctx.location(), expression, BoolType.INSTANCE, false);
Expression left = visitVarnode(loc, expression, BoolType.INSTANCE, false);
return new Equal(cfg, loc, left, new TrueLiteral(cfg, loc));
}
public Expression visitExpression(StatementContext ctx) {
CodeLocation loc = ctx.location();
VarDefContext left = ctx.target();
PcodeContext right = ctx.expression();
int opcode = ctx.opcode();
switch (opcode) {
case PcodeOp.COPY -> {
Expression target = visitVariable(loc, left, true);
Expression expression = visitVarnode(loc, right.basicExpr(), false);
return new Assignment(cfg, loc, target, expression);
}
case PcodeOp.FLOAT_INT2FLOAT, PcodeOp.FLOAT_FLOAT2FLOAT -> {
Expression target = visitVariable(loc, left, true);
Expression expression = visitBinaryExpr(new BinaryExprContext(ctx));
return new Assignment(cfg, loc, target, expression);
}
case PcodeOp.CALL, PcodeOp.CALLIND, PcodeOp.CALLOTHER -> {
return visitCallExpr(new CallContext(ctx.getOp(), currentUnit));
}
case PcodeOp.RETURN -> {
return visitVarnode(loc, right.basicExpr(), false);
}
case PcodeOp.LOAD -> {
MemLocContext mem = new MemLocContext(ctx);
Expression target = visitVariable(loc, left, true);
Expression expression = visitVarnode(loc, mem, false);
return new Assignment(cfg, loc, target, expression);
}
case PcodeOp.STORE -> {
MemLocContext mem = new MemLocContext(ctx);
Expression target = visitVariable(loc, mem, true);
Expression expression = visitVarnode(loc, left, false);
return new Assignment(cfg, loc, target, expression);
}
}
if (right == null) {
throw new UnsupportedOperationException("Type of expression not supported: " + ctx);
}
return switch (right.getNumInputs()) {
case 1 -> {
Expression target = visitVariable(loc, left, true);
Expression expression = visitUnaryExpr(new UnaryExprContext(right));
yield new Assignment(cfg, loc, target, expression);
}
case 2 -> {
Expression target = visitVariable(loc, left, true);
Expression expression = visitBinaryExpr(new BinaryExprContext(right));
yield new Assignment(cfg, loc, target, expression);
}
default -> throw new UnsupportedOperationException(
"Type of expression not supported: " + ctx);
};
}
public Expression visitCallExpr(CallContext ctx) {
CodeLocation loc = ctx.location();
Expression lexp = visitVarnode(loc, ctx.left, false);
return new PcodeCallExpression(cfg, ctx, lexp);
}
public Expression visitUnaryExpr(UnaryExprContext ctx) {
CodeLocation loc = ctx.location();
Expression lexp = visitVarnode(loc, ctx.arg, false);
return new PcodeUnaryExpression(cfg, ctx, lexp);
}
public Expression visitBinaryExpr(BinaryExprContext ctx) {
CodeLocation loc = ctx.location();
Expression lexp = visitVariable(loc, ctx.left, false);
Expression rexp = visitVarnode(loc, ctx.right, false);
return new PcodeBinaryExpression(cfg, ctx, lexp, rexp);
}
public Expression visitVarnode(CodeLocation loc, VarnodeContext ctx, Type type,
boolean define) {
if (ctx.isConstant()) {
return visitConstant(loc, ctx);
}
return visitVariable(loc, ctx, type, define);
}
public Expression visitVarnode(CodeLocation loc, VarnodeContext ctx, boolean define) {
return visitVarnode(loc, ctx, Untyped.INSTANCE, define);
}
public Literal<?> visitConstant(CodeLocation loc, VarnodeContext ctx) {
return switch (ctx.getSize()) {
case 0 -> new NullLiteral(cfg, loc);
case 1 -> new Int8Literal(cfg, loc, (byte) ctx.getOffset());
case 2 -> new Int16Literal(cfg, loc, (short) ctx.getOffset());
case 4 -> new Int32Literal(cfg, loc, (int) ctx.getOffset());
case 8 -> new Int64Literal(cfg, loc, ctx.getOffset());
default -> new Int64Literal(cfg, loc, ctx.getOffset()); // FIXME
// default -> throw new UnsupportedOperationException(
// "Type of literal not supported: " + ctx);
};
}
private VariableRef visitVariable(CodeLocation loc, VarnodeContext ctx, boolean define) {
return visitVariable(loc, ctx, Untyped.INSTANCE, define);
}
private VariableRef visitVariable(CodeLocation loc, VarnodeContext ctx, Type type,
boolean define) {
VariableRef ref = new VariableRef(cfg, loc,
ctx.getText(), type);
if (!visibleIds.containsKey(ref.getName())) {
visibleIds.put(ref.getName(), Pair.of(ref, new Annotations()));
descriptor.addVariable(new VariableTableEntry(loc, varCount++, null, null,
ref.getName(), type));
}
return ref;
}
public Map<String, PcodeBranch> getFlows() {
return flows;
}
}

View File

@@ -0,0 +1,55 @@
/* ###
* 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.lisa.pcode;
import it.unive.lisa.program.language.LanguageFeatures;
import it.unive.lisa.program.language.hierarchytraversal.HierarcyTraversalStrategy;
import it.unive.lisa.program.language.hierarchytraversal.SingleInheritanceTraversalStrategy;
import it.unive.lisa.program.language.parameterassignment.ParameterAssigningStrategy;
import it.unive.lisa.program.language.parameterassignment.PythonLikeAssigningStrategy;
import it.unive.lisa.program.language.resolution.JavaLikeMatchingStrategy;
import it.unive.lisa.program.language.resolution.ParameterMatchingStrategy;
import it.unive.lisa.program.language.validation.BaseValidationLogic;
import it.unive.lisa.program.language.validation.ProgramValidationLogic;
/**
* Pcode's {@link LanguageFeatures} implementation.
*
*/
public class PcodeFeatures extends LanguageFeatures {
// For the PcodeFrontend, most of the strategies are probably not relevant.
@Override
public ParameterMatchingStrategy getMatchingStrategy() {
return JavaLikeMatchingStrategy.INSTANCE;
}
@Override
public HierarcyTraversalStrategy getTraversalStrategy() {
return SingleInheritanceTraversalStrategy.INSTANCE;
}
@Override
public ParameterAssigningStrategy getAssigningStrategy() {
return PythonLikeAssigningStrategy.INSTANCE;
}
@Override
public ProgramValidationLogic getProgramValidationLogic() {
return new BaseValidationLogic();
}
}

View File

@@ -0,0 +1,159 @@
/* ###
* 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.lisa.pcode;
import java.util.*;
import ghidra.lisa.pcode.contexts.UnitContext;
import ghidra.lisa.pcode.locations.PcodeLocation;
import ghidra.lisa.pcode.types.PcodeTypeSystem;
import ghidra.program.model.address.Address;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.listing.*;
import it.unive.lisa.program.Program;
import it.unive.lisa.program.cfg.*;
import it.unive.lisa.program.cfg.Parameter;
import it.unive.lisa.program.cfg.statement.Statement;
import it.unive.lisa.type.Untyped;
/**
* Instantiated {@link PcodeCodeMemberVisitor} that will parse the pcode building a
* representation that can be analyzed through LiSA.
*/
public class PcodeFrontend {
//private static final Logger log = LogManager.getLogger(PcodeFrontend.class);
private final Program program;
private Map<Address, Set<Statement>> nodeMap = new HashMap<>();
private Map<Address, CFG> targets = new HashMap<>();
private Set<CFG> cfgs = new HashSet<>();
public PcodeFrontend() {
program = new Program(new PcodeFeatures(), new PcodeTypeSystem());
}
public Program doWork(Listing listing, Address startAddress) {
Program p = visitListing(listing, startAddress);
Collection<CFG> baseline = p.getAllCFGs();
for (CFG cfg : cfgs) {
if (baseline.contains(cfg)) {
p.addEntryPoint(cfg);
}
}
return p;
}
public Program visitListing(Listing listing, Address startAddress) {
Program p = getProgram();
for (Function f : listing.getFunctions(startAddress, false)) {
CFG cfg = visitFunction(f, startAddress);
cfgs.add(cfg);
}
return p;
}
public CFG visitFunction(Function f, Address start) {
Program p = getProgram();
UnitContext ctx = new UnitContext(this, program, f, start);
CodeMemberDescriptor descr = mkDescriptor(ctx);
PcodeCodeMemberVisitor visitor =
new PcodeCodeMemberVisitor(descr, ctx.getListing());
CFG cfg = visitor.visitCodeMember(ctx);
targets.put(f.getEntryPoint(), cfg);
cfgs.add(cfg);
ctx.unit().addCodeMember(cfg);
p.addUnit(ctx.unit());
Collection<Statement> nodes = cfg.getNodes();
for (Statement statement : nodes) {
Address addr = toAddr(statement.getLocation());
nodeMap.computeIfAbsent(addr, a -> new HashSet<>()).add(statement);
}
return cfg;
}
private Address toAddr(CodeLocation location) {
if (location instanceof PcodeLocation loc) {
return loc.op.getSeqnum().getTarget();
}
return null;
}
private CodeMemberDescriptor mkDescriptor(UnitContext ctx) {
Parameter[] params = computeParameters(ctx);
CodeMemberDescriptor descriptor = new CodeMemberDescriptor(
ctx.location(),
ctx.unit(),
false, ctx.getText(), Untyped.INSTANCE,
params);
descriptor.setOverridable(!ctx.isFinal());
return descriptor;
}
private Parameter[] computeParameters(UnitContext ctx) {
Function f = ctx.function();
ProgramContext programContext = f.getProgram().getProgramContext();
Set<Parameter> pset = new HashSet<>();
for (Register r : programContext.getRegisters()) {
RegisterValue rv = programContext.getRegisterValue(r, f.getEntryPoint());
if (rv != null && rv.hasValue()) {
Parameter p = new Parameter(ctx.location(), r.getAddress().toString());
pset.add(p);
}
}
Parameter[] params = new Parameter[pset.size() + 1];
int index = 0;
params[index++] = new Parameter(ctx.location(), ctx.getText());
for (Parameter p : pset) {
params[index++] = p;
}
return params;
}
public Program getProgram() {
return program;
}
public Set<Statement> getStatement(Address addr) {
return nodeMap.get(addr);
}
public boolean hasProcessed(Function f) {
if (f == null) {
return false;
}
return targets.containsKey(f.getEntryPoint());
}
public void clearTargets() {
targets.clear();
}
public CFG getTarget(Address entryPoint) {
return targets.get(entryPoint);
}
public Map<Address, CFG> getTargets() {
return targets;
}
}

View File

@@ -0,0 +1,78 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.lisa.pcode;
import ghidra.lisa.pcode.contexts.StatementContext;
import ghidra.lisa.pcode.locations.PcodeLocation;
import ghidra.program.model.pcode.PcodeOp;
import it.unive.lisa.program.cfg.edge.*;
import it.unive.lisa.program.cfg.statement.Statement;
public class WorkItem {
public enum PredType {
TRUE,
FALSE,
SEQ
}
private Statement pred;
private PredType type;
private StatementContext context;
public WorkItem(Statement pred, StatementContext ctx) {
this.pred = pred;
this.context = ctx;
this.type = PredType.SEQ;
}
public StatementContext getContext() {
return context;
}
public Statement getPred() {
return pred;
}
public void setType(boolean val) {
type = val ? PredType.TRUE : PredType.FALSE;
}
public Edge computeBranch(Statement succ) {
PcodeLocation loc = (PcodeLocation) pred.getLocation();
if (loc.getOpcode() == PcodeOp.RETURN) {
return null;
}
return switch (getType()) {
case TRUE -> new TrueEdge(pred, succ);
case FALSE -> new FalseEdge(pred, succ);
case SEQ -> new SequentialEdge(pred, succ);
default -> throw new IllegalArgumentException("Unexpected value: " + getType());
};
}
public PredType getType() {
return type;
}
public String getKey() {
String key = context.getOp().getSeqnum().toString();
if (pred != null) {
key = pred.getLocation().getCodeLocation() + "=>" + key;
}
return key;
}
}

View File

@@ -0,0 +1,453 @@
/* ###
* IP: MIT
*/
package ghidra.lisa.pcode.analyses;
import java.math.BigDecimal;
import java.util.Iterator;
import it.unive.lisa.util.numeric.*;
/**
* An interval with long bounds.
*
* <p>
* Modified to handle pcode from original source written by:
* <p>
* @author <a href="mailto:luca.negrini@unive.it">Luca Negrini</a>
*/
public class LongInterval implements Iterable<Long>, Comparable<LongInterval> {
/**
* The interval {@code [-Inf, +Inf]}.
*/
public static final LongInterval INFINITY = new LongInterval();
/**
* The interval {@code [0, 0]}.
*/
public static final LongInterval ZERO = new LongInterval(0, 0);
/**
* The interval {@code [1, 1]}.
*/
public static final LongInterval ONE = new LongInterval(1, 1);
/**
* The interval {@code [-1, -1]}.
*/
public static final LongInterval MINUS_ONE = new LongInterval(-1, -1);
private final MathNumber low;
private final MathNumber high;
private LongInterval() {
this(MathNumber.MINUS_INFINITY, MathNumber.PLUS_INFINITY);
}
/**
* Builds a new interval. Order of the bounds is adjusted (i.e., if
* {@code low} is greater then {@code high}, then the interval
* {@code [high, low]} is created).
*
* @param low the lower bound
* @param high the upper bound
*/
public LongInterval(long low, long high) {
this(new MathNumber(low), new MathNumber(high));
}
/**
* Builds a new interval. Order of the bounds is adjusted (i.e., if
* {@code low} is greater then {@code high}, then the interval
* {@code [high, low]} is created).
*
* @param low the lower bound (if {@code null}, -inf will be used)
* @param high the upper bound (if {@code null}, +inf will be used)
*/
public LongInterval(
Integer low,
Integer high) {
this(low == null ? MathNumber.MINUS_INFINITY : new MathNumber(low),
high == null ? MathNumber.PLUS_INFINITY : new MathNumber(high));
}
/**
* Builds a new interval. Order of the bounds is adjusted (i.e., if
* {@code low} is greater then {@code high}, then the interval
* {@code [high, low]} is created).
*
* @param low the lower bound
* @param high the upper bound
*/
public LongInterval(
MathNumber low,
MathNumber high) {
if (low.isNaN() || high.isNaN()) {
this.low = MathNumber.NaN;
this.high = MathNumber.NaN;
}
else if (low.compareTo(high) <= 0) {
this.low = low;
this.high = high;
}
else {
this.low = high;
this.high = low;
}
}
/**
* Yields the upper bound of this interval.
*
* @return the upper bound of this interval
*/
public MathNumber getHigh() {
return high;
}
/**
* Yields the lower bound of this interval.
*
* @return the lower bound of this interval
*/
public MathNumber getLow() {
return low;
}
/**
* Yields {@code true} if the lower bound of this interval is set to minus
* infinity.
*
* @return {@code true} if that condition holds
*/
public boolean lowIsMinusInfinity() {
return low.isMinusInfinity();
}
/**
* Yields {@code true} if the upper bound of this interval is set to plus
* infinity.
*
* @return {@code true} if that condition holds
*/
public boolean highIsPlusInfinity() {
return high.isPlusInfinity();
}
/**
* Yields {@code true} if this is interval is not finite, that is, if at
* least one bound is set to infinity.
*
* @return {@code true} if that condition holds
*/
public boolean isInfinite() {
return this == INFINITY || (highIsPlusInfinity() || lowIsMinusInfinity());
}
/**
* Yields {@code true} if this is interval is finite, that is, if neither
* bound is set to infinity.
*
* @return {@code true} if that condition holds
*/
public boolean isFinite() {
return !isInfinite();
}
/**
* Yields {@code true} if this is the interval representing infinity, that
* is, {@code [-Inf, +Inf]}.
*
* @return {@code true} if that condition holds
*/
public boolean isInfinity() {
return this == INFINITY;
}
/**
* Yields {@code true} if this is a singleton interval, that is, if the
* lower bound and the upper bound are the same.
*
* @return {@code true} if that condition holds
*/
public boolean isSingleton() {
return isFinite() && low.equals(high);
}
/**
* Yields {@code true} if this is a singleton interval containing only
* {@code n}.
*
* @param n the integer to test
*
* @return {@code true} if that condition holds
*/
public boolean is(long n) {
BigDecimal number = low.getNumber();
return isSingleton() && number != null && number.equals(new BigDecimal(n));
}
private static LongInterval cacheAndRound(
LongInterval i) {
if (i.is(0)) {
return ZERO;
}
if (i.is(1)) {
return ONE;
}
if (i.is(-1)) {
return MINUS_ONE;
}
return new LongInterval(i.low.roundDown(), i.high.roundUp());
}
/**
* Performs the interval addition between {@code this} and {@code other}.
*
* @param other the other interval
*
* @return {@code this + other}
*/
public LongInterval plus(
LongInterval other) {
if (isInfinity() || other.isInfinity()) {
return INFINITY;
}
return cacheAndRound(new LongInterval(low.add(other.low), high.add(other.high)));
}
/**
* Performs the interval subtraction between {@code this} and {@code other}.
*
* @param other the other interval
*
* @return {@code this - other}
*/
public LongInterval diff(
LongInterval other) {
if (isInfinity() || other.isInfinity()) {
return INFINITY;
}
return cacheAndRound(new LongInterval(low.subtract(other.high), high.subtract(other.low)));
}
private static MathNumber min(
MathNumber... nums) {
if (nums.length == 0) {
throw new IllegalArgumentException("No numbers provided");
}
MathNumber min = nums[0];
for (int i = 1; i < nums.length; i++) {
min = min.min(nums[i]);
}
return min;
}
private static MathNumber max(
MathNumber... nums) {
if (nums.length == 0) {
throw new IllegalArgumentException("No numbers provided");
}
MathNumber max = nums[0];
for (int i = 1; i < nums.length; i++) {
max = max.max(nums[i]);
}
return max;
}
/**
* Performs the interval multiplication between {@code this} and
* {@code other}.
*
* @param other the other interval
*
* @return {@code this * other}
*/
public LongInterval mul(
LongInterval other) {
if (is(0) || other.is(0)) {
return ZERO;
}
if (isInfinity() || other.isInfinity()) {
return INFINITY;
}
if (low.compareTo(MathNumber.ZERO) >= 0 && other.low.compareTo(MathNumber.ZERO) >= 0) {
return cacheAndRound(
new LongInterval(low.multiply(other.low), high.multiply(other.high)));
}
MathNumber ll = low.multiply(other.low);
MathNumber lh = low.multiply(other.high);
MathNumber hl = high.multiply(other.low);
MathNumber hh = high.multiply(other.high);
return cacheAndRound(new LongInterval(min(ll, lh, hl, hh), max(ll, lh, hl, hh)));
}
/**
* Performs the interval division between {@code this} and {@code other}.
*
* @param other the other interval
* @param ignoreZero if {@code true}, causes the division to ignore the
* fact that {@code other} might contain 0, producing
* a smaller result
* @param errorOnZero whether or not an {@link ArithmeticException} should
* be thrown immediately if {@code other} contains
* zero
*
* @return {@code this / other}
*
* @throws ArithmeticException if {@code other} contains 0 and
* {@code errorOnZero} is set to
* {@code true}
*/
public LongInterval div(
LongInterval other,
boolean ignoreZero,
boolean errorOnZero) {
if (errorOnZero && (other.is(0) || other.includes(ZERO))) {
throw new ArithmeticException("IntInterval divide by zero");
}
if (is(0)) {
return ZERO;
}
if (!other.includes(ZERO)) {
return mul(new LongInterval(MathNumber.ONE.divide(other.high),
MathNumber.ONE.divide(other.low)));
}
else if (other.high.isZero()) {
return mul(
new LongInterval(MathNumber.MINUS_INFINITY, MathNumber.ONE.divide(other.low)));
}
else if (other.low.isZero()) {
return mul(
new LongInterval(MathNumber.ONE.divide(other.high), MathNumber.PLUS_INFINITY));
}
else if (ignoreZero) {
return mul(new LongInterval(MathNumber.ONE.divide(other.low),
MathNumber.ONE.divide(other.high)));
}
else {
LongInterval lower =
mul(new LongInterval(MathNumber.MINUS_INFINITY, MathNumber.ONE.divide(other.low)));
LongInterval higher =
mul(new LongInterval(MathNumber.ONE.divide(other.high), MathNumber.PLUS_INFINITY));
if (lower.includes(higher)) {
return lower;
}
else if (higher.includes(lower)) {
return higher;
}
else {
return cacheAndRound(
new LongInterval(lower.low.compareTo(higher.low) > 0 ? higher.low : lower.low,
lower.high.compareTo(higher.high) < 0 ? higher.high : lower.high));
}
}
}
/**
* Yields {@code true} if this interval includes the given one.
*
* @param other the other interval
*
* @return {@code true} if it is included, {@code false} otherwise
*/
public boolean includes(
LongInterval other) {
return low.compareTo(other.low) <= 0 && high.compareTo(other.high) >= 0;
}
/**
* Yields {@code true} if this interval intersects with the given one.
*
* @param other the other interval
*
* @return {@code true} if those intersects, {@code false} otherwise
*/
public boolean intersects(
LongInterval other) {
return includes(other) || other.includes(this) ||
(high.compareTo(other.low) >= 0 && high.compareTo(other.high) <= 0) ||
(other.high.compareTo(low) >= 0 && other.high.compareTo(high) <= 0);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((high == null) ? 0 : high.hashCode());
result = prime * result + ((low == null) ? 0 : low.hashCode());
return result;
}
@Override
public boolean equals(
Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
LongInterval other = (LongInterval) obj;
if (high == null) {
if (other.high != null) {
return false;
}
}
else if (!high.equals(other.high)) {
return false;
}
if (low == null) {
if (other.low != null) {
return false;
}
}
else if (!low.equals(other.low)) {
return false;
}
return true;
}
@Override
public String toString() {
return "[" + low + ", " + high + "]";
}
@Override
public Iterator<Long> iterator() {
if (!low.isFinite() || !high.isFinite() || low.isNaN() || high.isNaN())
throw new RuntimeException(low + " is infinite or NaN", null);
try {
return new IntIntervalIterator(low.toLong(), high.toLong());
}
catch (MathNumberConversionException e) {
throw new RuntimeException("Cannot convert " + low, null);
}
}
@Override
public int compareTo(
LongInterval o) {
int cmp;
if ((cmp = low.compareTo(o.low)) != 0) {
return cmp;
}
return high.compareTo(o.high);
}
}

View File

@@ -0,0 +1,367 @@
/* ###
* IP: MIT
*/
package ghidra.lisa.pcode.analyses;
import java.math.BigInteger;
import java.util.Objects;
import ghidra.lisa.pcode.locations.PcodeLocation;
import ghidra.lisa.pcode.statements.PcodeBinaryOperator;
import ghidra.pcode.exec.BytesPcodeArithmetic;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.util.Msg;
import it.unive.lisa.analysis.*;
import it.unive.lisa.analysis.lattices.Satisfiability;
import it.unive.lisa.analysis.nonrelational.value.BaseNonRelationalValueDomain;
import it.unive.lisa.analysis.nonrelational.value.ValueEnvironment;
import it.unive.lisa.program.cfg.ProgramPoint;
import it.unive.lisa.symbolic.value.*;
import it.unive.lisa.symbolic.value.operator.binary.*;
import it.unive.lisa.symbolic.value.operator.unary.UnaryOperator;
import it.unive.lisa.type.NumericType;
import it.unive.lisa.type.Type;
import it.unive.lisa.util.representation.StringRepresentation;
import it.unive.lisa.util.representation.StructuredRepresentation;
/**
* The overflow-insensitive basic numeric constant propagation abstract domain,
* tracking if a certain numeric value has constant value or not, implemented as
* a {@link BaseNonRelationalValueDomain}, handling top and bottom values for
* the expression evaluation and bottom values for the expression
* satisfiability. Top and bottom cases for least upper bounds, widening and
* less or equals operations are handled by {@link BaseLattice} in
* {@link BaseLattice#lub}, {@link BaseLattice#widening} and
* {@link BaseLattice#lessOrEqual}, respectively.
*
* <p>
* Modified to handle pcode from original source written by:
* <p>
* @author <a href="mailto:vincenzo.arceri@unive.it">Vincenzo Arceri</a>
*/
public class PcodeByteBasedConstantPropagation
implements PcodeNonRelationalValueDomain<PcodeByteBasedConstantPropagation> {
private static final PcodeByteBasedConstantPropagation TOP =
new PcodeByteBasedConstantPropagation(true, false);
private static final PcodeByteBasedConstantPropagation BOTTOM =
new PcodeByteBasedConstantPropagation(false, true);
private static BytesPcodeArithmetic arithmetic;
private static boolean isBigEndian;
private final boolean isTop, isBottom;
private Long value;
/**
* Builds the top abstract value.
*
* @param language base language for current program
*/
public PcodeByteBasedConstantPropagation(Language language) {
this(0L, true, false);
PcodeByteBasedConstantPropagation.arithmetic =
BytesPcodeArithmetic.forLanguage(language);
isBigEndian = language.isBigEndian();
}
private PcodeByteBasedConstantPropagation(
Long value,
boolean isTop,
boolean isBottom) {
this.value = value;
this.isTop = isTop;
this.isBottom = isBottom;
}
private PcodeByteBasedConstantPropagation(
boolean isTop,
boolean isBottom) {
this(0L, isTop, isBottom);
}
public PcodeByteBasedConstantPropagation(
Long value) {
this(value, false, false);
}
public PcodeByteBasedConstantPropagation(
Boolean value) {
this(value ? 1L : 0L, false, false);
}
public PcodeByteBasedConstantPropagation(
byte[] bytes,
int size,
boolean isBigEndian) {
this(Utils.bytesToLong(bytes, size, isBigEndian));
}
@Override
public PcodeByteBasedConstantPropagation evalNullConstant(
ProgramPoint pp,
SemanticOracle oracle) {
return top();
}
@Override
public PcodeByteBasedConstantPropagation evalNonNullConstant(
Constant constant,
ProgramPoint pp,
SemanticOracle oracle) {
Object cval = constant.getValue();
if (cval instanceof Number val) {
Type staticType = constant.getStaticType();
if (staticType != null && staticType instanceof NumericType numType) {
if (numType.isSigned()) {
return new PcodeByteBasedConstantPropagation(Long.valueOf(val.longValue()));
}
}
}
if (cval instanceof Long lval) {
return new PcodeByteBasedConstantPropagation(lval);
}
if (cval instanceof Integer ival) {
return new PcodeByteBasedConstantPropagation(Integer.toUnsignedLong(ival));
}
if (cval instanceof Short sval) {
return new PcodeByteBasedConstantPropagation(Short.toUnsignedLong(sval));
}
if (cval instanceof Byte bval) {
return new PcodeByteBasedConstantPropagation(Byte.toUnsignedLong(bval));
}
if (cval instanceof Boolean bval) {
return new PcodeByteBasedConstantPropagation(bval ? 1L : 0L);
}
Msg.error(this, "Unknown type for constant: " + cval);
return top();
}
@Override
public PcodeByteBasedConstantPropagation evalUnaryExpression(
UnaryOperator operator,
PcodeByteBasedConstantPropagation arg,
ProgramPoint pp,
SemanticOracle oracle) {
if (arg.isTop()) {
return top();
}
PcodeLocation ploc = (PcodeLocation) pp.getLocation();
PcodeOp op = ploc.op;
byte[] bytes = arithmetic.unaryOp(op.getOpcode(), op.getOutput().getSize(),
op.getInput(0).getSize(), arg.getValue(op.getInput(0).getSize()));
return new PcodeByteBasedConstantPropagation(bytes, op.getOutput().getSize(),
isBigEndian);
}
private byte[] getValue(int size) {
return Utils.longToBytes(value, size, isBigEndian);
}
@Override
public PcodeByteBasedConstantPropagation evalBinaryExpression(
BinaryOperator operator,
PcodeByteBasedConstantPropagation left,
PcodeByteBasedConstantPropagation right,
ProgramPoint pp,
SemanticOracle oracle) {
if ((left.isTop || right.isTop) && !left.equals(right)) {
if (left.value == 0L || right.value == 0L) {
if (operator instanceof PcodeBinaryOperator poperator &&
poperator.getOp().getOpcode() == PcodeOp.INT_MULT) {
return new PcodeByteBasedConstantPropagation(0L);
}
}
return top();
}
PcodeLocation ploc = (PcodeLocation) pp.getLocation();
PcodeOp op = ploc.op;
if (left.isTop) {
return specialCaseLogic(op);
}
int lsize = op.getInput(0).getSize();
int rsize = op.getInput(1).getSize();
byte[] bytes = arithmetic.binaryOp(op.getOpcode(), op.getOutput().getSize(),
lsize, left.getValue(lsize),
rsize, right.getValue(rsize));
return new PcodeByteBasedConstantPropagation(bytes, op.getOutput().getSize(),
isBigEndian);
}
// These are instances that return zero when left==right.
private PcodeByteBasedConstantPropagation specialCaseLogic(PcodeOp op) {
int opcode = op.getOpcode();
if (opcode == PcodeOp.INT_SUB || opcode == PcodeOp.FLOAT_SUB ||
opcode == PcodeOp.BOOL_XOR || opcode == PcodeOp.INT_XOR) {
return new PcodeByteBasedConstantPropagation(0L);
}
return top();
}
@Override
public Satisfiability satisfiesBinaryExpression(
BinaryOperator operator,
PcodeByteBasedConstantPropagation left,
PcodeByteBasedConstantPropagation right,
ProgramPoint pp,
SemanticOracle oracle) {
if (left.isTop() || right.isTop()) {
return Satisfiability.UNKNOWN;
}
if (operator instanceof ComparisonEq) {
return left.value == right.value
? Satisfiability.SATISFIED
: Satisfiability.NOT_SATISFIED;
}
if (operator instanceof ComparisonNe) {
return left.value != right.value
? Satisfiability.SATISFIED
: Satisfiability.NOT_SATISFIED;
}
if (operator instanceof ComparisonLe) {
return left.value <= right.value
? Satisfiability.SATISFIED
: Satisfiability.NOT_SATISFIED;
}
if (operator instanceof ComparisonLt) {
return left.value < right.value
? Satisfiability.SATISFIED
: Satisfiability.NOT_SATISFIED;
}
return Satisfiability.UNKNOWN;
}
@Override
public ValueEnvironment<PcodeByteBasedConstantPropagation> assumeBinaryExpression(
ValueEnvironment<PcodeByteBasedConstantPropagation> environment,
BinaryOperator operator,
ValueExpression left,
ValueExpression right,
ProgramPoint src,
ProgramPoint dest,
SemanticOracle oracle)
throws SemanticException {
if (!(operator instanceof PcodeBinaryOperator)) {
if (operator instanceof ComparisonEq) {
if (left instanceof Identifier leftId) {
PcodeByteBasedConstantPropagation eval = eval(right, environment, src, oracle);
if (eval.isBottom()) {
return environment.bottom();
}
return environment.putState(leftId, eval);
}
else if (right instanceof Identifier rightId) {
PcodeByteBasedConstantPropagation eval = eval(left, environment, src, oracle);
if (eval.isBottom()) {
return environment.bottom();
}
return environment.putState(rightId, eval);
}
}
if (operator instanceof ComparisonNe) {
if (left instanceof Identifier leftId) {
PcodeByteBasedConstantPropagation eval = eval(right, environment, src, oracle);
if (eval.isBottom()) {
return environment.bottom();
}
eval.value = 1L - eval.value;
return environment.putState(leftId, eval);
}
else if (right instanceof Identifier rightId) {
PcodeByteBasedConstantPropagation eval = eval(left, environment, src, oracle);
if (eval.isBottom()) {
return environment.bottom();
}
eval.value = 1L - eval.value;
return environment.putState(rightId, eval);
}
}
}
return environment;
}
@Override
public PcodeByteBasedConstantPropagation lubAux(PcodeByteBasedConstantPropagation other)
throws SemanticException {
return TOP;
}
@Override
public boolean lessOrEqualAux(PcodeByteBasedConstantPropagation other)
throws SemanticException {
return false;
}
@Override
public PcodeByteBasedConstantPropagation top() {
return TOP;
}
@Override
public PcodeByteBasedConstantPropagation bottom() {
return BOTTOM;
}
@Override
public StructuredRepresentation representation() {
if (isBottom()) {
return Lattice.bottomRepresentation();
}
if (isTop()) {
return Lattice.topRepresentation();
}
return new StringRepresentation(value.toString());
}
@Override
public int hashCode() {
return Objects.hash(isBottom, isTop, value);
}
@Override
public boolean equals(
Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
if (!(obj instanceof PcodeByteBasedConstantPropagation other)) {
return false;
}
if (isBottom != other.isBottom) {
return false;
}
if (isTop != other.isTop) {
return false;
}
return Objects.equals(this.value, other.value);
}
@Override
public PcodeByteBasedConstantPropagation getValue(RegisterValue rv) {
if (rv != null) {
BigInteger val = rv.getUnsignedValue();
if (val != null) {
return new PcodeByteBasedConstantPropagation(val.longValue());
}
}
return top();
}
}

View File

@@ -0,0 +1,291 @@
/* ###
* IP: MIT
*/
package ghidra.lisa.pcode.analyses;
import java.util.*;
import ghidra.lisa.pcode.locations.InstLocation;
import ghidra.lisa.pcode.locations.PcodeLocation;
import ghidra.pcode.exec.BytesPcodeArithmetic;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressFormatException;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.util.Msg;
import it.unive.lisa.analysis.ScopeToken;
import it.unive.lisa.analysis.SemanticException;
import it.unive.lisa.analysis.dataflow.DataflowElement;
import it.unive.lisa.analysis.dataflow.DefiniteDataflowDomain;
import it.unive.lisa.program.cfg.ProgramPoint;
import it.unive.lisa.program.cfg.statement.Assignment;
import it.unive.lisa.symbolic.SymbolicExpression;
import it.unive.lisa.symbolic.value.*;
import it.unive.lisa.type.NumericType;
import it.unive.lisa.type.Type;
import it.unive.lisa.util.representation.*;
/**
* An implementation of the overflow-insensitive constant propagation dataflow
* analysis, that focuses only on integers.
*
* <p>
* Modified to handle pcode from original source written by:
* <p>
* @author <a href="mailto:luca.negrini@unive.it">Luca Negrini</a>
*/
public class PcodeDataflowConstantPropagation implements
DataflowElement<DefiniteDataflowDomain<PcodeDataflowConstantPropagation>, PcodeDataflowConstantPropagation> {
private static BytesPcodeArithmetic arithmetic;
private static boolean isBigEndian;
private final Identifier id;
private final Long constant;
/**
* Builds an empty constant propagation object.
*
* @param language base language for current program
*/
public PcodeDataflowConstantPropagation(Language language) {
this(null, null);
PcodeDataflowConstantPropagation.arithmetic = BytesPcodeArithmetic.forLanguage(language);
isBigEndian = language.isBigEndian();
}
/**
* Builds the new constant propagation object.
*
* @param id the constant variable
* @param v the constant value
*/
public PcodeDataflowConstantPropagation(
Identifier id,
Long v) {
this.id = id;
this.constant = v;
}
@Override
public String toString() {
return representation().toString();
}
@Override
public Collection<Identifier> getInvolvedIdentifiers() {
return Collections.singleton(id);
}
private static byte[] getValue(long val, int size) {
return Utils.longToBytes(val, size, isBigEndian);
}
private static Long eval(
SymbolicExpression e, ProgramPoint pp,
DefiniteDataflowDomain<PcodeDataflowConstantPropagation> domain) {
if (e instanceof Constant c) {
Object cval = c.getValue();
if (cval instanceof Number nval) {
Type staticType = c.getStaticType();
if (staticType != null && staticType instanceof NumericType numType) {
if (numType.isSigned()) {
return Long.valueOf(nval.longValue());
}
}
}
if (cval instanceof Long lval) {
return lval;
}
if (cval instanceof Integer ival) {
return Integer.toUnsignedLong(ival);
}
if (cval instanceof Short sval) {
return Short.toUnsignedLong(sval);
}
if (cval instanceof Byte bval) {
return Byte.toUnsignedLong(bval);
}
if (cval instanceof Boolean bval) {
return bval ? 1L : 0L;
}
Msg.error(e, "Unknown type for constant: " + cval);
return null;
}
if (e instanceof Identifier) {
for (PcodeDataflowConstantPropagation cp : domain.getDataflowElements()) {
if (cp.id.equals(e)) {
return cp.constant;
}
}
return null;
}
if (!(pp.getLocation() instanceof PcodeLocation ploc)) {
return null;
}
PcodeOp op = ploc.op;
if (e instanceof UnaryExpression unary) {
Long i = eval(unary.getExpression(), pp, domain);
if (i == null) {
return i;
}
Long exp = eval(unary.getExpression(), pp, domain);
byte[] bytes = arithmetic.unaryOp(op.getOpcode(), op.getOutput().getSize(),
op.getInput(0).getSize(), getValue(exp, op.getInput(0).getSize()));
return Utils.bytesToLong(bytes, op.getOutput().getSize(), isBigEndian);
}
if (e instanceof BinaryExpression binary) {
Long right = eval(binary.getRight(), pp, domain);
Long left = eval(binary.getLeft(), pp, domain);
if (right == null || left == null) {
return null;
}
int lsize = op.getInput(0).getSize();
int rsize = op.getInput(1).getSize();
byte[] bytes = arithmetic.binaryOp(op.getOpcode(), op.getOutput().getSize(),
lsize, getValue(left, lsize),
rsize, getValue(right, rsize));
return Utils.bytesToLong(bytes, op.getOutput().getSize(), isBigEndian);
}
if (e instanceof PushAny) {
InstLocation loc = (InstLocation) pp.getLocation();
Function f = loc.function();
try {
if (f != null && pp instanceof Assignment a) {
Program program = f.getProgram();
Address address = program.getAddressFactory()
.getRegisterSpace()
.getAddress(a.getLeft().toString());
Register r = program.getRegister(address);
if (r != null) {
RegisterValue rv =
program.getProgramContext().getRegisterValue(r, f.getEntryPoint());
if (rv != null && rv.hasValue()) {
return rv.getUnsignedValue().longValue();
}
}
}
}
catch (AddressFormatException e1) {
// IGNORE
}
}
return null;
}
@Override
public Collection<PcodeDataflowConstantPropagation> gen(
Identifier idg,
ValueExpression expression,
ProgramPoint pp,
DefiniteDataflowDomain<PcodeDataflowConstantPropagation> domain) {
Set<PcodeDataflowConstantPropagation> gen = new HashSet<>();
Long v = eval(expression, pp, domain);
if (v != null) {
gen.add(new PcodeDataflowConstantPropagation(idg, v));
}
return gen;
}
@Override
public Collection<PcodeDataflowConstantPropagation> gen(
ValueExpression expression,
ProgramPoint pp,
DefiniteDataflowDomain<PcodeDataflowConstantPropagation> domain) {
return Collections.emptyList();
}
@Override
public Collection<PcodeDataflowConstantPropagation> kill(
Identifier idk,
ValueExpression expression,
ProgramPoint pp,
DefiniteDataflowDomain<PcodeDataflowConstantPropagation> domain) {
Collection<PcodeDataflowConstantPropagation> result = new HashSet<>();
for (PcodeDataflowConstantPropagation cp : domain.getDataflowElements()) {
if (cp.id.equals(idk)) {
result.add(cp);
}
}
return result;
}
@Override
public Collection<PcodeDataflowConstantPropagation> kill(
ValueExpression expression,
ProgramPoint pp,
DefiniteDataflowDomain<PcodeDataflowConstantPropagation> domain) {
return Collections.emptyList();
}
@Override
public int hashCode() {
return Objects.hash(id, constant);
}
@Override
public boolean equals(
Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
PcodeDataflowConstantPropagation other = (PcodeDataflowConstantPropagation) obj;
if (!Objects.equals(this.id, other.id)) {
return false;
}
return Objects.equals(this.constant, other.constant);
}
@Override
public StructuredRepresentation representation() {
return new ListRepresentation(new StringRepresentation(id),
new StringRepresentation(constant));
}
@Override
public PcodeDataflowConstantPropagation pushScope(
ScopeToken scope)
throws SemanticException {
return new PcodeDataflowConstantPropagation((Identifier) id.pushScope(scope), constant);
}
@Override
public PcodeDataflowConstantPropagation popScope(
ScopeToken scope)
throws SemanticException {
if (!(id instanceof OutOfScopeIdentifier)) {
return this;
}
return new PcodeDataflowConstantPropagation(((OutOfScopeIdentifier) id).popScope(scope),
constant);
}
}

View File

@@ -0,0 +1,546 @@
/* ###
* IP: MIT
*/
package ghidra.lisa.pcode.analyses;
import java.math.BigInteger;
import java.util.Objects;
import ghidra.lisa.pcode.locations.PcodeLocation;
import ghidra.lisa.pcode.statements.PcodeBinaryOperator;
import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.util.Msg;
import it.unive.lisa.analysis.*;
import it.unive.lisa.analysis.lattices.Satisfiability;
import it.unive.lisa.analysis.nonrelational.value.BaseNonRelationalValueDomain;
import it.unive.lisa.analysis.nonrelational.value.ValueEnvironment;
import it.unive.lisa.program.cfg.ProgramPoint;
import it.unive.lisa.symbolic.value.*;
import it.unive.lisa.symbolic.value.operator.binary.*;
import it.unive.lisa.symbolic.value.operator.unary.UnaryOperator;
import it.unive.lisa.util.numeric.IntInterval;
import it.unive.lisa.util.numeric.MathNumber;
import it.unive.lisa.util.representation.StringRepresentation;
import it.unive.lisa.util.representation.StructuredRepresentation;
/**
* The overflow-insensitive interval abstract domain, approximating integer
* values as the minimum numeric interval containing them. It is implemented as
* a {@link BaseNonRelationalValueDomain}, handling top and bottom values for
* the expression evaluation and bottom values for the expression
* satisfiability. Top and bottom cases for least upper bounds, widening and
* less or equals operations are handled by {@link BaseLattice} in
* {@link BaseLattice#lub}, {@link BaseLattice#widening} and
* {@link BaseLattice#lessOrEqual} methods, respectively.
*
* <p>
* Modified to handle pcode from original source written by:
* <p>
* @author <a href="mailto:vincenzo.arceri@unive.it">Vincenzo Arceri</a>
*/
public class PcodeInterval
implements PcodeNonRelationalValueDomain<PcodeInterval>, Comparable<PcodeInterval> {
/**
* The abstract zero ({@code [0, 0]}) element.
*/
public static final PcodeInterval ZERO = new PcodeInterval(LongInterval.ZERO);
/**
* The abstract top ({@code [-Inf, +Inf]}) element.
*/
public static final PcodeInterval TOP = new PcodeInterval(LongInterval.INFINITY);
/**
* The abstract bottom element.
*/
public static final PcodeInterval BOTTOM = new PcodeInterval(null);
/**
* The interval represented by this domain element.
*/
public final LongInterval interval;
/**
* Builds the interval.
*
* @param interval the underlying {@link IntInterval}
*/
public PcodeInterval(
LongInterval interval) {
this.interval = interval;
}
/**
* Builds the interval.
*
* @param low the lower bound
* @param high the higher bound
*/
public PcodeInterval(
MathNumber low,
MathNumber high) {
this(new LongInterval(low, high));
}
/**
* Builds the interval.
*
* @param low the lower bound
* @param high the higher bound
*/
public PcodeInterval(
long low,
long high) {
this(new LongInterval(low, high));
}
/**
* Builds the top interval.
*/
public PcodeInterval() {
this(LongInterval.INFINITY);
}
@Override
public PcodeInterval top() {
return TOP;
}
@Override
public boolean isTop() {
return interval != null && interval.isInfinity();
}
@Override
public PcodeInterval bottom() {
return BOTTOM;
}
@Override
public boolean isBottom() {
return interval == null;
}
@Override
public StructuredRepresentation representation() {
if (isBottom()) {
return Lattice.bottomRepresentation();
}
return new StringRepresentation(interval.toString());
}
@Override
public String toString() {
return representation().toString();
}
@Override
public PcodeInterval evalNonNullConstant(
Constant constant,
ProgramPoint pp,
SemanticOracle oracle) {
Object cval = constant.getValue();
if (cval instanceof Long lval) {
return new PcodeInterval(new MathNumber(lval), new MathNumber(lval));
}
if (cval instanceof Integer ival) {
return new PcodeInterval(new MathNumber(ival), new MathNumber(ival));
}
if (cval instanceof Short sval) {
return new PcodeInterval(new MathNumber(sval), new MathNumber(sval));
}
if (cval instanceof Byte bval) {
return new PcodeInterval(new MathNumber(bval), new MathNumber(bval));
}
if (cval instanceof Boolean bval) {
return new PcodeInterval(new MathNumber(bval ? 1L : 0L),
new MathNumber(bval ? 1L : 0L));
}
Msg.error(this, "Unknown type for constant: " + cval);
return top();
}
@Override
public PcodeInterval evalUnaryExpression(
UnaryOperator operator,
PcodeInterval arg,
ProgramPoint pp,
SemanticOracle oracle) {
PcodeLocation ploc = (PcodeLocation) pp.getLocation();
PcodeOp op = ploc.op;
int opcode = op.getOpcode();
if (opcode == PcodeOp.INT_NEGATE || opcode == PcodeOp.INT_2COMP ||
opcode == PcodeOp.FLOAT_NEG) {
if (arg.isTop()) {
return top();
}
return new PcodeInterval(arg.interval.mul(LongInterval.MINUS_ONE));
}
return top();
}
/**
* Tests whether this interval instance corresponds (i.e., concretizes)
* exactly to the given integer. The tests is performed through
* {@link IntInterval#is(int)}.
*
* @param n the integer value
*
* @return {@code true} if that condition holds
*/
public boolean is(
int n) {
return !isBottom() && interval.is(n);
}
@Override
public PcodeInterval evalBinaryExpression(
BinaryOperator operator,
PcodeInterval left,
PcodeInterval right,
ProgramPoint pp,
SemanticOracle oracle) {
PcodeLocation ploc = (PcodeLocation) pp.getLocation();
PcodeOp op = ploc.op;
int opcode = op.getOpcode();
if (!(opcode == PcodeOp.INT_DIV) && !(opcode == PcodeOp.FLOAT_DIV) &&
(left.isTop() || right.isTop())) {
// with div, we can return zero or bottom even if one of the
// operands is top
return top();
}
return switch (opcode) {
case PcodeOp.INT_ADD, PcodeOp.FLOAT_ADD -> new PcodeInterval(
left.interval.plus(right.interval));
case PcodeOp.INT_SUB, PcodeOp.FLOAT_SUB -> new PcodeInterval(
left.interval.diff(right.interval));
case PcodeOp.INT_MULT, PcodeOp.FLOAT_MULT -> {
if (left.is(0) || right.is(0)) {
yield ZERO;
}
yield new PcodeInterval(left.interval.mul(right.interval));
}
case PcodeOp.INT_DIV, PcodeOp.FLOAT_DIV -> {
if (left.isTop() || right.isTop()) {
yield top();
}
if (right.is(0)) {
yield bottom();
}
if (left.is(0)) {
yield ZERO;
}
yield new PcodeInterval(left.interval.div(right.interval, false, false));
}
case PcodeOp.INT_REM, PcodeOp.INT_SREM -> {
if (right.is(0)) {
yield bottom();
}
if (left.is(0)) {
yield ZERO;
}
if (left.isTop() || right.isTop()) {
yield top();
}
// the result takes the sign of the dividend - l%r is:
// - [-M+1,0] if l.high < 0 (fully negative)
// - [0,M-1] if l.low > 0 (fully positive)
// - [-M+1,M-1] otherwise
// where M is
// - -r.low if r.high < 0 (fully negative)
// - r.high if r.low > 0 (fully positive)
// - max(abs(r.low),abs(r.right)) otherwise
MathNumber M;
if (right.interval.getHigh().compareTo(MathNumber.ZERO) < 0) {
M = right.interval.getLow().multiply(MathNumber.MINUS_ONE);
}
else if (right.interval.getLow().compareTo(MathNumber.ZERO) > 0) {
M = right.interval.getHigh();
}
else {
M = right.interval.getLow().abs().max(right.interval.getHigh().abs());
}
if (left.interval.getHigh().compareTo(MathNumber.ZERO) < 0) {
yield new PcodeInterval(
M.multiply(MathNumber.MINUS_ONE).add(MathNumber.ONE), MathNumber.ZERO);
}
if (left.interval.getLow().compareTo(MathNumber.ZERO) > 0) {
yield new PcodeInterval(MathNumber.ZERO, M.subtract(MathNumber.ONE));
}
yield new PcodeInterval(M.multiply(MathNumber.MINUS_ONE).add(MathNumber.ONE),
M.subtract(MathNumber.ONE));
}
default -> left;
};
}
@Override
public PcodeInterval lubAux(
PcodeInterval other)
throws SemanticException {
MathNumber newLow = interval.getLow().min(other.interval.getLow());
MathNumber newHigh = interval.getHigh().max(other.interval.getHigh());
return newLow.isMinusInfinity() && newHigh.isPlusInfinity() ? top()
: new PcodeInterval(newLow, newHigh);
}
@Override
public PcodeInterval glbAux(
PcodeInterval other) {
MathNumber newLow = interval.getLow().max(other.interval.getLow());
MathNumber newHigh = interval.getHigh().min(other.interval.getHigh());
if (newLow.compareTo(newHigh) > 0) {
return bottom();
}
return newLow.isMinusInfinity() && newHigh.isPlusInfinity() ? top()
: new PcodeInterval(newLow, newHigh);
}
@Override
public PcodeInterval wideningAux(
PcodeInterval other)
throws SemanticException {
MathNumber newLow, newHigh;
if (other.interval.getHigh().compareTo(interval.getHigh()) > 0) {
newHigh = MathNumber.PLUS_INFINITY;
}
else {
newHigh = interval.getHigh();
}
if (other.interval.getLow().compareTo(interval.getLow()) < 0) {
newLow = MathNumber.MINUS_INFINITY;
}
else {
newLow = interval.getLow();
}
return newLow.isMinusInfinity() && newHigh.isPlusInfinity() ? top()
: new PcodeInterval(newLow, newHigh);
}
@Override
public PcodeInterval narrowingAux(
PcodeInterval other)
throws SemanticException {
MathNumber newLow, newHigh;
newHigh = interval.getHigh().isInfinite() ? other.interval.getHigh() : interval.getHigh();
newLow = interval.getLow().isInfinite() ? other.interval.getLow() : interval.getLow();
return new PcodeInterval(newLow, newHigh);
}
@Override
public boolean lessOrEqualAux(
PcodeInterval other)
throws SemanticException {
return other.interval.includes(interval);
}
@Override
public Satisfiability satisfiesBinaryExpression(
BinaryOperator operator,
PcodeInterval left,
PcodeInterval right,
ProgramPoint pp,
SemanticOracle oracle) {
if (left.isTop() || right.isTop()) {
return Satisfiability.UNKNOWN;
}
if (!(operator instanceof PcodeBinaryOperator)) {
if (operator instanceof ComparisonEq) {
PcodeInterval glb = null;
try {
glb = left.glb(right);
}
catch (SemanticException e) {
return Satisfiability.UNKNOWN;
}
if (glb.isBottom()) {
return Satisfiability.NOT_SATISFIED;
}
else if (left.interval.isSingleton() && left.equals(right)) {
return Satisfiability.SATISFIED;
}
return Satisfiability.UNKNOWN;
}
else if (operator instanceof ComparisonLe) {
PcodeInterval glb = null;
try {
glb = left.glb(right);
}
catch (SemanticException e) {
return Satisfiability.UNKNOWN;
}
if (glb.isBottom()) {
return Satisfiability.fromBoolean(
left.interval.getHigh().compareTo(right.interval.getLow()) <= 0);
}
// we might have a singleton as glb if the two intervals share a
// bound
if (glb.interval.isSingleton() &&
left.interval.getHigh().compareTo(right.interval.getLow()) == 0) {
return Satisfiability.SATISFIED;
}
return Satisfiability.UNKNOWN;
}
else if (operator instanceof ComparisonLt) {
PcodeInterval glb = null;
try {
glb = left.glb(right);
}
catch (SemanticException e) {
return Satisfiability.UNKNOWN;
}
if (glb.isBottom()) {
return Satisfiability.fromBoolean(
left.interval.getHigh().compareTo(right.interval.getLow()) < 0);
}
return Satisfiability.UNKNOWN;
}
else if (operator instanceof ComparisonNe) {
PcodeInterval glb = null;
try {
glb = left.glb(right);
}
catch (SemanticException e) {
return Satisfiability.UNKNOWN;
}
if (glb.isBottom()) {
return Satisfiability.SATISFIED;
}
return Satisfiability.UNKNOWN;
}
}
return Satisfiability.UNKNOWN;
}
@Override
public int hashCode() {
return Objects.hash(interval);
}
@Override
public boolean equals(
Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
PcodeInterval other = (PcodeInterval) obj;
return Objects.equals(this.interval, other.interval);
}
@Override
public ValueEnvironment<PcodeInterval> assumeBinaryExpression(
ValueEnvironment<PcodeInterval> environment,
BinaryOperator operator,
ValueExpression left,
ValueExpression right,
ProgramPoint src,
ProgramPoint dest,
SemanticOracle oracle)
throws SemanticException {
Identifier id;
PcodeInterval eval;
boolean rightIsExpr;
if (left instanceof Identifier) {
eval = eval(right, environment, src, oracle);
id = (Identifier) left;
rightIsExpr = true;
}
else if (right instanceof Identifier) {
eval = eval(left, environment, src, oracle);
id = (Identifier) right;
rightIsExpr = false;
}
else
return environment;
PcodeInterval starting = environment.getState(id);
if (eval.isBottom() || starting.isBottom()) {
return environment.bottom();
}
boolean lowIsMinusInfinity = eval.interval.lowIsMinusInfinity();
PcodeInterval low_inf = new PcodeInterval(eval.interval.getLow(), MathNumber.PLUS_INFINITY);
PcodeInterval lowp1_inf =
new PcodeInterval(eval.interval.getLow().add(MathNumber.ONE), MathNumber.PLUS_INFINITY);
PcodeInterval inf_high =
new PcodeInterval(MathNumber.MINUS_INFINITY, eval.interval.getHigh());
PcodeInterval inf_highm1 = new PcodeInterval(MathNumber.MINUS_INFINITY,
eval.interval.getHigh().subtract(MathNumber.ONE));
PcodeInterval update = null;
if (!(operator instanceof PcodeBinaryOperator)) {
if (operator instanceof ComparisonEq) {
update = eval;
}
else if (operator instanceof ComparisonLe) {
update = rightIsExpr ? starting.glb(inf_high)
: lowIsMinusInfinity ? null : starting.glb(low_inf);
}
else if (operator instanceof ComparisonLt) {
if (rightIsExpr) {
update = lowIsMinusInfinity ? eval : starting.glb(inf_highm1);
}
else {
update = lowIsMinusInfinity ? null : starting.glb(lowp1_inf);
}
}
}
if (update == null) {
return environment;
}
else if (update.isBottom()) {
return environment.bottom();
}
return environment.putState(id, update);
}
@Override
public int compareTo(
PcodeInterval o) {
if (isBottom()) {
return o.isBottom() ? 0 : -1;
}
if (isTop()) {
return o.isTop() ? 0 : 1;
}
if (o.isBottom()) {
return 1;
}
if (o.isTop()) {
return -1;
}
return interval.compareTo(o.interval);
}
@Override
public PcodeInterval getValue(RegisterValue rv) {
if (rv != null) {
BigInteger val = rv.getUnsignedValue();
if (val != null) {
return new PcodeInterval(val.longValue(), val.longValue());
}
}
return top();
}
}

View File

@@ -0,0 +1,259 @@
/* ###
* IP: MIT
*/
package ghidra.lisa.pcode.analyses;
import java.util.*;
import ghidra.lisa.pcode.statements.PcodeBinaryOperator;
import it.unive.lisa.analysis.SemanticException;
import it.unive.lisa.analysis.SemanticOracle;
import it.unive.lisa.analysis.nonRedundantSet.NonRedundantPowersetOfBaseNonRelationalValueDomain;
import it.unive.lisa.analysis.nonrelational.value.ValueEnvironment;
import it.unive.lisa.analysis.numeric.Interval;
import it.unive.lisa.program.cfg.ProgramPoint;
import it.unive.lisa.symbolic.value.Identifier;
import it.unive.lisa.symbolic.value.ValueExpression;
import it.unive.lisa.symbolic.value.operator.binary.*;
import it.unive.lisa.util.numeric.MathNumber;
/**
* The finite non redundant powerset of {@link Interval} abstract domain
* approximating numeric values as a non redundant set of interval. It is
* implemented as a {@link NonRedundantPowersetOfBaseNonRelationalValueDomain},
* which handles most of the basic operation (such as
* {@link NonRedundantPowersetOfBaseNonRelationalValueDomain#lubAux lub},
* {@link NonRedundantPowersetOfBaseNonRelationalValueDomain#glbAux glb},
* {@link NonRedundantPowersetOfBaseNonRelationalValueDomain#wideningAux
* widening} and others operations needed to calculate the previous ones).
*/
public class PcodeNonRedundantPowersetOfInterval
extends
NonRedundantPowersetOfBaseNonRelationalValueDomain<PcodeNonRedundantPowersetOfInterval, Interval> {
/**
* Constructs an empty non redundant set of intervals.
*/
public PcodeNonRedundantPowersetOfInterval() {
super(new TreeSet<>(), Interval.BOTTOM);
}
/**
* Constructs a non redundant set of intervals with the given intervals.
*
* @param elements the set of intervals
*/
public PcodeNonRedundantPowersetOfInterval(
SortedSet<Interval> elements) {
super(elements, Interval.BOTTOM);
}
/**
* This specific Egli-Milner connector follows this definition:<br>
* given two subsets S<sub>1</sub> and S<sub>2</sub> of a domain of a
* lattice:
* <p>
* S<sub>1</sub> +<sub>EM</sub> S<sub>2</sub> = {s<sub>2</sub> &ni;
* S<sub>2</sub> | &exist; s<sub>1</sub> &ni; S<sub>1</sub> : s<sub>1</sub>
* &le; s<sub>2</sub>} &cup; {lub(s'<sub>1</sub>, s<sub>2</sub>) |
* s'<sub>1</sub> &ni; S<sub>1</sub>, s<sub>2</sub> &ni; S<sub>2</sub>, NOT
* &exist; s<sub>1</sub> &ni; S<sub>1</sub> : s<sub>1</sub> &le;
* s<sub>2</sub>}
* </p>
* s'<sub>1</sub> can be chosen randomly but in this case is chosen to be
* the closest interval to s<sub>2</sub> (closest based on
* {@link #middlePoint(Interval) middle point}).
*/
@Override
protected PcodeNonRedundantPowersetOfInterval EgliMilnerConnector(
PcodeNonRedundantPowersetOfInterval other)
throws SemanticException {
SortedSet<Interval> newElementsSet = new TreeSet<>();
SortedSet<Interval> notCoverSet = new TreeSet<>();
// first side of the union
for (Interval s2 : other.elementsSet) {
boolean existsLower = false;
for (Interval s1 : elementsSet) {
if (s1.lessOrEqual(s2)) {
existsLower = true;
break;
}
}
if (existsLower) {
newElementsSet.add(s2);
}
else {
notCoverSet.add(s2);
}
}
// second side of the union
for (Interval s2 : notCoverSet) {
MathNumber middlePoint = middlePoint(s2);
MathNumber closestValue = middlePoint;
MathNumber closestDiff = closestValue.subtract(middlePoint).abs();
Interval closest = Interval.TOP;
for (Interval s1 : elementsSet) {
if (closestValue.compareTo(middlePoint) == 0) {
closest = s1;
closestValue = middlePoint(s1);
closestDiff = closestValue.subtract(middlePoint).abs();
}
else {
MathNumber s1Diff = middlePoint(s1).subtract(middlePoint).abs();
if (s1Diff.compareTo(closestDiff) < 0) {
closest = s1;
closestValue = middlePoint(s1);
closestDiff = closestValue.subtract(middlePoint).abs();
}
}
}
newElementsSet.add(s2.lub(closest));
}
return new PcodeNonRedundantPowersetOfInterval(newElementsSet).removeRedundancy()
.removeOverlapping();
}
/**
* Yields the middle point of an {@link Interval}. If both extremes are
* non-infinite the middle point is the sum of the two divided by two. If
* only one of the two extreme is infinite the middle point is said to be
* the non-infinite extreme. If both the extremes are infinite the middle
* point is said to be 0.
*
* @param interval the interval to calculate the middle point of
*
* @return the middle point of the interval
*/
protected MathNumber middlePoint(
Interval interval) {
if (interval.interval.isFinite()) {
return interval.interval.getLow()
.add(interval.interval.getHigh())
.divide(new MathNumber(2));
}
else if (interval.interval.getHigh().isFinite() && !interval.interval.getLow().isFinite()) {
return interval.interval.getHigh();
}
else if (!interval.interval.getHigh().isFinite() && interval.interval.getLow().isFinite()) {
return interval.interval.getLow().subtract(MathNumber.ONE);
}
// both infinite
return MathNumber.ZERO;
}
@Override
public ValueEnvironment<PcodeNonRedundantPowersetOfInterval> assumeBinaryExpression(
ValueEnvironment<PcodeNonRedundantPowersetOfInterval> environment,
BinaryOperator operator,
ValueExpression left,
ValueExpression right,
ProgramPoint src,
ProgramPoint dest,
SemanticOracle oracle)
throws SemanticException {
Identifier id;
PcodeNonRedundantPowersetOfInterval eval;
boolean rightIsExpr;
if (left instanceof Identifier leftId) {
eval = eval(right, environment, src, oracle);
id = leftId;
rightIsExpr = true;
}
else if (right instanceof Identifier rightId) {
eval = eval(left, environment, src, oracle);
id = rightId;
rightIsExpr = false;
}
else {
return environment;
}
PcodeNonRedundantPowersetOfInterval starting = environment.getState(id);
if (eval.isBottom() || starting.isBottom()) {
return environment.bottom();
}
SortedSet<Interval> newSet = new TreeSet<>();
for (Interval startingInterval : starting.elementsSet)
for (Interval interval : eval.elementsSet) {
boolean lowIsMinusInfinity = interval.interval.lowIsMinusInfinity();
Interval lowInf =
new Interval(interval.interval.getLow(), MathNumber.PLUS_INFINITY);
Interval lowp1Inf = new Interval(interval.interval.getLow().add(MathNumber.ONE),
MathNumber.PLUS_INFINITY);
Interval infHigh =
new Interval(MathNumber.MINUS_INFINITY, interval.interval.getHigh());
Interval infHighm1 = new Interval(MathNumber.MINUS_INFINITY,
interval.interval.getHigh().subtract(MathNumber.ONE));
if (!(operator instanceof PcodeBinaryOperator)) {
if (operator instanceof ComparisonEq) {
newSet.add(interval);
}
else if (operator instanceof ComparisonLe) {
if (rightIsExpr) {
newSet.add(startingInterval.glb(infHigh));
}
else if (lowIsMinusInfinity) {
newSet.add(startingInterval);
}
else {
newSet.add(startingInterval.glb(lowInf));
}
}
else if (operator instanceof ComparisonLt) {
if (rightIsExpr) {
newSet.add(
lowIsMinusInfinity ? interval : startingInterval.glb(infHighm1));
}
else if (lowIsMinusInfinity) {
newSet.add(startingInterval);
}
else {
newSet.add(startingInterval.glb(lowp1Inf));
}
}
else {
newSet.add(startingInterval);
}
}
}
PcodeNonRedundantPowersetOfInterval intervals =
new PcodeNonRedundantPowersetOfInterval(newSet)
.removeRedundancy()
.removeOverlapping();
if (intervals.isBottom()) {
return environment.bottom();
}
return environment.putState(id, intervals);
}
@Override
protected PcodeNonRedundantPowersetOfInterval mk(
SortedSet<Interval> elements) {
return new PcodeNonRedundantPowersetOfInterval(elements);
}
@Override
public boolean equals(
Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
PcodeNonRedundantPowersetOfInterval other = (PcodeNonRedundantPowersetOfInterval) obj;
if (!Objects.equals(this.elementsSet, other.elementsSet)) {
return false;
}
return Objects.equals(this.valueDomain, other.valueDomain);
}
}

View File

@@ -0,0 +1,72 @@
/* ###
* 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.lisa.pcode.analyses;
import ghidra.lisa.pcode.locations.InstLocation;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressFormatException;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import it.unive.lisa.analysis.SemanticException;
import it.unive.lisa.analysis.SemanticOracle;
import it.unive.lisa.analysis.nonrelational.value.BaseNonRelationalValueDomain;
import it.unive.lisa.program.cfg.ProgramPoint;
import it.unive.lisa.program.cfg.statement.Assignment;
import it.unive.lisa.symbolic.value.PushAny;
/**
* @param <T> the concrete type of this domain
*/
public interface PcodeNonRelationalValueDomain<T extends PcodeNonRelationalValueDomain<T>>
extends BaseNonRelationalValueDomain<T> {
T getValue(RegisterValue rv);
default T getValue(ProgramPoint pp) {
InstLocation loc = (InstLocation) pp.getLocation();
Function f = loc.function();
if (f != null && pp instanceof Assignment a) {
Program program = f.getProgram();
try {
Address address = program.getAddressFactory()
.getRegisterSpace()
.getAddress(a.getLeft().toString());
Register r = program.getRegister(address);
if (r != null) {
RegisterValue rv =
program.getProgramContext().getRegisterValue(r, f.getEntryPoint());
return getValue(rv);
}
}
catch (AddressFormatException e) {
// IGNORE
}
}
return getValue((RegisterValue) null);
}
@Override
default T evalPushAny(
PushAny pushAny,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
T v = getValue(pp);
return v == null ? top() : v;
}
}

View File

@@ -0,0 +1,304 @@
/* ###
* IP: MIT
*/
package ghidra.lisa.pcode.analyses;
import java.math.BigInteger;
import ghidra.lisa.pcode.locations.PcodeLocation;
import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.util.Msg;
import it.unive.lisa.analysis.*;
import it.unive.lisa.analysis.nonrelational.value.BaseNonRelationalValueDomain;
import it.unive.lisa.analysis.nonrelational.value.ValueEnvironment;
import it.unive.lisa.program.cfg.ProgramPoint;
import it.unive.lisa.symbolic.value.*;
import it.unive.lisa.symbolic.value.operator.binary.BinaryOperator;
import it.unive.lisa.symbolic.value.operator.binary.ComparisonEq;
import it.unive.lisa.symbolic.value.operator.unary.UnaryOperator;
import it.unive.lisa.util.representation.StringRepresentation;
import it.unive.lisa.util.representation.StructuredRepresentation;
/**
* The overflow-insensitive Parity abstract domain, tracking if a numeric value
* is even or odd, implemented as a {@link BaseNonRelationalValueDomain},
* handling top and bottom values for the expression evaluation and bottom
* values for the expression satisfiability. Top and bottom cases for least
* upper bound, widening and less or equals operations are handled by
* {@link BaseLattice} in {@link BaseLattice#lub}, {@link BaseLattice#widening}
* and {@link BaseLattice#lessOrEqual} methods, respectively.
*
* <p>
* Modified to handle pcode from original source written by:
* <p>
* @author <a href="mailto:vincenzo.arceri@unive.it">Vincenzo Arceri</a>
*/
public class PcodeParity implements PcodeNonRelationalValueDomain<PcodeParity> {
/**
* The abstract even element.
*/
public static final PcodeParity EVEN = new PcodeParity((byte) 3);
/**
* The abstract odd element.
*/
public static final PcodeParity ODD = new PcodeParity((byte) 2);
/**
* The abstract top element.
*/
public static final PcodeParity TOP = new PcodeParity((byte) 0);
/**
* The abstract bottom element.
*/
public static final PcodeParity BOTTOM = new PcodeParity((byte) 1);
private final byte parity;
/**
* Builds the parity abstract domain, representing the top of the parity
* abstract domain.
*/
public PcodeParity() {
this((byte) 0);
}
/**
* Builds the parity instance for the given parity value.
*
* @param parity the sign (0 = top, 1 = bottom, 2 = odd, 3 = even)
*/
public PcodeParity(
byte parity) {
this.parity = parity;
}
@Override
public PcodeParity top() {
return TOP;
}
@Override
public PcodeParity bottom() {
return BOTTOM;
}
@Override
public StructuredRepresentation representation() {
if (isBottom()) {
return Lattice.bottomRepresentation();
}
if (isTop()) {
return Lattice.topRepresentation();
}
String repr = this == EVEN ? "Even" : "Odd";
return new StringRepresentation(repr);
}
@Override
public PcodeParity evalNullConstant(
ProgramPoint pp,
SemanticOracle oracle) {
return top();
}
@Override
public PcodeParity evalNonNullConstant(
Constant constant,
ProgramPoint pp,
SemanticOracle oracle) {
Object cval = constant.getValue();
if (cval instanceof Long lval) {
return lval % 2 == 0 ? EVEN : ODD;
}
if (cval instanceof Integer ival) {
return ival % 2 == 0 ? EVEN : ODD;
}
if (cval instanceof Short sval) {
return sval % 2 == 0 ? EVEN : ODD;
}
if (cval instanceof Byte bval) {
return bval % 2 == 0 ? EVEN : ODD;
}
if (cval instanceof Boolean bval) {
return bval ? ODD : EVEN;
}
Msg.error(this, "Unknown type for constant: " + cval);
return top();
}
/**
* Yields whether or not this is the even parity.
*
* @return {@code true} if that condition holds
*/
public boolean isEven() {
return this == EVEN;
}
/**
* Yields whether or not this is the odd parity.
*
* @return {@code true} if that condition holds
*/
public boolean isOdd() {
return this == ODD;
}
@Override
public PcodeParity evalUnaryExpression(
UnaryOperator operator,
PcodeParity arg,
ProgramPoint pp,
SemanticOracle oracle) {
return arg;
}
@Override
public PcodeParity evalBinaryExpression(
BinaryOperator operator,
PcodeParity left,
PcodeParity right,
ProgramPoint pp,
SemanticOracle oracle) {
if (left.isTop() || right.isTop()) {
return top();
}
PcodeLocation ploc = (PcodeLocation) pp.getLocation();
PcodeOp op = ploc.op;
int opcode = op.getOpcode();
if (opcode == PcodeOp.INT_ADD || opcode == PcodeOp.FLOAT_ADD ||
opcode == PcodeOp.INT_SUB || opcode == PcodeOp.FLOAT_SUB) {
return (right.equals(left)) ? EVEN : ODD;
}
else if (opcode == PcodeOp.INT_AND || opcode == PcodeOp.BOOL_AND) {
return (right.equals(left)) ? left : EVEN;
}
else if (opcode == PcodeOp.INT_OR || opcode == PcodeOp.BOOL_OR) {
return (right.equals(left)) ? left : ODD;
}
else if (opcode == PcodeOp.INT_XOR || opcode == PcodeOp.BOOL_XOR) {
return (right.equals(left)) ? EVEN : ODD;
}
else if (opcode == PcodeOp.INT_MULT || opcode == PcodeOp.FLOAT_MULT) {
return left.isEven() || right.isEven() ? EVEN : ODD;
}
else if (opcode == PcodeOp.INT_DIV || opcode == PcodeOp.FLOAT_DIV) {
if (left.isOdd()) {
return right.isOdd() ? ODD : EVEN;
}
return right.isOdd() ? EVEN : TOP;
}
else if (opcode == PcodeOp.INT_REM || opcode == PcodeOp.INT_SREM) {
return TOP;
}
else if (opcode == PcodeOp.INT_AND) {
if (left.equals(EVEN) || right.equals(EVEN)) {
return EVEN;
}
return ODD;
}
else if (opcode == PcodeOp.INT_OR) {
if (left.equals(ODD) || right.equals(ODD)) {
return ODD;
}
return EVEN;
}
else if (opcode == PcodeOp.INT_XOR) {
if (left.equals(right))
return EVEN;
return ODD;
}
return left;
}
@Override
public PcodeParity lubAux(
PcodeParity other)
throws SemanticException {
return TOP;
}
@Override
public boolean lessOrEqualAux(
PcodeParity other)
throws SemanticException {
return false;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + parity;
return result;
}
@Override
public boolean equals(
Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
PcodeParity other = (PcodeParity) obj;
if (parity != other.parity) {
return false;
}
return true;
}
@Override
public ValueEnvironment<PcodeParity> assumeBinaryExpression(
ValueEnvironment<PcodeParity> environment,
BinaryOperator operator,
ValueExpression left,
ValueExpression right,
ProgramPoint src,
ProgramPoint dest,
SemanticOracle oracle)
throws SemanticException {
if (operator instanceof ComparisonEq) {
if (left instanceof Identifier) {
PcodeParity eval = eval(right, environment, src, oracle);
if (eval.isBottom()) {
return environment.bottom();
}
return environment.putState((Identifier) left, eval);
}
else if (right instanceof Identifier) {
PcodeParity eval = eval(left, environment, src, oracle);
if (eval.isBottom()) {
return environment.bottom();
}
return environment.putState((Identifier) right, eval);
}
}
return environment;
}
@Override
public PcodeParity getValue(RegisterValue rv) {
if (rv != null) {
BigInteger val = rv.getUnsignedValue();
if (val != null) {
return new PcodeParity((byte) (val.longValue() % 2 == 0 ? 3 : 2));
}
}
return top();
}
}

View File

@@ -0,0 +1,389 @@
/* ###
* IP: MIT
*/
package ghidra.lisa.pcode.analyses;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.Predicate;
import org.apache.commons.collections4.CollectionUtils;
import ghidra.lisa.pcode.locations.PcodeLocation;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.util.Msg;
import it.unive.lisa.analysis.*;
import it.unive.lisa.analysis.lattices.Satisfiability;
import it.unive.lisa.analysis.nonrelational.value.ValueEnvironment;
import it.unive.lisa.analysis.value.ValueDomain;
import it.unive.lisa.program.cfg.ProgramPoint;
import it.unive.lisa.symbolic.value.*;
import it.unive.lisa.util.numeric.MathNumber;
import it.unive.lisa.util.representation.*;
/**
* /** The pentagons abstract domain, a weakly relational numeric abstract
* domain. This abstract domain captures properties of the form of x \in [a, b]
* &and; x &lt; y. It is more precise than the well known interval domain, but
* it is less precise than the octagon domain. It is implemented as a
* {@link ValueDomain}.
*
* <p>
* Modified to handle pcode from original source written by:
* <p>
* @author <a href="mailto:luca.negrini@unive.it">Luca Negrini</a>
* @author <a href="mailto:vincenzo.arceri@unipr.it">Vincenzo Arceri</a>
*
* <p>
* @see <a href=
* "https://www.sciencedirect.com/science/article/pii/S0167642309000719?ref=cra_js_challenge&fr=RR-1">Pentagons:
* A weakly relational abstract domain for the efficient validation of
* array accesses</a>
*/
public class PcodePentagon implements ValueDomain<PcodePentagon>, BaseLattice<PcodePentagon> {
/**
* The interval environment.
*/
private final ValueEnvironment<PcodeInterval> intervals;
/**
* The upper bounds environment.
*/
private final ValueEnvironment<PcodeUpperBounds> upperBounds;
/**
* Builds the PcodePentagons.
*/
public PcodePentagon() {
this.intervals = new ValueEnvironment<>(new PcodeInterval()).top();
this.upperBounds = new ValueEnvironment<>(new PcodeUpperBounds(true)).top();
}
/**
* Builds the pentagons.
*
* @param intervals the interval environment
* @param upperBounds the upper bounds environment
*/
public PcodePentagon(
ValueEnvironment<PcodeInterval> intervals,
ValueEnvironment<PcodeUpperBounds> upperBounds) {
this.intervals = intervals;
this.upperBounds = upperBounds;
}
@Override
public PcodePentagon assign(
Identifier id,
ValueExpression expression,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
ValueEnvironment<PcodeUpperBounds> newBounds =
getUpperBounds().assign(id, expression, pp, oracle);
ValueEnvironment<PcodeInterval> newIntervals =
getIntervals().assign(id, expression, pp, oracle);
// we add the semantics for assignments here as we have access to the
// whole assignment
if (expression instanceof BinaryExpression) {
BinaryExpression be = (BinaryExpression) expression;
PcodeLocation ploc = (PcodeLocation) pp.getLocation();
PcodeOp op = ploc.op;
int opcode = op.getOpcode();
if (opcode == PcodeOp.INT_SUB || opcode == PcodeOp.FLOAT_SUB) {
if (be.getLeft() instanceof Identifier x) {
if (be.getRight() instanceof Constant) {
// r = x - c
newBounds = newBounds.putState(id, getUpperBounds().getState(x).add(x));
}
else if (be.getRight() instanceof Identifier) {
// r = x - y
Identifier y = (Identifier) be.getRight();
if (newBounds.getState(y).contains(x)) {
newIntervals = newIntervals.putState(id, newIntervals.getState(id)
.glb(new PcodeInterval(MathNumber.ZERO,
MathNumber.PLUS_INFINITY))); // was MathNumber.ONE
}
}
}
}
else if ((opcode == PcodeOp.INT_REM || opcode == PcodeOp.INT_SREM) &&
be.getRight() instanceof Identifier d) {
// r = u % d
MathNumber low = getIntervals().getState(d).interval.getLow();
if (low.isPositive() || low.isZero()) {
newBounds =
newBounds.putState(id, new PcodeUpperBounds(Collections.singleton(d)));
}
else {
newBounds = newBounds.putState(id, new PcodeUpperBounds().top());
}
}
}
return new PcodePentagon(
newIntervals,
newBounds).closure();
}
@Override
public PcodePentagon smallStepSemantics(
ValueExpression expression,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
return new PcodePentagon(
getIntervals().smallStepSemantics(expression, pp, oracle),
getUpperBounds().smallStepSemantics(expression, pp, oracle));
}
@Override
public PcodePentagon assume(
ValueExpression expression,
ProgramPoint src,
ProgramPoint dest,
SemanticOracle oracle)
throws SemanticException {
return new PcodePentagon(
getIntervals().assume(expression, src, dest, oracle),
getUpperBounds().assume(expression, src, dest, oracle));
}
@Override
public PcodePentagon forgetIdentifier(
Identifier id)
throws SemanticException {
return new PcodePentagon(
getIntervals().forgetIdentifier(id),
getUpperBounds().forgetIdentifier(id));
}
@Override
public PcodePentagon forgetIdentifiersIf(
Predicate<Identifier> test)
throws SemanticException {
return new PcodePentagon(
getIntervals().forgetIdentifiersIf(test),
getUpperBounds().forgetIdentifiersIf(test));
}
@Override
public Satisfiability satisfies(
ValueExpression expression,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
return getIntervals().satisfies(expression, pp, oracle)
.glb(getUpperBounds().satisfies(expression, pp, oracle));
}
@Override
public PcodePentagon pushScope(
ScopeToken token)
throws SemanticException {
return new PcodePentagon(getIntervals().pushScope(token),
getUpperBounds().pushScope(token));
}
@Override
public PcodePentagon popScope(
ScopeToken token)
throws SemanticException {
return new PcodePentagon(getIntervals().popScope(token), getUpperBounds().popScope(token));
}
@Override
public StructuredRepresentation representation() {
if (isTop())
return Lattice.topRepresentation();
if (isBottom())
return Lattice.bottomRepresentation();
Map<StructuredRepresentation, StructuredRepresentation> mapping = new HashMap<>();
for (Identifier id : CollectionUtils.union(getIntervals().getKeys(),
getUpperBounds().getKeys())) {
mapping.put(new StringRepresentation(id),
new StringRepresentation(getIntervals().getState(id).toString() + ", " +
getUpperBounds().getState(id).representation()));
}
return new MapRepresentation(mapping);
}
@Override
public PcodePentagon top() {
return new PcodePentagon(getIntervals().top(), getUpperBounds().top());
}
@Override
public boolean isTop() {
return getIntervals().isTop() && getUpperBounds().isTop();
}
@Override
public PcodePentagon bottom() {
return new PcodePentagon(getIntervals().bottom(), getUpperBounds().bottom());
}
@Override
public boolean isBottom() {
return getIntervals().isBottom() && getUpperBounds().isBottom();
}
private PcodePentagon closure() throws SemanticException {
ValueEnvironment<PcodeUpperBounds> newBounds = new ValueEnvironment<PcodeUpperBounds>(
getUpperBounds().lattice, getUpperBounds().getMap());
for (Identifier id1 : getIntervals().getKeys()) {
Set<Identifier> closure = new HashSet<>();
for (Identifier id2 : getIntervals().getKeys()) {
if (!id1.equals(id2)) {
PcodeInterval state1 = getIntervals().getState(id1);
LongInterval interval1 = state1.interval;
PcodeInterval state2 = getIntervals().getState(id2);
LongInterval interval2 = state2.interval;
if (interval1 != null && interval2 != null) {
if (interval1.getHigh().compareTo(interval2.getLow()) < 0) {
closure.add(id2);
}
}
else {
Msg.error(this, "Unexpected combination: " + state1 + " : " + state2);
}
}
}
if (!closure.isEmpty()) {
// glb is the union
newBounds = newBounds.putState(id1,
newBounds.getState(id1).glb(new PcodeUpperBounds(closure)));
}
}
return new PcodePentagon(getIntervals(), newBounds);
}
@Override
public PcodePentagon lubAux(
PcodePentagon other)
throws SemanticException {
ValueEnvironment<PcodeUpperBounds> newBounds = getUpperBounds().lub(other.getUpperBounds());
for (Entry<Identifier, PcodeUpperBounds> entry : getUpperBounds()) {
Set<Identifier> closure = new HashSet<>();
for (Identifier bound : entry.getValue()) {
PcodeInterval entryState = other.getIntervals().getState(entry.getKey());
LongInterval entryInterval = entryState.interval;
PcodeInterval boundsState = other.getIntervals().getState(bound);
LongInterval boundsInterval = boundsState.interval;
if (entryInterval != null && boundsInterval != null) {
if (entryInterval.getHigh().compareTo(boundsInterval.getLow()) < 0) {
closure.add(bound);
}
}
else {
Msg.error(this, "Unexpected combination: " + entryState + " : " + boundsState);
}
}
if (!closure.isEmpty()) {
// glb is the union
newBounds = newBounds.putState(entry.getKey(),
newBounds.getState(entry.getKey()).glb(new PcodeUpperBounds(closure)));
}
}
for (Entry<Identifier, PcodeUpperBounds> entry : other.getUpperBounds()) {
Set<Identifier> closure = new HashSet<>();
for (Identifier bound : entry.getValue()) {
PcodeInterval entryState = getIntervals().getState(entry.getKey());
LongInterval entryInterval = entryState.interval;
PcodeInterval boundsState = getIntervals().getState(bound);
LongInterval boundsInterval = boundsState.interval;
if (entryInterval != null && boundsInterval != null) {
if (entryInterval.getHigh().compareTo(boundsInterval.getLow()) < 0) {
closure.add(bound);
}
}
else {
Msg.error(this, "Unexpected combination: " + entryState + " : " + boundsState);
}
}
if (!closure.isEmpty()) {
// glb is the union
newBounds = newBounds.putState(entry.getKey(),
newBounds.getState(entry.getKey()).glb(new PcodeUpperBounds(closure)));
}
}
return new PcodePentagon(getIntervals().lub(other.getIntervals()), newBounds);
}
@Override
public PcodePentagon wideningAux(
PcodePentagon other)
throws SemanticException {
return new PcodePentagon(getIntervals().widening(other.getIntervals()),
getUpperBounds().widening(other.getUpperBounds()));
}
@Override
public boolean lessOrEqualAux(
PcodePentagon other)
throws SemanticException {
if (!getIntervals().lessOrEqual(other.getIntervals())) {
return false;
}
for (Entry<Identifier, PcodeUpperBounds> entry : other.getUpperBounds()) {
for (Identifier bound : entry.getValue()) {
if (!(getUpperBounds().getState(entry.getKey()).contains(bound) ||
getIntervals().getState(entry.getKey()).interval.getHigh()
.compareTo(getIntervals().getState(bound).interval.getLow()) < 0)) {
return false;
}
}
}
return true;
}
@Override
public int hashCode() {
return Objects.hash(getIntervals(), getUpperBounds());
}
@Override
public boolean equals(
Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
PcodePentagon other = (PcodePentagon) obj;
return Objects.equals(getIntervals(), other.getIntervals()) &&
Objects.equals(getUpperBounds(), other.getUpperBounds());
}
@Override
public String toString() {
return representation().toString();
}
@Override
public boolean knowsIdentifier(
Identifier id) {
return getIntervals().knowsIdentifier(id) || getUpperBounds().knowsIdentifier(id);
}
public ValueEnvironment<PcodeInterval> getIntervals() {
return intervals;
}
public ValueEnvironment<PcodeUpperBounds> getUpperBounds() {
return upperBounds;
}
}

View File

@@ -0,0 +1,520 @@
/* ###
* IP: MIT
*/
package ghidra.lisa.pcode.analyses;
import java.math.BigInteger;
import ghidra.lisa.pcode.locations.PcodeLocation;
import ghidra.lisa.pcode.statements.PcodeBinaryOperator;
import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.util.Msg;
import it.unive.lisa.analysis.*;
import it.unive.lisa.analysis.lattices.Satisfiability;
import it.unive.lisa.analysis.nonrelational.value.BaseNonRelationalValueDomain;
import it.unive.lisa.analysis.nonrelational.value.ValueEnvironment;
import it.unive.lisa.program.cfg.ProgramPoint;
import it.unive.lisa.symbolic.value.*;
import it.unive.lisa.symbolic.value.operator.binary.*;
import it.unive.lisa.symbolic.value.operator.ternary.TernaryOperator;
import it.unive.lisa.symbolic.value.operator.unary.UnaryOperator;
import it.unive.lisa.util.representation.StringRepresentation;
import it.unive.lisa.util.representation.StructuredRepresentation;
/**
* The basic overflow-insensitive Sign abstract domain, tracking zero, strictly
* positive and strictly negative integer values, implemented as a
* {@link BaseNonRelationalValueDomain}, handling top and bottom values for the
* expression evaluation and bottom values for the expression satisfiability.
* Top and bottom cases for least upper bounds, widening and less or equals
* operations are handled by {@link BaseLattice} in {@link BaseLattice#lub},
* {@link BaseLattice#widening} and {@link BaseLattice#lessOrEqual} methods,
* respectively.
*
* <p>
* Modified to handle pcode from original source written by:
* <p>
* @author <a href="mailto:vincenzo.arceri@unive.it">Vincenzo Arceri</a>
*/
public class PcodeSign implements PcodeNonRelationalValueDomain<PcodeSign> {
public static final PcodeSign POS = new PcodeSign((byte) 4);
public static final PcodeSign NEG = new PcodeSign((byte) 3);
public static final PcodeSign ZERO = new PcodeSign((byte) 2);
public static final PcodeSign TOP = new PcodeSign((byte) 0);
public static final PcodeSign BOTTOM = new PcodeSign((byte) 1);
private final byte sign;
/**
* Builds the sign abstract domain, representing the top of the sign
* abstract domain.
*/
public PcodeSign() {
this((byte) 0);
}
/**
* Builds the sign instance for the given sign value.
*
* @param sign the sign (0 = top, 1 = bottom, 2 = zero, 3 = negative, 4 =
* positive)
*/
public PcodeSign(byte sign) {
this.sign = sign;
}
@Override
public PcodeSign evalNullConstant(
ProgramPoint pp,
SemanticOracle oracle) {
return top();
}
@Override
public PcodeSign evalNonNullConstant(
Constant constant,
ProgramPoint pp,
SemanticOracle oracle) {
Object cval = constant.getValue();
if (cval instanceof Long lval) {
return lval == 0 ? ZERO : lval > 0 ? POS : NEG;
}
if (cval instanceof Integer ival) {
return ival == 0 ? ZERO : ival > 0 ? POS : NEG;
}
if (cval instanceof Short sval) {
return sval == 0 ? ZERO : sval > 0 ? POS : NEG;
}
if (cval instanceof Byte bval) {
return bval == 0 ? ZERO : bval > 0 ? POS : NEG;
}
if (cval instanceof Boolean bval) {
return bval ? POS : ZERO;
}
Msg.error(this, "Unknown type for constant: " + cval);
return top();
}
/**
* Yields whether or not this is the positive sign.
*
* @return {@code true} if that condition holds
*/
public boolean isPositive() {
return this == POS;
}
/**
* Yields whether or not this is the zero sign.
*
* @return {@code true} if that condition holds
*/
public boolean isZero() {
return this == ZERO;
}
/**
* Yields whether or not this is the negative sign.
*
* @return {@code true} if that condition holds
*/
public boolean isNegative() {
return this == NEG;
}
/**
* Yields the sign opposite to this one. Top and bottom elements do not
* change.
*
* @return the opposite sign
*/
public PcodeSign opposite() {
if (isTop() || isBottom()) {
return this;
}
return isPositive() ? NEG : isNegative() ? POS : ZERO;
}
@Override
public PcodeSign evalUnaryExpression(
UnaryOperator operator,
PcodeSign arg,
ProgramPoint pp,
SemanticOracle oracle) {
PcodeLocation ploc = (PcodeLocation) pp.getLocation();
PcodeOp op = ploc.op;
int opcode = op.getOpcode();
if (opcode == PcodeOp.INT_NEGATE || opcode == PcodeOp.INT_2COMP ||
opcode == PcodeOp.FLOAT_NEG) {
if (arg.isPositive()) {
return NEG;
}
else if (arg.isNegative()) {
return POS;
}
else if (arg.isZero()) {
return ZERO;
}
}
if (opcode == PcodeOp.FLOAT_ABS) {
return POS;
}
return arg;
}
@Override
public PcodeSign evalBinaryExpression(
BinaryOperator operator,
PcodeSign left,
PcodeSign right,
ProgramPoint pp,
SemanticOracle oracle) {
PcodeLocation ploc = (PcodeLocation) pp.getLocation();
PcodeOp op = ploc.op;
int opcode = op.getOpcode();
if (opcode == PcodeOp.INT_ADD || opcode == PcodeOp.FLOAT_ADD) {
if (left.isZero()) {
return right;
}
else if (right.isZero()) {
return left;
}
else if (left.equals(right)) {
return left;
}
return top();
}
if (opcode == PcodeOp.INT_SUB || opcode == PcodeOp.FLOAT_SUB ||
opcode == PcodeOp.INT_XOR) {
if (left.isZero()) {
return right.opposite();
}
else if (right.isZero()) {
return left;
}
else if (left.equals(right)) {
return top();
}
return left;
}
else if (opcode == PcodeOp.INT_SDIV || opcode == PcodeOp.FLOAT_DIV) {
if (right.isZero()) {
return bottom();
}
else if (left.isZero()) {
return ZERO;
}
else if (left.equals(right)) {
// top/top = top
// +/+ = +
// -/- = +
return left.isTop() ? left : POS;
}
else if (!left.isTop() && left.equals(right.opposite())) {
// +/- = -
// -/+ = -
return NEG;
}
else {
return top();
}
}
else if (opcode == PcodeOp.INT_MULT || opcode == PcodeOp.FLOAT_MULT) {
if (left.isZero() || right.isZero()) {
return ZERO;
}
else if (left.equals(right)) {
return POS;
}
else {
return NEG;
}
}
else if (opcode == PcodeOp.INT_AND) {
if (left.isZero() || right.isZero()) {
return ZERO;
}
else if (left.equals(POS) || right.equals(POS)) {
return POS;
}
else {
return NEG;
}
}
else if (opcode == PcodeOp.INT_OR) {
if (left.isZero() && right.isZero()) {
return ZERO;
}
else if (left.equals(NEG) || right.equals(NEG)) {
return NEG;
}
else {
return POS;
}
}
else if (opcode == PcodeOp.INT_XOR) {
if (left.equals(right)) {
return POS;
}
return NEG;
}
else {
return left;
}
}
@Override
public PcodeSign lubAux(
PcodeSign other)
throws SemanticException {
return TOP;
}
@Override
public boolean lessOrEqualAux(
PcodeSign other)
throws SemanticException {
return false;
}
@Override
public PcodeSign top() {
return TOP;
}
@Override
public PcodeSign bottom() {
return BOTTOM;
}
@Override
public String toString() {
return representation().toString();
}
@Override
public StructuredRepresentation representation() {
if (isBottom()) {
return Lattice.bottomRepresentation();
}
if (isTop()) {
return Lattice.topRepresentation();
}
String repr;
if (this == ZERO) {
repr = "0";
}
else if (this == POS) {
repr = "+";
}
else {
repr = "-";
}
return new StringRepresentation(repr);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + sign;
return result;
}
@Override
public boolean equals(
Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
PcodeSign other = (PcodeSign) obj;
if (sign != other.sign) {
return false;
}
return true;
}
@Override
public Satisfiability satisfiesBinaryExpression(
BinaryOperator operator,
PcodeSign left,
PcodeSign right,
ProgramPoint pp,
SemanticOracle oracle) {
if (left.isTop() || right.isTop()) {
return Satisfiability.UNKNOWN;
}
if (operator instanceof ComparisonEq) {
return left.eq(right);
}
// e1 <= e2 same as !(e1 > e2)
if (operator instanceof ComparisonLe) {
return left.gt(right).negate();
}
// e1 < e2 -> !(e1 >= e2) && !(e1 == e2)
if (operator instanceof ComparisonLt) {
return left.gt(right)
.negate()
.and(left.eq(right).negate());
}
if (operator instanceof ComparisonNe) {
return left.eq(right).negate();
}
return Satisfiability.UNKNOWN;
}
/**
* Tests if this instance is equal to the given one, returning a
* {@link Satisfiability} element.
*
* @param other the instance
*
* @return the satisfiability of {@code this = other}
*/
public Satisfiability eq(
PcodeSign other) {
if (!this.equals(other)) {
return Satisfiability.NOT_SATISFIED;
}
else if (isZero()) {
return Satisfiability.SATISFIED;
}
else {
return Satisfiability.UNKNOWN;
}
}
/**
* Tests if this instance is greater than the given one, returning a
* {@link Satisfiability} element.
*
* @param other the instance
*
* @return the satisfiability of {@code this > other}
*/
public Satisfiability gt(
PcodeSign other) {
if (this.equals(other)) {
return this.isZero() ? Satisfiability.NOT_SATISFIED : Satisfiability.UNKNOWN;
}
else if (this.isZero()) {
return other.isPositive() ? Satisfiability.NOT_SATISFIED : Satisfiability.SATISFIED;
}
else if (this.isPositive()) {
return Satisfiability.SATISFIED;
}
else {
return Satisfiability.NOT_SATISFIED;
}
}
@Override
public Satisfiability satisfiesTernaryExpression(
TernaryOperator operator,
PcodeSign left,
PcodeSign middle,
PcodeSign right,
ProgramPoint pp,
SemanticOracle oracle) {
return Satisfiability.UNKNOWN;
}
@Override
public ValueEnvironment<PcodeSign> assumeBinaryExpression(
ValueEnvironment<PcodeSign> environment,
BinaryOperator operator,
ValueExpression left,
ValueExpression right,
ProgramPoint src,
ProgramPoint dest,
SemanticOracle oracle)
throws SemanticException {
Identifier id;
PcodeSign eval;
boolean rightIsExpr;
if (left instanceof Identifier) {
eval = eval(right, environment, src, oracle);
id = (Identifier) left;
rightIsExpr = true;
}
else if (right instanceof Identifier) {
eval = eval(left, environment, src, oracle);
id = (Identifier) right;
rightIsExpr = false;
}
else {
return environment;
}
PcodeSign starting = environment.getState(id);
if (eval.isBottom() || starting.isBottom()) {
return environment.bottom();
}
PcodeSign update = null;
if (!(operator instanceof PcodeBinaryOperator)) {
if (operator instanceof ComparisonEq) {
update = eval;
}
else if (operator instanceof ComparisonLe) {
if (rightIsExpr && eval.isNegative()) {
update = eval;
}
else if (!rightIsExpr && eval.isPositive()) {
update = eval;
}
}
else if (operator instanceof ComparisonLt) {
if (rightIsExpr && (eval.isNegative() || eval.isZero())) {
// x < 0/-
update = NEG;
}
else if (!rightIsExpr && (eval.isPositive() || eval.isZero())) {
// 0/+ < x
update = POS;
}
}
}
if (update == null) {
return environment;
}
else if (update.isBottom()) {
return environment.bottom();
}
else {
return environment.putState(id, update);
}
}
@Override
public PcodeSign getValue(RegisterValue rv) {
if (rv != null) {
BigInteger val = rv.getUnsignedValue();
if (val != null) {
if (val.longValue() == 0L) {
return new PcodeSign();
}
return new PcodeSign((byte) (val.longValue() > 0 ? 1 : -1));
}
}
return top();
}
}

View File

@@ -0,0 +1,792 @@
/* ###
* IP: MIT
*/
package ghidra.lisa.pcode.analyses;
import java.util.*;
import java.util.function.Predicate;
import ghidra.lisa.pcode.statements.PcodeBinaryOperator;
import ghidra.lisa.pcode.statements.PcodeUnaryOperator;
import ghidra.program.model.pcode.PcodeOp;
import it.unive.lisa.analysis.*;
import it.unive.lisa.analysis.lattices.Satisfiability;
import it.unive.lisa.analysis.nonrelational.value.ValueEnvironment;
import it.unive.lisa.analysis.stability.Trend;
import it.unive.lisa.analysis.value.ValueDomain;
import it.unive.lisa.program.SyntheticLocation;
import it.unive.lisa.program.cfg.ProgramPoint;
import it.unive.lisa.symbolic.SymbolicExpression;
import it.unive.lisa.symbolic.value.*;
import it.unive.lisa.symbolic.value.operator.binary.*;
import it.unive.lisa.util.representation.ObjectRepresentation;
import it.unive.lisa.util.representation.StructuredRepresentation;
/**
* Implementation of the stability abstract domain (yet to appear publicly).
* This domain computes per-variable numerical trends to infer stability,
* covariance and contravariance relations on program variables, exploiting an
* auxiliary domain of choice. This is implemented as an open product where the
* stability domain gathers information from the auxiliary one through boolean
* queries.<br>
* <br>
* Implementation-wise, this class is built as a product between a given
* {@link ValueDomain} {@code aux} and a {@link ValueEnvironment} {@code trends}
* of {@link Trend} instances, representing per-variable trends. Queries are
* carried over by the
* {@link SemanticDomain#satisfies(SymbolicExpression, ProgramPoint, SemanticOracle)}
* operator invoked on {@code aux}.
*
* <p>
* Modified to handle pcode from original source written by:
* <p>
* @author <a href="mailto:luca.negrini@unive.it">Luca Negrini</a>
*
* @param <V> the kind of auxiliary domain
*/
public class PcodeStability<V extends ValueDomain<V>>
implements
BaseLattice<PcodeStability<V>>,
ValueDomain<PcodeStability<V>> {
private final V aux;
private final ValueEnvironment<Trend> trends;
/**
* Builds the top stability domain, using {@code aux} as auxiliary domain.
*
* @param aux the auxiliary domain
*/
public PcodeStability(
V aux) {
this.aux = aux.top();
this.trends = new ValueEnvironment<>(Trend.TOP);
}
/**
* Builds a stability domain instance, using {@code aux} as auxiliary
* domain.
*
* @param aux the auxiliary domain
* @param trends the existing per-variable trend information
*/
public PcodeStability(
V aux,
ValueEnvironment<Trend> trends) {
this.aux = trends.isBottom() ? aux.bottom() : aux;
this.trends = aux.isBottom() ? trends.bottom() : trends;
}
@Override
public PcodeStability<V> lubAux(
PcodeStability<V> other)
throws SemanticException {
V ad = aux.lub(other.aux);
ValueEnvironment<Trend> t = trends.lub(other.trends);
if (ad.isBottom() || t.isBottom()) {
return bottom();
}
return new PcodeStability<>(ad, t);
}
@Override
public PcodeStability<V> glbAux(
PcodeStability<V> other)
throws SemanticException {
V ad = aux.glb(other.aux);
ValueEnvironment<Trend> t = trends.glb(other.trends);
if (ad.isBottom() || t.isBottom()) {
return bottom();
}
return new PcodeStability<>(ad, t);
}
@Override
public PcodeStability<V> wideningAux(
PcodeStability<V> other)
throws SemanticException {
V ad = aux.widening(other.aux);
ValueEnvironment<Trend> t = trends.widening(other.trends);
if (ad.isBottom() || t.isBottom()) {
return bottom();
}
return new PcodeStability<>(ad, t);
}
@Override
public boolean lessOrEqualAux(
PcodeStability<V> other)
throws SemanticException {
return aux.lessOrEqual(other.aux) && trends.lessOrEqual(other.trends);
}
@Override
public boolean isTop() {
return aux.isTop() && trends.isTop();
}
@Override
public boolean isBottom() {
return aux.isBottom() || trends.isBottom();
}
@Override
public PcodeStability<V> top() {
return new PcodeStability<>(aux.top(), trends.top());
}
@Override
public PcodeStability<V> bottom() {
return new PcodeStability<>(aux.bottom(), trends.bottom());
}
@Override
public PcodeStability<V> pushScope(
ScopeToken token)
throws SemanticException {
return new PcodeStability<>(aux.pushScope(token), trends.pushScope(token));
}
@Override
public PcodeStability<V> popScope(
ScopeToken token)
throws SemanticException {
return new PcodeStability<>(aux.popScope(token), trends.popScope(token));
}
/**
* Yields {@code true} if the {@code aux.satisfies(query, pp, oracle)}
* returns {@link Satisfiability#SATISFIED}.
*
* @param query the query to execute
* @param pp the {@link ProgramPoint} where the evaluation happens
* @param oracle the oracle for inter-domain communication
*
* @return {@code true} if the query is always satisfied
*
* @throws SemanticException if something goes wrong during the evaluation
*/
private boolean query(
BinaryExpression query,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
return aux.satisfies(query, pp, oracle) == Satisfiability.SATISFIED;
}
/**
* Builds a {@link BinaryExpression} in the form of "l operator r".
*
* @param operator the {@link BinaryOperator} to apply
* @param l the left operand
* @param r the right operand
* @param pp the {@link ProgramPoint} where the expression is being
* built
*
* @return the new BinaryExpression
*/
private BinaryExpression binary(
BinaryOperator operator,
SymbolicExpression l,
SymbolicExpression r,
ProgramPoint pp) {
return new BinaryExpression(
pp.getProgram().getTypes().getBooleanType(),
l,
r,
operator,
SyntheticLocation.INSTANCE);
}
/**
* Builds a {@link Constant} with value {@code c}.
*
* @param c the integer constant
* @param pp the {@link ProgramPoint} where the expression is being built
*
* @return the new Constant
*/
private Constant constantInt(
int c,
ProgramPoint pp) {
return new Constant(
pp.getProgram().getTypes().getIntegerType(),
c,
SyntheticLocation.INSTANCE);
}
/**
* Generates a {@link Trend} based on the relationship between {@code a} and
* {@code b} in {@link #aux}.
*
* @param a the first expression
* @param b the second expression
* @param pp the {@link ProgramPoint} where the evaluation happens
* @param oracle the oracle for inter-domain communication
*
* @return {@link Trend#INC} if {@code a > b}
*
* @throws SemanticException if something goes wrong during the evaluation
*/
private Trend increasingIfGreater(
SymbolicExpression a,
SymbolicExpression b,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
if (query(binary(ComparisonEq.INSTANCE, a, b, pp), pp, oracle)) {
return Trend.STABLE;
}
else if (query(binary(ComparisonGt.INSTANCE, a, b, pp), pp, oracle)) {
return Trend.INC;
}
else if (query(binary(ComparisonGe.INSTANCE, a, b, pp), pp, oracle)) {
return Trend.NON_DEC;
}
else if (query(binary(ComparisonLt.INSTANCE, a, b, pp), pp, oracle)) {
return Trend.DEC;
}
else if (query(binary(ComparisonLe.INSTANCE, a, b, pp), pp, oracle)) {
return Trend.NON_INC;
}
else if (query(binary(ComparisonNe.INSTANCE, a, b, pp), pp, oracle)) {
return Trend.NON_STABLE;
}
else {
return Trend.TOP;
}
}
/**
* Generates a {@link Trend} based on the relationship between {@code a} and
* {@code b} in {@link #aux}.
*
* @param a the first expression
* @param b the second expression
* @param pp the {@link ProgramPoint} where the evaluation happens
* @param oracle the oracle for inter-domain communication
*
* @return {@link Trend#INC} if {@code a < b}
*
* @throws SemanticException if something goes wrong during the evaluation
*/
private Trend increasingIfLess(
SymbolicExpression a,
SymbolicExpression b,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
return increasingIfGreater(a, b, pp, oracle).invert();
}
/**
* Generates a {@link Trend} based on the relationship between {@code a} and
* {@code b} in {@link #aux}.
*
* @param a the first expression
* @param b the second expression
* @param pp the {@link ProgramPoint} where the evaluation happens
* @param oracle the oracle for inter-domain communication
*
* @return {@link Trend#NON_DEC} if {@code a > b}
*
* @throws SemanticException if something goes wrong during the evaluation
*/
private Trend nonDecreasingIfGreater(
SymbolicExpression a,
SymbolicExpression b,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
if (query(binary(ComparisonEq.INSTANCE, a, b, pp), pp, oracle)) {
return Trend.STABLE;
}
else if (query(binary(ComparisonGt.INSTANCE, a, b, pp), pp, oracle) ||
query(binary(ComparisonGe.INSTANCE, a, b, pp), pp, oracle)) {
return Trend.NON_DEC;
}
else if (query(binary(ComparisonLt.INSTANCE, a, b, pp), pp, oracle) ||
query(binary(ComparisonLe.INSTANCE, a, b, pp), pp, oracle)) {
return Trend.NON_INC;
}
else {
return Trend.TOP;
}
}
/**
* Generates a {@link Trend} based on the relationship between {@code a} and
* {@code b} in {@link #aux}.
*
* @param a the first expression
* @param b the second expression
* @param pp the {@link ProgramPoint} where the evaluation happens
* @param oracle the oracle for inter-domain communication
*
* @return {@link Trend#NON_DEC} if {@code a < b}
*
* @throws SemanticException if something goes wrong during the evaluation
*/
private Trend nonDecreasingIfLess(
SymbolicExpression a,
SymbolicExpression b,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
return nonDecreasingIfGreater(a, b, pp, oracle).invert();
}
/**
* Generates a {@link Trend} based on the relationship between {@code a} and
* {@code b} in {@link #aux}.
*
* @param a the expression
* @param pp the {@link ProgramPoint} where the evaluation happens
* @param oracle the oracle for inter-domain communication
*
* @return {@link Trend#INC} if {@code 0 < a < 1 || 0 <= a < 1}
*
* @throws SemanticException if something goes wrong during the evaluation
*/
private Trend increasingIfBetweenZeroAndOne(
SymbolicExpression a,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
Constant zero = constantInt(0, pp);
Constant one = constantInt(1, pp);
if (query(binary(ComparisonEq.INSTANCE, a, zero, pp), pp, oracle) ||
query(binary(ComparisonEq.INSTANCE, a, one, pp), pp, oracle)) {
return Trend.STABLE;
}
else if (query(binary(ComparisonGt.INSTANCE, a, zero, pp), pp, oracle) &&
query(binary(ComparisonLt.INSTANCE, a, one, pp), pp, oracle)) {
return Trend.INC;
}
else if (query(binary(ComparisonGe.INSTANCE, a, zero, pp), pp, oracle) &&
query(binary(ComparisonLe.INSTANCE, a, one, pp), pp, oracle)) {
return Trend.NON_DEC;
}
else if (query(binary(ComparisonLt.INSTANCE, a, zero, pp), pp, oracle) &&
query(binary(ComparisonGt.INSTANCE, a, one, pp), pp, oracle)) {
return Trend.DEC;
}
else if (query(binary(ComparisonLe.INSTANCE, a, zero, pp), pp, oracle) &&
query(binary(ComparisonGe.INSTANCE, a, one, pp), pp, oracle)) {
return Trend.NON_INC;
}
else if (query(binary(ComparisonNe.INSTANCE, a, zero, pp), pp, oracle) &&
query(binary(ComparisonNe.INSTANCE, a, one, pp), pp, oracle)) {
return Trend.NON_STABLE;
}
else {
return Trend.TOP;
}
}
/**
* Generates a {@link Trend} based on the value of {@code a} in
* {@link #aux}.
*
* @param a the expression
* @param pp the {@link ProgramPoint} where the evaluation happens
* @param oracle the oracle for inter-domain communication
*
* @return {@link Trend#INC} if {@code a < 0 || a > 1}
*
* @throws SemanticException if something goes wrong during the evaluation
*/
private Trend increasingIfOutsideZeroAndOne(
SymbolicExpression a,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
return increasingIfBetweenZeroAndOne(a, pp, oracle).invert();
}
/**
* Generates a {@link Trend} based on the value of {@code a} in
* {@link #aux}.
*
* @param a the expression
* @param pp the {@link ProgramPoint} where the evaluation happens
* @param oracle the oracle for inter-domain communication
*
* @return {@link Trend#NON_DEC} if {@code 0 < a < 1 || 0 <= a < 1}
*
* @throws SemanticException if something goes wrong during the evaluation
*/
private Trend nonDecreasingIfBetweenZeroAndOne(
SymbolicExpression a,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
Constant zero = constantInt(0, pp);
Constant one = constantInt(1, pp);
if (query(binary(ComparisonEq.INSTANCE, a, zero, pp), pp, oracle) ||
query(binary(ComparisonEq.INSTANCE, a, one, pp), pp, oracle)) {
return Trend.STABLE;
}
else if (query(binary(ComparisonGe.INSTANCE, a, zero, pp), pp, oracle) &&
query(binary(ComparisonLe.INSTANCE, a, one, pp), pp, oracle)) {
return Trend.NON_DEC;
}
else if (query(binary(ComparisonLe.INSTANCE, a, zero, pp), pp, oracle) ||
query(binary(ComparisonGe.INSTANCE, a, one, pp), pp, oracle)) {
return Trend.NON_INC;
}
else {
return Trend.TOP;
}
}
/**
* Generates a {@link Trend} based on the value of {@code a} in
* {@link #aux}.
*
* @param a the expression
* @param pp the {@link ProgramPoint} where the evaluation happens
* @param oracle the oracle for inter-domain communication
*
* @return {@link Trend#NON_DEC} if {@code a < 0 || a > 1}
*
* @throws SemanticException if something goes wrong during the evaluation
*/
private Trend nonDecreasingIfOutsideZeroAndOne(
SymbolicExpression a,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
return nonDecreasingIfBetweenZeroAndOne(a, pp, oracle).invert();
}
@Override
public PcodeStability<V> assign(
Identifier id,
ValueExpression expression,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
if (isBottom()) {
return bottom();
}
V post = aux.assign(id, expression, pp, oracle);
if (post.isBottom()) {
return bottom();
}
if (!trends.lattice.canProcess(id, pp, oracle) ||
!trends.lattice.canProcess(expression, pp, oracle)) {
return new PcodeStability<>(post, trends);
}
if (!trends.knowsIdentifier(id)) {
return new PcodeStability<>(post, trends.putState(id, Trend.STABLE));
}
Trend t = Trend.TOP;
t = increasingIfLess(id, expression, pp, oracle);
if (expression instanceof UnaryExpression ue) {
if (ue.getOperator() instanceof PcodeUnaryOperator op) {
int opcode = op.getOp().getOpcode();
if (opcode == PcodeOp.INT_NEGATE || opcode == PcodeOp.INT_2COMP ||
opcode == PcodeOp.FLOAT_NEG) {
t = increasingIfLess(id, expression, pp, oracle);
}
}
}
else if (expression instanceof BinaryExpression be) {
if (be.getOperator() instanceof PcodeBinaryOperator op) {
int opcode = op.getOp().getOpcode();
SymbolicExpression left = be.getLeft();
SymbolicExpression right = be.getRight();
boolean isLeft = id.equals(left);
boolean isRight = id.equals(right);
// x = a / 0
if ((opcode == PcodeOp.INT_DIV || opcode == PcodeOp.FLOAT_DIV) && query(
binary(ComparisonEq.INSTANCE, right, constantInt(0, pp), pp), pp, oracle)) {
return bottom();
}
if (isLeft || isRight) {
SymbolicExpression other = isLeft ? right : left;
if (opcode == PcodeOp.INT_ADD || opcode == PcodeOp.FLOAT_ADD) {
// x = x + other || x = other + x
t = increasingIfGreater(other, constantInt(0, pp), pp, oracle);
}
else if (opcode == PcodeOp.INT_SUB || opcode == PcodeOp.FLOAT_SUB) {
// x = x - other
if (isLeft) {
t = increasingIfLess(other, constantInt(0, pp), pp, oracle);
}
}
else if (opcode == PcodeOp.INT_MULT || opcode == PcodeOp.FLOAT_MULT) {
// x = x * other || x = other * x
if (query(binary(ComparisonEq.INSTANCE, id, constantInt(0, pp), pp), pp,
oracle) ||
query(binary(ComparisonEq.INSTANCE, other, constantInt(1, pp), pp), pp,
oracle)) {
// id == 0 || other == 1
t = Trend.STABLE;
}
else if (query(binary(ComparisonGt.INSTANCE, id, constantInt(0, pp), pp),
pp, oracle)) {
// id > 0
t = increasingIfGreater(other, constantInt(1, pp), pp, oracle);
}
else if (query(binary(ComparisonLt.INSTANCE, id, constantInt(0, pp), pp),
pp, oracle)) {
// id < 0
t = increasingIfLess(other, constantInt(1, pp), pp, oracle);
}
else if (query(binary(ComparisonGe.INSTANCE, id, constantInt(0, pp), pp),
pp, oracle)) {
// id >= 0
t = nonDecreasingIfGreater(other, constantInt(1, pp), pp, oracle);
}
else if (query(binary(ComparisonLe.INSTANCE, id, constantInt(0, pp), pp),
pp, oracle)) {
// id <= 0
t = nonDecreasingIfLess(other, constantInt(1, pp), pp, oracle);
}
else if (query(binary(ComparisonNe.INSTANCE, id, constantInt(0, pp), pp),
pp, oracle) &&
query(binary(ComparisonNe.INSTANCE, other, constantInt(1, pp), pp), pp,
oracle)) {
// id != 0 && other != 1
t = Trend.NON_STABLE;
}
}
else if (opcode == PcodeOp.INT_DIV || opcode == PcodeOp.FLOAT_DIV) {
// x = x / other
if (isLeft) {
if (query(binary(ComparisonEq.INSTANCE, id, constantInt(0, pp), pp), pp,
oracle) ||
query(binary(ComparisonEq.INSTANCE, other, constantInt(1, pp), pp),
pp, oracle)) {
// id == 0 || other == 1
t = Trend.STABLE;
}
else if (query(
binary(ComparisonGt.INSTANCE, id, constantInt(0, pp), pp), pp,
oracle)) {
// id > 0
t = increasingIfBetweenZeroAndOne(other, pp, oracle);
}
else if (query(
binary(ComparisonLt.INSTANCE, id, constantInt(0, pp), pp), pp,
oracle)) {
// id < 0
t = increasingIfOutsideZeroAndOne(other, pp, oracle);
}
else if (query(
binary(ComparisonGe.INSTANCE, id, constantInt(0, pp), pp), pp,
oracle)) {
// id >= 0
t = nonDecreasingIfBetweenZeroAndOne(other, pp, oracle);
}
else if (query(
binary(ComparisonLe.INSTANCE, id, constantInt(0, pp), pp), pp,
oracle)) {
// id <= 0
t = nonDecreasingIfOutsideZeroAndOne(other, pp, oracle);
}
else if (query(
binary(ComparisonNe.INSTANCE, id, constantInt(0, pp), pp), pp,
oracle) &&
query(binary(ComparisonNe.INSTANCE, other, constantInt(1, pp), pp),
pp, oracle)) {
// id != 0 && other != 1
t = Trend.NON_STABLE;
}
}
}
}
}
}
ValueEnvironment<Trend> trnd = stabilize(trends).putState(id, t);
if (trnd.isBottom()) {
return bottom();
}
return new PcodeStability<>(post, trnd);
}
@Override
public PcodeStability<V> smallStepSemantics(
ValueExpression expression,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
V post = aux.smallStepSemantics(expression, pp, oracle);
ValueEnvironment<Trend> sss = stabilize(trends).smallStepSemantics(expression, pp, oracle);
if (post.isBottom() || sss.isBottom()) {
return bottom();
}
return new PcodeStability<>(post, sss);
}
@Override
public PcodeStability<V> assume(
ValueExpression expression,
ProgramPoint src,
ProgramPoint dest,
SemanticOracle oracle)
throws SemanticException {
V post = aux.assume(expression, src, dest, oracle);
ValueEnvironment<Trend> assume = trends.assume(expression, src, dest, oracle);
if (post.isBottom() || assume.isBottom()) {
return bottom();
}
return new PcodeStability<>(post, assume);
}
@Override
public boolean knowsIdentifier(
Identifier id) {
return aux.knowsIdentifier(id) || trends.knowsIdentifier(id);
}
@Override
public PcodeStability<V> forgetIdentifier(
Identifier id)
throws SemanticException {
return new PcodeStability<>(aux.forgetIdentifier(id), trends.forgetIdentifier(id));
}
@Override
public PcodeStability<V> forgetIdentifiersIf(
Predicate<Identifier> test)
throws SemanticException {
return new PcodeStability<>(aux.forgetIdentifiersIf(test),
trends.forgetIdentifiersIf(test));
}
@Override
public Satisfiability satisfies(
ValueExpression expression,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
return aux.satisfies(expression, pp, oracle);
}
@Override
public StructuredRepresentation representation() {
return new ObjectRepresentation(Map.of(
"aux", aux.representation(),
"trends", trends.representation()));
}
@Override
public boolean equals(
Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
PcodeStability<?> other = (PcodeStability<?>) obj;
return Objects.equals(aux, other.aux) && Objects.equals(trends, other.trends);
}
@Override
public int hashCode() {
return Objects.hash(aux, trends);
}
@Override
public String toString() {
return representation().toString();
}
/**
* Yields the per-variable trends contained in this domain instance.
*
* @return the trends
*/
public ValueEnvironment<Trend> getTrends() {
return trends;
}
/**
* Yields the auxiliary domain contained in this domain instance.
*
* @return the auxiliary domain
*/
public V getAuxiliaryDomain() {
return aux;
}
/**
* Yields the combination of the trends in this stability instance with the
* ones contained in the given one. This operation is to be interpreted as
* the sequential concatenation of the two: if two (blocks of) instructions
* are executed sequentially, a variable having {@code t1} trend in the
* former and {@code t2} trend in the latter would have
* {@code t1.combine(t2)} as an overall trend. This delegates to
* {@link Trend#combine(Trend)} for single-trend combination.
*
* @param other the other trends
*
* @return the combination of the two trends
*
* @throws SemanticException if something goes wrong during the computation
*/
public PcodeStability<V> combine(
PcodeStability<V> other)
throws SemanticException {
ValueEnvironment<Trend> result =
new ValueEnvironment<>(other.trends.lattice, other.trends.function);
for (Identifier id : other.trends.getKeys()) {
// we iterate only on the keys of post to remove the ones that went
// out of scope
if (trends.knowsIdentifier(id)) {
Trend tmp = trends.getState(id).combine(other.trends.getState(id));
result = result.putState(id, tmp);
}
}
return new PcodeStability<>(other.aux, result);
}
/**
* Yields a mapping from {@link Trend}s to the {@link Identifier}s having
* that trend.
*
* @return the mapping
*/
public Map<Trend, Set<Identifier>> getCovarianceClasses() {
Map<Trend, Set<Identifier>> map = new HashMap<>();
for (Identifier id : trends.getKeys()) {
Trend t = trends.getState(id);
map.computeIfAbsent(t, k -> new HashSet<>()).add(id);
}
return map;
}
private static ValueEnvironment<Trend> stabilize(
ValueEnvironment<Trend> trends) {
ValueEnvironment<Trend> result = new ValueEnvironment<>(trends.lattice);
for (Identifier id : trends.getKeys()) {
result = result.putState(id, Trend.STABLE);
}
return result;
}
}

View File

@@ -0,0 +1,187 @@
/* ###
* IP: MIT
*/
package ghidra.lisa.pcode.analyses;
import ghidra.lisa.pcode.locations.PcodeLocation;
import it.unive.lisa.analysis.*;
import it.unive.lisa.analysis.taint.BaseTaint;
import it.unive.lisa.program.annotations.Annotation;
import it.unive.lisa.program.annotations.Annotations;
import it.unive.lisa.program.cfg.ProgramPoint;
import it.unive.lisa.symbolic.value.Identifier;
import it.unive.lisa.util.representation.StringRepresentation;
import it.unive.lisa.util.representation.StructuredRepresentation;
/**
* A {@link BaseTaint} implementation with only two level of taintedness: clean
* and tainted. As such, this class distinguishes values that are always clean
* from values that are tainted in at least one execution path.
*
* <p>
* Modified to handle pcode from original source written by:
* <p>
* @author <a href="mailto:luca.negrini@unive.it">Luca Negrini</a>
*/
public class PcodeTaint extends BaseTaint<PcodeTaint> {
private static final PcodeTaint TAINTED = new PcodeTaint(true);
private static final PcodeTaint CLEAN = new PcodeTaint(false);
private static final PcodeTaint BOTTOM = new PcodeTaint(null);
private final Boolean taint;
/**
* Builds a new instance of taint.
*/
public PcodeTaint() {
this(true);
}
public PcodeTaint(
Boolean taint) {
this.taint = taint;
}
@Override
protected PcodeTaint tainted() {
return TAINTED;
}
@Override
protected PcodeTaint clean() {
return CLEAN;
}
@Override
public boolean isPossiblyTainted() {
return this == TAINTED;
}
@Override
public boolean isAlwaysTainted() {
return false;
}
@Override
public StructuredRepresentation representation() {
if (this == BOTTOM) {
return Lattice.bottomRepresentation();
}
return this == TAINTED ? new StringRepresentation("#") : new StringRepresentation("_");
}
@Override
public PcodeTaint top() {
return CLEAN;
}
@Override
public PcodeTaint bottom() {
return BOTTOM;
}
@Override
protected PcodeTaint defaultApprox(
Identifier id,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
Annotations annots = id.getAnnotations();
if (annots.isEmpty()) {
return super.defaultApprox(id, pp, oracle);
}
if (pp.getLocation() instanceof PcodeLocation ploc) {
for (Annotation annotation : annots) {
String name = annotation.getAnnotationName();
if (name.contains("@" + ploc.getAddress())) {
if (name.contains("Tainted")) {
return tainted();
}
if (name.contains("Clean")) {
return clean();
}
}
}
}
return bottom();
}
@Override
public PcodeTaint lub(PcodeTaint other) throws SemanticException {
if (other == null || other.isBottom() || this.isTop() || this == other ||
this.equals(other)) {
return this;
}
if (this.isBottom()) { // || other.isTop())
return other;
}
return lubAux(other);
}
@Override
public PcodeTaint lubAux(
PcodeTaint other)
throws SemanticException {
return TAINTED;
}
@Override
public PcodeTaint wideningAux(
PcodeTaint other)
throws SemanticException {
return TAINTED; // should never happen
}
@Override
public boolean lessOrEqualAux(
PcodeTaint other)
throws SemanticException {
return false; // should never happen
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((taint == null) ? 0 : taint.hashCode());
return result;
}
@Override
public boolean equals(
Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
PcodeTaint other = (PcodeTaint) obj;
if (taint == null) {
if (other.taint != null) {
return false;
}
}
else if (!taint.equals(other.taint)) {
return false;
}
return true;
}
@Override
public String toString() {
return representation().toString();
}
}

View File

@@ -0,0 +1,210 @@
/* ###
* IP: MIT
*/
package ghidra.lisa.pcode.analyses;
import ghidra.lisa.pcode.locations.PcodeLocation;
import it.unive.lisa.analysis.*;
import it.unive.lisa.analysis.taint.BaseTaint;
import it.unive.lisa.program.annotations.Annotation;
import it.unive.lisa.program.annotations.Annotations;
import it.unive.lisa.program.cfg.ProgramPoint;
import it.unive.lisa.symbolic.value.Identifier;
import it.unive.lisa.symbolic.value.operator.binary.BinaryOperator;
import it.unive.lisa.symbolic.value.operator.ternary.TernaryOperator;
import it.unive.lisa.util.representation.StringRepresentation;
import it.unive.lisa.util.representation.StructuredRepresentation;
/**
* A {@link BaseTaint} implementation with three level of taintedness: clean,
* tainted and top. As such, this class distinguishes values that are always
* clean, always tainted, or tainted in at least one execution path.
*
* <p>
* Modified to handle pcode from original source written by:
* <p>
* @author <a href="mailto:luca.negrini@unive.it">Luca Negrini</a>
*/
public class PcodeThreeLevelTaint extends BaseTaint<PcodeThreeLevelTaint> {
private static final PcodeThreeLevelTaint TOP = new PcodeThreeLevelTaint((byte) 3);
private static final PcodeThreeLevelTaint TAINTED = new PcodeThreeLevelTaint((byte) 2);
private static final PcodeThreeLevelTaint CLEAN = new PcodeThreeLevelTaint((byte) 1);
private static final PcodeThreeLevelTaint BOTTOM = new PcodeThreeLevelTaint((byte) 0);
private final byte taint;
/**
* Builds a new instance of taint.
*/
public PcodeThreeLevelTaint() {
this((byte) 3);
}
private PcodeThreeLevelTaint(
byte v) {
this.taint = v;
}
@Override
protected PcodeThreeLevelTaint tainted() {
return TAINTED;
}
@Override
protected PcodeThreeLevelTaint clean() {
return CLEAN;
}
@Override
public boolean isAlwaysTainted() {
return this == TAINTED;
}
@Override
public boolean isPossiblyTainted() {
return this == TOP;
}
@Override
public StructuredRepresentation representation() {
return this == BOTTOM ? Lattice.bottomRepresentation()
: this == CLEAN ? new StringRepresentation("_")
: this == TAINTED ? new StringRepresentation("#")
: Lattice.topRepresentation();
}
@Override
public PcodeThreeLevelTaint top() {
return TOP;
}
@Override
public PcodeThreeLevelTaint bottom() {
return BOTTOM;
}
@Override
protected PcodeThreeLevelTaint defaultApprox(
Identifier id,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
Annotations annots = id.getAnnotations();
if (annots.isEmpty()) {
return super.defaultApprox(id, pp, oracle);
}
if (pp.getLocation() instanceof PcodeLocation ploc) {
for (Annotation annotation : annots) {
String name = annotation.getAnnotationName();
if (name.contains("@" + ploc.getAddress())) {
if (name.contains("Tainted")) {
return tainted();
}
if (name.contains("Clean")) {
return clean();
}
}
}
}
return bottom();
}
@Override
public PcodeThreeLevelTaint evalBinaryExpression(
BinaryOperator operator,
PcodeThreeLevelTaint left,
PcodeThreeLevelTaint right,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
if (left == TAINTED || right == TAINTED) {
return TAINTED;
}
if (left == TOP || right == TOP) {
return TOP;
}
return CLEAN;
}
@Override
public PcodeThreeLevelTaint evalTernaryExpression(
TernaryOperator operator,
PcodeThreeLevelTaint left,
PcodeThreeLevelTaint middle,
PcodeThreeLevelTaint right,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
if (left == TAINTED || right == TAINTED || middle == TAINTED) {
return TAINTED;
}
if (left == TOP || right == TOP || middle == TOP) {
return TOP;
}
return CLEAN;
}
@Override
public PcodeThreeLevelTaint lubAux(
PcodeThreeLevelTaint other)
throws SemanticException {
// only happens with clean and tainted, that are not comparable
return TOP;
}
@Override
public PcodeThreeLevelTaint wideningAux(
PcodeThreeLevelTaint other)
throws SemanticException {
// only happens with clean and tainted, that are not comparable
return TOP;
}
@Override
public boolean lessOrEqualAux(
PcodeThreeLevelTaint other)
throws SemanticException {
// only happens with clean and tainted, that are not comparable
return false;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + taint;
return result;
}
@Override
public boolean equals(
Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
PcodeThreeLevelTaint other = (PcodeThreeLevelTaint) obj;
if (taint != other.taint) {
return false;
}
return true;
}
@Override
public String toString() {
return representation().toString();
}
}

View File

@@ -0,0 +1,245 @@
/* ###
* IP: MIT
*/
package ghidra.lisa.pcode.analyses;
import java.util.*;
import ghidra.lisa.pcode.statements.PcodeBinaryOperator;
import ghidra.program.model.lang.RegisterValue;
import it.unive.lisa.analysis.*;
import it.unive.lisa.analysis.nonrelational.value.BaseNonRelationalValueDomain;
import it.unive.lisa.analysis.nonrelational.value.ValueEnvironment;
import it.unive.lisa.program.cfg.ProgramPoint;
import it.unive.lisa.symbolic.value.Identifier;
import it.unive.lisa.symbolic.value.ValueExpression;
import it.unive.lisa.symbolic.value.operator.binary.*;
import it.unive.lisa.util.representation.*;
/**
* The upper bounds abstract domain. It is implemented as a
* {@link BaseNonRelationalValueDomain}.
*
* <p>
* Modified to handle pcode from original source written by:
* <p>
* @author <a href="mailto:luca.negrini@unive.it">Luca Negrini</a>
* @author <a href="mailto:vincenzo.arceri@unipr.it">Vincenzo Arceri</a>
*/
public class PcodeUpperBounds
implements PcodeNonRelationalValueDomain<PcodeUpperBounds>, Iterable<Identifier> {
/**
* The abstract top element.
*/
private static final PcodeUpperBounds TOP = new PcodeUpperBounds(true);
/**
* The abstract bottom element.
*/
private static final PcodeUpperBounds BOTTOM = new PcodeUpperBounds(new TreeSet<>());
/**
* The flag to set abstract top state.
*/
private final boolean isTop;
/**
* The set containing the bounds.
*/
private final Set<Identifier> bounds;
/**
* Builds the upper bounds.
*/
public PcodeUpperBounds() {
this(true);
}
/**
* Builds the upper bounds.
*
* @param isTop {@code true} if the abstract domain is top; otherwise
* {@code false}.
*/
public PcodeUpperBounds(
boolean isTop) {
this.bounds = null;
this.isTop = isTop;
}
/**
* Builds the upper bounds.
*
* @param bounds the bounds to set
*/
public PcodeUpperBounds(
Set<Identifier> bounds) {
this.bounds = bounds;
this.isTop = false;
}
@Override
public StructuredRepresentation representation() {
if (isTop()) {
return new StringRepresentation("{}");
}
if (isBottom()) {
return Lattice.bottomRepresentation();
}
return new SetRepresentation(bounds, StringRepresentation::new);
}
@Override
public PcodeUpperBounds top() {
return TOP;
}
@Override
public PcodeUpperBounds bottom() {
return BOTTOM;
}
@Override
public boolean isBottom() {
return !isTop && bounds.isEmpty();
}
@Override
public PcodeUpperBounds lubAux(
PcodeUpperBounds other)
throws SemanticException {
Set<Identifier> lub = new HashSet<>(bounds);
lub.retainAll(other.bounds);
return new PcodeUpperBounds(lub);
}
@Override
public PcodeUpperBounds glbAux(
PcodeUpperBounds other)
throws SemanticException {
Set<Identifier> lub = new HashSet<>(bounds);
lub.addAll(other.bounds);
return new PcodeUpperBounds(lub);
}
@Override
public boolean lessOrEqualAux(
PcodeUpperBounds other)
throws SemanticException {
return bounds.containsAll(other.bounds);
}
@Override
public PcodeUpperBounds wideningAux(
PcodeUpperBounds other)
throws SemanticException {
return other.bounds.containsAll(bounds) ? other : TOP;
}
@Override
public boolean equals(
Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
PcodeUpperBounds other = (PcodeUpperBounds) obj;
return Objects.equals(bounds, other.bounds) && isTop == other.isTop;
}
@Override
public int hashCode() {
return Objects.hash(bounds, isTop);
}
@Override
public ValueEnvironment<PcodeUpperBounds> assumeBinaryExpression(
ValueEnvironment<PcodeUpperBounds> environment,
BinaryOperator operator,
ValueExpression left,
ValueExpression right,
ProgramPoint src,
ProgramPoint dest,
SemanticOracle oracle)
throws SemanticException {
if (!(left instanceof Identifier x && right instanceof Identifier y)) {
return environment;
}
// glb is the union!
if (!(operator instanceof PcodeBinaryOperator)) {
if (operator instanceof ComparisonEq) {
// x == y
PcodeUpperBounds set = environment.getState(x).glb(environment.getState(y));
return environment.putState(x, set).putState(y, set);
}
if (operator instanceof ComparisonLt) {
// x < y
PcodeUpperBounds set = environment.getState(x)
.glb(environment.getState(y))
.glb(new PcodeUpperBounds(Collections.singleton(y)));
return environment.putState(x, set);
}
if (operator instanceof ComparisonLe) {
// x <= y
PcodeUpperBounds set = environment.getState(x).glb(environment.getState(y));
return environment.putState(x, set);
}
}
return environment;
}
@Override
public Iterator<Identifier> iterator() {
if (bounds == null) {
return Collections.emptyIterator();
}
return bounds.iterator();
}
/**
* Checks if this bounds contains a specified identifier of a program
* variable.
*
* @param id the identifier to check
*
* @return {@code true} if this bounds contains the specified identifier;
* otherwise, {@code false}.
*/
public boolean contains(
Identifier id) {
return bounds != null && bounds.contains(id);
}
/**
* Adds the specified identifier of a program variable in the bounds.
*
* @param id the identifier to add in the bounds.
*
* @return the updated bounds.
*/
public PcodeUpperBounds add(
Identifier id) {
Set<Identifier> res = new HashSet<>();
if (!isTop() && !isBottom()) {
res.addAll(bounds);
}
res.add(id);
return new PcodeUpperBounds(res);
}
@Override
public PcodeUpperBounds getValue(RegisterValue rv) {
return top();
}
}

View File

@@ -0,0 +1 @@
Note: files in this directory are lightly-modified versions of analyses from https://github.com/lisa-analyzer, and, as such, are included with their original comments, author credits, and MIT License.

View File

@@ -0,0 +1,53 @@
/* ###
* 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.lisa.pcode.contexts;
import ghidra.lisa.pcode.locations.PcodeLocation;
import ghidra.program.model.pcode.PcodeOp;
import it.unive.lisa.program.cfg.CodeLocation;
public class BinaryExprContext {
public PcodeOp op;
public VarnodeContext left;
public VarnodeContext right;
public BinaryExprContext(PcodeContext ctx) {
this.op = ctx.op;
left = new VarnodeContext(op.getInput(0));
right = new VarnodeContext(op.getInput(1));
}
// Used for TypeConv
public BinaryExprContext(StatementContext ctx) {
this.op = ctx.op;
left = ctx.target();
right = new VarnodeContext(op.getInput(0));
}
public int opcode() {
return op.getOpcode();
}
public CodeLocation location() {
return new PcodeLocation(op);
}
public String mnemonic() {
return op.getMnemonic();
}
}

View File

@@ -0,0 +1,73 @@
/* ###
* 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.lisa.pcode.contexts;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function;
import ghidra.program.model.pcode.PcodeOp;
import it.unive.lisa.program.cfg.statement.call.Call.CallType;
public class CallContext extends PcodeContext {
private Function callee = null;
public VarnodeContext left;
private boolean isTarget;
public CallContext(PcodeOp op, UnitContext ctx) {
super(op);
this.op = op;
Address address = op.getInput(0).getAddress();
Function caller = ctx.function();
left = new SymbolVarnodeContext(address);
if (address.getAddressSpace().isMemorySpace()) {
callee = caller.getProgram().getFunctionManager().getFunctionAt(address);
if (callee != null) {
left = new SymbolVarnodeContext(callee.getName(), address);
}
}
}
public String getText() {
return op.toString();
}
public String getCalleeName() {
return callee == null ? "UNKNOWN" : callee.getName();
}
public Function function() {
return callee;
}
public CallType type() {
if (callee == null || callee.isThunk()) {
return CallType.UNKNOWN;
}
if (op.getInput(0).getAddress().getAddressSpace().isMemorySpace()) {
return CallType.STATIC;
}
return CallType.UNKNOWN;
}
public boolean isTarget() {
return isTarget;
}
public void setIsTarget(boolean isTarget) {
this.isTarget = isTarget;
}
}

View File

@@ -0,0 +1,32 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.lisa.pcode.contexts;
import ghidra.program.model.pcode.PcodeOp;
public class ConditionContext extends PcodeContext {
public ConditionContext(PcodeOp op) {
super(op);
}
public VarnodeContext expression() {
// opcode should be CBRANCH
assert (op.getInputs().length < 2);
return new VarnodeContext(op.getInput(1));
}
}

View File

@@ -0,0 +1,67 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.lisa.pcode.contexts;
import java.util.*;
import ghidra.lisa.pcode.locations.InstLocation;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*;
import ghidra.program.model.pcode.PcodeOp;
import it.unive.lisa.program.cfg.CodeLocation;
public class InstructionContext {
private Function function;
private Instruction inst;
private List<StatementContext> ops;
private InstLocation loc;
public InstructionContext(Function function, Instruction inst) {
this.function = function;
this.inst = inst;
ops = new ArrayList<>();
for (PcodeOp op : inst.getPcode()) {
StatementContext ctx = new StatementContext(inst, op);
ops.add(ctx);
}
loc = new InstLocation(function, inst.getAddress());
}
public Collection<StatementContext> getPcodeOps() {
return ops;
}
public StatementContext getPcodeOp(int i) {
return ops.get(i);
}
public Instruction getInstruction() {
return inst;
}
public InstructionContext next() {
Listing listing = inst.getProgram().getListing();
Address nextAddress = inst.getAddress().add(inst.getLength());
Instruction next = listing.getInstructionAt(nextAddress);
return next == null ? null : new InstructionContext(function, next);
}
public CodeLocation location() {
return loc;
}
}

View File

@@ -0,0 +1,36 @@
/* ###
* 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.lisa.pcode.contexts;
import ghidra.program.model.address.AddressFactory;
import ghidra.program.model.address.AddressSpace;
public class MemLocContext extends VarnodeContext {
private AddressSpace space;
public MemLocContext(StatementContext ctx) {
super(ctx.getOp().getInput(1));
AddressFactory addressFactory = ctx.inst.getProgram().getAddressFactory();
space = addressFactory.getAddressSpace((int) ctx.getOp().getInput(0).getOffset());
}
@Override
public String getText() {
return space.getName() + "@" + vn.getAddress().toString();
}
}

View File

@@ -0,0 +1,56 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.lisa.pcode.contexts;
import ghidra.lisa.pcode.locations.PcodeLocation;
import ghidra.program.model.pcode.PcodeOp;
import it.unive.lisa.program.SyntheticLocation;
import it.unive.lisa.program.cfg.CodeLocation;
public class PcodeContext {
protected PcodeOp op;
public PcodeContext(PcodeOp op) {
this.op = op;
}
public PcodeOp getOp() {
return op;
}
public CodeLocation location() {
if (op == null) {
return SyntheticLocation.INSTANCE;
}
return new PcodeLocation(op);
}
public VarnodeContext basicExpr() {
if (op.getNumInputs() == 1) {
return new VarnodeContext(op.getInput(0));
}
return null;
}
public int opcode() {
return op.getOpcode();
}
public int getNumInputs() {
return op.getNumInputs();
}
}

View File

@@ -0,0 +1,141 @@
/* ###
* 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.lisa.pcode.contexts;
import java.util.ArrayList;
import java.util.List;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Instruction;
import ghidra.program.model.listing.Listing;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
import ghidra.program.model.symbol.*;
public class StatementContext extends PcodeContext {
public StatementContext otherwise;
public StatementContext then;
private int opcode;
public Instruction inst;
public VarDefContext left;
public PcodeContext right;
public StatementContext(Instruction inst, PcodeOp op) {
super(op);
this.inst = inst;
if (op != null) {
this.opcode = op.getOpcode();
if (op.getOutput() != null) {
left = new VarDefContext(op, op.getOutput());
}
else {
left = new VarDefContext(op, op.getInput(2));
}
right = new PcodeContext(op);
}
}
public VarDefContext target() {
return left;
}
public PcodeContext expression() {
return right;
}
public ConditionContext condition() {
return new ConditionContext(op);
}
public boolean isRet() {
return opcode == PcodeOp.RETURN;
}
public boolean isBranch() {
return opcode == PcodeOp.BRANCH || opcode == PcodeOp.BRANCHIND || opcode == PcodeOp.CBRANCH;
}
public boolean isConditional() {
return opcode == PcodeOp.CBRANCH;
}
public List<StatementContext> branch(Listing listing, UnitContext currentUnit) {
List<StatementContext> list = new ArrayList<>();
if (opcode == PcodeOp.BRANCH || opcode == PcodeOp.CBRANCH) {
Varnode vn = op.getInput(0);
if (vn.getAddress().isConstantAddress()) {
int order = op.getSeqnum().getTime();
order += vn.getOffset();
list.add(new StatementContext(inst, inst.getPcode()[order]));
}
else {
Instruction next =
listing.getInstructionAt(vn.getAddress().getNewAddress(vn.getOffset()));
if (next == null || next.getPcode().length == 0) {
return list;
}
list.add(new StatementContext(next, next.getPcode()[0]));
}
}
if (opcode == PcodeOp.BRANCHIND) {
ReferenceManager referenceManager =
currentUnit.function().getProgram().getReferenceManager();
Reference[] refs = referenceManager.getReferencesFrom(inst.getAddress());
for (Reference ref : refs) {
Address fromAddress = ref.getToAddress();
Instruction next = listing.getInstructionAt(fromAddress);
if (next == null || next.getPcode().length == 0) {
return list;
}
list.add(new StatementContext(next, next.getPcode()[0]));
}
}
return list;
}
public StatementContext next(Listing listing) {
PcodeOp[] pcode = inst.getPcode();
if (op != null) {
int order = op.getSeqnum().getTime();
if (order + 1 < pcode.length) {
return new StatementContext(inst, inst.getPcode()[order + 1]);
}
}
Instruction next = listing.getInstructionAt(inst.getAddress().add(inst.getLength()));
while (next != null && next.getPcode().length == 0) {
next = listing.getInstructionAt(next.getAddress().add(next.getLength()));
}
if (next == null) {
return null;
}
return new StatementContext(next, next.getPcode()[0]);
}
public PcodeContext ret() {
if (opcode == PcodeOp.RETURN) {
return new PcodeContext(op);
}
return null;
}
@Override
public String toString() {
return inst.getAddress() + ": " + inst + ":" + op;
}
}

View File

@@ -0,0 +1,59 @@
/* ###
* 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.lisa.pcode.contexts;
import ghidra.program.model.address.Address;
public class SymbolVarnodeContext extends VarnodeContext {
private String context;
private int size;
/*
* This class is essentially a dummy context for the case where the varnode's id is a memory address
*/
public SymbolVarnodeContext(String name, Address context) {
this(context);
this.context = name;
}
public SymbolVarnodeContext(Address context) {
super(null);
this.context = context.toString();
size = context.getPointerSize();
}
@Override
public boolean isConstant() {
return false;
}
@Override
public int getSize() {
return size;
}
@Override
public long getOffset() {
return 0;
}
@Override
public String getText() {
return context;
}
}

View File

@@ -0,0 +1,44 @@
/* ###
* 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.lisa.pcode.contexts;
import ghidra.lisa.pcode.locations.PcodeLocation;
import ghidra.program.model.pcode.PcodeOp;
import it.unive.lisa.program.cfg.CodeLocation;
public class UnaryExprContext {
public PcodeOp op;
public VarnodeContext arg;
public UnaryExprContext(PcodeContext ctx) {
this.op = ctx.op;
arg = new VarnodeContext(op.getInput(0));
}
public int opcode() {
return op.getOpcode();
}
public CodeLocation location() {
return new PcodeLocation(op);
}
public String mnemonic() {
return op.getMnemonic();
}
}

View File

@@ -0,0 +1,88 @@
/* ###
* 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.lisa.pcode.contexts;
import ghidra.lisa.pcode.PcodeFrontend;
import ghidra.lisa.pcode.locations.InstLocation;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*;
import it.unive.lisa.program.CodeUnit;
import it.unive.lisa.program.Program;
import it.unive.lisa.program.SyntheticLocation;
import it.unive.lisa.program.cfg.CodeLocation;
public class UnitContext {
private PcodeFrontend frontend;
private Function function;
private CodeUnit unit;
private Address start;
public UnitContext(PcodeFrontend frontend, Program program, Function f) {
this.frontend = frontend;
this.function = f;
unit = new CodeUnit(SyntheticLocation.INSTANCE, program, f.getName());
start = function.getEntryPoint();
}
public UnitContext(PcodeFrontend frontend, Program program, Function f, Address entry) {
this.frontend = frontend;
this.function = f;
unit = new CodeUnit(SyntheticLocation.INSTANCE, program,
f.getName() + ":" + f.getEntryPoint());
start = entry;
}
public PcodeFrontend getFrontend() {
return frontend;
}
public CodeUnit unit() {
return unit;
}
public String getText() {
return function.getName();
}
public boolean isFinal() {
return false;
}
public Listing getListing() {
return function.getProgram().getListing();
}
public CodeLocation location() {
return new InstLocation(function, start);
}
public Function function() {
return function;
}
public InstructionContext entry() {
Instruction inst = getListing().getInstructionAt(start);
if (inst == null) {
inst = getListing().getInstructionAfter(start);
}
return inst == null ? null : new InstructionContext(function, inst);
}
public boolean contains(Address target) {
return function.getBody().contains(target);
}
}

View File

@@ -0,0 +1,44 @@
/* ###
* 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.lisa.pcode.contexts;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
public class VarDefContext extends VarnodeContext {
private PcodeOp op;
public VarDefContext(PcodeOp op, Varnode vn) {
super(vn);
this.op = op;
}
@Override
public boolean isConstant() {
return false;
}
public PcodeOp getOp() {
return op;
}
@Override
public String getText() {
return vn.getAddress().toString();
}
}

View File

@@ -0,0 +1,44 @@
/* ###
* 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.lisa.pcode.contexts;
import ghidra.program.model.pcode.Varnode;
public class VarnodeContext {
protected Varnode vn;
public VarnodeContext(Varnode vn) {
this.vn = vn;
}
public boolean isConstant() {
return vn.isConstant();
}
public int getSize() {
return vn.getSize();
}
public long getOffset() {
return vn.getOffset();
}
public String getText() {
return vn.getAddress().toString();
}
}

View File

@@ -0,0 +1,77 @@
/* ###
* 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.lisa.pcode.expressions;
import ghidra.lisa.pcode.contexts.BinaryExprContext;
import ghidra.lisa.pcode.statements.PcodeBinaryOperator;
import ghidra.program.model.pcode.PcodeOp;
import it.unive.lisa.analysis.*;
import it.unive.lisa.interprocedural.InterproceduralAnalysis;
import it.unive.lisa.program.cfg.CFG;
import it.unive.lisa.program.cfg.statement.Expression;
import it.unive.lisa.program.cfg.statement.Statement;
import it.unive.lisa.symbolic.SymbolicExpression;
import it.unive.lisa.symbolic.value.BinaryExpression;
import it.unive.lisa.symbolic.value.operator.binary.*;
public class PcodeBinaryExpression extends it.unive.lisa.program.cfg.statement.BinaryExpression {
private BinaryOperator operator;
public PcodeBinaryExpression(
CFG cfg,
BinaryExprContext ctx,
Expression left,
Expression right) {
super(cfg, ctx.location(), ctx.mnemonic(),
cfg.getDescriptor().getUnit().getProgram().getTypes().getIntegerType(), left, right);
this.operator = switch (ctx.op.getOpcode()) {
case PcodeOp.BOOL_AND -> LogicalAnd.INSTANCE;
case PcodeOp.BOOL_OR -> LogicalOr.INSTANCE;
case PcodeOp.INT_EQUAL, PcodeOp.FLOAT_EQUAL -> ComparisonEq.INSTANCE;
case PcodeOp.INT_NOTEQUAL, PcodeOp.FLOAT_NOTEQUAL -> ComparisonNe.INSTANCE;
case PcodeOp.INT_LESSEQUAL, PcodeOp.FLOAT_LESSEQUAL -> ComparisonLe.INSTANCE;
case PcodeOp.INT_LESS, PcodeOp.FLOAT_LESS -> ComparisonLt.INSTANCE;
default -> new PcodeBinaryOperator(ctx.op);
};
}
@Override
protected int compareSameClassAndParams(
Statement o) {
return 0; // no extra fields to compare
}
@Override
public <A extends AbstractState<A>> AnalysisState<A> fwdBinarySemantics(
InterproceduralAnalysis<A> interprocedural,
AnalysisState<A> state,
SymbolicExpression left,
SymbolicExpression right,
StatementStore<A> expressions)
throws SemanticException {
return state.smallStepSemantics(
new BinaryExpression(
getStaticType(),
left,
right,
operator,
getLocation()),
this);
}
}

View File

@@ -0,0 +1,128 @@
/* ###
* 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.lisa.pcode.expressions;
import java.util.HashSet;
import java.util.Set;
import ghidra.lisa.pcode.contexts.CallContext;
import ghidra.lisa.pcode.locations.PcodeLocation;
import ghidra.program.model.address.Address;
import ghidra.program.model.lang.PrototypeModel;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.ProgramContext;
import ghidra.program.model.pcode.Varnode;
import it.unive.lisa.analysis.*;
import it.unive.lisa.analysis.nonrelational.value.ValueEnvironment;
import it.unive.lisa.analysis.value.ValueDomain;
import it.unive.lisa.interprocedural.InterproceduralAnalysis;
import it.unive.lisa.program.cfg.CFG;
import it.unive.lisa.program.cfg.statement.Expression;
import it.unive.lisa.program.cfg.statement.call.UnresolvedCall;
import it.unive.lisa.symbolic.value.Variable;
public class PcodeCallExpression extends UnresolvedCall {
private Function function;
private Set<Varnode> unaffected = new HashSet<>();
private Set<Varnode> killedByCall = new HashSet<>();
public PcodeCallExpression(
CFG cfg,
CallContext ctx,
Expression expression) {
super(cfg, ctx.location(), ctx.type(), null, ctx.getCalleeName(), expression);
function = ctx.function();
if (function != null) {
PrototypeModel callingConvention = function.getCallingConvention();
if (callingConvention != null) {
Varnode[] unaffectedList = callingConvention.getUnaffectedList();
for (Varnode varnode : unaffectedList) {
unaffected.add(varnode);
}
Varnode[] killedByCallList = callingConvention.getKilledByCallList();
for (Varnode varnode : killedByCallList) {
killedByCall.add(varnode);
}
}
}
}
@SuppressWarnings("unchecked")
@Override
public <A extends AbstractState<A>> AnalysisState<A> forwardSemantics(
AnalysisState<A> entryState,
InterproceduralAnalysis<A> interprocedural,
StatementStore<A> expressions)
throws SemanticException {
// TODO: This triggers the processing of the callee, but we effectively clears the state.
// We're using the slightly-sanitized entry state instead. Quite possible, the return
// value from the function is being dropped here - not clear how this works.
super.forwardSemantics(entryState, interprocedural, expressions);
if (unaffected.isEmpty()) {
return entryState;
}
ProgramContext programContext = function.getProgram().getProgramContext();
Address entryPoint = function.getEntryPoint();
if (entryState.getState() instanceof SimpleAbstractState sas) {
ValueDomain<?> vDomain = sas.getValueState();
if (vDomain instanceof ValueEnvironment vEnv) {
if (vEnv.function == null) {
return entryState;
}
for (Object key : vEnv.function.keySet()) {
Object val = vEnv.function.get(key);
if (key instanceof Variable var && val instanceof Lattice lat) {
if (var.getCodeLocation() instanceof PcodeLocation loc) {
Varnode output = loc.op.getOutput();
if (output != null &&
output.getAddress().getAddressSpace().isRegisterSpace()) {
if (unaffected.contains(output)) {
continue;
}
if (killedByCall.contains(output)) {
vEnv.function.put(key, lat.top());
}
}
}
}
}
// for (Register r : programContext.getRegisters()) {
// RegisterValue rv = programContext.getRegisterValue(r, entryPoint);
// if (rv != null && rv.hasAnyValue()) {
// Variable v = new Variable(Untyped.INSTANCE,
// r.getAddress().toString(), new InstLocation(entryPoint));
// if (vEnv.lattice instanceof PcodeNonRelationalValueDomain pcodeDomain) {
// PcodeNonRelationalValueDomain<?> value = pcodeDomain.getValue(rv);
// if (value != null) {
// vEnv.function.put(v, value);
// }
// }
// if (vEnv.lattice instanceof PcodeDataflowConstantPropagation pcodeDomain) {
// PcodeDataflowConstantPropagation value = pcodeDomain.getValue(v, rv);
// if (value != null) {
// vEnv.function.put(v, value);
// }
// }
// }
// }
}
}
return entryState;
}
}

View File

@@ -0,0 +1,63 @@
/* ###
* 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.lisa.pcode.expressions;
import ghidra.lisa.pcode.contexts.UnaryExprContext;
import ghidra.lisa.pcode.statements.PcodeUnaryOperator;
import it.unive.lisa.analysis.*;
import it.unive.lisa.interprocedural.InterproceduralAnalysis;
import it.unive.lisa.program.cfg.CFG;
import it.unive.lisa.program.cfg.statement.Expression;
import it.unive.lisa.program.cfg.statement.Statement;
import it.unive.lisa.symbolic.SymbolicExpression;
import it.unive.lisa.symbolic.value.UnaryExpression;
import it.unive.lisa.symbolic.value.operator.unary.UnaryOperator;
public class PcodeUnaryExpression extends it.unive.lisa.program.cfg.statement.UnaryExpression {
private UnaryOperator operator;
public PcodeUnaryExpression(
CFG cfg,
UnaryExprContext ctx,
Expression expression) {
super(cfg, ctx.location(), ctx.mnemonic(), expression);
this.operator = new PcodeUnaryOperator(ctx.op);
}
@Override
protected int compareSameClassAndParams(
Statement o) {
return 0; // no extra fields to compare
}
@Override
public <A extends AbstractState<A>> AnalysisState<A> fwdUnarySemantics(
InterproceduralAnalysis<A> interprocedural,
AnalysisState<A> state,
SymbolicExpression expr,
StatementStore<A> expressions)
throws SemanticException {
return state.smallStepSemantics(
new UnaryExpression(
expr.getStaticType(),
expr,
operator,
getLocation()),
this);
}
}

View File

@@ -0,0 +1,53 @@
/* ###
* 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.lisa.pcode.locations;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function;
import it.unive.lisa.program.cfg.CodeLocation;
public class InstLocation implements CodeLocation {
private Function function;
private Address addr;
public InstLocation(Function function, Address addr) {
this.function = function;
this.addr = addr;
}
@Override
public int compareTo(CodeLocation o) {
if (o instanceof InstLocation i) {
return addr.compareTo(i.addr);
}
return -1;
}
@Override
public String getCodeLocation() {
return addr.toString();
}
public Function function() {
return function;
}
public Address getAddress() {
return addr;
}
}

View File

@@ -0,0 +1,68 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.lisa.pcode.locations;
import ghidra.program.model.address.Address;
import ghidra.program.model.pcode.PcodeOp;
import it.unive.lisa.program.cfg.CodeLocation;
public class PcodeLocation implements CodeLocation {
public PcodeOp op;
public PcodeLocation(PcodeOp op) {
this.op = op;
}
@Override
public int compareTo(CodeLocation o) {
if (o instanceof PcodeLocation pl) {
return op.getSeqnum().compareTo(pl.op.getSeqnum());
}
return -1;
}
@Override
public String getCodeLocation() {
return op.getSeqnum().toString();
}
public int getOpcode() {
return op.getOpcode();
}
public Address getAddress() {
return op.getSeqnum().getTarget();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof PcodeLocation ploc) {
return this.op.getSeqnum().equals(ploc.op.getSeqnum());
}
return false;
}
@Override
public int hashCode() {
return op.getSeqnum().hashCode();
}
@Override
public String toString() {
return getCodeLocation();
}
}

View File

@@ -0,0 +1,52 @@
/* ###
* 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.lisa.pcode.statements;
import java.util.Collections;
import java.util.Set;
import ghidra.program.model.pcode.PcodeOp;
import it.unive.lisa.symbolic.value.operator.binary.BinaryOperator;
import it.unive.lisa.type.*;
public class PcodeBinaryOperator implements BinaryOperator {
private PcodeOp op;
public PcodeBinaryOperator(PcodeOp op) {
this.op = op;
}
public PcodeOp getOp() {
return op;
}
@Override
public String toString() {
return op.getMnemonic();
}
@Override
public Set<Type> typeInference(
TypeSystem types,
Set<Type> left,
Set<Type> right) {
Set<Type> set = NumericType.commonNumericalType(left, right);
if (!set.isEmpty())
return set;
return Collections.singleton(types.getBooleanType());
}
}

View File

@@ -0,0 +1,88 @@
/* ###
* 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.lisa.pcode.statements;
import it.unive.lisa.analysis.*;
import it.unive.lisa.interprocedural.InterproceduralAnalysis;
import it.unive.lisa.program.cfg.CFG;
import it.unive.lisa.program.cfg.CodeLocation;
import it.unive.lisa.program.cfg.edge.Edge;
import it.unive.lisa.program.cfg.statement.Statement;
import it.unive.lisa.symbolic.value.Skip;
import it.unive.lisa.util.datastructures.graph.GraphVisitor;
/**
* A statement that does nothing. Can be used for instrumenting branching
* operations.
*
* <p>
* Modified to handle pcode from original source written by:
* <p>
* @author <a href="mailto:luca.negrini@unive.it">Luca Negrini</a>
*/
public class PcodeNop extends Statement {
public PcodeNop(
CFG cfg,
CodeLocation location) {
super(cfg, location);
}
@Override
public int hashCode() {
return super.hashCode() ^ getClass().getName().hashCode();
}
@Override
public boolean equals(
Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
return true;
}
@Override
protected int compareSameClass(
Statement o) {
return 0; // no extra fields to compare
}
@Override
public final String toString() {
// JMPs are the only current nops
return "JMP @ " + getLocation().getCodeLocation();
}
@Override
public <A extends AbstractState<A>> AnalysisState<A> forwardSemantics(
AnalysisState<A> entryState,
InterproceduralAnalysis<A> interprocedural,
StatementStore<A> expressions)
throws SemanticException {
return entryState.smallStepSemantics(new Skip(getLocation()), this);
}
@Override
public <V> boolean accept(
GraphVisitor<CFG, Statement, Edge, V> visitor,
V tool) {
return visitor.visit(tool, getCFG(), this);
}
}

View File

@@ -0,0 +1,49 @@
/* ###
* 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.lisa.pcode.statements;
import java.util.Collections;
import java.util.Set;
import ghidra.program.model.pcode.PcodeOp;
import it.unive.lisa.symbolic.value.operator.unary.UnaryOperator;
import it.unive.lisa.type.Type;
import it.unive.lisa.type.TypeSystem;
public class PcodeUnaryOperator implements UnaryOperator {
private PcodeOp op;
public PcodeUnaryOperator(PcodeOp op) {
this.op = op;
}
public PcodeOp getOp() {
return op;
}
@Override
public String toString() {
return op.getMnemonic();
}
@Override
public Set<Type> typeInference(
TypeSystem types,
Set<Type> argument) {
return Collections.singleton(types.getBooleanType());
}
}

View File

@@ -0,0 +1,415 @@
/* ###
* 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.lisa.pcode.types;
import static org.apache.commons.collections4.CollectionUtils.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import ghidra.lisa.pcode.locations.PcodeLocation;
import ghidra.program.model.pcode.PcodeOp;
import it.unive.lisa.analysis.*;
import it.unive.lisa.analysis.lattices.Satisfiability;
import it.unive.lisa.analysis.nonrelational.value.BaseNonRelationalTypeDomain;
import it.unive.lisa.analysis.nonrelational.value.TypeEnvironment;
import it.unive.lisa.program.cfg.ProgramPoint;
import it.unive.lisa.symbolic.value.*;
import it.unive.lisa.symbolic.value.operator.binary.BinaryOperator;
import it.unive.lisa.symbolic.value.operator.binary.TypeCast;
import it.unive.lisa.symbolic.value.operator.ternary.TernaryOperator;
import it.unive.lisa.symbolic.value.operator.unary.UnaryOperator;
import it.unive.lisa.type.*;
import it.unive.lisa.util.representation.*;
public class PcodeInferredTypes implements BaseNonRelationalTypeDomain<PcodeInferredTypes> {
private static final PcodeInferredTypes BOTTOM =
new PcodeInferredTypes(null, Collections.emptySet());
private final Set<Type> elements;
private final boolean isTop;
/**
* Builds the inferred types. The object built through this constructor
* represents an empty set of types.
*/
public PcodeInferredTypes() {
this(null, (Set<Type>) null);
}
/**
* Builds the inferred types, representing only the given {@link Type}.
*
* @param typeSystem the type system knowing about the types of the program
* where this element is created
* @param type the type to be included in the set of inferred types
*/
public PcodeInferredTypes(
TypeSystem typeSystem,
Type type) {
this(typeSystem, Collections.singleton(type));
}
/**
* Builds the inferred types, representing only the given set of
* {@link Type}s.
*
* @param typeSystem the type system knowing about the types of the program
* where this element is created
* @param types the types to be included in the set of inferred types
*/
public PcodeInferredTypes(
TypeSystem typeSystem,
Set<Type> types) {
this(typeSystem != null && types.equals(typeSystem.getTypes()), types);
}
/**
* Builds the inferred types, representing only the given set of
* {@link Type}s.
*
* @param isTop whether or not the set of types represents all possible
* types
* @param types the types to be included in the set of inferred types
*/
public PcodeInferredTypes(
boolean isTop,
Set<Type> types) {
this.elements = types;
this.isTop = isTop;
}
@Override
public Set<Type> getRuntimeTypes() {
if (elements == null)
Collections.emptySet();
return elements;
}
@Override
public PcodeInferredTypes top() {
return new PcodeInferredTypes(true, null);
}
@Override
public boolean isTop() {
return BaseNonRelationalTypeDomain.super.isTop() || isTop;
}
@Override
public PcodeInferredTypes bottom() {
return BOTTOM;
}
@Override
public boolean isBottom() {
return BaseNonRelationalTypeDomain.super.isBottom() || BOTTOM.elements.equals(elements);
}
@Override
public String toString() {
return representation().toString();
}
@Override
public StructuredRepresentation representation() {
if (isTop())
return Lattice.topRepresentation();
if (isBottom())
return Lattice.bottomRepresentation();
return new SetRepresentation(elements, StringRepresentation::new);
}
@Override
public PcodeInferredTypes evalIdentifier(
Identifier id,
TypeEnvironment<PcodeInferredTypes> environment,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
PcodeInferredTypes eval =
BaseNonRelationalTypeDomain.super.evalIdentifier(id, environment, pp, oracle);
if (!eval.isTop())
return eval;
TypeSystem types = pp.getProgram().getTypes();
return new PcodeInferredTypes(types, id.getStaticType().allInstances(types));
}
@Override
public PcodeInferredTypes evalPushAny(
PushAny pushAny,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
TypeSystem types = pp.getProgram().getTypes();
if (pushAny.getStaticType().isUntyped())
return new PcodeInferredTypes(true, types.getTypes());
return new PcodeInferredTypes(types, pushAny.getStaticType().allInstances(types));
}
@Override
public PcodeInferredTypes evalPushInv(
PushInv pushInv,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
return bottom();
}
@Override
public PcodeInferredTypes evalNullConstant(
ProgramPoint pp,
SemanticOracle oracle) {
return new PcodeInferredTypes(pp.getProgram().getTypes(), NullType.INSTANCE);
}
@Override
public PcodeInferredTypes evalNonNullConstant(
Constant constant,
ProgramPoint pp,
SemanticOracle oracle) {
return new PcodeInferredTypes(pp.getProgram().getTypes(), constant.getStaticType());
}
@Override
public PcodeInferredTypes evalUnaryExpression(
UnaryOperator operator,
PcodeInferredTypes arg,
ProgramPoint pp,
SemanticOracle oracle) {
TypeSystem types = pp.getProgram().getTypes();
Set<Type> elems = arg.isTop() ? types.getTypes() : arg.elements;
Set<Type> inferred = operator.typeInference(types, elems);
if (inferred.isEmpty())
return BOTTOM;
return new PcodeInferredTypes(types, inferred);
}
@Override
public PcodeInferredTypes evalBinaryExpression(
BinaryOperator operator,
PcodeInferredTypes left,
PcodeInferredTypes right,
ProgramPoint pp,
SemanticOracle oracle) {
TypeSystem types = pp.getProgram().getTypes();
Set<Type> lelems = left.isTop() ? types.getTypes() : left.elements;
Set<Type> relems = right.isTop() ? types.getTypes() : right.elements;
Set<Type> inferred = operator.typeInference(types, lelems, relems);
if (inferred.isEmpty())
return BOTTOM;
return new PcodeInferredTypes(types, inferred);
}
@Override
public PcodeInferredTypes evalTernaryExpression(
TernaryOperator operator,
PcodeInferredTypes left,
PcodeInferredTypes middle,
PcodeInferredTypes right,
ProgramPoint pp,
SemanticOracle oracle) {
TypeSystem types = pp.getProgram().getTypes();
Set<Type> lelems = left.isTop() ? types.getTypes() : left.elements;
Set<Type> melems = middle.isTop() ? types.getTypes() : middle.elements;
Set<Type> relems = right.isTop() ? types.getTypes() : right.elements;
Set<Type> inferred = operator.typeInference(types, lelems, melems, relems);
if (inferred.isEmpty())
return BOTTOM;
return new PcodeInferredTypes(types, inferred);
}
@Override
public Satisfiability satisfiesBinaryExpression(
BinaryOperator operator,
PcodeInferredTypes left,
PcodeInferredTypes right,
ProgramPoint pp,
SemanticOracle oracle) {
TypeSystem types = pp.getProgram().getTypes();
Set<Type> lelems = left.isTop() ? types.getTypes() : left.elements;
Set<Type> relems = right.isTop() ? types.getTypes() : right.elements;
PcodeLocation ploc = (PcodeLocation) pp.getLocation();
PcodeOp op = ploc.op;
int opcode = op.getOpcode();
if (opcode == PcodeOp.INT_EQUAL || opcode == PcodeOp.FLOAT_EQUAL ||
opcode == PcodeOp.INT_NOTEQUAL || opcode == PcodeOp.FLOAT_NOTEQUAL) {
Set<Type> lfiltered =
lelems.stream().filter(Type::isTypeTokenType).collect(Collectors.toSet());
Set<Type> rfiltered =
relems.stream().filter(Type::isTypeTokenType).collect(Collectors.toSet());
if (lelems.size() != lfiltered.size() || relems.size() != rfiltered.size())
// if there is at least one element that is not a type
// token, than we cannot reason about it
return Satisfiability.UNKNOWN;
if (opcode == PcodeOp.INT_EQUAL || opcode == PcodeOp.FLOAT_EQUAL) {
if (lelems.size() == 1 && lelems.equals(relems))
// only one element, and it is the same
return Satisfiability.SATISFIED;
else if (intersection(lelems, relems).isEmpty() &&
!typeTokensIntersect(lfiltered, rfiltered))
// no common elements, they cannot be equal
return Satisfiability.NOT_SATISFIED;
else
// we don't know really
return Satisfiability.UNKNOWN;
}
if (intersection(lelems, relems).isEmpty() &&
!typeTokensIntersect(lfiltered, rfiltered))
// no common elements, they cannot be equal
return Satisfiability.SATISFIED;
else if (lelems.size() == 1 && lelems.equals(relems))
// only one element, and it is the same
return Satisfiability.NOT_SATISFIED;
else
// we don't know really
return Satisfiability.UNKNOWN;
}
else if (opcode == PcodeOp.CAST) {
if (evalBinaryExpression(TypeCast.INSTANCE, left, right, pp, oracle).isBottom())
// no common types, the check will always fail
return Satisfiability.NOT_SATISFIED;
AtomicBoolean mightFail = new AtomicBoolean();
Set<Type> set = types.cast(lelems, relems, mightFail);
if (lelems.equals(set) && !mightFail.get())
// if all the types stayed in 'set' then the there is no
// execution that reach the expression with a type that cannot
// be casted, and thus this is a tautology
return Satisfiability.SATISFIED;
// sometimes yes, sometimes no
return Satisfiability.UNKNOWN;
}
return Satisfiability.UNKNOWN;
}
/**
* Checks whether or not the two given set of type tokens intersects,
* meaning that there exists at least one type token {@code t1} from
* {@code lfiltered} and one type token {@code t2} from {@code rfiltered}
* such that {@code t1.getTypes().intersects(t2.getTypes())}.<br>
* <br>
* Note that all types in both sets received as parameters are assumed to be
* {@link TypeTokenType}s, hence no type check is performed before
* converting them.
*
* @param lfiltered the first set of type tokens
* @param rfiltered the second set of type tokens
*
* @return {@code true} if the sets of tokens intersect
*
* @throws NullPointerException if one of the types is not a
* {@link TypeTokenType} (this is due to
* the conversion)
*/
static boolean typeTokensIntersect(
Set<Type> lfiltered,
Set<Type> rfiltered) {
for (Type l : lfiltered)
for (Type r : rfiltered)
if (!intersection(l.asTypeTokenType().getTypes(), r.asTypeTokenType().getTypes())
.isEmpty())
return true;
return false;
}
@Override
public PcodeInferredTypes lubAux(
PcodeInferredTypes other)
throws SemanticException {
Set<Type> lub = new HashSet<>(elements);
lub.addAll(other.elements);
return new PcodeInferredTypes(null, lub);
}
@Override
public boolean lessOrEqualAux(
PcodeInferredTypes other)
throws SemanticException {
return other.elements.containsAll(elements);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((elements == null) ? 0 : elements.hashCode());
result = prime * result + (isTop ? 1231 : 1237);
return result;
}
@Override
public boolean equals(
Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
PcodeInferredTypes other = (PcodeInferredTypes) obj;
if (elements == null) {
if (other.elements != null)
return false;
}
else if (!elements.equals(other.elements))
return false;
if (isTop != other.isTop)
return false;
return true;
}
@Override
public PcodeInferredTypes evalTypeCast(
BinaryExpression cast,
PcodeInferredTypes left,
PcodeInferredTypes right,
ProgramPoint pp,
SemanticOracle oracle) {
TypeSystem types = pp.getProgram().getTypes();
Set<Type> lelems = left.isTop() ? types.getTypes() : left.elements;
Set<Type> relems = right.isTop() ? types.getTypes() : right.elements;
Set<Type> inferred = cast.getOperator().typeInference(types, lelems, relems);
if (inferred.isEmpty())
return BOTTOM;
return new PcodeInferredTypes(types, inferred);
}
@Override
public PcodeInferredTypes evalTypeConv(
BinaryExpression conv,
PcodeInferredTypes left,
PcodeInferredTypes right,
ProgramPoint pp,
SemanticOracle oracle) {
TypeSystem types = pp.getProgram().getTypes();
Set<Type> lelems = left.isTop() ? types.getTypes() : left.elements;
Set<Type> relems = right.isTop() ? types.getTypes() : right.elements;
Set<Type> inferred = conv.getOperator().typeInference(types, lelems, relems);
if (inferred.isEmpty())
return BOTTOM;
return new PcodeInferredTypes(types, inferred);
}
}

View File

@@ -0,0 +1,244 @@
/* ###
* 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.lisa.pcode.types;
import java.util.Collections;
import java.util.Set;
import ghidra.lisa.pcode.statements.PcodeBinaryOperator;
import ghidra.program.model.pcode.PcodeOp;
import it.unive.lisa.analysis.*;
import it.unive.lisa.analysis.lattices.Satisfiability;
import it.unive.lisa.analysis.nonrelational.inference.InferredValue;
import it.unive.lisa.analysis.nonrelational.value.BaseNonRelationalTypeDomain;
import it.unive.lisa.analysis.nonrelational.value.TypeEnvironment;
import it.unive.lisa.program.cfg.ProgramPoint;
import it.unive.lisa.program.cfg.statement.Expression;
import it.unive.lisa.symbolic.value.*;
import it.unive.lisa.symbolic.value.operator.binary.BinaryOperator;
import it.unive.lisa.type.*;
import it.unive.lisa.util.representation.StringRepresentation;
import it.unive.lisa.util.representation.StructuredRepresentation;
/**
* An {@link InferredValue} holding a set of {@link Type}s, representing the
* inferred runtime types of an {@link Expression}.
*
* <p>
* @author <a href="mailto:luca.negrini@unive.it">Luca Negrini</a>
*/
public class PcodeStaticTypes implements BaseNonRelationalTypeDomain<PcodeStaticTypes> {
private static final PcodeStaticTypes BOTTOM = new PcodeStaticTypes(null, null);
private final Type type;
private final TypeSystem types;
/**
* Builds the inferred types. The object built through this constructor
* represents an empty set of types.
*/
public PcodeStaticTypes() {
this(null, Untyped.INSTANCE);
}
/**
* Builds the inferred types, representing only the given {@link Type}.
*
* @param types the type system knowing about the types of the program where
* this element is created
* @param type the type to be included in the set of inferred types
*/
PcodeStaticTypes(
TypeSystem types,
Type type) {
this.type = type;
this.types = types;
}
@Override
public Set<Type> getRuntimeTypes() {
if (this.isBottom())
Collections.emptySet();
return type.allInstances(types);
}
@Override
public PcodeStaticTypes top() {
return new PcodeStaticTypes(types, Untyped.INSTANCE);
}
@Override
public boolean isTop() {
return type == Untyped.INSTANCE;
}
@Override
public PcodeStaticTypes bottom() {
return BOTTOM;
}
@Override
public StructuredRepresentation representation() {
if (isTop())
return Lattice.topRepresentation();
if (isBottom())
return Lattice.bottomRepresentation();
return new StringRepresentation(type.toString());
}
@Override
public PcodeStaticTypes evalIdentifier(
Identifier id,
TypeEnvironment<PcodeStaticTypes> environment,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
PcodeStaticTypes eval =
BaseNonRelationalTypeDomain.super.evalIdentifier(id, environment, pp, oracle);
if (!eval.isTop() && !eval.isBottom())
return eval;
return new PcodeStaticTypes(pp.getProgram().getTypes(), id.getStaticType());
}
@Override
public PcodeStaticTypes evalPushAny(
PushAny pushAny,
ProgramPoint pp,
SemanticOracle oracle) {
return new PcodeStaticTypes(pp.getProgram().getTypes(), pushAny.getStaticType());
}
@Override
public PcodeStaticTypes evalPushInv(
PushInv pushInv,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
return new PcodeStaticTypes(pp.getProgram().getTypes(), pushInv.getStaticType());
}
@Override
public PcodeStaticTypes evalNullConstant(
ProgramPoint pp,
SemanticOracle oracle) {
return new PcodeStaticTypes(pp.getProgram().getTypes(), NullType.INSTANCE);
}
@Override
public PcodeStaticTypes evalNonNullConstant(
Constant constant,
ProgramPoint pp,
SemanticOracle oracle) {
return new PcodeStaticTypes(pp.getProgram().getTypes(), constant.getStaticType());
}
@Override
public PcodeStaticTypes eval(
ValueExpression expression,
TypeEnvironment<PcodeStaticTypes> environment,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
if (expression instanceof BinaryExpression) {
TypeSystem etypes = pp.getProgram().getTypes();
BinaryExpression binary = (BinaryExpression) expression;
if (binary.getOperator() instanceof PcodeBinaryOperator poperator) {
PcodeOp op = poperator.getOp();
int opcode = op.getOpcode();
if (opcode == PcodeOp.CAST) {
PcodeStaticTypes left = null, right = null;
try {
left = eval((ValueExpression) binary.getLeft(), environment, pp, oracle);
right = eval((ValueExpression) binary.getRight(), environment, pp, oracle);
}
catch (ClassCastException e) {
throw new SemanticException(expression + " is not a value expression");
}
Set<Type> lelems = left.type.allInstances(etypes);
Set<Type> relems = right.type.allInstances(etypes);
Set<Type> inferred = binary.getOperator().typeInference(etypes, lelems, relems);
if (inferred.isEmpty())
return BOTTOM;
return new PcodeStaticTypes(pp.getProgram().getTypes(),
Type.commonSupertype(inferred, Untyped.INSTANCE));
}
}
}
return new PcodeStaticTypes(pp.getProgram().getTypes(), expression.getStaticType());
}
@Override
public Satisfiability satisfiesBinaryExpression(
BinaryOperator operator,
PcodeStaticTypes left,
PcodeStaticTypes right,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
TypeSystem stypes = pp.getProgram().getTypes();
Set<Type> lelems = left.type.allInstances(stypes);
Set<Type> relems = right.type.allInstances(stypes);
return new PcodeInferredTypes().satisfiesBinaryExpression(operator,
new PcodeInferredTypes(stypes, lelems),
new PcodeInferredTypes(stypes, relems), pp, oracle);
}
@Override
public PcodeStaticTypes lubAux(
PcodeStaticTypes other)
throws SemanticException {
return new PcodeStaticTypes(types, type.commonSupertype(other.type));
}
@Override
public boolean lessOrEqualAux(
PcodeStaticTypes other)
throws SemanticException {
return type.canBeAssignedTo(other.type);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((type == null) ? 0 : type.hashCode());
return result;
}
@Override
public boolean equals(
Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
PcodeStaticTypes other = (PcodeStaticTypes) obj;
if (type == null) {
if (other.type != null)
return false;
}
else if (!type.equals(other.type))
return false;
return true;
}
}

View File

@@ -0,0 +1,55 @@
/* ###
* 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.lisa.pcode.types;
import it.unive.lisa.program.type.*;
import it.unive.lisa.program.type.StringType;
import it.unive.lisa.type.*;
/**
* The {@link TypeSystem} for the IMP language.
*
* <p>
* @author <a href="mailto:luca.negrini@unive.it">Luca Negrini</a>
*/
public class PcodeTypeSystem extends TypeSystem {
@Override
public BooleanType getBooleanType() {
return BoolType.INSTANCE;
}
@Override
public StringType getStringType() {
return StringType.INSTANCE;
}
@Override
public NumericType getIntegerType() {
return Int32Type.INSTANCE;
}
public NumericType getLongType() {
return Int64Type.INSTANCE;
}
@Override
public boolean canBeReferenced(
Type type) {
return type.isInMemoryType();
}
}

View File

@@ -0,0 +1,301 @@
/* ###
* 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.lisa;
import static org.junit.Assert.*;
import java.io.IOException;
import java.util.*;
import org.junit.Rule;
import org.junit.experimental.categories.Categories.ExcludeCategory;
import org.junit.rules.TestName;
import com.contrastsecurity.sarif.SarifSchema210;
import db.Transaction;
import ghidra.app.decompiler.component.DecompilerPanel;
import ghidra.app.plugin.assembler.*;
import ghidra.app.plugin.assembler.sleigh.sem.*;
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.*;
import ghidra.app.plugin.core.decompiler.taint.TaintState.MarkType;
import ghidra.app.plugin.core.decompiler.taint.TaintState.QueryType;
import ghidra.app.plugin.core.disassembler.DisassemblerPlugin;
import ghidra.app.services.ProgramManager;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.framework.model.DomainFolder;
import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.PluginTool;
import ghidra.lisa.gui.*;
import ghidra.lisa.gui.LisaOptions.*;
import ghidra.program.database.ProgramDB;
import ghidra.program.disassemble.Disassembler;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.SourceType;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.TestEnv;
import ghidra.util.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.ConsoleTaskMonitor;
import sarif.SarifController;
import sarif.SarifService;
import sarif.model.SarifDataFrame;
@ExcludeCategory(AbstractLisaTest.class)
public class AbstractLisaTest extends AbstractGhidraHeadedIntegrationTest {
protected TestEnv env;
protected PluginTool tool;
protected ProgramManager programManager;
protected Program program;
private Function f = null;
protected final ConsoleTaskMonitor monitor = new ConsoleTaskMonitor();
CodeBrowserPlugin codeBrowserPlugin;
ListingPanel staticListing;
ListingPanel dynamicListing;
DecompilerProvider decompilerProvider;
DecompilerPanel decompilerPanel;
TaintPlugin taint;
LisaPlugin lisa;
protected TaintOptions taintOptions;
protected LisaOptions lisaOptions;
protected String taintTarget = null;
protected String taintAddr = null;
protected HashMap<String, List<String>> types;
protected HashMap<String, List<String>> values;
@Rule
public TestName name = new TestName();
public AbstractLisaTest() {
init();
}
protected String getProgramName() {
return "static-" + getClass().getCanonicalName() + "." + name.getMethodName();
}
public void init() {
try {
env = new TestEnv();
tool = env.getTool();
programManager = tool.getService(ProgramManager.class);
env.showTool();
addPlugins();
f = createSimpleProgramX86_64();
}
catch (Throwable e) {
Msg.error(this, e.getMessage());
}
programManager.openProgram(program);
taintOptions = taint.getOptions();
lisaOptions = lisa.getOptions();
lisaOptions.setHeapDomain(HeapDomainOption.DEFAULT);
lisaOptions.setTypeDomain(TypeDomainOption.DEFAULT);
lisaOptions.setValueDomain(ValueDomainOption.DEFAULT);
lisaOptions.setShowTop(true);
lisaOptions.setShowUnique(true);
}
public void runTest() {
LisaTaintState state = (LisaTaintState) taint.getTaintState();
state.setSuppressTop(false);
if (taintTarget != null) {
state.setTaint(MarkType.SOURCE, f, program.getAddressFactory().getAddress(taintAddr),
taintTarget);
}
monitor.initialize(program.getFunctionManager().getFunctionCount());
state.queryIndex(program, tool, QueryType.DEFAULT);
SarifSchema210 data = state.getData();
SarifService sarifService = taint.getSarifService();
SarifController controller = sarifService.getController();
SarifDataFrame df = new SarifDataFrame(data, controller, false);
List<Map<String, Object>> results = df.getTableResults();
types = new HashMap<>();
values = new HashMap<>();
for (Map<String, Object> map : results) {
String key = (String) map.get("location");
key = key.substring(key.indexOf(":") + 1);
String type = (String) map.get("type");
String value = (String) map.get("value");
List<String> list = types.get(key);
if (list == null) {
list = new ArrayList<>();
types.put(key, list);
}
list.add(type);
list = values.get(key);
if (list == null) {
list = new ArrayList<>();
values.put(key, list);
}
list.add(value);
}
monitor.clearCancelled();
}
protected void createProgram(Language lang, CompilerSpec cSpec) throws IOException {
program = new ProgramDB(getProgramName(), lang, cSpec, this);
}
protected void createProgram(String languageID, String cSpecID) throws IOException {
Language language = getLanguageService().getLanguage(new LanguageID(languageID));
CompilerSpec cSpec = cSpecID == null ? language.getDefaultCompilerSpec()
: language.getCompilerSpecByID(new CompilerSpecID(cSpecID));
createProgram(language, cSpec);
}
public static void waitForDomainObject(DomainObject object) {
object.flushEvents();
waitForSwing();
}
protected void intoProject(DomainObject obj) {
waitForDomainObject(obj);
DomainFolder rootFolder = tool.getProject().getProjectData().getRootFolder();
waitForCondition(() -> {
try {
rootFolder.createFile(obj.getName(), obj, monitor);
return true;
}
catch (InvalidNameException | CancelledException e) {
throw new AssertionError(e);
}
catch (IOException e) {
// Usually "object is busy". Try again.
return false;
}
});
}
protected void addPlugins() throws Throwable {
codeBrowserPlugin = addPlugin(tool, CodeBrowserPlugin.class);
staticListing = codeBrowserPlugin.getProvider().getListingPanel();
addPlugin(tool, DisassemblerPlugin.class);
addPlugin(tool, DecompilePlugin.class);
taint = addPlugin(tool, TaintPlugin.class);
lisa = addPlugin(tool, LisaPlugin.class);
TaintState state = TaintState.newInstance(taint, "lisa");
taint.setTaintState(state);
decompilerProvider = waitForComponentProvider(DecompilerProvider.class);
decompilerPanel = decompilerProvider.getDecompilerPanel();
}
protected Function createSimpleProgramX86_64() throws Throwable {
createProgram("x86:LE:64:default", "gcc");
intoProject(program);
try (Transaction tx = program.openTransaction("Assemble")) {
ProgramBasedDataTypeManager dtm = program.getDataTypeManager();
Structure structure = new StructureDataType("MyStruct", 0, dtm);
structure.add(DWordDataType.dataType, "f1", "");
structure.add(DWordDataType.dataType, "f2", "");
structure.add(QWordDataType.dataType, "f3", "");
structure =
(Structure) dtm.addDataType(structure, DataTypeConflictHandler.DEFAULT_HANDLER);
Address entry = addr(program, 0x00400000);
program.getMemory()
.createInitializedBlock(".text", entry, 0x1000, (byte) 0, monitor, false);
Assembler asm = Assemblers.getAssembler(program.getLanguage(), NO_16BIT_CALLS);
AssemblyBuffer buf = new AssemblyBuffer(asm, entry);
buf.assemble("PUSH RBP");
buf.assemble("MOV RBP, RSP");
buf.assemble("MOV RAX, 0x4");
buf.assemble("SUB AX, 0x5");
buf.assemble("MOV RDX, RAX");
buf.assemble("RET");
Address end = buf.getNext();
program.getMemory().setBytes(entry, buf.getBytes());
Disassembler dis = Disassembler.getDisassembler(program, monitor, null);
dis.disassemble(entry, null);
Function funFillStruct = program.getFunctionManager()
.createFunction("simple", entry, new AddressSet(entry, end.previous()),
SourceType.ANALYSIS);
funFillStruct.addLocalVariable(new LocalVariableImpl("s", structure, -0x18, program),
SourceType.ANALYSIS);
return funFillStruct;
}
}
public static final AssemblySelector NO_16BIT_CALLS = new AssemblySelector() {
@Override
public Selection select(AssemblyResolutionResults rr, AssemblyPatternBlock ctx)
throws AssemblySemanticException {
for (AssemblyResolvedPatterns res : filterCompatibleAndSort(rr, ctx)) {
byte[] ins = res.getInstruction().getVals();
// HACK to avoid 16-bit CALL.... TODO: Why does this happen?
if (ins.length >= 2 && ins[0] == (byte) 0x66 && ins[1] == (byte) 0xe8) {
Msg.error(this,
"Filtered 16-bit call " + NumericUtilities.convertBytesToString(ins));
continue;
}
return new Selection(res.getInstruction().fillMask(), res.getContext());
}
throw new AssemblySemanticException(semanticErrors);
}
};
protected static Address addr(Program program, long offset) {
return program.getAddressFactory().getDefaultAddressSpace().getAddress(offset);
}
protected String typeOf(String key) {
return of(types, key);
}
protected String valueOf(String key) {
return of(values, key);
}
private String of(Map<String, List<String>> map, String key) {
List<String> list = map.get(key);
if (list == null) {
return null;
}
if (list.size() == 1) {
return list.get(0);
}
String[] sorted = new String[list.size()];
list.toArray(sorted);
Arrays.sort(sorted);
return Arrays.toString(sorted);
}
protected void equalsAssert(Object actual, Object expected) {
assertEquals(expected, actual);
}
}

View File

@@ -0,0 +1,72 @@
/* ###
* 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.lisa;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import ghidra.lisa.gui.LisaOptions.InterproceduralOption;
import ghidra.lisa.gui.LisaOptions.ValueDomainOption;
public class DataflowAnalysesTest extends AbstractLisaTest {
@Category(AbstractLisaTest.class)
@Test
public void testAvailable() {
lisaOptions.setValueDomain(ValueDomainOption.DDATA_AVAILABLE);
runTest();
equalsAssert(valueOf("0040000b:1:register:00000000"), "register:00000000 < 5"); //SUB AX, 0x5
equalsAssert(valueOf("0040000b:5:register:00000000"),
"[register:00000000 == 0, register:00000000 INT_SLESS 0]"); //SUB AX, 0x5
equalsAssert(valueOf("0040000f:0:register:00000000"),
"[register:00000000 == 0, register:00000000 INT_AND 255, register:00000000 INT_SLESS 0]"); //MOV RDX, RAX
equalsAssert(valueOf("00400012:0:register:00000010"), null); //RET
}
@Category(AbstractLisaTest.class)
@Test
public void testConstantPropagation() {
lisaOptions.setValueDomain(ValueDomainOption.DDATA_CONSTPROP);
runTest();
equalsAssert(valueOf("0040000b:0:register:00000000"), "4"); //SUB AX, 0x5
equalsAssert(valueOf("0040000f:0:register:00000000"), "ffff"); //MOV RDX, RAX
equalsAssert(valueOf("00400012:0:register:00000010"), "ffff"); //RET
}
@Category(AbstractLisaTest.class)
@Test
public void testLiveness() {
lisaOptions.setValueDomain(ValueDomainOption.PDATA_LIVENESS);
lisaOptions.setInterproceduralOption(InterproceduralOption.BACKWARDS);
runTest();
equalsAssert(valueOf("0040000b:0:register:00000000"), null); //SUB AX, 0x5
equalsAssert(valueOf("0040000b:1:register:00000000"), "register:00000000"); //SUB AX, 0x5
equalsAssert(valueOf("0040000f:0:register:00000000"), "register:00000000"); //MOV RDX, RAX
equalsAssert(valueOf("00400012:0:register:00000010"), null); //RET
}
@Category(AbstractLisaTest.class)
@Test
public void testReaching() {
lisaOptions.setValueDomain(ValueDomainOption.PDATA_REACHING);
runTest();
equalsAssert(valueOf("0040000b:0:register:00000000"), "register:00000000 = 4"); //SUB AX, 0x5
equalsAssert(valueOf("0040000f:0:register:00000000"),
"register:00000000 = INT_SUB(register:00000000, 5)"); //MOV RDX, RAX
equalsAssert(valueOf("00400012:0:register:00000010"),
"register:00000010 = register:00000000"); //RET
}
}

View File

@@ -0,0 +1,89 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.lisa;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import ghidra.lisa.gui.LisaOptions.InterproceduralOption;
import ghidra.lisa.gui.LisaOptions.ValueDomainOption;
public class InfoflowAnalysesTest extends AbstractLisaTest {
@Category(AbstractLisaTest.class)
@Test
public void testTaint() {
lisaOptions.setValueDomain(ValueDomainOption.VALUE_TAINT);
lisaOptions.setInterproceduralOption(InterproceduralOption.CONTEXT);
taintTarget = "register:00000000";
taintAddr = "00400004";
runTest();
equalsAssert(valueOf("0040000b:0:register:00000000"), "#"); //SUB AX, 0x5
equalsAssert(valueOf("0040000f:0:register:00000000"), "#"); //MOV RDX, RAX
equalsAssert(valueOf("0040000f:0:register:00000010"), null); //MOV RDX, RAX
equalsAssert(valueOf("00400012:0:register:00000010"), "#"); //RET
}
@Category(AbstractLisaTest.class)
@Test
public void testThreeLevelTaint() {
lisaOptions.setValueDomain(ValueDomainOption.VALUE_TAINT3L);
lisaOptions.setInterproceduralOption(InterproceduralOption.CONTEXT);
taintTarget = "register:00000000";
taintAddr = "0040000b";
runTest();
equalsAssert(valueOf("0040000b:0:register:00000000"), "_"); //SUB AX, 0x5
equalsAssert(valueOf("0040000f:0:register:00000000"), "#"); //MOV RDX, RAX
equalsAssert(valueOf("0040000f:0:register:00000010"), null); //MOV RDX, RAX
equalsAssert(valueOf("00400012:0:register:00000010"), "#"); //RET
}
/*
* Not clear how to run the following analyses correctly, so commented out
*/
//@Category(AbstractLisaTest.class)
//@Test
public void testConfidentiality() {
lisaOptions.setValueDomain(ValueDomainOption.NONINTERFERENCE);
runTest();
equalsAssert(valueOf("0040000b:0:register:00000000"), "4"); //SUB AX, 0x5
equalsAssert(valueOf("0040000f:0:register:00000000"), "-1"); //MOV RDX, RAX
equalsAssert(valueOf("00400012:0:register:00000010"), "-1"); //RET
}
//@Category(AbstractLisaTest.class)
//@Test
public void testIntegrity() {
lisaOptions.setValueDomain(ValueDomainOption.NONINTERFERENCE);
runTest();
equalsAssert(valueOf("0040000b:0:register:00000000"), "4"); //SUB AX, 0x5
equalsAssert(valueOf("0040000f:0:register:00000000"), "-1"); //MOV RDX, RAX
equalsAssert(valueOf("00400012:0:register:00000010"), "-1"); //RET
}
//@Category(AbstractLisaTest.class)
//@Test
public void testDeclassification() {
lisaOptions.setValueDomain(ValueDomainOption.NONINTERFERENCE);
lisaOptions.setInterproceduralOption(InterproceduralOption.FULLSTACK);
runTest();
equalsAssert(valueOf("0040000b:0:register:00000000"), "4"); //SUB AX, 0x5
equalsAssert(valueOf("0040000f:0:register:00000000"), "-1"); //MOV RDX, RAX
equalsAssert(valueOf("00400012:0:register:00000010"), "-1"); //RET
}
}

View File

@@ -0,0 +1,62 @@
/* ###
* 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.lisa;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import ghidra.lisa.gui.LisaOptions.InterproceduralOption;
import ghidra.lisa.gui.LisaOptions.ValueDomainOption;
public class MiscAnalysesTest extends AbstractLisaTest {
@Category(AbstractLisaTest.class)
@Test
public void testStability() {
lisaOptions.setValueDomain(ValueDomainOption.STABILITY);
lisaOptions.setInterproceduralOption(InterproceduralOption.CONTEXT);
runTest();
equalsAssert(valueOf("0040000b:0:register:00000000"), "="); //SUB AX, 0x5
equalsAssert(valueOf("0040000b:3:register:00000000"), ""); //SUB AX, 0x5
equalsAssert(valueOf("0040000f:0:register:00000000"), "="); //MOV RDX, RAX
equalsAssert(valueOf("0040000f:0:register:00000010"), null); //MOV RDX, RAX
equalsAssert(valueOf("00400012:0:register:00000010"), "="); //RET
}
@Category(AbstractLisaTest.class)
@Test
public void testNoninterference() {
lisaOptions.setValueDomain(ValueDomainOption.NONINTERFERENCE);
lisaOptions.setInterproceduralOption(InterproceduralOption.CONTEXT);
runTest();
equalsAssert(valueOf("0040000b:0:register:00000000"), "HL"); //SUB AX, 0x5
equalsAssert(valueOf("0040000f:0:register:00000010"), null); //MOV RDX, RAX
equalsAssert(valueOf("00400012:0:register:00000010"), "HL"); //RET
}
// @Category(AbstractLisaTest.class)
// @Test
// public void testTrend() {
// lisaOptions.setValueDomain(ValueDomainOption.VALUE_TREND);
// lisaOptions.setInterproceduralOption(InterproceduralOption.CONTEXT);
// runTest();
// equalsAssert(valueOf("0040000b:0:register:00000000"), "#TOP#"); //SUB AX, 0x5
// equalsAssert(valueOf("0040000f:0:register:00000000"), "#TOP#"); //MOV RDX, RAX
// equalsAssert(valueOf("0040000f:0:register:00000010"), null); //MOV RDX, RAX
// equalsAssert(valueOf("00400012:0:register:00000010"), "#TOP#"); //RET
// }
}

View File

@@ -0,0 +1,97 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.lisa;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import ghidra.lisa.gui.LisaOptions.DescendingPhaseOption;
import ghidra.lisa.gui.LisaOptions.ValueDomainOption;
public class NumericAnalysesTest extends AbstractLisaTest {
@Category(AbstractLisaTest.class)
@Test
public void testConstantPropagation() {
lisaOptions.setValueDomain(ValueDomainOption.VALUE_CONSTPROP);
runTest();
equalsAssert(valueOf("0040000b:0:register:00000000"), "4"); //SUB AX, 0x5
equalsAssert(valueOf("0040000f:0:register:00000000"), "ffff"); //MOV RDX, RAX
equalsAssert(valueOf("00400012:0:register:00000010"), "ffff"); //RET
}
@Category(AbstractLisaTest.class)
@Test
public void testInterval() {
lisaOptions.setValueDomain(ValueDomainOption.VALUE_INTERVAL);
runTest();
equalsAssert(valueOf("0040000b:0:register:00000000"), "[4, 4]"); //SUB AX, 0x5
equalsAssert(valueOf("0040000f:0:register:00000000"), "[-1, -1]"); //MOV RDX, RAX
equalsAssert(valueOf("00400012:0:register:00000010"), "[-1, -1]"); //RET
}
@Category(AbstractLisaTest.class)
@Test
public void testNonRedundantSetOfInterval() {
lisaOptions.setValueDomain(ValueDomainOption.VALUE_POWERSET);
lisaOptions.setDescendingPhaseOption(DescendingPhaseOption.GLB);
runTest();
equalsAssert(valueOf("0040000b:0:register:00000000"), "[-Inf, +Inf]"); //SUB AX, 0x5
equalsAssert(valueOf("0040000f:0:register:00000000"), "[-Inf, +Inf]"); //MOV RDX, RAX
equalsAssert(valueOf("00400012:0:register:00000010"), "[-Inf, +Inf]"); //RET
}
@Category(AbstractLisaTest.class)
@Test
public void testParity() {
lisaOptions.setValueDomain(ValueDomainOption.VALUE_PARITY);
runTest();
equalsAssert(valueOf("0040000b:0:register:00000000"), "Even"); //SUB AX, 0x5
equalsAssert(valueOf("0040000f:0:register:00000000"), "Odd"); //MOV RDX, RAX
equalsAssert(valueOf("00400012:0:register:00000010"), "Odd"); //RET
}
@Category(AbstractLisaTest.class)
@Test
public void testPentagons() {
init();
lisaOptions.setValueDomain(ValueDomainOption.VALUE_PENTAGON);
runTest();
equalsAssert(valueOf("0040000b:0:register:00000000"), "[4, 4]"); //SUB AX, 0x5
equalsAssert(valueOf("0040000f:0:register:00000000"), "[-1, -1]"); //MOV RDX, RAX
equalsAssert(valueOf("00400012:0:register:00000010"), "[-1, -1]"); //RET
}
@Category(AbstractLisaTest.class)
@Test
public void testSign() {
lisaOptions.setValueDomain(ValueDomainOption.VALUE_SIGN);
runTest();
equalsAssert(valueOf("0040000b:0:register:00000000"), "+"); //SUB AX, 0x5
equalsAssert(valueOf("0040000f:0:register:00000000"), "#TOP#"); //MOV RDX, RAX
equalsAssert(valueOf("00400012:0:register:00000010"), "#TOP#"); //RET
}
@Category(AbstractLisaTest.class)
@Test
public void testUpperBounds() {
lisaOptions.setValueDomain(ValueDomainOption.VALUE_UPPERBOUND);
runTest();
equalsAssert(valueOf("0040000b:0:register:00000000"), "{}"); //SUB AX, 0x5
equalsAssert(valueOf("0040000f:0:register:00000000"), "{}"); //MOV RDX, RAX
equalsAssert(valueOf("00400012:0:register:00000010"), "{}"); //RET
}
}

View File

@@ -38,7 +38,6 @@ import ghidra.util.exception.CancelledException;
* associated with the preferred keystore file specified by the {@link #KEYSTORE_PATH_PROPERTY}
* system property or set with {@link #setDefaultKeyStore(String, boolean)}.
* <p>
* <p>
* NOTE: Since {@link SslRMIClientSocketFactory} and {@link SSLServerSocketFactory} employ a
* static cache of a default {@link SSLSocketFactory}, with its default {@link SSLContext}, we
* must utilize a wrapped implementation of the associated {@link X509ExtendedKeyManager} so that