In my previous post, I described how one could leverage CVE-2019-0841 to backdoor the LAPS AdmPwd.dll
for EoP to NT AUTHORITY\SYSTEM
. The obvious question is that if a machine is not using LAPS, what can you do…? Well Rich Warren provided one solution, by using the Windows Diagnostics Hub Standard Collector Service.
Before we dive into any code, let’s think about the situation more generically.
CVE-2019-0841 allows low privileged users to overwrite the DACL on files that are owned by NT AUTHORITY\SYSTEM
. Successful exploitation results in Full Control
over the target file for the low priv user. This, in isolation, doesn’t result in code exec as SYSTEM
, pop a SYSTEM
shell or anything like that. But having Full Control
over a file means that you can modify it in some way, e.g. write or delete.
The great aspect of using the Collector Service to load and run DLLs, is that it’s compatible with any similar arbitrary privileged file write vulnerability - so if you can write content into System32
, regardless how, you’re golden. To that end, I decided to create a .NET Class Library to house the Collector Service code.
The idea being - you write your .NET PoC for arbitrary file write, add the CollectorService DLL as a dependancy, and call a single function within it to handle the magic of:
To test the idea, I ported the original C++ PoC for CVE-2019-0841 to C#, using James Forshaw’s NtApiDotNet package. This makes creating the required hardlink relatively simple:
Where path
is the target file to take Full Control
over (C:\Windows\System32\license.rtf
) and settings
is the path to the users’ MSEdge settings.dat
file.
My project is in the same Solution as SystemCollector, so I can a project reference.
After the exploit has verified that the user has taken control of the file, we simply add the following line:
SystemCollector.DLL.Load(filename);
Where filename
is license.rtf
.
99.9% of this code comes from Ryan Hanson’s CVE-2018-0952-SystemCollector PoC, so I won’t explain it here. For a detailed description, see his blog post. I will just conver my modifications.
The first line I added is:
var dll = DecompressDLL(Convert.FromBase64String("blah"));
Which is the same GZip (de)compression method I used in TikiTorch. This allows you to embedd your malicious DLL as a compressed, base64 encoded string, which is expanded and written to disk later.
I changed the scratch
directory to [email protected]"C:\Users\{Environment.UserName}\AppData\Local\Temp"
.
First, we backup the original target file.
We then overwrite the file with our DLL.
// overwrite
Console.WriteLine([email protected]" [>] Overwriting C:\Windows\System32\{filename} with malicious DLL");
File.WriteAllBytes([email protected]"C:\Windows\System32\{filename}", dll);
The final change was to replace Console.ReadLine();
with Thread.Sleep(3000);
. I found that if the application closes too quickly, the Collector Service does not load the DLL.
The DLL is loaded by DiagnosticsHub.StandardCollector.Service.exe
which, in my testing, closes after about 5 minutes and subsequently kills the Beacon.
In this demo, we have a foothold as a standard user. Let’s priv esc with a Cobalt Strike SMB DLL.
Solution available on GitHub.