DisplayLink USB Graphics Software arbitrary file write Elevation of Privilege

Published on Wed 01 July 2020 by Yannick Méheut

Product: DisplayLink USB Graphics Software 7.9.296.0

Type: Local Privilege Escalation

Summary: Due to overpermissive access rights on a logging folder, the DisplayLink USB Graphics software can be abused to perform privileged file operations, such as arbitrary file creation. This can be exploited, e.g. via DLL hijacking on the privileged DisplayLink process, to obtain SYSTEM privileges on the local machine.

This vulnerability is already patched in recent versions, users should update to the latest version.

What's more, according to DisplayLink, version 7.9 is not compatible with Windows 10 and may cause stability issues.

Introduction

While conducting a configuration audit on a Windows laptop, checks I usually perform include common privilege escalation techniques, using Clément Labro (@itm4n)'s excellent PrivescCheck.

PrivescCheck lit up with some modifiable paths, including this one:

ModifiablePath                                         IdentityReference Permissions
--------------                                         ----------------- -----------
C:\Program Files\DisplayLink Core Software\Debug       Everybody         {WriteOwner, Delete, WriteAttributes, Synchronize...}

So, Everybody seems to have quite a lot of permissions on the C:\Program Files\DisplayLink Core Software\Debug folder.

What even is this program? Let's check in services.msc to gain more information:

DisplayLink information in services.msc

This service is part of a graphic software, automatically run, and runs as SYSTEM.

If this DisplayLink software performs privileged operations using files from this Debug folder, it might be a path to local privilege escalation! (spoiler: it is)

This bug was discovered and exploited with a lot of help from my dear friend and colleague (and James Forshaw's number one fan), Clément Lavoillotte. His introduction to privileged file operation abuse on Windows proved to be an invaluable resource.

Onwards with exploit searching!

I then decided to load up a Windows 10 1909 virtual machine (closely enough to the audited laptop for our purpose), and install the same version of DisplayLink.

Turns out that the audited Windows laptop was using version 7.9.296.0 of DisplayLink. This is an old version that is not listed in the download section.

By poking around the Internet Archive, it's possible to find the download link for this old version.

Overpermissive access rights

With the software installed, let's take a look at the permissions on the Debug folder:

Everyone has Full Control over the Debug folder

Interestingly, the object Everybody has Full Control over this folder.

If this folder contains sensitive files, such as DLL files, we can maybe modify one, so that malicious code can be run as SYSTEM. Let's take a look at the content of the folder:

The Debug folder contains log files

Hmmm, only log files. So, no direct way to elevate our privileges. However, the presence of .log and .old.log files indicates that there might be log rotation in place. As Clément explained in his article, we can exploit log rotation to perform arbitrary file creation.

The log rotation

Let's observe a log rotation in Procmon:

Log rotation observed in Procmon

At launch, DisplayLinkManager.exe seems to:

  1. Open the DisplayLinkManager.log log file

  2. Perform some checks on it (content? size?)

  3. If the checks warrant it, perform a log rotation:

    3.1. Delete the DisplayLinkManager.old.log file, if it exists

    3.2. Rename DisplayLinkManager.log to DisplayLinkManager.old.log

    3.3. Create a new DisplayLinkManager.log

As we can see, the actions are performed as the SYSTEM user. By looking at the details of the events, no impersonation seems to be done.

As the DisplayLink Manager does not impersonate the user when accessing these files, we might be able to write or delete arbitrary files.

Arbitrary file creation

Let's try to trick DisplayLink Manager into moving a file we control into a privileged place. We can use the CreateSymlink.exe tool from Google Project Zero's SymbolicLink Testing Tools to do so.

The symlinks can be created as follow:

  • From C:\Program Files\DisplayLink Core Software\Debug\DisplayLinkManager.log to the file we want to move
  • From C:\Program Files\DisplayLink Core Software\Debug\DisplayLinkManager.old.log to the place where we want to put our file

However, for this to work, the Debug folder must be empty, as the CreateSymlink.exe program will replace it with a mount point to \RPC Control. And if we try to delete the log files present in Debug, we get the following error:

The action can't be completed because the file is open in DisplayLinkManager

While we have Full Control over the folder and its content, because the log files are opened by the DisplayLink Manager process, we can't remove them. And we can't stop the DisplayLink Manager process, because it runs as SYSTEM, and we're just a poor unprivileged user!

So, how can we bypass this? Well, since we have Full Control over the Debug folder, we can change its ACL. For example, we can change it so that SYSTEM does not have modification rights over this folder and its content:

We changed the ACL on Debug, so that SYSTEM does not have modification rights on the folder

Now, when the machine is rebooted, the DisplayLink Manager won't be able to open its log files in the folder, and we can delete them. We can also delete DisplayLinkUserAgent.log. This file is opened by the DisplayLink User Agent application, which also runs as SYSTEM.

As DisplayLinkUI.log and DisplayLinkUIAddOnApi.log are opened by the DisplayLink UI Systray application, which runs with our current user's rights, so we can just kill it in the task manager, and delete the log files.

We kill DisplayLinkUI.exe in the task manager. Good night, sweet prince.

After that, you get one, clean, and totally empty, Debug folder:

The Debug folder is now empty

Now, we can go and try to exploit the process. We'll try to move a file we control to C:\Windows\System32. By observing the sizes of the log files, we noticed that log rotation occurs when the log file goes over 101 Kb, so we'll make sure that our custom file is over that size.

PS C:\Temp> ls

    Directory: C:\Temp


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        4/24/2020   6:58 PM         192302 arbitrary_file.txt


PS C:\Temp> Get-Content -TotalCount 8 .\arbitrary_file.txt
"Disposable Heroes"

Bodies fill the fields I see, hungry heroes end
No one to play soldier now, no one to pretend
Running blind through killing fields, bred to kill them all
Victim of what said should be
A servant 'til I fall

PS C:\Temp> Get-FileHash -Algorithm SHA256 -Path .\arbitrary_file.txt

Algorithm       Hash                                                                   Path
---------       ----                                                                   ----
SHA256          B3C1196F2E9A45C71C31BC2B73A216025793A31FED1B0FBE6FD14106FC637C1D       C:\Temp\arbitrary_file.txt

Let's create the symlinks:

PS C:\SymlinkTestTools> .\CreateSymlink.exe -p "C:\Program Files\DisplayLink Core Software\Debug\DisplayLinkManager.log" "C:\Temp\arbitrary_file.txt"
PS C:\SymlinkTestTools> .\CreateSymlink.exe -p "C:\Program Files\DisplayLink Core Software\Debug\DisplayLinkManager.old.log" "C:\Windows\System32\target_arbitrary_file.dll"

Our target file has an extension of .dll just to prove that we can fully control the name.

NB: When creating the symlink, make sure that no other process is currently accessing the Debug folder. That includes explorer.exe windows open inside the folder or with a quick access pin to it. It will save you a few headaches...

If we look at objects created in \RPC Control, we can see our "log files". To do so, we use WinObj.exe, from the Sysinternals Suite.

We can see our symlinks using WinObj.exe

Now, we can just log off our account, log back in, aaaand:

Our arbitrary file was correctly moved to C:\Windows\System32
PS C:\> Get-FileHash -Algorithm SHA256 -Path "C:\Windows\System32\target_arbitary_file.dll"

Algorithm       Hash                                                                   Path
---------       ----                                                                   ----
SHA256          B3C1196F2E9A45C71C31BC2B73A216025793A31FED1B0FBE6FD14106FC637C1D       C:\Windows\System32\target_ar...

We have our arbitrary file write! Here's the Procmon entries showing the log rotation that moves our arbitrary file into the system32 folder:

Procmon view of the log rotation which moved our arbitrary file

Looking for the missing DLL

So, how can we exploit this arbitrary file to elevate our privileges? The first path we explored was trying to replace sethc.exe with cmd.exe, to pop a shell with the sticky keys.

However, a SYSTEM system process does not have the right to modify these files, you need to be TrustedInstaller (there are techniques to achieve this when you're already privileged, but these can't be used on a process we don't have control over).

So we took another path. We looked at the DLL that DisplayLink Manager tries to load but fails, because they are not in the location the loader tries to load them from first (following the standard DLL load order). The idea was to replace one of these missing DLLs with our own, using a straightforward DLL hijack to gain arbitrary code execution as SYSTEM.

So, we fired up ProcMon, and searched for DLLs that were unsuccesfully loaded by DisplayLink Manager:

DisplayLink Manager tries to load several missing DLLs

We can see that DisplayLink Manager tries to load several DLLs that seem to be missing from its folder, such as VERSION.dll, USERENV.dll, or dbghelp.dll (aka the usual suspects in DLL hijacks).

Now, if we managed to create a file, say, C:\Program Files\DisplayLink Core Software\USERENV.dll, we could gain code execution as SYSTEM. Any of these DLLs could be targeted, my choice fell on USERENV.dll.

To create the malicious DLL, we take a look at the functions that DisplayLink Manager is importing from USERENV.dll. For that, I'll use CFF Explorer.

Looking at DisplayLink imports in CFF Explorer

Several functions are imported from USERENV.dll, to wit, DestroyEnvironmentBlock, LoadUserProfileW, UnloadUserProfile, LoadUserProfileA, and CreateEnvironmentBlock.

We can then create a DLL that exports these functions, but will actually call the command we want to execute. The code for my DLL is inspired by this post on DLL hijacking:

// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        WinExec("cmd.exe", SW_NORMAL);
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

extern "C" __declspec(dllexport) void DestroyEnvironmentBlock()
{
    WinExec("cmd.exe", SW_NORMAL);
}

extern "C" __declspec(dllexport) void LoadUserProfileW()
{
    WinExec("cmd.exe", SW_NORMAL);
}

extern "C" __declspec(dllexport) void UnloadUserProfile()
{
    WinExec("cmd.exe", SW_NORMAL);
}

extern "C" __declspec(dllexport) void LoadUserProfileA()
{
    WinExec("cmd.exe", SW_NORMAL);
}

extern "C" __declspec(dllexport) void CreateEnvironmentBlock()
{
    WinExec("cmd.exe", SW_NORMAL);
}

As you can see, we're simply executing cmd.exe in every function (just for PoC) used by the calling process. Let's compile it and plant it.

NB: remember to compile your DLL statically when doing hijacks.

Our DLL needs to be over 101 Kb to trigger log rotation, so if it's too small, we can pad it with NULL bytes.

Full exploit chain

Time to chain everything we have covered:

  1. Change the ACL of C:\Program Files\DisplayLink Core Software\Debug so that SYSTEM is denied modification rights.
  2. Reboot the system.
  3. Kill the DisplayLinkUI.exe process.
  4. Empty the C:\Program Files\DisplayLink Core Software\Debug folder.
  5. Use CreateSymlink.exe to create a symlink from C:\Program Files\DisplayLink Core Software\Debug\DisplayLinkManager.log to the malicious DLL.
  6. Use CreateSymlink.exe to create a symlink from C:\Program Files\DisplayLink Core Software\Debug\DisplayLinkManager.old.log to C:\Program Files\DisplayLink Core Software\USERENV.dll.
  7. Log off the user session, and log back in.
  8. ???
  9. Profit!
DisplayLink loads our malicious DLL and executes our payload

We have our SYSTEM shell! Couple of points, you can see there are two cmd.exe shells popping up. That's because our malicious DLL is loaded by DisplayLink Manager (which runs as SYSTEM) and by DisplayLink UI Systray (which runs as the current user). Therefore, our payload is executed twice.

Second, we're lucky to have a shell pop up on our desktop. That's because, DisplayLink Manager launches a process in our session, which then loads our DLL. Therefore, the command shell pops up in our graphic Windows session:

Process Explorer shows our cmd.exe process running in DisplayLink Manager

Had it run in session 0, the cmd.exe wouldn't have appeared on our desktop - even though our payload would still have been executed - and we would have needed a more complex payload to create the process in the user's session.

Conclusion

Now, is this vulnerability useful? Well, not really. It exploits an old software version that does not seem to be supported by the vendor, plus it requires rebooting the machine - or maybe waiting for the log rotation to kick in - so that you can empty the Debug folder. Not super practical or stealthy.

But, does it work? Yes, it does! And it did allow us to find a local privilege escalation path on our client's laptop, regardless of any other configuration problems. So that's nice ¯\_(ツ)_/¯

So, how is this vulnerability corrected in further versions? Well the Debug folder does not seem to be there anymore. The structure of the whole installation folder is changed, the problematic ACLs are gone.

Users are therefore encouraged to update to the latest version available.

Timeline

2020-04-23: Vendor contacted to signal presence of vulnerability in version 7.9 of their software, and that following versions don't seem impacted.

2020-04-23: Received response from vendor, precising that version 7.9 is not compatible with Windows 10 and should not be used with this version of Windows, due to possible instability.

2020-04-28: Received GPG key to send encrypted advisory to DisplayLink security team.

2020-04-29: Sent advisory to DisplayLink security team.

2020-04-30: DisplayLink security team acknowledges reception of advisory.

2020-05-15: DisplayLink confirms that versions newer than 7.9 are not affected.

2020-07-01: Publication of this advisory.