diff --git a/Ghidra/Features/Base/ghidra_scripts/MultiInstructionMemReference.java b/Ghidra/Features/Base/ghidra_scripts/MultiInstructionMemReference.java index 8681e16449..70b4f86dfb 100644 --- a/Ghidra/Features/Base/ghidra_scripts/MultiInstructionMemReference.java +++ b/Ghidra/Features/Base/ghidra_scripts/MultiInstructionMemReference.java @@ -39,37 +39,59 @@ //@category Analysis import java.math.BigInteger; +import java.util.Arrays; +import java.util.List; import ghidra.app.script.GhidraScript; -import ghidra.program.model.address.*; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressRange; +import ghidra.program.model.address.AddressRangeImpl; +import ghidra.program.model.address.AddressRangeIterator; +import ghidra.program.model.address.AddressSet; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.address.AddressSpace; import ghidra.program.model.block.CodeBlock; import ghidra.program.model.block.PartitionCodeSubModel; -import ghidra.program.model.lang.*; +import ghidra.program.model.lang.OperandType; +import ghidra.program.model.lang.Register; +import ghidra.program.model.lang.RegisterValue; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Instruction; +import ghidra.program.model.pcode.Varnode; import ghidra.program.model.symbol.RefType; +import ghidra.program.model.symbol.Reference; import ghidra.program.model.symbol.SourceType; -import ghidra.program.util.*; +import ghidra.program.util.ContextEvaluator; +import ghidra.program.util.ContextEvaluatorAdapter; +import ghidra.program.util.OperandFieldLocation; +import ghidra.program.util.SymbolicPropogator; +import ghidra.program.util.VarnodeContext; import ghidra.util.exception.CancelledException; -import ghidra.util.task.TaskMonitor; public class MultiInstructionMemReference extends GhidraScript { Address memReferenceLocation = null; private Address curInstrloc; - + private Object[] inputObjects; + private Object[] resultObjects; + private Register singleRegister; + private boolean registerInOut; + @Override public void run() throws Exception { long numInstructions = currentProgram.getListing().getNumInstructions(); monitor.initialize((int) (numInstructions)); monitor.setMessage("Multi-Instruction Reference Markup"); - int currentOpIndex = 0; + int currentOpIndex = -1; Address start = currentLocation.getAddress(); if ((currentSelection == null || currentSelection.isEmpty()) && currentLocation instanceof OperandFieldLocation) { - currentOpIndex = ((OperandFieldLocation) currentLocation).getOperandIndex(); + OperandFieldLocation operandLocation = (OperandFieldLocation) currentLocation; + currentOpIndex = operandLocation.getOperandIndex(); + int subOpIndex = operandLocation.getSubOperandIndex(); + singleRegister = getRegister(start, currentOpIndex, subOpIndex); } // set up the address set to restrict processing @@ -81,6 +103,34 @@ public class MultiInstructionMemReference extends GhidraScript { findMemRefAtOperand(currentOpIndex, refLocationsSet); } + /** + * Get the register at the location + * + * @param opIndex index into operands for instruction + * @param subOpIndex index into operands for an operand location + * + * @return register if there is one at the location + */ + private Register getRegister(Address addr, int opIndex, int subOpIndex) { + if (addr == null) { + return null; + } + + Instruction instr = currentProgram.getListing().getInstructionContaining(addr); + if (instr == null) { + return null; + } + + List defOpRep = instr.getDefaultOperandRepresentationList(opIndex); + if (subOpIndex >= 0 && subOpIndex < defOpRep.size()) { + Object obj = defOpRep.get(subOpIndex); + if (obj instanceof Register) { + return (Register) obj; + } + } + return instr.getRegister(opIndex); + } + @SuppressWarnings("unused") private boolean isSingleInstructions(AddressSet restrictedSet) { if (restrictedSet.isEmpty()) { @@ -106,53 +156,154 @@ public class MultiInstructionMemReference extends GhidraScript { // use context to fill out addresses on certain instructions ContextEvaluator eval = new ContextEvaluatorAdapter() { + @Override + public boolean evaluateContextBefore(VarnodeContext context, Instruction instr) { + // if the requested reference was on an input op-object, get context before exec + return checkContext(true, opIndex, context, instr); + } + @Override public boolean evaluateContext(VarnodeContext context, Instruction instr) { - // TODO: could look at instructions like LEA, that are an address to create a reference to something. + // if the requested reference was on an output op-object, get context after exec + return checkContext(false, opIndex, context, instr); + } + + + private boolean checkContext(boolean input, final int opIndex, VarnodeContext context, Instruction instr) { if (instr.getMinAddress().equals(curInstrloc)) { - if (checkInstructionMatch(opIndex, context, instr)) { + if (checkInstructionMatch(opIndex, input, context, instr)) { return true; } // if instruction is in delayslot, assume reference is good. if (instr.getDelaySlotDepth() > 0) { instr = instr.getNext(); - return checkInstructionMatch(opIndex, context, instr); + return checkInstructionMatch(opIndex, input, context, instr); } } return false; } + - private boolean checkInstructionMatch(final int opIdx, VarnodeContext context, + @Override + public boolean evaluateReference(VarnodeContext context, Instruction instr, int pcodeop, Address address, + int size, RefType refType) { + + return super.evaluateReference(context, instr, pcodeop, address, size, refType); + } + + + private boolean checkInstructionMatch(final int opIdx, boolean input, VarnodeContext context, Instruction instr) { - int firstIndex = opIdx; - if (instr.getRegister(firstIndex) == null) { - firstIndex = 0; - } - for (int index = firstIndex; index < instr.getNumOperands(); index++) { - Object[] opObjects = instr.getOpObjects(index); - for (int indexOpObj = 0; indexOpObj < opObjects.length; indexOpObj++) { - if (!(opObjects[indexOpObj] instanceof Register)) { - continue; + List list = Arrays.asList(input ? inputObjects : resultObjects); + + for (int index = opIdx; index < instr.getNumOperands(); index++) + { + if (getRefsForOperand(context, instr, list, index)) { + // register is both an in/out check if symbolic on out + if (registerInOut) { + break; } - Register reg = (Register) opObjects[indexOpObj]; - RegisterValue rval = context.getRegisterValue(reg); - if (rval == null) { - continue; - } - BigInteger uval = rval.getUnsignedValue(); - if (uval == null) { - continue; - } - long offset = uval.longValue(); - AddressSpace space = instr.getMinAddress().getAddressSpace(); - Address addr = space.getTruncatedAddress(offset, true); - - // assume that they want the reference, don't worry it isn't in memory - makeReference(instr, index, addr, monitor); - return false; - + return true; } } + if (addSymbolicRefs(input, context, instr, list)) { + return true; + } + return false; + } + + + /** + * Check the current operand for references based on input/outputs + * + * @param context - context holding values + * @param instr - instruction under consideration + * @param list - input/output lists + * @param opIndex - index of operand to check + * + * @return true if a reference was found + */ + private boolean getRefsForOperand(VarnodeContext context, Instruction instr, List list, int opIndex) { + Object[] opObjects = instr.getOpObjects(opIndex); + for (int indexOpObj = 0; indexOpObj < opObjects.length; indexOpObj++) { + if (!(opObjects[indexOpObj] instanceof Register)) { + continue; + } + Register reg = (Register) opObjects[indexOpObj]; + + // if operand has a single register and this isn't it + if (singleRegister != null && !reg.equals(singleRegister)) { + continue; + } + + // check that the register is on the correct input/output list + if (!list.contains(reg)) { + continue; + } + RegisterValue rval = context.getRegisterValue(reg); + if (rval == null) { + continue; + } + BigInteger uval = rval.getUnsignedValue(); + if (uval == null) { + continue; + } + long offset = uval.longValue(); + + AddressSpace space = instr.getMinAddress().getAddressSpace(); + Address addr = space.getTruncatedAddress(offset, true); + + // assume that they want the reference, don't worry it isn't in memory + makeReference(instr, opIndex, addr); + return true; + } + return false; + } + + private boolean addSymbolicRefs(boolean input, VarnodeContext context, Instruction instr, List list) { + // get the value of the single register to see if this is the value desired + if (singleRegister == null) { + return false; + } + // check that the register is on the correct input/output list + if (!list.contains(singleRegister)) { + return false; + } + Varnode registerVarnodeValue = context.getRegisterVarnodeValue(singleRegister); + if (!context.isSymbol(registerVarnodeValue) && !registerVarnodeValue.isRegister()) { + return false; + } + Address symAddr = registerVarnodeValue.getAddress(); + if (symAddr == context.BAD_ADDRESS) { + return false; + } + + String valStr = ""; + if (registerVarnodeValue.isRegister()) { + valStr = context.getRegister(registerVarnodeValue).toString(); + } else { + // is an offset from a space + String name = symAddr.getAddressSpace().getName(); + BigInteger offset = symAddr.getOffsetAsBigInteger(); + valStr = name + " + 0x" + offset.toString(16); + } + Address lastSetLocation = context.getLastSetLocation(singleRegister, null); + + + String comment = instr.getComment(Instruction.EOL_COMMENT); + if (comment == null) { + comment = ""; + } + + String inoutChar = (input ? " " : "\'"); + String lastStr = (lastSetLocation != null ? " @" + lastSetLocation : ""); + + String markup = singleRegister+inoutChar+"= "+ valStr + lastStr; + if (comment.replace('\'',' ').contains(markup.replace('\'',' '))) { + return false; + } + comment = (comment.trim().length()==0 ? markup : comment + "\n" + markup); + instr.setComment(Instruction.EOL_COMMENT, comment); return false; } @@ -188,8 +339,14 @@ public class MultiInstructionMemReference extends GhidraScript { } } - // if the instruction attempting to markup is in the delayslot, backup an instruction Instruction instr = currentProgram.getListing().getInstructionAt(curInstrloc); + if (instr != null) { + inputObjects = instr.getInputObjects(); + resultObjects = instr.getResultObjects(); + registerInOut = checkRegisterInOut(singleRegister, inputObjects, resultObjects); + } + + // if the instruction attempting to markup is in the delayslot, backup an instruction if (instr != null && instr.isInDelaySlot()) { instr = instr.getPrevious(); if (instr != null) { @@ -209,16 +366,24 @@ public class MultiInstructionMemReference extends GhidraScript { } } - /** - * @param instruction - * @param space - * @param scalar - * @param nextInstr - * @param addend - * @param taskMonitor + private boolean checkRegisterInOut(Register reg, Object[] in, Object[] out) { + if (reg == null || in == null || out == null) { + return false; + } + + List inList = Arrays.asList(in); + List outList = Arrays.asList(out); + + return inList.contains(reg) && outList.contains(reg); + } + + /** Make the reference on the instruction at the correct location. + * + * @param instruction to receive reference + * @param space reference created in this space + * @param scalar used as offset into address space */ - private void makeReference(Instruction instruction, int opIndex, Address addr, - TaskMonitor taskMonitor) { + private void makeReference(Instruction instruction, int opIndex, Address addr) { if (instruction.getPrototype().hasDelaySlots()) { instruction = instruction.getNext(); if (instruction == null) { @@ -238,6 +403,13 @@ public class MultiInstructionMemReference extends GhidraScript { if (opIndex == -1) { opIndex = instruction.getNumOperands() - 1; } + + // check if it already has the reference + Reference[] referencesFrom = instruction.getReferencesFrom(); + boolean hasRef = Arrays.stream(referencesFrom).anyMatch(p -> p.getToAddress().equals(addr)); + if (hasRef) { + return; + } if (opIndex == -1) { instruction.addMnemonicReference(addr, RefType.DATA, SourceType.ANALYSIS); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/ConstantPropagationAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/ConstantPropagationAnalyzer.java index 88206fca8d..b8368f46ad 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/ConstantPropagationAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/ConstantPropagationAnalyzer.java @@ -67,10 +67,16 @@ public class ConstantPropagationAnalyzer extends AbstractAnalyzer { protected static final int MINKNOWNREFADDRESS_OPTION_DEFAULT_VALUE = 4; protected static final String MINSPECULATIVEREFADDRESS_OPTION_NAME = - "Min speculative reference"; + "Speculative reference min"; protected static final String MINSPECULATIVEREFADDRESS_OPTION_DESCRIPTION = "Minimum speculative reference address for offsets and parameters"; protected static final int MINSPECULATIVEREFADDRESS_OPTION_DEFAULT_VALUE = 1024; + + protected static final String MAXSPECULATIVEREFADDRESS_OPTION_NAME = + "Speculative reference max"; + protected static final String MAXSPECULATIVEREFADDRESS_OPTION_DESCRIPTION = + "Maxmimum speculative reference address offset from the end of memory for offsets and parameters"; + protected static final int MAXSPECULATIVEREFADDRESS_OPTION_DEFAULT_VALUE = 256; protected final static int NOTIFICATION_INTERVAL = 100; @@ -80,6 +86,7 @@ public class ConstantPropagationAnalyzer extends AbstractAnalyzer { protected int maxThreadCount = MAXTHREADCOUNT_OPTION_DEFAULT_VALUE; protected long minStoreLoadRefAddress = MINKNOWNREFADDRESS_OPTION_DEFAULT_VALUE; protected long minSpeculativeRefAddress = MINSPECULATIVEREFADDRESS_OPTION_DEFAULT_VALUE; + protected long maxSpeculativeRefAddress = MAXSPECULATIVEREFADDRESS_OPTION_DEFAULT_VALUE; protected boolean followConditional = false; @@ -391,7 +398,7 @@ public class ConstantPropagationAnalyzer extends AbstractAnalyzer { throws CancelledException { ContextEvaluator eval = new ConstantPropagationContextEvaluator(trustWriteMemOption, - minStoreLoadRefAddress, minSpeculativeRefAddress); + minStoreLoadRefAddress, minSpeculativeRefAddress, maxSpeculativeRefAddress); return symEval.flowConstants(flowStart, flowSet, eval, true, monitor); } @@ -461,9 +468,13 @@ public class ConstantPropagationAnalyzer extends AbstractAnalyzer { MINKNOWNREFADDRESS_OPTION_DESCRIPTION); long size = program.getAddressFactory().getDefaultAddressSpace().getSize(); - minSpeculativeRefAddress = size * 8; + minSpeculativeRefAddress = size * 16; options.registerOption(MINSPECULATIVEREFADDRESS_OPTION_NAME, minSpeculativeRefAddress, null, MINSPECULATIVEREFADDRESS_OPTION_DESCRIPTION); + + maxSpeculativeRefAddress = size * 8; + options.registerOption(MAXSPECULATIVEREFADDRESS_OPTION_NAME, maxSpeculativeRefAddress, null, + MAXSPECULATIVEREFADDRESS_OPTION_DESCRIPTION); } @Override @@ -479,6 +490,8 @@ public class ConstantPropagationAnalyzer extends AbstractAnalyzer { options.getLong(MINKNOWNREFADDRESS_OPTION_NAME, minStoreLoadRefAddress); minSpeculativeRefAddress = options.getLong(MINSPECULATIVEREFADDRESS_OPTION_NAME, minSpeculativeRefAddress); + maxSpeculativeRefAddress = + options.getLong(MAXSPECULATIVEREFADDRESS_OPTION_NAME, maxSpeculativeRefAddress); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/ConstantPropagationContextEvaluator.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/ConstantPropagationContextEvaluator.java index 08e4ac1315..acb8c08048 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/ConstantPropagationContextEvaluator.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/ConstantPropagationContextEvaluator.java @@ -42,7 +42,9 @@ public class ConstantPropagationContextEvaluator extends ContextEvaluatorAdapter protected AddressSet destSet = new AddressSet(); private boolean trustMemoryWrite = false; private long minStoreLoadOffset = 4; - private long minSpeculativeOffset = 1024; + private long minSpeculativeOffset = 1024; // from the beginning of memory + private long maxSpeculativeOffset = 256; // from the end of memory + public ConstantPropagationContextEvaluator() { } @@ -55,10 +57,10 @@ public class ConstantPropagationContextEvaluator extends ContextEvaluatorAdapter } public ConstantPropagationContextEvaluator(boolean trustWriteMemOption, - long minStoreLoadRefAddress, long minSpeculativeRefAddress) { + long minStoreLoadRefAddress, long minSpeculativeRefAddress, long maxSpeculativeRefAddress) { this(trustWriteMemOption); this.minStoreLoadOffset = minStoreLoadRefAddress; - this.minSpeculativeOffset = minSpeculativeRefAddress; + this.maxSpeculativeOffset = maxSpeculativeRefAddress; } /** @@ -85,7 +87,7 @@ public class ConstantPropagationContextEvaluator extends ContextEvaluatorAdapter long wordOffset = constant.getOffset(); if (((wordOffset >= 0 && wordOffset < minSpeculativeOffset) || - (Math.abs(maxAddrOffset - wordOffset) < minSpeculativeOffset)) && + (Math.abs(maxAddrOffset - wordOffset) < maxSpeculativeOffset)) && !space.isExternalSpace()) { return null; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/util/SymbolicPropogator.java b/Ghidra/Features/Base/src/main/java/ghidra/program/util/SymbolicPropogator.java index 5486eb65ac..11b468a459 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/program/util/SymbolicPropogator.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/program/util/SymbolicPropogator.java @@ -43,7 +43,7 @@ public class SymbolicPropogator { // 1. How are "register-relative" varnodes distinguished based upon target space ? Not sure how we handle wrapping/truncation concerns. // 1) The offset is the only thing that could be used as a reference. - private static final int _POINTER_MIN_BOUNDS = 0x7fff; + private static final int _POINTER_MIN_BOUNDS = 0x100; // mask for sub-piece extraction private static long[] maskSize = { 0xffL, 0xffL, 0xffffL, 0xffffffL, 0xffffffffL, 0xffffffffffL, @@ -1836,7 +1836,7 @@ public class SymbolicPropogator { // see if the offset is a large constant offset from the symbolic space long offset = refLocation.getOffset(); - if (checkPossibleOffsetAddr(offset)) { + if (evaluator != null) { // symbolic spaces will have the name of the symbolic space be the register space // String spaceName = refLocation.getAddress().getAddressSpace().getName(); // Register register = vContext.getRegister(spaceName); @@ -1850,7 +1850,7 @@ public class SymbolicPropogator { // } // } else - if (evaluator == null) { + if (!vContext.isStackSymbolicSpace(refLocation) && evaluator != null) { Address constant = program.getAddressFactory().getAddress( (int) targetSpaceID.getOffset(), offset); Address newTarget = evaluator.evaluateConstant(vContext, instruction, @@ -2051,7 +2051,7 @@ public class SymbolicPropogator { */ private int getReferenceSpaceID(Instruction instruction, long offset) { // TODO: this should be passed to the client callback to make the decision - if (offset <= 4096 && offset >= -1) { + if (offset <= 4 && offset >= -1) { return -1; // don't make speculative reference to certain offset values } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/util/VarnodeContext.java b/Ghidra/Features/Base/src/main/java/ghidra/program/util/VarnodeContext.java index 6266f9d77c..8be976f8d9 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/program/util/VarnodeContext.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/program/util/VarnodeContext.java @@ -312,7 +312,7 @@ public class VarnodeContext implements ProcessorContext { /** * Return true if this varnode is stored in the symbolic stack space */ - private boolean isStackSymbolicSpace(Varnode varnode) { + public boolean isStackSymbolicSpace(Varnode varnode) { // symbolic spaces are off of a register, find the space AddressSpace regSpace = addrFactory.getAddressSpace(varnode.getSpace()); @@ -785,7 +785,9 @@ public class VarnodeContext implements ProcessorContext { * return the location that this register was last set * This is a transient thing, so it should only be used as a particular flow is being processed... * - * @param reg + * @param reg register to find last set location + * @param bval value to look for to differentiate set locations, null if don't care + * * @return address that the register was set. */ public Address getLastSetLocation(Register reg, BigInteger bval) { @@ -1256,6 +1258,13 @@ public class VarnodeContext implements ProcessorContext { // too big anyway,already extended as far as it will go. vnodeVal = createConstantVarnode(vnodeVal.getOffset(), out.getSize()); } + } else if (vnodeVal.isRegister() && vnodeVal.getSize() < out.getSize()) { + Register reg = getRegister(vnodeVal); + if (reg == null) { + throw notFoundExc; + } + int spaceID = getAddressSpace(reg.getName()); + vnodeVal = createVarnode(0,spaceID,out.getSize()); } return vnodeVal; }