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