winreg_artifacts/amcache.rs
1//! Amcache registry artifact extractor.
2//!
3//! Amcache.hve records application execution evidence. This module decodes
4//! entries from `Root\InventoryApplicationFile`, each subkey representing a
5//! file that was executed or installed on the system.
6
7use std::io::Cursor;
8
9use winreg_core::hive::Hive;
10use winreg_core::key::filetime_to_datetime;
11
12// ---------------------------------------------------------------------------
13// Output type
14// ---------------------------------------------------------------------------
15
16/// A single file entry from `Root\InventoryApplicationFile`.
17#[derive(Debug, Clone, serde::Serialize)]
18pub struct AmcacheEntry {
19 /// Full lowercase file path (`LowerCaseLongPath`).
20 pub file_path: String,
21 /// SHA-1 hash: the `FileId` value with the leading `0000` prefix stripped.
22 /// Empty string if the value is absent.
23 pub sha1: String,
24 /// File size in bytes (`Size` as `REG_DWORD`).
25 pub size: u64,
26 /// PE link timestamp string, e.g. `"01/15/2023 10:30:00"` (`LinkDate`).
27 pub link_date: Option<String>,
28 /// Publisher name (`Publisher`).
29 pub publisher: String,
30 /// Product name (`ProductName`).
31 pub product_name: String,
32 /// Product version string (`ProductVersion`).
33 pub product_version: String,
34 /// Binary file version string (`BinFileVersion`).
35 pub bin_file_version: String,
36 /// The subkey name (hash identifier for this entry).
37 pub key_name: String,
38 /// Key `LastWriteTime` as ISO 8601, or `None` if unavailable.
39 pub last_written: Option<String>,
40}
41
42// ---------------------------------------------------------------------------
43// Key paths
44// ---------------------------------------------------------------------------
45
46/// Path to the `InventoryApplicationFile` container key (relative to hive root).
47const INVENTORY_APP_FILE: &str = "Root\\InventoryApplicationFile";
48
49// ---------------------------------------------------------------------------
50// Public parse function
51// ---------------------------------------------------------------------------
52
53/// Extract all `InventoryApplicationFile` entries from an Amcache hive.
54///
55/// Navigates `Root\InventoryApplicationFile`, iterates each subkey, and
56/// extracts the forensically relevant values. Missing values produce empty
57/// strings or zero rather than errors.
58///
59/// Returns an empty `Vec` if the key is not present.
60pub fn parse(hive: &Hive<Cursor<Vec<u8>>>) -> Vec<AmcacheEntry> {
61 let Ok(Some(container)) = hive.open_key(INVENTORY_APP_FILE) else {
62 return Vec::new();
63 };
64
65 let Ok(subkeys) = container.subkeys() else {
66 return Vec::new();
67 };
68
69 let mut entries = Vec::with_capacity(subkeys.len());
70
71 for subkey in subkeys {
72 let key_name = subkey.name();
73
74 let last_written = filetime_to_datetime(subkey.last_written_raw())
75 .map(|dt| dt.format("%Y-%m-%dT%H:%M:%SZ").to_string());
76
77 // Helper: read a REG_SZ value, returning empty string on any error.
78 let read_sz = |name: &str| -> String {
79 subkey
80 .value(name)
81 .ok()
82 .flatten()
83 .and_then(|v| v.as_string().ok())
84 .unwrap_or_default()
85 };
86
87 let file_path = read_sz("LowerCaseLongPath");
88
89 // FileId: strip the leading "0000" prefix if present.
90 let file_id_raw = read_sz("FileId");
91 let sha1 = file_id_raw
92 .strip_prefix("0000")
93 .map_or_else(|| file_id_raw.clone(), ToString::to_string);
94
95 let size = u64::from(
96 subkey
97 .value("Size")
98 .ok()
99 .flatten()
100 .and_then(|v| v.as_u32().ok())
101 .unwrap_or(0),
102 );
103
104 let link_date_raw = read_sz("LinkDate");
105 let link_date = if link_date_raw.is_empty() {
106 None
107 } else {
108 Some(link_date_raw)
109 };
110
111 let publisher = read_sz("Publisher");
112 let product_name = read_sz("ProductName");
113 let product_version = read_sz("ProductVersion");
114 let bin_file_version = read_sz("BinFileVersion");
115
116 entries.push(AmcacheEntry {
117 file_path,
118 sha1,
119 size,
120 link_date,
121 publisher,
122 product_name,
123 product_version,
124 bin_file_version,
125 key_name,
126 last_written,
127 });
128 }
129
130 entries
131}