1pub const MIN_STRING_LEN: usize = 6;
8
9pub fn compute_entropy(data: &[u8]) -> f32 {
13 if data.is_empty() {
14 return 0.0;
15 }
16 let mut freq = [0u32; 256];
17 for &b in data {
18 freq[b as usize] += 1;
19 }
20 let len = data.len() as f32;
21 let mut entropy = 0.0_f32;
22 for &count in &freq {
23 if count > 0 {
24 let p = count as f32 / len;
25 entropy -= p * p.log2();
26 }
27 }
28 entropy
29}
30
31pub fn extract_ascii(bytes: &[u8], min_len: usize) -> Vec<String> {
36 let mut results = Vec::new();
37 let mut current = String::new();
38 for &b in bytes {
39 if b >= 0x20 && b <= 0x7E {
40 current.push(b as char);
41 } else {
42 if current.len() >= min_len {
43 results.push(current.clone());
44 }
45 current.clear();
46 }
47 }
48 if current.len() >= min_len {
49 results.push(current);
50 }
51 results
52}
53
54pub fn extract_utf16le(bytes: &[u8], min_len: usize) -> Vec<String> {
60 let mut results = Vec::new();
61 let mut current = String::new();
62 let mut i = 0;
63 while i + 1 < bytes.len() {
64 let lo = bytes[i];
65 let hi = bytes[i + 1];
66 if hi == 0x00 && lo >= 0x20 && lo <= 0x7E {
67 current.push(lo as char);
68 i += 2;
69 } else {
70 if current.len() >= min_len {
71 results.push(current.clone());
72 }
73 current.clear();
74 i += 1;
75 }
76 }
77 if current.len() >= min_len {
78 results.push(current);
79 }
80 results
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
90 fn ascii_extracts_simple_string() {
91 let input = b"Hello, World!";
92 let strings = extract_ascii(input, 6);
93 assert_eq!(strings, vec!["Hello, World!"]);
94 }
95
96 #[test]
97 fn ascii_skips_short_runs() {
98 let input = b"AB\x00CDEFGH";
99 let strings = extract_ascii(input, 6);
100 assert!(
101 strings.iter().all(|s| s.len() >= 6),
102 "all returned strings must be >= min_len chars"
103 );
104 assert!(
105 !strings.iter().any(|s| s == "AB"),
106 "two-char run must be filtered"
107 );
108 }
109
110 #[test]
111 fn ascii_empty_input_returns_empty() {
112 assert!(extract_ascii(&[], 6).is_empty());
113 }
114
115 #[test]
116 fn ascii_extracts_multiple_strings() {
117 let mut buf = Vec::new();
118 buf.extend_from_slice(b"VirtualAlloc");
119 buf.push(0x00);
120 buf.extend_from_slice(b"CreateRemoteThread");
121 let strings = extract_ascii(&buf, 6);
122 assert!(strings.contains(&"VirtualAlloc".to_string()));
123 assert!(strings.contains(&"CreateRemoteThread".to_string()));
124 }
125
126 #[test]
127 fn ascii_handles_all_non_printable() {
128 let input = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06];
129 assert!(extract_ascii(&input, 6).is_empty());
130 }
131
132 #[test]
133 fn ascii_returns_exact_min_len_string() {
134 let input = b"ABCDEF"; let strings = extract_ascii(input, 6);
136 assert!(strings.contains(&"ABCDEF".to_string()));
137 }
138
139 #[test]
142 fn utf16le_extracts_simple_string() {
143 let input: Vec<u8> = "Hello!"
145 .encode_utf16()
146 .flat_map(|c| c.to_le_bytes())
147 .collect();
148 let strings = extract_utf16le(&input, 6);
149 assert!(
150 strings.contains(&"Hello!".to_string()),
151 "UTF-16LE 'Hello!' must be extracted"
152 );
153 }
154
155 #[test]
156 fn utf16le_empty_input_returns_empty() {
157 assert!(extract_utf16le(&[], 6).is_empty());
158 }
159
160 #[test]
161 fn utf16le_skips_short_runs() {
162 let input: Vec<u8> = "AB".encode_utf16().flat_map(|c| c.to_le_bytes()).collect();
164 let strings = extract_utf16le(&input, 6);
165 assert!(
166 strings.iter().all(|s| s.len() >= 6),
167 "two-char UTF-16LE run must be filtered"
168 );
169 }
170
171 #[test]
172 fn utf16le_mixed_with_binary_extracts_only_strings() {
173 let mut buf: Vec<u8> = vec![0xDE, 0xAD, 0xBE, 0xEF];
174 buf.extend("VirtualAlloc".encode_utf16().flat_map(|c| c.to_le_bytes()));
175 buf.extend_from_slice(&[0xFF, 0xFE]);
176 let strings = extract_utf16le(&buf, 6);
177 assert!(strings.contains(&"VirtualAlloc".to_string()));
178 }
179}