Detection rules › Splunk

Windows Known Abused DLL Loaded Suspiciously

Status
production
Severity
medium
Group by
command_line, dest, image_loaded, process_guid, process_hash, process_id, process_name, service_dll_signature_exists, service_dll_signature_verified, signature, signature_id, user_id, vendor_product
Author
Steven Dick
Source
github.com/splunk/security_content

The following analytic detects when DLLs with known abuse history are loaded from an unusual location. This activity may represent an attacker performing a DLL search order or sideload hijacking technique. These techniques are used to gain persistence as well as elevate privileges on the target system. This detection relies on Sysmon EID7 and is compatible with all Officla Sysmon TA versions.

MITRE ATT&CK coverage

Event coverage

ProviderEventTitle
SysmonEvent ID 7Image loaded

Rule body splunk

name: Windows Known Abused DLL Loaded Suspiciously
id: dd6d1f16-adc0-4e87-9c34-06189516b803
version: 12
creation_date: '2024-04-06'
modification_date: '2026-05-13'
author: Steven Dick
status: production
type: TTP
description: The following analytic detects when DLLs with known abuse history are loaded from an unusual location. This activity may represent an attacker performing a DLL search order or sideload hijacking technique. These techniques are used to gain persistence as well as elevate privileges on the target system. This detection relies on Sysmon EID7 and is compatible with all Officla Sysmon TA versions.
data_source:
    - Sysmon EventID 7
search: '`sysmon` ImageLoaded EventCode=7 NOT ImageLoaded IN ("*\\Program Files*","*\\system32\\*", "*\\syswow64\\*","*\\winsxs\\*","*\\wbem\\*") | stats count min(_time) as firstTime max(_time) as lastTime by Image ImageLoaded dest process_exec process_guid process_hash process_id process_path service_dll_signature_exists service_dll_signature_verified signature signature_id user_id vendor_product loaded_file | rename Image as process | eval process_name = case(isnotnull(process),replace(process,"(.*\\\)(?=.*(\.\w*)$|(\w+)$)","")), loaded_file_path = case(isnotnull(loaded_file), replace(loaded_file, "(:[\w\. ]+)", "")), loaded_file = case(isnotnull(loaded_file),replace(loaded_file,"(.*\\\)(?=.*(\.\w*)$|(\w+)$)","")), user = case(NOT user IN ("-"), replace(user, "(.*)\\\(.+)$","\2")) | lookup hijacklibs_loaded library AS loaded_file OUTPUT islibrary comment as desc | lookup hijacklibs_loaded library AS loaded_file excludes as loaded_file_path OUTPUT islibrary as excluded | search islibrary = TRUE AND excluded = false | stats count min(_time) as firstTime max(_time) as lastTime by dest loaded_file loaded_file_path process process_exec process_guid process_hash process_id process_name process_path service_dll_signature_exists service_dll_signature_verified signature signature_id user_id vendor_product | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)` | `windows_known_abused_dll_loaded_suspiciously_filter`'
how_to_implement: The following analytic requires Sysmon operational logs to be imported, with EID7 being mapped to the process_name field. Modify the sysmon macro as needed to match the sourcetype or add index.
known_false_positives: DLLs being loaded by user mode programs for legitimate reasons.
references:
    - https://attack.mitre.org/techniques/T1574/002/
    - https://hijacklibs.net/api/
    - https://wietze.github.io/blog/hijacking-dlls-in-windows
    - https://github.com/olafhartong/sysmon-modular/pull/195/files
drilldown_searches:
    - name: View the detection results for - "$dest$" and "$user$"
      search: '%original_detection_search% | search  dest = "$dest$" user = "$user$"'
      earliest_offset: $info_min_time$
      latest_offset: $info_max_time$
    - name: View risk events for the last 7 days for - "$dest$" and "$user$"
      search: '| from datamodel Risk.All_Risk | search normalized_risk_object IN ("$dest$", "$user$") | stats count min(_time) as firstTime max(_time) as lastTime values(search_name) as "Search Name" values(risk_message) as "Risk Message" values(analyticstories) as "Analytic Stories" values(annotations._all) as "Annotations" values(annotations.mitre_attack.mitre_tactic) as "ATT&CK Tactics" by normalized_risk_object | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`'
      earliest_offset: 7d
      latest_offset: "0"
finding:
    title: The module [$loaded_file$] was loaded from an unusual location.
    entity:
        field: dest
        type: system
        score: 50
analytic_story:
    - SolarWinds WHD RCE Post Exploitation
    - Windows Defense Evasion Tactics
    - Living Off The Land
asset_type: Endpoint
mitre_attack_id:
    - T1574.001
product:
    - Splunk Enterprise
    - Splunk Enterprise Security
    - Splunk Cloud
category: endpoint
security_domain: endpoint
tests:
    - name: True Positive Test
      attack_data:
        - data: https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/attack_techniques/T1574.002/hijacklibs/hijacklibs_sysmon.log
          source: XmlWinEventLog:Microsoft-Windows-Sysmon/Operational
          sourcetype: XmlWinEventLog
      test_type: unit

Stages and Predicates

Stage 1: search

`sysmon` ImageLoaded EventCode=7 NOT ImageLoaded IN ("*\\Program Files*","*\\system32\\*", "*\\syswow64\\*","*\\winsxs\\*","*\\wbem\\*")

Stage 2: stats

| stats count min(_time) as firstTime max(_time) as lastTime by Image ImageLoaded dest process_exec process_guid process_hash process_id process_path service_dll_signature_exists service_dll_signature_verified signature signature_id user_id vendor_product loaded_file

Stage 3: rename

| rename Image as process

Stage 4: eval

| eval process_name = case(isnotnull(process),replace(process,"(.*\\\)(?=.*(\.\w*)$|(\w+)$)","")), loaded_file_path = case(isnotnull(loaded_file), replace(loaded_file, "(:[\w\. ]+)", "")), loaded_file = case(isnotnull(loaded_file),replace(loaded_file,"(.*\\\)(?=.*(\.\w*)$|(\w+)$)","")), user = case(NOT user IN ("-"), replace(user, "(.*)\\\(.+)$","\2"))
loaded_file =
elsereplace(loaded_file, "(.*\\\)(?=.*(\.\w*)$|(\w+)$)", "")
loaded_file_path =
elsereplace(loaded_file, "(:[\w\. ]+)", "")
process_name =
elsereplace(process, "(.*\\\)(?=.*(\.\w*)$|(\w+)$)", "")
user =
elsereplace(user, "(.*)\\\(.+)$", "\2")

Stage 5: lookup

| lookup hijacklibs_loaded library AS loaded_file OUTPUT islibrary comment as desc
Lookup table
hijacklibs_loaded
Key field
library as loaded_file
Output columns
['islibrary', 'islibrary'], ['comment', 'desc']

Stage 6: lookup

| lookup hijacklibs_loaded library AS loaded_file excludes as loaded_file_path OUTPUT islibrary as excluded
Lookup table
hijacklibs_loaded
Key field
library as loaded_file
Output columns
['islibrary', 'excluded']

Stage 7: search

| search islibrary = TRUE AND excluded = false

Stage 8: stats

| stats count min(_time) as firstTime max(_time) as lastTime by dest loaded_file loaded_file_path process process_exec process_guid process_hash process_id process_name process_path service_dll_signature_exists service_dll_signature_verified signature signature_id user_id vendor_product

Stage 9: search

| `security_content_ctime(firstTime)`

Stage 10: search

| `security_content_ctime(lastTime)`

Stage 11: search

| `windows_known_abused_dll_loaded_suspiciously_filter`

Exclusions

Top-level NOT(...) conjuncts: predicates this rule actively suppresses.

FieldKindExcluded values
ImageLoadedin"*\\Program Files*", "*\\system32\\*", "*\\syswow64\\*", "*\\wbem\\*", "*\\winsxs\\*"

Indicators

Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.

FieldKindValues
EventCodeeq
  • 7 corpus 39 (splunk 38, kusto 1)
excludedeq
  • false
islibraryeq
  • TRUE corpus 2 (splunk 2)

Search terms

Bare-string tokens in the SPL search body. Splunk matches each token against _raw (the untyped raw event text) anywhere it appears, not against a specific field. These don't surface in the Indicators table because they aren't predicates on a known field.

StageTerm
1ImageLoaded