def assign_model(self, variables: Variables) -> int: logging.debug('Assign model into cfg...') gas = 0 state = State() for node in self.path: for opcode in node.opcodes: gas += state.simulate_with_model(opcode, self.model, variables) self.model_gas = gas
def start(self): for node in self.cfg.nodes: if node.color == 'yellow': try: s = State() s.stack = {'0': 0} s.memory = {64: 128} logging.debug('Start From Tag %s' % node.tag) self.symbolic_execution(node.tag, Path(), s) except: continue
def solve_path(variables: Variables, bound_path: [Path], gas_limit: int) -> int: logging.info('Solving bound paths...') max_gas = gas_limit gas_formula = gas_limit for id, path in enumerate(bound_path): logging.debug('Finding max gas...[%s/%s]' % (id + 1, len(bound_path))) if path.solve_max_gas(gas_limit): path.assign_model(variables) if path.model_gas > max_gas: max_gas = path.model_gas gas_formula = path.gas return max_gas, gas_formula
def classify_path(analyzer: Analyzer) -> ([Path], [Path], [Path]): logging.debug('classify path....') paths = analyzer.paths constant_path = list() bound_path = list() unbound_path = list() for id, path in enumerate(paths): logging.debug('Solving the constraints...[%s/%s]' % (id + 1, len(paths))) if path.gas_type == 'constant': constant_path.append(path) elif path.gas_type == 'bound': bound_path.append(path) elif path.gas_type == 'unbound': unbound_path.append(path) else: logging.error('Gas Type Error') return constant_path, bound_path, unbound_path
def solve_max_gas(self, gas: int) -> bool: self.solver.push() self.solver.add(self.gas > gas) is_sat = False self.solver.set(timeout=TIMEOUT) while self.solver.check() == sat: logging.debug('SAT: %s' % gas) is_sat = True gas += 10000 self.solver.pop() self.solver.push() self.solver.add(self.gas > gas) self.solver.set(timeout=TIMEOUT) else: logging.debug('UNSAT: %s' % gas) self.solver.pop() self.solver.push() self.solver.add(self.gas > gas - 10000) self.solver.set(timeout=TIMEOUT) if self.solver.check() == sat: self.model = self.solver.model() return True else: return False
def __extrapolation(self, nodes: list, pc: int, variables: list, cfg) -> (ArithRef, ArithRef): from src.Result import Result decl, constraint, formulae = list(), list(), list() for node in nodes: if isinstance(node.path_constraint, int): return None, None constraint.append(self.to_string(node.path_constraint)) if len(set(constraint)) == 1: return 'same', None formulae, decl, if_pair = self.__get_loop_formulae_info(nodes) for node in cfg.nodes: if node.tag == nodes[0].tag: node.loop_condition.append({ 'decl': str(decl[0]), 'constraint': formulae }) self.loop_info = { 'node': nodes[0].tag, 'loop_constraint': formulae } diff = simplify(formulae[1] - formulae[0]) diff = int(diff.as_long()) if isinstance(diff, BitVecNumRef) else diff if isinstance(diff, int): diff = diff - 2**256 if diff > 2**255 else diff # logging.debug('Loop constraint diff: %s' % diff) if len(set(decl)) == 1: if isinstance(diff, int): if diff != 0: loop_var = variables.get_variable(Variable('loop_%s' % pc, 'Loop iteration of pc: %s' % pc, BitVec('loop_%s' % pc, 256))) self.path_constraint.append(ULT(loop_var, UNSIGNED_BOUND_NUMBER)) if diff < 0: loop_formula = If(decl[0](formulae[0] - abs(diff)*loop_var, 0), if_pair[0], if_pair[1]) loop_formula_n = If(decl[0](formulae[0] - abs(diff)*(loop_var + 1), 0), if_pair[1], if_pair[0]) else: loop_formula = If(decl[0](formulae[0] + diff*loop_var, 0), if_pair[0], if_pair[1]) loop_formula_n = If(decl[0](formulae[0] + diff*(loop_var + 1), 0), if_pair[1], if_pair[0]) else: loop_formula = 'same' loop_formula_n = None else: loop_formula = None loop_formula_n = None else: loop_formula = None loop_formula_n = None # result = Result() # result.log_error(settings.ADDRESS, 'Operators are not same') # raise ValueError('Operators are not same: %s, %s' % (decl, formulae)) if loop_formula is not None: for i, node in enumerate(nodes): self.__remove_constraint_from_path(node.path_constraint) else: logging.debug('Cannot solve loop formula: %s' % formulae) return loop_formula, loop_formula_n
def symbolic_execution(self, tag: int, path: Path, state: State) -> None: from src.Result import Result # logging.debug('TAG: %s' % tag) # if settings.DETECT_LOOP: # return node = self.cfg.get_node(tag) if not node: return node.visit() node.init_state = deepcopy(state) gas = 0 if node.count % 10 == 0: logging.debug('%s visit %s times' % (tag, node.count)) if ENABLE_MAX_NODE_VISITED_TIMES and node.count > MAX_NODE_VISITED_TIMES: return for opcode in node.opcodes: # NOTE: state simulation result = state.simulate(opcode, self.variables) # if tag in [1133]: # logging.debug('%s: %s' % (opcode.pc, opcode.name)) # logging.debug('Stack: %s\n' % self.to_string(state.stack)) # logging.debug('MEM: %s' % self.to_string(state.memory)) # logging.debug('STO: %s\n' % self.to_string(state.storage)) path.add_path_constraints(result.path_constraints) gas += result.gas gas = simplify(gas) if is_expr(gas) else gas path.add_gas(result.gas) path.add_memory_gas(result.memory_gas) if opcode.name == 'JUMP': # NOTE: set gas to node node.set_gas(gas) node.set_state(deepcopy(state)) # NOTE: add tag to the path list path.add_node(deepcopy(node)) # NOTE: if edge is not in edges -> add edge into edges self.__add_edge(Edge(node.tag, result.jump_tag, 'red')) return self.symbolic_execution(result.jump_tag, path, state) elif opcode.name == 'JUMPI': tmp_cond = simplify(result.jump_condition) if is_expr( result.jump_condition) else result.jump_condition result.jump_condition = int(tmp_cond.as_long()) if isinstance( tmp_cond, BitVecNumRef) else result.jump_condition node.set_path_constraint(result.jump_condition) # NOTE: Loop detection detect_loop = False if LOOP_DETECTION: if path.count_specific_node_num(node.tag) > 0 and is_expr( result.jump_condition): jump_condition, jump_condition_n1 = path.handle_loop( node, opcode.pc, self.variables, self.cfg) if jump_condition is not None: detect_loop = True result.jump_condition = jump_condition if str( jump_condition ) != 'same' else result.jump_condition elif path.count_specific_node_num( node.tag) >= MAX_LOOP_ITERATIONS - 1: # LOG ERROR err_result = Result() err_message = 'Loop Error:[%s] %s' % ( tag, result.jump_condition) err_result.log_error(settings.ADDRESS, err_message) logging.error(err_message) return else: path_cond = simplify(node.path_constraint) if is_expr( node.path_constraint) else node.path_constraint if path.count_specific_node_num( node.tag) >= MAX_LOOP_ITERATIONS and is_expr( path_cond): for node in self.cfg.nodes: if node.tag == tag: node.loop_condition.append( path.find_loop_condition(node)) return # NOTE: if edge is not in edges -> add edge into edges self.__add_edge(Edge(node.tag, result.jump_tag, 'red')) self.__add_edge(Edge(node.tag, opcode.get_next_pc(), 'red')) edge_true = self.cfg.get_edge(node.tag, result.jump_tag) edge_false = self.cfg.get_edge(node.tag, opcode.get_next_pc()) if detect_loop: edge_true.set_path_constraint( self.to_string(simplify(result.jump_condition == 1))) edge_false.set_path_constraint( self.to_string(simplify(result.jump_condition == 0))) if path.contain_node(result.jump_tag): jump_condition_n1 = 0 if jump_condition_n1 is None else jump_condition_n1 path.add_path_constraints([ result.jump_condition == 1, jump_condition_n1 == 1 ]) return self.symbolic_execution(opcode.get_next_pc(), deepcopy(path), deepcopy(state)) elif path.contain_node(opcode.get_next_pc()): jump_condition_n1 = 1 if jump_condition_n1 is None else jump_condition_n1 path.add_path_constraints([ result.jump_condition == 0, jump_condition_n1 == 0 ]) return self.symbolic_execution(result.jump_tag, deepcopy(path), deepcopy(state)) else: # LOG ERROR err_result = Result() err_message = 'Loop Error:[%s] Both JUMPDEST tags have been executed' % tag err_result.log_error(settings.ADDRESS, err_message) raise ValueError(err_message) else: # NOTE: set gas to node node.set_gas(gas) node.set_state(deepcopy(state)) # NOTE: add tag to the path list path.add_node(deepcopy(node)) # NOTE: Jump to two path if isinstance(result.jump_condition, int) and result.jump_condition == 1: edge_true.set_path_constraint('True') edge_false.set_path_constraint('False') return self.symbolic_execution(result.jump_tag, deepcopy(path), deepcopy(state)) elif isinstance(result.jump_condition, int) and result.jump_condition == 0: edge_true.set_path_constraint('False') edge_false.set_path_constraint('True') return self.symbolic_execution(opcode.get_next_pc(), deepcopy(path), deepcopy(state)) elif isinstance(result.jump_condition, int): return else: edge_true.set_path_constraint( self.to_string( simplify(result.jump_condition == 1))) edge_false.set_path_constraint( self.to_string( simplify(result.jump_condition == 0))) true_path, false_path = deepcopy(path), deepcopy(path) true_state, false_state = deepcopy(state), deepcopy( state) true_path.add_path_constraints( [result.jump_condition == 1]) false_path.add_path_constraints( [result.jump_condition == 0]) self.symbolic_execution(result.jump_tag, true_path, true_state) self.symbolic_execution(opcode.get_next_pc(), false_path, false_state) return elif opcode.name in [ 'STOP', 'RETURN', 'REVERT', 'INVALID', 'SELFDESTRUCT' ]: # NOTE: set gas to node node.set_gas(gas) node.set_state(deepcopy(state)) # NOTE: add tag to the path list path.add_node(deepcopy(node)) # NOTE: simplify gas formula path.gas = simplify(path.gas) if is_expr(path.gas) else int( path.gas) path.gas = int(path.gas.as_long()) if isinstance( path.gas, BitVecNumRef) else path.gas path.gas = int(path.gas) if isinstance(path.gas, float) else path.gas # NOTE: solve gas satisfiability & set gas type if path.solve(): if isinstance(path.gas, int): path.set_gas_type('constant') elif 'loop' in str(path.gas) and path.solve_unbound(): settings.DETECT_LOOP = True path.set_gas_type('unbound') logging.debug('Detect loop') else: path.set_gas_type('bound') self.paths.append(path) self.count_path += 1 logging.debug('Finish one path...[%s]' % self.count_path) return """ NOTE: the end of the node is not in block ins -> jump to next node """ # NOTE: set gas to node node.set_gas(gas) # NOTE: add tag to the path list path.add_node(deepcopy(node)) # NOTE: if edge is not in edges -> add edge into edges self.__add_edge(Edge(node.tag, opcode.get_next_pc(), 'red')) return self.symbolic_execution(opcode.get_next_pc(), path, state)
def main(): parser = argparse.ArgumentParser() parser.add_argument('-s', '--sourcecode', dest='sourcecode', help='input source code file', action='store_true') parser.add_argument('-b', '--bytecode', dest='bytecode', help='input bytecode file', action='store_true') parser.add_argument('-code', '--code', dest='code', help='source code') parser.add_argument('-r', '--remove-node', dest='removenode', help='remove unreached node from cfg', action='store_true') parser.add_argument( '-f', '--format', dest='format', help='format of the cfg file. [options: svg, html(default)]', default='html') parser.add_argument('-o', '--output', dest='output', help='the output path') parser.add_argument('-d', '--debug', dest='debug', help='set logger to DEBUG mode', action='store_true') parser.add_argument('-l', '--linux-mode', dest='linuxmode', help='to run on linux, use linux mode', action='store_true') parser.add_argument('-fm', '--function-mode', dest='functionmode', help='Symbolic execution by function', action='store_true') args = parser.parse_args() if args.debug: logging.basicConfig(format='%(asctime)s [%(levelname)s]: %(message)s', datefmt='%y-%m-%d %H:%M', level=logging.DEBUG) else: logging.basicConfig(format='%(asctime)s [%(levelname)s]: %(message)s', datefmt='%y-%m-%d %H:%M', level=logging.INFO) if args.removenode: settings.REMOVE_UNREACHED_NODE = True if args.linuxmode: settings.LINUX_MODE = True if args.functionmode: settings.FUNCTION_MODE = True if args.format in ['html', 'svg']: settings.CFG_FORMAT = args.format else: logging.error('Wrong cfg format. Accept option are "html" and "svg"') exit(0) if args.output: logging.debug('Output path: %s' % args.output) settings.OUTPUT_PATH = args.output start_time = time.time() if args.sourcecode: if args.code == '': logging.error('Source code error') exit(0) else: code_src = os.path.abspath(args.code) contract_name = os.path.basename(code_src).split('.')[0] logging.info('Transforming contract %s source code to opcodes' % contract_name) # NOTE: Compile source code to opcodes preprocessing.source_code_to_opcodes(code_src) elif args.bytecode: if args.code == '': logging.error('Byte code error') exit(0) else: f_src = os.path.abspath(args.code) contract_name = os.path.basename(f_src).split('.')[0] logging.info('Transforming address %s bytecode to opcodes' % contract_name) # NOTE: Compile source code to opcodes preprocessing.bytecode_to_opcodes(f_src) else: logging.error('Must use an argument, --help for more details') # NOTE: Analyze the opcodes opcodes_analysis(contract_name) end_time = time.time() logging.debug('Analysis time: %ss' % (end_time - start_time)) logging.info('Analysis complete')
def opcodes_analysis(contract_name): settings.ADDRESS = contract_name settings.RANKING_FUNCTION_LIST = list() opcodes_path = os.path.join(ROOT_PATH, 'opcodes') for file in os.listdir('%s/%s' % (opcodes_path, contract_name)): settings.DETECT_LOOP = False file_name = file.split('.')[0] logging.info('Analyze contract %s - %s' % (contract_name, file_name)) with open('%s/%s/%s' % (opcodes_path, contract_name, file), 'r') as f: opcodes = f.read() if opcodes != '': result_path = os.path.join(ROOT_PATH, 'result') settings.CONTRACT_NAME = contract_name # NOTE: Build CFG cfg = Cfg() cfg.build_cfg(opcodes) settings.CFG_PATH = '%s/%s/cfg/%s' % (settings.OUTPUT_PATH, contract_name, file_name) cfg.render(settings.CFG_PATH) logging.debug('Function Hashes: %s' % cfg.function_map) logging.info('Total instructions: %s' % cfg.ins_num()) # NOTE: Analysis logging.info('Symbolic simulation...') analyzer = Analyzer(cfg) if settings.FUNCTION_MODE: analyzer.start() else: analyzer.symbolic_execution(0, Path(), State()) # analyzer.symbolic_execution_from_other_head() analyzer.set_paths_id() logging.info('CFG node count = %s' % cfg.node_num()) logging.info('CFG edge count = %s' % cfg.edge_num()) logging.info('Total path: %s' % len(analyzer.paths)) if settings.REMOVE_UNREACHED_NODE: cfg.remove_unreach_nodes() logging.info('CFG reachable node = %s' % cfg.node_num()) cfg.render( '%s/%s/cfg/%s' % (settings.OUTPUT_PATH, contract_name, file_name), analyzer.paths) # NOTE: Solve PATHS constant_path, bound_path, unbound_path = classify_path(analyzer) logging.info('Satisfiability constant gas path: %s' % len(constant_path)) logging.info('Satisfiability bound gas path: %s' % len(bound_path)) logging.info('Satisfiability unbound gas path: %s' % len(unbound_path)) if settings.LOOP_DETECTION: for node in cfg.nodes: if len(node.loop_condition) > 0: logging.debug('Create iRankFinder CFG of node %s' % node.tag) rf = RankingFunction() for constraint in node.loop_condition: rf.add_constraint(constraint['constraint'], constraint['decl']) rf.create_cfg() rf.render('final_%s' % node.tag) count_loop = 0 for upath in unbound_path: count_loop += 1 cfg.render_loop( '%s/%s/cfg/loop/%s_%s' % (settings.OUTPUT_PATH, contract_name, file_name, count_loop), upath) # logging.debug('Ranking Function CFG: %s' % len(settings.RANKING_FUNCTION_LIST)) # count_rf = 0 # for rfs in settings.RANKING_FUNCTION_LIST: # count_rf += 1 # rfs.render(count_rf) gas_formula = None if len(unbound_path) > 0: max_gas = unbound_path[-1].gas elif len(bound_path) > 0: max_gas, gas_formula = solve_path( analyzer.variables, bound_path, get_max_constant_gas(constant_path)) logging.info('Max gas formula: %s' % gas_formula) else: max_gas = get_max_constant_gas(constant_path) logging.info('Max gas: %s' % max_gas) # NOTE: Output Result File logging.info('Writing analysis result into file...') if gas_formula is None: result = Result(analyzer=analyzer, max_gas=max_gas, constant_path=constant_path, bound_path=bound_path, unbound_path=unbound_path) else: result = Result(analyzer=analyzer, max_gas=max_gas, gas_formula=gas_formula, constant_path=constant_path, bound_path=bound_path, unbound_path=unbound_path) result.render(contract_name, file_name) del cfg, analyzer, result logging.info('%s finished' % file_name) else: logging.info('%s is empyty' % file_name) result = Result() result.log_error(contract_name, 'empty')