Phrack 57, read carefully

Phrack 57 appeared in late September. As is now my habit, I have spent the past fortnight working through it carefully. Two articles in particular are worth writing about.

The Phrack reading discipline I committed to last year continues to pay back disproportionately to time invested. This issue is no exception.

The format-string sequel

In Phrack 55 the article on format-string vulnerabilities formalised the technique as a generic write-anywhere primitive. This issue contains a sequel: a deeper article on building exploits for format-string bugs in modern systems with various mitigations in place.

The gist: even with non-executable stack and stack canaries deployed, format-string bugs remain exploitable through indirection. The attacker uses the format-string write to overwrite a function pointer in the GOT (Global Offset Table) — an indirection table the dynamic linker maintains for shared library calls. The next call through the modified pointer transfers control to attacker-controlled code.

Because the GOT lives in writable memory (it has to, by the dynamic-linker design), and because legitimate code regularly calls through it, the standard memory-protection mechanisms do not stop this attack. The defence requires either making the GOT read-only after relocation (the RELRO mechanism, not yet widely deployed) or reorganising the linkage so that the indirection itself is harder to exploit.

The broader pattern the article articulates: every indirection table is a potential exploitation target. C++ vtables, function pointers in shared structures, callback registries, signal handlers — anything that uses a writable pointer for indirect control flow can be subverted by a sufficiently general write primitive.

The defensive implication: address-space layout randomisation (ASLR) becomes structurally more important. If the attacker does not know where the indirection tables are, the write primitive is much harder to use. ASLR is the right answer; deployment is years away in any mainstream system.

The kernel-side article

The second piece I want to write about is on writing kernel exploits for Linux. The article walks through several specific patterns:

  • Race conditions in syscall handling.
  • Integer-overflow bugs in length checks.
  • Confused-deputy attacks where a privileged kernel path acts on attacker-controlled state.
  • Sleeping-while-holding-resource patterns where the attacker can manipulate kernel state during a sleep.

The last one is interesting because it generalises. Many kernel paths sleep — wait for I/O, wait for a lock, wait for a timer. While sleeping, the kernel does not hold the resources it had been working on; user-space code can run, including user-space code that manipulates the same resources. If the kernel resumes and acts on resources that have been changed, the result can be exploitable.

This is temporal in nature, not spatial. The attacker is exploiting a window in time when kernel state is in a particular intermediate condition. Detection requires reasoning about the kernel's execution timeline, not just its data layout.

The defensive techniques the article describes are mostly structural — reducing the surface where sleeping-while-holding occurs, doing more validation on resume, using locks more strictly. None of these are quick fixes; they are properties of how kernels are written.

What this changes about my mental model

A few things, written down for my own reference.

The escalation from user-space exploit to kernel-level access is, in 2000, easier than I had assumed. Many privilege-escalation vulnerabilities are temporal in the sense above. They are not buffer overflows; they are subtle interactions between user space and kernel space.

Mitigations layer in non-obvious ways. Stack canaries help against stack overflows. Non-executable stack helps against shellcode execution. ASLR helps against attacks that need fixed addresses. Each is a useful defence; their combination is multiplicatively more effective than any individual one. The right architecture is to layer all of them.

Kernel-level defences need their own attention. Most of my writing has focused on application-level defences. The kernel is the trust boundary that enforces all of them; if the kernel is exploitable, application defences do not help. Reading kernel exploitation techniques carefully is a complementary discipline to reading application exploitation techniques.

What I am going to do

Three small things over the next few months.

Read the relevant Linux 2.4 kernel code more carefully. The new kernel adds several capabilities that touch on these exploitation patterns — finer-grained locking, more reliable preemption, better handling of long-running syscalls. Understanding how these interact with the techniques described in Phrack will be useful.

Audit my own systems for known exploitable patterns. Several of the techniques described are detectable — the attack patterns leave specific traces in dmesg or in syscall traces. I am going to write a small monitoring script to watch for these.

Continue the Phrack reading discipline. Issue 58 will be early next year. Worth scheduling time for it.

A closing note on adversarial publishing

One thing I appreciate about Phrack, on reflection: the articles are technical to the point of being practically useful. They are not abstract surveys; they include code, register layouts, specific exploitation primitives. The reader can actually use what they read.

The defensive community sometimes complains about this directness. The complaint is partly fair — the articles do produce attackers who use the techniques. The articles also produce defenders who understand the techniques. The net effect, I am increasingly convinced, is positive — the gap between what attackers know and what defenders know narrows when both communities read the same material.

More as the next quarter develops. October already has substantial news brewing — there are early rumblings of a serious IIS vulnerability that will dominate the next few weeks.


Back to all writing