Skip to content

Commit

Permalink
feat: add in initial sigma base64 and base64offset support
Browse files Browse the repository at this point in the history
The base64 modifier is self explanitory. The offset one is a tad more
complex, using offsets and then taking fixed substrings from the output.
Both approaches mirror the logic from Sigma.

Fixes: WithSecureLabs#138
  • Loading branch information
alexkornitzer committed Jul 4, 2023
1 parent 424734d commit 646cff0
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 11 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ edition = "2021"
[dependencies]
aho-corasick = "0.7"
anyhow = "1.0"
base64 = "0.21"
bincode = "1.3"
bytesize = "1.0"
chrono = "0.4"
Expand Down
53 changes: 42 additions & 11 deletions src/rule/sigma.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::io::prelude::*;
use std::path::Path;

use anyhow::Result;
use base64::Engine;
use regex::Regex;
use serde::{Deserialize, Serialize};
use serde_yaml::{Mapping, Sequence, Value as Yaml};
Expand Down Expand Up @@ -265,6 +266,8 @@ lazy_static::lazy_static! {
static ref SUPPORTED_MODIFIERS: HashSet<String> = {
let mut set = HashSet::new();
set.insert("all".to_owned());
set.insert("base64".to_owned());
set.insert("base64offset".to_owned());
set.insert("contains".to_owned());
set.insert("endswith".to_owned());
set.insert("startswith".to_owned());
Expand All @@ -274,6 +277,15 @@ lazy_static::lazy_static! {
}

fn parse_identifier(value: &Yaml, modifiers: &HashSet<String>) -> Result<Yaml> {
let mut unsupported: Vec<String> = modifiers
.difference(&*SUPPORTED_MODIFIERS)
.cloned()
.collect();
if unsupported.len() > 0 {
unsupported.sort();
return Err(anyhow!(unsupported.join(", ")).context("unsupported modifiers"));
}

let v = match value {
Yaml::Mapping(m) => {
let mut scratch = Mapping::new();
Expand All @@ -285,21 +297,40 @@ fn parse_identifier(value: &Yaml, modifiers: &HashSet<String>) -> Result<Yaml> {
Yaml::Sequence(s) => {
let mut scratch = vec![];
for s in s {
scratch.push(parse_identifier(s, modifiers)?);
let value = parse_identifier(s, modifiers)?;
match value {
Yaml::Sequence(s) => scratch.extend(s),
_ => scratch.push(value),
}
}
Yaml::Sequence(scratch)
}
Yaml::String(s) => {
let mut unsupported: Vec<String> = modifiers
.difference(&*SUPPORTED_MODIFIERS)
.cloned()
.collect();
if unsupported.len() > 0 {
unsupported.sort();
return Err(anyhow!(unsupported.join(", ")).context("unsupported modifiers"));
}

if modifiers.contains("contains") {
if modifiers.contains("base64") {
let mut remaining = modifiers.clone();
let _ = remaining.remove("base64");
let encoded = base64::engine::general_purpose::STANDARD.encode(s.to_owned());
parse_identifier(&Yaml::String(encoded), &remaining)?
} else if modifiers.contains("base64offset") {
let mut remaining = modifiers.clone();
let _ = remaining.remove("base64offset");
let mut scratch = Vec::with_capacity(3);
for i in 0..3 {
let mut value = (0..i).fold("".to_owned(), |mut s, _| {
s.push_str(" ");
s
});
value.push_str(s);
let encoded =
base64::engine::general_purpose::STANDARD.encode(value.to_owned());
static S: [usize; 3] = [0, 2, 3];
static E: [usize; 3] = [0, 3, 2];
let len = value.len();
let trimmed = encoded[S[i]..encoded.len() - E[len % 3]].to_owned();
scratch.push(parse_identifier(&Yaml::String(trimmed), &remaining)?);
}
Yaml::Sequence(scratch)
} else if modifiers.contains("contains") {
Yaml::String(s.as_contains())
} else if modifiers.contains("endswith") {
Yaml::String(s.as_endswith())
Expand Down

0 comments on commit 646cff0

Please sign in to comment.