---
description: Pipeline for Windows Sysmon Event Logs
processors:
## ECS and Event fields.

  - set:
      field: ecs.version
      value: '1.12.0'
  - script:
      description: Remove all empty values from event_data.
      lang: painless
      source: ctx.winlog?.event_data?.entrySet().removeIf(entry -> [null, "", "-", "{00000000-0000-0000-0000-000000000000}"].contains(entry.getValue()))
  - rename:
      field: winlog.level
      target_field: log.level
      ignore_missing: true
      ignore_failure: true
      if: ctx?.winlog?.level != ""
  - date:
      field: winlog.time_created
      target_field: event.created
      formats:
        - ISO8601
      ignore_failure: true
      if: ctx?.winlog?.time_created != null
  - date:
      field: winlog.event_data.UtcTime
      formats:
        - yyyy-MM-dd HH:mm:ss.SSS
      timezone: UTC
      ignore_failure: true
      if: ctx?.winlog?.event_data?.UtcTime != null

  - set:
      field: event.module
      value: sysmon
  - set:
      field: event.kind
      value: event
  - set:
      field: event.code
      value: '{{winlog.event_id}}'

  - script:
      description: Set event category and type for all event types.
      lang: painless
      params:
        "1":
          category:
            - process
          type:
            - start
        "2":
          category:
            - file
          type:
            - change
        "3":
          category:
            - network
          type:
            - start
            - connection
            - protocol
        "4":
          category:
            - process
          type:
            - change
        "5":
          category:
            - process
          type:
            - end
        "6":
          category:
            - driver
          type:
            - start
        "7":
          category:
            - process
          type:
            - change
        "10":
          category:
            - process
          type:
            - access
        "11":
          category:
            - file
          type:
            - creation
        "12":
          category:
            - configuration
            - registry
          type:
            - change
        "13":
          category:
            - configuration
            - registry
          type:
            - change
        "14":
          category:
            - configuration
            - registry
          type:
            - change
        "15":
          category:
            - file
          type:
            - access
        "16":
          category:
            - configuration
          type:
            - change
        "17":
          category:
            - file
          type:
            - creation
        "18":
          category:
            - file
          type:
            - access
        "22":
          category:
            - network
          type:
            - connection
            - protocol
            - info
        "23":
          category:
            - file
          type:
            - deletion
        "24":
          type:
            - change
        "25":
          category:
            - process
          type:
            - change
        "26":
          category:
            - file
          type:
            - deletion
      tag: Add ECS categorization fields
      source: |-
        if (ctx?.event?.code == null || params.get(ctx.event.code) == null) {
          return;
        }
        def hm = new HashMap(params[ctx.event.code]);
        hm.forEach((k, v) -> ctx.event[k] = v);
  - convert:
      field: winlog.record_id
      type: string
      ignore_failure: true 
      ignore_missing: true

  - rename:
      field: winlog.event_data.ID
      target_field: error.code
      ignore_failure: true
      ignore_missing: true
      if: ctx.event.code == "255"

  - rename:
      field: winlog.event_data.RuleName
      target_field: rule.name
      ignore_missing: true
      ignore_failure: true

  - rename:
      field: winlog.event_data.Type
      target_field: message
      ignore_missing: true
      ignore_failure: true
      if: ctx.event.code == "25"

  - rename:
      field: winlog.event_data.Hash
      target_field: winlog.event_data.Hashes
      ignore_missing: true
      ignore_failure: true
  - kv:
      field: winlog.event_data.Hashes
      target_field: _temp.hashes
      field_split: ","
      value_split: "="
      ignore_failure: true
      if: ctx?.winlog?.event_data?.Hashes != null
  - script:
      lang: painless
      if: ctx?._temp?.hashes != null
      source: |-
        def hashIsEmpty(String hash) {
          if (hash == "") {
            return true;
          }
          
          Pattern emptyHashRegex = /^0*$/;
          def matcher = emptyHashRegex.matcher(hash);
          
          return matcher.matches(); 
        }

        def hashes = new HashMap();
        def related = [
          "hash": new ArrayList()
        ];
        for (entry in ctx._temp.hashes.entrySet()) {
          def key = entry.getKey().toString().toLowerCase();
          def value = entry.getValue().toString().toLowerCase();

          if (hashIsEmpty(value)) {
            continue;
          }

          hashes[key] = value;
          related.hash.add(value);
        }

        ctx._temp.hashes = hashes;
        if (related.hash.length > 0) {
          ctx.related = related;
        }

## Process fields

  - rename:
      field: _temp.hashes
      target_field: process.hash
      if: |-
        ctx?._temp?.hashes != null &&
        ["1", "23", "24", "25", "26"].contains(ctx.event.code)
  - rename:
      field: process.hash.imphash
      target_field: process.pe.imphash
      ignore_failure: true
      ignore_missing: true
  - rename:
      field: winlog.event_data.ProcessGuid
      target_field: process.entity_id
      ignore_missing: true
      ignore_failure: true
  - convert:
      field: winlog.event_data.ProcessId
      target_field: process.pid
      type: long
      ignore_failure: true
      ignore_missing: true
  - rename:
      field: winlog.event_data.Image
      target_field: process.executable
      ignore_missing: true
      ignore_failure: true
  - rename:
      field: winlog.event_data.SourceProcessGuid
      target_field: process.entity_id
      ignore_missing: true
      ignore_failure: true
  - rename:
      field: winlog.event_data.SourceProcessGUID
      target_field: process.entity_id
      ignore_missing: true
      ignore_failure: true
  - convert:
      field: winlog.event_data.SourceProcessId
      target_field: process.pid
      type: long
      ignore_failure: true
      ignore_missing: true
  - convert:
      field: winlog.event_data.SourceThreadId
      target_field: process.thread.id
      type: long
      ignore_failure: true
      ignore_missing: true
  - rename:
      field: winlog.event_data.SourceImage
      target_field: process.executable
      ignore_missing: true
      ignore_failure: true
  - rename:
      field: winlog.event_data.Destination
      target_field: process.executable
      ignore_missing: true
      ignore_failure: true
  - rename:
      field: winlog.event_data.CommandLine
      target_field: process.command_line
      ignore_missing: true
      ignore_failure: true
  - rename:
      field: winlog.event_data.CurrentDirectory
      target_field: process.working_directory
      ignore_missing: true
      ignore_failure: true
  - rename:
      field: winlog.event_data.ParentProcessGuid
      target_field: process.parent.entity_id
      ignore_missing: true
      ignore_failure: true
  - convert:
      field: winlog.event_data.ParentProcessId
      target_field: process.parent.pid
      type: long
      ignore_failure: true
      ignore_missing: true
  - rename:
      field: winlog.event_data.ParentImage
      target_field: process.parent.executable
      ignore_missing: true
      ignore_failure: true
  - rename:
      field: winlog.event_data.ParentCommandLine
      target_field: process.parent.command_line
      ignore_missing: true
      ignore_failure: true
  - rename:
      field: winlog.event_data.OriginalFileName
      target_field: process.pe.original_file_name
      ignore_missing: true
      ignore_failure: true
      if: ctx.event.code != "7"
  - set:
      field: process.pe.company
      copy_from: winlog.event_data.Company
      ignore_empty_value: true
      ignore_failure: true
      if: ctx.event.code != "7"
  - set:
      field: process.pe.description
      copy_from: winlog.event_data.Description
      ignore_empty_value: true
      ignore_failure: true
      if: ctx.event.code != "7"
  - set:
      field: process.pe.file_version
      copy_from: winlog.event_data.FileVersion
      ignore_empty_value: true
      ignore_failure: true
      if: ctx.event.code != "7"
  - set:
      field: process.pe.product
      copy_from: winlog.event_data.Product
      ignore_empty_value: true
      ignore_failure: true
      if: ctx.event.code != "7"

  - script:
      description: Implements Windows-like SplitCommandLine
      lang: painless
      if: |-
        (ctx?.process?.command_line != null && ctx.process.command_line != "") || 
        (ctx?.process?.parent?.command_line != null && ctx.process.parent.command_line != "")
      source: |-
        // appendBSBytes appends n '\\' bytes to b and returns the resulting slice.
        def appendBSBytes(StringBuilder b, int n) {
            for (; n > 0; n--) {
                b.append('\\');
            }
            return b;
        }

        // readNextArg splits command line string cmd into next
        // argument and command line remainder.
        def readNextArg(String cmd) {
            def b = new StringBuilder();
            boolean inquote;
            int nslash;
            for (; cmd.length() > 0; cmd = cmd.substring(1)) {
                def c = cmd.charAt(0);
                if (c == (char)' ' || c == (char)0x09) {
                    if (!inquote) {
                        return [
                            "arg":  appendBSBytes(b, nslash).toString(),
                            "rest": cmd.substring(1)
                        ];
                    }
                } else if (c == (char)'"') {
                    b = appendBSBytes(b, nslash/2);
                    if (nslash%2 == 0) {
                        // use "Prior to 2008" rule from
                        // http://daviddeley.com/autohotkey/parameters/parameters.htm
                        // section 5.2 to deal with double double quotes
                        if (inquote && cmd.length() > 1 && cmd.charAt(1) == (char)'"') {
                            b.append(c);
                            cmd = cmd.substring(1);
                        }
                        inquote = !inquote;
                    } else {
                        b.append(c);
                    }
                    nslash = 0;
                    continue;
                } else if (c == (char)'\\') {
                    nslash++;
                    continue;
                }
                b = appendBSBytes(b, nslash);
                nslash = 0;
                b.append(c);
            }
            return [
                "arg":  appendBSBytes(b, nslash).toString(), 
                "rest": ''
            ];
        }

        // commandLineToArgv splits a command line into individual argument
        // strings, following the Windows conventions documented
        // at http://daviddeley.com/autohotkey/parameters/parameters.htm#WINARGV
        // Original implementation found at: https://github.com/golang/go/commit/39c8d2b7faed06b0e91a1ad7906231f53aab45d1
        def commandLineToArgv(String cmd) {
            def args = new ArrayList();
            while (cmd.length() > 0) {
                if (cmd.charAt(0) == (char)' ' || cmd.charAt(0) == (char)0x09) {
                    cmd = cmd.substring(1);
                    continue;
                }
                def next = readNextArg(cmd);
                cmd = next.rest;
                args.add(next.arg);
            }
            return args;
        }

        def cmd = ctx?.process?.command_line;
        if (cmd != null && cmd != "") {
          ctx.process.args = commandLineToArgv(cmd);
          ctx.process.args_count = ctx.process.args.length;
        }

        def parentCmd = ctx?.process?.parent?.command_line;
        if (parentCmd != null && parentCmd != "") {
          ctx.process.parent.args = commandLineToArgv(parentCmd);
          ctx.process.parent.args_count = ctx.process.parent.args.length;
        }

  - script:
      description: Adds process name information.
      lang: painless
      if: |-
        (ctx?.process?.executable != null && ctx.process.executable.length() > 1) ||
        (ctx?.process?.parent?.executable != null && ctx.process.parent.executable.length() > 1)
      source: |-
        def getProcessName(def path) {
          def idx = path.lastIndexOf("\\");
          if (idx > -1) {
              return path.substring(idx+1);
          }
          return "";
        }

        def cmd = ctx?.process?.executable;
        if (cmd != null && cmd != "" && ctx?.process?.name == null) {
          def name = getProcessName(cmd);
          if (name != "") {
            ctx.process.name = name;
          }
        }

        def parentCmd = ctx?.process?.parent?.executable;
        if (parentCmd != null && parentCmd != "" && ctx?.process?.parent?.name == null) {
          def name = getProcessName(parentCmd);
          if (name != "") {
            ctx.process.parent.name = name;
          }
        }

## File fields

  - rename:
      field: _temp.hashes
      target_field: file.hash
      if: |-
        ctx?._temp?.hashes != null &&
        ["6", "7", "15"].contains(ctx.event.code)
  - rename:
      field: file.hash.imphash
      target_field: file.pe.imphash
      ignore_failure: true
      ignore_missing: true
  - rename:
      field: winlog.event_data.TargetFilename
      target_field: file.path
      ignore_missing: true
      ignore_failure: true
  - rename:
      field: winlog.event_data.Device
      target_field: file.path
      ignore_missing: true
      ignore_failure: true
  - rename:
      field: winlog.event_data.PipeName
      target_field: file.name
      ignore_missing: true
      ignore_failure: true
  - rename:
      field: winlog.event_data.ImageLoaded
      target_field: file.path
      ignore_missing: true
      ignore_failure: true
  - set:
      field: file.code_signature.subject_name
      copy_from: winlog.event_data.Signature
      ignore_failure: true
      ignore_empty_value: true
  - set:
      field: file.code_signature.status
      copy_from: winlog.event_data.SignatureStatus
      ignore_failure: true
      ignore_empty_value: true
  - rename:
      field: winlog.event_data.OriginalFileName
      target_field: file.pe.original_file_name
      ignore_missing: true
      ignore_failure: true
      if: ctx.event.code == "7"
  - set:
      field: file.pe.company
      copy_from: winlog.event_data.Company
      ignore_failure: true
      ignore_empty_value: true
      if: ctx.event.code == "7"
  - set:
      field: file.pe.description
      copy_from: winlog.event_data.Description
      ignore_failure: true
      ignore_empty_value: true
      if: ctx.event.code == "7"
  - set:
      field: file.pe.file_version
      copy_from: winlog.event_data.FileVersion
      ignore_failure: true
      ignore_empty_value: true
      if: ctx.event.code == "7"
  - set:
      field: file.pe.product
      copy_from: winlog.event_data.Product
      ignore_failure: true
      ignore_empty_value: true
      if: ctx.event.code == "7"
  - set:
      field: file.code_signature.signed
      value: true
      if: ctx?.winlog?.event_data?.Signed == true
  - set:
      field: file.code_signature.valid
      value: true
      if: ctx?.winlog?.event_data?.SignatureStatus == "Valid"

  - script:
      description: Adds file information.
      lang: painless
      if: ctx?.file?.path != null && ctx.file.path.length() > 1
      source: |-
        def path = ctx.file.path;
        def idx = path.lastIndexOf("\\");
        if (idx > -1) {
            if (ctx?.file == null) {
                ctx.file = new HashMap();
            }
            ctx.file.name = path.substring(idx+1);
            ctx.file.directory = path.substring(0, idx);

            def extIdx = path.lastIndexOf(".");
            if (extIdx > -1) {
                ctx.file.extension = path.substring(extIdx+1);
            }
        }

## Network, Destination, and Source fields

  - rename:
      field: winlog.event_data.Protocol
      target_field: network.transport
      ignore_missing: true
      ignore_failure: true
  - rename:
      field: winlog.event_data.DestinationPortName
      target_field: network.protocol
      ignore_missing: true
      ignore_failure: true
      if: ctx.event.code != "22"
  - rename:
      field: winlog.event_data.SourcePortName
      target_field: network.protocol
      ignore_missing: true
      ignore_failure: true
      if: ctx.event.code != "22"
  - set:
      field: network.protocol
      value: dns
      if: ctx.event.code == "22"
  - convert:
      field: winlog.event_data.SourceIp
      target_field: source.ip
      type: ip
      ignore_failure: true
      ignore_missing: true
  - rename:
      field: winlog.event_data.SourceHostname
      target_field: source.domain
      ignore_missing: true
      ignore_failure: true
  - convert:
      field: winlog.event_data.SourcePort
      target_field: source.port
      type: long
      ignore_failure: true
      ignore_missing: true
  - convert:
      field: winlog.event_data.DestinationIp
      target_field: destination.ip
      type: ip
      ignore_failure: true
      ignore_missing: true
  - rename:
      field: winlog.event_data.DestinationHostname
      target_field: destination.domain
      ignore_missing: true
      ignore_failure: true
  - convert:
      field: winlog.event_data.DestinationPort
      target_field: destination.port
      type: long
      ignore_failure: true
      ignore_missing: true
  - rename:
      field: winlog.event_data.QueryName
      target_field: dns.question.name
      ignore_missing: true
      ignore_failure: true
  - set:
      field: network.direction
      value: egress
      if: ctx?.winlog?.event_data?.Initiated == "true"
  - set:
      field: network.direction
      value: ingress
      if: ctx?.winlog?.event_data?.Initiated == "false"
  - set:
      field: network.type
      value: ipv4
      if: ctx?.winlog?.event_data?.SourceIsIpv6 == "false"
  - set:
      field: network.type
      value: ipv6
      if: ctx?.winlog?.event_data?.SourceIsIpv6 == "true"
  - script:
      description: |
        Splits the QueryResults field that contains the DNS responses.
        Example: "type:  5 f2.taboola.map.fastly.net;::ffff:151.101.66.2;::ffff:151.101.130.2;::ffff:151.101.194.2;::ffff:151.101.2.2;"
      lang: painless
      if: ctx?.winlog?.event_data?.QueryResults != null
      params:
        "1": "A"
        "2": "NS"
        "3": "MD"
        "4": "MF"
        "5": "CNAME"
        "6": "SOA"
        "7": "MB"
        "8": "MG"
        "9": "MR"
        "10": "NULL"
        "11": "WKS"
        "12": "PTR"
        "13": "HINFO"
        "14": "MINFO"
        "15": "MX"
        "16": "TXT"
        "17": "RP"
        "18": "AFSDB"
        "19": "X25"
        "20": "ISDN"
        "21": "RT"
        "22": "NSAP"
        "23": "NSAPPTR"
        "24": "SIG"
        "25": "KEY"
        "26": "PX"
        "27": "GPOS"
        "28": "AAAA"
        "29": "LOC"
        "30": "NXT"
        "31": "EID"
        "32": "NIMLOC"
        "33": "SRV"
        "34": "ATMA"
        "35": "NAPTR"
        "36": "KX"
        "37": "CERT"
        "38": "A6"
        "39": "DNAME"
        "40": "SINK"
        "41": "OPT"
        "43": "DS"
        "46": "RRSIG"
        "47": "NSEC"
        "48": "DNSKEY"
        "49": "DHCID"
        "100": "UINFO"
        "101": "UID"
        "102": "GID"
        "103": "UNSPEC"
        "248": "ADDRS"
        "249": "TKEY"
        "250": "TSIG"
        "251": "IXFR"
        "252": "AXFR"
        "253": "MAILB"
        "254": "MAILA"
        "255": "ANY"
        "65281": "WINS"
        "65282": "WINSR"
      source: |-
        def results = /;/.split(ctx.winlog.event_data.QueryResults);
        def answers = new ArrayList();
        def ips = new ArrayList();
        def relatedHosts = new ArrayList();
        for (def i = 0; i < results.length; i++) {
          def answer = results[i];
          if (answer == "") {
            continue;
          }

          if (answer.startsWith("type:")) {
            def parts = /\s+/.split(answer);
            if (parts.length != 3) {
              throw new Exception("unexpected QueryResult format");
            }

            answers.add([
              "type": params[parts[1]],
              "data": parts[2]
            ]);
            relatedHosts.add(parts[2]);
          } else {
            answer = answer.replace("::ffff:", "");
            ips.add(answer);
          }
        }

        if (answers.length > 0) {
          ctx.dns.answers = answers;
        }
        if (ips.length > 0) {
          ctx.dns.resolved_ip = ips;
        }
        if (relatedHosts.length > 0) {
          if (ctx?.related == null) {
            ctx.related = new HashMap();
          }
          ctx.related.hosts = relatedHosts;
        }
  - foreach:
      field: dns.resolved_ip
      ignore_missing: true
      processor:
        convert:
          field: _ingest._value
          type: ip
          on_failure:
            - remove:
                field: _ingest._value
  - script:
      description: Convert V4MAPPED addresses.
      lang: painless
      if: ctx?.dns?.resolved_ip != null
      source: |-
        if (ctx.dns.answers == null) {
          ctx.dns.answers = new ArrayList();
        }
        for (def i = 0; i < ctx.dns.resolved_ip.length; i++) {
          def ip = ctx.dns.resolved_ip[i];
          if (ip == null) {
            ctx.dns.resolved_ip.remove(i);
            continue;
          }

          // Synthesize record type based on IP address type.
          def type = "A";
          if (ip.indexOf(":") != -1) {
            type = "AAAA";
          }
          ctx.dns.answers.add([
            "type": type,
            "data": ip
          ]);
        }
  - registered_domain:
      field: dns.question.name
      target_field: dns.question
      ignore_failure: true
      ignore_missing: true
  - append:
      field: related.hosts
      value: "{{dns.question.name}}"
      allow_duplicates: false
      if: ctx?.dns?.question?.name != null && ctx?.dns?.question?.name != ""
  - remove:
      description: Remove dns.question.domain because it is not part of ECS and is redundant with dns.question.name.
      field: dns.question.domain
      ignore_missing: true
      ignore_failure: true
  - foreach:
      field: dns.resolved_ip
      ignore_missing: true
      processor:
        append:
          field: related.ip
          value: "{{_ingest._value}}"
          allow_duplicates: false
          ignore_failure: true
  - community_id:
      ignore_failure: true
      ignore_missing: false

## User fields

  - set:
      field: user.id
      copy_from: winlog.user.identifier
      ignore_empty_value: true
      ignore_failure: true
  - split:
      field: winlog.event_data.User
      target_field: "_temp.user_parts"
      separator: '\\'
      if: ctx?.winlog?.event_data?.User != null
  - set:
      field: user.domain
      value: "{{_temp.user_parts.0}}"
      ignore_failure: true
      ignore_empty_value: true
      if: ctx?._temp?.user_parts != null && ctx._temp.user_parts.size() == 2
  - set:
      field: user.name
      value: "{{_temp.user_parts.1}}"
      ignore_failure: true
      ignore_empty_value: true
      if: ctx?._temp?.user_parts != null && ctx._temp.user_parts.size() == 2

## Sysmon fields

  - rename:
      field: winlog.event_data.QueryStatus
      target_field: sysmon.dns.status
      ignore_missing: true
      ignore_failure: true
  - script:
      description: Translate DNS Query status.
      lang: painless
      params:
        "5": "ERROR_ACCESS_DENIED"
        "0": "SUCCESS"
        "8": "ERROR_NOT_ENOUGH_MEMORY"
        "13": "ERROR_INVALID_DATA"
        "14": "ERROR_OUTOFMEMORY"
        "123": "ERROR_INVALID_NAME"
        "1214": "ERROR_INVALID_NETNAME"
        "1223": "ERROR_CANCELLED"
        "1460": "ERROR_TIMEOUT"
        "4312": "ERROR_OBJECT_NOT_FOUND"
        "9001": "DNS_ERROR_RCODE_FORMAT_ERROR"
        "9002": "DNS_ERROR_RCODE_SERVER_FAILURE"
        "9003": "DNS_ERROR_RCODE_NAME_ERROR"
        "9004": "DNS_ERROR_RCODE_NOT_IMPLEMENTED"
        "9005": "DNS_ERROR_RCODE_REFUSED"
        "9006": "DNS_ERROR_RCODE_YXDOMAIN"
        "9007": "DNS_ERROR_RCODE_YXRRSET"
        "9008": "DNS_ERROR_RCODE_NXRRSET"
        "9009": "DNS_ERROR_RCODE_NOTAUTH"
        "9010": "DNS_ERROR_RCODE_NOTZONE"
        "9016": "DNS_ERROR_RCODE_BADSIG"
        "9017": "DNS_ERROR_RCODE_BADKEY"
        "9018": "DNS_ERROR_RCODE_BADTIME"
        "9101": "DNS_ERROR_KEYMASTER_REQUIRED"
        "9102": "DNS_ERROR_NOT_ALLOWED_ON_SIGNED_ZONE"
        "9103": "DNS_ERROR_NSEC3_INCOMPATIBLE_WITH_RSA_SHA1"
        "9104": "DNS_ERROR_NOT_ENOUGH_SIGNING_KEY_DESCRIPTORS"
        "9105": "DNS_ERROR_UNSUPPORTED_ALGORITHM"
        "9106": "DNS_ERROR_INVALID_KEY_SIZE"
        "9107": "DNS_ERROR_SIGNING_KEY_NOT_ACCESSIBLE"
        "9108": "DNS_ERROR_KSP_DOES_NOT_SUPPORT_PROTECTION"
        "9109": "DNS_ERROR_UNEXPECTED_DATA_PROTECTION_ERROR"
        "9110": "DNS_ERROR_UNEXPECTED_CNG_ERROR"
        "9111": "DNS_ERROR_UNKNOWN_SIGNING_PARAMETER_VERSION"
        "9112": "DNS_ERROR_KSP_NOT_ACCESSIBLE"
        "9113": "DNS_ERROR_TOO_MANY_SKDS"
        "9114": "DNS_ERROR_INVALID_ROLLOVER_PERIOD"
        "9115": "DNS_ERROR_INVALID_INITIAL_ROLLOVER_OFFSET"
        "9116": "DNS_ERROR_ROLLOVER_IN_PROGRESS"
        "9117": "DNS_ERROR_STANDBY_KEY_NOT_PRESENT"
        "9118": "DNS_ERROR_NOT_ALLOWED_ON_ZSK"
        "9119": "DNS_ERROR_NOT_ALLOWED_ON_ACTIVE_SKD"
        "9120": "DNS_ERROR_ROLLOVER_ALREADY_QUEUED"
        "9121": "DNS_ERROR_NOT_ALLOWED_ON_UNSIGNED_ZONE"
        "9122": "DNS_ERROR_BAD_KEYMASTER"
        "9123": "DNS_ERROR_INVALID_SIGNATURE_VALIDITY_PERIOD"
        "9124": "DNS_ERROR_INVALID_NSEC3_ITERATION_COUNT"
        "9125": "DNS_ERROR_DNSSEC_IS_DISABLED"
        "9126": "DNS_ERROR_INVALID_XML"
        "9127": "DNS_ERROR_NO_VALID_TRUST_ANCHORS"
        "9128": "DNS_ERROR_ROLLOVER_NOT_POKEABLE"
        "9129": "DNS_ERROR_NSEC3_NAME_COLLISION"
        "9130": "DNS_ERROR_NSEC_INCOMPATIBLE_WITH_NSEC3_RSA_SHA1"
        "9501": "DNS_INFO_NO_RECORDS"
        "9502": "DNS_ERROR_BAD_PACKET"
        "9503": "DNS_ERROR_NO_PACKET"
        "9504": "DNS_ERROR_RCODE"
        "9505": "DNS_ERROR_UNSECURE_PACKET"
        "9506": "DNS_REQUEST_PENDING"
        "9551": "DNS_ERROR_INVALID_TYPE"
        "9552": "DNS_ERROR_INVALID_IP_ADDRESS"
        "9553": "DNS_ERROR_INVALID_PROPERTY"
        "9554": "DNS_ERROR_TRY_AGAIN_LATER"
        "9555": "DNS_ERROR_NOT_UNIQUE"
        "9556": "DNS_ERROR_NON_RFC_NAME"
        "9557": "DNS_STATUS_FQDN"
        "9558": "DNS_STATUS_DOTTED_NAME"
        "9559": "DNS_STATUS_SINGLE_PART_NAME"
        "9560": "DNS_ERROR_INVALID_NAME_CHAR"
        "9561": "DNS_ERROR_NUMERIC_NAME"
        "9562": "DNS_ERROR_NOT_ALLOWED_ON_ROOT_SERVER"
        "9563": "DNS_ERROR_NOT_ALLOWED_UNDER_DELEGATION"
        "9564": "DNS_ERROR_CANNOT_FIND_ROOT_HINTS"
        "9565": "DNS_ERROR_INCONSISTENT_ROOT_HINTS"
        "9566": "DNS_ERROR_DWORD_VALUE_TOO_SMALL"
        "9567": "DNS_ERROR_DWORD_VALUE_TOO_LARGE"
        "9568": "DNS_ERROR_BACKGROUND_LOADING"
        "9569": "DNS_ERROR_NOT_ALLOWED_ON_RODC"
        "9570": "DNS_ERROR_NOT_ALLOWED_UNDER_DNAME"
        "9571": "DNS_ERROR_DELEGATION_REQUIRED"
        "9572": "DNS_ERROR_INVALID_POLICY_TABLE"
        "9573": "DNS_ERROR_ADDRESS_REQUIRED"
        "9601": "DNS_ERROR_ZONE_DOES_NOT_EXIST"
        "9602": "DNS_ERROR_NO_ZONE_INFO"
        "9603": "DNS_ERROR_INVALID_ZONE_OPERATION"
        "9604": "DNS_ERROR_ZONE_CONFIGURATION_ERROR"
        "9605": "DNS_ERROR_ZONE_HAS_NO_SOA_RECORD"
        "9606": "DNS_ERROR_ZONE_HAS_NO_NS_RECORDS"
        "9607": "DNS_ERROR_ZONE_LOCKED"
        "9608": "DNS_ERROR_ZONE_CREATION_FAILED"
        "9609": "DNS_ERROR_ZONE_ALREADY_EXISTS"
        "9610": "DNS_ERROR_AUTOZONE_ALREADY_EXISTS"
        "9611": "DNS_ERROR_INVALID_ZONE_TYPE"
        "9612": "DNS_ERROR_SECONDARY_REQUIRES_MASTER_IP"
        "9613": "DNS_ERROR_ZONE_NOT_SECONDARY"
        "9614": "DNS_ERROR_NEED_SECONDARY_ADDRESSES"
        "9615": "DNS_ERROR_WINS_INIT_FAILED"
        "9616": "DNS_ERROR_NEED_WINS_SERVERS"
        "9617": "DNS_ERROR_NBSTAT_INIT_FAILED"
        "9618": "DNS_ERROR_SOA_DELETE_INVALID"
        "9619": "DNS_ERROR_FORWARDER_ALREADY_EXISTS"
        "9620": "DNS_ERROR_ZONE_REQUIRES_MASTER_IP"
        "9621": "DNS_ERROR_ZONE_IS_SHUTDOWN"
        "9622": "DNS_ERROR_ZONE_LOCKED_FOR_SIGNING"
        "9651": "DNS_ERROR_PRIMARY_REQUIRES_DATAFILE"
        "9652": "DNS_ERROR_INVALID_DATAFILE_NAME"
        "9653": "DNS_ERROR_DATAFILE_OPEN_FAILURE"
        "9654": "DNS_ERROR_FILE_WRITEBACK_FAILED"
        "9655": "DNS_ERROR_DATAFILE_PARSING"
        "9701": "DNS_ERROR_RECORD_DOES_NOT_EXIST"
        "9702": "DNS_ERROR_RECORD_FORMAT"
        "9703": "DNS_ERROR_NODE_CREATION_FAILED"
        "9704": "DNS_ERROR_UNKNOWN_RECORD_TYPE"
        "9705": "DNS_ERROR_RECORD_TIMED_OUT"
        "9706": "DNS_ERROR_NAME_NOT_IN_ZONE"
        "9707": "DNS_ERROR_CNAME_LOOP"
        "9708": "DNS_ERROR_NODE_IS_CNAME"
        "9709": "DNS_ERROR_CNAME_COLLISION"
        "9710": "DNS_ERROR_RECORD_ONLY_AT_ZONE_ROOT"
        "9711": "DNS_ERROR_RECORD_ALREADY_EXISTS"
        "9712": "DNS_ERROR_SECONDARY_DATA"
        "9713": "DNS_ERROR_NO_CREATE_CACHE_DATA"
        "9714": "DNS_ERROR_NAME_DOES_NOT_EXIST"
        "9715": "DNS_WARNING_PTR_CREATE_FAILED"
        "9716": "DNS_WARNING_DOMAIN_UNDELETED"
        "9717": "DNS_ERROR_DS_UNAVAILABLE"
        "9718": "DNS_ERROR_DS_ZONE_ALREADY_EXISTS"
        "9719": "DNS_ERROR_NO_BOOTFILE_IF_DS_ZONE"
        "9720": "DNS_ERROR_NODE_IS_DNAME"
        "9721": "DNS_ERROR_DNAME_COLLISION"
        "9722": "DNS_ERROR_ALIAS_LOOP"
        "9751": "DNS_INFO_AXFR_COMPLETE"
        "9752": "DNS_ERROR_AXFR"
        "9753": "DNS_INFO_ADDED_LOCAL_WINS"
        "9801": "DNS_STATUS_CONTINUE_NEEDED"
        "9851": "DNS_ERROR_NO_TCPIP"
        "9852": "DNS_ERROR_NO_DNS_SERVERS"
        "9901": "DNS_ERROR_DP_DOES_NOT_EXIST"
        "9902": "DNS_ERROR_DP_ALREADY_EXISTS"
        "9903": "DNS_ERROR_DP_NOT_ENLISTED"
        "9904": "DNS_ERROR_DP_ALREADY_ENLISTED"
        "9905": "DNS_ERROR_DP_NOT_AVAILABLE"
        "9906": "DNS_ERROR_DP_FSMO_ERROR"
        "9911": "DNS_ERROR_RRL_NOT_ENABLED"
        "9912": "DNS_ERROR_RRL_INVALID_WINDOW_SIZE"
        "9913": "DNS_ERROR_RRL_INVALID_IPV4_PREFIX"
        "9914": "DNS_ERROR_RRL_INVALID_IPV6_PREFIX"
        "9915": "DNS_ERROR_RRL_INVALID_TC_RATE"
        "9916": "DNS_ERROR_RRL_INVALID_LEAK_RATE"
        "9917": "DNS_ERROR_RRL_LEAK_RATE_LESSTHAN_TC_RATE"
        "9921": "DNS_ERROR_VIRTUALIZATION_INSTANCE_ALREADY_EXISTS"
        "9922": "DNS_ERROR_VIRTUALIZATION_INSTANCE_DOES_NOT_EXIST"
        "9923": "DNS_ERROR_VIRTUALIZATION_TREE_LOCKED"
        "9924": "DNS_ERROR_INVAILD_VIRTUALIZATION_INSTANCE_NAME"
        "9925": "DNS_ERROR_DEFAULT_VIRTUALIZATION_INSTANCE"
        "9951": "DNS_ERROR_ZONESCOPE_ALREADY_EXISTS"
        "9952": "DNS_ERROR_ZONESCOPE_DOES_NOT_EXIST"
        "9953": "DNS_ERROR_DEFAULT_ZONESCOPE"
        "9954": "DNS_ERROR_INVALID_ZONESCOPE_NAME"
        "9955": "DNS_ERROR_NOT_ALLOWED_WITH_ZONESCOPES"
        "9956": "DNS_ERROR_LOAD_ZONESCOPE_FAILED"
        "9957": "DNS_ERROR_ZONESCOPE_FILE_WRITEBACK_FAILED"
        "9958": "DNS_ERROR_INVALID_SCOPE_NAME"
        "9959": "DNS_ERROR_SCOPE_DOES_NOT_EXIST"
        "9960": "DNS_ERROR_DEFAULT_SCOPE"
        "9961": "DNS_ERROR_INVALID_SCOPE_OPERATION"
        "9962": "DNS_ERROR_SCOPE_LOCKED"
        "9963": "DNS_ERROR_SCOPE_ALREADY_EXISTS"
        "9971": "DNS_ERROR_POLICY_ALREADY_EXISTS"
        "9972": "DNS_ERROR_POLICY_DOES_NOT_EXIST"
        "9973": "DNS_ERROR_POLICY_INVALID_CRITERIA"
        "9974": "DNS_ERROR_POLICY_INVALID_SETTINGS"
        "9975": "DNS_ERROR_CLIENT_SUBNET_IS_ACCESSED"
        "9976": "DNS_ERROR_CLIENT_SUBNET_DOES_NOT_EXIST"
        "9977": "DNS_ERROR_CLIENT_SUBNET_ALREADY_EXISTS"
        "9978": "DNS_ERROR_SUBNET_DOES_NOT_EXIST"
        "9979": "DNS_ERROR_SUBNET_ALREADY_EXISTS"
        "9980": "DNS_ERROR_POLICY_LOCKED"
        "9981": "DNS_ERROR_POLICY_INVALID_WEIGHT"
        "9982": "DNS_ERROR_POLICY_INVALID_NAME"
        "9983": "DNS_ERROR_POLICY_MISSING_CRITERIA"
        "9984": "DNS_ERROR_INVALID_CLIENT_SUBNET_NAME"
        "9985": "DNS_ERROR_POLICY_PROCESSING_ORDER_INVALID"
        "9986": "DNS_ERROR_POLICY_SCOPE_MISSING"
        "9987": "DNS_ERROR_POLICY_SCOPE_NOT_ALLOWED"
        "9988": "DNS_ERROR_SERVERSCOPE_IS_REFERENCED"
        "9989": "DNS_ERROR_ZONESCOPE_IS_REFERENCED"
        "9990": "DNS_ERROR_POLICY_INVALID_CRITERIA_CLIENT_SUBNET"
        "9991": "DNS_ERROR_POLICY_INVALID_CRITERIA_TRANSPORT_PROTOCOL"
        "9992": "DNS_ERROR_POLICY_INVALID_CRITERIA_NETWORK_PROTOCOL"
        "9993": "DNS_ERROR_POLICY_INVALID_CRITERIA_INTERFACE"
        "9994": "DNS_ERROR_POLICY_INVALID_CRITERIA_FQDN"
        "9995": "DNS_ERROR_POLICY_INVALID_CRITERIA_QUERY_TYPE"
        "9996": "DNS_ERROR_POLICY_INVALID_CRITERIA_TIME_OF_DAY"
        "10054": "WSAECONNRESET"
        "10055": "WSAENOBUFS"
        "10060": "WSAETIMEDOUT"
      if: ctx?.sysmon?.dns?.status != null && ctx?.sysmon?.dns?.status != ""
      source: |-
        def status = params[ctx.sysmon.dns.status];
        if (status != null) {
          ctx.sysmon.dns.status = status;
        }
  - convert:
      field: winlog.event_data.Archived
      target_field: sysmon.file.archived
      type: boolean
      ignore_missing: true
      ignore_failure: true
  - convert:
      field: winlog.event_data.IsExecutable
      target_field: sysmon.file.is_executable
      type: boolean
      ignore_missing: true
      ignore_failure: true

## Related fields

  - append:
      field: related.user
      value: "{{user.name}}"
      ignore_failure: true
      allow_duplicates: false
      if: ctx?.user?.name != null && ctx.user.name != ""
  - append:
      field: related.ip
      value: "{{source.ip}}"
      ignore_failure: true
      allow_duplicates: false
      if: ctx?.source?.ip != null && ctx.source.ip != ""
  - append:
      field: related.ip
      value: "{{destination.ip}}"
      ignore_failure: true
      allow_duplicates: false
      if: ctx?.destination?.ip != null && ctx.destination.ip != ""

## Registry fields

  - script:
      description: Set registry fields.
      lang: painless
      if: |-
        ctx?.winlog?.event_data?.TargetObject != null && ["12", "13", "14"].contains(ctx.event.code)
      params:
        HKEY_CLASSES_ROOT: "HKCR"
        HKCR: "HKCR"
        HKEY_CURRENT_CONFIG: "HKCC"
        HKCC: "HKCC"
        HKEY_CURRENT_USER: "HKCU"
        HKCU: "HKCU"
        HKEY_DYN_DATA: "HKDD"
        HKDD: "HKDD"
        HKEY_LOCAL_MACHINE: "HKLM"
        HKLM: "HKLM"
        HKEY_PERFORMANCE_DATA: "HKPD"
        HKPD: "HKPD"
        HKEY_USERS: "HKU"
        HKU: "HKU"
      source: |-
        ctx.registry = new HashMap();
        Pattern qwordRegex = /(?i)QWORD \(((0x[0-9A-F]{8})-(0x[0-9A-F]{8}))\)/;
        Pattern dwordRegex = /(?i)DWORD \((0x[0-9A-F]{8})\)/;
        Pattern binDataRegex = /Binary Data/;

        def path = ctx.winlog.event_data.TargetObject;
        ctx.registry.path = path;

        def pathTokens = Arrays.asList(/\\/.split(path));
        def hive = params[pathTokens[0]];
        if (hive != null) {
          ctx.registry.hive = hive;
          if (pathTokens.length > 1) {
            ctx.registry.key = pathTokens.subList(1, pathTokens.length).join("\\");
          }
        }

        def value = pathTokens[pathTokens.length - 1];
        ctx.registry.value = value;

        def data = ctx?.winlog?.event_data?.Details;
        if (data != null && data != "") {
          def prefixLen = 2; // to remove 0x prefix
          def dataValue = "";
          def dataType = "";
          def matcher = qwordRegex.matcher(data);
          if (matcher.matches()) {
            def parsedHighByte = Long.parseLong(matcher.group(2).substring(prefixLen), 16);
            def parsedLowByte = Long.parseLong(matcher.group(3).substring(prefixLen), 16);
            if (!Double.isNaN(parsedHighByte) && !Double.isNaN(parsedLowByte)) {
              dataType = "SZ_QWORD";
              dataValue = Long.toString(((parsedHighByte << 8) + parsedLowByte));
              ctx.registry.data = [
                "strings": [dataValue],
                "type": dataType
              ];
            }
            return;
          }

          matcher = dwordRegex.matcher(data);
          if (matcher.matches()) {
            def parsedValue = Long.parseLong(matcher.group(1).substring(prefixLen), 16);
            if (!Double.isNaN(parsedValue)) {
              dataType = "SZ_DWORD";
              dataValue = Long.toString(parsedValue);
              ctx.registry.data = [
                "strings": [dataValue],
                "type": dataType
              ];
            }
            return;
          }

          matcher = binDataRegex.matcher(data);
          if (matcher.matches()) {
            // Data type could be REG_BINARY or REG_MULTI_SZ
            ctx.registry.data = [
              "strings": [data],
              "type": "REG_BINARY"
            ];
            return;
          }

          // REG_SZ or REG_EXPAND_SZ
          ctx.registry.data = [
            "strings": [data],
            "type": "REG_SZ"
          ];
        }

## Cleanup

  - remove:
      field:
        - _temp
        - winlog.event_data.ProcessId
        - winlog.event_data.ParentProcessId
        - winlog.event_data.SourceProcessId
        - winlog.event_data.SourceThreadId
        - winlog.event_data.SourceIp
        - winlog.event_data.SourcePort
        - winlog.event_data.SourcePortName
        - winlog.event_data.DestinationIp
        - winlog.event_data.DestinationPort
        - winlog.event_data.DestinationPortName
        - winlog.event_data.RuleName
        - winlog.event_data.User
        - winlog.event_data.Initiated
        - winlog.event_data.SourceIsIpv6
        - winlog.event_data.DestinationIsIpv6
        - winlog.event_data.QueryStatus
        - winlog.event_data.Archived
        - winlog.event_data.IsExecutable
        - winlog.event_data.QueryResults
        - winlog.event_data.UtcTime
        - winlog.event_data.Hash
        - winlog.event_data.Hashes
        - winlog.event_data.TargetObject
        - winlog.event_data.Details
        - winlog.time_created
        - winlog.level
      ignore_failure: true
      ignore_missing: true
  - remove:
      description: Remove empty event data.
      field: winlog.event_data
      ignore_missing: true
      ignore_failure: true
      if: ctx?.winlog?.event_data != null && ctx.winlog.event_data.size() == 0

on_failure:
  - set:
      field: "error.message"
      value: |-
        Processor "{{ _ingest.on_failure_processor_type }}" with tag "{{ _ingest.on_failure_processor_tag }}" in pipeline "{{ _ingest.on_failure_pipeline }}" failed with message "{{ _ingest.on_failure_message }}"
