From c76a50c04596375c2e1591f6b44e754a7c09a6ae Mon Sep 17 00:00:00 2001 From: Clement Rouault Date: Wed, 16 Aug 2017 17:38:18 +0200 Subject: [PATCH] add doc to alpc.py + improve doc now that Winstruct are documented --- windows/alpc.py | 137 +++++++++++++++++++++++++++++++---- windows/debug/debugger.py | 32 ++++++-- windows/winobject/process.py | 4 +- 3 files changed, 148 insertions(+), 25 deletions(-) diff --git a/windows/alpc.py b/windows/alpc.py index 5635dab..62577eb 100644 --- a/windows/alpc.py +++ b/windows/alpc.py @@ -15,8 +15,8 @@ from windows import generated_def as gdef # 0x1a: 0x4000000: ALPC_MESSAGE_DIRECT_ATTRIBUTE(0x4000000) size=0x8 # 0x19: 0x2000000: ALPC_MESSAGE_WORK_ON_BEHALF_ATTRIBUTE(0x2000000) size=0x8 - class AlpcMessage(object): + """Represent a full ALPC Message: a :class:`AlpcMessagePort` and a :class:`MessageAttribute`""" # PORT_MESSAGE + MessageAttribute def __init__(self, msg_or_size=0x1000, attributes=None): # Init the PORT_MESSAGE @@ -42,6 +42,7 @@ class AlpcMessage(object): # PORT_MESSAGE wrappers @property def type(self): + """The type of the message (``PORT_MESSAGE.u2.s2.Type``)""" return self.port_message.u2.s2.Type def get_port_message_data(self): @@ -51,49 +52,79 @@ class AlpcMessage(object): self.port_message.data = data data = property(get_port_message_data, set_port_message_data) + "The data of the message (located after the PORT_MESSAGE header)" # MessageAttributes wrappers ## Low level attributes access @property def security_attribute(self): + """The :data:`~windows.generated_def.ALPC_MESSAGE_SECURITY_ATTRIBUTE` of the message + : + :type: :class:`ALPC_SECURITY_ATTR` + """ return self.attributes.get_attribute(gdef.ALPC_MESSAGE_SECURITY_ATTRIBUTE) @property def view_attribute(self): + """The :data:`~windows.generated_def.ALPC_MESSAGE_VIEW_ATTRIBUTE` of the message: + + :type: :class:`ALPC_DATA_VIEW_ATTR` + """ return self.attributes.get_attribute(gdef.ALPC_MESSAGE_VIEW_ATTRIBUTE) @property def context_attribute(self): + """The :data:`~windows.generated_def.ALPC_MESSAGE_CONTEXT_ATTRIBUTE` of the message: + + :type: :class:`ALPC_CONTEXT_ATTR` + """ return self.attributes.get_attribute(gdef.ALPC_MESSAGE_CONTEXT_ATTRIBUTE) @property def handle_attribute(self): + """The :data:`~windows.generated_def.ALPC_MESSAGE_HANDLE_ATTRIBUTE` of the message: + + :type: :class:`ALPC_HANDLE_ATTR` + """ return self.attributes.get_attribute(gdef.ALPC_MESSAGE_HANDLE_ATTRIBUTE) ## Low level validity check (Test) @property def view_is_valid(self): # Change the name ? + """True if :data:`~windows.generated_def.ALPC_MESSAGE_VIEW_ATTRIBUTE` is a ValidAttributes""" return self.attributes.is_valid(gdef.ALPC_MESSAGE_VIEW_ATTRIBUTE) @property def security_is_valid(self): # Change the name ? + """True if :data:`~windows.generated_def.ALPC_MESSAGE_SECURITY_ATTRIBUTE` is a ValidAttributes""" return self.attributes.is_valid(gdef.ALPC_MESSAGE_SECURITY_ATTRIBUTE) @property def handle_is_valid(self): # Change the name ? + """True if :data:`~windows.generated_def.ALPC_MESSAGE_HANDLE_ATTRIBUTE` is a ValidAttributes""" return self.attributes.is_valid(gdef.ALPC_MESSAGE_HANDLE_ATTRIBUTE) @property def context_is_valid(self): # Change the name ? + """True if :data:`~windows.generated_def.ALPC_MESSAGE_CONTEXT_ATTRIBUTE` is a ValidAttributes""" return self.attributes.is_valid(gdef.ALPC_MESSAGE_CONTEXT_ATTRIBUTE) + @property def valid_attributes(self): + """The list of valid attributes + + :type: [:class:`~windows.generated_def.Flag`] + """ return self.attributes.valid_list @property def allocated_attributes(self): + """The list of allocated attributes + + :type: [:class:`~windows.generated_def.Flag`] + """ return self.attributes.allocated_list ## High level setup (Test) @@ -103,6 +134,7 @@ class AlpcMessage(object): class AlpcMessagePort(gdef.PORT_MESSAGE): + """The effective ALPC Message composed of a ``PORT_MESSAGE`` structure followed by the data""" # Constructeur @classmethod def from_buffer(self, buffer): @@ -131,6 +163,7 @@ class AlpcMessagePort(gdef.PORT_MESSAGE): self.set_datalen(len(data)) data = property(read_data, write_data) + "The data of the message (located after the header)" def set_datalen(self, datalen): self.u1.s1.TotalLength = self.header_size + datalen @@ -140,6 +173,7 @@ class AlpcMessagePort(gdef.PORT_MESSAGE): return self.u1.s1.DataLength datalen = property(get_datalen, set_datalen) + """The length of the data""" KNOWN_ALPC_ATTRIBUTES = (gdef.ALPC_MESSAGE_SECURITY_ATTRIBUTE, gdef.ALPC_MESSAGE_VIEW_ATTRIBUTE, @@ -153,6 +187,7 @@ KNOWN_ALPC_ATTRIBUTES_MAPPING = {x:x for x in KNOWN_ALPC_ATTRIBUTES} class MessageAttribute(gdef.ALPC_MESSAGE_ATTRIBUTES): + """The attributes of an ALPC message""" ATTRIBUTE_BY_FLAG = [(gdef.ALPC_MESSAGE_SECURITY_ATTRIBUTE, gdef.ALPC_SECURITY_ATTR), (gdef.ALPC_MESSAGE_VIEW_ATTRIBUTE, gdef.ALPC_DATA_VIEW_ATTR), (gdef.ALPC_MESSAGE_CONTEXT_ATTRIBUTE, gdef.ALPC_CONTEXT_ATTR), @@ -162,22 +197,34 @@ class MessageAttribute(gdef.ALPC_MESSAGE_ATTRIBUTES): (gdef.ALPC_MESSAGE_WORK_ON_BEHALF_ATTRIBUTE, gdef.ALPC_WORK_ON_BEHALF_ATTR), ] - # 0x1b: 0x8000000: ALPC_MESSAGE_TOKEN_ATTRIBUTE(0x8000000): size=0x18 -# 0x1a: 0x4000000: ALPC_MESSAGE_DIRECT_ATTRIBUTE(0x4000000) size=0x8 -# 0x19: 0x2000000: ALPC_MESSAGE_WORK_ON_BEHALF_ATTRIBUTE(0x2000000) size=0x8 - @classmethod - def with_attributes(cls, flags): - size = cls._get_required_buffer_size(flags) + def with_attributes(cls, attributes): + """Create a new :class:`MessageAttribute` with ``attributes`` allocated + + :returns: :class:`MessageAttribute` + """ + size = cls._get_required_buffer_size(attributes) buffer = ctypes.c_buffer(size) self = cls.from_buffer(buffer) self.raw_buffer = buffer res = gdef.DWORD() - winproxy.AlpcInitializeMessageAttribute(flags, self, len(self.raw_buffer), res) + winproxy.AlpcInitializeMessageAttribute(attributes, self, len(self.raw_buffer), res) return self @classmethod def with_all_attributes(cls): + """Create a new :class:`MessageAttribute` with the following attributes allocated: + + - :class:`ALPC_MESSAGE_SECURITY_ATTRIBUTE` + - :class:`ALPC_MESSAGE_VIEW_ATTRIBUTE` + - :class:`ALPC_MESSAGE_CONTEXT_ATTRIBUTE` + - :class:`ALPC_MESSAGE_HANDLE_ATTRIBUTE` + - :class:`ALPC_MESSAGE_TOKEN_ATTRIBUTE` + - :class:`ALPC_MESSAGE_DIRECT_ATTRIBUTE` + - :class:`ALPC_MESSAGE_WORK_ON_BEHALF_ATTRIBUTE` + + :returns: :class:`MessageAttribute` + """ return cls.with_attributes(gdef.ALPC_MESSAGE_SECURITY_ATTRIBUTE | gdef.ALPC_MESSAGE_VIEW_ATTRIBUTE | gdef.ALPC_MESSAGE_CONTEXT_ATTRIBUTE | @@ -197,11 +244,13 @@ class MessageAttribute(gdef.ALPC_MESSAGE_ATTRIBUTES): return res.value return res.value - def is_allocated(self, value): - return bool(self.AllocatedAttributes & value) + def is_allocated(self, attribute): + """Return ``True`` if ``attribute`` is allocated""" + return bool(self.AllocatedAttributes & attribute) - def is_valid(self, value): - return bool(self.ValidAttributes & value) + def is_valid(self, attribute): + """Return ``True`` if ``attribute`` is valid""" + return bool(self.ValidAttributes & attribute) def get_attribute(self, attribute): if not self.is_allocated(attribute): @@ -224,10 +273,18 @@ class MessageAttribute(gdef.ALPC_MESSAGE_ATTRIBUTES): @property def valid_list(self): + """The list of valid attributes + + :type: [:class:`~windows.generated_def.Flag`] + """ return self._extract_alpc_attributes_values(self.ValidAttributes) @property def allocated_list(self): + """The list of allocated attributes + + :type: [:class:`~windows.generated_def.Flag`] + """ return self._extract_alpc_attributes_values(self.AllocatedAttributes) @@ -235,6 +292,14 @@ AlpcSection = namedtuple("AlpcSection", ["handle", "size"]) class AlpcTransportBase(object): def send_receive(self, alpc_message, receive_msg=None, flags=gdef.ALPC_MSGFLG_SYNC_REQUEST): + """Send and receive a message with ``flags``. + + :param alpc_message: The message to send. If ``alpc_message`` is a :class:`str` it build an AlpcMessage with the message as data. + :type alpc_message: AlpcMessage or str + :param receive_msg: The message to send. If ``receive_msg`` is a ``None`` it create and return a simple :class:`AlpcMessage` + :type receive_msg: AlpcMessage or None + :param int flags: The flags for :func:`NtAlpcSendWaitReceivePort` + """ if isinstance(alpc_message, basestring): raw_alpc_message = alpc_message alpc_message = AlpcMessage(max(0x1000, len(alpc_message))) @@ -247,6 +312,12 @@ class AlpcTransportBase(object): return receive_msg def send(self, alpc_message, flags=0): + """Send the ``alpc_message`` with ``flags`` + + :param alpc_message: The message to send. If ``alpc_message`` is a :class:`str` it build an AlpcMessage with the message as data. + :type alpc_message: AlpcMessage or str + :param int flags: The flags for :func:`NtAlpcSendWaitReceivePort` + """ if isinstance(alpc_message, basestring): raw_alpc_message = alpc_message alpc_message = AlpcMessage(max(0x1000, len(alpc_message))) @@ -254,6 +325,12 @@ class AlpcTransportBase(object): winproxy.NtAlpcSendWaitReceivePort(self.handle, flags, alpc_message.port_message, alpc_message.attributes, None, None, None, None) def recv(self, receive_msg=None, flags=0): + """Receive a message into ``alpc_message`` with ``flags``. + + :param receive_msg: The message to send. If ``receive_msg`` is a ``None`` it create and return a simple :class:`AlpcMessage` + :type receive_msg: AlpcMessage or None + :param int flags: The flags for :func:`NtAlpcSendWaitReceivePort` + """ if receive_msg is None: receive_msg = AlpcMessage(0x1000) receive_size = gdef.SIZE_T(receive_msg.port_message_buffer_size) @@ -263,9 +340,11 @@ class AlpcTransportBase(object): class AlpcClient(AlpcTransportBase): + "An ALPC client able to connect to a port and send/receive messages" DEFAULT_MAX_MESSAGE_LENGTH = 0x1000 def __init__(self, port_name=None): + """Init the :class:`AlpcClient` automatically connect to ``port_name`` using default values if given""" self.handle = None self.portname = None if port_name is not None: @@ -275,7 +354,18 @@ class AlpcClient(AlpcTransportBase): utf16_len = len(name) * 2 return gdef.UNICODE_STRING(utf16_len, utf16_len, name) - def connect_to_port(self, port_name, connect_message=None, receive_message=None, port_attr=None, port_attr_flags=0x10000, obj_attr=None, flags=gdef.ALPC_MSGFLG_SYNC_REQUEST, timeout=None): + def connect_to_port(self, port_name, connect_message=None, + port_attr=None, port_attr_flags=0x10000, obj_attr=None, + flags=gdef.ALPC_MSGFLG_SYNC_REQUEST, timeout=None): + """Connect to the ALPC port ``port_name``. Most of the parameters have defauls value is ``None`` is passed. + + :param AlpcMessage connect_message: The message send with the connection request, if not ``None`` the function will return an :class:`AlpcMessage` + :param ALPC_PORT_ATTRIBUTES port_attr: The port attributes, one with default value will be used if this parameter is ``None`` + :param int port_attr_flags: ``ALPC_PORT_ATTRIBUTES.Flags`` used if ``port_attr`` is ``None`` (MUTUALY EXCLUSINVE WITH ``port_attr``) + :param OBJECT_ATTRIBUTES obj_attr: The attributes of the port (can be None) + :param int flags: The flags for :func:`NtAlpcConnectPort` + :param int timeout: The timeout of the request + """ # TODO raise on mutual exclusive parameter if self.handle is not None: raise ValueError("Client already connected") @@ -340,6 +430,7 @@ class AlpcClient(AlpcTransportBase): class AlpcServer(AlpcTransportBase): + """An ALPC server able to create a port, accept connections and send/receive messages""" DEFAULT_MAX_MESSAGE_LENGTH = 0x1000 def __init__(self, port_name=None): @@ -352,6 +443,14 @@ class AlpcServer(AlpcTransportBase): return gdef.UNICODE_STRING(utf16_len, utf16_len, name) def create_port(self, port_name, msglen=None, port_attr_flags=0, obj_attr=None, port_attr=None): + """Create the ALPC port ``port_name``. Most of the parameters have defauls value is ``None`` is passed. + + :param str port_name: The port's name to create. + :param int msglen: ``ALPC_PORT_ATTRIBUTES.MaxMessageLength`` used if ``port_attr`` is ``None`` (MUTUALY EXCLUSINVE WITH ``port_attr``) + :param int port_attr_flags: ``ALPC_PORT_ATTRIBUTES.Flags`` used if ``port_attr`` is ``None`` (MUTUALY EXCLUSINVE WITH ``port_attr``) + :param OBJECT_ATTRIBUTES obj_attr: The attributes of the port, one with default value will be used if this parameter is ``None`` + :param ALPC_PORT_ATTRIBUTES port_attr: The port attributes, one with default value will be used if this parameter is ``None`` + """ # TODO raise on mutual exclusive parameter (port_attr + port_attr_flags | obj_attr + msglen) handle = gdef.HANDLE() raw_name = port_name @@ -371,9 +470,9 @@ class AlpcServer(AlpcTransportBase): obj_attr.SecurityQualityOfService = None if port_attr is None: port_attr = gdef.ALPC_PORT_ATTRIBUTES() - # port_attr.Flags = port_attr_flags + port_attr.Flags = port_attr_flags # port_attr.Flags = 0x2080000 - port_attr.Flags = 0x90000 + # port_attr.Flags = 0x90000 port_attr.MaxMessageLength = msglen port_attr.MemoryBandwidth = 0 port_attr.MaxPoolUsage = 0xffffffff @@ -388,6 +487,14 @@ class AlpcServer(AlpcTransportBase): self.handle = handle.value def accept_connection(self, msg, port_attr=None, port_context=None): + """Accept the connection for a ``LPC_CONNECTION_REQUEST`` message. + ``msg.MessageId`` must be the same as the connection requesting message. + + :param AlpcMessage msg: The response message. + :param ALPC_PORT_ATTRIBUTES port_attr: The attributes of the port, one with default value will be used if this parameter is ``None`` + :param PVOID port_context: A value that will be copied in ``ALPC_CONTEXT_ATTR.PortContext`` of every message on this connection. + + """ rhandle = gdef.HANDLE() if port_attr is None: diff --git a/windows/debug/debugger.py b/windows/debug/debugger.py index bd9ad6f..d994513 100644 --- a/windows/debug/debugger.py +++ b/windows/debug/debugger.py @@ -973,33 +973,49 @@ class Debugger(object): raise NotImplementedError("Debugger that explicitly single step should implement ") def on_create_process(self, create_process): - """Called on create_process event (for param type see https://msdn.microsoft.com/en-us/library/windows/desktop/ms679286(v=vs.85).aspx)""" + """Called on create_process event + + :param CREATE_PROCESS_DEBUG_INFO create_process:""" pass def on_exit_process(self, exit_process): - """Called on exit_process event (for param type see https://msdn.microsoft.com/en-us/library/windows/desktop/ms679334(v=vs.85).aspx)""" + """Called on exit_process event + + :param EXIT_PROCESS_DEBUG_INFO exit_process:""" pass def on_create_thread(self, create_thread): - """Called on create_thread event (for param type see https://msdn.microsoft.com/en-us/library/windows/desktop/ms679287(v=vs.85).aspx)""" + """Called on create_thread event + + :param CREATE_THREAD_DEBUG_INFO create_thread:""" pass def on_exit_thread(self, exit_thread): - """Called on exit_thread event (for param type see https://msdn.microsoft.com/en-us/library/windows/desktop/ms679335(v=vs.85).aspx)""" + """Called on exit_thread event + + :param EXIT_THREAD_DEBUG_INFO exit_thread:""" pass def on_load_dll(self, load_dll): - """Called on load_dll event (for param type see https://msdn.microsoft.com/en-us/library/windows/desktop/ms680351(v=vs.85).aspx)""" + """Called on load_dll event + + :param LOAD_DLL_DEBUG_INFO load_dll:""" pass def on_unload_dll(self, unload_dll): - """Called on unload_dll event (for param type see https://msdn.microsoft.com/en-us/library/windows/desktop/ms681403(v=vs.85).aspx)""" + """Called on unload_dll event + + :param UNLOAD_DLL_DEBUG_INFO unload_dll:""" pass def on_output_debug_string(self, debug_string): - """Called on debug_string event (for param type see https://msdn.microsoft.com/en-us/library/windows/desktop/ms680545(v=vs.85).aspx)""" + """Called on debug_string event + + :param OUTPUT_DEBUG_STRING_INFO debug_string:""" pass def on_rip(self, rip_info): - """Called on rip_info event (for param type see https://msdn.microsoft.com/en-us/library/windows/desktop/ms680587(v=vs.85).aspx)""" + """Called on rip_info event + + :param RIP_INFO rip_info:""" pass \ No newline at end of file diff --git a/windows/winobject/process.py b/windows/winobject/process.py index 41831f4..6b33503 100644 --- a/windows/winobject/process.py +++ b/windows/winobject/process.py @@ -457,7 +457,7 @@ class Process(AutoHandle): def query_memory(self, addr): """Query the memory informations about page at ``addr`` - :rtype: :class:`MEMORY_BASIC_INFORMATION` + :rtype: :class:`~windows.generated_def.MEMORY_BASIC_INFORMATION` """ if windows.current_process.bitness == 32 and self.bitness == 64: res = MEMORY_BASIC_INFORMATION64() @@ -478,7 +478,7 @@ class Process(AutoHandle): def memory_state(self): """Yield the memory information for the whole address space of the process - :yield: :class:`MEMORY_BASIC_INFORMATION` + :yield: :class:`~windows.generated_def.MEMORY_BASIC_INFORMATION` """ addr = 0 res = []