reverse-eng-macos
Reverse macOS Mach-O binaries and .app bundles — codesign/notarization state, entitlements, Objective-C/Swift symbol recovery, and lldb/Frida dynamic analysis. Load when the target is a Mach-O executa
SKILL.mdskills/reverse-engineering/reverse-eng-macos/SKILL.md
GitHubmacOS / Mach-O reversing
When it applies
The target is a Mach-O binary, dylib, framework, or .app bundle on macOS (or a sample you can
read offline), and it is in scope (tradecraft-scope-roe, scope.txt). iOS IPAs are a different
surface — treat them with the iOS assessment flow (mobile-ios-assessment). Typical jobs:
understand a thick client, audit a persistence item (LaunchAgent/Daemon), or analyze Apple-platform
malware.
Why it works
Mach-O carries its trust model in-band: the code signature, entitlements, and load commands say
what the binary may do before you read a single instruction. Objective-C keeps class and selector
names in __objc_* sections and Swift symbols demangle to full signatures, so static recovery is
cheap — and Apple's own tools (codesign, otool, lldb, log) cover most of the workflow.
Method
- Trust posture first.
Record: signed vs ad-hoc, notarized or not, hardened runtime, library validation, and any powerful entitlements (file target.app/Contents/MacOS/target codesign -dv --verbose=4 target.app # signer, team ID, hardened-runtime flags codesign -d --entitlements :- target.app # com.apple.security.* capabilities spctl -a -vv target.app 2>&1 # Gatekeeper/notarization verdict otool -L target # linked dylibs + rpath entriesget-task-allow,disable-library-validation, TCC-related). - Static.
class-dump/dsdumpfor Objective-C headers,swift-demanglefor Swift symbols, then Hopper/Ghidra/IDA (jtool2 for deep Mach-O structure). Grep strings for XPC service names, TCC-sensitive APIs, URLs, and keys. Objective-C calls go throughobjc_msgSend— follow selector cross-references, not direct calls. - Dependencies. Audit
LC_LOAD_DYLIBand@rpathentries — a writable rpath search dir is a dylib-hijack primitive; note it for privesc/attack-path mapping. - Dynamic.
lldbfor debugging, Frida for instrumentation. Watch behavior withfs_usage -w -f filesystem <pid>andlog stream --predicate 'process == "target"'— file, network, and XPC activity show up without a single breakpoint. Proxy or hook for the network side. - Persistence & malware checks. For a suspicious sample: enumerate its LaunchAgents/Daemons
plist, install receipts, and
AuthorizationExecuteWithPrivileges/ SMJobBless usage, then confirm the behavior dynamically before calling it malicious.
Gotchas
- Universal (fat) binaries carry arm64 + x86_64 slices —
lipo -info, and analyze the slice matching your analysis host. - Hardened runtime + library validation blocks dylib injection and Frida — you need a copy
re-signed (or the target built) with
com.apple.security.cs.disable-library-validation/get-task-allow. SIP-protected platform binaries can't be attached at all. - Ad-hoc or revoked signatures change the verdict —
spctlfailing doesn't mean malicious; correlate with entitlements and behavior. - Swift UI/AppKit binaries look sparse statically — the real flow lives in closures and protocol witnesses; dynamic observation beats static here.
objc_msgSendhides call graphs in decompilers — resolve selectors via the__objc_selrefs/__objc_msgrefssections.
Verify success
You can state the signature/notarization/entitlement posture, name the entry points and the
interesting logic at symbol or address level, and back the claim with a dynamic observation
(debugger state, fs_usage/log stream trace).
References
Apple codesign/entitlement documentation; otool/jtool2 Mach-O references; Hopper & Ghidra docs.
Portions adapted from reverse-skill by zhaoxuya520, MIT License.