Windows.EventLogs.PowershellScriptblock

This Artifact will search and extract ScriptBlock events (Event ID 4104) from Powershell-Operational Event Logs.

Powershell is commonly used by attackers across all stages of the attack lifecycle. A valuable hunt is to search Scriptblock logs for signs of malicious content.

There are several parameter’s available for search leveraging regex.

  • DateAfter enables search for events after this date.
  • DateBefore enables search for events before this date.
  • SearchStrings enables regex search over scriptblock text field.
  • StringWhiteList enables a regex whitelist for scriptblock text field.
  • PathWhitelist enables a regex whitelist for path of scriptblock.
  • LogLevel enables searching on type of log. Default is Warning level which is logged even if ScriptBlock logging is turned off when suspicious keywords detected in Powershell interpreter. See second reference for list of keywords.
  • SearchVSS enables VSS search.

name: Windows.EventLogs.PowershellScriptblock
author: Matt Green - @mgreen27

description: |
  This Artifact will search and extract ScriptBlock events (Event ID 4104) from
  Powershell-Operational Event Logs.

  Powershell is commonly used by attackers across all stages of the attack
  lifecycle. A valuable hunt is to search Scriptblock logs for signs of
  malicious content.

  There are several parameter's available for search leveraging regex.
    - DateAfter enables search for events after this date.
    - DateBefore enables search for events before this date.
    - SearchStrings enables regex search over scriptblock text field.
    - StringWhiteList enables a regex whitelist for scriptblock text field.
    - PathWhitelist enables a regex whitelist for path of scriptblock.
    - LogLevel enables searching on type of log. Default is Warning level which
    is logged even if ScriptBlock logging is turned off when suspicious keywords
    detected in Powershell interpreter. See second reference for list of keywords.
    - SearchVSS enables VSS search.

reference:
  - https://attack.mitre.org/techniques/T1059/001/
  - https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs#L1781-L1943

parameters:
  - name: EvtxGlob
    default: '%SystemRoot%\System32\winevt\logs\Microsoft-Windows-PowerShell%4Operational.evtx'
  - name: DateAfter
    description: "search for events after this date. YYYY-MM-DDTmm:hh:ss Z"
    type: timestamp
  - name: DateBefore
    description: "search for events before this date. YYYY-MM-DDTmm:hh:ss Z"
    type: timestamp
  - name: SearchStrings
    type: regex
    description: "regex search over scriptblock text field."
  - name: StringWhitelist
    description: "Regex of string to witelist"
    type: regex
  - name: PathWhitelist
    description: "Regex of path to whitelist."
    type: regex
  - name: LogLevel
    description: "Log level. Warning is Powershell default bad keyword list."
    type: choices
    default: Warning
    choices:
       - All
       - Warning
       - Verbose
  - name: LogLevelMap
    type: hidden
    default: |
      Choice,Regex
      All,"."
      Warning,"3"
      Verbose,"5"

  - name: VSSAnalysisAge
    type: int
    default: 0
    description: |
      If larger than zero we analyze VSS within this many days
      ago. (e.g 7 will analyze all VSS within the last week).  Note
      that when using VSS analysis we have to use the ntfs accessor
      for everything which will be much slower.

sources:
  - query: |
      LET VSS_MAX_AGE_DAYS <= VSSAnalysisAge
      LET Accessor = if(condition=VSSAnalysisAge > 0, then="ntfs_vss", else="auto")

      -- firstly set timebounds for performance
      LET DateAfterTime <= if(condition=DateAfter,
        then=timestamp(epoch=DateAfter), else=timestamp(epoch="1600-01-01"))
      LET DateBeforeTime <= if(condition=DateBefore,
        then=timestamp(epoch=DateBefore), else=timestamp(epoch="2200-01-01"))

      -- Parse Log level dropdown selection
      LET LogLevelRegex <= SELECT format(format="%v", args=Regex) as value
        FROM parse_csv(filename=LogLevelMap, accessor="data")
        WHERE Choice=LogLevel LIMIT 1

      -- expand provided glob into a list of paths on the file system (fs)
      LET fspaths = SELECT OSPath
        FROM glob(globs=expand(path=EvtxGlob), accessor=Accessor)

      -- function returning IOC hits
      LET evtxsearch(PathList) = SELECT * FROM foreach(
            row=PathList,
            query={
                SELECT
                  timestamp(epoch=int(int=System.TimeCreated.SystemTime)) AS EventTime,
                  System.Computer as Computer,
                  System.Channel as Channel,
                  System.EventID.Value as EventID,
                  System.Security.UserID as SecurityID,
                  EventData.Path as Path,
                  EventData.ScriptBlockId as ScriptBlockId,
                  EventData.ScriptBlockText as ScriptBlockText,
                  get(field="Message") as Message,
                  System.EventRecordID as EventRecordID,
                  System.Level as Level,
                  System.Opcode as Opcode,
                  System.Task as Task,
                  OSPath
                FROM parse_evtx(filename=OSPath, accessor=Accessor)
                WHERE System.EventID.Value = 4104
                    AND EventTime < DateBeforeTime
                    AND EventTime > DateAfterTime
                    AND  format(format="%d", args=System.Level) =~ LogLevelRegex.value[0]
                    AND if(condition=SearchStrings,
                      then=ScriptBlockText =~ SearchStrings,
                      else=TRUE)
                    AND if(condition=StringWhitelist,
                      then= NOT ScriptBlockText =~ StringWhitelist,
                      else=TRUE)
                    AND if(condition=PathWhitelist,
                      then= NOT Path =~ PathWhitelist,
                      else=TRUE)
          })

        SELECT * FROM evtxsearch(PathList=fspaths)
        GROUP BY EventRecordID, Channel