Add doc to RPC + RPC_IF_ID generated-extended + lsass sample

This commit is contained in:
Clement Rouault
2017-08-30 18:29:49 +02:00
parent d44a3b032e
commit fccd2a49e6
12 changed files with 204 additions and 30 deletions
@@ -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;
@@ -0,0 +1,5 @@
INITIAL_RPC_IF_ID = RPC_IF_ID
class _RPC_IF_ID(INITIAL_RPC_IF_ID):
def __repr__(self):
return '<RPC_IF_ID "{0}" ({1}, {2})>'.format(self.Uuid.to_string(), self.VersMajor, self.VersMinor)
+1 -1
View File
@@ -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
+1
View File
@@ -22,6 +22,7 @@ Contents:
com.rst
crypto.rst
alpc.rst
rpc.rst
generated.rst
iat_hook.rst
wip.rst
+22
View File
@@ -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
+84
View File
@@ -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
+15
View File
@@ -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 '<RPC_IF_ID "{0}" ({1}, {2})>'.format(self.Uuid.to_string(), self.VersMajor, self.VersMinor)
RPC_IF_ID = _RPC_IF_ID
+1 -1
View File
@@ -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
+15 -18
View File
@@ -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 '<RPC_IF_ID "{0}" ({1}, {2})>'.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
+28 -10
View File
@@ -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("<BB", object.MajorVersion, object.MinorVersion)
floor_0_lsh = TOWER_PROTOCOL_IS_UUID + bytearray(object.Uuid) + struct.pack("<BB", object.VersMajor, object.VersMinor)
floor_0_rsh = TOWER_EMPTY_RHS
floor_0 = craft_floor(floor_0_lsh, floor_0_rsh)
# Floor 1
floor_1_lsh = TOWER_PROTOCOL_IS_UUID + bytearray(object.Uuid) + struct.pack("<BB", object.MajorVersion, object.MinorVersion)
floor_1_lsh = TOWER_PROTOCOL_IS_UUID + bytearray(object.Uuid) + struct.pack("<BB", object.VersMajor, object.VersMinor)
floor_1_rsh = TOWER_EMPTY_RHS
floor_1 = craft_floor(floor_1_lsh, floor_1_rsh)
# Floor 2
@@ -119,18 +119,28 @@ def construct_alpc_tower(object, syntax, protseq, endpoint, address):
return len(towerarray), bytearray(towerarray)
def endpoint_map_alpc(targetiid, version=(1,0), nb_response=1, sid=gdef.WinLocalSystemSid):
"""Ask the EPMapper for ALPC endpoints of ``targetiid:version`` (maximum of ``nb_response``)
:param str targetiid: The IID of the requested interface
:param (int,int) version: The version requested interface
:param int nb_response: The maximum number of response
:param WELL_KNOWN_SID_TYPE sid: The SID used to request the EPMapper
:returns: [:class:`~windows.rpc.epmapper.UnpackTower`] -- A list of :class:`~windows.rpc.epmapper.UnpackTower`
"""
if isinstance(targetiid, basestring):
targetiid = windows.com.IID.from_string(targetiid)
targetiid = gdef.IID.from_string(targetiid)
# Connect to epmapper
client = windows.rpc.RPCClient(r"\RPC Control\epmapper")
epmapperiid = client.bind("e1af8308-5d1f-11c9-91a4-08002b14a0fa", version=(3,0))
# Compute request tower
## object
rpc_object = windows.rpc.RPC_IF_ID(targetiid, *version)
rpc_object = gdef.RPC_IF_ID(targetiid, *version)
## Syntax
syntax_iid = windows.com.IID.from_string("8a885d04-1ceb-11c9-9fe8-08002b104860")
rpc_syntax = windows.rpc.RPC_IF_ID(syntax_iid, 2, 0)
syntax_iid = gdef.IID.from_string("8a885d04-1ceb-11c9-9fe8-08002b104860")
rpc_syntax = gdef.RPC_IF_ID(syntax_iid, 2, 0)
## Forge tower
tower_array_size, towerarray = construct_alpc_tower(rpc_object, rpc_syntax, "ncalrpc", "", None)
@@ -155,6 +165,14 @@ def endpoint_map_alpc(targetiid, version=(1,0), nb_response=1, sid=gdef.WinLocal
def find_alpc_endpoint_and_connect(targetiid, version=(1,0), sid=gdef.WinLocalSystemSid):
"""Ask the EPMapper for ALPC endpoints of ``targetiid:version`` and connect to one of them.
:param str targetiid: The IID of the requested interface
:param (int,int) version: The version requested interface
:param WELL_KNOWN_SID_TYPE sid: The SID used to request the EPMapper
:returns: A connected :class:`~windows.rpc.RPCClient`
"""
dbgprint("Finding ALPC endpoints for <{0}>".format(targetiid), "RPC")
alpctowers = endpoint_map_alpc(targetiid, version, nb_response=50, sid=sid)
dbgprint("ALPC endpoints list: <{0}>".format(alpctowers), "RPC")
+26
View File
@@ -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