diff --git a/src/rpcclient/rpcclient/darwin/client.py b/src/rpcclient/rpcclient/darwin/client.py index 14464de7..230dff05 100644 --- a/src/rpcclient/rpcclient/darwin/client.py +++ b/src/rpcclient/rpcclient/darwin/client.py @@ -9,7 +9,7 @@ from rpcclient.darwin import objective_c_class from rpcclient.darwin.consts import kCFNumberSInt64Type, kCFNumberDoubleType from rpcclient.darwin.fs import DarwinFs -from rpcclient.darwin.iokit import IOKit +from rpcclient.darwin.ioregistry import IORegistry from rpcclient.darwin.media import DarwinMedia from rpcclient.darwin.network import DarwinNetwork from rpcclient.darwin.objective_c_symbol import ObjectiveCSymbol @@ -57,7 +57,7 @@ def __init__(self, sock, sysname: str, hostname: str, port: int = None): self.processes = DarwinProcesses(self) self.media = DarwinMedia(self) self.network = DarwinNetwork(self) - self.iokit = IOKit(self) + self.ioregistry = IORegistry(self) @property def modules(self) -> typing.List[str]: diff --git a/src/rpcclient/rpcclient/darwin/consts.py b/src/rpcclient/rpcclient/darwin/consts.py index 8767dea8..80501f1c 100644 --- a/src/rpcclient/rpcclient/darwin/consts.py +++ b/src/rpcclient/rpcclient/darwin/consts.py @@ -1,3 +1,7 @@ +kCFAllocatorDefault = 0 +MACH_PORT_NULL = 0 +AVAudioSessionCategoryOptionDefaultToSpeaker = 0x8 + # Types from MacTypes.h # Basic C types @@ -19,4 +23,12 @@ kCFNumberCFIndexType = 14 kCFNumberMaxType = 14 -AVAudioSessionCategoryOptionDefaultToSpeaker = 0x8 +# Types from IOKitKeys.h + +# registry plane names +kIOServicePlane = 'IOService' +kIOPowerPlane = 'IOPower' +kIODeviceTreePlane = 'IODeviceTree' +kIOAudioPlane = 'IOAudio' +kIOFireWirePlane = 'IOFireWire' +kIOUSBPlane = 'IOUSB' diff --git a/src/rpcclient/rpcclient/darwin/iokit.py b/src/rpcclient/rpcclient/darwin/iokit.py deleted file mode 100644 index 7b10119c..00000000 --- a/src/rpcclient/rpcclient/darwin/iokit.py +++ /dev/null @@ -1,118 +0,0 @@ -from typing import Mapping - -from rpcclient.exceptions import RpcClientException -from rpcclient.allocated import Allocated - - -class IOService(Allocated): - """ representation of a remote IOService """ - - def __init__(self, client, service): - super().__init__() - self._client = client - self._service = service - - def set(self, properties: Mapping): - self._client.symbols.IORegistryEntrySetCFProperties(self._service, self._client.cf(properties)) - - def get(self, key: str): - return self._client.symbols.IORegistryEntryCreateCFProperty(self._service, self._client.cf(key), 0, 0).py - - def _deallocate(self): - self._client.symbols.IOObjectRelease(self._service) - - -class BacklightControlService(IOService): - @property - def display_parameters(self) -> Mapping: - return self.get('IODisplayParameters') - - @property - def brightness(self) -> int: - return self.display_parameters['brightness']['value'] - - @brightness.setter - def brightness(self, value: int): - self.set({'EnableBacklight': bool(value)}) - self.set({'brightness': value}) - - -class PowerSourceService(IOService): - @property - def battery_voltage(self) -> int: - return self.get('AppleRawBatteryVoltage') - - @property - def charging(self) -> bool: - return self.get('IsCharging') - - @charging.setter - def charging(self, value: bool): - self.set({'IsCharging': value, 'ExternalConnected': value}) - - @property - def external_connected(self) -> bool: - return self.get('ExternalConnected') - - @external_connected.setter - def external_connected(self, value: bool): - self.set({'ExternalConnected': value}) - - @property - def current_capacity(self) -> int: - return self.get('CurrentCapacity') - - @current_capacity.setter - def current_capacity(self, value: int): - self.set({'CurrentCapacity': value}) - - @property - def at_warn_level(self) -> bool: - return self.get('AtWarnLevel') - - @at_warn_level.setter - def at_warn_level(self, value: bool): - self.set({'AtWarnLevel': value}) - - @property - def time_remaining(self) -> int: - return self.get('TimeRemaining') - - @property - def temperature(self) -> int: - return self.get('Temperature') - - @temperature.setter - def temperature(self, value: int): - self.set({'Temperature': value}) - - -class IOKit: - """ IORegistry utils """ - - def __init__(self, client): - """ - :param rpcclient.darwin.client.DarwinClient client: - """ - self._client = client - - @property - def backlight_control(self) -> IOService: - return self.get_ioservice_by_property({'backlight-control': True}, cls=BacklightControlService) - - @property - def power_source(self) -> IOService: - return self.get_ioservice_by_service_name('IOPMPowerSource', cls=PowerSourceService) - - def get_ioservice_by_service_name(self, service_name: str, cls=IOService) -> IOService: - service = self._client.symbols.IOServiceGetMatchingService(0, - self._client.symbols.IOServiceMatching(service_name)) - if not service: - raise RpcClientException(f'IOServiceGetMatchingService failed for: {service_name}') - return cls(self._client, service) - - def get_ioservice_by_property(self, property_: Mapping, cls=IOService) -> IOService: - service = self._client.symbols.IOServiceGetMatchingService(0, self._client.cf({'IOPropertyMatch': property_})) - if not service: - raise RpcClientException(f'IOServiceGetMatchingService failed for: {property_}') - return cls(self._client, service) diff --git a/src/rpcclient/rpcclient/darwin/ioregistry.py b/src/rpcclient/rpcclient/darwin/ioregistry.py new file mode 100644 index 00000000..ef0607f7 --- /dev/null +++ b/src/rpcclient/rpcclient/darwin/ioregistry.py @@ -0,0 +1,172 @@ +from typing import Mapping + +from rpcclient.darwin.consts import MACH_PORT_NULL, kCFAllocatorDefault, kIOServicePlane +from rpcclient.darwin.structs import mach_port_t, io_name_t, io_object_t +from rpcclient.exceptions import RpcClientException, BadReturnValueError +from rpcclient.allocated import Allocated + + +class IOServiceIter(Allocated): + def __init__(self, client, service): + super().__init__() + self._client = client + self._service = service + + def __iter__(self): + with self._client.safe_malloc(io_object_t.sizeof()) as p_child_iter: + if self._client.symbols.IORegistryEntryGetChildIterator(self._service, kIOServicePlane, p_child_iter): + raise BadReturnValueError('IORegistryEntryGetChildIterator failed') + child_iter = p_child_iter[0] + + while True: + child = self._client.symbols.IOIteratorNext(child_iter) + if not child: + break + s = IOService(self._client, child) + yield s + + self.deallocate() + + def _deallocate(self): + self._client.symbols.IOObjectRelease(self._service) + + +class IOService(Allocated): + """ representation of a remote IOService """ + + def __init__(self, client, service): + super().__init__() + self._client = client + self._service = service + + @property + def name(self) -> str: + with self._client.safe_malloc(io_name_t.sizeof()) as name: + if self._client.symbols.IORegistryEntryGetName(self._service, name): + raise BadReturnValueError('IORegistryEntryGetName failed') + return name.peek_str() + + @property + def properties(self) -> Mapping: + with self._client.safe_malloc(8) as p_properties: + if self._client.symbols.IORegistryEntryCreateCFProperties(self._service, p_properties, + kCFAllocatorDefault, 0): + raise BadReturnValueError('IORegistryEntryCreateCFProperties failed') + return p_properties[0].py + + def iter(self) -> IOServiceIter: + return IOServiceIter(self._client, self._service) + + def set(self, properties: Mapping): + self._client.symbols.IORegistryEntrySetCFProperties(self._service, self._client.cf(properties)) + + def get(self, key: str): + return self._client.symbols.IORegistryEntryCreateCFProperty(self._service, self._client.cf(key), 0, 0).py + + def _deallocate(self): + self._client.symbols.IOObjectRelease(self._service) + + def __repr__(self): + return f'' + + +class BacklightControlService(IOService): + @property + def display_parameters(self) -> Mapping: + return self.get('IODisplayParameters') + + @property + def brightness(self) -> int: + return self.display_parameters['brightness']['value'] + + @brightness.setter + def brightness(self, value: int): + self.set({'EnableBacklight': bool(value)}) + self.set({'brightness': value}) + + +class PowerSourceService(IOService): + @property + def battery_voltage(self) -> int: + return self.get('AppleRawBatteryVoltage') + + @property + def charging(self) -> bool: + return self.get('IsCharging') + + @charging.setter + def charging(self, value: bool): + self.set({'IsCharging': value, 'ExternalConnected': value}) + + @property + def external_connected(self) -> bool: + return self.get('ExternalConnected') + + @external_connected.setter + def external_connected(self, value: bool): + self.set({'ExternalConnected': value}) + + @property + def current_capacity(self) -> int: + return self.get('CurrentCapacity') + + @current_capacity.setter + def current_capacity(self, value: int): + self.set({'CurrentCapacity': value}) + + @property + def at_warn_level(self) -> bool: + return self.get('AtWarnLevel') + + @at_warn_level.setter + def at_warn_level(self, value: bool): + self.set({'AtWarnLevel': value}) + + @property + def time_remaining(self) -> int: + return self.get('TimeRemaining') + + @property + def temperature(self) -> int: + return self.get('Temperature') + + @temperature.setter + def temperature(self, value: int): + self.set({'Temperature': value}) + + +class IORegistry: + """ + IORegistry utils + + For more information please read: + https://developer.apple.com/library/archive/documentation/DeviceDrivers/Conceptual/IOKitFundamentals/TheRegistry/TheRegistry.html + """ + + def __init__(self, client): + """ + :param rpcclient.darwin.client.DarwinClient client: + """ + self._client = client + + @property + def backlight_control(self) -> BacklightControlService: + service = self._client.symbols.IOServiceGetMatchingService( + 0, self._client.cf({'IOPropertyMatch': {'backlight-control': True}})) + if not service: + raise RpcClientException('IOServiceGetMatchingService failed') + return BacklightControlService(self._client, service) + + @property + def power_source(self) -> PowerSourceService: + service = self._client.symbols.IOServiceGetMatchingService( + 0, self._client.symbols.IOServiceMatching('IOPMPowerSource')) + if not service: + raise RpcClientException('IOServiceGetMatchingService failed') + return PowerSourceService(self._client, service) + + @property + def root(self) -> IOService: + with self._client.safe_malloc(mach_port_t.sizeof()) as p_master_port: + self._client.symbols.IOMasterPort(MACH_PORT_NULL, p_master_port) + return IOService(self._client, self._client.symbols.IORegistryGetRootEntry(p_master_port[0])) diff --git a/src/rpcclient/rpcclient/darwin/structs.py b/src/rpcclient/rpcclient/darwin/structs.py index a20c20b5..a25f2650 100644 --- a/src/rpcclient/rpcclient/darwin/structs.py +++ b/src/rpcclient/rpcclient/darwin/structs.py @@ -26,6 +26,9 @@ blksize_t = Int32ul ino64_t = Int64ul fsid_t = Array(2, Int32sl) +mach_port_t = Int64ul +io_name_t = PaddedString(1024, 'utf8') +io_object_t = Int64ul timespec = Struct( 'tv_sec' / long,