command line support (partial) via PEB stomping

This update include support to passing command line parameters to unmanaged exe via PEB stomping.
This technique is not working with every executable since it depends on which functions are used to pass arguments.
Generally, to get a universally working technique would be required to hook GetCommandlineA GetCommandlineW __getmainargs and __wgetmainargs since PEB stomping won't cover all cases, more details here:
https://blog-30cm-tw.translate.goog/2020/08/windows-c-mainargc-argv.html?_x_tr_sl=auto&_x_tr_tl=en&_x_tr_hl=it&_x_tr_pto=wapp

However, during my testing I found that mimikatz and several go binaries are working just by doing PEB stomping.
On the other hand, cmdline passing via PEB stomping alone to mingw and VS compiled binaries won't likely work.
This commit is contained in:
naksyn
2023-07-27 06:44:29 -07:00
parent 63ebe1c4ba
commit db1893910c
160 changed files with 70537 additions and 42 deletions
@@ -0,0 +1,181 @@
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,
}
@@ -0,0 +1,264 @@
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
@@ -0,0 +1,515 @@
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
@@ -0,0 +1,449 @@
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)
@@ -0,0 +1,375 @@
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
@@ -0,0 +1,63 @@
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
@@ -0,0 +1,196 @@
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
@@ -0,0 +1,447 @@
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`]
"""
@@ -0,0 +1,226 @@
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
@@ -0,0 +1,450 @@
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)
@@ -0,0 +1,191 @@
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)
@@ -0,0 +1,508 @@
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
@@ -0,0 +1,53 @@
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)
@@ -0,0 +1,485 @@
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)
@@ -0,0 +1,626 @@
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
@@ -0,0 +1,88 @@
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
@@ -0,0 +1,473 @@
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)