-
-
Notifications
You must be signed in to change notification settings - Fork 42
/
Copy pathDumpSMBShare.py
executable file
·230 lines (200 loc) · 10.5 KB
/
DumpSMBShare.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# File name : DumpSMBShare.py
# Author : Podalirius (@podalirius_)
# Date created : 6 Jul 2022
import argparse
import os
import sys
import traceback
from impacket import version
from impacket.examples import logger, utils
from impacket.smbconnection import SMBConnection, SMB2_DIALECT_002, SMB2_DIALECT_21, SMB_DIALECT, SessionError
class BFSDumpShare(object):
"""docstring for BFSDumpShare."""
def __init__(self, smb, share, base_dir="", dump_dir=".", quiet=False, debug=False, only_list_files=False):
super(BFSDumpShare, self).__init__()
self.quiet = quiet
self.debug = debug
self.smb = smb
self.share = share
self.dump_dir = dump_dir
self.base_dir = base_dir
self.only_list_files = only_list_files
if not os.path.exists(self.dump_dir):
os.makedirs(self.dump_dir, exist_ok=True)
def list_shares(self):
print("[>] Listing shares ...")
resp = self.smb.listShares()
shares = []
for k in range(len(resp)):
shares.append(resp[k]["shi1_netname"][:-1])
return shares
def dump_share(self, targetfile="", extensions=[], base_dir=None):
if base_dir is not None:
self.base_dir = base_dir
print("[>] Dumping files with extensions %s ... " % extensions)
# Breadth-first search algorithm to recursively find .extension files
files = []
searchdirs = [self.base_dir + "/"]
while len(searchdirs) != 0:
next_dirs = []
for sdir in searchdirs:
if self.debug:
print("[>] Searching in %s " % sdir)
try:
for sharedfile in self.smb.listPath(self.share, sdir + "*", password=None):
if sharedfile.get_longname() not in [".", ".."]:
if sharedfile.is_directory():
if self.debug:
print("[>] Found directory %s/" % sharedfile.get_longname())
next_dirs.append(sdir + sharedfile.get_longname() + "/")
else:
if len(extensions) == 0 or any([sharedfile.get_longname().endswith("." + e) for e in extensions]) or sharedfile.get_longname() == targetfile:
if self.debug or not self.quiet or self.only_list_files:
print("[>] Found matching file %s" % (sdir + sharedfile.get_longname()))
full_path = sdir + sharedfile.get_longname()
files.append(full_path)
if not self.only_list_files:
self.dump_file(full_path)
else:
if self.debug:
print("[>] Found file %s" % sharedfile.get_longname())
except SessionError as e:
if self.debug:
print("[error] %s " % e)
searchdirs = next_dirs
if self.debug:
print("[>] Next iteration with %d folders." % len(next_dirs))
return files
def dump_file(self, filepath, only_file=False):
# Sanitize dir
filepath = filepath.replace("\\", "/")
_dir, _file = os.path.dirname(filepath), os.path.basename(filepath)
if _dir.startswith("//"):
_dir = _dir[2:]
try:
if only_file:
if not os.path.exists(self.dump_dir):
os.makedirs(self.dump_dir, exist_ok=True)
path = self.dump_dir + "/" + _file
else:
# Create directory
if _dir.startswith(self.base_dir.rstrip('/')):
_dir = _dir[len(self.base_dir.rstrip('/')):].lstrip('/')
if not os.path.exists(self.dump_dir + "/" + _dir + "/"):
os.makedirs(self.dump_dir + "/" + _dir + "/", exist_ok=True)
path = self.dump_dir + "/" + _dir + "/" + _file
# Write file
f = open(path, "wb")
self.smb.getFile(self.share, filepath, f.write)
f.close()
return True
except SessionError as e:
if self.debug:
print("[error] %s" % e)
return False
except Exception as e:
raise
def parse_args():
print("DumpSMBShare v1.3 - by Remi GASCOU (Podalirius)\n")
parser = argparse.ArgumentParser(add_help=True, description="A script to dump files and folders remotely from a Windows SMB share.")
parser.add_argument("target", action="store", help="[[domain/]username[:password]@]<targetName or address>")
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("-s", "--share", type=str, default=None, help="SMB Share to dump")
group.add_argument("-l", "--list-shares", default=False, action="store_true", help="Lists SMB shares on the remote machine.")
parser.add_argument("-L", "--list-files", default=False, action="store_true", help="Lists all the files present in the SMB share.")
parser.add_argument("-e", "--extensions", type=str, required=False, default="", help="Extensions")
parser.add_argument("-D", "--dump-dir", type=str, required=False, default=None, help="Dump directory")
parser.add_argument("-f", "--file", type=str, default=None, help="SMB file to dump")
parser.add_argument("-B", "--base-dir", type=str, required=False, default="", help="Directory to search in (Default: /)")
parser.add_argument("--debug", action="store_true", help="Turn on debug output. (Default: False)")
parser.add_argument("-q", "--quiet", action="store_true", default=False, help="Turn DEBUG output ON")
group = parser.add_argument_group("authentication")
group.add_argument("-H", "--hashes", action="store", metavar="LMHASH:NTHASH", help="NTLM hashes, format is LMHASH:NTHASH")
group.add_argument("--no-pass", action="store_true", help="Don't ask for password (useful for -k)")
group.add_argument("-k", "--kerberos", action="store_true", help="Use Kerberos authentication. Grabs credentials from ccache file (KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use the ones specified in the command line")
group.add_argument("-A", "--aesKey", action="store", metavar="hex key", help="AES key to use for Kerberos Authentication (128 or 256 bits)")
group = parser.add_argument_group("connection")
group.add_argument("--dc-ip", action="store", metavar="ip address", help="IP Address of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter")
group.add_argument("-I", "--target-ip", action="store", metavar="ip address", help="IP Address of the target machine. If omitted it will use whatever was specified as target. This is useful when target is the NetBIOS name and you cannot resolve it")
group.add_argument("-P", "--port", choices=["139", "445"], nargs="?", default="445", metavar="destination port", help="Destination port to connect to SMB Server")
if len(sys.argv) == 1:
parser.print_help()
sys.exit(1)
return parser.parse_args()
def parse_target(args):
domain, username, password, address = utils.parse_target(args.target)
if args.target_ip is None:
args.target_ip = address
if domain is None:
domain = ""
if password == "" and username != "" and args.hashes is None and args.no_pass is False and args.aesKey is None:
from getpass import getpass
password = getpass("Password:")
if args.aesKey is not None:
args.k = True
if args.hashes is not None:
lmhash, nthash = args.hashes.split(":")
else:
lmhash = ""
nthash = ""
return domain, username, password, address, lmhash, nthash
def init_smb_session(args, domain, username, password, address, lmhash, nthash):
smbClient = SMBConnection(address, args.target_ip, sess_port=int(args.port))
dialect = smbClient.getDialect()
if dialect == SMB_DIALECT:
if args.debug:
print("[>] SMBv1 dialect used")
elif dialect == SMB2_DIALECT_002:
if args.debug:
print("[>] SMBv2.0 dialect used")
elif dialect == SMB2_DIALECT_21:
if args.debug:
print("[>] SMBv2.1 dialect used")
else:
if args.debug:
print("[>] SMBv3.0 dialect used")
if args.kerberos is True:
smbClient.kerberosLogin(username, password, domain, lmhash, nthash, args.aesKey, args.dc_ip)
else:
smbClient.login(username, password, domain, lmhash, nthash)
if smbClient.isGuestSession() > 0:
if args.debug:
print("[>] GUEST Session Granted")
else:
if args.debug:
print("[>] USER Session Granted")
return smbClient
if __name__ == "__main__":
args = parse_args()
args.extensions = [e.strip() for e in args.extensions.strip().split(",") if len(e.strip()) != 0]
domain, username, password, address, lmhash, nthash = parse_target(args)
try:
smbClient = init_smb_session(args, domain, username, password, address, lmhash, nthash)
if args.list_shares:
if args.dump_dir is None:
g = BFSDumpShare(smbClient, args.share)
else:
g = BFSDumpShare(smbClient, args.share, dump_dir=args.dump_dir)
shares = g.list_shares()
for s in shares:
print(" - %s" % s)
print()
else:
if args.dump_dir is None:
args.dump_dir = "./%s/%s/" % (address, args.share)
g = BFSDumpShare(smbClient, args.share, base_dir=args.base_dir, dump_dir=args.dump_dir, quiet=args.quiet, debug=args.debug, only_list_files=args.list_files)
if args.share in g.list_shares():
if args.file is not None:
print("[+] Dumping file '%s' from share '%s'" % (args.file, args.share))
g.base_dir = os.path.basename(args.base_dir)
g.dump_file(args.file, only_file=True)
else:
dumped_files = g.dump_share(extensions=args.extensions)
if not args.list_files:
print("[+] Dumped %d files from share '%s'" % (len(dumped_files), args.share))
else:
print("[>] Cannot find share '%s'" % args.share)
except Exception as e:
raise e