Merge branch 'develop' into enhance/886

pull/919/head
JoranHonig 6 years ago committed by GitHub
commit 10cbe0e48e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      CONTRIBUTING.md
  2. 37
      mythril/analysis/report.py
  3. 9
      mythril/analysis/symbolic.py
  4. 8
      mythril/disassembler/disassembly.py
  5. 29
      mythril/ethereum/evmcontract.py
  6. 10
      mythril/interfaces/cli.py
  7. 21
      mythril/laser/ethereum/state/account.py
  8. 16
      mythril/laser/ethereum/state/world_state.py
  9. 2
      mythril/laser/ethereum/transaction/symbolic.py
  10. 2
      mythril/laser/ethereum/transaction/transaction_models.py
  11. 4
      mythril/mythril.py
  12. 2
      mythril/mythril/mythril_analyzer.py
  13. 26
      mythril/support/source_support.py
  14. 19
      mythril/support/support_utils.py
  15. 2
      mythril/version.py
  16. 2
      tests/report_test.py
  17. 10
      tests/testdata/outputs_expected/ether_send.sol.o.jsonv2
  18. 10
      tests/testdata/outputs_expected/metacoin.sol.o.jsonv2
  19. 10
      tests/testdata/outputs_expected/nonascii.sol.o.jsonv2

@ -10,7 +10,7 @@ If you have a small question or aren't sure if you should create an issue for yo
# Coding # Coding
If you want to help out with the development of Mythril then you can take a look at our issues or [Waffle board](https://waffle.io/ConsenSys/mythril). If you want to help out with the development of Mythril then you can take a look at our issues or [Waffle board](https://waffle.io/ConsenSys/mythril).
Before you start working on an issue pkease stop by on Discord to message a collaborator, this way we can assign you to the issue making sure nobody does double work. We can also provide you with support through Discord if there are any questions during the development process. Before you start working on an issue please stop by on Discord to message a collaborator, this way we can assign you to the issue making sure nobody does double work. We can also provide you with support through Discord if there are any questions during the development process.
## New ideas ## New ideas
Before you start working on a new idea, it's useful to create an issue on GitHub, that way we know what you want to implement and that you are working on it. Additionally, it might happen that your feature does not fit with our roadmap, in which case it would be unfortunate if you have already spent some time working on it. Before you start working on a new idea, it's useful to create an issue on GitHub, that way we know what you want to implement and that you are working on it. Additionally, it might happen that your feature does not fit with our roadmap, in which case it would be unfortunate if you have already spent some time working on it.

@ -4,13 +4,13 @@ import json
import operator import operator
from jinja2 import PackageLoader, Environment from jinja2 import PackageLoader, Environment
from typing import Dict, List from typing import Dict, List
import _pysha3 as sha3
import hashlib import hashlib
from mythril.solidity.soliditycontract import SolidityContract from mythril.solidity.soliditycontract import SolidityContract
from mythril.analysis.swc_data import SWC_TO_TITLE from mythril.analysis.swc_data import SWC_TO_TITLE
from mythril.support.source_support import Source from mythril.support.source_support import Source
from mythril.support.start_time import StartTime from mythril.support.start_time import StartTime
from mythril.support.support_utils import get_code_hash
from time import time from time import time
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -63,20 +63,7 @@ class Issue:
self.lineno = None self.lineno = None
self.source_mapping = None self.source_mapping = None
self.discovery_time = time() - StartTime().global_start_time self.discovery_time = time() - StartTime().global_start_time
self.bytecode_hash = get_code_hash(bytecode)
try:
keccak = sha3.keccak_256()
keccak.update(
bytes.fromhex(bytecode[2:])
if bytecode[:2] == "0x"
else bytes.fromhex(bytecode)
)
self.bytecode_hash = "0x" + keccak.hexdigest()
except ValueError:
log.debug(
"Unable to change the bytecode to bytes. Bytecode: {}".format(bytecode)
)
self.bytecode_hash = ""
@property @property
def as_dict(self): def as_dict(self):
@ -144,7 +131,7 @@ class Report:
loader=PackageLoader("mythril.analysis"), trim_blocks=True loader=PackageLoader("mythril.analysis"), trim_blocks=True
) )
def __init__(self, verbose=False, source=None, exceptions=None): def __init__(self, verbose=False, contracts=None, exceptions=None):
""" """
:param verbose: :param verbose:
@ -153,7 +140,8 @@ class Report:
self.verbose = verbose self.verbose = verbose
self.solc_version = "" self.solc_version = ""
self.meta = {} self.meta = {}
self.source = source or Source() self.source = Source()
self.source.get_source_from_contracts_list(contracts)
self.exceptions = exceptions or [] self.exceptions = exceptions or []
def sorted_issues(self): def sorted_issues(self):
@ -197,7 +185,7 @@ class Report:
return {} return {}
logs = [] # type: List[Dict] logs = [] # type: List[Dict]
for exception in self.exceptions: for exception in self.exceptions:
logs += [{"level": "error", "hidden": "true", "error": exception}] logs += [{"level": "error", "hidden": "true", "msg": exception}]
return {"logs": logs} return {"logs": logs}
def as_swc_standard_format(self): def as_swc_standard_format(self):
@ -210,12 +198,7 @@ class Report:
for key, issue in self.issues.items(): for key, issue in self.issues.items():
if issue.bytecode_hash not in source_list: idx = self.source.get_source_index(issue.bytecode_hash)
idx = len(source_list)
source_list.append(issue.bytecode_hash)
else:
idx = source_list.index(issue.bytecode_hash)
try: try:
title = SWC_TO_TITLE[issue.swc_id] title = SWC_TO_TITLE[issue.swc_id]
except KeyError: except KeyError:
@ -238,9 +221,9 @@ class Report:
result = [ result = [
{ {
"issues": _issues, "issues": _issues,
"sourceType": "raw-bytecode", "sourceType": self.source.source_type,
"sourceFormat": "evm-byzantium-bytecode", "sourceFormat": self.source.source_format,
"sourceList": source_list, "sourceList": self.source.source_list,
"meta": meta_data, "meta": meta_data,
} }
] ]

@ -2,6 +2,7 @@
purposes.""" purposes."""
import copy import copy
from ethereum.utils import mk_contract_address
from mythril.analysis.security import get_detection_module_hooks, get_detection_modules from mythril.analysis.security import get_detection_module_hooks, get_detection_modules
from mythril.laser.ethereum import svm from mythril.laser.ethereum import svm
from mythril.laser.ethereum.state.account import Account from mythril.laser.ethereum.state.account import Account
@ -11,11 +12,12 @@ from mythril.laser.ethereum.strategy.basic import (
ReturnRandomNaivelyStrategy, ReturnRandomNaivelyStrategy,
ReturnWeightedRandomStrategy, ReturnWeightedRandomStrategy,
) )
from mythril.laser.ethereum.transaction.symbolic import CREATOR_ADDRESS
from mythril.laser.ethereum.plugins.plugin_factory import PluginFactory from mythril.laser.ethereum.plugins.plugin_factory import PluginFactory
from mythril.laser.ethereum.plugins.plugin_loader import LaserPluginLoader from mythril.laser.ethereum.plugins.plugin_loader import LaserPluginLoader
from mythril.solidity.soliditycontract import EVMContract, SolidityContract from mythril.solidity.soliditycontract import EVMContract, SolidityContract
from .ops import Call, SStore, VarType, get_variable from .ops import Call, SStore, VarType, get_variable
@ -110,6 +112,11 @@ class SymExecWrapper:
) )
else: else:
self.laser.sym_exec(address) self.laser.sym_exec(address)
created_address = "0x" + str(mk_contract_address(CREATOR_ADDRESS, 0).hex())
for key, value in self.laser.world_state.accounts.items():
if created_address == value.address:
contract.code = value.code.bytecode
break
if not requires_statespace: if not requires_statespace:
return return

@ -28,11 +28,15 @@ class Disassembly(object):
self.func_hashes = [] # type: List[str] self.func_hashes = [] # type: List[str]
self.function_name_to_address = {} # type: Dict[str, int] self.function_name_to_address = {} # type: Dict[str, int]
self.address_to_function_name = {} # type: Dict[int, str] self.address_to_function_name = {} # type: Dict[int, str]
self.enable_online_lookup = enable_online_lookup
self.assign_bytecode(bytecode=code)
def assign_bytecode(self, bytecode):
self.bytecode = bytecode
# open from default locations # open from default locations
# control if you want to have online signature hash lookups # control if you want to have online signature hash lookups
signatures = SignatureDB(enable_online_lookup=enable_online_lookup) signatures = SignatureDB(enable_online_lookup=self.enable_online_lookup)
self.instruction_list = asm.disassemble(util.safe_decode(bytecode))
# Need to take from PUSH1 to PUSH4 because solc seems to remove excess 0s at the beginning for optimizing # Need to take from PUSH1 to PUSH4 because solc seems to remove excess 0s at the beginning for optimizing
jump_table_indices = asm.find_op_code_sequence( jump_table_indices = asm.find_op_code_sequence(
[("PUSH1", "PUSH2", "PUSH3", "PUSH4"), ("EQ",)], self.instruction_list [("PUSH1", "PUSH2", "PUSH3", "PUSH4"), ("EQ",)], self.instruction_list

@ -1,15 +1,14 @@
"""This module contains the class representing EVM contracts, aka Smart """This module contains the class representing EVM contracts, aka Smart
Contracts.""" Contracts."""
import re import re
import _pysha3 as sha3
import logging import logging
log = logging.getLogger(__name__)
import persistent import persistent
from ethereum import utils
from ethereum import utils
from mythril.disassembler.disassembly import Disassembly from mythril.disassembler.disassembly import Disassembly
from mythril.support.support_utils import get_code_hash
log = logging.getLogger(__name__)
class EVMContract(persistent.Persistent): class EVMContract(persistent.Persistent):
@ -47,7 +46,7 @@ class EVMContract(persistent.Persistent):
:return: runtime bytecode hash :return: runtime bytecode hash
""" """
return self._get_hash(self.code[2:]) return get_code_hash(self.code)
@property @property
def creation_bytecode_hash(self): def creation_bytecode_hash(self):
@ -55,23 +54,7 @@ class EVMContract(persistent.Persistent):
:return: Creation bytecode hash :return: Creation bytecode hash
""" """
return self._get_hash(self.creation_code[2:]) return get_code_hash(self.creation_code)
@staticmethod
def _get_hash(code):
"""
:param code: bytecode
:return: Returns hash of the given bytecode
"""
try:
keccak = sha3.keccak_256()
keccak.update(bytes.fromhex(code[2:]))
return "0x" + keccak.hexdigest()
except ValueError:
log.debug(
"Unable to change the bytecode to bytes. Bytecode: {}".format(code)
)
return ""
def as_dict(self): def as_dict(self):
""" """

@ -47,7 +47,7 @@ def exit_with_error(format_, message):
"sourceFormat": "", "sourceFormat": "",
"sourceList": [], "sourceList": [],
"meta": { "meta": {
"logs": [{"level": "error", "hidden": "true", "error": message}] "logs": [{"level": "error", "hidden": "true", "msg": message}]
}, },
} }
] ]
@ -507,7 +507,7 @@ def parse_args(parser: argparse.ArgumentParser, args: argparse.Namespace) -> Non
quick_commands(args) quick_commands(args)
config = set_config(args) config = set_config(args)
leveldb_search(config, args) leveldb_search(config, args)
dissasembler = MythrilDisassembler( disassembler = MythrilDisassembler(
eth=config.eth, eth=config.eth,
solc_version=args.solv, solc_version=args.solv,
solc_args=args.solc_args, solc_args=args.solc_args,
@ -515,16 +515,16 @@ def parse_args(parser: argparse.ArgumentParser, args: argparse.Namespace) -> Non
) )
if args.truffle: if args.truffle:
try: try:
dissasembler.analyze_truffle_project(args) disassembler.analyze_truffle_project(args)
except FileNotFoundError: except FileNotFoundError:
print( print(
"Build directory not found. Make sure that you start the analysis from the project root, and that 'truffle compile' has executed successfully." "Build directory not found. Make sure that you start the analysis from the project root, and that 'truffle compile' has executed successfully."
) )
sys.exit() sys.exit()
address = get_code(dissasembler, args) address = get_code(disassembler, args)
execute_command( execute_command(
disassembler=dissasembler, address=address, parser=parser, args=args disassembler=disassembler, address=address, parser=parser, args=args
) )
except CriticalError as ce: except CriticalError as ce:
exit_with_error(args.outform, str(ce)) exit_with_error(args.outform, str(ce))

@ -2,7 +2,7 @@
This includes classes representing accounts and their storage. This includes classes representing accounts and their storage.
""" """
from copy import deepcopy, copy
from typing import Any, Dict, KeysView, Union from typing import Any, Dict, KeysView, Union
from z3 import ExprRef from z3 import ExprRef
@ -61,6 +61,13 @@ class Storage:
""" """
return self._storage.keys() return self._storage.keys()
def __deepcopy__(self, memodict={}):
storage = Storage(
concrete=self.concrete, address=self.address, dynamic_loader=self.dynld
)
storage._storage = copy(self._storage)
return storage
class Account: class Account:
"""Account class representing ethereum accounts.""" """Account class representing ethereum accounts."""
@ -92,7 +99,6 @@ class Account:
self.storage = Storage( self.storage = Storage(
concrete_storage, address=address, dynamic_loader=dynamic_loader concrete_storage, address=address, dynamic_loader=dynamic_loader
) )
# Metadata # Metadata
self.address = address self.address = address
self.contract_name = contract_name self.contract_name = contract_name
@ -128,3 +134,14 @@ class Account:
"balance": self.balance, "balance": self.balance,
"storage": self.storage, "storage": self.storage,
} }
def __deepcopy__(self, memodict={}):
new_account = Account(
address=self.address,
code=self.code,
balance=self.balance,
contract_name=self.contract_name,
)
new_account.storage = deepcopy(self.storage)
new_account.code = self.code
return new_account

@ -2,7 +2,7 @@
from copy import copy from copy import copy
from random import randint from random import randint
from typing import Dict, List, Iterator, Optional, TYPE_CHECKING from typing import Dict, List, Iterator, Optional, TYPE_CHECKING
from ethereum.utils import mk_contract_address
from mythril.laser.ethereum.state.account import Account from mythril.laser.ethereum.state.account import Account
from mythril.laser.ethereum.state.annotation import StateAnnotation from mythril.laser.ethereum.state.annotation import StateAnnotation
@ -50,7 +50,12 @@ class WorldState:
return new_world_state return new_world_state
def create_account( def create_account(
self, balance=0, address=None, concrete_storage=False, dynamic_loader=None self,
balance=0,
address=None,
concrete_storage=False,
dynamic_loader=None,
creator=None,
) -> Account: ) -> Account:
"""Create non-contract account. """Create non-contract account.
@ -60,7 +65,7 @@ class WorldState:
:param dynamic_loader: used for dynamically loading storage from the block chain :param dynamic_loader: used for dynamically loading storage from the block chain
:return: The new account :return: The new account
""" """
address = address if address else self._generate_new_address() address = address if address else self._generate_new_address(creator)
new_account = Account( new_account = Account(
address, address,
balance=balance, balance=balance,
@ -111,11 +116,14 @@ class WorldState:
""" """
return filter(lambda x: isinstance(x, annotation_type), self.annotations) return filter(lambda x: isinstance(x, annotation_type), self.annotations)
def _generate_new_address(self) -> str: def _generate_new_address(self, creator=None) -> str:
"""Generates a new address for the global state. """Generates a new address for the global state.
:return: :return:
""" """
if creator:
# TODO: Use nounce
return "0x" + str(mk_contract_address(creator, 0).hex())
while True: while True:
address = "0x" + "".join([str(hex(randint(0, 16)))[-1] for _ in range(40)]) address = "0x" + "".join([str(hex(randint(0, 16)))[-1] for _ in range(40)])
if address not in self.accounts.keys(): if address not in self.accounts.keys():

@ -73,7 +73,7 @@ def execute_contract_creation(
del laser_evm.open_states[:] del laser_evm.open_states[:]
new_account = laser_evm.world_state.create_account( new_account = laser_evm.world_state.create_account(
0, concrete_storage=True, dynamic_loader=None 0, concrete_storage=True, dynamic_loader=None, creator=CREATOR_ADDRESS
) )
if contract_name: if contract_name:
new_account.contract_name = contract_name new_account.contract_name = contract_name

@ -188,7 +188,7 @@ class ContractCreationTransaction(BaseTransaction):
contract_code = bytes.hex(array.array("B", return_data).tostring()) contract_code = bytes.hex(array.array("B", return_data).tostring())
global_state.environment.active_account.code = Disassembly(contract_code) global_state.environment.active_account.code.assign_bytecode(contract_code)
self.return_data = global_state.environment.active_account.address self.return_data = global_state.environment.active_account.address
assert global_state.environment.active_account.code.instruction_list != [] assert global_state.environment.active_account.code.instruction_list != []

@ -608,10 +608,8 @@ class Mythril(object):
all_issues += issues all_issues += issues
log.info("Solver statistics: \n{}".format(str(SolverStatistics()))) log.info("Solver statistics: \n{}".format(str(SolverStatistics())))
source_data = Source()
source_data.get_source_from_contracts_list(self.contracts)
# Finally, output the results # Finally, output the results
report = Report(verbose_report, source_data, exceptions=exceptions) report = Report(verbose_report, self.contracts, exceptions=exceptions)
for issue in all_issues: for issue in all_issues:
report.append_issue(issue) report.append_issue(issue)

@ -166,7 +166,7 @@ class MythrilAnalyzer:
source_data = Source() source_data = Source()
source_data.get_source_from_contracts_list(self.contracts) source_data.get_source_from_contracts_list(self.contracts)
# Finally, output the results # Finally, output the results
report = Report(verbose_report, source_data, exceptions=exceptions) report = Report(verbose_report, contracts=self.contracts, exceptions=exceptions)
for issue in all_issues: for issue in all_issues:
report.append_issue(issue) report.append_issue(issue)

@ -5,9 +5,7 @@ from mythril.ethereum.evmcontract import EVMContract
class Source: class Source:
"""Class to handle to source data""" """Class to handle to source data"""
def __init__( def __init__(self, source_type=None, source_format=None, source_list=None):
self, source_type=None, source_format=None, source_list=None, meta=None
):
""" """
:param source_type: whether it is a solidity-file or evm-bytecode :param source_type: whether it is a solidity-file or evm-bytecode
:param source_format: whether it is bytecode, ethereum-address or text :param source_format: whether it is bytecode, ethereum-address or text
@ -17,7 +15,7 @@ class Source:
self.source_type = source_type self.source_type = source_type
self.source_format = source_format self.source_format = source_format
self.source_list = source_list or [] self.source_list = source_list or []
self.meta = meta self._source_hash = []
def get_source_from_contracts_list(self, contracts): def get_source_from_contracts_list(self, contracts):
""" """
@ -32,16 +30,34 @@ class Source:
self.source_format = "text" self.source_format = "text"
for contract in contracts: for contract in contracts:
self.source_list += [file.filename for file in contract.solidity_files] self.source_list += [file.filename for file in contract.solidity_files]
self._source_hash.append(contract.bytecode_hash)
self._source_hash.append(contract.creation_bytecode_hash)
elif isinstance(contracts[0], EVMContract): elif isinstance(contracts[0], EVMContract):
self.source_format = "evm-byzantium-bytecode" self.source_format = "evm-byzantium-bytecode"
self.source_type = ( self.source_type = (
"raw-bytecode" if contracts[0].name == "MAIN" else "ethereum-address" "ethereum-address"
if len(contracts[0].name) == 42 and contracts[0].name[0:2] == "0x"
else "raw-bytecode"
) )
for contract in contracts: for contract in contracts:
if contract.creation_code: if contract.creation_code:
self.source_list.append(contract.creation_bytecode_hash) self.source_list.append(contract.creation_bytecode_hash)
if contract.code: if contract.code:
self.source_list.append(contract.bytecode_hash) self.source_list.append(contract.bytecode_hash)
self._source_hash = self.source_list
else: else:
assert False # Fail hard assert False # Fail hard
def get_source_index(self, bytecode_hash: str) -> int:
"""
Find the contract index in the list
:param bytecode_hash: The contract hash
:return: The index of the contract in the _source_hash list
"""
# TODO: Add this part to exception logs
try:
return self._source_hash.index(bytecode_hash)
except ValueError:
self._source_hash.append(bytecode_hash)
return len(self._source_hash) - 1

@ -1,5 +1,9 @@
"""This module contains utility functions for the Mythril support package.""" """This module contains utility functions for the Mythril support package."""
from typing import Dict from typing import Dict
import logging
import _pysha3 as sha3
log = logging.getLogger(__name__)
class Singleton(type): class Singleton(type):
@ -20,3 +24,18 @@ class Singleton(type):
if cls not in cls._instances: if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls] return cls._instances[cls]
def get_code_hash(code: str) -> str:
"""
:param code: bytecode
:return: Returns hash of the given bytecode
"""
code = code[2:] if code[:2] == "0x" else code
try:
keccak = sha3.keccak_256()
keccak.update(bytes.fromhex(code))
return "0x" + keccak.hexdigest()
except ValueError:
log.debug("Unable to change the bytecode to bytes. Bytecode: {}".format(code))
return ""

@ -4,4 +4,4 @@ This file is suitable for sourcing inside POSIX shell, e.g. bash as well
as for importing into Python. as for importing into Python.
""" """
VERSION = "v0.20.3" # NOQA VERSION = "v0.20.4" # NOQA

@ -39,7 +39,7 @@ def _generate_report(input_file):
) )
issues = fire_lasers(sym) issues = fire_lasers(sym)
report = Report() report = Report(contracts=[contract])
for issue in issues: for issue in issues:
issue.filename = "test-filename.sol" issue.filename = "test-filename.sol"
report.append_issue(issue) report.append_issue(issue)

@ -1,9 +1 @@
[ [{"issues": [], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0x3746c7c2ae7b0d4c3f8b1905df9a7ea169b9f93bec68a10a00b4c9d27a18c6fb"], "sourceType": "raw-bytecode"}]
{
"issues": [],
"meta": {},
"sourceFormat": "evm-byzantium-bytecode",
"sourceList": [],
"sourceType": "raw-bytecode"
}
]

@ -1,9 +1 @@
[ [{"issues": [], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0x0e6f727bb3301e02d3be831bf34357522fd2f1d40e90dff8e2214553b06b5f6c"], "sourceType": "raw-bytecode"}]
{
"issues": [],
"meta": {},
"sourceFormat": "evm-byzantium-bytecode",
"sourceList": [],
"sourceType": "raw-bytecode"
}
]

@ -1,9 +1 @@
[ [{"issues": [], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0x11a78eb09819f505ba4f10747e6d1f7a44480e602c67573b7abac2f733a85d93"], "sourceType": "raw-bytecode"}]
{
"issues": [],
"meta": {},
"sourceFormat": "evm-byzantium-bytecode",
"sourceList": [],
"sourceType": "raw-bytecode"
}
]

Loading…
Cancel
Save