added a few functions for dns enumeration

including a function that runs both the records
gathering command and the subdomain bruteforce
command and saves all the output to your notes

I also tweaked the gather contact parsing function
in order to save the output to the enumeration.md

This is important because the
print report information function actually works now!
it will read your enumeration notes and print the
data in a format that should paste into word
easily.
This commit is contained in:
Pyro57000
2025-05-19 16:57:53 -05:00
parent aef65f3b03
commit c4bda0022b
5 changed files with 389 additions and 71 deletions

View File

@@ -365,6 +365,18 @@ dependencies = [
"syn",
]
[[package]]
name = "dns-lookup"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5766087c2235fec47fafa4cfecc81e494ee679d0fd4a59887ea0919bfb0e4fc"
dependencies = [
"cfg-if",
"libc",
"socket2",
"windows-sys 0.48.0",
]
[[package]]
name = "either"
version = "1.13.0"
@@ -1246,6 +1258,7 @@ dependencies = [
"chrono",
"clearscreen",
"directories",
"dns-lookup",
"fs_extra",
"futures-io 0.2.1",
"reqwest",

View File

@@ -7,6 +7,7 @@ edition = "2021"
chrono = "0.4.39"
clearscreen = "3.0.0"
directories = "5.0.1"
dns-lookup = "2.0.4"
fs_extra = "1.3.0"
futures-io = { version = "0.2.0-beta" }
reqwest = {version = "0.12.12", features = ["blocking", "json"]}

View File

@@ -56,6 +56,9 @@ fn help(command: Option<String>){
"show scope" | "ss" | "show s" | "s s" | "scope" => {println!("Command:show scope\nAliases:ss, show s, s s, scope\n\nThis command displays the current project's scope as just the hosts in the scope table in your notes.")},
"port scan command" | "psc" | "nmap command" | "nmc" => {println!("command:port scan command\nAliases:psc,nmap command, nmc\n\nThis command will print the nmap command to manually run a scan to the terminal so you can copy paste it.")},
"update git tools" | "ugt" | "update git" | "ug" => {println!("Command: update git tools\nAliases: update git, ugt, ug\n\nThis command attempts to update the git tools in your tools directory, it will attempt to update every directory as a git project. If the directory is not a git project it should just error out and continue to the next one.")},
"dns records" | "dr" => {println!("Command:dnsrecords\nAliases:dr\n\nThis command will run dns recon inside of your distrobox and save the results to your enumeration notes.")},
"brute force subdomains"| "bsd" | "gobuster dns" | "gd" => {println!("Command:brute force subdomains\nAliases:bsd,gobuster dns, gd\n\nthis command will run gobuster in the project's distrobox and save the results to your notes.")},
"dns enumeration" | "de" | "all dns stuff" | "ads" | "dns stuff" | "ds" => {println!("Command:dns enumeration\nAliases:de, all dns stuff, ads, dns stuff, de\n\nThis command will perform both dns record enumeration with dnsrecon, and subdomain enumeration using gobster inside of your distrobox and save the output to your notes.")},
_ => ()
}
}
@@ -95,6 +98,9 @@ sharpersist command | spc | sharp scheduled task
port scan | ps | nmap | nmap scan | ns | nm
port scan command | psc | nmap command | nmc
update git tools | ugt | update git | ug
dns records | dr
brute force subdomains| bsd | gobuster dns | gd
dns enumeration | de | all dns stuff | ads | dns stuff | ds
help | ? | -h
")
}
@@ -207,6 +213,10 @@ pub fn run_command(cmd: String,
"port scan" | "ps" | "nmap" | "nmap scan" | "ns" | "nm" => {portscan_controls::run_nmap_portscan(&active_project); return None;},
"port scan command" | "psc" | "nmap command" | "nmc" => {portscan_controls::build_nmap_command(&active_project); return None;}
"sharpersist command" | "spc" | "sharp scheduled task" | "sst" => {victim_commands::sharp_persist_command(&tools_dir); return None;},
"dns records" | "dr" => {let dns_handle = enumeration::run_dns_enumeration(&active_project, None, true); return dns_handle;},
"brute force subdomains"| "bsd" | "gobuster dns" | "gd" => {let gobuster_handle = enumeration::bruteforce_subs(&active_project, None,None, true); return gobuster_handle},
"dns enumeration" | "de" | "all dns stuff" | "ads" | "dns stuff" | "ds" => {let all_dns_handle = enumeration::do_all_dns_enumeration(&active_project); return all_dns_handle},
"print report information" | "pri" => {info_controls::print_report_information(&active_project); return None;},
_ => {help(None); println!("\n\n unknown command."); return None;}
}
}

View File

@@ -1,23 +1,30 @@
use core::error;
use std::fmt::write;
use std::fs::{read_to_string, remove_file, OpenOptions};
use std::process::Command;
use std::thread::JoinHandle;
use std::thread::Thread;
use std::thread::{spawn, sleep};
use std::io::{self, Write};
use std::time::Duration;
use dns_lookup::lookup_host;
use crate::enumeration;
use crate::get_user_input;
use crate::Project;
use crate::open_append;
pub fn run_dns_enumeration(project: &Project) -> Option<JoinHandle<()>>{
pub fn run_dns_enumeration(project: &Project, given_domains: Option<&Vec<String>>, standalone: bool) -> Option<JoinHandle<()>>{
let notes_folder = project.notes_folder.clone();
let mut enumeration = notes_folder.clone();
enumeration.push("enumeration.md");
let mut enumeration_file = open_append(&enumeration);
if enumeration_file.is_none(){
let mut enumeration_file_res = open_append(&enumeration);
if enumeration_file_res.is_none(){
println!("error opening enumeration_file!");
println!("try creating it manually.");
return None;
}
let mut enumeration_file = enumeration_file_res.unwrap();
let mut domaind = Vec::new();
if given_domains.is_none(){
loop{
let domain = get_user_input("domain to add? enter DONE in all caps when you're finsihed");
match domain.as_str(){
@@ -25,57 +32,248 @@ pub fn run_dns_enumeration(project: &Project) -> Option<JoinHandle<()>>{
_ => domaind.push(domain),
}
}
/*let dns_handle = Thread::spawn(move || {
for domain in domaind{
}
else{
for domain in given_domains.unwrap(){
domaind.push(domain.to_owned());
}
}
let working_project = project.clone();
let dns_handle = spawn(move || {
for domain in &domaind{
let output_res = Command::new("distrobox")
.arg("enter")
.arg("--root")
.arg(project.boxname)
.arg(working_project.boxname.to_owned())
.arg("--")
.arg("dnsrecon")
.arg("-d")
.arg(domain)
.arg("-c")
.arg("dns_temp.csv")
.output();
if output_res.is_err(){
let error = output_res.err().unwrap();
println!("error running dnsrecon in the project's distrobox!");
println!("From DNS Enumeration Thread: error running dnsrecon in the project's distrobox!");
println!("{}", error);
return;
}
let output = output_res.unwrap().stdout;
let output_string = String::from_utf8_lossy(&output);
let mut recon_data = String::new();
if output_string.contains("Container Setup Complete"){
let parts: Vec<&str> = output_string.split("Container Setup Complete!").collect();
recon_data = parts[1].to_owned();
//sleep(Duration::from_secs(10));
let output_string_res = read_to_string("dns_temp.csv");
if output_string_res.is_err(){
let error = output_string_res.err().unwrap();
println!("From DNS Enumeration Thread: error reading output data!");
println!("{}", error);
return;
}
let output_string = output_string_res.unwrap();
let lines: Vec<&str> = output_string.split("\n").collect();
let mut out_data = String::new();
if standalone{
out_data.push_str("# DNS Enumeration\n");
out_data.push_str("## DNS Records\n");
}
let mut data_vec = Vec::new();
let mut first_line = true;
for line in lines{
if first_line == true{
first_line = false;
}
else{
recon_data = output_string.to_string();
if line.len() > 1{
let words: Vec<&str> = line.split(",").collect();
let domain_name = words[2].to_owned();
let domain_type = words[1].to_owned();
let mut data = String::new();
if words[3].len() > 2{
data = words[3].to_owned();
}
let lines: Vec<&str> = recon_data.split("\n").collect();
let mut out_data = String::new();
out_data.push_str("#DNS Enumeration\n");
out_data.push_str("## DNS Recon\n");
let mut error_vec = Vec::new();
for line in lines{
if line.len() > 2{
let words: Vec<&str> = line.split_whitespace().collect();
if words.len() > 2{
if words[1].contains("ERROR"){
let error_slice = &&words[2..];
for item in error_slice{
error_vec.push(item.to_owned());
else{
data = words[6].to_owned();
}
out_data.push_str("\n\n");
}
else if words[1].contains("INFO"){
if !words.contains(&"Starting enumeration for") || !words.contains(&"Performing General Enumeration"){
let record_type =
let data_line = format!("| {} | {} | {} |", domain_name, domain_type, data);
data_vec.push(data_line);
}
}
}
let domain_header = format!("#### {}\n", domain);
out_data.push_str(&domain_header);
if standalone{
out_data.push_str("#### DNS Records\n");
}
out_data.push_str("| Domain name | Record type | data |\n");
out_data.push_str("| ----------- | ----------- | ---- |\n");
for thang in data_vec{
let out_line = format!("{}\n", thang);
out_data.push_str(&out_line);
}
if standalone{
out_data.push_str("\n---\n");
}
println!("From DNS Enumeration Thread: Finished gathering data for {} writing to notes...", domain);
write!(enumeration_file, "{}", &out_data).unwrap();
let remove_res = remove_file("dns_temp.csv");
if remove_res.is_err(){
println!("From DNS Enumeration Thread: error removing temporay data file!");
println!("From DNS Enumeration Thread: please manually delete dns_temp.csv");
}
}
}
});*/
return None;
});
return Some(dns_handle);
}
pub fn bruteforce_subs(project: &Project, given_domains: Option<&Vec<String>>, given_wordlist: Option<String>, standalone: bool) -> Option<JoinHandle<()>>{
let mut enumeration_path = project.notes_folder.clone();
enumeration_path.push("enumeration.md");
let enumeration_file_res = OpenOptions::new().append(true).create(true).open(enumeration_path);
if enumeration_file_res.is_err(){
let error = enumeration_file_res.err().unwrap();
println!("error opening enumeration notes file!");
println!("{}", error);
return None;
}
let mut enumeration_file = enumeration_file_res.unwrap();
let mut domains = Vec::new();
if given_domains.is_none(){
loop{
let domain = get_user_input("Domain to add? Enter DONE in all caps when done.");
if domain == "DONE".to_owned(){
break;
}
else{
domains.push(domain);
}
}
}
else{
for domain in given_domains.unwrap(){
domains.push(domain.to_owned());
};
}
let mut wordlist = String::new();
if given_wordlist.is_none(){
wordlist = get_user_input("path to wordlist?");
}
else{
wordlist = given_wordlist.unwrap();
}
let working_project = project.clone();
let mut out_data = String::new();
if standalone{
out_data.push_str("# DNS Enumeration\n");
out_data.push_str("## Subdomain Enumeration\n");
}
let gobuster_thread = spawn( move ||{
for domain in domains{
if standalone{
out_data.push_str(format!("#### {}\n", &domain).as_str());
}
let gobuster_cmd_res = Command::new("distrobox")
.arg("enter")
.arg("--root")
.arg(working_project.boxname.to_owned())
.arg("--")
.arg("gobuster")
.arg("dns")
.arg("-d")
.arg(&domain)
.arg("-w")
.arg(wordlist.to_owned())
.output();
if gobuster_cmd_res.is_err(){
let error = gobuster_cmd_res.err().unwrap();
println!("From gobuster thread: Error running gobuster command!");
println!("{}", error);
return;
}
let gobuser_output = gobuster_cmd_res.unwrap().stdout;
println!("From Gobuster Thread: Sudomain enumeration Done!");
let gobuster_string = String::from_utf8_lossy(&gobuser_output);
let mut domain_names = Vec::new();
let lines: Vec<&str> = gobuster_string.split("\n").collect();
for line in lines{
if line.contains("Found:"){
let domain = line.split_whitespace().collect::<Vec<&str>>()[1];
domain_names.push(domain.to_owned());
}
}
out_data.push_str("\n| domain name | ips |\n");
out_data.push_str("| ----------- | --- |\n");
for name in domain_names{
let ips = lookup_host(&name);
if ips.is_ok(){
let mut ip_string = String::new();
for ip in ips.unwrap(){
ip_string = format!("{},{}", ip, ip_string);
}
out_data.push_str(format!("| {} | {} |\n", name, ip_string).as_str());
}
}
}
if standalone{
out_data.push_str("\n---\n");
}
let write_res = write!(enumeration_file, "{}", out_data);
if write_res.is_err(){
let error = write_res.err().unwrap();
println!("FROM Gobuster Thread: error writing notes!");
println!("{}", error);
return;
}
write_res.unwrap();
});
return Some(gobuster_thread);
}
pub fn do_all_dns_enumeration(project: &Project) -> Option<JoinHandle<()>>{
let mut enumeration_path = project.notes_folder.clone();
enumeration_path.push("enumeration.md");
let enumeration_file_res = OpenOptions::new().append(true).create(true).open(enumeration_path);
if enumeration_file_res.is_err(){
let error = enumeration_file_res.err().unwrap();
println!("error opening enumeration notes file!");
println!("{}", error);
return None;
}
let mut enumeration_file = enumeration_file_res.unwrap();
let mut domains = Vec::new();
loop{
let domain = get_user_input("Domain to add? enter DONE in all caps when you're finished");
if domain == "DONE"{
break;
}
else{
domains.push(domain);
}
}
let wordlist = get_user_input("path to wordlist for sub domain bruteforcing?");
let working_project = project.clone();
let all_dns_handle = spawn(move ||{
let mut write_success = true;
let write_res = write!(enumeration_file, "# DNS Enumeration\n");
if write_res.is_err(){
let error = write_res.err().unwrap();
println!("From All DNS thread: Error writing notes file!");
println!("{}", error);
write_success = false;
}
if write_success{
for domain in &domains{
let thread_domain = vec![domain.to_owned()];
write!(enumeration_file, "## {}\n", &domain).unwrap();
write!(enumeration_file, "### DNS Records\n").unwrap();
let dns_enum_thread = run_dns_enumeration(&working_project, Some(&thread_domain), false);
if dns_enum_thread.is_some(){
let _ = dns_enum_thread.unwrap().join();
}
write!(enumeration_file, "### Subdomain Enumeration\n").unwrap();
let gobuster_thread = bruteforce_subs(&working_project, Some(&thread_domain), Some(wordlist.to_owned()), false);
if gobuster_thread.is_some(){
let _ = gobuster_thread.unwrap().join();
}
write!(enumeration_file, "\n---\n").unwrap();
}
}
});
return Some(all_dns_handle);
}

View File

@@ -3,6 +3,7 @@ use std::fs;
use std::fs::create_dir_all;
use std::fs::read_to_string;
use std::fs::OpenOptions;
use std::hash::Hash;
use std::io::BufReader;
use std::io::Write;
use std::path::PathBuf;
@@ -19,6 +20,7 @@ use rodio::{Decoder, OutputStream, Sink};
use crate::get_user_input;
use crate::open_overwrite;
use crate::open_append;
use crate::project_controls::get_projects;
use crate::Project;
pub fn run_initial_enum(project: &Project){
@@ -210,33 +212,116 @@ pub fn open_in_dolphin(folder: &str, project: Project){
.spawn().expect("error opening dolphin");
}
pub fn print_report_information(project: Project){
let mut general_notes_path = project.notes_folder.clone();
let mut finding_notes_path = project.notes_folder.clone();
general_notes_path.push("general.md ");
finding_notes_path.push("findings.md ");
println!("general: {}\nfindings: {}", general_notes_path.display(), finding_notes_path.display());
let general_string = fs::read_to_string(general_notes_path).expect("error opening general notes");
let findings_string = fs::read_to_string(finding_notes_path).expect("error opening findings notes");
let general_lines: Vec<&str> = general_string.split("\n").collect();
let finding_lines: Vec<&str> = findings_string.split("\n").collect();
println!("Scope:");
for line in general_lines{
pub fn print_report_information(project: &Project){
let scope = get_scope_entries(project);
println!("Project: {} {}", project.customer, project.project_name);
if scope.is_some(){
println!("SCOPE:");
for entry in scope.unwrap(){
println!("{}", entry);
}
println!("\n\n");
}
let mut notes_path = project.notes_folder.clone();
notes_path.push("enumeration.md");
if notes_path.exists(){
let enumeration_read_res = fs::read_to_string(notes_path);
if enumeration_read_res.is_err(){
let error = enumeration_read_res.err().unwrap();
println!("error reading enumeration notes!");
println!("{}", error);
}
else{
let enumeration_text = enumeration_read_res.unwrap();
let sections: Vec<&str> = enumeration_text.split("\n---\n").collect();
let mut records_dict = HashMap::new();
let mut subs_dict = HashMap::new();
let mut extrapolated_dict = HashMap::new();
for section in sections{
let mut domain_name = String::new();
let mut domain_records = Vec::new();
let mut subdomains = Vec::new();
let lines: Vec<&str> = section.split("\n").collect();
if section.contains("## DNS Records"){
for line in &lines{
if line.contains("####"){
domain_name = line.split_whitespace().collect::<Vec<&str>>()[1].to_owned();
}
if line.contains("|"){
let split: Vec<&str> = line.split("|").collect();
println!("{}\t{}", split[0], split[2]);
let data: Vec<&str> = line.split("|").collect();
if data.len() == 5{
if !data.contains(&"Domain name") || !data.contains(&"----"){
domain_records.push(format!("{}\t{}\t{}", data[1], data[2], data[3]));
}
}
}
}
records_dict.insert(domain_name.to_owned(), domain_records);
}
if section.contains("Subdomain Enumeration"){
for line in &lines{
if line.contains("####"){
domain_name = line.split_whitespace().collect::<Vec<&str>>()[1].to_owned();
}
if line.contains("|"){
let data: Vec<&str> = line.split("|").collect();
if data.len() == 4{
subdomains.push(format!("{}\t{}", data[1], data[2]));
}
}
}
subs_dict.insert(domain_name.to_owned(), subdomains);
}
if section.contains("Email Enumeration"){
let section_parts: Vec<&str> = section.split("### ").collect();
for part in section_parts{
let mut domain_name = String::new();
let mut emails = Vec::new();
if part.contains("## "){
let part_lines: Vec<&str> = part.split("\n").collect();
for line in part_lines{
if line.contains("##"){
domain_name = line.split_whitespace().collect::<Vec<&str>>()[1].to_owned();
}
}
}
if part.contains("Extrapolated Emails"){
let part_lines: Vec<&str> = part.split("\n").collect();
for line in part_lines{
if line.contains("@"){
emails.push(line.to_owned());
}
}
}
extrapolated_dict.insert(domain_name, emails);
}
}
}
println!("\nDNS RECORDS");
for domain in records_dict.keys(){
println!("{}", domain);
println!("total records:{}", &records_dict[domain].len());
for recrod in &records_dict[domain]{
println!("{}", recrod);
}
}
println!("\nSUB DOMAINS");
for domain in subs_dict.keys(){
println!("{}", domain);
println!("total subs:{}", &subs_dict[domain].len());
for sub in &subs_dict[domain]{
println!("{}", sub);
}
}
println!("\nEXTRAPOLATE EMAILS");
for domain in extrapolated_dict.keys(){
println!("{}", domain);
for email in &extrapolated_dict[domain]{
println!("{}", email);
}
}
println!("Findings");
for line in finding_lines{
if line.contains("# "){
println!("{}", line);
}
else if line.contains("- "){
println!("{}", line)
}
}
}
fn find_file(dir: &PathBuf, file_name: &str) -> Option<String>{
@@ -770,7 +855,7 @@ last
let mut email_text_path = project.files_folder.clone();
let mut email_note_path = project.notes_folder.clone();
email_text_path.push("working/extrapolated_emails.txt");
email_note_path.push("email_enum.md");
email_note_path.push("enumeration.md");
let email_text_res = OpenOptions::new().append(true).create(true).open(email_text_path);
if email_text_res.is_err(){
let error = email_text_res.err().unwrap();
@@ -787,11 +872,17 @@ last
}
let mut email_text_file = email_text_res.unwrap();
let mut email_note_file = email_notes_res.unwrap();
let note_wriet_res = write!(email_note_file, "# Extrapolated Emails: \n");
let mut write_success = true;
let note_wriet_res = write!(email_note_file, "# Email Enumeration\n");
if note_wriet_res.is_err(){
let error = note_wriet_res.err().unwrap();
println!("error writing to email notes file!");
println!("{}", error);
write_success = false;
}
if write_success{
write!(email_note_file, "## {}\n", &domain).unwrap();
write!(email_note_file, "### Extrapolated Emails\n").unwrap();
}
println!("data gathered, printing emails and saving files...\n\n");
for email in &data[&format.to_lowercase().trim()]{
@@ -800,6 +891,9 @@ last
write!(email_note_file, "{}\n", outline).expect("error writing email notes file!");
write!(email_text_file, "{}\n", outline).expect("error writing email text file!");
}
if write_success{
write!(email_note_file, "\n---\n").unwrap();
}
}
pub fn get_all_host_addresses(project: &Project){
@@ -837,3 +931,5 @@ pub fn get_scope_entries(project: &Project) -> Option<Vec<String>>{
}
return Some(hosts);
}