diff --git a/ares-cli/src/dedup/labels.rs b/ares-cli/src/dedup/labels.rs index 4d58d0e0..7fb8c756 100644 --- a/ares-cli/src/dedup/labels.rs +++ b/ares-cli/src/dedup/labels.rs @@ -48,7 +48,6 @@ pub(crate) fn normalize_source_label(source: &str) -> String { let mut source = source.to_string(); - // Deduplicate "recon:recon" -> "recon" if source.contains(':') { let parts: Vec<&str> = source.split(':').collect(); if parts.len() >= 2 && parts[0] == parts[1] { @@ -56,7 +55,6 @@ pub(crate) fn normalize_source_label(source: &str) -> String { } } - // Extract task type from "task input (recon_abc123)" patterns let lower = source.to_lowercase(); if lower.contains("task input") { if let Some(caps) = TASK_INPUT_RE.captures(&source) { @@ -66,19 +64,16 @@ pub(crate) fn normalize_source_label(source: &str) -> String { let lower = source.to_lowercase(); - // Exact match if let Some(label) = LABEL_MAP.get(lower.as_str()) { return label.to_string(); } - // Prefix match for (key, label) in LABEL_MAP.iter() { if lower.starts_with(key) { return label.to_string(); } } - // Task ID suffix match (e.g., "recon_abc12345" -> "recon") if let Some(caps) = TASK_SUFFIX_RE.captures(&lower) { let task_type = &caps[1]; if let Some(label) = LABEL_MAP.get(task_type) { @@ -86,7 +81,6 @@ pub(crate) fn normalize_source_label(source: &str) -> String { } } - // Fallback: replace underscores and title-case source .replace('_', " ") .split_whitespace() diff --git a/ares-cli/src/orchestrator/automation/adcs_exploitation.rs b/ares-cli/src/orchestrator/automation/adcs_exploitation.rs index ef9f48f6..2364116f 100644 --- a/ares-cli/src/orchestrator/automation/adcs_exploitation.rs +++ b/ares-cli/src/orchestrator/automation/adcs_exploitation.rs @@ -1464,9 +1464,6 @@ async fn dispatch_relay_coerce_chain( "relay chain: cert captured; authenticating with certipy_auth" ); - // Phase 2: certipy auth -pfx -> NT hash for the relayed user. - // The auth produces a Hash discovery via the certipy_auth parser. - // // The relayed account's realm can differ from the vuln record's // domain (cross-forest ESC8 — a machine in forest A relayed to a // CA in forest B via trust). Resolve the home realm + KDC from @@ -1782,8 +1779,8 @@ fn esc_instructions(esc_type: &str) -> &'static str { ), "esc3" => concat!( "ESC3: Certificate Request Agent (enrollment agent).\n", - "Step 1: certipy_request the CRA template with target=ca_host.\n", - "Step 2: Use that cert to request a cert on behalf of administrator.\n", + "First: certipy_request the CRA template with target=ca_host.\n", + "Then: use that cert to request a cert on behalf of administrator.\n", "IMPORTANT: Set target to the ca_host IP, not the dc_ip." ), "esc4" => concat!( @@ -3236,7 +3233,7 @@ mod tests { fn parse_relay_output_captures_pfx_and_user() { let stdout = "\ RELAY_PID=12345 -=== Phase 1: unauth PetitPotam === +=== unauth PetitPotam === [*] PetitPotam succeeded === RELAY LOG === [+] Authenticating against http://192.168.58.50/certsrv diff --git a/ares-cli/src/orchestrator/automation/laps.rs b/ares-cli/src/orchestrator/automation/laps.rs index 190515ee..ca0fcb6b 100644 --- a/ares-cli/src/orchestrator/automation/laps.rs +++ b/ares-cli/src/orchestrator/automation/laps.rs @@ -441,8 +441,7 @@ mod tests { #[test] fn laps_hash_sweep_emits_work_item_for_valid_ntlm_hash() { let mut s = state_with_dc("contoso.local", "192.168.58.10"); - s.hashes - .push(ntlm_hash("alice", "contoso.local", HASH_A)); + s.hashes.push(ntlm_hash("alice", "contoso.local", HASH_A)); let work = collect_laps_hash_sweep_work(&s); assert_eq!(work.len(), 1); @@ -565,8 +564,7 @@ mod tests { // dedup key go through `.to_lowercase()` — the work item is still // emitted. let mut s = state_with_dc("contoso.local", "192.168.58.10"); - s.hashes - .push(ntlm_hash("Alice", "CONTOSO.LOCAL", HASH_A)); + s.hashes.push(ntlm_hash("Alice", "CONTOSO.LOCAL", HASH_A)); let work = collect_laps_hash_sweep_work(&s); assert_eq!(work.len(), 1); assert_eq!(work[0].dedup_key, "laps_extract:sweep:contoso.local:alice"); @@ -575,8 +573,7 @@ mod tests { #[test] fn laps_hash_sweep_emits_one_item_per_eligible_hash() { let mut s = state_with_dc("contoso.local", "192.168.58.10"); - s.hashes - .push(ntlm_hash("alice", "contoso.local", HASH_A)); + s.hashes.push(ntlm_hash("alice", "contoso.local", HASH_A)); s.hashes.push(ntlm_hash("bob", "contoso.local", HASH_B)); let work = collect_laps_hash_sweep_work(&s); assert_eq!(work.len(), 2); diff --git a/ares-cli/src/orchestrator/automation/stall_detection.rs b/ares-cli/src/orchestrator/automation/stall_detection.rs index cc89fdf3..3caa0d29 100644 --- a/ares-cli/src/orchestrator/automation/stall_detection.rs +++ b/ares-cli/src/orchestrator/automation/stall_detection.rs @@ -328,8 +328,6 @@ mod tests { } } - // --- dedup-key shape --------------------------------------------- - #[test] fn stall_spray_dedup_key_includes_recovery_attempt() { assert_eq!( @@ -354,8 +352,6 @@ mod tests { ); } - // --- domains_with_pending_delegation ---------------------------- - #[test] fn pending_delegation_empty_state() { let s = StateInner::new("op".into()); @@ -416,8 +412,6 @@ mod tests { assert!(domains_with_pending_delegation(&s).contains("contoso.local")); } - // --- resolve_stall_dc_ip -------------------------------------------- - #[test] fn resolve_stall_dc_ip_exact_match() { let mut s = StateInner::new("op".into()); @@ -448,8 +442,6 @@ mod tests { assert!(resolve_stall_dc_ip(&s, "contoso.local").is_none()); } - // --- select_stall_spray_work --------------------------------------- - #[test] fn select_stall_spray_empty_state() { let s = StateInner::new("op".into()); @@ -493,8 +485,6 @@ mod tests { assert_eq!(select_stall_spray_work(&s, 1).len(), 1); } - // --- select_stall_lhf_work ----------------------------------------- - #[test] fn select_stall_lhf_empty_state() { let s = StateInner::new("op".into()); diff --git a/ares-cli/src/orchestrator/state/publishing/credentials.rs b/ares-cli/src/orchestrator/state/publishing/credentials.rs index ec80b230..f9f676f0 100644 --- a/ares-cli/src/orchestrator/state/publishing/credentials.rs +++ b/ares-cli/src/orchestrator/state/publishing/credentials.rs @@ -27,7 +27,6 @@ impl SharedState { queue: &TaskQueueCore, cred: Credential, ) -> Result { - // Sanitize and validate before storage let (netbios_map, known_domains) = { let state = self.inner.read().await; // Known domains = explicit state.domains plus any DC domain keys. @@ -177,7 +176,6 @@ impl SharedState { } return Ok(false); } - // Emit before consuming `hash` into state. emit_op_state( self.recorder(), &operation_id, diff --git a/ares-cli/src/orchestrator/state/publishing/domains.rs b/ares-cli/src/orchestrator/state/publishing/domains.rs index 764e3382..a1d0f48a 100644 --- a/ares-cli/src/orchestrator/state/publishing/domains.rs +++ b/ares-cli/src/orchestrator/state/publishing/domains.rs @@ -373,12 +373,7 @@ mod tests { s.domains.push("contoso.local".into()); } let outcome = state - .publish_candidate_domain( - &q, - "evil.local", - DomainEvidence::HostnameInference, - None, - ) + .publish_candidate_domain(&q, "evil.local", DomainEvidence::HostnameInference, None) .await .unwrap(); assert_eq!(outcome, DomainPublishOutcome::Held); diff --git a/ares-core/src/nats.rs b/ares-core/src/nats.rs index b1ed69b7..2e6949c9 100644 --- a/ares-core/src/nats.rs +++ b/ares-core/src/nats.rs @@ -42,8 +42,6 @@ use crate::models::OpStateEvent; /// Default NATS URL used when neither `ARES_NATS_URL` nor an explicit URL is provided. pub const DEFAULT_NATS_URL: &str = "nats://127.0.0.1:4222"; -// === Subject taxonomy ===================================================== - /// Red team task work queue. `ares.tasks.{role}` (e.g. `ares.tasks.recon`). pub const TASK_SUBJECT_PREFIX: &str = "ares.tasks"; /// Tool dispatch RPC. `ares.tools.exec.{role}`. @@ -69,8 +67,6 @@ pub const URGENT_TASK_SUBJECT_PREFIX: &str = "ares.tasks.urgent"; /// Blue task result subject. `ares.blue.tasks.results.{task_id}`. pub const BLUE_TASK_RESULT_SUBJECT_PREFIX: &str = "ares.blue.tasks.results"; -// === Stream names ========================================================= - /// JetStream stream containing all red-team task subjects. pub const TASKS_STREAM: &str = "ARES_TASKS"; /// JetStream stream containing all blue-team task subjects. @@ -83,8 +79,6 @@ pub const DISCOVERIES_STREAM: &str = "ARES_DISCOVERIES"; /// Pattern B: this is the source of truth, Redis is a derived cache. pub const OP_STATE_STREAM: &str = "ARES_OPSTATE"; -// === Subject builders ===================================================== - #[inline] pub fn task_subject(role: &str) -> String { format!("{TASK_SUBJECT_PREFIX}.{role}") @@ -144,8 +138,6 @@ pub fn op_state_filter_for_op(operation_id: &str) -> String { format!("{OP_STATE_SUBJECT_PREFIX}.{operation_id}.>") } -// === Connection =========================================================== - /// Shared NATS broker handle. /// /// `async_nats::Client` is already cheaply cloneable and multiplexes all diff --git a/ares-tools/src/coercion.rs b/ares-tools/src/coercion.rs index 972e8d6b..9228eb83 100644 --- a/ares-tools/src/coercion.rs +++ b/ares-tools/src/coercion.rs @@ -810,10 +810,9 @@ async fn run_relay_and_coerce( let mut summary = format!("RELAY_PID={}\n", relay.pid()); let mut captured_via: Option<&'static str> = None; - // --- Phase 1: unauthenticated PetitPotam --- // Distros differ: Kali ships `petitpotam` (symlink), pip ships // `impacket-petitpotam`. Try in order, log if both missing. - summary.push_str("=== Phase 1: unauth PetitPotam ===\n"); + summary.push_str("=== unauth PetitPotam ===\n"); let petit_bin = ["petitpotam", "impacket-petitpotam"] .into_iter() .find(|b| procs.which_binary(b)) @@ -826,7 +825,7 @@ async fn run_relay_and_coerce( procs .run_phase( &coerce_log, - "Phase 1: unauth PetitPotam", + "unauth PetitPotam", petit_bin, &p1_args, &workdir, @@ -837,9 +836,8 @@ async fn run_relay_and_coerce( captured_via = Some("unauth_petitpotam"); } - // --- Phase 2: authenticated DFSCoerce --- if captured_via.is_none() && cfg.coerce_user.is_some() { - summary.push_str("=== Phase 2: authenticated DFSCoerce (MS-DFSNM) ===\n"); + summary.push_str("=== authenticated DFSCoerce (MS-DFSNM) ===\n"); let user = cfg.coerce_user.as_deref().unwrap(); let secret_args = coerce_secret_args(cfg.coerce_secret.as_ref()); let mut a: Vec<&str> = vec!["-u", user, "-d", cfg.coerce_domain.as_str()]; @@ -849,28 +847,18 @@ async fn run_relay_and_coerce( a.push(cfg.attacker_ip.as_str()); a.push(cfg.coerce_target.as_str()); procs - .run_phase( - &coerce_log, - "Phase 2: DFSCoerce", - "dfscoerce", - &a, - &workdir, - 25, - ) + .run_phase(&coerce_log, "DFSCoerce", "dfscoerce", &a, &workdir, 25) .await; if poll_for_cert(&relay_log, opts.poll_phase_2, opts.poll_interval).await { captured_via = Some("MS-DFSNM"); } } - // --- Phase 3: coercer over MS-EFSR / MS-RPRN --- if captured_via.is_none() && cfg.coerce_user.is_some() { let user = cfg.coerce_user.as_deref().unwrap(); let secret_args = coerce_secret_args(cfg.coerce_secret.as_ref()); for proto in ["MS-EFSR", "MS-RPRN"] { - summary.push_str(&format!( - "=== Phase 3: authenticated coerce via {proto} ===\n" - )); + summary.push_str(&format!("=== authenticated coerce via {proto} ===\n")); let mut a: Vec<&str> = vec![ "coerce", "-u", @@ -893,7 +881,7 @@ async fn run_relay_and_coerce( procs .run_phase( &coerce_log, - &format!("Phase 3: {proto}"), + &format!("coerce via {proto}"), "coercer", &a, &workdir, @@ -1721,10 +1709,10 @@ mod tests { } } - const PHASE1: &str = "Phase 1: unauth PetitPotam"; - const PHASE2: &str = "Phase 2: DFSCoerce"; - const PHASE3_EFSR: &str = "Phase 3: MS-EFSR"; - const PHASE3_RPRN: &str = "Phase 3: MS-RPRN"; + const PHASE1: &str = "unauth PetitPotam"; + const PHASE2: &str = "DFSCoerce"; + const PHASE3_EFSR: &str = "coerce via MS-EFSR"; + const PHASE3_RPRN: &str = "coerce via MS-RPRN"; #[tokio::test] async fn run_attacker_ip_not_local_bails_with_clear_error() { diff --git a/ares-tools/src/privesc/adcs.rs b/ares-tools/src/privesc/adcs.rs index ac473a0f..fbd682de 100644 --- a/ares-tools/src/privesc/adcs.rs +++ b/ares-tools/src/privesc/adcs.rs @@ -7,6 +7,24 @@ use crate::args::{optional_bool, optional_str, required_str}; use crate::executor::CommandBuilder; use crate::ToolOutput; +/// Concatenate the stdout/stderr of a chained tool invocation under `===