Introduction

Windows OS uses a specific file format in order to store the logs generated by the different programs running on the system. One can usually find those logs at path C:\Windows\System32\winevt\Logs. According to the settings of your OS you can find a bunch of useful information.

  • Login / logoffs (probably the most well known)
  • Execution of programs
  • Services installed / ran
  • Powershell Execution / scripts (according to the PS version)
  • Information about the handles
  • Some custom applications logs
  • ...

All these information can be very useful in the frame of incident response or forensics investigations. Under some circumstances, we may have to deal with corrupted disks or corrupted EVTX files. In such scenario, it is not easy to recover the EVTX files and extract the information inside that is why we decided to write this article and introduce our toolset to do so.

EVTX file-format in short

The EVTX file-format is quite straightforward. We can see it as being a four levels encapsulation file as depicted in the following image.

EVTX Fileformat

It is worth noting that every Chunk is independent from the others. A Chunk is always 64kB in size, in order to reduce the memory impact of the log collection on Windows OS. As a consequence, only one Chunk (per source) has to stay in memory and is committed to the corresponding EVTX file when it is full. One can notice that this design also implies that the Chunk structure is a great element for parallelisation.

Every Chunk contains cross-reference tables that the Events can use:

  1. A Template Reference table holding the BinXML Templates
  2. A String Reference table holding some strings shared between the Events

These cross-reference tables are used to optimize the size of the Chunks both in memory and on disk.

Due to this hard link between the Events and their corresponding Chunk we understand that our carving granularity cannot go lower than the Chunk Level. Indeed we would miss all the information shared in the cross-reference tables if we would like to carve at Event Level, which would result in incomplete Events.

Carving Pseudo-Algorithm

We describe here a simplified version of the carving algorithm we can use to recover events from raw data (file or disk image).

// Pseudo-algorithm in a Go like syntax (so it is probably not Go valid code !)

// Chunk Magic definition
ChunkMagic := "ElfChnk\x00"
// Allocate buffer to check Chunk Magic
buf := make([]byte, len(ChunkMagic))

// Reads bytes from data and set buf until EOF
for err := data.Read(buf); err != io.EOF; err = data.Read(buf) {
  data.Read(buf)
  // We can expect a chunk
  if string(buf) == ChunkMagic {
    // Parse Chunk Header
    header := ParseChunkHeader(data)
    // We validate the header to verify that it is not corrupted
    if header.Valid() {
      // We can parse the chunk
      chunk := ParseChunk(data)
      // We can now parse the Events in the chunk
      for event := range chunk.Events() {
        // Check the validity of the event header to prevent any corruption
        if event.Header.Valid() {
          // Process event (print, return ...)
        }
      }
    }
  }
}

EVTX carving example

Sorry this section is now missing because I had a backup issue and I did not bother to write it again