Fix merge conflict with master

pull/555/head
Dominik Muhs 6 years ago
commit 4ee2fdb5fa
  1. 6
      README.md
  2. 8
      mythril/analysis/symbolic.py
  3. 4
      mythril/ether/asm.py
  4. 13
      mythril/ether/ethcontract.py
  5. 2
      mythril/ether/evm.py
  6. 2
      mythril/ether/util.py
  7. 7
      mythril/interfaces/cli.py
  8. 10
      mythril/laser/ethereum/instructions.py
  9. 3
      mythril/laser/ethereum/taint_analysis.py
  10. 4
      mythril/laser/ethereum/util.py
  11. 23
      mythril/leveldb/accountindexing.py
  12. 105
      mythril/leveldb/client.py
  13. 18
      mythril/leveldb/eth_db.py
  14. 36
      mythril/leveldb/state.py
  15. 9
      mythril/mythril.py
  16. 28
      mythril/rpc/base_client.py
  17. 4
      mythril/rpc/client.py
  18. 16
      mythril/rpc/utils.py
  19. 4
      mythril/support/loader.py
  20. 1
      requirements.txt
  21. 1
      setup.py
  22. 12
      tests/native_test.py

@ -2,16 +2,16 @@
[![Discord](https://img.shields.io/discord/481002907366588416.svg)](https://discord.gg/E3YrVtG)
[![PyPI](https://badge.fury.io/py/mythril.svg)](https://pypi.python.org/pypi/mythril)
![Master Build Status](https://img.shields.io/circleci/project/github/ConsenSys/mythril/master.svg)
[![Waffle.io - Columns and their card count](https://badge.waffle.io/ConsenSys/mythril.svg?columns=all)](https://waffle.io/ConsenSys/mythril)
[![Waffle.io - Columns and their card count](https://badge.waffle.io/ConsenSys/mythril.svg?columns=In%20Progress)](https://waffle.io/ConsenSys/mythril)
[![Sonarcloud - Maintainability](https://sonarcloud.io/api/project_badges/measure?project=mythril&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=mythril)
[![PyPI Statistics](https://pypistats.com/badge/mythril.svg)](https://pypistats.com/package/mythril)
<img height="120px" align="right" src="https://github.com/ConsenSys/mythril/raw/master/static/mythril.png" alt="mythril" />
Mythril OSS is the classic security analysis tool for Ethereum smart contracts. It uses concolic analysis, taint analysis and control flow checking to detect a variety of security vulnerabilities.
Whether you want to contribute, need support, or want to learn what we have cooking for the future, our [Discord server](https://discord.gg/E3YrVtG) will serve your needs!
Oh and by the way, we're now building a whole security tools ecosystem with [Mythril Platform](https://mythril.ai). You should definitely check that out as well.
Oh and by the way, we're building an easy-to-use SaaS solution and tools ecosystem for Ethereum developers called [Mythril Platform](https://mythril.ai). You should definitely check that out as well.
## Installation and setup

@ -9,9 +9,9 @@ from mythril.laser.ethereum.strategy.basic import DepthFirstSearchStrategy, Brea
class SymExecWrapper:
'''
"""
Wrapper class for the LASER Symbolic virtual machine. Symbolically executes the code and does a bit of pre-analysis for convenience.
'''
"""
def __init__(self, contract, address, strategy, dynloader=None, max_depth=22,
execution_timeout=None, create_timeout=None):
@ -66,7 +66,7 @@ class SymExecWrapper:
# ignore prebuilts
continue
if (meminstart.type == VarType.CONCRETE and meminsz.type == VarType.CONCRETE):
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 * 4]))
else:
self.calls.append(Call(self.nodes[key], state, state_index, op, to, gas, value))
@ -104,7 +104,7 @@ class SymExecWrapper:
taint = True
for constraint in s.node.constraints:
if ("caller" in str(constraint)):
if "caller" in str(constraint):
taint = False
break

@ -80,7 +80,7 @@ def find_opcode_sequence(pattern, instruction_list):
matched = False
break
if (matched):
if matched:
match_indexes.append(i)
return match_indexes
@ -102,7 +102,7 @@ def disassemble(bytecode):
instruction = {'address': addr}
try:
if (sys.version_info > (3, 0)):
if sys.version_info > (3, 0):
opcode = opcodes[bytecode[addr]]
else:
opcode = opcodes[ord(bytecode[addr])]

@ -7,15 +7,16 @@ import re
class ETHContract(persistent.Persistent):
def __init__(self, code, creation_code="", name="Unknown", enable_online_lookup=True):
self.creation_code = creation_code
self.name = name
# Workaround: We currently do not support compile-time linking.
# Dynamic contract addresses of the format __[contract-name]_____________ are replaced with a generic address
# Apply this for creation_code & code
creation_code = re.sub(r'(_+.*_+)', 'aa' * 20, creation_code)
code = re.sub(r'(_+.*_+)', 'aa' * 20, code)
self.creation_code = creation_code
self.name = name
self.code = code
self.disassembly = Disassembly(code, enable_online_lookup=enable_online_lookup)
self.creation_disassembly = Disassembly(creation_code, enable_online_lookup=enable_online_lookup)
@ -49,7 +50,7 @@ class ETHContract(persistent.Persistent):
m = re.match(r'^code#([a-zA-Z0-9\s,\[\]]+)#', token)
if (m):
if m:
if easm_code is None:
easm_code = self.get_easm()
@ -59,7 +60,7 @@ class ETHContract(persistent.Persistent):
m = re.match(r'^func#([a-zA-Z0-9\s_,(\\)\[\]]+)#$', token)
if (m):
if m:
sign_hash = "0x" + utils.sha3(m.group(1))[:4].hex()

@ -37,7 +37,7 @@ def trace(code, calldata=""):
if m:
stackitems = re.findall(r'b\'(\d+)\'', m.group(1))
stack = "[";
stack = "["
if len(stackitems):
for i in range(0, len(stackitems) - 1):

@ -10,7 +10,7 @@ import json
def safe_decode(hex_encoded_string):
if (hex_encoded_string.startswith("0x")):
if hex_encoded_string.startswith("0x"):
return bytes.fromhex(hex_encoded_string[2:])
else:
return bytes.fromhex(hex_encoded_string)

@ -5,7 +5,7 @@
http://www.github.com/ConsenSys/mythril
"""
import logging
import logging, coloredlogs
import json
import sys
import argparse
@ -103,7 +103,10 @@ def main():
if args.v:
if 0 <= args.v < 3:
logging.basicConfig(level=[logging.NOTSET, logging.INFO, logging.DEBUG][args.v])
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")

@ -773,7 +773,8 @@ class Instruction:
return self._sload_helper(global_state, str(index))
def _sload_helper(self, global_state, index, constraints=None):
@staticmethod
def _sload_helper(global_state, index, constraints=None):
try:
data = global_state.environment.active_account.storage[index]
except KeyError:
@ -786,8 +787,8 @@ class Instruction:
global_state.mstate.stack.append(data)
return [global_state]
def _get_constraints(self, keccak_keys, this_key, argument):
@staticmethod
def _get_constraints(keccak_keys, this_key, argument):
global keccak_function_manager
for keccak_key in keccak_keys:
if keccak_key == this_key:
@ -837,7 +838,8 @@ class Instruction:
return self._sstore_helper(global_state, str(index), value)
def _sstore_helper(self, global_state, index, value, constraint=None):
@staticmethod
def _sstore_helper(global_state, index, value, constraint=None):
try:
global_state.environment.active_account = deepcopy(global_state.environment.active_account)
global_state.accounts[

@ -109,7 +109,8 @@ class TaintRunner:
records = TaintRunner.execute_node(node, record, index)
result.add_records(records)
if len(records) == 0: # continue if there is no record to work on
continue
children = TaintRunner.children(node, statespace, environment, transaction_stack_length)
for child in children:
current_nodes.append((child, records[-1], 0))

@ -16,7 +16,7 @@ def sha3(seed):
def safe_decode(hex_encoded_string):
if (hex_encoded_string.startswith("0x")):
if hex_encoded_string.startswith("0x"):
return bytes.fromhex(hex_encoded_string[2:])
else:
return bytes.fromhex(hex_encoded_string)
@ -99,7 +99,7 @@ def concrete_int_to_bytes(val):
# logging.debug("concrete_int_to_bytes " + str(val))
if (type(val) == int):
if type(val) == int:
return val.to_bytes(32, byteorder='big')
return (simplify(val).as_long()).to_bytes(32, byteorder='big')

@ -34,9 +34,9 @@ class CountableList(object):
class ReceiptForStorage(rlp.Serializable):
'''
"""
Receipt format stored in levelDB
'''
"""
fields = [
('state_root', binary),
@ -50,9 +50,9 @@ class ReceiptForStorage(rlp.Serializable):
class AccountIndexer(object):
'''
"""
Updates address index
'''
"""
def __init__(self, ethDB):
self.db = ethDB
@ -62,19 +62,20 @@ class AccountIndexer(object):
self.updateIfNeeded()
def get_contract_by_hash(self, contract_hash):
'''
"""
get mapped contract_address by its hash, if not found try indexing
'''
"""
contract_address = self.db.reader._get_address_by_hash(contract_hash)
if contract_address is not None:
return contract_address
else:
raise AddressNotFoundError
def _process(self, startblock):
'''
"""
Processesing method
'''
"""
logging.debug("Processing blocks %d to %d" % (startblock, startblock + BATCH_SIZE))
addresses = []
@ -94,9 +95,9 @@ class AccountIndexer(object):
return addresses
def updateIfNeeded(self):
'''
"""
update address index
'''
"""
headBlock = self.db.reader._get_head_block()
if headBlock is not None:
# avoid restarting search if head block is same & we already initialized
@ -126,7 +127,7 @@ class AccountIndexer(object):
count = 0
processed = 0
while (blockNum <= self.lastBlock):
while blockNum <= self.lastBlock:
# leveldb cannot be accessed on multiple processes (not even readonly)
# multithread version performs significantly worse than serial
try:

@ -26,23 +26,23 @@ address_mapping_head_key = b'accountMapping' # head (latest) number of indexed
def _format_block_number(number):
'''
"""
formats block number to uint64 big endian
'''
"""
return utils.zpad(utils.int_to_big_endian(number), 8)
def _encode_hex(v):
'''
"""
encodes hash as hex
'''
"""
return '0x' + utils.encode_hex(v)
class LevelDBReader(object):
'''
"""
level db reading interface, can be used with snapshot
'''
"""
def __init__(self, db):
self.db = db
@ -50,34 +50,34 @@ class LevelDBReader(object):
self.head_state = None
def _get_head_state(self):
'''
"""
gets head state
'''
"""
if not self.head_state:
root = self._get_head_block().state_root
self.head_state = State(self.db, root)
return self.head_state
def _get_account(self, address):
'''
"""
gets account by address
'''
"""
state = self._get_head_state()
account_address = binascii.a2b_hex(utils.remove_0x_head(address))
return state.get_and_cache_account(account_address)
def _get_block_hash(self, number):
'''
"""
gets block hash by block number
'''
"""
num = _format_block_number(number)
hash_key = header_prefix + num + num_suffix
return self.db.get(hash_key)
def _get_head_block(self):
'''
"""
gets head block header
'''
"""
if not self.head_block_header:
block_hash = self.db.get(head_header_key)
num = self._get_block_number(block_hash)
@ -91,38 +91,29 @@ class LevelDBReader(object):
return self.head_block_header
def _get_block_number(self, block_hash):
'''
gets block number by its hash
'''
"""Get block number by its hash"""
number_key = block_hash_prefix + block_hash
return self.db.get(number_key)
def _get_block_header(self, block_hash, num):
'''
get block header by block header hash & number
'''
"""Get block header by block header hash & number"""
header_key = header_prefix + num + block_hash
block_header_data = self.db.get(header_key)
header = rlp.decode(block_header_data, sedes=BlockHeader)
return header
def _get_address_by_hash(self, block_hash):
'''
get mapped address by its hash
'''
"""Get mapped address by its hash"""
address_key = address_prefix + block_hash
return self.db.get(address_key)
def _get_last_indexed_number(self):
'''
latest indexed block number
'''
"""Get latest indexed block number"""
return self.db.get(address_mapping_head_key)
def _get_block_receipts(self, block_hash, num):
'''
get block transaction receipts by block header hash & number
'''
"""Get block transaction receipts by block header hash & number"""
number = _format_block_number(num)
receipts_key = block_receipts_prefix + number + block_hash
receipts_data = self.db.get(receipts_key)
@ -131,44 +122,44 @@ class LevelDBReader(object):
class LevelDBWriter(object):
'''
"""
level db writing interface
'''
"""
def __init__(self, db):
self.db = db
self.wb = None
def _set_last_indexed_number(self, number):
'''
"""
sets latest indexed block number
'''
"""
return self.db.put(address_mapping_head_key, _format_block_number(number))
def _start_writing(self):
'''
"""
start writing a batch
'''
"""
self.wb = self.db.write_batch()
def _commit_batch(self):
'''
"""
commit batch
'''
"""
self.wb.write()
def _store_account_address(self, address):
'''
"""
get block transaction receipts by block header hash & number
'''
"""
address_key = address_prefix + utils.sha3(address)
self.wb.put(address_key, address)
class EthLevelDB(object):
'''
"""
Go-Ethereum LevelDB client class
'''
"""
def __init__(self, path):
self.path = path
@ -177,9 +168,9 @@ class EthLevelDB(object):
self.writer = LevelDBWriter(self.db)
def get_contracts(self):
'''
"""
iterate through all contracts
'''
"""
for account in self.reader._get_head_state().get_all_accounts():
if account.code is not None:
code = _encode_hex(account.code)
@ -188,9 +179,9 @@ class EthLevelDB(object):
yield contract, account.address, account.balance
def search(self, expression, callback_func):
'''
"""
searches through all contract accounts
'''
"""
cnt = 0
indexer = AccountIndexer(self)
@ -217,9 +208,7 @@ class EthLevelDB(object):
logging.info("Searched %d contracts" % cnt)
def contract_hash_to_address(self, contract_hash):
'''
tries to find corresponding account address
'''
"""Tries to find corresponding account address"""
address_hash = binascii.a2b_hex(utils.remove_0x_head(contract_hash))
indexer = AccountIndexer(self)
@ -227,17 +216,17 @@ class EthLevelDB(object):
return _encode_hex(indexer.get_contract_by_hash(address_hash))
def eth_getBlockHeaderByNumber(self, number):
'''
"""
gets block header by block number
'''
"""
block_hash = self.reader._get_block_hash(number)
block_number = _format_block_number(number)
return self.reader._get_block_header(block_hash, block_number)
def eth_getBlockByNumber(self, number):
'''
"""
gets block body by block number
'''
"""
block_hash = self.reader._get_block_hash(number)
block_number = _format_block_number(number)
body_key = body_prefix + block_number + block_hash
@ -246,22 +235,22 @@ class EthLevelDB(object):
return body
def eth_getCode(self, address):
'''
"""
gets account code
'''
"""
account = self.reader._get_account(address)
return _encode_hex(account.code)
def eth_getBalance(self, address):
'''
"""
gets account balance
'''
"""
account = self.reader._get_account(address)
return account.balance
def eth_getStorageAt(self, address, position):
'''
"""
gets account storage data at position
'''
"""
account = self.reader._get_account(address)
return _encode_hex(utils.zpad(utils.encode_int(account.get_storage_data(position)), 32))

@ -3,27 +3,27 @@ from ethereum.db import BaseDB
class ETH_DB(BaseDB):
'''
"""
adopts pythereum BaseDB using plyvel
'''
"""
def __init__(self, path):
self.db = plyvel.DB(path)
def get(self, key):
'''
"""
gets value for key
'''
"""
return self.db.get(key)
def put(self, key, value):
'''
"""
puts value for key
'''
"""
self.db.put(key, value)
def write_batch(self):
'''
"""
start writing a batch
'''
return self.db.write_batch()
"""
return self.db.write_batch()

@ -32,9 +32,9 @@ STATE_DEFAULTS = {
class Account(rlp.Serializable):
'''
"""
adjusted account from ethereum.state
'''
"""
fields = [
('nonce', big_endian_int),
@ -57,15 +57,15 @@ class Account(rlp.Serializable):
@property
def code(self):
'''
"""
code rlp data
'''
"""
return self.db.get(self.code_hash)
def get_storage_data(self, key):
'''
"""
get storage data
'''
"""
if key not in self.storage_cache:
v = self.storage_trie.get(utils.encode_int32(key))
self.storage_cache[key] = utils.big_endian_to_int(
@ -74,24 +74,24 @@ class Account(rlp.Serializable):
@classmethod
def blank_account(cls, db, addr, initial_nonce=0):
'''
"""
creates a blank account
'''
"""
db.put(BLANK_HASH, b'')
o = cls(initial_nonce, 0, trie.BLANK_ROOT, BLANK_HASH, db, addr)
o.existent_at_start = False
return o
def is_blank(self):
'''
"""
checks if is a blank account
'''
"""
return self.nonce == 0 and self.balance == 0 and self.code_hash == BLANK_HASH
class State():
'''
class State:
"""
adjusted state from ethereum.state
'''
"""
def __init__(self, db, root):
self.db = db
@ -101,14 +101,14 @@ class State():
self.cache = {}
def get_and_cache_account(self, addr):
'''
gets and caches an account for an addres, creates blank if not found
'''
"""Gets and caches an account for an addres, creates blank if not found"""
if addr in self.cache:
return self.cache[addr]
rlpdata = self.secure_trie.get(addr)
if rlpdata == trie.BLANK_NODE and len(addr) == 32: # support for hashed addresses
rlpdata = self.trie.get(addr)
if rlpdata != trie.BLANK_NODE:
o = rlp.decode(rlpdata, Account, db=self.db, address=addr)
else:
@ -120,9 +120,9 @@ class State():
return o
def get_all_accounts(self):
'''
"""
iterates through trie to and yields non-blank leafs as accounts
'''
"""
for address_hash, rlpdata in self.secure_trie.trie.iter_branch():
if rlpdata != trie.BLANK_NODE:
yield rlp.decode(rlpdata, Account, db=self.db, address=address_hash)

@ -103,7 +103,8 @@ class Mythril(object):
self.contracts = [] # loaded contracts
def _init_mythril_dir(self):
@staticmethod
def _init_mythril_dir():
try:
mythril_dir = os.environ['MYTHRIL_DIR']
except KeyError:
@ -179,7 +180,8 @@ class Mythril(object):
def analyze_truffle_project(self, *args, **kwargs):
return analyze_truffle_project(self.sigs, *args, **kwargs) # just passthru by passing signatures for now
def _init_solc_binary(self, version):
@staticmethod
def _init_solc_binary(version):
# Figure out solc binary and version
# Only proper versions are supported. No nightlies, commits etc (such as available in remix)
@ -434,7 +436,8 @@ class Mythril(object):
raise CriticalError("Could not connect to RPC server. Make sure that your node is running and that RPC parameters are set correctly.")
return '\n'.join(outtxt)
def disassemble(self, contract):
@staticmethod
def disassemble(contract):
return contract.get_easm()
@staticmethod

@ -20,64 +20,64 @@ class BaseClient(object):
pass
def eth_coinbase(self):
'''
"""
https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_coinbase
TESTED
'''
"""
return self._call('eth_coinbase')
def eth_blockNumber(self):
'''
"""
https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_blocknumber
TESTED
'''
"""
return hex_to_dec(self._call('eth_blockNumber'))
def eth_getBalance(self, address=None, block=BLOCK_TAG_LATEST):
'''
"""
https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getbalance
TESTED
'''
"""
address = address or self.eth_coinbase()
block = validate_block(block)
return hex_to_dec(self._call('eth_getBalance', [address, block]))
def eth_getStorageAt(self, address=None, position=0, block=BLOCK_TAG_LATEST):
'''
"""
https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getstorageat
TESTED
'''
"""
block = validate_block(block)
return self._call('eth_getStorageAt', [address, hex(position), block])
def eth_getCode(self, address, default_block=BLOCK_TAG_LATEST):
'''
"""
https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getcode
NEEDS TESTING
'''
"""
if isinstance(default_block, str):
if default_block not in BLOCK_TAGS:
raise ValueError
return self._call('eth_getCode', [address, default_block])
def eth_getBlockByNumber(self, block=BLOCK_TAG_LATEST, tx_objects=True):
'''
"""
https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbynumber
TESTED
'''
"""
block = validate_block(block)
return self._call('eth_getBlockByNumber', [block, tx_objects])
def eth_getTransactionReceipt(self, tx_hash):
'''
"""
https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionreceipt
TESTED
'''
"""
return self._call('eth_getTransactionReceipt', [tx_hash])

@ -17,9 +17,9 @@ JSON_MEDIA_TYPE = 'application/json'
This code is adapted from: https://github.com/ConsenSys/ethjsonrpc
'''
class EthJsonRpc(BaseClient):
'''
"""
Ethereum JSON-RPC client class
'''
"""
def __init__(self, host='localhost', port=GETH_DEFAULT_RPC_PORT, tls=False):
self.host = host

@ -2,17 +2,17 @@ from .constants import BLOCK_TAGS
def hex_to_dec(x):
'''
"""
Convert hex to decimal
'''
"""
return int(x, 16)
def clean_hex(d):
'''
"""
Convert decimal to hex and remove the "L" suffix that is appended to large
numbers
'''
"""
return hex(d).rstrip('L')
def validate_block(block):
@ -25,14 +25,14 @@ def validate_block(block):
def wei_to_ether(wei):
'''
"""
Convert wei to ether
'''
"""
return 1.0 * wei / 10**18
def ether_to_wei(ether):
'''
"""
Convert ether to wei
'''
"""
return ether * 10**18

@ -37,7 +37,7 @@ class DynLoader:
m = re.match(r'^(0x[0-9a-fA-F]{40})$', dependency_address)
if (m):
if m:
dependency_address = m.group(1)
else:
@ -47,7 +47,7 @@ class DynLoader:
code = self.eth.eth_getCode(dependency_address)
if (code == "0x"):
if code == "0x":
return None
else:
return Disassembly(code)

@ -1,3 +1,4 @@
coloredlogs>=10.0
configparser>=3.5.0
coverage
eth_abi>=1.0.0

@ -82,6 +82,7 @@ setup(
packages=find_packages(exclude=['contrib', 'docs', 'tests']),
install_requires=[
'coloredlogs>=10.0',
'ethereum>=2.3.2',
'z3-solver>=4.5',
'requests',

@ -6,13 +6,13 @@ from mythril.laser.ethereum import svm
from tests import *
SHA256_TEST = [ (0,False) for i in range(6)]
SHA256_TEST = [(0, False) for _ in range(6)]
RIPEMD160_TEST = [ (0,False) for i in range(6)]
RIPEMD160_TEST = [(0, False) for _ in range(6)]
ECRECOVER_TEST = [ (0,False) for i in range(9)]
ECRECOVER_TEST = [(0, False) for _ in range(9)]
IDENTITY_TEST = [ (0, False) for i in range(4)]
IDENTITY_TEST = [(0, False) for _ in range(4)]
SHA256_TEST[0] = (5555555555555555, True) #These are Random numbers to check whether the 'if condition' is entered or not(True means entered)
SHA256_TEST[1] = (323232325445454546, True)
@ -98,9 +98,9 @@ def _test_natives(laser_info, test_list, test_name):
assert(success == len(test_list))
class NativeTests(BaseTestCase):
def runTest(self):
@staticmethod
def runTest():
disassembly = SolidityContract('./tests/native_tests.sol').disassembly
account = Account("0x0000000000000000000000000000000000000000", disassembly)
accounts = {account.address: account}

Loading…
Cancel
Save