MS-FSRVP, MS-SCMR (#2265)

* Fix some SMB relay server syntax error

* Added MS-FSRVP, 2 new calls to SCMR

* Remove wrong email

* added tests for MS-FSRVP

* fix some errors with vairable typing

* Added remote mark for test FSRVP

* Fix FSRVP type hints for Python 3.9

* Add offline NDR tests for FSRVP and SCMR

* Expand FSRVP and SCMR remote tests and skip unsupported methods

* Fix SCMR service creation response tag pointer types

---------

Co-authored-by: Gabi Gonzalez <[email protected]>
This commit is contained in:
AndreySolodandGabi Gonzalez authored and GitHub committed 2026-09-29 17:21:50 -03:00
1 parent ba33c7ed5f
commit 505d39ec39
5 files changed
+832 -7

No files matched your search

+70 -4
View File
@@ -95,8 +95,10 @@ Active Directory Setup and Configuration
In order to run remote test cases, a target Active Directory need to be properly
configured with the expected objects. Current remote test cases are expected to
work against a Windows Server 2012 R2 Domain Controller. The following are the
main steps required:
work against a Windows Server 2012 R2 Domain Controller. Some protocol tests need
additional server roles or newer Windows versions; see
[Protocol-specific remote test prerequisites](#protocol-specific-remote-test-prerequisites).
The following are the main steps required:
1. Make sure to disable the firewall on the interface you want to use for connecting
to the Domain Controller.
@@ -231,7 +233,7 @@ do that by running Mimikatz with an elevated user and executing:
Configure Remote Test Cases
---------------------------
Create a copy of the [dcetest.cfg.template](tests/dcetests.cfg.template) file and
Create a copy of the [dcetests.cfg.template](tests/dcetests.cfg.template) file and
configure it with the necessary information associated to the Active Directory you
configured. Path to the configuration file to use when running tests can be then
specified in the following ways:
@@ -239,7 +241,7 @@ specified in the following ways:
* Using the pytest `--remote-config` command-line option.
* Using the pytest `remote-config` option in `tox.ini`.
* Using the `REMOTE_CONFIG` environment variable.
* Default to loading from `tests/dcetests.cg`.
* Default to loading from `tests/dcetests.cfg`.
For example, you can keep configuration of different environments in
separate files, and specify which one you want the test to run against:
@@ -257,3 +259,67 @@ resolve DNS queries for the Active Directory Domain configured. If you don't wan
change your test machine's DNS settings to point to the AD DNS server, you can
configure your system to statically resolve (e.g. via `/etc/hosts` file) the host
and domain FQDN to the server's IP address.
Protocol-specific remote test prerequisites
------------------------------------------
Some RPC interfaces require additional Windows roles or a newer operating system
than the Windows Server 2012 R2 environment described above. Configure the target
for each interface as follows, then specify its connection information as
described in [Configure Remote Test Cases](#configure-remote-test-cases).
### SCMR: RCreateWowService and ROpenSCManager2
For the `RCreateWowService` and `ROpenSCManager2` cases in
[test_scmr.py](tests/dcerpc/test_scmr.py):
These cases skip automatically when the target reports an unsupported RPC
operation or `ERROR_CALL_NOT_IMPLEMENTED`. Other errors, including access denied
and connection failures, still fail the tests. No version flag is required.
1. Prepare a Windows host supporting these methods, such as Windows 11.
A domain controller is not required for these cases. Refer to the
[MS-SCMR product behavior notes](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-scmr/041d2a89-9d7d-4f79-91a1-c336d0a668f6)
for version restrictions. In particular, `ROpenSCManager2` is unavailable on
Windows 10 version 1809 and earlier and Windows Server version 1809 and earlier.
1. Configure an administrator account with network logon access and permission
to open the SCM database and create, query, and delete services.
1. Allow SMB connections on TCP 445. For the TCP transport cases, also allow
TCP 135 and the dynamic RPC ports used by the server.
1. Set the account and target details in the remote configuration file's
`[TCPTransport]` section, which is used by both transport classes. For a local
account on a standalone host, set `domain` to the target computer name and
`username` to the local account name.
### FSRVP: file-server shadow copies
For [test_fsrvp.py](tests/dcerpc/test_fsrvp.py), prepare a Windows Server with
storage that supports shadow copies through FSRVP. Run the following setup steps
in an elevated PowerShell session on the target:
1. Install the File Server VSS Agent Service role service. Complete any restart
requested by the installation before continuing.
PS C:\> Install-WindowsFeature -Name File-Services,FS-VSS-Agent
1. Enable and start the `FSSAgent` service.
PS C:\> Set-Service -Name FSSAgent -StartupType Automatic
PS C:\> Start-Service -Name FSSAgent
1. Create a regular SMB share named `Windows`, matching the `SHARE_NAME` constant
in the test module. Administrative shares such as `C$` cannot be used. For
example, create a test directory and share it with the test administrator:
PS C:\> New-Item -Path C:\FSRVPTest -ItemType Directory
PS C:\> New-SmbShare -Name Windows -Path C:\FSRVPTest -FullAccess '<Domain>\<Admin User Name>'
1. Ensure the test administrator has access to both the share and its underlying
directory, and allow SMB connections on TCP 445.
1. Configure the target address and administrator credentials in the remote
test configuration file.
+442
View File
@@ -0,0 +1,442 @@
# Impacket - Collection of Python classes for working with network protocols.
#
# Copyright Fortra, LLC and its affiliated companies
#
# All rights reserved.
#
# This software is provided under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
# Description:
# Implementation of MS-FSRVP: File server remote VSS Protocol.
#
# Authors:
# Andrei Solodiankin https://github.com/AndreySolod
#
from __future__ import division
from __future__ import print_function
import enum
from typing import Optional, Union
from impacket import nt_errors
from impacket.dcerpc.v5.dtypes import LONG, DWORD, WSTR, LPWSTR, ULONG, GUID, LONGLONG, BOOL
from impacket.dcerpc.v5.ndr import NDRCALL, NDRPOINTER, NDRSTRUCT, NULL, NDRUNION
from impacket.dcerpc.v5.rpcrt import DCERPCException, DCERPC_v5
from impacket.uuid import uuidtup_to_bin, string_to_bin
MSRPC_UUID_FSRVP = uuidtup_to_bin(('a8e0653c-2744-4389-a61d-7373df8b2292', '1.0'))
MSRPC_NAMED_PIPE_FSRVP = "\\pipe\\FssagentRpc"
FSRVP_ERROR_MESSAGES = {
0x80042301 : ("FSRVP_E_BAD_STATE", "A method call was invalid because of the state of the server. (For example, calling AddToShadowCopySet (Opnum 3) before StartShadowCopySet (Opnum 2).)"),
0x80042316 : ("FSRVP_E_SHADOW_COPY_SET_IN_PROGRESS", "A call was made to either SetContext (Opnum 1) or StartShadowCopySet (Opnum 2) while the creation of another shadow copy set is in progress."),
0x8004230C : ("FSRVP_E_NOT_SUPPORTED", "The file store which contains the share to be shadow copied is not supported by the server."),
0xFFFFFFFF : ("FSRVP_E_WAIT_FAILED", "The wait for a shadow copy commit expose operation has failed."),
0x8004230D : ("FSRVP_E_OBJECT_ALREADY_EXISTS", "The specified object already exists."),
0x80042308 : ("FSRVP_E_OBJECT_NOT_FOUND", "The specified object does not exist."),
0x8004231B : ("FSRVP_E_UNSUPPORTED_CONTEXT", "The specified context value is invalid"),
0x80042501 : ("FSRVP_E_SHADOWCOPYSET_ID_MISMATCH", "The provided ShadowCopySetId does not exist."),
0x80070005 : ("E_ACCESSDENIED", "The caller does not have the permissions to perform the operation"),
0x80070057 : ("E_INVALIDARG", "One or more arguments are invalid.")
}
class DCERPCSessionError(DCERPCException):
def __init__(self, error_string=None, error_code=None, packet=None):
DCERPCException.__init__(self, error_string, error_code, packet)
def __str__( self ):
key = self.error_code
if key in FSRVP_ERROR_MESSAGES:
error_msg_short = FSRVP_ERROR_MESSAGES[key][0]
error_msg_verbose = FSRVP_ERROR_MESSAGES[key][1]
return 'FSRVP SessionError: code: 0x%x - %s - %s' % (self.error_code, error_msg_short, error_msg_verbose)
if key in nt_errors.ERROR_MESSAGES:
error_msg_short = nt_errors.ERROR_MESSAGES[key][0]
error_msg_verbose = nt_errors.ERROR_MESSAGES[key][1]
return 'FSRVP SessionError: code: 0x%x - %s - %s' % (self.error_code, error_msg_short, error_msg_verbose)
else:
return 'FSRVP SessionError: unknown error code: 0x%x' % self.error_code
################################################################################
# CONSTANTS
################################################################################
class ShadowCopyAttributes(enum.IntEnum):
ATTR_PERSISTENT = 0x00000001
ATTR_NO_AUTO_RECOVERY = 0x00000002
ATTR_NO_AUTO_RELEASE = 0x00000008
ATTR_NO_WRITERS = 0x00000010
ATTR_AUTO_RECOVERY = 0x00400000
class ContextValues(enum.IntEnum):
CTX_BACKUP = 0x00000000
CTX_FILE_SHARE_BACKUP = 0x00000010
CTX_NAS_ROLLBACK = 0x00000019
CTX_APP_ROLLBACK = 0x00000009
class ShadowCopyCompatibilityValues(enum.IntEnum):
DISABLE_DEFRAG = 0x00000001
DISABLE_CONTENTINDEX = 0x00000002
################################################################################
# STRUCTURES
################################################################################
class FSSAGENT_SHARE_MAPPING_1(NDRSTRUCT):
structure = (
('ShadowCopySetId', GUID),
('ShadowCopyId', GUID),
('ShareNameUNC', LPWSTR),
('ShadowCopyShareName', LPWSTR),
('CreationTimestamp', LONGLONG),
)
class PFSSAGENT_SHARE_MAPPING_1(NDRPOINTER):
referent = (
('Data', FSSAGENT_SHARE_MAPPING_1),
)
class FSSAGENT_SHARE_MAPPING(NDRUNION):
commonHdr = (
('tag', DWORD),
)
union = {
1: ('ShareMapping1', PFSSAGENT_SHARE_MAPPING_1),
}
class PFSSAGENT_SHARE_MAPPING(NDRPOINTER):
referent = (
('Data', FSSAGENT_SHARE_MAPPING),
)
################################################################################
# RPC CALLS
################################################################################
class GetSupportedVersion(NDRCALL):
'''The GetSupportedVersion method is invoked by the client to get the minimum and maximum versions of the protocol that the server supports.'''
opnum = 0
structure = tuple()
class GetSupportedVersionResponse(NDRCALL):
structure = (
('MinVersion', DWORD),
('MaxVersion', DWORD),
('ErrorCode',DWORD),
)
class SetContext(NDRCALL):
'''The SetContext method sets the context for the current shadow copy creation process.'''
opnum = 1
structure = (
('Context', ULONG),
)
class SetContextResponse(NDRCALL):
structure = (
('ErrorCode', DWORD),
)
class StartShadowCopySet(NDRCALL):
'''The StartShadowCopySet method is called by the client to initiate a new shadow copy set for shadow copy creation'''
opnum = 2
structure = (
('ClientShadowCopySetId', GUID),
)
class StartShadowCopySetResponse(NDRCALL):
structure = (
('pShadowCopySetId', GUID),
('ErrorCode', DWORD)
)
class AddToShadowCopySet(NDRCALL):
'''The AddToShadowCopySet method adds a share to an existing shadow copy set.'''
opnum = 3
structure = (
('ClientShadowCopyId', GUID),
('ShadowCopySetId', GUID),
('ShareName', WSTR)
)
class AddToShadowCopySetResponse(NDRCALL):
structure = (
("ShadowCopyId", GUID),
("ErrorCode", DWORD)
)
class CommitShadowCopySet(NDRCALL):
'''The CommitShadowCopySet method is invoked by the client to commit a given shadow copy set.'''
opnum = 4
structure = (
('ShadowCopySetId', GUID),
('TimeOutInMilliseconds', ULONG)
)
class CommitShadowCopySetResponse(NDRCALL):
structure = (
("ErrorCode", DWORD),
)
class ExposeShadowCopySet(NDRCALL):
'''The ExposeShadowCopySet method exposes all the shadow copies in a shadow copy set as file shares on the file server.'''
opnum = 5
structure = (
("ShadowCopySetId", GUID),
('TimeOutInMilliseconds', ULONG)
)
class ExposeShadowCopySetResponse(NDRCALL):
structure = (
("ErrorCode", DWORD),
)
class RecoveryCompleteShadowCopySet(NDRCALL):
'''The RecoveryCompleteShadowCopySet method is invoked by the client to indicate to the server
that the data associated with the file shares in a shadow copy set have been recovered by the VSS
writers.
'''
opnum = 6
structure = (
("ShadowCopySetId", GUID),
)
class RecoveryCompleteShadowCopySetResponse(NDRCALL):
structure = (
("ErrorCode", DWORD),
)
class AbortShadowCopySet(NDRCALL):
'''The AbortShadowCopySet method is invoked by the client to delete a given shadow copy set on the server.'''
opnum = 7
structure = (
("ShadowCopySetId", GUID),
)
class AbortShadowCopySetResponse(NDRCALL):
structure = (
("ErrorCode", DWORD),
)
class IsPathSupported(NDRCALL):
'''The IsPathSupported method is invoked by the client to query if a given share is supported by the server for shadow copy operations.'''
opnum = 8
structure = (
("ShareName", WSTR),
)
class IsPathSupportedResponse(NDRCALL):
structure = (
('SupportedByThisProvider', BOOL),
('OwnerMachineName', LPWSTR),
('ErrorCode', DWORD)
)
class IsPathShadowCopied(NDRCALL):
'''The IsPathShadowCopied method is invoked by the client to query if any shadow copy for a share already exists.'''
opnum = 9
structure = (
("ShareName", WSTR),
)
class IsPathShadowCopiedResponse(NDRCALL):
structure = (
("ShadowCopyPresent", BOOL),
("ShadowCopyCompatibility", LONG),
('ErrorCode', DWORD)
)
class GetShareMapping(NDRCALL):
'''The GetShareMapping method is invoked by the client to get the shadow copy information on a
given file share on the server after the shadow copy of the share has been exposed.'''
opnum = 10
structure = (
("ShadowCopyId", GUID),
("ShadowCopySetId", GUID),
("ShareName", WSTR),
("Level", DWORD)
)
class GetShareMappingResponse(NDRCALL):
structure = (
('ShareMapping', FSSAGENT_SHARE_MAPPING),
('ErrorCode', DWORD),
)
class DeleteShareMapping(NDRCALL):
'''The DeleteShareMapping method deletes the mapping of a share's shadow copy from a shadow copy set. '''
opnum = 11
structure = (
('ShadowCopySetId', GUID),
('ShadowCopyId', GUID),
('ShareName', WSTR)
)
class DeleteShareMappingResponse(NDRCALL):
structure = (
('ErrorCode', DWORD),
)
class PrepareShadowCopySet(NDRCALL):
'''The PrepareShadowCopySet method is invoked by the client to ensure that the server has
completed preparation for creating the shadow copy set.'''
opnum = 12
structure = (
('ShadowCopySetId', GUID),
('TimeOutInMilliseconds', ULONG)
)
class PrepareShadowCopySetResponse(NDRCALL):
structure = (
('ErrorCode', DWORD),
)
################################################################################
# OPNUMs and their corresponding structures
################################################################################
OPNUMS = {
0 : (GetSupportedVersion, GetSupportedVersionResponse),
1 : (SetContext, SetContextResponse),
2 : (StartShadowCopySet, StartShadowCopySetResponse),
3 : (AddToShadowCopySet, AddToShadowCopySetResponse),
4 : (CommitShadowCopySet, CommitShadowCopySetResponse),
5 : (ExposeShadowCopySet, ExposeShadowCopySetResponse),
6 : (RecoveryCompleteShadowCopySet, RecoveryCompleteShadowCopySetResponse),
7 : (AbortShadowCopySet, AbortShadowCopySetResponse),
8 : (IsPathSupported, IsPathSupportedResponse),
9 : (IsPathShadowCopied, IsPathShadowCopiedResponse),
10 : (GetShareMapping, GetShareMappingResponse),
11 : (DeleteShareMapping, DeleteShareMappingResponse),
12 : (PrepareShadowCopySet, PrepareShadowCopySetResponse)
}
################################################################################
# HELPER FUNCTIONS
################################################################################
def checkNullString(string):
if string == NULL:
return string
if string is None:
return NULL
if string[-1:] != '\x00':
return string + '\x00'
else:
return string
def hGetSupportedVersion(dce: DCERPC_v5) -> GetSupportedVersionResponse:
gsv = GetSupportedVersion()
return dce.request(gsv)
def hSetContext(dce: DCERPC_v5, context: Union[ShadowCopyAttributes, int]) -> SetContextResponse:
set_context = SetContext()
ctx = context if isinstance(context, int) else context.value
set_context['Context'] = ctx
return dce.request(set_context)
def hStartShadowCopySet(dce: DCERPC_v5, clientShadowCopySetId: bytes) -> StartShadowCopySetResponse:
start_shadow_copy = StartShadowCopySet()
start_shadow_copy['ClientShadowCopySetId'] = clientShadowCopySetId
return dce.request(start_shadow_copy)
def hAddToShadowCopySet(dce: DCERPC_v5, ClientShadowCopyId: bytes, ShadowCopySetId: bytes, ShareName: str) -> AddToShadowCopySetResponse:
add_to_shadow_copy = AddToShadowCopySet()
add_to_shadow_copy['ClientShadowCopyId'] = ClientShadowCopyId
add_to_shadow_copy['ShadowCopySetId'] = ShadowCopySetId
add_to_shadow_copy['ShareName'] = checkNullString(ShareName)
return dce.request(add_to_shadow_copy)
def hCommitShadowCopySet(dce: DCERPC_v5, ShadowCopySetId: str, TimeOutInMilliseconds: int) -> CommitShadowCopySetResponse:
commit_shadow_copy = CommitShadowCopySet()
commit_shadow_copy['ShadowCopySetId'] = ShadowCopySetId
commit_shadow_copy['TimeOutInMilliseconds'] = TimeOutInMilliseconds
return dce.request(commit_shadow_copy)
def hExposeShadowCopySet(dce: DCERPC_v5, ShadowCopySetId: bytes, TimeOutInMilliseconds: int) -> ExposeShadowCopySetResponse:
expose_shadow_copy = ExposeShadowCopySet()
expose_shadow_copy['ShadowCopySetId'] = ShadowCopySetId
expose_shadow_copy['TimeOutInMilliseconds'] = TimeOutInMilliseconds
return dce.request(expose_shadow_copy)
def hRecoveryCompleteShadowCopySet(dce: DCERPC_v5, ShadowCopySetId: bytes) -> RecoveryCompleteShadowCopySetResponse:
recovery_complete = RecoveryCompleteShadowCopySet()
recovery_complete['ShadowCopySetId'] = ShadowCopySetId
return dce.request(recovery_complete)
def hAbortShadowCopySet(dce: DCERPC_v5, ShadowCopySetId: bytes) -> AbortShadowCopySetResponse:
request = AbortShadowCopySet()
request['ShadowCopySetId'] = ShadowCopySetId
return dce.request(request)
def hIsPathSupported(dce: DCERPC_v5, ShareName: Optional[str]) -> IsPathSupportedResponse:
request = IsPathSupported()
request['ShareName'] = checkNullString(ShareName)
return dce.request(request)
def hIsPathShadowCopied(dce: DCERPC_v5, ShareName: Optional[str]) -> IsPathShadowCopiedResponse:
request = IsPathShadowCopied()
request['ShareName'] = checkNullString(ShareName)
return dce.request(request)
def hGetShareMapping(dce: DCERPC_v5, ShadowCopyId: bytes, ShadowCopySetId: bytes, ShareName: str, Level: int=1) -> GetShareMappingResponse:
request = GetShareMapping()
request['ShadowCopyId'] = ShadowCopyId
request['ShadowCopySetId'] = ShadowCopySetId
request['ShareName'] = checkNullString(ShareName)
request['Level'] = Level
return dce.request(request)
def hDeleteShareMapping(dce: DCERPC_v5, ShadowCopySetId: bytes, ShadowCopyId: bytes, ShareName: str) -> DeleteShareMappingResponse:
request = DeleteShareMapping()
request['ShadowCopySetId'] = ShadowCopySetId
request['ShadowCopyId'] = ShadowCopyId
request['ShareName'] = checkNullString(ShareName)
return dce.request(request)
def hPrepareShadowCopySet(dce: DCERPC_v5, ShadowCopySetId: bytes, TimeOutInMilliseconds: int) -> PrepareShadowCopySetResponse:
request = PrepareShadowCopySet()
request['ShadowCopySetId'] = ShadowCopySetId
request['TimeOutInMilliseconds'] = TimeOutInMilliseconds
return dce.request(request)
+112 -2
View File
@@ -209,6 +209,41 @@ SERVICE_TRIGGER_DATA_TYPE_LEVEL = 0x00000003
SERVICE_TRIGGER_DATA_TYPE_KEYWORD_ANY = 0x00000004
SERVICE_TRIGGER_DATA_TYPE_KEYWORD_ALL = 0x00000005
# RCreateWowService dwServiceWowType dataTypes (https://learn.microsoft.com/ko-kr/openspecs/windows_protocols/ms-scmr/b0d95ad9-30f3-4df0-998d-d43fe8ea580c#Appendix_A_Target_84)
IMAGE_FILE_MACHINE_UNKNOWN = 0x0
IMAGE_FILE_MACHINE_TARGET_HOST = 0x0001
IMAGE_FILE_MACHINE_I386 = 0x014c
IMAGE_FILE_MACHINE_R3000BE = 0x0160
IMAGE_FILE_MACHINE_R3000 = 0x0162
IMAGE_FILE_MACHINE_R4000 = 0x0166
IMAGE_FILE_MACHINE_R10000 = 0x0168
IMAGE_FILE_MACHINE_WCEMIPSV2 = 0x0169
IMAGE_FILE_MACHINE_ALPHA = 0x0184
IMAGE_FILE_MACHINE_SH3 = 0x01a2
IMAGE_FILE_MACHINE_SH3DSP = 0x01a3
IMAGE_FILE_MACHINE_SH3E = 0x01a4
IMAGE_FILE_MACHINE_SH4 = 0x01a6
IMAGE_FILE_MACHINE_SH5 = 0x01a8
IMAGE_FILE_MACHINE_ARM = 0x01c0
IMAGE_FILE_MACHINE_THUMB = 0x01c2
IMAGE_FILE_MACHINE_ARMNT = 0x01c4
IMAGE_FILE_MACHINE_AM33 = 0x01d3
IMAGE_FILE_MACHINE_POWERPC = 0x01F0
IMAGE_FILE_MACHINE_POWERPCFP = 0x01f1
IMAGE_FILE_MACHINE_IA64 = 0x0200
IMAGE_FILE_MACHINE_MIPS16 = 0x0266
IMAGE_FILE_MACHINE_ALPHA64 = 0x0284
IMAGE_FILE_MACHINE_MIPSFPU = 0x0366
IMAGE_FILE_MACHINE_MIPSFPU16 = 0x0466
IMAGE_FILE_MACHINE_AXP64 = 0x0284
IMAGE_FILE_MACHINE_TRICORE = 0x0520
IMAGE_FILE_MACHINE_CEF = 0x0CEF
IMAGE_FILE_MACHINE_EBC = 0x0EBC
IMAGE_FILE_MACHINE_AMD64 = 0x8664
IMAGE_FILE_MACHINE_M32R = 0x9041
IMAGE_FILE_MACHINE_ARM64 = 0xAA64
IMAGE_FILE_MACHINE_CEE = 0xC0EE
################################################################################
# STRUCTURES
################################################################################
@@ -811,7 +846,7 @@ class RCreateServiceW(NDRCALL):
class RCreateServiceWResponse(NDRCALL):
structure = (
('lpdwTagId',LPWSTR),
('lpdwTagId',LPDWORD),
('lpServiceHandle',SC_RPC_HANDLE),
('ErrorCode', DWORD),
)
@@ -1055,7 +1090,7 @@ class RCreateServiceWOW64W(NDRCALL):
class RCreateServiceWOW64WResponse(NDRCALL):
structure = (
('lpdwTagId',LPWSTR),
('lpdwTagId',LPDWORD),
('lpServiceHandle',SC_RPC_HANDLE),
('ErrorCode', DWORD),
)
@@ -1133,6 +1168,49 @@ class RQueryServiceConfigExResponse(NDRCALL):
('ErrorCode', DWORD),
)
class RCreateWowService(NDRCALL):
opnum = 60
structure = (
('hSCManager',SC_RPC_HANDLE),
('lpServiceName',WSTR),
('lpDisplayName',LPWSTR),
('dwDesiredAccess',DWORD),
('dwServiceType',DWORD),
('dwStartType',DWORD),
('dwErrorControl',DWORD),
('lpBinaryPathName',WSTR),
('lpLoadOrderGroup',LPWSTR),
('lpdwTagId',LPDWORD),
('lpDependencies',LPBYTE),
('dwDependSize',DWORD),
('lpServiceStartName',LPWSTR),
('lpPassword',LPBYTE),
('dwPwSize',DWORD),
('dwServiceWowType',USHORT),
)
class RCreateWowServiceResponse(NDRCALL):
structure = (
('lpdwTagId',LPDWORD),
('lpServiceHandle',SC_RPC_HANDLE),
('ErrorCode', DWORD),
)
class ROpenSCManager2(NDRCALL):
opnum = 64
structure = (
('lpDatabaseName', LPWSTR),
('dwDesiredAccess', DWORD),
)
class ROpenSCManager2Response(NDRCALL):
structure = (
('lpScHandle', SC_RPC_HANDLE),
('ReturnCode', DWORD),
)
################################################################################
# OPNUMs and their corresponding structures
################################################################################
@@ -1169,6 +1247,8 @@ OPNUMS = {
49 : (RCloseNotifyHandle, RCloseNotifyHandleResponse),
51 : (RControlServiceExW, RControlServiceExWResponse),
56 : (RQueryServiceConfigEx, RQueryServiceConfigExResponse),
60 : (RCreateWowService, RCreateWowServiceResponse),
64 : (ROpenSCManager2, ROpenSCManager2Response),
}
################################################################################
@@ -1289,6 +1369,30 @@ def hRCreateServiceW(dce, hSCManager, lpServiceName, lpDisplayName, dwDesiredAcc
createService['dwPwSize'] = dwPwSize
return dce.request(createService)
def hRCreateWowServiceW(dce, hSCManager, lpServiceName, lpDisplayName, dwDesiredAccess=SERVICE_ALL_ACCESS,
dwServiceType=SERVICE_WIN32_OWN_PROCESS, dwStartType=SERVICE_AUTO_START, dwErrorControl=SERVICE_ERROR_IGNORE,
lpBinaryPathName=NULL, lpLoadOrderGroup=NULL, lpdwTagId=NULL, lpDependencies=NULL, dwDependSize=0, lpServiceStartName=NULL,
lpPassword=NULL, dwPwSize=0, dwServiceWowType: int=IMAGE_FILE_MACHINE_AMD64):
createService = RCreateWowService()
createService['hSCManager'] = hSCManager
createService['lpServiceName'] = checkNullString(lpServiceName)
createService['lpDisplayName'] = checkNullString(lpDisplayName)
createService['dwDesiredAccess'] = dwDesiredAccess
createService['dwServiceType'] = dwServiceType
createService['dwStartType'] = dwStartType
createService['dwErrorControl'] = dwErrorControl
createService['lpBinaryPathName'] = checkNullString(lpBinaryPathName)
createService['lpLoadOrderGroup'] = checkNullString(lpLoadOrderGroup)
createService['lpdwTagId'] = lpdwTagId
createService['lpDependencies'] = lpDependencies
createService['dwDependSize'] = dwDependSize
createService['lpServiceStartName'] = checkNullString(lpServiceStartName)
createService['lpPassword'] = lpPassword
createService['dwPwSize'] = dwPwSize
createService['dwServiceWowType'] = dwServiceWowType
return dce.request(createService)
def hREnumDependentServicesW(dce, hService, dwServiceState, cbBufSize ):
enumDependentServices = REnumDependentServicesW()
enumDependentServices['hService'] = hService
@@ -1361,6 +1465,12 @@ def hROpenSCManagerW(dce, lpMachineName='DUMMY\x00', lpDatabaseName='ServicesAct
openSCManager['dwDesiredAccess'] = dwDesiredAccess
return dce.request(openSCManager)
def hROpenSCManager2(dce, lpDatabaseName=NULL, dwDesiredAccess: int=SERVICE_START | SERVICE_STOP | SERVICE_CHANGE_CONFIG | SERVICE_QUERY_CONFIG | SERVICE_QUERY_STATUS | SERVICE_ENUMERATE_DEPENDENTS | SC_MANAGER_ENUMERATE_SERVICE):
openSCManager = ROpenSCManager2()
openSCManager['lpDatabaseName'] = checkNullString(lpDatabaseName)
openSCManager['dwDesiredAccess'] = dwDesiredAccess
return dce.request(openSCManager)
def hROpenServiceW(dce, hSCManager, lpServiceName, dwDesiredAccess= SERVICE_ALL_ACCESS):
openService = ROpenServiceW()
openService['hSCManager'] = hSCManager
+117
View File
@@ -0,0 +1,117 @@
# Impacket - Collection of Python classes for working with network protocols.
#
# Copyright Fortra, LLC and its affiliated companies
#
# All rights reserved.
#
# This software is provided under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
# Tested so far:
# GetSupportedVersion
# SetContext
# StartShadowCopySet
# AddToShadowCopySet
# CommitShadowCopySet
# ExposeShadowCopySet
# RecoveryCompleteShadowCopySet
# AbortShadowCopySet
# IsPathSupported
# IsPathShadowCopied
# GetShareMapping
# DeleteShareMapping
# PrepareShadowCopySet
#
# Prerequisites and run commands: TESTING.md, "FSRVP: file-server shadow copies".
# Set this to a regular SMB share on the configured target (not an administrative share).
SHARE_NAME = "Windows"
from tests.dcerpc import DCERPCTests
import pytest
from impacket.dcerpc.v5 import fsrvp, rpcrt
from impacket.uuid import string_to_bin
import uuid
import unittest
class FSRVPTests(DCERPCTests):
iface_uuid = fsrvp.MSRPC_UUID_FSRVP
authn = True
authn_level = rpcrt.RPC_C_AUTHN_LEVEL_PKT_PRIVACY
string_binding = r"ncacn_np:{0.machine}[%s]" % fsrvp.MSRPC_NAMED_PIPE_FSRVP
def test_GetSupportedVersion(self):
dce, transport = self.connect()
self.addCleanup(dce.disconnect)
request = fsrvp.GetSupportedVersion()
resp = dce.request(request)
resp.dump()
self.assertEqual(resp['MinVersion'], 1)
self.assertEqual(resp['MaxVersion'], 1)
def test_hGetSupportedVersion(self):
dce, transport = self.connect()
self.addCleanup(dce.disconnect)
resp = fsrvp.hGetSupportedVersion(dce)
resp.dump()
self.assertEqual(resp['MinVersion'], 1)
self.assertEqual(resp['MaxVersion'], 1)
def test_fsrvp(self):
global SHARE_NAME
# All test are passed in one function because almost all functions depend on each other and must be called sequentially.
dce, transport = self.connect()
# Shadow copy preparation
# According to the documentation https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fsrvp/e9f295cf-0f5c-4eec-8cfc-f2a031ae7805
is_path_supported = fsrvp.hIsPathSupported(dce, r"\\%s\%s" % (self.machine, SHARE_NAME))
self.assertEqual(is_path_supported["SupportedByThisProvider"], 1)
machine_name = is_path_supported["OwnerMachineName"][:-1:]
version_call = fsrvp.hGetSupportedVersion(dce) # Always MinVersion == MaxVersion == 1
self.assertEqual(version_call["MinVersion"], 1)
self.assertEqual(version_call["MaxVersion"], 1)
fsrvp.hSetContext(dce, fsrvp.ContextValues.CTX_FILE_SHARE_BACKUP)
client_set_id = string_to_bin(str(uuid.uuid4()))
start_shadow_copy_set = fsrvp.hStartShadowCopySet(dce, client_set_id)
shadow_copy_set_id = start_shadow_copy_set['pShadowCopySetId']
client_shadow_copy_id = string_to_bin(str(uuid.uuid4()))
add_to_shadow_copy = fsrvp.hAddToShadowCopySet(dce, client_shadow_copy_id, shadow_copy_set_id, f"\\\\{machine_name}\\{SHARE_NAME}")
shadow_copy_id = add_to_shadow_copy['ShadowCopyId']
prepare_shadow_copy = fsrvp.hPrepareShadowCopySet(dce, shadow_copy_set_id, 30000)
self.assertEqual(prepare_shadow_copy['ErrorCode'], 0) # Server ready to create a shadow copy
# Shadow copy creation
fsrvp.hCommitShadowCopySet(dce, shadow_copy_set_id, 30000)
fsrvp.hExposeShadowCopySet(dce, shadow_copy_set_id, 30000)
share_mapping = fsrvp.hGetShareMapping(dce, shadow_copy_id, shadow_copy_set_id, f'\\\\{machine_name}\\{SHARE_NAME}')
share_name = share_mapping['ShareMapping']['ShareMapping1']['ShadowCopyShareName'] # Server are exposed shadow copy of share to this share name
fsrvp.hRecoveryCompleteShadowCopySet(dce, shadow_copy_set_id)
# Check if path shadow copied:
is_path_shadow_copied = fsrvp.hIsPathShadowCopied(dce, f'\\\\{machine_name}\\{SHARE_NAME}')
self.assertEqual(is_path_shadow_copied["ShadowCopyPresent"], 1)
# Shadow copy deletion
delete_share_mapping = fsrvp.hDeleteShareMapping(dce, shadow_copy_set_id, shadow_copy_id, f'\\\\{machine_name}\\{SHARE_NAME}')
def test_abort_shadow_copy(self):
global SHARE_NAME
dce, transport = self.connect()
is_path_supported = fsrvp.hIsPathSupported(dce, r"\\%s\%s" % (self.machine, SHARE_NAME))
self.assertEqual(is_path_supported["SupportedByThisProvider"], 1)
machine_name = is_path_supported["OwnerMachineName"][:-1:]
version_call = fsrvp.hGetSupportedVersion(dce)
self.assertEqual(version_call["MinVersion"], 1)
self.assertEqual(version_call["MaxVersion"], 1)
fsrvp.hSetContext(dce, fsrvp.ContextValues.CTX_FILE_SHARE_BACKUP)
client_set_id = string_to_bin(str(uuid.uuid4()))
start_shadow_copy_set = fsrvp.hStartShadowCopySet(dce, client_set_id)
shadow_copy_set_id = start_shadow_copy_set['pShadowCopySetId']
fsrvp.hAbortShadowCopySet(dce, shadow_copy_set_id)
@pytest.mark.remote
class FSRVPTestsSMBTransport(FSRVPTests, unittest.TestCase):
transfer_syntax = DCERPCTests.TRANSFER_SYNTAX_NDR
# Process command-line arguments.
if __name__ == "__main__":
unittest.main(verbosity=1)
+91 -1
View File
@@ -10,6 +10,8 @@
#
# Tested so far:
# ROpenSCManagerW
# ROpenSCManager2
# RCreateWowService
# RControlService
# RDeleteService
# RLockServiceDatabase
@@ -45,13 +47,16 @@
# RSetServiceStatus
# RCreateServiceWOW64W
#
# Prerequisites for the new methods: TESTING.md, "SCMR: RCreateWowService and ROpenSCManager2".
#
import time
import pytest
import unittest
from uuid import uuid4
from struct import unpack
from tests.dcerpc import DCERPCTests
from impacket.dcerpc.v5 import scmr
from impacket.dcerpc.v5 import rpcrt, scmr
from impacket.dcerpc.v5.ndr import NULL
from impacket.crypto import encryptSecret
from impacket.uuid import string_to_bin
@@ -61,6 +66,91 @@ from impacket import ntlm
class SCMRTests(DCERPCTests):
iface_uuid = scmr.MSRPC_UUID_SCMR
authn = True
def call_optional_method(self, method_name, call, *args, **kwargs):
try:
return call(*args, **kwargs)
except rpcrt.DCERPCException as exc:
# recv() currently reports RPC faults by name without a numeric code.
unsupported_opnums = (0x1c010002, 0x16c9a001)
if (exc.get_error_code() in unsupported_opnums or
exc.error_string in ('nca_s_op_rng_error', 'rpc_s_op_rng_error') or
(isinstance(exc, scmr.DCERPCSessionError) and
exc.get_error_code() == 120)): # ERROR_CALL_NOT_IMPLEMENTED
self.skipTest('%s is not supported by the target' % method_name)
raise
def test_ROpenSCManager2(self):
dce, rpc_transport = self.connect()
self.addCleanup(dce.disconnect)
request = scmr.ROpenSCManager2()
request['lpDatabaseName'] = 'ServicesActive\x00'
request['dwDesiredAccess'] = scmr.SC_MANAGER_CONNECT
resp = self.call_optional_method('ROpenSCManager2', dce.request, request)
self.addCleanup(scmr.hRCloseServiceHandle, dce, resp['lpScHandle'])
resp.dump()
self.assertEqual(resp['ReturnCode'], 0)
def test_hROpenSCManager2(self):
dce, rpc_transport = self.connect()
self.addCleanup(dce.disconnect)
resp = self.call_optional_method('ROpenSCManager2', scmr.hROpenSCManager2,
dce, 'ServicesActive', scmr.SC_MANAGER_CONNECT)
self.addCleanup(scmr.hRCloseServiceHandle, dce, resp['lpScHandle'])
resp.dump()
self.assertEqual(resp['ReturnCode'], 0)
def create_wow_service_and_query(self, use_helper):
dce, rpc_transport = self.connect()
self.addCleanup(dce.disconnect)
manager = scmr.hROpenSCManagerW(
dce, dwDesiredAccess=scmr.SC_MANAGER_CREATE_SERVICE)['lpScHandle']
self.addCleanup(scmr.hRCloseServiceHandle, dce, manager)
service_name = 'ImpacketTest_' + uuid4().hex
display_name = service_name
# Only create a disabled record; no service binary is installed or run.
binary_path = r'C:\impacket-test-service.exe'
if use_helper:
resp = self.call_optional_method('RCreateWowService', scmr.hRCreateWowServiceW,
dce, manager, service_name, display_name,
dwStartType=scmr.SERVICE_DISABLED,
lpBinaryPathName=binary_path,
dwServiceWowType=scmr.IMAGE_FILE_MACHINE_I386)
else:
request = scmr.RCreateWowService()
request['hSCManager'] = manager
request['lpServiceName'] = service_name + '\x00'
request['lpDisplayName'] = display_name + '\x00'
request['dwDesiredAccess'] = scmr.SERVICE_ALL_ACCESS
request['dwServiceType'] = scmr.SERVICE_WIN32_OWN_PROCESS
request['dwStartType'] = scmr.SERVICE_DISABLED
request['dwErrorControl'] = scmr.SERVICE_ERROR_IGNORE
request['lpBinaryPathName'] = binary_path + '\x00'
request['lpLoadOrderGroup'] = NULL
request['lpdwTagId'] = NULL
request['lpDependencies'] = NULL
request['dwDependSize'] = 0
request['lpServiceStartName'] = NULL
request['lpPassword'] = NULL
request['dwPwSize'] = 0
request['dwServiceWowType'] = scmr.IMAGE_FILE_MACHINE_I386
resp = self.call_optional_method('RCreateWowService', dce.request, request)
service = resp['lpServiceHandle']
self.addCleanup(scmr.hRCloseServiceHandle, dce, service)
self.addCleanup(scmr.hRDeleteService, dce, service)
resp.dump()
self.assertEqual(resp['ErrorCode'], 0)
config = scmr.hRQueryServiceConfigW(dce, service)['lpServiceConfig']
self.assertEqual(config['lpDisplayName'], display_name + '\x00')
self.assertEqual(config['lpBinaryPathName'], binary_path + '\x00')
self.assertEqual(config['dwServiceType'], scmr.SERVICE_WIN32_OWN_PROCESS)
self.assertEqual(config['dwStartType'], scmr.SERVICE_DISABLED)
def test_RCreateWowService(self):
self.create_wow_service_and_query(use_helper=False)
def test_hRCreateWowServiceW(self):
self.create_wow_service_and_query(use_helper=True)
def get_service_handle(self, dce):
lpMachineName = 'DUMMY\x00'