Task 195: .EVTX File Format

Task 195: .EVTX File Format

1. List of Properties Intrinsic to the .EVTX File Format

The .EVTX file format is a binary format used for Windows event logs, introduced in Windows Vista. It consists of a file header, followed by one or more chunks, each containing a chunk header, string and template tables, and event records. Each event record wraps binary-encoded XML data. The properties intrinsic to the format include the following structural fields, derived from official specifications. These are presented in tables for clarity, grouped by structure, with offsets relative to the start of each structure.

File Header Properties (Size: 128 bytes used, padded to 4096 bytes)

Offset (hex) Size (bytes) Type Property Name Description
0x00 8 String Magic String Fixed value "ElfFile\0" to identify the file as an EVTX log.
0x08 8 uint64 First Chunk Number Zero-based index of the first chunk in the file.
0x10 8 uint64 Next Record Number The next available event record number in the log.
0x18 4 uint32 Header Size Fixed value 0x80 (128 bytes) indicating used header space.
0x1C 2 uint16 Minor Version Fixed value 1.
0x1E 2 uint16 Major Version Fixed value 3.
0x20 2 uint16 Header Block Size Fixed value 4096 bytes for the total header block.
0x22 2 uint16 Chunk Count Number of chunks in the file.
0x78 4 uint32 Flags Bit flags: bit 0 set if log is dirty (open/changed); bit 1 set if log is full.
0x7C 4 uint32 Checksum 32-bit checksum for header integrity.

Chunk Header Properties (Size: 128 bytes)

Each chunk is 65536 bytes, self-contained, with no records spanning chunks.

Offset (hex) Size (bytes) Type Property Name Description
0x00 8 String Magic String Fixed value "ElfChnk\0" to identify the chunk.
0x08 8 uint64 First Record Number in Log Number of the first event record in this chunk (log-wide).
0x10 8 uint64 Last Record Number in Log Number of the last event record in this chunk (log-wide).
0x18 8 uint64 First Record Number in File Number of the first event record in the file.
0x20 8 uint64 Last Record Number in File Number of the last event record in the file.
0x28 4 uint32 Header Size Offset or size related to the chunk header (typically 128 bytes).
0x2C 4 uint32 Last Record Offset Relative offset to the last record in the chunk.
0x30 4 uint32 Next Record Offset Relative offset to where the next record would be written.
0x7C 4 uint32 Checksum 32-bit checksum for chunk header integrity.

Event Record Properties (Variable size)

Event records follow the string and template tables in each chunk.

Offset (hex) Size (bytes) Type Property Name Description
0x00 4 uint32 Record Size (Start) Total length of the event record for forward traversal.
0x04 8 uint64 Record ID Unique numerical identifier for the record.
0x0C 8 FILETIME Timestamp Creation time of the event in FILETIME format.
Variable Variable Binary Binary XML Data Proprietary binary-encoded XML containing event details.
End - 4 4 uint32 Record Size (End) Matches start size for backward traversal and integrity.

These properties represent the core binary structure, enabling parsing, validation, and navigation without interpreting the XML content.

The following are direct download links to sample .EVTX files from public repositories on GitHub:

3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .EVTX File Dump

The following is an HTML snippet with embedded JavaScript that can be inserted into a Ghost blog post. It creates a drag-and-drop area where a user can drop an .EVTX file. The script parses the file using a FileReader, extracts the properties listed in section 1, and displays them on the screen in a structured format. Note that this handles binary data via ArrayBuffer and DataView.

Drag and drop .EVTX file here

4. Python Class for .EVTX File Handling

The following Python class opens an .EVTX file, decodes its structures, reads the properties, provides a method to write a modified file (e.g., updating flags), and prints the properties to the console.

import struct
import os

class EvtxHandler:
    def __init__(self, filepath):
        self.filepath = filepath
        self.data = None
        self.properties = {}

    def read(self):
        with open(self.filepath, 'rb') as f:
            self.data = f.read()
        self.decode_properties()

    def decode_properties(self):
        # File Header
        offset = 0
        self.properties['file_magic'] = self.data[offset:offset+8].decode('ascii', errors='ignore')
        offset += 8
        self.properties['first_chunk_num'] = struct.unpack('<Q', self.data[offset:offset+8])[0]
        offset += 8
        self.properties['next_record_num'] = struct.unpack('<Q', self.data[offset:offset+8])[0]
        offset += 8
        self.properties['header_size'] = struct.unpack('<I', self.data[offset:offset+4])[0]
        offset += 4
        self.properties['minor_version'] = struct.unpack('<H', self.data[offset:offset+2])[0]
        offset += 2
        self.properties['major_version'] = struct.unpack('<H', self.data[offset:offset+2])[0]
        offset += 2
        self.properties['header_block_size'] = struct.unpack('<H', self.data[offset:offset+2])[0]
        offset += 2
        self.properties['chunk_count'] = struct.unpack('<H', self.data[offset:offset+2])[0]
        offset += 86  # Skip to flags
        self.properties['flags'] = struct.unpack('<I', self.data[offset:offset+4])[0]
        offset += 4
        self.properties['file_checksum'] = struct.unpack('<I', self.data[offset:offset+4])[0]

        # Chunk Header (first chunk at 4096)
        offset = 4096
        self.properties['chunk_magic'] = self.data[offset:offset+8].decode('ascii', errors='ignore')
        offset += 8
        self.properties['first_rec_log'] = struct.unpack('<Q', self.data[offset:offset+8])[0]
        offset += 8
        self.properties['last_rec_log'] = struct.unpack('<Q', self.data[offset:offset+8])[0]
        offset += 8
        self.properties['first_rec_file'] = struct.unpack('<Q', self.data[offset:offset+8])[0]
        offset += 8
        self.properties['last_rec_file'] = struct.unpack('<Q', self.data[offset:offset+8])[0]
        offset += 8
        self.properties['chunk_header_size'] = struct.unpack('<I', self.data[offset:offset+4])[0]
        offset += 4
        self.properties['last_rec_offset'] = struct.unpack('<I', self.data[offset:offset+4])[0]
        offset += 4
        self.properties['next_rec_offset'] = struct.unpack('<I', self.data[offset:offset+4])[0]
        offset += 76  # Skip to checksum
        self.properties['chunk_checksum'] = struct.unpack('<I', self.data[offset:offset+4])[0]

        # Event Record example (simplified, first record position varies)
        # Additional parsing can be added for multiple records

    def print_properties(self):
        print("File Header Properties:")
        for key, value in self.properties.items():
            if 'chunk' not in key:
                print(f"{key}: {value}")
        print("\nChunk Header Properties (First Chunk):")
        for key, value in self.properties.items():
            if 'chunk' in key:
                print(f"{key}: {value}")

    def write(self, new_filepath, update_flags=None):
        if update_flags is not None:
            # Update flags example
            offset = 120  # Flags offset in file header
            new_data = bytearray(self.data)
            struct.pack_into('<I', new_data, offset, update_flags)
            self.data = bytes(new_data)
            self.decode_properties()  # Re-decode
        with open(new_filepath, 'wb') as f:
            f.write(self.data)

5. Java Class for .EVTX File Handling

The following Java class opens an .EVTX file, decodes its structures, reads the properties, provides a method to write a modified file, and prints the properties to the console.

import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;
import java.nio.file.*;

public class EvtxHandler {
    private String filepath;
    private ByteBuffer buffer;
    private java.util.Map<String, Object> properties = new java.util.HashMap<>();

    public EvtxHandler(String filepath) {
        this.filepath = filepath;
    }

    public void read() throws IOException {
        FileChannel channel = FileChannel.open(Paths.get(filepath));
        buffer = ByteBuffer.allocate((int) channel.size()).order(ByteOrder.LITTLE_ENDIAN);
        channel.read(buffer);
        buffer.flip();
        decodeProperties();
    }

    private void decodeProperties() {
        // File Header
        int offset = 0;
        properties.put("file_magic", new String(buffer.array(), offset, 8));
        offset += 8;
        properties.put("first_chunk_num", buffer.getLong(offset));
        offset += 8;
        properties.put("next_record_num", buffer.getLong(offset));
        offset += 8;
        properties.put("header_size", buffer.getInt(offset));
        offset += 4;
        properties.put("minor_version", buffer.getShort(offset));
        offset += 2;
        properties.put("major_version", buffer.getShort(offset));
        offset += 2;
        properties.put("header_block_size", buffer.getShort(offset));
        offset += 2;
        properties.put("chunk_count", buffer.getShort(offset));
        offset += 86;
        properties.put("flags", buffer.getInt(offset));
        offset += 4;
        properties.put("file_checksum", buffer.getInt(offset));

        // Chunk Header
        offset = 4096;
        properties.put("chunk_magic", new String(buffer.array(), offset, 8));
        offset += 8;
        properties.put("first_rec_log", buffer.getLong(offset));
        offset += 8;
        properties.put("last_rec_log", buffer.getLong(offset));
        offset += 8;
        properties.put("first_rec_file", buffer.getLong(offset));
        offset += 8;
        properties.put("last_rec_file", buffer.getLong(offset));
        offset += 8;
        properties.put("chunk_header_size", buffer.getInt(offset));
        offset += 4;
        properties.put("last_rec_offset", buffer.getInt(offset));
        offset += 4;
        properties.put("next_rec_offset", buffer.getInt(offset));
        offset += 76;
        properties.put("chunk_checksum", buffer.getInt(offset));
    }

    public void printProperties() {
        System.out.println("File Header Properties:");
        properties.entrySet().stream().filter(e -> !e.getKey().contains("chunk")).forEach(e -> System.out.println(e.getKey() + ": " + e.getValue()));
        System.out.println("\nChunk Header Properties (First Chunk):");
        properties.entrySet().stream().filter(e -> e.getKey().contains("chunk")).forEach(e -> System.out.println(e.getKey() + ": " + e.getValue()));
    }

    public void write(String newFilepath, Integer newFlags) throws IOException {
        if (newFlags != null) {
            buffer.putInt(120, newFlags);  // Update flags
            decodeProperties();  // Re-decode
        }
        try (FileOutputStream fos = new FileOutputStream(newFilepath)) {
            fos.write(buffer.array());
        }
    }
}

6. JavaScript Class for .EVTX File Handling

The following JavaScript class (for Node.js) opens an .EVTX file, decodes its structures, reads the properties, provides a method to write a modified file, and prints the properties to the console.

const fs = require('fs');

class EvtxHandler {
  constructor(filepath) {
    this.filepath = filepath;
    this.data = null;
    this.properties = {};
  }

  read() {
    this.data = fs.readFileSync(this.filepath);
    this.decodeProperties();
  }

  decodeProperties() {
    const view = new DataView(this.data.buffer);
    let offset = 0;
    this.properties.file_magic = this.getString(view, offset, 8);
    offset += 8;
    this.properties.first_chunk_num = view.getBigUint64(offset, true);
    offset += 8;
    this.properties.next_record_num = view.getBigUint64(offset, true);
    offset += 8;
    this.properties.header_size = view.getUint32(offset, true);
    offset += 4;
    this.properties.minor_version = view.getUint16(offset, true);
    offset += 2;
    this.properties.major_version = view.getUint16(offset, true);
    offset += 2;
    this.properties.header_block_size = view.getUint16(offset, true);
    offset += 2;
    this.properties.chunk_count = view.getUint16(offset, true);
    offset += 86;
    this.properties.flags = view.getUint32(offset, true);
    offset += 4;
    this.properties.file_checksum = view.getUint32(offset, true);

    offset = 4096;
    this.properties.chunk_magic = this.getString(view, offset, 8);
    offset += 8;
    this.properties.first_rec_log = view.getBigUint64(offset, true);
    offset += 8;
    this.properties.last_rec_log = view.getBigUint64(offset, true);
    offset += 8;
    this.properties.first_rec_file = view.getBigUint64(offset, true);
    offset += 8;
    this.properties.last_rec_file = view.getBigUint64(offset, true);
    offset += 8;
    this.properties.chunk_header_size = view.getUint32(offset, true);
    offset += 4;
    this.properties.last_rec_offset = view.getUint32(offset, true);
    offset += 4;
    this.properties.next_rec_offset = view.getUint32(offset, true);
    offset += 76;
    this.properties.chunk_checksum = view.getUint32(offset, true);
  }

  printProperties() {
    console.log('File Header Properties:');
    for (let [key, value] of Object.entries(this.properties)) {
      if (!key.includes('chunk')) console.log(`${key}: ${value}`);
    }
    console.log('\nChunk Header Properties (First Chunk):');
    for (let [key, value] of Object.entries(this.properties)) {
      if (key.includes('chunk')) console.log(`${key}: ${value}`);
    }
  }

  write(newFilepath, newFlags = null) {
    if (newFlags !== null) {
      const view = new DataView(this.data.buffer);
      view.setUint32(120, newFlags, true);
      this.decodeProperties();
    }
    fs.writeFileSync(newFilepath, this.data);
  }

  getString(view, offset, length) {
    let str = '';
    for (let i = 0; i < length; i++) {
      const char = view.getUint8(offset + i);
      if (char === 0) break;
      str += String.fromCharCode(char);
    }
    return str;
  }
}

7. C Class for .EVTX File Handling

The following C code defines a struct-based "class" (using functions) to open an .EVTX file, decode its structures, read the properties, provide a function to write a modified file, and print the properties to the console.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

typedef struct {
    char *filepath;
    uint8_t *data;
    size_t size;
    // Properties
    char file_magic[9];
    uint64_t first_chunk_num;
    uint64_t next_record_num;
    uint32_t header_size;
    uint16_t minor_version;
    uint16_t major_version;
    uint16_t header_block_size;
    uint16_t chunk_count;
    uint32_t flags;
    uint32_t file_checksum;
    char chunk_magic[9];
    uint64_t first_rec_log;
    uint64_t last_rec_log;
    uint64_t first_rec_file;
    uint64_t last_rec_file;
    uint32_t chunk_header_size;
    uint32_t last_rec_offset;
    uint32_t next_rec_offset;
    uint32_t chunk_checksum;
} EvtxHandler;

void evtx_read(EvtxHandler *handler) {
    FILE *f = fopen(handler->filepath, "rb");
    fseek(f, 0, SEEK_END);
    handler->size = ftell(f);
    fseek(f, 0, SEEK_SET);
    handler->data = malloc(handler->size);
    fread(handler->data, 1, handler->size, f);
    fclose(f);
    evtx_decode_properties(handler);
}

void evtx_decode_properties(EvtxHandler *handler) {
    uint8_t *data = handler->data;
    size_t offset = 0;
    memcpy(handler->file_magic, data + offset, 8);
    handler->file_magic[8] = '\0';
    offset += 8;
    memcpy(&handler->first_chunk_num, data + offset, 8);
    offset += 8;
    memcpy(&handler->next_record_num, data + offset, 8);
    offset += 8;
    memcpy(&handler->header_size, data + offset, 4);
    offset += 4;
    memcpy(&handler->minor_version, data + offset, 2);
    offset += 2;
    memcpy(&handler->major_version, data + offset, 2);
    offset += 2;
    memcpy(&handler->header_block_size, data + offset, 2);
    offset += 2;
    memcpy(&handler->chunk_count, data + offset, 2);
    offset += 86;
    memcpy(&handler->flags, data + offset, 4);
    offset += 4;
    memcpy(&handler->file_checksum, data + offset, 4);

    offset = 4096;
    memcpy(handler->chunk_magic, data + offset, 8);
    handler->chunk_magic[8] = '\0';
    offset += 8;
    memcpy(&handler->first_rec_log, data + offset, 8);
    offset += 8;
    memcpy(&handler->last_rec_log, data + offset, 8);
    offset += 8;
    memcpy(&handler->first_rec_file, data + offset, 8);
    offset += 8;
    memcpy(&handler->last_rec_file, data + offset, 8);
    offset += 8;
    memcpy(&handler->chunk_header_size, data + offset, 4);
    offset += 4;
    memcpy(&handler->last_rec_offset, data + offset, 4);
    offset += 4;
    memcpy(&handler->next_rec_offset, data + offset, 4);
    offset += 76;
    memcpy(&handler->chunk_checksum, data + offset, 4);
}

void evtx_print_properties(EvtxHandler *handler) {
    printf("File Header Properties:\n");
    printf("file_magic: %s\n", handler->file_magic);
    printf("first_chunk_num: %llu\n", handler->first_chunk_num);
    printf("next_record_num: %llu\n", handler->next_record_num);
    printf("header_size: %u\n", handler->header_size);
    printf("minor_version: %u\n", handler->minor_version);
    printf("major_version: %u\n", handler->major_version);
    printf("header_block_size: %u\n", handler->header_block_size);
    printf("chunk_count: %u\n", handler->chunk_count);
    printf("flags: %u\n", handler->flags);
    printf("file_checksum: %u\n", handler->file_checksum);
    printf("\nChunk Header Properties (First Chunk):\n");
    printf("chunk_magic: %s\n", handler->chunk_magic);
    printf("first_rec_log: %llu\n", handler->first_rec_log);
    printf("last_rec_log: %llu\n", handler->last_rec_log);
    printf("first_rec_file: %llu\n", handler->first_rec_file);
    printf("last_rec_file: %llu\n", handler->last_rec_file);
    printf("chunk_header_size: %u\n", handler->chunk_header_size);
    printf("last_rec_offset: %u\n", handler->last_rec_offset);
    printf("next_rec_offset: %u\n", handler->next_rec_offset);
    printf("chunk_checksum: %u\n", handler->chunk_checksum);
}

void evtx_write(EvtxHandler *handler, char *new_filepath, uint32_t new_flags) {
    if (new_flags != 0) {  // 0 means no update
        memcpy(handler->data + 120, &new_flags, 4);
        evtx_decode_properties(handler);
    }
    FILE *f = fopen(new_filepath, "wb");
    fwrite(handler->data, 1, handler->size, f);
    fclose(f);
}

void evtx_free(EvtxHandler *handler) {
    free(handler->data);
}