Skip to content

Commit e352013

Browse files
authored
Merge branch 'main' into rdp-exec
2 parents 217adf3 + 83b7148 commit e352013

184 files changed

Lines changed: 5024 additions & 2048 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/ISSUE_TEMPLATE/bug_report.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# ❗❗❗ Before filing this bug report, MAKE SURE you have already downloaded the newest version of NetExec from GitHub and installed it! Many issues have already been reported and fixed, _especially_ if you are running the native Kali version! Please delete this line before submitting your issue if you have done so.❗❗❗
2+
13
---
24
name: Bug report
35
about: Create a report to help us improve
@@ -7,6 +9,8 @@ assignees: ''
79

810
---
911

12+
13+
1014
**Describe the bug**
1115
A clear and concise description of what the bug is.
1216

@@ -30,8 +34,8 @@ If applicable, add screenshots to help explain your problem.
3034

3135
**NetExec info**
3236
- OS: [e.g. Kali]
33-
- Version of nxc: [e.g. v1.5.2]
34-
- Installed from: apt/github/pip/docker/...? Please try with latest release before openning an issue
37+
- Version of nxc: [e.g. v1.5.2] (run nxc --version and post the _exact_ string that is output)
38+
- Installed from: apt/github/pip/docker/...? Please try with latest release before opening an issue
3539

3640
**Additional context**
3741
Add any other context about the problem here.

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,35 @@
11
## Description
22

33
Please include a summary of the change and which issue is fixed, or what the enhancement does.
4-
Please also include relevant motivation and context.
54
List any dependencies that are required for this change.
65

76
## Type of change
8-
Please delete options that are not relevant.
7+
Insert an "x" inside the brackets for relevant items (do not delete options)
8+
99
- [ ] Bug fix (non-breaking change which fixes an issue)
1010
- [ ] New feature (non-breaking change which adds functionality)
1111
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
12+
- [ ] Deprecation of feature or functionality
1213
- [ ] This change requires a documentation update
1314
- [ ] This requires a third party update (such as Impacket, Dploot, lsassy, etc)
1415

15-
## How Has This Been Tested?
16-
Please describe the tests that you ran to verify your changes (e2e, single commands, etc)
17-
Please also list any relevant details for your test configuration, such as your locally running machine Python version & OS, as well as the target(s) you tested against, including software versions
18-
19-
If you are using poetry, you can easily run tests via:
20-
`poetry run python tests/e2e_tests.py -t $TARGET -u $USER -p $PASSWORD`
21-
There are additional options like `--errors` to display ALL errors (some may not be failures), `--poetry` (output will include the poetry run prepended), `--line-num $START-$END $SINGLE` for only running a subset
16+
## Setup guide for the review
17+
Please provide guidance on what setup is needed to test the introduced changes, such as your locally running machine Python version & OS, as well as the target(s) you tested against, including software versions.
18+
In particular:
19+
- Bug Fix: Please provide a short description on how to trigger the bug, to make the bug reproducable for the reviewer.
20+
- Added Feature/Enhancement: Please specify what setup is needed in order to test the changes. E.g. is additional software needed? GPO changes required? Specific registry settings that need to be changed?
2221

2322
## Screenshots (if appropriate):
2423
Screenshots are always nice to have and can give a visual representation of the change.
2524
If appropriate include before and after screenshot(s) to show which results are to be expected.
2625

2726
## Checklist:
27+
Insert an "x" inside the brackets for completed and relevant items (do not delete options)
2828

2929
- [ ] I have ran Ruff against my changes (via poetry: `poetry run python -m ruff check . --preview`, use `--fix` to automatically fix what it can)
30-
- [ ] I have added or updated the tests/e2e_commands.txt file if necessary
30+
- [ ] I have added or updated the `tests/e2e_commands.txt` file if necessary (new modules or features are _required_ to be added to the e2e tests)
3131
- [ ] New and existing e2e tests pass locally with my changes
32-
- [ ] My code follows the style guidelines of this project (should be covered by Ruff above)
33-
- [ ] If reliant on third party dependencies, such as Impacket, dploot, lsassy, etc, I have linked the relevant PRs in those projects
32+
- [ ] If reliant on changes of third party dependencies, such as Impacket, dploot, lsassy, etc, I have linked the relevant PRs in those projects
3433
- [ ] I have performed a self-review of my own code
3534
- [ ] I have commented my code, particularly in hard-to-understand areas
3635
- [ ] I have made corresponding changes to the documentation (PR here: https://github.com/Pennyw0rth/NetExec-Wiki)

Dockerfile

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,45 @@
1-
FROM python:3.11-slim
2-
1+
FROM python:3.13-slim-bookworm AS builder
32
ENV LANG=C.UTF-8
43
ENV LC_ALL=C.UTF-8
54
ENV PIP_NO_CACHE_DIR=off
65

76
WORKDIR /usr/src/netexec
87

9-
RUN apt-get update && \
10-
apt-get install -y libffi-dev libxml2-dev libxslt-dev libssl-dev openssl autoconf g++ python3-dev curl git
11-
RUN apt-get update
12-
# Get Rust
13-
RUN curl https://sh.rustup.rs -sSf | bash -s -- -y
14-
# Add .cargo/bin to PATH
8+
RUN apt update && \
9+
apt install -y --no-install-recommends \
10+
libffi-dev \
11+
libxml2-dev \
12+
libxslt-dev \
13+
libssl-dev \
14+
openssl \
15+
autoconf \
16+
g++ \
17+
python3-dev \
18+
curl \
19+
git \
20+
unzip \
21+
&& apt clean && rm -rf /var/lib/apt/lists/*
22+
23+
RUN curl https://sh.rustup.rs -sSf | bash -s -- -y --default-toolchain stable
1524
ENV PATH="/root/.cargo/bin:${PATH}"
16-
# Check cargo is visible
17-
RUN cargo --help
1825

19-
COPY . .
20-
RUN pip install .
26+
RUN git clone https://github.com/Pennyw0rth/NetExec.git . \
27+
&& pip install .
28+
29+
FROM python:3.13-slim-bookworm
30+
31+
ENV LANG=C.UTF-8
32+
ENV LC_ALL=C.UTF-8
33+
ENV PIP_NO_CACHE_DIR=off
34+
35+
WORKDIR /usr/src/netexec
36+
37+
COPY --from=builder /usr/local/lib/python3.13/site-packages /usr/local/lib/python3.13/site-packages
38+
COPY --from=builder /usr/local/bin /usr/local/bin
39+
40+
RUN apt update && \
41+
apt install -y --no-install-recommends \
42+
openssl \
43+
&& apt clean && rm -rf /var/lib/apt/lists/*
2144

22-
ENTRYPOINT [ "nxc" ]
45+
ENTRYPOINT ["nxc"]

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,5 @@ Awesome code contributors of NetExec:
6363
[![](https://github.com/NeffIsBack.png?size=50)](https://github.com/NeffIsBack)
6464
[![](https://github.com/Hackndo.png?size=50)](https://github.com/Hackndo)
6565
[![](https://github.com/XiaoliChan.png?size=50)](https://github.com/XiaoliChan)
66+
[![](https://github.com/termanix.png?size=50)](https://github.com/termanix)
67+
[![](https://github.com/Dfte.png?size=50)](https://github.com/Dfte)

netexec.spec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ a = Analysis(
2727
'impacket.dcerpc.v5.gkdi',
2828
'impacket.dcerpc.v5.rprn',
2929
'impacket.dcerpc.v5.even',
30+
'impacket.dcerpc.v5.even6',
3031
'impacket.dpapi_ng',
3132
'impacket.tds',
3233
'impacket.version',
@@ -43,6 +44,7 @@ a = Analysis(
4344
'nxc.parsers.ldap_results',
4445
'nxc.helpers.bash',
4546
'nxc.helpers.bloodhound',
47+
'nxc.helpers.even6_parser',
4648
'nxc.helpers.msada_guids',
4749
'nxc.helpers.ntlm_parser',
4850
'paramiko',

nxc/cli.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ def gen_cli_args():
2525
COMMIT = ""
2626
DISTANCE = ""
2727
CODENAME = "SmoothOperator"
28-
nxc_logger.debug(f"NXC VERSION: {VERSION} - {CODENAME} - {COMMIT} - {DISTANCE}")
2928

3029
generic_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
3130
generic_group = generic_parser.add_argument_group("Generic", "Generic options for nxc across protocols")
@@ -61,7 +60,7 @@ def gen_cli_args():
6160
6261
The network execution tool
6362
Maintained as an open source project by @NeffIsBack, @MJHallenbeck, @_zblurx
64-
63+
6564
For documentation and usage examples, visit: https://www.netexec.wiki/
6665
6766
{highlight('Version', 'red')} : {highlight(VERSION)}
@@ -133,7 +132,7 @@ def gen_cli_args():
133132
if hasattr(args, "get_output_tries"):
134133
args.get_output_tries = args.get_output_tries * 10
135134

136-
return args
135+
return args, [CODENAME, VERSION, COMMIT, DISTANCE]
137136

138137

139138
def get_module_names():

nxc/config.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import os
21
from os.path import join as path_join
32
import configparser
4-
from nxc.paths import NXC_PATH, DATA_PATH
3+
from nxc.paths import DATA_PATH, CONFIG_PATH
54
from nxc.first_run import first_run_setup
65
from nxc.logger import nxc_logger
76
from ast import literal_eval
@@ -10,20 +9,25 @@
109
nxc_default_config.read(path_join(DATA_PATH, "nxc.conf"))
1110

1211
nxc_config = configparser.ConfigParser()
13-
nxc_config.read(os.path.join(NXC_PATH, "nxc.conf"))
12+
nxc_config.read(CONFIG_PATH)
1413

1514
if "nxc" not in nxc_config.sections():
1615
first_run_setup()
17-
nxc_config.read(os.path.join(NXC_PATH, "nxc.conf"))
16+
nxc_config.read(CONFIG_PATH)
1817

1918
# Check if there are any missing options in the config file
2019
for section in nxc_default_config.sections():
20+
if not nxc_config.has_section(section):
21+
nxc_logger.display(f"Adding missing section '{section}' to nxc.conf")
22+
nxc_config.add_section(section)
23+
with open(CONFIG_PATH, "w") as config_file:
24+
nxc_config.write(config_file)
2125
for option in nxc_default_config.options(section):
2226
if not nxc_config.has_option(section, option):
2327
nxc_logger.display(f"Adding missing option '{option}' in config section '{section}' to nxc.conf")
2428
nxc_config.set(section, option, nxc_default_config.get(section, option))
2529

26-
with open(path_join(NXC_PATH, "nxc.conf"), "w") as config_file:
30+
with open(CONFIG_PATH, "w") as config_file:
2731
nxc_config.write(config_file)
2832

2933
# THESE OPTIONS HAVE TO EXIST IN THE DEFAULT CONFIG FILE
@@ -32,7 +36,6 @@
3236
audit_mode = nxc_config.get("nxc", "audit_mode", fallback=False)
3337
reveal_chars_of_pwd = int(nxc_config.get("nxc", "reveal_chars_of_pwd", fallback=0))
3438
config_log = nxc_config.getboolean("nxc", "log_mode", fallback=False)
35-
ignore_opsec = nxc_config.getboolean("nxc", "ignore_opsec", fallback=False)
3639
host_info_colors = literal_eval(nxc_config.get("nxc", "host_info_colors", fallback=["green", "red", "yellow", "cyan"]))
3740

3841

nxc/connection.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from datetime import datetime
2+
import os
13
import random
24
import sys
35
import contextlib
@@ -15,6 +17,7 @@
1517
from nxc.loaders.moduleloader import ModuleLoader
1618
from nxc.logger import nxc_logger, NXCAdapter
1719
from nxc.context import Context
20+
from nxc.paths import NXC_PATH
1821
from nxc.protocols.ldap.laps import laps_search
1922
from nxc.helpers.pfx import pfx_auth
2023

@@ -136,7 +139,11 @@ def __init__(self, args, db, target):
136139
# Authentication info
137140
self.password = ""
138141
self.username = ""
139-
self.kerberos = bool(self.args.kerberos or self.args.use_kcache or self.args.aesKey or (hasattr(self.args, "delegate") and self.args.delegate))
142+
self.kerberos = bool(self.args.kerberos or
143+
self.args.use_kcache or
144+
self.args.aesKey or
145+
(hasattr(self.args, "delegate") and self.args.delegate) or
146+
(hasattr(self.args, "no_preauth_targets") and self.args.no_preauth_targets))
140147
self.aesKey = None if not self.args.aesKey else self.args.aesKey[0]
141148
self.use_kcache = None if not self.args.use_kcache else self.args.use_kcache
142149
self.admin_privs = False
@@ -152,6 +159,11 @@ def __init__(self, args, db, target):
152159
self.local_ip = None
153160
self.dns_server = self.args.dns_server
154161

162+
# Construct the output file template using os.path.join for OS compatibility
163+
base_log_dir = os.path.join(os.path.expanduser(NXC_PATH), "logs")
164+
filename_pattern = f"{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-")
165+
self.output_file_template = os.path.join(base_log_dir, "{output_folder}", filename_pattern)
166+
155167
# DNS resolution
156168
dns_result = self.resolver(target)
157169
if dns_result:
@@ -275,7 +287,7 @@ def call_modules(self):
275287
extra={
276288
"module_name": module.name.upper(),
277289
"host": self.host,
278-
"port": self.args.port,
290+
"port": self.port,
279291
"hostname": self.hostname,
280292
},
281293
)
@@ -293,8 +305,7 @@ def call_modules(self):
293305
module.on_admin_login(context, self)
294306

295307
def inc_failed_login(self, username):
296-
global global_failed_logins
297-
global user_failed_logins
308+
global global_failed_logins, user_failed_logins
298309

299310
if username not in user_failed_logins:
300311
user_failed_logins[username] = 0
@@ -304,16 +315,15 @@ def inc_failed_login(self, username):
304315
self.failed_logins += 1
305316

306317
def over_fail_limit(self, username):
307-
global global_failed_logins
308-
global user_failed_logins
318+
global global_failed_logins, user_failed_logins
309319

310320
if global_failed_logins == self.args.gfail_limit:
311321
return True
312322

313323
if self.failed_logins == self.args.fail_limit:
314324
return True
315325

316-
if username in user_failed_logins and self.args.ufail_limit == user_failed_logins[username]:
326+
if username in user_failed_logins and self.args.ufail_limit == user_failed_logins[username]: # noqa: SIM103
317327
return True
318328

319329
return False
@@ -418,14 +428,14 @@ def parse_credentials(self):
418428
with open(ntlm_hash) as ntlm_hash_file:
419429
for i, line in enumerate(ntlm_hash_file):
420430
line = line.strip()
421-
if len(line) != 32 and len(line) != 65:
431+
if len(line) != 32 and len(line) != 65 and len(line) != 0:
422432
self.logger.fail(f"Invalid NTLM hash length on line {(i + 1)} (len {len(line)}): {line}")
423433
continue
424434
else:
425435
secret.append(line)
426436
cred_type.append("hash")
427437
else:
428-
if len(ntlm_hash) != 32 and len(ntlm_hash) != 65:
438+
if len(ntlm_hash) != 32 and len(ntlm_hash) != 65 and len(ntlm_hash) != 0:
429439
self.logger.fail(f"Invalid NTLM hash length {len(ntlm_hash)}, authentication not sent")
430440
exit(1)
431441
else:

nxc/context.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import configparser
22
import os
33

4+
from nxc.paths import NXC_PATH, CONFIG_PATH
5+
46

57
class Context:
68
def __init__(self, db, logger, args):
79
for key, value in vars(args).items():
810
setattr(self, key, value)
911

1012
self.db = db
11-
self.log_folder_path = os.path.join(os.path.expanduser("~/.nxc"), "logs")
13+
self.log_folder_path = os.path.join(NXC_PATH, "logs")
1214
self.localip = None
1315

1416
self.conf = configparser.ConfigParser()
15-
self.conf.read(os.path.expanduser("~/.nxc/nxc.conf"))
17+
self.conf.read(CONFIG_PATH)
1618

1719
self.log = logger

0 commit comments

Comments
 (0)