Skip to content

Commit

Permalink
feat: activity and timeline
Browse files Browse the repository at this point in the history
  • Loading branch information
SecSamDev committed Feb 8, 2024
1 parent 245ae64 commit b007b52
Show file tree
Hide file tree
Showing 9 changed files with 251 additions and 89 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "forensic-rs"
version = "0.8.1"
version = "0.9.0"
authors = ["Samuel Garcés Marín <samuel.garces@protonmail.com>"]
keywords = ["forensic", "windows", "parser", "registry", "cybersecurity"]
categories = ["parsing"]
Expand Down
47 changes: 40 additions & 7 deletions src/activity.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
use std::{borrow::Cow, collections::BTreeMap};

use crate::utils::time::Filetime;

/// Activity of a user in a device
#[derive(Clone, Debug, Default)]
pub struct ForensicActivity {
pub timestamp : i64,
pub artifact : Cow<'static, str>,
pub host : String,
pub timestamp : Filetime,
pub user : String,
pub session_id : SessionId,
pub fields : BTreeMap<Cow<'static, str>, String>,
pub activity : ActivityType
}
#[derive(Clone, Debug, Default)]
Expand All @@ -20,8 +18,43 @@ pub enum SessionId {
#[derive(Clone, Debug, Default)]
pub enum ActivityType {
Login,
Browsing,
FileSystem,
Browsing(String),
FileSystem(FileSystemActivity),
ProgramExecution(ProgramExecution),
#[default]
Unknown
}

#[derive(Clone, Default)]
pub struct ProgramExecution {
pub executable : String
}

impl ProgramExecution {
pub fn new(executable : String) -> Self {
Self {
executable
}
}
}

impl From<ProgramExecution> for ActivityType {
fn from(v: ProgramExecution) -> Self {
ActivityType::ProgramExecution(v)
}
}
impl std::fmt::Debug for ProgramExecution {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.executable)
}
}

#[derive(Clone, Default, Debug)]
pub enum FileSystemActivity {
Open(String),
Delete(String),
Move((String, String)),
Create(String),
#[default]
Unknown
}
24 changes: 24 additions & 0 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,30 @@ pub fn context() -> ForensicContext {
FORENSIC_CONTEXT.with(|context| context.borrow().clone())
}

/// Changes the type of artifact being processed by the current thread
pub fn set_artifact<A : Into<Artifact>>(artifact : A) {
let artifact = artifact.into();
FORENSIC_CONTEXT.with(|context| {
let mut borrowed = context.borrow_mut();
borrowed.artifact = artifact;
})
}

/// Change the tenant ID for which artifacts are being processed by the current thread
pub fn set_tenant(tenant : String) {
FORENSIC_CONTEXT.with(|context| {
let mut borrowed = context.borrow_mut();
borrowed.tenant = tenant;
})
}
/// Change the name of the computer for which artifacts are being processed by the current thread
pub fn set_host(host : String) {
FORENSIC_CONTEXT.with(|context| {
let mut borrowed = context.borrow_mut();
borrowed.host = host;
})
}

#[test]
fn should_initialize_log_with_context() {
use crate::artifact::Artifact;
Expand Down
5 changes: 2 additions & 3 deletions src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,8 @@ impl<'a> ForensicData {
pub fn field_mut(&'a mut self, field_name: &str) -> Option<&mut Field> {
Some(&mut self.fields.get_mut(field_name)?.original)
}
pub fn add_field(&mut self, field_name: &str, field_value: Field) {
let field_name = Text::Owned(field_name.to_owned());
self.insert(field_name, field_value);
pub fn add_field(&mut self, field_name: &'static str, field_value: Field) {
self.insert(Text::Borrowed(field_name), field_value);
}
pub fn insert(&mut self, field_name: Text, field_value: Field) {
self.fields.insert(field_name, field_value.into());
Expand Down
127 changes: 72 additions & 55 deletions src/dictionary.rs
Original file line number Diff line number Diff line change
@@ -1,65 +1,82 @@
//https://www.elastic.co/guide/en/ecs/current/index.html
// Some of this events are automatically created when you map a SiemLog to a SiemEvent. The object field types are not supported for simplicity in uSIEM.
// If needed join the values by the character "\n" into a single String. Useful for file names.
pub static EVENT_OUTCOME: &'static str = "event.outcome";
pub const EVENT_OUTCOME: &str = "event.outcome";
/// The action captured by the event. This describes the information in the event. It is more specific than event.category. Examples are group-add, process-started, file-created. The value is normally defined by the implementer.
pub static EVENT_ACTION: &'static str = "event.action";
pub const EVENT_ACTION: &str = "event.action";
/// event.category represents the "big buckets" of ECS categories. For example, filtering on event.category:process yields all events relating to process activity. Valudes: authentication, configuration, database, driver, file, host, iam, intrusion_detection, malware, network, package, process, web
pub static EVENT_CATEGORY: &'static str = "event.category";
pub const EVENT_CATEGORY: &str = "event.category";
/// Some event sources use event codes to identify messages unambiguously, regardless of message language or wording adjustments over time. An example of this is the Windows Event ID.
pub static EVENT_CODE: &'static str = "event.code";
pub const EVENT_CODE: &str = "event.code";

pub static USER_NAME: &'static str = "user.name";
pub static USER_DOMAIN: &'static str = "user.domain";
pub static SOURCE_IP: &'static str = "source.ip";
pub static SOURCE_PORT: &'static str = "source.port";
pub const USER_NAME: &str = "user.name";
pub const USER_DOMAIN: &str = "user.domain";
pub const SOURCE_IP: &str = "source.ip";
pub const SOURCE_PORT: &str = "source.port";
/// Amount of bytes sent by the local host
pub static SOURCE_BYTES: &'static str = "source.bytes";
pub static DESTINATION_IP: &'static str = "destination.ip";
pub static DESTINATION_PORT: &'static str = "destination.port";
pub const SOURCE_BYTES: &str = "source.bytes";
pub const DESTINATION_IP: &str = "destination.ip";
pub const DESTINATION_PORT: &str = "destination.port";

/// Amount of bytes sent by the remote host
pub static DESTINATION_BYTES: &'static str = "destination.bytes";

pub static NETWORK_TRANSPORT: &'static str = "network.transport";
pub static NETWORK_PROTOCOL: &'static str = "network.protocol";
pub static NETWORK_DURATION: &'static str = "network.duration";

pub static IN_INTERFACE: &'static str = "observer.ingress.interface";
pub static OUT_INTERFACE: &'static str = "observer.egress.interface";

pub static OBSERVER_IP: &'static str = "observer.ip";
pub static OBSERVER_NAME: &'static str = "observer.name";

pub static URL_FULL: &'static str = "url.full";
pub static URL_DOMAIN: &'static str = "url.domain";
pub static URL_PATH: &'static str = "url.path";
pub static URL_QUERY: &'static str = "url.query";

pub static HTTP_REQUEST_METHOD: &'static str = "http.request.method";
pub static HTTP_RESPONSE_MIME_TYPE: &'static str = "http.response.mime_type";
pub static HTTP_RESPONSE_STATUS_CODE: &'static str = "http.response.status_code";

pub static RULE_NAME: &'static str = "rule.name";
pub static RULE_CATEGORY: &'static str = "rule.category";
pub static RULE_ID: &'static str = "rule.id";

pub static DNS_OP_CODE: &'static str = "dns.op_code";
pub static DNS_ANSWER_CLASS: &'static str = "dns.answer.class";
pub static DNS_ANSWER_NAME: &'static str = "dns.answer.name";
pub static DNS_ANSWER_TYPE: &'static str = "dns.answer.type";
pub static DNS_ANSWER_TTL: &'static str = "dns.answer.ttl";
pub static DNS_ANSWER_DATA: &'static str = "dns.answer.data";
pub static DNS_QUESTION_CLASS: &'static str = "dns.question.class";
pub static DNS_QUESTION_NAME: &'static str = "dns.question.name";
pub static DNS_QUESTION_TYPE: &'static str = "dns.question.type";
pub static DNS_RESOLVED_IP: &'static str = "dns.resolved_ip";

pub static DHCP_RECORD_TYPE: &'static str = "dhcp.type";

pub static TAG_REPROCESS: &'static str = "reprocess_log";

pub static ARTIFACT_NAME: &'static str = "artifact.name";
pub static ARTIFACT_PATH: &'static str = "artifact.path";
pub static ARTIFACT_HOST: &'static str = "artifact.host";
pub static ARTIFACT_TENANT: &'static str = "artifact.tenant";
pub const DESTINATION_BYTES: &str = "destination.bytes";

pub const NETWORK_TRANSPORT: &str = "network.transport";
pub const NETWORK_PROTOCOL: &str = "network.protocol";
pub const NETWORK_DURATION: &str = "network.duration";

pub const IN_INTERFACE: &str = "observer.ingress.interface";
pub const OUT_INTERFACE: &str = "observer.egress.interface";

pub const OBSERVER_IP: &str = "observer.ip";
pub const OBSERVER_NAME: &str = "observer.name";

pub const URL_FULL: &str = "url.full";
pub const URL_DOMAIN: &str = "url.domain";
pub const URL_PATH: &str = "url.path";
pub const URL_QUERY: &str = "url.query";

pub const HTTP_REQUEST_METHOD: &str = "http.request.method";
pub const HTTP_RESPONSE_MIME_TYPE: &str = "http.response.mime_type";
pub const HTTP_RESPONSE_STATUS_CODE: &str = "http.response.status_code";

pub const RULE_NAME: &str = "rule.name";
pub const RULE_CATEGORY: &str = "rule.category";
pub const RULE_ID: &str = "rule.id";

pub const DNS_OP_CODE: &str = "dns.op_code";
pub const DNS_ANSWER_CLASS: &str = "dns.answer.class";
pub const DNS_ANSWER_NAME: &str = "dns.answer.name";
pub const DNS_ANSWER_TYPE: &str = "dns.answer.type";
pub const DNS_ANSWER_TTL: &str = "dns.answer.ttl";
pub const DNS_ANSWER_DATA: &str = "dns.answer.data";
pub const DNS_QUESTION_CLASS: &str = "dns.question.class";
pub const DNS_QUESTION_NAME: &str = "dns.question.name";
pub const DNS_QUESTION_TYPE: &str = "dns.question.type";
pub const DNS_RESOLVED_IP: &str = "dns.resolved_ip";

pub const DHCP_RECORD_TYPE: &str = "dhcp.type";

pub const TAG_REPROCESS: &str = "reprocess_log";

pub const ARTIFACT_NAME: &str = "artifact.name";
pub const ARTIFACT_PATH: &str = "artifact.path";
pub const ARTIFACT_HOST: &str = "artifact.host";
pub const ARTIFACT_TENANT: &str = "artifact.tenant";

pub const PROCESS_EXECUTABLE : &str = "process.executable";

pub const FILE_INODE : &str = "file.inode";
pub const FILE_NAME : &str = "file.name";
pub const FILE_OWNER : &str = "file.OWNER";
pub const FILE_PATH : &str = "file.path";
pub const FILE_SIZE : &str = "file.size";
pub const FILE_TYPE : &str = "file.type";
pub const FILE_ACCESSED : &str = "file.accessed";
pub const FILE_CREATED : &str = "file.created";
pub const FILE_DEVICE : &str = "file.device";
pub const FILE_DIRECTORY : &str = "file.directory";
pub const FILE_EXTENSION : &str = "file.extension";


pub const PE_IMPORTS : &str = "pe.imports";
13 changes: 10 additions & 3 deletions src/field/internal.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::utils::time::Filetime;

use super::{Field, Text, Ip};


Expand All @@ -9,13 +11,14 @@ pub enum PreStoredField<T> {
Some(T)
}

#[derive(Debug, Clone, Default)]
#[derive(Clone, Default)]
pub struct InternalField {
pub original : Field,
pub array : Box<PreStoredField<Vec<Text>>>,
pub text : Box<PreStoredField<Text>>,
pub nu64 : Box<PreStoredField<u64>>,
pub ni64 : Box<PreStoredField<i64>>,
pub date : Box<PreStoredField<Filetime>>,
pub nf64 : Box<PreStoredField<f64>>,
pub ip : Box<PreStoredField<Ip>>
}
Expand All @@ -25,7 +28,11 @@ impl InternalField {
field.into()
}
}

impl std::fmt::Debug for InternalField {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("{:?}", self.original))
}
}
impl Into<InternalField> for Field{
fn into(self) -> InternalField {
let mut ifield = InternalField {
Expand All @@ -40,7 +47,7 @@ impl Into<InternalField> for Field{
ifield.ni64 = Box::new(PreStoredField::Some(*v));
},
Field::Date(v) => {
ifield.ni64 = Box::new(PreStoredField::Some(*v));
ifield.date = Box::new(PreStoredField::Some(*v));
},
Field::U64(v) => {
ifield.nu64 = Box::new(PreStoredField::Some(*v));
Expand Down
44 changes: 38 additions & 6 deletions src/field/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ pub mod utils;

pub use ip::Ip;

use crate::utils::time::Filetime;

pub type Text = Cow<'static, str>;

#[derive(Debug, Clone, Default)]
#[derive(Clone, Default)]
#[non_exhaustive]
pub enum Field {
#[default]
Expand Down Expand Up @@ -42,11 +44,30 @@ pub enum Field {
/// decimal number with 64 bits
F64(f64),
///A date in a decimal number format with 64 bits
Date(i64),
Date(Filetime),
Array(Vec<Text>),
Path(PathBuf),
}

impl std::fmt::Debug for Field {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Null => write!(f, "Null"),
Self::Text(arg0) => f.write_fmt(format_args!("{:?}", arg0)),
Self::Ip(arg0) => f.write_fmt(format_args!("{}", arg0)),
Self::Domain(arg0) => f.write_fmt(format_args!("{:?}", arg0)),
Self::User(arg0) => f.write_fmt(format_args!("{:?}", arg0)),
Self::AssetID(arg0) => f.write_fmt(format_args!("{:?}", arg0)),
Self::U64(arg0) => f.write_fmt(format_args!("{}", arg0)),
Self::I64(arg0) => f.write_fmt(format_args!("{}", arg0)),
Self::F64(arg0) => f.write_fmt(format_args!("{}", arg0)),
Self::Date(arg0) => f.write_fmt(format_args!("{:?}", arg0)),
Self::Array(arg0) => f.debug_list().entries(arg0.iter()).finish(),
Self::Path(arg0) => f.write_fmt(format_args!("{:?}", arg0.to_string_lossy())),
}
}
}

impl<'a> TryInto<&'a str> for &'a Field {
type Error = &'static str;

Expand Down Expand Up @@ -126,7 +147,7 @@ impl<'a> TryInto<u64> for &'a Field {
Field::F64(v) => *v as u64,
Field::I64(v) => *v as u64,
Field::U64(v) => *v,
Field::Date(v) => *v as u64,
Field::Date(v) => v.filetime() as u64,
_ => return Err("Invalid type"),
})
}
Expand All @@ -139,7 +160,7 @@ impl<'a> TryInto<i64> for &'a Field {
Field::F64(v) => *v as i64,
Field::I64(v) => *v as i64,
Field::U64(v) => *v as i64,
Field::Date(v) => *v as i64,
Field::Date(v) => v.filetime() as i64,
_ => return Err("Invalid type"),
})
}
Expand All @@ -152,7 +173,7 @@ impl<'a> TryInto<f64> for &'a Field {
Field::F64(v) => *v as f64,
Field::I64(v) => *v as f64,
Field::U64(v) => *v as f64,
Field::Date(v) => *v as f64,
Field::Date(v) => v.filetime() as f64,
_ => return Err("Invalid type"),
})
}
Expand Down Expand Up @@ -205,6 +226,17 @@ impl From<u64> for Field {
Field::U64(v)
}
}
impl From<&u32> for Field {
fn from(v: &u32) -> Field {
Field::U64(*v as u64)
}
}
impl From<u32> for Field {
fn from(v: u32) -> Field {
Field::U64(v as u64)
}
}

impl From<&i64> for Field {
fn from(v: &i64) -> Field {
Field::I64(*v)
Expand Down Expand Up @@ -263,7 +295,7 @@ impl Serialize for Field {
Field::U64(v) => serializer.serialize_u64(*v),
Field::I64(v) => serializer.serialize_i64(*v),
Field::F64(v) => serializer.serialize_f64(*v),
Field::Date(v) => serializer.serialize_i64(*v),
Field::Date(v) => serializer.serialize_str(&v.to_string()),
Field::Array(v) => v.serialize(serializer),
Field::Path(v) => serializer.serialize_str(&v.to_string_lossy()[..]),
}
Expand Down
Loading

0 comments on commit b007b52

Please sign in to comment.