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