Skip to content

Commit 58519e5

Browse files
authored
Windows SSPs: various improvements (#4314)
- All SSPs: support PFC_SUPPORT_HEADER_SIGN for DCE/RPC - Refactor SSPs and DCE/RPC client/server to use req_flags in GSS_Init_sec_context - KerberosSSP: - support DCE_STYLE (for DCE/RPC) - add MIC/WRAP support - NTLMSSP: fix SeqNum when used with SPNEGO - Fix a bunch of SPNEGO edge cases - Many tests
1 parent 4e343bb commit 58519e5

24 files changed

+2319
-511
lines changed

doc/scapy/layers/dcerpc.rst

Lines changed: 102 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -192,20 +192,77 @@ Here's an example sending a ``ServerAlive`` over the ``IObjectExporter`` interfa
192192
resp = client.sr1_req(req)
193193
resp.show()
194194
195-
Here's a different example, this time connecting over ``NCACN_NP`` to `[MS-SAMR] <https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/4df07fab-1bbc-452f-8e92-7853a3c7e380>`_ to enumerate the domains a server is in:
195+
Here's the same example, but this time asking for :const:`~scapy.layers.dcerpc.RPC_C_AUTHN_LEVEL.PKT_PRIVACY` (encryption) using ``NTLMSSP``:
196+
197+
.. code-block:: python
198+
199+
from scapy.layers.ntlm import *
200+
from scapy.layers.dcerpc import *
201+
from scapy.layers.msrpce.all import *
202+
203+
ssp = NTLMSSP(
204+
UPN="Administrator",
205+
PASSWORD="Password1",
206+
)
207+
client = DCERPC_Client(
208+
DCERPC_Transport.NCACN_IP_TCP,
209+
auth_level=DCE_C_AUTHN_LEVEL.PKT_PRIVACY,
210+
ssp=ssp,
211+
ndr64=False,
212+
)
213+
client.connect("192.168.0.100")
214+
client.bind(find_dcerpc_interface("IObjectExporter"))
215+
216+
req = ServerAlive_Request(ndr64=False)
217+
resp = client.sr1_req(req)
218+
resp.show()
219+
220+
Again, but this time using :const:`~scapy.layers.dcerpc.RPC_C_AUTHN_LEVEL.PKT_INTEGRITY` (signing) using ``SPNEGOSSP[KerberosSSP]``:
221+
222+
.. code-block:: python
223+
224+
from scapy.layers.kerberos import *
225+
from scapy.layers.spnego import *
226+
from scapy.layers.dcerpc import *
227+
from scapy.layers.msrpce.all import *
228+
229+
ssp = SPNEGOSSP(
230+
[
231+
KerberosSSP(
232+
233+
PASSWORD="Password1",
234+
SPN="host/dc1",
235+
)
236+
]
237+
)
238+
client = DCERPC_Client(
239+
DCERPC_Transport.NCACN_IP_TCP,
240+
auth_level=DCE_C_AUTHN_LEVEL.PKT_INTEGRITY,
241+
ssp=ssp,
242+
ndr64=False,
243+
)
244+
client.connect("192.168.0.100")
245+
client.bind(find_dcerpc_interface("IObjectExporter"))
246+
247+
req = ServerAlive_Request(ndr64=False)
248+
resp = client.sr1_req(req)
249+
resp.show()
250+
251+
Here's a different example, this time connecting over :const:`~scapy.layers.dcerpc.DCERPC_Transport.NCACN_NP` to `[MS-SAMR] <https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-samr/4df07fab-1bbc-452f-8e92-7853a3c7e380>`_ to enumerate the domains a server is in:
196252

197253
.. code-block:: python
198254
199255
from scapy.layers.ntlm import NTLMSSP, MD4le
200256
from scapy.layers.dcerpc import *
201257
from scapy.layers.msrpce.all import *
202258
259+
ssp = NTLMSSP(
260+
UPN="User",
261+
HASHNT=MD4le("Password"),
262+
)
203263
client = DCERPC_Client(
204264
DCERPC_Transport.NCACN_NP,
205-
ssp=NTLMSSP(
206-
UPN="User",
207-
HASHNT=MD4le("Password"),
208-
),
265+
ssp=ssp,
209266
ndr64=False,
210267
)
211268
client.connect("192.168.0.100")
@@ -238,7 +295,9 @@ Here's a different example, this time connecting over ``NCACN_NP`` to `[MS-SAMR]
238295
239296
.. note:: As you can see, we used the :class:`~scapy.layers.ntlm.NTLMSSP` security provider in the above connection.
240297

241-
There's an extension of the ``DCERPC_Client``: the ``NetlogonClient`` which is unfinished because I can't seem to make ``NetrLogonGetCapabilities`` work, but worth mentioning because it implements its own ``NetlogonSSP``:
298+
There are extensions to the :class:`~scapy.layers.msrpce.rpcclient.DCERPC_Client` class:
299+
300+
- the :class:`~scapy.layers.msrpce.msnrpc.NetlogonClient`, worth mentioning because it implements its own :class:`~scapy.layers.msrpce.msnrpc.NetlogonSSP`:
242301

243302
.. code-block:: python
244303
@@ -254,6 +313,8 @@ There's an extension of the ``DCERPC_Client``: the ``NetlogonClient`` which is u
254313
client.negotiate_sessionkey(bytes.fromhex("77777777777777777777777777777777"))
255314
client.close()
256315
316+
- the :class:`~scapy.layers.msrpce.msdcom.DCOM_Client` (unfinished)
317+
257318
Server
258319
------
259320

@@ -335,6 +396,41 @@ To start an endpoint mapper (this should be a separate process from your RPC ser
335396
.. note:: Currently, a DCERPC_Server will let a client bind on all interfaces that Scapy has registered (imported). Supposedly though, you know which RPCs are going to be queried.
336397

337398

399+
Passive sniffing
400+
----------------
401+
402+
If you're doing passive sniffing of a DCE/RPC session, you can instruct Scapy to still use its DCE/RPC session in order to check the INTEGRITY and decrypt (if PRIVACY is used) the packets.
403+
404+
.. code-block:: python
405+
406+
from scapy.all import *
407+
408+
# Bind DCE/RPC port
409+
bind_bottom_up(TCP, DceRpc5, dport=12345)
410+
bind_bottom_up(TCP, DceRpc5, dport=12345)
411+
412+
# Enable passive DCE/RPC session
413+
conf.dcerpc_session_enable = True
414+
415+
# Define SSPs that can be used for decryption / verify
416+
conf.winssps_passive = [
417+
SPNEGOSSP([
418+
NTLMSSP(
419+
IDENTITIES={
420+
"User1": MD4le("Password1!"),
421+
},
422+
),
423+
])
424+
]
425+
426+
# Sniff
427+
pkts = sniff(offline="dcerpc_exchange.pcapng", session=TCPSession)
428+
pkts.show()
429+
430+
431+
.. warning:: Only NTLM is currently fully supported. KerberosSSP is sadly not supported as of today, nor is NetlogonSSP.
432+
433+
338434
Define custom packets
339435
---------------------
340436

doc/scapy/layers/gssapi.rst

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
GSSAPI
2+
======
3+
4+
Scapy provides access to various `Security Providers <https://learn.microsoft.com/en-us/windows-server/security/windows-authentication/security-support-provider-interface-architecture>`_ following the GSSAPI model, but aiming at interacting with the Windows world.
5+
6+
.. note::
7+
8+
The GSSAPI interfaces are based off the following documentations:
9+
10+
- GSSAPI: `RFC4121 <https://datatracker.ietf.org/doc/html/rfc4121>`_ / `RFC2743 <https://datatracker.ietf.org/doc/html/rfc2743>`_
11+
- GSSAPI C bindings: `RFC2744 <https://datatracker.ietf.org/doc/html/rfc2744>`_
12+
13+
Usage
14+
-----
15+
16+
The following SSPs are currently provided:
17+
18+
- :class:`~scapy.layers.ntlm.NTLMSSP`
19+
- :class:`~scapy.layers.kerberos.KerberosSSP`
20+
- :class:`~scapy.layers.spnego.SPNEGOSSP`
21+
- :class:`~scapy.layers.msrpce.msnrpc.NetlogonSSP`
22+
23+
Basically those are classes that implement two functions, trying to micmic the RFCs:
24+
25+
- :func:`~scapy.layers.gssapi.SSP.GSS_Init_sec_context`: called by the client, passing it a ``Context`` and optionally a token
26+
- :func:`~scapy.layers.gssapi.SSP.GSS_Accept_sec_context`: called by the server, passing it a ``Context`` and optionally a token
27+
28+
They both return the updated Context, a token to optionally send to the server/client and a GSSAPI status code.
29+
30+
.. note::
31+
32+
You can typically use it in :class:`~scapy.layers.smbclient.SMB_Client`, :class:`~scapy.layers.smbserver.SMB_Server`, :class:`~scapy.layers.msrpce.rpcclient.DCERPC_Client` or :class:`~scapy.layers.msrpce.rpcserver.DCERPC_Server`.
33+
Have a look at `SMB <smb.html>`_ and `DCE/RPC <dcerpc.html>`_ to get examples on how to use it.
34+
35+
Let's implement our own client that uses one of those SSPs.
36+
37+
Client
38+
~~~~~~
39+
40+
First let's create the SSP. We'll take :class:`~scapy.layers.ntlm.NTLMSSP` as an example but the others would work just as well.
41+
42+
.. code:: python
43+
44+
from scapy.layers.ntlm import *
45+
clissp = NTLMSSP(
46+
47+
PASSWORD="Password1!",
48+
)
49+
50+
Let's get the first token (in this case, the ntlm negotiate):
51+
52+
.. code:: python
53+
54+
# We start with a context = None and a val (server answer) = None
55+
sspcontext, token, status = clissp.GSS_Init_sec_context(None, None)
56+
# sspcontext will be passed to subsequent calls and stores information
57+
# regarding this NTLM session, token is the NTLM_NEGOTIATE and status
58+
# the state of the SSP
59+
assert status == GSS_S_CONTINUE_NEEDED
60+
61+
Send this token to the server, or use it as required, and get back the server's token.
62+
You can then pass that token as the second parameter of :func:`~scapy.layers.gssapi.SSP.GSS_Init_sec_context`.
63+
To give an example, this is what is done in the LDAP client:
64+
65+
.. code:: python
66+
67+
# Do we have a token to send to the server?
68+
while token:
69+
resp = self.sr1(
70+
LDAP_BindRequest(
71+
bind_name=ASN1_STRING(b""),
72+
authentication=LDAP_Authentication_SaslCredentials(
73+
mechanism=ASN1_STRING(b"SPNEGO"),
74+
credentials=ASN1_STRING(bytes(token)),
75+
),
76+
)
77+
)
78+
sspcontext, token, status = clissp.GSS_Init_sec_context(
79+
self.sspcontext, GSSAPI_BLOB(resp.protocolOp.serverSaslCreds.val)
80+
)
81+
82+
If you want to use :class:`~scapy.layers.spnego.SPEGOSSP`, you could wrap the SSP as so:
83+
84+
.. code:: python
85+
86+
from scapy.layers.ntlm import *
87+
from scapy.layers.spnegossp import SPNEGOSSP
88+
clissp = SPNEGOSSP(
89+
[
90+
NTLMSSP(
91+
92+
PASSWORD="Password1!",
93+
),
94+
KerberosSSP(
95+
96+
PASSWORD="Password1!",
97+
SPN="host/dc1.domain.local",
98+
),
99+
]
100+
)
101+
102+
You can override the GSS-API ``req_flags`` when calling :func:`~scapy.layers.gssapi.SSP.GSS_Init_sec_context`, using values from :class:`~scapy.layers.gssapi.GSS_C_FLAGS`:
103+
104+
.. code:: python
105+
106+
sspcontext, token, status = clissp.GSS_Init_sec_context(None, None, req_flags=(
107+
GSS_C_FLAGS.GSS_C_EXTENDED_ERROR_FLAG |
108+
GSS_C_FLAGS.GSS_C_MUTUAL_FLAG |
109+
GSS_C_FLAGS.GSS_C_CONF_FLAG # Asking for CONFIDENTIALITY
110+
))
111+
112+
113+
Server
114+
~~~~~~
115+
116+
Implementing a server is very similar to a client but you'd use :func:`~scapy.layers.gssapi.SSP.GSS_Accept_sec_context` instead.
117+
The client is properly authenticated when `status` is `GSS_S_COMPLETE`.

doc/scapy/layers/ntlm.rst

Lines changed: 0 additions & 35 deletions
This file was deleted.

scapy/config.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,6 +1103,15 @@ class Conf(ConfClass):
11031103
)
11041104
#: Dictionary containing parsed NSS Keys
11051105
tls_nss_keys: Dict[str, bytes] = None
1106+
#: When TCPSession is used, parse DCE/RPC sessions automatically.
1107+
#: This should be used for passive sniffing.
1108+
dcerpc_session_enable = False
1109+
#: Some implementations of DCE/RPC incorrectly use header signing
1110+
#: without properly negotiating it. This forces it on.
1111+
dcerpc_force_header_signing = False
1112+
#: Windows SSPs for sniffing. This is used with
1113+
#: dcerpc_session_enable
1114+
winssps_passive = []
11061115

11071116
def __getattribute__(self, attr):
11081117
# type: (str) -> Any

0 commit comments

Comments
 (0)