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
withdrawfunction 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).