0. Side Note
It's very time consuming to relocate this article from the old Geocities website to this blog. Therefore, I opted to relocate it to Google sites. You can read it at: http://sites.google.com/site/pinczakko/pinczakko-s-guide-to-award-bios-reverse-engineering
This blog post is a work in progress. This reminder will be removed once this lengthy detailed post is completed.
A more detailed tutorial on BIOS Reverse engineering is in my book. The book is not just a compilation of the articles I wrote in this blog and the old Geocities website. It's much more than that and it has a fundamental explanation on issues regarding the BIOS security aspect and analysis. I regard it more like a "log-book" about the work that I've done through the last couple of years in BIOS subject, including undisclosed research results. It provides a thorough tutorial on Award BIOS version 6.00PG reverse engineering as well as AMI BIOS version 8 reverse engineering. Moreover, the basics and fundamental concepts is explained in detail in the first part of the book. Therefore, newbies will find it very helpful. Here it is (at the moment, the book is out of print):
It takes quite a lot of effort to finish this book due to the excessive amount of material I wrote in it. Nonetheless, this is just the starting point. I plan to update the book some time in the future. The TIANO Core/UEFI has been gaining momentum recently and also the advances in LinuxBIOS are not minor either. These advances should take the BIOS technology one step above the ladder.
1. Foreword
I would like to welcome you to the darkside of a working example of spaghetty code, The Award BIOS.
This article is not an official guide to award bios reverse engineering nor it's compiled by an Award
Corp. insider. I'm just an ordinary curious person who really attracted to know how my computer BIOS
works. I made this article available to the public to share my findings and looking for feedback from
others since I'm sure I've made some "obscure mistakes" that I didn't realize during my reverse
engineering process. There are several possibilities that make you reading this article now, perhaps
you are an old-time BIOS hacker, perhaps you are a kind of person who really love
"system programming" like me or you are just a curious person who like to tinker. One thing for sure,
you'll get most of out of this article if you've done some BIOS hacking before and looking forward to
improve your skill. However, I've made a prerequisite section below to
ensure you've armed yourself with knowledge needed to get most out of this article.
You may be asking, why would anyone need this guide ? indeed, you need this guide if you found
yourself cannot figure out how award BIOS code works. In my experience, unless you are disassembling
a working BIOS binary, you won't be able to comprehend it. Also, you have to have the majority
(if not all) of your mainboard chips datasheets. The most important one is the chipset datasheet.
The purpose of this article is to clean up the mess and positioned as a handy reference for myself and the
reader as we are going through the BIOS disassembling session. I'm not held responsible about the correctness
of any explanation in this article, you have to cross-check what I wrote here and what you have in your hand.
Note that what I explain here based on 2Mbit(256KB) award bios version 4.51PGNM which I have. You can check it against
award bios version 6.0PG or 6.0 to see if it's still valid. I'll working on that version when I have enough
time. As an addition, I suggest you to read this article throughly from beginning to end to get most out of it.
[+/-] details
2. Prerequisite
First, I would like to thank to the readers of the earlier "beta-version" of this article, from whom I
consider that this part of the article should be included.
I have to admit that BIOS is somehow a state of the art code that requires lots of low level x86 knowledge
that only matter to such a small audience such as operating system developer, BIOS developer, driver writer,
possibly exploit and virus writer (yes exploit and virus writer! coz they are curious people). Due to this
fact, there are couple of things that I won't explain here and it's your homework that you should do to
comprehend this guide. They are :
- The most important thing is you have to be able to program and understand x86 assembly language.
If you don't know it, then you'd better start learning it. I'm using masm syntax throughout this
article.
- Protected mode programming. You have to learn how to switch the x86 machine from real mode to
protected mode. This means you need to learn a preliminary x86 protected mode OS development. I've
done it in the past, that's why I know it pretty good. You can go to
www.osdever.net and other x86 operating system developer
site to get some tutorials to make yourself comfortable. The most important thing to master
is how the protected mode data structures work. I mean how Global Descriptor Table (GDT), Interrupt
Descriptor Table (IDT), also x86 control and segment registers work. BIOS, particularly award BIOS
uses them to perform its "magic" as later explained in this article.
- What x86 "Unreal-Mode" is. Some people also call these mode of operation "Voodoo-mode" or "Flat
real-mode ". It's an x86 state that's between real-mode and protected-mode. This is partially
explained below.
- x86 "direct hardware programming". You need to know how to program the hardware directly,
especially the chips in your motherboard. You can practice this from within windows by developing
an application that directly access the hardware. This is not a must, but it's better if you master
it first. You also have to know some x86 bus protocol, such as PCI and ISA. I'll explain a bit about
the bus protocols below.
- You have to be able to comprehend part (if not all) of the datasheets of your motherboard chip.
Such as the northbridge and southbridge control registers.
[+/-] details
2.1. PCI BUS
We'll begin with the PCI bus. I've been working with this stuff for quite a while. The official
standard for the PCI bus system is maintained by a board named PCISIG (PCI Special Interest Group). This
board actually is some sort of cooperation between Intel and some other big corporation such as Microsoft.
Anyway, in the near future PCI bus will be fully replaced by a much more faster bus system such as Arapahoe
(PCI-Express a.k.a PCI-e) and Hypertransport. But PCI will still remain a standard for sometime I think. I've
read some of the specification of the Hypertansport bus, it's backward compatible with PCI. This means that
the addressing scheme will remains the same or at least only needs a minor modification. This also holds true
for the Arapahoe. One thing I hate about this PCI stuff is that the standard is not an open standard. Thus, you
gotta pay a lot to get the datasheets and whitepapers. This become my main reason providing you with this
sort of tute.
First, PCI bus is a bus which is 32 bits wide. This imply that communicating using this bus should be in
32 bits addressing mode. Pretty logical isn't it? So, writing or reading to this bus will require 32 bits
addresses. Note that eventhough there is a 64-bit PCI bus, it's not natively supported, since PCI uses a dual
address cycle to implement it. So, we can say that PCI primarily a 32-bit bus system.
Second, this bus system is defined in the port CF8h - CFBh which acts as the configuration address port
and port CFCh - CFFh which acts as the configuration data port. These ports are used to configure the
corresponding PCI chip, i.e. reading/writing the PCI chip configuration register values.
Third, this bus system force us to communicate with PCI chips with the following algorithm
(from host CPU point of view):
- Write the target bus number, device number, function number and offset/register number to the
Configuration Address Port and set the Enable bit in it to one. In plain english, write the address
of the register you're willing to read/write into the PCI address port.
- Perform a one-byte, two-byte, or four-byte I/O read from or a write to the Configuration Data Port.
In plain english, write/read the data you're willing to read/write into the PCI data port.
As a note, as far as I know every bus/communication protocol implemented in chip design today uses similar
algorithm to communicate with another chip which has a different bus protocol.
With the above definition, now I'll provide you with an x86 assembly code snippet that shows how to use
those configuration ports.
No. |
Mnemonic (masm syntax) |
Comment |
1 |
pushad |
save all the contents of General Purpose
Registers |
2 |
mov eax,80000064h |
put the address of the PCI chip register to be
accessed in eax (offset 64h device 00:00:00 or hostbridge) |
3 |
mov dx,0CF8h |
put the address port in dx. Since this is PCI,
we use CF8h as the port to open an access to the device. |
4 |
out dx,eax |
send the PCI address port to the I/O space of
the processor |
5 |
mov dx,0CFCh |
put the data port in dx. Since this is PCI, we
use CFCh as the data port to communicate with the device. |
6 |
in eax,dx |
put the data read from the device in eax |
7 |
or eax, 00020202 |
modify the data (this is only example, don't
try this in your machine, it may hang or even destroy your machine) |
8 |
out dx,eax |
send it back .... |
9 |
............ |
- |
10 |
popad |
pop all the saved register |
11 |
ret |
return... |
I think the code above clear enough. In line one the current data in the processors general purpose
registers were saved. Then comes the crucial part. As I said above, PCI is 32 bits bus system hence we have
to use 32 bits chunk of data to communicate with them. We do this by sending the PCI chip a 32 bits address
through eax register, and using port CF8h as the port to send this data. Here's an example of the PCI register
(sometimes called offset) address format. In the routine above you saw :
....
mov eax,80000064h
....
the 80000064h is the address. The meaning of these bits are:
bit position |
31 |
30 |
29 |
28 |
27 |
26 |
25 |
24 |
23 |
22 |
21 |
20 |
19 |
18 |
17 |
16 |
15 |
14 |
13 |
12 |
11 |
10 |
9 |
8 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
binary value |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
1 |
0 |
0 |
1 |
0 |
0 |
hexadecimal value |
8 |
0 |
0 |
0 |
0 |
0 |
6 |
4 |
- The 31st bit is an enable bit. If this bit sets, it means that we are granted to do a write/read
transaction through the PCI bus, otherwise we're prohibited to do so, that's why we need an 8 in the
leftmost hexdigit.
- Bits 30 - 24 are reserved bits.
- Bits 23 - 16 is the PCI Bus number.
- Bits 15 - 11 is the PCI Device number.
- Bits 10 - 8 is the PCI Function Number.
- Bits 7 - 0 is the offset address.
Now, we'll examine the previous value that was sent. If you're curious, you'll find out that
80000064h means we're communicating with the device in bus 0, device 0 , function 0 and at
offset 64. Actually this is the memory controller configuration register of my mainboard's Northbridge. In
most circumstances the PCI device that occupy bus 0, device 0, function 0 is the Hostbridge, but you'll need
to consult your chipset datasheet to verify this. This stuff is pretty easy to be understood, isn't it ?
The next routines are pretty easy to understand. But if you still feel confused you'd better learn
assembly language a bit, since I'm not here to teach you assembly (!_!) . But, in general they do the following
jobs: reading the offset data then modifying it then writing it back to the device, if not better to say
tweaking it (^__^) .
[+/-] details
2.2. ISA BUS
AFAIK, ISA bus is not a well standardized bus. Thus, any ISA device can reside virtually almost anywhere in
the system's 16-bit I/O address space. My experience with ISA bus is very limited. I've only play with two
chips this time around, the first is the CMOS chip and the second one is my mainboard's hardware monitoring
chip, i.e. Winbond W83781D. Both chips uses the same "general algorithm" as mentioned above in the PCI BUS:
- Send the address of the part of the device you're willing to read/write at first. Only after
that your access to send/receive data through the data port to/from the device will be granted.
- Send/receive the data to be read/write through the data port.
My hardware monitoring chip defines port 295h as its address port (a.k.a index port) and port
296h as its data port. CMOS chip defines port 70h as its address port and port 71h
as its data port.
[+/-] details
3. Some Hardware Peculiarities
Due to its history, the x86 platform contains lots of hacks, especially its BIOS. This is due to the
backward compatiblity that should be maintained by any x86 system. In this section I'll try to
explain couple of stuff that I found during my BIOS disassembly journey that reveal these peculiarities.
[+/-] details
3.1. BIOS Chip Addressing
The most important chips which responsible for the BIOS code handling are the southbridge and northbridge.
In this respect, the northbridge is responsible for the system address space management, i.e. BIOS
shadowing, handling accesses to RAM and forwarding transaction which uses BIOS ROM as its target to
the southbridge which then eventually forwarded to BIOS ROM by southbridge.
While the southbridge is responsible for enabling the ROM decode control, which will forward (or not) the
memory addresses to be accessed to the BIOS ROM chip. The addresses shown below can reside either
in the system DRAM or in BIOS ROM chip, depending on the southbridge and northbridge register setting at the
time the BIOS code is executed.
Physical Address |
Also Known As |
Used by |
Address Aliasing Note |
000F_0000h - 000F_FFFFh |
F_seg / F_segment |
1 Mbit, 2 MBit, and 4 MBit BIOS |
alias to FFFF_0000h - FFFF_FFFFh in all chipset just after power-up |
000E_0000h - 000E_FFFFh |
E_seg / E_segment |
1 Mbit, 2 MBit, and 4 MBit BIOS |
alias to FFFE_0000h - FFFE_FFFFh in some chipset just after power-up |
The address ranges shown above contain the BIOS code and pretty much system specific. So, you have to consult your
chipset datasheets to understand it. Also, note that the address above which will be occupied by the BIOS code during
runtime (after BIOS code executes) is only F_seg i.e. F_0000h - F_FFFFh. However, certain operating system might "trash" this address and use it for their own purposes. The addresses written above only reflect the addressing of the BIOS ROM chip to the system address space when it's set to be accessed
by the BIOS code or another code that accesses the BIOS ROM chip directly. The mainboard chipsets
are responsible for the mapping of certain BIOS ROM chip area to the system address space. As we will see later,
this mapping can be changed by programming certain chipset registers.
BIOS chip with capacity bigger than 1 Mbit, i.e. 2 Mbit and 4 Mbit chips has a quite different addressing for their
lower bios area, i.e. C_seg, D_seg and other lower segment(s). In most cases, this area is mapped to
near-4GB address range. This address range is handled by the norhtbridge analogous to the PCI address range.
In this scheme the chipset behaves as follows:
- The northbridge acts as the address forwarder, meaning: it responds to this "special" memory address in
different fashion compared to "normal" memory address which is forwarded directly to RAM. On the contrary,
this "special" memory address is forwarded by the northbridge to the southbridge to be decoded.
- The southbridge acts as the address decoder, meaning: it decodes this "special" memory addresses into the
right chip "beneath" it, such as the BIOS chip. In this respect, the southbridge will return "void"
(bus address cycle termination) if the address range is not being enabled to be decoded in the southbridge
configuration registers.
Below is an example:
Physical Address |
Also Known As |
Used by |
Address Aliasing Note |
000F_0000h - 000F_FFFFh |
F_seg / F_segment |
1 Mbit, 2 MBit, and 4 Mbit BIOS |
alias to FFFF_0000h - FFFF_FFFFh in all chipset just after power-up |
000E_0000h - 000E_FFFFh |
E_seg / E_segment |
1 Mbit, 2 Mbit, and 4 Mbit BIOS |
alias to FFFE_0000h - FFFE_FFFFh in some chipset just after power-up |
FFFD_0000h - FFFD_FFFFh |
D_seg / D_segment |
2 Mbit, and 4 Mbit BIOS |
- |
FFFC_0000h - FFFC_FFFFh |
C_seg / C_segment |
2 Mbit, and 4 Mbit BIOS |
- |
FFF8_0000h - FFFB_FFFFh |
- |
4 Mbit BIOS |
- |
The conclusion is: modern day chipsets performs an emulation for F_seg and E_seg handling. This is
a proof that modern day x86 systems maintains backward compatibility. However, this "kludge" sometimes referred
to as the thing of the past that vendors should've been removed from x86 systems.
Below is the VIA693A chipset (Northbridge) system memory map just after system power-up as written in its datasheet.
Table 4. System Memory Map
Space Start Size Address Range Comment
DOS 0 640K 00000000-0009FFFF Cacheable
VGA 640K 128K 000A0000-000BFFFF Used for SMM
BIOS 768K 16K 000C0000-000C3FFF Shadow Ctrl 1
BIOS 784K 16K 000C4000-000C7FFF Shadow Ctrl 1
BIOS 800K 16K 000C8000-000CBFFF Shadow Ctrl 1
BIOS 816K 16K 000CC000-000CFFFF Shadow Ctrl 1
BIOS 832K 16K 000D0000-000D3FFF Shadow Ctrl 2
BIOS 848K 16K 000D4000-000D7FFF Shadow Ctrl 2
BIOS 864K 16K 000D8000-000DBFFF Shadow Ctrl 2
BIOS 880K 16K 000DC000-000DFFFF Shadow Ctrl 2
BIOS 896K 64K 000E0000-000EFFFF Shadow Ctrl 3
BIOS 960K 64K 000F0000-000FFFFF Shadow Ctrl 3
Sys 1MB 00100000-DRAM Top Can have hole
Bus D Top DRAM Top-FFFEFFFF
Init 4G-64K 64K FFFEFFFF-FFFFFFFF 000Fxxxx alias
The most important thing to take into account here is the address aliasing, as you can see the FFFE_FFFFh-
FFFF_FFFFh address range is an alias into 000Fxxxxh, this is where the BIOS ROM chip address mapped
(at least in my mainboard, cross check with yours). But, we also have to consider that this only applies at
the very beginning of boot stage (just after reset). After the chipset reprogrammed by the BIOS, this address
range will be mapped into system DRAM chips. We can consider this as the Power-On default values. As a note,
the majority of x86 chipsets use this address aliasing scheme, at least for the F-segment address range.
Another fact that we have to take into account: most chipset only provides default addressing scheme
for F-segment just after power-up in its configuration registers, other "BIOS ROM segment(s)"
remains inaccessible. The addressing scheme for these segments will be configured later by the bootblock
code by altering the related chipset registers (in most cases the southbridge registers). The chipset that's being dissected here also belongs to this group.
Modern day systems connect the BIOS ROM chip to the southbridge through LPC(Low Pin Count) interface.
However, the southbridge described in this article don't have such a interface. It's an old chipset and still uses ISA
BUS to interface with the BIOS ROM chip.
[+/-] details
3.2. Obscure Hardware Port
Some "obscure" hardware port which sometimes not documented in the chipset datasheets described below.
Note that these info's were found from Intel ICH5, VIA 586B and VIA596B datasheet.
I/O Port address Purpose
92h Fast A20 and Init Register
4D0h Master PIC Edge/Level Triggered (R/W)
4D1h Slave PIC Edge/Level Triggered (R/W)
Table 146. RTC I/O Registers (LPC I/F D31:F0)
I/O Port Locations If U128E bit = 0 Function
70h and 74h Also alias to 72h and 76h Real-Time Clock (Standard RAM) Index Register
71h and 75h Also alias to 73h and 77h Real-Time Clock (Standard RAM) Target Register
72h and 76h Extended RAM Index Register (if enabled)
73h and 77h Extended RAM Target Register (if enabled)
NOTES:
1. I/O locations 70h and 71h are the standard ISA location for the real-time clock. The map for this bank is
shown in Table 147. Locations 72h and 73h are for accessing the extended RAM. The extended RAM bank is
also accessed using an indexed scheme. I/O address 72h is used as the address pointer and I/O address
73h is used as the data register. Index addresses above 127h are not valid. If the extended RAM is not
needed, it may be disabled.
2. Software must preserve the value of bit 7 at I/O addresses 70h. When writing to this address, software
must first read the value, and then write the same value for bit 7 during the sequential address write.
Note that port 70h is not directly readable. The only way to read this register is through Alt Access
mode. If the NMI# enable is not changed during normal operation, software can alternatively read this bit
once and then retain the value for all subsequent writes to port 70h.
The RTC contains two sets of indexed registers that are accessed using the two separate Index and
Target registers (70/71h or 72/73h), as shown in Table 147.
Table 147. RTC (Standard) RAM Bank (LPC I/F D31:F0)
Index Name
00h Seconds
01h Seconds Alarm
02h Minutes
03h Minutes Alarm
04h Hours
05h Hours Alarm
06h Day of Week
07h Day of Month
08h Month
09h Year
0Ah Register A
0Bh Register B
0Ch Register C
0Dh Register D
0Eh-EFh 114 Bytes of User RAM
[+/-] details
3.3. "Relocatable" Hardware Port
There are several kinds of hardware port that is relocatable in the system I/O address space. In this BIOS,
those ports include SMBus-related ports and Power-Management-Related ports. These ports has certain
base address. This so called base address is controlled via programmable base address
register. SMBus has SMBus base address register and Power-Management has Power-Management I/O base address
register. Since these ports are programmable, the bootblock routine initializes the value of the base address
registers in the very beginning of BIOS routine execution. Due to the programmable nature of these ports, one
must start reverse engineering of BIOS in the bootblock to find out which port address(es) used by these
programmable hardware ports. Otherwise one will be confused by the occurence of "weird" ports later during
the reverse engineering process. An example of this case provided below.
Address Hex Mnemonic
F000:F604 BE C4 F6 mov si, 0F6C4h ; addr of chipset reg mask
F000:F607 next_PCI_reg: ; CODE XREF: Chipset_Reg_Early_Init+29
F000:F607 2E 8B 0C mov cx, cs:[si]
F000:F60A BC 10 F6 mov sp, 0F610h
F000:F60D E9 F8 00 jmp Read_PCI_Byte
F000:F60D ; ---------------------------------------------------------------------------
F000:F610 12 F6 dw 0F612h
F000:F612 ; ---------------------------------------------------------------------------
F000:F612 2E 22 44 02 and al, cs:[si+2]
F000:F616 2E 0A 44 03 or al, cs:[si+3]
F000:F61A BC 20 F6 mov sp, 0F620h
F000:F61D E9 02 01 jmp Write_PCI_Byte
F000:F61D ; ---------------------------------------------------------------------------
F000:F620 22 F6 dw 0F622h
F000:F622 ; ---------------------------------------------------------------------------
F000:F622 83 C6 04 add si, 4
F000:F625 81 FE 04 F7 cmp si, 0F704h ; are we done yet?
.........
F000:F6F4 48 3B dw 3B48h ; B#0 D#7 F#3: PwrMngmt&SMBus - PwrMngmt IO Base Addr lo_byte
F000:F6F6 00 db 0 ; and mask
F000:F6F7 00 db 0 ; or mask
F000:F6F7 ;
F000:F6F8 49 3B dw 3B49h ; B#0 D#7 F#3: PwrMngmt&SMBus - PwrMngmt IO Base Addr hi_byte
F000:F6FA 40 db 40h ; and mask
F000:F6FB 40 db 40h ; PwrMngmt IO Base Addr = IO Port 4000h
.........
F000:F643 B9 90 3B mov cx, 3B90h ; B#0 D#7 F#3: PwrMngmt&SMBus - SMBus IO Base Addr lo_byte
F000:F646 B0 00 mov al, 0 ; set SMBus IO Base lo_byte to 00h
F000:F648 BC 4E F6 mov sp, 0F64Eh
F000:F64B E9 D4 00 jmp Write_PCI_Byte
F000:F64B ; ---------------------------------------------------------------------------
F000:F64E 50 F6 dw 0F650h
F000:F650 ; ---------------------------------------------------------------------------
F000:F650 B9 91 3B mov cx, 3B91h ; B#0 D#7 F#3: PwrMngmt&SMBus - SMBus IO Base Addr hi_byte
F000:F653 B0 50 mov al, 50h ; 'P' ; set SMBus IO Base hi_byte to 50h,
F000:F653 ; so, now SMBus IO Base is at port 5000h !!!
F000:F655 BC 5B F6 mov sp, 0F65Bh
F000:F658 E9 C7 00 jmp Write_PCI_Byte
F000:F658 ; ---------------------------------------------------------------------------
F000:F65B 5D F6 dw 0F65Dh
.........
F000:F66A BA 05 40 mov dx, 4005h ; access ACPI Reg 05h
F000:F66D B0 80 mov al, 80h ; ; setting reserved bit?
.........
Certainly, there are more relocatable hardware ports than those described here, but at least you've been given the
hints about it. So that, once you found code in the BIOS that seems to be accessing "weird" ports,
you'll know where to go.
[+/-] details
3.4. Expansion ROM Handling
There are couples of more things to take into account, such as the Video BIOS and other expansion ROM
handling. I'll try to cover that stuff later when I've done dissecting BIOS code that handle it. But here's
the basic run-down of PCI Expansion ROM handling in BIOS:
- System BIOS detect all PCI chip in the system and initialize its BARs(Base Address Registers).
Once initialization completes, the system will have a usable system-wide addressing scheme.
- By using the system-wide addressing scheme, system BIOS then copies the implemented
PCI Expansion ROM into RAM one by one in the expansion ROM area (C000:0000h - D000:FFFFh) and execute it there
until all of the PCI expansion ROM have been executed/initialized.
ISA Expansion ROM is not relevant these days. Therefore it's not discussed here.
[+/-] details