Pinczakko's Guide to Reversing Award BIOS DLL Support
Table of Contents
1. Foreword
Welcome to another installment of the Pinczakko Guide's series :). In this article I explain how the emerging "support" for Windows Dynamic Link Libraries (DLL) is implemented in recent Award BIOS. As you will see later, full-fledged DLL is not supported by Award BIOS. In fact, the DLL header is just discarded entirely by the Award BIOS code.
This article assumes that you have a deep understanding of Award BIOS binaries. If you haven't mastered it yet, you'd better read the prerequisite section below and make sure that you've armed yourself with enough knowledge before proceeding to read this article.
The purpose of this article is to be your reference when disassembling a DLL file in Award BIOS binary. It's important to know the execution environment of the DLL in order to understand the DLL contents. Anyway, 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: The explanation in this article is based on 8Mbit(1MB) Award BIOS version 6.00PG (a "legacy" BIOS code base, not EFI/UEFI). The BIOS dissected here is M78GUS2H.BIN; the BIOS binary file for Gigabyte GA-MA78GM-US2H motherboard. The BIOS release date is September 17th, 2009.
2. Prerequisites
The 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 have fairly good knowledge on the matter. 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), x86 control registers and segment registers work.
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. Remember that "flat real-mode" and flat protected-mode are two different beast.
x86 "direct hardware programming". You need to know how to program the hardware directly, especially the chips in your motherboard. You can practice this in 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 have to have deep understanding of Award BIOS binary file. I have written an in-depth article about the matter here. You should read that article if you haven't understand the basic structure and execution flow of an Award BIOS.
Some familiarity with PE file format is important. You can read Iczelion's PE tutorials about the subject if you haven't know it yet.
Some familiarity with DOS MZ file format is also important. The article in this link explains about the subject.
Those are all you need to know to understand this article. If you think you're prepared, move on to the next section.
3. The Rationale Behind DLL "Support" in BIOS
Recent BIOS supports 32-bit DLL or certain "derivative" form of 32-bit DLL (read: stripped to the bare essential) for several reasons:
For code reuse. Several years ago the migration to UEFI-based firmware in x86/x86-64 platforms were started. This event "forces" the chipset vendors to provide one code base for both UEFI and legacy BIOS if they don't want to duplicate the chipset-specific code for both firmware types. This means, the code must be provided in 32-bit environment, specifically in the form of 32-bit Portable Executable (PE) file format because UEFI supports that file format. DLL is a file with PE file format.
To ease code maintenance. The move to 32-bit base code means that the use of C language for BIOS module development is possible. Moving to higher level language from assembler means the burden on maintaining the source code for the BIOS module is relatively reduced.
There are probably other reasons for the DLL "support", but those are the ones that I'm aware of at the moment.
4. The DLL
DLL components in Award BIOS are uncompressed components. Some of them are called directly from the bootblock. That's the reason why they are not compressed at all.
You can use CBROM or a hex editor to extract the DLL components from the BIOS binary. The CBROM command to extract a DLL component is simple. For example, to extract HT.DLL from the M78GUS2H.BIN binary file, I use the following command.
CBROM32_195.exe M78GUS2H.BIN /htinit extract
Another example, to extract ahci.DLL from the M78GUS2H.BIN binary file, I use the following command.
CBROM32_195.exe M78GUS2H.BIN /oem3 extract
The command to use to extract the DLL varies depending on the "type" of the DLL. The following command shows how to look for the type of files within the M78GUS2H.BIN binary file.
CBROM32_195.exe M78GUS2H.BIN /D
******** M78GUS2H.BIN BIOS component ********
No. Item-Name Original-Size Compressed-Size Original-File-Name ================================================================================
0. System BIOS 20000h(128.00K) 12B4Ch(74.82K) M78GUS2H.BIN
1. XGROUP CODE 0E6B0h(57.67K) 09ED3h(39.71K) awardext.rom
2. ACPI table 0748Dh(29.14K) 03479h(13.12K) ACPITBL.BIN
3. EPA LOGO 0168Ch(5.64K) 0030Dh(0.76K) AwardBmp.bmp
4. GROUP ROM[18] 04500h(17.25K) 02E43h(11.57K) ggroup.bin
5. GROUP ROM[20] 030E0h(12.22K) 02411h(9.02K) ffgroup.bin
6. YGROUP ROM 0BC50h(47.08K) 060CDh(24.20K) awardeyt.rom
7. GROUP ROM[22] 0F630h(61.55K) 008A2h(2.16K) tgroup.bin
8. GROUP ROM[23] 0F630h(61.55K) 0015Bh(0.34K) t1group.bin
9. GROUP ROM[24] 0F630h(61.55K) 0015Ch(0.34K) t2group.bin
10. GROUP ROM[ 0] 07660h(29.59K) 03041h(12.06K) _EN_CODE.BIN
11. OEM1 CODE 0FFFFh(64.00K) 012F6h(4.74K) GBTEC140.BIN
12. OEM2 CODE 0FFFFh(64.00K) 00C3Eh(3.06K) GBTEC8_2.BIN
13. VGA ROM[1] 0EA00h(58.50K) 094CFh(37.20K) VGA_DVI.BIN
14. OEM6 CODE 0EA00h(58.50K) 094D0h(37.20K) VGA_HDMI.BIN
15. PCI ROM[A] 0FC00h(63.00K) 09CBCh(39.18K) RAID750.BIN
16. PCI ROM[B] 03600h(13.50K) 02427h(9.04K) ahci.BIN
17. OEM3 CODE 0C000h(48.00K) 0704Ch(28.07K) ahci.DLL
18. PCI ROM[C] 0B800h(46.00K) 068C8h(26.20K) RTEGPXE.LOM
19. LOGO1 ROM 00B64h(2.85K) 00520h(1.28K) DBIOS.BMP
20. OEM0 CODE 03006h(12.01K) 022A6h(8.66K) SBF.BIN
21. GV3 09003h(36.00K) 03072h(12.11K) AGESACPU.ROM
22. MINIT 0E5EFh(57.48K) 0E61Eh(57.53K) MEMINIT.BIN
23. HTINIT 05011h(20.02K) 05042h(20.06K) HT.DLL
24. 2 PE32 in MB 00550h(1.33K) 0058Fh(1.39K) HT32GATE.BIN
(SP) NCPUCODE 06000h(24.00K) 06000h(24.00K) NCPUCODE.BIN
(SP) HOLE0 7CD8h(31.21K) 7CD8h(31.21K) CIMRS780.B2
(SP) HOLE1 2FE8h(11.98K) 2FE8h(11.98K) CIMSB700.B2
(SP) HOLE2 C800h(50.00K) C800h(50.00K) UI750.BIN
(SP) HOLE3 FFFFh(64.00K) FFFFh(64.00K) GBTEC140.BIN
Total hole area space = 30000h(192.00K)
Total compress code space = A4000h(656.00K)
Total compressed code size = 7C771h(497.86K)
Remain compress code space = 278AFh(158.17K)
** Micro Code Information **
Update ID CPUID | Update ID CPUID | Update ID CPUID | Update ID CPUID
------------------+--------------------+--------------------+-------------------
1000002 0000|
The second column (Item-Name) above is the "type". You can use the following command to find out how to work with certain "type" of item in the BIOS binary.
CBROM32_195.exe /?
That's it for the intro to the DLL in Award BIOS. The next sections will explain the details.
4.1 The DLL Execution Environment
Note: Listings in this article show "alternating" addresses in the BIOS binary because they are meant to reveal the code execution flow, not the position of the code in the BIOS binary.
The DLL file in Award BIOS executes in flat 32-bit protected mode with linear address equal to physical address and paging is disabled. Listing 0 shows the Global Descriptor Table (GDT) and the switch to 32-bit flat protected mode routine prior to execution of the DLL.
Listing 0: DLL execution environment in recent Award BIOS
Address Hex Mnemonic
.........
F000:2F0D FF FF 00 00 00 93 8F 00 CIM_GDT dq 8F93000000FFFFh ; 16-bit segment; limit=4GB; base addr=0000_0000; F000:2F0D ; present; user:data (R/W)
F000:2F15 FF FF 00 00 00 93 CF 00 dq 0CF93000000FFFFh ; 32-bit segment; limit=4GB; base addr=0000_0000; F000:2F15 ; present; user:data (R/W)
F000:2F1D FF FF 00 00 00 9B CF 00 dq 0CF9B000000FFFFh ; 32-bit segment; limit=4GB; base addr=0000_0000; F000:2F1D ; present; user:code (Readable/eXecutable)
F000:2F25 FF FF 20 00 0E 9B 8F 00 dq 8F9B0E0020FFFFh ; 16-bit segment; limit=4GB; base addr=000E_0020; F000:2F25 ; present; user:code (Readable/eXecutable)
F000:2F2D FF FF 00 00 00 93 0F 00 dq 0F93000000FFFFh ; 16-bit segment; limit=1MB; base addr=0000_0000; F000:2F2D ; present; user:data (R/W)
.........
F000:2F6D 00 62 FB FF HT32GATE_BIN_phy_addr dd 0FFFB6200h ; phy addr of HT32GATE.BIN start in BIOS ROM
F000:2F71 xec_32_flat_pm_HT32GATE proc near
F000:2F71 FA cli
F000:2F72 FC cld
F000:2F73 66 52 push edx
F000:2F75 66 B8 04 00 F0 FF mov eax, 0FFF00004h
F000:2F7B 66 33 D2 xor edx, edx
F000:2F7E 66 B9 0E 02 00 00 mov ecx, 20Eh
F000:2F84 0F 30 wrmsr
F000:2F86 FE C1 inc cl
F000:2F88 66 B8 00 08 F0 FF mov eax, 0FFF00800h
F000:2F8E 66 BA FF 00 00 00 mov edx, 0FFh
F000:2F94 0F 30 wrmsr
F000:2F96 66 5A pop edx
F000:2F98 8B C4 mov ax, sp
F000:2F9A 50 push ax
F000:2F9B 8C D0 mov ax, ss
F000:2F9D 50 push ax
F000:2F9E B9 28 00 mov cx, 28h ; '('
F000:2FA1 2B E1 sub sp, cx
F000:2FA3 8B EC mov bp, sp
F000:2FA5 8D 3E 0D 2F lea di, CIM_GDT
F000:2FA9 F000:2FA9 loc_F000_2FA9:
F000:2FA9 2E 8A 05 mov al, cs:[di]
F000:2FAC 88 46 00 mov [bp+0], al
F000:2FAF 47 inc di
F000:2FB0 45 inc bp
F000:2FB1 E2 F6 loop loc_
F000_2FA9
F000:2FB3 8B EC mov bp, sp
F000:2FB5 66 33 C0 xor eax, eax
F000:2FB8 8C C8 mov ax, cs
F000:2FBA 66 C1 E0 04 shl eax, 4
F000:2FBE 89 46 1A mov [bp+1Ah], ax
F000:2FC1 66 C1 E8 10 shr eax, 10h
F000:2FC5 88 46 1C mov [bp+1Ch], al
F000:2FC8 66 33 C0 xor eax, eax
F000:2FCB 8C D0 mov ax, ss
F000:2FCD 66 C1 E0 04 shl eax, 4
F000:2FD1 66 03 C4 add eax, esp
F000:2FD4 66 50 push eax
F000:2FD6 68 27 00 push 27h ; '''
F000:2FD9 8B EC mov bp, sp
F000:2FDB 0F 01 56 00 lgdt fword ptr [bp+0]
F000:2FDF 66 33 C0 xor eax, eax
F000:2FE2 8C D0 mov ax, ss
F000:2FE4 66 C1 E0 04 shl eax, 4
F000:2FE8 66 03 C4 add eax, esp
F000:2FEB 66 8B E0 mov esp, eax
F000:2FEE 0F 20 C0 mov eax, cr0
F000:2FF1 0C 01 or al, 1
F000:2FF3 0F 22 C0 mov cr0, eax
F000:2FF6 EB 00 jmp short $+2
F000:2FF8 B8 08 00 mov ax, 8
F000:2FFB 8E D8 mov ds, ax
F000:2FFD assume ds:nothing
F000:2FFD 8E C0 mov es, ax
F000:2FFF assume es:nothing
F000:2FFF 8E E0 mov fs, ax
F000:3001 assume fs:nothing
F000:3001 8E E8 mov gs, ax
F000:3003 assume gs:nothing
F000:3003 8E D0 mov ss, ax
F000:3005 assume ss:nothing
F000:3005 66 B8 00 F0 00 00 mov eax, 0F000h
F000:300B 66 C1 E0 04 shl eax, 4
F000:300F 66 05 21 30 00 00 add eax, 3021h
F000:3015 66 50 push eax
F000:3017 66 68 10 00 00 00 push large 10h
F000:301D 66 56 push esi ; esi = phy addr of HT32GATE.BIN entry point
F000:301F db 66h
F000:301F 66 CB retf ; call HT32GATE.BIN entry point and enter 32-bit Protected Mode
Listing 0 doesn't show how the DLL gets called. Section 4.2 will explain that in detail. The important thing from listing 0 is the listing shows the state of the segment descriptors prior to HT32GATE.BIN execution. HT32GATE.BIN calls the DLL module directly without changing anything in the segment descriptors. Therefore, the execution environment of both HT32GATE.BIN and the DLL is the same.
4.2 How The BIOS "Calls" The DLL
The process to call the DLL starts at the bootblock and continues to uncompressed parts of the BIOS binary until the DLL file gets executed.
Listing 1: BIOS code execution from reset until the call to HT32GATE.BIN
Address Hex Mnemonic
F000:FFF0 EA 5B E0 00 F0 jmp far ptr bb_entry
.........
F000:E05B bb_entry:
F000:E05B EA 60 E0 00 F0 jmp far ptr loc_F000_E060
F000:E060
F000:E060 loc_F000_E060:
F000:E060 8E EA mov gs, dx
F000:E062 FA cli
F000:E063 FC cld
F000:E064 8C C8 mov ax, cs
F000:E066 8E D0 mov ss, ax
F000:E068 assume ss:nothing
F000:E068 F000:E068 loc_F000_E068:
F000:E068 66 B9 1B 00 00 00 mov ecx, 1Bh
F000:E06E 0F 32 rdmsr
F000:E070 A9 00 01 test ax, 100h
F000:E073 74 20 jz short init_chipset
.........
F000:E095 init_chipset:
F000:E095 BC 9B E0 mov sp, 0E09Bh
F000:E098 E9 D5 4F jmp prep_exec_HT_init
.........
F000:3070 prep_exec_HT_init proc near
F000:3070 0F 6E CC movd mm1, esp
F000:3073 BC 79 30 mov sp, 3079h
F000:3076 E9 57 60 jmp c_c_exec_HT32GATE_N_HT_DLL
.........
F000:308C C3 retn
F000:308C prep_exec_HT_init endp
.........
F000:90D0 c_c_exec_HT32GATE_N_HT_DLL proc near
F000:90D0 BB D6 90 mov bx, 90D6h
F000:90D3 E9 8E 07 jmp sub_F000_9864
.........
F000:913D E8 2B 00 call near ptr c_exec_HT32GATE_N_HT_DLL
F000:9140 E8 C0 CE call nullsub_5
F000:916A C3 retn
F000:916A c_c_exec_HT32GATE_N_HT_DLL endp
.........
F000:916B c_exec_HT32GATE_N_HT_DLL proc far
.........
F000:9193 E8 BA DA call sub_F000_6C50
F000:9196 72 05 jb short loc_F000_919D
F000:9198 E8 25 9D call exec_HT32GATE_N_HT_DLL
......... F000:91E4 C3 retn
F000:91E4 c_exec_HT32GATE_N_HT_DLL endp
.........
F000:2EC0 exec_HT32GATE_N_HT_DLL proc near
F000:2EC0 66 B9 1B 00 00 00 mov ecx, 1Bh
F000:2EC6 0F 32 rdmsr
F000:2EC8 66 0F BA E0 08 bt eax, 8
F000:2ECD 73 31 jnb short loc_F000_2F00
F000:2ECF 66 8D 1E 45 2F lea ebx, HT_DLL_entry_phy_addr
F000:2ED4 67 66 2E 8B 13 mov edx, cs:[ebx]
F000:2ED9 66 8B F2 mov esi, edx
F000:2EDC E8 86 6F call sub_F000_9E65
F000:2EDF E8 CE 6E call sub_F000_9DB0
F000:2EE2 72 1F jb short loc_F000_2F03
F000:2EE4 66 8D 36 6D 2F lea esi, HT32GATE_BIN_phy_addr
F000:2EE9 67 66 2E 8B 06 mov eax, cs:[esi]
F000:2EEE 66 8B F0 mov esi, eax
F000:2EF1 E8 BC 6E call sub_F000_9DB0
F000:2EF4 72 0D jb short loc_F000_2F03
F000:2EF6 66 81 C6 00 02 00 00 add esi, 200h ; esi = HT32GATE entry point (discard DOS MZ header)
F000:2EFD E8 71 00 call xec_32_flat_pm_HT32GATE
F000:2F00 F000:2F00 loc_F000_2F00:
F000:2F00 F8 clc
F000:2F01 EB 09 jmp short exit
F000:2F03 ; ---------------------------------------------------------------------------
F000:2F03 F000:2F03 loc_F000_2F03:
F000:2F03
F000:2F03 B0 F9 mov al, 0F9h ; '·'
F000:2F05 E6 72 out 72h, al
F000:2F07 B0 55 mov al, 55h ; 'U'
F000:2F09 E6 73 out 73h, al
F000:2F0B F9 stc
F000:2F0C F000:2F0C exit:
F000:2F0C C3 retn
F000:2F0C exec_HT32GATE_N_HT_DLL endp
F000:2F0D FF FF 00 00 00 93 8F 00 CIM_GDT dq 8F93000000FFFFh ; 16-bit segment; limit=4GB; base addr=0000_0000;
F000:2F0D ; present; user:data (R/W)
F000:2F15 FF FF 00 00 00 93 CF 00 dq 0CF93000000FFFFh ; 32-bit segment; limit=4GB; base addr=0000_0000;
F000:2F15 ; present; user:data (R/W)
F000:2F1D FF FF 00 00 00 9B CF 00 dq 0CF9B000000FFFFh ; 32-bit segment; limit=4GB; base addr=0000_0000;
F000:2F1D ; present; user:code (Readable/eXecutable)
F000:2F25 FF FF 20 00 0E 9B 8F 00 dq 8F9B0E0020FFFFh ; 16-bit segment; limit=4GB; base addr=000E_0020;
F000:2F25 ; present; user:code (Readable/eXecutable)
F000:2F2D FF FF 00 00 00 93 0F 00 dq 0F93000000FFFFh ; 16-bit segment; limit=1MB; base addr=0000_0000;
F000:2F2D ; present; user:data (R/W)
F000:2F35 48 db 48h ; H
F000:2F36 54 db 54h ; T
F000:2F37 49 db 49h ; I
F000:2F38 4E db 4Eh ; N
F000:2F39 49 db 49h ; I
F000:2F3A 54 db 54h ; T
F000:2F3B 45 db 45h ; E
F000:2F3C 4E db 4Eh ; N
F000:2F3D 54 db 54h ; T
F000:2F3E 52 db 52h ; R
F000:2F3F 59 db 59h ; Y
F000:2F40 50 db 50h ; P
F000:2F41 4F db 4Fh ; O
F000:2F42 49 db 49h ; I
F000:2F43 4E db 4Eh ; N
F000:2F44 54 db 54h ; T
F000:2F45 EC 5B FB FF HT_DLL_entry_phy_addr dd 0FFFB5BECh ; phy addr of HT.DLL entry point ("dllmain" function) address in BIOS ROM .........
F000:2F6D 00 62 FB FF HT32GATE_BIN_phy_addr dd 0FFFB6200h ; phy addr of HT32GATE.BIN start in BIOS ROM
F000:2F71 xec_32_flat_pm_HT32GATE proc near
F000:2F71 FA cli
F000:2F72 FC cld
F000:2F73 66 52 push edx
F000:2F75 66 B8 04 00 F0 FF mov eax, 0FFF00004h
F000:2F7B 66 33 D2 xor edx, edx
F000:2F7E 66 B9 0E 02 00 00 mov ecx, 20Eh
F000:2F84 0F 30 wrmsr
F000:2F86 FE C1 inc cl
F000:2F88 66 B8 00 08 F0 FF mov eax, 0FFF00800h
F000:2F8E 66 BA FF 00 00 00 mov edx, 0FFh
F000:2F94 0F 30 wrmsr
F000:2F96 66 5A pop edx
F000:2F98 8B C4 mov ax, sp
F000:2F9A 50 push ax
F000:2F9B 8C D0 mov ax, ss
F000:2F9D 50 push ax
F000:2F9E B9 28 00 mov cx, 28h ; '('
F000:2FA1 2B E1 sub sp, cx
F000:2FA3 8B EC mov bp, sp
F000:2FA5 8D 3E 0D 2F lea di, CIM_GDT
F000:2FA9 F000:2FA9 loc_F000_2FA9:
F000:2FA9 2E 8A 05 mov al, cs:[di]
F000:2FAC 88 46 00 mov [bp+0], al
F000:2FAF 47 inc di
F000:2FB0 45 inc bp
F000:2FB1 E2 F6 loop loc_F000_2FA9
F000:2FB3 8B EC mov bp, sp
F000:2FB5 66 33 C0 xor eax, eax
F000:2FB8 8C C8 mov ax, cs
F000:2FBA 66 C1 E0 04 shl eax, 4
F000:2FBE 89 46 1A mov [bp+1Ah], ax
F000:2FC1 66 C1 E8 10 shr eax, 10h
F000:2FC5 88 46 1C mov [bp+1Ch], al
F000:2FC8 66 33 C0 xor eax, eax
F000:2FCB 8C D0 mov ax, ss
F000:2FCD 66 C1 E0 04 shl eax, 4
F000:2FD1 66 03 C4 add eax, esp
F000:2FD4 66 50 push eax
F000:2FD6 68 27 00 push 27h ; '''
F000:2FD9 8B EC mov bp, sp
F000:2FDB 0F 01 56 00 lgdt fword ptr [bp+0]
F000:2FDF 66 33 C0 xor eax, eax
F000:2FE2 8C D0 mov ax, ss
F000:2FE4 66 C1 E0 04 shl eax, 4
F000:2FE8 66 03 C4 add eax, esp
F000:2FEB 66 8B E0 mov esp, eax
F000:2FEE 0F 20 C0 mov eax, cr0
F000:2FF1 0C 01 or al, 1
F000:2FF3 0F 22 C0 mov cr0, eax
F000:2FF6 EB 00 jmp short $+2
F000:2FF8 B8 08 00 mov ax, 8
F000:2FFB 8E D8 mov ds, ax
F000:2FFD assume ds:nothing
F000:2FFD 8E C0 mov es, ax
F000:2FFF assume es:nothing
F000:2FFF 8E E0 mov fs, ax
F000:3001 assume fs:nothing
F000:3001 8E E8 mov gs, ax
F000:3003 assume gs:nothing
F000:3003 8E D0 mov ss, ax
F000:3005 assume ss:nothing
F000:3005 66 B8 00 F0 00 00 mov eax, 0F000h
F000:300B 66 C1 E0 04 shl eax, 4
F000:300F 66 05 21 30 00 00 add eax, 3021h
F000:3015 66 50 push eax
F000:3017 66 68 10 00 00 00 push large 10h
F000:301D 66 56 push esi ; esi = phy addr of HT32GATE.BIN entry point
F000:301F db 66h
F000:301F 66 CB retf ; call HT32GATE.BIN entry point and enter PMode
F000:3021 ; ---------------------------------------------------------------------------
F000:3021 EA 28 30 00 00 jmp far ptr 0:3028h ; jump "below" to go back to real mode
F000:3021 ; ---------------------------------------------------------------------------
F000:3026 18 db 18h
F000:3027 00 db 0
F000:3028 ; ---------------------------------------------------------------------------
F000:3028 B8 20 00 mov ax, 20h ; ' '
F000:302B 8E D0 mov ss, ax
F000:302D assume ss:nothing
F000:302D 0F 20 C0 mov eax, cr0
F000:3030 24 FE and al, 0FEh
F000:3032 0F 22 C0 mov cr0, eax
F000:3035 EA 3A 30 00 F0 jmp far ptr back2realmode
F000:303A F000:303A back2realmode:
F000:303A 66 33 C0 xor eax, eax
F000:303D 66 33 D2 xor edx, edx
F000:3040 66 B9 0E 02 00 00 mov ecx, 20Eh
F000:3046 0F 30 wrmsr
F000:3048 FE C1 inc cl
F000:304A 0F 30 wrmsr
F000:304C 33 C0 xor ax, ax
F000:304E 8E D8 mov ds, ax
F000:3050 assume ds:nothing
F000:3050 8E C0 mov es, ax
F000:3052 assume es:nothing
F000:3052 8E E0 mov fs, ax
F000:3054 assume fs:nothing
F000:3054 8E E8 mov gs, ax
F000:3056 assume gs:nothing
F000:3056 66 81 C4 2E 00 00 00 add esp, 2Eh
F000:305D 67 66 3E 8B 04 24 mov eax, ds:[esp]
F000:3063 8E D0 mov ss, ax
F000:3065 assume ss:nothing
F000:3065 66 C1 E8 10 shr eax, 10h
F000:3069 66 33 E4 xor esp, esp
F000:306C 8B E0 mov sp, ax
F000:306E C3 retn
F000:306E xec_32_flat_pm_HT32GATE endp
.........
These lines in listing 1 probably vague:
F000:2EE4 66 8D 36 6D 2F lea esi, HT32GATE_BIN_phy_addr
F000:2EE9 67 66 2E 8B 06 mov eax, cs:[esi]
F000:2EEE 66 8B F0 mov esi, eax
.........
F000:2EF6 66 81 C6 00 02 00 00 add esi, 200h ; esi = HT32GATE entry point (discard DOS MZ header)
Let me decipher them. The value of HT32GATE_BIN_phy_addr is FFFB_6200h. Looking at the BIOS binary in that address from Hex Workshop, I found:
Offset Hex Values ASCII
000B6200 4D5A 4F01 0300 0000 2000 0100 FFFF 0000 MZO..... ....... 000B6210 0000 9713 0000 0000 1E00 0000 0100 0000 ................
The "MZ" header means the file is an MS-DOS executable. However, looking back to listing 1, we know that routines in HT32GATE.BIN should be executed in 32-bit flat protected mode. Therefore, let's forget about MS-DOS 16-bit real mode code execution. Now, the question is why the code added 200h into HT32GATE_BIN_phy_addr? It's because the call to HT32GATE.BIN must skip over the DOS MZ header. The format of DOS MZ header is explained in one of the link over at the Prerequisite section. I will show you the relevant excerpt below (courtesy of Max Maischein):
The old EXE files are the EXE files executed directly by MS-DOS. They were a major improvement over the old 64K COM files, since EXE files can span multiple segments. An EXE file consists of three different parts, the header, the relocation table and the binary code. The header is expanded by a lot of programs to store their copyright information in the executable, some extensions are documented below. The format of the header is as follows :
OFFSET Count TYPE Description
0000h 2 char ID='MZ' ID='ZM'
0002h 1 word Number of bytes in last 512-byte page
of executable
0004h 1 word Total number of 512-byte pages in executable
(including the last page)
0006h 1 word Number of relocation entries
0008h 1 word Header size in paragraphs
000Ah 1 word Minimum paragraphs of memory allocated in
addition to the code size
000Ch 1 word Maximum number of paragraphs allocated in
addition to the code size
000Eh 1 word Initial SS relative to start of executable
0010h 1 word Initial SP
0012h 1 word Checksum (or 0) of executable
0014h 1 dword CS:IP relative to start of executable
(entry point)
0018h 1 word Offset of relocation table;
40h for new-(NE,LE,LX,W3,PE etc.) executable
001Ah 1 word Overlay number (0h = main program)
Looking at offset 06h of the DOS MZ header, we found the "Header size in paragraphs" which translates to "multiply the value with 16". Why 16? because 1 paragraph is 16 bytes. If you recall from real mode, the physical address of a segment is obtained by shifting the value in one of the segment register to the left 4-bits, which is equivalent to multiplying with 16. Now, you see the relation, don't you ;). Looking back to the HT32GATE.BIN hex dump, we see that the value at offset 6h (that is at B6206h) is 20h. Multiplying 20h with 16, we obtain 200h. Bingo! That's the header size of the DOS MZ file inside the HT32GATE.BIN. Therefore, the entry point to the executable routine in HT32GATE.BIN is located at 200h (512) bytes after the start of the DOS MZ header.
Anyway, HT32GATE.BIN is placed in the BIOS binary in uncompressed form. That's why the bootblock code can call the routine in HT32GATE.BIN directly without decompressing it first. From Award BIOS component header structure, we know that the header of an Award BIOS component contains information about the compression applied to the component. In the case of HT32GATE.BIN, the header "says" this component is not compressed:
Offset Hex Values ASCII
000B61C0 A4FF 2533 2D6C 6830 2D67 0500 0050 0500 ..%3-lh0-g...P..
000B61D0 0000 007C 4020 010C 4854 3332 4741 5445 ...|@ ..HT32GATE
000B61E0 2E42 494E FECD 2017 0000 0000 0000 0000 .BIN.. .........
000B61F0 0000 0000 0000 0000 0000 0000 0000 0000 ................
000B6200 4D5A 4F01 0300 0000 2000 0100 FFFF 0000 MZO..... .......
The hex dump above shows the "-lh0-" string which means the component is not compressed.
Now, let's continue the code execution to HT32GATE.BIN. The entry point is located at FFFB_6400h.
Listing 2: HT32GATE.BIN execution until the call to HT.DLL
0000:FFFB6400 entry_point_func proc near
0000:FFFB6400
0000:FFFB6400 var_38 = byte ptr -38h
0000:FFFB6400 arg_3 = dword ptr 0Bh
0000:FFFB6400 arg_7 = dword ptr 0Fh
0000:FFFB6400 arg_B = dword ptr 13h
0000:FFFB6400 arg_F = dword ptr 17h
0000:FFFB6400 arg_13 = dword ptr 1Bh
0000:FFFB6400 arg_17 = dword ptr 1Fh
0000:FFFB6400 arg_1B = dword ptr 23h
0000:FFFB6400 arg_1F = dword ptr 27h
0000:FFFB6400 arg_23 = dword ptr 2Bh
0000:FFFB6400 arg_27 = dword ptr 2Fh
0000:FFFB6400 arg_2B = dword ptr 33h
0000:FFFB6400 0000:FFFB6400 55 push ebp
0000:FFFB6401 8B EC mov ebp, esp
0000:FFFB6403 83 C4 C8 add esp, 0FFFFFFC8h
0000:FFFB6406 8D 5D C8 lea ebx, [ebp+var_38]
0000:FFFB6409 36 C7 03 00 00 00 00 mov dword ptr ss:[ebx+0], 0
0000:FFFB6410 36 C6 43 04 00 mov byte ptr ss:[ebx+4], 0
0000:FFFB6415 36 C6 43 05 FF mov byte ptr ss:[ebx+5], 0FFh
0000:FFFB641A 36 C6 43 06 10 mov byte ptr ss:[ebx+6], 10h
0000:FFFB641F 33 C0 xor eax, eax
0000:FFFB6421 36 89 43 07 mov ss:[ebx+7], eax
0000:FFFB6425 36 89 43 0B mov ss:[ebx+arg_3], eax
0000:FFFB6429 8D 05 8D 00 00 00 lea eax, large ds:8Dh
0000:FFFB642F 03 C6 add eax, esi
0000:FFFB6431 36 89 43 0F mov ss:[ebx+arg_7], eax
0000:FFFB6435 8D 05 03 01 00 00 lea eax, large ds:103h
0000:FFFB643B 03 C6 add eax, esi
0000:FFFB643D 36 89 43 13 mov ss:[ebx+arg_B], eax
0000:FFFB6441 33 C0 xor eax, eax
0000:FFFB6443 36 89 43 17 mov ss:[ebx+arg_F], eax
0000:FFFB6447 8D 05 D5 00 00 00 lea eax, large ds:0D5h
0000:FFFB644D 03 C6 add eax, esi
0000:FFFB644F 36 89 43 1B mov ss:[ebx+arg_13], eax
0000:FFFB6453 33 C0 xor eax, eax
0000:FFFB6455 36 89 43 1F mov ss:[ebx+arg_17], eax
0000:FFFB6459 36 89 43 23 mov ss:[ebx+arg_1B], eax
0000:FFFB645D 36 89 43 27 mov ss:[ebx+arg_1F], eax
0000:FFFB6461 8D 05 31 01 00 00 lea eax, large ds:131h
0000:FFFB6467 03 C6 add eax, esi
0000:FFFB6469 36 89 43 2B mov ss:[ebx+arg_23], eax
0000:FFFB646D 8D 05 4D 01 00 00 lea eax, large ds:14Dh
0000:FFFB6473 03 C6 add eax, esi
0000:FFFB6475 36 89 43 2F mov ss:[ebx+arg_27], eax
0000:FFFB6479 33 C0 xor eax, eax
0000:FFFB647B 36 89 43 33 mov ss:[ebx+arg_2B], eax
0000:FFFB647F C1 CB 10 ror ebx, 10h
0000:FFFB6482 66 53 push bx
0000:FFFB6484 C1 C3 10 rol ebx, 10h
0000:FFFB6487 66 53 push bx
0000:FFFB6489 FF D2 call edx ; Call HT.DLL
0000:FFFB648B C9 leave
0000:FFFB648C C3 retn
0000:FFFB648C entry_point_func endp
.............
Listing 2 shows a call to an address in EDX register (in the end of the entry point function execution). What is the value of EDX register at that point? We should revert to listing 1 to find the value because listing 2 shows that EDX register is unchanged in the entry point function. The last EDX value in listing 1 prior to calling HT32GATE.BIN is HT_DLL_entry_phy_addr as shown below:
F000:2ECF 66 8D 1E 45 2F lea ebx, HT_DLL_entry_phy_addr
F000:2ED4 67 66 2E 8B 13 mov edx, cs:[ebx]
F000:2ED9 66 8B F2 mov esi, edx
.........
F000:2F45 EC 5B FB FF HT_DLL_entry_phy_addr dd 0FFFB5BECh ; phy addr of HT.DLL entry point ("dllmain" function) address
; in BIOS ROM
Now, we know the flow of the code in HT32GATE.BIN. The last routine it called is a routine in HT.DLL.
Now, let's move to our final destination, the DLL file, namely HT.DLL. HT.DLL is also an uncompressed part of the Award BIOS binary that I dissect. The hex dump below shows the header of the HT.DLL component.
Offset Hex Values ASCII
000B1180 1F60 2D6C 6830 2D20 5000 0011 5000 0000 .`-lh0- P...P...
000B1190 007A 4020 0106 4854 2E44 4C4C 0675 200F .z@ ..HT.DLL.u .
000B11A0 0000 0000 0000 0000 0000 0000 0000 0000 ................
As you can see in the preceding hex dump, HT.DLL is "-lh0-"-compressed, which means it's not compressed at all.
Now, let's move to the DLL file itself. As you can see in listing 1, HT32GATE.BIN calls an address which is far away from the start of the HT.DLL file in the BIOS (HT.DLL starts at FFFB_1180h in the BIOS ROM). That particular address (FFFB_5BECh) is the entry point of HT.DLL, the "dllmain" function as shown in listing 3.
Listing 3: HT.DLL execution
.............
0000:FFFB5BEC ; BOOL __stdcall agesa_entry(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpReserved) 0000:FFFB5BEC public agesa_entry
0000:FFFB5BEC agesa_entry proc near
0000:FFFB5BEC
0000:FFFB5BEC global_func_table= cpu_fam_0Fh_func_table_t ptr -0B30h
0000:FFFB5BEC l_hinstDLL = dword ptr -0AB0h
0000:FFFB5BEC _x_way_capable = byte ptr -0AAAh
0000:FFFB5BEC var_9 = byte ptr -9
0000:FFFB5BEC var_8 = byte ptr -8
0000:FFFB5BEC p_global_func_table= dword ptr -7
0000:FFFB5BEC hinstDLL = dword ptr 8
0000:FFFB5BEC fdwReason = dword ptr 0Ch
0000:FFFB5BEC lpReserved = dword ptr 10h
0000:FFFB5BEC
0000:FFFB5BEC 55 push ebp ; p_l_hinstDLL + AA9h = p_global_func_table 0000:FFFB5BED 8B EC mov ebp, esp
0000:FFFB5BEF 81 EC 30 0B 00 00 sub esp, 0B30h
0000:FFFB5BF5 E8 FA DA FF FF call read_APIC_base?
0000:FFFB5BFA 0F B6 C0 movzx eax, al
0000:FFFB5BFD 85 C0 test eax, eax
0000:FFFB5BFF 0F 84 84 00 00 00 jz exit
0000:FFFB5C05 8D 85 D0 F4 FF FF lea eax, [ebp+global_func_table]
0000:FFFB5C0B 50 push eax
0000:FFFB5C0C 6A 00 push 0 ; cpu node number ?
0000:FFFB5C0E E8 C7 D7 FF FF call init_ht_func_table
0000:FFFB5C13 59 pop ecx
0000:FFFB5C14 59 pop ecx
0000:FFFB5C15 8B 45 08 mov eax, [ebp+hinstDLL]
0000:FFFB5C18 89 85 50 F5 FF FF mov [ebp+l_hinstDLL], eax
0000:FFFB5C1E 8D 85 D0 F4 FF FF lea eax, [ebp+global_func_table]
0000:FFFB5C24 89 45 F9 mov [ebp+p_global_func_table], eax
.............
0000:FFFB5C89 exit:
0000:FFFB5C89 C9 leave
0000:FFFB5C8A C3 retn
0000:FFFB5C8A agesa_entry endp .............
As you can see in listing 3, an ordinary DLL expects three input parameters. However, the current execution environment is not Windows or Wine. Therefore, the "ordinary" DLL calling convention doesn't hold in this case. Looking back to listing 2, we see that HT.DLL was called with single 32-bit parameter which is pushed to the stack right before the DLL is executed. The disassembly of HT.DLL above is obtained by opening the DLL directly in IDA Pro without altering the DLL file at all. That's why IDA Pro "guess" that the DLL is an ordinary DLL which runs in Windows which results in the disassembly above.
5. Conclusion
The reverse engineering work on DLL support in recent Award BIOS shows that Award BIOS doesn't really support DLL file format as in Windows because it doesn't parse the DLL header at all. It just jumps directly to the DLL entry point function (in 32-bit flat protected mode) and execute the DLL. My analysis of the DLL with IDA Pro also shows that there is only one exported function in the DLL, i.e. the DLL entry point and there is no imported function. It doesn't matter though because Award BIOS doesn't even care about the DLL header ;).
6. Closing
What I've explained above possibly far too premature to be ended here. However, if you follow this article from beginning to end, you'll be able to understand the "BIG Picture" of how DLL file is handled in recent Award BIOS. If you find any mistake(s) within this article or have any suggestion, please contact me.
copyright © Darmawan M S a.k.a Pinczakko