Windows.EventLogs.RecordIDCheck

This artifact will compare EventLog records and report on abnormalities in RecordID sequence and optional time gap. The artifact can be used for both hunting, remote or local analysis.

There are several parameter’s available.

  • EvtxGlob glob of EventLogs to target. Default to all but can be targeted.
  • PathRegex enables filtering on evtx path for specific log targetting.
  • DateAfter enables search for events after this date.
  • DateBefore enables search for events before this date.
  • MaxTimeDifference enables flaging temporal gaps between Events. Note also potential false positives on machines turned off.
  • SearchVSS enables searching over VSS

Note: Please use with caution this artifact can potentially be heavy on the endpoint. Temporal analysis is turned off by default due to potential for false positives during machine shutdown. Sequential false positives may also occur very occasionally.

version: 0.6.1


name: Windows.EventLogs.RecordIDCheck
author: Matt Green - @mgreen27
description: |
  This artifact will compare EventLog records and report on
  abnormalities in RecordID sequence and optional time gap. The
  artifact can be used for both hunting, remote or local analysis.

  There are several parameter's available.
    - EvtxGlob glob of EventLogs to target. Default to all but can be targeted.
    - PathRegex enables filtering on evtx path for specific log targetting.
    - DateAfter enables search for events after this date.
    - DateBefore enables search for events before this date.
    - MaxTimeDifference enables flaging temporal gaps between Events. Note also potential false positives on machines turned off.
    - SearchVSS enables searching over VSS

  Note: Please use with caution this artifact can potentially be heavy
  on the endpoint.  Temporal analysis is turned off by default due to
  potential for false positives during machine shutdown. Sequential
  false positives may also occur very occasionally.

  version: 0.6.1

parameters:
  - name: EvtxGlob
    description: Target glob to process for abnormalities.
    default: '%SystemRoot%\System32\Winevt\Logs\*.evtx'
  - name: PathRegex
    description: Event log Regex to enable filtering on path
    default: .
  - name: DateAfter
    type: timestamp
    description: "search for events after this date. YYYY-MM-DDTmm:hh:ssZ"
  - name: DateBefore
    type: timestamp
    description: "search for events before this date. YYYY-MM-DDTmm:hh:ssZ"
  - name: MaxTimeDifference
    description: Alert on events with a gap between previous event greater than this number in seconds.
  - name: SearchVSS
    description: "Add VSS into query."
    type: bool

sources:
  - query: |
      -- time testing
      LET time_test(stamp) =
            if(condition= DateBefore AND DateAfter,
                then= stamp < DateBefore AND stamp > DateAfter,
                else=
            if(condition=DateBefore,
                then= stamp < DateBefore,
                else=
            if(condition= DateAfter,
                then= stamp > DateAfter,
                else= True
            )))

      -- create dict for previous results.
      LET EvtxPath<=dict(FullPath='',RecordID='',EventTime='')

      -- expand provided glob into a list of paths on the file system (fs)
      LET fspaths <= SELECT FullPath
        FROM glob(globs=expand(path=EvtxGlob))
        WHERE FullPath =~ PathRegex

      -- function returning list of VSS paths corresponding to path
      LET vsspaths(path) = SELECT FullPath
        FROM Artifact.Windows.Search.VSS(SearchFilesGlob=path)
        WHERE FullPath =~ PathRegex

      -- function returning IOC hits
      LET evtxsearch(PathList) = SELECT * FROM foreach(
            row=PathList,
            query={
                SELECT
                    FullPath,
                    System.Computer as Computer,
                    System.Channel as Channel,
                    EvtxPath.OLDEventTime as FirstEventTime,
                    set(item=EvtxPath,field='OLDFullPath',value=EvtxPath.FullPath) as _SetOLDFullPath,
                    set(item=EvtxPath,field='FullPath',value=FullPath) as _SetFullPath,
                    set(item=EvtxPath,field='OLDRecordID',value=EvtxPath.RecordID) as _SetOLDRecordID,
                    set(item=EvtxPath,field='RecordID',value=System.EventRecordID) as _SetRecordID,
                    set(item=EvtxPath,field='OLDEventTime',value=EvtxPath.EventTime) as _SetOLDEventTime,
                    set(item=EvtxPath,field='EventTime',value=timestamp(epoch=int(int=System.TimeCreated.SystemTime))) as _SetEventTime,
                    EvtxPath.OLDRecordID as FirstRecordID,
                    timestamp(epoch=int(int=System.TimeCreated.SystemTime)) AS SecondEventTime,
                    System.EventRecordID as SecondRecordID,
                    System.EventRecordID - EvtxPath.OLDRecordID as _RecordIDSequence,
                    EvtxPath.OLDFullPath as _OLDFullPath
                FROM parse_evtx(filename=FullPath)
                WHERE
                    time_test(stamp=SecondEventTime)
            }
          )

      -- include VSS
      LET include_vss = SELECT * FROM foreach(row=fspaths,
            query={
                SELECT *
                FROM evtxsearch(PathList={
                        SELECT FullPath FROM vsspaths(path=FullPath)
                    })
                --GROUP BY EventRecordID,Channel
              })

      -- exclude VSS`
      LET exclude_vss = SELECT *
        FROM evtxsearch(PathList={SELECT FullPath FROM fspaths})
      -- return rows
      SELECT *,
        SecondEventTime.Unix - FirstEventTime.Unix as SecondsGap,
        if(condition= NOT _RecordIDSequence=1,
                then= "EventRecordID not sequential",
                else= "Gap between EventRecordIDs exceeds maximum seconds.") as Description
      FROM if(condition=SearchVSS,
        then=include_vss,
        else=exclude_vss)
      WHERE _RecordIDSequence
        AND FullPath = _OLDFullPath
        AND
            ( if(condition=MaxTimeDifference,
                then= SecondsGap > int(int=MaxTimeDifference),
                else= False)
            OR NOT _RecordIDSequence=1 )