[Book] Rootkit And Bootkit

First Post:

Last Update:

Word Count:
8.8k

Read Time:
55 min

Introduction

This article serves as my study notes while reading Rootkit And Bootkit.

As I continue learning about rootkits and bootkits, I will document important concepts, techniques, and case studies from the book, along with my own understanding.

The content will be continuously updated as I progress through the book.

El Libro

Reflections

I have always been interested in rootkits and bootkits. Althrough I had known about this book for quite a while, I had never seriously read it before. One of the primary reasons was that I simply did not have enough background knowledge to fully understand it.

To study this book effectively, you should already have some understanding of operating systems, especially Windows internals and APIs, because most of the rootkits and bootkits introduced in this book are closely related to the Windows operating system.

In addition, you may still feel confused even if you already have reverse engineering experience and knowledge of the aforementioned fields. This is absolutely normal because many of the techniques used by these malware families were discovered through countless experiments and investigations rather than derived directly from theory.

Therefore, I believe the best way to learn this book is to repeatedly study its content while also practically analyzing the provided samples.

Chapter 1 - What’s In a Rootkit: The TDL3 Case Study

History of TDL3 Distribution in the Wild

It was first discovered in 2010.

Controlling the Flow of Data

To fulfill their mission of stealth, kernel rootkits must modify the control flow or the data flow (or both) of the kernel’s system calls.

To do so, rootkits typically inject their code somewhere on the execution path of the system call implementation.

The Hidden Filesystem

TDL3 was the first malware system to store its configuration files and payload in a hidden encrypted storage area on the target system, instead of relying on the filesystem service provided by the operating system. Today, TDL3’s approach has been adopted by other complex threats (e.g., Rovnix Bootkit, ZeroAccess, Avatar and Gapz).

TDL3 allocates its image of the hidden filesystem on the hard disk. The image is divided into blocks of 1024 bytes each. The first block contains a file table whose entries describe files contained within the filesystem and include the following information:

  • A filename limited to 16 characters, including the terminating null
  • The size of the file
  • The actual file offset, which is calculated by subtracting the starting offset of a file, multiplied by 1024, from the offset of the beginning of the filesystem
  • The time the filesystem was created

The contents of the filesystem are encrypted with a custom encryption algorithm on a per-block basis (e.g., RC4 or XOR operation with a fixed key).

From user mode, the payload accesses the hidden storage by opening a handle for a device object named \Device\XXXXXXXX\YYYYYYYY where XXXXXXXX and YYYYYYYY are randomly generated hexadecimal numbers.

Note that the codepath to access this storage relies on many standard Windows components.

The name of the device object is generated each time the system boots and then passed as a parameter to the payload modules.

The rootkit is responsible for maintaining and handling I/O requests to this filesystem. For example, when a payload module performs an I/O operation with a file stored in the hidden storage area, the OS transfers this request to the rootkit and executes its entry point functions to handle the request.

TDL3 shows the general trend followed by rootkits. Rather than providing brand-new code for all of its operations, burdening the third-party malware developers with learning the peculiarities of that code, a rootkit piggybacks on the existing and familiar Windows functionality.

Note: This design is particularly interesting because it avoids relying on the operating system’s filesystem, making traditional file-based detection much less effective.

Chapter 2 - Festi Rootkit: The Most Advanced Spam and DDoS Bot

Compared to TDL3, Festi adopts a more modular design by introducing a plug-in architecture, allowing its functionality to be extended dynamically.

Dissecting the Rootkit Driver

The Festi rootkit is distributed mainly through a PPI scheme similar to the TDL3 rootkit. The dropper’s rather simple functionality installs into the system a kernel-mode driver that implements the main logic of the malware.

A dropper is a special type of infector. Droppers carry payload to the victim system within itself. The payload is frequently compressed and encrypted or obfuscated. Once executed, a dropper extracts the payload from its image and installs it on a victim system.

Plug-in Management

Plug-ins downloaded from the C7C server and loaded and executed by the malware. Festi maintains an array of pointers to a specially defined PLUGIN_INTERFACE structure.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct PLUGIN_INTERFACE
{
PVOID Instalize; // Initialize plug-in

PVOID Release; // Release plug-in, perform cleanup operations

PVOID GetVersionInfo_1; // Get plug-in version information

PVOID GetVersionInfo_2; // Get plug-in version information

PVOID WriteIntoTcpStream; // Write plug-in specific information into TCP stream

PVOID ReadFromTcpStream; // Read plug-in specific information from TCP stream and parse data

PVOID Reserved_1;

PVOID Reserved_2;
}

AntiDebugging Techniques

Festi also checks for the presence of a kernel debugger in the system by examining the KdDebuggerEnabled variable exported from the operating system kernel image. If a system debugger is attached to the operating system, the variable contains the value TRUE; otherwise, it contains FALSE.

Festi actively counteracts the system debugger by periodically zeroing the debugging registers dr0 through dr3. These registers are used to store addresses for breakpoints, and removing the hardware breakpoints hinders the debugging process.

1
2
3
4
5
6
7
8
9
char _thiscall ProtoHandler_1(STRUCT_4_4 *this, PKEVENT a1)
{
__writedr(0, 0); // mov dr0, 0
__writedr(1u, 0); // mov dr1, 0
__writedr(2u, 0); // mov dr2, 0
__writedr(3u, 0); // mov dr3, 0

return _ProtoHandler(&this->struct43, a1);
}

The Method for Hiding the Malicious Driver on Disk

To protect and conceal the image of the malicious kernel-mode driver stored on the hard drive, Festi hooks the filesystem driver so that it can intercept and modify all requests sent to the filesystem driver to exclude evidence of its presence.

A simplified version of the routine for installing the hook is shown below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
NTSTATUS __stdcall SetHookOnSystemRoot(PDRIVER_OBJECT DriverObject, int **HookParams)
{
RtlInitUnicodeString(&DestinationString, L"\\SystemRoot");
ObjectAttributes.Length = 24;
ObjectAttributes.RootDirectory = 0;
ObjectAttributes.Attributes = 64;
ObjectAttributes.ObjectName = &DestinationString;
ObjectAttributes.SecurityDescriptor = 0;
ObjectAttributes.SecurityQualityOfService = 0;

NTSTATUS status = IoCreateFile(&hSystemRoot, 0x80000000, &ObjectAttributes, &IoStatusBlock, 0, 0, 3u, 1u, 1u, 0, 0, 0, 0, 0x100u);
if (status < 0)
return status;

status = ObReferenceObjectByHandle(hSystemRoot, 1u , 0, 0, &SystemRootFileObject, 0);
if (status < 0)
return status;

PDEVICE_OBJECT TargetDevice = IoGetRelatedDeviceObject(SystemRootFileObject);
if (!TargetDevice)
return STATUS_UNSUCCESSFUL;

ObfReferenceObject(TargetDevice);
status = IoCreateDevice(DriverObject, 0xCu, 0, TargetDevice->DeviceType, TargetDevice->Characteristics, 0, &SourceDevice);
if (status < 0)
return status;

PDEVICE_OBJECT DeviceAttachedTo = IoAttachDeviceToDeviceStack(SourceDevice, TargetDevice);
if (!DeviceAttachedTo)
{
IoDeleteDevice(SourceDevice);
return STATUS_UNSUCCESSFUL;
}

return STATUS_SUCCESS;
}

The malware first tries to obtain a handle to the special system file SystemRoot, which corresponds to the Windows installation directory. Then, by executing the ObReferenceObjectByHandle system routine, Festi obtains a pointer to the FILE_OBJECT that corresponds to the handle for SystemRoot.

The FILE_OBJECT is a special data structure used by the operating system to manage access to device objects and so contains a pointer to the related device object.

The Method for Protecting the Festi Registry Key

Festi also hides a registry key corresponding to the registered kernel-mode driver using a similar method. Located in HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services, the registry key contains Festi’s driver type and the path to the driver’s image on the filesystem. This makes it vulnerable to detection by security software, so Festi must hide the key.

To do so, Festi first hooks the ZwEnumerateKey, a system service that queries information on a specified registry key and returns all of its subkeys, by modifying the System Service Descriptor Table (SSDT), a special data structure in the operating system kernel that contains addresses of the system service handlers.

The Festi Network Communication Protocol

The data is obfuscated with a simple encryption algorithm before being sent over the network. The Python implementation of the encryption algorithm is shown below:

1
2
3
4
key = (0x17, 0xFB, 0x71, 0x5C)
def decr_data(data):
for ix in range(len(data)):
data[ix] ^= key[ix % 4]

The malware uses a rolling XOR algorithm with a fixed 4-byte key.

Bypassing Security and Forensics Software

To send and receive packets, the malware opens a handle to the \Device\Tcp and \Device\Udp devices depending on the protocol type being used, employing a rather interesting technique to acquire the handle without drawing the attention of security software.

The most common ways for security software t monitor access to the device objects are:

  • Hooking the ZwCreateFile system service handler to intercept all attempts to open the devices
  • Attaching to \Device\Tcp or \Device\Udp in order to intercept all IRP requests sent

Festi bypasses both techniques to establish a connection with a remote host over the network.

First, instead of using the system implementation of the ZwCreateFile system service, Festi implements its own system service with almost the same functionality as the original one.

Festi manually creates a file object to communicate with the device being opened and sends an IRP_MJ_CREATE request directly to the transport driver. Thus, all the devices attached to \Device\Tcp or \Device\Udp will miss the request, and the operation goes unnoticed by security software.

On the left side of the figure, it demonstrates how an IRP is normally processed. The IRP packet goes through the complete driver stack, and all the drivers hooked within it, including the security software, receive the IRP packet and inspect its content. The right side illustrates how Festi instead sends the IRP packet directly to the target driver, bypassing all the intermediate ones.

To send a request directly to \Device\Tcp or \Device\Udp, the malware requires pointers to the corresponding device objects. The fragment of code below shows the implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
RtlInitUnicodeString(&DriverName, L"\\Driver\\Tcpip");
RtlInitUnicodeString(&tcp_name, L"\\Driver\\Tcpip");
RtlInitUnicodeString(&udp_name, L"\\Driver\\Udpip");
if (!ObReferenceObjectByName(&DriverName, 64, 0, 0x1F01FF, IoDriverObjectType, 0, 0, &TcpipDriver))
{
DevObj = TcpipDriver->DeviceObject;
while (DevObj) // itegrate through DEVICE_OBJECT linked list
{
if (!ObQueryNameString(DevObj, &Objname, 256, &v8))
{
if (RtlCompareUnicodeString(&tcp_name, &Objname, 1u))
{
if (!RtlCompareUnicodeString(&udp_name, &Objname, 1u))
{
ObfReferenceObject(DevObj);
this->DeviceUdp = DevObj; // Save pointer to \Device\UDP
}
else
{
ObfReferenceObject(DevObj);
this->DeviceTcp = DevObj; // Save pointer to \Device\Tcp
}
}
}

DevObj = DevObj->NextDevice; // get pointer to next DEVICE_OBJECT in the linked list
}

ObfDereferenceObject(TcpipDriver);
}

Festi obtains a pointer to the tcpip.sys driver object by executing the ObReferenceObjectByName routine, an undocumented system routine, and passing as a parameter a pointer to a Unicode string with the target driver’s name. Then the malware iterates through the list of device objects corresponding to the driver object and compares its names with \Device\Tcp and \Device\Udp.

When the malware obtains a handle for the opened device in this way, it uses the handle to send and receive data over the network. Though Festi is able to avoid security software.

Note: This highlights how powerful a malware developed by skilled authors can be.

The Domain Generation Algorithm for C&C Failure

The author implemented a Domain Generation Algorithm (DGA). It generates pseudorandom domains to make the botnet resilient to takedown attempts.

Note: In the analysis article of CryptoLocker, the author also implemented DGA for this purpose.

Malicious Functionality

Festi downloads plug-ins from C&C servers. The servers provide the plug-ins below:

  • BotSpam.sys: Sending spam emails
  • BotDos.sys: Performing DDoS attacks
  • BotSocks.sys: Providing proxy services

The Spam Module

The figure below shows interactions below a Festi bot and a Festi C&C server.

The DDoS Engine

The BotDos.sys plug-in allows the bot to perform DDoS attacks against specified hosts. The plug-in supports several types of DDoS attacks against remote hosts:

  • TCP Flood
  • UDP Flood
  • DNS Flood
  • HTTP Flood

Festi Proxy Plug-in

The BotSocks.sys plug-in provides remote proxy service to the attacker by implementing the SOCKS server over the TCP and UDP protocols. The SOCKS server establishes a network connection to another target server on behalf of a client, then routes all traffic back and forth between the client and the target server.

Cybercriminals may use such a service for anonymization.

Chapter 3 - Observing Rootkit Infections

“If you shame attack research, you misjudge its contribution. Offense and defense aren’t peers. Defense is offense’s child”.

Methods of Interception

The rootkit must intercept control at particular points in the operating system to prevent the anti-rootkit tools from launching or initializing. These points of interception are abundant, present in both standard OS mechanisms and non-documented ones.

In Windows, every kernel-mode driver has a dedicated entry in the system registry, located under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services key. This entry specifies the name of the driver, the driver type, the location of the driver image on disk, and when the driver should be loaded (on demand, at boot time, at system initialization, etc.).

Rootkits frequently abuse the PsSetLoadImageNotifyRoutine routine to inject a malicious payload into the user-mode address of target processes.

Chapter 4 - Evolution of the Bootkit

From this point, this book is going to introduce bootkit.

Note: The title of “first bootkit” is usually bestowed upon Creeper, while Reaper was well-known to be the first antivirus.

Boot Sector Infectors

Boot Sector Infectors (BSIs) were among the earliest bootkits. They were first discovered in the days of MS-DOS, the non-graphical operating system that proceeded Windows, when the PC BIOS’s default behavior was to attempt to boot from whatever disk it found in the floppy drive.

The Evolution of Bootkits

The End of the BSI Era

As operating systems became more sophisticated, pure BSIs began to confront some challenges. Newer versions of operating systems replaced the BIOS-provided interrupts used to communicate with disks that had OS-specific drivers. As a result, once the OS was booted, the BSIs could no longer access BIOS interrupts and so could not infect other disks in the system.

The Kernel-Mode Code Signing Policy

Bootkit technology had to undergo major revision with the introduction of Microsoft’s Kernel-Mode Code Signing Policy in Windows Vista and later 64-bit versions of Windows, which turned the table on attackers by incorporating a new requirement for kernel-mode drivers.

The first group operates entirely within user mode and relies on built-in Microsoft Windows methods for legitimately disabling the signing policy in order to debug and test drivers. The OS provides an interface for temporarily disabling driver image authentication or enabling test signing by using a custom certificate to verify the digital signature of the drivers.

The second group attempts to exploit a vulnerability in the system kernel or a legitimate third-party driver with a valid digital signature, which allows the malware to penetrate into kernel mode.

The third group targets the OS bootloader in order to modify the OS kernel and disable the Kernel-Mode Code Signing Policy.

The forth group aims to compromise system firmware. It attacks target firmware rather than bootloader components.

Note: In practice, the third method, compromising the boot process, is the most common, because it allows for a more persistent attack.

The Rise of Secure Boot

Today, computers increasingly ship with functional Secure Boot protection. Secure Boot is a security standard designed to ensure the integrity of the components involved in the boot process.

Just as Microsoft’s Kernel-Mode Code Signing Policy eradicated kernel-mode rootkits and initiated a new era of bootkits, Secure Boot is currently creating obstacles for modern bootkits.

Chapter 5 - Operating System Boot Process Essentials

Before diving into how bootkits are built and how they behave, it is essential to understand how the boot process works. Therefore, this chapter introduces more important bootkit-related aspects of Microsoft Windows boot process.

High-Level Overview of the Windows Boot Process

The figure below shows the general flow of the modern boot process. Almost any part of the process can be attacked by a bootkit, but the most common target are the Basic Input/Output System (BIOS) initialization, the Master Boot Record (MBR), and the operating system bootloader.

The Legacy Boot Process

In 1980s to 2000s, the procedure below shows how the boot process is normally executed:

  1. Power on (a cold boot)
  2. Power supply self-test
  3. ROM BIOS execution
  4. ROM BIOS test of hardware
  5. Video test
  6. Memory test
  7. Power-On Self-Test (POST), a full hardware check
  8. Test of the MBR at the first sector of the default boot drive
  9. MBR execution
  10. Operating system file initialization
  11. Base device driver initializations
  12. Device status check
  13. Configuration file reading
  14. Command shell loading
  15. Shell’s startup command file execution

The Windows Boot Process

The figure below shows a high-level picture of the Windows boot process and the components involved, applicable to Windows versions Vista and higher. Each block in the figure represents modules that are executed and given control during the boot process, in order from top to bottom.

BIOS and the Preboot Environment

The BIOS performs basic system initialization and a POST to ensure that the critical system hardware is working properly.

The disk service is accessible through a special handler known as the interrupt 13h handler, or simply INT 13h. Bootkits will often target the disk service by tampering with its INT 13h; they do this in an effort to disable or circumvent OS protections by modifying operating system and boot components that are read from the hard drive during system startup.

The Master Boot Record

The MBR is a data structure containing information on hard drive partitions and the boot code. Its main task is to determine the active partition of the bootable hard drive, which contains the instance of the OS to load.

1
2
3
4
5
typedef struct _MASTER_BOOT_RECORD {
BYTE bootCode[0x1BE]; // space to hold actual boot code
MBR_PARTITION_TABLE_ENTRY partitionTable[4];
USHORT mbrSignature; // set to 0xAA55 to indicate PC MBR format
} MASTER_BOOT_RECORD, *PMASTER_BOOT_RECORD;

Note: Total size = 0x1BE (446 byte) + 4 x ENTRY (16 byte) + 2 byte (1 SHORT = 2 byte) = 446 + 64 + 2 = 512 byte

the MBR boot code is restricted to just 446 bytes (0x1BE in hexadecimal). Next, the MBR parses the partition table, in order to locate the active partition, reads the Volume Boot Record (VBR) in its first sector, then transfer control to it.

Partition Table

1
2
3
4
5
6
7
8
typedef struct _MBR_PARTITION_TABLE_ENTRY {
BYTE status; // active? 0 = no, 128 = yes
BYTE chsFirst[3]; // starting sector number
BYTE type; // OS type indicator code
BYTE chsLast[3]; // ending sector number
DWORD lbaStart; // first sector relative to start of disk
DWORD size; // number of sectors in partiton
} MBR_PARTITION_TABLE_ENTRY, *PMBR_PARTITION_TABLE_ENTRY;

The status field signifies whether the partition is active.

The type field lists the partition type. The most common types are:

  • EXTENDED MBR partition type
  • FAT12 filesystem
  • FAT16 filesystem
  • FAT32 filesystem
  • IFS (Installable File System used for the installation process)
  • LDM (Logical Disk Manager for Microsoft Windows NT)
  • NTFS (the primary Windows filesystem)

Note that a type of 0 means unused.

The fields lbaStart and size define the location of the partition on disk, expressed in sectors. The lbaStart field contains the offset of the partition from the beginning of the hard drive, and the size field contains the size of the partition.

Microsoft Windows Drive Layout

The figure below shows the typical bootable hard drive layout of a Microsoft Windows systm with two partitions.

The Bootmgr partition contains the bootmgr module and some other OS boot components, while the OS partition contains a volume that hosts the OS and user data.

The Volume Boot Record and Initial Program Loader

The hard drive may contain several partitions hosting multiple instances of different operating systems, but only one partition should normally be marked as active.

The MBR does not contain the code to parse the particular filesystem used on the active partition, so it reads and executes the first sector of the partition, the VBR.

The code block below shows the layout of the VBR, which is composed of BIOS_PARAMETER_BLOCK_NTFS and BOOTSTRAP_CODE structures. The layout of the BIOS_PARAMETER_BLOCK structure is used for the specifying the volume’s filesystem. The BIOS_PARAMETER_BLOCK_NTFS and VOLUME_BOOT_RECORD structures correspond to the NTFS volume.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef struct _BIOS_PARAMETER_BLOCK_NTFS {
WORD SectorSize;
BYTE SectorPerCluster;
WORD ReservedSectors;
BYTE Reserved[5];
BYTE MediaId;
BYTE Reserved2[2];
WORD SectorsPerTrack;
WORD NumberOfHeads;
DWORD HiddenSectors;
BYTE Reserved3[8];
QWORD NumberOfSectors;
QWORD MFTStartingCluster;
QWORD MFTMirrorStartingCluster;
BYTE ClusterPerFileRecord;
BYTE Reserved4[3];
BYTE ClusterPerIndexBuffer;
BYTE Reserved5[3];
QWORD NTFSSerial;
BYTE Reserved6[4];
} BIOS_PARAMETER_BLOCK_NTFS, *PBIOS_PARAMETER_BLOCK_NTFS;
1
2
3
4
typedef struct _BOOTSTRAP_CODE{
BYTE bootCode[420]; // boot sector machine code
WORD bootSectorSignature; // 0x55AA
} BOOTSTRAP_CODE, *PBOOTSTRAP_CODE;
1
2
3
4
5
6
7
8
typedef struct _VOLUME_BOOT_RECORD{
WORD jmp;
BYTE nop;
DWORD OEM_Name
DWORD OEM_ID; // NTFS
BIOS_PARAMETER_BLOCK_NTFS BPB;
BOOTSTRAP_CODE BootStrap;
} VOLUME_BOOT_RECORD, *PVOLUME_BOOT_RECORD;

Notice that the VBR starts with a jmp instruction, which transfers control of the system to the VBR code. The VBR code in turn reads and executes the Initial Program Loader (IPL) from the partition, the location of which is specified by the HiddenSectors field.

The layout of the VBR is summarized below:

The VBR essentially consists of the following components:

  • The VBR code responsible for loading the IPL
  • The BIOS parameter block
  • Text strings displayed to a user if an error occurs
  • 0xAA55 a 2-byte signature of the VBR

The bootmgr Module and Boot Configuration Data

The IPL reads and loads the OS boot manager’s bootmgr module from the filesystem, Once the IPL runs, bootmgr takes over the boot process.

Real Mode vs. Protected Mode

When a computer is first powered on, the CPU operates in real mode, a legacy execution mode that uses a 16-bit memory model in which each byte in RAM is addressed by a pointer consisting of two words (2 bytes): segment_start:segment_offset.

The real-mode addressing scheme allows the use of only a small amount of the available system RAM.

The bootmgr module consists of 16-bit real-mode code and a compressed PE image, which, when uncompressed, is executed in protected mode.

When winload.exe receives control of the operating system boot, it enables paging in protected mode and then loads the OS kernel image and its dependencies, including:

  • bootvid.dll: A library for video VGA support at boot time
  • ci.dll: The code integrity library
  • clfs.dll: The common logging filesystem driver
  • hal.dll: The hardware abstraction layer library
  • Kdcom.dll: The kernel debugger protocol communications library
  • pshed.dll: The platform-specific hardware error driver

Note: In order to read all the components from the hard drive, winload.exe uses the interface provided by bootmgr. This interface relies on the BIOS INT 13h disk service. Therefore, if the INT 13h handler is hooked by a bootkit, the malware can spoof all data read by winload.exe

Chapter 6 - Boot Process Security

This chapter fucuses on two important security mechanisms implemented in Microsoft Windows kernel: the Early Launch Anti-Malware (ELAM) module, introduced in Windows 8, and the Kernel-Mode Code Signing Policy, introduced in WIndows Vista.

Both mechanism were designed to prevent the execution of unauthorized code in the kernel address space, in order to make it harder for rootkits to compromise a system.

The Early Launch Anti-Malware Module

The Early Launch Anti-Malware (ELAM) module is a detection mechanism for Windows systems that allows third-party security software, such as anti-virus software, to register a kernel-mode driver that is guarateed to execute every early in the boot process, before any other third-party driver is loaded.

API Callback Routines

The ELAM driver registers callback routines that the kernel uses to evaluate data in the system registry hive and boot-start drivers.

The Windows kernel registers and unregisters these callbacks by implementing the following API routines:

  • CmRegisterCallbackEx and CmUnRegisterCallback: Register and unregister callbacks for monitoring registry data
  • IoRegisterBootDriverCallback: Register and unregister callbacks for boot-start drivers

These callback routines use the prototype EX_CALLBACK_FUNCTION shown below:

1
2
3
4
5
NTSTATUS EX_CALLBACK_FUNCTION(
IN PVOID CallbackContext,
IN PVOID Argument1, // callback type
IN PVOID Argument2 // system-provided context structure
);

The first parameter receives a context from the ELAM driver has executed one of the aforementioned callback routines to register a callback.

The second parameter provides the callback type, which may be either of the following for the boot-start drivers:

  • BdCbStatusUpdate
  • BdCbInitializeImage

The third argument at provides information that the operating system uses to classify the boot-start driver as known good (drivers known to be legitimate and clean), unknown (drivers that ELAM can’t classify), and known bad (drivers known to be malicious).

The ELAM driver does not receive the image’s base address, nor can it access the binary image on the hard drive **because the storage device driver stack is not yet initialized.

Consequently, the protection for the drivers is not very effective at this stage.

ELAM Policy

Windows decides whether to load known bad or unknown drivers based on the ELAM policy specified in registry key: HKLM\System\CurrentControlSet\Control\EarlyLaunch\DriverLoadPolicy.

Policy Name Policy Value Description
PNP_INITIALIZE_DRIVERS_DEFAULT 0x00 Load known good drivers only
PNP_INITIALIZE_UNKNOWN_DRIVERS 0x01 Load known good and unknown drivers only
PNP_INITIALIZE_BAD_CRITICAL_DRIVERS 0x03 Load known good, unknown, and known bad critical drivers (default setting)
PNP_INITIALIZE_BAD_DRIVERS 0x07 Load all drivers

How Bootkits Bypass ELAM

ELAM gives security software an advantage against rootkit threats but not against bootkits, nor was it designed to. ELAM can monitor only legitimately loaded drivers, but most bootkits load kernel-mode drivers that use undocumented operating system features.

Most bootkits load their kernel-mode code in the middle of kernel initialization, once all OS subsystem have been initialized but before ELAM is executed.

Microsoft Kernel-Mode Code Signing Policy

The Kernel-Mode Code Signing Policy protects the Windows operating system by imposing code-signing requirements for modules meant to be loaded into the kernel address space.

Attackers can disable the entire logic of on-load signature verification by manipulating a few vaariables that correspond to startup configuration options.

Kernel-Mode Drivers Subject to Integrity Checks

On 64-bit systems, all kernel-mode modules, regardless of type, are subject to integrity checks.

On 32-bit systems, the signing policy applies only to boot-start and media drivers; other drivers are not checked (PnP device installation enforces an install-time signing requirement).

Location of Driver Signatures

The embedded driver signature within a PE file, such as a boot-start driver, is specified in the IMAGE_DIRECTORY_DATA_SECURITY entry in the PE header data directories.

1
2
3
4
5
6
7
8
9
10
11
12
13
BOOL ImageEnumerateCertificates(
_In_ HANDLE FileHandle,
_In_ WORD TypeFilter,
_Out_ PDWORD CertificateCount,
_In_out_ PDWORD Indices,
_In_opt_ DWORD IndexCount
);
BOOL ImageGetCertificateData(
_In_ HANDLE FileHandle,
_In_ DWORD CertificateIndex,
_Out_ LPWIN_CERTIFICATE Certificate,
_Inout_ PDWORD RequiredLength
);

The Kernel-Mode Code Signing Policy has increased the security resilience of the system, but it does have its limitations.

In addition to the Kernel-Mode Code Signing Policy, Microsoft Windows has another type of signing policy: the Plug and Play Device Installation Signing Policy. It is important not to confuse the two.

System administrators can disable the PnP policy, allowing Pnp driver packages to be installed on a system without proper signatures. Also, note that the policy is applied only when the driver package is installed, not when the drivers are loaded.

The Legacy Code Integrity Weakness

The logic in the Kernel-Mode Code Signing Policy responsible for enforcing code integrity is shared between the Windows kernel image and the kernel-mode library ci.dll. The kernel uses this library to verify the integrity of all modules being loaded into the kernel address space.

The key weakness of the signing process lies in a single point of failure in this code.

In Microsoft Windows Vista and 7, a single variable in the kernel image lies at the hart of this mechanism and determines whether integrity checks are enforced. It looks like this:

1
BOOL nt!g_CiEnabled

The ci.dll Module

The kernel-mode library ci.dll, which is responsible for enforcing code integrity policy, contains the following routines:

  • CiCheckSignedFile: Verify the digest and validate the digital signature
  • CiFindPageHashesInCatalog: Validate whether a verified system catalog contains the digest of the first memory page of the PE image
  • CiFindPageHashesInSignedFile: Verify the digest and validate the digital signature of the first memory page of the PE image
  • CiFreePolicyInfo: Free memory allocated by the functions CiVerifyHashInCatalog, CiCheckSignedFile, CiFindPageHashesInCatalog and CiFindPageHashesInSignedFile
  • CiGetPEInformation: Create an encrypted communication channel between the caller and the ci.dll module
  • CiInitialize: Initialize the capability of ci.dll to validate PE image file integrity
  • CiVerifyHashInCatalog: Validate the digest of the PE image contained within a verified system catalog

The routine CiInitialize is the most important one.

1
2
3
4
5
NTSTATUS CiInitialize(
IN ULONG CiOptions,
PVOID Parameters,
OUT PVOID g_CiCallbacks
);

The CiInitialize routine also performs a self-check to ensure that no one has tampered with it. The routine then proceeds to verify the integrity of all the drivers in the boot-driver list, which essentially contains boot-start drivers and their dependencies.

Once initialization of the ci.dll library is complete, the kernel uses callbacks in the g_CiCallbacks buffer to verify the integrity of the modules.

Defensive Changes in Windows 8

With Windows 8, Microsoft made a few changes designed to limit the kinds of attacks possible in this scenario.

First, Microsoft deprecated the kernel variable nt!g_CiEnabled, leaving no single point of control over the integrity policy in the kernel image as in earlier versions of Windows.

Windows 8 also changed the layout of the g_CiCallbacks buffer.

Note: More information on the implementation details of the ci.dll module can be found at https://github.com/airbus-seclab/warbirdvm.

Secure Boot Technology

Secure Boot technology was introduced in Windows 8 to protect the boot against bootkit infection. It leverages the Unified Extensible Firmware Interface (UEFI) to block the loading and execution of any boot application or driver without a valid digital signature in order to protect the integrity of the operating system kernel, system files, and boot-critical drivers.

Virtualization-Based Security in Windows 10

Up until Windows 10, code integrity mechanisms were part of the system kernel itself. That essentially means that the integrity mechanism runs with the same privilege level that it is trying to protect.

To increase the effectiveness of the code integrity mechanism, Windows 10 introduced two new features:

  1. Virtual Secure Mode
  2. Device Guard

Both of which are based on memory isolation assisted by hardware. This technology is generally referred to as Second Level Address Translation, and it is included in both Intel (Extended Page Tables, or EPT) and AMD (Rapid Virtualization Indexing, or RVI) CPUs.

Second Level Address Translation

Windows has supported Second Level Address Translation (SLAT) since Windows 8 with Hyper-V (a Microsoft hypervisor).

Hyper-V uses SLAT to perform memory management (for example, access protection) for virtual machines and to reduce the overhead of translating guest physical addresses (memory isolated by virtualization technologies) to real physical addresses.

Virtual Secure Mode and Device Guard

Virtual Secure Mode (VSM) virtualization-based security first appeared in Windows 10 and is based on Microsoft’s Hyper-V.

VSM isolation makes it impossible to use vulnerable legitimate kernel-mode drivers to disable code integrity.

Device Guard technology leverages VSM to prevent untrusted code from running on the system.

Device Guard Limitations on Driver Development

Device Guard imposes specific requirements and limitations on the driver development process, and some existing drivers will not run correctly with it active.

Chapter 7 - Bootkit Infection Techniques

This chapter targets MBR infection techniques and VBR/Initial Program Loader (IPL) infection techniques.

MBR Infection Techniques

Approaches based on MBR modifications are the most common infection techniques used by bootkits to attack the Windows boot process. Most MBR infection techniques directly modify either/both the MBR code or/and MBR data (such as the partition table).

MBR Code Modification: The TLD4 Infection Technique

TDL4 reuses the notoriously advanced evasion and anti-forensic techniques of its rootkit predecessor, TDL3, but has the added ability to bypass the Kernel-Mode Code Signing Policy and infect 64-bit Windows systems.

On 32-bit systems, the TDL3 rootkit was able to persist through a system reboot by modifying a boot-start kernel-mode driver. However, the mandatory signature checks introduced in 64-bit systems prevented the infected driver from being loaded, rendering TDL3 ineffective.

Infecting the System

TDL4 infects the system by overwriting the MBR of the bootable hard drive with a malicious MBR.

TDL4 stores the original MBR so that it can be loaded later, once infection has taken place, and the system will seemingly boot as normal.

TDL4 exploits the MS-10-092 vulnerability in the Windows Task Scheduler service to elevate its privileges. It then modifies the symbolic link \??\PhysicalDriveXX to infect the MBR.

Once all of its components are installed, TDL4 forces the system to reboot by executing the NtRaiseHardError native API.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
NTSYSAPI 
NTSTATUS
NTAPI

NtRaiseHardError(
IN NTSTATUS ErrorStatus,
IN ULONG NumberOfParameters,
IN PUNICODE_STRING UnicodeStringParameterMask OPTIONAL,
IN PVOID *Parameters,
IN HARDERROR_RESPONSE_OPTION ResponseOption,
OUT PHARDERROR_RESPONSE Response
);

//Source: http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FError%2FNtRaiseHardError.html

The code passes OptionShutdownSystem as its fifth parameter, which puts the system into a BSOD.

Note: This is also how Petya and Petya are implemented

Disabling the Code Integrity Checks

In order to replace the original version of kdcom.dll with the malicious DLL on Windows Vista and later versions, the malware needs to disable the kernel-mode code integrity checks.

Encrypting the Malicious MBR Code

The code below shows a part of the malicious MBR code in the TDL4 bootkit. Notice that the malicious code is encrypted in order to avoid detection by static analysis, which uses static signatures.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    xor ax, ax
mov ss, ax
mov sp, 7C00h
mov es, ax
mov ds, ax
sti
pusha
mov cx, 0CFh ; size of decrypted data
mov bp, 7C19h ; offset to encrypted data

decrypt_routine:
ror byte ptr [bp+0], cl
inc bp
loop decrypt_routine

; --------------------------------------

db 44h ; beginning of encrypted data
db 85h
db 0C7h
db 1Ch
db 0B8h
db 26h
db 04h
--snip--

MBR Partition Table Modification

One variant of TDL4, known as Olmasco, demonstrates another approach to MBR infection: modifying the partition table rather than the MBRcode.

Olmasco first create an unallocated partition at the end of the bootable hard drive, then create a hidden partition in the same place by modifying a free partition table entry, number 2, in the MBR partition table.

This route of infection is possible because the MBR contains a partition table with entries beginning at offset 0x1BE consisting of four 16-byte entries, each decribing a corresponding partition (the array of MBR_PARTITION_TABLE_ENTRY is shown back in the image below) on the hrad drive.

Thus, the hard drive can have no more than four primary partitions, with only one marked as active.

VBR/IPL Infection Techniques

Sometimes security software checks only for unauthorized modifications on the MBR, leaving the VBR and IPL uninspected.

All known VBR infection techniques fall into one of two groups: IPL modification and BIOS parameter block (BPB) modifications

IPL Modifications: Rovnix

Instead of overwriting the MBR sector, Rovnix modifies the IPL on the bootable hard drive’s active partition and the NTFS bootstrap code.

VBR Infection: Gapz

The Gapz bookit infects the VBR of the active partition rather than the IPL. Gapz is a remarkably stealthy bootkit because it infects only a few bytes of the original VBR, modifying the HiddenSectors field and leaving all other data and code in the VBR and IPL untouched.

Chapter 8 - Static Analysis of a Bootkit Using IDA Pro

This chaper introduces the basic concepts of bootkit static analysis with IDA Pro.

Analyzing the Bootkit MBR

The IDA script below decrypts the MBR code:

1
2
3
4
5
6
7
8
9
10
import idaapi

start_ea = 0x7C19
encr_size = 0xCF

for ix in xrange(encr_size):
byte_to_decr = idaapi.get_byte(start_ea + ix)
to_rotate = (0xCF - ix) % 8
byte_decr = (byte_to_decr >> to_rotate) | (byte_to_decr << (8 - to_rotate))
idaapi.patch_byte(start_ea + ix, byte_decr)

Analyzing the BIOS Disk Service

Another unique aspect of the preboot environment is the BIOS disk service, an API used to communicate with a hard drive.

The BIOS disk service is accessible via an INT 13h instruction. In order to perform I/O operations, software passes I/O parameters through the processor registers and executes the INT 13h instruction, which transfer control to the appropriate handler.

The I/O operation code, or identifier, is passed in the ah register——the higher-order part of the ax register. The register dl is used to pass the index of the disk in question. The processor’s carry flag (CF) is used to indicate whether an error has occurred during execution of the service: if CF is set to 1, an error has occurred and the detailed error code is returned in the ah register.

This BIOS convention for passing arguments to a function predates the modern OS system call conventions.

This INT 13h interrupt is an entry point to the BIOS disk service, and it allows software in the preboot environment to perform basic I/O operations on disk devices, like hard drive, floppy drives, and CD-ROMs:

Operation code Operation description
2h Read sectors into memory
3h Write disk sectors
8h Get drive parameters
41h Extensions installation check
42h Extended read
43h Extended write
48h Extended get drive parameters

The extended operations can use an addressing scheme based on Logical Block Addressing (LBA), whereas the legacy operations rely solely on a legacy Cylinder Head Sector (CHS).

In the case of the LBA-based scheme, sectors are enumerated linearly on the disk, beginning with index 0, whereas in the CHS-based scheme, each sector is addressed using tuple (c, h, s), where c is the cylinder number, h is the head number, and s is the number of the sector.

Although bootkits may use either group, almost all modern hardware supports the LBA-based addressing scheme.

Obtaining Drive Parameters to Locate Hidden Storage

1
2
3
4
5
6
7
8
mov ah, 48h                             ; Extended get drive parameter
mov si, 7CF9h ; Upon execution, this routine fills a special structure
; called EXTENDED_GET_PARAMS that provides the drive parameter
; The address of this structure is stored in the si register.
mov dsLdrive_param.bResultSize, 1Eh
int 13h ; DISK - IBM/MS Extension
; GET DRIVE PARAMETERS
; (DL - drive, DS:SI - buffer)

The small size of the MBR (512 bytes) restricts the functionality of the code that can be implemented within it. For this reason, the bootkit loads additional code to execute, called a malicious boot loader, which is placed in hidden storage at the end of the hard drive.

To obtain the corrdinates of the hidden storage on the disk, the MBR code uses the exneded “get drive parameters” operation, which returns information about the hard drive’s size and geometry. This information allows the bootkit to compute the offset at which the additional code is located on the hard drive.

Examining EXTENDED_GET_PARAMS

The EXTENDED_GET_PARAMS routing is provided below:

1
2
3
4
5
6
7
8
9
typedef struct _EXTENDED_GET_PARAMS {
WORD bResultSize; // Size of the result
WORD InfoFlags; // Information flags
DWORD CylNumber; // Number of physical cylinders on drive
DWROD HeadNumber; // Number of physical heads on drive
DWORD SectorsPerTrack; // Number of sectors per track
QWORD TotalSectors; // Total number of sectors on drive
WORD BytesPerSector; // Bytes per sector
}

The only fields the bootkit actually looks at in the returned structure are the number of sectors on the hard drive and the size of the disk sector in bytes.

Reading Malicious Boot Loader Sectors

Once the bootkit has obtained the hard drive parameters and calculated the offset of the hidden storage, the bootkit MBR code reads this hidden data from the disk with the extended read operation of he BIOS disk service.

This data is the next-stage malicious boot loader intended to bypass OS security checks and load a malicious kernel-mode driver.

1
2
3
4
5
6
7
8
9
10
read_loop:
call read_sector
mov si, 7D1Dh
mov cx, ds:word_7D1B
rep movsb
mov ax, ds:word_7D1B
test ax, ax
jnz short read_loop
popa
jmp far boot_loader

In the read_loop, this code repeatedly reads sectors from the hard drive using the routine read_sector and stores them in the previously allocated memory buffer. Then the code transfer control to this malicious boot loader by executing a jmp far instruction.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
read_sector:
pusha
mov ds:disk_address_packet.PacketSize, 10h ; Size of the structure
mov byte ptr ds:disk_address_packet.SectorsToTransfer, 1 ; Number of sectors to transfer
push cs
pop word ptr ds:disl_address_packet.TargetBuffer+2
mov word ptr ds:disk_address_packet.TargetBuffer, 7D17h ; Address of the buffer to store the result
push large [dword ptr ds:drive_param.TotalSectors_l]
pop large [ds:disk_address_packet.StartLBA_l] ; Address of the sectors to read
push large [dword ptr ds:drive_param.TotalSectors_h]
pop large [ds:disk_address_packet.StartLBA_h] ; Address of the sectors to read
inc eax
sub ds_disk_address_packet.StartLBA_l, eax
sbb ds:disk_address_packet.StartLBA_h, 0
mov ah, 42h
mov si, 7CE9h ; Sturecture address
mov dl, ds:drive_no
int 13h ; DISK - IBM/MS Extension
; EXTENDED READ
; (DL - drive, DS:SI - disk address packet)

popa
retn
endp

The BIOS disk service uses DISK_ADDRESS_PACKET to uniquely identify which sectors to read from the hard drive.

1
2
3
4
5
6
7
typedef struct _DISK_ADDRESS_PACKET {
BYTE PacketSize; // Size of the structure
BYTE Reserved;
WORD SectorsToTransfer; // Number of sectors to read/write
DWORD TargetBuffer; // segment:offset of the data buffer
QWORD StartLBA; // LBA address of the starting sector
} DISK_ADDRESS_PACKET, *PDISK_ADDRESS_PACKET;

Analyzing the Infected MBR’s Partition Table

The MBR partition table is a common target of bootkits because the data it contains (although limited) plays a crucial part in the boot process’s logic.

The partition table is located at the offset 0x1BE in the MBR and consists of four entries, each 0x10 bytes in size. It lists the partitions available on the hard drive, describes their type and location, and specifies where the MBR code should transfer control when it’s done.

Usually, the sole purpose of legitimate MBR code is to scan this table fro the active partition. Bootkit might be able to intercept this execution flow at the very early boot stage by simply manipulating the information contained in the table, without modifying the MBR code itself.

This illustrates an important principle of bootkit and rootkit design: if you can manipulate some data surreptitiously enough to bend the control flow, then that approach is preferred to patching the code. This saves the malware programmer the effort of testing new, altered code.

VBR Analysis Techniques

Analyzing the IPL

The main purpose of the VBR is to locate the Initial Program Loader (IPL) and to read it into RAM. The location of the IPL on the hard drive is specified in the BIOS_PARAMETER_BLOCK_NTFS structure.

The HiddenSectors field, which stores the number of sectors from the beginning of the hard drive to the beginning of the NTFS volume, defines the actual location of the IPL. So the VBR code loads the IPL by fetching the contents of the HiddenSectors field, incrementing the fetched value by 1, and then reading 0x2000 bytes, which correspnds to 16 sectos, from the calculated offset. Once the IPL is loaded from disk, the VBR transfers control to it.

The code below shows a part of the BIOS parameter block structure of TDL4 rootkit:

1
2
3
4
5
6
7
8
9
10
dw 200h     ; SectorSize
db 8 ; SectorsPerCluster
db 3 dup(0) ; Reserved
dw 0 ; RootDirectoyIndex
dw 0 ; NumberOfSectorsFAT
db 0F8h ; MediaId
db 2 dup(0) ; Reserved 2
dw 3Fh ; SectosPerTrack
dw 0FFh ; NumberOfHeads
dd 800h ; HiddenSectors

Evaluating Other Bootkit Components

Once the IPL receives control, it loads bootmgr, which is stored in the filesystem of the volume. After this, other bootkit components, such as malicious boot loaders and kernel-mode drivers, may kick it.

Malicious Boot Loaders

Malicious boot loaders continute an important part of bootkits. Their main purposes are to survive through the CPU’s execution mode switching, bypass OS security checks, and load malicious kernel-mode drivers.

Bootkits store their boot loaders in hidden storage areas located either at the end of the hard drive, where there is usually some unused disk space, or in free disk space between partitions, if there is any.

Advanced IDA Pro Usage: Writing a Custom MBR Loader

Chapter 9 - Bootkit Dynamic Analysis: Emulation and Virtualization

This is often true for bootkits that contain encrypted components for which decryption is problematic or for bootkits like Rovnix.

Emulation with Bochs

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.

Creating a Bochs disk image with the bximage tool

Infecting the Disk Image

The Python pseudo-code below shows how bootkit infects the disk image:

1
2
3
4
5
6
7
8
9
10
# 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()

Using the Bochs Internal Debugger

Chapter 10 - An Evolution of MBR and VBR Infection Techniques: Olmasco

In early 2011, the TDL4 family evolved into new-malware with infection tricks that had never before been seen in the wild. One example is Olmasco, a bootkit largely based on TDL4 but with a key difference: Olmasco infects the partition table of the MBR rather than the MBR code, allowing it to infect the system and bypass the Kernel-Mode Code Signing Policy while avoiding detection by increasingly savvy anti-malware software.

Creating a Bochs disk image with the bximage tool

Chapter 13 - The Rise of MBR Ransomware

This chapter focuses on two families that have received a lot of media attention: Petya and Satana.

Ransomware with Bootkit Functionality

Instead of encrypting user files in the filesystem, Petya and Satana encrypted parts of the hard drive to make the OS unbootable and displayed a message to victims demanding payment to restore the encrypted sectors.

Petya locked users out of their systems by encrypting the contents of the Master File Table (MFT) on the hard drive. The MFT is an essential, special data structure in the NTFS volume that contains information on all the files stored within it, like their location on the volume, their filenames, and other attributes.

It is primarily used an index for finding the locations of files on the hard drive. By encrypting the MFT, Petya ensured that files could not be located and that victims weren’t able to access files on the volume or even boot their system.

Petya is capable of infecting hard drives with either MBR-based partitions or GUID Partition Table (GPT) partitions.

To infect an MBR partitioning scheme, Petya first reads the MBR to calculate the amount of free space between the beginning of the hard drive and the beginning of the very first partition. This space is used to store the malicious bootloader and its configuration information.

Petya retrieves the starting sector number of the very first partition; if it starts at a sector with an index less than 60 (0x3C), it means there’s not enough space on the hard drive, so Petya stops the infection process and exits.

The malicious bootloader of Petya consists of two components: the malicious MBR code and second-stage bootloader.

Petya combines the partition table of the original MBR with the malicious MBR code, writing the result to the very first sector of the hard drive in place of the original MBR. The original MBR is XORed with a fixed byte value 0x37, and the result is written to sector 56.

Encrypting with the Malicious Bootloader Configuration Data

Petya writes the bootloader configuration data to sector 54 of the hard drive. The bootloader uses this data to complete the encryption of the hard drive’s sectors. The code below shows how the data is generated:

1
2
3
4
5
6
7
typedef struct _PETYA_CONFIGURATION_DATA {
BYTE EncryptionStatus;
BYTE SalsaKey[32];
BYTE SalsaNonce[8];
CHAR RansomURLs[128];
BYTE RansomCode[343];
} PETYA_CONFIGURATION, *PPETYA_CONFIGURATION_DATA;

The first parameter indicates that whether the MFT of the hard drive is encrypted or not. The malware clears that flag during the step 1 of the infection process.

SalsaNonce represents the initialization value (IV) used for encrypting the MFT.

Generating Cryptographic Keys

1
2
3
4
5
6
do
{
config_data->salsa20_key[2*i] = password[i] + 0x7A;
config_data->salsa20_key[2*i + 1] = 2 * password[i];
++i;
} while (i < 0x10);

Generating the Ransom Key

Only the attacker should be able to retrieve the password from the ransom key, so in order to protect it, Petya uses the ECC public key encryption scheme, which is embedded in the malware.

First, Petya generates a temporary ECC key pair, known as an ephemeral key, on the victim’s system to establish secure communication with the C&C server: ecc_ephemeral_pub and ecc_ephemeral_priv.

Next, it generates a shared secret (that is, a shared key) using the ECC Diffie-Hellman key agreement algorithm. One the victim’s computer, the shared secret is computed as:

Where ECDH is the Diffie-Hellman key agreement routine.

Encrypting the MFT

Chapter 14 - UEFI Boot vs. the MBR/VBR Boot Process

This chapter focuses on the specifics of the Unified Extensible Firmware Interface (UEFI) boot process, specifically on its differences from the legacy boot MBR/VBR infection.

The Unified Extensible Firmware Interface

UEFI is a specification that defines a software interface between an operating system and the firmware. It was originally developed by Intel to replace the widely divergent legacy BIOS boot software, which was also limited to 16-bit mode and thus unsuitable for new hardware.

For compatibility reasons, some UEFI-based firmware contains a Compatibility Support Module (CSM) to support the legacy BIOS boot process for previous generations of operating systems; however, Secure Boot cannot be supported under CSM.

Differences Between the Legacy BIOS and UEFI Boot Processes

From a security standpoint, the main differences in UEFI’s boot process derive from the aim of supporting Secure Boot: the flow logic of the MBR/VBR is eliminated and completely replaced by UEFI components.

The Boot Process Flow

The task of MBR-based legacy BIOS was merely to apply the necessary hardware configurations and then transfer control to each succeeding stage of the boot code, from boot code to MBR to VBR and finally to an OS bootloader (e.g., bootmgr and winload.exe).

On the other hand, the boot process in UEFI is substantially different. The MBR and VBR no longer exist; instead, UEFI’s own single piece of boot code is responsible for loading the bootmgr.

Disk Partitioning: MBR vs. GPT

Unlike the legacy BIOS, which uses an MBR-style partition table, UEFI supports the GUID Partition Table (GPT). The GPT is rather different from the MBR. MBR tables support only four primary or extended partition slots (with multiple logical partitions in an extended partition, if needed), whereas a GPT supports a much larger number of partitions.

To support the UEFI boot process, the new GPT partitioning scheme specifies a dedicated partition from which the UEFI OS bootloader is loaded (in the legacy MBR table, this role was played by an “active” bit flag set on primary partition). This special partition is referred to as the EFI system partition, and it is formatted with the FAT32 filesystem (although FAT12 and FAT16 are also possible). The path to this bootloader within the partition’s filesystem is specified in a dedicated nonvolatile random access memory (NVRAM) variable, also know as a UEFI variable.

Note: NVRAM is a small memory storage module, located on PC motherboards, that is used to store the BIOS and operating system configuration settings.

For Microsoft Windows, the path to the bootloader on a UEFI system looks like \EFI\Microsoft\Boot\bootmgfw.efi. The purpose of this module is to locate the operating system kernel loader——winload.efi for modern Windows versions with UEFI support——and transfer control to it.

The figure below shows the boot process flow for legacy BIOS vs. UEFI, skipping those MBR and VBR steps:

GUID Partition Table Specifics

This Protective MBR prevents legacy software such as disk utilities from accidentally destroying GUID partitions by marking the entire disk space as claimed by a single partition; legacy tools unaware of GPT do not mistake its GPT-partitioned parts for free space.

How can malware developers transfer control of the boot process to their malicious code in the GPT scheme? One idea is to modify EFI bootloaders before they transfer control to the OS kernel.

How UEFI Firmware Works

Chapter 15 - Contemporary UEFI Bootkits

The BIOS starts the initial stages for the hardware setup in the boot process, meaning the BIOS firmware level is the last boundary before hardware.

Ways to Infect the BIOS

Over the years, security researchers have identified many vulnerabilities that allow an attacker to modify the boot process with additiona malicious code. As of today, most of these have been fixed, but some hardware——even new hardware——can still be vulnerable to those old issues.

The following are different ways to infect UEFI firmware with a persistent rootkit or implant:

  • Modifying an unsigned UEFI Option ROM: An attacker can modify a UEFI DXE driver in some add-on cards (used for networks, storage, and so forth) to allow malicious code execution at the DXE stage.
  • Adding/modifying a DXE driver
  • Replacing the Windows Boot Manager (fallback bootloader)
  • Adding a new bootloader (bootkit.efi)

Chapter 16 - Contemporary UEFI Bootkits

Chapter 17 - How UEFI Secure Boot Works

Chapter 18 - Approaches to Analyzing Hidden Filesystems

Chapter 19 - BIOS/UEFI Forensics: Firmware Acquisition and Analysis Approaches

THANKS FOR READING