def CSTtoMEM_write(arg1, cst, constraint, assertion, n=1, clmax=LMAX): """ reg <- cst mem(arg2) <- reg """ if (clmax <= 0): return [] res = [] addr_reg = arg1[0] addr_cst = arg1[1] # 1. First strategy (direct) # reg <- cst # mem(arg1) <- reg for reg in range(0, Arch.ssaRegCount): if (reg == Arch.ipNum() or reg == Arch.spNum() or reg == addr_reg): continue # Find reg <- cst # maxdepth 3 or it's too slow cst_to_reg_chains = search(QueryType.CSTtoREG, reg, cst, constraint.add(RegsNotModified([addr_reg])), assertion, n, clmax - 1, maxdepth=3) if (not cst_to_reg_chains): continue # Search for mem(arg1) <- reg # We get all reg2,cst2 s.t mem(arg1) <- reg2+cst2 possible_mem_writes = DBPossibleMemWrites(addr_reg, addr_cst, constraint, assertion, n=1) # 1.A. Ideally we look for reg2=reg and cst2=0 (direct_writes) possible_mem_writes_reg = possible_mem_writes.get(reg) if (possible_mem_writes_reg): direct_writes = possible_mem_writes[reg].get(0, []) else: direct_writes = [] padding = constraint.getValidPadding(Arch.octets()) for write_gadget in direct_writes: for cst_to_reg_chain in cst_to_reg_chains: # Pad the gadgets write_chain = ROPChain([write_gadget]) for i in range(0, write_gadget.spInc - Arch.octets(), Arch.octets()): write_chain.addPadding(padding) full_chain = cst_to_reg_chain.addChain(write_chain, new=True) if (len(full_chain) <= clmax): res.append(full_chain) if (len(res) >= n): return res # 1.B. return res
def _CSTtoREG_pop(reg, cst, constraint, assertion, n=1, clmax=LMAX, comment=None): """ Returns a payload that puts cst into register reg by poping it from the stack """ # Test clmax if (clmax <= 0): return [] # Test n if (n < 1): return [] # Check if the cst is incompatible with the constraint if (not constraint.badBytes.verifyAddress(cst)): return [] if (not comment): comment = "Constant: " + string_bold("0x{:x}".format(cst)) # Direct pop from the stack res = [] if (reg == Arch.ipNum()): constraint2 = constraint.remove([CstrTypeID.CHAINABLE]) else: constraint2 = constraint.add(Chainable(ret=True)) possible = DBPossiblePopOffsets(reg, constraint2, assertion) for offset in sorted(filter(lambda x: x >= 0, possible.keys())): # If offsets are too big to fit in the lmax just break if (offset > clmax * Arch.octets()): break # Get possible gadgets possible_gadgets = [g for g in possible[offset]\ if g.spInc >= Arch.octets() \ and g.spInc - Arch.octets() > offset \ and (g.spInc/Arch.octets()-1) <= clmax] # Test if padding is too much for clmax # Pad the gadgets padding = constraint.getValidPadding(Arch.octets()) for gadget in possible_gadgets: chain = ROPChain([gadget]) for i in range(0, gadget.spInc - Arch.octets(), Arch.octets()): if (i == offset): chain.addPadding(cst, comment) else: chain.addPadding(padding) if (len(chain) <= clmax): res.append(chain) if (len(res) >= n): return res return res
def build_call_linux64(funcName, funcArgs, constraint, assertion, clmax=None, optimizeLen=False): # Arguments registers # (Args should go in these registers for x64) argsRegsNames = ['rdi','rsi','rdx','rcx', 'r8', 'r9'] argsRegs = [Arch.n2r(name) for name in argsRegsNames] # Find the address of the fonction (funcName2, funcAddr) = getFunctionAddress(funcName) if( funcName2 is None ): return "Couldn't find function '{}' in the binary".format(funcName) # Check if bad bytes in function address if( not constraint.badBytes.verifyAddress(funcAddr) ): return "'{}' address ({}) contains bad bytes".format(funcName2, string_special('0x'+format(funcAddr, '0'+str(Arch.octets()*2)+'x'))) # Check how many arguments if( len(funcArgs) > 6 ): return "Doesn't support function call with more than 6 arguments with Linux X64 calling convention :(" # Find a gadget for the fake return address if( funcArgs ): # Build the ropchain with the arguments args_chain = popMultiple(map(lambda x,y:(x,)+y, argsRegs[:len(funcArgs)], funcArgs), constraint, assertion, clmax=clmax, optimizeLen=optimizeLen) if( not args_chain): return "Couldn't load arguments in registers" else: # No arguments args_chain = ROPChain() # Build call chain (function address + fake return address) return args_chain.addPadding(funcAddr, comment=string_ropg(funcName2))
def build_call(funcName, funcArgs, constraint, assertion): # Find the address of the fonction (funcName2, funcAddr) = getFunctionAddress(funcName) if (funcName2 is None): return "Couldn't find function '{}' in the binary".format(funcName) # Check if bad bytes in function address if (not constraint.badBytes.verifyAddress(funcAddr)): return "'{}' address ({}) contains bad bytes".format( funcName2, string_special('0x' + format(funcAddr, '0' + str(Arch.octets() * 2) + 'x'))) # Find a gadget for the fake return address offset = len( funcArgs) * 8 - 8 # Because we do +8 at the beginning of the loop skip_args_chains = [] i = 4 while (i > 0 and (not skip_args_chains)): offset += 8 skip_args_chains = search(QueryType.MEMtoREG, Arch.ipNum(), \ (Arch.spNum(),offset), constraint, assertion, n=1) i -= 1 if (not skip_args_chains): return "Couldn't build ROP-Chain" skip_args_chain = skip_args_chains[0] # Build the ropchain with the arguments args_chain = ROPChain() arg_n = len(funcArgs) for arg in reversed(funcArgs): if (isinstance(arg, int)): args_chain.addPadding(arg, comment="Arg{}: {}".format( arg_n, string_ropg(hex(arg)))) arg_n -= 1 else: return "Type of argument '{}' not supported yet :'(".format(arg) # Build call chain (function address + fake return address) call_chain = ROPChain() call_chain.addPadding(funcAddr, comment=string_ropg(funcName2)) skip_args_addr = int( validAddrStr(skip_args_chain.chain[0], constraint.getBadBytes(), Arch.bits()), 16) call_chain.addPadding(skip_args_addr, comment="Address of: " + string_bold(str(skip_args_chain.chain[0]))) return call_chain.addChain(args_chain)
def build_call_linux86(funcName, funcArgs, constraint, assertion, clmax=None, optimizeLen=False): # Find the address of the fonction (funcName2, funcAddr) = getFunctionAddress(funcName) if( funcName2 is None ): return "Couldn't find function '{}' in the binary".format(funcName) # Check if bad bytes in function address if( not constraint.badBytes.verifyAddress(funcAddr) ): return "'{}' address ({}) contains bad bytes".format(funcName2, string_special('0x'+format(funcAddr, '0'+str(Arch.octets()*2)+'x'))) # Check if lmax too small if( (1 + len(funcArgs) + (lambda x: 1 if len(x)>0 else 0)(funcArgs)) > clmax ): return "Not enough bytes to call function '{}'".format(funcName) # Find a gadget for the fake return address if( funcArgs ): offset = (len(funcArgs)-1)*Arch.octets() # Because we do +octets() at the beginning of the loop skip_args_chains = [] i = 4 # Try 4 more maximum while( i > 0 and (not skip_args_chains)): offset += Arch.octets() skip_args_chains = search(QueryType.MEMtoREG, Arch.ipNum(), \ (Arch.spNum(),offset), constraint, assertion, n=1, optimizeLen=optimizeLen) i -= 1 if( not skip_args_chains ): return "Couldn't build ROP-Chain" skip_args_chain = skip_args_chains[0] else: # No arguments skip_args_chain = None # Build the ropchain with the arguments args_chain = ROPChain() arg_n = len(funcArgs) for arg in reversed(funcArgs): if( isinstance(arg, int) ): args_chain.addPadding(arg, comment="Arg{}: {}".format(arg_n, string_ropg(hex(arg)))) arg_n -= 1 else: return "Type of argument '{}' not supported yet :'(".format(arg) # Build call chain (function address + fake return address) call_chain = ROPChain() call_chain.addPadding(funcAddr, comment=string_ropg(funcName2)) if( funcArgs ): skip_args_addr = int( validAddrStr(skip_args_chain.chain[0], constraint.getBadBytes(), Arch.bits()) ,16) call_chain.addPadding(skip_args_addr, comment="Address of: "+string_bold(str(skip_args_chain.chain[0]))) return call_chain.addChain(args_chain)
def _CSTtoMEM_write(arg1, cst, env, n=1): """ reg <- cst mem(arg2) <- reg """ ID = StrategyType.CSTtoMEM_WRITE ## Test for special cases # Test lmax if (env.getLmax() <= 0): return FailRecord(lmax=True) # Limit number of calls to ... elif (env.nbCalls(ID) >= 99): return FailRecord() # Set env env.addCall(ID) ###################################### res = [] res_fail addr_reg = arg1[0] addr_cst = arg1[1] # 1. First strategy (direct) # reg <- cst # mem(arg1) <- reg for reg in Arch.registers(): if (reg == Arch.ipNum() or reg == Arch.spNum() or reg == addr_reg): continue # Find reg <- cst constraint = env.getConstraint() env.setConstraint(constraint.add(RegsNotModified([addr_reg]))) env.subLmax(1) cst_to_reg_chains = _search(QueryType.CSTtoREG, reg, cst, env, n) env.addLmax(1) env.setConstraint(constraint) if (not cst_to_reg_chains): res_fail.merge(cst_to_reg_chains) continue # Search for mem(arg1) <- reg # We get all reg2,cst2 s.t mem(arg1) <- reg2+cst2 possible_mem_writes = DBPossibleMemWrites(addr_reg, addr_cst, env.getConstraint(), env.getAssertion(), n=1) # 1.A. Ideally we look for reg2=reg and cst2=0 (direct_writes) possible_mem_writes_reg = possible_mem_writes.get(reg) if (possible_mem_writes_reg): direct_writes = possible_mem_writes[reg].get(0, []) else: direct_writes = [] padding = constraint.getValidPadding(Arch.octets()) for write_gadget in direct_writes: for cst_to_reg_chain in cst_to_reg_chains: # Pad the gadgets write_chain = ROPChain([write_gadget]) for i in range(0, write_gadget.spInc - Arch.octets(), Arch.octets()): write_chain.addPadding(padding) full_chain = cst_to_reg_chain.addChain(write_chain, new=True) if (len(full_chain) <= env.getLmax()): res.append(full_chain) if (len(res) >= n): break if (len(res) >= n): break if (len(res) >= n): break # 1.B. Otherwise we try to adjust the cst2 # To be implemented # 2d Strategy: indirect # reg <- arg2 - cst # mem(arg1) <- reg + cst # TO IMPLEMENT IN ANOTHER FUNCTION ! ################### # Restore env env.removeCall(ID) return res if res else res_fail
def _CSTtoREG_pop(reg, cst, env, n=1): """ Returns a payload that puts cst into register reg by poping it from the stack """ ID = StrategyType.CSTtoREG_POP ## Test for special cases # Test lmax if (env.getLmax() <= 0): return FailRecord(lmax=True) # Limit number of calls to ... elif (env.nbCalls(ID) >= 99): return FailRecord() # Check if the cst is in badBytes elif (not env.getConstraint().badBytes.verifyAddress(cst)): return FailRecord() # Set env env.addCall(ID) # Get comment if (env.hasComment(ID)): envHadComment = True comment = env.popComment(ID) else: envHadComment = False comment = "Constant: " + string_bold("0x{:x}".format(cst)) ######################## # Direct pop from the stack res = [] res_fail = FailRecord() # Adapt constraint if ip <- cst if (reg != Arch.ipNum()): constraint2 = env.getConstraint().add(Chainable(ret=True)) else: constraint2 = env.getConstraint() possible = DBPossiblePopOffsets(reg, constraint2, env.getAssertion()) for offset in sorted(filter(lambda x: x >= 0, possible.keys())): # If offsets are too big to fit in the lmax just break if (offset > env.getLmax() * Arch.octets()): break # Get possible gadgets possible_gadgets = [g for g in possible[offset]\ if g.spInc >= Arch.octets() \ and g.spInc - Arch.octets() > offset \ and (g.spInc/Arch.octets()-1) <= env.getLmax()] # Test if padding is too much for clmax # Pad the gadgets padding = env.getConstraint().getValidPadding(Arch.octets()) for gadget in possible_gadgets: chain = ROPChain([gadget]) for i in range(0, gadget.spInc - Arch.octets(), Arch.octets()): if (i == offset): chain.addPadding(cst, comment) else: chain.addPadding(padding) if (len(chain) <= env.getLmax()): res.append(chain) if (len(res) >= n): break if (len(res) >= n): break ######################### # Restore env env.removeCall(ID) if (envHadComment): env.pushComment(ID, comment) return res if res else res_fail
def _basic(qtype, arg1, arg2, env, n=1, enablePreConds=False): """ Search for gadgets basic method ( without chaining ) Direct Database check """ if (env.getLmax() <= 0): return FailRecord(lmax=True) if (env.getNoPadding()): maxSpInc = None else: maxSpInc = env.getLmax() * Arch.octets() # Check for special gadgets if (qtype == QueryType.INT80 or qtype == QueryType.SYSCALL): gadgets = DBSearch(qtype, arg1, arg2, env.getConstraint(), env.getAssertion(), n=n, maxSpInc=maxSpInc) res = [ROPChain().addGadget(g) for g in gadgets] return res # Check if the type is IP <- ... # In this case we remove the CHAINABLE constraint which makes no sense if (arg1 == Arch.ipNum()): constraint2 = env.getConstraint().remove([CstrTypeID.CHAINABLE]) else: constraint2 = env.getConstraint() # Check to add assertions when looking for Memory gadgets if (qtype == QueryType.CSTtoMEM or qtype == QueryType.REGtoMEM): assertion2 = env.getAssertion().add( RegsNoOverlap([(arg1[0], Arch.spNum())])) else: assertion2 = env.getAssertion() # Regular gadgets # maxSpInc -> +1 because we don't count the ret but -1 because the gadget takes one place gadgets = DBSearch(qtype, arg1, arg2, constraint2, assertion2, n, enablePreConds=enablePreConds, maxSpInc=maxSpInc) if (enablePreConds): return [(ROPChain().addGadget(g[0]), g[1]) for g in gadgets] elif (env.getNoPadding()): return [ROPChain().addGadget(g) for g in gadgets] else: res = [] padding = constraint2.getValidPadding(Arch.octets()) for g in gadgets: chain = ROPChain().addGadget(g) # Padding the chain if possible if (g.spInc > 0): for i in range(0, g.spInc / Arch.octets() - 1): chain.addPadding(padding) # Adding to the result res.append(chain) if (len(res) == 0): return FailRecord() else: return res
def _basic(qtype, arg1, arg2, constraint, assertion, n=1, clmax=LMAX, noPadding=False): """ Search for gadgets basic method ( without chaining ) Direct Database check """ # Test clmax if (clmax <= 0): return [] if (not noPadding): maxSpInc = clmax * Arch.octets() else: maxSpInc = None # Check for special gadgets if (qtype == QueryType.INT80 or qtype == QueryType.SYSCALL): gadgets = DBSearch(qtype, arg1, arg2, constraint, assertion, n=1, maxSpInc=maxSpInc) res = [ROPChain().addGadget(g) for g in gadgets] return res # Check if the type is IP <- ... # In this case we remove the CHAINABLE constraint which makes no sense if (arg1 == Arch.ipNum()): constraint2 = constraint.remove([CstrTypeID.CHAINABLE]) else: constraint2 = constraint # Check to add assertions when looking for Memory gadgets if (qtype == QueryType.CSTtoMEM or qtype == QueryType.REGtoMEM): assertion2 = assertion.add(RegsNoOverlap([(arg1[0], Arch.spNum())])) else: assertion2 = assertion # Regular gadgets # maxSpInc -> +1 because we don't count the ret but -1 because the gadget takes one place gadgets = DBSearch(qtype, arg1, arg2, constraint2, assertion2, n, maxSpInc=maxSpInc) if (noPadding): return [ROPChain().addGadget(g) for g in gadgets] else: res = [] padding = constraint2.getValidPadding(Arch.currentArch.octets) for g in gadgets: chain = ROPChain().addGadget(g) # Padding the chain if possible if (g.spInc > 0): for i in range(0, g.spInc / Arch.octets() - 1): chain.addPadding(padding) # Adding to the result res.append(chain) return res
def STRtoMEM_memcpy(string, addr, constraint, assertion, lmax=STR_TO_MEM_LMAX, addr_str=None, hex_info=False): """ MEMCPY STRATEGY Copy the string using memcpy function """ if (not addr_str): addr_str = "0x" + format(addr, '0' + str(Arch.octets() * 2) + 'x') # Getting strcpy function (func_name, func_addr) = getFunctionAddress('memcpy') if (not func_addr): verbose('Could not find memcpy function') return None elif (not constraint.badBytes.verifyAddress(func_addr)): verbose("memcpy address ({}) contains bad bytes".format( hex(func_addr))) return None # We decompose the string in substrings to be copied substrings_addr = findBytes(string, badBytes=constraint.getBadBytes()) if (not substrings_addr): return None elif (len(substrings_addr) * 5 > lmax): verbose( "Memcpy: ROP-Chain too long (length: {}, available bytes: {}) ". format( len(substrings_addr) * 5 * Arch.octets(), lmax * Arch.octets())) return None # Get a pop-pop-pop-ret gadget pppr_chains = _basic(QueryType.MEMtoREG, Arch.ipNum(), [Arch.spNum(), 3 * Arch.octets()], constraint.add( StackPointerIncrement(4 * Arch.octets())), assertion, clmax=1, noPadding=True) if (not pppr_chains): verbose("Memcpy: Could not find suitable pop-pop-pop-ret gadget") return None pppr_gadget = pppr_chains[0].chain[0] # Get the first gadget # Build chain res = ROPChain() offset = 0 custom_stack = addr for (substring_addr, substring_str) in substrings_addr: if (hex_info): substring_info = "'" + '\\x' + '\\x'.join( ["%02x" % ord(c) for c in substring_str]) + "'" else: substring_info = "'" + substring_str + "'" res.addPadding(func_addr, comment=string_ropg(func_name)) res.addGadget(pppr_gadget) res.addPadding(len(substring_str), comment="Arg3: " + string_ropg(str(len(substring_str)))) res.addPadding(substring_addr, comment="Arg2: " + string_ropg(substring_info)) res.addPadding(custom_stack, comment="Arg1: " + string_ropg("{} + {}".format(addr_str, offset))) # Adjust custom_stack = custom_stack + len(substring_str) offset = offset + len(substring_str) return res