diff --git a/README.md b/README.md index 161d4bf..545ac4e 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ sudo setcap 'cap_net_raw+ep' $(which armada) Armada comes with help docs by running `armada -h`; however, if you want to get started immediately, the typical way to perform a port scan is the following: ``` -armada -t -p +armada -t -p ``` e.g. @@ -35,7 +35,7 @@ armada -t 8.8.8.0/24 -p 1-1000 ``` ### Targets -Armada supports two different kinds of targets at this time: IP addresses (e.g. `1.2.3.4`) and CIDR ranges (e.g. `8.8.8.0/24`). These different kinds of targets can be mix and matched. +Armada supports three different kinds of targets at this time: IP addresses (e.g. `1.2.3.4`), CIDR ranges (e.g. `8.8.8.0/24`), and domain names (e.g. `google.com`). These different kinds of targets can be mix and matched. Additionally, Armada supports three ways of supplying targets: @@ -46,7 +46,7 @@ armada -t 1.2.3.4,8.8.8.0/24 -p 1-1000 A newline delimited targets file ``` -armada --target_file some_ips_and_cidrs.txt -p 1-1000 +armada --target_file some_target_addresses.txt -p 1-1000 ``` or via stdin diff --git a/armada/Cargo.toml b/armada/Cargo.toml index c07fdd8..017281f 100644 --- a/armada/Cargo.toml +++ b/armada/Cargo.toml @@ -23,6 +23,6 @@ console = "0" indicatif = "0" rand = "0" regex = "1" -tokio = { version = "1", features = ["macros", "rt-multi-thread"]} +tokio = { version = "1", features = ["net", "macros", "rt-multi-thread"]} toml = "0" serde = { version = "1.0", features = ["derive"] } diff --git a/armada/src/args.rs b/armada/src/args.rs index 7dc43b4..7fda307 100644 --- a/armada/src/args.rs +++ b/armada/src/args.rs @@ -1,6 +1,8 @@ +use std::collections::HashMap; use std::fs::read_to_string; use std::io::{stdin, BufRead}; use std::net::IpAddr; +use std::net::ToSocketAddrs; use std::str::FromStr; use std::time::Duration; @@ -8,7 +10,6 @@ use armada_lib::{HostIterator, PortIterator}; use atty::Stream; use cidr_utils::cidr::IpCidr; use clap::{crate_version, Arg, ArgGroup, ArgMatches, Command}; -use rand::Rng; use crate::config::get_toml_config; @@ -18,7 +19,9 @@ const DEFAULT_TIMEOUT_IN_MS: u64 = 1_000; pub(crate) struct ArmadaConfig { pub(crate) targets: HostIterator, + pub(crate) target_domains: HashMap, pub(crate) ports: PortIterator, + pub(crate) ip_only: bool, pub(crate) quiet_mode: bool, pub(crate) rate_limit: Option, pub(crate) listening_port: u16, @@ -35,8 +38,9 @@ pub(crate) fn get_armada_config() -> ArmadaConfig { matches = app_config().get_matches_from(args); } - let targets = get_targets(&matches); + let (targets, target_domains) = get_targets(&matches); let ports = get_ports(&matches); + let ip_only = get_ip_only_mode(&matches); let quiet_mode = get_quiet_mode(&matches); let rate_limit = get_rate_limit(&matches); let listening_port = get_listening_port(&matches); @@ -53,7 +57,9 @@ pub(crate) fn get_armada_config() -> ArmadaConfig { ArmadaConfig { targets, + target_domains, ports, + ip_only, quiet_mode, rate_limit, listening_port, @@ -64,7 +70,7 @@ pub(crate) fn get_armada_config() -> ArmadaConfig { } } -fn get_targets(matches: &ArgMatches) -> HostIterator { +fn get_targets(matches: &ArgMatches) -> (HostIterator, HashMap) { let targets: Vec = if let Some(targets_cli) = matches.values_of("targets") { // use targets passed in via cli targets_cli.map(str::to_owned).collect() @@ -80,18 +86,31 @@ fn get_targets(matches: &ArgMatches) -> HostIterator { stdin().lock().lines().filter_map(Result::ok).collect() }; - targets - .into_iter() - .fold(HostIterator::new(), |host_iterator, target_str| { - if let Ok(ip_addr) = IpAddr::from_str(&target_str) { - host_iterator.add_ip(ip_addr) - } else { - // we'll force this to parse. If it fails, then an illegal value was placed into the target list and we should panic here. - let cidr = IpCidr::from_str(&target_str).expect(&format!("Unable to parse target '{}'.", target_str)); + let mut target_domains = HashMap::new(); + ( + targets + .into_iter() + .fold(HostIterator::new(), |host_iterator, target_str| { + if let Ok(ip_addr) = IpAddr::from_str(&target_str) { + host_iterator.add_ip(ip_addr) + } else if let Some(ip_addr) = (target_str.clone(), 0) // resolve ip address of domain + .to_socket_addrs() + .ok() + .map(|mut addrs| addrs.next()) + .flatten() + { + target_domains.insert(ip_addr.ip(), target_str); // store the domain name for this IP so we can print it later + host_iterator.add_ip(ip_addr.ip()) + } else { + // we'll force this to parse. If it fails, then an illegal value was placed into the target list and we should panic here. + let cidr = + IpCidr::from_str(&target_str).expect(&format!("Unable to parse target '{}'.", target_str)); - host_iterator.add_cidr(cidr) - } - }) + host_iterator.add_cidr(cidr) + } + }), + target_domains, + ) } fn get_ports(matches: &ArgMatches) -> PortIterator { @@ -139,6 +158,10 @@ fn get_ports(matches: &ArgMatches) -> PortIterator { }) } +fn get_ip_only_mode(matches: &ArgMatches) -> bool { + matches.is_present("ip_only") +} + fn get_quiet_mode(matches: &ArgMatches) -> bool { matches.is_present("quiet") } @@ -167,7 +190,7 @@ fn get_listening_port(matches: &ArgMatches) -> u16 { .parse::() .expect(&format!("Unable to parse listening port value '{}'.", value)) }) - .unwrap_or_else(|| rand::thread_rng().gen_range(50_000..60_000)) + .unwrap_or_else(|| rand::random_range(50_000..60_000)) } fn get_retries(matches: &ArgMatches) -> u8 { @@ -237,6 +260,10 @@ fn app_config() -> Command<'static> { .value_delimiter(',') .conflicts_with_all(&["top100", "top1000"]) .required_unless_present_any(&["top100", "top1000", "toml_config"])) + .arg(Arg::new("ip_only") + .help("Outputs only resolved IP addresses instead of domain names.") + .long("ip-only") + .takes_value(false)) .arg(Arg::new("quiet") .help("Disables any progress reporting during the scan.") .short('q') diff --git a/armada/src/main.rs b/armada/src/main.rs index c62a763..709f546 100644 --- a/armada/src/main.rs +++ b/armada/src/main.rs @@ -1,13 +1,9 @@ mod args; +mod config; mod ranges; mod run_variants; -mod config; -use std::net::{ - IpAddr, - Ipv4Addr, - Ipv6Addr, -}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use armada_lib::Armada; @@ -17,14 +13,16 @@ use crate::args::ArmadaConfig; async fn main() { let ArmadaConfig { targets, + target_domains, ports, + ip_only, quiet_mode, rate_limit, listening_port, retries, timeout, source_ips, - stream_results + stream_results, } = args::get_armada_config(); let armada = Armada::new(listening_port); @@ -35,13 +33,31 @@ async fn main() { use run_variants::QuietArmada; armada - .run_quiet(targets, ports, source_ipv4, source_ipv6, retries, timeout, rate_limit, stream_results) + .run_quiet( + targets, + ports, + source_ipv4, + source_ipv6, + retries, + timeout, + rate_limit, + stream_results, + ) .await } else { use run_variants::ProgressArmada; armada - .run_with_stats(targets, ports, source_ipv4, source_ipv6, retries, timeout, rate_limit, stream_results) + .run_with_stats( + targets, + ports, + source_ipv4, + source_ipv6, + retries, + timeout, + rate_limit, + stream_results, + ) .await }; @@ -49,7 +65,11 @@ async fn main() { syn_scan_results.sort(); syn_scan_results.into_iter().for_each(|remote| { - println!("{}:{}", remote.ip(), remote.port()); + let ip = match target_domains.get(&remote.ip()) { + Some(domain) if !ip_only => domain, + _ => &remote.ip().to_string() + }; + println!("{}:{}", ip, remote.port()); }); } }