def get_state(): active_account = Account("0x0", code=Disassembly("60606040")) environment = Environment(active_account, None, None, None, None, None) state = GlobalState(None, environment, None, MachineState(gas_limit=8000000)) state.transaction_stack.append( (MessageCallTransaction(world_state=WorldState(), gas_limit=8000000), None) ) return state
def _get_global_state(): active_account = Account("0x0", code=Disassembly("60606040")) passive_account = Account("0x325345346564645654645", code=Disassembly("6060604061626364")) environment = Environment(active_account, None, None, None, None, None) world_state = WorldState() world_state.put_account(active_account) world_state.put_account(passive_account) return GlobalState(world_state, environment, None, MachineState(gas_limit=8000000))
def test_codecopy_concrete(): # Arrange world_state = WorldState() account = world_state.create_account(balance=10, address=101) account.code = Disassembly("60606040") environment = Environment(account, None, None, None, None, None) og_state = GlobalState(world_state, environment, None, MachineState(gas_limit=8000000)) og_state.transaction_stack.append( (MessageCallTransaction(world_state=WorldState(), gas_limit=8000000), None)) og_state.mstate.stack = [2, 2, 2] instruction = Instruction("codecopy", dynamic_loader=None) # Act new_state = instruction.evaluate(og_state)[0] # Assert assert new_state.mstate.memory[2] == 96 assert new_state.mstate.memory[3] == 64
def test_extcodecopy(): # Arrange new_world_state = WorldState() new_account = new_world_state.create_account(balance=10, address=101) new_account.code = Disassembly("60616240") ext_account = new_world_state.create_account(balance=1000, address=121) ext_account.code = Disassembly("6040404040") new_environment = Environment(new_account, None, None, None, None, None) state = GlobalState( new_world_state, new_environment, None, MachineState(gas_limit=8000000) ) state.transaction_stack.append( (MessageCallTransaction(world_state=WorldState(), gas_limit=8000000), None) ) state.mstate.stack = [3, 0, 0, 121] instruction = Instruction("extcodecopy", dynamic_loader=None) # Act new_state = instruction.evaluate(state)[0] # Assert assert new_state.mstate.memory[0:3] == [96, 64, 64]
def execute_contract_creation( laser_evm, contract_initialization_code, contract_name=None, world_state=None ) -> Account: """Executes a contract creation transaction from all open states. :param laser_evm: :param contract_initialization_code: :param contract_name: :return: """ # TODO: Resolve circular import between .transaction and ..svm to import LaserEVM here del laser_evm.open_states[:] world_state = world_state or WorldState() open_states = [world_state] new_account = None for open_world_state in open_states: next_transaction_id = get_next_transaction_id() # call_data "should" be '[]', but it is easier to model the calldata symbolically # and add logic in codecopy/codesize/calldatacopy/calldatasize than to model code "correctly" transaction = ContractCreationTransaction( world_state=open_world_state, identifier=next_transaction_id, gas_price=symbol_factory.BitVecSym( "gas_price{}".format(next_transaction_id), 256 ), gas_limit=8000000, # block gas limit origin=symbol_factory.BitVecSym( "origin{}".format(next_transaction_id), 256 ), code=Disassembly(contract_initialization_code), caller=symbol_factory.BitVecVal(CREATOR_ADDRESS, 256), contract_name=contract_name, call_data=None, call_value=symbol_factory.BitVecSym( "call_value{}".format(next_transaction_id), 256 ), ) _setup_global_state_for_execution(laser_evm, transaction) new_account = new_account or transaction.callee_account laser_evm.exec(True) return new_account
def test_execute_message_call(mocked_setup: MagicMock): # Arrange laser_evm = LaserEVM({}) world_state = WorldState() world_state.put_account(Account("0x0")) laser_evm.open_states = [world_state] laser_evm.exec = MagicMock() mocked_setup.side_effect = _is_message_call # Act execute_message_call(laser_evm, symbol_factory.BitVecVal(0, 256)) # Assert # laser_evm.exec.assert_called_once() assert laser_evm.exec.call_count == 1 # mocked_setup.assert_called_once() assert mocked_setup.call_count == 1 assert len(laser_evm.open_states) == 0
def __init__( self, accounts: Dict[str, Account], dynamic_loader=None, max_depth=float("inf"), execution_timeout=60, create_timeout=10, strategy=DepthFirstSearchStrategy, max_transaction_count=3, ): world_state = WorldState() world_state.accounts = accounts # this sets the initial world state self.world_state = world_state self.open_states = [world_state] self.nodes = {} self.edges = [] self.coverage = {} self.total_states = 0 self.dynamic_loader = dynamic_loader self.graph = (SimpleGraph() if issubclass( strategy, BasicSearchStrategy) else Graph()) self.strategy = strategy(self.graph, max_depth) self.max_depth = max_depth self.max_transaction_count = max_transaction_count self.execution_timeout = execution_timeout self.create_timeout = create_timeout self.time = None self.pre_hooks = {} self.post_hooks = {} logging.info("LASER EVM initialized with dynamic loader: " + str(dynamic_loader))
def __init__( self, contract, address: Union[int, str, BitVec], strategy: str, dynloader=None, max_depth: int = 22, execution_timeout: Optional[int] = None, loop_bound: int = 3, create_timeout: Optional[int] = None, transaction_count: int = 2, modules=(), compulsory_statespace: bool = True, iprof: Optional[InstructionProfiler] = None, disable_dependency_pruning: bool = False, run_analysis_modules: bool = True, enable_coverage_strategy: bool = False, custom_modules_directory: str = "", ): """ :param contract: Contract to symbolically execute :param address: Address of the contract to symbolically execute :param strategy: Execution strategy to use (bfs, dfs, etc) :param dynloader: Dynamic Loader :param max_depth: Max analysis depth :param execution_timeout: Timeout for the entire analysis :param create_timeout: Timeout for the creation transaction :param transaction_count: Number of transactions to symbolically execute :param modules: Analysis modules to run during analysis :param compulsory_statespace: Boolean indicating whether or not the statespace should be saved :param iprof: Instruction Profiler :param disable_dependency_pruning: Boolean indicating whether dependency pruning should be disabled :param run_analysis_modules: Boolean indicating whether analysis modules should be executed :param enable_coverage_strategy: Boolean indicating whether the coverage strategy should be enabled :param custom_modules_directory: The directory to read custom analysis modules from """ if isinstance(address, str): address = symbol_factory.BitVecVal(int(address, 16), 256) if isinstance(address, int): address = symbol_factory.BitVecVal(address, 256) if strategy == "dfs": s_strategy = DepthFirstSearchStrategy # type: Type[BasicSearchStrategy] elif strategy == "bfs": s_strategy = BreadthFirstSearchStrategy elif strategy == "naive-random": s_strategy = ReturnRandomNaivelyStrategy elif strategy == "weighted-random": s_strategy = ReturnWeightedRandomStrategy else: raise ValueError("Invalid strategy argument supplied") creator_account = Account( hex(CREATOR_ADDRESS), "", dynamic_loader=dynloader, contract_name=None ) attacker_account = Account( hex(ATTACKER_ADDRESS), "", dynamic_loader=dynloader, contract_name=None ) requires_statespace = ( compulsory_statespace or len(get_detection_modules("post", modules, custom_modules_directory)) > 0 ) if not contract.creation_code: self.accounts = {hex(ATTACKER_ADDRESS): attacker_account} else: self.accounts = { hex(CREATOR_ADDRESS): creator_account, hex(ATTACKER_ADDRESS): attacker_account, } instruction_laser_plugin = PluginFactory.build_instruction_coverage_plugin() self.laser = svm.LaserEVM( dynamic_loader=dynloader, max_depth=max_depth, execution_timeout=execution_timeout, strategy=s_strategy, create_timeout=create_timeout, transaction_count=transaction_count, requires_statespace=requires_statespace, iprof=iprof, enable_coverage_strategy=enable_coverage_strategy, instruction_laser_plugin=instruction_laser_plugin, ) if loop_bound is not None: self.laser.extend_strategy(BoundedLoopsStrategy, loop_bound) plugin_loader = LaserPluginLoader(self.laser) plugin_loader.load(PluginFactory.build_mutation_pruner_plugin()) plugin_loader.load(instruction_laser_plugin) if not disable_dependency_pruning: plugin_loader.load(PluginFactory.build_dependency_pruner_plugin()) world_state = WorldState() for account in self.accounts.values(): world_state.put_account(account) if run_analysis_modules: self.laser.register_hooks( hook_type="pre", hook_dict=get_detection_module_hooks( modules, hook_type="pre", custom_modules_directory=custom_modules_directory, ), ) self.laser.register_hooks( hook_type="post", hook_dict=get_detection_module_hooks( modules, hook_type="post", custom_modules_directory=custom_modules_directory, ), ) if isinstance(contract, SolidityContract): self.laser.sym_exec( creation_code=contract.creation_code, contract_name=contract.name, world_state=world_state, ) elif isinstance(contract, EVMContract) and contract.creation_code: self.laser.sym_exec( creation_code=contract.creation_code, contract_name=contract.name, world_state=world_state, ) else: account = Account( address, contract.disassembly, dynamic_loader=dynloader, contract_name=contract.name, concrete_storage=True if (dynloader is not None and dynloader.storage_loading) else False, ) world_state.put_account(account) self.laser.sym_exec(world_state=world_state, target_address=address.value) if not requires_statespace: return self.nodes = self.laser.nodes self.edges = self.laser.edges # Parse calls to make them easily accessible self.calls = [] # type: List[Call] for key in self.nodes: state_index = 0 for state in self.nodes[key].states: instruction = state.get_current_instruction() op = instruction["opcode"] if op in ("CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"): stack = state.mstate.stack if op in ("CALL", "CALLCODE"): gas, to, value, meminstart, meminsz, memoutstart, memoutsz = ( get_variable(stack[-1]), get_variable(stack[-2]), get_variable(stack[-3]), get_variable(stack[-4]), get_variable(stack[-5]), get_variable(stack[-6]), get_variable(stack[-7]), ) if ( to.type == VarType.CONCRETE and 0 < to.val <= PRECOMPILE_COUNT ): # ignore prebuilts continue if ( meminstart.type == VarType.CONCRETE and meminsz.type == VarType.CONCRETE ): self.calls.append( Call( self.nodes[key], state, state_index, op, to, gas, value, state.mstate.memory[ meminstart.val : meminsz.val + meminstart.val ], ) ) else: self.calls.append( Call( self.nodes[key], state, state_index, op, to, gas, value, ) ) else: gas, to, meminstart, meminsz, memoutstart, memoutsz = ( get_variable(stack[-1]), get_variable(stack[-2]), get_variable(stack[-3]), get_variable(stack[-4]), get_variable(stack[-5]), get_variable(stack[-6]), ) self.calls.append( Call(self.nodes[key], state, state_index, op, to, gas) ) state_index += 1
def test_vmtest( test_name: str, environment: dict, pre_condition: dict, action: dict, gas_used: int, post_condition: dict, ) -> None: # Arrange if test_name in ignored_test_names: return world_state = WorldState() for address, details in pre_condition.items(): account = Account(address, concrete_storage=True) account.code = Disassembly(details["code"][2:]) account.nonce = int(details["nonce"], 16) for key, value in details["storage"].items(): account.storage[int(key, 16)] = int(value, 16) world_state.put_account(account) account.set_balance(int(details["balance"], 16)) laser_evm = LaserEVM() laser_evm.open_states = [world_state] # Act laser_evm.time = datetime.now() final_states = execute_message_call( laser_evm, callee_address=symbol_factory.BitVecVal(int(action["address"], 16), 256), caller_address=symbol_factory.BitVecVal(int(action["caller"], 16), 256), origin_address=symbol_factory.BitVecVal(int(action["origin"], 16), 256), code=action["code"][2:], gas_limit=int(action["gas"], 16), data=binascii.a2b_hex(action["data"][2:]), gas_price=int(action["gasPrice"], 16), value=int(action["value"], 16), track_gas=True, ) # Assert if gas_used is not None and gas_used < int(environment["currentGasLimit"], 16): # avoid gas usage larger than block gas limit # this currently exceeds our estimations gas_min_max = [(s.mstate.min_gas_used, s.mstate.max_gas_used) for s in final_states] gas_ranges = [g[0] <= gas_used for g in gas_min_max] assert all(map(lambda g: g[0] <= g[1], gas_min_max)) assert any(gas_ranges) if post_condition == {}: # no more work to do if error happens or out of gas assert len(laser_evm.open_states) == 0 else: assert len(laser_evm.open_states) == 1 world_state = laser_evm.open_states[0] for address, details in post_condition.items(): account = world_state[symbol_factory.BitVecVal( int(address, 16), 256)] assert account.nonce == int(details["nonce"], 16) assert account.code.bytecode == details["code"][2:] for index, value in details["storage"].items(): expected = int(value, 16) actual = account.storage[int(index, 16)] if isinstance(actual, Expression): actual = actual.value actual = 1 if actual is True else 0 if actual is False else actual else: if type(actual) == bytes: actual = int(binascii.b2a_hex(actual), 16) elif type(actual) == str: actual = int(actual, 16) assert actual == expected
def execute( self, timeout: Optional[float] = 60, max_depth: Optional[int] = 128, call_depth_limit: Optional[int] = 3, bounded_loops_limit: Optional[int] = 3, creation_code: Optional[Text] = None, target_address: Optional[Text] = None, dyn_loader: Optional[DynLoader] = None, contract_loader: Optional[Union[FileLoader, JsonRpcLoader]] = None ) -> Report: if contract_loader is not None: if isinstance(contract_loader, FileLoader): creation_code = contract_loader.contract( ).creation_disassembly.bytecode elif isinstance(contract_loader, JsonRpcLoader): target_address = contract_loader.address dyn_loader = contract_loader.dyn_loader else: raise ValueError('Invalid type for contract_loader parameter') world_state = None if creation_code is not None and target_address is None: log.info('Running symbolic execution in creation mode...') laser = svm.LaserEVM(execution_timeout=timeout, max_depth=max_depth, requires_statespace=False) elif creation_code is None and target_address is not None: assert dyn_loader is not None, "Dynamic Loader has not been provided" log.info('Running symbolic execution in existing mode...') laser = svm.LaserEVM(dynamic_loader=dyn_loader, execution_timeout=timeout, max_depth=max_depth, requires_statespace=False) world_state = WorldState() world_state.accounts_exist_or_load(target_address, dyn_loader) else: raise ValueError( 'Either creation_code or target_address needs to be provided') for strategy in self.strategy_loader.get_strategies(): for hook in strategy.pre_hooks: laser.register_hooks('pre', {hook: [strategy.execute]}) for hook in strategy.post_hooks: laser.register_hooks('post', {hook: [strategy.execute]}) # Load laser plugins laser.extend_strategy(BoundedLoopsStrategy, bounded_loops_limit) plugin_loader = LaserPluginLoader() plugin_loader.load(CoveragePluginBuilder()) plugin_loader.load(MutationPrunerBuilder()) # Temporarily disabled due to unhandled exception # plugin_loader.load(CallDepthLimitBuilder()) plugin_loader.load(InstructionProfilerBuilder()) plugin_loader.load(DependencyPrunerBuilder()) # plugin_loader.add_args("call-depth-limit", call_depth_limit=call_depth_limit) plugin_loader.instrument_virtual_machine(laser, None) # Run symbolic execution start_time = time.time() laser.sym_exec( creation_code=creation_code, contract_name='Unknown', world_state=world_state, target_address=int(target_address, 16) if target_address else None) log.info('Symbolic execution finished in %.2f seconds.', time.time() - start_time) report = Report(start_time=start_time, end_time=time.time()) report.contract_code = creation_code report.contract_address = target_address for strategy in self.strategy_loader.get_strategies(): report.add_report(strategy.generate_report()) self._post_process_report(report, target_address, dyn_loader) return report
from mythril.disassembler.disassembly import Disassembly from mythril.laser.ethereum.state.environment import Environment from mythril.laser.ethereum.state.account import Account from mythril.laser.ethereum.state.machine_state import MachineState from mythril.laser.ethereum.state.global_state import GlobalState from mythril.laser.ethereum.state.world_state import WorldState from mythril.laser.ethereum.instructions import Instruction from mythril.laser.ethereum.transaction.transaction_models import MessageCallTransaction from mythril.support.support_utils import get_code_hash from mythril.laser.smt import symbol_factory # Arrange world_state = WorldState() account = world_state.create_account(balance=10, address=101) account.code = Disassembly("60606040") world_state.create_account(balance=10, address=1000) environment = Environment(account, None, None, None, None, None) og_state = GlobalState(world_state, environment, None, MachineState(gas_limit=8000000)) og_state.transaction_stack.append( (MessageCallTransaction(world_state=WorldState(), gas_limit=8000000), None)) instruction = Instruction("extcodehash", dynamic_loader=None) def test_extcodehash_no_account(): # If account does not exist, return 0