diff --git a/src/error.rs b/src/error.rs index f2291e6..14df2ca 100644 --- a/src/error.rs +++ b/src/error.rs @@ -39,6 +39,12 @@ pub enum Error { JsonFormatError(&'static str, u32), #[error("invalid rule: {0} - {1}")] InvalidRule(u32, String), + #[error("invalid scope: {0} - {1}")] + InvalidScope(u32, String), + #[error("invalid static scope: {0}")] + InvalidStaticScope(u32), + #[error("invalid dynamic scope: {0}")] + InvalidDynamicScope(u32), #[error("{0}")] InvalidRuleFile(String), #[error("operand error")] diff --git a/src/rules/features.rs b/src/rules/features.rs index 789150c..1acb143 100755 --- a/src/rules/features.rs +++ b/src/rules/features.rs @@ -1,5 +1,12 @@ +use std::{ + collections::HashSet, + hash::{Hash, Hasher}, +}; + use crate::{rules::Value, Error, Result}; +use super::{Scope, Scopes}; + #[derive(Debug, Clone, PartialEq, Hash, Eq)] pub enum FeatureAccess { Read, @@ -35,6 +42,20 @@ pub enum RuleFeatureType { OperandOffset(usize), } +pub trait FeatureT { + fn scopes(&self) -> &HashSet; + + fn is_supported_in_scope(&self, scopes: &Scopes) -> Result { + if self.scopes().contains(&scopes.r#static.scope) { + return Ok(true); + } + if self.scopes().contains(&scopes.dynamic.scope) { + return Ok(true); + } + Ok(true) + } +} + #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub enum Feature { Property(PropertyFeature), @@ -180,31 +201,31 @@ impl Feature { } } - pub fn is_supported_in_scope(&self, scope: &crate::rules::Scope) -> Result { + pub fn is_supported_in_scope(&self, scopes: &crate::rules::Scopes) -> Result { match self { - Feature::Property(a) => a.is_supported_in_scope(scope), - Feature::Api(a) => a.is_supported_in_scope(scope), - Feature::Regex(a) => a.is_supported_in_scope(scope), - Feature::String(a) => a.is_supported_in_scope(scope), - Feature::Substring(a) => a.is_supported_in_scope(scope), - Feature::Bytes(a) => a.is_supported_in_scope(scope), - Feature::Number(a) => a.is_supported_in_scope(scope), - Feature::Offset(a) => a.is_supported_in_scope(scope), - Feature::Mnemonic(a) => a.is_supported_in_scope(scope), - Feature::BasicBlock(a) => a.is_supported_in_scope(scope), - Feature::Characteristic(a) => a.is_supported_in_scope(scope), - Feature::Export(a) => a.is_supported_in_scope(scope), - Feature::Import(a) => a.is_supported_in_scope(scope), - Feature::Section(a) => a.is_supported_in_scope(scope), - Feature::MatchedRule(a) => a.is_supported_in_scope(scope), - Feature::FunctionName(a) => a.is_supported_in_scope(scope), - Feature::Os(a) => a.is_supported_in_scope(scope), - Feature::Format(a) => a.is_supported_in_scope(scope), - Feature::Arch(a) => a.is_supported_in_scope(scope), - Feature::Namespace(a) => a.is_supported_in_scope(scope), - Feature::Class(a) => a.is_supported_in_scope(scope), - Feature::OperandNumber(a) => a.is_supported_in_scope(scope), - Feature::OperandOffset(a) => a.is_supported_in_scope(scope), + Feature::Property(a) => a.is_supported_in_scope(&scopes), + Feature::Api(a) => a.is_supported_in_scope(&scopes), + Feature::Regex(a) => a.is_supported_in_scope(&scopes), + Feature::String(a) => a.is_supported_in_scope(&scopes), + Feature::Substring(a) => a.is_supported_in_scope(&scopes), + Feature::Bytes(a) => a.is_supported_in_scope(&scopes), + Feature::Number(a) => a.is_supported_in_scope(&scopes), + Feature::Offset(a) => a.is_supported_in_scope(&scopes), + Feature::Mnemonic(a) => a.is_supported_in_scope(&scopes), + Feature::BasicBlock(a) => a.is_supported_in_scope(&scopes), + Feature::Characteristic(a) => a.is_supported_in_scope(&scopes), + Feature::Export(a) => a.is_supported_in_scope(&scopes), + Feature::Import(a) => a.is_supported_in_scope(&scopes), + Feature::Section(a) => a.is_supported_in_scope(&scopes), + Feature::MatchedRule(a) => a.is_supported_in_scope(&scopes), + Feature::FunctionName(a) => a.is_supported_in_scope(&scopes), + Feature::Os(a) => a.is_supported_in_scope(&scopes), + Feature::Format(a) => a.is_supported_in_scope(&scopes), + Feature::Arch(a) => a.is_supported_in_scope(&scopes), + Feature::Namespace(a) => a.is_supported_in_scope(&scopes), + Feature::Class(a) => a.is_supported_in_scope(&scopes), + Feature::OperandNumber(a) => a.is_supported_in_scope(&scopes), + Feature::OperandOffset(a) => a.is_supported_in_scope(&scopes), } } @@ -268,28 +289,41 @@ impl Feature { } } -#[derive(Debug, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Eq)] pub struct FunctionNameFeature { value: String, - description: String, + _description: String, + scopes: HashSet, +} + +impl Hash for FunctionNameFeature { + fn hash(&self, state: &mut H) { + "function_name_feature".hash(state); + self.value.hash(state); + } +} + +impl PartialEq for FunctionNameFeature { + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} + +impl FeatureT for FunctionNameFeature { + fn scopes(&self) -> &HashSet { + &self.scopes + } } impl FunctionNameFeature { pub fn new(value: &str, description: &str) -> Result { Ok(FunctionNameFeature { value: value.to_string(), - description: description.to_string(), + _description: description.to_string(), + scopes: maplit::hashset!(Scope::File), }) } - pub fn is_supported_in_scope(&self, scope: &crate::rules::Scope) -> Result { - match scope { - crate::rules::Scope::Function => Ok(false), - crate::rules::Scope::File => Ok(true), - crate::rules::Scope::BasicBlock => Ok(false), - crate::rules::Scope::Instruction => Ok(false), - } - } pub fn evaluate( &self, features: &std::collections::HashMap>, @@ -301,27 +335,40 @@ impl FunctionNameFeature { } } -#[derive(Debug, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Eq)] pub struct SectionFeature { value: String, - description: String, + _description: String, + scopes: HashSet, +} + +impl Hash for SectionFeature { + fn hash(&self, state: &mut H) { + "section_feature".hash(state); + self.value.hash(state); + } +} + +impl PartialEq for SectionFeature { + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} + +impl FeatureT for SectionFeature { + fn scopes(&self) -> &HashSet { + &self.scopes + } } impl SectionFeature { pub fn new(value: &str, description: &str) -> Result { Ok(SectionFeature { value: value.to_string(), - description: description.to_string(), + _description: description.to_string(), + scopes: maplit::hashset!(Scope::File), }) } - pub fn is_supported_in_scope(&self, scope: &crate::rules::Scope) -> Result { - match scope { - crate::rules::Scope::Function => Ok(false), - crate::rules::Scope::File => Ok(true), - crate::rules::Scope::BasicBlock => Ok(false), - crate::rules::Scope::Instruction => Ok(false), - } - } pub fn evaluate( &self, features: &std::collections::HashMap>, @@ -333,27 +380,40 @@ impl SectionFeature { } } -#[derive(Debug, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Eq)] pub struct ImportFeature { value: String, - description: String, + _description: String, + scopes: HashSet, +} + +impl Hash for ImportFeature { + fn hash(&self, state: &mut H) { + "import_feature".hash(state); + self.value.hash(state); + } +} + +impl PartialEq for ImportFeature { + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} + +impl FeatureT for ImportFeature { + fn scopes(&self) -> &HashSet { + &self.scopes + } } impl ImportFeature { pub fn new(value: &str, description: &str) -> Result { Ok(ImportFeature { value: value.to_string(), - description: description.to_string(), + _description: description.to_string(), + scopes: maplit::hashset!(Scope::File), }) } - pub fn is_supported_in_scope(&self, scope: &crate::rules::Scope) -> Result { - match scope { - crate::rules::Scope::Function => Ok(false), - crate::rules::Scope::File => Ok(true), - crate::rules::Scope::BasicBlock => Ok(false), - crate::rules::Scope::Instruction => Ok(false), - } - } pub fn evaluate( &self, features: &std::collections::HashMap>, @@ -365,27 +425,40 @@ impl ImportFeature { } } -#[derive(Debug, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Eq)] pub struct ExportFeature { value: String, - description: String, + _description: String, + scopes: HashSet, +} + +impl Hash for ExportFeature { + fn hash(&self, state: &mut H) { + "export_feature".hash(state); + self.value.hash(state); + } +} + +impl PartialEq for ExportFeature { + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} + +impl FeatureT for ExportFeature { + fn scopes(&self) -> &HashSet { + &self.scopes + } } impl ExportFeature { pub fn new(value: &str, description: &str) -> Result { Ok(ExportFeature { value: value.to_string(), - description: description.to_string(), + _description: description.to_string(), + scopes: maplit::hashset!(Scope::File), }) } - pub fn is_supported_in_scope(&self, scope: &crate::rules::Scope) -> Result { - match scope { - crate::rules::Scope::Function => Ok(false), - crate::rules::Scope::File => Ok(true), - crate::rules::Scope::BasicBlock => Ok(false), - crate::rules::Scope::Instruction => Ok(false), - } - } pub fn evaluate( &self, features: &std::collections::HashMap>, @@ -397,20 +470,34 @@ impl ExportFeature { } } -#[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub struct BasicBlockFeature {} +#[derive(Debug, Clone, Eq)] +pub struct BasicBlockFeature { + scopes: HashSet, +} + +impl Hash for BasicBlockFeature { + fn hash(&self, state: &mut H) { + "basic_block_feature".hash(state); + } +} + +impl PartialEq for BasicBlockFeature { + fn eq(&self, _other: &Self) -> bool { + true + } +} + +impl FeatureT for BasicBlockFeature { + fn scopes(&self) -> &HashSet { + &self.scopes + } +} impl BasicBlockFeature { pub fn new() -> Result { - Ok(BasicBlockFeature {}) - } - pub fn is_supported_in_scope(&self, scope: &crate::rules::Scope) -> Result { - match scope { - crate::rules::Scope::Function => Ok(true), - crate::rules::Scope::File => Ok(false), - crate::rules::Scope::BasicBlock => Ok(false), - crate::rules::Scope::Instruction => Ok(false), - } + Ok(BasicBlockFeature { + scopes: maplit::hashset!(Scope::Function), + }) } pub fn evaluate( &self, @@ -423,10 +510,11 @@ impl BasicBlockFeature { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq)] pub struct MnemonicFeature { value: String, _description: String, + scopes: HashSet, } impl MnemonicFeature { @@ -434,16 +522,9 @@ impl MnemonicFeature { Ok(MnemonicFeature { value: value.to_string(), _description: description.to_string(), + scopes: maplit::hashset!(Scope::Instruction, Scope::BasicBlock), }) } - pub fn is_supported_in_scope(&self, scope: &crate::rules::Scope) -> Result { - match scope { - crate::rules::Scope::Function => Ok(true), - crate::rules::Scope::File => Ok(false), - crate::rules::Scope::BasicBlock => Ok(true), - crate::rules::Scope::Instruction => Ok(true), - } - } pub fn evaluate( &self, features: &std::collections::HashMap>, @@ -468,13 +549,18 @@ impl PartialEq for MnemonicFeature { } } -impl Eq for MnemonicFeature {} +impl FeatureT for MnemonicFeature { + fn scopes(&self) -> &HashSet { + &self.scopes + } +} -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq)] pub struct OffsetFeature { _bits: u32, value: i128, _description: String, + scopes: HashSet, } impl OffsetFeature { @@ -483,16 +569,9 @@ impl OffsetFeature { _bits: bitness, value: *value, _description: description.to_string(), + scopes: maplit::hashset!(Scope::Function, Scope::Instruction, Scope::BasicBlock), }) } - pub fn is_supported_in_scope(&self, scope: &crate::rules::Scope) -> Result { - match scope { - crate::rules::Scope::Function => Ok(true), - crate::rules::Scope::File => Ok(false), - crate::rules::Scope::BasicBlock => Ok(true), - crate::rules::Scope::Instruction => Ok(true), - } - } pub fn evaluate( &self, features: &std::collections::HashMap>, @@ -517,13 +596,18 @@ impl PartialEq for OffsetFeature { } } -impl Eq for OffsetFeature {} +impl FeatureT for OffsetFeature { + fn scopes(&self) -> &HashSet { + &self.scopes + } +} -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq)] pub struct OperandOffsetFeature { index: usize, value: i128, _description: String, + scopes: HashSet, } impl OperandOffsetFeature { @@ -532,16 +616,9 @@ impl OperandOffsetFeature { index: *index, value: *value, _description: description.to_string(), + scopes: maplit::hashset!(Scope::Function, Scope::Instruction, Scope::BasicBlock), }) } - pub fn is_supported_in_scope(&self, scope: &crate::rules::Scope) -> Result { - match scope { - crate::rules::Scope::Function => Ok(true), - crate::rules::Scope::File => Ok(false), - crate::rules::Scope::BasicBlock => Ok(true), - crate::rules::Scope::Instruction => Ok(true), - } - } pub fn evaluate( &self, features: &std::collections::HashMap>, @@ -570,13 +647,18 @@ impl PartialEq for OperandOffsetFeature { } } -impl Eq for OperandOffsetFeature {} +impl FeatureT for OperandOffsetFeature { + fn scopes(&self) -> &HashSet { + &self.scopes + } +} -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq)] pub struct NumberFeature { _bits: u32, value: i128, _description: String, + scopes: HashSet, } impl NumberFeature { @@ -585,16 +667,16 @@ impl NumberFeature { _bits: bitness, value: *value, _description: description.to_string(), + scopes: maplit::hashset!( + Scope::Function, + Scope::Instruction, + Scope::BasicBlock, + Scope::Call, + Scope::Thread, + Scope::Process + ), }) } - pub fn is_supported_in_scope(&self, scope: &crate::rules::Scope) -> Result { - match scope { - crate::rules::Scope::Function => Ok(true), - crate::rules::Scope::File => Ok(false), - crate::rules::Scope::BasicBlock => Ok(true), - crate::rules::Scope::Instruction => Ok(true), - } - } pub fn evaluate( &self, features: &std::collections::HashMap>, @@ -619,13 +701,18 @@ impl PartialEq for NumberFeature { } } -impl Eq for NumberFeature {} +impl FeatureT for NumberFeature { + fn scopes(&self) -> &HashSet { + &self.scopes + } +} -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq)] pub struct OperandNumberFeature { index: usize, value: i128, _description: String, + scopes: HashSet, } impl OperandNumberFeature { @@ -634,16 +721,9 @@ impl OperandNumberFeature { index: *index, value: *value, _description: description.to_string(), + scopes: maplit::hashset!(Scope::Function, Scope::Instruction, Scope::BasicBlock), }) } - pub fn is_supported_in_scope(&self, scope: &crate::rules::Scope) -> Result { - match scope { - crate::rules::Scope::Function => Ok(true), - crate::rules::Scope::File => Ok(false), - crate::rules::Scope::BasicBlock => Ok(true), - crate::rules::Scope::Instruction => Ok(true), - } - } pub fn evaluate( &self, features: &std::collections::HashMap>, @@ -672,29 +752,53 @@ impl PartialEq for OperandNumberFeature { } } -impl Eq for OperandNumberFeature {} +impl FeatureT for OperandNumberFeature { + fn scopes(&self) -> &HashSet { + &self.scopes + } +} -#[derive(Debug, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Eq)] pub struct ApiFeature { value: String, - description: String, + _description: String, + scopes: HashSet, +} + +impl Hash for ApiFeature { + fn hash(&self, state: &mut H) { + "api_feature".hash(state); + self.value.hash(state); + } +} + +impl PartialEq for ApiFeature { + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} + +impl FeatureT for ApiFeature { + fn scopes(&self) -> &HashSet { + &self.scopes + } } impl ApiFeature { pub fn new(value: &str, description: &str) -> Result { Ok(ApiFeature { value: value.to_string(), - description: description.to_string(), + _description: description.to_string(), + scopes: maplit::hashset!( + Scope::Function, + Scope::Instruction, + Scope::BasicBlock, + Scope::Call, + Scope::Thread, + Scope::Process + ), }) } - pub fn is_supported_in_scope(&self, scope: &crate::rules::Scope) -> Result { - match scope { - crate::rules::Scope::Function => Ok(true), - crate::rules::Scope::File => Ok(false), - crate::rules::Scope::BasicBlock => Ok(true), - crate::rules::Scope::Instruction => Ok(true), - } - } pub fn evaluate( &self, features: &std::collections::HashMap>, @@ -706,11 +810,32 @@ impl ApiFeature { } } -#[derive(Debug, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Eq)] pub struct PropertyFeature { value: String, access: Option, - description: String, + _description: String, + scopes: HashSet, +} + +impl Hash for PropertyFeature { + fn hash(&self, state: &mut H) { + "property_feature".hash(state); + self.value.hash(state); + self.access.hash(state); + } +} + +impl PartialEq for PropertyFeature { + fn eq(&self, other: &Self) -> bool { + self.value == other.value && self.access == other.access + } +} + +impl FeatureT for PropertyFeature { + fn scopes(&self) -> &HashSet { + &self.scopes + } } impl PropertyFeature { @@ -718,17 +843,10 @@ impl PropertyFeature { Ok(Self { value: value.to_string(), access, - description: description.to_string(), + _description: description.to_string(), + scopes: maplit::hashset!(Scope::Function, Scope::Instruction, Scope::BasicBlock), }) } - pub fn is_supported_in_scope(&self, scope: &crate::rules::Scope) -> Result { - match scope { - crate::rules::Scope::Function => Ok(true), - crate::rules::Scope::File => Ok(false), - crate::rules::Scope::BasicBlock => Ok(true), - crate::rules::Scope::Instruction => Ok(true), - } - } pub fn evaluate( &self, features: &std::collections::HashMap>, @@ -740,27 +858,48 @@ impl PropertyFeature { } } -#[derive(Debug, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Eq)] pub struct MatchedRuleFeature { pub value: String, - description: String, + _description: String, + scopes: HashSet, +} + +impl Hash for MatchedRuleFeature { + fn hash(&self, state: &mut H) { + "matched_rule_feature".hash(state); + self.value.hash(state); + } +} + +impl PartialEq for MatchedRuleFeature { + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} + +impl FeatureT for MatchedRuleFeature { + fn scopes(&self) -> &HashSet { + &self.scopes + } } impl MatchedRuleFeature { pub fn new(value: &str, description: &str) -> Result { Ok(MatchedRuleFeature { value: value.to_string(), - description: description.to_string(), + _description: description.to_string(), + scopes: maplit::hashset!( + Scope::Function, + Scope::Instruction, + Scope::BasicBlock, + Scope::Call, + Scope::Thread, + Scope::Process, + Scope::File + ), }) } - pub fn is_supported_in_scope(&self, scope: &crate::rules::Scope) -> Result { - match scope { - crate::rules::Scope::Function => Ok(true), - crate::rules::Scope::File => Ok(true), - crate::rules::Scope::BasicBlock => Ok(true), - crate::rules::Scope::Instruction => Ok(true), - } - } pub fn evaluate( &self, features: &std::collections::HashMap>, @@ -772,71 +911,74 @@ impl MatchedRuleFeature { } } -#[derive(Debug, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Eq)] pub struct CharacteristicFeature { value: String, - description: String, + _description: String, + scopes: HashSet, +} + +impl Hash for CharacteristicFeature { + fn hash(&self, state: &mut H) { + "characteristic_feature".hash(state); + self.value.hash(state); + } +} + +impl PartialEq for CharacteristicFeature { + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} + +impl FeatureT for CharacteristicFeature { + fn scopes(&self) -> &HashSet { + &self.scopes + } } impl CharacteristicFeature { pub fn new(value: &str, description: &str) -> Result { Ok(CharacteristicFeature { value: value.to_string(), - description: description.to_string(), + _description: description.to_string(), + scopes: match value { + "calls from" => maplit::hashset!(Scope::Function), + "calls to" => maplit::hashset!(Scope::Function), + "loop" => maplit::hashset!(Scope::Function), + "recursive call" => maplit::hashset!(Scope::Function), + "nzxor" => maplit::hashset!(Scope::Function, Scope::BasicBlock, Scope::Instruction), + "peb access" => { + maplit::hashset!(Scope::Function, Scope::BasicBlock, Scope::Instruction) + } + "fs access" => { + maplit::hashset!(Scope::Function, Scope::BasicBlock, Scope::Instruction) + } + "gs access" => { + maplit::hashset!(Scope::Function, Scope::BasicBlock, Scope::Instruction) + } + "cross section flow" => { + maplit::hashset!(Scope::Function, Scope::BasicBlock, Scope::Instruction) + } + "tight loop" => maplit::hashset!(Scope::Function, Scope::BasicBlock), + "stack string" => maplit::hashset!(Scope::Function, Scope::BasicBlock), + "indirect call" => { + maplit::hashset!(Scope::Function, Scope::BasicBlock, Scope::Instruction) + } + "call $+5" => { + maplit::hashset!(Scope::Function, Scope::BasicBlock, Scope::Instruction) + } + "unmanaged call" => { + maplit::hashset!(Scope::Function, Scope::BasicBlock, Scope::Instruction) + } + "embedded pe" => maplit::hashset!(Scope::File), + "mixed mode" => maplit::hashset!(Scope::File), + "forwarded export" => maplit::hashset!(Scope::File), + _ => maplit::hashset!(), + }, }) } - pub fn is_supported_in_scope(&self, scope: &crate::rules::Scope) -> Result { - match scope { - crate::rules::Scope::Function => match self.value.as_str() { - "calls from" => Ok(true), - "calls to" => Ok(true), - "loop" => Ok(true), - "recursive call" => Ok(true), - "nzxor" => Ok(true), - "peb access" => Ok(true), - "fs access" => Ok(true), - "gs access" => Ok(true), - "cross section flow" => Ok(true), - "tight loop" => Ok(true), - "stack string" => Ok(true), - "indirect call" => Ok(true), - "call $+5" => Ok(true), - "unmanaged call" => Ok(true), - _ => Ok(false), - }, - crate::rules::Scope::File => match self.value.as_str() { - "embedded pe" => Ok(true), - "mixed mode" => Ok(true), - "forwarded export" => Ok(true), - _ => Ok(false), - }, - crate::rules::Scope::BasicBlock => match self.value.as_str() { - "nzxor" => Ok(true), - "peb access" => Ok(true), - "fs access" => Ok(true), - "gs access" => Ok(true), - "cross section flow" => Ok(true), - "tight loop" => Ok(true), - "stack string" => Ok(true), - "indirect call" => Ok(true), - "call $+5" => Ok(true), - "unmanaged call" => Ok(true), - _ => Ok(false), - }, - crate::rules::Scope::Instruction => match self.value.as_str() { - "nzxor" => Ok(true), - "peb access" => Ok(true), - "fs access" => Ok(true), - "gs access" => Ok(true), - "cross section flow" => Ok(true), - "indirect call" => Ok(true), - "call $+5" => Ok(true), - "unmanaged call" => Ok(true), - _ => Ok(false), - }, - } - } pub fn evaluate( &self, features: &std::collections::HashMap>, @@ -851,28 +993,48 @@ impl CharacteristicFeature { } } -#[derive(Debug, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Eq)] pub struct StringFeature { value: String, - description: String, + _description: String, + scopes: HashSet, +} + +impl Hash for StringFeature { + fn hash(&self, state: &mut H) { + "string_feature".hash(state); + self.value.hash(state); + } +} + +impl PartialEq for StringFeature { + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} + +impl FeatureT for StringFeature { + fn scopes(&self) -> &HashSet { + &self.scopes + } } impl StringFeature { pub fn new(value: &str, description: &str) -> Result { Ok(StringFeature { value: value.to_string(), - description: description.to_string(), + _description: description.to_string(), + scopes: maplit::hashset!( + Scope::Function, + Scope::Instruction, + Scope::BasicBlock, + Scope::File, + Scope::Call, + Scope::Thread, + Scope::Process + ), }) } - - pub fn is_supported_in_scope(&self, scope: &crate::rules::Scope) -> Result { - match scope { - crate::rules::Scope::Function => Ok(true), - crate::rules::Scope::File => Ok(true), - crate::rules::Scope::BasicBlock => Ok(true), - crate::rules::Scope::Instruction => Ok(true), - } - } pub fn evaluate( &self, features: &std::collections::HashMap>, @@ -884,27 +1046,48 @@ impl StringFeature { } } -#[derive(Debug, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Eq)] pub struct SubstringFeature { value: String, - description: String, + _description: String, + scopes: HashSet, +} + +impl Hash for SubstringFeature { + fn hash(&self, state: &mut H) { + "substring_feature".hash(state); + self.value.hash(state); + } +} + +impl PartialEq for SubstringFeature { + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} + +impl FeatureT for SubstringFeature { + fn scopes(&self) -> &HashSet { + &self.scopes + } } impl SubstringFeature { pub fn new(value: &str, description: &str) -> Result { Ok(SubstringFeature { value: value.to_string(), - description: description.to_string(), + _description: description.to_string(), + scopes: maplit::hashset!( + Scope::Function, + Scope::Instruction, + Scope::BasicBlock, + Scope::File, + Scope::Call, + Scope::Thread, + Scope::Process + ), }) } - pub fn is_supported_in_scope(&self, scope: &crate::rules::Scope) -> Result { - match scope { - crate::rules::Scope::Function => Ok(true), - crate::rules::Scope::File => Ok(true), - crate::rules::Scope::BasicBlock => Ok(true), - crate::rules::Scope::Instruction => Ok(true), - } - } pub fn evaluate( &self, features: &std::collections::HashMap>, @@ -953,6 +1136,7 @@ pub struct RegexFeature { value: String, _description: String, re: regex::bytes::Regex, + scopes: HashSet, } impl RegexFeature { @@ -976,16 +1160,17 @@ impl RegexFeature { value: value.to_string(), _description: description.to_string(), re: rr, + scopes: maplit::hashset!( + Scope::Function, + Scope::Instruction, + Scope::BasicBlock, + Scope::File, + Scope::Call, + Scope::Thread, + Scope::Process + ), }) } - pub fn is_supported_in_scope(&self, scope: &crate::rules::Scope) -> Result { - match scope { - crate::rules::Scope::Function => Ok(true), - crate::rules::Scope::File => Ok(true), - crate::rules::Scope::BasicBlock => Ok(true), - crate::rules::Scope::Instruction => Ok(true), - } - } pub fn evaluate( &self, features: &std::collections::HashMap>, @@ -1022,6 +1207,12 @@ impl PartialEq for RegexFeature { impl Eq for RegexFeature {} +impl FeatureT for RegexFeature { + fn scopes(&self) -> &HashSet { + &self.scopes + } +} + // #[derive(Debug, Clone, Hash, PartialEq, Eq)] // pub struct StringFactoryFeature{ // value: String, @@ -1056,10 +1247,11 @@ impl Eq for RegexFeature {} // // } // // } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq)] pub struct BytesFeature { value: Vec, _description: String, + scopes: HashSet, } impl BytesFeature { @@ -1067,16 +1259,9 @@ impl BytesFeature { Ok(BytesFeature { value: value.to_owned(), _description: description.to_string(), + scopes: maplit::hashset!(Scope::Function, Scope::Instruction, Scope::BasicBlock,), }) } - pub fn is_supported_in_scope(&self, scope: &crate::rules::Scope) -> Result { - match scope { - crate::rules::Scope::Function => Ok(true), - crate::rules::Scope::File => Ok(false), - crate::rules::Scope::BasicBlock => Ok(true), - crate::rules::Scope::Instruction => Ok(true), - } - } pub fn evaluate( &self, features: &std::collections::HashMap>, @@ -1107,12 +1292,17 @@ impl PartialEq for BytesFeature { } } -impl Eq for BytesFeature {} +impl FeatureT for BytesFeature { + fn scopes(&self) -> &HashSet { + &self.scopes + } +} -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq)] pub struct ArchFeature { value: String, _description: String, + scopes: HashSet, } impl ArchFeature { @@ -1120,16 +1310,18 @@ impl ArchFeature { Ok(ArchFeature { value: value.to_string(), _description: description.to_string(), + scopes: maplit::hashset!( + Scope::Function, + Scope::Instruction, + Scope::BasicBlock, + Scope::File, + Scope::Call, + Scope::Thread, + Scope::Process, + Scope::Global + ), }) } - pub fn is_supported_in_scope(&self, scope: &crate::rules::Scope) -> Result { - match scope { - crate::rules::Scope::Function => Ok(true), - crate::rules::Scope::File => Ok(true), - crate::rules::Scope::BasicBlock => Ok(true), - crate::rules::Scope::Instruction => Ok(true), - } - } pub fn evaluate( &self, features: &std::collections::HashMap>, @@ -1154,12 +1346,17 @@ impl PartialEq for ArchFeature { } } -impl Eq for ArchFeature {} +impl FeatureT for ArchFeature { + fn scopes(&self) -> &HashSet { + &self.scopes + } +} -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq)] pub struct NamespaceFeature { value: String, _description: String, + scopes: HashSet, } impl NamespaceFeature { @@ -1167,16 +1364,14 @@ impl NamespaceFeature { Ok(Self { value: value.to_string(), _description: description.to_string(), + scopes: maplit::hashset!( + Scope::Function, + Scope::Instruction, + Scope::BasicBlock, + Scope::File + ), }) } - pub fn is_supported_in_scope(&self, scope: &crate::rules::Scope) -> Result { - match scope { - crate::rules::Scope::Function => Ok(true), - crate::rules::Scope::File => Ok(true), - crate::rules::Scope::BasicBlock => Ok(true), - crate::rules::Scope::Instruction => Ok(true), - } - } pub fn evaluate( &self, features: &std::collections::HashMap>, @@ -1201,12 +1396,17 @@ impl PartialEq for NamespaceFeature { } } -impl Eq for NamespaceFeature {} +impl FeatureT for NamespaceFeature { + fn scopes(&self) -> &HashSet { + &self.scopes + } +} -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq)] pub struct ClassFeature { value: String, _description: String, + scopes: HashSet, } impl ClassFeature { @@ -1214,16 +1414,14 @@ impl ClassFeature { Ok(Self { value: value.to_string(), _description: description.to_string(), + scopes: maplit::hashset!( + Scope::Function, + Scope::Instruction, + Scope::BasicBlock, + Scope::File + ), }) } - pub fn is_supported_in_scope(&self, scope: &crate::rules::Scope) -> Result { - match scope { - crate::rules::Scope::Function => Ok(true), - crate::rules::Scope::File => Ok(true), - crate::rules::Scope::BasicBlock => Ok(true), - crate::rules::Scope::Instruction => Ok(true), - } - } pub fn evaluate( &self, features: &std::collections::HashMap>, @@ -1248,12 +1446,17 @@ impl PartialEq for ClassFeature { } } -impl Eq for ClassFeature {} +impl FeatureT for ClassFeature { + fn scopes(&self) -> &HashSet { + &self.scopes + } +} -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq)] pub struct OsFeature { value: String, _description: String, + scopes: HashSet, } impl OsFeature { @@ -1261,16 +1464,18 @@ impl OsFeature { Ok(OsFeature { value: value.to_string(), _description: description.to_string(), + scopes: maplit::hashset!( + Scope::Function, + Scope::Instruction, + Scope::BasicBlock, + Scope::File, + Scope::Call, + Scope::Thread, + Scope::Process, + Scope::Global + ), }) } - pub fn is_supported_in_scope(&self, scope: &crate::rules::Scope) -> Result { - match scope { - crate::rules::Scope::Function => Ok(true), - crate::rules::Scope::File => Ok(true), - crate::rules::Scope::BasicBlock => Ok(true), - crate::rules::Scope::Instruction => Ok(true), - } - } pub fn evaluate( &self, features: &std::collections::HashMap>, @@ -1295,12 +1500,17 @@ impl PartialEq for OsFeature { } } -impl Eq for OsFeature {} +impl FeatureT for OsFeature { + fn scopes(&self) -> &HashSet { + &self.scopes + } +} -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq)] pub struct FormatFeature { value: String, _description: String, + scopes: HashSet, } impl FormatFeature { @@ -1308,16 +1518,18 @@ impl FormatFeature { Ok(FormatFeature { value: value.to_string(), _description: description.to_string(), + scopes: maplit::hashset!( + Scope::Function, + Scope::Instruction, + Scope::BasicBlock, + Scope::File, + Scope::Call, + Scope::Thread, + Scope::Process, + Scope::Global + ), }) } - pub fn is_supported_in_scope(&self, scope: &crate::rules::Scope) -> Result { - match scope { - crate::rules::Scope::Function => Ok(true), - crate::rules::Scope::File => Ok(true), - crate::rules::Scope::BasicBlock => Ok(true), - crate::rules::Scope::Instruction => Ok(true), - } - } pub fn evaluate( &self, features: &std::collections::HashMap>, @@ -1342,4 +1554,8 @@ impl PartialEq for FormatFeature { } } -impl Eq for FormatFeature {} +impl FeatureT for FormatFeature { + fn scopes(&self) -> &HashSet { + &self.scopes + } +} diff --git a/src/rules/mod.rs b/src/rules/mod.rs index 04de8d8..d7b4e3f 100644 --- a/src/rules/mod.rs +++ b/src/rules/mod.rs @@ -54,16 +54,102 @@ impl Value { #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub enum Scope { + None, + Global, Function, File, BasicBlock, Instruction, + Process, + Thread, + Call, +} + +impl TryFrom<&Yaml> for Scope { + type Error = Error; + fn try_from(value: &Yaml) -> std::result::Result { + Ok(match value.as_str() { + Some("global") => Scope::Global, + Some("function") => Scope::Function, + Some("file") => Scope::File, + Some("basic block") => Scope::BasicBlock, + Some("instruction") => Scope::Instruction, + Some("process") => Scope::Process, + Some("thread") => Scope::Thread, + Some("call") => Scope::Call, + Some("unsupported") => Scope::None, + Some(_) => { + return Err(Error::InvalidScope( + line!(), + value.as_str().unwrap().to_string(), + )) + } + None => Scope::None, + }) + } +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct StaticScope { + scope: Scope, +} + +impl TryFrom<&Yaml> for StaticScope { + type Error = Error; + fn try_from(value: &Yaml) -> std::result::Result { + let scope = Scope::try_from(value)?; + match scope { + Scope::None + | Scope::File + | Scope::Global + | Scope::Function + | Scope::BasicBlock + | Scope::Instruction => Ok(Self { scope }), + _ => Err(Error::InvalidStaticScope(line!())), + } + } +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct DynamicScope { + scope: Scope, +} + +impl TryFrom<&Yaml> for DynamicScope { + type Error = Error; + fn try_from(value: &Yaml) -> std::result::Result { + let scope = Scope::try_from(value)?; + match scope { + Scope::None + | Scope::File + | Scope::Global + | Scope::Process + | Scope::Thread + | Scope::Call => Ok(Self { scope }), + _ => Err(Error::InvalidDynamicScope(line!())), + } + } +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct Scopes { + r#static: StaticScope, + dynamic: DynamicScope, +} + +impl Scopes { + pub fn try_from_dict(dict: &Yaml) -> Result { + Ok(Scopes { + r#static: StaticScope::try_from(&dict["static"])?, + dynamic: DynamicScope::try_from(&dict["dynamic"])?, + }) + } } #[derive(Debug, Hash, PartialEq, Eq, Clone)] pub struct Rule { pub name: String, - scope: Scope, + scopes: Scopes, statement: StatementElement, pub meta: Hash, definition: String, @@ -284,17 +370,7 @@ impl Rule { .ok_or_else(|| Error::InvalidRule(line!(), definition.to_string()))?; //if scope is not specified, default to function scope. //this is probably the mode that rule authors will start with. - let scope = match &meta["scope"] { - Yaml::String(s) => match s.as_str() { - "function" => Scope::Function, - "file" => Scope::File, - "basic block" => Scope::BasicBlock, - "instruction" => Scope::Instruction, - _ => return Err(Error::InvalidRule(line!(), definition.to_string())), - }, - Yaml::Null => Scope::Function, - _ => return Err(Error::InvalidRule(line!(), definition.to_string())), - }; + let scopes = Scopes::try_from_dict(&meta["scopes"])?; let statements = d["rule"]["features"] .as_vec() .ok_or_else(|| Error::InvalidRule(line!(), definition.to_string()))?; @@ -319,8 +395,8 @@ impl Rule { } Rule::new( name, - &scope, - Rule::build_statements(&statements[0], &scope)?, + &scopes, + Rule::build_statements(&statements[0], &scopes)?, &meta .as_hash() .ok_or_else(|| Error::InvalidRule(line!(), definition.to_string()))? @@ -331,21 +407,21 @@ impl Rule { pub fn new( name: &str, - scope: &Scope, + scopes: &Scopes, statement: StatementElement, meta: &Hash, definition: &str, ) -> Result { Ok(Rule { name: name.to_string(), - scope: scope.clone(), + scopes: scopes.clone(), statement, meta: meta.clone(), definition: definition.to_string(), }) } - pub fn build_statements(dd: &Yaml, scope: &Scope) -> Result { + pub fn build_statements(dd: &Yaml, scopes: &Scopes) -> Result { let d = dd .as_hash() .ok_or_else(|| Error::InvalidRule(line!(), "statement need to be hash".to_string()))?; @@ -370,7 +446,7 @@ impl Rule { .as_vec() .ok_or_else(|| Error::InvalidRule(line!(), format!("{:?}", vval)))?; for vv in val { - let p = Rule::build_statements(vv, scope)?; + let p = Rule::build_statements(vv, scopes)?; match p { StatementElement::Description(s) => description = s.value, _ => params.push(p), @@ -387,7 +463,7 @@ impl Rule { .as_vec() .ok_or_else(|| Error::InvalidRule(line!(), format!("{:?}", vval)))?; for vv in val { - let p = Rule::build_statements(vv, scope)?; + let p = Rule::build_statements(vv, scopes)?; match p { StatementElement::Description(s) => description = s.value, _ => params.push(p), @@ -404,7 +480,7 @@ impl Rule { .as_vec() .ok_or_else(|| Error::InvalidRule(line!(), format!("{:?}", vval)))?; for vv in val { - let p = Rule::build_statements(vv, scope)?; + let p = Rule::build_statements(vv, scopes)?; match p { StatementElement::Description(s) => description = s.value, _ => params.push(p), @@ -421,7 +497,7 @@ impl Rule { .as_vec() .ok_or_else(|| Error::InvalidRule(line!(), format!("{:?}", vval)))?; for vv in val { - let p = Rule::build_statements(vv, scope)?; + let p = Rule::build_statements(vv, scopes)?; match p { StatementElement::Description(s) => description = s.value, _ => params.push(p), @@ -431,15 +507,144 @@ impl Rule { SomeStatement::new(0, params, &description)?, )))); } + "process" => { + if [Scope::File].contains(&scopes.r#static.scope) + || [Scope::File].contains(&scopes.dynamic.scope) + { + let mut params = vec![]; + let mut description = "".to_string(); + let val = vval + .as_vec() + .ok_or_else(|| Error::InvalidRule(line!(), format!("{:?}", vval)))?; + for vv in val { + let p = Rule::build_statements( + vv, + &Scopes { + r#static: StaticScope { + scope: Scope::Process, + }, + dynamic: DynamicScope { scope: Scope::None }, + }, + )?; + match p { + StatementElement::Description(s) => description = s.value, + _ => params.push(p), + } + } + if params.len() != 1 { + return Err(Error::InvalidRule( + line!(), + format!("{:?}: {:?}", key, vval), + )); + } + return Ok(StatementElement::Statement(Box::new(Statement::Subscope( + SubscopeStatement::new( + Scope::Process, + params[0].clone(), + &description, + )?, + )))); + } + return Err(Error::InvalidRule( + line!(), + format!("{:?}: {:?}", key, vval), + )); + } + "thread" => { + if [Scope::File, Scope::Process].contains(&scopes.r#static.scope) + || [Scope::File, Scope::Process].contains(&scopes.dynamic.scope) + { + let mut params = vec![]; + let mut description = "".to_string(); + let val = vval + .as_vec() + .ok_or_else(|| Error::InvalidRule(line!(), format!("{:?}", vval)))?; + for vv in val { + let p = Rule::build_statements( + vv, + &Scopes { + r#static: StaticScope { + scope: Scope::Thread, + }, + dynamic: DynamicScope { scope: Scope::None }, + }, + )?; + match p { + StatementElement::Description(s) => description = s.value, + _ => params.push(p), + } + } + if params.len() != 1 { + return Err(Error::InvalidRule( + line!(), + format!("{:?}: {:?}", key, vval), + )); + } + return Ok(StatementElement::Statement(Box::new(Statement::Subscope( + SubscopeStatement::new(Scope::Thread, params[0].clone(), &description)?, + )))); + } + return Err(Error::InvalidRule( + line!(), + format!("{:?}: {:?}", key, vval), + )); + } + "call" => { + if [Scope::File, Scope::Process, Scope::Thread].contains(&scopes.r#static.scope) + || [Scope::File, Scope::Process, Scope::Thread] + .contains(&scopes.dynamic.scope) + { + let mut params = vec![]; + let mut description = "".to_string(); + let val = vval + .as_vec() + .ok_or_else(|| Error::InvalidRule(line!(), format!("{:?}", vval)))?; + for vv in val { + let p = Rule::build_statements( + vv, + &Scopes { + r#static: StaticScope { scope: Scope::Call }, + dynamic: DynamicScope { scope: Scope::None }, + }, + )?; + match p { + StatementElement::Description(s) => description = s.value, + _ => params.push(p), + } + } + if params.len() != 1 { + return Err(Error::InvalidRule( + line!(), + format!("{:?}: {:?}", key, vval), + )); + } + return Ok(StatementElement::Statement(Box::new(Statement::Subscope( + SubscopeStatement::new(Scope::Call, params[0].clone(), &description)?, + )))); + } + return Err(Error::InvalidRule( + line!(), + format!("{:?}: {:?}", key, vval), + )); + } + "function" => { - if let Scope::File = scope { + if Scope::File == scopes.r#static.scope || Scope::File == scopes.dynamic.scope { let mut params = vec![]; let mut description = "".to_string(); let val = vval .as_vec() .ok_or_else(|| Error::InvalidRule(line!(), format!("{:?}", vval)))?; for vv in val { - let p = Rule::build_statements(vv, &Scope::Function)?; + let p = Rule::build_statements( + vv, + &Scopes { + r#static: StaticScope { + scope: Scope::Function, + }, + dynamic: DynamicScope { scope: Scope::None }, + }, + )?; match p { StatementElement::Description(s) => description = s.value, _ => params.push(p), @@ -465,14 +670,16 @@ impl Rule { )); } "basic block" => { - if let Scope::Function = scope { + if Scope::Function == scopes.r#static.scope + || Scope::Function == scopes.dynamic.scope + { let mut params = vec![]; let mut description = "".to_string(); let val = vval .as_vec() .ok_or_else(|| Error::InvalidRule(line!(), format!("{:?}", vval)))?; for vv in val { - let p = Rule::build_statements(vv, scope)?; + let p = Rule::build_statements(vv, scopes)?; match p { StatementElement::Description(s) => description = s.value, _ => params.push(p), @@ -498,14 +705,16 @@ impl Rule { )); } "instruction" => { - if let Scope::BasicBlock | Scope::Function = scope { + if [Scope::BasicBlock, Scope::Function].contains(&scopes.r#static.scope) + || [Scope::BasicBlock, Scope::Function].contains(&scopes.dynamic.scope) + { let mut params = vec![]; let mut description = "".to_string(); let val = vval .as_vec() .ok_or_else(|| Error::InvalidRule(line!(), format!("{:?}", vval)))?; for vv in val { - let p = Rule::build_statements(vv, scope)?; + let p = Rule::build_statements(vv, scopes)?; match p { StatementElement::Description(s) => description = s.value, _ => params.push(p), @@ -527,7 +736,7 @@ impl Rule { } return Err(Error::InvalidRule( line!(), - format!("{:?}, {:?}: {:?}", scope, key, vval), + format!("{:?}, {:?}: {:?}", scopes, key, vval), )); } _ => { @@ -546,7 +755,7 @@ impl Rule { .as_vec() .ok_or_else(|| Error::InvalidRule(line!(), format!("{:?}", vval)))?; for vv in val { - let p = Rule::build_statements(vv, scope)?; + let p = Rule::build_statements(vv, scopes)?; match p { StatementElement::Description(s) => description = s.value, _ => params.push(p), @@ -587,7 +796,7 @@ impl Rule { //TODO: what about embedded newlines? Feature::new(feature_type, &Value::Str(arg.to_string()), "")? }; - Rule::ensure_feature_valid_for_scope(scope, &feature)?; + Rule::ensure_feature_valid_for_scope(scopes, &feature)?; //let val = // vval.as_str().ok_or(Error::InvalidRule(line!(), // format!("{:?} must be string", vval)))?; @@ -676,7 +885,7 @@ impl Rule { _ => "".to_string(), }; let feature = Feature::new(feature_type, &value, &d)?; - Rule::ensure_feature_valid_for_scope(scope, &feature)?; + Rule::ensure_feature_valid_for_scope(scopes, &feature)?; return Ok(StatementElement::Feature(Box::new(feature))); } } @@ -685,13 +894,13 @@ impl Rule { Err(Error::InvalidRule(line!(), "finish".to_string())) } - pub fn ensure_feature_valid_for_scope(scope: &Scope, feature: &Feature) -> Result<()> { - if feature.is_supported_in_scope(scope)? { + pub fn ensure_feature_valid_for_scope(scopes: &Scopes, feature: &Feature) -> Result<()> { + if feature.is_supported_in_scope(scopes)? { return Ok(()); } Err(Error::InvalidRule( line!(), - format!("{:?} not suported in scope {:?}", feature, scope), + format!("{:?} not suported in scope {:?}", feature, scopes), )) } @@ -833,7 +1042,7 @@ pub fn get_rules_and_dependencies<'a>(rules: &'a [Rule], rule_name: &str) -> Res pub fn get_rules_with_scope<'a>(rules: Vec<&'a Rule>, scope: &Scope) -> Result> { let mut res = vec![]; for rule in rules { - if &rule.scope == scope { + if &rule.scopes.r#static.scope == scope || &rule.scopes.dynamic.scope == scope { res.push(rule); } }