def barfOperandToExpr(self, op): """ Translate a barf operand into an expression (Expr from Expr.py) Parameters : op - (ReilOperand) should be either an immediate or a register """ if (isinstance(op, ReilImmediateOperand)): res = ConstExpr(op._immediate, op.size) return res elif (isinstance(op, ReilRegisterOperand)): if (op._name[0] == "t"): res = self.valuesTable[op._name] if (op.size == res.size): return res else: return Convert(op.size, res) else: fullExpr = self._getReg(op._name) if (op.size == REGSIZE.size): return fullExpr else: (reg, offset) = barfGetAlias(op._name) if (reg == "rflags" or reg == "eflags"): return Convert(op.size, fullExpr) else: return Extr(op.size + offset - 1, offset, fullExpr) else: raise GadgetException("Operand %s neither register nor immediate", str(op))
def getMemDependencies( self, gadgetDep ): """ Extract dependencies from memory Dependencies of the memory are returned in the form of a dictionnary of lists of couples - Dictionnary entries are addresses where the memory is accessed ( like R7_0 + R6_0 + 0x56 ) - Dictionnary values are lists that are classical dependencies. This means lists of couples [value, condition] Parameters: (gadgetDep) (GadgetDependencies) The structure where to store the memory dependencies """ #raise GraphException("Function getMemDependencies() not yet implemented, sorry !") # The three dictionnaries below MUST have the exact same keys res = dict() # Key: Expr (address of store), Value: list of dependencies previousStoreSizes = dict() # Key: Expr (address of store), Value: size of the store tmpCond = None # We go through memory-writes in chronological order for a in sorted(self.outgoingArcs): # Key to manipulate the current store address addrKey = str(simplify(a.label.toZ3())) # We get the dependency for the node that is written in memory dep = a.dest.getDependencies(gadgetDep) # !!! HERE WE DON'T NEED TO CORRECT THE DEPENDENCIES WITH THE self.storedValues BECAUSE IT HAS ALREADY BEEN DONE FOR REGISTERS AND WE USE THEIR FINAL DEPENDENCIES DIRECTLY if( isinstance(a.dest, SSANode)): dep = [[self.storedValues[a.num].replaceReg(a.dest.reg, d[0]), d[1]] for d in dep ] # Handling conditionnal jumps if( tmpCond is None ): # If first memory access, then we get the condPath for the adequate level (condition that must be true so that we haven't jumper out from the gadget) tmpCond = CurrentAnalysis.graph.condPath[a.jmpLvl] else: # Else we only add the condJump corresponding to this level tmpCond = Cond( CT.AND, tmpCond, CurrentAnalysis.graph.condJmps[a.jmpLvl].invert() ) storeLen = a.size / 8 # Number of bytes written by this arc previousStoreSizes[a.label] = storeLen #readDict = {addrKey:0} # Basic constraint for optimisation # Create a new dependency for this # Updating dependencies for the previous memory-store resTmp = dict() for writeAddr, prevDep in res.iteritems(): # 1st Case Preparation : New store doesn't affect old store # Get the size of the previous by looking at one dependency (little hack and not so clean but heh) previousStoreSize = previousStoreSizes[writeAddr] higher = Op( "Add", [writeAddr, ConstExpr(previousStoreSize, writeAddr.size)] ) lower = Op( "Sub", [writeAddr, ConstExpr( a.size, writeAddr.size )]) outCond = Cond( CT.OR, Cond( CT.GE, a.label, higher ), Cond( CT.LE, a.label, lower )) newDict = {addrKey:[1-storeLen, previousStoreSize-1]} newDep = [] # Update each old dependency for prev in prevDep: # 1st Case Still... if( self.compatibleAccesses( prev[2], newDict )): newDep.append( [prev[0], Cond( CT.AND, outCond, prev[1]), self.dictFusion(newDict, prev[2])]) # Now 2d Case: New store overwrites old store # ... # Offset = current store - previous store for offset in range(1-storeLen, previousStoreSize-1): newDict = {addrKey:offset} newStorePossibleValue = Op("Add", [writeAddr, ConstExpr(offset, writeAddr.size)] ) addrCond = Cond( CT.EQUAL, a.label, newStorePossibleValue ) if( self.compatibleAccesses( newDict, prev[2] ) and self.filterOffset( offset, previousStoreSize, storeLen )): # For each dep of the new store, we update with the case where it overwrites the previous stores for d in dep: newValue = self.overWrite( offset, d[0], prev[0] ) newCond = Cond( CT.AND, Cond(CT.AND, prev[1], addrCond ), d[1] ) newDep.append( [newValue, newCond, self.dictFusion(newDict, prev[2])]) resTmp[writeAddr] = newDep # New dependency: resTmp[a.label] = [] newStoreDict={addrKey:0} for d in dep: resTmp[a.label].append([d[0], d[1], newStoreDict]) # Save everything res = resTmp # ... # Clean the deps from dictionnaries for addr, dep in res.iteritems(): dep = [[].append([d[0], d[1]]) for d in dep ] # Replace the memory addresses of the stores by their basic dependencies # So far we have MEM[expr] where expr doesn't necessarily have basic dependencies, so we want to express everything with the basic dependencies gadgetDep.memDep = res
def getDependencies( self, num, addr, size, gadgetDep ): """ Extract dependencies from memory Parameters: (num) (int) The number of the arc in the graph that represents the load operation (addr) (Expr) The address at which memory is read (size) (int) The size in bits of the read (gadgetDep) (GadgetDependencies) The structure used to compute dependencies """ res = [] tmp = num readLen = size / 8 addrID = -1 # Used to optimise combinations without calling the solver... addrKey = str(simplify(addr.toZ3())) readDict = {addrKey:0} # Basic constraint for optimisation value = MEMExpr( addr, size ) resTmp = [[MEMExpr(addr, size), CTrue(), readDict]] tmpCond = None # For each outgoing arc, i.e for each memory write for a in sorted( self.outgoingArcs, reverse=False): optWriteKey = str(simplify(a.label.toZ3())) # key for the store address for optimisation # If a.num > num, then we have checked all memory writes before the read we compute dependencies for if( a.num >= num ): break # Get the dependencies for the register written in memory dep = a.dest.getDependencies( gadgetDep ) # We correct the dependencies in case we stored an expression and not only a register or constant if( isinstance(a.dest, SSANode)): dep = [[self.storedValues[a.num].replaceReg(a.dest.reg, d[0]), d[1]] for d in dep ] # Handle previous conditional jumps if( tmpCond is None ): # If first memory access, then we get the condPath for the adequate level (condition that must be true so that we haven't jumper out from the gadget) tmpCond = CurrentAnalysis.graph.condPath[a.jmpLvl] else: # Else we only add the condJump corresponding to this level tmpCond = Cond( CT.AND, tmpCond, CurrentAnalysis.graph.condJmps[a.jmpLvl].invert() ) storeLen = a.size / 8 # Number of bytes written by this arc # For each dependency, we update the resTmp into some new array of dependencies # This models a store operation overwriting previous memory contents newResTmp = [] # For each case, update possibilities for prev in resTmp: # For each dependency of the register written in memory for d in dep: # We consider every possibility of memory overwrite ( offset between the read and write ). Some could modify 1 byte, others 2 bytes, etc... for offset in range( 1-storeLen, readLen-1 ): writeAddr = Op( "Add", [addr, ConstExpr( offset, addr.size )]) newDict = {optWriteKey:offset} if( self.compatibleAccesses( prev[2], newDict ) and self.filterOffset( offset, storeLen, readLen )): newValue = self.overWrite( offset, d[0], prev[0] ) addrCond = Cond( CT.EQUAL, a.label, writeAddr ) newCond = Cond( CT.AND, Cond(CT.AND, prev[1], addrCond ), d[1] ) newResTmp.append( [newValue, newCond, self.dictFusion(newDict, prev[2])]) # We keep the previous ones if the store is made out of the bounds of the read higher = Op( "Add", [addr, ConstExpr(readLen, addr.size)] ) lower = Op( "Sub", [addr, ConstExpr( storeLen, addr.size )]) outCond = Cond( CT.OR, Cond( CT.GE, a.label, higher ), Cond( CT.LE, a.label, lower )) newDict = {optWriteKey:[1-storeLen, readLen-1]} for prev in resTmp: if( self.compatibleAccesses( prev[2], newDict )): newResTmp.append( [prev[0], Cond( CT.AND, outCond, prev[1]), self.dictFusion(newDict, prev[2])] ) # give resTmp its new value resTmp = newResTmp # Extract only the values and conditions res = [[d[0], d[1]] for d in resTmp] return res
def getDependencies( self, gadgetDep ): return [[ConstExpr( self.value, self.size ),CTrue()]]
def buildGraph(self, irsb): # (1) Initialisations... self.valuesTable = {} # Keys are whatever, Values are Expr self.graph = Graph() # Empty graph CurrentAnalysis.gadget = self CurrentAnalysis.graph = self.graph self.regCount = {} # Update the index counts and nodes for registers that already have a translation into generic IR for reg in Analysis.regNamesTable.values(): self.regCount[reg] = 0 node = SSANode(SSAReg(reg, 0), SSAExpr(SSAReg(reg, 0))) self.graph.nodes["R%d_0" % reg] = node self.graph.nodes["MEM"] = MEMNode() memAccCount = 0 # Hold the number of the next arc incoming/outgoing to/from the memory node # (2) Graph construction... # Iterate through all reill instructions : for i in range(0, len(irsb)): instr = irsb[i] self.nbInstr = self.nbInstr + 1 # Translating different types of instructions if (instr.mnemonic == ReilMnemonic.NOP): pass # Basic operations ( ADD, SUB, MUL, etc ) elif (isCalculationInstr(instr.mnemonic)): if (instr.operands[2]._name[0] == "t"): expr = self.barfCalculationToExpr(instr.mnemonic, instr.operands[0], instr.operands[1], instr.operands[2].size) self.valuesTable[instr.operands[2]._name] = expr else: expr = self.barfCalculationToExpr(instr.mnemonic, instr.operands[0], instr.operands[1], instr.operands[2].size) regExpr = self._getReg(instr.operands[2]._name) reg = regExpr.reg expr = self.translateFullRegAssignement( expr, instr.operands[2]) reg = SSAReg(reg.num, reg.ind + 1) self.valuesTable[str(reg)] = expr # Adding the node node = SSANode(reg, expr) # Adding arcs toward other nodes and memory for dep in expr.getRegisters(): # Dep is a SSAReg on which node depends node.outgoingArcs.append( Arc(self.graph.nodes[str(dep)])) for mem in expr.getMemAcc(): addr = mem[0] size = mem[1] node.outgoingArcs.append( Arc(self.graph.nodes["MEM"], memAccCount, addr, size)) memAccCount += 1 self.graph.nodes[str(reg)] = node self.regCount[reg.num] += 1 # Memory load instruction elif (isLoadInstr(instr.mnemonic)): expr = self.barfLoadToExpr(instr.operands[0], instr.operands[2]) if (instr.operands[2]._name[0] == "t"): self.valuesTable[instr.operands[2]._name] = expr else: regExpr = self._getReg(instr.operands[2]._name) reg = regExpr.reg expr = self.translateFullRegAssignement( expr, instr.operands[2]) reg.ind += 1 self.regCount[reg.num] += 1 self.valuesTable[str(reg)] = expr # Adding node towards memory node = SSANode(reg, expr) for mem in expr.getMemAcc(): addr = mem[0] size = mem[1] node.outgoingArcs.append( Arc(self.graph.nodes["MEM"], memAccCount, addr, size)) memAccCount += 1 self.graph.nodes[str(reg)] = node # Memory store instruction elif (isStoreInstr(instr.mnemonic)): expr = self.barfOperandToExpr(instr.operands[0]) addr = self.barfOperandToExpr(instr.operands[2]) #if( isinstance( instr.operands[0], ReilImmediateOperand )): #node = ConstNode( instr.operands[0].immediate, instr.operands[0].size ) #self.graph.nodes["MEM"].outgoingArcs.append( Arc( node, memAccCount, addr, expr.size )) if (isinstance(expr, ConstExpr)): node = ConstNode(expr.value, expr.size) self.graph.nodes["MEM"].outgoingArcs.append( Arc(node, memAccCount, addr, expr.size)) elif (not expr.getRegisters()): raise GadgetException( "Expression is neither ConstExpr nor has registers and should be written in memory ? - not yet supported!" ) else: self.graph.nodes["MEM"].outgoingArcs.append( Arc(self.graph.nodes[str(expr.getRegisters()[0])], memAccCount, addr, expr.size)) self.graph.nodes["MEM"].storedValues[memAccCount] = expr memAccCount += 1 # Transfer value into register elif (isPutInstr(instr.mnemonic)): if (instr.operands[2]._name[0] == "t"): expr = self.barfOperandToExpr(instr.operands[0]) if (instr.operands[2].size != expr.size): expr = Convert(instr.operands[2].size, expr) self.valuesTable[instr.operands[2]._name] = expr else: regExpr = self._getReg(instr.operands[2]._name) reg = regExpr.reg expr = self.barfOperandToExpr(instr.operands[0]) if (instr.operands[0].size < instr.operands[2].size): expr = self.translateFullRegAssignement( expr, instr.operands[2]) else: expr = Convert(instr.operands[2].size, expr) if (instr.operands[2].size != REGSIZE.size): expr = self.translateFullRegAssignement( expr, instr.operands[2]) regDep = expr.getRegisters() memDep = expr.getMemAcc() # Adding node and arcs to the graph reg = SSAReg(reg.num, reg.ind + 1) node = SSANode(reg, expr) for r in regDep: node.outgoingArcs.append(Arc(self.graph.nodes[str(r)])) for mem in memDep: addr = mem[0] size = mem[1] node.outgoingArcs.append( Arc(self.graph.nodes["MEM"], memAccCount, addr, size)) memAccCount += 1 self.graph.nodes[str(reg)] = node self.regCount[reg.num] += 1 self.valuesTable[str(reg)] = expr # Boolean IS Zero instrution # BISZ( a, b ) has the following effects : # b <- 1 if a == 0 # b <- 0 if a != 0 elif (instr.mnemonic == ReilMnemonic.BISZ): zero = ConstExpr(0, instr.operands[0].size) ifzero = ConstExpr(1, instr.operands[2].size) ifnotzero = ConstExpr(0, instr.operands[2].size) testedValue = self.barfOperandToExpr(instr.operands[0]) # If operands[0] == 0 then the value assigned to operands[2] is 1. # If operands[0] != 0 then operands[2] becomes 0 cond = Cond(CT.EQUAL, testedValue, zero) expr = ITE(cond, ifzero, ifnotzero) if (instr.operands[2]._name[0] == "t"): self.valuesTable[instr.operands[2]._name] = expr else: regExpr = self._getReg(instr.operands[2]._name) reg = regExpr.reg reg.ind += 1 self.regCount[reg.num] += 1 self.valuesTable[str(reg)] = expr # Adding node to the graph # Creation of the ite node iteNode = ITENode(cond, ifzero, ifnotzero) # Link the real node to the ITE node node = SSANode(reg, Convert(REGSIZE.size, expr)) node.outgoingArcs.append(Arc(iteNode)) self.graph.nodes[str(reg)] = node # Conditionnal jump elif (instr.mnemonic == ReilMnemonic.JCC): # Determine the address where to jmp # If jmp to a fixed location if (isinstance(instr.operands[2], ReilImmediateOperand) and instr.operands[2].size != 40 and instr.operands[2].size != 72): addr = ConstExpr(instr.operands[2].immediate) addr.size = REGSIZE.size # If it is a pointer elif (instr.operands[2].size == 40 or instr.operands[2].size == 72): #We test if the value is less than 0x100 # If yes, then we ignore the condition because unable to compute dependencies # This kind of small values depict an instruction simulated in many basic blocks # We do not handle conditions for the Instr Pointer INSIDE a gadget if (isinstance(instr.operands[2], ReilImmediateOperand) and (instr.operands[2].immediate >> 8) - self.addr < 0x1): raise GadgetException( "REIL instruction(s) too complicated to emulate in gadget:\n" + self.asmStr) # We also test if there is a jump inside the bounds of the gadget # For now those are also not supported # Sadly, this also suppresses some conditional conditions if (isinstance(instr.operands[2], ReilImmediateOperand) and (instr.operands[2].immediate >> 8) - self.addr < (len(self.hexStr) / 4)): raise GadgetException( "Jumps inside the gadget are not handled yet in gadget:\n" + self.asmStr) # Get the real return/jump address with an 8 bit SHR expr = self.barfOperandToExpr(instr.operands[2]) addr = Extr(expr.size - 1, 8, expr) else: addr = self.barfOperandToExpr(instr.operands[2]) ip = self._getReg( Analysis.ArchInfo.ip).reg # Get the instruction pointer ip.ind += 1 # Quick check if simple 'ret' so the expression is easier to read # A jump is always taken if first argument is a constant != 0 if (isinstance(instr.operands[0], ReilImmediateOperand) and instr.operands[0].immediate != 0): self.valuesTable[str(ip)] = addr expr = addr node = SSANode(ip, Convert(REGSIZE.size, addr)) for dep in addr.getRegisters(ignoreMemAcc=True): node.outgoingArcs.append( Arc(self.graph.nodes[str(dep)])) for mem in addr.getMemAcc(): address = mem[0] size = mem[1] node.outgoingArcs.append( Arc(self.graph.nodes["MEM"], memAccCount, address, size)) memAccCount += 1 self.regCount[ip.num] += 1 self.graph.nodes[str(ip)] = node # Else processing conditional jump else: # Create node and stuff reg = ip zero = ConstExpr(0, instr.operands[0].size) cond = Cond(CT.NOTEQUAL, self.barfOperandToExpr(instr.operands[0]), zero) # Update the info about conditional jumps in Graph.py module self.graph.condJmps.append(cond) self.graph.condPath.append( Cond(CT.AND, cond.invert(), self.graph.condPath[self.graph.countCondJmps])) # TODO : this seems not to put the good address into nextInstrAddr ??? nextInstrAddr = ConstExpr(irsb[i + 1].address >> 8, REGSIZE.size) # 1 - FIRST STEP : ITE Node to say : IP takes new value OR stays the same expr = ITE(cond, addr, ConstExpr(instr.address >> 8, REGSIZE.size)) self.valuesTable[str(reg)] = expr # Adding node to the graph # Creation of the ite node # We consider that either the jump is taken ( to addr ) # or the value stays the one of the current instruction ( instr.address >> 8 ) iteNode = ITENode( cond, addr, ConstExpr(instr.address >> 8, REGSIZE.size)) for dep in addr.getRegisters(): iteNode.outgoingArcs.append( Arc(self.graph.nodes[str(dep)])) for mem in addr.getMemAcc(): address = mem[0] size = mem[1] iteNode.outgoingArcs.append( Arc(self.graph.nodes["MEM"], memAccCount, address, size)) memAccCount += 1 #iteNode.outgoingArcsCond.append( Arc( self.graph.nodes[str(prevOcc(reg))], -1, None)) # Link the real node to the ITE node node = SSANode(reg, expr) node.outgoingArcs.append(Arc(iteNode)) self.graph.nodes[str(reg)] = node # And do not forget self.regCount[reg.num] += 1 self.graph.countCondJmps += 1 # 2 - SECOND STEP # We update the register again with the address of the next instruction reg = SSAReg(reg.num, reg.ind + 1) node = SSANode(reg, nextInstrAddr) self.graph.nodes[str(reg)] = node self.regCount[reg.num] += 1 else: raise GadgetException( "REIL operation <%s> not supported in gadget:\n%s" % (ReilMnemonic.to_string(instr.mnemonic), self.asmStr))