~9 min read

Lenovo DCC: Part 1 - A simple ACL Exploit

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.
Authored by:

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

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

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

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

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!

  1. A race condition between creating and executing the script file
  2. 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

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:

  1. As soon as the system starts, rapidly create (or overwrite) the file C:/Windows/Temp/SetPriv.bat with a custom script
  2. Wait for the service to start
  3. 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!