1#!/usr/bin/env python
2# Copyright (c) 2003-2016 CORE Security Technologies
3#
4# This software is provided under under a slightly modified version
5# of the Apache Software License. See the accompanying LICENSE file
6# for more information.
7#
8# [MS-SCMR] services common functions for manipulating services
9#
10# Author:
11#  Alberto Solino (@agsolino)
12#
13# Reference for:
14#  DCE/RPC.
15# TODO:
16# [ ] Check errors
17
18import sys
19import argparse
20import logging
21import codecs
22
23from impacket.examples import logger
24from impacket import version
25from impacket.dcerpc.v5 import transport, scmr
26from impacket.dcerpc.v5.ndr import NULL
27from impacket.crypto import *
28
29
30class SVCCTL:
31
32    def __init__(self, username, password, domain, options, port=445):
33        self.__username = username
34        self.__password = password
35        self.__options = options
36        self.__port = port
37        self.__action = options.action.upper()
38        self.__domain = domain
39        self.__lmhash = ''
40        self.__nthash = ''
41        self.__aesKey = options.aesKey
42        self.__doKerberos = options.k
43        self.__kdcHost = options.dc_ip
44
45        if options.hashes is not None:
46            self.__lmhash, self.__nthash = options.hashes.split(':')
47
48    def run(self, remoteName, remoteHost):
49
50        stringbinding = 'ncacn_np:%s[\pipe\svcctl]' % remoteName
51        logging.debug('StringBinding %s'%stringbinding)
52        rpctransport = transport.DCERPCTransportFactory(stringbinding)
53        rpctransport.set_dport(self.__port)
54        rpctransport.setRemoteHost(remoteHost)
55        if hasattr(rpctransport, 'set_credentials'):
56            # This method exists only for selected protocol sequences.
57            rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey)
58
59        rpctransport.set_kerberos(self.__doKerberos, self.__kdcHost)
60        self.doStuff(rpctransport)
61
62    def doStuff(self, rpctransport):
63        dce = rpctransport.get_dce_rpc()
64        #dce.set_credentials(self.__username, self.__password)
65        dce.connect()
66        #dce.set_max_fragment_size(1)
67        #dce.set_auth_level(ntlm.NTLM_AUTH_PKT_PRIVACY)
68        #dce.set_auth_level(ntlm.NTLM_AUTH_PKT_INTEGRITY)
69        dce.bind(scmr.MSRPC_UUID_SCMR)
70        #rpc = svcctl.DCERPCSvcCtl(dce)
71        rpc = dce
72        ans = scmr.hROpenSCManagerW(rpc)
73        scManagerHandle = ans['lpScHandle']
74        if self.__action != 'LIST' and self.__action != 'CREATE':
75            ans = scmr.hROpenServiceW(rpc, scManagerHandle, self.__options.name+'\x00')
76            serviceHandle = ans['lpServiceHandle']
77
78        if self.__action == 'START':
79            logging.info("Starting service %s" % self.__options.name)
80            scmr.hRStartServiceW(rpc, serviceHandle)
81            scmr.hRCloseServiceHandle(rpc, serviceHandle)
82        elif self.__action == 'STOP':
83            logging.info("Stopping service %s" % self.__options.name)
84            scmr.hRControlService(rpc, serviceHandle, scmr.SERVICE_CONTROL_STOP)
85            scmr.hRCloseServiceHandle(rpc, serviceHandle)
86        elif self.__action == 'DELETE':
87            logging.info("Deleting service %s" % self.__options.name)
88            scmr.hRDeleteService(rpc, serviceHandle)
89            scmr.hRCloseServiceHandle(rpc, serviceHandle)
90        elif self.__action == 'CONFIG':
91            logging.info("Querying service config for %s" % self.__options.name)
92            resp = scmr.hRQueryServiceConfigW(rpc, serviceHandle)
93            print "TYPE              : %2d - " % resp['lpServiceConfig']['dwServiceType'],
94            if resp['lpServiceConfig']['dwServiceType'] & 0x1:
95                print "SERVICE_KERNEL_DRIVER ",
96            if resp['lpServiceConfig']['dwServiceType'] & 0x2:
97                print "SERVICE_FILE_SYSTEM_DRIVER ",
98            if resp['lpServiceConfig']['dwServiceType'] & 0x10:
99                print "SERVICE_WIN32_OWN_PROCESS ",
100            if resp['lpServiceConfig']['dwServiceType'] & 0x20:
101                print "SERVICE_WIN32_SHARE_PROCESS ",
102            if resp['lpServiceConfig']['dwServiceType'] & 0x100:
103                print "SERVICE_INTERACTIVE_PROCESS ",
104            print ""
105            print "START_TYPE        : %2d - " % resp['lpServiceConfig']['dwStartType'],
106            if resp['lpServiceConfig']['dwStartType'] == 0x0:
107                print "BOOT START"
108            elif resp['lpServiceConfig']['dwStartType'] == 0x1:
109                print "SYSTEM START"
110            elif resp['lpServiceConfig']['dwStartType'] == 0x2:
111                print "AUTO START"
112            elif resp['lpServiceConfig']['dwStartType'] == 0x3:
113                print "DEMAND START"
114            elif resp['lpServiceConfig']['dwStartType'] == 0x4:
115                print "DISABLED"
116            else:
117                print "UNKNOWN"
118
119            print "ERROR_CONTROL     : %2d - " % resp['lpServiceConfig']['dwErrorControl'],
120            if resp['lpServiceConfig']['dwErrorControl'] == 0x0:
121                print "IGNORE"
122            elif resp['lpServiceConfig']['dwErrorControl'] == 0x1:
123                print "NORMAL"
124            elif resp['lpServiceConfig']['dwErrorControl'] == 0x2:
125                print "SEVERE"
126            elif resp['lpServiceConfig']['dwErrorControl'] == 0x3:
127                print "CRITICAL"
128            else:
129                print "UNKNOWN"
130            print "BINARY_PATH_NAME  : %s" % resp['lpServiceConfig']['lpBinaryPathName'][:-1]
131            print "LOAD_ORDER_GROUP  : %s" % resp['lpServiceConfig']['lpLoadOrderGroup'][:-1]
132            print "TAG               : %d" % resp['lpServiceConfig']['dwTagId']
133            print "DISPLAY_NAME      : %s" % resp['lpServiceConfig']['lpDisplayName'][:-1]
134            print "DEPENDENCIES      : %s" % resp['lpServiceConfig']['lpDependencies'][:-1]
135            print "SERVICE_START_NAME: %s" % resp['lpServiceConfig']['lpServiceStartName'][:-1]
136        elif self.__action == 'STATUS':
137            print "Querying status for %s" % self.__options.name
138            resp = scmr.hRQueryServiceStatus(rpc, serviceHandle)
139            print "%30s - " % self.__options.name,
140            state = resp['lpServiceStatus']['dwCurrentState']
141            if state == scmr.SERVICE_CONTINUE_PENDING:
142               print "CONTINUE PENDING"
143            elif state == scmr.SERVICE_PAUSE_PENDING:
144               print "PAUSE PENDING"
145            elif state == scmr.SERVICE_PAUSED:
146               print "PAUSED"
147            elif state == scmr.SERVICE_RUNNING:
148               print "RUNNING"
149            elif state == scmr.SERVICE_START_PENDING:
150               print "START PENDING"
151            elif state == scmr.SERVICE_STOP_PENDING:
152               print "STOP PENDING"
153            elif state == scmr.SERVICE_STOPPED:
154               print "STOPPED"
155            else:
156               print "UNKNOWN"
157        elif self.__action == 'LIST':
158            logging.info("Listing services available on target")
159            #resp = rpc.EnumServicesStatusW(scManagerHandle, svcctl.SERVICE_WIN32_SHARE_PROCESS )
160            #resp = rpc.EnumServicesStatusW(scManagerHandle, svcctl.SERVICE_WIN32_OWN_PROCESS )
161            #resp = rpc.EnumServicesStatusW(scManagerHandle, serviceType = svcctl.SERVICE_FILE_SYSTEM_DRIVER, serviceState = svcctl.SERVICE_STATE_ALL )
162            resp = scmr.hREnumServicesStatusW(rpc, scManagerHandle)
163            for i in range(len(resp)):
164                print "%30s - %70s - " % (resp[i]['lpServiceName'][:-1], resp[i]['lpDisplayName'][:-1]),
165                state = resp[i]['ServiceStatus']['dwCurrentState']
166                if state == scmr.SERVICE_CONTINUE_PENDING:
167                   print "CONTINUE PENDING"
168                elif state == scmr.SERVICE_PAUSE_PENDING:
169                   print "PAUSE PENDING"
170                elif state == scmr.SERVICE_PAUSED:
171                   print "PAUSED"
172                elif state == scmr.SERVICE_RUNNING:
173                   print "RUNNING"
174                elif state == scmr.SERVICE_START_PENDING:
175                   print "START PENDING"
176                elif state == scmr.SERVICE_STOP_PENDING:
177                   print "STOP PENDING"
178                elif state == scmr.SERVICE_STOPPED:
179                   print "STOPPED"
180                else:
181                   print "UNKNOWN"
182            print "Total Services: %d" % len(resp)
183        elif self.__action == 'CREATE':
184            logging.info("Creating service %s" % self.__options.name)
185            scmr.hRCreateServiceW(rpc, scManagerHandle, self.__options.name + '\x00', self.__options.display + '\x00',
186                                  lpBinaryPathName=self.__options.path + '\x00')
187        elif self.__action == 'CHANGE':
188            logging.info("Changing service config for %s" % self.__options.name)
189            if self.__options.start_type is not None:
190                start_type = int(self.__options.start_type)
191            else:
192                start_type = scmr.SERVICE_NO_CHANGE
193            if self.__options.service_type is not None:
194                service_type = int(self.__options.service_type)
195            else:
196                service_type = scmr.SERVICE_NO_CHANGE
197
198            if self.__options.display is not None:
199                display = self.__options.display + '\x00'
200            else:
201                display = NULL
202
203            if self.__options.path is not None:
204                path = self.__options.path + '\x00'
205            else:
206                path = NULL
207
208            if self.__options.start_name is not None:
209                start_name = self.__options.start_name + '\x00'
210            else:
211                start_name = NULL
212
213            if self.__options.password is not None:
214                s = rpctransport.get_smb_connection()
215                key = s.getSessionKey()
216                try:
217                    password = (self.__options.password+'\x00').encode('utf-16le')
218                except UnicodeDecodeError:
219                    import sys
220                    password = (self.__options.password+'\x00').decode(sys.getfilesystemencoding()).encode('utf-16le')
221                password = encryptSecret(key, password)
222            else:
223                password = NULL
224
225
226            #resp = scmr.hRChangeServiceConfigW(rpc, serviceHandle,  display, path, service_type, start_type, start_name, password)
227            scmr.hRChangeServiceConfigW(rpc, serviceHandle, service_type, start_type, scmr.SERVICE_ERROR_IGNORE, path,
228                                        NULL, NULL, NULL, 0, start_name, password, 0, display)
229            scmr.hRCloseServiceHandle(rpc, serviceHandle)
230        else:
231            logging.error("Unknown action %s" % self.__action)
232
233        scmr.hRCloseServiceHandle(rpc, scManagerHandle)
234
235        dce.disconnect()
236
237        return
238
239
240# Process command-line arguments.
241if __name__ == '__main__':
242
243    # Init the example's logger theme
244    logger.init()
245    # Explicitly changing the stdout encoding format
246    if sys.stdout.encoding is None:
247        # Output is redirected to a file
248        sys.stdout = codecs.getwriter('utf8')(sys.stdout)
249    print version.BANNER
250
251    parser = argparse.ArgumentParser(add_help = True, description = "Windows Service manipulation script.")
252
253    parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>')
254    parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
255    subparsers = parser.add_subparsers(help='actions', dest='action')
256
257    # A start command
258    start_parser = subparsers.add_parser('start', help='starts the service')
259    start_parser.add_argument('-name', action='store', required=True, help='service name')
260
261    # A stop command
262    stop_parser = subparsers.add_parser('stop', help='stops the service')
263    stop_parser.add_argument('-name', action='store', required=True, help='service name')
264
265    # A delete command
266    delete_parser = subparsers.add_parser('delete', help='deletes the service')
267    delete_parser.add_argument('-name', action='store', required=True, help='service name')
268
269    # A status command
270    status_parser = subparsers.add_parser('status', help='returns service status')
271    status_parser.add_argument('-name', action='store', required=True, help='service name')
272
273    # A config command
274    config_parser = subparsers.add_parser('config', help='returns service configuration')
275    config_parser.add_argument('-name', action='store', required=True, help='service name')
276
277    # A list command
278    list_parser = subparsers.add_parser('list', help='list available services')
279
280    # A create command
281    create_parser = subparsers.add_parser('create', help='create a service')
282    create_parser.add_argument('-name', action='store', required=True, help='service name')
283    create_parser.add_argument('-display', action='store', required=True, help='display name')
284    create_parser.add_argument('-path', action='store', required=True, help='binary path')
285
286    # A change command
287    create_parser = subparsers.add_parser('change', help='change a service configuration')
288    create_parser.add_argument('-name', action='store', required=True, help='service name')
289    create_parser.add_argument('-display', action='store', required=False, help='display name')
290    create_parser.add_argument('-path', action='store', required=False, help='binary path')
291    create_parser.add_argument('-service_type', action='store', required=False, help='service type')
292    create_parser.add_argument('-start_type', action='store', required=False, help='service start type')
293    create_parser.add_argument('-start_name', action='store', required=False, help='string that specifies the name of '
294                               'the account under which the service should run')
295    create_parser.add_argument('-password', action='store', required=False, help='string that contains the password of '
296                               'the account whose name was specified by the start_name parameter')
297
298    group = parser.add_argument_group('authentication')
299
300    group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH')
301    group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)')
302    group.add_argument('-k', action="store_true", help='Use Kerberos authentication. Grabs credentials from ccache file '
303                       '(KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use the '
304                       'ones specified in the command line')
305    group.add_argument('-aesKey', action="store", metavar = "hex key", help='AES key to use for Kerberos Authentication '
306                                                                            '(128 or 256 bits)')
307
308    group = parser.add_argument_group('connection')
309
310    group.add_argument('-dc-ip', action='store',metavar = "ip address", help='IP Address of the domain controller. If '
311                       'ommited it use the domain part (FQDN) specified in the target parameter')
312    group.add_argument('-target-ip', action='store', metavar="ip address", help='IP Address of the target machine. If '
313                       'ommited it will use whatever was specified as target. This is useful when target is the NetBIOS '
314                       'name and you cannot resolve it')
315    group.add_argument('-port', choices=['139', '445'], nargs='?', default='445', metavar="destination port",
316                       help='Destination port to connect to SMB Server')
317
318    if len(sys.argv)==1:
319        parser.print_help()
320        sys.exit(1)
321
322    options = parser.parse_args()
323
324    if options.debug is True:
325        logging.getLogger().setLevel(logging.DEBUG)
326    else:
327        logging.getLogger().setLevel(logging.INFO)
328
329    import re
330
331    domain, username, password, remoteName = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(
332        options.target).groups('')
333
334    #In case the password contains '@'
335    if '@' in remoteName:
336        password = password + '@' + remoteName.rpartition('@')[0]
337        remoteName = remoteName.rpartition('@')[2]
338
339    if domain is None:
340        domain = ''
341
342    if options.target_ip is None:
343        options.target_ip = remoteName
344
345    if options.aesKey is not None:
346        options.k = True
347
348    if password == '' and username != '' and options.hashes is None and options.no_pass is False and options.aesKey is None:
349        from getpass import getpass
350        password = getpass("Password:")
351
352    services = SVCCTL(username, password, domain, options, int(options.port))
353    try:
354        services.run(remoteName, options.target_ip)
355    except Exception, e:
356        if logging.getLogger().level == logging.DEBUG:
357            import traceback
358            traceback.print_exc()
359        logging.error(str(e))
360