From 2bf76c42092c5f8269f46e414e6216dc2c18efcc Mon Sep 17 00:00:00 2001 From: Kazuminn Date: Fri, 2 Oct 2020 14:37:56 +0900 Subject: [PATCH 01/14] add check_regex() and check_creater() --- Cargo.lock | 44 +++++++++++++++++++++++++++++ Cargo.toml | 1 + regexes.txt | 19 +++++++++++++ src/detections/mod.rs | 1 + src/detections/security.rs | 7 ++--- src/detections/utils.rs | 57 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 124 insertions(+), 5 deletions(-) create mode 100644 regexes.txt create mode 100644 src/detections/utils.rs diff --git a/Cargo.lock b/Cargo.lock index c3e1647b..ba27fb23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,6 +76,18 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "bstr" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31accafdb70df7871592c058eca3985b71104e15ac32f64706022c58867da931" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", + "serde", +] + [[package]] name = "build_const" version = "0.2.1" @@ -219,6 +231,28 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "csv" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00affe7f6ab566df61b4be3ce8cf16bc2576bca0963ceb0955e45d514bf9a279" +dependencies = [ + "bstr", + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +dependencies = [ + "memchr", +] + [[package]] name = "dialoguer" version = "0.6.2" @@ -747,6 +781,15 @@ dependencies = [ "thread_local", ] +[[package]] +name = "regex-automata" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" +dependencies = [ + "byteorder", +] + [[package]] name = "regex-syntax" version = "0.6.18" @@ -1108,6 +1151,7 @@ name = "yamato_event_analyzer" version = "0.1.0" dependencies = [ "clap", + "csv", "evtx", "quick-xml 0.17.2", "regex", diff --git a/Cargo.toml b/Cargo.toml index 04818b2c..406ca26c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,4 @@ serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0"} clap = "*" regex = "1" +csv = "1.1" diff --git a/regexes.txt b/regexes.txt new file mode 100644 index 00000000..f59a3792 --- /dev/null +++ b/regexes.txt @@ -0,0 +1,19 @@ +Type,regex,string +0,^cmd.exe /c echo [a-z]{6} > \\\\.\\pipe\\[a-z]{6}$,Metasploit-style cmd with pipe (possible use of Meterpreter 'getsystem') +0,^%SYSTEMROOT%\\[a-zA-Z]{8}\.exe$,Metasploit-style %SYSTEMROOT% image path (possible use of Metasploit 'Native upload' exploit payload) +0,powershell.*FromBase64String.*IO.Compression.GzipStream,Metasploit-style base64 encoded/compressed PowerShell function (possible use of Metasploit PowerShell exploit payload) +0,DownloadString\(.http,Download via Net.WebClient DownloadString +0,mimikatz,Command referencing Mimikatz +0,Invoke-Mimikatz.ps,PowerSploit Invoke-Mimikatz.ps1 +0,PowerSploit.*ps1,Use of PowerSploit +0,User-Agent,User-Agent set via command line +0,[a-zA-Z0-9/+=]{500},500+ consecutive Base64 characters +0,powershell.exe.*Hidden.*Enc,Base64 encoded and hidden PowerShell command +# Generic csc.exe alert, comment out if experiencing false positives +0,\\csc\.exe,Use of C Sharp compiler csc.exe +0,\\csc\.exe.*\\Appdata\\Local\\Temp\\[a-z0-9]{8}\.cmdline,PSAttack-style command via csc.exe +# Generic cvtres.exe alert, comment out if experiencing false positives +0,\\cvtres\.exe.*,Resource File To COFF Object Conversion Utility cvtres.exe +0,\\cvtres\.exe.*\\AppData\\Local\\Temp\\[A-Z0-9]{7}\.tmp,PSAttack-style command via cvtres.exe +1,^[a-zA-Z]{22}$,Metasploit-style service name: 22 characters, [A-Za-z] +1,^[a-zA-Z]{16}$,Metasploit-style service name: 16 characters, [A-Za-z] \ No newline at end of file diff --git a/src/detections/mod.rs b/src/detections/mod.rs index 7238b4aa..ba8b0f90 100644 --- a/src/detections/mod.rs +++ b/src/detections/mod.rs @@ -3,3 +3,4 @@ mod common; pub mod detection; mod security; mod system; +mod utils; diff --git a/src/detections/security.rs b/src/detections/security.rs index 82e23f71..16426937 100644 --- a/src/detections/security.rs +++ b/src/detections/security.rs @@ -42,7 +42,6 @@ impl Security { // Special privileges assigned to new logon (possible admin access) // fn se_debug_privilege(&mut self, event_data: HashMap) { - if let Some(privileage_list) = event_data.get("PrivilegeList") { if let Some(_data) = privileage_list.find("SeDebugPrivilege") { // alert_all_adminが有効であれば、標準出力して知らせる @@ -72,10 +71,8 @@ impl Security { event_data["SubjectUserSid"].to_string(), sid[&event_data["SubjectUserSid"]] + 1, ); - self.admin_logons.insert( - event_data["SubjectUserName"].to_string(), - count_hash, - ); + self.admin_logons + .insert(event_data["SubjectUserName"].to_string(), count_hash); } } None => { diff --git a/src/detections/utils.rs b/src/detections/utils.rs new file mode 100644 index 00000000..608e9010 --- /dev/null +++ b/src/detections/utils.rs @@ -0,0 +1,57 @@ +extern crate csv; +use std::env; +use std::fs::File; +use std::io::prelude::*; +use std::string::String; + +pub fn check_command() {} + +fn check_regex(string: &str, r#type: &str) -> std::string::String { + let mut f = File::open("regexes.txt").expect("file not found"); + let mut contents = String::new(); + f.read_to_string(&mut contents); + + let mut rdr = csv::Reader::from_reader(contents.as_bytes()); + + let mut regextext = "".to_string(); + for regex in rdr.records() { + match regex { + /* + data[0] is type. + data[1] is regex. + data[2] is string. + */ + Ok(_data) => { + if &_data[0] == r#type && &_data[1] == string { + regextext.push_str(&_data[2]); + regextext.push_str("\n"); + } + } + Err(_data) => (), + } + } + return regextext; +} + +fn check_creator(command: &str, creator: &str) -> std::string::String { + let mut creatortext = "".to_string(); + if (!creator.is_empty()) { + if (command == "powershell") { + if (creator == "PSEXESVC") { + creatortext.push_str("PowerShell launched via PsExec: $creator\n"); + } else if (creator == "WmiPrvSE") { + creatortext.push_str("PowerShell launched via WMI: $creator\n"); + } + } + } + return creatortext; +} + +#[cfg(test)] +mod tests { + use crate::detections::utils; + #[test] + fn test_check_regex() { + let result = utils::check_regex("test", "0"); + } +} From acf8f8d022813101ad7d43ddeee62583e955eb06 Mon Sep 17 00:00:00 2001 From: Kazuminn Date: Fri, 2 Oct 2020 23:26:07 +0900 Subject: [PATCH 02/14] add check_obfu() --- src/detections/utils.rs | 49 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/src/detections/utils.rs b/src/detections/utils.rs index 608e9010..92646e18 100644 --- a/src/detections/utils.rs +++ b/src/detections/utils.rs @@ -1,10 +1,53 @@ extern crate csv; +extern crate regex; + +use regex::Regex; use std::env; use std::fs::File; use std::io::prelude::*; use std::string::String; -pub fn check_command() {} +fn check_obfu(string: &str) -> std::string::String { + let mut obfutext = "".to_string(); + let mut lowercasestring = string.to_lowercase(); + let mut length = lowercasestring.len(); + let mut minpercent = 0.65; + let mut maxbinary = 0.50; + + let mut re = Regex::new(r"[a-z0-9/\;:|.]").unwrap(); + let mut caps = re.captures(&lowercasestring).unwrap(); + let noalphastring = caps.get(0).unwrap().as_str(); + + re = Regex::new(r"[01]").unwrap(); + caps = re.captures(&lowercasestring).unwrap(); + let mut nobinarystring = caps.get(0).unwrap().as_str(); + + if (length > 0) { + let mut percent = ((length - noalphastring.len()) / length); + if ((length / 100) as f64)< minpercent { + minpercent = (length / 100) as f64; + } + if percent < minpercent as usize { + re = Regex::new(r"{0:P0}").unwrap(); + let percent = &percent.to_string(); + let caps = re.captures(percent).unwrap(); + obfutext.push_str("Possible command obfuscation: only "); + obfutext.push_str(caps.get(0).unwrap().as_str()); + obfutext.push_str("alphanumeric and common symbols\n"); + } + percent = ((nobinarystring.len() - length / length) /length); + let mut binarypercent = 1 - percent; + if binarypercent > maxbinary as usize { + re = Regex::new(r"{0:P0}").unwrap(); + let binarypercent = &binarypercent.to_string(); + let caps = re.captures(binarypercent).unwrap(); + obfutext.push_str("Possible command obfuscation: "); + obfutext.push_str(caps.get(0).unwrap().as_str()); + obfutext.push_str("zeroes and ones (possible numeric or binary encoding)\n"); + } + } + return obfutext; +} fn check_regex(string: &str, r#type: &str) -> std::string::String { let mut f = File::open("regexes.txt").expect("file not found"); @@ -52,6 +95,8 @@ mod tests { use crate::detections::utils; #[test] fn test_check_regex() { - let result = utils::check_regex("test", "0"); + let creatortext = + utils::check_regex("^cmd.exe /c echo [a-z]{6} > \\\\.\\pipe\\[a-z]{6}$", "0"); + println!("{}", creatortext); } } From bb2d4bc5374cfcb88b06dd61a9d19fb1b6245170 Mon Sep 17 00:00:00 2001 From: Kazuminn Date: Sat, 3 Oct 2020 13:06:25 +0900 Subject: [PATCH 03/14] add check_command() --- Cargo.lock | 29 ++++++++++++ Cargo.toml | 2 + src/detections/utils.rs | 102 +++++++++++++++++++++++++++++++++++----- whitelist.txt | 3 ++ 4 files changed, 125 insertions(+), 11 deletions(-) create mode 100644 whitelist.txt diff --git a/Cargo.lock b/Cargo.lock index ba27fb23..6b63ca2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,6 +70,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + [[package]] name = "bitflags" version = "1.2.1" @@ -184,6 +190,15 @@ dependencies = [ "build_const", ] +[[package]] +name = "crc32fast" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-channel" version = "0.4.4" @@ -389,6 +404,18 @@ dependencies = [ "winstructs", ] +[[package]] +name = "flate2" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da80be589a72651dcda34d8b35bcdc9b7254ad06325611074d9cc0fbb19f60ee" +dependencies = [ + "cfg-if", + "crc32fast", + "libc", + "miniz_oxide", +] + [[package]] name = "fs_extra" version = "1.2.0" @@ -1150,9 +1177,11 @@ dependencies = [ name = "yamato_event_analyzer" version = "0.1.0" dependencies = [ + "base64", "clap", "csv", "evtx", + "flate2", "quick-xml 0.17.2", "regex", "serde", diff --git a/Cargo.toml b/Cargo.toml index 406ca26c..863e7911 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,5 @@ serde_json = { version = "1.0"} clap = "*" regex = "1" csv = "1.1" +base64 = "*" +flate2 = "1.0" \ No newline at end of file diff --git a/src/detections/utils.rs b/src/detections/utils.rs index 92646e18..42090dda 100644 --- a/src/detections/utils.rs +++ b/src/detections/utils.rs @@ -1,12 +1,95 @@ +extern crate base64; extern crate csv; extern crate regex; +use flate2::read::GzDecoder; use regex::Regex; use std::env; use std::fs::File; use std::io::prelude::*; +use std::io::prelude::*; +use std::str; use std::string::String; +pub fn check_command( + event_id: usize, + commandline: &str, + minlength: usize, + servicecmd: usize, + servicename: &str, + creator: &str, +) { + let mut text = "".to_string(); + let mut base64 = "".to_string(); + + let mut f = File::open("whitelist.txt").expect("file not found"); + let mut contents = String::new(); + f.read_to_string(&mut contents); + + let mut rdr = csv::Reader::from_reader(contents.as_bytes()); + + for entry in rdr.records() { + if let Ok(_data) = entry { + if (commandline == &_data[0]) { + return; + } + } + } + if commandline.len() > minlength { + text.push_str("Long Command Line: greater than "); + text.push_str(&minlength.to_string()); + text.push_str("bytes\n"); + } + text.push_str(&check_obfu(commandline)); + text.push_str(&check_regex(commandline, 0)); + text.push_str(&check_creator(commandline, creator)); + if (Regex::new(r"\-enc.*[A-Za-z0-9/+=]{100}") + .unwrap() + .is_match(commandline)) + { + let re = Regex::new(r"^.* \-Enc(odedCommand)? ").unwrap(); + base64.push_str(&re.replace_all(commandline, "")); + } else if (Regex::new(r":FromBase64String\(") + .unwrap() + .is_match(commandline)) + { + let re = Regex::new(r"^^.*:FromBase64String\(\'*").unwrap(); + base64.push_str(&re.replace_all(commandline, "")); + let re = Regex::new(r"\'.*$").unwrap(); + base64.push_str(&re.replace_all(&base64.to_string(), "")); + } + if (!base64.is_empty()) { + if (Regex::new(r"Compression.GzipStream.*Decompress") + .unwrap() + .is_match(commandline)) + { + let decoded = base64::decode(base64).unwrap(); + let mut d = GzDecoder::new(decoded.as_slice()); + let mut uncompressed = String::new(); + d.read_to_string(&mut uncompressed).unwrap(); + println!("Decoded : {}", uncompressed); + text.push_str("Base64-encoded and compressed function\n"); + } else { + let decoded = base64::decode(base64).unwrap(); + println!("Decoded : {}", str::from_utf8(decoded.as_slice()).unwrap()); + text.push_str("Base64-encoded function\n"); + text.push_str(&check_obfu(str::from_utf8(decoded.as_slice()).unwrap())); + text.push_str(&check_regex(str::from_utf8(decoded.as_slice()).unwrap(), 0)); + } + } + if !text.is_empty() { + if servicecmd != 0 { + println!("Message : Suspicious Service Command"); + println!("Results : Service name: {}\n", servicename); + } else { + println!("Message : Suspicious Command Line"); + } + println!("command : {}", commandline); + println!("result : {}", text); + println!("EventID : {}", event_id); + } +} + fn check_obfu(string: &str) -> std::string::String { let mut obfutext = "".to_string(); let mut lowercasestring = string.to_lowercase(); @@ -24,7 +107,7 @@ fn check_obfu(string: &str) -> std::string::String { if (length > 0) { let mut percent = ((length - noalphastring.len()) / length); - if ((length / 100) as f64)< minpercent { + if ((length / 100) as f64) < minpercent { minpercent = (length / 100) as f64; } if percent < minpercent as usize { @@ -32,10 +115,10 @@ fn check_obfu(string: &str) -> std::string::String { let percent = &percent.to_string(); let caps = re.captures(percent).unwrap(); obfutext.push_str("Possible command obfuscation: only "); - obfutext.push_str(caps.get(0).unwrap().as_str()); + obfutext.push_str(caps.get(0).unwrap().as_str()); obfutext.push_str("alphanumeric and common symbols\n"); } - percent = ((nobinarystring.len() - length / length) /length); + percent = ((nobinarystring.len() - length / length) / length); let mut binarypercent = 1 - percent; if binarypercent > maxbinary as usize { re = Regex::new(r"{0:P0}").unwrap(); @@ -49,7 +132,7 @@ fn check_obfu(string: &str) -> std::string::String { return obfutext; } -fn check_regex(string: &str, r#type: &str) -> std::string::String { +fn check_regex(string: &str, r#type: usize) -> std::string::String { let mut f = File::open("regexes.txt").expect("file not found"); let mut contents = String::new(); f.read_to_string(&mut contents); @@ -58,19 +141,16 @@ fn check_regex(string: &str, r#type: &str) -> std::string::String { let mut regextext = "".to_string(); for regex in rdr.records() { - match regex { + if let Ok(_data) = regex { /* data[0] is type. data[1] is regex. data[2] is string. */ - Ok(_data) => { - if &_data[0] == r#type && &_data[1] == string { - regextext.push_str(&_data[2]); - regextext.push_str("\n"); - } + if &_data[0] == r#type.to_string() && &_data[1] == string { + regextext.push_str(&_data[2]); + regextext.push_str("\n"); } - Err(_data) => (), } } return regextext; diff --git a/whitelist.txt b/whitelist.txt new file mode 100644 index 00000000..a6165f49 --- /dev/null +++ b/whitelist.txt @@ -0,0 +1,3 @@ +regex +^"C:\\Program Files\\Google\\Chrome\\Application\\chrome\.exe" +^"C:\\Program Files\\Google\\Update\\GoogleUpdate\.exe" From 6d8e0a61d2b3efec86c2c7e17af077fda31c2bbb Mon Sep 17 00:00:00 2001 From: Kazuminn Date: Sat, 3 Oct 2020 16:52:39 +0900 Subject: [PATCH 04/14] test 2 pass --- src/detections/utils.rs | 82 +++++++++++++++++++++++++++++++---------- 1 file changed, 62 insertions(+), 20 deletions(-) diff --git a/src/detections/utils.rs b/src/detections/utils.rs index 42090dda..dd695882 100644 --- a/src/detections/utils.rs +++ b/src/detections/utils.rs @@ -97,13 +97,21 @@ fn check_obfu(string: &str) -> std::string::String { let mut minpercent = 0.65; let mut maxbinary = 0.50; - let mut re = Regex::new(r"[a-z0-9/\;:|.]").unwrap(); - let mut caps = re.captures(&lowercasestring).unwrap(); - let noalphastring = caps.get(0).unwrap().as_str(); + let mut re = Regex::new(r"[a-z0-9/¥;:|.]").unwrap(); + let mut noalphastring = ""; + if let Some(_caps) = re.captures(&lowercasestring) { + if let Some(_data) = _caps.get(0) { + noalphastring = _data.as_str(); + } + } re = Regex::new(r"[01]").unwrap(); - caps = re.captures(&lowercasestring).unwrap(); - let mut nobinarystring = caps.get(0).unwrap().as_str(); + let mut nobinarystring = ""; + if let Some(_caps) = re.captures(&lowercasestring) { + if let Some(_data) = _caps.get(0) { + nobinarystring = _data.as_str(); + } + } if (length > 0) { let mut percent = ((length - noalphastring.len()) / length); @@ -111,21 +119,31 @@ fn check_obfu(string: &str) -> std::string::String { minpercent = (length / 100) as f64; } if percent < minpercent as usize { + obfutext.push_str("Possible command obfuscation: only "); + re = Regex::new(r"{0:P0}").unwrap(); let percent = &percent.to_string(); - let caps = re.captures(percent).unwrap(); - obfutext.push_str("Possible command obfuscation: only "); - obfutext.push_str(caps.get(0).unwrap().as_str()); + if let Some(_caps) = re.captures(percent) { + if let Some(_data) = _caps.get(0) { + obfutext.push_str(_data.as_str()); + } + } + obfutext.push_str("alphanumeric and common symbols\n"); } percent = ((nobinarystring.len() - length / length) / length); let mut binarypercent = 1 - percent; if binarypercent > maxbinary as usize { + obfutext.push_str("Possible command obfuscation: "); + re = Regex::new(r"{0:P0}").unwrap(); let binarypercent = &binarypercent.to_string(); - let caps = re.captures(binarypercent).unwrap(); - obfutext.push_str("Possible command obfuscation: "); - obfutext.push_str(caps.get(0).unwrap().as_str()); + if let Some(_caps) = re.captures(binarypercent) { + if let Some(_data) = _caps.get(0) { + obfutext.push_str(_data.as_str()); + } + } + obfutext.push_str("zeroes and ones (possible numeric or binary encoding)\n"); } } @@ -143,13 +161,17 @@ fn check_regex(string: &str, r#type: usize) -> std::string::String { for regex in rdr.records() { if let Ok(_data) = regex { /* - data[0] is type. - data[1] is regex. - data[2] is string. + data[0] is type in csv. + data[1] is regex in csv. + data[2] is string in csv. */ - if &_data[0] == r#type.to_string() && &_data[1] == string { - regextext.push_str(&_data[2]); - regextext.push_str("\n"); + if &_data[0] == r#type.to_string() { + if let Ok(_re) = Regex::new(&_data[1]) { + if _re.is_match(string) { + regextext.push_str(&_data[2]); + regextext.push_str("\n"); + } + } } } } @@ -175,8 +197,28 @@ mod tests { use crate::detections::utils; #[test] fn test_check_regex() { - let creatortext = - utils::check_regex("^cmd.exe /c echo [a-z]{6} > \\\\.\\pipe\\[a-z]{6}$", "0"); - println!("{}", creatortext); + let regextext = utils::check_regex( + "Metasploit-style cmd with pipe (possible use of Meterpreter 'getsystem')", + 0, + ); + println!("{}", regextext); + } + + #[test] + fn test_check_creator() { + let mut creatortext = utils::check_creator("powershell", "PSEXESVC"); + assert!(creatortext == "PowerShell launched via PsExec: $creator\n"); + creatortext = utils::check_creator("powershell", "WmiPrvSE"); + assert!(creatortext == "PowerShell launched via WMI: $creator\n"); + } + + #[test] + fn test_check_obfu() { + let mut obfutext = utils::check_obfu("dir01"); + } + + #[test] + fn test_check_command() { + utils::check_command(1, "dir", 100, 100, "dir", "dir"); } } From 927df3f32a855f912353f7e5bbb9486c0cb265da Mon Sep 17 00:00:00 2001 From: Kazuminn Date: Sat, 3 Oct 2020 17:34:37 +0900 Subject: [PATCH 05/14] check_regex test ok --- src/detections/utils.rs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/detections/utils.rs b/src/detections/utils.rs index dd695882..b96fc66b 100644 --- a/src/detections/utils.rs +++ b/src/detections/utils.rs @@ -131,8 +131,8 @@ fn check_obfu(string: &str) -> std::string::String { obfutext.push_str("alphanumeric and common symbols\n"); } - percent = ((nobinarystring.len() - length / length) / length); - let mut binarypercent = 1 - percent; + percent = ((nobinarystring.len().wrapping_sub(length) / length) / length); + let mut binarypercent = 1_usize.wrapping_sub(percent); if binarypercent > maxbinary as usize { obfutext.push_str("Possible command obfuscation: "); @@ -183,9 +183,13 @@ fn check_creator(command: &str, creator: &str) -> std::string::String { if (!creator.is_empty()) { if (command == "powershell") { if (creator == "PSEXESVC") { - creatortext.push_str("PowerShell launched via PsExec: $creator\n"); + creatortext.push_str("PowerShell launched via PsExec: "); + creatortext.push_str(creator); + creatortext.push_str("\n"); } else if (creator == "WmiPrvSE") { - creatortext.push_str("PowerShell launched via WMI: $creator\n"); + creatortext.push_str("PowerShell launched via WMI: "); + creatortext.push_str(creator); + creatortext.push_str("\n"); } } } @@ -198,23 +202,27 @@ mod tests { #[test] fn test_check_regex() { let regextext = utils::check_regex( - "Metasploit-style cmd with pipe (possible use of Meterpreter 'getsystem')", + "\\cvtres.exe", 0, ); println!("{}", regextext); + assert!( + regextext == "Resource File To COFF Object Conversion Utility cvtres.exe\n" + ); } #[test] fn test_check_creator() { let mut creatortext = utils::check_creator("powershell", "PSEXESVC"); - assert!(creatortext == "PowerShell launched via PsExec: $creator\n"); + assert!(creatortext == "PowerShell launched via PsExec: PSEXESVC\n"); creatortext = utils::check_creator("powershell", "WmiPrvSE"); - assert!(creatortext == "PowerShell launched via WMI: $creator\n"); + assert!(creatortext == "PowerShell launched via WMI: WmiPrvSE\n"); } #[test] fn test_check_obfu() { let mut obfutext = utils::check_obfu("dir01"); + println!("{}", obfutext); } #[test] From 5071aa0783bb0872f1a69bf29b3f7d7377e7a303 Mon Sep 17 00:00:00 2001 From: Kazuminn Date: Sat, 3 Oct 2020 17:55:08 +0900 Subject: [PATCH 06/14] all test passed --- src/detections/utils.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/detections/utils.rs b/src/detections/utils.rs index b96fc66b..0dad0544 100644 --- a/src/detections/utils.rs +++ b/src/detections/utils.rs @@ -136,7 +136,7 @@ fn check_obfu(string: &str) -> std::string::String { if binarypercent > maxbinary as usize { obfutext.push_str("Possible command obfuscation: "); - re = Regex::new(r"{0:P0}").unwrap(); + re = Regex::new(r"\{0:P0}").unwrap(); let binarypercent = &binarypercent.to_string(); if let Some(_caps) = re.captures(binarypercent) { if let Some(_data) = _caps.get(0) { @@ -201,14 +201,8 @@ mod tests { use crate::detections::utils; #[test] fn test_check_regex() { - let regextext = utils::check_regex( - "\\cvtres.exe", - 0, - ); - println!("{}", regextext); - assert!( - regextext == "Resource File To COFF Object Conversion Utility cvtres.exe\n" - ); + let regextext = utils::check_regex("\\cvtres.exe", 0); + assert!(regextext == "Resource File To COFF Object Conversion Utility cvtres.exe\n"); } #[test] @@ -222,7 +216,6 @@ mod tests { #[test] fn test_check_obfu() { let mut obfutext = utils::check_obfu("dir01"); - println!("{}", obfutext); } #[test] From fb4ee59deea335d439f360008c8058657b19c596 Mon Sep 17 00:00:00 2001 From: Kazuminn Date: Sat, 3 Oct 2020 17:58:43 +0900 Subject: [PATCH 07/14] refactor --- src/detections/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/detections/utils.rs b/src/detections/utils.rs index 0dad0544..ec1c4877 100644 --- a/src/detections/utils.rs +++ b/src/detections/utils.rs @@ -121,7 +121,7 @@ fn check_obfu(string: &str) -> std::string::String { if percent < minpercent as usize { obfutext.push_str("Possible command obfuscation: only "); - re = Regex::new(r"{0:P0}").unwrap(); + re = Regex::new(r"\{0:P0}").unwrap(); let percent = &percent.to_string(); if let Some(_caps) = re.captures(percent) { if let Some(_data) = _caps.get(0) { From d5fba5e54b547700b2eceb645980287c66bfcaff Mon Sep 17 00:00:00 2001 From: Kazuminn Date: Sat, 3 Oct 2020 19:40:40 +0900 Subject: [PATCH 08/14] fix test --- src/detections/utils.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/detections/utils.rs b/src/detections/utils.rs index ec1c4877..b23c108a 100644 --- a/src/detections/utils.rs +++ b/src/detections/utils.rs @@ -216,6 +216,7 @@ mod tests { #[test] fn test_check_obfu() { let mut obfutext = utils::check_obfu("dir01"); + assert!(obfutext == "Possible command obfuscation: zeroes and ones (possible numeric or binary encoding)\n"); } #[test] From 61049ce9a8568ff2d3d686adadeed59c743f5f6c Mon Sep 17 00:00:00 2001 From: Kazuminn Date: Sat, 3 Oct 2020 19:52:04 +0900 Subject: [PATCH 09/14] refactor --- src/detections/utils.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/detections/utils.rs b/src/detections/utils.rs index b23c108a..f759664a 100644 --- a/src/detections/utils.rs +++ b/src/detections/utils.rs @@ -7,7 +7,6 @@ use regex::Regex; use std::env; use std::fs::File; use std::io::prelude::*; -use std::io::prelude::*; use std::str; use std::string::String; From 6d57923ff2ee3d13420cd04f027ed119fd3aaef5 Mon Sep 17 00:00:00 2001 From: Kazuminn Date: Sat, 3 Oct 2020 20:04:21 +0900 Subject: [PATCH 10/14] refactor --- src/detections/utils.rs | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/detections/utils.rs b/src/detections/utils.rs index f759664a..02440dc6 100644 --- a/src/detections/utils.rs +++ b/src/detections/utils.rs @@ -4,7 +4,6 @@ extern crate regex; use flate2::read::GzDecoder; use regex::Regex; -use std::env; use std::fs::File; use std::io::prelude::*; use std::str; @@ -42,25 +41,25 @@ pub fn check_command( text.push_str(&check_obfu(commandline)); text.push_str(&check_regex(commandline, 0)); text.push_str(&check_creator(commandline, creator)); - if (Regex::new(r"\-enc.*[A-Za-z0-9/+=]{100}") + if Regex::new(r"\-enc.*[A-Za-z0-9/+=]{100}") .unwrap() - .is_match(commandline)) + .is_match(commandline) { let re = Regex::new(r"^.* \-Enc(odedCommand)? ").unwrap(); base64.push_str(&re.replace_all(commandline, "")); - } else if (Regex::new(r":FromBase64String\(") + } else if Regex::new(r":FromBase64String\(") .unwrap() - .is_match(commandline)) + .is_match(commandline) { let re = Regex::new(r"^^.*:FromBase64String\(\'*").unwrap(); base64.push_str(&re.replace_all(commandline, "")); let re = Regex::new(r"\'.*$").unwrap(); base64.push_str(&re.replace_all(&base64.to_string(), "")); } - if (!base64.is_empty()) { - if (Regex::new(r"Compression.GzipStream.*Decompress") + if !base64.is_empty() { + if Regex::new(r"Compression.GzipStream.*Decompress") .unwrap() - .is_match(commandline)) + .is_match(commandline) { let decoded = base64::decode(base64).unwrap(); let mut d = GzDecoder::new(decoded.as_slice()); @@ -91,10 +90,10 @@ pub fn check_command( fn check_obfu(string: &str) -> std::string::String { let mut obfutext = "".to_string(); - let mut lowercasestring = string.to_lowercase(); - let mut length = lowercasestring.len(); + let lowercasestring = string.to_lowercase(); + let length = lowercasestring.len(); let mut minpercent = 0.65; - let mut maxbinary = 0.50; + let maxbinary = 0.50; let mut re = Regex::new(r"[a-z0-9/¥;:|.]").unwrap(); let mut noalphastring = ""; @@ -112,8 +111,8 @@ fn check_obfu(string: &str) -> std::string::String { } } - if (length > 0) { - let mut percent = ((length - noalphastring.len()) / length); + if length > 0 { + let mut percent = (length - noalphastring.len()) / length; if ((length / 100) as f64) < minpercent { minpercent = (length / 100) as f64; } @@ -130,8 +129,8 @@ fn check_obfu(string: &str) -> std::string::String { obfutext.push_str("alphanumeric and common symbols\n"); } - percent = ((nobinarystring.len().wrapping_sub(length) / length) / length); - let mut binarypercent = 1_usize.wrapping_sub(percent); + percent = (nobinarystring.len().wrapping_sub(length) / length) / length; + let binarypercent = 1_usize.wrapping_sub(percent); if binarypercent > maxbinary as usize { obfutext.push_str("Possible command obfuscation: "); @@ -179,13 +178,13 @@ fn check_regex(string: &str, r#type: usize) -> std::string::String { fn check_creator(command: &str, creator: &str) -> std::string::String { let mut creatortext = "".to_string(); - if (!creator.is_empty()) { - if (command == "powershell") { - if (creator == "PSEXESVC") { + if !creator.is_empty() { + if command == "powershell" { + if creator == "PSEXESVC" { creatortext.push_str("PowerShell launched via PsExec: "); creatortext.push_str(creator); creatortext.push_str("\n"); - } else if (creator == "WmiPrvSE") { + } else if creator == "WmiPrvSE" { creatortext.push_str("PowerShell launched via WMI: "); creatortext.push_str(creator); creatortext.push_str("\n"); @@ -214,7 +213,7 @@ mod tests { #[test] fn test_check_obfu() { - let mut obfutext = utils::check_obfu("dir01"); + let obfutext = utils::check_obfu("dir01"); assert!(obfutext == "Possible command obfuscation: zeroes and ones (possible numeric or binary encoding)\n"); } From 7242dfbc1bb7f90b791973571d21235b78114b07 Mon Sep 17 00:00:00 2001 From: Kazuminn Date: Sat, 3 Oct 2020 20:07:45 +0900 Subject: [PATCH 11/14] refactor --- src/detections/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/detections/utils.rs b/src/detections/utils.rs index 02440dc6..b50a0db9 100644 --- a/src/detections/utils.rs +++ b/src/detections/utils.rs @@ -28,7 +28,7 @@ pub fn check_command( for entry in rdr.records() { if let Ok(_data) = entry { - if (commandline == &_data[0]) { + if commandline == &_data[0] { return; } } From e3631abeb3dae12a6491f15bd28979ca95b049bc Mon Sep 17 00:00:00 2001 From: Kazuminn Date: Sun, 4 Oct 2020 16:13:26 +0900 Subject: [PATCH 12/14] =?UTF-8?q?add=20test=20:=20white=20list=E3=81=A8?= =?UTF-8?q?=E3=83=9E=E3=83=83=E3=83=81=E3=81=99=E3=82=8B=E6=99=82=E3=81=AF?= =?UTF-8?q?=E3=80=81=E3=81=99=E3=81=90=E3=81=ABreturn=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/detections/utils.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/detections/utils.rs b/src/detections/utils.rs index b50a0db9..04b53b76 100644 --- a/src/detections/utils.rs +++ b/src/detections/utils.rs @@ -28,8 +28,10 @@ pub fn check_command( for entry in rdr.records() { if let Ok(_data) = entry { - if commandline == &_data[0] { - return; + if let Ok(_re) = Regex::new(&_data[0]) { + if _re.is_match(commandline) { + return; + } } } } @@ -220,5 +222,13 @@ mod tests { #[test] fn test_check_command() { utils::check_command(1, "dir", 100, 100, "dir", "dir"); + utils::check_command( + 1, + "\"C:\\Program Files\\Google\\Update\\GoogleUpdate.exe\"", + 100, + 100, + "dir", + "dir", + ); } } From 3e3f7bc51ed7d168c5ac9b59366e1205010f0163 Mon Sep 17 00:00:00 2001 From: Kazuminn Date: Sun, 4 Oct 2020 17:07:09 +0900 Subject: [PATCH 13/14] =?UTF-8?q?fix=20:=E3=82=B3=E3=83=A1=E3=83=B3?= =?UTF-8?q?=E3=83=88=E3=81=A7=E6=8C=87=E6=91=98=E3=81=95=E3=82=8C=E3=81=9F?= =?UTF-8?q?=E3=81=A8=E3=81=93=E3=82=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/detections/utils.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/detections/utils.rs b/src/detections/utils.rs index 04b53b76..db42652a 100644 --- a/src/detections/utils.rs +++ b/src/detections/utils.rs @@ -16,16 +16,11 @@ pub fn check_command( servicecmd: usize, servicename: &str, creator: &str, + mut rdr: csv::Reader<&[u8]>, ) { let mut text = "".to_string(); let mut base64 = "".to_string(); - let mut f = File::open("whitelist.txt").expect("file not found"); - let mut contents = String::new(); - f.read_to_string(&mut contents); - - let mut rdr = csv::Reader::from_reader(contents.as_bytes()); - for entry in rdr.records() { if let Ok(_data) = entry { if let Ok(_re) = Regex::new(&_data[0]) { @@ -199,6 +194,8 @@ fn check_creator(command: &str, creator: &str) -> std::string::String { #[cfg(test)] mod tests { use crate::detections::utils; + use std::fs::File; + use std::io::Read; #[test] fn test_check_regex() { let regextext = utils::check_regex("\\cvtres.exe", 0); @@ -221,7 +218,14 @@ mod tests { #[test] fn test_check_command() { - utils::check_command(1, "dir", 100, 100, "dir", "dir"); + let mut f = File::open("whitelist.txt").expect("file not found"); + let mut contents = String::new(); + f.read_to_string(&mut contents); + + let rdr = csv::Reader::from_reader(contents.as_bytes()); + utils::check_command(1, "dir", 100, 100, "dir", "dir", rdr); + + let rdr = csv::Reader::from_reader(contents.as_bytes()); utils::check_command( 1, "\"C:\\Program Files\\Google\\Update\\GoogleUpdate.exe\"", @@ -229,6 +233,7 @@ mod tests { 100, "dir", "dir", + rdr, ); } } From 9cab0bb34333a4ec9583c56af2cd3f1ce465fb8b Mon Sep 17 00:00:00 2001 From: Kazuminn Date: Sun, 4 Oct 2020 17:15:08 +0900 Subject: [PATCH 14/14] add comment --- src/detections/utils.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/detections/utils.rs b/src/detections/utils.rs index db42652a..9a2ffdc3 100644 --- a/src/detections/utils.rs +++ b/src/detections/utils.rs @@ -226,6 +226,7 @@ mod tests { utils::check_command(1, "dir", 100, 100, "dir", "dir", rdr); let rdr = csv::Reader::from_reader(contents.as_bytes()); + //test return with whitelist. utils::check_command( 1, "\"C:\\Program Files\\Google\\Update\\GoogleUpdate.exe\"",