~9 min read
Lenovo DCC: Part 1 - A simple ACL Exploit

TL;DR
The Lenovo Display Control Center (DCC), widely deployed in Windows enterprise environments, contained a critical local privilege escalation vulnerability enabling unauthorized elevation to administrative privileges. This post examines the Lenovo DCC architecture, analyzes underlying security flaws through IDA Pro and ProcMon analysis, and presents two distinct exploitation methodologies for achieving local administrative access: a race condition-based approach and a junction path exploitation technique.
Introduction
The Lenovo Display Control Center (DCC) is an application we encountered on a recent Red Team engagement. The DCC enables end users to configure and calibrate their Lenovo displays and exposes display information via the Windows Management Instrumentation (WMI). The application is available through the Windows Store or as an enterprise installation package. In enterprise environments, DCC additionally supports remote software updates of the display firmware via WMI.
Internally, DCC spawns multiple processes that interact with each other through Remote Procedure Calls (RPC).
Among these, only two processes, the initial LenovoDCCBackGroundService
server and the DCCDataHelper
, run with elevated privileges.
The remaining processes, including the main RPC server component, operate with a MEDIUM
integrity token, presumably to reduce the attack surface.
We’ll examine vulnerabilities in this design in a follow-up post, but for now it’s important to note that LenovoDCCBackGroundService
runs as SYSTEM
.

Lenovo DCC services and their integrity levels
Service Interaction with %PROGRAMDATA%
As many (unsafe) services, the high privileged LenovoDCCBackgroundService.exe
interacts with the folder %PROGRAMDATA%
.
This environment variable usually points to C:/ProgramData
and contains quite lax permissions for low privileged users.
In the default configuration, low privileged users can create files in all (sub)folders of %PROGRAMDATA%
.
This itself may already pose a risk, as the service might interact with files created by a low privileged user in a high privileged context.
Attacks can span from “symlink” attacks to redirect file read/write operations to DLL hijacking.
Still, services use this folder for simplicity, but doing so often set proper, restrictive ACLs on those path.
The DCC interacts with the C:\ProgramData\Lenovo\LenovoDisplayControlCenterService
to read certain resources shared between the services.
Whilst the service can be configured to write log files to a somewhat deterministic folders in C:\ProgramData\Lenovo\LenovoDisplayControlCenterService\Logs
, a file redirection would yield no directly exploitable bugs.
However, if we take a look at the ACLs of the C:\ProgramData\Lenovo\LenovoDisplayControlCenterService
folder, we may find a surprise:

The folder LenovoDisplayControlCenterService is configured with overly permissive access rights, granting all users on the system unnecessary privileges
As we hadn’t noticed that the ACLs were set during the installation, our vulnerability senses started tingling. Something was going on. Let’s open the application in IDA Pro and investigate.
Reverse Engineering of the LenovoDCCBackgroundService.exe
Browsing the LenovoDCCBackgroundService.exe
in IDA Pro, a disassembler and decompiler, we could quickly identify the reason for the very permissive APIs for every user: A bat script was executed every time the service was started!
More concretely: The LenovoDCCBackgroundService.exe
application imports the WinAPI function ShellExecuteW, one of many APIs to start a new program directly in the context of the current process.
This API is used to call the SetPri.bat
located in %TEMP
.
Let’s take a look at the application’s decompiled and renamed binary code:
void __cdecl WriteExecACLBatFile(int a1)
{
int v1; // ecx
// Omitted [...]
int v23; // [esp+258h] [ebp-4h]
v23 = 0;
// [1] Get the C:/Windows/TEMP path
GetTempPathW(0x104u, Buffer);
v20 = 0;
v21 = 7;
LOWORD(lpFile[0]) = 0;
CreateATLString2(lpFile, Buffer, wcslen(Buffer));
LOBYTE(v23) = 1;
v1 = v20;
// [...]
v3 = v20;
// [2] Append `SetPri.bat` to temp path
memmove((char *)v2 + 2 * v1, L"SetPri.bat", filePath);
*((_WORD *)v2 + v3) = 0;
// [...]
v17 = 0;
v18 = 15;
LOBYTE(Block[0]) = 0;
// [3] Write static bat script content to memory
CreateATLString(
Block,
"ECHO Y|C:\\Windows\\System32\\cacls.exe \"C:\\ProgramData\\Lenovo\\LenovoDisplayControlCenterService\" /C /P everyone:F /T",
0x72u);
LOBYTE(v23) = 2;
fileContent = Block;
// [...]
ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(
&filePath,
v6);
// [4] Write file content of C:/Windows/Temp/SetPri.bat via C++ APIs
if ( WriteFileContent((int)fileContent, v7, filePath) )
{
v8 = (const WCHAR *)lpFile;
filePath = 0;
if ( v21 >= 8 )
v8 = lpFile[0];
// [5] Execute the created bat file
ShellExecuteW(0, L"open", v8, &Dependencies, 0, filePath);
Sleep(0x3E8u);
v9 = (const WCHAR *)lpFile;
if ( v21 >= 8 )
v9 = lpFile[0];
// [6] Delete the created bat file
DeleteFileW(v9);
}
In [1]
, the GetTempPathW API is used to retrieve a temporary path.
According to the documentation this defaults to the C:/Windows/Temp
path if no %TEMP%
, %TMP
or %USERPROFILE
environment variables are set.
Next, in [2]
, the retrieved temporary path is extended by appending the filename SetPri.bat
.
Eventually, a static bat
script is stored in a string variable (see [3]
) and written to C:/Windows/Temp/SetPri.bat
.
Once the script has been executed via ShellExecuteW in [5]
, the temporary file is removed again in [6]
.
So, let’s verify our assumption that every time we start the service, the bat script gets executed.
For this, we observe the service start in the ProcessMonitor (ProcMon), an API monitoring tool by SysInternals.
We set a filter to only show events where the path contains the string SetPri.bat
:

ProcMon filter showing events including the string SetPri.bat
Usually, only administrative users can restart the service LenovoDCCBackgroundService
.
Still, in our attack scenario we were able to restart the client system manually to trigger a system restart.
Restarting the service, we get the following information in ProcMon:

ProcMon filter showing access to SetPri.bat operations
The red section shows the steps up to [4]
, where the C:/Windows/Temp/SetPri.bat
script is created and 114 bytes of content are written to the script file.
The following green section shows that the cmd.exe
somehow interacts with the script file.
As ShellExecuteW internally uses a cmd.exe
command, it looks like this corresponds to step [5]
from above.
As we can see from the second to last column, the cmd.exe
is, just like the service itself, executed as NT AUTHORITY/SYSTEM
, the highest privileged user account on a Windows system. 👀
The final blue section shows the DeleteFileW API call, internally implemented via a kernel API call to ZwSetInformationFile with a FILE_DISPOSITION_DELETE argument.
Two Exploitable Issues
Now, what’s the issue with this anyway? Actually, there are two issues and both of them are exploitable!
- A race condition between creating and executing the script file
- The script itself, recursively giving
EVERYONE
permissions to the%ProgramData%/Lenovo/LenovoDisplayControlCenterService/
folder.
Let’s have a more detailed look at the two attack scenarios.
Exploit 1: Race Condition
The race condition arises from a delay of approximately 400 ms between the file write operation and the subsequent execution of the script.
That is more than enough time for an attacker to overwrite the C:/Windows/Temp/SetPri.bat
file and replace its content with a malicious script (that then gets executed with high privileges).
The malicious script can, for example, be used to create a new local administrator to achieve local privilege escalation.
Attentive and experienced readers might shout “objection to last statement”!
They might argue that the C:/Windows/Temp
folder has strict ACLs set and that a low privileged user can’t overwrite files owned by SYSTEM
.
Both points are valid and totally correct but still do not hinder the exploit.
To understand this, let’s have a look at the ACLs for C:/Windows/Temp
.
The Users
group has only special permissions
set.
Thus, a low privileged user can only execute files, create files and append data to files:

ACLs for the group “Users” on C:/Windows/Temp
While a low privileged user can’t read file content or even list the files inside this folder, they can still create and write new files.
Note that is not possible to overwrite files created by other users though (which was the point of the second objection).
But what happens if a low privileged user creates the file C:/Windows/Temp/SetPri.bat
and the SYSTEM
accounts overwrites its content?
While the content is changed, the ACLs still remain on the low privileged user as owner!
Even after the service overwrites SetPri.bat
, a low-privileged user can overwrite it again, as long as they remain the file’s owner.
Knowing all of this, the exploit is quite easy:
- As soon as the system starts, rapidly create (or overwrite) the file
C:/Windows/Temp/SetPriv.bat
with a custom script - Wait for the service to start
- Hopefully hit the race window 🤞
When tested locally, we could hit the race condition in 4/4 tries. Still, the service might start before the attacker might be too slow to make step 1 work. But we can do better anyway with the second exploit.
Exploit 2: Junctions
Let’s look at the script contents of the SetPri.bat
file:
ECHO Y|C:\Windows\System32\cacls.exe "C:\ProgramData\Lenovo\LenovoDisplayControlCenterService" /C /P everyone:F /T
The file pipes a Y
(for YES
) into a newly spawned cacls.exe process.
This deprecated utility assigns full permissions (F
) to the Everyone
group via the /P
parameter.
Additional parameters instruct the program to ignore access errors (/C
) and to apply the permissions recursively to all subfolders (/T
, for Traverse
).
And it is this last /T
parameter that makes this script easily exploitable for a local privilege escalation via a junction.
A junction, also called a “soft link”, simply redirects one folder to another.
Those junctions can be created by a low privileged user utilizing the mklink command.
Let’s see how the SetPri.bat
script behaves with (sub)folders containing a junction.
For this, we create the following folder structure.
C:/
myfolder1
mysubfolder1
myfolder2
mysubfolder2
We assume that a low privilege user has access to myfolder1
and wants to change the ACLs of myfolder2
.
Hence, we create a junction using the mklink /j
command and execute the SetPri.bat
command:
c:\myfolder1>mklink /j C:\myfolder1\mylink C:\myfolder2
Junction created for C:\myfolder1\mylink <<===>> C:\myfolder2
c:\myfolder1>ECHO Y|C:\Windows\System32\cacls.exe "C:\myfolder1" /C /P everyone:F /T
Are you sure (Y/N)?processed dir: C:\myfolder1
processed dir: C:\myfolder1\mylink
processed dir: C:\myfolder1\mysubfolder1
processed dir: C:\myfolder1\mylink\mysubfolder2
c:\myfolder1>
As we can see in the output, the junction was traversed by the script and Everyone
permissions were set on the C:\myfolder1\mylink\mysubfolder2
.
While the command output only prints C:\myfolder1\mylink\mysubfolder2
, the redirection actually affects the folder C:/myfolder2/mysubfolder2
!
The recursive traversal behaviour allows attackers to craft a very stable and reboot-persistent exploit to escalate the local privileges.
An attacker can simply create a junction in C:\ProgramData\Lenovo\LenovoDisplayControlCenterService
and point it to specific “target folder”.
On the next service start, ACLs of the subfolders in the “target folder” are then changed to be fully accessible by Everyone
.
A good, but quite destructive “target folder” could be C:/Windows
to modify DLLs in C:/Windows/System32
.
A cleaner way of exploitation could target any folder present in the system %PATH%
variable, as this allows for well known DLL Hijacking attacks with writeable PATHs.
In our proof-of-concept exploit, we used the folder C:\Windows\System32\WindowsPowerShell\
, overwriting the ACLs of the C:\Windows\System32\WindowsPowerShell\v1.0\
folder present in %PATH%
. This allowed us eventually to load our own DLLs with SYSTEM
privileges.
Closing Notes
This blog post emphasizes the heightened risk and expanded attack surface associated with deploying high-privileged third-party services across all customer machines. During our penetration testing engagements, we frequently identify and exploit such services. Following each engagement, we practice responsible vendor disclosure, as was the case in this instance.
The vulnerability discussed in this blog post was assigned CVE-2024-2175 and was successfully patched a few weeks after our initial reporting.
Stay tuned for the more technical follow-up post of the Lenovo DCC!