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