diff --git a/Cargo.lock b/Cargo.lock index 4efab28..8d403e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -227,6 +227,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "colored" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -1061,6 +1070,7 @@ name = "tetanus_shares" version = "0.1.0" dependencies = [ "clap", + "colored", "ldap3", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index 7350b82..df68270 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,5 +5,6 @@ edition = "2024" [dependencies] clap = { version = "4.5.37", features = ["derive"] } +colored = "3.0.0" ldap3 = { version = "0.11.5", features = ["gssapi"] } tokio = { version = "1.44.2", features = ["rt-multi-thread"] } diff --git a/src/main.rs b/src/main.rs index b4ee033..f57b0cd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,77 +1,206 @@ -use ldap3::{LdapConn, LdapConnAsync, Scope, SearchEntry}; -use ldap3::result::Result; -use ldap3::Ldap; -use clap::{error, Parser}; +use clap::Parser; use std::fmt::Debug; +use std::fs; +use std::fs::OpenOptions; +use std::io::Write; +use std::path::PathBuf; use std::process::exit; -use std::future::Future; +use std::process::Command; +use tokio; +use tokio::sync::mpsc::{channel, Sender, Receiver}; +use colored::Colorize; #[derive(Parser, Debug)] #[command(version, about, long_about = Some("finds shares, but its written in rust which sometimes gets past EDR!"))] struct Args{ - #[arg(short, long)] - domain: String, + #[arg(short, long, help = "path to save output file Defaults to not saving output.")] + outfile: Option, - #[arg(short, long)] - kdc: String, - - #[arg(short, long, default_value_t = String::from("none"))] - user: String, - - #[arg(short, long, default_value_t = String::from("none"))] - password: String, + #[arg(short, long, help = "number of threads to use, default to 10.")] + threads: Option } -async fn search(dc: String, url: String, dn: String){ - println!("DC: {}", &dc); - println!("URL: {}", &url); - println!("DN: {}", &dn); - let mut con_res = LdapConnAsync::new(&url).await; - if con_res.is_err(){ - let error = con_res.err().unwrap(); - println!("error setting up connection!"); - println!("{}", error); - return; - } - let (conn, mut ldap) = con_res.unwrap(); - Ldap::sasl_gssapi_bind(&mut ldap, &dc).await; - ldap3::drive!(conn); - let search_res = ldap.search(&dn, Scope::Subtree, "objectClass=share", vec![""]).await; - if search_res.is_err(){ - let error = search_res.err().unwrap(); - println!("error running search!"); - println!("{}", error); - return; - } - let rs = search_res.unwrap(); - let results = rs.success().unwrap(); - for entry in results.0{ - println!("{}", SearchEntry::construct(entry).dn); - } - ldap.unbind().await; + +struct ShareFinder{ + id: usize, + tx: Sender, } +async fn find_shares(task: ShareFinder, mut rx: Receiver){ + println!("{} started!", task.id); + task.tx.send(format!("{}:READY!", task.id)).await.unwrap(); + loop{ + let rx_res = rx.recv().await; + if rx_res.is_some(){ + let computer = rx_res.unwrap(); + if computer == String::from("||DONE||"){ + task.tx.send(format!("{}:||DONE||", task.id)).await.unwrap(); + break; + } + println!("scanning {}", computer); + let share_list_res = Command::new("net").arg("view").arg(computer.clone()).arg("/all").output(); + let mut error_string = String::new(); + let mut success_string = String::new(); + if share_list_res.is_ok(){ + let output = share_list_res.unwrap(); + if output.stdout.len() > 0{ + success_string = String::from_utf8_lossy(&output.stdout).to_string(); + } + + if output.stderr.len() > 0{ + error_string = String::from_utf8_lossy(&output.stderr).to_string(); + } + } + else{ + error_string = share_list_res.err().unwrap().to_string(); + } + if error_string.len() > 0{ + eprintln!("{}", "Error listing shares!".red()); + eprint!("{}", error_string.red()); + } + else if success_string.len() > 0{ + for line in success_string.lines(){ + if line.contains("Disk"){ + let share_name = line.split_whitespace().collect::>()[0]; + let share_path = format!("\\\\{}\\{}", computer, share_name); + task.tx.send(format!("{}:{}", task.id, share_path)).await.unwrap(); + } + } + } + } + } +} + + + #[tokio::main] -async fn main() { +async fn main(){ let args = Args::parse(); - let mut existing_con = true; - if args.user != "none".to_owned(){ - existing_con = false; - if args.password == "none".to_owned(){ - println!("if you're supplying a user, we need a password bud!"); - exit(1); + let mut outfile = PathBuf::new(); + let mut threads = 10; + let mut save = false; + if args.outfile.is_some(){ + outfile = args.outfile.unwrap(); + save = true; + } + if args.threads.is_some(){ + threads = args.threads.unwrap(); + } + println!("finding computers..."); + let command_string = String::from("net group \"domain computers\" /domain"); + let mut temp_file = fs::File::create("./temp.bat").unwrap(); + write!(temp_file, "{}", command_string).unwrap(); + let computer_res = Command::new(".\\temp.bat").output(); + let mut error_string = String::new(); + let mut success_string = String::new(); + fs::remove_file("./temp.bat").unwrap(); + if computer_res.is_ok(){ + let output = computer_res.unwrap(); + if output.stdout.len() > 0{ + success_string = String::from_utf8_lossy(&output.stdout).to_string(); + } + else if output.stderr.len() > 0{ + error_string = String::from_utf8_lossy(&output.stderr).to_string(); } } - let domain_controller = format!("{}.{}", args.kdc, args.domain); - let ldap_url = format!("ldap://{}", &domain_controller); - let domain_parts: Vec<&str> = args.domain.split(".").collect(); - let mut domain_string = format!("dc={}", args.domain); - if domain_parts.len() > 1{ - domain_string.clear(); - for part in domain_parts{ - let part_string = format!("dc={},", part); - domain_string.push_str(&part_string); - } - domain_string.pop(); + else{ + error_string = computer_res.err().unwrap().to_string(); + } + if error_string.len() > 0{ + eprintln!("{}", "error getting computers!".red()); + eprintln!("{}", error_string.red()); + exit(1); + } + let mut computers = Vec::new(); + if success_string.len() > 0{ + for line in success_string.lines(){ + if line.contains("$"){ + let words:Vec<&str> = line.split_whitespace().collect(); + for word in words{ + let mut computer_name = word.to_string(); + computer_name.pop(); + println!("{} {}", "found".green(), computer_name.green()); + computers.push(computer_name); + } + } + } + } + println!("computer enumeration finished, starting task finder threads..."); + if threads > computers.len(){ + threads = computers.len(); + } + let (maintx, mut mainrx) = channel(1024); + let mut tasks = Vec::new(); + let mut task_txes = Vec::new(); + for id in 0..threads{ + let (tx,rx) = channel(1); + let new_task = ShareFinder{id, tx: maintx.clone()}; + let new_thread = tokio::spawn(find_shares(new_task, rx)); + tasks.push(new_thread); + task_txes.push(tx.clone()); + } + let mut ready = 0; + let mut current_computer = 0; + let mut finished = false; + let mut done_threads = 0; + loop { + if done_threads == threads{ + break; + } + if !mainrx.is_empty(){ + let rx_res = mainrx.recv().await; + if rx_res.is_some(){ + let message = rx_res.unwrap(); + let message_parts: Vec<&str> = message.split(":").collect(); + let _id: usize = message_parts[0].parse().unwrap(); + let content = message_parts[1]; + match content{ + "READY!" => { + ready += 1; + } + "||DONE||" => { + done_threads += 1; + } + _ => {println!("{}", content.green()); + if save{ + let open_res = OpenOptions::new().append(true).create(true).open(&outfile); + if open_res.is_ok(){ + let mut file = open_res.unwrap(); + let write_res = write!(file, "{}\n", content); + if write_res.is_err(){ + eprintln!("{}", "error writing to outfile!".red()); + eprintln!("{}", write_res.err().unwrap().to_string().red()); + } + } + } + } + } + } + } + if ready == threads{ + let mut sent = false; + if !finished{ + for tx in &task_txes{ + if tx.capacity() > 0{ + tx.send(computers[current_computer].clone()).await.unwrap(); + sent = true; + break; + } + } + if sent{ + current_computer +=1; + if current_computer == computers.len() { + finished = true; + } + } + } + } + if finished{ + for tx in &task_txes{ + let send_res = tx.send(String::from("||DONE||")).await; + if send_res.is_ok(){ + send_res.unwrap(); + } + } + } } - search(domain_controller, ldap_url, domain_string).await; }