Ethernaut#1 - Fallback
What is Ethernaut?
Ethernaut is a Web3/Solidity based wargame meant to be played in the EVM. Each level is a smart contract that the user must hack, and the game allows users to both learn about ethereum and see their skills compare to historical hacks. The game may have an infinite number of levels.
Solving Ethernaut helps solidify your knowledge of Solidity, Vulnerability and Security in smart contracts and i will try to explain how to pass each challenge.
I will be interacting with the contracts mostly with Remix.
Ethernaut #1 - Fallback
Look carefully at the contract’s code below.
You will beat this level if
- you claim ownership of the contract
- you reduce its balance to 0
1 | // SPDX-License-Identifier: MIT |
Contract Breakdown
Let us breakdown the contracts to understand what each function and piece of code does. Mere looking at it we can see the code consists of a single contract Fallback
that is compiled with solidity version ^0.8.0.
Variables
1 | mapping(address => uint) public contributions; |
A global mapping of address to uint256 is declared called contributions
.
A global variable of type address called owner
Constructor
1 | constructor() { |
In the constructor, the owner is set to msg.sender,the deployer of the contract and the owner’s contribution is set to 1000 eth.
Functions
1 | function contribute() public payable { |
This function is used to contribute ether to the contract. It checks that the ether being sent is less than 0.001 and sets msg.value
to msg.sender
with the contributor mapping.
If the contribution made by that msg.sender
address is greater than that of the deployer of the contract (1000eth), the msg.sender address will become the new owner
.
1 | function withdraw() public onlyOwner { |
This withdraw
function allows the withdrawal of eth from the contract. It can only be called by the owner
because of the onlyOwner
modifier and once called the total balance of the contract will be sent to the caller of the withdraw
function.
1 | receive() external payable { |
In the receive function the msg.sender
is set to the owner
if the amount sent is greater than 0 and the contributions of msg.sender
is greater than 0.
Solution
Steps to pass the challenge:
- Find a way to make ourselves the
owner
. - Call the
withdraw
function to steal all of the eth.
Setting owner to our address
Inspecting the contract, we can see there are only 2 places where the owner variable is updated. It is updated in both the contribute function and in the receive function but contributing to the contract isn’t enough to set owner to our address because it requires that the amount contributed is greater than that of the present owner (which is 1000eth) yet each contribution must be less than 0.001 eth.
Looking at the receive
function, one of the requirements is that the contributions of msg.sender > 0 so we call the contribute
function with wei less than 0.001 eth to set that.
Now that the contributions[msg.sender] > 0, we can now proceed to call the receive
function with some amount of wei to satisfy the first condition and set owner to msg.sender
.
Ps: To interact with the receive function on Remix, we have to call the low level interaction Transact
with empty call data.
Stealing the eth balance.
After owner has been set to our address, we can then call the withdraw function and the eth gets sent to our address (msg.sender
).