mirror of
https://github.com/naksyn/PythonMemoryModule
synced 2026-06-06 16:24:25 +00:00
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:
@@ -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")
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user