#[cfg(feature = "wevt_templates")]
mod wevt_cache_fallback {
use std::sync::Arc;
use evtx::EvtxParser;
use evtx::ParserSettings;
use evtx::wevt_templates::WevtCache;
const EVTX_FILE_HEADER_SIZE: usize = 4096;
const EVTX_CHUNK_SIZE: usize = 65536;
fn compute_wevt_inline_name_hash_utf16(name: &str) -> u16 {
const MULT: u32 = 65599;
let mut hash: u32 = 0;
for cu in name.encode_utf16() {
hash = hash.wrapping_mul(MULT).wrapping_add(u32::from(cu));
}
(hash & 0xffff) as u16
}
fn wevt_inline_name_bytes(name: &str) -> Vec<u8> {
let mut out = Vec::new();
let hash = compute_wevt_inline_name_hash_utf16(name);
out.extend_from_slice(&hash.to_le_bytes());
out.extend_from_slice(&(name.encode_utf16().count() as u16).to_le_bytes());
for cu in name.encode_utf16() {
out.extend_from_slice(&cu.to_le_bytes());
}
out.extend_from_slice(&0u16.to_le_bytes()); out
}
fn build_minimal_wevt_template_binxml() -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&[0x0f, 0x01, 0x01, 0x00]);
buf.push(0x01);
buf.extend_from_slice(&0xFFFFu16.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&wevt_inline_name_bytes("Event"));
buf.push(0x02);
buf.push(0x01);
buf.extend_from_slice(&0xFFFFu16.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&wevt_inline_name_bytes("Data"));
buf.push(0x02);
buf.push(0x0d);
buf.extend_from_slice(&0u16.to_le_bytes());
buf.push(0x01);
buf.push(0x04); buf.push(0x04); buf.push(0x00); buf
}
fn find_first_template_instance_guid_and_offset(
evtx_bytes: &[u8],
) -> (usize, u64, u32, winstructs::guid::Guid) {
let settings = ParserSettings::default().num_threads(1);
let mut parser = EvtxParser::from_buffer(evtx_bytes.to_vec())
.expect("parse EVTX buffer")
.with_configuration(settings.clone());
for (chunk_index, chunk_res) in parser.chunks().enumerate() {
let mut chunk_data = chunk_res.expect("chunk");
let mut chunk = chunk_data
.parse(Arc::new(settings.clone()))
.expect("parse chunk");
for record_res in chunk.iter() {
let record = record_res.expect("record parse should succeed");
let instances = record.template_instances().expect("template instances");
let Some(tpl) = instances.first() else {
continue;
};
let template_def_offset = tpl.template_def_offset;
let guid = if let Some(g) = tpl.template_guid.as_ref() {
g.clone()
} else {
let off = template_def_offset as usize + 4;
let guid_bytes = record
.chunk
.data
.get(off..off + 16)
.expect("guid bytes in chunk");
winstructs::guid::Guid::from_buffer(guid_bytes).expect("guid parse")
};
return (
chunk_index,
record.event_record_id,
template_def_offset,
guid,
);
}
}
panic!("no TemplateInstance found in sample EVTX");
}
fn corrupt_chunk_template_binxml(
evtx_bytes: &mut [u8],
chunk_index: usize,
template_def_offset: u32,
) {
let template_data_start = EVTX_FILE_HEADER_SIZE
+ chunk_index * EVTX_CHUNK_SIZE
+ template_def_offset as usize
+ 24;
let b = evtx_bytes
.get_mut(template_data_start)
.expect("template data start in bounds");
*b = 0xFF;
}
#[test]
fn wevt_cache_fallback_recovers_corrupt_chunk_template_for_xml_and_json() {
let sample = include_bytes!("../samples/security.evtx");
let (chunk_index, record_id, template_def_offset, guid) =
find_first_template_instance_guid_and_offset(sample);
let mut temp_bytes = vec![0u8; 40];
temp_bytes.extend_from_slice(&build_minimal_wevt_template_binxml());
let cache = Arc::new(WevtCache::new());
cache.insert_temp_bytes(&guid.to_string(), Arc::new(temp_bytes));
let mut corrupted = sample.to_vec();
corrupt_chunk_template_binxml(&mut corrupted, chunk_index, template_def_offset);
{
let settings = ParserSettings::default().num_threads(1);
let mut parser = EvtxParser::from_buffer(corrupted.clone())
.expect("parse corrupted buffer")
.with_configuration(settings.clone());
let mut saw_target_error = false;
for chunk_res in parser.chunks() {
let mut chunk_data = chunk_res.expect("chunk");
let mut chunk = chunk_data
.parse(Arc::new(settings.clone()))
.expect("parse chunk");
for record_res in chunk.iter() {
match record_res {
Ok(record) => {
if record.event_record_id == record_id {
panic!("expected record {record_id} to fail without cache");
}
}
Err(e) => {
if let evtx::err::EvtxError::FailedToParseRecord {
record_id: rid, ..
} = &e
&& *rid == record_id
{
saw_target_error = true;
break;
}
}
}
}
if saw_target_error {
break;
}
}
assert!(
saw_target_error,
"expected to observe FailedToParseRecord for record_id={record_id} without cache"
);
}
{
let settings = ParserSettings::default()
.num_threads(1)
.wevt_cache(Some(cache));
let mut parser = EvtxParser::from_buffer(corrupted)
.expect("parse corrupted buffer")
.with_configuration(settings.clone());
let mut saw_target_ok = false;
for chunk_res in parser.chunks() {
let mut chunk_data = chunk_res.expect("chunk");
let mut chunk = chunk_data
.parse(Arc::new(settings.clone()))
.expect("parse chunk");
for record_res in chunk.iter() {
let record = match record_res {
Ok(r) => r,
Err(e) => panic!("unexpected parse error with cache: {e:?}"),
};
if record.event_record_id != record_id {
continue;
}
let xml = record.clone().into_xml().expect("render xml").data;
assert!(xml.contains("<Event>") && xml.contains("</Event>"));
assert!(xml.contains("<Data>") && xml.contains("</Data>"));
let json = record.into_json().expect("render json").data;
let v: serde_json::Value = serde_json::from_str(&json).expect("valid json");
assert!(v.get("Event").is_some(), "expected Event key in JSON");
saw_target_ok = true;
break;
}
if saw_target_ok {
break;
}
}
assert!(
saw_target_ok,
"expected to render record_id={record_id} successfully with cache"
);
}
}
}