boreal/module/
console.rs

1use std::fmt::Write;
2use std::panic::{RefUnwindSafe, UnwindSafe};
3use std::{collections::HashMap, sync::Arc};
4
5use super::{EvalContext, Module, ModuleData, ModuleDataMap, StaticValue, Type, Value};
6
7/// `console` module.
8///
9/// To use the module, you must provide a callback which will be called when
10/// `console.log` is called.
11///
12/// The callback can also be overridden on every scan by specifying a `ConsoleData`
13/// in the scanner.
14///
15/// ```
16/// use boreal::module::{Console, ConsoleData};
17/// use boreal::compiler::CompilerBuilder;
18///
19/// let mut compiler = CompilerBuilder::new()
20///     // Do not log anything by default
21///     .add_module(Console::with_callback(|_log| {}))
22///     .build();
23/// compiler.add_rules_str(r#"
24/// import "console"
25///
26/// rule a {
27///     condition: console.log("one")
28/// }"#).unwrap();
29/// let mut scanner = compiler.finalize();
30///
31/// scanner.scan_mem(b""); // Will not log anything
32///
33/// let console_data = ConsoleData::new(|log| {
34///     println!("yara console log: {log}");
35/// });
36/// scanner.set_module_data::<Console>(console_data);
37/// scanner.scan_mem(b""); // Will log "yara console log: one"
38/// ```
39pub struct Console {
40    callback: Arc<LogCallback>,
41}
42
43/// Type of callback called when a message is logged.
44pub type LogCallback = dyn Fn(String) + Send + Sync + UnwindSafe + RefUnwindSafe;
45
46impl Module for Console {
47    fn get_name(&self) -> &'static str {
48        "console"
49    }
50
51    fn get_static_values(&self) -> HashMap<&'static str, StaticValue> {
52        [
53            (
54                "log",
55                StaticValue::function(
56                    Self::log,
57                    vec![
58                        vec![Type::Bytes],
59                        vec![Type::Bytes, Type::Bytes],
60                        vec![Type::Integer],
61                        vec![Type::Bytes, Type::Integer],
62                        vec![Type::Float],
63                        vec![Type::Bytes, Type::Float],
64                    ],
65                    Type::Integer,
66                ),
67            ),
68            (
69                "hex",
70                StaticValue::function(
71                    Self::hex,
72                    vec![vec![Type::Integer], vec![Type::Bytes, Type::Integer]],
73                    Type::Integer,
74                ),
75            ),
76        ]
77        .into()
78    }
79
80    fn setup_new_scan(&self, data_map: &mut ModuleDataMap) {
81        data_map.insert::<Self>(PrivateData {
82            callback: Arc::clone(&self.callback),
83        });
84    }
85}
86
87pub struct PrivateData {
88    callback: Arc<LogCallback>,
89}
90
91impl ModuleData for Console {
92    type PrivateData = PrivateData;
93    type UserData = ConsoleData;
94}
95
96/// Data used by the console module.
97///
98/// This data can be provided by a call to
99/// [`Scanner::set_module_data`](crate::scanner::Scanner::set_module_data) to override
100/// the default callback that was specified when compiling rules.
101pub struct ConsoleData {
102    callback: Box<LogCallback>,
103}
104
105impl ConsoleData {
106    /// Provide a callback called when console.log is evaluted in rules.
107    pub fn new<T>(callback: T) -> Self
108    where
109        T: Fn(String) + Send + Sync + UnwindSafe + RefUnwindSafe + 'static,
110    {
111        Self {
112            callback: Box::new(callback),
113        }
114    }
115}
116
117impl Console {
118    /// Create a new console module with a callback.
119    ///
120    /// The callback will be called when expressions using this module
121    /// are used.
122    #[must_use]
123    pub fn with_callback<T>(callback: T) -> Self
124    where
125        T: Fn(String) + Send + Sync + UnwindSafe + RefUnwindSafe + 'static,
126    {
127        Self {
128            callback: Arc::new(callback),
129        }
130    }
131
132    fn log(ctx: &mut EvalContext, args: Vec<Value>) -> Option<Value> {
133        let mut args = args.into_iter();
134        let mut res = String::new();
135        add_value(args.next()?, &mut res)?;
136        if let Some(arg) = args.next() {
137            add_value(arg, &mut res)?;
138        }
139
140        call_callback(ctx, res)?;
141
142        Some(Value::Integer(1))
143    }
144
145    fn hex(ctx: &mut EvalContext, args: Vec<Value>) -> Option<Value> {
146        let mut args = args.into_iter();
147        let res = match args.next()? {
148            Value::Integer(v) => format!("0x{v:x}"),
149            value => {
150                let mut res = String::new();
151                add_value(value, &mut res)?;
152                let v: i64 = args.next()?.try_into().ok()?;
153                write!(&mut res, "0x{v:x}").ok()?;
154                res
155            }
156        };
157
158        call_callback(ctx, res)?;
159
160        Some(Value::Integer(1))
161    }
162}
163
164fn call_callback(ctx: &EvalContext, log: String) -> Option<()> {
165    // First, check if there is a callback specified for this scan.
166    if let Some(data) = ctx.module_data.get_user_data::<Console>() {
167        (data.callback)(log);
168    } else {
169        // Otherwise, use the callback specified when building the module.
170        let data = ctx.module_data.get::<Console>()?;
171        (data.callback)(log);
172    }
173    Some(())
174}
175
176fn add_value(value: Value, out: &mut String) -> Option<()> {
177    match value {
178        Value::Integer(v) => write!(out, "{v}").ok(),
179        Value::Float(v) => write!(out, "{v}").ok(),
180        Value::Bytes(v) => {
181            for byte in v {
182                for b in std::ascii::escape_default(byte) {
183                    out.push(char::from(b));
184                }
185            }
186            Some(())
187        }
188        _ => None,
189    }
190}