From 5aa181c4cf5ab3bb8d87575cf6d28693311fe7a0 Mon Sep 17 00:00:00 2001 From: lucasg Date: Mon, 13 Apr 2020 18:55:19 +0200 Subject: [PATCH] [Device] Implement API to retrieve resources associated with devices --- samples/device/device_manager.py | 9 +- windows/generated_def/winfuncs.py | 13 +++ windows/winobject/device.py | 138 ++++++++++++++++++++++++++++-- windows/winproxy/apis/cfgmgr32.py | 108 ++++++++++++++++++----- windows/winproxy/apis/setupapi.py | 6 -- 5 files changed, 238 insertions(+), 36 deletions(-) diff --git a/samples/device/device_manager.py b/samples/device/device_manager.py index c5c5733..b4beca1 100644 --- a/samples/device/device_manager.py +++ b/samples/device/device_manager.py @@ -7,11 +7,14 @@ def main(): print("-Class %s %s [%s]" % (device_class.name, " "*(60 - min(60,len(device_class.name))), device_class.guid)) for device in device_class.devices: - if device.name != "N/A": + if device.name != None: print(" -Device : %s" % (device.name)) + else: + print(" -Device : N/A") - # for resource in device.resources: - # print(' -%s : [0x%08x - 0x%08x] (0x%04x)' % (resource.type, resource.start, resource.end, resource.flags)) + for resource in device.resources: + print(' -%s' % (resource)) + # print(' -%s : [0x%08x - 0x%08x] (0x%04x)' % (resource.type, resource.start, resource.end, resource.flags)) diff --git a/windows/generated_def/winfuncs.py b/windows/generated_def/winfuncs.py index 203dbeb..b613ce6 100644 --- a/windows/generated_def/winfuncs.py +++ b/windows/generated_def/winfuncs.py @@ -3158,7 +3158,20 @@ FindCloseParams = ((1, 'hFindFile'),) CM_Enumerate_ClassesPrototype = WINFUNCTYPE(CR_STATUS, DWORD, POINTER(GUID), DWORD) CM_Enumerate_ClassesParams = ((1, 'ClassIndex'), (1, 'pGUID'), (1,'Params'),) +CM_Get_First_Log_ConfPrototype = WINFUNCTYPE(CR_STATUS, PHANDLE, HANDLE, ULONG) +CM_Get_First_Log_ConfParams = ((1, 'plcLogConf'), (1, 'dnDevInst'), (1,'ulFlags'),) +CM_Get_Next_Res_DesPrototype = WINFUNCTYPE(CR_STATUS, PHANDLE, HANDLE, ULONG, PULONG, ULONG) +CM_Get_Next_Res_DesParams = ((1, 'prdResDes'),(1, 'rdResDes'),(1, 'ForResource'),(1, 'pResourceID'),(1, 'ulFlags'),) + +CM_Free_Res_Des_HandlePrototype = WINFUNCTYPE(CR_STATUS, HANDLE) +CM_Free_Res_Des_HandleParams = ((1, 'rdResDes'),) + +CM_Get_Res_Des_Data_SizePrototype = WINFUNCTYPE(CR_STATUS, PULONG, HANDLE, ULONG) +CM_Get_Res_Des_Data_SizeParams = ((1, 'pulSize'),(1, 'rdResDes'),(1, 'ulFlags'),) + +CM_Get_Res_Des_DataPrototype = WINFUNCTYPE(CR_STATUS, HANDLE, PVOID, ULONG, ULONG) +CM_Get_Res_Des_DataParams = ((1, 'rdResDes'), (1, 'Buffer'), (1, 'BufferLen'), (1, 'ulFlags'),) SetupDiClassNameFromGuidAPrototype = WINFUNCTYPE(BOOL, POINTER(GUID), LPCSTR, DWORD , POINTER(DWORD)) diff --git a/windows/winobject/device.py b/windows/winobject/device.py index 70ecd74..53cd2fd 100644 --- a/windows/winobject/device.py +++ b/windows/winobject/device.py @@ -7,23 +7,99 @@ import windows.generated_def as gdef SPDRP_DEVICEDESC = 0x0000000 SPDRP_FRIENDLYNAME = 0x000000C +# Resource types +# source : +ResourceType_All = 0x00000000 # query every resource available +ResourceType_Mem = 0x00000001 # Physical address resource +ResourceType_IO = 0x00000002 # Physical I/O address resource +ResourceType_DMA = 0x00000003 # DMA channels resource +ResourceType_IRQ = 0x00000004 # IRQ resource +ResourceType_DoNotUse = 0x00000005 # Do not use it, idiot +ResourceType_BusNumber = 0x00000006 # (PCI) bus number +ResourceType_MAX = 0x00000007 + +# Log conf +# source : https://docs.microsoft.com/en-us/windows/win32/api/cfgmgr32/nf-cfgmgr32-cm_get_first_log_conf +BASIC_LOG_CONF = 0x00000000 # basic configuration information. +FILTERED_LOG_CONF = 0x00000001 # filtered configuration information. +ALLOC_LOG_CONF = 0x00000002 # allocated configuration information. +BOOT_LOG_CONF = 0x00000003 # boot configuration information. +FORCED_LOG_CONF = 0x00000004 # forced configuration information. +OVERRIDE_LOG_CONF = 0x00000005 # override configuration information. + +class AbstractDeviceResource(object): + """ An abstract Python object representing a setup device resource. """ + pass + +class MmioDeviceResource(AbstractDeviceResource): + """ A Python object representing a setup device MMIO resource. """ + + def __init__(self, data): + pass + +class IoDeviceResource(AbstractDeviceResource): + """ A Python object representing a setup device IO port resource. """ + + def __init__(self, data): + pass + +class DmaDeviceResource(AbstractDeviceResource): + """ A Python object representing a setup device DMA resource. """ + + def __init__(self, data): + pass + +class IrqDeviceResource(AbstractDeviceResource): + """ A Python object representing a setup device Irq resource. """ + + def __init__(self, data): + pass + +class DeviceResource(object): + """ A Python object representing a setup device resource. """ + + @classmethod + def parse(cls, resource_type, data): + + subclasses = { + ResourceType_Mem : MmioDeviceResource, + ResourceType_IO : IoDeviceResource, + ResourceType_DMA : DmaDeviceResource, + ResourceType_IRQ : IrqDeviceResource, + } + + return subclasses[resource_type](data) + class DeviceObject(object): - + """ A Python object representing a setup device instance. """ def __init__(self, dev_data, device_name): + # gdef.winstructs.SP_DEVINFO_DATA: associated device info self.dev_data = dev_data + + # str: Device instance name (optional) self._name = device_name + # list(AbstractDeviceResource): list of resources registered by the device + self._resources = None + @property def name(self): return self._name + @property + def resources(self): + if not self._resources: + self._resources = list(res for res in self.get_resources()) + + return self._resources + + @classmethod def from_device_info(cls, h_devs, device_data): - # DO NOT STORE h_devs since it may be invalidated in the future try: device_name = winproxy.SetupDiGetDeviceRegistryPropertyW(h_devs, device_data, SPDRP_FRIENDLYNAME, None) @@ -34,27 +110,75 @@ class DeviceObject(object): if device_name: device_name = device_name.decode('utf-16-le').rstrip('\x00') else: - device_name = "N/A" + device_name = None except WindowsError as e: if e.winerror == gdef.ERROR_INVALID_DATA: - return cls(device_data, "N/A") + return cls(device_data, None) raise return cls(device_data, device_name) + def get_resources(self, resource_type = None): + + conf = self._get_first_log_conf() + if conf is None: + return + + if resource_type: + yield from self._get_resources_by_type(conf, resource_type) + else: + yield from self._get_resources_by_type(conf, ResourceType_Mem) + yield from self._get_resources_by_type(conf, ResourceType_IO) + yield from self._get_resources_by_type(conf, ResourceType_DMA) + yield from self._get_resources_by_type(conf, ResourceType_IRQ) + + # TODO : free "conf" automagically + + def _get_first_log_conf(self): + """ Try to retrieve the first log conf by using several flags """ + + conf = winproxy.CM_Get_First_Log_Conf(self.dev_data.DevInst, ALLOC_LOG_CONF ) + if conf != None: + return conf + + conf = winproxy.CM_Get_First_Log_Conf(self.dev_data.DevInst, BOOT_LOG_CONF ) + if conf != None: + return conf + + + + + def _get_resources_by_type(self, conf, resource_type): + + h_res = winproxy.CM_Get_Next_Res_Des(conf, resource_type) + + while h_res != None: + + # open resource + resource_size = winproxy.CM_Get_Res_Des_Data_Size(h_res) + if resource_size: + resource_data = winproxy.CM_Get_Res_Des_Data(h_res, resource_size) + yield DeviceResource.parse(resource_type, resource_data) + + # goto next resource + h_res = winproxy.CM_Get_Next_Res_Des(h_res, resource_type) + + # TODO : free "h_res" automagically + class DeviceClass(object): + """ A Python object representing a setup device class. """ def __init__(self, guid): - # class GUID + # gdef.winstructs.GUID: class GUID self._guid = guid - # associated class name + # str: associated class name self._name = None - # List of devices registered under the class + # list(DeviceObject): list of devices registered under the class self._devices = None @property diff --git a/windows/winproxy/apis/cfgmgr32.py b/windows/winproxy/apis/cfgmgr32.py index 42efb35..97b0efc 100644 --- a/windows/winproxy/apis/cfgmgr32.py +++ b/windows/winproxy/apis/cfgmgr32.py @@ -1,4 +1,5 @@ import ctypes +from ctypes import wintypes import windows.generated_def as gdef from ..apiproxy import ApiProxy, NeededParameter, is_implemented @@ -6,13 +7,15 @@ from ..error import succeed_on_zero, no_error_check class CfgMgr32Proxy(ApiProxy): APIDLL = "CfgMgr32" - default_error_check = staticmethod(succeed_on_zero) + + # We suppress error checks since CM_** APIs return either : + # - CR_SUCCESS on success + # - or a custom status, e.g. CR_NO_SUCH_VALUE on an invalid class index + # if necessary, we can convert them to Win32 error usign CM_MapCrToWin32Err + default_error_check = staticmethod(no_error_check) -# We suppress error checks since CM_Enumerate_Classes return either : -# - CR_SUCCESS on success -# - usually CR_NO_SUCH_VALUE on an invalid class index -@CfgMgr32Proxy(error_check=no_error_check) +@CfgMgr32Proxy() def CM_Enumerate_Classes(ClassIndex, Params): """ Given a class index, either return the class GUID or None. @@ -27,22 +30,87 @@ def CM_Enumerate_Classes(ClassIndex, Params): return guid -# @CfgMgr32Proxy() -# def CM_Get_First_Log_Conf(hDevInst): -# return CM_Get_First_Log_Conf.ctypes_function(hDevInst) +@CfgMgr32Proxy() +def CM_Get_First_Log_Conf(hDevInst, Flags): -# @CfgMgr32Proxy() -# def CM_Get_Next_Res_Des(hRes, ResourceType): -# return CM_Get_Next_Res_Des.ctypes_function(hRes, ResourceType) + # TODO : test if process is running as wow64 on a windows >8 and raise an exception if true -# @CfgMgr32Proxy() -# def CM_Get_Res_Des_Data_Size(hRes): -# return CM_Get_Res_Des_Data_Size.ctypes_function(hRes) -# @CfgMgr32Proxy() -# def CM_Get_Res_Des_Data(hRes, ResourceSize): -# return CM_Get_Res_Des_Data.ctypes_function(hRes, ResourceSize) + conf = wintypes.HANDLE(0) -# @CfgMgr32Proxy() -# def CM_Free_Res_Des_Handle(hRes): -# return CM_Free_Res_Des_Handle.ctypes_function(hRes) + cr_status = CM_Get_First_Log_Conf.ctypes_function( + ctypes.byref(conf), + hDevInst, + Flags + ) + + if cr_status != gdef.CR_SUCCESS: + return None + + return conf + +@CfgMgr32Proxy() +def CM_Free_Res_Des_Handle(hRes): + return CM_Free_Res_Des_Handle.ctypes_function(hRes) + +@CfgMgr32Proxy() +def CM_Get_Next_Res_Des(hRes, ResourceType): + + # TODO : test if process is running as wow64 on a windows >8 and raise an exception if true + + updated_hRes = wintypes.HANDLE(0) + + # TODO : support ResType_All query (design change since we need to return the resource type queried) + if not ResourceType: + raise ValueError("ResType_All not supported") + + if ResourceType > 7: + raise ValueError("ResourceType %x > ResType_MAX" % ResourceType) + + status = CM_Get_Next_Res_Des.ctypes_function( + ctypes.byref(updated_hRes), + hRes, + ResourceType, + None, + 0 # flags parameter is not used, and must always be 0 + ) + + # clean up previous hRes + CM_Free_Res_Des_Handle(hRes) + + if status != gdef.CR_SUCCESS: + return None + + return updated_hRes + +@CfgMgr32Proxy() +def CM_Get_Res_Des_Data_Size(hRes): + + resource_size = wintypes.ULONG(0) + status = CM_Get_Res_Des_Data_Size.ctypes_function( + ctypes.byref(resource_size), + hRes, + 0 # flags parameter is not used, and must always be 0 + ) + + if status != gdef.CR_SUCCESS: + return None + + return resource_size.value + +@CfgMgr32Proxy() +def CM_Get_Res_Des_Data(hRes, ResourceSize): + + resource_buffer = ctypes.create_string_buffer(ResourceSize) + result = CM_Get_Res_Des_Data.ctypes_function( + hRes, + ctypes.byref(resource_buffer), + ResourceSize, + 0 # flags parameter is not used, and must always be 0 + ) + + if result != gdef.CR_SUCCESS: + return None + + # truncate and return data + return bytes(resource_buffer)[0:ResourceSize] diff --git a/windows/winproxy/apis/setupapi.py b/windows/winproxy/apis/setupapi.py index f24e6ba..c4d1517 100644 --- a/windows/winproxy/apis/setupapi.py +++ b/windows/winproxy/apis/setupapi.py @@ -112,9 +112,6 @@ def SetupDiGetDeviceRegistryPropertyA(hDevInfo, DevData, Property, PropertyType, ctypes.byref(bytes_written), ) - if not success: - return None - # Truncate read data registry_data = bytes(property_buffer) registry_data = registry_data[0:bytes_written.value] @@ -138,9 +135,6 @@ def SetupDiGetDeviceRegistryPropertyW(hDevInfo, DevData, Property, PropertyType, ctypes.byref(bytes_written), ) - if not success: - return None - # Truncate read data registry_data = bytes(property_buffer) registry_data = registry_data[0:bytes_written.value]