Skip to content

Commit 92dc819

Browse files
authored
Merge pull request Pennyw0rth#521 from XiaoliChan/remove-mic-check
[Module] Add remove mic check
2 parents 2a15590 + 7a6565c commit 92dc819

2 files changed

Lines changed: 193 additions & 0 deletions

File tree

nxc/modules/remove-mic.py

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
# Original Author:
2+
# Dirk-jan Mollema (@_dirkjan)
3+
# dlive (@D1iv3)
4+
#
5+
# Refernece:
6+
# - https://dirkjanm.io/exploiting-CVE-2019-1040-relay-vulnerabilities-for-rce-and-domain-admin/
7+
# - https://github.com/fox-it/cve-2019-1040-scanner
8+
# - https://github.com/Dliv3/cve-2019-1040-scanner
9+
#
10+
# Modify by:
11+
# XiaoliChan (@Memory_before)
12+
13+
import calendar
14+
import struct
15+
import time
16+
import random
17+
import string
18+
19+
from impacket import ntlm
20+
from impacket import nt_errors
21+
from impacket.smbconnection import SessionError
22+
23+
24+
class NXCModule:
25+
name = "remove-mic"
26+
description = "Check if host vulnerable to CVE-2019-1040"
27+
supported_protocols = ["smb"]
28+
opsec_safe = True
29+
multiple_hosts = False
30+
31+
def __init__(self, context=None, module_options=None):
32+
self.context = context
33+
self.module_options = module_options
34+
self.action = None
35+
36+
def options(self, context, module_options):
37+
"""PORT Port to check (defaults to 445)"""
38+
self.port = 445
39+
if "PORT" in module_options:
40+
self.port = int(module_options["PORT"])
41+
42+
def on_login(self, context, connection):
43+
ntlm.computeResponseNTLMv2 = Modify_Func.mod_computeResponseNTLMv2
44+
ntlm.getNTLMSSPType3 = Modify_Func.mod_getNTLMSSPType3
45+
try:
46+
connection.conn.reconnect()
47+
except SessionError as e:
48+
if e.getErrorCode() == nt_errors.STATUS_INVALID_PARAMETER:
49+
context.log.info("Target is not vulnerable to CVE-2019-1040 (authentication was rejected)")
50+
else:
51+
context.log.info("Unexpected Exception while authentication")
52+
else:
53+
context.log.highlight("Potentially vulnerable to CVE-2019-1040, next step: https://dirkjanm.io/exploiting-CVE-2019-1040-relay-vulnerabilities-for-rce-and-domain-admin/")
54+
55+
class Modify_Func:
56+
# Slightly modified version of impackets computeResponseNTLMv2
57+
def mod_computeResponseNTLMv2(flags, serverChallenge, clientChallenge, serverName, domain, user, password, lmhash="", nthash="",
58+
use_ntlmv2=ntlm.USE_NTLMv2, channel_binding_value=b""):
59+
60+
responseServerVersion = b"\x01"
61+
hiResponseServerVersion = b"\x01"
62+
responseKeyNT = ntlm.NTOWFv2(user, password, domain, nthash)
63+
64+
av_pairs = ntlm.AV_PAIRS(serverName)
65+
# In order to support SPN target name validation, we have to add this to the serverName av_pairs. Otherwise we will
66+
# get access denied
67+
# This is set at Local Security Policy -> Local Policies -> Security Options -> Server SPN target name validation
68+
# level
69+
av_pairs[ntlm.NTLMSSP_AV_TARGET_NAME] = "cifs/".encode("utf-16le") + av_pairs[ntlm.NTLMSSP_AV_HOSTNAME][1]
70+
if av_pairs[ntlm.NTLMSSP_AV_TIME] is not None:
71+
aTime = av_pairs[ntlm.NTLMSSP_AV_TIME][1]
72+
else:
73+
aTime = struct.pack("<q", (116444736000000000 + calendar.timegm(time.gmtime()) * 10000000))
74+
av_pairs[ntlm.NTLMSSP_AV_TIME] = aTime
75+
av_pairs[ntlm.NTLMSSP_AV_FLAGS] = b"\x02" + b"\x00" * 3
76+
serverName = av_pairs.getData()
77+
78+
if len(channel_binding_value) > 0:
79+
av_pairs[ntlm.NTLMSSP_AV_CHANNEL_BINDINGS] = channel_binding_value
80+
81+
# Format according to:
82+
# https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/aee311d6-21a7-4470-92a5-c4ecb022a87b
83+
temp = responseServerVersion # RespType 1 byte
84+
temp += hiResponseServerVersion # HiRespType 1 byte
85+
temp += b"\x00" * 2 # Reserved1 2 bytes
86+
temp += b"\x00" * 4 # Reserved2 4 bytes
87+
temp += aTime # TimeStamp 8 bytes
88+
temp += clientChallenge # ChallengeFromClient 8 bytes
89+
temp += b"\x00" * 4 # Reserved 4 bytes
90+
temp += av_pairs.getData() # AvPairs variable
91+
92+
ntProofStr = ntlm.hmac_md5(responseKeyNT, serverChallenge + temp)
93+
94+
ntChallengeResponse = ntProofStr + temp
95+
lmChallengeResponse = ntlm.hmac_md5(responseKeyNT, serverChallenge + clientChallenge) + clientChallenge
96+
sessionBaseKey = ntlm.hmac_md5(responseKeyNT, ntProofStr)
97+
98+
if user == "" and password == "":
99+
# Special case for anonymous authentication
100+
ntChallengeResponse = ""
101+
lmChallengeResponse = ""
102+
103+
return ntChallengeResponse, lmChallengeResponse, sessionBaseKey
104+
105+
def mod_getNTLMSSPType3(type1, type2, user, password, domain, lmhash="", nthash="", use_ntlmv2=ntlm.USE_NTLMv2, channel_binding_value=b""):
106+
# Safety check in case somebody sent password = None.. That's not allowed. Setting it to '' and hope for the best.
107+
if password is None:
108+
password = ""
109+
110+
# Let's do some encoding checks before moving on. Kind of dirty, but found effective when dealing with
111+
# international characters.
112+
import sys
113+
encoding = sys.getfilesystemencoding()
114+
if encoding is not None:
115+
try:
116+
user.encode("utf-16le")
117+
except Exception:
118+
user = user.decode(encoding)
119+
try:
120+
password.encode("utf-16le")
121+
except Exception:
122+
password = password.decode(encoding)
123+
try:
124+
domain.encode("utf-16le")
125+
except Exception:
126+
domain = user.decode(encoding)
127+
128+
ntlmChallenge = ntlm.NTLMAuthChallenge(type2)
129+
130+
# Let's start with the original flags sent in the type1 message
131+
responseFlags = type1["flags"]
132+
133+
# Token received and parsed. Depending on the authentication
134+
# method we will create a valid ChallengeResponse
135+
ntlmChallengeResponse = ntlm.NTLMAuthChallengeResponse(user, password, ntlmChallenge["challenge"])
136+
137+
clientChallenge = ntlm.b("".join([random.choice(string.digits + string.ascii_letters) for _ in range(8)]))
138+
139+
serverName = ntlmChallenge["TargetInfoFields"]
140+
141+
ntResponse, lmResponse, sessionBaseKey = ntlm.computeResponse(ntlmChallenge["flags"], ntlmChallenge["challenge"],
142+
clientChallenge, serverName, domain, user, password,
143+
lmhash, nthash, use_ntlmv2, channel_binding_value=channel_binding_value)
144+
145+
# Let's check the return flags
146+
if (ntlmChallenge["flags"] & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY) == 0:
147+
# No extended session security, taking it out
148+
responseFlags &= 0xffffffff ^ ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
149+
if (ntlmChallenge["flags"] & ntlm.NTLMSSP_NEGOTIATE_128) == 0:
150+
# No support for 128 key len, taking it out
151+
responseFlags &= 0xffffffff ^ ntlm.NTLMSSP_NEGOTIATE_128
152+
if (ntlmChallenge["flags"] & ntlm.NTLMSSP_NEGOTIATE_KEY_EXCH) == 0:
153+
# No key exchange supported, taking it out
154+
responseFlags &= 0xffffffff ^ ntlm.NTLMSSP_NEGOTIATE_KEY_EXCH
155+
156+
# drop the mic need to unset these flags
157+
# https://github.com/fortra/impacket/blob/master/impacket/examples/ntlmrelayx/clients/ldaprelayclient.py#L72
158+
if ntlmChallenge["flags"] & ntlm.NTLMSSP_NEGOTIATE_SEAL == ntlm.NTLMSSP_NEGOTIATE_SEAL:
159+
responseFlags ^= ntlm.NTLMSSP_NEGOTIATE_SEAL
160+
if ntlmChallenge["flags"] & ntlm.NTLMSSP_NEGOTIATE_SIGN == ntlm.NTLMSSP_NEGOTIATE_SIGN:
161+
responseFlags ^= ntlm.NTLMSSP_NEGOTIATE_SIGN
162+
if ntlmChallenge["flags"] & ntlm.NTLMSSP_NEGOTIATE_ALWAYS_SIGN == ntlm.NTLMSSP_NEGOTIATE_ALWAYS_SIGN:
163+
responseFlags ^= ntlm.NTLMSSP_NEGOTIATE_ALWAYS_SIGN
164+
165+
166+
keyExchangeKey = ntlm.KXKEY(ntlmChallenge["flags"], sessionBaseKey, lmResponse, ntlmChallenge["challenge"], password,
167+
lmhash, nthash, use_ntlmv2)
168+
169+
# Special case for anonymous login
170+
if user == "" and password == "" and lmhash == "" and nthash == "":
171+
keyExchangeKey = b"\x00" * 16
172+
173+
174+
if ntlmChallenge["flags"] & ntlm.NTLMSSP_NEGOTIATE_KEY_EXCH:
175+
exportedSessionKey = ntlm.b("".join([random.choice(string.digits + string.ascii_letters) for _ in range(16)]))
176+
encryptedRandomSessionKey = ntlm.generateEncryptedSessionKey(keyExchangeKey, exportedSessionKey)
177+
else:
178+
encryptedRandomSessionKey = None
179+
exportedSessionKey = keyExchangeKey
180+
181+
ntlmChallengeResponse["flags"] = responseFlags
182+
ntlmChallengeResponse["domain_name"] = domain.encode("utf-16le")
183+
ntlmChallengeResponse["host_name"] = type1.getWorkstation().encode("utf-16le")
184+
if lmResponse == "":
185+
ntlmChallengeResponse["lanman"] = b"\x00"
186+
else:
187+
ntlmChallengeResponse["lanman"] = lmResponse
188+
ntlmChallengeResponse["ntlm"] = ntResponse
189+
if encryptedRandomSessionKey is not None:
190+
ntlmChallengeResponse["session_key"] = encryptedRandomSessionKey
191+
192+
return ntlmChallengeResponse, exportedSessionKey

tests/e2e_commands.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M iis
8585
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M install_elevated
8686
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ioxidresolver
8787
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M security-questions
88+
netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M remove-mic
8889
# currently hanging indefinitely - TODO: look into this
8990
#netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M keepass_discover
9091
#netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M keepass_trigger -o ACTION=ALL USER=LOGIN_USERNAME KEEPASS_CONFIG_PATH="C:\\Users\\LOGIN_USERNAME\\AppData\\Roaming\\KeePass\\KeePass.config.xml"

0 commit comments

Comments
 (0)