winreg_artifacts/
userassist.rs1use std::io::Cursor;
9
10use winreg_core::hive::Hive;
11use winreg_core::key::filetime_to_datetime;
12
13const GUID_EXE: &str = "{CEBFF5CD-ACE2-4F4F-9178-9926F41749EA}";
17
18const GUID_LNK: &str = "{F4E57C4B-2036-45F0-A9AB-443BCFE33D9F}";
20
21const KNOWN_GUIDS: &[&str] = &[GUID_EXE, GUID_LNK];
23
24const UA_DATA_SIZE: usize = 68; #[derive(Debug, Clone, serde::Serialize)]
33pub struct UserAssistEntry {
34 pub program: String,
36 pub run_count: u32,
38 pub focus_count: u32,
40 pub focus_duration_ms: u32,
42 pub last_run: Option<String>,
44 pub guid: String,
46}
47
48pub fn rot13_decode(s: &str) -> String {
52 s.chars()
53 .map(|c| match c {
54 'A'..='Z' => (b'A' + (c as u8 - b'A' + 13) % 26) as char,
55 'a'..='z' => (b'a' + (c as u8 - b'a' + 13) % 26) as char,
56 other => other,
57 })
58 .collect()
59}
60
61pub fn parse(hive: &Hive<Cursor<Vec<u8>>>) -> Vec<UserAssistEntry> {
71 let mut entries = Vec::new();
72
73 for &guid in KNOWN_GUIDS {
74 let count_path = format!(
75 "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\UserAssist\\{guid}\\Count"
76 );
77
78 let count_key = match hive.open_key(&count_path) {
79 Ok(Some(k)) => k,
80 _ => continue,
81 };
82
83 let values = match count_key.values() {
84 Ok(v) => v,
85 Err(_) => continue,
86 };
87
88 for val in values {
89 let raw = match val.raw_data() {
90 Ok(d) => d,
91 Err(_) => continue,
92 };
93
94 if raw.len() < UA_DATA_SIZE {
95 continue;
96 }
97
98 let run_count = winreg_core::bytes::le_u32(&raw[..], 4);
99 let focus_count = winreg_core::bytes::le_u32(&raw[..], 8);
100 let focus_duration_ms = winreg_core::bytes::le_u32(&raw[..], 12);
101 let filetime = winreg_core::bytes::le_u64(&raw[..], 60);
102
103 let last_run = filetime_to_datetime(filetime)
104 .map(|dt| dt.format("%Y-%m-%dT%H:%M:%SZ").to_string());
105
106 let program = rot13_decode(&val.name());
107
108 entries.push(UserAssistEntry {
109 program,
110 run_count,
111 focus_count,
112 focus_duration_ms,
113 last_run,
114 guid: guid.to_string(),
115 });
116 }
117 }
118
119 entries
120}
121
122#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn rot13_roundtrip_hello() {
130 let s = "Hello, World!";
131 assert_eq!(rot13_decode(&rot13_decode(s)), s);
132 }
133
134 #[test]
135 fn rot13_numbers_unchanged() {
136 assert_eq!(rot13_decode("12345"), "12345");
137 }
138
139 #[test]
140 fn rot13_special_chars_unchanged() {
141 assert_eq!(rot13_decode("\\:{}[]()"), "\\:{}[]()");
142 }
143
144 #[test]
145 fn rot13_uppercase() {
146 assert_eq!(rot13_decode("HELLO"), "URYYB");
147 }
148
149 #[test]
150 fn rot13_lowercase() {
151 assert_eq!(rot13_decode("hello"), "uryyb");
152 }
153}