use std::fs::File;
use std::marker::PhantomData;
#[cfg(unix)]
use std::os::unix::io::AsRawFd;
#[cfg(windows)]
use std::os::windows::io::AsRawHandle as AsRawFd;
use std::path::Path;
use crate::compiler::CompilerVariableValue;
use crate::errors::*;
use crate::flags::ScanFlags;
use crate::internals::{
self, CallbackMsg, CallbackReturn, MemoryBlockIterator, MemoryBlockIteratorSized,
};
use crate::rules::{Rule, Rules};
pub struct Scanner<'rules> {
inner: *mut yara_sys::YR_SCANNER,
rules: PhantomData<&'rules Rules>,
}
unsafe impl std::marker::Send for Scanner<'_> {}
unsafe impl std::marker::Sync for Scanner<'_> {}
impl<'a> Scanner<'a> {
pub(crate) fn new(rules: &'a Rules) -> Result<Scanner<'a>, YaraError> {
Ok(Scanner {
inner: internals::scanner_create(rules.inner)?,
rules: PhantomData,
})
}
}
impl Drop for Scanner<'_> {
fn drop(&mut self) {
internals::scanner_destroy(self.inner);
}
}
impl<'rules> Scanner<'rules> {
pub fn define_variable<V: CompilerVariableValue>(
&mut self,
identifier: &str,
value: V,
) -> Result<(), YaraError> {
value.assign_in_scanner(self.inner, identifier)
}
pub fn scan_mem(&mut self, mem: &[u8]) -> Result<Vec<Rule<'rules>>, YaraError> {
let mut results: Vec<Rule> = Vec::new();
let callback = |message| {
if let internals::CallbackMsg::RuleMatching(rule) = message {
results.push(rule)
}
internals::CallbackReturn::Continue
};
self.scan_mem_callback(mem, callback).map(|_| results)
}
pub fn scan_mem_callback<'r>(
&mut self,
mem: &[u8],
callback: impl FnMut(CallbackMsg<'r>) -> CallbackReturn,
) -> Result<(), YaraError> {
internals::scanner_scan_mem(self.inner, mem, callback)
}
pub fn scan_file<'r, P: AsRef<Path>>(&mut self, path: P) -> Result<Vec<Rule<'r>>, Error> {
let mut results: Vec<Rule> = Vec::new();
let callback = |message| {
if let internals::CallbackMsg::RuleMatching(rule) = message {
results.push(rule)
}
internals::CallbackReturn::Continue
};
self.scan_file_callback(path, callback).map(|_| results)
}
pub fn scan_file_callback<'r, P: AsRef<Path>>(
&mut self,
path: P,
callback: impl FnMut(CallbackMsg<'r>) -> CallbackReturn,
) -> Result<(), Error> {
File::open(path)
.map_err(|e| IoError::new(e, IoErrorKind::OpenScanFile).into())
.and_then(|file| {
internals::scanner_scan_file(self.inner, &file, callback).map_err(|e| e.into())
})
}
pub fn scan_process<'r>(&mut self, pid: u32) -> Result<Vec<Rule<'r>>, YaraError> {
let mut results: Vec<Rule> = Vec::new();
let callback = |message| {
if let internals::CallbackMsg::RuleMatching(rule) = message {
results.push(rule)
}
internals::CallbackReturn::Continue
};
self.scan_process_callback(pid, callback).map(|_| results)
}
pub fn scan_process_callback<'r>(
&mut self,
pid: u32,
callback: impl FnMut(CallbackMsg<'r>) -> CallbackReturn,
) -> Result<(), YaraError> {
internals::scanner_scan_proc(self.inner, pid, callback)
}
pub fn scan_fd<'r, F: AsRawFd>(&self, file: &F) -> Result<Vec<Rule<'r>>, Error> {
let mut results: Vec<Rule> = Vec::new();
let callback = |message: CallbackMsg<'r>| {
if let CallbackMsg::RuleMatching(rule) = message {
results.push(rule)
}
CallbackReturn::Continue
};
self.scan_fd_callback(file, callback).map(|_| results)
}
pub fn scan_fd_callback<'r, F: AsRawFd>(
&self,
file: &F,
callback: impl FnMut(CallbackMsg<'r>) -> CallbackReturn,
) -> Result<(), Error> {
internals::scanner_scan_file(self.inner, file, callback).map_err(|e| e.into())
}
pub fn scan_mem_blocks<'r>(
&self,
iter: impl MemoryBlockIterator,
) -> Result<Vec<Rule<'r>>, Error> {
let mut results: Vec<Rule> = Vec::new();
let callback = |message: CallbackMsg<'r>| {
if let CallbackMsg::RuleMatching(rule) = message {
results.push(rule)
}
CallbackReturn::Continue
};
self.scan_mem_blocks_callback(iter, callback)
.map(|_| results)
}
pub fn scan_mem_blocks_callback<'r>(
&self,
iter: impl MemoryBlockIterator,
callback: impl FnMut(CallbackMsg<'r>) -> CallbackReturn,
) -> Result<(), Error> {
internals::scanner_scan_mem_blocks(self.inner, iter, callback).map_err(|e| e.into())
}
pub fn scan_mem_blocks_sized<'r>(
&self,
iter: impl MemoryBlockIteratorSized,
) -> Result<Vec<Rule<'r>>, Error> {
let mut results: Vec<Rule> = Vec::new();
let callback = |message: CallbackMsg<'r>| {
if let CallbackMsg::RuleMatching(rule) = message {
results.push(rule)
}
CallbackReturn::Continue
};
self.scan_mem_blocks_sized_callback(iter, callback)
.map(|_| results)
}
pub fn scan_mem_blocks_sized_callback<'r>(
&self,
iter: impl MemoryBlockIteratorSized,
callback: impl FnMut(CallbackMsg<'r>) -> CallbackReturn,
) -> Result<(), Error> {
internals::scanner_scan_mem_blocks_sized(self.inner, iter, callback).map_err(|e| e.into())
}
pub fn set_timeout(&mut self, seconds: i32) {
internals::scanner_set_timeout(self.inner, seconds)
}
pub fn set_flags(&mut self, flags: ScanFlags) {
internals::scanner_set_flags(self.inner, flags.bits())
}
}
#[cfg(test)]
mod test {
use std::{
io::Write,
process::{Command, Stdio},
};
use crate::Compiler;
static RULES: &str = r#"rule is_ferris {
strings:
$rust = "rust" nocase
condition:
$rust and habitat == "ocean" and life_expectancy <= 10 and size < 0.3 and is_cute
}
"#;
#[test]
fn external_vars_on_file() {
let mut compiler = Compiler::new().unwrap();
compiler.define_variable("habitat", "land").unwrap();
compiler.define_variable("life_expectancy", 99).unwrap();
compiler.define_variable("size", 1.0_f64).unwrap();
compiler.define_variable("is_cute", false).unwrap();
let rules = compiler
.add_rules_str(RULES)
.unwrap()
.compile_rules()
.unwrap();
let mut scanner1 = rules.scanner().unwrap();
let mut scanner2 = rules.scanner().unwrap();
scanner1.define_variable("habitat", "ocean").unwrap();
scanner1.define_variable("life_expectancy", 5).unwrap();
scanner1.define_variable("size", 0.20_f64).unwrap();
scanner1.define_variable("is_cute", true).unwrap();
scanner1.set_timeout(5);
scanner2.define_variable("habitat", "his house").unwrap();
scanner2.define_variable("life_expectancy", 82).unwrap();
scanner2.define_variable("size", 1.75_f64).unwrap();
scanner2.define_variable("is_cute", false).unwrap();
scanner2.set_timeout(10);
let mut file = tempfile::NamedTempFile::new().expect("temp file creation to succeed");
file.write_all("I love Rust!".as_bytes())
.expect("write to tempfile to succeed");
let results1 = scanner1
.scan_file(
file.path()
.to_str()
.expect("tempfile path to be valid utf-8"),
)
.unwrap();
let results2 = scanner2
.scan_file(
file.path()
.to_str()
.expect("tempfile path to be valid utf-8"),
)
.unwrap();
assert_eq!(1, results1.len());
assert_eq!(0, results2.len());
let is_ferris_rule = &results1[0];
assert_eq!("is_ferris", is_ferris_rule.identifier);
assert_eq!(1, is_ferris_rule.strings.len());
let string = &is_ferris_rule.strings[0];
assert_eq!("$rust", string.identifier);
let m = &string.matches[0];
assert_eq!(7, m.offset);
assert_eq!(4, m.length);
assert_eq!(b"Rust", m.data.as_slice());
}
static UUID_MATCH: &str = "401d67bf-ff9c-4632-992e-46afed0bbcff";
static UUID_NO_MATCH: &str = "db4f9dab-a622-4fc9-b71f-38398baf308b";
#[cfg(not(windows))]
static RULES_PROC: &str = r#"rule found_uuid {
strings:
$target = "401d67bf-ff9c-4632-992e-46afed0bbcff"
condition:
$target
}
"#;
#[cfg(windows)]
static RULES_PROC: &str = r#"rule found_uuid {
strings:
$target = "401d67bf-ff9c-4632-992e-46afed0bbcff" wide
condition:
$target
}
"#;
#[test]
fn scanner_scan_proc() {
let compiler = Compiler::new().unwrap().add_rules_str(RULES_PROC).unwrap();
let rules = compiler.compile_rules().unwrap();
let mut scanner = rules.scanner().unwrap();
scanner.set_timeout(10);
#[cfg(unix)]
let process_match = Command::new("sh")
.arg("-c")
.arg(format!("sleep 5; echo {UUID_MATCH}"))
.stdout(Stdio::null())
.spawn()
.unwrap();
#[cfg(unix)]
let process_no_match = Command::new("sh")
.arg("-c")
.arg(format!("sleep 5; echo {UUID_NO_MATCH}"))
.stdout(Stdio::null())
.spawn()
.unwrap();
#[cfg(windows)]
let process_match = Command::new("cmd")
.arg("/C")
.arg(format!("ping 127.0.0.1 -n 60 > nul & echo {}", UUID_MATCH))
.stdout(Stdio::null())
.spawn()
.unwrap();
#[cfg(windows)]
let process_no_match = Command::new("cmd")
.arg("/C")
.arg(format!(
"ping 127.0.0.1 -n 60 > nul & echo {}",
UUID_NO_MATCH
))
.stdout(Stdio::null())
.spawn()
.unwrap();
let results1 = scanner.scan_process(process_match.id()).unwrap();
let results2 = scanner.scan_process(process_no_match.id()).unwrap();
assert_eq!(1, results1.len());
assert_eq!(0, results2.len());
let found_uuid = &results1[0];
assert_eq!("found_uuid", found_uuid.identifier);
assert_eq!(1, found_uuid.strings.len());
for string in &found_uuid.strings {
assert_eq!("$target", string.identifier);
let bytes = if cfg!(windows) {
string.matches[0]
.data
.clone()
.into_iter()
.filter(|v| *v != 0)
.collect()
} else {
string.matches[0].data.clone()
};
assert_eq!(UUID_MATCH.as_bytes(), bytes);
}
}
}