Query Details

HUNT 24 AD Service Creation Modification Audit 30d

Query

// =========================================================
// HUNT-24 | AD-ServiceCreation-Modification-Audit-30d
// Description : Audits all service creation (7045 via
//               WindowsEvent) and modification (4657 registry
//               changes on HKLM\SYSTEM\CurrentControlSet\
//               Services) events. Surfaces suspicious
//               service binary paths, DLL service installs,
//               services created by non-admin users, and
//               services running from temp/user-writable
//               directories — all common persistence and
//               privilege escalation mechanisms.
// Period      : 30 days
// Use Case    : Service-based persistence, DLL hijacking,
//               privilege escalation (T1543.003)
// Tables      : SecurityEvent, WindowsEvent
// =========================================================

let Period = 30d;

// High-value service paths that indicate potential compromise
let SuspiciousServicePaths = dynamic([
    "\\temp\\", "\\tmp\\", "\\appdata\\",
    "\\programdata\\", "\\users\\public\\",
    "\\downloads\\", "\\desktop\\",
    "cmd /c ", "powershell ", "-enc ", "base64",
    "mshta", "cscript", "wscript", "rundll32",
    "regsvr32", ".js", ".vbs", ".bat", ".cmd",
    "net user", "net localgroup"
]);

// SecurityEvent 4697: Service installed (security log version)
let SecSvcInstall = SecurityEvent
    | where TimeGenerated > ago(Period)
    | where EventID == 4697
    | extend
        ServiceName    = ServiceName,
        ServicePath    = tolower(ServiceFileName),
        ServiceType    = tostring(column_ifexists("ServiceType", "")),
        ServiceAccount = ServiceAccount,
        InstallActor   = strcat(SubjectDomainName, "\\", SubjectUserName);

// Registry changes on Services key (4657)
let ServiceRegChanges = SecurityEvent
    | where TimeGenerated > ago(Period)
    | where EventID == 4657
    | where ObjectName has "\\Services\\"
    | extend
        ServiceName  = extract(@"\\Services\\([^\\]+)", 1, ObjectName),
        RegValue     = ObjectValueName,
        NewValue     = NewValue,
        Actor        = strcat(SubjectDomainName, "\\", SubjectUserName),
        IsImagePath  = ObjectValueName =~ "ImagePath",
        IsDLLPath    = ObjectValueName =~ "ServiceDll";

// Combine service creation sources — WindowsEvent (7045) is optional; SecurityEvent (4697) is always present
let AllNewServices = union isfuzzy=true
    (WindowsEvent
        | where TimeGenerated > ago(Period)
        | where EventID == 7045
        | extend
            ServiceName   = tostring(EventData.ServiceName),
            ServicePath   = tolower(tostring(EventData.ImagePath)),
            ServiceType   = tostring(EventData.ServiceType),
            ServiceAccount= tostring(EventData.AccountName),
            InstallActor  = tostring(EventData.SubjectUserName)
        | project TimeGenerated, ServiceName, ServicePath, ServiceType, ServiceAccount, Actor = InstallActor, Computer, Source = "System_7045"),
    (SecSvcInstall
        | project TimeGenerated, ServiceName, ServicePath, ServiceType, ServiceAccount, Actor = InstallActor, Computer, Source = "Security_4697");

// Flag suspicious services
AllNewServices
| extend
    HasSuspiciousPath = ServicePath has_any (SuspiciousServicePaths),
    IsWeakAccount     = ServiceAccount in~ ("LocalSystem", "NT AUTHORITY\\SYSTEM",
                                             ".\\LocalSystem", "LocalService",
                                             "NetworkService"),
    IsUserWritablePath = ServicePath has_any ("\\users\\", "\\temp", "\\tmp",
                                              "\\appdata", "\\programdata"),
    IsDLLSurrogate    = ServicePath has_any ("rundll32", "svchost -k", "regsvr32"),
    IsOffHours        = hourofday(TimeGenerated) < 7 or hourofday(TimeGenerated) >= 20
| summarize
    NewServiceInstalls = count(),
    SuspiciousInstalls = countif(HasSuspiciousPath),
    UserWritable       = countif(IsUserWritablePath),
    DLLSurrogates      = countif(IsDLLSurrogate),
    OffHoursInstalls   = countif(IsOffHours),
    Actors             = make_set(Actor, 10),
    ServiceNames       = make_set(ServiceName, 20),
    SuspiciousPaths    = make_set_if(ServicePath, HasSuspiciousPath, 10),
    FirstSeen          = min(TimeGenerated),
    LastSeen           = max(TimeGenerated)
  by Computer
| union (
    // Also include registry DLL path changes for existing services
    ServiceRegChanges
    | where IsDLLPath or IsImagePath
    | extend HasSuspValue = NewValue has_any (SuspiciousServicePaths)
    | where HasSuspValue
    | summarize
        NewServiceInstalls = 0,
        SuspiciousInstalls = count(),
        UserWritable       = countif(tolower(NewValue) has_any ("\\users\\","\\temp","\\tmp","\\appdata")),
        DLLSurrogates      = countif(tolower(NewValue) has "rundll32"),
        OffHoursInstalls   = 0,
        Actors             = make_set(Actor, 10),
        ServiceNames       = make_set(ServiceName, 20),
        SuspiciousPaths    = make_set(NewValue, 10),
        FirstSeen          = min(TimeGenerated),
        LastSeen           = max(TimeGenerated)
      by Computer
)
| extend
    RiskScore = (SuspiciousInstalls * 30)
              + (UserWritable * 25)
              + (DLLSurrogates * 20)
              + (OffHoursInstalls * 10),
    RiskLevel = case(
        SuspiciousInstalls >= 1 and UserWritable >= 1, "Critical",
        DLLSurrogates >= 1,                            "Critical",
        SuspiciousInstalls >= 1,                       "High",
        OffHoursInstalls >= 2,                         "Medium",
        "Low"
    )
| project
    Computer,
    RiskLevel,
    RiskScore,
    NewServiceInstalls,
    SuspiciousInstalls,
    UserWritable,
    DLLSurrogates,
    OffHoursInstalls,
    Actors,
    ServiceNames,
    SuspiciousPaths,
    FirstSeen,
    LastSeen
| order by RiskScore desc

Explanation

This KQL query is designed to audit and detect potentially suspicious service creation and modification activities on Windows systems over the past 30 days. Here's a simplified breakdown of what the query does:

  1. Purpose: The query aims to identify suspicious service activities that could indicate persistence mechanisms or privilege escalation attempts, such as services created by non-admin users, services running from user-writable directories, or services with suspicious binary paths.

  2. Data Sources: It uses data from two tables, SecurityEvent and WindowsEvent, to gather information about service installations and registry changes related to services.

  3. Suspicious Indicators: The query defines a list of suspicious paths and commands that are commonly associated with malicious activities, such as running from temporary or user directories, or using scripting engines like PowerShell or cmd.

  4. Service Creation and Modification:

    • It looks for service installation events (event ID 4697) and registry changes (event ID 4657) related to services.
    • It also considers service creation events from the WindowsEvent table (event ID 7045).
  5. Flagging Suspicious Services: The query checks for:

    • Services with paths matching the suspicious indicators.
    • Services running under weak or default accounts.
    • Services installed during off-hours (before 7 AM or after 8 PM).
    • Services using DLL surrogates like rundll32.
  6. Summarization: It summarizes the findings by computer, counting the number of new service installations, suspicious installations, and other indicators. It also collects information about the actors involved and the service names.

  7. Risk Assessment: The query calculates a risk score based on the presence of suspicious indicators and assigns a risk level (Critical, High, Medium, Low) to each computer.

  8. Output: The final output includes a list of computers with their associated risk levels, risk scores, and details about the suspicious activities detected, ordered by risk score in descending order.

Overall, this query helps security analysts identify and prioritize potential security threats related to service management on Windows systems.

Details

David Alonso profile picture

David Alonso

Released: March 24, 2026

Tables

SecurityEventWindowsEvent

Keywords

SecurityEventWindowsEventServicesServiceNameServicePathServiceTypeServiceAccountInstallActorObjectNameRegValueNewValueActorComputerRiskLevelRiskScoreNewServiceInstallsSuspiciousInstallsUserWritableDLLSurrogatesOffHoursInstallsActorsSuspiciousPathsFirstSeenLastSeen

Operators

letdynamicagotolowertostringcolumn_ifexistsstrcatextractunionisfuzzyprojecthashas_anyin~=~hourofdaysummarizecountcountifmake_setmake_set_ifminmaxcaseextendorder by

Actions