def get_filter_constraints(self, filter_opts): res = list() for opt in filter_opts: if opt["type"] == "limit_symbol": claripy_obj = self.symbols[opt["symbol_id"]].claripy_obj expr = claripy.And(claripy.SGE(claripy_obj, int(opt["min"])), claripy.SLE(claripy_obj, int(opt["max"]))) res.append(expr) return res
def check_symbol_range(state, range_symbols): for sid in range_symbols: symbol, rmin, rmax = range_symbols[sid] if not state.solver.satisfiable(extra_constraints=[ claripy.And( claripy.SGE(symbol, rmin), claripy.SLE(symbol, rmax) ) ]): return False return True
def claripy_ast_from_ail_condition(self, condition) -> claripy.ast.Base: # Unpack a condition all the way to the leaves if isinstance(condition, claripy.ast.Base): # pylint:disable=isinstance-second-argument-not-valid-type return condition def _op_with_unified_size(op, conv, operand0, operand1): # ensure operand1 is of the same size as operand0 if isinstance(operand1, ailment.Expr.Const): # amazing - we do the eazy thing here return op(conv(operand0), operand1.value) if operand1.bits == operand0.bits: return op(conv(operand0), conv(operand1)) # extension is required assert operand1.bits < operand0.bits operand1 = ailment.Expr.Convert(None, operand1.bits, operand0.bits, False, operand1) return op(conv(operand0), conv(operand1)) _mapping = { 'LogicalAnd': lambda expr, conv: claripy.And(conv(expr.operands[0]), conv(expr.operands[1])), 'LogicalOr': lambda expr, conv: claripy.Or(conv(expr.operands[0]), conv(expr.operands[1])), 'CmpEQ': lambda expr, conv: conv(expr.operands[0]) == conv(expr.operands[1]), 'CmpNE': lambda expr, conv: conv(expr.operands[0]) != conv(expr.operands[1]), 'CmpLE': lambda expr, conv: conv(expr.operands[0]) <= conv(expr.operands[1]), 'CmpLEs': lambda expr, conv: claripy.SLE(conv(expr.operands[0]), conv(expr.operands[1])), 'CmpLT': lambda expr, conv: conv(expr.operands[0]) < conv(expr.operands[1]), 'CmpLTs': lambda expr, conv: claripy.SLT(conv(expr.operands[0]), conv(expr.operands[1])), 'CmpGE': lambda expr, conv: conv(expr.operands[0]) >= conv(expr.operands[1]), 'CmpGEs': lambda expr, conv: claripy.SGE(conv(expr.operands[0]), conv(expr.operands[1])), 'CmpGT': lambda expr, conv: conv(expr.operands[0]) > conv(expr.operands[1]), 'CmpGTs': lambda expr, conv: claripy.SGT(conv(expr.operands[0]), conv(expr.operands[1])), 'Add': lambda expr, conv: conv(expr.operands[0]) + conv(expr.operands[1]), 'Sub': lambda expr, conv: conv(expr.operands[0]) - conv(expr.operands[1]), 'Mul': lambda expr, conv: conv(expr.operands[0]) * conv(expr.operands[1]), 'Not': lambda expr, conv: claripy.Not(conv(expr.operand)), 'Xor': lambda expr, conv: conv(expr.operands[0]) ^ conv(expr.operands[1]), 'And': lambda expr, conv: conv(expr.operands[0]) & conv(expr.operands[1]), 'Or': lambda expr, conv: conv(expr.operands[0]) | conv(expr.operands[1]), 'Shr': lambda expr, conv: _op_with_unified_size(claripy.LShR, conv, expr.operands[0], expr.operands[1]), 'Shl': lambda expr, conv: _op_with_unified_size(operator.lshift, conv, expr.operands[0], expr.operands[1]), 'Sar': lambda expr, conv: _op_with_unified_size(operator.rshift, conv, expr.operands[0], expr.operands[1]), } if isinstance(condition, (ailment.Expr.Load, ailment.Expr.DirtyExpression, ailment.Expr.BasePointerOffset, ailment.Expr.ITE, ailment.Stmt.Call)): var = claripy.BVS('ailexpr_%s' % repr(condition), condition.bits, explicit_name=True) self._condition_mapping[var.args[0]] = condition return var elif isinstance(condition, ailment.Expr.Register): var = claripy.BVS('ailexpr_%s-%d' % (repr(condition), condition.idx), condition.bits, explicit_name=True) self._condition_mapping[var.args[0]] = condition return var elif isinstance(condition, ailment.Expr.Convert): # convert is special. if it generates a 1-bit variable, it should be treated as a BVS if condition.to_bits == 1: var_ = self.claripy_ast_from_ail_condition(condition.operands[0]) name = 'ailcond_Conv(%d->%d, %s)' % (condition.from_bits, condition.to_bits, repr(var_)) var = claripy.BoolS(name, explicit_name=True) else: var_ = self.claripy_ast_from_ail_condition(condition.operands[0]) name = 'ailexpr_Conv(%d->%d, %s)' % (condition.from_bits, condition.to_bits, repr(var_)) var = claripy.BVS(name, condition.to_bits, explicit_name=True) self._condition_mapping[var.args[0]] = condition return var elif isinstance(condition, ailment.Expr.Const): var = claripy.BVV(condition.value, condition.bits) return var elif isinstance(condition, ailment.Expr.Tmp): l.warning("Left-over ailment.Tmp variable %s.", condition) if condition.bits == 1: var = claripy.BoolV('ailtmp_%d' % condition.tmp_idx) else: var = claripy.BVS('ailtmp_%d' % condition.tmp_idx, condition.bits, explicit_name=True) self._condition_mapping[var.args[0]] = condition return var lambda_expr = _mapping.get(condition.verbose_op, None) if lambda_expr is None: raise NotImplementedError("Unsupported AIL expression operation %s. Consider implementing." % condition.op) r = lambda_expr(condition, self.claripy_ast_from_ail_condition) if r is NotImplemented: r = claripy.BVS("ailexpr_%r" % condition, condition.bits, explicit_name=True) self._condition_mapping[r.args[0]] = condition else: # don't lose tags r = r.annotate(TagsAnnotation(**condition.tags)) return r
def run(self, _, length): # pylint: disable=W0221 bvs = claripy.BVS("Random.nextInt", 32) cs = claripy.And(claripy.SGE(bvs, 0), claripy.SLE(bvs, length)) self.state.add_constraints(cs) return bvs
def check_state(self, state, path=None): """Check a reachable state for bugs""" logger.debug("Check state: %s", state) logger.debug("Constraints: %s", state.solver.constraints) solver = state.solver.branch() if path is None: path = [state] # Static read were we never wrote, but we know the key is not symbolic. # So we go and fetch it. for key, value in state.storage_read.items(): constraint = state.storage_read[key] == self._read_storage( state, key) solver.add(constraint) logger.debug("Add storage constraint: %s", constraint) for s in path: solver.add(list(s.env.extra_constraints())) solver.add([ s.env.caller == utils.DEFAULT_CALLER, s.env.origin == utils.DEFAULT_CALLER, ]) # Calls total_sent = sum(s.env.value for s in path) sent_constraints = [s.env.value < self.max_wei_to_send for s in path] total_received_by_me = utils.bvv(0) total_received = utils.bvv(0) for call in state.calls: # TODO: Improve delegatecall support! And make it clearer it's # delegatecall, not just based on the length. assert 6 <= len(call) <= 7 value, to, gas = call[-3:] # pylint: disable=unused-variable,invalid-name delegatecall = len(call) == 6 if delegatecall: if solver.satisfiable( extra_constraints=[to[159:0] == self.caller[159:0]]): logger.info("Found delegatecall bug.") solver.add(to[159:0] == self.caller[159:0]) return solver else: total_received_by_me += claripy.If( to[159:0] == self.caller[159:0], value, utils.bvv(0)) total_received += value solver.add(value <= total_sent + path[0].env.balance) final_balance = path[0].env.balance + total_sent - total_received # Suicide if state.selfdestruct_to is not None: constraints = [ final_balance >= self.min_wei_to_receive, state.selfdestruct_to[159:0] == self.caller[159:0], ] logger.debug("Check for selfdestruct bug with constraints %s", constraints) if solver.satisfiable(extra_constraints=constraints): logger.info("Found selfdestruct bug.") solver.add(constraints) return solver if total_received_by_me is utils.bvv(0): return logger.debug("Found calls back to caller: %s", total_received_by_me) solver.add(sent_constraints) solver.add([ claripy.SGE(final_balance, 0), total_received_by_me > total_sent, # I get more than what I sent? total_received_by_me > self.min_wei_to_receive, ]) if solver.satisfiable(): logger.info("Found call bug.") return solver
def constrain_variables(self, func, solver, stack): self.offsets = self.funcdata[func.addr] self.bounds_marked = set() self.stack = stack self.solver = solver # do some sanity checking first top = min(self.offsets) for addr in stack.addr_list: if addr < top: raise Exception("Provided vars miss an access (off the top!)") base_addr = addr while base_addr not in self.offsets: base_addr -= 1 this_offset = addr - base_addr if this_offset >= self.offsets[base_addr][0]: raise Exception( "Provided vars miss an access (between the cracks!)") i = 0 while i < len(stack.addr_list): addr = stack.addr_list[i] if addr in self.offsets: if i != 0 and self.offsets[stack.addr_list[ i - 1]][0] + stack.addr_list[i - 1] > addr: raise Exception("Provided vars have an overlap!") i += 1 continue stack.merge_up(i) # standard stuff stack.alloc_op.apply_constraints(solver) solver.add(stack.alloc_op.symval == -stack.sym_size) for op in stack.dealloc_ops: op.apply_constraints(solver) solver.add(op.symval == 0) solver.add(stack.sym_size % stack.arch.bytes == 0) solver.add(claripy.SGE(stack.sym_size, stack.conc_size)) stack.unsafe_constraints.append( claripy.SGT(stack.sym_size, stack.conc_size)) stack.unsafe_constraints.append( claripy.SGE(stack.sym_size, stack.conc_size * 2)) stack.unsafe_constraints.append( claripy.SLT(stack.sym_size, stack.conc_size * 3)) # loop through variables, add the important constraints! i = 0 while i < len(stack.addr_list): addr = stack.addr_list[i] var = stack.variables[addr] var.size = self.offsets[addr][0] fix = self.offsets[addr][1] if fix == 'TOP': var.special_top = True elif fix == 'BOTTOM': var.special_bottom = True align = self.offsets[addr][2] if align != 1: solver.add(var.sym_addr % align == 0) var.sym_link( solver, stack) # this hooks up the constrains to actual immediates # also the top/bottom fixing happens in there if i != 0: prev_var = stack.variables[stack.addr_list[i - 1]] self.mark_boundaries(prev_var, var) if i != len(stack.addr_list) - 1: next_var = stack.variables[stack.addr_list[i + 1]] self.mark_boundaries(var, next_var) # ew. ew ew ew ew ew ew!!! diff = next_var.conc_addr - var.conc_addr solver.add(claripy.SLT(var.sym_addr, var.sym_addr + diff)) if i == 0: solver.add(claripy.SLE(-stack.sym_size, var.sym_addr)) i += 1
def claripy_ast_from_ail_condition(self, condition): # Unpack a condition all the way to the leaves if isinstance(condition, claripy.ast.Base): return condition _mapping = { 'LogicalAnd': lambda expr, conv: claripy.And(conv(expr.operands[0]), conv(expr.operands[1])), 'LogicalOr': lambda expr, conv: claripy.Or(conv(expr.operands[0]), conv(expr.operands[1])), 'CmpEQ': lambda expr, conv: conv(expr.operands[0]) == conv(expr.operands[1] ), 'CmpNE': lambda expr, conv: conv(expr.operands[0]) != conv(expr.operands[1] ), 'CmpLE': lambda expr, conv: conv(expr.operands[0]) <= conv(expr.operands[1] ), 'CmpLEs': lambda expr, conv: claripy.SLE(conv(expr.operands[0]), conv(expr.operands[1])), 'CmpLT': lambda expr, conv: conv(expr.operands[0]) < conv(expr.operands[1]), 'CmpLTs': lambda expr, conv: claripy.SLT(conv(expr.operands[0]), conv(expr.operands[1])), 'CmpGE': lambda expr, conv: conv(expr.operands[0]) >= conv(expr.operands[1] ), 'CmpGEs': lambda expr, conv: claripy.SGE(conv(expr.operands[0]), conv(expr.operands[1])), 'CmpGT': lambda expr, conv: conv(expr.operands[0]) > conv(expr.operands[1]), 'CmpGTs': lambda expr, conv: claripy.SGT(conv(expr.operands[0]), conv(expr.operands[1])), 'Add': lambda expr, conv: conv(expr.operands[0]) + conv(expr.operands[1]), 'Sub': lambda expr, conv: conv(expr.operands[0]) - conv(expr.operands[1]), 'Not': lambda expr, conv: claripy.Not(conv(expr.operand)), 'Xor': lambda expr, conv: conv(expr.operands[0]) ^ conv(expr.operands[1]), 'And': lambda expr, conv: conv(expr.operands[0]) & conv(expr.operands[1]), 'Or': lambda expr, conv: conv(expr.operands[0]) | conv(expr.operands[1]), 'Shr': lambda expr, conv: claripy.LShR(conv(expr.operands[0]), expr. operands[1].value) } if isinstance(condition, (ailment.Expr.Load, ailment.Expr.DirtyExpression, ailment.Expr.BasePointerOffset)): var = claripy.BVS('ailexpr_%s' % repr(condition), condition.bits, explicit_name=True) self._condition_mapping[var] = condition return var elif isinstance(condition, ailment.Expr.Register): var = claripy.BVS('ailexpr_%s-%d' % (repr(condition), condition.idx), condition.bits, explicit_name=True) self._condition_mapping[var] = condition return var elif isinstance(condition, ailment.Expr.Convert): # convert is special. if it generates a 1-bit variable, it should be treated as a BVS if condition.to_bits == 1: var_ = self.claripy_ast_from_ail_condition( condition.operands[0]) name = 'ailcond_Conv(%d->%d, %s)' % ( condition.from_bits, condition.to_bits, repr(var_)) var = claripy.BoolS(name, explicit_name=True) else: var_ = self.claripy_ast_from_ail_condition( condition.operands[0]) name = 'ailexpr_Conv(%d->%d, %s)' % ( condition.from_bits, condition.to_bits, repr(var_)) var = claripy.BVS(name, condition.to_bits, explicit_name=True) self._condition_mapping[var] = condition return var elif isinstance(condition, ailment.Expr.Const): var = claripy.BVV(condition.value, condition.bits) return var elif isinstance(condition, ailment.Expr.Tmp): l.warning("Left-over ailment.Tmp variable %s.", condition) if condition.bits == 1: var = claripy.BoolV('ailtmp_%d' % condition.tmp_idx) else: var = claripy.BVS('ailtmp_%d' % condition.tmp_idx, condition.bits) self._condition_mapping[var] = condition return var lambda_expr = _mapping.get(condition.verbose_op, None) if lambda_expr is None: raise NotImplementedError( "Unsupported AIL expression operation %s. Consider implementing." % condition.op) r = lambda_expr(condition, self.claripy_ast_from_ail_condition) if r is NotImplemented: r = claripy.BVS("ailexpr_%r" % condition, condition.bits, explicit_name=True) self._condition_mapping[r] = condition return r
import settings import claripy settings.WARNING_ADDRESS = 0x1fcb0 settings.VERBOSE = True settings.DEBUG = True settings.TARGET_BINARY = "/home/stefan/Documents/Graduation/RISC-V-toolchain/riscv/Programs/Poly1305_onetimeauth/Radix2.26_woMultiplier/program.elf" settings.TARGET_ADDRESS = 0x20011dfc settings.A = claripy.BVS('A', 32) settings.params = [settings.A] settings.secret = settings.A settings.constraints = [claripy.SLT(settings.A, 5), claripy.SGE(settings.A, 1)] from pluginTime import TIME_STRATEGY_SHORTEST settings.TIME_STRATEGY = TIME_STRATEGY_SHORTEST def stateInit(startState): return True settings.stateInit = stateInit import tool