1use forensicnomicon::report::{
6 Category, Evidence, Finding, Location, Provenance, Report, Severity, Source,
7};
8
9use crate::DiskReport;
10
11fn classify(code: &str) -> Category {
14 let c = code.to_ascii_uppercase();
15 if c.contains("CRC") || c.contains("INTEGRITY") {
16 Category::Integrity
17 } else if c.contains("OVERLAP")
18 || c.contains("OOB")
19 || c.contains("BOUND")
20 || c.contains("CHS")
21 || c.contains("MAP-COUNT")
22 {
23 Category::Structure
24 } else if c.contains("RESIDUAL")
25 || c.contains("SLACK")
26 || c.contains("GAP")
27 || c.contains("CARVE")
28 || c.contains("UNMAPPED")
29 || c.contains("ZEROLEN")
30 {
31 Category::Residue
32 } else if c.contains("HIDDEN")
33 || c.contains("CONCEAL")
34 || c.contains("WIPED")
35 || c.contains("ERASED")
36 || c.contains("PROTECTIVE")
37 {
38 Category::Concealment
39 } else if c.contains("BOOT") {
40 Category::Threat
41 } else {
42 Category::Structure
43 }
44}
45
46macro_rules! map_severity {
47 ($name:ident, $native:path) => {
48 fn $name(s: $native) -> Severity {
49 use $native as S;
50 match s {
51 S::Info => Severity::Info,
52 S::Low => Severity::Low,
53 S::Medium => Severity::Medium,
54 S::High => Severity::High,
55 S::Critical => Severity::Critical,
56 }
57 }
58 };
59}
60map_severity!(mbr_sev, mbr_forensic::Severity);
61map_severity!(gpt_sev, gpt_forensic::Severity);
62map_severity!(apm_sev, apm_forensic::Severity);
63
64#[must_use]
66pub fn mbr_findings(a: &mbr_forensic::MbrAnalysis) -> Vec<Finding> {
67 a.anomalies
68 .iter()
69 .map(|an| Finding {
70 severity: mbr_sev(an.severity),
71 category: classify(an.code),
72 code: an.code.to_string(),
73 note: an.note.clone(),
74 source: Source {
75 analyzer: "mbr-forensic".to_string(),
76 scope: "MBR".to_string(),
77 },
78 evidence: vec![Evidence {
79 field: "offset".to_string(),
80 value: format!("{:#x}", an.offset),
81 location: Some(Location::ByteOffset(an.offset)),
82 }],
83 })
84 .collect()
85}
86
87#[must_use]
89pub fn gpt_findings(a: &gpt_forensic::GptAnalysis) -> Vec<Finding> {
90 a.anomalies
91 .iter()
92 .map(|an| Finding {
93 severity: gpt_sev(an.severity),
94 category: classify(an.code),
95 code: an.code.to_string(),
96 note: an.note.clone(),
97 source: Source {
98 analyzer: "gpt-forensic".to_string(),
99 scope: "GPT".to_string(),
100 },
101 evidence: Vec::new(),
102 })
103 .collect()
104}
105
106#[must_use]
108pub fn apm_findings(a: &apm_forensic::ApmAnalysis) -> Vec<Finding> {
109 a.anomalies
110 .iter()
111 .map(|an| Finding {
112 severity: apm_sev(an.severity),
113 category: classify(an.code),
114 code: an.code.to_string(),
115 note: an.note.clone(),
116 source: Source {
117 analyzer: "apm-forensic".to_string(),
118 scope: "APM".to_string(),
119 },
120 evidence: Vec::new(),
121 })
122 .collect()
123}
124
125#[must_use]
127pub fn mbr_provenance(a: &mbr_forensic::MbrAnalysis) -> Vec<Provenance> {
128 vec![
129 Provenance {
130 label: "boot code".to_string(),
131 value: format!("{:?}", a.boot_code_id),
132 source: "mbr-forensic".to_string(),
133 },
134 Provenance {
135 label: "partitioning era".to_string(),
136 value: format!("{:?}", a.era),
137 source: "mbr-forensic".to_string(),
138 },
139 Provenance {
140 label: "disk signature".to_string(),
141 value: format!("{:#010x}", a.disk_serial),
142 source: "mbr-forensic".to_string(),
143 },
144 ]
145}
146
147#[must_use]
149pub fn gpt_provenance(a: &gpt_forensic::GptAnalysis) -> Vec<Provenance> {
150 vec![
151 Provenance {
152 label: "disk GUID".to_string(),
153 value: a.disk_guid.to_string(),
154 source: "gpt-forensic".to_string(),
155 },
156 Provenance {
157 label: "sector size".to_string(),
158 value: format!("{} bytes", a.sector_size),
159 source: "gpt-forensic".to_string(),
160 },
161 Provenance {
162 label: "GPT SHA-256".to_string(),
163 value: a.gpt_sha256.clone(),
164 source: "gpt-forensic".to_string(),
165 },
166 ]
167}
168
169#[must_use]
171pub fn apm_provenance(a: &apm_forensic::ApmAnalysis) -> Vec<Provenance> {
172 vec![
173 Provenance {
174 label: "block size".to_string(),
175 value: format!("{} bytes", a.block_size),
176 source: "apm-forensic".to_string(),
177 },
178 Provenance {
179 label: "device blocks".to_string(),
180 value: a.device_block_count.to_string(),
181 source: "apm-forensic".to_string(),
182 },
183 ]
184}
185
186#[must_use]
189pub fn report(disk: &DiskReport) -> Report {
190 let (findings, provenance) = match disk {
191 DiskReport::Apm(a) => (apm_findings(a), apm_provenance(a)),
192 DiskReport::Mbr(m) => (mbr_findings(m), mbr_provenance(m)),
193 DiskReport::Gpt(m) => {
194 let mut findings = mbr_findings(m);
195 let mut provenance = mbr_provenance(m);
196 if let Some(gpt) = &m.gpt {
197 findings.extend(gpt_findings(gpt));
198 provenance.extend(gpt_provenance(gpt));
199 }
200 (findings, provenance)
201 }
202 };
203 Report {
204 findings,
205 provenance,
206 timeline: Vec::new(),
207 }
208}