exec_pe_core/
rich_header.rs1#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
24pub struct RichEntry {
25 pub product_id: u16,
27 pub build_id: u16,
29 pub use_count: u32,
31}
32
33#[derive(Debug, Clone, serde::Serialize)]
35pub struct RichHeader {
36 pub entries: Vec<RichEntry>,
38 pub xor_key: u32,
40}
41
42pub fn parse_rich_header(bytes: &[u8]) -> Option<RichHeader> {
47 if bytes.len() < 0x40 {
48 return None;
49 }
50
51 let e_lfanew = read_u32_le(bytes, 0x3C)? as usize;
53 if e_lfanew < 0x44 || e_lfanew > bytes.len() {
54 return None;
55 }
56
57 let stub_area = bytes.get(0x40..e_lfanew)?;
59
60 let rich_rel = find_pattern(stub_area, b"Rich")?;
62 let rich_abs = 0x40 + rich_rel;
63
64 let xor_key = read_u32_le(bytes, rich_abs + 4)?;
66
67 const DANS: u32 = 0x536E_6144;
70 let mut dans_rel: Option<usize> = None;
71 let mut i = 0usize;
72 while i + 4 <= rich_rel {
73 if let Some(raw) = read_u32_le(stub_area, i) {
74 if raw ^ xor_key == DANS {
75 dans_rel = Some(i);
76 break;
77 }
78 }
79 i += 4;
80 }
81 let dans_rel = dans_rel?;
82
83 let entries_start = dans_rel + 16;
85 if entries_start > rich_rel {
86 return Some(RichHeader {
87 entries: vec![],
88 xor_key,
89 });
90 }
91
92 let mut entries = Vec::new();
93 let mut pos = entries_start;
94 while pos + 8 <= rich_rel {
95 let comp_id = read_u32_le(stub_area, pos)? ^ xor_key;
96 let use_count = read_u32_le(stub_area, pos + 4)? ^ xor_key;
97 entries.push(RichEntry {
98 product_id: (comp_id >> 16) as u16,
99 build_id: (comp_id & 0xFFFF) as u16,
100 use_count,
101 });
102 pos += 8;
103 }
104
105 Some(RichHeader { entries, xor_key })
106}
107
108pub(crate) fn read_u32_le(bytes: &[u8], offset: usize) -> Option<u32> {
111 bytes
112 .get(offset..offset + 4)
113 .and_then(|s| <[u8; 4]>::try_from(s).ok())
114 .map(u32::from_le_bytes)
115}
116
117pub(crate) fn find_pattern(haystack: &[u8], needle: &[u8]) -> Option<usize> {
118 haystack.windows(needle.len()).position(|w| w == needle)
119}
120
121#[cfg(test)]
122pub(crate) mod test_helpers {
123 use super::*;
124
125 pub fn make_pe_with_rich(entries: &[(u16, u16, u32)], xor_key: u32) -> Vec<u8> {
131 const DANS: u32 = 0x536E_6144; let mut stub: Vec<u8> = Vec::new();
134 stub.extend_from_slice(&(DANS ^ xor_key).to_le_bytes());
136 for _ in 0..3 {
137 stub.extend_from_slice(&xor_key.to_le_bytes()); }
139 for &(prod, build, count) in entries {
141 let comp_id = ((prod as u32) << 16) | (build as u32);
142 stub.extend_from_slice(&(comp_id ^ xor_key).to_le_bytes());
143 stub.extend_from_slice(&(count ^ xor_key).to_le_bytes());
144 }
145 stub.extend_from_slice(b"Rich");
147 stub.extend_from_slice(&xor_key.to_le_bytes());
148
149 let e_lfanew: u32 = 0x40 + stub.len() as u32;
150 let mut buf = vec![0u8; e_lfanew as usize + 4];
151 buf[0] = b'M';
152 buf[1] = b'Z';
153 buf[0x3C..0x40].copy_from_slice(&e_lfanew.to_le_bytes());
154 buf[0x40..e_lfanew as usize].copy_from_slice(&stub);
155 buf[e_lfanew as usize..].copy_from_slice(b"PE\0\0");
156 buf
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::{test_helpers::make_pe_with_rich, *};
163
164 #[test]
165 fn returns_none_for_empty_bytes() {
166 assert!(parse_rich_header(&[]).is_none());
167 }
168
169 #[test]
170 fn returns_none_when_no_rich_marker() {
171 let mut pe = vec![0u8; 68];
173 pe[0] = b'M';
174 pe[1] = b'Z';
175 let e_lfanew: u32 = 0x40;
176 pe[0x3C..0x40].copy_from_slice(&e_lfanew.to_le_bytes());
177 pe[0x40..0x44].copy_from_slice(b"PE\0\0");
178 assert!(parse_rich_header(&pe).is_none());
179 }
180
181 #[test]
182 fn returns_none_for_truncated_input() {
183 let buf = [b'M', b'Z'];
184 assert!(parse_rich_header(&buf).is_none());
185 }
186
187 #[test]
188 fn parses_single_entry_correctly() {
189 let key = 0xDEAD_BEEF_u32;
190 let buf = make_pe_with_rich(&[(0x0103, 0x6B6B, 5)], key);
191 let rh = parse_rich_header(&buf).expect("Rich header must be found");
192 assert_eq!(rh.xor_key, key);
193 assert_eq!(rh.entries.len(), 1);
194 assert_eq!(rh.entries[0].product_id, 0x0103);
195 assert_eq!(rh.entries[0].build_id, 0x6B6B);
196 assert_eq!(rh.entries[0].use_count, 5);
197 }
198
199 #[test]
200 fn parses_multiple_entries_in_order() {
201 let key = 0x1234_5678_u32;
202 let expected = [
203 (0x0001, 0x6B00, 1),
204 (0x010C, 0x6B1A, 12),
205 (0x0103, 0x6B6B, 42),
206 ];
207 let buf = make_pe_with_rich(&expected, key);
208 let rh = parse_rich_header(&buf).expect("Rich header must be found");
209 assert_eq!(rh.entries.len(), 3);
210 assert_eq!(rh.entries[0].product_id, 0x0001);
211 assert_eq!(rh.entries[1].use_count, 12);
212 assert_eq!(rh.entries[2].product_id, 0x0103);
213 assert_eq!(rh.entries[2].build_id, 0x6B6B);
214 }
215
216 #[test]
217 fn zero_entry_rich_header_parses_successfully() {
218 let key = 0xCAFE_BABE_u32;
219 let buf = make_pe_with_rich(&[], key);
220 let rh = parse_rich_header(&buf).expect("zero-entry Rich header");
221 assert!(rh.entries.is_empty());
222 assert_eq!(rh.xor_key, key);
223 }
224
225 #[test]
226 fn xor_key_is_correct() {
227 let expected_key = 0x0101_0202_u32;
228 let buf = make_pe_with_rich(&[(1, 2, 3)], expected_key);
229 let rh = parse_rich_header(&buf).unwrap();
230 assert_eq!(rh.xor_key, expected_key);
231 }
232}