- You are given a smart contract WithdrawalVault which is basically works like a vault.
-
You can withdraw money from that vault after you deploy it with some ETH.
-
Also the vault uses another contract called NumberLib (For basic arithmetic operations).
- This is done by exploiting vulnerability of delegatecall(): Storage Collision from another Attack contract.
- This Attack contract will have the same storage layout as vault.
- Plus, it will have several additional functions. These are:
attack() //Main function that performs the attack
setBase(uint offset) //Same function in NumberLib
getNumber(uint offset) //Same function in NumberLib
collect() //To transfer money from Attack contract to your personal account, right after steal
receive() //Needed since there will be inter-contract money transfer
1. It calls vault's setBase(address of Attack contract)
2. This will trigger vault's fallback()
3. Due to Attack contract having the same storage layout as vault, the very first element of vault (which is numberLibrary
) will be replaced with the address of Attack contract
4. From now on, Attack contract can act as if its the numberLibrary
of the vault
5. Then it calls vault's setBase(<anything>)
6. This will trigger vault's fallback()
again
7. Since our Attack contract was acting as if it is vault's numberLibrary
, vault's triggered fallback()
will call Attack's setBase()
8. This will set owner = address(Attack contract)
9. After taking over the ownership of vault, attack()
will call withdraw()
function of victim. But this won't transfer any money to Attack contract because of currentNumber
being zero
10. Nevertheless, Attack contract also had custom getNumber()
function. So withdraw()
function will call getNumber()
of Attack contract. This custom function will transfer all money from vault to Attack contract (receive()
function works here)
In the end, you can transfer all money from Attack contract to your personal address by calling collect()
.