From 24550d2ebf24465ba090b83534f9b95a927705c6 Mon Sep 17 00:00:00 2001 From: Bernhard Mueller Date: Tue, 31 Jul 2018 21:28:50 +0700 Subject: [PATCH 1/6] Make enable_online_lookup configurable in Disassembly class (plus some code cleanup) --- mythril/disassembler/disassembly.py | 4 ++-- mythril/support/loader.py | 4 +--- mythril/support/signatures.py | 6 ++---- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/mythril/disassembler/disassembly.py b/mythril/disassembler/disassembly.py index 2f7aec4c..e0e2034a 100644 --- a/mythril/disassembler/disassembly.py +++ b/mythril/disassembler/disassembly.py @@ -5,14 +5,14 @@ import logging class Disassembly(object): - def __init__(self, code): + def __init__(self, code, enable_online_lookup=True): self.instruction_list = asm.disassemble(util.safe_decode(code)) self.func_hashes = [] self.func_to_addr = {} self.addr_to_func = {} self.bytecode = code - signatures = SignatureDb(enable_online_lookup=True) # control if you want to have online sighash lookups + signatures = SignatureDb(enable_online_lookup) # control if you want to have online sighash lookups try: signatures.open() # open from default locations except FileNotFoundError: diff --git a/mythril/support/loader.py b/mythril/support/loader.py index 6559b840..7c0855ec 100644 --- a/mythril/support/loader.py +++ b/mythril/support/loader.py @@ -1,15 +1,14 @@ -from mythril.ether.ethcontract import ETHContract from mythril.disassembler.disassembly import Disassembly import logging import re + class DynLoader: def __init__(self, eth): self.eth = eth self.storage_cache = {} - def read_storage(self, contract_address, index): try: @@ -32,7 +31,6 @@ class DynLoader: return data - def dynld(self, contract_address, dependency_address): logging.info("Dynld at contract " + contract_address + ": " + dependency_address) diff --git a/mythril/support/signatures.py b/mythril/support/signatures.py index ac62a2e1..e5411d06 100644 --- a/mythril/support/signatures.py +++ b/mythril/support/signatures.py @@ -33,7 +33,7 @@ class SimpleFileLock(object): if self.locked: raise Exception("SimpleFileLock: lock already aquired") - t_end = time.time()+timeout + t_end = time.time() + timeout while time.time() < t_end: # try to aquire lock try: @@ -49,7 +49,7 @@ class SimpleFileLock(object): time.sleep(0.5) # busywait is evil continue - raise Exception("SimpleFileLock: timeout hit. failed to aquire lock: %s"% (time.time()-self.lockfile.stat().st_mtime)) + raise Exception("SimpleFileLock: timeout hit. failed to aquire lock: %s" % (time.time() - self.lockfile.stat().st_mtime)) def release(self, force=False): if not force and not self.locked: @@ -63,7 +63,6 @@ class SimpleFileLock(object): self.locked = False - class SignatureDb(object): def __init__(self, enable_online_lookup=True): @@ -172,7 +171,6 @@ class SignatureDb(object): return [self.signatures[sighash]] return self.signatures[sighash] # raise keyerror - def __getitem__(self, item): """ Provide dict interface Signatures()[sighash] From 224e03d201b7eda2dfb777cb5a5dad1ac18ad1dc Mon Sep 17 00:00:00 2001 From: Bernhard Mueller Date: Tue, 31 Jul 2018 22:19:39 +0700 Subject: [PATCH 2/6] Make online lookup configurable in ETHContract constructor & disable it in search --- mythril/disassembler/disassembly.py | 2 +- mythril/ether/ethcontract.py | 4 ++-- mythril/leveldb/client.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mythril/disassembler/disassembly.py b/mythril/disassembler/disassembly.py index e0e2034a..394f22b1 100644 --- a/mythril/disassembler/disassembly.py +++ b/mythril/disassembler/disassembly.py @@ -12,7 +12,7 @@ class Disassembly(object): self.addr_to_func = {} self.bytecode = code - signatures = SignatureDb(enable_online_lookup) # control if you want to have online sighash lookups + signatures = SignatureDb(enable_online_lookup=enable_online_lookup) # control if you want to have online sighash lookups try: signatures.open() # open from default locations except FileNotFoundError: diff --git a/mythril/ether/ethcontract.py b/mythril/ether/ethcontract.py index c262b3c1..035801c1 100644 --- a/mythril/ether/ethcontract.py +++ b/mythril/ether/ethcontract.py @@ -6,7 +6,7 @@ import re class ETHContract(persistent.Persistent): - def __init__(self, code, creation_code="", name="Unknown"): + def __init__(self, code, creation_code="", name="Unknown", enable_online_lookup=True): self.creation_code = creation_code self.name = name @@ -17,7 +17,7 @@ class ETHContract(persistent.Persistent): code = re.sub(r'(_+.*_+)', 'aa' * 20, code) self.code = code - self.disassembly = Disassembly(self.code) + self.disassembly = Disassembly(self.code, enable_online_lookup=enable_online_lookup) def as_dict(self): diff --git a/mythril/leveldb/client.py b/mythril/leveldb/client.py index 657d819a..231fe98f 100644 --- a/mythril/leveldb/client.py +++ b/mythril/leveldb/client.py @@ -184,7 +184,7 @@ class EthLevelDB(object): for account in self.reader._get_head_state().get_all_accounts(): if account.code is not None: code = _encode_hex(account.code) - contract = ETHContract(code) + contract = ETHContract(code, enable_online_lookup=False) address = indexer.get_contract_by_hash(account.address) if address is None: address = account.address From 1ebc791b9f64f1dde8270529442f966673719106 Mon Sep 17 00:00:00 2001 From: Bernhard Mueller Date: Wed, 1 Aug 2018 21:56:47 +0700 Subject: [PATCH 3/6] Remove --leveldb cmdline argument --- mythril/interfaces/cli.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index 341d8889..c097a2e4 100644 --- a/mythril/interfaces/cli.py +++ b/mythril/interfaces/cli.py @@ -82,7 +82,6 @@ def main(): 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 @@ -120,7 +119,7 @@ def main(): if args.dynld and not (args.ipc or args.rpc or args.i): mythril.set_api_from_config_path() - if args.address and not args.leveldb: + if args.address: # Establish RPC/IPC connection if necessary if args.i: mythril.set_api_rpc_infura() @@ -130,7 +129,7 @@ def main(): mythril.set_api_ipc() elif not args.dynld: mythril.set_api_rpc_localhost() - elif args.leveldb or args.search or args.contract_hash_to_address: + 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) From 62ff1f44fe6c8da9f53ea0ab12f70ec2a0b22ad9 Mon Sep 17 00:00:00 2001 From: Bernhard Mueller Date: Wed, 1 Aug 2018 22:00:43 +0700 Subject: [PATCH 4/6] Move 'strategy' argument to 'options' --- mythril/interfaces/cli.py | 8 ++------ tests/mythril_dir/config.ini | 4 ++++ 2 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 tests/mythril_dir/config.ini diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index c097a2e4..bcd4aeaa 100644 --- a/mythril/interfaces/cli.py +++ b/mythril/interfaces/cli.py @@ -67,11 +67,8 @@ def main(): 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('--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('--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') @@ -81,7 +78,6 @@ def main(): 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 diff --git a/tests/mythril_dir/config.ini b/tests/mythril_dir/config.ini new file mode 100644 index 00000000..71bab4ea --- /dev/null +++ b/tests/mythril_dir/config.ini @@ -0,0 +1,4 @@ +[defaults] +leveldb_dir = /Users/bernhardmueller/Library/Ethereum/geth/chaindata +dynamic_loading = infura + From 2051cfcb2febdbfae48b50834f8b8ae35500bb8c Mon Sep 17 00:00:00 2001 From: Bernhard Mueller Date: Wed, 1 Aug 2018 22:01:12 +0700 Subject: [PATCH 5/6] Remove config.ini --- tests/mythril_dir/config.ini | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 tests/mythril_dir/config.ini diff --git a/tests/mythril_dir/config.ini b/tests/mythril_dir/config.ini deleted file mode 100644 index 71bab4ea..00000000 --- a/tests/mythril_dir/config.ini +++ /dev/null @@ -1,4 +0,0 @@ -[defaults] -leveldb_dir = /Users/bernhardmueller/Library/Ethereum/geth/chaindata -dynamic_loading = infura - From 790b3517720d7226269e1bb7dc86ceb458d9f0f1 Mon Sep 17 00:00:00 2001 From: Bernhard Mueller Date: Thu, 2 Aug 2018 12:30:19 +0700 Subject: [PATCH 6/6] Resolve address hashes only when a match is found --- mythril/exceptions.py | 11 ++++++--- mythril/leveldb/accountindexing.py | 4 +++ mythril/leveldb/client.py | 39 ++++++++++++++++++++---------- 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/mythril/exceptions.py b/mythril/exceptions.py index 2c7dc510..bbcb5110 100644 --- a/mythril/exceptions.py +++ b/mythril/exceptions.py @@ -1,19 +1,22 @@ -class MythrilBaseException(Exception): +class MythrilBaseException(Exception): pass -class CompilerError(MythrilBaseException): +class CompilerError(MythrilBaseException): pass -class UnsatError(MythrilBaseException): +class UnsatError(MythrilBaseException): pass -class NoContractFoundError(MythrilBaseException): +class NoContractFoundError(MythrilBaseException): pass class CriticalError(MythrilBaseException): pass + +class AddressNotFoundError(MythrilBaseException): + pass diff --git a/mythril/leveldb/accountindexing.py b/mythril/leveldb/accountindexing.py index 75acf387..09d21edc 100644 --- a/mythril/leveldb/accountindexing.py +++ b/mythril/leveldb/accountindexing.py @@ -6,6 +6,7 @@ import rlp from rlp.sedes import big_endian_int, binary from ethereum import utils from ethereum.utils import hash32, address, int256 +from mythril.exceptions import AddressNotFoundError BATCH_SIZE = 8 * 4096 @@ -65,6 +66,9 @@ class AccountIndexer(object): address = self.db.reader._get_address_by_hash(contract_hash) if address is not None: return address + else: + raise AddressNotFoundError + self.updateIfNeeded() return self.db.reader._get_address_by_hash(contract_hash) diff --git a/mythril/leveldb/client.py b/mythril/leveldb/client.py index 3507264d..15a14e06 100644 --- a/mythril/leveldb/client.py +++ b/mythril/leveldb/client.py @@ -8,6 +8,7 @@ from ethereum.block import BlockHeader, Block from mythril.leveldb.state import State from mythril.leveldb.eth_db import ETH_DB from mythril.ether.ethcontract import ETHContract +from mythril.exceptions import AddressNotFoundError # Per https://github.com/ethereum/go-ethereum/blob/master/core/database_util.go # prefixes and suffixes for keys in geth @@ -180,25 +181,37 @@ class EthLevelDB(object): ''' iterate through all contracts ''' - indexer = AccountIndexer(self) for account in self.reader._get_head_state().get_all_accounts(): if account.code is not None: code = _encode_hex(account.code) contract = ETHContract(code, enable_online_lookup=False) - address = indexer.get_contract_by_hash(account.address) - if address is None: - address = account.address - yield contract, _encode_hex(address), account.balance + + # print("Resolving: %s | %s (%d)" % (account.address, _encode_hex(account.address), len(account.address))) + + # if(account.address[0] == 0): + # print("Zero first") + # print(account.address) + + # if address is None: + # address = account.address + yield contract, account.address, account.balance def search(self, expression, callback_func): ''' searches through non-zero balance contracts ''' cnt = 0 + indexer = AccountIndexer(self) - for contract, address, balance in self.get_contracts(): + for contract, address_hash, balance in self.get_contracts(): if contract.matches_expression(expression): + + try: + address = _encode_hex(indexer.get_contract_by_hash(address_hash)) + except AddressNotFoundError: + address = _encode_hex(address_hash) + callback_func(contract.name, contract, [address], [balance]) cnt += 1 @@ -210,13 +223,13 @@ class EthLevelDB(object): ''' tries to find corresponding account address ''' - indexer = AccountIndexer(self) - address_hash = binascii.a2b_hex(utils.remove_0x_head(hash)) - address = indexer.get_contract_by_hash(address_hash) - if address: - return _encode_hex(address) - else: - return "Not found" + # indexer = AccountIndexer(self) + return binascii.a2b_hex(utils.remove_0x_head(hash)) + + # if address: + # return _encode_hex(address) + # else: + # return "Not found" def eth_getBlockHeaderByNumber(self, number): '''