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.
### 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
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(...)"
```
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
$ 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.rpc.exceptions import ConnectionError
from mythril.support import signatures
from mythril.support.truffle import analyze_truffle_project
from mythril.support.loader import DynLoader
from mythril.exceptions import CompilerError
from mythril.analysis.symbolic import StateSpace
@ -41,38 +42,28 @@ def exitWithError(message):
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='*')
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('-x', '--fire-lasers', action='store_true', help='detect vulnerabilities')
commands.add_argument('-t', '--trace', action='store_true', help='trace contract, use with --data (optional)')
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')
commands.add_argument('-x', '--fire-lasers', action='store_true', help='detect vulnerabilities, use with -c, -a or solidity file(s)')
commands.add_argument('-t', '--truffle', action='store_true', help='analyze a truffle project (run from project dir)')
inputs = parser.add_argument_group('input arguments')
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('-l', '--dynld', action='store_true', help='auto-load dependencies (experimental)')
inputs.add_argument('--data', help='message call input data for tracing')
inputs.add_argument('-l', '--dynld', action='store_true', help='auto-load dependencies from the blockchain')
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')
database = parser.add_argument_group('local contracts database')
database.add_argument('--init-db', action='store_true', help='initialize the contract database')
database.add_argument('-s', '--search', help='search the contract database', metavar='EXPRESSION')
presets = parser.add_argument_group('RPC presets')
presets.add_argument('--ganache', action='store_true', help='Use local Ganache')
presets.add_argument('--infura-mainnet', action='store_true', help='Use Infura Node service (Mainnet)')
presets.add_argument('--infura-rinkeby', action='store_true', help='Use Infura Node service (Rinkeby)')
presets.add_argument('--infura-kovan', action='store_true', help='Use Infura Node service (Kovan)')
presets.add_argument('--infura-ropsten', action='store_true', help='Use Infura Node service (Ropsten)')
utils = parser.add_argument_group('utilities')
utils.add_argument('-d', '--disassemble', action='store_true', help='print disassembly')
utils.add_argument('--xrefs', action='store_true', help='get xrefs from a contract')
utils.add_argument('--hash', help='calculate function signature hash', metavar='SIGNATURE')
utils.add_argument('--storage', help='read state variables from storage index (index,length,array(in case of arrays)), use with -a', metavar='INDEX')
options = parser.add_argument_group('options')
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('-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
try:
@ -127,7 +128,7 @@ else:
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()
sys.exit()
@ -139,6 +140,17 @@ elif (args.hash):
print("0x" + utils.sha3(args.hash)[:4].hex())
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
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()
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):
print("\n".join(contracts[0].get_xrefs()))
@ -314,7 +312,14 @@ elif (args.graph) or (args.fire_lasers):
else:
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:
parser.print_help()

@ -38,7 +38,8 @@ def execute(statespace):
continue
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.

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

@ -25,8 +25,6 @@ def fire_lasers(statespace):
for i in range(0, len(issues)):
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=""):
self.code = code
self.creation_code = creation_code
self.name = name
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):
return {

@ -9,6 +9,7 @@ import re
def safe_decode(hex_encoded_string):
if (hex_encoded_string.startswith("0x")):
return bytes.fromhex(hex_encoded_string[2:])
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(
name='mythril',
version='0.9.1',
version='0.10.2',
description='Security analysis tool for Ethereum smart contracts',
long_description=long_description,

Loading…
Cancel
Save