use thiserror::Error;
use crate::evtx_parser::ReadSeek;
use crate::FileOffset;
use crate::utils::dump_stream;
use log::error;
use crate::evtx_record::RecordId;
use std::error::Error as StdError;
use std::io;
use std::path::Path;
use winstructs::guid::Guid;
pub type Result<T> = std::result::Result<T, EvtxError>;
pub type SerializationResult<T> = std::result::Result<T, crate::err::SerializationError>;
pub(crate) type DeserializationResult<T> = std::result::Result<T, crate::err::DeserializationError>;
pub(crate) type EvtxChunkResult<T> = std::result::Result<T, crate::err::ChunkError>;
const DEFAULT_LOOKBEHIND_LEN: i32 = 100;
#[derive(Error, Debug)]
#[error(
"Offset `0x{offset:08x} ({offset})` - An error has occurred while trying to deserialize binary stream \n\
{message}
Original message:
`{source}`
Hexdump:
{hexdump}"
)]
pub struct WrappedIoError {
offset: FileOffset,
hexdump: String,
message: String,
#[source]
source: Box<dyn StdError + 'static + Send + Sync>,
}
impl WrappedIoError {
pub fn capture_hexdump<S: ReadSeek>(
error: Box<dyn std::error::Error + 'static + Send + Sync>,
stream: &mut S,
) -> WrappedIoError {
let offset = stream.tell().unwrap_or_else(|_| {
error!("while trying to recover error information -> `tell` failed.");
0
});
let hexdump = dump_stream(stream, DEFAULT_LOOKBEHIND_LEN)
.unwrap_or_else(|_| "<Error while capturing hexdump>".to_string());
WrappedIoError {
offset,
hexdump,
message: "".to_string(),
source: error,
}
}
pub fn io_error_with_message<S: ReadSeek, T: AsRef<str>>(
error: io::Error,
context: T,
stream: &mut S,
) -> WrappedIoError {
let offset = stream.tell().unwrap_or_else(|_| {
error!("while trying to recover error information -> `tell` failed.");
0
});
let hexdump = dump_stream(stream, DEFAULT_LOOKBEHIND_LEN)
.unwrap_or_else(|_| "<Error while capturing hexdump>".to_string());
WrappedIoError {
offset,
hexdump,
message: context.as_ref().to_string(),
source: Box::new(error),
}
}
}
#[derive(Debug, Error)]
pub enum DeserializationError {
#[error("Failed to deserialize `{token_name}` of type `{t}`")]
FailedToReadToken {
t: String,
token_name: &'static str,
source: WrappedIoError,
},
#[error(transparent)]
IoWithContext(#[from] WrappedIoError),
#[error(transparent)]
Io(#[from] io::Error),
#[error("Failed to deserialize template `{template_id}`")]
FailedToDeserializeTemplate {
template_id: Guid,
source: Box<DeserializationError>,
},
#[error("Failed to decode ANSI string (encoding used: {encoding_used}) - `{inner_message}`")]
AnsiDecodeError {
encoding_used: &'static str,
inner_message: String,
},
#[error("Offset 0x{offset:08x}: Tried to read an invalid byte `0x{value:02x}` as binxml token")]
InvalidToken { value: u8, offset: u64 },
#[error(
"Offset 0x{offset:08x}: Tried to read an invalid byte `0x{value:2x}` as binxml value variant"
)]
InvalidValueVariant { value: u8, offset: u64 },
#[error("buffer too small for {what} at offset {offset} (need {need} bytes, have {have})")]
Truncated {
what: &'static str,
offset: u64,
need: usize,
have: usize,
},
#[error(
"Offset 0x{offset:08x}: WEVT inline name hash mismatch (expected 0x{expected:04x}, found 0x{found:04x})"
)]
WevtInlineNameHashMismatch {
expected: u16,
found: u16,
offset: u64,
},
#[error("Offset 0x{offset:08x}: WEVT inline name missing NUL terminator (found 0x{found:04x})")]
WevtInlineNameMissingNulTerminator { found: u16, offset: u64 },
#[error("An out-of-range date, invalid month and/or day")]
InvalidDateTimeError,
#[error("Invalid EVTX record header magic, expected `2a2a0000`, found `{magic:2X?}`")]
InvalidEvtxRecordHeaderMagic { magic: [u8; 4] },
#[error("Invalid EVTX chunk header magic, expected `ElfChnk0`, found `{magic:2X?}`")]
InvalidEvtxChunkMagic { magic: [u8; 8] },
#[error("Invalid EVTX file header magic, expected `ElfFile0`, found `{magic:2X?}`")]
InvalidEvtxFileHeaderMagic { magic: [u8; 8] },
#[error("Unknown EVTX record header flags value: {value}")]
UnknownEvtxHeaderFlagValue { value: u32 },
#[error("Offset {offset}: Token `{name}` is unimplemented")]
UnimplementedToken { name: &'static str, offset: u64 },
#[error("Offset {offset}: Value variant `{name}` (size {size:?}) is unimplemented")]
UnimplementedValueVariant {
name: String,
size: Option<u16>,
offset: u64,
},
}
#[derive(Debug, Error)]
pub enum SerializationError {
#[error("Writing to XML failed")]
XmlOutputError {
#[from]
source: io::Error,
},
#[error("Building a JSON document failed with message: {message}")]
JsonStructureError { message: String },
#[error("`serde_json` failed")]
JsonError {
#[from]
source: serde_json::error::Error,
},
#[error("Record data contains invalid UTF-8")]
RecordContainsInvalidUTF8 {
#[from]
source: std::string::FromUtf8Error,
},
#[error("Unimplemented: {message}")]
Unimplemented { message: String },
}
#[derive(Debug, Error)]
pub enum InputError {
#[error("Failed to open file {}", path.display())]
FailedToOpenFile {
source: std::io::Error,
path: std::path::PathBuf,
},
}
impl InputError {
pub fn failed_to_open_file<P: AsRef<Path>>(source: io::Error, path: P) -> Self {
InputError::FailedToOpenFile {
source,
path: path.as_ref().to_path_buf(),
}
}
}
#[derive(Debug, Error)]
pub enum ChunkError {
#[error("Reached EOF while trying to allocate chunk")]
IncompleteChunk,
#[error("Failed to seek to start of chunk.")]
FailedToSeekToChunk(io::Error),
#[error("Failed to parse chunk header")]
FailedToParseChunkHeader(#[from] DeserializationError),
#[error("chunk data CRC32 invalid")]
InvalidChunkChecksum { expected: u32, found: u32 },
#[error("Failed to build string cache")]
FailedToBuildStringCache { source: DeserializationError },
}
#[derive(Debug, Error)]
pub enum EvtxError {
#[error("An error occurred while trying to read input.")]
InputError(#[from] InputError),
#[error("An error occurred while trying to serialize binary xml to output.")]
SerializationError(#[from] SerializationError),
#[error("An error occurred while trying to deserialize evtx stream.")]
DeserializationError(#[from] DeserializationError),
#[error("Failed to parse chunk number {chunk_id}")]
FailedToParseChunk {
chunk_id: u64,
source: Box<ChunkError>,
},
#[error("Failed to parse record number {record_id}")]
FailedToParseRecord {
record_id: RecordId,
source: Box<EvtxError>,
},
#[error("Calculation Error, reason: {}", .0)]
CalculationError(String),
#[error("An IO error occured.")]
IoError(#[from] std::io::Error),
#[error("Failed to create record model, reason: {}", .0)]
FailedToCreateRecordModel(&'static str),
#[error("Unimplemented: {name}")]
Unimplemented { name: String },
#[error(
"Invalid EVTX record data size, should be equals or greater than {expected}, found `{length}`"
)]
InvalidDataSize { length: u32, expected: u32 },
}
impl EvtxError {
pub fn calculation_error(msg: String) -> EvtxError {
EvtxError::CalculationError(msg)
}
pub fn incomplete_chunk(chunk_id: u64) -> EvtxError {
EvtxError::FailedToParseChunk {
chunk_id,
source: Box::new(ChunkError::IncompleteChunk),
}
}
}
#[macro_export]
macro_rules! unimplemented_fn {
($($arg:tt)*) => { Err($crate::err::EvtxError::Unimplemented { name: format!($($arg)*) }) }
}