From 9882e0cfc60b8fdddc14cf90c7966dc2cd279115 Mon Sep 17 00:00:00 2001 From: gpotter2 <10530980+gpotter2@users.noreply.github.com> Date: Sun, 15 Feb 2026 15:15:33 +0100 Subject: [PATCH 1/5] Test more things from scapy-rpc --- test/configs/bsd.utsc | 1 + test/configs/linux.utsc | 1 + test/configs/windows.utsc | 1 + test/configs/windows2.utsc | 1 + test/scapy/layers/msrpce/mslsad.uts | 3 - test/scapy/layers/msrpce/msscmr.uts | 121 ++++++++++++++++++++++++++++ 6 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 test/scapy/layers/msrpce/msscmr.uts diff --git a/test/configs/bsd.utsc b/test/configs/bsd.utsc index 583a27cc0ef..194466f989f 100644 --- a/test/configs/bsd.utsc +++ b/test/configs/bsd.utsc @@ -23,6 +23,7 @@ "test/contrib/isotp_soft_socket.uts" ], "onlyfailed": true, + "extensions": ["scapy-rpc"], "preexec": { "test/contrib/*.uts": "load_contrib(\"%name%\")", "test/cert.uts": "load_layer(\"tls\")", diff --git a/test/configs/linux.utsc b/test/configs/linux.utsc index 7972e1e8739..b26e9166c85 100644 --- a/test/configs/linux.utsc +++ b/test/configs/linux.utsc @@ -20,6 +20,7 @@ ], "breakfailed": true, "onlyfailed": true, + "extensions": ["scapy-rpc"], "preexec": { "test/contrib/*.uts": "load_contrib(\"%name%\")", "test/scapy/layers/tls/*.uts": "load_layer(\"tls\")" diff --git a/test/configs/windows.utsc b/test/configs/windows.utsc index 2e015eb24e9..a38f065e8ca 100644 --- a/test/configs/windows.utsc +++ b/test/configs/windows.utsc @@ -19,6 +19,7 @@ ], "breakfailed": true, "onlyfailed": true, + "extensions": ["scapy-rpc"], "preexec": { "test\\contrib\\*.uts": "load_contrib(\"%name%\")", "test\\scapy\\layers\\tls\\*.uts": "load_layer(\"tls\")" diff --git a/test/configs/windows2.utsc b/test/configs/windows2.utsc index de1fe81d9ed..8d284880dd0 100644 --- a/test/configs/windows2.utsc +++ b/test/configs/windows2.utsc @@ -17,6 +17,7 @@ ], "breakfailed": true, "onlyfailed": true, + "extensions": ["scapy-rpc"], "preexec": { "contrib\\*.uts": "load_contrib(\"%name%\")", "scapy\\layers\\tls\\*.uts": "load_layer(\"tls\")" diff --git a/test/scapy/layers/msrpce/mslsad.uts b/test/scapy/layers/msrpce/mslsad.uts index 195f539dd44..1d9099a6cc7 100644 --- a/test/scapy/layers/msrpce/mslsad.uts +++ b/test/scapy/layers/msrpce/mslsad.uts @@ -5,12 +5,10 @@ * This files are stored in the scapy-rpc extension, but included as part of Scapy's main testing suite for consistency. = [MS-LSAD] - Import [MS-LSAD] -~ disabled from scapy.layers.msrpce.raw.ms_lsad import * = [MS-LSAD] - Build LsarEnumerateAccountsWithUserRight_Request -~ disabled policyHandle = NDRContextHandle(attributes=0, uuid=b'\x92\xa1*"\xc2\xc2\nJ\xaf\x0bL\xdd]C\x8c\x1a') right = "SeAuditPrivilege" @@ -25,7 +23,6 @@ pkt = LsarEnumerateAccountsWithUserRight_Request( assert bytes(pkt) == b'\x00\x00\x00\x00\x92\xa1*"\xc2\xc2\nJ\xaf\x0bL\xdd]C\x8c\x1a\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00 \x00 \x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00S\x00e\x00A\x00u\x00d\x00i\x00t\x00P\x00r\x00i\x00v\x00i\x00l\x00e\x00g\x00e\x00' = [MS-LSAD] - Dissect LsarEnumerateAccountsWithUserRight_Response -~ disabled from scapy.layers.smb2 import WINNT_SID diff --git a/test/scapy/layers/msrpce/msscmr.uts b/test/scapy/layers/msrpce/msscmr.uts new file mode 100644 index 00000000000..b925b2ba7d9 --- /dev/null +++ b/test/scapy/layers/msrpce/msscmr.uts @@ -0,0 +1,121 @@ +% MS-SCMR tests + ++ [MS-SCMR] build and dissection tests + +* This files are stored in the scapy-rpc extension, but included as part of Scapy's main testing suite for consistency. + += [MS-SCMR] - Import [MS-SCMR] + +from scapy.layers.msrpce.raw.ms_scmr import * + += [MS-SCMR] - Dissect ROpenServiceW_Request + +DATA = bytes.fromhex('00000000053914c3f0543646af7317818dbee820160000000000000016000000530065006300750072006900740079004800650061006c00740068005300650072007600690063006500000005000000') + +pkt = ROpenServiceW_Request(DATA, ndr64=False) +assert pkt.valueof("lpServiceName") == b"SecurityHealthService" +assert pkt.hSCManager.uuid == b'\x059\x14\xc3\xf0T6F\xafs\x17\x81\x8d\xbe\xe8 ' + += [MS-SCMR] - Re-Build ROpenServiceW_Request + +pkt = ROpenServiceW_Request( + hSCManager=NDRContextHandle(uuid=b'\x059\x14\xc3\xf0T6F\xafs\x17\x81\x8d\xbe\xe8 '), + lpServiceName=b"SecurityHealthService", + dwDesiredAccess=5, + ndr64=False, +) +assert bytes(pkt) == DATA + += [MS-SCMR] - Dissect RQueryServiceConfigW_Request + +DATA = bytes.fromhex('00000000d76d93463d7e9047856bbc0839ef836910100000') + +pkt = RQueryServiceConfigW_Request(DATA, ndr64=False) +assert pkt.cbBufSize == 4112 + += [MS-SCMR] - Dissect RQueryServiceConfig2W_Request + +DATA = bytes.fromhex('00000000d76d93463d7e9047856bbc0839ef83690100000010100000') + +pkt = RQueryServiceConfig2W_Request(DATA, ndr64=False) +assert pkt.dwInfoLevel == 1 +assert pkt.cbBufSize == 4112 + += [MS-SCMR] - Dissect RQueryServiceConfig2W_Response + +import zlib +DATA = zlib.decompress(bytes.fromhex('789ced8f4b0ec2300c05078903e408d9711d368875092d204a8b4805d7e7a5286c10127bde5876fc89ed240458026b6e8cdc39b1a72513e968488a7be924add9513723175507e941954136b261ab2951b9ab24cf5e9294be124deaacd5a87cf11a761f1b9ad93e14f5921a278eca24ceef7d657f9db7faba2fabdaceffe8a4e9871718638c31c618638c31ff4158bcce27d9e630e0')) + +pkt = RQueryServiceConfig2W_Response(DATA, ndr64=False, request_packet=pkt) +assert pkt.lpBuffer.max_count == 4112 +assert pkt.pcbBytesNeeded == 272 +assert pkt.status == 0 +assert pkt.lpBuffer.value[4:].decode("utf-16le").rstrip("\x00") == "Provides facilities for managing UWP apps access to app capabilities as well as checking an app's access to specific app capabilities" + += [MS-SCMR] - Dissect RQueryServiceConfigW_Response + +DATA = bytes.fromhex('200000000200000001000000000002000400020000000000080002000c0002001000020030000000000000003000000043003a005c00570049004e0044004f00570053005c00730079007300740065006d00330032005c0073007600630068006f00730074002e0065007800650020002d006b0020006f007300700072006900760061006300790020002d0070000000010000000000000001000000000000002e000000000000002e000000720070006300730073002f00730074006100740065007200650070006f007300690074006f00720079002f0043006f00720065004d006500730073006100670069006e0067005200650067006900730074007200610072002f0000000c000000000000000c0000004c006f00630061006c00530079007300740065006d0000002200000000000000220000004300610070006100620069006c00690074007900200041006300630065007300730020004d0061006e0061006700650072002000530065007200760069006300650000007e01000000000000') +pkt = RQueryServiceConfigW_Response(DATA, ndr64=False) +assert pkt.status == 0 +assert pkt.pcbBytesNeeded == 389 +assert pkt.lpServiceConfig.dwServiceType == 32 +assert pkt.lpServiceConfig.dwErrorControl == 1 +assert pkt.lpServiceConfig.valueof("lpBinaryPathName") == b'C:\\WINDOWS\\system32\\svchost.exe -k osprivacy -p' +assert pkt.lpServiceConfig.valueof("lpDependencies") == b'rpcss/staterepository/CoreMessagingRegistrar/' +assert pkt.lpServiceConfig.valueof("lpDisplayName") == b'Capability Access Manager Service' + += [MS-SCMR] - Dissect RCreateServiceW_Request + +DATA = bytes.fromhex('00000000dea1de2e22144844a5f5ea3948e8add905000000000000000500000074006500730074000000000000000000ff010f001000000003000000010000001c000000000000001c00000043003a005c00570069006e0064006f00770073005c00530079007300740065006d00330032005c0063006d0064002e00650078006500000000000000000000000000000000000000000000000000000000000000') +pkt = RCreateServiceW_Request(DATA, ndr64=False) +assert pkt.valueof("lpServiceName") == b"test" +assert pkt.dwDesiredAccess == 983551 +assert pkt.dwStartType == 3 +assert pkt.dwServiceType == 16 +assert pkt.dwErrorControl == 1 +assert pkt.valueof("lpBinaryPathName") == b"C:\\Windows\\System32\\cmd.exe" +assert pkt.lpDisplayName is None +assert pkt.dwPwSize == 0 + += [MS-SCMR] - Re-Build RCreateServiceW_Request + +pkt = RCreateServiceW_Request( + hSCManager=NDRContextHandle(uuid=b'\xde\xa1\xde."\x14HD\xa5\xf5\xea9H\xe8\xad\xd9'), + lpServiceName=b"test", + dwDesiredAccess=983551, + dwServiceType=16, + dwStartType=3, + dwErrorControl=1, + lpBinaryPathName=b"C:\\Windows\\System32\\cmd.exe", + ndr64=False, +) +assert bytes(pkt) == DATA + += [MS-SCMR] - Dissect RCreateServiceW_Request - with lpDisplayName + +DATA = bytes.fromhex('00000000dcf903ed9e7b604ca9971ce8d0938b2405000000000000000500000074006500730074000000000000000200050000000000000005000000740065007300740000000000ff010f001000000003000000010000001c000000000000001c00000043003a005c00570069006e0064006f00770073005c00530079007300740065006d00330032005c0063006d0064002e00650078006500000000000000000000000000000000000000000000000000000000000000') +pkt = RCreateServiceW_Request(DATA, ndr64=False) +assert pkt.valueof("lpServiceName") == b"test" +assert pkt.dwDesiredAccess == 983551 +assert pkt.dwStartType == 3 +assert pkt.dwServiceType == 16 +assert pkt.dwErrorControl == 1 +assert pkt.valueof("lpBinaryPathName") == b"C:\\Windows\\System32\\cmd.exe" +assert pkt.lpDisplayName.referent_id == 0x20000 +assert pkt.valueof("lpDisplayName") == b"test" +assert pkt.dwPwSize == 0 + += [MS-SCMR] - Build RCreateServiceW_Request - with lpDisplayName + +pkt = RCreateServiceW_Request( + hSCManager=NDRContextHandle(uuid=b'\xdc\xf9\x03\xed\x9e{`L\xa9\x97\x1c\xe8\xd0\x93\x8b$'), + lpServiceName=b"test", + lpDisplayName=b"test", + dwDesiredAccess=983551, + dwServiceType=16, + dwStartType=3, + dwErrorControl=1, + lpBinaryPathName=b"C:\\Windows\\System32\\cmd.exe", + ndr64=False, +) +assert bytes(pkt) == DATA From 13d7841c7bfacd304b9c9ba08f0886561fd9d0d8 Mon Sep 17 00:00:00 2001 From: gpotter2 <10530980+gpotter2@users.noreply.github.com> Date: Sun, 15 Feb 2026 15:16:06 +0100 Subject: [PATCH 2/5] smbclient: Fix glob support in put --- scapy/layers/smbclient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scapy/layers/smbclient.py b/scapy/layers/smbclient.py index d143cd29a01..0eebcb70fd1 100644 --- a/scapy/layers/smbclient.py +++ b/scapy/layers/smbclient.py @@ -1732,7 +1732,7 @@ def cat_complete(self, file): return [] return self._fs_complete(file) - @CLIUtil.addcommand(spaces=True) + @CLIUtil.addcommand(spaces=True, globsupport=True) def put(self, file): """ Upload a file From ea8c3349b1283f81b3e8741888e53bbe04f29b5c Mon Sep 17 00:00:00 2001 From: gpotter2 <10530980+gpotter2@users.noreply.github.com> Date: Sun, 15 Feb 2026 15:16:47 +0100 Subject: [PATCH 3/5] NTLM: check user case insensitively --- scapy/layers/ntlm.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/scapy/layers/ntlm.py b/scapy/layers/ntlm.py index 60258eb6be4..4e0d956be72 100644 --- a/scapy/layers/ntlm.py +++ b/scapy/layers/ntlm.py @@ -1416,7 +1416,15 @@ def __init__( self.COMPUTER_NB_NAME.lower() + "." + self.DOMAIN_FQDN ) - self.IDENTITIES = IDENTITIES + if IDENTITIES: + self.IDENTITIES = { + # Windows usernames are case insensitive + user.upper(): hashnt + for user, hashnt in IDENTITIES.items() + } + else: + self.IDENTITIES = IDENTITIES + self.DO_NOT_CHECK_LOGIN = DO_NOT_CHECK_LOGIN self.SERVER_CHALLENGE = SERVER_CHALLENGE super(NTLMSSP, self).__init__(**kwargs) @@ -1681,7 +1689,10 @@ def GSS_Init_sec_context( + [ AV_PAIR( AvId="MsvAvSingleHost", - Value=Single_Host_Data(MachineID=os.urandom(32)), + Value=Single_Host_Data( + MachineID=os.urandom(32), + PermanentMachineID=os.urandom(32), + ), ), ] + ( @@ -2048,7 +2059,8 @@ def _getSessionBaseKey(self, Context, auth_tok): Function that returns the SessionBaseKey from the ntlm Authenticate. """ try: - username = auth_tok.UserName + # Windows usernames are case insensitive + username = auth_tok.UserName.upper() except AttributeError: username = None try: @@ -2076,9 +2088,9 @@ def _checkLogin(self, Context, auth_tok): Overwrite and return True to bypass. """ - # Create the NTLM AUTH try: - username = auth_tok.UserName + # Windows usernames are case insensitive + username = auth_tok.UserName.upper() except AttributeError: username = None try: From 2184bf49ea153fc470f0db68a700d31703e30aa0 Mon Sep 17 00:00:00 2001 From: gpotter2 <10530980+gpotter2@users.noreply.github.com> Date: Sun, 15 Feb 2026 15:17:16 +0100 Subject: [PATCH 4/5] DCE/RPC client: improve endpoint mapper resilliency - close properly when it crashes - fix over SMB where the pipe was open too soon --- scapy/layers/msrpce/rpcclient.py | 34 ++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/scapy/layers/msrpce/rpcclient.py b/scapy/layers/msrpce/rpcclient.py index 8afe70ad55e..b8d227be17d 100644 --- a/scapy/layers/msrpce/rpcclient.py +++ b/scapy/layers/msrpce/rpcclient.py @@ -197,6 +197,7 @@ def connect( transport=self.transport, ndrendian=self.ndrendian, verb=self.verb, + ssp=self.ssp, smb_kwargs=smb_kwargs, ) if endpoints: @@ -234,14 +235,15 @@ def connect( ) if self.transport == DCERPC_Transport.NCACN_NP: # SMB - # If the endpoint is provided, connect to it. - if endpoint is not None: - self.open_smbpipe(endpoint) - # We pack the socket into a SMB_RPC_SOCKET sock = self.smbrpcsock = SMB_RPC_SOCKET.from_tcpsock( sock, ssp=self.ssp, **smb_kwargs ) + + # If the endpoint is provided, connect to it. + if endpoint is not None: + self.open_smbpipe(endpoint) + self.sock = DceRpcSocket(sock, DceRpc5, **self.dcesockargs) elif self.transport == DCERPC_Transport.NCACN_IP_TCP: self.sock = DceRpcSocket( @@ -351,6 +353,9 @@ def sr1_req(self, pkt, **kwargs): if "opnum" in kwargs: opnum["opnum"] = kwargs.pop("opnum") + # Set NDR64 + pkt.ndr64 = self.ndr64 + # Send/receive resp = self.sr1( DceRpc5Request( @@ -486,7 +491,10 @@ def _check_bind_context(self, interface, contexts) -> bool: return False def _bind( - self, interface: Union[DceRpcInterface, ComInterface], reqcls, respcls + self, + interface: Union[DceRpcInterface, ComInterface], + reqcls, + respcls, ) -> bool: """ Internal: used to send a bind/alter request @@ -681,11 +689,10 @@ def _bind( else: print(conf.color_theme.fail("! Failure")) resp.show() - if DceRpc5Fault in resp: - if resp[DceRpc5Fault].payload and not isinstance( - resp[DceRpc5Fault].payload, conf.raw_layer - ): - resp[DceRpc5Fault].payload.show() + if resp[DceRpc5Fault].payload and not isinstance( + resp[DceRpc5Fault].payload, conf.raw_layer + ): + resp[DceRpc5Fault].payload.show() else: print(conf.color_theme.fail("! Failure")) resp.show() @@ -900,7 +907,6 @@ def epm_map(self, interface): return endpoints elif status == 0x16C9A0D6: if self.verb: - pkt.show() print( conf.color_theme.fail( "! Server errored: 'There are no elements that satisfy" @@ -953,7 +959,9 @@ def get_endpoint( client.connect(ip, endpoint=endpoint, smb_kwargs=smb_kwargs) client.bind(find_dcerpc_interface("ept")) - endpoints = client.epm_map(interface) + try: + endpoints = client.epm_map(interface) + finally: + client.close() - client.close() return endpoints From 4ed132941d57234ba220ca800353883348f82f06 Mon Sep 17 00:00:00 2001 From: gpotter2 <10530980+gpotter2@users.noreply.github.com> Date: Sun, 15 Feb 2026 22:18:07 +0100 Subject: [PATCH 5/5] Fix explore() with plugins --- scapy/config.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scapy/config.py b/scapy/config.py index 3c5f05cf49b..95c4a419e99 100755 --- a/scapy/config.py +++ b/scapy/config.py @@ -314,7 +314,10 @@ def layers(self): except ImportError: import __builtin__ # noqa: F401 for lay in self.ldict: - doc = eval(lay).__doc__ + try: + doc = eval(lay).__doc__ + except AttributeError: + continue result.append((lay, doc.strip().split("\n")[0] if doc else lay)) return result