def _from_bytes(cls, buffer: memoryview, strict: bool = True) -> ChallengeMessage: # TODO: Check reserved. target_name_offset: int = struct_unpack_from('<I', buffer=buffer, offset=16)[0] target_info_offset: int = struct_unpack_from('<I', buffer=buffer, offset=44)[0] payload_offset_start: int = min(target_name_offset, target_info_offset) target_info_bytes: bytes = get_message_bytes_data( buffer, *struct_unpack_from('<HH', buffer=buffer, offset=40), target_info_offset) return cls( target_name=get_message_bytes_data_str( buffer, *struct_unpack_from('<HH', buffer=buffer, offset=12), target_name_offset), negotiate_flags=NegotiateFlags.from_int( value=struct_unpack_from('<I', buffer=buffer, offset=20)[0]), challenge=bytes(buffer[24:32]), target_info=AVPairSequence.from_bytes(buffer=target_info_bytes, break_on_eol=True) if target_info_bytes else None, os_version=Version.from_bytes(buffer=buffer, base_offset=48) if payload_offset_start != 48 else None)
def from_bytes(cls, buffer: ByteString, base_offset: int = 0, strict: bool = True): if cls == NTLMSSPMessageSignature: raise NotImplementedError if strict and (version := struct_unpack_from( '<I', buffer=buffer, offset=base_offset)) != cls.VERSION: # TODO: Use proper exception (the parse one?) raise ValueError
def from_bytes(cls, buffer: ByteString, base_offset: int = 0, strict: bool = True) -> Message: import ntlm.messages.negotiate import ntlm.messages.challenge import ntlm.messages.authenticate buffer = memoryview(buffer)[base_offset:] if strict and (signature := struct_unpack_from( '<8s', buffer=buffer, offset=0)[0]) != cls.SIGNATURE: raise MalformedSignatureError(observed_signature=signature)
def from_bytes(cls, buffer: ByteString, base_offset: int = 0, strict: bool = True, break_on_eol: bool = False) -> AVPairSequence: buffer = memoryview(buffer)[base_offset:] offset = 0 eol_observed = False av_pairs_list: List[AVPair] = [] while offset + 4 <= len(buffer): av_pair = AVPair.from_bytes(buffer=buffer[offset:]) av_pairs_list.append(av_pair) if isinstance(av_pair, EOLAVPair): if strict and eol_observed: raise MultipleEOLError eol_observed = True if break_on_eol: break av_len: int = struct_unpack_from('<H', buffer=buffer, offset=offset + 2)[0] offset += 4 + av_len if strict and not eol_observed: raise EOLNotObservedError return cls(av_pairs_list)
def from_bytes(cls, buffer: ByteString, base_offset: int = 0) -> AVPair: from ntlm.structures.av_pairs.single_host_data import SingleHostDataAVPair from ntlm.structures.av_pairs.dns_tree_name import DnsTreeNameAVPair from ntlm.structures.av_pairs.channel_bindings import ChannelBindingsAVPair from ntlm.structures.av_pairs.target_name import TargetNameAVPair from ntlm.structures.av_pairs.dns_computer_name import DnsComputerNameAVPair from ntlm.structures.av_pairs.flags import FlagsAVPair from ntlm.structures.av_pairs.domain_name import DomainNameAVPair from ntlm.structures.av_pairs.timestamp import TimestampAVPair from ntlm.structures.av_pairs.computer_name import ComputerNameAVPair from ntlm.structures.av_pairs.eol import EOLAVPair from ntlm.structures.av_pairs.dns_domain_name import DnsDomainNameAVPair buffer = memoryview(buffer)[base_offset:] av_id, av_len = struct_unpack_from('<HH', buffer=buffer, offset=0) value_bytes: bytes = get_message_bytes_data(buffer=buffer, length=av_len, alloc=0, offset=4) if cls != AVPair: if av_id != cls.AV_ID: # TODO: Use proper exception. raise ValueError return cls.from_value_bytes(value_bytes=value_bytes) else: return cls.AV_ID_TO_AV_PAIR_CLASS[av_id].from_value_bytes( value_bytes=value_bytes)
def chunk_reader(self): chunk_size = -1 while chunk_size != 0: chunk_header = self._recv(2) chunk_size, = struct_unpack_from(">H", chunk_header) if chunk_size > 0: data = self._recv(chunk_size) yield data
def _from_bytes(cls, buffer: memoryview, strict: bool = True) -> AuthenticateMessage: return cls( lm_challenge_response=get_message_bytes_data( buffer, *struct_unpack_from('<HHI', buffer=buffer, offset=12), ), nt_challenge_response=NTLMv2Response.from_bytes( buffer=get_message_bytes_data( buffer, *struct_unpack_from('<HHI', buffer=buffer, offset=20), )), domain_name=get_message_bytes_data_str( buffer, *struct_unpack_from('<HHI', buffer=buffer, offset=28)), user_name=get_message_bytes_data_str( buffer, *struct_unpack_from('<HHI', buffer=buffer, offset=36)), workstation_name=get_message_bytes_data_str( buffer, *struct_unpack_from('<HHI', buffer=buffer, offset=44)), encrypted_random_session_key=get_message_bytes_data( buffer, *struct_unpack_from('<HHI', buffer=buffer, offset=52)), negotiate_flags=NegotiateFlags.from_int( value=struct_unpack_from('<I', buffer=buffer, offset=60)[0]), os_version=Version.from_bytes(buffer=buffer, base_offset=64), mic=bytes(buffer[72:88]))
def from_bytes(cls, data: bytes, size_per_element: int = 1) -> UnidimensionalConformantArray: try: maximum_count: int = struct_unpack('<I', data[:4])[0] return cls(representation=tuple( bytes( struct_unpack_from(size_per_element * 'B', buffer=data[4:], offset=i * size_per_element)) for i in range(maximum_count))) except struct_error as e: raise ValueError from e
def _from_bytes(cls, buffer: memoryview, strict: bool = True) -> NegotiateMessage: domain_name_offset: int = struct_unpack_from('<I', buffer=buffer, offset=20)[0] workstation_name_offset: int = struct_unpack_from('<I', buffer=buffer, offset=28)[0] payload_offset_start: int = min(domain_name_offset, workstation_name_offset) return cls( negotiate_flags=NegotiateFlags.from_int( value=struct_unpack_from('<I', buffer=buffer, offset=12)[0]), domain_name=get_message_bytes_data_str( buffer, *struct_unpack_from('<HH', buffer[16:20]), domain_name_offset), workstation_name=get_message_bytes_data_str( buffer, *struct_unpack_from('<HH', buffer[24:28]), workstation_name_offset), os_version=Version.from_bytes( buffer=buffer[32:40]) if payload_offset_start != 32 else None)
def from_bytes(cls, buffer: ByteString, base_offset: int = 0, strict: bool = True): buffer = memoryview(buffer)[base_offset:] super().from_bytes(buffer=buffer, base_offset=base_offset, strict=strict) return cls(checksum=buffer[base_offset + 4:base_offset + 12], seq_num=struct_unpack_from('<I', buffer=buffer, offset=base_offset + 12)[0])
def import_bytes(cls, data: bytes, hash_function=None, base_offset: int = 0) -> BloomFilter: """ Import an exported `BloomFilter` byte sequence and make an instance. :param data: A byte sequence from which to extract the data representing a `BloomFilter`. :param hash_function: A hash function to use in case the exported name cannot be looked up in the `hashlib`. module. :param base_offset: The offset from the start of the byte sequence from where to start extracting. :return: A `BloomFilter` instance as represented by the pointed-to byte sequence. """ offset = base_offset capacity = struct_unpack_from('>Q', buffer=data, offset=offset)[0] offset += struct_calcsize('>Q') false_positive_probability = struct_unpack_from('f', buffer=data, offset=offset)[0] offset += struct_calcsize('f') hash_function_name_bytes_len: int = struct_unpack_from(cls._IMPORT_EXPORT_LENGTH_FORMAT, buffer=data, offset=offset)[0] offset += struct_calcsize(cls._IMPORT_EXPORT_LENGTH_FORMAT) hash_function_name: str = data[offset:offset+hash_function_name_bytes_len].decode() offset += hash_function_name_bytes_len bit_array_bytes_len: int = struct_unpack_from(cls._IMPORT_EXPORT_LENGTH_FORMAT, buffer=data, offset=offset)[0] offset += struct_calcsize(cls._IMPORT_EXPORT_LENGTH_FORMAT) bit_array = bitarray() bit_array.frombytes(data[offset:offset + bit_array_bytes_len]) return cls( capacity=capacity, false_positive_probability=false_positive_probability, hash_function=hash_function or getattr(hashlib, hash_function_name), bit_array=bit_array )
def dos_time_to_timedelta(dos_time: Union[IntLike, ByteString], offset: IntLike = 0) -> timedelta: """ Convert a DOS time value to a `timedelta` value. :param dos_time: A DOS time value as an integer or byte byte string. :param offset: An offset in the input value, in case it is a byte string, from where to extract the DOS time integer value. :return: The DOS time as a `timedelta` instance. """ dos_time: int = int(dos_time if is_int_like( value=dos_time ) else struct_unpack_from('<H', buffer=dos_time, offset=int(offset))[0]) return timedelta( # Number of "two-seconds", thus shift left by one (multiply by two). seconds=(dos_time & 0b0000_0000_0001_1111) << 1, minutes=(dos_time & 0b0000_0111_1110_0000) >> 5, hours=(dos_time & 0b1111_1000_0000_0000) >> 11)
def dos_date_to_datetime(dos_date: Union[IntLike, ByteString], offset: IntLike = 0) -> Optional[datetime]: """ Convert a DOS date value to a `datetime` value. :param dos_date: A DOS date value as an integer or byte string. :param offset: An offset in the input value, in case it is byte string, from where to extract the DOS date integer value. :return: The DOS time as a `datetime` instance, or `None` of the DOS date value is blank. """ dos_date: int = int(dos_date if is_int_like( value=dos_date ) else struct_unpack_from('<H', buffer=dos_date, offset=int(offset))[0]) return datetime(year=FAT_TIME_INCEPTION_YEAR + ((dos_date & 0b1111_1110_0000_0000) >> 9), month=((dos_date & 0b0000_0001_1110_0000) >> 5) or 1, day=(dos_date & 0b0000_0000_0001_1111) or 1) if dos_date else None
def filetime_to_datetime(filetime: Union[IntLike, ByteString], offset: IntLike = 0) -> Optional[datetime]: """ Convert a `FILETIME` value to a datetime object. A `FILETIME` value represents a period as a count of 100-nanosecond time slices. When interpreted as a timestamp, it should be considered in relation to the inception of the Windows epoch date: 1601-01-01. NOTE: There is a loss of precision when converting to a `datetime` object (tenth of a microsecond), because `datetime` only has microsecond precision. :param filetime: A `FILETIME` value as an integer or bytes. :param offset: An offset in the input value, in case it is a byte string, from where the extract the `FILETIME` integer value. :return: A datetime object corresponding to the provided timestamp; `None` if it is blank. """ filetime: int = int(filetime if is_int_like( value=filetime ) else struct_unpack_from('<Q', buffer=filetime, offset=int(offset))[0]) return (MS_EPOCH_INCEPTION + timedelta(microseconds=filetime // 10)) if filetime else None
class Message(ABC): SIGNATURE: ClassVar[bytes] = b'NTLMSSP\x00' MESSAGE_TYPE_ID_TO_MESSAGE_CLASS: ClassVar[Dict[int, Type[Message]]] = {} MESSAGE_TYPE_ID: ClassVar[int] = NotImplemented MALFORMED_MESSAGE_ERROR_CLASS: ClassVar[ Type[MalformedMessageError]] = NotImplementedError @classmethod def from_bytes(cls, buffer: ByteString, base_offset: int = 0, strict: bool = True) -> Message: import ntlm.messages.negotiate import ntlm.messages.challenge import ntlm.messages.authenticate buffer = memoryview(buffer)[base_offset:] if strict and (signature := struct_unpack_from( '<8s', buffer=buffer, offset=0)[0]) != cls.SIGNATURE: raise MalformedSignatureError(observed_signature=signature) message_type_id: int = struct_unpack_from('<I', buffer=buffer, offset=8)[0] if cls != Message: if message_type_id != cls.MESSAGE_TYPE_ID: raise UnexpectedMessageTypeError( observed_ntlm_message_type_id=message_type_id, expected_message_type_id=cls.MESSAGE_TYPE_ID) return cls._from_bytes(buffer=buffer, strict=strict) else: return cls.MESSAGE_TYPE_ID_TO_MESSAGE_CLASS[ message_type_id].from_bytes(buffer=buffer, strict=strict)
def from_value_bytes(cls, value_bytes: bytes) -> FlagsAVPair: return cls(flags=AvFlags.from_int( value=struct_unpack_from('<I', buffer=value_bytes)[0]))
def from_value_bytes(cls, value_bytes: bytes) -> TimestampAVPair: return cls( filetime=value_bytes, timestamp=filetime_to_datetime( filetime=struct_unpack_from('<Q', buffer=value_bytes)[0]))
def unpack(buf): """ Convert MariaDB dynamic columns data in a byte string into a dict """ flags, column_count, len_names = struct_unpack_from('<BHH', buf) data_offset_code, coldata_size, data_offset_mask = decode_data_size(flags) if (flags & 0xFC) != 4: raise DynColValueError("Unknown dynamic columns format") if column_count == 0: return {} column_directory_end = (1 + 2 + 2) + coldata_size * column_count names_end = column_directory_end + len_names column_directory = buf[(1 + 2 + 2):column_directory_end] enc_names = buf[column_directory_end:names_end] data = buf[names_end:] names = {} values = {} last_name_offset = None last_data_offset = None last_dtype = None for i in six_moves_range(column_count): if coldata_size % 2 == 0: name_offset, data_offset_dtype = struct_unpack_from( '<H' + data_offset_code, column_directory, offset=i * coldata_size ) else: name_offset, = struct_unpack_from( '<H', column_directory, offset=i * coldata_size ) # can't struct_unpack the 3 bytes so hack around dodt_bytes = column_directory[(i * coldata_size + 2): (i * coldata_size + 5)] + b'\x00' data_offset_dtype, = struct_unpack('<' + data_offset_code, dodt_bytes) data_offset_dtype &= data_offset_mask data_offset = data_offset_dtype >> 4 dtype = data_offset_dtype & 0xF # Store *last* column's name if last_name_offset is not None: names[i - 1] = ( enc_names[last_name_offset:name_offset].decode('utf-8') ) last_name_offset = name_offset # if last_data_offset is not None: values[i - 1] = decode(last_dtype, data[last_data_offset:data_offset]) last_data_offset = data_offset last_dtype = dtype names[column_count - 1] = enc_names[last_name_offset:].decode('utf-8') values[column_count - 1] = decode(last_dtype, data[last_data_offset:]) # join data and names return { names[i]: values[i] for i in six_moves_range(column_count) }
@classmethod def from_bytes(cls, buffer: ByteString, base_offset: int = 0, strict: bool = True) -> Version: buffer = memoryview(buffer)[base_offset:] if strict: if (reserved := bytes(buffer[4:7])) != cls._RESERVED: # TODO: Use proper exception raise ValueError if (ntlm_revision_current := buffer[7]) != cls._NTLMRevisionCurrent: # TODO: Use proper exception raise ValueError return cls(major_version_number=buffer[0], minor_version_number=buffer[1], build_number=struct_unpack_from('<H', buffer=buffer, offset=2)[0]) def __bytes__(self): return b''.join([ struct_pack('<B', self.major_version_number), struct_pack('<B', self.minor_version_number), struct_pack('<H', self.build_number), self._RESERVED, struct_pack('<B', self._NTLMRevisionCurrent) ])
def _from_bytes_and_header(cls, data: memoryview, header: Header) -> Message: cls._check_structure_size( structure_size_to_test=struct_unpack_from('<H', buffer=data, offset=len(header))[0] )