Skip to main content

rusthound_ce/modules/
mod.rs

1//! List of RustHound add-on modules
2pub mod adcs;
3pub mod gpo;
4pub mod resolver;
5pub mod sessions;
6
7use std::error::Error;
8
9use futures::future;
10
11use crate::api::ADResults;
12use crate::args::{CollectionMethod, Options};
13use crate::modules::adcs::probe_enterpriseca_esc8;
14use crate::modules::gpo::sysvol::collect_sysvol_targets;
15
16/// Function to run all modules requested
17pub async fn run_modules(
18    common_args: &Options,
19    ad: &mut ADResults
20) -> Result<(), Box<dyn Error>> {
21
22    let cert_auth = common_args.uses_cert();
23    if cert_auth {
24        log::warn!("Certificate authentication in use: skipping SMB-based modules \
25                    (sessions and GPO/SYSVOL) no SMB credentials available.");
26    }
27
28    // [MODULE - RESOLVER] Resolve FQDN to IP address.
29    if common_args.fqdn_resolver {
30        resolver::resolv::resolving_all_fqdn(
31            common_args.dns_tcp,
32            &common_args.name_server,
33            &mut ad.mappings.fqdn_ip,
34            &ad.computers,
35        )
36        .await;
37    }
38
39    // [MODULE - SESSIONS] Just does user session collection
40    // <https://github.com/g0h4n/HasSession-rs>
41    //
42    // - SRVSVC / NetrSessionEnum - inbound SMB sessions (client IP + username).
43    // - WKSSVC / NetrWkstaUserEnum - users with an active logon context on the machine.
44    // - WINREG / HKEY_USERS - SIDs of loaded profile hives (= logged-on users).
45    if common_args.collection_method.does_sessions() && !cert_auth {
46        sessions::run(common_args, &ad.users, &mut ad.computers).await?;
47    }
48
49    // [MODULE - ESC8] Web enrollment probe on all enterprise CAs.
50    // Skipped in DCOnly mode (no direct machine connections allowed).
51    // Each probe's blocking reqwest client runs via tokio::task::spawn_blocking
52    // on Tokio's dedicated blocking thread pool. Building/dropping a
53    // reqwest::blocking::Client (which owns its own nested Tokio runtime)
54    // panics on drop if done on a thread already inside an async context; the
55    // previous rayon par_iter_mut could run the closure on the calling Tokio
56    // worker thread itself (e.g. with a single CA), which was the crash.
57    if !matches!(common_args.collection_method, CollectionMethod::DCOnly)
58        && !matches!(common_args.collection_method, CollectionMethod::LdapOnly)
59        && !ad.enterprisecas.is_empty()
60    {
61        log::info!(
62            "Starting ESC8 web enrollment probe on {} CA(s)...",
63            ad.enterprisecas.len()
64        );
65        let hosts: Vec<String> = ad
66            .enterprisecas
67            .iter()
68            .map(|ca| ca.dns_host().to_string())
69            .collect();
70        let probes = future::join_all(hosts.into_iter().map(|host| {
71            tokio::task::spawn_blocking(move || probe_enterpriseca_esc8(&host))
72        }))
73        .await;
74        for (ca, probe) in ad.enterprisecas.iter_mut().zip(probes) {
75            match probe {
76                Ok(esc8) => ca.apply_esc8(esc8.http_enrollment_endpoints),
77                Err(join_err) => log::warn!(
78                    "[adcs] ESC8 probe task for {} did not complete ({}), skipping",
79                    ca.dns_host(),
80                    join_err
81                ),
82            }
83        }
84    }
85
86    // [MODULE - GPO SYSVOL] read GptTmpl.inf / Groups.xml off the DC SYSVOL share.
87    // <#47 Privileges> and <#56 LocalGroup>. DC-side I/O, so it also runs in DCOnly.
88    if common_args.collection_method.does_gpo() && !cert_auth {
89        let computer_scope = gpo::sysvol::ComputerGpoScope::from_gpos(&ad.gpos);
90        let sysvol = match collect_sysvol_targets(common_args, &computer_scope).await {
91            Ok(v) => v,
92            Err(e) => {
93                log::warn!("[gpo] SYSVOL collection failed: {e}");
94                Vec::new()
95            }
96        };
97        if !sysvol.is_empty() {
98            log::info!(
99                "[gpo] mapping {} GPO(s) to GPOChanges / UserRights",
100                sysvol.len()
101            );
102            gpo::apply_gpo(
103                &mut ad.ous,
104                &mut ad.domains,
105                &ad.users,
106                &ad.groups,
107                &mut ad.computers,
108                &sysvol,
109                &ad.mappings.dn_sid,
110            );
111        }
112    }
113
114    // Other modules need to be add here...
115    Ok(())
116}