mirror of
https://github.com/Karmaz95/Snake_Apple.git
synced 2026-04-09 14:42:03 +02:00
Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3442de04c | ||
|
|
54bcddbce5 | ||
|
|
190d6542d3 | ||
|
|
d4cbd1a64c | ||
|
|
ff55402c23 | ||
|
|
2b45f44b06 | ||
|
|
0c0b9ad5b9 | ||
|
|
43e98834cc | ||
|
|
46e647ad47 | ||
|
|
4d9bdde03a | ||
|
|
b3fbaacee6 | ||
|
|
05e95dcf39 | ||
|
|
d08cd41f2d | ||
|
|
ec2cfe8425 | ||
|
|
8d6a8b4c6b | ||
|
|
5a906283f3 | ||
|
|
cac76ae2aa | ||
|
|
1fda24819c | ||
|
|
1ae188683a | ||
|
|
de427b1cba | ||
|
|
deb19c3858 | ||
|
|
32ea1c4eda | ||
|
|
e6f94ef223 | ||
|
|
3473985e92 | ||
|
|
2976102984 | ||
|
|
4345a0412e | ||
|
|
29a3124b7e | ||
|
|
38cc7865bc | ||
|
|
96a0c023f0 | ||
|
|
713178663d | ||
|
|
26efd8b1b1 | ||
|
|
d5482eb959 | ||
|
|
6553126bfc | ||
|
|
58f97f589c | ||
|
|
e1cdd27c28 | ||
|
|
30d7d0e9b4 | ||
|
|
4b827afe20 | ||
|
|
2ffc0f982e | ||
|
|
2cdd37a9ff | ||
|
|
9872ec6fc4 | ||
|
|
06d77e7c09 | ||
|
|
0fd3c811db | ||
|
|
1962ab10ef | ||
|
|
3f1f2e6228 | ||
|
|
1a00625b0f | ||
|
|
8eb7589493 | ||
|
|
2e208d662c | ||
|
|
014ce2b5d5 | ||
|
|
18dfa39f42 | ||
|
|
b0439e7220 | ||
|
|
95752eefc7 | ||
|
|
0f8df62d82 | ||
|
|
bcc9f34241 | ||
|
|
ac5c9c9799 | ||
|
|
85fc5ffea3 | ||
|
|
1bca0fd124 | ||
|
|
78e70edcbb | ||
|
|
7c5d445980 | ||
|
|
fccc122ba5 | ||
|
|
0ef9bd433e | ||
|
|
58b2a53831 | ||
|
|
2d0f12c15a | ||
|
|
2fb33d88be | ||
|
|
dab7384bc8 | ||
|
|
deb421a620 |
BIN
App Bundle Extension/custom/XPC/final_secure_test_xpc.zip
Normal file
BIN
App Bundle Extension/custom/XPC/final_secure_test_xpc.zip
Normal file
Binary file not shown.
BIN
App Bundle Extension/custom/XPC/secure_test_xpc.zip
Normal file
BIN
App Bundle Extension/custom/XPC/secure_test_xpc.zip
Normal file
Binary file not shown.
99
App Bundle Extension/custom/check_bundle_exe
Executable file
99
App Bundle Extension/custom/check_bundle_exe
Executable 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
|
||||
|
||||
|
||||
149
App Bundle Extension/custom/diff_apss.sh
Normal file
149
App Bundle Extension/custom/diff_apss.sh
Normal 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
|
||||
@@ -34,7 +34,7 @@ class ASARPatcher:
|
||||
def extractASAR(self, app_path, output_path):
|
||||
'''Extracts {input_path} asar file to {output_path} directory.'''
|
||||
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:
|
||||
print(f"Extracted {input_path} to {output_path} directory.")
|
||||
@@ -44,8 +44,8 @@ class ASARPatcher:
|
||||
|
||||
def dumpEntitlements(self, app_path):
|
||||
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:
|
||||
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}")
|
||||
|
||||
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:
|
||||
return True
|
||||
else:
|
||||
@@ -62,12 +62,12 @@ class ASARPatcher:
|
||||
def packASAR(self, input_path, app_path):
|
||||
'''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.
|
||||
Codesign the
|
||||
Codesign the
|
||||
'''
|
||||
output_path = os.path.join(app_path, "Contents/Resources/app.asar")
|
||||
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:
|
||||
print(f"Packed {input_path} into {output_path}")
|
||||
|
||||
@@ -77,14 +77,14 @@ class ASARPatcher:
|
||||
new_hash = asar_calculator.calcASARHeaderHash()
|
||||
print(f"New hash: {new_hash}")
|
||||
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")
|
||||
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')
|
||||
|
||||
|
||||
print("Done!")
|
||||
|
||||
def main():
|
||||
@@ -113,4 +113,4 @@ def main():
|
||||
print("Invalid command. Use 'extract' or 'pack'.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
@@ -4,6 +4,7 @@ import sys
|
||||
import argparse
|
||||
import struct
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from threading import Lock
|
||||
import stat
|
||||
|
||||
class MachOFileFinder:
|
||||
@@ -37,6 +38,7 @@ class MachOFileFinder:
|
||||
self.directory_path = directory_path
|
||||
self.recursive = recursive
|
||||
self.only_arm64 = only_arm64
|
||||
self.print_lock = Lock()
|
||||
|
||||
def isRegularFile(self, file_path):
|
||||
"""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
|
||||
file_type = self.getMachoInfo(file_path)
|
||||
if file_type:
|
||||
print(f"{file_type}:{file_path}")
|
||||
with self.print_lock:
|
||||
print(f"{file_type}:{file_path}")
|
||||
|
||||
def processFiles(self):
|
||||
"""Walk through the directory and process files using threading for faster execution."""
|
||||
@@ -202,4 +205,4 @@ if __name__ == "__main__":
|
||||
sys.exit(1)
|
||||
|
||||
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
61
I. Mach-O/python/find_symbol.py
Executable 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
84
I. Mach-O/python/r2_dd.py
Executable 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()
|
||||
5
II. Code Signing/custom/check_cs.sh
Executable file
5
II. Code Signing/custom/check_cs.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Usage: check_cs PATH
|
||||
|
||||
codesign -dvvvv --entitlements - "$1" 2>&1
|
||||
150
IX. TCC/mac/TCC CheatSheet.md
Normal file
150
IX. TCC/mac/TCC CheatSheet.md
Normal 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 system’s 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
692
README.md
@@ -1,25 +1,37 @@
|
||||
# Snake & Apple
|
||||

|
||||
The code repository for the `Snake&Apple` article series, which documents my research about macOS security.
|
||||
[](https://karol-mazurek.medium.com/snake-apple-ff87a399ecc4?sk=v2%2Fb2295773-88e6-4654-9d3d-61d73b9001e5)
|
||||
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.
|
||||
* `custom` - code, for example, programs written for articles.
|
||||
* `python` - contains the latest CrimsonUroboros and other Python scripts created during research.
|
||||
|
||||
## ARTICLES
|
||||
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 short introduction is written in [Snake&Apple Intro](https://karol-mazurek.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 table of contents showing links to all articles is below:
|
||||
* ☑ [App Bundle Extension](https://karol-mazurek.medium.com/snake-apple-app-bundle-ext-f5c43a3c84c4?sk=v2%2F3ff105ad-f4f0-464d-b4d5-46b86c66fe14)
|
||||
* ☑ [Cracking macOS apps](https://karol-mazurek.medium.com/cracking-macos-apps-39575dd672e0?sk=v2%2F727dce55-53ee-45f6-b051-2979e62f2ba1)
|
||||
* ☑ [Cracking Electron Integrity](https://karol-mazurek.medium.com/cracking-electron-integrity-0a10e0d5f239?sk=v2%2F7726b99c-c6c9-4d70-8c37-da9f2f0874e8)
|
||||
* ☑ [XPC Programming on macOS](https://karol-mazurek.medium.com/xpc-programming-on-macos-7e1918573f6d?sk=v2%2F21c4e9c7-40a5-43dd-804b-0d8f9bc4e94c)
|
||||
* ☑ [I. Mach-O](https://karol-mazurek95.medium.com/snake-apple-i-mach-o-a8eda4b87263?sk=v2%2Ffc1cbfa4-e2d4-4387-9a82-b27191978b5b)
|
||||
* ☑ [AppleScript for Vulnerability Research](https://www.patreon.com/posts/applescript-for-130305213) `*`
|
||||
* ☑ [LLDB for Vulnerability Research](https://www.patreon.com/posts/lldb-for-131084875) `*`
|
||||
* ☑ [Scaling Vulnerability Discovery on macOS](https://www.patreon.com/posts/scaling-on-macos-131937045) `*`
|
||||
* ☑ [Applications Patch Diffing on macOS](https://www.patreon.com/posts/applications-on-131618568) `*`
|
||||
* ☑ [Threats of Unvalidated XPC Clients on macOS](https://afine.com/threats-of-unvalidated-xpc-clients-on-macos/)
|
||||
* ☑ [I. Mach-O](https://karol-mazurek.medium.com/snake-apple-i-mach-o-a8eda4b87263?sk=v2%2Ffc1cbfa4-e2d4-4387-9a82-b27191978b5b)
|
||||
* ☑ [Optimizing Mach-O Detection](https://karol-mazurek.medium.com/optimizing-mach-o-detection-40352101bbef?sk=v2%2F3378d3f5-874b-4b82-94d5-b2ccd8522ea3)
|
||||
* ☑ [II. Code Signing](https://karol-mazurek95.medium.com/snake-apple-ii-code-signing-f0a9967b7f02?sk=v2%2Fbbc87007-89ca-4135-91d6-668b5d2fe9ae)
|
||||
* ☑ [III. Checksec](https://karol-mazurek95.medium.com/snake-apple-iii-checksec-ed64a4b766c1?sk=v2%2Fb4b8d637-e906-4b6b-8088-ca1f893cd787)
|
||||
* ☑ [Static Analysis on Decompiled Code](https://www.patreon.com/posts/static-analysis-135790081) `*`
|
||||
* ☑ [II. Code Signing](https://karol-mazurek.medium.com/snake-apple-ii-code-signing-f0a9967b7f02?sk=v2%2Fbbc87007-89ca-4135-91d6-668b5d2fe9ae)
|
||||
* ☑ [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/)
|
||||
* ☑ [III. Checksec](https://karol-mazurek.medium.com/snake-apple-iii-checksec-ed64a4b766c1?sk=v2%2Fb4b8d637-e906-4b6b-8088-ca1f893cd787)
|
||||
* ☑ [IV. Dylibs](https://karol-mazurek.medium.com/snake-apple-iv-dylibs-2c955439b94e?sk=v2%2Fdef72b7a-121a-47a1-af89-7bf53aed1ea2)
|
||||
* ☑ [Breaking Hardened Runtime: The 0-Day Microsoft Delivered to macOS](https://afine.com/breaking-hardened-runtime-the-0-day-microsoft-delivered-to-macos/)
|
||||
* ☑ [Dyld Shared Cache Patch Diffing based on CVE-2025-43400](https://www.patreon.com/posts/dyld-shared-on-140770478) `*`
|
||||
* ☑ [V. Dyld](https://karol-mazurek.medium.com/snake-apple-v-dyld-8b36b674cc44?sk=v2%2F4acb16f8-fa88-41f0-8d7c-1362f4060010)
|
||||
* ☑ [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)
|
||||
* ☑ [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:
|
||||
* ☑ [Sandbox Detector](https://karol-mazurek.medium.com/sandbox-detector-4268ab3cd361?sk=v2%2F58fe49fb-1381-4db3-9db9-3f6309e4053a)
|
||||
* ☑ [Sandbox Validator](https://karol-mazurek.medium.com/sandbox-validator-e760e5d88617?sk=v2%2F145ac2ef-ca06-41a0-b310-c96f4ce0037b)
|
||||
* ☑ [App Sandbox startup](https://karol-mazurek.medium.com/app-sandbox-startup-71daf8f259d1?sk=v2%2F9f3b09a6-c7c0-445d-8613-8e25bf3f4e4d)
|
||||
* ☑ [System Intigrity Protection](https://karol-mazurek.medium.com/system-integrity-protection-sip-140562b07fea?sk=v2%2F9c293b8f-c376-4603-b8a1-2872ba3395cf)
|
||||
* ☑ [System Integrity Protection](https://karol-mazurek.medium.com/system-integrity-protection-sip-140562b07fea?sk=v2%2F9c293b8f-c376-4603-b8a1-2872ba3395cf)
|
||||
* ☑ [IX. TCC](https://karol-mazurek.medium.com/snake-apple-ix-tcc-ae822e3e2718?sk=v2%2F426ae6cf-6418-4e3f-a0ca-3aee06d6f676)
|
||||
* ☑ [Apple UUID Finder](https://karol-mazurek.medium.com/apple-uuid-finder-a5173bdd1a8a?sk=v2%2F04bb0d32-6dc9-437d-bf72-8f65e03fed90)
|
||||
* ☑ [Threat of TCC Bypasses on macOS](https://afine.com/threat-of-tcc-bypasses-on-macos/)
|
||||
* ☑ [TCC Bypass in Visual Studio Code via misconfigured Node fuses](https://afine.com/tcc-bypass-in-microsoft-visual-studio-code-via-misconfigured-node-fuses/)
|
||||
* ☑ [Reverse Engineering Apple’s TCC Daemon: When Decompiled Code Lies](https://afine.com/reverse-engineering-apples-tcc-daemon-when-decompiled-code-lies/)
|
||||
* ☑ [X. NU](https://karol-mazurek.medium.com/snake-apple-x-nu-0bc5c36170da?sk=v2%2F502ee9db-8d8a-4a1b-8655-546742a7d261)
|
||||
* ☑ [Kernel Debugging Setup on MacOS](https://karol-mazurek.medium.com/kernel-debugging-setup-on-macos-07dd8c86cdb6?sk=v2%2F782bf539-a057-4f14-bbe7-f8e1ace26701)
|
||||
* ☑ [Fixing an Infinite Loop](https://karol-mazurek.medium.com/fixing-an-infinite-loop-on-unix-e0a8a5501c54?sk=v2%2F140555f8-9770-4c6b-9734-d9c5b7cc9bc7)
|
||||
@@ -51,651 +66,24 @@ The table of contents showing links to all articles is below:
|
||||
* ☑ [MACF on macOS](https://karol-mazurek.medium.com/macf-on-macos-004b8a490e2c?sk=v2%2Fd9a61281-e230-4ac6-8608-ad062f4d2a9a)
|
||||
* ☑ [Kernel Extensions on macOS](https://karol-mazurek.medium.com/kernel-extensions-on-macos-1b0f38b632ea?sk=v2%2Fb6920735-90f9-459c-9c10-30980247bae7)
|
||||
* ☑ [Mach IPC Security on macOS](https://karol-mazurek.medium.com/mach-ipc-security-on-macos-63ee350cb59b?sk=v2%2F3afce264-9b59-447f-84ea-b1988606191a)
|
||||
* ☑ [Task Injection on macOS](https://afine.com/task-injection-on-macos/)
|
||||
* ☑ [Drivers on macOS](https://karol-mazurek.medium.com/drivers-on-macos-26edbde370ab?sk=v2%2F8a5bbc18-aae7-4a68-b0dd-bb5ce70b5752)
|
||||
* ☑ [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/)
|
||||
* ☑ [Case Study: IOMobileFramebuffer NULL Pointer Dereference](https://afine.com/case-study-iomobileframebuffer-null-pointer-dereference/)
|
||||
* ☑ [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/)
|
||||
* ☑ [Mapping IOKit Methods Exposed to User Space on macOS](https://phrack.org/issues/72/9_md#article) #PHRACK 💀
|
||||
* ☑ [SLAP & FLOP: Apple Silicon’s Data Speculation Vulnerabilities](https://afine.com/slap-flop-apple-silicons-data-speculation-vulnerabilities/)
|
||||
* ☑ [History of NULL Pointer Dereferences on macOS](https://afine.com/history-of-null-pointer-dereferences-on-macos/)
|
||||
|
||||
## TOOLS
|
||||
[CrimsonUroboros](#crimsonuroboros) • [MachOFileFinder](#machofilefinder) • [TrustCacheParser](#trustcacheparser) • [SignatureReader](#signaturereader) • [extract_cms.sh](#extract_cmssh) • [ModifyMachOFlags](#modifymachoflags) • [LCFinder](#lcfinder) • [MachODylibLoadCommandsFinder](#machodylibloadcommandsfinder) • [AMFI_test.sh](VI.%20AMFI/custom/AMFI_test.sh) • [make_plist](VIII.%20Sandbox/python/make_plist.py) • [sandbox_inspector](VIII.%20Sandbox/python/sandbox_inspector.py) • [spblp_compiler_wrapper](VIII.%20Sandbox/custom/sbpl_compiler_wrapper) • [make_bundle](#make_bundle) • [make_bundle_exe](#make_bundle_exe) • [make_dmg](#make_dmg) • [electron_patcher](#electron_patcher) • [sandbox_validator](#sandbox_validator) • [sandblaster](#sandblaster) • [sip_check](#sip_check) • [crimson_waccess.py](#crimson_waccesspy) • [sip_tester](#sip_tester) • [UUIDFinder](#uuidfinder)
|
||||
***
|
||||
* ☐ [Apple Intelligence]()
|
||||
* ☑ [AI-Enhanced Vulnerability Research](https://www.patreon.com/posts/ai-enhanced-135545364) `*`
|
||||
|
||||
### [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.
|
||||
* 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]
|
||||
## REFERENCES
|
||||
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:
|
||||
|
||||
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:
|
||||
-h, --help show this help message and exit
|
||||
* **[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!
|
||||
|
||||
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`.
|
||||
```
|
||||
|
||||
## 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.
|
||||
## Repository popularity
|
||||
[](https://star-history.com/#Karmaz95/Snake_Apple)
|
||||
|
||||
733
TOOLS.md
Normal file
733
TOOLS.md
Normal 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.
|
||||
|
||||

|
||||
|
||||
#### 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)
|
||||
```
|
||||
@@ -1,6 +1,7 @@
|
||||
_hook_iokit_check_get_property
|
||||
_hook_iokit_check_filter_properties
|
||||
_hook_vnode_notify_link
|
||||
_hook_proc_check_proc_info
|
||||
_hook_kext_check_unload
|
||||
_hook_kext_check_load
|
||||
_hook_pty_notify_close
|
||||
@@ -96,6 +97,7 @@ _hook_socket_check_create
|
||||
_hook_socket_check_connect
|
||||
_hook_socket_check_bind
|
||||
_hook_proc_check_signal
|
||||
_hook_proc_check_iopolicy
|
||||
_hook_proc_check_setauid
|
||||
_hook_proc_check_setaudit
|
||||
_hook_proc_check_sched
|
||||
@@ -138,13 +140,17 @@ _hook_mount_check_mount
|
||||
_hook_mount_check_fsctl
|
||||
_hook_mount_check_quotactl
|
||||
_hook_vnode_check_copyfile
|
||||
_hook_vnode_check_swap
|
||||
_hook_vnode_notify_unlink
|
||||
_hook_vnode_notify_swap
|
||||
_hook_proc_check_set_task_special_port
|
||||
_hook_proc_check_get_task_special_port
|
||||
_hook_vnode_notify_setflags
|
||||
_hook_vnode_notify_setextattr
|
||||
_hook_necp_check_client_action
|
||||
_hook_necp_check_open
|
||||
_hook_proc_check_set_thread_exception_port
|
||||
_hook_proc_check_set_task_exception_port
|
||||
_hook_file_check_set
|
||||
_hook_file_check_mmap
|
||||
_hook_file_check_lock
|
||||
@@ -155,4 +161,4 @@ _hook_cred_label_associate
|
||||
_hook_cred_check_label_update
|
||||
_hook_vnode_check_exec
|
||||
_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
2
VIII. Sandbox/python/crimson_waccess.py
Normal file → Executable file
@@ -20,7 +20,7 @@ class Waccess:
|
||||
elif os.path.isfile(path):
|
||||
self.check_file_write_permission(path, output_path)
|
||||
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):
|
||||
'''Check if a directory is writable by attempting to create a test file.'''
|
||||
|
||||
302
X. NU/custom/drivers/IOVerify.c
Normal file
302
X. NU/custom/drivers/IOVerify.c
Normal 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;
|
||||
}
|
||||
149
X. NU/custom/drivers/dtrace_NewUserClient.py
Executable file
149
X. NU/custom/drivers/dtrace_NewUserClient.py
Executable 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()
|
||||
153
X. NU/custom/drivers/dtrace_externalMethod.py
Executable file
153
X. NU/custom/drivers/dtrace_externalMethod.py
Executable 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()
|
||||
@@ -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 *
|
||||
import ida_bytes
|
||||
import idc
|
||||
import ida_name
|
||||
|
||||
def create_external_method_dispatch_struct():
|
||||
sid = idc.get_struc_id("IOExternalMethodDispatch2022")
|
||||
def create_external_method_dispatch_struct(struct_type=0):
|
||||
"""
|
||||
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:
|
||||
# Structure already exists
|
||||
return sid
|
||||
|
||||
sid = idc.add_struc(-1, "IOExternalMethodDispatch2022", 0)
|
||||
sid = idc.add_struc(-1, struct_name, 0)
|
||||
if sid == -1:
|
||||
print("Failed to create structure")
|
||||
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, "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, "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, "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)
|
||||
|
||||
if struct_type == 0:
|
||||
# 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
|
||||
|
||||
def format_external_method_array(start_addr, count):
|
||||
# Create structure if it doesn't exist
|
||||
sid = create_external_method_dispatch_struct()
|
||||
def format_external_method_array(start_addr, count, struct_type=0):
|
||||
"""
|
||||
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:
|
||||
return
|
||||
|
||||
struct_size = 0x28 # Size of IOExternalMethodDispatch2022
|
||||
struct_size = 0x28 if struct_type == 0 else 0x18
|
||||
|
||||
# Create array
|
||||
for i in range(count):
|
||||
current_addr = start_addr + (i * struct_size)
|
||||
|
||||
# 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
|
||||
func_ptr = idc.get_qword(current_addr)
|
||||
if func_ptr != 0:
|
||||
@@ -50,35 +91,46 @@ def format_external_method_array(start_addr, count):
|
||||
else:
|
||||
print(f"Entry {i}: Function = 0x{func_ptr:x}")
|
||||
|
||||
# Get other fields
|
||||
# Get common fields
|
||||
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)
|
||||
allow_async = idc.get_wide_byte(current_addr + 0x18)
|
||||
entitlement = idc.get_qword(current_addr + 0x20)
|
||||
|
||||
print(f" ScalarInput: {scalar_input}")
|
||||
print(f" StructInput: {struct_input}")
|
||||
print(f" ScalarOutput: {scalar_output}")
|
||||
print(f" StructOutput: {struct_output}")
|
||||
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')}")
|
||||
|
||||
if struct_type == 0:
|
||||
# Type 0 (2022) specific fields
|
||||
allow_async = idc.get_wide_byte(current_addr + 0x18)
|
||||
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("")
|
||||
|
||||
def main():
|
||||
if len(idc.ARGV) != 3:
|
||||
print("Usage: format_externalmethods.py <start_address> <count>")
|
||||
print("Example: format_externalmethods.py 0xFFFFFE0007E1B118 10")
|
||||
if len(idc.ARGV) < 3:
|
||||
print("Usage: format_externalmethods.py <start_address> <count> [type]")
|
||||
print("In IDA: format_external_method_array(0xFFFFFE0007DCDBD8, 16, 1)")
|
||||
print("Type: 0 = IOExternalMethodDispatch2022 (0x28 bytes)")
|
||||
print(" 1 = IOExternalMethodDispatch (0x18 bytes)")
|
||||
return
|
||||
|
||||
start_addr = int(idc.ARGV[1], 16)
|
||||
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__':
|
||||
main()
|
||||
|
||||
|
||||
# format_external_method_array(0xFFFFFE0007F647A0, 10)
|
||||
# format_external_method_array(0xFFFFFE0007DCDBD8, 15, 1)
|
||||
140
X. NU/custom/drivers/iokit_dump.py
Normal file
140
X. NU/custom/drivers/iokit_dump.py
Normal 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]")
|
||||
238
X. NU/custom/drivers/iokit_tracer.py
Normal file
238
X. NU/custom/drivers/iokit_tracer.py
Normal 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>'")
|
||||
69
X. NU/custom/drivers/print_externalmethods.py
Normal file
69
X. NU/custom/drivers/print_externalmethods.py
Normal 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
|
||||
109
X. NU/custom/drivers/trace_ioserviceopen.py
Normal file
109
X. NU/custom/drivers/trace_ioserviceopen.py
Normal 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')
|
||||
189
X. NU/custom/mach_ipc/ida_mig_subsystem_scanner.py
Normal file
189
X. NU/custom/mach_ipc/ida_mig_subsystem_scanner.py
Normal 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
BIN
img/afine_banner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 591 KiB |
Reference in New Issue
Block a user