Ethernaut Lvl 9 King Walkthrough: How bad contracts can abuse withdrawals

This is a in-depth series around Zeppelin team’s smart contract security puzzles. We learn key Solidity concepts to solve the puzzles 100% on your own.

Nicole Zhu
Coinmonks
Published in
3 min readAug 26, 2018

--

This levels requires you to prevent the level from regaining Kingship.

In an earlier deep dive, we discussed fallback functions .

Specifically, every smart contract can have a simple Fallback function in order to receive Ethers from other contracts and wallets.

Each time your contract sends Ethers to another contract, you are depending on the other contract’s code to handle the transaction and determine the transaction’s success.

This means your valid transactions can arbitrarily fail.

As the transaction sender, you are always susceptible to the following cases:

  • Loophole 1: The receiving contract doesn’t have a payable fallback function, cannot receive Ethers, and will throw an error upon a payable request.
  • Loophole 2: The receiving contract has a malicious payable fallback function that throws an exception and fails valid transactions.
  • Loophole 3: The receiving contract has a malicious payable function that consumes a large amount of gas, which fails your transaction or over-consumes your gas limit.

Detailed Walkthrough

When you submit this King.sol instance back to level, Ethernaut will call this fallback function to regain Kingship. The key is to guarantee that Ethernaut’s transaction will fail, so you can remain King.

Notice inside this fallback function, there is a king.transfer(), which can fail if the current king is a malicious contract and refuses to withdraw.

To quickly solve this level, you can either omit the fallback function (L1) or implement a malicious fallback function (L2) in a new contract. Let’s implement the malicious fallback function so we can also include a taunting message.

Note: If you are using Remix for this level, give it the full import path:

import ‘github.com/OpenZeppelin/zeppelin-solidity/contracts/ownership/Ownable.sol’;

  1. Create a BadKing contract and seed it with at least 1 Ether in the constructor:
contract BadKing {
functionBadKing() public payable {
}
}

2. Create a function to allow this BadKing to become the recognized King in King.sol, making sure to send at least 1 Ether to surpass the current prize.

function becomeKing() public {
address(king).call.value(1000000000000000000).gas(4000000)();
}

3. Implement a payable fallback function which immediately reverts the transaction. Give it an error message (optional).

Your final contract should look like:

4. Lastly, simply submit your King.sol contract instance back to the level and wait for the transaction to fail!

--

--