#![allow(dead_code)]
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct PeInfo {
pub file: String,
pub file_size: u64,
pub machine: String, #[serde(rename = "IsPE32Plus")]
pub is_pe32_plus: bool,
pub entry_point_rva: u32,
pub entry_point_hex: String,
pub image_base: u64,
pub section_count: u32,
pub sections: Vec<Section>,
pub imported_dll_count: u32,
pub imports: Vec<Import>,
pub imp_hash: Option<String>,
pub resource_size: u32, pub has_overlay: bool,
pub overlay_offset: Option<u64>,
pub overlay_size: u64, pub mapped_offset: Option<u64>,
pub mapped_section: Option<String>,
pub warnings: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct Section {
pub name: String,
pub virtual_size: u32,
pub virtual_address: u32,
pub size_of_raw_data: u32,
pub pointer_to_raw_data: u32,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct Import {
pub dll: String,
pub functions: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct EntropyResult {
pub file: String,
pub file_size: u64,
pub window_size: u32,
pub window_count: u32,
pub overall_entropy: f64,
pub high_entropy_threshold: f64,
pub high_window_count: u32,
pub windows: Vec<EntropyWindow>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct EntropyWindow {
pub index: u32,
pub start_offset: u64,
pub start_hex: String,
pub end_offset: u64, pub length: u32,
pub entropy: f64, pub is_high: bool,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct StringHit {
pub offset: u64,
pub offset_hex: String,
pub encoding: String, pub length: u32,
pub value: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct Ioc {
pub file: String,
pub file_size: u64,
#[serde(rename = "MD5")]
pub md5: String,
#[serde(rename = "SHA1")]
pub sha1: String,
#[serde(rename = "SHA256")]
pub sha256: String,
pub overall_entropy: f64,
pub high_entropy_windows: u32,
pub printable_string_count: u32,
#[serde(rename = "IsPE")]
pub is_pe: bool,
pub machine: Option<String>,
pub imp_hash: Option<String>,
pub imported_dll_count: Option<u32>,
pub has_overlay: Option<bool>,
pub overlay_size: Option<u64>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct OffsetResult {
pub success: bool,
pub file: Option<String>,
pub offset_input: Option<String>,
pub offset_decimal: Option<i64>,
pub offset_hex: Option<String>,
pub file_size: Option<i64>,
pub encoding_requested: Option<String>,
pub encoding_detected: Option<String>,
pub line_number: Option<i64>,
pub line_text: Option<String>,
pub line_text_truncated: bool,
pub character_position: Option<i32>,
pub preview_character_position: Option<i32>,
pub byte_position_in_line: Option<i64>,
#[serde(default)]
pub context_lines: Vec<Value>,
pub target_byte_hex: Option<String>,
pub target_byte_decimal: Option<i32>,
pub compare_file: Option<String>,
pub compare_byte_hex: Option<String>,
pub compare_byte_decimal: Option<i32>,
pub bytes_differ: Option<bool>,
pub window_start_offset: Option<i64>,
pub window_end_offset: Option<i64>,
#[serde(default)]
pub hex_dump: Vec<Value>,
pub duration_ms: f64,
#[serde(default)]
pub warnings: Vec<String>,
pub error: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct ThreatScanResult {
pub success: bool,
pub file: Option<String>,
pub file_size: Option<i64>,
pub file_sha256: Option<String>,
pub scan_timestamp_utc: Option<String>,
pub engine: Option<String>,
pub scan_mode: Option<String>,
pub boundary_unit: Option<String>,
pub search_model: Option<String>,
pub encoding: Option<String>,
pub initial_status: Option<String>,
pub detection_prefix_length: Option<i64>,
pub detection_boundary_offset: Option<i64>,
pub detection_boundary_hex: Option<String>,
pub detection_character_index: Option<i64>,
pub detection_utf16_code_unit_index: Option<i64>,
pub known_clean_prefix_length: Option<i64>,
pub stable: bool,
pub confidence: Option<String>,
pub scan_count: i32,
pub signature_name: Option<String>,
pub provider_result: Option<Value>, pub provider_h_result: Option<Value>, pub provider_metadata: Option<Value>, pub boundary_validation: Option<BoundaryValidation>,
pub provider_output: Option<String>,
#[serde(default)]
pub probe_log: Vec<ProbeLogEntry>,
pub inspection: Option<OffsetResult>,
pub duration_ms: f64,
#[serde(default)]
pub warnings: Vec<String>,
pub error: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct BoundaryValidation {
#[serde(default)]
pub full_content_statuses: Vec<String>,
#[serde(default)]
pub known_clean_statuses: Vec<String>,
#[serde(default)]
pub known_detected_statuses: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct ProbeLogEntry {
pub sequence: i64,
pub prefix_length: i64,
pub status: String,
pub provider_result: Option<Value>,
pub signature_name: Option<String>,
pub cacheable: bool,
pub elapsed_ms: f64,
pub timestamp_utc: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ioc_serializes_with_offsetinspect_field_names() {
let ioc = Ioc {
file: "x".into(),
file_size: 10,
md5: "m".into(),
sha1: "s1".into(),
sha256: "s2".into(),
overall_entropy: 1.0,
high_entropy_windows: 0,
printable_string_count: 0,
is_pe: false,
machine: None,
imp_hash: None,
imported_dll_count: None,
has_overlay: None,
overlay_size: None,
};
let json = serde_json::to_string(&ioc).unwrap();
for key in [
"\"MD5\":",
"\"SHA1\":",
"\"SHA256\":",
"\"IsPE\":",
"\"OverallEntropy\":",
"\"HighEntropyWindows\":",
"\"PrintableStringCount\":",
] {
assert!(json.contains(key), "missing {key} in {json}");
}
assert!(!json.contains("\"Md5\":"));
assert!(!json.contains("\"IsPe\":"));
}
#[test]
fn peinfo_uses_the_ispe32plus_rename() {
let pe = PeInfo {
file: "x".into(),
file_size: 0,
machine: "x64 (AMD64)".into(),
is_pe32_plus: true,
entry_point_rva: 0,
entry_point_hex: "0x0".into(),
image_base: 0,
section_count: 0,
sections: vec![],
imported_dll_count: 0,
imports: vec![],
imp_hash: None,
resource_size: 0,
has_overlay: false,
overlay_offset: None,
overlay_size: 0,
mapped_offset: None,
mapped_section: None,
warnings: vec![],
};
let json = serde_json::to_string(&pe).unwrap();
assert!(json.contains("\"IsPE32Plus\":"));
assert!(!json.contains("\"IsPe32Plus\":"));
}
}