diff --git a/Cargo.toml b/Cargo.toml index c34b555..f4aa716 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,6 @@ name = "windows-artifacts-generator" version = "1.0.0" edition = "2021" -rust-version = "1.74.1" description = "Generate malware artifacts for detection tests" documentation = "https://frack113.github.io/WAG/" repository = "https://github.com/frack113/WAG/" @@ -19,6 +18,7 @@ keywords = [ "rust", ] categories = ["command-line-utilities"] +rust-version = "1.80.1" [[bin]] name = "wag" diff --git a/docs/Artefacts.md b/docs/Artefacts.md deleted file mode 100644 index 75aa07c..0000000 --- a/docs/Artefacts.md +++ /dev/null @@ -1,178 +0,0 @@ - - -# Artefact list - -- [Sysmon V15 Artefact](#sysmon-v15-artefact) - - [Process creation (1)](#process-creation-1) - - [process changed a file creation time (2)](#process-changed-a-file-creation-time-2) - - [Network connection (3)](#network-connection-3) - - [Sysmon service state changed (4)](#sysmon-service-state-changed-4) - - [Process terminated (5)](#process-terminated-5) - - [Driver loaded (6)](#driver-loaded-6) - - [Image loaded (7)](#image-loaded-7) - - [CreateRemoteThread (8)](#createremotethread-8) - - [RawAccessRead (9)](#rawaccessread-9) - - [ProcessAccess (10)](#processaccess-10) - - [FileCreate (11)](#filecreate-11) - - [RegistryEvent (12,13,14)](#registryevent-121314) - - [FileCreateStreamHash (15)](#filecreatestreamhash-15) - - [ServiceConfigurationChange (16)](#serviceconfigurationchange-16) - - [PipeEvent (17,18)](#pipeevent-1718) - - [WmiEvent (19,20,21)](#wmievent-192021) - - [DNSEvent (22)](#dnsevent-22) - - [FileDelete (23)](#filedelete-23) - - [ClipboardChange (24)](#clipboardchange-24) - - [ProcessTampering (25)](#processtampering-25) - - [FileDeleteDetected (26)](#filedeletedetected-26) - - [FileBlockExecutable (27)](#fileblockexecutable-27) - - [FileBlockShredding (28)](#fileblockshredding-28) - - [FileExecutableDetected (29)](#fileexecutabledetected-29) - - [Error (255)](#error-255) -- [Windows builtin Channel](#windows-builtin-channel) - -# Sysmon V15 Artefact - -- ✔ Wag can create artefact -- ✖ Wag will not create artefact - -❓ Need to be check - -| EventID | Description | Cover by wag | -| ------- | ----------------------------------------------------- | ------------ | -| 1 | Process creation | ✖ | -| 2 | process changed a file creation time | ❓ | -| 3 | Network connection | ✖ | -| 4 | Sysmon service state changed | ✖ | -| 5 | Process terminated | ✖ | -| 6 | Driver loaded | ✔ | -| 7 | Image loaded | ❓ | -| 8 | CreateRemoteThread | ❓ | -| 9 | RawAccessRead | ❓ | -| 10 | ProcessAccess | ❓ | -| 11 | FileCreate | ✔ | -| 12 | RegistryEvent (Object create and delete) | ✖ | -| 13 | RegistryEvent (Value Set) | ✖ | -| 14 | RegistryEvent (Key and Value Rename) | ✖ | -| 15 | FileCreateStreamHash | ✔ | -| 16 | ServiceConfigurationChange | ✖ | -| 17 | PipeEvent (Pipe Created) | ✔ | -| 18 | PipeEvent (Pipe Connected) | ❓ | -| 19 | WmiEvent (WmiEventFilter activity detected) | ❓ | -| 20 | WmiEvent (WmiEventConsumer activity detected) | ❓ | -| 21 | WmiEvent (WmiEventConsumerToFilter activity detected) | ❓ | -| 22 | DNSEvent (DNS query) | ✖ | -| 23 | FileDelete (File Delete archived) | ❓ | -| 24 | ClipboardChange (New content in the clipboard) | ❓ | -| 25 | ProcessTampering (Process image change) | ❓ | -| 26 | FileDeleteDetected (File Delete logged) | ❓ | -| 27 | FileBlockExecutable | ❓ | -| 28 | FileBlockShredding | ❓ | -| 29 | FileExecutableDetected | ❓ | -| 255 | Error | ✖ | - -## Process creation (1) - -Cover by other tools like Atomic RedTeam - -## process changed a file creation time (2) - -Need to see its usefulness - -## Network connection (3) - -Cover by other tools like Atomic RedTeam - -## Sysmon service state changed (4) - -Need to see its usefulness - -## Process terminated (5) - -Cover by other tools like Atomic RedTeam - -## Driver loaded (6) - -Done by the option X - -## Image loaded (7) - -Need to see its usefulness - -## CreateRemoteThread (8) - -Need to see its usefulness - -## RawAccessRead (9) - -Need to see its usefulness - -## ProcessAccess (10) - -Need to see its usefulness - -## FileCreate (11) - -Done by the option X - -## RegistryEvent (12,13,14) - -Cover by other tools like Atomic RedTeam - -## FileCreateStreamHash (15) - -Done but get a bug when in Sysmon to validate - -## ServiceConfigurationChange (16) - -Need to see its usefulness - -## PipeEvent (17,18) - -Only Pipe Created , no Pipe Connected - -## WmiEvent (19,20,21) - -Need to see its usefulness - -## DNSEvent (22) - -Cover by other tools like Atomic RedTeam - -## FileDelete (23) - -Need to see its usefulness - -## ClipboardChange (24) - -Need to see its usefulness - -## ProcessTampering (25) - -Need to see its usefulness - -## FileDeleteDetected (26) - -Need to see its usefulness - -## FileBlockExecutable (27) - -Need to see its usefulness - -## FileBlockShredding (28) - -Need to see its usefulness - -## FileExecutableDetected (29) - -Need to see its usefulness - -## Error (255) - -Need to see its usefulness - -# Windows builtin Channel - -- code_integrity when use driver option diff --git a/docs/cli_help.md b/docs/cli_help.md deleted file mode 100644 index 07bcb0c..0000000 --- a/docs/cli_help.md +++ /dev/null @@ -1,109 +0,0 @@ - - -# Ads - -`wag ads -f fullpath -a ads -d data` - -- fullpath: regex of the full path -- ads: name of the stream -- data: base64 of the data to write - -| Type | ads | data | -| -------------- | --------------- | -------------------------------------------------------------------------------------------- | -| ZoneTransfer 0 | Zone.Identifier | W1pvbmVUcmFuc2Zlcl0NClpvbmVJZD0wDQpSZWZlcnJlclVybD1jOlx3aW5kb3dzXHdhZy56aXANCg== | -| ZoneTransfer 1 | Zone.Identifier | W1pvbmVUcmFuc2Zlcl0NClpvbmVJZD0xDQpSZWZlcnJlclVybD0vL3N2cl9BRC93YWcuemlwDQo= | -| ZoneTransfer 2 | Zone.Identifier | W1pvbmVUcmFuc2Zlcl0NClpvbmVJZD0yDQpSZWZlcnJlclVybD1odHRwOi8vbXlzaXRlLm9yZy93YWcuemlwDQo= | -| ZoneTransfer 3 | Zone.Identifier | W1pvbmVUcmFuc2Zlcl0NClpvbmVJZD0zDQpSZWZlcnJlclVybD1odHRwczovL3NvbWVzaXRlLmNvbS93YWcuemlwDQo= | -| ZoneTransfer 4 | Zone.Identifier | W1pvbmVUcmFuc2Zlcl0NClpvbmVJZD00DQpSZWZlcnJlclVybD1odHRwOi8vbWFsd2FyZS5iYWQvd2FnLnppcA0K | -| Sysmon | sysmon | SSBhbSB0aGUgYmVzdCB0byBoaWRlIGZyb20gc3lzbW9u | - -# File - -## magicbytes - -| Type | Hex | -| ---- | ------------------------------------ | -| Exe | TVo= | -| Zip | UEsDBA== | -| Vmdk | S0RN | -| Iso | Q0QwMDE= | -| Txt | QSBzaW1wbGUgdGV4dCBmaWxl | -| Ps1 | d3JpdGUtaG9zdCAiV0FHIHdhcyBIZXJlIgo= | - -## well known File - -`wag file-create -f fullpath -m Magicbyte_Hex ` - -- fullpath: regex of the full path -- Magicbyte_Hex: base64 of the magicbytes to write -- admin: can use `--admin` to check if run as administrator - -| Type | Admin | Magicbyte | fullpath | -| -------------- | ----- | --------- | ------------------------------------------------------- | -| NPPSpy | true | Exe | `C:/Windows/System32/NPPSpy\.dll` | -| SafetyKatz | false | Zip | _SystemRoot_ + `Temp\\debug\.bin` | -| SmallSieve_txt | false | Txt | _LocalAppData_ + `MicrosoftWindowsOutlookDataPlus\.txt` | -| SmallSieve_exe | false | Exe | _AppData_ + `OutlookMicrosift\\index\.exe` | -| SNAKE_jpsetup | false | Exe | _TEMP_ + `jpsetup\.exe` | -| SNAKE_jpinst | false | Exe | _TEMP_ + `jpinst\\.exe` | -| SNAKE_Comadmin | true | Exe | `C:\\Windows\\System32\\Com\\Comadmin\.dat` | -| COLDSTEEL_exe | false | Exe | `C:\\users\\public\\Documents\\dllhost\.exe` | -| COLDSTEEL_dll | false | Exe | _APPDATA_ + `newdev\.dll` | -| temp_ps1_12 | false | Ps1 | _SystemRoot_ + `temp\[0-9a-f]{12}\.ps1` | - -Remark: You need to convert the environment variable into a correct regular expression. - -# Named pipe - -`wag name-pipe -n name` - -- name: named pipe name as a regex - -| Type | name | -| ------------------ | -------------------------------------------------- | -| CSExec | `\\csexecsvc` | -| psexec | `\\psexec` | -| psexec | `\\PAExec` | -| psexec | `\\remcom` | -| psexec | `\\csexec` | -| psexec | `\\PSEXESVC` | -| Cobal_strike | `\\wkssvc_?[0-9a-f]{2}` | -| Cobal_strike | `\\ntsvcs[0-9a-f]{2}` | -| Cobal_strike | `\\DserNamePipe[0-9a-f]{2}` | -| Cobal_strike | `\\SearchTextHarvester[0-9a-f]{2}` | -| Cobal_strike | `\\windows\\.update\\.manager[0-9a-f]{2,3}` | -| Cobal_strike | `\\ntsvcs_[0-9a-f]{2}` | -| Cobal_strike | `\\scerpc_?[0-9a-f]{2}` | -| Cobal_strike | `\\PGMessagePipe[0-9a-f]{2}` | -| Cobal_strike | `\\MsFteWds[0-9a-f]{2}` | -| Cobal_strike | `\\f4c3[0-9a-f]{2}` | -| Cobal_strike | `\\fullduplex_[0-9a-f]{2}` | -| Cobal_strike | `\\msrpc_[0-9a-f]{4}` | -| Cobal_strike | `\\win\\msrpc_[0-9a-f]{2}` | -| Cobal_strike | `\\f53f[0-9a-f]{2}` | -| Cobal_strike | `\\rpc_[0-9a-f]{2}` | -| Cobal_strike | `\\spoolss_[0-9a-f]{2}` | -| Cobal_strike | `\\Winsock2\\CatalogChangeListener-[0-9a-f]{3}-0,` | -| DiagTrackEoP | `thisispipe` | -| EfsPotato | `\\pipe\\srvsvc` | -| Credential_Dumping | `\\cachedump` | -| Credential_Dumping | `\\lsadump` | -| Credential_Dumping | `\\wceservicepipe` | -| Koh | `\\imposecost` | -| Koh | `\\imposingcost` | -| PowerShell | `\\PSHost` | -| ADFS | `\\MICROSOFT##WID\\tsql\\query` | - -# Mutex - -`wag mutex -n name` - -- name: mutex name as a regex - -| Type | name | -| ---------- | ------------------ | -| avoslocker | `Cheic0WaZie6zeiy` | diff --git a/src/actions.rs b/src/actions.rs index ebc5141..0f613c2 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -2,18 +2,11 @@ // // SPDX-License-Identifier: GPL-3.0-or-later -use crate::actions::{ - ads::AlternateDataStreams, drivers::Drivers, files::Files, mutexes::Mutexes, pipes::Pipes, - processes::Processes, -}; +use crate::actions::{drivers::Drivers, processes::Processes}; use clap::{Args, Subcommand}; use std::error::Error; -pub mod ads; pub mod drivers; -pub mod files; -pub mod mutexes; -pub mod pipes; pub mod processes; #[derive(Debug, Args)] @@ -24,28 +17,18 @@ pub struct Actions { #[derive(Debug, Subcommand)] pub enum Commands { - AlternateDataStreams(AlternateDataStreams), Drivers(Drivers), - Files(Files), - Mutexes(Mutexes), - Pipes(Pipes), Processes(Processes), } pub trait Runnable { - fn run(&self) -> Result>; + fn run(&self) -> Result<(), Box>; } impl Runnable for Actions { - fn run(&self) -> Result> { + fn run(&self) -> Result<(), Box> { match &self.command { - Commands::AlternateDataStreams(alternate_data_streams) => { - alternate_data_streams as &dyn Runnable - } - Commands::Drivers(drivers) => drivers, - Commands::Files(files) => files, - Commands::Mutexes(mutexes) => mutexes, - Commands::Pipes(pipes) => pipes, + Commands::Drivers(drivers) => drivers as &dyn Runnable, Commands::Processes(processes) => processes, } .run() diff --git a/src/actions/ads.rs b/src/actions/ads.rs deleted file mode 100644 index 004c48f..0000000 --- a/src/actions/ads.rs +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The WAG development team -// -// SPDX-License-Identifier: GPL-3.0-or-later - -use crate::actions::{ads::create::Create, Runnable}; -use clap::{Args, Subcommand}; -use std::error::Error; - -pub mod create; - -#[derive(Debug, Args)] -pub struct AlternateDataStreams { - #[clap(subcommand)] - pub command: Commands, -} - -#[derive(Debug, Subcommand)] -pub enum Commands { - Create(Create), -} - -impl Runnable for AlternateDataStreams { - fn run(&self) -> Result> { - match &self.command { - Commands::Create(create) => create as &dyn Runnable, - } - .run() - } -} diff --git a/src/actions/ads/create.rs b/src/actions/ads/create.rs deleted file mode 100644 index bf29a46..0000000 --- a/src/actions/ads/create.rs +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The WAG development team -// -// SPDX-License-Identifier: GPL-3.0-or-later - -// Alternate Data Stream -// -// Last update 20240224 - -use crate::actions::Runnable; -use base64::engine::{general_purpose, Engine}; -use clap::Parser; -use regex_generate::{Generator, DEFAULT_MAX_REPEAT}; -use std::{error::Error, path::Path}; - -#[derive(Debug, Parser)] -pub struct Create { - #[clap( - short = 'f', - long, - required = true, - help = "Full path filename (regex)" - )] - filename: String, - #[clap(short = 'a', long, required = true, help = "ADS to use")] - ads: String, - #[clap( - short = 'd', - long, - required = false, - default_value = "V2VsY29tZSB0byB0aGUgV0FH", - help = "Data to write in base64" - )] - data: String, -} - -fn create_ads(fullpath: String, adsname: String, hex_data: Vec) -> bool { - let file_base: &Path = Path::new(&fullpath); - if !file_base.exists() { - println!("Missing base file for ADS, try to create it"); - let folder: &Path = file_base.parent().unwrap(); - - let ret_folder: Result<(), std::io::Error> = std::fs::create_dir_all(folder); - match ret_folder { - Ok(_) => println!("The folder is valid"), - Err(_) => return false, - } - let ret_file: Result<(), std::io::Error> = std::fs::write( - file_base, - vec![ - 87, 105, 110, 100, 111, 119, 115, 32, 65, 114, 116, 101, 102, 97, 99, 116, 32, 71, - 101, 110, 101, 114, 97, 116, 111, 114, - ], - ); - match ret_file { - Ok(_) => println!("The base file is created"), - Err(_) => return false, - } - } - let full_ads_name: String = format!("{}:{}", fullpath, adsname); - let file_ads: &Path = Path::new(&full_ads_name); - let ret_file: Result<(), std::io::Error> = std::fs::write(file_ads, hex_data); - ret_file.is_ok() -} - -impl Runnable for Create { - /* Version 20230908 */ - fn run(&self) -> Result> { - println!("Alternate Data Stream"); - - if !self.filename.is_empty() { - let mut generator: Generator = - Generator::new(&self.filename, rand::thread_rng(), DEFAULT_MAX_REPEAT)?; - let mut buffer: Vec = vec![]; - generator.generate(&mut buffer).unwrap(); - let fullname: String = String::from_utf8(buffer)?; - let barrow_ads: String = self.ads.to_string(); - let payload: Vec = general_purpose::STANDARD.decode(self.data.as_str())?; - let ret_ads: bool = create_ads(fullname, barrow_ads, payload); - - return Ok(!ret_ads as i32); - } - - Ok(1) - } -} diff --git a/src/actions/drivers.rs b/src/actions/drivers.rs index 03344c5..e168a21 100644 --- a/src/actions/drivers.rs +++ b/src/actions/drivers.rs @@ -2,11 +2,11 @@ // // SPDX-License-Identifier: GPL-3.0-or-later -use crate::actions::{drivers::create::Create, Runnable}; +use crate::actions::{drivers::byovd::Byovd, Runnable}; use clap::{Args, Subcommand}; use std::error::Error; -pub mod create; +pub mod byovd; #[derive(Debug, Args)] pub struct Drivers { @@ -16,13 +16,13 @@ pub struct Drivers { #[derive(Debug, Subcommand)] pub enum Commands { - Create(Create), + Byovd(Byovd), } impl Runnable for Drivers { - fn run(&self) -> Result> { + fn run(&self) -> Result<(), Box> { match &self.command { - Commands::Create(create) => create as &dyn Runnable, + Commands::Byovd(byovd) => byovd as &dyn Runnable, } .run() } diff --git a/src/actions/drivers/byovd.rs b/src/actions/drivers/byovd.rs new file mode 100644 index 0000000..2895ad8 --- /dev/null +++ b/src/actions/drivers/byovd.rs @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2023 The WAG development team +// +// SPDX-License-Identifier: GPL-3.0-or-later + +use crate::{actions::Runnable, windows::users::is_administrator}; +use clap::Parser; +use std::{error::Error, path::PathBuf}; +use windows::{ + core::{Owned, HSTRING, PCWSTR}, + Win32::{ + Foundation::GENERIC_READ, + System::Services::{ + CreateServiceW, OpenSCManagerW, OpenServiceW, StartServiceW, SC_HANDLE, + SC_MANAGER_ALL_ACCESS, SC_MANAGER_CREATE_SERVICE, SERVICE_AUTO_START, + SERVICE_ERROR_IGNORE, SERVICE_KERNEL_DRIVER, + }, + }, +}; + +#[derive(Debug, Parser)] +pub struct Byovd { + #[clap(required = true, help = "Name of the service")] + service_name: String, + #[clap(required = true, help = "Displayed name of the service")] + displayed_name: String, + #[clap(required = true, help = "Path to the driver")] + path: PathBuf, +} + +impl Runnable for Byovd { + fn run(&self) -> Result<(), Box> { + if !is_administrator()? { + return Ok(()); + } + + if !self.path.try_exists()? || !self.path.is_file() { + return Ok(()); + } + + unsafe { + let service_manager: Owned = Owned::new(OpenSCManagerW( + PCWSTR::null(), + PCWSTR::null(), + SC_MANAGER_CREATE_SERVICE, + )?); + + if OpenServiceW( + *service_manager, + &HSTRING::from(self.service_name.as_str()), + GENERIC_READ.0, + ) + .is_ok() + { + return Ok(()); + } + + let service: Owned = Owned::new(CreateServiceW( + *service_manager, + &HSTRING::from(self.service_name.as_str()), + &HSTRING::from(self.displayed_name.as_str()), + SC_MANAGER_ALL_ACCESS, + SERVICE_KERNEL_DRIVER, + SERVICE_AUTO_START, + SERVICE_ERROR_IGNORE, + &HSTRING::from(self.path.to_str().unwrap()), + PCWSTR::null(), + None, + PCWSTR::null(), + PCWSTR::null(), + PCWSTR::null(), + )?); + + Ok(StartServiceW(*service, None)?) + } + } +} diff --git a/src/actions/drivers/create.rs b/src/actions/drivers/create.rs deleted file mode 100644 index 566d299..0000000 --- a/src/actions/drivers/create.rs +++ /dev/null @@ -1,131 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The WAG development team -// -// SPDX-License-Identifier: GPL-3.0-or-later - -// Load Vulnerable Driver -// -// Last update 20240224 - -use crate::{actions::Runnable, windows::users::is_administrator}; -use clap::Parser; -use std::{error::Error, thread, time}; -use windows::{ - core::{Result as WindowsResult, PCWSTR}, - Win32::System::Services::{ - ControlService, CreateServiceW, DeleteService, OpenSCManagerW, StartServiceW, - ENUM_SERVICE_TYPE, SC_HANDLE, SC_MANAGER_ALL_ACCESS, SERVICE_CONTROL_STOP, SERVICE_ERROR, - SERVICE_START_TYPE, SERVICE_STATUS, - }, -}; - -#[derive(Debug, Parser)] -pub struct Create { - #[clap( - short = 'n', - long, - required = true, - help = "Internal Name of the service" - )] - internal: String, - #[clap( - short = 'd', - long, - required = true, - help = "Displayed Name of the service" - )] - display: String, - #[clap( - short = 'p', - long, - required = true, - help = "Full path to the driver eg: c:\\temp..." - )] - path: String, -} - -fn create_driver_service(name: &str, details: &str, path: &str) -> bool { - println!("Open the service manager"); - let scmanager: SC_HANDLE = - unsafe { OpenSCManagerW(PCWSTR::null(), PCWSTR::null(), SC_MANAGER_ALL_ACCESS) } - .expect("Sc Manager open failure"); - - let mut service_name: Vec = name.encode_utf16().collect(); - service_name.push(0); - let mut service_display: Vec = details.encode_utf16().collect(); - service_display.push(0); - let mut service_path: Vec = path.encode_utf16().collect(); - service_path.push(0); - - println!("Create the service manager"); - - let service_handle: SC_HANDLE = match unsafe { - CreateServiceW( - scmanager, - PCWSTR::from_raw(service_name.as_ptr()), - PCWSTR::from_raw(service_display.as_ptr()), - 0xF003F, - ENUM_SERVICE_TYPE(1), - SERVICE_START_TYPE(2), - SERVICE_ERROR(0), - PCWSTR::from_raw(service_path.as_ptr()), - PCWSTR::null(), - None, - PCWSTR::null(), - PCWSTR::null(), - PCWSTR::null(), - ) - } { - Ok(value) => value, - Err(_) => { - println!("Service creation failure"); - return false; - } - }; - - println!("Start Service "); - - match unsafe { StartServiceW(service_handle, None) } { - Ok(_) => { - println!("Wait a little"); - let sleep_duration: time::Duration = time::Duration::from_millis(2000); - thread::sleep(sleep_duration); - let mut service_status: SERVICE_STATUS = unsafe { std::mem::zeroed() }; - println!("Stop Service"); - let _result_stop: WindowsResult<()> = unsafe { - ControlService(service_handle, SERVICE_CONTROL_STOP, &mut service_status) - }; - } - Err(value) => { - println!("Service Start failure with code : {:#06x}", value.code().0); - } - }; - - match unsafe { DeleteService(service_handle) } { - Ok(_) => { - println!("Service remove succeed"); - true - } - Err(value) => { - println!("Service remove failure with code : {:#06x}", value.code().0); - false - } - } -} - -impl Runnable for Create { - /* Version 20230908 */ - fn run(&self) -> Result> { - println!("Bring Your Own Vulnerable Driver"); - - if !is_administrator()? { - println!("Need to have Administrator right to create the service"); - return Ok(1); - } - - // Todo check path is valid or not :) - - let result: bool = create_driver_service(&self.internal, &self.display, &self.path); - - Ok(!result as i32) - } -} diff --git a/src/actions/files.rs b/src/actions/files.rs deleted file mode 100644 index 5a7073e..0000000 --- a/src/actions/files.rs +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The WAG development team -// -// SPDX-License-Identifier: GPL-3.0-or-later - -use crate::actions::{files::create::Create, Runnable}; -use clap::{Args, Subcommand}; -use std::error::Error; - -pub mod create; - -#[derive(Debug, Args)] -pub struct Files { - #[clap(subcommand)] - pub command: Commands, -} - -#[derive(Debug, Subcommand)] -pub enum Commands { - Create(Create), -} - -impl Runnable for Files { - fn run(&self) -> Result> { - match &self.command { - Commands::Create(create) => create as &dyn Runnable, - } - .run() - } -} diff --git a/src/actions/files/create.rs b/src/actions/files/create.rs deleted file mode 100644 index b05a584..0000000 --- a/src/actions/files/create.rs +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The WAG development team -// -// SPDX-License-Identifier: GPL-3.0-or-later - -/* - File creation Artefact - -the magic bytes and NTFS ADS are store a string HEX value. -like 504B0304 or 4D5A. - -Path are regex in json so need `\\.` to get a `.`. - -if fullpath is empty , it build the path from -env variable and the cmd_path. -"SystemRoot" "Temp\\debug\\.bin" will give "c:\Windows\Temp\debug.bin" - -You can use `SET | more` or `Get-ChildItem Env:` to get the list - -*/ - -use crate::{actions::Runnable, windows::users::is_administrator}; -use base64::engine::{general_purpose, Engine}; -use clap::Parser; -use regex_generate::{Generator, DEFAULT_MAX_REPEAT}; -use std::{ - error::Error, - io::Result as IOResult, - path::Path, - thread, - time::{self, Duration}, -}; - -#[derive(Debug, Parser)] -pub struct Create { - #[clap( - short = 'f', - long, - required = true, - help = "Full path filename (regex)" - )] - filename: String, - #[clap( - short = 'm', - long, - required = false, - default_value = "V2VsY29tZSB0byB0aGUgV0FH", - help = "MagicBytes name to use with module manual in base64" - )] - magicbyte: String, - #[clap( - short = 'a', - long, - required = false, - default_value_t = false, - help = "Need to be admin" - )] - admin: bool, -} - -fn create_file(fullpath: String, hex_data: Vec) -> bool { - println!("Try to create : {}", fullpath); - let file_path: &Path = Path::new(&fullpath); - if !file_path.exists() { - let folder: &Path = file_path.parent().unwrap(); - - let ret_folder: IOResult<()> = std::fs::create_dir_all(folder); - match ret_folder { - Ok(_) => println!("The folder is valid"), - Err(_) => return false, - } - - let ret_file: IOResult<()> = std::fs::write(file_path, hex_data); - match ret_file { - Ok(_) => println!("The file is created"), - Err(_) => return false, - } - - let sleep_duration: Duration = time::Duration::from_millis(2000); - thread::sleep(sleep_duration); - - let ret_remove: IOResult<()> = std::fs::remove_file(file_path); - match ret_remove { - Ok(_) => println!("The file is removed"), - Err(_) => return false, - } - - return true; - } - false -} - -impl Runnable for Create { - fn run(&self) -> Result> { - if self.admin && !is_administrator()? { - println!("Need to have Administrator right to create the file"); - return Ok(1); - } - - let mut generator: Generator = - Generator::new(&self.filename, rand::thread_rng(), DEFAULT_MAX_REPEAT)?; - let mut buffer: Vec = vec![]; - generator.generate(&mut buffer).unwrap(); - let fullname: String = String::from_utf8(buffer)?; - - println!("Create a file on disk"); - - let payload: Vec = general_purpose::STANDARD.decode(self.magicbyte.as_str())?; - let ret: bool = create_file(fullname, payload); - - Ok(!ret as i32) - } -} diff --git a/src/actions/mutexes.rs b/src/actions/mutexes.rs deleted file mode 100644 index 3085d41..0000000 --- a/src/actions/mutexes.rs +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The WAG development team -// -// SPDX-License-Identifier: GPL-3.0-or-later - -use crate::actions::{mutexes::create::Create, Runnable}; -use clap::{Args, Subcommand}; -use std::error::Error; - -pub mod create; - -#[derive(Debug, Args)] -pub struct Mutexes { - #[clap(subcommand)] - pub command: Commands, -} - -#[derive(Debug, Subcommand)] -pub enum Commands { - Create(Create), -} - -impl Runnable for Mutexes { - fn run(&self) -> Result> { - match &self.command { - Commands::Create(create) => create as &dyn Runnable, - } - .run() - } -} diff --git a/src/actions/mutexes/create.rs b/src/actions/mutexes/create.rs deleted file mode 100644 index 559b947..0000000 --- a/src/actions/mutexes/create.rs +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The WAG development team -// -// SPDX-License-Identifier: GPL-3.0-or-later - -// Mutex -// -// Last update 20240224 - -use crate::actions::Runnable; -use clap::Parser; -use regex_generate::{Generator, DEFAULT_MAX_REPEAT}; -use std::{error::Error, thread, time}; -use windows::{ - core::{Result as WindowsResult, PCSTR}, - Win32::{ - Foundation::{CloseHandle, HANDLE}, - System::Threading::CreateMutexA, - }, -}; - -#[derive(Debug, Parser)] -pub struct Create { - #[clap( - short = 'n', - long, - required = true, - help = "Regex of the Mutex to Create" - )] - name: String, -} - -fn create_mutex(name: &String, wait: u64) { - let full_malware_mutex: String = format!("{}\0", name); - let mutex_name: PCSTR = PCSTR::from_raw(full_malware_mutex.as_ptr()); - let mutex_handle: WindowsResult = unsafe { CreateMutexA(None, true, mutex_name) }; - let sleep_duration: time::Duration = time::Duration::from_millis(wait); - thread::sleep(sleep_duration); - let _res_server_pipe: WindowsResult<()> = unsafe { CloseHandle(mutex_handle.unwrap()) }; -} - -impl Runnable for Create { - fn run(&self) -> Result> { - println!("Create Mutex"); - - let mut generator: Generator = - Generator::new(&self.name, rand::thread_rng(), DEFAULT_MAX_REPEAT)?; - let mut buffer: Vec = vec![]; - generator.generate(&mut buffer).unwrap(); - let payload: String = String::from_utf8(buffer)?; - - println!("Create the Mutex : {}", payload); - - create_mutex(&payload, 2000); - - Ok(0) - } -} diff --git a/src/actions/pipes.rs b/src/actions/pipes.rs deleted file mode 100644 index 502c2f2..0000000 --- a/src/actions/pipes.rs +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The WAG development team -// -// SPDX-License-Identifier: GPL-3.0-or-later - -use crate::actions::{pipes::create::Create, Runnable}; -use clap::{Args, Subcommand}; -use std::error::Error; - -pub mod create; - -#[derive(Debug, Args)] -pub struct Pipes { - #[clap(subcommand)] - pub command: Commands, -} - -#[derive(Debug, Subcommand)] -pub enum Commands { - Create(Create), -} - -impl Runnable for Pipes { - fn run(&self) -> Result> { - match &self.command { - Commands::Create(create) => create as &dyn Runnable, - } - .run() - } -} diff --git a/src/actions/pipes/create.rs b/src/actions/pipes/create.rs deleted file mode 100644 index cf20cec..0000000 --- a/src/actions/pipes/create.rs +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The WAG development team -// -// SPDX-License-Identifier: GPL-3.0-or-later - -// Name Pipe -// -// Last update 20240224 - -use crate::actions::Runnable; -use clap::Parser; -use regex_generate::{Generator, DEFAULT_MAX_REPEAT}; -use std::{error::Error, thread, time}; -use windows::{ - core::{Result as WindowsResult, PCSTR}, - Win32::{ - Foundation::{CloseHandle, HANDLE}, - Storage::FileSystem::PIPE_ACCESS_DUPLEX, - System::Pipes::{CreateNamedPipeA, PIPE_TYPE_MESSAGE}, - }, -}; - -#[derive(Debug, Parser)] -pub struct Create { - #[clap( - short = 'n', - long, - required = true, - help = "Regex of the PipeName to Create" - )] - name: String, -} - -fn create_name_pipe(name: &String, wait: u64) { - let full_malware_pipe: String = format!("\\\\.\\pipe\\{}\0", name); - let pipe_name: PCSTR = PCSTR::from_raw(full_malware_pipe.as_ptr()); - let server_pipe: WindowsResult = unsafe { - CreateNamedPipeA( - pipe_name, - PIPE_ACCESS_DUPLEX, - PIPE_TYPE_MESSAGE, - 1, - 2048, - 2048, - 0, - None, - ) - }; - let sleep_duration: time::Duration = time::Duration::from_millis(wait); - thread::sleep(sleep_duration); - let _res_server_pipe: WindowsResult<()> = unsafe { CloseHandle(server_pipe.unwrap()) }; -} - -impl Runnable for Create { - fn run(&self) -> Result> { - println!("Create NamePipe"); - - let mut generator: Generator = - Generator::new(&self.name, rand::thread_rng(), DEFAULT_MAX_REPEAT)?; - let mut buffer: Vec = vec![]; - generator.generate(&mut buffer).unwrap(); - let payload: String = String::from_utf8(buffer)?; - - println!("Create the namepipe : {}", payload); - - create_name_pipe(&payload, 2000); - - Ok(0) - } -} diff --git a/src/actions/processes.rs b/src/actions/processes.rs index 76452ac..0cbdf3d 100644 --- a/src/actions/processes.rs +++ b/src/actions/processes.rs @@ -20,7 +20,7 @@ pub enum Commands { } impl Runnable for Processes { - fn run(&self) -> Result> { + fn run(&self) -> Result<(), Box> { match &self.command { Commands::Spoofing(spoofing) => spoofing as &dyn Runnable, } diff --git a/src/actions/processes/spoofing.rs b/src/actions/processes/spoofing.rs index fab6f85..26b1ad8 100644 --- a/src/actions/processes/spoofing.rs +++ b/src/actions/processes/spoofing.rs @@ -2,184 +2,107 @@ // // SPDX-License-Identifier: GPL-3.0-or-later -// PPID Spoofing -// -// Last update 20240224 - -use crate::actions::Runnable; +use crate::{actions::Runnable, windows::processes::get_pid}; use clap::Parser; -use core::ffi::c_void; use std::{ - error::Error, - ffi::OsString, - fmt::{Display, Formatter, Result as FormatterResult}, - mem::size_of, - os::windows::ffi::OsStringExt, - thread, - time::Duration, + error::Error, ffi::OsString, iter::once, mem::size_of, os::windows::ffi::OsStrExt, + path::PathBuf, }; use windows::{ - core::{Owned, PSTR}, + core::{Owned, PWSTR}, Win32::{ - Foundation::{CloseHandle, HANDLE}, - System::{ - Diagnostics::ToolHelp::{ - CreateToolhelp32Snapshot, Process32FirstW, Process32NextW, PROCESSENTRY32W, - TH32CS_SNAPPROCESS, - }, - Memory::{GetProcessHeap, HeapAlloc, HEAP_FLAGS}, - Threading::{ - CreateProcessA, InitializeProcThreadAttributeList, OpenProcess, TerminateProcess, - UpdateProcThreadAttribute, WaitForSingleObject, LPPROC_THREAD_ATTRIBUTE_LIST, - PROCESS_ACCESS_RIGHTS, PROCESS_CREATION_FLAGS, PROCESS_INFORMATION, - PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, STARTF_USESHOWWINDOW, STARTUPINFOEXA, - }, + Foundation::HANDLE, + System::Threading::{ + CreateProcessW, InitializeProcThreadAttributeList, OpenProcess, + UpdateProcThreadAttribute, EXTENDED_STARTUPINFO_PRESENT, LPPROC_THREAD_ATTRIBUTE_LIST, + PROCESS_CREATE_PROCESS, PROCESS_INFORMATION, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, + STARTUPINFOEXW, STARTUPINFOW, }, }, }; #[derive(Debug, Parser)] pub struct Spoofing { - #[clap( - short = 'e', - long, - required = true, - help = "Full path to the executable eg: c:\\temp..." - )] - executable: String, - #[clap( - short = 'p', - long, - required = true, - help = "Full path to the parent executable eg: c:\\temp..." - )] + #[clap(required = true, help = "Path to the executable")] + executable: PathBuf, + #[clap(required = true, help = "Name of the parent executable")] parent_executable: String, } -#[derive(Debug)] -struct ProcessNotFound; - -impl Error for ProcessNotFound {} - -impl Display for ProcessNotFound { - fn fmt(&self, formatter: &mut Formatter) -> FormatterResult { - write!(formatter, "Process not found") - } -} - -fn get_pid_from_name(name: &str) -> Result> { - let snapshot: Owned = - unsafe { Owned::new(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)?) }; - let mut process_entry: PROCESSENTRY32W = PROCESSENTRY32W { - dwSize: size_of::() as u32, - ..Default::default() - }; - - unsafe { - Process32FirstW(*snapshot, &mut process_entry)?; - } - - loop { - if OsString::from_wide( - process_entry - .szExeFile - .into_iter() - .take_while(|&byte| byte != 0) - .collect::>() - .as_slice(), - ) == name - { - return Ok(process_entry.th32ProcessID); - } - - if unsafe { Process32NextW(*snapshot, &mut process_entry) }.is_err() { - break; - } - } - - Err(Box::new(ProcessNotFound)) -} - -fn create_ppid(name: &String, new_ppid: u32) -> bool { - println!("Use the PPID {}", new_ppid); - println!("Open the Parent Process"); - let mut parent_process_handle: HANDLE = - unsafe { OpenProcess(PROCESS_ACCESS_RIGHTS(0x02000000), false, new_ppid).unwrap() }; - - let mut pi: PROCESS_INFORMATION = PROCESS_INFORMATION::default(); - let mut sinfo: STARTUPINFOEXA = STARTUPINFOEXA::default(); - let mut cb_attribute_list_size: usize = size_of::(); - sinfo.StartupInfo.cb = cb_attribute_list_size as u32; - sinfo.StartupInfo.dwFlags = STARTF_USESHOWWINDOW; - - println!("allocate memory for PROC_THREAD_ATTRIBUTE_LIST"); - sinfo.lpAttributeList = LPPROC_THREAD_ATTRIBUTE_LIST(unsafe { - HeapAlloc( - GetProcessHeap().unwrap(), - HEAP_FLAGS(0), - cb_attribute_list_size, - ) - }); - - println!("InitializeProcThreadAttributeList"); - unsafe { - InitializeProcThreadAttributeList(sinfo.lpAttributeList, 1, 0, &mut cb_attribute_list_size) - .unwrap() - }; - - println!("UpdateProcThreadAttribute"); - let _ = unsafe { - UpdateProcThreadAttribute( - sinfo.lpAttributeList, - 0, - PROC_THREAD_ATTRIBUTE_PARENT_PROCESS as usize, - Some(&mut parent_process_handle as *mut _ as *mut c_void), - size_of::(), - None, - None, - ) - }; - - println!("CreateProcessA"); - let process_name = format!("{}\0", name); - let new_process = unsafe { - CreateProcessA( - None, - PSTR::from_raw(process_name.to_owned().as_mut_ptr()), - None, - None, - false, - PROCESS_CREATION_FLAGS(0x00080000), - None, - None, - &sinfo.StartupInfo, - &mut pi, - ) - }; - match new_process { - Ok(_) => { - println!("New process is created with pid {:}", pi.dwProcessId); - let wait_duration: Duration = Duration::from_millis(2000); - thread::sleep(wait_duration); - let _ = unsafe { TerminateProcess(pi.hProcess, 0) }; - let _ = unsafe { WaitForSingleObject(pi.hProcess, 5000) }; - let _ = unsafe { CloseHandle(pi.hProcess) }; - let _ = unsafe { CloseHandle(pi.hThread) }; - true - } - Err(_) => false, - } -} - impl Runnable for Spoofing { - /* Version 20240209 */ - fn run(&self) -> Result> { - println!("PPID spoofing"); - let result: bool = create_ppid( - &self.executable, - get_pid_from_name(&self.parent_executable)?, - ); + fn run(&self) -> Result<(), Box> { + if !self.executable.try_exists()? || !self.executable.is_file() { + return Ok(()); + } - Ok(!result as i32) + let mut required_size: usize = 0; + + unsafe { + let _ = InitializeProcThreadAttributeList( + LPPROC_THREAD_ATTRIBUTE_LIST::default(), + 1, + 0, + &mut required_size, + ); + }; + + let mut attributes: Box<[u8]> = vec![0; required_size].into_boxed_slice(); + let attributes_list: Owned = unsafe { + Owned::new(LPPROC_THREAD_ATTRIBUTE_LIST( + attributes.as_mut_ptr() as *mut _ + )) + }; + let startup_informations: STARTUPINFOEXW = STARTUPINFOEXW { + StartupInfo: STARTUPINFOW { + cb: size_of::() as u32, + ..Default::default() + }, + lpAttributeList: *attributes_list, + }; + + unsafe { + InitializeProcThreadAttributeList( + startup_informations.lpAttributeList, + 1, + 0, + &mut required_size, + )?; + + let mut parent_process: Owned = Owned::new(OpenProcess( + PROCESS_CREATE_PROCESS, + false, + get_pid(self.parent_executable.as_str())?, + )?); + UpdateProcThreadAttribute( + startup_informations.lpAttributeList, + 0, + PROC_THREAD_ATTRIBUTE_PARENT_PROCESS as usize, + Some(&mut *parent_process as *mut _ as *mut _), + size_of::(), + None, + None, + )?; + + CreateProcessW( + None, + PWSTR( + OsString::from(self.executable.as_os_str()) + .encode_wide() + .chain(once(0)) + .collect::>() + .as_mut_ptr(), + ), + None, + None, + false, + EXTENDED_STARTUPINFO_PRESENT, + None, + None, + &startup_informations.StartupInfo, + &mut PROCESS_INFORMATION::default(), + )?; + }; + + Ok(()) } } diff --git a/src/main.rs b/src/main.rs index b167f6b..d18c9a0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ mod windows; use actions::Runnable; use clap::Parser; use cli::{Arguments, Commands}; +use std::error::Error; fn banner() { let banner: &str = " @@ -23,17 +24,10 @@ fn banner() { println!("{}", banner); } -fn main() { +fn main() -> Result<(), Box> { banner(); - match Arguments::parse().command { - Commands::Actions(action) => match action.run() { - Ok(code) => std::process::exit(code), - Err(error) => { - println!("Error: {}", error); - - std::process::exit(1); - } - }, - }; + Ok(match Arguments::parse().command { + Commands::Actions(action) => action.run()?, + }) } diff --git a/src/windows.rs b/src/windows.rs index 3ac4988..3a99ec0 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -2,4 +2,5 @@ // // SPDX-License-Identifier: GPL-3.0-or-later +pub mod processes; pub mod users; diff --git a/src/windows/processes.rs b/src/windows/processes.rs new file mode 100644 index 0000000..9371c47 --- /dev/null +++ b/src/windows/processes.rs @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2023 The WAG development team +// +// SPDX-License-Identifier: GPL-3.0-or-later + +use std::{ + error::Error, + fmt::{Display, Formatter, Result as FormatterResult}, +}; +use windows::{ + core::{Owned, HSTRING}, + Win32::{ + Foundation::HANDLE, + System::Diagnostics::ToolHelp::{ + CreateToolhelp32Snapshot, Process32FirstW, Process32NextW, PROCESSENTRY32W, + TH32CS_SNAPPROCESS, + }, + }, +}; + +#[derive(Debug)] +pub struct ProcessNotFound; + +impl Error for ProcessNotFound {} + +impl Display for ProcessNotFound { + fn fmt(&self, formatter: &mut Formatter) -> FormatterResult { + write!(formatter, "Process not found") + } +} + +pub fn get_pid(name: &str) -> Result> { + let snapshot: Owned = + unsafe { Owned::new(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)?) }; + let mut process_entry: PROCESSENTRY32W = PROCESSENTRY32W { + dwSize: size_of::() as u32, + ..Default::default() + }; + + unsafe { + Process32FirstW(*snapshot, &mut process_entry)?; + } + + loop { + if HSTRING::from_wide( + process_entry + .szExeFile + .into_iter() + .take_while(|&byte| byte != 0) + .collect::>() + .as_slice(), + )? == name + { + return Ok(process_entry.th32ProcessID); + } + + if unsafe { Process32NextW(*snapshot, &mut process_entry) }.is_err() { + break; + } + } + + Err(Box::new(ProcessNotFound)) +} diff --git a/src/windows/users.rs b/src/windows/users.rs index 272ae94..0a008fb 100644 --- a/src/windows/users.rs +++ b/src/windows/users.rs @@ -3,21 +3,20 @@ // SPDX-License-Identifier: GPL-3.0-or-later use windows::{ - core::Error as WindowsError, + core::{Owned, Result as WindowsResult}, Win32::{ Foundation::BOOL, - Security::{ - AllocateAndInitializeSid, CheckTokenMembership, FreeSid, PSID, SECURITY_NT_AUTHORITY, - }, + Security::{AllocateAndInitializeSid, CheckTokenMembership, PSID, SECURITY_NT_AUTHORITY}, System::SystemServices::{DOMAIN_ALIAS_RID_ADMINS, SECURITY_BUILTIN_DOMAIN_RID}, }, }; -pub fn is_administrator() -> Result { - let is_admin: *mut BOOL = &mut BOOL::from(false); - let mut administrators_group: PSID = PSID::default(); +pub fn is_administrator() -> WindowsResult { + let mut is_admin: BOOL = false.into(); unsafe { + let mut administrators_group: Owned = Owned::new(PSID::default()); + AllocateAndInitializeSid( &SECURITY_NT_AUTHORITY, 2, @@ -29,16 +28,11 @@ pub fn is_administrator() -> Result { 0, 0, 0, - &mut administrators_group, + &mut *administrators_group, )?; - let result: Result<(), WindowsError> = - CheckTokenMembership(None, administrators_group, is_admin); - - FreeSid(administrators_group); - - result?; + CheckTokenMembership(None, *administrators_group, &mut is_admin as *mut _)?; } - Ok(unsafe { (*is_admin).into() }) + Ok(is_admin.as_bool()) }