From 3903db6a693484b496ddb18359558898bd6985fd Mon Sep 17 00:00:00 2001 From: pyro Date: Thu, 3 Sep 2026 17:01:52 -0500 Subject: [PATCH] Added the ability to write the host_notes from the nessus import, also restructured ALOT of code into seperate files to make it easier to find things. --- src/client.rs | 163 +++++++ src/host.rs | 197 ++++++++ src/lib.rs | 1191 ++++++--------------------------------------- src/modules.rs | 241 +++++++++ src/project.rs | 488 +++++++++++++++++++ src/rust_tools.rs | 6 - src/server.rs | 7 +- src/victim.rs | 40 -- 8 files changed, 1232 insertions(+), 1101 deletions(-) create mode 100644 src/client.rs create mode 100644 src/host.rs create mode 100644 src/modules.rs create mode 100644 src/project.rs diff --git a/src/client.rs b/src/client.rs new file mode 100644 index 0000000..b899d0e --- /dev/null +++ b/src/client.rs @@ -0,0 +1,163 @@ +use keyring::Entry; +use rustls::{ClientConfig, ClientConnection, RootCertStore, StreamOwned, pki_types::ServerName}; +use std::error::Error; +use std::fs::OpenOptions; +use std::io::{BufReader, Read, Write}; +use std::net::TcpStream; +use std::path::PathBuf; +use std::sync::Arc; +use std::time::Duration; +use std::time::{self, Instant}; + +#[derive(Clone, Debug)] +pub struct Server { + pub address: String, + pub connected: bool, + pub timer: Duration, + pub config: Option>, + pub last_check: time::Instant, + pub message_que: Vec, + pub action_que: Vec, + pub client_id: usize, + pub selected_client: usize, + pub logged_in: bool, + pub password: String, + pub name: String, + pub cert_text: String, +} + +impl Server { + pub fn donwload_certificate(&self) -> Result> { + let mut stream = TcpStream::connect(self.address.clone())?; + stream.write_all("CERT_REQ".as_bytes())?; + let mut buf = [0u8; 8192]; + let bytes_read = stream.read(&mut buf)?; + let response = String::from_utf8_lossy(&buf[..bytes_read]); + if let Some((_, cert)) = response.split_once("|") { + return Ok(cert.to_string()); + } + Err("Server did not send certificate".into()) + } + + pub fn connect(&mut self) -> Result> { + if let Ok(cert) = self.donwload_certificate() { + self.config = Some(self.build_tls_config(cert.as_str())?); + self.cert_text = cert; + } + let mut stream = self.tls_connect()?; + stream.write("HELLO|attack".as_bytes())?; + let mut buf = [0; 8192]; + let bytes_read = stream.read(&mut buf)?; + let response = String::from_utf8_lossy(&buf[..bytes_read]); + if let Some((_, id)) = response.split_once("|") { + self.client_id = id.trim().parse()?; + } + self.connected = true; + self.last_check = Instant::now(); + let entry = Entry::new("tetanus", &self.address)?; + if let Ok(password) = entry.get_password() { + self.login(password)?; + } + return Ok(self.client_id); + } + + pub fn login(&mut self, pass: String) -> Result<(), Box> { + let mut stream = self.tls_connect()?; + stream.write(format!("NONE**{}|||LOGIN|{}||", self.client_id, pass).as_bytes())?; + let mut buf = [0; 8192]; + let bytes_read = stream.read(&mut buf)?; + let response = String::from_utf8_lossy(&buf[..bytes_read]); + if response.contains("successful") { + self.password = pass; + self.last_check = Instant::now(); + self.logged_in = true; + self.message_que.push(format!("SET_NAME|{}", self.name)); + return Ok(()); + } else { + return Err("[error] logging in!".into()); + } + } + + pub fn checkin(&mut self) { + if self.logged_in { + if let Ok(mut stream) = self.tls_connect() { + self.last_check = Instant::now(); + let payload = format!( + "{}**{}|||{}\n", + self.password.clone(), + 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}")); + } + self.message_que.clear(); + let mut buffer = [0; 4096]; + if let Ok(bytes_read) = stream.read(&mut buffer) { + if bytes_read > 0 { + let response = String::from_utf8_lossy(&buffer[..bytes_read]); + response.split("||").into_iter().for_each(|action| { + self.action_que.push(action.trim().to_string()); + if action.contains("Not authenticated") { + self.logged_in = false; + } + }); + } + } + } + } + } + + pub fn build_tls_config(&self, pem: &str) -> Result, Box> { + let mut roots = RootCertStore::empty(); + let mut reader = BufReader::new(pem.as_bytes()); + let certs = rustls_pemfile::certs(&mut reader).collect::, _>>()?; + for cert in certs { + roots.add(cert)?; + } + let config = ClientConfig::builder() + .with_root_certificates(roots) + .with_no_client_auth(); + Ok(Arc::new(config)) + } + + fn tls_connect(&self) -> Result, Box> { + let config = self.config.clone().ok_or("TLS config not loaded")?; + let tcp = TcpStream::connect(&self.address)?; + let (host, _) = self.address.split_once(':').ok_or("Invalid address")?; + let server_name = ServerName::try_from(host.to_string())?; + let connection = ClientConnection::new(config, server_name)?; + let tls_stream = StreamOwned::new(connection, tcp); + Ok(tls_stream) + } + + pub fn disconnect(&mut self) -> Result<(), Box> { + let mut stream = self.tls_connect()?; + stream.write(format!("{}**{}|||DISCONNECT||", self.password, self.client_id).as_bytes())?; + self.connected = false; + Ok(()) + } + + pub fn save(&mut self, mut path: PathBuf) -> Result<(), Box> { + path.push(format!("{}.conf", self.address)); + let mut conf_file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(path)?; + let outdata = format!( + " +name: {} +address: {} +cert: {} +", + self.name, self.address, self.cert_text + ); + write!(conf_file, "{}", outdata)?; + let entry = Entry::new("tetanus", &self.address)?; + entry.set_password(&self.password)?; + Ok(()) + } +} diff --git a/src/host.rs b/src/host.rs new file mode 100644 index 0000000..7d74f4d --- /dev/null +++ b/src/host.rs @@ -0,0 +1,197 @@ +use crate::ShodanData; +use crate::ToolMessage; +use serde_json; +use shodan_rust::ShodanClient; +use std::error::Error; +use std::fs::File; +use std::io::Write; +use std::net::IpAddr; +use std::net::SocketAddr; +use std::net::TcpStream; +use std::path::PathBuf; +use std::sync::mpsc::Sender; +use std::time::Duration; + +#[derive(Clone, Debug)] +pub struct Host { + pub ip: String, + pub hostname: String, + pub open_ports: Vec, + pub control_port: usize, + pub users: Vec, + pub pwned: bool, + pub id: usize, + pub findings: Vec, + pub shodan_data: Option, +} + +impl Host { + pub fn new() -> Self { + Self { + ip: String::new(), + hostname: String::new(), + open_ports: Vec::new(), + control_port: 0, + users: Vec::new(), + pwned: false, + id: 0, + findings: Vec::new(), + shodan_data: None, + } + } + + pub fn ip(&mut self, ip: String) { + self.ip = ip; + } + + pub fn hostname(&mut self, name: String) { + self.hostname = name; + } + + pub fn open_ports(&mut self, ports: Vec) { + self.open_ports = ports; + } + + pub fn control_port(&mut self, port: usize) { + self.control_port = port; + } + + pub fn users(&mut self, users: Vec) { + self.users = users; + } + + pub fn pwnd(&mut self) { + self.pwned = true; + } + + pub fn id(&mut self, id: usize) { + self.id = id; + } + + pub fn findings(&mut self, findings: Vec) { + self.findings = findings; + } + + pub fn add_port(&mut self, port: &str) { + if let Ok(port) = port.trim().parse() { + if !self.open_ports.contains(&port) && port != 0 { + self.open_ports.push(port); + } + } + } + + pub fn add_user(&mut self, user: User) { + self.users.push(user); + } + + pub fn add_finding(&mut self, finding: String) { + self.findings.push(finding); + } + + pub fn save_config_file(&mut self, path: PathBuf) -> Result<(), Box> { + let mut host_file = File::create(path)?; + let mut out_string = format!( + " +ip: {} +hostname: {} +control_port: {} +pwned: {} +id: {}", + self.ip, self.hostname, self.control_port, self.pwned, self.id + ); + if !self.findings.is_empty() { + out_string.push_str(&format!("\nfindings: ")); + out_string.push_str(&self.findings.join(",")); + } + if !self.open_ports.is_empty() { + out_string.push_str("\nopen_ports: "); + out_string.push_str( + &self + .open_ports + .iter() + .map(|port| port.to_string()) + .collect::>() + .join(","), + ); + } + write!(host_file, "{}", out_string)?; + host_file.sync_all()?; + Ok(()) + } + + pub fn check_port(&mut self, port: u16, tx: Sender) -> Result<(), Box> { + if let Ok(ip) = self.ip.parse::() { + let address = SocketAddr::new(ip, port); + if let Ok(_) = TcpStream::connect_timeout(&address, Duration::from_secs(3)) { + self.add_port(&format!("{}", port)); + let _ = tx.send(ToolMessage::Output(( + 0, + format!("[success] {} is open on {}", port, self.ip), + ))); + } + } else { + let _ = tx.send(ToolMessage::Output(( + 0, + format!("[error] {} is not a valid ip address!", self.ip), + ))); + return Err("not valid IP".into()); + } + return Ok(()); + } + + pub fn get_shodan(&mut self, key: String, tx: Sender) { + let client = ShodanClient::new(key); + let runtime = tokio::runtime::Runtime::new().unwrap(); + runtime.block_on(async { + if let Ok(data) = client.host_info(&self.ip).await { + if let Ok(new_data) = serde_json::from_str::(&data.to_string()) { + if let Some(ports) = new_data.ports { + ports.iter().for_each(|p| { + self.add_port(&p.to_string()); + }); + } + if let Some(vulns) = new_data.vulns { + vulns.iter().for_each(|v| { + self.findings.push(format!("shodan: {}", v)); + }); + } + } else { + let _ = tx.send(ToolMessage::Output(( + 0, + "[error] parsing shodand data!".to_string(), + ))); + } + } else { + let _ = tx.send(ToolMessage::Output(( + 0, + "[error] getting shodand data!".to_string(), + ))); + } + }); + let _ = tx.send(ToolMessage::Output(( + 0, + format!("shodan_function finished for {}", self.ip), + ))); + } +} + +#[derive(Clone, Debug)] +pub struct User { + pub name: String, + pub password: Option, + pub hash: Option, + pub ticket: Option, + pub compromised: bool, +} + +impl User { + pub fn new() -> Self { + Self { + name: String::new(), + password: None, + hash: None, + ticket: None, + compromised: false, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 0be4d2c..8913323 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,28 +1,30 @@ +use crate::client::Server; +use crate::host::Host; +use crate::host::User; +use crate::modules::ModuleLoader; +use crate::project::DistroBox; use crate::rust_tools::rustwitness; use clipboard::ClipboardContext; use clipboard::ClipboardProvider; +use dns_lookup::lookup_host; use fs_extra::dir::{CopyOptions, copy}; use ipnet::IpNet; use keyring::Entry; +use project::Project; use ratatui::crossterm::event; use rayon::prelude::*; -use rhai::{AST, Dynamic, Engine, Scope}; +use rhai::{Dynamic, Scope}; use rust_tools::{BusterTarget, rustbuster}; -use rustls::{ClientConfig, ClientConnection, RootCertStore, StreamOwned, pki_types::ServerName}; use serde_derive::Deserialize; -use serde_json; -use shodan_rust::ShodanClient; use std::collections::HashMap; use std::env; use std::error::Error; use std::fs::OpenOptions; use std::fs::{self, File, create_dir_all, read_dir, read_to_string, remove_dir, remove_dir_all}; -use std::io::{self, BufRead, BufReader, Read, Write}; +use std::io::{self, Write}; use std::net::IpAddr; -use std::net::SocketAddr; -use std::net::TcpStream; use std::path::PathBuf; -use std::process::{Command, Stdio}; +use std::process::Command; use std::sync::RwLock; use std::sync::mpsc::Receiver; use std::sync::{Arc, Mutex, mpsc::Sender, mpsc::channel}; @@ -31,7 +33,11 @@ use std::time::Duration; use std::time::{self, Instant}; use walkdir::WalkDir; +pub mod client; pub mod funcs; +pub mod host; +pub mod modules; +pub mod project; pub mod rust_tools; pub mod server; pub mod victim; @@ -323,6 +329,7 @@ impl AppState { self.help.push( "save_server\nsave the server and password to your system's keyring.\n".to_string(), ); + self.help.push("parse_nessus\nparse a nessus csv to add hosts and ports to the currently selected project\n".to_string()); self.help.push("exit\nquit the tool\n".to_string()); self.initialize_modules(); return Ok(()); @@ -1526,6 +1533,131 @@ impl AppState { } } } + "parse_nessus" => { + if let Some(arg) = command_args { + let path = PathBuf::from(arg); + let csv_text = read_to_string(path)?; + let mut parsed_hosts: Vec = Vec::new(); + csv_text + .lines() + .into_iter() + .filter(|line| !line.contains("Host,Protocol,Port") && line.contains(",")) + .for_each(|line| { + let parts: Vec<&str> = line.split(",").collect(); + let name = parts[0].replace("\"", "").trim().to_string(); + let port = parts[2].replace("\"", "").trim().to_string(); + if let Ok(_) = name.parse::() { + if !parsed_hosts.iter().any(|h| h.ip == name) { + let mut new_host = Host::new(); + new_host.ip = name.clone(); + new_host.add_port(&port); + parsed_hosts.push(new_host); + } else { + parsed_hosts.iter_mut().filter(|h| h.ip == name).for_each( + |h| { + h.add_port(&port); + }, + ); + } + } else { + if let Ok(ips) = lookup_host(name.as_str()) { + ips.for_each(|ip| { + if !parsed_hosts.iter().any(|h| h.ip == ip.to_string()) { + let mut new_host = Host::new(); + new_host.ip = ip.to_string().clone(); + new_host.hostname = name.clone(); + new_host.add_port(&port); + parsed_hosts.push(new_host) + } else { + parsed_hosts + .iter_mut() + .filter(|h| h.ip == ip.to_string()) + .for_each(|h| { + if h.hostname != name { + h.hostname = name.clone(); + } + if port != "0" { + h.add_port(&port); + } + }); + } + }); + } + } + }); + parsed_hosts.iter().for_each(|h| { + if !self.projects[self.selected_project] + .hosts + .iter() + .any(|ph| ph.ip == h.ip) + { + self.projects[self.selected_project].add_host(h.clone()); + } else { + self.projects[self.selected_project] + .hosts + .iter_mut() + .filter(|ph| ph.ip == h.ip) + .for_each(|ph| { + h.open_ports.iter().for_each(|p| { + ph.add_port(&format!("{}", p)); + }); + if ph.hostname != h.hostname { + ph.hostname = h.hostname.clone(); + } + }); + } + }); + self.save_all()?; + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + "[success] Project hosts saved!".to_string(), + ))); + let mut host_notes_folder = self.projects[self.selected_project].notes.clone(); + host_notes_folder.push("host_notes.md"); + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!("writing to {}...", host_notes_folder.display()), + ))); + let mut host_notes_file = OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(host_notes_folder)?; + writeln!( + host_notes_file, + "#{} #external #host_notes", + self.projects[self.selected_project].org_name.clone() + )?; + self.projects[self.selected_project] + .hosts + .iter() + .filter(|h| !h.open_ports.is_empty()) + .for_each(|h| { + let _ = writeln!(host_notes_file, "# {}", h.ip); + let _ = writeln!(host_notes_file, "hostname: {}", h.hostname); + let _ = writeln!(host_notes_file, "ports:\n"); + let _ = writeln!(host_notes_file, "| port | service | vulns |",); + let _ = writeln!(host_notes_file, "| ---- | ------- | ----- |",); + h.open_ports.iter().for_each(|p| { + let _ = writeln!(host_notes_file, "| {} | | |", p); + }); + let _ = writeln!(host_notes_file, "\n\n",); + let _ = writeln!(host_notes_file, "---",); + let _ = self.main_tx.send(ToolMessage::Output(( + rid, + format!("[success] notes for {} written!", h.ip), + ))); + }); + let _ = writeln!(host_notes_file, "# Hosts with no ports open:\n"); + self.projects[self.selected_project] + .hosts + .iter() + .filter(|h| h.open_ports.is_empty()) + .for_each(|h| { + let _ = writeln!(host_notes_file, "- {}:{}", h.ip, h.hostname); + }); + } + } "exit" => { self.save_all()?; let _ = self.main_tx.send(ToolMessage::AppStateExit); @@ -2471,1049 +2603,6 @@ impl AppState { } } -#[derive(Clone, Debug)] -pub struct Server { - pub address: String, - pub connected: bool, - pub timer: Duration, - pub config: Option>, - pub last_check: time::Instant, - pub message_que: Vec, - pub action_que: Vec, - pub client_id: usize, - pub selected_client: usize, - pub logged_in: bool, - pub password: String, - pub name: String, - pub cert_text: String, -} - -impl Server { - pub fn donwload_certificate(&self) -> Result> { - let mut stream = TcpStream::connect(self.address.clone())?; - stream.write_all("CERT_REQ".as_bytes())?; - let mut buf = [0u8; 8192]; - let bytes_read = stream.read(&mut buf)?; - let response = String::from_utf8_lossy(&buf[..bytes_read]); - if let Some((_, cert)) = response.split_once("|") { - return Ok(cert.to_string()); - } - Err("Server did not send certificate".into()) - } - - pub fn connect(&mut self) -> Result> { - if let Ok(cert) = self.donwload_certificate() { - self.config = Some(self.build_tls_config(cert.as_str())?); - self.cert_text = cert; - } - let mut stream = self.tls_connect()?; - stream.write("HELLO|attack".as_bytes())?; - let mut buf = [0; 8192]; - let bytes_read = stream.read(&mut buf)?; - let response = String::from_utf8_lossy(&buf[..bytes_read]); - if let Some((_, id)) = response.split_once("|") { - self.client_id = id.trim().parse()?; - } - self.connected = true; - self.last_check = Instant::now(); - let entry = Entry::new("tetanus", &self.address)?; - if let Ok(password) = entry.get_password() { - self.login(password)?; - } - return Ok(self.client_id); - } - - pub fn login(&mut self, pass: String) -> Result<(), Box> { - let mut stream = self.tls_connect()?; - stream.write(format!("NONE**{}|||LOGIN|{}||", self.client_id, pass).as_bytes())?; - let mut buf = [0; 8192]; - let bytes_read = stream.read(&mut buf)?; - let response = String::from_utf8_lossy(&buf[..bytes_read]); - if response.contains("successful") { - self.password = pass; - self.last_check = Instant::now(); - self.logged_in = true; - self.message_que.push(format!("SET_NAME|{}", self.name)); - return Ok(()); - } else { - return Err("[error] logging in!".into()); - } - } - - pub fn checkin(&mut self) { - if self.logged_in { - if let Ok(mut stream) = self.tls_connect() { - self.last_check = Instant::now(); - let payload = format!( - "{}**{}|||{}\n", - self.password.clone(), - 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}")); - } - self.message_que.clear(); - let mut buffer = [0; 4096]; - if let Ok(bytes_read) = stream.read(&mut buffer) { - if bytes_read > 0 { - let response = String::from_utf8_lossy(&buffer[..bytes_read]); - response.split("||").into_iter().for_each(|action| { - self.action_que.push(action.trim().to_string()); - if action.contains("Not authenticated") { - self.logged_in = false; - } - }); - } - } - } - } - } - - pub fn build_tls_config(&self, pem: &str) -> Result, Box> { - let mut roots = RootCertStore::empty(); - let mut reader = BufReader::new(pem.as_bytes()); - let certs = rustls_pemfile::certs(&mut reader).collect::, _>>()?; - for cert in certs { - roots.add(cert)?; - } - let config = ClientConfig::builder() - .with_root_certificates(roots) - .with_no_client_auth(); - Ok(Arc::new(config)) - } - - fn tls_connect(&self) -> Result, Box> { - let config = self.config.clone().ok_or("TLS config not loaded")?; - let tcp = TcpStream::connect(&self.address)?; - let (host, _) = self.address.split_once(':').ok_or("Invalid address")?; - let server_name = ServerName::try_from(host.to_string())?; - let connection = ClientConnection::new(config, server_name)?; - let tls_stream = StreamOwned::new(connection, tcp); - Ok(tls_stream) - } - - fn disconnect(&mut self) -> Result<(), Box> { - let mut stream = self.tls_connect()?; - stream.write(format!("{}**{}|||DISCONNECT||", self.password, self.client_id).as_bytes())?; - self.connected = false; - Ok(()) - } - - fn save(&mut self, mut path: PathBuf) -> Result<(), Box> { - path.push(format!("{}.conf", self.address)); - let mut conf_file = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(path)?; - let outdata = format!( - " -name: {} -address: {} -cert: {} -", - self.name, self.address, self.cert_text - ); - write!(conf_file, "{}", outdata)?; - let entry = Entry::new("tetanus", &self.address)?; - entry.set_password(&self.password)?; - Ok(()) - } -} - -#[derive(Clone, Debug)] -pub struct Project { - pub org_name: String, - pub config_folder: PathBuf, - pub name: String, - pub notes: PathBuf, - pub files: PathBuf, - pub hosts: Vec, - pub current: bool, - pub db: Option, - pub scope: Vec, -} - -impl Project { - pub fn new() -> Self { - Self { - org_name: String::new(), - config_folder: PathBuf::new(), - name: String::new(), - notes: PathBuf::new(), - files: PathBuf::new(), - hosts: Vec::new(), - current: false, - db: None, - scope: Vec::new(), - } - } - - pub fn org_name(&mut self, name: String) { - self.org_name = name; - } - - pub fn notes(&mut self, path: PathBuf) { - self.notes = path; - } - - pub fn files(&mut self, path: PathBuf) { - self.files = path; - } - - pub fn hosts(&mut self, hosts: Vec) { - self.hosts = hosts; - } - - pub fn add_host(&mut self, mut host: Host) { - let mut new_id = 0; - self.hosts.iter().for_each(|h| { - if h.id > new_id { - new_id = h.id.clone(); - } - }); - new_id += 1; - if !self.hosts.iter().any(|h| h.ip == host.ip) { - host.id = new_id; - self.hosts.push(host); - } - } - - pub fn config_folder(&mut self, path: PathBuf) { - self.config_folder = path; - } - - pub fn add_scope(&mut self, host: String) { - if let Ok(ip) = host.parse::() { - self.scope.push(ip.to_string()) - } else if let Ok(net) = host.parse::() { - net.hosts().into_iter().for_each(|ip| { - self.scope.push(ip.to_string()); - }); - } else { - self.scope.push(host); - } - } - - pub fn load_config(&mut self, display: bool) -> Result<(), Box> { - let config_contents = read_to_string(&self.config_folder)?; - for line in config_contents.lines() { - let parts: Vec<&str> = line.split(": ").collect(); - if parts.len() == 2 { - match parts[0].trim() { - "org_name" => self.org_name(parts[1].trim().to_string()), - "name" => self.name = parts[1].trim().to_string(), - "notes" => self.notes(PathBuf::from(parts[1].trim())), - "files" => self.files(PathBuf::from(parts[1].trim())), - "stage" => { - if parts[1].trim() == "current" { - self.current = true; - } - } - "scope" => { - let ips: Vec<&str> = parts[1].split(",").collect(); - ips.iter().for_each(|ip| { - self.add_scope(ip.trim().to_string()); - }); - } - _ => {} - } - } - } - if display { - println!("{} | {} config loaded!", self.org_name, self.name); - println!("loading hosts..."); - } - let mut conf_folder = self.config_folder.clone(); - conf_folder.pop(); - let mut hosts_folder = conf_folder.clone(); - let mut users_folder = conf_folder.clone(); - hosts_folder.push("hosts"); - users_folder.push("users"); - let mut users = HashMap::new(); - if users_folder.exists() { - for entry in read_dir(users_folder)? { - let entry = entry?; - let user_config_string = read_to_string(entry.path())?; - let mut new_user = User::new(); - for line in user_config_string.lines() { - if line.contains(": ") { - let (setting, data) = line.split_once(": ").unwrap(); - match setting.trim() { - "username" => new_user.name = data.trim().to_string(), - "password" => new_user.password = Some(data.trim().to_string()), - "hash" => new_user.hash = Some(data.trim().to_string()), - "compromised" => new_user.compromised = data.trim().parse().unwrap(), - "ticket" => new_user.ticket = Some(PathBuf::from(data.trim())), - _ => {} - } - } - } - users.insert(new_user.name.clone(), new_user); - } - } - if hosts_folder.exists() { - read_dir(hosts_folder)?.into_iter().for_each(|res| { - if let Ok(entry) = res { - let host_path = entry.path(); - let mut new_host = Host::new(); - println!("loading {}...", host_path.display()); - if let Ok(host_conf_text) = read_to_string(host_path) { - host_conf_text.lines().into_iter().for_each(|line| { - if line.contains(": ") { - let (setting, data) = line.split_once(": ").unwrap(); - match setting.trim() { - "ip" => new_host.ip = data.trim().to_string(), - "hostname" => new_host.hostname = data.trim().to_string(), - "control_port" => { - new_host.control_port = data.trim().parse().unwrap(); - } - "pwned" => { - new_host.pwned = data.trim().parse().unwrap(); - } - "id" => new_host.id = data.trim().parse().unwrap(), - "findings" => { - new_host.findings = data - .trim() - .split(",") - .map(|finding| finding.to_string()) - .collect::>() - } - "open_ports" => { - new_host.open_ports = data - .trim() - .split(",") - .into_iter() - .map(|port| port.parse().unwrap_or_default()) - .collect::>() - } - "users" => { - let names: Vec<&str> = data.trim().split(",").collect(); - names.into_iter().for_each(|name| { - if let Some(user) = users.get(name) { - new_host.users.push(user.clone()); - } - }); - } - _ => {} - } - } - }); - self.add_host(new_host); - } - } - }); - } - if display { - println!("{} | {} loaded!", self.org_name, self.name); - } - return Ok(()); - } - - pub fn save_config(&mut self) -> Result<(), Box> { - let mut conf_folder = self.config_folder.clone(); - conf_folder.pop(); - if !conf_folder.exists() { - match create_dir_all(&conf_folder) { - Ok(_) => {} - Err(e) => { - return Err( - format!("save_config: couldn't create project conf folder! {e}").into(), - ); - } - } - } - let mut hosts_folder = conf_folder.clone(); - hosts_folder.push("hosts"); - let mut users_folder = conf_folder.clone(); - users_folder.push("users"); - if !hosts_folder.exists() { - create_dir_all(&hosts_folder)?; - } - if !users_folder.exists() { - create_dir_all(&users_folder)?; - } - let mut file = File::create(&self.config_folder)?; - let mut out_string = format!( - "org_name: {}\nname: {}\nnotes: {}\nfiles: {}\nscope: {}\n", - self.org_name, - self.name, - self.notes.display(), - self.files.display(), - self.scope.join(","), - ); - if self.current { - out_string.push_str("stage: current"); - } else { - out_string.push_str("stage: upcoming"); - } - file.write_all(out_string.as_bytes())?; - self.hosts - .iter_mut() - .try_for_each(|h| -> Result<(), Box> { - let mut host_path = hosts_folder.clone(); - host_path.push(format!("{}.conf", h.id)); - h.save_config_file(host_path) - })?; - return Ok(()); - } -} - -impl std::fmt::Display for Project { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{} | {}", self.org_name, self.name) - } -} - -impl PartialEq for Project { - fn eq(&self, other: &Self) -> bool { - format!("{} | {}", self.org_name, self.name) - == format!("{} | {}", other.org_name, other.name) - } -} - -impl Eq for Project {} - -#[derive(Clone, Debug)] -pub struct Host { - pub ip: String, - pub hostname: String, - pub open_ports: Vec, - pub control_port: usize, - pub users: Vec, - pub pwned: bool, - pub id: usize, - pub findings: Vec, - pub shodan_data: Option, -} - -impl Host { - pub fn new() -> Self { - Self { - ip: String::new(), - hostname: String::new(), - open_ports: Vec::new(), - control_port: 0, - users: Vec::new(), - pwned: false, - id: 0, - findings: Vec::new(), - shodan_data: None, - } - } - - pub fn ip(&mut self, ip: String) { - self.ip = ip; - } - - pub fn hostname(&mut self, name: String) { - self.hostname = name; - } - - pub fn open_ports(&mut self, ports: Vec) { - self.open_ports = ports; - } - - pub fn control_port(&mut self, port: usize) { - self.control_port = port; - } - - pub fn users(&mut self, users: Vec) { - self.users = users; - } - - pub fn pwnd(&mut self) { - self.pwned = true; - } - - pub fn id(&mut self, id: usize) { - self.id = id; - } - - pub fn findings(&mut self, findings: Vec) { - self.findings = findings; - } - - pub fn add_port(&mut self, port: &str) { - if let Ok(port) = port.trim().parse() { - if !self.open_ports.contains(&port) { - self.open_ports.push(port); - } - } - } - - pub fn add_user(&mut self, user: User) { - self.users.push(user); - } - - pub fn add_finding(&mut self, finding: String) { - self.findings.push(finding); - } - - pub fn save_config_file(&mut self, path: PathBuf) -> Result<(), Box> { - let mut host_file = File::create(path)?; - let mut out_string = format!( - " -ip: {} -hostname: {} -control_port: {} -pwned: {} -id: {}", - self.ip, self.hostname, self.control_port, self.pwned, self.id - ); - if !self.findings.is_empty() { - out_string.push_str(&format!("\nfindings: ")); - out_string.push_str(&self.findings.join(",")); - } - if !self.open_ports.is_empty() { - out_string.push_str("\nopen_ports: "); - out_string.push_str( - &self - .open_ports - .iter() - .map(|port| port.to_string()) - .collect::>() - .join(","), - ); - } - write!(host_file, "{}", out_string)?; - host_file.sync_all()?; - Ok(()) - } - - pub fn check_port(&mut self, port: u16, tx: Sender) -> Result<(), Box> { - if let Ok(ip) = self.ip.parse::() { - let address = SocketAddr::new(ip, port); - if let Ok(_) = TcpStream::connect_timeout(&address, Duration::from_secs(3)) { - self.add_port(&format!("{}", port)); - let _ = tx.send(ToolMessage::Output(( - 0, - format!("[success] {} is open on {}", port, self.ip), - ))); - } - } else { - let _ = tx.send(ToolMessage::Output(( - 0, - format!("[error] {} is not a valid ip address!", self.ip), - ))); - return Err("not valid IP".into()); - } - return Ok(()); - } - - pub fn get_shodan(&mut self, key: String, tx: Sender) { - let client = ShodanClient::new(key); - let runtime = tokio::runtime::Runtime::new().unwrap(); - runtime.block_on(async { - if let Ok(data) = client.host_info(&self.ip).await { - if let Ok(new_data) = serde_json::from_str::(&data.to_string()) { - if let Some(ports) = new_data.ports { - ports.iter().for_each(|p| { - self.add_port(&p.to_string()); - }); - } - if let Some(vulns) = new_data.vulns { - vulns.iter().for_each(|v| { - self.findings.push(format!("shodan: {}", v)); - }); - } - } else { - let _ = tx.send(ToolMessage::Output(( - 0, - "[error] parsing shodand data!".to_string(), - ))); - } - } else { - let _ = tx.send(ToolMessage::Output(( - 0, - "[error] getting shodand data!".to_string(), - ))); - } - }); - let _ = tx.send(ToolMessage::Output(( - 0, - format!("shodan_function finished for {}", self.ip), - ))); - } -} - -#[derive(Clone, Debug)] -pub struct User { - pub name: String, - pub password: Option, - pub hash: Option, - pub ticket: Option, - pub compromised: bool, -} - -impl User { - pub fn new() -> Self { - Self { - name: String::new(), - password: None, - hash: None, - ticket: None, - compromised: false, - } - } -} - -#[derive(Clone, Debug)] -pub struct ToolCommand { - pub name: String, - pub path: PathBuf, - pub help: String, - pub args: Vec, - pub output_type: String, - pub finished: bool, - pub result: bool, - pub calls: String, -} - -pub struct ModuleLoader { - pub engine: Arc, - pub commands: HashMap, - pub asts: HashMap, -} - -impl ModuleLoader { - pub fn new() -> Self { - let mut engine = Engine::new(); - engine - .register_type_with_name::("PathBuf") - .register_get_set( - "display", - |p: &mut PathBuf| p.to_string_lossy().into_owned(), - |p: &mut PathBuf, s: String| *p = PathBuf::from(s), - ); - engine - .register_type_with_name::("Host") - .register_get_set( - "ip", - |h: &mut Host| h.ip.clone(), - |h: &mut Host, val: String| h.ip = val, - ) - .register_get_set( - "hostname", - |h: &mut Host| h.hostname.clone(), - |h: &mut Host, val: String| h.hostname = val, - ) - .register_get_set( - "pwned", - |h: &mut Host| h.pwned, - |h: &mut Host, val: bool| h.pwned = val, - ) - .register_fn("add_port", Host::add_port) - .register_fn("add_finding", Host::add_finding); - engine - .register_type_with_name::("Project") - .register_get_set( - "name", - |p: &mut Project| p.name.clone(), - |p: &mut Project, val: String| p.name = val, - ) - .register_get_set( - "org_name", - |p: &mut Project| p.org_name.clone(), - |p: &mut Project, val: String| p.org_name = val, - ) - .register_get_set( - "current", - |p: &mut Project| p.current.clone(), - |p: &mut Project, val: bool| p.current = val, - ) - .register_get_set( - "notes", - |p: &mut Project| p.notes.clone(), - |p: &mut Project, val: PathBuf| p.notes = val, - ) - .register_get_set( - "files", - |p: &mut Project| p.files.clone(), - |p: &mut Project, val: PathBuf| p.files = val, - ) - .register_fn("add_host", Project::add_host); - engine.register_fn("get_host", |hosts: &mut Vec, index: i64| { - hosts.get(index as usize).cloned().unwrap_or_else(Host::new) - }); - engine.register_fn("new_host", Host::new); - engine.register_fn("open_file", |path: &str| { - std::fs::read_to_string(path).unwrap_or_else(|_| String::new()) - }); - engine.register_fn("add_port", |host: &mut Host, port: i64| { - host.add_port(format!("{}", port).as_str()); - }); - 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("filter_ips", |s: &str| -> rhai::Array { - let mut results = rhai::Array::new(); - if let Ok(net) = s.trim().parse::() { - results.push(net.addr().to_string().trim().into()); - } else if let Ok(ip) = s.trim().parse::() { - results.push(ip.to_string().trim().into()); - } else if s.contains(".") { - results.push(s.to_string().trim().into()); - } - results - }); - engine.register_fn("write_file", |path: &str, contents: &str| { - std::fs::write(path, contents).is_ok() - }); - engine.register_fn("find_file", |path: PathBuf, filename: String| -> String { - for entry in walkdir::WalkDir::new(path).max_depth(50) { - if let Ok(entry) = entry { - if entry - .file_name() - .to_string_lossy() - .contains(filename.as_str()) - { - return entry.path().display().to_string(); - } - } - } - - String::new() - }); - engine.register_fn("execute", |cmd: &str| -> String { - let out_string; - if let Ok(output) = Command::new("sh").arg("-c").arg(cmd).output() { - if output.status.success() { - out_string = format!("[success] {}", String::from_utf8_lossy(&output.stdout)); - } else { - out_string = format!("[error] {}", String::from_utf8_lossy(&output.stderr)); - } - } else { - out_string = "[error] failed to run command".to_string(); - } - return out_string; - }); - engine.register_fn("db_run", |p: &mut Project, command: String| -> String { - let mut out_string = format!("No distrobox configured for selected project."); - if let Some(db) = p.db.clone() { - out_string = db.run_command(&command); - } - return out_string; - }); - Self { - engine: Arc::new(engine), - commands: HashMap::new(), - asts: HashMap::new(), - } - } - - pub fn load_all(&mut self, base_path: &PathBuf) -> Result<(), std::io::Error> { - self.commands.clear(); - self.asts.clear(); - - self.load_from_dir(&base_path.join("default"))?; - self.load_from_dir(&base_path.join("custom"))?; - - Ok(()) - } - - fn load_from_dir(&mut self, dir: &PathBuf) -> Result<(), std::io::Error> { - if !dir.exists() { - return Ok(()); - } - - for entry in read_dir(dir)? { - let entry = entry?; - let path = entry.path(); - - if path.is_dir() { - if let Some((command, ast)) = self.parse_module_directory(&path) { - self.asts.insert(command.name.clone(), ast); - self.commands.insert(command.name.clone(), command); - } - } - } - Ok(()) - } - - fn parse_module_directory(&self, dir: &PathBuf) -> Option<(ToolCommand, AST)> { - let config_path = dir.join("config.conf"); - let help_path = dir.join("help.txt"); - let script_path = dir.join("script.rhai"); - - if !config_path.exists() || !help_path.exists() || !script_path.exists() { - return None; - } - let config_content = read_to_string(&config_path).ok()?; - 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() { - "name" => name = val.trim().to_string(), - "output_type" => output_type = val.trim().to_string(), - "args" => { - args = val - .split(',') - .map(|s| s.trim().to_string()) - .filter(|s| !s.is_empty()) - .collect(); - } - "calls" => { - calls = val.trim().to_string(); - } - _ => {} - } - } - } - if name.is_empty() { - return None; - } - let help = read_to_string(&help_path).unwrap_or_default(); - let ast = match self.engine.compile_file(script_path.clone()) { - Ok(compiled_ast) => compiled_ast, - Err(e) => { - eprintln!("[error] compiling script in {:?}: {}", script_path, e); - return None; - } - }; - - let command = ToolCommand { - name, - path: script_path, - help, - args, - output_type, - finished: false, - result: false, - calls, - }; - - Some((command, ast)) - } -} - -#[derive(Debug, Clone)] -pub struct DistroBox { - pub name: String, - pub volumes: Vec, - pub created: bool, - pub template: String, -} - -impl DistroBox { - pub fn create(&mut self, tx: Sender, rid: usize) -> Result<(), Box> { - if let Err(e) = self.stop_template(tx.clone(), rid) { - tx.send(ToolMessage::Output(( - rid, - format!("[error] stopping template box! {e}"), - )))?; - return Ok(()); - } - let mut create_command = Command::new("distrobox"); - create_command - .arg("create") - .arg("--clone") - .arg(self.template.clone()) - .arg("--name") - .arg(self.name.clone()); - let mut added_volumes = Vec::new(); - for volume in &self.volumes { - let folder_name = volume.split("/").last().unwrap(); - if !added_volumes.contains(&String::from(folder_name)) { - create_command - .arg("--volume") - .arg(format!("{}:/{}:rw", volume, folder_name)); - added_volumes.push(folder_name.to_string()); - } - } - create_command.stdout(Stdio::piped()); - create_command.stderr(Stdio::piped()); - let mut child = create_command.spawn()?; - if let Some(stdout) = child.stdout.take() { - let tx_out = tx.clone(); - std::thread::spawn(move || { - let reader = BufReader::new(stdout); - for line in reader.lines().flatten() { - let _ = tx_out.send(ToolMessage::Output((rid, line.clone()))); - println!("{}", line); - } - }); - } - if let Some(stderr) = child.stderr.take() { - let tx_out = tx.clone(); - std::thread::spawn(move || { - let reader = BufReader::new(stderr); - for line in reader.lines().flatten() { - let _ = tx_out.send(ToolMessage::Output((rid, line.clone()))); - println!("{}", line); - } - }); - } - let status = child.wait()?; - if !status.success() { - let _ = tx.send(ToolMessage::Output(( - rid, - format!("[error] creating distrobox!"), - ))); - } - - self.created = true; - return Ok(()); - } - - pub fn stop_template( - &mut self, - tx: Sender, - rid: usize, - ) -> Result<(), Box> { - let mut stop_command = Command::new("distrobox"); - stop_command - .arg("stop") - .arg(self.template.clone()) - .arg("--yes"); - stop_command.stdin(Stdio::piped()); - stop_command.stdout(Stdio::piped()); - stop_command.stderr(Stdio::piped()); - let mut child = stop_command.spawn()?; - if let Some(stdout) = child.stdout.take() { - let tx_out = tx.clone(); - std::thread::spawn(move || { - let reader = BufReader::new(stdout); - for line in reader.lines().flatten() { - let _ = tx_out.send(ToolMessage::Output((rid, line.clone()))); - println!("{}", line); - } - }); - } - if let Some(stderr) = child.stderr.take() { - let tx_out = tx.clone(); - std::thread::spawn(move || { - let reader = BufReader::new(stderr); - for line in reader.lines().flatten() { - let _ = tx_out.send(ToolMessage::Output((rid, line.clone()))); - println!("{}", line); - } - }); - } - let status = child.wait()?; - if !status.success() { - let _ = tx.send(ToolMessage::Output(( - rid, - format!("[error] stopping template distrobox!"), - ))); - } - return Ok(()); - } - - pub fn stop(&mut self, tx: Sender, rid: usize) -> Result<(), Box> { - let mut stop_command = Command::new("distrobox"); - 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()); - let mut child = stop_command.spawn()?; - if let Some(stdout) = child.stdout.take() { - let tx_out = tx.clone(); - std::thread::spawn(move || { - let reader = BufReader::new(stdout); - for line in reader.lines().flatten() { - let _ = tx_out.send(ToolMessage::Output((rid, line.clone()))); - } - }); - } - if let Some(stderr) = child.stderr.take() { - let tx_out = tx.clone(); - std::thread::spawn(move || { - let reader = BufReader::new(stderr); - for line in reader.lines().flatten() { - let _ = tx_out.send(ToolMessage::Output((rid, line.clone()))); - } - }); - } - let status = child.wait()?; - if !status.success() { - let _ = tx.send(ToolMessage::Output(( - rid, - format!("[error] stopping distrobox!"), - ))); - } - return Ok(()); - } - - pub fn destroy(&mut self, tx: Sender, rid: usize) -> Result<(), Box> { - let mut destroycmd = Command::new("distrobox"); - destroycmd.arg("rm").arg(self.name.clone()).arg("-f"); - let res = destroycmd.status()?; - if !res.success() { - let _ = tx.send(ToolMessage::Output(( - rid, - format!("[error] destroying distrobox!"), - ))); - let _ = tx.send(ToolMessage::Output(( - rid, - String::from("pleas try destroying manually:"), - ))); - let _ = tx.send(ToolMessage::Output(( - rid, - format!("distrobox rm {} -f", self.name), - ))); - } - self.created = false; - return Ok(()); - } - - pub fn launch_terminal(&self, cmd: String) { - if let Some((terminal_cmd, args)) = cmd.split_once(" ") { - let mut term_cmd = Command::new(terminal_cmd); - args.split(" ").into_iter().for_each(|arg| { - if arg == "|||COMMAND|||" { - term_cmd - .arg("distrobox") - .arg("enter") - .arg(self.name.clone()); - } else if arg.contains("ENV_NAME=") { - let (_, env_name) = arg.split_once("=").unwrap(); - unsafe { std::env::set_var(env_name, self.name.clone()) }; - } else { - term_cmd.arg(arg); - } - }); - let _ = term_cmd.spawn(); - } - } - - pub fn run_command(&self, cmd: &str) -> String { - let output = Command::new("distrobox") - .arg("enter") - .arg("-e") - .arg(format!("\"{}\"", cmd)) - .output(); - match output { - Ok(output) => { - if output.status.success() { - return String::from_utf8_lossy(&output.stdout).to_string(); - } else { - return format!("[error] {}", String::from_utf8_lossy(&output.stdout)); - } - } - Err(e) => { - return format!("[error] {e}"); - } - } - } -} - #[derive(Clone)] struct Prompt { action: Option, diff --git a/src/modules.rs b/src/modules.rs new file mode 100644 index 0000000..8571b7d --- /dev/null +++ b/src/modules.rs @@ -0,0 +1,241 @@ +use crate::Project; +use crate::host::Host; +use crate::project::ToolCommand; +use ipnet::IpNet; +use rhai::{AST, Engine}; +use std::collections::HashMap; +use std::fs::{read_dir, read_to_string}; +use std::net::IpAddr; +use std::path::PathBuf; +use std::process::Command; +use std::sync::Arc; + +pub struct ModuleLoader { + pub engine: Arc, + pub commands: HashMap, + pub asts: HashMap, +} + +impl ModuleLoader { + pub fn new() -> Self { + let mut engine = Engine::new(); + engine + .register_type_with_name::("PathBuf") + .register_get_set( + "display", + |p: &mut PathBuf| p.to_string_lossy().into_owned(), + |p: &mut PathBuf, s: String| *p = PathBuf::from(s), + ); + engine + .register_type_with_name::("Host") + .register_get_set( + "ip", + |h: &mut Host| h.ip.clone(), + |h: &mut Host, val: String| h.ip = val, + ) + .register_get_set( + "hostname", + |h: &mut Host| h.hostname.clone(), + |h: &mut Host, val: String| h.hostname = val, + ) + .register_get_set( + "pwned", + |h: &mut Host| h.pwned, + |h: &mut Host, val: bool| h.pwned = val, + ) + .register_fn("add_port", Host::add_port) + .register_fn("add_finding", Host::add_finding); + engine + .register_type_with_name::("Project") + .register_get_set( + "name", + |p: &mut Project| p.name.clone(), + |p: &mut Project, val: String| p.name = val, + ) + .register_get_set( + "org_name", + |p: &mut Project| p.org_name.clone(), + |p: &mut Project, val: String| p.org_name = val, + ) + .register_get_set( + "current", + |p: &mut Project| p.current.clone(), + |p: &mut Project, val: bool| p.current = val, + ) + .register_get_set( + "notes", + |p: &mut Project| p.notes.clone(), + |p: &mut Project, val: PathBuf| p.notes = val, + ) + .register_get_set( + "files", + |p: &mut Project| p.files.clone(), + |p: &mut Project, val: PathBuf| p.files = val, + ) + .register_fn("add_host", Project::add_host); + engine.register_fn("get_host", |hosts: &mut Vec, index: i64| { + hosts.get(index as usize).cloned().unwrap_or_else(Host::new) + }); + engine.register_fn("new_host", Host::new); + engine.register_fn("open_file", |path: &str| { + std::fs::read_to_string(path).unwrap_or_else(|_| String::new()) + }); + engine.register_fn("add_port", |host: &mut Host, port: i64| { + host.add_port(format!("{}", port).as_str()); + }); + 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("filter_ips", |s: &str| -> rhai::Array { + let mut results = rhai::Array::new(); + if let Ok(net) = s.trim().parse::() { + results.push(net.addr().to_string().trim().into()); + } else if let Ok(ip) = s.trim().parse::() { + results.push(ip.to_string().trim().into()); + } else if s.contains(".") { + results.push(s.to_string().trim().into()); + } + results + }); + engine.register_fn("write_file", |path: &str, contents: &str| { + std::fs::write(path, contents).is_ok() + }); + engine.register_fn("find_file", |path: PathBuf, filename: String| -> String { + for entry in walkdir::WalkDir::new(path).max_depth(50) { + if let Ok(entry) = entry { + if entry + .file_name() + .to_string_lossy() + .contains(filename.as_str()) + { + return entry.path().display().to_string(); + } + } + } + + String::new() + }); + engine.register_fn("execute", |cmd: &str| -> String { + let out_string; + if let Ok(output) = Command::new("sh").arg("-c").arg(cmd).output() { + if output.status.success() { + out_string = format!("[success] {}", String::from_utf8_lossy(&output.stdout)); + } else { + out_string = format!("[error] {}", String::from_utf8_lossy(&output.stderr)); + } + } else { + out_string = "[error] failed to run command".to_string(); + } + return out_string; + }); + engine.register_fn("db_run", |p: &mut Project, command: String| -> String { + let mut out_string = format!("No distrobox configured for selected project."); + if let Some(db) = p.db.clone() { + out_string = db.run_command(&command); + } + return out_string; + }); + Self { + engine: Arc::new(engine), + commands: HashMap::new(), + asts: HashMap::new(), + } + } + + pub fn load_all(&mut self, base_path: &PathBuf) -> Result<(), std::io::Error> { + self.commands.clear(); + self.asts.clear(); + + self.load_from_dir(&base_path.join("default"))?; + self.load_from_dir(&base_path.join("custom"))?; + + Ok(()) + } + + fn load_from_dir(&mut self, dir: &PathBuf) -> Result<(), std::io::Error> { + if !dir.exists() { + return Ok(()); + } + + for entry in read_dir(dir)? { + let entry = entry?; + let path = entry.path(); + + if path.is_dir() { + if let Some((command, ast)) = self.parse_module_directory(&path) { + self.asts.insert(command.name.clone(), ast); + self.commands.insert(command.name.clone(), command); + } + } + } + Ok(()) + } + + fn parse_module_directory(&self, dir: &PathBuf) -> Option<(ToolCommand, AST)> { + let config_path = dir.join("config.conf"); + let help_path = dir.join("help.txt"); + let script_path = dir.join("script.rhai"); + + if !config_path.exists() || !help_path.exists() || !script_path.exists() { + return None; + } + let config_content = read_to_string(&config_path).ok()?; + 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() { + "name" => name = val.trim().to_string(), + "output_type" => output_type = val.trim().to_string(), + "args" => { + args = val + .split(',') + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()) + .collect(); + } + "calls" => { + calls = val.trim().to_string(); + } + _ => {} + } + } + } + if name.is_empty() { + return None; + } + let help = read_to_string(&help_path).unwrap_or_default(); + let ast = match self.engine.compile_file(script_path.clone()) { + Ok(compiled_ast) => compiled_ast, + Err(e) => { + eprintln!("[error] compiling script in {:?}: {}", script_path, e); + return None; + } + }; + + let command = ToolCommand { + name, + path: script_path, + help, + args, + output_type, + finished: false, + result: false, + calls, + }; + + Some((command, ast)) + } +} diff --git a/src/project.rs b/src/project.rs new file mode 100644 index 0000000..7d19bd9 --- /dev/null +++ b/src/project.rs @@ -0,0 +1,488 @@ +use crate::Host; +use crate::ToolMessage; +use crate::User; +use ipnet::IpNet; +use std::collections::HashMap; +use std::error::Error; +use std::fs::{File, create_dir_all, read_dir, read_to_string}; +use std::io::{BufRead, BufReader, Write}; +use std::net::IpAddr; +use std::path::PathBuf; +use std::process::{Command, Stdio}; +use std::sync::mpsc::Sender; + +#[derive(Clone, Debug)] +pub struct Project { + pub org_name: String, + pub config_folder: PathBuf, + pub name: String, + pub notes: PathBuf, + pub files: PathBuf, + pub hosts: Vec, + pub current: bool, + pub db: Option, + pub scope: Vec, +} + +impl Project { + pub fn new() -> Self { + Self { + org_name: String::new(), + config_folder: PathBuf::new(), + name: String::new(), + notes: PathBuf::new(), + files: PathBuf::new(), + hosts: Vec::new(), + current: false, + db: None, + scope: Vec::new(), + } + } + + pub fn org_name(&mut self, name: String) { + self.org_name = name; + } + + pub fn notes(&mut self, path: PathBuf) { + self.notes = path; + } + + pub fn files(&mut self, path: PathBuf) { + self.files = path; + } + + pub fn hosts(&mut self, hosts: Vec) { + self.hosts = hosts; + } + + pub fn add_host(&mut self, mut host: Host) { + let mut new_id = 0; + self.hosts.iter().for_each(|h| { + if h.id > new_id { + new_id = h.id.clone(); + } + }); + new_id += 1; + if !self.hosts.iter().any(|h| h.ip == host.ip) { + host.id = new_id; + self.hosts.push(host); + } + } + + pub fn config_folder(&mut self, path: PathBuf) { + self.config_folder = path; + } + + pub fn add_scope(&mut self, host: String) { + if let Ok(ip) = host.parse::() { + self.scope.push(ip.to_string()) + } else if let Ok(net) = host.parse::() { + net.hosts().into_iter().for_each(|ip| { + self.scope.push(ip.to_string()); + }); + } else { + self.scope.push(host); + } + } + + pub fn load_config(&mut self, display: bool) -> Result<(), Box> { + let config_contents = read_to_string(&self.config_folder)?; + for line in config_contents.lines() { + let parts: Vec<&str> = line.split(": ").collect(); + if parts.len() == 2 { + match parts[0].trim() { + "org_name" => self.org_name(parts[1].trim().to_string()), + "name" => self.name = parts[1].trim().to_string(), + "notes" => self.notes(PathBuf::from(parts[1].trim())), + "files" => self.files(PathBuf::from(parts[1].trim())), + "stage" => { + if parts[1].trim() == "current" { + self.current = true; + } + } + "scope" => { + let ips: Vec<&str> = parts[1].split(",").collect(); + ips.iter().for_each(|ip| { + self.add_scope(ip.trim().to_string()); + }); + } + _ => {} + } + } + } + if display { + println!("{} | {} config loaded!", self.org_name, self.name); + println!("loading hosts..."); + } + let mut conf_folder = self.config_folder.clone(); + conf_folder.pop(); + let mut hosts_folder = conf_folder.clone(); + let mut users_folder = conf_folder.clone(); + hosts_folder.push("hosts"); + users_folder.push("users"); + let mut users = HashMap::new(); + if users_folder.exists() { + for entry in read_dir(users_folder)? { + let entry = entry?; + let user_config_string = read_to_string(entry.path())?; + let mut new_user = User::new(); + for line in user_config_string.lines() { + if line.contains(": ") { + let (setting, data) = line.split_once(": ").unwrap(); + match setting.trim() { + "username" => new_user.name = data.trim().to_string(), + "password" => new_user.password = Some(data.trim().to_string()), + "hash" => new_user.hash = Some(data.trim().to_string()), + "compromised" => new_user.compromised = data.trim().parse().unwrap(), + "ticket" => new_user.ticket = Some(PathBuf::from(data.trim())), + _ => {} + } + } + } + users.insert(new_user.name.clone(), new_user); + } + } + if hosts_folder.exists() { + read_dir(hosts_folder)?.into_iter().for_each(|res| { + if let Ok(entry) = res { + let host_path = entry.path(); + let mut new_host = Host::new(); + println!("loading {}...", host_path.display()); + if let Ok(host_conf_text) = read_to_string(host_path) { + host_conf_text.lines().into_iter().for_each(|line| { + if line.contains(": ") { + let (setting, data) = line.split_once(": ").unwrap(); + match setting.trim() { + "ip" => new_host.ip = data.trim().to_string(), + "hostname" => new_host.hostname = data.trim().to_string(), + "control_port" => { + new_host.control_port = data.trim().parse().unwrap(); + } + "pwned" => { + new_host.pwned = data.trim().parse().unwrap(); + } + "id" => new_host.id = data.trim().parse().unwrap(), + "findings" => { + new_host.findings = data + .trim() + .split(",") + .map(|finding| finding.to_string()) + .collect::>() + } + "open_ports" => { + new_host.open_ports = data + .trim() + .split(",") + .into_iter() + .map(|port| port.parse().unwrap_or_default()) + .collect::>() + } + "users" => { + let names: Vec<&str> = data.trim().split(",").collect(); + names.into_iter().for_each(|name| { + if let Some(user) = users.get(name) { + new_host.users.push(user.clone()); + } + }); + } + _ => {} + } + } + }); + self.add_host(new_host); + } + } + }); + } + if display { + println!("{} | {} loaded!", self.org_name, self.name); + } + return Ok(()); + } + + pub fn save_config(&mut self) -> Result<(), Box> { + let mut conf_folder = self.config_folder.clone(); + conf_folder.pop(); + if !conf_folder.exists() { + match create_dir_all(&conf_folder) { + Ok(_) => {} + Err(e) => { + return Err( + format!("save_config: couldn't create project conf folder! {e}").into(), + ); + } + } + } + let mut hosts_folder = conf_folder.clone(); + hosts_folder.push("hosts"); + let mut users_folder = conf_folder.clone(); + users_folder.push("users"); + if !hosts_folder.exists() { + create_dir_all(&hosts_folder)?; + } + if !users_folder.exists() { + create_dir_all(&users_folder)?; + } + let mut file = File::create(&self.config_folder)?; + let mut out_string = format!( + "org_name: {}\nname: {}\nnotes: {}\nfiles: {}\nscope: {}\n", + self.org_name, + self.name, + self.notes.display(), + self.files.display(), + self.scope.join(","), + ); + if self.current { + out_string.push_str("stage: current"); + } else { + out_string.push_str("stage: upcoming"); + } + file.write_all(out_string.as_bytes())?; + self.hosts + .iter_mut() + .try_for_each(|h| -> Result<(), Box> { + let mut host_path = hosts_folder.clone(); + host_path.push(format!("{}.conf", h.id)); + h.save_config_file(host_path) + })?; + return Ok(()); + } +} + +impl std::fmt::Display for Project { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{} | {}", self.org_name, self.name) + } +} + +impl PartialEq for Project { + fn eq(&self, other: &Self) -> bool { + format!("{} | {}", self.org_name, self.name) + == format!("{} | {}", other.org_name, other.name) + } +} + +impl Eq for Project {} + +#[derive(Clone, Debug)] +pub struct ToolCommand { + pub name: String, + pub path: PathBuf, + pub help: String, + pub args: Vec, + pub output_type: String, + pub finished: bool, + pub result: bool, + pub calls: String, +} + +#[derive(Debug, Clone)] +pub struct DistroBox { + pub name: String, + pub volumes: Vec, + pub created: bool, + pub template: String, +} + +impl DistroBox { + pub fn create(&mut self, tx: Sender, rid: usize) -> Result<(), Box> { + if let Err(e) = self.stop_template(tx.clone(), rid) { + tx.send(ToolMessage::Output(( + rid, + format!("[error] stopping template box! {e}"), + )))?; + return Ok(()); + } + let mut create_command = Command::new("distrobox"); + create_command + .arg("create") + .arg("--clone") + .arg(self.template.clone()) + .arg("--name") + .arg(self.name.clone()); + let mut added_volumes = Vec::new(); + for volume in &self.volumes { + let folder_name = volume.split("/").last().unwrap(); + if !added_volumes.contains(&String::from(folder_name)) { + create_command + .arg("--volume") + .arg(format!("{}:/{}:rw", volume, folder_name)); + added_volumes.push(folder_name.to_string()); + } + } + create_command.stdout(Stdio::piped()); + create_command.stderr(Stdio::piped()); + let mut child = create_command.spawn()?; + if let Some(stdout) = child.stdout.take() { + let tx_out = tx.clone(); + std::thread::spawn(move || { + let reader = BufReader::new(stdout); + for line in reader.lines().flatten() { + let _ = tx_out.send(ToolMessage::Output((rid, line.clone()))); + println!("{}", line); + } + }); + } + if let Some(stderr) = child.stderr.take() { + let tx_out = tx.clone(); + std::thread::spawn(move || { + let reader = BufReader::new(stderr); + for line in reader.lines().flatten() { + let _ = tx_out.send(ToolMessage::Output((rid, line.clone()))); + println!("{}", line); + } + }); + } + let status = child.wait()?; + if !status.success() { + let _ = tx.send(ToolMessage::Output(( + rid, + format!("[error] creating distrobox!"), + ))); + } + + self.created = true; + return Ok(()); + } + + pub fn stop_template( + &mut self, + tx: Sender, + rid: usize, + ) -> Result<(), Box> { + let mut stop_command = Command::new("distrobox"); + stop_command + .arg("stop") + .arg(self.template.clone()) + .arg("--yes"); + stop_command.stdin(Stdio::piped()); + stop_command.stdout(Stdio::piped()); + stop_command.stderr(Stdio::piped()); + let mut child = stop_command.spawn()?; + if let Some(stdout) = child.stdout.take() { + let tx_out = tx.clone(); + std::thread::spawn(move || { + let reader = BufReader::new(stdout); + for line in reader.lines().flatten() { + let _ = tx_out.send(ToolMessage::Output((rid, line.clone()))); + println!("{}", line); + } + }); + } + if let Some(stderr) = child.stderr.take() { + let tx_out = tx.clone(); + std::thread::spawn(move || { + let reader = BufReader::new(stderr); + for line in reader.lines().flatten() { + let _ = tx_out.send(ToolMessage::Output((rid, line.clone()))); + println!("{}", line); + } + }); + } + let status = child.wait()?; + if !status.success() { + let _ = tx.send(ToolMessage::Output(( + rid, + format!("[error] stopping template distrobox!"), + ))); + } + return Ok(()); + } + + pub fn stop(&mut self, tx: Sender, rid: usize) -> Result<(), Box> { + let mut stop_command = Command::new("distrobox"); + 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()); + let mut child = stop_command.spawn()?; + if let Some(stdout) = child.stdout.take() { + let tx_out = tx.clone(); + std::thread::spawn(move || { + let reader = BufReader::new(stdout); + for line in reader.lines().flatten() { + let _ = tx_out.send(ToolMessage::Output((rid, line.clone()))); + } + }); + } + if let Some(stderr) = child.stderr.take() { + let tx_out = tx.clone(); + std::thread::spawn(move || { + let reader = BufReader::new(stderr); + for line in reader.lines().flatten() { + let _ = tx_out.send(ToolMessage::Output((rid, line.clone()))); + } + }); + } + let status = child.wait()?; + if !status.success() { + let _ = tx.send(ToolMessage::Output(( + rid, + format!("[error] stopping distrobox!"), + ))); + } + return Ok(()); + } + + pub fn destroy(&mut self, tx: Sender, rid: usize) -> Result<(), Box> { + let mut destroycmd = Command::new("distrobox"); + destroycmd.arg("rm").arg(self.name.clone()).arg("-f"); + let res = destroycmd.status()?; + if !res.success() { + let _ = tx.send(ToolMessage::Output(( + rid, + format!("[error] destroying distrobox!"), + ))); + let _ = tx.send(ToolMessage::Output(( + rid, + String::from("pleas try destroying manually:"), + ))); + let _ = tx.send(ToolMessage::Output(( + rid, + format!("distrobox rm {} -f", self.name), + ))); + } + self.created = false; + return Ok(()); + } + + pub fn launch_terminal(&self, cmd: String) { + if let Some((terminal_cmd, args)) = cmd.split_once(" ") { + let mut term_cmd = Command::new(terminal_cmd); + args.split(" ").into_iter().for_each(|arg| { + if arg == "|||COMMAND|||" { + term_cmd + .arg("distrobox") + .arg("enter") + .arg(self.name.clone()); + } else if arg.contains("ENV_NAME=") { + let (_, env_name) = arg.split_once("=").unwrap(); + unsafe { std::env::set_var(env_name, self.name.clone()) }; + } else { + term_cmd.arg(arg); + } + }); + let _ = term_cmd.spawn(); + } + } + + pub fn run_command(&self, cmd: &str) -> String { + let output = Command::new("distrobox") + .arg("enter") + .arg("-e") + .arg(format!("\"{}\"", cmd)) + .output(); + match output { + Ok(output) => { + if output.status.success() { + return String::from_utf8_lossy(&output.stdout).to_string(); + } else { + return format!("[error] {}", String::from_utf8_lossy(&output.stdout)); + } + } + Err(e) => { + return format!("[error] {e}"); + } + } + } +} diff --git a/src/rust_tools.rs b/src/rust_tools.rs index 9fad85e..8f88486 100644 --- a/src/rust_tools.rs +++ b/src/rust_tools.rs @@ -3,8 +3,6 @@ use anyhow::Context; use dns_lookup::lookup_host; use headless_chrome::{Browser, LaunchOptions}; use reqwest::StatusCode; -use std::net::SocketAddr; -use trust_dns_resolver::lookup::Ipv4Lookup; #[derive(Clone)] pub enum BusterTarget { @@ -153,7 +151,3 @@ pub fn rustbuster(target: BusterTarget) -> Option { } } } - -pub fn shodan(ip: String) -> String { - return "todo".to_string(); -} diff --git a/src/server.rs b/src/server.rs index a6d9c6b..93615e7 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,4 +1,3 @@ -use keyring::Entry; use rcgen::generate_simple_self_signed; use std::error::Error; use std::fs; @@ -54,7 +53,7 @@ pub struct Server { } pub async fn start_server(server: Arc>) -> Result<(), Box> { - let mut lock = server.lock().unwrap(); + let lock = server.lock().unwrap(); let mut name_path = lock.certificate_path.clone(); name_path.pop(); name_path.push("names.txt"); @@ -494,7 +493,7 @@ where messages.push(format!("OUTPUT|{}", text)); } ClientAction::Collab(text) => { - println!("todo"); + println!("todo, {}", text); } } } @@ -565,7 +564,7 @@ where messages.push(format!("OUTPUT|{}", text)); } ClientAction::Collab(text) => { - println!("todo"); + println!("todo, {}", text); } } } diff --git a/src/victim.rs b/src/victim.rs index 81148ab..1a3b3bd 100644 --- a/src/victim.rs +++ b/src/victim.rs @@ -1,6 +1,4 @@ use crate::*; -use aes_gcm::{Aes256Gcm, Key, KeyInit, Nonce, aead::Aead}; -use rand::{self, Rng, TryRng}; use rayon::spawn; pub fn generate_code( @@ -48,44 +46,6 @@ pub fn generate_code( return Ok(()); } -/*fn generate_obfuscated_code( - address: String, - password: String, - config_file: PathBuf, - files: PathBuf, - target: String, - tx: Sender, - cert_text: String, -) -> Result<(), Box> { - let mut source_path = config_file.clone(); - source_path.pop(); - source_path.push("victim/src/main.rs"); - let mut source = read_to_string(&source_path)?; - source = source - .replace("|||SERVER|||", &address) - .replace("|||PASSWORD|||", &password) - .replace("|||CERT|||", &cert_text); - files_base.pop(); - files_base.pop(); - files_base.push("Cargo.toml"); - let cargo_string = read_to_string(source_path)?; - let mut files_base = files.clone(); - files_base.push("victim/src"); - create_dir_all(&files_base)?; - files_base.push("main.rs"); - let mut src_file = File::create(&files_base)?; - let mut key = [0u8; 32]; - rand::rng().fill_bytes(&mut key); - let mut iv = [0u8; 16]; - rand::rng().fill_bytes(&mut iv); - let cipher = Aes256Gcm::new_from_slice(&key)?; - let nonce = Nonce::try_from(&iv[..])?; - let mut ciphertext = cipher.encrypt(&nonce, source.as_bytes())?; - - - Ok(()) -}*/ - fn compile_victim(mut cargo_command: Command, tx: Sender) { let mut out = "Error compiling output!".to_string(); if let Ok(output) = cargo_command.output() {