diff --git a/Ghidra/Extensions/Lisa/Module.manifest b/Ghidra/Extensions/Lisa/Module.manifest new file mode 100644 index 0000000000..23adedea0b --- /dev/null +++ b/Ghidra/Extensions/Lisa/Module.manifest @@ -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 diff --git a/Ghidra/Extensions/Lisa/build.gradle b/Ghidra/Extensions/Lisa/build.gradle new file mode 100644 index 0000000000..dcdbee74e6 --- /dev/null +++ b/Ghidra/Extensions/Lisa/build.gradle @@ -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" +} + + + diff --git a/Ghidra/Extensions/Lisa/certification.manifest b/Ghidra/Extensions/Lisa/certification.manifest new file mode 100644 index 0000000000..419972a702 --- /dev/null +++ b/Ghidra/Extensions/Lisa/certification.manifest @@ -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| diff --git a/Ghidra/Extensions/Lisa/extension.properties b/Ghidra/Extensions/Lisa/extension.properties new file mode 100644 index 0000000000..f40ce7e810 --- /dev/null +++ b/Ghidra/Extensions/Lisa/extension.properties @@ -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@ diff --git a/Ghidra/Extensions/Lisa/ghidra_scripts/LisaLaunchScript.java b/Ghidra/Extensions/Lisa/ghidra_scripts/LisaLaunchScript.java new file mode 100644 index 0000000000..5c0b42ab11 --- /dev/null +++ b/Ghidra/Extensions/Lisa/ghidra_scripts/LisaLaunchScript.java @@ -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 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 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); + } + } + } + +} diff --git a/Ghidra/Extensions/Lisa/ghidra_scripts/Lisa_ResolveX86orX64LinuxSyscallsScript.java b/Ghidra/Extensions/Lisa/ghidra_scripts/Lisa_ResolveX86orX64LinuxSyscallsScript.java new file mode 100644 index 0000000000..20ea885e73 --- /dev/null +++ b/Ghidra/Extensions/Lisa/ghidra_scripts/Lisa_ResolveX86orX64LinuxSyscallsScript.java @@ -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 noreturnSyscalls = Set.of("exit", "exit_group"); + + //tests whether an instruction is making a system call + private Predicate 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 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> 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 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 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 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 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 getSyscallNumberMap() { + Map 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> getSyscallsInFunctions(Program program, + TaskMonitor tMonitor) throws CancelledException { + Map> 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
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 resolveConstants(Map> funcsToCalls, + Program program, TaskMonitor tMonitor) throws CancelledException { + initLisa(); + Set 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 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 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 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 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 ep = p.getEntryPoints(); + for (CFG cfg : ep) { + Collection 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; + } + +} diff --git a/Ghidra/Extensions/Lisa/src/main/help/help/TOC_Source.xml b/Ghidra/Extensions/Lisa/src/main/help/help/TOC_Source.xml new file mode 100644 index 0000000000..8235a43513 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/help/help/TOC_Source.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + diff --git a/Ghidra/Extensions/Lisa/src/main/help/help/topics/LisaPlugin/LisaPlugin.html b/Ghidra/Extensions/Lisa/src/main/help/help/topics/LisaPlugin/LisaPlugin.html new file mode 100644 index 0000000000..a91bb3e4b5 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/help/help/topics/LisaPlugin/LisaPlugin.html @@ -0,0 +1,134 @@ + + + + + + + Abstract Interpretation: LiSA + + + + + +

Abstract Interpretation: LiSA

+ +

The Lisa Plugin uses the "Library for Static Analysis", LiSA, 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. +

+ +

Setup

+ +

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.) +

+ +

Options

+ +

The analysis to run is chosen via the Edit → ToolOptions, which gives you a default category + Abstract Interpretation and two subcategories Abstract Interpretation/Domains and + Abstract Interpretation/Output. Domains is the most important of these, as it provides the list of available analyses. +

+ +

+ Domains has three options, corresponding to the choice of Heap, Type, and Value domains, + the last being the option you are most likely to want to change. +

+
    +
  • Heap: the abstraction used to model the target program's heap.
  • +
  • Type: the abstraction used to model types.
  • +
  • Value: the abstraction used to model values. ←
  • +
+

+ The symbolic state of an analysis is the combination of these domains. +

+ +

+ Output has several options (not well-tested), which correspond to fields in the LiSAConfiguration class. +

+
    +
  • WorkDir: the working directory to be used for output if SerializeResults is selected.
  • +
  • GraphFormat: graph format if graph output is desired.
  • +
  • SerializeResults: causes the analysis results to be dumped as JSON.
  • +
+ +

+ Abstract Interpretation (the base option set) has additional execution-related options, associated again with LiSAConfiguration. +

+
    +
  • CallGraph: "Class Hierarchy" or "Rapid Type" analysis of calls.
  • +
  • DescendingPhase: the descending phase applied by the fixpoint algorithm.
  • +
  • Interprocedural: type of interprocedural analysis desired.
  • +
  • OpenCallPolicy: how to treat unresolved calls.
  • +
  • OptimizeResults: whether to optimize fixpoint execution.
  • +
  • PostState: evaluate state post- or pre-statement.
  • +
  • CallDepth: cfg-computation depth (-1 == unlimited).
  • +
  • Threshhold: applied to either widening or glb.
  • +
+

+ For the most part, these options may be left to their defaults. However, specific analyses may benefit from different choices. +

+ +

Actions

+ +

Add CFGs

+

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. +

+ +

Clear CFGs

+

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. +

+ +

Set Taint

+

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 SOURCES of taint. + (SINKS and GATES 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. +

+ +

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). +

+ +

Current Analyses (Value Domains)

+
    +
  • Numeric: Constant Propagation: overflow-insensitive basic constant propagation.
  • +
  • Numeric: Interval: approximating numeric values as the minimum interval containing them.
  • +
  • Numeric: Non-redundant Power Sets Of Intervals: approximating numeric values as a non-redundant set of intervals.
  • +
  • Numeric: Parity: tracking whether numeric values are even or odd.
  • +
  • Numeric: Pentagon: capturing properties of the form of x in [a, b] ∧ x < y.
  • +
  • Numeric: Sign: tracking zero, strictly positive and strictly negative values.
  • +
  • Numeric: Upper Bound: capturing upper bounds on a variable.
  • +
  • Dataflow: Available Expressions: expressions stored in some variable.
  • +
  • Dataflow: Constant Propagation: overflow-insensitive basic constant propagation.
  • +
  • Dataflow: Reaching Definitions: instruction whose targets reach the given one without an intervening assignment.
  • +
  • Dataflow: Liveness: variables that are live at each point in the program. + NB: typically uses the BackwardModularWorstCase interprocedural analysis.
  • +
  • Dataflow: Taint: two levels of taintedness - clean and tainted. See above re setting taint.
  • +
  • Dataflow: Three-level Taint: three levels of taintedness - clean, tainted, and top. See above re setting taint.
  • +
  • Non-interference: type-system based implementation of non-interference analysis.
  • +
  • Stability: per-variable numerical trends.
  • +
+

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. +

+ +

Results

+ +

Results are passed, by default, via SARIF to a results table. The functionality of the table is described in more detail + in Decompiler Taint Operations. In general, you'll want to enable the "name", "type", "value", + and "displayName" columns, as well as "Address" and possibly "location". +

+ + diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/gui/ExtKeyValue.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/gui/ExtKeyValue.java new file mode 100644 index 0000000000..4cfa4828b4 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/gui/ExtKeyValue.java @@ -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() + "]"; + } + +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/gui/LisaOptions.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/gui/LisaOptions.java new file mode 100644 index 0000000000..bf99d481e5 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/gui/LisaOptions.java @@ -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. + * + *

+ * 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; + } + +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/gui/LisaPlugin.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/gui/LisaPlugin.java new file mode 100644 index 0000000000..a4c9f23824 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/gui/LisaPlugin.java @@ -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 cfgs = new HashSet<>(); + Address addr = context.getAddress(); + FunctionManager functionManager = currentProgram.getFunctionManager(); + Function f = functionManager.getFunctionContaining(addr); + addCfg(cfgs, f, true); + } + + private void addCfg(Set 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 baseline = p.getAllCFGs(); + for (CFG g : cfgs) { + if (baseline.contains(g)) { + p.addEntryPoint(g); + } + } + } + + private void addFunction(Set 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 cfgs, Function f, int depth) { + if (depth == 0 || frontend.hasProcessed(f) || monitor.isCancelled()) { + return; + } + Set 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 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 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> 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> combined = new HashMap<>(); + if (monitor.isCancelled()) { + return combined; + } + LiSAReport report = lisa.run(p); + ipa = report.getConfiguration().interproceduralAnalysis; + + FunctionManager functionManager = currentProgram.getFunctionManager(); + Map 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 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; + } + +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/gui/LisaTaintState.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/gui/LisaTaintState.java new file mode 100644 index 0000000000..cdb3edcff2 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/gui/LisaTaintState.java @@ -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 logicalLocations = new HashMap<>(); + private Set annotationSet = new HashSet<>(); + private int llIndex = 0; + private boolean suppressTop = true; + private boolean suppressUnique = true; + + private Map> 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> results = lisa.performAnalysis(monitor); + if (results != null) { + readQueryResultsIntoDataFrame(program, results); + } + return results != null; + } + + public void clearAnnotations() { + for (VariableRef var : annotationSet) { + Collection 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> 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> generateRegisterMap(Program program) { + Language language = program.getLanguage(); + for (Register r : language.getRegisters()) { + Map sizeMap = registerNames.computeIfAbsent(r.getAddress().toString(), + a -> new HashMap()); + 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 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 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 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 getTypeMap(SimpleAbstractState sas) { + if (sas.getTypeState() instanceof TypeEnvironment te) { + return te.function; + } + return null; + } + + private KTV repToStrings(String key, StructuredRepresentation rep, Map 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 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 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 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 paramList, String enginePath, File indexDBFile, + String indexDirectory) { + //UNNEEDED + } + + @Override + public void buildIndex(List 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; + } + +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/gui/SarifKeyValueWriter.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/gui/SarifKeyValueWriter.java new file mode 100644 index 0000000000..753bf8f227 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/gui/SarifKeyValueWriter.java @@ -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)); + } + +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/gui/SarifLogicalLocationWriter.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/gui/SarifLogicalLocationWriter.java new file mode 100644 index 0000000000..d4c18489d2 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/gui/SarifLogicalLocationWriter.java @@ -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; + } + +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/PcodeBranch.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/PcodeBranch.java new file mode 100644 index 0000000000..d70c541de0 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/PcodeBranch.java @@ -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 cfgMatrix, Statement condition) { + super(cfgMatrix, condition, null); + } + + @Override + protected Collection bodyStatements() { + Collection all = new HashSet<>(getTrueBranch()); + all.addAll(getFalseBranch()); + return all; + } + + private Collection getFalseBranch() { + if (fallThrough == null) { + return Set.of(); + } + return new HashSet<>(cfgMatrix.followersOf(fallThrough)); + } + + private Collection 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 getTargetedStatements() { + return bodyStatements(); + } + + public void addStatement(Statement st, PredType type) { + if (type.equals(PredType.TRUE)) { + branch = st; + } + else { + fallThrough = st; + setFirstFollower(st); + } + } + +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/PcodeCodeMemberVisitor.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/PcodeCodeMemberVisitor.java new file mode 100644 index 0000000000..c7392c6b23 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/PcodeCodeMemberVisitor.java @@ -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 list; + + private final Collection entrypoints; + private final Collection cfs; + private final Map> visibleIds; + + private final CFG cfg; + + private final CodeMemberDescriptor descriptor; + + private Listing listing; + + private final Collection visited; + private final Map visitedPcode; + private Stack workItems; + + private UnitContext currentUnit; + + private Map 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 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 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 getFlows() { + return flows; + } + +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/PcodeFeatures.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/PcodeFeatures.java new file mode 100644 index 0000000000..209250b1d3 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/PcodeFeatures.java @@ -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(); + } +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/PcodeFrontend.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/PcodeFrontend.java new file mode 100644 index 0000000000..450cc1f74a --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/PcodeFrontend.java @@ -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> nodeMap = new HashMap<>(); + private Map targets = new HashMap<>(); + private Set 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 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 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 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 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 getTargets() { + return targets; + } + +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/WorkItem.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/WorkItem.java new file mode 100644 index 0000000000..433a31abc3 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/WorkItem.java @@ -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; + } +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/LongInterval.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/LongInterval.java new file mode 100644 index 0000000000..7207b313d3 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/LongInterval.java @@ -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. + * + *

+ * Modified to handle pcode from original source written by: + *

+ * @author Luca Negrini + */ +public class LongInterval implements Iterable, Comparable { + + /** + * 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 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); + } +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeByteBasedConstantPropagation.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeByteBasedConstantPropagation.java new file mode 100644 index 0000000000..22be75d65d --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeByteBasedConstantPropagation.java @@ -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. + * + *

+ * Modified to handle pcode from original source written by: + *

+ * @author Vincenzo Arceri + */ +public class PcodeByteBasedConstantPropagation + implements PcodeNonRelationalValueDomain { + + 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 assumeBinaryExpression( + ValueEnvironment 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(); + } +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeDataflowConstantPropagation.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeDataflowConstantPropagation.java new file mode 100644 index 0000000000..b40553f618 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeDataflowConstantPropagation.java @@ -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. + * + *

+ * Modified to handle pcode from original source written by: + *

+ * @author Luca Negrini + */ +public class PcodeDataflowConstantPropagation implements + DataflowElement, 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 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 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 gen( + Identifier idg, + ValueExpression expression, + ProgramPoint pp, + DefiniteDataflowDomain domain) { + Set gen = new HashSet<>(); + + Long v = eval(expression, pp, domain); + if (v != null) { + gen.add(new PcodeDataflowConstantPropagation(idg, v)); + } + + return gen; + } + + @Override + public Collection gen( + ValueExpression expression, + ProgramPoint pp, + DefiniteDataflowDomain domain) { + return Collections.emptyList(); + } + + @Override + public Collection kill( + Identifier idk, + ValueExpression expression, + ProgramPoint pp, + DefiniteDataflowDomain domain) { + Collection result = new HashSet<>(); + + for (PcodeDataflowConstantPropagation cp : domain.getDataflowElements()) { + if (cp.id.equals(idk)) { + result.add(cp); + } + } + + return result; + } + + @Override + public Collection kill( + ValueExpression expression, + ProgramPoint pp, + DefiniteDataflowDomain 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); + } + +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeInterval.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeInterval.java new file mode 100644 index 0000000000..50756af219 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeInterval.java @@ -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. + * + *

+ * Modified to handle pcode from original source written by: + *

+ * @author Vincenzo Arceri + */ +public class PcodeInterval + implements PcodeNonRelationalValueDomain, Comparable { + + /** + * 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 assumeBinaryExpression( + ValueEnvironment 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(); + } +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeNonRedundantPowersetOfInterval.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeNonRedundantPowersetOfInterval.java new file mode 100644 index 0000000000..050553804f --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeNonRedundantPowersetOfInterval.java @@ -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 { + + /** + * 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 elements) { + super(elements, Interval.BOTTOM); + } + + /** + * This specific Egli-Milner connector follows this definition:
+ * given two subsets S1 and S2 of a domain of a + * lattice: + *

+ * S1 +EM S2 = {s2 ∋ + * S2 | ∃ s1 ∋ S1 : s1 + * ≤ s2} ∪ {lub(s'1, s2) | + * s'1 ∋ S1, s2 ∋ S2, NOT + * ∃ s1 ∋ S1 : s1 ≤ + * s2} + *

+ * s'1 can be chosen randomly but in this case is chosen to be + * the closest interval to s2 (closest based on + * {@link #middlePoint(Interval) middle point}). + */ + @Override + protected PcodeNonRedundantPowersetOfInterval EgliMilnerConnector( + PcodeNonRedundantPowersetOfInterval other) + throws SemanticException { + SortedSet newElementsSet = new TreeSet<>(); + SortedSet 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 assumeBinaryExpression( + ValueEnvironment 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 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 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); + } +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeNonRelationalValueDomain.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeNonRelationalValueDomain.java new file mode 100644 index 0000000000..069113e9e0 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeNonRelationalValueDomain.java @@ -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 the concrete type of this domain + */ +public interface PcodeNonRelationalValueDomain> + extends BaseNonRelationalValueDomain { + + 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; + } +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeParity.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeParity.java new file mode 100644 index 0000000000..4b2e40a3dd --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeParity.java @@ -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. + * + *

+ * Modified to handle pcode from original source written by: + *

+ * @author Vincenzo Arceri + */ +public class PcodeParity implements PcodeNonRelationalValueDomain { + + /** + * 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 assumeBinaryExpression( + ValueEnvironment 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(); + } + +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodePentagon.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodePentagon.java new file mode 100644 index 0000000000..6f90d68b34 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodePentagon.java @@ -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}. + * + *

+ * Modified to handle pcode from original source written by: + *

+ * @author Luca Negrini + * @author Vincenzo Arceri + * + *

+ * @see Pentagons: + * A weakly relational abstract domain for the efficient validation of + * array accesses + */ +public class PcodePentagon implements ValueDomain, BaseLattice { + + /** + * The interval environment. + */ + private final ValueEnvironment intervals; + + /** + * The upper bounds environment. + */ + private final ValueEnvironment 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 intervals, + ValueEnvironment upperBounds) { + this.intervals = intervals; + this.upperBounds = upperBounds; + } + + @Override + public PcodePentagon assign( + Identifier id, + ValueExpression expression, + ProgramPoint pp, + SemanticOracle oracle) + throws SemanticException { + ValueEnvironment newBounds = + getUpperBounds().assign(id, expression, pp, oracle); + ValueEnvironment 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 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 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 newBounds = new ValueEnvironment( + getUpperBounds().lattice, getUpperBounds().getMap()); + + for (Identifier id1 : getIntervals().getKeys()) { + Set 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 newBounds = getUpperBounds().lub(other.getUpperBounds()); + for (Entry entry : getUpperBounds()) { + Set 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 entry : other.getUpperBounds()) { + Set 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 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 getIntervals() { + return intervals; + } + + public ValueEnvironment getUpperBounds() { + return upperBounds; + } +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeSign.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeSign.java new file mode 100644 index 0000000000..6ad4d207fc --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeSign.java @@ -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. + * + *

+ * Modified to handle pcode from original source written by: + *

+ * @author Vincenzo Arceri + */ +public class PcodeSign implements PcodeNonRelationalValueDomain { + + 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 assumeBinaryExpression( + ValueEnvironment 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(); + } +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeStability.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeStability.java new file mode 100644 index 0000000000..e5335f6a2e --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeStability.java @@ -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.
+ *
+ * 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}. + * + *

+ * Modified to handle pcode from original source written by: + *

+ * @author Luca Negrini + * + * @param the kind of auxiliary domain + */ +public class PcodeStability> + implements + BaseLattice>, + ValueDomain> { + + private final V aux; + + private final ValueEnvironment 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 trends) { + this.aux = trends.isBottom() ? aux.bottom() : aux; + this.trends = aux.isBottom() ? trends.bottom() : trends; + } + + @Override + public PcodeStability lubAux( + PcodeStability other) + throws SemanticException { + V ad = aux.lub(other.aux); + ValueEnvironment t = trends.lub(other.trends); + if (ad.isBottom() || t.isBottom()) { + return bottom(); + } + return new PcodeStability<>(ad, t); + } + + @Override + public PcodeStability glbAux( + PcodeStability other) + throws SemanticException { + V ad = aux.glb(other.aux); + ValueEnvironment t = trends.glb(other.trends); + if (ad.isBottom() || t.isBottom()) { + return bottom(); + } + return new PcodeStability<>(ad, t); + } + + @Override + public PcodeStability wideningAux( + PcodeStability other) + throws SemanticException { + V ad = aux.widening(other.aux); + ValueEnvironment t = trends.widening(other.trends); + if (ad.isBottom() || t.isBottom()) { + return bottom(); + } + return new PcodeStability<>(ad, t); + } + + @Override + public boolean lessOrEqualAux( + PcodeStability 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 top() { + return new PcodeStability<>(aux.top(), trends.top()); + } + + @Override + public PcodeStability bottom() { + return new PcodeStability<>(aux.bottom(), trends.bottom()); + } + + @Override + public PcodeStability pushScope( + ScopeToken token) + throws SemanticException { + return new PcodeStability<>(aux.pushScope(token), trends.pushScope(token)); + } + + @Override + public PcodeStability 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 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 trnd = stabilize(trends).putState(id, t); + if (trnd.isBottom()) { + return bottom(); + } + return new PcodeStability<>(post, trnd); + } + + @Override + public PcodeStability smallStepSemantics( + ValueExpression expression, + ProgramPoint pp, + SemanticOracle oracle) + throws SemanticException { + V post = aux.smallStepSemantics(expression, pp, oracle); + ValueEnvironment sss = stabilize(trends).smallStepSemantics(expression, pp, oracle); + if (post.isBottom() || sss.isBottom()) { + return bottom(); + } + return new PcodeStability<>(post, sss); + } + + @Override + public PcodeStability assume( + ValueExpression expression, + ProgramPoint src, + ProgramPoint dest, + SemanticOracle oracle) + throws SemanticException { + V post = aux.assume(expression, src, dest, oracle); + ValueEnvironment 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 forgetIdentifier( + Identifier id) + throws SemanticException { + return new PcodeStability<>(aux.forgetIdentifier(id), trends.forgetIdentifier(id)); + } + + @Override + public PcodeStability forgetIdentifiersIf( + Predicate 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 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 combine( + PcodeStability other) + throws SemanticException { + ValueEnvironment 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> getCovarianceClasses() { + Map> 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 stabilize( + ValueEnvironment trends) { + ValueEnvironment result = new ValueEnvironment<>(trends.lattice); + + for (Identifier id : trends.getKeys()) { + result = result.putState(id, Trend.STABLE); + } + + return result; + } +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeTaint.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeTaint.java new file mode 100644 index 0000000000..ac812f435c --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeTaint.java @@ -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. + * + *

+ * Modified to handle pcode from original source written by: + *

+ * @author Luca Negrini + */ +public class PcodeTaint extends BaseTaint { + + 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(); + } + +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeThreeLevelTaint.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeThreeLevelTaint.java new file mode 100644 index 0000000000..0d052b4f91 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeThreeLevelTaint.java @@ -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. + * + *

+ * Modified to handle pcode from original source written by: + *

+ * @author Luca Negrini + */ +public class PcodeThreeLevelTaint extends BaseTaint { + + 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(); + } +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeUpperBounds.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeUpperBounds.java new file mode 100644 index 0000000000..24f9e90bba --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/PcodeUpperBounds.java @@ -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}. + * + *

+ * Modified to handle pcode from original source written by: + *

+ * @author Luca Negrini + * @author Vincenzo Arceri + */ +public class PcodeUpperBounds + implements PcodeNonRelationalValueDomain, Iterable { + + /** + * 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 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 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 lub = new HashSet<>(bounds); + lub.retainAll(other.bounds); + return new PcodeUpperBounds(lub); + } + + @Override + public PcodeUpperBounds glbAux( + PcodeUpperBounds other) + throws SemanticException { + Set 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 assumeBinaryExpression( + ValueEnvironment 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 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 res = new HashSet<>(); + if (!isTop() && !isBottom()) { + res.addAll(bounds); + } + res.add(id); + return new PcodeUpperBounds(res); + } + + @Override + public PcodeUpperBounds getValue(RegisterValue rv) { + return top(); + } +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/README.md b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/README.md new file mode 100644 index 0000000000..e667265e8d --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/analyses/README.md @@ -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. \ No newline at end of file diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/BinaryExprContext.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/BinaryExprContext.java new file mode 100644 index 0000000000..7f2303caf3 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/BinaryExprContext.java @@ -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(); + } + +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/CallContext.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/CallContext.java new file mode 100644 index 0000000000..4a2f0c21e8 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/CallContext.java @@ -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; + } + +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/ConditionContext.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/ConditionContext.java new file mode 100644 index 0000000000..9db334094f --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/ConditionContext.java @@ -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)); + } + +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/InstructionContext.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/InstructionContext.java new file mode 100644 index 0000000000..7d9189a229 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/InstructionContext.java @@ -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 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 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; + } + +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/MemLocContext.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/MemLocContext.java new file mode 100644 index 0000000000..25be770e7e --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/MemLocContext.java @@ -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(); + } + +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/PcodeContext.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/PcodeContext.java new file mode 100644 index 0000000000..6ea831ab1f --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/PcodeContext.java @@ -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(); + } +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/StatementContext.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/StatementContext.java new file mode 100644 index 0000000000..3932a8a3cf --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/StatementContext.java @@ -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 branch(Listing listing, UnitContext currentUnit) { + List 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; + } + +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/SymbolVarnodeContext.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/SymbolVarnodeContext.java new file mode 100644 index 0000000000..e2ee12fe04 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/SymbolVarnodeContext.java @@ -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; + } + +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/UnaryExprContext.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/UnaryExprContext.java new file mode 100644 index 0000000000..732d3d07e6 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/UnaryExprContext.java @@ -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(); + } + +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/UnitContext.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/UnitContext.java new file mode 100644 index 0000000000..55e5216887 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/UnitContext.java @@ -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); + } +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/VarDefContext.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/VarDefContext.java new file mode 100644 index 0000000000..ee883a80c4 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/VarDefContext.java @@ -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(); + } + +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/VarnodeContext.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/VarnodeContext.java new file mode 100644 index 0000000000..55289147f5 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/contexts/VarnodeContext.java @@ -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(); + } + +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/expressions/PcodeBinaryExpression.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/expressions/PcodeBinaryExpression.java new file mode 100644 index 0000000000..dd924f4e05 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/expressions/PcodeBinaryExpression.java @@ -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 > AnalysisState fwdBinarySemantics( + InterproceduralAnalysis interprocedural, + AnalysisState state, + SymbolicExpression left, + SymbolicExpression right, + StatementStore expressions) + throws SemanticException { + + return state.smallStepSemantics( + new BinaryExpression( + getStaticType(), + left, + right, + operator, + getLocation()), + this); + } +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/expressions/PcodeCallExpression.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/expressions/PcodeCallExpression.java new file mode 100644 index 0000000000..e99ad373e5 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/expressions/PcodeCallExpression.java @@ -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 unaffected = new HashSet<>(); + private Set 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 > AnalysisState forwardSemantics( + AnalysisState entryState, + InterproceduralAnalysis interprocedural, + StatementStore 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; + } +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/expressions/PcodeUnaryExpression.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/expressions/PcodeUnaryExpression.java new file mode 100644 index 0000000000..7ddf89f7ce --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/expressions/PcodeUnaryExpression.java @@ -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 > AnalysisState fwdUnarySemantics( + InterproceduralAnalysis interprocedural, + AnalysisState state, + SymbolicExpression expr, + StatementStore expressions) + throws SemanticException { + + return state.smallStepSemantics( + new UnaryExpression( + expr.getStaticType(), + expr, + operator, + getLocation()), + this); + } +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/locations/InstLocation.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/locations/InstLocation.java new file mode 100644 index 0000000000..f383c695d8 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/locations/InstLocation.java @@ -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; + } + +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/locations/PcodeLocation.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/locations/PcodeLocation.java new file mode 100644 index 0000000000..0fc6e594b4 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/locations/PcodeLocation.java @@ -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(); + } +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/statements/PcodeBinaryOperator.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/statements/PcodeBinaryOperator.java new file mode 100644 index 0000000000..fa2a896699 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/statements/PcodeBinaryOperator.java @@ -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 typeInference( + TypeSystem types, + Set left, + Set right) { + Set set = NumericType.commonNumericalType(left, right); + if (!set.isEmpty()) + return set; + return Collections.singleton(types.getBooleanType()); + } +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/statements/PcodeNop.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/statements/PcodeNop.java new file mode 100644 index 0000000000..42f16d49e7 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/statements/PcodeNop.java @@ -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. + * + *

+ * Modified to handle pcode from original source written by: + *

+ * @author Luca Negrini + */ +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 > AnalysisState forwardSemantics( + AnalysisState entryState, + InterproceduralAnalysis interprocedural, + StatementStore expressions) + throws SemanticException { + return entryState.smallStepSemantics(new Skip(getLocation()), this); + } + + @Override + public boolean accept( + GraphVisitor visitor, + V tool) { + return visitor.visit(tool, getCFG(), this); + } +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/statements/PcodeUnaryOperator.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/statements/PcodeUnaryOperator.java new file mode 100644 index 0000000000..04a7073c58 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/statements/PcodeUnaryOperator.java @@ -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 typeInference( + TypeSystem types, + Set argument) { + return Collections.singleton(types.getBooleanType()); + } +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/types/PcodeInferredTypes.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/types/PcodeInferredTypes.java new file mode 100644 index 0000000000..ad48863591 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/types/PcodeInferredTypes.java @@ -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 { + + private static final PcodeInferredTypes BOTTOM = + new PcodeInferredTypes(null, Collections.emptySet()); + + private final Set 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) 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 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 types) { + this.elements = types; + this.isTop = isTop; + } + + @Override + public Set 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 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 elems = arg.isTop() ? types.getTypes() : arg.elements; + Set 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 lelems = left.isTop() ? types.getTypes() : left.elements; + Set relems = right.isTop() ? types.getTypes() : right.elements; + Set 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 lelems = left.isTop() ? types.getTypes() : left.elements; + Set melems = middle.isTop() ? types.getTypes() : middle.elements; + Set relems = right.isTop() ? types.getTypes() : right.elements; + Set 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 lelems = left.isTop() ? types.getTypes() : left.elements; + Set 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 lfiltered = + lelems.stream().filter(Type::isTypeTokenType).collect(Collectors.toSet()); + Set 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 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())}.
+ *
+ * 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 lfiltered, + Set 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 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 lelems = left.isTop() ? types.getTypes() : left.elements; + Set relems = right.isTop() ? types.getTypes() : right.elements; + Set 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 lelems = left.isTop() ? types.getTypes() : left.elements; + Set relems = right.isTop() ? types.getTypes() : right.elements; + Set inferred = conv.getOperator().typeInference(types, lelems, relems); + if (inferred.isEmpty()) + return BOTTOM; + return new PcodeInferredTypes(types, inferred); + } +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/types/PcodeStaticTypes.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/types/PcodeStaticTypes.java new file mode 100644 index 0000000000..a05122dd8d --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/types/PcodeStaticTypes.java @@ -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}. + * + *

+ * @author Luca Negrini + */ +public class PcodeStaticTypes implements BaseNonRelationalTypeDomain { + + 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 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 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 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 lelems = left.type.allInstances(etypes); + Set relems = right.type.allInstances(etypes); + Set 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 lelems = left.type.allInstances(stypes); + Set 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; + } +} diff --git a/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/types/PcodeTypeSystem.java b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/types/PcodeTypeSystem.java new file mode 100644 index 0000000000..89324f98ca --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/main/java/ghidra/lisa/pcode/types/PcodeTypeSystem.java @@ -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. + * + *

+ * @author Luca Negrini + */ +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(); + } + +} diff --git a/Ghidra/Extensions/Lisa/src/test/java/ghidra/lisa/AbstractLisaTest.java b/Ghidra/Extensions/Lisa/src/test/java/ghidra/lisa/AbstractLisaTest.java new file mode 100644 index 0000000000..db4822aa8d --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/test/java/ghidra/lisa/AbstractLisaTest.java @@ -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> types; + protected HashMap> 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> results = df.getTableResults(); + types = new HashMap<>(); + values = new HashMap<>(); + for (Map 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 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> map, String key) { + List 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); + } + +} diff --git a/Ghidra/Extensions/Lisa/src/test/java/ghidra/lisa/DataflowAnalysesTest.java b/Ghidra/Extensions/Lisa/src/test/java/ghidra/lisa/DataflowAnalysesTest.java new file mode 100644 index 0000000000..17d6935097 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/test/java/ghidra/lisa/DataflowAnalysesTest.java @@ -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 + } +} diff --git a/Ghidra/Extensions/Lisa/src/test/java/ghidra/lisa/InfoflowAnalysesTest.java b/Ghidra/Extensions/Lisa/src/test/java/ghidra/lisa/InfoflowAnalysesTest.java new file mode 100644 index 0000000000..0ac7e713a6 --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/test/java/ghidra/lisa/InfoflowAnalysesTest.java @@ -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 + } + +} diff --git a/Ghidra/Extensions/Lisa/src/test/java/ghidra/lisa/MiscAnalysesTest.java b/Ghidra/Extensions/Lisa/src/test/java/ghidra/lisa/MiscAnalysesTest.java new file mode 100644 index 0000000000..29db6e262e --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/test/java/ghidra/lisa/MiscAnalysesTest.java @@ -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 +// } + +} diff --git a/Ghidra/Extensions/Lisa/src/test/java/ghidra/lisa/NumericAnalysesTest.java b/Ghidra/Extensions/Lisa/src/test/java/ghidra/lisa/NumericAnalysesTest.java new file mode 100644 index 0000000000..b52861bcfe --- /dev/null +++ b/Ghidra/Extensions/Lisa/src/test/java/ghidra/lisa/NumericAnalysesTest.java @@ -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 + } +}