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.
2. Two Direct Download Links for .EVTX Files
The following are direct download links to sample .EVTX files from public repositories on GitHub:
- https://raw.githubusercontent.com/sbousseaden/EVTX-ATTACK-SAMPLES/master/APT/APT29.evtx
- https://raw.githubusercontent.com/sbousseaden/EVTX-ATTACK-SAMPLES/master/Credential Access/4663_Security.evtx
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.
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);
}