Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions impacket/dcerpc/v5/scmr.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,15 +339,16 @@ class SC_ACTION(NDRSTRUCT):
('Delay', DWORD) ,
)

class SC_ACTIONS(NDRSTRUCT):
structure = (
('Data', NDRUniConformantArray),
# https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-scmr/58032b71-1e5c-4f2e-8545-34b0f2e8c6ad
# https://learn.microsoft.com/en-us/windows/win32/api/winsvc/ns-winsvc-sc_action
class SC_ACTION_ARRAY(NDRUniConformantArray):
item = SC_ACTION

# Helper for the [size_is(cActions)] SC_ACTION* field.
class SC_ACTIONS(NDRPOINTER):
referent = (
('Data', SC_ACTION_ARRAY),
)
def __init__(self, data = None, isNDR64 = False):
NDR.__init__(self,None,isNDR64)
self.fields['Data'].item = SC_ACTION
if data is not None:
self.fromString(data)

class SERVICE_FAILURE_ACTIONSW(NDRSTRUCT):
structure = (
Expand All @@ -358,6 +359,14 @@ class SERVICE_FAILURE_ACTIONSW(NDRSTRUCT):
('lpsaActions', SC_ACTIONS) ,
)

# Keep cActions synchronized with the pointed SC_ACTION array at marshal time.
def getData(self, soFar=0):
if self['lpsaActions'] == 0:
self['cActions'] = 0
else:
self['cActions'] = len(self['lpsaActions'])
return NDR.getData(self, soFar)

class LPSERVICE_FAILURE_ACTIONSW(NDRPOINTER):
referent = (
('Data', SERVICE_FAILURE_ACTIONSW),
Expand Down
83 changes: 67 additions & 16 deletions tests/dcerpc/test_scmr.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,22 +104,7 @@ def changeServiceAndQuery(self, dce, cbBufSize, hService, dwServiceType, dwStart
def changeServiceAndQuery2(self, dce, info, changeDone):
serviceHandle = info['hService']
dwInfoLevel = info['Info']['Union']['tag']
cbBuffSize = 0
request = scmr.RQueryServiceConfig2W()
request['hService'] = serviceHandle
request['dwInfoLevel'] = dwInfoLevel
request['cbBufSize'] = cbBuffSize
try:
resp = dce.request(request)
except scmr.DCERPCSessionError as e:
if str(e).find('ERROR_INSUFFICIENT_BUFFER') <= 0:
raise
else:
resp = e.get_packet()

request['cbBufSize'] = resp['pcbBytesNeeded']
resp = dce.request(request)
arrayData = b''.join(resp['lpBuffer'])
arrayData = self.query_service_config2(dce, serviceHandle, dwInfoLevel)
if dwInfoLevel == 1:
self.assertEqual(arrayData[4:].decode('utf-16le'), changeDone)
elif dwInfoLevel == 2:
Expand All @@ -137,6 +122,38 @@ def changeServiceAndQuery2(self, dce, info, changeDone):
elif dwInfoLevel == 7:
self.assertEqual(unpack('<L', arrayData)[0], changeDone)

def query_service_config2(self, dce, serviceHandle, dwInfoLevel):
cbBuffSize = 0
request = scmr.RQueryServiceConfig2W()
request['hService'] = serviceHandle
request['dwInfoLevel'] = dwInfoLevel
request['cbBufSize'] = cbBuffSize
try:
resp = dce.request(request)
except scmr.DCERPCSessionError as e:
if str(e).find('ERROR_INSUFFICIENT_BUFFER') <= 0:
raise
else:
resp = e.get_packet()

request['cbBufSize'] = resp['pcbBytesNeeded']
resp = dce.request(request)
return b''.join(resp['lpBuffer'])

def query_failure_actions(self, dce, serviceHandle):
arrayData = self.query_service_config2(dce, serviceHandle, 2)
_, reboot_offset, _, cActions, actions_offset = unpack('<LLLLL', arrayData[:20])
actions = []
for index in range(cActions):
start = actions_offset + (index * 8)
actions.append(unpack('<LL', arrayData[start:start + 8]))

return {
'rebootMsg': arrayData[reboot_offset:].decode('utf-16le').split('\x00', 1)[0] + '\x00',
'cActions': cActions,
'actions': actions,
}

def open_or_create_service(self, dce, scHandle, service_name, display_name, binary_path_name):

try:
Expand Down Expand Up @@ -257,6 +274,40 @@ def test_RChangeServiceConfig2W(self):
scmr.hRCloseServiceHandle(dce, scHandle)
if error:
self.fail()

def test_RChangeServiceConfig2W_failure_actions(self):
dce, rpc_transport = self.connect()
scHandle = self.get_service_handle(dce)
newHandle = self.open_or_create_service(dce, scHandle, 'TESTSVC\x00', 'DisplayName\x00', 'binaryPath\x00')
try:
request = scmr.RChangeServiceConfig2W()
request['hService'] = newHandle
request['Info']['dwInfoLevel'] = 2
request['Info']['Union']['tag'] = 2
request['Info']['Union']['psfa']['lpRebootMsg'] = 'rebootMsg\00'
request['Info']['Union']['psfa']['lpCommand'] = 'lpCommand\00'

action = scmr.SC_ACTION()
action['Type'] = scmr.SC_ACTION_RUN_COMMAND
action['Delay'] = 60000
actions = scmr.SC_ACTIONS()
actions['Data'].append(action)

# Intentionally set an incorrect cActions value to exercise
# automatic synchronization with the pointed actions array.
request['Info']['Union']['psfa']['cActions'] = 0
request['Info']['Union']['psfa']['lpsaActions'] = actions
resp = dce.request(request)
resp.dump()

self.changeServiceAndQuery2(dce, request, request['Info']['Union']['psfa']['lpRebootMsg'])
failure_actions = self.query_failure_actions(dce, newHandle)
self.assertEqual(failure_actions['cActions'], 1)
self.assertEqual(failure_actions['actions'], [(scmr.SC_ACTION_RUN_COMMAND, 60000)])
finally:
scmr.hRDeleteService(dce, newHandle)
scmr.hRCloseServiceHandle(dce, newHandle)
scmr.hRCloseServiceHandle(dce, scHandle)

def test_REnumServicesStatusExW(self):
dce, rpc_transport = self.connect()
Expand Down
Loading