Skip to content

Commit c16af5a

Browse files
committed
MS-NRPC: support Kerberos secure channel
1 parent 0a9e9c7 commit c16af5a

2 files changed

Lines changed: 152 additions & 83 deletions

File tree

scapy/layers/msrpce/msnrpc.py

Lines changed: 133 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,17 @@
2222
NL_AUTH_MESSAGE,
2323
NL_AUTH_SIGNATURE,
2424
)
25+
from scapy.layers.kerberos import KerberosSSP, _parse_upn
2526
from scapy.layers.gssapi import (
2627
GSS_C_FLAGS,
2728
GSS_C_NO_CHANNEL_BINDINGS,
2829
GSS_S_COMPLETE,
2930
GSS_S_CONTINUE_NEEDED,
3031
GSS_S_FAILURE,
3132
GSS_S_FLAGS,
33+
SSP,
3234
)
33-
from scapy.layers.ntlm import RC4, RC4K, RC4Init, SSP
35+
from scapy.layers.ntlm import RC4, RC4K, RC4Init, MD4le
3436

3537
from scapy.layers.msrpce.rpcclient import (
3638
DCERPC_Client,
@@ -40,6 +42,8 @@
4042
from scapy.layers.msrpce.raw.ms_nrpc import (
4143
NetrServerAuthenticate3_Request,
4244
NetrServerAuthenticate3_Response,
45+
NetrServerAuthenticateKerberos_Request,
46+
NetrServerAuthenticateKerberos_Response,
4347
NetrServerReqChallenge_Request,
4448
NetrServerReqChallenge_Response,
4549
NETLOGON_SECURE_CHANNEL_TYPE,
@@ -114,15 +118,17 @@
114118
0x00200000: "RODC-passthrough",
115119
# W: Supports Advanced Encryption Standard (AES) encryption and SHA2 hashing.
116120
0x01000000: "AES",
117-
# Supports Kerberos as the security support provider for secure channel setup.
118-
0x20000000: "Kerberos",
121+
# Not used. MUST be ignored on receipt.
122+
0x20000000: "X",
119123
# Y: Supports Secure RPC.
120124
0x40000000: "SecureRPC",
121-
# Not used. MUST be ignored on receipt.
122-
0x80000000: "Z",
125+
# Supports Kerberos as the security support provider for secure channel setup.
126+
0x80000000: "Kerberos",
123127
}
124128
_negotiateFlags = FlagsField("", 0, -32, _negotiateFlags).names
125129

130+
# -- CRYPTO
131+
126132

127133
# [MS-NRPC] sect 3.1.4.3.1
128134
@crypto_validator
@@ -569,8 +575,8 @@ class NetlogonClient(DCERPC_Client):
569575
>>> cli = NetlogonClient()
570576
>>> cli.connect_and_bind("192.168.0.100")
571577
>>> cli.establish_secure_channel(
572-
... domainname="DOMAIN", computername="WIN10",
573-
... HashNT=bytes.fromhex("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
578+
... UPN="WIN10@DOMAIN",
579+
... HASHNT=bytes.fromhex("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
574580
... )
575581
"""
576582

@@ -583,26 +589,25 @@ def __init__(
583589
**kwargs,
584590
):
585591
self.interface = find_dcerpc_interface("logon")
586-
self.ndr64 = False # Netlogon doesn't work with NDR64
587592
self.SessionKey = None
588593
self.ClientStoredCredential = None
589594
self.supportAES = supportAES
590595
super(NetlogonClient, self).__init__(
591596
DCERPC_Transport.NCACN_IP_TCP,
592597
auth_level=auth_level,
593-
ndr64=self.ndr64,
594598
verb=verb,
595599
**kwargs,
596600
)
597601

598-
def connect_and_bind(self, remoteIP):
602+
def connect(self, host, **kwargs):
599603
"""
600604
This calls DCERPC_Client's connect_and_bind to bind the 'logon' interface.
601605
"""
602-
super(NetlogonClient, self).connect_and_bind(remoteIP, self.interface)
603-
604-
def alter_context(self):
605-
return super(NetlogonClient, self).alter_context(self.interface)
606+
super(NetlogonClient, self).connect(
607+
host=host,
608+
interface=self.interface,
609+
**kwargs,
610+
)
606611

607612
def create_authenticator(self):
608613
"""
@@ -653,9 +658,12 @@ def validate_authenticator(self, auth):
653658

654659
def establish_secure_channel(
655660
self,
656-
computername: str,
657-
domainname: str,
658-
HashNt: bytes,
661+
UPN: str,
662+
DC_FQDN: str,
663+
HASHNT: Optional[bytes] = None,
664+
PASSWORD: Optional[str] = None,
665+
KEY=None,
666+
ssp: Optional[KerberosSSP] = None,
659667
mode=NETLOGON_SECURE_CHANNEL_METHOD.NetrServerAuthenticate3,
660668
secureChannelType=NETLOGON_SECURE_CHANNEL_TYPE.WorkstationSecureChannel,
661669
):
@@ -667,39 +675,34 @@ def establish_secure_channel(
667675
668676
:param mode: one of NETLOGON_SECURE_CHANNEL_METHOD. This defines which method
669677
to use to establish the secure channel.
670-
:param computername: the netbios computer account name that is used to establish
671-
the secure channel. (e.g. WIN10)
672-
:param domainname: the netbios domain name to connect to (e.g. DOMAIN)
673-
:param HashNt: the HashNT of the computer account.
678+
:param UPN: the UPN of the computer account name that is used to establish
679+
the secure channel. (e.g. WIN10@domain.local)
680+
:param DC_FQDN: the FQDN name of the DC.
681+
682+
The function then requires one of the following:
683+
684+
:param HASHNT: the HashNT of the computer account (in Authenticate3 mode).
685+
:param KEY: a Kerberos key to use (in Kerberos mode)
686+
:param PASSWORD: the password of the computer account (any mode).
687+
:param ssp: a KerberosSSP to use (in Kerberos mode)
674688
"""
675-
# Flow documented in 3.1.4 Session-Key Negotiation
676-
# and sect 3.4.5.2 for specific calls
677-
clientChall = os.urandom(8)
678-
679-
# Step 1: NetrServerReqChallenge
680-
netr_server_req_chall_response = self.sr1_req(
681-
NetrServerReqChallenge_Request(
682-
PrimaryName=None,
683-
ComputerName=computername,
684-
ClientChallenge=PNETLOGON_CREDENTIAL(
685-
data=clientChall,
686-
),
687-
ndr64=self.ndr64,
688-
ndrendian=self.ndrendian,
689-
)
690-
)
691-
if (
692-
NetrServerReqChallenge_Response not in netr_server_req_chall_response
693-
or netr_server_req_chall_response.status != 0
694-
):
695-
print(
696-
conf.color_theme.fail(
697-
"! %s"
698-
% STATUS_ERREF.get(netr_server_req_chall_response.status, "Failure")
689+
computername, domainname = _parse_upn(UPN)
690+
691+
if mode == NETLOGON_SECURE_CHANNEL_METHOD.NetrServerAuthenticate3:
692+
if ssp or KEY:
693+
raise ValueError("Cannot use 'ssp' on 'KEY' in Authenticate3 mode !")
694+
if not HASHNT:
695+
if PASSWORD:
696+
HASHNT = MD4le(PASSWORD)
697+
else:
698+
raise ValueError("Missing either 'PASSWORD' or 'HASHNT' !")
699+
if "." in domainname:
700+
raise ValueError(
701+
"The UPN in Authenticate3 must have a NETBIOS domain name !"
699702
)
700-
)
701-
netr_server_req_chall_response.show()
702-
raise ValueError
703+
else:
704+
if HASHNT:
705+
raise ValueError("Cannot use 'HASHNT' in Kerberos mode !")
703706

704707
# Calc NegotiateFlags
705708
NegotiateFlags = FlagValue(
@@ -712,23 +715,61 @@ def establish_secure_channel(
712715
# We are either using NetrServerAuthenticate3 or NetrServerAuthenticateKerberos
713716
if mode == NETLOGON_SECURE_CHANNEL_METHOD.NetrServerAuthenticate3:
714717
# We use the legacy NetrServerAuthenticate3 function (NetlogonSSP)
715-
# Step 2: Build the session key
718+
719+
# Make sure the interface is bound
720+
if not self.bind_or_alter(self.interface):
721+
raise ValueError("Bind failed !")
722+
723+
# Flow documented in 3.1.4 Session-Key Negotiation
724+
# and sect 3.4.5.2 for specific calls
725+
clientChall = os.urandom(8)
726+
727+
# Perform NetrServerReqChallenge request
728+
netr_server_req_chall_response = self.sr1_req(
729+
NetrServerReqChallenge_Request(
730+
PrimaryName=None,
731+
ComputerName=computername,
732+
ClientChallenge=PNETLOGON_CREDENTIAL(
733+
data=clientChall,
734+
),
735+
ndr64=self.ndr64,
736+
ndrendian=self.ndrendian,
737+
)
738+
)
739+
if (
740+
NetrServerReqChallenge_Response not in netr_server_req_chall_response
741+
or netr_server_req_chall_response.status != 0
742+
):
743+
print(
744+
conf.color_theme.fail(
745+
"! %s"
746+
% STATUS_ERREF.get(
747+
netr_server_req_chall_response.status, "Failure"
748+
)
749+
)
750+
)
751+
netr_server_req_chall_response.show()
752+
raise ValueError("NetrServerReqChallenge failed !")
753+
754+
# Build the session key
716755
serverChall = netr_server_req_chall_response.ServerChallenge.data
717756
if self.supportAES:
718-
SessionKey = ComputeSessionKeyAES(HashNt, clientChall, serverChall)
757+
SessionKey = ComputeSessionKeyAES(HASHNT, clientChall, serverChall)
719758
self.ClientStoredCredential = ComputeNetlogonCredentialAES(
720759
clientChall, SessionKey
721760
)
722761
else:
723762
SessionKey = ComputeSessionKeyStrongKey(
724-
HashNt, clientChall, serverChall
763+
HASHNT, clientChall, serverChall
725764
)
726765
self.ClientStoredCredential = ComputeNetlogonCredentialDES(
727766
clientChall, SessionKey
728767
)
768+
769+
# Perform Authenticate3 request
729770
netr_server_auth3_response = self.sr1_req(
730771
NetrServerAuthenticate3_Request(
731-
PrimaryName=None,
772+
PrimaryName="\\\\" + DC_FQDN,
732773
AccountName=computername + "$",
733774
SecureChannelType=secureChannelType,
734775
ComputerName=computername,
@@ -740,10 +781,7 @@ def establish_secure_channel(
740781
ndrendian=self.ndrendian,
741782
)
742783
)
743-
if (
744-
NetrServerAuthenticate3_Response not in netr_server_auth3_response
745-
or netr_server_auth3_response.status != 0
746-
):
784+
if netr_server_auth3_response.status != 0:
747785
# An error occurred.
748786
NegotiatedFlags = None
749787
if NetrServerAuthenticate3_Response in netr_server_auth3_response:
@@ -758,20 +796,8 @@ def establish_secure_channel(
758796
% (NegotiatedFlags ^ NegotiateFlags)
759797
)
760798
)
799+
raise ValueError("NetrServerAuthenticate3 failed !")
761800

762-
# Show the error
763-
print(
764-
conf.color_theme.fail(
765-
"! %s"
766-
% STATUS_ERREF.get(netr_server_auth3_response.status, "Failure")
767-
)
768-
)
769-
770-
# If error is unknown, show the packet entirely
771-
if netr_server_auth3_response.status not in STATUS_ERREF:
772-
netr_server_auth3_response.show()
773-
774-
raise ValueError
775801
# Check Server Credential
776802
if self.supportAES:
777803
if (
@@ -798,10 +824,44 @@ def establish_secure_channel(
798824
domainname=domainname,
799825
computername=computername,
800826
)
827+
828+
# Finally alter context (to use the SSP)
829+
if not self.alter_context(self.interface):
830+
raise ValueError("Bind failed !")
831+
801832
elif mode == NETLOGON_SECURE_CHANNEL_METHOD.NetrServerAuthenticateKerberos:
833+
# We use the brand new NetrServerAuthenticateKerberos function
802834
NegotiateFlags += "Kerberos"
803-
# TODO
804-
raise NotImplementedError
805835

806-
# Finally alter context (to use the SSP)
807-
self.alter_context()
836+
# Set KerberosSSP and alter context
837+
if ssp:
838+
self.ssp = self.sock.session.ssp = ssp
839+
else:
840+
self.ssp = self.sock.session.ssp = KerberosSSP(
841+
UPN=UPN,
842+
SPN="netlogon/" + DC_FQDN,
843+
PASSWORD=PASSWORD,
844+
KEY=KEY,
845+
)
846+
if not self.bind_or_alter(self.interface):
847+
raise ValueError("Bind failed !")
848+
849+
# Send AuthenticateKerberos request
850+
netr_server_authkerb_response = self.sr1_req(
851+
NetrServerAuthenticateKerberos_Request(
852+
PrimaryName="\\\\" + DC_FQDN,
853+
AccountName=computername + "$",
854+
AccountType=secureChannelType,
855+
ComputerName=computername,
856+
NegotiateFlags=int(NegotiateFlags),
857+
ndr64=self.ndr64,
858+
ndrendian=self.ndrendian,
859+
)
860+
)
861+
if netr_server_authkerb_response.status != 0:
862+
# An error occured
863+
netr_server_authkerb_response.show()
864+
raise ValueError("NetrServerAuthenticateKerberos failed !")
865+
866+
# The NRPC session key is in this case the kerberos one
867+
self.SessionKey = self.sspcontext.SessionKey

0 commit comments

Comments
 (0)