Damn Vulnerable DeFi is a series of challenges focused on DeFi smart contract attacks. The content includes flash loan attacks, lending pools, on-chain oracles, and more. Before starting, you need to have skills related to Solidity and JavaScript. For each challenge, your task is to ensure that the unit tests for that challenge can pass.
Challenge link: https://www.damnvulnerabledefi.xyz/challenges/11.html
Challenge description:
To incentivize the creation of safer wallets, someone deployed the WalletRegistry contract. When someone registers as a beneficiary in this contract and creates a Gnosis Safe wallet, they will receive 10 DVT tokens in their wallet.
Currently, there are four people registered as beneficiaries: Alice, Bob, Charlie, and David. There are 40 DVT in the WalletRegistry contract.
Your goal is to steal these 40 DVT.
Before starting, you need to understand knowledge related to proxy contracts, multisig wallets, EVM memory layout, and Solidity inline assembly. This will not be elaborated on here.
If you are already familiar with the above content, you can officially start.
However, before that, it is necessary to introduce the contract architecture of Gnosis Safe
version 1.3.0. Gnosis Safe
has three important contracts during the deployment phase:
GnosisSafeProxyFactory
GnosisSafeProxy
GnosisSafe
GnosisSafe
is the contract that handles multisig logic and is a pre-deployed contract. Its address on the Ethereum mainnet is 0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552
.
When we create a multisig wallet on the Gnosis Safe
website, the contract we interact with is actually GnosisSafeProxyFactory
, which creates and deploys the GnosisSafeProxy
proxy contract. The logic contract address of the proxy contract is the already deployed GnosisSafe
contract. Therefore, the multisig wallet we create is actually the GnosisSafeProxy
proxy contract.
Due to this proxy model, all data is stored in the GnosisSafeProxy
contract, which has the following advantages:
- Since the multisig logic can be reused, the proxy model avoids the repeated deployment of the logic contract. It can save
gas
fees for users when creating multisig wallets. - Data will be stored in the wallets created by users themselves, rather than being uniformly stored in the logic contract, achieving separation of data and logic for each multisig wallet.
The core method for deploying the proxy contract in GnosisSafeProxyFactory
is:
The parameters of this method are as follows:
_singleton
: The address of the deployed logic contract.initializer
: Thecalldata
for thesetup
method of the logic contractGnosisSafeProxy
.salt
: Thesalt
value for deploying the contract, which is 32 bytes long.
The function internally uses two inline assembly functions, create2
and call
, which you can check on evm.codes.
create2(value, offset, size, salt)
deploys a contract and returns the contract address.
value
: The amount ofETH
sent to the contract account during deployment, measured inwei
.offset
: The starting position in memory.size
: The length starting from the starting position.salt
: Thesalt
value for deploying the contract, which is 32 bytes long.
call(gas, address, value, argsOffset, argsSize, retOffset, retSize)
calls a contract method.
gas
: The required gas.address
: The target contract address.value
: The amount of ETH sent to the target contract.argsOffset
: The starting position of the sentcalldata
in memory.argsSize
: The length of the sentcalldata
.retOffset
: The starting position in memory for writing the return value.retSize
: The length of the return value.
Since deployProxy
is internal
, the GnosisSafeProxyFactory
contract exposes the following two methods for external calls to create proxy contracts:
Returning to the challenge, the WalletRegistry
contract inherits from IProxyCreationCallback
, and in the above method createProxyWithCallback
, there is a line of code:
When callback
exists, it calls its proxyCreated
method. If callback
is in WalletRegistry
, it calls its proxyCreated
method:
So far, we can summarize:
- The
WalletRegistry
contract can add beneficiary addresses, and beneficiaries can create multisig wallets, transferring 10 DVT to the wallet address. - Creating a multisig wallet requires calling the
createProxyWithCallback
method of theGnosisSafeProxyFactory
contract.- The
createProxyWithCallback
can pass in acallback
to call theproxyCreated
method. - If there is an
initializer
during the wallet creation process, it will be called.
- The
Currently, there are four beneficiaries, so four multisig wallets can be created, each containing 10 DVT.
The goal is to withdraw these DVT from the multisig wallets. However, we are not the owners of the wallets, so we need to find a way to transfer the DVT to our own account.
The key lies in the value of initializer
. As mentioned earlier, initializer
is the calldata
for the setup
method of the logic contract GnosisSafe
, which will be called when creating the wallet. The implementation of the setup
method is as follows:
Method parameters:
_owners
: The wallet owners._threshold
: The minimum number of approvals required to initiate a transaction.to
: The address of the contract to executedelegatecall
.data
: Thecalldata
for executingdelegatecall
.fallbackHandler
: The contract to handle calls to non-existent methods.paymentToken
: The address of the token for payment.payment
: The amount to be paid.paymentReceiver
: The recipient of the payment.
fallbackHandler
is a contract address that will be called when we invoke a non-existent method. For example, when calling transfer
, if there is no such method in GnosisSafe
, it will call fallbackHandler.transfer
. Therefore, we can set fallbackHandler
to the address of the DVT contract and pass in the relevant parameters.
Here is the attack contract BackdoorAttack.sol
:
In the test file backdoor.challenge.js
, add the attack entry:
Finally, execute yarn backdoor
and the test passes!