Merge remote-tracking branch 'upstream/master' into features/contract_creation

pull/419/head
Joran Honig 6 years ago
commit 9c02ab3f18
  1. 20
      README.md
  2. 6
      mythril/analysis/modules/external_calls.py
  3. 4
      mythril/analysis/modules/unchecked_retval.py
  4. 2
      mythril/analysis/templates/callgraph.html
  5. 4
      mythril/disassembler/disassembly.py
  6. 22
      mythril/ether/ethcontract.py
  7. 10
      mythril/ether/evm.py
  8. 11
      mythril/exceptions.py
  9. 34
      mythril/interfaces/cli.py
  10. 41
      mythril/laser/ethereum/instructions.py
  11. 12
      mythril/laser/ethereum/svm.py
  12. 4
      mythril/laser/ethereum/transaction.py
  13. 7
      mythril/leveldb/accountindexing.py
  14. 140
      mythril/leveldb/client.py
  15. 8
      mythril/leveldb/state.py
  16. 18
      mythril/mythril.py
  17. 4
      mythril/support/loader.py
  18. 18
      mythril/support/signatures.py
  19. 35
      mythril/support/truffle.py
  20. 2
      mythril/version.py
  21. 40
      tests/laser/test_transaction.py
  22. 25
      tests/test_cli_opts.py

@ -1,4 +1,4 @@
# Mythril
# Mythril [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Mythril%20-%20Security%20Analyzer%20for%20Ethereum%20Smart%20Contracts&url=https://www.github.com/ConsenSys/mythril)
[![PyPI](https://badge.fury.io/py/mythril.svg)](https://pypi.python.org/pypi/mythril)
[![Join the chat at https://gitter.im/ConsenSys/mythril](https://badges.gitter.im/ConsenSys/mythril.svg)](https://gitter.im/ConsenSys/mythril?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
![Master Build Status](https://img.shields.io/circleci/project/github/ConsenSys/mythril/master.svg)
@ -26,20 +26,16 @@ See the [Wiki](https://github.com/ConsenSys/mythril/wiki/Installation-and-Setup)
## Usage
Documentation has moved to the [Wiki page](https://github.com/ConsenSys/mythril/wiki). For support, visit the [Gitter channel](https://gitter.im/ConsenSys/mythril) or [Telegram group](https://t.me/mythril_tool).
Please visit the [Wiki page](https://github.com/ConsenSys/mythril/wiki) and [Gitter channel](https://gitter.im/ConsenSys/mythril) for documentation and technical support.
## Publications and Videos
Also, don't forget to join the Mythril community for the latest news, announcements and discussions:
- [Mythril on Twitter](https://twitter.com/mythril_tool)
- [Mythril Community Telegram group](https://t.me/mythril_tool)
## Presentations, papers and videos
- [HITBSecConf 2018 conference paper](https://github.com/b-mueller/smashing-smart-contracts/blob/master/smashing-smart-contracts-1of1.pdf)
- [HITBSecConf 2018 - Smashing Ethereum smart contracts for fun and real profit](https://www.youtube.com/watch?v=iqf6epACgds)
- [EDCon Toronto 2018 - Mythril: Find bugs and verify security properties in your contracts](https://www.youtube.com/watch?v=NJ9StJThxZY&feature=youtu.be&t=3h3m18s)
## Mythril is Hiring
[ConsenSys Diligence](https://consensys.net/diligence/) is building a dedicated Mythril team. If you're a coder and/or Ethereum security enthusiast who wants to do interesting and challenging work for a decentralized organization, check out the open positions below. Please visit the links below to apply.
- [Developer - Security Analysis Tools](https://new.consensys.net/careers/?gh_jid=1129067)
- [Developer - Security Analysis Tools (Part Time)](https://new.consensys.net/careers/?gh_jid=1129048)
- [Lead Developer - Security Tools for Auditors](https://new.consensys.net/careers/?gh_jid=1127282)
- [Lead Developer - Secure SDLC Tools](https://new.consensys.net/careers/?gh_jid=1127284)
- [Product Manager - Security Tools](https://new.consensys.net/careers/?gh_jid=1127271)

@ -21,11 +21,11 @@ def search_children(statespace, node, start_index=0, depth=0, results=[]):
if(depth < MAX_SEARCH_DEPTH):
nStates = len(node.states)
n_states = len(node.states)
if nStates > start_index:
if n_states > start_index:
for j in range(start_index, nStates):
for j in range(start_index, n_states):
if node.states[j].get_current_instruction()['opcode'] == 'SSTORE':
results.append(node.states[j].get_current_instruction()['address'])

@ -57,9 +57,9 @@ def execute(statespace):
else:
nStates = len(node.states)
n_states = len(node.states)
for idx in range(0, nStates - 1): # Ignore CALLs at last position in a node
for idx in range(0, n_states - 1): # Ignore CALLs at last position in a node
state = node.states[idx]
instr = state.get_current_instruction()

@ -1,5 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<title> Laser - Call Graph</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/vis/4.21.0/vis.min.css" integrity="sha256-iq5ygGJ7021Pi7H5S+QAUXCPUfaBzfqeplbg/KlEssg=" crossorigin="anonymous" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vis/4.21.0/vis.min.js" integrity="sha256-JuQeAGbk9rG/EoRMixuy5X8syzICcvB0dj3KindZkY0=" crossorigin="anonymous"></script>

@ -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=enable_online_lookup) # control if you want to have online sighash lookups
try:
signatures.open() # open from default locations
except FileNotFoundError:

@ -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):
@ -38,16 +38,7 @@ class ETHContract(persistent.Persistent):
str_eval = ''
easm_code = None
matches = re.findall(r'func#([a-zA-Z0-9\s_,(\\)\[\]]+)#', expression)
for m in matches:
# Calculate function signature hashes
sign_hash = utils.sha3(m)[:4].hex()
expression = expression.replace(m, sign_hash)
tokens = filter(None, re.split("(and|or|not)", expression.replace(" ", ""), re.IGNORECASE))
tokens = re.split("\s+(and|or|not)\s+", expression, re.IGNORECASE)
for token in tokens:
@ -65,10 +56,13 @@ class ETHContract(persistent.Persistent):
str_eval += "\"" + code + "\" in easm_code"
continue
m = re.match(r'^func#([a-fA-F0-9]+)#$', token)
m = re.match(r'^func#([a-zA-Z0-9\s_,(\\)\[\]]+)#$', token)
if (m):
str_eval += "\"" + m.group(1) + "\" in self.disassembly.func_hashes"
sign_hash = "0x" + utils.sha3(m.group(1))[:4].hex()
str_eval += "\"" + sign_hash + "\" in self.disassembly.func_hashes"
continue

@ -9,15 +9,15 @@ import re
def trace(code, calldata = ""):
logHandlers = ['eth.vm.op', 'eth.vm.op.stack', 'eth.vm.op.memory', 'eth.vm.op.storage']
log_handlers = ['eth.vm.op', 'eth.vm.op.stack', 'eth.vm.op.memory', 'eth.vm.op.storage']
output = StringIO()
streamHandler = StreamHandler(output)
stream_handler = StreamHandler(output)
for handler in logHandlers:
for handler in log_handlers:
log_vm_op = get_logger(handler)
log_vm_op.setLevel("TRACE")
log_vm_op.addHandler(streamHandler)
log_vm_op.addHandler(stream_handler)
addr = bytes.fromhex('0123456789ABCDEF0123456789ABCDEF01234567')
@ -29,7 +29,7 @@ def trace(code, calldata = ""):
res, gas, dat = vm.vm_execute(ext, message, util.safe_decode(code))
streamHandler.flush()
stream_handler.flush()
ret = output.getvalue()

@ -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

@ -12,7 +12,7 @@ import argparse
# logging.basicConfig(level=logging.DEBUG)
from mythril.exceptions import CriticalError
from mythril.exceptions import CriticalError, AddressNotFoundError
from mythril.mythril import Mythril
from mythril.version import VERSION
@ -48,7 +48,7 @@ def main():
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>')
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')
@ -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,15 +78,16 @@ 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')
rpc.add_argument('--leveldb', action='store_true', help='Enable direct leveldb access operations')
# Get config values
args = parser.parse_args()
if args.version:
print("Mythril version {}".format(VERSION))
if args.outform == 'json':
print(json.dumps({'version_str': VERSION}))
else:
print("Mythril version {}".format(VERSION))
sys.exit()
# Parse cmdline args
@ -112,25 +110,23 @@ def main():
try:
# the mythril object should be our main interface
# infura = None, rpc = None, rpctls = None, ipc = None,
# 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.ipc or args.rpc or args.i):
if args.dynld and not (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()
elif args.rpc:
mythril.set_api_rpc(rpc=args.rpc, rpctls=args.rpctls)
elif args.ipc:
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)
@ -141,7 +137,11 @@ def main():
if args.contract_hash_to_address:
# search corresponding address
mythril.contract_hash_to_address(args.contract_hash_to_address)
try:
mythril.contract_hash_to_address(args.contract_hash_to_address)
except AddressNotFoundError:
print("Address not found.")
sys.exit()
if args.truffle:

@ -548,13 +548,10 @@ class Instruction:
bytecode = global_state.environment.code.bytecode
for i in range(concrete_size):
try:
str_val = bytecode[2*(concrete_code_offset + i): 2*(concrete_code_offset + i + 1)]
if str_val == '':
raise IndexError()
if 2 * (concrete_code_offset + i + 1) <= len(bytecode):
global_state.mstate.memory[concrete_memory_offset + i] =\
int(str_val, 16)
except IndexError:
int(bytecode[2*(concrete_code_offset + i): 2*(concrete_code_offset + i + 1)], 16)
else:
global_state.mstate.memory[concrete_memory_offset + i] = \
BitVec("code({})".format(global_state.environment.active_account.contract_name), 256)
@ -807,9 +804,9 @@ class Instruction:
instr = disassembly.instruction_list[index]
# True case
condi = condition if type(condition) == BoolRef else condition != 0
condi = simplify(condition) if type(condition) == BoolRef else condition != 0
if instr['opcode'] == "JUMPDEST":
if (type(condi) == bool and condi) or (type(condi) == BoolRef and not is_false(simplify(condi))):
if (type(condi) == bool and condi) or (type(condi) == BoolRef and not is_false(condi)):
new_state = copy(global_state)
new_state.mstate.pc = index
new_state.mstate.depth += 1
@ -820,9 +817,9 @@ class Instruction:
logging.debug("Pruned unreachable states.")
# False case
negated = Not(condition) if type(condition) == BoolRef else condition == 0
negated = simplify(Not(condition)) if type(condition) == BoolRef else condition == 0
if (type(negated) == bool and negated) or (type(negated) == BoolRef and not is_false(simplify(negated))):
if (type(negated) == bool and negated) or (type(negated) == BoolRef and not is_false(negated)):
new_state = copy(global_state)
new_state.mstate.depth += 1
new_state.mstate.constraints.append(negated)
@ -1045,9 +1042,16 @@ class Instruction:
return [global_state]
try:
memory_out_offset = util.get_concrete_int(memory_out_offset) if isinstance(memory_out_offset, ExprRef) else memory_out_offset
memory_out_size = util.get_concrete_int(memory_out_size) if isinstance(memory_out_size, ExprRef) else memory_out_size
except AttributeError:
global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256))
return [global_state]
# Copy memory
global_state.mstate.mem_extend(memory_out_offset, min(memory_out_size.as_long(), len(global_state.last_return_data)))
for i in range(min(memory_out_size.as_long(), len(global_state.last_return_data))):
global_state.mstate.mem_extend(memory_out_offset, min(memory_out_size, len(global_state.last_return_data)))
for i in range(min(memory_out_size, len(global_state.last_return_data))):
global_state.mstate.memory[i + memory_out_offset] = global_state.last_return_data[i]
# Put return value on stack
@ -1108,10 +1112,19 @@ class Instruction:
return [global_state]
try:
memory_out_offset = util.get_concrete_int(memory_out_offset) if isinstance(memory_out_offset,
ExprRef) else memory_out_offset
memory_out_size = util.get_concrete_int(memory_out_size) if isinstance(memory_out_size,
ExprRef) else memory_out_size
except AttributeError:
global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256))
return [global_state]
# Copy memory
global_state.mstate.mem_extend(memory_out_offset,
min(memory_out_size.as_long(), len(global_state.last_return_data)))
for i in range(min(memory_out_size.as_long(), len(global_state.last_return_data))):
min(memory_out_size, len(global_state.last_return_data)))
for i in range(min(memory_out_size, len(global_state.last_return_data))):
global_state.mstate.memory[i + memory_out_offset] = global_state.last_return_data[i]
# Put return value on stack

@ -54,6 +54,10 @@ class LaserEVM:
logging.info("LASER EVM initialized with dynamic loader: " + str(dynamic_loader))
@property
def accounts(self):
return self.world_state.accounts
def sym_exec(self, main_address=None, creation_code=None):
logging.debug("Starting LASER execution")
self.time = datetime.now()
@ -94,7 +98,7 @@ class LaserEVM:
# Setup new global state
new_global_state = e.transaction.initial_global_state()
new_global_state.transaction_stack.append((e.transaction, global_state))
new_global_state.transaction_stack = copy(global_state.transaction_stack) + [(e.transaction, global_state)]
new_global_state.node = global_state.node
new_global_state.mstate.constraints = global_state.mstate.constraints
@ -117,7 +121,7 @@ class LaserEVM:
return_global_state.last_return_data = transaction.return_data
return_global_state.world_state = copy(global_state.world_state)
return_global_state.environment.active_account = \
global_state.accounts[return_global_state.environment.active_account.contract_name]
global_state.accounts[return_global_state.environment.active_account.address]
# Execute the post instruction handler
new_global_states = Instruction(op_code, self.dynamic_loader).evaluate(return_global_state, True)
@ -145,7 +149,7 @@ class LaserEVM:
# Keep track of added contracts so the graph can be generated properly
if state.environment.active_account.contract_name not in self.world_state.accounts.keys():
self.world_state.accounts[
state.environment.active_account.contract_name] = state.environment.active_account
state.environment.active_account.address] = state.environment.active_account
elif opcode == "RETURN":
for state in new_states:
self._new_node_state(state, JumpType.RETURN)
@ -172,7 +176,7 @@ class LaserEVM:
except IndexError:
new_node.flags |= NodeFlags.FUNC_ENTRY
address = state.environment.code.instruction_list[state.mstate.pc - 1]['address']
environment = state.environment
disassembly = environment.code
if address in state.environment.code.addr_to_func:

@ -10,13 +10,15 @@ class TransactionEndSignal(Exception):
def __init__(self, global_state):
self.global_state = global_state
class TransactionStartSignal(Exception):
""" Exception raised when a new transaction is started"""
def __init__(self, transaction, op_code):
self.transaction = transaction
self.op_code = op_code
@property
def has_ran(self):
return self.open_states is not None
class MessageCallTransaction:
""" Transaction object models an transaction"""

@ -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
@ -58,6 +59,8 @@ class AccountIndexer(object):
self.lastBlock = None
self.lastProcessedBlock = None
self.updateIfNeeded()
def get_contract_by_hash(self, contract_hash):
'''
get mapped address by its hash, if not found try indexing
@ -65,7 +68,9 @@ class AccountIndexer(object):
address = self.db.reader._get_address_by_hash(contract_hash)
if address is not None:
return address
self.updateIfNeeded()
else:
raise AddressNotFoundError
return self.db.reader._get_address_by_hash(contract_hash)
def _process(self, startblock):

@ -8,24 +8,24 @@ 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
# Per https://github.com/ethereum/go-ethereum/blob/master/core/rawdb/schema.go
# prefixes and suffixes for keys in geth
headerPrefix = b'h' # headerPrefix + num (uint64 big endian) + hash -> header
bodyPrefix = b'b' # bodyPrefix + num (uint64 big endian) + hash -> block body
numSuffix = b'n' # headerPrefix + num (uint64 big endian) + numSuffix -> hash
blockHashPrefix = b'H' # blockHashPrefix + hash -> num (uint64 big endian)
blockReceiptsPrefix = b'r' # blockReceiptsPrefix + num (uint64 big endian) + hash -> block receipts
header_prefix = b'h' # header_prefix + num (uint64 big endian) + hash -> header
body_prefix = b'b' # body_prefix + num (uint64 big endian) + hash -> block body
num_suffix = b'n' # header_prefix + num (uint64 big endian) + num_suffix -> hash
block_hash_prefix = b'H' # block_hash_prefix + hash -> num (uint64 big endian)
block_receipts_prefix = b'r' # block_receipts_prefix + num (uint64 big endian) + hash -> block receipts
# known geth keys
headHeaderKey = b'LastBlock' # head (latest) header hash
head_header_key = b'LastBlock' # head (latest) header hash
# custom prefixes
addressPrefix = b'AM' # addressPrefix + hash -> address
address_prefix = b'AM' # address_prefix + hash -> address
# custom keys
addressMappingHeadKey = b'accountMapping' # head (latest) number of indexed block
headHeaderKey = b'LastBlock' # head (latest) header hash
address_mapping_head_key = b'accountMapping' # head (latest) number of indexed block
def _formatBlockNumber(number):
def _format_block_number(number):
'''
formats block number to uint64 big endian
'''
@ -46,87 +46,87 @@ class LevelDBReader(object):
def __init__(self, db):
self.db = db
self.headBlockHeader = None
self.headState = None
self.head_block_header = None
self.head_state = None
def _get_head_state(self):
'''
gets head state
'''
if not self.headState:
if not self.head_state:
root = self._get_head_block().state_root
self.headState = State(self.db, root)
return self.headState
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()
accountAddress = binascii.a2b_hex(utils.remove_0x_head(address))
return state.get_and_cache_account(accountAddress)
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 = _formatBlockNumber(number)
hashKey = headerPrefix + num + numSuffix
return self.db.get(hashKey)
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.headBlockHeader:
hash = self.db.get(headHeaderKey)
if not self.head_block_header:
hash = self.db.get(head_header_key)
num = self._get_block_number(hash)
self.headBlockHeader = self._get_block_header(hash, num)
self.head_block_header = self._get_block_header(hash, num)
# find header with valid state
while not self.db.get(self.headBlockHeader.state_root) and self.headBlockHeader.prevhash is not None:
hash = self.headBlockHeader.prevhash
while not self.db.get(self.head_block_header.state_root) and self.head_block_header.prevhash is not None:
hash = self.head_block_header.prevhash
num = self._get_block_number(hash)
self.headBlockHeader = self._get_block_header(hash, num)
self.head_block_header = self._get_block_header(hash, num)
return self.headBlockHeader
return self.head_block_header
def _get_block_number(self, hash):
'''
gets block number by hash
'''
numberKey = blockHashPrefix + hash
return self.db.get(numberKey)
number_key = block_hash_prefix + hash
return self.db.get(number_key)
def _get_block_header(self, hash, num):
'''
get block header by block header hash & number
'''
headerKey = headerPrefix + num + hash
blockHeaderData = self.db.get(headerKey)
header = rlp.decode(blockHeaderData, sedes=BlockHeader)
header_key = header_prefix + num + 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, hash):
'''
get mapped address by its hash
'''
addressKey = addressPrefix + hash
return self.db.get(addressKey)
address_key = address_prefix + hash
return self.db.get(address_key)
def _get_last_indexed_number(self):
'''
latest indexed block number
'''
return self.db.get(addressMappingHeadKey)
return self.db.get(address_mapping_head_key)
def _get_block_receipts(self, hash, num):
'''
get block transaction receipts by block header hash & number
'''
number = _formatBlockNumber(num)
receiptsKey = blockReceiptsPrefix + number + hash
receiptsData = self.db.get(receiptsKey)
receipts = rlp.decode(receiptsData, sedes=CountableList(ReceiptForStorage))
number = _format_block_number(num)
receipts_key = block_receipts_prefix + number + hash
receipts_data = self.db.get(receipts_key)
receipts = rlp.decode(receipts_data, sedes=CountableList(ReceiptForStorage))
return receipts
@ -143,7 +143,7 @@ class LevelDBWriter(object):
'''
sets latest indexed block number
'''
return self.db.put(addressMappingHeadKey, _formatBlockNumber(number))
return self.db.put(address_mapping_head_key, _format_block_number(number))
def _start_writing(self):
'''
@ -161,8 +161,8 @@ class LevelDBWriter(object):
'''
get block transaction receipts by block header hash & number
'''
addressKey = addressPrefix + utils.sha3(address)
self.wb.put(addressKey, address)
address_key = address_prefix + utils.sha3(address)
self.wb.put(address_key, address)
class EthLevelDB(object):
@ -180,61 +180,69 @@ 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)
address = indexer.get_contract_by_hash(account.address)
if address is None:
address = account.address
yield contract, _encode_hex(address), account.balance
contract = ETHContract(code, enable_online_lookup=False)
yield contract, account.address, account.balance
def search(self, expression, callback_func):
'''
searches through non-zero balance contracts
searches through all contract accounts
'''
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):
callback_func(contract.name, contract, [address], [balance])
try:
address = _encode_hex(indexer.get_contract_by_hash(address_hash))
except AddressNotFoundError:
'''
The hash->address mapping does not exist in our index. If the index is up-to-date, this likely means
that the contract was created by an internal transaction. Skip this contract as right now we don't
have a good solution for this.
'''
continue
callback_func(contract, address, balance)
cnt += 1
if not cnt % 10:
if not cnt % 1000:
logging.info("Searched %d contracts" % cnt)
def contract_hash_to_address(self, hash):
'''
tries to find corresponding account address
'''
address_hash = binascii.a2b_hex(utils.remove_0x_head(hash))
indexer = AccountIndexer(self)
addressHash = binascii.a2b_hex(utils.remove_0x_head(hash))
address = indexer.get_contract_by_hash(addressHash)
if address:
return _encode_hex(address)
else:
return "Not found"
return _encode_hex(indexer.get_contract_by_hash(address_hash))
def eth_getBlockHeaderByNumber(self, number):
'''
gets block header by block number
'''
hash = self.reader._get_block_hash(number)
blockNumber = _formatBlockNumber(number)
return self.reader._get_block_header(hash, blockNumber)
block_number = _format_block_number(number)
return self.reader._get_block_header(hash, block_number)
def eth_getBlockByNumber(self, number):
'''
gets block body by block number
'''
blockHash = self.reader._get_block_hash(number)
blockNumber = _formatBlockNumber(number)
bodyKey = bodyPrefix + blockNumber + blockHash
blockData = self.db.get(bodyKey)
body = rlp.decode(blockData, sedes=Block)
block_hash = self.reader._get_block_hash(number)
block_number = _format_block_number(number)
body_key = body_prefix + block_number + block_hash
block_data = self.db.get(body_key)
body = rlp.decode(block_data, sedes=Block)
return body
def eth_getCode(self, address):

@ -96,7 +96,7 @@ class State():
def __init__(self, db, root):
self.db = db
self.trie = Trie(self.db, root)
self.secureTrie = SecureTrie(self.trie)
self.secure_trie = SecureTrie(self.trie)
self.journal = []
self.cache = {}
@ -106,7 +106,7 @@ class State():
'''
if address in self.cache:
return self.cache[address]
rlpdata = self.secureTrie.get(address)
rlpdata = self.secure_trie.get(address)
if rlpdata == trie.BLANK_NODE and len(address) == 32: # support for hashed addresses
rlpdata = self.trie.get(address)
if rlpdata != trie.BLANK_NODE:
@ -123,6 +123,6 @@ class State():
'''
iterates through trie to and yields non-blank leafs as accounts
'''
for addressHash, rlpdata in self.secureTrie.trie.iter_branch():
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=addressHash)
yield rlp.decode(rlpdata, Account, db=self.db, address=address_hash)

@ -101,7 +101,7 @@ class Mythril(object):
self.leveldb_dir = self._init_config()
self.eth = None # ethereum API client
self.ethDb = None # ethereum LevelDB client
self.eth_db = None # ethereum LevelDB client
self.contracts = [] # loaded contracts
@ -180,7 +180,7 @@ class Mythril(object):
config.set('defaults', 'dynamic_loading', 'infura')
def analyze_truffle_project(self, *args, **kwargs):
return analyze_truffle_project(*args, **kwargs) # just passthru for now
return analyze_truffle_project(self.sigs, *args, **kwargs) # just passthru by passing signatures for now
def _init_solc_binary(self, version):
# Figure out solc binary and version
@ -213,8 +213,8 @@ class Mythril(object):
return solc_binary
def set_api_leveldb(self, leveldb):
self.ethDb = EthLevelDB(leveldb)
self.eth = self.ethDb
self.eth_db = EthLevelDB(leveldb)
self.eth = self.eth_db
return self.eth
def set_api_rpc_infura(self):
@ -272,12 +272,12 @@ class Mythril(object):
def search_db(self, search):
def search_callback(code_hash, code, addresses, balances):
for i in range(0, len(addresses)):
print("Address: " + addresses[i] + ", balance: " + str(balances[i]))
def search_callback(contract, address, balance):
print("Address: " + address + ", balance: " + str(balance))
try:
self.ethDb.search(search, search_callback)
self.eth_db.search(search, search_callback)
except SyntaxError:
raise CriticalError("Syntax error in search expression.")
@ -286,7 +286,7 @@ class Mythril(object):
if not re.match(r'0x[a-fA-F0-9]{64}', hash):
raise CriticalError("Invalid address hash. Expected format is '0x...'.")
print(self.ethDb.contract_hash_to_address(hash))
print(self.eth_db.contract_hash_to_address(hash))
def load_from_bytecode(self, code):
address = util.get_indexed_address(0)

@ -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)

@ -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]
@ -216,9 +214,17 @@ class SignatureDb(object):
:param code: solidity source code
:return: dictionary {sighash: function_signature}
"""
sigs = {}
funcs = re.findall(r'function[\s]+(.*?\))', code, re.DOTALL)
return SignatureDb.get_sigs_from_functions(funcs)
@staticmethod
def get_sigs_from_functions(funcs):
"""
:param funcs: accepts a list of functions
:return: their signature mappings
"""
sigs = {}
for f in funcs:
f = re.sub(r'[\n]', '', f)
m = re.search(r'^([A-Za-z0-9_]+)', f)
@ -242,5 +248,5 @@ class SignatureDb(object):
signature = re.sub(r'\s', '', signature)
sigs["0x" + utils.sha3(signature)[:4].hex()] = signature
logging.debug("Signatures: parse soldiity found %d signatures" % len(sigs))
logging.debug("Signatures: found %d signatures after parsing" % len(sigs))
return sigs

@ -6,6 +6,7 @@ import json
import logging
from mythril.ether.ethcontract import ETHContract
from mythril.ether.soliditycontract import SourceMapping
from mythril.exceptions import CriticalError
from mythril.analysis.security import fire_lasers
from mythril.analysis.symbolic import SymExecWrapper
from mythril.analysis.report import Report
@ -14,7 +15,7 @@ from mythril.ether import util
from mythril.laser.ethereum.util import get_instruction_index
def analyze_truffle_project(args):
def analyze_truffle_project(sigs, args):
project_root = os.getcwd()
@ -33,13 +34,17 @@ def analyze_truffle_project(args):
name = contractdata['contractName']
bytecode = contractdata['deployedBytecode']
filename = PurePath(contractdata['sourcePath']).name
except:
abi = contractdata['abi']
except KeyError:
print("Unable to parse contract data. Please use Truffle 4 to compile your project.")
sys.exit()
if (len(bytecode) < 4):
if len(bytecode) < 4:
continue
list_of_functions = parse_abi_for_functions(abi)
sigs.signatures.update(sigs.get_sigs_from_functions(list_of_functions))
sigs.write()
ethcontract = ETHContract(bytecode, name=name)
address = util.get_indexed_address(0)
@ -47,7 +52,7 @@ def analyze_truffle_project(args):
issues = fire_lasers(sym)
if not len(issues):
if (args.outform == 'text' or args.outform == 'markdown'):
if args.outform == 'text' or args.outform == 'markdown':
print("# Analysis result for " + name + "\n\nNo issues found.")
else:
result = {'contract': name, 'result': {'success': True, 'error': None, 'issues': []}}
@ -60,11 +65,11 @@ def analyze_truffle_project(args):
disassembly = ethcontract.disassembly
source = contractdata['source']
deployedSourceMap = contractdata['deployedSourceMap'].split(";")
deployed_source_map = contractdata['deployedSourceMap'].split(";")
mappings = []
for item in deployedSourceMap:
for item in deployed_source_map:
mapping = item.split(":")
if len(mapping) > 0 and len(mapping[0]) > 0:
@ -97,13 +102,23 @@ def analyze_truffle_project(args):
report.append_issue(issue)
if (args.outform == 'json'):
if args.outform == 'json':
result = {'contract': name, 'result': {'success': True, 'error': None, 'issues': list(map(lambda x: x.as_dict, issues))}}
print(json.dumps(result))
else:
if (args.outform == 'text'):
if args.outform == 'text':
print("# Analysis result for " + name + ":\n\n" + report.as_text())
elif (args.outform == 'markdown'):
elif args.outform == 'markdown':
print(report.as_markdown())
def parse_abi_for_functions(abi):
funcs = []
for data in abi:
if data['type'] != 'function':
continue
args = '('+','.join([input['type'] for input in data['inputs']])+')'
funcs.append(data['name']+args)
return funcs

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

@ -0,0 +1,40 @@
from mythril.disassembler.disassembly import Disassembly
from mythril.laser.ethereum import svm
import mythril.laser.ethereum.cfg as cfg
def test_intercontract_call():
# Arrange
cfg.gbl_next_uid = 0
caller_code = Disassembly("6080604052348015600f57600080fd5b5073deadbeefdeadbeefdeadbeefdeadbeefdeadbeef73ffffffffffffffffffffffffffffffffffffffff166389627e13336040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050602060405180830381600087803b15801560be57600080fd5b505af115801560d1573d6000803e3d6000fd5b505050506040513d602081101560e657600080fd5b8101908080519060200190929190505050500000a165627a7a72305820fdb1e90f0d9775c94820e516970e0d41380a94624fa963c556145e8fb645d4c90029")
caller_address = "0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe"
callee_code = Disassembly("608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806389627e13146044575b600080fd5b348015604f57600080fd5b506082600480360381019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506084565b005b8073ffffffffffffffffffffffffffffffffffffffff166108fc3073ffffffffffffffffffffffffffffffffffffffff16319081150290604051600060405180830381858888f1935050505015801560e0573d6000803e3d6000fd5b50505600a165627a7a72305820a6b1335d6f994632bc9a7092d0eaa425de3dea05e015af8a94ad70b3969e117a0029")
callee_address = "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
caller_account = svm.Account(caller_address, caller_code, contract_name="Caller")
callee_account = svm.Account(callee_address, callee_code, contract_name="Callee")
accounts = {
caller_address: caller_account,
callee_address: callee_account
}
laser = svm.LaserEVM(accounts)
# Act
laser.sym_exec(caller_address)
# Assert
# Initial node starts in contract caller
assert len(laser.nodes.keys()) > 0
assert laser.nodes[0].contract_name == 'Caller'
# At one point we call into contract callee
for node in laser.nodes.values():
if node.contract_name == 'Callee':
assert len(node.states[0].transaction_stack) > 1
return
assert False

@ -0,0 +1,25 @@
from mythril.interfaces.cli import main
import pytest
import json
import sys
def test_version_opt(capsys):
# Check that "myth --version" returns a string with the word
# "version" in it
sys.argv = ['mythril', '--version']
with pytest.raises(SystemExit) as pytest_wrapped_e:
main()
assert pytest_wrapped_e.type == SystemExit
captured = capsys.readouterr()
assert captured.out.find(' version ') >= 1
# Check that "myth --version -o json" returns a JSON object
sys.argv = ['mythril', '--version', '-o', 'json']
with pytest.raises(SystemExit) as pytest_wrapped_e:
main()
assert pytest_wrapped_e.type == SystemExit
captured = capsys.readouterr()
d = json.loads(captured.out)
assert isinstance(d, dict)
assert d['version_str']
Loading…
Cancel
Save