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.
- dploot/__init__.py +0 -0
- dploot/action/__init__.py +0 -0
- dploot/action/backupkey.py +102 -0
- dploot/action/blob.py +167 -0
- dploot/action/browser.py +192 -0
- dploot/action/certificates.py +146 -0
- dploot/action/cng.py +129 -0
- dploot/action/credentials.py +132 -0
- dploot/action/machinecertificates.py +136 -0
- dploot/action/machinecng.py +120 -0
- dploot/action/machinecredentials.py +120 -0
- dploot/action/machinemasterkeys.py +117 -0
- dploot/action/machinetriage.py +158 -0
- dploot/action/machinevaults.py +127 -0
- dploot/action/masterkeys.py +200 -0
- dploot/action/mobaxterm.py +138 -0
- dploot/action/rdg.py +132 -0
- dploot/action/sccm.py +125 -0
- dploot/action/triage.py +187 -0
- dploot/action/vaults.py +132 -0
- dploot/action/wam.py +133 -0
- dploot/action/wifi.py +137 -0
- dploot/entry.py +98 -0
- dploot/lib/__init__.py +0 -0
- dploot/lib/consts.py +30 -0
- dploot/lib/crypto.py +415 -0
- dploot/lib/dpapi.py +302 -0
- dploot/lib/masterkey.py +75 -0
- dploot/lib/smb.py +517 -0
- dploot/lib/target.py +219 -0
- dploot/lib/utils.py +99 -0
- dploot/lib/wmi.py +61 -0
- dploot/triage/__init__.py +30 -0
- dploot/triage/backupkey.py +81 -0
- dploot/triage/browser.py +441 -0
- dploot/triage/certificates.py +444 -0
- dploot/triage/cng.py +165 -0
- dploot/triage/credentials.py +163 -0
- dploot/triage/masterkeys.py +279 -0
- dploot/triage/mobaxterm.py +653 -0
- dploot/triage/rdg.py +249 -0
- dploot/triage/sccm.py +302 -0
- dploot/triage/vaults.py +307 -0
- dploot/triage/wam.py +251 -0
- dploot/triage/wifi.py +424 -0
- dploot-3.2.0.dist-info/METADATA +865 -0
- dploot-3.2.0.dist-info/RECORD +50 -0
- dploot-3.2.0.dist-info/WHEEL +4 -0
- dploot-3.2.0.dist-info/entry_points.txt +3 -0
- 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
|