3 Commits
Author SHA1 Message Date
pyro 7247e85762 added a step to make sure state infomration is saved before exit, and
updated the parse_scope command to work better.
2026-09-23 11:14:28 -05:00
pyro 0bad1c7b66 added the ability to exclude hosts and net ranges to projects so that
excluded hosts are not touched by the tool.
2026-09-17 13:11:33 -05:00
pyro a6e50020ca updated some libraries, and fixed a bug with the victim code where it
wouldn't log in.
2026-09-17 11:55:33 -05:00
9 changed files with 682 additions and 412 deletions

No files matched your search

Generated
+486 -388
View File
File diff suppressed because it is too large. Load diff
+85 -16
View File
@@ -130,6 +130,7 @@ impl AppState {
password: String::new(), password: String::new(),
name: name.to_string(), name: name.to_string(),
cert_text: String::new(), cert_text: String::new(),
hostnames: Vec::new(),
}; };
self.servers.push(Arc::new(Mutex::new(new_server))); self.servers.push(Arc::new(Mutex::new(new_server)));
} }
@@ -218,6 +219,7 @@ impl AppState {
password: String::new(), password: String::new(),
name, name,
cert_text: cert, cert_text: cert,
hostnames: Vec::new(),
}; };
self.servers.push(Arc::new(Mutex::new(new_server))); self.servers.push(Arc::new(Mutex::new(new_server)));
} }
@@ -279,6 +281,7 @@ impl AppState {
self.help.push( self.help.push(
"Shortcuts:\nCTRL+P = new_project\nCTRL+s = save\nCTRL_+h = select_host\n".to_string(), "Shortcuts:\nCTRL+P = new_project\nCTRL+s = save\nCTRL_+h = select_host\n".to_string(),
); );
self.help.push("exclude\nexcludes an IP or a cidr network from the scope and all future tool calls.\nexport 192.168.1.1 192.168.2.0/24\n".to_string());
self.help.push("exit\nquit the tool\n".to_string()); self.help.push("exit\nquit the tool\n".to_string());
self.initialize_modules(); self.initialize_modules();
return Ok(()); return Ok(());
@@ -354,24 +357,24 @@ impl AppState {
} }
"generate_victim" => { "generate_victim" => {
let mut success = false; let mut success = false;
let mut addresses = Vec::new();
if let Some(args) = command_args { if let Some(args) = command_args {
if args.split(" ").into_iter().count() == 1 { if let Some((target, address)) = args.split_once(" ") {
let mut address = String::new();
let mut password = String::new(); let mut password = String::new();
let config_file = self.config_file.clone(); let config_file = self.config_file.clone();
let files = self.projects[self.selected_project].files.clone(); let files = self.projects[self.selected_project].files.clone();
let mut cert_text = String::new(); let mut cert_text = String::new();
if let Ok(lock) = self.servers[self.selected_server].lock() { if let Ok(lock) = self.servers[self.selected_server].lock() {
address = lock.address.clone();
password = lock.password.clone(); password = lock.password.clone();
cert_text = lock.cert_text.clone(); cert_text = lock.cert_text.clone();
addresses = lock.hostnames.clone();
} }
match victim::generate_code( match victim::generate_code(
address, address.to_string(),
password, password,
config_file, config_file,
files, files,
args, target.to_string(),
self.main_tx.clone(), self.main_tx.clone(),
cert_text, cert_text,
) { ) {
@@ -393,6 +396,14 @@ impl AppState {
} }
} }
if !success { if !success {
let _ = self
.main_tx
.send(ToolMessage::Output((0, format!("available addresses:"))));
addresses.iter().for_each(|a| {
let _ = self
.main_tx
.send(ToolMessage::Output((0, format!("{}", a))));
});
let _ = self.main_tx.send(ToolMessage::Output(( let _ = self.main_tx.send(ToolMessage::Output((
0, 0,
format!("Usage: generate_victim (windows/linux)"), format!("Usage: generate_victim (windows/linux)"),
@@ -625,6 +636,7 @@ impl AppState {
password: String::new(), password: String::new(),
name: String::new(), name: String::new(),
cert_text: String::new(), cert_text: String::new(),
hostnames: Vec::new(),
}; };
self.servers.push(Arc::new(Mutex::new(new_server))); self.servers.push(Arc::new(Mutex::new(new_server)));
self.save_all()?; self.save_all()?;
@@ -853,23 +865,52 @@ impl AppState {
} }
"parse_scope" => { "parse_scope" => {
let mut general_path = self.projects[self.selected_project].notes.clone(); let mut general_path = self.projects[self.selected_project].notes.clone();
let mut scope_path = self.projects[self.selected_project].notes.clone();
scope_path.push("scope.md");
general_path.push("general.md"); general_path.push("general.md");
let general_string = read_to_string(general_path)?; let general_string = read_to_string(general_path)?;
if let Some((_, scope_section)) = general_string.split_once("# Scope") { let mut scope_file = OpenOptions::new()
if let Some((scope_section, _)) = scope_section.split_once("#") { .create(true)
scope_section.lines().into_iter().for_each(|line| { .truncate(true)
if line.contains("|") { .write(true)
if !line.contains("Subnet") { .open(scope_path)?;
if !line.contains("--") { general_string
let host = .split_whitespace()
line.split("|").collect::<Vec<&str>>()[1].to_string(); .into_iter()
self.projects[self.selected_project].add_scope(host); .for_each(|word| {
if let Ok(_) = word.trim().parse::<IpNet>() {
if let Err(e) = write!(scope_file, "{}\n", word) {
let _ = self.main_tx.send(ToolMessage::Output((
rid,
format!("Error writing scope file! {e}"),
)));
} }
} else if let Ok(_) = word.trim().parse::<IpAddr>() {
if let Err(e) = write!(scope_file, "{}\n", word) {
let _ = self.main_tx.send(ToolMessage::Output((
rid,
format!("Error writing scope file! {e}"),
)));
}
} else if word.contains(".com")
|| word.contains(".org")
|| word.contains(".gov")
|| word.contains(".site")
|| word.contains(".local")
|| word.contains(".ad")
{
if let Err(e) = write!(scope_file, "{}\n", word) {
let _ = self.main_tx.send(ToolMessage::Output((
rid,
format!("Error writing scope file! {e}"),
)));
} }
} }
}); });
} let _ = self.main_tx.send(ToolMessage::Output((
} rid,
"[success] scope parsed and scope.md written!".to_string(),
)));
} }
"show_config" | "sc" => { "show_config" | "sc" => {
let _ = self let _ = self
@@ -1140,6 +1181,7 @@ impl AppState {
} }
2 => { 2 => {
args_vec[0].split(",").into_iter().for_each(|ip| { args_vec[0].split(",").into_iter().for_each(|ip| {
if !project.exclusions.contains(&ip.to_string()) {
if let Ok(ip) = ip.parse::<IpAddr>() { if let Ok(ip) = ip.parse::<IpAddr>() {
let mut new_host = Host::new(); let mut new_host = Host::new();
new_host.ip = ip.to_string(); new_host.ip = ip.to_string();
@@ -1151,6 +1193,7 @@ impl AppState {
hosts.push(new_host); hosts.push(new_host);
} }
} }
}
}); });
args_vec[1].split(",").into_iter().for_each(|p| { args_vec[1].split(",").into_iter().for_each(|p| {
if p.contains("-") { if p.contains("-") {
@@ -1649,6 +1692,30 @@ impl AppState {
} }
} }
"exclude" => {
if let Some(args) = command_args {
let project = self.projects[self.selected_project].clone();
if args.contains(" ") {
args.split(" ")
.into_iter()
.filter(|ex| !project.exclusions.contains(&ex.to_string()))
.for_each(|ex| {
if let Ok(_) = ex.parse::<IpAddr>() {
self.projects[self.selected_project]
.exclusions
.push(ex.to_string());
} else if let Ok(net) = ex.parse::<IpNet>() {
net.hosts().into_iter().for_each(|ip| {
self.projects[self.selected_project]
.exclusions
.push(ip.to_string());
});
}
});
}
}
}
"exit" => { "exit" => {
self.save_all()?; self.save_all()?;
let _ = self.main_tx.send(ToolMessage::AppStateExit); let _ = self.main_tx.send(ToolMessage::AppStateExit);
@@ -1934,6 +2001,7 @@ impl AppState {
current: false, current: false,
db: db, db: db,
scope: Vec::new(), scope: Vec::new(),
exclusions: Vec::new(),
}; };
match new_project.save_config() { match new_project.save_config() {
Ok(_) => {} Ok(_) => {}
@@ -2055,6 +2123,7 @@ impl AppState {
password: String::new(), password: String::new(),
name: String::new(), name: String::new(),
cert_text: String::new(), cert_text: String::new(),
hostnames: Vec::new(),
}; };
self.servers.push(Arc::new(Mutex::new(new_server))); self.servers.push(Arc::new(Mutex::new(new_server)));
self.prompt.reset(); self.prompt.reset();
+8 -1
View File
@@ -24,6 +24,7 @@ pub struct Server {
pub password: String, pub password: String,
pub name: String, pub name: String,
pub cert_text: String, pub cert_text: String,
pub hostnames: Vec<String>,
} }
impl Server { impl Server {
@@ -56,7 +57,9 @@ impl Server {
self.last_check = Instant::now(); self.last_check = Instant::now();
let entry = Entry::new("tetanus", &self.address)?; let entry = Entry::new("tetanus", &self.address)?;
if let Ok(password) = entry.get_password() { if let Ok(password) = entry.get_password() {
self.login(password)?; if let Err(e) = self.login(password) {
entry.delete_credential()?;
}
} }
return Ok(self.client_id); return Ok(self.client_id);
} }
@@ -72,6 +75,7 @@ impl Server {
self.last_check = Instant::now(); self.last_check = Instant::now();
self.logged_in = true; self.logged_in = true;
self.message_que.push(format!("SET_NAME|{}", self.name)); self.message_que.push(format!("SET_NAME|{}", self.name));
self.message_que.push(format!("REQ_HOSTNAMES"));
return Ok(()); return Ok(());
} else { } else {
return Err("[error] logging in!".into()); return Err("[error] logging in!".into());
@@ -82,6 +86,9 @@ impl Server {
if self.logged_in { if self.logged_in {
if let Ok(mut stream) = self.tls_connect() { if let Ok(mut stream) = self.tls_connect() {
self.last_check = Instant::now(); self.last_check = Instant::now();
if self.hostnames.is_empty() {
self.message_que.push("REQ_HOSTNAMES".to_string());
}
let payload = format!( let payload = format!(
"{}**{}|||{}\n", "{}**{}|||{}\n",
self.password.clone(), self.password.clone(),
+27
View File
@@ -595,6 +595,30 @@ pub fn run_tui(
let (action, data) = let (action, data) =
action.split_once("|").unwrap(); action.split_once("|").unwrap();
match action.trim() { match action.trim() {
"HOSTNAMES" => {
let _ = tx_clone.send(
ToolMessage::Output((
0,
"Hostnames recieved:"
.to_string(),
)),
);
data.split(",")
.into_iter()
.for_each(|h| {
let _ = tx_clone.send(
ToolMessage::Output((
0,
h.to_string(),
)),
);
locked_server
.hostnames
.push(
h.to_string(),
);
});
}
"OUTPUT" => { "OUTPUT" => {
let _ = tx_clone.send( let _ = tx_clone.send(
ToolMessage::Output(( ToolMessage::Output((
@@ -1062,6 +1086,9 @@ pub fn run_tui(
server_lock.action_que.push("disconnect".to_string()); server_lock.action_que.push("disconnect".to_string());
} }
}); });
println!("saving state information");
let _ = state.save_all();
println!("SAVED!");
for handle in handles { for handle in handles {
handle.join().unwrap(); handle.join().unwrap();
} }
+15 -1
View File
@@ -60,6 +60,13 @@ struct Args {
help = "The name to set this instance, on servers it will name the server so clients know who it is, on clients it names the clinet for server communications. This defaults to the hostname of the machine, or if the hostname can't be determined it falls back to the username of who runs it." help = "The name to set this instance, on servers it will name the server so clients know who it is, on clients it names the clinet for server communications. This defaults to the hostname of the machine, or if the hostname can't be determined it falls back to the username of who runs it."
)] )]
name: Option<String>, name: Option<String>,
#[arg(
short,
long,
help = "additional hostnames or ip addresses to add to the tls certificate to allow it to connect via portforwarding or proxies. Comma separate the values."
)]
addresses: Option<String>,
} }
#[tokio::main] #[tokio::main]
@@ -201,6 +208,13 @@ async fn main() {
} }
} }
} }
let mut addresses = Vec::new();
if let Some(adds) = args.addresses {
adds.split(",").into_iter().for_each(|a| {
addresses.push(a.to_string());
});
}
addresses.push(server_hostname);
let new_server = server::Server { let new_server = server::Server {
address, address,
clients: Vec::new(), clients: Vec::new(),
@@ -208,7 +222,7 @@ async fn main() {
key_path, key_path,
password: server_pass, password: server_pass,
name, name,
hostname: server_hostname, hostnames: addresses,
}; };
let res = start_server(Arc::new(Mutex::new(new_server))).await; let res = start_server(Arc::new(Mutex::new(new_server))).await;
match res { match res {
+13 -1
View File
@@ -10,6 +10,7 @@ use std::net::IpAddr;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
use walkdir::WalkDir;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Project { pub struct Project {
@@ -22,6 +23,7 @@ pub struct Project {
pub current: bool, pub current: bool,
pub db: Option<DistroBox>, pub db: Option<DistroBox>,
pub scope: Vec<String>, pub scope: Vec<String>,
pub exclusions: Vec<String>,
} }
impl Project { impl Project {
@@ -36,6 +38,7 @@ impl Project {
current: false, current: false,
db: None, db: None,
scope: Vec::new(), scope: Vec::new(),
exclusions: Vec::new(),
} }
} }
@@ -56,6 +59,7 @@ impl Project {
} }
pub fn add_host(&mut self, mut host: Host) { pub fn add_host(&mut self, mut host: Host) {
if !self.exclusions.contains(&host.ip) {
let mut new_id = 0; let mut new_id = 0;
self.hosts.iter().for_each(|h| { self.hosts.iter().for_each(|h| {
if h.id > new_id { if h.id > new_id {
@@ -68,6 +72,7 @@ impl Project {
self.hosts.push(host); self.hosts.push(host);
} }
} }
}
pub fn config_folder(&mut self, path: PathBuf) { pub fn config_folder(&mut self, path: PathBuf) {
self.config_folder = path; self.config_folder = path;
@@ -106,6 +111,12 @@ impl Project {
self.add_scope(ip.trim().to_string()); self.add_scope(ip.trim().to_string());
}); });
} }
"exclusions" => {
let ips: Vec<&str> = parts[1].split(",").collect();
ips.iter().for_each(|ip| {
self.exclusions.push(ip.to_string());
});
}
_ => {} _ => {}
} }
} }
@@ -225,12 +236,13 @@ impl Project {
} }
let mut file = File::create(&self.config_folder)?; let mut file = File::create(&self.config_folder)?;
let mut out_string = format!( let mut out_string = format!(
"org_name: {}\nname: {}\nnotes: {}\nfiles: {}\nscope: {}\n", "org_name: {}\nname: {}\nnotes: {}\nfiles: {}\nscope: {}\nexclusions: {}\n",
self.org_name, self.org_name,
self.name, self.name,
self.notes.display(), self.notes.display(),
self.files.display(), self.files.display(),
self.scope.join(","), self.scope.join(","),
self.exclusions.join(","),
); );
if self.current { if self.current {
out_string.push_str("stage: current"); out_string.push_str("stage: current");
+26 -5
View File
@@ -23,6 +23,7 @@ enum ClientAction {
Output(String), Output(String),
Cmd(String), Cmd(String),
Collab(String), Collab(String),
Hostnames(String),
} }
pub struct Client { pub struct Client {
@@ -49,7 +50,7 @@ pub struct Server {
pub key_path: PathBuf, pub key_path: PathBuf,
pub password: String, pub password: String,
pub name: String, pub name: String,
pub hostname: String, pub hostnames: Vec<String>,
} }
pub async fn start_server(server: Arc<Mutex<Server>>) -> Result<(), Box<dyn Error>> { pub async fn start_server(server: Arc<Mutex<Server>>) -> Result<(), Box<dyn Error>> {
@@ -58,13 +59,15 @@ pub async fn start_server(server: Arc<Mutex<Server>>) -> Result<(), Box<dyn Erro
name_path.pop(); name_path.pop();
name_path.push("names.txt"); name_path.push("names.txt");
let (server_ip, _) = lock.address.split_once(':').unwrap(); let (server_ip, _) = lock.address.split_once(':').unwrap();
let server_hostname = lock.hostname.clone(); let server_hostnames = lock.hostnames.clone();
let current_config_names = vec![ let mut current_config_names = vec![
server_hostname,
server_ip.trim().to_string(), server_ip.trim().to_string(),
"127.0.0.1".to_string(), "127.0.0.1".to_string(),
"localhost".to_string(), "localhost".to_string(),
]; ];
server_hostnames.iter().for_each(|n| {
current_config_names.push(n.clone());
});
let mut regen = true; let mut regen = true;
if let Ok(saved_names_string) = read_to_string(&name_path).await { if let Ok(saved_names_string) = read_to_string(&name_path).await {
let mut saved_names = Vec::new(); let mut saved_names = Vec::new();
@@ -103,7 +106,10 @@ pub async fn start_server(server: Arc<Mutex<Server>>) -> Result<(), Box<dyn Erro
println!("Listening on {}", lock.address); println!("Listening on {}", lock.address);
println!("password: {}", lock.password); println!("password: {}", lock.password);
println!("Hostnames:");
lock.hostnames.iter().for_each(|h| {
println!("{}", h);
});
drop(lock); drop(lock);
loop { loop {
@@ -479,6 +485,15 @@ where
_ => {} _ => {}
} }
} }
else{
if action.contains("REQ_HOSTNAMES") {
let hostnames = lock.hostnames.clone().join(",");
lock.clients.iter_mut().filter(|c| c.id == source_id).for_each(|c|{
c.actions.push(ClientAction::Hostnames(hostnames.clone()));
});
println!("hostnames sent!");
}
}
}); });
} }
if responses.len() > 0 { if responses.len() > 0 {
@@ -495,6 +510,9 @@ where
ClientAction::Collab(text) => { ClientAction::Collab(text) => {
println!("todo, {}", text); println!("todo, {}", text);
} }
ClientAction::Hostnames(hostnames) => {
messages.push(format!("HOSTNAMES|{}", hostnames));
}
} }
} }
let full_message = messages.join("||"); let full_message = messages.join("||");
@@ -566,6 +584,9 @@ where
ClientAction::Collab(text) => { ClientAction::Collab(text) => {
println!("todo, {}", text); println!("todo, {}", text);
} }
ClientAction::Hostnames(_) => {
messages.push("OUTPUT|Not authenticated".to_string());
}
} }
} }
let full_message = messages.join("||"); let full_message = messages.join("||");
+21
View File
@@ -10,3 +10,24 @@ rayon = "1.12.0"
rustls = "0.23.43" rustls = "0.23.43"
rustls-pemfile = "2.2.0" rustls-pemfile = "2.2.0"
sysinfo = "0.39.6" sysinfo = "0.39.6"
[package]
name = "tetanus_victim"
version = "0.1.0"
edition = "2024"
[dependencies]
ipnet = "2.12.1"
obfstr = "0.4.6"
rayon = "1.12.0"
rustls = "0.23.43"
rustls-pemfile = "2.2.0"
sysinfo = "0.39.6"
[profile.release]
opt-level = "s"
lto = true
codegen-units = 1
panic = "abort"
strip = "symbols"
debug = false
+1
View File
@@ -54,6 +54,7 @@ impl Server {
} }
self.connected = true; self.connected = true;
self.last_check = Instant::now(); self.last_check = Instant::now();
self.login(self.password.clone())?;
return Ok(self.client_id); return Ok(self.client_id);
} }