def search(qtype, arg1, arg2, constraint, assertion, n=1, clmax=LMAX, enablePreConds=False, \ record=None, noPadding=False, comment=None, maxdepth=4): """ Searches for gadgets enablePreConds : return couples (GAdget, preCond) record : stores info about the current search maxdepth : only for CSTtoREG_transitivity """ # Test clmax if (clmax <= 0): return [] # Set the search record and increase its depth of 1 if (record is None): record = SearchRecord() record.incDepth() # Search basic res = _basic(qtype, arg1, arg2, constraint.add(Chainable(ret=True)), assertion, n, clmax) # Search chaining if (len(res) < n and (qtype not in [QueryType.SYSCALL, QueryType.INT80])): res += _chain(qtype, arg1, arg2, constraint, assertion, record, n - len(res), clmax, comment) # Reset the depth of the search record record.decDepth() return sorted(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 _search_first_hit(qtype, arg1, arg2, env, n=1): """ Searches for gadgets """ # Search basic # Add Chainable constraint in env.constraint constraint = env.getConstraint() env.setConstraint(constraint.add(Chainable(ret=True))) res = _basic(qtype, arg1, arg2, env, n) # Restore normal constraint env.setConstraint(constraint) # Search chaining if (len(res) < n and (qtype not in [QueryType.SYSCALL, QueryType.INT80])): res += _chain(qtype, arg1, arg2, env, n - len(res)) return sorted(res)
def _search_first_hit(qtype, arg1, arg2, env, n=1): """ Searches for gadgets """ # Search basic res = [] res_fail = FailRecord() # Add Chainable constraint in env.constraint constraint = env.getConstraint() env.setConstraint(constraint.add(Chainable(ret=True))) basic = _basic(qtype, arg1, arg2, env, n) analyze_res(res, res_fail, basic) # Restore normal constraint env.setConstraint(constraint) # Search chaining if (len(res) < n and (qtype not in [QueryType.SYSCALL, QueryType.INT80])): chain = _chain(qtype, arg1, arg2, env, n - len(res)) analyze_res(res, res_fail, chain) return sorted(res) if res else res_fail
def MEMtoREG_transitivity(reg, arg2, constraint, assertion, n=1, clmax=LMAX): if (clmax <= 0): return [] res = [] for inter in range(0, Arch.ssaRegCount): if (inter == reg or inter in constraint.getRegsNotModified() or inter == Arch.ipNum() or inter == Arch.spNum()): continue # Find arg1 <- inter REGtoREG_record = SearchRecord(maxdepth=4) REGtoREG_record.unusable_REGtoREG.append(reg) inter_to_reg = search(QueryType.REGtoREG, reg, (inter, 0), constraint, assertion, n, clmax - 1, record=REGtoREG_record) if (inter_to_reg): len_min = min([len(chain) for chain in inter_to_reg]) # Try to find inter <- arg2 # First strategy basic arg2_to_inter = _basic(QueryType.MEMtoREG, inter, arg2, constraint.add(Chainable(ret=True)), assertion, n, clmax - len_min) res += [chain1.addChain(chain2, new=True) for chain1 in arg2_to_inter \ for chain2 in inter_to_reg if len(chain1)+len(chain2) <= clmax ] # Second strategy read reg (TODO) if (len(res) < n): pass # Did we get enough chains ? if (len(res) >= n): return res # Return the best we got return res
def build_dshell(shellcode, constraint, assertion, address, limit, lmax): """ Returns a PwnChain() instance or None """ # Build exploit ################# res = PwnChain() #Find address for the payload if (not address): # Get the .bss address # TODO notify("Getting delivery address for shellcode") address = getSectionAddress('.bss') addr_str = ".bss" if (not address): verbose("Couldn't find .bss address") return [] else: addr_str = hex(address) if (not limit): limit = address + Arch.minPageSize() # Deliver shellcode notify("Building chain to copy shellcode in memory") verbose("{}/{} bytes available".format(lmax * Arch.octets(), lmax * Arch.octets())) (shellcode_address, STRtoMEM_chain) = STRtoMEM(shellcode, address, constraint, assertion, limit=limit, lmax=lmax, addr_str=addr_str, hex_info=True, optimizeLen=True) address = shellcode_address addr_str = hex(address) if (not STRtoMEM_chain): verbose("Could not copy shellcode into memory") return None # Building mprotect notify("Building mprotect() chain") # Getting page to make executable # Arg of mprotect MUST be a valid multiple of page size over_page_size = address % Arch.minPageSize() page_address = address - over_page_size length = len(shellcode) + 1 + over_page_size flag = 7 lmax2 = lmax - len(STRtoMEM_chain) verbose("{}/{} bytes available".format(lmax2 * Arch.octets(), lmax * Arch.octets())) if (lmax2 <= 0): return None if (Arch.currentArch == Arch.ArchX86): mprotect_chain = build_mprotect32(page_address, length, flag, constraint.add(Chainable(ret=True)), assertion, clmax=lmax2 - 2, optimizeLen=True) elif (Arch.currentArch == Arch.ArchX64): mprotect_chain = build_mprotect64(page_address, length, flag, constraint.add(Chainable(ret=True)), assertion, clmax=lmax2 - 2, optimizeLen=True) else: mprotect_chain = None verbose("mprotect call not supported for architecture {}".format( Arch.currentArch.name)) return None if (not mprotect_chain): return None verbose("Done") # Jump to shellcode notify("Searching chain to jump to shellcode") verbose("{}/{} bytes available".format( (lmax2 - len(mprotect_chain)) * Arch.octets(), lmax * Arch.octets())) jmp_shellcode_chains = search(QueryType.CSTtoREG, Arch.ipNum(), address, constraint, assertion, clmax=lmax - len(STRtoMEM_chain) - len(mprotect_chain), optimizeLen=True) if (not jmp_shellcode_chains): verbose("Couldn't find a jump to the shellcode") return None verbose("Done") notify("Done") # Build PwnChain res and return res.add(mprotect_chain, "Call mprotect({},{},{})".format(hex(page_address), length, flag)) res.add(STRtoMEM_chain, "Copy shellcode to {}".format(addr_str)) res.add(jmp_shellcode_chains[0], "Jump to shellcode (address {})".format(addr_str)) return res
def _adjust_ret(qtype, arg1, arg2, env, n): """ Search with basic but adjust the bad returns they have """ global LMAX ID = StrategyType.ADJUST_RET ## Test for special cases # Test lmax if (env.getLmax() <= 0): return FailRecord(lmax=True) # Limit number of calls to ... elif (env.nbCalls(ID) >= 2): return FailRecord() # Test for ip # Reason: can not adjust ip if ip is the # target of the query :/ elif (arg1 == Arch.ipNum()): return FailRecord() # Set env env.addCall(ID) saved_adjust_ret = env.getImpossible_adjust_ret().copy() ######################################## res = [] res_fail = FailRecord() padding = env.getConstraint().getValidPadding(Arch.octets()) # Get possible gadgets constraint = env.getConstraint() env.setConstraint(constraint.add(Chainable(jmp=True, call=True))) possible = _basic(qtype, arg1, arg2, env, 10 * n) env.setConstraint(constraint) if (not possible): res_fail.merge(possible) possible = [] # Try to adjust them for chain in possible: g = chain.chain[0] ret_reg = g.retValue.reg.num # Check if we already know that ret_reg can't be adjusted if (env.checkImpossible_adjust_ret(ret_reg)): continue #Check if ret_reg not modified within the gadget elif (ret_reg in g.modifiedRegs()): continue # Check if stack is preserved elif (g.spInc is None): continue # Find adjustment if (g.spInc < 0): offset = -1 * g.spInc padding_length = 0 else: padding_length = g.spInc / Arch.octets() if (g.retType == RetType.JMP): offset = 0 else: offset = Arch.octets() if (isinstance(arg1, int)): arg1_reg = arg1 else: arg1_reg = arg1[0] # Get adjustment gadgets env.setConstraint(constraint.add(RegsNotModified([arg1_reg]))) saved_lmax = env.getLmax() env.setLmax(LMAX) adjust_gadgets = _search(QueryType.MEMtoREG, Arch.ipNum(), \ (Arch.spNum(),offset), env, n=1) env.setConstraint(constraint) env.setLmax(saved_lmax) if (not adjust_gadgets): res_fail.merge(adjust_gadgets) continue else: adjust_addr = int(validAddrStr(adjust_gadgets[0].chain[0],\ constraint.getBadBytes(), Arch.bits()), 16) # Find gadgets to put the gadget address in the jmp/call register if (isinstance(arg2, int)): arg2_reg = arg2 else: arg2_reg = arg2[0] env.setConstraint(constraint.add(RegsNotModified([arg2_reg]))) env.subLmax(1 + padding_length) env.pushComment( StrategyType.CSTtoREG_POP, "Address of " + string_bold(str(adjust_gadgets[0].chain[0]))) adjust = _search(QueryType.CSTtoREG, ret_reg, adjust_addr, env, n=1) env.popComment(StrategyType.CSTtoREG_POP) env.addLmax(1 + padding_length) env.setConstraint(constraint) if (adjust): res.append(adjust[0].addGadget(g).addPadding(padding, n=padding_length)) if (len(res) >= n): break else: # Update the search record to say that reg_ret cannot be adjusted env.addImpossible_adjust_ret(ret_reg) res_fail.merge(adjust) ######################################## # Restore env env.impossible_adjust_ret = saved_adjust_ret 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 store_constant_address(qtype, cst_addr, value, constraint=None, assertion=None, clmax=None, optimizeLen=False): """ Does a XXXtoMEM kind of query BUT the memory address is a simple constant ! Expected qtypes are only XXXtoMEM cst_addr is the store address value is the value to store, a single cst or a couple (reg,cst) """ if (clmax is None): clmax = STORE_CONSTANT_ADDRESS_LMAX elif (clmax <= 0): return None if (constraint is None): constr = Constraint() else: constr = constraint if (assertion is None): a = Assertion() else: a = assertion # Tranform the query type if (qtype == QueryType.CSTtoMEM): qtype2 = QueryType.CSTtoREG elif (qtype == QueryType.REGtoMEM): qtype2 = QueryType.REGtoREG elif (qtype == QueryType.MEMtoREG): qtype2 = QueryType.MEMtoREG else: raise Exception( "Query type {} should not appear in this function!".format(qtype)) tried_values = [] tried_cst_addr = [] best = None # If optimizeLen shortest = clmax # Shortest ROPChain found if optimizeLen ;) for ((addr_reg, addr_cst), (reg,cst), gadget) in \ sorted(DBAllPossibleWrites(constr.add(Chainable(ret=True)), a), \ key=lambda x: 0 if (x[1] == value) else 1) : # DOn't use rip or rsp... if( reg == Arch.ipNum() or reg == Arch.spNum()\ or addr_reg == Arch.ipNum() or addr_reg == Arch.spNum()): continue res = None # Check if directly the registers we want to write ;) value_is_reg = False value_to_reg = [] addr_to_reg = [] if ((reg, cst) == value): value_to_reg = [ROPChain()] value_is_reg = True # adapt value if (not isinstance(value, tuple)): adjusted_value = value - cst else: adjusted_value = (value[0], value[1] - cst) adjusted_cst_addr = cst_addr - addr_cst # Get spInc gadget_paddingLen = (gadget.spInc / Arch.octets()) - 1 # Check if tried before if ((reg, cst) in tried_values): continue elif ((addr_reg, addr_cst) in tried_cst_addr): continue ### Try to do reg first then addr_reg # Try to put the value into reg clmax2 = shortest - gadget_paddingLen - 1 if (not value_is_reg): value_to_reg = search(qtype2, reg, adjusted_value, constr, a, clmax=clmax2, n=1, optimizeLen=optimizeLen) if (not value_to_reg): tried_values.append((reg, cst)) continue else: clmax2 = clmax2 - len(value_to_reg[0]) # Try to put the cst_addr in addr_reg addr_to_reg = search(QueryType.CSTtoREG, addr_reg, adjusted_cst_addr, constr.add(RegsNotModified([reg])), a, clmax=clmax2, n=1, optimizeLen=optimizeLen) if (addr_to_reg): # If we found a solution # Combine them and return # Padd the gadget res = value_to_reg[0].addChain(addr_to_reg[0]).addGadget(gadget) if (gadget.spInc > 0): padding_value = constr.getValidPadding(Arch.octets()) res = res.addPadding(padding_value, n=(gadget.spInc / Arch.octets()) - 1) if (optimizeLen): if (best): best = min(best, res) else: best = res shortest = len(best) else: return res ### Try to do addr_reg first and then reg clmax2 = shortest - gadget_paddingLen - 1 # Try to put the cst_addr in addr_reg addr_to_reg = search(QueryType.CSTtoREG, addr_reg, adjusted_cst_addr, constr, a, clmax=clmax2, n=1, optimizeLen=optimizeLen) if (not addr_to_reg): tried_cst_addr.append((addr_reg, addr_cst)) continue else: clmax2 = clmax2 - len(addr_to_reg[0]) # Try to put the value into reg if (not value_is_reg): value_to_reg = search(qtype2, reg, adjusted_value, constr.add(RegsNotModified([addr_reg])), a, clmax=clmax2, n=1, optimizeLen=optimizeLen) if (value_to_reg): # If we found a solution # Combine them and return # Padd the gadget res = addr_to_reg[0].addChain(value_to_reg[0]).addGadget(gadget) if (gadget.spInc > 0): padding_value = constr.getValidPadding(Arch.octets()) res = res.addPadding(padding_value, n=(gadget.spInc / Arch.octets()) - 1) if (optimizeLen): if (best): best = min(best, res) else: best = res shortest = len(best) else: return res # 5 = two pops for addr_reg and reg + 1 for the write gadget # So since 5 is the shortest possible with two pops we can return # We can have < 5 if reg is already equal to 'value' argument # But we try this case first (see sorted()) when getting possibleWrites ;) if (((not optimizeLen) or (not value_is_reg)) and (not best is None) and len(best) <= 5): return best elif (optimizeLen and (not best is None) and len(best) <= 3): return best return best
def _adjust_ret(qtype, arg1, arg2, constraint, assertion, n, clmax=LMAX, record=None, comment=""): """ Search with basic but adjust the bad returns they have """ # Test clmax if (clmax <= 0): return [] # Test for ip if (arg1 == Arch.ipNum()): return [] # Test for search record if (record is None): record = SearchRecord() res = [] possible = _basic(qtype, arg1, arg2, \ constraint.add(Chainable(jmp=True, call=True)), assertion, n) padding = constraint.getValidPadding(Arch.currentArch.octets) for chain in possible: g = chain.chain[0] ret_reg = g.retValue.reg.num # Check if we already know that ret_reg can't be adjusted if (record.impossible_AdjustRet.check(ret_reg)): continue #Check if ret_reg not modified within the gadget if (ret_reg in g.modifiedRegs()): continue # Check if stack is preserved if (g.spInc is None): continue # Find adjustment if (g.spInc < 0): offset = -1 * g.spInc padding_length = 0 else: padding_length = g.spInc if (g.retType == RetType.JMP): offset = 0 else: offset = Arch.octets() adjust_gadgets = search(QueryType.MEMtoREG, Arch.ipNum(), \ (Arch.spNum(),offset), constraint.add(RegsNotModified([arg1])), assertion, n=1, record=record) if (not adjust_gadgets): continue else: adjust_addr = int(validAddrStr(adjust_gadgets[0].chain[0],\ constraint.getBadBytes(), Arch.bits()), 16) # Put the gadget address in the register adjust = search(QueryType.CSTtoREG, ret_reg, adjust_addr, \ constraint.add(RegsNotModified([arg2[0]])), assertion, n=1, clmax=clmax-len(chain),record=record,\ comment="Address of "+string_bold(str(adjust_gadgets[0].chain[0]))) if (adjust): res.append(adjust[0].addGadget(g).addPadding(padding, n=padding_length)) if (len(res) >= n): return res else: # Update the search record to say that reg_ret cannot be adjusted record.impossible_AdjustRet.add(ret_reg) return res