Weaponizing vulnerable driver for privilege escalation— Gigabyte Edition!

Hoang Bui
9 min readJun 29, 2019

--

End Result

Privilege Escalation

Priv Esc

Protected Process Light

PPL

Introduction

The end of June is approaching and I would like to pay my monthly blog tax.

As far as I remember, kernel land is seldomly touched by attackers (outside of kernel exploits) and I can see why. It is dangerous, undocumented, love to crash along with the steep learning curve to be able to utilize the kernel properly. However, I see it as the next frontier for both the blue and red team to gain an advantage over the other. With that being said, let’s hop into Weaponizing vulnerable driver for privilege escalation (and more) — Gigabyte Edition!

The Discovery

Let’s start with me saying that this is not a Windows bug but a bad practice by Gigabyte instead. I am referring to CVE-2018–19321 reported by SecureAuth’s Diego Juarez. You can read about it here. Again, thank you to Diego Juarez for documenting the exploit as well as providing the PoC code so that freeloaders like me can enjoy the fruits of your research ❤. However, without listing the rest of them here, I want to say that Gigabyte and the listed drivers are not the only one that is doing it wrong. Plenty of other drivers from all different sources are failing to secure their driver correctly.

The Bug

[CVE-2018–19321] Both GPCI and GIO expose functionality to read/write arbitrary physical memory, allowing a local attacker to take complete control of the affected system.

The issue at hand is that Gigabyte’s drivers allows a non-admin usermode application to map physical memory to its process with read/write access. In another word, the layer of separation that Virtual Address provides no longer exists.

The Objective

As mentioned by SecureAuth and Diego Juarez, an attacker could use this to escalate privileges. However, this will requires the machine that the exploit is running on to be running the driver already. The attacker cannot loads a driver and start it without administrative access. This somewhat makes the attack redundant due to the needs of admin to gain…admin. However, I believe that there is a possible and valid attack path that I will covers it in this post.

Endpoint Detection and Response (EDR) products are getting better at using the tools at hand. One of this tool is PPL or Protected Process Light (click here to read more from Alex Ionescu).

Once a process is in the PPL state… preventing any handle open for all but a few limited rights. Additionally, the memory manager will prevent loading of DLLs that are not signed appropriately, using the Code Integrity improvements in Windows 8

What this mean is once a process is under PPL, you can no longer (regardless of security context beside being a PPL process yourself) inject unsigned DLLs and also no longer have the ability to read/write to said process. This would be a valid and effective defense to stop attackers from accessing sensitive processes (such as lsass.exe) and stopping critical services from running (anti-malware). Luckily, this can be disable on run time as long as you get a bit of help from Gigabyte ❤. This is our second objective, to disable PPL.

So, the objectives are:

  • Disable PPL on a PPL Process
  • Privilege escalation

What is EPROCESS?

EPROCESS is a kernel struct that exists for every running processes. It contains information on the process’ PID, Image name, header, size, protection, etc. Having access to a process’ EPROCESS is a crucial step in modifying said process’ run time information. You can look at the complete EPROCESS struct here: https://umuttosun.com/eprocess-structure/

Leaking EProcess from userland

For both of our objectives to be successful, we need to know the process’ EPROCESS struct’s kernel pointer. Luckily for us, Microsoft provides a method through an undocumented API to do just that.

const unsigned long SystemExtendedHandleInformation = 0x40;

unsigned long buffer_length = 0;
unsigned char probe_buffer[1024] = { 0 };
NTSTATUS status = NtQuerySystemInformation(static_cast<SYSTEM_INFORMATION_CLASS>(SystemExtendedHandleInformation), &probe_buffer, sizeof(probe_buffer), &buffer_length);

Using NtQuerySystemInformation along with the SYSTEM_INFORMATION_CLASS SystemExtendedHandleInformation argument, we can receive an SYSTEM_HANDLE_INFORMATION_EX buffer, containing the handle information of running processes. However, what valuable to us is the SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX array, containing the addresses the EPROCESS struct of all the running processes. This can be narrowed down further by using the values belonging in this struct.

SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX

Although we now have the EPROCESS address of every single processes — there is no reliable way to map an EPROCESS struct to its respective process. I could be wrong about this but using the above struct, it is possible to narrow down the list to be able to differentiate a few system processes from each other but beside that, no further improvement could be made. This is as far as I can go without relying on the kernel aspect of this project, the Gigabyte’s driver.

Circular Active Process List

From this point, we have a list of EPROCESS address for all processes. However there is a slight unreliability due to the method of how we obtained those addresses. Therefore, we will rely on the Circular or Double linked Active Process List to enumerate all the running processes’ EPROCESS. Due to the list being in kernel memory, we cannot read it as if we own that address. However, we can utilize the driver’s physical memory reading capability to traverse this list. We can do this through IChooseYou’s wrapper, which will performs the virtual address to physical address translation and uses the Gigabyte’s driver to read that value back to us. Read more on address translation here. In our case, we can just use the wrapper read_virtual_memory to performs the read and translation in one go.

By reading the FLink and BLink, we can walk the circular list and see all the EPROCESS struct of all running processes.

Walking the Circular List

While we are walking the active process list, we can also read out the EPROCESS’ PID and Image name from the EPROCESS struct to perform comparison to the process we are looking for.

PID and image_name comparison

If the PID and Image_name matches with what we are looking for, we found the EPROCESS struct of our target process. Now, let’s move into exploiting.

Exploitation

Both PPL and Privilege escalation relies on a very similar technique. PPL and its attribute resides inside EPROCESS.Protection in a 1 BYTE bitwise field. A process’ privilege resides in EPROCESS.Token and it is a pointer to a _TOKEN struct.

Read about both of them here:

http://www.alex-ionescu.com/?p=97 (PPL)

_PS_PROTECTION field image from above blog

http://mcdermottcybersecurity.com/articles/x64-kernel-privilege-escalation — (Privilege Escalation)

Token field image from above blog

Protected Process Light

For PPL, because it is a bitwise field, we have to play with it a bit differently. However, we can simplify the bitwise field into 0x41 for enable and 0x00 for disable.

0x41 represent the _PS_PROTECTED_SIGNER being 4 (PsProtectedSignerLsa) and _PS_PROTECTED_TYPE being 1 (PsProtectedTypeProtectedLight)

If we want to enable PPL for a certain process, we just have to find that particular process’ EPROCESS in kernel, use Gigabyte driver to write a 1 byte 0x41 into EPROCESS + PROTECTION_OFFSET. Similarly, if we want to disable PPL for a process, we can do the exact same thing but write a 1 byte 0x00 instead.

Privilege Escalation

For privilege escalation, we will be doing something very similar. However, instead of writing a byte into some field, we have to copy a pointer from a system process and write it into the low privilege process. At a process’ EPROCESS + TOKEN_OFFSET, there is a pointer that is pointing toward a _EX_FAST_REF struct which also represent _TOKEN struct. Here is an example of a SYSTEM process’ token and an unprivileged process’ token.

SYSTEM process’ token struct

Note that the Security Identifier (SID) with value S-1–5–18 is the built-in SID for the Local System account.

Notepad.exe’s token struct

As mentioned previously, EPROCESS + TOKEN_OFFSET contains a pointer and only a pointer. No data is actually stored there. Therefore, if we want to escalate a process’ privilege, we only have to copy the pointer to our own EPROCESS struct and not have to replicate the actual _TOKEN struct itself.

We can do this by finding a SYSTEM process such as wininit.exe or any of the million SYSTEM processes, copy its EPROCESS + 0x358 (TOKEN_OFFSET) and write to the target EPROCESS + 0x358.

Privilege Escalation

After this is done, the target process should immediately be running as SYSTEM and you should be able to do almost anything.

Introducing, DanSpecial

Named after a colleague (people cannot take me seriously in interviews when I tell them about my projects… and this is why) who recently moved to a new company. I hope they treat you well.

Get it here: https://github.com/hoangprod/DanSpecial

WARNING: MIGHT BSOD YOU on UNTESTED SYSTEM.

WARNING: ONLY WORKS ON 64bit VERSIONS OF WINDOWS

Usage: ./DanSpecial.exe [0 | 1] [0 | 1 | 2] [process.exe]

Program takes in 3 arguments.

First argument is whether the process should loads the driver or not. DanSpecial comes with the driver already embedded in it so you don’t have to worry about that. Leave it at 0 if the system is already running the driver, or 1 if the system is not running the driver. Using option 1 would requires administrative access.

Second argument is what you want the program to do. 0 is to disable PPL on process.exe, 1 is to enable PPL and 2 is to elevate process.exe’s privilege to SYSTEM.

Third argument is the targeted process. You want to disable PPL on this process, enable PPL on this process, or elevate this process’ privilege. All action will be perform on this process as the target.

Uses

Okay, this is going to be the controversial part. How are you going to utilize this when you relies on having admin access already on most use cases? I have 2.5 scenarios for you.

Scenario 1: You have a low privilege access to a machine that is currently running a vulnerable Gigabyte’s driver. Drop exe, run it, ez priv esc.

Scenario 2: You gained admin on a machine but they are running CrowdStrike and CrowdStrike placed PPL on lsass.exe. You want to dump credential from lsass, PPL says no, you drop exe, run it and boom, ezpz Lsass dump. This is going to fail if Crowdstrike blacklist Gigabyte’s driver or detect this executable, both have their own bypass. I tested this on 1 CrowdStrike Falcon environment and it worked flawlessly. However, I also know this particular CS Falcon setup is not the most secure one but my trial request for their Falcon product was denied so testing ended there.

Scenario 2.5: You want to do cool stuff with physical memory / kernel memory and does not have the cert to sign your driver with. Use this code base to play with the kernel.

Summary

I tried my best to make this work on Windows 7 through latest Windows 10. However, I have only tested this on Windows 10 1709 64bit and 1803 64bit. It should be stable and work fine but we are using an undocumented function as well as messing with the kernel memory so… /shrug ?

Also, please notes that PPL started with Windows 8.1 and _PROTECTION field does not exist in earlier copies of Windows. Therefore, don’t try to enable PPL on Pre 8.1.

Good luck, have fun and let’s me know if I made any mistake in both this post and the posted code. I will try to fix stuff if it is obvious. Thank you again for reading my post, I appreciate your time.

P.S - I’m looking for a job

Credits

This would not have been possible, or at least not as painless, without the help of these individuals and resources. Thank you, IChooseYou for majority of the address translation and virtual memory read/write wrapper!

IChooseYou, where I stole most of the kernel code from and possibly the only reason this was completed within the short period of time that it did— https://www.unknowncheats.me/forum/members/278477.html

Can1357 for driver loading code— https://blog.can.ac/2018/05/02/making-the-perfect-injector-abusing-windows-address-sanitization-and-cow/

An amazing resource for Windows structs’ offsets — https://www.geoffchappell.com/studies/windows/km/ntoskrnl/structs/eprocess/index.htm

--

--