El eternauta original.

Back in November Zeppelin Solutions invited everybody to participate in their smart contract CTF competition called “The Ethernaut”. It was a great way to start Devcon3! It didn’t take long for devs around the world to crush the initial six challenges.

Most of these puzzles are simplified implementations of real hacks that have occurred in Ethereum’s rocky history. They’re a great entry point for anyone getting started with secure smart contract development and a good site to revisit since the OZ team keeps adding new challenges!

So, here’s a little write-up on the latest addition (currently) #6 King.

If you don’t want to be spoiled please stop reading and give it a shot: King

King of the Hill

The King contract

The objective of the challenge is to prevent the contract from reclaiming kingship once you submit your instance back. Being that the only way to change the kingship in this contract is through the fallback function we can focus exclusively on that.

First we definitely want to take the kingship from the contract, to do this in the legit way we just need to send the contract address a transaction with a value that is larger than the current prize (1 ETH)

We can take it easily enough, now the tricky part is retaining when the contract ends up calling the fallback function again. So let’s go line by line and see what we can come up with.

require(msg.value >= prize || msg.sender == owner);

This looks promising. Put another way the fallback function will fail if the msg.value is smaller than the current prize AND the message sender is not the owner.

It’s worth taking a look at Ownable.sol to see what we’re dealing with. Here I was reminded of the infamous November 2017 Parity Multi-sig wallet hacks, where an alleged beginner locked out over 150 million dollars worth of ETH in hilarious deadpan.

Not even the biggest news from that week

In that instance the “hacker” gained ownership of an uninitialized library that all individual Parity wallet instances utilized. With his new privilege we crashed the library with no survivors.

Perhaps we could do the same?

Revisiting :

require(msg.value >= prize || msg.sender == owner);

If we could take ownership of the contract and replace the prize with a value beyond the balance of the current owner we could lock the game out and RULE forever. But we can’t. This case is not like the parity hack, Ownable.sol is pretty solid and there is no way to co-opt such a simple implementation of the contract.

If we accept that as the reality there is no reason to waste more time in this juicy require, since as long as we can’t take ownership of the contract it will always pass. We can safely move along…

king.transfer(msg.value);

Good old PUSH transactions, without exception handling. Always a treasure trove of issues. This line gets the current king paid right before he’s ousted. There are a ton of ways for transactions to fail, in general now we just need one that suits our needs.

Out of gas errors. We’re not making the call here so this is not really something we can control. Arbitrary Error from Fallback Function (FF). If our transaction ends up executing code from a contract that can always fail in some contrived way. That could work. But we can keep it simpler Transact with Contract with no FF/ non payable FF. DING DING DING. As per the docs, when a transaction is made towards a contract address without including any data the fallback function of that contract is called. If a contract doesn’t have one we FAIL, or if we’re including some ETH in the transaction and the fallback function lacks the payable modifier we also FAIL! Hurray!

We can take the Kingship by proxy with our own contract. Then when the contract attempts to depose us the value transfer will fail. Success!! (We’ll be down 1 ETH for life so this is really a “Let the world burn” kind of hack)

Wrapping up

This one wasn’t so tricky. When you KNOW there is a vulnerability it is relatively simple to go line by line through contract code, until you find a weak spot. But that’s only if you KNOW. It’s very hard to apply the same discipline to code you think might be working fine. The hacks will keep piling up, make sure your code is not responsible and be through with your auditing. :P