_._     _,-'""`-._
(,-.`._,'(       |\`-/|
    `-.-' \ )-`( , o o)
          `-    \`_`"'-

SignalRGB: standard user to SYSTEM, by way of a service binary anyone can overwrite

2026-06-15 · lpewindowssignalrgbservice-aclcwe-732cwe-276system

Whoso breaketh an hedge, a serpent shall bite him.

Ecclesiastes 10:8 (KJV)

same well i went down for SteelSeries GG: RGB software, the kind everyone installs and nobody audits, running a service as the most privileged account on the box because it needs to poke hardware. SignalRGB was already on the bench for a driver bug, so while it was sitting there installed i ran the boring pass i run on anything that ships a service, list the services, see which ones are SYSTEM, then go look at where their binaries actually live and who's allowed to write them. most days that's a dead end. this time it was the whole finding, first service on the list.

Name      : SignalRgb.Service
StartName : LocalSystem
StartMode : Auto
PathName  : "C:\Users\exp\AppData\Local\WhirlwindFX\SignalRgb\service\SignalRgbService.exe"

a service that runs as LocalSystem, starts on its own at boot, and whose executable sits inside a per-user AppData\Local folder. the path alone is a bad smell, because profile directories belong to the user, not to SYSTEM. so the only question left is the ACL on that file.

# icacls ...\service\SignalRgbService.exe
NT AUTHORITY\SYSTEM:(I)(F)
BUILTIN\Administrators:(I)(F)
WIN-EXP\exp:(I)(F)
WIN-EXP\noob:(I)(F)                  # a plain standard user. Full Control.

that's the bug, all of it, in one line. noob is a standard user with no groups and no admin, and it holds Full Control over the executable of a service that runs as SYSTEM. the installer dropped a SYSTEM binary into a user-owned directory and then granted every user write access to it. you don't need a memory corruption or a race or a clever primitive. you need copy.

the swap

the one real obstacle is that the running image is locked against an in-place write. that stops less than it sounds like. Windows is perfectly happy to let you rename a running executable, it only protects the bytes the loaded image came from, not the directory entry pointing at them. move the real binary aside, drop your own in the name it left behind. the file sits under another user's profile, but Bypass-Traverse-Checking is handed to Everyone by default, so a standard user reaches it by full path without any right to the folders above it.

whoami:  win-exp\noob
rename:  1 file(s) moved.            # SignalRgbService.exe -> .bak
copy:    1 file(s) copied.           # our payload -> SignalRgbService.exe
dir:     06/15/2026  05:43 AM   14,848 SignalRgbService.exe

the service binary is ours now. fourteen kilobytes where twelve megabytes used to sit, waiting for the next start.

the trigger, honestly

this is the part writeups like to skip, so here it is plainly. the service control DACL grants Users RP, which is SERVICE_START, but not stop. and you can't taskkill it, it runs as SYSTEM. so a standard user can start the service but cannot restart one that's already running, and in normal operation it's running. that leaves two honest triggers, and i'm not going to pretend there's a third.

the clean one is a reboot. the service is Auto-start, so on the next boot the service manager runs our planted binary as SYSTEM, and a standard user is allowed to reboot. that's the whole thing if you can wait for a restart or a logoff.

the no-reboot one is a chain, and it leans on the box already being soft elsewhere. this machine also ran SteelSeries GG, whose update proxy service is demand-start and startable by any user, i already had a SYSTEM sideload into it. i used that to borrow SYSTEM just long enough to stop and start SignalRgb.Service. here's the shortcut worth saying out loud, because it's the part that makes the chain cheap: the moment anything restarts the service through a stop, it lands in the stopped state, and from there noob's own SERVICE_START fires the planted binary directly. borrow SYSTEM once to knock it loose; every pop after that is a one-liner.

the pop

the payload is the ordinary service-launched move. SCM starts it as SYSTEM in session 0, so it duplicates its own token into the active console session and spawns a cmd.exe on the interactive desktop. a quieter build skips the window and just adds you to Administrators. i ran both.

# Administrators, after
    Administrator
    exp
    noob                  # standard user, now a local admin

# cmd.exe: pid / session / user
    PID   Session#  User Name
    5416  1         NT AUTHORITY\SYSTEM   # a SYSTEM shell, on noob's own desktop

two separate SYSTEM executions, both stamped nt authority\system, ending with a standard user in the Administrators group and a SYSTEM prompt sitting on their own console. the dropper and the payload are about a hundred lines of C between them. one finds the service binary under any profile, renames the real one to .bak, drops the payload, and calls sc start. the other is the token-to-session spawn. nothing in either is clever. the cleverness was all in the installer, which ran as admin for one moment, and used that moment to hand the keys back to the tenant. CWE-732 and CWE-276 wearing one coat: a per-user install directory is the wrong home for anything that runs as SYSTEM, and stacking Users:Full-Control on top turns the wrong home into a free one. if the binary has to live in a writable tree, it has to be ACL'd down to SYSTEM and Administrators, and the service ought to verify it before it launches. none of that held here.

. _SiCk · 0xdeadbeefnetwork · afflicted.sh