node-yara-rs

git clone git://archive.git.mtrnord.blog/MTRNord/node-yara-rs.git
Log | Files | Refs | README | LICENSE

lib.rs (10071B)


      1 #![deny(clippy::all)]
      2 
      3 use napi::{
      4   anyhow::Context,
      5   bindgen_prelude::{Buffer, Either3, Either4, Reference, SharedReference},
      6   Env, Result,
      7 };
      8 use yara::{Compiler, MetadataValue, Rule as ExtYaraRule, Rules, Scanner};
      9 
     10 #[macro_use]
     11 extern crate napi_derive;
     12 
     13 #[napi(object)]
     14 #[derive(Debug)]
     15 pub struct YaraRule {
     16   pub filename: Option<String>,
     17   pub string: Option<String>,
     18   pub namespace: Option<String>,
     19 }
     20 
     21 #[napi(object)]
     22 #[derive(Debug)]
     23 pub struct YaraVariable {
     24   pub id: String,
     25   pub value: Either4<i64, f64, bool, String>,
     26 }
     27 
     28 /// An interface to use yara with node in a stable manner using Rust
     29 /// @public
     30 #[napi]
     31 pub struct YaraCompiler {
     32   rules: Rules,
     33 }
     34 
     35 /// An interface to use yara with node in a stable manner using Rust
     36 /// @public
     37 #[napi]
     38 pub struct YaraScanner {
     39   scanner: SharedReference<YaraCompiler, Scanner<'static>>,
     40 }
     41 
     42 #[napi(object)]
     43 #[derive(Debug)]
     44 pub struct YaraRuleResult {
     45   /// Name of the rule.
     46   pub identifier: String,
     47   /// Namespace of the rule.
     48   pub namespace: String,
     49   /// Metadatas of the rule.
     50   pub metadatas: Vec<YaraRuleMetadata>,
     51   /// Tags of the rule.
     52   pub tags: Vec<String>,
     53   /// Matcher strings of the rule.
     54   pub strings: Vec<YaraString>,
     55 }
     56 
     57 #[napi(object)]
     58 #[derive(Debug)]
     59 pub struct YaraRuleMetadata {
     60   pub identifier: String,
     61   pub value: Either3<i64, String, bool>,
     62 }
     63 
     64 #[napi(object)]
     65 #[derive(Debug)]
     66 pub struct YaraString {
     67   /// Name of the string, with the '$'.
     68   pub identifier: String,
     69   /// Matches of the string for the scan.
     70   pub matches: Vec<YaraMatch>,
     71 }
     72 
     73 #[napi(object)]
     74 #[derive(Debug, Clone)]
     75 pub struct YaraMatch {
     76   // base offset of the memory block in which the match occurred.
     77   pub base: i64,
     78   /// Offset of the match within the scanning area.
     79   pub offset: i64,
     80   /// Length of the file. Can be useful if the matcher string has not a fixed length.
     81   pub length: i64,
     82   /// Matched data.
     83   pub data: Vec<u8>,
     84   /// If utf-8 then we decode it here
     85   pub string_data: Option<String>,
     86 }
     87 
     88 #[napi]
     89 impl YaraCompiler {
     90   /// Constructs a new Yara instance and compiles the provided rules and variables.
     91   ///
     92   /// @param rules - The rules which shall be compiled.
     93   /// @param variables - The variables you want to pass to the rules.
     94   /// @throws This can throw if there is an unexpected error.
     95   ///
     96   /// @returns A new instance of a YaraScanner which can be used to scan data
     97   #[napi(constructor)]
     98   pub fn new(rules: Vec<YaraRule>, variables: Vec<YaraVariable>) -> napi::Result<Self> {
     99     let mut compiler = Compiler::new().context("Failed to create yara compiler")?;
    100 
    101     // Load variables
    102     for variable in variables {
    103       match variable.value {
    104         Either4::A(integer_value) => {
    105           compiler
    106             .define_variable(&variable.id, integer_value)
    107             .context(format!("Failed to set variable with id: {}", variable.id))?;
    108         }
    109         Either4::B(float_value) => {
    110           compiler
    111             .define_variable(&variable.id, float_value)
    112             .context(format!("Failed to set variable with id: {}", variable.id))?;
    113         }
    114         Either4::C(bool_value) => {
    115           compiler
    116             .define_variable(&variable.id, bool_value)
    117             .context(format!("Failed to set variable with id: {}", variable.id))?;
    118         }
    119         Either4::D(string_value) => {
    120           compiler
    121             .define_variable(&variable.id, string_value.as_str())
    122             .context(format!("Failed to set variable with id: {}", variable.id))?;
    123         }
    124       }
    125     }
    126 
    127     // Load rules
    128     for rule in rules {
    129       if let Some(namespace) = rule.namespace {
    130         if let Some(filepath) = rule.filename {
    131           compiler = compiler
    132             .add_rules_file_with_namespace(filepath.clone(), &namespace)
    133             .context(format!("Failed to load rule at {}", filepath))?;
    134         } else if let Some(string) = rule.string {
    135           compiler = compiler
    136             .add_rules_str_with_namespace(&string, &namespace)
    137             .context("Failed to load string rule")?;
    138         }
    139       } else if let Some(filepath) = rule.filename {
    140         compiler = compiler
    141           .add_rules_file(filepath.clone())
    142           .context(format!("Failed to load rule at {}", filepath))?;
    143       } else if let Some(string) = rule.string {
    144         compiler = compiler
    145           .add_rules_str(&string)
    146           .context("Failed to load string rule")?;
    147       }
    148     }
    149 
    150     let rules = compiler
    151       .compile_rules()
    152       .context("Failed to compile rules")?;
    153 
    154     Ok(YaraCompiler { rules })
    155   }
    156 
    157   /// Creates a new yara scanner for the rules defined earlier.
    158   /// This can be called multiple times
    159   ///
    160   /// @returns A {@link YaraScanner} instance
    161   #[napi]
    162   pub fn new_scanner(&self, reference: Reference<YaraCompiler>, env: Env) -> Result<YaraScanner> {
    163     YaraScanner::new(reference, env)
    164   }
    165 }
    166 
    167 #[napi]
    168 impl YaraScanner {
    169   pub fn new(reference: Reference<YaraCompiler>, env: Env) -> Result<Self> {
    170     let scanner = reference.share_with(env, |compiler| {
    171       Ok(compiler.rules.scanner().context("Failed to get scanner")?)
    172     })?;
    173     Ok(YaraScanner { scanner })
    174   }
    175 
    176   /// Converts the yara-rs types to types which we can return to napi-rs.
    177   /// Ideally we dont need this but sadly for now this is required.
    178   fn convert_yara_results(&self, rules: Vec<ExtYaraRule>) -> Vec<YaraRuleResult> {
    179     let results = rules
    180       .iter()
    181       .map(|rule| YaraRuleResult {
    182         identifier: rule.identifier.to_string(),
    183         namespace: rule.namespace.to_string(),
    184         metadatas: rule
    185           .metadatas
    186           .iter()
    187           .map(|metadata| match metadata.value {
    188             MetadataValue::Integer(int) => YaraRuleMetadata {
    189               identifier: metadata.identifier.to_string(),
    190               value: Either3::A(int),
    191             },
    192             MetadataValue::String(string) => YaraRuleMetadata {
    193               identifier: metadata.identifier.to_string(),
    194               value: Either3::B(string.to_string()),
    195             },
    196             MetadataValue::Boolean(boolean) => YaraRuleMetadata {
    197               identifier: metadata.identifier.to_string(),
    198               value: Either3::C(boolean),
    199             },
    200           })
    201           .collect(),
    202         tags: rule.tags.iter().map(ToString::to_string).collect(),
    203         strings: rule
    204           .strings
    205           .iter()
    206           .map(|string| YaraString {
    207             identifier: string.identifier.to_string(),
    208             matches: string
    209               .matches
    210               .iter()
    211               .map(|matches| {
    212                 let string = String::from_utf8(matches.data.clone());
    213                 let string_data = if let Ok(string_data) = string {
    214                   Some(string_data)
    215                 } else {
    216                   None
    217                 };
    218                 YaraMatch {
    219                   base: matches.base as i64,
    220                   offset: matches.offset as i64,
    221                   length: matches.length as i64,
    222                   data: matches.data.clone(),
    223                   string_data,
    224                 }
    225               })
    226               .collect(),
    227           })
    228           .collect(),
    229       })
    230       .collect();
    231     results
    232   }
    233 
    234   /// Scan a buffer of data with yara
    235   ///
    236   /// @param buffer - The data which shall be scanned by yara.
    237   /// @throws This can throw if there is an unexpected error.
    238   ///
    239   /// @returns The results of yara scan_mem.
    240   #[napi]
    241   pub fn scan_buffer(&mut self, buffer: Buffer) -> Result<Vec<YaraRuleResult>> {
    242     let buf: Vec<u8> = buffer.into();
    243     let results = self
    244       .scanner
    245       .scan_mem(&buf)
    246       .context("Failed to scan buffer")?;
    247 
    248     Ok(self.convert_yara_results(results))
    249   }
    250 
    251   /// Scan a string of data with yara
    252   ///
    253   /// @param input - The data which shall be scanned by yara.
    254   /// @throws This can throw if there is an unexpected error.
    255   ///
    256   /// @returns The results of yara scan_mem.
    257   #[napi]
    258   pub fn scan_string(&mut self, input: String) -> Result<Vec<YaraRuleResult>> {
    259     let buf: &[u8] = input.as_bytes();
    260     let results = self
    261       .scanner
    262       .scan_mem(buf)
    263       .context("Failed to scan string")?;
    264 
    265     Ok(self.convert_yara_results(results))
    266   }
    267 
    268   /// Scan a file with yara
    269   ///
    270   /// @param filepath - The path to the file yara shall scan
    271   /// @throws This can throw if there is an unexpected error.
    272   ///
    273   /// @returns The results of yara scan_mem.
    274   #[napi]
    275   pub fn scan_file(&mut self, filepath: String) -> Result<Vec<YaraRuleResult>> {
    276     let results = self
    277       .scanner
    278       .scan_file(filepath)
    279       .context("Failed to scan file")?;
    280 
    281     Ok(self.convert_yara_results(results))
    282   }
    283 
    284   /// Scan a process with yara
    285   ///
    286   /// @param pid - The process id of the process that shall be scanned.
    287   /// @throws This can throw if there is an unexpected error.
    288   ///
    289   /// @returns The results of yara scan_mem.
    290   #[napi]
    291   pub fn scan_process(&mut self, pid: u32) -> Result<Vec<YaraRuleResult>> {
    292     let results = self
    293       .scanner
    294       .scan_process(pid)
    295       .context("Failed to scan file")?;
    296 
    297     Ok(self.convert_yara_results(results))
    298   }
    299 
    300   #[napi]
    301   pub fn define_variable(
    302     &mut self,
    303     identifier: String,
    304     value: Either4<String, i64, f64, bool>,
    305   ) -> Result<()> {
    306     match value {
    307       Either4::A(string_value) => Ok(
    308         self
    309           .scanner
    310           .define_variable(&identifier, string_value.as_str())
    311           .context(format!("Failed to define string variable: {identifier}"))?,
    312       ),
    313       Either4::B(bool_value) => Ok(
    314         self
    315           .scanner
    316           .define_variable(&identifier, bool_value)
    317           .context(format!("Failed to define bool variable: {identifier}"))?,
    318       ),
    319       Either4::C(float_value) => Ok(
    320         self
    321           .scanner
    322           .define_variable(&identifier, float_value)
    323           .context(format!("Failed to define float variable: {identifier}"))?,
    324       ),
    325       Either4::D(integer_value) => Ok(
    326         self
    327           .scanner
    328           .define_variable(&identifier, integer_value)
    329           .context(format!("Failed to define integer variable: {identifier}"))?,
    330       ),
    331     }
    332   }
    333 }