mirror of
https://github.com/edu4rdshl/unimap.git
synced 2026-07-17 23:24:49 +00:00
Initial commit.
This commit is contained in:
parent
7c308a7d74
commit
43b878e0d1
19 changed files with 4892 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
*.txt
|
||||
19
.vscode/launch.json
vendored
Normal file
19
.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug",
|
||||
"program": "${workspaceRoot}/target/debug/${workspaceRootFolderName}",
|
||||
"args": "-f targets.txt -u log.csv --resolvers resolvers.txt --iport 1 --lport 10000 --min-rate 10",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"sourceLanguages": [
|
||||
"rust"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
15
.vscode/tasks.json
vendored
Normal file
15
.vscode/tasks.json
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"type": "shell",
|
||||
"command": "cargo build",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
||||
1844
Cargo.lock
generated
Normal file
1844
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
37
Cargo.toml
Normal file
37
Cargo.toml
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
[package]
|
||||
name = "unimap"
|
||||
version = "0.1.0"
|
||||
authors = ["Eduard Tolosa <edu4rdshl@protonmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
trust-dns-resolver = "0.19.5"
|
||||
rayon = "1.4.0"
|
||||
log = { version = "0.4.11", features = ["std"] }
|
||||
colored = { version = "1.9.3", optional = true }
|
||||
rand = "0.7.3"
|
||||
lazy_static = "1.4.0"
|
||||
serde = { version = "1.0.114", features = ["derive"] }
|
||||
serde_derive = "1.0.114"
|
||||
prettytable-rs = "0.8.0"
|
||||
serde-xml-rs = "0.4.0"
|
||||
webpage = "1.1.0"
|
||||
chrono = "0.4.15"
|
||||
clap = { version = "2.33.1", features = ["yaml"] }
|
||||
config = { version = "0.10.1", features = ["yaml", "json", "toml", "hjson", "ini"] }
|
||||
failure = "0.1.8"
|
||||
|
||||
[target.arm-unknown-linux-gnueabihf.dependencies]
|
||||
openssl = { version = "0.10.30", features = ["vendored"] }
|
||||
|
||||
[target.aarch64-unknown-linux-gnu.dependencies]
|
||||
openssl = { version = "0.10.30", features = ["vendored"] }
|
||||
|
||||
[target.armv7-unknown-linux-gnueabihf.dependencies]
|
||||
openssl = { version = "0.10.30", features = ["vendored"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
atty = "0.2.14"
|
||||
winapi = { version = "0.3.9", features = ["handleapi", "winbase"] }
|
||||
72
src/args.rs
Normal file
72
src/args.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
use {
|
||||
crate::{
|
||||
defaults,
|
||||
logic::validate_target,
|
||||
misc::{return_matches_vec, sanitize_target_string},
|
||||
structs::Args,
|
||||
},
|
||||
chrono::Utc,
|
||||
clap::{load_yaml, value_t, App},
|
||||
std::{collections::HashSet, time::Instant},
|
||||
};
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
pub fn get_args() -> Args {
|
||||
let yaml = load_yaml!("cli.yml");
|
||||
let matches = App::from_yaml(yaml)
|
||||
.version(clap::crate_version!())
|
||||
.get_matches();
|
||||
|
||||
Args {
|
||||
target: {
|
||||
let target = sanitize_target_string(
|
||||
value_t!(matches, "target", String).unwrap_or_else(|_| String::new()),
|
||||
);
|
||||
if validate_target(&target) {
|
||||
target
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
},
|
||||
file_name: if matches.is_present("output") {
|
||||
value_t!(matches, "logs-dir", String).unwrap_or_else(|_| "logs".to_string())
|
||||
+ "/"
|
||||
+ "unimap"
|
||||
+ &Utc::now().format("-log-%Y-%m-%d_%H-%M-%S").to_string()
|
||||
+ ".csv"
|
||||
} else if matches.is_present("unique-output") {
|
||||
matches.value_of("unique-output").unwrap().to_string()
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
logs_dir: value_t!(matches, "logs-dir", String).unwrap_or_else(|_| "logs".to_string()),
|
||||
threads: if (matches.is_present("port-scan")
|
||||
|| matches.is_present("initial-port")
|
||||
|| matches.is_present("last-port"))
|
||||
&& !matches.is_present("threads")
|
||||
{
|
||||
30
|
||||
} else {
|
||||
value_t!(matches, "threads", usize).unwrap_or_else(|_| 50)
|
||||
},
|
||||
version: clap::crate_version!().to_string(),
|
||||
initial_port: value_t!(matches, "initial-port", usize).unwrap_or_else(|_| 1),
|
||||
last_port: value_t!(matches, "last-port", usize).unwrap_or_else(|_| 65535),
|
||||
with_output: matches.is_present("output") || matches.is_present("unique-output"),
|
||||
unique_output_flag: matches.is_present("unique-output"),
|
||||
from_file_flag: matches.is_present("files"),
|
||||
quiet_flag: matches.is_present("quiet"),
|
||||
custom_resolvers: matches.is_present("custom-resolvers"),
|
||||
custom_ports_range: matches.is_present("initial-port") || matches.is_present("last-port"),
|
||||
fast_scan: matches.is_present("fast-scan"),
|
||||
files: return_matches_vec(&matches, "files"),
|
||||
min_rate: value_t!(matches, "min-rate", usize).unwrap_or_else(|_| 30000),
|
||||
resolvers: if matches.is_present("custom-resolvers") {
|
||||
return_matches_vec(&matches, "custom-resolvers")
|
||||
} else {
|
||||
defaults::ipv4_resolvers()
|
||||
},
|
||||
targets: HashSet::new(),
|
||||
time_wasted: Instant::now(),
|
||||
}
|
||||
}
|
||||
84
src/cli.yml
Normal file
84
src/cli.yml
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
name: Unimap
|
||||
author: Eduard Tolosa <edu4rdshl@protonmail.com>
|
||||
settings:
|
||||
- ArgRequiredElseHelp
|
||||
- StrictUtf8
|
||||
about: Scan only once by IP address.
|
||||
args:
|
||||
- target:
|
||||
short: t
|
||||
long: target
|
||||
help: Target host.
|
||||
takes_value: true
|
||||
multiple: false
|
||||
conflicts_with:
|
||||
- files
|
||||
|
||||
- files:
|
||||
short: f
|
||||
long: file
|
||||
help: Use a list of targets writen in a file as input.
|
||||
takes_value: true
|
||||
multiple: true
|
||||
conflicts_with:
|
||||
- target
|
||||
|
||||
- output:
|
||||
short: o
|
||||
long: output
|
||||
help: Write to an output file. The name of the output file will be the target string with TXT format.
|
||||
takes_value: false
|
||||
|
||||
- unique-output:
|
||||
short: u
|
||||
long: unique-output
|
||||
help: Write all the results for a target or a list of targets to a specified filename.
|
||||
takes_value: true
|
||||
multiple: false
|
||||
conflicts_with:
|
||||
- output
|
||||
|
||||
- quiet:
|
||||
short: q
|
||||
long: quiet
|
||||
help: Remove informative messages.
|
||||
takes_value: false
|
||||
|
||||
- threads:
|
||||
help: Number of threads to use to perform the resolution.
|
||||
long: threads
|
||||
takes_value: true
|
||||
|
||||
- custom-resolvers:
|
||||
help: Path to a file (or files) containing a list of DNS IP address. If no specified then 1.6k of built-in valid DNS servers from public-dns.info are used.
|
||||
long: resolvers
|
||||
takes_value: true
|
||||
multiple: true
|
||||
|
||||
- initial-port:
|
||||
help: Initial port to scan. Default 0.
|
||||
long: iport
|
||||
takes_value: true
|
||||
multiple: false
|
||||
|
||||
- last-port:
|
||||
help: Last port to scan. Default 1000.
|
||||
long: lport
|
||||
takes_value: true
|
||||
multiple: false
|
||||
|
||||
- min-rate:
|
||||
help: Nmap --min-rate value for ports scan.
|
||||
long: min-rate
|
||||
takes_value: true
|
||||
|
||||
- fast-scan:
|
||||
help: Use fast scanning for ports (no version detection).
|
||||
long: fast-scan
|
||||
takes_value: false
|
||||
|
||||
- logs-dir:
|
||||
help: Path to save the subdomains HTML data.
|
||||
long: logs-dir
|
||||
takes_value: true
|
||||
multiple: false
|
||||
1957
src/defaults.rs
Normal file
1957
src/defaults.rs
Normal file
File diff suppressed because it is too large
Load diff
2
src/errors.rs
Normal file
2
src/errors.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
pub use failure::{Error, ResultExt};
|
||||
pub type Result<T> = ::std::result::Result<T, Error>;
|
||||
81
src/files.rs
Normal file
81
src/files.rs
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
use {
|
||||
crate::{errors::*, structs::Args},
|
||||
log::error,
|
||||
prettytable::Table,
|
||||
std::{
|
||||
collections::HashSet,
|
||||
fs::{self, File, OpenOptions},
|
||||
io::{BufRead, BufReader, Write},
|
||||
path::Path,
|
||||
},
|
||||
};
|
||||
|
||||
pub fn return_file_targets(args: &Args, mut files: Vec<String>) -> Vec<String> {
|
||||
let mut targets: Vec<String> = Vec::new();
|
||||
files.sort();
|
||||
files.dedup();
|
||||
for f in files {
|
||||
match File::open(&f) {
|
||||
Ok(file) => {
|
||||
for target in BufReader::new(file).lines().flatten() {
|
||||
targets.push(target);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
if args.files.len() == 1 {
|
||||
error!("Can not open file {}. Error: {}\n", f, e);
|
||||
std::process::exit(1)
|
||||
} else if !args.quiet_flag {
|
||||
error!(
|
||||
"Can not open file {}, working with next file. Error: {}\n",
|
||||
f, e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
targets.sort();
|
||||
targets.dedup();
|
||||
targets.iter().map(|target| target.to_lowercase()).collect()
|
||||
}
|
||||
|
||||
pub fn table_to_file(table: &Table, file_name: Option<std::fs::File>) -> Result<()> {
|
||||
table.to_csv(file_name.unwrap())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn return_output_file(args: &Args) -> Option<File> {
|
||||
if args.file_name.is_empty() || !args.with_output {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
OpenOptions::new()
|
||||
.append(true)
|
||||
.create(true)
|
||||
.open(&args.file_name)
|
||||
.with_context(|_| format!("Can't create file {}", &args.file_name))
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_full_path(full_path: &str) -> bool {
|
||||
(Path::new(full_path).exists() && Path::new(full_path).is_dir())
|
||||
|| fs::create_dir_all(full_path).is_ok()
|
||||
}
|
||||
|
||||
pub fn delete_files(paths: &HashSet<String>) {
|
||||
for file in paths {
|
||||
if Path::new(&file).exists() {
|
||||
match std::fs::remove_file(&file) {
|
||||
Ok(_) => (),
|
||||
Err(e) => error!("Error deleting the file {}. Description: {}", &file, e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn string_to_file(data: String, mut file: File) -> Result<()> {
|
||||
file.write_all(data.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
21
src/lib.rs
Normal file
21
src/lib.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate prettytable;
|
||||
|
||||
extern crate log;
|
||||
|
||||
pub mod args;
|
||||
pub mod errors;
|
||||
pub mod files;
|
||||
pub mod logger;
|
||||
pub mod resolver_engine;
|
||||
|
||||
mod defaults;
|
||||
mod logic;
|
||||
mod misc;
|
||||
mod networking;
|
||||
mod nmap;
|
||||
mod structs;
|
||||
121
src/logger.rs
Normal file
121
src/logger.rs
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
#[cfg(windows)]
|
||||
extern crate atty;
|
||||
#[cfg(feature = "chrono")]
|
||||
extern crate chrono;
|
||||
#[cfg(feature = "colored")]
|
||||
extern crate colored;
|
||||
extern crate log;
|
||||
#[cfg(windows)]
|
||||
extern crate winapi;
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
use chrono::Local;
|
||||
#[cfg(feature = "colored")]
|
||||
use colored::*;
|
||||
use log::{Level, Log, Metadata, Record, SetLoggerError};
|
||||
struct SimpleLogger {
|
||||
level: Level,
|
||||
}
|
||||
|
||||
impl Log for SimpleLogger {
|
||||
fn enabled(&self, metadata: &Metadata) -> bool {
|
||||
metadata.level() <= self.level
|
||||
}
|
||||
|
||||
fn log(&self, record: &Record) {
|
||||
let target = if !record.target().is_empty() {
|
||||
record.target()
|
||||
} else {
|
||||
record.module_path().unwrap_or_default()
|
||||
};
|
||||
if target.contains("unimap") && self.enabled(record.metadata()) {
|
||||
let level_string = {
|
||||
#[cfg(feature = "colored")]
|
||||
{
|
||||
match record.level() {
|
||||
Level::Error => record.level().to_string().red(),
|
||||
Level::Warn => record.level().to_string().yellow(),
|
||||
Level::Info => record.level().to_string().cyan(),
|
||||
Level::Debug => record.level().to_string().purple(),
|
||||
Level::Trace => record.level().to_string().normal(),
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "colored"))]
|
||||
{
|
||||
record.level().to_string()
|
||||
}
|
||||
};
|
||||
#[cfg(feature = "chrono")]
|
||||
{
|
||||
print!(
|
||||
"\n{} [{}] {}",
|
||||
Local::now().format("%Y-%m-%d %H:%M:%S,%3f"),
|
||||
level_string,
|
||||
record.args()
|
||||
);
|
||||
}
|
||||
#[cfg(not(feature = "chrono"))]
|
||||
{
|
||||
print!("\n[{}] {}", level_string, record.args());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&self) {}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn set_up_color_terminal() {
|
||||
use atty::Stream;
|
||||
|
||||
if atty::is(Stream::Stdout) {
|
||||
unsafe {
|
||||
use winapi::um::consoleapi::*;
|
||||
use winapi::um::handleapi::*;
|
||||
use winapi::um::processenv::*;
|
||||
use winapi::um::winbase::*;
|
||||
use winapi::um::wincon::*;
|
||||
|
||||
let stdout = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
|
||||
if stdout == INVALID_HANDLE_VALUE {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut mode: winapi::shared::minwindef::DWORD = 0;
|
||||
|
||||
if GetConsoleMode(stdout, &mut mode) == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
SetConsoleMode(stdout, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_with_level(level: Level) -> Result<(), SetLoggerError> {
|
||||
#[cfg(all(windows, feature = "colored"))]
|
||||
set_up_color_terminal();
|
||||
|
||||
let logger = SimpleLogger { level };
|
||||
log::set_boxed_logger(Box::new(logger))?;
|
||||
log::set_max_level(level.to_level_filter());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn init() -> Result<(), SetLoggerError> {
|
||||
init_with_level(Level::Trace)
|
||||
}
|
||||
|
||||
pub fn init_by_env() {
|
||||
match std::env::var("UNIMAP_LOG_LEVEL") {
|
||||
Ok(x) => match x.to_lowercase().as_str() {
|
||||
"trace" => init_with_level(log::Level::Trace).unwrap(),
|
||||
"debug" => init_with_level(log::Level::Debug).unwrap(),
|
||||
"info" => init_with_level(log::Level::Info).unwrap(),
|
||||
"warn" => init_with_level(log::Level::Warn).unwrap(),
|
||||
_ => init_with_level(log::Level::Error).unwrap(),
|
||||
},
|
||||
_ => init_with_level(log::Level::Error).unwrap(),
|
||||
}
|
||||
}
|
||||
31
src/logic.rs
Normal file
31
src/logic.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
use lazy_static;
|
||||
|
||||
lazy_static! {
|
||||
static ref SPECIAL_CHARS: Vec<char> = vec![
|
||||
'[', ']', '{', '}', '(', ')', '*', '|', ':', '<', '>', '/', '\\', '%', '&', '¿', '?', '¡',
|
||||
'!', '#', '\'', ' ', ','
|
||||
];
|
||||
}
|
||||
|
||||
pub fn validate_target(target: &str) -> bool {
|
||||
!target.starts_with('.')
|
||||
&& target.contains('.')
|
||||
&& !target.contains(&SPECIAL_CHARS[..])
|
||||
&& target.chars().all(|c| c.is_ascii())
|
||||
}
|
||||
|
||||
pub fn null_ip_checker(ip: &str) -> String {
|
||||
if ip.is_empty() {
|
||||
String::from("NULL")
|
||||
} else {
|
||||
ip.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn return_ports_string(ports: &Vec<String>) -> String {
|
||||
if ports.is_empty() {
|
||||
String::from("NULL")
|
||||
} else {
|
||||
ports.join(";")
|
||||
}
|
||||
}
|
||||
46
src/main.rs
Normal file
46
src/main.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
use {
|
||||
log::{error, Level},
|
||||
std::{collections::HashSet, iter::FromIterator},
|
||||
unimap::{args, errors::*, files::return_file_targets, logger, resolver_engine},
|
||||
};
|
||||
|
||||
fn run() -> Result<()> {
|
||||
if std::env::var("UNIMAP_LOG_LEVEL").is_ok() {
|
||||
logger::init_by_env()
|
||||
} else {
|
||||
logger::init_with_level(Level::Info).unwrap()
|
||||
}
|
||||
let mut arguments = args::get_args();
|
||||
if !arguments.files.is_empty() {
|
||||
arguments.targets =
|
||||
HashSet::from_iter(return_file_targets(&arguments, arguments.files.clone()))
|
||||
} else {
|
||||
arguments.targets.insert(arguments.target.clone());
|
||||
}
|
||||
|
||||
if arguments.targets.len() < 50 {
|
||||
arguments.threads = arguments.targets.len()
|
||||
}
|
||||
|
||||
rayon::ThreadPoolBuilder::new()
|
||||
.num_threads(arguments.threads)
|
||||
.build_global()
|
||||
.unwrap();
|
||||
|
||||
if !arguments.target.is_empty() || !arguments.files.is_empty() {
|
||||
resolver_engine::async_resolver_all(&mut arguments)
|
||||
} else {
|
||||
error!("Error: Target is empty or invalid!\n");
|
||||
std::process::exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if let Err(err) = run() {
|
||||
error!("Error: {}", err);
|
||||
for cause in err.iter_chain().skip(1) {
|
||||
error!("Error description: {}\n", cause);
|
||||
}
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
34
src/misc.rs
Normal file
34
src/misc.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
pub fn sanitize_target_string(target: String) -> String {
|
||||
target
|
||||
.replace("www.", "")
|
||||
.replace("https://", "")
|
||||
.replace("http://", "")
|
||||
.replace("/", "")
|
||||
}
|
||||
|
||||
pub fn return_matches_vec(matches: &clap::ArgMatches, value: &str) -> Vec<String> {
|
||||
if matches.is_present(value) {
|
||||
matches
|
||||
.values_of(value)
|
||||
.unwrap()
|
||||
.map(str::to_owned)
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn return_matches_hashset(matches: &clap::ArgMatches, value: &str) -> HashSet<String> {
|
||||
if matches.is_present(value) {
|
||||
matches
|
||||
.values_of(value)
|
||||
.unwrap()
|
||||
.map(str::to_owned)
|
||||
.collect()
|
||||
} else {
|
||||
HashSet::new()
|
||||
}
|
||||
}
|
||||
68
src/networking.rs
Normal file
68
src/networking.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
use {
|
||||
log::error,
|
||||
rand::Rng,
|
||||
std::net::{IpAddr, Ipv4Addr},
|
||||
trust_dns_resolver::{
|
||||
config::{NameServerConfigGroup, ResolverConfig, ResolverOpts},
|
||||
proto::rr::RecordType,
|
||||
Resolver,
|
||||
},
|
||||
};
|
||||
|
||||
pub fn get_records(resolver: &Resolver, domain: &str, record_type: RecordType) -> String {
|
||||
if let Ok(rdata) = resolver.lookup(&domain, record_type) {
|
||||
let mut record_data: Vec<String> = Vec::new();
|
||||
if record_type == RecordType::AAAA {
|
||||
record_data = rdata
|
||||
.iter()
|
||||
.filter_map(|rdata| rdata.as_aaaa())
|
||||
.map(|ipv6| ipv6.to_string())
|
||||
.collect();
|
||||
} else if record_type == RecordType::A {
|
||||
record_data = rdata
|
||||
.iter()
|
||||
.filter_map(|rdata| rdata.as_a())
|
||||
.map(|ipv4| ipv4.to_string())
|
||||
.collect();
|
||||
} else if record_type == RecordType::CNAME {
|
||||
record_data = rdata
|
||||
.iter()
|
||||
.filter_map(|rdata| rdata.as_cname())
|
||||
.map(|name| {
|
||||
let name = name.to_string();
|
||||
name[..name.len() - 1].to_owned()
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
record_data
|
||||
.iter()
|
||||
.next()
|
||||
.expect("Failed retrieving records data.")
|
||||
.to_owned()
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_resolver(resolvers_ips: &[Ipv4Addr], opts: &ResolverOpts) -> Resolver {
|
||||
match Resolver::new(
|
||||
ResolverConfig::from_parts(
|
||||
None,
|
||||
vec![],
|
||||
NameServerConfigGroup::from_ips_clear(
|
||||
&[IpAddr::V4(
|
||||
resolvers_ips[rand::thread_rng().gen_range(0, resolvers_ips.len())],
|
||||
)],
|
||||
53,
|
||||
),
|
||||
),
|
||||
*opts,
|
||||
) {
|
||||
Ok(resolver) => resolver,
|
||||
|
||||
Err(e) => {
|
||||
error!("Failed to create the resolver. Error: {}\n", e);
|
||||
std::process::exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
183
src/nmap.rs
Normal file
183
src/nmap.rs
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
use {
|
||||
crate::resolver_engine,
|
||||
log::error,
|
||||
std::{path::Path, process::Command},
|
||||
};
|
||||
|
||||
lazy_static! {
|
||||
static ref NMAP_DNS_RESOLVERS: String = resolver_engine::RESOLVERS
|
||||
.clone()
|
||||
.iter()
|
||||
.map(|f| f.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(",");
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Root {
|
||||
pub nmaprun: Nmaprun,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Nmaprun {
|
||||
pub scanner: String,
|
||||
pub args: String,
|
||||
pub start: String,
|
||||
pub startstr: String,
|
||||
pub version: String,
|
||||
pub xmloutputversion: String,
|
||||
pub host: Option<Host>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Host {
|
||||
pub starttime: String,
|
||||
pub endtime: String,
|
||||
pub status: Status,
|
||||
pub address: Address,
|
||||
pub hostnames: Hostnames,
|
||||
pub ports: Option<Ports>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Status {
|
||||
pub state: String,
|
||||
pub reason: String,
|
||||
#[serde(rename = "reason_ttl")]
|
||||
pub reason_ttl: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Address {
|
||||
pub addr: Option<String>,
|
||||
pub addrtype: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Hostnames {
|
||||
pub hostname: Option<Hostname>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Hostname {
|
||||
pub name: String,
|
||||
#[serde(rename = "type")]
|
||||
pub type_field: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Ports {
|
||||
pub port: Vec<Port>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Hash)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Port {
|
||||
pub protocol: String,
|
||||
pub portid: String,
|
||||
pub state: State,
|
||||
pub service: Option<Service>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Hash)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct State {
|
||||
pub state: String,
|
||||
pub reason: String,
|
||||
#[serde(rename = "reason_ttl")]
|
||||
pub reason_ttl: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Hash)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Service {
|
||||
pub name: String,
|
||||
pub method: String,
|
||||
pub conf: String,
|
||||
pub product: Option<String>,
|
||||
pub ostype: Option<String>,
|
||||
pub version: Option<String>,
|
||||
pub extrainfo: Option<String>,
|
||||
}
|
||||
|
||||
pub fn get_nmap_data(
|
||||
filename: &str,
|
||||
host: &str,
|
||||
min_rate: usize,
|
||||
initial_port: usize,
|
||||
last_port: usize,
|
||||
fast_scan: bool,
|
||||
) -> Result<Nmaprun, serde_xml_rs::Error> {
|
||||
let _ports_range = format!("{}-{}", initial_port, last_port);
|
||||
let min_rate = min_rate.to_string();
|
||||
let nmap_args = if fast_scan {
|
||||
vec![
|
||||
"nmap",
|
||||
"-n",
|
||||
"--dns-servers",
|
||||
&NMAP_DNS_RESOLVERS,
|
||||
"-Pn",
|
||||
"--host-timeout",
|
||||
"5m",
|
||||
"--min-rate",
|
||||
&min_rate,
|
||||
"-sS",
|
||||
"--open",
|
||||
"-dd",
|
||||
"-T4",
|
||||
"--max-retries",
|
||||
"2",
|
||||
"-oX",
|
||||
filename,
|
||||
host,
|
||||
]
|
||||
} else {
|
||||
vec![
|
||||
"nmap",
|
||||
"-n",
|
||||
"--dns-servers",
|
||||
&NMAP_DNS_RESOLVERS,
|
||||
"-Pn",
|
||||
"--host-timeout",
|
||||
"10m",
|
||||
"-sV",
|
||||
"--min-rate",
|
||||
&min_rate,
|
||||
"-sS",
|
||||
"-p-",
|
||||
"--open",
|
||||
"-dd",
|
||||
"-T4",
|
||||
"--max-retries",
|
||||
"2",
|
||||
"-oX",
|
||||
filename,
|
||||
host,
|
||||
]
|
||||
};
|
||||
match Command::new("sudo").args(&nmap_args).output() {
|
||||
Ok(_) => {
|
||||
if Path::new(&filename).exists() && Path::new(&filename).is_file() {
|
||||
serde_xml_rs::from_str(&std::fs::read_to_string(filename).unwrap_or_default())
|
||||
} else {
|
||||
error!("Error executing nmap, you need root permissions. Leaving.\n");
|
||||
std::process::exit(1)
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Error waiting command to finish for {}, continuing with remaining hosts. Description: {}",
|
||||
host, e
|
||||
);
|
||||
Ok(Nmaprun::default())
|
||||
}
|
||||
}
|
||||
}
|
||||
234
src/resolver_engine.rs
Normal file
234
src/resolver_engine.rs
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
use {
|
||||
crate::{
|
||||
args,
|
||||
errors::*,
|
||||
files, logic, networking,
|
||||
nmap::{self, Nmaprun},
|
||||
structs::{Args, ResolvData},
|
||||
},
|
||||
log::{error, info},
|
||||
prettytable,
|
||||
prettytable::Table,
|
||||
rayon::prelude::*,
|
||||
std::{
|
||||
collections::{HashMap, HashSet},
|
||||
net::Ipv4Addr,
|
||||
time::Duration,
|
||||
},
|
||||
trust_dns_resolver::{config::ResolverOpts, proto::rr::RecordType},
|
||||
};
|
||||
|
||||
lazy_static! {
|
||||
pub static ref RESOLVERS: Vec<Ipv4Addr> = {
|
||||
let args = args::get_args();
|
||||
let mut resolver_ips = Vec::new();
|
||||
if args.custom_resolvers {
|
||||
for r in &files::return_file_targets(&args, args.resolvers.clone()) {
|
||||
match r.parse::<Ipv4Addr>() {
|
||||
Ok(ip) => resolver_ips.push(ip),
|
||||
Err(e) => {
|
||||
error!("Error parsing the {} IP from resolvers file to IP address. Please check and try again. Error: {}\n", r, e);
|
||||
std::process::exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for r in args.resolvers {
|
||||
match r.parse::<Ipv4Addr>() {
|
||||
Ok(ip) => resolver_ips.push(ip),
|
||||
Err(e) => {
|
||||
error!("Error parsing the {} IP from resolvers file to IP address. Please check and try again. Error: {}\n", r, e);
|
||||
std::process::exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
resolver_ips
|
||||
};
|
||||
}
|
||||
|
||||
pub fn async_resolver_all(args: &mut Args) -> Result<()> {
|
||||
if args.with_output && !args.unique_output_flag && !files::check_full_path(&args.logs_dir) {
|
||||
error!(
|
||||
"Error creating the logs folder in the path {}. Leaving...",
|
||||
&args.logs_dir
|
||||
);
|
||||
std::process::exit(1)
|
||||
}
|
||||
|
||||
if !args.quiet_flag {
|
||||
info!(
|
||||
"Performing asynchronous resolution for {} targets with {} threads, it will take a while...\n",
|
||||
args.targets.len(), args.threads
|
||||
)
|
||||
}
|
||||
|
||||
let data = async_resolver_engine(&args, args.targets.clone());
|
||||
|
||||
let mut table = Table::new();
|
||||
table.set_titles(row![
|
||||
bcFg => "HOST",
|
||||
"IP",
|
||||
"OPEN PORTS",
|
||||
"SERVICES"
|
||||
]);
|
||||
for (target, resolv_data) in &data {
|
||||
if !resolv_data.ip.is_empty() {
|
||||
let mut services_table = Table::new();
|
||||
for port_data in &resolv_data.ports_data {
|
||||
services_table
|
||||
.add_row(row![bc => &format!("PORT => {}", port_data.portid.clone())]);
|
||||
services_table.add_row(
|
||||
row![c => &format!("SERVICE: {}", port_data.service.clone().unwrap_or_default().name)],
|
||||
);
|
||||
services_table.add_row(row![c => &format!("VERSION: {}" ,port_data
|
||||
.service.clone().unwrap_or_default()
|
||||
.version
|
||||
.clone()
|
||||
.unwrap_or_else(|| "NULL".to_string()))]);
|
||||
services_table.add_row(row![c => &format!("PRODUCT: {}", port_data
|
||||
.service.clone().unwrap_or_default()
|
||||
.product
|
||||
.clone()
|
||||
.unwrap_or_else(|| "NULL".to_string()))]);
|
||||
services_table.add_row(row![c => &format!("OS TYPE: {}", port_data
|
||||
.service.clone().unwrap_or_default()
|
||||
.ostype
|
||||
.clone()
|
||||
.unwrap_or_else(|| "NULL".to_string()))]);
|
||||
services_table.add_row(row![c => &format!("EXTRA INFO: {}", port_data
|
||||
.service.clone().unwrap_or_default()
|
||||
.extrainfo
|
||||
.clone()
|
||||
.unwrap_or_else(|| "NULL".to_string()))]);
|
||||
}
|
||||
table.add_row(row![ d =>
|
||||
target,
|
||||
logic::null_ip_checker(&resolv_data.ip),
|
||||
logic::return_ports_string(
|
||||
&resolv_data
|
||||
.ports_data
|
||||
.iter()
|
||||
.map(|f| f.portid.clone())
|
||||
.collect(),
|
||||
),
|
||||
services_table,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if args.with_output && !args.targets.is_empty() {
|
||||
if files::table_to_file(&table, files::return_output_file(&args)).is_err()
|
||||
&& !args.quiet_flag
|
||||
{
|
||||
error!(
|
||||
"An error occurred while writing the output file {}.\n",
|
||||
args.file_name
|
||||
)
|
||||
}
|
||||
}
|
||||
if !args.quiet_flag {
|
||||
table.printstd();
|
||||
}
|
||||
|
||||
if ((args.with_output && !args.unique_output_flag) || args.unique_output_flag)
|
||||
&& !args.quiet_flag
|
||||
{
|
||||
info!(
|
||||
"Job finished in {} seconds.\n",
|
||||
args.time_wasted.elapsed().as_secs()
|
||||
);
|
||||
info!("Logfile saved in {}\n\n", args.file_name);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn async_resolver_engine(args: &Args, targets: HashSet<String>) -> HashMap<String, ResolvData> {
|
||||
let mut opts = ResolverOpts::default();
|
||||
opts.timeout = Duration::from_secs(2);
|
||||
|
||||
let resolv_data: HashMap<String, ResolvData> = targets
|
||||
.par_iter()
|
||||
.map(|target| {
|
||||
let fqdn_target = format!("{}.", target);
|
||||
let mut resolv_data = ResolvData::default();
|
||||
resolv_data.ip = networking::get_records(
|
||||
&networking::get_resolver(&RESOLVERS, &opts),
|
||||
&fqdn_target,
|
||||
RecordType::A,
|
||||
);
|
||||
(target.to_owned(), resolv_data)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut nmap_ips: HashSet<String> = resolv_data
|
||||
.iter()
|
||||
.map(|(_, resolv_data)| resolv_data.ip.clone())
|
||||
.collect();
|
||||
|
||||
nmap_ips.retain(|ip| !ip.is_empty());
|
||||
|
||||
let nmap_data: HashMap<String, Nmaprun> = nmap_ips
|
||||
.par_iter()
|
||||
.map(|ip| {
|
||||
let filename = format!("{}.xml", &ip);
|
||||
match nmap::get_nmap_data(
|
||||
&filename,
|
||||
&ip,
|
||||
args.min_rate,
|
||||
args.initial_port,
|
||||
args.last_port,
|
||||
args.fast_scan,
|
||||
) {
|
||||
Ok(nmap_data) => {
|
||||
nmap_data
|
||||
.host
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.ports
|
||||
.unwrap_or_default()
|
||||
.port
|
||||
.retain(|f| f.state.state == "open");
|
||||
match std::fs::remove_file(&filename) {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
error!("Error removing filename {}. Description: {}", &filename, e)
|
||||
}
|
||||
};
|
||||
(ip.clone(), nmap_data)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error scanning the ip {}. Description: {}", &ip, e);
|
||||
(String::new(), Nmaprun::default())
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
resolv_data
|
||||
.iter()
|
||||
.map(|(target, resolv_data)| {
|
||||
(
|
||||
target.clone(),
|
||||
ResolvData {
|
||||
ip: resolv_data.ip.clone(),
|
||||
ports_data: if resolv_data.ip.is_empty() {
|
||||
resolv_data.ports_data.clone()
|
||||
} else {
|
||||
nmap_data
|
||||
.get_key_value(&resolv_data.ip)
|
||||
.unwrap()
|
||||
.1
|
||||
.host
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.ports
|
||||
.unwrap_or_default()
|
||||
.port
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
41
src/structs.rs
Normal file
41
src/structs.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
use {
|
||||
crate::nmap::Port,
|
||||
std::{collections::HashSet, time::Instant},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Args {
|
||||
pub target: String,
|
||||
pub file_name: String,
|
||||
pub version: String,
|
||||
pub logs_dir: String,
|
||||
pub threads: usize,
|
||||
pub initial_port: usize,
|
||||
pub last_port: usize,
|
||||
pub with_output: bool,
|
||||
pub unique_output_flag: bool,
|
||||
pub min_rate: usize,
|
||||
pub from_file_flag: bool,
|
||||
pub quiet_flag: bool,
|
||||
pub custom_resolvers: bool,
|
||||
pub custom_ports_range: bool,
|
||||
pub fast_scan: bool,
|
||||
pub files: Vec<String>,
|
||||
pub resolvers: Vec<String>,
|
||||
pub targets: HashSet<String>,
|
||||
pub time_wasted: Instant,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct ResolvData {
|
||||
pub ip: String,
|
||||
pub ports_data: Vec<Port>,
|
||||
}
|
||||
impl ResolvData {
|
||||
pub fn default() -> ResolvData {
|
||||
ResolvData {
|
||||
ip: String::new(),
|
||||
ports_data: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue