Skip to main content
Be Bitwise

Reading other people's exploits without getting lost

Public exploit code is a fantastic teacher and an easy way to drown. Where to find quality code, how to read it backwards, and what to do when you stall.

#exploit-development#learning#methodology

A public exploit is a strange document. Executable, documentation of a bug class, a tour of the target's internals, an argument for a specific exploitation technique — all at once. Reading one front-to-back is like reading a novel by starting with the prologue: technically possible, often pointless. There's a better order.

This post covers where to find code worth reading, the four-act structure most exploits follow, why you should read them backwards, and what to do when you've been staring at the same pack('<Q', x) for an hour and nothing has clicked.

Where to find quality exploits

Public exploit code varies wildly. A lot of GitHub is garbage: random "PoC" repos by drive-by accounts, sometimes laden with malware to pop a shell on the analyst rather than the target. Stick to authors and orgs with a track record.

The shortlist:

  • Project Zero issue tracker. Each closed issue has a writeup and often working exploit code. The writeups usually outvalue the code itself; the combination is unmatched. Filter by component (kernel, browser, mobile) to narrow the firehose.
  • Personal repos by named researchers. saelo, itszn, jvoisin, alisaesage, theori-io, the watchTowr team. If a name appears in two or three Project Zero credits, look at their personal GitHub.
  • Vendor-affiliated research blogs. ZDI, Google's security blog, GitHub Security Lab, Trail of Bits, NCC Group. Long-form writeups, often with attached code.
  • CTF team writeups for hard challenges. PPP, perfect blue, DiceGang, Shellphish. CTF code is always more annotated than real exploit code because the target audience is other learners.

Treat any "0day" repo from an account you've never heard of with the same suspicion you'd give an .exe from a Russian forum.

The structure most exploits follow

Almost every memory-corruption exploit, from the simplest stack overflow to the most baroque kernel chain, has the same four-act shape. Recognising it is what separates feeling lost in 800 lines of pwntools from navigating to the part you actually care about.

Act one, trigger. Code that talks to the target enough to reach the vulnerable function. For a network bug, a handful of socket calls and protocol setup. For a browser bug, an HTML harness with the right JS to allocate the right object at the right time. Beginners over-focus on this layer because it's the easiest to read.

Act two, primitive construction. Turning the raw bug into a useful capability. A buffer overflow by itself is just bytes-overwriting-bytes; to do anything you need a primitive (arbitrary read, arbitrary write, controlled call, type confusion). Real bugs almost never hand you the primitive you want directly. The construction code is the ingenious bit that turns "I can overflow this struct by 8 bytes" into "I can write any value to any address". Heap shaping, object grooming, and info leaks all live here.

Act three, primitive use. With a primitive in hand, you spend it on a specific corruption: function pointer, vtable, credentials struct, page permissions. The choice is the most target-specific part. Linux kernel exploits often overwrite modprobe_path; browser exploits often overwrite a JIT-compiled JS function pointer; a SUID binary exploit might overwrite the GOT entry for system.

Act four, payload. What you actually do once you have control. Pop a shell, drop a beacon, escalate to root, dump credentials, exfiltrate a flag. In CTFs it's one line; in real exploits, often a small loader stage that pulls in something larger.

Every exploit is some arrangement of those four acts. Sometimes they fold together; sometimes act two takes 600 lines and acts one and four take twelve. The shape persists.

Read backwards

Here's the trick. Don't start at the top. Start at the payload and work up.

The payload is the easiest act to understand because it's just "execute these bytes" or "call this function". Once you know what the exploit ends up doing, you can ask the right question of the next layer: how did the author obtain control of RIP to make this payload run? That takes you naturally into act three. Knowing what they corrupted, you can ask how they got the primitive to corrupt it. Now you're in act two.

Top-down reading wades you through hundreds of lines of socket setup, struct definitions, and helpers before you reach anything that explains why any of it matters. By the time you arrive, you've forgotten the setup, and the satisfying click of "oh, that's why we needed those magic bytes" never happens.

In practice:

  1. Find the last meaningful action. Usually p.recvline() after a system("/bin/sh"), a remote command running, or a flag printing.
  2. Walk back: what caused that? An execve from an overwrite. Find the overwrite.
  3. What gave you the address you wrote to? An info leak. Find the leak.
  4. What made the leak possible? A primitive built from the bug. Find the bug.
  5. How did the author reach the bug? The trigger. Read that top-down with full context.

The pattern isn't mine; saelo and others have described variations in talks. It is the single most useful habit junior researchers pick up from senior ones.

What to do when stuck

You will get stuck. Some exploits are hard, under-commented, or written for an audience that has already read three previous bugs in the same component. When that happens, escalate roughly in this order.

Read the patch. If a CVE was assigned, the upstream fix says concretely what the bug was. A patch often clarifies in two minutes what the exploit code obscured for two hours. (See the companion post on reading CVEs for how to find one.)

Read the bug report. Project Zero issues, GitHub Security Advisories, vendor bulletins. The reporter usually explains the root cause in plain English before the exploit author starts wielding it.

Read a simpler exploit by the same author. If saelo wrote four browser exploits in a row, the fourth assumes you've internalised the first. Working through earlier ones is often faster than brute-forcing the latest.

Run it under a debugger. Drop breakpoints at each major step and inspect state. Watching memory change in slow motion makes primitives visible that the source obscured.

Read someone else's writeup. Multiple writeups of the same bug rarely emphasise the same things. Triangulate.

The thing not to do is push through, skim, and convince yourself you understood. Bugs and primitives don't reward bluffing; they reward being able to derive the exploit from scratch given the bug.

How long it takes

A good exploit, read properly, takes hours. A great one (kernel chain, browser sandbox escape, hypervisor break) takes days. That's normal. Senior researchers spent weeks writing them; you won't absorb the work in fifteen minutes of skim-reading.

Build the habit of taking notes as you go. A scratch document per exploit, with one paragraph per act, becomes its own teaching artefact when the same technique surfaces in a different target six months later.

Where this fits in

The bug-to-primitive-to-payload progression is the bread and butter of Module 74: Exploit Development. The module walks the four-act anatomy on small targets first, then graduates to harder ones, with the explicit goal of making published exploits legible to you. Reading public code in parallel doubles the value of both.