Added control and moved folder

Added control for empty passed argument and moved windows folder out for easier importing.
This commit is contained in:
naksyn
2023-12-16 13:03:40 +01:00
parent c676f20717
commit 9bb2f6761d
160 changed files with 9 additions and 4 deletions
+9 -4
View File
@@ -563,8 +563,11 @@ class MemoryModule(pe.PE):
self.pythonmemorymodule.contents.initialized = 1
def load_module(self):
if self.new_command:
if self.new_command != None and len(self.new_command) != 0:
passed_args=True
self.cmdline_check()
else:
passed_args=False
if not self.is_exe() and not self.is_dll():
raise WindowsError('The specified module does not appear to be an exe nor a dll.')
@@ -639,15 +642,17 @@ class MemoryModule(pe.PE):
self.finalize_sections()
self.dbg('Executing TLS.')
self.ExecuteTLS()
self.dbg('Stomping PEB')
self.stomp_PEB()
if passed_args:
self.dbg('Stomping PEB')
self.stomp_PEB()
self.dbg('Starting new thread to execute PE')
my_thread = threading.Thread(target=self.execPE)
my_thread.start()
self.unstomp_PEB()
if passed_args:
self.unstomp_PEB()
def IMAGE_FIRST_SECTION(self):
-46
View File
@@ -1,46 +0,0 @@
"""
Python for Windows
A lot of python object to help navigate windows stuff
Exported:
system : :class:`windows.winobject.System`
current_process : :class:`windows.winobject.CurrentProcess`
current_thread : :class:`windows.winobject.CurrentThread`
"""
# check we are on windows
import sys
if sys.platform != "win32":
raise NotImplementedError("It's called PythonForWindows not PythonFor{0}".format(sys.platform.capitalize()))
import warnings
warnings.filterwarnings('once', category=DeprecationWarning, module=__name__)
from windows import winproxy
from windows import winobject
from .winobject.system import System
from .winobject.process import CurrentProcess, CurrentThread, WinProcess, WinThread
from .winobject.file import WinFile
system = System()
current_process = CurrentProcess()
current_thread = CurrentThread()
del System
del CurrentProcess
del CurrentThread
# Late import: other imports should go here
# Do not move it: risk of circular import
import windows.utils
import windows.wintrust
import windows.syswow64
import windows.com
__all__ = ["system", 'current_process', 'current_thread']
-541
View File
@@ -1,541 +0,0 @@
import sys
import ctypes
from collections import namedtuple
import windows
from windows import winproxy
from windows import generated_def as gdef
import windows.pycompat
## For 64b python
# 0x1f: 0x80000000: ALPC_MESSAGE_SECURITY_ATTRIBUTE(0x80000000) : size=0x18?
# 0x1e: 0x40000000: ALPC_MESSAGE_VIEW_ATTRIBUTE(0x40000000): size=0x20
# 0x1d: 0x20000000: ALPC_MESSAGE_CONTEXT_ATTRIBUTE(0x20000000): size=0x20
# 0x1c: 0x10000000: ALPC_MESSAGE_HANDLE_ATTRIBUTE(0x10000000): size=0x18
# 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
DEFAULT_MESSAGE_SIZE = 0x1000
class AlpcMessage(object):
"""Represent a full ALPC Message: a :class:`AlpcMessagePort` and a :class:`MessageAttribute`"""
# PORT_MESSAGE + MessageAttribute
def __init__(self, msg_or_size=DEFAULT_MESSAGE_SIZE, attributes=None):
# Init the PORT_MESSAGE
if isinstance(msg_or_size, windows.pycompat.int_types):
self.port_message_buffer_size = msg_or_size
self.port_message_raw_buffer = ctypes.c_buffer(msg_or_size)
self.port_message = AlpcMessagePort.from_buffer(self.port_message_raw_buffer)
self.port_message.set_datalen(0)
elif isinstance(msg_or_size, AlpcMessagePort):
self.port_message = msg_or_size
self.port_message_raw_buffer = self.port_message.raw_buffer
self.port_message_buffer_size = len(self.port_message_raw_buffer)
else:
raise NotImplementedError("Uneexpected type for <msg_or_size>: {0}".format(msg_or_size))
# Init the MessageAttributes
if attributes is None:
# self.attributes = MessageAttribute.with_all_attributes()
self.attributes = MessageAttribute.with_all_attributes() ## Testing
else:
self.attributes = attributes
# 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):
return self.port_message.data
def set_port_message_data(self, data):
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)
def setup_view(self, size, section_handle=0, flags=None):
raise NotImplementedError(self.setup_view)
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):
# A sort of super(AlpcMessagePort).from_buffer
# But from_buffer is from the Metaclass of AlpcMessagePort so we use 'type(AlpcMessagePort)'
# To access the standard version of from_buffer.
self = type(AlpcMessagePort).from_buffer(AlpcMessagePort, buffer)
self.buffer_size = len(buffer)
self.raw_buffer = buffer
self.header_size = ctypes.sizeof(self)
self.max_datasize = self.buffer_size - self.header_size
return self
@classmethod
def from_buffer_size(cls, buffer_size):
buffer = ctypes.c_buffer(buffer_size)
return cls.from_buffer(buffer)
def read_data(self):
return self.raw_buffer[ctypes.sizeof(self):ctypes.sizeof(self) + self.u1.s1.DataLength]
def write_data(self, data):
if len(data) > self.max_datasize:
import pdb; pdb.set_trace()
raise ValueError("Cannot write data of len <{0}> (raw_buffer size == <{1}>)".format(len(data), self.buffer_size))
self.raw_buffer[self.header_size: self.header_size + len(data)] = data
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
self.u1.s1.DataLength = datalen
def get_datalen(self):
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,
gdef.ALPC_MESSAGE_CONTEXT_ATTRIBUTE,
gdef.ALPC_MESSAGE_HANDLE_ATTRIBUTE,
gdef.ALPC_MESSAGE_TOKEN_ATTRIBUTE,
gdef.ALPC_MESSAGE_DIRECT_ATTRIBUTE,
gdef.ALPC_MESSAGE_WORK_ON_BEHALF_ATTRIBUTE)
KNOWN_ALPC_ATTRIBUTES_MAPPING = gdef.FlagMapper(*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),
(gdef.ALPC_MESSAGE_HANDLE_ATTRIBUTE, gdef.ALPC_HANDLE_ATTR),
(gdef.ALPC_MESSAGE_TOKEN_ATTRIBUTE, gdef.ALPC_TOKEN_ATTR),
(gdef.ALPC_MESSAGE_DIRECT_ATTRIBUTE, gdef.ALPC_DIRECT_ATTR),
(gdef.ALPC_MESSAGE_WORK_ON_BEHALF_ATTRIBUTE, gdef.ALPC_WORK_ON_BEHALF_ATTR),
]
@classmethod
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(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 |
gdef.ALPC_MESSAGE_HANDLE_ATTRIBUTE |
gdef.ALPC_MESSAGE_TOKEN_ATTRIBUTE |
gdef.ALPC_MESSAGE_DIRECT_ATTRIBUTE |
gdef.ALPC_MESSAGE_WORK_ON_BEHALF_ATTRIBUTE)
@staticmethod
def _get_required_buffer_size(flags):
res = gdef.DWORD()
try:
windows.winproxy.AlpcInitializeMessageAttribute(flags, None, 0, res)
except windows.generated_def.ntstatus.NtStatusException as e:
# Buffer too small: osef
return res.value
return res.value
def is_allocated(self, attribute):
"""Return ``True`` if ``attribute`` is allocated"""
return bool(self.AllocatedAttributes & attribute)
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):
raise ValueError("Cannot get non-allocated attribute <{0}>".format(attribute))
offset = ctypes.sizeof(self)
for sflag, struct in self.ATTRIBUTE_BY_FLAG:
if sflag == attribute:
# print("Attr {0:#x} was at offet {1:#x}".format(attribute, offset))
return struct.from_address(ctypes.addressof(self) + offset)
elif self.is_allocated(sflag):
offset += ctypes.sizeof(struct)
raise ValueError("ALPC Attribute <{0}> not found :(".format(attribute))
def _extract_alpc_attributes_values(self, value):
attrs = []
for mask in (1 << i for i in range(64)):
if value & mask:
attrs.append(mask)
return [KNOWN_ALPC_ATTRIBUTES_MAPPING[x] for x in attrs]
@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)
AlpcSection = namedtuple("AlpcSection", ["handle", "size"])
class AlpcTransportBase(object):
def send_receive(self, alpc_message, receive_msg=None, flags=gdef.ALPC_MSGFLG_SYNC_REQUEST, timeout=None):
"""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, windows.pycompat.anybuff):
raw_alpc_message = alpc_message
alpc_message = AlpcMessage(max(0x1000, len(alpc_message) + 0x200))
alpc_message.port_message.data = raw_alpc_message
if receive_msg is None:
receive_msg = AlpcMessage(DEFAULT_MESSAGE_SIZE)
receive_size = gdef.SIZE_T(receive_msg.port_message_buffer_size)
winproxy.NtAlpcSendWaitReceivePort(self.handle, flags, alpc_message.port_message, alpc_message.attributes, receive_msg.port_message, receive_size, receive_msg.attributes, timeout)
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, windows.pycompat.anybuff):
raw_alpc_message = alpc_message
alpc_message = AlpcMessage(max(0x1000, len(alpc_message) + 0x200))
alpc_message.port_message.data = raw_alpc_message
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(DEFAULT_MESSAGE_SIZE)
receive_size = gdef.SIZE_T(receive_msg.port_message_buffer_size)
winproxy.NtAlpcSendWaitReceivePort(self.handle, flags, None, None, receive_msg.port_message, receive_size, receive_msg.attributes, None)
return receive_msg
def _close_port(self, port_handle):
windows.winproxy.NtAlpcDisconnectPort(port_handle, 0)
windows.winproxy.CloseHandle(port_handle)
class AlpcClient(AlpcTransportBase):
"An ALPC client able to connect to a port and send/receive messages"
def __init__(self, port_name=None):
"""Init the :class:`AlpcClient` automatically connect to ``port_name`` using default values if given"""
self.handle = None
self.port_name = None #: The name of the ALPC port the client is connect to.
if port_name is not None:
x = self.connect_to_port(port_name, "")
def _alpc_port_to_unicode_string(self, name):
return gdef.UNICODE_STRING.from_string(name)
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")
handle = gdef.HANDLE()
port_name_unicode = self._alpc_port_to_unicode_string(port_name)
if port_attr is None:
port_attr = gdef.ALPC_PORT_ATTRIBUTES()
port_attr.Flags = port_attr_flags # Flag qui fonctionne pour l'UAC
port_attr.MaxMessageLength = DEFAULT_MESSAGE_SIZE
port_attr.MemoryBandwidth = 0
port_attr.MaxPoolUsage = 0xffffffff
port_attr.MaxSectionSize = 0xffffffff
port_attr.MaxViewSize = 0xffffffff
port_attr.MaxTotalSectionSize = 0xffffffff
port_attr.DupObjectTypes = 0xffffffff
port_attr.SecurityQos.Length = ctypes.sizeof(port_attr.SecurityQos)
port_attr.SecurityQos.ImpersonationLevel = gdef.SecurityImpersonation
port_attr.SecurityQos.ContextTrackingMode = 0
port_attr.SecurityQos.EffectiveOnly = 0
if connect_message is None:
send_msg = None
send_msg_attr = None
buffersize = None
elif isinstance(connect_message, windows.pycompat.anybuff):
buffersize = gdef.DWORD(len(connect_message) + 0x1000)
send_msg = AlpcMessagePort.from_buffer_size(buffersize.value)
send_msg.data = connect_message
send_msg_attr = MessageAttribute.with_all_attributes()
elif isinstance(connect_message, AlpcMessage):
send_msg = connect_message.port_message
send_msg_attr = connect_message.attributes
buffersize = gdef.DWORD(connect_message.port_message_buffer_size)
else:
raise ValueError("Don't know how to send <{0!r}> as connect message".format(connect_message))
receive_attr = MessageAttribute.with_all_attributes()
winproxy.NtAlpcConnectPort(handle, port_name_unicode, obj_attr, port_attr, flags, None, send_msg, buffersize, send_msg_attr, receive_attr, timeout)
# If send_msg is not None, it contains the ClientId.UniqueProcess : PID of the server :)
self.handle = handle.value
self.port_name = port_name
return AlpcMessage(send_msg, receive_attr) if send_msg is not None else None
def create_port_section(self, Flags, SectionHandle, SectionSize):
AlpcSectionHandle = gdef.HANDLE()
ActualSectionSize = gdef.SIZE_T()
# RPCRT4 USE FLAGS 0x40000 ALPC_VIEWFLG_NOT_SECURE ?
winproxy.NtAlpcCreatePortSection(self.handle, Flags, SectionHandle, SectionSize, AlpcSectionHandle, ActualSectionSize)
return AlpcSection(AlpcSectionHandle.value, ActualSectionSize.value)
def map_section(self, section_handle, size, flags=0):
view_attributes = gdef.ALPC_DATA_VIEW_ATTR()
view_attributes.Flags = 0
view_attributes.SectionHandle = section_handle
view_attributes.ViewBase = 0
view_attributes.ViewSize = size
r = winproxy.NtAlpcCreateSectionView(self.handle, flags, view_attributes)
return view_attributes
def disconnect(self):
if self.handle:
self._close_port(self.handle)
def __del__(self):
if sys.path is not None:
self.disconnect()
class AlpcServer(AlpcTransportBase):
"""An ALPC server able to create a port, accept connections and send/receive messages"""
def __init__(self, port_name=None):
self.port_name = None
self.communication_port_list = []
self.handle = None
if port_name is not None:
self.create_port(port_name)
def _alpc_port_to_unicode_string(self, name):
return gdef.UNICODE_STRING.from_string(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
if not raw_name.startswith("\\"):
raw_name = "\\" + port_name
port_name = self._alpc_port_to_unicode_string(raw_name)
if msglen is None:
msglen = DEFAULT_MESSAGE_SIZE
if obj_attr is None:
obj_attr = gdef.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
if port_attr is None:
port_attr = gdef.ALPC_PORT_ATTRIBUTES()
port_attr.Flags = port_attr_flags
# port_attr.Flags = 0x2080000
# port_attr.Flags = 0x90000
port_attr.MaxMessageLength = msglen
port_attr.MemoryBandwidth = 0
port_attr.MaxPoolUsage = 0xffffffff
port_attr.MaxSectionSize = 0xffffffff
port_attr.MaxViewSize = 0xffffffff
port_attr.MaxTotalSectionSize = 0xffffffff
port_attr.DupObjectTypes = 0xffffffff
# windows.utils.print_ctypes_struct(port_attr, " - PORT_ATTR", hexa=True)
winproxy.NtAlpcCreatePort(handle, obj_attr, port_attr)
self.port_name = raw_name
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:
port_attr = gdef.ALPC_PORT_ATTRIBUTES()
port_attr.Flags = 0x80000
# port_attr.Flags = 0x80000 + 0x2000000
# port_attr.Flags = 0x2000000
port_attr.MaxMessageLength = DEFAULT_MESSAGE_SIZE
port_attr.MemoryBandwidth = 0
port_attr.MaxPoolUsage = 0xffffffff
port_attr.MaxSectionSize = 0xffffffff
port_attr.MaxViewSize = 0xffffffff
port_attr.MaxTotalSectionSize = 0xffffffff
port_attr.DupObjectTypes = 0xffffffff
# windows.utils.print_ctypes_struct(port_attr, " - CONN_PORT_ATTR", hexa=True)
winproxy.NtAlpcAcceptConnectPort(rhandle, self.handle, 0, None, port_attr, port_context, msg.port_message, None, True)
self.communication_port_list.append(rhandle.value)
return msg
def disconnect(self):
if self.handle:
self._close_port(self.handle)
for com_port_handle in self.communication_port_list:
self._close_port(com_port_handle)
# TODO: add an API to close a communication port ?
def __del__(self):
if sys.path is not None:
self.disconnect()
-355
View File
@@ -1,355 +0,0 @@
import sys
import struct
import ctypes
import functools
from ctypes import HRESULT, byref, cast
import windows
from windows import winproxy
from windows.generated_def.winstructs import *
import windows.generated_def as gdef
from windows.generated_def import RPC_C_IMP_LEVEL_IMPERSONATE, CLSCTX_INPROC_SERVER
from windows.generated_def import interfaces
from windows.generated_def.interfaces import generate_IID, IID
from windows.pycompat import int_types, basestring
# We have windows.com.COMImplementation
# So we need windows.com.COMInterface
COMInterface = interfaces.COMInterface
# Simple raw -> UUID
# "-".join("{:02X}".format(c) for c in struct.unpack("<IHHHBBBBBB", x))
def init():
"""Init COM with some default parameters"""
try:
t = winproxy.CoInitializeEx()
except WindowsError as e:
t = e.winerror
if t:
return t & 0xffffffff
return initsecurity()
def initsecurity(): # Should take some parameters..
return winproxy.CoInitializeSecurity(0, -1, None, 0, 0, RPC_C_IMP_LEVEL_IMPERSONATE, 0,0,0)
class Dispatch(interfaces.IDispatch):
def TypeInfoCount(self):
count = gdef.UINT()
self.GetTypeInfoCount(count)
return count
def type_info(self, idx):
type_info = TypeInfo()
self.GetTypeInfo(idx, 0, type_info)
return type_info
class TypeInfo(interfaces.ITypeInfo):
def func(self, idx):
res = gdef.LPFUNCDESC()
self.GetFuncDesc(idx, res)
return res
def attr(self):
res = gdef.LPTYPEATTR()
self.GetTypeAttr(res)
return res
def names(self, memid):
size = gdef.UINT()
x = (gdef.BSTR * 10)(*tuple(gdef.BSTR() for i in range(10)))
self.GetNames(memid, x, 10, size)
return x[:size.value]
def docu(self, id):
res = gdef.BSTR()
self.GetDocumentation(id, res, None, None, None)
return res
def create_instance(clsiid, targetinterface, custom_iid=None, context=CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER):
"""A simple wrapper around ``CoCreateInstance <https://msdn.microsoft.com/en-us/library/windows/desktop/ms686615(v=vs.85).aspx>``"""
if custom_iid is None:
custom_iid = targetinterface.IID
if isinstance(clsiid, basestring):
clsiid = IID.from_string(clsiid)
winproxy.CoCreateInstance(byref(clsiid), None, context, byref(custom_iid), byref(targetinterface))
return targetinterface
def resolve_progid(progid):
clsid = CLSID()
winproxy.CLSIDFromProgID(progid, clsid)
# We just filed the CLSID: refresh the __repr__
clsid.update_strid()
return clsid
# Improved COM object
# Todo: ctypes_generation extended struct ?
class SafeArray(SAFEARRAY):
@classmethod
def of_type(cls, addr, t):
self = cls.from_address(addr)
self.elt_type = t
return self
@classmethod
def from_PSAFEARRAY(self, psafearray):
res = cast(psafearray, POINTER(SafeArray))[0]
return res
def to_list(self, t=None):
if t is None:
if hasattr(self, "elt_type"):
t = self.elt_type
else:
raise ValueError("Missing type of the array")
if self.cDims != 1:
raise NotImplementedError("tagSAFEARRAY if dims != 1")
nb_element = self.rgsabound[0].cElements
llbound = self.rgsabound[0].lLbound
if self.cbElements != ctypes.sizeof(t):
raise ValueError("Size of elements != sizeof(type)")
data = [t.from_address(self.pvData + (i + llbound) * ctypes.sizeof(t)).value for i in range(nb_element)]
return data
#VT_VALUE_TO_TYPE = {
#VT_I2 : SHORT,
#VT_I4 : LONG,
#VT_BSTR : BSTR,
#VT_VARIANT : VARIANT,
#VT_UI1 : UCHAR,
#VT_UI2 : USHORT,
#VT_UI4 : DWORD,
#VT_I8 : LONGLONG,
#VT_UI8 : ULONG64,
#VT_INT : INT,
#VT_UINT : UINT,
#VT_HRESULT : HRESULT,
#VT_PTR : PVOID,
#VT_LPSTR : LPCSTR,
#VT_LPWSTR : LPWSTR,
#}
# VARIANT type checker
# Allow to guess a VARIANT_TYPE og a python value
def never_match(value):
return False
def check_type_null(value):
return value is None
def check_type_i4(value):
# 31 ? as we may want to keep sign :)
return isinstance(value, int_types) and (value).bit_length() <= 32
def check_type_i8(value):
# 63 ? as we may want to keep sign :)
return isinstance(value, int_types) and (value).bit_length() <= 64
def check_type_bstr(value):
return isinstance(value, basestring)
def check_type_bool(value):
return isinstance(value, bool)
def check_type_array(value):
return True
VARIAN_NAME_3_TYPE = [f[1] for f in VARIANT._fields_ if f[0] == "_VARIANT_NAME_3"][0]
empty = object()
class Variant(VARIANT):
def __init__(self, value=empty, type=None):
if type is not None:
self.set_value_and_type(value, type)
return
elif value is empty:
self.vt = VT_EMPTY
return
self.guess_type_and_set_value(value)
# Copy raw-ctypes fields which is a descriptor :)
rawvt = VARIANT.vt
# Most of the value in the colunm[1]
# are attribute of the sub-union _VARIANT_NAME_3
# This union must be ctypes-anonymous for this code to works
# We want to access these directly from the VARIANT
# to allow custom descriptor for complexe type to be referenced here
CHECK_TYPE = [
# Order is important
# as VT_I4 check may match VT_BOOL values
# VT_BOOL check must be before VT_I4 one
(VT_BOOL, "boolVal", check_type_bool),
(VT_I4, "lVal", check_type_i4),
(VT_I8, "llVal", check_type_i8),
(VT_BSTR, "bstrVal", check_type_bstr),
(VT_NULL, None, check_type_null),
(VT_EMPTY, None, never_match),
(VT_DISPATCH, "pdispVal", never_match), # I cannot recognize DISPATCH ptr for now
(VT_UNKNOWN, "punkVal", never_match), # recognise PFW ComInterface ?
# Test: do not allow auto-creation of small int values
# I don't know but a feel it may confuse some API expecting VT_I4
(VT_I2, "iVal", never_match),
(VT_UI1, "bVal", never_match),
]
VARIANT_TYPE_BY_NAME = {f[0]: f[1] for f in VARIAN_NAME_3_TYPE._fields_}
QUICK_CHECK_TYPE = {x: y for x,y, _ in CHECK_TYPE}
def get_vt(self):
rawvt = super(Variant, self).vt
return gdef.VARENUM.mapper[self.rawvt]
def set_vt(self, value):
self.rawvt = value
vt = property(get_vt, set_vt)
def set_value_and_type(self, value, type):
attr = self.QUICK_CHECK_TYPE[type]
# No check: user must be careful about non-match value&type
setattr(self, attr, value)
self.vt = type
def get_value_based_on_type(self):
rawvt = self.rawvt
if rawvt & VT_ARRAY:
realtype = rawvt & ~VT_ARRAY
attr = self.QUICK_CHECK_TYPE[realtype]
attrtype = self.VARIANT_TYPE_BY_NAME[attr]
array = SafeArray.from_PSAFEARRAY(self._VARIANT_NAME_3.parray)
return array.to_list(attrtype)
attr = self.QUICK_CHECK_TYPE[rawvt]
if attr is None:
return None
if attr == "punkVal":
# Quick hack for COM interface type
# Do something clean with CHECK_TYPE ?
x = gdef.IUnknown(self.punkVal)
x.AddRef()
return x
return getattr(self, attr)
def guess_type_and_set_value(self, value):
for t, attr, check in self.CHECK_TYPE:
try:
checkres = check(value)
except TypeError as e:
continue
if checkres:
self.vt = t
if attr is not None:
setattr(self, attr, value)
return True
raise ValueError("Could not guess VT_TYPE for <{0}> of type <{1}>".format(value, type(value)))
value = property(get_value_based_on_type, guess_type_and_set_value)
# quick_check: bypass python lookup-limitation
def generate_getter(vt_type, transfo=(lambda x:x), quick_check=QUICK_CHECK_TYPE):
attr = quick_check[vt_type]
@property
def getter(self):
if not self.rawvt == vt_type:
raise ValueError("Invalid vt-type for attribute expected <{0}> got <{1}>".format(vt_type, self.vt))
return transfo(getattr(self, attr))
return getter
asbstr = generate_getter(VT_BSTR)
aslong = generate_getter(VT_I4)
asbool = generate_getter(VT_BOOL)
asdispatch = generate_getter(VT_DISPATCH, transfo=interfaces.IDispatch)
asshort = generate_getter(VT_I2)
asbyte = generate_getter(VT_UI1)
asunknown = generate_getter(VT_UNKNOWN)
def __repr__(self):
return """<{0} of type {1}>""".format(type(self).__name__, self.vt)
# Deprecated: remove me when test pass :)
# ImprovedVariant.MAPPER = {
# VT_UI1: ImprovedVariant.asbyte.fget,
# VT_I2: ImprovedVariant.asshort.fget,
# VT_DISPATCH: ImprovedVariant.asdispatch.fget,
# VT_BOOL: ImprovedVariant.asbool.fget,
# VT_I4: ImprovedVariant.aslong.fget,
# VT_BSTR: ImprovedVariant.asbstr.fget,
# VT_EMPTY: (lambda x: None),
# VT_NULL: (lambda x: None),
# VT_UNKNOWN: ImprovedVariant.asunknown.fget,
# (VT_ARRAY | VT_BSTR): ImprovedVariant.asbstr_array.fget,
# (VT_ARRAY | VT_I4): ImprovedVariant.aslong_array.fget,
# (VT_ARRAY | VT_UI1): ImprovedVariant.asbyte_array.fget,
# (VT_ARRAY | VT_BOOL): ImprovedVariant.asbool_array.fget
# }
class COMImplementation(object):
"""The base class to implements COM object respecting a given interface"""
IMPLEMENT = None
def get_index_of_method(self, method):
# This code is horrible but not totally my fault
# the PyCFuncPtrObject->index is not exposed to Python..
# repr is: '<COM method offset 2: WinFunctionType at 0x035DDBE8>'
rpr = repr(method)
if not rpr.startswith("<COM method offset ") or ":" not in rpr:
raise ValueError("Could not extract offset of {0}".format(rpr))
return int(rpr[len("<COM method offset "): rpr.index(":")])
def extract_methods_order(self, interface):
index_and_method = sorted((self.get_index_of_method(m),name, m) for name, m in interface._functions_.items())
return index_and_method
def verify_implem(self, interface):
for func_name in interface._functions_:
implem = getattr(self, func_name, None)
if implem is None:
raise ValueError("<{0}> implementing <{1}> has no method <{2}>".format(type(self).__name__, self.IMPLEMENT.__name__, func_name))
if not callable(implem):
raise ValueError("{0} implementing <{1}>: <{2}> is not callable".format(type(self).__name__, self.IMPLEMENT.__name__, func_name))
return True
def _create_vtable(self, interface):
implems = []
names = []
for index, name, method in self.extract_methods_order(interface):
func_implem = getattr(self, name)
#'this' is a COM-interface of the type we are implementing
types = [method.restype, interface] + list(method.argtypes)
implems.append(ctypes.WINFUNCTYPE(*types)(func_implem))
names.append(name)
class Vtable(ctypes.Structure):
_fields_ = [(name, ctypes.c_void_p) for name in names]
return Vtable(*[ctypes.cast(x, ctypes.c_void_p) for x in implems]), implems
def __init__(self):
self.verify_implem(self.IMPLEMENT)
vtable, implems = self._create_vtable(self.IMPLEMENT)
self.vtable = vtable
self.implems = implems
self.vtable_pointer = ctypes.pointer(self.vtable)
self._as_parameter_ = ctypes.addressof(self.vtable_pointer)
def QueryInterface(self, this, piid, result):
"""Default ``QueryInterface`` implementation that returns ``self`` if piid is the implemented interface"""
if piid[0] in (gdef.IUnknown.IID, self.IMPLEMENT.IID):
result[0] = this
return 1
return E_NOINTERFACE
def AddRef(self, *args):
"""Default ``AddRef`` implementation that returns ``1``"""
return 1
def Release(self, *args):
"""Default ``Release`` implementation that returns ``1``"""
return 0
@@ -1,9 +0,0 @@
from windows.generated_def import X509_ASN_ENCODING, PKCS_7_ASN_ENCODING
DEFAULT_ENCODING = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING
# Keep other imports here so sub-crypto file can import windows.crypto.DEFAULT_ENCODING
from windows.crypto.certificate import *
from windows.crypto.encrypt_decrypt import *
from windows.crypto.sign_verify import *
from windows.crypto.dpapi import *
from windows.crypto.cryptmsg import CryptMessage
@@ -1,4 +0,0 @@
from windows import winproxy
import windows.generated_def as gdef
import windows.crypto
@@ -1,585 +0,0 @@
import itertools
import ctypes
import windows
from windows import winproxy
import windows.generated_def as gdef
from windows.crypto import DEFAULT_ENCODING
import windows.crypto.cryptmsg
CRYPT_OBJECT_FORMAT_TYPE = [
gdef.CERT_QUERY_OBJECT_FILE,
gdef.CERT_QUERY_OBJECT_BLOB,
gdef.CERT_QUERY_CONTENT_CERT,
gdef.CERT_QUERY_CONTENT_CTL,
gdef.CERT_QUERY_CONTENT_CRL,
gdef.CERT_QUERY_CONTENT_SERIALIZED_STORE,
gdef.CERT_QUERY_CONTENT_SERIALIZED_CERT,
gdef.CERT_QUERY_CONTENT_SERIALIZED_CTL,
gdef.CERT_QUERY_CONTENT_SERIALIZED_CRL,
gdef.CERT_QUERY_CONTENT_PKCS7_SIGNED,
gdef.CERT_QUERY_CONTENT_PKCS7_UNSIGNED,
gdef.CERT_QUERY_CONTENT_PKCS7_SIGNED_EMBED,
gdef.CERT_QUERY_CONTENT_PKCS10,
gdef.CERT_QUERY_CONTENT_PFX,
gdef.CERT_QUERY_CONTENT_CERT_PAIR,
gdef.CERT_QUERY_CONTENT_PFX_AND_LOAD
]
CRYPT_OBJECT_FORMAT_TYPE_DICT = gdef.FlagMapper(*CRYPT_OBJECT_FORMAT_TYPE)
## Move CryptObject to new .py ?
class CryptObject(object):
"""Extract information from an CryptoAPI object.
(see `CryptQueryObject <https://msdn.microsoft.com/en-us/library/windows/desktop/aa380264(v=vs.85).aspx>`_)
Current main use is extracting the signers certificates from a PE file.
"""
MSG_PARAM_KNOW_TYPES = {gdef.CMSG_SIGNER_INFO_PARAM: gdef.CMSG_SIGNER_INFO,
gdef.CMSG_SIGNER_COUNT_PARAM: gdef.DWORD,
gdef.CMSG_CERT_COUNT_PARAM: gdef.DWORD}
def __init__(self, filename, content_type=gdef.CERT_QUERY_CONTENT_FLAG_ALL):
# No other API than filename for now..
self.filename = filename
dwEncoding = gdef.DWORD()
dwContentType = gdef.DWORD()
dwFormatType = gdef.DWORD()
hStore = CertificateStore()
hMsg = windows.crypto.CryptMessage()
winproxy.CryptQueryObject(gdef.CERT_QUERY_OBJECT_FILE,
gdef.LPWSTR(filename),
# filename,
content_type,
gdef.CERT_QUERY_FORMAT_FLAG_BINARY,
0,
dwEncoding,
dwContentType,
dwFormatType,
hStore,
hMsg,
None)
self.cert_store = hStore if hStore else None
"""The :class:`CertificateStore` that includes all of the certificates, CRLs, and CTLs in the object"""
self.crypt_msg = hMsg if hMsg else None #: yolo
"""The :class:`CryptMessage` for any ``PKCS7`` content in the object"""
self.encoding = dwEncoding
self.content_type = CRYPT_OBJECT_FORMAT_TYPE_DICT[dwContentType.value]
"""The type of the opened message"""
def _signers_and_certs_generator(self):
if self.crypt_msg is None:
return
for signer in self.crypt_msg.signers:
# We could directly extract the certificates from the 'crypt_msg' (I guess)
# But 'CryptQueryObject' had the sympathy of already opening a CertificateStore
# for us. So we use it.
# I am open to counter-argument on this methodology.
cert = self.cert_store.find(signer.Issuer, signer.SerialNumber)
yield signer, cert
@property
def signers_and_certs(self):
"""The list of signer info and certificates signing the object.
:rtype: [(:class:`~windows.generated_def.winstructs.CMSG_SIGNER_INFO`, :class:`Certificate`)]
.. note::
:class:`~windows.generated_def.winstructs.CMSG_SIGNER_INFO` might be changed to a wrapping-subclass.
"""
return list(self._signers_and_certs_generator())
def __repr__(self):
return '<{0} "{1}" content_type={2!r}>'.format(type(self).__name__, self.filename, self.content_type)
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa382037(v=vs.85).aspx
class CertificateStore(gdef.HCERTSTORE):
"""A certificate store"""
@property
def certs(self):
"""The list of certificates in the store
:type: [:class:`Certificate`] -- A list of certificate
"""
res = []
last = None
while True:
try:
cert = winproxy.CertEnumCertificatesInStore(self, last)
except winproxy.WinproxyError as e:
if (e.winerror & 0xffffffff) in (gdef.CRYPT_E_NOT_FOUND,):
return tuple(res)
raise
# Need to duplicate as CertEnumCertificatesInStore will free the context 'last'
ecert = windows.crypto.Certificate.from_pointer(cert)
res.append(ecert.duplicate())
last = ecert
raise RuntimeError("Out of infinit loop")
def add_certificate(self, certificate):
"""Add a certificate to the store"""
winproxy.CertAddCertificateContextToStore(self, certificate, gdef.CERT_STORE_ADD_NEW, None)
@classmethod
def from_file(cls, filename):
"""Create a new :class:`CertificateStore` from ``filename``"""
res = winproxy.CertOpenStore(gdef.CERT_STORE_PROV_FILENAME_A, DEFAULT_ENCODING, None, gdef.CERT_STORE_OPEN_EXISTING_FLAG, filename)
return ctypes.cast(res, cls)
def _yolo(self):
x = winproxy.CertEnumCTLsInStore(self, None)
title = None
windows.winproxy.CryptUIDlgViewContext(gdef.CERT_STORE_CTL_CONTEXT, x, None, title, 0, None)
return x
# See https://msdn.microsoft.com/en-us/library/windows/desktop/aa388136(v=vs.85).aspx
@classmethod
def from_system_store(cls, store_name):
"""Create a new :class:`CertificateStore` from system store ``store_name``
(see `System Store Locations <https://msdn.microsoft.com/en-us/library/windows/desktop/aa388136(v=vs.85).aspx>`_)
"""
res = winproxy.CertOpenStore(gdef.CERT_STORE_PROV_SYSTEM_A, DEFAULT_ENCODING, None, gdef.CERT_SYSTEM_STORE_LOCAL_MACHINE | gdef.CERT_STORE_READONLY_FLAG, store_name)
return ctypes.cast(res, cls)
@classmethod
def new_in_memory(cls):
"""Create a new temporary :class:`CertificateStore` in memory"""
res = winproxy.CertOpenStore(gdef.CERT_STORE_PROV_MEMORY, DEFAULT_ENCODING, None, 0, None)
return ctypes.cast(res, cls)
# TODO: a more complete search API ?
def find(self, issuer, serialnumber):
"""Return the certificate that match `issuer` and `serialnumber`
:return: :class:`Certificate` -- ``None`` if certificate is not found
"""
# data = self.get_signer_data(index)
cert_info = gdef.CERT_INFO()
cert_info.Issuer = issuer
cert_info.SerialNumber = serialnumber
try:
rawcertcontext = winproxy.CertFindCertificateInStore(self, DEFAULT_ENCODING, 0, gdef.CERT_FIND_SUBJECT_CERT, ctypes.byref(cert_info), None)
except WindowsError as e:
if not e.winerror & 0xffffffff == gdef.CRYPT_E_NOT_FOUND:
raise
return None
return Certificate.from_pointer(rawcertcontext)
def __del__(self):
return winproxy.CertCloseStore(self, 0)
# PKCS12_NO_PERSIST_KEY -> do not save it in a key container on disk
# Without it, a key container is created at 'C:\Users\USERNAME\AppData\Roaming\Microsoft\Crypto\RSA\S-1-5-21-3241049326-165485355-1070449050-1001'
# More about this:
# If you use 'PKCS12_NO_PERSIST_KEY' the key are indeed NOT STORED but there is a problem
# If you use an algo like 'szOID_NIST_AES256_CBC' the function 'CryptDecryptMessage' won't be able to decrypt the message
# Unless you also specify the 'PKCS12_ALWAYS_CNG_KSP' flags.
# My guess: somewhere 'CryptDecryptMessage' ask for each (CNG_KSP | CSP ?) to try to decrypt with the keys
# BUT: as we DID NOT EXPORT the keys, they are not able to get the key from memory and expect them on disk.
# By forcing PKCS12_ALWAYS_CNG_KSP we remove this as the key are directly linked to the correct CNG_KSP in the CertStore
# Look like it's based on this part of the PFX:
# Microsoft CSP Name: Microsoft Enhanced Cryptographic Provider v1.0
# BUT this will not allow to decrypt RSA_RC4 ?
def import_pfx(pfx, password=None, flags=gdef.CRYPT_USER_KEYSET | gdef.PKCS12_NO_PERSIST_KEY | gdef.PKCS12_ALWAYS_CNG_KSP):
"""Import the file ``pfx`` with the ``password``.
``default flags = PKCS12_NO_PERSIST_KEY | CRYPT_USER_KEYSET``.
``PKCS12_NO_PERSIST_KEY`` tells ``CryptoAPI`` to NOT save the keys in a on-disk container.
:return: :class:`CertificateStore`
"""
if isinstance(pfx, windows.pycompat.anybuff) or isinstance(pfx, bytearray):
pfx = gdef.CRYPT_DATA_BLOB.from_string(pfx)
cert_store = winproxy.PFXImportCertStore(pfx, password, flags)
return CertificateStore(cert_store)
class Certificate(gdef.CERT_CONTEXT):
"""Represent a Certificate """
@property
def raw_serial(self):
"""The raw serial number of the certificate.
:type: [:class:`int`]: A list of int ``0 <= x <= 255``"""
serial_number = self.pCertInfo[0].SerialNumber
return [(c & 0xff) for c in serial_number.pbData[:serial_number.cbData][::-1]]
@property
def serial(self):
"""The string representation of the certificate's serial.
:type: :class:`str`
"""
serial_bytes = self.raw_serial
return " ".join("{:02x}".format(x) for x in serial_bytes)
def get_name(self, nametype=gdef.CERT_NAME_SIMPLE_DISPLAY_TYPE, param_type=0, flags=0):
"""Retrieve the subject or issuer name of the certificate.
See `CertGetNameStringA <https://msdn.microsoft.com/en-us/library/windows/desktop/aa376086(v=vs.85).aspx>`_
:returns: :class:`str`
"""
if nametype == gdef.CERT_NAME_RDN_TYPE:
param_type = gdef.DWORD(param_type)
param_type = gdef.LPDWORD(param_type)
size = winproxy.CertGetNameStringA(self, nametype, flags, param_type, None, 0)
namebuff = ctypes.c_buffer(size)
size = winproxy.CertGetNameStringA(self, nametype, flags, param_type, namebuff, size)
return namebuff[:-1]
name = property(get_name)
"""The name of the certificate.
:type: :class:`str`"""
def raw_hash(self):
size = gdef.DWORD(100)
buffer = ctypes.c_buffer(size.value)
winproxy.CryptHashCertificate(None, 0, 0, self.pbCertEncoded, self.cbCertEncoded, ctypes.cast(buffer, gdef.LPBYTE), size)
return buffer[:size.value]
@property
def thumbprint(self):
"""The thumbprint of the certificate (which is the sha1 of the encoded cert).
Example:
>>> x
<Certificate "YOLO2" serial="6f 1d 3e 7d d9 77 59 a9 4c 1c 53 dc 80 db 0c fe">
>>> x.thumbprint
'E2 A2 DB 76 A1 DD 8E 70 0D C6 9F CB 71 CF 29 12 C6 D9 78 97'
:type: :class:`str`
"""
return " ".join("{:02X}".format(x) for x in bytearray(self.raw_hash()))
@property
def distinguished_name(self):
"""The distinguished name (DN) of the certificate.
Example:
>>> x
<Certificate "Microsoft Windows Production PCA 2011" serial="61 07 76 56 00 00 00 00 00 08">
>>> x.distinguished_name
'C=US, S=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Windows Production PCA 2011'
:type: :class:`str`
"""
return self.get_name(gdef.CERT_NAME_RDN_TYPE, gdef.CERT_X500_NAME_STR)
@property
def issuer(self):
"""The name of the certificate's issuer.
:type: :class:`str`"""
return self.get_name(flags=gdef.CERT_NAME_ISSUER_FLAG)
@property
def store(self):
"""The certificate store that contains the certificate
:type: :class:`CertificateStore`
"""
return CertificateStore(self.hCertStore)
def get_raw_certificate_chains(self): # Rename to all_chains ?
chain_context = EPCCERT_CHAIN_CONTEXT()
enhkey_usage = gdef.CERT_ENHKEY_USAGE()
enhkey_usage.cUsageIdentifier = 0
enhkey_usage.rgpszUsageIdentifier = None
cert_usage = gdef.CERT_USAGE_MATCH()
cert_usage.dwType = gdef.USAGE_MATCH_TYPE_AND
cert_usage.Usage = enhkey_usage
chain_para = gdef.CERT_CHAIN_PARA()
chain_para.cbSize = ctypes.sizeof(chain_para)
chain_para.RequestedUsage = cert_usage
winproxy.CertGetCertificateChain(None, self, None, self.hCertStore, ctypes.byref(chain_para), 0, None, ctypes.byref(chain_context))
# Lower chains ?
# winproxy.CertGetCertificateChain(None, self, None, self[0].hCertStore, ctypes.byref(chain_para), 0x80, None, ctypes.byref(chain_context))
#return CertficateChain(chain_context)
return chain_context
@property # fixedproperty ?
def chains(self):
"""The list of chain context available for this certificate. Each elements of this list is a list of ``Certificate`` that should
go from the ``self`` certificate to a trusted certificate.
:type: [[:class:`Certificate`]] -- A list of chain (list) of :class:`Certificate`
"""
chain_context = self.get_raw_certificate_chains()
res = []
for chain in chain_context.chains:
chain_res = [elt.cert for elt in chain.elements]
res.append(chain_res)
return res
# API Arround CertSelectCertificateChains ?
# https://msdn.microsoft.com/en-us/library/windows/desktop/dd433797(v=vs.85).aspx
def duplicate(self):
"""Duplicate the certificate by incrementing the internal refcount. (see `CertDuplicateCertificateContext <https://msdn.microsoft.com/en-us/library/windows/desktop/aa376045(v=vs.85).aspx>`_)
note: The object returned is ``self``
:return: :class:`Certificate`
"""
res = winproxy.CertDuplicateCertificateContext(self)
# Check what the doc says: the pointer returned is actually the PCERT in parameter
# Only the refcount is incremented
# This postulate allow us to return 'self' directly
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa376045(v=vs.85).aspx
if not ctypes.addressof(res[0]) == ctypes.addressof(self):
raise ValueError("CertDuplicateCertificateContext did not returned the argument (check doc)")
return self
def view(self, title=None):
return windows.winproxy.CryptUIDlgViewContext(gdef.CERT_STORE_CERTIFICATE_CONTEXT, ctypes.byref(self), None, title, 0, None)
KNOWN_PROPERTIES_VALUES = gdef.FlagMapper(
gdef.CERT_KEY_PROV_HANDLE_PROP_ID,
gdef.CERT_KEY_PROV_INFO_PROP_ID,
gdef.CERT_SHA1_HASH_PROP_ID,
gdef.CERT_MD5_HASH_PROP_ID,
gdef.CERT_HASH_PROP_ID,
gdef.CERT_KEY_CONTEXT_PROP_ID,
gdef.CERT_KEY_SPEC_PROP_ID,
gdef.CERT_IE30_RESERVED_PROP_ID,
gdef.CERT_PUBKEY_HASH_RESERVED_PROP_ID,
gdef.CERT_ENHKEY_USAGE_PROP_ID,
gdef.CERT_CTL_USAGE_PROP_ID,
gdef.CERT_NEXT_UPDATE_LOCATION_PROP_ID,
gdef.CERT_FRIENDLY_NAME_PROP_ID,
gdef.CERT_PVK_FILE_PROP_ID,
gdef.CERT_DESCRIPTION_PROP_ID,
gdef.CERT_ACCESS_STATE_PROP_ID,
gdef.CERT_SIGNATURE_HASH_PROP_ID,
gdef.CERT_SMART_CARD_DATA_PROP_ID,
gdef.CERT_EFS_PROP_ID,
gdef.CERT_FORTEZZA_DATA_PROP_ID,
gdef.CERT_ARCHIVED_PROP_ID,
gdef.CERT_KEY_IDENTIFIER_PROP_ID,
gdef.CERT_AUTO_ENROLL_PROP_ID,
gdef.CERT_PUBKEY_ALG_PARA_PROP_ID,
gdef.CERT_CROSS_CERT_DIST_POINTS_PROP_ID,
gdef.CERT_ISSUER_PUBLIC_KEY_MD5_HASH_PROP_ID,
gdef.CERT_SUBJECT_PUBLIC_KEY_MD5_HASH_PROP_ID,
gdef.CERT_ENROLLMENT_PROP_ID,
gdef.CERT_DATE_STAMP_PROP_ID,
gdef.CERT_ISSUER_SERIAL_NUMBER_MD5_HASH_PROP_ID,
gdef.CERT_SUBJECT_NAME_MD5_HASH_PROP_ID,
gdef.CERT_EXTENDED_ERROR_INFO_PROP_ID,
gdef.CERT_RENEWAL_PROP_ID,
gdef.CERT_ARCHIVED_KEY_HASH_PROP_ID,
gdef.CERT_AUTO_ENROLL_RETRY_PROP_ID,
gdef.CERT_AIA_URL_RETRIEVED_PROP_ID,
gdef.CERT_AUTHORITY_INFO_ACCESS_PROP_ID,
gdef.CERT_BACKED_UP_PROP_ID,
gdef.CERT_OCSP_RESPONSE_PROP_ID,
gdef.CERT_REQUEST_ORIGINATOR_PROP_ID,
gdef.CERT_SOURCE_LOCATION_PROP_ID)
def enum_properties(self):
prop = 0
res = []
while True:
prop = winproxy.CertEnumCertificateContextProperties(self, prop)
if not prop:
return res
res.append(self.KNOWN_PROPERTIES_VALUES[prop])
raise RuntimeError("Unreachable code")
properties = property(enum_properties)
"""The properties of the certificate
:type: [:class:`int` or :class:`~windows.generated_def.Flag`] -- A list of property ID
"""
#def get_property(self):
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa376079(v=vs.85).aspx
# - Usefull:
# CERT_SHA1_HASH_PROP_ID
def get_property(self, prop):
"TODO: DOC :D + auto-type ?"
datasize = gdef.DWORD()
windows.winproxy.CertGetCertificateContextProperty(self, prop, None, datasize)
buf = (gdef.BYTE * datasize.value)()
windows.winproxy.CertGetCertificateContextProperty(self, prop, buf, datasize)
return bytearray(buf)
@property
def encoded(self):
"""The encoded certificate.
:type: :class:`bytearray`"""
return bytearray(self.pbCertEncoded[:self.cbCertEncoded])
@property
def version(self):
"""The version number of the certificate
:type: :class:`int`
"""
return self.pCertInfo[0].dwVersion
@classmethod
def from_file(cls, filename):
"""Create a :class:`Certificate` from the file ``filename``
:return: :class:`Certificate`
"""
with open(filename, "rb") as f:
data = f.read()
buf = (ctypes.c_ubyte * len(data))(*bytearray(data))
pcert = windows.winproxy.CertCreateCertificateContext(windows.crypto.DEFAULT_ENCODING, buf, len(data))
return cls.from_pointer(pcert)
@classmethod
def from_buffer(cls, data):
"""Create a :class:`Certificate` from the buffer ``data``
:return: :class:`Certificate`
"""
buf = (ctypes.c_ubyte * len(data))(*bytearray(data))
pcert = windows.winproxy.CertCreateCertificateContext(windows.crypto.DEFAULT_ENCODING, buf, len(data))
return cls.from_pointer(pcert)
@classmethod
def from_pointer(self, ptr):
return ctypes.cast(ptr, ctypes.POINTER(Certificate))[0]
def __eq__(self, other):
if not isinstance(other, Certificate):
return NotImplemented
return windows.winproxy.CertCompareCertificate(DEFAULT_ENCODING, self.pCertInfo, other.pCertInfo)
def __repr__(self):
return '<{0} "{1}" serial="{2}">'.format(type(self).__name__, self.name, self.serial)
# class CertficateChain(object):
# def __init__(self, pc_chain_context):
# self.chain = pc_chain_context[0]
#
# def to_list(self):
# res = []
# for i in range(self.chain.rgpChain[0][0].cElement):
# res.append(CertificateContext(self.chain.rgpChain[0][0].rgpElement[i][0].pCertContext[0]))
# return res
# Those classes are more of a POC than anything else
# Should be the struct itself (like Certificate ?)
class EPCCERT_CHAIN_CONTEXT(gdef.PCCERT_CHAIN_CONTEXT):
_type_ = gdef.PCCERT_CHAIN_CONTEXT._type_
@property
def chains(self):
res = []
# if (self[0].cLowerQualityChainContext):
# print("LOL")
# import pdb;pdb.set_trace()
# if self[0].cChain > 1:
# print("HAAAAA")
# import pdb;pdb.set_trace()
for i in range(self[0].cChain):
simple_chain = ctypes.cast(self[0].rgpChain[i], EPCCERT_SIMPLE_CHAIN)
res.append(simple_chain)
return res
@property
def all_cert(self):
res = []
for chain in self.chains:
ch = []
res.append(ch)
for element in chain.elements:
ch.append(element.cert)
return res
class EPCCERT_SIMPLE_CHAIN(gdef.PCCERT_SIMPLE_CHAIN):
_type_ = gdef.PCCERT_SIMPLE_CHAIN._type_
@property
def elements(self):
res = []
for i in range(self[0].cElement):
element = ctypes.cast(self[0].rgpElement[i], EPCERT_CHAIN_ELEMENT)
res.append(element)
return res
class EPCERT_CHAIN_ELEMENT(gdef.PCERT_CHAIN_ELEMENT):
_type_ = gdef.PCERT_CHAIN_ELEMENT._type_
@property
def cert(self):
return Certificate.from_pointer(self[0].pCertContext)
# Move this in another .py ?
class CryptContext(gdef.HCRYPTPROV):
""" A context manager arround ``CryptAcquireContextW`` & ``CryptReleaseContext``
.. note::
see usage in sample :ref:`sample_crypto_encryption` (function ``genkeys``)
"""
_type_ = gdef.HCRYPTPROV._type_
def __init__(self, pszContainer=None, pszProvider=None, dwProvType=0, dwFlags=0, retrycreate=False):
self.pszContainer = pszContainer
self.pszProvider = pszProvider
self.dwProvType = dwProvType
self.dwFlags = dwFlags
self.retrycreate = True
#self.value = HCRYPTPROV()
pass
def __enter__(self):
self.acquire()
return self
def __exit__(self, *args):
self.release()
def acquire(self):
try:
return winproxy.CryptAcquireContextW(self, self.pszContainer, self.pszProvider, self.dwProvType, self.dwFlags)
except WindowsError as e:
if not self.retrycreate:
raise
return winproxy.CryptAcquireContextW(self, self.pszContainer, self.pszProvider, self.dwProvType, self.dwFlags | gdef.CRYPT_NEWKEYSET)
def release(self):
return winproxy.CryptReleaseContext(self, False)
@@ -1,133 +0,0 @@
import ctypes
from windows import winproxy
import windows.generated_def as gdef
import windows.crypto
class CryptMessage(gdef.HCRYPTMSG):
"""Represent a PKCS #7 message
(see `Low-level Message Functions <https://msdn.microsoft.com/en-us/library/windows/desktop/aa380252(v=vs.85).aspx#low_level_message_functions>`_)
"""
MSG_PARAM_KNOW_TYPES = {gdef.CMSG_SIGNER_INFO_PARAM: gdef.CMSG_SIGNER_INFO,
gdef.CMSG_SIGNER_COUNT_PARAM: gdef.DWORD,
gdef.CMSG_CERT_COUNT_PARAM: gdef.DWORD,
gdef.CMSG_ENVELOPE_ALGORITHM_PARAM: gdef.CRYPT_ALGORITHM_IDENTIFIER,
gdef.CMSG_RECIPIENT_COUNT_PARAM: gdef.DWORD,
gdef.CMSG_RECIPIENT_INFO_PARAM: gdef.CERT_INFO,
}
def get_param(self, param_type, index=0, raw=False):
data_size = gdef.DWORD()
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa380227(v=vs.85).aspx
winproxy.CryptMsgGetParam(self, param_type, index, None, data_size)
buffer = ctypes.c_buffer(data_size.value)
winproxy.CryptMsgGetParam(self, param_type, index, buffer, data_size)
if raw:
return (buffer, data_size)
if param_type in self.MSG_PARAM_KNOW_TYPES:
buffer = self.MSG_PARAM_KNOW_TYPES[param_type].from_buffer(buffer)
if isinstance(buffer, gdef.DWORD): # DWORD -> return the Python int
return buffer.value
return buffer
# Certificate accessors
@property
def nb_cert(self):
"""The number of certificate embded in the :class:`CryptObject`
:type: :class:`int`
"""
return self.get_param(gdef.CMSG_CERT_COUNT_PARAM)
def get_raw_cert(self, index=0):
return self.get_param(gdef.CMSG_CERT_PARAM, index)
def get_cert(self, index=0):
"""Return embded :class:`Certificate` number ``index``.
.. note::
Not all embded certificate are directly used to sign the :class:`CryptObject`.
"""
return windows.crypto.Certificate.from_buffer(self.get_raw_cert(index))
@property
def certs(self):
"""The list of :class:`Certificate` embded in the message"""
return [self.get_cert(i) for i in range(self.nb_cert)]
# Signers accessors
@property
def nb_signer(self):
"""The number of signers for the CryptObject
:type: :class:`int`
"""
try:
return self.get_param(gdef.CMSG_SIGNER_COUNT_PARAM)
except WindowsError as e:
if (e.winerror & 0xffffffff) == gdef.CRYPT_E_INVALID_MSG_TYPE:
return 0
raise
def get_signer_data(self, index=0):
"""Returns the signer informations for signer nb ``index``
:return: :class:`~windows.generated_def.winstructs.CMSG_SIGNER_INFO`
"""
return self.get_param(gdef.CMSG_SIGNER_INFO_PARAM, index)
@property
def signers(self):
"""The list of :class:`~windows.generated_def.winstructs.CMSG_SIGNER_INFO` embed in the message"""
return [self.get_signer_data(i) for i in range(self.nb_signer)]
@property
def nb_recipient(self):
"""TODO: DOC"""
return self.get_param(gdef.CMSG_RECIPIENT_COUNT_PARAM)
def get_recipient_data(self, index=0):
"""TODO: DOC"""
return self.get_param(gdef.CMSG_RECIPIENT_INFO_PARAM, index)
@property
def recipients(self):
"""TODO: DOC"""
return [self.get_recipient_data(i) for i in range(self.nb_recipient)]
@property
def content(self):
return self.get_param(gdef.CMSG_CONTENT_PARAM)[:]
@property
def content_type(self):
data = self.get_param(gdef.CMSG_INNER_CONTENT_TYPE_PARAM)
assert data[-1] == "\x00", "CMSG_INNER_CONTENT_TYPE_PARAM not NULL TERMINATED"
return data[:-1]
def update(self, blob, final):
# Test isinstance string ?
if isinstance(blob, (windows.pycompat.anybuff, bytearray)):
blob = windows.pycompat.raw_encode(blob)
buffer = windows.utils.BUFFER(gdef.BYTE).from_buffer_copy(blob)
return winproxy.CryptMsgUpdate(self, buffer, len(blob), final)
return winproxy.CryptMsgUpdate(self, blob.pbData, blob.cbData, final)
# constructor
@classmethod
def from_buffer(self, data):
hmsg = winproxy.CryptMsgOpenToDecode(windows.crypto.DEFAULT_ENCODING, 0, 0, None, None, None)
newmsg = CryptMessage(hmsg)
newmsg.update(data, final=True)
return newmsg
def __del__(self):
return winproxy.CryptMsgClose(self)
@@ -1,34 +0,0 @@
from windows import winproxy
import windows.generated_def as gdef
__all__ = ["protect", "unprotect"]
def protect(data, entropy=None, flags=gdef.CRYPTPROTECT_UI_FORBIDDEN):
in_blob = gdef.DATA_BLOB.from_string(data)
out_blob = gdef.DATA_BLOB()
if entropy is not None:
entropy = gdef.DATA_BLOB.from_string(entropy)
winproxy.CryptProtectData(in_blob, pOptionalEntropy=entropy, dwFlags=flags, pDataOut=out_blob)
encrypted_data = bytes(out_blob.data)
# https://docs.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptprotectdata
# pDataOut: A pointer to a DATA_BLOB structure that receives the encrypted data.
# When you have finished using the DATA_BLOB structure, free its pbData member by calling the LocalFree function.
winproxy.LocalFree(out_blob.pbData)
del out_blob
return encrypted_data
def unprotect(data, entropy=None, flags=gdef.CRYPTPROTECT_UI_FORBIDDEN):
in_blob = gdef.DATA_BLOB.from_string(data)
out_blob = gdef.DATA_BLOB()
if entropy is not None:
entropy = gdef.DATA_BLOB.from_string(entropy)
winproxy.CryptUnprotectData(in_blob, pOptionalEntropy=entropy, dwFlags=flags, pDataOut=out_blob)
decrypted_data = bytes(out_blob.data)
# https://docs.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptprotectdata
# pDataOut: A pointer to a DATA_BLOB structure that receives the encrypted data.
# When you have finished using the DATA_BLOB structure, free its pbData member by calling the LocalFree function.
winproxy.LocalFree(out_blob.pbData)
del out_blob
return decrypted_data
@@ -1,119 +0,0 @@
from os import urandom
from windows import winproxy
from windows.crypto import DEFAULT_ENCODING
from windows.generated_def import *
__all__ = ["encrypt", "decrypt"]
def encode_init_vector(data):
blob = CRYPT_DATA_BLOB.from_string(data)
size = DWORD()
buf = None
winproxy.CryptEncodeObjectEx(DEFAULT_ENCODING, X509_OCTET_STRING, ctypes.byref(blob), 0, None, buf, size)
buf = (BYTE * size.value)()
winproxy.CryptEncodeObjectEx(DEFAULT_ENCODING, X509_OCTET_STRING, ctypes.byref(blob), 0, None, buf, size)
return buf[:]
class GenerateInitVector(object):
def __repr__(self):
return "GenerateInitVector()"
def generate_init_vector(self, algo):
if algo in [szOID_OIWSEC_desCBC, szOID_RSA_DES_EDE3_CBC]:
return urandom(8)
if algo in [szOID_NIST_AES128_CBC, szOID_NIST_AES192_CBC, szOID_NIST_AES256_CBC]:
return urandom(16)
return None
geninitvector = GenerateInitVector()
def encrypt(cert_or_certlist, msg, algo=szOID_NIST_AES256_CBC, initvector=geninitvector):
"""Encrypt ``msg`` one or many :class:`Certificate` using ``algo`` with the initial
vector ``initvector``.
If ``geninitvector`` is left as it is, it will generate a random one.
Algorithms supported by ``GenerateInitVector`` are:
* ``szOID_OIWSEC_desCBC``
* ``szOID_RSA_DES_EDE3_CBC``
* ``szOID_NIST_AES128_CBC``
* ``szOID_NIST_AES192_CBC``
* ``szOID_NIST_AES256_CBC``
:param cert_or_certlist: One or many :class:`Certificate` used to encrypt the msg
:type cert_or_certlist: :class:`Certificate` | [:class:`Certificate`]
:return: :class:`bytearray`: The encrypted message
"""
alg_ident = CRYPT_ALGORITHM_IDENTIFIER()
alg_ident.pszObjId = algo.encode("ascii")
# We want to have automatique translation of Certificate -> PCERT_CONTEXT
# In order to simple create the 'PCERT_CONTEXT[] certs'
# For that we need a tuple of X * 1-item-tuple
# as a (cert,) will be automaticly translatable to a PCERT_CONTEXT
if isinstance(cert_or_certlist, CERT_CONTEXT):
certlist = ((cert_or_certlist,),)
else:
certlist = tuple((c,) for c in cert_or_certlist)
# Set (compute if needed) the IV
if initvector is None:
alg_ident.Parameters.cbData = 0
elif initvector is geninitvector:
initvector = initvector.generate_init_vector(algo)
if initvector is None:
raise ValueError("I don't know how to generate an <initvector> for <{0}> please provide one (or None)".format(algo))
initvector_encoded = encode_init_vector(initvector)
alg_ident.Parameters = CRYPT_DATA_BLOB.from_string(initvector_encoded)
else:
initvector_encoded = encode_init_vector(initvector)
alg_ident.Parameters = CRYPT_DATA_BLOB.from_string(initvector_encoded)
# Setup encryption parameters
param = CRYPT_ENCRYPT_MESSAGE_PARA()
param.cbSize = ctypes.sizeof(param)
param.dwMsgEncodingType = DEFAULT_ENCODING
param.hCryptProv = None
param.ContentEncryptionAlgorithm = alg_ident
param.pvEncryptionAuxInfo = None
param.dwFlags = 0
param.dwInnerContentType = 0
certs = (PCERT_CONTEXT * len(certlist))(*certlist)
#Ask the output buffer size
size = DWORD()
winproxy.CryptEncryptMessage(param, len(certs), certs, msg, len(msg), None, size)
#Encrypt the msg
buf = (BYTE * size.value)()
winproxy.CryptEncryptMessage(param, len(certs), certs, msg, len(msg), buf, size)
return bytearray(buf[:size.value])
def decrypt(cert_store, encrypted):
"""Try to decrypt the ``encrypted`` msg with any certificate in ``cert_store``.
If there is no certificate able to decrypt the message ``WinproxyError(winerror=0x8009200c)`` is raised.
:param cert_store:
:type cert_store: :class:`CertificateStore`
:return: :class:`str`: The decrypted message
"""
# Setup decryption parameters
dparam = CRYPT_DECRYPT_MESSAGE_PARA()
dparam.cbSize = ctypes.sizeof(dparam)
dparam.dwMsgAndCertEncodingType = DEFAULT_ENCODING
dparam.cCertStore = 1
dparam.rghCertStore = (cert_store,)
dparam.dwFlags = 0
#Ask the output buffer size
buf = (BYTE * len(encrypted)).from_buffer_copy(encrypted)
dcryptsize = DWORD()
winproxy.CryptDecryptMessage(dparam, buf, ctypes.sizeof(buf), None, dcryptsize, None)
#Decrypt the msg
dcryptbuff = (BYTE * (dcryptsize.value + 0x1000))()
winproxy.CryptDecryptMessage(dparam, buf, ctypes.sizeof(buf), dcryptbuff, dcryptsize, None)
return bytes(bytearray(dcryptbuff[:dcryptsize.value]))
@@ -1,53 +0,0 @@
import windows
from windows import winproxy
from windows.generated_def import *
from windows.crypto import DEFAULT_ENCODING, CertificateStore
def generate_selfsigned_certificate(name="CN=DEFAULT", prov=None, key_info=None, flags=0, signature_algo=None):
"""Generate a selfsigned certificate.
See `CertCreateSelfSignCertificate <https://msdn.microsoft.com/en-us/library/windows/desktop/aa376039(v=vs.85).aspx>`_
:return: :class:`windows.crypto.Certificate`
"""
size = ULONG(len(name) + 0x100)
buffer = (ctypes.c_ubyte * size.value)()
winproxy.CertStrToNameA(X509_ASN_ENCODING, name, CERT_OID_NAME_STR, None, buffer, size, None)
blobname = CRYPT_DATA_BLOB(size.value, buffer)
cert = winproxy.CertCreateSelfSignCertificate(prov, blobname, flags, key_info, signature_algo, None, None, None)
return windows.crypto.Certificate.from_pointer(cert)
def generate_key(prov, keytype=AT_KEYEXCHANGE, flags=CRYPT_EXPORTABLE):
"""Generate a keypair if type ``keytype``.
:return: :class:`HCRYPTKEY`
"""
key = HCRYPTKEY()
winproxy.CryptGenKey(prov, keytype, flags , key)
return key
# print(key[0])
# print("[OK] Key created")
# size = DWORD()
# winproxy.CryptExportKey(key, None, PRIVATEKEYBLOB, 0, None, size)
# buffer = (BYTE * size.value)()
# print("needed size = {0}".format(size))
# winproxy.CryptExportKey(key, None, PRIVATEKEYBLOB, 0, buffer, size)
# print("[OK] Key in buffer")
# keyraw = bytearray(buffer)
# # openssl.exe rsa -in key.out -inform MS\PRIVATEKEYBLOB -text
# save_as(keyraw, "key.out")
# #res = ctypes.WinDLL("advapi32").CryptReleaseContext(prov, 0)
# return key
def generate_pfx(hstore, password=None):
"""Generate a pfx protected by ``password`` contaning the certificates in ``hstore``
:return: :class:`bytearray` -- The raw PFX
"""
blob = CRYPT_DATA_BLOB(0, None)
winproxy.PFXExportCertStoreEx(hstore, blob, password, None, EXPORT_PRIVATE_KEYS | REPORT_NO_PRIVATE_KEY | REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY)
blob.pbData = (ctypes.c_ubyte * blob.cbData)()
winproxy.PFXExportCertStoreEx(hstore, blob, password, None, EXPORT_PRIVATE_KEYS | REPORT_NO_PRIVATE_KEY | REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY)
return blob.data
@@ -1,70 +0,0 @@
import windows
from windows import winproxy
from windows.crypto import DEFAULT_ENCODING
import windows.generated_def as gdef
import ctypes
__all__ = ["sign", "verify_signature"]
def sign(cert, msg, detached_signature=False, algo=gdef.szOID_RSA_SHA256RSA):
# hash algorithm
alg_hash = gdef.CRYPT_ALGORITHM_IDENTIFIER()
alg_hash.pszObjId = algo.encode()
# Signing parameters
sign_para = gdef.CRYPT_SIGN_MESSAGE_PARA()
sign_para.cbSize = ctypes.sizeof(sign_para)
sign_para.dwMsgEncodingType = DEFAULT_ENCODING
sign_para.pSigningCert = gdef.PCERT_CONTEXT(cert)
sign_para.HashAlgorithm = alg_hash
sign_para.pvHashAuxInfo = None
sign_para.cMsgCert = 0
sign_para.rgpMsgCert = None
sign_para.cMsgCrl = 0
sign_para.rgpMsgCrl = None
sign_para.cAuthAttr = 0
sign_para.rgAuthAttr = None
sign_para.cUnauthAttr = 0
sign_para.rgUnauthAttr = None
sign_para.dwFlags = 0
sign_para.dwInnerContentType = 0
sign_para.HashEncryptionAlgorithm = alg_hash
sign_para.pvHashEncryptionAuxInfo = None
ByteBuffer = windows.utils.BUFFER(gdef.BYTE)
result_buffer = ByteBuffer(nbelt=0x2000)
result_size = gdef.DWORD(len(result_buffer))
buff = ByteBuffer(*bytearray(msg))
buff_pr = windows.utils.BUFFER(gdef.LPBYTE, nbelt=1)(buff)
buff_size = gdef.DWORD(len(msg))
try:
windows.winproxy.CryptSignMessage(sign_para, False, 1, buff_pr, buff_size, result_buffer, result_size)
except WindowsError as e:
if not e.winerror == gdef.ERROR_MORE_DATA:
raise
result_buffer = ByteBuffer(nbelt=result_size.value)
windows.winproxy.CryptSignMessage(sign_para, False, 1, buff_pr, buff_size, result_buffer, result_size)
return bytearray(result_buffer[:result_size.value])
def verify_signature(cert, encoded_blob):
# Verify parameters
verif_param = gdef.CRYPT_KEY_VERIFY_MESSAGE_PARA()
verif_param.cbSize = ctypes.sizeof(gdef.CRYPT_KEY_VERIFY_MESSAGE_PARA)
verif_param.dwMsgEncodingType = windows.crypto.DEFAULT_ENCODING
verif_param.hCryptProv = None
# The public key used
pubkey = cert.pCertInfo[0].SubjectPublicKeyInfo
# Preparing in/out buffer/size
signed_buffer = windows.utils.BUFFER(gdef.BYTE).from_buffer_copy(encoded_blob)
decoded_buffer = windows.utils.BUFFER(gdef.BYTE).from_buffer_copy(encoded_blob)
decoded_size = gdef.DWORD(len(decoded_buffer))
winproxy.CryptVerifyMessageSignatureWithKey(verif_param,
pubkey,
signed_buffer,
len(encoded_blob),
decoded_buffer,
decoded_size)
return bytearray(decoded_buffer[:decoded_size.value])
-53
View File
@@ -1,53 +0,0 @@
import os
import sys
import logging
import inspect
options = {'active': False, 'cats': None}
# options = {'active': True, 'cats': ["HANDLE"]}
def get_stack_func_name(lvl):
info = inspect.stack()[lvl]
return info[0], info[3]
def do_dbgprint(msg, type=None):
if ("ALL" in options['cats']) or type.upper() in options['cats']:
frame, func = get_stack_func_name(2)
logger = logging.getLogger(frame.f_globals['__name__'] + ":" + func)
logger.debug(msg)
def do_nothing(*args, **kwargs):
return None
def parse_option(s):
if s[0] == "=":
s = s[1:]
if s:
cats = [x.upper().strip() for x in s.split('-')]
options['cats'] = cats
formt = 'DBG|%(name)s|%(message)s'
logging.basicConfig(format=formt, level=logging.DEBUG)
try:
if 'DBGPRINT' in os.environ:
parse_option(os.environ['DBGPRINT'])
dbgprint = do_dbgprint
elif any([opt.startswith("--DBGPRINT") for opt in sys.argv]):
dbgprint = do_dbgprint
option_str = [opt for opt in sys.argv if opt.startswith("--DBGPRINT")][0]
parse_option(option_str[len('--DBGPRINT'):])
elif options["active"]:
formt = 'DBG|%(name)s|%(message)s'
logging.basicConfig(format=formt, level=logging.DEBUG)
dbgprint = do_dbgprint
else:
dbgprint = do_nothing
except Exception as e:
dbgprint = do_nothing
print("dbgprint Error: {0}({1})".format(type(e), e))
x = type(e), e
@@ -1,5 +0,0 @@
from .debugger import Debugger, HXBreakpoint
from .symboldbg import SymbolDebugger
from .localdbg import LocalDebugger
from .breakpoints import *
from .breakpoints import *
@@ -1,258 +0,0 @@
from collections import OrderedDict
import windows
from windows.generated_def.winstructs import *
from windows.generated_def import windef
from windows.winobject.process import WinProcess, WinThread
from windows.pycompat import basestring
STANDARD_BP = "BP"
HARDWARE_EXEC_BP = "HXBP"
MEMORY_BREAKPOINT = "MEMBP"
class Breakpoint(object):
"""An standard (Int3) breakpoint (type == ``STANDARD_BP``)"""
type = STANDARD_BP # REAL BP
def __init__(self, addr):
self.addr = addr
def apply_to_target(self, target):
return isinstance(target, WinProcess)
def trigger(self, dbg, exception):
"""Called when breakpoint is hit"""
pass
class ProxyBreakpoint(Breakpoint):
def __init__(self, target, addr, type):
self.target = target
self.addr = addr
self.type = type
def trigger(self, dbg, exception):
return self.target(dbg, exception)
class HXBreakpoint(Breakpoint):
"""An hardware-execution breakpoint (type == ``HARDWARE_EXEC_BP``)"""
type = HARDWARE_EXEC_BP
def apply_to_target(self, target):
return isinstance(target, WinThread)
class MemoryBreakpoint(Breakpoint):
"""A memory breakpoint (type == ``MEMORY_BREAKPOINT``)"""
type = MEMORY_BREAKPOINT
DEFAULT_EVENTS = "RWX"
DEFAULT_SIZE = 0x1000
def __init__(self, addr, size=None, events=None):
"""``size``: the size of the memory breakpoint.
``events``: a string representing the events that interest the BP (any of "RWX")"""
super(MemoryBreakpoint, self).__init__(addr)
self.size = size if size is not None else self.DEFAULT_SIZE
events = events if events is not None else self.DEFAULT_EVENTS
self.events = set(events)
def trigger(self, dbg, exception):
"""Called when breakpoint is hit"""
pass
## Arguments Helper (need to move this elsewhere)
class X86ArgumentRetriever(object):
def get_arg(self, nb, proc, thread):
return proc.read_dword(thread.context.sp + 4 + (4 * nb))
def set_arg(self, nb, value, proc, thread):
return proc.write_dword(thread.context.sp + 4 + (4 * nb), value)
class X64ArgumentRetriever(object):
REG_ARGS = ["Rcx", "Rdx", "R8", "R9"]
def get_arg(self, nb, proc, thread):
if nb < len(self.REG_ARGS):
return getattr(thread.context, self.REG_ARGS[nb])
return proc.read_qword(thread.context.sp + 8 + (8 * nb))
def set_arg(self, nb, value, proc, thread):
if nb < len(self.REG_ARGS):
ctx = thread.context
setattr(ctx, self.REG_ARGS[nb], value)
return thread.set_context(ctx)
return proc.write_qword(thread.context.sp + 8 + (8 * nb), value)
## Behaviour breakpoint !
# class FunctionParamDumpBP(Breakpoint):
class FunctionParamDumpBPAbstract(object):
def __init__(self, addr=None, target=None):
if target is None:
try:
target = self.TARGET
except AttributeError as e:
raise ValueError("{0} bp without a <target> must have a <TARGET> class attribute")
if addr is None:
addr = "{0}!{1}".format(target.target_dll, target.target_func)
super(FunctionParamDumpBPAbstract, self).__init__(addr)
self.target = target
self.target_args = target.prototype._argtypes_
self.target_params = target.params
def extract_arguments_32bits(self, cproc, cthread):
x = windows.debug.X86ArgumentRetriever()
res = OrderedDict()
for i, (name, type) in enumerate(zip(self.target_params, self.target_args)):
value = x.get_arg(i, cproc, cthread)
rt = windows.remotectypes.transform_type_to_remote32bits(type)
if issubclass(rt, windows.remotectypes.RemoteValue):
t = rt(value, cproc)
else:
t = rt(value)
# Will fail in py3..
content = None
try:
content = t.contents
except Exception as e:
# contents will fail on basic type
# Not really an expected behavior
# But it works for now.. (and since a while)
pass
if content is None:
t = t.value
res[name[1]] = t
return res
def extract_arguments_64bits(self, cproc, cthread):
x = windows.debug.X64ArgumentRetriever()
res = OrderedDict()
for i, (name, type) in enumerate(zip(self.target_params, self.target_args)):
value = x.get_arg(i, cproc, cthread)
rt = windows.remotectypes.transform_type_to_remote64bits(type)
if issubclass(rt, windows.remotectypes.RemoteValue):
t = rt(value, cproc)
else:
t = rt(value)
if not hasattr(t, "contents"):
try:
t = t.value
except AttributeError:
pass
res[name[1]] = t
return res
def extract_arguments(self, cproc, cthread):
"""Extracts the functions parameters in an :class:`OrderedDict`"""
if windows.current_process.bitness == 32:
return self.extract_arguments_32bits(cproc, cthread)
if cproc.bitness == 64:
return self.extract_arguments_64bits(cproc, cthread)
# SysWow process from a 64bits debugger, handle bitness with CS
if cthread.context.SegCs == windows.syswow64.CS_32bits:
return self.extract_arguments_32bits(cproc, cthread)
return self.extract_arguments_64bits(cproc, cthread)
def arguments(self, dbg):
"TEST PARAM DICT"
if windows.current_process.bitness == 32:
extractor = windows.debug.X86ArgumentRetriever()
elif dbg.current_process.bitness == 64:
extractor = windows.debug.X64ArgumentRetriever()
elif dbg.current_thread.context.SegCs == windows.syswow64.CS_32bits:
extractor = windows.debug.X86ArgumentRetriever()
else:
extractor = windows.debug.X64ArgumentRetriever()
name_map = {name:i for i, name in enumerate(t[1] for t in self.target_params)}
return FunctionParameterProxy(extractor, name_map, self.target_args, dbg)
class FunctionParameterProxy(object):
# TODO: clean this + put more of the logic in the X64ArgumentRetriever
def __init__(self, extractor, name_map, parameters_type, x):
self.extractor = extractor
self.name_map = name_map
self.parameters_type = parameters_type
self.x = x
def __getitem__(self, x):
if isinstance(x, basestring):
x = self.name_map[x]
# import pdb;pdb.set_trace()
argtype = self.parameters_type[x]
value = self.extractor.get_arg(x, self.x.current_process, self.x.current_thread)
rt = windows.remotectypes.transform_type_to_remote32bits(argtype)
if issubclass(rt, windows.remotectypes.RemoteValue):
t = rt(value, self.x.current_process)
else:
t = rt(value)
if not hasattr(t, "contents"):
try:
t = t.value
except AttributeError:
pass
return t
def __setitem__(self, x, value):
if isinstance(x, basestring):
x = self.name_map[x]
try:
ctypes.cast(value, PVOID)
except ctypes.ArgumentError:
pass
value = getattr(value, "value", value)
return self.extractor.set_arg(x, value, self.x.current_process, self.x.current_thread)
class FunctionParamDumpBP(FunctionParamDumpBPAbstract, Breakpoint):
pass
class FunctionParamDumpHXBP(FunctionParamDumpBPAbstract, HXBreakpoint):
pass
class FunctionRetBP(Breakpoint):
def __init__(self, addr, initial_breakpoint):
super(FunctionRetBP, self).__init__(addr)
self.initial_breakpoint = initial_breakpoint
def trigger(self, dbg, exc):
dbg.del_bp(self, targets=[dbg.current_process])
return self.initial_breakpoint.ret_trigger(dbg, exc)
class FunctionCallBP(Breakpoint):
"""A Breakpoint that allow to trigger at the return of a function"""
def break_on_ret(self, dbg, exception):
"""Setup a breakpoint at the return address of the function, this breakpoint will call :func:`ret_trigger`"""
return_addr = self.get_ret_addr(dbg, exception)
dbg.add_bp(FunctionRetBP(return_addr, self), target=dbg.current_process)
def get_ret_addr(self, dbg, exception):
"""Get the return address of the current target, only valid in the trigger() function."""
cproc = dbg.current_process
return dbg.current_process.read_ptr(dbg.current_thread.context.sp)
def ret_trigger(self, dbg, exception):
"""Called at the return of the function if :func:`break_on_ret` was called"""
raise NotImplementedError("ret_trigger")
class FunctionBP(FunctionCallBP, FunctionParamDumpBP):
"""A breakpoint that accepts a function from :mod:`windows.winproxy` and able to:
- Extract the arguments of the functions
- Break at the return of the function
"""
class PrintBP(Breakpoint):
def __init__(self, addr, format, func=None):
super(PrintBP, self).__init__(addr)
self.format = format
self.func = func
def trigger(self, dbg, exc):
thread = dbg.current_thread
format_dict = {"dbg": dbg, "exc": exc, "proc": dbg.current_process, "thread": thread, "ctx": thread.context}
if self.func:
format_dict.update(self.func(**format_dict))
print(self.format.format(**format_dict))
File diff suppressed because it is too large Load Diff
@@ -1,305 +0,0 @@
from collections import defaultdict
from contextlib import contextmanager
import windows
import windows.winobject.exception as winexception
from windows import winproxy
from windows.generated_def import windef
from windows.generated_def.winstructs import *
from .breakpoints import *
class FakeDebuggerCurrentThread(object):
"""A pseudo thread representing the current thread at exception time"""
def __init__(self, dbg):
self.dbg = dbg
@property
def tid(self):
return windows.current_thread.tid
@property
def context(self):
"""!!! This context in-place modification will be effective without set_context"""
return self.dbg.get_exception_context()
def set_context(self, context):
# The context returned by 'self.context' already modify the return context in place..
pass
class LocalDebugger(object):
"""A debugger interface around :func:`AddVectoredExceptionHandler`.
Handle:
* Standard BP (int3)
* Hardware-Exec BP (DrX)
"""
def __init__(self):
self.breakpoints = {}
self._memory_save = {}
self._reput_breakpoint = {}
self._hxbp_breakpoint = defaultdict(dict)
self.callback_vectored = winexception.VectoredException(self.callback)
winproxy.AddVectoredExceptionHandler(0, self.callback_vectored)
self.setup_hxbp_callback_vectored = winexception.VectoredException(self.setup_hxbp_callback)
self.hxbp_info = None
self.code = windows.native_exec.create_function(b"\xcc\xc3", [PVOID])
self.veh_depth = 0
self.current_exception = None
self.exceptions_stack = [None]
self.current_process = windows.current_process
self.current_thread = FakeDebuggerCurrentThread(self)
@contextmanager
def NewCurrentException(self, exc):
try:
self.exceptions_stack.append(exc)
self.current_exception = exc
self.veh_depth += 1
yield exc
finally:
self.exceptions_stack.pop()
self.current_exception = self.exceptions_stack[-1]
self.veh_depth -= 1
def get_exception_code(self):
"""Return ExceptionCode of current exception"""
return self.current_exception[0].ExceptionRecord[0].ExceptionCode
def get_exception_context(self):
"""Return context of current exception"""
return self.current_exception[0].ContextRecord[0]
def single_step(self):
"""Make the current thread to single step"""
self.get_exception_context().EEFlags.TF = 1
return windef.EXCEPTION_CONTINUE_EXECUTION
def _pass_breakpoint(self, addr, single_step):
with windows.utils.VirtualProtected(addr, 1, PAGE_EXECUTE_READWRITE):
windows.current_process.write_memory(addr, self._memory_save[addr])
self._reput_breakpoint[windows.current_thread.tid] = self.breakpoints[addr], single_step
return self.single_step()
def _local_resolve(self, addr):
if not isinstance(addr, basestring):
return addr
dll, api = addr.split("!")
dll = dll.lower()
modules = {m.name[:-len(".dll")] if m.name.endswith(".dll") else m.name : m for m in windows.current_process.peb.modules}
mod = None
if dll in modules:
mod = [modules[dll]]
if not mod:
return None
# TODO: optim exports are the same for whole system (32 vs 64 bits)
# I don't have to reparse the exports each time..
# Try to interpret api as an int
try:
api_int = int(api, 0)
return mod[0].baseaddr + api_int
except ValueError:
pass
exports = mod[0].pe.exports
if api not in exports:
dbgprint("Error resolving <{0}> in local process".format(addr, target), "DBG")
raise ValueError("Unknown API <{0}> in DLL {1}".format(api, dll))
return exports[api]
def callback(self, exc):
with self.NewCurrentException(exc):
return self.handle_exception(exc)
def handle_exception(self, exc):
exp_code = self.get_exception_code()
context = self.get_exception_context()
exp_addr = context.pc
if exp_code == EXCEPTION_BREAKPOINT and exp_addr in self.breakpoints:
res = self.breakpoints[exp_addr].trigger(self, exc)
single_step = self.get_exception_context().EEFlags.TF # single step activated by breakpoint
if exp_addr in self.breakpoints: # Breakpoint deleted itself ?
return self._pass_breakpoint(exp_addr, single_step)
return EXCEPTION_CONTINUE_EXECUTION
if exp_code == EXCEPTION_SINGLE_STEP and windows.current_thread.tid in self._reput_breakpoint:
bp, single_step = self._reput_breakpoint[windows.current_thread.tid]
self._memory_save[bp._addr] = windows.current_process.read_memory(bp._addr, 1)
with windows.utils.VirtualProtected(bp._addr, 1, PAGE_EXECUTE_READWRITE):
windows.current_process.write_memory(bp._addr, b"\xcc")
del self._reput_breakpoint[windows.current_thread.tid]
if single_step:
return self.on_exception(exc)
return windef.EXCEPTION_CONTINUE_EXECUTION
elif exp_code == EXCEPTION_SINGLE_STEP and exp_addr in self._hxbp_breakpoint[windows.current_thread.tid]:
res = self._hxbp_breakpoint[windows.current_thread.tid][exp_addr].trigger(self, exc)
context.EEFlags.RF = 1
return EXCEPTION_CONTINUE_EXECUTION
return self.on_exception(exc)
def on_exception(self, exc):
"""Called on exception"""
if not self.get_exception_code() in winexception.exception_name_by_value:
return windef.EXCEPTION_CONTINUE_SEARCH
return windef.EXCEPTION_CONTINUE_EXECUTION
def del_bp(self, bp, targets=None):
"""Delete a breakpoint"""
# TODO: check targets..
if bp.type == STANDARD_BP:
with windows.utils.VirtualProtected(bp.addr, 1, PAGE_EXECUTE_READWRITE):
windows.current_process.write_memory(bp.addr, self._memory_save[bp.addr])
del self._memory_save[bp.addr]
del self.breakpoints[bp.addr]
return
if bp.type == HARDWARE_EXEC_BP:
threads_by_tid = {t.tid: t for t in windows.current_process.threads}
for tid in self._hxbp_breakpoint:
if bp.addr in self._hxbp_breakpoint[tid] and self._hxbp_breakpoint[tid][bp.addr] == bp:
if tid == windows.current_thread.tid:
self.remove_hxbp_self_thread(bp.addr)
else:
self.remove_hxbp_other_thread(bp.addr, threads_by_tid[tid])
del self._hxbp_breakpoint[tid][bp.addr]
return
raise NotImplementedError("Unknow BP type {0}".format(bp.type))
def add_bp(self, bp, target=None):
"""Add a breakpoint, bp is a "class:`Breakpoint`
If the ``bp`` type is ``STANDARD_BP``, target must be None.
If the ``bp`` type is ``HARDWARE_EXEC_BP``, target can be None (all threads), or some threads of the process
"""
if bp.type == HARDWARE_EXEC_BP:
return self.add_bp_hxbp(bp, target)
if bp.type != STANDARD_BP:
raise NotImplementedError("Unknow BP type {0}".format(bp.type))
if target not in [None, windows.current_process]:
raise ValueError("LocalDebugger: STANDARD_BP doest not support targets {0}".format(targets))
addr = self._local_resolve(bp.addr)
bp._addr = addr
self.breakpoints[addr] = bp
self._memory_save[addr] = windows.current_process.read_memory(addr, 1)
with windows.utils.VirtualProtected(addr, 1, PAGE_EXECUTE_READWRITE):
windows.current_process.write_memory(addr, b"\xcc")
return
def add_bp_hxbp(self, bp, targets=None):
if bp.type != HARDWARE_EXEC_BP:
raise NotImplementedError("Add non standard-BP in LocalDebugger")
if targets is None:
targets = windows.current_process.threads
for thread in targets:
if thread.owner.pid != windows.current_process.pid:
raise ValueError("Cannot add HXBP to target in remote process {0}".format(thread))
if thread.tid == windows.current_thread.tid:
self.setup_hxbp_self_thread(bp.addr)
else:
self.setup_hxbp_other_thread(bp.addr, thread)
self._hxbp_breakpoint[thread.tid][bp.addr] = bp
def setup_hxbp_callback(self, exc):
with self.NewCurrentException(exc):
exp_code = self.get_exception_code()
if exp_code != windef.EXCEPTION_BREAKPOINT:
return windef.EXCEPTION_CONTINUE_SEARCH
context = self.get_exception_context()
exp_addr = context.pc
hxbp_used = self.setup_hxbp_in_context(context, self.data)
windows.current_process.write_memory(exp_addr, b"\x90")
# Raising in the VEH is a bad idea..
# So better give the information to triggerer..
if hxbp_used is not None:
self.get_exception_context().func_result = exp_addr
else:
self.get_exception_context().func_result = 0
return windef.EXCEPTION_CONTINUE_EXECUTION
def remove_hxbp_callback(self, exc):
with self.NewCurrentException(exc):
exp_code = self.get_exception_code()
context = self.get_exception_context()
exp_addr = context.pc
hxbp_used = self.remove_hxbp_in_context(context, self.data)
windows.current_process.write_memory(exp_addr, b"\x90")
# Raising in the VEH is a bad idea..
# So better give the information to triggerer..
if hxbp_used is not None:
self.get_exception_context().Eax = exp_addr
else:
self.get_exception_context().Eax = 0
return windef.EXCEPTION_CONTINUE_EXECUTION
def setup_hxbp_in_context(self, context, addr):
for i in range(4):
is_used = getattr(context.EDr7, "L" + str(i))
empty_drx = str(i)
if not is_used:
context.EDr7.GE = 1
context.EDr7.LE = 1
setattr(context.EDr7, "L" + empty_drx, 1)
setattr(context, "Dr" + empty_drx, addr)
return i
return None
def remove_hxbp_in_context(self, context, addr):
for i in range(4):
target_drx = str(i)
is_used = getattr(context.EDr7, "L" + str(i))
draddr = getattr(context, "Dr" + target_drx)
if is_used and draddr == addr:
setattr(context.EDr7, "L" + target_drx, 0)
setattr(context, "Dr" + target_drx, 0)
return i
return None
def setup_hxbp_self_thread(self, addr):
if self.current_exception is not None:
x = self.setup_hxbp_in_context(self.get_exception_context(), addr)
if x is None:
raise ValueError("Could not setup HXBP")
return
self.data = addr
with winexception.VectoredExceptionHandler(1, self.setup_hxbp_callback):
x = self.code()
if x is None:
raise ValueError("Could not setup HXBP")
windows.current_process.write_memory(x, b"\xcc")
return
def setup_hxbp_other_thread(self, addr, thread):
thread.suspend()
ctx = thread.context
x = self.setup_hxbp_in_context(ctx, addr)
if x is None:
raise ValueError("Could not setup HXBP in {0}".format(thread))
thread.set_context(ctx)
thread.resume()
def remove_hxbp_self_thread(self, addr):
if self.current_exception is not None:
x = self.remove_hxbp_in_context(self.get_exception_context(), addr)
if x is None:
raise ValueError("Could not setup HXBP")
return
self.data = addr
with winexception.VectoredExceptionHandler(1, self.remove_hxbp_callback):
x = self.code()
if x is None:
raise ValueError("Could not remove HXBP")
windows.current_process.write_memory(x, b"\xcc")
return
def remove_hxbp_other_thread(self, addr, thread):
thread.suspend()
ctx = thread.context
x = self.remove_hxbp_in_context(ctx, addr)
if x is None:
raise ValueError("Could not setup HXBP in {0}".format(thread))
thread.set_context(ctx)
thread.resume()
@@ -1,48 +0,0 @@
import windows
import windows.generated_def as gdef
from windows.pycompat import int_types
from . import Debugger
from . import symbols
class SymbolDebugger(Debugger):
"""A debugger using the symbol API (hence PDB) for name resolution.
To use PDB, a correct version of dbghelp should be configured as well as ``_NT_SYMBOL_PATH``.
(See :ref:`debug_symbols_module`)
This debugger add a ``current_resolver`` variable (A :class:`~windows.debug.symbols.ProcessSymbolHandler`) for the ``current_process``.
"""
def __init__(self, *args, **kwargs):
super(SymbolDebugger, self).__init__(*args, **kwargs)
self._resolvers = {}
def _internal_on_load_dll(self, load_dll):
path = self._get_loaded_dll(load_dll)
# Path is used instead of name for naming the module (and can be set to whatever if using file handle)
x = self.current_resolver.load_module(load_dll.hFile, path=path, addr=load_dll.lpBaseOfDll)
def _internal_on_create_process(self, create_process):
# Create and setup a symbol resolver for the new process
resolver = symbols.ProcessSymbolHandler(self.current_process)
self._resolvers[self.current_process.pid] = resolver
self.current_resolver = resolver
def _update_debugger_state(self, debug_event):
super(SymbolDebugger, self)._update_debugger_state(debug_event)
self.current_resolver = self._resolvers[debug_event.dwProcessId]
def _resolve(self, addr, target):
if isinstance(addr, int_types):
return addr
if "+" in addr:
symbol, deplacement = addr.split("+", 1)
deplacement = int(deplacement, 0)
else:
symbol = addr
deplacement = 0
try:
return self.current_resolver[symbol].addr + deplacement
except WindowsError as e:
if not e.winerror in (gdef.ERROR_NOT_FOUND, gdef.ERROR_MOD_NOT_FOUND):
raise
return None
-744
View File
@@ -1,744 +0,0 @@
import os.path
import ctypes
import copy
import itertools
from collections import namedtuple
import windows
import windows.generated_def as gdef
from windows import winproxy
from windows.pycompat import basestring
DEFAULT_DBG_OPTION = gdef.SYMOPT_DEFERRED_LOADS + gdef.SYMOPT_UNDNAME
def set_dbghelp_path(path):
"""Set the path of the ``dbghelp.dll`` file to use. It allow to configure a different version of the DLL handling PDB downloading.
If ``path`` is a directory, the final ``dbghelp.dll`` will be computed as
``path\<current_process_bitness>\dbghelp.dll``.
This allow to use the same script transparently in both 32b & 64b python interpreters.
"""
loaded_modules = [m.name.lower() for m in windows.current_process.peb.modules]
if os.path.isdir(path):
path = os.path.join(path, str(windows.current_process.bitness), "dbghelp.dll")
if "dbghelp.dll" in loaded_modules:
raise ValueError("setup_dbghelp_path should be called before any dbghelp function")
# Change the DLL used by DbgHelpProxy
winproxy.DbgHelpProxy.APIDLL = path
return
# Load symbol config from ENV if present
try:
env_dbghelp_path = os.environ["PFW_DBGHELP_PATH"]
# Setup the dbghelp path used by PFW
set_dbghelp_path(env_dbghelp_path)
except KeyError as e:
pass
class SymbolInfoBase(object):
"""Represent a Symbol.
This class in based on the class `SYMBOL_INFO <https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/ns-dbghelp-symbol_info>`_
with the handling on displacement embeded into it.
"""
# Init on ctypes struct is not always called
# resolver & displacement should be set manually
CHAR_TYPE = None
def __init__(self, *args, **kwargs):
self.resolver = kwargs.get("resolver", None)
#: POUET POUET
self.displacement = kwargs.get("displacement", 0) #: POUET POUET
def as_type(self):
# assert self.Address == 0 ?
return SymbolType(self.Index, self.ModBase, self.resolver)
@property
def name(self):
"""The name of the symbol"""
if not self.NameLen:
return None
size = self.NameLen
addr = ctypes.addressof(self) + type(self).Name.offset
return (self.CHAR_TYPE * size).from_address(addr)[:]
@property
def fullname(self):
"""The fullname of the symbol in the windbg format ``mod!sym+displacement``"""
return str(self)
@property
def addr(self):
"""The address of the symbol"""
return self.Address + self.displacement
@property
def start(self):
"""The address of the start of the symbol
If the symbol include a displacement, it is not taken into account
"""
return self.Address
@property # Fixed ?
def module(self):
"""The module containing the symbol
:type: :class:`SymbolModule`
"""
return self.resolver.get_module(self.ModBase)
@property
def tag(self):
"""The Tag of the module
:type: :class:`~windows.generated_def.winstructs.SymTagEnum`
"""
return gdef.SymTagEnum.mapper[self.Tag]
def __int__(self):
"""An alias for ``addr``"""
return self.addr
def __str__(self):
"""The fullname of the symbol in the windbg format ``mod!sym+displacement``"""
if self.displacement:
return "{self.module.name}!{self.name}+{self.displacement:#x}".format(self=self)
return "{self.module.name}!{self.name}".format(self=self)
def __repr__(self):
if self.displacement:
return '<{0} name="{1}" start={2:#x} displacement={3:#x} tag={4}>'.format(type(self).__name__, self.name, self.start, self.displacement, self.tag.name)
return '<{0} name="{1}" start={2:#x} tag={3}>'.format(type(self).__name__, self.name, self.start, self.tag.name)
class SymbolInfoA(gdef.SYMBOL_INFO, SymbolInfoBase):
"""Represent a Symbol.
This class in based on the class `SYMBOL_INFO <https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/ns-dbghelp-symbol_info>`_
with the handling on displacement embeded into it.s
Exemple:
>>> sh = windows.debug.symbols.VirtualSymbolHandler()
>>> mod = sh.load_file(r"c:\windows\system32\kernelbase.dll")
>>> sym1 = sh["kernelbase!CreateFileW"]
>>> sym2 = sh[int(sym1) + 3]
>>> sym2
<SymbolInfoA name="CreateFileW" start=0x100f20b0 displacement=0x3 tag=SymTagPublicSymbol>
>>> hex(sym2.start)
'0x100f20b0L'
>>> hex(sym2.addr)
'0x100f20b3L'
>>> hex(sym2.displacement)
'0x3L'
>>> str(sym2)
'kernelbase!CreateFileW+0x3'
"""
CHAR_TYPE = gdef.CHAR
class SymbolInfoW(gdef.SYMBOL_INFOW, SymbolInfoBase):
CHAR_TYPE = gdef.WCHAR
# We use the A Api in our code (for now)
SymbolInfo = SymbolInfoA
class SymbolType(object):
def __init__(self, typeid, modbase, resolver):
# Inheritance ?
self.resolver = resolver
self._typeid = typeid # Kind of a handle. Different of typeid property.
self.modbase = modbase
def _get_type_info(self, typeinfo, ires=None):
res = ires
if res is None:
res = TST_TYPE_RES_TYPE.get(typeinfo, gdef.DWORD)()
windows.winproxy.SymGetTypeInfo(self.resolver.handle, self.modbase, self._typeid, typeinfo, ctypes.byref(res))
if ires is not None:
return ires
newres = res.value
if isinstance(res, gdef.LPWSTR):
windows.winproxy.LocalFree(res)
return newres
@property
def name(self):
return self._get_type_info(gdef.TI_GET_SYMNAME)
@property
def size(self):
return self._get_type_info(gdef.TI_GET_LENGTH)
@property
def tag(self):
return self._get_type_info(gdef.TI_GET_SYMTAG)
# Diff type/typeid ?
@property
def type(self):
return self.new_typeid(self._get_type_info(gdef.TI_GET_TYPE))
@property
def typeid(self):
return self.new_typeid(self._get_type_info(gdef.TI_GET_TYPEID))
@property
def basetype(self):
return gdef.BasicType.mapper[self._get_type_info(gdef.TI_GET_BASETYPE)]
@property
def parent(self):
return self.new_typeid(self._get_type_info(gdef.TI_GET_CLASSPARENTID))
@property
def datakind(self):
return gdef.DataKind.mapper[self._get_type_info(gdef.TI_GET_DATAKIND)]
@property
def udtkind(self):
return gdef.UdtKind.mapper[self._get_type_info(gdef.TI_GET_UDTKIND)]
@property
def offset(self):
return self._get_type_info(gdef.TI_GET_OFFSET)
@property
def nb_children(self):
return self._get_type_info(gdef.TI_GET_CHILDRENCOUNT)
@property
def value(self):
return self._get_type_info(gdef.TI_GET_VALUE)
@property
def children(self):
count = self.nb_children
class res_struct(ctypes.Structure):
_fields_ = [("Count", gdef.ULONG), ("Start", gdef.ULONG), ("Types", (gdef.ULONG * count))]
x = res_struct()
x.Count = count
x.Start = 0
self._get_type_info(gdef.TI_FINDCHILDREN, x)
return [self.new_typeid(ch) for ch in x.Types]
# Constructor
@classmethod
def from_symbol_info(cls, syminfo, resolver):
return cls(syminfo.TypeIndex, syminfo.ModBase, resolver)
# Constructor
def new_typeid(self, newtypeid):
return type(self)(newtypeid, self.modbase, self.resolver)
def __repr__(self):
if self.tag == gdef.SymTagBaseType:
return '<{0} <basetype> {1!r}>'.format(type(self).__name__, self.basetype)
elif self.tag == gdef.SymTagPointerType:
target_type = self.type.name
return '<{0} PTR TO "{1}" tag={2}>'.format(type(self).__name__, target_type, self.tag)
return '<{0} name="{1}" tag={2}>'.format(type(self).__name__, self.name, self.tag)
class SymbolModule(gdef.IMAGEHLP_MODULE64):
"""Represent a loaded symbol module
(see `MSDN IMAGEHLP_MODULE64 <https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/ns-dbghelp-imagehlp_module64>`_)
.. note::
This represent a module in the ``symbol space`` for symbol resolution.
This can be completly virtual (particularly in the case of :class:`VirtualSymbolHandler`
"""
# Init on ctypes struct is not always called
# resolver should be set manually
def __init__(self, resolver):
self.resolver = resolver
@property
def addr(self):
"""The load address of the module"""
return self.BaseOfImage
@property
def name(self):
"""The name of the module"""
return self.ModuleName
@property
def path(self):
"""The full path and file name of the file from which symbols were loaded."""
return self.LoadedImageName
@property
def type(self):
"""The type of module (:class:`~windows.generated_def.winstructs.SYM_TYPE`),
which can be one of:
=========== =========================
SymCoff COFF symbols.
SymCv CodeView symbols.
SymDeferred Symbol loading deferred.
SymDia DIA symbols.
SymExport Symbols generated from a DLL export table.
SymNone No symbols are loaded.
SymPdb PDB symbols.
SymSym .sym file.
SymVirtual The virtual module created by SymLoadModuleEx with SLMFLAG_VIRTUAL.
=========== =========================
"""
return self.SymType
@property
def pdb(self):
"""The local path of the loaded PDB if present
Exemple:
>>> sh = windows.debug.symbols.VirtualSymbolHandler()
>>> mod = sh.load_file(r"c:\windows\system32\kernelbase.dll")
>>> mod.pdb
'd:\\symbols\\wkernelbase.pdb\\017FA9C5278235B7E6BFBA74A9A5AAD91\\wkernelbase.pdb'
"""
LoadedPdbName = self.LoadedPdbName
if not LoadedPdbName:
return None
return LoadedPdbName
def __repr__(self):
pdb_basename = self.LoadedPdbName.split(b"\\")[-1]
return '<{0} name="{1}" type={2} pdb="{3}" addr={4:#x}>'.format(type(self).__name__, self.name, self.type.value.name, pdb_basename, self.addr)
# https://docs.microsoft.com/en-us/windows/win32/debug/symbol-handler-initialization
class SymbolHandler(object):
"""Base class of symbol handler"""
def __init__(self, handle, search_path=None, invade_process=False):
# https://docs.microsoft.com/en-us/windows/desktop/api/dbghelp/nf-dbghelp-syminitialize
# This value should be unique and nonzero, but need not be a process handle.
# be sure to use the correct handle.
self.handle = handle #: The handle of the symbol handler
if not engine.options_already_setup:
engine.set_options(DEFAULT_DBG_OPTION)
winproxy.SymInitialize(handle, search_path, invade_process)
def load_module(self, file_handle=None, path=None, name=None, addr=0, size=0, data=None, flags=0):
"""Load a module at a given ``addr``. The module to load can be pass via a ``file_handle``
or the direct ``path`` of the file to load.
:return: :class:`SymbolModule` -- The loaded module
.. note::
The logic of ``SymLoadModuleEx`` seems somewhat strange about the naming of the loaded module.
A custom module ``name`` is only taken into account if the file is passed via a File handle.
To make it more intuitive, if this function is call with a ``path`` and ``name`` and no ``file_handle``,
it will open the path and directly call ``SymLoadModuleEx`` with a file handle and a name.
"""
# Is that a bug in SymLoadModuleEx ?
# To get a custom name for a module it use "path"
# So we need to use file_handle and set a custom path
# ! BUT it means we cannot get a custom name for a module where the path is not explicit and need to be searched
if name is not None and file_handle is None and os.path.exists(path):
try:
f = open(path)
file_handle = windows.utils.get_handle_from_file(f)
path = name
except Exception as e:
pass
# Expect a-string
path = windows.pycompat.raw_encode(path)
try:
load_addr = winproxy.SymLoadModuleEx(self.handle, file_handle, path, name, addr, size, data, flags)
except WindowsError as e:
# if e.winerror == 0:
# Already loaded ?
# What if someone try to load another PE at the same BaseOfDll ?
# return BaseOfDll
raise
return self.get_module(load_addr)
def load_file(self, path, name=None, addr=0, size=0, data=None, flags=0):
"""Load the module ``path`` at ``addr``
:return: :class:`SymbolModule` -- The loaded module
"""
return self.load_module(path=path, name=name, addr=addr, size=size, data=data, flags=flags)
def unload(self, addr):
"""Unload the module at ``addr``"""
return winproxy.SymUnloadModule64(self.handle, addr)
@staticmethod
@ctypes.WINFUNCTYPE(gdef.BOOL, gdef.PCSTR, gdef.DWORD64, ctypes.py_object)
def modules_aggregator(modname, modaddr, ctx):
ctx.append(modaddr)
return True
@property
def modules(self):
"""The list of loaded modules
:return: [:class:`SymbolModule`] -- A list of modules
"""
res = []
windows.winproxy.SymEnumerateModules64(self.handle, self.modules_aggregator, res)
return [self.get_module(addr) for addr in res]
def get_module(self, base):
modinfo = SymbolModule(self)
modinfo.SizeOfStruct = ctypes.sizeof(modinfo)
winproxy.SymGetModuleInfo64(self.handle, base, modinfo)
return modinfo
def symbol_and_displacement_from_address(self, addr):
displacement = gdef.DWORD64()
max_len_size = 0x1000
full_size = ctypes.sizeof(SymbolInfo) + (max_len_size - 1)
buff = windows.utils.BUFFER(SymbolInfo)(size=full_size)
sym = buff[0]
sym.SizeOfStruct = ctypes.sizeof(SymbolInfo)
sym.MaxNameLen = max_len_size
winproxy.SymFromAddr(self.handle, addr, displacement, buff) # SymFromAddrW ?
sym.resolver = self
sym.displacement = displacement.value
return sym
def symbol_from_name(self, name):
max_len_size = 0x1000
full_size = ctypes.sizeof(SymbolInfo) + (max_len_size - 1)
buff = windows.utils.BUFFER(SymbolInfo)(size=full_size)
sym = buff[0]
sym.SizeOfStruct = ctypes.sizeof(SymbolInfo)
sym.MaxNameLen = max_len_size
# Expect a-string
name = windows.pycompat.raw_encode(name)
windows.winproxy.SymFromName(self.handle, name, buff)
sym.resolver = self
sym.displacement = 0
return sym
def resolve(self, name_or_addr):
"""Resolve ``name_or_addr``.
If its an int -> Return the :class:`SymbolInfo` at the address.
If its a string -> Return the :class:`SymbolInfo` corresponding to the symbol name
:return: :class:`SymbolInfo`
.. note::
``__getitem__`` is an alias for ``resolve()``
Exemple:
>>> sh = windows.debug.symbols.VirtualSymbolHandler()
>>> mod = sh.load_file(r"c:\windows\system32\kernelbase.dll")
>>> mod
<SymbolModule name="kernelbase" type=SymPdb pdb="wkernelbase.pdb" addr=0x10000000>
>>> sh.resolve("kernelbase!CreateFileInternal")
<SymbolInfoA name="CreateFileInternal" addr=0x100f2120 tag=SymTagFunction>
>>> sh[0x100f2042]
<SymbolInfoA name="ReadFile" addr=0x100f1ee0 displacement=0x162 tag=SymTagFunction>
>>> str(sh[0x100f2042])
'kernelbase!ReadFile+0x162'
"""
# Only returns None if symbol is not Found ?
if isinstance(name_or_addr, windows.pycompat.anybuff):
return self.symbol_from_name(name_or_addr)
try:
return self.symbol_and_displacement_from_address(name_or_addr)
except WindowsError as e:
if e.winerror != gdef.ERROR_MOD_NOT_FOUND:
raise
# We could not resolve and address -> return None
return None
__getitem__ = resolve
"""Alias to resolve for simpler use"""
@staticmethod
@ctypes.WINFUNCTYPE(gdef.BOOL, ctypes.POINTER(SymbolInfo), gdef.ULONG , ctypes.py_object)
def simple_aggregator(info, size, ctx):
sym = info[0]
fullsize = sym.SizeOfStruct + sym.NameLen
cpy = windows.utils.BUFFER(SymbolInfo)(size=fullsize)
ctypes.memmove(cpy, info, fullsize)
ctx.append(cpy[0])
return True
def search(self, mask, mod=0, tag=0, options=gdef.SYMSEARCH_ALLITEMS, callback=None):
"""Search the symbols matching ``mask`` (``Windbg`` like).
:return: [:class:`SymbolInfo`] -- A list of :class:`SymbolInfo`
>>> sh = windows.debug.symbols.VirtualSymbolHandler()
>>> mod = sh.load_file(r"c:\windows\system32\kernelbase.dll")
>>> sh.search("kernelbase!CreateFile*")
[<SymbolInfoA name="CreateFileInternal" addr=0x100f2120 tag=SymTagFunction>,
<SymbolInfoA name="CreateFileMoniker" addr=0x10117d80 tag=SymTagFunction>,
<SymbolInfoA name="CreateFile2" addr=0x1011e690 tag=SymTagFunction>,
...]
"""
res = []
if callback is None:
callback = self.simple_aggregator
else:
callback = ctypes.WINFUNCTYPE(gdef.BOOL, ctypes.POINTER(SymbolInfo), gdef.ULONG , ctypes.py_object)(callback)
addr = getattr(mod, "addr", mod) # Retrieve mod.addr, else us the value directly
# Expect A-string
mask = windows.pycompat.raw_encode(mask)
windows.winproxy.SymSearch(self.handle, gdef.DWORD64(addr), 0, tag, mask, 0, callback, res, options)
for sym in res:
sym.resolver = self
sym.displacement = 0
return res
def get_symbols(self, addr, callback=None):
res = []
if callback is None:
callback = self.simple_aggregator
else:
callback = ctypes.WINFUNCTYPE(gdef.BOOL, ctypes.POINTER(SymbolInfo), gdef.ULONG , ctypes.py_object)(callback)
try:
windows.winproxy.SymEnumSymbolsForAddr(self.handle, addr, callback, res)
except WindowsError as e:
if e.winerror == gdef.ERROR_MOD_NOT_FOUND:
return []
raise
for sym in res:
sym.resolver = self
sym.displacement = 0
return res
# Type stuff
def get_type(self, name, mod=0):
max_len_size = 0x1000
full_size = ctypes.sizeof(SymbolInfo) + (max_len_size - 1)
buff = windows.utils.BUFFER(SymbolInfo)(size=full_size)
buff[0].SizeOfStruct = ctypes.sizeof(SymbolInfo)
buff[0].MaxNameLen = max_len_size
windows.winproxy.SymGetTypeFromName(self.handle, mod, name, buff)
return SymbolType.from_symbol_info(buff[0], resolver=self)
# TODO: mets de l'huile pour w4kfu
class StackWalker(object):
def __init__(self, resolver, process=None, thread=None, context=None):
self.resolver = resolver
if process is None and thread is None:
raise ValueError("At least a process or thread must be provided")
if process is None:
process = thread.owner
self.process = process
self.thread = thread
self.context = context
if windows.current_process.bitness == 32 and process.bitness == 64:
raise NotImplementedError("StackWalking 64b does not seems to works from 32b process")
def _stack_frame_generator(self):
ctx, machine = self._get_effective_context_and_machine()
frame = self._setup_initial_frame_from_context(ctx, machine)
thread_handle = self.thread.handle if self.thread else None
while True:
try:
windows.winproxy.StackWalkEx(machine,
# dbg.current_process.handle,
self.resolver.handle,
thread_handle,
# 0,
frame,
ctypes.byref(ctx),
None,
winproxy.resolve(winproxy.SymFunctionTableAccess64),
winproxy.resolve(winproxy.SymGetModuleBase64),
None,
0)
except WindowsError as e:
if not e.winerror:
return # No_ERROR -> end of stack walking
raise
yield type(frame).from_buffer_copy(frame) # Make a copy ?
def __iter__(self):
return self._stack_frame_generator()
# Autorise to force the retrieving of 32b stack when code is currently on 64b code ?
def _get_effective_context_and_machine(self):
ctx = self.context or self.thread.context
if self.process.bitness == 32:
# Process is 32b, so the context is inevitably x86
return (ctx, gdef.IMAGE_FILE_MACHINE_I386)
if windows.current_process.bitness == 32:
# If we are 32b, we will only be able to handle x86 stack
# ctx is obligatory a 32b one, as the case us32/target64 is handled
# in __init__ with a NotImplementedError
return (ctx, gdef.IMAGE_FILE_MACHINE_I386)
if self.process.bitness == 64:
# Process is 64b, so the context is inevitably x64
return (ctx, gdef.IMAGE_FILE_MACHINE_AMD64)
# Thing get a little more complicated here :)
# We are a 64b process and target is 32b.
# So we must find-out if we are in 32 or 64b world at the moment.
# The context_syswow.SegCS give us the information
# The context32.SegCs would be always 32
ctxsyswow = dbg.current_thread.context_syswow
if ctxsyswow.SegCs == gdef.CS_USER_32B:
return (ctx, gdef.IMAGE_FILE_MACHINE_I386)
return (ctxsyswow, gdef.IMAGE_FILE_MACHINE_AMD64)
def _setup_initial_frame_from_context(self, ctx, machine):
frame = gdef.STACKFRAME_EX()
frame.AddrPC.Mode = gdef.AddrModeFlat
frame.AddrFrame.Mode = gdef.AddrModeFlat
frame.AddrStack.Mode = gdef.AddrModeFlat
frame.AddrPC.Offset = ctx.pc
frame.AddrStack.Offset = ctx.sp
if machine == gdef.IMAGE_FILE_MACHINE_I386:
frame.AddrFrame.Offset = ctx.Ebp
# Need RBP on 64b ?
return frame
class VirtualSymbolHandler(SymbolHandler):
"""A SymbolHandler where its handle is not a valid process handle
Allow to create/resolve symbol in a 'virtual' process
But all API needing a real process handle will fail
"""
VIRTUAL_HANDLER_COUNTER = itertools.count(0x11223344)
def __init__(self, search_path=None):
handle = next(self.VIRTUAL_HANDLER_COUNTER)
super(VirtualSymbolHandler, self).__init__(handle, search_path, False)
# The VirtualSymbolHandler is not based on an existing process
# So load() in its simplest for should just take the path of the file to load
load = SymbolHandler.load_file
"""An alias for :func:`VirtualSymbolHandler.load_file`"""
def refresh(self):
"""Do nothing for a :class:`VirtualSymbolHandler`"""
return False
class ProcessSymbolHandler(SymbolHandler):
def __init__(self, process, search_path=None, invade_process=False):
super(ProcessSymbolHandler, self).__init__(process.handle, search_path, invade_process)
self.target = process
# The ProcessSymbolHandler is based on an existing process
# So load() in its simplest form should be able to load the symbol for an existing
# module that is already loaded
# Question: should be able to load other module at other address ?
def load(self, name):
"""Load the :class:`SymbolModule` associated with the loaded module ``name`` (as found in the PEB)
:return: :class:`SymbolModule`
Exemple:
>>> sh = windows.debug.symbols.ProcessSymbolHandler(windows.test.pop_proc_64())
<windows.debug.symbols.ProcessSymbolHandler object at 0x033A2C30>
>>> sh
<windows.debug.symbols.ProcessSymbolHandler object at 0x033A2C30>
>>> sh.load("kernelbase.dll")
<SymbolModule name="kernelbase" type=SymDeferred pdb="" addr=0x7ffb5b090000>
>>> sh["kernelbase!CreateProcessA"]
<SymbolInfoA name="CreateProcessA" start=0x7ffb5b2371f0 tag=SymTagPublicSymbol>
"""
mods = [x for x in self.target.peb.modules if x.name == name]
if not mods:
raise ValueError("Could not find module <{0}>".format(name))
assert len(mods) == 1 # Load all if multiple match ?
mod = mods[0]
return self.load_module(addr=mod.baseaddr, path=mod.fullname)
def refresh(self):
"""Update the list of loaded modules to match the modules present in the target process
.. note::
This function only call `SymRefreshModuleList <https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symrefreshmodulelist>`_ for now.
It seems that this function do not handle refreshing a 64b target from a 32b python
Also, on a 32b target from a 64b python it seems to only load symbols for the 64b modules (ntdll + syswow dll)
Exemple:
>>> sh = windows.debug.symbols.ProcessSymbolHandler(windows.test.pop_proc_64())
>>> sh.modules
[]
>>> sh.refresh()
44
>>> sh.modules
[<SymbolModule name="notepad" type=SymDeferred pdb="" addr=0x7ff772b80000>,
<SymbolModule name="ntdll" type=SymDeferred pdb="" addr=0x7ffb5d860000>,
<SymbolModule name="KERNEL32" type=SymDeferred pdb="" addr=0x7ffb5bb90000>,
<SymbolModule name="KERNELBASE" type=SymDeferred pdb="" addr=0x7ffb5b090000>,
...]
"""
return windows.winproxy.SymRefreshModuleList(self.handle)
def stackwalk(self, ctx):
pass
class SymbolEngine(object):
"""Represent the global symbol engine. Just a proxy to get/set global engine options
Its instance can be accessed using ``windows.debug.symbols.engine``
Exemple:
>>> windows.debug.symbols.engine.options
6L
>>> windows.debug.symbols.engine.options = gdef.SYMOPT_UNDNAME
>>> windows.debug.symbols.engine.options
2L
"""
def __init__(self):
# use to now if we need to call the setup of options
# At the first DbgHelp call
self.options_already_setup = False
def set_options(self, options):
self.options_already_setup = True
return windows.winproxy.SymSetOptions(options)
def get_options(self):
return windows.winproxy.SymGetOptions()
options = property(get_options, set_options)
"""The options of the Symbol engine
(`see options <https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symsetoptions#parameters>`_)
.. note::
Default options are: ``gdef.SYMOPT_DEFERRED_LOADS + gdef.SYMOPT_UNDNAME``
"""
engine = SymbolEngine()
"""The instance of the :class:`SymbolEngine`"""
TST_TYPE_RES_TYPE = {
gdef.TI_GET_SYMNAME: gdef.LPWSTR,
gdef.TI_GET_LENGTH: gdef.ULONG64,
gdef.TI_GET_ADDRESS: gdef.ULONG64,
gdef.TI_GTIEX_REQS_VALID: gdef.ULONG64,
gdef.TI_GET_SYMTAG: gdef.SymTagEnum,
gdef.TI_GET_VALUE: windows.com.Variant,
}
@@ -1,71 +0,0 @@
from . import windef
from . import winstructs
def bitness():
"""Return 32 or 64"""
import platform
bits = platform.architecture()[0]
return int(bits[:2])
# Use windows.current_process.bitness ? need to fix problem of this imported before the creation of windows.current_process
if bitness() == 32:
winstructs.CONTEXT = winstructs.CONTEXT32
winstructs.PCONTEXT = winstructs.PCONTEXT32
winstructs.LPCONTEXT = winstructs.LPCONTEXT32
winstructs.EXCEPTION_POINTERS = winstructs.EXCEPTION_POINTERS32
winstructs.PEXCEPTION_POINTERS = winstructs.PEXCEPTION_POINTERS32
winstructs.SYSTEM_MODULE = winstructs.SYSTEM_MODULE32
winstructs.SYSTEM_MODULE_INFORMATION = winstructs.SYSTEM_MODULE_INFORMATION32
winstructs.PALPC_PORT_ATTRIBUTES = winstructs.PALPC_PORT_ATTRIBUTES32
winstructs.ALPC_PORT_ATTRIBUTES = winstructs.ALPC_PORT_ATTRIBUTES32
winstructs.PORT_MESSAGE = winstructs.PORT_MESSAGE32
winstructs.PPORT_MESSAGE = winstructs.PPORT_MESSAGE32
# CFGMGR32
winstructs.IRQ_RESOURCE = winstructs.IRQ_RESOURCE_32
# Socket
windef.WSADATA = winstructs.WSADATA32
windef.INVALID_SOCKET = windef.INVALID_SOCKET32
else:
winstructs.CONTEXT = winstructs.CONTEXT64
winstructs.PCONTEXT = winstructs.PCONTEXT64
winstructs.LPCONTEXT = winstructs.LPCONTEXT64
winstructs.EXCEPTION_POINTERS = winstructs.EXCEPTION_POINTERS64
winstructs.PEXCEPTION_POINTERS = winstructs.PEXCEPTION_POINTERS64
winstructs.SYSTEM_MODULE = winstructs.SYSTEM_MODULE64
winstructs.SYSTEM_MODULE_INFORMATION = winstructs.SYSTEM_MODULE_INFORMATION64
winstructs.PALPC_PORT_ATTRIBUTES = winstructs.PALPC_PORT_ATTRIBUTES64
winstructs.ALPC_PORT_ATTRIBUTES = winstructs.ALPC_PORT_ATTRIBUTES64
winstructs.PORT_MESSAGE = winstructs.PORT_MESSAGE64
winstructs.PPORT_MESSAGE = winstructs.PPORT_MESSAGE64
# CFGMGR32
winstructs.IRQ_RESOURCE = winstructs.IRQ_RESOURCE_64
# Socket
windef.WSADATA = winstructs.WSADATA64
windef.INVALID_SOCKET = windef.INVALID_SOCKET64
from . import winfuncs
from . import windef
from . import interfaces
# Fuck it
from .winstructs import *
from .winfuncs import *
from .windef import *
from .interfaces import *
@@ -1,22 +0,0 @@
def pretty_print_ctypes_type(t):
format = "{0}"
if issubclass(t, ctypes.Array):
format = "[{0}" + "* {0}]".format(t._length_)
t = t._type_
if issubclass(t, ctypes._Pointer):
format = format.format("Pointer({0})")
t = t._type_
if issubclass(t, ctypes.Structure):
return format.format(":class:`{0}`".format(t.__name__))
return t
def autodoc_ctypes_struct(struct):
doc = ["fields:"]
for name, type in struct._fields_:
doc.append(" {0} -> {1}".format(name, pretty_print_ctypes_type(type)))
struct.__doc__ = "\n\n".join(doc)
return struct
@@ -1,74 +0,0 @@
import sys
if sys.version_info.major >= 3:
long = int
class Flag(long):
def __new__(cls, name, value):
return super(Flag, cls).__new__(cls, value)
def __init__(self, name, value):
self.name = name
def __repr__(self):
return "{0}({1:#x})".format(self.name, self)
# Custom __str__ removed for multiple reason
# Main one -> it breaks the json encoding of structure with flags :)
# Moving to a new politic -> if people want the name in a string use {x!r}
# The __str__ of security descriptor & guid will change soon as well :)
# __str__ = __repr__
# Fix pickling with protocol 2
def __getnewargs__(self, *args):
return self.name, long(self)
class StrFlag(str):
def __new__(cls, name, value):
if isinstance(value, cls):
return value
return super(StrFlag, cls).__new__(cls, value)
def __init__(self, name, value):
self.name = name
def __repr__(self):
return "{0}({1})".format(self.name, str.__repr__(self))
# __str__ = __repr__
# Fix pickling with protocol 2
def __getnewargs__(self, *args):
return self.name, str.__str__(self)
def make_flag(name, value):
if isinstance(value, (int, long)):
return Flag(name, value)
return StrFlag(name, value)
class FlagMapper(dict):
def __init__(self, *values):
self.update({x:x for x in values})
def __missing__(self, key):
return key
class FlagExatractor(object):
def __init__(self, attr, values):
self.attr = attr
self.attrsize = attr.size * 8
self.mapper = FlagMapper(*values)
def __get__(self, obj, type):
if obj is None:
return self
# Retrieve the real value
value = self.attr.__get__(obj)
generator = (1 << i for i in range(self.attrsize))
return [self.mapper[f] for f in generator if value & f]
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
-130
View File
@@ -1,130 +0,0 @@
import sys
import ctypes
import windows
import windows.utils as utils
from . import native_exec
from .generated_def import winfuncs
from .generated_def.windef import PAGE_EXECUTE_READWRITE
from .generated_def.winstructs import *
# TODO Not a big fan of importing 'meta' every load
# Should do an Hook API that take the winproxy function (not generate every hook possible)
import windows.generated_def.meta
class Callback(object):
"""Give type information to hook callback"""
def __init__(self, *types):
self.types = types
def __call__(self, func):
func._types_info = self.types
return func
class KnownCallback(object):
types = ()
def __call__(self, func):
func._types_info = self.types
return func
def add_callback_to_module(callback):
setattr(sys.modules[__name__], type(callback).__name__, callback)
# Generate IATCallback decorator for all known functions
for func in windows.generated_def.meta.functions:
prototype = getattr(winfuncs, func + "Prototype")
callback_name = func + "Callback"
class CallBackDeclaration(KnownCallback):
types = (prototype._restype_,) + prototype._argtypes_
CallBackDeclaration.__name__ = callback_name
add_callback_to_module(CallBackDeclaration())
class IATHook(object):
"""Look at my hook <3"""
def __init__(self, IAT_entry, callback, types=None):
if types is None:
if not hasattr(callback, "_types_info"):
raise ValueError("Callback for IATHook has no type infomations")
types = callback._types_info
self.original_types = types
self.callback_types = self.transform_arguments(self.original_types)
self.entry = IAT_entry
self.callback = callback
## No more circular ref -> but stub is destroyed -> segv :(
self.stub = ctypes.WINFUNCTYPE(*self.callback_types)(self.hook_callback)
# stub = ctypes.WINFUNCTYPE(*self.callback_types)(self.hook_callback)
# self.stub_addr = ctypes.cast(stub, PVOID) # Same problem as keep stub... (GC..)
self.stub_addr = ctypes.cast(self.stub, PVOID).value # Same problem as keep stub... (GC..)
# IAT_entry.stub = stub
self.realfunction = ctypes.WINFUNCTYPE(*types)(IAT_entry.nonhookvalue)
self.is_enable = False
def transform_arguments(self, types):
res = []
for type in types:
if type in (ctypes.c_wchar_p, ctypes.c_char_p):
res.append(ctypes.c_void_p)
else:
res.append(type)
return res
def enable(self):
"""Enable the IAT hook: you MUST keep a reference to the IATHook while the hook is enabled"""
with utils.VirtualProtected(self.entry.addr, ctypes.sizeof(PVOID), PAGE_EXECUTE_READWRITE):
self.entry.value = self.stub_addr
self.is_enable = True
self.entry.enabled = True
def disable(self):
"""Disable the IAT hook"""
with utils.VirtualProtected(self.entry.addr, ctypes.sizeof(PVOID), PAGE_EXECUTE_READWRITE):
self.entry.value = self.entry.nonhookvalue
self.is_enable = False
self.entry.enabled = True
def hook_callback(self, *args):
adapted_args = []
for value, type in zip(args, self.original_types[1:]):
if type == ctypes.c_wchar_p:
adapted_args.append(ctypes.c_wchar_p(value))
elif type == ctypes.c_char_p:
adapted_args.append(ctypes.c_char_p((value)))
else:
adapted_args.append(value)
def real_function(*args):
if args == ():
args = adapted_args
return self.realfunction(*args)
return self.callback(*adapted_args, real_function=real_function)
## New simple hook API based on winproxy
def setup_hook(target, hook, dll_to_hook):
"TODO: Test and doc :D"
dll_name, api_name = windows.winproxy.get_target(target)
prototype = target.prototype
hook._types_info = (prototype._restype_,) + prototype._argtypes_
if not dll_name.endswith(".dll"):
dll_name += ".dll"
# Get the peb of our process
peb = windows.current_process.peb
# Get the dll_to_hook
module_to_hook = [m for m in peb.modules if m.name.lower() == dll_to_hook.lower()][0]
# Get the iat entries for DLL dll_name
adv_imports = module_to_hook.pe.imports[dll_name]
# Get RegOpenKeyExA iat entry
iat = [n for n in adv_imports if n.name == api_name][0]
iat.set_hook(hook)
return iat
-438
View File
@@ -1,438 +0,0 @@
import struct
import ctypes
import os
import sys
import windows
import windows.utils as utils
import windows.generated_def as gdef
from .native_exec import simple_x86 as x86
from .native_exec import simple_x64 as x64
from windows.generated_def import STATUS_THREAD_IS_TERMINATING
from windows.native_exec.nativeutils import GetProcAddress64, GetProcAddress32
from windows.dbgprint import dbgprint
class InjectionFailedError(WindowsError):
pass
def get_kernel32_dll_name():
# Our injected shellcode search for 'kernel32.dll' with a strcmp
# The BaseDllName of k32 might be 'KERNEL32.DLL' or 'kernel32.dll' on different system32
# We base the name on our own loaded kernel32
k32 = [m for m in windows.current_process.peb.modules if m.name == "kernel32.dll"]
assert len(k32) == 1
k32name = k32[0].BaseDllName.str
return (k32name + "\x00").encode("utf-16-le")
def perform_manual_getproc_loadlib_32(target, dll_name):
dll = get_kernel32_dll_name()
api = "LoadLibraryA\x00"
dll_to_load = dll_name + "\x00"
RemoteManualLoadLibray = x86.MultipleInstr()
code = RemoteManualLoadLibray
code += x86.Mov("ECX", x86.mem("[ESP + 4]"))
code += x86.Push(x86.mem("[ECX + 4]"))
code += x86.Push(x86.mem("[ECX]"))
code += x86.Call(":FUNC_GETPROCADDRESS32")
code += x86.Push(x86.mem("[ECX + 8]"))
code += x86.Call("EAX") # LoadLibrary
code += x86.Pop("ECX")
code += x86.Pop("ECX")
code += x86.Ret()
RemoteManualLoadLibray += GetProcAddress32
with target.allocated_memory(0x1000) as addr:
addr2 = addr + len(dll)
addr3 = addr2 + len(api)
addr4 = addr3 + len(dll_to_load)
target.write_memory(addr, dll)
target.write_memory(addr2, api)
target.write_memory(addr3, dll_to_load)
target.write_qword(addr4, addr)
target.write_qword(addr4 + 4, addr2)
target.write_qword(addr4 + 0x8, addr3)
t = target.execute(RemoteManualLoadLibray.get_code(), addr4)
t.wait()
if not t.exit_code:
raise InjectionFailedError("Injection of <{0}> failed".format(dll_name))
return True
def perform_manual_getproc_loadlib_64(target, dll_name):
dll = get_kernel32_dll_name()
api = "LoadLibraryA\x00"
dll_to_load = dll_name + "\x00"
RemoteManualLoadLibray = x64.MultipleInstr()
code = RemoteManualLoadLibray
code += x64.Mov("R15", "RCX")
code += x64.Mov("RCX", x64.mem("[R15 + 0]"))
code += x64.Mov("RDX", x64.mem("[R15 + 8]"))
code += x64.Call(":FUNC_GETPROCADDRESS64")
code += x64.Mov("RCX", x64.mem("[R15 + 0x10]"))
code += x64.Push("RCX")
code += x64.Push("RCX")
code += x64.Push("RCX")
code += x64.Call("RAX") # LoadLibrary
code += x64.Pop("RCX")
code += x64.Pop("RCX")
code += x64.Pop("RCX")
code += x64.Ret()
RemoteManualLoadLibray += GetProcAddress64
with target.allocated_memory(0x1000) as addr:
addr2 = addr + len(dll)
addr3 = addr2 + len(api)
addr4 = addr3 + len(dll_to_load)
target.write_memory(addr, dll)
target.write_memory(addr2, api)
target.write_memory(addr3, dll_to_load)
target.write_qword(addr4, addr)
target.write_qword(addr4 + 8, addr2)
target.write_qword(addr4 + 0x10, addr3)
t = target.execute(RemoteManualLoadLibray.get_code(), addr4)
t.wait()
if not t.exit_code:
raise InjectionFailedError("Injection of <{0}> failed".format(dll_name))
return True
def generate_simple_LoadLibraryW_64(load_libraryW, remote_store):
code = RemoteLoadLibrayStub = x64.MultipleInstr()
code += x64.Mov("RAX", load_libraryW)
code += (x64.Push("RDI") * 5) # Prepare stack
code += x64.Call("RAX")
code += (x64.Pop("RDI") * 5) # Clean stack
code += x64.Mov(x64.deref(remote_store), "RAX")
code += x64.Ret()
return RemoteLoadLibrayStub.get_code()
def perform_manual_getproc_loadlib(target, *args, **kwargs):
if target.bitness == 32:
return perform_manual_getproc_loadlib_32(target, *args, **kwargs)
return perform_manual_getproc_loadlib_64(target, *args, **kwargs)
def load_dll_in_remote_process(target, dll_path):
rpeb = target.peb
if rpeb.Ldr:
# LDR est parcourable, ca va etre deja plus simple..
modules = rpeb.modules
if any(mod.fullname.lower() == dll_path.lower() for mod in modules):
# DLL already loaded
dbgprint("DLL already present in target", "DLLINJECT")
return False
k32 = [mod for mod in modules if mod.name.lower() == "kernel32.dll"]
if k32:
# We have kernel32 \o/
k32 = k32[0]
try:
load_libraryW = k32.pe.exports["LoadLibraryW"]
except KeyError:
raise ValueError("Kernel32 have no export <LoadLibraryA> (wtf)")
with target.allocated_memory(0x1000) as addr:
if target.bitness == 32:
target.write_memory(addr, (dll_path + "\x00").encode('utf-16le'))
t = target.create_thread(load_libraryW, addr)
t.wait()
module_baseaddr = t.exit_code
else:
# For 64b target we need a special stub as the return value of
# load_libraryW does not fit in t.exit_code (DWORD)
retval_addr = addr
target.write_ptr(retval_addr, 0)
addr += ctypes.sizeof(ctypes.c_ulonglong)
full_dll_name = (dll_path + "\x00").encode('utf-16le')
target.write_memory(addr, full_dll_name)
param_addr = addr
addr += len(full_dll_name)
shellcode_addr = addr
shellcode = generate_simple_LoadLibraryW_64(load_libraryW, retval_addr)
target.write_memory(shellcode_addr, shellcode)
t = target.create_thread(shellcode_addr, param_addr)
t.wait()
module_baseaddr = target.read_ptr(retval_addr)
if not module_baseaddr:
raise InjectionFailedError(u"Injection of <{0}> failed".format(dll_path))
dbgprint("DLL Injected via LoadLibray", "DLLINJECT")
# Cannot return the full return value of load_libraryW in 64b target.. (exit_code is a DWORD)
return module_baseaddr
# Hardcore mode
# We don't have k32 or PEB->Ldr
# Go inject a GetProcAddress(LoadLib) + LoadLib shellcode :D
dbgprint("DLL Via manual getproc / loadlib", "DLLINJECT")
if target.bitness == 32:
return perform_manual_getproc_loadlib_32(target, dll_path)
return perform_manual_getproc_loadlib_64(target, dll_path)
python_function_32_bits = {}
def generate_python_exec_shellcode_32(target, PyDll):
pymodule = [mod for mod in target.peb.modules if mod.name == PyDll][0]
base = pymodule.baseaddr
if not python_function_32_bits:
Py_exports = pymodule.pe.exports
python_function_32_bits["PyEval_InitThreads"] = Py_exports["PyEval_InitThreads"] - base
python_function_32_bits["Py_IsInitialized"] = Py_exports["Py_IsInitialized"] - base
python_function_32_bits["PyGILState_Release"] = Py_exports["PyGILState_Release"] - base
python_function_32_bits["PyGILState_Ensure"] = Py_exports["PyGILState_Ensure"] - base
python_function_32_bits["PyEval_SaveThread"] = Py_exports["PyEval_SaveThread"] - base
python_function_32_bits["Py_Initialize"] = Py_exports["Py_Initialize"] - base
python_function_32_bits["PyRun_SimpleString"] = Py_exports["PyRun_SimpleString"] - base
Py_exports = python_function_32_bits
PyEval_InitThreads = Py_exports["PyEval_InitThreads"] + base
Py_IsInitialized = Py_exports["Py_IsInitialized"] + base
PyGILState_Release = Py_exports["PyGILState_Release"] + base
PyGILState_Ensure = Py_exports["PyGILState_Ensure"] + base
PyEval_SaveThread = Py_exports["PyEval_SaveThread"] + base
Py_Initialize = Py_exports["Py_Initialize"] + base
PyRun_SimpleString = Py_exports["PyRun_SimpleString"] + base
code = x86.MultipleInstr()
code += x86.Mov('EAX', Py_IsInitialized)
code += x86.Call('EAX')
code += x86.Mov("EDI", "EAX")
code += x86.Cmp("EAX", 0)
code += x86.Jnz(":DO_ENSURE")
code += x86.Mov('EAX', Py_Initialize)
code += x86.Call('EAX')
# https://docs.python.org/3/c-api/init.html#c.PyEval_InitThreads
code += x86.Mov('EAX', PyEval_InitThreads)
code += x86.Call('EAX')
code += x86.Label(":DO_ENSURE")
code += x86.Mov('EAX', PyGILState_Ensure)
code += x86.Call('EAX')
code += x86.Push('EAX')
# Get the string to execute from parameters
code += x86.Mov("EAX", x86.mem("[ESP + 0x8]"))
code += x86.Push('EAX')
code += x86.Mov('EAX', PyRun_SimpleString)
code += x86.Call('EAX')
code += x86.Mov("ESI", "EAX")
code += x86.Mov('EAX', PyGILState_Release)
code += x86.Call('EAX')
code += x86.Pop('EAX')
code += x86.Cmp("EDI", 0)
code += x86.Jnz(":RETURN")
# If PyEval_InitThreads was called (init done in this thread)
# We must release the GIL
code += x86.Mov('EAX', PyEval_SaveThread)
code += x86.Call('EAX')
code += x86.Label(":RETURN")
code += x86.Mov("EAX", "ESI")
code += x86.Pop("EDI")
code += x86.Ret()
return code.get_code()
python_function_64_bits = {}
def generate_python_exec_shellcode_64(target, PyDll):
pymodule = [mod for mod in target.peb.modules if mod.name == PyDll][0]
base = pymodule.baseaddr
if not python_function_64_bits:
Py_exports = pymodule.pe.exports
python_function_64_bits["PyEval_InitThreads"] = Py_exports["PyEval_InitThreads"] - base
python_function_64_bits["Py_IsInitialized"] = Py_exports["Py_IsInitialized"] - base
python_function_64_bits["PyGILState_Release"] = Py_exports["PyGILState_Release"] - base
python_function_64_bits["PyGILState_Ensure"] = Py_exports["PyGILState_Ensure"] - base
python_function_64_bits["PyEval_SaveThread"] = Py_exports["PyEval_SaveThread"] - base
python_function_64_bits["Py_Initialize"] = Py_exports["Py_Initialize"] - base
python_function_64_bits["PyRun_SimpleString"] = Py_exports["PyRun_SimpleString"] - base
Py_exports = python_function_64_bits
PyEval_InitThreads = Py_exports["PyEval_InitThreads"] + base
Py_IsInitialized = Py_exports["Py_IsInitialized"] + base
PyGILState_Release = Py_exports["PyGILState_Release"] + base
PyGILState_Ensure = Py_exports["PyGILState_Ensure"] + base
PyEval_SaveThread = Py_exports["PyEval_SaveThread"] + base
Py_Initialize = Py_exports["Py_Initialize"] + base
PyRun_SimpleString = Py_exports["PyRun_SimpleString"] + base
Reserve_space_for_call = x64.MultipleInstr([x64.Push('RDI')] * 4)
Clean_space_for_call = x64.MultipleInstr([x64.Pop('RDI')] * 4)
code = x64.MultipleInstr()
# Do stack alignement
code += x64.Push('RCX')
code += Reserve_space_for_call
code += x64.Mov('RAX', Py_IsInitialized)
code += x64.Call('RAX')
code += x64.Mov("RDI", "RAX")
code += x64.Cmp("RAX", 0)
code += x64.Jnz(":DO_ENSURE")
code += x64.Mov('RAX', Py_Initialize)
code += x64.Call('RAX')
# https://docs.python.org/3/c-api/init.html#c.PyEval_InitThreads
code += x64.Mov('RAX', PyEval_InitThreads)
code += x64.Call('RAX')
code += x64.Label(":DO_ENSURE")
code += x64.Mov('RAX', PyGILState_Ensure)
code += x64.Call('RAX')
code += x64.Mov('R15', 'RAX')
code += x64.Mov("RCX", x64.mem("[RSP + 0x20]"))
code += x64.Mov('RAX', PyRun_SimpleString)
code += x64.Call('RAX')
code += x64.Mov('RCX', 'R15')
code += x64.Mov('R15', 'RAX')
code += x64.Mov('RAX', PyGILState_Release)
code += x64.Call('RAX')
code += x64.Cmp("RDI", 0)
code += x64.Jnz(":RETURN")
# If PyEval_InitThreads was called (init done in this thread)
# We must release the GIL
code += x64.Mov('RAX', PyEval_SaveThread)
code += x64.Call('RAX')
code += x64.Label(":RETURN")
code += Clean_space_for_call
# Remove stack alignement
code += x64.Pop('RCX')
code += x64.Mov("RAX", "R15")
code += x64.Ret()
return code.get_code()
def inject_python_command(target, code_injected, PYDLL):
"""Postulate: PYDLL is already loaded in target process"""
PYCODE = code_injected + "\x00"
# TODO: free this (how ? when ?)
remote_python_code_addr = target.virtual_alloc(len(PYCODE))
target.write_memory(remote_python_code_addr, PYCODE)
shellcode_addr = getattr(target, "_execute_python_shellcode", None)
if shellcode_addr is not None:
return shellcode_addr, remote_python_code_addr
if target.bitness == 32:
shellcode_generator = generate_python_exec_shellcode_32
else:
shellcode_generator = generate_python_exec_shellcode_64
shellcode = shellcode_generator(target, PYDLL)
shellcode_addr = target.virtual_alloc(len(shellcode))
target.write_memory(shellcode_addr, shellcode)
target._execute_python_shellcode = shellcode_addr
return shellcode_addr, remote_python_code_addr
def get_dll_name_from_python_version():
version = sys.version_info
return "python{v.major}{v.minor}.dll".format(v=version)
def find_python_dll_to_inject(target_bitness):
pydll_name = get_dll_name_from_python_version()
if windows.current_process.bitness == target_bitness:
# We can inject our own DLL
pymodules = [m for m in windows.current_process.peb.modules if m.name == pydll_name]
assert len(pymodules) == 1
return pymodules[0].fullname
# Okay, so we need to find the DLL to inject.
# Problem is, for py3 the DLL is not un system32, so we need for search for it
# Simpler solution is the registry
# Add a check using %PATH% ?
assert windows.system.bitness == 64, "How can we have process of different bitness on 32b system ?"
if sys.version_info.major == 2:
# Python2 DLL are located in system32/syswow64
# We know that we are looking to DLL of the other bitness
if windows.current_process.bitness == 32:
# We need to check that the real system32\pythonXX.dll exists
systempath = "sysnative"
else:
# We need to check that the wow64 system32\pythonXX.dll exists
systempath = "syswow64"
if os.path.exists(os.path.join(os.environ["windir"], systempath, pydll_name)):
# In any way (32b ou 64b) the target process will load system32\pydll
# If the target is 32b the wow64 layer will translate it
return os.path.join(os.environ["windir"], "system32", pydll_name)
# If not found this way -> may mean we have a install only for a user, give registry a try
# Python 3 dll must be located using the registry
for base_key in "HKEY_LOCAL_MACHINE", "HKEY_CURRENT_USER":
# Open the registry in 64b view regardless of current process bitness
regbase = windows.system.registry(base_key, gdef.KEY_WOW64_64KEY | gdef.KEY_READ)
# we cannot use sys.winver as we are looking for the OTHER version
# But from Python <PCbuild/python.props> it looks like format is
# {Major}.{Minor}{-32}(for 32b build)
# This code do not handle -test version
winver_base = sys.winver[:3] # major-minor
if target_bitness == 64:
pyinstallkeys = [regbase(r"SOFTWARE\Python\PythonCore")(winver_base)]
else:
pyinstallkeys = [regbase(r"SOFTWARE\Python\PythonCore")(winver_base + "-32"),
regbase(r"SOFTWARE\WOW6432Node\Python\PythonCore")(winver_base + "-32")]
for pyinstallkey in pyinstallkeys:
if not pyinstallkey.exists:
continue
try:
pyinstallpath = pyinstallkey("InstallPath")[""].value
final_path = os.path.join(pyinstallpath, pydll_name)
assert os.path.exists(final_path), "Could not find <{0}> pydll referenced from registry".format(final_path)
return final_path
except WindowsError as e:
if e.winerror != gdef.ERROR_FILE_NOT_FOUND:
raise
# Not found
continue
# Could not find a valid installation
raise ValueError("Could not find a path for python-dll <{0}>({1}bits)".format(sys.winver, target_bitness))
def execute_python_code(process, code):
# Cache the value ?
py_dll_name = get_dll_name_from_python_version()
pydll_path = find_python_dll_to_inject(process.bitness)
if sys.version_info.major == 3:
# FOr py3, we may have a per-user install.
# Meaning that the vcruntime140.dll will not be in the injected process path
# Find it & load-it as well, it should be in the same directory as pythonxx.dll
vc_runtime_dll = os.path.join(os.path.dirname(pydll_path), "vcruntime140.dll")
load_dll_in_remote_process(process, vc_runtime_dll)
# Try to inject the vcrunt
load_dll_in_remote_process(process, pydll_path)
shellcode, pythoncode = inject_python_command(process, code, py_dll_name)
t = process.create_thread(shellcode, pythoncode)
return t
retrieve_exc = r"""
import traceback
import sys
addr = {0}
txt = "".join(traceback.format_exception(sys.last_type, sys.last_value, sys.last_traceback))
import ctypes
size = ctypes.c_uint.from_address(addr)
size.value = len(txt)
buff = (ctypes.c_char * len(txt)).from_address(addr + ctypes.sizeof(ctypes.c_uint))
buff[:] = txt.encode()
"""
def retrieve_last_exception_data(process):
with process.allocated_memory(0x1000) as mem:
execute_python_code(process, retrieve_exc.format(mem)).wait()
size = struct.unpack("<I", process.read_memory(mem, ctypes.sizeof(ctypes.c_uint)))[0]
data = process.read_memory(mem + ctypes.sizeof(ctypes.c_uint), size)
return data
class RemotePythonError(Exception):
pass
def safe_execute_python(process, code):
t = execute_python_code(process, code)
t.wait() # Wait terminaison of the thread
if t.exit_code == 0:
return True
if t.exit_code == STATUS_THREAD_IS_TERMINATING or process.is_exit:
raise WindowsError("{0} died during execution of python command".format(process))
if t.exit_code != 0xffffffff:
raise ValueError("Unknown exit code {0}".format(hex(t.exit_code)))
data = retrieve_last_exception_data(process)
raise RemotePythonError(data)
@@ -1,3 +0,0 @@
from .native_function import create_function
__all__ = ["create_function"]
@@ -1,159 +0,0 @@
import ctypes
import struct
import native_function
import simple_x86 as x86
import simple_x64 as x64
from windows.generated_def.winstructs import *
def _bitness():
"""Returns 32 or 64"""
import platform
bits = platform.architecture()[0]
return int(bits[:2])
class X86CpuidResult(ctypes.Structure):
"""Raw result of the CPUID instruction"""
_fields_ = [("EAX", DWORD),
("EBX", DWORD),
("ECX", DWORD),
("EDX", DWORD)]
fields = [f[0] for f in _fields_]
"""Fields of the Structure"""
class X64CpuidResult(ctypes.Structure):
_fields_ = [("RAX", ULONG64),
("RBX", ULONG64),
("RCX", ULONG64),
("RDX", ULONG64)]
class X86IntelCpuidFamilly(ctypes.Structure):
_fields_ = [("SteppingID", DWORD, 4),
("ModelID", DWORD, 4),
("FamilyID", DWORD, 4),
("ProcessorType", DWORD, 2),
("Reserved2", DWORD, 2),
("ExtendedModel", DWORD, 4),
("ExtendedFamily", DWORD, 8),
("Reserved", DWORD, 2)]
fields = [f[0] for f in _fields_]
"""Fields of the Structure"""
class X86AmdCpuidFamilly(ctypes.Structure):
_fields_ = [("SteppingID", DWORD, 4),
("ModelID", DWORD, 4),
("FamilyID", DWORD, 4),
("Reserved2", DWORD, 4),
("ExtendedModel", DWORD, 4),
("ExtendedFamily", DWORD, 8),
("Reserved", DWORD, 2)]
fields = [f[0] for f in _fields_]
"""Fields of the Structure"""
cpuid32_code = x86.MultipleInstr()
cpuid32_code += x86.Push('EDI')
cpuid32_code += x86.Mov('EAX', x86.mem('[ESP + 0x8]'))
cpuid32_code += x86.Mov('EDI', x86.mem('[ESP + 0xc]'))
cpuid32_code += x86.Cpuid()
cpuid32_code += x86.Mov(x86.mem('[EDI + 0x0]'), 'EAX')
cpuid32_code += x86.Mov(x86.mem('[EDI + 0x4]'), 'EBX')
cpuid32_code += x86.Mov(x86.mem('[EDI + 0x8]'), 'ECX')
cpuid32_code += x86.Mov(x86.mem('[EDI + 0xc]'), 'EDX')
cpuid32_code += x86.Pop('EDI')
cpuid32_code += x86.Ret()
do_cpuid32 = native_function.create_function(cpuid32_code.get_code(), [DWORD, DWORD, PVOID])
cpuid64_code = x64.MultipleInstr()
cpuid64_code += x64.Mov('RAX', 'RCX')
cpuid64_code += x64.Mov('R10', 'RDX')
cpuid64_code += x64.Cpuid()
# For now assembler cannot do 32bits register in x64
cpuid64_code += x64.Mov(x64.mem('[R10 + 0x00]'), 'RAX')
cpuid64_code += x64.Mov(x64.mem('[R10 + 0x08]'), 'RBX')
cpuid64_code += x64.Mov(x64.mem('[R10 + 0x10]'), 'RCX')
cpuid64_code += x64.Mov(x64.mem('[R10 + 0x18]'), 'RDX')
cpuid64_code += x64.Ret()
do_cpuid64 = native_function.create_function(cpuid64_code.get_code(), [DWORD, DWORD, PVOID])
def x86_cpuid(req):
"""Performs a CPUID in 32bits mode
:rtype: :class:`X86CpuidResult`
"""
cpuid_res = X86CpuidResult()
do_cpuid32(req, ctypes.addressof(cpuid_res))
return cpuid_res
def x64_cpuid(req):
"""Performs a CPUID in 64bits mode
:rtype: :class:`X86CpuidResult`
"""
cpuid_res = X64CpuidResult()
do_cpuid64(req, ctypes.addressof(cpuid_res))
# For now assembler cannot do 32bits register in x64
return X86CpuidResult(cpuid_res.RAX, cpuid_res.RBX, cpuid_res.RCX, cpuid_res.RDX)
if _bitness() == 32:
_do_cpuid = x86_cpuid
else:
_do_cpuid = x64_cpuid
def do_cpuid(req):
"""Performs a CPUID for the current process bitness
:rtype: :class:`X86CpuidResult`
"""
return _do_cpuid(req)
def get_vendor_id():
"""Extracts the VendorId string from CPUID
:rtype: :class:`str`
"""
cpuid_res = do_cpuid(0)
return struct.pack("<III", cpuid_res.EBX, cpuid_res.EDX, cpuid_res.ECX)
# platform.processor() could do the trick
def is_intel_proc():
"""get_vendor_id() == 'GenuineIntel'"""
return get_vendor_id() == "GenuineIntel"
def is_amd_proc():
"""get_vendor_id() == 'AuthenticAMD'"""
return get_vendor_id() == "AuthenticAMD"
def get_proc_family_model():
"""Extracts the family and model based on vendorId
:rtype: (ComputedFamily, ComputedModel)
"""
cpuid_res = do_cpuid(1)
if is_intel_proc():
format = X86IntelCpuidFamilly
elif is_amd_proc():
format = X86AmdCpuidFamilly
else:
raise NotImplementedError("Cannot get familly information of proc <{0}>".format(get_vendor_id()))
infos = format.from_buffer_copy(struct.pack("<I", cpuid_res.EAX))
if infos.FamilyID == 0x6 or infos.FamilyID == 0x0F:
ComputedModel = infos.ModelID + (infos.ExtendedModel << 4)
else:
ComputedModel = infos.ModelID
if infos.FamilyID == 0x0F:
ComputedFamily = infos.FamilyID + infos.ExtendedFamily
else:
ComputedFamily = infos.FamilyID
return ComputedFamily, ComputedModel
@@ -1,87 +0,0 @@
import ctypes
import mmap
import platform
import sys
import windows
import windows.winproxy
import windows.generated_def as gdef
from . import simple_x86 as x86
from . import simple_x64 as x64
class CustomAllocator(object):
int_size = {'32bit': 4, '64bit': 8}
def __init__(self):
self.maps = []
self.cur_offset = 0
self.cur_page_size = 0 # Force get_new_page on first request
self.names = []
@classmethod
def get_int_size(cls):
bits = platform.architecture()[0]
if bits not in cls.int_size:
raise ValueError("Unknow platform bits <{0}>".format(bits))
return cls.int_size[bits]
def get_new_page(self, size):
addr = windows.winproxy.VirtualAlloc(0, size, 0x1000, gdef.PAGE_EXECUTE_READWRITE)
mymap = (ctypes.c_char * size).from_address(addr)
mymap.addr = addr
self.maps.append(mymap)
self.cur_offset = 0
self.cur_page_size = size
def reserve_size(self, size):
if size + self.cur_offset > self.cur_page_size:
self.get_new_page((size + 0x1000) & ~0xfff)
addr = self.maps[-1].addr + self.cur_offset
self.cur_offset += size
return addr
def reserve_int(self, nb_int=1):
int_size = self.get_int_size()
return self.reserve_size(int_size * nb_int)
def write_code(self, code):
size = len(code)
if size + self.cur_offset > self.cur_page_size:
self.get_new_page((size + 0x1000) & ~0xfff)
self.maps[-1][self.cur_offset: self.cur_offset + size] = code
addr = self.maps[-1].addr + self.cur_offset
self.cur_offset += size
return addr
def close(self):
maps = self.maps
self.maps = []
self.cur_offset = 0
self.cur_page_size = 0
if getattr(sys, "path", None) is None:
# Path is None -> Python shutdown
return
for mymap in maps:
windows.winproxy.VirtualFree(mymap.addr, dwFreeType=gdef.MEM_RELEASE)
def __del__(self):
self.close()
allocator = CustomAllocator()
def create_function(code, types, calling_convention=ctypes.CFUNCTYPE):
"""Create a python function that call raw machine code
:param str code: Raw machine code that will be called
:param list types: Return type and parameters type (see :mod:`ctypes`)
:return: the created function
:rtype: function
"""
func_type = calling_convention(*types)
addr = allocator.write_code(code)
res = func_type(addr)
res.code_addr = addr
return res
@@ -1,280 +0,0 @@
import windows
import windows.native_exec.simple_x64 as x64
import windows.native_exec.simple_x86 as x86
from windows.generated_def.winstructs import *
StrlenW64 = x64.MultipleInstr()
StrlenW64 += x64.Label(":FUNC_STRLENW64")
StrlenW64 += x64.Push("RCX")
StrlenW64 += x64.Push("RDI")
StrlenW64 += x64.Mov("RDI", "RCX")
StrlenW64 += x64.Xor("RAX", "RAX")
StrlenW64 += x64.Xor("RCX", "RCX")
StrlenW64 += x64.Dec("RCX")
StrlenW64 += x64.Repne + x64.ScasW()
StrlenW64 += x64.Not("RCX")
StrlenW64 += x64.Dec("RCX")
StrlenW64 += x64.Mov("RAX", "RCX")
StrlenW64 += x64.Pop("RDI")
StrlenW64 += x64.Pop("RCX")
StrlenW64 += x64.Ret()
StrlenA64 = x64.MultipleInstr()
StrlenA64 += x64.Label(":FUNC_STRLENA64")
StrlenA64 += x64.Push("RCX")
StrlenA64 += x64.Push("RDI")
StrlenA64 += x64.Mov("RDI", "RCX")
StrlenA64 += x64.Xor("RAX", "RAX")
StrlenA64 += x64.Xor("RCX", "RCX")
StrlenA64 += x64.Dec("RCX")
StrlenA64 += x64.Repne + x64.ScasB()
StrlenA64 += x64.Not("RCX")
StrlenA64 += x64.Dec("RCX")
StrlenA64 += x64.Mov("RAX", "RCX")
StrlenA64 += x64.Pop("RDI")
StrlenA64 += x64.Pop("RCX")
StrlenA64 += x64.Ret()
GetProcAddress64 = x64.MultipleInstr()
GetProcAddress64 += x64.Label(":FUNC_GETPROCADDRESS64")
GetProcAddress64 += x64.Push("RBX")
GetProcAddress64 += x64.Push("RCX")
GetProcAddress64 += x64.Push("RDX")
GetProcAddress64 += x64.Push("RSI")
GetProcAddress64 += x64.Push("RDI")
GetProcAddress64 += x64.Push("R8")
GetProcAddress64 += x64.Push("R9")
GetProcAddress64 += x64.Push("R10")
GetProcAddress64 += x64.Push("R11")
GetProcAddress64 += x64.Push("R12")
GetProcAddress64 += x64.Push("R13")
# Params : RCX -> libname
# Params : RDX -> API Name
GetProcAddress64 += x64.Mov("R11", "RCX")
GetProcAddress64 += x64.Mov("R12", "RDX")
GetProcAddress64 += x64.Mov("RAX", x64.mem("GS:[0x60]")) #PEB !
GetProcAddress64 += x64.Mov("RAX", x64.mem("[RAX + 24] ")) # ; RAX = ldr (+ 6 for 64 cause of 2 ptr)
GetProcAddress64 += x64.Mov("RAX", x64.mem("[RAX + 32]")) # ; RAX on the first elt of the list (first module)
GetProcAddress64 += x64.Mov("RDX", "RAX")
GetProcAddress64 += x64.Label(":a_dest")
GetProcAddress64 += x64.Mov("RAX", "RDX")
GetProcAddress64 += x64.Mov("RBX", x64.mem("[RAX + 32]")) # RBX : first base ! (base of current module)
#GetProcAddress64 += x64.Mov("RBX ", x64.mem("[RAX + 32]")) # RBX : first base ! (base of current module)
GetProcAddress64 += x64.Cmp("RBX", 0)
GetProcAddress64 += x64.Jz(":DLL_NOT_FOUND")
GetProcAddress64 += x64.Mov("RCX", x64.mem("[RAX + 80]")) # RCX = NAME (UNICODE_STRING.Buffer)
GetProcAddress64 += x64.Call(":FUNC_STRLENW64")
GetProcAddress64 += x64.Mov("RDI", "RCX")
GetProcAddress64 += x64.Mov("RCX", "RAX")
GetProcAddress64 += x64.Mov("RSI", "R11")
GetProcAddress64 += x64.Rep + x64.CmpsW() #;cmp with current dll name (unicode)
GetProcAddress64 += x64.Test("RCX", "RCX")
GetProcAddress64 += x64.Jz(":DLL_FOUND")
GetProcAddress64 += x64.Mov("RDX", x64.mem("[RDX]"))
GetProcAddress64 += x64.Jmp(":a_dest")
GetProcAddress64 += x64.Label(":DLL_FOUND") # here rbx = base
GetProcAddress64 += x64.Mov("EAX", x64.mem("[RBX + 60]")) # rax = PEBASE RVA
GetProcAddress64 += x64.Add("RAX", "RBX") # RAX = PEBASE
GetProcAddress64 += x64.Add("RAX", 24) # ;OPTIONAL HEADER
GetProcAddress64 += x64.Mov("ECX", x64.mem("[rax + 112]")) # ;rcx = RVA export dir
GetProcAddress64 += x64.Add("RCX", "RBX") # ;rcx = export_dir
GetProcAddress64 += x64.Mov("RAX", "RCX") # ;RAX = export_dir
GetProcAddress64 += x64.Push("RAX") # ;Save it for after function search
# ; EBX = BASE | EAX = EXPORT DIR
GetProcAddress64 += x64.Mov("ECX", x64.mem("[RAX + 24] "))
GetProcAddress64 += x64.Mov("R13", "RCX") # ;r13 = NB names
GetProcAddress64 += x64.Mov("EDX", x64.mem("[RAX + 32] ")) # EDX = names array RVA
GetProcAddress64 += x64.Add("RDX", "RBX") # RDX = names array
GetProcAddress64 += x64.Xor("RCX", "RCX")
GetProcAddress64 += x64.Label(":SEARCH_LOOP")
GetProcAddress64 += x64.Cmp("RCX", "R13")
GetProcAddress64 += x64.Jz(":API_NOT_FOUND")
GetProcAddress64 += x64.Mov("ESI", x64.mem("[RDX + RCX * 4]")) # ;Get function name RVA
GetProcAddress64 += x64.Add("RSI", "RBX") # ;Get name addr
GetProcAddress64 += x64.Push("RCX") # ;Save current index (could use x64 register)
GetProcAddress64 += x64.Mov("RCX", "R12")
GetProcAddress64 += x64.Call(":FUNC_STRLENA64") # TODO: mov outside the loop :D
GetProcAddress64 += x64.Mov("RCX", "RAX")
GetProcAddress64 += x64.Mov("RDI", "R12")
GetProcAddress64 += x64.Inc("RCX")
GetProcAddress64 += x64.Rep + x64.CmpsB()
GetProcAddress64 += x64.Mov("EAX", "ECX")
GetProcAddress64 += x64.Pop("RCX")
GetProcAddress64 += x64.Inc("RCX")
GetProcAddress64 += x64.Test("RAX", "RAX")
GetProcAddress64 += x64.Jnz(":SEARCH_LOOP")
# Func FOUND !
GetProcAddress64 += x64.Dec("RCX")
GetProcAddress64 += x64.Pop("RAX") # ;Restore export_dir addr
GetProcAddress64 += x64.Mov("EDX", x64.mem("[RAX + 36]")) # ;EDX = AddressOfNameOrdinals RVX
GetProcAddress64 += x64.Add("RDX", "RBX")
GetProcAddress64 += x64.OperandSizeOverride + x64.Mov("ECX", x64.mem("[rdx + rcx * 2]")) # ; ecx = Ieme ordinal (short array)
GetProcAddress64 += x64.And('RCX', 0xffff)
GetProcAddress64 += x64.Mov("EDX", x64.mem("[RAX + 28]")) # ; AddressOfFunctions RVA
GetProcAddress64 += x64.Add("RDX", "RBX")
GetProcAddress64 += x64.Mov("EDX", x64.mem("[RDX + RCX * 4]"))
GetProcAddress64 += x64.Add("RDX", "RBX")
GetProcAddress64 += x64.Mov("RAX", "RDX")
GetProcAddress64 += x64.Label(":RETURN")
GetProcAddress64 += x64.Pop("R13")
GetProcAddress64 += x64.Pop("R12")
GetProcAddress64 += x64.Pop("R11")
GetProcAddress64 += x64.Pop("R10")
GetProcAddress64 += x64.Pop("R9")
GetProcAddress64 += x64.Pop("R8")
GetProcAddress64 += x64.Pop("RDI")
GetProcAddress64 += x64.Pop("RSI")
GetProcAddress64 += x64.Pop("RDX")
GetProcAddress64 += x64.Pop("RCX")
GetProcAddress64 += x64.Pop("RBX")
GetProcAddress64 += x64.Ret()
GetProcAddress64 += x64.Label(":DLL_NOT_FOUND")
GetProcAddress64 += x64.Mov("RAX", 0xfffffffffffffffe)
GetProcAddress64 += x64.Jmp(":RETURN")
GetProcAddress64 += x64.Label(":API_NOT_FOUND")
GetProcAddress64 += x64.Pop("RAX")
GetProcAddress64 += x64.Mov("RAX", 0xffffffffffffffff)
GetProcAddress64 += x64.Jmp(":RETURN")
# Ajout des dependances
GetProcAddress64 += StrlenW64
GetProcAddress64 += StrlenA64
###### 32 bits #######
StrlenW32 = x86.MultipleInstr()
StrlenW32 += x86.Label(":FUNC_STRLENW32")
StrlenW32 += x86.Push("EDI")
StrlenW32 += x86.Mov("EDI", x86.mem("[ESP + 8]"))
StrlenW32 += x86.Push("ECX")
StrlenW32 += x86.Xor("EAX", "EAX")
StrlenW32 += x86.Xor("ECX", "ECX")
StrlenW32 += x86.Dec("ECX")
StrlenW32 += x86.Repne + x86.ScasW()
StrlenW32 += x86.Not("ECX")
StrlenW32 += x86.Dec("ECX")
StrlenW32 += x86.Mov("EAX", "ECX")
StrlenW32 += x86.Pop("ECX")
StrlenW32 += x86.Pop("EDI")
StrlenW32 += x86.Ret()
StrlenA32 = x86.MultipleInstr()
StrlenA32 += x86.Label(":FUNC_STRLENA32")
StrlenA32 += x86.Push("EDI")
StrlenA32 += x86.Mov("EDI", x86.mem("[ESP + 8]"))
StrlenA32 += x86.Push("ECX")
StrlenA32 += x86.Xor("EAX", "EAX")
StrlenA32 += x86.Xor("ECX", "ECX")
StrlenA32 += x86.Dec("ECX")
StrlenA32 += x86.Repne + x86.ScasB()
StrlenA32 += x86.Not("ECX")
StrlenA32 += x86.Dec("ECX")
StrlenA32 += x86.Mov("EAX", "ECX")
StrlenA32 += x86.Pop("ECX")
StrlenA32 += x86.Pop("EDI")
StrlenA32 += x86.Ret()
GetProcAddress32 = x86.MultipleInstr()
GetProcAddress32 += x86.Label(":FUNC_GETPROCADDRESS32")
GetProcAddress32 += x86.Push("EBX")
GetProcAddress32 += x86.Push("ECX")
GetProcAddress32 += x86.Push("EDI")
GetProcAddress32 += x86.Push("ESI")
GetProcAddress32 += x86.Push("EBP")
GetProcAddress32 += x86.Mov("EAX", x86.mem("FS:[0x30]"))
GetProcAddress32 += x86.Mov("EAX", x86.mem("[EAX + 0xC]"))
GetProcAddress32 += x86.Mov("EAX", x86.mem("[EAX + 0xC]")) # ; RAX on the first elt of the list (first module)
GetProcAddress32 += x86.Mov("EDX", "EAX")
GetProcAddress32 += x86.Label(":a_dest")
GetProcAddress32 += x86.Mov("EAX", "EDX")
GetProcAddress32 += x86.Mov("EBX", x86.mem("[EAX + 0x18]")) # EBX : first base ! (base of current module)
GetProcAddress32 += x86.Cmp("EBX", 0)
GetProcAddress32 += x86.Jz(":DLL_NOT_FOUND")
GetProcAddress32 += x86.Mov("ECX", x86.mem("[EAX + 0x30]")) # RCX = NAME (UNICODE_STRING.Buffer)
GetProcAddress32 += x86.Push("ECX")
GetProcAddress32 += x86.Call(":FUNC_STRLENW32")
GetProcAddress32 += x86.Pop("EDI") # Current name
GetProcAddress32 += x86.Mov("ECX", "EAX")
GetProcAddress32 += x86.Mov("ESI", x86.mem("[ESP + 0x18]"))
GetProcAddress32 += x86.Rep + x86.CmpsW()
GetProcAddress32 += x86.Test("ECX", "ECX")
GetProcAddress32 += x86.Jz(":DLL_FOUND")
GetProcAddress32 += x86.Mov("EDX", x86.mem("[EDX]"))
GetProcAddress32 += x86.Jmp(":a_dest")
GetProcAddress32 += x86.Label(":DLL_FOUND")
GetProcAddress32 += x86.Mov("EAX", x86.mem("[EBX + 0x3c]")) # rax = PEBASE RVA
GetProcAddress32 += x86.Add("EAX", "EBX") # RAX = PEBASE
GetProcAddress32 += x86.Add("EAX", 0x18) # ;OPTIONAL HEADER
GetProcAddress32 += x86.Mov("ECX", x86.mem("[EAX + 0x60]")) # ;ecx = RVA export dir
GetProcAddress32 += x86.Add("ECX", "EBX") # ;ecx = export_dir
GetProcAddress32 += x86.Mov("EAX", "ECX")
GetProcAddress32 += x86.Push("EAX") # Save it
# ; EBX = BASE | EAX = EXPORT DIR
GetProcAddress32 += x86.Mov("ECX", x86.mem("[EAX + 24] "))
GetProcAddress32 += x86.Mov("EBP", "ECX") # ;EBP = NB names
GetProcAddress32 += x86.Mov("EDX", x86.mem("[EAX + 32] ")) # EDX = names array RVA
GetProcAddress32 += x86.Add("EDX", "EBX") # RDX = names array
GetProcAddress32 += x86.Xor("ECX", "ECX")
GetProcAddress32 += x86.Mov("ESI", x86.mem("[ESP + 0x20]"))
GetProcAddress32 += x86.Label(":SEARCH_LOOP")
GetProcAddress32 += x86.Cmp("ECX", "EBP")
GetProcAddress32 += x86.Jz(":API_NOT_FOUND")
GetProcAddress32 += x86.Mov("EDI", x86.mem("[EDX + ECX * 4]")) # ;Get function name RVA
GetProcAddress32 += x86.Add("EDI", "EBX") # ;Get name addr
GetProcAddress32 += x86.Push("ECX") # Save current index
GetProcAddress32 += x86.Push("ESI")
GetProcAddress32 += x86.Call(":FUNC_STRLENA32")
GetProcAddress32 += x86.Mov("ECX", "EAX")
GetProcAddress32 += x86.Push("EDI")
GetProcAddress32 += x86.Call(":FUNC_STRLENA32")
GetProcAddress32 += x86.Pop("EDI")
GetProcAddress32 += x86.Cmp("EAX", "ECX")
GetProcAddress32 += x86.Jnz(":ABORT_STRCMP")
GetProcAddress32 += x86.Inc("ECX")
GetProcAddress32 += x86.Rep + x86.CmpsB()
GetProcAddress32 += x86.Label(":ABORT_STRCMP")
GetProcAddress32 += x86.Pop("ESI")
GetProcAddress32 += x86.Mov("EAX", "ECX")
GetProcAddress32 += x86.Pop("ECX")
GetProcAddress32 += x86.Inc("ECX")
GetProcAddress32 += x86.Test("EAX", "EAX")
GetProcAddress32 += x86.Jnz(":SEARCH_LOOP")
GetProcAddress32 += x86.Dec("ECX")
#GetProcAddress32 += x86.Int3() # da poi(edx + (ecx * 4)) + ebx; da esi
GetProcAddress32 += x86.Pop("EAX") # ;Restore export_dir addr
GetProcAddress32 += x86.Mov("EDX", x86.mem("[EAX + 36]")) # ;EDX = AddressOfNameOrdinals RVX
GetProcAddress32 += x86.Add("EDX", "EBX")
#GetProcAddress32 += x86.Mov("ECX", x86.mem("[EDX + ECX * 2]"))
GetProcAddress32 += x86.OperandSizeOverride + x86.Mov("ECX", x86.mem("[EDX + ECX * 2]"))
# ; ecx = Ieme ordinal (short array)
GetProcAddress32 += x86.And('ECX', 0xffff)
GetProcAddress32 += x86.Mov("EDX", x86.mem("[EAX + 28]")) # ; AddressOfFunctions RVA
GetProcAddress32 += x86.Add("EDX", "EBX")
GetProcAddress32 += x86.Mov("EDX", x86.mem("[EDX + ECX * 4]"))
GetProcAddress32 += x86.Add("EDX", "EBX")
GetProcAddress32 += x86.Mov("EAX", "EDX")
GetProcAddress32 += x86.Label(":RETURN")
GetProcAddress32 += x86.Pop("EBP")
GetProcAddress32 += x86.Pop("ESI")
GetProcAddress32 += x86.Pop("EDI")
GetProcAddress32 += x86.Pop("ECX")
GetProcAddress32 += x86.Pop("EBX")
GetProcAddress32 += x86.Ret()
GetProcAddress32 += x86.Label(":DLL_NOT_FOUND")
GetProcAddress32 += x86.Mov("EAX", 0xfffffffe)
GetProcAddress32 += x86.Jmp(":RETURN")
GetProcAddress32 += x86.Label(":API_NOT_FOUND")
GetProcAddress32 += x86.Pop("EAX")
GetProcAddress32 += x86.Mov("EAX", 0xffffffff)
GetProcAddress32 += x86.Jmp(":RETURN")
GetProcAddress32 += StrlenW32
GetProcAddress32 += StrlenA32
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-453
View File
@@ -1,453 +0,0 @@
import ctypes
import windows
import windows.hooks as hooks
import windows.utils as utils
from windows.generated_def.winstructs import *
from windows.utils import transform_ctypes_fields
import windows.remotectypes as rctypes
IMAGE_ORDINAL_FLAG32 = 0x80000000
IMAGE_ORDINAL_FLAG64 = 0x8000000000000000
def get_structure_transformer_for_target(target, targetbitness=None):
current_bitness = windows.current_process.bitness
if target is None:
ctypes_structure_transformer = lambda x:x
create_structure_at = lambda structcls, addr: structcls.from_address(addr)
return ctypes_structure_transformer, create_structure_at
if targetbitness is None:
targetbitness = target.bitness
if targetbitness == 32 and current_bitness == 64:
ctypes_structure_transformer = rctypes.transform_type_to_remote32bits
elif targetbitness == 64 and current_bitness == 32:
ctypes_structure_transformer = rctypes.transform_type_to_remote64bits
elif targetbitness == current_bitness:
ctypes_structure_transformer = rctypes.transform_type_to_remote
else:
raise NotImplementedError("Parsing {0} PE from {1} Process".format(targetedbitness, proc_bitness))
def create_structure_at(structcls, addr): # Il reste une closure sur 'target' ici !!
return ctypes_structure_transformer(structcls)(addr, target)
return ctypes_structure_transformer, create_structure_at
def get_pe_bitness(baseaddr, target):
# We can force bitness as the field we access are bitness-independant
pe = GetPEFile(baseaddr, target, force_bitness=32)
machine = pe.get_NT_HEADER().FileHeader.Machine
if machine == 0x14c:
return 32
elif machine == 0x8664:
return 64
else:
raise ValueError("Unknow PE target machine <0x{0:x}>".format(machine))
## == PEPARSE V2 ==
import collections
CtypesStructureTransformers = collections.namedtuple("CtypesStructureTransformers", ["ctypes_structure_transformer", "create_structure_at"])
def GetPEFile(baseaddr, target=None, force_bitness=None):
"""Returns a :class:`PEFile` to explore a PE loaded at `baseaddr` in process `target`.
:rtype: :class:`PEFile`
.. note::
If target is ``None`` it refers to the current process
"""
proc_bitness = windows.current_process.bitness
if force_bitness is None:
targetedbitness = get_pe_bitness(baseaddr, target)
else:
targetedbitness = force_bitness
transformers = get_structure_transformer_for_target(target, targetedbitness)
#ctypes_structure_transformer, create_structure_at = transformers
transfor_funcs = CtypesStructureTransformers(*transformers) # TODO: rename
return PEFile(target, baseaddr, targetedbitness, transfor_funcs)
class THUNK_DATA(ctypes.Union):
_fields_ = [
("Ordinal", PVOID),
("AddressOfData", PVOID)
]
# Special case for .NET PE32 rewrite as 64b
# We may have a PE in a 64b process with a 32b IAT
class THUNK_DATA_32(ctypes.Union):
_fields_ = [
("Ordinal", DWORD),
("AddressOfData", DWORD)
]
class IMPORT_BY_NAME(ctypes.Structure):
_fields_ = [
("Hint", WORD),
("Name", BYTE)
]
def get_string(target, addr):
if target is None:
return ctypes.c_char_p(addr).value.decode("latin1")
return target.read_string(addr)
class PESection(IMAGE_SECTION_HEADER):
@property
def name(self):
if self.target is None:
name = get_string(self.target, ctypes.addressof(self.Name))[:8]
else:
name = get_string(self.target, self._base_addr)[:8]
# Decode as UTF-8 as the MS doc say ?
return name
@property
def start(self):
return self.baseaddr + self.VirtualAddress
@property
def size(self):
return self.VirtualSize
def __repr__(self):
return "<PESection \"{0}\">".format(self.name)
@classmethod
def create(cls, pefile, addr):
self = pefile.transformers.create_structure_at(cls, addr)
self.baseaddr = pefile.baseaddr
self.target = pefile.target
return self
class IATPtr(PVOID):
@classmethod
def from_iatentry(cls, iat_entry):
self = cls.from_address(iat_entry.addr)
self.addr = iat_entry.addr
self.nonhookvalue = iat_entry.nonhookvalue
return self
class IATEntry(ctypes.Structure):
"""Represent an entry in the IAT of a module
Can be used to get resolved value and setup hook
"""
_fields_ = [
("value", PVOID)]
@classmethod
def create(cls, addr, ord, name, target, transformers):
self = transformers.create_structure_at(cls, addr)
self.addr = addr
self.ord = ord
self.name = name
self.hook = None
self.nonhookvalue = self.value
self.target = target
return self
def __repr__(self):
return '<{0} "{1}" ordinal {2}>'.format(self.__class__.__name__, self.name, self.ord)
def set_hook(self, callback, types=None):
"""Setup a hook on the entry and return it.
You MUST keep a reference to the hook while the hook is enabled.
:param callback: the hook
.. note::
see :ref:`hook_protocol`
:rtype: :class:`windows.hooks.IATHook`
.. warning::
This works only for PEFile with the current process as target.
"""
if self.target is not None:
raise NotImplementedError("Setting hook in remote process (use python code injection)")
hook = hooks.IATHook(self, callback, types)
import weakref
self.whook = weakref.ref(hook, self.on_destroy)
self.hook = hook
hook.enable()
return hook
def on_destroy(self, *args):
# We cannot know if the hook was enabled here..
print("DESTROY: {0} -> ".format(args, self.enabled))
# print(args[0]())
def remove_hook(self):
"""Remove the hook on the entry"""
if self.hook is None:
return False
self.hook.disable()
self.hook = None
return True
# def __del__(self):
# print(self.hook)
# if self.hook:
# print("LOL BYE {0}".format(self.hook))
class IMAGE_IMPORT_DESCRIPTOR(IMAGE_IMPORT_DESCRIPTOR): # TODO: use explicite name winstructs.IMAGE_IMPORT_DESCRIPTOR
def get_INT(self, pe):
THUNK_DATA_TYPE = THUNK_DATA
if not self.OriginalFirstThunk:
return None
# We may have 32bits PE mapped in 32bits process (thanks to .NET PE)
if self.target is None and pe.bitness != windows.current_process.bitness:
assert windows.current_process.bitness == 64 and pe.bitness == 32, "Mapped 64b PE in current process 32b not handled"
THUNK_DATA_TYPE = THUNK_DATA_32
int_addr = self.OriginalFirstThunk + self.baseaddr
int_entry = self.transformers.create_structure_at(THUNK_DATA_TYPE, int_addr)
res = []
while int_entry.Ordinal:
if int_entry.Ordinal & self.IMAGE_ORDINAL_FLAG:
res += [(int_entry.Ordinal & 0x7fffffff, None)]
else:
import_by_name = self.transformers.create_structure_at(IMPORT_BY_NAME, self.baseaddr + int_entry.AddressOfData)
name_address = self.baseaddr + int_entry.AddressOfData + type(import_by_name).Name.offset
name = get_string(self.target, name_address)
res.append((import_by_name.Hint, name))
int_addr += ctypes.sizeof(type(int_entry))
int_entry = self.transformers.create_structure_at(THUNK_DATA_TYPE, int_addr)
return res
def get_IAT(self, pe):
THUNK_DATA_TYPE = THUNK_DATA
if self.target is None and pe.bitness != windows.current_process.bitness:
assert windows.current_process.bitness == 64 and pe.bitness == 32, "Mapped 64b PE in current process 32b not handled"
THUNK_DATA_TYPE = THUNK_DATA_32
iat_addr = self.FirstThunk + self.baseaddr
iat_entry = self.transformers.create_structure_at(THUNK_DATA_TYPE, iat_addr)
res = []
while iat_entry.Ordinal:
res.append(IATEntry.create(iat_addr, -1, "??", self.target, self.transformers))
iat_addr += ctypes.sizeof(type(iat_entry))
iat_entry = self.transformers.create_structure_at(THUNK_DATA_TYPE, iat_addr)
return res
@classmethod
def create(cls, pefile, addr):
self = pefile.transformers.create_structure_at(cls, addr)
self.baseaddr = pefile.baseaddr
self.transformers = pefile.transformers
self.IMAGE_ORDINAL_FLAG = pefile.IMAGE_ORDINAL_FLAG
self.target = pefile.target
return self
class IMAGE_EXPORT_DIRECTORY(IMAGE_EXPORT_DIRECTORY): # TODO: use explicite name winstructs._IMAGE_EXPORT_DIRECTORY
def get_exports(self):
NameOrdinals = self.transformers.create_structure_at((WORD * self.NumberOfNames), self.AddressOfNameOrdinals + self.baseaddr)
NameOrdinals = list(NameOrdinals)
Functions = self.transformers.create_structure_at((DWORD * self.NumberOfFunctions), self.AddressOfFunctions + self.baseaddr)
Names = self.transformers.create_structure_at((DWORD * self.NumberOfNames), self.AddressOfNames + self.baseaddr)
res = []
for nb, func in enumerate(Functions):
func += self.baseaddr
if nb in NameOrdinals:
name = get_string(self.target, Names[NameOrdinals.index(nb)] + self.baseaddr)
# Export name should be ascii
# Decode from ascii or return bytes ?
# https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#export-address-table
else:
name = None
res.append((nb, func, name))
return res
@classmethod
def create(cls, pefile, addr):
self = pefile.transformers.create_structure_at(cls, addr)
self.transformers = pefile.transformers
self.target = pefile.target
self.baseaddr = pefile.baseaddr
return self
class IMAGE_DOS_HEADER(ctypes.Structure):
_fields_ = [
("e_magic", CHAR * 2),
("e_cblp", WORD),
("e_cp", WORD),
("e_crlc", WORD),
("e_cparhdr", WORD),
("e_minalloc", WORD),
("e_maxalloc", WORD),
("e_ss", WORD),
("e_sp", WORD),
("e_csum", WORD),
("e_ip", WORD),
("e_cs", WORD),
("e_lfarlc", WORD),
("e_ovno", WORD),
("e_res", WORD * 4),
("e_oemid", WORD),
("e_oeminfo", WORD),
("e_res2", WORD * 10),
("e_lfanew", DWORD),
]
class PEFile(object):
"""Represent a PE loaded in a process (current or remote)"""
def __init__(self, target, baseaddr, targetedbitness, transformers):
self.target = target
self.baseaddr = baseaddr
self.bitness = targetedbitness
self.transformers = transformers
if targetedbitness == 32:
self.IMAGE_ORDINAL_FLAG = IMAGE_ORDINAL_FLAG32
else:
self.IMAGE_ORDINAL_FLAG = IMAGE_ORDINAL_FLAG64
def get_DOS_HEADER(self):
return self.transformers.create_structure_at(IMAGE_DOS_HEADER, self.baseaddr)
def get_NT_HEADER(self):
offset = self.get_DOS_HEADER().e_lfanew
if self.bitness == 32:
return self.transformers.create_structure_at(IMAGE_NT_HEADERS32, self.baseaddr + offset)
return self.transformers.create_structure_at(IMAGE_NT_HEADERS64, self.baseaddr + offset)
STANDARD_OPTIONAL_HEADER_TYPE_PER_MAGIC = {
IMAGE_NT_OPTIONAL_HDR32_MAGIC: IMAGE_OPTIONAL_HEADER32,
IMAGE_NT_OPTIONAL_HDR64_MAGIC: IMAGE_OPTIONAL_HEADER64,
}
STANDARD_OPTIONAL_HEADER_SIZE_PER_MAGIC = (
(IMAGE_NT_OPTIONAL_HDR32_MAGIC, ctypes.sizeof(IMAGE_OPTIONAL_HEADER32)),
(IMAGE_NT_OPTIONAL_HDR64_MAGIC, ctypes.sizeof(IMAGE_OPTIONAL_HEADER64)),
)
def get_OptionalHeader(self):
# We can have a 32bits PE with a 64 bits OptionalHeader
# Ex : PE32 .NET that allow to be loaded in 64b process
# See: https://github.com/dotnet/runtime/blob/8bbe33819464216becffb7cf8b7ea8dd3bab5836/src/coreclr/src/vm/peimagelayout.cpp#L599
# In this case the OptionalHeader is transformed in 64bits & OptionalHeader.Magic is changed accordingly
# So we cannot just rely on get_NT_HEADER() to give us the correct OptionalHeader type. some re-check are required
default_opth = self.get_NT_HEADER().OptionalHeader
# Cannot juste compare types with type(default_opth) as it may be a remoteType
current_opth_infos = (default_opth.Magic, ctypes.sizeof(default_opth))
if current_opth_infos in self.STANDARD_OPTIONAL_HEADER_SIZE_PER_MAGIC:
# The default OptionalHeader structure match what we expect based on the magic (most of the cases)
return default_opth
# Mismatch -> PE32 remapped as 64b (with OptionalHeader rewrite)
# Remap the correct OptionalHeader
opt_header_real_type = self.STANDARD_OPTIONAL_HEADER_TYPE_PER_MAGIC[default_opth.Magic]
opt_header_addr = default_opth._base_addr if self.target else ctypes.addressof(default_opth)
return self.transformers.create_structure_at(opt_header_real_type, opt_header_addr)
def get_DataDirectory(self):
return self.get_OptionalHeader().DataDirectory
def get_IMPORT_DESCRIPTORS(self):
import_datadir = self.get_DataDirectory()[IMAGE_DIRECTORY_ENTRY_IMPORT]
if import_datadir.VirtualAddress == 0:
return []
import_descriptor_addr = self.baseaddr + import_datadir.VirtualAddress
current_import_descriptor = IMAGE_IMPORT_DESCRIPTOR.create(self, import_descriptor_addr)
res = []
while current_import_descriptor.FirstThunk:
res.append(current_import_descriptor)
import_descriptor_addr += ctypes.sizeof(IMAGE_IMPORT_DESCRIPTOR)
current_import_descriptor = IMAGE_IMPORT_DESCRIPTOR.create(self, import_descriptor_addr)
return res
def get_EXPORT_DIRECTORY(self):
export_directory_rva = self.get_DataDirectory()[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress
if export_directory_rva == 0:
return None
export_directory_addr = self.baseaddr + export_directory_rva
exp_dir = IMAGE_EXPORT_DIRECTORY.create(self, export_directory_addr)
return exp_dir
@utils.fixedpropety
def sections(self):
nt_header = self.get_NT_HEADER()
nb_section = nt_header.FileHeader.NumberOfSections
SizeOfOptionalHeader = self.get_NT_HEADER().FileHeader.SizeOfOptionalHeader
if self.target is None:
opt_header_addr = ctypes.addressof(self.get_NT_HEADER().OptionalHeader)
else:
opt_header_addr = self.get_NT_HEADER().OptionalHeader._base_addr
base_section = opt_header_addr + SizeOfOptionalHeader
#pe_section_type = IMAGE_SECTION_HEADER
return [PESection.create(self, base_section + (sizeof(IMAGE_SECTION_HEADER) * i)) for i in range(nb_section)]
#sections_array = self.transformers.create_structure_at((self.PESection * nb_section), base_section)
#return list(sections_array)
@utils.fixedpropety
def exports(self):
"""The exports of the PE in a dict. Keys are ordinal (:class:`int`) and name (:class:`str`).
The values are the addresses of the exports.
:type: {(:class:`int` or :class:`str`) : :class:`int`}"""
res = {}
exp_dir = self.get_EXPORT_DIRECTORY()
export_datadir = self.get_DataDirectory()[IMAGE_DIRECTORY_ENTRY_EXPORT]
export_start = self.baseaddr + export_datadir.VirtualAddress
export_end = export_start + export_datadir.Size
if exp_dir is None:
return res
raw_exports = exp_dir.get_exports()
for id, rva_addr, rva_name in raw_exports:
if export_start <= rva_addr < export_end:
# Export proxy...
# Contains the string to another Dll.Function
rva_addr = get_string(self.target, rva_addr) # Put the string proxy instead
res[id] = rva_addr
if rva_name is not None:
res[rva_name] = rva_addr
return res
@utils.fixedpropety
def export_name(self):
"""The Name attribute of the ``EXPORT_DIRECTORY``"""
exp_dir = self.get_EXPORT_DIRECTORY()
if exp_dir is None:
return None
if not exp_dir.Name:
return None
return get_string(self.target, self.baseaddr + exp_dir.Name)
# TODO: get imports by parsing other modules exports if no INT
@utils.fixedpropety
def imports(self):
"""The imports of the PE in a dict.
Keys are the names of DLL to import from and values are :class:`list`
of :class:`IATEntry`
:type: {:class:`str` : [:class:`IATEntry`]}"""
res = {}
for import_descriptor in self.get_IMPORT_DESCRIPTORS():
INT = import_descriptor.get_INT(self)
IAT = import_descriptor.get_IAT(self)
if INT is not None:
for iat_entry, (ord, name) in zip(IAT, INT):
# str(name.decode()) -> python2 and python3 compatible for str result
iat_entry.ord = ord
iat_entry.name = str(name) if name else ""
name = get_string(self.target, self.baseaddr + import_descriptor.Name)
res.setdefault(name.lower(), []).extend(IAT)
return res
-127
View File
@@ -1,127 +0,0 @@
import windows
from windows import winproxy
import windows.generated_def as gdef
import ctypes
from windows.pycompat import is_py3
if is_py3:
from multiprocessing.connection import PipeConnection as native_PipeConnection
else:
from _multiprocessing import PipeConnection as native_PipeConnection
# Inspired from 'multiprocessing\connection.py'
def full_pipe_address(addr):
"""Return the full address of the pipe `addr`"""
if not isinstance(addr, bytes):
addr = addr.encode("ascii")
if addr.startswith(b"\\\\"):
return addr
return br"\\.\pipe" + "\\".encode() + addr
class PipeConnection(object): # Cannot inherit: crash the interpreter
"""A wrapper arround :class:`_multiprocessing.PipeConnection` able to work as a ContextManager"""
BUFFER_SIZE = 0x2000
def __init__(self, connection, name=None, server=False):
self.handle = connection.fileno()
self.connection = connection
self.name = name
self.server = server
@classmethod
def from_handle(cls, phandle, *args, **kwargs):
"""Create a :class:`PipeConnection` from pipe handle `phandle`"""
connection = native_PipeConnection(phandle)
return cls(connection, *args, **kwargs)
@classmethod
def create(cls, addr, security_descriptor=None):
"""Create a namedpipe pipe ``addr``
:returns type: :class:`PipeConnection`
"""
addr = full_pipe_address(addr)
security_attributes = None
if security_descriptor is not None:
if isinstance(security_descriptor, str):
security_descriptor = windows.security.SecurityDescriptor.from_string(security_descriptor)
security_attributes = gdef.SECURITY_ATTRIBUTES()
security_attributes.nLength = ctypes.sizeof(security_attributes)
security_attributes.lpSecurityDescriptor = security_descriptor # Accept as arg ?
security_attributes.bInheritHandle = True # Accept as arg ?
pipehandle = winproxy.CreateNamedPipeA(
addr, gdef.PIPE_ACCESS_DUPLEX,
gdef.PIPE_TYPE_MESSAGE | gdef.PIPE_READMODE_MESSAGE |
gdef.PIPE_WAIT,
gdef.PIPE_UNLIMITED_INSTANCES, cls.BUFFER_SIZE, cls.BUFFER_SIZE,
gdef.NMPWAIT_WAIT_FOREVER, security_attributes
)
return cls.from_handle(pipehandle, name=addr, server=True)
@classmethod
def connect(cls, addr):
"""Connect to the named pipe ``addr``
:returns type: :class:`PipeConnection`
"""
addr = full_pipe_address(addr)
pipehandle = winproxy.CreateFileA(addr, gdef.GENERIC_READ | gdef.GENERIC_WRITE, 0, None, gdef.OPEN_EXISTING, 0, None)
winproxy.SetNamedPipeHandleState(pipehandle, gdef.ULONG(gdef.PIPE_READMODE_MESSAGE), None, None)
return cls.from_handle(pipehandle, name=addr, server=False)
def send(self, *args, **kwargs):
"""Send an object on the pipe"""
return self.connection.send(*args, **kwargs)
def recv(self, *args, **kwargs):
"""Send an object from the pipe"""
return self.connection.recv(*args, **kwargs)
def wait_connection(self):
"""Wait for a client process to connect to the named pipe"""
return winproxy.ConnectNamedPipe(self.handle, None)
def get_security_descriptor(self):
return windows.security.SecurityDescriptor.from_handle(self.handle)
def set_security_descriptor(self, sd):
if isinstance(sd, basestring):
sd = windows.security.SecurityDescriptor.from_string(sd)
sd._apply_to_handle_and_type(self.handle)
security_descriptor = property(get_security_descriptor, set_security_descriptor)
def close(self):
"""Close the handle of the pipe"""
self.connection.close()
self.handle = None
def __enter__(self):
return self
def __exit__(self, *args, **kwargs):
self.close()
def __repr__(self):
return """<{0} name="{1}" server={2}>""".format(type(self).__name__, self.name, self.server)
connect = PipeConnection.connect
create = PipeConnection.create
def send_object(addr, obj):
"""Send `obj` on pipe ``addr``"""
with connect(addr) as np:
np.send(obj)
return None
def recv_object(addr):
"""Receive an object from pipe ``addr``"""
with create(addr) as np:
np.wait_connection()
return np.recv()
-38
View File
@@ -1,38 +0,0 @@
import sys
is_py3 = (sys.version_info.major >= 3)
if is_py3:
def str_from_ascii_function(s):
return s.decode("ascii")
int_types = int
basestring = str
anybuff = (str, bytes)
def raw_encode(s):
if isinstance(s, str):
return s.encode("latin1")
return s
def raw_decode(s):
if isinstance(s, bytes):
return s.decode("latin1")
return s
else: # py2.7
def str_from_ascii_function(s):
return s
int_types = (int, long)
basestring = basestring
anybuff = basestring
def raw_encode(s):
if isinstance(s, unicode):
return s.encode("latin1")
return s
def raw_decode(s):
# No unicode for now on py2
return s
-525
View File
@@ -1,525 +0,0 @@
"""remote ctypes, a try to a ctypes wrapper that accept a target object for every ready operation
Some code is copy-paste, might be userful to rewrite some part later"""
import _ctypes
import ctypes
import ctypes.wintypes
import itertools
from _ctypes import _SimpleCData
# No PFW deps in this file
import sys
is_py3 = (sys.version_info.major >= 3)
if is_py3:
int_types = int
else:
int_types = (int, long)
# ## Utils ### #
def is_pointer(x):
return isinstance(x, _ctypes._Pointer)
def is_pointer_type(x):
return issubclass(x, _ctypes._Pointer)
def is_array(x):
return isinstance(x, _ctypes.Array)
def is_array_type(x):
return issubclass(x, _ctypes.Array)
def is_structure_type(x):
return issubclass(x, ctypes.Structure)
def is_union_type(x):
return issubclass(x, ctypes.Union)
# ### My types ### #
# # 64bits pointer types # #
# I know direct inheritance from _SimpleCData seems bad
# But it seems to be the only way to have the normal
# ctypes.Structure way of working (need to investigate)
class c_void_p64(_SimpleCData):
_type_ = "Q"
class c_char_p64(_SimpleCData):
_type_ = "Q"
class c_wchar_p64(_SimpleCData):
_type_ = "Q"
# # 32bits pointer types # #
class c_void_p32(_SimpleCData):
_type_ = "I"
class c_char_p32(_SimpleCData):
_type_ = "I"
class c_wchar_p32(_SimpleCData):
_type_ = "I"
# standard type translation
# don't know how to handle size_t since it's non-distinguable from c_ulong
# maybe force import before ctypes and modif stuff into ctypes ?
# # Remote Value
# Used by the RemoteStructure to access the target memory
class RemoteValue(object):
@classmethod
def from_buffer_with_target(cls, buffer, offset=0, target=None):
x = cls.from_buffer(buffer)
x.target = target
return x
class RemotePtr(RemoteValue):
@property
def raw_value(self):
return ctypes.cast(self, ctypes.c_void_p).value
class RemoteCCharP(RemotePtr, ctypes.c_char_p):
@property
def value(self):
base = self.raw_value
if not base:
return None
res = []
for i in itertools.count():
x = self.target.read_memory(base + (i * 0x100), 0x100)
if b"\x00" in x:
res.append(x.split(b"\x00", 1)[0])
break
res.append(x)
return b"".join(res)
class RemoteWCharP(RemotePtr, ctypes.c_char_p):
@property
def value(self):
base = self.raw_value
if not base:
return None
# Simple case where target in a WinProcess
try:
return self.target.read_wstring(base)
except AttributeError as e:
pass
# Copy of Winprocess.read_wstring if target is a custom object
read_size = 0x100
readden = 0
# I am trying to do something smart here..
while True:
try:
x = self.read_memory(addr + readden, read_size)
except winproxy.WinproxyError as e:
if read_size == 2:
raise
# handle read_wstring at end of page
# Of read failed: read only the half of size
# read_size must remain a multiple of 2
read_size = read_size / 2
continue
readden += read_size
utf16_chars = ["".join(c) for c in zip(*[iter(x)] * 2)]
if "\x00\x00" in utf16_chars:
res.extend(utf16_chars[:utf16_chars.index("\x00\x00")])
break
res.extend(x)
return "".join(res).decode('utf16')
class RemoteStructurePointer(RemotePtr, ctypes.c_void_p):
@classmethod
def from_buffer_with_target_and_ptr_type(cls, buffer, offset=0, target=None, ptr_type=None):
x = cls.from_buffer(buffer)
x.target = target
x.real_pointer_type = ptr_type
return x
@property
def contents(self):
remote_pointed_type = RemoteStructure.from_structure(self.real_pointer_type._type_)
return remote_pointed_type(self.raw_value, self.target)
def __repr__(self):
return "<RemoteStructurePointer to {0}>".format(self.real_pointer_type._type_.__name__)
def create_remote_array(subtype, len):
class RemoteArray(_ctypes.Array):
_length_ = len
_type_ = subtype
def __init__(self, addr, target):
self._base_addr = addr
self.target = target
def __getitem__(self, slice):
# import pdb;pdb.set_trace()
if not isinstance(slice, int_types):
raise NotImplementedError("RemoteArray slice __getitem__")
if slice >= len:
raise IndexError("Access to {0} for a RemoteArray of size {1}".format(slice, len))
item_addr = self._base_addr + (ctypes.sizeof(subtype) * slice)
# TODO: do better ?
class TST(ctypes.Structure):
_fields_ = [("TST", subtype)]
return RemoteStructure.from_structure(TST)(item_addr, target=self.target).TST
def __getslice__(self, start, stop): # Still used even for python 2.7 wtf :F
stop = min(stop, len)
start = max(start, 0)
# dummy implementation
return [self[i] for i in range(start, stop)]
return RemoteArray
# 64bits pointers
class RemotePtr64(RemoteValue):
def __init__(self, value, target):
self.target = target
super(RemotePtr64, self).__init__(value)
@property
def raw_value(self):
# Bypass our own 'value' implementation
# Even if we are a subclass of c_ulonglong
my_addr = ctypes.addressof(self)
return ctypes.c_ulonglong.from_address(my_addr).value
class Remote_c_void_p64(RemotePtr64, c_void_p64):
pass
# base explanation:
# RemotePtr64 for the good `raw_value` implem
# RemoteCCharP for the good `value` implem
# c_char_p64 for the good _type_ (ctypes size)
class Remote_c_char_p64(c_char_p64, RemotePtr64, RemoteCCharP):
def __repr__(self):
return "<Remote_c_char_p64({0})>".format(self.raw_value)
class Remote_w_char_p64(c_wchar_p64, RemotePtr64, RemoteWCharP):
def __repr__(self):
return "<Remote_c_wchar_p64({0})>".format(self.raw_value)
class RemoteStructurePointer64(Remote_c_void_p64):
@property
def raw_value(self):
return self.value
@classmethod
def from_buffer_with_target_and_ptr_type(cls, buffer, offset=0, target=None, ptr_type=None):
x = cls.from_buffer(buffer)
x.target = target
x.real_pointer_type = ptr_type
return x
@property
def contents(self):
remote_pointed_type = transform_type_to_remote64bits(self.real_pointer_type._sub_ctypes_)
return remote_pointed_type(self.raw_value, self.target)
type_32_64_translation_table = {
ctypes.c_void_p: Remote_c_void_p64,
ctypes.c_char_p: Remote_c_char_p64,
ctypes.c_wchar_p: Remote_w_char_p64,
}
# 32bits pointers
class RemotePtr32(RemoteValue):
def __init__(self, value, target):
self.target = target
super(RemotePtr32, self).__init__(value)
@property
def raw_value(self):
# Bypass our own 'value' implementation
# Even if we are a subclass of c_ulonglong
my_addr = ctypes.addressof(self)
return ctypes.c_ulong.from_address(my_addr).value
class Remote_c_void_p32(RemotePtr32, c_void_p32):
pass
# base explanation:
# RemotePtr64 for the good `raw_value` implem
# RemoteCCharP for the good `value` implem
# c_char_p64 for the good _type_ (ctypes size)
class Remote_c_char_p32(c_char_p32, RemotePtr32, RemoteCCharP):
def __repr__(self):
return "<Remote_c_char_p32({0})>".format(self.raw_value)
class Remote_w_char_p32(c_wchar_p32, RemotePtr32, RemoteWCharP):
def __repr__(self):
return "<Remote_c_wchar_p32({0})>".format(self.raw_value)
class RemoteStructurePointer32(Remote_c_void_p32):
@property
def raw_value(self):
return self.value
@classmethod
def from_buffer_with_target_and_ptr_type(cls, buffer, offset=0, target=None, ptr_type=None):
x = cls.from_buffer(buffer)
x.target = target
x.real_pointer_type = ptr_type
return x
@property
def contents(self):
# What if we have a non-struct pointer
# Like a Ptr(DWORD) we would like to get the underlying value
realtype = self.real_pointer_type._sub_ctypes_
# if _SimpleCData in realtype.__bases__:
# A ctypes original value.
# Returnthe real pointed value
# Kind of a tricks for now compared to the real ctypes behavior
# import pdb;pdb.set_trace()
# if not self.value:
# return None
# targetptr = self.target.read_ptr(self.value)
# if not targetptr:
# return None
# ss = self.target.read_memory(targetptr, ctypes.sizeof(realtype))
# return realtype.from_buffer(bytearray(ss)).value
remote_pointed_type = transform_type_to_remote32bits(self.real_pointer_type._sub_ctypes_)
return remote_pointed_type(self.raw_value, self.target)
type_64_32_translation_table = {
ctypes.c_void_p: Remote_c_void_p32,
ctypes.c_char_p: Remote_c_char_p32,
ctypes.c_wchar_p: Remote_w_char_p32,
}
class RemoteStructureUnion(object):
"""Target is a process object"""
_reserved_name = ["_target", "_fields_", "_fields_dict_", "_base_addr", "_get_field_by_name",
"_get_field_descrptor_by_name", "_handle_field_getattr", "_field_type_to_remote_type",
"__getattribute__", "_fields_"]
_field_type_to_remote_type = {
ctypes.c_char_p: RemoteCCharP,
ctypes.c_wchar_p: RemoteWCharP,
Remote_c_void_p64: Remote_c_void_p64,
Remote_c_char_p64: Remote_c_char_p64,
Remote_w_char_p64: Remote_w_char_p64,
Remote_c_void_p32: Remote_c_void_p32,
Remote_c_char_p32: Remote_c_char_p32,
Remote_w_char_p32: Remote_w_char_p32
}
def __init__(self, base_addr, target):
self._target = target
self._base_addr = base_addr
self._fields_dict_ = dict(self._fields_)
def _get_field_by_name(self, fieldname):
try:
return self._fields_dict_[fieldname]
except KeyError:
raise AttributeError(fieldname + "is not a field of {0}".format(type(self)))
def _get_field_descrptor_by_name(self, fieldname):
return getattr(type(self), fieldname) # ctypes metaclass fill this for us
def _handle_field_getattr(self, ftype, fosset, fsize):
s = self._target.read_memory(self._base_addr + fosset, fsize)
if ftype in self._field_type_to_remote_type:
return self._field_type_to_remote_type[ftype].from_buffer_with_target(bytearray(s), target=self._target).value
if issubclass(ftype, _ctypes._Pointer): # Pointer
return RemoteStructurePointer.from_buffer_with_target_and_ptr_type(bytearray(s), target=self._target, ptr_type=ftype)
if issubclass(ftype, RemotePtr64): # Pointer to remote64 bits process
return RemoteStructurePointer64.from_buffer_with_target_and_ptr_type(bytearray(s), target=self._target, ptr_type=ftype)
if issubclass(ftype, RemotePtr32): # Pointer to remote32 bits process
return RemoteStructurePointer32.from_buffer_with_target_and_ptr_type(bytearray(s), target=self._target, ptr_type=ftype)
if issubclass(ftype, RemoteStructureUnion): # Structure|Union already transfomed in remote
return ftype(self._base_addr + fosset, self._target)
if issubclass(ftype, ctypes.Structure): # Structure that must be transfomed
return RemoteStructure.from_structure(ftype)(self._base_addr + fosset, self._target)
if issubclass(ftype, ctypes.Union): # Union that must be transfomed
return RemoteUnion.from_structure(ftype)(self._base_addr + fosset, self._target)
if issubclass(ftype, _ctypes.Array): # Arrays
# if this is a string: just cast the read value to string
if ftype._type_ == ctypes.c_char: # Use issubclass instead ?
return s.split(b"\x00", 1)[0]
elif ftype._type_ == ctypes.c_wchar: # Use issubclass instead ?
# Decode from utf16 -> size /=2 | put it in a wchar array | split at the first "\x00"
return (ftype._type_ * (fsize / 2)).from_buffer_copy(s.decode('utf16'))[:].split("\x00", 1)[0] # Sorry..
# I am pretty sur something smarter is possible..
return create_remote_array(ftype._type_, ftype._length_)(self._base_addr + fosset, self._target)
# Normal types
# Follow the ctypes usage: if it's not directly inherited from _SimpleCData
# We do not apply the .value
# Seems weird but it's mandatory AND useful :D (in pe_parse)
if _SimpleCData not in ftype.__bases__:
return ftype.from_buffer(bytearray(s))
return ftype.from_buffer(bytearray(s)).value
def __getattribute__(self, fieldname):
if fieldname in type(self)._reserved_name: # Prevent recursion !
return super(RemoteStructureUnion, self).__getattribute__(fieldname)
try:
t = self._get_field_by_name(fieldname)
except AttributeError: # Not a real attribute
return super(RemoteStructureUnion, self).__getattribute__(fieldname)
descr = self._get_field_descrptor_by_name(fieldname)
return self._handle_field_getattr(t, descr.offset, descr.size)
@classmethod
def from_structure(cls, structcls):
class MyStruct(cls, structcls): # inherit of structcls to keep property (see winobject.LoadedModule)
_fields_ = structcls._fields_
MyStruct.__name__ = "Remote" + structcls.__name__
return MyStruct
@classmethod
def from_fields(cls, fields, base_cls=None):
bases = [cls]
if base_cls:
bases.append(base_cls)
# inherit of structcls to keep property (see winobject.LoadedModule)
RemoteStruct = type("RemoteStruct", tuple(bases), {"_fields_": fields})
if base_cls:
RemoteStruct.__name__ = "Remote" + base_cls.__name__
return RemoteStruct
class RemoteStructure(RemoteStructureUnion, ctypes.Structure):
pass
class RemoteUnion(RemoteStructureUnion, ctypes.Union):
pass
remote_struct = RemoteStructure.from_structure
# ctypes 32 -> 64 methods
def MakePtr64(type):
class PointerToStruct64(Remote_c_void_p64):
_sub_ctypes_ = (type)
@property
def contents(self):
return RemoteStructurePointer64.from_buffer_with_target_and_ptr_type(bytearray(self), target=self.target, ptr_type=self).contents
def __repr__(self):
return "<RemotePtr64 to struct {0}>".format(type.__name__)
return PointerToStruct64
def transform_structure_to_remote64bits(structcls):
"""Create a remote structure for a 64bits target process"""
new_fields = []
for fname, ftype in structcls._fields_:
ftype = transform_type_to_remote64bits(ftype)
new_fields.append((fname, ftype))
return RemoteStructure.from_fields(new_fields, base_cls=structcls)
def transform_union_to_remote64bits(structcls):
"""Create a remote union for a 64bits target process"""
new_fields = []
for fname, ftype in structcls._fields_:
ftype = transform_type_to_remote64bits(ftype)
new_fields.append((fname, ftype))
return RemoteUnion.from_fields(new_fields, base_cls=structcls)
def transform_type_to_remote64bits(ftype):
if is_pointer_type(ftype):
return MakePtr64(ftype._type_)
if is_array_type(ftype):
return create_remote_array(transform_type_to_remote64bits(ftype._type_), ftype._length_)
if is_structure_type(ftype):
return transform_structure_to_remote64bits(ftype)
if is_union_type(ftype):
return transform_union_to_remote64bits(ftype)
# Normal types
return type_32_64_translation_table.get(ftype, ftype)
# ctypes 64 -> 32 methods
def MakePtr32(type):
class PointerToStruct32(Remote_c_void_p32):
_sub_ctypes_ = (type)
# Not sur about this code..
# Logic problem: why do I have PointerToStruct32 and RemoteStructurePointer32... ?
@property
def contents(self):
return RemoteStructurePointer32.from_buffer_with_target_and_ptr_type(bytearray(self), target=self.target, ptr_type=self).contents
def __repr__(self):
return "<RemotePtr32 to struct {0}>".format(type.__name__)
return PointerToStruct32
def transform_structure_to_remote32bits(structcls):
"""Create a remote structure for a 32bits target process"""
new_fields = []
for fname, ftype in structcls._fields_:
ftype = transform_type_to_remote32bits(ftype)
new_fields.append((fname, ftype))
return RemoteStructure.from_fields(new_fields, base_cls=structcls)
def transform_union_to_remote32bits(structcls):
"""Create a remote union for a 32bits target process"""
new_fields = []
for fname, ftype in structcls._fields_:
ftype = transform_type_to_remote32bits(ftype)
new_fields.append((fname, ftype))
return RemoteUnion.from_fields(new_fields, base_cls=structcls)
def transform_type_to_remote32bits(ftype):
if issubclass(ftype, RemoteStructureUnion):
return ftype
if is_pointer_type(ftype):
return MakePtr32(ftype._type_)
if is_array_type(ftype):
return create_remote_array(transform_type_to_remote32bits(ftype._type_), ftype._length_)
if is_structure_type(ftype):
return transform_structure_to_remote32bits(ftype)
if is_union_type(ftype):
return transform_union_to_remote32bits(ftype)
# Normal types
return type_64_32_translation_table.get(ftype, ftype)
if ctypes.sizeof(ctypes.c_void_p) == 4:
transform_type_to_remote = transform_type_to_remote32bits
if ctypes.sizeof(ctypes.c_void_p) == 8:
transform_type_to_remote = transform_type_to_remote64bits
@@ -1,3 +0,0 @@
from . import ndr
from .client import RPCClient
from .epmapper import find_alpc_endpoint_and_connect, find_alpc_endpoints, construct_alpc_tower
-180
View File
@@ -1,180 +0,0 @@
import ctypes
import struct
import windows.alpc as alpc
import windows.com
import windows.generated_def as gdef
if windows.pycompat.is_py3:
buffer = bytes
KNOW_REQUEST_TYPE = gdef.FlagMapper(gdef.RPC_REQUEST_TYPE_CALL, gdef.RPC_REQUEST_TYPE_BIND)
KNOW_RESPONSE_TYPE = gdef.FlagMapper(gdef.RPC_RESPONSE_TYPE_FAIL, gdef.RPC_RESPONSE_TYPE_SUCCESS, gdef.RPC_RESPONSE_TYPE_BIND_OK)
KNOWN_RPC_ERROR_CODE = gdef.FlagMapper(
gdef.ERROR_INVALID_HANDLE,
gdef.RPC_X_BAD_STUB_DATA,
gdef.RPC_S_UNKNOWN_IF,
gdef.RPC_S_PROTOCOL_ERROR,
gdef.RPC_S_UNSUPPORTED_TRANS_SYN,
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", gdef.RPC_IF_ID),
("flags", gdef.DWORD),
("if_nb_ndr32", gdef.USHORT),
("if_nb_ndr64", gdef.USHORT),
("if_nb_unkn", gdef.USHORT),
("PAD", gdef.USHORT),
("register_multiple_syntax", gdef.DWORD),
("use_flow", gdef.DWORD),
("UNK5", gdef.DWORD),
("maybe_flow_id", gdef.DWORD),
("UNK7", gdef.DWORD),
("some_context_id", gdef.DWORD),
("UNK9", gdef.DWORD),
]
class ALPC_RPC_CALL(ctypes.Structure):
_pack_ = 1
_fields_ = [
("request_type", gdef.DWORD),
("UNK1", gdef.DWORD),
("flags",gdef.DWORD),
("request_id", gdef.DWORD),
("if_nb", gdef.DWORD),
("method_offset", gdef.DWORD),
("UNK2", gdef.DWORD),
("UNK3", gdef.DWORD),
("UNK4", gdef.DWORD),
("UNK5", gdef.DWORD),
("UNK6", gdef.DWORD),
("UNK7", gdef.DWORD),
("ORPC_IPID", gdef.GUID)
]
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) #: The :class:`windows.alpc.AlpcClient` used to communicate with the server
self.number_of_bind_if = 0 # if -> interface
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)
# Parse reponse
request_type = self._get_request_type(response)
if request_type != gdef.RPC_RESPONSE_TYPE_BIND_OK:
raise ValueError("Unexpected reponse type. Expected RESPONSE_TYPE_BIND_OK got {0}".format(KNOW_RESPONSE_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 forge_alpc_request(self, IID, method_offset, params, ipid=None):
"""Craft an ALPC message containing an RPC request to call ``method_offset`` of interface ``IID`
with ``params``.
Can be used to craft request without directly sending it
"""
iid_hash = hash(buffer(IID)[:])
interface_nb = self.if_bind_number[iid_hash] # TODO: add __hash__ to IID
if len(params) > 0x900: # 0x1000 - size of meta-data
request = self._forge_call_request_in_view(interface_nb, method_offset, params, ipid=ipid)
else:
request = self._forge_call_request(interface_nb, method_offset, params, ipid=ipid)
return request
def call(self, IID, method_offset, params, ipid=None):
"""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`
"""
request = self.forge_alpc_request(IID, method_offset, params, ipid=ipid)
response = self._send_request(request)
# Parse reponse
request_type = self._get_request_type(response)
if request_type != gdef.RPC_RESPONSE_TYPE_SUCCESS:
raise ValueError("Unexpected reponse type. Expected RESPONSE_SUCCESS got {0}".format(KNOW_RESPONSE_TYPE[request_type]))
# windows.utils.sprint(ALPC_RPC_CALL.from_buffer_copy(response + "\x00" * 12))
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):
response = self.alpc_client.send_receive(request)
return response.data
def _forge_call_request(self, interface_nb, method_offset, params, ipid=None):
# TODO: differents REQUEST_IDENTIFIER for each req ?
# TODO: what is this '0' ? (1 is also accepted) (flags ?)
# request = struct.pack("<16I", gdef.RPC_REQUEST_TYPE_CALL, NOT_USED, 1, self.REQUEST_IDENTIFIER, interface_nb, method_offset, *[NOT_USED] * 10)
req = ALPC_RPC_CALL()
req.request_type = gdef.RPC_REQUEST_TYPE_CALL
req.flags = 0
req.request_id = self.REQUEST_IDENTIFIER
req.if_nb = interface_nb
req.method_offset = method_offset
if ipid:
req.ORPC_IPID = ipid
this = gdef.ORPCTHIS()
this.version = (5,7)
this.flags = 1
lthis = gdef.LOCALTHIS()
return buffer(req)[:] + buffer(this)[:] + buffer(lthis)[:] + params
return buffer(req)[:] + params
def _forge_call_request_in_view(self, interface_nb, method_offset, params, ipid=None):
# import pdb;pdb.set_trace()
# Version crade qui clean rien pour POC. GROS DOUTES :D
raw_request = self._forge_call_request(interface_nb, method_offset, "")
p = windows.alpc.AlpcMessage(0x2000)
section = self.alpc_client.create_port_section(0x40000, 0, len(params))
view = self.alpc_client.map_section(section[0], len(params))
p.port_message.data = raw_request + windows.rpc.ndr.NdrLong.pack(len(params) + 0x200) + "\x00" * 40
p.attributes.ValidAttributes |= gdef.ALPC_MESSAGE_VIEW_ATTRIBUTE
p.view_attribute.Flags = 0x40000
p.view_attribute.ViewBase = view.ViewBase
p.view_attribute.SectionHandle = view.SectionHandle
p.view_attribute.ViewSize = len(params)
windows.current_process.write_memory(view.ViewBase, params) # Write NDR to view
return p
def _forge_bind_request(self, uuid, syntaxversion, requested_if_nb):
version_major, version_minor = syntaxversion
req = ALPC_RPC_BIND()
req.request_type = gdef.RPC_REQUEST_TYPE_BIND
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
req.if_nb_unkn = 0
req.register_multiple_syntax = False
req.some_context_id = 0xB00B00B
return buffer(req)[:]
def _get_request_type(self, response):
"raise if request_type == RESPONSE_TYPE_FAIL"
request_type = struct.unpack("<I", response[:4])[0]
if request_type == gdef.RPC_RESPONSE_TYPE_FAIL:
error_code = struct.unpack("<5I", response)[2]
raise ValueError("RPC Response error {0} ({1})".format(error_code, KNOWN_RPC_ERROR_CODE.get(error_code, error_code)))
return request_type
-199
View File
@@ -1,199 +0,0 @@
import struct
from collections import namedtuple
import windows
import windows.generated_def as gdef
from windows.rpc import ndr
from windows.dbgprint import dbgprint
from windows.pycompat import basestring
class NdrTower(ndr.NdrStructure):
MEMBERS = [ndr.NdrLong, ndr.NdrByteConformantArray]
@classmethod
def post_unpack(cls, data):
size = data[0]
tower = data[1]
return bytearray(struct.pack("<I", size)) + bytearray(tower)
class NdrContext(ndr.NdrStructure):
MEMBERS = [ndr.NdrLong, ndr.NdrLong, ndr.NdrLong, ndr.NdrLong, ndr.NdrLong]
class NDRIID(ndr.NdrStructure):
MEMBERS = [ndr.NdrByte] * 16
class EptMapAuthParameters(ndr.NdrParameters):
MEMBERS = [NDRIID,
NdrTower,
ndr.NdrUniquePTR(ndr.NdrSID),
NdrContext,
ndr.NdrLong]
class Towers(ndr.NdrConformantVaryingArrays):
MEMBER_TYPE = ndr.NdrUniquePTR(NdrTower)
class EptMapAuthResults(ndr.NdrParameters):
MEMBERS = [NdrContext,
ndr.NdrLong,
Towers]
UnpackTower = namedtuple("UnpackTower", ["protseq", "endpoint", "address", "object", "syntax"])
def parse_floor(stream):
lhs_size = stream.partial_unpack("<H")[0]
lhs = stream.read(lhs_size)
rhs_size = stream.partial_unpack("<H")[0]
rhs = stream.read(rhs_size)
return lhs, rhs
def craft_floor(lhs, rhs):
return struct.pack("<H", len(lhs)) + lhs + struct.pack("<H", len(rhs)) + rhs
def explode_alpc_tower(tower):
stream = ndr.NdrStream(bytearray(tower))
size = stream.partial_unpack("<I")[0]
if size != len(stream.data):
raise ValueError("Invalid tower size: indicate {0}, tower size {1}".format(size, len(stream.data)))
floor_count = stream.partial_unpack("<H")[0]
if floor_count != 4:
raise ValueError("ALPC Tower are expected to have 4 floors ({0} instead)".format(floor_count))
# Floor 0
lhs, rhs = parse_floor(stream)
if not (lhs[0] == 0xd):
raise ValueError("Floor 0: IID expected")
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 = 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)
if (len(lhs) != 1 or lhs[0] != 0x0c):
raise ValueError("Alpc Tower expects 0xc as Floor2 LHS (got {0:#x})".format(lhs[0]))
lhs, rhs = parse_floor(stream)
if not (rhs[-1] == 0):
rhs = rhs[:rhs.find("\x00")]
# raise ValueError("ALPC Port name doest not end by \\x00")
return UnpackTower("ncalrpc", bytes(rhs[:-1]), None, object, syntax)
# http://pubs.opengroup.org/onlinepubs/9629399/apdxi.htm#tagcjh_28
# Octet 0 contains the hexadecimal value 0d. This is a reserved protocol identifier prefix that indicates that the protocol ID is UUID derived
TOWER_PROTOCOL_IS_UUID = b"\x0d"
TOWER_EMPTY_RHS = b"\x00\x00"
TOWER_PROTOCOL_ID_ALPC = b"\x0c" # From RE
def construct_alpc_tower(object, syntax, protseq, endpoint, address):
if address is not None:
raise NotImplementedError("Construct ALPC Tower with address != None")
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.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(syntax.Uuid) + struct.pack("<BB", syntax.VersMajor, syntax.VersMinor)
floor_1_rsh = TOWER_EMPTY_RHS
floor_1 = craft_floor(floor_1_lsh, floor_1_rsh)
# Floor 2
floor_2_lsh = TOWER_PROTOCOL_ID_ALPC
floor_2_rsh = TOWER_EMPTY_RHS
floor_2 = craft_floor(floor_2_lsh, floor_2_rsh)
# Floor 3
if endpoint is None:
floor_3_lsh = b"\xff"
floor_3_rsh = TOWER_EMPTY_RHS
floor_3 = craft_floor(floor_3_lsh, floor_3_rsh)
else:
floor_3_lsh = b"\x10"
floor_3_rsh = endpoint
floor_3 = craft_floor(floor_3_lsh, floor_3_rsh)
towerarray = struct.pack("<H", 4) + floor_0 + floor_1 + floor_2 + floor_3
return len(towerarray), bytearray(towerarray)
def find_alpc_endpoints(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 = 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 = gdef.RPC_IF_ID(targetiid, *version)
## Syntax
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", b"", None)
# parameters
local_system_psid = windows.utils.get_known_sid(sid)
context = (0, 0, 0, 0, 0)
# Pack request
fullreq = EptMapAuthParameters.pack([bytearray(targetiid),
(tower_array_size, towerarray),
local_system_psid,
context,
nb_response])
# RPC Call
response = client.call(epmapperiid, 7, fullreq)
# Unpack response
stream = ndr.NdrStream(response)
unpacked = EptMapAuthResults.unpack(stream)
# Looks like there is a memory leak here (in stream.data) if nb_response > 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), 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 = find_alpc_endpoints(targetiid, version, nb_response=50, sid=sid)
dbgprint("ALPC endpoints list: <{0}>".format(alpctowers), "RPC")
for tower in alpctowers:
dbgprint("Trying to connect to endpoint <{0}>".format(tower.endpoint), "RPC")
alpc_port = r"\RPC Control\{0}".format(tower.endpoint.decode())
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
-618
View File
@@ -1,618 +0,0 @@
import windows
import windows.generated_def as gdef
import struct
try:
unichr # Py2/Py3 compat
except NameError:
unichr = chr
# 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("<I", x)
def dword_pad(s):
if (len(s) % 4) == 0:
return s
return s + (b"P" * (4 - len(s) % 4))
class NdrUniquePTR(object):
"""Create a UNIQUE PTR around a given Ndr type"""
def __init__(self, subcls):
self.subcls = subcls
def pack(self, data):
subpack = self.subcls.pack(data)
if subpack is None:
return pack_dword(0)
return pack_dword(0x02020202) + subpack
def unpack(self, stream):
ptr = NdrLong.unpack(stream)
if not ptr:
return None
return self.subcls.unpack(stream)
def pack_in_struct(self, data, id):
if data is None:
return pack_dword(0), None
subpack = self.subcls.pack(data)
if subpack is None:
return pack_dword(0), None
return pack_dword(0x01010101 * (id + 1)), subpack
def unpack_in_struct(self, stream):
ptr = NdrLong.unpack(stream)
if not ptr:
return 0, NdrUnpackNone
return ptr, self.subcls
def parse(self, stream):
data = stream.partial_unpack("<I")
if data[0] == 0:
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):
return None
class NdrRef(object):
# TESTING
def __init__(self, subcls):
self.subcls = subcls
def unpack(self, stream):
ptr = NdrLong.unpack(stream)
if not ptr:
raise ValueError("Ndr REF cannot be NULL")
return self.subcls.unpack(stream)
class NdrFixedArray(object):
def __init__(self, subcls, size):
self.subcls = subcls
self.size = size
def pack(self, data):
data = list(data)
assert len(data) == self.size
return dword_pad(b"".join([self.subcls.pack(elt) for elt in data]))
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
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)
return pack_dword(subcount[0]) + dword_pad(sid_data)
@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))
@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):
"""Pack string ``data``. append ``\\x00`` if not present at the end of the string"""
if data is None:
return None
if not data.endswith('\x00'):
data += '\x00'
l = len(data)
result = struct.pack("<2I", 0, l)
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):
"""Pack string ``data``. append ``\\x00`` if not present at the end of the string"""
if data is None:
return None
if not data.endswith('\x00'):
data += '\x00'
data = data.encode("utf-16-le")
l = (len(data) // 2)
result = struct.pack("<3I", l, 0, l)
result += data
return dword_pad(result)
@classmethod
def unpack(cls, stream):
stream.align(4)
size1, zero, size2 = stream.partial_unpack("<3I")
assert size1 == size2
assert zero == 0
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):
"""Pack string ``data``. append ``\\x00`` if not present at the end of the string"""
if data is None:
return None
if not data.endswith('\x00'):
data += '\x00'
l = len(data)
result = struct.pack("<3I", l, 0, l)
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")
# return maxcount, offset, count
NdrUniqueCString = NdrUniquePTR(NdrCString)
NdrUniqueWString = NdrUniquePTR(NdrWString)
class NdrLong(object):
@classmethod
def pack(cls, data):
return struct.pack("<I", data)
@classmethod
def unpack(self, stream):
stream.align(4)
return stream.partial_unpack("<I")[0]
@classmethod
def get_alignment(self):
return 4
class NdrHyper(object):
@classmethod
def pack(cls, data):
return struct.pack("<Q", data)
@classmethod
def unpack(self, stream):
stream.align(8)
return stream.partial_unpack("<Q")[0]
@classmethod
def get_alignment(self):
return 8
class NdrShort(object):
@classmethod
def pack(cls, data):
return struct.pack("<H", data)
@classmethod
def unpack(self, stream):
return stream.partial_unpack("<H")[0]
@classmethod
def get_alignment(self):
return 2
class NdrByte(object):
@classmethod
def pack(self, data):
return struct.pack("<B", data)
@classmethod
def unpack(self, stream):
return stream.partial_unpack("<B")[0]
@classmethod
def get_alignment(self):
return 1
class NdrGuid(object):
@classmethod
def pack(cls, data):
if not isinstance(data, gdef.IID):
data = gdef.IID.from_string(data)
return bytes(bytearray(data))
@classmethod
def unpack(self, stream):
rawguid = stream.partial_unpack("16s")[0]
return gdef.IID.from_buffer_copy(rawguid)
@classmethod
def get_alignment(self):
return 1
class NdrContextHandle(object):
@classmethod
def pack(cls, data):
if not isinstance(data, gdef.IID):
data = gdef.IID.from_string(data)
return bytes(struct.pack("<I", 0) + bytearray(data))
@classmethod
def unpack(self, stream):
attributes, rawguid = stream.partial_unpack("<I16s")
return gdef.IID.from_buffer_copy(rawguid)
@classmethod
def get_alignment(self):
return 4
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)))
print(" * members size = {0}".format(len(cls.MEMBERS)))
print(" * data {0}".format(data))
print(" * members = {0}".format(cls.MEMBERS))
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()
pointed_to_pack = []
# pointedoutstream = 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)
assert len(x) == 4, "Pointer should be size 4"
# Write the pointer
outstream.align(4)
outstream.write(x)
if y is not None:
# Store the info to the pointed to pack
pointed_to_pack.append((member.subcls.get_alignment(), y))
# pointedoutstream.write(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_size += len(data)
else:
packed_member = member.pack(memberdata)
outstream.align(member.get_alignment())
outstream.write(packed_member)
# Pack the pointed to the stream
for alignement, pointed_data in pointed_to_pack:
outstream.align(alignement)
outstream.write(pointed_data)
return dword_pad(b"".join(conformant_size)) + outstream.get_data()
@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"
data = []
if is_conformant:
conformant_size = NdrLong.unpack(stream)
post_subcls = []
for i, member in enumerate(cls.MEMBERS):
if conformant_members[i]:
data.append(member.unpack_conformant(stream, conformant_size))
else:
if hasattr(member, "unpack_in_struct"):
# print("[{0}] Dereferenced unpacking".format(i))
ptr, subcls = member.unpack_in_struct(stream)
if not ptr:
data.append(None)
else:
data.append(ptr)
post_subcls.append((i, subcls))
# print(post_subcls)
else:
data.append(member.unpack(stream))
# print("Applying deref unpack")
for i, entry in post_subcls:
new_data = entry.unpack(stream)
if getattr(entry, "post_unpack", None):
new_data = entry.post_unpack(new_data)
data[i] = new_data
return cls.post_unpack(data)
@classmethod
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):
"""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)):
print("Size mistach:")
print(" * data size = {0}".format(len(data)))
print(" * members size = {0}".format(len(cls.MEMBERS)))
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)))
outstream = NdrWriteStream()
for (member, memberdata) in zip(cls.MEMBERS, data):
alignment = member.get_alignment()
outstream.align(alignment)
packed_member = member.pack(memberdata)
outstream.write(packed_member)
return outstream.get_data()
@classmethod
def unpack(cls, stream):
res = []
for member in cls.MEMBERS:
unpacked_member = member.unpack(stream)
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
@classmethod
def pack(cls, data):
ndrsize = NdrLong.pack(len(data))
return dword_pad(ndrsize + b"".join([cls.MEMBER_TYPE.pack(memberdata) for memberdata in data]))
@classmethod
def pack_conformant(cls, data):
ndrsize = NdrLong.pack(len(data))
ndrdata = dword_pad(b"".join([cls.MEMBER_TYPE.pack(memberdata) for memberdata in data]))
return ndrsize, ndrdata
@classmethod
def unpack(cls, stream):
nbelt = NdrLong.unpack(stream)
result = cls.unpack_conformant(stream, nbelt)
return cls._post_unpack(result)
@classmethod
def _post_unpack(cls, result):
return result
@classmethod
def unpack_conformant(cls, stream, size):
res = [cls.MEMBER_TYPE.unpack(stream) for i in range(size)]
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
@classmethod
def pack(cls, data):
ndrsize = NdrLong.pack(len(data))
offset = NdrLong.pack(0)
return dword_pad(ndrsize + offset + ndrsize + b"".join([cls.MEMBER_TYPE.pack(memberdata) for memberdata in data]))
@classmethod
def unpack(cls, stream):
maxcount = NdrLong.unpack(stream)
offset = NdrLong.unpack(stream)
count = NdrLong.unpack(stream)
assert(offset == 0)
# assert(maxcount == count)
result = []
post_subcls = []
for i in range(count):
member = cls.MEMBER_TYPE
if hasattr(member, "unpack_in_struct"):
ptr, subcls = member.unpack_in_struct(stream)
if not ptr:
result.append(None)
else:
result.append(ptr)
post_subcls.append((i, subcls))
else:
data = member.unpack(stream)
result.append(data)
# Unpack pointers
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
def get_alignment(self):
# TODO: test on array of Hyper
return max(4, self.MEMBER_TYPE.get_alignment())
class NdrWcharConformantVaryingArrays(NdrConformantVaryingArrays):
MEMBER_TYPE = NdrShort
@classmethod
def _post_unpack(self, result):
return u"".join(unichr(c) for c in result)
class NdrCharConformantVaryingArrays(NdrConformantVaryingArrays):
MEMBER_TYPE = NdrByte
class NdrHyperConformantVaryingArrays(NdrConformantVaryingArrays):
MEMBER_TYPE = NdrHyper
class NdrHyperConformantArray(NdrConformantArray):
MEMBER_TYPE = NdrHyper
class NdrLongConformantArray(NdrConformantArray):
MEMBER_TYPE = NdrLong
class NdrShortConformantArray(NdrConformantArray):
MEMBER_TYPE = NdrShort
class NdrByteConformantArray(NdrConformantArray):
MEMBER_TYPE = NdrByte
@classmethod
def _post_unpack(self, result):
return bytearray(result)
class NdrWcharConformantArray(NdrConformantArray):
MEMBER_TYPE = NdrShort
@classmethod
def _post_unpack(self, result):
return bytearray(result)
class NdrGuidConformantArray(NdrConformantArray):
MEMBER_TYPE = NdrGuid
class NdrStream(object):
"""A stream of bytes used for NDR unpacking"""
def __init__(self, data):
self.fulldata = data
self.data = data
def partial_unpack(self, format):
size = struct.calcsize(format)
toparse = self.data[:size]
self.data = self.data[size:]
return struct.unpack(format, toparse)
def read_aligned_dword(self, size):
aligned_size = size
if size % 4:
aligned_size = size + (4 - (size % 4))
retdata = self.data[:size]
self.data = self.data[aligned_size:]
return retdata
def read(self, size):
data = self.data[:size]
self.data = self.data[size:]
if len(data) < size:
raise ValueError("Could not read {0} from stream".format(size))
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
size_to_align = (size - (already_read % size))
self.data = self.data[size_to_align:]
# print("align {0}: {1}".format(size, size_to_align))
return size_to_align
# 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):
MEMBERS = types
return NdrCustomParameters
def make_structure(types, name=None):
class NdrCustomStructure(NdrStructure):
MEMBERS = types
return NdrCustomStructure
File diff suppressed because it is too large Load Diff
-331
View File
@@ -1,331 +0,0 @@
import struct
import ctypes
from ctypes import byref
import codecs
import functools
import threading
import windows
import windows.native_exec.simple_x86 as x86
import windows.native_exec.simple_x64 as x64
from .generated_def.winstructs import *
from windows.winobject import process
from windows import winproxy
from .winproxy import NeededParameter
from .pycompat import int_types
# Special code for syswow64 process
CS_32bits = 0x23
CS_64bits = 0x33
# Allow to keep per-thread state of asm stub
class ThreadState(threading.local):
def __init__(self): # Called once per thread
self.allocator = windows.native_exec.native_function.CustomAllocator()
self.raw_call_per_function = {}
self.current_original_args = None
thread_state = ThreadState()
def generate_64bits_execution_stub_from_syswow(x64shellcode):
"""shellcode must NOT end by a ret"""
current_process = windows.current_process
if not current_process.is_wow_64:
raise ValueError("Calling generate_64bits_execution_stub_from_syswow from non-syswow process")
transition64 = x64.MultipleInstr()
transition64 += x64.Call(":TOEXEC")
transition64 += x64.Mov("RDX", "RAX")
transition64 += x64.Shr("RDX", 32)
transition64 += x64.Retf32() # 32 bits return addr
transition64 += x64.Label(":TOEXEC")
x64shellcodeaddr = thread_state.allocator.write_code(transition64.get_code() + x64shellcode)
transition = x86.MultipleInstr()
transition += x86.Call(CS_64bits, x64shellcodeaddr)
# Reset the SS segment selector.
# We need to do that due to a bug in AMD CPUs with RETF & SS
# https://github.com/hakril/PythonForWindows/issues/10
# http://blog.rewolf.pl/blog/?p=1484
transition += x86.Mov("ECX", "SS")
transition += x86.Mov("SS", "ECX")
transition += x86.Ret()
stubaddr = thread_state.allocator.write_code(transition.get_code())
exec_stub = ctypes.CFUNCTYPE(ULONG64)(stubaddr)
return exec_stub
def execute_64bits_code_from_syswow(x64shellcode):
return generate_64bits_execution_stub_from_syswow(x64shellcode)()
def generate_syswow64_call(target, errcheck=None):
nb_args = len(target.prototype._argtypes_)
target_addr = get_syswow_ntdll_exports()[target.__name__]
argument_buffer_len = (nb_args * 8)
argument_buffer = thread_state.allocator.reserve_size(argument_buffer_len)
alignement_information = thread_state.allocator.reserve_size(8)
nb_args_on_stack = max(nb_args - 4, 0)
code_64b = x64.MultipleInstr()
# Save registers
code_64b += x64.Push('RBX')
code_64b += x64.Push('RCX')
code_64b += x64.Push('RDX')
code_64b += x64.Push('RSI')
code_64b += x64.Push('RDI')
code_64b += x64.Push('R8')
code_64b += x64.Push('R9')
code_64b += x64.Push('R10')
code_64b += x64.Push('R11')
code_64b += x64.Push('R12')
code_64b += x64.Push('R13')
# Alignment stuff :)
code_64b += x64.Mov('RCX', 'RSP')
code_64b += x64.And('RCX', 0x0f)
code_64b += x64.Mov(x64.deref(alignement_information), 'RCX')
code_64b += x64.Sub('RSP', 'RCX')
# retrieve argument from the argument buffer
if nb_args >= 1:
code_64b += x64.Mov('RCX', x64.create_displacement(disp=argument_buffer))
if nb_args >= 2:
code_64b += x64.Mov('RDX', x64.create_displacement(disp=argument_buffer + (8 * 1)))
if nb_args >= 3:
code_64b += x64.Mov('R8', x64.create_displacement(disp=argument_buffer + (8 * 2)))
if nb_args >= 4:
code_64b += x64.Mov('R9', x64.create_displacement(disp=argument_buffer + (8 * 3)))
for i in range(nb_args_on_stack):
code_64b += x64.Mov('RAX', x64.create_displacement(disp=argument_buffer + 8 * (nb_args - 1 - i)))
code_64b += x64.Push('RAX')
# reserve space for register (calling convention)
code_64b += x64.Push('R9')
code_64b += x64.Push('R8')
code_64b += x64.Push('RDX')
code_64b += x64.Push('RCX')
# Call
code_64b += x64.Mov('R13', target_addr)
code_64b += x64.Call('R13')
# Realign stack :)
code_64b += x64.Add('RSP', x64.deref(alignement_information))
# Clean stack
code_64b += x64.Add('RSP', (4 + nb_args_on_stack) * 8)
code_64b += x64.Pop('R13')
code_64b += x64.Pop('R12')
code_64b += x64.Pop('R11')
code_64b += x64.Pop('R10')
code_64b += x64.Pop('R9')
code_64b += x64.Pop('R8')
code_64b += x64.Pop('RDI')
code_64b += x64.Pop('RSI')
code_64b += x64.Pop('RDX')
code_64b += x64.Pop('RCX')
code_64b += x64.Pop('RBX')
code_64b += x64.Ret()
return try_generate_stub_target(code_64b.get_code(), argument_buffer, target, errcheck=errcheck)
def try_generate_stub_target(shellcode, argument_buffer, target, errcheck=None):
if not windows.current_process.is_wow_64:
raise ValueError("Calling execute_64bits_code_from_syswow from non-syswow process")
native_caller = generate_64bits_execution_stub_from_syswow(shellcode)
native_caller.errcheck = errcheck if errcheck is not None else target.errcheck
# Generate the wrapper function that fill the argument_buffer
expected_arguments_number = len(target.prototype._argtypes_)
def wrapper(*args):
if len(args) != expected_arguments_number:
raise ValueError("{0} syswow accept {1} args ({2} given)".format(target.__name__, expected_arguments_number, len(args)))
# Transform args (ctypes byref possibly) to int
writable_args = []
for i, value in enumerate(args):
if not isinstance(value, int_types):
try:
value = ctypes.cast(value, ctypes.c_void_p).value
except ctypes.ArgumentError as e:
raise ctypes.ArgumentError("Argument {0}: wrong type <{1}>".format(i, type(value).__name__))
writable_args.append(value)
# Build buffer
buffer = struct.pack("<" + "Q" * len(writable_args), *writable_args)
ctypes.memmove(argument_buffer, buffer, len(buffer))
# Copy origincal args in function, for errcheck if needed
thread_state.current_original_args = args
return native_caller()
wrapper.__name__ = "{0}<syswow64>".format(target.__name__,)
wrapper.__doc__ = "This is a wrapper to {0} in 64b mode, it accept <{1}> args".format(target.__name__, expected_arguments_number)
return wrapper
def get_current_process_syswow_peb_addr():
get_peb_64_code = x64.assemble("mov rax, gs:[0x60]; ret")
return execute_64bits_code_from_syswow(get_peb_64_code)
def get_current_process_syswow_peb():
current_process = windows.current_process
class CurrentProcessReadSyswow(process.Process):
bitness = 64
def _get_handle(self):
return winproxy.OpenProcess(dwProcessId=current_process.pid)
def read_memory(self, addr, size):
buffer_addr = ctypes.create_string_buffer(size)
winproxy.NtWow64ReadVirtualMemory64(self.handle, addr, buffer_addr, size)
return buffer_addr[:]
peb_addr = get_current_process_syswow_peb_addr()
return windows.winobject.process.RemotePEB64(peb_addr, CurrentProcessReadSyswow())
class ReadSyswow64Process(process.Process):
def __init__(self, target):
self.target = target
self._bitness = target.bitness
def _get_handle(self):
return self.target.handle
def read_memory(self, addr, size):
buffer_addr = ctypes.create_string_buffer(size)
winproxy.NtWow64ReadVirtualMemory64(self.target.handle, addr, buffer_addr, size)
return buffer_addr[:]
#read_string = process.Process.read_string
def get_syswow_ntdll_exports():
if get_syswow_ntdll_exports.value is not None:
return get_syswow_ntdll_exports.value
peb64 = get_current_process_syswow_peb()
ntdll64 = [m for m in peb64.modules if m.name == "ntdll.dll"]
if not ntdll64:
raise ValueError("Could not find ntdll.dll in syswow peb")
ntdll64 = ntdll64[0]
exports = ntdll64.pe.exports
get_syswow_ntdll_exports.value = exports
return exports
get_syswow_ntdll_exports.value = None
class Syswow64ApiProxy(object):
"""Create a python wrapper around a function"""
def __init__(self, winproxy_function, errcheck=None):
self.winproxy_function = winproxy_function
self.errcheck = errcheck
if winproxy_function is not None:
self.params_name = [param[1] for param in winproxy_function.params]
def __call__(self, python_proxy):
if not windows.winproxy.is_implemented(self.winproxy_function):
return None
def force_resolution():
if self.winproxy_function in thread_state.raw_call_per_function:
return True
try:
stub = generate_syswow64_call(self.winproxy_function, errcheck=self.errcheck)
thread_state.raw_call_per_function[self.winproxy_function] = stub
except KeyError:
raise windows.winproxy.ExportNotFound(self.winproxy_function.__name__, "SysWow[ntdll64]")
def perform_call(*args):
if len(self.params_name) != len(args):
print("ERROR:")
print("Expected params: {0}".format(self.params_name))
print("Just Got params: {0}".format(args))
raise ValueError("I do not have all parameters: how is that possible ?")
for param_name, param_value in zip(self.params_name, args):
if param_value is NeededParameter:
raise TypeError("{0}: Missing Mandatory parameter <{1}>".format(self.winproxy_function.__name__, param_name))
if self.winproxy_function not in thread_state.raw_call_per_function:
force_resolution()
return thread_state.raw_call_per_function[self.winproxy_function](*args)
setattr(python_proxy, "ctypes_function", perform_call)
setattr(python_proxy, "force_resolution", force_resolution)
return python_proxy
def ntquerysysteminformation_syswow64_error_check(result, func, args):
args = thread_state.current_original_args
if result == 0:
return args
# Ignore STATUS_INFO_LENGTH_MISMATCH if SystemInformation is None
if result == STATUS_INFO_LENGTH_MISMATCH and not args[1]:
return args
raise winproxy.WinproxyError("NtQuerySystemInformation failed with NTStatus {0}".format(hex(result)))
@Syswow64ApiProxy(winproxy.NtQuerySystemInformation, errcheck=ntquerysysteminformation_syswow64_error_check)
# @Syswow64ApiProxy(winproxy.NtQuerySystemInformation)
def NtQuerySystemInformation_32_to_64(SystemInformationClass, SystemInformation=None, SystemInformationLength=0, ReturnLength=NeededParameter):
if SystemInformation is not None and SystemInformationLength == 0:
SystemInformationLength = ctypes.sizeof(SystemInformation)
if SystemInformation is None:
SystemInformation = 0
return NtQuerySystemInformation_32_to_64.ctypes_function(SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength)
@Syswow64ApiProxy(winproxy.NtCreateThreadEx)
def NtCreateThreadEx_32_to_64(ThreadHandle=None, DesiredAccess=0x1fffff, ObjectAttributes=0, ProcessHandle=NeededParameter, lpStartAddress=NeededParameter, lpParameter=NeededParameter, CreateSuspended=0, dwStackSize=0, Unknown1=0, Unknown2=0, Unknown3=0):
if ThreadHandle is None:
ThreadHandle = byref(HANDLE())
return NtCreateThreadEx_32_to_64.ctypes_function(ThreadHandle, DesiredAccess, ObjectAttributes, ProcessHandle, lpStartAddress, lpParameter, CreateSuspended, dwStackSize, Unknown1, Unknown2, Unknown3)
ProcessBasicInformation = 0
@Syswow64ApiProxy(winproxy.NtQueryInformationProcess)
def NtQueryInformationProcess_32_to_64(ProcessHandle, ProcessInformationClass=ProcessBasicInformation, ProcessInformation=NeededParameter, ProcessInformationLength=0, ReturnLength=None):
if ProcessInformation is not None and ProcessInformationLength == 0:
ProcessInformationLength = ctypes.sizeof(ProcessInformation)
if type(ProcessInformation) == PROCESS_BASIC_INFORMATION:
ProcessInformation = byref(ProcessInformation)
if ReturnLength is None:
ReturnLength = byref(ULONG())
return NtQueryInformationProcess_32_to_64.ctypes_function(ProcessHandle, ProcessInformationClass, ProcessInformation, ProcessInformationLength, ReturnLength)
@Syswow64ApiProxy(winproxy.NtQueryInformationThread)
def NtQueryInformationThread_32_to_64(ThreadHandle, ThreadInformationClass, ThreadInformation, ThreadInformationLength=0, ReturnLength=None):
if ReturnLength is None:
ReturnLength = byref(ULONG())
if ThreadInformation is not None and ThreadInformationLength == 0:
ThreadInformationLength = ctypes.sizeof(ThreadInformation)
return NtQueryInformationThread_32_to_64.ctypes_function(ThreadHandle, ThreadInformationClass, ThreadInformation, ThreadInformationLength, ReturnLength)
@Syswow64ApiProxy(winproxy.NtQueryVirtualMemory)
def NtQueryVirtualMemory_32_to_64(ProcessHandle, BaseAddress, MemoryInformationClass, MemoryInformation=NeededParameter, MemoryInformationLength=0, ReturnLength=None):
if ReturnLength is None:
ReturnLength = byref(ULONG())
if MemoryInformation is not None and MemoryInformationLength == 0:
MemoryInformationLength = ctypes.sizeof(MemoryInformation)
if isinstance(MemoryInformation, ctypes.Structure):
MemoryInformation = byref(MemoryInformation)
return NtQueryVirtualMemory_32_to_64.ctypes_function(ProcessHandle, BaseAddress, MemoryInformationClass, MemoryInformation, MemoryInformationLength, ReturnLength)
@Syswow64ApiProxy(winproxy.NtProtectVirtualMemory)
def NtProtectVirtualMemory_32_to_64(ProcessHandle, BaseAddress, NumberOfBytesToProtect, NewAccessProtection, OldAccessProtection=None):
if OldAccessProtection is None:
XOldAccessProtection = DWORD()
OldAccessProtection = ctypes.addressof(XOldAccessProtection)
return NtProtectVirtualMemory_32_to_64.ctypes_function(ProcessHandle, BaseAddress, NumberOfBytesToProtect, NewAccessProtection, OldAccessProtection)
@Syswow64ApiProxy(winproxy.NtGetContextThread)
def NtGetContextThread_32_to_64(hThread, lpContext):
if type(lpContext) == windows.winobject.exception.ECONTEXT64:
lpContext = byref(lpContext)
return NtGetContextThread_32_to_64.ctypes_function(hThread, lpContext)
@Syswow64ApiProxy(winproxy.LdrLoadDll)
def LdrLoadDll_32_to_64(PathToFile, Flags, ModuleFileName, ModuleHandle):
return LdrLoadDll_32_to_64.ctypes_function(PathToFile, Flags, ModuleFileName, ModuleHandle)
@Syswow64ApiProxy(winproxy.NtSetContextThread)
def NtSetContextThread_32_to_64(hThread, lpContext):
return NtSetContextThread_32_to_64.ctypes_function(hThread, lpContext)
-25
View File
@@ -1,25 +0,0 @@
import windows
import windows.generated_def as gdef
from windows.utils import create_process, DisableWow64FsRedirection
test_binary_name = "notepad.exe"
DEFAULT_CREATION_FLAGS = gdef.CREATE_NEW_CONSOLE
if windows.system.bitness == 32:
def pop_proc_32(dwCreationFlags=DEFAULT_CREATION_FLAGS):
return create_process(r"C:\Windows\system32\{0}".format(test_binary_name).encode(), dwCreationFlags=dwCreationFlags, show_windows=True)
def pop_proc_64(dwCreationFlags=DEFAULT_CREATION_FLAGS):
raise WindowsError("Cannot create calc64 in 32bits system")
else:
def pop_proc_32(dwCreationFlags=DEFAULT_CREATION_FLAGS):
return create_process(r"C:\Windows\syswow64\{0}".format(test_binary_name).encode(), dwCreationFlags=dwCreationFlags, show_windows=True)
if windows.current_process.bitness == 32:
def pop_proc_64(dwCreationFlags=DEFAULT_CREATION_FLAGS):
with DisableWow64FsRedirection():
return create_process(r"C:\Windows\system32\{0}".format(test_binary_name).encode(), dwCreationFlags=dwCreationFlags, show_windows=True)
else:
def pop_proc_64(dwCreationFlags=DEFAULT_CREATION_FLAGS):
return create_process(r"C:\Windows\system32\{0}".format(test_binary_name).encode(), dwCreationFlags=dwCreationFlags, show_windows=True)
@@ -1,3 +0,0 @@
from .pythonutils import *
from .winutils import *
from .improved_buffer import *
@@ -1,134 +0,0 @@
import sys
import ctypes
import _ctypes
import windows.generated_def as gdef
from windows.pycompat import basestring
## TESTING Improved Buffer code ###
## This code is not stable and WILL CHANGE ##
## Do not use for now :) ##
# Uses cases:
# Simple buffer
# String / Wstring / Bytes
# Resize array in struct
# Typed buffer : filed call & contains X struct S
# Having a ptr on struct with a buffer > sizeof(struct)
# Autocreate a good-typed buffer from a tuple of ctypes objects
# On peut vouloir creer un buffer avec 12 elts de type X
# Ou creer un buffer avec 12 elt de type X mais une sub-size de 1000
if sys.version_info.major >= 3:
long = int
class ImprovedCtypesBufferBase(object):
def cast(self, type):
return ctypes.cast(self, type)
def as_string(self):
return ctypes.cast(self, gdef.LPCSTR).value
def as_wstring(self):
return ctypes.cast(self, gdef.LPWSTR).value
def as_pvoid(self):
return self.cast(gdef.PVOID)
# Constructor
@classmethod
def from_size(cls, size):
raw_buffer = ctypes.c_buffer(size)
buffer = cls.from_buffer(raw_buffer)
buffer._raw_buffer_ = raw_buffer
return buffer
@property
def real_size(self):
real_buffer = getattr(self, "_raw_buffer_", self)
return ctypes.sizeof(real_buffer)
def __new__(cls, *args, **kwargs):
if "size" in kwargs:
buff = ctypes.create_string_buffer(kwargs["size"])
self = cls.from_buffer(buff)
self._raw_buffer_ = buff
return self
# Add a '_raw_buffer_' even when no explicit size ?
return super(ImprovedCtypesBufferBase, cls).__new__(cls, *args, **kwargs)
# Used in windows.crypto.sign_verify for test
class PartialBufferType(object):
def __init__(self, type, nbelt=None):
self.type = type
self.nbelt = None
@staticmethod
def create_real_implem(item_type, nbelt):
if isinstance(nbelt, long):
nbelt = int(nbelt)
assert isinstance(nbelt, int)
cls_name = "TypedBuffer<{0}><{1}>".format(item_type.__name__, nbelt)
class TmpImplemArrayName(ImprovedCtypesBufferBase, ctypes.Array):
_type_ = item_type
_length_ = nbelt
TmpImplemArrayName.__name__ = cls_name
return TmpImplemArrayName
def from_buffer(self, buffer): # size as kwargs ?
if len(buffer) % ctypes.sizeof(self.type):
raise NotImplementedError("Buffer size of not a multiple of sizeof({0})".format(self.type.__name__))
nbelt = int(len(buffer) / ctypes.sizeof(self.type))
return self.create_real_implem(self.type, nbelt).from_buffer(buffer)
def from_buffer_copy(self, buffer): # size as kwargs ?
if len(buffer) % ctypes.sizeof(self.type):
raise NotImplementedError("Buffer size of not a multiple of sizeof({0})".format(self.type.__name__))
nbelt = int(len(buffer) / ctypes.sizeof(self.type))
return self.create_real_implem(self.type, nbelt).from_buffer_copy(buffer)
def create(self, nbelt):
return self.create_real_implem(self.type, nbelt)
def __mul__(self, nbelt):
return self.create_real_implem(self.type, nbelt)
def __call__(self, *args, **kwargs):
if len(args) == 1: # String magic: explode string as arg
if isinstance(args[0], basestring):
args = args[0]
nbelt = kwargs.get("nbelt", max(len(args), 1))
return self.create_real_implem(self.type, nbelt)(*args, **kwargs)
# # Expose these predefined types ?
# CharBuffer = PartialBufferType(gdef.CHAR)
# WCharBuffer = PartialBufferType(gdef.WCHAR)
# ByteBuffer = PartialBufferType(gdef.BYTE)
def BUFFER(type, nbelt=None):
if nbelt is None:
return PartialBufferType(type) # Allow user to create custom sized buffer
return PartialBufferType.create_real_implem(type, int(nbelt))
def buffer(obj, eltclass=None):
if eltclass is None: # Guess
obj = list(obj)
item = obj[0]
eltclass = type(item) # All object must have the same type
dlen = len(obj)
return BUFFER(eltclass, dlen)(*obj)
def resized_array(array, newnbelt, newtype=None):
if newtype is None:
newtype = array._type_
btype = BUFFER(newtype, newnbelt)
new_array = btype.from_address(ctypes.addressof(array))
new_array._base_array_ = array # Keep a ref to prevent some gc
return new_array
@@ -1,115 +0,0 @@
"""utils fonctions non windows-related"""
import sys
import ctypes
import _ctypes
import windows.generated_def as gdef
from windows import winproxy
from windows.dbgprint import dbgprint
from windows.pycompat import basestring
def fixedpropety(f):
cache_name = "_" + f.__name__
def prop(self):
try:
return getattr(self, cache_name)
except AttributeError:
setattr(self, cache_name, f(self))
return getattr(self, cache_name)
return property(prop, doc=f.__doc__)
# Slow fix of the typo :)
fixedproperty = fixedpropety
# type replacement based on name
def transform_ctypes_fields(struct, replacement):
return [(name, replacement.get(name, type)) for name, type in struct._fields_]
def print_ctypes_struct(struct, name="", hexa=False):
sprint_method = getattr(struct, "__sprint__", None)
if sprint_method is not None:
# Allow function to accept 'hexa' param
# But handle function that don't, So we can just do:
# __sprint__ = __repr__
print("{0} -> {1}".format(name, sprint_method()))
return
if isinstance(struct, _ctypes._Pointer):
if ctypes.cast(struct, ctypes.c_void_p).value is None:
print("{0} -> NULL".format(name))
return
return print_ctypes_struct(struct[0], name + "<deref>", hexa=hexa)
if not hasattr(struct, "_fields_"):
value = struct
if hasattr(struct, "value"):
value = struct.value
if isinstance(value, basestring):
value = repr(value)
if hexa and not isinstance(value, gdef.Flag):
try:
print("{0} -> {1}".format(name, hex(value)))
return
except TypeError:
pass
print("{0} -> {1}".format(name, value))
return
for field in struct._fields_:
if len(field) == 2:
fname, ftype = field
nb_bits = None
elif len(field) == 3:
fname, ftype, nb_bits = field
else:
raise ValueError("Unknown ctypes field entry format <{0}>".format(field))
try:
value = getattr(struct, fname)
except Exception as e:
print("Error while printing <{0}> : {1}".format(fname, e))
continue
print_ctypes_struct(value, "{0}.{1}".format(name, fname), hexa=hexa)
def sprint(struct, name="struct", hexa=True):
"""Print recursively the content of a :mod:`ctypes` structure"""
return print_ctypes_struct(struct, name=name, hexa=hexa)
class AutoHandle(object):
"""An abstract class that allow easy handle creation/destruction/wait"""
# Big bypass to prevent missing reference at programm exit..
_close_function = ctypes.WinDLL("kernel32").CloseHandle
def _get_handle(self):
raise NotImplementedError("{0} is abstract".format(type(self).__name__))
@property
def handle(self):
"""An handle on the object
:type: HANDLE
.. note::
The handle is automaticaly closed when the object is destroyed
"""
if hasattr(self, "_handle"):
return self._handle
self._handle = self._get_handle()
dbgprint("Open handle {0} for {1}".format(hex(self._handle), self), "HANDLE")
return self._handle
def wait(self, timeout=gdef.INFINITE):
"""Wait for the object"""
return winproxy.WaitForSingleObject(self.handle, timeout)
def __del__(self):
# sys.path is not None -> check if python shutdown
if hasattr(sys, "path") and sys.path is not None and hasattr(self, "_handle") and self._handle:
# Prevent some bug where dbgprint might be None when __del__ is called in a closing process
dbgprint("Closing Handle {0} for {1}".format(hex(self._handle), self), "HANDLE") if dbgprint is not None else None
self._close_function(self._handle)
@@ -1,627 +0,0 @@
import ctypes
import msvcrt
import os
import sys
import code
import math
import datetime
import warnings
from collections import namedtuple
import windows
from windows.dbgprint import dbgprint
import windows.generated_def as gdef
from .. import winproxy
from ..generated_def.winstructs import *
# Function resolution !
# should be in winproxy ?
def get_func_addr(dll_name, func_name):
# Load the DLL
ctypes.WinDLL(dll_name)
modules = windows.current_process.peb.modules
if not dll_name.lower().endswith(".dll"):
dll_name += ".dll"
mod = [x for x in modules if x.name == dll_name][0]
return mod.pe.exports[func_name]
def get_remote_func_addr(target, dll_name, func_name):
name_modules = [m for m in target.peb.modules if m.name == dll_name]
if not len(name_modules):
raise ValueError("Module <{0}> not loaded in target <{1}>".format(dll_name, target))
mod = name_modules[0]
return mod.pe.exports[func_name]
def is_wow_64(hProcess):
try:
fnIsWow64Process = get_func_addr("kernel32.dll", "IsWow64Process")
except winproxy.WinproxyError:
return False
IsWow64Process = ctypes.WINFUNCTYPE(BOOL, HANDLE, ctypes.POINTER(BOOL))(fnIsWow64Process)
Wow64Process = BOOL()
res = IsWow64Process(hProcess, ctypes.byref(Wow64Process))
if res:
return bool(Wow64Process)
raise ctypes.WinError()
def create_file_from_handle(handle, mode="r"):
"""Return a Python :class:`file` around a ``Windows`` HANDLE"""
flags = os.O_BINARY if "b" in mode else os.O_TEXT
fd = msvcrt.open_osfhandle(handle, flags)
kwargs = {}
if windows.pycompat.is_py3 and flags == os.O_TEXT:
# Buffering, encoding
args = (100, "ascii")
else:
# Buffering
args = (0,)
# In py2 os.fdopen do not accept kwargs
return os.fdopen(fd, mode, *args)
def get_handle_from_file(f):
"""Get the ``Windows`` HANDLE of a python :class:`file`"""
return msvcrt.get_osfhandle(f.fileno())
def create_console():
"""Create a new console displaying STDOUT.
Useful in injection of GUI process"""
winproxy.AllocConsole()
stdout_handle = winproxy.GetStdHandle(gdef.STD_OUTPUT_HANDLE)
console_stdout = create_file_from_handle(stdout_handle, "w")
sys.stdout = console_stdout
stdin_handle = winproxy.GetStdHandle(gdef.STD_INPUT_HANDLE)
console_stdin = create_file_from_handle(stdin_handle, "r")
sys.stdin = console_stdin
stderr_handle = winproxy.GetStdHandle(gdef.STD_ERROR_HANDLE)
console_stderr = create_file_from_handle(stderr_handle, "w")
sys.stderr = console_stderr
def create_process(path, args=None, dwCreationFlags=0, show_windows=True):
"""A convenient wrapper arround :func:`windows.winproxy.CreateProcessA`"""
proc_info = PROCESS_INFORMATION()
lpStartupInfo = None
if show_windows:
StartupInfo = STARTUPINFOW()
StartupInfo.cb = ctypes.sizeof(StartupInfo)
StartupInfo.dwFlags = 0
lpStartupInfo = ctypes.byref(StartupInfo)
lpCommandLine = None
if isinstance(path, bytes):
path = path.decode()
if args:
unicode_args = []
for arg in args:
if isinstance(arg, bytes):
arg = arg.decode()
unicode_args.append(arg)
lpCommandLine = (" ".join(unicode_args))
windows.winproxy.CreateProcessW(path, lpCommandLine=lpCommandLine, dwCreationFlags=dwCreationFlags, lpProcessInformation=ctypes.byref(proc_info), lpStartupInfo=lpStartupInfo)
dbgprint("CreateProcessW new process handle {:#x}".format(proc_info.hProcess), "HANDLE")
dbgprint("CreateProcessW new thread handle {:#x}".format(proc_info.hThread), "HANDLE")
dbgprint("Automatic close of thread handle {:#x}".format(proc_info.hThread), "HANDLE")
windows.winproxy.CloseHandle(proc_info.hThread) # Give access to a WinThread in addition of the WinProcess ?
return windows.winobject.process.WinProcess(pid=proc_info.dwProcessId, handle=proc_info.hProcess)
def device_io_control(handle, iocode, buffer):
outbuffer = ctypes.c_buffer(0x1000)
returned_size = gdef.DWORD()
windows.winproxy.DeviceIoControl(handle, iocode, buffer, lpOutBuffer=outbuffer, lpBytesReturned=returned_size)
return outbuffer[:returned_size.value]
def tmp_cp_as(path, token):
proc_info = PROCESS_INFORMATION()
windows.winproxy.CreateProcessAsUserA(token, path, lpCommandLine=None, dwCreationFlags=gdef.CREATE_NEW_CONSOLE, lpProcessInformation=ctypes.byref(proc_info), lpStartupInfo=None)
return windows.winobject.process.WinProcess(pid=proc_info.dwProcessId, handle=proc_info.hProcess)
def find_handle(proc, value):
return [h for h in windows.system.handles if h.dwProcessId == proc.pid and h.wValue == value]
def lookup_privilege_value(privilege_name):
luid = LUID()
winproxy.LookupPrivilegeValueA(None, privilege_name, byref(luid))
return luid
def lookup_privilege_name(privilege_value):
if isinstance(privilege_value, tuple):
luid = LUID(privilege_value[1], privilege_value[0])
privilege_value = luid
size = DWORD(0x100)
buff = ctypes.c_buffer(size.value)
winproxy.LookupPrivilegeNameA(None, privilege_value, buff, size)
return buff[:size.value]
def lookup_sid(psid):
"""Retrieves the name of the Computer/Domain and the name of the Account for a given SID
:returns: (:class:`unicode`, :class:`unicode`) - A tuple of two unicode strings
"""
usernamesize = gdef.DWORD(0x1000)
computernamesize = gdef.DWORD(0x1000)
username = ctypes.create_unicode_buffer(usernamesize.value)
computername = ctypes.create_unicode_buffer(computernamesize.value)
peUse = gdef.SID_NAME_USE()
winproxy.LookupAccountSidW(None, psid, username, usernamesize, computername, computernamesize, peUse)
return computername[:computernamesize.value], username[:usernamesize.value]
def enable_privilege(lpszPrivilege, bEnablePrivilege):
"""
Enable or disable a privilege::
enable_privilege(SE_DEBUG_NAME, True)
"""
tp = TOKEN_PRIVILEGES()
luid = LUID()
hToken = HANDLE()
winproxy.OpenProcessToken(winproxy.GetCurrentProcess(), TOKEN_ALL_ACCESS, byref(hToken))
winproxy.LookupPrivilegeValueA(None, lpszPrivilege, byref(luid))
tp.PrivilegeCount = 1
tp.Privileges[0].Luid = luid
if bEnablePrivilege:
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED
else:
tp.Privileges[0].Attributes = 0
winproxy.AdjustTokenPrivileges(hToken, False, byref(tp), sizeof(TOKEN_PRIVILEGES))
winproxy.CloseHandle(hToken)
if winproxy.GetLastError() == gdef.ERROR_NOT_ALL_ASSIGNED:
raise ValueError("Failed to get privilege {0}".format(lpszPrivilege))
return True
def check_is_elevated():
"""Return ``True`` if process is Admin"""
hToken = HANDLE()
elevation = TOKEN_ELEVATION()
cbsize = DWORD()
winproxy.OpenProcessToken(winproxy.GetCurrentProcess(), TOKEN_ALL_ACCESS, byref(hToken))
winproxy.GetTokenInformation(hToken, TokenElevation, byref(elevation), sizeof(elevation), byref(cbsize))
winproxy.CloseHandle(hToken)
return elevation.TokenIsElevated
def check_debug():
"""Check that kernel is in debug mode (beware of NOUMEX):
https://msdn.microsoft.com/en-us/library/windows/hardware/ff556253(v=vs.85).aspx#_______noumex______
"""
options = windows.system.registry(r'HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control')['SystemStartOptions']
control = options.value
if "DEBUG" not in control:
# print "[-] Enable debug boot!"
# print "> bcdedit /debug on"
return False
if "DEBUG=NOUMEX" not in control:
pass
# print "[*] Warning noumex not set!"
# print "> bcdedit /set noumex on"
return True
UNIX_EPOCH = datetime.datetime(1970, 1, 1, 0, 0)
WINDOWS_EPOCH = datetime.datetime(1601, 1, 1, 0, 0)
# https://docs.microsoft.com/en-us/cpp/atl-mfc-shared/date-type?view=vs-2019
# Why keep it simple and have only one epoch ? :D
# I don't want to name this "DATE_EPOCH" as everything is a DATE
# So let's go with COMDATE as this structure seems very related to COM/AUTOMATION
COMDATE_EPOCH = datetime.datetime(1899, 12, 30, 0, 0)
WIN_TO_UNIX_EPOCH_SECOND = int((UNIX_EPOCH - WINDOWS_EPOCH).total_seconds())
WIN_TICK_PER_SECOND_INT = 10**7
WIN_TICK_PER_SECOND_FLOAT = 10.0**7
WIN_TO_UNIX_EPOCH_WIN_TICKS = WIN_TO_UNIX_EPOCH_SECOND * WIN_TICK_PER_SECOND_INT
# TODO: look in python stblib how filetime -> unix timestamp translation is down (os.stat code ?)
def unix_timestamp_from_filetime(filetime):
# Round the filetime
last_number = (filetime % 10)
# We do some sort of "manual rounding cause of py2 vs py3
# PY2: round(0.5) == 1
# PY3: round(0.5) == 0
if last_number == 5:
rounding = 1
else:
rounding = round(last_number / 10.0)
round_win_ticks = ((filetime // 10) + int(rounding)) * 10
return round((round_win_ticks - WIN_TO_UNIX_EPOCH_WIN_TICKS) / WIN_TICK_PER_SECOND_FLOAT, 7)
def datetime_from_filetime(filetime):
"""return a :class:`datetime.datetime` from a ``windows`` FILETIME int"""
# Manual non-approx rounding as filetime will not have a perfect representation as Python float
# We do some sort of "manual rounding cause of py2 vs py3
# PY2: round(0.5) == 1
# PY3: round(0.5) == 0
last_number = (filetime % 10)
if last_number == 5:
rounding = 1
else:
rounding = round(last_number / 10.0)
round_microsecond = (filetime // 10) + int(rounding)
return WINDOWS_EPOCH + datetime.timedelta(microseconds=round_microsecond)
def filetime_from_datetime(dtime):
"""Return the FILETIME value from a :class:`datetime.datetime` in a python :class:`int`"""
return int((dtime - WINDOWS_EPOCH).total_seconds()) * WIN_TICK_PER_SECOND_INT
def datetime_from_comdate(comtime):
# Hour values are expressed as the absolute value of the fractional part of the number.
if comtime < 0:
# The date timeline becomes discontinuous for date values less than 0 (before 30 December 1899). This is because the whole-number portion of the date value is treated as signed, while the fractional part is treated as unsigned.
# other words, the whole-number part of the date value may be positive or negative, while the fractional part of the date value is always added to the overall logical date.
# WTF :D
dec, nb = math.modf(comtime)
final_delta = nb + abs(dec)
return COMDATE_EPOCH + datetime.timedelta(final_delta)
return COMDATE_EPOCH + datetime.timedelta(comtime)
def datetime_from_systemtime(systime):
return datetime.datetime(
year=systime.wYear,
month=systime.wMonth,
day=systime.wDay,
hour=systime.wHour,
minute=systime.wMinute,
second=systime.wSecond,
microsecond=systime.wMilliseconds * 1000,
)
class FixedInteractiveConsole(code.InteractiveConsole):
def raw_input(self, prompt=">>>"):
sys.stdout.write(prompt)
return raw_input("")
def pop_shell(locs=None):
"""Pop a console with an InterativeConsole"""
if locs is None:
locs = globals()
create_console()
FixedInteractiveConsole(locs).interact()
def get_kernel_modules():
warnings.warn("get_kernel_modules() will be removed: use windows.system.modules instead", DeprecationWarning)
return windows.system.modules
class FileStreamInformation(gdef.FILE_STREAM_INFORMATION):
@property
def name(self):
return gdef.LPWSTR(ctypes.addressof(self) + type(self).StreamName.offset).value
@property
def next(self):
if not self.NextEntryOffset:
return None
return type(self).from_address(ctypes.addressof(self) + self.NextEntryOffset)
def all(self):
return list(self)
def __iter__(self):
while self:
yield self
self = self.next
def __repr__(self):
return "<ADS name='{0}'>".format(self.name)
ntqueryinformationfile_info_structs = {
gdef.FileAccessInformation: gdef.FILE_ACCESS_INFORMATION,
gdef.FileAlignmentInformation: gdef.FILE_ALIGNMENT_INFORMATION,
gdef.FileAllInformation: gdef.FILE_ALL_INFORMATION,
gdef.FileAttributeTagInformation: gdef.FILE_ATTRIBUTE_TAG_INFORMATION,
gdef.FileBasicInformation: gdef.FILE_BASIC_INFORMATION,
gdef.FileEaInformation: gdef.FILE_EA_INFORMATION ,
gdef.FileInternalInformation: gdef.FILE_INTERNAL_INFORMATION,
gdef.FileIoPriorityHintInformation: gdef.FILE_IO_PRIORITY_HINT_INFORMATION,
gdef.FileModeInformation: gdef.FILE_MODE_INFORMATION,
gdef.FileNetworkOpenInformation: gdef.FILE_NETWORK_OPEN_INFORMATION,
gdef.FileNameInformation: gdef.FILE_NAME_INFORMATION,
gdef.FilePositionInformation: gdef.FILE_POSITION_INFORMATION,
gdef.FileStandardInformation: gdef.FILE_STANDARD_INFORMATION,
gdef.FileIsRemoteDeviceInformation: gdef.FILE_IS_REMOTE_DEVICE_INFORMATION,
gdef.FileStreamInformation: FileStreamInformation,
}
def query_file_information(file_or_handle, file_info_class):
if not isinstance(file_or_handle, windows.pycompat.int_types):
file_or_handle = windows.utils.get_handle_from_file(file_or_handle)
handle = file_or_handle
io_status = gdef.IO_STATUS_BLOCK()
info = ntqueryinformationfile_info_structs[file_info_class]()
# Do helper for 'is_pointer' / get pointed_size & co ? (useful for winproxy)
pinfo = ctypes.pointer(info)
try:
windows.winproxy.NtQueryInformationFile(handle, io_status, pinfo, ctypes.sizeof(info), FileInformationClass=file_info_class)
except Exception as e:
if not (e.winerror & 0xffffffff) == gdef.STATUS_BUFFER_OVERFLOW:
raise
# STATUS_BUFFER_OVERFLOW -> Guess we have a FILE_NAME_INFORMATION somewhere that need a bigger buffer
if file_info_class == gdef.FileNameInformation:
file_name_length = pinfo[0].FileNameLength
elif file_info_class == gdef.FileAllInformation:
file_name_length = pinfo[0].NameInformation.FileNameLength
elif file_info_class == gdef.FileStreamInformation:
file_name_length = 0x10000
else:
raise
full_size = ctypes.sizeof(info) + file_name_length # We add a little too much size for the sake of simplicity
buffer = ctypes.c_buffer(full_size)
windows.winproxy.NtQueryInformationFile(handle, io_status, buffer, full_size, FileInformationClass=file_info_class)
pinfo = ctypes.cast(buffer, ctypes.POINTER(ntqueryinformationfile_info_structs[file_info_class]))
info = pinfo[0]
# return list of ADS if FileStreamInformation ?
return info
class EAInfo(gdef.FILE_FULL_EA_INFORMATION):
@property
def name(self):
return gdef.LPCSTR(ctypes.addressof(self) + type(self).EaName.offset).value
@property
def value(self):
value_addr = ctypes.addressof(self) + type(self).EaName.offset + self.EaNameLength + 1 # +1 -> Name \x00
return (ctypes.c_char * self.EaValueLength).from_address(value_addr)[:]
@property
def next(self):
# NextEntryOffset is Relative to our current offset
if not self.NextEntryOffset:
return None
try: # First entry
raw_buffer = self._b_base_._raw_buffer_
except AttributeError as e:
raw_buffer = self._raw_buffer_
curoffset = getattr(self, "_raw_buffer_offset_", 0)
new = type(self).from_buffer(raw_buffer, curoffset + self.NextEntryOffset)
# Keep the underlying buffer easily accessible
new._raw_buffer_ = raw_buffer
new._raw_buffer_offset_ = curoffset + self.NextEntryOffset
return new
def __iter__(self):
while self:
yield self
self = self.next
def __repr__(self):
return '<{0} name="{1}">'.format(type(self).__name__, self.name)
MAXIMUM_EA_SIZE = 0x0000ffff
def query_extended_attributes(file_or_handle):
if isinstance(file_or_handle, file):
file_or_handle = windows.utils.get_handle_from_file(file_or_handle)
# Check EaSize
x = windows.utils.query_file_information(file_or_handle, gdef.FileEaInformation)
if not x.EaSize:
return
io_status = gdef.IO_STATUS_BLOCK()
# Handle Win10 / Win7
# Saw on Win10 -> EaSize > MAXIMUM_EA_SIZE
# Saw on Win7 -> EaSize not enought (STATUS_BUFFER_OVERFLOW)
buffsize = max(MAXIMUM_EA_SIZE, x.EaSize)
buffer = windows.utils.BUFFER(EAInfo)(size=buffsize)
windows.winproxy.NtQueryEaFile(file_or_handle, io_status, buffer, buffsize, False, None, 0, None, True)
return buffer[0]
ntqueryvolumeinformationfile_info_structs = {
gdef.FileFsAttributeInformation: gdef.FILE_FS_ATTRIBUTE_INFORMATION,
gdef.FileFsControlInformation: gdef.FILE_FS_CONTROL_INFORMATION,
gdef.FileFsDeviceInformation: gdef.FILE_FS_DEVICE_INFORMATION,
gdef.FileFsDriverPathInformation: gdef.FILE_FS_DRIVER_PATH_INFORMATION,
gdef.FileFsFullSizeInformation: gdef.FILE_FS_FULL_SIZE_INFORMATION,
gdef.FileFsObjectIdInformation: gdef.FILE_FS_OBJECTID_INFORMATION,
gdef.FileFsSizeInformation: gdef.FILE_FS_SIZE_INFORMATION,
gdef.FileFsVolumeInformation: gdef.FILE_FS_VOLUME_INFORMATION,
gdef.FileFsSectorSizeInformation: gdef.FILE_FS_SECTOR_SIZE_INFORMATION,
}
# TODO: FileFsDriverPathInformation
# TODO: Extended FILE_FS_VOLUME_INFORMATION that can read the real value of 'VolumeLabel'
def query_volume_information(file_or_handle, volume_info_class):
if not isinstance(file_or_handle, windows.pycompat.int_types):
file_or_handle = get_handle_from_file(file_or_handle)
handle = file_or_handle
io_status = gdef.IO_STATUS_BLOCK()
info = ntqueryvolumeinformationfile_info_structs[volume_info_class]()
# Do helper for 'is_pointer' / get pointed_size & co ? (useful for winproxy)
pinfo = ctypes.pointer(info)
try:
windows.winproxy.NtQueryVolumeInformationFile(handle, io_status, pinfo, ctypes.sizeof(info), FsInformationClass=volume_info_class)
except WindowsError as e:
# import pdb;pdb.set_trace()
if not (e.winerror & 0xffffffff) == gdef.STATUS_BUFFER_OVERFLOW:
raise
if volume_info_class == gdef.FileFsAttributeInformation:
file_name_length = pinfo[0].FileSystemNameLength
elif volume_info_class == gdef.FileFsVolumeInformation:
# Well VolumeLabelLength is clearly broken (after testing..) so we are adding some bytes to it..
file_name_length = pinfo[0].VolumeLabelLength + 0x100 # I have seen cases where the VolumeLabelLength is not even enough..
else:
raise
full_size = ctypes.sizeof(info) + file_name_length # We add a little too much size for the sake of simplicity
buffer = ctypes.c_buffer(full_size)
windows.winproxy.NtQueryVolumeInformationFile(handle, io_status, buffer, full_size, FsInformationClass=volume_info_class)
pinfo = ctypes.cast(buffer, ctypes.POINTER(ntqueryvolumeinformationfile_info_structs[volume_info_class]))
info = pinfo[0]
return info
return info
# String stuff
def ntstatus(code):
return windows.generated_def.ntstatus.NtStatusException(code)
_WINERROR_BY_VALUE = None
def winerror(code):
global _WINERROR_BY_VALUE
if not _WINERROR_BY_VALUE: # Lazy init
_WINERROR_BY_VALUE = gdef.FlagMapper(*(getattr(gdef, error) for error in gdef.meta.errors))
val = _WINERROR_BY_VALUE[code]
if val is code: # Not found
val = _WINERROR_BY_VALUE[code & 0xffff] # Hresult: extract code (https://en.wikipedia.org/wiki/HRESULT)
return val
def get_long_path(path):
"""Return the long path form for ``path``.
:raise: :class:`~windows.winproxy.WinproxyError` if ``path`` does not exists
:param path: a valid Windows path
:type path: :class:`str` | :obj:`unicode`
:returns: :class:`str` | :obj:`unicode` -- same type as ``path`` parameter
"""
size = 0x1000
buffer = ctypes.create_unicode_buffer(size)
rsize = winproxy.GetLongPathNameW(path, buffer, size)
return buffer[:rsize]
def get_short_path(path):
"""Return the short path form for ``path``
:raise: :class:`~windows.winproxy.WinproxyError` if ``path`` does not exists
:param path: a valid Windows path
:type path: :class:`str` | :obj:`unicode`
:returns: :class:`str` | :obj:`unicode` -- same type as ``path`` parameter
"""
size = 0x1000
buffer = ctypes.create_unicode_buffer(size)
rsize = winproxy.GetShortPathNameW(path, buffer, size)
return buffer[:rsize]
def dospath_to_ntpath(dospath):
ustring = gdef.UNICODE_STRING()
windows.winproxy.RtlDosPathNameToNtPathName_U(dospath, ustring, None, None)
return ustring.str
def get_shared_mapping(name=None, handle=INVALID_HANDLE_VALUE, size=0x1000):
# TODO: real code
h = windows.winproxy.CreateFileMappingA(handle, dwMaximumSizeLow=size, lpName=name)
addr = windows.winproxy.MapViewOfFile(h, dwNumberOfBytesToMap=size)
return addr
def create_file(name, access=gdef.GENERIC_READ, share=gdef.FILE_SHARE_READ, security=None, creation=gdef.OPEN_EXISTING, flags=gdef.FILE_ATTRIBUTE_NORMAL):
return windows.winproxy.CreateFileA(name, access, share, security, creation, flags, 0)
#def mapfile(file):
# fhandle = get_handle_from_file(file)
# h = windows.winproxy.CreateFileMappingA(fhandle, None, PAGE_READONLY, 0, 1, None)
# addr = windows.winproxy.MapViewOfFile(h, dwDesiredAccess=FILE_MAP_READ, dwNumberOfBytesToMap=1)
# return addr
def decompress_buffer(buffer, comptype=gdef.COMPRESSION_FORMAT_LZNT1, uncompress_size=None):
if uncompress_size is None:
uncompress_size = len(buffer) * 10
result_size = DWORD()
uncompressed = ctypes.c_buffer(uncompress_size)
windows.winproxy.RtlDecompressBuffer(comptype, uncompressed, uncompress_size, buffer, len(buffer), result_size)
return uncompressed[:result_size.value]
def compress_buffer(buffer, comptype=gdef.COMPRESSION_FORMAT_LZNT1):
uncompress_size = len(buffer)
CompressedBufferSize = uncompress_size + 0x1000
CompressedBuffer = ctypes.c_buffer(CompressedBufferSize)
chunk = 4096
final_size = gdef.DWORD()
work_space_size = gdef.ULONG()
ignore_data = gdef.ULONG()
windows.winproxy.RtlGetCompressionWorkSpaceSize(comptype, work_space_size, ignore_data)
work_space = ctypes.c_buffer(work_space_size.value)
windows.winproxy.RtlCompressBuffer(comptype, buffer, uncompress_size, CompressedBuffer, CompressedBufferSize, chunk, final_size, work_space)
return CompressedBuffer[:final_size.value]
# sid.py + real SID type ?
def get_known_sid(sid_type):
size = DWORD()
try:
windows.winproxy.CreateWellKnownSid(sid_type, None, None, size)
except WindowsError:
pass
buffer = ctypes.c_buffer(size.value)
windows.winproxy.CreateWellKnownSid(sid_type, None, buffer, size)
return ctypes.cast(buffer, PSID)
UnloadEventTraceInfo = namedtuple("UnloadEventTraceInfo", ["size", "nb_elt", "array_ptr"])
def get_unload_event_trace():
x = PULONG()
y = PULONG()
z = PVOID()
windows.winproxy.RtlGetUnloadEventTraceEx(x, y, z)
return UnloadEventTraceInfo(x[0], y[0], z.value)
class VirtualProtected(object):
"""
A context manager usable like `VirtualProtect` that will restore the old protection at exit ::
with utils.VirtualProtected(IATentry.addr, ctypes.sizeof(PVOID), gdef.PAGE_EXECUTE_READWRITE):
IATentry.value = 0x42424242
"""
def __init__(self, addr, size, new_protect):
if (addr % 0x1000):
addr = addr - addr % 0x1000
self.addr = addr
self.size = size
self.new_protect = new_protect
def __enter__(self):
self.old_protect = DWORD()
winproxy.VirtualProtect(self.addr, self.size, self.new_protect, ctypes.byref(self.old_protect))
return self
def __exit__(self, exc_type, exc_value, traceback):
winproxy.VirtualProtect(self.addr, self.size, self.old_protect.value, ctypes.byref(self.old_protect))
return False
class DisableWow64FsRedirection(object):
"""
A context manager that disable the SysWow64 Filesystem Redirection ::
if is_process_32_bits:
def pop_calc_64():
with windows.utils.DisableWow64FsRedirection():
return windows.utils.create_process(r"C:\Windows\system32\calc.exe", True)
"""
def __enter__(self):
if windows.current_process.bitness == 64 or windows.system.bitness == 32:
return self
self.OldValue = PVOID()
winproxy.Wow64DisableWow64FsRedirection(ctypes.byref(self.OldValue))
return self
def __exit__(self, exc_type, exc_value, traceback):
if windows.current_process.bitness == 64 or windows.system.bitness == 32:
return False
winproxy.Wow64RevertWow64FsRedirection(self.OldValue)
return False
@@ -1,181 +0,0 @@
import ctypes
import windows
import windows.generated_def as gdef
from windows import utils
def get_api_set_map_for_current_process(base):
base = windows.current_process.peb.ApiSetMap
version = windows.current_process.read_dword(base)
if version not in API_SET_MAP_BY_VERSION:
raise NotImplementedError("ApiSetMap version <{0}> not implemented, please contact me, I need a sample to implement it ;)")
return API_SET_MAP_BY_VERSION[version](base)
class ApiSetMap(object):
"""The base class for the ApiSeMap
(see `Runtime DLL name resolution: ApiSetSchema <https://blog.quarkslab.com/runtime-dll-name-resolution-apisetschema-part-ii.html>`_)
"""
version = None #: The version of the ApiSetMap
def __init__(self, base):
self.base = base
self.target = windows.current_process
# helpers
def read_apiset_wstring(self, offset, length):
return self.target.read_memory(self.base + offset, length).decode("utf-16")
# Low-level version-dependent parsing function
def entries_array(self):
raise NotImplementedError("Should be implemented by subclasses")
def get_entry_name(self, entry):
raise NotImplementedError("Should be implemented by subclasses")
def get_entry_name_basicimpl(self, entry):
return self.read_apiset_wstring(entry.NameOffset, entry.NameLength)
def values_for_entry(self, entry):
raise NotImplementedError("Should be implemented by subclasses")
@utils.fixedpropety
def apisetmap_dict(self):
"""The apisetmap dll-mapping content extracted from memory as a :class:`dict`
``key -> value example``::
u'ext-ms-win-advapi32-encryptedfile-l1-1-1' -> u'advapi32.dll'
"""
res = {}
for entry in self.entries_array():
values = self.values_for_entry(entry)
if not values:
final_value = None
else:
final_value = values[-1]
res[self.get_entry_name(entry)] = final_value
return res
@utils.fixedpropety
def resolution_dict(self):
"""The :class:`dict` based on :obj:`apisetmap_dict` with only the part checked by ``Windows``.
``Windows`` does not care about what is after the last ``-``
``key -> value example``::
u'ext-ms-win-advapi32-encryptedfile-l1-1-' -> u'advapi32.dll'
"""
res = {}
for name, resolved_name in self.apisetmap_dict.items():
# ApiSetResolveToHost does not care about last version + extension
# It remove everything after the last '-'
# Possible to have no '-' ?
try:
cutname = name[:name.rindex("-") + 1]
except ValueError as e:
cutname = name
res[cutname] = resolved_name
return res
def resolve(self, dllname):
"""The method used to resolve a DLL name using the ApiSetMap.
The behavior should match the non-exported function ``ntdll!ApiSetResolveToHost``
"""
try:
cutname = dllname[:dllname.rindex("-") + 1]
except ValueError as e:
return None
return self.resolution_dict[cutname]
class ApiSetMapVersion2(ApiSetMap):
"""Represent an ApiSetMap version-2"""
version = 2 #: The version of the ApiSetMap
def namespace(self):
return gdef.API_SET_NAMESPACE_ARRAY_V2.from_address(self.base)
def entries_array(self):
namespace = self.namespace()
array_addr = ctypes.addressof(namespace.Array)
array_size = namespace.Count
return (gdef.API_SET_NAMESPACE_ENTRY_V2 * array_size).from_address(array_addr)
get_entry_name = ApiSetMap.get_entry_name_basicimpl
def values_for_entry(self, entry):
values_array_v2 = (gdef.API_SET_VALUE_ARRAY_V2).from_address(self.base + entry.DataOffset)
array_size = values_array_v2.Count
array_addr = ctypes.addressof(values_array_v2.Array)
values_array = (gdef.API_SET_VALUE_ENTRY_V2 * array_size).from_address(array_addr)
res = []
for value in values_array:
if value.ValueLength:
v = self.read_apiset_wstring(value.ValueOffset, value.ValueLength)
res.append(v)
return res
class ApiSetMapVersion4(ApiSetMap):
"""Represent an ApiSetMap version-4"""
version = 4 #: The version of the ApiSetMap
def namespace(self):
return gdef.API_SET_NAMESPACE_ARRAY_V4.from_address(self.base)
def entries_array(self):
namespace = self.namespace()
array_addr = ctypes.addressof(namespace.Array)
array_size = namespace.Count
return (gdef.API_SET_NAMESPACE_ENTRY_V4 * array_size).from_address(array_addr)
get_entry_name = ApiSetMap.get_entry_name_basicimpl
def values_for_entry(self, entry):
values_array_v2 = (gdef.API_SET_VALUE_ARRAY_V4).from_address(self.base + entry.DataOffset)
array_size = values_array_v2.Count
array_addr = ctypes.addressof(values_array_v2.Array)
values_array = (gdef.API_SET_VALUE_ENTRY * array_size).from_address(array_addr)
res = []
for value in values_array:
if value.ValueLength:
v = self.read_apiset_wstring(value.ValueOffset, value.ValueLength)
res.append(v)
return res
class ApiSetMapVersion6(ApiSetMap):
"""Represent an ApiSetMap version-6"""
version = 6 #: The version of the ApiSetMap
def namespace(self):
return gdef.API_SET_NAMESPACE_V6.from_address(self.base)
get_entry_name = ApiSetMap.get_entry_name_basicimpl
def entries_array(self):
namespace = self.namespace()
array_offset = namespace.EntryOffset
array_size = namespace.Count
return (gdef.API_SET_NAMESPACE_ENTRY_V6 * array_size).from_address(self.base + array_offset)
def values_for_entry(self, entry):
values_array = (gdef.API_SET_VALUE_ENTRY * entry.ValueCount).from_address(self.base + entry.ValueOffset)
res = []
for value in values_array:
if value.ValueLength:
v = self.read_apiset_wstring(value.ValueOffset, value.ValueLength)
res.append(v)
return res
API_SET_MAP_BY_VERSION = {
2: ApiSetMapVersion2,
4: ApiSetMapVersion4,
6: ApiSetMapVersion6,
}
@@ -1,264 +0,0 @@
import threading
import windows
import windows.com
from windows.com import COMImplementation
from windows.generated_def.interfaces import (IBackgroundCopyManager, IEnumBackgroundCopyJobs, IBackgroundCopyJob,
IBackgroundCopyCallback, IUnknown, IBackgroundCopyError, IEnumBackgroundCopyFiles,
IBackgroundCopyFile)
import windows.generated_def as gdef
BackgroundCopyManager = windows.com.IID.from_string("4991d34b-80a1-4291-83b6-3328366b9097")
BackgroundCopyManager1_5 = windows.com.IID.from_string("f087771f-d74f-4c1a-bb8a-e16aca9124ea")
BackgroundCopyManager2_0 = windows.com.IID.from_string("6d18ad12-bde3-4393-b311-099c346e6df9s")
BackgroundCopyManager2_5 = windows.com.IID.from_string("03ca98d6-ff5d-49b8-abc6-03dd84127020")
BackgroundCopyManager3_0 = windows.com.IID.from_string("659cdea7-489e-11d9-a9cd-000d56965251")
BITS_CLS_BY_VERSION = {
(1,0): BackgroundCopyManager,
(1,5): BackgroundCopyManager1_5,
(2,0): BackgroundCopyManager2_0,
(2,5): BackgroundCopyManager2_5,
(3,0): BackgroundCopyManager3_0,
}
class BitsCopyCallback(COMImplementation):
IMPLEMENT = IBackgroundCopyCallback
def JobError(self, this, job, error):
return True
def JobTransferred(self, this, job):
#copy_terminated.set()
return True
def JobModification(self, job, reserved):
return True
class BitsCopyCallbackSetEvent(BitsCopyCallback):
def __init__(self, event):
super(BitsCopyCallbackSetEvent, self).__init__()
self.event = event
# With the current generated_def.interface design, the current
# prototype is:
# ctypes.WINFUNCTYPE(HRESULT, PVOID, PVOID)(4, "JobError")
# How should I address that ?
def JobError(self, this, job, error):
job = BitsCopyJob(job)
error = BitsCopyError(error)
errcode, errctx = error.error
print("Copy failed with error code <{0:#x}> (ctx={1})".format(errcode, errctx))
print("see <https://msdn.microsoft.com/en-us/library/windows/desktop/aa362823(v=vs.85).aspx>")
self.event.set()
return True
def JobTransferred(self, this, job):
self.event.set()
return True
class BitsCopyManager(IBackgroundCopyManager):
def get_jobs(self, flags=0):
jobsenum = IEnumBackgroundCopyJobs()
self.EnumJobs(flags, jobsenum)
res = []
nbretrieved = gdef.DWORD()
while True:
current = BitsCopyJob()
jobsenum.Next(1, current, nbretrieved)
if not nbretrieved.value:
break
res.append(current.promote())
jobsenum.Release()
return res
@property
def jobs(self):
return self.get_jobs()
def create(self, name, jobtype):
myjob_uuid = windows.com.IID()
newjob = BitsCopyJob()
self.CreateJob(name, jobtype, myjob_uuid, newjob)
return newjob.promote()
class BitsCopyJob(IBackgroundCopyJob):
version = 1
@property
def owner(self):
owner = gdef.LPWSTR()
self.GetOwner(owner)
data = owner.value
windows.winproxy.CoTaskMemFree(owner)
return data
@property
def iid(self):
res = windows.com.IID()
self.GetId(res)
res.update_strid()
return res
@property
def state(self):
x = gdef.BG_JOB_STATE()
self.GetState(x)
return x.value
@property
def name(self):
descr = gdef.LPWSTR()
self.GetDisplayName(descr)
data = descr.value
windows.winproxy.CoTaskMemFree(descr)
return data
@property
def description(self):
descr = gdef.LPWSTR()
self.GetDescription(descr)
data = descr.value
windows.winproxy.CoTaskMemFree(descr)
return data
@property
def files(self):
enum = IEnumBackgroundCopyFiles()
self.EnumFiles(enum)
count = gdef.ULONG()
enum.GetCount(count)
if not count:
return []
res_size = gdef.ULONG()
array = (BitsFile * count.value)()
enum.Next(count.value, array, res_size)
return array[:res_size.value]
@property
def type(self):
res = gdef.BG_JOB_TYPE()
self.GetType(res)
return res.value
@property
def priority(self):
priority = gdef.BG_JOB_PRIORITY()
self.GetPriority(priority)
return priority.value
@property
def minimum_retry_delay(self):
retry_delay = gdef.ULONG()
self.GetMinimumRetryDelay(retry_delay)
return retry_delay.value
@property
def proxy_settings(self):
ProxyUsage = gdef.BG_JOB_PROXY_USAGE()
ProxyList = gdef.LPWSTR()
ProxyBypassList = gdef.LPWSTR()
self.GetProxySettings(ProxyUsage, ProxyList, ProxyBypassList)
result = ProxyUsage.value, ProxyList.value, ProxyBypassList.value
windows.winproxy.CoTaskMemFree(ProxyList)
windows.winproxy.CoTaskMemFree(ProxyBypassList)
return result
@property
def times(self):
res = gdef.BG_JOB_TIMES()
self.GetTimes(res)
return res
def wait(self):
if self.state.value == gdef.BG_JOB_STATE_SUSPENDED:
raise ValueError("Cannot wait a BG_JOB_STATE_SUSPENDED job")
event = threading.Event()
callback_event = BitsCopyCallbackSetEvent(event)
self.SetNotifyInterface(callback_event)
self.SetNotifyFlags(1 | 2) # BG_NOTIFY_JOB_TRANSFERRED | BG_NOTIFY_JOB_ERROR
event.wait()
return True
def promote(self):
try:
return self.query(BitsCopyJob2)
except WindowsError as e:
return self
def __repr__(self):
return '<{0} iid="{1}" at {2:#08x}>'.format(type(self).__name__, self.iid, id(self))
class BitsCopyJob2(gdef.IBackgroundCopyJob2, BitsCopyJob):
version = 2
@property
def notify_cmdline(self):
path = gdef.LPWSTR()
params = gdef.LPWSTR()
self.GetNotifyCmdLine(path, params)
strpath, strparams = path.value, params.value
windows.winproxy.CoTaskMemFree(path)
windows.winproxy.CoTaskMemFree(params)
return strpath, strparams
class BitsFile(IBackgroundCopyFile):
version = 1
@property
def local_name(self):
name = gdef.LPWSTR()
self.GetLocalName(name)
data = name.value
windows.winproxy.CoTaskMemFree(name)
return data
@property
def remote_name(self):
name = gdef.LPWSTR()
self.GetRemoteName(name)
data = name.value
windows.winproxy.CoTaskMemFree(name)
return data
@property
def progress(self):
progress = gdef.BG_FILE_PROGRESS()
self.GetProgress(progress)
return progress
def promote(self):
try:
return self.query(BitsFile3)
except WindowsError as e:
return self
class BitsFile3(gdef.IBackgroundCopyFile3, BitsFile):
version = 3
@property
def temporary_name(self):
name = gdef.LPWSTR()
self.GetTemporaryName(name)
data = name.value
windows.winproxy.CoTaskMemFree(name)
return data
class BitsCopyError(IBackgroundCopyError):
@property
def error(self):
err_ctx = gdef.BG_ERROR_CONTEXT()
err = gdef.HRESULT()
self.GetError(err_ctx, err)
return (err.value & 0xffffffff, err_ctx)
def create_manager(version=(3,0)):
windows.com.init()
clsid = BITS_CLS_BY_VERSION[version]
manager = BitsCopyManager()
windows.com.create_instance(clsid, manager)
return manager
@@ -1,515 +0,0 @@
import ctypes
import itertools
import windows
from windows import winproxy
import windows.generated_def as gdef
from windows.security import SecurityDescriptor
from windows.utils import fixedproperty
class DeviceManager(object):
"""Represent the device manager"""
@property
def classes(self):
"""The list of installed device classes.
:return: [:class:`DeviceClass`] -- A list of :class:`DeviceClass`
"""
return list(self._classes_generator())
def _classes_generator(self):
for index in itertools.count():
try:
yield self._enumerate_classes(index, 0)
except WindowsError as e:
if e.winerror == gdef.CR_NO_SUCH_VALUE:
break
# Some index values might represent list entries containing invalid class data,
# in which case the function returns CR_INVALID_DATA.
# This return value can be ignored.
if e.winerror == gdef.CR_INVALID_DATA:
continue
raise
def _enumerate_classes(self, index, flags=0):
res = DeviceClass()
x = winproxy.CM_Enumerate_Classes(index, res, flags)
return res
class DeviceClass(gdef.GUID):
"""A Device class, which is mainly a :class:`GUID` with additional attributes"""
def __init__(self):
# Bypass GUID __init__ that is not revelant here
pass
@fixedproperty
def name(self):
"""The name of the device class"""
return self._get_device_class_name()
@property
def devices(self):
"""The set of devices of the current class.
:type: :class:`DeviceInformationSet`
"""
return self.enumerate_devices()
def enumerate_devices(self, flags=0):
handle = winproxy.SetupDiGetClassDevsA(self, Flags=flags)
return DeviceInformationSet(handle)
def _get_device_class_name(self):
name = ctypes.create_string_buffer(gdef.MAX_CLASS_NAME_LEN)
winproxy.SetupDiClassNameFromGuidA(self, name)
return name.value
def __repr__(self):
guid_cls = self.to_string()
return """<{0} name="{1}" guid={2}>""".format(type(self).__name__, self.name, guid_cls)
__str__ = __repr__ # Overwrite default GUID str
class DeviceInformationSet(gdef.HDEVINFO):
"""A device instances, can be itered to retrieve the underliyings :class:`DeviceInstance`"""
def all_device_infos(self):
for index in itertools.count():
try:
yield self.enum_device_info(index)
except WindowsError as e:
if e.winerror == gdef.ERROR_NO_MORE_ITEMS:
return
raise
__iter__ = all_device_infos
def enum_device_info(self, index):
res = DeviceInstance(self)
res.cbSize = ctypes.sizeof(res)
winproxy.SetupDiEnumDeviceInfo(self, index, res)
return res
def enum_device_interface(self, index):
"""Not Implemented Yet"""
raise NotImplementedError("enum_device_interface")
def all(self):
return list(self)
class DeviceInstance(gdef.SP_DEVINFO_DATA):
"""An instance of a Device.
The properties are from the page https://docs.microsoft.com/en-us/windows/win32/api/setupapi/nf-setupapi-setupdigetdeviceregistrypropertya#spdrp_address
"""
def __init__(self, information_set=None):
self.information_set = information_set
# make a .device_class ? that return the DeviceClass ased in ClassGuid ?
def get_property(self, property):
datatype = gdef.DWORD()
buffer_size = 0x1000
buffer = windows.utils.BUFFER(gdef.BYTE, nbelt=buffer_size)()
required_size = gdef.DWORD()
# Registry parsing code expect W stuff, so use W function
try:
winproxy.SetupDiGetDeviceRegistryPropertyW(self.information_set, self, property, datatype, buffer.cast(gdef.LPBYTE), buffer_size, required_size)
except WindowsError as e:
if e.winerror == gdef.ERROR_INVALID_DATA:
return None
raise
# PropertyRegDataType
# A pointer to a variable that receives the data type of the property
# that is being retrieved.
# This is one of the standard registry data types
# Look like its registry based, so use the registry decoders :)
return windows.winobject.registry.decode_registry_buffer(datatype.value, buffer, required_size.value)
def _generate_property_getter(prop):
def getter(self):
return self.get_property(prop)
return property(getter)
name = _generate_property_getter(gdef.SPDRP_FRIENDLYNAME)
"""The name of the device"""
description = _generate_property_getter(gdef.SPDRP_DEVICEDESC)
"""The description of the device"""
hardware_id = _generate_property_getter(gdef.SPDRP_HARDWAREID)
"""The list of hardware IDs for the device.
(https://docs.microsoft.com/en-us/windows/win32/api/setupapi/nf-setupapi-setupdigetdeviceregistrypropertya#spdrp_hardwareid)
"""
enumerator_name = _generate_property_getter(gdef.SPDRP_ENUMERATOR_NAME)
"""The enumerator name of the devices
(https://docs.microsoft.com/en-us/windows/win32/api/setupapi/nf-setupapi-setupdigetdeviceregistrypropertya#spdrp_enumerator_name)
"""
driver = _generate_property_getter(gdef.SPDRP_DRIVER)
"""The driver of the device
https://docs.microsoft.com/en-us/windows/win32/api/setupapi/nf-setupapi-setupdigetdeviceregistrypropertya#spdrp_driver
"""
# Map on Device type ?
# https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/specifying-device-types
type = _generate_property_getter(gdef.SPDRP_DEVTYPE)
"""The type of device
(https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/specifying-device-types)
"""
upper_filters = _generate_property_getter(gdef.SPDRP_UPPERFILTERS)
"""A list of string that contains the names of a device's upper filter drivers."""
lower_filters = _generate_property_getter(gdef.SPDRP_LOWERFILTERS)
"""A list of string that contains the names of a device's lower filter drivers."""
raw_security_descriptor = _generate_property_getter(gdef.SPDRP_SECURITY)
"""The raw (binary) security descriptor of the device"""
# I would prefer to use the security_descriptor sddl
# ssdl = _generate_property_getter(gdef.SPDRP_SECURITY_SDS)
service_name = _generate_property_getter(gdef.SPDRP_SERVICE)
"""The name of the service for the device
(https://docs.microsoft.com/en-us/windows/win32/api/setupapi/nf-setupapi-setupdigetdeviceregistrypropertya#spdrp_service)
"""
manufacturer = _generate_property_getter(gdef.SPDRP_MFG)
"""The name of the device manufacturer."""
location_information = _generate_property_getter(gdef.SPDRP_LOCATION_INFORMATION)
"""The hardware location of a device."""
location_paths = _generate_property_getter(gdef.SPDRP_LOCATION_PATHS)
"""A list of strings that represents the location of the device in the device tree."""
# Looks like it can raise ERROR_NO_SUCH_DEVINST
# install_date = _generate_property_getter(gdef.SPDRP_INSTALL_STATE)
capabilites = _generate_property_getter(gdef.SPDRP_CAPABILITIES)
"""The device capabilites
(https://docs.microsoft.com/en-us/windows/win32/api/setupapi/nf-setupapi-setupdigetdeviceregistrypropertya#spdrp_capabilities)
"""
bus_type = _generate_property_getter(gdef.SPDRP_BUSTYPEGUID)
"""The function retrieves the GUID for the device's bus type."""
bus_number = _generate_property_getter(gdef.SPDRP_BUSNUMBER)
"""The device's bus number."""
address = _generate_property_getter(gdef.SPDRP_ADDRESS)
"""The device's address."""
ui_number = _generate_property_getter(gdef.SPDRP_UI_NUMBER)
"""Retrieves a DWORD value set to the value of the UINumber member of the device's"""
ui_number_desc_format = _generate_property_getter(gdef.SPDRP_UI_NUMBER_DESC_FORMAT)
# Getter with special error handling
@property
def device_object_name(self):
"""The function retrieves a string that contains the name that is associated with the device's PDO."""
try:
return self.get_property(gdef.SPDRP_PHYSICAL_DEVICE_OBJECT_NAME)
except WindowsError as e:
if e.winerror not in (gdef.ERROR_INVALID_DATA, gdef.ERROR_NO_SUCH_DEVINST):
raise
# https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/hardware-resources
# Explanation of types:
# - https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/hardware-resources#logical-configuration-types-for-resource-requirements-lists
def get_first_logical_configuration(self, type):
res = LogicalConfiguration()
try:
winproxy.CM_Get_First_Log_Conf(res, self.DevInst, type)
except WindowsError as e:
if e.winerror == gdef.CR_CALL_NOT_IMPLEMENTED:
e.strerror += " (Cannot be called from Wow64 process since Win8)"
raise
return res
def get_next_logical_configuration(self, logconf):
res = gdef.HANDLE(0)
winproxy.CM_Get_Next_Log_Conf(res, logconf)
return res
def _logical_configuration_generator(self, type):
x = self.get_first_logical_configuration(type)
while x:
yield x
try:
x = self.get_next_logical_configuration(x)
except WindowsError as e:
if e.winerror == gdef.CR_NO_MORE_LOG_CONF:
return
raise
def get_logical_configuration(self, type):
return list(self._logical_configuration_generator(type))
# Allocated Configuration
# From https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/hardware-resources#logical-configuration-types-for-resource-lists
# A resource list identifying resources currently in use by a device instance.
# !!! Only one allocated configuration can exist for each device instance.
@property
def allocated_configuration(self):
"""The allocated configuration of the device.
(https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/hardware-resources#logical-configuration-types-for-resource-lists)
:type: :class:`LogicalConfiguration`
"""
allocconfs = self.get_logical_configuration(gdef.ALLOC_LOG_CONF)
if not allocconfs:
return allocconfs
assert len(allocconfs) == 1 # Only one allocated configuration can exist for each device instance.
return allocconfs[0]
# Boot Configuration
# From https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/hardware-resources#logical-configuration-types-for-resource-lists
# A resource list identifying the resources assigned to a device instance when the system is booted
# Only one boot configuration can exist for each device instance.
@property
def boot_configuration(self):
"""The boot configuration of the device.
(https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/hardware-resources#logical-configuration-types-for-resource-lists)
:type: :class:`LogicalConfiguration`
"""
bootconfs = self.get_logical_configuration(gdef.BOOT_LOG_CONF)
if not bootconfs:
return bootconfs
assert len(bootconfs) == 1 # Only one boot configuration can exist for each device instance.
return bootconfs[0]
# Make properties for Each type of logical configuration ?
# 'advanced' attributes extrapolated from properties
@property
def security_descriptor(self):
"""The security descriptor of the device.
:type: :class:`~windows.security.SecurityDescriptor`
"""
return SecurityDescriptor.from_binary(self.raw_security_descriptor)
def __repr__(self):
return """<{0} "{1}" (id={2})>""".format(type(self).__name__, self.description, self.DevInst)
class LogicalConfiguration(gdef.HANDLE):
"""Logical Configuration of a Device instance"""
def get_next_resource_descriptor(self, resource, resdes=None):
if resdes is None:
# Using logical-conf as resdes will retrieve the first one
# https://docs.microsoft.com/en-us/windows/win32/api/cfgmgr32/nf-cfgmgr32-cm_get_next_res_des#remarks
resdes = self
resid = None
if resource == gdef.ResType_All:
resid = gdef.RESOURCEID()
res = gdef.HANDLE()
winproxy.CM_Get_Next_Res_Des(res, resdes, resource, resid, 0)
resdes_type = resid.value if resid is not None else resource
return ResourceDescriptor.from_handle_and_type(res.value, resdes_type)
def get_resources_for_type(self, type):
try:
current = self.get_next_resource_descriptor(type)
yield current
while True:
current = self.get_next_resource_descriptor(type, current)
yield current
except WindowsError as e:
if e.winerror == gdef.CR_NO_MORE_RES_DES:
return
raise
@property
def resources(self):
"""The list of resources in the current logical configuration
:type: [:class:`ResourceDescriptor`] -- A list of [:class:`ResourceDescriptor`]
"""
return list(self.get_resources_for_type(gdef.ResType_All))
def __repr__(self):
return "<{0}>".format(type(self).__name__)
ResType_Mapper = gdef.FlagMapper(
gdef.ResType_None,
gdef.ResType_Mem,
gdef.ResType_IO,
gdef.ResType_DMA,
gdef.ResType_IRQ,
gdef.ResType_BusNumber,
gdef.ResType_MemLarge,
gdef.ResType_ClassSpecific,
gdef.ResType_DevicePrivate,
gdef.ResType_MfCardConfig,
gdef.ResType_PcCardConfig,
)
class ResourceDescriptor(gdef.HANDLE):
"""Describe a resource allocated or reserved by a device instance.
This class is a base class, all resources returned by :class:`LogicalConfiguration` should be one of the following:
* :class:`ResourceNoType`
* :class:`MemoryResource`
* :class:`IoResource`
* :class:`DmaResource`
* :class:`IrqResource`
* :class:`BusNumberResource`
* :class:`MemLargeResource`
* :class:`ClassSpecificResource`
* :class:`DevicePrivateResource`
* :class:`MfCardConfigResource`
* :class:`PcCardConfigResource`
"""
SUBCLASSES = {}
def __init__(self, handle, type):
super(ResourceDescriptor, self).__init__(handle)
self.type = ResType_Mapper[type]
@classmethod
def from_handle_and_type(cls, handle, type):
ecls = cls.SUBCLASSES[type]
return ecls(handle, type)
@property
def rawdata(self):
"""The raw data describing the resource"""
data_size = gdef.ULONG()
winproxy.CM_Get_Res_Des_Data_Size(data_size, self)
if not self:
return None
data_size = data_size.value
buffer = ctypes.create_string_buffer(data_size)
winproxy.CM_Get_Res_Des_Data(self, buffer, data_size)
return bytearray(buffer[:data_size])
def __repr__(self):
return "<{0} type={1!r}>".format(type(self).__name__, self.type)
class ResourceDescriptorWithHeader(ResourceDescriptor):
# Assert the header is the first field
@property
def header_type(self):
# Type of first field
return self.DATA_TYPE._fields_[0][1]
@property
def header(self):
return self.header_type.from_buffer(self.rawdata)
@property
def data(self):
return None
class ResourceDescriptorWithHeaderAndRanges(ResourceDescriptorWithHeader):
def count_field_name(self):
# Assert (manyally checked) that the first field of the
# header is a field containing the size of the data array
# Return name of the first field of the header
return self.header_type._fields_[0][0]
@property
def data(self):
count_field_name = self.count_field_name()
count = getattr(self.header, count_field_name)
# No entry:
if not count:
return []
raise NotImplementedError("Resource descriptor with non-zero entry in range array")
class ResourceNoType(ResourceDescriptor):
@property
def data(self):
return self.rawdata
class MemoryResource(ResourceDescriptorWithHeaderAndRanges):
"""A resource of type MEM_RESOURCE"""
DATA_TYPE = gdef.MEM_RESOURCE
def __str__(self):
return "<{0} : [{1:#016x}-{2:#016x}]>".format(type(self).__name__, self.header.MD_Alloc_Base, self.header.MD_Alloc_End)
class IoResource(ResourceDescriptorWithHeaderAndRanges):
"""A resource of type IO_RESOURCE"""
DATA_TYPE = gdef.IO_RESOURCE
def __str__(self):
return "<{0} : [{1:#016x}-{2:#016x}]>".format(type(self).__name__, self.header.IOD_Alloc_Base, self.header.IOD_Alloc_End)
class DmaResource(ResourceDescriptorWithHeaderAndRanges):
"""A resource of type DMA_RESOURCE"""
DATA_TYPE = gdef.DMA_RESOURCE
def __str__(self):
return "<{0} : [{1:#016x}]>".format(type(self).__name__, self.header.DD_Alloc_Chan)
class IrqResource(ResourceDescriptorWithHeaderAndRanges):
"""A resource of type IRQ_RESOURCE"""
# 32/64 based on current process bitness
# Cross bitness cannot be implemented as >=Win8 block it
DATA_TYPE = gdef.IRQ_RESOURCE
def __str__(self):
return "<{0} : [{1:#016x}]>".format(type(self).__name__, self.header.IRQD_Alloc_Num)
class BusNumberResource(ResourceDescriptorWithHeaderAndRanges):
"""A resource of type BUSNUMBER_RESOURCE"""
DATA_TYPE = gdef.BUSNUMBER_RESOURCE
def __str__(self):
return "<{0} : [{1:#016x}-{2:#016x}]>".format(type(self).__name__, self.header.BUSD_Alloc_Base, self.header.BUSD_Alloc_End)
class MemLargeResource(ResourceDescriptor):
"""A resource of type MEM_LARGE_RESOURCE"""
DATA_TYPE = gdef.MEM_LARGE_RESOURCE
def __str__(self):
return "<{0} : [{1:#016x}-{2:#016x}]>".format(type(self).__name__, self.header.MLD_Alloc_Base, self.header.MLD_Alloc_End)
class ClassSpecificResource(ResourceDescriptorWithHeader):
"""A resource of type CS_RESOURCE"""
DATA_TYPE = gdef.CS_RESOURCE
# Any idea for __str__ ?
class DevicePrivateResource(ResourceDescriptor):
"""A device private resource
(https://docs.microsoft.com/en-us/windows-hardware/drivers/install/devprivate-resource)
"""
@property
def header(self):
return None
# Any idea for __str__ ?
class MfCardConfigResource(ResourceDescriptorWithHeader):
"""A resource of type MFCARD_RESOURCE"""
DATA_TYPE = gdef.MFCARD_RESOURCE
# Any idea for __str__ ?
class PcCardConfigResource(ResourceDescriptorWithHeader):
"""A resource of type PCCARD_RESOURCE"""
DATA_TYPE = gdef.PCCARD_RESOURCE
# Any idea for __str__ ?
# Flemme de faire une meta-classe pour ca..
ResourceDescriptor.SUBCLASSES.update({
gdef.ResType_None: ResourceNoType,
gdef.ResType_Mem: MemoryResource,
gdef.ResType_IO: IoResource,
gdef.ResType_DMA: DmaResource,
gdef.ResType_IRQ: IrqResource,
gdef.ResType_BusNumber: BusNumberResource,
gdef.ResType_MemLarge: MemLargeResource,
gdef.ResType_ClassSpecific: ClassSpecificResource,
gdef.ResType_DevicePrivate: DevicePrivateResource,
gdef.ResType_MfCardConfig: MfCardConfigResource,
gdef.ResType_PcCardConfig: PcCardConfigResource,
})
File diff suppressed because it is too large Load Diff
@@ -1,449 +0,0 @@
import ctypes
import windows
import windows.generated_def as gdef
from windows.pycompat import basestring
# Renommer le fichier etw ?
MAX_ETW_SESSIONS = 64
MAX_SESSION_NAME_LEN = 1024
MAX_LOGFILE_PATH_LEN = 1024
MAX_SESSION_NAME_LEN_W = MAX_SESSION_NAME_LEN * 2
MAX_LOGFILE_PATH_LEN_W = MAX_LOGFILE_PATH_LEN * 2
class EventRecord(gdef.EVENT_RECORD):
@property
def tid(self):
"""Thread ID that provided the event"""
return self.EventHeader.ThreadId
@property
def pid(self):
"""Process ID that provided the event"""
return self.EventHeader.ProcessId
@property
def guid(self):
"""Guid of the Event"""
# Well, this is called "ProviderId" but seems to be the Event GUID
# As a provider can generated multiple event with differents GUID
# And this value reflect EVENT_TRACE_HEADER.Guid passed to TraceEvent
return self.EventHeader.ProviderId
@property
def id(self):
"""ID of the Event"""
return self.EventHeader.EventDescriptor.Id
@property
def opcode(self):
return self.EventHeader.EventDescriptor.Opcode
@property
def version(self):
return self.EventHeader.EventDescriptor.Version
@property
def level(self):
return self.EventHeader.EventDescriptor.Level
@property
def context(self):
if self.UserContext is None:
return None
return ctypes.py_object.from_address(self.UserContext).value
@property
def user_data(self):
"""Event specific data
:type: :class:`str`
"""
if not (self.UserData and self.UserDataLength):
return ""
dbuf = (ctypes.c_char * self.UserDataLength).from_address(self.UserData)
return dbuf[:]
# def match(self, provider=None, id=None, opcode=None):
def __repr__(self):
guid = self.EventHeader.ProviderId
return """<{0} provider="{1}" id={2}>""".format(type(self).__name__, guid, self.id)
PEventRecord = ctypes.POINTER(EventRecord)
class EventTraceProperties(gdef.EVENT_TRACE_PROPERTIES):
"""Represent an Event Trace session that may exist or now. (https://docs.microsoft.com/en-us/windows/win32/api/evntrace/ns-evntrace-event_trace_properties)
This class is widly used by :class:`EtwTrace`
"""
# Test: ascii / Use Wchar ?
FULL_SIZE = ctypes.sizeof(gdef.EVENT_TRACE_PROPERTIES) + MAX_SESSION_NAME_LEN_W + MAX_LOGFILE_PATH_LEN_W
# def alloc(cls, size) ?
@classmethod
def create(cls):
"""Initialize a new :class:`EventTraceProperties`"""
buff = windows.utils.BUFFER(cls)(size=cls.FULL_SIZE)
# ctypes.memset(buff, "\x00", cls.FULL_SIZE)
self = buff[0]
self.Wnode.BufferSize = cls.FULL_SIZE
self.LoggerNameOffset = ctypes.sizeof(cls)
self.LogFileNameOffset = ctypes.sizeof(cls) + MAX_SESSION_NAME_LEN
return self
def get_logfilename(self):
assert self.LogFileNameOffset
return windows.current_process.read_string(ctypes.addressof(self) + self.LogFileNameOffset)
def set_logfilename(self, filename):
assert self.LogFileNameOffset
if not filename.endswith("\x00"):
filename += "\x00"
return windows.current_process.write_memory(ctypes.addressof(self) + self.LogFileNameOffset, filename)
logfile = property(get_logfilename, set_logfilename) #: The logfile associated with the session
def get_logger_name(self):
assert self.LoggerNameOffset
return windows.current_process.read_string(ctypes.addressof(self) + self.LoggerNameOffset)
def set_logfilename(self, filename):
assert self.LoggerNameOffset
if not filename.endswith("\x00"):
filename += "\x00"
return windows.current_process.write_memory(ctypes.addressof(self) + self.LoggerNameOffset, filename)
name = property(get_logger_name, set_logfilename) #: The name of the session
@property
def guid(self):
"""The GUID of the Event Trace session (see ``Wnode.Guid``)"""
return self.Wnode.Guid
@property
def id(self):
"""The LoggerId if the session (see ``Wnode.HistoricalContext``)"""
return self.Wnode.HistoricalContext
def __repr__(self):
return """<{0} name="{1}" guid={2}>""".format(type(self).__name__, self.name, self.guid)
# GUID setter ?
class CtxProcess(object):
def __init__(self, trace, func, stop=False):
self.trace = trace
self.func = func
self.stop = stop
self.timing = {}
def _get_time(self):
now = gdef.FILETIME()
windows.winproxy.GetSystemTimeAsFileTime(now)
return now
def __enter__(self):
self.timing["begin"] = self._get_time()
return self.timing
def __exit__(self, exc_type, exc_value, traceback):
# bad_end = self._get_time()
self.trace.flush()
if self.stop:
self.trace.stop()
# End time after the flush is effective.
self.timing["end"] = self._get_time()
# print("Trace ctx: fake-end: {0:#x}".format(int(fake_end)))
print("Trace ctx: begin={0:#x} | end={1:#x}".format(int(self.timing["begin"]), int(self.timing["end"])))
self.trace.process(self.func, **self.timing)
class EtwTrace(object):
"""Represent an ETW Trace for tracing/processing events"""
def __init__(self, name, logfile=None, guid=None):
self.name = windows.pycompat.raw_encode(name) #: The name of the trace
self.logfile = logfile #: The logging file of the trace (``None`` means real time trace)
if guid and isinstance(guid, basestring):
guid = gdef.GUID.from_string(guid)
self.guid = guid #: The guid of the trace
self.handle = 0
def exists(self):
"""Return ``True`` if the trace already exist (based on its name)"""
prop = EventTraceProperties.create()
try:
windows.winproxy.ControlTraceA(self.handle, self.name, prop, gdef.EVENT_TRACE_CONTROL_QUERY)
except WindowsError as e:
if e.winerror == gdef.ERROR_WMI_INSTANCE_NOT_FOUND:
return False # Not found -> does not exists
raise # Other error -> reraise
return True
def start(self, flags=0, mode=0):
"""Start the tracing"""
prop = EventTraceProperties.create()
prop.NumberOfBuffers = 42
prop.EnableFlags = flags
prop.LogFileMode = mode
if self.guid:
prop.Wnode.Guid = self.guid
if self.logfile:
prop.logfile = self.logfile
if self.name: # Base REAL_TIME on option ? name presence ? logfile presence ?
prop.LogFileMode |= gdef.EVENT_TRACE_REAL_TIME_MODE
handle = gdef.TRACEHANDLE()
windows.winproxy.StartTraceA(handle, self.name, prop)
if not self.guid:
self.guid = prop.Wnode.Guid
self.handle = handle
def stop(self, soft=False): # Change name
"""stop the tracing.
``soft`` will allow to stop a non-existing trace that do not exists/run.
This allow for simpler script that stop/start some EtwTrace.
"""
prop = EventTraceProperties.create()
try:
windows.winproxy.ControlTraceA(0, self.name, prop, gdef.EVENT_TRACE_CONTROL_STOP)
except WindowsError as e:
if soft and e.winerror == gdef.ERROR_WMI_INSTANCE_NOT_FOUND:
return False
raise
return True
def flush(self):
"""Flush the trace"""
prop = EventTraceProperties.create()
windows.winproxy.ControlTraceA(0, self.name, prop, gdef.EVENT_TRACE_CONTROL_FLUSH)
def enable(self, guid, flags=0xff, level=0xff):
"""Enable the specified event trace provider."""
if isinstance(guid, basestring):
guid = gdef.GUID.from_string(guid)
return windows.winproxy.EnableTrace(1, flags, level, guid, self.handle) # EnableTraceEx ?
def enable_ex(self, guid, flags=0xff, level=0xff, any_keyword = 0xffffffff, all_keyword=0x00):
"""Enable the specified event trace provider."""
if isinstance(guid, basestring):
guid = gdef.GUID.from_string(guid)
# TODO : implement EnableParameters
EVENT_CONTROL_CODE_ENABLE_PROVIDER = 1
# EnableTraceEx only accept a UCHAR for the level param
# TODO : maybe raise an Exception instead of silently masking the value ?
level = gdef.UCHAR(chr(level & 0xff))
return windows.winproxy.EnableTraceEx2(self.handle, guid, EVENT_CONTROL_CODE_ENABLE_PROVIDER, level , any_keyword, all_keyword, 0, None)
def process(self, callback, begin=None, end=None, context=None):
"""Process the event retrieved by the trace.
This function will call ``callback`` with any :class:`EventRecord` in the trace.
``begin/end`` allow to filter and only process events in a given timeframe.
.. warning::
If the trace if ``REALTIME`` (no logfile) this function will hang/process new event until the trace is stopped.
Using ``logman -ets stop TRACE_NAME`` for exemple.
"""
if end == "now":
end = gdef.FILETIME()
windows.winproxy.GetSystemTimeAsFileTime(end)
windows.utils.sprint(end)
logfile = gdef.EVENT_TRACE_LOGFILEW()
logfile.LoggerName = windows.pycompat.raw_decode(self.name)
# logfile.ProcessTraceMode = gdef.PROCESS_TRACE_MODE_EVENT_RECORD | gdef.PROCESS_TRACE_MODE_RAW_TIMESTAMP
logfile.ProcessTraceMode = gdef.PROCESS_TRACE_MODE_EVENT_RECORD
if not self.logfile:
logfile.ProcessTraceMode |= gdef.PROCESS_TRACE_MODE_REAL_TIME
else:
# logfile.ProcessTraceMode |= gdef.PROCESS_TRACE_MODE_REAL_TIME
logfile.LogFileName = self.logfile
if context:
context_ptr = ctypes.pointer(ctypes.py_object(context))
logfile.Context = ctypes.cast(context_ptr, ctypes.c_void_p)
@ctypes.WINFUNCTYPE(gdef.PVOID, PEventRecord)
def real_callback(record_ptr):
try:
x = callback(record_ptr[0])
except Exception as e:
print("CALLBACK ERROR: {0}".format(e))
return 1
if x is None:
x = 1
return x
@ctypes.WINFUNCTYPE(gdef.PVOID, gdef.PEVENT_TRACE_LOGFILEW)
def buffer_callback(trace):
print("Buffer-callback: event-lost={0}".format(trace[0].LogfileHeader.EventsLost))
print("Buffer-callback: buffer-lost={0}".format(trace[0].LogfileHeader.BuffersLost))
return True
logfile.EventRecordCallback = ctypes.cast(real_callback, gdef.PVOID)
# logfile.BufferCallback = ctypes.cast(buffer_callback, gdef.PVOID)
r = windows.winproxy.OpenTraceW(logfile)
rh = gdef.TRACEHANDLE(r)
return windows.winproxy.ProcessTrace(rh, 1, begin, end)
def CtxProcess(self, func, stop=False):
return CtxProcess(self, func, stop=stop)
def __repr__(self):
return """<{0} name={1!r} logfile={2!r}>""".format(type(self).__name__, self.name, self.logfile)
class TraceProvider(object):
"""Represent a ETW provider, which is just a GUID.
Corresponding name for a provider may be available trhought WMI.
"""
def __init__(self, guid):
self.guid = guid
@property
def infos(self):
"""The :class:`TraceGuidInfo` associated with the provider.
Main use is to retrieve the instances of the provider (directly available with ``instances``)
:type: :class:`TraceGuidInfo`
"""
size = gdef.DWORD()
info_buffer = ctypes.c_buffer(0x1000)
try:
windows.winproxy.EnumerateTraceGuidsEx(gdef.TraceGuidQueryInfo, self.guid, ctypes.sizeof(self.guid), info_buffer, ctypes.sizeof(info_buffer), size)
except WindowsError as e:
if not e.winerror == gdef.ERROR_INSUFFICIENT_BUFFER:
raise
# Buffer to small
info_buffer = ctypes.c_buffer(size.value)
windows.winproxy.EnumerateTraceGuidsEx(gdef.TraceGuidQueryInfo, self.guid, ctypes.sizeof(self.guid), info_buffer, ctypes.sizeof(info_buffer), size)
return TraceGuidInfo.from_raw_buffer(info_buffer)
# We dont really care about the C struct layout
# Our trace providers should be able to directly returns its instances
@property
def instances(self):
"""The instances of the provider.
:type: [:class:`TraceProviderInstanceInfo`] -- A list of :class:`TraceProviderInstanceInfo`
"""
return self.infos.instances
def __repr__(self):
return """<{0} for "{1}">""".format(type(self).__name__, self.guid)
class TraceGuidInfo(gdef.TRACE_GUID_INFO):
"""Defines the header to the list of sessions that enabled the provider
(see https://docs.microsoft.com/en-us/windows/win32/api/evntrace/ns-evntrace-trace_guid_info)
"""
@classmethod
def from_raw_buffer(cls, buffer):
self = cls.from_buffer(buffer)
self._raw_buffer_ = buffer
return self
def _instance_generator(self):
if not self.InstanceCount:
return
abs_offset = ctypes.sizeof(self)
for i in range(self.InstanceCount):
instance = TraceProviderInstanceInfo.from_raw_buffer(self._raw_buffer_, abs_offset)
abs_offset += instance.NextOffset
yield instance
@property
def instances(self):
"""The instances of the provider.
:type: [:class:`TraceProviderInstanceInfo`] -- A list of :class:`TraceProviderInstanceInfo`
"""
return [x for x in self._instance_generator()]
def __repr__(self):
return "<{0} InstanceCount={1} Reserved={2}>".format(type(self).__name__, self.InstanceCount, self.Reserved)
class TraceProviderInstanceInfo(gdef.TRACE_PROVIDER_INSTANCE_INFO):
"""Defines an instance of the provider
(see https://docs.microsoft.com/en-us/windows/win32/api/evntrace/ns-evntrace-trace_provider_instance_info)
"""
@classmethod
def from_raw_buffer(cls, buffer, offset):
self = cls.from_buffer(buffer, offset)
self._offset = offset
self._raw_buffer_ = buffer
return self
def _instance_generator(self):
offset = self._offset + ctypes.sizeof(self)
entry_size = ctypes.sizeof(gdef.TRACE_ENABLE_INFO)
for i in range(self.EnableCount):
yield gdef.TRACE_ENABLE_INFO.from_buffer(self._raw_buffer_, offset)
offset += entry_size
@property
def sessions(self):
"""The sessions for the instance
:type: [:class:`~windows.generated_def.winstructs.TRACE_ENABLE_INFO`] -- A list of session
"""
return [x for x in self._instance_generator()]
def __repr__(self):
return "<{0} Pid={1} EnableCount={2}>".format(type(self).__name__, self.Pid, self.EnableCount)
class EtwManager(object):
"""An object to query ETW session/providers and open new trace"""
@property
def sessions(self):
"""The list of currently active ETW session.
:type: [:class:`EventTraceProperties`] -- A list of :class:`EventTraceProperties`
"""
# Create a tuple of MAX_ETW_SESSIONS EventTraceProperties ptr
t = [EventTraceProperties.create() for _ in range(MAX_ETW_SESSIONS)]
# Put this in a ctypes array
array = (gdef.POINTER(EventTraceProperties) * MAX_ETW_SESSIONS)(*(ctypes.pointer(e) for e in t))
# Cast as array/ptr does not handle subtypes very-well
tarray = ctypes.cast(array, ctypes.POINTER(ctypes.POINTER(gdef.EVENT_TRACE_PROPERTIES)))
count = gdef.DWORD()
windows.winproxy.QueryAllTracesA(tarray, MAX_ETW_SESSIONS, count)
return t[:count.value]
@property
def providers(self):
"""The list of currently existing ETW providers.
:type: [:class:`TraceProvider`] -- A list of ETW providers
"""
buffer = windows.utils.BUFFER(gdef.GUID, 0x1000)()
size = gdef.DWORD()
windows.winproxy.EnumerateTraceGuidsEx(gdef.TraceGuidQueryList, None, 0, buffer, buffer.real_size, size)
return [TraceProvider(g) for g in buffer[:size.value // ctypes.sizeof(gdef.GUID)]]
# Temp name / API ?
def open_trace(self, name=None, logfile=None, guid=None):
"""Open a new ETW Trace
:return: :class:`EtwTrace`
"""
return EtwTrace(name, logfile, guid)
@@ -1,375 +0,0 @@
import ctypes
import windows
from windows.generated_def.winstructs import *
import windows.generated_def.windef as windef
EXCEPTION_CONTINUE_SEARCH = (0x0)
EXCEPTION_CONTINUE_EXECUTION = (0xffffffff)
exception_type = [
"EXCEPTION_ACCESS_VIOLATION",
"EXCEPTION_DATATYPE_MISALIGNMENT",
"EXCEPTION_BREAKPOINT",
"EXCEPTION_SINGLE_STEP",
"EXCEPTION_ARRAY_BOUNDS_EXCEEDED",
"EXCEPTION_FLT_DENORMAL_OPERAND",
"EXCEPTION_FLT_DIVIDE_BY_ZERO",
"EXCEPTION_FLT_INEXACT_RESULT",
"EXCEPTION_FLT_INVALID_OPERATION",
"EXCEPTION_FLT_OVERFLOW",
"EXCEPTION_FLT_STACK_CHECK",
"EXCEPTION_FLT_UNDERFLOW",
"EXCEPTION_INT_DIVIDE_BY_ZERO",
"EXCEPTION_INT_OVERFLOW",
"EXCEPTION_PRIV_INSTRUCTION",
"EXCEPTION_IN_PAGE_ERROR",
"EXCEPTION_ILLEGAL_INSTRUCTION",
"EXCEPTION_NONCONTINUABLE_EXCEPTION",
"EXCEPTION_STACK_OVERFLOW",
"EXCEPTION_INVALID_DISPOSITION",
"EXCEPTION_GUARD_PAGE",
"EXCEPTION_INVALID_HANDLE",
"EXCEPTION_POSSIBLE_DEADLOCK",
]
# x -> x dict may seems strange but useful to get the Flags (with name) from the int
# exception_name_by_value[0x80000001] -> EXCEPTION_GUARD_PAGE(0x80000001L)
exception_name_by_value = dict([(x, x) for x in [getattr(windows.generated_def.windef, name) for name in exception_type]])
class EEXCEPTION_RECORDBase(object):
@property
def ExceptionCode(self):
"""The Exception code
:type: :class:`int`"""
real_code = super(EEXCEPTION_RECORDBase, self).ExceptionCode
return exception_name_by_value.get(real_code, windows.generated_def.windef.Flag("UNKNOW_EXCEPTION", real_code))
@property
def ExceptionAddress(self):
"""The Exception Address
:type: :class:`int`"""
x = super(EEXCEPTION_RECORDBase, self).ExceptionAddress
if x is None:
return 0x0
return x
class EEXCEPTION_RECORD(EEXCEPTION_RECORDBase, EXCEPTION_RECORD):
"""Enhanced exception record"""
fields = [f[0] for f in EXCEPTION_RECORD._fields_]
"""The fields of the structure"""
class EEXCEPTION_RECORD32(EEXCEPTION_RECORDBase, EXCEPTION_RECORD32):
"""Enhanced exception record (32bits)"""
fields = [f[0] for f in EXCEPTION_RECORD32._fields_]
"""The fields of the structure"""
class EEXCEPTION_RECORD64(EEXCEPTION_RECORDBase, EXCEPTION_RECORD64):
"""Enhanced exception record (64bits)"""
fields = [f[0] for f in EXCEPTION_RECORD64._fields_]
"""The fields of the structure"""
class EEXCEPTION_DEBUG_INFO32(ctypes.Structure):
"""Enhanced Debug info"""
_fields_ = windows.utils.transform_ctypes_fields(EXCEPTION_DEBUG_INFO, {"ExceptionRecord": EEXCEPTION_RECORD32})
fields = [f[0] for f in _fields_]
"""The fields of the structure"""
class EEXCEPTION_DEBUG_INFO64(ctypes.Structure):
"""Enhanced Debug info"""
_fields_ = windows.utils.transform_ctypes_fields(EXCEPTION_DEBUG_INFO, {"ExceptionRecord": EEXCEPTION_RECORD64})
fields = [f[0] for f in _fields_]
"""The fields of the structure"""
class EEflags(ctypes.Structure):
"Flag view of the Eflags register"
_fields_ = [("CF", DWORD, 1),
("RES_1", DWORD, 1),
("PF", DWORD, 1),
("RES_3", DWORD, 1),
("AF", DWORD, 1),
("RES_5", DWORD, 1),
("ZF", DWORD, 1),
("SF", DWORD, 1),
("TF", DWORD, 1),
("IF", DWORD, 1),
("DF", DWORD, 1),
("OF", DWORD, 1),
("IOPL_1", DWORD, 1),
("IOPL_2", DWORD, 1),
("NT", DWORD, 1),
("RES_15", DWORD, 1),
("RF", DWORD, 1),
("VM", DWORD, 1),
("AC", DWORD, 1),
("VIF", DWORD, 1),
("VIP", DWORD, 1),
("ID", DWORD, 1),
]
fields = [f[0] for f in _fields_]
"""The fields of the structure"""
def get_raw(self):
x = DWORD.from_address(ctypes.addressof(self))
return x.value
def set_raw(self, value):
x = DWORD.from_address(ctypes.addressof(self))
x.value = value
return None
def dump(self):
res = []
for name in [x[0] for x in self._fields_]:
if name.startswith("RES_"):
continue
if getattr(self, name):
res.append(name)
return "|".join(res)
def __repr__(self):
return hex(self)
def __hex__(self):
if self.raw == 0:
return "{0}({1})".format(type(self).__name__, hex(self.raw))
return "{0}({1}:{2})".format(type(self).__name__, hex(self.raw), self.dump())
raw = property(get_raw, set_raw)
"""Raw value of the eflags
:type: :class:`int`
"""
class EDr7(ctypes.Structure):
"Flag view of the DR7 register"
_fields_ = [("L0", DWORD, 1),
("G0", DWORD, 1),
("L1", DWORD, 1),
("G1", DWORD, 1),
("L2", DWORD, 1),
("G2", DWORD, 1),
("L3", DWORD, 1),
("G3", DWORD, 1),
("LE", DWORD, 1),
("GE", DWORD, 1),
("RES_1", DWORD, 3),
("GD", DWORD, 1),
("RES_1", DWORD, 2),
("RW0", DWORD, 2),
("LEN0", DWORD, 2),
("RW1", DWORD, 2),
("LEN1", DWORD, 2),
("RW2", DWORD, 2),
("LEN2", DWORD, 2),
("RW3", DWORD, 2),
("LEN3", DWORD, 2),
]
fields = [f[0] for f in _fields_]
"""The fields of the structure"""
class ECONTEXTBase(object):
"""DAT CONTEXT"""
default_dump = ()
pc_reg = ''
sp_reg = ''
func_result_reg = ''
special_reg_type = {}
def regs(self, to_dump=None):
"""Return the name and values of the registers
:returns: [(reg_name, value)] -- A :class:`list` of :class:`tuple`"""
res = []
if to_dump is None:
to_dump = self.default_dump
for name in to_dump:
value = getattr(self, name)
if name in self.special_reg_type:
value = self.special_reg_type[name](value)
res.append((name, value))
return res
def dump(self, to_dump=None):
"""Dump (print) the current context"""
regs = self.regs()
for name, value in regs:
print("{0} -> {1}".format(name, hex(value)))
return None
def get_pc(self):
return getattr(self, self.pc_reg)
def set_pc(self, value):
return setattr(self, self.pc_reg, value)
def get_sp(self):
return getattr(self, self.sp_reg)
def set_sp(self, value):
return setattr(self, self.sp_reg, value)
def get_func_result(self):
return getattr(self, self.func_result_reg)
def set_func_result(self, value):
return setattr(self, self.func_result_reg, value)
pc = property(get_pc, set_pc, None, "Program Counter register (EIP or RIP)")
sp = property(get_sp, set_sp, None, "Stack Pointer register (ESP or RSP)")
func_result = property(get_func_result, set_func_result, None, "Function Resultat register (EAX or RAX)")
@property
def EEFlags(self):
"""Enhanced view of the Eflags (you also have ``EFlags`` for the raw value)
:type: :class:`EEflags`
"""
off = type(self).EFlags.offset
x = EEflags.from_address(ctypes.addressof(self) + off)
x.self = self
return x
@property
def EDr7(self):
"""Enhanced view of the DR7 register (you also have ``Dr7`` for the raw value)
:type: :class:`EDr7`
"""
off = type(self).Dr7.offset
x = EDr7.from_address(ctypes.addressof(self) + off)
x.self = self
return x
class ECONTEXT32(ECONTEXTBase, CONTEXT32):
default_dump = ('Eip', 'Esp', 'Eax', 'Ebx', 'Ecx', 'Edx', 'Ebp', 'Edi', 'Esi', 'EFlags')
pc_reg = 'Eip'
sp_reg = 'Esp'
func_result_reg = 'Eax'
fields = [f[0] for f in CONTEXT32._fields_]
"""The fields of the structure"""
class ECONTEXTWOW64(ECONTEXTBase, WOW64_CONTEXT):
default_dump = ('Eip', 'Esp', 'Eax', 'Ebx', 'Ecx', 'Edx', 'Ebp', 'Edi', 'Esi', 'EFlags')
pc_reg = 'Eip'
sp_reg = 'Esp'
func_result_reg = 'Eax'
fields = [f[0] for f in WOW64_CONTEXT._fields_]
"""The fields of the structure"""
class ECONTEXT64(ECONTEXTBase, CONTEXT64):
default_dump = ('Rip', 'Rsp', 'Rax', 'Rbx', 'Rcx', 'Rdx', 'Rbp', 'Rdi', 'Rsi',
'R8', 'R9', 'R10', 'R11', 'R12', 'R13', 'R14', 'R15', 'EFlags')
pc_reg = 'Rip'
sp_reg = 'Rsp'
func_result_reg = 'Rax'
fields = [f[0] for f in CONTEXT64._fields_]
"""The fields of the structure"""
@classmethod
def new_aligned(cls):
"""Return a new :class:`ECONTEXT64` aligned on 16 bits
temporary workaround or horrible hack ? choose your side
"""
size = ctypes.sizeof(cls)
nb_qword = int((size + 8) / ctypes.sizeof(ULONGLONG))
buffer = (nb_qword * ULONGLONG)()
struct_address = ctypes.addressof(buffer)
if (struct_address & 0xf) not in [0, 8]:
raise ValueError("ULONGLONG array not aligned on 8")
if (struct_address & 0xf) == 8:
struct_address += 8
self = cls.from_address(struct_address)
# Keep the raw buffer alive
self._buffer = buffer
return self
def bitness():
"""Return 32 or 64"""
import platform
bits = platform.architecture()[0]
return int(bits[:2])
if bitness() == 32:
ECONTEXT = ECONTEXT32
else:
ECONTEXT = ECONTEXT64
class EEXCEPTION_POINTERS(ctypes.Structure):
_fields_ = [
("ExceptionRecord", ctypes.POINTER(EEXCEPTION_RECORD)),
("ContextRecord", ctypes.POINTER(ECONTEXT)),
]
def dump(self):
"""Dump (print) the EEXCEPTION_POINTERS"""
record = self.ExceptionRecord[0]
print("Dumping Exception: ")
print(" ExceptionCode = {0} at {1}".format(record.ExceptionCode, hex(record.ExceptionAddress)))
regs = self.ContextRecord[0].regs()
for name, value in regs:
print(" {0} -> {1}".format(name, hex(value)))
class VectoredException(object):
"""A decorator that create a callable which can be passed to :func:`AddVectoredExceptionHandler`"""
func_type = ctypes.WINFUNCTYPE(ctypes.c_uint, ctypes.POINTER(EEXCEPTION_POINTERS))
def __new__(cls, func):
self = object.__new__(cls)
self.func = func
v = self.func_type(self.decorator)
v.self = self
return v
def decorator(self, exception_pointers):
try:
return self.func(exception_pointers)
except BaseException as e:
import traceback
print("Ignored Python Exception in Vectored Exception: {0}".format(e))
traceback.print_exc()
return windef.EXCEPTION_CONTINUE_SEARCH
class VectoredExceptionHandler(object):
def __init__(self, pos, handler):
self.handler = VectoredException(handler)
self.pos = pos
def __enter__(self):
self.value = windows.winproxy.AddVectoredExceptionHandler(self.pos, self.handler)
return self
def __exit__(self, exc_type, exc_value, traceback):
windows.winproxy.RemoveVectoredExceptionHandler(self.value)
return False
class DumpContextOnException(VectoredExceptionHandler):
def __init__(self, exit=False):
self.exit = exit
super(DumpContextOnException, self).__init__(self.print_context_result)
def print_context_result(self, exception_pointers):
except_record = exception_pointers[0].ExceptionRecord[0]
exception_pointers[0].dump()
sys.stdout.flush()
if self.exit:
windows.current_process.exit()
return 0
@@ -1,63 +0,0 @@
import os.path
import windows
import windows.generated_def as gdef
from windows import security
from windows import utils
class WinFile(object):
def __init__(self, filename=None, handle=None):
if not filename and handle:
raise ValueError("File constructor should be given a filename OR handle")
self.filename = filename
if handle:
self._handle = handle
self._file = utils.create_file_from_handle(self.handle)
@utils.fixedproperty
def file(self):
assert not getattr(self, "_handle", None)
return open(self.filename, "r")
# We do not close the handle on __del__ -> the destructor of file will do it ?
# BUt in this case handle without a file will NOT close it..
@utils.fixedproperty
def handle(self):
if os.path.isdir(self.filename):
return windows.utils.create_file(self.filename, share=gdef.FILE_SHARE_READ | gdef.FILE_SHARE_WRITE, flags=gdef.FILE_FLAG_BACKUP_SEMANTICS)
else:
file = self.file
return utils.get_handle_from_file(file)
def get_security_descriptor(self, query_sacl=False, flags=security.SecurityDescriptor.DEFAULT_SECURITY_INFORMATION):
return security.SecurityDescriptor.from_handle(self.handle, query_sacl=query_sacl, flags=flags, obj_type="file")
def set_security_descriptor(self, sd):
flags = 0
if sd.owner:
flags |= gdef.OWNER_SECURITY_INFORMATION
if sd.group:
flags |= gdef.GROUP_SECURITY_INFORMATION
if sd.dacl:
flags |= gdef.DACL_SECURITY_INFORMATION
if sd.sacl:
flags |= gdef.SACL_SECURITY_INFORMATION
# Check Mandatory label ?
handle = windows.utils.create_file(self.filename, access=gdef.GENERIC_READ|gdef.WRITE_DAC, share=gdef.FILE_SHARE_READ | gdef.FILE_SHARE_WRITE, flags=gdef.FILE_FLAG_BACKUP_SEMANTICS)
return windows.winproxy.SetSecurityInfo(handle, gdef.SE_KERNEL_OBJECT, flags, sd.owner, sd.group, sd.dacl, sd.sacl)
security_descriptor = property(get_security_descriptor, set_security_descriptor)
@classmethod
def from_file(cls):
handle = utils.get_handle_from_file(file)
self = cls(filename=file.name, handle=handle)
self._file = file
return self
@@ -1,196 +0,0 @@
import os
import ctypes
import windows
from windows import winproxy
from windows.generated_def import windef
import windows.generated_def as gdef
current_process_pid = os.getpid()
class BaseSystemHandle(object):
# Big bypass to prevent missing reference at programm exit..
_close_function = ctypes.WinDLL("kernel32").CloseHandle
"""A handle of the system"""
@windows.utils.fixedpropety
def process(self):
"""The process possessing the handle
:type: :class:`WinProcess <windows.winobject.process.WinProcess>`"""
# "TODO: something smart ? :D"
# return [p for p in windows.system.processes if p.pid == self.dwProcessId][0]
return windows.WinProcess(pid=self.dwProcessId)
@property
def pid(self):
return self.dwProcessId
@property
def value(self):
return self.wValue
@windows.utils.fixedpropety
def name(self):
"""The name of the handle
:type: :class:`str`"""
return self._get_object_name()
@windows.utils.fixedpropety
def type(self):
"""The type of the handle
:type: :class:`str`"""
return self._get_object_type()
@property
def infos(self):
"""TODO: DOC"""
return self._get_object_basic_infos()
def _get_object_name(self):
lh = self.local_handle
size_needed = gdef.DWORD()
yyy = ctypes.c_buffer(0x1000)
winproxy.NtQueryObject(lh, gdef.ObjectNameInformation, ctypes.byref(yyy), ctypes.sizeof(yyy), ctypes.byref(size_needed))
return gdef.LSA_UNICODE_STRING.from_buffer_copy(yyy[:size_needed.value]).str
def _get_object_type(self):
lh = self.local_handle
xxx = gdef.PUBLIC_OBJECT_TYPE_INFORMATION()
size_needed = gdef.DWORD()
try:
winproxy.NtQueryObject(lh, gdef.ObjectTypeInformation, ctypes.byref(xxx), ctypes.sizeof(xxx), ctypes.byref(size_needed))
except WindowsError as e:
if e.code != gdef.STATUS_INFO_LENGTH_MISMATCH:
raise
size = size_needed.value
buffer = ctypes.c_buffer(size)
winproxy.NtQueryObject(lh, gdef.ObjectTypeInformation, buffer, size, ctypes.byref(size_needed))
xxx = gdef.PUBLIC_OBJECT_TYPE_INFORMATION.from_buffer_copy(buffer)
return xxx.TypeName.str
def _get_object_basic_infos(self):
pass
lh = self.local_handle
size_needed = gdef.DWORD()
basic_infos = gdef.PUBLIC_OBJECT_BASIC_INFORMATION()
winproxy.NtQueryObject(lh, gdef.ObjectBasicInformation, ctypes.byref(basic_infos), ctypes.sizeof(basic_infos), ctypes.byref(size_needed))
return basic_infos
@windows.utils.fixedpropety
def local_handle(self):
"""A local copy of the handle, acquired with ``DuplicateHandle``
:type: :class:`int`"""
if self.dwProcessId == windows.current_process.pid:
return self.wValue
res = gdef.HANDLE()
winproxy.DuplicateHandle(self.process.handle, self.wValue, windows.current_process.handle, ctypes.byref(res), dwOptions=gdef.DUPLICATE_SAME_ACCESS)
return res.value
def description(self):
stype = self.type
descr_func = getattr(self, "description_" + stype, None)
if descr_func is None:
return None
return descr_func()
def description_Process(self):
proc = windows.WinProcess(handle=self.wValue)
res = str(proc)
del proc._handle
return res
def description_Thread(self):
thread = windows.WinThread(handle=self.wValue)
res = str(thread)
del thread._handle
return res
def __repr__(self):
return "<{0} value=<0x{1:x}> in process pid={2}>".format(type(self).__name__, self.wValue, self.dwProcessId)
def __del__(self):
if self.dwProcessId == current_process_pid:
return
if hasattr(self, "_local_handle"):
return self._close_function(self._local_handle)
class Handle(gdef.SYSTEM_HANDLE, BaseSystemHandle):
pass
class HandleWow64(gdef.SYSTEM_HANDLE64, BaseSystemHandle):
pass # For wow64 process
def enumerate_handles():
if windows.current_process.is_wow_64:
return enumerate_handles_syswow64()
size_needed = gdef.ULONG()
# Should at least be sizeof(gdef.SYSTEM_HANDLE_INFORMATION)
tmp_buffer = windows.utils.BUFFER(gdef.SYSTEM_HANDLE_INFORMATION)()
try:
winproxy.NtQuerySystemInformation(gdef.SystemHandleInformation, tmp_buffer, tmp_buffer.real_size, ReturnLength=ctypes.byref(size_needed))
except WindowsError as e:
pass
size = size_needed.value + 0x1000 # In case we have some more handle created
buf = windows.utils.BUFFER(gdef.SYSTEM_HANDLE_INFORMATION)(size=size)
size_needed.value = 0
winproxy.NtQuerySystemInformation(gdef.SystemHandleInformation, buf, buf.real_size, ReturnLength=ctypes.byref(size_needed))
handle_array = windows.utils.resized_array(buf[0].Handles, buf[0].HandleCount, Handle)
return list(handle_array)
def enumerate_handles_syswow64():
size_needed = gdef.ULONG()
# Should at least be sizeof(gdef.SYSTEM_HANDLE_INFORMATION)
tmp_buffer = windows.utils.BUFFER(gdef.SYSTEM_HANDLE_INFORMATION64)()
try:
windows.syswow64.NtQuerySystemInformation_32_to_64(gdef.SystemHandleInformation, tmp_buffer, tmp_buffer.real_size, ReturnLength=ctypes.byref(size_needed))
except WindowsError as e:
pass
size = size_needed.value + 0x1000 # In case we have some more handle created
buf = windows.utils.BUFFER(gdef.SYSTEM_HANDLE_INFORMATION64)(size=size)
size_needed.value = 0
windows.syswow64.NtQuerySystemInformation_32_to_64(gdef.SystemHandleInformation, buf, buf.real_size, ReturnLength=ctypes.byref(size_needed))
handle_array = windows.utils.resized_array(buf[0].Handles, buf[0].HandleCount, HandleWow64)
return list(handle_array)
def enumerate_type():
"WIP: DO NOT USE"
size_needed = DWORD()
fsize = 8
fbuffer = ctypes.c_buffer(fsize)
try:
winproxy.NtQueryObject(None, gdef.ObjectTypesInformation, fbuffer, fsize, ctypes.byref(size_needed))
except WindowsError as e:
if e.code != STATUS_INFO_LENGTH_MISMATCH:
raise
else:
# We had enought memory ?
return
# Looks like the Wow64 syscall emulation is broken :D
# It write AFTER the buffer if we are a wow64 process :D
# So better allocate a standalone buffer (triggering a ACCESS_VIOLATION) that corrupting the heap
# This is a worst case scenario, as we allocation more space it should not happen !
size = size_needed.value + 0x200
size_needed.value = 0
with windows.current_process.allocated_memory(size, gdef.PAGE_READWRITE) as buffer_base:
winproxy.NtQueryObject(None, gdef.ObjectTypesInformation, buffer_base, size, ctypes.byref(size_needed))
# Cache some exceptions ?
# Parse the buffer data in-place as string are addr-dependant
types_info = gdef.OBJECT_TYPES_INFORMATION.from_address(buffer_base)
offset = ctypes.sizeof(gdef.PVOID) # Looks like the size of the struct is PTR aligned as the struct is follower by other stuff
for i in range(types_info.NumberOfTypes):
info = gdef.PUBLIC_OBJECT_TYPE_INFORMATION.from_address(buffer_base + offset)
yield info
offset += ctypes.sizeof(gdef.PUBLIC_OBJECT_TYPE_INFORMATION) + info.TypeName.MaximumLength
if offset % ctypes.sizeof(gdef.PVOID):
offset += ctypes.sizeof(gdef.PVOID) - (offset % ctypes.sizeof(gdef.PVOID))
# End-of ctx-manager
return
@@ -1,447 +0,0 @@
import windows
import ctypes
import socket
import struct
from windows import winproxy
import windows.generated_def as gdef
from windows.com import interfaces as cominterfaces
from windows.generated_def.winstructs import *
from windows.generated_def.windef import *
class TCP4Connection(MIB_TCPROW_OWNER_PID):
"""A TCP4 socket (connected or listening)"""
@property
def established(self):
"""``True`` if connection is established else it's a listening socket"""
return self.dwState == MIB_TCP_STATE_ESTAB
@property
def remote_port(self):
""":type: :class:`int`"""
if not self.established:
return None
return socket.ntohs(self.dwRemotePort)
@property
def local_port(self):
""":type: :class:`int`"""
return socket.ntohs(self.dwLocalPort)
@property
def local_addr(self):
"""Local address IP (x.x.x.x)
:type: :class:`str`"""
return socket.inet_ntoa(struct.pack("<I", self.dwLocalAddr))
@property
def remote_addr(self):
"""remote address IP (x.x.x.x)
:type: :class:`str`"""
if not self.established:
return None
return socket.inet_ntoa(struct.pack("<I", self.dwRemoteAddr))
@property
def remote_proto(self):
"""Identification of the protocol associated with the remote port.
Equals ``remote_port`` if no protocol is associated with it.
:type: :class:`str` or :class:`int`
"""
try:
return socket.getservbyport(self.remote_port, 'tcp')
except socket.error:
return self.remote_port
@property
def remote_host(self):
"""Identification of the remote hostname.
Equals ``remote_addr`` if the resolution fails
:type: :class:`str` or :class:`int`
"""
try:
return socket.gethostbyaddr(self.remote_addr)
except socket.error:
return self.remote_addr
def close(self):
"""Close the connection <require elevated process>"""
closing = MIB_TCPROW()
closing.dwState = MIB_TCP_STATE_DELETE_TCB
closing.dwLocalAddr = self.dwLocalAddr
closing.dwLocalPort = self.dwLocalPort
closing.dwRemoteAddr = self.dwRemoteAddr
closing.dwRemotePort = self.dwRemotePort
return winproxy.SetTcpEntry(ctypes.byref(closing))
def __repr__(self):
if not self.established:
return "<TCP IPV4 Listening socket on {0}:{1}>".format(self.local_addr, self.local_port)
return "<TCP IPV4 Connection {s.local_addr}:{s.local_port} -> {s.remote_addr}:{s.remote_port}>".format(s=self)
class TCP6Connection(MIB_TCP6ROW_OWNER_PID):
"""A TCP6 socket (connected or listening)"""
@staticmethod
def _str_ipv6_addr(addr):
return ":".join(c.encode('hex') for c in addr)
@property
def established(self):
"""``True`` if connection is established else it's a listening socket"""
return self.dwState == MIB_TCP_STATE_ESTAB
@property
def remote_port(self):
""":type: :class:`int`"""
if not self.established:
return None
return socket.ntohs(self.dwRemotePort)
@property
def local_port(self):
""":type: :class:`int`"""
return socket.ntohs(self.dwLocalPort)
@property
def local_addr(self):
"""Local address IP
:type: :class:`str`"""
return self._str_ipv6_addr(self.ucLocalAddr)
@property
def remote_addr(self):
"""remote address IP
:type: :class:`str`"""
if not self.established:
return None
return self._str_ipv6_addr(self.ucRemoteAddr)
@property
def remote_proto(self):
"""Equals to ``self.remote_port`` for Ipv6"""
return self.remote_port
@property
def remote_host(self):
"""Equals to ``self.remote_addr`` for Ipv6"""
return self.remote_addr
def close(self):
raise NotImplementedError("Closing IPV6 connection non implemented")
def __repr__(self):
if not self.established:
return "<TCP IPV6 Listening socket on {0}:{1}>".format(self.local_addr, self.local_port)
return "<TCP IPV6 Connection {0}:{1} -> {2}:{3}>".format(self.local_addr, self.local_port, self.remote_addr, self.remote_port)
def get_MIB_TCPTABLE_OWNER_PID_from_buffer(buffer):
x = windows.generated_def.winstructs.MIB_TCPTABLE_OWNER_PID.from_buffer(buffer)
nb_entry = x.dwNumEntries
class _GENERATED_MIB_TCPTABLE_OWNER_PID(ctypes.Structure):
_fields_ = [
("dwNumEntries", DWORD),
("table", TCP4Connection * nb_entry),
]
return _GENERATED_MIB_TCPTABLE_OWNER_PID.from_buffer(buffer)
def get_MIB_TCP6TABLE_OWNER_PID_from_buffer(buffer):
x = windows.generated_def.winstructs.MIB_TCP6TABLE_OWNER_PID.from_buffer(buffer)
nb_entry = x.dwNumEntries
# Struct _MIB_TCP6TABLE_OWNER_PID definitions
class _GENERATED_MIB_TCP6TABLE_OWNER_PID(Structure):
_fields_ = [
("dwNumEntries", DWORD),
("table", TCP6Connection * nb_entry),
]
return _GENERATED_MIB_TCP6TABLE_OWNER_PID.from_buffer(buffer)
class Firewall(cominterfaces.INetFwPolicy2):
"""The windows firewall"""
@property
def rules(self):
"""The rules of the firewall
:type: [:class:`FirewallRule`] -- A list of rule
"""
ifw_rules = cominterfaces.INetFwRules()
self.get_Rules(ifw_rules)
nb_rules = gdef.LONG()
ifw_rules.get_Count(nb_rules)
unknw = cominterfaces.IUnknown()
ifw_rules.get__NewEnum(unknw)
pVariant = cominterfaces.IEnumVARIANT()
unknw.QueryInterface(pVariant.IID, pVariant)
count = gdef.ULONG()
var = windows.com.Variant()
rules = []
for i in range(nb_rules.value):
pVariant.Next(1, var, count)
if not count.value:
break
rule = FirewallRule()
idisp = var.asdispatch
idisp.QueryInterface(rule.IID, rule)
rules.append(rule)
return rules
@property
def current_profile_types(self):
"""Mask of the profiles currently enabled
:type: :class:`long`
"""
cpt = gdef.LONG()
self.get_CurrentProfileTypes(cpt)
return cpt.value
@property
def enabled(self):
"""A maping of the active firewall profiles
{
``NET_FW_PROFILE_TYPE2_.NET_FW_PROFILE2_DOMAIN(0x1L)``: ``True`` or ``False``,
``NET_FW_PROFILE_TYPE2_.NET_FW_PROFILE2_PRIVATE(0x2L)``: ``True`` or ``False``,
``NET_FW_PROFILE_TYPE2_.NET_FW_PROFILE2_PUBLIC(0x4L)``: ``True`` or ``False``,
}
:type: :class:`dict`
"""
profiles = [gdef.NET_FW_PROFILE2_DOMAIN, gdef.NET_FW_PROFILE2_PRIVATE, gdef.NET_FW_PROFILE2_PUBLIC]
return {prof: self.enabled_for_profile_type(prof) for prof in profiles}
def enabled_for_profile_type(self, profile_type):
enabled = gdef.VARIANT_BOOL()
self.get_FirewallEnabled(profile_type, enabled)
return enabled.value
class FirewallRule(cominterfaces.INetFwRule):
"""A rule of the firewall"""
@property
def name(self):
"""Name of the rule
:type: :class:`unicode`
"""
name = gdef.BSTR()
self.get_Name(name)
return name.value
@property
def description(self):
"""Description of the rule
:type: :class:`unicode`
"""
description = gdef.BSTR()
self.get_Description(description)
return description.value
@property
def application_name(self):
"""Name of the application to which apply the rule
:type: :class:`unicode`
"""
applicationname = gdef.BSTR()
self.get_ApplicationName(applicationname)
return applicationname.value
@property
def service_name(self):
"""Name of the service to which apply the rule
:type: :class:`unicode`
"""
servicename = gdef.BSTR()
self.get_ServiceName(servicename)
return servicename.value
@property
def protocol(self):
"""Protocol to which apply the rule
:type: :class:`long`
"""
protocol = gdef.LONG()
self.get_Protocol(protocol)
return protocol.value
@property
def local_address(self):
"""Local address of the rule
:type: :class:`unicode`
"""
local_address = gdef.BSTR()
self.get_LocalAddresses(local_address)
return local_address.value
@property
def remote_address(self):
"""Remote address of the rule
:type: :class:`unicode`
"""
remote_address = gdef.BSTR()
self.get_RemoteAddresses(remote_address)
return remote_address.value
@property
def direction(self):
"""Direction of the rule, values might be:
* ``NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN(0x1L)``
* ``NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_OUT(0x2L)``
subclass of :class:`long`
"""
direction = gdef.NET_FW_RULE_DIRECTION()
self.get_Direction(direction)
return direction.value
@property
def interface_types(self):
"""Types of interface of the rule
:type: :class:`unicode`
"""
interface_type = gdef.BSTR()
self.get_InterfaceTypes(interface_type)
return interface_type.value
@property
def local_port(self):
"""Local port of the rule
:type: :class:`unicode`
"""
local_port = gdef.BSTR()
self.get_LocalPorts(local_port)
return local_port.value
@property
def remote_port(self):
"""Remote port of the rule
:type: :class:`unicode`
"""
remote_port = gdef.BSTR()
self.get_RemotePorts(remote_port)
return remote_port.value
@property
def action(self):
"""Action of the rule, values might be:
* ``NET_FW_ACTION_.NET_FW_ACTION_BLOCK(0x0L)``
* ``NET_FW_ACTION_.NET_FW_ACTION_ALLOW(0x1L)``
subclass of :class:`long`
"""
action = gdef.NET_FW_ACTION()
self.get_Action(action)
return action.value
@property
def enabled(self):
"""``True`` if rule is enabled"""
enabled = gdef.VARIANT_BOOL()
self.get_Enabled(enabled)
return enabled.value
@property
def grouping(self):
"""Grouping of the rule
:type: :class:`unicode`
"""
grouping = gdef.BSTR()
self.get_RemotePorts(grouping)
return grouping.value
@property
def icmp_type_and_code(self):
icmp_type_and_code = gdef.BSTR()
self.get_RemotePorts(icmp_type_and_code)
return icmp_type_and_code.value
def __repr__(self):
return u'<{0} "{1}">'.format(type(self).__name__, self.name).encode("ascii", errors='backslashreplace')
class Network(object):
NetFwPolicy2 = windows.com.IID.from_string("E2B3C97F-6AE1-41AC-817A-F6F92166D7DD")
@property
def firewall(self):
"""The firewall of the system
:type: :class:`Firewall`
"""
windows.com.init()
firewall = Firewall()
windows.com.create_instance(self.NetFwPolicy2, firewall)
return firewall
@staticmethod
def _get_tcp_ipv4_sockets():
size = ctypes.c_uint(0)
try:
winproxy.GetExtendedTcpTable(None, ctypes.byref(size), ulAf=AF_INET)
except winproxy.WinproxyError:
pass # Allow us to set size to the needed value
buffer = (ctypes.c_char * size.value)()
winproxy.GetExtendedTcpTable(buffer, ctypes.byref(size), ulAf=AF_INET)
t = get_MIB_TCPTABLE_OWNER_PID_from_buffer(buffer)
return list(t.table)
@staticmethod
def _get_tcp_ipv6_sockets():
size = ctypes.c_uint(0)
try:
winproxy.GetExtendedTcpTable(None, ctypes.byref(size), ulAf=AF_INET6)
except winproxy.WinproxyError:
pass # Allow us to set size to the needed value
buffer = (ctypes.c_char * size.value)()
winproxy.GetExtendedTcpTable(buffer, ctypes.byref(size), ulAf=AF_INET6)
t = get_MIB_TCP6TABLE_OWNER_PID_from_buffer(buffer)
return list(t.table)
ipv4 = property(lambda self: self._get_tcp_ipv4_sockets())
"""List of TCP IPv4 socket (connection and listening)
:type: [:class:`TCP4Connection`]"""
ipv6 = property(lambda self: self._get_tcp_ipv6_sockets())
"""List of TCP IPv6 socket (connection and listening)
:type: [:class:`TCP6Connection`]
"""
@@ -1,226 +0,0 @@
import os.path
import ctypes
from collections import namedtuple
import windows
from windows import winproxy
import windows.generated_def as gdef
def query_link(linkpath):
"""Resolve the link object with path ``linkpath``"""
obj_attr = gdef.OBJECT_ATTRIBUTES()
obj_attr.Length = ctypes.sizeof(obj_attr)
obj_attr.RootDirectory = 0
obj_attr.ObjectName = ctypes.pointer(gdef.LSA_UNICODE_STRING.from_string(linkpath))
obj_attr.Attributes = gdef.OBJ_CASE_INSENSITIVE
obj_attr.SecurityDescriptor = 0
obj_attr.SecurityQualityOfService = 0
res = gdef.HANDLE()
x = winproxy.NtOpenSymbolicLinkObject(res, gdef.DIRECTORY_QUERY | gdef.READ_CONTROL , obj_attr)
v = gdef.LSA_UNICODE_STRING.from_size(1000)
s = gdef.ULONG()
try:
winproxy.NtQuerySymbolicLinkObject(res, v, s)
except WindowsError as e:
if not (e.winerror & 0xffffffff) == gdef.STATUS_BUFFER_TOO_SMALL:
raise
# If our initial 1000 buffer is not enought (improbable) retry with correct size
v = gdef.LSA_UNICODE_STRING.from_size(s.value)
winproxy.NtQuerySymbolicLinkObject(res, v, s)
return v.str
class KernelObject(object):
"""Represent an object in the Object Manager namespace"""
def __init__(self, path, name, type=None):
self.path = path
self.name = name
if path and not path.endswith("\\"):
path += "\\"
self.fullname = path + name
self.type = type
@property
def target(self):
"""Resolve the target of a symbolic link object.
:rtype: :class:`str` or None if object is not a link
"""
try:
return query_link(self.fullname)
except windows.generated_def.ntstatus.NtStatusException as e:
if e.code != gdef.STATUS_OBJECT_TYPE_MISMATCH:
raise
return None
def items(self):
"""Return the list of tuple (object's name, object) in the current directory object.
:rtype: [(:class:`str`, :class:`KernelObject`)] -- A list of tuple
.. note::
the :class:`KernelObject` must be of type ``Directory`` or
it will raise :class:`~windows.generated_def.ntstatus.NtStatusException` with
code :data:`~windows.generated_def.STATUS_OBJECT_TYPE_MISMATCH`
"""
path = self.fullname
return [(name, KernelObject(path, name, typename)) for name, typename in self._directory_query_generator()]
def keys(self):
"""Return the list of objects' name in the current directory object.
:rtype: [:class:`str`] -- A list of name
.. note::
the :class:`KernelObject` must be of type ``Directory`` or
it will raise :class:`~windows.generated_def.ntstatus.NtStatusException` with
code :data:`~windows.generated_def.STATUS_OBJECT_TYPE_MISMATCH`
"""
return list(self)
def values(self):
"""Return the list of objects in the current directory object.
:rtype: [:class:`KernelObject`] -- A list of object
.. note::
the :class:`KernelObject` must be of type ``Directory`` or
it will raise :class:`~windows.generated_def.ntstatus.NtStatusException` with
code :data:`~windows.generated_def.STATUS_OBJECT_TYPE_MISMATCH`
"""
path = self.fullname
return [KernelObject(path, name, typename) for name, typename in self._directory_query_generator()]
def _open_directory(self):
path = self.fullname
utf16_len = len(path) * 2
obj_attr = gdef.OBJECT_ATTRIBUTES()
obj_attr.Length = ctypes.sizeof(obj_attr)
obj_attr.RootDirectory = None
obj_attr.ObjectName = ctypes.pointer(gdef.LSA_UNICODE_STRING.from_string(path))
obj_attr.Attributes = gdef.OBJ_CASE_INSENSITIVE
obj_attr.SecurityDescriptor = 0
obj_attr.SecurityQualityOfService = 0
res = gdef.HANDLE()
winproxy.NtOpenDirectoryObject(res, gdef.DIRECTORY_QUERY | gdef.READ_CONTROL , obj_attr)
return res.value
def _directory_query_generator(self):
handle = self._open_directory()
size = 0x1000
buf = ctypes.c_buffer(size)
rres = gdef.ULONG()
ctx = gdef.ULONG()
while True:
try:
# Restart == True has we don't save the buffer when resizing it for next call
winproxy.NtQueryDirectoryObject(handle, buf, size, False, True, ctypes.byref(ctx), rres)
break
except gdef.NtStatusException as e:
if e.code == gdef.STATUS_NO_MORE_ENTRIES:
return
if e.code == gdef.STATUS_MORE_ENTRIES:
# If the call did not extrack all data: retry with bigger buffer
size *= 2
buf = ctypes.c_buffer(size)
continue
raise
# Function -> _extract_objects ?
t = gdef.OBJECT_DIRECTORY_INFORMATION.from_buffer(buf)
t = gdef.POBJECT_DIRECTORY_INFORMATION(t)
res = {}
for v in t:
if v.Name.Buffer is None:
break
yield v.Name.str, v.TypeName.str
def __iter__(self):
"""Iter over the list of name in the Directory object.
:yield: :class:`str` -- The names of objects in the directory.
.. note::
the :class:`KernelObject` must be of type ``Directory`` or
it will raise :class:`~windows.generated_def.ntstatus.NtStatusException` with
code :data:`~windows.generated_def.STATUS_OBJECT_TYPE_MISMATCH`
"""
return (name for name, type in self._directory_query_generator())
def __repr__(self):
return """<{0} "{1}" (type="{2}")>""".format(type(self).__name__, self.fullname, self.type)
def get(self, name):
"""Retrieve the object ``name`` in the current directory.
:rtype: :class:`KernelObject`
"""
for objname, objtype in self._directory_query_generator():
if objname.lower() == name.lower():
return KernelObject(self.fullname, name, objtype)
raise KeyError("Could not find WinObject <{0}> under <{1}>".format(name, self.fullname))
def __getitem__(self, name):
"""Query object ``name`` from the directory, split and subquery on ``\\``::
>>> obj
<KernelObject "\Windows" (type="Directory")>
>>> obj["WindowStations"]["WinSta0"]
<KernelObject "\Windows\WindowStations" (type="Directory")>
>>> obj["WindowStations\\WinSta0"]
<KernelObject "\Windows\WindowStations" (type="Directory")>
:rtype: :class:`KernelObject`
:raise: :class:`KeyError` if ``name`` can not be found.
"""
if name.startswith("\\"):
# Are we the root directory ?
if not self.fullname == "\\" :
raise ValueError("Cannot query an object path begining by '\\' from an object other than '\\'")
elif name == "\\": # Ask for root ? return ourself
return self
else:
name = name[1:] # Strip the leading \ and go to normal case
obj = self
for part in name.split("\\"):
try:
obj = obj.get(part)
except gdef.NtStatusException as e:
if e.code == gdef.STATUS_OBJECT_TYPE_MISMATCH:
raise KeyError("Could not find object <{0}> under <{1}> because it is a <{2}>".format(
part, obj.name, obj.type))
raise # Something smart to do ?
return obj
class ObjectManager(object):
"""Represent the object manager.
.. note::
For now, it only offers the ``root`` :class:`KernelObject`. But I want a ``manager`` object accessible
from ``windows.system`` just like other API and not directly the ``root`` directory.
"""
@property
def root(self):
"""The root ``\\`` Directory
:type: :class:`KernelObject` -- The root :class:`KernelObject`
"""
return KernelObject("", "\\", "Directory")
def __getitem__(self, name):
"""Query ``name`` from the root ``\\`` directory::
object_manager["RPC Control"]["lsasspirpc"]
object_manager[r"\\RPC Control\\lsasspirpc"]
:rtype: :class:`KernelObject`
"""
return self.root[name]
File diff suppressed because it is too large Load Diff
@@ -1,450 +0,0 @@
import sys
import ctypes
import itertools
import struct
from collections import namedtuple, defaultdict
import windows
from windows.dbgprint import dbgprint
import windows.generated_def as gdef
from windows import winproxy
from windows.pycompat import basestring, int_types, is_py3
WENCODING = "utf-16-le"
# So _winreg does not handle unicode stuff in Py2 :(
# Need to rewrite everything to get it working with unicode
class WinRegistryKey(gdef.HKEY):
_close_function = staticmethod(winproxy.RegCloseKey)
def __del__(self):
if sys is None or sys.path is None: # Late shutdown (not sur winproxy is still up)
return
if self: # Not NULL handle ?
dbgprint(u"Closing registry key handle {0:#x}".format(self.value), 'REGISTRY')
self._close_function(self)
class ExpectWindowsError(object):
def __init__(self, errornumber):
self.errornumber = errornumber
def __enter__(self):
pass
def __exit__(self, etype, e, tb):
return (etype in (winproxy.WinproxyError, WindowsError) and e.winerror == self.errornumber)
# Translation reg-buffer <-> python methodes
def Reg2Py_QWORD(buffer, size):
return buffer.cast(gdef.PULONG64)[0]
def Py2Reg_QWORD(obj):
return struct.pack("<Q", obj)
def Reg2Py_DWORD(buffer, size):
# Check size ?
return buffer.cast(gdef.LPDWORD)[0]
def Py2Reg_DWORD(obj):
return struct.pack("<I", obj)
def Reg2Py_DWORD_BIG_ENDIAN(buffer, size):
# Check size ?
return (buffer[0] << 24) + (buffer[1] << 16) + (buffer[2] << 8) + buffer[3]
def Py2Reg_DWORD_BIG_ENDIAN(obj):
return struct.pack(">I", obj)
def Reg2Py_BINARY(buffer, size):
return bytes(bytearray(buffer[:size]))
def Py2Reg_BINARY(obj):
# latin-1 encoding if py3 & type is str ?
return obj
def Reg2Py_SZ(buffer, size):
# Buffer is UTF16. buffer is extended-buffer
if size == 0:
return u""
if buffer[size - 1] == 0 and buffer[size - 2] == 0:
# NULL TERMINATED: EASY
return buffer.as_wstring()
# Not null terminated: keep last byte
assert not size % 2
return (gdef.WCHAR * (size // 2)).from_buffer(buffer)[:]
def Py2Reg_SZ(obj):
return obj.encode(WENCODING)
def Reg2Py_Multi_SZ(buffer, size):
if not size:
return []
# Simple path
if is_py3:
rawstr = bytes(buffer)
else:
rawstr = "".join([chr(c) for c in buffer[:size]])
try:
unistr = rawstr.decode(WENCODING)
return unistr.rstrip(u"\x00").split(u"\x00")
except UnicodeDecodeError as e:
pass
# Complexe-path
# This is not some valide UTF-16
# Try our best to extract some stuff from raw
return rawstr.rstrip(b"\x00").split(b"\x00")
def Py2Reg_Multi_SZ(obj):
# Work on encoded values (to prevent str/unicode errors)
uni_list = [s.encode(WENCODING) for s in obj]
# Separate by UTF-16 NULL BYTE (2 \x00)
uni_str = b"\x00\x00".join(uni_list)
# Add UTF-16 NULL byte for final string + final UTF-16 \x00 (4 \x00)
return uni_str + b"\x00\x00\x00\x00"
DECODE_METHOD = 0
ENCODE_METHOD = 1
KNOWN_ENCODE_DECODE_METHODS = {
gdef.REG_SZ: (Reg2Py_SZ, Py2Reg_SZ),
gdef.REG_EXPAND_SZ: (Reg2Py_SZ, Py2Reg_SZ),
gdef.REG_MULTI_SZ: (Reg2Py_Multi_SZ, Py2Reg_Multi_SZ),
gdef.REG_DWORD: (Reg2Py_DWORD, Py2Reg_DWORD),
gdef.REG_DWORD_BIG_ENDIAN: (Reg2Py_DWORD_BIG_ENDIAN, Py2Reg_DWORD_BIG_ENDIAN),
gdef.REG_QWORD: (Reg2Py_QWORD, Py2Reg_QWORD),
# Binary formats
gdef.REG_LINK: (Reg2Py_BINARY, Py2Reg_BINARY), # TESTING
gdef.REG_BINARY: (Reg2Py_BINARY, Py2Reg_BINARY),
gdef.REG_NONE: (Reg2Py_BINARY, Py2Reg_BINARY),
}
# All unknown format are seens as binary data
UNKNOWM_FORMAT = (Reg2Py_BINARY, Py2Reg_BINARY)
ENCODE_DECODE_METHODS = defaultdict(lambda: UNKNOWM_FORMAT, KNOWN_ENCODE_DECODE_METHODS)
def decode_registry_buffer(type, buffer, size):
try:
return ENCODE_DECODE_METHODS[type][DECODE_METHOD](buffer, size)
except UnicodeDecodeError as e:
# Best effort if any decoding error happen
return "".join(chr(c) for c in buffer[:size])
KeyValue = namedtuple("KeyValue", ["name", "value", "type"])
"""A registry value (name, value, type)"""
class PyHKey(object):
"""A windows registry key"""
def __init__(self, surkey, name, sam=gdef.KEY_READ):
self.surkey = surkey
self.name = name
self.fullname = self.surkey.fullname + "\\" + self.name if self.name else self.surkey.name
self.sam = sam
self._phkey = None
#self.phkey
def __repr__(self):
return '<PyHKey "{0}">'.format(self.fullname)
def _open_key(self, handle, name, sam):
result = WinRegistryKey()
winproxy.RegOpenKeyExW(handle, name, 0, sam, result) # TODO: options REG_OPTION_OPEN_LINK
dbgprint(u"Opening registry key <{0}> (handle={1:#x})".format(name, result.value), "REGISTRY")
return result
def _create_key(self, parent, name, sam):
result = WinRegistryKey()
flags = 0
winproxy.RegCreateKeyExW(parent, name, 0, None, flags, sam, None, result, None)
dbgprint(u"Creating registry key <{0}> (handle={1:#x})".format(name, result.value), "REGISTRY")
return result
@property
def phkey(self):
if self._phkey is not None:
return self._phkey
try:
self._phkey = self._open_key(self.surkey.phkey, self.name, self.sam)
except WindowsError as e:
raise WindowsError(e.winerror, "Could not open registry key <{0}> ({1})".format(self.fullname, e.strerror))
return self._phkey
@property
def exists(self):
# May have been deleted in between
# So <self._phkey> tells use nothing
if self._phkey: # Not None + pointer not NULL
try:
self.get_key_size_info()
except WindowsError as e:
if e.winerror == gdef.ERROR_KEY_DELETED:
return False
raise
return True
try:
tmpphkey = self._open_key(self.surkey.phkey, self.name, gdef.KEY_READ)
except WindowsError as e:
return False
# tmpphkey will be garbage collected and auto-closed
return True
@property
def subkeys(self):
"""The subkeys of the registry key
:type: [:class:`PyHKey`] - A list of keys"""
res = []
with ExpectWindowsError(259):
default_name_size = 256 + 1
name_size = gdef.DWORD(default_name_size)
name_buffer = ctypes.create_unicode_buffer(name_size.value)
for i in itertools.count():
name_size.value = default_name_size
winproxy.RegEnumKeyExW(self.phkey, i, name_buffer, name_size, None, None, None, None)
res.append(name_buffer[:name_size.value]) # Will allow key name with \x00 inside
return [PyHKey(self, n) for n in res]
def get_key_size_info(self):
max_name_len = gdef.DWORD()
max_value_len = gdef.DWORD()
winproxy.RegQueryInfoKeyW(self.phkey, None, None, None, None, None, None, None, max_name_len, max_value_len, None, None)
return (max_name_len.value, max_value_len.value)
@property
def values(self):
"""The values of the registry key
:type: [:class:`KeyValue`] - A list of values"""
res = []
# Get max info keys
max_name_size, max_data_size = self.get_key_size_info()
# Null terminators
max_name_size += 1
max_data_size += 2
with ExpectWindowsError(259):
for i in itertools.count():
value_type = gdef.DWORD()
namesize = gdef.DWORD(max_name_size)
keyname = ctypes.create_unicode_buffer(namesize.value)
datasize = gdef.DWORD(max_data_size)
databuffer = windows.utils.BUFFER(gdef.BYTE, nbelt=datasize.value)()
# A value can have been added in-between.
# So recheck the size given by get_key_size_info :)
# But check 10 times max as RegEnumValueW may bug (seen) and always return ERROR_MORE_DATA even with enought size
for _ in range(10):
try:
winproxy.RegEnumValueW(self.phkey, i, keyname, namesize, None, value_type, databuffer, datasize)
break
except WindowsError as e:
if e.winerror != gdef.ERROR_MORE_DATA:
raise
# I found some strange Windows where even with a big enought buffer:
# - the data was filled
# - ERROR_MORE_DATA was returned
## To prevent such bug to trigger and infinite loop, two things
# - If the retuned namesize <= the passed keysize and keyname is not empty -> return the data`
# - Max 10 test to prevent Infinite loop
if ((namesize.value <= max_name_size) and (datasize.value <= max_data_size) and
(keyname[:namesize.value].count("\x00") < namesize.value)): # Not just 0 Zero ?
break
# Update the sizes / buffers & try again :)
max_name_size, max_data_size = self.get_key_size_info()
max_name_size = max(max_name_size + 1, namesize.value + 1) # namesize.value may be > to max_name_size apparently (guessed)
max_data_size = max(max_data_size + 2, datasize.value + 2) # datasize.value may be > to max_data_size apparently (seen)
namesize = gdef.DWORD(max_name_size)
keyname = ctypes.create_unicode_buffer(namesize.value)
datasize = gdef.DWORD(max_data_size)
databuffer = windows.utils.BUFFER(gdef.BYTE, nbelt=datasize.value)()
else:
# Probably a windows bug that prevent us from retrieving the data
# Raise something (thus preventing getting the other values..) ? ignore it ?
raise ValueError("Could not extract registry key values, problably a Windows/hook bug")
vobj = decode_registry_buffer(value_type.value, databuffer, datasize.value)
res.append(KeyValue(keyname.value, vobj, value_type.value))
return res
@property
def info(self):
# Need other stuff ?
nb_key = gdef.DWORD()
nb_values = gdef.DWORD()
last_modif = gdef.FILETIME()
winproxy.RegQueryInfoKeyW(self.phkey, None, None, None, nb_key, None, None, nb_values, None, None, None, last_modif)
return nb_key.value, nb_values.value, int(last_modif)
@property
def last_write(self):
return self.info[2]
def get(self, value_name):
"""Retrieves the value ``value_name``
:rtype: :class:`KeyValue`
"""
type = gdef.DWORD(0)
size = gdef.DWORD(0x100)
while True:
buffer = windows.utils.BUFFER(gdef.BYTE, nbelt=size.value)()
try:
winproxy.RegQueryValueExW(self.phkey, value_name, None, type, buffer, size)
break
except WindowsError as e:
if e.winerror != gdef.ERROR_MORE_DATA:
raise
size.value *= 2
buffer = windows.utils.BUFFER(gdef.BYTE, nbelt=size.value)()
continue
vobj = decode_registry_buffer(type.value, buffer, size.value)
return KeyValue(value_name, vobj, type.value)
def _guess_value_type(self, value):
if isinstance(value, basestring):
return gdef.REG_SZ
elif isinstance(value, int_types):
return gdef.REG_DWORD
# elif isinstance(value, (list, tuple)):
# if all(isinstance(v, basestring) in value):
# return _winreg.REG_MULTI_SZ
raise ValueError("Cannot guest registry type of value to set <{0}>".format(value))
def set(self, name, value, type=None):
"""Set the value for ``name`` to ``value``. if ``type`` is None try to guess items"""
if type is None:
type = self._guess_value_type(value)
buffer = ENCODE_DECODE_METHODS[type][ENCODE_METHOD](value)
if isinstance(buffer, bytes): # Should not be unicode at this point
buffer = windows.utils.BUFFER(gdef.BYTE).from_buffer_copy(buffer)
return winproxy.RegSetValueExW(self.phkey, name, 0, type, buffer, len(buffer))
def delete_value(self, name):
"""Delete the value with ``name``"""
return winproxy.RegDeleteValueW(self.phkey, name)
def open_subkey(self, name, sam=None):
"""Open the subkey ``name``
:rtype: :class:`PyHKey`
"""
if sam is None:
sam = self.sam
return PyHKey(self, name, sam)
def reopen(self, sam):
"""Reopen the registry key with a new ``sam``
:rtype: :class:`PyHKey`
"""
return PyHKey(self.surkey, self.name, sam)
def create(self):
"""Create the registry key"""
try:
self._phkey = self._create_key(self.surkey.phkey, self.name, self.sam)
except WindowsError as e:
raise WindowsError(e.winerror, "Could not create registry key <{0}> ({1})".format(self.fullname, e.strerror))
return self
def delete(self):
"""Delete the registry key"""
# Allow a 'recursive' param to empty before delete ?
try:
windows.winproxy.RegDeleteKeyExW(self.surkey.phkey, self.name, self.sam, 0)
except WindowsError as e:
raise WindowsError(e.winerror, "Could not delete registry key <{0}> ({1})".format(self.fullname, e.strerror))
return None
def empty(self):
windows.winproxy.RegDeleteTreeW(self.phkey, None)
def __setitem__(self, name, value):
rtype = None
if not (isinstance(value, basestring) or isinstance(value, int_types)):
value, rtype = value
return self.set(name, value, rtype)
__getitem__ = get
__delitem__ = delete_value
__call__ = open_subkey
class DummyPHKEY(object):
def __init__(self, phkey, name):
self.phkey = phkey
self.name = name
HKEY_LOCAL_MACHINE = PyHKey(DummyPHKEY(gdef.HKEY_LOCAL_MACHINE, "HKEY_LOCAL_MACHINE"), "", gdef.KEY_READ)
HKEY_CLASSES_ROOT = PyHKey(DummyPHKEY(gdef.HKEY_CLASSES_ROOT, "HKEY_CLASSES_ROOT"), "", gdef.KEY_READ )
HKEY_CURRENT_USER = PyHKey(DummyPHKEY(gdef.HKEY_CURRENT_USER, "HKEY_CURRENT_USER"), "", gdef.KEY_READ)
HKEY_DYN_DATA = PyHKey(DummyPHKEY(gdef.HKEY_DYN_DATA, "HKEY_DYN_DATA"), "", gdef.KEY_READ)
HKEY_PERFORMANCE_DATA = PyHKey(DummyPHKEY(gdef.HKEY_PERFORMANCE_DATA, "HKEY_PERFORMANCE_DATA"), "", gdef.KEY_READ)
HKEY_USERS = PyHKey(DummyPHKEY(gdef.HKEY_USERS, "HKEY_USERS"), "", gdef.KEY_READ )
class Registry(object):
"""The ``Windows`` registry"""
registry_base_keys = {
"HKEY_LOCAL_MACHINE" : HKEY_LOCAL_MACHINE,
"HKEY_CLASSES_ROOT" : HKEY_CLASSES_ROOT,
"HKEY_CURRENT_USER" : HKEY_CURRENT_USER,
"HKEY_DYN_DATA" : HKEY_DYN_DATA,
"HKEY_PERFORMANCE_DATA": HKEY_PERFORMANCE_DATA,
"HKEY_USERS" : HKEY_USERS
}
def __init__(self, sam=gdef.KEY_READ):
self.sam = sam
@classmethod
def reopen(cls, sam):
"""Return a new :class:`Registry` using ``sam`` as the new default
:rtype: :class:`Registry`
"""
return cls(sam)
def __call__(self, name, sam=None):
"""Get a registry key::
registry(r"HKEY_LOCAL_MACHINE\\Software")
registry("HKEY_LOCAL_MACHINE")("Software")
:rtype: :class:`PyHKey`
"""
if sam is None:
sam = self.sam
if name in self.registry_base_keys:
key = self.registry_base_keys[name]
if sam != key.sam:
key = key.reopen(sam)
return key
if "\\" not in name:
raise ValueError("Unknow registry base key <{0}>".format(name))
base_name, subkey = name.split("\\", 1)
if base_name not in self.registry_base_keys:
raise ValueError("Unknow registry base key <{0}>".format(base_name))
return self.registry_base_keys[base_name](subkey, sam)
@@ -1,191 +0,0 @@
import ctypes
import windows
from collections import namedtuple
from contextlib import contextmanager
from windows import utils
from windows.pycompat import int_types
import windows.generated_def as gdef
from windows.generated_def import *
from windows import security
from windows.pycompat import basestring
"""
``type`` might be one of:
* ``SERVICE_KERNEL_DRIVER(0x1L)``
* ``SERVICE_FILE_SYSTEM_DRIVER(0x2L)``
* ``SERVICE_WIN32_OWN_PROCESS(0x10L)``
* ``SERVICE_WIN32_SHARE_PROCESS(0x20L)``
* ``SERVICE_INTERACTIVE_PROCESS(0x100L)``
``state`` might be one of:
* ``SERVICE_STOPPED(0x1L)``
* ``SERVICE_START_PENDING(0x2L)``
* ``SERVICE_STOP_PENDING(0x3L)``
* ``SERVICE_RUNNING(0x4L)``
* ``SERVICE_CONTINUE_PENDING(0x5L)``
* ``SERVICE_PAUSE_PENDING(0x6L)``
* ``SERVICE_PAUSED(0x7L)``
``flags`` might be one of:
* ``0``
* ``SERVICE_RUNS_IN_SYSTEM_PROCESS(0x1L)``
"""
class ServiceManager(utils.AutoHandle):
"""An object to query, list and explore services"""
def _get_handle(self):
return windows.winproxy.OpenSCManagerW(dwDesiredAccess=gdef.MAXIMUM_ALLOWED)
def open_service(self, name, access=gdef.MAXIMUM_ALLOWED):
return windows.winproxy.OpenServiceW(self.handle, name, access) # Check service exists :)
def get_service(self, key, access=gdef.MAXIMUM_ALLOWED):
"""Get a service by its name/index or a list of services via a slice
:return: :class:`Service` or [:class:`Service`] -- A :class:`Service` or list of :class:`Service`
"""
if isinstance(key, int_types):
return self.enumerate_services()[key]
if isinstance(key, slice):
# Get service list
servlist = self.enumerate_services()
# Extract indexes matching the slice
indexes = key.indices(len(servlist))
return [servlist[idx] for idx in range(*indexes)]
# Retrieve service by its name
handle = self.open_service(key, access)
return Service(name=key, handle=handle)
__getitem__ = get_service
"""Get a service by its name/index or a list of services via a slice
:return: :class:`Service` or [:class:`Service`] -- A :class:`Service` or list of :class:`Service`
"""
def get_service_display_name(self, name):
# This API is strange..
# Why can't we retrieve the display name for a service handle ?
BUFFER_SIZE = 0x1000
result = (WCHAR * BUFFER_SIZE)()
size_needed = gdef.DWORD(BUFFER_SIZE)
windows.winproxy.GetServiceDisplayNameW(self.handle, name, result, size_needed)
return result.value
def _enumerate_services_generator(self):
"""The generator code behind __iter__.
Allow to iter over the services on the system
"""
size_needed = gdef.DWORD()
nb_services = gdef.DWORD()
counter = gdef.DWORD()
try:
windows.winproxy.EnumServicesStatusExW(self.handle, SC_ENUM_PROCESS_INFO, SERVICE_TYPE_ALL, SERVICE_STATE_ALL, None, 0, ctypes.byref(size_needed), ctypes.byref(nb_services), byref(counter), None)
except WindowsError:
pass
while True:
size = size_needed.value
buffer = (BYTE * size)()
try:
windows.winproxy.EnumServicesStatusExW(self.handle, SC_ENUM_PROCESS_INFO, SERVICE_TYPE_ALL, SERVICE_STATE_ALL, buffer, size, ctypes.byref(size_needed), ctypes.byref(nb_services), byref(counter), None)
except WindowsError as e:
continue
break
services_array = (gdef.ENUM_SERVICE_STATUS_PROCESSW * nb_services.value).from_buffer(buffer)
for service_info in services_array:
shandle = self.open_service(service_info.lpServiceName)
yield Service(handle=shandle, name=service_info.lpServiceName, description=service_info.lpDisplayName)
return
__iter__ = _enumerate_services_generator
"""Iter over the services on the system
:yield: :class:`Service`
"""
def enumerate_services(self):
return list(self._enumerate_services_generator())
class Service(gdef.SC_HANDLE):
"""Represent a service on the system"""
def __init__(self, handle, name, description=None):
super(Service, self).__init__(handle)
self.name = name
"""The name of the service
:type: :class:`str`
"""
if description is not None:
self._description = description # Setup fixedpropety
@property
def description(self):
"""The description of the service
:type: :class:`str`
"""
return ServiceManager().get_service_display_name(self.name)
@property
def status(self):
"""The status of the service
:type: :class:`~windows.generated_def.winstructs.SERVICE_STATUS_PROCESS`
"""
buffer = windows.utils.BUFFER(gdef.SERVICE_STATUS_PROCESS)()
size_needed = gdef.DWORD()
windows.winproxy.QueryServiceStatusEx(self, gdef.SC_STATUS_PROCESS_INFO, buffer.cast(gdef.LPBYTE), ctypes.sizeof(buffer), size_needed)
return buffer[0]
@property # Can change if service is started/stopped when the object exist
def process(self):
"""The process running the service (if any)
:type: :class:`WinProcess <windows.winobject.process.WinProcess>` or ``None``
"""
pid = self.status.dwProcessId
if not pid:
return None
l = windows.WinProcess(pid=pid)
return l
@property
def security_descriptor(self):
"""The security descriptor of the service
:type: :class:`~windows.security.SecurityDescriptor`
"""
return security.SecurityDescriptor.from_service(self.name)
def start(self, args=None):
"""Start the service
:param args: a list of :class:`str`
"""
nbelt = 0
if args is not None:
if isinstance(args, windows.pycompat.anybuff):
args = [args]
nbelt = len(args)
args = (gdef.LPWSTR * (nbelt))(*args)
return windows.winproxy.StartServiceW(self, nbelt, args)
def stop(self):
"""Stop the service"""
status = SERVICE_STATUS()
windows.winproxy.ControlService(self, gdef.SERVICE_CONTROL_STOP, status)
return status
def __repr__(self):
return """<{0} "{1}" {2!r}>""".format(type(self).__name__, self.name, self.status.state)
def __del__(self):
return windows.winproxy.CloseServiceHandle(self)
@@ -1,508 +0,0 @@
import os
import ctypes
import copy
import struct
import windows
from windows import winproxy
from windows import utils
import windows.generated_def as gdef
from windows.winobject import process
from windows.winobject import network
from windows.winobject import registry
from windows.winobject import exception
from windows.winobject import service
from windows.winobject import volume
from windows.winobject import wmi
from windows.winobject import object_manager
from windows.winobject import device_manager
from windows.winobject import handle
from windows.winobject import event_log
from windows.winobject import event_trace
from windows.winobject import task_scheduler
from windows.winobject import system_module
from windows.winobject import bits
from windows.dbgprint import dbgprint
class System(object):
"""The state of the current ``Windows`` system ``Python`` is running on"""
# Setup these in a fixedproperty ?
network = network.Network()
"""Object of class :class:`windows.winobject.network.Network`"""
registry = registry.Registry()
"""Object of class :class:`windows.winobject.registry.Registry`"""
@property
def processes(self):
"""The list of running processes
:type: [:class:`~windows.winobject.process.WinProcess`] -- A list of Process
"""
return self.enumerate_processes()
@property
def threads(self):
"""The list of running threads
:type: [:class:`~windows.winobject.process.WinThread`] -- A list of Thread
"""
return self.enumerate_threads_setup_owners()
@property
def logicaldrives(self):
"""List of logical drives [C:\, ...]
:type: [:class:`~windows.winobject.volume.LogicalDrive`] -- A list of LogicalDrive
"""
return volume.enum_logical_drive()
@utils.fixedpropety
def services(self):
"""An object to query, list and explore services
:type: :class:`~windows.winobject.service.ServiceManager`
"""
return service.ServiceManager()
@property
def handles(self):
"""The list of system handles
:type: [:class:`~windows.winobject.handle.Handle`] -- A list of Hanlde"""
return handle.enumerate_handles()
@property
def modules(self):
"""The list of system modules
:type: [:class:`~windows.winobject.system_module.SystemModule`] -- A list of :class:`~windows.winobject.system_module.SystemModule` or :class:`~windows.winobject.system_module.SystemModuleWow64`
"""
return system_module.enumerate_kernel_modules()
@utils.fixedpropety
def bitness(self):
"""The bitness of the system
:type: :class:`int` -- 32 or 64
"""
if os.environ["PROCESSOR_ARCHITECTURE"].lower() != "x86":
return 64
if "PROCESSOR_ARCHITEW6432" in os.environ:
return 64
return 32
@utils.fixedpropety
def wmi(self):
r"""An object to perform wmi requests to various namespaces
:type: :class:`~windows.winobject.wmi.WmiManager`"""
return wmi.WmiManager()
@utils.fixedpropety
def event_log(self):
"""An object to open Event channel/publisher and evtx file
:type: :class:`~windows.winobject.event_log.EvtlogManager`
"""
return event_log.EvtlogManager()
@utils.fixedpropety
def etw(self):
"""An object to interact with ETW (Event Tracing for Windows)
:type: :class:`~windows.winobject.event_trace.EtwManager`
"""
return event_trace.EtwManager()
@utils.fixedpropety
def task_scheduler(self):
"""An object able to manage scheduled tasks on the local system
:type: :class:`~windows.winobject.task_scheduler.TaskService`
"""
windows.com.init()
clsid_task_scheduler = gdef.IID.from_string("0f87369f-a4e5-4cfc-bd3e-73e6154572dd")
task_service = task_scheduler.TaskService()
# What is non-implemented (WinXP)
# Raise (NotImplementedError?) ? Return NotImplemented ?
windows.com.create_instance(clsid_task_scheduler, task_service)
task_service.connect()
return task_service
@utils.fixedpropety
def object_manager(self):
"""An object to query the objects in the kernel object manager.
:type: :class:`~windows.winobject.object_manager.ObjectManager`
"""
return windows.winobject.object_manager.ObjectManager()
@utils.fixedpropety
def device_manager(self):
"""An object to query the device&driver configured on the computer.
:type: :class:`~windows.winobject.device_manager.DeviceManager`
"""
return windows.winobject.device_manager.DeviceManager()
@utils.fixedpropety
def bits(self):
return bits.create_manager()
#TODO: use GetComputerNameExA ? and recover other names ?
@utils.fixedpropety
def computer_name(self):
"""The name of the computer
:type: :class:`str`
"""
size = gdef.DWORD(0x1000)
# For now I don't know what is best as A vs W APIs...
if windows.pycompat.is_py3:
buf = ctypes.create_unicode_buffer(size.value)
winproxy.GetComputerNameW(buf, ctypes.byref(size))
else:
buf = ctypes.create_string_buffer(size.value)
winproxy.GetComputerNameA(buf, ctypes.byref(size))
return buf[:size.value]
def _computer_name_ex(self, nametype):
size = gdef.DWORD(0)
try:
winproxy.GetComputerNameExW(nametype, None, ctypes.byref(size))
except WindowsError as e:
if e.winerror != gdef.ERROR_MORE_DATA:
raise
buf = ctypes.create_unicode_buffer(size.value)
winproxy.GetComputerNameExW(nametype, buf, ctypes.byref(size))
return buf[:size.value]
@utils.fixedproperty
def domain(self):
# [WIP] name of the domain joined by the computer, None is no domain joined
return self._computer_name_ex(gdef.ComputerNameDnsDomain) or None
@utils.fixedpropety
def version(self):
"""The version of the system
:type: (:class:`int`, :class:`int`) -- (Major, Minor)
"""
data = self.get_version()
result = data.dwMajorVersion, data.dwMinorVersion
if result == (6,2):
result_str = self.get_file_version("kernel32")
result_tup = [int(x) for x in result_str.split(".")]
result = tuple(result_tup[:2])
return result
@utils.fixedpropety
def version_name(self):
"""The name of the system version, values are:
* Windows Server 2016
* Windows 10
* Windows Server 2012 R2
* Windows 8.1
* Windows Server 2012
* Windows 8
* Windows Server 2008
* Windows 7
* Windows Server 2008
* Windows Vista
* Windows XP Professional x64 Edition
* TODO: version (5.2) + is_workstation + bitness == 32 (don't even know if possible..)
* Windows Server 2003 R2
* Windows Server 2003
* Windows XP
* Windows 2000
* "Unknow Windows <version={0} | is_workstation={1}>".format(version, is_workstation)
:type: :class:`str`
"""
version = self.version
is_workstation = self.product_type == gdef.VER_NT_WORKSTATION
if version == (10, 0):
return ["Windows Server 2016", "Windows 10"][is_workstation]
elif version == (6, 3):
return ["Windows Server 2012 R2", "Windows 8.1"][is_workstation]
elif version == (6, 2):
return ["Windows Server 2012", "Windows 8"][is_workstation]
elif version == (6, 1):
return ["Windows Server 2008 R2", "Windows 7"][is_workstation]
elif version == (6, 0):
return ["Windows Server 2008", "Windows Vista"][is_workstation]
elif version == (5, 2):
metric = winproxy.GetSystemMetrics(gdef.SM_SERVERR2)
if is_workstation:
if self.bitness == 64:
return "Windows XP Professional x64 Edition"
else:
return "TODO: version (5.2) + is_workstation + bitness == 32"
elif metric != 0:
return "Windows Server 2003 R2"
else:
return "Windows Server 2003"
elif version == (5, 1):
return "Windows XP"
elif version == (5, 0):
return "Windows 2000"
else:
return "Unknow Windows <version={0} | is_workstation={1}>".format(version, is_workstation)
VERSION_MAPPER = gdef.FlagMapper(gdef.VER_NT_WORKSTATION, gdef.VER_NT_DOMAIN_CONTROLLER, gdef.VER_NT_SERVER)
@utils.fixedpropety
def product_type(self):
"""The product type, value might be:
* VER_NT_WORKSTATION(0x1L)
* VER_NT_DOMAIN_CONTROLLER(0x2L)
* VER_NT_SERVER(0x3L)
:type: :class:`long` or :class:`int` (or subclass)
"""
version = self.get_version()
return self.VERSION_MAPPER[version.wProductType]
EDITION_MAPPER = gdef.FlagMapper(gdef.PRODUCT_UNDEFINED,
gdef.PRODUCT_ULTIMATE,
gdef.PRODUCT_HOME_BASIC,
gdef.PRODUCT_HOME_PREMIUM,
gdef.PRODUCT_ENTERPRISE,
gdef.PRODUCT_HOME_BASIC_N,
gdef.PRODUCT_BUSINESS,
gdef.PRODUCT_STANDARD_SERVER,
gdef.PRODUCT_DATACENTER_SERVER,
gdef.PRODUCT_SMALLBUSINESS_SERVER,
gdef.PRODUCT_ENTERPRISE_SERVER,
gdef.PRODUCT_STARTER,
gdef.PRODUCT_DATACENTER_SERVER_CORE,
gdef.PRODUCT_STANDARD_SERVER_CORE,
gdef.PRODUCT_ENTERPRISE_SERVER_CORE,
gdef.PRODUCT_ENTERPRISE_SERVER_IA64,
gdef.PRODUCT_BUSINESS_N,
gdef.PRODUCT_WEB_SERVER,
gdef.PRODUCT_CLUSTER_SERVER,
gdef.PRODUCT_HOME_SERVER,
gdef.PRODUCT_STORAGE_EXPRESS_SERVER,
gdef.PRODUCT_STORAGE_STANDARD_SERVER,
gdef.PRODUCT_STORAGE_WORKGROUP_SERVER,
gdef.PRODUCT_STORAGE_ENTERPRISE_SERVER,
gdef.PRODUCT_SERVER_FOR_SMALLBUSINESS,
gdef.PRODUCT_SMALLBUSINESS_SERVER_PREMIUM,
gdef.PRODUCT_HOME_PREMIUM_N,
gdef.PRODUCT_ENTERPRISE_N,
gdef.PRODUCT_ULTIMATE_N,
gdef.PRODUCT_WEB_SERVER_CORE,
gdef.PRODUCT_MEDIUMBUSINESS_SERVER_MANAGEMENT,
gdef.PRODUCT_MEDIUMBUSINESS_SERVER_SECURITY,
gdef.PRODUCT_MEDIUMBUSINESS_SERVER_MESSAGING,
gdef.PRODUCT_SERVER_FOUNDATION,
gdef.PRODUCT_HOME_PREMIUM_SERVER,
gdef.PRODUCT_SERVER_FOR_SMALLBUSINESS_V,
gdef.PRODUCT_STANDARD_SERVER_V,
gdef.PRODUCT_DATACENTER_SERVER_V,
gdef.PRODUCT_ENTERPRISE_SERVER_V,
gdef.PRODUCT_DATACENTER_SERVER_CORE_V,
gdef.PRODUCT_STANDARD_SERVER_CORE_V,
gdef.PRODUCT_ENTERPRISE_SERVER_CORE_V,
gdef.PRODUCT_HYPERV,
gdef.PRODUCT_STORAGE_EXPRESS_SERVER_CORE,
gdef.PRODUCT_STORAGE_STANDARD_SERVER_CORE,
gdef.PRODUCT_STORAGE_WORKGROUP_SERVER_CORE,
gdef.PRODUCT_STORAGE_ENTERPRISE_SERVER_CORE,
gdef.PRODUCT_STARTER_N,
gdef.PRODUCT_PROFESSIONAL,
gdef.PRODUCT_PROFESSIONAL_N,
gdef.PRODUCT_SB_SOLUTION_SERVER,
gdef.PRODUCT_SERVER_FOR_SB_SOLUTIONS,
gdef.PRODUCT_STANDARD_SERVER_SOLUTIONS,
gdef.PRODUCT_STANDARD_SERVER_SOLUTIONS_CORE,
gdef.PRODUCT_SB_SOLUTION_SERVER_EM,
gdef.PRODUCT_SERVER_FOR_SB_SOLUTIONS_EM,
gdef.PRODUCT_SOLUTION_EMBEDDEDSERVER,
gdef.PRODUCT_SOLUTION_EMBEDDEDSERVER_CORE,
gdef.PRODUCT_SMALLBUSINESS_SERVER_PREMIUM_CORE,
gdef.PRODUCT_ESSENTIALBUSINESS_SERVER_MGMT,
gdef.PRODUCT_ESSENTIALBUSINESS_SERVER_ADDL,
gdef.PRODUCT_ESSENTIALBUSINESS_SERVER_MGMTSVC,
gdef.PRODUCT_ESSENTIALBUSINESS_SERVER_ADDLSVC,
gdef.PRODUCT_CLUSTER_SERVER_V,
gdef.PRODUCT_EMBEDDED,
gdef.PRODUCT_STARTER_E,
gdef.PRODUCT_HOME_BASIC_E,
gdef.PRODUCT_HOME_PREMIUM_E,
gdef.PRODUCT_PROFESSIONAL_E,
gdef.PRODUCT_ENTERPRISE_E,
gdef.PRODUCT_ULTIMATE_E,
gdef.PRODUCT_ENTERPRISE_EVALUATION,
gdef.PRODUCT_MULTIPOINT_STANDARD_SERVER,
gdef.PRODUCT_MULTIPOINT_PREMIUM_SERVER,
gdef.PRODUCT_STANDARD_EVALUATION_SERVER,
gdef.PRODUCT_DATACENTER_EVALUATION_SERVER,
gdef.PRODUCT_ENTERPRISE_N_EVALUATION,
gdef.PRODUCT_STORAGE_WORKGROUP_EVALUATION_SERVER,
gdef.PRODUCT_STORAGE_STANDARD_EVALUATION_SERVER,
gdef.PRODUCT_CORE_ARM,
gdef.PRODUCT_CORE_N,
gdef.PRODUCT_CORE_COUNTRYSPECIFIC,
gdef.PRODUCT_CORE_LANGUAGESPECIFIC,
gdef.PRODUCT_CORE,
gdef.PRODUCT_PROFESSIONAL_WMC,
gdef.PRODUCT_UNLICENSED)
@utils.fixedpropety
def edition(self): # Find a better name ?
version = self.get_version()
edition = gdef.DWORD()
try:
winproxy.GetProductInfo(version.dwMajorVersion,
version.dwMinorVersion,
version.wServicePackMajor,
version.wServicePackMinor,
edition)
except winproxy.ExportNotFound as e:
# Windows XP does not implem GetProductInfo
assert version.dwMajorVersion, version.dwMinorVersion == (5,1)
return self._edition_windows_xp()
return self.EDITION_MAPPER[edition.value]
def _edition_windows_xp(self):
# Emulate standard response from IsOS(gdef.OS_PROFESSIONAL)
if winproxy.IsOS(gdef.OS_PROFESSIONAL):
return gdef.PRODUCT_PROFESSIONAL
return gdef.PRODUCT_HOME_BASIC
@utils.fixedpropety
def windir(self):
buffer = ctypes.c_buffer(0x100)
reslen = winproxy.GetWindowsDirectoryA(buffer)
return buffer[:reslen]
def get_version(self):
data = gdef.OSVERSIONINFOEXA()
data.dwOSVersionInfoSize = ctypes.sizeof(data)
winproxy.GetVersionExA(ctypes.cast(ctypes.pointer(data), ctypes.POINTER(gdef.OSVERSIONINFOA)))
return data
def get_file_version(self, name):
size = winproxy.GetFileVersionInfoSizeA(name)
buf = ctypes.c_buffer(size)
winproxy.GetFileVersionInfoA(name, 0, size, buf)
bufptr = gdef.PVOID()
bufsize = gdef.UINT()
winproxy.VerQueryValueA(buf, "\\VarFileInfo\\Translation", ctypes.byref(bufptr), ctypes.byref(bufsize))
bufstr = ctypes.cast(bufptr, gdef.LPCSTR)
tup = struct.unpack("<HH", bufstr.value[:4])
req = "{0:04x}{1:04x}".format(*tup)
winproxy.VerQueryValueA(buf, "\\StringFileInfo\\{0}\\ProductVersion".format(req), ctypes.byref(bufptr), ctypes.byref(bufsize))
bufstr = ctypes.cast(bufptr, gdef.LPCSTR)
return bufstr.value
@utils.fixedpropety
def build_number(self):
# Best effort. use get_file_version if registry code fails
try:
# Does not works on Win7..
# Missing CurrentMajorVersionNumber/CurrentMinorVersionNumber/UBR
# We have CurrentVersion instead
# Use this code and get_file_version as a backup ?
curver_key = windows.system.registry(r"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion")
try:
major = curver_key["CurrentMajorVersionNumber"].value
minor = curver_key["CurrentMinorVersionNumber"].value
except WindowsError as e:
version = curver_key["CurrentVersion"].value
# May raise ValueError if no "."
major, minor = version.split(".")
build = curver_key["CurrentBuildNumber"].value
# Update Build Revision
try:
ubr = curver_key["UBR"].value
except WindowsError as e:
ubr = 0 # Not present on Win7
return "{0}.{1}.{2}.{3}".format(major, minor, build, ubr)
except (WindowsError, ValueError):
return self.get_file_version("ntdll")
@staticmethod
def enumerate_processes():
dbgprint("Enumerating processes with CreateToolhelp32Snapshot", "SLOW")
process_entry = gdef.PROCESSENTRY32W()
process_entry.dwSize = ctypes.sizeof(process_entry)
snap = winproxy.CreateToolhelp32Snapshot(gdef.TH32CS_SNAPPROCESS, 0)
winproxy.Process32FirstW(snap, process_entry)
res = []
res.append(process.WinProcess._from_PROCESSENTRY32(process_entry))
while winproxy.Process32NextW(snap, process_entry):
res.append(process.WinProcess._from_PROCESSENTRY32(process_entry))
winproxy.CloseHandle(snap)
return res
@staticmethod
def enumerate_threads_generator():
# Ptet dangereux, parce que on yield la meme THREADENTRY32 a chaque fois
dbgprint("Enumerating threads with CreateToolhelp32Snapshot <generator>", "SLOW")
thread_entry = gdef.THREADENTRY32()
thread_entry.dwSize = ctypes.sizeof(thread_entry)
snap = winproxy.CreateToolhelp32Snapshot(gdef.TH32CS_SNAPTHREAD, 0)
dbgprint("New handle CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD) <generator> | {0:#x}".format(snap), "HANDLE")
try:
winproxy.Thread32First(snap, thread_entry)
yield thread_entry
while winproxy.Thread32Next(snap, thread_entry):
yield thread_entry
finally:
winproxy.CloseHandle(snap)
dbgprint("CLOSE CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD) <generator> | {0:#x}".format(snap), "HANDLE")
@staticmethod
def enumerate_threads():
return [WinThread._from_THREADENTRY32(th) for th in System.enumerate_threads_generator()]
def enumerate_threads_setup_owners(self):
# Enumerating threads is a special operation concerning the owner process.
# We may not be able to retrieve the name of the owning process by normal way
# (as we need to get a handle on the process)
# So, this implementation of enumerate_thread also setup the owner with the result of enumerate_processes
dbgprint("Enumerating threads with CreateToolhelp32Snapshot and setup owner", "SLOW")
# One snap for both enum to be prevent race
snap = winproxy.CreateToolhelp32Snapshot(gdef.TH32CS_SNAPTHREAD | gdef.TH32CS_SNAPPROCESS, 0)
process_entry = gdef.PROCESSENTRY32W()
process_entry.dwSize = ctypes.sizeof(process_entry)
winproxy.Process32FirstW(snap, process_entry)
processes = []
processes.append(process.WinProcess._from_PROCESSENTRY32(process_entry))
while winproxy.Process32NextW(snap, process_entry):
processes.append(process.WinProcess._from_PROCESSENTRY32(process_entry))
# Forge a dict pid -> process
proc_dict = {proc.pid: proc for proc in processes}
thread_entry = gdef.THREADENTRY32()
thread_entry.dwSize = ctypes.sizeof(thread_entry)
threads = []
winproxy.Thread32First(snap, thread_entry)
parent = proc_dict[thread_entry.th32OwnerProcessID]
threads.append(process.WinThread._from_THREADENTRY32(thread_entry, owner=parent))
while winproxy.Thread32Next(snap, thread_entry):
parent = proc_dict[thread_entry.th32OwnerProcessID]
threads.append(process.WinThread._from_THREADENTRY32(thread_entry, owner=parent))
winproxy.CloseHandle(snap)
return threads
@@ -1,53 +0,0 @@
import ctypes
import windows
import windows.winproxy as winproxy
import windows.generated_def as gdef
class BaseSystemModule(object):
"""[ABSTRACT] A common base class for all system modules"""
@property
def name(self):
"""The name of the system module: alias for ``ImageName``"""
return self.ImageName
def __repr__(self):
return """<{0} name="{1}" base={2:#x}>""".format(type(self).__name__, self.ImageName, self.Base)
class SystemModule(BaseSystemModule, gdef.SYSTEM_MODULE):
"""A system module.
.. note::
inherit from SYSTEM_MODULE[32/64] based on the current process bitness
"""
pass
# Only useful / meaningful in Wow64 Process
class SystemModuleWow64(BaseSystemModule, gdef.SYSTEM_MODULE64):
"""An explicite 64b system module for SysWow64 processes"""
pass
def enumerate_kernel_modules():
if windows.current_process.is_wow_64:
return enumerate_kernel_modules_syswow64()
cbsize = gdef.DWORD()
winproxy.NtQuerySystemInformation(gdef.SystemModuleInformation, None, 0, ctypes.byref(cbsize))
raw_buffer = (cbsize.value * gdef.BYTE)()
buffer = gdef.SYSTEM_MODULE_INFORMATION.from_address(ctypes.addressof(raw_buffer))
winproxy.NtQuerySystemInformation(gdef.SystemModuleInformation, ctypes.byref(raw_buffer), ctypes.sizeof(raw_buffer), ctypes.byref(cbsize))
modules = (SystemModule * buffer.ModulesCount).from_buffer(raw_buffer, gdef.SYSTEM_MODULE_INFORMATION.Modules.offset)
return list(modules)
def enumerate_kernel_modules_syswow64():
cbsize = gdef.DWORD()
windows.syswow64.NtQuerySystemInformation_32_to_64(gdef.SystemModuleInformation, None, 0, ctypes.addressof(cbsize))
raw_buffer = (cbsize.value * gdef.BYTE)()
buffer = gdef.SYSTEM_MODULE_INFORMATION64.from_address(ctypes.addressof(raw_buffer))
windows.syswow64.NtQuerySystemInformation_32_to_64(gdef.SystemModuleInformation, ctypes.byref(raw_buffer), ctypes.sizeof(raw_buffer), ctypes.byref(cbsize))
modules = (SystemModuleWow64 * buffer.ModulesCount).from_buffer(raw_buffer, gdef.SYSTEM_MODULE_INFORMATION64.Modules.offset)
return list(modules)
@@ -1,485 +0,0 @@
import windows.com
import windows.generated_def as gdef
def generate_simple_getter(function, restype, extract_value=True, doc=None):
def value_getter(self):
res = restype()
getattr(self, function)(res)
if extract_value:
return res.value
return res
return property(value_getter, doc=doc)
def add_simple_setter(getter, function, restype):
@getter.setter
def value_setter(self, value):
resvalue = restype(value)
return getattr(self, function)(resvalue)
return value_setter
class TaskCollectionType(object):
ITEM_TYPE = None
count = generate_simple_getter("get_Count", gdef.LONG)
def get_item_type(self):
return self.ITEM_TYPE
def get_item(self, index):
"""Return elements nb ``index``. Collection index starts at 1"""
if index == 0:
raise IndexError("<{0}> Index start as 1".format(type(self).__name__))
index = self.get_index(index)
res = self.get_item_type()()
self.get_Item(index, res)
return res
def get_index(self, index):
return index
def items_generator(self):
for i in range(self.count):
# Start index is 1
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa446901(v=vs.85).aspx
yield self.get_item(1 + i)
@property
def items(self):
"""Return the list of item in the collection
:type: :class:`list`
"""
return list(self.items_generator())
def __iter__(self):
return self.items_generator()
def __getitem__(self, index): # Allow subclasses to only overwrite 'get_item' to rewrite both behavior
return self.get_item(index)
# Need to-do the doc=xx tricks to have the documentation in the 'AbstractAction' subclasses
class AbstractAction(object):
type_doc = """The type of action
:type: :class:`~windows.generated_def.winstructs.TASK_ACTION_TYPE`
"""
type = generate_simple_getter("get_Type", gdef.TASK_ACTION_TYPE, doc=type_doc)
id_doc = """The action id
:type: :class:`~windows.generated_def.winstructs.BSTR`
"""
id = generate_simple_getter("get_Id", gdef.BSTR, doc=id_doc)
class Action(gdef.IAction, AbstractAction):
"""Describe an action performed by a task"""
ACTION_SUBTYPE = {}
@property
def subtype(self):
"""Return the :class:`Action`-subtype according to :data:`AbstractAction.type`"""
subinterface = self.ACTION_SUBTYPE[self.type] # KeyError ?
return self.query(subinterface)
class ExecAction(gdef.IExecAction, AbstractAction):
"""Represent an action of type
:data:`~windows.generated_def.winstructs._TASK_ACTION_TYPE.TASK_ACTION_EXEC`"""
path = generate_simple_getter("get_Path", gdef.BSTR)
path = add_simple_setter(path, "put_Path", gdef.BSTR)
"""[R-W] The path of the programm to execute"""
arguments = generate_simple_getter("get_Arguments", gdef.BSTR)
arguments = add_simple_setter(arguments, "put_Arguments", gdef.BSTR)
"""[R-W] The arguments for the command to execute"""
working_directory = generate_simple_getter("get_WorkingDirectory", gdef.BSTR)
"""The working directory for the command to execute"""
# Register action subtype
Action.ACTION_SUBTYPE[gdef.TASK_ACTION_EXEC] = ExecAction
class ComHandlerAction(gdef.IComHandlerAction, AbstractAction):
"""Represent an action of type
:data:`~windows.generated_def.winstructs._TASK_ACTION_TYPE.TASK_ACTION_COM_HANDLER`"""
classid = generate_simple_getter("get_ClassId", gdef.BSTR)
classid = add_simple_setter(classid, "put_ClassId", gdef.BSTR)
"""The CLSID of the COM server executed
:type: :class:`~windows.generated_def.winstructs.BSTR`
"""
data = generate_simple_getter("get_Data", gdef.BSTR)
data = add_simple_setter(data, "put_Data", gdef.BSTR)
"""The DATA for the COM class
:type: :class:`~windows.generated_def.winstructs.BSTR`
"""
# Register action subtype
Action.ACTION_SUBTYPE[gdef.TASK_ACTION_COM_HANDLER] = ComHandlerAction
class EmailAction(gdef.IEmailAction, AbstractAction):
pass
Action.ACTION_SUBTYPE[gdef.TASK_ACTION_SEND_EMAIL] = EmailAction
class ShowMessageAction(gdef.IShowMessageAction, AbstractAction):
pass
Action.ACTION_SUBTYPE[gdef.TASK_ACTION_SHOW_MESSAGE] = ShowMessageAction
class Trigger(gdef.ITrigger):
"""A task trigger"""
type = generate_simple_getter("get_Type", gdef.TASK_TRIGGER_TYPE2)
"""The type of trigger
:type: :class:`~windows.generated_def.winstructs.TASK_TRIGGER_TYPE2`
"""
class ActionCollection(gdef.IActionCollection, TaskCollectionType):
ITEM_TYPE = Action
def create(self, action_type):
"""Create a new action of type ``action_type``
:rtype: A subclass of :class:`Action`
"""
res = self.ITEM_TYPE()
self.Create(action_type, res)
return res.subtype
def get_item(self, index):
item = super(ActionCollection, self).get_item(index)
# Need to Release() item ?
return item.subtype
class TriggerCollection(gdef.ITriggerCollection, TaskCollectionType):
ITEM_TYPE = Trigger
class TaskRegistrationInfo(gdef.IRegistrationInfo):
"""Provides the administrative information that can be used to describe the task.
This information includes details such as a description of the task,
the author of the task, the date the task is registered,
and the security descriptor of the task.
"""
author = generate_simple_getter("get_Author", gdef.BSTR)
"""The author of the task"""
description = generate_simple_getter("get_Description", gdef.BSTR)
"""The description of the task"""
date = generate_simple_getter("get_Date", gdef.BSTR)
"""The registration date of the task"""
source = generate_simple_getter("get_Source", gdef.BSTR)
"""Where the task originated from.
For example, a task may originate from a component, service, application, or user.
"""
documentation = generate_simple_getter("get_Documentation", gdef.BSTR)
"""Any additional documentation for the task"""
uri = generate_simple_getter("get_URI", gdef.BSTR)
"""the URI of the task."""
version = generate_simple_getter("get_Version", gdef.BSTR)
"""The version number of the task."""
# Return WindowsError: [Error -2147467263] Not implemented
# xml = generate_simple_getter("get_XmlText", gdef.BSTR)
sddl = generate_simple_getter("get_SecurityDescriptor", windows.com.Variant)
@property
def security_descriptor(self):
sddl = self.sddl
if not sddl:
return None
return windows.security.SecurityDescriptor.from_string(sddl)
class TaskPrincipal(gdef.IPrincipal):
"""Provides the security credentials for a principal.
These security credentials define the security context for the tasks that are associated with the principal.
"""
name = generate_simple_getter("get_DisplayName", gdef.BSTR)
name = add_simple_setter(name, "put_DisplayName", gdef.BSTR)
"""The name of the principal"""
id = generate_simple_getter("get_Id", gdef.BSTR)
id = add_simple_setter(id, "put_Id", gdef.BSTR)
"""the identifier of the principal."""
user_id = generate_simple_getter("get_UserId", gdef.BSTR)
user_id = add_simple_setter(user_id, "put_UserId", gdef.BSTR)
"""the user identifier that is required to run the task"""
group_id = generate_simple_getter("get_GroupId", gdef.BSTR)
group_id = add_simple_setter(group_id, "put_GroupId", gdef.BSTR)
"""the user group that is required to run the task"""
run_level = generate_simple_getter("get_RunLevel", gdef.TASK_RUNLEVEL_TYPE)
"""the privilege level that is required to run the tasks
:type: :class:`~windows.generated_def.winstructs.TASK_RUNLEVEL_TYPE`
"""
logon_type = generate_simple_getter("get_LogonType", gdef.TASK_LOGON_TYPE)
""" logon method that is required to run the task
:type: :class:`~windows.generated_def.winstructs.TASK_LOGON_TYPE`
"""
class TaskDefinition(gdef.ITaskDefinition):
"""The definition of a task"""
actions = generate_simple_getter("get_Actions", ActionCollection, extract_value=False)
"""The list of actions of the task
:type: :class:`ActionCollection`
"""
triggers = generate_simple_getter("get_Triggers", TriggerCollection, extract_value=False)
"""The list of triggers of the task
:type: :class:`TriggerCollection`
"""
registration_info = generate_simple_getter("get_RegistrationInfo", TaskRegistrationInfo, extract_value=False)
"""The registration information of the task
:type: :class:`TaskRegistrationInfo`
"""
principal = generate_simple_getter("get_Principal", TaskPrincipal, extract_value=False)
"""The principal that provides the security credentials for the task.
These security credentials define the security context for the tasks that are associated with the principal.
:type: :class:`TaskPrincipal`
"""
xml = generate_simple_getter("get_XmlText", gdef.BSTR)
"""The XML representig the task definition
:type: :class:`str`
"""
class Task(gdef.IRegisteredTask):
"""A scheduled task"""
name = generate_simple_getter("get_Name", gdef.BSTR)
"""The name of the task"""
path = generate_simple_getter("get_Path", gdef.BSTR)
"""The path of the task"""
state = generate_simple_getter("get_State", gdef.TASK_STATE)
"""The state of the task
:type: :class:`~windows.generated_def.winstructs.TASK_STATE`
"""
enabled = generate_simple_getter("get_Enabled", gdef.VARIANT_BOOL)
"""``True`` is the task is enabled"""
last_runtime = generate_simple_getter("get_LastRunTime", gdef.DATE)
"""Gets the last time the registered task was last run."""
next_runtime = generate_simple_getter("get_NextRunTime", gdef.DATE)
"""Gets the next time the registered task will be run."""
definition = generate_simple_getter("get_Definition", TaskDefinition, extract_value=False)
"""The definition of the task
:type: :class:`TaskDefinition`
"""
xml = generate_simple_getter("get_Xml", gdef.BSTR)
"""The XML representig the task
:type: :class:`str`
"""
def run(self, params=None, flags=gdef.TASK_RUN_NO_FLAGS, sessionid=0, user=None):
if params is None: params = gdef.VARIANT() # Empty variant
result = gdef.IRunningTask()
self.RunEx(params, flags, sessionid, user, result)
return result
def get_security_descriptor(self, secinfo):
res = gdef.BSTR()
self.GetSecurityDescriptor(secinfo, res)
return res.value
def __repr__(self):
return """<{0} "{1}" at {2:#x}>""".format(type(self).__name__, self.name, id(self))
class TaskCollection(gdef.IRegisteredTaskCollection, TaskCollectionType):
ITEM_TYPE = Task
def get_index(self, index):
vindex = windows.com.Variant()
vindex.vt = gdef.VT_I4
vindex._VARIANT_NAME_3.lVal = index
return vindex
class TaskService(gdef.ITaskService):
"""The task scheduler"""
def create(self, flags=0):
"""Create a new :class:`TaskDefinition` that can be used to create/register a new scheduled task
:rtype: :class:`TaskDefinition`
"""
res = TaskDefinition()
self.NewTask(flags, res)
return res
def connect(self, server=None, user=None, domain=None, password=None):
if server is None: server = gdef.VARIANT() # Empty variant
if user is None: user = gdef.VARIANT() # Empty variant
if domain is None: domain = gdef.VARIANT() # Empty variant
if password is None: password = gdef.VARIANT() # Empty variant
self.Connect(server, user, domain, password)
def folder(self, name):
"""Return the :class:`TaskFolder` with ``name``
:rtype: :class:`TaskFolder`
"""
folder = TaskFolder()
self.GetFolder(name, folder)
return folder
__call__ = folder # use the same 'API' than the registry
"""Alias for :func:`folder`"""
@property
def root(self):
r"""The root ``\`` :class:`TaskFolder`"""
return self.folder("\\")
class TaskFolder(gdef.ITaskFolder):
"""A folder of tasks"""
path = generate_simple_getter("get_Path", gdef.BSTR)
name = generate_simple_getter("get_Name", gdef.BSTR)
@property
def folders(self):
"""The list of sub-folders
:type: :class:`TaskFolderCollection`
"""
res = TaskFolderCollection()
self.GetFolders(0, res)
return res
def register(self, name, taskdef, flags=gdef.TASK_CREATE, userid=None, password=None, logonType=gdef.TASK_LOGON_NONE, ssid=None):
"""Register the task definition ``taskdef`` as a new task with ``name``
:rtype: :class:`Task`
"""
new_task = Task()
if userid is None: userid = gdef.VARIANT() # Empty variant
if password is None: password = gdef.VARIANT() # Empty variant
if ssid is None: ssid = gdef.VARIANT() # Empty variant
self.RegisterTaskDefinition(name, taskdef, flags, userid, password, logonType, ssid, new_task)
return new_task
@property
def tasks(self, flags=gdef.TASK_ENUM_HIDDEN):
"""The list of tasks in the folder
:type: :class:`TaskCollection`
"""
tasks = TaskCollection()
self.GetTasks(flags, tasks)
return tasks
def get_task(self, name):
"""Retrieve the task with ``name`` in the current folder
:rtype: :class:`Task`
"""
res = Task()
self.GetTask(name, res)
return res
def delete_task(self, name):
"""Delete the task with ``name`` in the current folder"""
return self.DeleteTask(name, 0)
def folder(self, name):
"""Return the :class:`TaskFolder` with ``name``"""
folder = TaskFolder()
self.GetFolder(name, folder)
return folder
def create_folder(self, name):
"""Create a new sub-:class:`TaskFolder` with ``name``"""
folder = TaskFolder()
self.CreateFolder(name, gdef.VARIANT(), folder)
return folder
def delete_folder(self, name):
"""Delete the sub-folder with ``name`` in the current folder"""
return self.DeleteFolder(name, 0)
__getitem__ = get_task
""" Alias for :func:`get_task`"""
__delitem__ = delete_task
""" Alias for :func:`delete_task`"""
__call__ = folder # use the same 'API' than the registry
""" Alias for :func:`folder`"""
def __repr__(self):
return """<{0} "{1}" at {2:#x}>""".format(type(self).__name__, self.path, id(self))
class TaskFolderCollection(gdef.ITaskFolderCollection, TaskCollectionType):
ITEM_TYPE = TaskFolder
def get_index(self, index):
vindex = windows.com.Variant()
vindex.vt = gdef.VT_I4
vindex._VARIANT_NAME_3.lVal = index
return vindex
# windows.com.init()
# clsid_task_scheduler = gdef.IID.from_string("0f87369f-a4e5-4cfc-bd3e-73e6154572dd")
# x = TaskService()
# emptvar = gdef.VARIANT()
# windows.com.create_instance(clsid_task_scheduler, x)
# x.connect()
# folder = x.folder("\\")
# assert folder.value
# tasks = folder.tasks
# for task in tasks.items:
# print(task.name)
# for action in task.definition.actions.items:
# print(" * {0}".format(action.type))
# subtype = action.subtype
# print(" * Path: {0}".format(subtype.path))
# print(" * Args: {0}".format(subtype.arguments))
# print(" * WDir: {0}".format(subtype.working_directory))
# Test creation
# ntd = x.create()
# actions = ntd.actions
# nea = actions.create(gdef.TASK_ACTION_EXEC).subtype
# nea.path = "MY_BINARY"
# nea.arguments = "MY_ARGUMENTS"
# folder.register("PROUT", ntd)
# path = gdef.BSTR()
# e.get_Path(path)
# print(path)
@@ -1,626 +0,0 @@
import ctypes
import functools
import windows
from windows import utils
from windows import winproxy
import windows.generated_def as gdef
# import windows.security # at the end of this file (loop import)
bltn_type = type
KNOW_INTEGRITY_LEVEL = gdef.FlagMapper(
gdef.SECURITY_MANDATORY_UNTRUSTED_RID,
gdef.SECURITY_MANDATORY_LOW_RID,
gdef.SECURITY_MANDATORY_MEDIUM_RID,
gdef.SECURITY_MANDATORY_MEDIUM_PLUS_RID,
gdef.SECURITY_MANDATORY_HIGH_RID,
gdef.SECURITY_MANDATORY_SYSTEM_RID,
gdef.SECURITY_MANDATORY_PROTECTED_PROCESS_RID
)
# Voodoo to fix lookup-strangeness in class declaration
def meta_craft(x):
def partial_applier(infos_class, rtype):
return property(functools.partial(x, infos_class=infos_class, rtype=rtype))
return partial_applier
class TokenGroups(gdef.TOKEN_GROUPS):
@property
def _groups(self):
return windows.utils.resized_array(self.Groups, self.GroupCount)
@property
def sids_and_attributes(self):
"""The sids and attributes of each group
:type: [:class:`~windows.generated_def.winstructs.SID_AND_ATTRIBUTES`] - A list of :class:`~windows.generated_def.winstructs.SID_AND_ATTRIBUTES`
"""
return self._groups # Something else ?
@property
def sids(self):
"""The sids of each group
:type: [:class:`~windows.generated_def.winstructs.PSID`] - A list of :class:`~windows.generated_def.winstructs.PSID`
"""
return [g.Sid for g in self._groups]
def __repr__(self):
return "<{0} count={1}>".format(type(self).__name__, self.GroupCount)
TokenGroupsType = TokenGroups # Prevent confusion with token.TokenGroups
class TokenPrivileges(gdef.TOKEN_PRIVILEGES):
"""Improved ``TOKEN_PRIVILEGES`` usable like a mapping"""
@property
def _privileges(self):
return windows.utils.resized_array(self.Privileges, self.PrivilegeCount)
def all(self):
"""The list of all privileges
:returns: [:class:`~windows.generated_def.winstructs.LUID_AND_ATTRIBUTES`] - A list of :class:`~windows.generated_def.winstructs.LUID_AND_ATTRIBUTES`
"""
return list(self._privileges)
def keys(self):
"""The name of all privileges in the TokenPrivileges
:returns: [:class:`str`] - A list of name
"""
return [self._lookup_name(p.Luid) for p in self._privileges]
__iter__ = keys
def items(self):
"""The (name, Attribute) of all privileges in the TokenPrivileges
:returns: [(:class:`str`, :class:`int`)] - A list of (name, Attribute) tuple
"""
return [(self._lookup_name(p.Luid), p.Attributes) for p in self._privileges]
def _get_priv_by_name(self, name):
luid = self._lookup_value(name)
x = [p for p in self._privileges if p.Luid == luid]
if not x:
return None
assert len(x) == 1
return x[0]
def __getitem__(self, name):
"""Retrieve the attribute value for privilege ``name``
:raises: KeyError if privilege ``name`` not in the TokenPrivileges
:returns: :class:`int`
"""
priv = self._get_priv_by_name(name)
if not priv:
raise KeyError(name)
return priv.Attributes
def __setitem__(self, name, value):
"""Set the attribute value for privilege ``name``
:raises: KeyError if privilege ``name`` not in the TokenPrivileges
"""
priv = self._get_priv_by_name(name)
if not priv:
raise KeyError(name)
priv.Attributes = value
# __delitem__ that set SE_PRIVILEGE_REMOVED ?
def _lookup_name(self, luid):
size = gdef.DWORD(0x100)
buff = ctypes.create_unicode_buffer(size.value)
winproxy.LookupPrivilegeNameW(None, luid, buff, size)
return buff[:size.value]
def _lookup_value(self, name):
luid = gdef.LUID()
winproxy.LookupPrivilegeValueW(None, name, ctypes.byref(luid))
return luid
TokenPrivilegesType = TokenPrivileges
class TokenSecurityAttributesInformation(gdef.TOKEN_SECURITY_ATTRIBUTES_INFORMATION):
@property
def attributes(self):
"""Return all the attributes as :class:`TokenSecurityAttributeV1`
:type: [:class:`TokenSecurityAttributeV1`] - A list of token security attributes
"""
tptr = ctypes.cast(self.Attribute.pAttributeV1, ctypes.POINTER(TokenSecurityAttributeV1))
# Well look like this cast does NOT keep a ref to self.
# Setup the base object ref ourself
tptr._custom_base_ = self
return tptr[:self.AttributeCount]
class TokenSecurityAttributeV1(gdef.TOKEN_SECURITY_ATTRIBUTE_V1):
VALUE_ARRAY_PTR_BY_TYPE = {
gdef.TOKEN_SECURITY_ATTRIBUTE_TYPE_INT64: "pInt64",
gdef.TOKEN_SECURITY_ATTRIBUTE_TYPE_UINT64: "pUint64",
gdef.TOKEN_SECURITY_ATTRIBUTE_TYPE_STRING: "pString",
gdef.TOKEN_SECURITY_ATTRIBUTE_TYPE_FQBN: "pFqbn",
# TOKEN_SECURITY_ATTRIBUTE_TYPE_SID
# TOKEN_SECURITY_ATTRIBUTE_TYPE_BOOLEAN
gdef.TOKEN_SECURITY_ATTRIBUTE_TYPE_OCTET_STRING: "pOctetString",
}
@property
def name(self):
"""The name of the security attribute"""
return self.Name.str
@property
def values(self):
"""The values of the security attribute"""
array_name = self.VALUE_ARRAY_PTR_BY_TYPE[self.ValueType]
return getattr(self.Values, array_name)[:self.ValueCount]
def __repr__(self):
return """<{0} name="{1}">""".format(type(self).__name__, self.name)
# https://docs.microsoft.com/en-us/windows/desktop/SecAuthZ/access-tokens
class Token(utils.AutoHandle):
"""Represent a Windows Token.
The attributes only documented by a type are from the :class:`~windows.generated_def.winstructs.TOKEN_INFORMATION_CLASS`, such return values may be improved version of the structure.
.. note::
see `[MSDN] TOKEN_INFORMATION_CLASS <https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ne-winnt-_token_information_class>`_
"""
def __init__(self, handle):
self._handle = handle
def _get_required_token_information_size(self, infos_class):
cbsize = gdef.DWORD()
try:
winproxy.GetTokenInformation(self.handle, infos_class, None, 0, ctypes.byref(cbsize))
except winproxy.WinproxyError as e:
if not e.winerror in (gdef.ERROR_INSUFFICIENT_BUFFER, gdef.ERROR_BAD_LENGTH):
raise
return cbsize.value
def get_token_infomations(self, infos_class, rtype):
required_size = self._get_required_token_information_size(infos_class)
requested_size = max(required_size, ctypes.sizeof(rtype))
buffer = utils.BUFFER(rtype, 1)(size=requested_size)
cbsize = gdef.DWORD()
winproxy.GetTokenInformation(self.handle, infos_class, buffer, buffer.real_size, cbsize)
return buffer[0]
def set_informations(self, info_type, infos):
return winproxy.SetTokenInformation(self.handle, info_type, ctypes.byref(infos), ctypes.sizeof(infos))
craft = meta_craft(get_token_infomations)
# https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ne-winnt-_token_information_class
TokenUser = craft(gdef.TokenUser, gdef.TOKEN_USER) #: :class:`~windows.generated_def.winstructs.TOKEN_USER`
TokenGroups = craft(gdef.TokenGroups , TokenGroupsType) #: :class:`TokenGroups`
TokenPrivileges = craft(gdef.TokenPrivileges , TokenPrivilegesType) #: :class:`TokenPrivileges`
TokenOwner = craft(gdef.TokenOwner, gdef.TOKEN_OWNER) #: :class:`~windows.generated_def.winstructs.TOKEN_OWNER`
TokenPrimaryGroup = craft(gdef.TokenPrimaryGroup, gdef.TOKEN_PRIMARY_GROUP) #: :class:`~windows.generated_def.winstructs.TOKEN_PRIMARY_GROUP`
TokenDefaultDacl = craft(gdef.TokenDefaultDacl, gdef.TOKEN_DEFAULT_DACL) #: :class:`~windows.generated_def.winstructs.TOKEN_DEFAULT_DACL`
TokenSource = craft(gdef.TokenSource, gdef.TOKEN_SOURCE) #: :class:`~windows.generated_def.winstructs.TOKEN_SOURCE`
TokenType = craft(gdef.TokenType, gdef.TOKEN_TYPE) #: :class:`~windows.generated_def.winstructs.TOKEN_TYPE`
TokenImpersonationLevel = craft(gdef.TokenImpersonationLevel, gdef.SECURITY_IMPERSONATION_LEVEL) #: :class:`~windows.generated_def.winstructs.SECURITY_IMPERSONATION_LEVEL`
TokenStatistics = craft(gdef.TokenStatistics, gdef.TOKEN_STATISTICS) #: :class:`~windows.generated_def.winstructs.TOKEN_STATISTICS`
TokenRestrictedSids = craft(gdef.TokenRestrictedSids, TokenGroupsType) #: :class:`~windows.generated_def.winstructs.TokenGroups`
TokenSessionId = craft(gdef.TokenSessionId, gdef.DWORD) #: :class:`~windows.generated_def.winstructs.DWORD`
TokenGroupsAndPrivileges = craft(gdef.TokenGroupsAndPrivileges, gdef.TOKEN_GROUPS_AND_PRIVILEGES) #: :class:`~windows.generated_def.winstructs.TOKEN_GROUPS_AND_PRIVILEGES`
# TokenSessionReference = craft(gdef.TokenSessionReference, ???) # Reserved.
TokenSandBoxInert = craft(gdef.TokenSandBoxInert, gdef.DWORD) #: :class:`~windows.generated_def.winstructs.DWORD`
# TokenAuditPolicy = craft(gdef.TokenAuditPolicy, ???) # Reserved.
TokenOrigin = craft(gdef.TokenOrigin, gdef.TOKEN_ORIGIN) #: :class:`~windows.generated_def.winstructs.TOKEN_ORIGIN`
TokenElevationType = craft(gdef.TokenElevationType, gdef.TOKEN_ELEVATION_TYPE) #: :class:`~windows.generated_def.winstructs.TOKEN_ELEVATION_TYPE`
TokenLinkedToken = craft(gdef.TokenLinkedToken, gdef.TOKEN_LINKED_TOKEN) #: :class:`~windows.generated_def.winstructs.TOKEN_LINKED_TOKEN`
TokenElevation = craft(gdef.TokenElevation, gdef.TOKEN_ELEVATION) #: :class:`~windows.generated_def.winstructs.TOKEN_ELEVATION`
TokenHasRestrictions = craft(gdef.TokenHasRestrictions, gdef.DWORD) #: :class:`~windows.generated_def.winstructs.DWORD`
TokenAccessInformation = craft(gdef.TokenAccessInformation, gdef.TOKEN_ACCESS_INFORMATION) #: :class:`~windows.generated_def.winstructs.TOKEN_ACCESS_INFORMATION`
TokenVirtualizationAllowed = craft(gdef.TokenVirtualizationAllowed, gdef.DWORD) #: :class:`~windows.generated_def.winstructs.DWORD`
TokenVirtualizationEnabled = craft(gdef.TokenVirtualizationEnabled, gdef.DWORD) #: :class:`~windows.generated_def.winstructs.DWORD`
TokenIntegrityLevel = craft(gdef.TokenIntegrityLevel, gdef.TOKEN_MANDATORY_LABEL) #: :class:`~windows.generated_def.winstructs.TOKEN_MANDATORY_LABEL`
TokenUIAccess = craft(gdef.TokenUIAccess, gdef.DWORD) #: :class:`~windows.generated_def.winstructs.DWORD`
TokenMandatoryPolicy = craft(gdef.TokenMandatoryPolicy, gdef.TOKEN_MANDATORY_POLICY) #: :class:`~windows.generated_def.winstructs.TOKEN_MANDATORY_POLICY`
TokenLogonSid = craft(gdef.TokenLogonSid, TokenGroupsType) #: :class:`TokenGroups`
TokenIsAppContainer = craft(gdef.TokenIsAppContainer, gdef.DWORD) #: :class:`~windows.generated_def.winstructs.DWORD`
TokenCapabilities = craft(gdef.TokenCapabilities, TokenGroupsType) #: :class:`TokenGroups`
TokenAppContainerSid = craft(gdef.TokenAppContainerSid, gdef.TOKEN_APPCONTAINER_INFORMATION) #: :class:`~windows.generated_def.winstructs.TOKEN_APPCONTAINER_INFORMATION`
TokenAppContainerNumber = craft(gdef.TokenAppContainerNumber, gdef.DWORD) #: :class:`~windows.generated_def.winstructs.DWORD`
TokenUserClaimAttributes = craft(gdef.TokenUserClaimAttributes, gdef.CLAIM_SECURITY_ATTRIBUTES_INFORMATION) #: :class:`~windows.generated_def.winstructs.CLAIM_SECURITY_ATTRIBUTES_INFORMATION`
TokenDeviceClaimAttributes = craft(gdef.TokenDeviceClaimAttributes, gdef.CLAIM_SECURITY_ATTRIBUTES_INFORMATION) #: :class:`~windows.generated_def.winstructs.CLAIM_SECURITY_ATTRIBUTES_INFORMATION`
# TokenRestrictedUserClaimAttributes = craft(gdef.TokenRestrictedUserClaimAttributes, ???) # Reserved.
# TokenRestrictedDeviceClaimAttributes = craft(gdef.TokenRestrictedDeviceClaimAttributes, ???) # Reserved.
TokenDeviceGroups = craft(gdef.TokenDeviceGroups, TokenGroupsType) #: :class:`TokenGroups`
TokenRestrictedDeviceGroups = craft(gdef.TokenRestrictedDeviceGroups, gdef.TOKEN_GROUPS) #: :class:`~windows.generated_def.winstructs.TOKEN_GROUPS`
# Reserved.
# Structure found in ntseapi.h (thx internet)
TokenSecurityAttributes = craft(gdef.TokenSecurityAttributes, TokenSecurityAttributesInformation) #: :class:`TokenSecurityAttributesInformation`
# Help would be appreciated for the structures of the following query type
# TokenIsRestricted = craft(gdef.TokenIsRestricted, ???) # Reserved.
TokenProcessTrustLevel = craft(gdef.TokenProcessTrustLevel, gdef.PSID) #: :class:`~windows.generated_def.winstructs.PSID`
# TokenPrivateNameSpace = craft(gdef.TokenPrivateNameSpace, gdef.ULONG) # Reserved.
# TokenSingletonAttributes = craft(gdef.TokenSingletonAttributes, ???) # Reserved.
# TokenBnoIsolation = craft(gdef.TokenBnoIsolation, ???) # Reserved.
# TokenChildProcessFlags = craft(gdef.TokenChildProcessFlags, ???) # Reserved.
# TokenIsLessPrivilegedAppContainer = craft(gdef.TokenIsLessPrivilegedAppContainer, ???) # Reserved.
# High level properties
@property
def user(self):
"""The user sid of the token
:type: :class:`~windows.generated_def.winstructs.PSID`
"""
return self.TokenUser.User.Sid
@property
def username(self):
"""The username of the token
:type: :class:`str`
"""
return self._user_and_computer_name()[1]
@property
def computername(self):
"""The computername of the token
:type: :class:`str`
"""
return self._user_and_computer_name()[0]
def _user_and_computer_name(self):
return windows.utils.lookup_sid(self.user)
groups = TokenGroups #: Alias for TokenGroups (type may change in the future for improved struct)
@property
def owner(self):
"""The owner sid of the token
:type: :class:`~windows.generated_def.winstructs.PSID`
"""
return self.TokenOwner.Owner
@property
def primary_group(self):
"""The sid of the primary group of the token
:type: :class:`~windows.generated_def.winstructs.PSID`
"""
return self.TokenPrimaryGroup.PrimaryGroup
@property
def default_dacl(self):
"""The defaul DACL of the token
:type: :class:`windows.security.Acl`
"""
return self.get_token_infomations(gdef.TokenDefaultDacl, windows.security.PAcl)[0]
# def source(self): (tok.TokenSource) ??
@property
def type(self):
"""The type (Primary / Impersonation) of the token
"""
return self.TokenType.value
@property
def impersonation_level(self):
"""The impersonation level of a ``TokenImpersonation`` token.
:raises: :class:`WindowsError` if token is not a ``TokenImpersonation``
:type: :class:`int` -- Enum value from :class:`~windows.generated_def.winstructs.SECURITY_IMPERSONATION_LEVEL`
"""
try:
return self.TokenImpersonationLevel.value
except WindowsError as e:
if (e.winerror == gdef.ERROR_INVALID_PARAMETER and
self.type != gdef.TokenImpersonation):
# raise ValueError ?
e.strerror += " This Token is not an Impersonation token"
raise
statistics = TokenStatistics #: Alias for TokenStatistics (type may change in the future for improved struct)
@property
def id(self):
"""The TokenId Specifies an unique identifier that identifies this instance of the token object.
:type: :class:`int`
"""
return int(self.TokenStatistics.TokenId)
@property
def authentication_id(self):
"""The AuthenticationId Specifies an unique identifier assigned to the session this token represents.
There can be many tokens representing a single logon session.
:type: :class:`int`
"""
return int(self.TokenStatistics.AuthenticationId)
@property
def modified_id(self):
"""The ModifiedId Specifies an unique identifier that changes each time the token is modified.
:type: :class:`int`
"""
return int(self.TokenStatistics.ModifiedId)
restricted_sids = TokenRestrictedSids #: Alias for TokenRestrictedSids (type may change in the future for improved struct)
session_id = TokenSessionId #: Alias for TokenSessionId (type may change in the future for improved struct)
@property
def groups_and_privileges(self):
"""Alias for TokenGroupsAndPrivileges (type may change in the future for improved struct)"""
# Return enhanced 'TOKEN_GROUPS_AND_PRIVILEGES' ?
return self.TokenGroupsAndPrivileges
@property
def privileges(self):
"""Alias for ``TokenPrivileges``
:type: :class:`TokenPrivileges`
"""
return self.TokenPrivileges
sandbox_inert = TokenSandBoxInert #: Alias for TokenSandBoxInert (type may change in the future for improved struct)
# def audit_policy(self):
# raise NotImplementedError("Need to find the type of TokenAuditPolicy")
@property
def origin(self):
"""The originating logon session of the token.
:type: :class:`int`
"""
origin_logon_session = self.TokenOrigin.OriginatingLogonSession
return int(origin_logon_session) # improved LUID implem __int__ :)
@property
def elevation_type(self):
"""The elevation type of the token.
:type: :class:`int` -- Enum value from :class:`~windows.generated_def.winstructs.TOKEN_ELEVATION_TYPE`
"""
return self.TokenElevationType.value
@property
def linked_token(self):
"""The token linked to our token if present (may raise else)
:type: :class:`Token`
"""
# TODO: return None if not present ?
return Token(self.TokenLinkedToken.LinkedToken)
@property
def elevated(self):
"""``True`` if token is an elevated token"""
return bool(self.TokenElevation.TokenIsElevated)
is_elevated = elevated #: Alias for ``elevated`` deprecated and may disapear
has_restriction = TokenHasRestrictions #: Alias for TokenHasRestrictions (type may change in the future for improved struct)
@property
def access_information(self):
"""Alias for TokenAccessInformation (type may change in the future for improved struct)"""
# Return enhanced subclass ?
return self.TokenAccessInformation
@property
def trust_level(self):
"""The trust level of the process if present else ``None``.
:type: :class:`~windows.generated_def.winstructs.PSID`
"""
tl = self.TokenProcessTrustLevel
if not tl: # NULL:
return None
return tl
virtualization_allowed = TokenVirtualizationAllowed #: Alias for TokenVirtualizationAllowed (type may change in the future for improved struct)
virtualization_enabled = TokenVirtualizationEnabled #: Alias for TokenVirtualizationEnabled (type may change in the future for improved struct)
@property
def integrity_level(self):
"""The integrity level and attributes of the token
:type: :class:`windows.generated_def.winstructs.SID_AND_ATTRIBUTES`
"""
return self.TokenIntegrityLevel.Label # SID_AND_ATTRIBUTES
def get_integrity(self):
"""Return the integrity level of the token
:type: :class:`int`
"""
sid = self.integrity_level.Sid
count = winproxy.GetSidSubAuthorityCount(sid)
integrity = winproxy.GetSidSubAuthority(sid, count[0] - 1)[0]
return KNOW_INTEGRITY_LEVEL[integrity]
def set_integrity(self, integrity):
"""Set the integrity level of a token
:param type: :class:`int`
"""
mandatory_label = gdef.TOKEN_MANDATORY_LABEL()
mandatory_label.Label.Attributes = 0x60
# cast integrity to int to accept SECURITY_MANDATORY_LOW_RID & other Flags
mandatory_label.Label.Sid = gdef.PSID.from_string("S-1-16-{0}".format(int(integrity)))
return self.set_informations(gdef.TokenIntegrityLevel, mandatory_label)
_INTEGRITY_PROPERTY_DOC = """The integrity of the token as an int (extracted from integrity PSID)
:getter: :func:`get_integrity`
:setter: :func:`set_integrity`
"""
integrity = property(get_integrity, set_integrity, doc=_INTEGRITY_PROPERTY_DOC)
ui_access = TokenUIAccess #: Alias for TokenUIAccess (type may change in the future for improved struct)
VALID_TOKEN_POLICIES = gdef.FlagMapper(
gdef.TOKEN_MANDATORY_POLICY_OFF,
gdef.TOKEN_MANDATORY_POLICY_NO_WRITE_UP,
gdef.TOKEN_MANDATORY_POLICY_NEW_PROCESS_MIN,
gdef.TOKEN_MANDATORY_POLICY_VALID_MASK,
)
@property
def mandatory_policy(self):
"""mandatory integrity access policy for the associated token
:type: :class:`int` -- see `[MSDN] mandatory policy <https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ns-winnt-_token_mandatory_policy>`_
"""
return self.VALID_TOKEN_POLICIES[self.TokenMandatoryPolicy.Policy]
@property
def logon_sid(self):
"""The logon sid of the token. (Case of multiple logon sid not handled and will raise AssertionError)
:type: :class:`windows.generated_def.winstructs.SID_AND_ATTRIBUTES`
"""
rgroups = self.TokenLogonSid
assert rgroups.GroupCount == 1, "More than 1 TokenLogonSid"
return rgroups.Groups[0]
is_appcontainer = TokenIsAppContainer #: Alias for TokenIsAppContainer (type may change in the future for improved struct)
capabilities = TokenCapabilities #: Alias for TokenCapabilities (type may change in the future for improved struct)
@property
def appcontainer_sid(self):
"""The sid of the TokenAppContainerSid if present else ``None``
:type: :class:`~windows.generated_def.winstructs.PSID`
"""
sid = self.TokenAppContainerSid.TokenAppContainer
if not sid: # NULL
return None
return sid
appcontainer_number = TokenAppContainerNumber #: Alias for TokenAppContainerNumber (type may change in the future for improved struct)
@property
def security_attributes(self):
"""The security attributes of the token
:type: [:class:`TokenSecurityAttributeV1`] - A list of token security attributes
"""
return self.TokenSecurityAttributes.attributes
## Token Methods
def duplicate(self, access_rigth=gdef.MAXIMUM_ALLOWED, attributes=None, type=None, impersonation_level=None):
"""Duplicate the token into a new :class:`Token`.
:param type: The type of token: ``TokenPrimary(0x1L)`` or ``TokenImpersonation(0x2L)``
:param impersonation_level: The :class:`~windows.generated_def.winstructs.SECURITY_IMPERSONATION_LEVEL` for a ``TokenImpersonation(0x2L)``:
- If ``type`` is ``TokenPrimary(0x1L)`` this parameter is ignored if ``None`` or used as-is.
- If ``type`` is ``TokenImpersonation(0x2L)`` and this parameter is None, ``self.impersonation_level`` is used.
- If ``type`` is ``TokenImpersonation(0x2L)`` and our Token is a ``TokenPrimary(0x1L)`` this parameter MUST be provided
:returns: :class:`Token` - The duplicate token
Example:
>>> tok
<Token TokenId=0x39d6dde5 Type=TokenPrimary(0x1L)>
>>> tok.duplicate()
<Token TokenId=0x39d7b206 Type=TokenPrimary(0x1L)>
>>> tok.duplicate(type=gdef.TokenImpersonation)
...
ValueError: Duplicating a PrimaryToken as a TokenImpersonation require explicit <impersonation_level> parameter
>>> tok.duplicate(type=gdef.TokenImpersonation, impersonation_level=gdef.SecurityImpersonation)
<Token TokenId=0x39dadbf8 Type=TokenImpersonation(0x2L) ImpersonationLevel=SecurityImpersonation(0x2L)>
"""
newtoken = gdef.HANDLE()
if type is None:
type = self.type
if impersonation_level is None:
if self.type == gdef.TokenImpersonation:
impersonation_level = self.impersonation_level
elif type != gdef.TokenImpersonation:
impersonation_level = 0 #: ignored
else:
raise ValueError("Duplicating a PrimaryToken as a TokenImpersonation require explicit <impersonation_level> parameter")
winproxy.DuplicateTokenEx(self.handle, access_rigth, attributes, impersonation_level, type, newtoken)
return bltn_type(self)(newtoken.value)
def adjust_privileges(self, privileges):
"""Adjust the token privileges according to ``privileges``.
This API is the `complex one` to adjust multiple privileges at once.
To simply enable one privilege see :func:`enable_privilege`.
:param privileges: :class:`~windows.generated_def.winstructs.TOKEN_PRIVILEGES` (or subclass as :class:`TokenPrivileges`). To easily update your token privileges use the result of :data:`privileges`.
Example:
>>> tok = windows.current_process.token
>>> privs = tok.privileges
>>> privs["SeShutdownPrivilege"] = gdef.SE_PRIVILEGE_ENABLED
>>> privs["SeUndockPrivilege"] = gdef.SE_PRIVILEGE_ENABLED
>>> tok.adjust_privileges(privs)
"""
buffsize = None
if isinstance(privileges, TokenPrivilegesType):
# The TokenPrivilegesType should come from a PTR via Improved buffer
try:
buffsize = privileges._b_base_.real_size
except AttributeError as e:
pass
if buffsize is None:
buffsize = ctypes.sizeof(privileges)
winproxy.AdjustTokenPrivileges(self.handle, False, privileges, buffsize, None, None)
if winproxy.GetLastError() == gdef.ERROR_NOT_ALL_ASSIGNED:
# Transform this in a real WindowsError
raise WindowsError(gdef.ERROR_NOT_ALL_ASSIGNED, "Failed to adjust all privileges")
def enable_privilege(self, name):
"""Enable privilege ``name`` in the token
:raises: :class:`ValueError` if :class:`Token` has no privilege ``name``
"""
privs = self.privileges
try:
privs[name] = gdef.SE_PRIVILEGE_ENABLED
except KeyError as e:
# Emulate the WindowsError that would be triggered in 'adjust_privileges' ?
raise ValueError("{0} has no privilege <{1}>".format(self, name))
return self.adjust_privileges(privs)
def __repr__(self):
flag_repr = gdef.Flag.__repr__
try:
tid_int = int(self.TokenStatistics.TokenId) # May raise -> which is bad as __repr__ may be called on __del__...
except WindowsError as e:
return object.__repr__(self)
toktype = self.type
if toktype == gdef.TokenPrimary:
return "<{0} TokenId={1:#x} Type={2}>".format(type(self).__name__, tid_int, flag_repr(toktype))
return "<{0} TokenId={1:#x} Type={2} ImpersonationLevel={3}>".format(type(self).__name__, tid_int, flag_repr(toktype), flag_repr(self.impersonation_level))
import windows.security
@@ -1,88 +0,0 @@
import ctypes
import windows
from windows import winproxy
import windows.generated_def as gdef
from windows.generated_def.winstructs import *
from windows.utils import AutoHandle
import sys
class LogicalDrive(AutoHandle):
DRIVE_TYPE = gdef.FlagMapper(DRIVE_UNKNOWN, DRIVE_NO_ROOT_DIR, DRIVE_REMOVABLE,
DRIVE_FIXED, DRIVE_REMOTE, DRIVE_CDROM, DRIVE_RAMDISK)
def __init__(self, name):
self.name = name
@property
def type(self):
"""The type of drive, values are:
* DRIVE_UNKNOWN(0x0L)
* DRIVE_NO_ROOT_DIR(0x1L)
* DRIVE_REMOVABLE(0x2L)
* DRIVE_FIXED(0x3L)
* DRIVE_REMOTE(0x4L)
* DRIVE_CDROM(0x5L)
* DRIVE_RAMDISK(0x6L)
:type: :class:`long` or :class:`int` (or subclass)
"""
t = winproxy.GetDriveTypeA(self.name)
return self.DRIVE_TYPE.get(t,t)
@property
def path(self):
"""The target path of the device
:type: :class:`str`"""
res = query_dos_device(self.name.strip("\\"))
if len(res) != 1:
raise ValueError("[Unexpected result] query_dos_device(logicaldrive) returned multiple path")
return res[0]
def query_info(self, info):
return windows.utils.query_volume_information(self.handle, info)
@property
def volume_info(self):
return self.query_info(gdef.FileFsVolumeInformation)
@property
def serial(self):
return self.volume_info.VolumeSerialNumber
def _get_handle(self):
nt_name = windows.utils.dospath_to_ntpath(self.name)
handle = windows.winproxy.CreateFileA(nt_name, gdef.GENERIC_READ,
gdef.FILE_SHARE_READ, None, gdef.OPEN_EXISTING, gdef.FILE_FLAG_BACKUP_SEMANTICS , None)
return handle
def __repr__(self):
return """<{0} "{1}" ({2})>""".format(type(self).__name__, self.name, self.type.name)
def enum_logical_drive():
return [LogicalDrive(name) for name in get_logical_drive_names()]
def get_logical_drive_names():
size = 0x100
buffer = ctypes.c_buffer(size)
rsize = winproxy.GetLogicalDriveStringsA(0x1000, buffer)
return buffer[:rsize].rstrip(b"\x00").split(b"\x00")
def get_info(drivename):
size = 0x1000
volume_name = ctypes.c_buffer(size)
fs_name = ctypes.c_buffer(size)
flags = DWORD()
winproxy.GetVolumeInformationA(drivename, volume_name, size, None, None, ctypes.byref(flags), fs_name, size)
return volume_name[:10], fs_name[:10]
def query_dos_device(name):
size = 0x1000
buffer = ctypes.c_buffer(size)
rsize = winproxy.QueryDosDeviceA(name, buffer, size)
return buffer[:rsize].rstrip(b"\x00").split(b"\x00")
-473
View File
@@ -1,473 +0,0 @@
import windows
import ctypes
import struct
import functools
from functools import partial
from collections import namedtuple
from ctypes.wintypes import *
import windows.com
import windows.generated_def as gdef
from windows.generated_def.winstructs import *
from windows.pycompat import basestring
# Common error check for all WMI COM interfaces
# This 'just' add the corresponding 'WBEMSTATUS' to the hresult error code
class WmiComInterface(object):
"""Base class used for COM call error checking for WMI interfaces"""
def errcheck(self, result, func, args):
if result < 0:
wmitag = gdef.WBEMSTATUS.mapper[result & 0xffffffff]
raise ctypes.WinError(result, wmitag)
return args
sentinel = object()
# POC
class QualifierSet(gdef.IWbemQualifierSet):
def get_variant(self, name):
"""Retrieve the value of property ``name`` as a :class:`~windows.com.Variant`
:return: :class:`~windows.com.Variant`
"""
if not isinstance(name, basestring):
nametype = type(name).__name__
raise TypeError("WmiObject attributes name must be str, not <{0}>".format(nametype))
variant_res = windows.com.Variant()
self.Get(name, 0, variant_res, None)
return variant_res
def get(self, name, default=sentinel):
"""Return the value of the property ``name``. The return value depends of the type of the property and can vary"""
try:
return self.get_variant(name).value
except WindowsError as e:
if (e.winerror & 0xffffffff) != gdef.WBEM_E_NOT_FOUND:
raise
if default is sentinel:
raise
return default
def names(self):
res = POINTER(windows.com.SafeArray)()
x = ctypes.pointer(res)
self.GetNames(0, cast(x, POINTER(POINTER(gdef.SAFEARRAY))))
# need to free the safearray / unlock ?
properties = [p for p in res[0].to_list(BSTR)]
return properties
# https://docs.microsoft.com/en-us/windows/desktop/api/wbemcli/nn-wbemcli-iwbemclassobject
WmiMethod = namedtuple("WmiMethod", ["inparam", "outparam"])
# https://docs.microsoft.com/en-us/windows/desktop/WmiSdk/calling-a-method
class WmiObject(gdef.IWbemClassObject, WmiComInterface):
"""The WmiObject (which wrap ``IWbemClassObject``) contains and manipulates both class definitions and class object instances.
Can be used as a mapping to access properties.
"""
def get_variant(self, name):
"""Retrieve the value of property ``name`` as a :class:`~windows.com.Variant`
:return: :class:`~windows.com.Variant`
"""
if not isinstance(name, basestring):
nametype = type(name).__name__
raise TypeError("WmiObject attributes name must be str, not <{0}>".format(nametype))
variant_res = windows.com.Variant()
self.Get(name, 0, variant_res, None, None)
return variant_res
def get(self, name):
"""Return the value of the property ``name``. The return value depends of the type of the property and can vary"""
return self.get_variant(name).value
def get_method(self, name):
"""Return the information about the method ``name``
:returns: :class:`WmiMethod`
"""
inpararm = type(self)()
outpararm = type(self)()
variant_res = windows.com.Variant()
self.GetMethod(name, 0, inpararm, outpararm)
return WmiMethod(inpararm, outpararm)
def put_variant(self, name, variant):
if not isinstance(name, basestring):
nametype = type(name).__name__
raise TypeError("WmiObject attributes name must be str, not <{0}>".format(nametype))
return self.Put(name, 0, variant, 0)
def put(self, name, value):
"""Set the property ``name`` to ``value``"""
variant_value = windows.com.Variant(value)
return self.put_variant(name, variant_value)
def spawn_instance(self):
"""Create a new object of the class represented by the current :class:`WmiObject`
:returns: :class:`WmiObject`
"""
instance = type(self)()
self.SpawnInstance(0, instance)
return instance
@property
def genus(self):
"""The genus of the object.
:returns: ``WBEM_GENUS_CLASS(0x1L)`` if the :class:`WmiObject` is a Class and ``WBEM_GENUS_INSTANCE(0x2L)`` for instances and events.
"""
return gdef.tag_WBEM_GENUS_TYPE.mapper[self.get("__GENUS")]
## Higher level API
def get_properties(self, system_properties=False):
"""Return the list of properties names available for the current object.
If ``system_properties`` is ``False`` property names begining with ``_`` are ignored.
:returns: [:class:`str`] -- A list of string
.. note:
About system properties: https://docs.microsoft.com/en-us/windows/desktop/wmisdk/wmi-system-properties
"""
res = POINTER(windows.com.SafeArray)()
x = ctypes.pointer(res)
self.GetNames(None, 0, None, cast(x, POINTER(POINTER(gdef.SAFEARRAY))))
# need to free the safearray / unlock ?
properties = [p for p in res[0].to_list(BSTR) if system_properties or (not p.startswith("_"))]
return properties
properties = property(get_properties) #: The properties of the object (exclude system properties)
@property
def qualifier_set(self): # changer de nom ?
res = QualifierSet()
self.GetQualifierSet(res)
return res
def get_p_set(self, name): # Changer de nom ?
res = QualifierSet()
self.GetPropertyQualifierSet(name, res)
return res
# Make WmiObject a mapping object
def keys(self):
"""The properties of the object (include system properties)"""
return self.get_properties(system_properties=True)
__getitem__ = get
__setitem__ = put
def items(self):
return [(k, self.get(k)) for k in self.properties]
def values(self): # Not sur anyone will use this but keep the dict interface
return [x[1] for x in self.items()]
## Make it callable like any class :D
__call__ = spawn_instance
def __repr__(self):
if not self:
return """<{0} (NULL)>""".format(type(self).__name__,)
if self.genus == gdef.WBEM_GENUS_CLASS:
return """<{0} class "{1}">""".format(type(self).__name__, self.get("__Class"))
return """<{0} instance of "{1}">""".format(type(self).__name__, self.get("__Class"))
def __sprint__(self):
return """ {0}\n
{1}
""".format(repr(self), "\n".join(": ".join([x[0], str(x[1])]) for x in sorted(self.items())))
class WmiEnumeration(gdef.IEnumWbemClassObject, WmiComInterface):
"""Represent an enumeration of object that can be itered"""
DEFAULT_TIMEOUT = gdef.WBEM_INFINITE #: The default timeout
def next(self, timeout=None):
"""Return the next object in the enumeration with `timeout`.
:raises: ``WindowsError(WBEM_S_TIMEDOUT)`` if timeout expire
:returns: :class:`WmiObject`
"""
timeout = self.DEFAULT_TIMEOUT if timeout is None else timeout
# For now the count is hardcoded to 1
obj = WmiObject()
return_count = gdef.ULONG(0)
error = self.Next(timeout, 1, obj, return_count)
if error == gdef.WBEM_S_TIMEDOUT:
raise ctypes.WinError(gdef.WBEM_S_TIMEDOUT, "Wmi timeout")
elif error == WBEM_S_FALSE:
return None
else:
return obj
def __iter__(self):
"""Return an iterator with ``DEFAULT_TIMEOUT``"""
return self.iter_timeout(self.DEFAULT_TIMEOUT)
def iter_timeout(self, timeout=None):
"""Return an iterator with a custom ``timeout``"""
while True:
obj = self.next(timeout)
if obj is None:
return
yield obj
def all(self):
"""Return all elements in the enumeration as a list
:returns: [:class:`WmiObject`] - A list of :class:`WmiObject`
"""
return list(self) # SqlAlchemy like :)
class WmiCallResult(gdef.IWbemCallResult, WmiComInterface):
"""The result of a WMI call/query. Real result value type depends of the context"""
def __init__(self, result_type=None, namespace_name=None):
self.result_type = result_type
self.namespace_name = namespace_name
def get_call_status(self, timeout=gdef.WBEM_INFINITE):
"""The status of the call"""
status = gdef.LONG()
self.GetCallStatus(timeout, status)
return WBEMSTATUS.mapper[status.value & 0xffffffff]
def get_result_object(self, timeout=gdef.WBEM_INFINITE):
"""The result as a :class:`WmiObject` (returned by :func:`WmiNamespace.exec_method`)"""
result = WmiObject()
self.GetResultObject(timeout, result)
return result
def get_result_string(self, timeout=gdef.WBEM_INFINITE):
"""The result as a :class:`WmiObject` (returned by :func:`WmiNamespace.put_instance`)"""
result = gdef.BSTR()
self.GetResultString(timeout, result)
return result
def get_result_service(self, timeout=gdef.WBEM_INFINITE):
"""The result as a :class:`WmiNamespace` (not used yet)"""
result = WmiNamespace()
self.GetResultServices(timeout, result)
return result
@property
def result(self):
"""The result of the correct type based on ``self.result_type``"""
if self.result_type is None:
raise ValueError("Cannot call <result> with no result_type")
return getattr(self, "get_result_" + self.result_type)()
class WmiLocator(gdef.IWbemLocator, WmiComInterface):
pass # Just for the WMI errcheck callback
# !TEST CODE
class WmiNamespace(gdef.IWbemServices, WmiComInterface):
r"""An object to perform wmi request to a given ``namespace``"""
#CLSID_WbemAdministrativeLocator_IID = windows.com.IID.from_string('CB8555CC-9128-11D1-AD9B-00C04FD8FDFF')
WbemLocator_CLSID = windows.com.IID.from_string('4590F811-1D3A-11D0-891F-00AA004B2E24')
DEFAULT_ENUM_FLAGS = (gdef.WBEM_FLAG_RETURN_IMMEDIATELY |
WBEM_FLAG_FORWARD_ONLY) #: The defauls flags used for enumeration. ``(WBEM_FLAG_RETURN_IMMEDIATELY | WBEM_FLAG_FORWARD_ONLY)``
def __init__(self, namespace):
self.name = namespace
@classmethod
def connect(cls, namespace, user=None, password=None):
"""Connect to ``namespace`` using ``user`` and ``password`` for authentification if given
:return: :class:`WmiNamespace` - The connected :class:`WmiNamespace`"""
# this method assert com is initialised
self = cls(namespace) # IWbemServices subclass
locator = WmiLocator()
windows.com.create_instance(cls.WbemLocator_CLSID, locator)
locator.ConnectServer(namespace, user, password , None, gdef.WBEM_FLAG_CONNECT_USE_MAX_WAIT, None, None, self)
locator.Release()
return self
def query(self, query):
"""Return the list of :class:`WmiObject` matching ``query``.
This API is the `simple one`, if you need timeout or complexe feature see :func:`exec_query`
:return: [:class:`WmiObject`] - A list of :class:`WmiObject`
"""
return list(self.exec_query(query))
def select(self, clsname, deep=True):
"""Return the list of :class:`WmiObject` that are instance of ``clsname``. Deep has the same meaning as in :func:`create_instance_enum`.
This API is the `simple one`, if you need timeout or complexe feature see :func:`create_instance_enum`
:return: [:class:`WmiObject`] - A list of :class:`WmiObject`
"""
return list(self.create_instance_enum(clsname, deep=deep))
def exec_query(self, query, flags=DEFAULT_ENUM_FLAGS, ctx=None):
"""Execute a WQL query with custom flags and returns a ::class:`WmiEnumeration` that can be used to
iter the result with timeouts
:returns: :class:`WmiEnumeration`
"""
enumerator = WmiEnumeration()
self.ExecQuery("WQL", query, flags, ctx, enumerator)
return enumerator
# Create friendly name for create_class_enum & create_instance_enum ?
def create_class_enum(self, superclass, flags=DEFAULT_ENUM_FLAGS, deep=True):
"""Enumerate the classes in the ``namespace`` that match ``superclass``.
if ``superclass`` is None will enumerate all top-level class. ``deep`` allow to returns all subclasses
:returns: :class:`WmiEnumeration`
.. note::
See https://docs.microsoft.com/en-us/windows/desktop/api/wbemcli/nf-wbemcli-iwbemservices-createclassenum
"""
flags |= gdef.WBEM_FLAG_DEEP if deep else gdef.WBEM_FLAG_SHALLOW
enumerator = WmiEnumeration()
self.CreateClassEnum(superclass, flags, None, enumerator)
return enumerator
@property
def classes(self):
"""The list of classes in the namespace. This a a wrapper arround :func:`create_class_enum`.
:return: [:class:`WmiObject`] - A list of :class:`WmiObject`
"""
return self.create_class_enum(None, deep=True)
def create_instance_enum(self, clsname, flags=DEFAULT_ENUM_FLAGS, deep=True):
"""Enumerate the instances of ``clsname``. Deep allows to enumerate the instance of subclasses as well
:returns: :class:`WmiEnumeration`
Example:
>>> windows.system.wmi["root\\subscription"].create_instance_enum("__EventConsumer", deep=False).all()
[]
>>> windows.system.wmi["root\\subscription"].create_instance_enum("__EventConsumer", deep=True).all()
[<WmiObject instance of "NTEventLogEventConsumer">]
.. note::
See https://docs.microsoft.com/en-us/windows/desktop/api/wbemcli/nf-wbemcli-iwbemservices-createinstanceenum
"""
flags |= gdef.WBEM_FLAG_DEEP if deep else gdef.WBEM_FLAG_SHALLOW
enumerator = WmiEnumeration()
self.CreateInstanceEnum(clsname, flags, None, enumerator)
return enumerator
def get_object(self, path):
"""Return the object matching ``path``. If ``path`` is a class name return the class object``
:return: :class:`WmiObject`
"""
result = WmiObject()
self.GetObject(path, gdef.WBEM_FLAG_RETURN_WBEM_COMPLETE, None, result, None)
return result
def put_instance(self, instance, flags=gdef.WBEM_FLAG_CREATE_ONLY):
"""Creates or updates an instance of an existing class in the namespace
:return: :class:`WmiCallResult` ``(string)`` - Used to retrieve the string representing the path of the object created/updated
"""
res = WmiCallResult(result_type="string")
self.PutInstance(instance, flags, None, res)
return res
def delete_instance(self, instance, flags=0):
"""TODO: Document"""
if isinstance(instance, gdef.IWbemClassObject):
instance = instance["__Path"]
return self.DeleteInstance(instance, flags, None, None)
def exec_method(self, obj, method, inparam, flags=0):
"""Exec method named on ``object`` with ``inparam``.
:params obj: The :class:`WmiObject` or path of the object the call apply to
:params method: The name of the method to call on the object
:params inparam: The :class:`WmiObject` representing the input parameters and retrieve using :func:`WmiObject.get_method`
:returns: :class:`WmiCallResult` ``(object)`` if flag `WBEM_FLAG_RETURN_IMMEDIATELY` was passed
:returns: :class:`WmiObject` the outparam object if flag `WBEM_FLAG_RETURN_IMMEDIATELY` was NOT passed
.. note::
This API will lakely change to better wrap with WmiObject/inparam/Dict & co
"""
if flags & gdef.WBEM_FLAG_RETURN_IMMEDIATELY:
# semisynchronous call -> WmiCallResult
result = WmiCallResult(result_type="object")
outparam = None
else:
# Synchronous call -> WmiObject (outparam)
result = None
outparam = WmiObject()
if isinstance(obj, gdef.IWbemClassObject):
obj = obj.get("__Path")
# Flags 0 -> synchronous call
# No WmiCallResult result is directly in outparam
self.ExecMethod(obj, method, 0, None, inparam, outparam, result)
return outparam or result
def __repr__(self):
null = "" if self else " (NULL)"
return """<{0} "{1}"{2}>""".format(type(self).__name__, self.name, null)
class WmiManager(dict):
"""The main WMI class exposed, used to list and access differents WMI namespace, can be used as a dict to access
:class:`WmiNamespace` by name
Example:
>>> windows.system.wmi["root\\SecurityCenter2"]
<WmiNamespace "root\SecurityCenter2">
"""
DEFAULT_NAMESPACE = "root\\cimv2" #: The default namespace for :func:`select` & :func:`query`
def __init__(self):
# Someone is going to use wmi: let's init com !
windows.com.init()
self.wmi_requester_by_namespace = {}
@property
def default_namespace(self):
return self[self.DEFAULT_NAMESPACE]
@property
def select(self):
r""":func:`WmiRequester.select` for default WMI namespace 'root\\cimv2'"""
return self.default_namespace.select
@property
def query(self):
r""":func:`WmiRequester.query` for default WMI namespace 'root\\cimv2'"""
return self.default_namespace.query
def get_subnamespaces(self, root="root"):
return [x["Name"] for x in self[root].select("__NameSpace")]
namespaces = property(get_subnamespaces)
"""The list of available WMI namespaces"""
def _open_wmi_requester(self, namespace):
return WmiNamespace.connect(namespace)
def __missing__(self, key):
self[key] = self._open_wmi_requester(key)
return self[key]
def __repr__(self):
return object.__repr__(self)
@@ -1,3 +0,0 @@
from .apiproxy import is_implemented, get_target, resolve
from .error import WinproxyError, ExportNotFound
from .apis import * # Import all functions
@@ -1,122 +0,0 @@
import ctypes
import functools
import windows.generated_def as gdef
from .error import ExportNotFound
from windows.pycompat import is_py3
# Utils
def is_implemented(apiproxy):
"""Return :obj:`True` if DLL/Api can be found"""
try:
apiproxy.force_resolution()
except ExportNotFound:
return False
return True
def get_target(apiproxy):
"""POC for newshook"""
return apiproxy.target_dll, apiproxy.target_func
def resolve(apiproxy):
"""Resolve the address of ``apiproxy``. Might raise if ``apiproxy`` is not implemented"""
apiproxy.force_resolution()
func = ctypes.WinDLL(apiproxy.target_dll)[apiproxy.target_func]
return ctypes.cast(func, gdef.PVOID).value
class NeededParameterType(object):
_inst = None
def __new__(cls):
if cls._inst is None:
cls._inst = super(NeededParameterType, cls).__new__(cls)
return cls._inst
def __repr__(self):
return "NeededParameter"
NeededParameter = NeededParameterType()
sentinel = object()
class ApiProxy(object):
APIDLL = None
"""Create a python wrapper around a kernel32 function"""
def __init__(self, func_name=None, error_check=sentinel, deffunc_module=None):
self.deffunc_module = deffunc_module if deffunc_module is not None else gdef.winfuncs
self.func_name = func_name
if error_check is sentinel:
error_check = self.default_error_check
self.error_check = error_check
self._cprototyped = None
def __call__(self, python_proxy):
# Use the name of the sub-function if None was given
if self.func_name is None:
self.func_name = python_proxy.__name__
errchk = None
if self.error_check is not None:
errchk = functools.wraps(self.error_check)(functools.partial(self.error_check, self.func_name))
prototype = getattr(self.deffunc_module, self.func_name + "Prototype")
params = getattr(self.deffunc_module, self.func_name + "Params")
python_proxy.prototype = prototype
python_proxy.params = params
python_proxy.errcheck = errchk
python_proxy.target_dll = self.APIDLL
python_proxy.target_func = self.func_name
# Give access to the 'ApiProxy' object from the function
python_proxy.proxy = self
params_name = [param[1] for param in params]
if (self.error_check.__doc__):
doc = python_proxy.__doc__
doc = doc if doc else ""
python_proxy.__doc__ = doc + "\nErrcheck:\n " + self.error_check.__doc__
def generate_ctypes_function():
try:
api_dll = ctypes.windll[self.APIDLL]
except WindowsError as e:
if e.winerror == gdef.ERROR_BAD_EXE_FORMAT:
e.strerror = e.strerror.replace("%1", "<{0}>".format(self.APIDLL))
raise
try:
c_prototyped = prototype((self.func_name, api_dll), params)
except (AttributeError, WindowsError):
raise ExportNotFound(self.func_name, self.APIDLL)
if errchk is not None:
c_prototyped.errcheck = errchk
self._cprototyped = c_prototyped
def perform_call(*args):
if self._cprototyped is None:
generate_ctypes_function()
try:
return self._cprototyped(*args)
except ctypes.ArgumentError as e:
# We just add a conversion ctypes argument fail
# We can do some heavy computation if needed
# Not a case that normally happen
# "argument 2: <type 'exceptions.TypeError'>: wrong type"
# Thx ctypes..
argnbstr, ecx, reason = e.args[0].split(":") # py2 / py3 compat :)
if not argnbstr.startswith("argument "):
raise # Don't knnow if it can happen
argnb = int(argnbstr[len("argument "):])
badarg = args[argnb - 1]
if badarg is NeededParameter:
badargname = params_name[argnb - 1]
raise TypeError("{0}: Missing Mandatory parameter <{1}>".format(self.func_name, badargname))
# Not NeededParameter: the caller need to fix the used param :)
# raise the real ctypes error
raise
setattr(python_proxy, "ctypes_function", perform_call)
setattr(python_proxy, "force_resolution", generate_ctypes_function)
return python_proxy
@@ -1,27 +0,0 @@
from .advapi32 import *
from .cfgmgr32 import *
from .crypt32 import *
from .cryptui import *
from .dbghelp import *
from .dnsapi import *
from .iphlpapi import *
from .kernel32 import *
from .ktmw32 import *
from .ntdll import *
from .netapi32 import *
from .ole32 import *
from .oleaut32 import *
from .oleacc import *
from .psapi import *
from .setupapi import *
from .shell32 import *
from .shlwapi import *
from .tdh import *
from .user32 import *
from .version import *
from .virtdisk import *
from .wevtapi import *
from .winhttp import *
from .wininet import *
from .wintrust import *
from .ws2_32 import *

Some files were not shown because too many files have changed in this diff Show More