From 2c96300d934c27c7f7c6338b3ab5c64abb80e291 Mon Sep 17 00:00:00 2001 From: Bernhard Mueller Date: Tue, 12 Dec 2017 16:16:13 +0700 Subject: [PATCH] Add more solidity examples --- examples/discover_writes.py | 91 ---- examples/find-fallback-dcl.py | 72 --- examples/walletlibrary.sol | 464 ------------------ {samples => solidity_examples}/ether_send.sol | 0 .../integer_overflow.sol | 0 solidity_examples/reentrancy.sol | 23 + solidity_examples/underflow.sol | 20 + 7 files changed, 43 insertions(+), 627 deletions(-) delete mode 100644 examples/discover_writes.py delete mode 100644 examples/find-fallback-dcl.py delete mode 100644 examples/walletlibrary.sol rename {samples => solidity_examples}/ether_send.sol (100%) rename {samples => solidity_examples}/integer_overflow.sol (100%) create mode 100644 solidity_examples/reentrancy.sol create mode 100644 solidity_examples/underflow.sol diff --git a/examples/discover_writes.py b/examples/discover_writes.py deleted file mode 100644 index 50e7378c..00000000 --- a/examples/discover_writes.py +++ /dev/null @@ -1,91 +0,0 @@ -from mythril.ether import util -from mythril.rpc.client import EthJsonRpc -from mythril.ether.contractstorage import get_persistent_storage -from mythril.disassembler.disassembly import Disassembly -from ethereum.abi import encode_abi -import re -import os - - -# Discover contract functions that write the sender address, or an address passed as an argument, to storage. -# Needs testrpc running on port 8546 - -# testrpc --port 8546 --gasLimit 0xFFFFFF --account 0x0b6f3fd29ca0e570faf9d0bb8945858b9c337cd2a2ff89d65013eec412a4a811,500000000000000000000 --account 0x2194ac1cd3b9ca6cccc1a90aa2c6f944994b80bb50c82b973adce7f288734d5c,500000000000000000000 - - -addr_knupper= "0xe2beffc4bc7ebb9eae43d59d2b555749d9ce7c54" -addr_schnupper = "0xadc2f8617191ff60a36c3c136170cc69c03e64cd" - -contract_storage = get_persistent_storage(os.path.join(os.path.expanduser('~'), ".mythril")) -testrpc = EthJsonRpc("localhost", 8546) - -testargs1 = [ - ([], []), - (['address'], [addr_schnupper]), - (['address', 'uint256'], [addr_schnupper, 1 ]), - (['address', 'uint256', 'uint256'], [addr_schnupper, 1, 1]), - (['address[]'], [[addr_schnupper]]), - (['address[]', 'uint256'], [[addr_schnupper], 1 ]), - (['address[]', 'uint256', 'uint256'], [[addr_schnupper], 1, 1]), -] - - -def testCase(contract_addr, function_selector, arg_types, args): - - if re.match(r'^UNK_0x', function_selector): - args = encode_abi(['address'], [addr_schnupper]) - data= function_selector[4:] + args.hex() - else: - data = util.encode_calldata(function_selector, arg_types, args) - - tx = testrpc.eth_sendTransaction(to_address=contract_addr, from_address=addr_schnupper, gas=5000000, value=0, data=data) - - trace = testrpc.traceTransaction(tx) - - if trace: - for t in trace['structLogs']: - if t['op'] == 'SSTORE': - if addr_schnupper[2:] in t['stack'][-2]: - return True - - return False - - -def testDynamic(contract_hash, contract, addresses, balances): - - ret = testrpc.eth_sendTransaction(from_address=addr_knupper, gas=5000000, value=0, data=contract.creation_code) - receipt = testrpc.eth_getTransactionReceipt(ret) - contract_addr = receipt['contractAddress'] - - try: - disas = Disassembly(contract.code) - except: - return - - found = False - - for function_selector in disas.func_to_addr: - - try: - for t in testargs1: - if(testCase(contract_addr, function_selector, t[0], t[1])): - print("Possible write!") - print("Contract hash: " + contract_hash) - print("Selector: " + function_selector) - print("Input data: " + str(t[1])) - - for i in range(0, len(addresses)): - print("Address: " + addresses[i] + ", balance: " + str(balances[i])) - - found = True - break - - if found: - break - except: - break - - -print("Searching " +str(len(list(contract_storage.contracts))) + " contracts...") - -contract_storage.search("code#PUSH#", testDynamic) # Returns all contracts diff --git a/examples/find-fallback-dcl.py b/examples/find-fallback-dcl.py deleted file mode 100644 index fc242eee..00000000 --- a/examples/find-fallback-dcl.py +++ /dev/null @@ -1,72 +0,0 @@ -from mythril.ether import evm -from mythril.ether.contractstorage import get_persistent_storage -from mythril.disassembler.disassembly import Disassembly -from mythril.rpc.client import EthJsonRpc -from mythril.disassembler.callgraph import generate_callgraph -import os - - -contract_storage = get_persistent_storage() -contract_keys = list(contract_storage.contracts) -homestead = EthJsonRpc() - -# Iterate over all contracts in the database - -for k in contract_keys: - - contract = contract_storage.contracts[k] - - # Run each contract in the PyEthereum EVM trace to check whether DELEGATECALL is reached - # To execute the fallback function, we don't provide any input data - - ret = evm.trace(contract.code) - - if 'DELEGATECALL' in ret: - - print("DELEGATECALL in fallback function: Contract 0x" + k.hex() + " deployed at: ") - - instance_list = contract_storage.instance_lists[k] - - for i in range(1, len(instance_list.addresses)): - print("Address: " + instance_list.addresses[i] + ", balance: " + str(instance_list.balances[i])) - - # contract.get_xrefs() should contain the delegateCall() target (library contract) - - print("Referenced contracts:") - - xrefs = contract.get_xrefs() - - print ("\n".join(xrefs)) - - ''' - from here on are many different options! - - In this example, we'll only check one of the refernced contracts contains the initWallet() function - If it does, we save the disassembly and callgraph for further analysis - - ''' - - for xref in xrefs: - code = homestead.eth_getCode(xref) - - disassembly = Disassembly(code) - - if contract.matches_expression("func#initWallet(address[],uint256,uint256)#"): - print ("initWallet() in referenced library contract: " + xref) - - # Save list of contracts that forward calls to this library contract - - cwd = os.getcwd() - - with open("contracts_calling_" + xref + ".txt", "w") as f: - addresses = contract_storage.instance_lists[k].addresses - - f.write("\n".join(addresses)) - - easm = disassembly.get_easm() - - with open("library_" + xref + ".easm", "w") as f: - f.write(easm) - - generate_callgraph(disassembly, os.path.join(cwd, "library_" + xref)) - diff --git a/examples/walletlibrary.sol b/examples/walletlibrary.sol deleted file mode 100644 index f446f766..00000000 --- a/examples/walletlibrary.sol +++ /dev/null @@ -1,464 +0,0 @@ -//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 WalletAbi { - // Revokes a prior confirmation of the given operation - function revoke(bytes32 _operation) external; - - // Replaces an owner `_from` with another `_to`. - function changeOwner(address _from, address _to) external; - - function addOwner(address _owner) external; - - function removeOwner(address _owner) external; - - function changeRequirement(uint _newRequired) external; - - function isOwner(address _addr) constant returns (bool); - - function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool); - - // (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today. - function setDailyLimit(uint _newLimit) external; - - function execute(address _to, uint _value, bytes _data) external returns (bytes32 o_hash); - function confirm(bytes32 _h) returns (bool o_success); -} - -contract WalletLibrary is WalletEvents { - // TYPES - - // struct for the status of a pending operation. - struct PendingState { - uint yetNeeded; - uint ownersDone; - uint index; - } - - // Transaction structure to remember details of transaction lest it need be saved for a later call. - struct Transaction { - address to; - uint value; - bytes data; - } - - // MODIFIERS - - // simple single-sig function modifier. - modifier onlyowner { - if (isOwner(msg.sender)) - _; - } - // multi-sig function modifier: the operation must have an intrinsic hash in order - // that later attempts can be realised as the same underlying operation and - // thus count as confirmations. - modifier onlymanyowners(bytes32 _operation) { - if (confirmAndCheck(_operation)) - _; - } - - // METHODS - - // gets called when no other function matches - function() payable { - // just being sent some cash? - if (msg.value > 0) - Deposit(msg.sender, msg.value); - } - - // constructor is given number of sigs required to do protected "onlymanyowners" transactions - // as well as the selection of addresses capable of confirming them. - function initMultiowned(address[] _owners, uint _required) only_uninitialized { - m_numOwners = _owners.length + 1; - m_owners[1] = uint(msg.sender); - m_ownerIndex[uint(msg.sender)] = 1; - for (uint i = 0; i < _owners.length; ++i) - { - m_owners[2 + i] = uint(_owners[i]); - m_ownerIndex[uint(_owners[i])] = 2 + i; - } - m_required = _required; - } - - // Revokes a prior confirmation of the given operation - function revoke(bytes32 _operation) external { - uint ownerIndex = m_ownerIndex[uint(msg.sender)]; - // make sure they're an owner - if (ownerIndex == 0) return; - uint ownerIndexBit = 2**ownerIndex; - var pending = m_pending[_operation]; - if (pending.ownersDone & ownerIndexBit > 0) { - pending.yetNeeded++; - pending.ownersDone -= ownerIndexBit; - Revoke(msg.sender, _operation); - } - } - - // Replaces an owner `_from` with another `_to`. - function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external { - if (isOwner(_to)) return; - uint ownerIndex = m_ownerIndex[uint(_from)]; - if (ownerIndex == 0) return; - - clearPending(); - m_owners[ownerIndex] = uint(_to); - m_ownerIndex[uint(_from)] = 0; - m_ownerIndex[uint(_to)] = ownerIndex; - OwnerChanged(_from, _to); - } - - function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external { - if (isOwner(_owner)) return; - - clearPending(); - if (m_numOwners >= c_maxOwners) - reorganizeOwners(); - if (m_numOwners >= c_maxOwners) - return; - m_numOwners++; - m_owners[m_numOwners] = uint(_owner); - m_ownerIndex[uint(_owner)] = m_numOwners; - OwnerAdded(_owner); - } - - function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) external { - uint ownerIndex = m_ownerIndex[uint(_owner)]; - if (ownerIndex == 0) return; - if (m_required > m_numOwners - 1) return; - - m_owners[ownerIndex] = 0; - m_ownerIndex[uint(_owner)] = 0; - clearPending(); - reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot - OwnerRemoved(_owner); - } - - function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external { - if (_newRequired > m_numOwners) return; - m_required = _newRequired; - clearPending(); - RequirementChanged(_newRequired); - } - - // Gets an owner by 0-indexed position (using numOwners as the count) - function getOwner(uint ownerIndex) external constant returns (address) { - return address(m_owners[ownerIndex + 1]); - } - - function isOwner(address _addr) constant returns (bool) { - return m_ownerIndex[uint(_addr)] > 0; - } - - function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool) { - var pending = m_pending[_operation]; - uint ownerIndex = m_ownerIndex[uint(_owner)]; - - // make sure they're an owner - if (ownerIndex == 0) return false; - - // determine the bit to set for this owner. - uint ownerIndexBit = 2**ownerIndex; - return !(pending.ownersDone & ownerIndexBit == 0); - } - - // constructor - stores initial daily limit and records the present day's index. - function initDaylimit(uint _limit) only_uninitialized { - m_dailyLimit = _limit; - m_lastDay = today(); - } - // (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today. - function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external { - m_dailyLimit = _newLimit; - } - // resets the amount already spent today. needs many of the owners to confirm. - function resetSpentToday() onlymanyowners(sha3(msg.data)) external { - m_spentToday = 0; - } - - // throw unless the contract is not yet initialized. - modifier only_uninitialized { if (m_numOwners > 0) throw; _; } - - // constructor - just pass on the owner array to the multiowned and - // the limit to daylimit - function initWallet(address[] _owners, uint _required, uint _daylimit) only_uninitialized { - initDaylimit(_daylimit); - initMultiowned(_owners, _required); - } - - // kills the contract sending everything to `_to`. - function kill(address _to) onlymanyowners(sha3(msg.data)) external { - suicide(_to); - } - - // Outside-visible transact entry point. Executes transaction immediately if below daily spend limit. - // If not, goes into multisig process. We provide a hash on return to allow the sender to provide - // shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value - // and _data arguments). They still get the option of using them if they want, anyways. - function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 o_hash) { - // first, take the opportunity to check that we're under the daily limit. - if ((_data.length == 0 && underLimit(_value)) || m_required == 1) { - // yes - just execute the call. - address created; - if (_to == 0) { - created = create(_value, _data); - } else { - if (!_to.call.value(_value)(_data)) - throw; - } - SingleTransact(msg.sender, _value, _to, _data, created); - } else { - // determine our operation hash. - o_hash = sha3(msg.data, block.number); - // store if it's new - if (m_txs[o_hash].to == 0 && m_txs[o_hash].value == 0 && m_txs[o_hash].data.length == 0) { - m_txs[o_hash].to = _to; - m_txs[o_hash].value = _value; - m_txs[o_hash].data = _data; - } - if (!confirm(o_hash)) { - ConfirmationNeeded(o_hash, msg.sender, _value, _to, _data); - } - } - } - - function create(uint _value, bytes _code) internal returns (address o_addr) { - assembly { - o_addr := create(_value, add(_code, 0x20), mload(_code)) - jumpi(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; -} - -contract Wallet is WalletEvents { - - // WALLET CONSTRUCTOR - // calls the `initWallet` method of the Library in this context - function Wallet(address[] _owners, uint _required, uint _daylimit) { - // Signature of the Wallet Library's init function - bytes4 sig = bytes4(sha3("initWallet(address[],uint256,uint256)")); - address target = _walletLibrary; - - // Compute the size of the call data : arrays has 2 - // 32bytes for offset and length, plus 32bytes per element ; - // plus 2 32bytes for each uint - uint argarraysize = (2 + _owners.length); - uint argsize = (2 + argarraysize) * 32; - - assembly { - // Add the signature first to memory - mstore(0x0, sig) - // Add the call data, which is at the end of the - // code - codecopy(0x4, sub(codesize, argsize), argsize) - // Delegate call to the library - delegatecall(sub(gas, 10000), target, 0x0, add(argsize, 0x4), 0x0, 0x0) - } - } - - // 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 = 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; -} \ No newline at end of file diff --git a/samples/ether_send.sol b/solidity_examples/ether_send.sol similarity index 100% rename from samples/ether_send.sol rename to solidity_examples/ether_send.sol diff --git a/samples/integer_overflow.sol b/solidity_examples/integer_overflow.sol similarity index 100% rename from samples/integer_overflow.sol rename to solidity_examples/integer_overflow.sol diff --git a/solidity_examples/reentrancy.sol b/solidity_examples/reentrancy.sol new file mode 100644 index 00000000..78210aa8 --- /dev/null +++ b/solidity_examples/reentrancy.sol @@ -0,0 +1,23 @@ +contract Reentrancy { + + mapping(address => uint) public balances; + + function donate(address _to) public payable { + balances[_to] += msg.value; + } + + function balanceOf(address _who) public constant returns (uint balance) { + return balances[_who]; + } + + function withdraw(uint _amount) public { + if(balances[msg.sender] >= _amount) { + if(msg.sender.call.value(_amount)()) { + _amount; + } + balances[msg.sender] -= _amount; + } + } + + function() payable {} +} \ No newline at end of file diff --git a/solidity_examples/underflow.sol b/solidity_examples/underflow.sol new file mode 100644 index 00000000..6ecef5df --- /dev/null +++ b/solidity_examples/underflow.sol @@ -0,0 +1,20 @@ +contract Under { + + mapping(address => uint) balances; + uint public totalSupply; + + function Token(uint _initialSupply) { + balances[msg.sender] = totalSupply = _initialSupply; + } + + function sendeth(address _to, uint _value) public returns (bool) { + require(balances[msg.sender] - _value >= 0); + balances[msg.sender] -= _value; + balances[_to] += _value; + return true; + } + + function balanceOf(address _owner) public constant returns (uint balance) { + return balances[_owner]; + } +} \ No newline at end of file