Intel, Please Stop Assisting Me

November 10, 2020 Eran Shimony

This post focuses on two vulnerabilities the CyberArk Labs team uncovered in the Intel Support Assistant that affected the millions of Windows machines that run this software. The first vulnerability is of an arbitrary file deletion, which is quite common among update-related programs and the second is an easy full privilege escalation vulnerability that allows you to run code as NT AUTHORITY\SYSTEM.

These vulnerabilities have since been disclosed and Intel has issued a fix.

While both vulnerabilities can lead to a full privilege escalation, for the purposes of this blog, we’ll focus on the arbitrary delete vulnerability as it is a bit more complex. We will also touch on the second, which is a trivial arbitrary write with arbitrary content vulnerability.

This is the fourth part of the research series – you can read, part 1, part 2, part 3 and part 4 on the CyberArk Threat Research Blog.

TL;DR

Intel Support Assistant exposes the local Windows machine to privilege escalation. By running a non-privileged software, or by going to https://www.intel.com/content/www/us/en/support/intel-driver-support-assistant.html.

Invoking the Service

The Intel Support Assistant, as its name states, is software that is designed to assist the local user in finding missing drivers and providing updates upon release. To do so, it must run in a privileged context so that it can install new software on the system. As a result, we should expect a service or some other kind of privileged software that will do that. Examining this further, the architecture is rather complicated. There are four processes; two services and two helper programs that are responsible for the whole shebang.

Notice the integrity level of the first two processes.
Figure 1. Notice the integrity level of the first two processes.

The first and most important service is the DSAService, which essentially acts as a manager. It is responsible for downloading the new software, installing drivers, and monitoring existing products.

Another privileged component is the DSAUpdateService, which runs and installs new patches, as we will discuss later.

The bottom two are responsible for user interaction. You, as a regular user, can run the DSATray.exe client, which will initiate the beginning of an update session on the local machine. Besides that, DSATray will fire the process DSATray.exe, which will open a browser window and display the following:

The DSATray.exe process opens this webpage in your browser
Figure 2. The DSATray.exe process opens this webpage in your browser

Now, we have a scanning animation that will eventually indicate what software we need to install. But before this, DSAService starts looking for the currently installed drivers on our machine. It will check the version and see if we’ve missed some software or a patch. DSAService writes its status update to a log directory (%programdata%\Intel\DSA), which it does extensively. This log directory is not admin protected and is something that can be exploited (we’ll show that in a bit).

Back to our browser, which as a reminder, we can access as a regular user. Scanning the system for updates is the status quo with any support\update programs, and all software that has this module of a privileged service talking with a regular user program is prone to EoP bugs.

During our scan, I start my process monitor, filtering for the following:

Intel SupportAssistant
Figure 3. Here’s the filter that I used when checking Intel SupportAssistant. Note that I included many relevant operations for file manipulation attacks and the suffixes of the target path that are recorded.

Oddly, I see that DSAService.exe looks for some executables that resemble installers in a local folder inside my Downloads directory, which is, of course, not admin protected. The service checks if three files exist in the Intel Driver and Support Assistant Directory.

DSAService looking for installations in Downloads.
Figure 4. DSAService looking for installations in Downloads.

It is a good strategy to look for missing files when you try to exploit file manipulation bugs (note the PATH NOT FOUND result).

Now, there is no reason for these executables to be there in the first place. Unless they are leftovers of a previous installation that was stopped in the middle for any reason, we can take advantage of this knowledge by understanding that DSAService.exe or DSAUpdateService do file operations on files in an unprotected folder. Hopefully, we can cause those services to either create a file that is supposed to be there and redirect it to our liking or to execute a binary as a privileged software.

If we go back to the browser, we can see that I missed two updates – one for a WiFi driver and a second for a Bluetooth driver. It is easy to see that the CreateFile operations that ended up with PATH NOT FOUND on the missing binaries correspond to the ones we have in the webpage, beside the igfx-win10_100.7985.exe binary (and, admittedly, I have no clue why it’s there considering it doesn’t need to be updated from my tests).

My poor PC needs those two.
Figure 5. My poor PC needs those two.

If we try to mess with the software and press the Download all button, the DSAService tries again to find previous copies of the would-be updated software in C:\Users\John\Download\Intel Driver and Support Assistant directory. But what happens if we put those missing files in this directory?

BUG 1, Arbitrary Delete

To understand why the service looks for these files and how it reacts if they exist or not, we should find answers in the binary. Luckily, every component of the Support Assistant client is written in Dot Net, which makes my job incredibly easier. My rule of thumb when “reversing” Dot Net applications is always to look in the DLLs the application loads from and focus on controllers with promising names like DSAServiceCore.Controllers.Network or DSAServiceCore.Controllers.Flow.

In there, we can find many useful methods like IsUpdateAlreadyDownloaded as well as a method called Download.

Let’s look at how the Download method of DSAService works:

HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(this._downloadInfo.DownloadUrl); 
Global.Download.AddUserAgentToHttpWebRequest(httpWebRequest); 

if (!SecurityController.ValidateHttpWebRequest(httpWebRequest)) 
{ 
    this.DownloadState = DownloadState.Failed; 
    if (this.DownloadStopped != null) 
    { 
        Global.Logging.TraceError(string.Format("The Request URL ({0}) for driver {1} is invalid. Cannot download.", this._downloadInfo.DownloadUrl, this._downloadInfo.RecordId), base.GetType(), "Download"); 
        this.DownloadStopped(this, new DownloadStoppedRestEventArgs(this._downloadInfo.RecordId, DownloadState.Failed)); 
    } 
    this.DownloadThreadExitedEvent.Set(); 
} 
else 
{ 
    string text = Path.Combine(this._downloadDirectory, this._downloadInfo.FileName); 
    FileSystemSafetyController fileSystemSafetyController = new FileSystemSafetyController(new FileSystemController()); 
    if (fileSystemSafetyController.IsFileSafe(text) != PathSafetyEnum.SafeFile) 
    { 
        try 
        { 
            if (File.Exists(text)) 
            { 
                File.Delete(text); 
            } 
        } 
        catch 
        { 
        } 
        if (fileSystemSafetyController.IsFileSafe(text) != PathSafetyEnum.SafeFile) 
        { 
            throw new Exception(text + " is not safe"); 
        } 
    }
 }

At the beginning of the try block, we can see the service communicates with the downloading server, which is a mirroring site that hosts the downloads. If it succeeds in downloading the payload, which it should have no problem doing, it goes to the else block then performs an interesting call to a method named IsFileSafe with one argument; the path of the would-be written binary on disk.

 public PathSafetyEnum IsFileSafe(string path) 
{ 
    if (string.IsNullOrEmpty(path)) 
    { 
        throw new ArgumentException(); 
    } 
    if (this._fileSystem.JunctionPointExists(path)) 
    { 
        return PathSafetyEnum.JunctionPoint; 
    } 
    if (this._fileSystem.DirectoryExists(path)) 
    { 
        return PathSafetyEnum.FileIsDirectory; 
    } 
    PathSafetyEnum pathSafetyEnum = this.IsDirectorySafe(Path.GetDirectoryName(path)); 
    if (pathSafetyEnum != PathSafetyEnum.SafeDirectory) 
    { 
        return pathSafetyEnum; 
    } 

    if (this._fileSystem.FileExists(path)) 
    { 
        if (this._fileSystem.FileIsSymbolicLink(path)) 
        { 
            return PathSafetyEnum.FileSymbolicLink; 

        } 

        if (this._fileSystem.FileHasReparsePoint(path)) 
        { 
            return PathSafetyEnum.FileReparsePoint; 
        } 
        if (this._fileSystem.FileHasHardLinks(path)) 
        { 
            return PathSafetyEnum.FileHardLink; 
        } 
    } 
    return PathSafetyEnum.SafeFile; 
}

The text argument to this function is the path that will be used when the server writes the payload into the disk. It will now go for a series of tests that will make sure it is a “safe” place to be written in.

In our case, if we create an NTFS mount point or a junction (doesn’t really matter), the return code from the method would be PathSafetyEnum.JunctionPoint.

The return value PathSafetyEnum.JunctionPoint is, of course, different than PathSafetyEnum.SafeFile. This will cause the program to go into the if statement (Code Snippet 2), which checks if the file exists; if it does, then it deletes it, no questions asked.

So basically, we can reliably get into a code path that allows us to delete an arbitrary file.

What DSAService does to accomplish this is to delete previous instances of the would-be-updated drivers. It does it to give “room” to the newer version before it downloads new installers into its downloads folder. This by itself is an ok thing to do, but the whole downloading process is done without impersonating the local user, which is very helpful in terms of EoP, but also problematic.

We can see here that if we do a simple combo of a mount point to \RPC Control + object manager symlink, we get the arbitrary delete vulnerability. If you wish to read more about the attack, you can read my previous blog here.

Delete file in place
Figure 6. Delete file in place.

Here, we can see DSAService opens the file for reading and checks its content to know if this “driver” is up to date. In our case, it is not a driver, just a simple file inside the protected directory C:\Windows. The service continues by deleting this file before trying to download a newer one.

Figure7
Figure 7

Imagine malware using this primitive to delete files as NT AUTHORITY\SYSTEM. It can create a copy of protected files, encrypt them, then will delete the original ones with high privileges – all likely undetected by anti-malware solutions as it doesn’t involve a code injection. Also, this would be done by a trusted signed Intel service, which provides even fewer reasons to suspect anything malicious. While spawning a system shell with only an arbitrary delete vulnerability is not reliable for privilege escalation, it can still work with other primitives.

Race Condition

But wait, can’t we use the WriteFile operation for arbitrary file creation attack? The Download method will indeed delete any existing file, but if the file doesn’t exist, like in the case of targeting a new file, it should create a new one, right?

It turns out we can win here – the service will do a delete operation followed by a call to IsFileSafe method on the desired download path, which will return PathSafetyEnum.JunctionPoint again.

Now, if the file doesn’t exist and it is not a junction of any kind, then the service will begin writing the payload from memory into the disk, which leads to a TOCTOU problem.

If we can create a mount point + symlink after the check, then we will get to the code path of the creation of the new to be installed file.

How can we do it? By using OpLocks, of course. But don’t get too excited. After winning the race condition, the ACLs of the payload is not permissive. This means we can create arbitrary files in arbitrary locations, but we can’t change the content of files. This is great for denial of service attacks, creating files that bug check the system upon reboot (there are tons out there), but this doesn’t help us to get system shell for us.

Figure 8
Figure 8

Bug 2, A Shared Resource = System Shell

During this installation frenzy, I noticed some odd behavior DSAService.exe was writing and updating a bunch of files inside C:\ProgramData\Intel\DSA. The first thing I did was to check the permission level of this DSA directory. It turns out, this directory and its parent directory are not admin-ACLed. Therefore, any service that does an I/O operation on files within might be exploited by either symlink attack or DLL Hijacking. If you are interested in hearing how I discovered those types of bugs and how to exploit them automatically, you can take a look at a talk I did at HITB 2020 (Lockdown edition this year).

In our case, we focused on one of a bunch of shared files that are being handled by the Intel Support Assistant software pack. This is an XML file called DXDiag.xml. What is so special about this file, among other files that reside in C:\ProgramData\Intel\DSA ? This file is being handled both by the process DSAService.exe and by DSATray.exe. Due to this fact, the XML file DXDiag.xml should have permissive permission on it. On its own, this is not a bad thing, but the service does a write file operation on it again, not in an impersonated thread, and this time it doesn’t call the IsSafeFile method before doing the write operation.

Figure 9
Figure 9

Arbitrary Write With Arbitrary Content

As you can see, we can redirect this write file operation that’s been done by the service to arbitrary create and write. After that, it is only a matter of creating a mount point to \RPC Control and a symlink from DXDiag.xml to C:\Windows\ext.dll. Continue by pressing “Rescan” on the web interface, and you are golden. In order to decide which arbitrary DLL name you want to pick, just choose one of many options you have between many missing DLLs the system tries to load. For more reading on this topic, check out this blog from itm4n or this by James Forshaw.

Kernel Level Mitigation

For security vendors out there, if you have a kernel mini-filter driver, you can prevent the CreateFile operation from following the symlink by specifying (to some degree) IO_STOP_ON_SYMLINK in the Options argument in the IoCreateFile method. Oddly, this flag can be only used from kernel mode and I haven’t seen it being used by any drivers thus far.

Driver developers can also see if the symlink object in the object manager is pointing on a user writeable path in the object manager to understand if this symlink was created for nefarious purposes. I await to see the first AV\EDR solutions that use it to block this file-system attack.

Wrapping Up

There are two root causes of these bugs that are easy to fix. The first one is about knowing the default permission levels on directories in Windows. A privileged software must validate its destination path and that the file that is being edited is admin protected. The second is that we need understand that client-service relationships are potentially troublesome and must be handled with care.

You may also want to take a look at CVE-2020-5316, a vulnerability in Dell Support Assist, which is also a privileged software that is responsible for automatic updates of millions of computers. In this case, the vulnerability is due to a service that looked for a DLL in a place it wasn’t supposed to. It’s for this reason that these services are pretty good candidates for file manipulation attacks and a great way for attacker to escalate their privileges.

Disclosure Timeline

July 21, 2019 – Vulnerabilities reported to Intel

July 23, 2019 – Intel Opened a case 2208036168

October 7, 2019 – Intel reproduced the vulnerabilities, confirmed that a patch would be released in Q2 2020.

November 2019 – July 2020 – Continued communication with Intel inquiring about status

July 9, 2020 – Intel provides the official patch release date, November 10, 2020

November 10, 2020 – Patch released & CVE Issued – CVE-2020-22460

Previous Article
A Modern Exploration of Windows Memory Corruption Exploits – Part I: Stack Overflows
A Modern Exploration of Windows Memory Corruption Exploits – Part I: Stack Overflows

Introduction The topic of memory corruption exploits can be a difficult one to initially break in to. When ...

Next Article
Attacking Kubernetes Clusters Through Your Network Plumbing: Part 1
Attacking Kubernetes Clusters Through Your Network Plumbing: Part 1

Have you ever wondered how the water supply gets into your home and to the taps? Honestly it may not be som...