use std::sync::LazyLock;
use protobuf::MessageDyn;
use protobuf::reflect::MessageDescriptor;
use rustc_hash::FxHashMap;
use thiserror::Error;
pub mod protos {
#[cfg(feature = "generate-proto-code")]
include!(concat!(env!("OUT_DIR"), "/protos/mod.rs"));
#[cfg(not(feature = "generate-proto-code"))]
include!("protos/generated/mod.rs");
}
#[cfg(test)]
mod tests;
pub(crate) mod field_docs;
#[allow(unused_imports)]
pub(crate) mod prelude {
pub(crate) use crate::scanner::ScanContext;
pub(crate) use crate::wasm::runtime::Caller;
pub(crate) use crate::wasm::string::FixedLenString;
pub(crate) use crate::wasm::string::RuntimeString;
pub(crate) use crate::wasm::string::String as _;
pub(crate) use crate::wasm::string::{Lowercase, Uppercase};
pub(crate) use crate::wasm::*;
pub(crate) use bstr::ByteSlice;
#[cfg(not(feature = "inventory"))]
pub(crate) use linkme::distributed_slice;
pub(crate) use yara_x_macros::{module_export, module_main, wasm_export};
}
include!("modules.rs");
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum ModuleError {
#[error("invalid metadata: {err}")]
MetadataError {
err: String,
},
#[error("internal error: {err}")]
InternalError {
err: String,
},
}
type MainFn =
fn(&[u8], Option<&[u8]>) -> Result<Box<dyn MessageDyn>, ModuleError>;
pub(crate) struct Module {
pub main_fn: Option<MainFn>,
pub rust_module_name: Option<&'static str>,
pub root_struct_descriptor: MessageDescriptor,
}
macro_rules! add_module {
($modules:expr, $name:literal, $proto:ident, $root_message:literal, $rust_module_name:expr, $main_fn:expr) => {{
use std::stringify;
let root_struct_descriptor = protos::$proto::file_descriptor()
.message_by_full_name(format!(".{}", $root_message).as_str())
.expect(format!(
"`root_message` option in protobuf `{}` is wrong, message `{}` is not defined",
stringify!($proto),
$root_message
).as_str());
$modules.insert(
$name,
Module {
main_fn: $main_fn,
rust_module_name: $rust_module_name,
root_struct_descriptor,
},
);
}};
}
pub(crate) static BUILTIN_MODULES: LazyLock<FxHashMap<&'static str, Module>> =
LazyLock::new(|| {
let mut modules = FxHashMap::default();
include!("add_modules.rs");
modules
});
pub mod mods {
pub use super::protos::crx;
pub use super::protos::crx::Crx;
pub use super::protos::dex;
pub use super::protos::dex::Dex;
pub use super::protos::dotnet;
pub use super::protos::dotnet::Dotnet;
pub use super::protos::elf;
pub use super::protos::elf::ELF;
pub use super::protos::lnk;
pub use super::protos::lnk::Lnk;
pub use super::protos::macho;
pub use super::protos::macho::Macho;
pub use super::protos::pe;
pub use super::protos::pe::PE;
pub use super::protos::mods::Modules;
pub fn invoke<T: protobuf::MessageFull>(data: &[u8]) -> Option<Box<T>> {
let module_output = invoke_dyn::<T>(data)?;
Some(<dyn protobuf::MessageDyn>::downcast_box(module_output).unwrap())
}
pub fn invoke_with_meta<T: protobuf::MessageFull>(
data: &[u8],
meta: Option<&[u8]>,
) -> Option<Box<T>> {
let module_output = invoke_with_meta_dyn::<T>(data, meta)?;
Some(<dyn protobuf::MessageDyn>::downcast_box(module_output).unwrap())
}
pub fn invoke_dyn<T: protobuf::MessageFull>(
data: &[u8],
) -> Option<Box<dyn protobuf::MessageDyn>> {
invoke_with_meta_dyn::<T>(data, None)
}
pub fn invoke_with_meta_dyn<T: protobuf::MessageFull>(
data: &[u8],
meta: Option<&[u8]>,
) -> Option<Box<dyn protobuf::MessageDyn>> {
let descriptor = T::descriptor();
let proto_name = descriptor.full_name();
let (_, module) =
super::BUILTIN_MODULES.iter().find(|(_, module)| {
module.root_struct_descriptor.full_name() == proto_name
})?;
module.main_fn?(data, meta).ok()
}
pub fn invoke_all(data: &[u8]) -> Box<Modules> {
let mut info = Box::new(Modules::new());
info.pe = protobuf::MessageField(invoke::<PE>(data));
info.elf = protobuf::MessageField(invoke::<ELF>(data));
info.dotnet = protobuf::MessageField(invoke::<Dotnet>(data));
info.macho = protobuf::MessageField(invoke::<Macho>(data));
info.lnk = protobuf::MessageField(invoke::<Lnk>(data));
info.crx = protobuf::MessageField(invoke::<Crx>(data));
info.dex = protobuf::MessageField(invoke::<Dex>(data));
info
}
pub fn module_names() -> impl Iterator<Item = &'static str> {
use itertools::Itertools;
super::BUILTIN_MODULES.keys().sorted_by_key(|k| **k).copied()
}
pub fn module_definition(name: &str) -> Option<reflect::Struct> {
use crate::types;
use std::rc::Rc;
super::BUILTIN_MODULES
.get(name)
.map(|m| reflect::Struct::new(Rc::<types::Struct>::from(m)))
}
#[doc(hidden)]
pub mod reflect {
use std::borrow::Cow;
use std::rc::Rc;
use crate::types;
use crate::types::{Map, TypeValue};
#[derive(Clone, Debug, PartialEq)]
pub struct Struct {
inner: Rc<types::Struct>,
}
impl Struct {
pub(super) fn new(inner: Rc<types::Struct>) -> Self {
Self { inner }
}
pub fn fields(&self) -> impl Iterator<Item = Field<'_>> + '_ {
self.inner
.fields()
.map(|(name, field)| Field::new(name, field))
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Func {
pub signatures: Vec<FuncSignature>,
}
impl From<Rc<types::Func>> for Func {
fn from(func: Rc<types::Func>) -> Self {
let mut signatures =
Vec::with_capacity(func.signatures().len());
for signature in func.signatures() {
signatures.push(FuncSignature {
args: signature
.args
.iter()
.map(|(name, ty)| (name.clone(), Type::from(ty)))
.collect(),
ret: Type::from(&signature.result),
doc: signature.doc.clone(),
});
}
Func { signatures }
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct FuncSignature {
args: Vec<(String, Type)>,
ret: Type,
doc: Option<Cow<'static, str>>,
}
impl FuncSignature {
pub fn args(
&self,
) -> impl ExactSizeIterator<Item = (&str, &Type)> {
self.args.iter().map(|(name, ty)| (name.as_str(), ty))
}
pub fn ret_type(&self) -> &Type {
&self.ret
}
pub fn doc(&self) -> Option<&str> {
self.doc.as_deref()
}
}
#[derive(Clone)]
pub struct Field<'a> {
name: &'a str,
struct_field: &'a types::StructField,
}
impl<'a> Field<'a> {
fn new(
name: &'a str,
struct_field: &'a types::StructField,
) -> Self {
Self { name, struct_field }
}
pub fn name(&self) -> &'a str {
self.name
}
pub fn ty(&self) -> Type {
Type::from(&self.struct_field.type_value)
}
pub fn doc(&self) -> Option<&str> {
self.struct_field.doc.as_deref()
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Type {
Integer,
Float,
Bool,
String,
Regexp,
Struct(Struct),
Array(Box<Type>),
Map(Box<Type>, Box<Type>),
Func(Func),
}
impl From<&TypeValue> for Type {
fn from(type_value: &TypeValue) -> Self {
match type_value {
TypeValue::Bool { .. } => Type::Bool,
TypeValue::Float { .. } => Type::Float,
TypeValue::Integer { .. } => Type::Integer,
TypeValue::String { .. } => Type::String,
TypeValue::Regexp(_) => Type::Regexp,
TypeValue::Struct(s) => {
Type::Struct(Struct::new(s.clone()))
}
TypeValue::Array(a) => {
Type::Array(Box::new(Type::from(&a.deputy())))
}
TypeValue::Map(m) => {
let key_kind = match **m {
Map::IntegerKeys { .. } => Type::Integer,
Map::StringKeys { .. } => Type::String,
};
Type::Map(
Box::new(key_kind),
Box::new(Type::from(&m.deputy())),
)
}
TypeValue::Func(func) => Type::Func(func.clone().into()),
TypeValue::Unknown => unreachable!(),
}
}
}
}
}
#[cfg(feature = "crypto")]
pub(crate) mod utils;