OPHANIM
Run an iOS app natively on macOS, then watch - or rewrite - what it does from the inside.
A runtime instrumentation and interception toolkit for iOS apps on Apple Silicon. It re-signs an .ipa, injects a runtime, hosts it as a Mac Catalyst process, and gives you a vantage point inside the target where plaintext, in-process state, and ObjC/Swift dispatch are all visible.
// what it is
A dynamic-analysis lab for iOS apps, on your Mac
Ophanim is a macOS dynamic-analysis and security-testing tool. The analyst's UI and the target app are separate processes - so the only vantage point that sees decrypted traffic, in-process state, and ObjC/Swift dispatch is code running inside the target. Ophanim's core is an injected in-process agent, and everything flows from that.
◉ Observe
Capture everything interesting an app does - with provenance and without crashing the host.
⟲ Intercept
Rewrite responses, block hosts, fake return values, defeat pinning and jailbreak checks - under a rule engine that is observe-by-default.
⌘ Script
Everything the GUI does is also available over an MCP server, so analysis can be driven programmatically.
// capabilities
What you can see and change
Every hook routes through a policy decision: observe by default, or - when a rule matches - block, replace the return/response, delay, or fault.
⇄ Network
HTTP(S) requests and decrypted response bodies, raw socket/DNS, Secure-Transport plaintext, and certificate-pinning bypass + logging.
⚷ Keychain & crypto
SecItem* access and CommonCrypto (CCCrypt / CCHmac) - observe keys, items, and crypto operations as they happen.
◎ Device & privacy
Vendor/advertising IDs, location, pasteboard, App Attest / DeviceCheck, biometrics, and camera/mic/photos/contacts prompts - observe or fake.
▤ Filesystem & process
File access and jailbreak-path probes, dlopen/fork/posix_spawn, and inter-app launches.
⌖ Custom hooks
Swizzle any ObjC (class, selector); patch overridable native-Swift methods at the vtable; or inline-hook arbitrary machine code by address, symbol, module+offset, or byte signature.
ƒ Rules & scripting
A static rule replaces the same thing every time; a JavaScriptCore rule body sees each call's context (URL, headers, body, fields) and can return a different result per call.
// the interface
Drop in an .ipa. Pick what to capture. Launch.
Ophanim's SwiftUI front-end carries a "hackery" terminal aesthetic - dark slate surfaces, phosphor-green accents, system monospace everywhere, with optional CRT theatrics. The interface elements below are rendered in that same theme.
// how it works
Three parts, one process boundary
Ophanim imports an .ipa, re-signs it, rewrites its Mach-O load commands to inject a runtime, converts it to Mac Catalyst, and runs it natively. From there the engine watches from inside.
Ophanim.app
The SwiftUI front-end. Imports/re-signs/injects .ipas, rewrites load commands, converts to Mac Catalyst, manages per-app instrumentation settings and the log viewer. Also exposes the MCP server.
Galgal.framework
The injected in-process runtime. Loads into the hosted app, provides the iPad-emulation/compatibility layer, and hosts the instrumentation engine in embedded mode.
OphanimCore
The shared instrumentation engine: hook modules, a lock-free capture ring, the rule/scripting engine, and the log sinks. Compiles into Galgal (embedded) and a standalone agent dylib (sibling).
▣ Embedded mode (default)
The engine runs inside the Galgal runtime that's already in the app. Full capture, no extra re-sign.
▥ Sibling mode
A standalone agent dylib is injected alongside the runtime via a second LC_LOAD_DYLIB (the app is re-signed). Use it when instrumentation should be independent of the runtime.
// coverage by mode
Capture coverage
The engine deliberately only interposes C symbols the runtime doesn't already own, so the two never collide - that's why keychain and raw C-level filesystem stay embedded-only.
| Category | Embedded | Sibling |
|---|---|---|
| Network (HTTP(S) bodies, socket/DNS, TLS plaintext, pinning bypass + log) | ✔ | ✔ |
Process (dlopen/fork/posix_spawn, app/URL launches) | ✔ | ✔ |
| Device / Privacy (IDs, location, pasteboard, App Attest, biometrics, media prompts) | ✔ | ✔ |
Crypto (CCCrypt / CCHmac) | ✔ | ✔ |
| Custom ObjC hooks (swizzle any class/selector) | ✔ | ✔ |
| Custom Swift hooks (native-Swift vtable patching) | ✔ | ✔ |
| Inline hooks (arm64 machine-code patch by address/symbol/offset/signature) | ✔ | ✔ |
| Rules engine + JavaScriptCore scripting + all sinks | ✔ | ✔ |
| Filesystem | ✔ full | ◑ NSFileManager |
Keychain (SecItem*) | ✔ | ✗ |
| Jailbreak / root-detector bypass + logging | ✔ | bypass only |
// interception
Observe by default. Modify on purpose.
Instrumentation must never silently change behavior you didn't ask for. Active modification is available where it's safe to act before the call returns.
Can be actively modified
- HTTP(S) responses - block / replace body + status + headers
- Device & privacy values - fake vendor/ad IDs, pasteboard,
canOpenURL - Inline-hooked functions - block / replace return / fault / delay
- ObjC & Swift hooks - block / delay (void methods, no return to replace)
Observe-only (captured after return)
- TLS read/write plaintext
- Socket / DNS
- Crypto & keychain
dlopen/fork/posix_spawn
These are captured through a lock-free ring on a drain thread - the original call has already returned by the time the event is processed.
// scripting
Drive it from an agent
Ophanim --mcp speaks the Model Context Protocol over stdio (or an HTTP port), exposing tools to list/launch apps, read & write per-app config, set rules and ObjC/Swift hooks, list and apply presets, enumerate jailbreak detectors, inspect a binary's import/symbol surface, and tail/query captured events.
# launch the MCP server over stdio $ Ophanim --mcp # then, from an MCP client / agent: list_apps # find a bundle id set_config be.target.app # enable categories + sinks set_rules be.target.app # observe-by-default, block, replace… launch_app be.target.app query_events --category network --disposition returnReplaced