Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: utf16/utf16be/utf16le/wide modifiers #1503

Merged
merged 4 commits into from
Nov 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions CHANGELOG-Japanese.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@
- `gt`、`gte`、`lt`、`lte`のフィールドモディファイアに対応した。(#1433) (@fukusuket)
- 新しい`log-metrics`コマンドで`.evtx`ファイルの情報を取得できるようになった。(コンピュータ名、イベント数、最初のタイムスタンプ、最後のタイムスタンプ、チャネル、プロバイダ) (#1474) (@fukusuket)
- 以下のコマンドに`Channel`と`Provider`の略称を無効にする`-b, --disable-abbreviations`オプションを追加した。元の値を確認したい時に便利。 (#1485) (@fukusuket)
* csv-timeline
* json-timeline
* eid-metrics
* log-metrics
* search
* `csv-timeline`
* `json-timeline`
* `eid-metrics`
* `log-metrics`
* `search`
- `utf16/utf16be/utf16le/wide`フィールドモディファイアが`base64offset|contains`フィールドモディファイアと一緒に使えるようになった。 (#1432) (@fukusuket)
* `utf16|base64offset|contains`
* `utf16be|base64offset|contains`
* `utf16le|base64offset|contains`
* `wide|base64offset|contains`

**改善:**

Expand Down
15 changes: 10 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@
- Support for the `gt`, `gte`, `lt`, `lte` field modifiers. (#1433) (@fukusuket)
- New `log-metrics` command to get information about `.evtx` files. (computer names, event count, first timestamp, last timestamp, channels, providers) (#1474) (@fukusuket)
- New `-b, --disable-abbreviations` options for the following commands to disable `Channel` and `Provider` abbreviations for when you want to check the original values. (#1485) (@fukusuket)
* csv-timeline
* json-timeline
* eid-metrics
* log-metrics
* search
* `csv-timeline`
* `json-timeline`
* `eid-metrics`
* `log-metrics`
* `search`
- Support for `utf16/utf16be/utf16le/wide` field modifiers to be used with the `base64offset|contains` field modifier. (#1432) (@fukusuket)
* `utf16|base64offset|contains`
* `utf16be|base64offset|contains`
* `utf16le|base64offset|contains`
* `wide|base64offset|contains`

**Enhancements:**

Expand Down
187 changes: 187 additions & 0 deletions src/detections/rule/base64_match.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
use crate::detections::rule::fast_match::{convert_to_fast_match, FastMatch};
use crate::detections::rule::matchers::PipeElement;
use base64::engine::general_purpose;
use base64::Engine;
use std::io::Write;
use std::string::FromUtf8Error;

pub fn convert_to_base64_str(
encode: Option<&PipeElement>,
org_str: &str,
err_msges: &mut Vec<String>,
) -> Option<Vec<FastMatch>> {
let mut fastmatches = vec![];
for i in 0..3 {
let convstr_b64 = make_base64_str(encode, org_str, i);
match convstr_b64 {
Ok(b64_str) => {
let b64_s_null_filtered = b64_str.replace('\0', "");
let b64_offset_contents = base64_offset(i, b64_str, b64_s_null_filtered);
if let Some(fm) = convert_to_fast_match(&format!("*{b64_offset_contents}*"), false)
{
fastmatches.extend(fm);
}
}
Err(e) => {
err_msges.push(format!("Failed base64 encoding: {}", e));
}
}
}
if fastmatches.is_empty() {
return None;
}
Some(fastmatches)
}

fn make_base64_str(
encode: Option<&PipeElement>,
org_str: &str,
variant_index: usize,
) -> Result<String, FromUtf8Error> {
let mut b64_result = vec![];
let mut target_byte = vec![];
target_byte.resize_with(variant_index, || 0b0);
if let Some(en) = encode.as_ref() {
match en {
PipeElement::Utf16Be => {
let mut buffer = Vec::new();
for utf16 in org_str.encode_utf16() {
buffer.write_all(&utf16.to_be_bytes()).unwrap();
}
target_byte.extend_from_slice(buffer.as_slice())
}
PipeElement::Utf16Le | PipeElement::Wide => {
let mut buffer = Vec::new();
for utf16 in org_str.encode_utf16() {
buffer.write_all(&utf16.to_le_bytes()).unwrap();
}
target_byte.extend_from_slice(buffer.as_slice())
}
_ => target_byte.extend_from_slice(org_str.as_bytes()),
}
} else {
target_byte.extend_from_slice(org_str.as_bytes());
}
b64_result.resize_with(target_byte.len() * 4 / 3 + 4, || 0b0);
general_purpose::STANDARD
.encode_slice(target_byte, &mut b64_result)
.ok();
String::from_utf8(b64_result)
}

fn base64_offset(offset: usize, b64_str: String, b64_str_null_filtered: String) -> String {
match b64_str.find('=').unwrap_or_default() % 4 {
2 => {
if offset == 0 {
b64_str_null_filtered[..b64_str_null_filtered.len() - 3].to_string()
} else {
b64_str_null_filtered[(offset + 1)..b64_str_null_filtered.len() - 3].to_string()
}
}
3 => {
if offset == 0 {
b64_str_null_filtered[..b64_str_null_filtered.len() - 2].to_string()
} else {
b64_str_null_filtered.replace('\0', "")
[(offset + 1)..b64_str_null_filtered.len() - 2]
.to_string()
}
}
_ => {
if offset == 0 {
b64_str_null_filtered
} else {
b64_str_null_filtered[(offset + 1)..].to_string()
}
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_base64_offset() {
let b64_str = "aGVsbG8gd29ybGQ=".to_string();
let b64_str_null_filtered = "aGVsbG8gd29ybGQ=".to_string();
assert_eq!(
base64_offset(0, b64_str.clone(), b64_str_null_filtered.clone()),
"aGVsbG8gd29ybG"
);
assert_eq!(
base64_offset(1, b64_str.clone(), b64_str_null_filtered.clone()),
"VsbG8gd29ybG"
);
assert_eq!(
base64_offset(2, b64_str.clone(), b64_str_null_filtered.clone()),
"sbG8gd29ybG"
);
}

#[test]
fn test_convert_to_base64_str_utf8() {
let mut err_msges = vec![];
let val = "Hello, world!";
let m = convert_to_base64_str(None, val, &mut err_msges).unwrap();
assert_eq!(m[0], FastMatch::Contains("SGVsbG8sIHdvcmxkI".to_string()));
assert_eq!(m[1], FastMatch::Contains("hlbGxvLCB3b3JsZC".to_string()));
assert_eq!(m[2], FastMatch::Contains("IZWxsbywgd29ybGQh".to_string()));
}

#[test]
fn test_convert_to_base64_str_wide() {
let mut err_msges = vec![];
let val = "Hello, world!";
let m = convert_to_base64_str(Some(&PipeElement::Wide), val, &mut err_msges).unwrap();
assert_eq!(
m[0],
FastMatch::Contains("SABlAGwAbABvACwAIAB3AG8AcgBsAGQAIQ".to_string())
);
assert_eq!(
m[1],
FastMatch::Contains("gAZQBsAGwAbwAsACAAdwBvAHIAbABkACEA".to_string())
);
assert_eq!(
m[2],
FastMatch::Contains("IAGUAbABsAG8ALAAgAHcAbwByAGwAZAAhA".to_string())
);
}
#[test]
fn test_convert_to_base64_str_utf16le() {
let mut err_msges = vec![];
let val = "Hello, world!";
let m = convert_to_base64_str(Some(&PipeElement::Utf16Le), val, &mut err_msges).unwrap();
assert_eq!(
m[0],
FastMatch::Contains("SABlAGwAbABvACwAIAB3AG8AcgBsAGQAIQ".to_string())
);
assert_eq!(
m[1],
FastMatch::Contains("gAZQBsAGwAbwAsACAAdwBvAHIAbABkACEA".to_string())
);
assert_eq!(
m[2],
FastMatch::Contains("IAGUAbABsAG8ALAAgAHcAbwByAGwAZAAhA".to_string())
);
}

#[test]
fn test_convert_to_base64_str_utf16be() {
let mut err_msges = vec![];
let val = "Hello, world!";
let m = convert_to_base64_str(Some(&PipeElement::Utf16Be), val, &mut err_msges).unwrap();
assert_eq!(
m[0],
FastMatch::Contains("AEgAZQBsAGwAbwAsACAAdwBvAHIAbABkAC".to_string())
);
assert_eq!(
m[1],
FastMatch::Contains("BIAGUAbABsAG8ALAAgAHcAbwByAGwAZAAh".to_string())
);
assert_eq!(
m[2],
FastMatch::Contains("ASABlAGwAbABvACwAIAB3AG8AcgBsAGQAI".to_string())
);
}
}
Loading
Loading