Detecting Virtualized Rootkits
Thomas Ptacek | January 24th, 2007 | Filed Under: Defenses, Disclosure, Uncategorized
Joanna Rutkowska responded, below.
1.
Recall that hardware-assisted virtualization, as exploited by Dino’s Vitriol and Rutkowska’s BluePill, hides code in the “ring -1” hypervisor privilege level carved out by the AMD and Intel Virtualization Extensions (SVM and VT, respectively). In this environment, we can arrange for the CPU to trap to our malicious code any time someone does something to try to detect us, and emulate the innocuous response.
For example, under VT, the CPUID instruction could betray the presence of virtualization extensions. So VT traps to the hypervisor (where we’ve stuck our rootkit) when CPUID is executed, and allows us to emulate the response. From a functional perspective, you can’t look at the result and know it was emulated. You can, however, use the cycle counter to time the CPUID instruction; a trap and the software code executed to emulate CPUID will take noticeably longer than a “native” CPUID execution. “Unfortunately”, Intel has arranged for hypervisors to control the cycle counter as well; the cycles consumed during virtualization can be trivially masked with about 5 lines of C code.
So the blog debate about detecting virtualization has revolved around using “trusted” clocks, such as those on an uninfected machine, to time the instructions. Of course this will work, but it’s a pain.
2.
Last September, Peter Ferrie at Symantec made this blog claim:
Anyway, I found something that you can do in the guest that the hypervisor can’t see until after it’s happened; so, it can’t hide the side effects. It doesn’t need a network connection and it doesn’t need a user to time anything. It’s also quick (it executes in one timeslice), but, I still can’t tell you how I did it.
Today, I read Ferrie’s paper describing the trick. It’s very similar to one of the (many) tricks we recently presented in a speaking engagement.
You can’t trust the TSC timing of the CPUID intruction, because the hypervisor can simply mask the cycle overhead of any VM trap. But you might be able to trust the TSC timing of side effects that don’t cause VM traps.
For instance, you can saturate any of the caches in the system, and baseline the cycles it takes to read known cached values. Then you cause VM trap (for instance, by executing CPUID). The VM exit and entrance, along with the software that runs in the hypervisor to emulate CPUID, will evict cache entries. When you get control back from the hypervisor, you loop timing the “cached” values again; if they differ significantly, you’re virtualized.
Ferrie uses the TLB cache; the TLB keeps you from having to walk the whole page directory hierarchy when you access a virtual address. There are other caches you can saturate. I could give details to Joanna, or just refer you to any of the last N blog posts we’ve written about timing attacks against cryptosystems; the differences between timing RSA and timing a hypervisor are:
Unlike the crypto attacks, hypervisor timing is done with the target on the same CPU core and the same CPU thread and the same CPU resources as the “attacker”.
Similarly, unlike in a crypto timing attack, a hypervisor timer can quiesce the whole system (for a few milliseconds), which dramatically reduces the signal/noise ratio.
Unlike the crypto attacks, the “signal” you’re looking for isn’t complex; you don’t need a trace of “branches taken” in a known piece of code, averaged out to eliminate noise. You just have to observe a causal timing relationship with CPUID (or something else that causes a VM exit).
Remember also that BluePill “emulates” the SVM instructions, allowing for nested VMs. BluePill therefore has to pretend to allow code to run in “ring -1”, to complete the illusion that code is talking directly to the hardware. The only difference between “ring 0” and “ring -1” is that there are additional instructions available in “ring -1”. This is a long-winded way of saying that you can detect a surreptitious hypervisor even on systems that are “supposed to be” running hypervisors.
3.
So that’s the timing attack on hypervisors. We use the following terms:
Direct timing executes instruction sequences that are known to cause VM exits, and counts the cycles they take, looking for abnormally high results.
Indirect timing executes those same instruction sequences, but brackets them with code that saturates a chip resource, baselines the resource, and then checks that the timing remains invariant after the VM exit.
We demoed Direct Timing, against our Vitriol rootkit, at Black Hat. We’ve written several Indirect Timing test cases since then; we hoped to demo at Black Hat this year, but Ferrie has scooped us!
But, at the risk of putting both Ferrie and Rutkowska on our scent, that is far from the end of the story. Ferrie’s paper is premised on the idea that hardware virtualization confers “functional transparency” to a rootkit —- that is to say, there’s no functional difference to observe if hardware virtualization is being used to hide a rootkit (Ferrie presents this in stark contrast to software virtualization, like VMWare and Bochs, which offer a myriad of observable functional “tells”).
Not so!
Here’s a simple example. It’s not ours, but it illustrates the point nicely.
AGP, the graphics bus, wants to offer graphics chipsets access to a physically contiguous buffer of host memory. But it’s hard for a kernel to promise physically contiguous memory in the quantities needed; the kernel is better used to assembling large amounts of memory out of a bunch of physically discontiguous pages; these big chunks of memory can be “virtually” contiguous on the host, using the MMU, but that doesn’t do a graphics chipset much good.
So AGP uses a scatter/gather mechanism called the GART. The GART is a physical-to-physical memory mapping mechanism that establishes an aperture in system memory. It appears contiguous, but is actually backed by discontiguous pages and a programmable mapping table.
You can use the GART to alias physical addresses, and scan memory. Yes, attempts to program the GART can be intercepted and emulated. But they aren’t. That would be work!
4.
And so you have one of the central challenges of trying to install an invisible malicious hypervisor. VMware doesn’t have this problem; it just doesn’t give you a GART to play with, and you don’t care, because you know you’re being virtualized. But Joanna can’t do that! She has to perfectly emulate every detail of the CPU and the chipset, while selectively guarding and emulating behaviors betray the presence of the hypervisor.
If this sounds suspiciously similar to the problem of passively observing network traffic between two hosts whose operating systems you don’t know on a network filled with chaff traffic designed to confuse the observer, that’s because it’s the exact same problem. But turned against the attacker. Ah, irony.
So we have the following set of tactics for detecting virtualization:
Timing Challenges
Direct Timing Challenges: how long does CPUID take?
Indirect Timing Challenges: how does CPUID impact the cache?
Functional Challenges
5.
It turns out, from where we stand, that virtualization is kind of a sucky place to hide rootkit code. There’s a jungle full of places to hide kernel code; “orphaned” kernel threads and backdoored page fault handlers are probably just scratching the surface. From what we can see, there’s no one good way to detect all kernel rootkits. But a virtualized rootkit is a different story: all you ever have to know is, “on this Pentium 4 633, when I’m in ring -1, does this system behave in a way that indicates it’s already virtualized?”.
There are some really easy functional and timing traps you can fall in to. Some of them are easy to defend against, if you know they’re there. And I didn’t think of all (or most) of them. So instead of telling you about them, I’ll:
Hope I’ve gotten some credibility from this post, and
Say “95faf2cfb27b4e271a8943ad44f7d865”, which is the SHA-1 of a quick text file list of the tricks we know about. When Ferrie or Joanna publish enough of them, I’ll post the tfile. =)
It’s nonced, by the way.
Virtualized rootkits are detectable, without external clocks.
(By the way: Ferrie’s paper is excellent, and goes into a huge amount of errata detail he found in software hypervisors).


Add New Comment
Viewing 14 Comments
Thanks. Your comment is awaiting approval by a moderator.
Do you already have an account? Log in and claim this comment.
Do you already have an account? Log in and claim this comment.
Do you already have an account? Log in and claim this comment.
Do you already have an account? Log in and claim this comment.
Do you already have an account? Log in and claim this comment.
Do you already have an account? Log in and claim this comment.
Do you already have an account? Log in and claim this comment.
Do you already have an account? Log in and claim this comment.
Do you already have an account? Log in and claim this comment.
Do you already have an account? Log in and claim this comment.
Do you already have an account? Log in and claim this comment.
Do you already have an account? Log in and claim this comment.
Do you already have an account? Log in and claim this comment.
Do you already have an account? Log in and claim this comment.
Do you already have an account? Log in and claim this comment.
Add New Comment
Trackbacks