diff --git a/src/rpcclient/rpcclient/client.py b/src/rpcclient/rpcclient/client.py index f906957c..1f7b6351 100644 --- a/src/rpcclient/rpcclient/client.py +++ b/src/rpcclient/rpcclient/client.py @@ -21,7 +21,7 @@ from rpcclient.darwin.structs import pid_t, exitcode_t from rpcclient.exceptions import ArgumentError, SymbolAbsentError, SpawnError, ServerDiedError, \ InvalidServerVersionMagicError, BadReturnValueError, RpcFileExistsError, RpcNotEmptyError, RpcFileNotFoundError, \ - RpcBrokenPipeError, RpcIsADirectoryError + RpcBrokenPipeError, RpcIsADirectoryError, RpcPermissionError from rpcclient.fs import Fs from rpcclient.lief import Lief from rpcclient.network import Network @@ -29,7 +29,7 @@ from rpcclient.protocol import protocol_message_t, cmd_type_t, exec_chunk_t, exec_chunk_type_t, \ reply_protocol_message_t, dummy_block_t, SERVER_MAGIC_VERSION, argument_type_t, call_response_t, arch_t, \ protocol_handshake_t, call_response_t_size -from rpcclient.structs.consts import EEXIST, ENOTEMPTY, ENOENT, EPIPE, EISDIR +from rpcclient.structs.consts import EEXIST, ENOTEMPTY, ENOENT, EPIPE, EISDIR, EPERM from rpcclient.symbol import Symbol from rpcclient.symbols_jar import SymbolsJar from rpcclient.sysctl import Sysctl @@ -557,6 +557,7 @@ def _execution_loop(self, stdin: io_or_str = sys.stdin, stdout=sys.stdout): def raise_errno_exception(self, message: str): message += f' ({self.last_error})' exceptions = { + EPERM: RpcPermissionError, ENOENT: RpcFileNotFoundError, EEXIST: RpcFileExistsError, EISDIR: RpcIsADirectoryError, diff --git a/src/rpcclient/rpcclient/darwin/crash_reports.py b/src/rpcclient/rpcclient/darwin/crash_reports.py index a6a01a5d..a8a45cb9 100644 --- a/src/rpcclient/rpcclient/darwin/crash_reports.py +++ b/src/rpcclient/rpcclient/darwin/crash_reports.py @@ -30,7 +30,7 @@ def list(self, prefixed='') -> List[CrashReport]: for entry in self._client.fs.scandir(root): if entry.is_file() and entry.name.endswith('.ips') and entry.name.startswith(prefixed): with self._client.fs.open(entry.path, 'r') as f: - result.append(CrashReport(f.readall().decode(), filename=entry.path)) + result.append(CrashReport(f.read().decode(), filename=entry.path)) return result def clear(self, prefixed=''): diff --git a/src/rpcclient/rpcclient/darwin/darwin_lief.py b/src/rpcclient/rpcclient/darwin/darwin_lief.py index 86f44ea9..254a9298 100644 --- a/src/rpcclient/rpcclient/darwin/darwin_lief.py +++ b/src/rpcclient/rpcclient/darwin/darwin_lief.py @@ -14,7 +14,7 @@ class DarwinLief(Lief): @path_to_str('path') def get_entitlements(self, path: str) -> Mapping: with self._client.fs.open(path, 'r') as f: - buf = f.readall() + buf = f.read() parsed = lief.parse(buf) code_signature = buf[parsed.code_signature.data_offset: parsed.code_signature.data_offset + parsed.code_signature.data_size] diff --git a/src/rpcclient/rpcclient/darwin/location.py b/src/rpcclient/rpcclient/darwin/location.py index 5c8d9393..354f5147 100644 --- a/src/rpcclient/rpcclient/darwin/location.py +++ b/src/rpcclient/rpcclient/darwin/location.py @@ -1,7 +1,7 @@ from enum import Enum from typing import Mapping, Optional -from rpcclient.exceptions import MissingLibraryError, PermissionDeniedError +from rpcclient.exceptions import MissingLibraryError, RpcPermissionError from rpcclient.structs.consts import RTLD_NOW @@ -71,7 +71,7 @@ def last_sample(self) -> Optional[Mapping]: def start_updating_location(self): """ request location updates from CLLocationManager """ if self.authorization_status.value < CLAuthorizationStatus.kCLAuthorizationStatusAuthorizedAlways.value: - raise PermissionDeniedError() + raise RpcPermissionError() self._location_manager.objc_call('startUpdatingLocation') def stop_updating_location(self): @@ -81,5 +81,5 @@ def stop_updating_location(self): def request_oneshot_location(self): """ requests the one-time delivery of the user’s current location """ if self.authorization_status.value < CLAuthorizationStatus.kCLAuthorizationStatusAuthorizedAlways.value: - raise PermissionDeniedError() + raise RpcPermissionError() self._location_manager.objc_call('requestLocation') diff --git a/src/rpcclient/rpcclient/darwin/reports.py b/src/rpcclient/rpcclient/darwin/reports.py index 63f2dc75..060378a1 100644 --- a/src/rpcclient/rpcclient/darwin/reports.py +++ b/src/rpcclient/rpcclient/darwin/reports.py @@ -32,4 +32,4 @@ def get_logs(self, prefix='') -> List[Path]: @property def system_log(self) -> str: with self._client.fs.open('/var/log/system.log', 'r') as f: - return f.readall().decode() + return f.read().decode() diff --git a/src/rpcclient/rpcclient/exceptions.py b/src/rpcclient/rpcclient/exceptions.py index 81c596e6..675cc979 100644 --- a/src/rpcclient/rpcclient/exceptions.py +++ b/src/rpcclient/rpcclient/exceptions.py @@ -63,11 +63,6 @@ class MissingLibraryError(RpcClientException): pass -class PermissionDeniedError(RpcClientException): - """ failed to access a certain something """ - pass - - class NoEntitlementsError(RpcClientException): """ binary contains no entitlements """ pass @@ -101,3 +96,8 @@ class RpcNotEmptyError(BadReturnValueError): class RpcIsADirectoryError(BadReturnValueError): """ RPC version for IsADirectoryError (errno = ENOTEMPTY) """ pass + + +class RpcPermissionError(BadReturnValueError): + """ RPC version for PermissionError (errno = EPERM) """ + pass diff --git a/src/rpcclient/rpcclient/fs.py b/src/rpcclient/rpcclient/fs.py index 1565b41a..3c86558d 100644 --- a/src/rpcclient/rpcclient/fs.py +++ b/src/rpcclient/rpcclient/fs.py @@ -6,6 +6,7 @@ from rpcclient.allocated import Allocated from rpcclient.common import path_to_str from rpcclient.darwin.structs import MAXPATHLEN +from rpcclient.darwin.symbol import DarwinSymbol from rpcclient.exceptions import BadReturnValueError, ArgumentError, RpcFileNotFoundError, RpcFileExistsError, \ RpcIsADirectoryError from rpcclient.structs.consts import O_RDONLY, O_WRONLY, O_CREAT, O_TRUNC, S_IFMT, S_IFDIR, O_RDWR, SEEK_CUR, S_IFREG, \ @@ -96,7 +97,7 @@ def __repr__(self): class File(Allocated): - CHUNK_SIZE = 1024 + CHUNK_SIZE = 1024 * 64 def __init__(self, client, fd: int): """ @@ -123,39 +124,36 @@ def seek(self, offset: int, whence: int) -> int: def tell(self) -> int: return self.seek(0, SEEK_CUR) - def write(self, buf: bytes) -> int: + def _write(self, buf: bytes) -> int: """ write(fd, buf, size) at remote. read man for more details. """ n = self._client.symbols.write(self.fd, buf, len(buf)).c_int64 if n < 0: self._client.raise_errno_exception(f'failed to write on fd: {self.fd}') return n - def writeall(self, buf: bytes): + def write(self, buf: bytes): """ continue call write() until """ while buf: - err = self.write(buf) + err = self._write(buf) buf = buf[err:] - def read(self, size: int = CHUNK_SIZE) -> bytes: + def _read(self, buf: DarwinSymbol, size: int) -> bytes: """ read file at remote """ - with self._client.safe_malloc(size) as chunk: - err = self._client.symbols.read(self.fd, chunk, self.CHUNK_SIZE).c_int64 - if err < 0: - self._client.raise_errno_exception(f'read failed for fd: {self.fd}') - return chunk.peek(err) + err = self._client.symbols.read(self.fd, buf, size).c_int64 + if err < 0: + self._client.raise_errno_exception(f'read failed for fd: {self.fd}') + return buf.peek(err) - def readall(self, chunk_size: int = CHUNK_SIZE) -> bytes: + def read(self, size: int = -1, chunk_size: int = CHUNK_SIZE) -> bytes: """ read file at remote """ buf = b'' with self._client.safe_malloc(chunk_size) as chunk: - while True: - err = self._client.symbols.read(self.fd, chunk, chunk_size).c_int64 - if err == 0: + while size == -1 or len(buf) < size: + read_chunk = self._read(chunk, chunk_size) + if not read_chunk: # EOF break - if err < 0: - self._client.raise_errno_exception(f'read failed for fd: {self.fd}') - buf += chunk.peek(err) + buf += read_chunk return buf def __repr__(self): @@ -304,12 +302,12 @@ def open(self, file: str, mode: str, access: int = 0o777) -> File: @path_to_str('file') def write_file(self, file: str, buf: bytes, access: int = 0o777): with self.open(file, 'w+', access=access) as f: - f.writeall(buf) + f.write(buf) @path_to_str('file') def read_file(self, file: str) -> bytes: with self.open(file, 'r') as f: - return f.readall() + return f.read() @path_to_str('file') def touch(self, file: str, mode: int = None): diff --git a/src/rpcclient/rpcclient/ios/lockdown.py b/src/rpcclient/rpcclient/ios/lockdown.py index d1b8d123..cb271bf1 100644 --- a/src/rpcclient/rpcclient/ios/lockdown.py +++ b/src/rpcclient/rpcclient/ios/lockdown.py @@ -15,7 +15,7 @@ def pair_records(self) -> List[PairRecord]: result = [] for entry in self._client.fs.scandir('/var/root/Library/Lockdown/pair_records'): with self._client.fs.open(entry.path, 'r') as f: - record = plistlib.loads(f.readall()) + record = plistlib.loads(f.read()) result.append(PairRecord(hostname=record['HostName'], host_id=record['HostID'], certificate=record['HostCertificate'])) diff --git a/src/rpcclient/rpcclient/lief.py b/src/rpcclient/rpcclient/lief.py index 33037166..b72aa26a 100644 --- a/src/rpcclient/rpcclient/lief.py +++ b/src/rpcclient/rpcclient/lief.py @@ -17,7 +17,7 @@ def __init__(self, client): @path_to_str('path') def parse(self, path: str): with self._client.fs.open(path, 'r') as f: - return lief.parse(f.readall()) + return lief.parse(f.read()) @path_to_str('path') def get_symbols(self, path: str) -> Mapping[str, Symbol]: diff --git a/src/rpcclient/rpcclient/structs/consts.py b/src/rpcclient/rpcclient/structs/consts.py index 17e84887..b170167b 100644 --- a/src/rpcclient/rpcclient/structs/consts.py +++ b/src/rpcclient/rpcclient/structs/consts.py @@ -86,6 +86,7 @@ SIGXCPU = 24 SIGXFSZ = 25 +EPERM = 1 ENOENT = 2 EEXIST = 17 EISDIR = 21 diff --git a/src/rpcclient/rpcclient/xonshrc.py b/src/rpcclient/rpcclient/xonshrc.py index e8cd5bfc..47147366 100644 --- a/src/rpcclient/rpcclient/xonshrc.py +++ b/src/rpcclient/rpcclient/xonshrc.py @@ -279,9 +279,7 @@ def _rpc_cp(self, args, stdin, stdout, stderr): parser.add_argument('src') parser.add_argument('dst') args = parser.parse_args(args) - with self.client.fs.open(args.src, 'r') as src: - with self.client.fs.open(args.dst, 'w') as dst: - dst.writeall(src.readall()) + self.client.fs.write_file(args.dst, self.client.fs.read_file(args.src)) def _rpc_mkdir(self, args, stdin, stdout, stderr): parser = ArgumentParser(description='create a directory') @@ -297,7 +295,7 @@ def _rpc_cat(self, args, stdin, stdout, stderr): args = parser.parse_args(args) for filename in args.filename: with self.client.fs.open(filename, 'r') as f: - print(f.readall(), file=stdout, end='', flush=True) + print(f.read(), file=stdout, end='', flush=True) print('', file=stdout, flush=True) def _rpc_bat(self, args, stdin, stdout, stderr): @@ -351,7 +349,7 @@ def _rpc_plshow(self, args, stdin, stdout, stderr): parser.add_argument('filename') args = parser.parse_args(args) with self.client.fs.open(args.filename, 'r') as f: - _print_json(plistlib.loads(f.readall()), file=stdout) + _print_json(plistlib.loads(f.read()), file=stdout) def _rpc_record(self, args, stdin, stdout, stderr): parser = ArgumentParser(description='start recording for specified duration') @@ -451,14 +449,12 @@ def _listdir(self, path: str) -> List[str]: return self.client.fs.listdir(path) def _pull(self, remote_filename, local_filename): - with self.client.fs.open(remote_filename, 'r') as remote_fd: - with open(local_filename, 'wb') as local_fd: - local_fd.write(remote_fd.readall()) + with open(local_filename, 'wb') as f: + f.write(self.client.fs.read_file(remote_filename)) def _push(self, local_filename, remote_filename): - with open(local_filename, 'rb') as from_fd: - with self.client.fs.open(remote_filename, 'w') as to_fd: - to_fd.write(from_fd.read()) + with open(local_filename, 'rb') as f: + self.client.fs.write_file(remote_filename, f.read()) # actual RC contents