Skip to content

Add symlink support to smbclient #2018

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions impacket/examples/smbclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import cmd
import os
import ntpath
import shlex

from six import PY2
from impacket.dcerpc.v5 import samr, transport, srvs
Expand Down Expand Up @@ -123,6 +124,8 @@ def do_help(self,line):
cat {filename} - reads the filename from the current path
mount {target,path} - creates a mount point from {path} to {target} (admin required)
umount {path} - removes the mount point at {path} without deleting the directory (admin required)
create_symlink {target,path} - creates a symlink from {path} to {target}, can be file or dir, path must exist (admin required)
remove_symlink {path} - removes the symlink at {path} without deleting the directory (admin required)
list_snapshots {path} - lists the vss snapshots for the specified path
info - returns NetrServerInfo main results
who - returns the sessions currently connected at the target host (admin required)
Expand Down Expand Up @@ -664,6 +667,20 @@ def do_umount(self, mountpoint):

self.smb.removeMountPoint(self.tid, mountPath)

def do_create_symlink(self, line):
target, path = shlex.split(line)
target = target.replace('/','\\')
path = path.replace('/','\\')
if not path.startswith('\\'):
path = ntpath.join(self.pwd, path)
self.smb.createSymlink(self.tid, path, target)

def do_remove_symlink(self, line):
path = line.replace('/','\\')
if not path.startswith('\\'):
path = ntpath.join(self.pwd, path)
self.smb.removeSymlink(self.tid, path)

def do_EOF(self, line):
print('Bye!\n')
return True
22 changes: 22 additions & 0 deletions impacket/smb3structs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1296,6 +1296,28 @@ class MOUNT_POINT_REPARSE_GUID_DATA_STRUCTURE(Structure):
("DataBuffer", ":")
)

class SYMLINK_REPARSE_DATA_STRUCTURE(Structure):
structure = (
('ReparseTag', '<L=0xA000000C'),
('ReparseDataLen', '<H=len(self["PathBuffer"]) + 8'),
('Reserved', '<H=0'),
('SubstituteNameOffset', '<H=0'),
('SubstituteNameLength', '<H=0'),
('PrintNameOffset', '<H=0'),
('PrintNameLength', '<H=0'),
('Flags', '<L=0'),
('PathBuffer', ':')
)

class SYMLINK_REPARSE_GUID_DATA_STRUCTURE(Structure):
structure = (
('ReparseTag', '<L=0xA000000C'),
('ReparseDataLen', '<H=len(self["DataBuffer"])'),
('Reserved', '<H=0'),
('ReparseGuid', '16s=""'),
('DataBuffer', ':')
)

class SMB2Ioctl_Response(Structure):
structure = (
('StructureSize','<H=49'),
Expand Down
49 changes: 48 additions & 1 deletion impacket/smbconnection.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
SMB2_IL_IMPERSONATION, SMB2_OPLOCK_LEVEL_NONE, FILE_READ_DATA , FILE_WRITE_DATA, FILE_OPEN, GENERIC_READ, GENERIC_WRITE, \
FILE_OPEN_REPARSE_POINT, MOUNT_POINT_REPARSE_DATA_STRUCTURE, FSCTL_SET_REPARSE_POINT, SMB2_0_IOCTL_IS_FSCTL, \
MOUNT_POINT_REPARSE_GUID_DATA_STRUCTURE, FSCTL_DELETE_REPARSE_POINT, FSCTL_SRV_ENUMERATE_SNAPSHOTS, SRV_SNAPSHOT_ARRAY, \
FILE_SYNCHRONOUS_IO_NONALERT, FILE_READ_EA, FILE_READ_ATTRIBUTES, READ_CONTROL, SYNCHRONIZE, SMB2_DIALECT_311
FILE_SYNCHRONOUS_IO_NONALERT, FILE_READ_EA, FILE_READ_ATTRIBUTES, READ_CONTROL, SYNCHRONIZE, SMB2_DIALECT_311, \
SYMLINK_REPARSE_DATA_STRUCTURE, SYMLINK_REPARSE_GUID_DATA_STRUCTURE


# So the user doesn't need to import smb, the smb3 are already in here
Expand Down Expand Up @@ -900,6 +901,52 @@ def removeMountPoint(self, tid, path):

self.closeFile(tid, fid)

def createSymlink(self, tid, path, target):
"""
creates a symlink from path to target

:param int tid: tree id of current connection
:param string path: file or directory where symlink is created (must already exist)
:param string target: file or directory where symlink will point to (can be inexistent)

:raise SessionError: if error
"""

substitute_name = f'\\??\\{target}'.encode('utf-16le')
print_name = target.encode('utf-16le')
reparse_data = SYMLINK_REPARSE_DATA_STRUCTURE()
reparse_data['PathBuffer'] = print_name + substitute_name
reparse_data['ReparseDataLen'] = len(print_name + substitute_name) + 12
reparse_data['SubstituteNameOffset'] = len(print_name)
reparse_data['SubstituteNameLength'] = len(substitute_name)
reparse_data['PrintNameOffset'] = 0
reparse_data['PrintNameLength'] = len(print_name)

fid = self.openFile(tid, path, GENERIC_READ|GENERIC_WRITE, creationOption=FILE_OPEN_REPARSE_POINT)
try:
self._SMBConnection.ioctl(tid, fid, FSCTL_SET_REPARSE_POINT, flags=SMB2_0_IOCTL_IS_FSCTL, inputBlob=reparse_data)
finally:
self.closeFile(tid, fid)

def removeSymlink(self, tid, path):
"""
removes a symlink without deleting the underlying file or directory

:param int tid: tree id of current connection
:param string path: path to symlink

:raise SessionError: if error
"""

reparse_data = SYMLINK_REPARSE_GUID_DATA_STRUCTURE()
reparse_data['DataBuffer'] = b''

fid = self.openFile(tid, path, GENERIC_READ|GENERIC_WRITE, creationOption=FILE_OPEN_REPARSE_POINT)
try:
self._SMBConnection.ioctl(tid, fid, FSCTL_DELETE_REPARSE_POINT, flags=SMB2_0_IOCTL_IS_FSCTL, inputBlob=reparse_data)
finally:
self.closeFile(tid, fid)

def rename(self, shareName, oldPath, newPath):
"""
renames a file/directory
Expand Down