def get_code(disassembler: MythrilDisassembler, args: argparse.Namespace): address = None if args.code: # Load from bytecode code = args.code[2:] if args.code.startswith("0x") else args.code address, _ = disassembler.load_from_bytecode(code, args.bin_runtime) elif args.codefile: bytecode = "".join([l.strip() for l in args.codefile if len(l.strip()) > 0]) bytecode = bytecode[2:] if bytecode.startswith("0x") else bytecode address, _ = disassembler.load_from_bytecode(bytecode, args.bin_runtime) elif args.address: # Get bytecode from a contract address address, _ = disassembler.load_from_address(args.address) elif args.solidity_file: # Compile Solidity source file(s) if args.graph and len(args.solidity_file) > 1: exit_with_error( args.outform, "Cannot generate call graphs from multiple input files. Please do it one at a time.", ) address, _ = disassembler.load_from_solidity( args.solidity_file ) # list of files else: exit_with_error( args.outform, "No input bytecode. Please provide EVM code via -c BYTECODE, -a ADDRESS, or -i SOLIDITY_FILES", ) return address
def test_get_data_from_storage_incorrect_params(params): config = MythrilConfig() config.set_api_rpc_infura() disassembler = MythrilDisassembler(eth=config.eth, solc_version="0.4.23") with pytest.raises(CriticalError): disassembler.get_state_variable_from_storage( "0x76799f77587738bfeef09452df215b63d2cfb08a", params)
def _run_mythril(self, contract_address=None): eth_json_rpc = EthJsonRpc(url=self.web3_rpc) disassembler = MythrilDisassembler(eth=eth_json_rpc, solc_version=None, enable_online_lookup=True) disassembler.load_from_address(contract_address) analyzer = MythrilAnalyzer( strategy="bfs", use_onchain_data=self.onchain_storage, disassembler=disassembler, address=contract_address, execution_timeout=self.timeout, solver_timeout=self.timeout, loop_bound=self.loop_bound, max_depth=64, create_timeout=10, ) self.logger.info("Analyzing %s", contract_address) self.logger.debug("Running Mythril") return analyzer.fire_lasers(modules=self.modules, transaction_count=self.tx_count)
def test_get_data_from_storage(params, ans): config = MythrilConfig() config.set_api_rpc_infura() disassembler = MythrilDisassembler(eth=config.eth, solc_version="0.4.23") outtext = disassembler.get_state_variable_from_storage( "0x76799f77587738bfeef09452df215b63d2cfb08a", params).split("\n") assert outtext == ans
def parse_args(parser: argparse.ArgumentParser, args: argparse.Namespace) -> None: """ Parses the arguments :param parser: The parser :param args: The args """ if args.epic: path = os.path.dirname(os.path.realpath(__file__)) sys.argv.remove("--epic") os.system(" ".join(sys.argv) + " | python3 " + path + "/epic.py") sys.exit() if args.version: if args.outform == "json": print(json.dumps({"version_str": VERSION})) else: print("Mythril version {}".format(VERSION)) sys.exit() # Parse cmdline args validate_args(parser, args) try: quick_commands(args) config = set_config(args) leveldb_search(config, args) disassembler = MythrilDisassembler( eth=config.eth, solc_version=args.solv, solc_args=args.solc_args, enable_online_lookup=args.query_signature, ) if args.truffle: try: disassembler.analyze_truffle_project(args) except FileNotFoundError: print( "Build directory not found. Make sure that you start the analysis from the project root, and that 'truffle compile' has executed successfully." ) sys.exit() address = get_code(disassembler, args) execute_command(disassembler=disassembler, address=address, parser=parser, args=args) except CriticalError as ce: exit_with_error(args.outform, str(ce)) except Exception: exit_with_error(args.outform, traceback.format_exc())
def test_generate_graph(self): for input_file in TESTDATA_INPUTS.iterdir(): output_expected = TESTDATA_OUTPUTS_EXPECTED / (input_file.name + ".graph.html") output_current = TESTDATA_OUTPUTS_CURRENT / (input_file.name + ".graph.html") contract = EVMContract(input_file.read_text()) disassembler = MythrilDisassembler() disassembler.contracts.append(contract) analyzer = MythrilAnalyzer( disassembler=disassembler, strategy="dfs", execution_timeout=5, max_depth=30, address=(util.get_indexed_address(0)), ) html = analyzer.graph_html(transaction_count=1) output_current.write_text(html) lines_expected = re.findall(r"'label': '.*'", str(output_current.read_text())) lines_found = re.findall(r"'label': '.*'", str(output_current.read_text())) if not (lines_expected == lines_found): self.found_changed_files(input_file, output_expected, output_current) self.assert_and_show_changed_files()
def contract_hash_to_address(args: Namespace): """ prints the hash from function signature :param args: :return: """ print(MythrilDisassembler.hash_for_function_signature(args.func_name)) sys.exit()
def test_fire_lasers(mock_sym, mock_fire_lasers, mock_code_info): disassembler = MythrilDisassembler(eth=None) disassembler.load_from_solidity( [ str( ( Path(__file__).parent.parent / "testdata/input_contracts/origin.sol" ).absolute() ) ] ) analyzer = MythrilAnalyzer(disassembler, strategy="dfs") issues = analyzer.fire_lasers(modules=[]).sorted_issues() mock_sym.assert_called() mock_fire_lasers.assert_called() mock_code_info.assert_called() assert len(issues) == 1 assert issues[0]["swc-id"] == "101"
def parse_args_and_execute(parser: ArgumentParser, args: Namespace) -> None: """ Parses the arguments :param parser: The parser :param args: The args """ if args.epic: path = os.path.dirname(os.path.realpath(__file__)) sys.argv.remove("--epic") os.system(" ".join(sys.argv) + " | python3 " + path + "/epic.py") sys.exit() if args.command not in COMMAND_LIST or args.command is None: parser.print_help() sys.exit() if args.command == "version": if args.outform == "json": print(json.dumps({"version_str": VERSION})) else: print("Mythril version {}".format(VERSION)) sys.exit() if args.command == "help": parser.print_help() sys.exit() # Parse cmdline args validate_args(args) try: if args.command == "function-to-hash": contract_hash_to_address(args) config = set_config(args) leveldb_search(config, args) query_signature = args.__dict__.get("query_signature", None) solc_json = args.__dict__.get("solc_json", None) solv = args.__dict__.get("solv", None) disassembler = MythrilDisassembler( eth=config.eth, solc_version=solv, solc_settings_json=solc_json, enable_online_lookup=query_signature, ) address = load_code(disassembler, args) execute_command(disassembler=disassembler, address=address, parser=parser, args=args) except CriticalError as ce: exit_with_error(args.__dict__.get("outform", "text"), str(ce)) except Exception: exit_with_error(args.__dict__.get("outform", "text"), traceback.format_exc())
def test_get_source_info_without_name_gets_latest_contract_info(self): input_file = TEST_FILES / "multi_contracts.sol" contract = SolidityContract( str(input_file), solc_binary=MythrilDisassembler._init_solc_binary("0.5.0")) code_info = contract.get_source_info(142) self.assertEqual(code_info.filename, str(input_file)) self.assertEqual(code_info.lineno, 14) self.assertEqual(code_info.code, "msg.sender.transfer(2 ether)")
def test_get_source_info_with_contract_name_specified_constructor(self): input_file = TEST_FILES / "constructor_assert.sol" contract = SolidityContract( str(input_file), name="AssertFail", solc_binary=MythrilDisassembler._init_solc_binary("0.5.0"), ) code_info = contract.get_source_info(70, constructor=True) self.assertEqual(code_info.filename, str(input_file)) self.assertEqual(code_info.lineno, 6) self.assertEqual(code_info.code, "assert(var1 > 0)")
def test_get_source_info_with_contract_name_specified(self): input_file = TEST_FILES / "multi_contracts.sol" contract = SolidityContract( str(input_file), name="Transfer1", solc_binary=MythrilDisassembler._init_solc_binary("0.5.0"), ) code_info = contract.get_source_info(142) self.assertEqual(code_info.filename, str(input_file)) self.assertEqual(code_info.lineno, 6) self.assertEqual(code_info.code, "msg.sender.transfer(1 ether)")
def load_code(disassembler: MythrilDisassembler, args: Namespace): """ Loads code into disassembly and returns address :param disassembler: :param args: :return: Address """ address = None if args.__dict__.get("code", False): # Load from bytecode code = args.code[2:] if args.code.startswith("0x") else args.code address, _ = disassembler.load_from_bytecode(code, args.bin_runtime) elif args.__dict__.get("codefile", False): bytecode = "".join( [l.strip() for l in args.codefile if len(l.strip()) > 0]) bytecode = bytecode[2:] if bytecode.startswith("0x") else bytecode address, _ = disassembler.load_from_bytecode(bytecode, args.bin_runtime) elif args.__dict__.get("address", False): # Get bytecode from a contract address address, _ = disassembler.load_from_address(args.address) elif args.__dict__.get("solidity_file", False): # Compile Solidity source file(s) if args.command in ANALYZE_LIST and args.graph and len( args.solidity_file) > 1: exit_with_error( args.outform, "Cannot generate call graphs from multiple input files. Please do it one at a time.", ) address, _ = disassembler.load_from_solidity( args.solidity_file) # list of files else: exit_with_error( args.__dict__.get("outform", "text"), "No input bytecode. Please provide EVM code via -c BYTECODE, -a ADDRESS, -f BYTECODE_FILE or <SOLIDITY_FILE>", ) return address
def test_sym_exec(): contract = SolidityContract( str(tests.TESTDATA_INPUTS_CONTRACTS / "calls.sol"), solc_binary=MythrilDisassembler._init_solc_binary("0.5.0"), ) sym = SymExecWrapper( contract, address=(util.get_indexed_address(0)), strategy="dfs", execution_timeout=25, ) issues = fire_lasers(sym) assert len(issues) != 0
def runTest(): """""" disassembly = SolidityContract( "./tests/native_tests.sol", solc_binary=MythrilDisassembler._init_solc_binary("0.5.0"), ).disassembly account = Account("0x0000000000000000000000000000000000000000", disassembly) accounts = {account.address: account} laser = svm.LaserEVM(accounts, max_depth=100, transaction_count=1) laser.sym_exec(account.address) laser_info = str(_all_info(laser)) _test_natives(laser_info, SHA256_TEST, "SHA256") _test_natives(laser_info, RIPEMD160_TEST, "RIPEMD160") _test_natives(laser_info, ECRECOVER_TEST, "ECRECOVER") _test_natives(laser_info, IDENTITY_TEST, "IDENTITY")
def parse_args(parser: argparse.ArgumentParser, args: argparse.Namespace) -> None: """ Parses the arguments :param parser: The parser :param args: The args """ if args.epic: path = os.path.dirname(os.path.realpath(__file__)) sys.argv.remove("--epic") os.system(" ".join(sys.argv) + " | python3 " + path + "/epic.py") sys.exit() if args.version: if args.outform == "json": print(json.dumps({"version_str": VERSION})) else: print("Mythril version {}".format(VERSION)) sys.exit() # Parse cmdline args validate_args(parser, args) try: quick_commands(args) config = set_config(args) leveldb_search(config, args) disassembler = MythrilDisassembler( eth=config.eth, solc_version=args.solv, solc_settings_json=args.solc_json, enable_online_lookup=args.query_signature, ) address = get_code(disassembler, args) execute_command(disassembler=disassembler, address=address, parser=parser, args=args) except CriticalError as ce: exit_with_error(args.outform, str(ce)) except Exception: exit_with_error(args.outform, traceback.format_exc())
def runTest(): """""" disassembly = SolidityContract( "./tests/native_tests.sol", solc_binary=MythrilDisassembler._init_solc_binary("0.5.3"), ).disassembly account = Account("0x0000000000000000000000000000000000000000", disassembly) world_state = WorldState() world_state.put_account(account) laser = svm.LaserEVM(max_depth=100, transaction_count=1) laser.sym_exec(world_state=world_state, target_address=account.address.value) laser_info = str(_all_info(laser)) _test_natives(laser_info, SHA256_TEST, "SHA256") _test_natives(laser_info, RIPEMD160_TEST, "RIPEMD160") _test_natives(laser_info, ECRECOVER_TEST, "ECRECOVER") _test_natives(laser_info, IDENTITY_TEST, "IDENTITY")
def test_create(): contract = SolidityContract( str(tests.TESTDATA_INPUTS_CONTRACTS / "calls.sol"), solc_binary=MythrilDisassembler._init_solc_binary("0.5.0"), ) laser_evm = svm.LaserEVM({}) laser_evm.time = datetime.now() execute_contract_creation(laser_evm, contract.creation_code) resulting_final_state = laser_evm.open_states[0] for address, created_account in resulting_final_state.accounts.items(): created_account_code = created_account.code actual_code = Disassembly(contract.code) for i in range(len(created_account_code.instruction_list)): found_instruction = created_account_code.instruction_list[i] actual_instruction = actual_code.instruction_list[i] assert found_instruction["opcode"] == actual_instruction["opcode"]
def execute_command( disassembler: MythrilDisassembler, address: str, parser: argparse.ArgumentParser, args: argparse.Namespace, ): if args.storage: if not args.address: exit_with_error( args.outform, "To read storage, provide the address of a deployed contract with the -a option.", ) storage = disassembler.get_state_variable_from_storage( address=address, params=[a.strip() for a in args.storage.strip().split(",")] ) print(storage) return analyzer = MythrilAnalyzer( strategy=args.strategy, disassembler=disassembler, address=address, max_depth=args.max_depth, execution_timeout=args.execution_timeout, create_timeout=args.create_timeout, enable_iprof=args.enable_iprof, onchain_storage_access=not args.no_onchain_storage_access, ) if args.disassemble: # or mythril.disassemble(mythril.contracts[0]) if disassembler.contracts[0].code: print("Runtime Disassembly: \n" + disassembler.contracts[0].get_easm()) if disassembler.contracts[0].creation_code: print("Disassembly: \n" + disassembler.contracts[0].get_creation_easm()) elif args.graph or args.fire_lasers: if not disassembler.contracts: exit_with_error( args.outform, "input files do not contain any valid contracts" ) if args.graph: html = analyzer.graph_html( contract=analyzer.contracts[0], enable_physics=args.enable_physics, phrackify=args.phrack, ) try: with open(args.graph, "w") as f: f.write(html) except Exception as e: exit_with_error(args.outform, "Error saving graph: " + str(e)) else: try: report = analyzer.fire_lasers( modules=[m.strip() for m in args.modules.strip().split(",")] if args.modules else [], verbose_report=args.verbose_report, transaction_count=args.transaction_count, ) outputs = { "json": report.as_json(), "jsonv2": report.as_swc_standard_format(), "text": report.as_text(), "markdown": report.as_markdown(), } print(outputs[args.outform]) except ModuleNotFoundError as e: exit_with_error( args.outform, "Error loading analyis modules: " + format(e) ) elif args.statespace_json: if not analyzer.contracts: exit_with_error( args.outform, "input files do not contain any valid contracts" ) statespace = analyzer.dump_statespace(contract=analyzer.contracts[0]) try: with open(args.statespace_json, "w") as f: json.dump(statespace, f) except Exception as e: exit_with_error(args.outform, "Error saving json: " + str(e)) else: parser.print_help()
def signature_hash(signature: Text) -> Text: if signature.startswith('_function'): return signature[-10:] else: return MythrilDisassembler.hash_for_function_signature(signature)
def quick_commands(args: argparse.Namespace): if args.hash: print(MythrilDisassembler.hash_for_function_signature(args.hash)) sys.exit()
def exploits_from_mythril( rpcHTTP="http://localhost:8545", rpcWS=None, rpcIPC=None, timeout=300, contract="", account_pk="", strategy="bfs", modules=["ether_thief", "suicide"], transaction_count=3, execution_timeout=120, max_depth=32, loop_bound=3, disable_dependency_pruning=False, onchain_storage_access=True, enable_online_lookup=False, ): if re.match(r"^https", rpcHTTP): host, port = rpcHTTP[8:].split(":") rpc_tls = True else: host, port = rpcHTTP[7:].split(":") rpc_tls = False try: # Disassembler disassembler = MythrilDisassembler( eth=EthJsonRpc(host=host, port=port, tls=rpc_tls), solc_version=None, solc_args=None, enable_online_lookup=enable_online_lookup, ) disassembler.load_from_address(contract) # Analyzer analyzer = MythrilAnalyzer( strategy=strategy, disassembler=disassembler, address=contract, execution_timeout=execution_timeout, max_depth=max_depth, loop_bound=loop_bound, disable_dependency_pruning=disable_dependency_pruning, onchain_storage_access=onchain_storage_access, ) # Generate report report = analyzer.fire_lasers( modules=modules, transaction_count=transaction_count ) except CriticalError as e: print(e) return [] if rpcIPC is not None: print("Connecting to IPC: {rpc}.".format(rpc=rpcIPC)) w3 = Web3(Web3.IPCProvider(rpcIPC, timeout=timeout)) elif rpcWS is not None: print("Connecting to WebSocket: {rpc}.".format(rpc=rpcWS)) w3 = Web3(Web3.WebsocketProvider(rpcWS, websocket_kwargs={"timeout": timeout})) else: print("Connecting to HTTP: {rpc}.".format(rpc=rpcHTTP)) w3 = Web3(Web3.HTTPProvider(rpcHTTP, request_kwargs={"timeout": timeout})) exploits = [] for ri in report.issues: txs = [] issue = report.issues[ri] for si in issue.transaction_sequence["steps"]: txs.append(Tx(data=si["input"], value=si["value"], name=si["name"])) exploits.append( Exploit( txs=txs, w3=w3, contract=contract, account=private_key_to_account(account_pk), account_pk=account_pk, title=issue.title, description=issue.description, swc_id=issue.swc_id, ) ) return exploits
def test_solc_install(): MythrilDisassembler(eth=None, solc_version="0.4.19")
def execute_command( disassembler: MythrilDisassembler, address: str, parser: ArgumentParser, args: Namespace, ): """ Execute command :param disassembler: :param address: :param parser: :param args: :return: """ if args.command == "read-storage": storage = disassembler.get_state_variable_from_storage( address=address, params=[a.strip() for a in args.storage_slots.strip().split(",")], ) print(storage) elif args.command in DISASSEMBLE_LIST: if disassembler.contracts[0].code: print("Runtime Disassembly: \n" + disassembler.contracts[0].get_easm()) if disassembler.contracts[0].creation_code: print("Disassembly: \n" + disassembler.contracts[0].get_creation_easm()) elif args.command in ANALYZE_LIST: analyzer = MythrilAnalyzer( strategy=args.strategy, disassembler=disassembler, address=address, max_depth=args.max_depth, execution_timeout=args.execution_timeout, loop_bound=args.loop_bound, create_timeout=args.create_timeout, enable_iprof=args.enable_iprof, disable_dependency_pruning=args.disable_dependency_pruning, onchain_storage_access=not args.no_onchain_storage_access, ) if not disassembler.contracts: exit_with_error(args.outform, "input files do not contain any valid contracts") if args.graph: html = analyzer.graph_html( contract=analyzer.contracts[0], enable_physics=args.enable_physics, phrackify=args.phrack, transaction_count=args.transaction_count, ) try: with open(args.graph, "w") as f: f.write(html) except Exception as e: exit_with_error(args.outform, "Error saving graph: " + str(e)) elif args.statespace_json: if not analyzer.contracts: exit_with_error( args.outform, "input files do not contain any valid contracts") statespace = analyzer.dump_statespace( contract=analyzer.contracts[0]) try: with open(args.statespace_json, "w") as f: json.dump(statespace, f) except Exception as e: exit_with_error(args.outform, "Error saving json: " + str(e)) else: try: report = analyzer.fire_lasers( modules=[ m.strip() for m in args.modules.strip().split(",") ] if args.modules else [], transaction_count=args.transaction_count, ) outputs = { "json": report.as_json(), "jsonv2": report.as_swc_standard_format(), "text": report.as_text(), "markdown": report.as_markdown(), } print(outputs[args.outform]) except ModuleNotFoundError as e: exit_with_error(args.outform, "Error loading analysis modules: " + format(e)) else: parser.print_help()