From 7b1f9f9d4e530073ba3df9a1b8b3524706189713 Mon Sep 17 00:00:00 2001 From: pyro Date: Wed, 12 Aug 2026 17:05:35 -0500 Subject: [PATCH] added a new module and the ability to specify a config file if you want to. --- Cargo.lock | 4 +- default-modules/info/config.conf | 1 + .../print_host_discovery/config.conf | 5 + default-modules/print_host_discovery/help.txt | 1 + .../print_host_discovery/script.rhai | 28 ++++ note_templates/internal_pentest/scope.md | 8 ++ src/funcs.rs | 5 +- src/lib.rs | 127 +++++++++++++++--- src/main.rs | 38 +++++- src/server.rs | 24 +++- 10 files changed, 208 insertions(+), 33 deletions(-) create mode 100644 default-modules/print_host_discovery/config.conf create mode 100644 default-modules/print_host_discovery/help.txt create mode 100644 default-modules/print_host_discovery/script.rhai create mode 100644 note_templates/internal_pentest/scope.md diff --git a/Cargo.lock b/Cargo.lock index 3b3d0fe..5c52811 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4511,9 +4511,9 @@ dependencies = [ [[package]] name = "unicode-width" -version = "0.2.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "unicode-xid" diff --git a/default-modules/info/config.conf b/default-modules/info/config.conf index f152714..edb73fb 100644 --- a/default-modules/info/config.conf +++ b/default-modules/info/config.conf @@ -2,3 +2,4 @@ name: info output_type: String, new_thread: false args: config_file,config,projects +calls: none diff --git a/default-modules/print_host_discovery/config.conf b/default-modules/print_host_discovery/config.conf new file mode 100644 index 0000000..709fc51 --- /dev/null +++ b/default-modules/print_host_discovery/config.conf @@ -0,0 +1,5 @@ +name: print_host_discovery +output_type: String, +new_thread: false +args: project +calls: none diff --git a/default-modules/print_host_discovery/help.txt b/default-modules/print_host_discovery/help.txt new file mode 100644 index 0000000..ec83e3e --- /dev/null +++ b/default-modules/print_host_discovery/help.txt @@ -0,0 +1 @@ +parse the scope file in your notes and save the host discovery targets to a file then print the cmd one-liner to perform a simple ping sweep scan. diff --git a/default-modules/print_host_discovery/script.rhai b/default-modules/print_host_discovery/script.rhai new file mode 100644 index 0000000..4a870fe --- /dev/null +++ b/default-modules/print_host_discovery/script.rhai @@ -0,0 +1,28 @@ +let ips = []; +let scope_path = find_scope_file(project.notes); +let scope_text = open_file(scope_path); +for line in scope_text.split("\n"){ + if line.contains("|") && !line.contains("//"){ + let cols = line.split("|"); + let data = cols[1]; + if data.len() > 0{ + for host in expand_target(data){ + ips.push(host); + } + } + } +} + +let output = project.files.display + "/host_discovery_targets.txt"; +let ip_string = ""; +for ip in ips{ + ip_string = ip + "\n" + ip_string; +} +if write_file(output, ip_string){ + let out_string = "Upload " + output + " to the target host then run the following command:\nfor /f %i in (host_discovery_targets.txt) do @ping -n 1 -w 1000 %i | find \"TTL=\" > nul && echo %i>>alive.txt"; + return out_string; +} + +else{ + return "Error writing host_target file!"; +} diff --git a/note_templates/internal_pentest/scope.md b/note_templates/internal_pentest/scope.md new file mode 100644 index 0000000..2eb38c9 --- /dev/null +++ b/note_templates/internal_pentest/scope.md @@ -0,0 +1,8 @@ +# Scope + +// scope_format: +// | ips or subnets | description | +// | -------------- | ----------- | +// | data | data | +// +// if you paste the scope from the work book using the excel to markdown obsidian plugin it will be properly formatted! diff --git a/src/funcs.rs b/src/funcs.rs index 513c792..26fd956 100644 --- a/src/funcs.rs +++ b/src/funcs.rs @@ -5,16 +5,15 @@ use crossterm::{ execute, terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode}, }; -use iced::keyboard::key::Code::Sleep; use ratatui::{ prelude::*, widgets::{Block, Borders, List, ListItem, ListState, Paragraph}, }; +use std::thread::sleep; +use std::thread::spawn; use std::time::Duration; use std::{error::Error, time::Instant}; use std::{io::Write, usize}; -use std::{sync::Arc, thread::sleep}; -use std::{sync::Mutex, thread::spawn}; pub fn get_user_input(prompt: &str) -> Result> { println!("{}", prompt); diff --git a/src/lib.rs b/src/lib.rs index ef8e210..afe2697 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -689,6 +689,62 @@ impl AppState { } } } + "show_config" | "sc" => { + let _ = self + .main_tx + .send(ToolMessage::Output((0, "Current Config:".to_string()))); + self.config.iter().for_each(|setting| { + let _ = self.main_tx.send(ToolMessage::Output(( + 0, + format!("{}:{}", setting.0, setting.1), + ))); + }); + } + "set" => { + let mut ready = false; + let mut key = String::new(); + let mut value = String::new(); + if let Some(args) = command_args.clone() { + let args_vec: Vec<&str> = args.split(" ").collect(); + if args_vec.len() == 2 { + key = args_vec[0].to_string(); + value = args_vec[1].to_string(); + if self.config.contains_key(&key) { + ready = true; + } else { + let _ = self.main_tx.send(ToolMessage::Output(( + 0, + "Error Unknown setting! {key}, {value}".to_string(), + ))); + return Ok(()); + } + } + } + if ready { + self.config.insert(key, value); + self.save_config()?; + let _ = self.main_tx.send(ToolMessage::Output(( + 0, + "Setting changed successfully!".to_string(), + ))); + } else { + let _ = self.main_tx.send(ToolMessage::Output(( + 0, + "Error malformed command!".to_string(), + ))); + let _ = self.main_tx.send(ToolMessage::Output(( + 0, + "Usag: set setting value".to_string(), + ))); + } + } + "list_modules" => { + self.module_loader.commands.iter().for_each(|module| { + let _ = self + .main_tx + .send(ToolMessage::Output((0, format!("{}", module.0)))); + }); + } "exit" => { self.save_all()?; let _ = self.main_tx.send(ToolMessage::AppStateExit); @@ -752,6 +808,7 @@ impl AppState { _ => {} } } + let worker_tx = self.main_tx.clone(); self.workers.spawn(move || { match engine.eval_ast_with_scope::(&mut scope, &ast) { Ok(result) => { @@ -768,9 +825,14 @@ impl AppState { "failed to process array output".into() } } else if result.is_string() { - result + let out_string = result .into_string() - .unwrap_or_else(|_| "failed to parse string".into()) + .unwrap_or_else(|_| "failed to parse string".into()); + if cmd_meta.calls != "none" { + let _ = + worker_tx.send(ToolMessage::Input((0, out_string.clone()))); + } + out_string } else { format!("{:?}", result) }; @@ -795,6 +857,16 @@ impl AppState { Ok(()) } + pub fn edit_config(&mut self, key: String, value: String) { + if self.config.contains_key(&key) { + self.config.insert(key.clone(), value.clone()); + } + let _ = self.main_tx.send(ToolMessage::Output(( + 0, + format!("Setting saved! {key}:{value}"), + ))); + } + pub fn new_project( &mut self, org: String, @@ -1086,6 +1158,7 @@ impl Server { self.client_id.clone(), self.message_que.join("||") ); + if let Err(e) = stream.write_all(payload.as_bytes()) { self.action_que .push(format!("Error|sending reponse output to server! {e}")); @@ -1500,6 +1573,7 @@ pub struct ToolCommand { pub output_type: String, pub finished: bool, pub result: bool, + pub calls: String, } pub struct ModuleLoader { @@ -1575,6 +1649,33 @@ impl ModuleLoader { engine.register_fn("add_port", |host: &mut Host, port: i64| { host.add_port(port as usize); }); + engine.register_fn("expand_target", |s: &str| -> rhai::Array { + let mut results = rhai::Array::new(); + if let Ok(net) = s.trim().parse::() { + for host in net.hosts() { + results.push(host.to_string().into()); + } + } else if let Ok(ip) = s.trim().parse::() { + results.push(ip.to_string().into()); + } else if s.contains(".") { + results.push(s.to_string().into()); + } + results + }); + engine.register_fn("write_file", |path: &str, contents: &str| { + std::fs::write(path, contents).is_ok() + }); + engine.register_fn("find_scope_file", |path: PathBuf| -> String { + for entry in walkdir::WalkDir::new(path).max_depth(10) { + if let Ok(entry) = entry { + if entry.file_name().to_string_lossy().contains("scope") { + return entry.path().display().to_string(); + } + } + } + + String::new() + }); Self { engine: Arc::new(engine), commands: HashMap::new(), @@ -1623,6 +1724,7 @@ impl ModuleLoader { let mut name = String::new(); let mut output_type = String::new(); let mut args = Vec::new(); + let mut calls = String::new(); for line in config_content.lines() { if let Some((key, val)) = line.split_once(':') { match key.trim() { @@ -1635,6 +1737,9 @@ impl ModuleLoader { .filter(|s| !s.is_empty()) .collect(); } + "calls" => { + calls = val.trim().to_string(); + } _ => {} } } @@ -1659,6 +1764,7 @@ impl ModuleLoader { output_type, finished: false, result: false, + calls, }; Some((command, ast)) @@ -1685,7 +1791,6 @@ impl DistroBox { let mut create_command = Command::new("distrobox"); create_command .arg("create") - .arg("--root") .arg("--clone") .arg(self.template.clone()) .arg("--name") @@ -1743,7 +1848,6 @@ impl DistroBox { let mut stop_command = Command::new("distrobox"); stop_command .arg("stop") - .arg("--root") .arg(self.template.clone()) .arg("--yes"); stop_command.stdin(Stdio::piped()); @@ -1782,11 +1886,7 @@ impl DistroBox { pub fn stop(&mut self, tx: Sender, rid: usize) -> Result<(), Box> { let mut stop_command = Command::new("distrobox"); - stop_command - .arg("stop") - .arg("--root") - .arg(self.name.clone()) - .arg("--yes"); + stop_command.arg("stop").arg(self.name.clone()).arg("--yes"); stop_command.stdin(Stdio::piped()); stop_command.stdout(Stdio::piped()); stop_command.stderr(Stdio::piped()); @@ -1823,11 +1923,7 @@ impl DistroBox { pub fn destroy(&mut self, tx: Sender, rid: usize) -> Result<(), Box> { let mut destroycmd = Command::new("distrobox"); - destroycmd - .arg("rm") - .arg("--root") - .arg(self.name.clone()) - .arg("-f"); + destroycmd.arg("rm").arg(self.name.clone()).arg("-f"); let res = destroycmd.status()?; if !res.success() { let _ = tx.send(ToolMessage::Output(( @@ -1840,7 +1936,7 @@ impl DistroBox { ))); let _ = tx.send(ToolMessage::Output(( rid, - format!("distrobox rm --root {} -f", self.name), + format!("distrobox rm {} -f", self.name), ))); } self.created = false; @@ -1855,7 +1951,6 @@ impl DistroBox { term_cmd .arg("distrobox") .arg("enter") - .arg("--root") .arg(self.name.clone()); } else if arg.contains("ENV_NAME=") { let (_, env_name) = arg.split_once("=").unwrap(); diff --git a/src/main.rs b/src/main.rs index 326ac63..a64799a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,4 @@ use clap::Parser; -use rcgen::generate_simple_self_signed; use std::sync::{Arc, Mutex}; use std::{env, path::PathBuf, process::exit}; use tetanus::funcs::*; @@ -26,14 +25,38 @@ struct Args { #[arg(short, long, help = "for testing stuff...")] test: bool, + + #[arg( + short, + long, + help = "The IP address for the server, in server mode it will listen on this IP, in client mode it will add this ip as its first server. Default is 127.0.0.1" + )] + ip: Option, + + #[arg( + short, + long, + help = "The port for the server, in server mode this will be the port the server listens on, in client mode this will be the default server port it connects to." + )] + port: Option, + + #[arg( + short = 'C', + long, + help = "Custom Config file. Give the path to a config file to use." + )] + config: Option, } #[tokio::main] async fn main() { println!("checking for server or client config files..."); + let args = Args::parse(); let mut config_path = PathBuf::new(); let mut config_path_opt = env::home_dir(); - if let Some(home_config_path) = config_path_opt.as_mut() { + if let Some(given_config_path) = args.config { + config_path = given_config_path.clone(); + } else if let Some(home_config_path) = config_path_opt.as_mut() { println!("made it to home_config_path"); home_config_path.push(".config/tetanus"); if !home_config_path.exists() { @@ -53,11 +76,8 @@ async fn main() { config_path = home_config_path.clone(); } } - let args = Args::parse(); let mut client_config_path = config_path.clone(); client_config_path.push("client.conf"); - let mut server_config_path = config_path.clone(); - server_config_path.push("server.conf"); if args.client { if !client_config_path.exists() { eprintln!( @@ -76,8 +96,7 @@ async fn main() { println!("entering tui..."); let _res = run_tui(appstate, rx).unwrap(); } else if args.server { - let address = "127.0.0.1:31337".to_string(); - let names = vec!["127.0.0.1".to_string(), "localhost".to_string()]; + let mut address = "127.0.0.1:31337".to_string(); let mut certificate_path = config_path.clone(); certificate_path.push("server"); let mut key_path = certificate_path.clone(); @@ -85,6 +104,11 @@ async fn main() { key_path.push("key.pem"); println!("key paht: {}", key_path.display()); println!("cert path: {}", certificate_path.display()); + if let Some(ip) = args.ip { + if let Some(port) = args.port { + address = format!("{}:{}", ip.trim(), port.trim()); + } + } let new_server = server::Server { address, clients: Vec::new(), diff --git a/src/server.rs b/src/server.rs index 186fd06..4120310 100644 --- a/src/server.rs +++ b/src/server.rs @@ -17,7 +17,6 @@ use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer}; enum ClientAction { Output(String), Cmd(String), - Ping, } pub struct Client { @@ -240,6 +239,10 @@ where lock.clients.iter_mut().find(|c| c.id == source_id) { if source_client.controlling != 0 { + println!( + "deslelcting client: {}", + source_client.controlling + ); let dest_client_id = source_client.controlling.clone(); if let Some(dest_client) = lock .clients @@ -247,6 +250,7 @@ where .find(|c| c.id == dest_client_id) { dest_client.controlled = 0; + println!("client {} deselected", dest_client.id); } } } @@ -268,7 +272,12 @@ where } "LIST_CLIENTS" => { lock.clients.iter().for_each(|c| { - let out = format!("{}: {}", c.id, c.address); + let out; + if let Some(hostname) = c.hostname.clone() { + out = format!("{}: {}({})", c.id, hostname, c.address); + } else { + out = format!("{}: {}", c.id, c.address); + } println!("client list requested!"); println!("adding {} to response...", out); responses.push(ClientAction::Output(out)); @@ -277,6 +286,14 @@ where "TEST" => { responses.push(ClientAction::Output("TEST BACK".to_string())); } + "SET_HOSTNAME" => { + lock.clients + .iter_mut() + .filter(|c| c.id == source_id) + .for_each(|c| { + c.hostname = Some(data.trim().to_string()); + }); + } _ => {} } } @@ -293,9 +310,6 @@ where println!("adding {} to output", text); messages.push(format!("OUTPUT|{}", text)); } - ClientAction::Ping => { - messages.push(format!("PONG")); - } } } let full_message = messages.join("||");