def _check_capabilities_internal_dynamic(cls, app_bundle : Bundle, operation_name): """Checks whether the app sandbox allows a particular operation at runtime""" sandbox_check = tool_named("sandbox_check") # Initialize sandbox success = app_utils.init_sandbox(app_bundle, logger) if not success: return True # Unsandboxed apps may do whatever they want. sandbox_ruleset = app_utils.get_sandbox_rules(app_bundle) response = subprocess.run([sandbox_check, operation_name], input=sandbox_ruleset) # Capability is... if response.returncode == 0: # allowed return True elif response.returncode == 1: # not allowed return False else: # Some kind of error occurred. raise RuntimeError("Capability could not be checked.")
def get_entitlements(cls, filepath, raw=False): """ Extract entitlements from a target binary. :param filepath: filepath for binary for which to extract entitlements :param raw: Whether to return the raw bytes. If false, returns a dictionary. Else, returns bytes :return: Dictionary containing application entitlements. Returns empty dictionary in case of errors. """ jtool = tool_named("jtool") env = os.environ.copy() env['ARCH'] = 'x86_64' exit_code, results = jtool("--ent", filepath, env=env) if exit_code != 0: return dict() if raw: return results else: return plist.parse_resilient_bytes(results)
def _entitlements_can_be_parsed(app_bundle: Bundle) -> bool: """ Check whether an application's entitlements can be parsed by libsecinit. We only check part of the process, namely the parsing of entitlements via xpc_create_from_plist. :param app_bundle: Bundle for which to check whether the entitlements can be parsed :type app_bundle: Bundle :return: True, iff the entitlements of the main executable can be parsed, else false. """ # No entitlements, no problem # If the app contains no entitlements, entitlement validation cannot fail. if not app_bundle.has_entitlements(): return True exe_path = app_bundle.executable_path() raw_entitlements = Binary.get_entitlements(exe_path, raw=True) # Call the local xpc_vuln_checker program that does the actual checking. exit_code, _ = tool_named("xpc_vuln_checker")(input=raw_entitlements) return exit_code != 1
def sandbox_status(app_bundle: Bundle, logger: logging.Logger) -> Optional[int]: process = subprocess.Popen([app_bundle.executable_path()], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) # Sandbox initialisation should be almost instant. If the application is still # running after a couple of seconds, the sandbox failed to initialise or is # not enabled at all. # We use 10 seconds as an arbitrary cutoff time. time.sleep(10) pid = str(process.pid) if process.poll() is not None: logger.error("Process terminated early: {}".format( app_bundle.executable_path())) return None sb_status = subprocess.run([tool_named("sandbox_status"), pid], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) process.kill() rx = re.compile(r'^Sandbox status for PID {} is (\d+)$'.format(pid)) m = rx.match(sb_status.stdout.decode().strip()) if m: return int(m.group(1)) logger.error( "`sandbox_status` did not return a status for executable at {}. Skipping." .format(app_bundle.executable_path())) return None
"""The Plist module assists in Plist (propertly list) parsing. Even though the `plistlib` module exists, it fails to parse some files that are malformed and choke up the XML parser, while being accepted by macOS. This module bridges the gap and attempts to accept all plists that are accepted by macOS.""" import os import tempfile import subprocess import plistlib from extern.tools import tool_named SANITIZER = tool_named("plist_sanitizer") class tdict(dict): """ Typed dict extension. """ def typed_get(self, key, type, default=None): """ Return the value for `key` if key is in the dictionary and is of type `type`, else return `default`. """ if key not in self: return default if not isinstance(self[key], type): return default return self[key]