You saw in Chapter 8 that static analysis is a powerful tool for bootkit reverse engineering. In some situations, however, it can’t give you the information you’re looking for, so you’ll need to use dynamic analysis techniques instead. This is often true for bootkits that contain encrypted components for which decryption is problematic or for bootkits like Rovnix—covered in Chapter 11—that employ multiple hooks during execution to disable OS protection mechanisms. Static analysis tools can’t always tell which modules the bootkit tampers with, so dynamic analysis is more effective in these cases.
Dynamic analysis generally relies on the debugging facilities of the platform being analyzed, but the preboot environment doesn’t provide conventional debugging facilities. Debugging in a preboot environment usually requires special equipment, software, and knowledge, making it a challenging task.
To overcome this hurdle, we need an additional layer of software—either an emulator or a virtual machine (VM). Emulation and virtualization tools enable us to run boot code in the controlled preboot environment with conventional debugging interfaces.
In this chapter, we’ll explore both approaches to dynamic bootkit analysis—specifically, emulation with Bochs and virtualization with VMware Workstation. The two types of approaches are similar, and both allow researchers to observe the boot code’s behavior at the moment of execution, provide the same level of insight into the code being debugged, and permit the same access to the CPU registers and memory.
The difference between the two methods lies in their implementation. The Bochs emulator interprets the code to emulate entirely on a virtual CPU, whereas VMware Workstation uses the real, physical CPU to execute most instructions of a guest OS.
The bootkit components we’ll be using for the analysis in this chapter are available in the book’s resources at https://nostarch.com/rootkits/. You’ll need the MBR in the file mbr.mbr and the VBR and IPL in the file partition0.data.
Bochs (http://bochs.sourceforge.net/), pronounced “box,” is an open source emulator for the Intel x86-64 platform capable of emulating an entire computer. Our primary interest in this tool is that it provides a debugging interface that can trace the code it emulates, so we can use it to debug modules executed in the preboot environment, such as the MBR and VBR/IPL. Bochs also runs as a single user-mode process, so there’s no need to install kernel-mode drivers or any special system services to support the emulated environment.
Other tools, like the open source emulator QEMU (http://wiki.qemu.org/Main_Page), provide the same functionality as Bochs and can also be used for bootkit analysis. But we chose Bochs over QEMU because in our extensive experience, Bochs has shown better integration with Hex-Rays IDA Pro for Microsoft Windows platforms. Bochs also has a more compact architecture that focuses on emulating only x86/x64 platforms, and it has an embedded debugging interface that we can use for boot code debugging without having to use IDA Pro—although its performance is enhanced when paired with IDA Pro, as we’ll demonstrate later in “Combining Bochs with IDA” on page 123.
It’s worth noting that QEMU is more efficient and supports more architectures, including the Advanced RISC Machine (ARM) architecture. QEMU’s use of an internal GNU Debugger (GDB) interface also provides opportunities for debugging from early on in the VM booting process. So, if you want to explore debugging more after this chapter, QEMU may be worth trying out.
You can download the latest version of Bochs from https://sourceforge.net/projects/bochs/files/bochs/. You have two download options: the Bochs installer and a ZIP archive with Bochs components. The installer includes more components and tools—including the bximage tool we’ll discuss later—so we recommend downloading it instead of the ZIP archive. The installation is straightforward: just click through the steps and leave the default values for the parameters. Throughout the chapter, we’ll refer to the directory where Bochs has been installed as the Bochs working directory.
To use the Bochs emulator, we first need to create an environment for it, consisting of a Bochs configuration file and a disk image. The configuration file is a text file that contains all the essential information the emulator needs to execute the code (which disk image to use, the CPU parameters, and so forth), and the disk image contains the guest OS and boot modules to emulate.
Listing 9-1 demonstrates the most frequently used parameters for bootkit debugging, and we’ll use this as our Bochs configuration file throughout this chapter. Open a new text file and enter the contents of Listing 9-1. Or, if you prefer, you can use the bochsrc.bxrc file provided in the book’s resources. You’ll need to save this file in the Bochs working directory and name it bochsrc.bxrc. The .bxrc extension means that the file contains configuration parameters for Bochs.
megs: 512
romimage: file="../BIOS-bochs-latest" ➊
vgaromimage: file="../VGABIOS-lgpl-latest" ➋
boot: cdrom, disk ➌
ata0-master: type=disk, path="win_os.img", mode=flat, cylinders=6192, heads=16, spt=63 ➍
mouse: enabled=0 ➎
cpu: ips=90000000 ➏
Listing 9-1: Sample Bochs configuration file
The first parameter, megs, sets a RAM limit for the emulated environment in megabytes. For our boot code–debugging needs, 512MB is more than sufficient. The romimage parameter ➊ and vgaromimage parameter ➋ specify the paths to the BIOS and VGA-BIOS modules to be used in the emulated environment. Bochs comes with default BIOS modules, but you can use custom modules if necessary (for example, in the case of firmware development). Because our goal is to debug MBR and VBR code, we’ll use the default BIOS module. The boot option specifies the boot device sequence ➌. With the settings shown, Bochs will first attempt to boot from the CD-ROM device, and if that fails, it will proceed to the hard drive. The next option, ata0-master, specifies the type and characteristics of the hard drive to be emulated by Bochs ➍. It has several parameters:
type The type of device, either disk or cdrom.
path The path to a file on the host filesystem with the disk image.
mode The type of image. This option is valid only for disk devices; we’ll discuss it in more detail in “Combining Bochs with IDA” on page 123.
cylinders The number of cylinders for the disk; this option defines the size of the disk.
heads The number of heads for the disk; this option defines the size of the disk.
spt The number of sectors per track; this option defines the size of the disk.
NOTE
In the following section, you’ll see how to create a disk image using the bximage tool included with Bochs. Once it has created a new disk image, bximage outputs the parameters for you to provide in the ata0-master option.
The mouse parameter enables the use of a mouse in the guest OS ➎. The cpu option defines the parameters of the virtual CPU inside the Bochs emulator ➏. In our example, we use ips to specify the number of instructions to emulate per second. You can tweak this option to change performance characteristics; for example, for Bochs version 2.6.8 and a CPU with Intel Core i7, the typical ips value would be between 85 and 95 MIPS (millions of instructions per second), which is the case with the value we’re using here.
To create a disk image for Bochs, you can use either the dd utility in Unix or the bximage tool provided with the Bochs emulator. We’ll choose bximage because we can use it on both Linux and Windows machines.
Open the bximage disk image creation tool. When it starts, bximage provides a list of options, as shown in Figure 9-1. Enter 1 to create a new image ➊.
The tool then asks whether you want to make a floppy or hard disk image. In our case, we specify hd ➋ to create a hard disk image. Next, it asks what type of image to create. Generally, the type of disk image determines the layout of the disk image in the file. The tool can create multiple types of disk images; for a full list of supported types, refer to the Bochs documentation. We choose flat ➌ to produce a disk image in a single file with flat layout. This means the offset within the file disk image corresponds to the offset on the disk, which allows us to easily edit and modify the image.
Figure 9-1: Creating a Bochs disk image with the bximage tool
Next, we need to specify the disk size in megabytes. The value you provide depends on what you’re using Bochs for. If you want to install an OS onto the disk image, the disk size needs to be large enough to store all the OS files. On the other hand, if you want to use the disk image only for debugging boot code, a disk size of 10MB ➍ is sufficient.
Finally, bximage prompts for an image name—this is the path to the file on the host filesystem in which the image will be stored ➎. If you provide only the filename without the full path, the file will be stored in the same directory as Bochs. Once you enter the filename, Bochs creates the disk image and outputs a configuration string ➏ for you to enter in the ata0-master line of the Bochs configuration file (Listing 9-1). To avoid confusion, either provide a full path to the image file in bximage or copy the newly created image file into the same directory as the configuration file. This ensures that Bochs can find and load the image file.
Once you’ve created the disk image, we can proceed with infecting the disk with a bootkit. We can do so in one of two ways. The first option is to install a guest OS onto the Bochs disk image and then execute the bootkit infector into the guest environment. At execution, the malware will infect the disk image with the bootkit. This approach allows you to perform deeper malware analysis because the malware installs all the components onto the guest system, including the bootkit and the kernel-mode drivers. But it also has some drawbacks:
For these reasons, we’ll use the second option: infecting the disk image by extracting the bootkit components (the MBR, VBR, and IPL) from the malware and writing them directly to the disk image. This approach requires a substantially smaller disk size, and it is usually much faster. But it also means we can’t observe and analyze other components of the malware, like kernel-mode drivers. This approach also requires some prior understanding of the malware and its architecture. So another reason we’re choosing it is that it gives us more insight into using Bochs in the context of dynamic analysis.
Make sure you’ve downloaded and saved the mbr.mbr code from the resources at https://nostarch.com/rootkits/. Listing 9-2 shows the Python code that writes the malicious MBR onto the disk image. Copy it into a text editor and save it as an external Python file.
# read MBR from file
mbr_file = open("path_to_mbr_file", "rb") ➊
mbr = mbr_file.read()
mbr_file.close()
# write MBR to the very beginning of the disk image
disk_image_file = open("path_to_disk_image", "r+b") ➋
disk_image_file.seek(0)
disk_image_file.write(mbr) ➌
disk_image_file.close()
Listing 9-2: Writing the MBR code onto the disk image
In this example, enter the file location for the MBR in place of path_to_mbr_file ➊, enter the disk image location in place of path_to_disk_image ➋, and then save the code into a file with the extension .py. Now, execute python path_to_the_script_file.py, and the Python interpreter will execute the code in Bochs. The MBR we’ve written ➌ onto the disk image contains only one active partition (0) in the partition table, as shown in Table 9-1.
Table 9-1: MBR Partition Table
Partition number |
Type |
Starting sector |
Partition size in sectors |
0 |
0x80 (bootable) |
0x10 ➊ |
0x200 |
1 |
0 (no partition) |
0 |
0 |
2 |
0 (no partition) |
0 |
0 |
3 |
0 (no partition) |
0 |
0 |
Next, we need to write the VBR and IPL onto the disk image. Make sure you download and save the partition0.data code from the resources at https://nostarch.com/rootkits/. We need to write these modules at the offset ➊ specified in Table 9-1, which corresponds to the starting offset of the active partition.
To write the VBR and IPL onto the disk image, enter the code presented in Listing 9-3 in a text editor and save it as a Python script.
# read VBR and IPL from file
vbr_file = open("path_to_vbr_file", "rb") ➊
vbr = vbr_file.read()
vbr_file.close()
# write VBR and IPL at the offset 0x2000
disk_image_file = open("path_to_disk_image", "r+b") ➋
disk_image_file.seek(0x10 * 0x200)
disk_image_file.write(vbr)
disk_image_file.close()
Listing 9-3: Writing the VBR and IPL onto the disk image
Again, as with Listing 9-2, replace path_to_vbr_file ➊ with the path to the file containing the VBR and replace path_to_disk_image ➋ with the image location before running the script.
After executing the script, we have a disk image ready for debugging in Bochs. We’ve successfully written the malicious MBR and VBR/IPL onto the image, and we can analyze them in the Bochs debugger.
The Bochs debugger is a stand-alone application, bochsdbg.exe, with a command line interface. We can use the functions supported by the Bochs debugger—such as breakpoint, memory manipulation, tracing, and code disassembly—to examine boot code for malicious activity or decrypt polymorphic MBR code. To start a debugging session, call the bochsdbg.exe application from the command line with a path to the Bochs configuration file bochsrc.bxrc, like so:
bochsdbg.exe -q -f bochsrc.bxrc
This command starts a virtual machine and opens a debugging console. First, set a breakpoint at the beginning of the boot code so that the debugger stops the execution of the MBR code at the beginning, giving us an opportunity to analyze the code. The first MBR instruction is placed at address 0x7c00, so enter the command lb 0x7c00 to set the breakpoint at the beginning of the instructions. To commence execution, we apply the c command, as shown in Figure 9-2. To see the disassembled instructions from the current address, we use the u debugger command; for example, Figure 9-2 shows the first 10 disassembled instructions with the command u /10.
Figure 9-2: The command line Bochs debugger interface
You can get a full list of the debugger commands by entering help or visiting the documentation at http://bochs.sourceforge.net/doc/docbook/user/internal-debugger.html. Here are a few of the more useful ones:
c Continue executing.
s [count] Execute count instructions (step); the default value is 1.
q Quit the debugger and execution.
CTRL-C Stop execution and return to the command line prompt.
lb addr Set a linear address instruction breakpoint.
info break Display the state of all current breakpoints.
bpe n Enable a breakpoint.
bpd n Disable a breakpoint.
del n Delete a breakpoint.
Although we can use the Bochs debugger on its own for basic dynamic analysis, we can do more when it’s bound with IDA, mainly because the code navigation in IDA is much more powerful than batch-mode debugging. In an IDA session, we can also continue with a static analysis of the created IDA Pro database file and use features like the decompiler.
Now that we have an infected disk image prepared, we’ll launch Bochs and start the emulation. Starting with version 5.4, IDA Pro provides a frontend for the DBG debugger, which we can use with Bochs to debug guest operating systems. To launch the Bochs debugger in IDA Pro, open IDA Pro and then go to Debugger▸Run▸Local Bochs debugger.
A dialog will open, asking for some options, as shown in Figure 9-3. In the Application field, specify the path to the Bochs configuration file you created earlier.
Figure 9-3: Specifying the path to the Bochs configuration file
Next, we need to set some options. Click Debug options and then go to Set specific options. You’ll see a dialog like the one in Figure 9-4, offering three options for the Bochs operation mode:
Disk image Launch Bochs and execute the disk image.
IDB Emulate a selected part of the code inside Bochs.
PE Load and emulate the PE image inside Bochs.
Figure 9-4: Choosing the operation mode for Bochs
For our case, we select Disk image ➊ to make Bochs load and execute the disk image we created and infected earlier.
Next, IDA Pro launches Bochs with our specified parameters, and because we set the breakpoint earlier, it will break upon execution of the first instruction of the MBR at address 0000:7c00h. We can then use the standard IDA Pro debugger interface to debug the boot components (see Figure 9-5).
Figure 9-5: Debugging MBR from IDA interface on a Bochs VM
The interface presented in Figure 9-5 is considerably more user-friendly than the command line interface the Bochs debugger provides (shown previously in Figure 9-2). You can see the disassembly of the boot code ➊, the contents of the CPU’s registers ➋, a memory dump ➌, and the CPU’s stack ➍ in a single window. This significantly simplifies the process of boot code debugging.
IDA Pro and Bochs are a powerful combination for boot code analysis. But debugging OS boot processes is sometimes unstable with Bochs, and there are some performance limitations to the emulation technique. For instance, performing an in-depth analysis of malware requires you to create a disk image with a preinstalled OS. This step can be time-consuming due to the nature of emulation. Bochs also lacks a convenient system for managing snapshots of an emulated environment—an indispensable feature in malware analysis.
For something more stable and efficient, we can use VMware’s internal GDB debugging interface with IDA. In this section, we introduce the VMware GDB debugger and demonstrate how to set up a debugging session. We’ll discuss the specifics of debugging Microsoft Windows bootloaders over the next few chapters, which focus on MBR and VBR bootkits. We’ll also look at switching from real mode to protected mode from a debugging perspective.
VMware Workstation is a powerful tool for replicating operating systems and environments. It allows us to create virtual machines with guest operating systems and run them on the same machine as the host operating system. The guest and host operating systems will work without interfering with each other, as if they were running on two different physical machines. This is very useful for debugging because it makes it easy to run two programs—the debugger and the application being debugged—on the same host. In this regard, the VMware Workstation is quite similar to Bochs, except that the latter emulates CPU instructions, whereas VMware Workstation executes them on the physical CPU. As a result, the code executed in the VM runs faster than in Bochs.
The recent versions of VMware Workstation (version 6.5 onward) include a GDB stub for debugging VMs running inside VMware. This allows us to debug the VM from the very beginning of its execution, even before BIOS executes the MBR code. Starting from version 5.4, IDA Pro includes a debugger module that supports the GDB debug protocol, which we can use in conjunction with VMware.
At the time of writing this chapter, VMware Workstation is available in two versions: Professional (the commercial version) and Workstation Player (the free version). The Professional version offers extended functionality, including the ability to create and edit VMs, whereas Workstation Player allows users only to run VMs or to modify their configurations. But both versions include the GDB debugger, and we can use both for bootkit analysis. In this chapter, we’ll use the Professional version so we can create a VM.
NOTE
Before you can start using the VMware GBD debugger, you need to create a virtual machine instance using VMware Workstation and preinstall an operating system on it. The process of creating a VM is beyond the scope of this chapter, but you can find all the necessary information in the documentation at https://www.vmware.com/pdf/desktop/ws90-using.pdf.
Once you’ve created a virtual machine, VMware Workstation places the VM image and a configuration file in a user-specified directory, which we will refer to as the virtual machine’s directory.
To enable VMware to work with GDB, you first need to specify certain configuration options in the virtual machine configuration file, shown in Listing 9-4. The virtual machine configuration file is a text file that should have the extension .vmx, and it is located in the virtual machine’s directory. Open it in the text editor of your choice and copy the parameters in Listing 9-4.
➊ debugStub.listen.guest32 = "TRUE"
➋ debugStub.hideBreakpoints= "TRUE"
➌ monitor.debugOnStartGuest32 = "TRUE"
Listing 9-4: Enabling a GDB stub in the VM
The first option ➊ allows guest debugging from the local host. It enables the VMware GDB stub, which allows us to attach a debugger supporting the GDB protocol to the debugged VM. If our debugger and VM were running on different machines, we would instead need to enable remote debugging with the command debugStub.listen.guest32.remote.
The second option ➋ enables the use of hardware breakpoints rather than software breakpoints. The hardware breakpoints employ CPU debugging facilities—namely, debugging registers dr0 through dr7—whereas implementing software breakpoints usually involves executing the int 3 instruction. In the context of malware debugging, this means hardware breakpoints are more resilient and more difficult to detect.
The last option ➌ instructs GDB to break the debugger upon executing the very first instruction from the CPU—that is, right after the VM is launched. If we skip this configuration option, VMware Workstation will start executing the boot code without breaking on it, and as a result, we won’t be able to debug it.
After configuring the VM, we can proceed with launching the debugging session. First, to start the VM in VMware Workstation, go to the menu and choose VM▸Power▸Power On.
Next, we’ll run the IDA Pro debugger to attach to the VM. Select Debugger and go to Attach▸Remote GDB debugger.
Now we need to configure the debugging options. First, we specify the hostname and the port of the target it should attach to. We’re running the VM on the same host, so we specify localhost as the hostname (as shown in Figure 9-6) and 8832 as the port. This is the port the GDB stub will listen to for incoming connections when we’re using debugStub.listen.guest32 in the VM configuration file (when we’re using debugStub.listen.guest64 in the configuration file, the port number is 8864). We can leave the rest of debug parameters at their default values.
Figure 9-6: Specifying GDB parameters
Once all the options are set, IDA Pro attempts to attach to the target and suggests a list of processes it can attach to. Since we have already started debugging the preboot components, we should choose <attach to the process started on target>, as shown in Figure 9-7.
Figure 9-7: Selecting the target process
At this point, IDA Pro attaches to the VM and breaks upon execution of the very first instruction.
Before going any further, we need to change the type of the memory segment the debugger has created for us. When we started the debugging session, IDA Pro created a 32-bit memory segment, something like Figure 9-8.
Figure 9-8: Parameters of the memory segment in IDA Pro
In the preboot environment, the CPU operates in real mode, so in order to correctly disassemble the code, we need to change this segment from 32-bit to 16-bit. To do this, right-click the target segment and choose Change segment attributes. In the dialog that appears, select 16-bit ➊ in the Segment bitness pane, as shown in Figure 9-9.
Figure 9-9: Changing the bitness of the memory segment
This will make the segment 16-bit, and all the instructions in the boot components will be correctly disassembled.
With all the correct options set, we can proceed with the MBR loading. Since the debugger was attached to the VM at the very beginning of the execution, the MBR code hasn’t yet been loaded. To load the MBR code, we set a breakpoint at the very start of the code at the address 0000:7c00h and then continue the execution. To set the breakpoint, go to address 0000:7c00h in the disassembly window and press F2. This will display a dialog with the breakpoint parameters (see Figure 9-10).
The Location text box ➊ specifies the address at which the breakpoint will be set: 0x7c00, which corresponds to virtual address 0000:7c00h. In the Settings area ➋, we select the Enabled and Hardware checkbox options. Checking the Enabled box means that the breakpoint is active, and once the execution flow reaches the address specified in the Location text box, the breakpoint is triggered. Checking the Hardware box means that the debugger will use the CPU’s debugging registers to set up the breakpoint, and it also activates the Hardware breakpoint mode options ➌, which specify the type of the breakpoint. In our case, we specify Execute to set up the breakpoint for executing an instruction at address 0000:7c00h. The other types of hardware breakpoints are for reading and writing memory at the specified location, which we don’t need here. The Size drop-down menu ➍ specifies the size of the controlled memory. We can leave the default value, 1, meaning that the breakpoint will control only 1 byte at address 0000:7c00h. Once these parameters are set, click OK and then resume execution by pressing F9.
Figure 9-10: The Breakpoint settings dialog
Once the MBR is loaded and executed, the debugger breaks. The debugger window is shown in Figure 9-11.
Figure 9-11: The IDA Pro debugger interface
At this point, we are at the very first instruction of the MBR code, as the instruction pointer register ➊ points to 0000:7c00h. We can see in the memory dump window and in the disassembly that the MBR has been successfully loaded. From here, we can continue the debugging process of the MBR code and execute each instruction, step by step.
NOTE
The purpose of this section was simply to introduce you to the possibility of using the VMware Workstation GDB debugger with IDA Pro, so we aren’t going any deeper into using the GDB debugger in this chapter. You’ll find more information on its usage over the next few chapters, however, as we analyze the Rovnix bootkit.
This chapter doesn’t cover the Hyper-V virtual machine manager, which is a component of Microsoft’s client operating systems since Windows 8, nor does it cover the VirtualBox open source virtual machine manager (VMM). This is because, at the time of this writing, neither program has a documented interface for debugging early enough in the VM boot process for the requirements of boot code malware analysis.
At the time of publication, Microsoft Hyper-V is the only virtualization software that can support VMs with Secure Boot enabled, which may be one reason no debugging interface is provided for the early stages of the boot process. We’ll look more deeply at Secure Boot technology and its vulnerabilities in Chapter 17. We mention these two programs here because they are used extensively in malware analysis, but their lack of early boot process debugging interfaces is the main reason we prefer the VMware Workstation for debugging malicious bootstrap code.
In this chapter, we demonstrated how to debug bootkit MBR and VBR code using the Bochs emulator and VMware Workstation. These techniques for dynamic analysis are useful to have in your arsenal when you need to take a deeper look inside malicious bootstrap code. They complement methods you might use in static analysis and help answer questions that static analysis can’t.
We’ll use these tools and methods again in Chapter 11 to analyze the Rovnix bootkit, whose architecture and functionality is too elaborate for static analysis methods to be effective.
We’ve provided a series of exercises for you to test out the skills you learned in this chapter. You’ll construct a Bochs image of a PC from an MBR, a VBR/IPL, and a New Technology File System (NTFS) partition and then perform dynamic analysis using the IDA Pro frontend for Bochs. First, you need to download the following resources at https://nostarch.com/rootkits/.
mbr.mbr A binary file containing an MBR
partition0.data An NTFS partition image, containing a VBR and an IPL
bochs.bochsrc The Bochs configuration file
You’ll also need the IDA Pro disassembler, a Python interpreter, and the Bochs emulator. Using these tools and the information covered in this chapter, you should be able to complete the following exercises:
18.218.189.173