Improved NDR padding capabilities for primitive types

This commit is contained in:
hakril
2020-06-15 10:00:05 +02:00
parent 0fa8026c1b
commit d8ac02e6a2
2 changed files with 229 additions and 7 deletions
+127
View File
@@ -0,0 +1,127 @@
import pytest
from windows.rpc import ndr
from .pfwtest import *
from tests.test_rpc import UACParameters
# Memo: Padding byte is 'P' in ndr.py
# 20 Bytes structures alignes on 4 butees
DoubleDwordStructure = ndr.make_structure([ndr.NdrLong] * 5)
# Struct with some specificities
# Start Must be aligned on 4 (even if it begin with a short)
# full size == 13 -> usefull to test afterward padding as well
# Pack format ah follow:
# SSBPLLLLBPSSB
InternalAlignementStructure = ndr.make_structure([ndr.NdrShort, ndr.NdrByte, ndr.NdrLong, ndr.NdrByte, ndr.NdrShort, ndr.NdrByte])
# IDL code:
# typedef struct InternalAlignementStructure
# {
#short sfield0;
#byte bfield1;
#long lfield2;
#byte bfield3;
#short sfield4;
#byte bfield5;
# }InternalAlignementStructure;
# NdrObject, Values, result
NDR_PACK_TEST_CASE = [
# Simple case
(ndr.make_structure([ndr.NdrLong, ndr.NdrLong]), (2, 2), b"\x02\x00\x00\x00\x02\x00\x00\x00"),
# Check alignement on small native types (dword aligned)
(ndr.make_structure([ndr.NdrShort, ndr.NdrByte]), (0x0101, 2), b"\x01\x01\x02"),
# Check alignement on small native types (dword aligned)
(ndr.make_structure([ndr.NdrShort, ndr.NdrShort]), (0x0101, 0x0202), b"\x01\x01\x02\x02"),
# Same check on parameters
(ndr.make_parameters([ndr.NdrShort, ndr.NdrByte]), (0x0101, 2), b"\x01\x01\x02"),
# Test some Hyper
(ndr.make_parameters([ndr.NdrByte, ndr.NdrHyper, ndr.NdrByte, ndr.NdrLong, ndr.NdrHyper]),
(0x01, 0x4141414141414141, 0x42, 0x43434343, 0x4444444444444444 ),
b"\x01PPPPPPPAAAAAAAABPPPCCCCDDDDDDDD"),
# Complexe structure (with 4B alignement of 20B structure)
(ndr.make_parameters([ndr.NdrShort, DoubleDwordStructure]),
(0x0101, [0x41414141, 0x42424242, 0x43434343, 0x44444444, 0x45454545]),
b"\x01\x01PPAAAABBBBCCCCDDDDEEEE"),
# Same check on parameters
(ndr.make_parameters([ndr.NdrShort, DoubleDwordStructure]),
(0x0101, [0x41414141, 0x42424242, 0x43434343, 0x44444444, 0x45454545]),
b"\x01\x01PPAAAABBBBCCCCDDDDEEEE"),
# Check on InternalAlignementStructure before any nested test
(InternalAlignementStructure,
[0x4141, 0x42, 0x43434343, 0x44, 0x4545, 0x46],
b"AABPCCCCDPEEF"),
# Nested alignement
# Alignement with a sub-structure that also have internal alignement
(ndr.make_parameters([ndr.NdrShort, InternalAlignementStructure, ndr.NdrByte]),
(0x0101, [0x4141, 0x42, 0x43434343, 0x44, 0x4545, 0x46], 0x47),
# Verified with an actual RPC server
b"\x01\x01PPAABPCCCCDPEEFG"),
# Nested alignement
# Alignement with a sub-structure that also have internal alignement
# Afterward short should be aligned on 2
(ndr.make_parameters([ndr.NdrShort, InternalAlignementStructure, ndr.NdrShort]),
(0x0101, [0x4141, 0x42, 0x43434343, 0x44, 0x4545, 0x46], 0x4747),
# Verified with an actual RPC server
b"\x01\x01PPAABPCCCCDPEEFPGG"),
]
@pytest.mark.parametrize("ndrobj, values, result", NDR_PACK_TEST_CASE)
def test_ndr_packing(ndrobj, values, result):
assert ndrobj.pack(values) == result
# Check the result of serializing a fixed 'EptMapAuthParameters' call known to works
# It allow to test for packing of real-complexe Parameter without relying on the whole ALPC/RPC stack
def test_ndr_packing_complex_epmapper_call():
# Param from a real UAC endpoint resolution
targetiid = gdef.GUID.from_string("201EF99A-7FA0-444C-9399-19BA84F12A1A")
towerarray = bytearray(b'\x04\x00\x13\x00\r\x9a\xf9\x1e \xa0\x7fLD\x93\x99\x19\xba\x84\xf1*\x1a\x01\x00\x02\x00\x00\x00\x13\x00\r\x04]\x88\x8a\xeb\x1c\xc9\x11\x9f\xe8\x08\x00+\x10H`\x02\x00\x02\x00\x00\x00\x01\x00\x0c\x02\x00\x00\x00\x01\x00\x10\x00\x00')
local_system_psid = gdef.PSID.from_string("S-1-5-18")
context = (0, 0, 0, 0, 0)
nb_response = 1
packed = windows.rpc.epmapper.EptMapAuthParameters.pack([bytearray(targetiid),
(len(towerarray), towerarray),
local_system_psid,
context,
nb_response])
expected_result = b'\x9a\xf9\x1e \xa0\x7fLD\x93\x99\x19\xba\x84\xf1*\x1a@\x00\x00\x00@\x00\x00\x00\x04\x00\x13\x00\r\x9a\xf9\x1e \xa0\x7fLD\x93\x99\x19\xba\x84\xf1*\x1a\x01\x00\x02\x00\x00\x00\x13\x00\r\x04]\x88\x8a\xeb\x1c\xc9\x11\x9f\xe8\x08\x00+\x10H`\x02\x00\x02\x00\x00\x00\x01\x00\x0c\x02\x00\x00\x00\x01\x00\x10\x00\x00\x02\x02\x02\x02\x01\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x05\x12\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00'
assert packed == packed
# Check the result of serializing a fixed 'UAC' call known to works
# It allow to test for packing of real-complexe Parameter without relying on the whole ALPC/RPC stack
def test_ndr_packing_complex_uac_call():
parameters = UACParameters.pack([
r"c:\windows\system32\notepad.exe", # Application Path
"NOT_ALIGNED_STRINGXXX", # Commandline
17, # UAC-Request Flag
gdef.CREATE_UNICODE_ENVIRONMENT, # dwCreationFlags
"", # StartDirectory
"WinSta0\\Default\x00", # Station
# Startup Info
(None, # Title
0, # dwX
0, # dwY
0, # dwXSize
0, # dwYSize
0, # dwXCountChars
0, # dwYCountChars
0, # dwFillAttribute
0, # dwFlags
5, # wShowWindow
# Point structure: Use MonitorFromPoint to setup StartupInfo.hStdOutput
(0, 0)),
0, # Window-Handle to know if UAC can steal focus
0xffffffff]) # UAC Timeout
expected_result = b'\x02\x02\x02\x02 \x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00c\x00:\x00\\\x00w\x00i\x00n\x00d\x00o\x00w\x00s\x00\\\x00s\x00y\x00s\x00t\x00e\x00m\x003\x002\x00\\\x00n\x00o\x00t\x00e\x00p\x00a\x00d\x00.\x00e\x00x\x00e\x00\x00\x00\x02\x02\x02\x02\x16\x00\x00\x00\x00\x00\x00\x00\x16\x00\x00\x00N\x00O\x00T\x00_\x00A\x00L\x00I\x00G\x00N\x00E\x00D\x00_\x00S\x00T\x00R\x00I\x00N\x00G\x00X\x00X\x00X\x00\x00\x00\x11\x00\x00\x00\x00\x04\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00PP\x10\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00W\x00i\x00n\x00S\x00t\x00a\x000\x00\\\x00D\x00e\x00f\x00a\x00u\x00l\x00t\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff'
assert parameters == expected_result
+102 -7
View File
@@ -64,6 +64,11 @@ class NdrUniquePTR(object):
return None
return self.subcls.parse(stream)
def get_alignment(self):
# 14.3.2 Alignment of Constructed Types
# Pointer alignment is always modulo 4.
return 4
class NdrUnpackNone(object):
@classmethod
def unpack(cls, stream):
@@ -94,6 +99,9 @@ class NdrFixedArray(object):
def unpack(self, stream):
return [self.subcls.unpack(stream) for i in range(self.size)]
def get_alignment(self):
return self.subcls.get_alignment()
class NdrSID(object):
@classmethod
@@ -113,6 +121,11 @@ class NdrSID(object):
subcount = NdrLong.unpack(stream)
return stream.read(8 + (subcount * 4))
@classmethod
def get_alignment(self):
# Not sur, but it seems to contain an array of long
return 4
class NdrVaryingCString(object):
@classmethod
def pack(cls, data):
@@ -126,6 +139,11 @@ class NdrVaryingCString(object):
result += data
return dword_pad(result)
@classmethod
def get_alignment(self):
# Not sur, but size is on 4 bytes so...
return 4
class NdrWString(object):
@classmethod
def pack(cls, data):
@@ -149,6 +167,11 @@ class NdrWString(object):
s = stream.read(size1 * 2)
return s.decode("utf-16-le")
@classmethod
def get_alignment(self):
# Not sur, but size is on 4 bytes so...
return 4
class NdrCString(object):
@classmethod
def pack(cls, data):
@@ -162,6 +185,11 @@ class NdrCString(object):
result += data
return dword_pad(result)
@classmethod
def get_alignment(self):
# Not sur, but size is on 4 bytes so...
return 4
# @classmethod
# def unpack(self, stream):
# maxcount, offset, count = stream.partial_unpack("<3I")
@@ -180,6 +208,10 @@ class NdrLong(object):
stream.align(4)
return stream.partial_unpack("<I")[0]
@classmethod
def get_alignment(self):
return 4
class NdrHyper(object):
@classmethod
def pack(cls, data):
@@ -190,6 +222,10 @@ class NdrHyper(object):
stream.align(8)
return stream.partial_unpack("<Q")[0]
@classmethod
def get_alignment(self):
return 8
class NdrShort(object):
@classmethod
def pack(cls, data):
@@ -199,6 +235,10 @@ class NdrShort(object):
def unpack(self, stream):
return stream.partial_unpack("<H")[0]
@classmethod
def get_alignment(self):
return 2
class NdrByte(object):
@classmethod
@@ -209,6 +249,10 @@ class NdrByte(object):
def unpack(self, stream):
return stream.partial_unpack("<B")[0]
@classmethod
def get_alignment(self):
return 1
class NdrGuid(object):
@classmethod
@@ -251,21 +295,30 @@ class NdrStructure(object):
raise ValueError("NdrStructure packing number elements mismatch: structure has <{0}> members got <{1}>".format(len(cls.MEMBERS), len(data)))
conformant_size = []
res = []
res_size = 0
pointed = []
outstream = 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)
res.append(x)
outstream.align(member.get_alignment())
outstream.write(x)
# res.append(x)
# res_size += len(x)
if y is not None:
pointed.append(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.append(data)
# res_size += len(data)
else:
packed_member = member.pack(memberdata)
res.append(packed_member)
return dword_pad(b"".join(conformant_size)) + dword_pad(b"".join(res)) + dword_pad(b"".join(pointed))
outstream.align(member.get_alignment())
outstream.write(packed_member)
return dword_pad(b"".join(conformant_size)) + outstream.get_data() + dword_pad(b"".join(pointed))
@classmethod
def unpack(cls, stream):
@@ -305,6 +358,10 @@ class NdrStructure(object):
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):
@@ -320,11 +377,17 @@ class NdrParameters(object):
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)))
res = []
outstream = NdrWriteStream()
for (member, memberdata) in zip(cls.MEMBERS, data):
alignment = member.get_alignment()
outstream.align(alignment)
packed_member = member.pack(memberdata)
res.append(packed_member)
return b"".join(dword_pad(elt) for elt in res)
outstream.write(packed_member)
return outstream.get_data()
@classmethod
def unpack(cls, stream):
@@ -334,6 +397,9 @@ class NdrParameters(object):
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
@@ -364,6 +430,11 @@ class NdrConformantArray(object):
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
@@ -406,6 +477,9 @@ class NdrConformantVaryingArrays(object):
def _post_unpack(cls, result):
return result
def get_alignment(self):
# TODO: test on array of Hyper
return max(4, self.MEMBER_TYPE.get_alignment())
class NdrWcharConformantVaryingArrays(NdrConformantVaryingArrays):
MEMBER_TYPE = NdrShort
@@ -487,6 +561,27 @@ class NdrStream(object):
# 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(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):