Showing posts with label BIOS. Show all posts
Showing posts with label BIOS. Show all posts

2015-01-08

Easily create UEFI applications using Visual Studio 2015

As pointed out before, Visual Studio is now essentially free for all development, and its solid IDE of course makes is very desirable as the environment to use to develop UEFI applications on Windows.

Now, you might have read that, short of using the oh-so-daunting EDK2, and the intricate voodoo magic you'll have to spend days on, to make it play nice with the Visual Studio IDE, there is no salvation in the UEFI world. However, this couldn't be further from the truth.

Enters UEFI:SIMPLE.

The thing is, Visual Studio can already compile EFI applications without having to rely on any external tools, and even if you want an EDK2 like environment, with the common EFI API calls that it provides, you can totally do away with the super heavy installation and setup of the EDK, and instead use the lightweight and straightforward GNU-EFI library, that provides about the same level of functionality (as far as building standalone EFI applications or drivers are concerned, which is what we are interested in).

So really, if you want to craft an EFI application in no time at all, all you need to do is:

  1. Install Visual Studio 2015, which is totally free and which, no matter who you work for or what restrictions your corporate IT department tries to impose, you are 100% legally entitled to when it comes to trying to compile and test UEFI:SIMPLE.
  2. As suggested by the Visual Studio installer, install a git client such as msys-git (or TortoiseGit + msys-git). Now, you're going to wonder why, with git support being an integral part of Visual Studio 2015, we actually need an external client, but one problem is that Microsoft decided to strip their embedded git client of critical functionality, such as git submodule support, which we'll need.
  3. Because you'd be a fool not to want to test your EFI application or driver in a virtual environment, and, thanks to QEMU, this is so exceedingly simple to achieve that UEFI:SIMPLE will do it for you, you should download and install QEMU, preferably the 64 bit version (you can find a 64 bit qemu installer here), and preferably to its default of C:\Program Files\qemu.
  4. Clone the UEFI:SIMPLE git project, using the URI https://github.com/pbatard/uefi-simple.git. For this part, you can either use the embedded git client from Visual Studio or your external client.
  5. Now, using your external git client, navigate to your uefi-simple directory and issue the following commands:
    git submodule init
    git submodule update
    This will fetch the gnu-efi library source, which we rely on to build our application.
  6. Open the solution file in Visual Studio and just click the "Local Windows Debugger" button to both compile and run our "Hello, World"-type application in QEMU.
    Through its debug.vbs script, which can be found under the "Resource File" category, the UEFI:SIMPLE solution will take of setting everything up for you, including downloading the OVMF UEFI firmware for QEMU.
    Note that if you didn't install QEMU into C:\Program Files\qemu\ you will need to edit debug.vbs to modify the path.
  7. Finally, because the UEFI:SIMPLE source is public domain, you can now use it as a starting point to build your own UEFI application, whilst relying on the standard EFI API calls that one expects, and, more importantly, with an easy way to test your module at your fingertips.
Oh and I should point out that UEFI:SIMPLE also has ARM support, and can also be compiled on Linux, or using MinGW if you don't want to use Visual Studio on Windows. Also, if you want real-life examples of fully fledged UEFI applications, that were built using UEFI:SIMPLE as their starting point, you should look no further than efifs, a project that builds whole slew of EFI file system drivers, or UEFI:NTFS, which allows seamless EFI boot of NTFS partition.

2012-04-02

Crafting an MBR from scratch

If you follow this site, you'll remember that we previoulsy crafted a BIOS from scratch. Of course we may as well follow that up with writing an MBR while we're at it!

This time, the problem that was put to us was as follows:

As part of XP/2003 installation support in Rufus, which, if needed to be reminded, is your friendly bootable USB stick creation tool, we thought it'd be nice if, rather than having to fiddle with boot.ini options like other XP ISO → USB apps do for the second part of the XP setup process, we did something similar to what the original optical installation medium provides, with a "Press any key to boot from CD/DVD..." prompt.

More technically, the issue is that XP was not exactly designed by Microsoft to be installable from and USB drive. Therefore Windows expects to see the BIOS disk ID of the HDD it boots from, during the later stage of the installation process, as the first bootable device = 0x80, whereas 0x80 is the disk ID the BIOS assigns to the USB whenever it boots from it.
Thus, scripted methods of installing XP from USB would install an ntldr + boot.ini on the USB and prompt the user to select the second drive (first bootable HDD) to continue the process. Else the other workaround is to unplug the USB drive during reboot after the first part of the installation process is complete, and plug it back later, as Windows still needs to read files from it. Since these methods deviate from what users would see from a regular installation from CD/DVD, we tried to see if we couldn't come up with something better.

Our solution, then, is to craft an MBR that does the following:
  1. If a bootable HDD is detected as second BIOS bootable device (0x81), the MBR prompts the user whether they want to boot from USB and, if no input is given, will fall back to booting from the HDD (0x81) instead of USB (0x80)
  2. According to the bootable disk ID provided in the USB partition table, the MBR will swap the 0x80 device with the ID provided.
    This means that for instance, if the first partition on the USB drive has disk ID 0x81, then the USB disk is remapped to this ID, whereas the original 0x81, which would typically be the first bootable HDD, is remapped as 0x80 (first bootable device). Because this approach falls between swap and remapping, we call it masquerading, as each bootable drive is now being masqueraded as a different one.
Once the above is properly set, then the setup process on the HD can be led to think it always boots from 0x80, even if it was the USB that actually booted the system, and operate as if that was the case, leaving the installation process as close as possible to what users would experience when installing from CD/DVD.

For a more technical breakdown, of our process is as follows, knowing that the BIOS would have copied over the 512 bytes MBR at address 0x00007C00 when we start to run it.
  1. Because later stages need to copy boot records at address 0x00007C00, the first thing we do is move our 512 bytes code out of the way, by allocating 1 KB of RAM, duplicating our code there and then jumping to it.
  2. With our code now safely out of the way, we attempt to read the MBR of the second bootable device (0x81, as 0x80 would be the USB) into the 0x00007C00 address we just moved out from using INT_13h (disk), with either function 02h or 42h depending on the extensions found (Some BIOSes, such as older DELL, can only use 42h, so we must handle both 02h and 42h). If the read is unsuccessful, we just boot the USB.
  3. If the read is successful, we check the partition entries of the HDD MBR we just read, to see if there exists one that is bootable/active. If none is found, we give up and boot the USB.
  4. If an active partition is found,  we prompt the user to hit a key if they want to boot from USB by installing an override for INT_08h (timer) to provide us with both a timeout and the ability to print a dot every second.
  5. If the user presses a key, we install an override for INT_13h that does the following:
    - masquerades boot device 0x81 (first bootable HDD, second bootable device after the USB) as 0x80 (first bootable device)
    - masquerades the USB boot device (0x80) with the disk ID provided for its first partition (typically 0x81, but could be higher)
  6. We then handle the rest of the boot process to the relevant boot record (after removing the INT_08h override if needed). If the HDD is booted, we simply jump to the MBR we read at address 0x00007C00. If booting from USB, we read the partition boot record (eg. NTFS boot record) into address 0x00007C00 and jump there.
All of the above (and more!) is done in 440 bytes and with 2 bytes to spare (or 9 if you count the ones spent on convenience instructions). Not bad...

On a side note, the process of overriding INT_13h is used by various MBR viruses (eg. Michelangelo), and we actually had to take some measures to prevent anti virus applications from detecting our MBR as one. Our override of INT_08h for "Please press any key to boot from USB..." is also very close to what Microsoft does in the bootfix.bin it provides on its XP/2003 installation media.

The x86 assembly source of the MBR can be accessed here. If you're interested in writing your own MBR, feel free to have a look at it. Or, if you just want to see the MBR in action, feel free to download the latest version of Rufus, and use it to install Windows XP or Windows 2003 from USB.

2011-08-09

Enabling option ROM and flashing gPXE on an SMC 1211

If you're going to play with PCI option ROMs, you're likely to salvage a PCI Network Interface Card (NIC) with a flash ROM, or at least one that has a socket for it. And given their popularity in the late 90s/early 2ks, you have a fair chance getting your hands on with a RealTek RT8139 based one. One such NIC is is the SMC 1211 (or "Accton Technology Corporation SMC2-1211TX" as reported by lscpi -nn on Linux), which, in its basic configuration, comes with a DIP socket for 5V flash chips up to 128 KB in size. This is actually quite a desirable card to have as it is beautifully supported by flashrom, which has great support for the RT8139 chip, and no extra tweaking is needed to support the maximum flash size of 128 KB, as can be the case with other NICs. Add a W29C011A-15 or compatible, which can be easily obtained or salvaged from an old motherboard and you'll have more than enough space to play with an option ROM.

Only problem of course is that most SMC 1211s don't come with a flash chip by default so the option ROM is disabled and needs to be re-enabled. Luckily, Realtek does provide a tool called rset8139.exe to do just that. Of course, rather than blindly trusting the tool before we go and play with a custom option ROM, we may as well attempt to check that everything is in order so we are first going to flash a proper one, such as gPXE before running the rset8139 tool. Off we go then to etherboot/gPXE's awesome rom-o-matic, fill our options, including the 1113:1211 VID:PID of our SMC card and get our gPXE ROM back then.

First order of the day, since we're using a 128 KB flash chip and the gPXE ROM we got was smaller, is pad our ROM to our target size with:

cat gpxe-1.0.1-11131211.rom /dev/zero | dd bs=1k count=128 > gpxe_128k.rom

Then we flash with:

flashrom -p nicrealtek -w gpxe_128k.rom

So that takes care of having a proper option ROM. Of course, since we haven't enabled it on the card, you will find that no matter the options you select in your BIOS, the SMC option ROM is not executed and this is precisely why we need to run that rset8139 utility. Now, it is possible that Linux or Windows version of this utility exist, but it looks like the most common version is the DOS one, which, thanks to the oh-so-convenient Rufus (see this post), running from a DOS bootable USB stick is no problem at all.

One important thing to note is there exists multiple versions of the utility, ranging from 5.00 to 5.09, and that not all of these appear to detect the SMC 1211 card. Some regression has been introduced by Realtek, which leaves the most recent versions of rset8139 unable to change settings for the 1211. Therefore, the version I recommend using if you have an SMC card is v5.01, which can be picked here. If you don't have an SMC card, then you can try your luck with v5.0.9, which appears to be the most recent, and which is available here.

Once you have created your DOS bootable USB stick and copied rset8193.exe over, you should end up with something similar to the screenshot below (courtesy of desconexão.net), where you will be able to enable and set your ROM size:


After these settings are saved and you reboot, you should find that the gPXE option ROM payload is now executed by your BIOS, and with that you can get cracking on building a custom option ROM using your RT8139 based card.

2011-08-03

UBRX - L2 cache as instruction RAM (CAiR)

Word of advice: if you want to play with CPU caches, and especially an L2 cache, don't use a Slot 1 Pentium III as your test system: L2 is disabled by default there and, because it's really some fast RAM that was added on the board that also has the CPU chip, initializing it take a little more effort than simply flipping a switch... If you want to know all about how to initialize L2 cache on Slot 1 Pentiums, you may want to have a look at the old freebios code.

But now, with L2 sorted out and as a famous robot once said: "Things are starting to look up..."

Why is L2 cache so important for UBRX you ask? Good question.
You see, our goal is to run binary code that was uploaded by the user, on a system that is considered RAM-less, therefore all we have at our disposal are the caches. Now, CAR (Cache As RAM) has been in use by coreboot for some time, but the problem with this implementation is that it only uses L1 cache. However, if you know your CPU architecture 101, you are aware that there are two L1 caches ondie: one for data and another for instructions, with the instruction one being read-only. Thus, the CAR setup method from coreboot only provides access to the L1 data cache, not the instruction one, so we can't simply upload our code into L1-Data and expect it to run.

On the other hand, the L2 cache is a unified one, which means that it works for both data and instruction. Thus, if we manage to get our code onto L2, and have all the caches in WriteBack mode, we should be able to get the CPU to fetch instructions, which we uploaded, from L2, and we're good. This is called Cache As instruction RAM (CAiR). And with L2 caches being more than 256 KB in size, we could actually run a hefty and quite complex section of code, rather than be limited to the 16 or 32 KB of L1.

To achieve that, simply upload the code you want into L1-Data (which would have been initialized as CAR), then read or write a contiguous section of data, from a different address, that is larger than your L1 cache. As L1-Data gets replaced, your code gets pushed onto L2, where it is not accessible for execution by the CPU.
Neat!

So, how does it work in the UBRX console? Like this:
s/u/r/q> s
$60000010 a             # disable cache
$11e c $0 d $01043531 m # setup L2 for PIII Slot 1
$2ff c $0 d $c00 m      # fixed + var MTRRs
$268 c $06060606 d m    # C0000-C7FFF as WriteBack
! $10 a                 # flush and enable cache
$8000 c $c0000 <        # preload region to L2-Unified
# load our code in L1-Data
$c0000 d $f8ba68b0 z    # 'h'
$c0004 d $65b0ee03 z    # 'e'
$c0008 d $ee03f8ba z
$c000c d $f8ba6cb0 z    # 'l'
$c0010 d $6cb0ee03 z    # 'l'
$c0014 d $ee03f8ba z
$c0018 d $f8ba6fb0 z    # 'o'
$c001c d $0db0ee03 z    # CR
$c0020 d $ee03f8ba z
$c0024 d $f8ba0ab0 z    # LF
$c0028 d $ffcbee03 z
$8000 c $c0030 >        # flush L1-Data onto L2-Unified

$c0800 b $c0000         # stack at C8000, code at C0000
.
s/u/r/q> r
hello
s/u/r/q>
Now, by flashing less than 4KB of your BIOS bootblock, you are able to run ANY code you want using the UBRX the recovery console, even if you don't have any RAM installed, with the added benefit that your code can be as large as your L2 cache. Neat!

All of this and more in UBRX v0.4.

2011-07-15

Creating a bootable UEFI DUET USB stick

Now that one of my patches has made it into the UEFI/EDK2 SVN repository, I'm going to provide a quite guide on the easiest way to create a bootable UEFI USB stick for legacy platforms, on Windows.
In case you are not familiar with EFI/UEFI, it is very much possible to run EFI even on legacy (i.e. non EFI) hardware, through a process called DUET, which provides a complete EFI emulation layer using the underlying BIOS. This is great for testing, without having to modify your hardware in any way.

It is all provided by the DuetPkg of the EDK2. However, the main issue with EDK2 is that it may be a difficult to get working, unless you used the same development tools as the EDK2 developers, which, on Windows would default to Visual Studio 2008, which is not free.

Isn't there simple way to use freely available tools on Windows, to easily compile a DUET USB bootstick?
Glad you asked. There is now, and here is the complete process that goes with it:
  • Download and install the Windows Server 2003 WinDDK, v3790.1830, using this direct link (230 MB ISO download). This is of course not the latest DDK but unless you want to waste time in extra configuration, you might as well go with the supported version. If I find the time to fix EDK2 for the latest, I may send another patch to the EDK2 team, but for now, this will have to do.
  • (Optional) If you plan to use ASL to compile ACPI, which isn't needed for DUET, create a C:\ASL directory, download the Microsoft ASL Assembler/Compiler archive from here, open the .exe with 7-zip. Then open the .msi from the .exe (Open Inside), find the file _C6C6461BF87C49D66033009609D53FA5, extract it to your C:\ASL directory and rename it asl.exe (count on Microsoft to provide an installer... for a mere 55 KB exe). Also note that the ASL compiler is for 32 bit only. If you're running 64 bit, tough luck.
  • Mount or burn the ISO and install the WinDDK, to the default C:\WINDDK\3790.1830 directory.
  • If needed, download and install TortoiseSVN, or any other SVN client.
  • Fetch the latest EDK2 from SVN, using https://edk2.svn.sourceforge.net/svnroot/edk2/trunk/edk2 as the SVN repository.
  • Open a DDK console ("Development Kits" → "Win2k3 Free x64 Build Environment" or "Win2k3 Free x86 Build Environment") and navigate to your edk2 direcvtory
  • Run the command:
    edksetup.bat
    This will initialize your environment by creating the required files in the Conf\ directory. You can safely ignore the warning about cygwin.
  • Open the file Conf\target.txt and edit the TOOL_CHAIN_TAG line to have:
    TOOL_CHAIN_TAG = DDK3790xASL
    You also may want to change MAX_CONCURRENT_THREAD_NUMBER and make sure TARGET_ARCH is properly set to either IA32 or X64 according to your needs, as it avoids having to provide extra arch parameter when invoking the commands below).
  • In the console, run (32 bit):
    build -p DuetPkg\DuetPkgIA32.dsc
    or (64 bit)
    build -p DuetPkg\DuetPkgX64.dsc
    It may take a while but it should complete successfully.
  • cd to DuetPkg\ and run:
    PostBuild.bat
  • Plug in your USB key and check its drive letter. In the following I will assume it is F:, and run:
    CreateBootDisk.bat usb F: FAT16
  • Unplug and replug the key as requested, and run:
    CreateBootDisk.bat usb F: FAT16 step2
You should now have a bootable UEFI USB key. Plug that into a PC, boot it, and you will be welcomed into the wonderful world of EFI.
Note that if you rebuild any of the packages, you only have to perform the last step to update the key.

2011-07-11

Introducing UBRX - an Universal BIOS Recovery Console for x86 PCs

Following up on the previous BIOS generation endeavour, as well as wanting to demonstrate to any naysayer that a universal recovery console (or panic room) in the BIOS bootblock of an x86 system is not something from the realm of fantasy, it is my pleasure to introduce UBRX, the Universal Bootblock Recovery console for X86 systems.

In its current version (0.1), it is mostly a Proof of Concept aimed at the coreboot team, since the crafting of a 'panic room' type bootblock has been on their mind for some time, and my understanding is that their approach would be to make such a bootblock platform specific (the motherboard would first need to be supported by coreboot), whereas I believe that there is a way to be a lot more generic to produce a 'panic room' feature that works on all platforms, even the ones that coreboot doesn't support yet. The current bootblock is less than 2 KB, which ought to leave plenty of room for a Y-modem, CAR, and a full console implementation.

If your aim is to develop a real BIOS from scratch, and you have a PC that is more recent than the year 2000 (most PCs from the late nineties should work too) UBRX might also be of interest to you, since it should provide you with a serial console. In short, UBRX can also be used as a base for BIOS development, regardless of the machine you have.

What UBRX implements then is a safe method for the detection of a PnP Super I/O chip, as well as the detection and initialisation of a 16550 compatible UART there, to then provide on-demand access to a serial console. The emphasis here has to be with the safety of the detection being performed, as it is intended to be executed at every boot, without resulting in advert consequences for non PnP Super I/O hardware residing in the same hardware space as the one we check, or any non UART function residing in the Super I/O. To find more about how we achieve detection safety, I suggest you check the "Detection Primer" section of the UBRX README.

Now, of course, as with "unlimited" broadband, the "universal" applicability of UBRX comes with some understanding that fairness needs to be applied to the claim. As such, non PnP Super I/Os, PnP Super I/Os that require uncommon initialization (I'm looking at you ITE), and platforms using CPUs other than Intel or AMD are not currently supported. Also, due to the lack of datasheets from nVidia, it is very likely that UBRX may not work with motherboards sporting an nVidia chipset. However, if you have an Intel motherboard with an ICH# chipset, or an AMD motherboard with an SB##0 SouthBridge (which probably covers more than 90% of PCs from the last few years), UBRX is expected to work.

Finally, as UBRX is only a Proof of Concept for now, the console is limited to a serial repeater, so there's not much you can do with it. Especially it it missing CAR (Cache As RAM) initialization and Y-modem functionality, to be able to transfer and run bare metal executables to do interesting things, such as flashing the remainder of the BIOS, initializing the full range of RAM according to the hardware, or loading a debugger. Therefore, if you choose to test and flash UBRX, remember that you must have means to reflash your BIOS using an external program, as your PC will become unusable.

Please head to the github project for more info. A source tar archive of the source can also be downloaded here.

2011-06-08

Crafting a BIOS from scratch

Introduction

Ideally, there would have been an entry between this one and the last, where I'd give you some pointers on how to disassemble the VMware BIOS we extracted using IDA Pro. However, one of the points of this series is to explore ways of improving/revisiting the too often ignored x86 bootloader recovery process (a.k.a. 'panic room'), that really ought to be part of any system boot operations. As such, we might as well jump straight into the fun by creating a VMware BIOS from scratch.

Your first question might be "Why would anyone want to craft their own BIOS from scratch (apart for an academical exercise)?". Well, in an ideal world, chipmakers such as intel and AMD would follow the example of the few SoCs manufacturers who got it right and provide both an UART and a small serial boot recovery ROM, ondie, to relegate the prospect of a non-functional system because of bad/uninitialized flash, to the footnotes of history. Alas, with CPUs well into their 4th decade of existence, that still hasn't happened. Therefore, to compensate for this missing feature, we'll have to implement such a feature ourselves, in the flash ROM, and that means writing a BIOS bootblock from scratch. And if you know how to write a BIOS bootblock, then you know how to write a complete BIOS. As to why one would actually want to replace a fully functional BIOS on actual hardware, just wait till you purchase a 3TB (or larger) HDD, or create a >2TB RAID array, on an not so old machine, with the intent of booting Windows from it...

Of course, crafting a fully featured BIOS, that can actually boot an OS, is something better left to large projects such as coreboot with a payload of either SeaBIOS or Tianocore (UEFI), so we're not going to do that here. Instead, our aim will be to produce a very simple BIOS, that just does serial I/O, and that can be used as a development base for more interesting endeavours, such as 'panic room' type flash recovery, or regular BIOS instrumentation, to help with the development of an UEFI 'BIOS' for legacy platforms. Trying things out with a virtual machine, before jumping onto actual hardware, seems like the smart thing to do.


Know thy enemy, a.k.a. "Which SuperIO?"

Hardware wise, the only subsystem we want to access then is the SuperIO chip, since it provides the (virtual) UART we are after. We're not going to bother about PCI, RAM, Video, or even Cache as RAM (CAR) access: just plain good old serial debug will do. And while I'm not going to go as far as saying that implementing the items listed above would be trivial, the fact is, as long as you have serial debug, at least you don't have to shoot in the dark, and that's big help.

In the case of VMware, all you need to know is that the SuperIO is a (virtual) National Semiconductor PC97338 (datasheet here). Unfortunately neither coreboot's superiotool or lm-sensors's sensors-detect seem to detect it at the moment, which is quite unfortunate (Didn't someone mention they were porting coreboot/LinuxBIOS to VMware some time ago? What happened?), as a lot of time was wasted on the SuperIO errand.

And maybe I missed a step somewhere, but from what I can see, the VMware virtual chip is not set to run in PnP mode. Thus, what I'm going to expose in the code below with regards to accessing the serial port is very specific to the VMware PC97338 non-PnP implementation, and may not translate so well to SuperIO chips that run in PnP mode. Oh well... Also, if you disassemble the VMware BIOS, you'll see some mucking around with a SuperIO chip located at port 0x398 early in the bootblock, with 0x398 being one of the possible bases for the PC97338... Except the VMware SuperIO base is indeed at the 0x2e location, so all that early stuff is a wild goose chase. Thanks a lot guys!

Therefore, just to reiterate, all you need to know is that the VMware SuperIO is a PC97338, at port 0x2e and running in non PnP mode. With that you can run along, and get going implementing early serial in your own BIOS.


Toolchain considerations and software constraints

With the hardware in check, and before we start writing anything, it might help to have a look at our other requirements.

First of all, as far as the development toolchain is concerned, and even more so as what follows is aimed at being usable by the largest number of people, we will use a GNU toolchain all the way. That means, as soon as you have a gcc setup on your platform that can produce x86 code, you should be good to go. And for the record, I have verified that the files I'm presenting below can produce a BIOS ROM on Windows, with either MinGW32, MinGW-w64 or cygwin, as well as Linux x86 or x64, with regular gcc. OSX (with a proper gcc toolchain) as well as cross compilers on other UNIX architectures are expected to work too. So if you don't have gcc setup on your system, go get it now!

Then comes our choice of language. The coreboot and other projects seem to be quite adamant about developing as little as possible in assembly, but I don't see it that way for the two following reasons:
  1. We have no stack after reset but unless we plan on doing non 'panic room' type things, we actually don't have much use for one in the first place. From experience (with Realtek SoCs) I can tell you that if your 'panic room' needs any form of memory to be initialized to be able to run, and that applies to Cache as RAM, you're not doing it right.
  2. RAM space is infinite. BIOS bootcode blocks aren't. If there's one space you want to optimize it's that 4K or 8K BIOS recovery bootblock that you'll keep and never re-flash at the end of your BIOS. Flash manufacturers are providing features to help with flash recovery - make use of them dammit!!
Therefore, assembly it is.

Now, the one caveat is that the GNU assembler seems to be the only tool still around defaulting to the AT&T syntax which, while arguably more sensible than the intel one, nobody else, and especially not IDA, uses. Instead the intel syntax prevails. While this could have been an annoyance, any recent versions of GNU as also supports the Intel syntax, which can be be switched on in your code with .intel_syntax noprefix. Now that's better!

Finally, we know we'll have to follow the following constraints:
  1. First instruction must be located at address 4GB-0x10 (or FFFF:FFF0 if you prefer), and the whole BIOS must reside at the very end of the 32 bit address space. This is an x86 CPU initialization requirement
  2. The processor starts in real address mode on reset. Another x86 reset constraint. Now, some people choose to switch to protected mode as soon as they can (so that they can use C), but we have optimization in mind, so we'll keep real address mode all the way.
  3. The BIOS ROM size must be 512 KB. This time it's a VMware requirement.
  4. We are also supposed to be careful about far jumps in our code, as another x86 boottime constraint. But that won't be an issue for a bootblock section of a few KB, which we plan to locate at the end of the BIOS anyway.


Producing a BIOS ROM

Now we jump into the gory details at last.

Since the reset vector is located at the end of the BIOS, we need to have at least two sections in our sourcecode: one that contains the bulk of our code, which I'll call main and which I'll arbitrarily set to start at 4 KB before the end of the ROM, and another, starting at FFFF:FFF0 and going to the end of the ROM, which I'll call reset and whose only purpose will be to jump into our entrypoint in the main section.

Below is an example of how one can establish these two sections in the assembly source, as well as the associated GNU ld script that ensures they will be located at the right destination address in the ROM. Because our BIOS is short, I'll use a single bios.S source for the code, and I'll call the ld script bios.ld. Hence bios.S:
.section main
init:   <insert useful code here>
        ...

.section reset
        jmp init
        .align 16
NB: the .align 16 at the end is there to ensure that the reset section is exactly 16 bytes. This way, we're sure that our reset section will occupy [FFFF:FFF0 - FFFF:FFFF] and we won't have to do extra padding.

bios.ld:
MEMORY { ROM (rx) : org = 4096M - 512K, len = 512K }
SECTIONS { 
        .main 4096M - 4K    : { *(main) }
        .reset 4096M - 0x10 : { *(reset) }
        }
As you can see above, the ld script simply sets the ROM to be 512 KB in size, located at the end of the 4 GB (=4096M, since GNU ld doesn't know the G suffix yet) and, as indicated, we placed our bootcode segment (main) to start at the last 4 KB block of ROM.
The script should sort out our addresses as we want them then, and once ld has churned through it and produced a new object file (which I'll call bios.out), we should be able to use objcopy with option -j to extract the various binary payloads of interest to us.

Now, the problem is that objcopy -j will only extract the payload data. We could of course use a trick like.align 4K-0x10 at the end of our main section, but that would mean we'd then have to edit our bootcode size in two separate files when we update it. The smarter approach is to use the --gap-fill option of objcopy, to conveniently fill any gap between sections main and reset.

Another problem we face is that the above script only produces the binary data starting with the 4K at the end of the ROM, since the first section we extract (main) starts there. So at most objcopy will create 4 KB of data, far from the 512 KB we actually need. The solution: create a dummy section in our source, which I'll call begin and which I'll also use to put a BIOS ID string, and tell ld either explicitly, or better simply with a >ROM directive (so that we don't have to fill in the ROM size a 3rd time in the script) where it should reside.

After that, if we extract the begin, main and reset sections in order, with the --gap-fill option, we should have a 512 KB binary file with everything mapped where it should be. Neat!


Caveats

Before I present the actual code, a quick summary caveats & gotchas which might be of interest to you if you use this code as a base, and clarifying why everything in our sources isn't exactly as simple as what's exposed above:
  • Gotcha #1: Linux will bother you with a missing .igot.plt section. This looks like a known bug. As a workaround, we added a dummy section for it.
  • Gotcha #2: This is a minor annoyance, but GNU ld doesn't handle constants in the MEMORY section (it's a bug). So the ROM size has to be specified twice in the ld script, and we couldn't use two nice constants at the top, as anybody would think of doing.
  • Gotcha #3: objcopy can only extract sections that have the ALLOC attribute. This attribute is properly set on Windows as soon as you define a section, but not on Linux, where you have to add the flag explicitly (eg: .section main, "ax" for 'ALLOC' and 'CODE'). Note that you always can check how the attributes of your sections are set with objdump -x bios.out
  • Gotcha #4: Using a jmp init in the reset section may result in a target address that is offset by 2 on some platforms (this seems to be a binutils bug). Thus we have to handcraft it.
  • Gotcha #5 (this is getting better and better): On Windows, when using MinGW32 or cygwin (but not MinGW-w64), if you don't define an entrypoint in the linker script, your antivirus may erroneously identify bios.out as containing a Trojan and delete it. "Holy mother of false positives, Batman!" So we need to add an ENTRY(init) statement at the top of our section list.
  • Gotcha #6: DON'T waste your time trying to use XCode on OSX. It is riddled with problems. Use a proper GNU suite instead.

Sourcecode

bios.S:
/********************************************************************************/
/*                         VMware BIOS ROM example                              */
/*       Copyright (c) 2011 Pete Batard ([email protected]) -  Public Domain         */
/********************************************************************************/


/********************************************************************************/
/* GNU Assembler Settings:                                                      */
/********************************************************************************/
.intel_syntax noprefix  /* Use Intel assembler syntax (same as IDA Pro)         */
.code16                 /* After reset, the x86 CPU is in real / 16 bit mode    */
/********************************************************************************/


/********************************************************************************/
/* Macros:                                                                      */
/********************************************************************************/
/* This macro allows stackless subroutine calls                                 */
.macro  ROM_CALL addr
        mov  sp, offset 1f      /* Use a local label as we don't know the size  */
        jmp  \addr              /* of the jmp instruction (can be 2 or 3 bytes) */
1:      /* see http://sourceware.org/binutils/docs-2.21/as/Symbol-Names.html    */
.endm


/********************************************************************************/
/* Constants:                                                                   */
/********************************************************************************/
/* The VMware platform uses an emulated NS PC97338 as SuperIO                   */
SUPERIO_BASE  = 0x2e    /* Do NOT believe what you see in the BIOS bootblock:   */
                        /* the VMware SuperIO base is 0x2e and not 0x398.       */
PC97338_FER   = 0x00    /* PC97338 Function Enable Register                     */
PC97338_FAR   = 0x01    /* PC97338 Function Address Register                    */
PC97338_PTR   = 0x02    /* PC97338 Power and Test Register                      */

/* 16650 UART setup */
COM_BASE      = 0x3f8   /* Our default COM1 base, after SuperIO init            */
COM_RB        = 0x00    /* Receive Buffer (R)                                   */
COM_TB        = 0x00    /* Transmit Buffer (W)                                  */
COM_BRD_LO    = 0x00    /* Baud Rate Divisor LSB (when bit 7 of LCR is set)     */
COM_BRD_HI    = 0x01    /* Daud Rate Divisor MSB (when bit 7 of LCR is set)     */
COM_IER       = 0x01    /* Interrupt Enable Register                            */
COM_FCR       = 0x02    /* 16650 FIFO Control Register (W)                      */
COM_LCR       = 0x03    /* Line Control Register                                */
COM_MCR       = 0x04    /* Modem Control Registrer                              */
COM_LSR       = 0x05    /* Line Status Register                                 */
/********************************************************************************/


/********************************************************************************/
/* begin : Dummy section marking the very start of the BIOS.                    */
/* This allows the .rom binary to be filled to the right size with objcopy.     */
/********************************************************************************/
.section begin, "a"             /* The 'ALLOC' flag is needed for objcopy       */
        .ascii "VMBIOS v1.00"   /* Dummy ID string                              */
        .align 16
/********************************************************************************/


/********************************************************************************/
/* main:                                                                        */
/* This section will be relocated according to the bios.ld script.              */
/********************************************************************************/
/* 'init' doesn't have to be at the beginning, so you can move it around, as    */
/* long as remains reachable, with a short jump, from the .reset section.       */
.section main, "ax"
.globl init             /* init must be declared global for the linker and must */
init:                   /* point to the first instruction of your code section  */
        cli             /* NOTE: This sample BIOS runs with interrupts disabled */
        cld             /* String direction lookup: forward                     */
        mov  ax, cs     /* A real BIOS would keep a copy of ax, dx as well as   */
        mov  ds, ax     /* initialize fs, gs and possibly a GDT for protected   */
        mov  ss, ax     /* mode. We don't do any of this here.                  */

init_superio:
        mov  dx, SUPERIO_BASE   /* The PC97338 datasheet says we are supposed   */
        in   al, dx             /* to read this port twice on startup, but the  */
        in   al, dx             /* VMware virtual chip doesn't seem to care...  */

        /* Feed the SuperIO configuration values from a data section            */
        mov  si, offset superio_conf    /* Don't forget the 'offset' here!      */
        mov  cx, (serial_conf - superio_conf)/2
write_superio_conf:
        mov  ax, [si]
        ROM_CALL superio_out
        add  si, 0x02
        loop write_superio_conf

init_serial:            /* Init serial port                                     */
        mov  si, offset serial_conf
        mov  cx, (hello_string - serial_conf)/2
write_serial_conf:
        mov  ax, [si]
        ROM_CALL serial_out
        add  si, 0x02
        loop write_serial_conf

print_hello:            /* Print a string                                       */
        mov  si, offset hello_string
        ROM_CALL print_string

serial_repeater:        /* End the BIOS with a simple serial repeater           */
        ROM_CALL readchar
        ROM_CALL putchar
        jmp serial_repeater

/********************************************************************************/
/* Subroutines:                                                                 */
/********************************************************************************/
superio_out:            /* AL (IN): Register index,  AH (IN): Data to write     */
        mov  dx, SUPERIO_BASE
        out  dx, al
        inc  dx
        xchg al, ah
        out  dx, al
        jmp  sp


serial_out:             /* AL (IN): COM Register index, AH (IN): Data to Write  */
        mov  dx, COM_BASE
        add  dl, al     /* Unless something is wrong, we won't overflow to DH   */
        mov  al, ah
        out  dx, al
        jmp  sp


putchar:                /* AL (IN): character to print                          */
        mov  dx, COM_BASE + COM_LSR
        mov  ah, al
tx_wait:
        in   al, dx
        and  al, 0x20   /* Check that transmit register is empty                */
        jz   tx_wait
        mov  dx, COM_BASE + COM_TB
        mov  al, ah
        out  dx, al
        jmp  sp


readchar:               /* AL (OUT): character read from serial                 */
        mov  dx, COM_BASE + COM_LSR
rx_wait:
        in   al, dx
        and  al, 0x01
        jz   rx_wait
        mov  dx, COM_BASE + COM_RB
        in   al, dx
        jmp  sp


print_string:           /* SI (IN): offset to NUL terminated string             */
        lodsb
        or   al, al
        jnz  write_char
        jmp  sp
write_char:
        shl  esp, 0x10  /* We're calling a sub from a sub => preserve SP        */
        ROM_CALL putchar
        shr  esp, 0x10  /* Restore SP                                           */
        jmp  print_string


/********************************************************************************/
/* Data:                                                                        */
/********************************************************************************/
superio_conf:
/* http://www.datasheetcatalog.org/datasheet/nationalsemiconductor/PC97338.pdf  */
        .byte PC97338_FER, 0x0f         /* Enable COM, PAR and FDC              */
        .byte PC97338_FAR, 0x10         /* LPT=378, COM1=3F8, COM2=2F8          */
        .byte PC97338_PTR, 0x00         /* Make sure COM1 test mode is cleared  */
serial_conf:    /* See http://www.versalogic.com/kb/KB.asp?KBID=1395            */
        .byte COM_MCR,     0x00         /* RTS/DTS off, disable loopback        */
        .byte COM_FCR,     0x07         /* Enable & reset FIFOs. DMA mode 0.    */
        .byte COM_LCR,     0x80         /* Set DLAB (access baudrate registers) */
        .byte COM_BRD_LO,  0x01         /* Baud Rate 115200 = 0x0001            */
        .byte COM_BRD_HI,  0x00
        .byte COM_LCR,     0x03         /* Unset DLAB. Set 8N1 mode             */
hello_string:
        .string "\r\nHello BIOS world!\r\n" /* .string adds a NUL terminator    */
/********************************************************************************/


/********************************************************************************/
/* reset: this section must reside at 0xfffffff0, and be exactly 16 bytes       */
/********************************************************************************/
.section reset, "ax"
        /* Issue a manual jmp to work around a binutils bug.                    */
        /* See coreboot's src/cpu/x86/16bit/reset16.inc                         */
        .byte  0xe9
        .int   init - ( . + 2 )
        .align 16, 0xff /* fills section to end of ROM (with 0xFF)              */
/********************************************************************************/

bios.ld:
OUTPUT_ARCH(i8086)                      /* i386 for 32 bit, i8086 for 16 bit       */

/* Set the variable below to the address you want the "main" section, from bios.S, */
/* to be located. The BIOS should be located at the area just below 4GB (4096 MB). */
main_address = 4096M - 4K;              /* Use the last 4K block                   */

/* Set the BIOS size below (both locations) according to your target flash size    */
MEMORY {
        ROM (rx) : org = 4096M - 512K, len = 512K
}

/* You shouldn't have to modify anything below this                                */
SECTIONS {
        ENTRY(init)                     /* To avoid antivirus false positives      */
        /* Sanity check on the init entrypoint                                     */
        _assert = ASSERT(init >= 4096M - 64K, 
                "'init' entrypoint too low - it needs to reside in the last 64K.");
        .begin : {      /* NB: ld section labels MUST be 6 letters or less         */
                *(begin)
        } >ROM          /* Places this first section at the beginning of the ROM   */
        /* the --gap-fill option of objcopy will be used to fill the gap to .main  */
        .main main_address : {
                *(main)
        }
        .reset 4096M - 0x10 : {         /* First instruction executed after reset  */
                *(reset)
        }
        .igot 0 : {                     /* Required on Linux                       */
                *(.igot.plt)
        }
}

Makefile (IMPORTANT: if you copy/paste, you will have to restore the tabs at the beginning of each line that start with a space):
ASM       = gcc
CC        = gcc
LD        = ld
OBJDUMP   = objdump
OBJCOPY   = objcopy

CFLAGS    = -m32 -nostartfiles

OBJECTS   = bios.o
TARGET    = bios
MEMLAYOUT = xMemLayout.map

.PHONY: all clean

all: $(TARGET).rom

clean:
 @-rm -f -v *.o $(TARGET).out $(MEMLAYOUT)

%.o: %.c Makefile
 @echo "[CC]  $@"
 @$(CC) -c -o $*.o $(CFLAGS) $<

%.o: %.S Makefile
 @echo "[AS]  $<"
 @$(ASM) -c -o $*.o $(CFLAGS) $<

# Produce a disassembly dump of the main section, for verification purposes
dis: $(TARGET).out
 @echo "[DIS] $<"
 @$(OBJCOPY) -O binary -j .main --set-section-flags .main=alloc,load,readonly,code $< main.bin
 @$(OBJDUMP) -D -bbinary -mi8086 -Mintel main.bin | less
 @-rm -f main.bin

$(TARGET).out: $(OBJECTS) $(TARGET).ld
 @echo "[LD]  $@"
 @$(LD) $(LDFLAGS) -T$(TARGET).ld -o $@ $(OBJECTS) -Map $(MEMLAYOUT)

$(TARGET).rom: $(TARGET).out
 @echo "[ROM] $@"
 @# Note: -j only works for sections that have the 'ALLOC' flag set
 @$(OBJCOPY) -O binary -j .begin -j .main -j .reset --gap-fill=0x0ff $< $@

Compiling and testing
  • Copy the Makefile, bios.S and bios.ld from above, or extract the files from the archive below to a directory
  • run make. You should end up with a 512 KB bios.rom file
  • Copy bios.rom to your target VMware image directory and manually edit your .vmx file to have the line:
    bios440.filename = "bios.rom"
  • Edit your virtual machine settings to make sure it has a serial port.
    The preferred method to access the serial console is to use a null modem emulator, such as com0com, a signed version of which I made available in the next post.
    Otherwise, you can use either use an actual host COM port (you'll need a null modem cable to another serial port), output to file (which is the easiest way to confirm that the BIOS works, as you will see some output there, but you won't be able to test the repeater) or a named pipe (with the end of pipe set for an application such as putty - the problem with using a pipe however being that you can only connect to it after the VM is started, so you will likely miss the initial serial output).
    The Serial port needs to be set to 115200 bauds, 8N1.
  • Run the machine. You should see an "hello world" message printed out, and, provided your serial configuration allows input, anything you type should be echoed back on your terminal

Goodies
  • vmbios-1.1.tgz: an archive containing all the files above, as well as the generated BIOS.
  • Note that you can issue 'make dis' to get a disassembly output of the main section if needed.
  • Note that for reference, a memory map called xMemLayout.map is also produced during the build.
  • BIOS Disassembly Ninjutsu Uncovered, by Darmawan Salihun (PDF): If you're going to do start with BIOS modding, this should be your reference. Or see the author's page.

Happy BIOS hacking!

2011-06-05

Extracting and using a modified VMWare Player BIOS or UEFI firmware

(updated 2016.05.17 for VMWare UEFI firmware extraction)

One day or another, you may want to play with BIOS/UEFI firmware modification. But before jumping into physical motherboard flash alteration, where the consequences of a mishap can be difficult to salvage, experimenting with the BIOS/UEFI in a virtual environment sounds like a sound first step. You have of course the opportunity to do so using bochs, however the ubiquitous nature and ease of use of the (freely available) VMware Player can turn the latter in a much better candidate for the job if you don't really care about the extra features that bochs brings in. This post details how one can extract the BIOS/UEFI firmwares used by the VMWare Player, and how to setup the player to use a modified one.
  1. Download and install VMware Player. The current version of VMware Player is 12, and the download can be obtained from this page (freely, but after e-mail registration). Binaries are available for Windows and Linux, in both 32 and 64 bit versions.
  2. Extract the BIOS from the VMware executables by following the instructions below:

    • Windows: Download and install 7-zip (which any reasonable Windows user should have installed anyway). Then navigate to your VMware Player installation directory and locate the vmware-vmx.exe application (notice the -vmx here). It should reside in the same directory for 32 bit, or in the x64\ directory for 64 bit. Open it in 7-Zip and go to the .rsrc\BINRES\ directory.
      For the BIOS (internally called bios440, as it emulates an intel 440BX chipset), you need to look for a file exactly 524 288 bytes (512 KB) in size. On current versions, there should be only one, called '6006'. This is the BIOS file we are after, so just extract it to a directory of your choice.
      For the UEFI firmware, you should look for a file that is 2 097 152 bytes (2 MB) in size. Because VMWare can emulate both 32 and 64-bit platforms, you will find that there exists 2 of these. At the time of writing this article, just know that '6020' is the IA32 UEFI firmware and '6021' is the X64 UEFI firmware.
    • Linux: From a terminal, navigate to the directory vmware-vmx binary resides (default is /usr/lib/vmware/bin. Issue the following set of commands (copied from the Arch Linux VMware page):
      $ objcopy /usr/lib/vmware/bin/vmware-vmx -O binary -j bios440 --set-section-flags bios440=a bios440.rom.Z
      $ perl -e 'use Compress::Zlib; my $v; read STDIN, $v, '$(stat -c%s "./bios440.rom.Z")'; $v = uncompress($v); print $v;' < bios440.rom.Z > bios440.rom
      Remember that you can always use objdump -h to find the various sections before using objcopy, in case the BIOS is no longer called bios440.rom
  3. Edit the BIOS or UEFI firmware as you see fit. Note that the BIOS VMware uses is of type Phoenix.
    If you are on Windows, you can download a full version of Phoenix Bios Editor Pro v2.1.0.0 from Intel under the name BIOS Logo Change Utility (or simply search for "Phoenix" on the Intel Download Center). If you install this tool on Vista or later, you will have to run Phoenix Bios Editor Pro as Administrator, and possibly in a 32 bit environment, for it to work.
  4. Once your BIOS/UEFI file is modified according to your needs, locate the Virtual Machine you plan to test your firmware with and copy your modified file into its directory. Then, edit the .vmx file to add one of these lines, according to the type of platform you want to run.
    • For BIOS, you only need to add:
      bios440.filename = "bios440.rom"
    • For 32-bit UEFI, you will need to have something like:
      firmware = "efi"
      efi32.filename = "efi32.rom"
    • For 64-bit UEFI, you will need:
      firmware = "efi"
      efi32.filename = "efi64.rom"
  5. Run the image, and it should use your modified firmware. If you have simply modded the original BIOS using Phoenix Bios Editor, a good way to confirm that the VM is using your custom BIOS is to change the 'Quiet Boot Logo' section, which contains the VMware logos you see during BIOS execution.
Also note that, as pointed out on this page, if you are usingVMWare Fusion, you may actually be able to extract the various embedded binaries using the -e switch (the page linked also provides a list of all the binaries you can extract). Sadly however, this switch doesn't apply for VMWare Player on Windows...