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
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
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
from jinja2 import PackageLoader, Environment
from typing import Dict, List
import _pysha3 as sha3
import hashlib
from mythril.solidity.soliditycontract import SolidityContract
from mythril.analysis.swc_data import SWC_TO_TITLE
from mythril.support.source_support import Source
from mythril.support.start_time import StartTime
from mythril.support.support_utils import get_code_hash
from time import time
log = logging.getLogger(__name__)
@ -63,20 +63,7 @@ class Issue:
self.lineno = None
self.source_mapping = None
self.discovery_time = time() - StartTime().global_start_time
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 = ""
self.bytecode_hash = get_code_hash(bytecode)
@property
def as_dict(self):
@ -144,7 +131,7 @@ class Report:
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:
@ -153,7 +140,8 @@ class Report:
self.verbose = verbose
self.solc_version = ""
self.meta = {}
self.source = source or Source()
self.source = Source()
self.source.get_source_from_contracts_list(contracts)
self.exceptions = exceptions or []
def sorted_issues(self):
@ -197,7 +185,7 @@ class Report:
return {}
logs = [] # type: List[Dict]
for exception in self.exceptions:
logs += [{"level": "error", "hidden": "true", "error": exception}]
logs += [{"level": "error", "hidden": "true", "msg": exception}]
return {"logs": logs}
def as_swc_standard_format(self):
@ -210,12 +198,7 @@ class Report:
for key, issue in self.issues.items():
if issue.bytecode_hash not in source_list:
idx = len(source_list)
source_list.append(issue.bytecode_hash)
else:
idx = source_list.index(issue.bytecode_hash)
idx = self.source.get_source_index(issue.bytecode_hash)
try:
title = SWC_TO_TITLE[issue.swc_id]
except KeyError:
@ -238,9 +221,9 @@ class Report:
result = [
{
"issues": _issues,
"sourceType": "raw-bytecode",
"sourceFormat": "evm-byzantium-bytecode",
"sourceList": source_list,
"sourceType": self.source.source_type,
"sourceFormat": self.source.source_format,
"sourceList": self.source.source_list,
"meta": meta_data,
}
]

@ -2,6 +2,7 @@
purposes."""
import copy
from ethereum.utils import mk_contract_address
from mythril.analysis.security import get_detection_module_hooks, get_detection_modules
from mythril.laser.ethereum import svm
from mythril.laser.ethereum.state.account import Account
@ -11,11 +12,12 @@ from mythril.laser.ethereum.strategy.basic import (
ReturnRandomNaivelyStrategy,
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_loader import LaserPluginLoader
from mythril.solidity.soliditycontract import EVMContract, SolidityContract
from .ops import Call, SStore, VarType, get_variable
@ -110,6 +112,11 @@ class SymExecWrapper:
)
else:
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:
return

@ -28,11 +28,15 @@ class Disassembly(object):
self.func_hashes = [] # type: List[str]
self.function_name_to_address = {} # type: Dict[str, int]
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
# 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
jump_table_indices = asm.find_op_code_sequence(
[("PUSH1", "PUSH2", "PUSH3", "PUSH4"), ("EQ",)], self.instruction_list

@ -1,15 +1,14 @@
"""This module contains the class representing EVM contracts, aka Smart
Contracts."""
import re
import _pysha3 as sha3
import logging
log = logging.getLogger(__name__)
import persistent
from ethereum import utils
from ethereum import utils
from mythril.disassembler.disassembly import Disassembly
from mythril.support.support_utils import get_code_hash
log = logging.getLogger(__name__)
class EVMContract(persistent.Persistent):
@ -47,7 +46,7 @@ class EVMContract(persistent.Persistent):
:return: runtime bytecode hash
"""
return self._get_hash(self.code[2:])
return get_code_hash(self.code)
@property
def creation_bytecode_hash(self):
@ -55,23 +54,7 @@ class EVMContract(persistent.Persistent):
:return: Creation bytecode hash
"""
return self._get_hash(self.creation_code[2:])
@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 ""
return get_code_hash(self.creation_code)
def as_dict(self):
"""

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

@ -2,7 +2,7 @@
This includes classes representing accounts and their storage.
"""
from copy import deepcopy, copy
from typing import Any, Dict, KeysView, Union
from z3 import ExprRef
@ -61,6 +61,13 @@ class Storage:
"""
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:
"""Account class representing ethereum accounts."""
@ -92,7 +99,6 @@ class Account:
self.storage = Storage(
concrete_storage, address=address, dynamic_loader=dynamic_loader
)
# Metadata
self.address = address
self.contract_name = contract_name
@ -128,3 +134,14 @@ class Account:
"balance": self.balance,
"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 random import randint
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.annotation import StateAnnotation
@ -50,7 +50,12 @@ class WorldState:
return new_world_state
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:
"""Create non-contract account.
@ -60,7 +65,7 @@ class WorldState:
:param dynamic_loader: used for dynamically loading storage from the block chain
: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(
address,
balance=balance,
@ -111,11 +116,14 @@ class WorldState:
"""
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.
:return:
"""
if creator:
# TODO: Use nounce
return "0x" + str(mk_contract_address(creator, 0).hex())
while True:
address = "0x" + "".join([str(hex(randint(0, 16)))[-1] for _ in range(40)])
if address not in self.accounts.keys():

@ -73,7 +73,7 @@ def execute_contract_creation(
del laser_evm.open_states[:]
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:
new_account.contract_name = contract_name

@ -188,7 +188,7 @@ class ContractCreationTransaction(BaseTransaction):
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
assert global_state.environment.active_account.code.instruction_list != []

@ -608,10 +608,8 @@ class Mythril(object):
all_issues += issues
log.info("Solver statistics: \n{}".format(str(SolverStatistics())))
source_data = Source()
source_data.get_source_from_contracts_list(self.contracts)
# 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:
report.append_issue(issue)

@ -166,7 +166,7 @@ class MythrilAnalyzer:
source_data = Source()
source_data.get_source_from_contracts_list(self.contracts)
# 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:
report.append_issue(issue)

@ -5,9 +5,7 @@ from mythril.ethereum.evmcontract import EVMContract
class Source:
"""Class to handle to source data"""
def __init__(
self, source_type=None, source_format=None, source_list=None, meta=None
):
def __init__(self, source_type=None, source_format=None, source_list=None):
"""
:param source_type: whether it is a solidity-file or evm-bytecode
:param source_format: whether it is bytecode, ethereum-address or text
@ -17,7 +15,7 @@ class Source:
self.source_type = source_type
self.source_format = source_format
self.source_list = source_list or []
self.meta = meta
self._source_hash = []
def get_source_from_contracts_list(self, contracts):
"""
@ -32,16 +30,34 @@ class Source:
self.source_format = "text"
for contract in contracts:
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):
self.source_format = "evm-byzantium-bytecode"
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:
if contract.creation_code:
self.source_list.append(contract.creation_bytecode_hash)
if contract.code:
self.source_list.append(contract.bytecode_hash)
self._source_hash = self.source_list
else:
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."""
from typing import Dict
import logging
import _pysha3 as sha3
log = logging.getLogger(__name__)
class Singleton(type):
@ -20,3 +24,18 @@ class Singleton(type):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
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.
"""
VERSION = "v0.20.3" # NOQA
VERSION = "v0.20.4" # NOQA

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

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

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

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

Loading…
Cancel
Save