LPE In LiquidWare ProfileUnity
On a penetration test last year, I discovered a local privilege escalation (LPE) in a piece of software running on a client’s systems. This software is 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.
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, I had access to a compromised Windows 10 machine configured as a standard employee build, with a low-privilege employee account. As I explored the attack surface visible from this vantage point, one of ProfileUnity’s core features jumped out at us: the elevation service.
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
The first step in my analysis was to understand what the elevation service was really doing. With a little bit of digging, I found an XML file that is used to configure the elevation service.
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
This file cannot be written by a normal user, so in order to play with it I will need
to edit it as a local administrator. I added lines 11-12 as seen in the screenshot
above. My new rules will automatically elevate every process named
MyTest2.exe. (The significance of the latter will be revealed later.)
Now if I open a command shell as a normal user, I will see this result:
Even though I am 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.
So great, I am “elevated”—whatever that means—I 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.
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
CreateProcessInternalW. What is the first instruction in this 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
CreateProcessInternalW is located in
kernelbase.dll, so if I load that up in IDA I
can see what the first few bytes of this function are supposed to be:
The first instruction,
push 0xa00, is 5 bytes long, which is the same length as the
jmp hook. Back in the debugger, I can remove the hook by overwriting the jump
instruction with the machine code for the push instruction:
68 00 0a 00 00.
After applying this patch I can continue running the target, and now I find that my shell can run subprocesses!
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.
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.
Although the current shell is restricted in ways that I don’t totally understand, it does have a lot of powerful 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
API. Two privileges in particular are very powerful:
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
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.exeand running as
- Copy the access token from the parent process.
- Create a new
cmd.exewith the copied token (using the
CreateProcessWithTokenWAPI—see note below).
I compiled this into
MyTest2.exe, which as you saw at the outset, is on my whitelist
for elevated applications.
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
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.
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:
- Almost any code execution vulnerability in any signed LiquidWare executable will
allow an attacker to escalate to
- 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
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.
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.)
In Process Monitor, I can see that LouZip implements 7z compression by loading a file
.\7z\x64\7z.dll. Of course, my 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
So there you have it, a local privilege escalation from unprivileged user to SYSTEM using the default ProfileUnity configuration.
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.xmlto 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.
- 2020-01-07: Disclosed the issue to the penetration test customer.
- 2020-02-14: 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: Notified LiquidWare of intent to publish on of after April 13.
- 2020-04-16: Initial publication of this article.