Cover photo

Ethernaut - Fallback Writeup

Fallback is the first of the Ethernaut challenges. It is fairly simple, requiring only basic Solidity knowledge, mainly about fallback functions.

Clear conditions:

  • Become the owner of the contract.

  • Empty all ether from the contract.

Let’s first run through the code…

Fallback Contract
Fallback Contract

First, notice the two variables:

contributions: A mapping keeping track of how much wei an address contributed.

owner: A single address that can withdraw funds from the contract.

The constructor then sets up the scenario for this challenge, the owner of the contract is now Ethernaut who has 1000 ether in contributions.

Looking at the rest of the code, two functions stand out in particular.

contribute()

This function will revert if we contribute an amount over 0.001 ether, and we can only become the owner of the contract if we donate 1000 ether through this function.

So let’s look for another way.

receive()

If you are unfamiliar with what receive() is, it is basically the fallback function of this contract. What this means is that if you send a transaction to this contract with empty calldata, the transaction will end up triggering the receive() function instead.

calldata: Raw hexadecimal bytes sent with a transaction that specifies which external contract function you want to call, along with what parameters.

With the basics out of the way, we notice that the receive function requires us to send some amount of money (obviously), and have some amount of a contribution.

If we fulfill both of these requirements, we can be assigned as the owner of the contract.

We can then use our ownership to empty the ether in the contract by calling withdraw().

The attack

Now we know we first need a contribution to become the owner of the contract, let’s write a Python3 script named "fallback.py” that will:

  1. Contribute 1 wei.

  2. Send a transaction with no calldata.

  3. Call the withdraw() function, emptying the contract.

Imports

post image

We will only need these four modules for this challenge. Make sure to load_dotenv() before doing anything else, because we will need to import some variables from env soon.

web3 to interact with the Sepolia network.

solcx to compile the Ethernaut contract and get the ABI needed to interact with the contract.

dotenv & os to read environment variables.

post image

You can then load your private key, address, and provider like so.

Function -- read_contract_return_abi()

This function will read “fallback.sol“ that exists in the same directory as our python script. In the file, we can copy the Fallback contract’s source code from Ethernaut.

post image

The function above first opens “fallback.sol“, installs solidity 0.8.0 and compiles the contract using the source code.

Then, we get the ABI, which will allow us to encode/decode function calls. Without it, we wouldn’t be able to interact with the contract.

Finally, we return the contract object, which gives us an easy interface to encode function calls when interacting with the contract on Sepolia.

Function -- sign_and_send_transaction()

post image

This function will allow us to sign the transactions we’ll need to exploit the contract.

We sign the transaction we want to send using our private key and store it in signed_tx, then get the receipt using the transaction hash, which allows us to look at details of our transactions, like if the transaction failed or not.

Attack

Main function with instructions on how to attack the contract.
Main function with instructions on how to attack the contract.

We’ll now work on our main function, where we can now exploit the contract.

Getting the contract ABI.
Getting the contract ABI.

At the top of the main function is where we specify the gas and gas price we’ll use to send all of our transactions. web3.py does have modules to estimate how much gas a future transaction will use. But, I found that my transactions usually ran out of gas, so I’m using 50,000 gas at 5 gwei.

post image

Then we contribute to the contract, and save the receipt to analyze our transaction.

Checking our contribution amount.
Checking our contribution amount.

Now we’ll check how much we contributed by calling contributions() with our address and save it in contribution. This is to make sure we actually contributed some amount to the contract.

Logic if we contributed successfully.
Logic if we contributed successfully.

If we contributed successfully, we send the transaction with no calldata, then attempt to withdraw. The script will print a success message if the withdrawal worked, and the program will exit. Then submit your instance on Ethernaut and you’re done!

If you want to see the full code for this challenge, you can find it here:

https://github.com/imam-abbudi/ethernaut-web3py-solutions/blob/main/fallback/fallback.py