Skip to content

Commit

Permalink
Add packet attribute triage function
Browse files Browse the repository at this point in the history
  • Loading branch information
kimhanbeom committed Oct 7, 2024
1 parent 714de1a commit bd05b0b
Show file tree
Hide file tree
Showing 6 changed files with 550 additions and 53 deletions.
69 changes: 63 additions & 6 deletions src/event/bootp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,46 @@ use std::{fmt, net::IpAddr, num::NonZeroU8};

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

use super::{common::Match, EventCategory, TriagePolicy, TriageScore, MEDIUM};
use crate::event::common::{to_hardware_address, triage_scores_to_string};
use strum_macros::EnumString;

use super::{common::Match, EventCategory, TriageScore, MEDIUM};
use crate::event::common::{to_hardware_address, triage_scores_to_string, AttrValue};

#[derive(Debug, EnumString)]
enum BootpAttr {
#[strum(serialize = "bootp-id.orig_h")]
SrcAddr,
#[strum(serialize = "bootp-id.orig_p")]
SrcPort,
#[strum(serialize = "bootp-id.resp_h")]
DstAddr,
#[strum(serialize = "bootp-id.resp_p")]
DstPort,
#[strum(serialize = "bootp-proto")]
Proto,
#[strum(serialize = "bootp-op")]
Op,
#[strum(serialize = "bootp-htype")]
Htype,
#[strum(serialize = "bootp-hops")]
Hops,
#[strum(serialize = "bootp-xid")]
Xid,
#[strum(serialize = "bootp-ciaddr")]
CiAddr,
#[strum(serialize = "bootp-yiaddr")]
YiAddr,
#[strum(serialize = "bootp-siaddr")]
SiAddr,
#[strum(serialize = "bootp-giaddr")]
GiAddr,
#[strum(serialize = "bootp-chaddr")]
ChAddr,
#[strum(serialize = "bootp-sname")]
SName,
#[strum(serialize = "bootp-file")]
File,
}

#[derive(Serialize, Deserialize)]
pub struct BlockListBootpFields {
Expand Down Expand Up @@ -135,7 +172,7 @@ impl BlockListBootp {
}
}

impl Match for BlockListBootp {
impl Match<BootpAttr> for BlockListBootp {
fn src_addr(&self) -> IpAddr {
self.src_addr
}
Expand Down Expand Up @@ -176,7 +213,27 @@ impl Match for BlockListBootp {
None
}

fn score_by_packet_attr(&self, _triage: &TriagePolicy) -> f64 {
0.0
fn target_attribute(&self, proto_attr: BootpAttr) -> Option<AttrValue> {
let target_val = match proto_attr {
BootpAttr::SrcAddr => AttrValue::Addr(self.src_addr),
BootpAttr::SrcPort => AttrValue::UInt(self.src_port.into()),
BootpAttr::DstAddr => AttrValue::Addr(self.dst_addr),
BootpAttr::DstPort => AttrValue::UInt(self.dst_port.into()),
BootpAttr::Proto => AttrValue::UInt(self.proto.into()),
BootpAttr::Op => AttrValue::UInt(self.op.into()),
BootpAttr::Htype => AttrValue::UInt(self.htype.into()),
BootpAttr::Hops => AttrValue::UInt(self.hops.into()),
BootpAttr::Xid => AttrValue::UInt(self.xid.into()),
BootpAttr::CiAddr => AttrValue::Addr(self.ciaddr),
BootpAttr::YiAddr => AttrValue::Addr(self.yiaddr),
BootpAttr::SiAddr => AttrValue::Addr(self.siaddr),
BootpAttr::GiAddr => AttrValue::Addr(self.giaddr),
BootpAttr::ChAddr => {
AttrValue::VecUInt(self.chaddr.iter().map(|s| u64::from(*s)).collect())
}
BootpAttr::SName => AttrValue::String(self.sname.clone()),
BootpAttr::File => AttrValue::String(self.file.clone()),
};
Some(target_val)
}
}
201 changes: 192 additions & 9 deletions src/event/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,24 @@ use std::{
fmt::{self, Formatter},
net::IpAddr,
num::NonZeroU8,
str::FromStr,
sync::{Arc, Mutex},
};

use anyhow::{bail, Result};
use bincode::Options;
use num_traits::ToPrimitive;
use serde::{Deserialize, Serialize};

use super::{
eq_ip_country, EventCategory, EventFilter, FlowKind, LearningMethod, TrafficDirection,
TriagePolicy,
};
use crate::{AttrCmpKind, PacketAttr};

// TODO: Make new Match trait to support Windows Events

pub(super) trait Match {
pub(super) trait Match<T: FromStr> {
fn src_addr(&self) -> IpAddr;
#[allow(dead_code)] // for future use
fn src_port(&self) -> u16;
Expand All @@ -30,14 +33,22 @@ pub(super) trait Match {
fn kind(&self) -> &str;
fn source(&self) -> &str;
fn confidence(&self) -> Option<f32>;

/// Calculates a score based on packet attributes according to the triage policy.
///
/// Note: This method is currently unused. All implementations return 0.0.
/// It's retained for future use as planned by @syncpark.
/// For more details, see:
/// <https://github.com/petabi/review-database/pull/321#discussion_r1721392271>
fn score_by_packet_attr(&self, triage: &TriagePolicy) -> f64;
fn target_attribute(&self, proto_attr: T) -> Option<AttrValue>;
fn score_by_packet_attr(&self, triage: &TriagePolicy) -> f64 {
let mut attr_total_score = 0.0;
for attr in &triage.packet_attr {
let Ok(proto_attr) = T::from_str(&attr.attr_name) else {
continue;
};
let Some(target_attr_val) = self.target_attribute(proto_attr) else {
continue;
};
if process_attr_compare(target_attr_val, attr) {
attr_total_score += attr.weight.unwrap(); //weight always exist.
}
}
attr_total_score
}

/// Returns whether the event matches the filter and the triage scores. The triage scores are
/// only returned if the event matches the filter.
Expand Down Expand Up @@ -295,6 +306,178 @@ pub fn to_hardware_address(chaddr: &[u8]) -> String {
)
}

pub enum AttrValue {
Addr(IpAddr),
Bool(bool),
Float(f64),
SInt(i64),
UInt(u64),
String(String),
VecAddr(Vec<IpAddr>),
VecFloat(Vec<f64>),
VecSInt(Vec<i64>),
VecUInt(Vec<u64>),
VecString(Vec<String>),
}

fn process_attr_compare(target_value: AttrValue, attr: &PacketAttr) -> bool {
match target_value {
AttrValue::Addr(ip_addr) => process_attr_compare_addr(ip_addr, attr),
AttrValue::Bool(bool_val) => process_attr_compare_bool(bool_val, attr),
AttrValue::Float(float_val) => process_attr_compare_number::<_, f64>(&float_val, attr),
AttrValue::SInt(signed_int_val) => {
process_attr_compare_number::<_, i64>(&signed_int_val, attr)
}
AttrValue::UInt(unsigned_int_val) => {
process_attr_compare_number::<_, u64>(&unsigned_int_val, attr)
}
AttrValue::String(str_val) => process_attr_compare_string(&str_val, attr),
AttrValue::VecAddr(vec_addr_val) => vec_addr_val
.iter()
.any(|addr| process_attr_compare_addr(*addr, attr)),
AttrValue::VecFloat(vec_float_val) => vec_float_val
.iter()
.any(|float| process_attr_compare_number::<_, f64>(float, attr)),
AttrValue::VecSInt(vec_int_val) => vec_int_val
.iter()
.any(|int| process_attr_compare_number::<_, i64>(int, attr)),
AttrValue::VecUInt(vec_int_val) => vec_int_val
.iter()
.any(|int| process_attr_compare_number::<_, u64>(int, attr)),
AttrValue::VecString(vec_str_val) => vec_str_val
.iter()
.any(|str| process_attr_compare_string(str, attr)),
}
}

fn deserialize<'de, T>(value: &'de [u8]) -> Option<T>
where
T: Deserialize<'de>,
{
bincode::DefaultOptions::new().deserialize::<T>(value).ok()
}

fn check_second_value<'de, T, K>(kind: AttrCmpKind, value: &'de Option<Vec<u8>>) -> Option<T>
where
T: TryFrom<K> + std::cmp::PartialOrd,
K: Deserialize<'de>,
{
if matches!(
kind,
AttrCmpKind::CloseRange
| AttrCmpKind::LeftOpenRange
| AttrCmpKind::RightOpenRange
| AttrCmpKind::NotOpenRange
| AttrCmpKind::NotCloseRange
| AttrCmpKind::NotLeftOpenRange
| AttrCmpKind::NotRightOpenRange
) {
let value_result = value.as_ref()?;
let de_second_value = deserialize::<K>(value_result)?;
let convert_second_value = T::try_from(de_second_value).ok()?;
Some(convert_second_value)
} else {
None
}
}

fn compare_all_attr_cmp_kind<T>(
cmp_kind: AttrCmpKind,
attr_val: &T,
first_val: &T,
second_val: Option<T>,
) -> bool
where
T: PartialOrd,
{
match (cmp_kind, second_val) {
(AttrCmpKind::Less, _) => attr_val < first_val,
(AttrCmpKind::LessOrEqual, _) => attr_val <= first_val,
(AttrCmpKind::Equal, _) => attr_val == first_val,
(AttrCmpKind::NotEqual, _) => attr_val != first_val,
(AttrCmpKind::Greater, _) => attr_val > first_val,
(AttrCmpKind::GreaterOrEqual, _) => attr_val >= first_val,
(AttrCmpKind::OpenRange, Some(second_val)) => {
(first_val < attr_val) && (second_val > *attr_val)
}
(AttrCmpKind::CloseRange, Some(second_val)) => {
(first_val <= attr_val) && (second_val >= *attr_val)
}
(AttrCmpKind::LeftOpenRange, Some(second_val)) => {
(first_val < attr_val) && (second_val >= *attr_val)
}
(AttrCmpKind::RightOpenRange, Some(second_val)) => {
(first_val <= attr_val) && (second_val > *attr_val)
}
(AttrCmpKind::NotOpenRange, Some(second_val)) => {
!((first_val < attr_val) && (second_val > *attr_val))
}
(AttrCmpKind::NotCloseRange, Some(second_val)) => {
!((first_val <= attr_val) && (second_val >= *attr_val))
}
(AttrCmpKind::NotLeftOpenRange, Some(second_val)) => {
!((first_val < attr_val) && (second_val >= *attr_val))
}
(AttrCmpKind::NotRightOpenRange, Some(second_val)) => {
!((first_val <= attr_val) && (second_val > *attr_val))
}
_ => false,
}
}

fn process_attr_compare_bool(attr_val: bool, packet_attr: &PacketAttr) -> bool {
deserialize::<bool>(&packet_attr.first_value).is_some_and(|compare_val| {
match packet_attr.cmp_kind {
AttrCmpKind::Equal => attr_val == compare_val,
AttrCmpKind::NotEqual => attr_val != compare_val,
_ => false,
}
})
}

fn process_attr_compare_string(attr_val: &str, packet_attr: &PacketAttr) -> bool {
deserialize::<String>(&packet_attr.first_value).is_some_and(|compare_val| {
let cmp_result = attr_val.contains(&compare_val);
match packet_attr.cmp_kind {
AttrCmpKind::Contain => cmp_result,
AttrCmpKind::NotContain => !cmp_result,
_ => false,
}
})
}

fn process_attr_compare_addr(attr_val: IpAddr, packet_attr: &PacketAttr) -> bool {
if let Some(first_val) = deserialize::<IpAddr>(&packet_attr.first_value) {
let second_val = if let Some(serde_val) = &packet_attr.second_value {
deserialize::<IpAddr>(serde_val)
} else {
None
};
return compare_all_attr_cmp_kind(packet_attr.cmp_kind, &attr_val, &first_val, second_val);
}
false
}

fn process_attr_compare_number<'de, T, K>(attr_val: &T, packet_attr: &'de PacketAttr) -> bool
where
T: TryFrom<K> + PartialOrd,
K: Deserialize<'de>,
{
if let Some(first_val) = deserialize::<K>(&packet_attr.first_value) {
if let Ok(first_val) = T::try_from(first_val) {
let second_val =
check_second_value::<T, K>(packet_attr.cmp_kind, &packet_attr.second_value);
return compare_all_attr_cmp_kind(
packet_attr.cmp_kind,
attr_val,
&first_val,
second_val,
);
}
};
false
}

mod tests {
#[test]
fn empty_byte_slice_to_colon_separated_string() {
Expand Down
Loading

0 comments on commit bd05b0b

Please sign in to comment.