1pub mod b64file;
4pub mod core;
5pub mod hexfile;
6pub mod coding;
7
8use std::collections::HashSet;
9use std::sync::LazyLock;
10
11use crate::entropy::composite_entropy;
12use crate::{AnyRule, Hit, RuleResult, State};
13
14pub struct Assertion {
15 pub offset: usize,
16 pub expected: bool,
17 pub find: Find,
18}
19
20pub enum Find {
21 Regex(regex::Regex),
22 Fn(fn(&mut State) -> bool),
23}
24
25impl Find {
26 pub fn find(&self, state: &mut State) -> bool {
27 match self {
28 Find::Regex(regex) => has_regex(state, regex),
29 Find::Fn(f) => f(state),
30 }
31 }
32 pub fn is_match(&self, input: &str) -> bool {
33 match self {
34 Find::Regex(regex) => regex.is_match(input),
35 Find::Fn(_) => {
36 let mut temp_state = State::new(input);
37 self.find(&mut temp_state)
38 }
39 }
40 }
41}
42
43pub struct RawRule {
44 pub find: Find,
45 pub describe: &'static str,
46 pub importance: u8,
47 pub min_entropy: Option<f64>,
48 pub before_assert: Option<Assertion>, pub after_assert: Option<Assertion>, pub is_and_assert: bool, pub check: Option<fn(&str, &str) -> bool>,
52}
53
54impl RawRule {
55 pub fn check_custom(&self, state: &mut State) {
56 if let Some(check_fn) = self.check {
57 state.retain(|input, (start, end)| check_fn(&input[start..end], input));
58 }
59 }
60
61 fn check_assertions(&self, state: &mut State) {
62 state.retain(|input, (start, end)| {
63 let before_pass = self.before_assert.as_ref().map(|assert| {
65 let mut check_start = start.saturating_sub(assert.offset);
66 while check_start > 0 && !input.is_char_boundary(check_start) {
67 check_start -= 1;
68 }
69 let slice = &input[check_start..start];
70 assert.find.is_match(slice) == assert.expected
71 });
72
73 let after_pass = self.after_assert.as_ref().map(|assert| {
75 let mut check_end = (end + assert.offset).min(input.len());
76 while check_end < input.len() && !input.is_char_boundary(check_end) {
77 check_end += 1;
78 }
79 let slice = &input[end..check_end];
80 assert.find.is_match(slice) == assert.expected
81 });
82
83 match (before_pass, after_pass) {
85 (Some(b), Some(a)) => {
86 if self.is_and_assert {
87 b && a } else {
89 b || a }
91 }
92 (Some(b), None) => b,
93 (None, Some(a)) => a,
94 (None, None) => true,
95 }
96 });
97 }
98}
99pub struct RuleEntry {
100 pub name: &'static str,
101 pub module_path: &'static str,
102 pub importance: u8,
103 pub get_rule: fn() -> &'static RawRule,
104}
105
106inventory::collect!(RuleEntry);
107
108macro_rules! lazy_rule {
109
110 ($name:ident = |$state:ident| $find:expr, $describe:literal, $importance:literal $(, $($rest:tt)+)?) => {
116 $crate::rules::lazy_rule!(@parse_args
117 $name,
118 $crate::rules::Find::Fn(|$state: &mut $crate::State| $find),
119 $describe,
120 $importance
121 $(, $($rest)+)?
122 );
123 };
124 ($name:ident = $re:literal, $describe:literal, $importance:literal $(, $($rest:tt)+)?) => {
126 $crate::rules::lazy_rule!(@parse_args
127 $name,
128 $crate::rules::Find::Regex(regex::Regex::new($re).expect(concat!("Invalid regex in ", stringify!($name)))),
129 $describe,
130 $importance
131 $(, $($rest)+)?
132 );
133 };
134
135 (@build_assert None) => { None };
140
141 (@build_assert ($offset:expr, $expected:expr, $re:literal)) => {
142 Some($crate::rules::Assertion {
143 offset: $offset,
144 expected: $expected,
145 find: $crate::rules::Find::Regex(regex::Regex::new($re).expect("Invalid regex in assertion")),
146 })
147 };
148
149 (@build_assert ($offset:expr, $expected:expr, |$state:ident| $func:expr)) => {
150 Some($crate::rules::Assertion {
151 offset: $offset,
152 expected: $expected,
153 find: $crate::rules::Find::Fn(|$state: &mut $crate::State| $func),
154 })
155 };
156
157 (@build_assert_tuple ($before_assert:tt, $after_assert:tt, $is_and:expr)) => {
163 (
164 $crate::rules::lazy_rule!(@build_assert $before_assert),
165 $crate::rules::lazy_rule!(@build_assert $after_assert),
166 $is_and,
167 )
168 };
169 (@build_assert_tuple ($before_assert:tt, $after_assert:tt)) => {
171 $crate::rules::lazy_rule!(@build_assert_tuple ($before_assert, $after_assert, true))
172 };
173 (@build_assert_tuple ($before_assert:tt, )) => {
175 (
176 $crate::rules::lazy_rule!(@build_assert $before_assert),
177 None,
178 true,
179 )
180 };
181 (@build_assert_tuple (, $after_assert:tt)) => {
183 (
184 None,
185 $crate::rules::lazy_rule!(@build_assert $after_assert),
186 true,
187 )
188 };
189
190
191 (@parse_args $name:ident, $filter:expr, $describe:literal, $importance:literal, $min_entropy:expr, $asserts:expr, $check:expr $(,)?) => {
195 pub static $name: std::sync::LazyLock<$crate::rules::RawRule> = std::sync::LazyLock::new(|| {
196 let asserts = $asserts;
197 $crate::rules::RawRule {
198 find: $filter,
199 describe: $describe,
200 importance: $importance,
201 min_entropy: $min_entropy,
202 before_assert: asserts.0,
203 after_assert: asserts.1,
204 is_and_assert: asserts.2,
205 check: $check,
206 }
207 });
208 inventory::submit! {
209 $crate::rules::RuleEntry {
210 name: stringify!($name),
211 module_path: module_path!(),
212 importance: $importance,
213 get_rule: || &$name
214 }
215 }
216 };
217
218 (@parse_args $name:ident, $filter:expr, $describe:literal, $importance:literal, $min_entropy:expr, ($($asserts:tt)+), |$slice:pat_param,$input:pat_param| $check:expr $(,)?) => {
224 $crate::rules::lazy_rule!(@parse_args $name, $filter, $describe, $importance, Some($min_entropy),
225 $crate::rules::lazy_rule!(@build_assert_tuple ($($asserts)+)),
226 Some(|$slice,$input| $check)
227 );
228 };
229
230 (@parse_args $name:ident, $filter:expr, $describe:literal, $importance:literal, ($($asserts:tt)+), |$slice:pat_param,$input:pat_param| $check:expr $(,)?) => {
233 $crate::rules::lazy_rule!(@parse_args $name, $filter, $describe, $importance, None,
234 $crate::rules::lazy_rule!(@build_assert_tuple ($($asserts)+)),
235 Some(|$slice,$input| $check)
236 );
237 };
238 (@parse_args $name:ident, $filter:expr, $describe:literal, $importance:literal, $min_entropy:expr, ($($asserts:tt)+) $(,)?) => {
240 $crate::rules::lazy_rule!(@parse_args $name, $filter, $describe, $importance, Some($min_entropy),
241 $crate::rules::lazy_rule!(@build_assert_tuple ($($asserts)+)),
242 None
243 );
244 };
245 (@parse_args $name:ident, $filter:expr, $describe:literal, $importance:literal, $min_entropy:expr, |$slice:pat_param,$input:pat_param| $check:expr $(,)?) => {
247 $crate::rules::lazy_rule!(@parse_args $name, $filter, $describe, $importance, Some($min_entropy), (None, None, true), Some(|$slice,$input| $check));
248 };
249
250 (@parse_args $name:ident, $filter:expr, $describe:literal, $importance:literal, |$slice:pat_param,$input:pat_param| $check:expr $(,)?) => {
253 $crate::rules::lazy_rule!(@parse_args $name, $filter, $describe, $importance, None, (None, None, true), Some(|$slice,$input| $check));
254 };
255 (@parse_args $name:ident, $filter:expr, $describe:literal, $importance:literal, ($($asserts:tt)+) $(,)?) => {
257 $crate::rules::lazy_rule!(@parse_args $name, $filter, $describe, $importance, None,
258 $crate::rules::lazy_rule!(@build_assert_tuple ($($asserts)+)),
259 None
260 );
261 };
262 (@parse_args $name:ident, $filter:expr, $describe:literal, $importance:literal, $min_entropy:expr $(,)?) => {
264 $crate::rules::lazy_rule!(@parse_args $name, $filter, $describe, $importance, Some($min_entropy), (None, None, true), None);
265 };
266
267 (@parse_args $name:ident, $filter:expr, $describe:literal, $importance:literal $(,)?) => {
269 $crate::rules::lazy_rule!(@parse_args $name, $filter, $describe, $importance, None, (None, None, true), None);
270 };
271}
272
273use crate::tool::has_regex;
274pub(crate) use lazy_rule;
275
276pub static ALL_RULES: LazyLock<Vec<AnyRule>> = LazyLock::new(|| get_rules(|_, _| true));
277
278pub fn get_rules<F>(filter: F) -> Vec<AnyRule>
279where
280 F: Fn(&str, u8) -> bool,
281{
282 let mut rules = Vec::new();
283 let mut seen_names = HashSet::new();
284
285 for entry in inventory::iter::<RuleEntry> {
286 if filter(entry.module_path, entry.importance) {
287
288 if !seen_names.insert(entry.name) {
289 continue;
290 }
291
292 let r: &'static RawRule = (entry.get_rule)();
293
294 rules.push(
295 AnyRule::new(|state| Hit {
296 describe: r.describe.into(),
297 importance: r.importance,
298 data: RuleResult::from(state),
299 })
300 .add_flow(move |state| r.find.find(state))
301 .add_flow(move |state| {
302 if let Some(min_ent) = r.min_entropy {
303 state.retain(|input, (start, end)| {
304 composite_entropy((&input[start..end]).as_bytes()) >= min_ent
305 });
306 }
307 !state.ranges.is_empty()
308 })
309 .add_flow(move |state| {
310 r.check_assertions(state);
311 !state.ranges.is_empty()
312 })
313 .add_flow(move |state| {
314 r.check_custom(state);
315 !state.ranges.is_empty()
316 }),
317 );
318 }
319 }
320
321 rules
322}