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::utils::exec::run;
9#[cfg(feature = "noargs")]
10use regex::Regex;
11
12#[derive(Clone, Debug)]
13pub struct Options {
14 pub domain: String,
15 pub username: Option<String>,
16 pub password: Option<String>,
17 pub ldapfqdn: String,
18 pub ip: Option<String>,
19 pub port: Option<u16>,
20 pub name_server: String,
21 pub path: String,
22 pub collection_method: CollectionMethod,
23 pub ldaps: bool,
24 pub dns_tcp: bool,
25 pub fqdn_resolver: bool,
26 pub kerberos: bool,
27 pub zip: bool,
28 pub verbose: log::LevelFilter,
29 pub ldap_filter: String,
30}
31
32#[derive(Clone, Debug)]
33pub enum CollectionMethod {
34 All,
35 DCOnly,
36}
37
38pub const RUSTHOUND_VERSION: &str = env!("CARGO_PKG_VERSION");
40
41#[cfg(not(feature = "noargs"))]
42fn cli() -> Command {
43 Command::new("rusthound-ce")
45 .version(RUSTHOUND_VERSION)
46 .about("Active Directory data collector for BloodHound Community Edition.\ng0h4n <https://twitter.com/g0h4n_0>")
47 .arg(Arg::new("v")
48 .short('v')
49 .help("Set the level of verbosity")
50 .action(ArgAction::Count),
51 )
52 .next_help_heading("REQUIRED VALUES")
53 .arg(Arg::new("domain")
54 .short('d')
55 .long("domain")
56 .help("Domain name like: DOMAIN.LOCAL")
57 .required(true)
58 .value_parser(value_parser!(String))
59 )
60 .next_help_heading("OPTIONAL VALUES")
61 .arg(Arg::new("ldapusername")
62 .short('u')
63 .long("ldapusername")
64 .help("LDAP username, like: user@domain.local")
65 .required(false)
66 .value_parser(value_parser!(String))
67 )
68 .arg(Arg::new("ldappassword")
69 .short('p')
70 .long("ldappassword")
71 .help("LDAP password")
72 .required(false)
73 .value_parser(value_parser!(String))
74 )
75 .arg(Arg::new("ldapfqdn")
76 .short('f')
77 .long("ldapfqdn")
78 .help("Domain Controller FQDN like: DC01.DOMAIN.LOCAL or just DC01")
79 .required(false)
80 .value_parser(value_parser!(String))
81 )
82 .arg(Arg::new("ldapip")
83 .short('i')
84 .long("ldapip")
85 .help("Domain Controller IP address like: 192.168.1.10")
86 .required(false)
87 .value_parser(value_parser!(String))
88 )
89 .arg(Arg::new("ldapport")
90 .short('P')
91 .long("ldapport")
92 .help("LDAP port [default: 389]")
93 .required(false)
94 .value_parser(value_parser!(String))
95 )
96 .arg(Arg::new("name-server")
97 .short('n')
98 .long("name-server")
99 .help("Alternative IP address name server to use for DNS queries")
100 .required(false)
101 .value_parser(value_parser!(String))
102 )
103 .arg(Arg::new("output")
104 .short('o')
105 .long("output")
106 .help("Output directory where you would like to save JSON files [default: ./]")
107 .required(false)
108 .value_parser(value_parser!(String))
109 )
110 .next_help_heading("OPTIONAL FLAGS")
111 .arg(Arg::new("collectionmethod")
112 .short('c')
113 .long("collectionmethod")
114 .help("Which information to collect. Supported: All (LDAP,SMB,HTTP requests), DCOnly (no computer connections, only LDAP requests). (default: All)")
115 .required(false)
116 .value_name("COLLECTIONMETHOD")
117 .value_parser(["All", "DCOnly"])
118 .num_args(0..=1)
119 .default_missing_value("All")
120 )
121 .arg(Arg::new("ldap-filter")
122 .long("ldap-filter")
123 .help("Use custom ldap-filter default is : (objectClass=*)")
124 .required(false)
125 .value_parser(value_parser!(String))
126 .default_missing_value("(objectClass=*)")
127 )
128 .arg(Arg::new("ldaps")
129 .long("ldaps")
130 .help("Force LDAPS using for request like: ldaps://DOMAIN.LOCAL/")
131 .required(false)
132 .action(ArgAction::SetTrue)
133 .global(false)
134 )
135 .arg(Arg::new("kerberos")
136 .short('k')
137 .long("kerberos")
138 .help("Use Kerberos authentication. Grabs credentials from ccache file (KRB5CCNAME) based on target parameters for Linux.")
139 .required(false)
140 .action(ArgAction::SetTrue)
141 .global(false)
142 )
143 .arg(Arg::new("dns-tcp")
144 .long("dns-tcp")
145 .help("Use TCP instead of UDP for DNS queries")
146 .required(false)
147 .action(ArgAction::SetTrue)
148 .global(false)
149 )
150 .arg(Arg::new("zip")
151 .long("zip")
152 .short('z')
153 .help("Compress the JSON files into a zip archive")
154 .required(false)
155 .action(ArgAction::SetTrue)
156 .global(false)
157 )
158 .next_help_heading("OPTIONAL MODULES")
159 .arg(Arg::new("fqdn-resolver")
160 .long("fqdn-resolver")
161 .help("Use fqdn-resolver module to get computers IP address")
162 .required(false)
163 .action(ArgAction::SetTrue)
164 .global(false)
165 )
166}
167
168#[cfg(not(feature = "noargs"))]
169pub fn extract_args() -> Options {
171
172 let matches = cli().get_matches();
174
175 let d = matches
177 .get_one::<String>("domain")
178 .map(|s| s.as_str())
179 .unwrap();
180 let username = matches
181 .get_one::<String>("ldapusername")
182 .map(|s| s.to_owned());
183 let password = matches
184 .get_one::<String>("ldappassword")
185 .map(|s| s.to_owned());
186 let f = matches
187 .get_one::<String>("ldapfqdn")
188 .map(|s| s.as_str())
189 .unwrap_or("not set");
190 let ip = matches.get_one::<String>("ldapip").cloned();
191 let port = match matches.get_one::<String>("ldapport") {
192 Some(val) => val.parse::<u16>().ok(),
193 None => None,
194 };
195 let n = matches
196 .get_one::<String>("name-server")
197 .map(|s| s.as_str())
198 .unwrap_or("not set");
199 let path = matches
200 .get_one::<String>("output")
201 .map(|s| s.as_str())
202 .unwrap_or("./");
203 let ldaps = matches
204 .get_one::<bool>("ldaps")
205 .map(|s| s.to_owned())
206 .unwrap_or(false);
207 let dns_tcp = matches
208 .get_one::<bool>("dns-tcp")
209 .map(|s| s.to_owned())
210 .unwrap_or(false);
211 let z = matches
212 .get_one::<bool>("zip")
213 .map(|s| s.to_owned())
214 .unwrap_or(false);
215 let fqdn_resolver = matches
216 .get_one::<bool>("fqdn-resolver")
217 .map(|s| s.to_owned())
218 .unwrap_or(false);
219 let kerberos = matches
220 .get_one::<bool>("kerberos")
221 .map(|s| s.to_owned())
222 .unwrap_or(false);
223 let v = match matches.get_count("v") {
224 0 => log::LevelFilter::Info,
225 1 => log::LevelFilter::Debug,
226 _ => log::LevelFilter::Trace,
227 };
228 let collection_method = match matches.get_one::<String>("collectionmethod").map(|s| s.as_str()).unwrap_or("All") {
229 "All" => CollectionMethod::All,
230 "DCOnly" => CollectionMethod::DCOnly,
231 _ => CollectionMethod::All,
232 };
233 let ldap_filter = matches.get_one::<String>("ldap-filter").map(|s| s.as_str()).unwrap_or("(objectClass=*)");
234
235 Options {
237 domain: d.to_string(),
238 username,
239 password,
240 ldapfqdn: f.to_string(),
241 ip,
242 port,
243 name_server: n.to_string(),
244 path: path.to_string(),
245 collection_method,
246 ldaps,
247 dns_tcp,
248 fqdn_resolver,
249 kerberos,
250 zip: z,
251 verbose: v,
252 ldap_filter: ldap_filter.to_string(),
253 }
254}
255
256#[cfg(feature = "noargs")]
257pub fn auto_args() -> Options {
259
260 let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
262 let cur_ver = hklm.open_subkey("SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters").unwrap();
263 let domain: String = match cur_ver.get_value("Domain") {
265 Ok(domain) => domain,
266 Err(err) => {
267 panic!("Error: {:?}",err);
268 }
269 };
270
271 let _fqdn: String = run(&format!("nslookup -query=srv _ldap._tcp.{}",&domain));
273 let re = Regex::new(r"hostname.*= (?<ldap_fqdn>[0-9a-zA-Z]{1,})").unwrap();
274 let mut values = re.captures_iter(&_fqdn);
275 let caps = values.next().unwrap();
276 let fqdn = caps["ldap_fqdn"].to_string();
277
278 let re = Regex::new(r"port.*= (?<ldap_port>[0-9]{3,})").unwrap();
280 let mut values = re.captures_iter(&_fqdn);
281 let caps = values.next().unwrap();
282 let port = match caps["ldap_port"].to_string().parse::<u16>() {
283 Ok(x) => Some(x),
284 Err(_) => None
285 };
286 let ldaps: bool = {
287 if let Some(p) = port {
288 p == 636
289 } else {
290 false
291 }
292 };
293
294 Options {
296 domain: domain.to_string(),
297 username: "not set".to_string(),
298 password: "not set".to_string(),
299 ldapfqdn: fqdn.to_string(),
300 ip: None,
301 port: port,
302 name_server: "127.0.0.1".to_string(),
303 path: "./output".to_string(),
304 collection_method: CollectionMethod::All,
305 ldaps: ldaps,
306 dns_tcp: false,
307 fqdn_resolver: false,
308 kerberos: true,
309 zip: true,
310 verbose: log::LevelFilter::Info,
311 ldap_filter: "(objectClass=*)".to_string()
312 }
313}