LPE In LiquidWare ProfileUnity

On a penetration test last year, we discovered a local privilege escalation (LPE) in a piece of software running on our client's systems called Liquidware ProfileUnity. This post is a quick run down of the techinical details of the vulnerability, how to determine if you are affected, and potential mitigations.

Background

I'm not an expert on administering large Windows networks, so I'll describe ProfileUnity in the vendor's own words. LiquidWare specializes in "adaptive workspace management," and ProfileUnity is an enterprise solution that offers "sophisticated user environment management and migration."

In the pen test scenario, we had access to a compromised Windows 10 machine configured as a standard employee build, with a low-privilege employee account. As we explored the attack surface visible from this vantage point, one of ProfileUnity's core features jumped out at us: the elevation service.

Note: This vulnerability was confirmed in ProfileUnity 6.8.2 in January, 2020. The vendor also released version 6.8.3 in January, around the same time that we were conducting our client engagment. The vendor declined to confirm whether this specific vulnerability was fixed in the latest release, but the release notes say: "Client security has been enhanced when it comes to elevated processes in the user space."

The elevation service allows system administrators to configure certain processes to run on endpoints with elevated privileges, without requiring the process owner to be a local administrator or type in an administrator password. The elevation service also imposes some safety constraints, such as not allowing elevated processes to spawn subprocesses. The name of the service immediately flagged it as an interesting attack surface, as well as the fact that it runs as NT AUTHORITY\SYSTEM.

Understanding "Elevation"

The first step in our analysis was to understand what the elevation service was really doing. With a little bit of digging, we found an XML file that is used to configure the elevation service.

elevation service configuration XML file

By default, the configuration has two white list rules: elevate any process that has a security certificate signed by Liquidware Labs, Inc. or Liquidware Labs (lines 9-10).

This file cannot be written by a normal user, so in order to play with it we will need to edit it as a local administrator. We added lines 11-12 as seen in the screenshot above. Our new rules will automatically elevate every process named cmd.exe or MyTest2.exe. (The significance of the latter will be revealed later.)

Now if we open a command shell as a normal user, we will see this result:

elevated-whoami

Even though we are logged in as a low-privilege user, the window is labeled "Administrator". In the background, the elevation service:

  • Saw a new process being created.
  • Recognized that the process matched one of its elevation rules.
  • Terminated the new process.
  • Created its own subprocess with the same executable and command line arguments.

For the last step to work, the executable either needs to be in the search path or the command needs to include the full path to the executable. Process Explorer shows that cmd.exe has moved underneath the elevation service.

elevated cmd.exe is underneath elevation service

So great, we are "elevated"—whatever that means—we own the box, right? Well… not so fast. Did you notice that the whoami command didn't work up above?

Working Around Safety Measures

As stated in the overview, the elevation service has some safety constraints on elevated processes. One of these constraints is not allowing elevated processes to create child processes.

elevated process cannot create subprocesses.

In the screenshot above, I ran dir and then ping. The first command works, and the second does nothing. Why? Well dir is a builtin: cmd.exe can execute that command without creating a subprocess. But ping not built in, so cmd.exe has to find it in the path and run it. Somehow, the elevation service is preventing me from doing that.

Tracing through the command shell in a debugger, I came across an internal Windows API called CreateProcessInternalW. What is the first instruction in this function?

elevation service has hooked the create process function

It is a jmp into some function in the lwl_userapp_monitor segment; this corresponds to a DLL that the elevation service loaded into this process. This is an example of userspace hooking, i.e. redirecting Windows API calls so that they can be monitored or modified. Antivirus often uses a similar technique, as well as some malware.

I tried single stepping past the jump for a little while, but it's really complicated code relative to my beginner capabilities as a reverse engineer. The upshot is that CreateProcessInternalW never finishes running: the hook code returns directly to the caller.

CreateProcessInternalW is located in kernelbase.dll, so if we load that up in IDA we can see what the first few bytes of this function are supposed to be:

the unhooked DLL code viewed in IDA

The first instruction, push 0xa00, is 5 bytes long, which is the same length as the jmp hook. Back in the debugger, we can remove the hook by overwriting the jump instruction with the machine code for the push instruction: 68 00 0a 00 00.

unhooking create process function

After applying this patch we can continue running the target, and now we find that our shell can run subprocesses!

subprocesses are now allowed

However, the shell still has some restrictions. For example, I can write a file to \Windows\System32, which an ordinary user cannot do, but I cannot overwrite or delete files that already exist.

other-restrictions

There are probably some attacks possible with this write primitive, or some other ways to take advantage of this elevated shell. I'm not a Windows security expert, so maybe some good folks on the interwebz will enlighten me. Instead, I set about trying to escalate to a less restricted shell.

Getting to SYSTEM

Although the current shell is restricted in ways that I don't totally understand, it does have a lot of powerful privileges:

elevated shell has privileges

One surprising fact that I learned while researching this vulnerability is that the process has all of the privileges listed under whoami /priv, even if a privilege is disabled! A process can enable one of its privileges with the AdjustTokenPrivileges API. Two privileges in particular are very powerful: SeDebugPrivilege and SeImpersonatePrivilege, both of which I have in my elevated shell.

My next step was to write a stand-alone program that enables these two privileges, then uses those privileges to copy an access token from a system process into a new cmd.exe. I won't post the full code here, but Andre Marques' detailed blog post about Windows security tokens and impersonation was an invaluable resource for me to figure this out. The basic process is:

  • Enable the debug and impersonate privileges.
  • Open a handle to your parent process, which in this case is lwl_elevation_service.exe and running as SYSTEM.
  • Copy the access token from the parent process.
  • Create a new cmd.exe with the copied token (using the CreateProcessWithTokenW API—see note below).

Side note: it turns out that CreateProcessWithTokenW is not hooked by the elevation service, so all the work I put into reverse engineering and unhooking CreateProcessInternalW was a waste of time! But maybe the real treasure was the friends we made along the way. ¯\_(ツ)_/¯

I compiled this into MyTest2.exe, which as you saw at the outset, is on my whitelist for elevated applications.

escalated to system shell

In this screenshot, the window all the way in the back is my low-privilege shell. The window in the middle is the process that the elevation service creates to run my process at an elevated level. And the front window is my system shell.

Great! So what does this prove? Well, a process that is elevated by ProfileUnity can escalate to SYSTEM, bypassing any of the restrictions that are supposed to be in place. But so far, this demonstration relies on the fact that I used an administrator account to whitelist MyTest2.exe. A savvy administrator should be careful about which applications they whitelist, and how they whitelist them.

In the configuration that I showed at the beginning, any process named MyTest2.exe is elevated. This makes it trivial for an attacker with access to the machine to run malicious code with elevated privileges. A possible mitigation here is to put elevated executables in a secure location (not world writable), and specify the whitelist rule as a full path, i.e. \Program Files\MyTest2\MyTest2.exe.

Exploiting the Default Configuration

What if the system administrator hasn't whitelisted any elevated processes? Is the elevation service secure in its default configuration? Unfortunately, the answer is no, because the default configuration still whitelists all executables signed by Liquidware itself. This default has two serious consequences:

  1. Almost any code execution vulnerability in any signed LiquidWare executable will allow an attacker to escalate to SYSTEM.
  2. If LiquidWare's signing keys are compromised, then an attacker can sign malware and run with it with elevated privileges, without needing to compromise the software distribution channel itself.

The second bullet is mostly hypothetical: it illustrates the inherent trust that ProfileUnity customers must place on the vendor to adequately protect signing keys.

The first bullet, however, is not hypothetical. ProfileUnity ships with dozens of signed executables, and each one of them increases the attack surface for LPE. As I browsed through them, one stood out. LouZip.exe is a compression tool similar to WinZip except that it supports ProfileUnity's proprietary compression format in addition to several mainstream formats.

This signed executable is installed as part of the "client tools" package that is automatically installed on every client machine managed by ProfileUnity. Alternatively, an old version can be downloaded from LiquidWare's website without needing a ProfileUnity license.

louzip help text

The help text for LouZip indicates that it supports *.lou files (a proprietary format) as well as *.7z (the open source 7z format). (Notice in the example above, LouZip did not run as an elevated process because it is not in the path.)

process-monitor

In Process Monitor, we can see that LouZip implements 7z compression by loading a file from .\7z\x64\7z.dll. Of course, our low privilege user owns this file and can overwrite it, leading to a classic DLL highjacking vulnerability.

I reused the payload from MyTest2.exe above and turned it into a DLL, with the malicious code running inside DllMain. I overwrote 7z.dll with this malicious payload. Finally, to get a system shell, I just need to unzip any *.7z file using the full path to LouZip.exe:

louzip-escalation

So there you have it, a local privilege escalation from unprivileged user to SYSTEM using the default ProfileUnity configuration.

Mitigations

For customers still running a vulnerable version of ProfileUnity, the mitigation scenarios are a bit complicated. LouZip is already signed and available for public download, and revoking the certificate seems unlikely because LiquidWare signs dozens of other binaries with it. My personal recommendations for now are to do one of the following:

  • Modify the whitelist in lwl_elevation_service.xml to white list individual applications either by file hash or full path.
  • If you aren't using the elevation service, it seems like it might be possible to disable it completely.

Disclosure Timeline

  • 2020-01-07: Hyperion Gray disclosed the issue to our penetration test customer.
  • 2020-02-14: Hyperion Gray disclosed the existence of the vulnerability to Liquidware in Profile Unity 6.8.2.
  • 2020-02-17: LiquidWare indicated that they had fixed some vulnerabilities in 6.8.3. LiquidWare declined to agree to a coordinate disclosure agreement.
  • 2020-04-02: Hyperion Gray notified LiquidWare of our intent to publish on of after April 13.
  • 2020-04-16: Initial publication of this article.