65 Commits
v1.1 ... main

Author SHA1 Message Date
Karol Mazurek
e3442de04c Update README.md 2026-01-09 22:52:18 +01:00
Karol Mazurek
54bcddbce5 Add repository popularity section with star history chart 2025-12-25 08:31:37 +01:00
Karol Mazurek
190d6542d3 Add diff_apss.sh script for app patching analysis 2025-12-22 23:54:41 +01:00
Karol Mazurek
d4cbd1a64c Refactor comment 2025-12-22 20:01:10 +01:00
Karol Mazurek
ff55402c23 Add check_bundle_exe script for extracting executable names from macOS app bundles 2025-12-22 19:59:15 +01:00
Karol Mazurek
2b45f44b06 Add check_cs.sh wrapper 2025-12-18 13:29:58 +01:00
Karol Mazurek
0c0b9ad5b9 Merge branch 'main' of https://github.com/Karmaz95/Snake_Apple 2025-12-15 16:03:42 +01:00
Karol Mazurek
43e98834cc Fix error message formatting in check_paths method 2025-12-15 16:01:17 +01:00
Karol Mazurek
46e647ad47 Make find_symbol.py executable 2025-12-15 15:50:19 +01:00
Karol Mazurek
4d9bdde03a Add new hooks for process and vnode checks 2025-12-12 17:26:05 +01:00
Karol Mazurek
b3fbaacee6 Update TOOLS.md to include find_symbol documentation 2025-12-09 11:33:14 +01:00
Karol Mazurek
05e95dcf39 Add find_symbol.py script for locating symbols in PATH recursively 2025-12-09 11:33:03 +01:00
Karol Mazurek
d08cd41f2d Add IDA Pro MIG Subsystem Scanner for identifying and labeling MIG subsystems in Mach binaries 2025-12-07 20:28:07 +01:00
Karol Mazurek
ec2cfe8425 Change file mode of r2_dd.py to make it executable 2025-12-07 00:26:26 +01:00
Karol Mazurek
8d6a8b4c6b Add r2_dd documentation and usage examples to TOOLS.md 2025-12-06 22:27:38 +01:00
Karol Mazurek
5a906283f3 Add r2_dd script for binary extraction based on Virtual Addresses using radare2 2025-12-06 22:17:34 +01:00
Karol Mazurek
cac76ae2aa Add Lock to synchronize print statements and prevent stdout corruption when multiple threads write simultaneously. Example corruption before:
```
DYLIB:/Applications/NordVPN.app/Contents/Frameworks/norddropFFI.framework/Versions/A/norddropFFI

/* No comment provided by engineer. */
"Update Error!" = "Virhe p�ivEXECUTE:/Applications/KnockKnock.app/Contents/MacOS/KnockKnock
```
2025-12-06 21:38:19 +01:00
Karol Mazurek
1fda24819c Add final_secure_test_xpc.zip to the App Bundle Extension 2025-12-02 19:17:52 +01:00
Karol Mazurek
1ae188683a Add secure_test_xpc.zip to the App Bundle Extension 2025-12-02 18:53:33 +01:00
Karol Mazurek
de427b1cba Add secure_test_xpc.zip to the App Bundle Extension 2025-12-02 18:16:13 +01:00
Karol Mazurek
deb19c3858 Add link to "Dyld Shared Cache Patch Diffing based on CVE-2025-43400" in the table of contents 2025-11-01 14:37:53 +01:00
Karol Mazurek
32ea1c4eda Add link to "Threats of Unvalidated XPC Clients on macOS" in the table of contents 2025-10-27 09:56:25 +01:00
Karol Mazurek
e6f94ef223 Fix formatting issue in README.md 2025-10-18 15:24:13 +02:00
Karol Mazurek
3473985e92 Description update. 2025-10-18 15:23:14 +02:00
Karol Mazurek
2976102984 Add link to "Static Analysis on Decompiled Code" in the table of contents 2025-10-01 20:21:57 +02:00
Karol Mazurek
4345a0412e Fix: Add IOKit 16-scalar limit validation to prevent OOB access
Validates scalar input/output counts in -y parameter don't exceed
IOKit's maximum of 16, preventing garbage values from array bounds.
2025-09-27 17:10:10 +02:00
Karol Mazurek
29a3124b7e Add link to "Breaking Hardened Runtime: The 0-Day Microsoft Delivered to macOS" in the table of contents. 2025-09-15 17:47:00 +02:00
Karol Mazurek
38cc7865bc Update link for "AI-Enhanced Vulnerability Research" in the table of contents 2025-09-03 11:02:16 +02:00
Karol Mazurek
96a0c023f0 Add link to "To allow or not to get-task-allow, that is the question" in the table of contents and update Tools.md reference 2025-09-02 11:05:50 +02:00
Karol Mazurek
713178663d Add link to "Reverse Engineering Apple’s TCC Daemon: When Decompiled Code Lies" in the table of contents 2025-08-25 20:57:57 +02:00
Karol Mazurek
26efd8b1b1 Add link to "Mapping IOKit Methods Exposed to User Space on macOS" in the table of contents 2025-08-19 00:11:57 +02:00
Karol Mazurek
d5482eb959 Add link to "TCC Bypass in Visual Studio Code via misconfigured Node fuses" in the table of contents 2025-08-18 10:55:29 +02:00
Karol Mazurek
6553126bfc Adding article link "A mouse move that crashed the system – Stack Buffer Overflow in Display Driver on macOS" to README.md 2025-08-11 10:25:34 +02:00
Karol Mazurek
58f97f589c Add new article placeholders for "Apple Intelligence" and "AI-Enhanced Vulnerability Research" in the table of contents 2025-08-02 14:07:29 +02:00
Karol Mazurek
e1cdd27c28 Add link to "Applications Patch Diffing on macOS" in the table of contents 2025-08-01 09:31:35 +02:00
Karol Mazurek
30d7d0e9b4 Update README to enhance description of exclusive content for Elite Patrons and mark articles with asterisks 2025-07-25 21:02:17 +02:00
Karol Mazurek
4b827afe20 Create TCC CheatSheet.md 2025-07-25 08:07:46 +02:00
Karol Mazurek
2ffc0f982e Add article link for "Scaling Vulnerability Discovery on macOS" to README 2025-07-02 18:04:00 +02:00
Karol Mazurek
2cdd37a9ff Add link to "LLDB for Vulnerability Research" article in README 2025-06-16 15:54:18 +02:00
Karol Mazurek
9872ec6fc4 Fix typo in article link for System Integrity Protection 2025-06-12 01:42:09 +02:00
Karol Mazurek
06d77e7c09 Enhance ioconnectcallmethod_hook to generate unique filenames for dumped inputStruct and print IOConnectCallMethod return code 2025-06-11 00:04:42 +02:00
Karol Mazurek
0fd3c811db Add inputStruct dumping functionality to iokit_tracer with output directory support 2025-06-10 19:08:16 +02:00
Karol Mazurek
1962ab10ef Enhance trace_iokit command to utilize the current LLDB target/process if no PID or executable path is specified 2025-06-10 18:53:50 +02:00
Karol Mazurek
3f1f2e6228 Add error handling for subprocess output in dtrace scripts 2025-06-10 18:36:34 +02:00
Karol Mazurek
1a00625b0f Add IOVerify tool for IOKit driver communication verification 2025-06-09 15:32:42 +02:00
Karol Mazurek
8eb7589493 Add iokit_dump.py script for dumping IOKit IOConnectCallMethod data in LLDB 2025-06-09 02:34:05 +02:00
Karol Mazurek
2e208d662c Add iokit_tracer.py script for complete IOKit data inspection and tracing 2025-06-09 02:26:24 +02:00
Karol Mazurek
014ce2b5d5 Add dtrace_externalMethod.py script to trace IOConnectCallMethod kernel functions 2025-06-09 01:47:35 +02:00
Karol Mazurek
18dfa39f42 Add dtrace_NewUserClient.py script to trace kernel newUserClient calls on macOS 2025-06-09 01:39:38 +02:00
Karol Mazurek
b0439e7220 Add trace_ioserviceopen.py script to trace IOServiceOpen calls in LLDB 2025-06-09 01:38:54 +02:00
Karol Mazurek
95752eefc7 Add print_methods function to display external methods details in IDA 2025-06-08 00:50:59 +02:00
Karol Mazurek
0f8df62d82 Enhance IDA script for IOExternalMethodDispatch structures:
- Added support for struct_type argument in create_external_method_dispatch_struct and format_external_method_array functions.
- Updated documentation for usage and structure formats.
- Improved handling of specific fields for IOExternalMethodDispatch2022.
2025-06-08 00:50:34 +02:00
Karol Mazurek
bcc9f34241 Information Update 2025-05-31 23:26:27 +02:00
Karol Mazurek
ac5c9c9799 Add link to "Threat of TCC Bypasses on macOS" article in README.md 2025-05-26 12:16:03 +02:00
Karol Mazurek
85fc5ffea3 Fixing links in README.md 2025-05-23 10:27:12 +02:00
asdh1qwe
1bca0fd124 Adding "Case Study: IOMobileFramebuffer NULL Pointer Dereference" article link to README.md 2025-04-22 11:05:15 +02:00
Karmaz95
78e70edcbb Adding "History of NULL Pointer Dereferences on macOS" article link to README.md 2025-03-28 19:52:47 +01:00
Karmaz95
7c5d445980 Minor changes to README.md in "WHY UROBOROS?" section. 2025-02-15 15:25:39 +01:00
Karmaz95
fccc122ba5 Detaching Tools section from README.md to TOOLS.md file. 2025-02-15 15:20:59 +01:00
Karmaz95
0ef9bd433e Adding a note for the Articles section of the README.md about free access to Medium links. 2025-02-15 11:00:58 +01:00
Karmaz95
58b2a53831 Adding "Case Study: Analyzing macOS IONVMeFamily NS_01 Driver Denial of Service Issue" article link. 2025-02-12 13:33:13 +01:00
Karmaz95
2d0f12c15a Adding SLAP & FLOP: Apple Silicon’s Data Speculation Vulnerabilities article. 2025-01-30 17:25:38 +01:00
Karmaz95
2fb33d88be Patrons update 2025-01-30 12:20:13 +01:00
Karol Mazurek
dab7384bc8 Merge pull request #2 from devnoname120/fix-electron-patcher
Fix electron patching
2025-01-08 11:31:46 +01:00
Paul
deb421a620 Fix electron patching
- Use the `@electron/asar` NPM package instead of the old outdated `asar` package.
- Fix escaping error in the `npx` call when there are spaces in the paths.
2025-01-05 18:57:23 +01:00
24 changed files with 2772 additions and 693 deletions

Binary file not shown.

View File

@@ -0,0 +1,99 @@
#!/bin/bash
# CHECK BUNDLE EXECUTABLE
# A tool for extracting executable names from macOS app bundles Info.plist
get_executable_name() {
local plist="$1"
local value
value=$(/usr/libexec/PlistBuddy -c "Print :CFBundleExecutable" "$plist" 2>/dev/null)
if [ $? -eq 0 ] && [ -n "$value" ]; then
echo "$value"
return 0
fi
local app_dir
app_dir=$(dirname "$(dirname "$plist")")
local app_name
app_name=$(basename "$app_dir" .app)
if [ -n "$app_name" ] && [ "$app_name" != "Contents" ]; then
echo "$app_name"
return 0
fi
echo "UNKNOWN"
return 1
}
process_app() {
local app_path="$1"
local exec_name="UNKNOWN"
local plist
local found=false
app_path="${app_path%/}"
if [[ "$app_path" =~ /Wrapper/.*\.app$ ]]; then
return
fi
if [ -d "$app_path" ]; then
plist="$app_path/Contents/Info.plist"
if [ -f "$plist" ]; then
exec_name=$(get_executable_name "$plist")
if [ "$exec_name" != "UNKNOWN" ]; then
found=true
fi
fi
if [ "$found" = false ]; then
local wrapped_bundle="$app_path/WrappedBundle"
if [ -L "$wrapped_bundle" ] && [ -d "$wrapped_bundle" ]; then
plist="$wrapped_bundle/Contents/Info.plist"
if [ -f "$plist" ]; then
exec_name=$(get_executable_name "$plist")
if [ "$exec_name" != "UNKNOWN" ]; then
found=true
fi
fi
fi
fi
if [ "$found" = false ]; then
while IFS= read -r nested_plist; do
exec_name=$(get_executable_name "$nested_plist")
if [ "$exec_name" != "UNKNOWN" ]; then
found=true
break
fi
done < <(find "$app_path" -name "Info.plist" -path "*/Wrapper/*.app/Contents/Info.plist" 2>/dev/null)
fi
if [ "$found" = false ]; then
while IFS= read -r nested_plist; do
exec_name=$(get_executable_name "$nested_plist")
if [ "$exec_name" != "UNKNOWN" ]; then
found=true
break
fi
done < <(find "$app_path" -name "Info.plist" -maxdepth 3 2>/dev/null | grep -E "($app_path/Info.plist|$app_path/Wrapper/.*Info.plist)")
fi
echo "$exec_name"
else
echo "UNKNOWN" >&2
fi
}
if [ "$#" -ge 1 ]; then
for app in "$@"; do
process_app "$app"
done
else
while IFS= read -r line; do
process_app "$line"
done
fi

View File

@@ -0,0 +1,149 @@
#!/usr/bin/env bash
# Check if the correct number of arguments provided
if [ $# -ne 2 ]; then
echo "Usage: $0 OLD_APP NEW_APP"
echo "Example: $0 OLD/Fantastical.app NEW/Fantastical.app"
exit 1
fi
OLD_APP="$1"
NEW_APP="$2"
# Check if directories exist
if [ ! -d "$OLD_APP" ]; then
echo "Error: Old app directory '$OLD_APP' does not exist"
exit 1
fi
if [ ! -d "$NEW_APP" ]; then
echo "Error: New app directory '$NEW_APP' does not exist"
exit 1
fi
echo "=========================================="
echo "APP PATCHING ANALYSIS"
echo "=========================================="
echo "Old App: $OLD_APP"
echo "New App: $NEW_APP"
echo "=========================================="
# Run diff with -r for recursive and -q for brief output
DIFF_OUTPUT=$(diff -rq "$OLD_APP" "$NEW_APP" 2>/dev/null)
# Process new files
echo -e "\n🆕 NEW FILES:"
echo "----------------------------------------"
NEW_LIST=$(echo "$DIFF_OUTPUT" | grep "Only in $NEW_APP" | sed "s|Only in $NEW_APP/||" | sed 's|: | → |')
if [ -n "$NEW_LIST" ]; then
echo "$NEW_LIST" | while read -r line; do
echo " + $line"
done
else
echo " (No new files)"
fi
# Process deleted files
echo -e "\n❌ DELETED FILES:"
echo "----------------------------------------"
DELETED_LIST=$(echo "$DIFF_OUTPUT" | grep "Only in $OLD_APP" | sed "s|Only in $OLD_APP/||" | sed 's|: | → |')
if [ -n "$DELETED_LIST" ]; then
echo "$DELETED_LIST" | while read -r line; do
echo " - $line"
done
else
echo " (No deleted files)"
fi
# Process modified files
echo -e "\n📝 MODIFIED FILES:"
echo "----------------------------------------"
MODIFIED_LIST=$(echo "$DIFF_OUTPUT" | grep "^Files.*differ$" | sed "s|Files $OLD_APP/||" | sed "s| and $NEW_APP/.*differ$||")
if [ -n "$MODIFIED_LIST" ]; then
echo "$MODIFIED_LIST" | while read -r line; do
echo " ~ $line"
done
else
echo " (No modified files)"
fi
# Find and report symlink changes
echo -e "\n🔗 SYMLINK CHANGES:"
echo "----------------------------------------"
SYMLINK_CHANGES=""
# Find all symlinks in OLD_APP
while IFS= read -r old_symlink; do
rel_path="${old_symlink#$OLD_APP/}"
new_symlink="$NEW_APP/$rel_path"
if [ ! -L "$new_symlink" ]; then
# Symlink was removed or converted to regular file
SYMLINK_CHANGES="${SYMLINK_CHANGES} - Removed: $rel_path\n"
else
# Check if symlink target changed
old_target=$(readlink "$old_symlink")
new_target=$(readlink "$new_symlink")
if [ "$old_target" != "$new_target" ]; then
SYMLINK_CHANGES="${SYMLINK_CHANGES} ~ Modified: $rel_path\n"
SYMLINK_CHANGES="${SYMLINK_CHANGES} Old target: $old_target\n"
SYMLINK_CHANGES="${SYMLINK_CHANGES} New target: $new_target\n"
fi
fi
done < <(find "$OLD_APP" -type l)
# Find new symlinks in NEW_APP
while IFS= read -r new_symlink; do
rel_path="${new_symlink#$NEW_APP/}"
old_symlink="$OLD_APP/$rel_path"
if [ ! -L "$old_symlink" ]; then
# New symlink added
new_target=$(readlink "$new_symlink")
SYMLINK_CHANGES="${SYMLINK_CHANGES} + Added: $rel_path$new_target\n"
fi
done < <(find "$NEW_APP" -type l)
if [ -n "$SYMLINK_CHANGES" ]; then
echo -e "$SYMLINK_CHANGES"
else
echo " (No symlink changes)"
fi
# Count totals
NEW_COUNT=$(echo "$DIFF_OUTPUT" | grep -c "Only in $NEW_APP" 2>/dev/null || echo "0")
DELETED_COUNT=$(echo "$DIFF_OUTPUT" | grep -c "Only in $OLD_APP" 2>/dev/null || echo "0")
MODIFIED_COUNT=$(echo "$DIFF_OUTPUT" | grep -c "^Files.*differ$" 2>/dev/null || echo "0")
# Ensure counts are single integers
NEW_COUNT=$(echo "$NEW_COUNT" | head -n1 | tr -d '\n')
DELETED_COUNT=$(echo "$DELETED_COUNT" | head -n1 | tr -d '\n')
MODIFIED_COUNT=$(echo "$MODIFIED_COUNT" | head -n1 | tr -d '\n')
# Summary
echo -e "\n📊 SUMMARY:"
echo "=========================================="
echo "New files: $NEW_COUNT"
echo "Deleted files: $DELETED_COUNT"
echo "Modified files: $MODIFIED_COUNT"
echo "Total changes: $((NEW_COUNT + DELETED_COUNT + MODIFIED_COUNT))"
echo "=========================================="
# Binary file detection
BINARY_CHANGES=""
while IFS= read -r file; do
if [ -n "$file" ]; then
full_path="$NEW_APP/$file"
if [ -f "$full_path" ] && [ ! -L "$full_path" ]; then
file_type=$(file -b "$full_path" 2>/dev/null)
if echo "$file_type" | grep -qE "(executable|shared library)"; then
BINARY_CHANGES="$BINARY_CHANGES$file\n"
fi
fi
fi
done <<< "$MODIFIED_LIST"
if [ -n "$BINARY_CHANGES" ]; then
echo "⚠️ Binary files modified:"
echo -e "$BINARY_CHANGES" | sed 's/^/ /'
fi

View File

@@ -34,7 +34,7 @@ class ASARPatcher:
def extractASAR(self, app_path, output_path): def extractASAR(self, app_path, output_path):
'''Extracts {input_path} asar file to {output_path} directory.''' '''Extracts {input_path} asar file to {output_path} directory.'''
input_path = os.path.join(app_path, "Contents/Resources/app.asar") input_path = os.path.join(app_path, "Contents/Resources/app.asar")
status_code = os.system(f"npx asar extract {input_path} {output_path}") status_code = os.system(f"npx @electron/asar extract '{input_path}' '{output_path}'")
if status_code == 0: if status_code == 0:
print(f"Extracted {input_path} to {output_path} directory.") print(f"Extracted {input_path} to {output_path} directory.")
@@ -44,8 +44,8 @@ class ASARPatcher:
def dumpEntitlements(self, app_path): def dumpEntitlements(self, app_path):
output_path='/tmp/extracted_entitlements.xml' output_path='/tmp/extracted_entitlements.xml'
status_code = os.system(f"codesign -d --entitlements :- {app_path} > {output_path}") status_code = os.system(f"codesign -d --entitlements :- '{app_path}' > '{output_path}'")
if status_code == 0: if status_code == 0:
print(f"Dumped entitlements from {app_path} to {output_path}") print(f"Dumped entitlements from {app_path} to {output_path}")
@@ -53,7 +53,7 @@ class ASARPatcher:
print(f"Failed to dump entitlements from {app_path} to {output_path}. Error code: {status_code}") print(f"Failed to dump entitlements from {app_path} to {output_path}. Error code: {status_code}")
def checkIfElectronAsarIntegrityIsUsed(self, app_path): def checkIfElectronAsarIntegrityIsUsed(self, app_path):
status_code = os.system(f"plutil -p {app_path}/Contents/Info.plist | grep -q ElectronAsarIntegrity") status_code = os.system(f"plutil -p '{app_path}/Contents/Info.plist' | grep -q ElectronAsarIntegrity")
if status_code == 0: if status_code == 0:
return True return True
else: else:
@@ -62,12 +62,12 @@ class ASARPatcher:
def packASAR(self, input_path, app_path): def packASAR(self, input_path, app_path):
'''Packs {input_path} directory to {output_path} asar file. '''Packs {input_path} directory to {output_path} asar file.
Check if ElectronAsarIntegrity is used in Info.plist, and if so, calculate hash and replace it. Check if ElectronAsarIntegrity is used in Info.plist, and if so, calculate hash and replace it.
Codesign the Codesign the
''' '''
output_path = os.path.join(app_path, "Contents/Resources/app.asar") output_path = os.path.join(app_path, "Contents/Resources/app.asar")
info_plist_path = os.path.join(app_path, "Contents/Info.plist") info_plist_path = os.path.join(app_path, "Contents/Info.plist")
status_code = os.system(f"npx asar pack {input_path} {output_path}") status_code = os.system(f"npx @electron/asar pack '{input_path}' '{output_path}'")
if status_code == 0: if status_code == 0:
print(f"Packed {input_path} into {output_path}") print(f"Packed {input_path} into {output_path}")
@@ -77,14 +77,14 @@ class ASARPatcher:
new_hash = asar_calculator.calcASARHeaderHash() new_hash = asar_calculator.calcASARHeaderHash()
print(f"New hash: {new_hash}") print(f"New hash: {new_hash}")
print("Replacing ElectronAsarIntegrity in Info.plist") print("Replacing ElectronAsarIntegrity in Info.plist")
os.system(f"/usr/libexec/PlistBuddy -c 'Set :ElectronAsarIntegrity:Resources/app.asar:hash {new_hash}' {info_plist_path}") os.system(f"/usr/libexec/PlistBuddy -c 'Set :ElectronAsarIntegrity:Resources/app.asar:hash {new_hash}' '{info_plist_path}'")
print("Resigning app") print("Resigning app")
self.dumpEntitlements(app_path) self.dumpEntitlements(app_path)
os.system(f"codesign --force --entitlements /tmp/extracted_entitlements.xml --sign - {app_path}") os.system(f"codesign --force --entitlements /tmp/extracted_entitlements.xml --sign - '{app_path}'")
os.remove('/tmp/extracted_entitlements.xml') os.remove('/tmp/extracted_entitlements.xml')
print("Done!") print("Done!")
def main(): def main():
@@ -113,4 +113,4 @@ def main():
print("Invalid command. Use 'extract' or 'pack'.") print("Invalid command. Use 'extract' or 'pack'.")
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@@ -4,6 +4,7 @@ import sys
import argparse import argparse
import struct import struct
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from threading import Lock
import stat import stat
class MachOFileFinder: class MachOFileFinder:
@@ -37,6 +38,7 @@ class MachOFileFinder:
self.directory_path = directory_path self.directory_path = directory_path
self.recursive = recursive self.recursive = recursive
self.only_arm64 = only_arm64 self.only_arm64 = only_arm64
self.print_lock = Lock()
def isRegularFile(self, file_path): def isRegularFile(self, file_path):
"""Check if the specified file is a regular file.""" """Check if the specified file is a regular file."""
@@ -178,7 +180,8 @@ class MachOFileFinder:
# Check if the file is a Mach-O binary or FAT binary # Check if the file is a Mach-O binary or FAT binary
file_type = self.getMachoInfo(file_path) file_type = self.getMachoInfo(file_path)
if file_type: if file_type:
print(f"{file_type}:{file_path}") with self.print_lock:
print(f"{file_type}:{file_path}")
def processFiles(self): def processFiles(self):
"""Walk through the directory and process files using threading for faster execution.""" """Walk through the directory and process files using threading for faster execution."""
@@ -202,4 +205,4 @@ if __name__ == "__main__":
sys.exit(1) sys.exit(1)
finder = MachOFileFinder(directory_path, recursive=args.recursive, only_arm64=args.only_arm64) finder = MachOFileFinder(directory_path, recursive=args.recursive, only_arm64=args.only_arm64)
finder.processFiles() finder.processFiles()

61
I. Mach-O/python/find_symbol.py Executable file
View File

@@ -0,0 +1,61 @@
#!/usr/bin/env python3
'''
Example usage - find _sandbox_check function across extracted libraries from Dyld Shared Cache:
$ python3 find_symbol.py . _sandbox_check
./usr/lib/libspindump.dylib
U _sandbox_check
----
./usr/lib/dyld
0000000180141a6c T _sandbox_check
0000000180141b4c t _sandbox_check_common
----
./usr/lib/libnetworkextension.dylib
U _sandbox_check
'''
import os
import subprocess
import sys
def find_symbol(target_dir, symbol):
if not os.path.exists(target_dir):
print(f"Error: Directory '{target_dir}' does not exist.")
sys.exit(1)
# Walk recursively through all files
for root, _, files in os.walk(target_dir):
for file in files:
file_path = os.path.join(root, file)
# Construct the command using nm instead of disarm
# nm FILE_PATH | grep SYMBOL
cmd = f"nm \"{file_path}\" 2>/dev/null | grep \"{symbol}\""
try:
result = subprocess.run(
cmd,
shell=True,
capture_output=True,
text=True
)
if result.stdout:
print(file_path)
print(result.stdout.rstrip())
print("----")
except Exception:
continue
def main():
if len(sys.argv) != 3:
print(f"Usage: {sys.argv[0]} <directory_path> <symbol>")
sys.exit(1)
target_dir = sys.argv[1]
symbol = sys.argv[2]
find_symbol(target_dir, symbol)
if __name__ == "__main__":
main()

84
I. Mach-O/python/r2_dd.py Executable file
View File

@@ -0,0 +1,84 @@
#!/usr/bin/env python3
import sys
import subprocess
import os
def print_usage():
print("Usage: r2_dd BINARY_PATH START_ADDR END_ADDR OUT_FILE")
print("Example: r2_dd ./kernelcache 0xFFFFFF80002A0000 0xFFFFFF80002A0500 ./dump.bin")
print("\nNote: Addresses can be Hex (0x...) or Decimal.")
def parse_addr(addr_str):
"""Parses hex or decimal string to integer."""
try:
if addr_str.lower().startswith("0x"):
return int(addr_str, 16)
else:
return int(addr_str)
except ValueError:
print(f"Error: Invalid address format '{addr_str}'")
sys.exit(1)
def main():
if len(sys.argv) != 5:
print_usage()
sys.exit(1)
bin_path = sys.argv[1]
start_str = sys.argv[2]
end_str = sys.argv[3]
out_file = sys.argv[4]
if not os.path.exists(bin_path):
print(f"Error: Binary file not found at '{bin_path}'")
sys.exit(1)
start_ea = parse_addr(start_str)
end_ea = parse_addr(end_str)
if end_ea <= start_ea:
print("Error: END_ADDR must be greater than START_ADDR")
sys.exit(1)
size = end_ea - start_ea
print(f"--- Extraction Details ---")
print(f"Binary: {bin_path}")
print(f"Start : {hex(start_ea)}")
print(f"End : {hex(end_ea)}")
print(f"Size : {size} bytes")
print(f"--------------------------")
# We use radare2 (r2) because it automatically maps Virtual Addresses
# to file offsets for Mach-O/ELF files.
# -q : quiet mode
# -N : no user settings (clean environment)
# -c : execute command
# s : seek to address
# pr : print raw bytes
r2_cmd = ["r2", "-q", "-N", "-c", f"s {start_str}; pr {size}", bin_path]
try:
print("Running r2...")
with open(out_file, "wb") as f:
# We pipe stderr to DEVNULL to avoid r2 warnings cluttering output
result = subprocess.run(r2_cmd, stdout=f, stderr=subprocess.DEVNULL)
if result.returncode == 0:
print(f"Success! Saved to: {out_file}")
# Verify file size
if os.path.exists(out_file):
dump_size = os.path.getsize(out_file)
if dump_size == size:
print("Verification: File size matches requested size.")
else:
print(f"Warning: Dumped size ({dump_size}) differs from expected ({size}).")
else:
print("Error: r2 command failed. Do you have radare2 installed?")
except FileNotFoundError:
print("Error: 'r2' command not found. Please install radare2 (brew install radare2).")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,5 @@
#!/bin/bash
# Usage: check_cs PATH
codesign -dvvvv --entitlements - "$1" 2>&1

View File

@@ -0,0 +1,150 @@
| TCC Service | Combined Description |
| :-- | :-- |
| kTCCService | Serves as a general identifier for TCC services. |
| kTCCServiceAccessibility | Enables apps to control the computer, often for assistive tools like screen readers or automation scripts. Apps may prompt: "Allows client to control computer." |
| kTCCServiceAddressBook | Permits access to contacts; prompts might say: "Client would like to access your contacts." |
| kTCCServiceAll | Grants broad access to all TCC-protected resources. |
| kTCCServiceAppleEvents | Allows sending Apple Events for app control; e.g., "Client wants access to control indirect_object_identifier, providing access to its documents and actions." |
| kTCCServiceAudioCapture | Enables audio input capture, useful for recording apps. |
| kTCCServiceBluetoothAlways | Provides ongoing Bluetooth access; prompts: "Client would like to use Bluetooth." |
| kTCCServiceBluetoothPeripheral | Facilitates connections to Bluetooth devices. |
| kTCCServiceBluetoothWhileInUse | Limits Bluetooth access to active app use. |
| kTCCServiceCalendar | Allows calendar access; e.g., "Client would like to access your calendar." |
| kTCCServiceCalls | Handles call-related functionalities. |
| kTCCServiceCamera | Grants camera access; common prompt: "Client would like to access the camera." |
| kTCCServiceContactlessAccess | Supports features like NFC or contactless interactions. |
| kTCCServiceContactsFull | Provides complete contacts access; e.g., "Client would like to access all of your contacts information." |
| kTCCServiceContactsLimited | Offers restricted contacts access; e.g., "Client would like to access your contacts basic information." |
| kTCCServiceCrashDetection | Enables crash detection capabilities. |
| kTCCServiceDeveloperTool | Allows running non-secure software locally; e.g., "Allows client to run software that does not meet the systems security policy." |
| kTCCServiceEndpointSecurityClient | Provides endpoint security features. |
| kTCCServiceExposureNotification | Manages exposure alerts, such as for health notifications. |
| kTCCServiceExposureNotificationRegion | Handles region-based exposure notifications. |
| kTCCServiceFaceID | Permits Face ID usage. |
| kTCCServiceFacebook | Integrates with Facebook features. |
| kTCCServiceFallDetection | Supports fall detection sensors. |
| kTCCServiceFileProviderDomain | Allows access to managed file domains; e.g., "Client wants to access files managed by indirect_object_identifier." |
| kTCCServiceFileProviderPresence | Tracks file usage in providers; e.g., "Do you want to allow client to see when you are using files managed by it?" |
| kTCCServiceFinancialData | Enables access to financial information. |
| kTCCServiceFocusStatus | Shares Focus mode status; e.g., "Allow client to share that you have notifications silenced when using Focus?" |
| kTCCServiceFSKitBlockDevice | Manages block devices in FSKit. |
| kTCCServiceGameCenterFriends | Connects to Game Center friends; e.g., "Allow client to connect you with your Game Center friends?" |
| kTCCServiceKeyboardNetwork | Permits network access for keyboards. |
| kTCCServiceLinkedIn | Integrates with LinkedIn. |
| kTCCServiceListenEvent | Monitors keyboard or system events; e.g., "Allows client to monitor your keyboard." |
| kTCCServiceLiverpool | Internal identifier for Liverpool-related features. |
| kTCCServiceLocation | Accesses location data; e.g., "Client would like to use your current location." |
| kTCCServiceMediaLibrary | Grants media library access; e.g., "Client would like to access Apple Music, your music and video activity, and your media library." |
| kTCCServiceMicrophone | Allows microphone use; e.g., "Client would like to access the microphone." |
| kTCCServiceMotion | Accesses motion and fitness data; e.g., "Client would like to access your Motion \& Fitness Activity." |
| kTCCServiceMSO | Supports mobile service operator features. |
| kTCCServiceNearbyInteraction | Enables nearby device interactions. |
| kTCCServicePasteboard | Accesses clipboard data. |
| kTCCServicePhotos | Permits photo library access; e.g., "Client would like to access your Photos." |
| kTCCServicePhotosAdd | Allows adding to photos; e.g., "Client would like to add to your Photos." |
| kTCCServicePostEvent | Enables sending keystrokes or events; e.g., "Allows client to send keystrokes." |
| kTCCServicePrototype3Rights | Internal prototype rights (version 3); e.g., "Client would like authorization to Test Service Proto3Right." |
| kTCCServicePrototype4Rights | Internal prototype rights (version 4); e.g., "Client would like authorization to Test Service Proto4Right." |
| kTCCServiceReminders | Accesses reminders; e.g., "Client would like to access your reminders." |
| kTCCServiceRemoteDesktop | Supports remote desktop access. |
| kTCCServiceScreenCapture | Enables screen recording; e.g., "Client would like to capture the contents of the system display." |
| kTCCServiceSecureElementAccess | Handles secure elements like NFC. |
| kTCCServiceSensorKit* (various) | Provides access to sensor data (e.g., ambient light, pedometer, heart rate); specific variants target metrics like elevation, motion, or watch-based stats. |
| kTCCServiceShareKit | Enables content sharing via ShareKit. |
| kTCCServiceSinaWeibo | Integrates with Sina Weibo. |
| kTCCServiceSiri | Allows Siri interactions; e.g., "Would you like to use client with Siri?" |
| kTCCServiceSpeechRecognition | Enables speech recognition; e.g., "Client would like to access Speech Recognition." |
| kTCCServiceSystemPolicyAllFiles | Grants full disk access; e.g., "Client would like Full Disk Access." |
| kTCCServiceSystemPolicyAppBundles | Allows modifying app bundles; e.g., "Client would like to modify apps on your Mac." |
| kTCCServiceSystemPolicyAppData | Accesses app-specific data. |
| kTCCServiceSystemPolicyDesktopFolder | Accesses Desktop files; e.g., "Client would like to access files in your Desktop folder." |
| kTCCServiceSystemPolicyDeveloperFiles | Accesses development files; e.g., "Client would like to access a file used in Software Development." |
| kTCCServiceSystemPolicyDocumentsFolder | Accesses Documents; e.g., "Client would like to access files in your Documents folder." |
| kTCCServiceSystemPolicyDownloadsFolder | Accesses Downloads; e.g., "Client would like to access files in your Downloads folder." |
| kTCCServiceSystemPolicyNetworkVolumes | Accesses network volumes; e.g., "Client would like to access files on a network volume." |
| kTCCServiceSystemPolicyRemovableVolumes | Accesses removable volumes; e.g., "Client would like to access files on a removable volume." |
| kTCCServiceSystemPolicySysAdminFiles | Allows admin tasks; e.g., "Client would like to administer your computer." |
| kTCCServiceTencentWeibo | Integrates with Tencent Weibo. |
| kTCCServiceTwitter | Integrates with Twitter (now X). |
| kTCCServiceUbiquity | Enables iCloud syncing. |
| kTCCServiceUserAvailability | Accesses availability info; e.g., "Client would like to access your Availability." |
| kTCCServiceUserTracking | Handles user tracking features. |
| kTCCServiceVirtualMachineNetworking | Supports VM networking. |
| kTCCServiceVoiceBanking | Enables voice-based banking. |
| kTCCServiceWebBrowserPublicKeyCredential | Manages passkeys; e.g., "Would you like to allow client to access and use your saved passkeys?" |
| kTCCServiceWebKitIntelligentTrackingPrevention | Provides tracking prevention in WebKit. |
| kTCCServiceWillow | Internal identifier for Home-related data; e.g., "Client would like to access your Home data." |
### Practical Applications of TCC Services
TCC ensures apps can't access private data without approval, which is crucial for security research. Here's why certain services are commonly requested:
- Assistive technologies rely on accessibility permissions to enable features like voice commands.
- Video apps need camera and microphone access for calls or recordings.
- Productivity tools use calendar, contacts, or reminders to sync schedules and people.
- Device integrations, like Bluetooth or motion sensors, support wearables and fitness tracking.
- File-related permissions are vital for apps handling documents, downloads, or network storage.
- Advanced features, such as screen capture or Siri, enhance sharing and voice control in collaborative or automated workflows.
These permissions appear in System Settings under Privacy \& Security, updating dynamically as apps request them.
### Retrieving the Latest TCC Service List
To get an up-to-date list directly from your system (tested on macOS Ventura and later), use these methods. Ensure Terminal has Full Disk Access for queries.
1. **Database Query**:
```bash
sqlite3 ~/Library/Application\ Support/com.apple.TCC/TCC.db "SELECT * FROM access"
sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db "SELECT * FROM access"
```
This pulls from the user-level / system-level database.
2. **Extract from Framework**:
```bash
strings /System/Library/PrivateFrameworks/TCC.framework/Support/tccd | grep -iEo "^kTCCService.*" | sort -u
```
This scans for service strings in the TCC framework.
### Modifying TCC Permissions via Command Line
TCC stores data in SQLite databases at `~/Library/Application Support/com.apple.TCC/TCC.db` (user-specific) and `/Library/Application Support/com.apple.TCC/TCC.db` (system-wide). The key table is `access`, with fields like `service` (permission type), `client` (app bundle ID or path), `client_type` (0 for bundle ID, 1 for path), and `auth_value` (2 for allowed, 0 for denied).
#### Viewing Permissions
List apps with Full Disk Access:
```bash
sqlite3 ~/Library/Application\ Support/com.apple.TCC/TCC.db 'SELECT client FROM access WHERE auth_value > 0 AND service = "kTCCServiceSystemPolicyAllFiles"'
```
Check system database similarly.
#### Editing Permissions
- Deny a permission (sets `auth_value` to 0):
```bash
sqlite3 ~/Library/Application\ Support/com.apple.TCC/TCC.db 'UPDATE access SET auth_value = 0 WHERE client = "com.example.app" AND service = "kTCCServiceCamera"'
```
- Delete a specific entry:
```bash
sqlite3 ~/Library/Application\ Support/com.apple.TCC/TCC.db "DELETE FROM access WHERE client = 'com.example.app' AND service = 'kTCCServiceCamera'"
```
- Add an entry (requires code signing requirement blob via `codesign` and `csreq`):
First, extract the blob for the app and target:
```bash
codesign -dr - /Path/To/App.app 2>&1 | awk -F ' => ' '/designated/{print $2}' | csreq -r- -b /tmp/csreq.bin
xxd -p /tmp/csreq.bin | tr -d '\n' # Output for INSERT
```
Then insert (adapt values accordingly).
For simpler resets, use Apple's `tccutil reset` command to revoke permissions for a service or all for an app `tccutil reset All com.apple.Terminal`.

692
README.md
View File

@@ -1,25 +1,37 @@
# Snake & Apple # Snake & Apple
![alt](img/Snake_Apple.jpg) [![alt](img/Snake_Apple.jpg)](https://karol-mazurek.medium.com/snake-apple-ff87a399ecc4?sk=v2%2Fb2295773-88e6-4654-9d3d-61d73b9001e5)
The code repository for the `Snake&Apple` article series, which documents my research about macOS security. This is the code repository for the "[Snake & Apple](https://karol-mazurek.medium.com/list/snakeapple-50baea541374)" article series, which documents my research on macOS security. The primary tool developed during the creation of the series is called `CrimsonUroboros`. You can find its description, along with instructions for other tools in this repository, in [Tools.md](https://github.com/Karmaz95/Snake_Apple/blob/main/TOOLS.md).
Each article directory contains three subdirectories: ## ARTICLES
I have been writing about Apple Security across different platforms for years, compiling them in this repository. Currently, I am writing on [Patreon](https://www.patreon.com/Karol_Mazurek). All articles are free, except those marked with a `*`, which are [exclusive content](https://www.patreon.com/collection/1529482) for Elite Patrons—my "thank-you" to the folks who support me.
---
Each main article directory contains three subdirectories:
* `mac` - source code of macOS for references and copy of presentations. * `mac` - source code of macOS for references and copy of presentations.
* `custom` - code, for example, programs written for articles. * `custom` - code, for example, programs written for articles.
* `python` - contains the latest CrimsonUroboros and other Python scripts created during research. * `python` - contains the latest CrimsonUroboros and other Python scripts created during research.
---
## ARTICLES The short introduction is written in [Snake&Apple Intro](https://karol-mazurek.medium.com/snake-apple-ff87a399ecc4?sk=v2%2Fb2295773-88e6-4654-9d3d-61d73b9001e5)
The short introduction is written in [Snake&Apple Intro](https://karol-mazurek95.medium.com/snake-apple-ff87a399ecc4?sk=v2%2Fb2295773-88e6-4654-9d3d-61d73b9001e5)
The tags for each article are in the [Article_tags.md](Article_tags.md). The tags for each article are in the [Article_tags.md](Article_tags.md).
The table of contents showing links to all articles is below: The table of contents showing links to all articles is below:
* &#9745; [App Bundle Extension](https://karol-mazurek.medium.com/snake-apple-app-bundle-ext-f5c43a3c84c4?sk=v2%2F3ff105ad-f4f0-464d-b4d5-46b86c66fe14) * &#9745; [App Bundle Extension](https://karol-mazurek.medium.com/snake-apple-app-bundle-ext-f5c43a3c84c4?sk=v2%2F3ff105ad-f4f0-464d-b4d5-46b86c66fe14)
* &#9745; [Cracking macOS apps](https://karol-mazurek.medium.com/cracking-macos-apps-39575dd672e0?sk=v2%2F727dce55-53ee-45f6-b051-2979e62f2ba1) * &#9745; [Cracking macOS apps](https://karol-mazurek.medium.com/cracking-macos-apps-39575dd672e0?sk=v2%2F727dce55-53ee-45f6-b051-2979e62f2ba1)
* &#9745; [Cracking Electron Integrity](https://karol-mazurek.medium.com/cracking-electron-integrity-0a10e0d5f239?sk=v2%2F7726b99c-c6c9-4d70-8c37-da9f2f0874e8) * &#9745; [Cracking Electron Integrity](https://karol-mazurek.medium.com/cracking-electron-integrity-0a10e0d5f239?sk=v2%2F7726b99c-c6c9-4d70-8c37-da9f2f0874e8)
* &#9745; [XPC Programming on macOS](https://karol-mazurek.medium.com/xpc-programming-on-macos-7e1918573f6d?sk=v2%2F21c4e9c7-40a5-43dd-804b-0d8f9bc4e94c) * &#9745; [XPC Programming on macOS](https://karol-mazurek.medium.com/xpc-programming-on-macos-7e1918573f6d?sk=v2%2F21c4e9c7-40a5-43dd-804b-0d8f9bc4e94c)
* &#9745; [I. Mach-O](https://karol-mazurek95.medium.com/snake-apple-i-mach-o-a8eda4b87263?sk=v2%2Ffc1cbfa4-e2d4-4387-9a82-b27191978b5b) * &#9745; [AppleScript for Vulnerability Research](https://www.patreon.com/posts/applescript-for-130305213) `*`
* &#9745; [LLDB for Vulnerability Research](https://www.patreon.com/posts/lldb-for-131084875) `*`
* &#9745; [Scaling Vulnerability Discovery on macOS](https://www.patreon.com/posts/scaling-on-macos-131937045) `*`
* &#9745; [Applications Patch Diffing on macOS](https://www.patreon.com/posts/applications-on-131618568) `*`
* &#9745; [Threats of Unvalidated XPC Clients on macOS](https://afine.com/threats-of-unvalidated-xpc-clients-on-macos/)
* &#9745; [I. Mach-O](https://karol-mazurek.medium.com/snake-apple-i-mach-o-a8eda4b87263?sk=v2%2Ffc1cbfa4-e2d4-4387-9a82-b27191978b5b)
* &#9745; [Optimizing Mach-O Detection](https://karol-mazurek.medium.com/optimizing-mach-o-detection-40352101bbef?sk=v2%2F3378d3f5-874b-4b82-94d5-b2ccd8522ea3) * &#9745; [Optimizing Mach-O Detection](https://karol-mazurek.medium.com/optimizing-mach-o-detection-40352101bbef?sk=v2%2F3378d3f5-874b-4b82-94d5-b2ccd8522ea3)
* &#9745; [II. Code Signing](https://karol-mazurek95.medium.com/snake-apple-ii-code-signing-f0a9967b7f02?sk=v2%2Fbbc87007-89ca-4135-91d6-668b5d2fe9ae) * &#9745; [Static Analysis on Decompiled Code](https://www.patreon.com/posts/static-analysis-135790081) `*`
* &#9745; [III. Checksec](https://karol-mazurek95.medium.com/snake-apple-iii-checksec-ed64a4b766c1?sk=v2%2Fb4b8d637-e906-4b6b-8088-ca1f893cd787) * &#9745; [II. Code Signing](https://karol-mazurek.medium.com/snake-apple-ii-code-signing-f0a9967b7f02?sk=v2%2Fbbc87007-89ca-4135-91d6-668b5d2fe9ae)
* &#9745; [To allow or not to get-task-allow, that is the question](https://afine.com/to-allow-or-not-to-get-task-allow-that-is-the-question/)
* &#9745; [III. Checksec](https://karol-mazurek.medium.com/snake-apple-iii-checksec-ed64a4b766c1?sk=v2%2Fb4b8d637-e906-4b6b-8088-ca1f893cd787)
* &#9745; [IV. Dylibs](https://karol-mazurek.medium.com/snake-apple-iv-dylibs-2c955439b94e?sk=v2%2Fdef72b7a-121a-47a1-af89-7bf53aed1ea2) * &#9745; [IV. Dylibs](https://karol-mazurek.medium.com/snake-apple-iv-dylibs-2c955439b94e?sk=v2%2Fdef72b7a-121a-47a1-af89-7bf53aed1ea2)
* &#9745; [Breaking Hardened Runtime: The 0-Day Microsoft Delivered to macOS](https://afine.com/breaking-hardened-runtime-the-0-day-microsoft-delivered-to-macos/)
* &#9745; [Dyld Shared Cache Patch Diffing based on CVE-2025-43400](https://www.patreon.com/posts/dyld-shared-on-140770478) `*`
* &#9745; [V. Dyld](https://karol-mazurek.medium.com/snake-apple-v-dyld-8b36b674cc44?sk=v2%2F4acb16f8-fa88-41f0-8d7c-1362f4060010) * &#9745; [V. Dyld](https://karol-mazurek.medium.com/snake-apple-v-dyld-8b36b674cc44?sk=v2%2F4acb16f8-fa88-41f0-8d7c-1362f4060010)
* &#9745; [DYLD — Do You Like Death? (I)](https://karol-mazurek.medium.com/dyld-do-you-like-death-i-8199faad040e?sk=v2%2F359b081f-d944-409b-9e7c-95f7c171b969) * &#9745; [DYLD — Do You Like Death? (I)](https://karol-mazurek.medium.com/dyld-do-you-like-death-i-8199faad040e?sk=v2%2F359b081f-d944-409b-9e7c-95f7c171b969)
* &#9745; [DYLD — Do You Like Death? (II)](https://karol-mazurek.medium.com/dyld-do-you-like-death-ii-b74360b8af47?sk=v2%2Ff0cff71c-5345-4228-a639-653325fc979d) * &#9745; [DYLD — Do You Like Death? (II)](https://karol-mazurek.medium.com/dyld-do-you-like-death-ii-b74360b8af47?sk=v2%2Ff0cff71c-5345-4228-a639-653325fc979d)
@@ -41,9 +53,12 @@ The table of contents showing links to all articles is below:
* &#9745; [Sandbox Detector](https://karol-mazurek.medium.com/sandbox-detector-4268ab3cd361?sk=v2%2F58fe49fb-1381-4db3-9db9-3f6309e4053a) * &#9745; [Sandbox Detector](https://karol-mazurek.medium.com/sandbox-detector-4268ab3cd361?sk=v2%2F58fe49fb-1381-4db3-9db9-3f6309e4053a)
* &#9745; [Sandbox Validator](https://karol-mazurek.medium.com/sandbox-validator-e760e5d88617?sk=v2%2F145ac2ef-ca06-41a0-b310-c96f4ce0037b) * &#9745; [Sandbox Validator](https://karol-mazurek.medium.com/sandbox-validator-e760e5d88617?sk=v2%2F145ac2ef-ca06-41a0-b310-c96f4ce0037b)
* &#9745; [App Sandbox startup](https://karol-mazurek.medium.com/app-sandbox-startup-71daf8f259d1?sk=v2%2F9f3b09a6-c7c0-445d-8613-8e25bf3f4e4d) * &#9745; [App Sandbox startup](https://karol-mazurek.medium.com/app-sandbox-startup-71daf8f259d1?sk=v2%2F9f3b09a6-c7c0-445d-8613-8e25bf3f4e4d)
* &#9745; [System Intigrity Protection](https://karol-mazurek.medium.com/system-integrity-protection-sip-140562b07fea?sk=v2%2F9c293b8f-c376-4603-b8a1-2872ba3395cf) * &#9745; [System Integrity Protection](https://karol-mazurek.medium.com/system-integrity-protection-sip-140562b07fea?sk=v2%2F9c293b8f-c376-4603-b8a1-2872ba3395cf)
* &#9745; [IX. TCC](https://karol-mazurek.medium.com/snake-apple-ix-tcc-ae822e3e2718?sk=v2%2F426ae6cf-6418-4e3f-a0ca-3aee06d6f676) * &#9745; [IX. TCC](https://karol-mazurek.medium.com/snake-apple-ix-tcc-ae822e3e2718?sk=v2%2F426ae6cf-6418-4e3f-a0ca-3aee06d6f676)
* &#9745; [Apple UUID Finder](https://karol-mazurek.medium.com/apple-uuid-finder-a5173bdd1a8a?sk=v2%2F04bb0d32-6dc9-437d-bf72-8f65e03fed90) * &#9745; [Apple UUID Finder](https://karol-mazurek.medium.com/apple-uuid-finder-a5173bdd1a8a?sk=v2%2F04bb0d32-6dc9-437d-bf72-8f65e03fed90)
* &#9745; [Threat of TCC Bypasses on macOS](https://afine.com/threat-of-tcc-bypasses-on-macos/)
* &#9745; [TCC Bypass in Visual Studio Code via misconfigured Node fuses](https://afine.com/tcc-bypass-in-microsoft-visual-studio-code-via-misconfigured-node-fuses/)
* &#9745; [Reverse Engineering Apples TCC Daemon: When Decompiled Code Lies](https://afine.com/reverse-engineering-apples-tcc-daemon-when-decompiled-code-lies/)
* &#9745; [X. NU](https://karol-mazurek.medium.com/snake-apple-x-nu-0bc5c36170da?sk=v2%2F502ee9db-8d8a-4a1b-8655-546742a7d261) * &#9745; [X. NU](https://karol-mazurek.medium.com/snake-apple-x-nu-0bc5c36170da?sk=v2%2F502ee9db-8d8a-4a1b-8655-546742a7d261)
* &#9745; [Kernel Debugging Setup on MacOS](https://karol-mazurek.medium.com/kernel-debugging-setup-on-macos-07dd8c86cdb6?sk=v2%2F782bf539-a057-4f14-bbe7-f8e1ace26701) * &#9745; [Kernel Debugging Setup on MacOS](https://karol-mazurek.medium.com/kernel-debugging-setup-on-macos-07dd8c86cdb6?sk=v2%2F782bf539-a057-4f14-bbe7-f8e1ace26701)
* &#9745; [Fixing an Infinite Loop](https://karol-mazurek.medium.com/fixing-an-infinite-loop-on-unix-e0a8a5501c54?sk=v2%2F140555f8-9770-4c6b-9734-d9c5b7cc9bc7) * &#9745; [Fixing an Infinite Loop](https://karol-mazurek.medium.com/fixing-an-infinite-loop-on-unix-e0a8a5501c54?sk=v2%2F140555f8-9770-4c6b-9734-d9c5b7cc9bc7)
@@ -51,651 +66,24 @@ The table of contents showing links to all articles is below:
* &#9745; [MACF on macOS](https://karol-mazurek.medium.com/macf-on-macos-004b8a490e2c?sk=v2%2Fd9a61281-e230-4ac6-8608-ad062f4d2a9a) * &#9745; [MACF on macOS](https://karol-mazurek.medium.com/macf-on-macos-004b8a490e2c?sk=v2%2Fd9a61281-e230-4ac6-8608-ad062f4d2a9a)
* &#9745; [Kernel Extensions on macOS](https://karol-mazurek.medium.com/kernel-extensions-on-macos-1b0f38b632ea?sk=v2%2Fb6920735-90f9-459c-9c10-30980247bae7) * &#9745; [Kernel Extensions on macOS](https://karol-mazurek.medium.com/kernel-extensions-on-macos-1b0f38b632ea?sk=v2%2Fb6920735-90f9-459c-9c10-30980247bae7)
* &#9745; [Mach IPC Security on macOS](https://karol-mazurek.medium.com/mach-ipc-security-on-macos-63ee350cb59b?sk=v2%2F3afce264-9b59-447f-84ea-b1988606191a) * &#9745; [Mach IPC Security on macOS](https://karol-mazurek.medium.com/mach-ipc-security-on-macos-63ee350cb59b?sk=v2%2F3afce264-9b59-447f-84ea-b1988606191a)
* &#9745; [Task Injection on macOS](https://afine.com/task-injection-on-macos/)
* &#9745; [Drivers on macOS](https://karol-mazurek.medium.com/drivers-on-macos-26edbde370ab?sk=v2%2F8a5bbc18-aae7-4a68-b0dd-bb5ce70b5752) * &#9745; [Drivers on macOS](https://karol-mazurek.medium.com/drivers-on-macos-26edbde370ab?sk=v2%2F8a5bbc18-aae7-4a68-b0dd-bb5ce70b5752)
* &#9745; [Case Study: Analyzing macOS IONVMeFamily NS_01 Driver Denial of Service Issue](https://afine.com/case-study-analyzing-macos-ionvmefamily-driver-denial-of-service-issue/)
* &#9745; [Case Study: IOMobileFramebuffer NULL Pointer Dereference](https://afine.com/case-study-iomobileframebuffer-null-pointer-dereference/)
* &#9745; [A mouse move that crashed the system Stack Buffer Overflow in Display Driver on macOS](https://afine.com/a-mouse-move-that-crashed-the-system-stack-buffer-overflow-in-display-driver-on-macos/)
* &#9745; [Mapping IOKit Methods Exposed to User Space on macOS](https://phrack.org/issues/72/9_md#article) #PHRACK 💀
* &#9745; [SLAP & FLOP: Apple Silicons Data Speculation Vulnerabilities](https://afine.com/slap-flop-apple-silicons-data-speculation-vulnerabilities/)
* &#9745; [History of NULL Pointer Dereferences on macOS](https://afine.com/history-of-null-pointer-dereferences-on-macos/)
## TOOLS * &#9744; [Apple Intelligence]()
[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) * &#9745; [AI-Enhanced Vulnerability Research](https://www.patreon.com/posts/ai-enhanced-135545364) `*`
***
### [CrimsonUroboros](tests/CrimsonUroboros.py) ## REFERENCES
![alt](img/CrimsonUroboros.jpg) I have studied tons of resources, crediting other researchers and their contributions at the end of each article I wrote. Thank you all for sharing your hard-earned knowledge for free. You are all awesome! However, two individuals have significantly accelerated my progress, and I want to honor them:
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] [--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]
[--extract_sandbox_operations] [--extract_sandbox_platform_profile] [--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]
[--parse_mpo mpo_addr] [--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] [--dump_kext kext_name]
Mach-O files parser for binary analysis * **[Jonathan Levin](https://x.com/Morpheus______)** His [*OS Internals trilogy](https://newosxbook.com/home.html) helped me rapidly learn the beauty of the macOS system. If there is a single resource I would recommend for anybody, it is the masterpiece you wrote. Thank you!
options: * **[Patrick Wardle](https://x.com/patrickwardle)** He created the [OBTS conference](https://objective-see.org/), where many brilliant minds come together to share their research. You've created something to look forward to every year. Thank you!
-h, --help show this help message and exit
GENERAL ARGS: ## Repository popularity
-p, --path PATH Path to the Mach-O file [![Star History Chart](https://api.star-history.com/svg?repos=Karmaz95/Snake_Apple&type=Date)](https://star-history.com/#Karmaz95/Snake_Apple)
-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_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
MACH-O ARGS:
--file_type Print binary file type
--header_flags Print binary header flags
--endian Print binary endianess
--header Print binary header
--load_commands Print binary load commands names
--has_cmd LC_MAIN Check of binary has given load command
--segments Print binary segments in human-friendly form
--has_segment __SEGMENT
Check if binary has given '__SEGMENT'
--sections Print binary sections in human-friendly form
--has_section __SEGMENT,__section
Check if binary has given '__SEGMENT,__section'
--symbols Print all binary symbols
--imports Print imported symbols
--exports Print exported symbols
--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)
--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
--dump_data [offset,size,output_path]
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)
--constructors Print binary constructors
--dump_section __SEGMENT,__section
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)
--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)
--extract_cms cms_signature.der
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
--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)
--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)
--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)
--is_stripped Check if binary is stripped
--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
--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)
--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_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)
--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
--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).
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)
--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
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.
--compiled_with_dyld_env
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:
--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
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
--xattr_value xattr_name
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
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
--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
--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
--sandbox_redirected_paths
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
--sandbox_system_profiles
Print the system profile from the sandbox container metadata in JSON format
--sandbox_content_protection
Print the content protection from the sandbox container metadata
--sandbox_profile_data
Print raw bytes ofthe sandbox profile data from the sandbox container metadata
--extract_sandbox_operations
Extract sandbox operations from the Sandbox.kext file
--extract_sandbox_platform_profile
Extract sandbox platform profile 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
XNU ARGS:
--parse_mpo mpo_addr Parse mac_policy_ops at given address from Kernel Cache and print pointers in use (not
zeroed)
--dump_prelink_info [(optional) out_name]
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_kext [kext_name]
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}
--kmod_info kext_name
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
--kext_exit kext_name
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
--dump_kext kext_name
Dump the kernel extension binary from the kernelcache.decompressed file
```
* Example:
```bash
CrimsonUroboros.py -p PATH --info
```
***
### [MachOFileFinder](I.%20Mach-O/python/MachOFileFinder.py)
Designed to find ARM64 Mach-O binaries within a specified directory and print their file type.
* Usage:
```bash
python MachOFileFinder.py PATH
```
* Example:
```bash
python MachOFileFinder.py . -r 2>/dev/null
EXECUTE:/Users/karmaz95/t/pingsender
DYLIB:/Users/karmaz95/t/dylibs/use_dylib_app/customs/custom.dylib
BUNDLE:/Users/karmaz95/t/bundles/MyBundle
```
***
### [TrustCacheParser](II.%20Code%20Signing/python/TrustCacheParser.py)
Designed to parse trust caches and print it in human readable form (based on [PyIMG4](https://github.com/m1stadev/PyIMG4) and [trustcache](https://github.com/CRKatri/trustcache))
* Usage:
```console
usage: TrustCacheParser [-h] [--dst DST] [--parse_img] [--parse_tc] [--print_tc] [--all]
Copy Trust Cache files to a specified destination.
options:
-h, --help show this help message and exit
--dst DST, -d DST Destination directory to copy Trust Cache files to.
--parse_img Parse copied Image4 to extract payload data.
--parse_tc Parse extract payload data to human-readable form trust cache using
trustcache.
--print_tc Print the contents of trust_cache (files must be in the current
directory and ends with .trust_cache)
--all parse_img -> parse_tc -> print_tc
```
***
### [SignatureReader](II.%20Code%20Signing/python/SignatureReader.py)
Designed to parse extracted cms sginature from Mach-O files.
* Usage:
```bash
# First extract CMS Signature using CrimsonUroboros
CrimsonUroboros -p target_binary --extract_cms cms_sign
# or using extract_cms.sh script
./extract_cms.sh target_binary cms_sign
```
```console
usage: SignatureReader [-h] [--load_cms cms_signature.der]
[--extract_signature cms_signature.der]
[--extract_pubkey cert_0] [--human]
CMS Signature Loader
options:
-h, --help show this help message and exit
--load_cms cms_signature.der
Load the DER encoded CMS Signature from the filesystem
and print it
--extract_signature cms_signature.der
Extract and print the signature part from the DER
encoded CMS Signature
--extract_pubkey cert_0
Extract public key from the given certificate and save
it to extracted_pubkey.pem
--human Print in human-readable format
CrimsonUroboros -p signed_ad_hoc_example --extract_cms cms_sign
```
* Example:
```bash
SignatureReader --extract_signature cms_sign --human
0x25ca80ad5f11be197dc7a2d53f3db5b6bf463a38224db8c0a17fa4b8fd5ad7e0c60f2be8e8849cf2e581272290991c0db40b0d452b2d2dbf230c0ccab3a6d78e0230bca7bccbc50d379372bcddd8d8542add5ec59180bc3409b2df3bd8995301b9ba1e65ac62420c75104f12cb58b430fde8a177a1cd03940d4b0e77a9d875d65552cf96f03cb63b437c36d9bab12fa727e17603da49fcb870edaec115f90def1ac2ad12c2e9349a5470b5ed2f242b5566cd7ddee785eff8ae5484f145a8464d4dc3891b10a3b2981e9add1e4c0aec31fa80320eb5494d9623400753adf24106efdd07ad657035ed2876e9460219944a4730b0b620954961350ddb1fcf0ea539
```
***
### [extract_cms.sh](II.%20Code%20Signing/custom/extract_cms.sh)
Designed to extract cms sginature from Mach-O files (bash alternative to `SingatureReader --extract_signature`).
* Example:
```
./extract_cms.sh target_binary cms_sign
```
***
### [ModifyMachOFlags](III.%20Checksec/python/ModifyMachOFlags.py)
Designed to change Mach-O header flags.
* Usage:
```console
usage: ModifyMachOFlags [-h] -i INPUT -o OUT [--flag FLAG] [--sign_binary [adhoc|identity_number]]
Modify the Mach-O binary flags.
options:
-h, --help show this help message and exit
-i INPUT, --input INPUT
Path to the Mach-O file.
-o OUT, --out OUT Where to save a modified file.
--flag FLAG Specify the flag constant name and value (e.g., NO_HEAP_EXECUTION=1). Can be used multiple times. Available
flags: NOUNDEFS, INCRLINK, DYLDLINK, BINDATLOAD, PREBOUND, SPLIT_SEGS, LAZY_INIT, TWOLEVEL, FORCE_FLAT,
NOMULTIDEFS, NOFIXPREBINDING, PREBINDABLE, ALLMODSBOUND, SUBSECTIONS_VIA_SYMBOLS, CANONICAL, WEAK_DEFINES,
BINDS_TO_WEAK, ALLOW_STACK_EXECUTION, ROOT_SAFE, SETUID_SAFE, NO_REEXPORTED_DYLIBS, PIE,
DEAD_STRIPPABLE_DYLIB, HAS_TLV_DESCRIPTORS, NO_HEAP_EXECUTION, APP_EXTENSION_SAFE,
NLIST_OUTOFSYNC_WITH_DYLDINFO, SIM_SUPPORT, DYLIB_IN_CACHE
--sign_binary [adhoc|identity_number]
Sign binary using specified identity - use : 'security find-identity -v -p codesigning' to get the
identity. (default: adhoc)
```
* Example:
```bash
ModifyMachOFlags -i hello -o hello_modified --flag NO_HEAP_EXECUTION=1 --sign_binary
```
***
### [LCFinder](III.%20Checksec/python/LCFinder.py)
Designed to find if specified Load Command exist in the binary or list of binaries.
* Usage:
```console
usage: LCFinder [-h] [--path PATH] [--list_path LIST_PATH] --lc LC
Check for a specific load command in Mach-O binaries.
options:
-h, --help show this help message and exit
--path PATH, -p PATH Absolute path to the valid MachO binary.
--list_path LIST_PATH, -l LIST_PATH
Path to a wordlist file containing absolute paths.
--lc LC The load command to check for.
```
* Example:
```bash
LCFinder -l macho_paths.txt --lc SEGMENT_64 2>/dev/null
LCFinder -p hello --lc lc_segment_64 2>/dev/null
```
***
### [MachODylibLoadCommandsFinder](IV.%20Dylibs/python/MachODylibLoadCommandsFinder.py)
Designed to Recursively crawl the system and parse Mach-O files to find DYLIB related load commands.
Print the total Mach-O files analyzed and how many DYLIB-related LCs existed
* Usage:
```console
MachODylibLoadCommandsFinder 2>/dev/null
```
***
### [check_amfi](VI.%20AMFI/python/check_amfi.py)
Simple script for calculating `amfiFlags` (described [here](https://karol-mazurek.medium.com/dyld-do-you-like-death-vi-1013a69118ff) in `ProcessConfig — AMFI properties`)
* Usage:
```console
python3 check_amfi.py 0x1df
```
***
### [make_bundle](App%20Bundle%20Extension/custom/make_bundle.sh)
Build a codeless bundle with a red icon.
* Usage:
```console
./make_bundle.sh
```
***
### [make_bundle_exe](App%20Bundle%20Extension/custom/make_bundle_exe.sh)
Bash template for building a PoC app bundle with Mach-O binary that utilizes Framework:
* Usage:
```console
./make_bundle_exe.sh
```
***
### [make_dmg](App%20Bundle%20Extension/custom/make_dmg.sh)
Script for packing the app in a compressed DMG container:
* Usage (change names in the script):
```console
./make_dmg.sh
```
### [electron_patcher](App%20Bundle%20Extension/custom/electron_patcher.py)
Python script for extracting ASAR files from Electron apps and patching them with a custom ASAR file.
```
python3 electron_patcher.py extract app_bundle.app extracted_asar
python3 electron_patcher.py pack extracted_asar app_bundle.app
```
### [sandbox_validator](VIII.%20Sandbox/custom/sandbox_validator.c)
It can be used to quickly check if a given process is allowed to perform a particular operation while it is sandboxed.
```bash
# Compile
clang -o sandbox_validator sandbox_validator.c
# Usage: sandbox_validator PID "OPERATION" "FILTER_NAME" "FILTER_VALUE"
sandbox_validator 93298
sandbox_validator 93298 "file-read*"
sandbox_validator 93298 "file-read*" PATH "/users/karmaz/.trash"
sandbox_validator 93298 "authorization-right-obtain" RIGHT_NAME "system.burn"
```
### [sandblaster](https://github.com/Karmaz95/sandblaster)
This is my forked version of [sandblaster](https://github.com/cellebrite-labs/sandblaster) with MacOS Support:
```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
```
pip3 install -r requirements.txt
wget https://github.com/CRKatri/trustcache/releases/download/v2.0/trustcache_macos_arm64 -O /usr/local/bin/trustcache
chmod +x /usr/local/bin/trustcache
xattr -d com.apple.quarantine /usr/local/bin/trustcache
brew install keith/formulae/dyld-shared-cache-extractor
brew install blacktop/tap/ipsw
brew install tree
```
## LIMITATIONS
* Codesigning module(codesign wrapper) works only on macOS.
* `--dylib_hijacking` needs [ipsw](https://github.com/blacktop/ipsw) to be installed.
* `--dylibtree` needs the [dyld-shared-cache-extractor](https://github.com/keith/dyld-shared-cache-extractor) to be installed.
## WHY UROBOROS?
I will write the code for each article as a class SnakeX, where X will be the article number, to make it easier for the audience to follow.
Each Snake class will be a child of the previous one and infinitely "eat itself" (inherit methods of the previous class), like Uroboros.
## ADDITIONAL LINKS
* [Apple Open Source](https://opensource.apple.com/releases/)
* [XNU](https://github.com/apple-oss-distributions/xnu)
* [dyld](https://github.com/apple-oss-distributions/dyld)
## TODO - IDEAS / IMPROVES
* DER Entitlements converter method - currently, only the `convert_xml_entitlements_to_dict()` method exists. I need to create a Python parser for DER-encoded entitlements.
* SuperBlob parser - to find other blobs in Code Signature.
* Entitlements Blob parser - to check if XML and DER blobs exist.
* Every method in the Snake class that use Entitlements should parse first XML > DER (currently, only XML parser exists)
* After making a SuperBlob parser and CodeDirectory blob parser, modify hasHardenedRuntime to check Runtime flag by using bitmask, instead of string.
* Build Dyld Shared Cache parser and extractor to make SnakeIV independant of dyld-shared-cache-extractor.
* Create `RottenApple.app` in another repository and use it for testing.
* Add Dyld Closure chapter to Snake&Apple V - Dyld.
* Move `kext_prelinkinfo`, `dumpPrelink_info` and `dumpPrelink_text` to Snake & Apple chapter about Kernel Extensions when ready.
* Add kernelcache parser.
* Add `LC_FILESET_ENTRY` method to `dumpKernelExtension`.
* Consider moving methods like `removeNullBytesAlignment`, `calcTwoComplement64` etc. to `Utils` class.
* Make Thread manager class and improve the Threading.thread with tracing methods and `kill()`.
* Reconsider moving --xattr like args to another Snake class related to filesystem.

733
TOOLS.md Normal file
View File

@@ -0,0 +1,733 @@
# TOOLS
Here is the list of all tools in this repository:
[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) • [IOVerify](#ioverify) • [r2_dd](#r2_dd) • [find_symbol](#find_symbol)
***
### [CrimsonUroboros](tests/CrimsonUroboros.py)
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.
![alt](img/CrimsonUroboros.jpg)
#### WHY UROBOROS?
I wrote the code for each article as a class `SnakeX`. The `X` was the article number, to make it easier for the audience to follow. Each `Snake` class is a child of the previous one. It infinitely "eats itself" (inherits methods of the last class), like Uroboros.
#### INSTALLATION
```
pip3 install -r requirements.txt
wget https://github.com/CRKatri/trustcache/releases/download/v2.0/trustcache_macos_arm64 -O /usr/local/bin/trustcache
chmod +x /usr/local/bin/trustcache
xattr -d com.apple.quarantine /usr/local/bin/trustcache
brew install keith/formulae/dyld-shared-cache-extractor
brew install blacktop/tap/ipsw
brew install tree
```
#### LIMITATIONS
* Codesigning module(codesign wrapper) works only on macOS.
* `--dylib_hijacking` needs [ipsw](https://github.com/blacktop/ipsw) to be installed.
* `--dylibtree` needs the [dyld-shared-cache-extractor](https://github.com/keith/dyld-shared-cache-extractor) to be installed.
#### 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] [--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]
[--extract_sandbox_operations] [--extract_sandbox_platform_profile] [--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]
[--parse_mpo mpo_addr] [--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] [--dump_kext kext_name]
Mach-O files parser for binary analysis
options:
-h, --help show this help message and exit
GENERAL ARGS:
-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_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
MACH-O ARGS:
--file_type Print binary file type
--header_flags Print binary header flags
--endian Print binary endianess
--header Print binary header
--load_commands Print binary load commands names
--has_cmd LC_MAIN Check of binary has given load command
--segments Print binary segments in human-friendly form
--has_segment __SEGMENT
Check if binary has given '__SEGMENT'
--sections Print binary sections in human-friendly form
--has_section __SEGMENT,__section
Check if binary has given '__SEGMENT,__section'
--symbols Print all binary symbols
--imports Print imported symbols
--exports Print exported symbols
--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)
--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
--dump_data [offset,size,output_path]
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)
--constructors Print binary constructors
--dump_section __SEGMENT,__section
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)
--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)
--extract_cms cms_signature.der
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
--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)
--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)
--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)
--is_stripped Check if binary is stripped
--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
--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)
--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_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)
--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
--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).
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)
--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
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.
--compiled_with_dyld_env
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:
--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
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
--xattr_value xattr_name
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
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
--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
--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
--sandbox_redirected_paths
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
--sandbox_system_profiles
Print the system profile from the sandbox container metadata in JSON format
--sandbox_content_protection
Print the content protection from the sandbox container metadata
--sandbox_profile_data
Print raw bytes ofthe sandbox profile data from the sandbox container metadata
--extract_sandbox_operations
Extract sandbox operations from the Sandbox.kext file
--extract_sandbox_platform_profile
Extract sandbox platform profile 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
XNU ARGS:
--parse_mpo mpo_addr Parse mac_policy_ops at given address from Kernel Cache and print pointers in use (not
zeroed)
--dump_prelink_info [(optional) out_name]
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_kext [kext_name]
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}
--kmod_info kext_name
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
--kext_exit kext_name
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
--dump_kext kext_name
Dump the kernel extension binary from the kernelcache.decompressed file
```
* Example:
```bash
CrimsonUroboros.py -p PATH --info
```
***
### [MachOFileFinder](I.%20Mach-O/python/MachOFileFinder.py)
Designed to find ARM64 Mach-O binaries within a specified directory and print their file type.
* Usage:
```bash
python MachOFileFinder.py PATH
```
* Example:
```bash
python MachOFileFinder.py . -r 2>/dev/null
EXECUTE:/Users/karmaz95/t/pingsender
DYLIB:/Users/karmaz95/t/dylibs/use_dylib_app/customs/custom.dylib
BUNDLE:/Users/karmaz95/t/bundles/MyBundle
```
***
### [TrustCacheParser](II.%20Code%20Signing/python/TrustCacheParser.py)
Designed to parse trust caches and print it in human readable form (based on [PyIMG4](https://github.com/m1stadev/PyIMG4) and [trustcache](https://github.com/CRKatri/trustcache))
* Usage:
```console
usage: TrustCacheParser [-h] [--dst DST] [--parse_img] [--parse_tc] [--print_tc] [--all]
Copy Trust Cache files to a specified destination.
options:
-h, --help show this help message and exit
--dst DST, -d DST Destination directory to copy Trust Cache files to.
--parse_img Parse copied Image4 to extract payload data.
--parse_tc Parse extract payload data to human-readable form trust cache using
trustcache.
--print_tc Print the contents of trust_cache (files must be in the current
directory and ends with .trust_cache)
--all parse_img -> parse_tc -> print_tc
```
***
### [SignatureReader](II.%20Code%20Signing/python/SignatureReader.py)
Designed to parse extracted cms sginature from Mach-O files.
* Usage:
```bash
# First extract CMS Signature using CrimsonUroboros
CrimsonUroboros -p target_binary --extract_cms cms_sign
# or using extract_cms.sh script
./extract_cms.sh target_binary cms_sign
```
```console
usage: SignatureReader [-h] [--load_cms cms_signature.der]
[--extract_signature cms_signature.der]
[--extract_pubkey cert_0] [--human]
CMS Signature Loader
options:
-h, --help show this help message and exit
--load_cms cms_signature.der
Load the DER encoded CMS Signature from the filesystem
and print it
--extract_signature cms_signature.der
Extract and print the signature part from the DER
encoded CMS Signature
--extract_pubkey cert_0
Extract public key from the given certificate and save
it to extracted_pubkey.pem
--human Print in human-readable format
CrimsonUroboros -p signed_ad_hoc_example --extract_cms cms_sign
```
* Example:
```bash
SignatureReader --extract_signature cms_sign --human
0x25ca80ad5f11be197dc7a2d53f3db5b6bf463a38224db8c0a17fa4b8fd5ad7e0c60f2be8e8849cf2e581272290991c0db40b0d452b2d2dbf230c0ccab3a6d78e0230bca7bccbc50d379372bcddd8d8542add5ec59180bc3409b2df3bd8995301b9ba1e65ac62420c75104f12cb58b430fde8a177a1cd03940d4b0e77a9d875d65552cf96f03cb63b437c36d9bab12fa727e17603da49fcb870edaec115f90def1ac2ad12c2e9349a5470b5ed2f242b5566cd7ddee785eff8ae5484f145a8464d4dc3891b10a3b2981e9add1e4c0aec31fa80320eb5494d9623400753adf24106efdd07ad657035ed2876e9460219944a4730b0b620954961350ddb1fcf0ea539
```
***
### [extract_cms.sh](II.%20Code%20Signing/custom/extract_cms.sh)
Designed to extract cms sginature from Mach-O files (bash alternative to `SingatureReader --extract_signature`).
* Example:
```
./extract_cms.sh target_binary cms_sign
```
***
### [ModifyMachOFlags](III.%20Checksec/python/ModifyMachOFlags.py)
Designed to change Mach-O header flags.
* Usage:
```console
usage: ModifyMachOFlags [-h] -i INPUT -o OUT [--flag FLAG] [--sign_binary [adhoc|identity_number]]
Modify the Mach-O binary flags.
options:
-h, --help show this help message and exit
-i INPUT, --input INPUT
Path to the Mach-O file.
-o OUT, --out OUT Where to save a modified file.
--flag FLAG Specify the flag constant name and value (e.g., NO_HEAP_EXECUTION=1). Can be used multiple times. Available
flags: NOUNDEFS, INCRLINK, DYLDLINK, BINDATLOAD, PREBOUND, SPLIT_SEGS, LAZY_INIT, TWOLEVEL, FORCE_FLAT,
NOMULTIDEFS, NOFIXPREBINDING, PREBINDABLE, ALLMODSBOUND, SUBSECTIONS_VIA_SYMBOLS, CANONICAL, WEAK_DEFINES,
BINDS_TO_WEAK, ALLOW_STACK_EXECUTION, ROOT_SAFE, SETUID_SAFE, NO_REEXPORTED_DYLIBS, PIE,
DEAD_STRIPPABLE_DYLIB, HAS_TLV_DESCRIPTORS, NO_HEAP_EXECUTION, APP_EXTENSION_SAFE,
NLIST_OUTOFSYNC_WITH_DYLDINFO, SIM_SUPPORT, DYLIB_IN_CACHE
--sign_binary [adhoc|identity_number]
Sign binary using specified identity - use : 'security find-identity -v -p codesigning' to get the
identity. (default: adhoc)
```
* Example:
```bash
ModifyMachOFlags -i hello -o hello_modified --flag NO_HEAP_EXECUTION=1 --sign_binary
```
***
### [LCFinder](III.%20Checksec/python/LCFinder.py)
Designed to find if specified Load Command exist in the binary or list of binaries.
* Usage:
```console
usage: LCFinder [-h] [--path PATH] [--list_path LIST_PATH] --lc LC
Check for a specific load command in Mach-O binaries.
options:
-h, --help show this help message and exit
--path PATH, -p PATH Absolute path to the valid MachO binary.
--list_path LIST_PATH, -l LIST_PATH
Path to a wordlist file containing absolute paths.
--lc LC The load command to check for.
```
* Example:
```bash
LCFinder -l macho_paths.txt --lc SEGMENT_64 2>/dev/null
LCFinder -p hello --lc lc_segment_64 2>/dev/null
```
***
### [MachODylibLoadCommandsFinder](IV.%20Dylibs/python/MachODylibLoadCommandsFinder.py)
Designed to Recursively crawl the system and parse Mach-O files to find DYLIB related load commands.
Print the total Mach-O files analyzed and how many DYLIB-related LCs existed
* Usage:
```console
MachODylibLoadCommandsFinder 2>/dev/null
```
***
### [check_amfi](VI.%20AMFI/python/check_amfi.py)
Simple script for calculating `amfiFlags` (described [here](https://karol-mazurek.medium.com/dyld-do-you-like-death-vi-1013a69118ff) in `ProcessConfig — AMFI properties`)
* Usage:
```console
python3 check_amfi.py 0x1df
```
***
### [make_bundle](App%20Bundle%20Extension/custom/make_bundle.sh)
Build a codeless bundle with a red icon.
* Usage:
```console
./make_bundle.sh
```
***
### [make_bundle_exe](App%20Bundle%20Extension/custom/make_bundle_exe.sh)
Bash template for building a PoC app bundle with Mach-O binary that utilizes Framework:
* Usage:
```console
./make_bundle_exe.sh
```
***
### [make_dmg](App%20Bundle%20Extension/custom/make_dmg.sh)
Script for packing the app in a compressed DMG container:
* Usage (change names in the script):
```console
./make_dmg.sh
```
### [electron_patcher](App%20Bundle%20Extension/custom/electron_patcher.py)
Python script for extracting ASAR files from Electron apps and patching them with a custom ASAR file.
```
python3 electron_patcher.py extract app_bundle.app extracted_asar
python3 electron_patcher.py pack extracted_asar app_bundle.app
```
### [sandbox_validator](VIII.%20Sandbox/custom/sandbox_validator.c)
It can be used to quickly check if a given process is allowed to perform a particular operation while it is sandboxed.
```bash
# Compile
clang -o sandbox_validator sandbox_validator.c
# Usage: sandbox_validator PID "OPERATION" "FILTER_NAME" "FILTER_VALUE"
sandbox_validator 93298
sandbox_validator 93298 "file-read*"
sandbox_validator 93298 "file-read*" PATH "/users/karmaz/.trash"
sandbox_validator 93298 "authorization-right-obtain" RIGHT_NAME "system.burn"
```
### [sandblaster](https://github.com/Karmaz95/sandblaster)
This is my forked version of [sandblaster](https://github.com/cellebrite-labs/sandblaster) with MacOS Support:
```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`.
```
### [IOVerify](X.%20NU/custom/drivers/IOVerify.c)
This tool allows for direct interaction with macOS IOKit drivers using IOConnectCallMethod. It was introduced in the article I made for PHRACK - [Mapping IOKit Methods Exposed to User Space on macOS](https://phrack.org/issues/72/9_md#article).
```bash
./IOVerify -h
Usage: ./IOVerify -n <name> (-m <method> | -y <spec>) [options]
Options:
-n <name> Target driver class name (required).
-t <type> Connection type (default: 0).
-m <id> Method selector ID.
-y <spec> Specify method and buffer sizes in one string.
Format: "ID: [IN_SCA, IN_STR, OUT_SCA, OUT_STR]"
Example: -y "0: [0, 96, 0, 96]"
-p <string> Payload as a string.
-f <file> File path for payload.
-b <hex_str> Space-separated hex string payload.
-i <size> Input buffer size (ignored if -y is used).
-o <size> Output buffer size (ignored if -y is used).
-s <value> Scalar input (uint64_t). Can be specified multiple times.
-S <count> Scalar output count (ignored if -y is used).
-h Show this help message.
./IOVerify -n "H11ANEIn" -t 1 -y "0: [0,1,0,1]"
Starting verification for driver: H11ANEIn
--- [VERIFY] Event Log ---
Driver: H11ANEIn
Connection Type: 1
Method Selector: 0
Result: 0xe00002c2 ((iokit/common) invalid argument)
--- Scalar I/O ---
Scalar In Cnt: 0
Scalar Out Cnt: 0
--- Structure I/O ---
Input Size: 1 bytes
Input Data:
00
Output Size: 1 bytes
Output Data:
00
--- End of Log ---
```
### [r2_dd](I.%20Mach-O/python/r2_dd.py)
A wrapper script that uses radare2 to dump binary data from Mach-O files between specified virtual addresses. It automatically maps virtual addresses to file offsets.
* Usage:
```bash
python3 r2_dd.py BINARY_PATH START_ADDR END_ADDR OUT_FILE
```
* Example:
```bash
python3 r2_dd.py ./kernelcache 0xFFFFFF80002A0000 0xFFFFFF80002A0500 ./dump.bin
```
* Note: Requires `radare2` to be installed:
```bash
brew install radare2
```
### [find_symbol](I.%20Mach-O/python/find_symbol.py)
A python wrapper for searching symbols in binary files using `nm`. Recursively walks through directories to find symbol definitions and references across executables and libraries.
```txt
usage: find_symbol.py PATH SYMBOL
Search for symbols in binary files within a directory
Arguments:
PATH Directory to search recursively
SYMBOL Symbol name to search for
Examples:
---------
1. Find _sandbox_check function across extracted libraries from Dyld Shared Cache:
find_symbol.py . _sandbox_check
2. Search for a specific symbol in system libraries:
find_symbol.py /usr/lib _malloc
3. Locate symbol references in a framework:
find_symbol.py /System/Library/Frameworks/Security.framework SecItemAdd
Sample Output:
--------------
./usr/lib/libspindump.dylib
U _sandbox_check
----
./usr/lib/dyld
0000000180141a6c T _sandbox_check
0000000180141b4c t _sandbox_check_common
----
./usr/lib/libnetworkextension.dylib
U _sandbox_check
----
Notes:
------
- The tool uses `nm` to extract symbol information from binary files
- Symbol types: T (text/code), U (undefined/external reference), t (local text)
- Can be imported as a module: `from find_symbol import find_symbol`
- Skips files that `nm` cannot process (non-binary files are silently ignored)
```

View File

@@ -1,6 +1,7 @@
_hook_iokit_check_get_property _hook_iokit_check_get_property
_hook_iokit_check_filter_properties _hook_iokit_check_filter_properties
_hook_vnode_notify_link _hook_vnode_notify_link
_hook_proc_check_proc_info
_hook_kext_check_unload _hook_kext_check_unload
_hook_kext_check_load _hook_kext_check_load
_hook_pty_notify_close _hook_pty_notify_close
@@ -96,6 +97,7 @@ _hook_socket_check_create
_hook_socket_check_connect _hook_socket_check_connect
_hook_socket_check_bind _hook_socket_check_bind
_hook_proc_check_signal _hook_proc_check_signal
_hook_proc_check_iopolicy
_hook_proc_check_setauid _hook_proc_check_setauid
_hook_proc_check_setaudit _hook_proc_check_setaudit
_hook_proc_check_sched _hook_proc_check_sched
@@ -138,13 +140,17 @@ _hook_mount_check_mount
_hook_mount_check_fsctl _hook_mount_check_fsctl
_hook_mount_check_quotactl _hook_mount_check_quotactl
_hook_vnode_check_copyfile _hook_vnode_check_copyfile
_hook_vnode_check_swap
_hook_vnode_notify_unlink _hook_vnode_notify_unlink
_hook_vnode_notify_swap
_hook_proc_check_set_task_special_port _hook_proc_check_set_task_special_port
_hook_proc_check_get_task_special_port _hook_proc_check_get_task_special_port
_hook_vnode_notify_setflags _hook_vnode_notify_setflags
_hook_vnode_notify_setextattr _hook_vnode_notify_setextattr
_hook_necp_check_client_action _hook_necp_check_client_action
_hook_necp_check_open _hook_necp_check_open
_hook_proc_check_set_thread_exception_port
_hook_proc_check_set_task_exception_port
_hook_file_check_set _hook_file_check_set
_hook_file_check_mmap _hook_file_check_mmap
_hook_file_check_lock _hook_file_check_lock
@@ -155,4 +161,4 @@ _hook_cred_label_associate
_hook_cred_check_label_update _hook_cred_check_label_update
_hook_vnode_check_exec _hook_vnode_check_exec
_hook_cred_check_label_update_execve _hook_cred_check_label_update_execve
_hook_cred_label_update_execve _hook_cred_label_update_execve

2
VIII. Sandbox/python/crimson_waccess.py Normal file → Executable file
View File

@@ -20,7 +20,7 @@ class Waccess:
elif os.path.isfile(path): elif os.path.isfile(path):
self.check_file_write_permission(path, output_path) self.check_file_write_permission(path, output_path)
else: else:
sys.stderr.write(f"Path {path} does not exist.") sys.stderr.write(f"Path {path} does not exist.\n")
def check_directory_write_permission(self, directory_path, output_path): def check_directory_write_permission(self, directory_path, output_path):
'''Check if a directory is writable by attempting to create a test file.''' '''Check if a directory is writable by attempting to create a test file.'''

View File

@@ -0,0 +1,302 @@
/**
* @file IOVerify.c
* @brief Standalone tool for IOKit driver communication verification.
* clang IOVerify.c -o IOVerify -framework IOKit
*
* This tool allows for direct interaction with macOS IOKit drivers using IOConnectCallMethod.
*
* Usage:
* IOVerify -n <name> (-m <method> | -y <spec>) [options]
*
* Options:
* -n <name> : The class name of the IOKit service to target (required).
* -t <type> : The connection type (user client type) to open. Default: 0.
* -m <id> : The selector ID of the method to call.
* -y <spec> : Shorthand to specify method and buffer sizes.
* Format: "ID:[IN_SCA,IN_STR,OUT_SCA,OUT_STR]"
* Example: -y "0: [0, 96, 0, 96]"
* -p <payload> : A string to be used as the input buffer.
* -f <file_name> : A file whose contents will be the input buffer.
* -b <hex_string>: A space-separated hex string for the input buffer (e.g., "00 11 22 aa").
* -i <size> : The size of the input structure buffer.
* -o <size> : The size of the output structure buffer.
* -s <val> : A 64-bit scalar input value (can be used multiple times).
* -S <count> : The number of scalar output values.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdarg.h>
#include <unistd.h> // For getopt
#include <stdbool.h> // For bool type
#include <IOKit/IOKitLib.h>
#include <mach/mach_error.h> // For mach_error_string
// --- Structure Definitions ---
// IOKit has a hard limit of 16 scalar inputs/outputs maximum
#define IOKIT_MAX_SCALAR_COUNT 16
uint64_t scalar_inputs[IOKIT_MAX_SCALAR_COUNT] = {0};
typedef struct {
const char* driver_name;
uint64_t conn_type;
uint64_t method;
const char* payload;
const char* file_name;
uint64_t input_size;
uint64_t output_size;
uint64_t* scalar_input;
size_t scalar_in_size;
size_t scalar_out_size;
const char* byte_payload;
} verify_args_t;
// --- Forward Declarations ---
io_service_t find_driver_service(const char* driver_name);
io_connect_t get_driver_connection_handle(io_service_t service, const char* driver_name, uint32_t conn_type);
kern_return_t verify_driver_communication(const verify_args_t* args);
void print_usage(const char* prog_name);
// --- Implementations ---
void log_with_args(const char* format, ...) {
va_list args;
va_start(args, format);
vprintf(format, args);
va_end(args);
printf("\n");
}
void print_payload_hexdump(const unsigned char *data, size_t size) {
if (!data || size == 0) {
printf("[empty]\n");
return;
}
for (size_t i = 0; i < size; ++i) {
printf("%02x ", data[i]);
if ((i + 1) % 16 == 0) printf("\n");
}
if (size % 16 != 0) printf("\n");
}
void log_event_with_scalars(const char* event, const verify_args_t* args, kern_return_t result, const void* output_buffer, size_t output_size, const uint64_t* scalar_output, size_t scalar_out_size_actual, const void* input_buffer, size_t input_size) {
printf("\n--- [%s] Event Log ---\n", event);
printf("Driver: %s\n", args->driver_name);
printf("Connection Type: %llu\n", args->conn_type);
printf("Method Selector: %llu\n", args->method);
printf("Result: 0x%x (%s)\n", result, mach_error_string(result));
printf("\n--- Scalar I/O ---\n");
printf("Scalar In Cnt: %zu\n", args->scalar_in_size);
if (args->scalar_in_size > 0 && args->scalar_input) {
printf("Scalar In: ");
for (size_t i = 0; i < args->scalar_in_size; ++i) printf("0x%llx ", args->scalar_input[i]);
printf("\n");
}
printf("Scalar Out Cnt: %zu\n", scalar_out_size_actual);
if (scalar_out_size_actual > 0 && scalar_output) {
printf("Scalar Out: ");
for (size_t i = 0; i < scalar_out_size_actual; ++i) printf("0x%llx ", scalar_output[i]);
printf("\n");
}
printf("\n--- Structure I/O ---\n");
printf("Input Size: %zu bytes\n", input_size);
printf("Input Data:\n");
print_payload_hexdump(input_buffer, input_size);
printf("\nOutput Size: %zu bytes\n", output_size);
printf("Output Data:\n");
print_payload_hexdump(output_buffer, output_size);
printf("--- End of Log ---\n\n");
}
io_service_t find_driver_service(const char* driver_name) {
return IOServiceGetMatchingService(kIOMainPortDefault, IOServiceMatching(driver_name));
}
io_connect_t get_driver_connection_handle(io_service_t service, const char* driver_name, uint32_t conn_type) {
io_connect_t client = MACH_PORT_NULL;
kern_return_t kr = IOServiceOpen(service, mach_task_self(), conn_type, &client);
if (kr != KERN_SUCCESS) {
log_with_args("Failed to open connection for driver '%s' (type %u). Error: 0x%x (%s)", driver_name, conn_type, kr, mach_error_string(kr));
return MACH_PORT_NULL;
}
return client;
}
kern_return_t verify_driver_communication(const verify_args_t* args) {
if (!args || !args->driver_name) {
return KERN_INVALID_ARGUMENT;
}
const io_service_t driver_service = find_driver_service(args->driver_name);
if (driver_service == MACH_PORT_NULL) {
log_with_args("Driver service '%s' not found.", args->driver_name);
return KERN_FAILURE;
}
const io_connect_t client = get_driver_connection_handle(driver_service, args->driver_name, args->conn_type);
if (client == MACH_PORT_NULL) {
IOObjectRelease(driver_service);
return KERN_FAILURE;
}
void* input_buffer = NULL;
size_t input_size = args->input_size;
if (args->file_name) {
FILE* file = fopen(args->file_name, "rb");
if (file) {
fseek(file, 0, SEEK_END);
input_size = ftell(file);
fseek(file, 0, SEEK_SET);
input_buffer = calloc(1, input_size);
if (input_buffer) fread(input_buffer, 1, input_size, file);
fclose(file);
}
} else if (args->byte_payload) {
char* bp_copy = strdup(args->byte_payload);
char* p = bp_copy;
size_t count = 0;
while (*p) {
if (*p != ' ' && (p == bp_copy || *(p-1) == ' ')) count++;
p++;
}
input_size = count;
input_buffer = calloc(1, input_size);
p = bp_copy;
size_t idx = 0;
while(idx < input_size) {
char* next;
((unsigned char*)input_buffer)[idx++] = (unsigned char)strtol(p, &next, 16);
p = next;
while(*p == ' ') p++;
}
free(bp_copy);
} else if (args->payload) {
input_size = strlen(args->payload);
input_buffer = calloc(1, input_size + 1);
if (input_buffer) memcpy(input_buffer, args->payload, input_size);
} else if (input_size > 0) {
input_buffer = calloc(1, input_size);
}
size_t scalar_output_count = args->scalar_out_size;
uint64_t* scalar_output = (scalar_output_count > 0) ? calloc(scalar_output_count, sizeof(uint64_t)) : NULL;
size_t output_size = args->output_size;
void* output_buffer = (output_size > 0) ? calloc(1, output_size) : NULL;
uint32_t scalar_output_count_32 = scalar_output_count;
kern_return_t result = IOConnectCallMethod(client, args->method, args->scalar_input, args->scalar_in_size, input_buffer, input_size, scalar_output, &scalar_output_count_32, output_buffer, &output_size);
log_event_with_scalars("VERIFY", args, result, output_buffer, output_size, scalar_output, scalar_output_count_32, input_buffer, input_size);
free(input_buffer);
free(output_buffer);
free(scalar_output);
IOServiceClose(client);
IOObjectRelease(driver_service);
return result;
}
void print_usage(const char* prog_name) {
printf("Usage: %s -n <name> (-m <method> | -y <spec>) [options]\n", prog_name);
printf("Options:\n");
printf(" -n <name> Target driver class name (required).\n");
printf(" -t <type> Connection type (default: 0).\n");
printf(" -m <id> Method selector ID.\n");
printf(" -y <spec> Specify method and buffer sizes in one string.\n");
printf(" Format: \"ID: [IN_SCA, IN_STR, OUT_SCA, OUT_STR]\"\n");
printf(" Example: -y \"0: [0, 96, 0, 96]\"\n");
printf(" -p <string> Payload as a string.\n");
printf(" -f <file> File path for payload.\n");
printf(" -b <hex_str> Space-separated hex string payload.\n");
printf(" -i <size> Input buffer size (ignored if -y is used).\n");
printf(" -o <size> Output buffer size (ignored if -y is used).\n");
printf(" -s <value> Scalar input (uint64_t). Can be specified multiple times.\n");
printf(" -S <count> Scalar output count (ignored if -y is used).\n");
printf(" -h Show this help message.\n");
}
int main(int argc, char *argv[]) {
verify_args_t args = {0};
int opt;
uint64_t scalar_inputs[16] = {0};
size_t scalar_input_idx = 0;
bool method_is_set = false;
bool y_flag_used = false;
while ((opt = getopt(argc, argv, "hn:t:m:p:f:b:i:o:s:S:y:")) != -1) {
switch (opt) {
case 'h': print_usage(argv[0]); return 0;
case 'n': args.driver_name = optarg; break;
case 't': args.conn_type = strtoull(optarg, NULL, 0); break;
case 'm': args.method = strtoull(optarg, NULL, 0); method_is_set = true; break;
case 'y': {
y_flag_used = true;
unsigned long long m, si, is, so, os;
int items = sscanf(optarg, "%llu : [ %llu , %llu , %llu , %llu ]", &m, &si, &is, &so, &os);
if (items == 5) { // Adding Scalar In/Out count checks - can't be more than 16
if (si > IOKIT_MAX_SCALAR_COUNT) {
fprintf(stderr, "Error: Scalar input count %llu exceeds IOKit maximum of %d.\n", si, IOKIT_MAX_SCALAR_COUNT);
return 1;
}
if (so > IOKIT_MAX_SCALAR_COUNT) {
fprintf(stderr, "Error: Scalar output count %llu exceeds IOKit maximum of %d.\n", so, IOKIT_MAX_SCALAR_COUNT);
return 1;
}
args.method = m;
args.scalar_in_size = si;
args.input_size = is;
args.scalar_out_size = so;
args.output_size = os;
method_is_set = true;
} else {
fprintf(stderr, "Error: Invalid format for -y. Use 'ID: [sc_in, st_in, sc_out, st_out]'.\n");
return 1;
}
break;
}
case 'p': args.payload = optarg; break;
case 'f': args.file_name = optarg; break;
case 'b': args.byte_payload = optarg; break;
case 'i': if (!y_flag_used) args.input_size = strtoull(optarg, NULL, 0); break;
case 'o': if (!y_flag_used) args.output_size = strtoull(optarg, NULL, 0); break;
case 's': // Adding Scalar In count checks - can't be more than 16
if (scalar_input_idx < IOKIT_MAX_SCALAR_COUNT) {
scalar_inputs[scalar_input_idx++] = strtoull(optarg, NULL, 0);
} else {
fprintf(stderr, "Exceeded IOKit maximum of %d scalar inputs.\n", IOKIT_MAX_SCALAR_COUNT);
}
break;
case 'S': if (!y_flag_used) args.scalar_out_size = strtoull(optarg, NULL, 0); break;
default: print_usage(argv[0]); return 1;
}
}
if (!args.driver_name || !method_is_set) {
fprintf(stderr, "Error: Driver name (-n) and method ID (-m or -y) are required.\n");
print_usage(argv[0]);
return 1;
}
if (y_flag_used) {
if (scalar_input_idx > args.scalar_in_size) {
fprintf(stderr, "Warning: More scalars via -s (%zu) than in -y (%zu). Extra values ignored.\n", scalar_input_idx, args.scalar_in_size);
} else if (scalar_input_idx < args.scalar_in_size) {
fprintf(stderr, "Warning: Fewer scalars via -s (%zu) than in -y (%zu). Remaining values zeroed.\n", scalar_input_idx, args.scalar_in_size);
}
} else {
args.scalar_in_size = scalar_input_idx;
}
args.scalar_input = scalar_inputs;
printf("Starting verification for driver: %s\n", args.driver_name);
verify_driver_communication(&args);
return 0;
}

View File

@@ -0,0 +1,149 @@
#!/usr/bin/env python3
"""
dtrace_NewUserClient.py
A script to trace all kernel newUserClient calls using DTrace on macOS.
Prints the PID, full executable path, demangled kernel function name, and user call stack for each event.
Requirements:
- Must be run as root (sudo).
- System Integrity Protection (SIP) must be disabled for DTrace to work on macOS.
- Tested on macOS with DTrace and c++filt available.
Usage:
1. Make the script executable:
chmod +x dtrace_NewUserClient.py
2. Run with sudo:
sudo ./dtrace_NewUserClient.py
3. Press CTRL+C to stop tracing gracefully.
Output:
For each newUserClient call, prints:
- PID
- Executable path
- Demangled kernel function name
- User call stack
Graceful termination is handled via signal (SIGINT/SIGTERM).
"""
import subprocess
import sys
import signal
import os
import threading
def main():
"""
Runs dtrace to find newUserClient calls, captures the user stack,
and formats the output to include PID, full path, the demangled
kernel function name, and the call stack. Includes a robust signal
handler for graceful termination.
"""
if os.getuid() != 0:
print("This script requires root privileges. Please run with sudo.", file=sys.stderr)
sys.exit(1)
dtrace_script = (
"fbt::*NewUserClient*:entry, "
"fbt::*newUserClient*:entry"
"{"
" printf(\"DTRACE_EVENT|%d|%s|%s\\n\", pid, execname, probefunc);"
" ustack();"
" printf(\"DTRACE_END_EVENT\\n\");"
"}"
)
dtrace_command = ["sudo", "dtrace", "-x", "switchrate=10hz", "-qn", dtrace_script]
# Use preexec_fn=os.setsid to run dtrace in its own process group.
# This prevents signals sent to the script from being forwarded to dtrace,
# and vice-versa, providing better isolation.
proc = subprocess.Popen(
dtrace_command,
stdout=subprocess.PIPE,
text=True,
bufsize=1,
preexec_fn=os.setsid,
errors="replace" # <-- Add this argument to handle decode errors
)
# Use a thread-safe event to ensure the signal handler logic runs only once.
stop_event = threading.Event()
def signal_handler(sig, frame):
if stop_event.is_set():
return # Already handling signal
stop_event.set()
print("\nStopping dtrace...", file=sys.stderr)
try:
# Terminate the process group started by setsid
os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
proc.wait(timeout=5)
except (ProcessLookupError, subprocess.TimeoutExpired):
try:
proc.kill() # Force kill if terminate fails
except ProcessLookupError:
pass # Process already gone
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
try:
output_iterator = iter(proc.stdout.readline, '')
for line in output_iterator:
# Exit loop if the signal handler has been triggered
if stop_event.is_set():
break
if not line.startswith("DTRACE_EVENT|"):
continue
parts = line.split('|', 3)
if len(parts) != 4:
continue
_, pid_str, execname, func_raw = parts
stack_trace = []
for stack_line in output_iterator:
if stop_event.is_set(): break
stack_line = stack_line.strip()
if stack_line == "DTRACE_END_EVENT": break
stack_trace.append(stack_line)
if stop_event.is_set(): break
path = f"{execname} (process terminated before path lookup)"
if pid_str.isdigit():
ps_proc = subprocess.run(
["/bin/ps", "-p", pid_str, "-o", "command="],
capture_output=True, text=True, check=False
)
if ps_proc.stdout.strip():
path = ps_proc.stdout.strip()
func_clean = func_raw.split(':')[0]
symbol_to_demangle = "_" + func_clean
demangled_proc = subprocess.run(
["/usr/bin/c++filt"],
input=symbol_to_demangle, capture_output=True, text=True, check=False
)
demangled_func = demangled_proc.stdout.strip()
print(f"\nPID: {pid_str}")
print(f"Path: {path}")
print(f"Function: {demangled_func}")
print("--- Call Stack---")
for stack_line in stack_trace:
print(stack_line)
print("-" * 40)
sys.stdout.flush()
finally:
if proc.poll() is None and not stop_event.is_set():
os.killpg(os.getpgid(proc.pid), signal.SIGKILL)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,153 @@
#!/usr/bin/env python3
"""
dtrace_externalMethod.py
A script to trace kernel functions related to IOConnectCallMethod using DTrace.
Prints the PID, full executable path, demangled kernel function name, and user
call stack for each event.
Requirements:
- Must be run as root (sudo).
- System Integrity Protection (SIP) may need to be disabled for full
DTrace functionality on macOS.
Usage:
1. Make the script executable:
chmod +x dtrace_externalMethod.py
2. Run with sudo:
sudo ./dtrace_externalMethod.py
3. Press CTRL+C to stop tracing gracefully.
Output:
For each relevant kernel call, prints:
- PID
- Executable path
- Demangled kernel function name
- User call stack
Graceful termination is handled via signal (SIGINT/SIGTERM).
"""
import subprocess
import sys
import signal
import os
import threading
def main():
"""
Runs dtrace to find IOConnectCallMethod related calls, captures the user stack,
and formats the output to include PID, full path, the demangled
kernel function name, and the call stack. Includes a robust signal
handler for graceful termination.
"""
if os.getuid() != 0:
print("This script requires root privileges. Please run with sudo.", file=sys.stderr)
sys.exit(1)
# DTrace script to trace kernel functions that are part of the
# IOConnectCallMethod call chain.
dtrace_script = (
"fbt::*getTargetAndMethodForIndex*:entry, "
"fbt::*externalMethod*:entry, "
"fbt::*ExternalMethod*:entry"
"{"
" printf(\"DTRACE_EVENT|%d|%s|%s\\n\", pid, execname, probefunc);"
" ustack();"
" printf(\"DTRACE_END_EVENT\\n\");"
"}"
)
dtrace_command = ["sudo", "dtrace", "-x", "switchrate=10hz", "-qn", dtrace_script]
# Use preexec_fn=os.setsid to run dtrace in its own process group.
# This prevents signals sent to the script from being forwarded to dtrace,
# and vice-versa, providing better isolation.
proc = subprocess.Popen(
dtrace_command,
stdout=subprocess.PIPE,
text=True,
bufsize=1,
preexec_fn=os.setsid,
errors="replace" # Handle decode errors gracefully
)
# Use a thread-safe event to ensure the signal handler logic runs only once.
stop_event = threading.Event()
def signal_handler(sig, frame):
if stop_event.is_set():
return # Already handling signal
stop_event.set()
print("\nStopping dtrace...", file=sys.stderr)
try:
# Terminate the process group started by setsid
os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
proc.wait(timeout=5)
except (ProcessLookupError, subprocess.TimeoutExpired):
try:
proc.kill() # Force kill if terminate fails
except ProcessLookupError:
pass # Process already gone
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
try:
output_iterator = iter(proc.stdout.readline, '')
for line in output_iterator:
# Exit loop if the signal handler has been triggered
if stop_event.is_set():
break
if not line.startswith("DTRACE_EVENT|"):
continue
parts = line.split('|', 3)
if len(parts) != 4:
continue
_, pid_str, execname, func_raw = parts
stack_trace = []
for stack_line in output_iterator:
if stop_event.is_set(): break
stack_line = stack_line.strip()
if stack_line == "DTRACE_END_EVENT": break
stack_trace.append(stack_line)
if stop_event.is_set(): break
path = f"{execname} (process terminated before path lookup)"
if pid_str.isdigit():
ps_proc = subprocess.run(
["/bin/ps", "-p", pid_str, "-o", "command="],
capture_output=True, text=True, check=False
)
if ps_proc.stdout.strip():
path = ps_proc.stdout.strip()
func_clean = func_raw.split(':')[0]
symbol_to_demangle = "_" + func_clean
demangled_proc = subprocess.run(
["/usr/bin/c++filt"],
input=symbol_to_demangle, capture_output=True, text=True, check=False
)
demangled_func = demangled_proc.stdout.strip() if demangled_proc.returncode == 0 else func_clean
print(f"\nPID: {pid_str}")
print(f"Path: {path}")
print(f"Function: {demangled_func}")
print("--- Call Stack---")
for stack_line in stack_trace:
print(stack_line)
print("-" * 40)
sys.stdout.flush()
finally:
if proc.poll() is None and not stop_event.is_set():
os.killpg(os.getpgid(proc.pid), signal.SIGKILL)
if __name__ == "__main__":
main()

View File

@@ -1,46 +1,87 @@
"""
IDA script to format and analyze IOExternalMethodDispatch structures in iOS kernelcache.
Supports both IOExternalMethodDispatch (0x18 bytes) and IOExternalMethodDispatch2022 (0x28 bytes) formats.
Usage in IDA Python console:
format_external_method_array(0xFFFFFE0007DCDBD8, 16, 1) # For old format
format_external_method_array(0xFFFFFE0007DCDBD8, 16) # For 2022 format
Structure formats:
IOExternalMethodDispatch (0x18):
0x00 - function (ptr)
0x08 - checkScalarInputCount
0x0C - checkStructureInputSize
0x10 - checkScalarOutputCount
0x14 - checkStructureOutputSize
IOExternalMethodDispatch2022 (0x28):
[all fields from IOExternalMethodDispatch]
0x18 - allowAsync
0x20 - checkEntitlement (ptr)
"""
from idaapi import * from idaapi import *
import ida_bytes import ida_bytes
import idc import idc
import ida_name import ida_name
def create_external_method_dispatch_struct(): def create_external_method_dispatch_struct(struct_type=0):
sid = idc.get_struc_id("IOExternalMethodDispatch2022") """
Creates IDA structure for IOExternalMethodDispatch.
Args:
struct_type: 0 for IOExternalMethodDispatch2022 (0x28 bytes)
1 for IOExternalMethodDispatch (0x18 bytes)
Returns:
Structure ID or -1 on failure
"""
struct_name = f"IOExternalMethodDispatch{2022 if struct_type == 0 else ''}"
sid = idc.get_struc_id(struct_name)
if sid != -1: if sid != -1:
# Structure already exists
return sid return sid
sid = idc.add_struc(-1, "IOExternalMethodDispatch2022", 0) sid = idc.add_struc(-1, struct_name, 0)
if sid == -1: if sid == -1:
print("Failed to create structure") print("Failed to create structure")
return -1 return -1
# Define structure members # Common fields for both types
idc.add_struc_member(sid, "function", 0, ida_bytes.qword_flag(), -1, 8) idc.add_struc_member(sid, "function", 0, ida_bytes.qword_flag(), -1, 8)
idc.add_struc_member(sid, "checkScalarInputCount", 8, ida_bytes.dword_flag(), -1, 4) idc.add_struc_member(sid, "checkScalarInputCount", 8, ida_bytes.dword_flag(), -1, 4)
idc.add_struc_member(sid, "checkStructureInputSize", 0xC, ida_bytes.dword_flag(), -1, 4) idc.add_struc_member(sid, "checkStructureInputSize", 0xC, ida_bytes.dword_flag(), -1, 4)
idc.add_struc_member(sid, "checkScalarOutputCount", 0x10, ida_bytes.dword_flag(), -1, 4) idc.add_struc_member(sid, "checkScalarOutputCount", 0x10, ida_bytes.dword_flag(), -1, 4)
idc.add_struc_member(sid, "checkStructureOutputSize", 0x14, ida_bytes.dword_flag(), -1, 4) idc.add_struc_member(sid, "checkStructureOutputSize", 0x14, ida_bytes.dword_flag(), -1, 4)
idc.add_struc_member(sid, "allowAsync", 0x18, ida_bytes.byte_flag(), -1, 1)
# Align to pointer size for checkEntitlement if struct_type == 0:
idc.add_struc_member(sid, "checkEntitlement", 0x20, ida_bytes.qword_flag(), -1, 8) # Type 0 (2022) specific fields
idc.add_struc_member(sid, "allowAsync", 0x18, ida_bytes.byte_flag(), -1, 1)
# Align to pointer size for checkEntitlement
idc.add_struc_member(sid, "checkEntitlement", 0x20, ida_bytes.qword_flag(), -1, 8)
return sid return sid
def format_external_method_array(start_addr, count): def format_external_method_array(start_addr, count, struct_type=0):
# Create structure if it doesn't exist """
sid = create_external_method_dispatch_struct() Formats and analyzes an array of IOExternalMethodDispatch structures.
Args:
start_addr: Start address of the methods array
count: Number of entries to process
struct_type: 0 for IOExternalMethodDispatch2022 (default)
1 for IOExternalMethodDispatch
"""
sid = create_external_method_dispatch_struct(struct_type)
if sid == -1: if sid == -1:
return return
struct_size = 0x28 # Size of IOExternalMethodDispatch2022 struct_size = 0x28 if struct_type == 0 else 0x18
# Create array
for i in range(count): for i in range(count):
current_addr = start_addr + (i * struct_size) current_addr = start_addr + (i * struct_size)
# Create structure instance # Create structure instance
idc.create_struct(current_addr, struct_size, "IOExternalMethodDispatch2022") idc.create_struct(current_addr, struct_size, f"IOExternalMethodDispatch{2022 if struct_type == 0 else ''}")
# Get function pointer and try to get its name # Get function pointer and try to get its name
func_ptr = idc.get_qword(current_addr) func_ptr = idc.get_qword(current_addr)
if func_ptr != 0: if func_ptr != 0:
@@ -50,35 +91,46 @@ def format_external_method_array(start_addr, count):
else: else:
print(f"Entry {i}: Function = 0x{func_ptr:x}") print(f"Entry {i}: Function = 0x{func_ptr:x}")
# Get other fields # Get common fields
scalar_input = idc.get_wide_dword(current_addr + 8) scalar_input = idc.get_wide_dword(current_addr + 8)
struct_input = idc.get_wide_dword(current_addr + 0xC) struct_input = idc.get_wide_dword(current_addr + 0xC)
scalar_output = idc.get_wide_dword(current_addr + 0x10) scalar_output = idc.get_wide_dword(current_addr + 0x10)
struct_output = idc.get_wide_dword(current_addr + 0x14) struct_output = idc.get_wide_dword(current_addr + 0x14)
allow_async = idc.get_wide_byte(current_addr + 0x18)
entitlement = idc.get_qword(current_addr + 0x20)
print(f" ScalarInput: {scalar_input}") print(f" ScalarInput: {scalar_input}")
print(f" StructInput: {struct_input}") print(f" StructInput: {struct_input}")
print(f" ScalarOutput: {scalar_output}") print(f" ScalarOutput: {scalar_output}")
print(f" StructOutput: {struct_output}") print(f" StructOutput: {struct_output}")
print(f" AllowAsync: {allow_async}")
if entitlement != 0: if struct_type == 0:
ent_str = idc.get_strlit_contents(entitlement, -1, STRTYPE_C) # Type 0 (2022) specific fields
if ent_str: allow_async = idc.get_wide_byte(current_addr + 0x18)
print(f" Entitlement: {ent_str.decode('utf-8')}") entitlement = idc.get_qword(current_addr + 0x20)
print(f" AllowAsync: {allow_async}")
if entitlement != 0:
ent_str = idc.get_strlit_contents(entitlement, -1, STRTYPE_C)
if ent_str:
print(f" Entitlement: {ent_str.decode('utf-8')}")
print("") print("")
def main(): def main():
if len(idc.ARGV) != 3: if len(idc.ARGV) < 3:
print("Usage: format_externalmethods.py <start_address> <count>") print("Usage: format_externalmethods.py <start_address> <count> [type]")
print("Example: format_externalmethods.py 0xFFFFFE0007E1B118 10") print("In IDA: format_external_method_array(0xFFFFFE0007DCDBD8, 16, 1)")
print("Type: 0 = IOExternalMethodDispatch2022 (0x28 bytes)")
print(" 1 = IOExternalMethodDispatch (0x18 bytes)")
return return
start_addr = int(idc.ARGV[1], 16) start_addr = int(idc.ARGV[1], 16)
count = int(idc.ARGV[2]) count = int(idc.ARGV[2])
struct_type = int(idc.ARGV[3]) if len(idc.ARGV) > 3 else 0
format_external_method_array(start_addr, count) format_external_method_array(start_addr, count, struct_type)
if __name__ == '__main__': if __name__ == '__main__':
main() main()
# format_external_method_array(0xFFFFFE0007F647A0, 10)
# format_external_method_array(0xFFFFFE0007DCDBD8, 15, 1)

View File

@@ -0,0 +1,140 @@
#!/usr/bin/env python
import lldb
import shlex
def hexdump(process, address, count):
"""
Generates a formatted hexdump of a memory region.
"""
err = lldb.SBError()
data = process.ReadMemory(address, count, err)
if not err.Success():
return "error reading memory"
lines = []
for i in range(0, len(data), 16):
chunk = data[i:i+16]
left = " ".join(f"{b:02x}" for b in chunk[0:8])
right = " ".join(f"{b:02x}" for b in chunk[8:]) if len(chunk) > 8 else ""
hex_str = left + (" " + right if right else "")
ascii_str = "".join(chr(b) if 32 <= b < 127 else "." for b in chunk)
lines.append(f"{address + i:08x} {hex_str:<48s} |{ascii_str:<16s}|")
return "\n".join(lines)
def format_row(typ, name, value):
"""
Formats a single row for the argument summary table.
"""
return f"{typ:<26}{name:<40}{value:>20}"
def iokit_dump(debugger, command, result, internal_dict):
"""
Main function for the 'iokit_dump' LLDB command.
"""
# --- Argument Parsing ---
try:
args = shlex.split(command)
dump_filename = args[0] if args else None
except Exception as e:
result.PutCString(f"Error parsing arguments: {e}")
result.SetStatus(1) # Use 1 for lldb.eReturnStatusFailed
return
# --- LLDB Object Setup with validation ---
target = debugger.GetSelectedTarget()
if not target or not target.IsValid():
result.PutCString("Error: No valid target selected.")
result.SetStatus(1) # Use 1 for lldb.eReturnStatusFailed
return
process = target.GetProcess()
if not process or not process.IsValid():
result.PutCString("Error: No valid process running.")
result.SetStatus(1) # Use 1 for lldb.eReturnStatusFailed
return
thread = process.GetSelectedThread()
if not thread or not thread.IsValid():
result.PutCString("Error: No valid thread selected.")
result.SetStatus(1) # Use 1 for lldb.eReturnStatusFailed
return
frame = thread.GetFrameAtIndex(0)
if not frame or not frame.IsValid():
result.PutCString("Error: Could not get the current stack frame.")
result.SetStatus(1) # Use 1 for lldb.eReturnStatusFailed
return
# --- Read Registers for inputStruct ---
inputStruct_ptr = frame.FindRegister("x4").GetValueAsUnsigned()
inputStructCnt = frame.FindRegister("x5").GetValueAsUnsigned()
# --- New: Dump inputStruct to a file ---
if dump_filename:
if not inputStruct_ptr or inputStructCnt == 0:
result.PutCString("No inputStruct data to dump (pointer is null or size is zero).")
result.SetStatus(0) # Use 0 for lldb.eReturnStatusSuccess
return
err = lldb.SBError()
data = process.ReadMemory(inputStruct_ptr, inputStructCnt, err)
if not err.Success():
result.PutCString(f"Error reading memory for inputStruct at {hex(inputStruct_ptr)}: {err}")
result.SetStatus(1) # Use 1 for lldb.eReturnStatusFailed
return
try:
with open(dump_filename, 'wb') as f:
f.write(data)
result.PutCString(f"Successfully dumped {inputStructCnt} bytes of inputStruct to '{dump_filename}'")
result.SetStatus(0) # Use 0 for lldb.eReturnStatusSuccess
except IOError as e:
result.PutCString(f"Error writing to file '{dump_filename}': {e}")
result.SetStatus(1) # Use 1 for lldb.eReturnStatusFailed
return
# --- Original: Print formatted argument summary ---
# Read remaining registers
connection = frame.FindRegister("x0").GetValueAsUnsigned()
selector = frame.FindRegister("x1").GetValueAsUnsigned()
input_ptr = frame.FindRegister("x2").GetValueAsUnsigned()
inputCnt = frame.FindRegister("x3").GetValueAsUnsigned()
output_ptr = frame.FindRegister("x6").GetValueAsUnsigned()
outputCnt_ptr = frame.FindRegister("x7").GetValueAsUnsigned()
outputStruct_ptr = frame.FindRegister("x8").GetValueAsUnsigned()
outputStructCnt_ptr = frame.FindRegister("x9").GetValueAsUnsigned()
lines = ["kern_return_t IOConnectCallMethod", "-------------------------------------"]
rows = [
format_row("mach_port_t", "connection:", f"{hex(connection)}"),
format_row("uint32_t", "selector:", f"{hex(selector)}"),
format_row("const uint64_t *", "input:", f"{hex(input_ptr)}"),
format_row("uint32_t", "inputCnt:", f"{hex(inputCnt)}"),
format_row("const void *", "inputStruct:", f"{hex(inputStruct_ptr)}"),
format_row("size_t", "inputStructCnt:", f"{hex(inputStructCnt)}"),
format_row("uint64_t *", "output:", f"{hex(output_ptr)}"),
format_row("uint32_t *", "outputCnt:", f"{hex(outputCnt_ptr)}"),
format_row("void *", "outputStruct:", f"{hex(outputStruct_ptr)}"),
format_row("size_t *", "outputStructCnt:", f"{hex(outputStructCnt_ptr)}"),
]
lines.extend(rows)
# Input/Output scalars and struct hexdumps...
if inputStruct_ptr and inputStructCnt > 0:
lines.append("\nInput Struct Hexdump (first 32 bytes):")
dump = hexdump(process, inputStruct_ptr, min(inputStructCnt, 32))
lines.append(dump)
# (Additional logic for other pointers as in previous script)
result.PutCString("\n".join(lines))
result.SetStatus(0) # Use 0 for lldb.eReturnStatusSuccess
def __lldb_init_module(debugger, internal_dict):
"""
Registers the 'iokit_dump' command when the script is loaded in LLDB.
"""
debugger.HandleCommand('command script add -f iokitargs.iokit_dump iokit_dump')
print("Loaded 'iokit_dump' command.")
print("Usage: iokit_dump [FILENAME]")

View File

@@ -0,0 +1,238 @@
#!/usr/bin/env python
# iokit_tracer.py (v5 - Complete Data Inspection)
import lldb
import shlex
import threading
import os
import re
# --- Shared State & Locking ---
g_lock = threading.Lock()
connection_info_map = {}
pending_connections = {}
# --- Helper Functions ---
def hexdump(process, address, count):
"""Returns a formatted hexdump string for a memory region."""
err = lldb.SBError()
# Limit the dump to a reasonable size to avoid flooding the console
count = min(count, 256)
if count == 0:
return "<empty>"
data = process.ReadMemory(address, count, err)
if not err.Success():
return f"<error reading memory at {hex(address)}: {err.GetCString()}>"
lines = []
for i in range(0, len(data), 16):
chunk = data[i:i+16]
hex_str = " ".join(f"{b:02x}" for b in chunk)
ascii_str = "".join(chr(b) if 32 <= b < 127 else "." for b in chunk)
lines.append(f" {i:04x}: {hex_str:<48} |{ascii_str}|")
return "\n".join(lines)
def resolve_service_name(frame, service_ptr):
"""Evaluates IORegistryEntryGetName to get the service name from a service object."""
if not service_ptr: return "null_service"
expr = (f'char n[128]={{0}}; extern int IORegistryEntryGetName(void*,char*); IORegistryEntryGetName((void*){service_ptr},n); n')
val = frame.EvaluateExpression(expr)
if val.IsValid() and val.GetSummary():
s = val.GetSummary().strip('"')
if s and not s.startswith('0x'): return s
return f"0x{service_ptr:x}"
def sanitize_filename(s):
return re.sub(r'[^A-Za-z0-9._-]', '_', s)
# --- LLDB Hook Functions ---
def ioserviceopen_hook(frame, bp_loc, internal_dict):
"""Hook at IOServiceOpen. Stores args in a pending dictionary for the thread."""
thread = frame.GetThread()
target = thread.GetProcess().GetTarget()
service_ptr = frame.FindRegister("x0").GetValueAsUnsigned()
connect_ptr_addr = frame.FindRegister("x3").GetValueAsUnsigned()
if service_ptr and connect_ptr_addr:
with g_lock:
pending_connections[thread.GetThreadID()] = {
"service_name": resolve_service_name(frame, service_ptr),
"connect_ptr_addr": connect_ptr_addr,
"pid": thread.GetProcess().GetProcessID(),
"exe_path": target.GetModuleAtIndex(0).GetFileSpec().fullpath or "unknown",
"type": frame.FindRegister("x2").GetValueAsUnsigned()
}
return False
corpus_dir = None
corpus_counter = 0
def ioconnectcallmethod_hook(frame, bp_loc, internal_dict):
global corpus_counter
thread = frame.GetThread()
process = thread.GetProcess()
connection = frame.FindRegister("x0").GetValueAsUnsigned()
thread_id = thread.GetThreadID()
with g_lock:
info = connection_info_map.get(connection)
if not info and thread_id in pending_connections:
pending_info = pending_connections.pop(thread_id)
err = lldb.SBError()
handle = process.ReadUnsignedFromMemory(pending_info["connect_ptr_addr"], 4, err)
if err.Success() and handle != 0:
connection_info_map[handle] = pending_info
if handle == connection: info = pending_info
if info:
# Read all argument registers
selector = frame.FindRegister("x1").GetValueAsUnsigned()
input_ptr = frame.FindRegister("x2").GetValueAsUnsigned()
inputCnt = frame.FindRegister("x3").GetValueAsUnsigned()
inputStruct_ptr = frame.FindRegister("x4").GetValueAsUnsigned()
inputStructCnt = frame.FindRegister("x5").GetValueAsUnsigned()
output_ptr = frame.FindRegister("x6").GetValueAsUnsigned()
outputCnt_ptr = frame.FindRegister("x7").GetValueAsUnsigned()
outputStruct_ptr = frame.FindRegister("x8").GetValueAsUnsigned()
outputStructCnt_ptr = frame.FindRegister("x9").GetValueAsUnsigned()
err = lldb.SBError()
# --- Read output scalar count and output struct count for filename ---
outputCnt_val = process.ReadUnsignedFromMemory(outputCnt_ptr, 4, err) if outputCnt_ptr else 0
outputCnt_str = str(outputCnt_val) if err.Success() else "err"
outputStructCnt_val = process.ReadUnsignedFromMemory(outputStructCnt_ptr, 8, err) if outputStructCnt_ptr else 0
outputStructCnt_str = str(outputStructCnt_val) if err.Success() else "err"
# --- Dump inputStruct to corpus directory if requested ---
if corpus_dir and inputStruct_ptr and inputStructCnt > 0:
try:
data = process.ReadMemory(inputStruct_ptr, inputStructCnt, err)
if err.Success():
service_name = sanitize_filename(info['service_name'])
type_val = info['type']
# Find the next available n for the filename
n = 0
while True:
fname = (
f"corpus_{service_name}_{type_val}_{selector}_"
f"{inputCnt}_{inputStructCnt}_{outputCnt_str}_{outputStructCnt_str}_{n}.bin"
)
full_path = os.path.join(corpus_dir, fname)
if not os.path.exists(full_path):
break
n += 1
with open(full_path, "wb") as f:
f.write(data)
print(f"[iokit_tracer] Dumped inputStruct ({inputStructCnt} bytes) to {full_path}")
except Exception as e:
print(f"[iokit_tracer] Error dumping inputStruct: {e}")
# Read input scalars from memory
input_scalars = []
if input_ptr and inputCnt > 0:
for i in range(inputCnt):
val = process.ReadUnsignedFromMemory(input_ptr + i*8, 8, err)
input_scalars.append(f"{hex(val)}" if err.Success() else "<read_err>")
# Read output scalars (Note: This is post-call, data may not be valid yet on entry)
# We read the *capacity* of the output buffer from the pointer.
outputCnt_val = process.ReadUnsignedFromMemory(outputCnt_ptr, 4, err) if outputCnt_ptr else 0
outputCnt_str = str(outputCnt_val) if err.Success() else "<read_err>"
outputStructCnt_val = process.ReadUnsignedFromMemory(outputStructCnt_ptr, 8, err) if outputStructCnt_ptr else 0
outputStructCnt_str = str(outputStructCnt_val) if err.Success() else "<read_err>"
# Build output
lines = [
"----------------------------------------------------------------",
f"PID: {info['pid']} | EXE: {info['exe_path']}",
f"SERVICE: {info['service_name']} (Connection: {hex(connection)}) | TYPE: {info['type']}",
f"SELECTOR: {selector} (0x{selector:x})",
"--- INPUT ---",
f"input Scalars ({inputCnt} values at {hex(input_ptr)}): [{', '.join(input_scalars)}]",
f"inputStruct ({inputStructCnt} bytes at {hex(inputStruct_ptr)}):"
]
lines.append(hexdump(process, inputStruct_ptr, inputStructCnt))
lines.append("--- OUTPUT ---")
lines.append(f"outputCnt: {outputCnt_str} (capacity pointer: {hex(outputCnt_ptr)})")
lines.append(f"outputStructCnt: {outputStructCnt_str} (capacity pointer: {hex(outputStructCnt_ptr)})")
lines.append("----------------------------------------------------------------\n")
print("\n".join(lines))
# After printing all trace info, try to get the return value from the previous frame (caller)
result_code = None
caller_frame = thread.GetFrameAtIndex(1) if thread.GetNumFrames() > 1 else None
if caller_frame:
# On macOS/ARM64, return value is in x0; on x86_64, it's rax
reg = caller_frame.FindRegister("x0") if caller_frame.FindRegister("x0").IsValid() else caller_frame.FindRegister("rax")
if reg and reg.IsValid():
result_code = reg.GetValueAsUnsigned()
# Print result code if available
if result_code is not None:
print(f"[iokit_tracer] IOConnectCallMethod return code: 0x{result_code:x} ({result_code})")
else:
print("[iokit_tracer] IOConnectCallMethod return code: <unavailable>")
return False
def trace_iokit(debugger, command, result, internal_dict):
"""Command to attach or launch and set up the IOKit tracer."""
global corpus_dir, corpus_counter
args = shlex.split(command)
pid, path, prog_args = None, None, []
corpus_dir = None
corpus_counter = 0
# --- Parse -o/--out flag ---
if "-o" in args:
idx = args.index("-o")
if idx + 1 < len(args):
corpus_dir = args[idx + 1]
del args[idx:idx+2]
elif "--out" in args:
idx = args.index("--out")
if idx + 1 < len(args):
corpus_dir = args[idx + 1]
del args[idx:idx+2]
if corpus_dir:
os.makedirs(corpus_dir, exist_ok=True)
print(f"[iokit_tracer] Will dump inputStructs to: {corpus_dir}")
if "--pid" in args: pid = args[args.index("--pid") + 1]
if "--executable_path" in args: path = args[args.index("--executable_path") + 1]
if "--" in args: prog_args = args[args.index("--") + 1:]
with g_lock: connection_info_map.clear(); pending_connections.clear()
def setup_breakpoints(target):
bp_open = target.BreakpointCreateByName("IOServiceOpen")
bp_open.SetScriptCallbackFunction(f"{__name__}.ioserviceopen_hook")
bp_open.SetAutoContinue(True)
bp_call = target.BreakpointCreateByName("IOConnectCallMethod")
bp_call.SetScriptCallbackFunction(f"{__name__}.ioconnectcallmethod_hook")
bp_call.SetAutoContinue(True)
if path:
target = debugger.CreateTargetWithFileAndArch(path, lldb.LLDB_ARCH_DEFAULT)
setup_breakpoints(target)
err = lldb.SBError()
target.Launch(debugger.GetListener(), prog_args, None, None, None, None, ".", 0, False, err)
if err.Success(): print(f"Launched '{path}'. IOKit tracer is active.")
else: result.PutCString(f"Error launching: {err.GetCString()}")
elif pid:
debugger.HandleCommand(f'process attach --pid {pid}')
target = debugger.GetSelectedTarget()
if target and target.GetProcess().IsValid():
setup_breakpoints(target)
print(f"Attached to PID {pid}. IOKit tracer is active.")
debugger.HandleCommand('continue')
else: result.PutCString(f"Error: Failed to attach to PID {pid}")
else:
# Try to use the currently selected target and process
target = debugger.GetSelectedTarget()
process = target.GetProcess() if target else None
if target and process and process.IsValid() and process.GetState() in [lldb.eStateStopped, lldb.eStateRunning]:
setup_breakpoints(target)
print("Using current LLDB target/process. IOKit tracer is active.")
else:
result.PutCString("Error: Specify --pid <PID> or --executable_path <path>, or attach to a process first.")
def __lldb_init_module(debugger, internal_dict):
"""Registers the 'trace_iokit' command with LLDB."""
debugger.HandleCommand(f'command script add -f {__name__}.trace_iokit trace_iokit')
print("Loaded IOKit tracer. Use 'trace_iokit --pid <PID>' or 'trace_iokit --executable_path <path>'")

View File

@@ -0,0 +1,69 @@
import ida_bytes
import ida_name
import idc
def print_methods(start_addr, count, struct_type=0):
"""
Print details of external methods or method templates.
Args:
start_addr: Start address of methods array
count: Number of entries to process
struct_type: 0 for IOExternalMethodDispatch2022
1 for IOExternalMethodDispatch
2 for getTargetAndMethodForIndex methodTemplate
"""
if struct_type == 0:
struct_size = 0x28
elif struct_type == 1:
struct_size = 0x18
elif struct_type == 2:
struct_size = 0x30
else:
print("Unsupported struct_type.")
return
all_methods = []
print("Methods:")
print("-" * 40)
for i in range(count):
current_addr = start_addr + (i * struct_size)
func_ptr = idc.get_qword(current_addr)
func_name = ida_name.get_name(func_ptr) if func_ptr else "Unknown"
if struct_type == 2:
allow_async = idc.get_wide_byte(current_addr + 0x8)
scalar_input = idc.get_wide_dword(current_addr + 0x10)
struct_input = idc.get_wide_dword(current_addr + 0x14)
scalar_output = idc.get_wide_dword(current_addr + 0x18)
struct_output = idc.get_wide_dword(current_addr + 0x1C)
print(f"Method {i}: {func_name}")
print(f" Async: {bool(allow_async)}")
else:
print(f"Method {i}: {func_name}")
scalar_input = idc.get_wide_dword(current_addr + 8)
struct_input = idc.get_wide_dword(current_addr + 0xC)
scalar_output = idc.get_wide_dword(current_addr + 0x10)
struct_output = idc.get_wide_dword(current_addr + 0x14)
if struct_type == 0:
allow_async = idc.get_wide_byte(current_addr + 0x18)
entitlement_ptr = idc.get_qword(current_addr + 0x20)
if entitlement_ptr:
ent_str = idc.get_strlit_contents(entitlement_ptr, -1, idc.STRTYPE_C)
if ent_str:
print(f" Entitlement: {ent_str.decode('utf-8')}")
all_methods.append(f"{i}: [{scalar_input}, {struct_input}, {scalar_output}, {struct_output}]")
print("\nMethod summary (ID: [SCALAR_IN, IN_SIZE, SCALAR_OUT, OUT_SIZE]):")
for m in all_methods:
print(m)
# Usage in IDA:
# print_methods(0xFFFFFE000835F490, 20, 2) # For getTargetAndMethodForIndex
# print_methods(0xFFFFFE0007DCDBD8, 15, 1) # For old format
# print_methods(0xFFFFFE0007F647A0, 10) # For 2022 format

View File

@@ -0,0 +1,109 @@
"""
trace_ioserviceopen.py
Trace IOServiceOpen calls in a target process using LLDB Python scripting to get Service Names and User Client types.
How to use:
1. In LLDB, import this script:
(lldb) command script import trace_ioserviceopen.py
2. Start tracing with:
(lldb) setup_ioserviceopen --executable_path EXE_PATH -- [args...]
or
(lldb) setup_ioserviceopen --pid PID
What it does:
- Sets a breakpoint on IOServiceOpen.
- On every call, prints:
- PID
- Executable path
- IOService name
- Type
- Does not stop or break execution; output is continuous and non-intrusive.
"""
import lldb
import shlex
def ioserviceopen_trace_hook(frame, bp_loc, dict):
# Get target and process objects from the current frame
target = frame.GetThread().GetProcess().GetTarget()
process = frame.GetThread().GetProcess()
pid = process.GetProcessID()
# Get the main module (executable) path
module = target.GetModuleAtIndex(0)
exe_path = module.GetFileSpec().fullpath or "unknown"
# x0 holds the IOService pointer, x2 holds the type argument
service = frame.FindRegister("x0").GetValueAsUnsigned()
type_val = frame.FindRegister("x2").GetValueAsUnsigned()
# Try to resolve the IOService name using IORegistryEntryGetName.
# This only works if the symbol is available and the process is not restricted.
name_str = f"0x{service:x}"
try:
# Evaluate an expression in the target to call IORegistryEntryGetName.
# This is safe if the symbol is present and the process allows it.
expr = (
'char name[128] = {0}; '
'extern int IORegistryEntryGetName(void*, char*); '
f'IORegistryEntryGetName((void*){service}, name); '
'name'
)
val = frame.EvaluateExpression(expr)
if val.IsValid() and val.GetSummary():
s = val.GetSummary().strip('"')
if s and not s.startswith('0x'):
name_str = s
except Exception:
# If anything fails, just show the pointer value
pass
print(f"\nPID: {pid}\nEXE PATH: {exe_path}\nSERVICE: {name_str}\nTYPE: {type_val}\n")
# Returning False tells LLDB to auto-continue without stopping at the breakpoint
return False
def setup_ioserviceopen(debugger, command, result, internal_dict):
args = shlex.split(command)
executable_path = None
pid = None
program_args = []
i = 0
# Parse command-line arguments for executable path, pid, and any program arguments
while i < len(args):
if args[i] == "--executable_path" and i + 1 < len(args):
executable_path = args[i + 1]
i += 2
elif args[i] == "--pid" and i + 1 < len(args):
pid = args[i + 1]
i += 2
elif args[i] == "--" and i + 1 < len(args):
program_args = args[i + 1:]
break
else:
i += 1
if executable_path and pid:
print("Error: Specify either --executable_path or --pid", file=result)
return
if not executable_path and not pid:
print("Error: Specify --executable_path or --pid", file=result)
return
# Set up the target and breakpoint, and ensure auto-continue is enabled
if executable_path:
debugger.HandleCommand(f'target create "{executable_path}"')
bp = debugger.GetSelectedTarget().BreakpointCreateByName("IOServiceOpen")
bp.SetScriptCallbackFunction("trace_ioserviceopen.ioserviceopen_trace_hook")
bp.SetAutoContinue(True)
debugger.HandleCommand(f'process launch -- {" ".join(shlex.quote(arg) for arg in program_args)}')
elif pid:
debugger.HandleCommand(f'process attach --pid {pid}')
bp = debugger.GetSelectedTarget().BreakpointCreateByName("IOServiceOpen")
bp.SetScriptCallbackFunction("trace_ioserviceopen.ioserviceopen_trace_hook")
bp.SetAutoContinue(True)
debugger.HandleCommand('continue')
def __lldb_init_module(debugger, internal_dict):
# Register the setup command with LLDB
debugger.HandleCommand('command script add -f trace_ioserviceopen.setup_ioserviceopen setup_ioserviceopen')

View File

@@ -0,0 +1,189 @@
"""
IDA Pro MIG Subsystem Scanner
Automatically identifies and labels Mach Interface Generator (MIG) subsystems
in XNU kernelcache, kext binaries and other Mach-based binaries.
Author: Karol Mazurek @karmaz95
Based on: https://github.com/knightsc/hopper/blob/master/scripts/MIG%20Detect.py
Usage: Run from IDA Pro's script console or via File > Script file
"""
import idc
import idautils
import idaapi
import ida_bytes
import ida_name
import ida_segment
import ida_funcs
class MIGSubsystemScanner:
"""Scanner for MIG subsystem structures in Mach kernel binaries."""
# Target segments where MIG subsystems are typically stored
TARGET_SEGMENTS = [
"__DATA:__const",
"__CONST:__constdata",
"__DATA_CONST:__const",
"__const"
]
# MIG subsystem structure offsets (arm64)
OFFSET_START = 0x08 # subsystem start ID (u32)
OFFSET_END = 0x0C # subsystem end ID (u32)
OFFSET_RESERVED = 0x18 # reserved field (u64)
OFFSET_ROUTINES = 0x20 # routine array start (u64)
# MIG routine descriptor size
ROUTINE_SIZE = 0x28 # 40 bytes per routine
ROUTINE_STUB_OFFSET = 0x08 # stub_routine pointer offset
def __init__(self):
self.found_count = 0
self.total_messages = 0
def is_valid_subsystem(self, addr):
"""
Validate potential MIG subsystem structure using heuristics.
Args:
addr: Address to check
Returns:
tuple: (is_valid, start_id, end_id) or (False, 0, 0)
"""
# Check reserved field must be 0
reserved = ida_bytes.get_qword(addr + self.OFFSET_RESERVED)
if reserved != 0:
return (False, 0, 0)
# Check first routine impl must be 0 (first entry is always NULL)
routine0_impl = ida_bytes.get_qword(addr + self.OFFSET_ROUTINES)
if routine0_impl != 0:
return (False, 0, 0)
# Read subsystem ID range
start_id = ida_bytes.get_dword(addr + self.OFFSET_START)
end_id = ida_bytes.get_dword(addr + self.OFFSET_END)
# Validate ID range
num_msgs = end_id - start_id
if start_id == 0 or num_msgs <= 0 or num_msgs >= 1024:
return (False, 0, 0)
return (True, start_id, end_id)
def label_subsystem(self, addr, start_id, end_id):
"""
Label MIG subsystem structure and its routine handlers.
Args:
addr: Address of subsystem structure
start_id: Starting message ID
end_id: Ending message ID
"""
num_msgs = end_id - start_id
subsys_name = f"MIG_subsystem_{start_id}"
print(f"[+] Found {subsys_name} at {hex(addr)}")
print(f" Message range: {start_id} - {end_id} ({num_msgs} handlers)")
# Label the subsystem structure
ida_name.set_name(addr, subsys_name, ida_name.SN_NOWARN | ida_name.SN_NOCHECK)
idc.set_cmt(addr, f"MIG Subsystem {start_id}-{end_id}", 0)
# Process each routine in the subsystem
array_base = addr + self.OFFSET_ROUTINES
labeled_count = 0
for i in range(num_msgs):
routine_addr = array_base + (i * self.ROUTINE_SIZE)
stub_ptr_addr = routine_addr + self.ROUTINE_STUB_OFFSET
stub_func_ea = ida_bytes.get_qword(stub_ptr_addr)
msg_id = start_id + i
# Skip NULL entries
if stub_func_ea == 0 or stub_func_ea == 0xFFFFFFFFFFFFFFFF:
continue
# Verify pointer points to valid code segment
if not ida_segment.getseg(stub_func_ea):
continue
# Create descriptive name for message handler
handler_name = f"MIG_msg_{msg_id}_handler"
# Label the handler function
if ida_name.set_name(stub_func_ea, handler_name, ida_name.SN_NOWARN):
labeled_count += 1
# Make sure it's treated as a function
if not ida_funcs.get_func(stub_func_ea):
ida_funcs.add_func(stub_func_ea)
# Add comment at pointer location
idc.set_cmt(stub_ptr_addr, f"Handler for MIG message {msg_id}", 0)
print(f" Labeled {labeled_count}/{num_msgs} handlers")
self.total_messages += labeled_count
self.found_count += 1
def scan_segment(self, seg_ea):
"""
Scan a single segment for MIG subsystems.
Args:
seg_ea: Segment address
"""
seg_name = idc.get_segm_name(seg_ea)
start = idc.get_segm_start(seg_ea)
end = idc.get_segm_end(seg_ea)
# Leave safety buffer for structure reads
scan_end = end - self.ROUTINE_SIZE
print(f"\n[*] Scanning {seg_name} ({hex(start)} - {hex(end)})")
# Scan with 8-byte alignment (pointer size on arm64)
for addr in range(start, scan_end, 8):
is_valid, start_id, end_id = self.is_valid_subsystem(addr)
if is_valid:
self.label_subsystem(addr, start_id, end_id)
def scan(self):
"""Main scanning routine - iterate all relevant segments."""
print("=" * 70)
print("MIG Subsystem Scanner for IDA Pro")
print("=" * 70)
# Find and scan target segments
scanned_segments = 0
for seg_ea in idautils.Segments():
seg_name = idc.get_segm_name(seg_ea)
if any(target in seg_name for target in self.TARGET_SEGMENTS):
self.scan_segment(seg_ea)
scanned_segments += 1
# Print summary
print("\n" + "=" * 70)
print(f"Scan Complete!")
print(f" Segments scanned: {scanned_segments}")
print(f" Subsystems found: {self.found_count}")
print(f" Message handlers labeled: {self.total_messages}")
print("=" * 70)
def main():
"""Entry point for the script."""
scanner = MIGSubsystemScanner()
scanner.scan()
# Refresh IDA views to show new names
idaapi.refresh_idaview_anyway()
print("\n[*] IDA views refreshed. Check Functions window for MIG_msg_* handlers")
if __name__ == "__main__":
main()

BIN
img/afine_banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 KiB