From 337e50c8520968aaa98dd0ca4d2bb603a6b71d38 Mon Sep 17 00:00:00 2001 From: Amelia Rossi Date: Fri, 6 Jun 2025 16:48:31 -0500 Subject: [PATCH 1/6] add host resolution (untested) --- armada/Cargo.toml | 2 +- armada/src/args.rs | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) 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..ff65755 100644 --- a/armada/src/args.rs +++ b/armada/src/args.rs @@ -1,6 +1,7 @@ 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; @@ -85,6 +86,13 @@ fn get_targets(matches: &ArgMatches) -> HostIterator { .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 Ok(mut addresses) = (target_str.clone(), 0).to_socket_addrs() { + if let Some(ip_addr) = addresses.next() { + println!("{}", ip_addr); + host_iterator.add_ip(ip_addr.ip()) + } else { + host_iterator + } } 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)); From 4965147bb53b88fddcf2c8d316bf3ece23f8ae86 Mon Sep 17 00:00:00 2001 From: Amelia Rossi Date: Fri, 6 Jun 2025 23:28:12 -0500 Subject: [PATCH 2/6] remove deprecated rand function --- armada/src/args.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/armada/src/args.rs b/armada/src/args.rs index ff65755..b7707c7 100644 --- a/armada/src/args.rs +++ b/armada/src/args.rs @@ -9,7 +9,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; @@ -88,7 +87,6 @@ fn get_targets(matches: &ArgMatches) -> HostIterator { host_iterator.add_ip(ip_addr) } else if let Ok(mut addresses) = (target_str.clone(), 0).to_socket_addrs() { if let Some(ip_addr) = addresses.next() { - println!("{}", ip_addr); host_iterator.add_ip(ip_addr.ip()) } else { host_iterator @@ -175,7 +173,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 { From 5c4d729bdf4b637057453e51a63ebaa6750b9e63 Mon Sep 17 00:00:00 2001 From: Amelia Rossi Date: Fri, 6 Jun 2025 23:29:09 -0500 Subject: [PATCH 3/6] rewrite target resolution statement --- armada/src/args.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/armada/src/args.rs b/armada/src/args.rs index b7707c7..bdcaa48 100644 --- a/armada/src/args.rs +++ b/armada/src/args.rs @@ -85,12 +85,13 @@ fn get_targets(matches: &ArgMatches) -> HostIterator { .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 Ok(mut addresses) = (target_str.clone(), 0).to_socket_addrs() { - if let Some(ip_addr) = addresses.next() { - host_iterator.add_ip(ip_addr.ip()) - } else { - host_iterator - } + } 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() + { + 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)); From 15b43bfb3e6ac2feab7fa888299312c7385b8cae Mon Sep 17 00:00:00 2001 From: Amelia Rossi Date: Fri, 6 Jun 2025 23:36:37 -0500 Subject: [PATCH 4/6] update readme to include host resolution --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 From 4e5530b77ff4653ea5ed554cebeb0e7eb004af31 Mon Sep 17 00:00:00 2001 From: Amelia Rossi Date: Sat, 7 Jun 2025 19:14:27 -0500 Subject: [PATCH 5/6] add printing domain names in output --- armada/src/args.rs | 49 +++++++++++++++++++++++++++------------------- armada/src/main.rs | 40 +++++++++++++++++++++++++++---------- 2 files changed, 59 insertions(+), 30 deletions(-) diff --git a/armada/src/args.rs b/armada/src/args.rs index bdcaa48..6d4aa28 100644 --- a/armada/src/args.rs +++ b/armada/src/args.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::fs::read_to_string; use std::io::{stdin, BufRead}; use std::net::IpAddr; @@ -18,6 +19,7 @@ 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) quiet_mode: bool, pub(crate) rate_limit: Option, @@ -35,7 +37,7 @@ 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 quiet_mode = get_quiet_mode(&matches); let rate_limit = get_rate_limit(&matches); @@ -53,6 +55,7 @@ pub(crate) fn get_armada_config() -> ArmadaConfig { ArmadaConfig { targets, + target_domains, ports, quiet_mode, rate_limit, @@ -64,7 +67,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,25 +83,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 if let Some(ip_addr) = (target_str.clone(), 0) // resolve ip address of domain - .to_socket_addrs() - .ok() - .map(|mut addrs| addrs.next()) - .flatten() - { - 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)); + 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 { diff --git a/armada/src/main.rs b/armada/src/main.rs index c62a763..444193e 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,6 +13,7 @@ use crate::args::ArmadaConfig; async fn main() { let ArmadaConfig { targets, + target_domains, ports, quiet_mode, rate_limit, @@ -24,7 +21,7 @@ async fn main() { retries, timeout, source_ips, - stream_results + stream_results, } = args::get_armada_config(); let armada = Armada::new(listening_port); @@ -35,13 +32,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 +64,12 @@ async fn main() { syn_scan_results.sort(); syn_scan_results.into_iter().for_each(|remote| { - println!("{}:{}", remote.ip(), remote.port()); + let ip = if let Some(domain) = target_domains.get(&remote.ip()) { // use stored domain name if one exists + domain + } else { + &remote.ip().to_string() + }; + println!("{}:{}", ip, remote.port()); }); } } From cbc136234063befb540c40a3de9d594c081ae847 Mon Sep 17 00:00:00 2001 From: Amelia Rossi Date: Sat, 7 Jun 2025 19:30:34 -0500 Subject: [PATCH 6/6] add json output only option --- armada/src/args.rs | 11 +++++++++++ armada/src/main.rs | 8 ++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/armada/src/args.rs b/armada/src/args.rs index 6d4aa28..7fda307 100644 --- a/armada/src/args.rs +++ b/armada/src/args.rs @@ -21,6 +21,7 @@ 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, @@ -39,6 +40,7 @@ pub(crate) fn get_armada_config() -> ArmadaConfig { 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); @@ -57,6 +59,7 @@ pub(crate) fn get_armada_config() -> ArmadaConfig { targets, target_domains, ports, + ip_only, quiet_mode, rate_limit, listening_port, @@ -155,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") } @@ -253,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 444193e..709f546 100644 --- a/armada/src/main.rs +++ b/armada/src/main.rs @@ -15,6 +15,7 @@ async fn main() { targets, target_domains, ports, + ip_only, quiet_mode, rate_limit, listening_port, @@ -64,10 +65,9 @@ async fn main() { syn_scan_results.sort(); syn_scan_results.into_iter().for_each(|remote| { - let ip = if let Some(domain) = target_domains.get(&remote.ip()) { // use stored domain name if one exists - domain - } else { - &remote.ip().to_string() + let ip = match target_domains.get(&remote.ip()) { + Some(domain) if !ip_only => domain, + _ => &remote.ip().to_string() }; println!("{}:{}", ip, remote.port()); });