rusthound/
args.rs

1//! Parsing arguments
2#[cfg(not(feature = "noargs"))]
3use clap::{Arg, ArgAction, value_parser, Command};
4
5#[cfg(feature = "noargs")]
6use winreg::{RegKey,{enums::*}};
7#[cfg(feature = "noargs")]
8use crate::exec::run;
9#[cfg(feature = "noargs")]
10use regex::Regex;
11
12#[derive(Clone, Debug)]
13pub struct Options {
14    pub domain: String,
15    pub username: String,
16    pub password: String,
17    pub ldapfqdn: String,
18    pub ip: String,
19    pub port: String,
20    pub name_server: String,
21    pub path: String,
22    pub ldaps: bool,
23    pub dns_tcp: bool,
24    pub fqdn_resolver: bool,
25    pub adcs: bool,
26    pub old_bloodhound: bool,
27    pub dc_only: bool,
28    pub kerberos: bool,
29    pub zip: bool,
30    pub verbose: log::LevelFilter,
31}
32
33#[cfg(not(feature = "noargs"))]
34fn cli() -> Command {
35    Command::new("rusthound")
36        .version("1.1.69")
37        .about("Active Directory data collector for BloodHound.\ng0h4n <https://twitter.com/g0h4n_0>")
38        .arg(Arg::new("v")
39            .short('v')
40            .help("Set the level of verbosity")
41            .action(ArgAction::Count),
42        )
43        .next_help_heading("REQUIRED VALUES")
44        .arg(Arg::new("domain")
45                .short('d')
46                .long("domain")
47                .help("Domain name like: DOMAIN.LOCAL")
48                .required(true)
49                .value_parser(value_parser!(String))
50            )
51        .next_help_heading("OPTIONAL VALUES")
52        .arg(Arg::new("ldapusername")
53            .short('u')
54            .long("ldapusername")
55            .help("LDAP username, like: user@domain.local")
56            .required(false)
57            .value_parser(value_parser!(String))
58        )
59        .arg(Arg::new("ldappassword")
60            .short('p')
61            .long("ldappassword")
62            .help("LDAP password")
63            .required(false)
64            .value_parser(value_parser!(String))
65        )
66        .arg(Arg::new("ldapfqdn")
67            .short('f')
68            .long("ldapfqdn")
69            .help("Domain Controler FQDN like: DC01.DOMAIN.LOCAL or just DC01")
70            .required(false)
71            .value_parser(value_parser!(String))
72        )
73        .arg(Arg::new("ldapip")
74            .short('i')
75            .long("ldapip")
76            .help("Domain Controller IP address like: 192.168.1.10")
77            .required(false)
78            .value_parser(value_parser!(String))
79        )
80        .arg(Arg::new("ldapport")
81            .short('P')
82            .long("ldapport")
83            .help("LDAP port [default: 389]")
84            .required(false)
85            .value_parser(value_parser!(String))
86        )
87        .arg(Arg::new("name-server")
88            .short('n')
89            .long("name-server")
90            .help("Alternative IP address name server to use for DNS queries")
91            .required(false)
92            .value_parser(value_parser!(String))
93        )
94        .arg(Arg::new("output")
95            .short('o')
96            .long("output")
97            .help("Output directory where you would like to save JSON files [default: ./]")
98            .required(false)
99            .value_parser(value_parser!(String))
100        )
101        .next_help_heading("OPTIONAL FLAGS")
102        .arg(Arg::new("ldaps")
103            .long("ldaps")
104            .help("Force LDAPS using for request like: ldaps://DOMAIN.LOCAL/")
105            .required(false)
106            .action(ArgAction::SetTrue)
107            .global(false)
108        )
109        .arg(Arg::new("kerberos")
110            .short('k')
111            .long("kerberos")
112            .help("Use Kerberos authentication. Grabs credentials from ccache file (KRB5CCNAME) based on target parameters for Linux.")
113            .required(false)
114            .action(ArgAction::SetTrue)
115            .global(false)
116        )
117        .arg(Arg::new("dns-tcp")
118                .long("dns-tcp")
119                .help("Use TCP instead of UDP for DNS queries")
120                .required(false)
121                .action(ArgAction::SetTrue)
122                .global(false)
123            )
124        .arg(Arg::new("dc-only")
125            .long("dc-only")
126            .help("Collects data only from the domain controller. Will not try to retrieve CA security/configuration or check for Web Enrollment")
127            .required(false)
128            .action(ArgAction::SetTrue)
129            .global(false)
130        )
131        .arg(Arg::new("old-bloodhound")
132            .long("old-bloodhound")
133            .help("For ADCS only. Output result as BloodHound data for the original BloodHound version from @BloodHoundAD without PKI support")
134            .required(false)
135            .action(ArgAction::SetTrue)
136            .global(false)
137        )
138        .arg(Arg::new("zip")
139            .long("zip")
140            .short('z')
141            .help("Compress the JSON files into a zip archive")
142            .required(false)
143            .action(ArgAction::SetTrue)
144            .global(false)
145        )
146        .next_help_heading("OPTIONAL MODULES")
147        .arg(Arg::new("fqdn-resolver")
148            .long("fqdn-resolver")
149            .help("Use fqdn-resolver module to get computers IP address")
150            .required(false)
151            .action(ArgAction::SetTrue)
152            .global(false)
153        )
154        .arg(Arg::new("adcs")
155            .long("adcs")
156            .help("Use ADCS module to enumerate Certificate Templates, Certificate Authorities and other configurations.\n(For the custom-built BloodHound version from @ly4k with PKI support)")
157            .required(false)
158            .action(ArgAction::SetTrue)
159            .global(false)
160        )
161}
162
163#[cfg(not(feature = "noargs"))]
164/// Function to extract all argument and put it in 'Options' structure.
165pub fn extract_args() -> Options {
166
167    // Get arguments
168    let matches = cli().get_matches();
169
170    // Now get values
171    let d = matches.get_one::<String>("domain").map(|s| s.as_str()).unwrap();
172    let u = matches.get_one::<String>("ldapusername").map(|s| s.as_str()).unwrap_or("not set");
173    let p = matches.get_one::<String>("ldappassword").map(|s| s.as_str()).unwrap_or("not set");
174    let f = matches.get_one::<String>("ldapfqdn").map(|s| s.as_str()).unwrap_or("not set");
175    let ip = matches.get_one::<String>("ldapip").map(|s| s.as_str()).unwrap_or("not set");
176    let port = matches.get_one::<String>("ldapport").map(|s| s.as_str()).unwrap_or("not set");
177    let n = matches.get_one::<String>("name-server").map(|s| s.as_str()).unwrap_or("not set");
178    let path = matches.get_one::<String>("output").map(|s| s.as_str()).unwrap_or("./");
179    let ldaps = matches.get_one::<bool>("ldaps").map(|s| s.to_owned()).unwrap_or(false);
180    let dns_tcp = matches.get_one::<bool>("dns-tcp").map(|s| s.to_owned()).unwrap_or(false);
181    let dc_only = matches.get_one::<bool>("dc-only").map(|s| s.to_owned()).unwrap_or(false);
182    let old_bh = matches.get_one::<bool>("old-bloodhound").map(|s| s.to_owned()).unwrap_or(false);
183    let z = matches.get_one::<bool>("zip").map(|s| s.to_owned()).unwrap_or(false);
184    let fqdn_resolver = matches.get_one::<bool>("fqdn-resolver").map(|s| s.to_owned()).unwrap_or(false);
185    let adcs = matches.get_one::<bool>("adcs").map(|s| s.to_owned()).unwrap_or(false);
186    let kerberos = matches.get_one::<bool>("kerberos").map(|s| s.to_owned()).unwrap_or(false);
187    let v = match matches.get_count("v") {
188        0 => log::LevelFilter::Info,
189        1 => log::LevelFilter::Debug,
190        _ => log::LevelFilter::Trace,
191    };
192
193    // Return all
194    Options {
195        domain: d.to_string(),
196        username: u.to_string(),
197        password: p.to_string(),
198        ldapfqdn: f.to_string(),
199        ip: ip.to_string(),
200        port: port.to_string(),
201        name_server: n.to_string(),
202        path: path.to_string(),
203        ldaps: ldaps,
204        dns_tcp: dns_tcp,
205        dc_only: dc_only,
206        old_bloodhound: old_bh,
207        fqdn_resolver: fqdn_resolver,
208        adcs: adcs,
209        kerberos: kerberos,
210        zip: z,
211        verbose: v,
212    }
213}
214
215#[cfg(feature = "noargs")]
216/// Function to automatically get all informations needed and put it in 'Options' structure.
217pub fn auto_args() -> Options {
218
219    // Request registry key to get informations
220    let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
221    let cur_ver = hklm.open_subkey("SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters").unwrap();
222    //Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Domain
223    let domain: String = match cur_ver.get_value("Domain") {
224        Ok(domain) => domain,
225        Err(err) => {
226            panic!("Error: {:?}",err);
227        }
228    };
229    
230    // Get LDAP fqdn
231    let _fqdn: String = run(&format!("nslookup -query=srv _ldap._tcp.{}",&domain));
232    let re = Regex::new(r"hostname.*= (?<ldap_fqdn>[0-9a-zA-Z]{1,})").unwrap();
233    let mut values =  re.captures_iter(&_fqdn);
234    let caps = values.next().unwrap();
235    let fqdn = caps["ldap_fqdn"].to_string();
236
237    // Get LDAP port
238    let re = Regex::new(r"port.*= (?<ldap_port>[0-9]{3,})").unwrap();
239    let mut values =  re.captures_iter(&_fqdn);
240    let caps = values.next().unwrap();
241    let port = caps["ldap_port"].to_string();
242    let mut ldaps: bool = false;
243    if port == "636" {
244        ldaps = true;
245    }
246
247    // Return all
248    Options {
249        domain: domain.to_string(),
250        username: "not set".to_string(),
251        password: "not set".to_string(),
252        ldapfqdn: fqdn.to_string(),
253        ip: "not set".to_string(),
254        port: port.to_string(),
255        name_server: "127.0.0.1".to_string(),
256        path: "./output".to_string(),
257        ldaps: ldaps,
258        dns_tcp: false,
259        dc_only: false,
260        old_bloodhound: false,
261        fqdn_resolver: false,
262        adcs: true,
263        kerberos: true,
264        zip: true,
265        verbose: log::LevelFilter::Info,
266    }
267}