import sys import windows import windows.generated_def as gdef import struct try: unichr # Py2/Py3 compat except NameError: unichr = chr # http://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagcjh_19_03_07 ## Array # A conformant array is an array in which the maximum number of elements is not known beforehand and therefore is included in the representation of the array. # A varying array is an array in which the actual number of elements passed in a given call varies and therefore is included in the representation of the array. ## Pointers # NDR defines two classes of pointers that differ both in semantics and in representation # - reference pointers, which cannot be null and cannot be aliases # - full pointers, which can be null and can be an aliases # - unique pointers, which can be null and cannot be aliases, and are transmitted as full pointers. def pack_dword(x): return struct.pack(" members got <{1}>".format(len(cls.MEMBERS), len(data))) conformant_size = [] res = [] res_size = 0 pointed = [] outstream = NdrWriteStream() pointed_to_pack = [] # pointedoutstream = NdrWriteStream() for i, (member, memberdata) in enumerate(zip(cls.MEMBERS, data)): if hasattr(member, "pack_in_struct"): x, y = member.pack_in_struct(memberdata, i) assert len(x) == 4, "Pointer should be size 4" # Write the pointer outstream.align(4) outstream.write(x) if y is not None: # Store the info to the pointed to pack pointed_to_pack.append((member.subcls.get_alignment(), y)) # pointedoutstream.write(y) elif hasattr(member, "pack_conformant"): size, data = member.pack_conformant(memberdata) outstream.align(member.get_alignment()) outstream.write(data) conformant_size.append(size) # res.append(data) # res_size += len(data) else: packed_member = member.pack(memberdata) outstream.align(member.get_alignment()) outstream.write(packed_member) # Pack the pointed to the stream for alignement, pointed_data in pointed_to_pack: outstream.align(alignement) outstream.write(pointed_data) return dword_pad(b"".join(conformant_size)) + outstream.get_data() @classmethod def unpack(cls, stream): """Unpack the structure from the stream""" conformant_members = [hasattr(m, "pack_conformant") for m in cls.MEMBERS] is_conformant = any(conformant_members) assert(conformant_members.count(True) <= 1), "Unpack conformant struct with more that one conformant MEMBER not implem" data = [] if is_conformant: conformant_size = NdrLong.unpack(stream) post_subcls = [] for i, member in enumerate(cls.MEMBERS): if conformant_members[i]: data.append(member.unpack_conformant(stream, conformant_size)) else: if hasattr(member, "unpack_in_struct"): # print("[{0}] Dereferenced unpacking".format(i)) ptr, subcls = member.unpack_in_struct(stream) if not ptr: data.append(None) else: data.append(ptr) post_subcls.append((i, subcls)) # print(post_subcls) else: data.append(member.unpack(stream)) # print("Applying deref unpack") for i, entry in post_subcls: new_data = entry.unpack(stream) if getattr(entry, "post_unpack", None): new_data = entry.post_unpack(new_data) data[i] = new_data return cls.post_unpack(data) @classmethod def post_unpack(cls, data): return data @classmethod def get_alignment(self): return max([x.get_alignment() for x in self.MEMBERS]) class NdrParameters(object): """a class to pack NDR parameters together to performs RPC call, this class should be subclassed with an attribute ``MEMBERS`` describing the members of the class """ @classmethod def pack(cls, data): if not (len(data) == len(cls.MEMBERS)): print("Size mistach:") print(" * data size = {0}".format(len(data))) print(" * members size = {0}".format(len(cls.MEMBERS))) print(" * data {0}".format(data)) print(" * members = {0}".format(cls.MEMBERS)) raise ValueError("NdrParameters packing number elements mismatch: structure has <{0}> members got <{1}>".format(len(cls.MEMBERS), len(data))) outstream = NdrWriteStream() for (member, memberdata) in zip(cls.MEMBERS, data): alignment = member.get_alignment() outstream.align(alignment) packed_member = member.pack(memberdata) outstream.write(packed_member) return outstream.get_data() @classmethod def unpack(cls, stream): res = [] for member in cls.MEMBERS: unpacked_member = member.unpack(stream) res.append(unpacked_member) return res def get_alignment(self): raise ValueError("NdrParameters should always be top type in NDR description") class NdrConformantArray(object): MEMBER_TYPE = None @classmethod def pack(cls, data): ndrsize = NdrLong.pack(len(data)) return dword_pad(ndrsize + b"".join([cls.MEMBER_TYPE.pack(memberdata) for memberdata in data])) @classmethod def pack_conformant(cls, data): ndrsize = NdrLong.pack(len(data)) ndrdata = dword_pad(b"".join([cls.MEMBER_TYPE.pack(memberdata) for memberdata in data])) return ndrsize, ndrdata @classmethod def unpack(cls, stream): nbelt = NdrLong.unpack(stream) result = cls.unpack_conformant(stream, nbelt) return cls._post_unpack(result) @classmethod def _post_unpack(cls, result): return result @classmethod def unpack_conformant(cls, stream, size): res = [cls.MEMBER_TYPE.unpack(stream) for i in range(size)] stream.align(4) return res @classmethod def get_alignment(self): # TODO: test on array of Hyper return max(4, self.MEMBER_TYPE.get_alignment()) class NdrConformantVaryingArrays(object): MEMBER_TYPE = None @classmethod def pack(cls, data): ndrsize = NdrLong.pack(len(data)) offset = NdrLong.pack(0) return dword_pad(ndrsize + offset + ndrsize + b"".join([cls.MEMBER_TYPE.pack(memberdata) for memberdata in data])) @classmethod def unpack(cls, stream): maxcount = NdrLong.unpack(stream) offset = NdrLong.unpack(stream) count = NdrLong.unpack(stream) assert(offset == 0) # assert(maxcount == count) result = [] post_subcls = [] for i in range(count): member = cls.MEMBER_TYPE if hasattr(member, "unpack_in_struct"): ptr, subcls = member.unpack_in_struct(stream) if not ptr: result.append(None) else: result.append(ptr) post_subcls.append((i, subcls)) else: data = member.unpack(stream) result.append(data) # Unpack pointers for i, entry in post_subcls: data = entry.unpack(stream) result[i] = data return cls._post_unpack(result) @classmethod def _post_unpack(cls, result): return result @classmethod def get_alignment(cls): # TODO: test on array of Hyper return max(4, cls.MEMBER_TYPE.get_alignment()) class NdrWcharConformantVaryingArrays(NdrConformantVaryingArrays): MEMBER_TYPE = NdrShort @classmethod def _post_unpack(self, result): return u"".join(unichr(c) for c in result) class NdrCharConformantVaryingArrays(NdrConformantVaryingArrays): MEMBER_TYPE = NdrByte class NdrHyperConformantVaryingArrays(NdrConformantVaryingArrays): MEMBER_TYPE = NdrHyper class NdrHyperConformantArray(NdrConformantArray): MEMBER_TYPE = NdrHyper class NdrLongConformantArray(NdrConformantArray): MEMBER_TYPE = NdrLong class NdrShortConformantArray(NdrConformantArray): MEMBER_TYPE = NdrShort class NdrByteConformantArray(NdrConformantArray): MEMBER_TYPE = NdrByte @classmethod def _post_unpack(self, result): return bytearray(result) class NdrWcharConformantArray(NdrConformantArray): MEMBER_TYPE = NdrShort @classmethod def _post_unpack(self, result): return bytearray(result) class NdrGuidConformantArray(NdrConformantArray): MEMBER_TYPE = NdrGuid class NdrStream(object): """A stream of bytes used for NDR unpacking""" def __init__(self, data, use_memoryview=False): """""" self.fulldata = data self.use_memoryview = use_memoryview if use_memoryview: self.data = memoryview(data) # FAST but need some code rewrite at some places else: self.data = data def partial_unpack(self, format): size = struct.calcsize(format) toparse = self.data[:size] self.data = self.data[size:] return struct.unpack(format, toparse) def read_aligned_dword(self, size): aligned_size = size if size % 4: aligned_size = size + (4 - (size % 4)) retdata = self.data[:size] self.data = self.data[aligned_size:] return retdata def read(self, size): data = self.data[:size] self.data = self.data[size:] if len(data) < size: raise ValueError("Could not read {0} from stream".format(size)) return data def align(self, size): """Discard some bytes to align the remaining stream on ``size``""" already_read = len(self.fulldata) - len(self.data) if already_read % size: # Realign size_to_align = (size - (already_read % size)) self.data = self.data[size_to_align:] # print("align {0}: {1}".format(size, size_to_align)) return size_to_align # print("align {0}: 0".format(size)) return 0 class NdrWriteStream(object): def __init__(self): self.data_parts = [] self.data_size = 0 def get_data(self): data = b"".join(bytes(x) for x in self.data_parts) assert len(data) == self.data_size return data def write(self, data): self.data_parts.append(data) self.data_size += len(data) return None def align(self, alignement): if self.data_size % alignement == 0: return topadsize = (alignement) - (self.data_size % alignement) self.write(b"P" * topadsize) return def make_parameters(types, name=None): class NdrCustomParameters(NdrParameters): MEMBERS = types return NdrCustomParameters def make_structure(types, name=None): class NdrCustomStructure(NdrStructure): MEMBERS = types return NdrCustomStructure