Skip to content

Commit 4086238

Browse files
Merge pull request Pennyw0rth#321 from Pennyw0rth/marshall-options-fix
Refactor argparse options
2 parents 2191dee + 3def548 commit 4086238

12 files changed

Lines changed: 207 additions & 155 deletions

File tree

nxc/cli.py

Lines changed: 59 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from nxc.paths import NXC_PATH
1010
from nxc.loaders.protocolloader import ProtocolLoader
1111
from nxc.helpers.logger import highlight
12+
from nxc.helpers.args import DisplayDefaultsNotNone
1213
from nxc.logger import nxc_logger, setup_debug_logging
1314
import importlib.metadata
1415

@@ -23,8 +24,29 @@ def gen_cli_args():
2324
COMMIT = ""
2425
CODENAME = "nxc4u"
2526
nxc_logger.debug(f"NXC VERSION: {VERSION} - {CODENAME} - {COMMIT}")
26-
27-
parser = argparse.ArgumentParser(description=rf"""
27+
28+
generic_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
29+
generic_group = generic_parser.add_argument_group("Generic", "Generic options for nxc across protocols")
30+
generic_group.add_argument("-t", "--threads", type=int, dest="threads", default=256, help="set how many concurrent threads to use")
31+
generic_group.add_argument("--timeout", default=None, type=int, help="max timeout in seconds of each thread")
32+
generic_group.add_argument("--jitter", metavar="INTERVAL", type=str, help="sets a random delay between each authentication")
33+
34+
output_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
35+
output_group = output_parser.add_argument_group("Output", "Options to set verbosity levels and control output")
36+
output_group.add_argument("--verbose", action="store_true", help="enable verbose output")
37+
output_group.add_argument("--debug", action="store_true", help="enable debug level information")
38+
output_group.add_argument("--no-progress", action="store_true", help="do not displaying progress bar during scan")
39+
output_group.add_argument("--log", metavar="LOG", help="export result into a custom file")
40+
41+
dns_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
42+
dns_group = dns_parser.add_argument_group("DNS")
43+
dns_group.add_argument("-6", dest="force_ipv6", action="store_true", help="Enable force IPv6")
44+
dns_group.add_argument("--dns-server", action="store", help="Specify DNS server (default: Use hosts file & System DNS)")
45+
dns_group.add_argument("--dns-tcp", action="store_true", help="Use TCP instead of UDP for DNS queries")
46+
dns_group.add_argument("--dns-timeout", action="store", type=int, default=3, help="DNS query timeout in seconds")
47+
48+
parser = argparse.ArgumentParser(
49+
description=rf"""
2850
. .
2951
.| |. _ _ _ _____
3052
|| || | \ | | ___ | |_ | ____| __ __ ___ ___
@@ -42,62 +64,55 @@ def gen_cli_args():
4264
{highlight('Version', 'red')} : {highlight(VERSION)}
4365
{highlight('Codename', 'red')}: {highlight(CODENAME)}
4466
{highlight('Commit', 'red')} : {highlight(COMMIT)}
45-
""", formatter_class=RawTextHelpFormatter)
46-
47-
parser.add_argument("-t", type=int, dest="threads", default=256, help="set how many concurrent threads to use (default: 256)")
48-
parser.add_argument("--timeout", default=None, type=int, help="max timeout in seconds of each thread (default: None)")
49-
parser.add_argument("--jitter", metavar="INTERVAL", type=str, help="sets a random delay between each authentication (default: None)")
50-
parser.add_argument("--no-progress", action="store_true", help="Not displaying progress bar during scan")
51-
parser.add_argument("--verbose", action="store_true", help="enable verbose output")
52-
parser.add_argument("--debug", action="store_true", help="enable debug level information")
53-
parser.add_argument("--version", action="store_true", help="Display nxc version")
67+
""",
68+
formatter_class=RawTextHelpFormatter,
69+
parents=[generic_parser, output_parser, dns_parser]
70+
)
5471

55-
dns_parser = parser.add_argument_group("DNS")
56-
dns_parser.add_argument("-6", dest="force_ipv6", action="store_true", help="Enable force IPv6")
57-
dns_parser.add_argument("--dns-server", action="store", help="Specify DNS server (default: Use hosts file & System DNS)")
58-
dns_parser.add_argument("--dns-tcp", action="store_true", help="Use TCP instead of UDP for DNS queries")
59-
dns_parser.add_argument("--dns-timeout", action="store", type=int, default=3, help="DNS query timeout in seconds (default: %(default)s)")
72+
parser.add_argument("--version", action="store_true", help="Display nxc version")
6073

6174
# we do module arg parsing here so we can reference the module_list attribute below
62-
module_parser = argparse.ArgumentParser(add_help=False)
63-
mgroup = module_parser.add_mutually_exclusive_group()
75+
module_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
76+
mgroup = module_parser.add_argument_group("Modules", "Options for nxc modules")
6477
mgroup.add_argument("-M", "--module", choices=get_module_names(), action="append", metavar="MODULE", help="module to use")
65-
module_parser.add_argument("-o", metavar="MODULE_OPTION", nargs="+", default=[], dest="module_options", help="module options")
66-
module_parser.add_argument("-L", "--list-modules", action="store_true", help="list available modules")
67-
module_parser.add_argument("--options", dest="show_module_options", action="store_true", help="display module options")
68-
module_parser.add_argument("--server", choices={"http", "https"}, default="https", help="use the selected server (default: https)")
69-
module_parser.add_argument("--server-host", type=str, default="0.0.0.0", metavar="HOST", help="IP to bind the server to (default: 0.0.0.0)")
70-
module_parser.add_argument("--server-port", metavar="PORT", type=int, help="start the server on the specified port")
71-
module_parser.add_argument("--connectback-host", type=str, metavar="CHOST", help="IP for the remote system to connect back to (default: same as server-host)")
78+
mgroup.add_argument("-o", metavar="MODULE_OPTION", nargs="+", default=[], dest="module_options", help="module options")
79+
mgroup.add_argument("-L", "--list-modules", action="store_true", help="list available modules")
80+
mgroup.add_argument("--options", dest="show_module_options", action="store_true", help="display module options")
7281

73-
subparsers = parser.add_subparsers(title="protocols", dest="protocol", description="available protocols")
82+
subparsers = parser.add_subparsers(title="Available Protocols", dest="protocol")
7483

75-
std_parser = argparse.ArgumentParser(add_help=False)
84+
std_parser = argparse.ArgumentParser(add_help=False, parents=[generic_parser, output_parser, dns_parser], formatter_class=DisplayDefaultsNotNone)
7685
std_parser.add_argument("target", nargs="+" if not (module_parser.parse_known_args()[0].list_modules or module_parser.parse_known_args()[0].show_module_options) else "*", type=str, help="the target IP(s), range(s), CIDR(s), hostname(s), FQDN(s), file(s) containing a list of targets, NMap XML or .Nessus file(s)")
77-
std_parser.add_argument("-id", metavar="CRED_ID", nargs="+", default=[], type=str, dest="cred_id", help="database credential ID(s) to use for authentication")
78-
std_parser.add_argument("-u", metavar="USERNAME", dest="username", nargs="+", default=[], help="username(s) or file(s) containing usernames")
79-
std_parser.add_argument("-p", metavar="PASSWORD", dest="password", nargs="+", default=[], help="password(s) or file(s) containing passwords")
80-
std_parser.add_argument("--ignore-pw-decoding", action="store_true", help="Ignore non UTF-8 characters when decoding the password file")
81-
std_parser.add_argument("-k", "--kerberos", action="store_true", help="Use Kerberos authentication")
82-
std_parser.add_argument("--no-bruteforce", action="store_true", help="No spray when using file for username and password (user1 => password1, user2 => password2")
83-
std_parser.add_argument("--continue-on-success", action="store_true", help="continues authentication attempts even after successes")
84-
std_parser.add_argument("--use-kcache", action="store_true", help="Use Kerberos authentication from ccache file (KRB5CCNAME)")
85-
std_parser.add_argument("--log", metavar="LOG", help="Export result into a custom file")
86-
std_parser.add_argument("--aesKey", metavar="AESKEY", nargs="+", help="AES key to use for Kerberos Authentication (128 or 256 bits)")
87-
std_parser.add_argument("--kdcHost", metavar="KDCHOST", help="IP Address of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter")
88-
89-
fail_group = std_parser.add_mutually_exclusive_group()
90-
fail_group.add_argument("--gfail-limit", metavar="LIMIT", type=int, help="max number of global failed login attempts")
91-
fail_group.add_argument("--ufail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per username")
92-
fail_group.add_argument("--fail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per host")
86+
credential_group = std_parser.add_argument_group("Authentication", "Options for authenticating")
87+
credential_group.add_argument("-u", "--username", metavar="USERNAME", dest="username", nargs="+", default=[], help="username(s) or file(s) containing usernames")
88+
credential_group.add_argument("-p", "--password", metavar="PASSWORD", dest="password", nargs="+", default=[], help="password(s) or file(s) containing passwords")
89+
credential_group.add_argument("-id", metavar="CRED_ID", nargs="+", default=[], type=str, dest="cred_id", help="database credential ID(s) to use for authentication")
90+
credential_group.add_argument("--ignore-pw-decoding", action="store_true", help="Ignore non UTF-8 characters when decoding the password file")
91+
credential_group.add_argument("--no-bruteforce", action="store_true", help="No spray when using file for username and password (user1 => password1, user2 => password2)")
92+
credential_group.add_argument("--continue-on-success", action="store_true", help="continues authentication attempts even after successes")
93+
credential_group.add_argument("--gfail-limit", metavar="LIMIT", type=int, help="max number of global failed login attempts")
94+
credential_group.add_argument("--ufail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per username")
95+
credential_group.add_argument("--fail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per host")
96+
97+
kerberos_group = std_parser.add_argument_group("Kerberos", "Options for Kerberos authentication")
98+
kerberos_group.add_argument("-k", "--kerberos", action="store_true", help="Use Kerberos authentication")
99+
kerberos_group.add_argument("--use-kcache", action="store_true", help="Use Kerberos authentication from ccache file (KRB5CCNAME)")
100+
kerberos_group.add_argument("--aesKey", metavar="AESKEY", nargs="+", help="AES key to use for Kerberos Authentication (128 or 256 bits)")
101+
kerberos_group.add_argument("--kdcHost", metavar="KDCHOST", help="FQDN of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter")
102+
103+
server_group = std_parser.add_argument_group("Servers", "Options for nxc servers")
104+
server_group.add_argument("--server", choices={"http", "https"}, default="https", help="use the selected server")
105+
server_group.add_argument("--server-host", type=str, default="0.0.0.0", metavar="HOST", help="IP to bind the server to")
106+
server_group.add_argument("--server-port", metavar="PORT", type=int, help="start the server on the specified port")
107+
server_group.add_argument("--connectback-host", type=str, metavar="CHOST", help="IP for the remote system to connect back to")
93108

94109
p_loader = ProtocolLoader()
95110
protocols = p_loader.get_protocols()
96111

97112
try:
98113
for protocol in protocols:
99114
protocol_object = p_loader.load_protocol(protocols[protocol]["argspath"])
100-
subparsers = protocol_object.proto_args(subparsers, std_parser, module_parser)
115+
subparsers = protocol_object.proto_args(subparsers, [std_parser, module_parser])
101116
except Exception as e:
102117
nxc_logger.exception(f"Error loading proto_args from proto_args.py file in protocol folder: {protocol} - {e}")
103118

nxc/helpers/args.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from argparse import ArgumentDefaultsHelpFormatter, SUPPRESS, OPTIONAL, ZERO_OR_MORE
2+
3+
class DisplayDefaultsNotNone(ArgumentDefaultsHelpFormatter):
4+
def _get_help_string(self, action):
5+
help_string = action.help
6+
if "%(default)" not in action.help and action.default is not SUPPRESS:
7+
defaulting_nargs = [OPTIONAL, ZERO_OR_MORE]
8+
if (action.option_strings or action.nargs in defaulting_nargs) and action.default: # Only add default info if it's not None
9+
help_string += " (default: %(default)s)" # NORUFF
10+
return help_string

nxc/protocols/ftp/proto_args.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
def proto_args(parser, std_parser, module_parser):
2-
ftp_parser = parser.add_parser("ftp", help="own stuff using FTP", parents=[std_parser, module_parser])
3-
ftp_parser.add_argument("--port", type=int, default=21, help="FTP port (default: 21)")
1+
from nxc.helpers.args import DisplayDefaultsNotNone
42

5-
cgroup = ftp_parser.add_argument_group("FTP Access", "Options for enumerating your access")
6-
cgroup.add_argument("--ls", metavar="DIRECTORY", nargs="?", const=".", help="List files in the directory, ex: --ls or --ls Directory")
7-
cgroup.add_argument("--get", metavar="FILE", help="Download a file, ex: --get fileName.txt")
8-
cgroup.add_argument("--put", metavar=("LOCAL_FILE", "REMOTE_FILE"), nargs=2, help="Upload a file, ex: --put inputFileName.txt outputFileName.txt")
3+
4+
def proto_args(parser, parents):
5+
ftp_parser = parser.add_parser("ftp", help="own stuff using FTP", parents=parents, formatter_class=DisplayDefaultsNotNone)
6+
ftp_parser.add_argument("--port", type=int, default=21, help="FTP port")
7+
8+
cgroup = ftp_parser.add_argument_group("File Operations", "Options for enumerating and interacting with files on the target")
9+
cgroup.add_argument("--ls", metavar="DIRECTORY", nargs="?", const=".", help="List files in the directory")
10+
cgroup.add_argument("--get", metavar="FILE", help="Download a file")
11+
cgroup.add_argument("--put", metavar=("LOCAL_FILE", "REMOTE_FILE"), nargs=2, help="Upload a file")
912
return parser

nxc/protocols/ldap/proto_args.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
def proto_args(parser, std_parser, module_parser):
2-
ldap_parser = parser.add_parser("ldap", help="own stuff using LDAP", parents=[std_parser, module_parser])
1+
from nxc.helpers.args import DisplayDefaultsNotNone
2+
3+
4+
def proto_args(parser, parents):
5+
ldap_parser = parser.add_parser("ldap", help="own stuff using LDAP", parents=parents, formatter_class=DisplayDefaultsNotNone)
36
ldap_parser.add_argument("-H", "--hash", metavar="HASH", dest="hash", nargs="+", default=[], help="NTLM hash(es) or file(s) containing NTLM hashes")
4-
ldap_parser.add_argument("--port", type=int, choices={389, 636}, default=389, help="LDAP port (default: 389)")
7+
ldap_parser.add_argument("--port", type=int, default=389, help="LDAP port")
58
ldap_parser.add_argument("--no-smb", action="store_true", help="No smb connection")
69

710
dgroup = ldap_parser.add_mutually_exclusive_group()
@@ -31,6 +34,6 @@ def proto_args(parser, std_parser, module_parser):
3134
bgroup = ldap_parser.add_argument_group("Bloodhound Scan", "Options to play with Bloodhoud")
3235
bgroup.add_argument("--bloodhound", action="store_true", help="Perform a Bloodhound scan")
3336
bgroup.add_argument("-ns", "--nameserver", help="Custom DNS IP")
34-
bgroup.add_argument("-c", "--collection", help="Which information to collect. Supported: Group, LocalAdmin, Session, Trusts, Default, DCOnly, DCOM, RDP, PSRemote, LoggedOn, Container, ObjectProps, ACL, All. You can specify more than one by separating them with a comma. (default: Default)'")
37+
bgroup.add_argument("-c", "--collection", default="Collection", help="Which information to collect. Supported: Group, LocalAdmin, Session, Trusts, Default, DCOnly, DCOM, RDP, PSRemote, LoggedOn, Container, ObjectProps, ACL, All. You can specify more than one by separating them with a comma")
3538

3639
return parser

nxc/protocols/mssql/proto_args.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
def proto_args(parser, std_parser, module_parser):
2-
mssql_parser = parser.add_parser("mssql", help="own stuff using MSSQL", parents=[std_parser, module_parser])
1+
from nxc.helpers.args import DisplayDefaultsNotNone
2+
3+
4+
def proto_args(parser, parents):
5+
mssql_parser = parser.add_parser("mssql", help="own stuff using MSSQL", parents=parents, formatter_class=DisplayDefaultsNotNone)
36
mssql_parser.add_argument("-H", "--hash", metavar="HASH", dest="hash", nargs="+", default=[], help="NTLM hash(es) or file(s) containing NTLM hashes")
4-
mssql_parser.add_argument("--port", default=1433, type=int, metavar="PORT", help="MSSQL port (default: 1433)")
5-
mssql_parser.add_argument("--mssql-timeout", help="SQL server connection timeout, default is %(default)s seconds", type=int, default=5)
7+
mssql_parser.add_argument("--port", default=1433, type=int, metavar="PORT", help="MSSQL port")
8+
mssql_parser.add_argument("--mssql-timeout", help="SQL server connection timeout", type=int, default=5)
69
mssql_parser.add_argument("-q", "--query", dest="mssql_query", metavar="QUERY", type=str, help="execute the specified query against the MSSQL DB")
710

811
dgroup = mssql_parser.add_mutually_exclusive_group()
@@ -23,7 +26,7 @@ def proto_args(parser, std_parser, module_parser):
2326
psgroup.add_argument("--no-encode", action="store_true", default=False, help="Do not encode the PowerShell command ran on target")
2427

2528
tgroup = mssql_parser.add_argument_group("Files", "Options for put and get remote files")
26-
tgroup.add_argument("--put-file", nargs=2, metavar=("SRC_FILE", "DEST_FILE"), help="Put a local file into remote target, ex: whoami.txt C:\\Windows\\Temp\\whoami.txt")
27-
tgroup.add_argument("--get-file", nargs=2, metavar=("SRC_FILE", "DEST_FILE"), help="Get a remote file, ex: C:\\Windows\\Temp\\whoami.txt whoami.txt")
29+
tgroup.add_argument("--put-file", nargs=2, metavar=("SRC_FILE", "DEST_FILE"), help="Put a local file into remote target, ex: whoami.txt C:\\\\Windows\\\\Temp\\\\whoami.txt")
30+
tgroup.add_argument("--get-file", nargs=2, metavar=("SRC_FILE", "DEST_FILE"), help="Get a remote file, ex: C:\\\\Windows\\\\Temp\\\\whoami.txt whoami.txt")
2831

2932
return parser

nxc/protocols/rdp/proto_args.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
def proto_args(parser, std_parser, module_parser):
2-
rdp_parser = parser.add_parser("rdp", help="own stuff using RDP", parents=[std_parser, module_parser])
1+
from nxc.helpers.args import DisplayDefaultsNotNone
2+
3+
4+
def proto_args(parser, parents):
5+
rdp_parser = parser.add_parser("rdp", help="own stuff using RDP", parents=parents, formatter_class=DisplayDefaultsNotNone)
36
rdp_parser.add_argument("-H", "--hash", metavar="HASH", dest="hash", nargs="+", default=[], help="NTLM hash(es) or file(s) containing NTLM hashes")
4-
rdp_parser.add_argument("--port", type=int, default=3389, help="Custom RDP port")
5-
rdp_parser.add_argument("--rdp-timeout", type=int, default=5, help="RDP timeout on socket connection, defalut is %(default)ss")
7+
rdp_parser.add_argument("--port", type=int, default=3389, help="RDP port")
8+
rdp_parser.add_argument("--rdp-timeout", type=int, default=5, help="RDP timeout on socket connection")
69
rdp_parser.add_argument("--nla-screenshot", action="store_true", help="Screenshot RDP login prompt if NLA is disabled")
710

811
dgroup = rdp_parser.add_mutually_exclusive_group()
@@ -11,7 +14,7 @@ def proto_args(parser, std_parser, module_parser):
1114

1215
egroup = rdp_parser.add_argument_group("Screenshot", "Remote Desktop Screenshot")
1316
egroup.add_argument("--screenshot", action="store_true", help="Screenshot RDP if connection success")
14-
egroup.add_argument("--screentime", type=int, default=10, help="Time to wait for desktop image, default is %(default)ss")
15-
egroup.add_argument("--res", default="1024x768", help='Resolution in "WIDTHxHEIGHT" format. Default: "1024x768"')
17+
egroup.add_argument("--screentime", type=int, default=10, help="Time to wait for desktop image")
18+
egroup.add_argument("--res", default="1024x768", help="Resolution in WIDTHxHEIGHT format")
1619

1720
return parser

0 commit comments

Comments
 (0)