pub const ELFFILE_MAGIC: [u8; 8] = *b"ElfFile\0";
pub const FILE_HEADER_SIZE: u64 = 0x80;
pub const FILE_HEADER_BLOCK_SIZE: u64 = 0x1000;
pub const ELFCHNK_MAGIC: [u8; 8] = *b"ElfChnk\0";
pub const CHUNK_SIZE: u64 = 0x1_0000; pub const CHUNK_HEADER_SIZE: u64 = 0x80;
pub const CHUNK_RECORDS_OFFSET: u64 = 0x200;
pub const CHUNK_HEADER_CRC_RANGE: core::ops::Range<usize> = 0..0x78;
pub const CHUNK_HEADER_CRC_OFFSET: usize = 0x78;
pub const EVENT_RECORDS_CRC_OFFSET: usize = 0x34;
pub const RECORD_MAGIC: [u8; 4] = [0x2A, 0x2A, 0x00, 0x00];
pub const RECORD_HEADER_SIZE: u64 = 0x18;
pub const FILE_FLAG_DIRTY: u32 = 0x0001;
pub const FILE_FLAG_FULL: u32 = 0x0002;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct EvtxFileHeaderOffsets {
pub magic: u64, pub first_chunk_number: u64, pub last_chunk_number: u64, pub next_record_id: u64, pub header_size: u64, pub minor_version: u64, pub major_version: u64, pub header_block_size: u64, pub chunk_count: u64, pub file_flags: u64, pub checksum: u64, }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct EvtxChunkHeaderOffsets {
pub magic: u64, pub first_event_record_number: u64, pub last_event_record_number: u64, pub first_event_record_id: u64, pub last_event_record_id: u64, pub header_size: u64, pub last_event_record_data_offset: u64, pub free_space_offset: u64, pub event_records_checksum: u64, pub header_checksum: u64, }
pub const EVTX_FILE_HEADER_OFFSETS: EvtxFileHeaderOffsets = EvtxFileHeaderOffsets {
magic: 0x00,
first_chunk_number: 0x08,
last_chunk_number: 0x10,
next_record_id: 0x18,
header_size: 0x20,
minor_version: 0x24,
major_version: 0x26,
header_block_size: 0x28,
chunk_count: 0x2A,
file_flags: 0x78,
checksum: 0x7C,
};
pub const EVTX_CHUNK_HEADER_OFFSETS: EvtxChunkHeaderOffsets = EvtxChunkHeaderOffsets {
magic: 0x00,
first_event_record_number: 0x08,
last_event_record_number: 0x10,
first_event_record_id: 0x18,
last_event_record_id: 0x20,
header_size: 0x28,
last_event_record_data_offset: 0x2C,
free_space_offset: 0x30,
event_records_checksum: 0x34,
header_checksum: 0x78,
};
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct LateralMovementEvent {
pub timestamp: String,
pub event_id: u32,
pub source_user: Option<String>,
pub target_user: Option<String>,
pub target_host: Option<String>,
pub logon_type: Option<u32>,
pub auth_package: Option<String>,
pub encryption_type: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RdpSessionEvent {
pub timestamp: String,
pub event_id: u32,
pub user: Option<String>,
pub session_id: Option<u32>,
pub source_ip: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SmbAccessEvent {
pub timestamp: String,
pub event_id: u32,
pub subject_user: Option<String>,
pub share_name: Option<String>,
pub share_path: Option<String>,
pub relative_target: Option<String>,
pub ip_address: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DefenderEvent {
pub timestamp: String,
pub event_id: u32,
pub threat_name: Option<String>,
pub severity: Option<String>,
pub path: Option<String>,
pub action_taken: Option<String>,
pub process_name: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct WmiEvent {
pub timestamp: String,
pub event_id: u32,
pub provider: Option<String>,
pub filter_name: Option<String>,
pub consumer_name: Option<String>,
pub query: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ScheduledTask {
pub timestamp: String,
pub event_id: u32,
pub task_name: Option<String>,
pub task_content: Option<String>,
pub subject_user: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ProcessExecution {
pub timestamp: String,
pub event_id: u32,
pub pid: u64,
pub parent_pid: u64,
pub image: String,
pub command_line: String,
pub parent_image: Option<String>,
pub is_lolbin: bool,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "kind"))]
pub enum EvtxEvent {
LateralMovement(LateralMovementEvent),
RdpSession(RdpSessionEvent),
SmbAccess(SmbAccessEvent),
Defender(DefenderEvent),
Wmi(WmiEvent),
ScheduledTask(ScheduledTask),
ProcessExecution(ProcessExecution),
}
impl EvtxEvent {
pub fn timestamp(&self) -> &str {
match self {
Self::LateralMovement(e) => &e.timestamp,
Self::RdpSession(e) => &e.timestamp,
Self::SmbAccess(e) => &e.timestamp,
Self::Defender(e) => &e.timestamp,
Self::Wmi(e) => &e.timestamp,
Self::ScheduledTask(e) => &e.timestamp,
Self::ProcessExecution(e) => &e.timestamp,
}
}
pub fn event_id(&self) -> u32 {
match self {
Self::LateralMovement(e) => e.event_id,
Self::RdpSession(e) => e.event_id,
Self::SmbAccess(e) => e.event_id,
Self::Defender(e) => e.event_id,
Self::Wmi(e) => e.event_id,
Self::ScheduledTask(e) => e.event_id,
Self::ProcessExecution(e) => e.event_id,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn file_magic_is_correct() {
assert_eq!(&ELFFILE_MAGIC, b"ElfFile\0");
}
#[test]
fn chunk_magic_is_correct() {
assert_eq!(&ELFCHNK_MAGIC, b"ElfChnk\0");
}
#[test]
fn record_magic_is_correct() {
assert_eq!(RECORD_MAGIC, [0x2A, 0x2A, 0x00, 0x00]);
}
#[test]
fn chunk_size_is_64kib() {
assert_eq!(CHUNK_SIZE, 65536);
}
#[test]
fn records_start_at_0x200() {
assert_eq!(CHUNK_RECORDS_OFFSET, 0x200);
}
#[test]
fn header_crc_covers_first_120_bytes() {
assert_eq!(CHUNK_HEADER_CRC_RANGE, 0..0x78);
assert_eq!(CHUNK_HEADER_CRC_OFFSET, 0x78);
}
#[test]
fn file_header_offsets_are_correct() {
assert_eq!(EVTX_FILE_HEADER_OFFSETS.magic, 0x00);
assert_eq!(EVTX_FILE_HEADER_OFFSETS.next_record_id, 0x18);
assert_eq!(EVTX_FILE_HEADER_OFFSETS.chunk_count, 0x2A);
assert_eq!(EVTX_FILE_HEADER_OFFSETS.checksum, 0x7C);
}
#[test]
fn chunk_header_offsets_are_correct() {
assert_eq!(EVTX_CHUNK_HEADER_OFFSETS.first_event_record_number, 0x08);
assert_eq!(EVTX_CHUNK_HEADER_OFFSETS.event_records_checksum, 0x34);
assert_eq!(EVTX_CHUNK_HEADER_OFFSETS.header_checksum, 0x78);
}
}