Skip to content

Commit c7e7b11

Browse files
committed
resolve conflicts
2 parents 1f640e7 + 67d90e0 commit c7e7b11

50 files changed

Lines changed: 2969 additions & 1696 deletions

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/config.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
blank_issues_enabled: false
2+
contact_links:
3+
- name: NetExec Wiki
4+
url: https://www.netexec.wiki/
5+
about: Check the wiki for usage guides and documentation before opening an issue.
6+
- name: NetExec Discord
7+
url: https://discord.com/invite/pjwUTQzg8R
8+
about: Join the Discord for general questions and community support.

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
## Description
2-
32
Please include a summary of the change and which issue is fixed, or what the enhancement does.
43
List any dependencies that are required for this change.
54

5+
If you have used AI in any form, please state the tool you used (e.g. Claude Code, Cursor, Amp) along with the extent that the work was AI-assisted. See the project's AI policy for more details: https://github.com/Pennyw0rth/NetExec/blob/main/AI_POLICY.md
6+
67
## Type of change
78
Insert an "x" inside the brackets for relevant items (do not delete options)
89

@@ -12,24 +13,28 @@ Insert an "x" inside the brackets for relevant items (do not delete options)
1213
- [ ] Deprecation of feature or functionality
1314
- [ ] This change requires a documentation update
1415
- [ ] This requires a third party update (such as Impacket, Dploot, lsassy, etc)
16+
- [ ] This PR was created with the assistance of AI (list what type of assistance, tool(s)/model(s) in the description)
1517

1618
## Setup guide for the review
1719
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.
1820
In particular:
1921
- 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?
22+
- Added Feature/Enhancement: Please specify what setup is needed in order to test the changes, such as:
23+
- Is additional software needed?
24+
- GPO changes required?
25+
- Specific registry settings that need to be changed?
2126

2227
## Screenshots (if appropriate):
2328
Screenshots are always nice to have and can give a visual representation of the change.
24-
If appropriate include before and after screenshot(s) to show which results are to be expected.
29+
If appropriate, include before and after screenshot(s) to show which results are to be expected.
2530

2631
## Checklist:
2732
Insert an "x" inside the brackets for completed and relevant items (do not delete options)
2833

29-
- [ ] I have ran Ruff against my changes (via poetry: `poetry run python -m ruff check . --preview`, use `--fix` to automatically fix what it can)
34+
- [ ] I have ran Ruff against my changes (poetry: `poetry run ruff check .`, use `--fix` to automatically fix what it can)
3035
- [ ] 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)
31-
- [ ] New and existing e2e tests pass locally with my changes
3236
- [ ] If reliant on changes of third party dependencies, such as Impacket, dploot, lsassy, etc, I have linked the relevant PRs in those projects
33-
- [ ] I have performed a self-review of my own code
37+
- [ ] I have linked relevant sources that describes the added technique (blog posts, documentation, etc)
38+
- [ ] I have performed a self-review of my own code (_not_ an AI review)
3439
- [ ] I have commented my code, particularly in hard-to-understand areas
3540
- [ ] I have made corresponding changes to the documentation (PR here: https://github.com/Pennyw0rth/NetExec-Wiki)
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: PR Template Check
2+
3+
on:
4+
pull_request:
5+
types: [opened, edited]
6+
7+
permissions:
8+
pull-requests: write
9+
10+
jobs:
11+
check-template:
12+
runs-on: ubuntu-latest
13+
permissions:
14+
pull-requests: write
15+
steps:
16+
- name: Check PR description for template sections
17+
uses: actions/github-script@v7
18+
with:
19+
script: |
20+
const body = context.payload.pull_request.body || '';
21+
const requiredSections = [
22+
'## Description',
23+
'## Type of change',
24+
'## Setup guide for the review',
25+
'## Checklist'
26+
];
27+
28+
const missingSections = requiredSections.filter(
29+
section => !body.includes(section)
30+
);
31+
32+
if (missingSections.length === 0) return;
33+
34+
// Check if we already left a comment to avoid spamming
35+
const comments = await github.rest.issues.listComments({
36+
owner: context.repo.owner,
37+
repo: context.repo.repo,
38+
issue_number: context.payload.pull_request.number
39+
});
40+
41+
const botComment = comments.data.find(
42+
c => c.user.type === 'Bot' && c.body.includes('<!-- pr-template-check -->')
43+
);
44+
45+
if (botComment) return;
46+
47+
const missing = missingSections.map(s => `- ${s}`).join('\n');
48+
49+
await github.rest.issues.createComment({
50+
owner: context.repo.owner,
51+
repo: context.repo.repo,
52+
issue_number: context.payload.pull_request.number,
53+
body: `<!-- pr-template-check -->\nIt looks like the PR template may not have been filled out. The following sections appear to be missing:\n\n${missing}\n\nPlease edit your PR description to include them. The template helps reviewers understand and test your changes. Thanks!`
54+
});

AI_POLICY.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# AI Usage Policy
2+
3+
This policy was adapted from the [ghostty project](https://github.com/ghostty-org/ghostty/).
4+
The original version can be found [Ghostty's PR #10412](https://github.com/ghostty-org/ghostty/pull/10412).
5+
6+
The NetExec project has strict rules for AI usage:
7+
8+
- **All AI usage in any form must be disclosed.** You must state
9+
the tool(s) and model(s) you used (e.g. Claude Code, Cursor, Opus 4.6,
10+
Codex 5.2, etc) along with the extent that the work was AI-assisted.
11+
12+
- **Pull requests created in any way by AI can only be for accepted issues.**
13+
Drive-by pull requests that do not reference an accepted issue may be
14+
rejected and closed. If AI isn't disclosed but a maintainer suspects its use,
15+
the PR may be rejected and closed. If you want to share code for a
16+
non-accepted issue, open a discussion or attach it to the existing issue.
17+
18+
- **Pull requests created by AI must have been fully verified with
19+
human use.** AI must not create hypothetically correct code that
20+
hasn't been tested. Importantly, you must not allow AI to write
21+
code for platforms or environments you don't have access to manually
22+
test on.
23+
24+
- **Issues and discussions can use AI assistance but must have a full
25+
human-in-the-loop.** This means that any content generated with AI
26+
must have been reviewed _and edited_ by a human before submission.
27+
AI is very good at being overly verbose and including noise that
28+
distracts from the main point. Humans must do their research and
29+
trim this down.
30+
31+
- **No AI-generated media is allowed (art, images, videos, audio, etc.).**
32+
Text and code are the only acceptable AI-generated content, per the
33+
other rules in this policy.
34+
35+
- **Bad AI drivers will be banned** You've been warned. We love to help junior
36+
developers learn and grow, but if you're interested in that then don't use
37+
AI, and we'll help you.
38+
39+
- **Official maintainers have the final say** We always strive to be helpful,
40+
but there are limits. If you submit agregiously terrible AI generated code
41+
with no review, we may ban you without word. We do not want to waste our
42+
time reviewing slop if the contributor can't be bothered to review the work
43+
themselves.
44+
45+
These rules apply only to outside contributions to NetExec. Maintainers
46+
and trusted contributors are exempt from these rules and may use AI tools at
47+
their discretion; they've proven themselves trustworthy to apply good judgment.
48+
49+
## There are Humans Here
50+
51+
Please remember that NetExec is maintained by humans.
52+
53+
Every discussion, issue, and pull request is read and reviewed by
54+
humans (and sometimes machines, too). It is a boundary point at which
55+
people interact with each other and the work done. It is rude and
56+
disrespectful to approach this boundary with low-effort, unqualified
57+
work, since it puts the burden of validation on the maintainers.
58+
59+
In a perfect world, AI would produce high-quality, accurate work
60+
every time, but today that is simply not true. This is compounded by the fact
61+
that accessibility to AI is high, allowing low skilled individuals to think
62+
that they are contributing useful code. Even many skilled programmers do not
63+
understand how to use it effectively. This has opened up a waterfall of low
64+
quality contributions across the Open Source community, wasting resources.
65+
66+
## AI is Welcome Here, Within Reason
67+
68+
NetExec maintainers acknowledge AI as a productive tool to some workflows, and
69+
are open to leveraging this technology to improve NetExec; however, there are
70+
many low quality AI tools whose use results in pure slop being generated. The
71+
security communinity is not immune from AI psychosis, over-hype, or FOMO.
72+
As with any new technology, it is important to understand how it works and how
73+
to best use it, not blindly apply it to every use case with the hope that it
74+
will fix all your issues.
75+
76+
We include this section to be transparent about the project's usage about
77+
AI for people who may disagree with it.

netexec.spec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ a = Analysis(
4949
'nxc.helpers.bloodhound',
5050
'nxc.helpers.even6_parser',
5151
'nxc.helpers.msada_guids',
52-
'nxc.helpers.ntlm_parser',
52+
'nxc.helpers.negotiate_parser',
5353
'paramiko',
5454
'pefile',
5555
'pypsrp.client',

nxc/connection.py

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import contextlib
66

77
from os.path import isfile
8-
from threading import BoundedSemaphore
8+
from threading import BoundedSemaphore, Lock
99
from functools import wraps
1010
from time import sleep
1111
from ipaddress import ip_address
@@ -22,8 +22,10 @@
2222
from nxc.helpers.pfx import pfx_auth
2323

2424
from impacket.dcerpc.v5 import transport
25+
from impacket.krb5.ccache import CCache
2526

2627
sem = BoundedSemaphore(1)
28+
fail_lock = Lock()
2729
global_failed_logins = 0
2830
user_failed_logins = {}
2931

@@ -314,26 +316,28 @@ def call_modules(self):
314316
def inc_failed_login(self, username):
315317
global global_failed_logins, user_failed_logins
316318

317-
if username not in user_failed_logins:
318-
user_failed_logins[username] = 0
319+
with fail_lock:
320+
if username not in user_failed_logins:
321+
user_failed_logins[username] = 0
319322

320-
user_failed_logins[username] += 1
321-
global_failed_logins += 1
322-
self.failed_logins += 1
323+
user_failed_logins[username] += 1
324+
global_failed_logins += 1
325+
self.failed_logins += 1
323326

324327
def over_fail_limit(self, username):
325328
global global_failed_logins, user_failed_logins
326329

327-
if global_failed_logins == self.args.gfail_limit:
328-
return True
330+
with fail_lock:
331+
if global_failed_logins == self.args.gfail_limit:
332+
return True
329333

330-
if self.failed_logins == self.args.fail_limit:
331-
return True
334+
if self.failed_logins == self.args.fail_limit:
335+
return True
332336

333-
if username in user_failed_logins and self.args.ufail_limit == user_failed_logins[username]: # noqa: SIM103
334-
return True
337+
if username in user_failed_logins and self.args.ufail_limit == user_failed_logins[username]: # noqa: SIM103
338+
return True
335339

336-
return False
340+
return False
337341

338342
def query_db_creds(self):
339343
"""Queries the database for credentials to be used for authentication.
@@ -480,8 +484,6 @@ def try_credentials(self, domain, username, owned, secret, cred_type, data=None)
480484
- NTLM-hash (/kerberos)
481485
- AES-key
482486
"""
483-
if self.over_fail_limit(username):
484-
return False
485487
if self.args.continue_on_success and owned:
486488
return False
487489

@@ -497,6 +499,8 @@ def try_credentials(self, domain, username, owned, secret, cred_type, data=None)
497499
sleep(value)
498500

499501
with sem:
502+
if self.over_fail_limit(username):
503+
return False
500504
if cred_type == "plaintext":
501505
if self.kerberos:
502506
self.logger.debug("Trying to authenticate using Kerberos")
@@ -552,7 +556,7 @@ def login(self):
552556
if self.args.use_kcache:
553557
self.logger.debug("Trying to authenticate using Kerberos cache")
554558
with sem:
555-
username = self.args.username[0] if len(self.args.username) else ""
559+
username = self.args.username[0] if len(self.args.username) else CCache.parseFile()[1]
556560
password = self.args.password[0] if len(self.args.password) else ""
557561
self.kerberos_login(self.domain, username, password, "", "", self.kdcHost, True)
558562
self.logger.info("Successfully authenticated using Kerberos cache")

nxc/helpers/negotiate_parser.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Parsing helpers for auth negotiation: NTLM challenges and TDS ERROR/INFO on MSSQL LOGIN7.
2+
# Original NTLM parsing from: https://github.com/fortra/impacket/blob/master/examples/DumpNTLMInfo.py#L568
3+
4+
import struct
5+
6+
from impacket import ntlm
7+
from impacket.smb3 import WIN_VERSIONS
8+
from impacket.tds import TDS_ERROR_TOKEN, TDS_INFO_TOKEN, TDS_INFO_ERROR
9+
import contextlib
10+
11+
12+
def parse_challenge(challange):
13+
target_info = {
14+
"hostname": None,
15+
"domain": None,
16+
"os_version": None
17+
}
18+
challange = ntlm.NTLMAuthChallenge(challange)
19+
av_pairs = ntlm.AV_PAIRS(challange["TargetInfoFields"][:challange["TargetInfoFields_len"]])
20+
if av_pairs[ntlm.NTLMSSP_AV_HOSTNAME] is not None:
21+
with contextlib.suppress(Exception):
22+
target_info["hostname"] = av_pairs[ntlm.NTLMSSP_AV_HOSTNAME][1].decode("utf-16le")
23+
if av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME] is not None:
24+
with contextlib.suppress(Exception):
25+
target_info["domain"] = av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME][1].decode("utf-16le")
26+
if "Version" in challange.fields:
27+
version = challange["Version"]
28+
if len(version) >= 4:
29+
major_version = version[0]
30+
minor_version = version[1]
31+
product_build = struct.unpack("<H", version[2:4])[0]
32+
if product_build in WIN_VERSIONS:
33+
target_info["os_version"] = f"{WIN_VERSIONS[product_build]} Build {product_build}"
34+
else:
35+
target_info["os_version"] = f"{major_version}.{minor_version} Build {product_build}"
36+
return target_info
37+
38+
39+
def decode_tds_info_error_msgtext(data, offset):
40+
"""Extract MsgText from a TDS ERROR (0xAA) or INFO (0xAB) token at *offset*.
41+
42+
Official spec: [MS-TDS] Tabular Data Stream Protocol (Microsoft Learn).
43+
Token layout per MS-TDS 2.2.7.9 (INFO) / 2.2.7.10 (ERROR):
44+
TokenType BYTE 0xAA | 0xAB
45+
Length USHORT LE byte count of the remaining fields
46+
Number LONG LE error / info number
47+
State BYTE
48+
Class BYTE severity
49+
MsgText US_VARCHAR (2-byte LE length prefix + UTF-16LE)
50+
... (ServerName, ProcName, LineNumber follow but are unused here)
51+
52+
The minimum *Length* value for a valid token is 8: Number(4) + State(1) +
53+
Class(1) + MsgText length prefix(2, may be zero-length string).
54+
55+
References (Microsoft Learn, MS-TDS):
56+
INFO: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/284bb815-d083-4ed5-b33a-bdc2492e322b
57+
ERROR: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/9805e9fa-1f8b-4cf8-8f78-8d2602228635
58+
Data packet stream tokens: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/f79bb5b8-5919-439a-a696-48064b78b091
59+
"""
60+
remaining = len(data) - offset
61+
if remaining < 3 or data[offset] not in (TDS_ERROR_TOKEN, TDS_INFO_TOKEN):
62+
return None
63+
64+
# Length (USHORT LE) after TokenType, see MS-TDS INFO/ERROR links in docstring
65+
payload_len = int.from_bytes(data[offset + 1 : offset + 3], "little")
66+
67+
if payload_len < 8 or remaining < 3 + payload_len:
68+
return None
69+
try:
70+
token = TDS_INFO_ERROR(data[offset:])
71+
text = token["MsgText"].decode("utf-16le").strip()
72+
except Exception:
73+
return None
74+
return text or None
75+
76+
77+
def login7_integrated_auth_error_message(packet_data, data_after_login_header):
78+
"""Scan raw LOGIN7 response buffers for the first ERROR/INFO message.
79+
80+
When a server does not support Integrated Windows Authentication it replies
81+
to the LOGIN7 NTLMSSP negotiate with a TDS error token instead of an
82+
NTLMSSP challenge. This helper locates the first ERROR (0xAA) or INFO
83+
(0xAB) token in either the full packet or the payload after the 3-byte
84+
LOGIN7 response header and returns its MsgText.
85+
"""
86+
token_markers = (TDS_ERROR_TOKEN, TDS_INFO_TOKEN)
87+
for buf in filter(None, (packet_data, data_after_login_header)):
88+
for offset in (i for i in range(len(buf)) if buf[i] in token_markers):
89+
msg = decode_tds_info_error_msgtext(buf, offset)
90+
if msg:
91+
return msg
92+
return None

0 commit comments

Comments
 (0)