diff --git a/README.md b/README.md index e52952fb..d61ae7fe 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,17 @@ Mythril is a reverse engineering and bug hunting framework for the Ethereum blockchain. + * [Installation and setup](#installation-and-setup) + * [Command line usage](#command-line-usage) + + [Input formats](#input-formats) + - [Working with on-chain contracts](#working-with-on-chain-contracts) + - [Working with Solidity files](#working-with-solidity-files) + + [Disassembler](#disassembler) + + [Control flow graph](#control-flow-graph) + + [Contract search](#contract-search) + - [Searching from the command line](#searching-from-the-command-line) + - [Finding cross-references](#finding-cross-references) + ## Installation and setup Install from Pypi: @@ -22,15 +33,35 @@ $ python setup.py install Note that Mythril requires Python 3.5 to work. -You also need a [go-ethereum](https://github.com/ethereum/go-ethereum) node that is synced with the network (note that Mythril uses non-standard RPC APIs only supported by go-ethereum, so other clients likely won't work). Start the node as follows: +## Command line usage + +The Mythril command line tool (aptly named `myth`) allows you to conveniently access most of Mythril's functionality. + +### Input formats + +Mythril can handle various sources and input formats, including bytecode, addresses of contracts on the blockchain, and Solidity source code files. + +#### Working with on-chain contracts + +To pull contracts from the blockchain you need an Ethereum node that is synced with the network. By default, Mythril will query a local node via RPC. Alternatively, you can connect to a remote service such as [INFURA](https://infura.io): + +``` +$ myth --rpchost=mainnet.infura.io/{API-KEY} --rpcport=443 --rpctls=True (... etc ...) +``` + +The recommended way is to use [go-ethereum](https://github.com/ethereum/go-ethereum). Start your local node as follows: ```bash $ geth --rpc --rpcapi eth,debug --syncmode fast ``` -## Command line usage +#### Working with Solidity files + +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.: -The Mythril command line tool (aptly named `myth`) allows you to conveniently access some of Mythril's functionality. +```bash +$ myth -g ./graph.html myContract.sol +``` ### Disassembler @@ -62,9 +93,9 @@ Mythril integrates the LASER symbolic virtual machine. Right now, this is mainly $ myth -g ./graph.html -a "0xFa52274DD61E1643d2205169732f29114BC240b3" ``` -![callgraph](https://raw.githubusercontent.com/b-mueller/mythril/master/static/callgraph6.png "Call graph") +![callgraph](https://raw.githubusercontent.com/b-mueller/mythril/master/static/callgraph7.png "Call graph") -The "bounce" effect, while awesome (and thus enabled by default), sometimes messes up the graph layout. If that happens, disable the effect with the `--disable-physics` flag. +~~The "bounce" effect, while awesome (and thus enabled by default), sometimes messes up the graph layout.~~ Try adding the `--enable-physics` flag for a very entertaining "bounce" effect that unfortunately completely destroys usability. ### Contract search @@ -77,8 +108,6 @@ Processing block 4323000, 3 individual contracts in database (...) ``` -Mythril retrieves contract data over RPC by default. You can switch to IPC using the `--ipc` flag. - The default behavior is to only sync contracts with a non-zero balance. You can disable this behavior with the `--sync-all` flag, but be aware that this will result in a huge (as in: dozens of GB) database. #### Searching from the command line @@ -99,19 +128,10 @@ It is often useful to find other contracts referenced by a particular contract. $ myth --search "code#DELEGATECALL#" Matched contract with code hash 07459966443977122e639cbf7804c446 Address: 0x76799f77587738bfeef09452df215b63d2cfb08a, balance: 1000000000000000 -$ myth --xrefs 07459966443977122e639cbf7804c446 +$ myth --xrefs -a 0x76799f77587738bfeef09452df215b63d2cfb08a 5b9e8728e316bbeb692d22daaab74f6cbf2c4691 ``` -## Issues - -The database sync is currently not very efficient. - -- Using PyEthereum: I encountered issues syncing PyEthereum with Homestead. Also, PyEthApp only supports Python 2.7, which causes issues with other important packages. -- Accessing the Go-Ethereum LevelDB: This would be a great option. However, PyEthereum database code seems unable to deal with Go-Ethereum's LevelDB. It would take quite a bit of effort to figure this out. - -I'm writing this in my spare time, so contributors would be highly welcome! - ## Credit - JSON RPC library is adapted from [ethjsonrpc](https://github.com/ConsenSys/ethjsonrpc) (it doesn't seem to be maintained anymore, and I needed to make some changes to it). diff --git a/examples/wallet.sol b/examples/wallet.sol new file mode 100644 index 00000000..c3bf86e0 --- /dev/null +++ b/examples/wallet.sol @@ -0,0 +1,85 @@ +//sol Wallet +// Multi-sig, daily-limited account proxy/wallet. +// @authors: +// Gav Wood +// 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 Wallet is WalletEvents { + + // METHODS + + // gets called when no other function matches + function() payable { + // just being sent some cash? + if (msg.value > 0) + Deposit(msg.sender, msg.value); + else if (msg.data.length > 0) + _walletLibrary.delegatecall(msg.data); + } + + // Gets an owner by 0-indexed position (using numOwners as the count) + function getOwner(uint ownerIndex) constant returns (address) { + return address(m_owners[ownerIndex + 1]); + } + + // As return statement unavailable in fallback, explicit the method here + + function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool) { + return _walletLibrary.delegatecall(msg.data); + } + + function isOwner(address _addr) constant returns (bool) { + return _walletLibrary.delegatecall(msg.data); + } + + // FIELDS + + + + address constant _walletLibrary = 0x1111111111111111111111111111111111111111; + + // 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; +} \ No newline at end of file diff --git a/examples/walletlibrary.sol b/examples/walletlibrary.sol new file mode 100644 index 00000000..88b846af --- /dev/null +++ b/examples/walletlibrary.sol @@ -0,0 +1,357 @@ +pragma solidity ^0.4.9; + +contract WalletLibrary { + // 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); + + + // 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) { + 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) { + 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; + } + + // constructor - just pass on the owner array to the multiowned and + // the limit to daylimit + function initWallet(address[] _owners, uint _required, uint _daylimit) { + 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(invalidJumpLabel, 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; +} \ No newline at end of file diff --git a/myth b/myth index b0ea5a62..3c5b9017 100755 --- a/myth +++ b/myth @@ -4,16 +4,19 @@ http://www.github.com/b-mueller/mythril """ -from mythril.ether import evm,util -from mythril.disassembler.disassembly import Disassembly +from mythril.ether import evm, util from mythril.disassembler.callgraph import generate_callgraph from mythril.ether.contractstorage import get_persistent_storage +from mythril.ether.ethcontract import ETHContract +from mythril.ether.util import compile_solidity from mythril.rpc.client import EthJsonRpc from mythril.ipc.client import EthIpc +from mythril.support.loader import DynLoader +from mythril.exceptions import CompilerError from ethereum import utils -from laser.ethereum import laserfree +from laser.ethereum import svm, laserfree +from pathlib import Path import logging -import binascii import sys import argparse import os @@ -21,7 +24,7 @@ import re def searchCallback(code_hash, code, addresses, balances): - print("Matched contract with code hash " + code_hash ) + print("Matched contract with code hash " + code_hash) for i in range(0, len(addresses)): print("Address: " + addresses[i] + ", balance: " + str(balances[i])) @@ -33,119 +36,154 @@ def exitWithError(message): parser = argparse.ArgumentParser(description='Bug hunting on the Ethereum blockchain') +parser.add_argument("solidity_file", nargs='*') commands = parser.add_argument_group('commands') -commands.add_argument('-d', '--disassemble', action='store_true', help='disassemble, specify input with -c or -a') -commands.add_argument('-t', '--trace', action='store_true', help='trace, use with -c or -a and --data (optional)') -commands.add_argument('-g', '--graph', help='generate a call graph', metavar='OUTPUT_FILE') -commands.add_argument('-l', '--fire-lasers', action='store_true', help='detect vulnerabilities, use with -c or -a') -commands.add_argument('-s', '--search', help='search the contract database') -commands.add_argument('--xrefs', help='get xrefs from contract in database', metavar='CONTRACT_HASH') +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('--init-db', action='store_true', help='initialize the contract database') 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='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('--data', help='message call input data for tracing') options = parser.add_argument_group('options') options.add_argument('--sync-all', action='store_true', help='Also sync contracts with zero balance') +options.add_argument('--infura-mainnet', action='store_true', help='Use Infura Node service, equivalent to: --rpchost=mainnet.infura.io --rpcport=443 --rpctls="True"') +options.add_argument('--infura-rinkeby', action='store_true', help='Use Infura Node service, equivalent to: --rpchost=rinkeby.infura.io --rpcport=443 --rpctls="True"') +options.add_argument('--infura-kovan', action='store_true', help='Use Infura Node service, equivalent to: --rpchost=kovan.infura.io --rpcport=443 --rpctls="True"') +options.add_argument('--infura-ropsten', action='store_true', help='Use Infura Node service, equivalent to: --rpchost=ropsten.infura.io --rpcport=443 --rpctls="True"') options.add_argument('--rpchost', default='127.0.0.1', help='RPC host') options.add_argument('--rpcport', type=int, default=8545, help='RPC port') +options.add_argument('--rpctls', type=bool, default=False, help='RPC port') options.add_argument('--ipc', help='use IPC interface instead of RPC', action='store_true') -options.add_argument('--disable-physics', action='store_true', help='disable 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') +# Get config values try: db_dir = os.environ['DB_DIR'] except KeyError: db_dir = None +try: + solc_binary = os.environ['SOLC'] +except KeyError: + solc_binary = 'solc' + +# Parse cmdline 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): + parser.print_help() + sys.exit() + if (args.v): if (0 <= args.v < 3): logging.basicConfig(level=[logging.NOTSET, logging.INFO, logging.DEBUG][args.v]) -if (args.disassemble or args.graph or args.fire_lasers): +elif (args.hash): + print("0x" + utils.sha3(args.hash)[:4].hex()) + sys.exit() + +# Database search ops + +if args.search or args.init_db: - if (args.code): - encoded_bytecode = args.code - elif (args.address): + contract_storage = get_persistent_storage(db_dir) + + if (args.search): + + try: + contract_storage.search(args.search, searchCallback) + except SyntaxError: + exitWithError("Syntax error in search expression.") + + elif (args.init_db): + contract_storage.initialize(args.rpchost, args.rpcport, args.rpctls, args.sync_all, args.ipc) + + sys.exit() + +# Establish RPC/IPC connection if necessary + +if (args.address or len(args.solidity_file)): if args.ipc: try: eth = EthIpc() - encoded_bytecode = eth.eth_getCode(args.address) except Exception as e: - exitWithError("Exception loading bytecode via IPC: " + str(e)) + exitWithError("Error establishing IPC connection: " + str(e)) else: try: - eth = EthJsonRpc(args.rpchost, args.rpcport) - - encoded_bytecode = eth.eth_getCode(args.address) + if args.infura_mainnet: + eth = EthJsonRpc('mainnet.infura.io', 443, True) + elif args.infura_rinkeby: + eth = EthJsonRpc('rinkeby.infura.io', 443, True) + elif args.infura_kovan: + eth = EthJsonRpc('kovan.infura.io', 443, True) + elif args.infura_ropsten: + eth = EthJsonRpc('ropsten.infura.io', 443, True) + else: + eth = EthJsonRpc(args.rpchost, args.rpcport, args.rpctls) except Exception as e: - exitWithError("Exception loading bytecode via RPC: " + str(e)) - - else: - exitWithError("No input bytecode. Please provide the code via -c BYTECODE or -a address") - - try: - disassembly = Disassembly(encoded_bytecode) - except binascii.Error: - exitWithError("Disassembler: Invalid code string.") + exitWithError("Error establishing RPC connection: " + str(e)) - if (args.disassemble): +# Load / compile input contracts - easm_text = disassembly.get_easm() - sys.stdout.write(easm_text) +contracts = [] - elif (args.graph): +if (args.code): + contracts.append(ETHContract(args.code, name="MAIN", address = util.get_indexed_address(0))) +elif (args.address): + contracts.append(ETHContract(eth.eth_getCode(args.address), name=args.address, address = args.address)) +elif (len(args.solidity_file)): + index = 0 - if (args.disable_physics): - physics = False - else: - physics = True + for file in args.solidity_file: - html = generate_callgraph(disassembly, physics) + file = file.replace("~", str(Path.home())) # Expand user path try: - with open(args.graph, "w") as f: - f.write(html) - except Exception as e: - print("Error saving graph: " + str(e)) + name, bytecode = compile_solidity(solc_binary, file) + except CompilerError as e: + exitWithError(e) + # Max. 16 contracts supported! - elif (args.fire_lasers): + contract = ETHContract(bytecode, name = name, address = util.get_indexed_address(index)) + index += 1 - laserfree.fire(disassembly) + contracts.append(contract) + logging.info(contract.name + " at " + contract.address) -elif (args.trace): +else: + exitWithError("No input bytecode. Please provide EVM code via -c BYTECODE, -a ADDRESS, or -i SOLIDITY_FILES") - if (args.code): - encoded_bytecode = args.code +# Commands - elif (args.address): - if args.ipc: - eth = EthIpc() - encoded_bytecode = eth.eth_getCode(args.address) +if (args.disassemble): - else: - eth = EthJsonRpc(args.rpchost, args.rpcport) - encoded_bytecode = eth.eth_getCode(args.address) + easm_text = contracts[0].get_easm() + sys.stdout.write(easm_text) - else: - exitWithError("Disassembler: Provide the input bytecode via -c BYTECODE or --id ID") +elif (args.trace): if (args.data): - trace = evm.trace(encoded_bytecode, args.data) + trace = evm.trace(contracts[0].code, args.data) else: - trace = evm.trace(encoded_bytecode) + trace = evm.trace(contracts[0].code) for i in trace: if (re.match(r'^PUSH.*', i['op'])): @@ -153,36 +191,44 @@ elif (args.trace): else: print(str(i['pc']) + " " + i['op'] + ";\tSTACK: " + i['stack']) -else: - contract_storage = get_persistent_storage(db_dir) - if (args.search): +elif (args.xrefs): - try: - contract_storage.search(args.search, searchCallback) - except SyntaxError: - exitWithError("Syntax error in search expression.") + print("\n".join(contracts[0].get_xrefs())) - elif (args.xrefs): +elif (args.graph) or (args.fire_lasers): - try: - contract_hash = util.safe_decode(args.xrefs) - except binascii.Error: - exitWithError("Invalid contract hash.") + # Convert to LASER SVM format - try: - contract = contract_storage.get_contract_by_hash(contract_hash) - print("\n".join(contract.get_xrefs())) - except KeyError: - exitWithError("Contract not found in the database.") + modules = {} - elif (args.init_db): - if args.ipc: - contract_storage.initialize(args.rpchost, args.rpcport, args.sync_all, args.ipc) - else: - contract_storage.initialize(args.rpchost, args.rpcport, args.sync_all, args.ipc) + for contract in contracts: + modules[contract.address] = contract.as_dict() + + if (args.dynld): + loader = DynLoader(eth) + _svm = svm.SVM(modules, dynamic_loader=loader) + else: + _svm = svm.SVM(modules) + + if (args.graph): + + _svm.simplify_model = True + + if args.enable_physics is not None: + physics = True + + html = generate_callgraph(_svm, contracts[0].address, args.enable_physics) + + try: + with open(args.graph, "w") as f: + f.write(html) + except Exception as e: - elif (args.hash): - print("0x" + utils.sha3(args.hash)[:4].hex()) + print("Error saving graph: " + str(e)) else: - parser.print_help() + + laserfree.fire(modules, contracts[0].address) + +else: + parser.print_help() diff --git a/mythril/disassembler/callgraph.py b/mythril/disassembler/callgraph.py index 0d851bb3..d166223a 100644 --- a/mythril/disassembler/callgraph.py +++ b/mythril/disassembler/callgraph.py @@ -1,4 +1,3 @@ -from laser.ethereum import svm from z3 import Z3Exception, simplify import re @@ -7,11 +6,11 @@ graph_html = ''' @@ -49,14 +48,6 @@ graph_html = ''' align: 'left', color: '#FFFFFF', }, - color: { - border: '#26996f', - background: '#1f7e5b', - highlight: { - border: '#26996f', - background: '#28a16f' - } - } }, edges:{ font: { @@ -95,18 +86,28 @@ var gph = new vis.Network(container, data, options); ''' -def serialize(_svm): +colors = [ + "{border: '#26996f', background: '#2f7e5b', highlight: {border: '#26996f', background: '#28a16f'}}", + "{border: '#9e42b3', background: '#842899', highlight: {border: '#9e42b3', background: '#933da6'}}", + "{border: '#b82323', background: '#991d1d', highlight: {border: '#b82323', background: '#a61f1f'}}", + "{border: '#4753bf', background: '#3b46a1', highlight: {border: '#4753bf', background: '#424db3'}}", +] + + +def serialize(_svm, color_map): nodes = [] edges = [] - for n in _svm.nodes: + for node_key in _svm.nodes: - code = _svm.nodes[n].as_dict()['code'] + code = _svm.nodes[node_key].as_dict()['code'] code = re.sub("([0-9a-f]{8})[0-9a-f]+", lambda m: m.group(1) + "(...)", code) - nodes.append("{id: " + str(_svm.nodes[n].as_dict()['id']) + ", size: 150, 'label': '" + code + "'}") + color = color_map[_svm.nodes[node_key].as_dict()['module_name']] + + nodes.append("{id: '" + node_key + "', color: " + color + ", size: 150, 'label': '" + code + "'}") for edge in _svm.edges: @@ -118,23 +119,29 @@ def serialize(_svm): label = str(simplify(edge.condition)).replace("\n", "") except Z3Exception: label = str(edge.condition).replace("\n", "") - - label = re.sub("[^_]([[\d]{2}\d+)", lambda m: hex(int(m.group(1))), label) + + label = re.sub("([^_])([\d]{2}\d+)", lambda m: m.group(1) + hex(int(m.group(2))), label) code = re.sub("([0-9a-f]{8})[0-9a-f]+", lambda m: m.group(1) + "(...)", code) - edges.append("{from: " + str(edge.as_dict()['from']) + ', to: ' + str(edge.as_dict()['to']) + ", 'arrows': 'to', 'label': '" + label + "', 'smooth': {'type': 'cubicBezier'}}") + edges.append("{from: '" + str(edge.as_dict()['from']) + "', to: '" + str(edge.as_dict()['to']) + "', 'arrows': 'to', 'label': '" + label + "', 'smooth': {'type': 'cubicBezier'}}") return "var nodes = [\n" + ",\n".join(nodes) + "\n];\nvar edges = [\n" + ",\n".join(edges) + "\n];" -def generate_callgraph(disassembly, physics): +def generate_callgraph(svm, main_address, physics): + + svm.sym_exec(main_address) + + i = 0 - _svm = svm.SVM(disassembly) + color_map = {} - _svm.sym_exec() + for k in svm.modules: + color_map[svm.modules[k]['name']] = colors[i] + i += 1 - html = graph_html.replace("[JS]", serialize(_svm)) + html = graph_html.replace("[JS]", serialize(svm, color_map)) html = html.replace("[ENABLE_PHYSICS]", str(physics).lower()) return html diff --git a/mythril/disassembler/disassembly.py b/mythril/disassembler/disassembly.py index e378cac5..2dc4b11d 100644 --- a/mythril/disassembler/disassembly.py +++ b/mythril/disassembler/disassembly.py @@ -3,32 +3,10 @@ import os import json -class Block: - - def __init__(self, id, code_index, funcname): - self.id = id - self.code_index = code_index - self.funcname = funcname - self.instruction_list = [] - - def update_length(self, num_instructions): - self.length = num_instructions - self.start_addr = self.instruction_list[0]['address'] - self.end_addr = self.instruction_list[-1]['address'] - - def get_easm(self): - - easm = str(self.instruction_list[0]['address']) + " " + self.funcname + "\n" - easm += asm.instruction_list_to_easm(self.instruction_list[1:]) - - return easm - - class Disassembly: def __init__(self, code): self.instruction_list = asm.disassemble(util.safe_decode(code)) - self.blocks = [] self.xrefs = [] self.func_to_addr = {} self.addr_to_func = {} @@ -59,80 +37,8 @@ class Disassembly: except: continue - # Parse instructions into basic blocks - - current_block = Block(0, 0, "PROLOGUE") - - index = 0 - blocklen = 0 - blocknumber = 1 - - for instruction in self.instruction_list: - - if (instruction['opcode'] == "JUMPDEST"): - - try: - func_name = "- FUNCTION " + self.addr_to_func[instruction['address']] + " -" - except KeyError: - func_name = "- JUMPDEST_UNK -" - - current_block.update_length(blocklen) - self.blocks.append(current_block) - current_block = Block(blocknumber, index, func_name) - blocklen = 0 - blocknumber += 1 - - current_block.instruction_list.append(instruction) - blocklen += 1 - - index += 1 - - # Add the last block - current_block.update_length(blocklen) - self.blocks.append(current_block) - - # Resolve cross-references - - for block in self.blocks: - - jmp_indices = asm.find_opcode_sequence(["JUMP"], block.instruction_list) - jmp_indices += asm.find_opcode_sequence(["JUMPI"], block.instruction_list) - - for i in jmp_indices: - try: - dest_hex = block.instruction_list[i - 1]['argument'] - dest = int(dest_hex[2:], 16) - except: - continue - - j = 0 - - try: - while(self.blocks[j].end_addr < dest): - j += 1 - except IndexError: - continue - - - if not (block.id, self.blocks[j].id) in self.xrefs: - self.xrefs.append((block.id, self.blocks[j].id)) - - # if the last instruction isn't an unconditional jump or halt, also add a reference to the following block - - try: - if (block.id < len(self.blocks)) and (block.instruction_list[block.length - 1]['opcode'] not in ['JUMP', 'STOP', 'THROW', 'REVERT', 'INVALID']): - if not (block.id, self.blocks[j].id) in self.xrefs: - self.xrefs.append((block.id, block.id + 1)) - except UnboundLocalError: - # quickfix - continue def get_easm(self): - easm = asm.instruction_list_to_easm(self.instruction_list[0:self.blocks[0].length]) - - for block in self.blocks[1:]: - easm += block.get_easm() - - return easm \ No newline at end of file + return asm.instruction_list_to_easm(self.instruction_list) diff --git a/mythril/ether/contractstorage.py b/mythril/ether/contractstorage.py index e4d6e6c2..4f17b555 100644 --- a/mythril/ether/contractstorage.py +++ b/mythril/ether/contractstorage.py @@ -47,11 +47,11 @@ class ContractStorage(persistent.Persistent): return self.contracts[contract_hash] - def initialize(self, rpchost, rpcport, sync_all, ipc): + def initialize(self, rpchost, rpcport, rpctls, sync_all, ipc): if ipc: eth = EthIpc() else: - eth = EthJsonRpc(rpchost, rpcport) + eth = EthJsonRpc(rpchost, rpcport, rpctls) if self.last_block: blockNum = self.last_block @@ -105,6 +105,10 @@ class ContractStorage(persistent.Persistent): self.last_block = blockNum blockNum -= 1 + # If we've finished initializing the database, start over from the end of the chain if we want to initialize again + self.last_block = 0 + transaction.commit() + def search(self, expression, callback_func): diff --git a/mythril/ether/ethcontract.py b/mythril/ether/ethcontract.py index 456dffc3..02e4ab64 100644 --- a/mythril/ether/ethcontract.py +++ b/mythril/ether/ethcontract.py @@ -1,20 +1,32 @@ -from mythril.ether import asm, util -import re -import persistent +from mythril.disassembler.disassembly import Disassembly from ethereum import utils +import persistent +import re class ETHContract(persistent.Persistent): - def __init__(self, code, creation_code = ""): + def __init__(self, code, creation_code="", name="", address=""): self.code = code self.creation_code = creation_code + self.name = name + self.address = address + + def as_dict(self): + + return { + 'address': self.address, + 'name': self.name, + 'code': self.code, + 'creation_code': self.creation_code, + 'disassembly': self.get_disassembly() + } def get_xrefs(self): - instruction_list = asm.disassemble(util.safe_decode(self.code)) + instruction_list = Disassembly(self.code).instruction_list xrefs = [] @@ -30,14 +42,14 @@ class ETHContract(persistent.Persistent): return xrefs - def get_instruction_list(self): + def get_disassembly(self): - return asm.disassemble(util.safe_decode(self.code)) + return Disassembly(self.code) def get_easm(self): - return asm.instruction_list_to_easm(asm.disassemble(util.safe_decode(self.code))) + return Disassembly(self.code).get_easm() def matches_expression(self, expression): diff --git a/mythril/ether/evm.py b/mythril/ether/evm.py index 42bbd737..29376e0d 100644 --- a/mythril/ether/evm.py +++ b/mythril/ether/evm.py @@ -7,7 +7,6 @@ from io import StringIO import re - def trace(code, calldata = ""): logHandlers = ['eth.vm.op', 'eth.vm.op.stack', 'eth.vm.op.memory', 'eth.vm.op.storage'] @@ -57,7 +56,7 @@ def trace(code, calldata = ""): if (len(stackitems)): for i in range(0, len(stackitems) - 1): - stack += hex(int(stackitems[i])) + ", " + stack += hex(int(stackitems[i])) + ", " stack += hex(int(stackitems[-1])) @@ -74,5 +73,3 @@ def trace(code, calldata = ""): trace.append({'pc': pc, 'op': op, 'stack': stack}) return trace - - diff --git a/mythril/ether/util.py b/mythril/ether/util.py index 4cdc6cca..ac6ea6f7 100644 --- a/mythril/ether/util.py +++ b/mythril/ether/util.py @@ -1,67 +1,52 @@ -from mythril.rpc.client import EthJsonRpc -from mythril.ipc.client import EthIpc from ethereum.abi import encode_abi, encode_int from ethereum.utils import zpad from ethereum.abi import method_id +from mythril.exceptions import CompilerError +import subprocess +from subprocess import Popen, PIPE +import binascii +import os +import re -def safe_decode(hex_encoded_string): - - # print(type(hex_encoded_string)) +def safe_decode(hex_encoded_string): if (hex_encoded_string.startswith("0x")): return bytes.fromhex(hex_encoded_string[2:]) - # return codecs.decode(, 'hex_codec') else: return bytes.fromhex(hex_encoded_string) - # return codecs.decode(hex_encoded_string, 'hex_codec') - -def bytecode_from_blockchain(creation_tx_hash, ipc, rpc_host='127.0.0.1', rpc_port=8545): - """Load bytecode from a local node via - creation_tx_hash = ID of transaction that created the contract. - """ - if ipc: - pass - else: - eth = EthJsonRpc(rpc_host, rpc_port) - trace = eth.traceTransaction(creation_tx_hash) +def compile_solidity(solc_binary, file): - if trace['returnValue']: + try: + p = Popen(["solc", "--bin-runtime", file], stdout=PIPE, stderr=PIPE) + stdout, stderr = p.communicate() + except FileNotFoundError: + raise CompilerError("Compiler not found. Make sure that solc is installed and in PATH, or set the SOLC environment variable.") - return trace['returnValue'] + out = stdout.decode("UTF-8") - raise RuntimeError("Transaction trace didn't return any bytecode") + if out == "": + err = "Error compiling input file. Solc returned:\n" + stderr.decode("UTF-8") + raise CompilerError(err) + # out = out.replace("[\n\s]", "") -def fire_lasers(disassembly): - return laserfree.analysis(disassembly) + m = re.search(r":(.*?) =======\nBinary of the runtime part: \n([0-9a-f]+)\n", out) + return [m.group(1), m.group(2)] def encode_calldata(func_name, arg_types, args): - mid = method_id(func_name, arg_types) function_selector = zpad(encode_int(mid), 4) args = encode_abi(arg_types, args) return "0x" + function_selector.hex() + args.hex() -def raw_bytes_to_file(filename, bytestring): - with open(filename, 'wb') as f: - f.write(bytestring) - - -def file_to_raw_bytes(filename): - with open(filename, 'rb') as f: - data = f.read() - return data +def get_random_address(): + return binascii.b2a_hex(os.urandom(20)).decode('UTF-8') -def string_to_file(filename, string): - with open(filename, 'w') as f: - f.write(string) +def get_indexed_address(index): + return "0x" + (hex(index)[2:] * 40) -def file_to_string(filename): - with open(filename, 'r') as f: - data = f.read() - return data diff --git a/mythril/exceptions.py b/mythril/exceptions.py new file mode 100644 index 00000000..7915c482 --- /dev/null +++ b/mythril/exceptions.py @@ -0,0 +1,2 @@ +class CompilerError(Exception): + pass \ No newline at end of file diff --git a/mythril/ipc/client.py b/mythril/ipc/client.py index 4c48098f..a6377048 100644 --- a/mythril/ipc/client.py +++ b/mythril/ipc/client.py @@ -75,11 +75,12 @@ class EthIpc(object): def call(self, address, sig, args, result_types): ''' - Call a contract function on the RPC server, without sending a + Call a contract function on the IPC server, without sending a transaction (useful for reading data) ''' data = self._encode_function(sig, args) data_hex = data.encode('hex') + # could be made to use web3py directly, but instead uses eth_call which is adapted response = self.eth_call(to_address=address, data=data_hex) return decode_abi(result_types, response[2:].decode('hex')) @@ -103,7 +104,6 @@ class EthIpc(object): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#web3_clientversion http://web3py.readthedocs.io/en/latest/web3.version.html#web3.version.Version.node - ''' return self.web3.version.node @@ -130,8 +130,8 @@ class EthIpc(object): def net_listening(self): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#net_listening - ONLY indirectly available - TESTED + Only indirectly available + ''' return self.web3net.listening() @@ -139,7 +139,7 @@ class EthIpc(object): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#net_peercount ONLY indirectly available - TESTED + ''' return self.web3.net.peerCount() @@ -181,7 +181,7 @@ class EthIpc(object): http://web3py.readthedocs.io/en/latest/web3.eth.html?highlight=syncing#web3.eth.Eth.hashrate ''' - return hex_to_dec(web3.eth.hashrate) + return web3.eth.hashrate def eth_gasPrice(self): ''' @@ -189,7 +189,7 @@ class EthIpc(object): http://web3py.readthedocs.io/en/latest/web3.eth.html?highlight=gasprice#web3.eth.Eth.gasPrice ''' - return hex_to_dec(web3.eth.gasPrice) + return web3.eth.gasPrice def eth_accounts(self): ''' @@ -205,7 +205,7 @@ class EthIpc(object): http://web3py.readthedocs.io/en/latest/web3.eth.html?highlight=gasprice#web3.eth.Eth.blockNumber ''' - return hex_to_dec(self.web3.eth.blockNumber) + return self.web3.eth.blockNumber def eth_getBalance(self, address=None, block=BLOCK_TAG_LATEST): ''' @@ -256,8 +256,9 @@ class EthIpc(object): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getunclecountbyblockhash http://web3py.readthedocs.io/en/latest/web3.eth.html?highlight=gasprice#web3.eth.Eth.getUncle - + (not implemented with convenience functions) ''' + return self.web3.manager.request_blocking('eth_getUncleCountByBlockHash', [block_hash]) return self.web3.eth.getUncleCount(block_hash) def eth_getUncleCountByBlockNumber(self, block=BLOCK_TAG_LATEST): @@ -267,13 +268,13 @@ class EthIpc(object): ''' block = validate_block(block) + self.web3.manager.request_blocking('debug_getBlockRlp', [number]) return self.web3.eth.getUncleCount(block) def eth_getCode(self, address, default_block=BLOCK_TAG_LATEST): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getcode http://web3py.readthedocs.io/en/latest/web3.eth.html?highlight=gasprice#web3.eth.Eth.getCode - NEEDS TESTING ''' if isinstance(default_block, str): if default_block not in BLOCK_TAGS: @@ -285,7 +286,6 @@ class EthIpc(object): https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign http://web3py.readthedocs.io/en/latest/web3.eth.html?highlight=gasprice#web3.eth.Eth.sign either data= hexstr= or text= probably not needed now but if used should be differentiated - NEEDS TESTING ''' return self.web3.eth.sign(address, data) @@ -294,7 +294,6 @@ class EthIpc(object): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sendtransaction http://web3py.readthedocs.io/en/latest/web3.eth.html?highlight=syncing#web3.eth.Eth.sendTransaction - NEEDS TESTING ''' params = {} params['from'] = from_address or self.eth_coinbase() @@ -316,7 +315,6 @@ class EthIpc(object): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sendrawtransaction http://web3py.readthedocs.io/en/latest/web3.eth.html?highlight=syncing#web3.eth.Eth.sendRawTransaction - NEEDS TESTING ''' return self.web3.eth.sendRawTransaction(data) @@ -326,7 +324,6 @@ class EthIpc(object): https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_call http://web3py.readthedocs.io/en/latest/web3.eth.html?highlight=syncing#web3.eth.Eth.call - NEEDS TESTING ''' if isinstance(default_block, str): if default_block not in BLOCK_TAGS: @@ -350,7 +347,6 @@ class EthIpc(object): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_estimategas http://web3py.readthedocs.io/en/latest/web3.eth.html?highlight=gasprice#web3.eth.Eth.estimateGas - NEEDS TESTING ''' if isinstance(default_block, str): if default_block not in BLOCK_TAGS: @@ -374,7 +370,6 @@ class EthIpc(object): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbyhash http://web3py.readthedocs.io/en/latest/web3.eth.html?highlight=gasprice#web3.eth.Eth.getBlock - TESTED ''' return self.web3.eth.getBlock(block_identifier=block_hash, full_transactions=tx_objects) @@ -382,7 +377,6 @@ class EthIpc(object): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbynumber http://web3py.readthedocs.io/en/latest/web3.eth.html?highlight=gasprice#web3.eth.Eth.getBlock - TESTED ''' block = validate_block(block) return self.web3.eth.getBlock(block_identifier=block, full_transactions=tx_objects) @@ -391,7 +385,6 @@ class EthIpc(object): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionbyhash http://web3py.readthedocs.io/en/latest/web3.eth.html?highlight=gasprice#web3.eth.Eth.getTransaction - TESTED ''' return self.web3.eth.getTransactionByHash(tx_hash) @@ -399,7 +392,6 @@ class EthIpc(object): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionbyblockhashandindex http://web3py.readthedocs.io/en/latest/web3.eth.html#web3.eth.Eth.getTransactionFromBlock - TESTED ''' return self.web3.eth.getTransactionFromBlock(block_identifier=block_hash, transaction_index=index) @@ -408,7 +400,6 @@ class EthIpc(object): https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionbyblocknumberandindex http://web3py.readthedocs.io/en/latest/web3.eth.html#web3.eth.Eth.getTransactionFromBlock - TESTED ''' block = validate_block(block) return self.web3.eth.getTransactionFromBlock(block_identifier=block, transaction_index=index) @@ -417,64 +408,60 @@ class EthIpc(object): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionreceipt http://web3py.readthedocs.io/en/latest/web3.eth.html?highlight=gasprice#web3.eth.Eth.getTransactionReceipt - TESTED ''' return self.web3.eth.getTransactionReceipt(tx_hash) def eth_getUncleByBlockHashAndIndex(self, block_hash, index=0): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getunclebyblockhashandindex - NOT IMPLEMENTED - TESTED + Indirectly accessible + self.web3.manager.request_blocking('rpc/ipc function', [params]) + ''' - return "Not implemented" + return self.web3.manager.request_blocking('eth_getUncleByBlockHashAndIndex', [block_hash, web3.toHex(index)]) def eth_getUncleByBlockNumberAndIndex(self, block=BLOCK_TAG_LATEST, index=0): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getunclebyblocknumberandindex - NOT IMPLEMENTED - TESTED + Indirectly accessible ''' block = validate_block(block) - return "Not implemented" + return self.web3.manager.request_blocking('eth_getUncleByBlockNumberAndIndex', [block, web3.toHex(index)]) def eth_getCompilers(self): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getcompilers - Does not seem to be implemented - TESTED + Indirectly implemented ''' - return "Not implemented" + return self.web3.manager.request_blocking('eth_getCompilers') def eth_compileSolidity(self, code): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_compilesolidity - Implemented? - TESTED + Indirectly implemented ''' - return self.web3.eth.compileSolidity(code) + return self.web3.manager.request_blocking('eth_compileSolidity', [code]) + def eth_compileLLL(self, code): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_compilelll - Implemented? - N/A + Indirectly accessible ''' - return self.web3.eth.compileLLL(code) + return self.web3.manager.request_blocking('eth_compileLLL', [code]) def eth_compileSerpent(self, code): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_compileserpent - Implemented? - N/A + Indirectly implemented ''' - return self.web3.eth.compileSerpent(code) + return self.web3.manager.request_blocking('eth_compileSerpent', [code]) + def eth_newFilter(self, from_block=BLOCK_TAG_LATEST, to_block=BLOCK_TAG_LATEST, address=None, topics=None): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newfilter http://web3py.readthedocs.io/en/latest/web3.eth.html#web3.eth.Eth.filter - NEEDS TESTING ''' filter_params = { 'fromBlock': from_block, @@ -488,7 +475,6 @@ class EthIpc(object): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newblockfilter http://web3py.readthedocs.io/en/latest/web3.eth.html#web3.eth.Eth.filter - TESTED ''' return self.web3.eth.newFilter('latest') @@ -496,7 +482,6 @@ class EthIpc(object): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newpendingtransactionfilter http://web3py.readthedocs.io/en/latest/web3.eth.html#web3.eth.Eth.filter - TESTED ''' return self.web3.eth.newFilter('pending') @@ -504,7 +489,6 @@ class EthIpc(object): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_uninstallfilter http://web3py.readthedocs.io/en/latest/web3.eth.html#web3.eth.Eth.uninstallFilter - NEEDS TESTING ''' return self.web3.eth.uninstallFilter(filter_id) @@ -512,7 +496,6 @@ class EthIpc(object): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getfilterchanges http://web3py.readthedocs.io/en/latest/web3.eth.html#web3.eth.Eth.getFilterChanges - NEEDS TESTING ''' return self.web3.eth.getFilterChanges(filter_id) @@ -520,7 +503,6 @@ class EthIpc(object): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getfilterlogs http://web3py.readthedocs.io/en/latest/web3.eth.html#web3.eth.Eth.getFilterLogs - NEEDS TESTING ''' return self.web3.eth.getFilterLogs(filter_id) @@ -528,7 +510,6 @@ class EthIpc(object): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getlogs http://web3py.readthedocs.io/en/latest/filters.html?highlight=getLogs#web3.utils.filters.LogFilter.get - NEEDS TESTING ''' return self.filter_object.get() @@ -536,164 +517,117 @@ class EthIpc(object): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getwork http://web3py.readthedocs.io/en/latest/releases.html?highlight=getWork#id15 - TESTED ''' return self.web3.eth.getWork() def eth_submitWork(self, nonce, header, mix_digest): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_submitwork - Not sure if implemented - NEEDS TESTING + Implemented indirectly ''' - return self.web3.eth.submitWork(nonce, header, mix_digest) + return self.web3.manager.request_blocking('eth_submitWork', [nonce, header, mix_digest]) def eth_submitHashrate(self, hash_rate, client_id): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_submithashrate - Not sure if implemented - TESTED - ''' - return self.web3.eth.submitHashrate(hash_rate, client_id) - - def db_putString(self, db_name, key, value): - ''' - https://github.com/ethereum/wiki/wiki/JSON-RPC#db_putstring - Not implemented I think - TESTED - ''' - warnings.warn('deprecated', DeprecationWarning) - return self.web3.db.putString(db_name, key, value) - - def db_getString(self, db_name, key): - ''' - https://github.com/ethereum/wiki/wiki/JSON-RPC#db_getstring - Not implemented I think - TESTED - ''' - warnings.warn('deprecated', DeprecationWarning) - return self.web3.db.getString(db_name, key) - - def db_putHex(self, db_name, key, value): + Implemented indirectly ''' - https://github.com/ethereum/wiki/wiki/JSON-RPC#db_puthex - - TESTED - ''' - if not value.startswith('0x'): - value = '0x{}'.format(value) - warnings.warn('deprecated', DeprecationWarning) - return self.web3.db.putHex(db_name, key, value) - - def db_getHex(self, db_name, key): - ''' - https://github.com/ethereum/wiki/wiki/JSON-RPC#db_gethex - - TESTED - ''' - warnings.warn('deprecated', DeprecationWarning) - return self.web3.db.getHex(db_name, key) + return self.web3.manager.request_blocking('eth_submitHashrate', [hash_rate, client_id]) def shh_version(self): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#shh_version http://web3py.readthedocs.io/en/latest/web3.shh.html#web3.shh.Shh.version - N/A ''' return self.web3.shh.version() def shh_post(self, topics, payload, priority, ttl, from_=None, to=None): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#shh_post - - NEEDS TESTING + http://web3py.readthedocs.io/en/latest/web3.shh.html#web3.shh.Shh.post + # only topics and payload are necessary according to web3.py ''' whisper_object = { 'from': from_, 'to': to, - 'topics': topics, - 'payload': payload, - 'priority': hex(priority), - 'ttl': hex(ttl), + 'topics': web3.toHex(topics), + 'payload': web3.toHex(payload), + 'priority': web3.toHex(priority), + 'ttl': web3.toHex(ttl) } - return self._call('shh_post', [whisper_object]) + return self.web3.shh.post(whisper_object) def shh_newIdentity(self): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#shh_newidentity - - N/A + http://web3py.readthedocs.io/en/latest/web3.shh.html#web3.shh.Shh.newIdentity ''' - return self._call('shh_newIdentity') + return self.web3.shh.newIdentity() def shh_hasIdentity(self, address): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#shh_hasidentity - - NEEDS TESTING + http://web3py.readthedocs.io/en/latest/web3.shh.html#web3.shh.Shh.hasIdentity ''' - return self._call('shh_hasIdentity', [address]) + return self.web3.shh.hasIdentity(address) def shh_newGroup(self): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#shh_newgroup - - N/A + http://web3py.readthedocs.io/en/latest/web3.shh.html#web3.shh.Shh.newGroup ''' - return self._call('shh_newGroup') + return self.web3.shh.newGroup() def shh_addToGroup(self): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#shh_addtogroup - - NEEDS TESTING + http://web3py.readthedocs.io/en/latest/web3.shh.html#web3.shh.Shh.addToGroup ''' - return self._call('shh_addToGroup') + return self.web3.shh.addToGroup() def shh_newFilter(self, to, topics): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#shh_newfilter - - NEEDS TESTING + http://web3py.readthedocs.io/en/latest/web3.shh.html#web3.shh.Shh.filter + # to is optional ''' _filter = { - 'to': to, 'topics': topics, + 'to': to } - return self._call('shh_newFilter', [_filter]) + return self.web3.shh.newFilter(_filter) def shh_uninstallFilter(self, filter_id): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#shh_uninstallfilter - - NEEDS TESTING + http://web3py.readthedocs.io/en/latest/web3.shh.html#web3.shh.Shh.uninstallFilter ''' - return self._call('shh_uninstallFilter', [filter_id]) + return self.web3.shh.uninstallFilter(filter_id) def shh_getFilterChanges(self, filter_id): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#shh_getfilterchanges http://web3py.readthedocs.io/en/latest/web3.eth.html?highlight=syncing#web3.eth.Eth.getFilterChanges - NEEDS TESTING ''' filt = self.web3.eth.filter() - return self._call('shh_getFilterChanges', [filter_id]) + return self.web3.shh.getFilterChanges(filter_id) def shh_getMessages(self, filter_id): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#shh_getmessages - - NEEDS TESTING + http://web3py.readthedocs.io/en/latest/web3.shh.html#web3.shh.Shh.getMessages ''' - return self._call('shh_getMessages', [filter_id]) + return self.web3.shh.getMessages(filter_id) def getBlockRlp(self, number=0): - return self._call('debug_getBlockRlp', [number]) + #not accessible with convenience functions + + return self.web3.manager.request_blocking('debug_getBlockRlp', [number]) def traceTransaction(self, txHash): #atm not directly accessible, so something like this is needed #https://github.com/pipermerriam/web3.py/issues/308 - #web3.manager.request_blocking('debug_traceTransaction', ["TX_ID_AS_HEX_STRING"]) + return self.web3.manager.request_blocking('debug_traceTransaction', [txHash]) diff --git a/mythril/rpc/client.py b/mythril/rpc/client.py index ef826ce0..f5bd4aa2 100644 --- a/mythril/rpc/client.py +++ b/mythril/rpc/client.py @@ -1,5 +1,4 @@ import json -import warnings import requests from requests.adapters import HTTPAdapter @@ -587,44 +586,6 @@ class EthJsonRpc(object): ''' return self._call('eth_submitHashrate', [hex(hash_rate), client_id]) - def db_putString(self, db_name, key, value): - ''' - https://github.com/ethereum/wiki/wiki/JSON-RPC#db_putstring - - TESTED - ''' - warnings.warn('deprecated', DeprecationWarning) - return self._call('db_putString', [db_name, key, value]) - - def db_getString(self, db_name, key): - ''' - https://github.com/ethereum/wiki/wiki/JSON-RPC#db_getstring - - TESTED - ''' - warnings.warn('deprecated', DeprecationWarning) - return self._call('db_getString', [db_name, key]) - - def db_putHex(self, db_name, key, value): - ''' - https://github.com/ethereum/wiki/wiki/JSON-RPC#db_puthex - - TESTED - ''' - if not value.startswith('0x'): - value = '0x{}'.format(value) - warnings.warn('deprecated', DeprecationWarning) - return self._call('db_putHex', [db_name, key, value]) - - def db_getHex(self, db_name, key): - ''' - https://github.com/ethereum/wiki/wiki/JSON-RPC#db_gethex - - TESTED - ''' - warnings.warn('deprecated', DeprecationWarning) - return self._call('db_getHex', [db_name, key]) - def shh_version(self): ''' https://github.com/ethereum/wiki/wiki/JSON-RPC#shh_version diff --git a/mythril/support/__init__.py b/mythril/support/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mythril/support/loader.py b/mythril/support/loader.py new file mode 100644 index 00000000..b4581d5b --- /dev/null +++ b/mythril/support/loader.py @@ -0,0 +1,43 @@ +from mythril.ether.ethcontract import ETHContract +import logging +import re + +class DynLoader: + + def __init__(self, eth): + self.eth = eth + + def dynld(self, contract_address, dependency_address): + + logging.info("Dynld at contract " + contract_address + ": " + dependency_address) + + # Hack-ish + + m = re.match(r'(0x[0-9a-fA-F]{40})', dependency_address) + + if (m): + dependency_address = m.group(1) + + else: + m = re.search(r'storage_(\d+)', dependency_address) + + if (m): + idx = int(m.group(1)) + logging.info("Dynamic contract address at storage index " + str(idx)) + + dependency_address = "0x" + self.eth.eth_getStorageAt(contract_address, position=idx, block='latest')[26:] + + else: + logging.info("Unable to resolve address.") + return None + + + logging.info("Dependency address: " + dependency_address) + + code = self.eth.eth_getCode(dependency_address) + + if (code == "0x"): + return None + else: + contract = ETHContract(self.eth.eth_getCode(dependency_address), name=dependency_address, address=dependency_address) + return contract.as_dict() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 19d9f446..50a451ee 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,6 @@ ethereum>=2.0.4 ZODB>=5.3.0 z3-solver>=4.5 web3 -laser-ethereum>=0.1.4 +laser-ethereum==0.2.4 +requests +BTrees diff --git a/setup.py b/setup.py index 4df56d5e..0469194c 100755 --- a/setup.py +++ b/setup.py @@ -219,7 +219,7 @@ security community. setup( name='mythril', - version='0.5.6', + version='0.7.6', description='A reversing and bug hunting framework for the Ethereum blockchain', long_description=long_description, @@ -256,7 +256,9 @@ setup( 'web3', 'ZODB>=5.3.0', 'z3-solver>=4.5', - 'laser-ethereum>=0.1.6' + 'laser-ethereum==0.2.4', + 'requests', + 'BTrees' ], python_requires='>=3.5', diff --git a/static/callgraph6.png b/static/callgraph6.png deleted file mode 100644 index 6830ed40..00000000 Binary files a/static/callgraph6.png and /dev/null differ diff --git a/static/callgraph7.png b/static/callgraph7.png new file mode 100644 index 00000000..b1cad798 Binary files /dev/null and b/static/callgraph7.png differ diff --git a/static/mythril.html b/static/mythril.html index a9f15e60..4dfd1546 100644 --- a/static/mythril.html +++ b/static/mythril.html @@ -2,11 +2,11 @@ @@ -68,43 +68,61 @@ }, physics:{ - enabled: true, + enabled: false, } } var nodes = [ -{id: 0, size: 150, 'label': '0 PUSH1 0x60\n2 PUSH1 0x40\n4 MSTORE\n5 PUSH1 0x04\n7 CALLDATASIZE\n8 LT\n9 PUSH2 0x0057\n12 JUMPI\n13 PUSH1 0x00\n15 CALLDATALOAD\n16 PUSH29 0x01000000(...)\n46 SWAP1\n47 DIV\n48 PUSH4 0xffffffff\n53 AND\n54 DUP1\n55 PUSH4 0x3ccfd60b\n60 EQ\n61 PUSH2 0x005c\n64 JUMPI\n65 DUP1\n66 PUSH4 0xf8b2cb4f\n71 EQ\n72 PUSH2 0x0071\n75 JUMPI\n76 DUP1\n77 PUSH4 0xfcddd056\n82 EQ\n83 PUSH2 0x00be\n86 JUMPI\n87 JUMPDEST\n88 PUSH1 0x00\n90 DUP1\n91 REVERT\n'}, -{id: 198, size: 150, 'label': '198 JUMPDEST\n199 STOP\n'}, -{id: 113, size: 150, 'label': '113 getBalance(address)\n114 CALLVALUE\n115 ISZERO\n116 PUSH2 0x007c\n119 JUMPI\n120 PUSH1 0x00\n122 DUP1\n123 REVERT\n'}, -{id: 168, size: 150, 'label': '168 JUMPDEST\n169 PUSH1 0x40\n171 MLOAD\n172 DUP1\n173 DUP3\n174 DUP2\n175 MSTORE\n176 PUSH1 0x20\n178 ADD\n179 SWAP2\n180 POP\n181 POP\n182 PUSH1 0x40\n184 MLOAD\n185 DUP1\n186 SWAP2\n187 SUB\n188 SWAP1\n189 RETURN\n'}, -{id: 92, size: 150, 'label': '92 withdraw()\n93 CALLVALUE\n94 ISZERO\n95 PUSH2 0x0067\n98 JUMPI\n99 PUSH1 0x00\n101 DUP1\n102 REVERT\n'}, -{id: 103, size: 150, 'label': '103 JUMPDEST\n104 PUSH2 0x006f\n107 PUSH2 0x00c8\n110 JUMP\n'}, -{id: 190, size: 150, 'label': '190 _function_0xfcddd056\n191 PUSH2 0x00c6\n194 PUSH2 0x021c\n197 JUMP\n'}, -{id: 398, size: 150, 'label': '398 JUMPDEST\n399 PUSH1 0x00\n401 DUP1\n402 PUSH1 0x00\n404 CALLER\n405 PUSH20 0xffffffff(...)\n426 AND\n427 PUSH20 0xffffffff(...)\n448 AND\n449 DUP2\n450 MSTORE\n451 PUSH1 0x20\n453 ADD\n454 SWAP1\n455 DUP2\n456 MSTORE\n457 PUSH1 0x20\n459 ADD\n460 PUSH1 0x00\n462 SHA3\n463 DUP2\n464 SWAP1\n465 SSTORE\n466 POP\n467 JUMP\n'}, -{id: 111, size: 150, 'label': '111 JUMPDEST\n112 STOP\n'}, -{id: 200, size: 150, 'label': '200 JUMPDEST\n201 PUSH1 0x00\n203 DUP1\n204 PUSH1 0x00\n206 CALLER\n207 PUSH20 0xffffffff(...)\n228 AND\n229 PUSH20 0xffffffff(...)\n250 AND\n251 DUP2\n252 MSTORE\n253 PUSH1 0x20\n255 ADD\n256 SWAP1\n257 DUP2\n258 MSTORE\n259 PUSH1 0x20\n261 ADD\n262 PUSH1 0x00\n264 SHA3\n265 SLOAD\n266 GT\n267 ISZERO\n268 ISZERO\n269 PUSH2 0x0115\n272 JUMPI\n273 PUSH1 0x00\n275 DUP1\n276 REVERT\n'}, -{id: 468, size: 150, 'label': '468 JUMPDEST\n469 PUSH1 0x00\n471 DUP1\n472 PUSH1 0x00\n474 DUP4\n475 PUSH20 0xffffffff(...)\n496 AND\n497 PUSH20 0xffffffff(...)\n518 AND\n519 DUP2\n520 MSTORE\n521 PUSH1 0x20\n523 ADD\n524 SWAP1\n525 DUP2\n526 MSTORE\n527 PUSH1 0x20\n529 ADD\n530 PUSH1 0x00\n532 SHA3\n533 SLOAD\n534 SWAP1\n535 POP\n536 SWAP2\n537 SWAP1\n538 POP\n539 JUMP\n'}, -{id: 277, size: 150, 'label': '277 JUMPDEST\n278 CALLER\n279 PUSH20 0xffffffff(...)\n300 AND\n301 PUSH1 0x00\n303 DUP1\n304 CALLER\n305 PUSH20 0xffffffff(...)\n326 AND\n327 PUSH20 0xffffffff(...)\n348 AND\n349 DUP2\n350 MSTORE\n351 PUSH1 0x20\n353 ADD\n354 SWAP1\n355 DUP2\n356 MSTORE\n357 PUSH1 0x20\n359 ADD\n360 PUSH1 0x00\n362 SHA3\n363 SLOAD\n364 PUSH1 0x40\n366 MLOAD\n367 PUSH1 0x00\n369 PUSH1 0x40\n371 MLOAD\n372 DUP1\n373 DUP4\n374 SUB\n375 DUP2\n376 DUP6\n377 DUP8\n378 PUSH2 0x8796\n381 GAS\n382 SUB\n383 CALL\n384 SWAP3\n385 POP\n386 POP\n387 POP\n388 ISZERO\n389 ISZERO\n390 PUSH2 0x018e\n393 JUMPI\n394 PUSH1 0x00\n396 DUP1\n397 REVERT\n'}, -{id: 87, size: 150, 'label': '87 JUMPDEST\n88 PUSH1 0x00\n90 DUP1\n91 REVERT\n'}, -{id: 124, size: 150, 'label': '124 JUMPDEST\n125 PUSH2 0x00a8\n128 PUSH1 0x04\n130 DUP1\n131 DUP1\n132 CALLDATALOAD\n133 PUSH20 0xffffffff(...)\n154 AND\n155 SWAP1\n156 PUSH1 0x20\n158 ADD\n159 SWAP1\n160 SWAP2\n161 SWAP1\n162 POP\n163 POP\n164 PUSH2 0x01d4\n167 JUMP\n'}, -{id: 540, size: 150, 'label': '540 JUMPDEST\n541 CALLVALUE\n542 PUSH1 0x00\n544 DUP1\n545 CALLER\n546 PUSH20 0xffffffff(...)\n567 AND\n568 PUSH20 0xffffffff(...)\n589 AND\n590 DUP2\n591 MSTORE\n592 PUSH1 0x20\n594 ADD\n595 SWAP1\n596 DUP2\n597 MSTORE\n598 PUSH1 0x20\n600 ADD\n601 PUSH1 0x00\n603 SHA3\n604 PUSH1 0x00\n606 DUP3\n607 DUP3\n608 SLOAD\n609 ADD\n610 SWAP3\n611 POP\n612 POP\n613 DUP2\n614 SWAP1\n615 SSTORE\n616 POP\n617 JUMP\n'} +{id: 256, size: 150, 'label': '256 JUMPDEST\n257 PUSH1 0x40\n259 MLOAD\n260 DUP1\n261 DUP3\n262 DUP2\n263 MSTORE\n264 PUSH1 0x20\n266 ADD\n267 SWAP2\n268 POP\n269 POP\n270 PUSH1 0x40\n272 MLOAD\n273 DUP1\n274 SWAP2\n275 SUB\n276 SWAP1\n277 RETURN\n'}, +{id: 0, size: 150, 'label': '0 PUSH1 0x60\n2 PUSH1 0x40\n4 MSTORE\n5 PUSH1 0x04\n7 CALLDATASIZE\n8 LT\n9 PUSH2 0x006d\n12 JUMPI\n13 PUSH1 0x00\n15 CALLDATALOAD\n16 PUSH29 0x01000000(...)\n46 SWAP1\n47 DIV\n48 PUSH4 0xffffffff\n53 AND\n54 DUP1\n55 PUSH4 0x1803334e\n60 EQ\n61 PUSH2 0x0072\n64 JUMPI\n65 DUP1\n66 PUSH4 0x2e1a7d4d\n71 EQ\n72 PUSH2 0x0087\n75 JUMPI\n76 DUP1\n77 PUSH4 0x41c0e1b5\n82 EQ\n83 PUSH2 0x00aa\n86 JUMPI\n87 DUP1\n88 PUSH4 0x69774c2d\n93 EQ\n94 PUSH2 0x00bf\n97 JUMPI\n98 DUP1\n99 PUSH4 0xf8b2cb4f\n104 EQ\n105 PUSH2 0x00c9\n108 JUMPI\n109 JUMPDEST\n110 PUSH1 0x00\n112 DUP1\n113 REVERT\n'}, +{id: 770, size: 150, 'label': '770 JUMPDEST\n771 PUSH1 0x00\n773 DUP1\n774 PUSH1 0x00\n776 DUP4\n777 PUSH20 0xffffffff(...)\n798 AND\n799 PUSH20 0xffffffff(...)\n820 AND\n821 DUP2\n822 MSTORE\n823 PUSH1 0x20\n825 ADD\n826 SWAP1\n827 DUP2\n828 MSTORE\n829 PUSH1 0x20\n831 ADD\n832 PUSH1 0x00\n834 SHA3\n835 SLOAD\n836 SWAP1\n837 POP\n838 SWAP2\n839 SWAP1\n840 POP\n841 JUMP\n'}, +{id: 133, size: 150, 'label': '133 JUMPDEST\n134 STOP\n'}, +{id: 135, size: 150, 'label': '135 withdraw(uint256)\n136 CALLVALUE\n137 ISZERO\n138 PUSH2 0x0092\n141 JUMPI\n142 PUSH1 0x00\n144 DUP1\n145 REVERT\n'}, +{id: 201, size: 150, 'label': '201 getBalance(address)\n202 CALLVALUE\n203 ISZERO\n204 PUSH2 0x00d4\n207 JUMPI\n208 PUSH1 0x00\n210 DUP1\n211 REVERT\n'}, +{id: 653, size: 150, 'label': '653 JUMPDEST\n654 CALLER\n655 PUSH20 0xffffffff(...)\n676 AND\n677 SUICIDE\n'}, +{id: 146, size: 150, 'label': '146 JUMPDEST\n147 PUSH2 0x00a8\n150 PUSH1 0x04\n152 DUP1\n153 DUP1\n154 CALLDATALOAD\n155 SWAP1\n156 PUSH1 0x20\n158 ADD\n159 SWAP1\n160 SWAP2\n161 SWAP1\n162 POP\n163 POP\n164 PUSH2 0x0159\n167 JUMP\n'}, +{id: 212, size: 150, 'label': '212 JUMPDEST\n213 PUSH2 0x0100\n216 PUSH1 0x04\n218 DUP1\n219 DUP1\n220 CALLDATALOAD\n221 PUSH20 0xffffffff(...)\n242 AND\n243 SWAP1\n244 PUSH1 0x20\n246 ADD\n247 SWAP1\n248 SWAP2\n249 SWAP1\n250 POP\n251 POP\n252 PUSH2 0x0302\n255 JUMP\n'}, +{id: 278, size: 150, 'label': '278 JUMPDEST\n279 CALLER\n280 PUSH1 0x01\n282 PUSH1 0x00\n284 PUSH2 0x0100\n287 EXP\n288 DUP2\n289 SLOAD\n290 DUP2\n291 PUSH20 0xffffffff(...)\n312 MUL\n313 NOT\n314 AND\n315 SWAP1\n316 DUP4\n317 PUSH20 0xffffffff(...)\n338 AND\n339 MUL\n340 OR\n341 SWAP1\n342 SSTORE\n343 POP\n344 JUMP\n'}, +{id: 345, size: 150, 'label': '345 JUMPDEST\n346 PUSH1 0x00\n348 DUP1\n349 PUSH1 0x00\n351 CALLER\n352 PUSH20 0xffffffff(...)\n373 AND\n374 PUSH20 0xffffffff(...)\n395 AND\n396 DUP2\n397 MSTORE\n398 PUSH1 0x20\n400 ADD\n401 SWAP1\n402 DUP2\n403 MSTORE\n404 PUSH1 0x20\n406 ADD\n407 PUSH1 0x00\n409 SHA3\n410 SLOAD\n411 EQ\n412 ISZERO\n413 PUSH2 0x01a5\n416 JUMPI\n417 PUSH1 0x00\n419 DUP1\n420 REVERT\n'}, +{id: 421, size: 150, 'label': '421 JUMPDEST\n422 DUP1\n423 PUSH1 0x00\n425 DUP1\n426 CALLER\n427 PUSH20 0xffffffff(...)\n448 AND\n449 PUSH20 0xffffffff(...)\n470 AND\n471 DUP2\n472 MSTORE\n473 PUSH1 0x20\n475 ADD\n476 SWAP1\n477 DUP2\n478 MSTORE\n479 PUSH1 0x20\n481 ADD\n482 PUSH1 0x00\n484 SHA3\n485 PUSH1 0x00\n487 DUP3\n488 DUP3\n489 SLOAD\n490 SUB\n491 SWAP3\n492 POP\n493 POP\n494 DUP2\n495 SWAP1\n496 SSTORE\n497 POP\n498 CALLER\n499 PUSH20 0xffffffff(...)\n520 AND\n521 PUSH2 0x08fc\n524 DUP3\n525 SWAP1\n526 DUP2\n527 ISZERO\n528 MUL\n529 SWAP1\n530 PUSH1 0x40\n532 MLOAD\n533 PUSH1 0x00\n535 PUSH1 0x40\n537 MLOAD\n538 DUP1\n539 DUP4\n540 SUB\n541 DUP2\n542 DUP6\n543 DUP9\n544 DUP9\n545 CALL\n546 SWAP4\n547 POP\n548 POP\n549 POP\n550 POP\n551 ISZERO\n552 ISZERO\n553 PUSH2 0x0231\n556 JUMPI\n557 PUSH1 0x00\n559 DUP1\n560 REVERT\n'}, +{id: 678, size: 150, 'label': '678 JUMPDEST\n679 PUSH1 0x00\n681 CALLVALUE\n682 EQ\n683 ISZERO\n684 PUSH2 0x02b4\n687 JUMPI\n688 PUSH1 0x00\n690 DUP1\n691 REVERT\n'}, +{id: 168, size: 150, 'label': '168 JUMPDEST\n169 STOP\n'}, +{id: 170, size: 150, 'label': '170 kill()\n171 CALLVALUE\n172 ISZERO\n173 PUSH2 0x00b5\n176 JUMPI\n177 PUSH1 0x00\n179 DUP1\n180 REVERT\n'}, +{id: 199, size: 150, 'label': '199 JUMPDEST\n200 STOP\n'}, +{id: 109, size: 150, 'label': '109 JUMPDEST\n110 PUSH1 0x00\n112 DUP1\n113 REVERT\n'}, +{id: 561, size: 150, 'label': '561 JUMPDEST\n562 POP\n563 JUMP\n'}, +{id: 114, size: 150, 'label': '114 _function_0x1803334e\n115 CALLVALUE\n116 ISZERO\n117 PUSH2 0x007d\n120 JUMPI\n121 PUSH1 0x00\n123 DUP1\n124 REVERT\n'}, +{id: 692, size: 150, 'label': '692 JUMPDEST\n693 CALLVALUE\n694 PUSH1 0x00\n696 DUP1\n697 CALLER\n698 PUSH20 0xffffffff(...)\n719 AND\n720 PUSH20 0xffffffff(...)\n741 AND\n742 DUP2\n743 MSTORE\n744 PUSH1 0x20\n746 ADD\n747 SWAP1\n748 DUP2\n749 MSTORE\n750 PUSH1 0x20\n752 ADD\n753 PUSH1 0x00\n755 SHA3\n756 PUSH1 0x00\n758 DUP3\n759 DUP3\n760 SLOAD\n761 ADD\n762 SWAP3\n763 POP\n764 POP\n765 DUP2\n766 SWAP1\n767 SSTORE\n768 POP\n769 JUMP\n'}, +{id: 181, size: 150, 'label': '181 JUMPDEST\n182 PUSH2 0x00bd\n185 PUSH2 0x0234\n188 JUMP\n'}, +{id: 564, size: 150, 'label': '564 JUMPDEST\n565 PUSH1 0x01\n567 PUSH1 0x00\n569 SWAP1\n570 SLOAD\n571 SWAP1\n572 PUSH2 0x0100\n575 EXP\n576 SWAP1\n577 DIV\n578 PUSH20 0xffffffff(...)\n599 AND\n600 PUSH20 0xffffffff(...)\n621 AND\n622 CALLER\n623 PUSH20 0xffffffff(...)\n644 AND\n645 EQ\n646 ISZERO\n647 ISZERO\n648 PUSH2 0x028d\n651 JUMPI\n652 INVALID\n'}, +{id: 125, size: 150, 'label': '125 JUMPDEST\n126 PUSH2 0x0085\n129 PUSH2 0x0116\n132 JUMP\n'}, +{id: 191, size: 150, 'label': '191 _function_0x69774c2d\n192 PUSH2 0x00c7\n195 PUSH2 0x02a6\n198 JUMP\n'} ]; var edges = [ -{from: 0, to: 87, 'arrows': 'to', 'label': 'Not(ULE(4, calldatasize))', 'smooth': {'type': 'cubicBezier'}}, -{from: 398, to: 111, 'arrows': 'to', 'label': '', 'smooth': {'type': 'cubicBezier'}}, -{from: 277, to: 398, 'arrows': 'to', 'label': 'Not(retval_384_115 == 0)', 'smooth': {'type': 'cubicBezier'}}, -{from: 200, to: 277, 'arrows': 'to', 'label': 'Not(storage_sha_hash == 0)', 'smooth': {'type': 'cubicBezier'}}, -{from: 103, to: 200, 'arrows': 'to', 'label': '', 'smooth': {'type': 'cubicBezier'}}, -{from: 92, to: 103, 'arrows': 'to', 'label': 'callvalue == 0', 'smooth': {'type': 'cubicBezier'}}, -{from: 0, to: 92, 'arrows': 'to', 'label': 'Extract0xff,0xe0, calldata_0) ==0x3ccfd60b', 'smooth': {'type': 'cubicBezier'}}, -{from: 468, to: 168, 'arrows': 'to', 'label': '', 'smooth': {'type': 'cubicBezier'}}, -{from: 124, to: 468, 'arrows': 'to', 'label': '', 'smooth': {'type': 'cubicBezier'}}, -{from: 113, to: 124, 'arrows': 'to', 'label': 'callvalue == 0', 'smooth': {'type': 'cubicBezier'}}, -{from: 0, to: 113, 'arrows': 'to', 'label': 'Extract0xff,0xe0, calldata_0) ==0xf8b2cb4f', 'smooth': {'type': 'cubicBezier'}}, -{from: 540, to: 198, 'arrows': 'to', 'label': '', 'smooth': {'type': 'cubicBezier'}}, -{from: 190, to: 540, 'arrows': 'to', 'label': '', 'smooth': {'type': 'cubicBezier'}}, -{from: 0, to: 190, 'arrows': 'to', 'label': 'Extract0xff,0xe0, calldata_0) ==0xfcddd056', 'smooth': {'type': 'cubicBezier'}} +{from: 0, to: 109, 'arrows': 'to', 'label': 'Not(ULE(4, calldatasize))', 'smooth': {'type': 'cubicBezier'}}, +{from: 278, to: 133, 'arrows': 'to', 'label': '', 'smooth': {'type': 'cubicBezier'}}, +{from: 125, to: 278, 'arrows': 'to', 'label': '', 'smooth': {'type': 'cubicBezier'}}, +{from: 114, to: 125, 'arrows': 'to', 'label': 'callvalue == 0', 'smooth': {'type': 'cubicBezier'}}, +{from: 0, to: 114, 'arrows': 'to', 'label': 'Extract(0xff, 0xe0, calldata_0) == 0x1803334e', 'smooth': {'type': 'cubicBezier'}}, +{from: 561, to: 168, 'arrows': 'to', 'label': '', 'smooth': {'type': 'cubicBezier'}}, +{from: 421, to: 561, 'arrows': 'to', 'label': 'Not(retval_546_333 == 0)', 'smooth': {'type': 'cubicBezier'}}, +{from: 345, to: 421, 'arrows': 'to', 'label': 'Not(storage_98ea2e05 == 0)', 'smooth': {'type': 'cubicBezier'}}, +{from: 146, to: 345, 'arrows': 'to', 'label': '', 'smooth': {'type': 'cubicBezier'}}, +{from: 135, to: 146, 'arrows': 'to', 'label': 'callvalue == 0', 'smooth': {'type': 'cubicBezier'}}, +{from: 0, to: 135, 'arrows': 'to', 'label': 'Extract(0xff, 0xe0, calldata_0) == 0x2e1a7d4d', 'smooth': {'type': 'cubicBezier'}}, +{from: 564, to: 653, 'arrows': 'to', 'label': 'Extract(0x9f, 0, caller) == Extract(0xa7, 8, storage_1)', 'smooth': {'type': 'cubicBezier'}}, +{from: 181, to: 564, 'arrows': 'to', 'label': '', 'smooth': {'type': 'cubicBezier'}}, +{from: 170, to: 181, 'arrows': 'to', 'label': 'callvalue == 0', 'smooth': {'type': 'cubicBezier'}}, +{from: 0, to: 170, 'arrows': 'to', 'label': 'Extract(0xff, 0xe0, calldata_0) == 0x41c0e1b5', 'smooth': {'type': 'cubicBezier'}}, +{from: 692, to: 199, 'arrows': 'to', 'label': '', 'smooth': {'type': 'cubicBezier'}}, +{from: 678, to: 692, 'arrows': 'to', 'label': 'Not(callvalue == 0)', 'smooth': {'type': 'cubicBezier'}}, +{from: 191, to: 678, 'arrows': 'to', 'label': '', 'smooth': {'type': 'cubicBezier'}}, +{from: 0, to: 191, 'arrows': 'to', 'label': 'Extract(0xff, 0xe0, calldata_0) == 0x69774c2d', 'smooth': {'type': 'cubicBezier'}}, +{from: 770, to: 256, 'arrows': 'to', 'label': '', 'smooth': {'type': 'cubicBezier'}}, +{from: 212, to: 770, 'arrows': 'to', 'label': '', 'smooth': {'type': 'cubicBezier'}}, +{from: 201, to: 212, 'arrows': 'to', 'label': 'callvalue == 0', 'smooth': {'type': 'cubicBezier'}}, +{from: 0, to: 201, 'arrows': 'to', 'label': 'Extract(0xff, 0xe0, calldata_0) == 0xf8b2cb4f', 'smooth': {'type': 'cubicBezier'}} ]; diff --git a/tests/cmline_test.py b/tests/cmline_test.py new file mode 100644 index 00000000..93e044b5 --- /dev/null +++ b/tests/cmline_test.py @@ -0,0 +1,19 @@ +import unittest +import os +from subprocess import check_output + + +class CommandLineToolTestCase(unittest.TestCase): + + def runTest(self): + + script_path = os.path.dirname(os.path.realpath(__file__)) + myth = os.path.join(script_path, '..', 'myth') + + out = check_output([myth,'-d','-c', '0x5050']).decode("UTF-8") + + self.assertEqual('0 POP\n1 POP\n', out) + + out = check_output([myth,'-d', os.path.join(script_path,'testdata','metacoin.sol')]).decode("UTF-8") + + self.assertIn('0 PUSH1 0x60\n2 PUSH1 0x40', out) \ No newline at end of file diff --git a/tests/disassembler_test.py b/tests/disassembler_test.py index abce8ede..8ba31e0a 100644 --- a/tests/disassembler_test.py +++ b/tests/disassembler_test.py @@ -2,17 +2,10 @@ import unittest from mythril.disassembler.disassembly import Disassembly - - -class ETHContractTestCase(unittest.TestCase): - - def setUp(self): - self.code = "0x606060405236156100ca5763ffffffff60e060020a600035041663054f7d9c81146100d3578063095c21e3146100f45780630ba50baa146101165780631a3719321461012857806366529e3f14610153578063682789a81461017257806389f21efc146101915780638da5cb5b146101ac5780638f4ffcb1146101d55780639a1f2a5714610240578063b5f522f71461025b578063bd94b005146102b6578063c5ab5a13146102c8578063cc424839146102f1578063deb077b914610303578063f3fef3a314610322575b6100d15b5b565b005b34610000576100e0610340565b604080519115158252519081900360200190f35b3461000057610104600435610361565b60408051918252519081900360200190f35b34610000576100d1600435610382565b005b3461000057610104600160a060020a03600435166103b0565b60408051918252519081900360200190f35b346100005761010461041e565b60408051918252519081900360200190f35b3461000057610104610424565b60408051918252519081900360200190f35b34610000576100d1600160a060020a036004351661042b565b005b34610000576101b961046f565b60408051600160a060020a039092168252519081900360200190f35b3461000057604080516020600460643581810135601f81018490048402850184019095528484526100d1948235600160a060020a039081169560248035966044359093169594608494929391019190819084018382808284375094965061048595505050505050565b005b34610000576100d1600160a060020a03600435166106e7565b005b346100005761026b60043561072b565b60408051600160a060020a0390991689526020890197909752878701959095526060870193909352608086019190915260a085015260c084015260e083015251908190036101000190f35b34610000576100d160043561077a565b005b34610000576101b9610830565b60408051600160a060020a039092168252519081900360200190f35b34610000576100d160043561083f565b005b34610000576101046108a1565b60408051918252519081900360200190f35b34610000576100d1600160a060020a03600435166024356108a7565b005b60015474010000000000000000000000000000000000000000900460ff1681565b600681815481101561000057906000526020600020900160005b5054905081565b600054600160a060020a036301000000909104811690331681146103a557610000565b60038290555b5b5050565b6005546040805160006020918201819052825160e260020a631d010437028152600160a060020a03868116600483015293519194939093169263740410dc92602480830193919282900301818787803b156100005760325a03f115610000575050604051519150505b919050565b60035481565b6006545b90565b600054600160a060020a0363010000009091048116903316811461044e57610000565b60018054600160a060020a031916600160a060020a0384161790555b5b5050565b60005463010000009004600160a060020a031681565b6000600060006000600060006000600160149054906101000a900460ff16156104ad57610000565b87600081518110156100005760209101015160005460f860020a918290048202975002600160f860020a031990811690871614156105405760009450600196505b600587101561053057878781518110156100005790602001015160f860020a900460f860020a0260f860020a900485610100020194505b6001909601956104ee565b61053b8b868c610955565b6106d6565b600054610100900460f860020a02600160f860020a0319908116908716141561069e57506001955060009250829150819050805b60058710156105b657878781518110156100005790602001015160f860020a900460f860020a0260f860020a900481610100020190505b600190960195610574565b600596505b60098710156105fd57878781518110156100005790602001015160f860020a900460f860020a0260f860020a900484610100020193505b6001909601956105bb565b600996505b600d87101561064457878781518110156100005790602001015160f860020a900460f860020a0260f860020a900483610100020192505b600190960195610602565b600d96505b601187101561068b57878781518110156100005790602001015160f860020a900460f860020a0260f860020a900482610100020191505b600190960195610649565b61053b8b828c878787610bc4565b6106d6565b60005462010000900460f860020a02600160f860020a031990811690871614156106d15761053b8b8b610e8e565b6106d6565b610000565b5b5b5b5b5050505050505050505050565b600054600160a060020a0363010000009091048116903316811461070a57610000565b60058054600160a060020a031916600160a060020a0384161790555b5b5050565b600760208190526000918252604090912080546001820154600283015460038401546004850154600586015460068701549690970154600160a060020a03909516969395929491939092909188565b600081815260076020526040812054600160a060020a0390811690331681146107a257610000565b600083815260076020526040902080546004820154600583015460038401549395506107dc93600160a060020a039093169291029061105e565b50600060058301556107ed83611151565b6040805184815290517fb5dc9baf0cb4e7e4759fa12eadebddf9316e26147d5a9ae150c4228d5a1dd23f9181900360200190a161082933611244565b5b5b505050565b600154600160a060020a031681565b600054600160a060020a0363010000009091048116903316811461086257610000565b600080546040516301000000909104600160a060020a0316916108fc851502918591818181858888f1935050505015156103ab57610000565b5b5b5050565b60025481565b600054600160a060020a036301000000909104811690331681146108ca57610000565b6000805460408051602090810184905281517fa9059cbb0000000000000000000000000000000000000000000000000000000081526301000000909304600160a060020a0390811660048501526024840187905291519187169363a9059cbb9360448082019492918390030190829087803b156100005760325a03f115610000575050505b5b505050565b610100604051908101604052806000600160a060020a03168152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525060006007600085815260200190815260200160002061010060405190810160405290816000820160009054906101000a9004600160a060020a0316600160a060020a0316600160a060020a0316815260200160018201548152602001600282015481526020016003820154815260200160048201548152602001600582015481526020016006820154815260200160078201548152505091508260001415610a4857610000565b8160400151838115610000570615610a5f57610000565b6002548410610a6d57610000565b8160400151838115610000570490508160a00151811115610a8d57610000565b610a9c8584846020015161128e565b1515610aa757610000565b60a082018051829003815260008581526007602081815260409283902086518154600160a060020a031916600160a060020a038216178255918701516001820181905593870151600282015560608701516003820155608087015160048201559351600585015560c0860151600685015560e08601519390910192909255610b319190859061105e565b1515610b3c57610000565b610b518582846080015102846060015161105e565b1515610b5c57610000565b60a0820151158015610b71575060c082015115155b15610b7f57610b7f84611151565b5b6040805185815290517fb5dc9baf0cb4e7e4759fa12eadebddf9316e26147d5a9ae150c4228d5a1dd23f9181900360200190a1610bbc85611244565b5b5050505050565b831515610bd057610000565b82851415610bdd57610000565b801580610be8575081155b15610bf257610000565b80848115610000570615610c0557610000565b6005546040805160006020918201819052825160e260020a631d010437028152600160a060020a038b8116600483015293518695949094169363740410dc9360248084019491938390030190829087803b156100005760325a03f11561000057505050604051805190501015610c7a57610000565b610c8586858761128e565b1515610c9057610000565b600554604080517fbe0140a6000000000000000000000000000000000000000000000000000000008152600160a060020a03898116600483015260006024830181905260448301869052925193169263be0140a69260648084019391929182900301818387803b156100005760325a03f115610000575050506101006040519081016040528087600160a060020a03168152602001848152602001838152602001868152602001828681156100005704815260200182815260200160068054905081526020014281525060076000600254815260200190815260200160002060008201518160000160006101000a815481600160a060020a030219169083600160a060020a031602179055506020820151816001015560408201518160020155606082015181600301556080820151816004015560a0820151816005015560c0820151816006015560e0820151816007015590505060068054806001018281815481835581811511610e2757600083815260209020610e279181019083015b80821115610e235760008155600101610e0f565b5090565b5b505050916000526020600020900160005b50600280549182905560018201905560408051918252517fb5dc9baf0cb4e7e4759fa12eadebddf9316e26147d5a9ae150c4228d5a1dd23f92509081900360200190a1610e8586611244565b5b505050505050565b600354818115610000570615610ea357610000565b600160009054906101000a9004600160a060020a0316600160a060020a031663cf35bdd060016000604051602001526040518263ffffffff1660e060020a02815260040180828152602001915050602060405180830381600087803b156100005760325a03f115610000575050604080518051600080546020938401829052845160e060020a6323b872dd028152600160a060020a038981166004830152630100000090920482166024820152604481018890529451921694506323b872dd936064808201949392918390030190829087803b156100005760325a03f1156100005750506040515115159050610f9857610000565b600554600354600160a060020a039091169063be0140a6908490600190858115610000576040805160e060020a63ffffffff8816028152600160a060020a039095166004860152921515602485015204604483015251606480830192600092919082900301818387803b156100005760325a03f1156100005750505061101d82611244565b60408051600160a060020a038416815290517f30a29a0aa75376a69254bb98dbd11db423b7e8c3473fb5bf0fcba60bcbc42c4b9181900360200190a15b5050565b600081151561106c57610000565b6001546040805160006020918201819052825160e460020a630cf35bdd028152600481018790529251600160a060020a039094169363cf35bdd09360248082019493918390030190829087803b156100005760325a03f1156100005750505060405180519050600160a060020a031663a9059cbb85856000604051602001526040518363ffffffff1660e060020a0281526004018083600160a060020a0316600160a060020a0316815260200182815260200192505050602060405180830381600087803b156100005760325a03f115610000575050604051519150505b9392505050565b6000818152600760205260409020600690810154815490919060001981019081101561000057906000526020600020900160005b5054600682815481101561000057906000526020600020900160005b50556006805460001981018083559091908280158290116111e7576000838152602090206111e79181019083015b80821115610e235760008155600101610e0f565b5090565b5b50506006548314915061122d9050578060076000600684815481101561000057906000526020600020900160005b505481526020810191909152604001600020600601555b6000828152600760205260408120600601555b5050565b60045481600160a060020a031631101561128957600454604051600160a060020a0383169180156108fc02916000818181858888f19350505050151561128957610000565b5b5b50565b600081151561129c57610000565b6001546040805160006020918201819052825160e460020a630cf35bdd028152600481018790529251600160a060020a039094169363cf35bdd09360248082019493918390030190829087803b156100005760325a03f11561000057505060408051805160006020928301819052835160e060020a6323b872dd028152600160a060020a038a811660048301523081166024830152604482018a905294519490921694506323b872dd93606480840194939192918390030190829087803b156100005760325a03f115610000575050604051519150505b93925050505600a165627a7a723058204dee0e1bf170a9d122508f3e876c4a84893b12a7345591521af4ca737bb765000029" - -class DisassembyTestCase(ETHContractTestCase): +class DisassemblerTestCase(unittest.TestCase): def runTest(self): - disassembly = Disassembly(self.code) - - self.assertEqual(len(disassembly.blocks), 162, 'Disassembler error: Incorrect number of blocks generated)') \ No newline at end of file + code = "0x606060405236156100ca5763ffffffff60e060020a600035041663054f7d9c81146100d3578063095c21e3146100f45780630ba50baa146101165780631a3719321461012857806366529e3f14610153578063682789a81461017257806389f21efc146101915780638da5cb5b146101ac5780638f4ffcb1146101d55780639a1f2a5714610240578063b5f522f71461025b578063bd94b005146102b6578063c5ab5a13146102c8578063cc424839146102f1578063deb077b914610303578063f3fef3a314610322575b6100d15b5b565b005b34610000576100e0610340565b604080519115158252519081900360200190f35b3461000057610104600435610361565b60408051918252519081900360200190f35b34610000576100d1600435610382565b005b3461000057610104600160a060020a03600435166103b0565b60408051918252519081900360200190f35b346100005761010461041e565b60408051918252519081900360200190f35b3461000057610104610424565b60408051918252519081900360200190f35b34610000576100d1600160a060020a036004351661042b565b005b34610000576101b961046f565b60408051600160a060020a039092168252519081900360200190f35b3461000057604080516020600460643581810135601f81018490048402850184019095528484526100d1948235600160a060020a039081169560248035966044359093169594608494929391019190819084018382808284375094965061048595505050505050565b005b34610000576100d1600160a060020a03600435166106e7565b005b346100005761026b60043561072b565b60408051600160a060020a0390991689526020890197909752878701959095526060870193909352608086019190915260a085015260c084015260e083015251908190036101000190f35b34610000576100d160043561077a565b005b34610000576101b9610830565b60408051600160a060020a039092168252519081900360200190f35b34610000576100d160043561083f565b005b34610000576101046108a1565b60408051918252519081900360200190f35b34610000576100d1600160a060020a03600435166024356108a7565b005b60015474010000000000000000000000000000000000000000900460ff1681565b600681815481101561000057906000526020600020900160005b5054905081565b600054600160a060020a036301000000909104811690331681146103a557610000565b60038290555b5b5050565b6005546040805160006020918201819052825160e260020a631d010437028152600160a060020a03868116600483015293519194939093169263740410dc92602480830193919282900301818787803b156100005760325a03f115610000575050604051519150505b919050565b60035481565b6006545b90565b600054600160a060020a0363010000009091048116903316811461044e57610000565b60018054600160a060020a031916600160a060020a0384161790555b5b5050565b60005463010000009004600160a060020a031681565b6000600060006000600060006000600160149054906101000a900460ff16156104ad57610000565b87600081518110156100005760209101015160005460f860020a918290048202975002600160f860020a031990811690871614156105405760009450600196505b600587101561053057878781518110156100005790602001015160f860020a900460f860020a0260f860020a900485610100020194505b6001909601956104ee565b61053b8b868c610955565b6106d6565b600054610100900460f860020a02600160f860020a0319908116908716141561069e57506001955060009250829150819050805b60058710156105b657878781518110156100005790602001015160f860020a900460f860020a0260f860020a900481610100020190505b600190960195610574565b600596505b60098710156105fd57878781518110156100005790602001015160f860020a900460f860020a0260f860020a900484610100020193505b6001909601956105bb565b600996505b600d87101561064457878781518110156100005790602001015160f860020a900460f860020a0260f860020a900483610100020192505b600190960195610602565b600d96505b601187101561068b57878781518110156100005790602001015160f860020a900460f860020a0260f860020a900482610100020191505b600190960195610649565b61053b8b828c878787610bc4565b6106d6565b60005462010000900460f860020a02600160f860020a031990811690871614156106d15761053b8b8b610e8e565b6106d6565b610000565b5b5b5b5b5050505050505050505050565b600054600160a060020a0363010000009091048116903316811461070a57610000565b60058054600160a060020a031916600160a060020a0384161790555b5b5050565b600760208190526000918252604090912080546001820154600283015460038401546004850154600586015460068701549690970154600160a060020a03909516969395929491939092909188565b600081815260076020526040812054600160a060020a0390811690331681146107a257610000565b600083815260076020526040902080546004820154600583015460038401549395506107dc93600160a060020a039093169291029061105e565b50600060058301556107ed83611151565b6040805184815290517fb5dc9baf0cb4e7e4759fa12eadebddf9316e26147d5a9ae150c4228d5a1dd23f9181900360200190a161082933611244565b5b5b505050565b600154600160a060020a031681565b600054600160a060020a0363010000009091048116903316811461086257610000565b600080546040516301000000909104600160a060020a0316916108fc851502918591818181858888f1935050505015156103ab57610000565b5b5b5050565b60025481565b600054600160a060020a036301000000909104811690331681146108ca57610000565b6000805460408051602090810184905281517fa9059cbb0000000000000000000000000000000000000000000000000000000081526301000000909304600160a060020a0390811660048501526024840187905291519187169363a9059cbb9360448082019492918390030190829087803b156100005760325a03f115610000575050505b5b505050565b610100604051908101604052806000600160a060020a03168152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525060006007600085815260200190815260200160002061010060405190810160405290816000820160009054906101000a9004600160a060020a0316600160a060020a0316600160a060020a0316815260200160018201548152602001600282015481526020016003820154815260200160048201548152602001600582015481526020016006820154815260200160078201548152505091508260001415610a4857610000565b8160400151838115610000570615610a5f57610000565b6002548410610a6d57610000565b8160400151838115610000570490508160a00151811115610a8d57610000565b610a9c8584846020015161128e565b1515610aa757610000565b60a082018051829003815260008581526007602081815260409283902086518154600160a060020a031916600160a060020a038216178255918701516001820181905593870151600282015560608701516003820155608087015160048201559351600585015560c0860151600685015560e08601519390910192909255610b319190859061105e565b1515610b3c57610000565b610b518582846080015102846060015161105e565b1515610b5c57610000565b60a0820151158015610b71575060c082015115155b15610b7f57610b7f84611151565b5b6040805185815290517fb5dc9baf0cb4e7e4759fa12eadebddf9316e26147d5a9ae150c4228d5a1dd23f9181900360200190a1610bbc85611244565b5b5050505050565b831515610bd057610000565b82851415610bdd57610000565b801580610be8575081155b15610bf257610000565b80848115610000570615610c0557610000565b6005546040805160006020918201819052825160e260020a631d010437028152600160a060020a038b8116600483015293518695949094169363740410dc9360248084019491938390030190829087803b156100005760325a03f11561000057505050604051805190501015610c7a57610000565b610c8586858761128e565b1515610c9057610000565b600554604080517fbe0140a6000000000000000000000000000000000000000000000000000000008152600160a060020a03898116600483015260006024830181905260448301869052925193169263be0140a69260648084019391929182900301818387803b156100005760325a03f115610000575050506101006040519081016040528087600160a060020a03168152602001848152602001838152602001868152602001828681156100005704815260200182815260200160068054905081526020014281525060076000600254815260200190815260200160002060008201518160000160006101000a815481600160a060020a030219169083600160a060020a031602179055506020820151816001015560408201518160020155606082015181600301556080820151816004015560a0820151816005015560c0820151816006015560e0820151816007015590505060068054806001018281815481835581811511610e2757600083815260209020610e279181019083015b80821115610e235760008155600101610e0f565b5090565b5b505050916000526020600020900160005b50600280549182905560018201905560408051918252517fb5dc9baf0cb4e7e4759fa12eadebddf9316e26147d5a9ae150c4228d5a1dd23f92509081900360200190a1610e8586611244565b5b505050505050565b600354818115610000570615610ea357610000565b600160009054906101000a9004600160a060020a0316600160a060020a031663cf35bdd060016000604051602001526040518263ffffffff1660e060020a02815260040180828152602001915050602060405180830381600087803b156100005760325a03f115610000575050604080518051600080546020938401829052845160e060020a6323b872dd028152600160a060020a038981166004830152630100000090920482166024820152604481018890529451921694506323b872dd936064808201949392918390030190829087803b156100005760325a03f1156100005750506040515115159050610f9857610000565b600554600354600160a060020a039091169063be0140a6908490600190858115610000576040805160e060020a63ffffffff8816028152600160a060020a039095166004860152921515602485015204604483015251606480830192600092919082900301818387803b156100005760325a03f1156100005750505061101d82611244565b60408051600160a060020a038416815290517f30a29a0aa75376a69254bb98dbd11db423b7e8c3473fb5bf0fcba60bcbc42c4b9181900360200190a15b5050565b600081151561106c57610000565b6001546040805160006020918201819052825160e460020a630cf35bdd028152600481018790529251600160a060020a039094169363cf35bdd09360248082019493918390030190829087803b156100005760325a03f1156100005750505060405180519050600160a060020a031663a9059cbb85856000604051602001526040518363ffffffff1660e060020a0281526004018083600160a060020a0316600160a060020a0316815260200182815260200192505050602060405180830381600087803b156100005760325a03f115610000575050604051519150505b9392505050565b6000818152600760205260409020600690810154815490919060001981019081101561000057906000526020600020900160005b5054600682815481101561000057906000526020600020900160005b50556006805460001981018083559091908280158290116111e7576000838152602090206111e79181019083015b80821115610e235760008155600101610e0f565b5090565b5b50506006548314915061122d9050578060076000600684815481101561000057906000526020600020900160005b505481526020810191909152604001600020600601555b6000828152600760205260408120600601555b5050565b60045481600160a060020a031631101561128957600454604051600160a060020a0383169180156108fc02916000818181858888f19350505050151561128957610000565b5b5b50565b600081151561129c57610000565b6001546040805160006020918201819052825160e460020a630cf35bdd028152600481018790529251600160a060020a039094169363cf35bdd09360248082019493918390030190829087803b156100005760325a03f11561000057505060408051805160006020928301819052835160e060020a6323b872dd028152600160a060020a038a811660048301523081166024830152604482018a905294519490921694506323b872dd93606480840194939192918390030190829087803b156100005760325a03f115610000575050604051519150505b93925050505600a165627a7a723058204dee0e1bf170a9d122508f3e876c4a84893b12a7345591521af4ca737bb765000029" + disassembly = Disassembly(code) + self.assertEqual(len(disassembly.instruction_list), 3537) diff --git a/tests/ethcontract_test.py b/tests/ethcontract_test.py index 118cebef..b34923b7 100644 --- a/tests/ethcontract_test.py +++ b/tests/ethcontract_test.py @@ -14,9 +14,9 @@ class Getinstruction_listTestCase(ETHContractTestCase): contract = ETHContract(self.code, self.creation_code) - instruction_list = contract.get_instruction_list() + disassembly = contract.get_disassembly() - self.assertEqual(len(instruction_list), 71, 'Error disassembling code using ETHContract.get_instruction_list()') + self.assertEqual(len(disassembly.instruction_list), 71, 'Error disassembling code using ETHContract.get_instruction_list()') class GetEASMTestCase(ETHContractTestCase): diff --git a/tests/evm_test.py b/tests/evm_test.py deleted file mode 100644 index c5c2eee9..00000000 --- a/tests/evm_test.py +++ /dev/null @@ -1,2 +0,0 @@ -import unittest - diff --git a/tests/ipc_test.py b/tests/ipc_test.py deleted file mode 100644 index 9ca3c612..00000000 --- a/tests/ipc_test.py +++ /dev/null @@ -1 +0,0 @@ -import unittest \ No newline at end of file diff --git a/tests/rpc_test.py b/tests/rpc_test.py deleted file mode 100644 index c5c2eee9..00000000 --- a/tests/rpc_test.py +++ /dev/null @@ -1,2 +0,0 @@ -import unittest - diff --git a/tests/svm_test.py b/tests/svm_test.py new file mode 100644 index 00000000..acf41400 --- /dev/null +++ b/tests/svm_test.py @@ -0,0 +1,19 @@ +import unittest +from mythril.disassembler.callgraph import generate_callgraph +from mythril.disassembler.disassembly import Disassembly +from laser.ethereum import svm + + +class SVMTestCase(unittest.TestCase): + + def runTest(self): + + modules = {} + modules['0x0000000000000000000000000000000000000000'] = {'name': 'metaCoin', 'address': '0x0000000000000000000000000000000000000000', 'creation_code': '', 'code': '60606040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806327e235e314610051578063412664ae1461009e575b600080fd5b341561005c57600080fd5b610088600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506100f8565b6040518082815260200191505060405180910390f35b34156100a957600080fd5b6100de600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610110565b604051808215151515815260200191505060405180910390f35b60006020528060005260406000206000915090505481565b6000816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101561016157600090506101fe565b816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540392505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550600090505b929150505600a165627a7a72305820fd4fa106da498514e90965a45ffecc1da53a0cd8bb7a7135910f8612245a46370029'} + modules['0x0000000000000000000000000000000000000000']['disassembly'] = Disassembly(modules['0x0000000000000000000000000000000000000000']['code']) + + _svm = svm.SVM(modules) + + html = generate_callgraph(_svm, '0x0000000000000000000000000000000000000000', False) + + self.assertTrue("var nodes = [\n{id: \'metaCoin:" in html) diff --git a/tests/testdata/metacoin.sol b/tests/testdata/metacoin.sol new file mode 100644 index 00000000..e5efbf4d --- /dev/null +++ b/tests/testdata/metacoin.sol @@ -0,0 +1,15 @@ +pragma solidity ^0.4.17; + +contract metaCoin { + mapping (address => uint) public balances; + function metaCoin() public { + balances[msg.sender] = 10000; + } + + function sendToken(address receiver, uint amount) public returns(bool successful){ + if (balances[msg.sender] < amount) return false; + balances[msg.sender] -= amount; + balances[receiver] += amount; + return false; + } +} diff --git a/tests/util_test.py b/tests/util_test.py deleted file mode 100644 index c5c2eee9..00000000 --- a/tests/util_test.py +++ /dev/null @@ -1,2 +0,0 @@ -import unittest -