Skip to content
All modules
AdvancedModule 27 of 2790 minutes

Smart contracts and the EVM

Public code, public state, and an execution model where a callback in the middle of your function is a normal event.

Assumes21. Reverse engineering

By the end you can

  • Read contract storage directly and explain why 'private' means unindexed rather than secret
  • Trace a reentrancy path and name the state update that happened too late
  • Distinguish delegatecall from call in terms of whose storage is written
  • Recognise an unprotected initialiser or owner-setting function from the bytecode alone
  • Follow a transaction trace to the exact call that changed the balance
  • Explain why an on-chain source of randomness is an attacker-observable value

1. Read

2. Use the tools

In the order they come up while solving. Read what each one does, or go straight to the workspace tab that runs it.

3. Try one now

Generated in your browser and checked in your browser. No account, nothing to download, and a fresh one whenever you want another.

4. Practise on the real thing

Real picoCTF challenges that use these techniques, easiest first.

Common mistakes

The wrong turns this topic reliably produces. Written as the mistake rather than the rule, because the rule is easy to agree with and easy to walk straight past.

  • Reading `private` as confidential. Storage is public by construction; the keyword only stops other contracts reading it through the compiler.
  • Looking for the bug inside a single function. Most of these are ordering bugs across an external call, which is why checks-effects-interactions exists as a rule.
  • Using block values as randomness in your own solution and then being surprised the challenge does the same. Miners and callers can both see them before they commit.
  • Reading a successful transaction as the state change you wanted. A call that reverts inside a try block, a fallback that silently accepts, and a transfer that returns false instead of throwing all produce a receipt marked successful.
  • Testing only against a freshly deployed copy. The challenge's contract has a live history - who initialised it, which balances exist, what has already been called - and that accumulated state is frequently the whole vulnerability.

Checkpoint

Given a vulnerable contract, drain it in a single transaction and write down the call sequence, marking the exact point where the contract's own state was stale.

Teaching note

The mental model that unlocks this module is that an external call is a yield point. Draw the stack for a reentrant withdraw once, with the balance update below the call, and the whole bug class becomes obvious rather than exotic.

Go deeper

The lessons above are written to get you through a challenge. These are the chapters to read when you want the subject instead - each one named so you can check it rather than take our word for it. Nothing here is affiliate-linked or sold by us.

  • Designing Secure Software - Loren Kohnfelder

    Chapter 10, Untrusted Input

    A contract's caller is untrusted input that can run code, which is the framing that makes reentrancy ordinary.

  • Designing Secure Software - Loren Kohnfelder

    Chapter 6, Secure Design

    Invariants and trust boundaries, which is what a contract audit is checking and what a CTF contract violates on purpose.