Skip to content

Commit 5977b4a

Browse files
committed
Publish NTLMSSP_DOMAIN
1 parent cbb09c4 commit 5977b4a

4 files changed

Lines changed: 216 additions & 11 deletions

File tree

doc/scapy/layers/smb.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,14 @@ A share is identified by a ``name`` and a ``path`` (+ an optional description ca
246246
)
247247
)
248248
249+
**Start a SMB server with NTLM auth in an AD, using machine credentials:**
250+
251+
.. note:: This requires an active account with ``WORKSTATION_TRUST_ACCOUNT`` in its ``userAccountControl``. (otherwise you might get ``STATUS_NO_TRUST_SAM_ACCOUNT``)
252+
253+
.. code:: python
254+
255+
smbserver(ssp=NTLMSSP_DOMAIN(UPN="Computer1@domain.local", HASHNT=bytes.fromhex("7facdc498ed1680c4fd1448319a8c04f")))
256+
249257
**Start a SMB server with Kerberos auth:**
250258

251259
.. code:: python

scapy/layers/msrpce/msnrpc.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -563,15 +563,16 @@ class NetlogonClient(DCERPC_Client):
563563
564564
>>> cli = NetlogonClient()
565565
>>> cli.connect_and_bind("192.168.0.100")
566-
>>> cli.establishSecureChannel(
566+
>>> cli.establish_secure_channel(
567567
... domainname="DOMAIN", computername="WIN10",
568568
... HashNT=bytes.fromhex("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
569569
... )
570570
"""
571571

572572
def __init__(
573573
self,
574-
auth_level=DCE_C_AUTHN_LEVEL.NONE,
574+
# Default to PRIVACY: see KB5021130
575+
auth_level=DCE_C_AUTHN_LEVEL.PKT_PRIVACY,
575576
verb=True,
576577
supportAES=True,
577578
**kwargs,
@@ -645,7 +646,7 @@ def validate_authenticator(self, auth):
645646
if tempcred != auth.Credential.data:
646647
raise ValueError("Server netlogon authenticator is wrong !")
647648

648-
def establishSecureChannel(
649+
def establish_secure_channel(
649650
self,
650651
computername: str,
651652
domainname: str,
@@ -669,6 +670,7 @@ def establishSecureChannel(
669670
# Flow documented in 3.1.4 Session-Key Negotiation
670671
# and sect 3.4.5.2 for specific calls
671672
clientChall = os.urandom(8)
673+
672674
# Step 1: NetrServerReqChallenge
673675
netr_server_req_chall_response = self.sr1_req(
674676
NetrServerReqChallenge_Request(
@@ -693,13 +695,15 @@ def establishSecureChannel(
693695
)
694696
netr_server_req_chall_response.show()
695697
raise ValueError
698+
696699
# Calc NegotiateFlags
697700
NegotiateFlags = FlagValue(
698701
0x602FFFFF, # sensible default (Windows)
699702
names=_negotiateFlags,
700703
)
701704
if self.supportAES:
702705
NegotiateFlags += "AES"
706+
703707
# We are either using NetrServerAuthenticate3 or NetrServerAuthenticateKerberos
704708
if mode == NETLOGON_SECURE_CHANNEL_METHOD.NetrServerAuthenticate3:
705709
# We use the legacy NetrServerAuthenticate3 function (NetlogonSSP)
@@ -735,6 +739,7 @@ def establishSecureChannel(
735739
NetrServerAuthenticate3_Response not in netr_server_auth3_response
736740
or netr_server_auth3_response.status != 0
737741
):
742+
# An error occured.
738743
NegotiatedFlags = None
739744
if NetrServerAuthenticate3_Response in netr_server_auth3_response:
740745
NegotiatedFlags = FlagValue(
@@ -748,14 +753,19 @@ def establishSecureChannel(
748753
% (NegotiatedFlags ^ NegotiateFlags)
749754
)
750755
)
756+
757+
# Show the error
751758
print(
752759
conf.color_theme.fail(
753760
"! %s"
754761
% STATUS_ERREF.get(netr_server_auth3_response.status, "Failure")
755762
)
756763
)
764+
765+
# If error is unknown, show the packet entirely
757766
if netr_server_auth3_response.status not in STATUS_ERREF:
758767
netr_server_auth3_response.show()
768+
759769
raise ValueError
760770
# Check Server Credential
761771
if self.supportAES:
@@ -772,8 +782,10 @@ def establishSecureChannel(
772782
):
773783
print(conf.color_theme.fail("! Invalid ServerCredential."))
774784
raise ValueError
785+
775786
# SessionKey negotiated !
776787
self.SessionKey = SessionKey
788+
777789
# Create the NetlogonSSP and assign it to the local client
778790
self.ssp = self.sock.session.ssp = NetlogonSSP(
779791
SessionKey=self.SessionKey,
@@ -785,5 +797,6 @@ def establishSecureChannel(
785797
NegotiateFlags += "Kerberos"
786798
# TODO
787799
raise NotImplementedError
800+
788801
# Finally alter context (to use the SSP)
789802
self.alter_context()

scapy/layers/ntlm.py

Lines changed: 191 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,8 +1180,8 @@ class NTLMSSP(SSP):
11801180
11811181
Server-only arguments:
11821182
1183-
:param DOMAIN_NB_NAME: the domain Netbios name (default: DOMAIN)
1184-
:param DOMAIN_FQDN: the domain FQDN (default: <domain_nb_name>.local)
1183+
:param DOMAIN_FQDN: the domain FQDN (default: domain.local)
1184+
:param DOMAIN_NB_NAME: the domain Netbios name (default: strip DOMAIN_FQDN)
11851185
:param COMPUTER_NB_NAME: the server Netbios name (default: SRV)
11861186
:param COMPUTER_FQDN: the server FQDN
11871187
(default: <computer_nb_name>.<domain_fqdn>)
@@ -1248,9 +1248,9 @@ def __init__(
12481248
PASSWORD=None,
12491249
USE_MIC=True,
12501250
NTLM_VALUES={},
1251-
DOMAIN_NB_NAME="DOMAIN",
12521251
DOMAIN_FQDN=None,
1253-
COMPUTER_NB_NAME="SRV",
1252+
DOMAIN_NB_NAME=None,
1253+
COMPUTER_NB_NAME=None,
12541254
COMPUTER_FQDN=None,
12551255
IDENTITIES=None,
12561256
DO_NOT_CHECK_LOGIN=False,
@@ -1263,9 +1263,21 @@ def __init__(
12631263
self.HASHNT = HASHNT
12641264
self.USE_MIC = USE_MIC
12651265
self.NTLM_VALUES = NTLM_VALUES
1266-
self.DOMAIN_NB_NAME = DOMAIN_NB_NAME
1267-
self.DOMAIN_FQDN = DOMAIN_FQDN or (self.DOMAIN_NB_NAME.lower() + ".local")
1268-
self.COMPUTER_NB_NAME = COMPUTER_NB_NAME
1266+
if DOMAIN_FQDN is None and UPN is not None:
1267+
from scapy.layers.kerberos import _parse_upn
1268+
1269+
self.DOMAIN_FQDN = _parse_upn(UPN)[1]
1270+
else:
1271+
self.DOMAIN_FQDN = DOMAIN_FQDN or "domain.local"
1272+
self.DOMAIN_NB_NAME = (
1273+
DOMAIN_NB_NAME or self.DOMAIN_FQDN.split(".")[0].upper()[:15]
1274+
)
1275+
if COMPUTER_NB_NAME is None and UPN is not None:
1276+
from scapy.layers.kerberos import _parse_upn
1277+
1278+
self.COMPUTER_NB_NAME = _parse_upn(UPN)[0]
1279+
else:
1280+
self.COMPUTER_NB_NAME = COMPUTER_NB_NAME or "SRV"
12691281
self.COMPUTER_FQDN = COMPUTER_FQDN or (
12701282
self.COMPUTER_NB_NAME.lower() + "." + self.DOMAIN_FQDN
12711283
)
@@ -1843,7 +1855,8 @@ def _getSessionBaseKey(self, Context, auth_tok):
18431855
return NTLMv2_ComputeSessionBaseKey(
18441856
ResponseKeyNT, auth_tok.NtChallengeResponse.NTProofStr
18451857
)
1846-
log_runtime.debug("NTLMSSP: Bad credentials for %s" % username)
1858+
elif self.IDENTITIES:
1859+
log_runtime.debug("NTLMSSP: Bad credentials for %s" % username)
18471860
return None
18481861

18491862
def _checkLogin(self, Context, auth_tok):
@@ -1872,3 +1885,173 @@ def _checkLogin(self, Context, auth_tok):
18721885
if NTProofStr == auth_tok.NtChallengeResponse.NTProofStr:
18731886
return True
18741887
return False
1888+
1889+
1890+
class NTLMSSP_DOMAIN(NTLMSSP):
1891+
"""
1892+
A variant of the NTLMSSP to be used in server mode that gets the session
1893+
keys from the domain using a Netlogon channel.
1894+
1895+
This has the same arguments as NTLMSSP, but supports the following in server
1896+
mode:
1897+
1898+
:param UPN: the UPN of the machine account to login for Netlogon.
1899+
:param HASHNT: the HASHNT of the machine account to use for Netlogon.
1900+
:param PASSWORD: the PASSWORD of the machine acconut to use for Netlogon.
1901+
:param DC_IP: (optional) specify the IP of the DC.
1902+
1903+
Examples::
1904+
1905+
>>> mySSP = NTLMSSP_DOMAIN(
1906+
... UPN="Server1@domain.local",
1907+
... HASHNT=bytes.fromhex("8846f7eaee8fb117ad06bdd830b7586c"),
1908+
... )
1909+
"""
1910+
1911+
def __init__(self, UPN, *args, timeout=3, ssp=None, **kwargs):
1912+
from scapy.layers.kerberos import KerberosSSP
1913+
1914+
# UPN is mandatory
1915+
kwargs["UPN"] = UPN
1916+
1917+
# Either PASSWORD or HASHNT or ssp
1918+
if "HASHNT" not in kwargs and "PASSWORD" not in kwargs and ssp is None:
1919+
raise ValueError(
1920+
"Must specify either 'HASHNT', 'PASSWORD' or "
1921+
"provide a ssp=KerberosSSP()"
1922+
)
1923+
elif ssp is not None and not isinstance(ssp, KerberosSSP):
1924+
raise ValueError("'ssp' can only be None or a KerberosSSP !")
1925+
1926+
# Call parent
1927+
super(NTLMSSP_DOMAIN, self).__init__(
1928+
*args,
1929+
**kwargs,
1930+
)
1931+
1932+
# Treat specific parameters
1933+
self.DC_IP = kwargs.pop("DC_IP", None)
1934+
if self.DC_IP is None:
1935+
# Get DC_IP from dclocator
1936+
from scapy.layers.ldap import dclocator
1937+
1938+
self.DC_IP = dclocator(
1939+
self.DOMAIN_FQDN,
1940+
timeout=timeout,
1941+
debug=kwargs.get("debug", 0),
1942+
).ip
1943+
1944+
# If logging in via Kerberos
1945+
self.ssp = ssp
1946+
1947+
def _getSessionBaseKey(self, Context, ntlm):
1948+
"""
1949+
Return the Session Key by asking the DC.
1950+
"""
1951+
# No user / no domain: skip.
1952+
if not ntlm.UserNameLen or not ntlm.DomainNameLen:
1953+
return super(NTLMSSP_DOMAIN, self)._getSessionBaseKey(Context, ntlm)
1954+
1955+
# Import RPC stuff
1956+
from scapy.layers.dcerpc import NDRUnion
1957+
from scapy.layers.msrpce.msnrpc import (
1958+
NetlogonClient,
1959+
NETLOGON_SECURE_CHANNEL_METHOD,
1960+
)
1961+
from scapy.layers.msrpce.raw.ms_nrpc import (
1962+
NetrLogonSamLogonWithFlags_Request,
1963+
PNETLOGON_NETWORK_INFO,
1964+
PNETLOGON_AUTHENTICATOR,
1965+
NETLOGON_LOGON_IDENTITY_INFO,
1966+
RPC_UNICODE_STRING,
1967+
STRING,
1968+
)
1969+
1970+
# Create NetlogonClient with PRIVACY
1971+
client = NetlogonClient()
1972+
client.connect_and_bind(self.DC_IP)
1973+
1974+
# Establish the Netlogon secure channel (this will bind)
1975+
try:
1976+
if self.ssp is None:
1977+
# Login via classic NetlogonSSP
1978+
client.establish_secure_channel(
1979+
mode=NETLOGON_SECURE_CHANNEL_METHOD.NetrServerAuthenticate3,
1980+
computername=self.COMPUTER_NB_NAME,
1981+
domainname=self.DOMAIN_NB_NAME,
1982+
HashNt=self.HASHNT,
1983+
)
1984+
else:
1985+
# Login via KerberosSSP (Windows 2025)
1986+
# TODO
1987+
client.establish_secure_channel(
1988+
mode=NETLOGON_SECURE_CHANNEL_METHOD.NetrServerAuthenticateKerberos,
1989+
)
1990+
except ValueError:
1991+
log_runtime.warning(
1992+
"Couldn't establish the Netlogon secure channel. "
1993+
"Check the credentials for '%s' !" % self.COMPUTER_NB_NAME
1994+
)
1995+
return super(NTLMSSP_DOMAIN, self)._getSessionBaseKey(Context, ntlm)
1996+
1997+
# Request validation of the NTLM request
1998+
req = NetrLogonSamLogonWithFlags_Request(
1999+
LogonServer="",
2000+
ComputerName=self.COMPUTER_NB_NAME,
2001+
Authenticator=client.create_authenticator(),
2002+
ReturnAuthenticator=PNETLOGON_AUTHENTICATOR(),
2003+
LogonLevel=6, # NetlogonNetworkTransitiveInformation
2004+
LogonInformation=NDRUnion(
2005+
tag=6,
2006+
value=PNETLOGON_NETWORK_INFO(
2007+
Identity=NETLOGON_LOGON_IDENTITY_INFO(
2008+
LogonDomainName=RPC_UNICODE_STRING(
2009+
Buffer=ntlm.DomainName,
2010+
),
2011+
ParameterControl=0x00002AE0,
2012+
UserName=RPC_UNICODE_STRING(
2013+
Buffer=ntlm.UserName,
2014+
),
2015+
Workstation=RPC_UNICODE_STRING(
2016+
Buffer=ntlm.Workstation,
2017+
),
2018+
),
2019+
LmChallenge=Context.chall_tok.ServerChallenge,
2020+
NtChallengeResponse=STRING(
2021+
Buffer=bytes(ntlm.NtChallengeResponse),
2022+
),
2023+
LmChallengeResponse=STRING(
2024+
Buffer=bytes(ntlm.LmChallengeResponse),
2025+
),
2026+
),
2027+
),
2028+
ValidationLevel=6,
2029+
ExtraFlags=0,
2030+
ndr64=client.ndr64,
2031+
)
2032+
2033+
# Get response
2034+
resp = client.sr1_req(req)
2035+
if resp and resp.status == 0:
2036+
# Success
2037+
2038+
# Validate DC authenticator
2039+
client.validate_authenticator(resp.ReturnAuthenticator.value)
2040+
2041+
# Get and return the SessionKey
2042+
UserSessionKey = resp.ValidationInformation.value.value.UserSessionKey
2043+
return bytes(UserSessionKey)
2044+
else:
2045+
# Failed
2046+
print(
2047+
conf.color_theme.fail(
2048+
"! %s" % STATUS_ERREF.get(resp.status, "Failure !")
2049+
)
2050+
)
2051+
if resp.status not in STATUS_ERREF:
2052+
resp.show()
2053+
return super(NTLMSSP_DOMAIN, self)._getSessionBaseKey(Context, ntlm)
2054+
2055+
def _checkLogin(self, Context, auth_tok):
2056+
# Always OK if we got the session key
2057+
return True

scapy/layers/smbclient.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1227,6 +1227,7 @@ def __init__(
12271227
ssp=ssp,
12281228
debug=debug,
12291229
REQUIRE_ENCRYPTION=REQUIRE_ENCRYPTION,
1230+
timeout=timeout,
12301231
**kwargs,
12311232
)
12321233
try:

0 commit comments

Comments
 (0)