use std::fs::File;
use std::io::{Read, Write};
#[cfg(unix)]
use std::os::unix::io::AsRawFd;
#[cfg(windows)]
use std::os::windows::io::AsRawHandle as AsRawFd;
use std::path::Path;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::errors::*;
use crate::flags::ScanFlags;
use crate::initialize::InitializationToken;
use crate::internals::{self, CallbackMsg, CallbackReturn};
use crate::string::YrString;
pub struct Rules {
pub(crate) inner: *mut yara_sys::YR_RULES,
pub(crate) _token: InitializationToken,
flags: ScanFlags,
}
unsafe impl std::marker::Send for Rules {}
unsafe impl std::marker::Sync for Rules {}
impl Rules {
pub unsafe fn unsafe_try_from(rules: *mut yara_sys::YR_RULES) -> Result<Self, YaraError> {
let token = InitializationToken::new()?;
Ok(Rules {
inner: rules,
_token: token,
flags: ScanFlags::default(),
})
}
}
impl Rules {
pub fn get_rules(&self) -> Vec<RulesetRule<'_>> {
internals::get_rules(self.inner)
}
pub fn scanner(&self) -> Result<crate::scanner::Scanner<'_>, YaraError> {
crate::scanner::Scanner::new(self)
}
pub fn scan_mem<'r>(&'r self, mem: &[u8], timeout: i32) -> Result<Vec<Rule<'r>>, YaraError> {
let mut results: Vec<Rule<'r>> = Vec::new();
let callback = |message: CallbackMsg<'r>| {
if let CallbackMsg::RuleMatching(rule) = message {
results.push(rule)
}
CallbackReturn::Continue
};
self.scan_mem_callback(mem, timeout, callback)
.map(|_| results)
}
pub fn scan_mem_callback<'r>(
&'r self,
mem: &[u8],
timeout: i32,
callback: impl FnMut(CallbackMsg<'r>) -> CallbackReturn,
) -> Result<(), YaraError> {
internals::rules_scan_mem(self.inner, mem, timeout, self.flags.bits(), callback)
}
pub fn scan_file<'r, P: AsRef<Path>>(
&'r self,
path: P,
timeout: i32,
) -> 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_file_callback(path, timeout, callback)
.map(|_| results)
}
pub fn scan_file_callback<'r, P: AsRef<Path>>(
&'r self,
path: P,
timeout: i32,
callback: impl FnMut(CallbackMsg<'r>) -> CallbackReturn,
) -> Result<(), Error> {
File::open(path)
.map_err(|e| IoError::new(e, IoErrorKind::OpenScanFile).into())
.and_then(|file| {
internals::rules_scan_file(self.inner, &file, timeout, self.flags.bits(), callback)
.map_err(|e| e.into())
})
}
pub fn scan_process(&self, pid: u32, timeout: i32) -> Result<Vec<Rule<'_>>, 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, timeout, callback)
.map(|_| results)
}
pub fn scan_process_callback<'r>(
&'r self,
pid: u32,
timeout: i32,
callback: impl FnMut(CallbackMsg<'r>) -> CallbackReturn,
) -> Result<(), YaraError> {
internals::rules_scan_proc(self.inner, pid, timeout, self.flags.bits(), callback)
}
pub fn scan_fd<'r, F: AsRawFd>(&'r self, fd: &F, timeout: i32) -> 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(fd, timeout, callback)
.map(|_| results)
}
pub fn scan_fd_callback<'r, F: AsRawFd>(
&'r self,
fd: &F,
timeout: i32,
callback: impl FnMut(CallbackMsg<'r>) -> CallbackReturn,
) -> Result<(), Error> {
internals::rules_scan_file(self.inner, fd, timeout, self.flags.bits(), callback)
.map_err(|e| e.into())
}
pub fn save(&mut self, filename: &str) -> Result<(), YaraError> {
internals::rules_save(self.inner, filename)
}
pub fn save_to_stream<W>(&mut self, writer: W) -> Result<(), Error>
where
W: Write,
{
internals::rules_save_stream(self.inner, writer)
}
pub fn load_from_stream<R: Read>(reader: R) -> Result<Self, Error> {
let token = InitializationToken::new()?;
internals::rules_load_stream(reader).map(|inner| Rules {
inner,
_token: token,
flags: ScanFlags::default(),
})
}
pub fn load_from_file(filename: &str) -> Result<Self, YaraError> {
let token = InitializationToken::new()?;
internals::rules_load(filename).map(|inner| Rules {
inner,
_token: token,
flags: ScanFlags::default(),
})
}
pub fn set_flags(&mut self, flags: ScanFlags) {
self.flags = flags
}
}
impl Drop for Rules {
fn drop(&mut self) {
internals::rules_destroy(self.inner);
}
}
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct RulesetRule<'r> {
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) inner: *mut yara_sys::YR_RULE,
pub identifier: &'r str,
pub namespace: &'r str,
pub metadatas: Vec<Metadata<'r>>,
pub tags: Vec<&'r str>,
}
impl RulesetRule<'_> {
pub fn enable(&mut self) {
unsafe {
(*self.inner).enable();
}
}
pub fn disable(&mut self) {
unsafe {
(*self.inner).disable();
}
}
}
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Rule<'r> {
pub identifier: &'r str,
pub namespace: &'r str,
pub metadatas: Vec<Metadata<'r>>,
pub tags: Vec<&'r str>,
pub strings: Vec<YrString<'r>>,
}
#[derive(Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Metadata<'r> {
pub identifier: &'r str,
pub value: MetadataValue<'r>,
}
#[derive(Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MetadataValue<'r> {
Integer(i64),
String(&'r str),
Boolean(bool),
}
#[cfg(test)]
mod test {
use std::process::{Command, Stdio};
use crate::Compiler;
static UUID_MATCH: &str = "401d67bf-ff9c-4632-992e-46afed0bbcff";
static UUID_NO_MATCH: &str = "db4f9dab-a622-4fc9-b71f-38398baf308b";
static RULES_PROC: &str = r#"rule found_uuid {
strings:
$target = "401d67bf-ff9c-4632-992e-46afed0bbcff"
condition:
$target
}
"#;
#[cfg_attr(not(windows), test)]
fn rules_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 6 > 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 6 > 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());
let string = &found_uuid.strings[0];
assert_eq!("$target", string.identifier);
let m = &string.matches[0];
assert_eq!(UUID_MATCH.as_bytes(), m.data.as_slice());
}
}