Ethernaut
Ethernaut is OpenZeppelin’s smart-contract wargame. Each level hands you a vulnerable contract and you have to break it. I played through it for fun, and my attack contracts for each level I solved, up to and including GatekeeperOne, are in the repo.
Levels
| Level | What I exploited |
|---|---|
| Coin Flip | The “randomness” comes from blockhash, so a contract calling in the same block can predict it |
| Telephone | The contract checks tx.origin when it should check msg.sender |
| Token | Unchecked integer underflow (pre-0.8 arithmetic) |
| Delegation | delegatecall runs my code inside the victim’s storage |
| Force | selfdestruct pushes ETH into a contract that has no receive |
| Vault | “Private” storage can still be read on-chain |
| King | A receiver that reverts, so nobody can ever take the throne back |
| Re-entrancy | The contract makes the external call before it updates balances |
| Elevator | An interface that gives different answers to the same call |
| GatekeeperOne | Brute-forcing gasleft() % 8191 and building a key out of type-casting rules |
GatekeeperOne
This was the last level I did. The third gate wants a bytes8 key whose uint32, uint16 and uint64 truncations satisfy a chain of equalities tied to tx.origin. Masking my own address gives the key:
bytes8 key = bytes8(uint64(uint160(address(msg.sender)))) & 0xFFFFFFFF0000FFFF;
(bool success,) = gatekeeper.call{gas: gas}(abi.encodeWithSignature("enter(bytes8)", key));
The second gate needs gasleft() to be a multiple of 8191 at the moment it’s checked, so I swept the gas argument until a call went through.