mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-12-30 10:39:40 -06:00
Merge remote-tracking branch 'origin/patch'
This commit is contained in:
4
Ghidra/Extensions/Lisa/Module.manifest
Normal file
4
Ghidra/Extensions/Lisa/Module.manifest
Normal 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
|
||||
57
Ghidra/Extensions/Lisa/build.gradle
Normal file
57
Ghidra/Extensions/Lisa/build.gradle
Normal 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"
|
||||
}
|
||||
|
||||
|
||||
|
||||
8
Ghidra/Extensions/Lisa/certification.manifest
Normal file
8
Ghidra/Extensions/Lisa/certification.manifest
Normal 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|
|
||||
5
Ghidra/Extensions/Lisa/extension.properties
Normal file
5
Ghidra/Extensions/Lisa/extension.properties
Normal 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@
|
||||
116
Ghidra/Extensions/Lisa/ghidra_scripts/LisaLaunchScript.java
Normal file
116
Ghidra/Extensions/Lisa/ghidra_scripts/LisaLaunchScript.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
19
Ghidra/Extensions/Lisa/src/main/help/help/TOC_Source.xml
Normal file
19
Ghidra/Extensions/Lisa/src/main/help/help/TOC_Source.xml
Normal 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>
|
||||
@@ -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 → 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. ← </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] ∧ x < 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>
|
||||
@@ -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() + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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> ∋
|
||||
* S<sub>2</sub> | ∃ s<sub>1</sub> ∋ S<sub>1</sub> : s<sub>1</sub>
|
||||
* ≤ s<sub>2</sub>} ∪ {lub(s'<sub>1</sub>, s<sub>2</sub>) |
|
||||
* s'<sub>1</sub> ∋ S<sub>1</sub>, s<sub>2</sub> ∋ S<sub>2</sub>, NOT
|
||||
* ∃ s<sub>1</sub> ∋ S<sub>1</sub> : s<sub>1</sub> ≤
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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]
|
||||
* ∧ x < 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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user