Phrack 55, read carefully

Phrack issue 55 appeared in September. It is, as always, the most concentrated source of new exploitation technique writing on the internet. I have been reading it slowly — two months for one issue is the normal rate, in my experience, if you are reading it for understanding rather than skimming.

This is a small writeup of what stuck. Two articles in particular have shifted my mental model of vulnerability classes I thought I understood.

Format string bugs

The first article that I want to write about is on format string vulnerabilities. The bug class itself is not new — it has been showing up in advisories all year — but the article systematises the technique in a way I had not seen before.

The basic bug is well-known. A function like printf(user_input) — passing untrusted data as the format string — is dangerous because the format specifiers in the string control the function's behaviour. %s reads from a memory address; %n writes to a memory address. By controlling the format string, an attacker controls reads and writes.

What the article articulates clearly, which I had not fully internalised, is the generality of the technique. A format string bug is not just a way to leak memory or to crash the program. It is a generic write-anywhere primitive. The attacker can:

  • Read arbitrary memory by including specifiers that walk up the stack.
  • Write specific values to specific addresses by combining %n with field-width directives.
  • Construct a payload that overwrites a return address, a function pointer in the GOT, or any other writeable location.

The last of these is the killer. Once you have arbitrary write, you have effectively arbitrary code execution. A format string bug in any C program that handles untrusted input is, in principle, a memory corruption bug of the most severe kind.

The article walks through specific construction techniques, including how to build a payload that is robust against varying stack layouts. The technical detail is at the level where you could, after a careful reading, write your own exploits. This is exactly the level Phrack has always been at.

What this changed about my thinking

Until this article I had been treating format string bugs as a category of memory corruption — distinct from buffer overflows, but in roughly the same severity range. After reading the article, I think they are worse than buffer overflows in a specific way.

A buffer overflow requires the attacker to know what is in memory adjacent to the buffer. The exploit is sensitive to the binary's layout, the compiler version, the runtime conditions. Different builds of the same program may need different exploits.

A format string bug, by contrast, is more general. The attacker provides a format string that walks the stack to find what they need; the construction adapts to whatever the runtime conditions are. The same exploit, with minor modification, can target multiple builds and even different programs that share the bug shape.

This is the kind of insight that comes from reading exploitation work carefully, not just from running scanners against your own software. The gap between "I know this bug class exists" and "I understand how an attacker would actually use it" is a big gap, and Phrack is the cheapest way I know to close it.

What an operator should do

The defensive implications are clear:

  1. Audit your own C code for format string usage. Any place where user input reaches a printf-family function as the format string is a vulnerability. The fix is to use a constant format string and pass user input as an argument: printf("%s", user_input) not printf(user_input).
  2. Watch advisories for format string bugs in software you run. They are showing up in increasing numbers. Treat them as severe.
  3. Compile with format-string warnings enabled. GCC's -Wformat-security flag warns about printf calls with non-constant format strings. It is not perfect — it has false positives and the warning is easily silenced — but it is a useful baseline.

The other article: a deep dive on Linux kernel races

The other piece I want to write about is the article on race conditions in the Linux kernel. The author works through several specific bugs in the 2.0 and 2.2 kernels where two threads of execution interact in unexpected ways and produce a security-relevant outcome.

The article's structure is itself instructive. Each bug is presented as: the relevant code, the timing assumption the code makes, the specific sequence of events that violates the assumption, the consequence. Reading the article in that structure makes me realise how common the pattern is in code that I have not been thinking about as racey.

The specific technique it articulates is time-of-check to time-of-use (TOCTOU). Code that checks a property of an object, then acts based on the check, can be defeated if an attacker changes the object between check and use. The classic example is file-permission checking: code that checks stat(path) and then later does open(path) can be defeated by an attacker who replaces the file with a symbolic link in the gap.

The defence is structural: do not check-then-use; instead, do-and-check. Open the file, then check the file-descriptor's properties — which describe the file you actually opened, not a path that might now resolve differently.

This is the same logical pattern as the path-traversal defence: operate on the resolved object, not on the user-supplied name.

The general pattern these articles point at

Reading both articles in close sequence, the pattern is clear. The bugs are consequences of code structure that was correct in some narrower context. The format-string bug is a consequence of printf having been designed in an era where its arguments were always trusted. The TOCTOU race is a consequence of code that was correct in single-threaded contexts being deployed in multi-threaded ones.

The systemic answer to either is structural: change the API so that the dangerous case cannot easily occur. Make printf-with-non-constant-format-string a compile-time error. Make file-system APIs operate on file descriptors rather than paths.

Neither of these has fully happened yet. They are both visibly happening. The next decade is going to see APIs being progressively tightened in this direction. The legacy code with the old, dangerous patterns will continue to run in the meantime, producing advisories.

Why Phrack matters

A short note on why I read Phrack carefully when I do not read most other publications carefully.

The articles are written by working practitioners, often anonymously, with no commercial pressure. They are, frequently, the first public articulation of a technique that is already in use among offensive specialists. By the time the technique is in commercial security publications, it is six to eighteen months later.

The gap between Phrack publication and broader awareness is the period in which attackers using the technique have an asymmetric advantage. Reading Phrack, even slowly, narrows that gap for me. It is, in a real sense, my best operational defence against the next-six-months threat landscape.

Issue 56 will probably appear in the new year. I expect to spend the first quarter of 2000 reading it. The exercise pays back disproportionately to the time spent.


Back to all writing