1#[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"))]
164pub fn extract_args() -> Options {
166
167 let matches = cli().get_matches();
169
170 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 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")]
216pub fn auto_args() -> Options {
218
219 let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
221 let cur_ver = hklm.open_subkey("SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters").unwrap();
222 let domain: String = match cur_ver.get_value("Domain") {
224 Ok(domain) => domain,
225 Err(err) => {
226 panic!("Error: {:?}",err);
227 }
228 };
229
230 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 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 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}