Skip to content

[8.18](backport #44026) Fix EvtVarTypeAnsiString conversion in winlogbeat#45229

Merged
marc-gr merged 2 commits into8.18from
mergify/bp/8.18/pr-44026
Jul 8, 2025
Merged

[8.18](backport #44026) Fix EvtVarTypeAnsiString conversion in winlogbeat#45229
marc-gr merged 2 commits into8.18from
mergify/bp/8.18/pr-44026

Conversation

@mergify
Copy link
Copy Markdown
Contributor

@mergify mergify Bot commented Jul 8, 2025

Handle null terminator in ANSIBytesToString

WHAT:

Trim AnsiStringVal to the null terminator in the ANSIBytesToString function.

WHY:

In winlogbeat/sys/wineventlog/syscall_windows.go, there's a recurring pattern of _EvtGet* WinAPI functions called twice, first with a nil buffer pointer, to establish the required buffer size. Indeed, looking at e.g. documentation for EvtGetEventMetadataProperty this seems to be the right way. However, despite the phrase "required buffer size" in the docs, the value returned via EventMetadataPropertyBufferUsed is not guaranteed to exactly describe the retrieved property size.

The EvtVariant.Data method makes an implicit assumption that variable-length event data types fill the buf[offset:] byte slice exactly, factoring in further golang-specific interpretation. This certainly isn't true for both flavours of strings, for two reasons:

  • The null terminator is not part of a string in golang.
  • wevtapi.dll!EvtGetEventMetadataProperty occasionally returns a buffer size rounded up to align to 4 or 8

I have no clue why inflating the returned buffer size happens, but I have oberved it with my very own eyes, tracing a v9.0.0 winlogbeat on a Win10 machine with default sysmon+winlogbeat configuration. I have it on good authority that a similar thing happens on Windows Server machines.

Anyway, this detail makes no difference for consumers written in languages with first-class null-terminated strings. According to the EVT_VARIANT documentation, both StringVal and AnsiStringVal are null terminated.
The parsing logic for StringVal/EvtVarTypeString is not affected, because it relies on common.UTF16ToUTF8Bytes, which explicitly handles the (wide) null terminator. However, the EvtVarTypeAnsiString case passes the byte slice directly to a Decoder from golang.org/x/text/encoding, which happily interprets null bytes as U+0000.

The end result is that events with fields with inType="win:AnsiString" contain extra code points beyond the correct string; at the very least, a trailing U+0000. This is especially severe for empty strings.
It's easiest to spot with the Microsoft-Windows-Kernel-Boot event provider (enabled and emitting events by default), event code 27, field LoadOptions. The decompiled manifest confirms that its type is AnsiString.

(Please note that I'm not a golang developer. This was the first project I've ever compiled.)

Checklist

  • [Hopefully] My code follows the style guidelines of this project
  • I have commented my code, particularly in hard-to-understand areas
  • [ ] I have made corresponding changes to the documentation
  • [ ] I have made corresponding change to the default configuration files
  • I have added tests that prove my fix is effective or that my feature works
  • I have added an entry in CHANGELOG.next.asciidoc or CHANGELOG-developer.next.asciidoc.

Disruptive User Impact

I can't imagine anyone relies on this bug.

How to test this PR locally

  • Start Winlogbeat (v9.0 / current main) with the default configuration, but edited to use output.file
  • Check the output files for events containing stray \u0000 codepoints -- see the Logs section
  • Compile the patched version
  • Run it again, first deleting the registry
  • Observe no stray null characters

Testing using an EVTX file would've been better, sure, but I couldn't get it to work. I'd be happy to provide one.

Related issues

Logs

With the bug

Example of an affected event, generated as described above. Notice the stray null byte in winlog.event_data.LoadOptions.

{
    "@timestamp": "2025-04-08T23:11:57.099Z",
    "@metadata": {
        "beat": "winlogbeat",
        "type": "_doc",
        "version": "9.0.0"
    },
    "host": {
        "hostname": "(REDACTED)",
        "architecture": "x86_64",
        "os": {
            "build": "19045.5737",
            "type": "windows",
            "platform": "windows",
            "version": "10.0",
            "family": "windows",
            "name": "Windows 10 Pro",
            "kernel": "10.0.19041.5737 (WinBuild.160101.0800)"
        },
        "id": "b7f83483-6ced-4ed7-8a45-ff70f45f821f",
        "ip": [
            "(REDACTED)"
        ],
        "mac": [
            "(REDACTED)"
        ],
        "name": "(REDACTED)"
    },
    "winlog": {
        "provider_guid": "{15CA44FF-4D7A-4BAA-BBA5-0998955E531E}",
        "provider_name": "Microsoft-Windows-Kernel-Boot",
        "computer_name": "(REDACTED)",
        "user": {
            "identifier": "S-1-5-18",
            "domain": "NT AUTHORITY",
            "name": "SYSTEM",
            "type": "Well Known Group"
        },
        "event_data": {
            "BootType": "0",
            "LoadOptions": " NOEXECUTE=OPTIN  NOVGA\u0000"
        },
        "channel": "System",
        "version": 1,
        "event_id": "27",
        "opcode": "Info",
        "record_id": 1351,
        "process": {
            "pid": 4,
            "thread": {
                "id": 8
            }
        },
        "task": "BootType"
    },
    "event": {
        "code": "27",
        "kind": "event",
        "provider": "Microsoft-Windows-Kernel-Boot",
        "action": "BootType",
        "created": "2025-04-22T13:13:08.659Z"
    },
    "log": {
        "level": "information"
    },
    "message": "The boot type was 0.",
    "ecs": {
        "version": "8.0.0"
    },
    "agent": {
        "name": "(REDACTED)",
        "type": "winlogbeat",
        "version": "9.0.0",
        "ephemeral_id": "f2c28050-e72b-4c51-9851-c4162f40e256",
        "id": "12c3d9d7-8121-42d3-864d-c0e2cc957ab6"
    }
}

Another, more severe example, from the same machine and the same event provider (just the relevant portion):

    "winlog": {
        "provider_name": "Microsoft-Windows-Kernel-Boot",
        "opcode": "Info",
        "provider_guid": "{15CA44FF-4D7A-4BAA-BBA5-0998955E531E}",
        "computer_name": "(REDACTED)",
        "event_data": {
            "BootType": "2",
            "LoadOptions": "\u0000\u0000i\u0000c\u0000r\u0000"
        },
        "process": {
            "pid": 4,
            "thread": {
                "id": 16028
            }
        },
        "event_id": "27",
        "record_id": 1786,
        "task": "BootType",
        "version": 1,
        "channel": "System"
    },

I strongly suspect this is a leak from a re-used buffer, and what we're seing in winlog.event_data.LoadOptions are the remnants of "Microsoft" encoded with UTF-16LE.

Patched

Just the winlog part:

"winlog": {
    "user": {
        "identifier": "S-1-5-18",
        "domain": "NT AUTHORITY",
        "name": "SYSTEM",
        "type": "Well Known Group"
    },
    "event_data": {
        "BootType": "0",
        "LoadOptions": " NOEXECUTE=OPTIN  NOVGA"
    },
    "version": 1,
    "record_id": 10,
    "opcode": "Info",
    "provider_guid": "{15CA44FF-4D7A-4BAA-BBA5-0998955E531E}",
    "channel": "System",
    "task": "BootType",
    "event_id": "27",
    "provider_name": "Microsoft-Windows-Kernel-Boot",
    "computer_name": "(REDACTED)",
    "process": {
        "pid": 4,
        "thread": {
            "id": 8
        }
    }
}

And the analogous event to the second "bugged" example; notice the LoadOptions are missing completely, since the field is empty.

"winlog": {
    "event_id": "27",
    "record_id": 2067,
    "task": "BootType",
    "provider_guid": "{15CA44FF-4D7A-4BAA-BBA5-0998955E531E}",
    "provider_name": "Microsoft-Windows-Kernel-Boot",
    "version": 1,
    "event_data": {
        "BootType": "2"
    },
    "channel": "System",
    "opcode": "Info",
    "computer_name": "(REDACTED)",
    "process": {
        "pid": 4,
        "thread": {
            "id": 23660
        }
    }
}

Bonus

How the ~same events look in Event Viewer:
image

image


This is an automatic backport of pull request #44026 done by [Mergify](https://mergify.com).

* Naive first fix

* Fix import order

* Update strings_windows.go

* Update CHANGELOG.next.asciidoc

---------

Co-authored-by: Marc Guasch <marc-gr@users.noreply.github.com>
(cherry picked from commit 6e1d4ea)
@mergify mergify Bot added the backport label Jul 8, 2025
@mergify mergify Bot requested a review from a team as a code owner July 8, 2025 07:17
@mergify mergify Bot added the backport label Jul 8, 2025
@botelastic botelastic Bot added the needs_team Indicates that the issue/PR needs a Team:* label label Jul 8, 2025
@github-actions github-actions Bot added Winlogbeat bugfix Team:Security-Windows Platform Windows Platform Team in Security Solution labels Jul 8, 2025
@elasticmachine
Copy link
Copy Markdown
Contributor

Pinging @elastic/sec-windows-platform (Team:Security-Windows Platform)

@botelastic botelastic Bot removed the needs_team Indicates that the issue/PR needs a Team:* label label Jul 8, 2025
@marc-gr marc-gr enabled auto-merge (squash) July 8, 2025 09:03
@marc-gr marc-gr merged commit ac3f9b3 into 8.18 Jul 8, 2025
29 checks passed
@marc-gr marc-gr deleted the mergify/bp/8.18/pr-44026 branch July 8, 2025 09:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants