1use std::{borrow::Cow, path::PathBuf};
2
3use forensic_rs::{
4 activity::{ForensicActivity, ProgramExecution, SessionId},
5 data::ForensicData,
6 dictionary::*,
7 err::{ForensicError, ForensicResult},
8 field::{Field, Text},
9 traits::forensic::{IntoActivity, IntoTimeline, TimeContext, TimelineData},
10 utils::time::Filetime,
11};
12
13pub const FLAG_PROGRAM_BLOCK_EXECUTABLE: u32 = 0x0200;
15
16pub const FLAG_PROGRAM_BLOCK_RESOURCE: u32 = 0x0002;
18
19pub const FLAG_PROGRAM_BLOCK_DONT_PREFETCH: u32 = 0x0001;
21
22pub const FLAG_BLOCK_EXECUTABLE: u8 = 0x02;
24
25pub const FLAG_BLOCK_RESOURCE: u8 = 0x04;
27pub const FLAG_BLOCK_FORCE_PREFETCH: u8 = 0x08;
29pub const FLAG_BLOCK_DONT_PREFETCH: u8 = 0x01;
31
32#[derive(Debug, Clone, Default)]
33pub struct PrefetchFile {
34 pub version: u32,
36 pub name: String,
38 pub metrics: Vec<Metric>,
40 pub last_run_times: Vec<Filetime>,
42 pub run_count: u32,
44 pub volume: Vec<VolumeInformation>,
46}
47#[derive(Clone, Debug, Default)]
48pub struct PrefetchFileInformation {
49 pub metrics_offsets: u32,
50 pub metrics_count: u32,
51 pub trace_chain_offset: u32,
52 pub trace_chain_count: u32,
53 pub filename_string_offset: u32,
54 pub filename_string_size: u32,
55 pub volume_information_offset: u32,
56 pub volume_count: u32,
57 pub volume_information_size: u32,
58 pub last_run_times: Vec<Filetime>,
59 pub run_count: u32,
60}
61
62#[derive(Debug, Clone, Default)]
64pub struct Metric {
65 pub file: String,
67 pub flags: PrefetchFlag,
69 pub blocks_to_prefetch: u32,
71 pub traces: Vec<Trace>,
73}
74#[derive(Debug, Clone, Default)]
75pub struct Trace {
76 pub flags: BlockFlags,
78 pub block_offset: u32,
80 pub used_bitfield: u8,
82 pub prefetched_bitfield: u8,
84}
85#[derive(Clone, Default)]
86pub struct PrefetchFlag(u32);
87
88impl PrefetchFlag {
89 pub fn is_executable(&self) -> bool {
90 self.0 & FLAG_PROGRAM_BLOCK_EXECUTABLE > 0
91 }
92 pub fn is_resource(&self) -> bool {
93 self.0 & FLAG_PROGRAM_BLOCK_RESOURCE > 0
94 }
95 pub fn is_not_prefetched(&self) -> bool {
96 self.0 & FLAG_PROGRAM_BLOCK_DONT_PREFETCH > 0
97 }
98}
99impl From<u32> for PrefetchFlag {
100 fn from(value: u32) -> Self {
101 Self(value)
102 }
103}
104impl core::fmt::Debug for PrefetchFlag {
105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 let mut writed = 0;
107 if self.is_executable() {
108 f.write_str("X")?;
109 writed += 1;
110 }
111 if self.is_resource() {
112 f.write_str("R")?;
113 writed += 1;
114 }
115 if self.is_not_prefetched() {
116 f.write_str("D")?;
117 writed += 1;
118 }
119 if writed == 0 {
120 f.write_str("-")?;
121 }
122 Ok(())
123 }
124}
125
126impl core::fmt::Display for PrefetchFlag {
127 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128 let mut writed = 0;
129 if self.is_executable() {
130 f.write_str("X")?;
131 writed += 1;
132 }
133 if self.is_resource() {
134 f.write_str("R")?;
135 writed += 1;
136 }
137 if self.is_not_prefetched() {
138 f.write_str("D")?;
139 writed += 1;
140 }
141 if writed == 0 {
142 f.write_str("-")?;
143 }
144 Ok(())
145 }
146}
147#[derive(Clone, Default)]
148pub struct BlockFlags(u8);
149
150impl BlockFlags {
151 pub fn is_executable(&self) -> bool {
152 self.0 & FLAG_BLOCK_EXECUTABLE > 0
153 }
154 pub fn is_resource(&self) -> bool {
155 self.0 & FLAG_BLOCK_RESOURCE > 0
156 }
157 pub fn is_not_prefetched(&self) -> bool {
158 self.0 & FLAG_BLOCK_DONT_PREFETCH > 0
159 }
160 pub fn is_force_prefetch(&self) -> bool {
161 self.0 & FLAG_BLOCK_FORCE_PREFETCH > 0
162 }
163}
164
165impl core::fmt::Debug for BlockFlags {
166 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167 let mut writed = 0;
168 if self.is_executable() {
169 f.write_str("X")?;
170 writed += 1;
171 }
172 if self.is_resource() {
173 f.write_str("R")?;
174 writed += 1;
175 }
176 if self.is_force_prefetch() {
177 f.write_str("F")?;
178 writed += 1;
179 }
180 if self.is_not_prefetched() {
181 f.write_str("D")?;
182 writed += 1;
183 }
184 if writed == 0 {
185 f.write_str("-")?;
186 }
187 Ok(())
188 }
189}
190
191impl From<u8> for BlockFlags {
192 fn from(value: u8) -> Self {
193 Self(value)
194 }
195}
196
197impl core::fmt::Display for BlockFlags {
198 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199 let mut writed = 0;
200 if self.is_executable() {
201 f.write_str("X")?;
202 writed += 1;
203 }
204 if self.is_resource() {
205 f.write_str("R")?;
206 writed += 1;
207 }
208 if self.is_force_prefetch() {
209 f.write_str("F")?;
210 writed += 1;
211 }
212 if self.is_not_prefetched() {
213 f.write_str("D")?;
214 writed += 1;
215 }
216 if writed == 0 {
217 f.write_str("-")?;
218 }
219 Ok(())
220 }
221}
222
223#[derive(Debug, Clone, Default)]
224pub struct VolumeInformation {
225 pub device_path: String,
226 pub file_references: Vec<NtfsFile>,
227 pub directory_strings: Vec<String>,
228 pub creation_time: u64,
229 pub serial_number: u32,
230}
231
232#[derive(Debug, Clone, Default)]
233pub struct NtfsFile {
234 pub mft_entry: u64,
235 pub seq_number: u16,
236}
237
238pub fn utf16_at_offset(file_buffer: &[u8], offset: usize, size: usize) -> ForensicResult<String> {
239 let end_pos = offset + size;
240 if end_pos > file_buffer.len() {
241 return Err(ForensicError::bad_format_str(
242 "The utf16 string position is greater than the file buffer",
243 ));
244 }
245 let txt = &file_buffer[offset..end_pos];
246 let txt_u16: &[u16] = unsafe { std::mem::transmute(txt) };
247 let end = txt_u16
248 .iter()
249 .position(|&v| v == 0)
250 .unwrap_or(txt_u16.len());
251 let txt = String::from_utf16_lossy(&txt_u16[0..end]);
252 Ok(txt)
253}
254
255pub fn u16_at_pos(buffer: &[u8], pos: usize) -> u16 {
256 u16::from_le_bytes(buffer[pos..pos + 2].try_into().unwrap_or_default())
257}
258pub fn u32_at_pos(buffer: &[u8], pos: usize) -> u32 {
259 u32::from_le_bytes(buffer[pos..pos + 4].try_into().unwrap_or_default())
260}
261pub fn u64_at_pos(buffer: &[u8], pos: usize) -> u64 {
262 u64::from_le_bytes(buffer[pos..pos + 8].try_into().unwrap_or_default())
263}
264
265impl Metric {
266 pub fn has_executable_block(&self) -> bool {
267 for trace in self.traces.iter() {
268 if trace.flags.is_executable() {
269 return true;
270 }
271 }
272 false
273 }
274}
275
276impl PrefetchFile {
277 pub fn new() -> Self {
278 PrefetchFile::default()
279 }
280
281 pub fn executable_path(&self) -> &str {
282 for loaded in &self.metrics {
283 if loaded.file.ends_with(&self.name) {
284 return &loaded.file;
285 }
286 }
287 &self.name
288 }
289 pub fn user(&self) -> Option<&str> {
291 for volume in &self.volume {
292 for file in &volume.directory_strings {
293 if !file.starts_with(r"\") {
294 continue;
295 }
296 let filename = &file[1..];
297 let mut splited = filename.split(r"\");
298 if splited.next().is_none() {
299 continue;
300 };
301 match splited.next() {
302 Some("USERS") => {}
303 _ => continue,
304 };
305 let user = match splited.next() {
306 Some(v) => v,
307 None => continue,
308 };
309 match splited.next() {
310 Some("APPDATA") => {}
311 _ => continue,
312 };
313 return Some(user);
314 }
315 }
316 None
317 }
318}
319
320pub struct PrefetchTimelineIterator<'a> {
321 prefetch: &'a PrefetchFile,
322 time_pos: usize,
323}
324impl<'a> Iterator for PrefetchTimelineIterator<'a> {
325 type Item = TimelineData;
326 fn next(&mut self) -> Option<Self::Item> {
327 let actual_pos = self.time_pos;
328 if actual_pos >= self.prefetch.last_run_times.len() {
329 return None;
330 }
331 self.time_pos += 1;
332 let mut data = ForensicData::default();
333 data.add_field(
334 FILE_ACCESSED,
335 Field::Date(self.prefetch.last_run_times[actual_pos]),
336 );
337 data.add_field(FILE_PATH, Field::Path(PathBuf::from(&self.prefetch.name)));
338 let dependencies: Vec<Text> = self
339 .prefetch
340 .metrics
341 .iter()
342 .map(|v| Cow::Owned(v.file.clone()))
343 .collect();
344 data.add_field(PE_IMPORTS, Field::Array(dependencies));
345 data.add_field("prefetch.execution_times", self.prefetch.run_count.into());
346 data.add_field("prefetch.version", self.prefetch.version.into());
347 let mut volume_files = Vec::with_capacity(1024);
348 for volumn in self.prefetch.volume.iter() {
349 for files in volumn.directory_strings.iter() {
350 volume_files.push(Cow::Owned(files.clone()))
351 }
352 }
353 data.add_field("prefetch.volume_files", Field::Array(volume_files));
354 Some(TimelineData {
355 time: self.prefetch.last_run_times[actual_pos],
356 data,
357 time_context: TimeContext::Accessed,
358 })
359 }
360 fn size_hint(&self) -> (usize, Option<usize>) {
361 (self.time_pos, Some(self.prefetch.last_run_times.len()))
362 }
363}
364
365pub struct PrefetchActivityIterator<'a> {
366 prefetch: &'a PrefetchFile,
367 time_pos: usize,
368}
369impl<'a> Iterator for PrefetchActivityIterator<'a> {
370 type Item = ForensicActivity;
371 fn next(&mut self) -> Option<Self::Item> {
372 let actual_pos = self.time_pos;
373 if actual_pos >= self.prefetch.last_run_times.len() {
374 return None;
375 }
376 self.time_pos += 1;
377 Some(ForensicActivity {
378 timestamp: self.prefetch.last_run_times[actual_pos],
379 activity: ProgramExecution::new(self.prefetch.executable_path().to_string()).into(),
380 user: self
381 .prefetch
382 .user()
383 .map(|v| v.to_string())
384 .unwrap_or_default(),
385 session_id: SessionId::Unknown,
386 })
387 }
388 fn size_hint(&self) -> (usize, Option<usize>) {
389 (self.time_pos, Some(self.prefetch.last_run_times.len()))
390 }
391}
392
393impl<'a> IntoActivity<'a> for &'a PrefetchFile {
394 fn activity(&'a self) -> Self::IntoIter {
395 PrefetchActivityIterator {
396 prefetch: self,
397 time_pos: 0,
398 }
399 }
400
401 type IntoIter = PrefetchActivityIterator<'a> where Self: 'a;
402}
403
404impl<'a> IntoActivity<'a> for PrefetchFile {
405 fn activity(&'a self) -> Self::IntoIter {
406 PrefetchActivityIterator {
407 prefetch: self,
408 time_pos: 0,
409 }
410 }
411
412 type IntoIter = PrefetchActivityIterator<'a> where Self: 'a;
413}
414
415impl<'a> IntoTimeline<'a> for &'a PrefetchFile {
416 fn timeline(&'a self) -> Self::IntoIter {
417 PrefetchTimelineIterator {
418 prefetch: self,
419 time_pos: 0,
420 }
421 }
422
423 type IntoIter = PrefetchTimelineIterator<'a> where Self: 'a;
424}
425
426impl<'a> IntoTimeline<'a> for PrefetchFile {
427 fn timeline(&'a self) -> Self::IntoIter {
428 PrefetchTimelineIterator {
429 prefetch: self,
430 time_pos: 0,
431 }
432 }
433
434 type IntoIter = PrefetchTimelineIterator<'a> where Self: 'a;
435}