From ad7121ef3f218676fad3bbca5f163f0eebefb07a Mon Sep 17 00:00:00 2001 From: matan Date: Tue, 15 Feb 2022 10:07:23 +0200 Subject: [PATCH] Client: Add ObjectiveC symbol. --- src/rpcclient/requirements.txt | 2 + src/rpcclient/rpcclient/client.py | 17 +- src/rpcclient/rpcclient/darwin/client.py | 59 +++++ src/rpcclient/rpcclient/darwin/objc.py | 209 ++++++++++++++++ .../rpcclient/darwin/objective_c_class.py | 199 +++++++++++++++ .../rpcclient/darwin/objective_c_symbol.py | 232 ++++++++++++++++++ src/rpcclient/rpcclient/darwin/symbol.py | 8 + src/rpcclient/rpcclient/exceptions.py | 4 + src/rpcclient/rpcclient/protocol.py | 7 + src/rpcserver/rpcserver.c | 43 +++- 10 files changed, 773 insertions(+), 7 deletions(-) create mode 100644 src/rpcclient/rpcclient/darwin/objc.py create mode 100644 src/rpcclient/rpcclient/darwin/objective_c_class.py create mode 100644 src/rpcclient/rpcclient/darwin/objective_c_symbol.py diff --git a/src/rpcclient/requirements.txt b/src/rpcclient/requirements.txt index 1800fd11..f6f8f82f 100644 --- a/src/rpcclient/requirements.txt +++ b/src/rpcclient/requirements.txt @@ -5,3 +5,5 @@ IPython traitlets cached-property dataclasses; python_version<"3.7" +pygments +objc_types_decoder diff --git a/src/rpcclient/rpcclient/client.py b/src/rpcclient/rpcclient/client.py index 200507c4..f4a89184 100644 --- a/src/rpcclient/rpcclient/client.py +++ b/src/rpcclient/rpcclient/client.py @@ -17,7 +17,8 @@ from rpcclient.fs import Fs from rpcclient.network import Network from rpcclient.processes import Processes -from rpcclient.protocol import protocol_message_t, cmd_type_t, exec_chunk_t, exec_chunk_type_t, UNAME_VERSION_LEN +from rpcclient.protocol import protocol_message_t, cmd_type_t, exec_chunk_t, exec_chunk_type_t, UNAME_VERSION_LEN, \ + reply_protocol_message_t from rpcclient.darwin.structs import pid_t, exitcode_t from rpcclient.symbol import Symbol from rpcclient.symbols_jar import SymbolsJar @@ -164,6 +165,9 @@ def peek(self, address: int, size: int) -> bytes: 'data': {'address': address, 'size': size}, }) self._sock.sendall(message) + reply = protocol_message_t.parse(self._recvall(reply_protocol_message_t.sizeof())) + if reply.cmd_type == cmd_type_t.CMD_REPLY_ERROR: + raise ArgumentError() return self._recvall(size) def poke(self, address: int, data: bytes): @@ -258,11 +262,16 @@ def safe_calloc(self, size: int): @contextlib.contextmanager def safe_malloc(self, size: int): - x = self.symbols.malloc(size) - try: + with self.freeing(self.symbols.malloc(size)) as x: yield x + + @contextlib.contextmanager + def freeing(self, symbol): + try: + yield symbol finally: - self.symbols.free(x) + if symbol: + self.symbols.free(symbol) def interactive(self): """ Start an interactive shell """ diff --git a/src/rpcclient/rpcclient/darwin/client.py b/src/rpcclient/rpcclient/darwin/client.py index 25011c34..995b75e2 100644 --- a/src/rpcclient/rpcclient/darwin/client.py +++ b/src/rpcclient/rpcclient/darwin/client.py @@ -1,12 +1,16 @@ +from collections import namedtuple +from functools import lru_cache import struct import typing from cached_property import cached_property from rpcclient.client import Client +from rpcclient.darwin import objective_c_class from rpcclient.darwin.fs import DarwinFs from rpcclient.darwin.media import DarwinMedia from rpcclient.darwin.network import DarwinNetwork +from rpcclient.darwin.objective_c_symbol import ObjectiveCSymbol from rpcclient.darwin.processes import DarwinProcesses from rpcclient.darwin.preferences import Preferences from rpcclient.exceptions import RpcClientException @@ -14,6 +18,16 @@ from rpcclient.darwin.consts import kCFNumberSInt64Type, kCFNumberDoubleType from rpcclient.darwin.symbol import DarwinSymbol +IsaMagic = namedtuple('IsaMagic', 'mask value') +ISA_MAGICS = [ + # ARM64 + IsaMagic(mask=0x000003f000000001, value=0x000001a000000001), + # X86_64 + IsaMagic(mask=0x001f800000000001, value=0x001d800000000001), +] +# Mask for tagged pointer, from objc-internal.h +OBJC_TAG_MASK = (1 << 63) + class DarwinClient(Client): @@ -103,3 +117,48 @@ def cf(self, o: object): return self.symbols.CFDictionaryCreate(0, keys_buf, values_buf, len(cfvalues), 0, 0, 0) else: raise NotImplementedError() + + def objc_symbol(self, address) -> ObjectiveCSymbol: + """ + Get objc symbol wrapper for given address + :param address: + :return: ObjectiveC symbol object + """ + return ObjectiveCSymbol.create(int(address), self) + + @lru_cache(maxsize=None) + def objc_get_class(self, name: str): + """ + Get ObjC class object + :param name: + :return: + """ + return objective_c_class.Class.from_class_name(self, name) + + @staticmethod + def is_objc_type(symbol: DarwinSymbol) -> bool: + """ + Test if a given symbol represents an objc object + :param symbol: + :return: + """ + # Tagged pointers are ObjC objects + if symbol & OBJC_TAG_MASK == OBJC_TAG_MASK: + return True + + # Class are not ObjC objects + for mask, value in ISA_MAGICS: + if symbol & mask == value: + return False + + try: + with symbol.change_item_size(8): + isa = symbol[0] + except RpcClientException: + return False + + for mask, value in ISA_MAGICS: + if isa & mask == value: + return True + + return False diff --git a/src/rpcclient/rpcclient/darwin/objc.py b/src/rpcclient/rpcclient/darwin/objc.py new file mode 100644 index 00000000..7f5ec7ac --- /dev/null +++ b/src/rpcclient/rpcclient/darwin/objc.py @@ -0,0 +1,209 @@ +from collections import namedtuple +from typing import Mapping +from dataclasses import dataclass, field +from functools import lru_cache + +from objc_types_decoder.decode import decode as decode_type, decode_with_tail + +Property = namedtuple('Property', 'name attributes') +PropertyAttributes = namedtuple('PropertyAttributes', 'synthesize type_ list') + + +def convert_encoded_property_attributes(encoded): + conversions = { + 'R': lambda x: 'readonly', + 'C': lambda x: 'copy', + '&': lambda x: 'strong', + 'N': lambda x: 'nonatomic', + 'G': lambda x: 'getter=' + x[1:], + 'S': lambda x: 'setter=' + x[1:], + 'd': lambda x: 'dynamic', + 'W': lambda x: 'weak', + 'P': lambda x: '', + 't': lambda x: 'encoding=' + x[1:], + } + + type_, tail = decode_with_tail(encoded[1:]) + attributes = [] + synthesize = None + for attr in filter(None, tail.lstrip(',').split(',')): + if attr[0] in conversions: + attributes.append(conversions[attr[0]](attr)) + elif attr[0] == 'V': + synthesize = attr[1:] + + return PropertyAttributes(type_=type_, synthesize=synthesize, list=attributes) + + +@dataclass +class Method: + name: str + address: int = field(compare=False) + type_: str = field(compare=False) + return_type: str = field(compare=False) + is_class: bool = field(compare=False) + args_types: list = field(compare=False) + + @staticmethod + def from_data(data: Mapping, client): + """ + Create Method object from raw data. + :param data: Data as loaded from get_objectivec_symbol_data.m. + :param rpcclient.darwin.client.DarwinClient client: Darwin client. + """ + return Method( + name=data['name'], + address=client.symbol(data['address']), + type_=data['type'], + return_type=decode_type(data['return_type']), + is_class=data['is_class'], + args_types=list(map(decode_type, data['args_types'])) + ) + + def __str__(self): + if ':' in self.name: + args_names = self.name.split(':') + name = ' '.join(['{}:({})'.format(*arg) for arg in zip(args_names, self.args_types[2:])]) + else: + name = self.name + prefix = '+' if self.is_class else '-' + return f'{prefix} {name}; // 0x{self.address:x} (returns: {self.return_type})\n' + + +def _class_data_list(client, class_, function): + with client.safe_malloc(4) as out_count: + out_count.item_size = 4 + c_result = client.symbols.get_lazy(function)(class_, out_count) + count = out_count[0] + for i in range(count): + yield c_result[i] + if c_result: + client.symbols.free(c_result) + + +@lru_cache(maxsize=None) +def get_class_protocols(client, class_): + return [ + client.symbols.protocol_getName(protocol).peek_str() + for protocol in _class_data_list(client, class_, 'class_copyProtocolList') + ] + + +def _iter_class_ivars(client, class_): + for ivar in _class_data_list(client, class_, 'class_copyIvarList'): + yield { + 'name': client.symbols.ivar_getName(ivar).peek_str(), + 'type': decode_type(client.symbols.ivar_getTypeEncoding(ivar).peek_str()), + 'offset': client.symbols.ivar_getOffset(ivar), + } + + +@lru_cache(maxsize=None) +def get_class_ivars(client, class_): + return sorted(_iter_class_ivars(client, class_), key=lambda ivar: ivar['offset']) + + +@lru_cache(maxsize=None) +def get_super(client, class_): + return client.symbols.class_getSuperclass(class_) + + +@lru_cache(maxsize=None) +def get_class_name(client, class_): + return client.symbols.class_getName(class_).peek_str() + + +def _iter_until_super(client, class_): + while True: + yield class_ + class_ = client.symbols.class_getSuperclass(class_) + if not class_: + break + + +def _iter_object_ivars(client, class_, object_): + for objc_class in _iter_until_super(client, class_): + for ivar in _class_data_list(client, objc_class, 'class_copyIvarList'): + yield { + 'name': client.symbols.ivar_getName(ivar).peek_str(), + 'type': decode_type(client.symbols.ivar_getTypeEncoding(ivar).peek_str()), + 'offset': client.symbols.ivar_getOffset(ivar), + 'value': client.symbols.object_getIvar(object_, ivar) + } + + +def get_object_ivars(client, class_, object_): + ivars = sorted(_iter_object_ivars(client, class_, object_), key=lambda ivar: ivar['offset']) + for i, ivar in enumerate(ivars): + value = ivar['value'] + if i < len(ivars) - 1: + # The .fm file returns a 64bit value, regardless of the real size. + size = ivars[i + 1]['offset'] - ivar['offset'] + ivar['value'] = value & ((2 ** (size * 8)) - 1) + return ivars + + +def _iter_class_properties(client, class_): + for property_ in _class_data_list(client, class_, 'class_copyPropertyList'): + yield { + 'name': client.symbols.property_getName(property_).peek_str(), + 'attributes': client.symbols.property_getAttributes(property_).peek_str(), + } + + +@lru_cache(maxsize=None) +def get_class_properties(client, class_): + return [ + Property(name=prop['name'], attributes=convert_encoded_property_attributes(prop['attributes'])) + for prop in _iter_class_properties(client, class_) + ] + + +def _iter_object_properties(client, class_): + fetched_properties = [] + for objc_class in _iter_until_super(client, class_): + for property in _iter_class_properties(client, objc_class): + if property['name'] in fetched_properties: + continue + fetched_properties.append(property['name']) + yield property + + +@lru_cache(maxsize=None) +def get_object_properties(client, class_): + return [ + Property(name=prop['name'], attributes=convert_encoded_property_attributes(prop['attributes'])) + for prop in _iter_object_properties(client, class_) + ] + + +def _iter_class_methods(client, class_): + for method in _class_data_list(client, class_, 'class_copyMethodList'): + args_types = [] + for arg in range(client.symbols.method_getNumberOfArguments(method)): + with client.freeing( + client.symbols.method_copyArgumentType(method, arg)) as method_arguments_types: + args_types.append(method_arguments_types.peek_str()) + with client.freeing(client.symbols.method_copyReturnType(method)) as method_return_type: + return_type = method_return_type.peek_str() + yield { + 'name': client.symbols.sel_getName(client.symbols.method_getName(method)).peek_str(), + 'address': client.symbols.method_getImplementation(method), + 'type': client.symbols.method_getTypeEncoding(method).peek_str(), + 'return_type': return_type, + 'args_types': args_types, + } + + +def _iter_methods(client, class_): + for method in _iter_class_methods(client, client.symbols.object_getClass(class_)): + method['is_class'] = True + yield method + for method in _iter_class_methods(client, class_): + method['is_class'] = False + yield method + + +@lru_cache(maxsize=None) +def get_class_methods(client, class_): + return [Method.from_data(method, client) for method in _iter_methods(client, class_)] diff --git a/src/rpcclient/rpcclient/darwin/objective_c_class.py b/src/rpcclient/rpcclient/darwin/objective_c_class.py new file mode 100644 index 00000000..4eef9e1d --- /dev/null +++ b/src/rpcclient/rpcclient/darwin/objective_c_class.py @@ -0,0 +1,199 @@ +from collections import namedtuple +from typing import Mapping +from functools import partial + +from pygments import highlight +from pygments.formatters import TerminalTrueColorFormatter +from pygments.lexers import ObjectiveCLexer + +from rpcclient.exceptions import GettingObjectiveCClassError +from rpcclient.symbols_jar import SymbolsJar +from rpcclient.darwin import objc + +Ivar = namedtuple('Ivar', 'name type_ offset') + + +class Class: + """ + Wrapper for ObjectiveC Class object. + """ + + def __init__(self, client, class_object=0, class_data: Mapping = None): + """ + :param rpcclient.darwin.client.DarwinClient client: Darwin client. + :param rpcclient.darwin.objective_c_symbol.Symbol class_object: + """ + self._client = client + self._class_object = class_object + self.protocols = [] + self.ivars = [] + self.properties = [] + self.methods = [] + self.name = '' + self.super = None + if class_data is None: + self.reload() + else: + self._load_class_data(class_data) + + @staticmethod + def from_class_name(client, class_name: str): + """ + Create ObjectiveC Class from given class name. + :param rpcclient.darwin.client.DarwinClient client: Darwin client. + :param class_name: Class name. + """ + class_object = client.symbols.objc_getClass(class_name) + class_symbol = Class(client, class_object) + if class_symbol.name != class_name: + raise GettingObjectiveCClassError() + return class_symbol + + @staticmethod + def sanitize_name(name: str): + """ + Sanitize python name to ObjectiveC name. + """ + if name.startswith('_'): + name = '_' + name[1:].replace('_', ':') + else: + name = name.replace('_', ':') + return name + + def reload(self): + """ + Reload class object data. + Should be used whenever the class layout changes (for example, during method swizzling) + """ + objc_class = self._class_object if self._class_object else self._client.symbols.objc_getClass(self.name) + super = objc.get_super(self._client, objc_class) + self.super = Class(self._client, super) if super else None + self.name = objc.get_class_name(self._client, objc_class) + self.protocols = objc.get_class_protocols(self._client, objc_class) + self.ivars = [ + Ivar(name=ivar['name'], type_=ivar['type'], offset=ivar['offset']) + for ivar in objc.get_class_ivars(self._client, objc_class) + ] + self.properties = objc.get_class_properties(self._client, objc_class) + self.methods = objc.get_class_methods(self._client, objc_class) + + def show(self): + """ + Print to terminal the highlighted class description. + """ + print(highlight(str(self), ObjectiveCLexer(), TerminalTrueColorFormatter(style='native'))) + + def objc_call(self, sel: str, *args): + """ + Invoke a selector on the given class object. + :param sel: Selector name. + :return: whatever the selector returned as a symbol. + """ + return self._class_object.objc_call(sel, *args) + + def get_method(self, name: str): + """ + Get a specific method implementation. + :param name: Method name. + :return: Method. + """ + for method in self.methods: + if method.name == name: + return method + + def iter_supers(self): + """ + Iterate over the super classes of the class. + """ + sup = self.super + while sup is not None: + yield sup + sup = sup.super + + def _load_class_data(self, data: Mapping): + self._class_object = self._client.symbol(data['address']) + self.super = Class(self._client, data['super']) if data['super'] else None + self.name = data['name'] + self.protocols = data['protocols'] + self.ivars = [Ivar(name=ivar['name'], type_=ivar['type'], offset=ivar['offset']) for ivar in data['ivars']] + self.properties = data['properties'] + self.methods = data['methods'] + + @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[f'[{self.name} {m.name}]'] = m.address + + return jar + + def __dir__(self): + result = set() + + for method in self.methods: + if method.is_class: + result.add(method.name.replace(':', '_')) + + for sup in self.iter_supers(): + for method in sup.methods: + if method.is_class: + result.add(method.name.replace(':', '_')) + + result.update(list(super(Class, self).__dir__())) + return list(result) + + def __str__(self): + protocol_buf = f'<{",".join(self.protocols)}>' if self.protocols else '' + + if self.super is not None: + buf = f'@interface {self.name}: {self.super.name} {protocol_buf}\n' + else: + buf = f'@interface {self.name} {protocol_buf}\n' + + # Add ivars + buf += '{\n' + for ivar in self.ivars: + buf += f'\t{ivar.type_} {ivar.name}; // 0x{ivar.offset:x}\n' + buf += '}\n' + + # Add properties + for prop in self.properties: + buf += f'@property ({",".join(prop.attributes.list)}) {prop.attributes.type_} {prop.name};\n' + + if prop.attributes.synthesize is not None: + buf += f'@synthesize {prop.name} = {prop.attributes.synthesize};\n' + + # Add methods + for method in self.methods: + buf += str(method) + + buf += '@end' + return buf + + def __repr__(self): + return f'' + + def __getitem__(self, item): + for method in self.methods: + if method.name == item: + if method.is_class: + return partial(self.objc_call, item) + else: + raise AttributeError(f'{self.name} class has an instance method named {item}, ' + f'not a class method') + + for sup in self.iter_supers(): + for method in sup.methods: + if method.name == item: + if method.is_class: + return partial(self.objc_call, item) + else: + raise AttributeError(f'{self.name} class has an instance method named {item}, ' + f'not a class method') + + raise AttributeError(f''''{self.name}' class has no attribute {item}''') + + def __getattr__(self, item: str): + return self[self.sanitize_name(item)] diff --git a/src/rpcclient/rpcclient/darwin/objective_c_symbol.py b/src/rpcclient/rpcclient/darwin/objective_c_symbol.py new file mode 100644 index 00000000..f08ec8ca --- /dev/null +++ b/src/rpcclient/rpcclient/darwin/objective_c_symbol.py @@ -0,0 +1,232 @@ +from contextlib import suppress +from dataclasses import dataclass +from functools import partial + +from pygments import highlight +from pygments.formatters import TerminalTrueColorFormatter +from pygments.lexers import ObjectiveCLexer + +from rpcclient.exceptions import RpcClientException +from rpcclient.darwin.objective_c_class import Class +from rpcclient.symbols_jar import SymbolsJar +from rpcclient.darwin.symbol import DarwinSymbol +from rpcclient.darwin import objc + + +class SettingIvarError(RpcClientException): + """ Raise when trying to set an Ivar too early or when the Ivar doesn't exist. """ + pass + + +@dataclass +class Ivar: + name: str + value: DarwinSymbol + type_: str + offset: int + + +class ObjectiveCSymbol(DarwinSymbol): + """ + Wrapper object for an objective-c symbol. + Allowing easier access to its properties, methods and ivars. + """ + + @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 + + def reload(self): + """ + Reload object's in-memory layout. + """ + self.ivars.clear() + self.properties.clear() + self.methods.clear() + self.class_ = None + + objc_class = self.objc_call('class') + ivars = objc.get_object_ivars(self._client, objc_class, self) + self.ivars = [ + Ivar(name=ivar['name'], type_=ivar['type'], offset=ivar['offset'], value=ivar['value']) for ivar in ivars + ] + self.properties = objc.get_object_properties(self._client, objc_class) + self.methods = objc.get_class_methods(self._client, objc_class) + + data = { + 'name': objc.get_class_name(self._client, objc_class), + 'address': objc_class, + 'super': objc.get_super(self._client, objc_class), + 'protocols': objc.get_class_protocols(self._client, objc_class), + 'ivars': ivars, + 'properties': self.properties, + 'methods': self.methods, + } + self.class_ = Class(self._client, objc_class, data) + + def show(self, recursive: bool = False): + """ + Print to terminal the highlighted class description. + :param recursive: Show methods of super classes. + """ + print(highlight(self._to_str(recursive), ObjectiveCLexer(), TerminalTrueColorFormatter(style='native'))) + + def objc_call(self, selector: str, *params): + """ + 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) + 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) diff --git a/src/rpcclient/rpcclient/darwin/symbol.py b/src/rpcclient/rpcclient/darwin/symbol.py index f4b13ebd..44b51c44 100644 --- a/src/rpcclient/rpcclient/darwin/symbol.py +++ b/src/rpcclient/rpcclient/darwin/symbol.py @@ -65,3 +65,11 @@ def py(self): result[keys[i].py] = values[i].py return result raise NotImplementedError(f'type: {t}') + + @property + def objc_symbol(self): + """ + Get an ObjectiveC symbol of the same address + :return: Object representing the ObjectiveC symbol + """ + return self._client.objc_symbol(self) diff --git a/src/rpcclient/rpcclient/exceptions.py b/src/rpcclient/rpcclient/exceptions.py index cc3c2cdb..e4c1b8d5 100644 --- a/src/rpcclient/rpcclient/exceptions.py +++ b/src/rpcclient/rpcclient/exceptions.py @@ -36,3 +36,7 @@ class FailedToConnectError(RpcClientException): class UnrecognizedSelector(RpcClientException): pass + + +class GettingObjectiveCClassError(RpcClientException): + pass diff --git a/src/rpcclient/rpcclient/protocol.py b/src/rpcclient/rpcclient/protocol.py index 6671d925..a30d6c4e 100644 --- a/src/rpcclient/rpcclient/protocol.py +++ b/src/rpcclient/rpcclient/protocol.py @@ -9,6 +9,8 @@ CMD_CALL=4, CMD_PEEK=5, CMD_POKE=6, + CMD_REPLY_ERROR=7, + CMD_REPLY_PEEK=8, ) DEFAULT_PORT = 5910 MAGIC = 0x12345678 @@ -64,6 +66,11 @@ }) ) +reply_protocol_message_t = Struct( + 'magic' / Const(MAGIC, Int32ul), + 'cmd_type' / cmd_type_t, +) + exec_chunk_type_t = Enum(Int32ul, CMD_EXEC_CHUNK_TYPE_STDOUT=0, CMD_EXEC_CHUNK_TYPE_ERRORCODE=1, diff --git a/src/rpcserver/rpcserver.c b/src/rpcserver/rpcserver.c index 765c9943..78f192cf 100644 --- a/src/rpcserver/rpcserver.c +++ b/src/rpcserver/rpcserver.c @@ -1,5 +1,5 @@ #ifndef __APPLE__ -#define _XOPEN_SOURCE (600) +#define _XOPEN_SOURCE (600) #define _GNU_SOURCE (1) #endif // __APPLE__ #include @@ -25,6 +25,10 @@ #include #include +#ifdef __APPLE__ +#include +#endif // __APPLE__ + #include "common.h" #define DEFAULT_PORT ("5910") @@ -50,6 +54,8 @@ typedef enum CMD_CALL = 4, CMD_PEEK = 5, CMD_POKE = 6, + CMD_REPLY_ERROR = 7, + CMD_REPLY_PEEK = 8, } cmd_type_t; typedef enum @@ -157,7 +163,7 @@ int internal_spawn(char *const *argv, char *const *envp, pid_t *pid) CHECK(0 == posix_spawn_file_actions_adddup2(&actions, slave_fd, STDERR_FILENO)); CHECK(0 == posix_spawn_file_actions_addclose(&actions, slave_fd)); CHECK(0 == posix_spawn_file_actions_addclose(&actions, master_fd)); - + CHECK(0 == posix_spawnp(pid, argv[0], &actions, &attr, argv, envp)); posix_spawnattr_destroy(&attr); @@ -181,6 +187,15 @@ int internal_spawn(char *const *argv, char *const *envp, pid_t *pid) return -1; } +bool send_reply(int sockfd, cmd_type_t type) +{ + protocol_message_t protocol_message = {.magic = MAGIC, .cmd_type = type}; + CHECK(sendall(sockfd, (char *)&protocol_message, sizeof(protocol_message))); + return true; +error: + return false; +} + bool handle_exec(int sockfd) { pid_t pid = INVALID_PID; @@ -190,7 +205,7 @@ bool handle_exec(int sockfd) char **envp = NULL; u32 argc; u32 envc; - + CHECK(recvall(sockfd, (char *)&argc, sizeof(argc))); CHECK(argc > 0); @@ -467,8 +482,30 @@ bool handle_peek(int sockfd) int result = false; u64 *argv = NULL; cmd_peek_t cmd; + +#ifdef __APPLE__ + kern_return_t rc; + mach_port_t task; + vm_offset_t data; + mach_msg_type_number_t size; + + CHECK(recvall(sockfd, (char *)&cmd, sizeof(cmd))); + + CHECK(task_for_pid(mach_task_self(), getpid(), &task) == KERN_SUCCESS); + if (vm_read(task, cmd.address, cmd.size, &data, &size) == KERN_SUCCESS) + { + CHECK(send_reply(sockfd, CMD_REPLY_PEEK)); + CHECK(sendall(sockfd, (char *)cmd.address, cmd.size)); + } + else + { + CHECK(send_reply(sockfd, CMD_REPLY_ERROR)); + } +#else // __APPLE__ CHECK(recvall(sockfd, (char *)&cmd, sizeof(cmd))); + CHECK(send_reply(sockfd, CMD_REPLY_PEEK)); CHECK(sendall(sockfd, (char *)cmd.address, cmd.size)); +#endif // __APPLE__ result = true;