Windows Event Log (EVT): C++11/STL 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.

Usage

Runtime library

All parsing code for C++11/STL generated by Kaitai Struct depends on the C++/STL runtime library. You have to install it before you can parse data.

For C++, the easiest way is to clone the runtime library sources and build them along with your project.

Code

Using Kaitai Struct in C++/STL usually consists of 3 steps.

  1. We need to create an STL input stream (std::istream). One can open local file for that, or use existing std::string or char* buffer.
    #include <fstream>
    
    std::ifstream is("path/to/local/file.evt", std::ifstream::binary);
    
    #include <sstream>
    
    std::istringstream is(str);
    
    #include <sstream>
    
    const char buf[] = { ... };
    std::string str(buf, sizeof buf);
    std::istringstream is(str);
    
  2. We need to wrap our input stream into Kaitai stream:
    #include "kaitai/kaitaistream.h"
    
    kaitai::kstream ks(&is);
    
  3. And finally, we can invoke the parsing:
    windows_evt_log_t data(&ks);
    

After that, one can get various attributes from the structure by invoking getter methods like:

data.header() // => get header

C++11/STL source code to parse Windows Event Log (EVT)

windows_evt_log.h

#pragma once

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

#include "kaitai/kaitaistruct.h"
#include <stdint.h>
#include <memory>
#include <vector>

#if KAITAI_STRUCT_VERSION < 9000L
#error "Incompatible Kaitai Struct C++/STL API: version 0.9 or later is required"
#endif

/**
 * 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.
 * \sa https://learn.microsoft.com/en-us/windows/win32/eventlog/event-log-file-format Source
 */

class windows_evt_log_t : public kaitai::kstruct {

public:
    class header_t;
    class record_t;
    class record_body_t;
    class cursor_record_body_t;

    windows_evt_log_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = nullptr, windows_evt_log_t* p__root = nullptr);

private:
    void _read();
    void _clean_up();

public:
    ~windows_evt_log_t();

    /**
     * \sa https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/bb309024(v=vs.85) Source
     */

    class header_t : public kaitai::kstruct {

    public:
        class flags_t;

        header_t(kaitai::kstream* p__io, windows_evt_log_t* p__parent = nullptr, windows_evt_log_t* p__root = nullptr);

    private:
        void _read();
        void _clean_up();

    public:
        ~header_t();

        class flags_t : public kaitai::kstruct {

        public:

            flags_t(kaitai::kstream* p__io, windows_evt_log_t::header_t* p__parent = nullptr, windows_evt_log_t* p__root = nullptr);

        private:
            void _read();
            void _clean_up();

        public:
            ~flags_t();

        private:
            uint64_t m_reserved;
            bool m_archive;
            bool m_log_full;
            bool m_wrap;
            bool m_dirty;
            windows_evt_log_t* m__root;
            windows_evt_log_t::header_t* m__parent;

        public:
            uint64_t reserved() const { return m_reserved; }

            /**
             * True if archive attribute has been set for this log file.
             */
            bool archive() const { return m_archive; }

            /**
             * True if last write operation failed due to log being full.
             */
            bool log_full() const { return m_log_full; }

            /**
             * True if wrapping of record has occured.
             */
            bool wrap() const { return m_wrap; }

            /**
             * True if write operation was in progress, but log file
             * wasn't properly closed.
             */
            bool dirty() const { return m_dirty; }
            windows_evt_log_t* _root() const { return m__root; }
            windows_evt_log_t::header_t* _parent() const { return m__parent; }
        };

    private:
        uint32_t m_len_header;
        std::string m_magic;
        uint32_t m_version_major;
        uint32_t m_version_minor;
        uint32_t m_ofs_start;
        uint32_t m_ofs_end;
        uint32_t m_cur_rec_idx;
        uint32_t m_oldest_rec_idx;
        uint32_t m_len_file_max;
        std::unique_ptr<flags_t> m_flags;
        uint32_t m_retention;
        uint32_t m_len_header_2;
        windows_evt_log_t* m__root;
        windows_evt_log_t* m__parent;

    public:

        /**
         * Size of the header structure, must be 0x30.
         */
        uint32_t len_header() const { return m_len_header; }
        std::string magic() const { return m_magic; }
        uint32_t version_major() const { return m_version_major; }
        uint32_t version_minor() const { return m_version_minor; }

        /**
         * Offset of oldest record kept in this log file.
         */
        uint32_t ofs_start() const { return m_ofs_start; }

        /**
         * Offset of EOF log record, which is a placeholder for new record.
         */
        uint32_t ofs_end() const { return m_ofs_end; }

        /**
         * Index of current record, where a new submission would be
         * written to (normally there should to EOF log record there).
         */
        uint32_t cur_rec_idx() const { return m_cur_rec_idx; }

        /**
         * Index of oldest record in the log file
         */
        uint32_t oldest_rec_idx() const { return m_oldest_rec_idx; }

        /**
         * Total maximum size of the log file
         */
        uint32_t len_file_max() const { return m_len_file_max; }
        flags_t* flags() const { return m_flags.get(); }
        uint32_t retention() const { return m_retention; }

        /**
         * Size of the header structure repeated again, and again it must be 0x30.
         */
        uint32_t len_header_2() const { return m_len_header_2; }
        windows_evt_log_t* _root() const { return m__root; }
        windows_evt_log_t* _parent() const { return m__parent; }
    };

    /**
     * \sa https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-eventlogrecord Source
     */

    class record_t : public kaitai::kstruct {

    public:

        record_t(kaitai::kstream* p__io, windows_evt_log_t* p__parent = nullptr, windows_evt_log_t* p__root = nullptr);

    private:
        void _read();
        void _clean_up();

    public:
        ~record_t();

    private:
        uint32_t m_len_record;
        uint32_t m_type;
        std::unique_ptr<kaitai::kstruct> m_body;
        bool n_body;

    public:
        bool _is_null_body() { body(); return n_body; };

    private:
        uint32_t m_len_record2;
        windows_evt_log_t* m__root;
        windows_evt_log_t* m__parent;
        std::string m__raw_body;
        std::unique_ptr<kaitai::kstream> m__io__raw_body;

    public:

        /**
         * Size of whole record, including all headers, footers and data
         */
        uint32_t len_record() const { return m_len_record; }

        /**
         * Type of record. Normal log records specify "LfLe"
         * (0x654c664c) in this field, cursor records use 0x11111111.
         */
        uint32_t type() const { return m_type; }

        /**
         * 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`).
         */
        kaitai::kstruct* body() const { return m_body.get(); }

        /**
         * Size of whole record again.
         */
        uint32_t len_record2() const { return m_len_record2; }
        windows_evt_log_t* _root() const { return m__root; }
        windows_evt_log_t* _parent() const { return m__parent; }
        std::string _raw_body() const { return m__raw_body; }
        kaitai::kstream* _io__raw_body() const { return m__io__raw_body.get(); }
    };

    /**
     * \sa https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-eventlogrecord Source
     */

    class record_body_t : public kaitai::kstruct {

    public:

        enum event_types_t {
            EVENT_TYPES_ERROR = 1,
            EVENT_TYPES_AUDIT_FAILURE = 2,
            EVENT_TYPES_AUDIT_SUCCESS = 3,
            EVENT_TYPES_INFO = 4,
            EVENT_TYPES_WARNING = 5
        };

        record_body_t(kaitai::kstream* p__io, windows_evt_log_t::record_t* p__parent = nullptr, windows_evt_log_t* p__root = nullptr);

    private:
        void _read();
        void _clean_up();

    public:
        ~record_body_t();

    private:
        bool f_user_sid;
        std::string m_user_sid;

    public:
        std::string user_sid();

    private:
        bool f_data;
        std::string m_data;

    public:
        std::string data();

    private:
        uint32_t m_idx;
        uint32_t m_time_generated;
        uint32_t m_time_written;
        uint32_t m_event_id;
        event_types_t m_event_type;
        uint16_t m_num_strings;
        uint16_t m_event_category;
        std::string m_reserved;
        uint32_t m_ofs_strings;
        uint32_t m_len_user_sid;
        uint32_t m_ofs_user_sid;
        uint32_t m_len_data;
        uint32_t m_ofs_data;
        windows_evt_log_t* m__root;
        windows_evt_log_t::record_t* m__parent;

    public:

        /**
         * Index of record in the file.
         */
        uint32_t idx() const { return m_idx; }

        /**
         * Time when this record was generated, POSIX timestamp format.
         */
        uint32_t time_generated() const { return m_time_generated; }

        /**
         * Time when thsi record was written into the log file, POSIX timestamp format.
         */
        uint32_t time_written() const { return m_time_written; }

        /**
         * Identifier of an event, meaning is specific to particular
         * source of events / event type.
         */
        uint32_t event_id() const { return m_event_id; }

        /**
         * Type of event.
         * \sa https://learn.microsoft.com/en-us/windows/win32/eventlog/event-types Source
         */
        event_types_t event_type() const { return m_event_type; }

        /**
         * Number of strings present in the log.
         */
        uint16_t num_strings() const { return m_num_strings; }

        /**
         * \sa https://learn.microsoft.com/en-us/windows/win32/eventlog/event-categories Source
         */
        uint16_t event_category() const { return m_event_category; }
        std::string reserved() const { return m_reserved; }

        /**
         * Offset of strings present in the log
         */
        uint32_t ofs_strings() const { return m_ofs_strings; }
        uint32_t len_user_sid() const { return m_len_user_sid; }
        uint32_t ofs_user_sid() const { return m_ofs_user_sid; }
        uint32_t len_data() const { return m_len_data; }
        uint32_t ofs_data() const { return m_ofs_data; }
        windows_evt_log_t* _root() const { return m__root; }
        windows_evt_log_t::record_t* _parent() const { return m__parent; }
    };

    /**
     * \sa https://forensics.wiki/windows_event_log_(evt)/#cursor-record Source
     */

    class cursor_record_body_t : public kaitai::kstruct {

    public:

        cursor_record_body_t(kaitai::kstream* p__io, windows_evt_log_t::record_t* p__parent = nullptr, windows_evt_log_t* p__root = nullptr);

    private:
        void _read();
        void _clean_up();

    public:
        ~cursor_record_body_t();

    private:
        std::string m_magic;
        uint32_t m_ofs_first_record;
        uint32_t m_ofs_next_record;
        uint32_t m_idx_next_record;
        uint32_t m_idx_first_record;
        windows_evt_log_t* m__root;
        windows_evt_log_t::record_t* m__parent;

    public:
        std::string magic() const { return m_magic; }
        uint32_t ofs_first_record() const { return m_ofs_first_record; }
        uint32_t ofs_next_record() const { return m_ofs_next_record; }
        uint32_t idx_next_record() const { return m_idx_next_record; }
        uint32_t idx_first_record() const { return m_idx_first_record; }
        windows_evt_log_t* _root() const { return m__root; }
        windows_evt_log_t::record_t* _parent() const { return m__parent; }
    };

private:
    std::unique_ptr<header_t> m_header;
    std::unique_ptr<std::vector<std::unique_ptr<record_t>>> m_records;
    windows_evt_log_t* m__root;
    kaitai::kstruct* m__parent;

public:
    header_t* header() const { return m_header.get(); }
    std::vector<std::unique_ptr<record_t>>* records() const { return m_records.get(); }
    windows_evt_log_t* _root() const { return m__root; }
    kaitai::kstruct* _parent() const { return m__parent; }
};

windows_evt_log.cpp

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

#include "windows_evt_log.h"
#include "kaitai/exceptions.h"

windows_evt_log_t::windows_evt_log_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, windows_evt_log_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = this;
    m_header = nullptr;
    m_records = nullptr;
    _read();
}

void windows_evt_log_t::_read() {
    m_header = std::unique_ptr<header_t>(new header_t(m__io, this, m__root));
    m_records = std::unique_ptr<std::vector<std::unique_ptr<record_t>>>(new std::vector<std::unique_ptr<record_t>>());
    {
        int i = 0;
        while (!m__io->is_eof()) {
            m_records->push_back(std::move(std::unique_ptr<record_t>(new record_t(m__io, this, m__root))));
            i++;
        }
    }
}

windows_evt_log_t::~windows_evt_log_t() {
    _clean_up();
}

void windows_evt_log_t::_clean_up() {
}

windows_evt_log_t::header_t::header_t(kaitai::kstream* p__io, windows_evt_log_t* p__parent, windows_evt_log_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_flags = nullptr;
    _read();
}

void windows_evt_log_t::header_t::_read() {
    m_len_header = m__io->read_u4le();
    m_magic = m__io->read_bytes(4);
    if (!(magic() == std::string("\x4C\x66\x4C\x65", 4))) {
        throw kaitai::validation_not_equal_error<std::string>(std::string("\x4C\x66\x4C\x65", 4), magic(), _io(), std::string("/types/header/seq/1"));
    }
    m_version_major = m__io->read_u4le();
    m_version_minor = m__io->read_u4le();
    m_ofs_start = m__io->read_u4le();
    m_ofs_end = m__io->read_u4le();
    m_cur_rec_idx = m__io->read_u4le();
    m_oldest_rec_idx = m__io->read_u4le();
    m_len_file_max = m__io->read_u4le();
    m_flags = std::unique_ptr<flags_t>(new flags_t(m__io, this, m__root));
    m_retention = m__io->read_u4le();
    m_len_header_2 = m__io->read_u4le();
}

windows_evt_log_t::header_t::~header_t() {
    _clean_up();
}

void windows_evt_log_t::header_t::_clean_up() {
}

windows_evt_log_t::header_t::flags_t::flags_t(kaitai::kstream* p__io, windows_evt_log_t::header_t* p__parent, windows_evt_log_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    _read();
}

void windows_evt_log_t::header_t::flags_t::_read() {
    m_reserved = m__io->read_bits_int_be(28);
    m_archive = m__io->read_bits_int_be(1);
    m_log_full = m__io->read_bits_int_be(1);
    m_wrap = m__io->read_bits_int_be(1);
    m_dirty = m__io->read_bits_int_be(1);
}

windows_evt_log_t::header_t::flags_t::~flags_t() {
    _clean_up();
}

void windows_evt_log_t::header_t::flags_t::_clean_up() {
}

windows_evt_log_t::record_t::record_t(kaitai::kstream* p__io, windows_evt_log_t* p__parent, windows_evt_log_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m__io__raw_body = nullptr;
    _read();
}

void windows_evt_log_t::record_t::_read() {
    m_len_record = m__io->read_u4le();
    m_type = m__io->read_u4le();
    n_body = true;
    switch (type()) {
    case 1699505740: {
        n_body = false;
        m__raw_body = m__io->read_bytes((len_record() - 12));
        m__io__raw_body = std::unique_ptr<kaitai::kstream>(new kaitai::kstream(m__raw_body));
        m_body = std::unique_ptr<record_body_t>(new record_body_t(m__io__raw_body.get(), this, m__root));
        break;
    }
    case 286331153: {
        n_body = false;
        m__raw_body = m__io->read_bytes((len_record() - 12));
        m__io__raw_body = std::unique_ptr<kaitai::kstream>(new kaitai::kstream(m__raw_body));
        m_body = std::unique_ptr<cursor_record_body_t>(new cursor_record_body_t(m__io__raw_body.get(), this, m__root));
        break;
    }
    default: {
        m__raw_body = m__io->read_bytes((len_record() - 12));
        break;
    }
    }
    m_len_record2 = m__io->read_u4le();
}

windows_evt_log_t::record_t::~record_t() {
    _clean_up();
}

void windows_evt_log_t::record_t::_clean_up() {
    if (!n_body) {
    }
}

windows_evt_log_t::record_body_t::record_body_t(kaitai::kstream* p__io, windows_evt_log_t::record_t* p__parent, windows_evt_log_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    f_user_sid = false;
    f_data = false;
    _read();
}

void windows_evt_log_t::record_body_t::_read() {
    m_idx = m__io->read_u4le();
    m_time_generated = m__io->read_u4le();
    m_time_written = m__io->read_u4le();
    m_event_id = m__io->read_u4le();
    m_event_type = static_cast<windows_evt_log_t::record_body_t::event_types_t>(m__io->read_u2le());
    m_num_strings = m__io->read_u2le();
    m_event_category = m__io->read_u2le();
    m_reserved = m__io->read_bytes(6);
    m_ofs_strings = m__io->read_u4le();
    m_len_user_sid = m__io->read_u4le();
    m_ofs_user_sid = m__io->read_u4le();
    m_len_data = m__io->read_u4le();
    m_ofs_data = m__io->read_u4le();
}

windows_evt_log_t::record_body_t::~record_body_t() {
    _clean_up();
}

void windows_evt_log_t::record_body_t::_clean_up() {
    if (f_user_sid) {
    }
    if (f_data) {
    }
}

std::string windows_evt_log_t::record_body_t::user_sid() {
    if (f_user_sid)
        return m_user_sid;
    std::streampos _pos = m__io->pos();
    m__io->seek((ofs_user_sid() - 8));
    m_user_sid = m__io->read_bytes(len_user_sid());
    m__io->seek(_pos);
    f_user_sid = true;
    return m_user_sid;
}

std::string windows_evt_log_t::record_body_t::data() {
    if (f_data)
        return m_data;
    std::streampos _pos = m__io->pos();
    m__io->seek((ofs_data() - 8));
    m_data = m__io->read_bytes(len_data());
    m__io->seek(_pos);
    f_data = true;
    return m_data;
}

windows_evt_log_t::cursor_record_body_t::cursor_record_body_t(kaitai::kstream* p__io, windows_evt_log_t::record_t* p__parent, windows_evt_log_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    _read();
}

void windows_evt_log_t::cursor_record_body_t::_read() {
    m_magic = m__io->read_bytes(12);
    if (!(magic() == std::string("\x22\x22\x22\x22\x33\x33\x33\x33\x44\x44\x44\x44", 12))) {
        throw kaitai::validation_not_equal_error<std::string>(std::string("\x22\x22\x22\x22\x33\x33\x33\x33\x44\x44\x44\x44", 12), magic(), _io(), std::string("/types/cursor_record_body/seq/0"));
    }
    m_ofs_first_record = m__io->read_u4le();
    m_ofs_next_record = m__io->read_u4le();
    m_idx_next_record = m__io->read_u4le();
    m_idx_first_record = m__io->read_u4le();
}

windows_evt_log_t::cursor_record_body_t::~cursor_record_body_t() {
    _clean_up();
}

void windows_evt_log_t::cursor_record_body_t::_clean_up() {
}