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:
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:
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:
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:
At launch, DisplayLinkManager.exe
seems to:
Open the
DisplayLinkManager.log
log filePerform some checks on it (content? size?)
If the checks warrant it, perform a log rotation:
3.1. Delete the
DisplayLinkManager.old.log
file, if it exists3.2. Rename
DisplayLinkManager.log
toDisplayLinkManager.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:
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:
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.
After that, you get one, clean, and totally empty, Debug
folder:
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.
Now, we can just log off our account, log back in, aaaand:
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:
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:
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.
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:
- Change the ACL of
C:\Program Files\DisplayLink Core Software\Debug
so thatSYSTEM
is denied modification rights. - Reboot the system.
- Kill the
DisplayLinkUI.exe
process. - Empty the
C:\Program Files\DisplayLink Core Software\Debug
folder. - Use
CreateSymlink.exe
to create a symlink fromC:\Program Files\DisplayLink Core Software\Debug\DisplayLinkManager.log
to the malicious DLL. - Use
CreateSymlink.exe
to create a symlink fromC:\Program Files\DisplayLink Core Software\Debug\DisplayLinkManager.old.log
toC:\Program Files\DisplayLink Core Software\USERENV.dll
. - Log off the user session, and log back in.
- ???
- Profit!
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:
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.