Merge branch 'master' into master

pull/48/head
Bernhard Mueller 7 years ago committed by GitHub
commit c888cf1a48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      README.md
  2. 81
      myth
  3. 3
      mythril/analysis/modules/integer_underflow.py
  4. 5
      mythril/analysis/report.py
  5. 4
      mythril/analysis/security.py
  6. 9
      mythril/ether/ethcontract.py
  7. 1
      mythril/ether/util.py
  8. 87
      mythril/support/truffle.py
  9. 2
      setup.py

@ -40,6 +40,10 @@ Run `myth -x` with one of the input options described below to run the analysis.
Mythril detects a range of [security issues](security_checks.md), including integer underflows, owner-overwrite-to-Ether-withdrawal, and others. However, the analysis will not detect business logic issues and is not equivalent to formal verification. Mythril detects a range of [security issues](security_checks.md), including integer underflows, owner-overwrite-to-Ether-withdrawal, and others. However, the analysis will not detect business logic issues and is not equivalent to formal verification.
### Analyzing a Truffle project
[Truffle Suite](http://truffleframework.com) is a popular development framework for Ethereum. To analyze the smart contracts in a Truffle project, change in the project root directory and make run `truffle compile` followed by `myth --truffle`.
### Analyzing Solidity code ### Analyzing Solidity code
In order to work with Solidity source code files, the [solc command line compiler](http://solidity.readthedocs.io/en/develop/using-the-compiler.html) needs to be installed and in path. You can then provide the source file(s) as positional arguments, e.g.: In order to work with Solidity source code files, the [solc command line compiler](http://solidity.readthedocs.io/en/develop/using-the-compiler.html) needs to be installed and in path. You can then provide the source file(s) as positional arguments, e.g.:
@ -54,7 +58,7 @@ Alternatively, compile the code on [Remix](http://remix.ethereum.org) and pass t
$ myth -x -c "0x5060(...)" $ myth -x -c "0x5060(...)"
``` ```
If you have multiple interdependent contracts, pass them to Mythril as separate input files. Mythril will map the first contract to address "0x0000(..)", the second one to "0x1111(...)", and so forth (make sure that contract addresses are set accordingly in the source). The contract passed in the first argument will be executed as the "main" contract. If you have multiple interdependent contracts, pass them to Mythril as separate input files. Mythril will map the first contract to address "0x0000(..)", the second one to "0x1111(...)", and so forth (make sure that contract addresses are set accordingly in the source). The contract passed as the first argument will be used as analysis entrypoint.
```bash ```bash
$ myth -x myContract.sol myLibrary.sol $ myth -x myContract.sol myLibrary.sol

81
myth

@ -12,6 +12,7 @@ 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
from mythril.support import signatures from mythril.support import signatures
from mythril.support.truffle import analyze_truffle_project
from mythril.support.loader import DynLoader from mythril.support.loader import DynLoader
from mythril.exceptions import CompilerError from mythril.exceptions import CompilerError
from mythril.analysis.symbolic import StateSpace from mythril.analysis.symbolic import StateSpace
@ -41,38 +42,28 @@ def exitWithError(message):
sys.exit() sys.exit()
parser = argparse.ArgumentParser(description='Bug hunting on the Ethereum blockchain') parser = argparse.ArgumentParser(description='Security analysis of Ethereum smart contracts')
parser.add_argument("solidity_file", nargs='*') parser.add_argument("solidity_file", nargs='*')
commands = parser.add_argument_group('commands') commands = parser.add_argument_group('commands')
commands.add_argument('-d', '--disassemble', action='store_true', help='disassemble')
commands.add_argument('-g', '--graph', help='generate a control flow graph', metavar='OUTPUT_FILE') commands.add_argument('-g', '--graph', help='generate a control flow graph', metavar='OUTPUT_FILE')
commands.add_argument('-x', '--fire-lasers', action='store_true', help='detect vulnerabilities') commands.add_argument('-x', '--fire-lasers', action='store_true', help='detect vulnerabilities, use with -c, -a or solidity file(s)')
commands.add_argument('-t', '--trace', action='store_true', help='trace contract, use with --data (optional)') commands.add_argument('-t', '--truffle', action='store_true', help='analyze a truffle project (run from project dir)')
commands.add_argument('-s', '--search', help='search the contract database', metavar='EXPRESSION')
commands.add_argument('--xrefs', action='store_true', help='get xrefs from a contract')
commands.add_argument('--hash', help='calculate function signature hash', metavar='SIGNATURE')
commands.add_argument('--storage', help='read state variables from storage index (index,length,array(in case of arrays)), use with -a', metavar='INDEX')
commands.add_argument('--init-db', action='store_true', help='initialize the contract database')
inputs = parser.add_argument_group('input arguments') inputs = parser.add_argument_group('input arguments')
inputs.add_argument('-c', '--code', help='hex-encoded bytecode string ("6060604052...")', metavar='BYTECODE') inputs.add_argument('-c', '--code', help='hex-encoded bytecode string ("6060604052...")', metavar='BYTECODE')
inputs.add_argument('-a', '--address', help='pull contract from the blockchain', metavar='CONTRACT_ADDRESS') inputs.add_argument('-a', '--address', help='pull contract from the blockchain', metavar='CONTRACT_ADDRESS')
inputs.add_argument('-l', '--dynld', action='store_true', help='auto-load dependencies (experimental)') inputs.add_argument('-l', '--dynld', action='store_true', help='auto-load dependencies from the blockchain')
inputs.add_argument('--data', help='message call input data for tracing')
rpc = parser.add_argument_group('RPC options') database = parser.add_argument_group('local contracts database')
rpc.add_argument('--rpchost', default='127.0.0.1', help='RPC host') database.add_argument('--init-db', action='store_true', help='initialize the contract database')
rpc.add_argument('--rpcport', type=int, default=8545, help='RPC port') database.add_argument('-s', '--search', help='search the contract database', metavar='EXPRESSION')
rpc.add_argument('--rpctls', type=bool, default=False, help='RPC port')
presets = parser.add_argument_group('RPC presets') utils = parser.add_argument_group('utilities')
presets.add_argument('--ganache', action='store_true', help='Use local Ganache') utils.add_argument('-d', '--disassemble', action='store_true', help='print disassembly')
presets.add_argument('--infura-mainnet', action='store_true', help='Use Infura Node service (Mainnet)') utils.add_argument('--xrefs', action='store_true', help='get xrefs from a contract')
presets.add_argument('--infura-rinkeby', action='store_true', help='Use Infura Node service (Rinkeby)') utils.add_argument('--hash', help='calculate function signature hash', metavar='SIGNATURE')
presets.add_argument('--infura-kovan', action='store_true', help='Use Infura Node service (Kovan)') utils.add_argument('--storage', help='read state variables from storage index (index,length,array(in case of arrays)), use with -a', metavar='INDEX')
presets.add_argument('--infura-ropsten', action='store_true', help='Use Infura Node service (Ropsten)')
options = parser.add_argument_group('options') options = parser.add_argument_group('options')
options.add_argument('--ipc', help='use IPC interface instead of RPC', action='store_true') options.add_argument('--ipc', help='use IPC interface instead of RPC', action='store_true')
@ -81,6 +72,16 @@ options.add_argument('--max-depth', type=int, default=12, help='Maximum recursio
options.add_argument('--enable-physics', type=bool, default=False, help='enable graph physics simulation') options.add_argument('--enable-physics', type=bool, default=False, help='enable graph physics simulation')
options.add_argument('-v', type=int, help='log level (0-2)', metavar='LOG_LEVEL') options.add_argument('-v', type=int, help='log level (0-2)', metavar='LOG_LEVEL')
rpc = parser.add_argument_group('RPC options')
rpc.add_argument('--rpchost', default='127.0.0.1', help='RPC host')
rpc.add_argument('--rpcport', type=int, default=8545, help='RPC port')
rpc.add_argument('--rpctls', type=bool, default=False, help='RPC port')
rpc.add_argument('--ganache', action='store_true', help='Preset: local Ganache')
rpc.add_argument('--infura-mainnet', action='store_true', help='Preset: Infura Node service (Mainnet)')
rpc.add_argument('--infura-rinkeby', action='store_true', help='Preset: Infura Node service (Rinkeby)')
rpc.add_argument('--infura-kovan', action='store_true', help='Preset: Infura Node service (Kovan)')
rpc.add_argument('--infura-ropsten', action='store_true', help='Preset: Infura Node service (Ropsten)')
# Get config values # Get config values
try: try:
@ -127,7 +128,7 @@ else:
args = parser.parse_args() args = parser.parse_args()
if not (args.search or args.init_db or args.hash or args.disassemble or args.graph or args.xrefs or args.fire_lasers or args.trace or args.storage): if not (args.search or args.init_db or args.hash or args.disassemble or args.graph or args.xrefs or args.fire_lasers or args.storage or args.truffle):
parser.print_help() parser.print_help()
sys.exit() sys.exit()
@ -139,6 +140,17 @@ elif (args.hash):
print("0x" + utils.sha3(args.hash)[:4].hex()) print("0x" + utils.sha3(args.hash)[:4].hex())
sys.exit() sys.exit()
if args.truffle:
try:
analyze_truffle_project()
except FileNotFoundError:
print("Build directory not found. Make sure that you start the analysis from the project root, and that 'truffle compile' has executed successfully.")
sys.exit()
# Establish RPC/IPC connection if necessary # Establish RPC/IPC connection if necessary
if (args.address or len(args.solidity_file) or args.init_db): if (args.address or len(args.solidity_file) or args.init_db):
@ -269,20 +281,6 @@ elif (args.disassemble):
easm_text = contracts[0].get_easm() easm_text = contracts[0].get_easm()
sys.stdout.write(easm_text) sys.stdout.write(easm_text)
elif (args.trace):
if (args.data):
trace = evm.trace(contracts[0].code, args.data)
else:
trace = evm.trace(contracts[0].code)
for i in trace:
if (re.match(r'^PUSH.*', i['op'])):
print(str(i['pc']) + " " + i['op'] + " " + i['pushvalue'] + ";\tSTACK: " + i['stack'])
else:
print(str(i['pc']) + " " + i['op'] + ";\tSTACK: " + i['stack'])
elif (args.xrefs): elif (args.xrefs):
print("\n".join(contracts[0].get_xrefs())) print("\n".join(contracts[0].get_xrefs()))
@ -314,7 +312,14 @@ elif (args.graph) or (args.fire_lasers):
else: else:
states = StateSpace(contracts, max_depth=args.max_depth) states = StateSpace(contracts, max_depth=args.max_depth)
fire_lasers(states) report = fire_lasers(states)
if (len(report.issues)):
print(report.as_text())
else:
print("The analysis was completed successfully. No issues were detected.")
else: else:
parser.print_help() parser.print_help()

@ -38,7 +38,8 @@ def execute(statespace):
continue continue
if (re.search(r'calldatasize_', str(op0))) \ if (re.search(r'calldatasize_', str(op0))) \
or (re.search(r'256\*.*If\(1', str(op0), re.DOTALL) or re.search(r'256\*.*If\(1', str(op1), re.DOTALL)): or (re.search(r'256\*.*If\(1', str(op0), re.DOTALL) or re.search(r'256\*.*If\(1', str(op1), re.DOTALL)) \
or (re.search(r'32 \+.*calldata', str(op0), re.DOTALL) or re.search(r'32 \+.*calldata', str(op1), re.DOTALL)):
# Filter for patterns that contain possible (but apparently non-exploitable) Integer underflows. # Filter for patterns that contain possible (but apparently non-exploitable) Integer underflows.

@ -11,6 +11,7 @@ class Issue:
self.description = description self.description = description
self.type = _type self.type = _type
self.debug = debug self.debug = debug
self.code = None
def as_dict(self): def as_dict(self):
@ -47,10 +48,12 @@ class Report:
text += issue.description + "\n--------------------\n" text += issue.description + "\n--------------------\n"
if issue.code:
text += "Affected code:\n\n" + issue.code + "\n--------------------\n"
if len(issue.debug): if len(issue.debug):
text += "++++ Debugging info ++++\n" + issue.debug + "\n" text += "++++ Debugging info ++++\n" + issue.debug + "\n"
text+="\n" text+="\n"
return text return text

@ -25,8 +25,6 @@ def fire_lasers(statespace):
for i in range(0, len(issues)): for i in range(0, len(issues)):
report.append_issue(issues[i]) report.append_issue(issues[i])
print(report.as_text())
else: return report
print("The analysis was completed successfully. No issues were detected.")

@ -8,11 +8,18 @@ class ETHContract(persistent.Persistent):
def __init__(self, code, creation_code="", name="", address=""): def __init__(self, code, creation_code="", name="", address=""):
self.code = code
self.creation_code = creation_code self.creation_code = creation_code
self.name = name self.name = name
self.address = address self.address = address
# Workaround: We currently do not support compile-time linking.
# Dynamic contract addresses of the format __[contract-name]_____________ are replaced with a generic address
code = re.sub(r'(_+[A-Za-z0-9]+_+)', 'aa' * 20, code)
self.code = code
def as_dict(self): def as_dict(self):
return { return {

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

@ -0,0 +1,87 @@
import os
import re
import sys
import json
from mythril.ether import util
from mythril.ether.ethcontract import ETHContract
from mythril.analysis.security import fire_lasers
from mythril.analysis.symbolic import StateSpace
from laser.ethereum import helper
def analyze_truffle_project():
project_root = os.getcwd()
build_dir = os.path.join(project_root, "build", "contracts")
files = os.listdir(build_dir)
for filename in files:
if re.match(r'.*\.json$', filename) and filename != "Migrations.json":
with open(os.path.join(build_dir, filename)) as cf:
contractdata = json.load(cf)
try:
name = contractdata['contractName']
bytecode = contractdata['deployedBytecode']
except:
print("Unable to parse contract data. Please use Truffle 4 to compile your project.")
sys.exit()
if (len(bytecode) < 4):
continue
ethcontract= ETHContract(bytecode, name=name, address = util.get_indexed_address(0))
contracts = [ethcontract]
states = StateSpace(contracts, max_depth = 10)
report = fire_lasers(states)
# augment with source code
disassembly = ethcontract.get_disassembly()
source = contractdata['source']
deployedSourceMap = contractdata['deployedSourceMap'].split(";")
mappings = []
i = 0
while(i < len(deployedSourceMap)):
m = re.search(r"^(\d+):*(\d+)", deployedSourceMap[i])
if (m):
offset = m.group(1)
length = m.group(2)
else:
m = re.search(r"^:(\d+)", deployedSourceMap[i])
if m:
length = m.group(1)
mappings.append((int(offset), int(length)))
i += 1
for key, issue in report.issues.items():
index = helper.get_instruction_index(disassembly.instruction_list, issue.pc)
if index:
issue.code_start = mappings[index][0]
issue.code_length = mappings[index][1]
issue.code = source[mappings[index][0]: mappings[index][0] + mappings[index][1]]
if len(report.issues):
print("Analysis result for " + name + ":\n" + report.as_text())
else:
print("Analysis result for " + name + ": No issues found.")

@ -254,7 +254,7 @@ Credit
setup( setup(
name='mythril', name='mythril',
version='0.9.1', version='0.10.2',
description='Security analysis tool for Ethereum smart contracts', description='Security analysis tool for Ethereum smart contracts',
long_description=long_description, long_description=long_description,

Loading…
Cancel
Save