1pub mod b64file;
8pub mod coding;
9pub mod core;
10pub mod hexfile;
11
12use std::collections::HashSet;
13use std::sync::LazyLock;
14
15use crate::entropy::composite_entropy;
16use crate::{AnyRule, Hit, RuleResult, State};
17
18pub struct Assertion {
22 pub offset: usize,
24 pub expected: bool,
26 pub find: Find,
28}
29
30pub enum Find {
34 Regex(regex::Regex),
36 Fn(fn(&mut State) -> bool),
38}
39
40impl Find {
41 pub fn find(&self, state: &mut State) -> bool {
43 match self {
44 Find::Regex(regex) => has_regex(state, regex),
45 Find::Fn(f) => f(state),
46 }
47 }
48
49 pub fn is_match(&self, input: &str) -> bool {
51 match self {
52 Find::Regex(regex) => regex.is_match(input),
53 Find::Fn(_) => {
54 let mut temp_state = State::new(input);
55 self.find(&mut temp_state)
56 }
57 }
58 }
59}
60
61pub struct RawRule {
66 pub find: Find,
68 pub describe: &'static str,
70 pub importance: u8,
72 pub min_entropy: Option<f64>,
74 pub before_assert: Option<Assertion>,
76 pub after_assert: Option<Assertion>,
78 pub is_and_assert: bool,
80 pub check: Option<fn(&str, &str) -> bool>,
82}
83
84impl RawRule {
85 pub fn check_custom(&self, state: &mut State) {
87 if let Some(check_fn) = self.check {
88 state.retain(|input, (start, end)| check_fn(&input[start..end], input));
89 }
90 }
91
92 fn check_assertions(&self, state: &mut State) {
94 state.retain(|input, (start, end)| {
95 let before_pass = self.before_assert.as_ref().map(|assert| {
97 let mut check_start = start.saturating_sub(assert.offset);
98 while check_start > 0 && !input.is_char_boundary(check_start) {
100 check_start -= 1;
101 }
102 let slice = &input[check_start..start];
103 assert.find.is_match(slice) == assert.expected
104 });
105
106 let after_pass = self.after_assert.as_ref().map(|assert| {
108 let mut check_end = (end + assert.offset).min(input.len());
109 while check_end < input.len() && !input.is_char_boundary(check_end) {
111 check_end += 1;
112 }
113 let slice = &input[end..check_end];
114 assert.find.is_match(slice) == assert.expected
115 });
116
117 match (before_pass, after_pass) {
119 (Some(b), Some(a)) => {
120 if self.is_and_assert {
121 b && a } else {
123 b || a }
125 }
126 (Some(b), None) => b,
127 (None, Some(a)) => a,
128 (None, None) => true,
129 }
130 });
131 }
132}
133
134pub struct RuleEntry {
136 pub name: &'static str,
137 pub module_path: &'static str,
138 pub importance: u8,
139 pub get_rule: fn() -> &'static RawRule,
140}
141
142inventory::collect!(RuleEntry);
143
144#[macro_export]
195macro_rules! lazy_rule {
196
197 ($name:ident = |$state:ident| $find:expr, $describe:literal, $importance:literal $(, $($rest:tt)+)?) => {
203 $crate::rules::lazy_rule!(@parse_args
204 $name,
205 $crate::rules::Find::Fn(|$state: &mut $crate::State| $find),
206 $describe,
207 $importance
208 $(, $($rest)+)?
209 );
210 };
211 ($name:ident = $re:literal, $describe:literal, $importance:literal $(, $($rest:tt)+)?) => {
213 $crate::rules::lazy_rule!(@parse_args
214 $name,
215 $crate::rules::Find::Regex(regex::Regex::new($re).expect(concat!("Invalid regex in ", stringify!($name)))),
216 $describe,
217 $importance
218 $(, $($rest)+)?
219 );
220 };
221
222 (@build_assert None) => { None };
227
228 (@build_assert ($offset:expr, $expected:expr, $re:literal)) => {
229 Some($crate::rules::Assertion {
230 offset: $offset,
231 expected: $expected,
232 find: $crate::rules::Find::Regex(regex::Regex::new($re).expect("Invalid regex in assertion")),
233 })
234 };
235
236 (@build_assert ($offset:expr, $expected:expr, |$state:ident| $func:expr)) => {
237 Some($crate::rules::Assertion {
238 offset: $offset,
239 expected: $expected,
240 find: $crate::rules::Find::Fn(|$state: &mut $crate::State| $func),
241 })
242 };
243
244 (@build_assert_tuple ($before_assert:tt, $after_assert:tt, $is_and:expr)) => {
250 (
251 $crate::rules::lazy_rule!(@build_assert $before_assert),
252 $crate::rules::lazy_rule!(@build_assert $after_assert),
253 $is_and,
254 )
255 };
256 (@build_assert_tuple ($before_assert:tt, $after_assert:tt)) => {
258 $crate::rules::lazy_rule!(@build_assert_tuple ($before_assert, $after_assert, true))
259 };
260 (@build_assert_tuple ($before_assert:tt, )) => {
262 (
263 $crate::rules::lazy_rule!(@build_assert $before_assert),
264 None,
265 true,
266 )
267 };
268 (@build_assert_tuple (, $after_assert:tt)) => {
270 (
271 None,
272 $crate::rules::lazy_rule!(@build_assert $after_assert),
273 true,
274 )
275 };
276
277
278 (@parse_args $name:ident, $filter:expr, $describe:literal, $importance:literal, $min_entropy:expr, $asserts:expr, $check:expr $(,)?) => {
282 pub static $name: std::sync::LazyLock<$crate::rules::RawRule> = std::sync::LazyLock::new(|| {
283 let asserts = $asserts;
284 $crate::rules::RawRule {
285 find: $filter,
286 describe: $describe,
287 importance: $importance,
288 min_entropy: $min_entropy,
289 before_assert: asserts.0,
290 after_assert: asserts.1,
291 is_and_assert: asserts.2,
292 check: $check,
293 }
294 });
295 inventory::submit! {
296 $crate::rules::RuleEntry {
297 name: stringify!($name),
298 module_path: module_path!(),
299 importance: $importance,
300 get_rule: || &$name
301 }
302 }
303 };
304
305 (@parse_args $name:ident, $filter:expr, $describe:literal, $importance:literal, $min_entropy:expr, ($($asserts:tt)+), |$slice:pat_param,$input:pat_param| $check:expr $(,)?) => {
311 $crate::rules::lazy_rule!(@parse_args $name, $filter, $describe, $importance, Some($min_entropy),
312 $crate::rules::lazy_rule!(@build_assert_tuple ($($asserts)+)),
313 Some(|$slice,$input| $check)
314 );
315 };
316
317 (@parse_args $name:ident, $filter:expr, $describe:literal, $importance:literal, ($($asserts:tt)+), |$slice:pat_param,$input:pat_param| $check:expr $(,)?) => {
320 $crate::rules::lazy_rule!(@parse_args $name, $filter, $describe, $importance, None,
321 $crate::rules::lazy_rule!(@build_assert_tuple ($($asserts)+)),
322 Some(|$slice,$input| $check)
323 );
324 };
325 (@parse_args $name:ident, $filter:expr, $describe:literal, $importance:literal, $min_entropy:expr, ($($asserts:tt)+) $(,)?) => {
327 $crate::rules::lazy_rule!(@parse_args $name, $filter, $describe, $importance, Some($min_entropy),
328 $crate::rules::lazy_rule!(@build_assert_tuple ($($asserts)+)),
329 None
330 );
331 };
332 (@parse_args $name:ident, $filter:expr, $describe:literal, $importance:literal, $min_entropy:expr, |$slice:pat_param,$input:pat_param| $check:expr $(,)?) => {
334 $crate::rules::lazy_rule!(@parse_args $name, $filter, $describe, $importance, Some($min_entropy), (None, None, true), Some(|$slice,$input| $check));
335 };
336
337 (@parse_args $name:ident, $filter:expr, $describe:literal, $importance:literal, |$slice:pat_param,$input:pat_param| $check:expr $(,)?) => {
340 $crate::rules::lazy_rule!(@parse_args $name, $filter, $describe, $importance, None, (None, None, true), Some(|$slice,$input| $check));
341 };
342 (@parse_args $name:ident, $filter:expr, $describe:literal, $importance:literal, ($($asserts:tt)+) $(,)?) => {
344 $crate::rules::lazy_rule!(@parse_args $name, $filter, $describe, $importance, None,
345 $crate::rules::lazy_rule!(@build_assert_tuple ($($asserts)+)),
346 None
347 );
348 };
349 (@parse_args $name:ident, $filter:expr, $describe:literal, $importance:literal, $min_entropy:expr $(,)?) => {
351 $crate::rules::lazy_rule!(@parse_args $name, $filter, $describe, $importance, Some($min_entropy), (None, None, true), None);
352 };
353
354 (@parse_args $name:ident, $filter:expr, $describe:literal, $importance:literal $(,)?) => {
356 $crate::rules::lazy_rule!(@parse_args $name, $filter, $describe, $importance, None, (None, None, true), None);
357 };
358}
359
360use crate::tool::has_regex;
361pub use lazy_rule;
362
363pub static ALL_RULES: LazyLock<Vec<AnyRule>> = LazyLock::new(|| get_rules(|_, _| true));
366
367pub fn get_rules<F>(filter: F) -> Vec<AnyRule>
379where
380 F: Fn(&str, u8) -> bool,
381{
382 let mut rules = Vec::new();
383 let mut seen_names = HashSet::new();
384
385 for entry in inventory::iter::<RuleEntry> {
386 if filter(entry.module_path, entry.importance) {
388 if !seen_names.insert(entry.name) {
390 continue;
391 }
392
393 let r: &'static RawRule = (entry.get_rule)();
394
395 rules.push(
397 AnyRule::new(|state| Hit {
398 describe: r.describe.into(),
399 importance: r.importance,
400 data: RuleResult::from(state),
401 })
402 .add_flow(move |state| r.find.find(state))
404 .add_flow(move |state| {
406 if let Some(min_ent) = r.min_entropy {
407 state.retain(|input, (start, end)| {
408 composite_entropy(&input.as_bytes()[start..end]) >= min_ent
409 });
410 }
411 !state.ranges.is_empty()
412 })
413 .add_flow(move |state| {
415 r.check_assertions(state);
416 !state.ranges.is_empty()
417 })
418 .add_flow(move |state| {
420 r.check_custom(state);
421 !state.ranges.is_empty()
422 }),
423 );
424 }
425 }
426
427 rules
428}