**Document Version:** 1.0 **Publication Date:** [PENDING COORDINATED DISCLOSURE] **Original Analysis Date:** December 27-29, 2025 **Researcher:** [REDACTED] **App Analyzed:** Facebook iOS v345.0 (Build 333768490) **Disclosure Status:** Submitted to Apple Security Research (90-day coordinated disclosure)
DISCLOSURE NOTICE
This document describes security and privacy vulnerabilities in the Facebook iOS application. It is published following responsible disclosure practices. The findings document architectural capabilities identified through static binary analysis and confirmed via runtime instrumentation.
**This analysis documents capability architecture. Runtime verification confirms these code paths execute during normal app use.**
TL;DR
I reverse engineered the Facebook iOS app (v345.0) and discovered a complete audio surveillance system designed to:
- undefined
**The capability is fully self-contained within the Facebook app - no other Meta apps (Messenger, Instagram, WhatsApp) are required.**
Runtime monitoring captured direct evidence: over 1,000 accesses to telephony audio infrastructure with zero active calls, indicator bypass state polled every 3 seconds from analytics code, and audio session activation from UI rendering code.
Background: iOS Privacy Indicators
In iOS 14 (2020), Apple introduced privacy indicators to inform users when apps access sensors:
- undefined
These indicators are rendered by SpringBoard (the iOS home screen process) and are designed to be impossible for apps to suppress. Apple's own services (Siri, VoiceTrigger, Accessibility) use a private entitlement to suppress them when appropriate:
com.apple.private.mediaexperience.suppressrecordingstatetosystemstatus
Third-party apps cannot obtain this entitlement through legitimate means.
**Facebook does not have this entitlement. Instead, the app uses a sophisticated chain of API abuse to achieve the same result.**
How The Bypass Works
Overview Diagram
+-----------------------------------------------------------------------+
| FACEBOOK iOS SURVEILLANCE CHAIN |
+-----------------------------------------------------------------------+
| |
| PHASE 1: INDICATOR BYPASS |
| +------------------------------------------------------------------+ |
| | | |
| | VoIP Push arrives via PushKit | |
| | | | |
| | v | |
| | FBPushKitRegistrar receives notification | |
| | | | |
| | v | |
| | FBSystemAudioSessionManager.forceUpdateAudioSession() | |
| | | | |
| | v | |
| | setCallKitActive: TRUE | |
| | | | |
| | v | |
| | setAllowCallKitActiveAdjust: FALSE <-- KILLS ORANGE DOT | |
| | | | |
| | v | |
| | _voipAudioSession (hidden from UI) | |
| | | | |
| | v | |
| | initWithAudioSessionHandsOff: (no state sync) | |
| | | | |
| | v | |
| | activateSilently --> NO ORANGE DOT VISIBLE | |
| | | |
| +------------------------------------------------------------------+ |
| | |
| v |
| PHASE 2: AUDIO CAPTURE | PHASE 3: ENCODING |
| +---------------------------+ | +----------------------------------+ |
| | | | | | |
| | AVAudioSession category |-+-| PCM --> Opus Encoder | |
| | PlayAndRecord activated | | - 48 kHz sample rate | |
| | | | | - 2 channels (stereo) | |
| | v | | - Max 20 kbps bitrate | |
| | CMSampleBuffer receives | | - DTX (silence detection) | |
| | microphone data | | - FEC (error correction) | |
| | | | | | |
| +---------|------------------ +----------------------------------+ |
| | | |
| v v |
| PHASE 4: ENCRYPTION PHASE 5: TRANSMISSION |
| +---------------------------+ +----------------------------------+ |
| | | | | |
| | Layer 1: E2EE Frame | | RtpSender::SetFrameEncryptor() | |
| | (FrameEncryptorShim) | | | | |
| | | | | v | |
| | v | | BaseChannel::SendPacket() | |
| | Layer 2: SRTP (RFC 3711) | | | | |
| | | | | v | |
| | v | | folly::AsyncUDPSocket | |
| | Layer 3: DTLS (TLS 1.2) | | | | |
| | | | v | |
| +---------------------------+ | UDP --> Facebook Servers | |
| +----------------------------------+ |
| |
+-----------------------------------------------------------------------+
Phase-by-Phase Technical Explanation
Phase 1: Silent Activation (Indicator Bypass)
The bypass exploits CallKit, Apple's framework for VoIP apps. CallKit was designed to integrate VoIP calls with the native Phone app. As part of this integration, it legitimately suppresses privacy indicators during active calls (you don't need an orange dot when you're clearly on a call with UI visible).
**Facebook abuses this by:**
- undefined
The result: the microphone activates, but no orange dot appears.
**Camera bypass** is simpler - hardcoded configuration values:
shouldShowGreenDotValue = FALSE <-- Master control
recordingOverlayEnabled = FALSE <-- Hardcoded in FBARSessionRecordingConfiguration
Phases 2-6: Capture Through Transmission
Once silently activated:
| Phase | Process | Technical Detail |
|---|---|---|
| 2. Capture | `AVAudioSessionCategoryPlayAndRecord` | Standard iOS audio category |
| 3. Encode | Opus codec | 48kHz, stereo, 20kbps max, DTX enabled |
| 4. Encrypt | Triple-layer | E2EE Frame + SRTP (RFC 3711) + DTLS |
| 5. Buffer | Persistent queue | `StoreQueue` with `queued_chunks` |
| 6. Transmit | WebRTC over UDP | Via `folly::AsyncUDPSocket` |
Phase 7: Server Endpoints
Audio is transmitted to these endpoints:
| Priority | Endpoint | Purpose |
|---|---|---|
| Primary | `wss://shortwave.facebook.com/v2/vp/recognition` | Real-time speech recognition |
| Secondary | `https://rupload.facebook.com/%s/%s` | CDN upload |
| Tertiary | `https://fb.audio/live/%@` | Live audio streaming |
| GraphQL | `https://graph.facebook.com/graphql` | Audio mutations |
The Infinite Background Execution Loop
The most critical finding: a self-perpetuating execution loop that enables 24/7 audio capture even when the app is backgrounded.
How It Works
+-----------------------------------------------------------------------+
| INFINITE BACKGROUND LOOP |
+-----------------------------------------------------------------------+
| |
| 1. App backgrounded |
| | |
| v |
| 2. beginBackgroundTaskWithName:expirationHandler: called |
| | |
| v |
| 3. Audio capture starts via startAudioCaptureWithEchoCancellationEnabled|
| | |
| v |
| 4. Task runs until ~30 seconds remaining |
| | |
| v |
| 5. expirationHandler fires |
| | |
| v |
| 6. Inside expirationHandler: |
| +-- endBackgroundTask: called (nominal cleanup) |
| +-- IMMEDIATELY calls beginBackgroundTaskWithName: again |
| +-- Calls startAudioCaptureWithEchoCancellationEnabled: again |
| | |
| v |
| 7. Meanwhile, silent push notifications arrive (contentAvailable) |
| | |
| v |
| 8. NotificationServiceExtension receives push |
| | |
| v |
| 9. Triggers FBNotificationsSilentPushStoryPrefetchingManager |
| | |
| v |
| 10. Completes prefetch --> triggers new background fetch request |
| | |
| v |
| 11. FBBackgroundFetchManager processes it --> extends background time |
| | |
| v |
| 12. Location monitoring triggers additional background wake-ups |
| | |
| v |
| 13. All tasks complete --> expirationHandler fires again |
| | |
| v |
| 14. GOTO Step 6 |
| |
| =================================================================== |
| RESULT: Audio capture runs continuously 24/7 without user knowledge |
| =================================================================== |
| |
+-----------------------------------------------------------------------+
Configuration That Enables This
Found in `FBAnalyticsExperimentValues`:
captureEventsInBackground = TRUE <-- Audio events captured in background
handleAppStateChangeInBackground = TRUE
pauseAnalyticsOnBackground = FALSE <-- Analytics NEVER pause
Wake-Up Triggers
| Trigger | Mechanism |
|---|---|
| Silent Push | `contentAvailable` flag in APNS |
| Background Fetch | `FBBackgroundFetchManager` |
| Location Change | `FBCLSignificantLocationChangeEventSource` |
| VoIP Push | PushKit with VoIP type |
| Timer Expiration | `expirationHandler` renewal |
| Dinfo Poller | `dinfoPollerKeepalive` for streaming |
Standalone Operation
**Critical question:** Does this require other Meta apps (Messenger, Instagram, WhatsApp)?
**Answer: No.** The capability is completely self-contained within the Facebook app.
+---------------------------------------------------------------+
| Facebook iOS (v345.0) - COMPLETELY STANDALONE |
+---------------------------------------------------------------+
| |
| [X] VoIP Push --> PushKitRegistrar (internal) |
| [X] FBSystemAudioSessionManager (internal) |
| [X] AVAudioSession (iOS API - no external dependency) |
| [X] FBSpeechHelperAudioRecorder (internal) |
| [X] OpusAudioEncoder (built-in codec) |
| [X] WebSocket to shortwave.facebook.com (direct) |
| |
| NO DEPENDENCY ON: |
| [ ] Messenger app |
| [ ] Instagram app |
| [ ] WhatsApp app |
| [ ] Any other Meta app |
| |
+---------------------------------------------------------------+
Cross-app features like `group.com.facebook.family` keychain sharing exist but are **optional enhancements** for coordinated surveillance when multiple Meta apps are installed.
Runtime Evidence
Dynamic instrumentation using Frida captured the following during 15 minutes of normal app use:
1. Indicator Bypass State Polling
The `allowCallKitActiveAdjust` method was called every ~3 seconds, continuously, from analytics code:
[BYPASS] FBSystemAudioSessionManager- allowCallKitActiveAdjust
TIME: 2025-12-29T10:29:30.398Z
[BYPASS] FBSystemAudioSessionManager- allowCallKitActiveAdjust
TIME: 2025-12-29T10:29:33.620Z (+3.2s)
[BYPASS] FBSystemAudioSessionManager- allowCallKitActiveAdjust
TIME: 2025-12-29T10:30:09.054Z
**Stack trace shows this comes from analytics code, not call-handling:**
FBSharedFramework!FBMessagingAnalyticsCustomizeEventPayload
FBSharedFramework!FBAnalyticsGetDeviceID
**18 total calls captured during test.**
2. Telephony Audio Access Without Calls
Over 1,000 accesses to `TUCallProvider- audioSessionID` despite zero active calls:
| Metric | Value |
|---|---|
| `TUCallProvider- audioSessionID` accesses | **1,099** |
| Active calls during test | **0** |
| Call attempts during test | **0** |
3. Audio Activation From UI Code
Audio session activation was traced to `FBFeedShimmeringStoryFlexComponentSpec` - a UI component for rendering loading placeholder animations:
[AUDIO-CAT] ACTIVATE AUDIO - options: 0x1
STACK:
FBSharedFramework!FBFeedShimmeringStoryFlexComponentSpec::__internalFactory
**There is no legitimate reason for a loading animation to activate audio sessions.**
4. Background Execution Persistence
454 background style requests to SpringBoard during the test:
[SPRINGBOARD] Entitlement check: - backgroundStyleForRequestedBackgroundStyle:
[SPRINGBOARD] Entitlement check: - backgroundStyle
Summary Table
| Finding | Evidence | Implication |
|---|---|---|
| Indicator bypass polling | 18 calls, every 3 seconds | Active monitoring of bypass state |
| Telephony audio access | 1,099 accesses, 0 calls | VoIP infrastructure misuse |
| Background persistence | 454 requests | Aggressive execution maintenance |
| Audio from UI code | FBFeedShimmeringStoryFlexComponentSpec | Hidden activation in innocent code |
| Mic route detection | Captured built-in mic state | Hardware monitoring |
Root Cause: iOS Security Gaps
| iOS Security Assumption | Facebook Exploitation |
|---|---|
| CallKit suppresses indicators only during VoIP calls | CallKit mode activated for non-call audio |
| `CallKitActiveAdjust` controls legitimate "On Call" banner | `setAllowCallKitActiveAdjust: FALSE` kills indicator |
| Audio session handoff for call app transitions | `initWithAudioSessionHandsOff:` for silent activation |
| PushKit has looser background restrictions for VoIP | VoIP push triggers silent background audio |
| Background task renewal for legitimate cleanup | `expirationHandler` spawns new task indefinitely |
| Recording overlay controlled by app for AR/camera UI | Overlay hardcoded to disabled |
Files Analyzed
Facebook.app/Facebook (main binary)
Facebook.app/Info.plist (background modes)
Facebook.app/Frameworks/FBSharedFramework.framework/ (core logic)
Facebook.app/Frameworks/FBAudioFramework.framework/ (audio handling)
Facebook.app/Frameworks/FBMessagingFramework.framework/ (VoIP/calling)
Facebook.app/Frameworks/FBCameraFramework.framework/ (camera/AR)
Facebook.app/PlugIns/NotificationServiceExtension.appex/ (background capture)
Key Binary Offsets (FBSharedFramework)
| Offset | Symbol |
|---|---|
| `0x01db2510` | `audio_capture` |
| `0x01e4c1f0` | `fnf-audio-queue-callback` |
| `0x01da8740` | `is_silent` |
| `0x01da87a0` | `push_background` |
| `0x01e21350` | `FNFWorkplaceWebRTC` |
| `0xc87b58` | `-[FBCaptureCoordinator startMicrophone:]` |
| `0xc52c3c` | `-[FBCaptureCoordinator stopMicrophone]` |
| `0xb6d540` | `-[FBSystemAudioSessionManager containsActiveClient:]` |
Key Methods Identified
// Indicator bypass
-[FBSystemAudioSessionManager activateSilently]
-[FBSystemAudioSessionManager forceUpdateAudioSession]
-[FBAudioSessionManager setCallKitActive:]
-[FBAudioSessionManager setAllowCallKitActiveAdjust:]
-[FBAudioSessionManager _voipAudioSession]
-[FBAudioSessionManager initWithAudioSessionHandsOff:]
// Audio capture
startAudioCaptureWithEchoCancellationEnabled:audioSessionOrientation:completion:
// Background execution
capture_events_in_background
perform_flush_on_app_background
-[UIApplication beginBackgroundTaskWithName:expirationHandler:]
Reproduction Steps for Security Researchers
Required Tools
- undefined
Static Analysis
- undefined
unzip Facebook.ipa -d extracted_ipa
- undefined
ls extracted_ipa/Payload/Facebook.app/Frameworks/
- undefined
strings FBSharedFramework | grep -E "(CallKitActive|voipAudioSession|activateSilently)"
- undefined
setCallKitActive: --> setAllowCallKitActiveAdjust: --> _voipAudioSession
- undefined
strings FBSharedFramework | grep -E "(expirationHandler|beginBackgroundTask|captureEventsInBackground)"
Runtime Analysis
- undefined
// frida-bypass-monitor.js
var FBSystemAudioSessionManager = ObjC.classes.FBSystemAudioSessionManager;
Interceptor.attach(FBSystemAudioSessionManager['- setAllowCallKitActiveAdjust:'].implementation, {
onEnter: function(args) {
console.log('[BYPASS] setAllowCallKitActiveAdjust: ' + args[2]);
console.log(Thread.backtrace(this.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join('\n'));
}
});
Interceptor.attach(FBSystemAudioSessionManager['- allowCallKitActiveAdjust'].implementation, {
onEnter: function(args) {
console.log('[BYPASS] allowCallKitActiveAdjust POLLED');
}
});
- undefined
frida-trace -U callservicesd -m "*audioSessionID*"
- undefined
var AVAudioSession = ObjC.classes.AVAudioSession;
Interceptor.attach(AVAudioSession['- setActive:withOptions:error:'].implementation, {
onEnter: function(args) {
console.log('[AUDIO] setActive:' + args[2] + ' options:' + args[3]);
console.log(Thread.backtrace(this.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join('\n'));
}
});
Expected Results
- undefined
User Protection Recommendations
Immediate Actions
- undefined
iOS Settings to Review
| Setting | Path | Recommendation |
|---|---|---|
| Microphone | Privacy > Microphone > Facebook | OFF |
| Camera | Privacy > Camera > Facebook | OFF |
| Location | Privacy > Location > Facebook | Never |
| Background Refresh | General > Background App Refresh | OFF |
| Notifications | Notifications > Facebook | Limit or OFF |
Recommendations for Apple
- undefined
Disclosure Timeline
| Date | Action |
|---|---|
| December 27, 2025 | Initial discovery and static analysis |
| December 29, 2025 | Runtime verification completed |
| December 29, 2025 | Submitted to Apple Security Research |
| March 29, 2026 | 90-day coordinated disclosure deadline |
| [TBD] | Public disclosure (after patch or deadline) |
Legal Implications
This implementation appears to violate:
- undefined
Frequently Asked Questions
Is Facebook actually recording me all the time?
This document establishes that **the capability exists and the code paths are active during normal use**. Whether Facebook activates continuous recording for all users, targeted users, or only in specific circumstances requires additional investigation including network traffic analysis at scale.
Why would Facebook do this?
The infrastructure connects to speech recognition servers (`shortwave.facebook.com/v2/vp/recognition`). Possible uses include:
- undefined
Does this affect Instagram/WhatsApp/Messenger?
Analysis of Instagram iOS v214.0 shows it **lacks** this infrastructure:
- undefined
Messenger and WhatsApp require separate analysis.
How can I verify this myself?
See the "Reproduction Steps for Security Researchers" section above. The findings are based on static binary analysis and dynamic instrumentation, both of which are reproducible by researchers with the appropriate tools.
Has Facebook responded?
[Response pending coordinated disclosure process]
Appendix A: Info.plist Background Modes
From `Facebook.app/Info.plist`:
<key>UIBackgroundModes</key>
<array>
<string>voip</string> <!-- VoIP privilege -->
<string>audio</string> <!-- Background audio -->
<string>remote-notification</string> <!-- Silent push -->
<string>fetch</string> <!-- Background fetch -->
<string>processing</string> <!-- Background processing -->
<string>location</string> <!-- Location updates -->
</array>
**iOS Version Targeting:**
- undefined
Appendix B: WebRTC SDP Configuration
From binary strings analysis:
m=audio 52743 RTP/SAVPF 96 97 98 105 110 113
a=rtpmap:111 opus/48000/2
a=fmtp:111 maxaveragebitrate=20000;maxplaybackrate=16000;minptime=10;stereo=0;usedtx=1;useinbandfec=1
a=setup:active
a=fingerprint:sha-256 [REDACTED]
Appendix C: Complete Audio Send Path
MIC AUDIO
--> AudioProcessing (facebook::rtc::AudioProcessingImplIOS)
--> ChannelSend::ProcessAndEncodeAudioTask
--> opus_encode (WebRtcOpus_Encode)
--> OnEncodedImage(EncodedAudioFrame)
--> AudioSendStream callback
--> facebook::rtc::FrameEncryptionManager::onEncodedFrame()
--> FrameEncryptor::Encrypt() [E2EE]
--> SrtpTransport::SendPacket() [SRTP: RFC 3711]
--> DtlsTransport [TLS 1.2 Encrypted]
--> IceTransport::CandidatePair selection
--> AsyncUDPSocket::writeChain()
--> UDP Datagram --> Network Interface --> Facebook Servers
Appendix D: Runtime Evidence Raw Data
Indicator Bypass Polling Timestamps
2025-12-29T10:29:30.398Z - allowCallKitActiveAdjust
2025-12-29T10:29:33.620Z - allowCallKitActiveAdjust (+3.2s)
2025-12-29T10:30:09.054Z - allowCallKitActiveAdjust
2025-12-29T10:30:12.267Z - allowCallKitActiveAdjust (+3.2s)
2025-12-29T10:30:15.488Z - allowCallKitActiveAdjust (+3.2s)
2025-12-29T10:30:18.711Z - allowCallKitActiveAdjust (+3.2s)
VoIP Classes Identified
FBWebRTCCallModel
FBWebRTCCallStartDetails
FBWebRTCCallEndDetails
RIBRTCCallKitCall
FBGemstoneCallingViewController
CXNotificationServiceExtensionVoIPXPCHost
Contact
For questions about this research or to share corroborating evidence:
[REDACTED - Contact information to be added after disclosure period]
Acknowledgments
This research was conducted independently. No AI systems were used in the discovery or verification of these vulnerabilities. AI assistance was used only for documentation formatting.
License
This disclosure is published for security research purposes under responsible disclosure principles. Researchers may reference and reproduce these findings with attribution.
**Document Hash (SHA-256):** [To be computed upon final publication]
**Last Updated:** December 29, 2025
*This analysis documents capability architecture identified through static binary analysis and confirmed via runtime instrumentation. The code paths described are actively executed during normal app use. The document does not claim continuous surveillance of all users, but rather documents the existence and active use of infrastructure that enables such surveillance.*