diff --git a/yara-x/Cargo.toml b/yara-x/Cargo.toml index 33fe738e5..951d4b620 100644 --- a/yara-x/Cargo.toml +++ b/yara-x/Cargo.toml @@ -40,6 +40,8 @@ text-module = [ # The Time module allows you to retrieve epoch in seconds that can # be used in conditions of a rule to check againts other epoch time. time-module = [] +# The Strings Module +string-module = [] # The hash module provides functions for computing md5, sha1 and sha-256 hashes hash-module = [ "dep:md5", diff --git a/yara-x/src/modules/modules.rs b/yara-x/src/modules/modules.rs index 0854f5dc1..d1414005e 100644 --- a/yara-x/src/modules/modules.rs +++ b/yara-x/src/modules/modules.rs @@ -1,4 +1,6 @@ // File generated automatically by build.rs. Do not edit. +#[cfg(feature = "string-module")] +pub mod string; #[cfg(feature = "text-module")] pub mod text; #[cfg(feature = "hash-module")] diff --git a/yara-x/src/modules/protos/string.proto b/yara-x/src/modules/protos/string.proto new file mode 100644 index 000000000..a4b5350b2 --- /dev/null +++ b/yara-x/src/modules/protos/string.proto @@ -0,0 +1,13 @@ +syntax = "proto2"; + +import "yara.proto"; + +option (yara.module_options) = { + name : "string" + root_message: "String" + rust_module: "string" +}; + +message String { + // This module contains only exported functions, and doesn't return any data +} \ No newline at end of file diff --git a/yara-x/src/modules/string.rs b/yara-x/src/modules/string.rs new file mode 100644 index 000000000..17b055b25 --- /dev/null +++ b/yara-x/src/modules/string.rs @@ -0,0 +1,69 @@ +use crate::modules::prelude::*; +use crate::modules::protos::string::*; + +#[module_main] +fn main(_ctx: &ScanContext) -> String { + // Nothing to do, but we have to return our protobuf + String::new() +} + +#[module_export] +fn to_int(ctx: &ScanContext, string: RuntimeString) -> Option { + let string = string.to_str(ctx).ok()?; + string.parse::().ok() +} + +#[module_export(name = "to_int")] +fn to_int_base( + ctx: &ScanContext, + string: RuntimeString, + base: i64, +) -> Option { + let base: u32 = base.try_into().ok()?; + if !(2..=36).contains(&base) { + return None; + } + let string = string.to_str(ctx).ok()?; + i64::from_str_radix(string, base).ok() +} + +#[module_export] +fn length(ctx: &ScanContext, string: RuntimeString) -> Option { + Some(string.as_bstr(ctx).len().try_into().unwrap()) +} + +#[cfg(test)] +mod tests { + #[test] + fn end2end() { + let rules = crate::compile( + r#"import "string" + // True + rule rule_1 { condition: string.length("AXsx00ERS") == 9 } + rule rule_2 { condition: string.length("AXsx00ERS") == 9 } + // False + rule rule_3 { condition: string.length("AXsx00ERS") > 9 } + rule rule_4 { condition: string.length("AXsx00ERS") < 9 } + + + // True + rule rule_5 { condition: string.to_int("1234") == 1234 } + rule rule_6 { condition: string.to_int("-10") == -10 } + // False + rule rule_7 { condition: string.to_int("-10") == -8 } + + + // True + rule rule_8 { condition: string.to_int("A", 16) == 10 } + rule rule_9 { condition: string.to_int("011", 8) == 9 } + // False + rule rule_10 { condition: string.to_int("-011", 0) == -9 } + "#, + ) + .unwrap(); + + let mut scanner = crate::scanner::Scanner::new(&rules); + + assert_eq!(scanner.scan(&[]).unwrap().matching_rules().len(), 6); + } +}