Hacktricks-skills macos-app-analysis
Use this skill whenever analyzing macOS binaries, debugging applications, performing security research on macOS apps, or fuzzing macOS software. Trigger for any macOS binary analysis, reverse engineering, crash investigation, security assessment, or when the user mentions inspecting, debugging, or testing macOS applications. Also use when investigating suspicious macOS binaries, analyzing crash reports, or performing malware analysis on macOS.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/macos-hardening/macos-security-and-privilege-escalation/macos-apps-inspecting-debugging-and-fuzzing/macos-apps-inspecting-debugging-and-fuzzing/SKILL.MDmacOS App Analysis
A comprehensive skill for inspecting, debugging, and fuzzing macOS applications and binaries.
Quick Start
# Analyze a binary analyze-macos-binary.sh /path/to/binary # Check code signature check-signature.sh /path/to/binary # Enumerate network processes enumerate-network-procs.sh # Set up fuzzing environment setup-fuzzing.sh
Static Analysis
Binary Inspection Tools
otool - Native macOS binary analysis:
otool -L /path/to/binary # List dynamically linked libraries otool -tv /path/to/binary # Disassemble application otool -ov /path/to/binary # Dump Objective-C metadata otool -l /path/to/binary # List load commands (sections)
objdump - GNU binary utilities:
objdump -m --dylibs-used /path/to/binary # List dynamically linked libraries objdump -m -h /path/to/binary # Get headers information objdump -m --syms /path/to/binary # Check symbol table objdump -m --full-contents /path/to/binary # Dump every section objdump -d /path/to/binary # Disassemble the binary objdump --disassemble-symbols=_func --x86-asm-syntax=intel /path/to/binary
nm - Symbol listing:
nm -m /path/to/binary # List of symbols nm --dyldinfo-only /path/to/binary # Dynamic linker info
Disarm (jtool2 successor)
Download from: https://newosxbook.com/tools/disarm.html
export JCOLOR=1 ARCH=arm64e disarm -c -i -I --signature /path/to/binary # Get binary info and signature ARCH=arm64e disarm -c -l /path/to/binary # Get binary sections ARCH=arm64e disarm -c -L /path/to/binary # Get binary commands (dependencies) ARCH=arm64e disarm -c -S /path/to/binary # Get symbols (func names, strings) ARCH=arm64e disarm -c -d /path/to/binary # Get disassembled code disarm -e filesets kernelcache.release.d23 # Extract filesets from kernelcache JDEBUG=1 disarm -e filesets kernelcache.release.d23 # Extract with debug info disarm -r "code signature" /path/to/binary # Check code signature disarm -e "code signature" /path/to/binary # Extract code signature
Code Signing Analysis
codesign (macOS):
# Get signer information codesign -vv -d /path/to/binary 2>&1 | grep -E "Authority|TeamIdentifier" # Verify app contents haven't been modified codesign --verify --verbose /path/to/app.app # Get entitlements from binary codesign -d --entitlements :- /path/to/binary # Check if signature is valid spctl --assess --verbose /path/to/app.app # Sign a binary codesign -s <cert-name-keychain> /path/to/binary # Remove signature (for debugging) codesign --remove-signature /path/to/binary
ldid (iOS/macOS):
ldid -h /path/to/binary # Get signature info ldid -e /path/to/binary # Get entitlements ldid -S /path/to/entitlements.xml /path/to/binary # Change entitlements
Package and Disk Image Analysis
SuspiciousPackage - Inspect .pkg installers:
- Download: https://mothersruin.com/software/SuspiciousPackage/get.html
- Check preinstall/postinstall scripts for persistence mechanisms
hdiutil - Mount disk images:
hdiutil attach ~/Downloads/file.dmg # Mounted in /Volumes/ hdiutil detach /Volumes/VolumeName
Detecting Packed Binaries
Look for these indicators:
- High entropy in binary sections
- Almost no readable strings
- UPX packer creates
section on macOS__XHDR
# Check entropy otool -l /path/to/binary | grep -A 5 "__XHDR" strings /path/to/binary | wc -l # Low string count suggests packing
Objective-C Analysis
Understanding objc_msgSend
When Objective-C methods are called, they use
objc_msgSend:
| Register | Parameter | Description |
|---|---|---|
| rdi (x64) / x0 (arm64) | self | Object receiving the message |
| rsi (x64) / x1 (arm64) | op | Method selector (name) |
| rdx (x64) / x2 (arm64) | arg1 | First method argument |
| rcx (x64) / x3 (arm64) | arg2 | Second method argument |
| r8 (x64) / x4 (arm64) | arg3 | Third method argument |
| r9 (x64) / x5 (arm64) | arg4 | Fourth method argument |
| stack | arg5+ | Additional arguments |
Dumping Objective-C Metadata
Dynadump (recommended):
./dynadump dump /path/to/binary
class-dump (legacy):
class-dump /path/to/binary > output.h
iCDump (modern, cross-platform):
import icdump metadata = icdump.objc.parse("/path/to/binary") print(metadata.to_decl())
Native tools:
nm --dyldinfo-only /path/to/binary otool -ov /path/to/binary objdump --macho --objc-meta-data /path/to/binary
Swift Analysis
Finding Swift Metadata Sections
jtool2 -l /path/to/binary | grep "__swift5" otool -l /path/to/binary | grep "__swift5"
Common Swift sections:
- Type references__swift5_typeref
- Reflection strings__swift5_reflstr
- Field metadata__swift5_fieldmd
- Capture descriptors__swift5_capture
Demangling Swift Symbols
# Using Swift CLI swift demangle "__T10MyApp10MyClassC10myMethodyyF" # Using Ghidra plugin # https://github.com/ghidraninja/ghidra_scripts/blob/master/swift_demangler.py
Dynamic Analysis
Prerequisites
Disable SIP (System Integrity Protection):
# In Recovery Mode csrutil disable # Or partial disable for debugging csrutil enable --without debug # Or without DTrace csrutil enable --without dtrace
Remove binary signature (for debugging):
codesign --remove-signature /path/to/binary
lldb - Primary Debugging Tool
# Launch debugger lldb /path/to/binary lldb -p <pid> # Attach to running process lldb -n /path/to/binary --waitfor # Wait for connection # Configure Intel syntax (create ~/.lldbinit) settings set target.x86-disassembly-flavor intel
Common lldb commands:
| Command | Description |
|---|---|
/ | Start execution |
| Stop at entry point |
/ | Continue execution |
/ | Step over instruction |
/ | Step into instruction |
/ | Run to function return |
| Pause execution |
| Set breakpoint on main |
| Regex breakpoint |
| All functions in library |
| Delete breakpoint |
/ | List breakpoints |
| Enable/disable breakpoint |
| Read all registers |
| Read specific register |
| Write to register |
| Display as string |
| Display as instruction |
| Display as bytes |
/ | Print Objective-C object |
| Read memory |
| Write memory |
| Disassemble current function |
| Disassemble function |
| Disassemble 6 lines |
| Print array |
| Print memory map |
| List library symbols |
| Dump process to core file |
Printing Objective-C method names:
# When objc_msgSend is called, rsi holds the method name (lldb) x/s $rsi (lldb) print (char*)$rsi (lldb) reg read $rsi
DTrace - Dynamic Tracing
# List available probes dtrace -l | head # Count syscalls per process sudo dtrace -n 'syscall:::entry {@[execname] = count()}' # Log syscalls for specific PID sudo dtrace -s script.d <pid> # Log file operations sudo dtrace -s b.d -c "cat /etc/hosts" # Trace with dtruss sudo dtruss -c /path/to/binary sudo dtruss -p <pid> # Trace by PID sudo dtruss -n /path/to/binary # No argument values
DTrace script example (save as
syscalls.d):
syscall::open:entry { printf("%s(%s)", probefunc, copyinstr(arg0)); } syscall::close:entry { printf("%s(%d)\n", probefunc, arg0); }
Kernel Tracing Tools
ktrace (works with SIP enabled):
ktrace trace -s -S -t c -c /path/to/binary | grep "binary("
fs_usage - File system monitoring:
fs_usage -w -f filesys ls # Track filesystem actions fs_usage -w -f network curl # Track network actions
kdebug - Kernel tracing facility:
- Trace codes:
/usr/share/misc/trace.codes - Tools:
,latency
,sc_usage
,fs_usagetrace - Note: Only one kdebug client at a time
Process Monitoring Tools
ProcessMonitor (Objective-See):
- Monitor process creation, file access, network connections
- Download: https://objective-see.com/products/utilities.html#ProcessMonitor
TaskExplorer (Objective-See):
- View libraries, files, network connections used by binary
- VirusTotal integration
- Download: https://objective-see.com/products/taskexplorer.html
FileMonitor (Objective-See):
- Monitor file creation, modifications, deletions
- Download: https://objective-see.com/products/utilities.html#FileMonitor
SpriteTree - Process relationship visualization:
# Capture process events (requires FDA) sudo eslogger fork exec rename create > cap.json # Load cap.json in SpriteTree GUI
Crescendo - Sysinternals-style monitoring:
- GUI tool for event recording
- Filter by file, process, network events
- Export to JSON
- Download: https://github.com/SuprHackerSteve/Crescendo
System Diagnostics
sysdiagnose - Comprehensive system dump:
sudo /usr/bin/sysdiagnose # Output in /tmp/
Stackshots - Process state capture:
sample <pid> <seconds> <outputfile> spindump <pid>
Unified Logs:
log show --predicate 'process == "AppName"' --last 1h log stream --predicate 'process == "AppName"'
To reveal
<private> data, install disclosure certificate (see: https://superuser.com/questions/1532031/how-to-show-private-data-in-macos-unified-log)
Anti-Debugging Detection
Common Techniques
VM Detection:
sysctl hw.model # Returns "Mac" on physical, different on VM sysctl hw.logicalcpu # CPU count checks sysctl hw.physicalcpu # Physical CPU count # VMware MAC addresses start with 00:50:56
Debugger Detection:
# Check for P_TRACED flag # Check for ptrace/PT_DENY_ATTACH usage # Status 45 (0x2d) indicates PT_DENY_ATTACH
Import Analysis:
# Check if sysctl or ptrace is imported otool -L /path/to/binary | grep -E "sysctl|ptrace" nm /path/to/binary | grep -E "sysctl|ptrace"
Core Dumps
Configuration
# Check core dump settings sysctl kern.coredump # Should be 1 sysctl kern.sugid_coredump # Should be 1 for suid binaries sysctl kern.corefile # Core file path pattern # Control core dump size ulimit -c 0 # Disable core dumps ulimit -c unlimited # Enable unlimited core dumps
Core Dump Locations
- User processes:
/cores/core.%P - Default pattern:
sysctlkern.corefile - Check:
ls -la /cores/
Fuzzing
Crash Report Analysis
ReportCrash locations:
- User apps:
~/Library/Logs/DiagnosticReports/ - System daemons:
/Library/Logs/DiagnosticReports/
Disable crash reporting:
launchctl unload -w /System/Library/LaunchAgents/com.apple.ReportCrash.plist sudo launchctl unload -w /System/Library/LaunchDaemons/com.apple.ReportCrash.Root.plist
Re-enable:
launchctl load -w /System/Library/LaunchAgents/com.apple.ReportCrash.plist sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.ReportCrash.Root.plist
Preventing Sleep During Fuzzing
# System setup sudo systemsetup -setsleep Never # pmset pmset sleep 0 # Using KeepingYouAwake # https://github.com/newmarcel/KeepingYouAwake
SSH Session Persistence
Edit
/etc/ssh/sshd_config:
TCPKeepAlive yes ClientAliveInterval 0 ClientAliveCountMax 0
sudo launchctl unload /System/Library/LaunchDaemons/ssh.plist sudo launchctl load -w /System/Library/LaunchDaemons/ssh.plist
Fuzzing Tools
AFL++ - CLI tools:
# https://github.com/AFLplusplus/AFLplusplus afl-fuzz -i input_dir -o output_dir -- /path/to/binary @@
Litefuzz - GUI apps (recommended for macOS):
# Basic usage litefuzz -l -c "/path/to/app.app/Contents/MacOS/AppName FUZZ" \ -i input_dir -o crashes_dir -n 100000 -ez # Options: # -l : Local mode # -c : Command line with FUZZ placeholder # -i : Input directory # -o : Output directory for crashes # -t : Runtime artifacts directory # -x : Timeout (default 1s) # -n : Iterations (default 1) # -e : Enable second round (reuse crashes) # -z : Enable malloc debug helpers # -p : Use pcap replay # -s : Sockets mode # -a : Address for network mode # Example: Fuzz iBooks litefuzz -l -c "/System/Applications/Books.app/Contents/MacOS/Books FUZZ" \ -i files/epub -o crashes/ibooks \ -t ~/Library/Containers/com.apple.iBooksX/Data/tmp \ -x 10 -n 100000 -ez # Example: Fuzz Font Book litefuzz -l -c "/System/Applications/Font Book.app/Contents/MacOS/Font Book FUZZ" \ -i input/fonts -o crashes/font-book -x 2 -n 500000 -ez # Example: Network fuzzing with pcap litefuzz -lk -c "smbutil view smb://localhost:4455" \ -a tcp://localhost:4455 -i input/mac-smb-resp -p -n 100000 -z
Memory Error Detection
libgmalloc - Debug malloc:
lldb -o "target create \`which binary\"" \ -o "settings set target.env-vars DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib" \ -o "run arg1 arg2" \ -o "bt" \ -o "reg read" \ -o "dis -s \$pc-32 -c 24 -m -F intel" \ -o "quit"
Network Process Enumeration
# Using dtrace dtrace -n 'syscall::recv*:entry { printf("-> %s (pid=%d)", execname, pid); }' >> recv.log sort -u recv.log > procs.txt # Using netstat netstat -an | grep LISTEN # Using lsof lsof -i -P lsof -i :<port>
Hopper Disassembler
Interface Overview
Left Panel:
- Labels: Binary symbols
- Proc: Procedures and functions
- Str: Strings from binary sections
Middle Panel:
- Raw disassembly
- Control flow graph
- Decompiled pseudocode
- Binary view
- Python console (bottom)
Right Panel:
- Navigation history
- Call graph (callers/callees)
- Local variables
Useful Actions
- Right-click code object → References to/from
- Right-click → Rename symbol
- Use Python console for custom analysis
Best Practices
- Always work on copies - Never modify original binaries
- Document findings - Keep notes on analysis steps and discoveries
- Use multiple tools - Cross-verify findings with different analyzers
- Check entitlements - Review what permissions the binary requests
- Monitor side effects - Watch file, network, and process activity
- Preserve evidence - Save crash reports, logs, and core dumps
- Test in isolated environment - Use VMs or sandboxed environments for suspicious binaries
Common Workflows
Quick Binary Analysis
# 1. Basic info file /path/to/binary otool -L /path/to/binary # 2. Check signature codesign -vv -d /path/to/binary # 3. List symbols nm /path/to/binary | head -50 # 4. Check for anti-debug strings /path/to/binary | grep -iE "ptrace|debug|vmware|virtual" # 5. Run with monitoring sudo dtruss -c /path/to/binary
Malware Analysis
# 1. Isolate in VM with SIP disabled # 2. Remove signature codesign --remove-signature /path/to/malware # 3. Static analysis ./analyze-macos-binary.sh /path/to/malware # 4. Dynamic analysis with lldb lldb /path/to/malware (lldb) process launch --stop-at-entry (lldb) breakpoint set -n main (lldb) continue # 5. Monitor activity sudo ProcessMonitor sudo FileMonitor # 6. Capture network sudo dtrace -n 'syscall::recv*:entry { printf("%s (pid=%d)\n", execname, pid); }'
Fuzzing Setup
# 1. Prepare environment sudo systemsetup -setsleep Never # Configure SSH persistence # 2. Set up input corpus mkdir -p fuzz_input # Add sample files to fuzz_input/ # 3. Run fuzzer litefuzz -l -c "/path/to/app FUZZ" -i fuzz_input -o crashes -n 1000000 -ez # 4. Monitor crashes watch -n 1 'ls -lt crashes/ | head -10'