Windows.Detection.PrintNightmare

This artifact returns any binaries in the Windows/spool/drivers/** folders with an untrusted Authenticode entry.

It can be used to hunt for dll files droped during exploitation of CVE-2021-1675 - PrintNightmare.

To query all attached ntfs drives: check the AllDrives switch.

I have added several filters to uplift search capabilities from the original MFT artifact. Due to the multi-drive features, the MFTPath will output the MFT path of the entry.

Available filters include:

  • FullPath regex
  • FileName regex
  • Time bounds to select files with a timestamp within time ranges
  • FileSize bounds

Sample output
Sample output

name: Windows.Detection.PrintNightmare
author: "Matt Green - @mgreen27"
description: |
  This artifact returns any binaries in the Windows/spool/drivers/** folders with
  an untrusted Authenticode entry.
  
  It can be used to hunt for dll files droped during exploitation of 
  CVE-2021-1675 - PrintNightmare.
  
  To query all attached ntfs drives: check the AllDrives switch.

  I have added several filters to uplift search capabilities from the original
  MFT artifact. Due to the multi-drive features, the MFTPath will output the MFT
  path of the entry.

  Available filters include:
  - FullPath regex
  - FileName regex
  - Time bounds to select files with a timestamp within time ranges
  - FileSize bounds

  ![Sample output](https://github.com/mgreen27/velociraptor-docs/raw/patch-5/content/exchange/artifacts/PrintNightmare.png)

parameters:
  - name: MFTFilename
    default: "C:/$MFT"
  - name: Accessor
    default: ntfs
    type: hidden
  - name: PathRegex
    description: "Regex search over FullPath."
    default: Windows/System32/spool/drivers
  - name: FileRegex
    description: "Regex search over File Name"
    default: .
  - name: AllAuthenticode
    type: bool
    description: "Show all binaries despite Authenticode trusted status (default shows only untrusted)."
  - 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: SizeMax
    type: int64
    description: "Entries in the MFT under this size in bytes."
  - name: SizeMin
    type: int64
    description: "Entries in the MFT over this size in bytes."
  - name: AllDrives
    type: bool
    description: "Select MFT search on all attached ntfs drives."


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
            )))


      -- find all ntfs drives
      LET ntfs_drives = SELECT FullPath + '/$MFT'as Path
        FROM glob(globs="/*", accessor="ntfs")


      -- function returning MFT entries
      LET mftsearch(MFTPath) = SELECT 
            split(sep='\\$',string=MFTPath)[0] + FullPath as FullPath,
            InUse,FileName,FileSize,
            dict(
                Created0x10 = Created0x10,
                LastModified0x10 = LastModified0x10,
                LastRecordChange0x10 = LastRecordChange0x10,
                LastAccess0x10 = LastAccess0x10
                ) as SI,
            dict(
                Created0x30 = Created0x10,
                LastModified0x30 = LastModified0x10,
                LastRecordChange0x30 = LastRecordChange0x10,
                LastAccess0x30 = LastAccess0x10
                ) as FN
        FROM parse_mft(filename=MFTPath, accessor=Accessor)
        WHERE NOT IsDir
            AND FullPath =~ PathRegex
            AND FileName =~ FileRegex
            AND if(condition=SizeMax,
                then=FileSize < atoi(string=SizeMax),
                else=TRUE)
            AND if(condition=SizeMin,
                then=FileSize > atoi(string=SizeMin),
                else=TRUE)
            AND 
             ( time_test(stamp=Created0x10)
            OR time_test(stamp=Created0x30)
            OR time_test(stamp=LastModified0x10)
            OR time_test(stamp=LastModified0x30)
            OR time_test(stamp=LastRecordChange0x10)
            OR time_test(stamp=LastRecordChange0x30)
            OR time_test(stamp=LastAccess0x10)
            OR time_test(stamp=LastAccess0x30))


      -- include all attached drives
      LET all_drives = SELECT * FROM foreach(row=ntfs_drives,
            query={
                SELECT *
                FROM mftsearch(MFTPath=Path)
                WHERE log(message="Processing " + Path)
              })


      -- return rows
      SELECT *,
            parse_pe(file=FullPath) as PE,
            authenticode(filename=FullPath) as Authenticode,
            hash(path=FullPath) as Hash
      FROM if(condition=AllDrives,
        then= all_drives,
        else= {
           SELECT * FROM mftsearch(MFTPath=MFTFilename)
        })
      WHERE PE
        AND if(condition=AllAuthenticode,
            then=TRUE,
            else= NOT Authenticode.Trusted = 'trusted')