diff --git a/VIII. Sandbox/mac/Apple-Sandbox-Guide-v1.0.pdf b/VIII. Sandbox/mac/Apple-Sandbox-Guide-v1.0.pdf new file mode 100644 index 0000000..b83a8d3 Binary files /dev/null and b/VIII. Sandbox/mac/Apple-Sandbox-Guide-v1.0.pdf differ diff --git a/VIII. Sandbox/mac/The Apple Sandbox 2011 - Dionysus Blazakis.pdf b/VIII. Sandbox/mac/The Apple Sandbox 2011 - Dionysus Blazakis.pdf new file mode 100644 index 0000000..8a32789 Binary files /dev/null and b/VIII. Sandbox/mac/The Apple Sandbox 2011 - Dionysus Blazakis.pdf differ diff --git a/VIII. Sandbox/mac/sandbox_hooks_list.txt b/VIII. Sandbox/mac/sandbox_hooks_list.txt new file mode 100644 index 0000000..607c0f5 --- /dev/null +++ b/VIII. Sandbox/mac/sandbox_hooks_list.txt @@ -0,0 +1,158 @@ +_hook_iokit_check_get_property +_hook_iokit_check_filter_properties +_hook_vnode_notify_link +_hook_kext_check_unload +_hook_kext_check_load +_hook_pty_notify_close +_hook_pty_notify_grant +_hook_system_check_info +_hook_vnode_check_lookup_preflight +_hook_system_check_kas_info +_hook_vnode_notify_deleteextattr +_hook_vnode_check_setacl +_hook_vnode_notify_rename +_hook_vnode_notify_create +_hook_proc_check_ledger +_hook_iokit_check_open +_hook_vnode_check_fsgetpath +_hook_proc_check_map_anon +_hook_priv_grant +_hook_priv_check +_hook_vnode_check_searchfs +_hook_iokit_check_set_properties +___hook_iokit_check_set_properties_block_invoke +___hook_iokit_check_set_properties_block_invoke_2 +_hook_thread_userret +_hook_proc_check_suspend_resume +_hook_vnode_check_uipc_connect +_hook_vnode_check_uipc_bind +_hook_vnode_label_recycle +_hook_vnode_label_destroy +_hook_vnode_label_copy +_hook_vnode_check_unlink +_hook_vnode_check_truncate +_hook_vnode_check_stat +_hook_vnode_check_setutimes +_hook_vnode_check_setowner +_hook_vnode_check_setmode +_hook_vnode_check_setflags +_hook_vnode_check_setextattr +_hook_vnode_check_setattrlist +_hook_vnode_check_revoke +_hook_vnode_check_readlink +_hook_vnode_check_open +_hook_vnode_check_listextattr +_hook_vnode_check_link +_hook_vnode_check_ioctl +_hook_vnode_check_getextattr +_hook_vnode_check_getattrlist +_hook_vnode_check_exchangedata +_hook_vnode_check_deleteextattr +_hook_vnode_check_create +_hook_vnode_check_chroot +_hook_vnode_check_access +_hook_iokit_check_hid_control +_hook_proc_check_set_cs_info +_hook_proc_check_get_cs_info +_hook_vnode_check_clone +_hook_mount_check_snapshot_delete +_hook_mount_check_snapshot_create +_hook_mount_check_snapshot_revert +_hook_proc_notify_exit +_hook_sysvshm_check_shmget +_hook_sysvshm_check_shmdt +_hook_sysvshm_check_shmctl +_hook_sysvshm_check_shmat +_hook_sysvsem_check_semop +_hook_sysvsem_check_semget +_hook_sysvsem_check_semctl +_hook_sysvmsq_check_msqsnd +_hook_sysvmsq_check_msqrcv +_hook_sysvmsq_check_msqget +_hook_sysvmsq_check_msqctl +_hook_sysvmsq_check_msgrmid +_hook_sysvmsq_check_msgrcv +_hook_sysvmsq_check_enqueue +_hook_socket_check_ioctl +_hook_system_check_swapon +_hook_system_check_swapoff +_hook_system_check_settime +_hook_system_check_reboot +_hook_system_check_nfsd +_hook_system_check_host_priv +_hook_system_check_auditon +_hook_system_check_auditctl +_hook_system_check_audit +_hook_system_check_acct +_hook_iokit_check_open_service +_hook_thread_microstackshot +_hook_proc_check_memorystatus_control +_hook_socket_check_getsockopt +_hook_socket_check_setsockopt +_hook_socket_check_send +_hook_socket_check_receive +_hook_socket_check_listen +_hook_socket_check_create +_hook_socket_check_connect +_hook_socket_check_bind +_hook_proc_check_signal +_hook_proc_check_setauid +_hook_proc_check_setaudit +_hook_proc_check_sched +_hook_proc_check_fork +_hook_proc_check_debug +_hook_posixshm_check_unlink +_hook_posixshm_check_open +_hook_posixshm_check_create +_hook_posixsem_check_wait +_hook_posixsem_check_unlink +_hook_posixsem_check_post +_hook_posixsem_check_open +_hook_posixsem_check_create +_hook_skywalk_flow_check_listen +_hook_skywalk_flow_check_connect +_hook_mount_check_snapshot_mount +_hook_mount_check_mount_late +_hook_vnode_check_trigger_resolve +_hook_proc_check_set_host_exception_port +_hook_proc_check_set_host_special_port +_hook_proc_check_syscall_unix +_hook_proc_notify_exec_complete +_hook_kext_check_query +_hook_vnode_check_rename +_hook_system_check_sysctlbyname +_hook_policy_syscall +_hook_policy_initbsd +_hook_policy_init +_hook_proc_check_migroutine_invoke +_hook_proc_check_syscall_mach +_hook_proc_check_syscall_mac +_hook_proc_check_get_task_with_flavor +_hook_proc_check_expose_task_with_flavor +_hook_mount_label_init +_hook_mount_label_destroy +_hook_mount_label_associate +_hook_mount_check_umount +_hook_mount_check_remount +_hook_mount_check_mount +_hook_mount_check_fsctl +_hook_mount_check_quotactl +_hook_vnode_check_copyfile +_hook_vnode_notify_unlink +_hook_proc_check_set_task_special_port +_hook_proc_check_get_task_special_port +_hook_vnode_notify_setflags +_hook_vnode_notify_setextattr +_hook_necp_check_client_action +_hook_necp_check_open +_hook_file_check_set +_hook_file_check_mmap +_hook_file_check_lock +_hook_file_check_fcntl +_hook_cred_label_update +_hook_cred_label_destroy +_hook_cred_label_associate +_hook_cred_check_label_update +_hook_vnode_check_exec +_hook_cred_check_label_update_execve +_hook_cred_label_update_execve \ No newline at end of file diff --git a/VIII. Sandbox/python/CrimsonUroboros.py b/VIII. Sandbox/python/CrimsonUroboros.py index ae8ca28..f6be979 100755 --- a/VIII. Sandbox/python/CrimsonUroboros.py +++ b/VIII. Sandbox/python/CrimsonUroboros.py @@ -97,7 +97,7 @@ class SnakeHatchery: ''' Initialize the binary object in global scope.''' global binaries # It must be global, becuase after this object is destructed, the snake_instance would point to invalid memory ("binary" is dependant on "binaries"). - if self.file_path_exists: + if self.file_path: # We cannot use here self.file_path_exists because at this point the file_path cpuld be set fron None to a valid path by filePathInit() method (when only -b specified) adhoc_macho_processor = MachOProcessor() # Just for this limited scope if adhoc_macho_processor.isFileMachO(self.file_path): @@ -208,6 +208,14 @@ class BundleProcessor: else: return None + def getBundleId(self): + ''' Return CFBundleIdentifier from Info.plist of the App Bundle. ''' + if self.info_plist_exists: + with open(self.info_plist_path, 'rb') as f: + plist = plistlib.load(f) + return plist.get('CFBundleIdentifier') + return None + class SnakeAppBundleExtension: def __init__(self, binaries, file_path): ''' It stores only logic for CrimsonUroboros flags. Most of the code related to parsing and extracting data from App Bundle is in BundleProcessor class. @@ -248,6 +256,14 @@ class SnakeAppBundleExtension: else: print("No plugins found.") + def printBundleId(self): + ''' Print the CFBundleIdentifier from Info.plist of the App Bundle. ''' + bundle_id = bundle_processor.getBundleId() + if bundle_id: + print(bundle_id) + else: + print("No bundle id found.") + ### --- I. MACH-O --- ### class MachOProcessor: def __init__(self): @@ -384,6 +400,9 @@ class MachOProcessor: if args.dump_section: # Dump section to a stdout snake_instance.dumpSectionToStdout(args.dump_section) + if args.dump_binary: # Dump binary to a given file + snake_instance.dumpBinaryToFile(args.dump_binary) + def isFileMachO(self, file_path): '''Check if file is Mach-O. ''' try: @@ -695,12 +714,12 @@ class SnakeI(SnakeAppBundleExtension): def getStringSection(self): '''Return strings from the __cstring (string table).''' - extracted_strings = set() + extracted_strings = [] for section in self.binary.sections: if section.type == lief.MachO.SECTION_TYPES.CSTRING_LITERALS: strings_bytes = section.content.tobytes() - strings = strings_bytes.decode('utf-8', errors='ignore') # Adjust the encoding as per your requirements - extracted_strings.update(strings.split('\x00')) + strings = strings_bytes.decode('utf-8', errors='ignore') + extracted_strings.extend(strings.split('\x00')) return extracted_strings def findAllStringsInBinary(self): @@ -914,7 +933,15 @@ class SnakeI(SnakeAppBundleExtension): segment_name = segment_section[0] section_name = segment_section[1] extracted_bytes = self.extractSection(segment_name, section_name) - print(extracted_bytes) + sys.stdout.buffer.write(extracted_bytes) + + def dumpBinaryToFile(self, output_path): + ''' Dump ARM64 binary from FAT binary and save to a given file. ''' + if binaries is not None: + if self.binary is not None: + if not output_path.startswith('/'): + output_path = os.path.join(os.getcwd(), output_path) + self.binary.write(output_path) ### --- II. CODE SIGNING --- ### class CodeSigningProcessor: @@ -2809,6 +2836,281 @@ class SnakeVII(SnakeVI): value_as_bytes = value.encode() xattr.setxattr(self.file_path, 'com.apple.quarantine', value_as_bytes) +### ---- VIII. SANDBOX --- ### +class SandboxProcessor: + def __init__(self): + '''This class contains part of the code from the main() for the SnakeVIII: Sandbox.''' + pass + + def process(self, args): + if args.sandbox_container_path: # Print the sandbox container path + snake_instance.printSandboxContainerPath() + + if args.sandbox_container_metadata: # Print the sandbox container metadata + snake_instance.printSandboxContainerMetadata() + + if args.sandbox_redirectable_paths: # Print the sandbox redirectable paths + snake_instance.printSandboxRedirectablePaths() + + if args.sandbox_parameters: # Print the sandbox parameters + snake_instance.printSandboxParameters() + + if args.sandbox_entitlements: # Print the sandbox entitlements + snake_instance.printSandboxEntitlements() + + if args.sandbox_build_uuid: # Print the sandbox build UUID + snake_instance.printSandboxBuildUUID() + + if args.sandbox_redirected_paths: # Print the sandbox redirected paths + snake_instance.printSandboxRedirectedPaths() + + if args.sandbox_system_images: # Print the sandbox system images + snake_instance.printSandboxSystemImages() + + if args.sandbox_system_profiles: # Print the sandbox system profiles + snake_instance.printSandboxSystemProfiles() + + if args.sandbox_content_protection: # Print the sandbox com.apple.MobileInstallation.ContentProtectionClass value + snake_instance.printContentProtectionClass() + + if args.sandbox_profile_data: # Print the sandbox profile data + snake_instance.printSandboxProfileData() + + if args.dump_kext: # Dump the kernel extension binary from the kernelcache.decompressed file + snake_instance.dumpKernelExtensionBinary(args.dump_kext) + + if args.extract_sandbox_operations: # Extract sandbox operations from the kernelcache.decompressed file + snake_instance.printSandboxOperations() + + +class SnakeVIII(SnakeVII): + def __init__(self, binaries, file_path): + super().__init__(binaries, file_path) + self.kext_map.update({ + 'com.apple.security.sandbox' : 'sandbox', + 'sandbox.kext' : 'sandbox', + }) + self.container_metadata_file = ".com.apple.containermanagerd.metadata.plist" + + def getSandboxContainerPath(self): + ''' Check if sandbox container exists for the given App Bundle and if it exists, return the path to the container. ''' + bundle_id = bundle_processor.getBundleId() + if bundle_id: + container_path = os.path.join(os.path.expanduser('~'), 'Library', 'Containers', bundle_id) + if os.path.exists(container_path): + return container_path + else: + print("Bundle id is not set in Info.plist for the given App Bundle.") + return None + + def printSandboxContainerPath(self): + ''' Print the sandbox container path. ''' + container_path = self.getSandboxContainerPath() + if container_path: + print(f'APP BUNDLE SANDBOX CONTAINER: {container_path}') + else: + print("No sandbox container found for the given App Bundle.") + + def getSandboxContainerMetadata(self): + ''' Return the sandbox container metadata in XML format. ''' + container_path = self.getSandboxContainerPath() + if container_path: + container_metadata_path = os.path.join(container_path, self.container_metadata_file) + if os.path.exists(container_metadata_path): + with open(container_metadata_path, 'rb') as f: + container_metadata = plistlib.load(f) + return container_metadata + return None + + def printSandboxContainerMetadata(self): + ''' Print the sandbox container metadata in XML format. ''' + container_metadata = self.getSandboxContainerMetadata() + if container_metadata is None: + print("No sandbox container found for the given App Bundle.") + else: + container_metadata_xml = (plistlib.dumps(container_metadata, fmt=plistlib.FMT_XML).decode('utf-8')) + print(container_metadata_xml) + + def getSandboxRedirectablePaths(self): + ''' Return the redirectable paths as array. ''' + container_metadata = self.getSandboxContainerMetadata() + if container_metadata: + redirectable_paths = container_metadata.get('MCMMetadataInfo', {}).get('SandboxProfileDataValidationInfo', {}).get('RedirectablePaths', []) + return redirectable_paths + return None + + def printSandboxRedirectablePaths(self): + ''' Print the redirectable paths. ''' + redirectable_paths = self.getSandboxRedirectablePaths() + if redirectable_paths: + for path in redirectable_paths: + print(path) + else: + print("No redirectable paths found for the given App Bundle.") + + def getSandboxParameters(self): + ''' Return the sandbox parameters as dictionary. ''' + container_metadata = self.getSandboxContainerMetadata() + if container_metadata: + parameters = container_metadata.get('MCMMetadataInfo', {}).get('SandboxProfileDataValidationInfo', {}).get('Parameters', {}) + return parameters + return None + + def printSandboxParameters(self): + ''' Print the sandbox parameters as key-value pairs. ''' + parameters = self.getSandboxParameters() + if parameters: + for parameter_key, parameter_value in parameters.items(): + print(f"{parameter_key}: {parameter_value}") + else: + print("No sandbox parameters found for the given App Bundle.") + + def getSandboxEntitlements(self): + ''' Return the sandbox entitlements as dictionary. ''' + container_metadata = self.getSandboxContainerMetadata() + if container_metadata: + entitlements = container_metadata.get('MCMMetadataInfo', {}).get('SandboxProfileDataValidationInfo', {}).get('Entitlements', {}) + return entitlements + return None + + def printSandboxEntitlements(self): + ''' Print the sandbox entitlements as JSON. ''' + entitlements = self.getSandboxEntitlements() + if entitlements: + print(json.dumps(entitlements, indent=4)) + else: + print("No sandbox entitlements found for the given App Bundle.") + + def getSandboxBuildUUID(self): + ''' Return the sandbox build UUID as string. ''' + container_metadata = self.getSandboxContainerMetadata() + if container_metadata: + build_uuid = container_metadata.get('MCMMetadataInfo', {}).get('SandboxProfileDataValidationInfo', {}).get('Sandbox_Build_UUID', '') + return build_uuid + return None + + def printSandboxBuildUUID(self): + ''' Print the sandbox build UUID as string. ''' + build_uuid = self.getSandboxBuildUUID() + if build_uuid: + print(f"Build UUID: {build_uuid}") + else: + print("No sandbox build UUID found for the given App Bundle.") + + def getSandboxRedirectedPaths(self): + ''' Return the redirected paths as list. ''' + container_metadata = self.getSandboxContainerMetadata() + if container_metadata: + redirected_paths = container_metadata.get('MCMMetadataInfo', {}).get('SandboxProfileDataValidationInfo', {}).get('RedirectedPaths', []) + return redirected_paths + return None + + def printSandboxRedirectedPaths(self): + ''' Print the redirected paths as dictionary. ''' + redirected_paths = self.getRedirectedPaths() + if redirected_paths: + for path in redirected_paths: + print(path) + else: + print("No redirected paths found for the given App Bundle.") + + def getSandboxSystemImages(self): + ''' Return the system images as list. ''' + container_metadata = self.getSandboxContainerMetadata() + if container_metadata: + system_images = container_metadata.get('MCMMetadataInfo', {}).get('SandboxProfileDataValidationInfo', {}).get('SystemImages', []) + return system_images + return None + + def printSandboxSystemImages(self): + ''' Print the system images as dictionary. ''' + system_images = self.getSandboxSystemImages() + if system_images: + for path in system_images: + print(path) + else: + print("No system images found for the given App Bundle.") + + def getSandboxSystemProfiles(self): + ''' Return the system profiles as dict. ''' + container_metadata = self.getSandboxContainerMetadata() + if container_metadata: + system_profiles = container_metadata.get('MCMMetadataInfo', {}).get('SandboxProfileDataValidationInfo', {}).get('SystemProfiles', {}) + return system_profiles + return None + + def printSandboxSystemProfiles(self): + ''' Print the system profiles as JSON. ''' + system_profiles = self.getSandboxSystemProfiles() + if system_profiles: + # Convert datetime to string for all profiles + for profile in system_profiles: + if 'AppSandboxProfileSnippetModificationDateKey' in profile: + profile['AppSandboxProfileSnippetModificationDateKey'] = profile['AppSandboxProfileSnippetModificationDateKey'].isoformat() + + # Print all profiles as a single JSON object + print(json.dumps(system_profiles, indent=4)) + else: + print("No system profiles found for the given App Bundle.") + + def getContentProtectionClass(self): + '''Return the com.apple.MobileInstallation.ContentProtectionClass value''' + container_metadata = self.getSandboxContainerMetadata() + if container_metadata: + content_protection = container_metadata.get('MCMMetadataInfo', {}).get('com.apple.MobileInstallation.ContentProtectionClass') + return content_protection + return None + + def printContentProtectionClass(self): + '''Print the com.apple.MobileInstallation.ContentProtectionClass value''' + content_protection = self.getContentProtectionClass() + if content_protection is not None: + print(f"com.apple.MobileInstallation.ContentProtectionClass: {content_protection}") + else: + print("No com.apple.MobileInstallation.ContentProtectionClass found for the given App Bundle.") + + def getSandboxProfileData(self): + ''' Return the sandbox profile data as bytes. ''' + container_metadata = self.getSandboxContainerMetadata() + if container_metadata: + sandbox_profile_data = container_metadata.get('MCMMetadataInfo', {}).get('SandboxProfileData', None) + return sandbox_profile_data + return None + + def printSandboxProfileData(self): + ''' Print the sandbox profile data as bytes. ''' + sandbox_profile_data = self.getSandboxProfileData() + if sandbox_profile_data: + sys.stdout.buffer.write(sandbox_profile_data) + else: + print("No SandboxProfileData found for the given App Bundle.") + + def dumpKernelExtensionBinary(self, kext_name): + ''' Dump the kernel extension binary from the kernelcache.decompressed file. + For now it is only wrapper arround ipsw''' + os.system(f"ipsw kernel extract {self.file_path} {kext_name}") + + def extractSandboxOperations(self): + ''' Extract sandbox operations from the Sandbox.kext file. ''' + all_strings = self.getStringSection() + operations = [] + capture = False + + for string in all_strings: + if string == 'default': + capture = True + if capture: + operations.append(string) + if string == 'xpc-message-send': + capture = False + + return operations + + def printSandboxOperations(self): + '''Print the sandbox operations.''' + operations = self.extractSandboxOperations() + for operation in operations: + print(operation) ### --- ARGUMENT PARSER --- ### class ArgumentParser: @@ -2824,6 +3126,7 @@ class ArgumentParser: self.addDyldArgs() self.addAMFIArgs() self.addAntivirusArgs() + self.addSandboxArgs() def addGeneralArgs(self): general_group = self.parser.add_argument_group('GENERAL ARGS') @@ -2837,6 +3140,7 @@ class ArgumentParser: bundle_group.add_argument('--bundle_info_syntax_check', action='store_true', help="Check if bundle info syntax is valid") bundle_group.add_argument('--bundle_frameworks', action='store_true', help="Print the list of frameworks in the bundle") bundle_group.add_argument('--bundle_plugins', action='store_true', help="Print the list of plugins in the bundle") + bundle_group.add_argument('--bundle_id', action='store_true', help="Print the CFBundleIdentifier value from the Info.plist file if it exists") def addMachOArgs(self): macho_group = self.parser.add_argument_group('MACH-O ARGS') @@ -2867,6 +3171,7 @@ class ArgumentParser: macho_group.add_argument('--calc_offset', help="Calculate the real address (file on disk) of the given Virtual Memory {vm_offset} (e.g. 0xfffffe000748f580)", metavar='vm_offset') macho_group.add_argument('--constructors', action='store_true', help="Print binary constructors") macho_group.add_argument('--dump_section', help="Dump '__SEGMENT,__section' to standard output as a raw bytes", metavar='__SEGMENT,__section') + macho_group.add_argument('--dump_binary', help="Dump arm64 binary to a given file", metavar='output_path') def addCodeSignArgs(self): codesign_group = self.parser.add_argument_group('CODE SIGNING ARGS') @@ -2954,6 +3259,24 @@ class ArgumentParser: antivirus_group.add_argument('--remove_quarantine', action='store_true', help="Remove com.apple.quarantine extended attribute from the file") antivirus_group.add_argument('--add_quarantine', action='store_true', help="Add com.apple.quarantine extended attribute to the file") + def addSandboxArgs(self): + sandbox_group = self.parser.add_argument_group('SANDBOX ARGS') + sandbox_group.add_argument('--sandbox_container_path', action='store_true', help="todo") + sandbox_group.add_argument('--sandbox_container_metadata', action='store_true', help="Print the .com.apple.containermanagerd.metadata.plist contents for the given bundlein XML format") + sandbox_group.add_argument('--sandbox_redirectable_paths', action='store_true', help="Print the redirectable paths from the sandbox container metadata as list") + sandbox_group.add_argument('--sandbox_parameters', action='store_true', help="Print the parameters from the sandbox container metadata as key-value pairs") + sandbox_group.add_argument('--sandbox_entitlements', action='store_true', help="Print the entitlements from the sandbox container metadata in JSON format") + sandbox_group.add_argument('--sandbox_build_uuid', action='store_true', help="Print the sandbox build UUID from the sandbox container metadata") + sandbox_group.add_argument('--sandbox_redirected_paths', action='store_true', help="Print the redirected paths from the sandbox container metadata as list") + sandbox_group.add_argument('--sandbox_system_images', action='store_true', help="Print the system images from the sandbox container metadata as key-value pairs") + sandbox_group.add_argument('--sandbox_system_profiles', action='store_true', help="Print the system profile from the sandbox container metadata in JSON format") + sandbox_group.add_argument('--sandbox_content_protection', action='store_true', help="Print the content protection from the sandbox container metadata") + sandbox_group.add_argument('--sandbox_profile_data', action='store_true', help="Print raw bytes ofthe sandbox profile data from the sandbox container metadata") + sandbox_group.add_argument('--dump_kext', help="Dump the kernel extension binary from the kernelcache.decompressed file", metavar='kext_name') + sandbox_group.add_argument('--extract_sandbox_operations', action='store_true', help="Extract sandbox operations from the kernelcache.decompressed file") + + + def parseArgs(self): args = self.parser.parse_args() @@ -3324,7 +3647,7 @@ if __name__ == "__main__": args = arg_parser.parseArgs() ### --- APP BUNDLE EXTENSION --- ### - snake_hatchery = SnakeHatchery(args, SnakeVII) + snake_hatchery = SnakeHatchery(args, SnakeVIII) snake_hatchery.hatch() ### --- I. MACH-O --- ### @@ -3354,3 +3677,7 @@ if __name__ == "__main__": ### --- VII. ANTIVIRUS --- ### antivirus_processor = AntivirusProcessor() antivirus_processor.process(args) + + ### --- VIII. SANDBOX --- ### + sandbox_processor = SandboxProcessor() + sandbox_processor.process(args) diff --git a/VIII. Sandbox/python/sandbox_inspector.py b/VIII. Sandbox/python/sandbox_inspector.py index 8faf3d3..60ddb24 100644 --- a/VIII. Sandbox/python/sandbox_inspector.py +++ b/VIII. Sandbox/python/sandbox_inspector.py @@ -1,6 +1,7 @@ import os import plistlib import argparse +import base64 class SandboxInspector: def __init__(self, home_dir): @@ -71,7 +72,7 @@ def main(): parser.add_argument('-p', '--path', type=str, required=True, help="Path to the application (e.g., /Applications/Notes.app)") parser.add_argument('-m', '--metadata', action='store_true', help="Print the .com.apple.containermanagerd.metadata.plist contents") parser.add_argument('-r', '--redirectable', action='store_true', help="Print the redirectable paths") - parser.add_argument('-s', '--sandbox_profile_data', action='store_true', help="Print the SandboxProfileData bytes") + parser.add_argument('-s', '--sandbox_profile_data', action='store_true', help="Print the SandboxProfileData as base64-encoded bytes") args = parser.parse_args() app_path = args.path @@ -103,7 +104,8 @@ def main(): sandbox_profile_data = inspector.get_sandbox_profile_data(bundle_id) if sandbox_profile_data: parsed_data = inspector.parse_sandbox_profile_data(sandbox_profile_data) - print(f"SandboxProfileData for {bundle_id}:\n{parsed_data}") + encoded_data = base64.b64encode(parsed_data).decode('utf-8') + print(f"SandboxProfileData for {bundle_id} (base64-encoded):\n{encoded_data}") else: print(f"No SandboxProfileData found for {bundle_id}.") else: diff --git a/tests/CrimsonUroboros.py b/tests/CrimsonUroboros.py index 65cec4e..e16bcf9 120000 --- a/tests/CrimsonUroboros.py +++ b/tests/CrimsonUroboros.py @@ -1 +1 @@ -../App Bundle Extension/python/CrimsonUroboros.py \ No newline at end of file +../VIII. Sandbox/python/CrimsonUroboros.py \ No newline at end of file diff --git a/tests/test_CrimsonUroboros.py b/tests/test_CrimsonUroboros.py index 49916d7..336f031 100644 --- a/tests/test_CrimsonUroboros.py +++ b/tests/test_CrimsonUroboros.py @@ -306,6 +306,9 @@ class TestSnakeI(): # Purge the compiled files cls.compiler.purgeCompiledFiles() assert not os.path.exists("hello_1") # Check if the file is removed after purging + + os.remove("test_bin") + assert not os.path.exists("test_bin") # Check if the file is removed after purging def test_MachOProcessor(self): '''Test the initialization of MachOProcessor. @@ -769,7 +772,15 @@ class TestSnakeI(): def test_dump_section(self): '''Test the --dump_section flag of SnakeI.''' - args_list = ['-p', 'hello_1', '--dump_section', '__TEXT,__cstring'] + uroboros_output = run_and_get_stdout('python3 CrimsonUroboros.py -p hello_1 --dump_section "__TEXT,__cstring"') + print(uroboros_output) + expected_output = 'Hello, World!' + + assert expected_output in uroboros_output + + def test_dump_binary(self): + '''Test the --dump_binary flag of SnakeI.''' + args_list = ['-p', 'hello_1', '--dump_binary', 'test_bin'] args = argumentWrapper(args_list) snake_hatchery = SnakeHatchery(args, snake_class) snake_hatchery.hatch() @@ -779,9 +790,11 @@ class TestSnakeI(): macho_processor.process(args) uroboros_output = executeCodeBlock(code_block) - expected_output = 'Hello, World!' + expected_output = '' assert expected_output in uroboros_output + assert os.path.exists('test_bin') + assert run_and_get_stdout('file test_bin') == 'test_bin: Mach-O 64-bit executable arm64' class TestSnakeII(): '''Testing II. CODE SIGNING'''