Example #1
0
 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
Example #2
0
 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
Example #3
0
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
Example #4
0
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
Example #5
0
 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
Example #6
0
    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
Example #7
0
    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)
Example #8
0
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')
Example #9
0
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')