From 3a53c2eb1e9a8369322b6c8caf41744eaad12877 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Tue, 6 Dec 2016 19:43:46 +0000 Subject: [PATCH 01/16] bump version to 0.2.5 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 65295524..1b739b19 100644 --- a/setup.py +++ b/setup.py @@ -75,7 +75,7 @@ def run_setup(with_extensions): ext_modules.append(lw_krb_module) cmdclass = dict(cmdclass, build_ext=ve_build_ext) setup(name='Pike', - version='0.2.4', + version='0.2.5', description='Pure python SMB client', author='Brian Koropoff', author_email='Brian.Koropoff@emc.com', From f27a2c109cb32a551739fdcd82ea472805a9dccd Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Fri, 2 Dec 2016 20:04:36 +0000 Subject: [PATCH 02/16] fix peer connection drop notification in transport the correct events to catch peer disconnect were not being registered on the poll (linux) poller. in some cases, pre-connection failures were not being propagated since the connection is now happening async, the previous logic was ignoring errors on unestablished connections. obviously we want errors such as connection refused. --- pike/model.py | 15 +++++++++++---- pike/transport.py | 20 ++++++++++++-------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/pike/model.py b/pike/model.py index 70b4f519..0d471c4a 100644 --- a/pike/model.py +++ b/pike/model.py @@ -588,6 +588,8 @@ def handle_read(self): self.process_callbacks(EV_RES_PRE_RECV, remaining) data = array.array('B', self.recv(remaining)) self.process_callbacks(EV_RES_POST_RECV, data) + if not data: + self.handle_error() self._in_buffer.extend(data) avail = len(self._in_buffer) if avail >= 4: @@ -626,15 +628,20 @@ def close(self): This unceremoniously terminates the connection and fails all outstanding requests with EOFError. """ + # If there is no error, propagate EOFError + if self.error is None: + self.error = EOFError("close") + + # if the connection hasn't been established, raise the error + if self.connection_future.response is None: + self.connection_future(self.error) + + # otherwise, ignore this connection since it's not associated with its client if self not in self.client._connections: return super(Connection, self).close() - # Run down connection - if self.error is None: - self.error = EOFError("close") - if self.remote_addr is not None: self.client.logger.debug("disconnect (%s/%s -> %s/%s): %s", self.local_addr[0], self.local_addr[1], diff --git a/pike/transport.py b/pike/transport.py index e25e252f..2332dbe1 100644 --- a/pike/transport.py +++ b/pike/transport.py @@ -388,6 +388,13 @@ class PollPoller(BasePoller): """ Implementation of poll, available on Linux """ + READ_EVENTS = (select.POLLIN | + select.POLLERR | + select.POLLHUP | + select.POLLNVAL | + select.POLLMSG | + select.POLLPRI) + WRITE_EVENTS = select.POLLOUT def __init__(self): super(PollPoller, self).__init__() self.p = select.poll() @@ -396,7 +403,7 @@ def add_channel(self, transport): super(PollPoller, self).add_channel(transport) self.p.register( transport._fileno, - select.POLLIN | select.POLLOUT | select.POLLERR) + self.READ_EVENTS | self.WRITE_EVENTS) def del_channel(self, transport): super(PollPoller, self).del_channel(transport) @@ -406,21 +413,18 @@ def defer_write(self, transport): super(PollPoller, self).defer_write(transport) self.p.modify( transport._fileno, - select.POLLIN | select.POLLOUT | select.POLLERR) + self.READ_EVENTS | self.WRITE_EVENTS) def poll(self): events = self.p.poll(0) readables = [] writables = [] for fd, event in events: - if event == select.POLLIN: + if event & self.READ_EVENTS: readables.append(fd) - elif event == select.POLLOUT: + elif event & self.WRITE_EVENTS: writables.append(fd) - self.p.modify(fd, select.POLLIN | select.POLLERR) - elif event == select.POLLERR: - t = self.connections[fd] - t.handle_error() + self.p.modify(fd, self.READ_EVENTS) self.process_readables(readables) self.process_writables(writables) From 77befa3b109cce889e2b2ed2841f350651afd933 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Tue, 6 Dec 2016 19:39:45 +0000 Subject: [PATCH 03/16] improve file info type handling now undefined info types and classes will still be recoded properly into a raw byte field. these buffers can be treated as opaque blobs to copy security descriptors and other complex info types which are not yet decoded --- pike/model.py | 40 +++++++++++-- pike/smb2.py | 162 +++++++++++++++++--------------------------------- 2 files changed, 88 insertions(+), 114 deletions(-) diff --git a/pike/model.py b/pike/model.py index 70b4f519..c47a859f 100644 --- a/pike/model.py +++ b/pike/model.py @@ -1468,7 +1468,8 @@ def query_file_info_request( create_res, file_information_class=smb2.FILE_BASIC_INFORMATION, info_type=smb2.SMB2_0_INFO_FILE, - output_buffer_length=4096): + output_buffer_length=4096, + additional_information=None): smb_req = self.request(obj=create_res) query_req = smb2.QueryInfoRequest(smb_req) @@ -1476,27 +1477,54 @@ def query_file_info_request( query_req.file_information_class = file_information_class query_req.file_id = create_res.file_id query_req.output_buffer_length = output_buffer_length + if additional_information: + query_req.additional_information = additional_information return query_req def query_file_info(self, create_res, file_information_class=smb2.FILE_BASIC_INFORMATION, info_type=smb2.SMB2_0_INFO_FILE, - output_buffer_length=4096): + output_buffer_length=4096, + additional_information=None): return self.connection.transceive( self.query_file_info_request( create_res, file_information_class, info_type, - output_buffer_length).parent.parent)[0][0][0] + output_buffer_length, + additional_information).parent.parent)[0][0][0] - @contextlib.contextmanager - def set_file_info(self, handle, cls): + def set_file_info_request( + self, + handle, + file_information_class=smb2.FILE_BASIC_INFORMATION, + info_type=smb2.SMB2_0_INFO_FILE, + input_buffer_length=4096, + additional_information=None): smb_req = self.request(obj=handle) set_req = smb2.SetInfoRequest(smb_req) set_req.file_id = handle.file_id + set_req.file_information_class = file_information_class + set_req.info_type = info_type + set_req.input_buffer_length = input_buffer_length + if additional_information: + set_req.additional_information = additional_information + return set_req + + @contextlib.contextmanager + def set_file_info(self, handle, cls): + info_type = file_information_class = None + if hasattr(cls, "info_type"): + info_type = cls.info_type + if hasattr(cls, "file_information_class"): + file_information_class = cls.file_information_class + set_req = self.set_file_info_request( + handle, + file_information_class, + info_type) yield cls(set_req) - self.connection.transceive(smb_req.parent)[0] + self.connection.transceive(set_req.parent.parent)[0] # Send an echo request and get a response def echo(self): diff --git a/pike/smb2.py b/pike/smb2.py index 93ad641b..53bb75fa 100644 --- a/pike/smb2.py +++ b/pike/smb2.py @@ -1718,6 +1718,7 @@ class QueryDirectoryResponse(Response): def __init__(self, parent): Response.__init__(self, parent) + self._file_information_class = None # Try to figure out file information class by looking up # associated request in context @@ -1726,10 +1727,8 @@ def __init__(self, parent): if context: request = context.get_request(parent.message_id) - if request: + if request and request.children: self._file_information_class = request[0].file_information_class - else: - self._file_information_class = None self._entries = [] @@ -1754,7 +1753,7 @@ def _decode(self, cur): while cur < end: cls(self).decode(cur) else: - cur.advanceto(end) + Information(self, end).decode(cur) class InfoType(core.ValueEnum): SMB2_0_INFO_FILE = 0x01 @@ -1815,24 +1814,21 @@ class QueryInfoResponse(Response): structure_size = 9 allowed_status = [ntstatus.STATUS_SUCCESS, ntstatus.STATUS_BUFFER_OVERFLOW] - _file_info_map = {} - file_information = core.Register(_file_info_map, "file_information_class") - - _fs_info_map = {} - fs_information = core.Register(_fs_info_map, "file_information_class") + _info_map = {} + information = core.Register(_info_map, "info_type", "file_information_class") def __init__(self, parent): Response.__init__(self, parent) - self._info_type = 0 - self._file_information_class = 0 + self._info_type = None + self._file_information_class = None context = self.context if context: request = context.get_request(parent.message_id) - if request: - self._info_type = request[0].info_type - self._file_information_class = request[0].file_information_class + if request and request.children and request[0].children: + self._info_type = request[0][0].info_type + self._file_information_class = request[0][0].file_information_class self._entries = [] @@ -1849,20 +1845,13 @@ def _decode(self, cur): cur.advanceto(self.parent.start + output_buffer_offset) end = cur + output_buffer_length - if self._file_information_class is not None: - if self._info_type == SMB2_0_INFO_FILE: - table = self._file_info_map - elif self._info_type == SMB2_0_INFO_FILESYSTEM: - table = self._fs_info_map - else: - table = None - - if table is not None and self._file_information_class in table: - cls = table[self._file_information_class] - with cur.bounded(cur, end): - cls(self).decode(cur) + key = (self._info_type, self._file_information_class) + if key in self._info_map: + cls = self._info_map[key] + with cur.bounded(cur, end): + cls(self).decode(cur) else: - cur.advanceto(end) + Information(self, end).decode(cur) class SetInfoRequest(Request): @@ -1875,7 +1864,7 @@ def __init__(self, parent): self.file_information_class = 0 self.input_buffer_length = 0 self.input_buffer_offset = 0 - self.security_info = 0 # SMB2_QUERY_INFO.AdditionalInformation + self.additional_information = 0 self.file_id = None self._entries = [] @@ -1888,10 +1877,7 @@ def append(self, e): def _encode(self, cur): if not self.info_type: - # Only one supported at the moment. - # When we support more, look at child object - # to decide what kind of set we are doing - self.info_type = SMB2_0_INFO_FILE + self.info_type = self[0].info_type if not self.file_information_class: # Determine it from child object @@ -1904,7 +1890,7 @@ def _encode(self, cur): buffer_offset_hole = cur.hole.encode_uint16le(0) cur.encode_uint16le(0) # Reserved - cur.encode_uint32le(self.security_info) + cur.encode_uint32le(self.additional_information) cur.encode_uint64le(self.file_id[0]) cur.encode_uint64le(self.file_id[1]) @@ -1929,14 +1915,43 @@ def _decode(self, cur): pass +class Information(core.Frame): + """ + Base class for all info frames. Holds the raw data if no specific decoder + has been registered for the given info type + """ + info_type = 0 + file_information_class = 0 + + def __init__(self, parent = None, end = None): + super(Information, self).__init__(parent) + if parent is not None: + parent.append(self) + self.end = end + self.raw_data = '' + + def _decode(self, cur): + """ + decode the raw data to the specified end of buffer + """ + if self.end is not None: + self.raw_data = cur.decode_bytes(self.end - cur) + + def _encode(self, cur): + """ + encode the raw data + """ + cur.encode_bytes(self.raw_data) + + @QueryDirectoryResponse.file_information -@QueryInfoResponse.file_information -class FileInformation(core.Frame): - pass +@QueryInfoResponse.information +class FileInformation(Information): + info_type = SMB2_0_INFO_FILE -@QueryInfoResponse.fs_information -class FileSystemInformation(core.Frame): - pass +@QueryInfoResponse.information +class FileSystemInformation(Information): + info_type = SMB2_0_INFO_FILESYSTEM class FileAccessInformation(FileInformation): file_information_class = FILE_ACCESS_INFORMATION @@ -1944,8 +1959,6 @@ class FileAccessInformation(FileInformation): def __init__(self, parent = None): FileInformation.__init__(self, parent) self.access_flags = 0 - if parent is not None: - parent.append(self) def _decode(self, cur): self.access_flags = Access(cur.decode_uint32le()) @@ -1971,8 +1984,6 @@ class FileAlignmentInformation(FileInformation): def __init__(self, parent = None): FileInformation.__init__(self, parent) self.alignment_requirement = 0 - if parent is not None: - parent.append(self) def _decode(self, cur): self.alignment_requirement = Alignment(cur.decode_uint32le()) @@ -1982,8 +1993,6 @@ class FileAllInformation(FileInformation): def __init__(self, parent = None): FileInformation.__init__(self, parent) - if parent is not None: - parent.append(self) self.basic_information = FileBasicInformation() self.standard_information = FileStandardInformation() @@ -2013,8 +2022,6 @@ def __init__(self, parent = None): self.allocation_size = 0 self.file_attributes = 0 self.file_name = None - if parent is not None: - parent.append(self) def _decode(self, cur): next_offset = cur.decode_uint32le() @@ -2051,8 +2058,6 @@ def __init__(self, parent = None): self.file_attributes = 0 self.ea_size = 0 self.file_name = None - if parent is not None: - parent.append(self) def _decode(self, cur): next_offset = cur.decode_uint32le() @@ -2090,8 +2095,6 @@ def __init__(self, parent = None): self.reserved = 0 self.file_id = 0 self.file_name = None - if parent is not None: - parent.append(self) def _decode(self, cur): next_offset = cur.decode_uint32le() @@ -2132,8 +2135,6 @@ def __init__(self, parent = None): self.file_id = 0 self.short_name = None self.file_name = None - if parent is not None: - parent.append(self) def _decode(self, cur): next_offset = cur.decode_uint32le() @@ -2171,8 +2172,6 @@ def __init__(self, parent = None): self.last_write_time = 0 self.change_time = 0 self.file_attributes = 0 - if parent is not None: - parent.append(self) def _decode(self, cur): self.creation_time = nttime.NtTime(cur.decode_uint64le()) @@ -2205,8 +2204,6 @@ def __init__(self, parent = None): self.allocation_size = 0 self.end_of_file = 0 self.file_attributes = 0 - if parent is not None: - parent.append(self) def _decode(self, cur): self.creation_time = nttime.NtTime(cur.decode_uint64le()) @@ -2227,8 +2224,6 @@ def __init__(self, parent = None): FileInformation.__init__(self, parent) self.file_attributes = 0 self.reparse_tag = 0 - if parent is not None: - parent.append(self) def _decode(self, cur): self.file_attributes = FileAttributes(cur.decode_uint32le()) @@ -2245,8 +2240,6 @@ def __init__(self, parent = None): self.stream_size = 0 self.stream_allocation_size = 0 self.stream_name = None - if parent is not None: - parent.append(self) def _decode(self, cur): self.next_entry_offset = cur.decode_uint32le() @@ -2275,8 +2268,6 @@ def __init__(self, parent = None): self.chunk_shift = 0 self.cluster_shift = None self.reserved = 0 - if parent is not None: - parent.append(self) def _decode(self, cur): self.compressed_file_size = cur.decode_int64le() @@ -2294,8 +2285,6 @@ class FileInternalInformation(FileInformation): def __init__(self, parent = None): FileInformation.__init__(self, parent) self.index_number = 0 - if parent is not None: - parent.append(self) def _decode(self, cur): self.index_number = cur.decode_uint64le() @@ -2307,8 +2296,6 @@ def __init__(self, parent = None): FileInformation.__init__(self, parent) # See Create options (e.g. FILE_DELETE_ON_CLOSE) for available flags self.mode = 0 - if parent is not None: - parent.append(self) def _decode(self, cur): self.mode = CreateOptions(cur.decode_uint32le()) @@ -2323,9 +2310,6 @@ def __init__(self, parent = None): FileInformation.__init__(self, parent) self.file_name = None - if parent is not None: - parent.append(self) - def _decode(self, cur): file_name_length = cur.decode_uint32le() self.file_name = cur.decode_utf16le(file_name_length) @@ -2339,9 +2323,6 @@ def __init__(self, parent = None): self.root_directory = (0,0) self.file_name = None - if parent is not None: - parent.append(self) - def _encode(self, cur): cur.encode_uint8le(self.replace_if_exists) cur.encode_uint8le(0) # reserved @@ -2361,9 +2342,6 @@ def __init__(self, parent = None): FileInformation.__init__(self,parent) self.allocation_size = 0 - if parent is not None: - parent.append(self) - def _encode(self, cur): cur.encode_int64le(self.allocation_size) @@ -2375,9 +2353,6 @@ def __init__(self, parent = None): FileInformation.__init__(self,parent) self.delete_pending = 0 - if parent is not None: - parent.append(self) - def _encode(self, cur): cur.encode_uint8le(self.delete_pending) @@ -2389,9 +2364,6 @@ def __init__(self, parent = None): FileInformation.__init__(self, parent) self.endoffile = 0 - if parent is not None: - parent.append(self) - def _encode(self, cur): cur.encode_int64le(self.endoffile) @@ -2403,9 +2375,6 @@ def __init__(self, parent = None): FileInformation.__init__(self, parent) self.valid_data_length = 0 - if parent is not None: - parent.append(self) - def _encode(self, cur): cur.encode_int64le(self.valid_data_length) @@ -2417,9 +2386,6 @@ def __init__(self, parent = None): self.file_index = 0 self.file_name = None - if parent is not None: - parent.append(self) - def _decode(self, cur): next_entry_offset = cur.decode_uint32le() self.file_index = cur.decode_uint32le() @@ -2438,8 +2404,6 @@ class FilePositionInformation(FileInformation): def __init__(self, parent = None): FileInformation.__init__(self, parent) self.current_byte_offset = 0 - if parent is not None: - parent.append(self) def _decode(self, cur): self.current_byte_offset = cur.decode_uint64le() @@ -2457,8 +2421,6 @@ def __init__(self, parent = None): self.number_of_links = 0 self.delete_pending = 0 self.directory = 0 - if parent is not None: - parent.append(self) def _decode(self, cur): self.allocation_size = cur.decode_uint64le() @@ -2484,8 +2446,6 @@ class FileEaInformation(FileInformation): def __init__(self, parent = None): FileInformation.__init__(self, parent) self.ea_size = 0 - if parent is not None: - parent.append(self) def _decode(self, cur): self.ea_size = cur.decode_uint32le() @@ -2500,8 +2460,6 @@ def __init__(self, parent = None): self.available_allocation_units = 0 self.sectors_per_allocation_unit = 0 self.bytes_per_sector = 0 - if parent is not None: - parent.append(self) def _decode(self, cur): self.total_allocation_units = cur.decode_int64le() @@ -2520,8 +2478,6 @@ def __init__(self, parent = None): self.actual_available_allocation_units = 0 self.sectors_per_allocation_unit = 0 self.bytes_per_sector = 0 - if parent is not None: - parent.append(self) def _decode(self, cur): self.total_allocation_units = cur.decode_uint64le() @@ -2563,8 +2519,6 @@ def __init__(self, parent = None): FileSystemInformation.__init__(self, parent) self.device_type = 0 self.characteristics = 0 - if parent is not None: - parent.append(self) def _decode(self, cur): self.device_type = DeviceType(cur.decode_uint32le()) @@ -2607,8 +2561,6 @@ def __init__(self, parent = None): self.maximum_component_name_length = 0 self.file_system_name_length = 0 self.file_system_name = 0 - if parent is not None: - parent.append(self) def _decode(self, cur): self.file_system_attibutes = cur.decode_uint32le() @@ -2628,8 +2580,6 @@ def __init__(self, parent = None): self.supports_objects = 0 self.reserved = 0 self.volume_label = None - if parent is not None: - parent.append(self) def _decode(self, cur): self.volume_creation_time = nttime.NtTime(cur.decode_uint64le()) @@ -2667,8 +2617,6 @@ def __init__(self, parent = None): self.default_quota_limit = 0 self.file_system_control_flags = None self.padding = 0 - if parent is not None: - parent.append(self) def _encode(self, cur): cur.encode_int64le(self.free_space_start_filtering) @@ -2696,8 +2644,6 @@ def __init__(self, parent = None): FileSystemInformation.__init__(self, parent) self.objectid = "" self.extended_info = "" - if parent is not None: - parent.append(self) def _decode(self, cur): for count in xrange(2): From 762e35709a97d4347041be01aa1637fce5d85c85 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Wed, 5 Oct 2016 00:25:00 +0000 Subject: [PATCH 04/16] allow for customizing control fields of negotiate request --- pike/smb2.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/pike/smb2.py b/pike/smb2.py index 53bb75fa..17a905b0 100644 --- a/pike/smb2.py +++ b/pike/smb2.py @@ -477,6 +477,9 @@ def __init__(self, parent): self.capabilities = 0 self.client_guid = [0]*16 self.dialects = [] + self.negotiate_contexts_count = None + self.negotiate_contexts_offset = None + self.negotiate_contexts_data_length = None self._negotiate_contexts = [] def _children(self): @@ -490,7 +493,9 @@ def _encode(self, cur): cur.encode_bytes(self.client_guid) if DIALECT_SMB3_1_1 in self.dialects: negotiate_contexts_offset_hole = cur.hole.encode_uint32le(0) - cur.encode_uint16le(len(self._negotiate_contexts)) + if self.negotiate_contexts_count is None: + self.negotiate_contexts_count = len(self._negotiate_contexts) + cur.encode_uint16le(self.negotiate_contexts_count) cur.encode_uint16le(0) #reserved else: cur.encode_uint64le(0) @@ -499,7 +504,11 @@ def _encode(self, cur): if self._negotiate_contexts: cur.align(self.parent.start, 8) - negotiate_contexts_offset_hole(cur - self.parent.start) + if self.negotiate_contexts_offset is not None: + negotiate_contexts_offset_hole( + self.negotiate_contexts_offset) + else: + negotiate_contexts_offset_hole(cur - self.parent.start) for ctx in self._negotiate_contexts: cur.align(self.parent.start, 8) @@ -508,7 +517,10 @@ def _encode(self, cur): cur.encode_uint32le(0) # reserved data_start = cur.copy() ctx.encode(cur) - data_length_hole(cur - data_start) + if self.negotiate_contexts_data_length is not None: + data_length_hole(self.negotiate_contexts_data_length) + else: + data_length_hole(cur - data_start) def append(self, e): self._negotiate_contexts.append(e) From ad04b17c1e6bbc3832d21f1363f7a89d8f86efd8 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Wed, 5 Oct 2016 23:03:17 +0000 Subject: [PATCH 05/16] add hash_algorithms_count and salt_length as legitimate fields --- pike/smb2.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/pike/smb2.py b/pike/smb2.py index 17a905b0..47db54d4 100644 --- a/pike/smb2.py +++ b/pike/smb2.py @@ -599,19 +599,25 @@ class PreauthIntegrityCapabilities(core.Frame): context_type = SMB2_PREAUTH_INTEGRITY_CAPABILITIES def __init__(self): self.hash_algorithms = [] + self.hash_algorithms_count = None self.salt = "" + self.salt_length = None def _encode(self, cur): - cur.encode_uint16le(len(self.hash_algorithms)) - cur.encode_uint16le(len(self.salt)) + if self.hash_algorithms_count is None: + self.hash_algorithms_count = len(self.hash_algorithms) + cur.encode_uint16le(self.hash_algorithms_count) + if self.salt_length is None: + self.salt_length = len(self.salt) + cur.encode_uint16le(self.salt_length) for h in self.hash_algorithms: cur.encode_uint16le(h) cur.encode_bytes(self.salt) def _decode(self, cur): - hash_algorithm_count = cur.decode_uint16le() - salt_length = cur.decode_uint16le() - for ix in xrange(hash_algorithm_count): + self.hash_algorithm_count = cur.decode_uint16le() + self.salt_length = cur.decode_uint16le() + for ix in xrange(self.hash_algorithm_count): self.hash_algorithms.append(HashAlgorithms(cur.decode_uint16le())) - self.salt = cur.decode_bytes(salt_length) + self.salt = cur.decode_bytes(self.salt_length) class PreauthIntegrityCapabilitiesRequest(NegotiateRequestContext, PreauthIntegrityCapabilities): From 68868d1af5c586fc80644e1764e68d932ec04058 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Wed, 5 Oct 2016 23:03:39 +0000 Subject: [PATCH 06/16] add cipher_count as a legitimate field --- pike/crypto.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pike/crypto.py b/pike/crypto.py index f18ff12b..2a4f4370 100644 --- a/pike/crypto.py +++ b/pike/crypto.py @@ -52,15 +52,18 @@ class EncryptionCapabilities(core.Frame): context_type = smb2.SMB2_ENCRYPTION_CAPABILITIES def __init__(self): self.ciphers = [] + self.cipher_count = None def _encode(self, cur): - cur.encode_uint16le(len(self.ciphers)) + if self.cipher_count is None: + self.cipher_count = len(self.ciphers) + cur.encode_uint16le(self.cipher_count) for c in self.ciphers: cur.encode_uint16le(c) def _decode(self, cur): - cipher_count = cur.decode_uint16le() - for ix in xrange(cipher_count): + self.cipher_count = cur.decode_uint16le() + for ix in xrange(self.cipher_count): self.ciphers.append(Ciphers(cur.decode_uint16le())) From 22f90c8a74ef260572e6c70b7854121876b3e854 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 6 Oct 2016 22:51:59 +0000 Subject: [PATCH 07/16] fix custom data_length field --- pike/smb2.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pike/smb2.py b/pike/smb2.py index 47db54d4..8fec5c17 100644 --- a/pike/smb2.py +++ b/pike/smb2.py @@ -479,7 +479,6 @@ def __init__(self, parent): self.dialects = [] self.negotiate_contexts_count = None self.negotiate_contexts_offset = None - self.negotiate_contexts_data_length = None self._negotiate_contexts = [] def _children(self): @@ -517,10 +516,9 @@ def _encode(self, cur): cur.encode_uint32le(0) # reserved data_start = cur.copy() ctx.encode(cur) - if self.negotiate_contexts_data_length is not None: - data_length_hole(self.negotiate_contexts_data_length) - else: - data_length_hole(cur - data_start) + if ctx.data_length is None: + ctx.data_length = cur - data_start + data_length_hole(ctx.data_length) def append(self, e): self._negotiate_contexts.append(e) @@ -587,6 +585,7 @@ def __init__(self, parent): core.Frame.__init__(self, parent) if parent is not None: parent.append(self) + self.data_length = None @NegotiateResponse.negotiate_context class NegotiateResponseContext(core.Frame): From 961acfe8c9987691f3c70ce0c2cc1888eb226489 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Fri, 7 Oct 2016 21:12:42 +0000 Subject: [PATCH 08/16] add negotiate_contexts_alignment_skew field for really screwing up the packets --- pike/smb2.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pike/smb2.py b/pike/smb2.py index 8fec5c17..96695e2a 100644 --- a/pike/smb2.py +++ b/pike/smb2.py @@ -479,6 +479,7 @@ def __init__(self, parent): self.dialects = [] self.negotiate_contexts_count = None self.negotiate_contexts_offset = None + self.negotiate_contexts_alignment_skew = 0 self._negotiate_contexts = [] def _children(self): @@ -503,6 +504,7 @@ def _encode(self, cur): if self._negotiate_contexts: cur.align(self.parent.start, 8) + cur.seekto(cur + self.negotiate_contexts_alignment_skew) if self.negotiate_contexts_offset is not None: negotiate_contexts_offset_hole( self.negotiate_contexts_offset) From 80ae77230b640956287b369a856ee46ae82eb92c Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Fri, 14 Oct 2016 21:17:57 +0000 Subject: [PATCH 09/16] allow negotiate contexts to be sent regardless of dialect specified --- pike/smb2.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pike/smb2.py b/pike/smb2.py index 96695e2a..000b7fba 100644 --- a/pike/smb2.py +++ b/pike/smb2.py @@ -485,13 +485,18 @@ def __init__(self, parent): def _children(self): return self._negotiate_contexts + def _has_negotiate_contexts(self): + return (self._negotiate_contexts or + self.negotiate_contexts_offset is not None or + self.negotiate_contexts_count is not None) + def _encode(self, cur): cur.encode_uint16le(len(self.dialects)) cur.encode_uint16le(self.security_mode) cur.encode_uint16le(0) cur.encode_uint32le(self.capabilities) cur.encode_bytes(self.client_guid) - if DIALECT_SMB3_1_1 in self.dialects: + if self._has_negotiate_contexts(): negotiate_contexts_offset_hole = cur.hole.encode_uint32le(0) if self.negotiate_contexts_count is None: self.negotiate_contexts_count = len(self._negotiate_contexts) @@ -502,7 +507,7 @@ def _encode(self, cur): for dialect in self.dialects: cur.encode_uint16le(dialect) - if self._negotiate_contexts: + if self._has_negotiate_contexts(): cur.align(self.parent.start, 8) cur.seekto(cur + self.negotiate_contexts_alignment_skew) if self.negotiate_contexts_offset is not None: From 94302926c9f3dfff0c164dcc3d524e2b4d4a0940 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Mon, 17 Oct 2016 23:19:26 +0000 Subject: [PATCH 10/16] store superflous information about the negotiate response also rename _negotiate_contexts for rodolfo --- pike/smb2.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/pike/smb2.py b/pike/smb2.py index 000b7fba..bb297d26 100644 --- a/pike/smb2.py +++ b/pike/smb2.py @@ -480,13 +480,13 @@ def __init__(self, parent): self.negotiate_contexts_count = None self.negotiate_contexts_offset = None self.negotiate_contexts_alignment_skew = 0 - self._negotiate_contexts = [] + self.negotiate_contexts = [] def _children(self): - return self._negotiate_contexts + return self.negotiate_contexts def _has_negotiate_contexts(self): - return (self._negotiate_contexts or + return (self.negotiate_contexts or self.negotiate_contexts_offset is not None or self.negotiate_contexts_count is not None) @@ -499,7 +499,7 @@ def _encode(self, cur): if self._has_negotiate_contexts(): negotiate_contexts_offset_hole = cur.hole.encode_uint32le(0) if self.negotiate_contexts_count is None: - self.negotiate_contexts_count = len(self._negotiate_contexts) + self.negotiate_contexts_count = len(self.negotiate_contexts) cur.encode_uint16le(self.negotiate_contexts_count) cur.encode_uint16le(0) #reserved else: @@ -516,7 +516,7 @@ def _encode(self, cur): else: negotiate_contexts_offset_hole(cur - self.parent.start) - for ctx in self._negotiate_contexts: + for ctx in self.negotiate_contexts: cur.align(self.parent.start, 8) cur.encode_uint16le(ctx.context_type) data_length_hole = cur.hole.encode_uint16le(0) @@ -528,7 +528,7 @@ def _encode(self, cur): data_length_hole(ctx.data_length) def append(self, e): - self._negotiate_contexts.append(e) + self.negotiate_contexts.append(e) class NegotiateResponse(Response): command_id = SMB2_NEGOTIATE @@ -549,15 +549,17 @@ def __init__(self, parent): self.system_time = 0 self.server_start_time = 0 self.security_buffer = None - self._negotiate_contexts = [] + self.negotiate_contexts_count = None + self.negotiate_contexts_offset = None + self.negotiate_contexts = [] def _children(self): - return self._negotiate_contexts + return self.negotiate_contexts def _decode(self, cur): self.security_mode = SecurityMode(cur.decode_uint16le()) self.dialect_revision = Dialect(cur.decode_uint16le()) - negotiate_contexts_count = cur.decode_uint16le() + self.negotiate_contexts_count = cur.decode_uint16le() self.server_guid = cur.decode_bytes(16) self.capabilities = GlobalCaps(cur.decode_uint32le()) self.max_transact_size = cur.decode_uint32le() @@ -569,14 +571,14 @@ def _decode(self, cur): offset = cur.decode_uint16le() length = cur.decode_uint16le() - negotiate_contexts_offset = cur.decode_uint32le() + self.negotiate_contexts_offset = cur.decode_uint32le() # Advance to security buffer cur.advanceto(self.parent.start + offset) self.security_buffer = cur.decode_bytes(length) - if negotiate_contexts_count > 0: - cur.seekto(self.parent.start + negotiate_contexts_offset) - for ix in xrange(negotiate_contexts_count): + if self.negotiate_contexts_count > 0: + cur.seekto(self.parent.start + self.negotiate_contexts_offset) + for ix in xrange(self.negotiate_contexts_count): cur.align(self.parent.start, 8) context_type = cur.decode_uint16le() data_length = cur.decode_uint16le() @@ -585,7 +587,7 @@ def _decode(self, cur): ctx(self).decode(cur) def append(self, e): - self._negotiate_contexts.append(e) + self.negotiate_contexts.append(e) class NegotiateRequestContext(core.Frame): def __init__(self, parent): From 0322d3f9c0898f49c710965ecc6b1914b3509312 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Tue, 18 Oct 2016 00:01:48 +0000 Subject: [PATCH 11/16] rename cipher_count to ciphers_count --- pike/crypto.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pike/crypto.py b/pike/crypto.py index 2a4f4370..57612a1c 100644 --- a/pike/crypto.py +++ b/pike/crypto.py @@ -52,18 +52,18 @@ class EncryptionCapabilities(core.Frame): context_type = smb2.SMB2_ENCRYPTION_CAPABILITIES def __init__(self): self.ciphers = [] - self.cipher_count = None + self.ciphers_count = None def _encode(self, cur): - if self.cipher_count is None: - self.cipher_count = len(self.ciphers) - cur.encode_uint16le(self.cipher_count) + if self.ciphers_count is None: + self.ciphers_count = len(self.ciphers) + cur.encode_uint16le(self.ciphers_count) for c in self.ciphers: cur.encode_uint16le(c) def _decode(self, cur): - self.cipher_count = cur.decode_uint16le() - for ix in xrange(self.cipher_count): + self.ciphers_count = cur.decode_uint16le() + for ix in xrange(self.ciphers_count): self.ciphers.append(Ciphers(cur.decode_uint16le())) From afb164d14144a2294ab5dda468cb53cf17302ed8 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 27 Oct 2016 22:15:55 +0000 Subject: [PATCH 12/16] add let to SessionSetupContext to allow request manipulation --- pike/core.py | 41 +++++++++++++++++++++++++++++++++++++++++ pike/model.py | 22 ++++++++-------------- 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/pike/core.py b/pike/core.py index 5012895e..25d4ca21 100644 --- a/pike/core.py +++ b/pike/core.py @@ -697,3 +697,44 @@ def __or__(self, o): def __and__(self, o): return self.__class__(super(FlagEnum,self).__and__(o)) + +class Let(object): + """ + A Let allows for temporarily storing passed arguments in the _settings + member of a target object for the duration of the contextmanager's scope + + Implementation on a factory class + + class MyThingFactory(object): + def __init__(self): + self._settings = {} + def generate_thing(self): + # do something here + new_obj = Thing() + for k,v in self._settings.iteritems(): + setattr(new_obj, k, v) + return new_obj + def let(self, **kwds): + return Let(self, kwds) + + Calling code that wants to temporarily customize the Thing objects that are + returned by the factory: + + fac = MyThingFactory() + with fac.let(this="that", foo="bar"): + a_thing = fac.generate_thing() + self.assertEqual(a_thing.this, "that") + """ + def __init__(self, target, settings_dict): + self.target = target + self.settings_dict = settings_dict + if not hasattr(target, "_settings"): + target._settings = {} + + def __enter__(self): + self.backup = dict(self.target._settings) + self.target._settings.update(self.settings_dict) + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.target._settings = self.backup diff --git a/pike/model.py b/pike/model.py index 6241b83e..a6eea86a 100644 --- a/pike/model.py +++ b/pike/model.py @@ -913,6 +913,7 @@ def __init__(self, conn, creds=None, bind=None, resume=None, raise ImportError("Neither ntlm nor kerberos authentication " "methods are available") + self._settings = {} self.prev_session_id = 0 self.session_id = 0 self.requests = [] @@ -930,6 +931,9 @@ def __init__(self, conn, creds=None, bind=None, resume=None, assert conn.negotiate_response.dialect_revision >= 0x300 self.prev_session_id = resume.session_id + def let(self, **kwargs): + return core.Let(self, kwargs) + def derive_signing_key(self, session_key=None, context=None): if session_key is None: session_key = self.session_key @@ -976,6 +980,9 @@ def _send_session_setup(self, sec_buf): smb_req.flags = smb2.SMB2_FLAGS_SIGNED session_req.flags = smb2.SMB2_SESSION_FLAG_BINDING + for (attr,value) in self._settings.iteritems(): + setattr(session_req, attr, value) + self.requests.append(smb_req) return self.conn.submit(smb_req.parent)[0] @@ -1095,20 +1102,7 @@ def request(self, parent=None): return req def let(self, **kwargs): - return self._Let(self, kwargs) - - class _Let(object): - def __init__(self, conn, settings): - self.conn = conn - self.settings = settings - - def __enter__(self): - self.backup = dict(self.conn._settings) - self.conn._settings.update(self.settings) - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - self.conn._settings = self.backup + return core.Let(self, kwargs) # # SMB2 context upcalls From 3ffa531b850beeb5e0f61ced20d21e17a3319dc0 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 27 Oct 2016 22:21:14 +0000 Subject: [PATCH 13/16] add mutable fields to SessionSetupRequest channel security_buffer_offset security_buffer_length for rodolfo --- pike/smb2.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pike/smb2.py b/pike/smb2.py index bb297d26..5f0400cb 100644 --- a/pike/smb2.py +++ b/pike/smb2.py @@ -714,21 +714,27 @@ def __init__(self, parent): self.flags = 0 self.security_mode = 0 self.capabilities = 0 + self.channel = 0 # must not be used self.previous_session_id = 0 self.security_buffer = None + self.security_buffer_offset = None + self.security_buffer_length = None def _encode(self, cur): cur.encode_uint8le(self.flags) cur.encode_uint8le(self.security_mode) cur.encode_uint32le(self.capabilities) - # Channel, must not be used - cur.encode_uint32le(0) + cur.encode_uint32le(self.channel) # Encode 0 for security buffer offset for now sec_buf_ofs = cur.hole.encode_uint16le(0) - cur.encode_uint16le(len(self.security_buffer)) + if self.security_buffer_length is None: + self.security_buffer_length = len(self.security_buffer) + cur.encode_uint16le(self.security_buffer_length) cur.encode_uint64le(self.previous_session_id) # Go back and set security buffer offset - sec_buf_ofs(cur - self.parent.start) + if self.security_buffer_offset is None: + self.security_buffer_offset = cur - self.parent.start + sec_buf_ofs(self.security_buffer_offset) cur.encode_bytes(self.security_buffer) class SessionSetupResponse(Response): From e0bba695502a02522c33d8c322acef2b9f9966b8 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 17 Nov 2016 21:36:22 +0000 Subject: [PATCH 14/16] rename TreeConnectRequest reserved field to flags --- pike/smb2.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pike/smb2.py b/pike/smb2.py index 5f0400cb..32c4474c 100644 --- a/pike/smb2.py +++ b/pike/smb2.py @@ -762,14 +762,14 @@ class TreeConnectRequest(Request): def __init__(self, parent): Request.__init__(self, parent) - self.reserved = 0 + self.flags = 0 self.path_offset = None self.path_length = None self.path = None def _encode(self, cur): - # Reserved - cur.encode_uint16le(self.reserved) + # Reserved/Flags (SMB 3.1.1) + cur.encode_uint16le(self.flags) # Path Offset path_offset_hole = cur.hole.encode_uint16le(0) # Path Length From 4737a36f86ac8aac23d9ebc8b3926b6728066b8f Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Wed, 23 Nov 2016 00:55:25 +0000 Subject: [PATCH 15/16] add handling for server returning unknown ciphers --- pike/crypto.py | 37 +++++++++++++++++++++++++++---------- pike/model.py | 13 ++++++++----- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/pike/crypto.py b/pike/crypto.py index 57612a1c..64cf5c1e 100644 --- a/pike/crypto.py +++ b/pike/crypto.py @@ -42,14 +42,27 @@ from Cryptodome.Cipher import AES + +class CipherMismatch(Exception): + pass + + class Ciphers(core.ValueEnum): + SMB2_NONE_CIPHER = 0x0000 SMB2_AES_128_CCM = 0x0001 SMB2_AES_128_GCM = 0x0002 Ciphers.import_items(globals()) +cipher_map = { + SMB2_AES_128_CCM: (AES.MODE_CCM, 11), + SMB2_AES_128_GCM: (AES.MODE_GCM, 12) +} + + class EncryptionCapabilities(core.Frame): context_type = smb2.SMB2_ENCRYPTION_CAPABILITIES + def __init__(self): self.ciphers = [] self.ciphers_count = None @@ -235,13 +248,15 @@ class EncryptionContext(object): """ def __init__(self, keys, ciphers): self.keys = keys - self.cipher = ciphers[0] - if self.cipher == SMB2_AES_128_CCM: - self.aes_mode = AES.MODE_CCM - self.nonce_length = 11 - elif self.cipher == SMB2_AES_128_GCM: - self.aes_mode = AES.MODE_GCM - self.nonce_length = 12 + for c in ciphers: + if c in cipher_map: + self.aes_mode, self.nonce_length = cipher_map[c] + self.cipher = c + break + else: + raise CipherMismatch( + "Client did not recognize any ciphers returned by the " + "server: {0}".format(ciphers)) def encrypt(self, plaintext, authenticated_data, nonce): enc_cipher = AES.new(self.keys.encryption, @@ -256,6 +271,8 @@ def decrypt(self, ciphertext, signature, authenticated_data, nonce): self.aes_mode, nonce=nonce[:self.nonce_length].tostring()) dec_cipher.update(authenticated_data.tostring()) - return array.array('B', - dec_cipher.decrypt_and_verify(ciphertext.tostring(), - signature.tostring())) + return array.array( + 'B', + dec_cipher.decrypt_and_verify( + ciphertext.tostring(), + signature.tostring())) diff --git a/pike/model.py b/pike/model.py index a6eea86a..03b7cb36 100644 --- a/pike/model.py +++ b/pike/model.py @@ -957,11 +957,14 @@ def derive_encryption_keys(self, session_key=None, context=None): context = self.conn._pre_auth_integrity_hash for nctx in self.conn.negotiate_response: if isinstance(nctx, crypto.EncryptionCapabilitiesResponse): - return crypto.EncryptionContext( - crypto.CryptoKeys311( - self.session_key, - context), - nctx.ciphers) + try: + return crypto.EncryptionContext( + crypto.CryptoKeys311( + self.session_key, + context), + nctx.ciphers) + except crypto.CipherMismatch: + pass elif self.dialect_revision >= smb2.DIALECT_SMB3_0: if self.conn.negotiate_response.capabilities & smb2.SMB2_GLOBAL_CAP_ENCRYPTION: return crypto.EncryptionContext( From 182554feb05074edda1df3bac6ea1629915817a1 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 1 Dec 2016 00:34:41 +0000 Subject: [PATCH 16/16] make the following TransformHeader fields editable for pre serialize nonce wire_nonce signature original_message_length --- pike/crypto.py | 61 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 17 deletions(-) diff --git a/pike/crypto.py b/pike/crypto.py index 64cf5c1e..3fb3fe42 100644 --- a/pike/crypto.py +++ b/pike/crypto.py @@ -43,6 +43,14 @@ from Cryptodome.Cipher import AES +def pad_right(value, length, byte='\0'): + if len(value) > length: + value = value[:length] + elif len(value) < 16: + value += array.array('B', byte*(length - len(value))) + return value + + class CipherMismatch(Exception): pass @@ -110,12 +118,17 @@ def __init__(self, parent): core.Frame.__init__(self, parent) self.protocol_id = array.array('B', "\xfdSMB") self.signature = None + # the value of nonce is always used in the encryption routine self.nonce = array.array('B', map(random.randint, [0]*16, [255]*16)) - self.original_message_size = 0 + # if wire_nonce is set, it will be sent on the wire instead of nonce + self.wire_nonce = None + self.original_message_size = None + self.reserved = None self.flags = 0x1 self.session_id = None self.encryption_context = None + self.additional_authenticated_data_buf = array.array('B') if parent is not None: parent.transform = self else: @@ -146,21 +159,24 @@ def _encode_header(self, cur): # the signature will be written in _encode_smb2 self.signature_offset = cur.offset cur.advanceto(cur + 16) + # the crypto header will be written in _encode_smb2 + self.crypto_header_offset = cur.offset + # save a space for the wire nonce + self.wire_nonce_hole = cur.hole.encode_bytes('\0'*16) + cur.advanceto(cur + 16) # the following fields are part of AdditionalAuthenticatedData and are # used as inputs to the AES cipher - self.crypto_header_start = cur.offset + aad_cur = core.Cursor(self.additional_authenticated_data_buf, 0) # nonce field size is 16 bytes right padded with zeros - if len(self.nonce) > 16: - self.nonce = self.nonce[:16] - elif len(self.nonce) < 16: - self.nonce = self.nonce + array.array('B', "\0"*(16-len(self.nonce))) - cur.encode_bytes(self.nonce) - self.original_message_size_hole = cur.hole.encode_uint32le(0) - cur.encode_uint16le(0) # reserved - cur.encode_uint16le(self.flags) - cur.encode_uint64le(self.session_id) - self.crypto_header_end = cur.offset + self.nonce = pad_right(self.nonce, 16) + aad_cur.encode_bytes(self.nonce) + self.original_message_size_hole = aad_cur.hole.encode_uint32le(0) + if self.reserved is None: + self.reserved = 0 + aad_cur.encode_uint16le(self.reserved) # reserved + aad_cur.encode_uint16le(self.flags) + aad_cur.encode_uint64le(self.session_id) def _encode_smb2(self, cur): # serialize all chained Smb2 commands into one buffer @@ -168,20 +184,31 @@ def _encode_smb2(self, cur): original_message_cur = core.Cursor(original_message_buf, 0) for smb_frame in self.parent: smb_frame.encode(original_message_cur) - self.original_message_size = len(original_message_buf) + if self.original_message_size is None: + self.original_message_size = len(original_message_buf) self.original_message_size_hole(self.original_message_size) - crypto_header = cur.array[self.crypto_header_start:self.crypto_header_end] (self.ciphertext, - self.signature) = self.encryption_context.encrypt( + crypto_hmac) = self.encryption_context.encrypt( original_message_buf, - crypto_header, + self.additional_authenticated_data_buf, self.nonce) cur.encode_bytes(self.ciphertext) # fill in the signature hole sig_cur = core.Cursor(cur.array, self.signature_offset) + if self.signature is None: + self.signature = crypto_hmac sig_cur.encode_bytes(self.signature) + # fill in the header + aad_cur = core.Cursor(cur.array, self.crypto_header_offset) + aad_cur.encode_bytes(self.additional_authenticated_data_buf) + + # fill in the wire nonce + if self.wire_nonce is None: + self.wire_nonce = self.nonce + self.wire_nonce_hole(pad_right(self.wire_nonce, 16)) + def _decode(self, cur): self._decode_header(cur) self._decode_smb2(cur) @@ -194,7 +221,7 @@ def _decode_header(self, cur): self.crypto_header_start = cur.offset self.nonce = cur.decode_bytes(16) self.original_message_size = cur.decode_uint32le() - cur.decode_uint16le() # reserved + self.reserved = cur.decode_uint16le() # reserved self.flags = cur.decode_uint16le() self.session_id = cur.decode_uint64le() self.crypto_header_end = cur.offset