Source code for rpcclient.darwin.objective_c_symbol

from contextlib import suppress
from dataclasses import dataclass
from functools import partial
from pathlib import Path
from typing import Optional

from pygments import highlight
from pygments.formatters import TerminalTrueColorFormatter
from pygments.lexers import ObjectiveCLexer

from rpcclient.darwin import objc
from rpcclient.darwin.objective_c_class import Class
from rpcclient.darwin.symbol import DarwinSymbol
from rpcclient.exceptions import RpcClientException
from rpcclient.symbol import Symbol
from rpcclient.symbols_jar import SymbolsJar


[docs] class SettingIvarError(RpcClientException): """ Raise when trying to set an Ivar too early or when the Ivar doesn't exist. """ pass
[docs] @dataclass class Ivar: name: str value: DarwinSymbol type_: str offset: int
[docs] class ObjectiveCSymbol(DarwinSymbol): """ Wrapper object for an objective-c symbol. Allowing easier access to its properties, methods and ivars. """
[docs] @classmethod def create(cls, value: int, client): """ Create an ObjectiveCSymbol object. :param value: Symbol address. :param rpcclient.darwin.client.Client client: client. :return: ObjectiveCSymbol object. :rtype: ObjectiveCSymbol """ symbol = super().create(value, client) symbol.ivars = [] symbol.properties = [] symbol.methods = [] symbol.class_ = None # type: Class symbol.reload() return symbol
[docs] def reload(self): """ Reload object's in-memory layout. """ object_data = self._client.showobject(self) ivars_list = [ Ivar(name=ivar['name'], type_=ivar['type'], offset=ivar['offset'], value=ivar['value']) for ivar in object_data['ivars'] ] methods_list = [ objc.Method.from_data(method, self._client) for method in object_data['methods'] ] properties_list = [ objc.Property(name=prop['name'], attributes=objc.convert_encoded_property_attributes(prop['attributes'])) for prop in object_data['properties'] ] class_object = Symbol.create(object_data['class_address'], self._client) class_wrapper = Class(self._client, class_object) self.ivars = ivars_list self.methods = methods_list self.properties = properties_list self.class_ = class_wrapper
[docs] def show(self, dump_to: Optional[str] = None, recursive: bool = False): """ Print to terminal the highlighted class description. :param dump_to: directory to dump. :param recursive: Show methods of super classes. """ formatted = self._to_str(recursive) print(highlight(formatted, ObjectiveCLexer(), TerminalTrueColorFormatter(style='native'))) if dump_to is None: return (Path(dump_to) / f'{self.class_.name}.m').expanduser().write_text(formatted)
[docs] def objc_call(self, selector: str, *params, **kwargs): """ Make objc_call() from self return ObjectiveCSymbol when it's an objc symbol. :param selector: Selector to execute. :param params: Additional parameters. :return: ObjectiveCSymbol when return type is an objc symbol. """ symbol = super(ObjectiveCSymbol, self).objc_call(selector, *params, **kwargs) return symbol.objc_symbol if self._client.is_objc_type(symbol) else symbol
def _set_ivar(self, name, value): try: ivars = self.__getattribute__('ivars') class_name = self.__getattribute__('class_').name except AttributeError as e: raise SettingIvarError from e for i, ivar in enumerate(ivars): if ivar.name == name: size = self.item_size if i < len(self.ivars) - 1: size = ivars[i + 1].offset - ivar.offset with self.change_item_size(size): self[ivar.offset // size] = value ivar.value = value return raise SettingIvarError(f'Ivar "{name}" does not exist in "{class_name}"') def _to_str(self, recursive=False): protocols_buf = f'<{",".join(self.class_.protocols)}>' if self.class_.protocols else '' if self.class_.super is not None: buf = f'@interface {self.class_.name}: {self.class_.super.name} {protocols_buf}\n' else: buf = f'@interface {self.class_.name} {protocols_buf}\n' # Add ivars buf += '{\n' for ivar in self.ivars: buf += f'\t{ivar.type_} {ivar.name} = 0x{int(ivar.value):x}; // 0x{ivar.offset:x}\n' buf += '}\n' # Add properties for prop in self.properties: attrs = prop.attributes buf += f'@property ({",".join(attrs.list)}) {prop.attributes.type_} {prop.name};\n' if attrs.synthesize is not None: buf += f'@synthesize {prop.name} = {attrs.synthesize};\n' # Add methods methods = self.methods.copy() # Add super methods. if recursive: for sup in self.class_.iter_supers(): for method in filter(lambda m: m not in methods, sup.methods): methods.append(method) # Print class methods first. methods.sort(key=lambda m: not m.is_class) for method in methods: buf += str(method) buf += '@end' return buf @property def symbols_jar(self) -> SymbolsJar: """ Get a SymbolsJar object for quick operations on all methods """ jar = SymbolsJar.create(self._client) for m in self.methods: jar[m.name] = m.address return jar def __dir__(self): result = set() for ivar in self.ivars: result.add(ivar.name) for method in self.methods: result.add(method.name.replace(':', '_')) for sup in self.class_.iter_supers(): for method in sup.methods: result.add(method.name.replace(':', '_')) result.update(list(super(ObjectiveCSymbol, self).__dir__())) return list(result) def __getitem__(self, item): if isinstance(item, int): return super(ObjectiveCSymbol, self).__getitem__(item) # Ivars for ivar in self.ivars: if ivar.name == item: if self._client.is_objc_type(ivar.value): return ivar.value.objc_symbol return ivar.value # Properties for prop in self.properties: if prop.name == item: return self.objc_call(item) # Methods for method in self.methods: if method.name == item: return partial(self.class_.objc_call, item) if method.is_class else partial(self.objc_call, item) for sup in self.class_.iter_supers(): for method in sup.methods: if method.name == item: return partial(self.class_.objc_call, item) if method.is_class else partial(self.objc_call, item) raise AttributeError(f''''{self.class_.name}' has no attribute {item}''') def __getattr__(self, item: str): return self[self.class_.sanitize_name(item)] def __setitem__(self, key, value): if isinstance(key, int): super(ObjectiveCSymbol, self).__setitem__(key, value) return with suppress(SettingIvarError): self._set_ivar(key, value) return def __setattr__(self, key, value): try: key = self.__getattribute__('class_').sanitize_name(key) except AttributeError: pass try: self._set_ivar(key, value) except SettingIvarError: super(ObjectiveCSymbol, self).__setattr__(key, value) def __str__(self): return self._to_str(False)