Skip to content

Commit 2347874

Browse files
authored
Merge branch 'main' into main
2 parents 92e2b8f + b037969 commit 2347874

23 files changed

Lines changed: 288 additions & 110 deletions

.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.

nxc/connection.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
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)
2728
global_failed_logins = 0
@@ -552,7 +553,7 @@ def login(self):
552553
if self.args.use_kcache:
553554
self.logger.debug("Trying to authenticate using Kerberos cache")
554555
with sem:
555-
username = self.args.username[0] if len(self.args.username) else ""
556+
username = self.args.username[0] if len(self.args.username) else CCache.parseFile()[1]
556557
password = self.args.password[0] if len(self.args.password) else ""
557558
self.kerberos_login(self.domain, username, password, "", "", self.kdcHost, True)
558559
self.logger.info("Successfully authenticated using Kerberos cache")

nxc/modules/add-computer.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,11 +175,12 @@ def do_samr_add(self):
175175
self.noLDAPRequired = True
176176
self.context.log.highlight(f"Successfully added the machine account: '{self.__computerName}' with Password: '{self.__computerPassword}'")
177177
self.context.db.add_credential("plaintext", self.__domain, self.__computerName, self.__computerPassword)
178-
except samr.DCERPCSessionError as e:
178+
except Exception as e:
179179
self.context.log.debug(f"samrCreateUser2InDomain failed: {e}")
180-
if "STATUS_ACCESS_DENIED" in str(e):
180+
# See error codes at: https://github.com/fortra/impacket/blob/8c155a5b492e8b0f9d08e5ca82b72c91d76f5c7f/impacket/dcerpc/v5/samr.py#L2591
181+
if "Authenticating account doesn't have the right to create a new machine account!" in str(e):
181182
self.context.log.fail(f"The following user does not have the right to create a computer account: {self.__username}")
182-
elif "STATUS_DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED" in str(e):
183+
elif "Authenticating account's machine account quota exceeded!" in str(e):
183184
self.context.log.fail(f"The following user exceeded their machine account quota: {self.__username}")
184185
return
185186

nxc/modules/backup_operator.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from impacket.smbconnection import SessionError
66
from impacket.dcerpc.v5 import transport, rrp
77
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
8-
from nxc.helpers.misc import CATEGORY
8+
from nxc.helpers.misc import CATEGORY, gen_random_string
99

1010

1111
class NXCModule:
@@ -26,6 +26,8 @@ def options(self, context, module_options):
2626

2727
def on_login(self, context, connection):
2828
connection.args.share = "SYSVOL"
29+
rand_suffix = gen_random_string(8)
30+
2931
# enable remote registry
3032
context.log.display("Triggering RemoteRegistry to start through named pipe...")
3133
connection.trigger_winreg()
@@ -43,7 +45,7 @@ def on_login(self, context, connection):
4345

4446
for hive in ["HKLM\\SAM", "HKLM\\SYSTEM", "HKLM\\SECURITY"]:
4547
hRootKey, subKey = self._strip_root_key(dce, hive)
46-
outputFileName = f"\\\\{connection.host}\\SYSVOL\\{subKey}"
48+
outputFileName = f"\\\\{connection.host}\\SYSVOL\\{subKey}_{rand_suffix}"
4749
context.log.debug(f"Dumping {hive}, be patient it can take a while for large hives (e.g. HKLM\\SYSTEM)")
4850
try:
4951
ans2 = rrp.hBaseRegOpenKey(dce, hRootKey, subKey, dwOptions=rrp.REG_OPTION_BACKUP_RESTORE | rrp.REG_OPTION_OPEN_LINK, samDesired=rrp.KEY_READ)
@@ -62,7 +64,7 @@ def on_login(self, context, connection):
6264
# copy remote file to local
6365
log_path = f"{connection.output_filename}."
6466
for hive in ["SAM", "SECURITY", "SYSTEM"]:
65-
connection.get_file_single(hive, log_path + hive)
67+
connection.get_file_single(f"{hive}_{rand_suffix}", log_path + hive)
6668

6769
# read local file
6870
try:
@@ -97,24 +99,25 @@ def parse_sam(secret):
9799
context.log.fail(f"Fail to dump the NTDS: {e!s}")
98100

99101
context.log.display(f"Cleaning dump with user {self.domain_admin} and hash {self.domain_admin_hash} on domain {connection.domain}")
100-
connection.execute("del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM")
102+
connection.execute(f"del C:\\Windows\\sysvol\\sysvol\\SECURITY_{rand_suffix} && del C:\\Windows\\sysvol\\sysvol\\SAM_{rand_suffix} && del C:\\Windows\\sysvol\\sysvol\\SYSTEM_{rand_suffix}")
101103
sleep(0.2)
102104
for hive in ["SAM", "SECURITY", "SYSTEM"]:
105+
remote_name = f"{hive}_{rand_suffix}"
103106
try:
104-
out = connection.conn.listPath("SYSVOL", hive)
107+
out = connection.conn.listPath("SYSVOL", remote_name)
105108
if out:
106109
self.deleted_files = False
107-
context.log.fail(f"Fail to remove the file {hive}, path: C:\\Windows\\sysvol\\sysvol\\{hive}")
110+
context.log.fail(f"Fail to remove the file {remote_name}, path: C:\\Windows\\sysvol\\sysvol\\{remote_name}")
108111
except SessionError as e:
109-
context.log.debug(f"File {hive} successfully removed: {e}")
112+
context.log.debug(f"File {remote_name} successfully removed: {e}")
110113
else:
111114
self.deleted_files = False
112115
else:
113116
self.deleted_files = False
114117

115118
if not self.deleted_files:
116119
context.log.display("Use the domain admin account to clean the file on the remote host")
117-
context.log.display("netexec smb dc_ip -u user -p pass -x \"del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM\"") # noqa: Q003
120+
context.log.display(f"netexec smb dc_ip -u user -p pass -x \"del C:\\Windows\\sysvol\\sysvol\\SECURITY_{rand_suffix} && del C:\\Windows\\sysvol\\sysvol\\SAM_{rand_suffix} && del C:\\Windows\\sysvol\\sysvol\\SYSTEM_{rand_suffix}\"") # noqa: Q003
118121
else:
119122
context.log.display("Successfully deleted dump files !")
120123

nxc/modules/certipy-find.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,12 @@ def on_login(self, context, connection):
7575
username=connection.username,
7676
password=connection.password,
7777
remote_name=connection.remoteName,
78+
hashes=f"{connection.lmhash}:{connection.nthash}",
7879
lmhash=connection.lmhash,
7980
nthash=connection.nthash,
8081
do_kerberos=connection.kerberos,
82+
aes=connection.aesKey,
83+
dc_ip=connection.kdcHost,
8184
target_ip=connection.host,
8285
ldap_port=connection.port,
8386
ldap_scheme="ldaps" if connection.port == 636 else "ldap",

nxc/modules/coerce_plus.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -212,14 +212,14 @@ def on_login(self, context, connection):
212212
context.log.error("Invalid method, please check the method name.")
213213
return
214214

215-
@staticmethod
216-
def get_dynamic_endpoint(interface: bytes, target: str, timeout: int = 5) -> str:
217-
string_binding = rf"ncacn_ip_tcp:{target}[135]"
218-
rpctransport = transport.DCERPCTransportFactory(string_binding)
219-
rpctransport.set_connect_timeout(timeout)
220-
dce = rpctransport.get_dce_rpc()
221-
dce.connect()
222-
return epm.hept_map(target, interface, protocol="ncacn_ip_tcp", dce=dce)
215+
216+
def get_dynamic_endpoint(interface: bytes, target: str, timeout: int = 5) -> str:
217+
string_binding = rf"ncacn_ip_tcp:{target}[135]"
218+
rpctransport = transport.DCERPCTransportFactory(string_binding)
219+
rpctransport.set_connect_timeout(timeout)
220+
dce = rpctransport.get_dce_rpc()
221+
dce.connect()
222+
return epm.hept_map(target, interface, protocol="ncacn_ip_tcp", dce=dce)
223223

224224

225225
class ShadowCoerceTrigger:
@@ -541,7 +541,7 @@ def connect(self, username, password, domain, lmhash, nthash, aesKey, target, do
541541
# activates EFS
542542
# https://specterops.io/blog/2025/08/19/will-webclient-start/
543543
with contextlib.suppress(Exception):
544-
NXCModule.get_dynamic_endpoint(uuidtup_to_bin(("df1941c5-fe89-4e79-bf10-463657acf44d", "0.0")), target, timeout=1)
544+
get_dynamic_endpoint(uuidtup_to_bin(("df1941c5-fe89-4e79-bf10-463657acf44d", "0.0")), target, timeout=1)
545545

546546
rpctransport = transport.DCERPCTransportFactory(binding_params[pipe]["stringBinding"])
547547
rpctransport.set_dport(445)
@@ -778,11 +778,12 @@ def connect(self, username, password, domain, lmhash, nthash, aesKey, target, do
778778
"port": 445
779779
},
780780
"[dcerpc]": {
781-
"stringBinding": NXCModule.get_dynamic_endpoint(uuidtup_to_bin(("12345678-1234-abcd-ef00-0123456789ab", "1.0")), target),
782781
"MSRPC_UUID_RPRN": ("12345678-1234-abcd-ef00-0123456789ab", "1.0"),
783782
"port": None
784783
}
785784
}
785+
if pipe == "[dcerpc]":
786+
binding_params["[dcerpc]"]["stringBinding"] = get_dynamic_endpoint(uuidtup_to_bin(("12345678-1234-abcd-ef00-0123456789ab", "1.0")), target)
786787
rpctransport = transport.DCERPCTransportFactory(binding_params[pipe]["stringBinding"])
787788
if binding_params[pipe]["port"] is not None:
788789
rpctransport.set_dport(binding_params[pipe]["port"])

nxc/modules/get-network.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
# Credit to https://github.com/dirkjanm/adidnsdump @_dirkjan
33
# module by @mpgn_x64
44
import re
5-
import codecs
65
import socket
76
from datetime import datetime
87
from struct import unpack
@@ -154,12 +153,12 @@ def on_login(self, context, connection):
154153

155154
context.log.highlight(f"Found {len(outdata)} records")
156155
path = expanduser(f"{NXC_PATH}/logs/{connection.domain}_network_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.log")
157-
with codecs.open(path, "w", "utf-8") as outfile:
156+
with open(path, "w") as outfile:
158157
for row in outdata:
159158
if self.showhosts:
160-
outfile.write(f"{row['name'] + '.' + connection.domain}\n")
159+
outfile.write(f"{row['name'] + '.' + zone}\n")
161160
elif self.showall:
162-
outfile.write(f"{row['name'] + '.' + connection.domain} \t {row['value']}\n")
161+
outfile.write(f"{row['name'] + '.' + zone} \t {row['value']}\n")
163162
else:
164163
outfile.write(f"{row['value']}\n")
165164
context.log.success(f"Dumped {len(outdata)} records to {path}")

0 commit comments

Comments
 (0)