def loadConfig(self, config): """ Receives a dictionary of parameters: - modules: a list of either module IDs, module name or 2-tuples with module name and version Default: None (MANDATORY PARAMETER) - badchars: a list of characters not allowed in the generated ROP Default: "" - roparea: A 2-tuple with the initial address of the user controlled space where EIP points to, and the size of this area as a second element Default: (ESP, 0x1000) - stackpage: size in bytes of the current stack (stack is considered to be writable and some gadget could overwrite non-protected areas) Default: 0x1000 - memory: A list of 3-tuples with: Initial Address, Size and Permission (see self.setMemFlags) Initial address can be a numeric address, an expression's dump or a Expression instance. Default: None - initstate: A dictionary used to initialize the state of the ROP machine. Keys are regs/flags/mem address as used in a SequenceAnalyzer instance, values are dumped expressions (see Solver.dumpExpr) Memory addresses are expressed as expression dumps too (not MEM<CRC32>). This are values that are known to be always precise. Run-dependant values (like object addresses) should be avoided. Default: None - banned: A list of banned gadgets that we can't use. Each gadget is a 2-tuple with module_id and offset. - DB related parameters: - dbtype: sqlite3/mysql (Default: sqlite3) - dbname (Default: gadgets.sq3 if sqlite3 or gadgets) - username - passwd - host (Default: 127.0.0.1) """ args = { "dbtype": None, "dbname": None, "host": None, "username": "", "passwd": "" } for k in args.keys(): if config.has_key(k) and config[k] != None: args[k] = config[k] args["quiet"] = True self.gdb = GadgetsDB(None, **args) if config.has_key("modules"): mods = config["modules"] self.modules = self.gdb.get_module_ids(mods) self.bases = self.gdb.get_module_base_from_id(self.modules) self.hashes = HashesDictionary(self.gdb, self.modules) self.props = PropertiesDictionary(self.gdb, self.modules) if config.has_key("badchars"): self.badchars = config["badchars"] if config.has_key("stackpage"): self.stackpage = config["stackpage"] if config.has_key("banned"): self.bannedGadgets = config["banned"] if not self.setMemFlags(self.state.regs["ESP"], self.stackpage, "RW"): raise Exception, "There was some error setting the initial permissions for the stack page. Check your 'memory' configuration and the stack page size." if config.has_key("memory"): for mem in config["memory"]: self.setMemFlags(mem[0], mem[1], mem[2]) if config.has_key("roparea"): addr = config["roparea"][0] size = config["roparea"][1] else: addr = self.state.regs["ESP"] size = 0x1000 if not self.setMemFlags(addr, size, "RWA"): raise Exception, "ROP Area configuration is wrong" if config.has_key("initstate"): for k, v in config["initstate"].iteritems(): if k in self.state.regs.keys(): self.state.regs[k] = self.state.solver.loadExpr(v) elif k in self.state.flags.keys(): self.state.flags[k] = self.state.solver.loadExpr(v) else: self.state.memory[k] = self.state.solver.loadExpr(v) else: #set a default initial state suitable for ROP (emulate a RETN) EIP = self.readMemory(self.state.regs["ESP"], 4, "R") self.state.regs["ESP"] += 4 self.state.EIP = EIP #detect Branching Oriented Programming main mode #EIP is supposed to be pointing to user controlled space #If EIP is pointing to memory indexed by ESP, we use ROP, JOP otherwise. ptr = self.getEIPptr() if "ESP" in ptr.varsnames(): self.mode = "ROP" else: self.mode = "JOP"
def loadConfig(self, config): """ Receives a dictionary of parameters: - modules: a list of either module IDs, module name or 2-tuples with module name and version Default: None (MANDATORY PARAMETER) - badchars: a list of characters not allowed in the generated ROP Default: "" - roparea: A 2-tuple with the initial address of the user controlled space where EIP points to, and the size of this area as a second element Default: (ESP, 0x1000) - stackpage: size in bytes of the current stack (stack is considered to be writable and some gadget could overwrite non-protected areas) Default: 0x1000 - memory: A list of 3-tuples with: Initial Address, Size and Permission (see self.setMemFlags) Initial address can be a numeric address, an expression's dump or a Expression instance. Default: None - initstate: A dictionary used to initialize the state of the ROP machine. Keys are regs/flags/mem address as used in a SequenceAnalyzer instance, values are dumped expressions (see Solver.dumpExpr) Memory addresses are expressed as expression dumps too (not MEM<CRC32>). This are values that are known to be always precise. Run-dependant values (like object addresses) should be avoided. Default: None - banned: A list of banned gadgets that we can't use. Each gadget is a 2-tuple with module_id and offset. - DB related parameters: - dbtype: sqlite3/mysql (Default: sqlite3) - dbname (Default: gadgets.sq3 if sqlite3 or gadgets) - username - passwd - host (Default: 127.0.0.1) """ args={"dbtype":None, "dbname":None, "host":None, "username":"", "passwd":""} for k in args.keys(): if config.has_key(k) and config[k] != None: args[k]=config[k] args["quiet"]=True self.gdb = GadgetsDB(None, **args) if config.has_key("modules"): mods=config["modules"] self.modules = self.gdb.get_module_ids(mods) self.bases = self.gdb.get_module_base_from_id(self.modules) self.hashes = HashesDictionary(self.gdb, self.modules) self.props = PropertiesDictionary(self.gdb, self.modules) if config.has_key("badchars"): self.badchars = config["badchars"] if config.has_key("stackpage"): self.stackpage = config["stackpage"] if config.has_key("banned"): self.bannedGadgets = config["banned"] if not self.setMemFlags(self.state.regs["ESP"], self.stackpage, "RW"): raise Exception, "There was some error setting the initial permissions for the stack page. Check your 'memory' configuration and the stack page size." if config.has_key("memory"): for mem in config["memory"]: self.setMemFlags(mem[0], mem[1], mem[2]) if config.has_key("roparea"): addr=config["roparea"][0] size=config["roparea"][1] else: addr=self.state.regs["ESP"] size=0x1000 if not self.setMemFlags(addr, size, "RWA"): raise Exception, "ROP Area configuration is wrong" if config.has_key("initstate"): for k,v in config["initstate"].iteritems(): if k in self.state.regs.keys(): self.state.regs[k]=self.state.solver.loadExpr(v) elif k in self.state.flags.keys(): self.state.flags[k]=self.state.solver.loadExpr(v) else: self.state.memory[k]=self.state.solver.loadExpr(v) else: #set a default initial state suitable for ROP (emulate a RETN) EIP=self.readMemory(self.state.regs["ESP"], 4, "R") self.state.regs["ESP"]+=4 self.state.EIP=EIP #detect Branching Oriented Programming main mode #EIP is supposed to be pointing to user controlled space #If EIP is pointing to memory indexed by ESP, we use ROP, JOP otherwise. ptr = self.getEIPptr() if "ESP" in ptr.varsnames(): self.mode = "ROP" else: self.mode = "JOP"
class DeplibFinder: def __init__(self, config=None, compiler_instance=None): """ config is a dictionary of configuration parameters (see self.loadConfig). """ self.hashes = None self.props = None self.memoryFlags = {} self.gadgets = {} self.rop = {} self.chunks = set() self.allocated = {} self.stack = [] self.mode = "" self.rollbacks = [ ] #stack of decisions that we might rollback to try other options self.currentCommand = None #gadget searching SequenceAnalyzer instance self.sea = StateMachine(solver=PrettySolver()) self.sea.push( ) #push the initial clean state on the search instance so we can move back to it without having to create a new one #current deplib state machine self.state = StateMachine(solver=PrettySolver()) #defaults self.modules = None #Set this or it's going to fail... this's on purpose! self.badchars = "" self.stackpage = 0x1000 self.bannedGadgets = [] if config: self.loadConfig(config) if compiler_instance: self.processCommands(compiler_instance) def processCommands(self, compiler_instance): self.compilerInstance = compiler_instance self.compilerInstance.finderInstance = self #we need to cross information compiler_instance.protectCommands() compiler_instance.processDefUseChains() cmdid = 0 self.currentRollback = None while cmdid < len(compiler_instance.cmdList): opername = cmd[0] args = cmd[2] ret = False self.currentCommand = { "id":cmdid, \ "name":cmd[0], \ "protectedvars":self.protectedVarsRegs[cmdid], \ "protectedcmd":cmdid <= cmd[1], \ "args":cmd[2], \ "uses":cmd[3], \ "defines":cmd[4] } self.updateGuarded( ) #this must be done in two steps as updateGuarded() uses information from this same dictionary. self.currentCommand[ "dependencies"] = self.updateVariableDependencies() if not compiler_instance.handlers.has_key(opername): raise Exception, "An operation was registered (%s) but no handler was" % opername prefs = compiler_instance.handlers[opername].keys() prefs.sort() for p in prefs: handlers = compiler_instance.handlers[opername][ p][:] #get a copy while len(handlers): handler = handlers.pop() self.currentCommand["handlerid"] = id(handler) self.cleanAfterHandlerOps() level = self.push() ret = handler(self, args) if ret and self.processAfterHandlerOps(): break self.popto( level ) #handler didnt work out at some point, revert everything to the previous state if ret: break if not ret: #if we are here, it means we didn't find any good handler origcmdid = cmdid cmdid = self.processRollbacks() if cmdid == None: raise Exception, "Any of the available handlers worked out for '%s' on cmdid=%d" % ( opername, origcmdid) else: cmdid += 1 self.currentRollback = None def updateVariableDependencies(self): """ a variable might point to memory pointed by another variable, so we must find those cases and solve them to give an accurate list to freeReg. """ deps = set() for var in self.currentCommand["uses"] + self.currentCommand["defines"]: deps.union(self.__variable_dependencies_helper(var)) return list(deps) def __variable_dependencies_helper(self, var): deps = set() if var.isMemBound(): for x in range(0, 2): if var.containerValue[x][0][0] == "var": deps.union( self.__variable_dependencies_helper( self.compilerInstance.variables[ var.containerValue[x][0][1]])) elif var.containerValue[x][0][0] == "reg": deps.add(var.containerValue[x][0][1]) return deps def processRollbacks(self): if len(self.rollbacks) < 1: return None tmp = self.rollbacks.pop() self.currentRollback = { "cmdid": tmp[0], "handlerid": tmp[1], "varid": tmp[2], "tries": tmp[3] } return self.currentRollback["cmdid"] def cleanAfterHandlerOps(self): self.afterhandlerops = [] def processAfterHandlerOps(self): """ The only operation after handler that can be registered by now is move to memory Each operation is a 3-tuple: - mem (move to mem) - variable uniqid - final memory address/reg """ for op in self.afterhandlerops: if op[0] == "mem": var = self.compilerInstance.variables[op[1]] if not var.moveToMemory(op[2]): return False return True def getFreeRegisters(self): regs = ["EAX", "EBX", "ECX", "EDX", "ESI", "EDI", "EBP", "ESP"] if self.mode == "ROP": regs.remove("ESP") for var in self.compilerInstance.variables.values(): if var.isRegBound(): regs.remove(var.containerValue) for addr in self.allocated.keys(): varnames = addr.varsnames() for var in varnames: try: regs.remove(var) except: pass return regs def freeReg(self, tries=None, forceReg=None): """ Free a register that is not being used in the current command and is not in the tries list. """ for var in self.compilerInstance.variables.values(): if var.isRegBound() and \ var not in self.currentCommand["uses"] and \ var not in self.currentCommand["defines"] and \ var not in self.currentCommand["dependencies"] and \ (tries == None or var.containerValue not in tries) and \ (forceReg == None or var.containerValue == forceReg): reg = var.containerValue if var.moveToMemory(): var.forcedReg = reg self.currentCommand["guarded"].remove(reg) return reg return False def getRollbackTries(self, var): if self.currentRollback and \ self.currentRollback["cmdid"] == self.currentCommand["id"] and \ self.currentRollback["handlerid"] == self.currentCommand["handlerid"] and \ self.currentRollback["varid"] == var.uniqid: return self.currentRollback["tries"] return [] def addRollback(self, var): """ This is executed AFTER electing a register for a variable and is not used if getVar/bindVar is executed with a provided reg (there's no election there) """ tries = self.getRollbackTries(var) tries += [var.contentValue] self.rollbacks.append( (self.currentCommand["id"], self.currentCommand["handlerid"], var.uniqid, tries)) def solveMemoryExpression(self, indexedMemExpression): """ Returns a single expression that represents the memory offset that a variable is pointing to. """ idx0 = indexedMemExpression[0] idx1 = indexedMemExpression[1] const = indexedMemExpression[2] finalvalue = self.state.solver.constExpr(0) if const: finalvalue += const for idx in (idx0, idx1): if idx[0][0] == "notused" or idx[1] == 0: continue if idx[0][0] == "reg": finalvalue += self.state.solver.lookupVar( idx[0][1])[0] * idx[1] elif idx[0][0] == "var": var = self.compilerInstance.variables[idx[0][1]] if not var.isBound(): raise Exception, "you're trying to use an unbound/unset variable as a memory index" reg = var.getVar() if not reg: return False finalvalue += self.state.solver.lookupVar(reg)[0] * idx[1] return finalvalue def loadConfig(self, config): """ Receives a dictionary of parameters: - modules: a list of either module IDs, module name or 2-tuples with module name and version Default: None (MANDATORY PARAMETER) - badchars: a list of characters not allowed in the generated ROP Default: "" - roparea: A 2-tuple with the initial address of the user controlled space where EIP points to, and the size of this area as a second element Default: (ESP, 0x1000) - stackpage: size in bytes of the current stack (stack is considered to be writable and some gadget could overwrite non-protected areas) Default: 0x1000 - memory: A list of 3-tuples with: Initial Address, Size and Permission (see self.setMemFlags) Initial address can be a numeric address, an expression's dump or a Expression instance. Default: None - initstate: A dictionary used to initialize the state of the ROP machine. Keys are regs/flags/mem address as used in a SequenceAnalyzer instance, values are dumped expressions (see Solver.dumpExpr) Memory addresses are expressed as expression dumps too (not MEM<CRC32>). This are values that are known to be always precise. Run-dependant values (like object addresses) should be avoided. Default: None - banned: A list of banned gadgets that we can't use. Each gadget is a 2-tuple with module_id and offset. - DB related parameters: - dbtype: sqlite3/mysql (Default: sqlite3) - dbname (Default: gadgets.sq3 if sqlite3 or gadgets) - username - passwd - host (Default: 127.0.0.1) """ args = { "dbtype": None, "dbname": None, "host": None, "username": "", "passwd": "" } for k in args.keys(): if config.has_key(k) and config[k] != None: args[k] = config[k] args["quiet"] = True self.gdb = GadgetsDB(None, **args) if config.has_key("modules"): mods = config["modules"] self.modules = self.gdb.get_module_ids(mods) self.bases = self.gdb.get_module_base_from_id(self.modules) self.hashes = HashesDictionary(self.gdb, self.modules) self.props = PropertiesDictionary(self.gdb, self.modules) if config.has_key("badchars"): self.badchars = config["badchars"] if config.has_key("stackpage"): self.stackpage = config["stackpage"] if config.has_key("banned"): self.bannedGadgets = config["banned"] if not self.setMemFlags(self.state.regs["ESP"], self.stackpage, "RW"): raise Exception, "There was some error setting the initial permissions for the stack page. Check your 'memory' configuration and the stack page size." if config.has_key("memory"): for mem in config["memory"]: self.setMemFlags(mem[0], mem[1], mem[2]) if config.has_key("roparea"): addr = config["roparea"][0] size = config["roparea"][1] else: addr = self.state.regs["ESP"] size = 0x1000 if not self.setMemFlags(addr, size, "RWA"): raise Exception, "ROP Area configuration is wrong" if config.has_key("initstate"): for k, v in config["initstate"].iteritems(): if k in self.state.regs.keys(): self.state.regs[k] = self.state.solver.loadExpr(v) elif k in self.state.flags.keys(): self.state.flags[k] = self.state.solver.loadExpr(v) else: self.state.memory[k] = self.state.solver.loadExpr(v) else: #set a default initial state suitable for ROP (emulate a RETN) EIP = self.readMemory(self.state.regs["ESP"], 4, "R") self.state.regs["ESP"] += 4 self.state.EIP = EIP #detect Branching Oriented Programming main mode #EIP is supposed to be pointing to user controlled space #If EIP is pointing to memory indexed by ESP, we use ROP, JOP otherwise. ptr = self.getEIPptr() if "ESP" in ptr.varsnames(): self.mode = "ROP" else: self.mode = "JOP" def addressToExpression(self, addr): if isinstance(addr, int) or isinstance(addr, long): #numeric address addr = self.state.solver.constExpr(addr) elif isinstance(addr, tuple): #expression's dump addr = self.state.solver.loadExpr(addr) elif isinstance(addr, str) and self.state.regs.has_key(addr.upper()): #is a register addr = self.state.regs[addr.upper()] if not isinstance(addr, Expression): raise Exception, "Memory Address is not an Expression's instance (is a %s)" % type( addr) addr.simplify() return addr def setMemFlags(self, addr, size, flags): """ addr is either a numeric address, an expression's dump or an Expression instance. perm is: (read access is always granted if it has any other flag set, unless "N" is set): - R: read access - W: write access - P: this byte is part of the generated ROP - G: this byte is part of a gadget's address - S: this byte is a spare byte added to the ROP to align it (it doesn't matter what value it has, as long as it pass the badchars checks) - A: this byte is part of the ROP area (the user controlled piece of memory where our ROP is stored), mutually exclusive with P. - F: a perm can change to any other state until the F flag is set, which means the permissions for this memory byte cannot be changed anymore - B: Part of a ROP allocation (busy byte) - N: NO ACCESS NOTE: Always check the return from setMemFlags, if it fails, it returns False without touching anything. """ addr = self.addressToExpression(addr) addr.simplify() for x in xrange(0, size): tmp = addr + x tmp.simplify() if self.memoryFlags.has_key(tmp) and "F" in self.memoryFlags[tmp]: return False if not self.memoryFlags.has_key(addr): self.chunks.add((addr, size)) if "R" not in flags and "N" not in flags: flags += "R" for x in xrange(0, size): tmp = addr + x tmp.simplify() self.memoryFlags[tmp] = flags return True def checkMemAccess(self, addr, perm, size=1): """ check a given addr over our memory flags. addr is either a numeric address, an expression's dump or an Expression instance. """ addr = self.addressToExpression(addr) for x in xrange(0, size): tmp = addr + x tmp.simplify() if not self.memoryFlags.has_key(tmp): return False p = self.memoryFlags[tmp] if "N" in p: return False if perm not in p: return False return True def readMemory(self, addr, size=1, perms="R"): """ If readMemory returns False, it means that at least one of the bytes requested for read access was not permited. """ addr = self.addressToExpression(addr) if not self.checkMemAccess(addr, perms, size): return False return self.state.readMemory(addr, size) def writeMemory(self, addr, value, perms): """ value size is determined automatically. Always check the return from writeMemory, as it might fail if the current permissions dont allow updates over a given address. It returns False on error or True if everything went well. """ addr = self.addressToExpression(addr) addr.simplify() size = (len(value) + 7) // 8 if not self.setMemFlags( addr, size, perms ): #first set memory flags to fail here, before modifying anything. return False value.zeroExtend(size * 8) #we store complete bytes in memory if "G" in perms: #save gadget addresses for later use self.gadgets[addr] = value for x in range(0, size): tmp = value[x * 8:(x + 1) * 8] self.state.memory[addr] = tmp if "P" in perms: self.rop[ addr] = tmp #if this is part of the ROP, make a clean copy for later use :) addr += 1 return True def alloc(self, bytes, useRelativeAddresses=False): """ Small ROP allocator function. It takes the information from the memoryFlags dictionary to find a free chunk suitable for use and returns its address. For this allocator we only use absolute addresses, a useRelativeAddresses flag is there that allow non-absolute addresses, but keep in mind that the memory indexes must be guarded as long as you want access to your allocated memory. """ for addr, size in self.chunks: varsnames = addr.varsnames() if not useRelativeAddresses and len(varsnames) > 0: continue for pos in range(0, size): cur = addr + pos cur.simplify() if self.checkMemAccess(cur, "W", bytes) and \ not self.checkMemAccess(cur, "F", bytes) and \ not self.checkMemAccess(cur, "P", bytes) and \ not self.checkMemAccess(cur, "A", bytes) and \ not self.checkMemAccess(cur, "S", bytes) and \ not self.checkMemAccess(cur, "B", bytes): #set bytes as busy and remove writeable flag for x in range(0, bytes): tmp = cur + x tmp.simplify() flags = self.memoryFlags[tmp].replace("W", "") #remove W flags += "B" #add B self.setMemFlags(tmp, 1, flags) self.allocated[cur] = bytes return cur def free(self, addr): """ Free an allocated piece of memory by removing the B flag and reseting the W flag. """ addr.simplify() if not self.allocated.has_key(addr): return False bytes = self.allocated.pop(addr) for x in range(0, bytes): tmp = addr + x tmp.simplify() flags = self.memoryFlags[tmp].replace("B", "") #remove B flags += "W" #add W self.setMemFlags(tmp, 1, flags) return True def push(self): """ Pushes the state of the deplib finder. Returns the stackLevel where the state was saved. """ self.state.push() #deepcopying, except variables where we must avoid copy recursion tosave = (copy(self.gadgets), deepcopy(self.memoryFlags), deepcopy(self.rop), deepcopy(self.chunks), deepcopy(self.allocated), copy(self.compilerInstance.variables), deepcopy(self.currentCommand)) self.stack.append(tosave) return len(self.stack) - 1 def pop(self): self.state.pop() ret = self.stack.pop() (self.gadgets, self.memoryFlags, self.rop, self.chunks, self.allocated, self.compilerInstance.variables, self.currentCommand) = ret return ret def popto(self, stackLevel): ret = None while len(self.stack) > stackLevel: ret = self.pop() return ret def stacklevel(self): return len(self.stack) def cleanSearch(self): """ clean the search state machine by popping and pushing the orig state """ self.sea.popto(0) self.sea.push() ################## gadget's searching ##################### def findGadget(self, mode=1, allresults=False): """ Search for a gadget that complies with all constrains given in self.sea. It also has to comply with constrains in guarded (regs/flags/mem) and to have some standard properties like controlled ESP and EIP. First it tries using the hashes dictionary and if it cant find a match, it searchs on the DB using first the properties value and then instantiating each gadget and quering the solver. mode is a bitmask that decides if we search by hashes (=1) and/or by props(=2) This is an iterator that returns a 4-tuple (modid, offset, addedcomplexity, mode) of the chosen gadget. """ self.sea.simplify() searchHashes = self.sea.hashState() tmp = self.sea.calcProperties() searchProps = {} for k, v in tmp[0].iteritems(): if v: searchProps[k] = v if tmp[1]: searchProps["FLAGS"] = ( tmp[1], tmp[1]) #we only care about the flags that changed findings = [] #first search by hashes and then by properties #each search is ordered by module and complexity (it respects module order from self.modules) if mode & 1: by_hashes = self.gdb.search_by_hashes(searchHashes, self.hashes) generator = self.findBestGadget(by_hashes, mode=1) for gadget in generator: if tuple(gadget) not in findings: findings.append(tuple(gadget)) gadget.append(1) yield gadget if not allresults: break generator.close() if mode & 2: by_props = self.gdb.search_by_properties(searchProps, self.props) generator = self.findBestGadget(by_props, mode=2) for gadget in generator: if tuple(gadget) not in findings: findings.append(tuple(gadget)) gadget.append(2) yield gadget if not allresults: break generator.close() def findBestGadget(self, generator, mode): """ It tries to find the gadget with the lower added complexity from the gadget's generator provided. This function is an iterator. Which is useful for returning all best matches. """ bestMatch = None for candidate in generator: complexity = candidate[2] if bestMatch and complexity > bestMatch[2]: yield bestMatch bestMatch = None addedcomplexity = self.applyGadget(candidate, mode=mode, dryRun=True) if addedcomplexity == None: continue addedcomplexity += complexity #add the complexity of the chosen gadget to have a number that represents the whole bunch #if we are here, it means we found a match if not bestMatch or addedcomplexity < bestMatch[2]: bestMatch = [candidate[0], candidate[1], addedcomplexity] if bestMatch: yield bestMatch def applyGadget(self, gadget, mode=1, recursiveEnter=False, dryRun=False): """ This function might destroy the state of the ROP, so please remember to push/pop before using it. Returns the added complexity resulting from using a given gadget or None if it's not possible to apply. """ modid = gadget[0] offset = gadget[1] #we cant use banned gadgets if (modid, offset) in self.bannedGadgets: return None gadget_sm = self.gdb.get_gadget_by_offset(modid, offset) address = self.bases[modid] + offset newcomplexity = 0 ret = self.checkPreconditions(gadget_sm, address, mode) if ret == 0: return None #it's impossible to apply this gadget if ret == 1: if recursiveEnter: return None if dryRun: level = self.push( ) #save the current state before any modification newcomplexity = self.fixPreconditions(gadget_sm, mode) if dryRun: self.popto(level) if newcomplexity == None: #it was impossible to fix the preconditions return None #if we are here, it's because we can use this gadget, so we update the state machine and self.memoryFlags to reflect this change. if not dryRun: #mark mem address as ROP and write it to the state memory EIPptr = self.getEIPptr() perms = "RPG" if self.currentCommand["protectedcmd"]: perms += "F" #if this is a protected operation, gadget's address cannot be overwritten address = self.state.solver.constExpr(address) self.writeMemory(EIPptr, address, perms) #merge the changes from the added gadget self.state.mergeState(gadget_sm) self.state.simplify() return newcomplexity def getEIPptr(self): tmp = self.state.EIP[0:8] tmp.simplify() tmp = self.state.memory.sources[str(tmp).replace("VAL", "MEM")] return self.state.solver.loadExpr(tmp) def checkPreconditions(self, gadget, address, mode): """ Check if we can use this gadget and return an integer that means: - 0: we cant use this gadget - 1: we can use this gadget after applying fixes from fixPreconditions - 2: we can use this gadget as it is right now Note: this function only CHECK things, NEVER changes the machine state if it returns 1: It saves a list of fixes that must be applied to the gadget in order to be useful. This fixes are correct as long as the machine state remains the same and the gadget remain the same. Conditions: - Each byte of EIP must be a VALxxxxxxxx pointing to 4 sucessives bytes in memory """ #self.fixes[hash(self.state)][hash(gadget)]=["fixes"] #XXX: TODO return 2 def fixPreconditions(self, gadget, mode): """ Try to fix the current state machine so we can use the given gadget. It returns the added complexity for all the added gadgets or None if the problem is unfixable. Note: this function CHANGES the machine state. """ return 0 #XXX: TODO #XXX: call applyGadget with recursiveEnter=True ################################# Variables handling ############################################ def updateGuarded(self): """ return a list of guarded registers and other information that needs to be protected. """ guarded = [] for var in self.currentCommand["protectedvars"]: if var.isRegBound(): guarded.append(var.containerValue) self.currentCommand["guarded"] = guarded ################################### Utilities ################################################### def checkBadChars(self, tocheck): return True #XXX: TODO def moveExprToReg(self, exp, reg=None): """ Move an expression to a register. Returns the register where the expression was set. """ mergeDict = {} mergeDict.update(self.state.regs) mergeDict.update(self.state.flags) mergedExpr = exp.merge(mergeDict) freeregs = self.getFreeRegisters( ) #if we're gonna change something, we can only use free registers allregs = self.state.regs.keys() if reg: ROregs = [reg] #we can just use this one RWregs = [reg] TMPregs = freeregs else: ROregs = allregs #if it doesnt imply changing the content of the register, we can use any register RWregs = freeregs TMPregs = freeregs if not self.currentCommand[ "protectedcmd"]: #protected cmds cannot use CONTEXT for searchs #first check if some of the registers have the given value, just by chance. for tmp in ROregs: if self.state.regs[tmp] == mergedExpr: return tmp #MOV REG, EXP bestcomplexity = None for tmp in RWregs: self.cleanSearch() self.sea.regs[tmp] = exp for g in self.findGadget(): if not bestcomplexity or g[2] < bestcomplexity[1][2]: bestcomplexity = (tmp, g) if bestcomplexity: if self.applyGadget(bestcomplexity[1]) == None: raise Exception, "Shouldn't happen" return bestcomplexity[0] if not self.currentCommand["protectedcmd"] and mergedExpr.isConstant( ) and mergedExpr != exp: #protected cmds cannot use CONTEXT for searchs #MOV REG, MERGED_EXP bestcomplexity = None for tmp in RWregs: self.cleanSearch() self.sea.regs[tmp] = mergedExpr for g in self.findGadget(): if not bestcomplexity or g[2] < bestcomplexity[1][2]: bestcomplexity = (tmp, g) if bestcomplexity: if self.applyGadget(bestcomplexity[1]) == None: raise Exception, "Shouldn't happen" return bestcomplexity[0] #Check if there's a simple operation that ends in our desired value using the current CONTEXT #SUB|ADD|etc REG, TMP (CONTEXT) bestcomplexity = None for r in RWregs: for tmp in allregs: #this secondary reg is used RO tri = [] if self.state.regs[r] + self.state.regs[tmp] == mergedExpr: tri.append("add") if self.state.regs[r] - self.state.regs[tmp] == mergedExpr: tri.append("sub1") if self.state.regs[tmp] - self.state.regs[r] == mergedExpr: tri.append("sub2") if (self.state.regs[r] * self.state.regs[tmp])[0:32] == mergedExpr: tri.append("mul") if self.state.regs[r] / self.state.regs[tmp] == mergedExpr: tri.append("div1") if self.state.regs[tmp] / self.state.regs[r] == mergedExpr: tri.append("div2") for t in tri: self.cleanSearch() if t == "add": self.sea.regs[r] += self.sea.regs[tmp] elif t == "sub1": self.sea.regs[ r] = self.sea.regs[r] - self.sea.regs[tmp] elif t == "sub2": self.sea.regs[ r] = self.sea.regs[tmp] - self.sea.regs[r] elif t == "mul": self.sea.regs[r] = (self.sea.regs[r] * self.sea.regs[tmp])[0:32] elif t == "div1": self.sea.regs[ r] = self.sea.regs[r] / self.sea.regs[tmp] elif t == "div2": self.sea.regs[ r] = self.sea.regs[tmp] / self.sea.regs[r] for g in self.findGadget(): if not bestcomplexity or g[2] < bestcomplexity[1][ 2]: bestcomplexity = (r, g) if bestcomplexity: if self.applyGadget(bestcomplexity[1]) == None: raise Exception, "Shouldn't happen" return bestcomplexity[0] if self.mode == "ROP": if exp.isConstant() or (mergedExpr.isConstant() and not self.currentCommand["protectedcmd"]): if exp.isConstant(): useexp = exp else: useexp = mergedExpr if self.checkBadChars(useexp): #POP REG bestcomplexity = None for r in RWregs: self.cleanSearch() self.sea.regs[r] = self.sea.readMemory( self.sea.regs["ESP"], 4) for g in self.findGadget( mode=3): #do a search by properties too gadget_sm = self.gdb.get_gadget_by_offset( g[0], g[1]) tmpreg = gadget_sm.regs[r] bytes = [] for x in range(0, 32, 8): b = gadget_sm.solver.simplify( gadget_sm.solver.extractExpr( tmpreg, x, x + 7)) b = gadget_sm.solver.exprString(b).replace( "VAL", "MEM") if b in bytes: break bytes.append(b) if len(bytes) != 4: continue #it should be composed of 4 different bytes, all coming from unsolved memory areas itsok = True if g[3] == 2: #search-by-properties gadget, check if it actually have a format that we can handle for b in bytes: if not gadget_sm.memory.sources.has_key(b): itsok = False break if gadget_sm.memory.getIndexes( b, recursive=False ) != set( ["ESP"] ): #it should be memory indexed by ESP ONLY itsok = False break if itsok: if not bestcomplexity or g[2] < bestcomplexity[ 1][2]: bestcomplexity = (r, g, bytes, gadget_sm) if bestcomplexity: bytes = bestcomplexity[2] gadget_sm = bestcomplexity[3] bytes.reverse() #little endian c = 0 for b in bytes: #set memory before applying the gadget tmp = self.state.solver.loadExpr( gadget_sm.memory.sources[b]) merged = tmp.merge(mergeDict) perms = "RP" if self.currentCommand["protectedcmd"]: perms += "F" self.writeMemory(merged, useexp[c:c + 8], perms) c += 8 if self.applyGadget(bestcomplexity[1]) == None: raise Exception, "This shouldn't happen" return bestcomplexity[0] #POP TMP|REG/SUB|ADD REG, TMP (CONTEXT) #POP REG/POP TMP/SUB|ADD REG, TMP #XXX: TODO def __find_good_op_chars(self, op, C, A=None, B=None): """ find a pair of values so that doing A <operation> B = C. Where A and B are assured to be outside the badchars blacklist. """ pass
class DeplibFinder: def __init__(self, config=None, compiler_instance=None): """ config is a dictionary of configuration parameters (see self.loadConfig). """ self.hashes = None self.props = None self.memoryFlags = {} self.gadgets = {} self.rop = {} self.chunks = set() self.allocated = {} self.stack = [] self.mode = "" self.rollbacks = [] #stack of decisions that we might rollback to try other options self.currentCommand = None #gadget searching SequenceAnalyzer instance self.sea = StateMachine(solver=PrettySolver()) self.sea.push() #push the initial clean state on the search instance so we can move back to it without having to create a new one #current deplib state machine self.state = StateMachine(solver=PrettySolver()) #defaults self.modules = None #Set this or it's going to fail... this's on purpose! self.badchars = "" self.stackpage = 0x1000 self.bannedGadgets = [] if config: self.loadConfig(config) if compiler_instance: self.processCommands(compiler_instance) def processCommands(self, compiler_instance): self.compilerInstance = compiler_instance self.compilerInstance.finderInstance = self #we need to cross information compiler_instance.protectCommands() compiler_instance.processDefUseChains() cmdid=0 self.currentRollback = None while cmdid < len(compiler_instance.cmdList): opername=cmd[0] args=cmd[2] ret=False self.currentCommand = { "id":cmdid, \ "name":cmd[0], \ "protectedvars":self.protectedVarsRegs[cmdid], \ "protectedcmd":cmdid <= cmd[1], \ "args":cmd[2], \ "uses":cmd[3], \ "defines":cmd[4] } self.updateGuarded() #this must be done in two steps as updateGuarded() uses information from this same dictionary. self.currentCommand["dependencies"]=self.updateVariableDependencies() if not compiler_instance.handlers.has_key(opername): raise Exception, "An operation was registered (%s) but no handler was"%opername prefs = compiler_instance.handlers[opername].keys() prefs.sort() for p in prefs: handlers = compiler_instance.handlers[opername][p][:] #get a copy while len(handlers): handler=handlers.pop() self.currentCommand["handlerid"]=id(handler) self.cleanAfterHandlerOps() level = self.push() ret=handler(self, args) if ret and self.processAfterHandlerOps(): break self.popto(level) #handler didnt work out at some point, revert everything to the previous state if ret: break if not ret: #if we are here, it means we didn't find any good handler origcmdid=cmdid cmdid = self.processRollbacks() if cmdid == None: raise Exception, "Any of the available handlers worked out for '%s' on cmdid=%d"%(opername, origcmdid) else: cmdid+=1 self.currentRollback = None def updateVariableDependencies(self): """ a variable might point to memory pointed by another variable, so we must find those cases and solve them to give an accurate list to freeReg. """ deps=set() for var in self.currentCommand["uses"] + self.currentCommand["defines"]: deps.union(self.__variable_dependencies_helper(var)) return list(deps) def __variable_dependencies_helper(self, var): deps=set() if var.isMemBound(): for x in range(0,2): if var.containerValue[x][0][0] == "var": deps.union(self.__variable_dependencies_helper(self.compilerInstance.variables[var.containerValue[x][0][1]])) elif var.containerValue[x][0][0] == "reg": deps.add(var.containerValue[x][0][1]) return deps def processRollbacks(self): if len(self.rollbacks) < 1: return None tmp=self.rollbacks.pop() self.currentRollback={"cmdid":tmp[0], "handlerid":tmp[1], "varid":tmp[2], "tries":tmp[3]} return self.currentRollback["cmdid"] def cleanAfterHandlerOps(self): self.afterhandlerops = [] def processAfterHandlerOps(self): """ The only operation after handler that can be registered by now is move to memory Each operation is a 3-tuple: - mem (move to mem) - variable uniqid - final memory address/reg """ for op in self.afterhandlerops: if op[0] == "mem": var=self.compilerInstance.variables[op[1]] if not var.moveToMemory(op[2]): return False return True def getFreeRegisters(self): regs=["EAX", "EBX", "ECX", "EDX", "ESI", "EDI", "EBP", "ESP"] if self.mode == "ROP": regs.remove("ESP") for var in self.compilerInstance.variables.values(): if var.isRegBound(): regs.remove(var.containerValue) for addr in self.allocated.keys(): varnames = addr.varsnames() for var in varnames: try: regs.remove(var) except: pass return regs def freeReg(self, tries=None, forceReg=None): """ Free a register that is not being used in the current command and is not in the tries list. """ for var in self.compilerInstance.variables.values(): if var.isRegBound() and \ var not in self.currentCommand["uses"] and \ var not in self.currentCommand["defines"] and \ var not in self.currentCommand["dependencies"] and \ (tries == None or var.containerValue not in tries) and \ (forceReg == None or var.containerValue == forceReg): reg = var.containerValue if var.moveToMemory(): var.forcedReg = reg self.currentCommand["guarded"].remove(reg) return reg return False def getRollbackTries(self, var): if self.currentRollback and \ self.currentRollback["cmdid"] == self.currentCommand["id"] and \ self.currentRollback["handlerid"] == self.currentCommand["handlerid"] and \ self.currentRollback["varid"] == var.uniqid: return self.currentRollback["tries"] return [] def addRollback(self, var): """ This is executed AFTER electing a register for a variable and is not used if getVar/bindVar is executed with a provided reg (there's no election there) """ tries = self.getRollbackTries(var) tries += [var.contentValue] self.rollbacks.append( (self.currentCommand["id"], self.currentCommand["handlerid"], var.uniqid, tries) ) def solveMemoryExpression(self, indexedMemExpression): """ Returns a single expression that represents the memory offset that a variable is pointing to. """ idx0 = indexedMemExpression[0] idx1 = indexedMemExpression[1] const = indexedMemExpression[2] finalvalue = self.state.solver.constExpr(0) if const: finalvalue += const for idx in (idx0, idx1): if idx[0][0] == "notused" or idx[1] == 0: continue if idx[0][0] == "reg": finalvalue += self.state.solver.lookupVar(idx[0][1])[0] * idx[1] elif idx[0][0] == "var": var = self.compilerInstance.variables[idx[0][1]] if not var.isBound(): raise Exception, "you're trying to use an unbound/unset variable as a memory index" reg = var.getVar() if not reg: return False finalvalue += self.state.solver.lookupVar(reg)[0] * idx[1] return finalvalue def loadConfig(self, config): """ Receives a dictionary of parameters: - modules: a list of either module IDs, module name or 2-tuples with module name and version Default: None (MANDATORY PARAMETER) - badchars: a list of characters not allowed in the generated ROP Default: "" - roparea: A 2-tuple with the initial address of the user controlled space where EIP points to, and the size of this area as a second element Default: (ESP, 0x1000) - stackpage: size in bytes of the current stack (stack is considered to be writable and some gadget could overwrite non-protected areas) Default: 0x1000 - memory: A list of 3-tuples with: Initial Address, Size and Permission (see self.setMemFlags) Initial address can be a numeric address, an expression's dump or a Expression instance. Default: None - initstate: A dictionary used to initialize the state of the ROP machine. Keys are regs/flags/mem address as used in a SequenceAnalyzer instance, values are dumped expressions (see Solver.dumpExpr) Memory addresses are expressed as expression dumps too (not MEM<CRC32>). This are values that are known to be always precise. Run-dependant values (like object addresses) should be avoided. Default: None - banned: A list of banned gadgets that we can't use. Each gadget is a 2-tuple with module_id and offset. - DB related parameters: - dbtype: sqlite3/mysql (Default: sqlite3) - dbname (Default: gadgets.sq3 if sqlite3 or gadgets) - username - passwd - host (Default: 127.0.0.1) """ args={"dbtype":None, "dbname":None, "host":None, "username":"", "passwd":""} for k in args.keys(): if config.has_key(k) and config[k] != None: args[k]=config[k] args["quiet"]=True self.gdb = GadgetsDB(None, **args) if config.has_key("modules"): mods=config["modules"] self.modules = self.gdb.get_module_ids(mods) self.bases = self.gdb.get_module_base_from_id(self.modules) self.hashes = HashesDictionary(self.gdb, self.modules) self.props = PropertiesDictionary(self.gdb, self.modules) if config.has_key("badchars"): self.badchars = config["badchars"] if config.has_key("stackpage"): self.stackpage = config["stackpage"] if config.has_key("banned"): self.bannedGadgets = config["banned"] if not self.setMemFlags(self.state.regs["ESP"], self.stackpage, "RW"): raise Exception, "There was some error setting the initial permissions for the stack page. Check your 'memory' configuration and the stack page size." if config.has_key("memory"): for mem in config["memory"]: self.setMemFlags(mem[0], mem[1], mem[2]) if config.has_key("roparea"): addr=config["roparea"][0] size=config["roparea"][1] else: addr=self.state.regs["ESP"] size=0x1000 if not self.setMemFlags(addr, size, "RWA"): raise Exception, "ROP Area configuration is wrong" if config.has_key("initstate"): for k,v in config["initstate"].iteritems(): if k in self.state.regs.keys(): self.state.regs[k]=self.state.solver.loadExpr(v) elif k in self.state.flags.keys(): self.state.flags[k]=self.state.solver.loadExpr(v) else: self.state.memory[k]=self.state.solver.loadExpr(v) else: #set a default initial state suitable for ROP (emulate a RETN) EIP=self.readMemory(self.state.regs["ESP"], 4, "R") self.state.regs["ESP"]+=4 self.state.EIP=EIP #detect Branching Oriented Programming main mode #EIP is supposed to be pointing to user controlled space #If EIP is pointing to memory indexed by ESP, we use ROP, JOP otherwise. ptr = self.getEIPptr() if "ESP" in ptr.varsnames(): self.mode = "ROP" else: self.mode = "JOP" def addressToExpression(self, addr): if isinstance(addr, int) or isinstance(addr, long): #numeric address addr=self.state.solver.constExpr(addr) elif isinstance(addr, tuple): #expression's dump addr=self.state.solver.loadExpr(addr) elif isinstance(addr, str) and self.state.regs.has_key(addr.upper()): #is a register addr=self.state.regs[addr.upper()] if not isinstance(addr, Expression): raise Exception, "Memory Address is not an Expression's instance (is a %s)"%type(addr) addr.simplify() return addr def setMemFlags(self, addr, size, flags): """ addr is either a numeric address, an expression's dump or an Expression instance. perm is: (read access is always granted if it has any other flag set, unless "N" is set): - R: read access - W: write access - P: this byte is part of the generated ROP - G: this byte is part of a gadget's address - S: this byte is a spare byte added to the ROP to align it (it doesn't matter what value it has, as long as it pass the badchars checks) - A: this byte is part of the ROP area (the user controlled piece of memory where our ROP is stored), mutually exclusive with P. - F: a perm can change to any other state until the F flag is set, which means the permissions for this memory byte cannot be changed anymore - B: Part of a ROP allocation (busy byte) - N: NO ACCESS NOTE: Always check the return from setMemFlags, if it fails, it returns False without touching anything. """ addr=self.addressToExpression(addr) addr.simplify() for x in xrange(0, size): tmp = addr+x tmp.simplify() if self.memoryFlags.has_key(tmp) and "F" in self.memoryFlags[tmp]: return False if not self.memoryFlags.has_key(addr): self.chunks.add( (addr, size) ) if "R" not in flags and "N" not in flags: flags+="R" for x in xrange(0, size): tmp = addr+x tmp.simplify() self.memoryFlags[tmp]=flags return True def checkMemAccess(self, addr, perm, size=1): """ check a given addr over our memory flags. addr is either a numeric address, an expression's dump or an Expression instance. """ addr=self.addressToExpression(addr) for x in xrange(0, size): tmp = addr+x tmp.simplify() if not self.memoryFlags.has_key(tmp): return False p=self.memoryFlags[tmp] if "N" in p: return False if perm not in p: return False return True def readMemory(self, addr, size=1, perms="R"): """ If readMemory returns False, it means that at least one of the bytes requested for read access was not permited. """ addr=self.addressToExpression(addr) if not self.checkMemAccess(addr, perms, size): return False return self.state.readMemory(addr, size) def writeMemory(self, addr, value, perms): """ value size is determined automatically. Always check the return from writeMemory, as it might fail if the current permissions dont allow updates over a given address. It returns False on error or True if everything went well. """ addr=self.addressToExpression(addr) addr.simplify() size = (len(value)+7)//8 if not self.setMemFlags(addr, size, perms): #first set memory flags to fail here, before modifying anything. return False value.zeroExtend(size*8) #we store complete bytes in memory if "G" in perms: #save gadget addresses for later use self.gadgets[addr] = value for x in range(0, size): tmp = value[x*8:(x+1)*8] self.state.memory[addr] = tmp if "P" in perms: self.rop[addr] = tmp #if this is part of the ROP, make a clean copy for later use :) addr+=1 return True def alloc(self, bytes, useRelativeAddresses=False): """ Small ROP allocator function. It takes the information from the memoryFlags dictionary to find a free chunk suitable for use and returns its address. For this allocator we only use absolute addresses, a useRelativeAddresses flag is there that allow non-absolute addresses, but keep in mind that the memory indexes must be guarded as long as you want access to your allocated memory. """ for addr,size in self.chunks: varsnames=addr.varsnames() if not useRelativeAddresses and len(varsnames) > 0: continue for pos in range(0, size): cur=addr+pos cur.simplify() if self.checkMemAccess(cur, "W", bytes) and \ not self.checkMemAccess(cur, "F", bytes) and \ not self.checkMemAccess(cur, "P", bytes) and \ not self.checkMemAccess(cur, "A", bytes) and \ not self.checkMemAccess(cur, "S", bytes) and \ not self.checkMemAccess(cur, "B", bytes): #set bytes as busy and remove writeable flag for x in range(0, bytes): tmp = cur+x tmp.simplify() flags=self.memoryFlags[tmp].replace("W","") #remove W flags+="B" #add B self.setMemFlags(tmp, 1, flags) self.allocated[cur]=bytes return cur def free(self, addr): """ Free an allocated piece of memory by removing the B flag and reseting the W flag. """ addr.simplify() if not self.allocated.has_key(addr): return False bytes = self.allocated.pop(addr) for x in range(0, bytes): tmp = addr+x tmp.simplify() flags=self.memoryFlags[tmp].replace("B","") #remove B flags+="W" #add W self.setMemFlags(tmp, 1, flags) return True def push(self): """ Pushes the state of the deplib finder. Returns the stackLevel where the state was saved. """ self.state.push() #deepcopying, except variables where we must avoid copy recursion tosave = ( copy(self.gadgets), deepcopy(self.memoryFlags), deepcopy(self.rop), deepcopy(self.chunks), deepcopy(self.allocated), copy(self.compilerInstance.variables), deepcopy(self.currentCommand) ) self.stack.append(tosave) return len(self.stack) - 1 def pop(self): self.state.pop() ret = self.stack.pop() ( self.gadgets, self.memoryFlags, self.rop, self.chunks, self.allocated, self.compilerInstance.variables, self.currentCommand ) = ret return ret def popto(self, stackLevel): ret=None while len(self.stack) > stackLevel: ret=self.pop() return ret def stacklevel(self): return len(self.stack) def cleanSearch(self): """ clean the search state machine by popping and pushing the orig state """ self.sea.popto(0) self.sea.push() ################## gadget's searching ##################### def findGadget(self, mode=1, allresults=False): """ Search for a gadget that complies with all constrains given in self.sea. It also has to comply with constrains in guarded (regs/flags/mem) and to have some standard properties like controlled ESP and EIP. First it tries using the hashes dictionary and if it cant find a match, it searchs on the DB using first the properties value and then instantiating each gadget and quering the solver. mode is a bitmask that decides if we search by hashes (=1) and/or by props(=2) This is an iterator that returns a 4-tuple (modid, offset, addedcomplexity, mode) of the chosen gadget. """ self.sea.simplify() searchHashes=self.sea.hashState() tmp=self.sea.calcProperties() searchProps={} for k,v in tmp[0].iteritems(): if v: searchProps[k]=v if tmp[1]: searchProps["FLAGS"]=(tmp[1], tmp[1]) #we only care about the flags that changed findings=[] #first search by hashes and then by properties #each search is ordered by module and complexity (it respects module order from self.modules) if mode & 1: by_hashes=self.gdb.search_by_hashes(searchHashes, self.hashes) generator=self.findBestGadget(by_hashes, mode=1) for gadget in generator: if tuple(gadget) not in findings: findings.append(tuple(gadget)) gadget.append(1) yield gadget if not allresults: break generator.close() if mode & 2: by_props=self.gdb.search_by_properties(searchProps, self.props) generator=self.findBestGadget(by_props, mode=2) for gadget in generator: if tuple(gadget) not in findings: findings.append(tuple(gadget)) gadget.append(2) yield gadget if not allresults: break generator.close() def findBestGadget(self, generator, mode): """ It tries to find the gadget with the lower added complexity from the gadget's generator provided. This function is an iterator. Which is useful for returning all best matches. """ bestMatch = None for candidate in generator: complexity = candidate[2] if bestMatch and complexity > bestMatch[2]: yield bestMatch bestMatch = None addedcomplexity = self.applyGadget(candidate, mode=mode, dryRun=True) if addedcomplexity == None: continue addedcomplexity += complexity #add the complexity of the chosen gadget to have a number that represents the whole bunch #if we are here, it means we found a match if not bestMatch or addedcomplexity < bestMatch[2]: bestMatch = [candidate[0], candidate[1], addedcomplexity] if bestMatch: yield bestMatch def applyGadget(self, gadget, mode=1, recursiveEnter=False, dryRun = False): """ This function might destroy the state of the ROP, so please remember to push/pop before using it. Returns the added complexity resulting from using a given gadget or None if it's not possible to apply. """ modid = gadget[0] offset = gadget[1] #we cant use banned gadgets if (modid, offset) in self.bannedGadgets: return None gadget_sm = self.gdb.get_gadget_by_offset(modid, offset) address = self.bases[modid] + offset newcomplexity = 0 ret = self.checkPreconditions(gadget_sm, address, mode) if ret == 0: return None #it's impossible to apply this gadget if ret == 1: if recursiveEnter: return None if dryRun: level=self.push() #save the current state before any modification newcomplexity = self.fixPreconditions(gadget_sm, mode) if dryRun: self.popto(level) if newcomplexity == None: #it was impossible to fix the preconditions return None #if we are here, it's because we can use this gadget, so we update the state machine and self.memoryFlags to reflect this change. if not dryRun: #mark mem address as ROP and write it to the state memory EIPptr=self.getEIPptr() perms="RPG" if self.currentCommand["protectedcmd"]: perms+="F" #if this is a protected operation, gadget's address cannot be overwritten address = self.state.solver.constExpr(address) self.writeMemory(EIPptr, address, perms) #merge the changes from the added gadget self.state.mergeState(gadget_sm) self.state.simplify() return newcomplexity def getEIPptr(self): tmp=self.state.EIP[0:8] tmp.simplify() tmp=self.state.memory.sources[str(tmp).replace("VAL","MEM")] return self.state.solver.loadExpr(tmp) def checkPreconditions(self, gadget, address, mode): """ Check if we can use this gadget and return an integer that means: - 0: we cant use this gadget - 1: we can use this gadget after applying fixes from fixPreconditions - 2: we can use this gadget as it is right now Note: this function only CHECK things, NEVER changes the machine state if it returns 1: It saves a list of fixes that must be applied to the gadget in order to be useful. This fixes are correct as long as the machine state remains the same and the gadget remain the same. Conditions: - Each byte of EIP must be a VALxxxxxxxx pointing to 4 sucessives bytes in memory """ #self.fixes[hash(self.state)][hash(gadget)]=["fixes"] #XXX: TODO return 2 def fixPreconditions(self, gadget, mode): """ Try to fix the current state machine so we can use the given gadget. It returns the added complexity for all the added gadgets or None if the problem is unfixable. Note: this function CHANGES the machine state. """ return 0 #XXX: TODO #XXX: call applyGadget with recursiveEnter=True ################################# Variables handling ############################################ def updateGuarded(self): """ return a list of guarded registers and other information that needs to be protected. """ guarded = [] for var in self.currentCommand["protectedvars"]: if var.isRegBound(): guarded.append(var.containerValue) self.currentCommand["guarded"] = guarded ################################### Utilities ################################################### def checkBadChars(self, tocheck): return True #XXX: TODO def moveExprToReg(self, exp, reg=None): """ Move an expression to a register. Returns the register where the expression was set. """ mergeDict = {} mergeDict.update(self.state.regs) mergeDict.update(self.state.flags) mergedExpr = exp.merge(mergeDict) freeregs=self.getFreeRegisters() #if we're gonna change something, we can only use free registers allregs=self.state.regs.keys() if reg: ROregs=[reg] #we can just use this one RWregs=[reg] TMPregs=freeregs else: ROregs=allregs #if it doesnt imply changing the content of the register, we can use any register RWregs=freeregs TMPregs=freeregs if not self.currentCommand["protectedcmd"]: #protected cmds cannot use CONTEXT for searchs #first check if some of the registers have the given value, just by chance. for tmp in ROregs: if self.state.regs[tmp] == mergedExpr: return tmp #MOV REG, EXP bestcomplexity=None for tmp in RWregs: self.cleanSearch() self.sea.regs[tmp] = exp for g in self.findGadget(): if not bestcomplexity or g[2] < bestcomplexity[1][2]: bestcomplexity=(tmp,g) if bestcomplexity: if self.applyGadget(bestcomplexity[1]) == None: raise Exception, "Shouldn't happen" return bestcomplexity[0] if not self.currentCommand["protectedcmd"] and mergedExpr.isConstant() and mergedExpr != exp: #protected cmds cannot use CONTEXT for searchs #MOV REG, MERGED_EXP bestcomplexity=None for tmp in RWregs: self.cleanSearch() self.sea.regs[tmp] = mergedExpr for g in self.findGadget(): if not bestcomplexity or g[2] < bestcomplexity[1][2]: bestcomplexity=(tmp,g) if bestcomplexity: if self.applyGadget(bestcomplexity[1]) == None: raise Exception, "Shouldn't happen" return bestcomplexity[0] #Check if there's a simple operation that ends in our desired value using the current CONTEXT #SUB|ADD|etc REG, TMP (CONTEXT) bestcomplexity=None for r in RWregs: for tmp in allregs: #this secondary reg is used RO tri=[] if self.state.regs[r] + self.state.regs[tmp] == mergedExpr: tri.append("add") if self.state.regs[r] - self.state.regs[tmp] == mergedExpr: tri.append("sub1") if self.state.regs[tmp] - self.state.regs[r] == mergedExpr: tri.append("sub2") if (self.state.regs[r] * self.state.regs[tmp])[0:32] == mergedExpr: tri.append("mul") if self.state.regs[r] / self.state.regs[tmp] == mergedExpr: tri.append("div1") if self.state.regs[tmp] / self.state.regs[r] == mergedExpr: tri.append("div2") for t in tri: self.cleanSearch() if t == "add": self.sea.regs[r] += self.sea.regs[tmp] elif t == "sub1": self.sea.regs[r] = self.sea.regs[r] - self.sea.regs[tmp] elif t == "sub2": self.sea.regs[r] = self.sea.regs[tmp] - self.sea.regs[r] elif t == "mul": self.sea.regs[r] = (self.sea.regs[r] * self.sea.regs[tmp])[0:32] elif t == "div1": self.sea.regs[r] = self.sea.regs[r] / self.sea.regs[tmp] elif t == "div2": self.sea.regs[r] = self.sea.regs[tmp] / self.sea.regs[r] for g in self.findGadget(): if not bestcomplexity or g[2] < bestcomplexity[1][2]: bestcomplexity=(r,g) if bestcomplexity: if self.applyGadget(bestcomplexity[1]) == None: raise Exception, "Shouldn't happen" return bestcomplexity[0] if self.mode == "ROP": if exp.isConstant() or (mergedExpr.isConstant() and not self.currentCommand["protectedcmd"]): if exp.isConstant(): useexp = exp else: useexp = mergedExpr if self.checkBadChars(useexp): #POP REG bestcomplexity=None for r in RWregs: self.cleanSearch() self.sea.regs[r] = self.sea.readMemory(self.sea.regs["ESP"], 4) for g in self.findGadget(mode=3): #do a search by properties too gadget_sm = self.gdb.get_gadget_by_offset(g[0], g[1]) tmpreg =gadget_sm.regs[r] bytes=[] for x in range(0, 32, 8): b = gadget_sm.solver.simplify(gadget_sm.solver.extractExpr(tmpreg, x, x+7)) b = gadget_sm.solver.exprString(b).replace("VAL","MEM") if b in bytes: break bytes.append(b) if len(bytes) != 4: continue #it should be composed of 4 different bytes, all coming from unsolved memory areas itsok=True if g[3] == 2: #search-by-properties gadget, check if it actually have a format that we can handle for b in bytes: if not gadget_sm.memory.sources.has_key(b): itsok=False break if gadget_sm.memory.getIndexes(b, recursive=False) != set(["ESP"]): #it should be memory indexed by ESP ONLY itsok=False break if itsok: if not bestcomplexity or g[2] < bestcomplexity[1][2]: bestcomplexity=(r,g,bytes,gadget_sm) if bestcomplexity: bytes=bestcomplexity[2] gadget_sm=bestcomplexity[3] bytes.reverse() #little endian c=0 for b in bytes: #set memory before applying the gadget tmp = self.state.solver.loadExpr(gadget_sm.memory.sources[b]) merged =tmp.merge(mergeDict) perms="RP" if self.currentCommand["protectedcmd"]: perms+="F" self.writeMemory(merged, useexp[c:c+8], perms) c+=8 if self.applyGadget(bestcomplexity[1]) == None: raise Exception, "This shouldn't happen" return bestcomplexity[0] #POP TMP|REG/SUB|ADD REG, TMP (CONTEXT) #POP REG/POP TMP/SUB|ADD REG, TMP #XXX: TODO def __find_good_op_chars(self, op, C, A=None, B=None): """ find a pair of values so that doing A <operation> B = C. Where A and B are assured to be outside the badchars blacklist. """ pass