rimossi cloudscraper, simplejson e torrentool, aggiornato sambatools

This commit is contained in:
marco
2020-12-26 14:37:12 +01:00
parent 483fab34df
commit e755d71127
147 changed files with 21555 additions and 17142 deletions

View File

@@ -0,0 +1,144 @@
import os, logging, random, socket, time, select
from .base import NBNS, NotConnectedError
from .nmb_constants import TYPE_CLIENT, TYPE_SERVER, TYPE_WORKSTATION
class NetBIOS(NBNS):
log = logging.getLogger('NMB.NetBIOS')
def __init__(self, broadcast = True, listen_port = 0):
"""
Instantiate a NetBIOS instance, and creates a IPv4 UDP socket to listen/send NBNS packets.
:param boolean broadcast: A boolean flag to indicate if we should setup the listening UDP port in broadcast mode
:param integer listen_port: Specifies the UDP port number to bind to for listening. If zero, OS will automatically select a free port number.
"""
self.broadcast = broadcast
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
if self.broadcast:
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
if listen_port:
self.sock.bind(( '', listen_port ))
def close(self):
"""
Close the underlying and free resources.
The NetBIOS instance should not be used to perform any operations after this method returns.
:return: None
"""
self.sock.close()
self.sock = None
def write(self, data, ip, port):
assert self.sock, 'Socket is already closed'
self.sock.sendto(data, ( ip, port ))
def queryName(self, name, ip = '', port = 137, timeout = 30):
"""
Send a query on the network and hopes that if machine matching the *name* will reply with its IP address.
:param string ip: If the NBNSProtocol instance was instianted with broadcast=True, then this parameter can be an empty string. We will leave it to the OS to determine an appropriate broadcast address.
If the NBNSProtocol instance was instianted with broadcast=False, then you should provide a target IP to send the query.
:param integer port: The NetBIOS-NS port (IANA standard defines this port to be 137). You should not touch this parameter unless you know what you are doing.
:param integer/float timeout: Number of seconds to wait for a reply, after which the method will return None
:return: A list of IP addresses in dotted notation (aaa.bbb.ccc.ddd). On timeout, returns None.
"""
assert self.sock, 'Socket is already closed'
trn_id = random.randint(1, 0xFFFF)
data = self.prepareNameQuery(trn_id, name)
if self.broadcast and not ip:
ip = '<broadcast>'
elif not ip:
self.log.warning('queryName: ip parameter is empty. OS might not transmit this query to the network')
self.write(data, ip, port)
return self._pollForNetBIOSPacket(trn_id, timeout)
def queryIPForName(self, ip, port = 137, timeout = 30):
"""
Send a query to the machine with *ip* and hopes that the machine will reply back with its name.
The implementation of this function is contributed by Jason Anderson.
:param string ip: If the NBNSProtocol instance was instianted with broadcast=True, then this parameter can be an empty string. We will leave it to the OS to determine an appropriate broadcast address.
If the NBNSProtocol instance was instianted with broadcast=False, then you should provide a target IP to send the query.
:param integer port: The NetBIOS-NS port (IANA standard defines this port to be 137). You should not touch this parameter unless you know what you are doing.
:param integer/float timeout: Number of seconds to wait for a reply, after which the method will return None
:return: A list of string containing the names of the machine at *ip*. On timeout, returns None.
"""
assert self.sock, 'Socket is already closed'
trn_id = random.randint(1, 0xFFFF)
data = self.prepareNetNameQuery(trn_id, False)
self.write(data, ip, port)
ret = self._pollForQueryPacket(trn_id, timeout)
if ret:
return list(map(lambda s: s[0], filter(lambda s: s[1] == TYPE_SERVER, ret)))
else:
return None
#
# Protected Methods
#
def _pollForNetBIOSPacket(self, wait_trn_id, timeout):
end_time = time.time() - timeout
while True:
try:
_timeout = time.time()-end_time
if _timeout <= 0:
return None
ready, _, _ = select.select([ self.sock.fileno() ], [ ], [ ], _timeout)
if not ready:
return None
data, _ = self.sock.recvfrom(0xFFFF)
if len(data) == 0:
raise NotConnectedError
trn_id, ret = self.decodePacket(data)
if trn_id == wait_trn_id:
return ret
except select.error as ex:
if type(ex) is tuple:
if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
raise ex
else:
raise ex
#
# Contributed by Jason Anderson
#
def _pollForQueryPacket(self, wait_trn_id, timeout):
end_time = time.time() - timeout
while True:
try:
_timeout = time.time()-end_time
if _timeout <= 0:
return None
ready, _, _ = select.select([ self.sock.fileno() ], [ ], [ ], _timeout)
if not ready:
return None
data, _ = self.sock.recvfrom(0xFFFF)
if len(data) == 0:
raise NotConnectedError
trn_id, ret = self.decodeIPQueryPacket(data)
if trn_id == wait_trn_id:
return ret
except select.error as ex:
if type(ex) is tuple:
if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
raise ex
else:
raise ex

View File

@@ -0,0 +1,134 @@
import os, logging, random, socket, time
from twisted.internet import reactor, defer
from twisted.internet.protocol import DatagramProtocol
from .base import NBNS
class NetBIOSTimeout(Exception):
"""Raised in NBNSProtocol via Deferred.errback method when queryName method has timeout waiting for reply"""
pass
class NBNSProtocol(DatagramProtocol, NBNS):
log = logging.getLogger('NMB.NBNSProtocol')
def __init__(self, broadcast = True, listen_port = 0):
"""
Instantiate a NBNSProtocol instance.
This automatically calls reactor.listenUDP method to start listening for incoming packets, so you **must not** call the listenUDP method again.
:param boolean broadcast: A boolean flag to indicate if we should setup the listening UDP port in broadcast mode
:param integer listen_port: Specifies the UDP port number to bind to for listening. If zero, OS will automatically select a free port number.
"""
self.broadcast = broadcast
self.pending_trns = { } # TRN ID -> ( expiry_time, name, Deferred instance )
self.transport = reactor.listenUDP(listen_port, self)
if self.broadcast:
self.transport.getHandle().setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
reactor.callLater(1, self.cleanupPendingTrns)
def datagramReceived(self, data, from_info):
host, port = from_info
trn_id, ret = self.decodePacket(data)
# pending transaction exists for trn_id - handle it and remove from queue
if trn_id in self.pending_trns:
_, ip, d = self.pending_trns.pop(trn_id)
if ip is NAME_QUERY:
# decode as query packet
trn_id, ret = self.decodeIPQueryPacket(data)
d.callback(ret)
def write(self, data, ip, port):
# We don't use the transport.write method directly as it keeps raising DeprecationWarning for ip='<broadcast>'
self.transport.getHandle().sendto(data, ( ip, port ))
def queryName(self, name, ip = '', port = 137, timeout = 30):
"""
Send a query on the network and hopes that if machine matching the *name* will reply with its IP address.
:param string ip: If the NBNSProtocol instance was instianted with broadcast=True, then this parameter can be an empty string. We will leave it to the OS to determine an appropriate broadcast address.
If the NBNSProtocol instance was instianted with broadcast=False, then you should provide a target IP to send the query.
:param integer port: The NetBIOS-NS port (IANA standard defines this port to be 137). You should not touch this parameter unless you know what you are doing.
:param integer/float timeout: Number of seconds to wait for a reply, after which the returned Deferred instance will be called with a NetBIOSTimeout exception.
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of IP addresses in dotted notation (aaa.bbb.ccc.ddd).
On timeout, the errback function will be called with a Failure instance wrapping around a NetBIOSTimeout exception
"""
trn_id = random.randint(1, 0xFFFF)
while True:
if trn_id not in self.pending_trns:
break
else:
trn_id = (trn_id + 1) & 0xFFFF
data = self.prepareNameQuery(trn_id, name)
if self.broadcast and not ip:
ip = '<broadcast>'
elif not ip:
self.log.warning('queryName: ip parameter is empty. OS might not transmit this query to the network')
self.write(data, ip, port)
d = defer.Deferred()
self.pending_trns[trn_id] = ( time.time()+timeout, name, d )
return d
def queryIPForName(self, ip, port = 137, timeout = 30):
"""
Send a query to the machine with *ip* and hopes that the machine will reply back with its name.
The implementation of this function is contributed by Jason Anderson.
:param string ip: If the NBNSProtocol instance was instianted with broadcast=True, then this parameter can be an empty string. We will leave it to the OS to determine an appropriate broadcast address.
If the NBNSProtocol instance was instianted with broadcast=False, then you should provide a target IP to send the query.
:param integer port: The NetBIOS-NS port (IANA standard defines this port to be 137). You should not touch this parameter unless you know what you are doing.
:param integer/float timeout: Number of seconds to wait for a reply, after which the method will return None
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of names of the machine at *ip*.
On timeout, the errback function will be called with a Failure instance wrapping around a NetBIOSTimeout exception
"""
trn_id = random.randint(1, 0xFFFF)
while True:
if trn_id not in self.pending_trns:
break
else:
trn_id = (trn_id + 1) & 0xFFFF
data = self.prepareNetNameQuery(trn_id)
self.write(data, ip, port)
d = defer.Deferred()
d2 = defer.Deferred()
d2.addErrback(d.errback)
def stripCode(ret):
if ret is not None: # got valid response. Somehow the callback is also called when there is an error.
d.callback(map(lambda s: s[0], filter(lambda s: s[1] == TYPE_SERVER, ret)))
d2.addCallback(stripCode)
self.pending_trns[trn_id] = ( time.time()+timeout, NAME_QUERY, d2 )
return d
def stopProtocol(self):
DatagramProtocol.stopProtocol(self)
def cleanupPendingTrns(self):
now = time.time()
# reply should have been received in the past
# info is tuple of ( expiry_time, name, d )
expired = filter(lambda trn_id, info: info[0] < now, self.pending_trns.iteritems())
# remove expired items from dict + call errback
def expire_item(item):
trn_id, (expiry_time, name, d) = item
del self.pending_trns[trn_id]
try:
d.errback(NetBIOSTimeout(name))
except: pass
map(expire_item, expired)
if self.transport:
reactor.callLater(1, self.cleanupPendingTrns)

View File

View File

@@ -0,0 +1,187 @@
import struct, logging, random
from .nmb_constants import *
from .nmb_structs import *
from .utils import encode_name
class NMBSession:
log = logging.getLogger('NMB.NMBSession')
def __init__(self, my_name, remote_name, host_type = TYPE_SERVER, is_direct_tcp = False):
self.my_name = my_name.upper()
self.remote_name = remote_name.upper()
self.host_type = host_type
self.data_buf = b''
if is_direct_tcp:
self.data_nmb = DirectTCPSessionMessage()
self.sendNMBPacket = self._sendNMBPacket_DirectTCP
else:
self.data_nmb = NMBSessionMessage()
self.sendNMBPacket = self._sendNMBPacket_NetBIOS
#
# Overridden Methods
#
def write(self, data):
raise NotImplementedError
def onNMBSessionMessage(self, flags, data):
pass
def onNMBSessionOK(self):
pass
def onNMBSessionFailed(self):
pass
#
# Public Methods
#
def feedData(self, data):
self.data_buf = self.data_buf + data
offset = 0
while True:
length = self.data_nmb.decode(self.data_buf, offset)
if length == 0:
break
elif length > 0:
offset += length
self._processNMBSessionPacket(self.data_nmb)
else:
raise NMBError
if offset > 0:
self.data_buf = self.data_buf[offset:]
def sendNMBMessage(self, data):
self.sendNMBPacket(SESSION_MESSAGE, data)
def requestNMBSession(self):
my_name_encoded = encode_name(self.my_name, TYPE_WORKSTATION)
remote_name_encoded = encode_name(self.remote_name, self.host_type)
self.sendNMBPacket(SESSION_REQUEST, remote_name_encoded + my_name_encoded)
#
# Protected Methods
#
def _processNMBSessionPacket(self, packet):
if packet.type == SESSION_MESSAGE:
self.onNMBSessionMessage(packet.flags, packet.data)
elif packet.type == POSITIVE_SESSION_RESPONSE:
self.onNMBSessionOK()
elif packet.type == NEGATIVE_SESSION_RESPONSE:
self.onNMBSessionFailed()
elif packet.type == SESSION_KEEPALIVE:
# Discard keepalive packets - [RFC1002]: 5.2.2.1
pass
else:
self.log.warning('Unrecognized NMB session type: 0x%02x', packet.type)
def _sendNMBPacket_NetBIOS(self, packet_type, data):
length = len(data)
assert length <= 0x01FFFF
flags = 0
if length > 0xFFFF:
flags |= 0x01
length &= 0xFFFF
self.write(struct.pack('>BBH', packet_type, flags, length) + data)
def _sendNMBPacket_DirectTCP(self, packet_type, data):
length = len(data)
assert length <= 0x00FFFFFF
self.write(struct.pack('>I', length) + data)
class NBNS:
log = logging.getLogger('NMB.NBNS')
HEADER_STRUCT_FORMAT = '>HHHHHH'
HEADER_STRUCT_SIZE = struct.calcsize(HEADER_STRUCT_FORMAT)
def write(self, data, ip, port):
raise NotImplementedError
def decodePacket(self, data):
if len(data) < self.HEADER_STRUCT_SIZE:
raise Exception
trn_id, code, question_count, answer_count, authority_count, additional_count = \
struct.unpack(self.HEADER_STRUCT_FORMAT, data[:self.HEADER_STRUCT_SIZE])
is_response = bool((code >> 15) & 0x01)
opcode = (code >> 11) & 0x0F
flags = (code >> 4) & 0x7F
rcode = code & 0x0F
if opcode == 0x0000 and is_response:
name_len = data[self.HEADER_STRUCT_SIZE]
# Constant 2 for the padding bytes before/after the Name and constant 8 for the Type,
# Class and TTL fields in the Answer section after the Name:
offset = self.HEADER_STRUCT_SIZE + 2 + name_len + 8
record_count = (struct.unpack('>H', data[offset:offset+2])[0]) // 6
offset += 4 # Constant 4 for the Data Length and Flags field
ret = []
for i in range(0, record_count):
ret.append('%d.%d.%d.%d' % struct.unpack('4B', (data[offset:offset + 4])))
offset += 6
return trn_id, ret
else:
return trn_id, None
def prepareNameQuery(self, trn_id, name, is_broadcast = True):
header = struct.pack(self.HEADER_STRUCT_FORMAT,
trn_id, (is_broadcast and 0x0110) or 0x0100, 1, 0, 0, 0)
payload = encode_name(name, 0x20) + b'\x00\x20\x00\x01'
return header + payload
#
# Contributed by Jason Anderson
#
def decodeIPQueryPacket(self, data):
if len(data) < self.HEADER_STRUCT_SIZE:
raise Exception
trn_id, code, question_count, answer_count, authority_count, additional_count = struct.unpack(self.HEADER_STRUCT_FORMAT, data[:self.HEADER_STRUCT_SIZE])
is_response = bool((code >> 15) & 0x01)
opcode = (code >> 11) & 0x0F
flags = (code >> 4) & 0x7F
rcode = code & 0x0F
try:
numnames = data[self.HEADER_STRUCT_SIZE + 44]
if numnames > 0:
ret = [ ]
offset = self.HEADER_STRUCT_SIZE + 45
for i in range(0, numnames):
mynme = data[offset:offset + 15]
mynme = mynme.strip()
ret.append(( str(mynme, 'ascii'), data[offset+15] ))
offset += 18
return trn_id, ret
except IndexError:
pass
return trn_id, None
#
# Contributed by Jason Anderson
#
def prepareNetNameQuery(self, trn_id, is_broadcast = True):
header = struct.pack(self.HEADER_STRUCT_FORMAT,
trn_id, (is_broadcast and 0x0010) or 0x0000, 1, 0, 0, 0)
payload = encode_name('*', 0) + b'\x00\x21\x00\x01'
return header + payload

View File

@@ -0,0 +1,38 @@
# Default port for NetBIOS name service
NETBIOS_NS_PORT = 137
# Default port for NetBIOS session service
NETBIOS_SESSION_PORT = 139
# Owner Node Type Constants
NODE_B = 0x00
NODE_P = 0x01
NODE_M = 0x10
NODE_RESERVED = 0x11
# Name Type Constants
TYPE_UNKNOWN = 0x01
TYPE_WORKSTATION = 0x00
TYPE_CLIENT = 0x03
TYPE_SERVER = 0x20
TYPE_DOMAIN_MASTER = 0x1B
TYPE_MASTER_BROWSER = 0x1D
TYPE_BROWSER = 0x1E
TYPE_NAMES = { TYPE_UNKNOWN: 'Unknown',
TYPE_WORKSTATION: 'Workstation',
TYPE_CLIENT: 'Client',
TYPE_SERVER: 'Server',
TYPE_MASTER_BROWSER: 'Master Browser',
TYPE_BROWSER: 'Browser Server',
TYPE_DOMAIN_MASTER: 'Domain Master'
}
# Values for Session Packet Type field in Session Packets
SESSION_MESSAGE = 0x00
SESSION_REQUEST = 0x81
POSITIVE_SESSION_RESPONSE = 0x82
NEGATIVE_SESSION_RESPONSE = 0x83
REGTARGET_SESSION_RESPONSE = 0x84
SESSION_KEEPALIVE = 0x85

View File

@@ -0,0 +1,66 @@
import struct
class NMBError(Exception): pass
class NotConnectedError(NMBError):
"""
Raisd when the underlying NMB connection has been disconnected or not connected yet
"""
pass
class NMBSessionMessage:
HEADER_STRUCT_FORMAT = '>BBH'
HEADER_STRUCT_SIZE = struct.calcsize(HEADER_STRUCT_FORMAT)
def __init__(self):
self.reset()
def reset(self):
self.type = 0
self.flags = 0
self.data = ''
def decode(self, data, offset):
data_len = len(data)
if data_len < offset + self.HEADER_STRUCT_SIZE:
# Not enough data for decoding
return 0
self.reset()
self.type, self.flags, length = struct.unpack(self.HEADER_STRUCT_FORMAT, data[offset:offset+self.HEADER_STRUCT_SIZE])
if self.flags & 0x01:
length |= 0x010000
if data_len < offset + self.HEADER_STRUCT_SIZE + length:
return 0
self.data = data[offset+self.HEADER_STRUCT_SIZE:offset+self.HEADER_STRUCT_SIZE+length]
return self.HEADER_STRUCT_SIZE + length
class DirectTCPSessionMessage(NMBSessionMessage):
HEADER_STRUCT_FORMAT = '>I'
HEADER_STRUCT_SIZE = struct.calcsize(HEADER_STRUCT_FORMAT)
def decode(self, data, offset):
data_len = len(data)
if data_len < offset + self.HEADER_STRUCT_SIZE:
# Not enough data for decoding
return 0
self.reset()
length = struct.unpack(self.HEADER_STRUCT_FORMAT, data[offset:offset+self.HEADER_STRUCT_SIZE])[0]
if length >> 24 != 0:
raise NMBError("Invalid protocol header for Direct TCP session message")
if data_len < offset + self.HEADER_STRUCT_SIZE + length:
return 0
self.data = data[offset+self.HEADER_STRUCT_SIZE:offset+self.HEADER_STRUCT_SIZE+length]
return self.HEADER_STRUCT_SIZE + length

View File

@@ -0,0 +1,50 @@
import string, re
def encode_name(name, type, scope = None):
"""
Perform first and second level encoding of name as specified in RFC 1001 (Section 4)
"""
if name == '*':
name = name + '\0' * 15
elif len(name) > 15:
name = name[:15] + chr(type)
else:
name = name.ljust(15) + chr(type)
def _do_first_level_encoding(m):
s = ord(m.group(0))
return string.ascii_uppercase[s >> 4] + string.ascii_uppercase[s & 0x0f]
encoded_name = chr(len(name) * 2) + re.sub('.', _do_first_level_encoding, name)
if scope:
encoded_scope = ''
for s in string.split(scope, '.'):
encoded_scope = encoded_scope + chr(len(s)) + s
return bytes(encoded_name + encoded_scope + '\0', 'ascii')
else:
return bytes(encoded_name + '\0', 'ascii')
def decode_name(name):
name_length = ord(name[0])
assert name_length == 32
def _do_first_level_decoding(m):
s = m.group(0)
return chr(((ord(s[0]) - ord('A')) << 4) | (ord(s[1]) - ord('A')))
decoded_name = re.sub('..', _do_first_level_decoding, name[1:33])
if name[33] == '\0':
return 34, decoded_name, ''
else:
decoded_domain = ''
offset = 34
while 1:
domain_length = ord(name[offset])
if domain_length == 0:
break
decoded_domain = '.' + name[offset:offset + domain_length]
offset = offset + domain_length
return offset + 1, decoded_name, decoded_domain

View File

@@ -0,0 +1,637 @@
import os, logging, select, socket, types, struct, errno
from .smb_constants import *
from .smb_structs import *
from .base import SMB, NotConnectedError, NotReadyError, SMBTimeout
class SMBConnection(SMB):
log = logging.getLogger('SMB.SMBConnection')
#: SMB messages will never be signed regardless of remote server's configurations; access errors will occur if the remote server requires signing.
SIGN_NEVER = 0
#: SMB messages will be signed when remote server supports signing but not requires signing.
SIGN_WHEN_SUPPORTED = 1
#: SMB messages will only be signed when remote server requires signing.
SIGN_WHEN_REQUIRED = 2
def __init__(self, username, password, my_name, remote_name, domain = '', use_ntlm_v2 = True, sign_options = SIGN_WHEN_REQUIRED, is_direct_tcp = False):
"""
Create a new SMBConnection instance.
*username* and *password* are the user credentials required to authenticate the underlying SMB connection with the remote server.
*password* can be a string or a callable returning a string.
File operations can only be proceeded after the connection has been authenticated successfully.
Note that you need to call *connect* method to actually establish the SMB connection to the remote server and perform authentication.
The default TCP port for most SMB/CIFS servers using NetBIOS over TCP/IP is 139.
Some newer server installations might also support Direct hosting of SMB over TCP/IP; for these servers, the default TCP port is 445.
:param string my_name: The local NetBIOS machine name that will identify where this connection is originating from.
You can freely choose a name as long as it contains a maximum of 15 alphanumeric characters and does not contain spaces and any of ``\/:*?";|+``
:param string remote_name: The NetBIOS machine name of the remote server.
On windows, you can find out the machine name by right-clicking on the "My Computer" and selecting "Properties".
This parameter must be the same as what has been configured on the remote server, or else the connection will be rejected.
:param string domain: The network domain. On windows, it is known as the workgroup. Usually, it is safe to leave this parameter as an empty string.
:param boolean use_ntlm_v2: Indicates whether pysmb should be NTLMv1 or NTLMv2 authentication algorithm for authentication.
The choice of NTLMv1 and NTLMv2 is configured on the remote server, and there is no mechanism to auto-detect which algorithm has been configured.
Hence, we can only "guess" or try both algorithms.
On Sambda, Windows Vista and Windows 7, NTLMv2 is enabled by default. On Windows XP, we can use NTLMv1 before NTLMv2.
:param int sign_options: Determines whether SMB messages will be signed. Default is *SIGN_WHEN_REQUIRED*.
If *SIGN_WHEN_REQUIRED* (value=2), SMB messages will only be signed when remote server requires signing.
If *SIGN_WHEN_SUPPORTED* (value=1), SMB messages will be signed when remote server supports signing but not requires signing.
If *SIGN_NEVER* (value=0), SMB messages will never be signed regardless of remote server's configurations; access errors will occur if the remote server requires signing.
:param boolean is_direct_tcp: Controls whether the NetBIOS over TCP/IP (is_direct_tcp=False) or the newer Direct hosting of SMB over TCP/IP (is_direct_tcp=True) will be used for the communication.
The default parameter is False which will use NetBIOS over TCP/IP for wider compatibility (TCP port: 139).
"""
SMB.__init__(self, username, password, my_name, remote_name, domain, use_ntlm_v2, sign_options, is_direct_tcp)
self.sock = None
self.auth_result = None
self.is_busy = False
self.is_direct_tcp = is_direct_tcp
#
# SMB (and its superclass) Methods
#
def onAuthOK(self):
self.auth_result = True
def onAuthFailed(self):
self.auth_result = False
def write(self, data):
assert self.sock
data_len = len(data)
total_sent = 0
while total_sent < data_len:
sent = self.sock.send(data[total_sent:])
if sent == 0:
raise NotConnectedError('Server disconnected')
total_sent = total_sent + sent
#
# Support for "with" context
#
def __enter__(self):
return self
def __exit__(self, *args):
self.close()
#
# Misc Properties
#
@property
def isUsingSMB2(self):
"""A convenient property to return True if the underlying SMB connection is using SMB2 protocol."""
return self.is_using_smb2
#
# Public Methods
#
def connect(self, ip, port = 139, sock_family = socket.AF_INET, timeout = 60):
"""
Establish the SMB connection to the remote SMB/CIFS server.
You must call this method before attempting any of the file operations with the remote server.
This method will block until the SMB connection has attempted at least one authentication.
:return: A boolean value indicating the result of the authentication atttempt: True if authentication is successful; False, if otherwise.
"""
if self.sock:
self.sock.close()
self.auth_result = None
self.sock = socket.socket(sock_family)
self.sock.settimeout(timeout)
self.sock.connect(( ip, port ))
self.is_busy = True
try:
if not self.is_direct_tcp:
self.requestNMBSession()
else:
self.onNMBSessionOK()
while self.auth_result is None:
self._pollForNetBIOSPacket(timeout)
finally:
self.is_busy = False
return self.auth_result
def close(self):
"""
Terminate the SMB connection (if it has been started) and release any sources held by the underlying socket.
"""
if self.sock:
self.sock.close()
self.sock = None
def listShares(self, timeout = 30):
"""
Retrieve a list of shared resources on remote server.
:return: A list of :doc:`smb.base.SharedDevice<smb_SharedDevice>` instances describing the shared resource
"""
if not self.sock:
raise NotConnectedError('Not connected to server')
results = [ ]
def cb(entries):
self.is_busy = False
results.extend(entries)
def eb(failure):
self.is_busy = False
raise failure
self.is_busy = True
try:
self._listShares(cb, eb, timeout)
while self.is_busy:
self._pollForNetBIOSPacket(timeout)
finally:
self.is_busy = False
return results
def listPath(self, service_name, path,
search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_ARCHIVE | SMB_FILE_ATTRIBUTE_INCL_NORMAL,
pattern = '*', timeout = 30):
"""
Retrieve a directory listing of files/folders at *path*
For simplicity, pysmb defines a "normal" file as a file entry that is not read-only, not hidden, not system, not archive and not a directory.
It ignores other attributes like compression, indexed, sparse, temporary and encryption.
Note that the default search parameter will query for all read-only (SMB_FILE_ATTRIBUTE_READONLY), hidden (SMB_FILE_ATTRIBUTE_HIDDEN),
system (SMB_FILE_ATTRIBUTE_SYSTEM), archive (SMB_FILE_ATTRIBUTE_ARCHIVE), normal (SMB_FILE_ATTRIBUTE_INCL_NORMAL) files
and directories (SMB_FILE_ATTRIBUTE_DIRECTORY).
If you do not need to include "normal" files in the result, define your own search parameter without the SMB_FILE_ATTRIBUTE_INCL_NORMAL constant.
SMB_FILE_ATTRIBUTE_NORMAL should be used by itself and not be used with other bit constants.
:param string/unicode service_name: the name of the shared folder for the *path*
:param string/unicode path: path relative to the *service_name* where we are interested to learn about its files/sub-folders.
:param integer search: integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py).
:param string/unicode pattern: the filter to apply to the results before returning to the client.
:return: A list of :doc:`smb.base.SharedFile<smb_SharedFile>` instances.
"""
if not self.sock:
raise NotConnectedError('Not connected to server')
results = [ ]
def cb(entries):
self.is_busy = False
results.extend(entries)
def eb(failure):
self.is_busy = False
raise failure
self.is_busy = True
try:
self._listPath(service_name, path, cb, eb, search = search, pattern = pattern, timeout = timeout)
while self.is_busy:
self._pollForNetBIOSPacket(timeout)
finally:
self.is_busy = False
return results
def listSnapshots(self, service_name, path, timeout = 30):
"""
Retrieve a list of available snapshots (shadow copies) for *path*.
Note that snapshot features are only supported on Windows Vista Business, Enterprise and Ultimate, and on all Windows 7 editions.
:param string/unicode service_name: the name of the shared folder for the *path*
:param string/unicode path: path relative to the *service_name* where we are interested in the list of available snapshots
:return: A list of python *datetime.DateTime* instances in GMT/UTC time zone
"""
if not self.sock:
raise NotConnectedError('Not connected to server')
results = [ ]
def cb(entries):
self.is_busy = False
results.extend(entries)
def eb(failure):
self.is_busy = False
raise failure
self.is_busy = True
try:
self._listSnapshots(service_name, path, cb, eb, timeout = timeout)
while self.is_busy:
self._pollForNetBIOSPacket(timeout)
finally:
self.is_busy = False
return results
def getAttributes(self, service_name, path, timeout = 30):
"""
Retrieve information about the file at *path* on the *service_name*.
:param string/unicode service_name: the name of the shared folder for the *path*
:param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be raised.
:return: A :doc:`smb.base.SharedFile<smb_SharedFile>` instance containing the attributes of the file.
"""
if not self.sock:
raise NotConnectedError('Not connected to server')
results = [ ]
def cb(info):
self.is_busy = False
results.append(info)
def eb(failure):
self.is_busy = False
raise failure
self.is_busy = True
try:
self._getAttributes(service_name, path, cb, eb, timeout)
while self.is_busy:
self._pollForNetBIOSPacket(timeout)
finally:
self.is_busy = False
return results[0]
def getSecurity(self, service_name, path, timeout = 30):
"""
Retrieve the security descriptor of the file at *path* on the *service_name*.
:param string/unicode service_name: the name of the shared folder for the *path*
:param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be raised.
:return: A :class:`smb.security_descriptors.SecurityDescriptor` instance containing the security information of the file.
"""
if not self.sock:
raise NotConnectedError('Not connected to server')
results = [ ]
def cb(info):
self.is_busy = False
results.append(info)
def eb(failure):
self.is_busy = False
raise failure
self.is_busy = True
try:
self._getSecurity(service_name, path, cb, eb, timeout)
while self.is_busy:
self._pollForNetBIOSPacket(timeout)
finally:
self.is_busy = False
return results[0]
def retrieveFile(self, service_name, path, file_obj, timeout = 30):
"""
Retrieve the contents of the file at *path* on the *service_name* and write these contents to the provided *file_obj*.
Use *retrieveFileFromOffset()* method if you wish to specify the offset to read from the remote *path* and/or the number of bytes to write to the *file_obj*.
:param string/unicode service_name: the name of the shared folder for the *path*
:param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be raised.
:param file_obj: A file-like object that has a *write* method. Data will be written continuously to *file_obj* until EOF is received from the remote service. In Python3, this file-like object must have a *write* method which accepts a bytes parameter.
:return: A 2-element tuple of ( file attributes of the file on server, number of bytes written to *file_obj* ).
The file attributes is an integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py)
"""
return self.retrieveFileFromOffset(service_name, path, file_obj, 0, -1, timeout)
def retrieveFileFromOffset(self, service_name, path, file_obj, offset = 0, max_length = -1, timeout = 30):
"""
Retrieve the contents of the file at *path* on the *service_name* and write these contents to the provided *file_obj*.
:param string/unicode service_name: the name of the shared folder for the *path*
:param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be raised.
:param file_obj: A file-like object that has a *write* method. Data will be written continuously to *file_obj* up to *max_length* number of bytes. In Python3, this file-like object must have a *write* method which accepts a bytes parameter.
:param integer/long offset: the offset in the remote *path* where the first byte will be read and written to *file_obj*. Must be either zero or a positive integer/long value.
:param integer/long max_length: maximum number of bytes to read from the remote *path* and write to the *file_obj*. Specify a negative value to read from *offset* to the EOF.
If zero, the method returns immediately after the file is opened successfully for reading.
:return: A 2-element tuple of ( file attributes of the file on server, number of bytes written to *file_obj* ).
The file attributes is an integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py)
"""
if not self.sock:
raise NotConnectedError('Not connected to server')
results = [ ]
def cb(r):
self.is_busy = False
results.append(r[1:])
def eb(failure):
self.is_busy = False
raise failure
self.is_busy = True
try:
self._retrieveFileFromOffset(service_name, path, file_obj, cb, eb, offset, max_length, timeout = timeout)
while self.is_busy:
self._pollForNetBIOSPacket(timeout)
finally:
self.is_busy = False
return results[0]
def storeFile(self, service_name, path, file_obj, timeout = 30):
"""
Store the contents of the *file_obj* at *path* on the *service_name*.
If the file already exists on the remote server, it will be truncated and overwritten.
:param string/unicode service_name: the name of the shared folder for the *path*
:param string/unicode path: Path of the file on the remote server. If the file at *path* does not exist, it will be created. Otherwise, it will be overwritten.
If the *path* refers to a folder or the file cannot be opened for writing, an :doc:`OperationFailure<smb_exceptions>` will be raised.
:param file_obj: A file-like object that has a *read* method. Data will read continuously from *file_obj* until EOF. In Python3, this file-like object must have a *read* method which returns a bytes parameter.
:return: Number of bytes uploaded
"""
return self.storeFileFromOffset(service_name, path, file_obj, 0, True, timeout)
def storeFileFromOffset(self, service_name, path, file_obj, offset = 0, truncate = False, timeout = 30):
"""
Store the contents of the *file_obj* at *path* on the *service_name*.
:param string/unicode service_name: the name of the shared folder for the *path*
:param string/unicode path: Path of the file on the remote server. If the file at *path* does not exist, it will be created.
If the *path* refers to a folder or the file cannot be opened for writing, an :doc:`OperationFailure<smb_exceptions>` will be raised.
:param file_obj: A file-like object that has a *read* method. Data will read continuously from *file_obj* until EOF.
:param offset: Long integer value which specifies the offset in the remote server to start writing. First byte of the file is 0.
:param truncate: Boolean value. If True and the file exists on the remote server, it will be truncated first before writing. Default is False.
:return: the file position where the next byte will be written.
"""
if not self.sock:
raise NotConnectedError('Not connected to server')
results = [ ]
def cb(r):
self.is_busy = False
results.append(r[1])
def eb(failure):
self.is_busy = False
raise failure
self.is_busy = True
try:
self._storeFileFromOffset(service_name, path, file_obj, cb, eb, offset, truncate = truncate, timeout = timeout)
while self.is_busy:
self._pollForNetBIOSPacket(timeout)
finally:
self.is_busy = False
return results[0]
def deleteFiles(self, service_name, path_file_pattern, delete_matching_folders = False, timeout = 30):
"""
Delete one or more regular files. It supports the use of wildcards in file names, allowing for deletion of multiple files in a single request.
If delete_matching_folders is True, immediate sub-folders that match the path_file_pattern will be deleted recursively.
:param string/unicode service_name: Contains the name of the shared folder.
:param string/unicode path_file_pattern: The pathname of the file(s) to be deleted, relative to the service_name.
Wildcards may be used in th filename component of the path.
If your path/filename contains non-English characters, you must pass in an unicode string.
:return: None
"""
if not self.sock:
raise NotConnectedError('Not connected to server')
def cb(r):
self.is_busy = False
def eb(failure):
self.is_busy = False
raise failure
self.is_busy = True
try:
self._deleteFiles(service_name, path_file_pattern, delete_matching_folders, cb, eb, timeout = timeout)
while self.is_busy:
self._pollForNetBIOSPacket(timeout)
finally:
self.is_busy = False
def resetFileAttributes(self, service_name, path_file_pattern, file_attributes = ATTR_NORMAL, timeout = 30):
"""
Reset file attributes of one or more regular files or folders.
It supports the use of wildcards in file names, allowing for unlocking of multiple files/folders in a single request.
This function is very helpful when deleting files/folders that are read-only.
By default, it sets the ATTR_NORMAL flag, therefore clearing all other flags.
(See https://msdn.microsoft.com/en-us/library/cc232110.aspx for further information)
Note: this function is currently only implemented for SMB2!
:param string/unicode service_name: Contains the name of the shared folder.
:param string/unicode path_file_pattern: The pathname of the file(s) to be deleted, relative to the service_name.
Wildcards may be used in the filename component of the path.
If your path/filename contains non-English characters, you must pass in an unicode string.
:param int file_attributes: The desired file attributes to set. Defaults to `ATTR_NORMAL`.
:return: None
"""
if not self.sock:
raise NotConnectedError('Not connected to server')
def cb(r):
self.is_busy = False
def eb(failure):
self.is_busy = False
raise failure
self.is_busy = True
try:
self._resetFileAttributes(service_name, path_file_pattern, cb, eb, file_attributes, timeout)
while self.is_busy:
self._pollForNetBIOSPacket(timeout)
finally:
self.is_busy = False
def createDirectory(self, service_name, path, timeout = 30):
"""
Creates a new directory *path* on the *service_name*.
:param string/unicode service_name: Contains the name of the shared folder.
:param string/unicode path: The path of the new folder (relative to) the shared folder.
If the path contains non-English characters, an unicode string must be used to pass in the path.
:return: None
"""
if not self.sock:
raise NotConnectedError('Not connected to server')
def cb(r):
self.is_busy = False
def eb(failure):
self.is_busy = False
raise failure
self.is_busy = True
try:
self._createDirectory(service_name, path, cb, eb, timeout = timeout)
while self.is_busy:
self._pollForNetBIOSPacket(timeout)
finally:
self.is_busy = False
def deleteDirectory(self, service_name, path, timeout = 30):
"""
Delete the empty folder at *path* on *service_name*
:param string/unicode service_name: Contains the name of the shared folder.
:param string/unicode path: The path of the to-be-deleted folder (relative to) the shared folder.
If the path contains non-English characters, an unicode string must be used to pass in the path.
:return: None
"""
if not self.sock:
raise NotConnectedError('Not connected to server')
def cb(r):
self.is_busy = False
def eb(failure):
self.is_busy = False
raise failure
self.is_busy = True
try:
self._deleteDirectory(service_name, path, cb, eb, timeout = timeout)
while self.is_busy:
self._pollForNetBIOSPacket(timeout)
finally:
self.is_busy = False
def rename(self, service_name, old_path, new_path, timeout = 30):
"""
Rename a file or folder at *old_path* to *new_path* shared at *service_name*. Note that this method cannot be used to rename file/folder across different shared folders
*old_path* and *new_path* are string/unicode referring to the old and new path of the renamed resources (relative to) the shared folder.
If the path contains non-English characters, an unicode string must be used to pass in the path.
:param string/unicode service_name: Contains the name of the shared folder.
:return: None
"""
if not self.sock:
raise NotConnectedError('Not connected to server')
def cb(r):
self.is_busy = False
def eb(failure):
self.is_busy = False
raise failure
self.is_busy = True
try:
self._rename(service_name, old_path, new_path, cb, eb)
while self.is_busy:
self._pollForNetBIOSPacket(timeout)
finally:
self.is_busy = False
def echo(self, data, timeout = 10):
"""
Send an echo command containing *data* to the remote SMB/CIFS server. The remote SMB/CIFS will reply with the same *data*.
:param bytes data: Data to send to the remote server. Must be a bytes object.
:return: The *data* parameter
"""
if not self.sock:
raise NotConnectedError('Not connected to server')
results = [ ]
def cb(r):
self.is_busy = False
results.append(r)
def eb(failure):
self.is_busy = False
raise failure
self.is_busy = True
try:
self._echo(data, cb, eb)
while self.is_busy:
self._pollForNetBIOSPacket(timeout)
finally:
self.is_busy = False
return results[0]
#
# Protected Methods
#
def _pollForNetBIOSPacket(self, timeout):
expiry_time = time.time() + timeout
read_len = 4
data = b''
while read_len > 0:
try:
if expiry_time < time.time():
raise SMBTimeout
ready, _, _ = select.select([ self.sock.fileno() ], [ ], [ ], timeout)
if not ready:
raise SMBTimeout
d = self.sock.recv(read_len)
if len(d) == 0:
raise NotConnectedError
data = data + d
read_len -= len(d)
except select.error as ex:
if isinstance(ex, tuple):
if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
raise ex
else:
raise ex
type, flags, length = struct.unpack('>BBH', data)
if flags & 0x01:
length = length | 0x10000
read_len = length
while read_len > 0:
try:
if expiry_time < time.time():
raise SMBTimeout
ready, _, _ = select.select([ self.sock.fileno() ], [ ], [ ], timeout)
if not ready:
raise SMBTimeout
d = self.sock.recv(read_len)
if len(d) == 0:
raise NotConnectedError
data = data + d
read_len -= len(d)
except select.error as ex:
if isinstance(ex, tuple):
if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
raise ex
else:
raise ex
self.feedData(data)

View File

@@ -0,0 +1,96 @@
import os, sys, socket, urllib.request, urllib.error, urllib.parse, mimetypes, email, tempfile
from urllib.parse import (unwrap, unquote, splittype, splithost, quote,
splitport, splittag, splitattr, splituser, splitpasswd, splitvalue)
from urllib.response import addinfourl
from urllib.request import ftpwrapper
from nmb.NetBIOS import NetBIOS
from smb.SMBConnection import SMBConnection
from io import BytesIO
USE_NTLM = True
MACHINE_NAME = None
class SMBHandler(urllib.request.BaseHandler):
def smb_open(self, req):
global USE_NTLM, MACHINE_NAME
if not req.host:
raise urllib.error.URLError('SMB error: no host given')
host, port = splitport(req.host)
if port is None:
port = 139
else:
port = int(port)
# username/password handling
user, host = splituser(host)
if user:
user, passwd = splitpasswd(user)
else:
passwd = None
host = unquote(host)
user = user or ''
domain = ''
if ';' in user:
domain, user = user.split(';', 1)
passwd = passwd or ''
myname = MACHINE_NAME or self.generateClientMachineName()
server_name,host = host.split(',') if ',' in host else [None,host]
if server_name is None:
n = NetBIOS()
names = n.queryIPForName(host)
if names:
server_name = names[0]
else:
raise urllib.error.URLError('SMB error: Hostname does not reply back with its machine name')
path, attrs = splitattr(req.selector)
if path.startswith('/'):
path = path[1:]
dirs = path.split('/')
dirs = list(map(unquote, dirs))
service, path = dirs[0], '/'.join(dirs[1:])
try:
conn = SMBConnection(user, passwd, myname, server_name, domain=domain, use_ntlm_v2 = USE_NTLM)
conn.connect(host, port)
headers = email.message.Message()
if req.data:
filelen = conn.storeFile(service, path, req.data)
headers.add_header('Content-length', '0')
fp = BytesIO(b"")
else:
fp = self.createTempFile()
file_attrs, retrlen = conn.retrieveFile(service, path, fp)
fp.seek(0)
mtype = mimetypes.guess_type(req.get_full_url())[0]
if mtype:
headers.add_header('Content-type', mtype)
if retrlen is not None and retrlen >= 0:
headers.add_header('Content-length', '%d' % retrlen)
return addinfourl(fp, headers, req.get_full_url())
except Exception as ex:
raise urllib.error.URLError('smb error: %s' % ex).with_traceback(sys.exc_info()[2])
def createTempFile(self):
return tempfile.TemporaryFile()
def generateClientMachineName(self):
hostname = socket.gethostname()
if hostname:
return hostname.split('.')[0]
return 'SMB%d' % os.getpid()

View File

@@ -0,0 +1,412 @@
import os, logging, time
from twisted.internet import reactor, defer
from twisted.internet.protocol import ClientFactory, Protocol
from .smb_constants import *
from .smb_structs import *
from .base import SMB, NotConnectedError, NotReadyError, SMBTimeout
__all__ = [ 'SMBProtocolFactory', 'NotConnectedError', 'NotReadyError' ]
class SMBProtocol(Protocol, SMB):
log = logging.getLogger('SMB.SMBProtocol')
#
# Protocol Methods
#
def connectionMade(self):
self.factory.instance = self
if not self.is_direct_tcp:
self.requestNMBSession()
else:
self.onNMBSessionOK()
def connectionLost(self, reason):
if self.factory.instance == self:
self.instance = None
def dataReceived(self, data):
self.feedData(data)
#
# SMB (and its superclass) Methods
#
def write(self, data):
self.transport.write(data)
def onAuthOK(self):
if self.factory.instance == self:
self.factory.onAuthOK()
reactor.callLater(1, self._cleanupPendingRequests)
def onAuthFailed(self):
if self.factory.instance == self:
self.factory.onAuthFailed()
def onNMBSessionFailed(self):
self.log.error('Cannot establish NetBIOS session. You might have provided a wrong remote_name')
#
# Protected Methods
#
def _cleanupPendingRequests(self):
if self.factory.instance == self:
now = time.time()
to_remove = []
for mid, r in self.pending_requests.items():
if r.expiry_time < now:
try:
r.errback(SMBTimeout())
except Exception: pass
to_remove.append(mid)
for mid in to_remove:
del self.pending_requests[mid]
reactor.callLater(1, self._cleanupPendingRequests)
class SMBProtocolFactory(ClientFactory):
protocol = SMBProtocol
log = logging.getLogger('SMB.SMBFactory')
#: SMB messages will never be signed regardless of remote server's configurations; access errors will occur if the remote server requires signing.
SIGN_NEVER = 0
#: SMB messages will be signed when remote server supports signing but not requires signing.
SIGN_WHEN_SUPPORTED = 1
#: SMB messages will only be signed when remote server requires signing.
SIGN_WHEN_REQUIRED = 2
def __init__(self, username, password, my_name, remote_name, domain = '', use_ntlm_v2 = True, sign_options = SIGN_WHEN_REQUIRED, is_direct_tcp = False):
"""
Create a new SMBProtocolFactory instance. You will pass this instance to *reactor.connectTCP()* which will then instantiate the TCP connection to the remote SMB/CIFS server.
Note that the default TCP port for most SMB/CIFS servers using NetBIOS over TCP/IP is 139.
Some newer server installations might also support Direct hosting of SMB over TCP/IP; for these servers, the default TCP port is 445.
*username* and *password* are the user credentials required to authenticate the underlying SMB connection with the remote server.
File operations can only be proceeded after the connection has been authenticated successfully.
:param string my_name: The local NetBIOS machine name that will identify where this connection is originating from.
You can freely choose a name as long as it contains a maximum of 15 alphanumeric characters and does not contain spaces and any of ``\/:*?";|+``
:param string remote_name: The NetBIOS machine name of the remote server.
On windows, you can find out the machine name by right-clicking on the "My Computer" and selecting "Properties".
This parameter must be the same as what has been configured on the remote server, or else the connection will be rejected.
:param string domain: The network domain. On windows, it is known as the workgroup. Usually, it is safe to leave this parameter as an empty string.
:param boolean use_ntlm_v2: Indicates whether pysmb should be NTLMv1 or NTLMv2 authentication algorithm for authentication.
The choice of NTLMv1 and NTLMv2 is configured on the remote server, and there is no mechanism to auto-detect which algorithm has been configured.
Hence, we can only "guess" or try both algorithms.
On Sambda, Windows Vista and Windows 7, NTLMv2 is enabled by default. On Windows XP, we can use NTLMv1 before NTLMv2.
:param int sign_options: Determines whether SMB messages will be signed. Default is *SIGN_WHEN_REQUIRED*.
If *SIGN_WHEN_REQUIRED* (value=2), SMB messages will only be signed when remote server requires signing.
If *SIGN_WHEN_SUPPORTED* (value=1), SMB messages will be signed when remote server supports signing but not requires signing.
If *SIGN_NEVER* (value=0), SMB messages will never be signed regardless of remote server's configurations; access errors will occur if the remote server requires signing.
:param boolean is_direct_tcp: Controls whether the NetBIOS over TCP/IP (is_direct_tcp=False) or the newer Direct hosting of SMB over TCP/IP (is_direct_tcp=True) will be used for the communication.
The default parameter is False which will use NetBIOS over TCP/IP for wider compatibility (TCP port: 139).
"""
self.username = username
self.password = password
self.my_name = my_name
self.remote_name = remote_name
self.domain = domain
self.use_ntlm_v2 = use_ntlm_v2
self.sign_options = sign_options
self.is_direct_tcp = is_direct_tcp
self.instance = None #: The single SMBProtocol instance for each SMBProtocolFactory instance. Usually, you should not need to touch this attribute directly.
#
# Public Property
#
@property
def isReady(self):
"""A convenient property to return True if the underlying SMB connection has connected to remote server, has successfully authenticated itself and is ready for file operations."""
return bool(self.instance and self.instance.has_authenticated)
@property
def isUsingSMB2(self):
"""A convenient property to return True if the underlying SMB connection is using SMB2 protocol."""
return self.instance and self.instance.is_using_smb2
#
# Public Methods for Callbacks
#
def onAuthOK(self):
"""
Override this method in your *SMBProtocolFactory* subclass to add in post-authentication handling.
This method will be called when the server has replied that the SMB connection has been successfully authenticated.
File operations can proceed when this method has been called.
"""
pass
def onAuthFailed(self):
"""
Override this method in your *SMBProtocolFactory* subclass to add in post-authentication handling.
This method will be called when the server has replied that the SMB connection has been successfully authenticated.
If you want to retry authenticating from this method,
1. Disconnect the underlying SMB connection (call ``self.instance.transport.loseConnection()``)
2. Create a new SMBProtocolFactory subclass instance with different user credientials or different NTLM algorithm flag.
3. Call ``reactor.connectTCP`` with the new instance to re-establish the SMB connection
"""
pass
#
# Public Methods
#
def listShares(self, timeout = 30):
"""
Retrieve a list of shared resources on remote server.
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of :doc:`smb.base.SharedDevice<smb_SharedDevice>` instances.
"""
if not self.instance:
raise NotConnectedError('Not connected to server')
d = defer.Deferred()
self.instance._listShares(d.callback, d.errback, timeout)
return d
def listPath(self, service_name, path,
search = SMB_FILE_ATTRIBUTE_READONLY | SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM | SMB_FILE_ATTRIBUTE_DIRECTORY | SMB_FILE_ATTRIBUTE_ARCHIVE | SMB_FILE_ATTRIBUTE_INCL_NORMAL,
pattern = '*', timeout = 30):
"""
Retrieve a directory listing of files/folders at *path*
For simplicity, pysmb defines a "normal" file as a file entry that is not read-only, not hidden, not system, not archive and not a directory.
It ignores other attributes like compression, indexed, sparse, temporary and encryption.
Note that the default search parameter will query for all read-only (SMB_FILE_ATTRIBUTE_READONLY), hidden (SMB_FILE_ATTRIBUTE_HIDDEN),
system (SMB_FILE_ATTRIBUTE_SYSTEM), archive (SMB_FILE_ATTRIBUTE_ARCHIVE), normal (SMB_FILE_ATTRIBUTE_INCL_NORMAL) files
and directories (SMB_FILE_ATTRIBUTE_DIRECTORY).
If you do not need to include "normal" files in the result, define your own search parameter without the SMB_FILE_ATTRIBUTE_INCL_NORMAL constant.
SMB_FILE_ATTRIBUTE_NORMAL should be used by itself and not be used with other bit constants.
:param string/unicode service_name: the name of the shared folder for the *path*
:param string/unicode path: path relative to the *service_name* where we are interested to learn about its files/sub-folders.
:param integer search: integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py).
:param string/unicode pattern: the filter to apply to the results before returning to the client.
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of :doc:`smb.base.SharedFile<smb_SharedFile>` instances.
"""
if not self.instance:
raise NotConnectedError('Not connected to server')
d = defer.Deferred()
self.instance._listPath(service_name, path, d.callback, d.errback, search = search, pattern = pattern, timeout = timeout)
return d
def listSnapshots(self, service_name, path, timeout = 30):
"""
Retrieve a list of available snapshots (a.k.a. shadow copies) for *path*.
Note that snapshot features are only supported on Windows Vista Business, Enterprise and Ultimate, and on all Windows 7 editions.
:param string/unicode service_name: the name of the shared folder for the *path*
:param string/unicode path: path relative to the *service_name* where we are interested in the list of available snapshots
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a list of python *datetime.DateTime*
instances in GMT/UTC time zone
"""
if not self.instance:
raise NotConnectedError('Not connected to server')
d = defer.Deferred()
self.instance._listSnapshots(service_name, path, d.callback, d.errback, timeout = timeout)
return d
def getAttributes(self, service_name, path, timeout = 30):
"""
Retrieve information about the file at *path* on the *service_name*.
:param string/unicode service_name: the name of the shared folder for the *path*
:param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be raised.
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a :doc:`smb.base.SharedFile<smb_SharedFile>` instance containing the attributes of the file.
"""
if not self.instance:
raise NotConnectedError('Not connected to server')
d = defer.Deferred()
self.instance._getAttributes(service_name, path, d.callback, d.errback, timeout = timeout)
return d
def retrieveFile(self, service_name, path, file_obj, timeout = 30):
"""
Retrieve the contents of the file at *path* on the *service_name* and write these contents to the provided *file_obj*.
Use *retrieveFileFromOffset()* method if you need to specify the offset to read from the remote *path* and/or the maximum number of bytes to write to the *file_obj*.
The meaning of the *timeout* parameter will be different from other file operation methods. As the downloaded file usually exceeeds the maximum size
of each SMB/CIFS data message, it will be packetized into a series of request messages (each message will request about about 60kBytes).
The *timeout* parameter is an integer/float value that specifies the timeout interval for these individual SMB/CIFS message to be transmitted and downloaded from the remote SMB/CIFS server.
:param string/unicode service_name: the name of the shared folder for the *path*
:param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be called in the returned *Deferred* errback.
:param file_obj: A file-like object that has a *write* method. Data will be written continuously to *file_obj* until EOF is received from the remote service. In Python3, this file-like object must have a *write* method which accepts a bytes parameter.
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a 3-element tuple of ( *file_obj*, file attributes of the file on server, number of bytes written to *file_obj* ).
The file attributes is an integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py)
"""
return self.retrieveFileFromOffset(service_name, path, file_obj, 0, -1, timeout)
def retrieveFileFromOffset(self, service_name, path, file_obj, offset = 0, max_length = -1, timeout = 30):
"""
Retrieve the contents of the file at *path* on the *service_name* and write these contents to the provided *file_obj*.
The meaning of the *timeout* parameter will be different from other file operation methods. As the downloaded file usually exceeeds the maximum size
of each SMB/CIFS data message, it will be packetized into a series of request messages (each message will request about about 60kBytes).
The *timeout* parameter is an integer/float value that specifies the timeout interval for these individual SMB/CIFS message to be transmitted and downloaded from the remote SMB/CIFS server.
:param string/unicode service_name: the name of the shared folder for the *path*
:param string/unicode path: Path of the file on the remote server. If the file cannot be opened for reading, an :doc:`OperationFailure<smb_exceptions>` will be called in the returned *Deferred* errback.
:param file_obj: A file-like object that has a *write* method. Data will be written continuously to *file_obj* until EOF is received from the remote service. In Python3, this file-like object must have a *write* method which accepts a bytes parameter.
:param integer/long offset: the offset in the remote *path* where the first byte will be read and written to *file_obj*. Must be either zero or a positive integer/long value.
:param integer/long max_length: maximum number of bytes to read from the remote *path* and write to the *file_obj*. Specify a negative value to read from *offset* to the EOF.
If zero, the *Deferred* callback is invoked immediately after the file is opened successfully for reading.
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a 3-element tuple of ( *file_obj*, file attributes of the file on server, number of bytes written to *file_obj* ).
The file attributes is an integer value made up from a bitwise-OR of *SMB_FILE_ATTRIBUTE_xxx* bits (see smb_constants.py)
"""
if not self.instance:
raise NotConnectedError('Not connected to server')
d = defer.Deferred()
self.instance._retrieveFileFromOffset(service_name, path, file_obj, d.callback, d.errback, offset, max_length, timeout = timeout)
return d
def storeFile(self, service_name, path, file_obj, timeout = 30):
"""
Store the contents of the *file_obj* at *path* on the *service_name*.
The meaning of the *timeout* parameter will be different from other file operation methods. As the uploaded file usually exceeeds the maximum size
of each SMB/CIFS data message, it will be packetized into a series of messages (usually about 60kBytes).
The *timeout* parameter is an integer/float value that specifies the timeout interval for these individual SMB/CIFS message to be transmitted and acknowledged
by the remote SMB/CIFS server.
:param string/unicode service_name: the name of the shared folder for the *path*
:param string/unicode path: Path of the file on the remote server. If the file at *path* does not exist, it will be created. Otherwise, it will be overwritten.
If the *path* refers to a folder or the file cannot be opened for writing, an :doc:`OperationFailure<smb_exceptions>` will be called in the returned *Deferred* errback.
:param file_obj: A file-like object that has a *read* method. Data will read continuously from *file_obj* until EOF. In Python3, this file-like object must have a *read* method which returns a bytes parameter.
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a 2-element tuple of ( *file_obj*, number of bytes uploaded ).
"""
if not self.instance:
raise NotConnectedError('Not connected to server')
d = defer.Deferred()
self.instance._storeFile(service_name, path, file_obj, d.callback, d.errback, timeout = timeout)
return d
def deleteFiles(self, service_name, path_file_pattern, delete_matching_folders = False, timeout = 30):
"""
Delete one or more regular files. It supports the use of wildcards in file names, allowing for deletion of multiple files in a single request.
If delete_matching_folders is True, immediate sub-folders that match the path_file_pattern will be deleted recursively.
:param string/unicode service_name: Contains the name of the shared folder.
:param string/unicode path_file_pattern: The pathname of the file(s) to be deleted, relative to the service_name.
Wildcards may be used in th filename component of the path.
If your path/filename contains non-English characters, you must pass in an unicode string.
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *path_file_pattern* parameter.
"""
if not self.instance:
raise NotConnectedError('Not connected to server')
d = defer.Deferred()
self.instance._deleteFiles(service_name, path_file_pattern, delete_matching_folders, d.callback, d.errback, timeout = timeout)
return d
def createDirectory(self, service_name, path):
"""
Creates a new directory *path* on the *service_name*.
:param string/unicode service_name: Contains the name of the shared folder.
:param string/unicode path: The path of the new folder (relative to) the shared folder.
If the path contains non-English characters, an unicode string must be used to pass in the path.
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *path* parameter.
"""
if not self.instance:
raise NotConnectedError('Not connected to server')
d = defer.Deferred()
self.instance._createDirectory(service_name, path, d.callback, d.errback)
return d
def deleteDirectory(self, service_name, path):
"""
Delete the empty folder at *path* on *service_name*
:param string/unicode service_name: Contains the name of the shared folder.
:param string/unicode path: The path of the to-be-deleted folder (relative to) the shared folder.
If the path contains non-English characters, an unicode string must be used to pass in the path.
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *path* parameter.
"""
if not self.instance:
raise NotConnectedError('Not connected to server')
d = defer.Deferred()
self.instance._deleteDirectory(service_name, path, d.callback, d.errback)
return d
def rename(self, service_name, old_path, new_path):
"""
Rename a file or folder at *old_path* to *new_path* shared at *service_name*. Note that this method cannot be used to rename file/folder across different shared folders
*old_path* and *new_path* are string/unicode referring to the old and new path of the renamed resources (relative to) the shared folder.
If the path contains non-English characters, an unicode string must be used to pass in the path.
:param string/unicode service_name: Contains the name of the shared folder.
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with a 2-element tuple of ( *old_path*, *new_path* ).
"""
if not self.instance:
raise NotConnectedError('Not connected to server')
d = defer.Deferred()
self.instance._rename(service_name, old_path, new_path, d.callback, d.errback)
return d
def echo(self, data, timeout = 10):
"""
Send an echo command containing *data* to the remote SMB/CIFS server. The remote SMB/CIFS will reply with the same *data*.
:param bytes data: Data to send to the remote server. Must be a bytes object.
:param integer/float timeout: Number of seconds that pysmb will wait before raising *SMBTimeout* via the returned *Deferred* instance's *errback* method.
:return: A *twisted.internet.defer.Deferred* instance. The callback function will be called with the *data* parameter.
"""
if not self.instance:
raise NotConnectedError('Not connected to server')
d = defer.Deferred()
self.instance._echo(data, d.callback, d.errback, timeout)
return d
def closeConnection(self):
"""
Disconnect from the remote SMB/CIFS server. The TCP connection will be closed at the earliest opportunity after this method returns.
:return: None
"""
if not self.instance:
raise NotConnectedError('Not connected to server')
self.instance.transport.loseConnection()
#
# ClientFactory methods
# (Do not touch these unless you know what you are doing)
#
def buildProtocol(self, addr):
p = self.protocol(self.username, self.password, self.my_name, self.remote_name, self.domain, self.use_ntlm_v2, self.sign_options, self.is_direct_tcp)
p.factory = self
return p

View File

@@ -0,0 +1 @@

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,254 @@
import types, hmac, binascii, struct, random, string
from .utils.rc4 import RC4_encrypt
from .utils.pyDes import des
try:
import hashlib
hashlib.new('md4')
def MD4(): return hashlib.new('md4')
except ( ImportError, ValueError ):
from .utils.md4 import MD4
try:
import hashlib
def MD5(s): return hashlib.md5(s)
except ImportError:
import md5
def MD5(s): return md5.new(s)
################
# NTLMv2 Methods
################
# The following constants are defined in accordance to [MS-NLMP]: 2.2.2.5
NTLM_NegotiateUnicode = 0x00000001
NTLM_NegotiateOEM = 0x00000002
NTLM_RequestTarget = 0x00000004
NTLM_Unknown9 = 0x00000008
NTLM_NegotiateSign = 0x00000010
NTLM_NegotiateSeal = 0x00000020
NTLM_NegotiateDatagram = 0x00000040
NTLM_NegotiateLanManagerKey = 0x00000080
NTLM_Unknown8 = 0x00000100
NTLM_NegotiateNTLM = 0x00000200
NTLM_NegotiateNTOnly = 0x00000400
NTLM_Anonymous = 0x00000800
NTLM_NegotiateOemDomainSupplied = 0x00001000
NTLM_NegotiateOemWorkstationSupplied = 0x00002000
NTLM_Unknown6 = 0x00004000
NTLM_NegotiateAlwaysSign = 0x00008000
NTLM_TargetTypeDomain = 0x00010000
NTLM_TargetTypeServer = 0x00020000
NTLM_TargetTypeShare = 0x00040000
NTLM_NegotiateExtendedSecurity = 0x00080000
NTLM_NegotiateIdentify = 0x00100000
NTLM_Unknown5 = 0x00200000
NTLM_RequestNonNTSessionKey = 0x00400000
NTLM_NegotiateTargetInfo = 0x00800000
NTLM_Unknown4 = 0x01000000
NTLM_NegotiateVersion = 0x02000000
NTLM_Unknown3 = 0x04000000
NTLM_Unknown2 = 0x08000000
NTLM_Unknown1 = 0x10000000
NTLM_Negotiate128 = 0x20000000
NTLM_NegotiateKeyExchange = 0x40000000
NTLM_Negotiate56 = 0x80000000
NTLM_FLAGS = NTLM_NegotiateUnicode | \
NTLM_RequestTarget | \
NTLM_NegotiateSign | \
NTLM_NegotiateNTLM | \
NTLM_NegotiateAlwaysSign | \
NTLM_NegotiateExtendedSecurity | \
NTLM_NegotiateTargetInfo | \
NTLM_NegotiateVersion | \
NTLM_Negotiate128 | \
NTLM_NegotiateKeyExchange
def generateNegotiateMessage():
"""
References:
===========
- [MS-NLMP]: 2.2.1.1
"""
s = struct.pack('<8sII8s8s8s',
b'NTLMSSP\0', 0x01, NTLM_FLAGS,
b'\0' * 8, # Domain
b'\0' * 8, # Workstation
b'\x06\x00\x72\x17\x00\x00\x00\x0F') # Version [MS-NLMP]: 2.2.2.10
return s
def generateAuthenticateMessage(challenge_flags, nt_response, lm_response, request_session_key, user, domain = 'WORKGROUP', workstation = 'LOCALHOST'):
"""
References:
===========
- [MS-NLMP]: 2.2.1.3
"""
FORMAT = '<8sIHHIHHIHHIHHIHHIHHII'
FORMAT_SIZE = struct.calcsize(FORMAT)
# [MS-NLMP]: 3.1.5.1.2
# http://grutz.jingojango.net/exploits/davenport-ntlm.html
session_key = session_signing_key = request_session_key
if challenge_flags & NTLM_NegotiateKeyExchange:
session_signing_key = "".join([ random.choice(string.digits+string.ascii_letters) for _ in range(16) ]).encode('ascii')
session_key = RC4_encrypt(request_session_key, session_signing_key)
lm_response_length = len(lm_response)
lm_response_offset = FORMAT_SIZE
nt_response_length = len(nt_response)
nt_response_offset = lm_response_offset + lm_response_length
domain_unicode = domain.encode('UTF-16LE')
domain_length = len(domain_unicode)
domain_offset = nt_response_offset + nt_response_length
padding = b''
if domain_offset % 2 != 0:
padding = b'\0'
domain_offset += 1
user_unicode = user.encode('UTF-16LE')
user_length = len(user_unicode)
user_offset = domain_offset + domain_length
workstation_unicode = workstation.encode('UTF-16LE')
workstation_length = len(workstation_unicode)
workstation_offset = user_offset + user_length
session_key_length = len(session_key)
session_key_offset = workstation_offset + workstation_length
auth_flags = challenge_flags
auth_flags &= ~NTLM_NegotiateVersion
s = struct.pack(FORMAT,
b'NTLMSSP\0', 0x03,
lm_response_length, lm_response_length, lm_response_offset,
nt_response_length, nt_response_length, nt_response_offset,
domain_length, domain_length, domain_offset,
user_length, user_length, user_offset,
workstation_length, workstation_length, workstation_offset,
session_key_length, session_key_length, session_key_offset,
auth_flags)
return s + lm_response + nt_response + padding + domain_unicode + user_unicode + workstation_unicode + session_key, session_signing_key
def decodeChallengeMessage(ntlm_data):
"""
References:
===========
- [MS-NLMP]: 2.2.1.2
- [MS-NLMP]: 2.2.2.1 (AV_PAIR)
"""
FORMAT = '<8sIHHII8s8sHHI'
FORMAT_SIZE = struct.calcsize(FORMAT)
signature, message_type, \
targetname_len, targetname_maxlen, targetname_offset, \
flags, challenge, _, \
targetinfo_len, targetinfo_maxlen, targetinfo_offset, \
= struct.unpack(FORMAT, bytes(ntlm_data[:FORMAT_SIZE]))
assert signature == b'NTLMSSP\0'
assert message_type == 0x02
return challenge, flags, bytes(ntlm_data[targetinfo_offset:targetinfo_offset+targetinfo_len])
def generateChallengeResponseV2(password, user, server_challenge, server_info, domain = '', client_challenge = None):
client_timestamp = b'\0' * 8
if not client_challenge:
client_challenge = bytes([ random.getrandbits(8) for i in range(0, 8) ])
assert len(client_challenge) == 8
d = MD4()
d.update(password.encode('UTF-16LE'))
ntlm_hash = d.digest() # The NT password hash
response_key = hmac.new(ntlm_hash, (user.upper() + domain).encode('UTF-16LE'), 'md5').digest() # The NTLMv2 password hash. In [MS-NLMP], this is the result of NTOWFv2 and LMOWFv2 functions
temp = b'\x01\x01' + b'\0'*6 + client_timestamp + client_challenge + b'\0'*4 + server_info
ntproofstr = hmac.new(response_key, server_challenge + temp, 'md5').digest()
nt_challenge_response = ntproofstr + temp
lm_challenge_response = hmac.new(response_key, server_challenge + client_challenge, 'md5').digest() + client_challenge
session_key = hmac.new(response_key, ntproofstr, 'md5').digest()
return nt_challenge_response, lm_challenge_response, session_key
################
# NTLMv1 Methods
################
def expandDesKey(key):
"""Expand the key from a 7-byte password key into a 8-byte DES key"""
s = [ ((key[0] >> 1) & 0x7f) << 1,
((key[0] & 0x01) << 6 | ((key[1] >> 2) & 0x3f)) << 1,
((key[1] & 0x03) << 5 | ((key[2] >> 3) & 0x1f)) << 1,
((key[2] & 0x07) << 4 | ((key[3] >> 4) & 0x0f)) << 1,
((key[3] & 0x0f) << 3 | ((key[4] >> 5) & 0x07)) << 1,
((key[4] & 0x1f) << 2 | ((key[5] >> 6) & 0x03)) << 1,
((key[5] & 0x3f) << 1 | ((key[6] >> 7) & 0x01)) << 1,
(key[6] & 0x7f) << 1
]
return bytes(s)
def DESL(K, D):
"""
References:
===========
- http://ubiqx.org/cifs/SMB.html (2.8.3.4)
- [MS-NLMP]: Section 6
"""
d1 = des(expandDesKey(K[0:7]))
d2 = des(expandDesKey(K[7:14]))
d3 = des(expandDesKey(K[14:16] + b'\0' * 5))
return d1.encrypt(D) + d2.encrypt(D) + d3.encrypt(D)
def generateChallengeResponseV1(password, server_challenge, has_extended_security = False, client_challenge = None):
"""
Generate a NTLMv1 response
@param password: User password string
@param server_challange: A 8-byte challenge string sent from the server
@param has_extended_security: A boolean value indicating whether NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY flag is enabled in the NTLM negFlag
@param client_challenge: A 8-byte string representing client challenge. If None, it will be generated randomly if needed by the response generation
@return: a tuple of ( NT challenge response string, LM challenge response string )
References:
===========
- http://ubiqx.org/cifs/SMB.html (2.8.3.3 and 2.8.3.4)
- [MS-NLMP]: 3.3.1
"""
_password = bytes((password.upper() + '\0' * 14)[:14], 'ascii')
d1 = des(expandDesKey(_password[:7]))
d2 = des(expandDesKey(_password[7:]))
lm_response_key = d1.encrypt(b"KGS!@#$%") + d2.encrypt(b"KGS!@#$%") # LM password hash. In [MS-NLMP], this is the result of LMOWFv1 function
d = MD4()
d.update(password.encode('UTF-16LE'))
nt_response_key = d.digest() # In [MS-NLMP], this is the result of NTOWFv1 function
if has_extended_security:
if not client_challenge:
client_challenge = bytes([ random.getrandbits(8) for i in range(0, 8) ])
assert len(client_challenge) == 8
lm_challenge_response = client_challenge + b'\0'*16
nt_challenge_response = DESL(nt_response_key, MD5(server_challenge + client_challenge).digest()[0:8])
else:
nt_challenge_response = DESL(nt_response_key, server_challenge) # The result after DESL is the NT response
lm_challenge_response = DESL(lm_response_key, server_challenge) # The result after DESL is the LM response
d = MD4()
d.update(nt_response_key)
session_key = d.digest()
return nt_challenge_response, lm_challenge_response, session_key

View File

@@ -0,0 +1,367 @@
"""
This module implements security descriptors, and the partial structures
used in them, as specified in [MS-DTYP].
"""
import struct
# Security descriptor control flags
# [MS-DTYP]: 2.4.6
SECURITY_DESCRIPTOR_OWNER_DEFAULTED = 0x0001
SECURITY_DESCRIPTOR_GROUP_DEFAULTED = 0x0002
SECURITY_DESCRIPTOR_DACL_PRESENT = 0x0004
SECURITY_DESCRIPTOR_DACL_DEFAULTED = 0x0008
SECURITY_DESCRIPTOR_SACL_PRESENT = 0x0010
SECURITY_DESCRIPTOR_SACL_DEFAULTED = 0x0020
SECURITY_DESCRIPTOR_SERVER_SECURITY = 0x0040
SECURITY_DESCRIPTOR_DACL_TRUSTED = 0x0080
SECURITY_DESCRIPTOR_DACL_COMPUTED_INHERITANCE_REQUIRED = 0x0100
SECURITY_DESCRIPTOR_SACL_COMPUTED_INHERITANCE_REQUIRED = 0x0200
SECURITY_DESCRIPTOR_DACL_AUTO_INHERITED = 0x0400
SECURITY_DESCRIPTOR_SACL_AUTO_INHERITED = 0x0800
SECURITY_DESCRIPTOR_DACL_PROTECTED = 0x1000
SECURITY_DESCRIPTOR_SACL_PROTECTED = 0x2000
SECURITY_DESCRIPTOR_RM_CONTROL_VALID = 0x4000
SECURITY_DESCRIPTOR_SELF_RELATIVE = 0x8000
# ACE types
# [MS-DTYP]: 2.4.4.1
ACE_TYPE_ACCESS_ALLOWED = 0x00
ACE_TYPE_ACCESS_DENIED = 0x01
ACE_TYPE_SYSTEM_AUDIT = 0x02
ACE_TYPE_SYSTEM_ALARM = 0x03
ACE_TYPE_ACCESS_ALLOWED_COMPOUND = 0x04
ACE_TYPE_ACCESS_ALLOWED_OBJECT = 0x05
ACE_TYPE_ACCESS_DENIED_OBJECT = 0x06
ACE_TYPE_SYSTEM_AUDIT_OBJECT = 0x07
ACE_TYPE_SYSTEM_ALARM_OBJECT = 0x08
ACE_TYPE_ACCESS_ALLOWED_CALLBACK = 0x09
ACE_TYPE_ACCESS_DENIED_CALLBACK = 0x0A
ACE_TYPE_ACCESS_ALLOWED_CALLBACK_OBJECT = 0x0B
ACE_TYPE_ACCESS_DENIED_CALLBACK_OBJECT = 0x0C
ACE_TYPE_SYSTEM_AUDIT_CALLBACK = 0x0D
ACE_TYPE_SYSTEM_ALARM_CALLBACK = 0x0E
ACE_TYPE_SYSTEM_AUDIT_CALLBACK_OBJECT = 0x0F
ACE_TYPE_SYSTEM_ALARM_CALLBACK_OBJECT = 0x10
ACE_TYPE_SYSTEM_MANDATORY_LABEL = 0x11
ACE_TYPE_SYSTEM_RESOURCE_ATTRIBUTE = 0x12
ACE_TYPE_SYSTEM_SCOPED_POLICY_ID = 0x13
# ACE flags
# [MS-DTYP]: 2.4.4.1
ACE_FLAG_OBJECT_INHERIT = 0x01
ACE_FLAG_CONTAINER_INHERIT = 0x02
ACE_FLAG_NO_PROPAGATE_INHERIT = 0x04
ACE_FLAG_INHERIT_ONLY = 0x08
ACE_FLAG_INHERITED = 0x10
ACE_FLAG_SUCCESSFUL_ACCESS = 0x40
ACE_FLAG_FAILED_ACCESS = 0x80
# Pre-defined well-known SIDs
# [MS-DTYP]: 2.4.2.4
SID_NULL = "S-1-0-0"
SID_EVERYONE = "S-1-1-0"
SID_LOCAL = "S-1-2-0"
SID_CONSOLE_LOGON = "S-1-2-1"
SID_CREATOR_OWNER = "S-1-3-0"
SID_CREATOR_GROUP = "S-1-3-1"
SID_OWNER_SERVER = "S-1-3-2"
SID_GROUP_SERVER = "S-1-3-3"
SID_OWNER_RIGHTS = "S-1-3-4"
SID_NT_AUTHORITY = "S-1-5"
SID_DIALUP = "S-1-5-1"
SID_NETWORK = "S-1-5-2"
SID_BATCH = "S-1-5-3"
SID_INTERACTIVE = "S-1-5-4"
SID_SERVICE = "S-1-5-6"
SID_ANONYMOUS = "S-1-5-7"
SID_PROXY = "S-1-5-8"
SID_ENTERPRISE_DOMAIN_CONTROLLERS = "S-1-5-9"
SID_PRINCIPAL_SELF = "S-1-5-10"
SID_AUTHENTICATED_USERS = "S-1-5-11"
SID_RESTRICTED_CODE = "S-1-5-12"
SID_TERMINAL_SERVER_USER = "S-1-5-13"
SID_REMOTE_INTERACTIVE_LOGON = "S-1-5-14"
SID_THIS_ORGANIZATION = "S-1-5-15"
SID_IUSR = "S-1-5-17"
SID_LOCAL_SYSTEM = "S-1-5-18"
SID_LOCAL_SERVICE = "S-1-5-19"
SID_NETWORK_SERVICE = "S-1-5-20"
SID_COMPOUNDED_AUTHENTICATION = "S-1-5-21-0-0-0-496"
SID_CLAIMS_VALID = "S-1-5-21-0-0-0-497"
SID_BUILTIN_ADMINISTRATORS = "S-1-5-32-544"
SID_BUILTIN_USERS = "S-1-5-32-545"
SID_BUILTIN_GUESTS = "S-1-5-32-546"
SID_POWER_USERS = "S-1-5-32-547"
SID_ACCOUNT_OPERATORS = "S-1-5-32-548"
SID_SERVER_OPERATORS = "S-1-5-32-549"
SID_PRINTER_OPERATORS = "S-1-5-32-550"
SID_BACKUP_OPERATORS = "S-1-5-32-551"
SID_REPLICATOR = "S-1-5-32-552"
SID_ALIAS_PREW2KCOMPACC = "S-1-5-32-554"
SID_REMOTE_DESKTOP = "S-1-5-32-555"
SID_NETWORK_CONFIGURATION_OPS = "S-1-5-32-556"
SID_INCOMING_FOREST_TRUST_BUILDERS = "S-1-5-32-557"
SID_PERFMON_USERS = "S-1-5-32-558"
SID_PERFLOG_USERS = "S-1-5-32-559"
SID_WINDOWS_AUTHORIZATION_ACCESS_GROUP = "S-1-5-32-560"
SID_TERMINAL_SERVER_LICENSE_SERVERS = "S-1-5-32-561"
SID_DISTRIBUTED_COM_USERS = "S-1-5-32-562"
SID_IIS_IUSRS = "S-1-5-32-568"
SID_CRYPTOGRAPHIC_OPERATORS = "S-1-5-32-569"
SID_EVENT_LOG_READERS = "S-1-5-32-573"
SID_CERTIFICATE_SERVICE_DCOM_ACCESS = "S-1-5-32-574"
SID_RDS_REMOTE_ACCESS_SERVERS = "S-1-5-32-575"
SID_RDS_ENDPOINT_SERVERS = "S-1-5-32-576"
SID_RDS_MANAGEMENT_SERVERS = "S-1-5-32-577"
SID_HYPER_V_ADMINS = "S-1-5-32-578"
SID_ACCESS_CONTROL_ASSISTANCE_OPS = "S-1-5-32-579"
SID_REMOTE_MANAGEMENT_USERS = "S-1-5-32-580"
SID_WRITE_RESTRICTED_CODE = "S-1-5-33"
SID_NTLM_AUTHENTICATION = "S-1-5-64-10"
SID_SCHANNEL_AUTHENTICATION = "S-1-5-64-14"
SID_DIGEST_AUTHENTICATION = "S-1-5-64-21"
SID_THIS_ORGANIZATION_CERTIFICATE = "S-1-5-65-1"
SID_NT_SERVICE = "S-1-5-80"
SID_USER_MODE_DRIVERS = "S-1-5-84-0-0-0-0-0"
SID_LOCAL_ACCOUNT = "S-1-5-113"
SID_LOCAL_ACCOUNT_AND_MEMBER_OF_ADMINISTRATORS_GROUP = "S-1-5-114"
SID_OTHER_ORGANIZATION = "S-1-5-1000"
SID_ALL_APP_PACKAGES = "S-1-15-2-1"
SID_ML_UNTRUSTED = "S-1-16-0"
SID_ML_LOW = "S-1-16-4096"
SID_ML_MEDIUM = "S-1-16-8192"
SID_ML_MEDIUM_PLUS = "S-1-16-8448"
SID_ML_HIGH = "S-1-16-12288"
SID_ML_SYSTEM = "S-1-16-16384"
SID_ML_PROTECTED_PROCESS = "S-1-16-20480"
SID_AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY = "S-1-18-1"
SID_SERVICE_ASSERTED_IDENTITY = "S-1-18-2"
SID_FRESH_PUBLIC_KEY_IDENTITY = "S-1-18-3"
SID_KEY_TRUST_IDENTITY = "S-1-18-4"
SID_KEY_PROPERTY_MFA = "S-1-18-5"
SID_KEY_PROPERTY_ATTESTATION = "S-1-18-6"
class SID(object):
"""
A Windows security identifier. Represents a single principal, such a
user or a group, as a sequence of numbers consisting of the revision,
identifier authority, and a variable-length list of subauthorities.
See [MS-DTYP]: 2.4.2
"""
def __init__(self, revision, identifier_authority, subauthorities):
#: Revision, should always be 1.
self.revision = revision
#: An integer representing the identifier authority.
self.identifier_authority = identifier_authority
#: A list of integers representing all subauthorities.
self.subauthorities = subauthorities
def __str__(self):
"""
String representation, as specified in [MS-DTYP]: 2.4.2.1
"""
if self.identifier_authority >= 2**32:
id_auth = '%#x' % (self.identifier_authority,)
else:
id_auth = self.identifier_authority
auths = [self.revision, id_auth] + self.subauthorities
return 'S-' + '-'.join(str(subauth) for subauth in auths)
def __repr__(self):
return 'SID(%r)' % (str(self),)
@classmethod
def from_bytes(cls, data, return_tail=False):
revision, subauth_count = struct.unpack('<BB', data[:2])
identifier_authority = struct.unpack('>Q', b'\x00\x00' + data[2:8])[0]
subauth_data = data[8:]
subauthorities = [struct.unpack('<L', subauth_data[4 * i : 4 * (i+1)])[0]
for i in range(subauth_count)]
sid = cls(revision, identifier_authority, subauthorities)
if return_tail:
return sid, subauth_data[4 * subauth_count :]
return sid
class ACE(object):
"""
Represents a single access control entry.
See [MS-DTYP]: 2.4.4
"""
HEADER_FORMAT = '<BBH'
def __init__(self, type_, flags, mask, sid, additional_data):
#: An integer representing the type of the ACE. One of the
#: ``ACE_TYPE_*`` constants. Corresponds to the ``AceType`` field
#: from [MS-DTYP] 2.4.4.1.
self.type = type_
#: An integer bitmask with ACE flags, corresponds to the
#: ``AceFlags`` field.
self.flags = flags
#: An integer representing the ``ACCESS_MASK`` as specified in
#: [MS-DTYP] 2.4.3.
self.mask = mask
#: The :class:`SID` of a trustee.
self.sid = sid
#: A dictionary of additional fields present in the ACE, depending
#: on the type. The following fields can be present:
#:
#: * ``flags``
#: * ``object_type``
#: * ``inherited_object_type``
#: * ``application_data``
#: * ``attribute_data``
self.additional_data = additional_data
def __repr__(self):
return "ACE(type=%#04x, flags=%#04x, mask=%#010x, sid=%s)" % (
self.type, self.flags, self.mask, self.sid,
)
@property
def isInheritOnly(self):
"""Convenience property which indicates if this ACE is inherit
only, meaning that it doesn't apply to the object itself."""
return bool(self.flags & ACE_FLAG_INHERIT_ONLY)
@classmethod
def from_bytes(cls, data):
header_size = struct.calcsize(cls.HEADER_FORMAT)
header = data[:header_size]
type_, flags, size = struct.unpack(cls.HEADER_FORMAT, header)
assert len(data) >= size
body = data[header_size:size]
additional_data = {}
# In all ACE types, the mask immediately follows the header.
mask = struct.unpack('<I', body[:4])[0]
body = body[4:]
# All OBJECT-type ACEs contain additional flags, and two GUIDs as
# the following fields.
if type_ in (ACE_TYPE_ACCESS_ALLOWED_OBJECT,
ACE_TYPE_ACCESS_DENIED_OBJECT,
ACE_TYPE_ACCESS_ALLOWED_CALLBACK_OBJECT,
ACE_TYPE_ACCESS_DENIED_CALLBACK_OBJECT,
ACE_TYPE_SYSTEM_AUDIT_OBJECT,
ACE_TYPE_SYSTEM_AUDIT_CALLBACK_OBJECT):
additional_data['flags'] = struct.unpack('<I', body[:4])[0]
additional_data['object_type'] = body[4:20]
additional_data['inherited_object_type'] = body[20:36]
body = body[36:]
# Then the SID in all types.
sid, body = SID.from_bytes(body, return_tail=True)
# CALLBACK-type ACEs (and for some obscure reason,
# SYSTEM_AUDIT_OBJECT) have a final tail of application data.
if type_ in (ACE_TYPE_ACCESS_ALLOWED_CALLBACK,
ACE_TYPE_ACCESS_DENIED_CALLBACK,
ACE_TYPE_ACCESS_ALLOWED_CALLBACK_OBJECT,
ACE_TYPE_ACCESS_DENIED_CALLBACK_OBJECT,
ACE_TYPE_SYSTEM_AUDIT_OBJECT,
ACE_TYPE_SYSTEM_AUDIT_CALLBACK,
ACE_TYPE_SYSTEM_AUDIT_CALLBACK_OBJECT):
additional_data['application_data'] = body
# SYSTEM_RESOURCE_ATTRIBUTE ACEs have a tail of attribute data.
if type_ == ACE_TYPE_SYSTEM_RESOURCE_ATTRIBUTE:
additional_data['attribute_data'] = body
return cls(type_, flags, mask, sid, additional_data)
class ACL(object):
"""
Access control list, encapsulating a sequence of access control
entries.
See [MS-DTYP]: 2.4.5
"""
HEADER_FORMAT = '<BBHHH'
def __init__(self, revision, aces):
#: Integer value of the revision.
self.revision = revision
#: List of :class:`ACE` instances.
self.aces = aces
def __repr__(self):
return "ACL(%r)" % (self.aces,)
@classmethod
def from_bytes(cls, data):
revision = None
aces = []
header_size = struct.calcsize(cls.HEADER_FORMAT)
header, remaining = data[:header_size], data[header_size:]
revision, sbz1, size, count, sbz2 = struct.unpack(cls.HEADER_FORMAT, header)
assert len(data) >= size
for i in range(count):
ace_size = struct.unpack('<H', remaining[2:4])[0]
ace_data, remaining = remaining[:ace_size], remaining[ace_size:]
aces.append(ACE.from_bytes(ace_data))
return cls(revision, aces)
class SecurityDescriptor(object):
"""
Represents a security descriptor.
See [MS-DTYP]: 2.4.6
"""
HEADER_FORMAT = '<BBHIIII'
def __init__(self, flags, owner, group, dacl, sacl):
#: Integer bitmask of control flags. Corresponds to the
#: ``Control`` field in [MS-DTYP] 2.4.6.
self.flags = flags
#: Instance of :class:`SID` representing the owner user.
self.owner = owner
#: Instance of :class:`SID` representing the owner group.
self.group = group
#: Instance of :class:`ACL` representing the discretionary access
#: control list, which specifies access restrictions of an object.
self.dacl = dacl
#: Instance of :class:`ACL` representing the system access control
#: list, which specifies audit logging of an object.
self.sacl = sacl
@classmethod
def from_bytes(cls, data):
owner = None
group = None
dacl = None
sacl = None
header = data[:struct.calcsize(cls.HEADER_FORMAT)]
(revision, sbz1, flags, owner_offset, group_offset, sacl_offset,
dacl_offset) = struct.unpack(cls.HEADER_FORMAT, header)
assert revision == 1
assert flags & SECURITY_DESCRIPTOR_SELF_RELATIVE
for offset in (owner_offset, group_offset, sacl_offset, dacl_offset):
assert 0 <= offset < len(data)
if owner_offset:
owner = SID.from_bytes(data[owner_offset:])
if group_offset:
group = SID.from_bytes(data[group_offset:])
if dacl_offset:
dacl = ACL.from_bytes(data[dacl_offset:])
if sacl_offset:
sacl = ACL.from_bytes(data[sacl_offset:])
return cls(flags, owner, group, dacl, sacl)

View File

@@ -0,0 +1,136 @@
from pyasn1.type import tag, univ, namedtype, namedval, constraint
from pyasn1.codec.der import encoder, decoder
__all__ = [ 'generateNegotiateSecurityBlob', 'generateAuthSecurityBlob', 'decodeChallengeSecurityBlob', 'decodeAuthResponseSecurityBlob' ]
class UnsupportedSecurityProvider(Exception): pass
class BadSecurityBlobError(Exception): pass
def generateNegotiateSecurityBlob(ntlm_data):
mech_token = univ.OctetString(ntlm_data).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))
mech_types = MechTypeList().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))
mech_types.setComponentByPosition(0, univ.ObjectIdentifier('1.3.6.1.4.1.311.2.2.10'))
n = NegTokenInit().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))
n.setComponentByName('mechTypes', mech_types)
n.setComponentByName('mechToken', mech_token)
nt = NegotiationToken()
nt.setComponentByName('negTokenInit', n)
ct = ContextToken()
ct.setComponentByName('thisMech', univ.ObjectIdentifier('1.3.6.1.5.5.2'))
ct.setComponentByName('innerContextToken', nt)
return encoder.encode(ct)
def generateAuthSecurityBlob(ntlm_data):
response_token = univ.OctetString(ntlm_data).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))
n = NegTokenTarg().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))
n.setComponentByName('responseToken', response_token)
nt = NegotiationToken()
nt.setComponentByName('negTokenTarg', n)
return encoder.encode(nt)
def decodeChallengeSecurityBlob(data):
try:
d, _ = decoder.decode(data, asn1Spec = NegotiationToken())
nt = d.getComponentByName('negTokenTarg')
token = nt.getComponentByName('responseToken')
if not token:
raise BadSecurityBlobError('NTLMSSP_CHALLENGE security blob does not contain responseToken field')
provider_oid = nt.getComponentByName('supportedMech')
if provider_oid and str(provider_oid) != '1.3.6.1.4.1.311.2.2.10': # This OID is defined in [MS-NLMP]: 1.9
raise UnsupportedSecurityProvider('Security provider "%s" is not supported by pysmb' % str(provider_oid))
result = nt.getComponentByName('negResult')
return int(result), token
except Exception as ex:
raise BadSecurityBlobError(str(ex))
def decodeAuthResponseSecurityBlob(data):
try:
d, _ = decoder.decode(data, asn1Spec = NegotiationToken())
nt = d.getComponentByName('negTokenTarg')
result = nt.getComponentByName('negResult')
return int(result)
except Exception as ex:
raise BadSecurityBlobError(str(ex))
#
# GSS-API ASN.1 (RFC2478 section 3.2.1)
#
RESULT_ACCEPT_COMPLETED = 0
RESULT_ACCEPT_INCOMPLETE = 1
RESULT_REJECT = 2
class NegResultEnumerated(univ.Enumerated):
namedValues = namedval.NamedValues(
( 'accept_completed', 0 ),
( 'accept_incomplete', 1 ),
( 'reject', 2 )
)
subtypeSpec = univ.Enumerated.subtypeSpec + constraint.SingleValueConstraint(0, 1, 2)
class MechTypeList(univ.SequenceOf):
componentType = univ.ObjectIdentifier()
class ContextFlags(univ.BitString):
namedValues = namedval.NamedValues(
( 'delegFlag', 0 ),
( 'mutualFlag', 1 ),
( 'replayFlag', 2 ),
( 'sequenceFlag', 3 ),
( 'anonFlag', 4 ),
( 'confFlag', 5 ),
( 'integFlag', 6 )
)
class NegTokenInit(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.OptionalNamedType('mechTypes', MechTypeList().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
namedtype.OptionalNamedType('reqFlags', ContextFlags().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
namedtype.OptionalNamedType('mechToken', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
namedtype.OptionalNamedType('mechListMIC', univ.OctetString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)))
)
class NegTokenTarg(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.OptionalNamedType('negResult', NegResultEnumerated().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
namedtype.OptionalNamedType('supportedMech', univ.ObjectIdentifier().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
namedtype.OptionalNamedType('responseToken', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))),
namedtype.OptionalNamedType('mechListMIC', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3)))
)
class NegotiationToken(univ.Choice):
componentType = namedtype.NamedTypes(
namedtype.NamedType('negTokenInit', NegTokenInit().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))),
namedtype.NamedType('negTokenTarg', NegTokenTarg().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)))
)
class ContextToken(univ.Sequence):
tagSet = univ.Sequence.tagSet.tagImplicitly(tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 0))
componentType = namedtype.NamedTypes(
namedtype.NamedType('thisMech', univ.ObjectIdentifier()),
namedtype.NamedType('innerContextToken', NegotiationToken())
)

View File

@@ -0,0 +1,101 @@
# Bitmask for Flags field in SMB2 message header
SMB2_FLAGS_SERVER_TO_REDIR = 0x01
SMB2_FLAGS_ASYNC_COMMAND = 0x02
SMB2_FLAGS_RELATED_OPERATIONS = 0x04
SMB2_FLAGS_SIGNED = 0x08
SMB2_FLAGS_DFS_OPERATIONS = 0x10000000
# Values for Command field in SMB2 message header
SMB2_COM_NEGOTIATE = 0x0000
SMB2_COM_SESSION_SETUP = 0x0001
SMB2_COM_LOGOFF = 0x0002
SMB2_COM_TREE_CONNECT = 0x0003
SMB2_COM_TREE_DISCONNECT = 0x0004
SMB2_COM_CREATE = 0x0005
SMB2_COM_CLOSE = 0x0006
SMB2_COM_FLUSH = 0x0007
SMB2_COM_READ = 0x0008
SMB2_COM_WRITE = 0x0009
SMB2_COM_LOCK = 0x000A
SMB2_COM_IOCTL = 0x000B
SMB2_COM_CANCEL = 0x000C
SMB2_COM_ECHO = 0x000D
SMB2_COM_QUERY_DIRECTORY = 0x000E
SMB2_COM_CHANGE_NOTIFY = 0x000F
SMB2_COM_QUERY_INFO = 0x0010
SMB2_COM_SET_INFO = 0x0011
SMB2_COM_OPLOCK_BREAK = 0x0012
SMB2_COMMAND_NAMES = {
0x0000: 'SMB2_COM_NEGOTIATE',
0x0001: 'SMB2_COM_SESSION_SETUP',
0x0002: 'SMB2_COM_LOGOFF',
0x0003: 'SMB2_COM_TREE_CONNECT',
0x0004: 'SMB2_COM_TREE_DISCONNECT',
0x0005: 'SMB2_COM_CREATE',
0x0006: 'SMB2_COM_CLOSE',
0x0007: 'SMB2_COM_FLUSH',
0x0008: 'SMB2_COM_READ',
0x0009: 'SMB2_COM_WRITE',
0x000A: 'SMB2_COM_LOCK',
0x000B: 'SMB2_COM_IOCTL',
0x000C: 'SMB2_COM_CANCEL',
0x000D: 'SMB2_COM_ECHO',
0x000E: 'SMB2_COM_QUERY_DIRECTORY',
0x000F: 'SMB2_COM_CHANGE_NOTIFY',
0x0010: 'SMB2_COM_QUERY_INFO',
0x0011: 'SMB2_COM_SET_INFO',
0x0012: 'SMB2_COM_OPLOCK_BREAK',
}
# Values for dialect_revision field in SMB2NegotiateResponse class
SMB2_DIALECT_2 = 0x0202
SMB2_DIALECT_21 = 0x0210
SMB2_DIALECT_2ALL = 0x02FF
# Bit mask for SecurityMode field in SMB2NegotiateResponse class
SMB2_NEGOTIATE_SIGNING_ENABLED = 0x0001
SMB2_NEGOTIATE_SIGNING_REQUIRED = 0x0002
# Values for ShareType field in SMB2TreeConnectResponse class
SMB2_SHARE_TYPE_DISK = 0x01
SMB2_SHARE_TYPE_PIPE = 0x02
SMB2_SHARE_TYPE_PRINTER = 0x03
# Bitmask for Capabilities in SMB2TreeConnectResponse class
SMB2_SHARE_CAP_DFS = 0x0008
# Values for OpLockLevel field in SMB2CreateRequest class
SMB2_OPLOCK_LEVEL_NONE = 0x00
SMB2_OPLOCK_LEVEL_II = 0x01
SMB2_OPLOCK_LEVEL_EXCLUSIVE = 0x08
SMB2_OPLOCK_LEVEL_BATCH = 0x09
SMB2_OPLOCK_LEVEL_LEASE = 0xFF
# Values for FileAttributes field in SMB2CreateRequest class
# The values are defined in [MS-FSCC] 2.6
SMB2_FILE_ATTRIBUTE_ARCHIVE = 0x0020
SMB2_FILE_ATTRIBUTE_COMPRESSED = 0x0800
SMB2_FILE_ATTRIBUTE_DIRECTORY = 0x0010
SMB2_FILE_ATTRIBUTE_ENCRYPTED = 0x4000
SMB2_FILE_ATTRIBUTE_HIDDEN = 0x0002
SMB2_FILE_ATTRIBUTE_NORMAL = 0x0080
SMB2_FILE_ATTRIBUTE_NOTINDEXED = 0x2000
SMB2_FILE_ATTRIBUTE_OFFLINE = 0x1000
SMB2_FILE_ATTRIBUTE_READONLY = 0x0001
SMB2_FILE_ATTRIBUTE_SPARSE = 0x0200
SMB2_FILE_ATTRIBUTE_SYSTEM = 0x0004
SMB2_FILE_ATTRIBUTE_TEMPORARY = 0x0100
# Values for CreateAction field in SMB2CreateResponse class
SMB2_FILE_SUPERCEDED = 0x00
SMB2_FILE_OPENED = 0x01
SMB2_FILE_CREATED = 0x02
SMB2_FILE_OVERWRITTEN = 0x03
# Values for InfoType field in SMB2QueryInfoRequest class
SMB2_INFO_FILE = 0x01
SMB2_INFO_FILESYSTEM = 0x02
SMB2_INFO_SECURITY = 0x03
SMB2_INFO_QUOTA = 0x04

View File

@@ -0,0 +1,857 @@
import os, sys, struct, types, logging, binascii, time
from io import StringIO
from .smb_structs import ProtocolError
from .smb_constants import *
from .smb2_constants import *
from .utils import convertFILETIMEtoEpoch
class SMB2Message:
HEADER_STRUCT_FORMAT = "<4sHHIHHI" # This refers to the common header part that is shared by both sync and async SMB2 header
HEADER_STRUCT_SIZE = struct.calcsize(HEADER_STRUCT_FORMAT)
ASYNC_HEADER_STRUCT_FORMAT = "<IQQQ16s"
ASYNC_HEADER_STRUCT_SIZE = struct.calcsize(ASYNC_HEADER_STRUCT_FORMAT)
SYNC_HEADER_STRUCT_FORMAT = "<IQIIQ16s"
SYNC_HEADER_STRUCT_SIZE = struct.calcsize(SYNC_HEADER_STRUCT_FORMAT)
HEADER_SIZE = 64
log = logging.getLogger('SMB.SMB2Message')
protocol = 2
def __init__(self, payload = None):
self.reset()
if payload:
self.payload = payload
self.payload.initMessage(self)
def __str__(self):
b = StringIO()
b.write('Command: 0x%02X (%s) %s' % ( self.command, SMB2_COMMAND_NAMES.get(self.command, '<unknown>'), os.linesep ))
b.write('Status: 0x%08X %s' % ( self.status, os.linesep ))
b.write('Flags: 0x%02X %s' % ( self.flags, os.linesep ))
b.write('PID: %d %s' % ( self.pid, os.linesep ))
b.write('MID: %d %s' % ( self.mid, os.linesep ))
b.write('TID: %d %s' % ( self.tid, os.linesep ))
b.write('Data: %d bytes %s%s %s' % ( len(self.data), os.linesep, str(binascii.hexlify(self.data)), os.linesep ))
return b.getvalue()
def reset(self):
self.raw_data = b''
self.command = 0
self.status = 0
self.flags = 0
self.next_command_offset = 0
self.mid = 0
self.session_id = 0
self.signature = b'\0'*16
self.payload = None
self.data = b''
# For async SMB2 message
self.async_id = 0
# For sync SMB2 message
self.pid = 0
self.tid = 0
# Not used in this class. Maintained for compatibility with SMBMessage class
self.flags2 = 0
self.uid = 0
self.security = 0
self.parameters_data = b''
def encode(self):
"""
Encode this SMB2 message into a series of bytes suitable to be embedded with a NetBIOS session message.
AssertionError will be raised if this SMB message has not been initialized with a Payload instance
@return: a string containing the encoded SMB2 message
"""
assert self.payload
self.pid = os.getpid()
self.payload.prepare(self)
headers_data = struct.pack(self.HEADER_STRUCT_FORMAT,
b'\xFESMB', self.HEADER_SIZE, 0, self.status, self.command, 0, self.flags) + \
struct.pack(self.SYNC_HEADER_STRUCT_FORMAT, self.next_command_offset, self.mid, self.pid, self.tid, self.session_id, self.signature)
return headers_data + self.data
def decode(self, buf):
"""
Decodes the SMB message in buf.
All fields of the SMB2Message object will be reset to default values before decoding.
On errors, do not assume that the fields will be reinstated back to what they are before
this method is invoked.
References
==========
- [MS-SMB2]: 2.2.1
@param buf: data containing one complete SMB2 message
@type buf: string
@return: a positive integer indicating the number of bytes used in buf to decode this SMB message
@raise ProtocolError: raised when decoding fails
"""
buf_len = len(buf)
if buf_len < 64: # All SMB2 headers must be at least 64 bytes. [MS-SMB2]: 2.2.1.1, 2.2.1.2
raise ProtocolError('Not enough data to decode SMB2 header', buf)
self.reset()
protocol, struct_size, self.credit_charge, self.status, \
self.command, self.credit_re, self.flags = struct.unpack(self.HEADER_STRUCT_FORMAT, buf[:self.HEADER_STRUCT_SIZE])
if protocol != b'\xFESMB':
raise ProtocolError('Invalid 4-byte SMB2 protocol field', buf)
if struct_size != self.HEADER_SIZE:
raise ProtocolError('Invalid SMB2 header structure size')
if self.isAsync:
if buf_len < self.HEADER_STRUCT_SIZE+self.ASYNC_HEADER_STRUCT_SIZE:
raise ProtocolError('Not enough data to decode SMB2 header', buf)
self.next_command_offset, self.mid, self.async_id, self.session_id, \
self.signature = struct.unpack(self.ASYNC_HEADER_STRUCT_FORMAT,
buf[self.HEADER_STRUCT_SIZE:self.HEADER_STRUCT_SIZE+self.ASYNC_HEADER_STRUCT_SIZE])
else:
if buf_len < self.HEADER_STRUCT_SIZE+self.SYNC_HEADER_STRUCT_SIZE:
raise ProtocolError('Not enough data to decode SMB2 header', buf)
self.next_command_offset, self.mid, self.pid, self.tid, self.session_id, \
self.signature = struct.unpack(self.SYNC_HEADER_STRUCT_FORMAT,
buf[self.HEADER_STRUCT_SIZE:self.HEADER_STRUCT_SIZE+self.SYNC_HEADER_STRUCT_SIZE])
if self.next_command_offset > 0:
self.raw_data = buf[:self.next_command_offset]
self.data = buf[self.HEADER_SIZE:self.next_command_offset]
else:
self.raw_data = buf
self.data = buf[self.HEADER_SIZE:]
self._decodeCommand()
if self.payload:
self.payload.decode(self)
return len(self.raw_data)
def _decodeCommand(self):
if self.command == SMB2_COM_READ:
self.payload = SMB2ReadResponse()
elif self.command == SMB2_COM_WRITE:
self.payload = SMB2WriteResponse()
elif self.command == SMB2_COM_QUERY_DIRECTORY:
self.payload = SMB2QueryDirectoryResponse()
elif self.command == SMB2_COM_CREATE:
self.payload = SMB2CreateResponse()
elif self.command == SMB2_COM_CLOSE:
self.payload = SMB2CloseResponse()
elif self.command == SMB2_COM_QUERY_INFO:
self.payload = SMB2QueryInfoResponse()
elif self.command == SMB2_COM_SET_INFO:
self.payload = SMB2SetInfoResponse()
elif self.command == SMB2_COM_IOCTL:
self.payload = SMB2IoctlResponse()
elif self.command == SMB2_COM_TREE_CONNECT:
self.payload = SMB2TreeConnectResponse()
elif self.command == SMB2_COM_SESSION_SETUP:
self.payload = SMB2SessionSetupResponse()
elif self.command == SMB2_COM_NEGOTIATE:
self.payload = SMB2NegotiateResponse()
elif self.command == SMB2_COM_ECHO:
self.payload = SMB2EchoResponse()
@property
def isAsync(self):
return bool(self.flags & SMB2_FLAGS_ASYNC_COMMAND)
@property
def isReply(self):
return bool(self.flags & SMB2_FLAGS_SERVER_TO_REDIR)
class Structure:
def initMessage(self, message):
pass
def prepare(self, message):
raise NotImplementedError
def decode(self, message):
raise NotImplementedError
class SMB2NegotiateResponse(Structure):
"""
Contains information on the SMB2_NEGOTIATE response from server
After calling the decode method, each instance will contain the following attributes,
- security_mode (integer)
- dialect_revision (integer)
- server_guid (string)
- max_transact_size (integer)
- max_read_size (integer)
- max_write_size (integer)
- system_time (long)
- server_start_time (long)
- security_blob (string)
References:
===========
- [MS-SMB2]: 2.2.4
"""
STRUCTURE_FORMAT = "<HHHH16sIIIIQQHHI"
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
def decode(self, message):
assert message.command == SMB2_COM_NEGOTIATE
if message.status == 0:
struct_size, self.security_mode, self.dialect_revision, _, self.server_guid, self.capabilities, \
self.max_transact_size, self.max_read_size, self.max_write_size, self.system_time, self.server_start_time, \
security_buf_offset, security_buf_len, _ = struct.unpack(self.STRUCTURE_FORMAT, message.raw_data[SMB2Message.HEADER_SIZE:SMB2Message.HEADER_SIZE+self.STRUCTURE_SIZE])
self.server_start_time = convertFILETIMEtoEpoch(self.server_start_time)
self.system_time = convertFILETIMEtoEpoch(self.system_time)
self.security_blob = message.raw_data[security_buf_offset:security_buf_offset+security_buf_len]
class SMB2SessionSetupRequest(Structure):
"""
References:
===========
- [MS-SMB2]: 2.2.5
"""
STRUCTURE_FORMAT = "<HBBIIHHQ"
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
def __init__(self, security_blob):
self.security_blob = security_blob
def initMessage(self, message):
Structure.initMessage(self, message)
message.command = SMB2_COM_SESSION_SETUP
def prepare(self, message):
message.data = struct.pack(self.STRUCTURE_FORMAT,
25, # Structure size. Must be 25 as mandated by [MS-SMB2] 2.2.5
0, # VcNumber
0x01, # Security mode
0x00, # Capabilities
0, # Channel
SMB2Message.HEADER_SIZE + self.STRUCTURE_SIZE,
len(self.security_blob),
0) + self.security_blob
class SMB2SessionSetupResponse(Structure):
"""
Contains information about the SMB2_COM_SESSION_SETUP response from the server.
If the message has no errors, each instance contains the following attributes:
- session_flags (integer)
- security_blob (string)
References:
===========
- [MS-SMB2]: 2.2.6
"""
STRUCTURE_FORMAT = "<HHHH"
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
@property
def isGuestSession(self):
return (self.session_flags & 0x0001) > 0 # SMB2_SESSION_FLAG_IS_GUEST
@property
def isAnonymousSession(self):
return (self.session_flags & 0x0002) > 0 # SMB2_SESSION_FLAG_IS_NULL
def decode(self, message):
assert message.command == SMB2_COM_SESSION_SETUP
struct_size, self.session_flags, security_blob_offset, security_blob_len \
= struct.unpack(self.STRUCTURE_FORMAT, message.raw_data[SMB2Message.HEADER_SIZE:SMB2Message.HEADER_SIZE+self.STRUCTURE_SIZE])
self.security_blob = message.raw_data[security_blob_offset:security_blob_offset+security_blob_len]
class SMB2TreeConnectRequest(Structure):
"""
References:
===========
- [MS-SMB2]: 2.2.9
"""
STRUCTURE_FORMAT = "<HHHH"
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
def __init__(self, path):
self.path = path
def initMessage(self, message):
Structure.initMessage(self, message)
message.command = SMB2_COM_TREE_CONNECT
def prepare(self, message):
message.data = struct.pack(self.STRUCTURE_FORMAT,
9, # Structure size. Must be 9 as mandated by [MS-SMB2] 2.2.9
0, # Reserved
SMB2Message.HEADER_SIZE + self.STRUCTURE_SIZE,
len(self.path)*2) + self.path.encode('UTF-16LE')
class SMB2TreeConnectResponse(Structure):
"""
Contains information about the SMB2_COM_TREE_CONNECT response from the server.
If the message has no errors, each instance contains the following attributes:
- share_type (integer): one of the SMB2_SHARE_TYPE_xxx constants
- share_flags (integer)
- capabilities (integer): bitmask of SMB2_SHARE_CAP_xxx
- maximal_access (integer)
References:
===========
- [MS-SMB2]: 2.2.10
"""
STRUCTURE_FORMAT = "<HBBIII"
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
def decode(self, message):
assert message.command == SMB2_COM_TREE_CONNECT
if message.status == 0:
struct_size, self.share_type, _, \
self.share_flags, self.capabilities, self.maximal_access \
= struct.unpack(self.STRUCTURE_FORMAT, message.raw_data[SMB2Message.HEADER_SIZE:SMB2Message.HEADER_SIZE+self.STRUCTURE_SIZE])
class SMB2CreateRequest(Structure):
"""
References:
===========
- [MS-SMB2]: 2.2.13
"""
STRUCTURE_FORMAT = "<HBBIQQIIIIIHHII"
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
def __init__(self, filename, file_attributes = 0,
access_mask = 0, share_access = 0, create_disp = 0, create_options = 0,
impersonation = SEC_ANONYMOUS,
oplock = SMB2_OPLOCK_LEVEL_NONE,
create_context_data = b''):
self.filename = filename
self.file_attributes = file_attributes
self.access_mask = access_mask
self.share_access = share_access
self.create_disp = create_disp
self.create_options = create_options
self.oplock = oplock
self.impersonation = impersonation
self.create_context_data = create_context_data or b''
def initMessage(self, message):
Structure.initMessage(self, message)
message.command = SMB2_COM_CREATE
def prepare(self, message):
buf = self.filename.encode('UTF-16LE')
filename_len = len(buf)
if self.create_context_data:
n = SMB2Message.HEADER_SIZE + self.STRUCTURE_SIZE + len(buf)
if n % 8 != 0:
buf += b'\0'*(8-n%8)
create_context_offset = SMB2Message.HEADER_SIZE + self.STRUCTURE_SIZE + len(buf)
else:
create_context_offset = n
buf += self.create_context_data
else:
create_context_offset = 0
if not buf:
buf = b'\0'
assert create_context_offset % 8 == 0
message.data = struct.pack(self.STRUCTURE_FORMAT,
57, # Structure size. Must be 57 as mandated by [MS-SMB2] 2.2.13
0, # SecurityFlag. Must be 0
self.oplock,
self.impersonation,
0, # SmbCreateFlags. Must be 0
0, # Reserved. Must be 0
self.access_mask, # DesiredAccess. [MS-SMB2] 2.2.13.1
self.file_attributes,
self.share_access,
self.create_disp,
self.create_options,
SMB2Message.HEADER_SIZE + self.STRUCTURE_SIZE, # NameOffset
filename_len, # Length of encoded filename in bytes
create_context_offset, # CreateContextOffset
len(self.create_context_data) # CreateContextLength
) + buf
class SMB2CreateResponse(Structure):
"""
Contains information about the SMB2_COM_CREATE response from the server.
If the message has no errors, each instance contains the following attributes:
- oplock (integer): one of SMB2_OPLOCK_LEVEL_xxx constants
- create_action (integer): one of SMB2_FILE_xxx constants
- allocation_size (long)
- file_size (long)
- file_attributes (integer)
- fid (16-bytes string)
- create_time, lastaccess_time, lastwrite_time, change_time (float)
References:
===========
- [MS-SMB2]: 2.2.14
"""
STRUCTURE_FORMAT = "<HBBIQQQQQQII16sII"
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
def decode(self, message):
assert message.command == SMB2_COM_CREATE
if message.status == 0:
struct_size, self.oplock, _, self.create_action, \
create_time, lastaccess_time, lastwrite_time, change_time, \
self.allocation_size, self.file_size, self.file_attributes, \
_, self.fid, _, _ = struct.unpack(self.STRUCTURE_FORMAT, message.raw_data[SMB2Message.HEADER_SIZE:SMB2Message.HEADER_SIZE+self.STRUCTURE_SIZE])
self.create_time = convertFILETIMEtoEpoch(create_time)
self.lastaccess_time = convertFILETIMEtoEpoch(lastaccess_time)
self.lastwrite_time = convertFILETIMEtoEpoch(lastwrite_time)
self.change_time = convertFILETIMEtoEpoch(change_time)
class SMB2WriteRequest(Structure):
"""
References:
===========
- [MS-SMB2]: 2.2.21
"""
STRUCTURE_FORMAT = "<HHIQ16sIIHHI"
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
def __init__(self, fid, data, offset, remaining_len = 0, flags = 0):
assert len(fid) == 16
self.fid = fid
self.data = data
self.offset = offset
self.remaining_len = remaining_len
self.flags = flags
def initMessage(self, message):
Structure.initMessage(self, message)
message.command = SMB2_COM_WRITE
def prepare(self, message):
message.data = struct.pack(self.STRUCTURE_FORMAT,
49, # Structure size. Must be 49 as mandated by [MS-SMB2] 2.2.21
SMB2Message.HEADER_SIZE + self.STRUCTURE_SIZE, # DataOffset
len(self.data),
self.offset,
self.fid,
0, # Channel. Must be 0
self.remaining_len, # RemainingBytes
0, # WriteChannelInfoOffset,
0, # WriteChannelInfoLength
self.flags) + self.data
class SMB2WriteResponse(Structure):
"""
Contains information about the SMB2_WRITE response from the server.
If the message has no errors, each instance contains the following attributes:
- count (integer)
References:
===========
- [MS-SMB2]: 2.2.22
"""
STRUCTURE_FORMAT = "<HHIIHH"
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
def decode(self, message):
assert message.command == SMB2_COM_WRITE
if message.status == 0:
struct_size, _, self.count, _, _, _ = struct.unpack(self.STRUCTURE_FORMAT, message.raw_data[SMB2Message.HEADER_SIZE:SMB2Message.HEADER_SIZE+self.STRUCTURE_SIZE])
class SMB2ReadRequest(Structure):
"""
References:
===========
- [MS-SMB2]: 2.2.19
"""
STRUCTURE_FORMAT = "<HBBIQ16sIIIHH"
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
def __init__(self, fid, read_offset, read_len, min_read_len = 0):
self.fid = fid
self.read_offset = read_offset
self.read_len = read_len
self.min_read_len = min_read_len
def initMessage(self, message):
Structure.initMessage(self, message)
message.command = SMB2_COM_READ
def prepare(self, message):
message.data = struct.pack(self.STRUCTURE_FORMAT,
49, # Structure size. Must be 49 as mandated by [MS-SMB2] 2.2.19
0, # Padding
0, # Reserved
self.read_len,
self.read_offset,
self.fid,
self.min_read_len,
0, # Channel
0, # RemainingBytes
0, # ReadChannelInfoOffset
0 # ReadChannelInfoLength
) + b'\0'
class SMB2ReadResponse(Structure):
"""
References:
===========
- [MS-SMB2]: 2.2.20
"""
STRUCTURE_FORMAT = "<HBBIII"
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
def decode(self, message):
assert message.command == SMB2_COM_READ
if message.status == 0:
struct_size, data_offset, _, self.data_length, _, _ = struct.unpack(self.STRUCTURE_FORMAT, message.raw_data[SMB2Message.HEADER_SIZE:SMB2Message.HEADER_SIZE+self.STRUCTURE_SIZE])
self.data = message.raw_data[data_offset:data_offset+self.data_length]
class SMB2IoctlRequest(Structure):
"""
References:
===========
- [MS-SMB2]: 2.2.31
"""
STRUCTURE_FORMAT = "<HHI16sIIIIIIII"
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
def __init__(self, fid, ctlcode, flags, in_data, max_out_size = 65536):
self.ctlcode = ctlcode
self.fid = fid
self.flags = flags
self.in_data = in_data
self.max_out_size = max_out_size
def initMessage(self, message):
Structure.initMessage(self, message)
message.command = SMB2_COM_IOCTL
def prepare(self, message):
message.data = struct.pack(self.STRUCTURE_FORMAT,
57, # Structure size. Must be 57 as mandated by [MS-SMB2] 2.2.31
0, # Reserved
self.ctlcode, # CtlCode
self.fid,
SMB2Message.HEADER_SIZE + self.STRUCTURE_SIZE, # InputOffset
len(self.in_data), # InputCount
0, # MaxInputResponse
0, # OutputOffset
0, # OutputCount
self.max_out_size, # MaxOutputResponse
self.flags, # Flags
0 # Reserved
) + self.in_data
class SMB2IoctlResponse(Structure):
"""
Contains information about the SMB2_IOCTL response from the server.
If the message has no errors, each instance contains the following attributes:
- ctlcode (integer)
- fid (16-bytes string)
- flags (integer)
- in_data (string)
- out_data (string)
References:
===========
- [MS-SMB2]: 2.2.32
"""
STRUCTURE_FORMAT = "<HHI16sIIIIII"
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
def decode(self, message):
assert message.command == SMB2_COM_IOCTL
if message.status == 0:
struct_size, _, self.ctlcode, self.fid, \
input_offset, input_len, output_offset, output_len, \
self.flags, _ = struct.unpack(self.STRUCTURE_FORMAT, message.raw_data[SMB2Message.HEADER_SIZE:SMB2Message.HEADER_SIZE+self.STRUCTURE_SIZE])
if input_len > 0:
self.in_data = message.raw_data[input_offset:input_offset+input_len]
else:
self.in_data = b''
if output_len > 0:
self.out_data = message.raw_data[output_offset:output_offset+output_len]
else:
self.out_data = b''
class SMB2CloseRequest(Structure):
"""
References:
===========
- [MS-SMB2]: 2.2.15
"""
STRUCTURE_FORMAT = "<HHI16s"
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
def __init__(self, fid, flags = 0):
self.fid = fid
self.flags = flags
def initMessage(self, message):
Structure.initMessage(self, message)
message.command = SMB2_COM_CLOSE
def prepare(self, message):
message.data = struct.pack(self.STRUCTURE_FORMAT,
24, # Structure size. Must be 24 as mandated by [MS-SMB2]: 2.2.15
self.flags,
0, # Reserved. Must be 0
self.fid)
class SMB2CloseResponse(Structure):
"""
References:
===========
- [MS-SMB2]: 2.2.16
"""
def decode(self, message):
assert message.command == SMB2_COM_CLOSE
class SMB2QueryDirectoryRequest(Structure):
"""
References:
===========
- [MS-SMB2]: 2.2.33
"""
STRUCTURE_FORMAT = "<HBBI16sHHI"
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
def __init__(self, fid, filename, info_class, flags, output_buf_len):
self.fid = fid
self.filename = filename
self.info_class = info_class
self.flags = flags
self.output_buf_len = output_buf_len
def initMessage(self, message):
Structure.initMessage(self, message)
message.command = SMB2_COM_QUERY_DIRECTORY
def prepare(self, message):
message.data = struct.pack(self.STRUCTURE_FORMAT,
33, # Structure size. Must be 33 as mandated by [MS-SMB2] 2.2.33
self.info_class, # FileInformationClass
self.flags, # Flags
0, # FileIndex
self.fid, # FileID
SMB2Message.HEADER_SIZE + self.STRUCTURE_SIZE, # FileNameOffset
len(self.filename)*2,
self.output_buf_len) + self.filename.encode('UTF-16LE')
class SMB2QueryDirectoryResponse(Structure):
"""
Contains information about the SMB2_COM_QUERY_DIRECTORY response from the server.
If the message has no errors, each instance contains the following attributes:
- data_length (integer)
- data (string)
References:
===========
- [MS-SMB2]: 2.2.34
"""
STRUCTURE_FORMAT = "<HHI"
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
def decode(self, message):
assert message.command == SMB2_COM_QUERY_DIRECTORY
if message.status == 0:
struct_size, offset, self.data_length = struct.unpack(self.STRUCTURE_FORMAT, message.raw_data[SMB2Message.HEADER_SIZE:SMB2Message.HEADER_SIZE+self.STRUCTURE_SIZE])
self.data = message.raw_data[offset:offset+self.data_length]
class SMB2QueryInfoRequest(Structure):
"""
References:
===========
- [MS-SMB2]: 2.2.37
"""
STRUCTURE_FORMAT = "<HBBIHHIII16s"
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
def __init__(self, fid, flags, additional_info, info_type, file_info_class, input_buf, output_buf_len):
self.fid = fid
self.flags = flags
self.additional_info = additional_info
self.info_type = info_type
self.file_info_class = file_info_class
self.output_buf_len = output_buf_len
self.input_buf = input_buf or b''
def initMessage(self, message):
Structure.initMessage(self, message)
message.command = SMB2_COM_QUERY_INFO
def prepare(self, message):
message.data = struct.pack(self.STRUCTURE_FORMAT,
41, # Structure size. Must be 41 as mandated by [MS-SMB2] 2.2.37
self.info_type, # InfoType
self.file_info_class, # FileInfoClass
self.output_buf_len, # OutputBufferLength
SMB2Message.HEADER_SIZE + self.STRUCTURE_SIZE, # InputBufferOffset
0, # Reserved
len(self.input_buf), # InputBufferLength
self.additional_info, # AdditionalInformation
self.flags, # Flags
self.fid # FileId
) + self.input_buf
class SMB2QueryInfoResponse(Structure):
"""
Contains information about the SMB2_COM_QUERY_INFO response from the server.
If the message has no errors, each instance contains the following attributes:
- data_length (integer)
- data (string)
References:
===========
- [MS-SMB2]: 2.2.38
"""
STRUCTURE_FORMAT = "<HHI"
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
def decode(self, message):
assert message.command == SMB2_COM_QUERY_INFO
if message.status == 0:
struct_size, buf_offset, self.data_length = struct.unpack(self.STRUCTURE_FORMAT, message.raw_data[SMB2Message.HEADER_SIZE:SMB2Message.HEADER_SIZE+self.STRUCTURE_SIZE])
self.data = message.raw_data[buf_offset:buf_offset+self.data_length]
class SMB2SetInfoRequest(Structure):
"""
References:
===========
- [MS-SMB2]: 2.2.39
"""
STRUCTURE_FORMAT = "<HBBIHHI16s"
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
def __init__(self, fid, additional_info, info_type, file_info_class, data):
self.fid = fid
self.additional_info = additional_info
self.info_type = info_type
self.file_info_class = file_info_class
self.data = data or b''
def initMessage(self, message):
Structure.initMessage(self, message)
message.command = SMB2_COM_SET_INFO
def prepare(self, message):
message.data = struct.pack(self.STRUCTURE_FORMAT,
33, # StructureSize. Must be 33 as mandated by [MS-SMB2] 2.2.39
self.info_type, # InfoType
self.file_info_class, # FileInfoClass
len(self.data), # BufferLength
SMB2Message.HEADER_SIZE + self.STRUCTURE_SIZE, # BufferOffset
0, # Reserved
self.additional_info, # AdditionalInformation
self.fid # FileId
) + self.data
class SMB2SetInfoResponse(Structure):
"""
References:
===========
- [MS-SMB2]: 2.2.40
"""
def decode(self, message):
pass
class SMB2EchoRequest(Structure):
"""
References:
===========
- [MS-SMB2]: 2.2.28
"""
STRUCTURE_FORMAT = '<HH'
STRUCTURE_SIZE = struct.calcsize(STRUCTURE_FORMAT)
def initMessage(self, message):
Structure.initMessage(self, message)
message.command = SMB2_COM_ECHO
def prepare(self, message):
message.data = struct.pack(self.STRUCTURE_FORMAT,
4, # StructureSize. Must be 4 as mandated by [MS-SMB2] 2.2.29
0) # Reserved
class SMB2EchoResponse(Structure):
"""
References:
===========
- [MS-SMB2]: 2.2.29
"""
def decode(self, message):
pass

View File

@@ -0,0 +1,257 @@
# Values for Command field in SMB message header
SMB_COM_CREATE_DIRECTORY = 0x00
SMB_COM_DELETE_DIRECTORY = 0x01
SMB_COM_CLOSE = 0x04
SMB_COM_DELETE = 0x06
SMB_COM_RENAME = 0x07
SMB_COM_TRANSACTION = 0x25
SMB_COM_ECHO = 0x2B
SMB_COM_OPEN_ANDX = 0x2D
SMB_COM_READ_ANDX = 0x2E
SMB_COM_WRITE_ANDX = 0x2F
SMB_COM_TRANSACTION2 = 0x32
SMB_COM_NEGOTIATE = 0x72
SMB_COM_SESSION_SETUP_ANDX = 0x73
SMB_COM_TREE_CONNECT_ANDX = 0x75
SMB_COM_NT_TRANSACT = 0xA0
SMB_COM_NT_CREATE_ANDX = 0xA2
SMB_COMMAND_NAMES = {
0x00: 'SMB_COM_CREATE_DIRECTORY',
0x01: 'SMB_COM_DELETE_DIRECTORY',
0x04: 'SMB_COM_CLOSE',
0x06: 'SMB_COM_DELETE',
0x25: 'SMB_COM_TRANSACTION',
0x2B: 'SMB_COM_ECHO',
0x2D: 'SMB_COM_OPEN_ANDX',
0x2E: 'SMB_COM_READ_ANDX',
0x2F: 'SMB_COM_WRITE_ANDX',
0x32: 'SMB_COM_TRANSACTION2',
0x72: 'SMB_COM_NEGOTIATE',
0x73: 'SMB_COM_SESSION_SETUP_ANDX',
0x75: 'SMB_COM_TREE_CONNECT_ANDX',
0xA0: 'SMB_COM_NT_TRANSACT',
0xA2: 'SMB_COM_NT_CREATE_ANDX',
}
# Bitmask for Flags field in SMB message header
SMB_FLAGS_LOCK_AND_READ_OK = 0x01 # LANMAN1.0
SMB_FLAGS_BUF_AVAIL = 0x02 # LANMAN1.0, Obsolete
SMB_FLAGS_CASE_INSENSITIVE = 0x08 # LANMAN1.0, Obsolete
SMB_FLAGS_CANONICALIZED_PATHS = 0x10 # LANMAN1.0, Obsolete
SMB_FLAGS_OPLOCK = 0x20 # LANMAN1.0, Obsolete
SMB_FLAGS_OPBATCH = 0x40 # LANMAN1.0, Obsolete
SMB_FLAGS_REPLY = 0x80 # LANMAN1.0
# Bitmask for Flags2 field in SMB message header
SMB_FLAGS2_LONG_NAMES = 0x0001 # LANMAN2.0
SMB_FLAGS2_EAS = 0x0002 # LANMAN1.2
SMB_FLAGS2_SMB_SECURITY_SIGNATURE = 0x0004 # NT LANMAN
SMB_FLAGS2_IS_LONG_NAME = 0x0040 # NT LANMAN
SMB_FLAGS2_DFS = 0x1000 # NT LANMAN
SMB_FLAGS2_REPARSE_PATH = 0x0400 #
SMB_FLAGS2_EXTENDED_SECURITY = 0x0800 #
SMB_FLAGS2_PAGING_IO = 0x2000 # NT LANMAN
SMB_FLAGS2_NT_STATUS = 0x4000 # NT LANMAN
SMB_FLAGS2_UNICODE = 0x8000 # NT LANMAN
# Bitmask for Capabilities field in SMB_COM_SESSION_SETUP_ANDX response
# [MS-SMB]: 2.2.4.5.2.1 (Capabilities field)
CAP_RAW_MODE = 0x01
CAP_MPX_MODE = 0x02
CAP_UNICODE = 0x04
CAP_LARGE_FILES = 0x08
CAP_NT_SMBS = 0x10
CAP_RPC_REMOTE_APIS = 0x20
CAP_STATUS32 = 0x40
CAP_LEVEL_II_OPLOCKS = 0x80
CAP_LOCK_AND_READ = 0x0100
CAP_NT_FIND = 0x0200
CAP_DFS = 0x1000
CAP_INFOLEVEL_PASSTHRU = 0x2000
CAP_LARGE_READX = 0x4000
CAP_LARGE_WRITEX = 0x8000
CAP_LWIO = 0x010000
CAP_UNIX = 0x800000
CAP_COMPRESSED = 0x02000000
CAP_DYNAMIC_REAUTH = 0x20000000
CAP_PERSISTENT_HANDLES = 0x40000000
CAP_EXTENDED_SECURITY = 0x80000000
# Value for Action field in SMB_COM_SESSION_SETUP_ANDX response
SMB_SETUP_GUEST = 0x0001
SMB_SETUP_USE_LANMAN_KEY = 0X0002
# Bitmask for SecurityMode field in SMB_COM_NEGOTIATE response
NEGOTIATE_USER_SECURITY = 0x01
NEGOTIATE_ENCRYPT_PASSWORDS = 0x02
NEGOTIATE_SECURITY_SIGNATURES_ENABLE = 0x04
NEGOTIATE_SECURITY_SIGNATURES_REQUIRE = 0x08
# Available constants for Service field in SMB_COM_TREE_CONNECT_ANDX request
# [MS-CIFS]: 2.2.4.55.1 (Service field)
SERVICE_PRINTER = 'LPT1:'
SERVICE_NAMED_PIPE = 'IPC'
SERVICE_COMM = 'COMM'
SERVICE_ANY = '?????'
# Bitmask for Flags field in SMB_COM_NT_CREATE_ANDX request
# [MS-CIFS]: 2.2.4.64.1
# [MS-SMB]: 2.2.4.9.1
NT_CREATE_REQUEST_OPLOCK = 0x02
NT_CREATE_REQUEST_OPBATCH = 0x04
NT_CREATE_OPEN_TARGET_DIR = 0x08
NT_CREATE_REQUEST_EXTENDED_RESPONSE = 0x10 # Defined in [MS-SMB]: 2.2.4.9.1
# Bitmask for DesiredAccess field in SMB_COM_NT_CREATE_ANDX request
# and SMB2CreateRequest class
# Also used for MaximalAccess field in SMB2TreeConnectResponse class
# [MS-CIFS]: 2.2.4.64.1
# [MS-SMB2]: 2.2.13.1.1
FILE_READ_DATA = 0x01
FILE_WRITE_DATA = 0X02
FILE_APPEND_DATA = 0x04
FILE_READ_EA = 0x08
FILE_WRITE_EA = 0x10
FILE_EXECUTE = 0x20
FILE_DELETE_CHILD = 0x40
FILE_READ_ATTRIBUTES = 0x80
FILE_WRITE_ATTRIBUTES = 0x0100
DELETE = 0x010000
READ_CONTROL = 0x020000
WRITE_DAC = 0x040000
WRITE_OWNER = 0x080000
SYNCHRONIZE = 0x100000
ACCESS_SYSTEM_SECURITY = 0x01000000
MAXIMUM_ALLOWED = 0x02000000
GENERIC_ALL = 0x10000000
GENERIC_EXECUTE = 0x20000000
GENERIC_WRITE = 0x40000000
GENERIC_READ = 0x80000000
# SMB_EXT_FILE_ATTR bitmask ([MS-CIFS]: 2.2.1.2.3)
# Includes extensions defined in [MS-SMB] 2.2.1.2.1
# Bitmask for FileAttributes field in SMB_COM_NT_CREATE_ANDX request ([MS-CIFS]: 2.2.4.64.1)
# Also used for FileAttributes field in SMB2CreateRequest class ([MS-SMB2]: 2.2.13)
ATTR_READONLY = 0x01
ATTR_HIDDEN = 0x02
ATTR_SYSTEM = 0x04
ATTR_DIRECTORY = 0x10
ATTR_ARCHIVE = 0x20
ATTR_NORMAL = 0x80
ATTR_TEMPORARY = 0x0100
ATTR_SPARSE = 0x0200
ATTR_REPARSE_POINT = 0x0400
ATTR_COMPRESSED = 0x0800
ATTR_OFFLINE = 0x1000
ATTR_NOT_CONTENT_INDEXED = 0x2000
ATTR_ENCRYPTED = 0x4000
POSIX_SEMANTICS = 0x01000000
BACKUP_SEMANTICS = 0x02000000
DELETE_ON_CLOSE = 0x04000000
SEQUENTIAL_SCAN = 0x08000000
RANDOM_ACCESS = 0x10000000
NO_BUFFERING = 0x20000000
WRITE_THROUGH = 0x80000000
# Bitmask for ShareAccess field in SMB_COM_NT_CREATE_ANDX request
# and SMB2CreateRequest class
# [MS-CIFS]: 2.2.4.64.1
# [MS-SMB2]: 2.2.13
FILE_SHARE_NONE = 0x00
FILE_SHARE_READ = 0x01
FILE_SHARE_WRITE = 0x02
FILE_SHARE_DELETE = 0x04
# Values for CreateDisposition field in SMB_COM_NT_CREATE_ANDX request
# and SMB2CreateRequest class
# [MS-CIFS]: 2.2.4.64.1
# [MS-SMB2]: 2.2.13
FILE_SUPERSEDE = 0x00
FILE_OPEN = 0x01
FILE_CREATE = 0x02
FILE_OPEN_IF = 0x03
FILE_OVERWRITE = 0x04
FILE_OVERWRITE_IF = 0x05
# Bitmask for CreateOptions field in SMB_COM_NT_CREATE_ANDX request
# and SMB2CreateRequest class
# [MS-CIFS]: 2.2.4.64.1
# [MS-SMB2]: 2.2.13
FILE_DIRECTORY_FILE = 0x01
FILE_WRITE_THROUGH = 0x02
FILE_SEQUENTIAL_ONLY = 0x04
FILE_NO_INTERMEDIATE_BUFFERING = 0x08
FILE_SYNCHRONOUS_IO_ALERT = 0x10
FILE_SYNCHRONOUS_IO_NONALERT = 0x20
FILE_NON_DIRECTORY_FILE = 0x40
FILE_CREATE_TREE_CONNECTION = 0x80
FILE_COMPLETE_IF_OPLOCKED = 0x0100
FILE_NO_EA_KNOWLEDGE = 0x0200
FILE_OPEN_FOR_RECOVERY = 0x0400
FILE_RANDOM_ACCESS = 0x0800
FILE_DELETE_ON_CLOSE = 0x1000
FILE_OPEN_BY_FILE_ID = 0x2000
FILE_OPEN_FOR_BACKUP_INTENT = 0x4000
FILE_NO_COMPRESSION = 0x8000
FILE_RESERVE_OPFILTER = 0x100000
FILE_OPEN_NO_RECALL = 0x400000
FILE_OPEN_FOR_FREE_SPACE_QUERY = 0x800000
# Values for ImpersonationLevel field in SMB_COM_NT_CREATE_ANDX request
# and SMB2CreateRequest class
# For interpretations about these values, refer to [MS-WSO] and [MSDN-IMPERS]
# [MS-CIFS]: 2.2.4.64.1
# [MS-SMB]: 2.2.4.9.1
# [MS-SMB2]: 2.2.13
SEC_ANONYMOUS = 0x00
SEC_IDENTIFY = 0x01
SEC_IMPERSONATE = 0x02
SEC_DELEGATION = 0x03 # Defined in [MS-SMB]: 2.2.4.9.1
# Values for SecurityFlags field in SMB_COM_NT_CREATE_ANDX request
# [MS-CIFS]: 2.2.4.64.1
SMB_SECURITY_CONTEXT_TRACKING = 0x01
SMB_SECURITY_EFFECTIVE_ONLY = 0x02
# Bitmask for Flags field in SMB_COM_TRANSACTION2 request
# [MS-CIFS]: 2.2.4.46.1
DISCONNECT_TID = 0x01
NO_RESPONSE = 0x02
# Bitmask for basic file attributes
# [MS-CIFS]: 2.2.1.2.4
SMB_FILE_ATTRIBUTE_NORMAL = 0x00
SMB_FILE_ATTRIBUTE_READONLY = 0x01
SMB_FILE_ATTRIBUTE_HIDDEN = 0x02
SMB_FILE_ATTRIBUTE_SYSTEM = 0x04
SMB_FILE_ATTRIBUTE_VOLUME = 0x08 # Unsupported for listPath() operations
SMB_FILE_ATTRIBUTE_DIRECTORY = 0x10
SMB_FILE_ATTRIBUTE_ARCHIVE = 0x20
# SMB_FILE_ATTRIBUTE_INCL_NORMAL is a special placeholder to include normal files
# with other search attributes for listPath() operations. It is not defined in the MS-CIFS specs.
SMB_FILE_ATTRIBUTE_INCL_NORMAL = 0x10000
# Do not use the following values for listPath() operations as they are not supported for SMB2
SMB_SEARCH_ATTRIBUTE_READONLY = 0x0100
SMB_SEARCH_ATTRIBUTE_HIDDEN = 0x0200
SMB_SEARCH_ATTRIBUTE_SYSTEM = 0x0400
SMB_SEARCH_ATTRIBUTE_DIRECTORY = 0x1000
SMB_SEARCH_ATTRIBUTE_ARCHIVE = 0x2000
# Bitmask for OptionalSupport field in SMB_COM_TREE_CONNECT_ANDX response
SMB_TREE_CONNECTX_SUPPORT_SEARCH = 0x0001
SMB_TREE_CONNECTX_SUPPORT_DFS = 0x0002
# Bitmask for security information fields, specified as
# AdditionalInformation in SMB2
# [MS-SMB]: 2.2.7.4
# [MS-SMB2]: 2.2.37
OWNER_SECURITY_INFORMATION = 0x00000001
GROUP_SECURITY_INFORMATION = 0x00000002
DACL_SECURITY_INFORMATION = 0x00000004
SACL_SECURITY_INFORMATION = 0x00000008
LABEL_SECURITY_INFORMATION = 0x00000010
ATTRIBUTE_SECURITY_INFORMATION = 0x00000020
SCOPE_SECURITY_INFORMATION = 0x00000040
BACKUP_SECURITY_INFORMATION = 0x00010000

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,148 @@
# U32.py implements 32-bit unsigned int class for Python
# Version 1.0
# Copyright (C) 2001-2002 Dmitry Rozmanov
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# e-mail: dima@xenon.spb.ru
#
#====================================================================
C = 0x1000000000
#--------------------------------------------------------------------
def norm(n):
return n & 0xFFFFFFFF
#====================================================================
class U32:
v = 0
#--------------------------------------------------------------------
def __init__(self, value = 0):
self.v = C + norm(abs(int(value)))
#--------------------------------------------------------------------
def set(self, value = 0):
self.v = C + norm(abs(int(value)))
#--------------------------------------------------------------------
def __repr__(self):
return hex(norm(self.v))
#--------------------------------------------------------------------
def __long__(self): return int(norm(self.v))
#--------------------------------------------------------------------
def __int__(self): return int(norm(self.v))
#--------------------------------------------------------------------
def __chr__(self): return chr(norm(self.v))
#--------------------------------------------------------------------
def __add__(self, b):
r = U32()
r.v = C + norm(self.v + b.v)
return r
#--------------------------------------------------------------------
def __sub__(self, b):
r = U32()
if self.v < b.v:
r.v = C + norm(0x100000000 - (b.v - self.v))
else: r.v = C + norm(self.v - b.v)
return r
#--------------------------------------------------------------------
def __mul__(self, b):
r = U32()
r.v = C + norm(self.v * b.v)
return r
#--------------------------------------------------------------------
def __div__(self, b):
r = U32()
r.v = C + (norm(self.v) / norm(b.v))
return r
#--------------------------------------------------------------------
def __mod__(self, b):
r = U32()
r.v = C + (norm(self.v) % norm(b.v))
return r
#--------------------------------------------------------------------
def __neg__(self): return U32(self.v)
#--------------------------------------------------------------------
def __pos__(self): return U32(self.v)
#--------------------------------------------------------------------
def __abs__(self): return U32(self.v)
#--------------------------------------------------------------------
def __invert__(self):
r = U32()
r.v = C + norm(~self.v)
return r
#--------------------------------------------------------------------
def __lshift__(self, b):
r = U32()
r.v = C + norm(self.v << b)
return r
#--------------------------------------------------------------------
def __rshift__(self, b):
r = U32()
r.v = C + (norm(self.v) >> b)
return r
#--------------------------------------------------------------------
def __and__(self, b):
r = U32()
r.v = C + norm(self.v & b.v)
return r
#--------------------------------------------------------------------
def __or__(self, b):
r = U32()
r.v = C + norm(self.v | b.v)
return r
#--------------------------------------------------------------------
def __xor__(self, b):
r = U32()
r.v = C + norm(self.v ^ b.v)
return r
#--------------------------------------------------------------------
def __not__(self):
return U32(not norm(self.v))
#--------------------------------------------------------------------
def truth(self):
return norm(self.v)
#--------------------------------------------------------------------
def __cmp__(self, b):
if norm(self.v) > norm(b.v): return 1
elif norm(self.v) < norm(b.v): return -1
else: return 0
#--------------------------------------------------------------------
def __bool__(self):
return norm(self.v)

View File

@@ -0,0 +1,3 @@
def convertFILETIMEtoEpoch(t):
return (t - 116444736000000000) / 10000000.0;

View File

@@ -0,0 +1,256 @@
# md4.py implements md4 hash class for Python
# Version 1.0
# Copyright (C) 2001-2002 Dmitry Rozmanov
#
# based on md4.c from "the Python Cryptography Toolkit, version 1.0.0
# Copyright (C) 1995, A.M. Kuchling"
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# e-mail: dima@xenon.spb.ru
#
#====================================================================
# MD4 validation data
md4_test= [
('', 0x31d6cfe0d16ae931b73c59d7e0c089c0),
("a", 0xbde52cb31de33e46245e05fbdbd6fb24),
("abc", 0xa448017aaf21d8525fc10ae87aa6729d),
("message digest", 0xd9130a8164549fe818874806e1c7014b),
("abcdefghijklmnopqrstuvwxyz", 0xd79e1c308aa5bbcdeea8ed63df412da9),
("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
0x043f8582f241db351ce627e153e7f0e4),
("12345678901234567890123456789012345678901234567890123456789012345678901234567890",
0xe33b4ddc9c38f2199c3e7b164fcc0536),
]
#====================================================================
from .U32 import U32
#--------------------------------------------------------------------
class MD4:
A = None
B = None
C = None
D = None
count, len1, len2 = None, None, None
buf = []
#-----------------------------------------------------
def __init__(self):
self.A = U32(0x67452301)
self.B = U32(0xefcdab89)
self.C = U32(0x98badcfe)
self.D = U32(0x10325476)
self.count, self.len1, self.len2 = U32(0), U32(0), U32(0)
self.buf = [0x00] * 64
#-----------------------------------------------------
def __repr__(self):
r = 'A = %s, \nB = %s, \nC = %s, \nD = %s.\n' % (self.A.__repr__(), self.B.__repr__(), self.C.__repr__(), self.D.__repr__())
r = r + 'count = %s, \nlen1 = %s, \nlen2 = %s.\n' % (self.count.__repr__(), self.len1.__repr__(), self.len2.__repr__())
for i in range(4):
for j in range(16):
r = r + '%4s ' % hex(self.buf[i+j])
r = r + '\n'
return r
#-----------------------------------------------------
def make_copy(self):
dest = new()
dest.len1 = self.len1
dest.len2 = self.len2
dest.A = self.A
dest.B = self.B
dest.C = self.C
dest.D = self.D
dest.count = self.count
for i in range(int(self.count)):
dest.buf[i] = self.buf[i]
return dest
#-----------------------------------------------------
def update(self, str):
if isinstance(str, bytes):
buf = list(str)
else:
buf = [ord(i) for i in str]
ilen = U32(len(buf))
# check if the first length is out of range
# as the length is measured in bits then multiplay it by 8
if (int(self.len1 + (ilen << 3)) < int(self.len1)):
self.len2 = self.len2 + U32(1)
self.len1 = self.len1 + (ilen << 3)
self.len2 = self.len2 + (ilen >> 29)
L = U32(0)
bufpos = 0
while (int(ilen) > 0):
if (64 - int(self.count)) < int(ilen): L = U32(64 - int(self.count))
else: L = ilen
for i in range(int(L)): self.buf[i + int(self.count)] = buf[i + bufpos]
self.count = self.count + L
ilen = ilen - L
bufpos = bufpos + int(L)
if (int(self.count) == 64):
self.count = U32(0)
X = []
i = 0
for j in range(16):
X.append(U32(self.buf[i]) + (U32(self.buf[i+1]) << 8) + \
(U32(self.buf[i+2]) << 16) + (U32(self.buf[i+3]) << 24))
i = i + 4
A = self.A
B = self.B
C = self.C
D = self.D
A = f1(A,B,C,D, 0, 3, X)
D = f1(D,A,B,C, 1, 7, X)
C = f1(C,D,A,B, 2,11, X)
B = f1(B,C,D,A, 3,19, X)
A = f1(A,B,C,D, 4, 3, X)
D = f1(D,A,B,C, 5, 7, X)
C = f1(C,D,A,B, 6,11, X)
B = f1(B,C,D,A, 7,19, X)
A = f1(A,B,C,D, 8, 3, X)
D = f1(D,A,B,C, 9, 7, X)
C = f1(C,D,A,B,10,11, X)
B = f1(B,C,D,A,11,19, X)
A = f1(A,B,C,D,12, 3, X)
D = f1(D,A,B,C,13, 7, X)
C = f1(C,D,A,B,14,11, X)
B = f1(B,C,D,A,15,19, X)
A = f2(A,B,C,D, 0, 3, X)
D = f2(D,A,B,C, 4, 5, X)
C = f2(C,D,A,B, 8, 9, X)
B = f2(B,C,D,A,12,13, X)
A = f2(A,B,C,D, 1, 3, X)
D = f2(D,A,B,C, 5, 5, X)
C = f2(C,D,A,B, 9, 9, X)
B = f2(B,C,D,A,13,13, X)
A = f2(A,B,C,D, 2, 3, X)
D = f2(D,A,B,C, 6, 5, X)
C = f2(C,D,A,B,10, 9, X)
B = f2(B,C,D,A,14,13, X)
A = f2(A,B,C,D, 3, 3, X)
D = f2(D,A,B,C, 7, 5, X)
C = f2(C,D,A,B,11, 9, X)
B = f2(B,C,D,A,15,13, X)
A = f3(A,B,C,D, 0, 3, X)
D = f3(D,A,B,C, 8, 9, X)
C = f3(C,D,A,B, 4,11, X)
B = f3(B,C,D,A,12,15, X)
A = f3(A,B,C,D, 2, 3, X)
D = f3(D,A,B,C,10, 9, X)
C = f3(C,D,A,B, 6,11, X)
B = f3(B,C,D,A,14,15, X)
A = f3(A,B,C,D, 1, 3, X)
D = f3(D,A,B,C, 9, 9, X)
C = f3(C,D,A,B, 5,11, X)
B = f3(B,C,D,A,13,15, X)
A = f3(A,B,C,D, 3, 3, X)
D = f3(D,A,B,C,11, 9, X)
C = f3(C,D,A,B, 7,11, X)
B = f3(B,C,D,A,15,15, X)
self.A = self.A + A
self.B = self.B + B
self.C = self.C + C
self.D = self.D + D
#-----------------------------------------------------
def digest(self):
res = [0x00] * 16
s = [0x00] * 8
padding = [0x00] * 64
padding[0] = 0x80
padlen, oldlen1, oldlen2 = U32(0), U32(0), U32(0)
temp = self.make_copy()
oldlen1 = temp.len1
oldlen2 = temp.len2
if (56 <= int(self.count)): padlen = U32(56 - int(self.count) + 64)
else: padlen = U32(56 - int(self.count))
temp.update(int_array2str(padding[:int(padlen)]))
s[0]= (oldlen1) & U32(0xFF)
s[1]=((oldlen1) >> 8) & U32(0xFF)
s[2]=((oldlen1) >> 16) & U32(0xFF)
s[3]=((oldlen1) >> 24) & U32(0xFF)
s[4]= (oldlen2) & U32(0xFF)
s[5]=((oldlen2) >> 8) & U32(0xFF)
s[6]=((oldlen2) >> 16) & U32(0xFF)
s[7]=((oldlen2) >> 24) & U32(0xFF)
temp.update(int_array2str(s))
res[ 0]= temp.A & U32(0xFF)
res[ 1]=(temp.A >> 8) & U32(0xFF)
res[ 2]=(temp.A >> 16) & U32(0xFF)
res[ 3]=(temp.A >> 24) & U32(0xFF)
res[ 4]= temp.B & U32(0xFF)
res[ 5]=(temp.B >> 8) & U32(0xFF)
res[ 6]=(temp.B >> 16) & U32(0xFF)
res[ 7]=(temp.B >> 24) & U32(0xFF)
res[ 8]= temp.C & U32(0xFF)
res[ 9]=(temp.C >> 8) & U32(0xFF)
res[10]=(temp.C >> 16) & U32(0xFF)
res[11]=(temp.C >> 24) & U32(0xFF)
res[12]= temp.D & U32(0xFF)
res[13]=(temp.D >> 8) & U32(0xFF)
res[14]=(temp.D >> 16) & U32(0xFF)
res[15]=(temp.D >> 24) & U32(0xFF)
return int_array2str(res).encode('UTF-16LE')
#====================================================================
# helpers
def F(x, y, z): return (((x) & (y)) | ((~x) & (z)))
def G(x, y, z): return (((x) & (y)) | ((x) & (z)) | ((y) & (z)))
def H(x, y, z): return ((x) ^ (y) ^ (z))
def ROL(x, n): return (((x) << n) | ((x) >> (32-n)))
def f1(a, b, c, d, k, s, X): return ROL(a + F(b, c, d) + X[k], s)
def f2(a, b, c, d, k, s, X): return ROL(a + G(b, c, d) + X[k] + U32(0x5a827999), s)
def f3(a, b, c, d, k, s, X): return ROL(a + H(b, c, d) + X[k] + U32(0x6ed9eba1), s)
#--------------------------------------------------------------------
# helper function
def int_array2str(array):
str = ''
for i in array:
str = str + chr(i)
return str
#--------------------------------------------------------------------
# To be able to use md4.new() instead of md4.MD4()
new = MD4

View File

@@ -0,0 +1,852 @@
#############################################################################
# Documentation #
#############################################################################
# Author: Todd Whiteman
# Date: 16th March, 2009
# Verion: 2.0.0
# License: Public Domain - free to do as you wish
# Homepage: http://twhiteman.netfirms.com/des.html
#
# This is a pure python implementation of the DES encryption algorithm.
# It's pure python to avoid portability issues, since most DES
# implementations are programmed in C (for performance reasons).
#
# Triple DES class is also implemented, utilising the DES base. Triple DES
# is either DES-EDE3 with a 24 byte key, or DES-EDE2 with a 16 byte key.
#
# See the README.txt that should come with this python module for the
# implementation methods used.
#
# Thanks to:
# * David Broadwell for ideas, comments and suggestions.
# * Mario Wolff for pointing out and debugging some triple des CBC errors.
# * Santiago Palladino for providing the PKCS5 padding technique.
# * Shaya for correcting the PAD_PKCS5 triple des CBC errors.
#
"""A pure python implementation of the DES and TRIPLE DES encryption algorithms.
Class initialization
--------------------
pyDes.des(key, [mode], [IV], [pad], [padmode])
pyDes.triple_des(key, [mode], [IV], [pad], [padmode])
key -> Bytes containing the encryption key. 8 bytes for DES, 16 or 24 bytes
for Triple DES
mode -> Optional argument for encryption type, can be either
pyDes.ECB (Electronic Code Book) or pyDes.CBC (Cypher Block Chaining)
IV -> Optional Initial Value bytes, must be supplied if using CBC mode.
Length must be 8 bytes.
pad -> Optional argument, set the pad character (PAD_NORMAL) to use during
all encrypt/decrpt operations done with this instance.
padmode -> Optional argument, set the padding mode (PAD_NORMAL or PAD_PKCS5)
to use during all encrypt/decrpt operations done with this instance.
I recommend to use PAD_PKCS5 padding, as then you never need to worry about any
padding issues, as the padding can be removed unambiguously upon decrypting
data that was encrypted using PAD_PKCS5 padmode.
Common methods
--------------
encrypt(data, [pad], [padmode])
decrypt(data, [pad], [padmode])
data -> Bytes to be encrypted/decrypted
pad -> Optional argument. Only when using padmode of PAD_NORMAL. For
encryption, adds this characters to the end of the data block when
data is not a multiple of 8 bytes. For decryption, will remove the
trailing characters that match this pad character from the last 8
bytes of the unencrypted data block.
padmode -> Optional argument, set the padding mode, must be one of PAD_NORMAL
or PAD_PKCS5). Defaults to PAD_NORMAL.
Example
-------
from pyDes import *
data = "Please encrypt my data"
k = des("DESCRYPT", CBC, "\0\0\0\0\0\0\0\0", pad=None, padmode=PAD_PKCS5)
# For Python3, you'll need to use bytes, i.e.:
# data = b"Please encrypt my data"
# k = des(b"DESCRYPT", CBC, b"\0\0\0\0\0\0\0\0", pad=None, padmode=PAD_PKCS5)
d = k.encrypt(data)
print "Encrypted: %r" % d
print "Decrypted: %r" % k.decrypt(d)
assert k.decrypt(d, padmode=PAD_PKCS5) == data
See the module source (pyDes.py) for more examples of use.
You can also run the pyDes.py file without and arguments to see a simple test.
Note: This code was not written for high-end systems needing a fast
implementation, but rather a handy portable solution with small usage.
"""
import sys
# _pythonMajorVersion is used to handle Python2 and Python3 differences.
_pythonMajorVersion = sys.version_info[0]
# Modes of crypting / cyphering
ECB = 0
CBC = 1
# Modes of padding
PAD_NORMAL = 1
PAD_PKCS5 = 2
# PAD_PKCS5: is a method that will unambiguously remove all padding
# characters after decryption, when originally encrypted with
# this padding mode.
# For a good description of the PKCS5 padding technique, see:
# http://www.faqs.org/rfcs/rfc1423.html
# The base class shared by des and triple des.
class _baseDes(object):
def __init__(self, mode=ECB, IV=None, pad=None, padmode=PAD_NORMAL):
if IV:
IV = self._guardAgainstUnicode(IV)
if pad:
pad = self._guardAgainstUnicode(pad)
self.block_size = 8
# Sanity checking of arguments.
if pad and padmode == PAD_PKCS5:
raise ValueError("Cannot use a pad character with PAD_PKCS5")
if IV and len(IV) != self.block_size:
raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes")
# Set the passed in variables
self._mode = mode
self._iv = IV
self._padding = pad
self._padmode = padmode
def getKey(self):
"""getKey() -> bytes"""
return self.__key
def setKey(self, key):
"""Will set the crypting key for this object."""
key = self._guardAgainstUnicode(key)
self.__key = key
def getMode(self):
"""getMode() -> pyDes.ECB or pyDes.CBC"""
return self._mode
def setMode(self, mode):
"""Sets the type of crypting mode, pyDes.ECB or pyDes.CBC"""
self._mode = mode
def getPadding(self):
"""getPadding() -> bytes of length 1. Padding character."""
return self._padding
def setPadding(self, pad):
"""setPadding() -> bytes of length 1. Padding character."""
if pad is not None:
pad = self._guardAgainstUnicode(pad)
self._padding = pad
def getPadMode(self):
"""getPadMode() -> pyDes.PAD_NORMAL or pyDes.PAD_PKCS5"""
return self._padmode
def setPadMode(self, mode):
"""Sets the type of padding mode, pyDes.PAD_NORMAL or pyDes.PAD_PKCS5"""
self._padmode = mode
def getIV(self):
"""getIV() -> bytes"""
return self._iv
def setIV(self, IV):
"""Will set the Initial Value, used in conjunction with CBC mode"""
if not IV or len(IV) != self.block_size:
raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes")
IV = self._guardAgainstUnicode(IV)
self._iv = IV
def _padData(self, data, pad, padmode):
# Pad data depending on the mode
if padmode is None:
# Get the default padding mode.
padmode = self.getPadMode()
if pad and padmode == PAD_PKCS5:
raise ValueError("Cannot use a pad character with PAD_PKCS5")
if padmode == PAD_NORMAL:
if len(data) % self.block_size == 0:
# No padding required.
return data
if not pad:
# Get the default padding.
pad = self.getPadding()
if not pad:
raise ValueError("Data must be a multiple of " + str(self.block_size) + " bytes in length. Use padmode=PAD_PKCS5 or set the pad character.")
data += (self.block_size - (len(data) % self.block_size)) * pad
elif padmode == PAD_PKCS5:
pad_len = 8 - (len(data) % self.block_size)
if _pythonMajorVersion < 3:
data += pad_len * chr(pad_len)
else:
data += bytes([pad_len] * pad_len)
return data
def _unpadData(self, data, pad, padmode):
# Unpad data depending on the mode.
if not data:
return data
if pad and padmode == PAD_PKCS5:
raise ValueError("Cannot use a pad character with PAD_PKCS5")
if padmode is None:
# Get the default padding mode.
padmode = self.getPadMode()
if padmode == PAD_NORMAL:
if not pad:
# Get the default padding.
pad = self.getPadding()
if pad:
data = data[:-self.block_size] + \
data[-self.block_size:].rstrip(pad)
elif padmode == PAD_PKCS5:
if _pythonMajorVersion < 3:
pad_len = ord(data[-1])
else:
pad_len = data[-1]
data = data[:-pad_len]
return data
def _guardAgainstUnicode(self, data):
# Only accept byte strings or ascii unicode values, otherwise
# there is no way to correctly decode the data into bytes.
if _pythonMajorVersion < 3:
if isinstance(data, str):
raise ValueError("pyDes can only work with bytes, not Unicode strings.")
else:
if isinstance(data, str):
# Only accept ascii unicode values.
try:
return data.encode('ascii')
except UnicodeEncodeError:
pass
raise ValueError("pyDes can only work with encoded strings, not Unicode.")
return data
#############################################################################
# DES #
#############################################################################
class des(_baseDes):
"""DES encryption/decrytpion class
Supports ECB (Electronic Code Book) and CBC (Cypher Block Chaining) modes.
pyDes.des(key,[mode], [IV])
key -> Bytes containing the encryption key, must be exactly 8 bytes
mode -> Optional argument for encryption type, can be either pyDes.ECB
(Electronic Code Book), pyDes.CBC (Cypher Block Chaining)
IV -> Optional Initial Value bytes, must be supplied if using CBC mode.
Must be 8 bytes in length.
pad -> Optional argument, set the pad character (PAD_NORMAL) to use
during all encrypt/decrpt operations done with this instance.
padmode -> Optional argument, set the padding mode (PAD_NORMAL or
PAD_PKCS5) to use during all encrypt/decrpt operations done
with this instance.
"""
# Permutation and translation tables for DES
__pc1 = [56, 48, 40, 32, 24, 16, 8,
0, 57, 49, 41, 33, 25, 17,
9, 1, 58, 50, 42, 34, 26,
18, 10, 2, 59, 51, 43, 35,
62, 54, 46, 38, 30, 22, 14,
6, 61, 53, 45, 37, 29, 21,
13, 5, 60, 52, 44, 36, 28,
20, 12, 4, 27, 19, 11, 3
]
# number left rotations of pc1
__left_rotations = [
1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1
]
# permuted choice key (table 2)
__pc2 = [
13, 16, 10, 23, 0, 4,
2, 27, 14, 5, 20, 9,
22, 18, 11, 3, 25, 7,
15, 6, 26, 19, 12, 1,
40, 51, 30, 36, 46, 54,
29, 39, 50, 44, 32, 47,
43, 48, 38, 55, 33, 52,
45, 41, 49, 35, 28, 31
]
# initial permutation IP
__ip = [57, 49, 41, 33, 25, 17, 9, 1,
59, 51, 43, 35, 27, 19, 11, 3,
61, 53, 45, 37, 29, 21, 13, 5,
63, 55, 47, 39, 31, 23, 15, 7,
56, 48, 40, 32, 24, 16, 8, 0,
58, 50, 42, 34, 26, 18, 10, 2,
60, 52, 44, 36, 28, 20, 12, 4,
62, 54, 46, 38, 30, 22, 14, 6
]
# Expansion table for turning 32 bit blocks into 48 bits
__expansion_table = [
31, 0, 1, 2, 3, 4,
3, 4, 5, 6, 7, 8,
7, 8, 9, 10, 11, 12,
11, 12, 13, 14, 15, 16,
15, 16, 17, 18, 19, 20,
19, 20, 21, 22, 23, 24,
23, 24, 25, 26, 27, 28,
27, 28, 29, 30, 31, 0
]
# The (in)famous S-boxes
__sbox = [
# S1
[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13],
# S2
[15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9],
# S3
[10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12],
# S4
[7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14],
# S5
[2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3],
# S6
[12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13],
# S7
[4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12],
# S8
[13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11],
]
# 32-bit permutation function P used on the output of the S-boxes
__p = [
15, 6, 19, 20, 28, 11,
27, 16, 0, 14, 22, 25,
4, 17, 30, 9, 1, 7,
23,13, 31, 26, 2, 8,
18, 12, 29, 5, 21, 10,
3, 24
]
# final permutation IP^-1
__fp = [
39, 7, 47, 15, 55, 23, 63, 31,
38, 6, 46, 14, 54, 22, 62, 30,
37, 5, 45, 13, 53, 21, 61, 29,
36, 4, 44, 12, 52, 20, 60, 28,
35, 3, 43, 11, 51, 19, 59, 27,
34, 2, 42, 10, 50, 18, 58, 26,
33, 1, 41, 9, 49, 17, 57, 25,
32, 0, 40, 8, 48, 16, 56, 24
]
# Type of crypting being done
ENCRYPT = 0x00
DECRYPT = 0x01
# Initialisation
def __init__(self, key, mode=ECB, IV=None, pad=None, padmode=PAD_NORMAL):
# Sanity checking of arguments.
if len(key) != 8:
raise ValueError("Invalid DES key size. Key must be exactly 8 bytes long.")
_baseDes.__init__(self, mode, IV, pad, padmode)
self.key_size = 8
self.L = []
self.R = []
self.Kn = [ [0] * 48 ] * 16 # 16 48-bit keys (K1 - K16)
self.final = []
self.setKey(key)
def setKey(self, key):
"""Will set the crypting key for this object. Must be 8 bytes."""
_baseDes.setKey(self, key)
self.__create_sub_keys()
def __String_to_BitList(self, data):
"""Turn the string data, into a list of bits (1, 0)'s"""
if _pythonMajorVersion < 3:
# Turn the strings into integers. Python 3 uses a bytes
# class, which already has this behaviour.
data = [ord(c) for c in data]
l = len(data) * 8
result = [0] * l
pos = 0
for ch in data:
i = 7
while i >= 0:
if ch & (1 << i) != 0:
result[pos] = 1
else:
result[pos] = 0
pos += 1
i -= 1
return result
def __BitList_to_String(self, data):
"""Turn the list of bits -> data, into a string"""
result = []
pos = 0
c = 0
while pos < len(data):
c += data[pos] << (7 - (pos % 8))
if (pos % 8) == 7:
result.append(c)
c = 0
pos += 1
if _pythonMajorVersion < 3:
return ''.join([ chr(c) for c in result ])
else:
return bytes(result)
def __permutate(self, table, block):
"""Permutate this block with the specified table"""
return list([block[x] for x in table])
# Transform the secret key, so that it is ready for data processing
# Create the 16 subkeys, K[1] - K[16]
def __create_sub_keys(self):
"""Create the 16 subkeys K[1] to K[16] from the given key"""
key = self.__permutate(des.__pc1, self.__String_to_BitList(self.getKey()))
i = 0
# Split into Left and Right sections
self.L = key[:28]
self.R = key[28:]
while i < 16:
j = 0
# Perform circular left shifts
while j < des.__left_rotations[i]:
self.L.append(self.L[0])
del self.L[0]
self.R.append(self.R[0])
del self.R[0]
j += 1
# Create one of the 16 subkeys through pc2 permutation
self.Kn[i] = self.__permutate(des.__pc2, self.L + self.R)
i += 1
# Main part of the encryption algorithm, the number cruncher :)
def __des_crypt(self, block, crypt_type):
"""Crypt the block of data through DES bit-manipulation"""
block = self.__permutate(des.__ip, block)
self.L = block[:32]
self.R = block[32:]
# Encryption starts from Kn[1] through to Kn[16]
if crypt_type == des.ENCRYPT:
iteration = 0
iteration_adjustment = 1
# Decryption starts from Kn[16] down to Kn[1]
else:
iteration = 15
iteration_adjustment = -1
i = 0
while i < 16:
# Make a copy of R[i-1], this will later become L[i]
tempR = self.R[:]
# Permutate R[i - 1] to start creating R[i]
self.R = self.__permutate(des.__expansion_table, self.R)
# Exclusive or R[i - 1] with K[i], create B[1] to B[8] whilst here
self.R = list(map(lambda x, y: x ^ y, self.R, self.Kn[iteration]))
B = [self.R[:6], self.R[6:12], self.R[12:18], self.R[18:24], self.R[24:30], self.R[30:36], self.R[36:42], self.R[42:]]
# Optimization: Replaced below commented code with above
#j = 0
#B = []
#while j < len(self.R):
# self.R[j] = self.R[j] ^ self.Kn[iteration][j]
# j += 1
# if j % 6 == 0:
# B.append(self.R[j-6:j])
# Permutate B[1] to B[8] using the S-Boxes
j = 0
Bn = [0] * 32
pos = 0
while j < 8:
# Work out the offsets
m = (B[j][0] << 1) + B[j][5]
n = (B[j][1] << 3) + (B[j][2] << 2) + (B[j][3] << 1) + B[j][4]
# Find the permutation value
v = des.__sbox[j][(m << 4) + n]
# Turn value into bits, add it to result: Bn
Bn[pos] = (v & 8) >> 3
Bn[pos + 1] = (v & 4) >> 2
Bn[pos + 2] = (v & 2) >> 1
Bn[pos + 3] = v & 1
pos += 4
j += 1
# Permutate the concatination of B[1] to B[8] (Bn)
self.R = self.__permutate(des.__p, Bn)
# Xor with L[i - 1]
self.R = list(map(lambda x, y: x ^ y, self.R, self.L))
# Optimization: This now replaces the below commented code
#j = 0
#while j < len(self.R):
# self.R[j] = self.R[j] ^ self.L[j]
# j += 1
# L[i] becomes R[i - 1]
self.L = tempR
i += 1
iteration += iteration_adjustment
# Final permutation of R[16]L[16]
self.final = self.__permutate(des.__fp, self.R + self.L)
return self.final
# Data to be encrypted/decrypted
def crypt(self, data, crypt_type):
"""Crypt the data in blocks, running it through des_crypt()"""
# Error check the data
if not data:
return ''
if len(data) % self.block_size != 0:
if crypt_type == des.DECRYPT: # Decryption must work on 8 byte blocks
raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n.")
if not self.getPadding():
raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n. Try setting the optional padding character")
else:
data += (self.block_size - (len(data) % self.block_size)) * self.getPadding()
# print "Len of data: %f" % (len(data) / self.block_size)
if self.getMode() == CBC:
if self.getIV():
iv = self.__String_to_BitList(self.getIV())
else:
raise ValueError("For CBC mode, you must supply the Initial Value (IV) for ciphering")
# Split the data into blocks, crypting each one seperately
i = 0
dict = {}
result = []
#cached = 0
#lines = 0
while i < len(data):
# Test code for caching encryption results
#lines += 1
#if dict.has_key(data[i:i+8]):
#print "Cached result for: %s" % data[i:i+8]
# cached += 1
# result.append(dict[data[i:i+8]])
# i += 8
# continue
block = self.__String_to_BitList(data[i:i+8])
# Xor with IV if using CBC mode
if self.getMode() == CBC:
if crypt_type == des.ENCRYPT:
block = list(map(lambda x, y: x ^ y, block, iv))
#j = 0
#while j < len(block):
# block[j] = block[j] ^ iv[j]
# j += 1
processed_block = self.__des_crypt(block, crypt_type)
if crypt_type == des.DECRYPT:
processed_block = list(map(lambda x, y: x ^ y, processed_block, iv))
#j = 0
#while j < len(processed_block):
# processed_block[j] = processed_block[j] ^ iv[j]
# j += 1
iv = block
else:
iv = processed_block
else:
processed_block = self.__des_crypt(block, crypt_type)
# Add the resulting crypted block to our list
#d = self.__BitList_to_String(processed_block)
#result.append(d)
result.append(self.__BitList_to_String(processed_block))
#dict[data[i:i+8]] = d
i += 8
# print "Lines: %d, cached: %d" % (lines, cached)
# Return the full crypted string
if _pythonMajorVersion < 3:
return ''.join(result)
else:
return bytes.fromhex('').join(result)
def encrypt(self, data, pad=None, padmode=None):
"""encrypt(data, [pad], [padmode]) -> bytes
data : Bytes to be encrypted
pad : Optional argument for encryption padding. Must only be one byte
padmode : Optional argument for overriding the padding mode.
The data must be a multiple of 8 bytes and will be encrypted
with the already specified key. Data does not have to be a
multiple of 8 bytes if the padding character is supplied, or
the padmode is set to PAD_PKCS5, as bytes will then added to
ensure the be padded data is a multiple of 8 bytes.
"""
data = self._guardAgainstUnicode(data)
if pad is not None:
pad = self._guardAgainstUnicode(pad)
data = self._padData(data, pad, padmode)
return self.crypt(data, des.ENCRYPT)
def decrypt(self, data, pad=None, padmode=None):
"""decrypt(data, [pad], [padmode]) -> bytes
data : Bytes to be encrypted
pad : Optional argument for decryption padding. Must only be one byte
padmode : Optional argument for overriding the padding mode.
The data must be a multiple of 8 bytes and will be decrypted
with the already specified key. In PAD_NORMAL mode, if the
optional padding character is supplied, then the un-encrypted
data will have the padding characters removed from the end of
the bytes. This pad removal only occurs on the last 8 bytes of
the data (last data block). In PAD_PKCS5 mode, the special
padding end markers will be removed from the data after decrypting.
"""
data = self._guardAgainstUnicode(data)
if pad is not None:
pad = self._guardAgainstUnicode(pad)
data = self.crypt(data, des.DECRYPT)
return self._unpadData(data, pad, padmode)
#############################################################################
# Triple DES #
#############################################################################
class triple_des(_baseDes):
"""Triple DES encryption/decrytpion class
This algorithm uses the DES-EDE3 (when a 24 byte key is supplied) or
the DES-EDE2 (when a 16 byte key is supplied) encryption methods.
Supports ECB (Electronic Code Book) and CBC (Cypher Block Chaining) modes.
pyDes.des(key, [mode], [IV])
key -> Bytes containing the encryption key, must be either 16 or
24 bytes long
mode -> Optional argument for encryption type, can be either pyDes.ECB
(Electronic Code Book), pyDes.CBC (Cypher Block Chaining)
IV -> Optional Initial Value bytes, must be supplied if using CBC mode.
Must be 8 bytes in length.
pad -> Optional argument, set the pad character (PAD_NORMAL) to use
during all encrypt/decrpt operations done with this instance.
padmode -> Optional argument, set the padding mode (PAD_NORMAL or
PAD_PKCS5) to use during all encrypt/decrpt operations done
with this instance.
"""
def __init__(self, key, mode=ECB, IV=None, pad=None, padmode=PAD_NORMAL):
_baseDes.__init__(self, mode, IV, pad, padmode)
self.setKey(key)
def setKey(self, key):
"""Will set the crypting key for this object. Either 16 or 24 bytes long."""
self.key_size = 24 # Use DES-EDE3 mode
if len(key) != self.key_size:
if len(key) == 16: # Use DES-EDE2 mode
self.key_size = 16
else:
raise ValueError("Invalid triple DES key size. Key must be either 16 or 24 bytes long")
if self.getMode() == CBC:
if not self.getIV():
# Use the first 8 bytes of the key
self._iv = key[:self.block_size]
if len(self.getIV()) != self.block_size:
raise ValueError("Invalid IV, must be 8 bytes in length")
self.__key1 = des(key[:8], self._mode, self._iv,
self._padding, self._padmode)
self.__key2 = des(key[8:16], self._mode, self._iv,
self._padding, self._padmode)
if self.key_size == 16:
self.__key3 = self.__key1
else:
self.__key3 = des(key[16:], self._mode, self._iv,
self._padding, self._padmode)
_baseDes.setKey(self, key)
# Override setter methods to work on all 3 keys.
def setMode(self, mode):
"""Sets the type of crypting mode, pyDes.ECB or pyDes.CBC"""
_baseDes.setMode(self, mode)
for key in (self.__key1, self.__key2, self.__key3):
key.setMode(mode)
def setPadding(self, pad):
"""setPadding() -> bytes of length 1. Padding character."""
_baseDes.setPadding(self, pad)
for key in (self.__key1, self.__key2, self.__key3):
key.setPadding(pad)
def setPadMode(self, mode):
"""Sets the type of padding mode, pyDes.PAD_NORMAL or pyDes.PAD_PKCS5"""
_baseDes.setPadMode(self, mode)
for key in (self.__key1, self.__key2, self.__key3):
key.setPadMode(mode)
def setIV(self, IV):
"""Will set the Initial Value, used in conjunction with CBC mode"""
_baseDes.setIV(self, IV)
for key in (self.__key1, self.__key2, self.__key3):
key.setIV(IV)
def encrypt(self, data, pad=None, padmode=None):
"""encrypt(data, [pad], [padmode]) -> bytes
data : bytes to be encrypted
pad : Optional argument for encryption padding. Must only be one byte
padmode : Optional argument for overriding the padding mode.
The data must be a multiple of 8 bytes and will be encrypted
with the already specified key. Data does not have to be a
multiple of 8 bytes if the padding character is supplied, or
the padmode is set to PAD_PKCS5, as bytes will then added to
ensure the be padded data is a multiple of 8 bytes.
"""
ENCRYPT = des.ENCRYPT
DECRYPT = des.DECRYPT
data = self._guardAgainstUnicode(data)
if pad is not None:
pad = self._guardAgainstUnicode(pad)
# Pad the data accordingly.
data = self._padData(data, pad, padmode)
if self.getMode() == CBC:
self.__key1.setIV(self.getIV())
self.__key2.setIV(self.getIV())
self.__key3.setIV(self.getIV())
i = 0
result = []
while i < len(data):
block = self.__key1.crypt(data[i:i+8], ENCRYPT)
block = self.__key2.crypt(block, DECRYPT)
block = self.__key3.crypt(block, ENCRYPT)
self.__key1.setIV(block)
self.__key2.setIV(block)
self.__key3.setIV(block)
result.append(block)
i += 8
if _pythonMajorVersion < 3:
return ''.join(result)
else:
return bytes.fromhex('').join(result)
else:
data = self.__key1.crypt(data, ENCRYPT)
data = self.__key2.crypt(data, DECRYPT)
return self.__key3.crypt(data, ENCRYPT)
def decrypt(self, data, pad=None, padmode=None):
"""decrypt(data, [pad], [padmode]) -> bytes
data : bytes to be encrypted
pad : Optional argument for decryption padding. Must only be one byte
padmode : Optional argument for overriding the padding mode.
The data must be a multiple of 8 bytes and will be decrypted
with the already specified key. In PAD_NORMAL mode, if the
optional padding character is supplied, then the un-encrypted
data will have the padding characters removed from the end of
the bytes. This pad removal only occurs on the last 8 bytes of
the data (last data block). In PAD_PKCS5 mode, the special
padding end markers will be removed from the data after
decrypting, no pad character is required for PAD_PKCS5.
"""
ENCRYPT = des.ENCRYPT
DECRYPT = des.DECRYPT
data = self._guardAgainstUnicode(data)
if pad is not None:
pad = self._guardAgainstUnicode(pad)
if self.getMode() == CBC:
self.__key1.setIV(self.getIV())
self.__key2.setIV(self.getIV())
self.__key3.setIV(self.getIV())
i = 0
result = []
while i < len(data):
iv = data[i:i+8]
block = self.__key3.crypt(iv, DECRYPT)
block = self.__key2.crypt(block, ENCRYPT)
block = self.__key1.crypt(block, DECRYPT)
self.__key1.setIV(iv)
self.__key2.setIV(iv)
self.__key3.setIV(iv)
result.append(block)
i += 8
if _pythonMajorVersion < 3:
data = ''.join(result)
else:
data = bytes.fromhex('').join(result)
else:
data = self.__key3.crypt(data, DECRYPT)
data = self.__key2.crypt(data, ENCRYPT)
data = self.__key1.crypt(data, DECRYPT)
return self._unpadData(data, pad, padmode)

View File

@@ -0,0 +1,22 @@
def RC4_encrypt(key, data):
S = list(range(256))
j = 0
key_len = len(key)
for i in list(range(256)):
j = (j + S[i] + key[i % key_len]) % 256
S[i], S[j] = S[j], S[i]
j = 0
y = 0
out = []
for char in data:
j = (j + 1) % 256
y = (y + S[j]) % 256
S[j], S[y] = S[y], S[j]
out.append(char ^ S[(S[j] + S[y]) % 256])
return bytes(out)

View File

@@ -0,0 +1,109 @@
__author__ = 'Thomas Dixon'
__license__ = 'MIT'
import copy, struct, sys
digest_size = 32
blocksize = 1
def new(m=None):
return sha256(m)
class sha256(object):
_k = (0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2)
_h = (0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19)
_output_size = 8
blocksize = 1
block_size = 64
digest_size = 32
def __init__(self, m=None):
self._buffer = ''
self._counter = 0
if m is not None:
if type(m) is not str:
raise TypeError('%s() argument 1 must be string, not %s' % (self.__class__.__name__, type(m).__name__))
self.update(m)
def _rotr(self, x, y):
return ((x >> y) | (x << (32-y))) & 0xFFFFFFFF
def _sha256_process(self, c):
w = [0]*64
w[0:15] = struct.unpack('!16L', c)
for i in range(16, 64):
s0 = self._rotr(w[i-15], 7) ^ self._rotr(w[i-15], 18) ^ (w[i-15] >> 3)
s1 = self._rotr(w[i-2], 17) ^ self._rotr(w[i-2], 19) ^ (w[i-2] >> 10)
w[i] = (w[i-16] + s0 + w[i-7] + s1) & 0xFFFFFFFF
a,b,c,d,e,f,g,h = self._h
for i in range(64):
s0 = self._rotr(a, 2) ^ self._rotr(a, 13) ^ self._rotr(a, 22)
maj = (a & b) ^ (a & c) ^ (b & c)
t2 = s0 + maj
s1 = self._rotr(e, 6) ^ self._rotr(e, 11) ^ self._rotr(e, 25)
ch = (e & f) ^ ((~e) & g)
t1 = h + s1 + ch + self._k[i] + w[i]
h = g
g = f
f = e
e = (d + t1) & 0xFFFFFFFF
d = c
c = b
b = a
a = (t1 + t2) & 0xFFFFFFFF
self._h = [(x+y) & 0xFFFFFFFF for x,y in zip(self._h, [a,b,c,d,e,f,g,h])]
def update(self, m):
if not m:
return
if type(m) is not str:
raise TypeError('%s() argument 1 must be string, not %s' % (sys._getframe().f_code.co_name, type(m).__name__))
self._buffer += m
self._counter += len(m)
while len(self._buffer) >= 64:
self._sha256_process(self._buffer[:64])
self._buffer = self._buffer[64:]
def digest(self):
mdi = self._counter & 0x3F
length = struct.pack('!Q', self._counter<<3)
if mdi < 56:
padlen = 55-mdi
else:
padlen = 119-mdi
r = self.copy()
r.update('\x80'+('\x00'*padlen)+length)
return ''.join([struct.pack('!L', i) for i in r._h[:self._output_size]])
def hexdigest(self):
return self.digest().encode('hex')
def copy(self):
return copy.deepcopy(self)