finished writing the tool
Should be multithreaded, and fast af!
This commit is contained in:
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -227,6 +227,15 @@ version = "1.0.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
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]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
@@ -1061,6 +1070,7 @@ name = "tetanus_shares"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
|
"colored",
|
||||||
"ldap3",
|
"ldap3",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -5,5 +5,6 @@ edition = "2024"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.5.37", features = ["derive"] }
|
clap = { version = "4.5.37", features = ["derive"] }
|
||||||
|
colored = "3.0.0"
|
||||||
ldap3 = { version = "0.11.5", features = ["gssapi"] }
|
ldap3 = { version = "0.11.5", features = ["gssapi"] }
|
||||||
tokio = { version = "1.44.2", features = ["rt-multi-thread"] }
|
tokio = { version = "1.44.2", features = ["rt-multi-thread"] }
|
||||||
|
|||||||
247
src/main.rs
247
src/main.rs
@@ -1,77 +1,206 @@
|
|||||||
use ldap3::{LdapConn, LdapConnAsync, Scope, SearchEntry};
|
use clap::Parser;
|
||||||
use ldap3::result::Result;
|
|
||||||
use ldap3::Ldap;
|
|
||||||
use clap::{error, Parser};
|
|
||||||
use std::fmt::Debug;
|
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::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)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(version, about, long_about = Some("finds shares, but its written in rust which sometimes gets past EDR!"))]
|
#[command(version, about, long_about = Some("finds shares, but its written in rust which sometimes gets past EDR!"))]
|
||||||
struct Args{
|
struct Args{
|
||||||
#[arg(short, long)]
|
#[arg(short, long, help = "path to save output file Defaults to not saving output.")]
|
||||||
domain: String,
|
outfile: Option<PathBuf>,
|
||||||
|
|
||||||
#[arg(short, long)]
|
#[arg(short, long, help = "number of threads to use, default to 10.")]
|
||||||
kdc: String,
|
threads: Option<usize>
|
||||||
|
|
||||||
#[arg(short, long, default_value_t = String::from("none"))]
|
|
||||||
user: String,
|
|
||||||
|
|
||||||
#[arg(short, long, default_value_t = String::from("none"))]
|
|
||||||
password: String,
|
|
||||||
}
|
}
|
||||||
async fn search(dc: String, url: String, dn: String){
|
|
||||||
println!("DC: {}", &dc);
|
struct ShareFinder{
|
||||||
println!("URL: {}", &url);
|
id: usize,
|
||||||
println!("DN: {}", &dn);
|
tx: Sender<String>,
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn find_shares(task: ShareFinder, mut rx: Receiver<String>){
|
||||||
|
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::<Vec<&str>>()[0];
|
||||||
|
let share_path = format!("\\\\{}\\{}", computer, share_name);
|
||||||
|
task.tx.send(format!("{}:{}", task.id, share_path)).await.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main(){
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
let mut existing_con = true;
|
let mut outfile = PathBuf::new();
|
||||||
if args.user != "none".to_owned(){
|
let mut threads = 10;
|
||||||
existing_con = false;
|
let mut save = false;
|
||||||
if args.password == "none".to_owned(){
|
if args.outfile.is_some(){
|
||||||
println!("if you're supplying a user, we need a password bud!");
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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);
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
search(domain_controller, ldap_url, domain_string).await;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user