Windows.Forensics.CertUtil

The Windows Certutil binary is capable of downloading arbitrary files. Attackers typically use it to fetch tools undetected using Living off the Land (LOL) techniques.

Certutil maintains a cache of the downloaded files and this contains valuable metadata. This artifact parses this metadata to establish what was downloaded and when.


name: Windows.Forensics.CertUtil
description: |
  The Windows Certutil binary is capable of downloading arbitrary
  files. Attackers typically use it to fetch tools undetected using
  Living off the Land (LOL) techniques.

  Certutil maintains a cache of the downloaded files and this contains
  valuable metadata. This artifact parses this metadata to establish
  what was downloaded and when.

reference:
  - https://u0041.co/blog/post/3
  - https://thinkdfir.com/2020/07/30/certutil-download-artefacts/
  - https://lolbas-project.github.io/lolbas/Binaries/Certutil/

parameters:
  - name: MinSize
    type: int
    description: Only show contents larger than this size.
  - name: URLWhitelist
    type: csv
    default: |
      URL
      http://sf.symcd.com
      http://oneocsp.microsoft.com
      http://certificates.godaddy.com
      http://ocsp.pki.goog
      http://repository.certum.pl
      http://www.microsoft.com
      http://ocsp.verisign.com
      http://ctldl.windowsupdate.com
      http://ocsp.sectigo.com
      http://ocsp.usertrust.com
      http://ocsp.comodoca.com
      http://cacerts.digicert.com
      http://ocsp.digicert.com
  - name: MetadataGlobUser
    default: C:/Users/*/AppData/LocalLow/Microsoft/CryptnetUrlCache/MetaData/*
  - name: MetadataGlobSystem
    default: C:/Windows/*/config/systemprofile/AppData/LocalLow/Microsoft/CryptnetUrlCache/MetaData/*
  - name: AlsoUpload
    type: bool

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

  - name: DISABLE_DANGEROUS_API_CALLS
    type: bool
    description: |
      Enable this to disable potentially flakey APIs which may cause
      crashes.


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

      LET Profile = '[
        ["Header", 0, [
          ["UrlSize", 12, "uint32"],
          ["HashSize", 100, "uint32"],
          ["DownloadTime", 16, "uint64"],
          ["FileSize", 112, "uint32"],
          ["URL", 116, "String", {
              "encoding": "utf16",
              "length": "x=>x.UrlSize"
          }],
          ["Hash", "x=>x.UrlSize + 116", "String", {
              "encoding": "utf16",
              "length": "x=>x.HashSize"
          }]
        ]]
      ]'

      -- Build a whitelist regex
      LET URLRegex <= "^" + join(array=URLWhitelist.URL, sep="|")
      LET Files = SELECT OSPath,

          -- Parse each metadata file.
          parse_binary(filename=OSPath, accessor=Accessor,
                       profile=Profile,
                       struct="Header") AS Header,

          -- The content is kept in the Content directory.
          OSPath.Dirname.Dirname + "Content" + OSPath.Basename AS _ContentPath,
          read_file(length=4, accessor=Accessor,
                filename=OSPath.Dirname.Dirname + "Content" + OSPath.Basename) AS ContentHeader
      FROM glob(globs=[MetadataGlobUser, MetadataGlobSystem], accessor=Accessor)
      WHERE Header.FileSize > MinSize

      SELECT OSPath AS _MetadataFile, _ContentPath,
               if(condition=AlsoUpload, then=upload(file=OSPath, accessor=Accessor)) AS _MetdataUpload,
               if(condition=AlsoUpload, then=upload(file=_ContentPath, accessor=Accessor)) AS _Upload,
               Header.URL AS URL,
               url(parse=URL).Host AS UrlTLD,
               Header.FileSize AS FileSize,
               regex_replace(re='"', replace="", source=Header.Hash) AS Hash,
               timestamp(winfiletime=Header.DownloadTime) AS DownloadTime,
               if(condition= ContentHeader=~ 'MZ',
                    then= parse_pe(file= _ContentPath, accessor=Accessor).VersionInformation,
                    else= 'N/A' ) as VersionInformation,
               if(condition= ContentHeader=~ 'MZ',
                    then= authenticode(filename= _ContentPath, accessor=Accessor),
                    else= 'N/A' ) as Authenticode

      FROM Files
      WHERE NOT URL =~ URLRegex