mirror of https://github.com/ConsenSys/mythril
commit
8b2393e40c
@ -1,551 +0,0 @@ |
||||
#!/usr/bin/env python3 |
||||
# -*- coding: utf-8 -*- |
||||
"""mythril.py: Bug hunting on the Ethereum blockchain |
||||
|
||||
http://www.github.com/ConsenSys/mythril |
||||
""" |
||||
|
||||
import argparse |
||||
import json |
||||
import logging |
||||
import os |
||||
import sys |
||||
|
||||
import coloredlogs |
||||
import traceback |
||||
|
||||
import mythril.support.signatures as sigs |
||||
from mythril.exceptions import AddressNotFoundError, CriticalError |
||||
from mythril.mythril import ( |
||||
MythrilAnalyzer, |
||||
MythrilDisassembler, |
||||
MythrilConfig, |
||||
MythrilLevelDB, |
||||
) |
||||
from mythril.__version__ import __version__ as VERSION |
||||
|
||||
log = logging.getLogger(__name__) |
||||
|
||||
|
||||
def exit_with_error(format_, message): |
||||
""" |
||||
:param format_: |
||||
:param message: |
||||
""" |
||||
if format_ == "text" or format_ == "markdown": |
||||
log.error(message) |
||||
elif format_ == "json": |
||||
result = {"success": False, "error": str(message), "issues": []} |
||||
print(json.dumps(result)) |
||||
else: |
||||
result = [ |
||||
{ |
||||
"issues": [], |
||||
"sourceType": "", |
||||
"sourceFormat": "", |
||||
"sourceList": [], |
||||
"meta": {"logs": [{"level": "error", "hidden": True, "msg": message}]}, |
||||
} |
||||
] |
||||
print(json.dumps(result)) |
||||
sys.exit() |
||||
|
||||
|
||||
def main() -> None: |
||||
"""The main CLI interface entry point.""" |
||||
parser = argparse.ArgumentParser( |
||||
description="Security analysis of Ethereum smart contracts" |
||||
) |
||||
create_parser(parser) |
||||
|
||||
# Get config values |
||||
|
||||
args = parser.parse_args() |
||||
parse_args(parser=parser, args=args) |
||||
|
||||
|
||||
def create_parser(parser: argparse.ArgumentParser) -> None: |
||||
""" |
||||
Creates the parser by setting all the possible arguments |
||||
:param parser: The parser |
||||
""" |
||||
parser.add_argument("solidity_file", nargs="*") |
||||
|
||||
commands = parser.add_argument_group("commands") |
||||
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", |
||||
help="detect vulnerabilities, use with -c, -a or solidity file(s)", |
||||
) |
||||
commands.add_argument( |
||||
"--truffle", |
||||
action="store_true", |
||||
help="analyze a truffle project (run from project dir)", |
||||
) |
||||
commands.add_argument( |
||||
"-d", "--disassemble", action="store_true", help="print disassembly" |
||||
) |
||||
commands.add_argument( |
||||
"-j", |
||||
"--statespace-json", |
||||
help="dumps the statespace json", |
||||
metavar="OUTPUT_FILE", |
||||
) |
||||
|
||||
inputs = parser.add_argument_group("input arguments") |
||||
inputs.add_argument( |
||||
"-c", |
||||
"--code", |
||||
help='hex-encoded bytecode string ("6060604052...")', |
||||
metavar="BYTECODE", |
||||
) |
||||
inputs.add_argument( |
||||
"-f", |
||||
"--codefile", |
||||
help="file containing hex-encoded bytecode string", |
||||
metavar="BYTECODEFILE", |
||||
type=argparse.FileType("r"), |
||||
) |
||||
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 from the blockchain", |
||||
) |
||||
inputs.add_argument( |
||||
"--no-onchain-storage-access", |
||||
action="store_true", |
||||
help="turns off getting the data from onchain contracts", |
||||
) |
||||
inputs.add_argument( |
||||
"--bin-runtime", |
||||
action="store_true", |
||||
help="Only when -c or -f is used. Consider the input bytecode as binary runtime code, default being the contract creation bytecode.", |
||||
) |
||||
|
||||
outputs = parser.add_argument_group("output formats") |
||||
outputs.add_argument( |
||||
"-o", |
||||
"--outform", |
||||
choices=["text", "markdown", "json", "jsonv2"], |
||||
default="text", |
||||
help="report output format", |
||||
metavar="<text/markdown/json/jsonv2>", |
||||
) |
||||
|
||||
database = parser.add_argument_group("local contracts database") |
||||
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", |
||||
) |
||||
|
||||
utilities = parser.add_argument_group("utilities") |
||||
utilities.add_argument( |
||||
"--hash", help="calculate function signature hash", metavar="SIGNATURE" |
||||
) |
||||
utilities.add_argument( |
||||
"--storage", |
||||
help="read state variables from storage index, use with -a", |
||||
metavar="INDEX,NUM_SLOTS,[array] / mapping,INDEX,[KEY1, KEY2...]", |
||||
) |
||||
utilities.add_argument( |
||||
"--solv", |
||||
help="specify solidity compiler version. If not present, will try to install it (Experimental)", |
||||
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.add_argument( |
||||
"-m", |
||||
"--modules", |
||||
help="Comma-separated list of security analysis modules", |
||||
metavar="MODULES", |
||||
) |
||||
options.add_argument( |
||||
"--max-depth", |
||||
type=int, |
||||
default=50, |
||||
help="Maximum recursion depth for symbolic execution", |
||||
) |
||||
options.add_argument( |
||||
"--strategy", |
||||
choices=["dfs", "bfs", "naive-random", "weighted-random"], |
||||
default="bfs", |
||||
help="Symbolic execution strategy", |
||||
) |
||||
options.add_argument( |
||||
"-b", |
||||
"--loop-bound", |
||||
type=int, |
||||
default=4, |
||||
help="Bound loops at n iterations", |
||||
metavar="N", |
||||
) |
||||
options.add_argument( |
||||
"-t", |
||||
"--transaction-count", |
||||
type=int, |
||||
default=2, |
||||
help="Maximum number of transactions issued by laser", |
||||
) |
||||
options.add_argument( |
||||
"--solver-timeout", |
||||
type=int, |
||||
default=10000, |
||||
help="The maximum amount of time(in milli seconds) the solver spends for queries from analysis modules", |
||||
) |
||||
options.add_argument( |
||||
"--execution-timeout", |
||||
type=int, |
||||
default=86400, |
||||
help="The amount of seconds to spend on symbolic execution", |
||||
) |
||||
options.add_argument( |
||||
"--create-timeout", |
||||
type=int, |
||||
default=10, |
||||
help="The amount of seconds to spend on " "the initial contract creation", |
||||
) |
||||
options.add_argument( |
||||
"--solc-json", |
||||
help="Json for the optional 'settings' parameter of solc's standard-json input", |
||||
) |
||||
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( |
||||
"-v", type=int, help="log level (0-5)", metavar="LOG_LEVEL", default=2 |
||||
) |
||||
options.add_argument( |
||||
"-q", |
||||
"--query-signature", |
||||
action="store_true", |
||||
help="Lookup function signatures through www.4byte.directory", |
||||
) |
||||
options.add_argument( |
||||
"--enable-iprof", action="store_true", help="enable the instruction profiler" |
||||
) |
||||
options.add_argument( |
||||
"--disable-dependency-pruning", |
||||
action="store_true", |
||||
help="Deactivate dependency-based pruning", |
||||
) |
||||
|
||||
rpc = parser.add_argument_group("RPC options") |
||||
|
||||
rpc.add_argument( |
||||
"--rpc", |
||||
help="custom RPC settings", |
||||
metavar="HOST:PORT / ganache / infura-[network_name]", |
||||
default="infura-mainnet", |
||||
) |
||||
rpc.add_argument( |
||||
"--rpctls", type=bool, default=False, help="RPC connection over TLS" |
||||
) |
||||
parser.add_argument("--epic", action="store_true", help=argparse.SUPPRESS) |
||||
|
||||
|
||||
def validate_args(parser: argparse.ArgumentParser, args: argparse.Namespace): |
||||
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.contract_hash_to_address |
||||
): |
||||
parser.print_help() |
||||
sys.exit() |
||||
|
||||
if args.v: |
||||
if 0 <= args.v < 6: |
||||
log_levels = [ |
||||
logging.NOTSET, |
||||
logging.CRITICAL, |
||||
logging.ERROR, |
||||
logging.WARNING, |
||||
logging.INFO, |
||||
logging.DEBUG, |
||||
] |
||||
coloredlogs.install( |
||||
fmt="%(name)s [%(levelname)s]: %(message)s", level=log_levels[args.v] |
||||
) |
||||
logging.getLogger("mythril").setLevel(log_levels[args.v]) |
||||
else: |
||||
exit_with_error( |
||||
args.outform, "Invalid -v value, you can find valid values in usage" |
||||
) |
||||
|
||||
if args.query_signature: |
||||
if sigs.ethereum_input_decoder is None: |
||||
exit_with_error( |
||||
args.outform, |
||||
"The --query-signature function requires the python package ethereum-input-decoder", |
||||
) |
||||
|
||||
if args.enable_iprof: |
||||
if args.v < 4: |
||||
exit_with_error( |
||||
args.outform, |
||||
"--enable-iprof must be used with -v LOG_LEVEL where LOG_LEVEL >= 4", |
||||
) |
||||
elif not (args.graph or args.fire_lasers or args.statespace_json): |
||||
exit_with_error( |
||||
args.outform, |
||||
"--enable-iprof must be used with one of -g, --graph, -x, --fire-lasers, -j and --statespace-json", |
||||
) |
||||
|
||||
|
||||
def quick_commands(args: argparse.Namespace): |
||||
if args.hash: |
||||
print(MythrilDisassembler.hash_for_function_signature(args.hash)) |
||||
sys.exit() |
||||
|
||||
|
||||
def set_config(args: argparse.Namespace): |
||||
config = MythrilConfig() |
||||
if args.dynld or not args.no_onchain_storage_access and not (args.rpc or args.i): |
||||
config.set_api_from_config_path() |
||||
|
||||
if args.address: |
||||
# Establish RPC connection if necessary |
||||
config.set_api_rpc(rpc=args.rpc, rpctls=args.rpctls) |
||||
elif args.search or args.contract_hash_to_address: |
||||
# Open LevelDB if necessary |
||||
config.set_api_leveldb( |
||||
config.leveldb_dir if not args.leveldb_dir else args.leveldb_dir |
||||
) |
||||
return config |
||||
|
||||
|
||||
def leveldb_search(config: MythrilConfig, args: argparse.Namespace): |
||||
if args.search or args.contract_hash_to_address: |
||||
leveldb_searcher = MythrilLevelDB(config.eth_db) |
||||
if args.search: |
||||
# Database search ops |
||||
leveldb_searcher.search_db(args.search) |
||||
|
||||
else: |
||||
# search corresponding address |
||||
try: |
||||
leveldb_searcher.contract_hash_to_address(args.contract_hash_to_address) |
||||
except AddressNotFoundError: |
||||
print("Address not found.") |
||||
|
||||
sys.exit() |
||||
|
||||
|
||||
def get_code(disassembler: MythrilDisassembler, args: argparse.Namespace): |
||||
address = None |
||||
if args.code: |
||||
# Load from bytecode |
||||
code = args.code[2:] if args.code.startswith("0x") else args.code |
||||
address, _ = disassembler.load_from_bytecode(code, args.bin_runtime) |
||||
elif args.codefile: |
||||
bytecode = "".join([l.strip() for l in args.codefile if len(l.strip()) > 0]) |
||||
bytecode = bytecode[2:] if bytecode.startswith("0x") else bytecode |
||||
address, _ = disassembler.load_from_bytecode(bytecode, args.bin_runtime) |
||||
elif args.address: |
||||
# Get bytecode from a contract address |
||||
address, _ = disassembler.load_from_address(args.address) |
||||
elif args.solidity_file: |
||||
# Compile Solidity source file(s) |
||||
if args.graph and len(args.solidity_file) > 1: |
||||
exit_with_error( |
||||
args.outform, |
||||
"Cannot generate call graphs from multiple input files. Please do it one at a time.", |
||||
) |
||||
address, _ = disassembler.load_from_solidity( |
||||
args.solidity_file |
||||
) # list of files |
||||
else: |
||||
exit_with_error( |
||||
args.outform, |
||||
"No input bytecode. Please provide EVM code via -c BYTECODE, -a ADDRESS, or -i SOLIDITY_FILES", |
||||
) |
||||
return address |
||||
|
||||
|
||||
def execute_command( |
||||
disassembler: MythrilDisassembler, |
||||
address: str, |
||||
parser: argparse.ArgumentParser, |
||||
args: argparse.Namespace, |
||||
): |
||||
|
||||
if args.storage: |
||||
if not args.address: |
||||
exit_with_error( |
||||
args.outform, |
||||
"To read storage, provide the address of a deployed contract with the -a option.", |
||||
) |
||||
|
||||
storage = disassembler.get_state_variable_from_storage( |
||||
address=address, params=[a.strip() for a in args.storage.strip().split(",")] |
||||
) |
||||
print(storage) |
||||
return |
||||
|
||||
analyzer = MythrilAnalyzer( |
||||
strategy=args.strategy, |
||||
disassembler=disassembler, |
||||
address=address, |
||||
max_depth=args.max_depth, |
||||
execution_timeout=args.execution_timeout, |
||||
loop_bound=args.loop_bound, |
||||
create_timeout=args.create_timeout, |
||||
enable_iprof=args.enable_iprof, |
||||
disable_dependency_pruning=args.disable_dependency_pruning, |
||||
onchain_storage_access=not args.no_onchain_storage_access, |
||||
solver_timeout=args.solver_timeout, |
||||
) |
||||
|
||||
if args.disassemble: |
||||
# or mythril.disassemble(mythril.contracts[0]) |
||||
|
||||
if disassembler.contracts[0].code: |
||||
print("Runtime Disassembly: \n" + disassembler.contracts[0].get_easm()) |
||||
if disassembler.contracts[0].creation_code: |
||||
print("Disassembly: \n" + disassembler.contracts[0].get_creation_easm()) |
||||
|
||||
elif args.graph or args.fire_lasers: |
||||
if not disassembler.contracts: |
||||
exit_with_error( |
||||
args.outform, "input files do not contain any valid contracts" |
||||
) |
||||
|
||||
if args.graph: |
||||
html = analyzer.graph_html( |
||||
contract=analyzer.contracts[0], |
||||
enable_physics=args.enable_physics, |
||||
phrackify=args.phrack, |
||||
transaction_count=args.transaction_count, |
||||
) |
||||
|
||||
try: |
||||
with open(args.graph, "w") as f: |
||||
f.write(html) |
||||
except Exception as e: |
||||
exit_with_error(args.outform, "Error saving graph: " + str(e)) |
||||
|
||||
else: |
||||
try: |
||||
report = analyzer.fire_lasers( |
||||
modules=[m.strip() for m in args.modules.strip().split(",")] |
||||
if args.modules |
||||
else [], |
||||
transaction_count=args.transaction_count, |
||||
) |
||||
outputs = { |
||||
"json": report.as_json(), |
||||
"jsonv2": report.as_swc_standard_format(), |
||||
"text": report.as_text(), |
||||
"markdown": report.as_markdown(), |
||||
} |
||||
print(outputs[args.outform]) |
||||
except ModuleNotFoundError as e: |
||||
exit_with_error( |
||||
args.outform, "Error loading analyis modules: " + format(e) |
||||
) |
||||
|
||||
elif args.statespace_json: |
||||
|
||||
if not analyzer.contracts: |
||||
exit_with_error( |
||||
args.outform, "input files do not contain any valid contracts" |
||||
) |
||||
|
||||
statespace = analyzer.dump_statespace(contract=analyzer.contracts[0]) |
||||
|
||||
try: |
||||
with open(args.statespace_json, "w") as f: |
||||
json.dump(statespace, f) |
||||
except Exception as e: |
||||
exit_with_error(args.outform, "Error saving json: " + str(e)) |
||||
|
||||
else: |
||||
parser.print_help() |
||||
|
||||
|
||||
def parse_args(parser: argparse.ArgumentParser, args: argparse.Namespace) -> None: |
||||
""" |
||||
Parses the arguments |
||||
:param parser: The parser |
||||
:param args: The args |
||||
""" |
||||
|
||||
if args.epic: |
||||
path = os.path.dirname(os.path.realpath(__file__)) |
||||
sys.argv.remove("--epic") |
||||
os.system(" ".join(sys.argv) + " | python3 " + path + "/epic.py") |
||||
sys.exit() |
||||
|
||||
if args.version: |
||||
if args.outform == "json": |
||||
print(json.dumps({"version_str": VERSION})) |
||||
else: |
||||
print("Mythril version {}".format(VERSION)) |
||||
sys.exit() |
||||
|
||||
# Parse cmdline args |
||||
validate_args(parser, args) |
||||
try: |
||||
quick_commands(args) |
||||
config = set_config(args) |
||||
leveldb_search(config, args) |
||||
disassembler = MythrilDisassembler( |
||||
eth=config.eth, |
||||
solc_version=args.solv, |
||||
solc_settings_json=args.solc_json, |
||||
enable_online_lookup=args.query_signature, |
||||
) |
||||
if args.truffle: |
||||
try: |
||||
disassembler.analyze_truffle_project(args) |
||||
except FileNotFoundError: |
||||
print( |
||||
"Build directory not found. Make sure that you start the analysis from the project root, and that 'truffle compile' has executed successfully." |
||||
) |
||||
sys.exit() |
||||
|
||||
address = get_code(disassembler, args) |
||||
execute_command( |
||||
disassembler=disassembler, address=address, parser=parser, args=args |
||||
) |
||||
except CriticalError as ce: |
||||
exit_with_error(args.outform, str(ce)) |
||||
except Exception: |
||||
exit_with_error(args.outform, traceback.format_exc()) |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
main() |
@ -1,194 +0,0 @@ |
||||
"""This module contains functions for dynamic gas calculation and a gas cost |
||||
table.""" |
||||
from ethereum import opcodes |
||||
from ethereum.utils import ceil32 |
||||
from typing import Callable, Dict, Tuple, Union |
||||
|
||||
|
||||
def calculate_native_gas(size: int, contract: str): |
||||
""" |
||||
|
||||
:param size: |
||||
:param contract: |
||||
:return: |
||||
""" |
||||
gas_value = None |
||||
word_num = ceil32(size) // 32 |
||||
if contract == "ecrecover": |
||||
gas_value = opcodes.GECRECOVER |
||||
elif contract == "sha256": |
||||
gas_value = opcodes.GSHA256BASE + word_num * opcodes.GSHA256WORD |
||||
elif contract == "ripemd160": |
||||
gas_value = opcodes.GRIPEMD160BASE + word_num * opcodes.GRIPEMD160WORD |
||||
elif contract == "identity": |
||||
gas_value = opcodes.GIDENTITYBASE + word_num * opcodes.GIDENTITYWORD |
||||
else: |
||||
raise ValueError("Unknown contract type {}".format(contract)) |
||||
return gas_value, gas_value |
||||
|
||||
|
||||
def calculate_sha3_gas(length: int): |
||||
""" |
||||
|
||||
:param length: |
||||
:return: |
||||
""" |
||||
gas_val = 30 + opcodes.GSHA3WORD * (ceil32(length) // 32) |
||||
return gas_val, gas_val |
||||
|
||||
|
||||
# opcode -> (min_gas, max_gas) |
||||
OPCODE_GAS = { |
||||
"STOP": (0, 0), |
||||
"ADD": (3, 3), |
||||
"MUL": (5, 5), |
||||
"SUB": (3, 3), |
||||
"DIV": (5, 5), |
||||
"SDIV": (5, 5), |
||||
"MOD": (5, 5), |
||||
"SMOD": (5, 5), |
||||
"ADDMOD": (8, 8), |
||||
"MULMOD": (8, 8), |
||||
"EXP": (10, 340), # exponent max 2^32 |
||||
"SIGNEXTEND": (5, 5), |
||||
"LT": (3, 3), |
||||
"GT": (3, 3), |
||||
"SLT": (3, 3), |
||||
"SGT": (3, 3), |
||||
"EQ": (3, 3), |
||||
"ISZERO": (3, 3), |
||||
"AND": (3, 3), |
||||
"OR": (3, 3), |
||||
"XOR": (3, 3), |
||||
"NOT": (3, 3), |
||||
"BYTE": (3, 3), |
||||
"SHL": (3, 3), |
||||
"SHR": (3, 3), |
||||
"SAR": (3, 3), |
||||
"SHA3": ( |
||||
30, |
||||
30 + 6 * 8, |
||||
), # max can be larger, but usually storage location with 8 words |
||||
"SHA3_FUNC": calculate_sha3_gas, |
||||
"ADDRESS": (2, 2), |
||||
"BALANCE": (400, 400), |
||||
"ORIGIN": (2, 2), |
||||
"CALLER": (2, 2), |
||||
"CALLVALUE": (2, 2), |
||||
"CALLDATALOAD": (3, 3), |
||||
"CALLDATASIZE": (2, 2), |
||||
"CALLDATACOPY": (2, 2 + 3 * 768), # https://ethereum.stackexchange.com/a/47556 |
||||
"CODESIZE": (2, 2), |
||||
"CODECOPY": (2, 2 + 3 * 768), # https://ethereum.stackexchange.com/a/47556, |
||||
"GASPRICE": (2, 2), |
||||
"EXTCODESIZE": (700, 700), |
||||
"EXTCODECOPY": (700, 700 + 3 * 768), # https://ethereum.stackexchange.com/a/47556 |
||||
"EXTCODEHASH": (400, 400), |
||||
"RETURNDATASIZE": (2, 2), |
||||
"RETURNDATACOPY": (3, 3), |
||||
"BLOCKHASH": (20, 20), |
||||
"COINBASE": (2, 2), |
||||
"TIMESTAMP": (2, 2), |
||||
"NUMBER": (2, 2), |
||||
"DIFFICULTY": (2, 2), |
||||
"GASLIMIT": (2, 2), |
||||
"POP": (2, 2), |
||||
# assume 1KB memory r/w cost as upper bound |
||||
"MLOAD": (3, 96), |
||||
"MSTORE": (3, 98), |
||||
"MSTORE8": (3, 98), |
||||
# assume 64 byte r/w cost as upper bound |
||||
"SLOAD": (400, 400), |
||||
"SSTORE": (5000, 25000), |
||||
"JUMP": (8, 8), |
||||
"JUMPI": (10, 10), |
||||
"PC": (2, 2), |
||||
"MSIZE": (2, 2), |
||||
"GAS": (2, 2), |
||||
"JUMPDEST": (1, 1), |
||||
"PUSH1": (3, 3), |
||||
"PUSH2": (3, 3), |
||||
"PUSH3": (3, 3), |
||||
"PUSH4": (3, 3), |
||||
"PUSH5": (3, 3), |
||||
"PUSH6": (3, 3), |
||||
"PUSH7": (3, 3), |
||||
"PUSH8": (3, 3), |
||||
"PUSH9": (3, 3), |
||||
"PUSH10": (3, 3), |
||||
"PUSH11": (3, 3), |
||||
"PUSH12": (3, 3), |
||||
"PUSH13": (3, 3), |
||||
"PUSH14": (3, 3), |
||||
"PUSH15": (3, 3), |
||||
"PUSH16": (3, 3), |
||||
"PUSH17": (3, 3), |
||||
"PUSH18": (3, 3), |
||||
"PUSH19": (3, 3), |
||||
"PUSH20": (3, 3), |
||||
"PUSH21": (3, 3), |
||||
"PUSH22": (3, 3), |
||||
"PUSH23": (3, 3), |
||||
"PUSH24": (3, 3), |
||||
"PUSH25": (3, 3), |
||||
"PUSH26": (3, 3), |
||||
"PUSH27": (3, 3), |
||||
"PUSH28": (3, 3), |
||||
"PUSH29": (3, 3), |
||||
"PUSH30": (3, 3), |
||||
"PUSH31": (3, 3), |
||||
"PUSH32": (3, 3), |
||||
"DUP1": (3, 3), |
||||
"DUP2": (3, 3), |
||||
"DUP3": (3, 3), |
||||
"DUP4": (3, 3), |
||||
"DUP5": (3, 3), |
||||
"DUP6": (3, 3), |
||||
"DUP7": (3, 3), |
||||
"DUP8": (3, 3), |
||||
"DUP9": (3, 3), |
||||
"DUP10": (3, 3), |
||||
"DUP11": (3, 3), |
||||
"DUP12": (3, 3), |
||||
"DUP13": (3, 3), |
||||
"DUP14": (3, 3), |
||||
"DUP15": (3, 3), |
||||
"DUP16": (3, 3), |
||||
"SWAP1": (3, 3), |
||||
"SWAP2": (3, 3), |
||||
"SWAP3": (3, 3), |
||||
"SWAP4": (3, 3), |
||||
"SWAP5": (3, 3), |
||||
"SWAP6": (3, 3), |
||||
"SWAP7": (3, 3), |
||||
"SWAP8": (3, 3), |
||||
"SWAP9": (3, 3), |
||||
"SWAP10": (3, 3), |
||||
"SWAP11": (3, 3), |
||||
"SWAP12": (3, 3), |
||||
"SWAP13": (3, 3), |
||||
"SWAP14": (3, 3), |
||||
"SWAP15": (3, 3), |
||||
"SWAP16": (3, 3), |
||||
# apparently Solidity only allows byte32 as input to the log |
||||
# function. Virtually it could be as large as the block gas limit |
||||
# allows, but let's stick to the reasonable standard here. |
||||
# https://ethereum.stackexchange.com/a/1691 |
||||
"LOG0": (375, 375 + 8 * 32), |
||||
"LOG1": (2 * 375, 2 * 375 + 8 * 32), |
||||
"LOG2": (3 * 375, 3 * 375 + 8 * 32), |
||||
"LOG3": (4 * 375, 4 * 375 + 8 * 32), |
||||
"LOG4": (5 * 375, 5 * 375 + 8 * 32), |
||||
"CREATE": (32000, 32000), |
||||
"CREATE2": (32000, 32000), # TODO: Make the gas values dynamic |
||||
"CALL": (700, 700 + 9000 + 25000), |
||||
"NATIVE_COST": calculate_native_gas, |
||||
"CALLCODE": (700, 700 + 9000 + 25000), |
||||
"RETURN": (0, 0), |
||||
"DELEGATECALL": (700, 700 + 9000 + 25000), |
||||
"STATICCALL": (700, 700 + 9000 + 25000), |
||||
"REVERT": (0, 0), |
||||
"SUICIDE": (5000, 30000), |
||||
"ASSERT_FAIL": (0, 0), |
||||
"INVALID": (0, 0), |
||||
} # type: Dict[str, Union[Tuple[int, int], Callable]] |
@ -0,0 +1,222 @@ |
||||
from ethereum import opcodes |
||||
from ethereum.utils import ceil32 |
||||
from typing import Callable, Dict, Tuple, Union |
||||
|
||||
|
||||
Z_OPERATOR_TUPLE = (0, 1) |
||||
UN_OPERATOR_TUPLE = (1, 1) |
||||
BIN_OPERATOR_TUPLE = (2, 1) |
||||
T_OPERATOR_TUPLE = (3, 1) |
||||
GAS = "gas" |
||||
STACK = "stack" |
||||
|
||||
# Gas tuple contains (min_gas, max_gas) |
||||
# stack tuple contains (no_of_elements_popped, no_of_elements_pushed) |
||||
|
||||
OPCODES = { |
||||
"STOP": {GAS: (0, 0), STACK: (0, 0)}, |
||||
"ADD": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, |
||||
"MUL": {GAS: (5, 5), STACK: BIN_OPERATOR_TUPLE}, |
||||
"SUB": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, |
||||
"DIV": {GAS: (5, 5), STACK: BIN_OPERATOR_TUPLE}, |
||||
"SDIV": {GAS: (5, 5), STACK: BIN_OPERATOR_TUPLE}, |
||||
"MOD": {GAS: (5, 5), STACK: BIN_OPERATOR_TUPLE}, |
||||
"SMOD": {GAS: (5, 5), STACK: BIN_OPERATOR_TUPLE}, |
||||
"ADDMOD": {GAS: (8, 8), STACK: BIN_OPERATOR_TUPLE}, |
||||
"MULMOD": {GAS: (8, 8), STACK: T_OPERATOR_TUPLE}, |
||||
"EXP": {GAS: (10, 340), STACK: BIN_OPERATOR_TUPLE}, # exponent max 2^32 |
||||
"SIGNEXTEND": {GAS: (5, 5), STACK: BIN_OPERATOR_TUPLE}, |
||||
"LT": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, |
||||
"GT": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, |
||||
"SLT": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, |
||||
"SGT": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, |
||||
"EQ": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, |
||||
"AND": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, |
||||
"ISZERO": {GAS: (3, 3), STACK: UN_OPERATOR_TUPLE}, |
||||
"OR": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, |
||||
"XOR": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, |
||||
"NOT": {GAS: (3, 3), STACK: UN_OPERATOR_TUPLE}, |
||||
"BYTE": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, |
||||
"SHL": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, |
||||
"SHR": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, |
||||
"SAR": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, |
||||
"SHA3": { |
||||
GAS: ( |
||||
30, |
||||
30 + 6 * 8, |
||||
), # max can be larger, but usually storage location with 8 words |
||||
STACK: BIN_OPERATOR_TUPLE, |
||||
}, |
||||
"ADDRESS": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"BALANCE": {GAS: (400, 400), STACK: UN_OPERATOR_TUPLE}, |
||||
"ORIGIN": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"CALLER": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"CALLVALUE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"CALLDATALOAD": {GAS: (3, 3), STACK: UN_OPERATOR_TUPLE}, |
||||
"CALLDATASIZE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"CALLDATACOPY": { |
||||
GAS: (2, 2 + 3 * 768), # https://ethereum.stackexchange.com/a/47556 |
||||
STACK: (3, 0), |
||||
}, |
||||
"CODESIZE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"CODECOPY": { |
||||
GAS: (2, 2 + 3 * 768), # https://ethereum.stackexchange.com/a/47556, |
||||
STACK: (3, 0), |
||||
}, |
||||
"GASPRICE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"EXTCODESIZE": {GAS: (700, 700), STACK: Z_OPERATOR_TUPLE}, |
||||
"EXTCODECOPY": { |
||||
GAS: (700, 700 + 3 * 768), # https://ethereum.stackexchange.com/a/47556 |
||||
STACK: (4, 0), |
||||
}, |
||||
"EXTCODEHASH": {GAS: (400, 400), STACK: UN_OPERATOR_TUPLE}, |
||||
"RETURNDATASIZE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"RETURNDATACOPY": {GAS: (3, 3), STACK: (3, 0)}, |
||||
"BLOCKHASH": {GAS: (20, 20), STACK: UN_OPERATOR_TUPLE}, |
||||
"COINBASE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"TIMESTAMP": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"NUMBER": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"DIFFICULTY": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"GASLIMIT": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"POP": {GAS: (2, 2), STACK: (1, 0)}, |
||||
# assume 1KB memory r/w cost as upper bound |
||||
"MLOAD": {GAS: (3, 96), STACK: UN_OPERATOR_TUPLE}, |
||||
"MSTORE": {GAS: (3, 98), STACK: (2, 0)}, |
||||
"MSTORE8": {GAS: (3, 98), STACK: (2, 0)}, |
||||
# assume 64 byte r/w cost as upper bound |
||||
"SLOAD": {GAS: (400, 400), STACK: UN_OPERATOR_TUPLE}, |
||||
"SSTORE": {GAS: (5000, 25000), STACK: (1, 0)}, |
||||
"JUMP": {GAS: (8, 8), STACK: (1, 0)}, |
||||
"JUMPI": {GAS: (10, 10), STACK: (2, 0)}, |
||||
"PC": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"MSIZE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"GAS": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"JUMPDEST": {GAS: (1, 1), STACK: (0, 0)}, |
||||
"PUSH1": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH2": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH3": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH4": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH5": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH6": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH7": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH8": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH9": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH10": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH11": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH12": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH13": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH14": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH15": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH16": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH17": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH18": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH19": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH20": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH21": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH22": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH23": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH24": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH25": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH26": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH27": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH28": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH29": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH30": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH31": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH32": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP1": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP2": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP3": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP4": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP5": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP6": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP7": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP8": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP9": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP10": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP11": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP12": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP13": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP14": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP15": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP16": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"SWAP1": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP2": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP3": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP4": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP5": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP6": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP7": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP8": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP9": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP10": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP11": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP12": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP13": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP14": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP15": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP16": {GAS: (3, 3), STACK: (0, 0)}, |
||||
# apparently Solidity only allows byte32 as input to the log |
||||
# function. Virtually it could be as large as the block gas limit |
||||
# allows, but let's stick to the reasonable standard here. |
||||
# https://ethereum.stackexchange.com/a/1691 |
||||
"LOG0": {GAS: (375, 375 + 8 * 32), STACK: (2, 0)}, |
||||
"LOG1": {GAS: (2 * 375, 2 * 375 + 8 * 32), STACK: (3, 0)}, |
||||
"LOG2": {GAS: (3 * 375, 3 * 375 + 8 * 32), STACK: (4, 0)}, |
||||
"LOG3": {GAS: (4 * 375, 4 * 375 + 8 * 32), STACK: (5, 0)}, |
||||
"LOG4": {GAS: (5 * 375, 5 * 375 + 8 * 32), STACK: (6, 0)}, |
||||
"CREATE": {GAS: (32000, 32000), STACK: T_OPERATOR_TUPLE}, |
||||
"CREATE2": { |
||||
GAS: (32000, 32000), # TODO: Make the gas values dynamic |
||||
STACK: (4, 1), |
||||
}, |
||||
"CALL": {GAS: (700, 700 + 9000 + 25000), STACK: (7, 1)}, |
||||
"CALLCODE": {GAS: (700, 700 + 9000 + 25000), STACK: (7, 1)}, |
||||
"RETURN": {GAS: (0, 0), STACK: (2, 0)}, |
||||
"DELEGATECALL": {GAS: (700, 700 + 9000 + 25000), STACK: (6, 1)}, |
||||
"STATICCALL": {GAS: (700, 700 + 9000 + 25000), STACK: (6, 1)}, |
||||
"REVERT": {GAS: (0, 0), STACK: (2, 0)}, |
||||
"SUICIDE": {GAS: (5000, 30000), STACK: (1, 0)}, |
||||
"ASSERT_FAIL": {GAS: (0, 0), STACK: (0, 0)}, |
||||
"INVALID": {GAS: (0, 0), STACK: (0, 0)}, |
||||
} # type: Dict[str, Dict[str, Tuple[int, int]]] |
||||
|
||||
|
||||
def calculate_sha3_gas(length: int): |
||||
""" |
||||
|
||||
:param length: |
||||
:return: |
||||
""" |
||||
gas_val = 30 + opcodes.GSHA3WORD * (ceil32(length) // 32) |
||||
return gas_val, gas_val |
||||
|
||||
|
||||
def calculate_native_gas(size: int, contract: str): |
||||
""" |
||||
|
||||
:param size: |
||||
:param contract: |
||||
:return: |
||||
""" |
||||
gas_value = None |
||||
word_num = ceil32(size) // 32 |
||||
if contract == "ecrecover": |
||||
gas_value = opcodes.GECRECOVER |
||||
elif contract == "sha256": |
||||
gas_value = opcodes.GSHA256BASE + word_num * opcodes.GSHA256WORD |
||||
elif contract == "ripemd160": |
||||
gas_value = opcodes.GRIPEMD160BASE + word_num * opcodes.GRIPEMD160WORD |
||||
elif contract == "identity": |
||||
gas_value = opcodes.GIDENTITYBASE + word_num * opcodes.GIDENTITYWORD |
||||
else: |
||||
raise ValueError("Unknown contract type {}".format(contract)) |
||||
return gas_value, gas_value |
||||
|
||||
|
||||
def get_opcode_gas(opcode: str) -> Tuple[int, int]: |
||||
return OPCODES[opcode][GAS] |
||||
|
||||
|
||||
def get_required_stack_elements(opcode: str) -> int: |
||||
return OPCODES[opcode][STACK][0] |
@ -0,0 +1,138 @@ |
||||
from ethereum import utils |
||||
from mythril.laser.smt import ( |
||||
BitVec, |
||||
Function, |
||||
URem, |
||||
symbol_factory, |
||||
ULE, |
||||
And, |
||||
ULT, |
||||
Bool, |
||||
Or, |
||||
) |
||||
from typing import Dict, Tuple, List, Optional |
||||
|
||||
TOTAL_PARTS = 10 ** 40 |
||||
PART = (2 ** 256 - 1) // TOTAL_PARTS |
||||
INTERVAL_DIFFERENCE = 10 ** 30 |
||||
hash_matcher = "fffffff" # This is usually the prefix for the hash in the output |
||||
|
||||
|
||||
class KeccakFunctionManager: |
||||
""" |
||||
A bunch of uninterpreted functions are considered like keccak256_160 ,... |
||||
where keccak256_160 means the input of keccak256() is 160 bit number. |
||||
the range of these functions are constrained to some mutually disjoint intervals |
||||
All the hashes modulo 64 are 0 as we need a spread among hashes for array type data structures |
||||
All the functions are kind of one to one due to constraint of the existence of inverse |
||||
for each encountered input. |
||||
For more info https://files.sri.inf.ethz.ch/website/papers/sp20-verx.pdf |
||||
""" |
||||
|
||||
def __init__(self): |
||||
self.store_function = {} # type: Dict[int, Tuple[Function, Function]] |
||||
self.interval_hook_for_size = {} # type: Dict[int, int] |
||||
self._index_counter = TOTAL_PARTS - 34534 |
||||
self.hash_result_store = {} # type: Dict[int, List[BitVec]] |
||||
self.quick_inverse = {} # type: Dict[BitVec, BitVec] # This is for VMTests |
||||
|
||||
@staticmethod |
||||
def find_concrete_keccak(data: BitVec) -> BitVec: |
||||
""" |
||||
Calculates concrete keccak |
||||
:param data: input bitvecval |
||||
:return: concrete keccak output |
||||
""" |
||||
keccak = symbol_factory.BitVecVal( |
||||
int.from_bytes( |
||||
utils.sha3(data.value.to_bytes(data.size() // 8, byteorder="big")), |
||||
"big", |
||||
), |
||||
256, |
||||
) |
||||
return keccak |
||||
|
||||
def get_function(self, length: int) -> Tuple[Function, Function]: |
||||
""" |
||||
Returns the keccak functions for the corresponding length |
||||
:param length: input size |
||||
:return: tuple of keccak and it's inverse |
||||
""" |
||||
try: |
||||
func, inverse = self.store_function[length] |
||||
except KeyError: |
||||
func = Function("keccak256_{}".format(length), length, 256) |
||||
inverse = Function("keccak256_{}-1".format(length), 256, length) |
||||
self.store_function[length] = (func, inverse) |
||||
self.hash_result_store[length] = [] |
||||
return func, inverse |
||||
|
||||
@staticmethod |
||||
def get_empty_keccak_hash() -> BitVec: |
||||
""" |
||||
returns sha3("") |
||||
:return: |
||||
""" |
||||
val = 89477152217924674838424037953991966239322087453347756267410168184682657981552 |
||||
return symbol_factory.BitVecVal(val, 256) |
||||
|
||||
def create_keccak(self, data: BitVec) -> Tuple[BitVec, Bool]: |
||||
""" |
||||
Creates Keccak of the data |
||||
:param data: input |
||||
:return: Tuple of keccak and the condition it should satisfy |
||||
""" |
||||
length = data.size() |
||||
func, inverse = self.get_function(length) |
||||
|
||||
condition = self._create_condition(func_input=data) |
||||
self.quick_inverse[func(data)] = data |
||||
self.hash_result_store[length].append(func(data)) |
||||
return func(data), condition |
||||
|
||||
def get_concrete_hash_data(self, model) -> Dict[int, List[Optional[int]]]: |
||||
""" |
||||
returns concrete values of hashes in the self.hash_result_store |
||||
:param model: The z3 model to query for concrete values |
||||
:return: A dictionary with concrete hashes { <hash_input_size> : [<concrete_hash>, <concrete_hash>]} |
||||
""" |
||||
concrete_hashes = {} # type: Dict[int, List[Optional[int]]] |
||||
for size in self.hash_result_store: |
||||
concrete_hashes[size] = [] |
||||
for val in self.hash_result_store[size]: |
||||
eval_ = model.eval(val.raw) |
||||
try: |
||||
concrete_val = eval_.as_long() |
||||
concrete_hashes[size].append(concrete_val) |
||||
except AttributeError: |
||||
continue |
||||
return concrete_hashes |
||||
|
||||
def _create_condition(self, func_input: BitVec) -> Bool: |
||||
""" |
||||
Creates the constraints for hash |
||||
:param func_input: input of the hash |
||||
:return: condition |
||||
""" |
||||
length = func_input.size() |
||||
func, inv = self.get_function(length) |
||||
try: |
||||
index = self.interval_hook_for_size[length] |
||||
except KeyError: |
||||
self.interval_hook_for_size[length] = self._index_counter |
||||
index = self._index_counter |
||||
self._index_counter -= INTERVAL_DIFFERENCE |
||||
|
||||
lower_bound = index * PART |
||||
upper_bound = lower_bound + PART |
||||
|
||||
cond = And( |
||||
inv(func(func_input)) == func_input, |
||||
ULE(symbol_factory.BitVecVal(lower_bound, 256), func(func_input)), |
||||
ULT(func(func_input), symbol_factory.BitVecVal(upper_bound, 256)), |
||||
URem(func(func_input), symbol_factory.BitVecVal(64, 256)) == 0, |
||||
) |
||||
return cond |
||||
|
||||
|
||||
keccak_function_manager = KeccakFunctionManager() |
@ -0,0 +1,25 @@ |
||||
from typing import cast |
||||
import z3 |
||||
|
||||
from mythril.laser.smt.bitvec import BitVec |
||||
|
||||
|
||||
class Function: |
||||
"""An uninterpreted function.""" |
||||
|
||||
def __init__(self, name: str, domain: int, value_range: int): |
||||
"""Initializes an uninterpreted function. |
||||
|
||||
:param name: Name of the Function |
||||
:param domain: The domain for the Function (10 -> all the values that a bv of size 10 could take) |
||||
:param value_range: The range for the values of the function (10 -> all the values that a bv of size 10 could take) |
||||
""" |
||||
self.domain = z3.BitVecSort(domain) |
||||
self.range = z3.BitVecSort(value_range) |
||||
self.raw = z3.Function(name, self.domain, self.range) |
||||
|
||||
def __call__(self, item: BitVec) -> BitVec: |
||||
"""Function accessor, item can be symbolic.""" |
||||
return BitVec( |
||||
cast(z3.BitVecRef, self.raw(item.raw)), annotations=item.annotations |
||||
) |
@ -1,205 +0,0 @@ |
||||
"""This module contains functionality used to easily analyse Truffle |
||||
projects.""" |
||||
import json |
||||
import logging |
||||
import os |
||||
import re |
||||
import sys |
||||
import warnings |
||||
from pathlib import PurePath |
||||
|
||||
from ethereum.utils import sha3 |
||||
|
||||
from mythril.analysis.report import Report |
||||
from mythril.analysis.security import fire_lasers |
||||
from mythril.analysis.symbolic import SymExecWrapper |
||||
from mythril.ethereum import util |
||||
from mythril.ethereum.evmcontract import EVMContract |
||||
from mythril.laser.ethereum.util import get_instruction_index |
||||
from mythril.solidity.soliditycontract import SourceMapping |
||||
|
||||
log = logging.getLogger(__name__) |
||||
|
||||
|
||||
def format_Warning(message, category, filename, lineno, line=""): |
||||
return "{}: {}\n\n".format(str(filename), str(message)) |
||||
|
||||
|
||||
warnings.formatwarning = format_Warning |
||||
|
||||
|
||||
def analyze_truffle_project(sigs, args): |
||||
""" |
||||
|
||||
:param sigs: |
||||
:param args: |
||||
""" |
||||
warnings.warn( |
||||
"The option --truffle is being deprecated, Please use the truffle-security plugin, https://github.com/ConsenSys/truffle-security", |
||||
FutureWarning, |
||||
) |
||||
|
||||
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"] |
||||
filename = PurePath(contractdata["sourcePath"]).name |
||||
except KeyError: |
||||
print( |
||||
"Unable to parse contract data. Please use Truffle 4 to compile your project." |
||||
) |
||||
sys.exit() |
||||
if len(bytecode) < 4: |
||||
continue |
||||
get_sigs_from_truffle(sigs, contractdata) |
||||
|
||||
ethcontract = EVMContract(bytecode, name=name) |
||||
|
||||
address = util.get_indexed_address(0) |
||||
sym = SymExecWrapper( |
||||
ethcontract, |
||||
address, |
||||
args.strategy, |
||||
max_depth=args.max_depth, |
||||
create_timeout=args.create_timeout, |
||||
execution_timeout=args.execution_timeout, |
||||
transaction_count=args.transaction_count, |
||||
modules=args.modules or [], |
||||
compulsory_statespace=False, |
||||
) |
||||
issues = fire_lasers(sym) |
||||
|
||||
if not len(issues): |
||||
if args.outform == "text" or args.outform == "markdown": |
||||
print("# Analysis result for " + name + "\n\nNo issues found.") |
||||
else: |
||||
result = { |
||||
"contract": name, |
||||
"result": {"success": True, "error": None, "issues": []}, |
||||
} |
||||
print(json.dumps(result)) |
||||
else: |
||||
|
||||
report = Report() |
||||
# augment with source code |
||||
|
||||
deployed_disassembly = ethcontract.disassembly |
||||
constructor_disassembly = ethcontract.creation_disassembly |
||||
|
||||
source = contractdata["source"] |
||||
|
||||
deployed_source_map = contractdata["deployedSourceMap"].split(";") |
||||
source_map = contractdata["sourceMap"].split(";") |
||||
|
||||
deployed_mappings = get_mappings(source, deployed_source_map) |
||||
constructor_mappings = get_mappings(source, source_map) |
||||
|
||||
for issue in issues: |
||||
if issue.function == "constructor": |
||||
mappings = constructor_mappings |
||||
disassembly = constructor_disassembly |
||||
else: |
||||
mappings = deployed_mappings |
||||
disassembly = deployed_disassembly |
||||
|
||||
index = get_instruction_index( |
||||
disassembly.instruction_list, issue.address |
||||
) |
||||
|
||||
if index: |
||||
try: |
||||
offset = mappings[index].offset |
||||
length = mappings[index].length |
||||
|
||||
issue.filename = filename |
||||
issue.code = source.encode("utf-8")[ |
||||
offset : offset + length |
||||
].decode("utf-8") |
||||
issue.lineno = mappings[index].lineno |
||||
except IndexError: |
||||
log.debug("No code mapping at index %d", index) |
||||
|
||||
report.append_issue(issue) |
||||
|
||||
if args.outform == "json": |
||||
|
||||
result = { |
||||
"contract": name, |
||||
"result": { |
||||
"success": True, |
||||
"error": None, |
||||
"issues": list(map(lambda x: x.as_dict, issues)), |
||||
}, |
||||
} |
||||
print(json.dumps(result)) |
||||
|
||||
else: |
||||
if args.outform == "text": |
||||
print( |
||||
"# Analysis result for " + name + ":\n\n" + report.as_text() |
||||
) |
||||
elif args.outform == "markdown": |
||||
print(report.as_markdown()) |
||||
|
||||
|
||||
def get_sigs_from_truffle(sigs, contract_data): |
||||
""" |
||||
|
||||
:param sigs: |
||||
:param contract_data: |
||||
""" |
||||
abis = contract_data["abi"] |
||||
for abi in abis: |
||||
if abi["type"] != "function": |
||||
continue |
||||
function_name = abi["name"] |
||||
list_of_args = ",".join([input["type"] for input in abi["inputs"]]) |
||||
signature = function_name + "(" + list_of_args + ")" |
||||
sigs.add("0x" + sha3(signature)[:4].hex(), signature) |
||||
|
||||
|
||||
def get_mappings(source, deployed_source_map): |
||||
""" |
||||
|
||||
:param source: |
||||
:param deployed_source_map: |
||||
:return: |
||||
""" |
||||
mappings = [] |
||||
prev_item = "" |
||||
for item in deployed_source_map: |
||||
if item == "": |
||||
item = prev_item |
||||
|
||||
mapping = item.split(":") |
||||
|
||||
if len(mapping) > 0 and len(mapping[0]) > 0: |
||||
offset = int(mapping[0]) |
||||
|
||||
if len(mapping) > 1 and len(mapping[1]) > 0: |
||||
length = int(mapping[1]) |
||||
|
||||
if len(mapping) > 2 and len(mapping[2]) > 0: |
||||
idx = int(mapping[2]) |
||||
|
||||
if idx == -1: |
||||
lineno = None |
||||
else: |
||||
lineno = source.encode("utf-8")[0:offset].count("\n".encode("utf-8")) + 1 |
||||
prev_item = item |
||||
|
||||
mappings.append(SourceMapping(idx, offset, length, lineno, item)) |
||||
|
||||
return mappings |
@ -0,0 +1,57 @@ |
||||
from mythril.laser.smt import symbol_factory |
||||
from mythril.disassembler.disassembly import Disassembly |
||||
from mythril.laser.ethereum.state.environment import Environment |
||||
from mythril.laser.ethereum.state.machine_state import MachineState |
||||
from mythril.laser.ethereum.state.global_state import GlobalState |
||||
from mythril.laser.ethereum.state.world_state import WorldState |
||||
from mythril.laser.ethereum.instructions import Instruction |
||||
from mythril.laser.ethereum.transaction.transaction_models import MessageCallTransaction |
||||
|
||||
|
||||
def test_extcodecopy(): |
||||
# Arrange |
||||
new_world_state = WorldState() |
||||
new_account = new_world_state.create_account(balance=10, address=101) |
||||
new_account.code = Disassembly("60616240") |
||||
ext_account = new_world_state.create_account(balance=1000, address=121) |
||||
ext_account.code = Disassembly("6040404040") |
||||
|
||||
new_environment = Environment(new_account, None, None, None, None, None) |
||||
state = GlobalState( |
||||
new_world_state, new_environment, None, MachineState(gas_limit=8000000) |
||||
) |
||||
state.transaction_stack.append( |
||||
(MessageCallTransaction(world_state=WorldState(), gas_limit=8000000), None) |
||||
) |
||||
|
||||
state.mstate.stack = [3, 0, 0, 121] |
||||
instruction = Instruction("extcodecopy", dynamic_loader=None) |
||||
|
||||
# Act |
||||
new_state = instruction.evaluate(state)[0] |
||||
# Assert |
||||
assert new_state.mstate.memory[0:3] == [96, 64, 64] |
||||
|
||||
|
||||
def test_extcodecopy_fail(): |
||||
# Arrange |
||||
new_world_state = WorldState() |
||||
new_account = new_world_state.create_account(balance=10, address=101) |
||||
new_account.code = Disassembly("60616240") |
||||
new_environment = Environment(new_account, None, None, None, None, None) |
||||
state = GlobalState( |
||||
new_world_state, new_environment, None, MachineState(gas_limit=8000000) |
||||
) |
||||
state.transaction_stack.append( |
||||
(MessageCallTransaction(world_state=WorldState(), gas_limit=8000000), None) |
||||
) |
||||
|
||||
state.mstate.stack = [2, 2, 2, symbol_factory.BitVecSym("FAIL", 256)] |
||||
instruction = Instruction("extcodecopy", dynamic_loader=None) |
||||
|
||||
# Act |
||||
new_state = instruction.evaluate(state)[0] |
||||
|
||||
# Assert |
||||
assert new_state.mstate.stack == [] |
||||
assert new_state.mstate.memory._memory == state.mstate.memory._memory |
@ -0,0 +1,138 @@ |
||||
from mythril.laser.smt import Solver, symbol_factory, And |
||||
from mythril.laser.ethereum.keccak_function_manager import keccak_function_manager |
||||
import z3 |
||||
import pytest |
||||
|
||||
|
||||
@pytest.mark.parametrize( |
||||
"input1, input2, expected", |
||||
[ |
||||
(symbol_factory.BitVecVal(100, 8), symbol_factory.BitVecVal(101, 8), z3.unsat), |
||||
(symbol_factory.BitVecVal(100, 8), symbol_factory.BitVecVal(100, 16), z3.unsat), |
||||
(symbol_factory.BitVecVal(100, 8), symbol_factory.BitVecVal(100, 8), z3.sat), |
||||
( |
||||
symbol_factory.BitVecSym("N1", 256), |
||||
symbol_factory.BitVecSym("N2", 256), |
||||
z3.sat, |
||||
), |
||||
( |
||||
symbol_factory.BitVecVal(100, 256), |
||||
symbol_factory.BitVecSym("N1", 256), |
||||
z3.sat, |
||||
), |
||||
( |
||||
symbol_factory.BitVecVal(100, 8), |
||||
symbol_factory.BitVecSym("N1", 256), |
||||
z3.unsat, |
||||
), |
||||
], |
||||
) |
||||
def test_keccak_basic(input1, input2, expected): |
||||
s = Solver() |
||||
|
||||
o1, c1 = keccak_function_manager.create_keccak(input1) |
||||
o2, c2 = keccak_function_manager.create_keccak(input2) |
||||
s.add(And(c1, c2)) |
||||
|
||||
s.add(o1 == o2) |
||||
assert s.check() == expected |
||||
|
||||
|
||||
def test_keccak_symbol_and_val(): |
||||
""" |
||||
check keccak(100) == keccak(n) && n == 10 |
||||
:return: |
||||
""" |
||||
s = Solver() |
||||
hundred = symbol_factory.BitVecVal(100, 256) |
||||
n = symbol_factory.BitVecSym("n", 256) |
||||
o1, c1 = keccak_function_manager.create_keccak(hundred) |
||||
o2, c2 = keccak_function_manager.create_keccak(n) |
||||
s.add(And(c1, c2)) |
||||
s.add(o1 == o2) |
||||
s.add(n == symbol_factory.BitVecVal(10, 256)) |
||||
assert s.check() == z3.unsat |
||||
|
||||
|
||||
def test_keccak_complex_eq(): |
||||
""" |
||||
check for keccak(keccak(b)*2) == keccak(keccak(a)*2) && a != b |
||||
:return: |
||||
""" |
||||
s = Solver() |
||||
a = symbol_factory.BitVecSym("a", 160) |
||||
b = symbol_factory.BitVecSym("b", 160) |
||||
o1, c1 = keccak_function_manager.create_keccak(a) |
||||
o2, c2 = keccak_function_manager.create_keccak(b) |
||||
s.add(And(c1, c2)) |
||||
two = symbol_factory.BitVecVal(2, 256) |
||||
o1 = two * o1 |
||||
o2 = two * o2 |
||||
o1, c1 = keccak_function_manager.create_keccak(o1) |
||||
o2, c2 = keccak_function_manager.create_keccak(o2) |
||||
|
||||
s.add(And(c1, c2)) |
||||
s.add(o1 == o2) |
||||
s.add(a != b) |
||||
|
||||
assert s.check() == z3.unsat |
||||
|
||||
|
||||
def test_keccak_complex_eq2(): |
||||
""" |
||||
check for keccak(keccak(b)*2) == keccak(keccak(a)*2) |
||||
This isn't combined with prev test because incremental solving here requires extra-extra work |
||||
(solution is literally the opposite of prev one) so it will take forever to solve. |
||||
:return: |
||||
""" |
||||
s = Solver() |
||||
a = symbol_factory.BitVecSym("a", 160) |
||||
b = symbol_factory.BitVecSym("b", 160) |
||||
o1, c1 = keccak_function_manager.create_keccak(a) |
||||
o2, c2 = keccak_function_manager.create_keccak(b) |
||||
s.add(And(c1, c2)) |
||||
two = symbol_factory.BitVecVal(2, 256) |
||||
o1 = two * o1 |
||||
o2 = two * o2 |
||||
o1, c1 = keccak_function_manager.create_keccak(o1) |
||||
o2, c2 = keccak_function_manager.create_keccak(o2) |
||||
|
||||
s.add(And(c1, c2)) |
||||
s.add(o1 == o2) |
||||
|
||||
assert s.check() == z3.sat |
||||
|
||||
|
||||
def test_keccak_simple_number(): |
||||
""" |
||||
check for keccak(b) == 10 |
||||
:return: |
||||
""" |
||||
s = Solver() |
||||
a = symbol_factory.BitVecSym("a", 160) |
||||
ten = symbol_factory.BitVecVal(10, 256) |
||||
o, c = keccak_function_manager.create_keccak(a) |
||||
|
||||
s.add(c) |
||||
s.add(ten == o) |
||||
|
||||
assert s.check() == z3.unsat |
||||
|
||||
|
||||
def test_keccak_other_num(): |
||||
""" |
||||
check keccak(keccak(a)*2) == b |
||||
:return: |
||||
""" |
||||
s = Solver() |
||||
a = symbol_factory.BitVecSym("a", 160) |
||||
b = symbol_factory.BitVecSym("b", 256) |
||||
o, c = keccak_function_manager.create_keccak(a) |
||||
two = symbol_factory.BitVecVal(2, 256) |
||||
o = two * o |
||||
s.add(c) |
||||
o, c = keccak_function_manager.create_keccak(o) |
||||
s.add(c) |
||||
s.add(b == o) |
||||
|
||||
assert s.check() == z3.sat |
@ -1,30 +0,0 @@ |
||||
Truffle Hello World Demo |
||||
======================== |
||||
|
||||
``` |
||||
npm install -g truffle ethereumjs-testrpc |
||||
``` |
||||
|
||||
This repo is created by following commands: |
||||
|
||||
``` |
||||
truffle init |
||||
truffle compile |
||||
``` |
||||
|
||||
In order to run `truffle migrate`, we need to setup `truffle.js` first: |
||||
|
||||
``` |
||||
networks: { |
||||
development: { |
||||
host: "localhost", |
||||
port: 8545, |
||||
network_id: "*" // Match any network id |
||||
} |
||||
} |
||||
``` |
||||
|
||||
Resources |
||||
--------- |
||||
|
||||
- <https://medium.com/etherereum-salon/eth-testing-472c2f73b4c3> |
@ -1,23 +0,0 @@ |
||||
pragma solidity ^0.4.17; |
||||
|
||||
contract Migrations { |
||||
address public owner; |
||||
uint public last_completed_migration; |
||||
|
||||
modifier restricted() { |
||||
if (msg.sender == owner) _; |
||||
} |
||||
|
||||
function Migrations() public { |
||||
owner = msg.sender; |
||||
} |
||||
|
||||
function setCompleted(uint completed) public restricted { |
||||
last_completed_migration = completed; |
||||
} |
||||
|
||||
function upgrade(address new_address) public restricted { |
||||
Migrations upgraded = Migrations(new_address); |
||||
upgraded.setCompleted(last_completed_migration); |
||||
} |
||||
} |
@ -1,34 +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]; |
||||
} |
||||
|
||||
function() public payable { |
||||
invest(); |
||||
} |
||||
} |
@ -1,5 +0,0 @@ |
||||
var Migrations = artifacts.require("./Migrations.sol"); |
||||
|
||||
module.exports = function(deployer) { |
||||
deployer.deploy(Migrations); |
||||
}; |
@ -1,4 +0,0 @@ |
||||
module.exports = { |
||||
// See <http://truffleframework.com/docs/advanced/configuration>
|
||||
// to customize your Truffle configuration!
|
||||
}; |
@ -1,11 +0,0 @@ |
||||
module.exports = { |
||||
// See <http://truffleframework.com/docs/advanced/configuration>
|
||||
// to customize your Truffle configuration!
|
||||
networks: { |
||||
development: { |
||||
host: "localhost", |
||||
port: 8545, |
||||
network_id: "*" // Match any network id
|
||||
} |
||||
} |
||||
}; |
Loading…
Reference in new issue