disk_forensic/
normalize.rs1use forensicnomicon::report::{Category, Finding, Location, Provenance, Report, Source};
6
7use crate::DiskReport;
8
9fn classify(code: &str) -> Category {
12 let c = code.to_ascii_uppercase();
13 if c.contains("CRC") || c.contains("INTEGRITY") {
14 Category::Integrity
15 } else if c.contains("OVERLAP")
16 || c.contains("OOB")
17 || c.contains("BOUND")
18 || c.contains("CHS")
19 || c.contains("MAP-COUNT")
20 {
21 Category::Structure
22 } else if c.contains("RESIDUAL")
23 || c.contains("SLACK")
24 || c.contains("GAP")
25 || c.contains("CARVE")
26 || c.contains("UNMAPPED")
27 || c.contains("ZEROLEN")
28 {
29 Category::Residue
30 } else if c.contains("HIDDEN")
31 || c.contains("CONCEAL")
32 || c.contains("WIPED")
33 || c.contains("ERASED")
34 || c.contains("PROTECTIVE")
35 {
36 Category::Concealment
37 } else if c.contains("BOOT") {
38 Category::Threat
39 } else {
40 Category::Structure
41 }
42}
43
44#[must_use]
50pub fn mbr_findings(a: &mbr_forensic::MbrAnalysis) -> Vec<Finding> {
51 a.anomalies
52 .iter()
53 .map(|an| {
54 Finding::observation(an.severity, classify(an.code), an.code.to_string())
55 .note(an.note.clone())
56 .source(Source {
57 analyzer: "mbr-forensic".to_string(),
58 scope: "MBR".to_string(),
59 version: None,
60 })
61 .evidence_at(
62 "offset",
63 format!("{:#x}", an.offset),
64 Location::ByteOffset(an.offset),
65 )
66 .build()
67 })
68 .collect()
69}
70
71#[must_use]
73pub fn gpt_findings(a: &gpt_forensic::GptAnalysis) -> Vec<Finding> {
74 a.anomalies
75 .iter()
76 .map(|an| {
77 Finding::observation(an.severity, classify(an.code), an.code.to_string())
78 .note(an.note.clone())
79 .source(Source {
80 analyzer: "gpt-forensic".to_string(),
81 scope: "GPT".to_string(),
82 version: None,
83 })
84 .build()
85 })
86 .collect()
87}
88
89#[must_use]
91pub fn apm_findings(a: &apm_forensic::ApmAnalysis) -> Vec<Finding> {
92 a.anomalies
93 .iter()
94 .map(|an| {
95 Finding::observation(an.severity, classify(an.code), an.code.to_string())
96 .note(an.note.clone())
97 .source(Source {
98 analyzer: "apm-forensic".to_string(),
99 scope: "APM".to_string(),
100 version: None,
101 })
102 .build()
103 })
104 .collect()
105}
106
107#[must_use]
109pub fn mbr_provenance(a: &mbr_forensic::MbrAnalysis) -> Vec<Provenance> {
110 vec![
111 Provenance {
112 label: "boot code".to_string(),
113 value: format!("{:?}", a.boot_code_id),
114 source: "mbr-forensic".to_string(),
115 },
116 Provenance {
117 label: "partitioning era".to_string(),
118 value: format!("{:?}", a.era),
119 source: "mbr-forensic".to_string(),
120 },
121 Provenance {
122 label: "disk signature".to_string(),
123 value: format!("{:#010x}", a.disk_serial),
124 source: "mbr-forensic".to_string(),
125 },
126 ]
127}
128
129#[must_use]
131pub fn gpt_provenance(a: &gpt_forensic::GptAnalysis) -> Vec<Provenance> {
132 vec![
133 Provenance {
134 label: "disk GUID".to_string(),
135 value: a.disk_guid.to_string(),
136 source: "gpt-forensic".to_string(),
137 },
138 Provenance {
139 label: "sector size".to_string(),
140 value: format!("{} bytes", a.sector_size),
141 source: "gpt-forensic".to_string(),
142 },
143 Provenance {
144 label: "GPT SHA-256".to_string(),
145 value: a.gpt_sha256.clone(),
146 source: "gpt-forensic".to_string(),
147 },
148 ]
149}
150
151#[must_use]
153pub fn apm_provenance(a: &apm_forensic::ApmAnalysis) -> Vec<Provenance> {
154 vec![
155 Provenance {
156 label: "block size".to_string(),
157 value: format!("{} bytes", a.block_size),
158 source: "apm-forensic".to_string(),
159 },
160 Provenance {
161 label: "device blocks".to_string(),
162 value: a.device_block_count.to_string(),
163 source: "apm-forensic".to_string(),
164 },
165 ]
166}
167
168#[must_use]
171pub fn report(disk: &DiskReport) -> Report {
172 let (findings, provenance) = match disk {
173 DiskReport::Apm(a) => (apm_findings(a), apm_provenance(a)),
174 DiskReport::Mbr(m) => (mbr_findings(m), mbr_provenance(m)),
175 DiskReport::Gpt(m) => {
176 let mut findings = mbr_findings(m);
177 let mut provenance = mbr_provenance(m);
178 if let Some(gpt) = &m.gpt {
179 findings.extend(gpt_findings(gpt));
180 provenance.extend(gpt_provenance(gpt));
181 }
182 (findings, provenance)
183 }
184 };
185 let mut out = Report::default();
186 out.findings = findings;
187 out.provenance = provenance;
188 out
189}