Windows Event Log (EVT): Go parsing library

EVT files are Windows Event Log files written by older Windows operating systems (2000, XP, 2003). They are used as binary log files by several major Windows subsystems and applications. Typically, several of them can be found in %WINDIR%\system32\config directory:

  • Application = AppEvent.evt
  • System = SysEvent.evt
  • Security = SecEvent.evt

Alternatively, one can export any system event log as distinct .evt file using relevant option in Event Viewer application.

A Windows application can submit an entry into these logs using ReportEventA function of Windows API.

Internally, EVT files consist of a fixed-size header and event records. There are several usage scenarios (non-wrapping vs wrapping log files) which result in slightly different organization of records.

File extension

evt

KS implementation details

License: CC0-1.0
Minimal Kaitai Struct required: 0.9

References

This page hosts a formal specification of Windows Event Log (EVT) using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Go source code to parse Windows Event Log (EVT)

windows_evt_log.go

// Code generated by kaitai-struct-compiler from a .ksy source file. DO NOT EDIT.

import (
	"github.com/kaitai-io/kaitai_struct_go_runtime/kaitai"
	"bytes"
	"io"
)


/**
 * EVT files are Windows Event Log files written by older Windows
 * operating systems (2000, XP, 2003). They are used as binary log
 * files by several major Windows subsystems and
 * applications. Typically, several of them can be found in
 * `%WINDIR%\system32\config` directory:
 * 
 * * Application = `AppEvent.evt`
 * * System = `SysEvent.evt`
 * * Security = `SecEvent.evt`
 * 
 * Alternatively, one can export any system event log as distinct .evt
 * file using relevant option in Event Viewer application.
 * 
 * A Windows application can submit an entry into these logs using
 * [ReportEventA](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-reporteventa)
 * function of Windows API.
 * 
 * Internally, EVT files consist of a fixed-size header and event
 * records. There are several usage scenarios (non-wrapping vs wrapping
 * log files) which result in slightly different organization of
 * records.
 * @see <a href="https://learn.microsoft.com/en-us/windows/win32/eventlog/event-log-file-format">Source</a>
 */
type WindowsEvtLog struct {
	Header *WindowsEvtLog_Header
	Records []*WindowsEvtLog_Record
	_io *kaitai.Stream
	_root *WindowsEvtLog
	_parent kaitai.Struct
}
func NewWindowsEvtLog() *WindowsEvtLog {
	return &WindowsEvtLog{
	}
}

func (this WindowsEvtLog) IO_() *kaitai.Stream {
	return this._io
}

func (this *WindowsEvtLog) Read(io *kaitai.Stream, parent kaitai.Struct, root *WindowsEvtLog) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp1 := NewWindowsEvtLog_Header()
	err = tmp1.Read(this._io, this, this._root)
	if err != nil {
		return err
	}
	this.Header = tmp1
	for i := 0;; i++ {
		tmp2, err := this._io.EOF()
		if err != nil {
			return err
		}
		if tmp2 {
			break
		}
		tmp3 := NewWindowsEvtLog_Record()
		err = tmp3.Read(this._io, this, this._root)
		if err != nil {
			return err
		}
		this.Records = append(this.Records, tmp3)
	}
	return err
}

/**
 * @see <a href="https://forensics.wiki/windows_event_log_(evt)/#cursor-record">Source</a>
 */
type WindowsEvtLog_CursorRecordBody struct {
	Magic []byte
	OfsFirstRecord uint32
	OfsNextRecord uint32
	IdxNextRecord uint32
	IdxFirstRecord uint32
	_io *kaitai.Stream
	_root *WindowsEvtLog
	_parent *WindowsEvtLog_Record
}
func NewWindowsEvtLog_CursorRecordBody() *WindowsEvtLog_CursorRecordBody {
	return &WindowsEvtLog_CursorRecordBody{
	}
}

func (this WindowsEvtLog_CursorRecordBody) IO_() *kaitai.Stream {
	return this._io
}

func (this *WindowsEvtLog_CursorRecordBody) Read(io *kaitai.Stream, parent *WindowsEvtLog_Record, root *WindowsEvtLog) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp4, err := this._io.ReadBytes(int(12))
	if err != nil {
		return err
	}
	tmp4 = tmp4
	this.Magic = tmp4
	if !(bytes.Equal(this.Magic, []uint8{34, 34, 34, 34, 51, 51, 51, 51, 68, 68, 68, 68})) {
		return kaitai.NewValidationNotEqualError([]uint8{34, 34, 34, 34, 51, 51, 51, 51, 68, 68, 68, 68}, this.Magic, this._io, "/types/cursor_record_body/seq/0")
	}
	tmp5, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.OfsFirstRecord = uint32(tmp5)
	tmp6, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.OfsNextRecord = uint32(tmp6)
	tmp7, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.IdxNextRecord = uint32(tmp7)
	tmp8, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.IdxFirstRecord = uint32(tmp8)
	return err
}

/**
 * @see <a href="https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/bb309024(v=vs.85)">Source</a>
 */
type WindowsEvtLog_Header struct {
	LenHeader uint32
	Magic []byte
	VersionMajor uint32
	VersionMinor uint32
	OfsStart uint32
	OfsEnd uint32
	CurRecIdx uint32
	OldestRecIdx uint32
	LenFileMax uint32
	Flags *WindowsEvtLog_Header_Flags
	Retention uint32
	LenHeader2 uint32
	_io *kaitai.Stream
	_root *WindowsEvtLog
	_parent *WindowsEvtLog
}
func NewWindowsEvtLog_Header() *WindowsEvtLog_Header {
	return &WindowsEvtLog_Header{
	}
}

func (this WindowsEvtLog_Header) IO_() *kaitai.Stream {
	return this._io
}

func (this *WindowsEvtLog_Header) Read(io *kaitai.Stream, parent *WindowsEvtLog, root *WindowsEvtLog) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp9, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.LenHeader = uint32(tmp9)
	tmp10, err := this._io.ReadBytes(int(4))
	if err != nil {
		return err
	}
	tmp10 = tmp10
	this.Magic = tmp10
	if !(bytes.Equal(this.Magic, []uint8{76, 102, 76, 101})) {
		return kaitai.NewValidationNotEqualError([]uint8{76, 102, 76, 101}, this.Magic, this._io, "/types/header/seq/1")
	}
	tmp11, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.VersionMajor = uint32(tmp11)
	tmp12, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.VersionMinor = uint32(tmp12)
	tmp13, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.OfsStart = uint32(tmp13)
	tmp14, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.OfsEnd = uint32(tmp14)
	tmp15, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.CurRecIdx = uint32(tmp15)
	tmp16, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.OldestRecIdx = uint32(tmp16)
	tmp17, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.LenFileMax = uint32(tmp17)
	tmp18 := NewWindowsEvtLog_Header_Flags()
	err = tmp18.Read(this._io, this, this._root)
	if err != nil {
		return err
	}
	this.Flags = tmp18
	tmp19, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.Retention = uint32(tmp19)
	tmp20, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.LenHeader2 = uint32(tmp20)
	return err
}

/**
 * Size of the header structure, must be 0x30.
 */

/**
 * Offset of oldest record kept in this log file.
 */

/**
 * Offset of EOF log record, which is a placeholder for new record.
 */

/**
 * Index of current record, where a new submission would be
 * written to (normally there should to EOF log record there).
 */

/**
 * Index of oldest record in the log file
 */

/**
 * Total maximum size of the log file
 */

/**
 * Size of the header structure repeated again, and again it must be 0x30.
 */
type WindowsEvtLog_Header_Flags struct {
	Reserved uint64
	Archive bool
	LogFull bool
	Wrap bool
	Dirty bool
	_io *kaitai.Stream
	_root *WindowsEvtLog
	_parent *WindowsEvtLog_Header
}
func NewWindowsEvtLog_Header_Flags() *WindowsEvtLog_Header_Flags {
	return &WindowsEvtLog_Header_Flags{
	}
}

func (this WindowsEvtLog_Header_Flags) IO_() *kaitai.Stream {
	return this._io
}

func (this *WindowsEvtLog_Header_Flags) Read(io *kaitai.Stream, parent *WindowsEvtLog_Header, root *WindowsEvtLog) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp21, err := this._io.ReadBitsIntBe(28)
	if err != nil {
		return err
	}
	this.Reserved = tmp21
	tmp22, err := this._io.ReadBitsIntBe(1)
	if err != nil {
		return err
	}
	this.Archive = tmp22 != 0
	tmp23, err := this._io.ReadBitsIntBe(1)
	if err != nil {
		return err
	}
	this.LogFull = tmp23 != 0
	tmp24, err := this._io.ReadBitsIntBe(1)
	if err != nil {
		return err
	}
	this.Wrap = tmp24 != 0
	tmp25, err := this._io.ReadBitsIntBe(1)
	if err != nil {
		return err
	}
	this.Dirty = tmp25 != 0
	return err
}

/**
 * True if archive attribute has been set for this log file.
 */

/**
 * True if last write operation failed due to log being full.
 */

/**
 * True if wrapping of record has occured.
 */

/**
 * True if write operation was in progress, but log file
 * wasn't properly closed.
 */

/**
 * @see <a href="https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-eventlogrecord">Source</a>
 */
type WindowsEvtLog_Record struct {
	LenRecord uint32
	Type uint32
	Body interface{}
	LenRecord2 uint32
	_io *kaitai.Stream
	_root *WindowsEvtLog
	_parent *WindowsEvtLog
	_raw_Body []byte
}
func NewWindowsEvtLog_Record() *WindowsEvtLog_Record {
	return &WindowsEvtLog_Record{
	}
}

func (this WindowsEvtLog_Record) IO_() *kaitai.Stream {
	return this._io
}

func (this *WindowsEvtLog_Record) Read(io *kaitai.Stream, parent *WindowsEvtLog, root *WindowsEvtLog) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp26, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.LenRecord = uint32(tmp26)
	tmp27, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.Type = uint32(tmp27)
	switch (this.Type) {
	case 1699505740:
		tmp28, err := this._io.ReadBytes(int(this.LenRecord - 12))
		if err != nil {
			return err
		}
		tmp28 = tmp28
		this._raw_Body = tmp28
		_io__raw_Body := kaitai.NewStream(bytes.NewReader(this._raw_Body))
		tmp29 := NewWindowsEvtLog_RecordBody()
		err = tmp29.Read(_io__raw_Body, this, this._root)
		if err != nil {
			return err
		}
		this.Body = tmp29
	case 286331153:
		tmp30, err := this._io.ReadBytes(int(this.LenRecord - 12))
		if err != nil {
			return err
		}
		tmp30 = tmp30
		this._raw_Body = tmp30
		_io__raw_Body := kaitai.NewStream(bytes.NewReader(this._raw_Body))
		tmp31 := NewWindowsEvtLog_CursorRecordBody()
		err = tmp31.Read(_io__raw_Body, this, this._root)
		if err != nil {
			return err
		}
		this.Body = tmp31
	default:
		tmp32, err := this._io.ReadBytes(int(this.LenRecord - 12))
		if err != nil {
			return err
		}
		tmp32 = tmp32
		this._raw_Body = tmp32
	}
	tmp33, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.LenRecord2 = uint32(tmp33)
	return err
}

/**
 * Size of whole record, including all headers, footers and data
 */

/**
 * Type of record. Normal log records specify "LfLe"
 * (0x654c664c) in this field, cursor records use 0x11111111.
 */

/**
 * Record body interpretation depends on type of record. Body
 * size is specified in a way that it won't include a 8-byte
 * "header" (`len_record` + `type`) and a "footer"
 * (`len_record2`).
 */

/**
 * Size of whole record again.
 */

/**
 * @see <a href="https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-eventlogrecord">Source</a>
 */

type WindowsEvtLog_RecordBody_EventTypes int
const (
	WindowsEvtLog_RecordBody_EventTypes__Error WindowsEvtLog_RecordBody_EventTypes = 1
	WindowsEvtLog_RecordBody_EventTypes__AuditFailure WindowsEvtLog_RecordBody_EventTypes = 2
	WindowsEvtLog_RecordBody_EventTypes__AuditSuccess WindowsEvtLog_RecordBody_EventTypes = 3
	WindowsEvtLog_RecordBody_EventTypes__Info WindowsEvtLog_RecordBody_EventTypes = 4
	WindowsEvtLog_RecordBody_EventTypes__Warning WindowsEvtLog_RecordBody_EventTypes = 5
)
var values_WindowsEvtLog_RecordBody_EventTypes = map[WindowsEvtLog_RecordBody_EventTypes]struct{}{1: {}, 2: {}, 3: {}, 4: {}, 5: {}}
func (v WindowsEvtLog_RecordBody_EventTypes) isDefined() bool {
	_, ok := values_WindowsEvtLog_RecordBody_EventTypes[v]
	return ok
}
type WindowsEvtLog_RecordBody struct {
	Idx uint32
	TimeGenerated uint32
	TimeWritten uint32
	EventId uint32
	EventType WindowsEvtLog_RecordBody_EventTypes
	NumStrings uint16
	EventCategory uint16
	Reserved []byte
	OfsStrings uint32
	LenUserSid uint32
	OfsUserSid uint32
	LenData uint32
	OfsData uint32
	_io *kaitai.Stream
	_root *WindowsEvtLog
	_parent *WindowsEvtLog_Record
	_f_data bool
	data []byte
	_f_userSid bool
	userSid []byte
}
func NewWindowsEvtLog_RecordBody() *WindowsEvtLog_RecordBody {
	return &WindowsEvtLog_RecordBody{
	}
}

func (this WindowsEvtLog_RecordBody) IO_() *kaitai.Stream {
	return this._io
}

func (this *WindowsEvtLog_RecordBody) Read(io *kaitai.Stream, parent *WindowsEvtLog_Record, root *WindowsEvtLog) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp34, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.Idx = uint32(tmp34)
	tmp35, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.TimeGenerated = uint32(tmp35)
	tmp36, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.TimeWritten = uint32(tmp36)
	tmp37, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.EventId = uint32(tmp37)
	tmp38, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.EventType = WindowsEvtLog_RecordBody_EventTypes(tmp38)
	tmp39, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.NumStrings = uint16(tmp39)
	tmp40, err := this._io.ReadU2le()
	if err != nil {
		return err
	}
	this.EventCategory = uint16(tmp40)
	tmp41, err := this._io.ReadBytes(int(6))
	if err != nil {
		return err
	}
	tmp41 = tmp41
	this.Reserved = tmp41
	tmp42, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.OfsStrings = uint32(tmp42)
	tmp43, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.LenUserSid = uint32(tmp43)
	tmp44, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.OfsUserSid = uint32(tmp44)
	tmp45, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.LenData = uint32(tmp45)
	tmp46, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.OfsData = uint32(tmp46)
	return err
}
func (this *WindowsEvtLog_RecordBody) Data() (v []byte, err error) {
	if (this._f_data) {
		return this.data, nil
	}
	this._f_data = true
	_pos, err := this._io.Pos()
	if err != nil {
		return nil, err
	}
	_, err = this._io.Seek(int64(this.OfsData - 8), io.SeekStart)
	if err != nil {
		return nil, err
	}
	tmp47, err := this._io.ReadBytes(int(this.LenData))
	if err != nil {
		return nil, err
	}
	tmp47 = tmp47
	this.data = tmp47
	_, err = this._io.Seek(_pos, io.SeekStart)
	if err != nil {
		return nil, err
	}
	return this.data, nil
}
func (this *WindowsEvtLog_RecordBody) UserSid() (v []byte, err error) {
	if (this._f_userSid) {
		return this.userSid, nil
	}
	this._f_userSid = true
	_pos, err := this._io.Pos()
	if err != nil {
		return nil, err
	}
	_, err = this._io.Seek(int64(this.OfsUserSid - 8), io.SeekStart)
	if err != nil {
		return nil, err
	}
	tmp48, err := this._io.ReadBytes(int(this.LenUserSid))
	if err != nil {
		return nil, err
	}
	tmp48 = tmp48
	this.userSid = tmp48
	_, err = this._io.Seek(_pos, io.SeekStart)
	if err != nil {
		return nil, err
	}
	return this.userSid, nil
}

/**
 * Index of record in the file.
 */

/**
 * Time when this record was generated, POSIX timestamp format.
 */

/**
 * Time when thsi record was written into the log file, POSIX timestamp format.
 */

/**
 * Identifier of an event, meaning is specific to particular
 * source of events / event type.
 */

/**
 * Type of event.
 * @see <a href="https://learn.microsoft.com/en-us/windows/win32/eventlog/event-types">Source</a>
 */

/**
 * Number of strings present in the log.
 */

/**
 * @see <a href="https://learn.microsoft.com/en-us/windows/win32/eventlog/event-categories">Source</a>
 */

/**
 * Offset of strings present in the log
 */