1#!/usr/bin/env python
2# Copyright (c) 2003-2016 CORE Security Technologies
3#
4# This software is provided under a slightly modified version
5# of the Apache Software License. See the accompanying LICENSE file
6# for more information.
7#
8# Description: Remote registry manipulation tool.
9#              The idea is to provide similar functionality as the REG.EXE Windows utility.
10#
11# e.g:
12#    ./reg.py Administrator:password@targetMachine query -keyName HKLM\\Software\\Microsoft\\WBEM -s
13#
14# Author:
15#  Manuel Porto (@manuporto)
16#  Alberto Solino (@agsolino)
17#
18# Reference for: [MS-RRP]
19#
20import argparse
21import codecs
22import logging
23import sys
24import time
25from struct import unpack
26
27from impacket import version
28from impacket.dcerpc.v5 import transport, rrp, scmr, rpcrt
29from impacket.examples import logger
30from impacket.system_errors import ERROR_NO_MORE_ITEMS
31from impacket.winregistry import hexdump
32from impacket.smbconnection import SMBConnection
33
34
35class RemoteOperations:
36    def __init__(self, smbConnection, doKerberos, kdcHost=None):
37        self.__smbConnection = smbConnection
38        self.__smbConnection.setTimeout(5 * 60)
39        self.__serviceName = 'RemoteRegistry'
40        self.__stringBindingWinReg = r'ncacn_np:445[\pipe\winreg]'
41        self.__rrp = None
42        self.__regHandle = None
43
44        self.__doKerberos = doKerberos
45        self.__kdcHost = kdcHost
46
47        self.__disabled = False
48        self.__shouldStop = False
49        self.__started = False
50
51        self.__stringBindingSvcCtl = r'ncacn_np:445[\pipe\svcctl]'
52        self.__scmr = None
53
54    def getRRP(self):
55        return self.__rrp
56
57    def __connectSvcCtl(self):
58        rpc = transport.DCERPCTransportFactory(self.__stringBindingSvcCtl)
59        rpc.set_smb_connection(self.__smbConnection)
60        self.__scmr = rpc.get_dce_rpc()
61        self.__scmr.connect()
62        self.__scmr.bind(scmr.MSRPC_UUID_SCMR)
63
64    def connectWinReg(self):
65        rpc = transport.DCERPCTransportFactory(self.__stringBindingWinReg)
66        rpc.set_smb_connection(self.__smbConnection)
67        self.__rrp = rpc.get_dce_rpc()
68        self.__rrp.connect()
69        self.__rrp.bind(rrp.MSRPC_UUID_RRP)
70
71    def __checkServiceStatus(self):
72        # Open SC Manager
73        ans = scmr.hROpenSCManagerW(self.__scmr)
74        self.__scManagerHandle = ans['lpScHandle']
75        # Now let's open the service
76        ans = scmr.hROpenServiceW(self.__scmr, self.__scManagerHandle, self.__serviceName)
77        self.__serviceHandle = ans['lpServiceHandle']
78        # Let's check its status
79        ans = scmr.hRQueryServiceStatus(self.__scmr, self.__serviceHandle)
80        if ans['lpServiceStatus']['dwCurrentState'] == scmr.SERVICE_STOPPED:
81            logging.info('Service %s is in stopped state' % self.__serviceName)
82            self.__shouldStop = True
83            self.__started = False
84        elif ans['lpServiceStatus']['dwCurrentState'] == scmr.SERVICE_RUNNING:
85            logging.debug('Service %s is already running' % self.__serviceName)
86            self.__shouldStop = False
87            self.__started = True
88        else:
89            raise Exception('Unknown service state 0x%x - Aborting' % ans['CurrentState'])
90
91        # Let's check its configuration if service is stopped, maybe it's disabled :s
92        if self.__started is False:
93            ans = scmr.hRQueryServiceConfigW(self.__scmr, self.__serviceHandle)
94            if ans['lpServiceConfig']['dwStartType'] == 0x4:
95                logging.info('Service %s is disabled, enabling it' % self.__serviceName)
96                self.__disabled = True
97                scmr.hRChangeServiceConfigW(self.__scmr, self.__serviceHandle, dwStartType=0x3)
98            logging.info('Starting service %s' % self.__serviceName)
99            scmr.hRStartServiceW(self.__scmr, self.__serviceHandle)
100            time.sleep(1)
101
102    def enableRegistry(self):
103        self.__connectSvcCtl()
104        self.__checkServiceStatus()
105        self.connectWinReg()
106
107    def __restore(self):
108        # First of all stop the service if it was originally stopped
109        if self.__shouldStop is True:
110            logging.info('Stopping service %s' % self.__serviceName)
111            scmr.hRControlService(self.__scmr, self.__serviceHandle, scmr.SERVICE_CONTROL_STOP)
112        if self.__disabled is True:
113            logging.info('Restoring the disabled state for service %s' % self.__serviceName)
114            scmr.hRChangeServiceConfigW(self.__scmr, self.__serviceHandle, dwStartType=0x4)
115
116    def finish(self):
117        self.__restore()
118        if self.__rrp is not None:
119            self.__rrp.disconnect()
120        if self.__scmr is not None:
121            self.__scmr.disconnect()
122
123
124class RegHandler:
125    def __init__(self, username, password, domain, options):
126        self.__username = username
127        self.__password = password
128        self.__domain = domain
129        self.__options = options
130        self.__action = options.action.upper()
131        self.__lmhash = ''
132        self.__nthash = ''
133        self.__aesKey = options.aesKey
134        self.__doKerberos = options.k
135        self.__kdcHost = options.dc_ip
136        self.__smbConnection = None
137        self.__remoteOps = None
138
139        # It's possible that this is defined somewhere, but I couldn't find where
140        self.__regValues = {0: 'REG_NONE', 1: 'REG_SZ', 2: 'REG_EXPAND_SZ', 3: 'REG_BINARY', 4: 'REG_DWORD',
141                            5: 'REG_DWORD_BIG_ENDIAN', 6: 'REG_LINK', 7: 'REG_MULTI_SZ', 11: 'REG_QWORD'}
142
143        if options.hashes is not None:
144            self.__lmhash, self.__nthash = options.hashes.split(':')
145
146    def connect(self, remoteName, remoteHost):
147        self.__smbConnection = SMBConnection(remoteName, remoteHost, sess_port=int(self.__options.port))
148
149        if self.__doKerberos:
150            self.__smbConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash,
151                                               self.__nthash, self.__aesKey, self.__kdcHost)
152        else:
153            self.__smbConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
154
155    def run(self, remoteName, remoteHost):
156        self.connect(remoteName, remoteHost)
157        self.__remoteOps = RemoteOperations(self.__smbConnection, self.__doKerberos, self.__kdcHost)
158
159        try:
160            self.__remoteOps.enableRegistry()
161        except Exception, e:
162            logging.debug(str(e))
163            logging.warning('Cannot check RemoteRegistry status. Hoping it is started...')
164            self.__remoteOps.connectWinReg()
165
166        try:
167            dce = self.__remoteOps.getRRP()
168
169            if self.__action == 'QUERY':
170                self.query(dce, self.__options.keyName)
171            else:
172                logging.error('Method %s not implemented yet!' % self.__action)
173        except (Exception, KeyboardInterrupt), e:
174            # import traceback
175            # traceback.print_exc()
176            logging.critical(str(e))
177        finally:
178            if self.__remoteOps:
179                self.__remoteOps.finish()
180
181    def query(self, dce, keyName):
182        # Let's strip the root key
183        try:
184            rootKey = keyName.split('\\')[0]
185            subKey = '\\'.join(keyName.split('\\')[1:])
186        except Exception:
187            raise Exception('Error parsing keyName %s' % keyName)
188
189        if rootKey.upper() == 'HKLM':
190            ans = rrp.hOpenLocalMachine(dce)
191        elif rootKey.upper() == 'HKU':
192            ans = rrp.hOpenCurrentUser(dce)
193        elif rootKey.upper() == 'HKCR':
194            ans = rrp.hOpenClassesRoot(dce)
195        else:
196            raise Exception('Invalid root key %s ' % rootKey)
197
198        hRootKey = ans['phKey']
199
200        ans2 = rrp.hBaseRegOpenKey(dce, hRootKey, subKey,
201                                   samDesired=rrp.MAXIMUM_ALLOWED | rrp.KEY_ENUMERATE_SUB_KEYS | rrp.KEY_QUERY_VALUE)
202
203        if self.__options.v:
204            print keyName
205            value = rrp.hBaseRegQueryValue(dce, ans2['phkResult'], self.__options.v)
206            print '\t' + self.__options.v + '\t' + self.__regValues.get(value[0], 'KEY_NOT_FOUND') + '\t', str(value[1])
207        elif self.__options.ve:
208            print keyName
209            value = rrp.hBaseRegQueryValue(dce, ans2['phkResult'], '')
210            print '\t' + '(Default)' + '\t' + self.__regValues.get(value[0], 'KEY_NOT_FOUND') + '\t', str(value[1])
211        elif self.__options.s:
212            self.__print_all_subkeys_and_entries(dce, subKey + '\\', ans2['phkResult'], 0)
213        else:
214            print keyName
215            self.__print_key_values(dce, ans2['phkResult'])
216            i = 0
217            while True:
218                try:
219                    key = rrp.hBaseRegEnumKey(dce, ans2['phkResult'], i)
220                    print keyName + '\\' + key['lpNameOut'][:-1]
221                    i += 1
222                except Exception:
223                    break
224                    # ans5 = rrp.hBaseRegGetVersion(rpc, ans2['phkResult'])
225                    # ans3 = rrp.hBaseRegEnumKey(rpc, ans2['phkResult'], 0)
226
227    def __print_key_values(self, rpc, keyHandler):
228        i = 0
229        while True:
230            try:
231                ans4 = rrp.hBaseRegEnumValue(rpc, keyHandler, i)
232                lp_value_name = ans4['lpValueNameOut'][:-1]
233                if len(lp_value_name) == 0:
234                    lp_value_name = '(Default)'
235                lp_type = ans4['lpType']
236                lp_data = ''.join(ans4['lpData'])
237                print '\t' + lp_value_name + '\t' + self.__regValues.get(lp_type, 'KEY_NOT_FOUND') + '\t',
238                self.__parse_lp_data(lp_type, lp_data)
239                i += 1
240            except rrp.DCERPCSessionError, e:
241                if e.get_error_code() == ERROR_NO_MORE_ITEMS:
242                    break
243
244    def __print_all_subkeys_and_entries(self, rpc, keyName, keyHandler, index):
245        index = 0
246        while True:
247            try:
248                subkey = rrp.hBaseRegEnumKey(rpc, keyHandler, index)
249                index += 1
250                ans = rrp.hBaseRegOpenKey(rpc, keyHandler, subkey['lpNameOut'],
251                                          samDesired=rrp.MAXIMUM_ALLOWED | rrp.KEY_ENUMERATE_SUB_KEYS)
252                newKeyName = keyName + subkey['lpNameOut'][:-1] + '\\'
253                print newKeyName
254                self.__print_key_values(rpc, ans['phkResult'])
255                self.__print_all_subkeys_and_entries(rpc, newKeyName, ans['phkResult'], 0)
256            except rrp.DCERPCSessionError, e:
257                if e.get_error_code() == ERROR_NO_MORE_ITEMS:
258                    break
259            except rpcrt.DCERPCException, e:
260                if str(e).find('access_denied') >= 0:
261                    logging.error('Cannot access subkey %s, bypassing it' % subkey['lpNameOut'][:-1])
262                    continue
263                elif str(e).find('rpc_x_bad_stub_data') >= 0:
264                    logging.error('Fault call, cannot retrieve value for %s, bypassing it' % subkey['lpNameOut'][:-1])
265                    return
266                raise
267
268    @staticmethod
269    def __parse_lp_data(valueType, valueData):
270        try:
271            if valueType == rrp.REG_SZ or valueType == rrp.REG_EXPAND_SZ:
272                if type(valueData) is int:
273                    print 'NULL'
274                else:
275                    print "%s" % (valueData.decode('utf-16le')[:-1])
276            elif valueType == rrp.REG_BINARY:
277                print ''
278                hexdump(valueData, '\t')
279            elif valueType == rrp.REG_DWORD:
280                print "0x%x" % (unpack('<L', valueData)[0])
281            elif valueType == rrp.REG_QWORD:
282                print "0x%x" % (unpack('<Q', valueData)[0])
283            elif valueType == rrp.REG_NONE:
284                try:
285                    if len(valueData) > 1:
286                        print ''
287                        hexdump(valueData, '\t')
288                    else:
289                        print " NULL"
290                except:
291                    print " NULL"
292            elif valueType == rrp.REG_MULTI_SZ:
293                print "%s" % (valueData.decode('utf-16le')[:-2])
294            else:
295                print "Unknown Type 0x%x!" % valueType
296                hexdump(valueData)
297        except Exception, e:
298            logging.debug('Exception thrown when printing reg value %s', str(e))
299            print 'Invalid data'
300            pass
301
302
303if __name__ == '__main__':
304
305    # Init the example's logger theme
306    logger.init()
307    # Explicitly changing the stdout encoding format
308    if sys.stdout.encoding is None:
309        # Output is redirected to a file
310        sys.stdout = codecs.getwriter('utf8')(sys.stdout)
311    print version.BANNER
312
313    parser = argparse.ArgumentParser(add_help=True, description="Windows Register manipulation script.")
314
315    parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>')
316    parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
317    subparsers = parser.add_subparsers(help='actions', dest='action')
318
319    # A query command
320    query_parser = subparsers.add_parser('query', help='Returns a list of the next tier of subkeys and entries that '
321                                                       'are located under a specified subkey in the registry.')
322    query_parser.add_argument('-keyName', action='store', required=True,
323                              help='Specifies the full path of the subkey. The '
324                                   'keyName must include a valid root key. Valid root keys for the local computer are: HKLM,'
325                                   ' HKU.')
326    query_parser.add_argument('-v', action='store', metavar="VALUENAME", required=False, help='Specifies the registry '
327                           'value name that is to be queried. If omitted, all value names for keyName are returned. ')
328    query_parser.add_argument('-ve', action='store_true', default=False, required=False, help='Queries for the default '
329                                                                         'value or empty value name')
330    query_parser.add_argument('-s', action='store_true', default=False, help='Specifies to query all subkeys and value '
331                                                                             'names recursively.')
332
333    # An add command
334    # add_parser = subparsers.add_parser('add', help='Adds a new subkey or entry to the registry')
335
336    # An delete command
337    # delete_parser = subparsers.add_parser('delete', help='Deletes a subkey or entries from the registry')
338
339    # A copy command
340    # copy_parser = subparsers.add_parser('copy', help='Copies a registry entry to a specified location in the remote '
341    #                                                   'computer')
342
343    # A save command
344    # save_parser = subparsers.add_parser('save', help='Saves a copy of specified subkeys, entries, and values of the '
345    #                                                 'registry in a specified file.')
346
347    # A load command
348    # load_parser = subparsers.add_parser('load', help='Writes saved subkeys and entries back to a different subkey in '
349    #                                                 'the registry.')
350
351    # An unload command
352    # unload_parser = subparsers.add_parser('unload', help='Removes a section of the registry that was loaded using the '
353    #                                                     'reg load operation.')
354
355    # A compare command
356    # compare_parser = subparsers.add_parser('compare', help='Compares specified registry subkeys or entries')
357
358    # A export command
359    # status_parser = subparsers.add_parser('export', help='Creates a copy of specified subkeys, entries, and values into'
360    #                                                     'a file')
361
362    # A import command
363    # import_parser = subparsers.add_parser('import', help='Copies a file containing exported registry subkeys, entries, '
364    #                                                     'and values into the remote computer\'s registry')
365
366
367    group = parser.add_argument_group('authentication')
368
369    group.add_argument('-hashes', action="store", metavar="LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH')
370    group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)')
371    group.add_argument('-k', action="store_true",
372                       help='Use Kerberos authentication. Grabs credentials from ccache file (KRB5CCNAME) based on '
373                            'target parameters. If valid credentials cannot be found, it will use the ones specified '
374                            'in the command line')
375    group.add_argument('-aesKey', action="store", metavar="hex key",
376                       help='AES key to use for Kerberos Authentication (128 or 256 bits)')
377
378    group = parser.add_argument_group('connection')
379
380    group.add_argument('-dc-ip', action='store', metavar="ip address",
381                       help='IP Address of the domain controller. If omitted it will use the domain part (FQDN) specified in '
382                            'the target parameter')
383    group.add_argument('-target-ip', action='store', metavar="ip address",
384                       help='IP Address of the target machine. If omitted it will use whatever was specified as target. '
385                            'This is useful when target is the NetBIOS name and you cannot resolve it')
386    group.add_argument('-port', choices=['139', '445'], nargs='?', default='445', metavar="destination port",
387                       help='Destination port to connect to SMB Server')
388
389    if len(sys.argv) == 1:
390        parser.print_help()
391        sys.exit(1)
392
393    options = parser.parse_args()
394
395    if options.debug is True:
396        logging.getLogger().setLevel(logging.DEBUG)
397    else:
398        logging.getLogger().setLevel(logging.INFO)
399
400    import re
401
402    domain, username, password, remoteName = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(
403        options.target).groups('')
404
405    # In case the password contains '@'
406    if '@' in remoteName:
407        password = password + '@' + remoteName.rpartition('@')[0]
408        remoteName = remoteName.rpartition('@')[2]
409
410    if options.target_ip is None:
411        options.target_ip = remoteName
412
413    if domain is None:
414        domain = ''
415
416    if options.aesKey is not None:
417        options.k = True
418
419    if password == '' and username != '' and options.hashes is None and options.no_pass is False and options.aesKey is None:
420        from getpass import getpass
421
422        password = getpass("Password:")
423
424    regHandler = RegHandler(username, password, domain, options)
425    try:
426        regHandler.run(remoteName, options.target_ip)
427    except Exception, e:
428        logging.error(str(e))
429