def _mythril(bytecode): # import pudb; pudb.set_trace(); logging.info('starting mythril analysis') max_depth = 12 mythril = Mythril() address, contract_object = mythril.load_from_bytecode(bytecode) issues = mythril.fire_lasers(strategy="dfs", verbose_report=True, contracts=[contract_object], address=address, max_depth=max_depth, execution_timeout=30, create_timeout=30, modules=None) return issues.as_text() # issues.as_json() also available
def mythril_scanner(smart_contract): logging.info(f'[{smart_contract}] Mythril scanner') mythril = Mythril(solv=None, dynld=False, solc_args=None) address, _ = mythril.load_from_solidity([ smart_contract, ]) logging.info(f'[{smart_contract}] address={address}') report = mythril.fire_lasers(strategy='dfs', address=address, modules=[], verbose_report=False, max_depth=20, execution_timeout=200) logging.info(f'[{smart_contract}] report={report.as_json()}') return report.as_json()
def test_laser_result(self): for input_file in TESTDATA_INPUTS_CONTRACTS.iterdir(): if input_file.name in ["weak_random.sol", "environments.sol"]: continue output_expected = TESTDATA_OUTPUTS_EXPECTED_LASER_RESULT / ( input_file.name + ".json") output_current = TESTDATA_OUTPUTS_CURRENT_LASER_RESULT / ( input_file.name + ".json") disassembly = SolidityContract( str(input_file), solc_binary=Mythril._init_solc_binary("0.5.0")).disassembly account = Account("0x0000000000000000000000000000000000000000", disassembly) accounts = {account.address: account} laser = svm.LaserEVM(accounts, max_depth=22, transaction_count=1) laser.register_hooks(hook_type="post", hook_dict=get_detection_module_hooks()) laser.sym_exec(account.address) laser_info = _all_info(laser) output_current.write_text( json.dumps(laser_info, cls=LaserEncoder, indent=4)) if not (output_expected.read_text() == output_expected.read_text()): self.found_changed_files(input_file, output_expected, output_current) self.assert_and_show_changed_files()
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=Mythril._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_sym_exec(): contract = SolidityContract( str(tests.TESTDATA_INPUTS_CONTRACTS / "calls.sol"), solc_binary=Mythril._init_solc_binary("0.5.0"), ) sym = SymExecWrapper(contract, address=(util.get_indexed_address(0)), strategy="dfs") issues = fire_lasers(sym) assert len(issues) != 0
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=Mythril._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=Mythril._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 get_vulns(target_address, tx_count): myth = Mythril(enable_online_lookup=False, onchain_storage_access=True) if re.match(r"^https", rpc): rpchost = rpc[8:] rpctls = True else: rpchost = rpc[7:] rpctls = False myth.set_api_rpc(rpchost, rpctls) myth.load_from_address(target_address) report = myth.fire_lasers( strategy="dfs", modules=["ether_thief", "suicide"], address=target_address, execution_timeout=45, max_depth=22, transaction_count=tx_count, verbose_report=True, ) nIssues = len(report.issues) if nIssues == 0: raise InvulnerableError vulns = [] for i in range(0, nIssues): issue = report.sorted_issues()[i] tx = issue["debug"].replace("\n", " ").replace("'", '"') transactions = json.loads(tx) if "withdraw its balance" in issue["description"]: _type = VulnType.KILL_AND_WITHDRAW description = "Looks line anyone can kill this contract and steal its balance." elif "withdraw ETH" in issue["description"]: _type = VulnType.ETHER_THEFT description = "Looks like anyone can withdraw ETH from this contract." else: _type = VulnType.KILL_ONLY description = "Anybody can accidentally kill this contract." vulns.append(Vulnerability(_type, description, transactions)) if len(vulns): return vulns raise InvulnerableError
def runTest(): """""" disassembly = SolidityContract( "./tests/native_tests.sol", solc_binary=Mythril._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 _run_mythril(self, contract_address=None): myth = Mythril(onchain_storage_access=True, enable_online_lookup=True) myth.set_api_rpc(rpc=self.rpc, rpctls=self.rpc_tls) self.logger.info("Analyzing %s", contract_address) myth.load_from_address(contract_address) self.logger.debug("Running Mythril") return myth.fire_lasers( strategy="dfs", modules=["ether_thief", "suicide"], address=contract_address, execution_timeout=45, create_timeout=10, max_depth=22, transaction_count=3, verbose_report=True, )
def test_create(): contract = SolidityContract( str(tests.TESTDATA_INPUTS_CONTRACTS / "calls.sol"), solc_binary=Mythril._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 main(): start_meassure() parser = argparse.ArgumentParser( description='Security analysis of Ethereum smart contracts') parser.add_argument("solidity_file", nargs='*') commands = parser.add_argument_group('commands') commands.add_argument('-g', '--graph', help='generate a control flow graph') commands.add_argument('-V', '--version', action='store_true', help='print the Mythril version number and exit') commands.add_argument( '-x', '--fire-lasers', action='store_true', help='detect vulnerabilities, use with -c, -a or solidity file(s)') commands.add_argument( '-n', '--annotations', action='store_true', help='detect broken annotations, used with solidity file(s)') commands.add_argument( '-cn', '--contract-name', help= 'Specify the contract by name who\'s annotations should be checked.', metavar='cname') commands.add_argument( '-cf', '--config_file', help= 'Allows to specify a config file to set configurations for the annotary analysis', metavar='config_file') commands.add_argument( '-t', '--truffle', action='store_true', help='analyze a truffle project (run from project dir)') commands.add_argument('-d', '--disassemble', action='store_true', help='print disassembly') commands.add_argument('-j', '--statespace-json', help='dumps the statespace json', metavar='OUTPUT_FILE') inputs = parser.add_argument_group('input arguments') inputs.add_argument('-c', '--code', help='hex-encoded bytecode string ("6060604052...")', metavar='BYTECODE') inputs.add_argument('-f', '--codefile', help='file containing hex-encoded bytecode string', metavar='BYTECODEFILE', type=argparse.FileType('r')) inputs.add_argument('-a', '--address', help='pull contract from the blockchain', metavar='CONTRACT_ADDRESS') inputs.add_argument('-l', '--dynld', action='store_true', help='auto-load dependencies from the blockchain') outputs = parser.add_argument_group('output formats') outputs.add_argument('-o', '--outform', choices=['text', 'markdown', 'json'], default='text', help='report output format', metavar='<text/markdown/json>') outputs.add_argument('--verbose-report', action='store_true', help='Include debugging information in report') database = parser.add_argument_group('local contracts database') database.add_argument('-s', '--search', help='search the contract database', metavar='EXPRESSION') database.add_argument( '--leveldb-dir', help='specify leveldb directory for search or direct access operations', metavar='LEVELDB_PATH') utilities = parser.add_argument_group('utilities') utilities.add_argument('--hash', help='calculate function signature hash', metavar='SIGNATURE') utilities.add_argument( '--storage', help='read state variables from storage index, use with -a', metavar='INDEX,NUM_SLOTS,[array] / mapping,INDEX,[KEY1, KEY2...]') utilities.add_argument( '--solv', help= 'specify solidity compiler version. If not present, will try to install it (Experimental)', metavar='SOLV') utilities.add_argument( '--contract-hash-to-address', help='returns corresponding address for a contract address hash', metavar='SHA3_TO_LOOK_FOR') options = parser.add_argument_group('options') options.add_argument( '-m', '--modules', help='Comma-separated list of security analysis modules', metavar='MODULES') options.add_argument('--max-depth', type=int, default=22, help='Maximum recursion depth for symbolic execution') options.add_argument('--strategy', choices=['dfs', 'bfs'], default='dfs', help='Symbolic execution strategy') options.add_argument( '--execution-timeout', type=int, default=600, help="The amount of seconds to spend on symbolic execution") options.add_argument('--create-timeout', type=int, default=10, help="The amount of seconds to spend on " "the initial contract creation") options.add_argument('--solc-args', help='Extra arguments for solc') options.add_argument('--phrack', action='store_true', help='Phrack-style call graph') options.add_argument('--enable-physics', action='store_true', help='enable graph physics simulation') options.add_argument('-v', type=int, help='log level (0-2)', metavar='LOG_LEVEL') rpc = parser.add_argument_group('RPC options') rpc.add_argument('-i', action='store_true', help='Preset: Infura Node service (Mainnet)') rpc.add_argument('--rpc', help='custom RPC settings', metavar='HOST:PORT / ganache / infura-[network_name]') rpc.add_argument('--rpctls', type=bool, default=False, help='RPC connection over TLS') # Get config values args = parser.parse_args() if args.version: if args.outform == 'json': print(json.dumps({'version_str': VERSION})) else: print("Mythril version {}".format(VERSION)) sys.exit() # Parse cmdline args if not (args.search or args.hash or args.disassemble or args.graph or args.fire_lasers or args.storage or args.truffle or args.statespace_json or args.contract_hash_to_address or args.annotations): parser.print_help() sys.exit() if args.v: if 0 <= args.v < 3: logging.basicConfig( level=[logging.NOTSET, logging.INFO, logging.DEBUG][args.v]) else: exit_with_error( args.outform, "Invalid -v value, you can find valid values in usage") # -- commands -- if args.hash: print(Mythril.hash_for_function_signature(args.hash)) sys.exit() try: # the mythril object should be our main interface # infura = None, rpc = None, rpctls = None # solc_args = None, dynld = None, max_recursion_depth = 12): mythril = Mythril(solv=args.solv, dynld=args.dynld, solc_args=args.solc_args) if args.dynld and not (args.rpc or args.i): mythril.set_api_from_config_path() if args.address: # Establish RPC connection if necessary if args.i: mythril.set_api_rpc_infura() elif args.rpc: mythril.set_api_rpc(rpc=args.rpc, rpctls=args.rpctls) elif not args.dynld: mythril.set_api_rpc_localhost() elif args.search or args.contract_hash_to_address: # Open LevelDB if necessary mythril.set_api_leveldb(mythril.leveldb_dir if not args.leveldb_dir else args.leveldb_dir) if args.search: # Database search ops mythril.search_db(args.search) sys.exit() if args.contract_hash_to_address: # search corresponding address try: mythril.contract_hash_to_address(args.contract_hash_to_address) except AddressNotFoundError: print("Address not found.") sys.exit() if args.truffle: try: # not really pythonic atm. needs refactoring mythril.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() # Load / compile input contracts address = None if args.code: # Load from bytecode address, _ = mythril.load_from_bytecode(args.code) elif args.codefile: bytecode = ''.join( [l.strip() for l in args.codefile if len(l.strip()) > 0]) address, _ = mythril.load_from_bytecode(bytecode) elif args.address: # Get bytecode from a contract address address, _ = mythril.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." ) changed_files = args.solidity_file if args.annotations: annotary = Annotary(solc_args=args.solc_args, config_file=args.config_file) annotary.create_tmp_dir() annotary.copy_files_to_tmp(args.solidity_file) annotary.enter_tmp_dir() # create new subdirectory # change working direktory or append path to current source files # do necessary conversions of filenames changed_files = [ annotary.tmp_dir + "/" + (file[file.rfind("/") + 1:] if "/" in file else file) for file in args.solidity_file ] address, _ = mythril.load_from_solidity( changed_files) # 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" ) # Commands 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 = mythril.get_state_variable_from_storage( address=address, params=[a.strip() for a in args.storage.strip().split(",")]) print(storage) elif args.disassemble: easm_text = mythril.contracts[0].get_easm( ) # or mythril.disassemble(mythril.contracts[0]) sys.stdout.write(easm_text) elif args.graph or args.fire_lasers: if not mythril.contracts: exit_with_error( args.outform, "input files do not contain any valid contracts") if args.graph: html = mythril.graph_html( strategy=args.strategy, contract=mythril.contracts[0], address=address, enable_physics=args.enable_physics, phrackify=args.phrack, max_depth=args.max_depth, execution_timeout=args.execution_timeout, create_timeout=args.create_timeout) 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: report = mythril.fire_lasers( strategy=args.strategy, address=address, modules=[ m.strip() for m in args.modules.strip().split(",") ] if args.modules else [], verbose_report=args.verbose_report, max_depth=args.max_depth, execution_timeout=args.execution_timeout, create_timeout=args.create_timeout) outputs = { 'json': report.as_json(), 'text': report.as_text(), 'markdown': report.as_markdown() } print(outputs[args.outform]) if args.annotations: if not mythril.contracts: exit_with_error( args.outform, "input files do not contain any valid contracts") if args.contract_name: annotary.set_contract_name(args.contract_name) annotary.provide_resources(mythril.contracts, address, mythril.eth, mythril.dynld, mythril.sigs) annotary.parse_annotations() annotary.check_annotations() print(annotary.get_annotation_json()) printend() annotary.delete_tmp_dir() elif args.statespace_json: if not mythril.contracts: exit_with_error( args.outform, "input files do not contain any valid contracts") statespace = mythril.dump_statespace( strategy=args.strategy, contract=mythril.contracts[0], address=address, max_depth=args.max_depth, execution_timeout=args.execution_timeout, create_timeout=args.create_timeout) 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() except CriticalError as ce: exit_with_error(args.outform, str(ce))
def main(): parser = argparse.ArgumentParser( description="Security analysis of Ethereum smart contracts") parser.add_argument("solidity_file", nargs="*") commands = parser.add_argument_group("commands") commands.add_argument("-g", "--graph", help="generate a control flow graph") commands.add_argument( "-V", "--version", action="store_true", help="print the Mythril version number and exit", ) commands.add_argument( "-x", "--fire-lasers", action="store_true", help="detect vulnerabilities, use with -c, -a or solidity file(s)", ) commands.add_argument( "--truffle", action="store_true", help="analyze a truffle project (run from project dir)", ) commands.add_argument("-d", "--disassemble", action="store_true", help="print disassembly") commands.add_argument( "-j", "--statespace-json", help="dumps the statespace json", metavar="OUTPUT_FILE", ) inputs = parser.add_argument_group("input arguments") inputs.add_argument( "-c", "--code", help='hex-encoded bytecode string ("6060604052...")', metavar="BYTECODE", ) inputs.add_argument( "-f", "--codefile", help="file containing hex-encoded bytecode string", metavar="BYTECODEFILE", type=argparse.FileType("r"), ) inputs.add_argument( "-a", "--address", help="pull contract from the blockchain", metavar="CONTRACT_ADDRESS", ) inputs.add_argument( "-l", "--dynld", action="store_true", help="auto-load dependencies from the blockchain", ) inputs.add_argument( "--no-onchain-storage-access", action="store_true", help="turns off getting the data from onchain contracts", ) inputs.add_argument( "--bin-runtime", action="store_true", help= "Only when -c or -f is used. Consider the input bytecode as binary runtime code, default being the contract creation bytecode.", ) outputs = parser.add_argument_group("output formats") outputs.add_argument( "-o", "--outform", choices=["text", "markdown", "json"], default="text", help="report output format", metavar="<text/markdown/json>", ) outputs.add_argument( "--verbose-report", action="store_true", help="Include debugging information in report", ) database = parser.add_argument_group("local contracts database") database.add_argument("-s", "--search", help="search the contract database", metavar="EXPRESSION") database.add_argument( "--leveldb-dir", help="specify leveldb directory for search or direct access operations", metavar="LEVELDB_PATH", ) utilities = parser.add_argument_group("utilities") utilities.add_argument("--hash", help="calculate function signature hash", metavar="SIGNATURE") utilities.add_argument( "--storage", help="read state variables from storage index, use with -a", metavar="INDEX,NUM_SLOTS,[array] / mapping,INDEX,[KEY1, KEY2...]", ) utilities.add_argument( "--solv", help= "specify solidity compiler version. If not present, will try to install it (Experimental)", metavar="SOLV", ) utilities.add_argument( "--contract-hash-to-address", help="returns corresponding address for a contract address hash", metavar="SHA3_TO_LOOK_FOR", ) options = parser.add_argument_group("options") options.add_argument( "-m", "--modules", help="Comma-separated list of security analysis modules", metavar="MODULES", ) options.add_argument( "--max-depth", type=int, default=22, help="Maximum recursion depth for symbolic execution", ) options.add_argument( "--strategy", choices=["dfs", "bfs", "naive-random", "weighted-random"], default="dfs", help="Symbolic execution strategy", ) options.add_argument( "-t", "--transaction-count", type=int, default=2, help="Maximum number of transactions issued by laser", ) options.add_argument( "--execution-timeout", type=int, default=600, help="The amount of seconds to spend on symbolic execution", ) options.add_argument( "--create-timeout", type=int, default=10, help="The amount of seconds to spend on " "the initial contract creation", ) options.add_argument("--solc-args", help="Extra arguments for solc") options.add_argument("--phrack", action="store_true", help="Phrack-style call graph") options.add_argument("--enable-physics", action="store_true", help="enable graph physics simulation") options.add_argument("-v", type=int, help="log level (0-2)", metavar="LOG_LEVEL") options.add_argument( "-q", "--query-signature", action="store_true", help="Lookup function signatures through www.4byte.directory", ) rpc = parser.add_argument_group("RPC options") rpc.add_argument( "--rpc", help="custom RPC settings", metavar="HOST:PORT / ganache / infura-[network_name]", default="infura-mainnet", ) rpc.add_argument("--rpctls", type=bool, default=False, help="RPC connection over TLS") parser.add_argument("--epic", action="store_true", help=argparse.SUPPRESS) # Get config values args = parser.parse_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 if not (args.search or args.hash or args.disassemble or args.graph or args.fire_lasers or args.storage or args.truffle or args.statespace_json or args.contract_hash_to_address): parser.print_help() sys.exit() if args.v: if 0 <= args.v < 3: coloredlogs.install( fmt="%(name)s[%(process)d] %(levelname)s %(message)s", level=[logging.NOTSET, logging.INFO, logging.DEBUG][args.v], ) else: exit_with_error( args.outform, "Invalid -v value, you can find valid values in usage") if args.query_signature: if sigs.ethereum_input_decoder == None: exit_with_error( args.outform, "The --query-signature function requires the python package ethereum-input-decoder", ) # -- commands -- if args.hash: print(Mythril.hash_for_function_signature(args.hash)) sys.exit() try: # the mythril object should be our main interface # infura = None, rpc = None, rpctls = None # solc_args = None, dynld = None, max_recursion_depth = 12): mythril = Mythril( solv=args.solv, dynld=args.dynld, onchain_storage_access=(not args.no_onchain_storage_access), solc_args=args.solc_args, enable_online_lookup=args.query_signature, ) if (args.dynld or not args.no_onchain_storage_access and not (args.rpc or args.i)): mythril.set_api_from_config_path() if args.address: # Establish RPC connection if necessary mythril.set_api_rpc(rpc=args.rpc, rpctls=args.rpctls) elif args.search or args.contract_hash_to_address: # Open LevelDB if necessary mythril.set_api_leveldb(mythril.leveldb_dir if not args.leveldb_dir else args.leveldb_dir) if args.search: # Database search ops mythril.search_db(args.search) sys.exit() if args.contract_hash_to_address: # search corresponding address try: mythril.contract_hash_to_address(args.contract_hash_to_address) except AddressNotFoundError: print("Address not found.") sys.exit() if args.truffle: try: # not really pythonic atm. needs refactoring mythril.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() # Load / compile input contracts address = None if args.code: # Load from bytecode code = args.code[2:] if args.code.startswith("0x") else args.code address, _ = mythril.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, _ = mythril.load_from_bytecode(bytecode, args.bin_runtime) elif args.address: # Get bytecode from a contract address address, _ = mythril.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, _ = mythril.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", ) # Commands 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 = mythril.get_state_variable_from_storage( address=address, params=[a.strip() for a in args.storage.strip().split(",")], ) print(storage) elif args.disassemble: easm_text = mythril.contracts[0].get_easm( ) # or mythril.disassemble(mythril.contracts[0]) sys.stdout.write(easm_text) elif args.graph or args.fire_lasers: if not mythril.contracts: exit_with_error( args.outform, "input files do not contain any valid contracts") if args.graph: html = mythril.graph_html( strategy=args.strategy, contract=mythril.contracts[0], address=address, enable_physics=args.enable_physics, phrackify=args.phrack, max_depth=args.max_depth, execution_timeout=args.execution_timeout, create_timeout=args.create_timeout, ) 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 = mythril.fire_lasers( strategy=args.strategy, address=address, modules=[ m.strip() for m in args.modules.strip().split(",") ] if args.modules else [], verbose_report=args.verbose_report, max_depth=args.max_depth, execution_timeout=args.execution_timeout, create_timeout=args.create_timeout, transaction_count=args.transaction_count, ) outputs = { "json": report.as_json(), "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 mythril.contracts: exit_with_error( args.outform, "input files do not contain any valid contracts") statespace = mythril.dump_statespace( strategy=args.strategy, contract=mythril.contracts[0], address=address, max_depth=args.max_depth, execution_timeout=args.execution_timeout, create_timeout=args.create_timeout, ) 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() except CriticalError as ce: exit_with_error(args.outform, str(ce))
def main(): parser = argparse.ArgumentParser( description='Security analysis of Ethereum smart contracts') parser.add_argument("solidity_file", nargs='*') commands = parser.add_argument_group('commands') commands.add_argument('-g', '--graph', help='generate a control flow graph') commands.add_argument( '-x', '--fire-lasers', action='store_true', help='detect vulnerabilities, use with -c, -a or solidity file(s)') commands.add_argument( '-t', '--truffle', action='store_true', help='analyze a truffle project (run from project dir)') commands.add_argument('-d', '--disassemble', action='store_true', help='print disassembly') commands.add_argument('-j', '--statespace-json', help='dumps the statespace json', metavar='OUTPUT_FILE') inputs = parser.add_argument_group('input arguments') inputs.add_argument('-c', '--code', help='hex-encoded bytecode string ("6060604052...")', metavar='BYTECODE') inputs.add_argument('-a', '--address', help='pull contract from the blockchain', metavar='CONTRACT_ADDRESS') inputs.add_argument('-l', '--dynld', action='store_true', help='auto-load dependencies from the blockchain') outputs = parser.add_argument_group('output formats') outputs.add_argument('-o', '--outform', choices=['text', 'markdown', 'json'], default='text', help='report output format', metavar='<text/json>') outputs.add_argument('--verbose-report', action='store_true', help='Include debugging information in report') database = parser.add_argument_group('local contracts database') database.add_argument('--init-db', action='store_true', help='initialize the contract database') database.add_argument('-s', '--search', help='search the contract database', metavar='EXPRESSION') utilities = parser.add_argument_group('utilities') utilities.add_argument('--hash', help='calculate function signature hash', metavar='SIGNATURE') utilities.add_argument( '--storage', help='read state variables from storage index, use with -a', metavar='INDEX,NUM_SLOTS,[array] / mapping,INDEX,[KEY1, KEY2...]') utilities.add_argument( '--solv', help= 'specify solidity compiler version. If not present, will try to install it (Experimental)', metavar='SOLV') options = parser.add_argument_group('options') options.add_argument( '-m', '--modules', help='Comma-separated list of security analysis modules', metavar='MODULES') options.add_argument('--max-depth', type=int, default=12, help='Maximum recursion depth for symbolic execution') options.add_argument('--solc-args', help='Extra arguments for solc') options.add_argument('--phrack', action='store_true', help='Phrack-style call graph') options.add_argument('--enable-physics', action='store_true', help='enable graph physics simulation') options.add_argument('-v', type=int, help='log level (0-2)', metavar='LOG_LEVEL') options.add_argument('--leveldb', help='enable direct leveldb access operations', metavar='LEVELDB_PATH') rpc = parser.add_argument_group('RPC options') rpc.add_argument('-i', action='store_true', help='Preset: Infura Node service (Mainnet)') rpc.add_argument('--rpc', help='custom RPC settings', metavar='HOST:PORT / ganache / infura-[network_name]') rpc.add_argument('--rpctls', type=bool, default=False, help='RPC connection over TLS') rpc.add_argument('--ipc', action='store_true', help='Connect via local IPC') # Get config values args = parser.parse_args() # -- args sanity checks -- # Detect unsupported combinations of command line args if args.dynld and not args.address: exit_with_error( args.outform, "Dynamic loader can be used in on-chain analysis mode only (-a).") # Parse cmdline args if not (args.search or args.init_db or args.hash or args.disassemble or args.graph or args.fire_lasers or args.storage or args.truffle or args.statespace_json): parser.print_help() sys.exit() if args.v: if 0 <= args.v < 3: logging.basicConfig( level=[logging.NOTSET, logging.INFO, logging.DEBUG][args.v]) else: exit_with_error( args.outform, "Invalid -v value, you can find valid values in usage") # -- commands -- if args.hash: print(Mythril.hash_for_function_signature(args.hash)) sys.exit() try: # the mythril object should be our main interface #init_db = None, infura = None, rpc = None, rpctls = None, ipc = None, #solc_args = None, dynld = None, max_recursion_depth = 12): mythril = Mythril(solv=args.solv, dynld=args.dynld, solc_args=args.solc_args) if args.leveldb: # Open LevelDB if specified mythril.set_db_leveldb(args.leveldb) elif (args.address or args.init_db) and not args.leveldb: # Establish RPC/IPC connection if necessary if args.i: mythril.set_db_rpc_infura() elif args.rpc: mythril.set_db_rpc(rpc=args.rpc, rpctls=args.rpctls) elif args.ipc: mythril.set_db_ipc() else: mythril.set_db_rpc_localhost() if args.truffle: try: # not really pythonic atm. needs refactoring mythril.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() elif args.search: # Database search ops mythril.search_db(args.search) sys.exit() elif args.init_db: mythril.init_db() sys.exit() # Load / compile input contracts address = None if args.code: # Load from bytecode address, _ = mythril.load_from_bytecode(args.code) elif args.address: # Get bytecode from a contract address address, _ = mythril.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, _ = mythril.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" ) # Commands 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 = mythril.get_state_variable_from_storage( address=address, params=[a.strip() for a in args.storage.strip().split(",")]) print(storage) elif args.disassemble: easm_text = mythril.contracts[0].get_easm( ) # or mythril.disassemble(mythril.contracts[0]) sys.stdout.write(easm_text) elif args.graph or args.fire_lasers: if not mythril.contracts: exit_with_error( args.outform, "input files do not contain any valid contracts") if args.graph: html = mythril.graph_html(mythril.contracts[0], address=address, enable_physics=args.enable_physics, phrackify=args.phrack, max_depth=args.max_depth) 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: report = mythril.fire_lasers( address=address, modules=[ m.strip() for m in args.modules.strip().split(",") ] if args.modules else [], verbose_report=args.verbose_report, max_depth=args.max_depth) outputs = { 'json': report.as_json(), 'text': report.as_text() or "The analysis was completed successfully. No issues were detected.", 'markdown': report.as_markdown() or "The analysis was completed successfully. No issues were detected." } print(outputs[args.outform]) elif args.statespace_json: if not mythril.contracts: exit_with_error( args.outform, "input files do not contain any valid contracts") statespace = mythril.dump_statespace(mythril.contracts[0], address=address, max_depth=args.max_depth) 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() except CriticalError as ce: exit_with_error(args.outform, str(ce))
def mymain(file): # --zyx cmd = [ "solc", "--combined-json", "bin,bin-runtime,srcmap-runtime", '--allow-paths', "." ] cmd.append(file) p = Popen(cmd, stdout=PIPE, stderr=PIPE) stdout, stderr = p.communicate() ret = p.returncode if ret != 0: return "Solc experienced a fatal error (code" + str( ret) + ").\n\n" + str(stderr.decode('UTF-8')), 0 # --zyx parser = argparse.ArgumentParser( description='Security analysis of Ethereum smart contracts') parser.add_argument("solidity_file", nargs='*') commands = parser.add_argument_group('commands') commands.add_argument('-g', '--graph', help='generate a control flow graph') commands.add_argument('-V', '--version', action='store_true', help='print the Mythril version number and exit') commands.add_argument( '-x', '--fire-lasers', action='store_true', help='detect vulnerabilities, use with -c, -a or solidity file(s)') commands.add_argument( '-t', '--truffle', action='store_true', help='analyze a truffle project (run from project dir)') commands.add_argument('-d', '--disassemble', action='store_true', help='print disassembly') commands.add_argument('-j', '--statespace-json', help='dumps the statespace json', metavar='OUTPUT_FILE') inputs = parser.add_argument_group('input arguments') inputs.add_argument('-c', '--code', help='hex-encoded bytecode string ("6060604052...")', metavar='BYTECODE') inputs.add_argument('-a', '--address', help='pull contract from the blockchain', metavar='CONTRACT_ADDRESS') inputs.add_argument('-l', '--dynld', action='store_true', help='auto-load dependencies from the blockchain') outputs = parser.add_argument_group('output formats') outputs.add_argument('-o', '--outform', choices=['text', 'markdown', 'json'], default='text', help='report output format', metavar='<text/json>') outputs.add_argument('--verbose-report', action='store_true', help='Include debugging information in report') database = parser.add_argument_group('local contracts database') database.add_argument('-s', '--search', help='search the contract database', metavar='EXPRESSION') database.add_argument( '--leveldb-dir', help='specify leveldb directory for search or direct access operations', metavar='LEVELDB_PATH') utilities = parser.add_argument_group('utilities') utilities.add_argument('--hash', help='calculate function signature hash', metavar='SIGNATURE') utilities.add_argument( '--storage', help='read state variables from storage index, use with -a', metavar='INDEX,NUM_SLOTS,[array] / mapping,INDEX,[KEY1, KEY2...]') utilities.add_argument( '--solv', help= 'specify solidity compiler version. If not present, will try to install it (Experimental)', metavar='SOLV') utilities.add_argument( '--contract-hash-to-address', help='returns corresponding address for a contract address hash', metavar='SHA3_TO_LOOK_FOR') options = parser.add_argument_group('options') options.add_argument( '-m', '--modules', help='Comma-separated list of security analysis modules', metavar='MODULES') options.add_argument('--max-depth', type=int, default=22, help='Maximum recursion depth for symbolic execution') options.add_argument('--execution-timeout', type=int, default=600, help="The amount of seconds to spend on " "symbolic execution") outputs.add_argument('--strategy', choices=['dfs', 'bfs'], default='dfs', help='Symbolic execution strategy') options.add_argument('--solc-args', help='Extra arguments for solc') options.add_argument('--phrack', action='store_true', help='Phrack-style call graph') options.add_argument('--enable-physics', action='store_true', help='enable graph physics simulation') options.add_argument('-v', type=int, help='log level (0-2)', metavar='LOG_LEVEL') rpc = parser.add_argument_group('RPC options') rpc.add_argument('-i', action='store_true', help='Preset: Infura Node service (Mainnet)') rpc.add_argument('--rpc', help='custom RPC settings', metavar='HOST:PORT / ganache / infura-[network_name]') rpc.add_argument('--rpctls', type=bool, default=False, help='RPC connection over TLS') rpc.add_argument('--ipc', action='store_true', help='Connect via local IPC') rpc.add_argument('--leveldb', action='store_true', help='Enable direct leveldb access operations') # Get config values # args = parser.parse_args() # print("args: "+str(type(args))) # print("args : " + str(args)) # I did it --zyx arrr = argparse.Namespace(address=None, code=None, contract_hash_to_address=None, disassemble=False, dynld=False, enable_physics=False, execution_timeout=600, fire_lasers=True, graph=None, hash=None, i=False, ipc=False, leveldb=False, leveldb_dir=None, max_depth=22, modules=None, outform='text', phrack=False, rpc=None, rpctls=False, search=None, solc_args=None, solidity_file=[str(file)], solv=None, statespace_json=None, storage=None, strategy='dfs', truffle=False, v=None, verbose_report=False, version=False) ''' args : Namespace(address=None, code=None, contract_hash_to_address=None, disassemble=False, dynld=False, enable_physics=False, execution_timeout=600, fire_lasers=True, graph=None, hash=None, i=False, ipc=False, leveldb=False, leveldb_dir=None, max_depth=22, modules=None, outform='text', phrack=False, rpc=None, rpctls=False, search=None, solc_args=None, solidity_file=['calls.sol'], solv=None, statespace_json=None, storage=None, strategy='dfs', truffle=False, v=None, verbose_report=False, version=False) ''' # the rest arrr if changed from args and It's me who did this --zyx if arrr.version: print("Mythril version {}".format(VERSION)) sys.exit() # Parse cmdline args if not (arrr.search or arrr.hash or arrr.disassemble or arrr.graph or arrr.fire_lasers or arrr.storage or arrr.truffle or arrr.statespace_json or arrr.contract_hash_to_address): parser.print_help() sys.exit() if arrr.v: if 0 <= arrr.v < 3: logging.basicConfig( level=[logging.NOTSET, logging.INFO, logging.DEBUG][arrr.v]) else: exit_with_error( arrr.outform, "Invalid -v value, you can find valid values in usage") # -- commands -- if arrr.hash: print(Mythril.hash_for_function_signature(arrr.hash)) sys.exit() try: # the mythril object should be our main interface # infura = None, rpc = None, rpctls = None, ipc = None, # solc_args = None, dynld = None, max_recursion_depth = 12): mythril = Mythril(solv=arrr.solv, dynld=arrr.dynld, solc_args=arrr.solc_args) if arrr.dynld and not (arrr.ipc or arrr.rpc or arrr.i): mythril.set_api_from_config_path() if arrr.address and not arrr.leveldb: # Establish RPC/IPC connection if necessary if arrr.i: mythril.set_api_rpc_infura() elif arrr.rpc: mythril.set_api_rpc(rpc=arrr.rpc, rpctls=arrr.rpctls) elif arrr.ipc: mythril.set_api_ipc() elif not arrr.dynld: mythril.set_api_rpc_localhost() elif arrr.leveldb or arrr.search or arrr.contract_hash_to_address: # Open LevelDB if necessary mythril.set_api_leveldb(mythril.leveldb_dir if not arrr.leveldb_dir else arrr.leveldb_dir) if arrr.search: # Database search ops mythril.search_db(arrr.search) sys.exit() if arrr.contract_hash_to_address: # search corresponding address mythril.contract_hash_to_address(arrr.contract_hash_to_address) sys.exit() if arrr.truffle: try: # not really pythonic atm. needs refactoring mythril.analyze_truffle_project(arrr) 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() # Load / compile input contracts address = None if arrr.code: # Load from bytecode address, _ = mythril.load_from_bytecode(arrr.code) elif arrr.address: # Get bytecode from a contract address address, _ = mythril.load_from_address(arrr.address) elif arrr.solidity_file: # Compile Solidity source file(s) if arrr.graph and len(arrr.solidity_file) > 1: exit_with_error( arrr.outform, "Cannot generate call graphs from multiple input files. Please do it one at a time." ) address, _ = mythril.load_from_solidity( arrr.solidity_file) # list of files else: exit_with_error( arrr.outform, "No input bytecode. Please provide EVM code via -c BYTECODE, -a ADDRESS, or -i SOLIDITY_FILES" ) # Commands if arrr.storage: if not arrr.address: exit_with_error( arrr.outform, "To read storage, provide the address of a deployed contract with the -a option." ) storage = mythril.get_state_variable_from_storage( address=address, params=[a.strip() for a in arrr.storage.strip().split(",")]) print(storage) elif arrr.disassemble: easm_text = mythril.contracts[0].get_easm( ) # or mythril.disassemble(mythril.contracts[0]) sys.stdout.write(easm_text) elif arrr.graph or arrr.fire_lasers: if not mythril.contracts: exit_with_error( arrr.outform, "input files do not contain any valid contracts") if arrr.graph: html = mythril.graph_html(strategy=arrr.strategy, contract=mythril.contracts[0], address=address, enable_physics=arrr.enable_physics, phrackify=arrr.phrack, max_depth=arrr.max_depth) try: with open(arrr.graph, "w") as f: f.write(html) except Exception as e: exit_with_error(arrr.outform, "Error saving graph: " + str(e)) else: report = mythril.fire_lasers( strategy=arrr.strategy, address=address, modules=[ m.strip() for m in arrr.modules.strip().split(",") ] if arrr.modules else [], verbose_report=arrr.verbose_report, max_depth=arrr.max_depth, execution_timeout=arrr.execution_timeout) outputs = { 'json': report.as_json(), 'text': report.as_text(), 'markdown': report.as_markdown() } #输出 print(outputs[arrr.outform]) elif arrr.statespace_json: if not mythril.contracts: exit_with_error( arrr.outform, "input files do not contain any valid contracts") statespace = mythril.dump_statespace(strategy=arrr.strategy, contract=mythril.contracts[0], address=address, max_depth=arrr.max_depth) try: with open(arrr.statespace_json, "w") as f: json.dump(statespace, f) except Exception as e: exit_with_error(arrr.outform, "Error saving json: " + str(e)) else: parser.print_help() except CriticalError as ce: exit_with_error(arrr.outform, str(ce)) return mythril.zyx_lasers( strategy=arrr.strategy, address=address, modules=[m.strip() for m in arrr.modules.strip().split(",")] if arrr.modules else [], verbose_report=arrr.verbose_report, max_depth=arrr.max_depth, execution_timeout=arrr.execution_timeout), 1