dploot 3.2.0__py2.py3-none-any.whl

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 (50) hide show
  1. dploot/__init__.py +0 -0
  2. dploot/action/__init__.py +0 -0
  3. dploot/action/backupkey.py +102 -0
  4. dploot/action/blob.py +167 -0
  5. dploot/action/browser.py +192 -0
  6. dploot/action/certificates.py +146 -0
  7. dploot/action/cng.py +129 -0
  8. dploot/action/credentials.py +132 -0
  9. dploot/action/machinecertificates.py +136 -0
  10. dploot/action/machinecng.py +120 -0
  11. dploot/action/machinecredentials.py +120 -0
  12. dploot/action/machinemasterkeys.py +117 -0
  13. dploot/action/machinetriage.py +158 -0
  14. dploot/action/machinevaults.py +127 -0
  15. dploot/action/masterkeys.py +200 -0
  16. dploot/action/mobaxterm.py +138 -0
  17. dploot/action/rdg.py +132 -0
  18. dploot/action/sccm.py +125 -0
  19. dploot/action/triage.py +187 -0
  20. dploot/action/vaults.py +132 -0
  21. dploot/action/wam.py +133 -0
  22. dploot/action/wifi.py +137 -0
  23. dploot/entry.py +98 -0
  24. dploot/lib/__init__.py +0 -0
  25. dploot/lib/consts.py +30 -0
  26. dploot/lib/crypto.py +415 -0
  27. dploot/lib/dpapi.py +302 -0
  28. dploot/lib/masterkey.py +75 -0
  29. dploot/lib/smb.py +517 -0
  30. dploot/lib/target.py +219 -0
  31. dploot/lib/utils.py +99 -0
  32. dploot/lib/wmi.py +61 -0
  33. dploot/triage/__init__.py +30 -0
  34. dploot/triage/backupkey.py +81 -0
  35. dploot/triage/browser.py +441 -0
  36. dploot/triage/certificates.py +444 -0
  37. dploot/triage/cng.py +165 -0
  38. dploot/triage/credentials.py +163 -0
  39. dploot/triage/masterkeys.py +279 -0
  40. dploot/triage/mobaxterm.py +653 -0
  41. dploot/triage/rdg.py +249 -0
  42. dploot/triage/sccm.py +302 -0
  43. dploot/triage/vaults.py +307 -0
  44. dploot/triage/wam.py +251 -0
  45. dploot/triage/wifi.py +424 -0
  46. dploot-3.2.0.dist-info/METADATA +865 -0
  47. dploot-3.2.0.dist-info/RECORD +50 -0
  48. dploot-3.2.0.dist-info/WHEEL +4 -0
  49. dploot-3.2.0.dist-info/entry_points.txt +3 -0
  50. dploot-3.2.0.dist-info/licenses/LICENSE +21 -0
dploot/lib/smb.py ADDED
@@ -0,0 +1,517 @@
1
+ import ntpath
2
+ import os
3
+ import logging
4
+ import time
5
+
6
+ from pathlib import Path
7
+ from typing import Any, Dict, List, Optional
8
+
9
+ from dploot.lib.target import Target
10
+ from dploot.lib.consts import FalsePositives
11
+
12
+ from impacket.smbconnection import SMBConnection
13
+ from impacket.winregistry import Registry
14
+ from impacket.smb import ATTR_DIRECTORY
15
+ from impacket.smb import SMB_DIALECT
16
+ from impacket.smb import SharedFile
17
+ from impacket.nmb import NetBIOSTimeout
18
+ from impacket.smb import FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_SHARE_DELETE
19
+ from impacket.dcerpc.v5 import tsts
20
+ from impacket.examples.secretsdump import RemoteOperations, LocalOperations
21
+ from impacket.smb3structs import (
22
+ FILE_READ_DATA,
23
+ FILE_OPEN,
24
+ FILE_NON_DIRECTORY_FILE
25
+ )
26
+
27
+ from dploot.lib.wmi import DPLootWmiExec
28
+
29
+ class DPLootSMBConnection:
30
+ # if called with target = LOCAL, return an instance of DPLootLocalSMConnection,
31
+ # else return an instance of DPLootRemoteSMBConnection
32
+ def __new__(
33
+ cls, target=None
34
+ ) -> "DPLootRemoteSMBConnection | DPLootLocalSMBConnection":
35
+ if (
36
+ target is not None
37
+ and target.address.upper() == "LOCAL"
38
+ and cls.__name__ != DPLootLocalSMBConnection.__name__
39
+ ):
40
+ return DPLootLocalSMBConnection.__new__(DPLootLocalSMBConnection, target)
41
+ elif cls.__name__ == DPLootSMBConnection.__name__:
42
+ return DPLootRemoteSMBConnection.__new__(DPLootRemoteSMBConnection, target)
43
+ else:
44
+ # we end up here when a child class is instantiated.
45
+ return super().__new__(cls)
46
+
47
+ def __init__(self, target: Target, false_positive: List[str] | None = None) -> None:
48
+ self.target = target
49
+ self.remote_ops = None
50
+ self.local_session = None
51
+
52
+ self._usersProfiles = None
53
+
54
+ self.false_positive = FalsePositives(false_positive)
55
+
56
+ def listDirs(self, share: str, dirlist: List[str]) -> Dict[str, Any]:
57
+ result = {}
58
+ for path in dirlist:
59
+ tmp = self.remote_list_dir(share, path=path)
60
+ result[path] = tmp
61
+ return result
62
+
63
+ class DPLootRemoteSMBConnection(DPLootSMBConnection):
64
+ def __init__(self, target: Target) -> None:
65
+ super().__init__(target)
66
+
67
+ self.smb_session = None
68
+ self.smbv1 = False
69
+
70
+ def create_smbv1_conn(self, kdc=""):
71
+ try:
72
+ self.smb_session = SMBConnection(
73
+ kdc or self.target.address,
74
+ kdc or self.target.address,
75
+ None,
76
+ preferredDialect=SMB_DIALECT,
77
+ )
78
+ self.smbv1 = True
79
+ except OSError as e:
80
+ if str(e).find("Connection reset by peer") != -1:
81
+ logging.debug(
82
+ f"SMBv1 might be disabled on {kdc or self.target.address}"
83
+ )
84
+ return False
85
+ except (Exception, NetBIOSTimeout) as e:
86
+ logging.debug(
87
+ f"Error creating SMBv1 connection to {kdc or self.target.address}: {e}"
88
+ )
89
+ return False
90
+
91
+ return True
92
+
93
+ def create_smbv3_conn(self, kdc=""):
94
+ try:
95
+ self.smb_session = SMBConnection(
96
+ kdc or self.target.address,
97
+ kdc or self.target.address,
98
+ None,
99
+ )
100
+ self.smbv1 = False
101
+ except OSError as e:
102
+ if str(e).find("Too many open files") != -1:
103
+ logging.error(
104
+ f"SMBv3 connection error on {kdc or self.target.address}: {e}"
105
+ )
106
+ return False
107
+ except (Exception, NetBIOSTimeout) as e:
108
+ logging.debug(
109
+ f"Error creating SMBv3 connection to {kdc or self.target.address}: {e}"
110
+ )
111
+ return False
112
+
113
+ return True
114
+
115
+ def create_conn_obj(self, kdc=""):
116
+ if self.create_smbv3_conn(kdc) or self.create_smbv1_conn(kdc):
117
+ return True
118
+ logging.debug(
119
+ "Could not create connection object to %s"
120
+ % (kdc or self.target.address)
121
+ )
122
+ return False
123
+
124
+ def connect(self) -> "Any | None":
125
+ try:
126
+ if self.target.do_kerberos:
127
+ # getting hostname
128
+ no_ntlm = False
129
+ if not self.create_conn_obj():
130
+ return None
131
+ try:
132
+ self.smb_session.login("", "")
133
+ except Exception as e:
134
+ if "STATUS_NOT_SUPPORTED" in str(e):
135
+ no_ntlm = True
136
+ hostname = (
137
+ self.smb_session.getServerDNSHostName()
138
+ if not no_ntlm
139
+ else self.target.address
140
+ )
141
+ self.smb_session.close()
142
+ self.target.address = hostname
143
+ logging.debug("Connecting to %s" % self.target.address)
144
+ if not self.create_conn_obj(self.target.address):
145
+ return None
146
+ logging.debug(
147
+ "Authenticating with %s through Kerberos" % self.target.username
148
+ )
149
+ self.smb_session.kerberosLogin(
150
+ user=self.target.username,
151
+ password=self.target.password,
152
+ domain=self.target.domain,
153
+ lmhash=self.target.lmhash,
154
+ nthash=self.target.nthash,
155
+ aesKey=self.target.aesKey,
156
+ kdcHost=self.target.kdcHost,
157
+ useCache=self.target.use_kcache,
158
+ )
159
+ self.target.username = self.smb_session.getCredentials()[0]
160
+ else:
161
+ logging.debug("Connecting to %s" % self.target.address)
162
+ if not self.create_conn_obj():
163
+ return None
164
+ logging.debug(
165
+ "Authenticating with %s through NTLM" % self.target.username
166
+ )
167
+ self.smb_session.login(
168
+ user=self.target.username,
169
+ password=self.target.password,
170
+ domain=self.target.domain,
171
+ lmhash=self.target.lmhash,
172
+ nthash=self.target.nthash,
173
+ )
174
+ except Exception as e:
175
+ if logging.getLogger().level == logging.DEBUG:
176
+ import traceback
177
+
178
+ traceback.print_exc()
179
+ logging.debug(str(e))
180
+ return None
181
+ return self.smb_session
182
+
183
+ def remote_list_dir(self, share, path, wildcard=True) -> "Any | None":
184
+ if wildcard:
185
+ path = ntpath.join(path, "*")
186
+ try:
187
+ return self.smb_session.listPath(
188
+ shareName=share, path=ntpath.normpath(path)
189
+ )
190
+
191
+ except Exception:
192
+ return None
193
+
194
+ def is_admin(self) -> bool:
195
+ try:
196
+ self.smb_session.connectTree("C$")
197
+ is_admin = True
198
+ except Exception:
199
+ is_admin = False
200
+ return is_admin
201
+
202
+ def listPath(self, *args, **kwargs) -> Any:
203
+ return self.smb_session.listPath(*args, **kwargs)
204
+
205
+ def list_users(self, share):
206
+ users_dir_path = "Users\\*"
207
+ directories = self.listPath(
208
+ shareName=share, path=ntpath.normpath(users_dir_path)
209
+ )
210
+ return [d.get_longname() for d in directories if d.get_longname() not in self.false_positive and d.is_directory() > 0]
211
+
212
+ def reconnect(self) -> bool:
213
+ self.smb_session.reconnect()
214
+ if self.remote_ops is not None:
215
+ self.enable_remoteops(force=True)
216
+
217
+ def enable_remoteops(self, force=False) -> None:
218
+ logging.getLogger("impacket").disabled = True
219
+ if self.remote_ops is not None and self.bootkey is not None and not force:
220
+ return
221
+ try:
222
+ self.remote_ops = RemoteOperations(
223
+ self.smb_session, self.target.do_kerberos, self.target.dc_ip
224
+ )
225
+ self.remote_ops.enableRegistry()
226
+ self.bootkey = self.remote_ops.getBootKey()
227
+ except Exception as e:
228
+ logging.error(f"RemoteOperations failed: {e}")
229
+
230
+ def getFile(self, *args, **kwargs) -> "Any | None":
231
+ return self.smb_session.getFile(*args, **kwargs)
232
+
233
+ def readFile(
234
+ self,
235
+ shareName,
236
+ path,
237
+ mode=FILE_OPEN,
238
+ offset=0,
239
+ password=None,
240
+ shareAccessMode=FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
241
+ bypass_shared_violation=False,
242
+ looted_files=None
243
+ ) -> bytes:
244
+ # ToDo: Handle situations where share is password protected
245
+ path = path.replace("/", "\\")
246
+ path = ntpath.normpath(path)
247
+ if len(path) > 0 and path[0] == "\\":
248
+ path = path[1:]
249
+ treeId = self.smb_session.connectTree(shareName)
250
+ fileId = None
251
+
252
+ data = None
253
+
254
+ try:
255
+ fileId = self.smb_session.openFile(
256
+ treeId,
257
+ path,
258
+ FILE_READ_DATA,
259
+ shareAccessMode,
260
+ FILE_NON_DIRECTORY_FILE,
261
+ mode,
262
+ 0,
263
+ )
264
+ fileInfo = self.smb_session.queryInfo(treeId, fileId)
265
+ fileSize = fileInfo["EndOfFile"]
266
+ res = self.smb_session._SMBConnection.getIOCapabilities()
267
+ if (fileSize - offset) < res["MaxReadSize"]:
268
+ # Skip reading 0 bytes files.
269
+ if (fileSize - offset) > 0:
270
+ data = self.smb_session._SMBConnection.read(
271
+ treeId, fileId, offset, fileSize - offset
272
+ )
273
+ else:
274
+ written = 0
275
+ toBeRead = fileSize - offset
276
+ data = b""
277
+ while written < toBeRead:
278
+ bytesRead = self.smb_session._SMBConnection.read(
279
+ treeId, fileId, offset, res["MaxReadSize"]
280
+ )
281
+ written += len(bytesRead)
282
+ offset += len(bytesRead)
283
+ data += bytesRead
284
+ except Exception as e:
285
+ logging.debug(f"Exception occurred while trying to read {path}: {e}")
286
+ if "STATUS_OBJECT_PATH_NOT_FOUND" in str(e) or "STATUS_OBJECT_NAME_NOT_FOUND" in str(e):
287
+ pass
288
+ elif bypass_shared_violation and "STATUS_SHARING_VIOLATION" in str(e):
289
+ wmiexec = DPLootWmiExec(target=self.target)
290
+ command = (
291
+ f'cmd.exe /Q /c esentutl.exe /y "C:\\{path}" /d "C:\\Windows\\Temp\\{wmiexec.output}"'
292
+ )
293
+ wmiexec.run(command)
294
+ time.sleep(1)
295
+ while True:
296
+ try:
297
+ filepath = ntpath.join("Windows\\Temp\\",wmiexec.output)
298
+ data = self.readFile(shareName=shareName, path=filepath)
299
+ break
300
+ except Exception as e:
301
+ if str(e).find("STATUS_SHARING_VIOLATION") >= 0:
302
+ # Output not finished, let's wait
303
+ time.sleep(1)
304
+ self.smb_session.deleteFile(shareName, filepath)
305
+ elif str(e).find("Broken") >= 0:
306
+ logging.debug("Connection broken, trying to recreate it")
307
+ self.reconnect()
308
+ data = self.readFile(
309
+ shareName=shareName,
310
+ path=path,
311
+ mode=mode,
312
+ offset=offset,
313
+ password=password,
314
+ shareAccessMode=shareAccessMode,
315
+ bypass_shared_violation=bypass_shared_violation,
316
+ looted_files=looted_files
317
+ )
318
+ else:
319
+ logging.debug(str(e))
320
+ finally:
321
+ if fileId is not None:
322
+ self.smb_session._SMBConnection.close(treeId, fileId)
323
+ self.smb_session.disconnectTree(treeId)
324
+
325
+ if looted_files is not None and data is not None and data != b"":
326
+ looted_files[os.path.join(*(path.split("\\")))]=data
327
+ return data
328
+
329
+ def perform_taskkill(self, process_name):
330
+ with tsts.LegacyAPI(self.smb_session, self.target.address, self.target.do_kerberos) as legacy:
331
+ handle = legacy.hRpcWinStationOpenServer()
332
+ r = legacy.hRpcWinStationGetAllProcesses(handle)
333
+ if not len(r):
334
+ logging.debug("Could not get process list")
335
+ return
336
+ pid_list = [
337
+ i["UniqueProcessId"]
338
+ for i in r
339
+ if i["ImageName"].lower() == process_name.lower()
340
+ ]
341
+ if not len(pid_list):
342
+ logging.debug(f"No process {process_name} found")
343
+ logging.debug(f"Found {pid_list} pid(s) for process {process_name}")
344
+ for pid in pid_list:
345
+ logging.debug(f"Killing PID {pid}")
346
+ try:
347
+ if legacy.hRpcWinStationTerminateProcess(handle, pid)["ErrorCode"]:
348
+ logging(f"Successfully killed process {pid}")
349
+ else:
350
+ logging(f"Could not kill process {pid}")
351
+ except Exception as e:
352
+ logging.error(f"Error while terminating pid {pid}: {e}")
353
+
354
+
355
+ class DPLootLocalSMBConnection(DPLootSMBConnection):
356
+ systemroot = "C:\\Windows"
357
+ hklm_software_path = r"Windows/System32/config/SOFTWARE"
358
+
359
+ def __init__(self, target=None) -> None:
360
+ super().__init__(target)
361
+ self.local_ops = None
362
+ self.local_session = True
363
+ self.smb_session = DPLootDummySession()
364
+ # the following are functions that should never be called on this class.
365
+ self.enable_remoteops = None
366
+ self.reconnect = None
367
+
368
+
369
+ def connect(self) -> "Any | None":
370
+ return self.smb_session
371
+
372
+ def is_admin(self) -> bool:
373
+ return True
374
+
375
+ def enable_localops(self, systemHive, force=False) -> None:
376
+ if self.local_ops is not None and self.bootkey is not None and not force:
377
+ return
378
+ try:
379
+ self.local_ops = LocalOperations(systemHive)
380
+ self.bootkey = self.local_ops.getBootKey()
381
+ except Exception as e:
382
+ logging.error(f"LocalOperations failed: {e}")
383
+
384
+ # we 'emulate' remote file operations by converting local os.DirEntry() to impacket.SharedFile()
385
+ def _sharedfile_fromdirentry(d: os.DirEntry):
386
+ (filesize, atime, mtime, ctime) = d.stat(follow_symlinks=False)[6:]
387
+ attribs = 0
388
+ if d.is_dir(follow_symlinks=False):
389
+ attribs |= ATTR_DIRECTORY
390
+ return SharedFile(ctime, atime, mtime, filesize, None, attribs, d.name, d.name)
391
+
392
+ SharedFile.fromDirEntry = _sharedfile_fromdirentry
393
+
394
+ def remote_list_dir(self, share, path, wildcard=True) -> list[SharedFile]:
395
+ path = self.get_real_path(path)
396
+ if not wildcard:
397
+ raise NotImplementedError("Not implemented for wildcard == False")
398
+ try:
399
+ result = list(map(SharedFile.fromDirEntry, os.scandir(path)))
400
+ except FileNotFoundError:
401
+ result = []
402
+ return result
403
+
404
+ def list_users(self, share):
405
+ users_dir_path = "Users\\*"
406
+ directories = self.listPath(
407
+ shareName=share, path=ntpath.normpath(users_dir_path)
408
+ )
409
+ return [d.get_longname() for d in directories if d.get_longname() not in self.false_positive and d.is_directory() > 0]
410
+
411
+ def listPath(self, shareName: str = "C$", path: Optional[str] = None, password: Optional[str] = None):
412
+ if path[-2:] == r"\*":
413
+ return self.remote_list_dir(shareName, path[:-2], wildcard=True)
414
+ if path[-1] == "*":
415
+ return self.remote_list_dir(shareName, path[:-1], wildcard=True)
416
+ else:
417
+ raise NotImplementedError("Not implemented for wildcard == False")
418
+
419
+ def getFile(self, *args, **kwargs) -> "Any | None":
420
+ raise NotImplementedError("getFile is not implemented in LOCAL mode")
421
+
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
+ """
433
+ # clean path (remove c:\, /, and current root if already present)
434
+ path=path.removeprefix(self.target.local_root)
435
+ if path[:3].lower() == "c:\\":
436
+ path = path[3:]
437
+ path=path.replace("\\", os.sep).lstrip(os.sep)
438
+
439
+ globok = False
440
+ # The pattern to match does not contain jokers, so Path.glob() should return 0 or 1 match
441
+ try:
442
+ path=next(Path(self.target.local_root).glob(path, case_sensitive=False))
443
+ globok=True
444
+ except (StopIteration, TypeError):
445
+ # StopIteration: path does not exist.
446
+ # TypeError: unexpexted keyword (case_sensitive added in python 3.12)
447
+ # Return a representation of path anyway
448
+ path=os.path.join(self.target.local_root, path)
449
+
450
+ #logging.debug(f"get_real_path: [{globok=}] returning {path}")
451
+ return str(path)
452
+
453
+ def readFile(
454
+ self,
455
+ shareName,
456
+ path,
457
+ mode=FILE_OPEN,
458
+ offset=0,
459
+ password=None,
460
+ shareAccessMode=FILE_SHARE_READ,
461
+ bypass_shared_violation=False,
462
+ looted_files=None
463
+ ) -> bytes:
464
+ data = None
465
+ try:
466
+ with open(self.get_real_path(path), "rb") as f:
467
+ data = f.read()
468
+ except Exception as e:
469
+ logging.debug(f"Exception occurred while trying to read {path}: {e!r}")
470
+
471
+ return data
472
+
473
+ def getUsersProfiles(self) -> dict[str, str] | None:
474
+ """Returns the list of user profiles (from registry) in a dict
475
+
476
+ Each subkey of HKLM/SOFTWARE/Microsoft/Windows NT/CurrentVersion/ProfileList is a user SID,
477
+ and the ProfileImagePath value inside is the path to the user's profile
478
+ :return: dict of user_sid: path_to_profile
479
+
480
+ """
481
+ if self._usersProfiles is not None:
482
+ return self._usersProfiles
483
+
484
+ result = {}
485
+ # open hive
486
+ reg_file_path = self.get_real_path(self.hklm_software_path)
487
+ reg = Registry(reg_file_path, isRemote=False)
488
+
489
+ # open key
490
+ key_path = "Microsoft\\Windows NT\\CurrentVersion\\ProfileList"
491
+ parentKey = reg.findKey(key_path)
492
+ if parentKey is None:
493
+ logging.error(f"Key {key_path} not found in {reg_file_path}")
494
+ return None
495
+
496
+ for user_sid in reg.enumKey(parentKey):
497
+ # get 'ProfileImagePath' value
498
+ (_, path) = reg.getValue(
499
+ ntpath.join(key_path, user_sid, "ProfileImagePath")
500
+ )
501
+ path = (
502
+ path.decode("utf-16le")
503
+ .rstrip("\0")
504
+ .replace(r"%systemroot%", self.systemroot)
505
+ )
506
+ path = ntpath.normpath(path)
507
+ path = self.get_real_path(path)
508
+ # store in result dict
509
+ result[user_sid] = path
510
+
511
+ self._usersProfiles = result
512
+ return self._usersProfiles
513
+
514
+
515
+ class DPLootDummySession:
516
+ def login(*args, **kwargs) -> bool:
517
+ return True