Files
velociraptor/utils/read_seek_reader_adapter_test.go
Abdelghafour BouhdydandMike Cohen fa9a5b4cd1 Feature/carve evtx, (#5063)
Problem: When Windows event logs are cleared or deleted,
Windows.EventLogs.Cleared can show that a log was cleared, but the
events themselves are lost because parse_evtx() only reads intact log
files. The old data usually stays on disk, in unallocated space, the
pagefile or memory, until it is overwritten.

Solution: 
This PR adds a new carve_evtx() VQL plugin. It scans any file or device
for EVTX chunk signatures at any offset and parses each 64KB chunk on
its own.
- Rows have the same structure as parse_evtx(), plus ChunkOffset,
ChunkHeaderValid, ChunkDataValid, ChunkTruncated and ChunkCompressed.
- NTFS-compressed chunks (the Windows default for event logs) are
decompressed.
- Damaged chunks are flagged, not dropped.
- With output=, the recovered events are also written to a new .evtx
file that Event Viewer can open.

Example:
SELECT * FROM carve_evtx(filename='''\\.\C:''', accessor="raw_file")
WHERE System.Channel = "Application"

Known limitation
Old records in chunk slack are not recovered yet. This could be a
follow-up.

---------

Co-authored-by: Mike Cohen <[email protected]>
2026-10-07 21:34:46 +10:00

50 lines
1.2 KiB
Go

package utils
import (
"bytes"
"io"
"testing"
"github.com/stretchr/testify/assert"
)
// Reading past the end with Read() sets a sticky EOF flag. ReadAt()
// must not be affected by it.
func TestReadSeekReaderAdapterReadAt(t *testing.T) {
data := []byte("0123456789")
adapter := NewReadSeekReaderAdapter(bytes.NewReader(data), nil)
adapter.SetSize(int64(len(data)))
// Exhaust the reader with Read().
_, err := io.ReadAll(adapter)
assert.NoError(t, err)
n, err := adapter.Read(make([]byte, 1))
assert.Equal(t, 0, n)
assert.Equal(t, io.EOF, err)
// ReadAt still works anywhere in the file.
buf := make([]byte, 4)
n, err = adapter.ReadAt(buf, 2)
assert.NoError(t, err)
assert.Equal(t, "2345", string(buf[:n]))
// A read crossing the size is cut short with EOF.
n, err = adapter.ReadAt(buf, 8)
assert.Equal(t, io.EOF, err)
assert.Equal(t, "89", string(buf[:n]))
// Reading past the end does not break later reads.
n, err = adapter.ReadAt(buf, 20)
assert.Equal(t, 0, n)
assert.Equal(t, io.EOF, err)
n, err = adapter.ReadAt(buf, 0)
assert.NoError(t, err)
assert.Equal(t, "0123", string(buf[:n]))
// MakeReaderAtter uses the stateless ReadAt.
_, ok := MakeReaderAtter(adapter).(*ReadSeekReaderAdapter)
assert.True(t, ok)
}