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

pull/404/head
Joran Honig 6 years ago
commit 479d3ccf42
  1. 4
      .circleci/config.yml
  2. 35
      .github/ISSUE_TEMPLATE/analysis-module.md
  3. 68
      .github/ISSUE_TEMPLATE/bug-report.md
  4. 28
      .github/ISSUE_TEMPLATE/bug_report.md
  5. 24
      .github/ISSUE_TEMPLATE/feature-request.md
  6. 2
      README.md
  7. 6
      all_tests.sh
  8. 2
      myth
  9. 6
      mythril/__init__.py
  10. 22
      mythril/analysis/modules/ether_send.py
  11. 2
      mythril/analysis/modules/exceptions.py
  12. 1
      mythril/analysis/modules/integer.py
  13. 2
      mythril/analysis/solver.py
  14. 12
      mythril/analysis/symbolic.py
  15. 7
      mythril/disassembler/disassembly.py
  16. 22
      mythril/ether/ethcontract.py
  17. 22
      mythril/ether/soliditycontract.py
  18. 56
      mythril/interfaces/cli.py
  19. 60
      mythril/laser/ethereum/instructions.py
  20. 11
      mythril/laser/ethereum/state.py
  21. 36
      mythril/laser/ethereum/svm.py
  22. 17
      mythril/laser/ethereum/taint_analysis.py
  23. 4
      mythril/laser/ethereum/transaction.py
  24. 152
      mythril/leveldb/accountindexing.py
  25. 250
      mythril/leveldb/client.py
  26. 10
      mythril/leveldb/eth_db.py
  27. 145
      mythril/mythril.py
  28. 4
      mythril/support/signatures.py
  29. 8
      mythril/support/truffle.py
  30. 3
      mythril/version.py
  31. 1
      requirements.txt
  32. 22
      setup.py
  33. 299
      solidity_examples/BECToken.sol
  34. 396
      solidity_examples/WalletLibrary.sol
  35. 31
      solidity_examples/ether_send.sol
  36. 21
      solidity_examples/etherstore.sol
  37. 12
      solidity_examples/hashforether.sol
  38. 21
      solidity_examples/timelock.sol
  39. 6
      solidity_examples/token.sol
  40. 2
      tests/graph_test.py
  41. 2
      tests/native_test.py
  42. 2
      tests/report_test.py
  43. 4
      tests/svm_test.py
  44. 10
      tests/taint_runner_test.py
  45. 19
      tests/testdata/input_contracts/environments.sol
  46. 1
      tests/testdata/inputs/environments.sol.o
  47. 259
      tests/testdata/outputs_expected/environments.sol.o.easm
  48. 56
      tests/testdata/outputs_expected/environments.sol.o.graph.html
  49. 1
      tests/testdata/outputs_expected/environments.sol.o.json
  50. 37
      tests/testdata/outputs_expected/environments.sol.o.markdown
  51. 27
      tests/testdata/outputs_expected/environments.sol.o.text
  52. 54936
      tests/testdata/outputs_expected_laser_result/environments.sol.json
  53. 2
      tox.ini

@ -57,6 +57,10 @@ jobs:
command: python3 setup.py install command: python3 setup.py install
working_directory: /home/mythril working_directory: /home/mythril
- run:
name: Sonar analysis
command: if [ -z "$CIRCLE_PR_NUMBER" ]; then if [ -z "$CIRCLE_TAG" ]; then sonar-scanner -Dsonar.projectKey=$SONAR_PROJECT_KEY -Dsonar.organization=$SONAR_ORGANIZATION -Dsonar.branch.name=$CIRCLE_BRANCH -Dsonar.projectBaseDir=/home/mythril -Dsonar.sources=mythril -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN; fi; fi
- run: - run:
name: Integration tests name: Integration tests
command: if [ -z "$CIRCLE_PR_NUMBER" ]; then ./run-integration-tests.sh; fi command: if [ -z "$CIRCLE_PR_NUMBER" ]; then ./run-integration-tests.sh; fi

@ -4,13 +4,34 @@ about: Create an analysis module feature request
--- ---
# Detection issue: Please remove any of the optional sections if they are not applicable.
Name the issue that should be detected using the analysis module
## Description: ## Description
Provide a detailed description of the vulnerabiltity that should be detected
## Link Replace this text with a description of an vulnerability that should be
Provide resources that can help with implementing the analysis module detected by a Mythril analysis module.
## Implementation details:Initial implementation ideas/instruction ## Tests
_This section is optional._
Replace this text with suggestions on how to test the feature,
if it is not obvious. This might require certain Solidity source,
bytecode, or a Truffle project. You can also provide
links to existing code.
## Implementation details
_This section is optional._
If you have thoughts about how to implement the analysis, feel free
replace this text with that.
## Links
_This section is optional._
Replace this text with any links describing the issue or pointing to resources
that can help in implementing the analysis
Thanks for helping!

@ -0,0 +1,68 @@
---
name: Bug report
about: Tell us about Mythril bugs to help us improve
---
_Note: did you notice that there is now a template for requesting new features?_
Please remove any of the optional sections if they are not applicable.
## Description
Replace this text with a clear and concise description of the bug.
## How to Reproduce
Please show both the input you gave and the
output you got in describing how to reproduce the bug:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
or give a complete console log with input and output
```console
$ myth <command-line-options>
==== Exception state ====
Type: ...
Contract: ...
Function name: ...
...
$
```
If there is a Solidity source code, a truffle project, or bytecode
that is involved, please provide that or links to it.
## Expected behavior
A clear and concise description of what you expected to happen.
## Screenshots
_This section is optional._
If applicable, add screenshots to help explain your problem.
## Environment
_This section sometimes is optional but helpful to us._
Please modify for your setup
- Mythril version: output from `myth --version` or `pip show mythril`
- Solidity compiler and version: `solc --version`
- Python version: `python -V`
- OS: [e.g. iOS]
- OS Version [e.g. 22]
## Additional Environment or Context
_This section is optional._
Add any other context about the problem here or special environment setup
Thanks for helping!

@ -1,28 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

@ -0,0 +1,24 @@
---
name: Feature Request
about: Tell us about a new feature that would make Mythril better
---
## Description
Replace this text with a short description of the feature.
## Background
Replace this text with any additional background for the
feature, for example: user scenarios, or the value of the feature.
## Tests
_This section is optional._
Replace this text with suggestions on how to test the feature,
if it is not obvious. This might require certain Solidity source,
bytecode, or a Truffle project. You can also provide
links to existing code.
Thanks for helping!

@ -26,7 +26,7 @@ See the [Wiki](https://github.com/ConsenSys/mythril/wiki/Installation-and-Setup)
## Usage ## Usage
Documentation has moved to the [Wiki page](https://github.com/ConsenSys/mythril/wiki). 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).
## Publications and Videos ## Publications and Videos

@ -3,8 +3,8 @@
echo -n "Checking Python version... " echo -n "Checking Python version... "
python -c 'import sys python -c 'import sys
print(sys.version) print(sys.version)
assert sys.version_info[0:2] >= (3,6), \ assert sys.version_info[0:2] >= (3,5), \
"""Please make sure you are using Python 3.6.x. """Please make sure you are using Python 3.5 or later.
You ran with {}""".format(sys.version)' || exit $? You ran with {}""".format(sys.version)' || exit $?
echo "Checking solc version..." echo "Checking solc version..."
@ -15,7 +15,7 @@ out=$(solc --version) || {
case $out in case $out in
*Version:\ 0.4.2[1-9]* ) *Version:\ 0.4.2[1-9]* )
echo $out echo $out
break ;; ;;
* ) * )
echo $out echo $out
echo "Please make sure your solc version is at least 0.4.21" echo "Please make sure your solc version is at least 0.4.21"

@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: UTF-8 -*- # -*- coding: utf-8 -*-
"""mythril.py: Bug hunting on the Ethereum blockchain """mythril.py: Bug hunting on the Ethereum blockchain
http://www.github.com/b-mueller/mythril http://www.github.com/b-mueller/mythril
""" """

@ -0,0 +1,6 @@
# We use RsT document formatting in docstring. For example :param to mark parameters.
# See PEP 287
__docformat__ = 'restructuredtext'
# Accept mythril.VERSION to get mythril's current version number
from .version import VERSION # NOQA

@ -27,15 +27,14 @@ def execute(statespace):
state = call.state state = call.state
address = state.get_current_instruction()['address'] address = state.get_current_instruction()['address']
if ("callvalue" in str(call.value)): if "callvalue" in str(call.value):
logging.debug("[ETHER_SEND] Skipping refund function") logging.debug("[ETHER_SEND] Skipping refund function")
continue continue
# We're only interested in calls that send Ether # We're only interested in calls that send Ether
if call.value.type == VarType.CONCRETE: if call.value.type == VarType.CONCRETE and call.value.val == 0:
if call.value.val == 0: continue
continue
interesting = False interesting = False
@ -52,14 +51,14 @@ def execute(statespace):
else: else:
m = re.search(r'storage_([a-z0-9_&^]+)', str(call.to)) m = re.search(r'storage_([a-z0-9_&^]+)', str(call.to))
if (m): if m:
idx = m.group(1) idx = m.group(1)
description += "a non-zero amount of Ether is sent to an address taken from storage slot " + str(idx) + ".\n" description += "a non-zero amount of Ether is sent to an address taken from storage slot " + str(idx) + ".\n"
func = statespace.find_storage_write(state.environment.active_account.address, idx) func = statespace.find_storage_write(state.environment.active_account.address, idx)
if (func): if func:
description += "There is a check on storage index " + str(idx) + ". This storage slot can be written to by calling the function `" + func + "`.\n" description += "There is a check on storage index " + str(idx) + ". This storage slot can be written to by calling the function `" + func + "`.\n"
interesting = True interesting = True
else: else:
@ -74,7 +73,7 @@ def execute(statespace):
index = 0 index = 0
while(can_solve and index < len(node.constraints)): while can_solve and index < len(node.constraints):
constraint = node.constraints[index] constraint = node.constraints[index]
index += 1 index += 1
@ -82,14 +81,14 @@ def execute(statespace):
m = re.search(r'storage_([a-z0-9_&^]+)', str(constraint)) m = re.search(r'storage_([a-z0-9_&^]+)', str(constraint))
if (m): if m:
constrained = True constrained = True
idx = m.group(1) idx = m.group(1)
func = statespace.find_storage_write(state.environment.active_account.address, idx) func = statespace.find_storage_write(state.environment.active_account.address, idx)
if (func): if func:
description += "\nThere is a check on storage index " + str(idx) + ". This storage slot can be written to by calling the function `" + func + "`." description += "\nThere is a check on storage index " + str(idx) + ". This storage slot can be written to by calling the function `" + func + "`."
else: else:
logging.debug("[ETHER_SEND] No storage writes to index " + str(idx)) logging.debug("[ETHER_SEND] No storage writes to index " + str(idx))
@ -98,7 +97,7 @@ def execute(statespace):
# CALLER may also be constrained to hardcoded address. I.e. 'caller' and some integer # CALLER may also be constrained to hardcoded address. I.e. 'caller' and some integer
elif (re.search(r"caller", str(constraint)) and re.search(r'[0-9]{20}', str(constraint))): elif re.search(r"caller", str(constraint)) and re.search(r'[0-9]{20}', str(constraint)):
constrained = True constrained = True
can_solve = False can_solve = False
break break
@ -116,7 +115,8 @@ def execute(statespace):
debug = "SOLVER OUTPUT:\n" + solver.pretty_print_model(model) debug = "SOLVER OUTPUT:\n" + solver.pretty_print_model(model)
issue = Issue(call.node.contract_name, call.node.function_name, address, "Ether send", "Warning", description, debug) issue = Issue(call.node.contract_name, call.node.function_name, address, "Ether send", "Warning",
description, debug)
issues.append(issue) issues.append(issue)
except UnsatError: except UnsatError:

@ -24,9 +24,7 @@ def execute(statespace):
for state in node.states: for state in node.states:
instruction = state.get_current_instruction() instruction = state.get_current_instruction()
if(instruction['opcode'] == "ASSERT_FAIL"): if(instruction['opcode'] == "ASSERT_FAIL"):
try: try:
model = solver.get_model(node.constraints) model = solver.get_model(node.constraints)
address = state.get_current_instruction()['address'] address = state.get_current_instruction()['address']

@ -108,7 +108,6 @@ def _verify_integer_overflow(statespace, node, expr, state, model, constraint, o
return _try_constraints(node.constraints, [Not(constraint)]) is not None return _try_constraints(node.constraints, [Not(constraint)]) is not None
def _try_constraints(constraints, new_constraints): def _try_constraints(constraints, new_constraints):
""" """
Tries new constraints Tries new constraints

@ -4,7 +4,7 @@ import logging
def get_model(constraints): def get_model(constraints):
s = Solver() s = Solver()
s.set("timeout", 10000) s.set("timeout", 100000)
for constraint in constraints: for constraint in constraints:
s.add(constraint) s.add(constraint)

@ -3,6 +3,7 @@ from mythril.laser.ethereum import svm
import copy import copy
import logging import logging
from .ops import get_variable, SStore, Call, VarType from .ops import get_variable, SStore, Call, VarType
from mythril.laser.ethereum.strategy.basic import DepthFirstSearchStrategy, BreadthFirstSearchStrategy
class SymExecWrapper: class SymExecWrapper:
@ -11,13 +12,20 @@ class SymExecWrapper:
Wrapper class for the LASER Symbolic virtual machine. Symbolically executes the code and does a bit of pre-analysis for convenience. 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, dynloader=None, max_depth=12): def __init__(self, contract, address, strategy, dynloader=None, max_depth=22, execution_timeout=None):
s_strategy = None
if strategy == 'dfs':
s_strategy = DepthFirstSearchStrategy
elif strategy == 'bfs':
s_strategy = BreadthFirstSearchStrategy
else:
raise ValueError("Invalid strategy argument supplied")
account = svm.Account(address, contract.disassembly, contract_name=contract.name) account = svm.Account(address, contract.disassembly, contract_name=contract.name)
self.accounts = {address: account} self.accounts = {address: account}
self.laser = svm.LaserEVM(self.accounts, dynamic_loader=dynloader, max_depth=max_depth) self.laser = svm.LaserEVM(self.accounts, dynamic_loader=dynloader, max_depth=max_depth, execution_timeout=execution_timeout, strategy=s_strategy)
self.laser.sym_exec(address) self.laser.sym_exec(address)

@ -1,4 +1,4 @@
from mythril.ether import asm,util from mythril.ether import asm, util
from mythril.support.signatures import SignatureDb from mythril.support.signatures import SignatureDb
import logging import logging
@ -7,7 +7,7 @@ class Disassembly(object):
def __init__(self, code): def __init__(self, code):
self.instruction_list = asm.disassemble(util.safe_decode(code)) self.instruction_list = asm.disassemble(util.safe_decode(code))
self.xrefs = [] self.func_hashes = []
self.func_to_addr = {} self.func_to_addr = {}
self.addr_to_func = {} self.addr_to_func = {}
self.bytecode = code self.bytecode = code
@ -24,6 +24,7 @@ class Disassembly(object):
for i in jmptable_indices: for i in jmptable_indices:
func_hash = self.instruction_list[i]['argument'] func_hash = self.instruction_list[i]['argument']
self.func_hashes.append(func_hash)
try: try:
# tries local cache, file and optional online lookup # tries local cache, file and optional online lookup
# may return more than one function signature. since we cannot probe for the correct one we'll use the first # may return more than one function signature. since we cannot probe for the correct one we'll use the first
@ -38,7 +39,7 @@ class Disassembly(object):
func_name = "_function_" + func_hash func_name = "_function_" + func_hash
try: try:
offset = self.instruction_list[i+2]['argument'] offset = self.instruction_list[i + 2]['argument']
jump_target = int(offset, 16) jump_target = int(offset, 16)
self.func_to_addr[func_name] = jump_target self.func_to_addr[func_name] = jump_target

@ -31,12 +31,12 @@ class ETHContract(persistent.Persistent):
def get_easm(self): def get_easm(self):
return Disassembly(self.code).get_easm() return self.disassembly.get_easm()
def matches_expression(self, expression): def matches_expression(self, expression):
easm_code = self.get_easm()
str_eval = '' str_eval = ''
easm_code = None
matches = re.findall(r'func#([a-zA-Z0-9\s_,(\\)\[\]]+)#', expression) matches = re.findall(r'func#([a-zA-Z0-9\s_,(\\)\[\]]+)#', expression)
@ -58,6 +58,9 @@ class ETHContract(persistent.Persistent):
m = re.match(r'^code#([a-zA-Z0-9\s,\[\]]+)#', token) 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()
code = m.group(1).replace(",", "\\n") code = m.group(1).replace(",", "\\n")
str_eval += "\"" + code + "\" in easm_code" str_eval += "\"" + code + "\" in easm_code"
continue continue
@ -65,21 +68,8 @@ class ETHContract(persistent.Persistent):
m = re.match(r'^func#([a-fA-F0-9]+)#$', token) m = re.match(r'^func#([a-fA-F0-9]+)#$', token)
if (m): if (m):
str_eval += "\"" + m.group(1) + "\" in easm_code" str_eval += "\"" + m.group(1) + "\" in self.disassembly.func_hashes"
continue continue
return eval(str_eval.strip()) return eval(str_eval.strip())
class InstanceList(persistent.Persistent):
def __init__(self):
self.addresses = []
self.balances = []
pass
def add(self, address, balance=0):
self.addresses.append(address)
self.balances.append(balance)
self._p_changed = True

@ -1,8 +1,9 @@
import mythril.laser.ethereum.util as helper import mythril.laser.ethereum.util as helper
from mythril.ether.ethcontract import ETHContract from mythril.ether.ethcontract import ETHContract
from mythril.ether.util import * from mythril.ether.util import get_solc_json
from mythril.exceptions import NoContractFoundError from mythril.exceptions import NoContractFoundError
class SourceMapping: class SourceMapping:
def __init__(self, solidity_file_idx, offset, length, lineno): def __init__(self, solidity_file_idx, offset, length, lineno):
@ -27,6 +28,14 @@ class SourceCodeInfo:
self.code = code self.code = code
def get_contracts_from_file(input_file, solc_args=None):
data = get_solc_json(input_file, solc_args=solc_args)
for key, contract in data['contracts'].items():
filename, name = key.split(":")
if filename == input_file and len(contract['bin-runtime']):
yield SolidityContract(input_file, name, solc_args)
class SolidityContract(ETHContract): class SolidityContract(ETHContract):
def __init__(self, input_file, name=None, solc_args=None): def __init__(self, input_file, name=None, solc_args=None):
@ -45,11 +54,10 @@ class SolidityContract(ETHContract):
# If a contract name has been specified, find the bytecode of that specific contract # If a contract name has been specified, find the bytecode of that specific contract
if name: if name:
for key, contract in data['contracts'].items(): for key, contract in sorted(data['contracts'].items()):
filename, _name = key.split(":") filename, _name = key.split(":")
if filename == input_file and name == _name and len(contract['bin-runtime']): if filename == input_file and name == _name and len(contract['bin-runtime']):
name = name
code = contract['bin-runtime'] code = contract['bin-runtime']
creation_code = contract['bin'] creation_code = contract['bin']
srcmap = contract['srcmap-runtime'].split(";") srcmap = contract['srcmap-runtime'].split(";")
@ -59,11 +67,10 @@ class SolidityContract(ETHContract):
# If no contract name is specified, get the last bytecode entry for the input file # If no contract name is specified, get the last bytecode entry for the input file
else: else:
for key, contract in data['contracts'].items(): for key, contract in sorted(data['contracts'].items()):
filename, name = key.split(":") filename, name = key.split(":")
if filename == input_file and len(contract['bin-runtime']): if filename == input_file and len(contract['bin-runtime']):
name = name
code = contract['bin-runtime'] code = contract['bin-runtime']
creation_code = contract['bin'] creation_code = contract['bin']
srcmap = contract['srcmap-runtime'].split(";") srcmap = contract['srcmap-runtime'].split(";")
@ -85,8 +92,7 @@ class SolidityContract(ETHContract):
if len(mapping) > 2 and len(mapping[2]) > 0: if len(mapping) > 2 and len(mapping[2]) > 0:
idx = int(mapping[2]) idx = int(mapping[2])
lineno = self.solidity_files[idx].data.encode('utf-8')[0:offset].count('\n'.encode('utf-8')) + 1
lineno = self.solidity_files[idx].data[0:offset].count('\n') + 1
self.mappings.append(SourceMapping(idx, offset, length, lineno)) self.mappings.append(SourceMapping(idx, offset, length, lineno))
@ -103,7 +109,7 @@ class SolidityContract(ETHContract):
offset = self.mappings[index].offset offset = self.mappings[index].offset
length = self.mappings[index].length length = self.mappings[index].length
code = solidity_file.data[offset:offset + length] code = solidity_file.data.encode('utf-8')[offset:offset + length].decode('utf-8')
lineno = self.mappings[index].lineno lineno = self.mappings[index].lineno
return SourceCodeInfo(filename, lineno, code) return SourceCodeInfo(filename, lineno, code)

@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: UTF-8 -*- # -*- coding: utf-8 -*-
"""mythril.py: Bug hunting on the Ethereum blockchain """mythril.py: Bug hunting on the Ethereum blockchain
http://www.github.com/ConsenSys/mythril http://www.github.com/ConsenSys/mythril
@ -14,6 +14,7 @@ import argparse
from mythril.exceptions import CriticalError from mythril.exceptions import CriticalError
from mythril.mythril import Mythril from mythril.mythril import Mythril
from mythril.version import VERSION
def exit_with_error(format, message): def exit_with_error(format, message):
@ -31,6 +32,8 @@ def main():
commands = parser.add_argument_group('commands') commands = parser.add_argument_group('commands')
commands.add_argument('-g', '--graph', help='generate a control flow graph') commands.add_argument('-g', '--graph', help='generate a control flow graph')
commands.add_argument('-V', '--version', action='store_true',
help='print the Mythril version number and exit')
commands.add_argument('-x', '--fire-lasers', action='store_true', commands.add_argument('-x', '--fire-lasers', action='store_true',
help='detect vulnerabilities, use with -c, -a or solidity file(s)') help='detect vulnerabilities, use with -c, -a or solidity file(s)')
commands.add_argument('-t', '--truffle', action='store_true', commands.add_argument('-t', '--truffle', action='store_true',
@ -51,7 +54,6 @@ def main():
database = parser.add_argument_group('local contracts database') database = parser.add_argument_group('local contracts database')
database.add_argument('-s', '--search', help='search the contract database', metavar='EXPRESSION') database.add_argument('-s', '--search', help='search the contract database', metavar='EXPRESSION')
database.add_argument('--leveldb-dir', help='specify leveldb directory for search or direct access operations', metavar='LEVELDB_PATH') database.add_argument('--leveldb-dir', help='specify leveldb directory for search or direct access operations', metavar='LEVELDB_PATH')
database.add_argument('--search-all', action='store_true', help='search all contracts instead of active (non-zero balance) only')
utilities = parser.add_argument_group('utilities') utilities = parser.add_argument_group('utilities')
utilities.add_argument('--hash', help='calculate function signature hash', metavar='SIGNATURE') utilities.add_argument('--hash', help='calculate function signature hash', metavar='SIGNATURE')
@ -60,10 +62,16 @@ def main():
utilities.add_argument('--solv', utilities.add_argument('--solv',
help='specify solidity compiler version. If not present, will try to install it (Experimental)', help='specify solidity compiler version. If not present, will try to install it (Experimental)',
metavar='SOLV') metavar='SOLV')
utilities.add_argument('--contract-hash-to-address', help='returns corresponding address for a contract address hash', metavar='SHA3_TO_LOOK_FOR')
options = parser.add_argument_group('options') options = parser.add_argument_group('options')
options.add_argument('-m', '--modules', help='Comma-separated list of security analysis modules', metavar='MODULES') 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('--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('--solc-args', help='Extra arguments for solc') 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('--phrack', action='store_true', help='Phrack-style call graph')
options.add_argument('--enable-physics', action='store_true', help='enable graph physics simulation') options.add_argument('--enable-physics', action='store_true', help='enable graph physics simulation')
@ -80,16 +88,14 @@ def main():
args = parser.parse_args() args = parser.parse_args()
# -- args sanity checks -- if args.version:
# Detect unsupported combinations of command line args print("Mythril version {}".format(VERSION))
sys.exit()
if args.dynld and not args.address:
exit_with_error(args.outform, "Dynamic loader can be used in on-chain analysis mode only (-a).")
# Parse cmdline args # Parse cmdline args
if not (args.search or args.hash or args.disassemble or args.graph or args.fire_lasers if not (args.search or args.hash or args.disassemble or args.graph or args.fire_lasers
or args.storage or args.truffle or args.statespace_json): or args.storage or args.truffle or args.statespace_json or args.contract_hash_to_address):
parser.print_help() parser.print_help()
sys.exit() sys.exit()
@ -104,15 +110,15 @@ def main():
print(Mythril.hash_for_function_signature(args.hash)) print(Mythril.hash_for_function_signature(args.hash))
sys.exit() sys.exit()
try: try:
# the mythril object should be our main interface # the mythril object should be our main interface
#infura = None, rpc = None, rpctls = None, ipc = None, # infura = None, rpc = None, rpctls = None, ipc = None,
#solc_args = None, dynld = None, max_recursion_depth = 12): # solc_args = None, dynld = None, max_recursion_depth = 12):
mythril = Mythril(solv=args.solv, dynld=args.dynld, mythril = Mythril(solv=args.solv, dynld=args.dynld,
solc_args=args.solc_args) solc_args=args.solc_args)
if args.dynld and not (args.ipc or args.rpc or args.i):
mythril.set_api_from_config_path()
if args.address and not args.leveldb: if args.address and not args.leveldb:
# Establish RPC/IPC connection if necessary # Establish RPC/IPC connection if necessary
@ -122,15 +128,20 @@ def main():
mythril.set_api_rpc(rpc=args.rpc, rpctls=args.rpctls) mythril.set_api_rpc(rpc=args.rpc, rpctls=args.rpctls)
elif args.ipc: elif args.ipc:
mythril.set_api_ipc() mythril.set_api_ipc()
else: elif not args.dynld:
mythril.set_api_rpc_localhost() mythril.set_api_rpc_localhost()
elif args.leveldb or args.search: elif args.leveldb or args.search or args.contract_hash_to_address:
# Open LevelDB if necessary # Open LevelDB if necessary
mythril.set_api_leveldb(mythril.leveldb_dir if not args.leveldb_dir else args.leveldb_dir) mythril.set_api_leveldb(mythril.leveldb_dir if not args.leveldb_dir else args.leveldb_dir)
if args.search: if args.search:
# Database search ops # Database search ops
mythril.search_db(args.search, args.search_all) mythril.search_db(args.search)
sys.exit()
if args.contract_hash_to_address:
# search corresponding address
mythril.contract_hash_to_address(args.contract_hash_to_address)
sys.exit() sys.exit()
if args.truffle: if args.truffle:
@ -153,9 +164,9 @@ def main():
address, _ = mythril.load_from_address(args.address) address, _ = mythril.load_from_address(args.address)
elif args.solidity_file: elif args.solidity_file:
# Compile Solidity source file(s) # Compile Solidity source file(s)
#if args.graph and len(args.solidity_file) > 1: if args.graph and len(args.solidity_file) > 1:
# exit_with_error(args.outform, exit_with_error(args.outform,
# "Cannot generate call graphs from multiple input files. Please do it one at a time.") "Cannot generate call graphs from multiple input files. Please do it one at a time.")
address, _ = mythril.load_from_solidity(args.solidity_file) # list of files address, _ = mythril.load_from_solidity(args.solidity_file) # list of files
else: else:
exit_with_error(args.outform, exit_with_error(args.outform,
@ -181,7 +192,7 @@ def main():
exit_with_error(args.outform, "input files do not contain any valid contracts") exit_with_error(args.outform, "input files do not contain any valid contracts")
if args.graph: if args.graph:
html = mythril.graph_html(mythril.contracts[0], address=address, html = mythril.graph_html(strategy=args.strategy, contract=mythril.contracts[0], address=address,
enable_physics=args.enable_physics, phrackify=args.phrack, enable_physics=args.enable_physics, phrackify=args.phrack,
max_depth=args.max_depth) max_depth=args.max_depth)
@ -192,10 +203,10 @@ def main():
exit_with_error(args.outform, "Error saving graph: " + str(e)) exit_with_error(args.outform, "Error saving graph: " + str(e))
else: else:
report = mythril.fire_lasers(address=address, report = mythril.fire_lasers(strategy=args.strategy, address=address,
modules=[m.strip() for m in args.modules.strip().split(",")] if args.modules else [], modules=[m.strip() for m in args.modules.strip().split(",")] if args.modules else [],
verbose_report=args.verbose_report, verbose_report=args.verbose_report,
max_depth=args.max_depth) max_depth=args.max_depth, execution_timeout=args.execution_timeout)
outputs = { outputs = {
'json': report.as_json(), 'json': report.as_json(),
'text': report.as_text(), 'text': report.as_text(),
@ -208,7 +219,7 @@ def main():
if not mythril.contracts: if not mythril.contracts:
exit_with_error(args.outform, "input files do not contain any valid contracts") exit_with_error(args.outform, "input files do not contain any valid contracts")
statespace = mythril.dump_statespace(mythril.contracts[0], address=address, max_depth=args.max_depth) statespace = mythril.dump_statespace(strategy=args.strategy, contract=mythril.contracts[0], address=address, max_depth=args.max_depth)
try: try:
with open(args.statespace_json, "w") as f: with open(args.statespace_json, "w") as f:
@ -222,5 +233,6 @@ def main():
except CriticalError as ce: except CriticalError as ce:
exit_with_error(args.outform, str(ce)) exit_with_error(args.outform, str(ce))
if __name__ == "__main__": if __name__ == "__main__":
main() main()

@ -515,7 +515,7 @@ class Instruction:
return [global_state] return [global_state]
@instruction @instruction
def codecopy(self, global_state): def codecopy_(self, global_state):
# FIXME: not implemented # FIXME: not implemented
state = global_state.mstate state = global_state.mstate
start, s1, size = state.stack.pop(), state.stack.pop(), state.stack.pop() start, s1, size = state.stack.pop(), state.stack.pop(), state.stack.pop()
@ -623,8 +623,10 @@ class Instruction:
@instruction @instruction
def mstore_(self, global_state): def mstore_(self, global_state):
state = global_state.mstate state = global_state.mstate
try:
op0, value = state.stack.pop(), state.stack.pop() op0, value = state.stack.pop(), state.stack.pop()
except IndexError:
raise StackUnderflowException()
try: try:
mstart = util.get_concrete_int(op0) mstart = util.get_concrete_int(op0)
@ -705,13 +707,8 @@ class Instruction:
index = str(index) index = str(index)
try: try:
# Create a fresh copy of the account object before modifying storage global_state.environment.active_account = deepcopy(global_state.environment.active_account)
global_state.accounts[global_state.environment.active_account.address] = global_state.environment.active_account
for k in global_state.accounts:
if global_state.accounts[k] == global_state.environment.active_account:
global_state.accounts[k] = deepcopy(global_state.accounts[k])
global_state.environment.active_account = global_state.accounts[k]
break
global_state.environment.active_account.storage[index] = value global_state.environment.active_account.storage[index] = value
except KeyError: except KeyError:
@ -776,7 +773,7 @@ class Instruction:
new_state = copy(global_state) new_state = copy(global_state)
new_state.mstate.pc = index new_state.mstate.pc = index
new_state.mstate.depth += 1 new_state.mstate.depth += 1
new_state.mstate.constraints.append(condi) new_state.mstate.constraints.append(simplify(condi))
states.append(new_state) states.append(new_state)
else: else:
@ -784,12 +781,11 @@ class Instruction:
# False case # False case
negated = Not(condition) if type(condition) == BoolRef else condition == 0 negated = Not(condition) if type(condition) == BoolRef else condition == 0
sat = not is_false(simplify(negated)) if type(condi) == BoolRef else not negated
if sat: if (type(negated) == bool and negated) or (type(negated) == BoolRef and not is_false(simplify(negated))):
new_state = copy(global_state) new_state = copy(global_state)
new_state.mstate.depth += 1 new_state.mstate.depth += 1
new_state.mstate.constraints.append(negated) new_state.mstate.constraints.append(simplify(negated))
states.append(new_state) states.append(new_state)
else: else:
logging.debug("Pruned unreachable states.") logging.debug("Pruned unreachable states.")
@ -840,17 +836,16 @@ class Instruction:
except AttributeError: except AttributeError:
logging.debug("Return with symbolic length or offset. Not supported") logging.debug("Return with symbolic length or offset. Not supported")
#TODO: return 1
return_value = BitVec("retval_" + global_state.environment.active_function_name, 256) return_value = BitVec("retval_" + global_state.environment.active_function_name, 256)
state.stack.append(return_value)
if not global_state.call_stack: if not global_state.call_stack:
return [] return []
global_state.mstate.pc = global_state.call_stack.pop() new_global_state = deepcopy(global_state.call_stack.pop())
new_global_state.node = global_state.node
# TODO: copy memory
return [global_state] return [new_global_state]
@instruction @instruction
def suicide_(self, global_state): def suicide_(self, global_state):
@ -870,12 +865,11 @@ class Instruction:
@instruction @instruction
def stop_(self, global_state): def stop_(self, global_state):
state = global_state.mstate
state.stack.append(BitVecVal(0, 256))
if len(global_state.call_stack) is 0: if len(global_state.call_stack) is 0:
return [] return []
global_state.mstate.pc = global_state.call_stack.pop() new_global_state = deepcopy(global_state.call_stack.pop())
return [global_state] new_global_state.node = global_state.node
return [new_global_state]
@instruction @instruction
def call_(self, global_state): def call_(self, global_state):
@ -891,12 +885,12 @@ class Instruction:
# TODO: decide what to do in this case # TODO: decide what to do in this case
global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256)) global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256))
return [global_state] return [global_state]
global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256))
if 0 < int(callee_address, 16) < 5: if 0 < int(callee_address, 16) < 5:
logging.info("Native contract called: " + callee_address) logging.info("Native contract called: " + callee_address)
if call_data == [] and call_data_type == CalldataType.SYMBOLIC: if call_data == [] and call_data_type == CalldataType.SYMBOLIC:
logging.debug("CALL with symbolic data not supported") logging.debug("CALL with symbolic data not supported")
global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256))
return [global_state] return [global_state]
try: try:
@ -904,7 +898,6 @@ class Instruction:
mem_out_sz = memory_out_size.as_long() mem_out_sz = memory_out_size.as_long()
except AttributeError: except AttributeError:
logging.debug("CALL with symbolic start or offset not supported") logging.debug("CALL with symbolic start or offset not supported")
global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256))
return [global_state] return [global_state]
global_state.mstate.mem_extend(mem_out_start, mem_out_sz) global_state.mstate.mem_extend(mem_out_start, mem_out_sz)
@ -917,18 +910,14 @@ class Instruction:
global_state.mstate.memory[mem_out_start+i] = BitVec(contract_list[call_address_int - 1]+ global_state.mstate.memory[mem_out_start+i] = BitVec(contract_list[call_address_int - 1]+
"(" + str(call_data) + ")", 256) "(" + str(call_data) + ")", 256)
global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256))
return [global_state] return [global_state]
for i in range(min(len(data), mem_out_sz)): # If more data is used then it's chopped off for i in range(min(len(data), mem_out_sz)): # If more data is used then it's chopped off
global_state.mstate.memory[mem_out_start + i] = data[i] global_state.mstate.memory[mem_out_start + i] = data[i]
# TODO: maybe use BitVec here constrained to 1 # TODO: maybe use BitVec here constrained to 1
global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256))
return [global_state] return [global_state]
global_state.call_stack.append(instr['address'])
callee_environment = Environment(callee_account, callee_environment = Environment(callee_account,
BitVecVal(int(environment.active_account.address, 16), 256), BitVecVal(int(environment.active_account.address, 16), 256),
call_data, call_data,
@ -937,9 +926,11 @@ class Instruction:
environment.origin, environment.origin,
calldata_type=call_data_type) calldata_type=call_data_type)
new_global_state = GlobalState(global_state.accounts, callee_environment, global_state.node, MachineState(gas)) new_global_state = GlobalState(global_state.accounts, callee_environment, global_state.node, MachineState(gas))
new_global_state.call_stack.append(global_state)
new_global_state.mstate.pc = -1
new_global_state.mstate.depth = global_state.mstate.depth + 1 new_global_state.mstate.depth = global_state.mstate.depth + 1
new_global_state.mstate.constraints = copy(global_state.mstate.constraints) new_global_state.mstate.constraints = copy(global_state.mstate.constraints)
return [global_state] return [new_global_state]
@instruction @instruction
def callcode_(self, global_state): def callcode_(self, global_state):
@ -955,7 +946,7 @@ class Instruction:
global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256)) global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256))
return [global_state] return [global_state]
global_state.call_stack.append(instr['address']) global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256))
environment = deepcopy(environment) environment = deepcopy(environment)
@ -964,6 +955,8 @@ class Instruction:
environment.calldata = call_data environment.calldata = call_data
new_global_state = GlobalState(global_state.accounts, environment, global_state.node, MachineState(gas)) new_global_state = GlobalState(global_state.accounts, environment, global_state.node, MachineState(gas))
new_global_state.call_stack.append(global_state)
new_global_state.mstate.pc = -1
new_global_state.mstate.depth = global_state.mstate.depth + 1 new_global_state.mstate.depth = global_state.mstate.depth + 1
new_global_state.mstate.constraints = copy(global_state.mstate.constraints) new_global_state.mstate.constraints = copy(global_state.mstate.constraints)
@ -983,15 +976,16 @@ class Instruction:
global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256)) global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256))
return [global_state] return [global_state]
global_state.call_stack.append(instr['address']) global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256))
environment = deepcopy(environment)
environment = deepcopy(environment) environment = deepcopy(environment)
environment.code = callee_account.code environment.code = callee_account.code
environment.calldata = call_data environment.calldata = call_data
new_global_state = GlobalState(global_state.accounts, environment, global_state.node, MachineState(gas)) new_global_state = GlobalState(global_state.accounts, environment, global_state.node, MachineState(gas))
new_global_state.call_stack.append(global_state)
new_global_state.mstate.pc = -1
new_global_state.mstate.depth = global_state.mstate.depth + 1 new_global_state.mstate.depth = global_state.mstate.depth + 1
new_global_state.mstate.constraints = copy(global_state.mstate.constraints) new_global_state.mstate.constraints = copy(global_state.mstate.constraints)

@ -38,8 +38,14 @@ class Account:
def add_balance(self, balance): def add_balance(self, balance):
self.balance += balance self.balance += balance
def get_storage(self, index): # def get_storage(self, index):
return self.storage[index] if index in self.storage.keys() else BitVec("storage_" + str(index), 256) # return BitVec("storage_" + str(index), 256)
# if index in self.storage.keys():
# return self.storage[index]
# else:
# symbol = BitVec("storage_" + str(index), 256)
# self.storage[index] = symbol
# return symbol
@property @property
def as_dict(self): def as_dict(self):
@ -81,6 +87,7 @@ class Environment:
def __str__(self): def __str__(self):
return str(self.as_dict) return str(self.as_dict)
@property @property
def as_dict(self): def as_dict(self):
return dict(active_account=self.active_account, sender=self.sender, calldata=self.calldata, return dict(active_account=self.active_account, sender=self.sender, calldata=self.calldata,

@ -5,6 +5,7 @@ from mythril.laser.ethereum.transaction import MessageCall
from mythril.laser.ethereum.instructions import Instruction from mythril.laser.ethereum.instructions import Instruction
from mythril.laser.ethereum.cfg import NodeFlags, Node, Edge, JumpType from mythril.laser.ethereum.cfg import NodeFlags, Node, Edge, JumpType
from mythril.laser.ethereum.strategy.basic import DepthFirstSearchStrategy from mythril.laser.ethereum.strategy.basic import DepthFirstSearchStrategy
from datetime import datetime, timedelta
from functools import reduce from functools import reduce
TT256 = 2 ** 256 TT256 = 2 ** 256
@ -24,11 +25,15 @@ class LaserEVM:
""" """
Laser EVM class Laser EVM class
""" """
def __init__(self, accounts, dynamic_loader=None, max_depth=22):
def __init__(self, accounts, dynamic_loader=None, max_depth=float('inf'), execution_timeout=60,
strategy=DepthFirstSearchStrategy):
self.instructions_covered = [] self.instructions_covered = []
world_state = WorldState() world_state = WorldState()
world_state.accounts = accounts world_state.accounts = accounts
# this sets the initial world state
self.world_state = world_state
self.open_states = [world_state] self.open_states = [world_state]
self.nodes = {} self.nodes = {}
@ -38,8 +43,10 @@ class LaserEVM:
self.dynamic_loader = dynamic_loader self.dynamic_loader = dynamic_loader
self.work_list = [] self.work_list = []
self.strategy = DepthFirstSearchStrategy(self.work_list, max_depth) self.strategy = strategy(self.work_list, max_depth)
self.max_depth = max_depth self.max_depth = max_depth
self.execution_timeout = execution_timeout
self.time = None
self.pre_hooks = {} self.pre_hooks = {}
self.post_hooks = {} self.post_hooks = {}
@ -48,6 +55,7 @@ class LaserEVM:
def sym_exec(self, main_address): def sym_exec(self, main_address):
logging.debug("Starting LASER execution") logging.debug("Starting LASER execution")
self.time = datetime.now()
transaction = MessageCall(main_address) transaction = MessageCall(main_address)
transaction.run(self.open_states, self) transaction.run(self.open_states, self)
@ -56,10 +64,13 @@ class LaserEVM:
def exec(self): def exec(self):
for global_state in self.strategy: for global_state in self.strategy:
if self.execution_timeout:
if self.time + timedelta(seconds=self.execution_timeout) <= datetime.now():
return
try: try:
new_states, op_code = self.execute_state(global_state) new_states, op_code = self.execute_state(global_state)
except NotImplementedError: except NotImplementedError:
logging.debug("Encountered unimplemented instruction") logging.info("Encountered unimplemented instruction: {}".format(op_code))
continue continue
if len(new_states) == 0: if len(new_states) == 0:
@ -77,7 +88,10 @@ class LaserEVM:
def execute_state(self, global_state): def execute_state(self, global_state):
instructions = global_state.environment.code.instruction_list instructions = global_state.environment.code.instruction_list
op_code = instructions[global_state.mstate.pc]['opcode'] op_code = instructions[global_state.mstate.pc]['opcode']
self.instructions_covered[global_state.mstate.pc] = True
# Only count coverage for the main contract
if len(global_state.call_stack) == 0:
self.instructions_covered[global_state.mstate.pc] = True
self._execute_pre_hook(op_code, global_state) self._execute_pre_hook(op_code, global_state)
new_global_states = Instruction(op_code, self.dynamic_loader).evaluate(global_state) new_global_states = Instruction(op_code, self.dynamic_loader).evaluate(global_state)
@ -115,9 +129,12 @@ class LaserEVM:
if edge_type == JumpType.RETURN: if edge_type == JumpType.RETURN:
new_node.flags |= NodeFlags.CALL_RETURN new_node.flags |= NodeFlags.CALL_RETURN
elif edge_type == JumpType.CALL: elif edge_type == JumpType.CALL:
if 'retval' in str(state.mstate.stack[-1]): try:
new_node.flags |= NodeFlags.CALL_RETURN if 'retval' in str(state.mstate.stack[-1]):
else: new_node.flags |= NodeFlags.CALL_RETURN
else:
new_node.flags |= NodeFlags.FUNC_ENTRY
except IndexError:
new_node.flags |= NodeFlags.FUNC_ENTRY new_node.flags |= NodeFlags.FUNC_ENTRY
address = state.environment.code.instruction_list[state.mstate.pc - 1]['address'] address = state.environment.code.instruction_list[state.mstate.pc - 1]['address']
@ -129,7 +146,8 @@ class LaserEVM:
environment.active_function_name = disassembly.addr_to_func[address] environment.active_function_name = disassembly.addr_to_func[address]
new_node.flags |= NodeFlags.FUNC_ENTRY new_node.flags |= NodeFlags.FUNC_ENTRY
logging.info("- Entering function " + environment.active_account.contract_name + ":" + new_node.function_name) logging.info(
"- Entering function " + environment.active_account.contract_name + ":" + new_node.function_name)
elif address == 0: elif address == 0:
environment.active_function_name = "fallback" environment.active_function_name = "fallback"
@ -160,6 +178,7 @@ class LaserEVM:
self.pre_hooks[op_code] = [] self.pre_hooks[op_code] = []
self.pre_hooks[op_code].append(function) self.pre_hooks[op_code].append(function)
return function return function
return hook_decorator return hook_decorator
def post_hook(self, op_code): def post_hook(self, op_code):
@ -168,4 +187,5 @@ class LaserEVM:
self.post_hooks[op_code] = [] self.post_hooks[op_code] = []
self.post_hooks[op_code].append(function) self.post_hooks[op_code].append(function)
return function return function
return hook_decorator return hook_decorator

@ -101,16 +101,29 @@ class TaintRunner:
# List of (Node, TaintRecord, index) # List of (Node, TaintRecord, index)
current_nodes = [(node, init_record, state_index)] current_nodes = [(node, init_record, state_index)]
environment = node.states[0].environment
for node, record, index in current_nodes: for node, record, index in current_nodes:
records = TaintRunner.execute_node(node, record, index) records = TaintRunner.execute_node(node, record, index)
result.add_records(records) result.add_records(records)
children = [statespace.nodes[edge.node_to] for edge in statespace.edges if edge.node_from == node.uid] children = TaintRunner.children(node, statespace, environment)
for child in children: for child in children:
current_nodes.append((child, records[-1], 0)) current_nodes.append((child, records[-1], 0))
return result return result
@staticmethod
def children(node, statespace, environment):
direct_children = [statespace.nodes[edge.node_to] for edge in statespace.edges if edge.node_from == node.uid]
children = []
for child in direct_children:
if child.states[0].environment.active_account.address == environment.active_account.address:
children.append(child)
else:
children += TaintRunner.children(child, statespace, environment)
return children
@staticmethod @staticmethod
def execute_node(node, last_record, state_index=0): def execute_node(node, last_record, state_index=0):
""" """

@ -43,7 +43,6 @@ class MessageCall:
self.origin, self.origin,
calldata_type=CalldataType.SYMBOLIC, calldata_type=CalldataType.SYMBOLIC,
) )
new_node = Node(environment.active_account.contract_name) new_node = Node(environment.active_account.contract_name)
evm.instructions_covered = [False for _ in environment.code.instruction_list] evm.instructions_covered = [False for _ in environment.code.instruction_list]
@ -51,7 +50,8 @@ class MessageCall:
if open_world_state.node: if open_world_state.node:
evm.edges.append(Edge(open_world_state.node.uid, new_node.uid, edge_type=JumpType.Transaction, condition=None)) evm.edges.append(Edge(open_world_state.node.uid, new_node.uid, edge_type=JumpType.Transaction, condition=None))
global_state = GlobalState(open_world_state, environment, new_node) global_state = GlobalState(open_world_state.accounts, environment, new_node)
global_state.environment.active_function_name = 'fallback()'
new_node.states.append(global_state) new_node.states.append(global_state)
evm.work_list.append(global_state) evm.work_list.append(global_state)

@ -0,0 +1,152 @@
import logging
from mythril import ether
import time
from ethereum.messages import Log
import rlp
from rlp.sedes import big_endian_int, binary
from ethereum import utils
from ethereum.utils import hash32, address, int256
BATCH_SIZE = 8 * 4096
class CountableList(object):
"""A sedes for lists of arbitrary length.
:param element_sedes: when (de-)serializing a list, this sedes will be
applied to all of its elements
"""
def __init__(self, element_sedes):
self.element_sedes = element_sedes
def serialize(self, obj):
return [self.element_sedes.serialize(e) for e in obj]
def deserialize(self, serial):
# needed for 2 reasons:
# 1. empty lists are not zero elements
# 2. underlying logs are stored as list - if empty will also except and receipts will be lost
try:
return [self.element_sedes.deserialize(e) for e in serial]
except:
return []
class ReceiptForStorage(rlp.Serializable):
'''
Receipt format stored in levelDB
'''
fields = [
('state_root', binary),
('cumulative_gas_used', big_endian_int),
('bloom', int256),
('tx_hash', hash32),
('contractAddress', address),
('logs', CountableList(Log)),
('gas_used', big_endian_int)
]
class AccountIndexer(object):
'''
Updates address index
'''
def __init__(self, ethDB):
self.db = ethDB
self.lastBlock = None
self.lastProcessedBlock = None
def get_contract_by_hash(self, contract_hash):
'''
get mapped address by its hash, if not found try indexing
'''
address = self.db.reader._get_address_by_hash(contract_hash)
if address is not None:
return address
self.updateIfNeeded()
return self.db.reader._get_address_by_hash(contract_hash)
def _process(self, startblock):
'''
Processesing method
'''
logging.debug("Processing blocks %d to %d" % (startblock, startblock + BATCH_SIZE))
addresses = []
for blockNum in range(startblock, startblock + BATCH_SIZE):
hash = self.db.reader._get_block_hash(blockNum)
if hash is not None:
receipts = self.db.reader._get_block_receipts(hash, blockNum)
for receipt in receipts:
if receipt.contractAddress is not None and not all(b == 0 for b in receipt.contractAddress):
addresses.append(receipt.contractAddress)
else:
if len(addresses) == 0:
raise Exception()
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
# this is required for fastSync handling
if self.lastBlock is not None:
self.lastBlock = max(self.lastBlock, headBlock.number)
else:
self.lastBlock = headBlock.number
lastProcessed = self.db.reader._get_last_indexed_number()
if lastProcessed is not None:
self.lastProcessedBlock = utils.big_endian_to_int(lastProcessed)
# in fast sync head block is at 0 (e.g. in fastSync), we can't use it to determine length
if self.lastBlock is not None and self.lastBlock == 0:
self.lastBlock = 2e+9
if self.lastBlock is None or (self.lastProcessedBlock is not None and self.lastBlock <= self.lastProcessedBlock):
return
blockNum = 0
if self.lastProcessedBlock is not None:
blockNum = self.lastProcessedBlock + 1
print("Updating hash-to-address index from block " + str(self.lastProcessedBlock))
else:
print("Starting hash-to-address index")
count = 0
processed = 0
while (blockNum <= self.lastBlock):
# leveldb cannot be accessed on multiple processes (not even readonly)
# multithread version performs significantly worse than serial
try:
results = self._process(blockNum)
except:
break
# store new mappings
self.db.writer._start_writing()
count += len(results)
for addr in results:
self.db.writer._store_account_address(addr)
self.db.writer._commit_batch()
processed += BATCH_SIZE
blockNum = min(blockNum + BATCH_SIZE, self.lastBlock + 1)
cost_time = time.time() - ether.start_time
print("%d blocks processed (in %d seconds), %d unique addresses found, next block: %d" % (processed, cost_time, count, min(self.lastBlock, blockNum)))
self.lastProcessedBlock = blockNum - 1
self.db.writer._set_last_indexed_number(self.lastProcessedBlock)
print("Finished indexing")
self.lastBlock = self.lastProcessedBlock

@ -1,21 +1,29 @@
import plyvel
import binascii import binascii
import rlp import rlp
import hashlib from mythril.leveldb.accountindexing import CountableList
from mythril.leveldb.accountindexing import ReceiptForStorage, AccountIndexer
import logging
from ethereum import utils from ethereum import utils
from ethereum.block import BlockHeader, Block from ethereum.block import BlockHeader, Block
from mythril.leveldb.state import State, Account from mythril.leveldb.state import State
from mythril.leveldb.eth_db import ETH_DB from mythril.leveldb.eth_db import ETH_DB
from mythril.ether.ethcontract import ETHContract, InstanceList from mythril.ether.ethcontract import ETHContract
# Per https://github.com/ethereum/go-ethereum/blob/master/core/database_util.go # Per https://github.com/ethereum/go-ethereum/blob/master/core/database_util.go
# prefixes and suffixes for keys in geth # prefixes and suffixes for keys in geth
headerPrefix = b'h' # headerPrefix + num (uint64 big endian) + hash -> header headerPrefix = b'h' # headerPrefix + num (uint64 big endian) + hash -> header
bodyPrefix = b'b' # bodyPrefix + num (uint64 big endian) + hash -> block body bodyPrefix = b'b' # bodyPrefix + num (uint64 big endian) + hash -> block body
numSuffix = b'n' # headerPrefix + num (uint64 big endian) + numSuffix -> hash numSuffix = b'n' # headerPrefix + num (uint64 big endian) + numSuffix -> hash
blockHashPrefix = b'H' # blockHashPrefix + hash -> num (uint64 big endian) blockHashPrefix = b'H' # blockHashPrefix + hash -> num (uint64 big endian)
blockReceiptsPrefix = b'r' # blockReceiptsPrefix + num (uint64 big endian) + hash -> block receipts
# known geth keys # known geth keys
headHeaderKey = b'LastBlock' # head (latest) header hash headHeaderKey = b'LastBlock' # head (latest) header hash
# custom prefixes
addressPrefix = b'AM' # addressPrefix + hash -> address
# custom keys
addressMappingHeadKey = b'accountMapping' # head (latest) number of indexed block
headHeaderKey = b'LastBlock' # head (latest) header hash
def _formatBlockNumber(number): def _formatBlockNumber(number):
''' '''
@ -23,84 +31,24 @@ def _formatBlockNumber(number):
''' '''
return utils.zpad(utils.int_to_big_endian(number), 8) return utils.zpad(utils.int_to_big_endian(number), 8)
def _encode_hex(v): def _encode_hex(v):
''' '''
encodes hash as hex encodes hash as hex
''' '''
return '0x' + utils.encode_hex(v) return '0x' + utils.encode_hex(v)
class EthLevelDB(object):
class LevelDBReader(object):
''' '''
Go-Ethereum LevelDB client class level db reading interface, can be used with snapshot
''' '''
def __init__(self, path): def __init__(self, db):
self.path = path self.db = db
self.db = ETH_DB(path)
self.headBlockHeader = None self.headBlockHeader = None
self.headState = None self.headState = None
def get_contracts(self, search_all):
'''
iterate through contracts with non-zero balance by default or all if search_all is set
'''
for account in self._get_head_state().get_all_accounts():
if account.code is not None and (search_all or account.balance != 0):
code = _encode_hex(account.code)
md5 = hashlib.md5()
md5.update(code.encode('UTF-8'))
contract_hash = md5.digest()
contract = ETHContract(code, name=contract_hash.hex())
yield contract, _encode_hex(account.address), account.balance
def search(self, expression, search_all, callback_func):
'''
searches through non-zero balance contracts
'''
for contract, address, balance in self.get_contracts(search_all):
if contract.matches_expression(expression):
callback_func(contract.name, contract, [address], [balance])
def eth_getBlockHeaderByNumber(self, number):
'''
gets block header by block number
'''
hash = self._get_block_hash(number)
blockNumber = _formatBlockNumber(number)
return self._get_block_header(hash, blockNumber)
def eth_getBlockByNumber(self, number):
'''
gets block body by block number
'''
blockHash = self._get_block_hash(number)
blockNumber = _formatBlockNumber(number)
bodyKey = bodyPrefix + blockNumber + blockHash
blockData = self.db.get(bodyKey)
body = rlp.decode(blockData, sedes=Block)
return body
def eth_getCode(self, address):
'''
gets account code
'''
account = self._get_account(address)
return _encode_hex(account.code)
def eth_getBalance(self, address):
'''
gets account balance
'''
account = self._get_account(address)
return account.balance
def eth_getStorageAt(self, address, position):
'''
gets account storage data at position
'''
account = self._get_account(address)
return _encode_hex(utils.zpad(utils.encode_int(account.get_storage_data(position)), 32))
def _get_head_state(self): def _get_head_state(self):
''' '''
gets head state gets head state
@ -157,3 +105,155 @@ class EthLevelDB(object):
blockHeaderData = self.db.get(headerKey) blockHeaderData = self.db.get(headerKey)
header = rlp.decode(blockHeaderData, sedes=BlockHeader) header = rlp.decode(blockHeaderData, sedes=BlockHeader)
return header return header
def _get_address_by_hash(self, hash):
'''
get mapped address by its hash
'''
addressKey = addressPrefix + hash
return self.db.get(addressKey)
def _get_last_indexed_number(self):
'''
latest indexed block number
'''
return self.db.get(addressMappingHeadKey)
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))
return receipts
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(addressMappingHeadKey, _formatBlockNumber(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
'''
addressKey = addressPrefix + utils.sha3(address)
self.wb.put(addressKey, address)
class EthLevelDB(object):
'''
Go-Ethereum LevelDB client class
'''
def __init__(self, path):
self.path = path
self.db = ETH_DB(path)
self.reader = LevelDBReader(self.db)
self.writer = LevelDBWriter(self.db)
def get_contracts(self):
'''
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
def search(self, expression, callback_func):
'''
searches through non-zero balance contracts
'''
cnt = 0
for contract, address, balance in self.get_contracts():
if contract.matches_expression(expression):
callback_func(contract.name, contract, [address], [balance])
cnt += 1
if not cnt % 10:
logging.info("Searched %d contracts" % cnt)
def contract_hash_to_address(self, hash):
'''
tries to find corresponding account address
'''
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"
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)
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)
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))

@ -1,6 +1,6 @@
import plyvel import plyvel
from ethereum.db import BaseDB from ethereum.db import BaseDB
from ethereum import utils
class ETH_DB(BaseDB): class ETH_DB(BaseDB):
''' '''
@ -20,4 +20,10 @@ class ETH_DB(BaseDB):
''' '''
puts value for key puts value for key
''' '''
self.db.put(key, value) self.db.put(key, value)
def write_batch(self):
'''
start writing a batch
'''
return self.db.write_batch()

@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: UTF-8 -*- # -*- coding: utf-8 -*-
"""mythril.py: Bug hunting on the Ethereum blockchain """mythril.py: Bug hunting on the Ethereum blockchain
http://www.github.com/b-mueller/mythril http://www.github.com/b-mueller/mythril
@ -19,7 +19,7 @@ import platform
from mythril.ether import util from mythril.ether import util
from mythril.ether.ethcontract import ETHContract from mythril.ether.ethcontract import ETHContract
from mythril.ether.soliditycontract import SolidityContract from mythril.ether.soliditycontract import SolidityContract, get_contracts_from_file
from mythril.rpc.client import EthJsonRpc from mythril.rpc.client import EthJsonRpc
from mythril.ipc.client import EthIpc from mythril.ipc.client import EthIpc
from mythril.rpc.exceptions import ConnectionError from mythril.rpc.exceptions import ConnectionError
@ -34,7 +34,6 @@ from mythril.analysis.security import fire_lasers
from mythril.analysis.report import Report from mythril.analysis.report import Report
from mythril.leveldb.client import EthLevelDB from mythril.leveldb.client import EthLevelDB
# logging.basicConfig(level=logging.DEBUG) # logging.basicConfig(level=logging.DEBUG)
class Mythril(object): class Mythril(object):
@ -98,6 +97,7 @@ class Mythril(object):
raise CriticalError("Invalid JSON in signatures file " + self.sigs.signatures_file + "\n" + str(jde)) raise CriticalError("Invalid JSON in signatures file " + self.sigs.signatures_file + "\n" + str(jde))
self.solc_binary = self._init_solc_binary(solv) self.solc_binary = self._init_solc_binary(solv)
self.config_path = os.path.join(self.mythril_dir, 'config.ini')
self.leveldb_dir = self._init_config() self.leveldb_dir = self._init_config()
self.eth = None # ethereum API client self.eth = None # ethereum API client
@ -119,40 +119,66 @@ class Mythril(object):
return mythril_dir return mythril_dir
def _init_config(self): def _init_config(self):
"""
If no config file exists, create it and add default options.
Default LevelDB path is specified based on OS
dynamic loading is set to infura by default in the file
Returns: leveldb directory
"""
# If no config file exists, create it. Default LevelDB path is specified based on OS
config_path = os.path.join(self.mythril_dir, 'config.ini')
system = platform.system().lower() system = platform.system().lower()
fallback_dir = os.path.expanduser('~') leveldb_fallback_dir = os.path.expanduser('~')
if system.startswith("darwin"): if system.startswith("darwin"):
fallback_dir = os.path.join(fallback_dir, "Library", "Ethereum") leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, "Library", "Ethereum")
elif system.startswith("windows"): elif system.startswith("windows"):
fallback_dir = os.path.join(fallback_dir, "AppData", "Roaming", "Ethereum") leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, "AppData", "Roaming", "Ethereum")
else: else:
fallback_dir = os.path.join(fallback_dir, ".ethereum") leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, ".ethereum")
fallback_dir = os.path.join(fallback_dir, "geth", "chaindata") leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, "geth", "chaindata")
if not os.path.exists(config_path): if not os.path.exists(self.config_path):
logging.info("No config file found. Creating default: " + config_path) logging.info("No config file found. Creating default: " + self.config_path)
open(self.config_path, 'a').close()
config = ConfigParser(allow_no_value=True)
config.optionxform = str
config.add_section('defaults')
config.set('defaults', "#Default chaindata locations:")
config.set('defaults', "#– Mac: ~/Library/Ethereum/geth/chaindata")
config.set('defaults', "#– Linux: ~/.ethereum/geth/chaindata")
config.set('defaults', "#– Windows: %USERPROFILE%\\AppData\\Roaming\\Ethereum\\geth\\chaindata")
config.set('defaults', 'leveldb_dir', fallback_dir)
with codecs.open(config_path, 'w', 'utf-8') as fp:
config.write(fp)
config = ConfigParser(allow_no_value=True) config = ConfigParser(allow_no_value=True)
config.optionxform = str config.optionxform = str
config.read(config_path, 'utf-8') config.read(self.config_path, 'utf-8')
leveldb_dir = config.get('defaults', 'leveldb_dir', fallback=fallback_dir) if 'defaults' not in config.sections():
self._add_default_options(config)
if not config.has_option('defaults', 'leveldb_dir'):
self._add_leveldb_option(config, leveldb_fallback_dir)
if not config.has_option('defaults', 'dynamic_loading'):
self._add_dynamic_loading_option(config)
with codecs.open(self.config_path, 'w', 'utf-8') as fp:
config.write(fp)
leveldb_dir = config.get('defaults', 'leveldb_dir', fallback=leveldb_fallback_dir)
return os.path.expanduser(leveldb_dir) return os.path.expanduser(leveldb_dir)
@staticmethod
def _add_default_options(config):
config.add_section('defaults')
@staticmethod
def _add_leveldb_option(config, leveldb_fallback_dir):
config.set('defaults', "#Default chaindata locations:")
config.set('defaults', "#– Mac: ~/Library/Ethereum/geth/chaindata")
config.set('defaults', "#– Linux: ~/.ethereum/geth/chaindata")
config.set('defaults', "#– Windows: %USERPROFILE%\\AppData\\Roaming\\Ethereum\\geth\\chaindata")
config.set('defaults', 'leveldb_dir', leveldb_fallback_dir)
@staticmethod
def _add_dynamic_loading_option(config):
config.set('defaults', '#– To connect to Infura use dynamic_loading: infura')
config.set('defaults', '#– To connect to Ipc use dynamic_loading: ipc')
config.set('defaults', '#– To connect to Rpc use '
'dynamic_loading: HOST:PORT / ganache / infura-[network_name]')
config.set('defaults', '#– To connect to local host use dynamic_loading: localhost')
config.set('defaults', 'dynamic_loading', 'infura')
def analyze_truffle_project(self, *args, **kwargs): def analyze_truffle_project(self, *args, **kwargs):
return analyze_truffle_project(*args, **kwargs) # just passthru for now return analyze_truffle_project(*args, **kwargs) # just passthru for now
@ -227,19 +253,41 @@ class Mythril(object):
self.eth = EthJsonRpc('localhost', 8545) self.eth = EthJsonRpc('localhost', 8545)
logging.info("Using default RPC settings: http://localhost:8545") logging.info("Using default RPC settings: http://localhost:8545")
def search_db(self, search, search_all): def set_api_from_config_path(self):
config = ConfigParser(allow_no_value=False)
config.optionxform = str
config.read(self.config_path, 'utf-8')
if config.has_option('defaults', 'dynamic_loading'):
dynamic_loading = config.get('defaults', 'dynamic_loading')
else:
dynamic_loading = 'infura'
if dynamic_loading == 'ipc':
self.set_api_ipc()
elif dynamic_loading == 'infura':
self.set_api_rpc_infura()
elif dynamic_loading == 'localhost':
self.set_api_rpc_localhost()
else:
self.set_api_rpc(dynamic_loading)
def search_db(self, search):
def search_callback(code_hash, code, addresses, balances): def search_callback(code_hash, code, addresses, balances):
print("Matched contract with code hash " + code_hash)
for i in range(0, len(addresses)): for i in range(0, len(addresses)):
print("Address: " + addresses[i] + ", balance: " + str(balances[i])) print("Address: " + addresses[i] + ", balance: " + str(balances[i]))
try: try:
self.ethDb.search(search, search_all, search_callback) self.ethDb.search(search, search_callback)
except SyntaxError: except SyntaxError:
raise CriticalError("Syntax error in search expression.") raise CriticalError("Syntax error in search expression.")
def contract_hash_to_address(self, hash):
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))
def load_from_bytecode(self, code): def load_from_bytecode(self, code):
address = util.get_indexed_address(0) address = util.get_indexed_address(0)
self.contracts.append(ETHContract(code, name="MAIN")) self.contracts.append(ETHContract(code, name="MAIN"))
@ -284,49 +332,52 @@ class Mythril(object):
# import signatures from solidity source # import signatures from solidity source
with open(file, encoding="utf-8") as f: with open(file, encoding="utf-8") as f:
self.sigs.import_from_solidity_source(f.read()) self.sigs.import_from_solidity_source(f.read())
# Save updated function signatures
self.sigs.write() # dump signatures to disk (previously opened file or default location)
if contract_name is not None:
contract = SolidityContract(file, contract_name, solc_args=self.solc_args)
self.contracts.append(contract)
contracts.append(contract)
else:
for contract in get_contracts_from_file(file, solc_args=self.solc_args):
self.contracts.append(contract)
contracts.append(contract)
contract = SolidityContract(file, contract_name, solc_args=self.solc_args)
logging.info("Analyzing contract %s:%s" % (file, contract.name))
except FileNotFoundError: except FileNotFoundError:
raise CriticalError("Input file not found: " + file) raise CriticalError("Input file not found: " + file)
except CompilerError as e: except CompilerError as e:
raise CriticalError(e) raise CriticalError(e)
except NoContractFoundError: except NoContractFoundError:
logging.info("The file " + file + " does not contain a compilable contract.") logging.info("The file " + file + " does not contain a compilable contract.")
else:
self.contracts.append(contract)
contracts.append(contract)
# Save updated function signatures
self.sigs.write() # dump signatures to disk (previously opened file or default location)
return address, contracts return address, contracts
def dump_statespace(self, contract, address=None, max_depth=12): def dump_statespace(self, strategy, contract, address=None, max_depth=12):
sym = SymExecWrapper(contract, address, sym = SymExecWrapper(contract, address, strategy,
dynloader=DynLoader(self.eth) if self.dynld else None, dynloader=DynLoader(self.eth) if self.dynld else None,
max_depth=max_depth) max_depth=max_depth)
return get_serializable_statespace(sym) return get_serializable_statespace(sym)
def graph_html(self, contract, address, max_depth=12, enable_physics=False, phrackify=False): def graph_html(self, strategy, contract, address, max_depth=12, enable_physics=False, phrackify=False):
sym = SymExecWrapper(contract, address, sym = SymExecWrapper(contract, address, strategy,
dynloader=DynLoader(self.eth) if self.dynld else None, dynloader=DynLoader(self.eth) if self.dynld else None,
max_depth=max_depth) max_depth=max_depth)
return generate_graph(sym, physics=enable_physics, phrackify=phrackify) return generate_graph(sym, physics=enable_physics, phrackify=phrackify)
def fire_lasers(self, contracts=None, address=None, def fire_lasers(self, strategy, contracts=None, address=None,
modules=None, modules=None,
verbose_report=False, max_depth=12): verbose_report=False, max_depth=None, execution_timeout=None, ):
all_issues = [] all_issues = []
if self.dynld and self.eth is None:
self.set_api_rpc_infura()
for contract in (contracts or self.contracts): for contract in (contracts or self.contracts):
sym = SymExecWrapper(contract, address, sym = SymExecWrapper(contract, address, strategy,
dynloader=DynLoader(self.eth) if self.dynld else None, dynloader=DynLoader(self.eth) if self.dynld else None,
max_depth=max_depth) max_depth=max_depth, execution_timeout=execution_timeout)
issues = fire_lasers(sym, modules) issues = fire_lasers(sym, modules)

@ -154,7 +154,6 @@ class SignatureDb(object):
""" """
if not sighash.startswith("0x"): if not sighash.startswith("0x"):
sighash = "0x%s" % sighash # normalize sighash format sighash = "0x%s" % sighash # normalize sighash format
if self.enable_online_lookup and not self.signatures.get(sighash) and sighash not in self.online_lookup_miss and time.time() > self.online_directory_unavailable_until: if self.enable_online_lookup and not self.signatures.get(sighash) and sighash not in self.online_lookup_miss and time.time() > self.online_directory_unavailable_until:
# online lookup enabled, and signature not in cache, sighash was not a miss earlier, and online directory not down # online lookup enabled, and signature not in cache, sighash was not a miss earlier, and online directory not down
logging.debug("Signatures: performing online lookup for sighash %r" % sighash) logging.debug("Signatures: performing online lookup for sighash %r" % sighash)
@ -169,8 +168,11 @@ class SignatureDb(object):
except FourByteDirectoryOnlineLookupError as fbdole: except FourByteDirectoryOnlineLookupError as fbdole:
self.online_directory_unavailable_until = time.time() + 2 * 60 # wait at least 2 mins to try again self.online_directory_unavailable_until = time.time() + 2 * 60 # wait at least 2 mins to try again
logging.warning("online function signature lookup not available. will not try to lookup hash for the next 2 minutes. exception: %r" % fbdole) logging.warning("online function signature lookup not available. will not try to lookup hash for the next 2 minutes. exception: %r" % fbdole)
if type(self.signatures[sighash]) != list:
return [self.signatures[sighash]]
return self.signatures[sighash] # raise keyerror return self.signatures[sighash] # raise keyerror
def __getitem__(self, item): def __getitem__(self, item):
""" """
Provide dict interface Signatures()[sighash] Provide dict interface Signatures()[sighash]

@ -1,4 +1,5 @@
import os import os
from pathlib import PurePath
import re import re
import sys import sys
import json import json
@ -31,6 +32,7 @@ def analyze_truffle_project(args):
try: try:
name = contractdata['contractName'] name = contractdata['contractName']
bytecode = contractdata['deployedBytecode'] bytecode = contractdata['deployedBytecode']
filename = PurePath(contractdata['sourcePath']).name
except: except:
print("Unable to parse contract data. Please use Truffle 4 to compile your project.") print("Unable to parse contract data. Please use Truffle 4 to compile your project.")
sys.exit() sys.exit()
@ -41,7 +43,7 @@ def analyze_truffle_project(args):
ethcontract = ETHContract(bytecode, name=name) ethcontract = ETHContract(bytecode, name=name)
address = util.get_indexed_address(0) address = util.get_indexed_address(0)
sym = SymExecWrapper(ethcontract, address, max_depth=args.max_depth) sym = SymExecWrapper(ethcontract, address, args.strategy, max_depth=args.max_depth)
issues = fire_lasers(sym) issues = fire_lasers(sym)
if not len(issues): if not len(issues):
@ -74,7 +76,7 @@ def analyze_truffle_project(args):
if len(mapping) > 2 and len(mapping[2]) > 0: if len(mapping) > 2 and len(mapping[2]) > 0:
idx = int(mapping[2]) idx = int(mapping[2])
lineno = source[0:offset].count('\n') + 1 lineno = source.encode('utf-8')[0:offset].count('\n'.encode('utf-8')) + 1
mappings.append(SourceMapping(idx, offset, length, lineno)) mappings.append(SourceMapping(idx, offset, length, lineno))
@ -88,7 +90,7 @@ def analyze_truffle_project(args):
length = mappings[index].length length = mappings[index].length
issue.filename = filename issue.filename = filename
issue.code = source[offset:offset + length] issue.code = source.encode('utf-8')[offset:offset + length].decode('utf-8')
issue.lineno = mappings[index].lineno issue.lineno = mappings[index].lineno
except IndexError: except IndexError:
logging.debug("No code mapping at index %d", index) logging.debug("No code mapping at index %d", index)

@ -0,0 +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

@ -20,4 +20,5 @@ pytest-cov
pytest_mock pytest_mock
requests requests
rlp>=1.0.1 rlp>=1.0.1
transaction>=2.2.1
z3-solver>=4.5 z3-solver>=4.5

@ -1,13 +1,18 @@
from setuptools import setup, find_packages from setuptools import setup, find_packages
from setuptools.command.install import install from setuptools.command.install import install
from pathlib import Path
import sys import sys
import os import os
# To make lint checkers happy we set VERSION here, but
# it is redefined by the exec below
VERSION = None
# Package version (vX.Y.Z). It must match git tag being used for CircleCI # Package version (vX.Y.Z). It must match git tag being used for CircleCI
# deployment; otherwise the build will failed. # deployment; otherwise the build will failed.
VERSION = "v0.18.7" version_path = (Path(__file__).parent / 'mythril' / 'version.py').absolute()
exec(open(str(version_path), 'r').read())
class VerifyVersionCommand(install): class VerifyVersionCommand(install):
"""Custom command to verify that the git tag matches our version""" """Custom command to verify that the git tag matches our version"""
@ -43,7 +48,7 @@ Or, clone the GitHub repo to install the newest master branch:
$ cd mythril $ cd mythril
$ python setup.py install $ python setup.py install
Note that Mythril requires Python 3.5 to work. Note that Mythril requires Python 3.5 or later to work.
Function signatures Function signatures
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
@ -166,9 +171,6 @@ in the `legendary "Mitch Brenner" blog post
<https://medium.com/@rtaylor30/how-i-snatched-your-153-037-eth-after-a-bad-tinder-date-d1d84422a50b>`__ <https://medium.com/@rtaylor30/how-i-snatched-your-153-037-eth-after-a-bad-tinder-date-d1d84422a50b>`__
in [STRIKEOUT:seconds] minutes instead of days. in [STRIKEOUT:seconds] minutes instead of days.
The default behavior is to search contracts with a non-zero balance.
You can disable this behavior with the ``--search-all`` flag.
You may also use geth database directly for fetching contracts instead of You may also use geth database directly for fetching contracts instead of
using IPC/RPC APIs by specifying ``--leveldb`` flag. This is useful using IPC/RPC APIs by specifying ``--leveldb`` flag. This is useful
because search will return hashed addresses which will not be accepted by because search will return hashed addresses which will not be accepted by
@ -292,12 +294,9 @@ setup(
'License :: OSI Approved :: MIT License', 'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
], ],
keywords='hacking disassembler security ethereum', keywords='hacking disassembler security ethereum',
@ -321,6 +320,7 @@ setup(
'coverage', 'coverage',
'jinja2>=2.9', 'jinja2>=2.9',
'rlp>=1.0.1', 'rlp>=1.0.1',
'transaction>=2.2.1',
'py-flags', 'py-flags',
'mock', 'mock',
'configparser>=3.5.0', 'configparser>=3.5.0',

@ -0,0 +1,299 @@
pragma solidity ^0.4.16;
/**
* @title SafeMath
* @dev Math operations with safety checks that throw on error
*/
library SafeMath {
function mul(uint256 a, uint256 b) internal constant returns (uint256) {
uint256 c = a * b;
assert(a == 0 || c / a == b);
return c;
}
function div(uint256 a, uint256 b) internal constant returns (uint256) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
function sub(uint256 a, uint256 b) internal constant returns (uint256) {
assert(b <= a);
return a - b;
}
function add(uint256 a, uint256 b) internal constant returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}
/**
* @title ERC20Basic
* @dev Simpler version of ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/179
*/
contract ERC20Basic {
uint256 public totalSupply;
function balanceOf(address who) public constant returns (uint256);
function transfer(address to, uint256 value) public returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
}
/**
* @title Basic token
* @dev Basic version of StandardToken, with no allowances.
*/
contract BasicToken is ERC20Basic {
using SafeMath for uint256;
mapping(address => uint256) balances;
/**
* @dev transfer token for a specified address
* @param _to The address to transfer to.
* @param _value The amount to be transferred.
*/
function transfer(address _to, uint256 _value) public returns (bool) {
require(_to != address(0));
require(_value > 0 && _value <= balances[msg.sender]);
// SafeMath.sub will throw if there is not enough balance.
balances[msg.sender] = balances[msg.sender].sub(_value);
balances[_to] = balances[_to].add(_value);
Transfer(msg.sender, _to, _value);
return true;
}
/**
* @dev Gets the balance of the specified address.
* @param _owner The address to query the the balance of.
* @return An uint256 representing the amount owned by the passed address.
*/
function balanceOf(address _owner) public constant returns (uint256 balance) {
return balances[_owner];
}
}
/**
* @title ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/20
*/
contract ERC20 is ERC20Basic {
function allowance(address owner, address spender) public constant returns (uint256);
function transferFrom(address from, address to, uint256 value) public returns (bool);
function approve(address spender, uint256 value) public returns (bool);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
/**
* @title Standard ERC20 token
*
* @dev Implementation of the basic standard token.
* @dev https://github.com/ethereum/EIPs/issues/20
* @dev Based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
*/
contract StandardToken is ERC20, BasicToken {
mapping (address => mapping (address => uint256)) internal allowed;
/**
* @dev Transfer tokens from one address to another
* @param _from address The address which you want to send tokens from
* @param _to address The address which you want to transfer to
* @param _value uint256 the amount of tokens to be transferred
*/
function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
require(_to != address(0));
require(_value > 0 && _value <= balances[_from]);
require(_value <= allowed[_from][msg.sender]);
balances[_from] = balances[_from].sub(_value);
balances[_to] = balances[_to].add(_value);
allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
Transfer(_from, _to, _value);
return true;
}
/**
* @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
*
* Beware that changing an allowance with this method brings the risk that someone may use both the old
* and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this
* race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
* @param _spender The address which will spend the funds.
* @param _value The amount of tokens to be spent.
*/
function approve(address _spender, uint256 _value) public returns (bool) {
allowed[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
return true;
}
/**
* @dev Function to check the amount of tokens that an owner allowed to a spender.
* @param _owner address The address which owns the funds.
* @param _spender address The address which will spend the funds.
* @return A uint256 specifying the amount of tokens still available for the spender.
*/
function allowance(address _owner, address _spender) public constant returns (uint256 remaining) {
return allowed[_owner][_spender];
}
}
/**
* @title Ownable
* @dev The Ownable contract has an owner address, and provides basic authorization control
* functions, this simplifies the implementation of "user permissions".
*/
contract Ownable {
address public owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
function Ownable() {
owner = msg.sender;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) onlyOwner public {
require(newOwner != address(0));
OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
}
/**
* @title Pausable
* @dev Base contract which allows children to implement an emergency stop mechanism.
*/
contract Pausable is Ownable {
event Pause();
event Unpause();
bool public paused = false;
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*/
modifier whenNotPaused() {
require(!paused);
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*/
modifier whenPaused() {
require(paused);
_;
}
/**
* @dev called by the owner to pause, triggers stopped state
*/
function pause() onlyOwner whenNotPaused public {
paused = true;
Pause();
}
/**
* @dev called by the owner to unpause, returns to normal state
*/
function unpause() onlyOwner whenPaused public {
paused = false;
Unpause();
}
}
/**
* @title Pausable token
*
* @dev StandardToken modified with pausable transfers.
**/
contract PausableToken is StandardToken, Pausable {
function transfer(address _to, uint256 _value) public whenNotPaused returns (bool) {
return super.transfer(_to, _value);
}
function transferFrom(address _from, address _to, uint256 _value) public whenNotPaused returns (bool) {
return super.transferFrom(_from, _to, _value);
}
function approve(address _spender, uint256 _value) public whenNotPaused returns (bool) {
return super.approve(_spender, _value);
}
function batchTransfer(address[] _receivers, uint256 _value) public whenNotPaused returns (bool) {
uint cnt = _receivers.length;
uint256 amount = uint256(cnt) * _value;
require(cnt > 0 && cnt <= 20);
require(_value > 0 && balances[msg.sender] >= amount);
balances[msg.sender] = balances[msg.sender].sub(amount);
for (uint i = 0; i < cnt; i++) {
balances[_receivers[i]] = balances[_receivers[i]].add(_value);
Transfer(msg.sender, _receivers[i], _value);
}
return true;
}
}
/**
* @title Bec Token
*
* @dev Implementation of Bec Token based on the basic standard token.
*/
contract BecToken is PausableToken {
/**
* Public variables of the token
* The following variables are OPTIONAL vanities. One does not have to include them.
* They allow one to customise the token contract & in no way influences the core functionality.
* Some wallets/interfaces might not even bother to look at this information.
*/
string public name = "BeautyChain";
string public symbol = "BEC";
string public version = '1.0.0';
uint8 public decimals = 18;
/**
* @dev Function to check the amount of tokens that an owner allowed to a spender.
*/
function BecToken() {
totalSupply = 7000000000 * (10**(uint256(decimals)));
balances[msg.sender] = totalSupply; // Give the creator all initial tokens
}
function () {
//if ether is sent to this address, send it back.
revert();
}
}

@ -0,0 +1,396 @@
//sol Wallet
// Multi-sig, daily-limited account proxy/wallet.
// @authors:
// Gav Wood <g@ethdev.com>
// inheritable "property" contract that enables methods to be protected by requiring the acquiescence of either a
// single, or, crucially, each of a number of, designated owners.
// usage:
// use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by
// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the
// interior is executed.
pragma solidity ^0.4.9;
contract WalletEvents {
// EVENTS
// this contract only has six types of events: it can accept a confirmation, in which case
// we record owner and operation (hash) alongside it.
event Confirmation(address owner, bytes32 operation);
event Revoke(address owner, bytes32 operation);
// some others are in the case of an owner changing.
event OwnerChanged(address oldOwner, address newOwner);
event OwnerAdded(address newOwner);
event OwnerRemoved(address oldOwner);
// the last one is emitted if the required signatures change
event RequirementChanged(uint newRequirement);
// Funds has arrived into the wallet (record how much).
event Deposit(address _from, uint value);
// Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going).
event SingleTransact(address owner, uint value, address to, bytes data, address created);
// Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going).
event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data, address created);
// Confirmation still needed for a transaction.
event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data);
}
contract WalletAbi {
// Revokes a prior confirmation of the given operation
function revoke(bytes32 _operation) external;
// Replaces an owner `_from` with another `_to`.
function changeOwner(address _from, address _to) external;
function addOwner(address _owner) external;
function removeOwner(address _owner) external;
function changeRequirement(uint _newRequired) external;
function isOwner(address _addr) constant returns (bool);
function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool);
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
function setDailyLimit(uint _newLimit) external;
function execute(address _to, uint _value, bytes _data) external returns (bytes32 o_hash);
function confirm(bytes32 _h) returns (bool o_success);
}
contract WalletLibrary is WalletEvents {
// TYPES
// struct for the status of a pending operation.
struct PendingState {
uint yetNeeded;
uint ownersDone;
uint index;
}
// Transaction structure to remember details of transaction lest it need be saved for a later call.
struct Transaction {
address to;
uint value;
bytes data;
}
// MODIFIERS
// simple single-sig function modifier.
modifier onlyowner {
if (isOwner(msg.sender))
_;
}
// multi-sig function modifier: the operation must have an intrinsic hash in order
// that later attempts can be realised as the same underlying operation and
// thus count as confirmations.
modifier onlymanyowners(bytes32 _operation) {
if (confirmAndCheck(_operation))
_;
}
// METHODS
// gets called when no other function matches
function() payable {
// just being sent some cash?
if (msg.value > 0)
Deposit(msg.sender, msg.value);
}
// constructor is given number of sigs required to do protected "onlymanyowners" transactions
// as well as the selection of addresses capable of confirming them.
function initMultiowned(address[] _owners, uint _required) only_uninitialized {
m_numOwners = _owners.length + 1;
m_owners[1] = uint(msg.sender);
m_ownerIndex[uint(msg.sender)] = 1;
for (uint i = 0; i < _owners.length; ++i)
{
m_owners[2 + i] = uint(_owners[i]);
m_ownerIndex[uint(_owners[i])] = 2 + i;
}
m_required = _required;
}
// Revokes a prior confirmation of the given operation
function revoke(bytes32 _operation) external {
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
// make sure they're an owner
if (ownerIndex == 0) return;
uint ownerIndexBit = 2**ownerIndex;
var pending = m_pending[_operation];
if (pending.ownersDone & ownerIndexBit > 0) {
pending.yetNeeded++;
pending.ownersDone -= ownerIndexBit;
Revoke(msg.sender, _operation);
}
}
// Replaces an owner `_from` with another `_to`.
function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external {
if (isOwner(_to)) return;
uint ownerIndex = m_ownerIndex[uint(_from)];
if (ownerIndex == 0) return;
clearPending();
m_owners[ownerIndex] = uint(_to);
m_ownerIndex[uint(_from)] = 0;
m_ownerIndex[uint(_to)] = ownerIndex;
OwnerChanged(_from, _to);
}
function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
if (isOwner(_owner)) return;
clearPending();
if (m_numOwners >= c_maxOwners)
reorganizeOwners();
if (m_numOwners >= c_maxOwners)
return;
m_numOwners++;
m_owners[m_numOwners] = uint(_owner);
m_ownerIndex[uint(_owner)] = m_numOwners;
OwnerAdded(_owner);
}
function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
uint ownerIndex = m_ownerIndex[uint(_owner)];
if (ownerIndex == 0) return;
if (m_required > m_numOwners - 1) return;
m_owners[ownerIndex] = 0;
m_ownerIndex[uint(_owner)] = 0;
clearPending();
reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot
OwnerRemoved(_owner);
}
function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external {
if (_newRequired > m_numOwners) return;
m_required = _newRequired;
clearPending();
RequirementChanged(_newRequired);
}
// Gets an owner by 0-indexed position (using numOwners as the count)
function getOwner(uint ownerIndex) external constant returns (address) {
return address(m_owners[ownerIndex + 1]);
}
function isOwner(address _addr) constant returns (bool) {
return m_ownerIndex[uint(_addr)] > 0;
}
function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool) {
var pending = m_pending[_operation];
uint ownerIndex = m_ownerIndex[uint(_owner)];
// make sure they're an owner
if (ownerIndex == 0) return false;
// determine the bit to set for this owner.
uint ownerIndexBit = 2**ownerIndex;
return !(pending.ownersDone & ownerIndexBit == 0);
}
// constructor - stores initial daily limit and records the present day's index.
function initDaylimit(uint _limit) only_uninitialized {
m_dailyLimit = _limit;
m_lastDay = today();
}
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external {
m_dailyLimit = _newLimit;
}
// resets the amount already spent today. needs many of the owners to confirm.
function resetSpentToday() onlymanyowners(sha3(msg.data)) external {
m_spentToday = 0;
}
// throw unless the contract is not yet initialized.
modifier only_uninitialized { if (m_numOwners > 0) throw; _; }
// constructor - just pass on the owner array to the multiowned and
// the limit to daylimit
function initWallet(address[] _owners, uint _required, uint _daylimit) only_uninitialized {
initDaylimit(_daylimit);
initMultiowned(_owners, _required);
}
// kills the contract sending everything to `_to`.
function kill(address _to) onlymanyowners(sha3(msg.data)) external {
suicide(_to);
}
// Outside-visible transact entry point. Executes transaction immediately if below daily spend limit.
// If not, goes into multisig process. We provide a hash on return to allow the sender to provide
// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value
// and _data arguments). They still get the option of using them if they want, anyways.
function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 o_hash) {
// first, take the opportunity to check that we're under the daily limit.
if ((_data.length == 0 && underLimit(_value)) || m_required == 1) {
// yes - just execute the call.
address created;
if (_to == 0) {
created = create(_value, _data);
} else {
if (!_to.call.value(_value)(_data))
throw;
}
SingleTransact(msg.sender, _value, _to, _data, created);
} else {
// determine our operation hash.
o_hash = sha3(msg.data, block.number);
// store if it's new
if (m_txs[o_hash].to == 0 && m_txs[o_hash].value == 0 && m_txs[o_hash].data.length == 0) {
m_txs[o_hash].to = _to;
m_txs[o_hash].value = _value;
m_txs[o_hash].data = _data;
}
if (!confirm(o_hash)) {
ConfirmationNeeded(o_hash, msg.sender, _value, _to, _data);
}
}
}
function create(uint _value, bytes _code) internal returns (address o_addr) {
assembly {
o_addr := create(_value, add(_code, 0x20), mload(_code))
jumpi(0xdeadbeef, iszero(extcodesize(o_addr)))
}
}
// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
// to determine the body of the transaction from the hash provided.
function confirm(bytes32 _h) onlymanyowners(_h) returns (bool o_success) {
if (m_txs[_h].to != 0 || m_txs[_h].value != 0 || m_txs[_h].data.length != 0) {
address created;
if (m_txs[_h].to == 0) {
created = create(m_txs[_h].value, m_txs[_h].data);
} else {
if (!m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data))
throw;
}
MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data, created);
delete m_txs[_h];
return true;
}
}
// INTERNAL METHODS
function confirmAndCheck(bytes32 _operation) internal returns (bool) {
// determine what index the present sender is:
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
// make sure they're an owner
if (ownerIndex == 0) return;
var pending = m_pending[_operation];
// if we're not yet working on this operation, switch over and reset the confirmation status.
if (pending.yetNeeded == 0) {
// reset count of confirmations needed.
pending.yetNeeded = m_required;
// reset which owners have confirmed (none) - set our bitmap to 0.
pending.ownersDone = 0;
pending.index = m_pendingIndex.length++;
m_pendingIndex[pending.index] = _operation;
}
// determine the bit to set for this owner.
uint ownerIndexBit = 2**ownerIndex;
// make sure we (the message sender) haven't confirmed this operation previously.
if (pending.ownersDone & ownerIndexBit == 0) {
Confirmation(msg.sender, _operation);
// ok - check if count is enough to go ahead.
if (pending.yetNeeded <= 1) {
// enough confirmations: reset and run interior.
delete m_pendingIndex[m_pending[_operation].index];
delete m_pending[_operation];
return true;
}
else
{
// not enough: record that this owner in particular confirmed.
pending.yetNeeded--;
pending.ownersDone |= ownerIndexBit;
}
}
}
function reorganizeOwners() private {
uint free = 1;
while (free < m_numOwners)
{
while (free < m_numOwners && m_owners[free] != 0) free++;
while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--;
if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0)
{
m_owners[free] = m_owners[m_numOwners];
m_ownerIndex[m_owners[free]] = free;
m_owners[m_numOwners] = 0;
}
}
}
// checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and
// returns true. otherwise just returns false.
function underLimit(uint _value) internal onlyowner returns (bool) {
// reset the spend limit if we're on a different day to last time.
if (today() > m_lastDay) {
m_spentToday = 0;
m_lastDay = today();
}
// check to see if there's enough left - if so, subtract and return true.
// overflow protection // dailyLimit check
if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) {
m_spentToday += _value;
return true;
}
return false;
}
// determines today's index.
function today() private constant returns (uint) { return now / 1 days; }
function clearPending() internal {
uint length = m_pendingIndex.length;
for (uint i = 0; i < length; ++i) {
delete m_txs[m_pendingIndex[i]];
if (m_pendingIndex[i] != 0)
delete m_pending[m_pendingIndex[i]];
}
delete m_pendingIndex;
}
// FIELDS
address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe;
// the number of owners that must confirm the same operation before it is run.
uint public m_required;
// pointer used to find a free slot in m_owners
uint public m_numOwners;
uint public m_dailyLimit;
uint public m_spentToday;
uint public m_lastDay;
// list of owners
uint[256] m_owners;
uint constant c_maxOwners = 250;
// index on the list of owners to allow reverse lookup
mapping(uint => uint) m_ownerIndex;
// the ongoing operations.
mapping(bytes32 => PendingState) m_pending;
bytes32[] m_pendingIndex;
// pending transactions we have at present.
mapping (bytes32 => Transaction) m_txs;
}

@ -1,31 +0,0 @@
contract Crowdfunding {
mapping(address => uint) public balances;
address public owner;
uint256 INVEST_MIN = 1 ether;
uint256 INVEST_MAX = 10 ether;
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
function crowdfunding() {
owner = msg.sender;
}
function withdrawfunds() onlyOwner {
msg.sender.transfer(this.balance);
}
function invest() public payable {
require(msg.value > INVEST_MIN && msg.value < INVEST_MAX);
balances[msg.sender] += msg.value;
}
function getBalance() public constant returns (uint) {
return balances[msg.sender];
}
}

@ -0,0 +1,21 @@
contract EtherStore {
uint256 public withdrawalLimit = 1 ether;
mapping(address => uint256) public lastWithdrawTime;
mapping(address => uint256) public balances;
function depositFunds() public payable {
balances[msg.sender] += msg.value;
}
function withdrawFunds (uint256 _weiToWithdraw) public {
require(balances[msg.sender] >= _weiToWithdraw);
// limit the withdrawal
require(_weiToWithdraw <= withdrawalLimit);
// limit the time allowed to withdraw
require(now >= lastWithdrawTime[msg.sender] + 1 weeks);
require(msg.sender.call.value(_weiToWithdraw)());
balances[msg.sender] -= _weiToWithdraw;
lastWithdrawTime[msg.sender] = now;
}
}

@ -0,0 +1,12 @@
contract HashForEther {
function withdrawWinnings() {
// Winner if the last 8 hex characters of the address are 0.
require(uint32(msg.sender) == 0);
_sendWinnings();
}
function _sendWinnings() {
msg.sender.transfer(this.balance);
}
}

@ -0,0 +1,21 @@
contract TimeLock {
mapping(address => uint) public balances;
mapping(address => uint) public lockTime;
function deposit() public payable {
balances[msg.sender] += msg.value;
lockTime[msg.sender] = now + 1 weeks;
}
function increaseLockTime(uint _secondsToIncrease) public {
lockTime[msg.sender] += _secondsToIncrease;
}
function withdraw() public {
require(balances[msg.sender] > 0);
require(now > lockTime[msg.sender]);
balances[msg.sender] = 0;
msg.sender.transfer(balances[msg.sender]);
}
}

@ -1,4 +1,6 @@
contract Under { pragma solidity ^0.4.18;
contract Token {
mapping(address => uint) balances; mapping(address => uint) balances;
uint public totalSupply; uint public totalSupply;
@ -7,7 +9,7 @@ contract Under {
balances[msg.sender] = totalSupply = _initialSupply; balances[msg.sender] = totalSupply = _initialSupply;
} }
function sendeth(address _to, uint _value) public returns (bool) { function transfer(address _to, uint _value) public returns (bool) {
require(balances[msg.sender] - _value >= 0); require(balances[msg.sender] - _value >= 0);
balances[msg.sender] -= _value; balances[msg.sender] -= _value;
balances[_to] += _value; balances[_to] += _value;

@ -15,7 +15,7 @@ class GraphTest(BaseTestCase):
contract = ETHContract(input_file.read_text()) contract = ETHContract(input_file.read_text())
sym = SymExecWrapper(contract, address=(util.get_indexed_address(0))) sym = SymExecWrapper(contract, address=(util.get_indexed_address(0)), strategy="dfs")
html = generate_graph(sym) html = generate_graph(sym)
output_current.write_text(html) output_current.write_text(html)

@ -47,7 +47,7 @@ IDENTITY_TEST[3] = (83269476937987, False)
def _all_info(laser): def _all_info(laser):
accounts = {} accounts = {}
for address, _account in laser.accounts.items(): for address, _account in laser.world_state.accounts.items():
account = _account.as_dict account = _account.as_dict
account["code"] = account["code"].instruction_list account["code"] = account["code"].instruction_list
account['balance'] = str(account['balance']) account['balance'] = str(account['balance'])

@ -24,7 +24,7 @@ def _fix_debug_data(json_str):
def _generate_report(input_file): def _generate_report(input_file):
contract = ETHContract(input_file.read_text()) contract = ETHContract(input_file.read_text())
sym = SymExecWrapper(contract, address=(util.get_indexed_address(0))) sym = SymExecWrapper(contract, address=(util.get_indexed_address(0)), strategy="dfs")
issues = fire_lasers(sym) issues = fire_lasers(sym)
report = Report() report = Report()

@ -18,7 +18,7 @@ class LaserEncoder(json.JSONEncoder):
def _all_info(laser): def _all_info(laser):
accounts = {} accounts = {}
for address, _account in laser.accounts.items(): for address, _account in laser.world_state.accounts.items():
account = _account.as_dict account = _account.as_dict
account["code"] = account["code"].instruction_list account["code"] = account["code"].instruction_list
account['balance'] = str(account['balance']) account['balance'] = str(account['balance'])
@ -77,7 +77,7 @@ class SVMTestCase(BaseTestCase):
account = svm.Account("0x0000000000000000000000000000000000000000", disassembly) account = svm.Account("0x0000000000000000000000000000000000000000", disassembly)
accounts = {account.address: account} accounts = {account.address: account}
laser = svm.LaserEVM(accounts) laser = svm.LaserEVM(accounts, max_depth=22)
laser.sym_exec(account.address) laser.sym_exec(account.address)
laser_info = _all_info(laser) laser_info = _all_info(laser)

@ -3,7 +3,7 @@ import pytest
from pytest_mock import mocker from pytest_mock import mocker
from mythril.laser.ethereum.taint_analysis import * from mythril.laser.ethereum.taint_analysis import *
from mythril.laser.ethereum.svm import GlobalState, Node, Edge, LaserEVM from mythril.laser.ethereum.svm import GlobalState, Node, Edge, LaserEVM
from mythril.laser.ethereum.state import MachineState from mythril.laser.ethereum.state import MachineState, Account, Environment
def test_execute_state(mocker): def test_execute_state(mocker):
@ -57,12 +57,14 @@ def test_execute_node(mocker):
def test_execute(mocker): def test_execute(mocker):
state_1 = GlobalState(None, None, None, MachineState(gas=10000000)) active_account = Account('0x00')
environment = Environment(active_account, None, None, None, None, None)
state_1 = GlobalState(None, environment, None, MachineState(gas=10000000))
state_1.mstate.stack = [1, 2] state_1.mstate.stack = [1, 2]
mocker.patch.object(state_1, 'get_current_instruction') mocker.patch.object(state_1, 'get_current_instruction')
state_1.get_current_instruction.return_value = {"opcode": "PUSH"} state_1.get_current_instruction.return_value = {"opcode": "PUSH"}
state_2 = GlobalState(None, None, None, MachineState(gas=10000000)) state_2 = GlobalState(None, environment, None, MachineState(gas=10000000))
state_2.mstate.stack = [1, 2, 3] state_2.mstate.stack = [1, 2, 3]
mocker.patch.object(state_2, 'get_current_instruction') mocker.patch.object(state_2, 'get_current_instruction')
state_2.get_current_instruction.return_value = {"opcode": "ADD"} state_2.get_current_instruction.return_value = {"opcode": "ADD"}
@ -70,7 +72,7 @@ def test_execute(mocker):
node_1 = Node("Test contract") node_1 = Node("Test contract")
node_1.states = [state_1, state_2] node_1.states = [state_1, state_2]
state_3 = GlobalState(None, None, None, MachineState(gas=10000000)) state_3 = GlobalState(None, environment, None, MachineState(gas=10000000))
state_3.mstate.stack = [1, 2] state_3.mstate.stack = [1, 2]
mocker.patch.object(state_3, 'get_current_instruction') mocker.patch.object(state_3, 'get_current_instruction')
state_3.get_current_instruction.return_value = {"opcode": "ADD"} state_3.get_current_instruction.return_value = {"opcode": "ADD"}

@ -0,0 +1,19 @@
pragma solidity ^0.4.16;
contract IntegerOverflow2 {
uint256 public count = 7;
mapping(address => uint256) balances;
function batchTransfer(address[] _receivers, uint256 _value) public returns(bool){
uint cnt = _receivers.length;
uint256 amount = uint256(cnt) * _value;
require(cnt > 0 && cnt <= 20);
balances[msg.sender] -=amount;
return true;
}
}

@ -0,0 +1 @@
60806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306661abd1461005157806383f12fec1461007c575b600080fd5b34801561005d57600080fd5b50610066610104565b6040518082815260200191505060405180910390f35b34801561008857600080fd5b506100ea600480360381019080803590602001908201803590602001908080602002602001604051908101604052809392919081815260200183836020028082843782019150505050505091929192908035906020019092919050505061010a565b604051808215151515815260200191505060405180910390f35b60005481565b6000806000845191508382029050600082118015610129575060148211155b151561013457600080fd5b80600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540392505081905550600192505050929150505600a165627a7a7230582016b81221eb990028632ba9b34d3c01599d24acdb5b81dd6789845696f5db257c0029

@ -0,0 +1,259 @@
0 PUSH1 0x80
2 PUSH1 0x40
4 MSTORE
5 PUSH1 0x04
7 CALLDATASIZE
8 LT
9 PUSH2 0x004c
12 JUMPI
13 PUSH1 0x00
15 CALLDATALOAD
16 PUSH29 0x0100000000000000000000000000000000000000000000000000000000
46 SWAP1
47 DIV
48 PUSH4 0xffffffff
53 AND
54 DUP1
55 PUSH4 0x06661abd
60 EQ
61 PUSH2 0x0051
64 JUMPI
65 DUP1
66 PUSH4 0x83f12fec
71 EQ
72 PUSH2 0x007c
75 JUMPI
76 JUMPDEST
77 PUSH1 0x00
79 DUP1
80 REVERT
81 JUMPDEST
82 CALLVALUE
83 DUP1
84 ISZERO
85 PUSH2 0x005d
88 JUMPI
89 PUSH1 0x00
91 DUP1
92 REVERT
93 JUMPDEST
94 POP
95 PUSH2 0x0066
98 PUSH2 0x0104
101 JUMP
102 JUMPDEST
103 PUSH1 0x40
105 MLOAD
106 DUP1
107 DUP3
108 DUP2
109 MSTORE
110 PUSH1 0x20
112 ADD
113 SWAP2
114 POP
115 POP
116 PUSH1 0x40
118 MLOAD
119 DUP1
120 SWAP2
121 SUB
122 SWAP1
123 RETURN
124 JUMPDEST
125 CALLVALUE
126 DUP1
127 ISZERO
128 PUSH2 0x0088
131 JUMPI
132 PUSH1 0x00
134 DUP1
135 REVERT
136 JUMPDEST
137 POP
138 PUSH2 0x00ea
141 PUSH1 0x04
143 DUP1
144 CALLDATASIZE
145 SUB
146 DUP2
147 ADD
148 SWAP1
149 DUP1
150 DUP1
151 CALLDATALOAD
152 SWAP1
153 PUSH1 0x20
155 ADD
156 SWAP1
157 DUP3
158 ADD
159 DUP1
160 CALLDATALOAD
161 SWAP1
162 PUSH1 0x20
164 ADD
165 SWAP1
166 DUP1
167 DUP1
168 PUSH1 0x20
170 MUL
171 PUSH1 0x20
173 ADD
174 PUSH1 0x40
176 MLOAD
177 SWAP1
178 DUP2
179 ADD
180 PUSH1 0x40
182 MSTORE
183 DUP1
184 SWAP4
185 SWAP3
186 SWAP2
187 SWAP1
188 DUP2
189 DUP2
190 MSTORE
191 PUSH1 0x20
193 ADD
194 DUP4
195 DUP4
196 PUSH1 0x20
198 MUL
199 DUP1
200 DUP3
201 DUP5
202 CALLDATACOPY
203 DUP3
204 ADD
205 SWAP2
206 POP
207 POP
208 POP
209 POP
210 POP
211 POP
212 SWAP2
213 SWAP3
214 SWAP2
215 SWAP3
216 SWAP1
217 DUP1
218 CALLDATALOAD
219 SWAP1
220 PUSH1 0x20
222 ADD
223 SWAP1
224 SWAP3
225 SWAP2
226 SWAP1
227 POP
228 POP
229 POP
230 PUSH2 0x010a
233 JUMP
234 JUMPDEST
235 PUSH1 0x40
237 MLOAD
238 DUP1
239 DUP3
240 ISZERO
241 ISZERO
242 ISZERO
243 ISZERO
244 DUP2
245 MSTORE
246 PUSH1 0x20
248 ADD
249 SWAP2
250 POP
251 POP
252 PUSH1 0x40
254 MLOAD
255 DUP1
256 SWAP2
257 SUB
258 SWAP1
259 RETURN
260 JUMPDEST
261 PUSH1 0x00
263 SLOAD
264 DUP2
265 JUMP
266 JUMPDEST
267 PUSH1 0x00
269 DUP1
270 PUSH1 0x00
272 DUP5
273 MLOAD
274 SWAP2
275 POP
276 DUP4
277 DUP3
278 MUL
279 SWAP1
280 POP
281 PUSH1 0x00
283 DUP3
284 GT
285 DUP1
286 ISZERO
287 PUSH2 0x0129
290 JUMPI
291 POP
292 PUSH1 0x14
294 DUP3
295 GT
296 ISZERO
297 JUMPDEST
298 ISZERO
299 ISZERO
300 PUSH2 0x0134
303 JUMPI
304 PUSH1 0x00
306 DUP1
307 REVERT
308 JUMPDEST
309 DUP1
310 PUSH1 0x01
312 PUSH1 0x00
314 CALLER
315 PUSH20 0xffffffffffffffffffffffffffffffffffffffff
336 AND
337 PUSH20 0xffffffffffffffffffffffffffffffffffffffff
358 AND
359 DUP2
360 MSTORE
361 PUSH1 0x20
363 ADD
364 SWAP1
365 DUP2
366 MSTORE
367 PUSH1 0x20
369 ADD
370 PUSH1 0x00
372 SHA3
373 PUSH1 0x00
375 DUP3
376 DUP3
377 SLOAD
378 SUB
379 SWAP3
380 POP
381 POP
382 DUP2
383 SWAP1
384 SSTORE
385 POP
386 PUSH1 0x01
388 SWAP3
389 POP
390 POP
391 POP
392 SWAP3
393 SWAP2
394 POP
395 POP
396 JUMP
397 STOP

File diff suppressed because one or more lines are too long

@ -0,0 +1 @@
{"error": null, "issues": [{"address": 158, "contract": "Unknown", "debug": "<DEBUG-DATA>", "description": "A possible integer overflow exists in the function `_function_0x83f12fec`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", "function": "_function_0x83f12fec", "title": "Integer Overflow", "type": "Warning"}, {"address": 278, "contract": "Unknown", "debug": "<DEBUG-DATA>", "description": "A possible integer overflow exists in the function `_function_0x83f12fec`.\nThe addition or multiplication may result in a value higher than the maximum representable integer.", "function": "_function_0x83f12fec", "title": "Integer Overflow", "type": "Warning"}, {"address": 378, "contract": "Unknown", "debug": "<DEBUG-DATA>", "description": "A possible integer underflow exists in the function `_function_0x83f12fec`.\nThe subtraction may result in a value < 0.", "function": "_function_0x83f12fec", "title": "Integer Underflow", "type": "Warning"}], "success": true}

@ -0,0 +1,37 @@
# Analysis results for test-filename.sol
## Integer Overflow
- Type: Warning
- Contract: Unknown
- Function name: `_function_0x83f12fec`
- PC address: 158
### Description
A possible integer overflow exists in the function `_function_0x83f12fec`.
The addition or multiplication may result in a value higher than the maximum representable integer.
## Integer Overflow
- Type: Warning
- Contract: Unknown
- Function name: `_function_0x83f12fec`
- PC address: 278
### Description
A possible integer overflow exists in the function `_function_0x83f12fec`.
The addition or multiplication may result in a value higher than the maximum representable integer.
## Integer Underflow
- Type: Warning
- Contract: Unknown
- Function name: `_function_0x83f12fec`
- PC address: 378
### Description
A possible integer underflow exists in the function `_function_0x83f12fec`.
The subtraction may result in a value < 0.

@ -0,0 +1,27 @@
==== Integer Overflow ====
Type: Warning
Contract: Unknown
Function name: _function_0x83f12fec
PC address: 158
A possible integer overflow exists in the function `_function_0x83f12fec`.
The addition or multiplication may result in a value higher than the maximum representable integer.
--------------------
==== Integer Overflow ====
Type: Warning
Contract: Unknown
Function name: _function_0x83f12fec
PC address: 278
A possible integer overflow exists in the function `_function_0x83f12fec`.
The addition or multiplication may result in a value higher than the maximum representable integer.
--------------------
==== Integer Underflow ====
Type: Warning
Contract: Unknown
Function name: _function_0x83f12fec
PC address: 378
A possible integer underflow exists in the function `_function_0x83f12fec`.
The subtraction may result in a value < 0.
--------------------

File diff suppressed because it is too large Load Diff

@ -1,5 +1,5 @@
[tox] [tox]
envlist = py36 envlist = py35,py36
[testenv] [testenv]
deps = deps =

Loading…
Cancel
Save