frnsc_prefetch/
common.rs

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
13/// By default blocks will be loaded into executable memory sections
14pub const FLAG_PROGRAM_BLOCK_EXECUTABLE: u32 = 0x0200;
15
16/// By default blocks will be loaded as resources, not executable
17pub const FLAG_PROGRAM_BLOCK_RESOURCE: u32 = 0x0002;
18
19/// By default blocks should not be prefetched, should be pulled from disk.
20pub const FLAG_PROGRAM_BLOCK_DONT_PREFETCH: u32 = 0x0001;
21
22/// The block is loaded into a executable memory section
23pub const FLAG_BLOCK_EXECUTABLE: u8 = 0x02;
24
25/// The block is loaded as resouce
26pub const FLAG_BLOCK_RESOURCE: u8 = 0x04;
27/// The block is forced to be prefetched
28pub const FLAG_BLOCK_FORCE_PREFETCH: u8 = 0x08;
29/// The block will not be prefetched, should be pulled from disk.
30pub const FLAG_BLOCK_DONT_PREFETCH: u8 = 0x01;
31
32#[derive(Debug, Clone, Default)]
33pub struct PrefetchFile {
34    /// Prefetch file version
35    pub version: u32,
36    /// Executable name
37    pub name: String,
38    /// List of DLLs/EXEs loaded by the executable
39    pub metrics: Vec<Metric>,
40    /// Last execution times (max 8)
41    pub last_run_times: Vec<Filetime>,
42    /// Number of times executed
43    pub run_count: u32,
44    /// Information about the disks and other volumes
45    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/// Files loaded by the executable
63#[derive(Debug, Clone, Default)]
64pub struct Metric {
65    /// Full path to the dependency. Ex: File=\VOLUME{01d962d37536cd21-a2691d2c}\WINDOWS\SYSTEM32\NTDLL.DLL
66    pub file: String,
67    /// Default flags for loading blocks: executable, resource or non-prefetchable.
68    pub flags: PrefetchFlag,
69    /// Number of blocks to be prefetched
70    pub blocks_to_prefetch: u32,
71    /// Traces for this dependency
72    pub traces: Vec<Trace>,
73}
74#[derive(Debug, Clone, Default)]
75pub struct Trace {
76    /// Flags for loading blocks: executable, resource, non-prefetchable or force prefetch.
77    pub flags: BlockFlags,
78    /// Memory block offset
79    pub block_offset: u32,
80    /// Stores whether the block was used in each of the last eight runs (1 bit each)
81    pub used_bitfield: u8,
82    /// Stores whether the block was prefetched in each of the last eight runs (1 bit each)
83    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    /// Gets for which user was the program executed. Its not precise.
290    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}