From fccd2a49e6d32d6021e128468b9098d8f6871946 Mon Sep 17 00:00:00 2001 From: Clement Rouault Date: Wed, 30 Aug 2017 18:29:49 +0200 Subject: [PATCH] Add doc to RPC + RPC_IF_ID generated-extended + lsass sample --- .../definitions/winstruct_alpc.txt | 6 ++ .../extended_structs/_RPC_IF_ID.py | 5 ++ ctypes_generation/generate.py | 2 +- docs/source/index.rst | 1 + docs/source/winstructs_generated.rst | 22 +++++ samples/rpc/lsass.py | 84 +++++++++++++++++++ samples/{ => rpc}/uac.py | 0 windows/generated_def/winstructs.py | 15 ++++ windows/rpc/__init__.py | 2 +- windows/rpc/client.py | 33 ++++---- windows/rpc/epmapper.py | 38 ++++++--- windows/rpc/ndr.py | 26 ++++++ 12 files changed, 204 insertions(+), 30 deletions(-) create mode 100644 ctypes_generation/extended_structs/_RPC_IF_ID.py create mode 100644 samples/rpc/lsass.py rename samples/{ => rpc}/uac.py (100%) diff --git a/ctypes_generation/definitions/winstruct_alpc.txt b/ctypes_generation/definitions/winstruct_alpc.txt index 01f601e..b9075d4 100644 --- a/ctypes_generation/definitions/winstruct_alpc.txt +++ b/ctypes_generation/definitions/winstruct_alpc.txt @@ -261,3 +261,9 @@ typedef struct _ALPC_WORK_ON_BEHALF_ATTR { ULONGLONG Ticket; } ALPC_WORK_ON_BEHALF_ATTR, *PALPC_WORK_ON_BEHALF_ATTR; + +typedef struct _RPC_IF_ID { + IID Uuid; + USHORT VersMajor; + USHORT VersMinor; +} RPC_IF_ID; diff --git a/ctypes_generation/extended_structs/_RPC_IF_ID.py b/ctypes_generation/extended_structs/_RPC_IF_ID.py new file mode 100644 index 0000000..cc3e34e --- /dev/null +++ b/ctypes_generation/extended_structs/_RPC_IF_ID.py @@ -0,0 +1,5 @@ +INITIAL_RPC_IF_ID = RPC_IF_ID + +class _RPC_IF_ID(INITIAL_RPC_IF_ID): + def __repr__(self): + return ''.format(self.Uuid.to_string(), self.VersMajor, self.VersMinor) \ No newline at end of file diff --git a/ctypes_generation/generate.py b/ctypes_generation/generate.py index 75f7418..68af9c8 100644 --- a/ctypes_generation/generate.py +++ b/ctypes_generation/generate.py @@ -258,7 +258,7 @@ class InitialDefGenerator(CtypesGenerator): with open(target_file, "w") as f: f.writelines(all_lines) -EXTENDED_STRUCT = ["_GUID"] # TODO: check auto the dir +EXTENDED_STRUCT = ["_GUID", "_RPC_IF_ID"] # TODO: check auto the dir class StructGenerator(CtypesGenerator): PARSER = struct_parser.WinStructParser diff --git a/docs/source/index.rst b/docs/source/index.rst index 050edb9..28c3968 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -22,6 +22,7 @@ Contents: com.rst crypto.rst alpc.rst + rpc.rst generated.rst iat_hook.rst wip.rst diff --git a/docs/source/winstructs_generated.rst b/docs/source/winstructs_generated.rst index 163ddcb..b0b93e0 100644 --- a/docs/source/winstructs_generated.rst +++ b/docs/source/winstructs_generated.rst @@ -10555,6 +10555,28 @@ _ALPC_WORK_ON_BEHALF_ATTR :class:`ULONGLONG` +_RPC_IF_ID +'''''''''' +.. class:: RPC_IF_ID + + Alias for :class:`_RPC_IF_ID` + +.. class:: _RPC_IF_ID + + .. attribute:: Uuid + + :class:`IID` + + + .. attribute:: VersMajor + + :class:`USHORT` + + + .. attribute:: VersMinor + + :class:`USHORT` + WinEnums -------- _SYSTEM_INFORMATION_CLASS diff --git a/samples/rpc/lsass.py b/samples/rpc/lsass.py new file mode 100644 index 0000000..497ddd5 --- /dev/null +++ b/samples/rpc/lsass.py @@ -0,0 +1,84 @@ +import windows.rpc +from windows.rpc import ndr + + +# Ndr stuff +class NdrContext(ndr.NdrStructure): + MEMBERS = [ndr.NdrLong, ndr.NdrLong, ndr.NdrLong, ndr.NdrLong, ndr.NdrLong] + + +class PLSAPR_OBJECT_ATTRIBUTES(ndr.NdrStructure): + MEMBERS = [ndr.NdrLong, + ndr.NdrUniquePTR(ndr.NdrWString), + ndr.NdrUniquePTR(ndr.NdrLong), # We dont care if the subtype as we will pass None + ndr.NdrLong, + ndr.NdrUniquePTR(ndr.NdrLong), # We dont care if the subtype as we will pass None + ndr.NdrUniquePTR(ndr.NdrLong)] # We dont care if the subtype as we will pass None + + +class LsarOpenPolicy2Parameter(ndr.NdrParameters): + MEMBERS = [ndr.NdrUniquePTR(ndr.NdrWString), + PLSAPR_OBJECT_ATTRIBUTES, + ndr.NdrLong] + + +class LsarEnumeratePrivilegesParameter(ndr.NdrParameters): + MEMBERS = [NdrContext, + ndr.NdrLong, + ndr.NdrLong] + + +class LSAPR_POLICY_PRIVILEGE_DEF(object): + @classmethod + def unpack(cls, stream): + size1 = ndr.NdrShort.unpack(stream) + ptr = ndr.NdrShort.unpack(stream) + size2 = ndr.NdrLong.unpack(stream) + luid = ndr.NdrHyper.unpack(stream) + return ptr, luid + + +class LSAPR_PRIVILEGE_ENUM_BUFFER(object): + @classmethod + def unpack(cls, stream): + entries = ndr.NdrLong.unpack(stream) + array_size = ndr.NdrLong.unpack(stream) + array_ptr = ndr.NdrLong.unpack(stream) + # Unpack pointed array + array_size2 = ndr.NdrLong.unpack(stream) + assert array_size == array_size2 + x = [] + # unpack each elements LSAPR_POLICY_PRIVILEGE_DEF + for i in range(array_size2): + ptr, luid = LSAPR_POLICY_PRIVILEGE_DEF.unpack(stream) + if ptr: + x.append(luid) + # unpack pointed strings + result = [] + for luid in x: + name = ndr.NdrWcharConformantVaryingArrays.unpack(stream) + result.append((luid, name)) + return result + + +# Actual code + +## LSASS alpc endpoints is fixed, no need for the epmapper +client = windows.rpc.RPCClient(r"\RPC Control\lsasspirpc") +## Bind to the desired interface +iid = client.bind('12345778-1234-abcd-ef00-0123456789ab', version=(0,0)) + +## Craft parameters and call 'LsarOpenPolicy2' +params = LsarOpenPolicy2Parameter.pack([None, (0, None, None, 0, None, None), 0x20000000]) +res = client.call(iid, 44, params) +## Unpack the resulting handle +handle = NdrContext.unpack(ndr.NdrStream(res)) + +## Craft parameters and call 'LsarEnumeratePrivileges' +x = LsarEnumeratePrivilegesParameter.pack([handle, 0, 10000]); +res = client.call(iid, 2, x) + +## Unpack the resulting 'LSAPR_PRIVILEGE_ENUM_BUFFER' +priviledges = LSAPR_PRIVILEGE_ENUM_BUFFER.unpack(ndr.NdrStream(res)) +for priv in priviledges: + print priv \ No newline at end of file diff --git a/samples/uac.py b/samples/rpc/uac.py similarity index 100% rename from samples/uac.py rename to samples/rpc/uac.py diff --git a/windows/generated_def/winstructs.py b/windows/generated_def/winstructs.py index c1f1c6e..029a32c 100644 --- a/windows/generated_def/winstructs.py +++ b/windows/generated_def/winstructs.py @@ -4262,3 +4262,18 @@ class _ALPC_WORK_ON_BEHALF_ATTR(Structure): ] PALPC_WORK_ON_BEHALF_ATTR = POINTER(_ALPC_WORK_ON_BEHALF_ATTR) ALPC_WORK_ON_BEHALF_ATTR = _ALPC_WORK_ON_BEHALF_ATTR + +class _RPC_IF_ID(Structure): + _fields_ = [ + ("Uuid", IID), + ("VersMajor", USHORT), + ("VersMinor", USHORT), + ] +RPC_IF_ID = _RPC_IF_ID + +INITIAL_RPC_IF_ID = RPC_IF_ID + +class _RPC_IF_ID(INITIAL_RPC_IF_ID): + def __repr__(self): + return ''.format(self.Uuid.to_string(), self.VersMajor, self.VersMinor) +RPC_IF_ID = _RPC_IF_ID diff --git a/windows/rpc/__init__.py b/windows/rpc/__init__.py index 369f7e1..b76fd5d 100644 --- a/windows/rpc/__init__.py +++ b/windows/rpc/__init__.py @@ -1,4 +1,4 @@ import ndr -from client import RPCClient, RPC_IF_ID +from client import RPCClient from epmapper import find_alpc_endpoint_and_connect, endpoint_map_alpc, construct_alpc_tower \ No newline at end of file diff --git a/windows/rpc/client.py b/windows/rpc/client.py index f5c83df..33cbd71 100644 --- a/windows/rpc/client.py +++ b/windows/rpc/client.py @@ -6,23 +6,10 @@ import windows.com import windows.generated_def as gdef -class _RPC_IF_ID(ctypes.Structure): - _fields_ = [ - ("Uuid", gdef.IID), - ("MajorVersion", gdef.USHORT), - ("MinorVersion", gdef.USHORT), - ] - - def __repr__(self): - return ''.format(self.Uuid.to_string(), self.MajorVersion, self.MinorVersion) - -RPC_IF_ID = _RPC_IF_ID - - KNOW_REQUEST_TYPE = {x:x for x in [gdef.RPC_REQUEST_TYPE_CALL, gdef.RPC_REQUEST_TYPE_BIND]} -KNOW_RESPONSE_TYPE = {x:x for x in [gdef.RPC_RESPONSE_TYPE_FAIL, gdef.RPC_RESPONSE_TYPE_SUCESS, gdef.RPC_RESPONSE_TYPE_SUCESS]} +KNOW_RESPONSE_TYPE = {x:x for x in [gdef.RPC_RESPONSE_TYPE_FAIL, gdef.RPC_RESPONSE_TYPE_SUCESS, gdef.RPC_RESPONSE_TYPE_BIND_OK]} KNOWN_RPC_ERROR_CODE = {x:x for x in [ @@ -34,17 +21,15 @@ KNOWN_RPC_ERROR_CODE = {x:x for x in [ gdef.RPC_S_PROCNUM_OUT_OF_RANGE, ]} - NOT_USED = 0xBAADF00D - class ALPC_RPC_BIND(ctypes.Structure): _pack_ = 1 _fields_ = [ ("request_type", gdef.DWORD), ("UNK1", gdef.DWORD), ("UNK2", gdef.DWORD), - ("target", RPC_IF_ID), + ("target", gdef.RPC_IF_ID), ("flags", gdef.DWORD), ("if_nb_ndr32", gdef.USHORT), ("if_nb_ndr64", gdef.USHORT), @@ -61,6 +46,7 @@ class ALPC_RPC_BIND(ctypes.Structure): class RPCClient(object): + """A client for RPC-over-ALPC able to bind to interface and perform calls using NDR32 marshalling""" REQUEST_IDENTIFIER = 0x11223344 def __init__(self, port): self.alpc_client = alpc.AlpcClient(port) @@ -68,6 +54,10 @@ class RPCClient(object): self.if_bind_number = {} def bind(self, IID_str, version=(1,0)): + """Bind to the ``IID_str`` with the given ``version`` + + :returns: :class:`windows.generated_def.IID` + """ IID = windows.com.IID.from_string(IID_str) request = self._forge_bind_request(IID, version, self.number_of_bind_if) response = self._send_request(request) @@ -82,6 +72,13 @@ class RPCClient(object): return IID def call(self, IID, method_offset, params): + """Call method number ``method_offset`` of interface ``IID`` with mashalled ``params`` + + :param IID IID: An IID previously returned by :func:`bind` + :param int method_offset: + :param str params: The mashalled parameters (NDR32) + :returns: :class:`str` + """ iid_hash = hash(buffer(IID)[:]) interface_nb = self.if_bind_number[iid_hash] # TODO: add __hash__ to IID request = self._forge_call_request(interface_nb, method_offset, params) @@ -110,7 +107,7 @@ class RPCClient(object): version_major, version_minor = syntaxversion req = ALPC_RPC_BIND() req.request_type = gdef.RPC_REQUEST_TYPE_BIND - req.target = RPC_IF_ID(uuid, *syntaxversion) + req.target = gdef.RPC_IF_ID(uuid, *syntaxversion) req.flags = gdef.BIND_IF_SYNTAX_NDR32 req.if_nb_ndr32 = requested_if_nb req.if_nb_ndr64 = 0 diff --git a/windows/rpc/epmapper.py b/windows/rpc/epmapper.py index 68fc80f..6036b75 100644 --- a/windows/rpc/epmapper.py +++ b/windows/rpc/epmapper.py @@ -68,15 +68,15 @@ def explode_alpc_tower(tower): lhs, rhs = parse_floor(stream) if not (lhs[0] == 0xd): raise ValueError("Floor 0: IID expected") - iid = windows.com.IID.from_buffer_copy(lhs[1:17]) - object = windows.rpc.RPC_IF_ID(iid, lhs[17], lhs[18]) + iid = gdef.IID.from_buffer_copy(lhs[1:17]) + object = gdef.RPC_IF_ID(iid, lhs[17], lhs[18]) # Floor 1 lhs, rhs = parse_floor(stream) if not (lhs[0] == 0xd): raise ValueError("Floor 0: IID expected") - iid = windows.com.IID.from_buffer_copy(lhs[1:17]) - syntax = windows.rpc.RPC_IF_ID(iid, lhs[17], lhs[18]) + iid = gdef.IID.from_buffer_copy(lhs[1:17]) + syntax = gdef.RPC_IF_ID(iid, lhs[17], lhs[18]) # Floor 2 lhs, rhs = parse_floor(stream) @@ -100,11 +100,11 @@ def construct_alpc_tower(object, syntax, protseq, endpoint, address): if protseq != "ncalrpc": raise NotImplementedError("Construct ALPC Tower with protseq != 'ncalrpc'") # Floor 0 - floor_0_lsh = TOWER_PROTOCOL_IS_UUID + bytearray(object.Uuid) + struct.pack("".format(targetiid), "RPC") alpctowers = endpoint_map_alpc(targetiid, version, nb_response=50, sid=sid) dbgprint("ALPC endpoints list: <{0}>".format(alpctowers), "RPC") diff --git a/windows/rpc/ndr.py b/windows/rpc/ndr.py index d94120a..0a98492 100644 --- a/windows/rpc/ndr.py +++ b/windows/rpc/ndr.py @@ -28,6 +28,7 @@ def dword_pad(s): class NdrUniquePTR(object): + """Create a UNIQUE PTR around a given Ndr type""" def __init__(self, subcls): self.subcls = subcls @@ -77,6 +78,10 @@ class NdrFixedArray(object): class NdrSID(object): @classmethod def pack(cls, psid): + """Pack a PSID + + :param PSID psid: + """ subcount = windows.winproxy.GetSidSubAuthorityCount(psid) size = windows.winproxy.GetLengthSid(psid) sid_data = windows.current_process.read_memory(psid.value, size) @@ -84,6 +89,7 @@ class NdrSID(object): @classmethod def unpack(cls, stream): + """Unpack a PSID, partial implementation that returns a :class:`str` and not a PSID""" subcount = NdrLong.unpack(stream) return stream.read(8 + (subcount * 4)) @@ -163,8 +169,12 @@ class NdrByte(object): class NdrStructure(object): + """a NDR structure that tries to respect the rules of pointer packing, this class should be subclassed with + an attribute ``MEMBERS`` describing the members of the class + """ @classmethod def pack(cls, data): + """Pack data into the struct, ``data`` size must equals the number of members in the structure""" if not (len(data) == len(cls.MEMBERS)): print("Size mistach:") print(" * data size = {0}".format(len(data))) @@ -192,6 +202,7 @@ class NdrStructure(object): @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" @@ -213,6 +224,9 @@ class NdrStructure(object): 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)): @@ -291,9 +305,19 @@ class NdrConformantVaryingArrays(object): 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 +class NdrWcharConformantVaryingArrays(NdrConformantVaryingArrays): + MEMBER_TYPE = NdrShort + @classmethod + def _post_unpack(self, result): + return u"".join(unichr(c) for c in result) class NdrLongConformantArray(NdrConformantArray): MEMBER_TYPE = NdrLong @@ -303,6 +327,7 @@ class NdrByteConformantArray(NdrConformantArray): class NdrStream(object): + """A stream of bytes used for NDR unpacking""" def __init__(self, data): self.fulldata = data self.data = data @@ -329,6 +354,7 @@ class NdrStream(object): 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