diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 792cb4d8b050..33a4f9c6c298 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -389,6 +389,8 @@ https://github.com/elastic/beats/compare/v8.8.1\...main[Check the HEAD diff] *Winlogbeat* +- Add handling for missing `EvtVarType`s in experimental api. {issue}19337[19337] {pull}41418[41418] + *Functionbeat* diff --git a/winlogbeat/sys/strings.go b/winlogbeat/sys/strings.go index d542277f17b7..34325c32f9d1 100644 --- a/winlogbeat/sys/strings.go +++ b/winlogbeat/sys/strings.go @@ -44,3 +44,27 @@ func RemoveWindowsLineEndings(s string) string { s = strings.Replace(s, "\r\n", "\n", -1) return strings.TrimRight(s, "\n") } + +// BinaryToString converts a binary field which is encoded in hexadecimal +// to its string representation. This is equivalent to hex.EncodeToString +// but its output is in uppercase to be equivalent to the windows +// XML formatting of this fields. +func BinaryToString(bin []byte) string { + if len(bin) == 0 { + return "" + } + + const hexTable = "0123456789ABCDEF" + + size := len(bin) * 2 + buffer := make([]byte, size) + + j := 0 + for _, v := range bin { + buffer[j] = hexTable[v>>4] + buffer[j+1] = hexTable[v&0x0f] + j += 2 + } + + return string(buffer) +} diff --git a/winlogbeat/sys/strings_test.go b/winlogbeat/sys/strings_test.go index 0771b7b3cff5..53f2d8ae632d 100644 --- a/winlogbeat/sys/strings_test.go +++ b/winlogbeat/sys/strings_test.go @@ -36,6 +36,12 @@ func TestUTF16BytesToString(t *testing.T) { assert.Equal(t, input, output) } +func TestMakeDisplayableBinaryString(t *testing.T) { + input := []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF} + output := BinaryToString(input) + assert.Equal(t, "0123456789ABCDEF", output) +} + func BenchmarkUTF16BytesToString(b *testing.B) { utf16Bytes := common.StringToUTF16Bytes("A logon was attempted using explicit credentials.") diff --git a/winlogbeat/sys/strings_windows.go b/winlogbeat/sys/strings_windows.go new file mode 100644 index 000000000000..0ce8b09f5d63 --- /dev/null +++ b/winlogbeat/sys/strings_windows.go @@ -0,0 +1,53 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package sys + +import ( + "sync" + + "golang.org/x/sys/windows" + "golang.org/x/text/encoding" + "golang.org/x/text/encoding/charmap" +) + +var getCachedANSIDecoder = sync.OnceValue(initANSIDecoder) + +func initANSIDecoder() *encoding.Decoder { + ansiCP := windows.GetACP() + for _, enc := range charmap.All { + cm, ok := enc.(*charmap.Charmap) + if !ok { + continue + } + cmID, _ := cm.ID() + if uint32(cmID) != ansiCP { + continue + } + return cm.NewDecoder() + } + // This should never be reached. + // If the ANSI Code Page is not found, we will default to + // Windows1252 Code Page, which is default for ANSI in + // many regions and corresponds to Western European languages. + return charmap.Windows1252.NewDecoder() +} + +func ANSIBytesToString(enc []byte) (string, error) { + out, err := getCachedANSIDecoder().Bytes(enc) + return string(out), err +} diff --git a/winlogbeat/sys/wineventlog/syscall_windows.go b/winlogbeat/sys/wineventlog/syscall_windows.go index 6e03a1969cf3..2dde1329e6b5 100644 --- a/winlogbeat/sys/wineventlog/syscall_windows.go +++ b/winlogbeat/sys/wineventlog/syscall_windows.go @@ -442,11 +442,16 @@ func (v EvtVariant) Data(buf []byte) (interface{}, error) { switch typ { case EvtVarTypeNull: return nil, nil - case EvtVarTypeString: + case EvtVarTypeString, EvtVarTypeEvtXml: addr := unsafe.Pointer(&buf[0]) offset := v.ValueAsUintPtr() - uintptr(addr) s, err := sys.UTF16BytesToString(buf[offset:]) return s, err + case EvtVarTypeAnsiString: + addr := unsafe.Pointer(&buf[0]) + offset := v.ValueAsUintPtr() - uintptr(addr) + s, err := sys.ANSIBytesToString(buf[offset:]) + return s, err case EvtVarTypeSByte: return int8(v.ValueAsUint8()), nil case EvtVarTypeByte: @@ -476,15 +481,28 @@ func (v EvtVariant) Data(buf []byte) (interface{}, error) { return false, nil } return true, nil + case EvtVarTypeBinary: + addr := unsafe.Pointer(&buf[0]) + offset := v.ValueAsUintPtr() - uintptr(addr) + return sys.BinaryToString(buf[offset:]), nil case EvtVarTypeGuid: addr := unsafe.Pointer(&buf[0]) offset := v.ValueAsUintPtr() - uintptr(addr) guid := (*windows.GUID)(unsafe.Pointer(&buf[offset])) copy := *guid return copy, nil + case EvtVarTypeSizeT: + return v.ValueAsUintPtr(), nil case EvtVarTypeFileTime: ft := (*windows.Filetime)(unsafe.Pointer(&v.Value)) return time.Unix(0, ft.Nanoseconds()).UTC(), nil + case EvtVarTypeSysTime: + st := (*windows.Systemtime)(unsafe.Pointer(&v.Value)) + var ft windows.Filetime + if err := sys.SystemTimeToFileTime(st, &ft); err != nil { + return nil, err + } + return time.Unix(0, ft.Nanoseconds()).UTC(), nil case EvtVarTypeSid: addr := unsafe.Pointer(&buf[0]) offset := v.ValueAsUintPtr() - uintptr(addr) diff --git a/winlogbeat/sys/zsyscall_windows.go b/winlogbeat/sys/zsyscall_windows.go new file mode 100644 index 000000000000..726140ed1265 --- /dev/null +++ b/winlogbeat/sys/zsyscall_windows.go @@ -0,0 +1,40 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package sys + +import ( + "fmt" + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +var ( + modkernel = windows.NewLazySystemDLL("Kernel32.dll") + + procSystemTimeToFileTime = modkernel.NewProc("SystemTimeToFileTime") +) + +func SystemTimeToFileTime(systemTime *windows.Systemtime, fileTime *windows.Filetime) error { + r1, _, err := syscall.SyscallN(procSystemTimeToFileTime.Addr(), uintptr(unsafe.Pointer(systemTime)), uintptr(unsafe.Pointer(fileTime))) + if r1 == 0 { + return fmt.Errorf("error converting system time to file time: %w", err) + } + return nil +} diff --git a/winlogbeat/sys/zsyscall_windows_test.go b/winlogbeat/sys/zsyscall_windows_test.go new file mode 100644 index 000000000000..b7bcbe0009a0 --- /dev/null +++ b/winlogbeat/sys/zsyscall_windows_test.go @@ -0,0 +1,42 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package sys + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "golang.org/x/sys/windows" +) + +func TestSystemTimeToFileTime(t *testing.T) { + ts := time.Date( + 2024, time.Month(9), 3, + 0, 0, 0, 0, time.UTC).UnixNano() + st := windows.Systemtime{ + Year: 2024, + Month: 9, + Day: 3, + } + var ft windows.Filetime + if err := SystemTimeToFileTime(&st, &ft); err != nil { + t.Fatal(err) + } + assert.Equal(t, ts, ft.Nanoseconds()) +}