diff --git a/windows/alpc.py b/windows/alpc.py new file mode 100644 index 0000000..f78f2dd --- /dev/null +++ b/windows/alpc.py @@ -0,0 +1,260 @@ +import ctypes + +import windows +from windows import winproxy +from windows import generated_def as gn + +ALPC_MSGFLG_REPLY_MESSAGE = 0x1 +ALPC_MSGFLG_LPC_MODE = 0x2 +ALPC_MSGFLG_RELEASE_MESSAGE = 0x10000 +ALPC_MSGFLG_SYNC_REQUEST = 0x20000 +ALPC_MSGFLG_WAIT_USER_MODE = 0x100000 +ALPC_MSGFLG_WAIT_ALERTABLE = 0x200000 +ALPC_MSGFLG_WOW64_CALL = 0x80000000 + +ALPC_MESSAGE_SECURITY_ATTRIBUTE = 0x80000000 +ALPC_MESSAGE_VIEW_ATTRIBUTE = 0x40000000 +ALPC_MESSAGE_CONTEXT_ATTRIBUTE = 0x20000000 +ALPC_MESSAGE_HANDLE_ATTRIBUTE = 0x10000000 + + + +class AlpcMessage(gn.PORT_MESSAGE): + def __new__(cls, buffersize): + size = ctypes.sizeof(cls) + buffersize + buffer = ctypes.c_buffer(size) + self = cls.from_buffer(buffer) + self.raw_buffer = buffer + return self + + def __init__(self, buffersize): + self.u1.s1.TotalLength = buffersize + ctypes.sizeof(self) + self.u1.s1.DataLength = buffersize + return super(AlpcMessage, self).__init__() + + def read_data(self): + return self.raw_buffer[ctypes.sizeof(self):ctypes.sizeof(self) + self.u1.s1.DataLength] + + def write_data(self, data): + self.raw_buffer[ctypes.sizeof(self): ctypes.sizeof(self) + len(data)] = data + + data = property(read_data, write_data) + +class MessageAttribute(gn.ALPC_MESSAGE_ATTRIBUTES): + def __new__(cls, flags): + size = cls._get_required_buffer_size(flags) + buffer = ctypes.c_buffer(size) + self = cls.from_buffer(buffer) + self.raw_buffer = buffer + return self + + def __init__(self, flags): + res = gn.DWORD() + winproxy.AlpcInitializeMessageAttribute(flags, self, len(self.raw_buffer), res) + + @staticmethod + def _get_required_buffer_size(flags): + res = gn.DWORD() + try: + windows.winproxy.AlpcInitializeMessageAttribute(flags, None, 0, res) + except windows.generated_def.ntstatus.NtStatusException: + return res.value + + +class AlpcPORT(object): + def __init__(self, port_name, msglen=0x1000): + self.port_name = port_name + self.handle = self._create_port(port_name, msglen) + + def _create_port(self, port_name, msglen=0x1000): + handle = gn.HANDLE() + raw_name = port_name + if not raw_name.startswith("\\"): + raw_name = "\\" + port_name + utf16_len = len(raw_name) * 2 + port_name = gn.UNICODE_STRING(utf16_len, utf16_len, raw_name) + + obj_attr = gn.OBJECT_ATTRIBUTES() + obj_attr.Length = ctypes.sizeof(obj_attr) + obj_attr.RootDirectory = None + obj_attr.ObjectName = ctypes.pointer(port_name) + obj_attr.Attributes = 0 + obj_attr.SecurityDescriptor = None + obj_attr.SecurityQualityOfService = None + + port_attr = gn.ALPC_PORT_ATTRIBUTES() + port_attr.Flags = 0 + port_attr.MaxMessageLength = msglen + port_attr.MemoryBandwidth = 0 + port_attr.MaxPoolUsage = 0 + + winproxy.NtAlpcCreatePort(handle, obj_attr, port_attr) + return handle.value + +#class AlpcExchange(object): +# def send_receive_data(port_handle, data): +# raw_sendmsg = ctypes.c_buffer(0x1000) +# size = gn.SIZE_T(0x1000) +# sendmsg = ctypes.cast(raw_sendmsg, gn.PPORT_MESSAGE) +# buffer = ctypes.c_buffer(0x200) +# sendmsg_attr = ctypes.cast(buffer, gn.PALPC_MESSAGE_ATTRIBUTES) +# res = gn.DWORD() +# winproxy.AlpcInitializeMessageAttribute(ALPC_MESSAGE_CONTEXT_ATTRIBUTE + ALPC_MESSAGE_HANDLE_ATTRIBUTE + 1, sendmsg_attr , 0x200, res) +# +# sendmsg = AlpcMessage(len(data)) +# sendmsg.data = data +# +# size = gn.SIZE_T(0x1000) +# receive = AlpcMessage(size.value) +# buffer = ctypes.c_buffer(0x200) +# receive_attr = ctypes.cast(buffer, gn.PALPC_MESSAGE_ATTRIBUTES) +# res = gn.DWORD() +# +# winproxy.NtAlpcSendWaitReceivePort(port_handle, ALPC_MSGFLG_SYNC_REQUEST, sendmsg, sendmsg_attr, receive, size, receive_attr, None) +# return receive, receive_attr + + +def send_receive_data(port_handle, data): + #sendmsg_attr = MessageAttribute(ALPC_MESSAGE_CONTEXT_ATTRIBUTE + ALPC_MESSAGE_HANDLE_ATTRIBUTE + 1) + sendmsg_attr = MessageAttribute(0) + sendmsg = AlpcMessage(len(data)) + sendmsg.data = data + + size = gn.SIZE_T(0x1000) + receive = AlpcMessage(size.value) + receive_attr = MessageAttribute(0) + + winproxy.NtAlpcSendWaitReceivePort(port_handle, ALPC_MSGFLG_SYNC_REQUEST, sendmsg, sendmsg_attr, receive, size, receive_attr, None) + return receive_attr, receive + +class AlpcClient(object): + def __init__(self): + self.portname = None + self.handle = None + + def connect_to_port(self, port_name, connect_msg=None, maxmsglen=0x1000): + if self.handle is not None: + raise ValueError("Client already connected") + handle = gn.HANDLE() + + #raw_name = "\\" + port_name + raw_name = port_name + utf16_len = len(raw_name) * 2 + + port_name = gn.UNICODE_STRING(utf16_len, utf16_len, raw_name) + + obj_attr = gn.OBJECT_ATTRIBUTES() + obj_attr.Length = ctypes.sizeof(obj_attr) + obj_attr.RootDirectory = None + obj_attr.ObjectName = None + obj_attr.Attributes = 0 + obj_attr.SecurityDescriptor = None + obj_attr.SecurityQualityOfService = None + + + port_attr = gn.ALPC_PORT_ATTRIBUTES() + port_attr.Flags = 0 + port_attr.MaxMessageLength = maxmsglen + port_attr.MemoryBandwidth = 0 + port_attr.MaxPoolUsage = 0 + + if True: + port_attr.SecurityQos.Length = 12 + port_attr.SecurityQos.ImpersonationLevel = 2 + port_attr.SecurityQos.ContextTrackingMode = 0 + port_attr.SecurityQos.EffectiveOnly = 0 + + + #define ALPC_PORFLG_ALLOW_LPC_REQUESTS 0x20000 // rev + #define ALPC_PORFLG_WAITABLE_PORT 0x40000 // dbg + #define ALPC_PORFLG_SYSTEM_PROCESS 0x100000 // dbg + + #port_attr.MaxPoolUsage = 0 + port_attr.Flags = 0x10000 # Flag qui fonctionne pour l'UAC + # 0x0010000 est le flag qui permet l'impersonation (en tout cas le pop UAC) + #port_attr.MaxPoolUsage = 4294967295 + #port_attr.MaxSectionSize = 4294967295 + ##port_attr.MaxViewSize = 4294967295 + #port_attr.MaxTotalSectionSize = 4294967295 + #port_attr.DupObjectTypes = 4093 + + # tst.Flags -> 34144256 + # tst.SecurityQos.Length -> 12 + # tst.SecurityQos.ImpersonationLevel -> SecurityImpersonation(0x2L) + # tst.SecurityQos.ContextTrackingMode -> 0 + # tst.SecurityQos.EffectiveOnly -> 0 + # tst.MaxMessageLength -> 4096 + # tst.MemoryBandwidth -> 0 + # tst.MaxPoolUsage -> 4294967295 + # tst.MaxSectionSize -> 4294967295 + # tst.MaxViewSize -> 4294967295 + # tst.MaxTotalSectionSize -> 4294967295 + # tst.DupObjectTypes -> 4093 + + + if connect_msg is not None: + size = len(connect_msg) + send_msg = AlpcMessage(size) + send_msg.data = connect_msg + sendmsg_attr = MessageAttribute(0) + receive_attr = MessageAttribute(0) + receive_attr = None + sendmsg_attr = None + buffersize = gn.DWORD(len(send_msg.raw_buffer)) + else: + size = None + send_msg = None + sendmsg_attr = None + receive_attr = None + buffersize = None + + #print(hex([0].AllocatedAttributes)) + #import pdb;pdb.set_trace() + x = winproxy.NtAlpcConnectPort(handle, port_name,obj_attr, port_attr, ALPC_MSGFLG_SYNC_REQUEST, None, send_msg, buffersize, sendmsg_attr, receive_attr, None) + + self.handle = handle.value + self.portname = port_name + if connect_msg is not None: + return send_msg + + def send_receive(self, data): + return send_receive_data(self.handle, data) + +class AlpcServer(object): + def __init__(self, port_name): + self.port = AlpcPORT(port_name) + + def wait_data(self): + size = gn.SIZE_T(0x1000) + receive = AlpcMessage(size.value) + receive_attr = MessageAttribute(0) + winproxy.NtAlpcSendWaitReceivePort(self.port.handle, 0, None, None, receive, size, receive_attr, None) + return receive_attr, receive + + def accept_connection(self, msg): + port_handle = self.port.handle + rhandle = gn.HANDLE() + + ALPC_HANDLEFLG_DUPLICATE_INHERIT = 0x80000 + port_attr = gn.ALPC_PORT_ATTRIBUTES() + port_attr.Flags = ALPC_HANDLEFLG_DUPLICATE_INHERIT + port_attr.DupObjectTypes = 4 + port_attr.MaxMessageLength = 0x578 + port_attr.MemoryBandwidth = 0 + port_attr.MaxPoolUsage = 0x15E00 + + winproxy.NtAlpcAcceptConnectPort(rhandle, port_handle, 0, None, port_attr, None, msg, None, 1) + return rhandle.value, msg + + def send_receive(self, data): + return send_receive_data(self.port.handle, data) + + def reply(self, reply_to_msg, reply_msg): + port_handle = self.port.handle + sendmsg = AlpcMessage(len(reply_msg)) + sendmsg.data = reply_msg + sendmsg_attr = MessageAttribute(0) + sendmsg.MessageId = reply_to_msg.MessageId + winproxy.NtAlpcSendWaitReceivePort(port_handle, ALPC_MSGFLG_RELEASE_MESSAGE, sendmsg, None, None, None, None, None) + return None, None + diff --git a/windows/rpc/__init__.py b/windows/rpc/__init__.py new file mode 100644 index 0000000..a4ee3a1 --- /dev/null +++ b/windows/rpc/__init__.py @@ -0,0 +1,3 @@ +from client import RPC_SYNTAX_IDENTIFIER, RPCClient +import ndr +from epmapper import find_alpc_endpoint_and_connect, construct_alpc_tower \ No newline at end of file diff --git a/windows/rpc/client.py b/windows/rpc/client.py new file mode 100644 index 0000000..ae425eb --- /dev/null +++ b/windows/rpc/client.py @@ -0,0 +1,110 @@ +import windows.alpc as alpc +import windows.com +from windows.generated_def import USHORT +import ctypes +import struct + + +class _RPC_SYNTAX_IDENTIFIER(ctypes.Structure): + _fields_ = [ + ("SyntaxGUID", windows.com.IID), + ("MajorVersion", USHORT), + ("MinorVersion", USHORT), + ] + + def __repr__(self): + return ''.format(self.SyntaxGUID.to_string(), self.MajorVersion, self.MinorVersion) +RPC_SYNTAX_IDENTIFIER = _RPC_SYNTAX_IDENTIFIER + +# DEFINES + +REQUEST_TYPE_CALL = 0 +REQUEST_TYPE_BIND = 1 + +KNOW_REQUEST_TYPE = { + REQUEST_TYPE_CALL : "REQUEST_CALL", + REQUEST_TYPE_BIND : "REQUEST_BIND", + } + + +RESPONSE_TYPE_BIND_OK = 1 +RESPONSE_TYPE_FAIL = 2 +RESPONSE_TYPE_SUCESS = 3 + + +KNOW_RESPONSE_TYPE = { + RESPONSE_TYPE_FAIL : "RESPONSE_FAIL", + RESPONSE_TYPE_SUCESS : "RESPONSE_SUCESS", + RESPONSE_TYPE_BIND_OK: "RESPONSE_BIND_OK", + } + + +KNOWN_RPC_ERROR_CODE = { + 1783 : "RPC_X_BAD_STUB_DATA", + 1717 : "RPC_S_UNKNOWN_IF" +} + +NOT_USED = 0xBAADF00D + +# def dword_pack(*args): +# return "".join(struct.pack(" interface + self.if_bind_number = {} + + def bind(self, IID_str, version=(1,0)): + IID = windows.com.IID.from_string(IID_str) + request = self._forge_bind_request(buffer(IID)[:], version, self.number_of_bind_if) + response = self._send_request(request) + # Parse reponse + request_type = self._get_request_type(response) + if request_type != RESPONSE_TYPE_BIND_OK: + raise ValueError("Unexpected reponse type. Expected RESPONSE_TYPE_BIND_OK got {0}".format(KNOW_RESPONSE_TYPE.get(request_type, request_type))) + iid_hash = hash(buffer(IID)[:]) # TODO: add __hash__ to IID + self.if_bind_number[iid_hash] = self.number_of_bind_if + self.number_of_bind_if += 1 + #TODO: attach version information to IID + return IID + + def call(self, IID, method_offset, params): + 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) + response = self._send_request(request) + # Parse reponse + request_type = self._get_request_type(response) + if request_type != RESPONSE_TYPE_SUCESS: + raise ValueError("Unexpected reponse type. Expected RESPONSE_SUCESS got {0}".format(KNOW_RESPONSE_TYPE.get(request_type, request_type))) + + data = struct.unpack("<6I", response[:6 * 4]) + assert data[3] == self.REQUEST_IDENTIFIER + return response[4 * 6:] # Should be the return value (not completly verified) + + def _send_request(self, request): + resp_attr, resp = self.aplc_client.send_receive(request) + return resp.data + + def _forge_bind_request(self, rawuuid, syntaxversion, requested_if_nb): + version_major, version_minor = syntaxversion + # TODO: flags + data = struct.pack("III16sHHIIIIIIIIII", REQUEST_TYPE_BIND, NOT_USED, NOT_USED, rawuuid, version_major, version_minor, NOT_USED, requested_if_nb, NOT_USED, NOT_USED ,NOT_USED, NOT_USED, NOT_USED, NOT_USED, NOT_USED, NOT_USED) # Fonctionne pour le BIND :D + return data + + def _forge_call_request(self, interface_nb, method_offset, params): + # TODO: differents REQUEST_IDENTIFIER for each req ? + request = struct.pack("<16I", REQUEST_TYPE_CALL, NOT_USED, 0, self.REQUEST_IDENTIFIER, interface_nb, method_offset, NOT_USED, NOT_USED, NOT_USED, NOT_USED, NOT_USED, NOT_USED, NOT_USED, NOT_USED, NOT_USED, NOT_USED) + request += params + return request + + def _get_request_type(self, response): + "raise if request_type == RESPONSE_TYPE_FAIL" + request_type = struct.unpack(" len(unpacked[2]) + # Parse towers + return [explode_alpc_tower(obj) for obj in unpacked[2]] + + +def find_alpc_endpoint_and_connect(targetiid, version=(1,0)): + alpctowers = endpoint_map_alpc(targetiid, version, nb_response=50) + for tower in alpctowers: + dbgprint("Trying to connect to endpoint <{0}>".format(tower.endpoint), "RPC") + alpc_port = r"\RPC Control\{0}".format(tower.endpoint) + try: + client = windows.rpc.RPCClient(alpc_port) + except Exception as e: + dbgprint("Could not connect to endpoint <{0}>: {1}".format(tower.endpoint, e), "RPC") + continue + break + else: + raise ValueError("Could not find a valid endpoint for target <{0}> version <{1}>".format(targetiid, version)) + dbgprint('Connected to ALPC port "{0}"'.format(alpc_port), "RPC") + return client + diff --git a/windows/rpc/ndr.py b/windows/rpc/ndr.py new file mode 100644 index 0000000..55a579a --- /dev/null +++ b/windows/rpc/ndr.py @@ -0,0 +1,269 @@ +import windows +import windows.generated_def as gdef + +import struct + +# 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("