A guide to understanding iOS application architecture through analysis of public APIs and frameworks, following Apple's developer guidelines and best practices.

✅

This documentation is completed and ready to use!

đŸŽ¯ Introduction

This educational guide demonstrates how to analyze iOS applications using developer tools like Frida and Objection to understand their architecture and implementation. Using Voice Memo as an example, we'll explore how iOS apps implement audio recording using public APIs and frameworks. The knowledge gained can help developers better understand iOS development patterns and best practices while staying within Apple's developer guidelines.

🛠ī¸ Required Tools

📱 Installation Guide

1. Installing Frida on iOS 📲

Add the following source to your package manager (Cydia, Sileo, etc.):

https://build.frida.re

2. Installing Frida on Your Computer đŸ’ģ

If you're using Windows, you can use WSL2 (Windows Subsystem for Linux). After setting up your Linux environment, create a Python virtual environment and install Frida tools:

pip install frida-tools

🔗 Remote Connection Setup

In the following sections, we'll cover how to set up and use Frida remotely between your iOS device and computer without requiring a USB connection - a setup that's often overlooked in most guides.

Frida automatically enables frida-server but it's set on localhost:27042. We need to change this to enable remote access from our computer.

frida-server -l <IP-Address>:27042
ℹī¸
Replace <IP-Address> with your iOS device's actual IP address. Make sure both devices are on the same network.

Then on your computer, test the connection with:

frida-ps -H <IP-Address>

đŸŽ¯ Target Application

Voice Memo App Icon

Voice Memo

Bundle ID: com.apple.VoiceMemos

Voice Memo is Apple's built-in audio recording application. For educational purposes only, we'll examine how this app implements standard iOS audio recording APIs and frameworks. This analysis focuses on understanding public APIs and documented frameworks, helping developers learn about proper audio implementation patterns on iOS. All exploration is done within the bounds of Apple's developer guidelines and terms of service.

1. Connecting with Objection 🔌

Use the following command to connect to Voice Memo:

objection --network --host <IP-Address> --gadget "com.apple.VoiceMemos" explore
ℹī¸
The Bundle ID is case sensitive. Make sure to use com.apple.VoiceMemos exactly as shown.

2. Identify Audio Recording Classes 🎤

First, we need to find the classes responsible for audio recording:

ios hooking search classes audio
💡
This search might return many results. You can narrow it down by using more specific terms like AVAudio or recorder.

🔍 Search Results in Action

Objection searching for audio classes

Demo: Objection searching for audio-related classes in the Voice Memo app

3. Analyzing State Transitions with Frida 🔄

The most interesting aspect of Voice Memo is how it manages recording states. Let's create a custom Frida script to track these state transitions in real-time:

// voice_memo_state_tracker.js
// Target the _VMAudioRecorder class
var VMAudioRecorder = ObjC.classes._VMAudioRecorder;

// Hook the setCurrentState: method
Interceptor.attach(VMAudioRecorder["- setCurrentState:"].implementation, {
    onEnter: function(args) {
        // args[0] is 'self', args[1] is 'SEL', args[2] is the new state value
        var newState = args[2].toInt32();
        console.log('[*] State Transition: ' + getStateName(newState));
    }
});

// Hook recording control methods
['startRecording', 'pauseRecording', 'stopRecording'].forEach(function(methodName) {
    Interceptor.attach(VMAudioRecorder["- " + methodName].implementation, {
        onEnter: function() {
            console.log('[*] Method called: ' + methodName);
        }
    });
});

// Helper function to convert state integers to readable names
function getStateName(stateInt) {
    var states = {
        0: 'Idle',
        1: 'Preparing',
        2: 'Recording',
        3: 'Paused',
        4: 'Stopping',
        5: 'Stopped'
    };
    return states[stateInt] || 'Unknown(' + stateInt + ')';
}

To run this script with Frida, save it as voice_memo_state_tracker.js and execute:

frida -H <IP-Address> -l voice_memo_state_tracker.js -f com.apple.VoiceMemos

4. Real-World State Transitions 📊

When running our Frida script on an actual device, we can observe the precise state transitions during a recording session:

Voice Memo State Transitions in Real-Time

Real-time output showing state transitions during Voice Memo recording

Let's break down what's happening in this recording session:

Starting a Recording

  • startRecording method is called
  • State changes to "Preparing"
  • Audio session and hardware are initialized
  • State changes to "Recording"
  • Recording confirmation occurs

Ending a Recording

  • stopRecording method is called
  • State changes to "Stopping"
  • Recording is finalized and saved
  • Resources are released
  • State returns to "Idle"

5. Tracking State Variables with Frida 🔍

Let's go deeper by examining the internal variables that change during state transitions:

// voice_memo_variable_tracker.js
var VMAudioRecorder = ObjC.classes._VMAudioRecorder;

// Map of variables to track with their corresponding ivar names
var stateVars = {
    'currentState': '_currentState',
    'targetState': '_targetState',
    'currentTime': '_currentTime',
    'currentDuration': '_currentDuration',
    'recording': '_recording',
    'paused': '_paused',
    'editing': '_editing'
};

// State name mapping
var stateNames = {
    0: 'Idle',
    1: 'Preparing',
    2: 'Recording',
    3: 'Paused',
    4: 'Stopping',
    5: 'Stopped'
};

// Hook the state change method
Interceptor.attach(VMAudioRecorder["- setCurrentState:"].implementation, {
    onEnter: function(args) {
        var newState = args[2].toInt32();
        var instance = new ObjC.Object(args[0]);
        
        console.log('
[*] State Change: ' + newState + ' (' + (stateNames[newState] || 'Unknown') + ')');
        logStateVariables(instance);
    }
});

// Log the current values of all tracked state variables
function logStateVariables(instance) {
    if (!instance || !instance.$ivars) return;
    
    for (var varName in stateVars) {
        try {
            var ivarName = stateVars[varName];
            var value = instance.$ivars[ivarName];
            
            // Format the value appropriately based on variable type
            var formattedValue;
            if (varName === 'currentState' || varName === 'targetState') {
                formattedValue = value + ' (' + (stateNames[value] || 'Unknown') + ')';
            } else if (varName === 'currentTime' || varName === 'currentDuration') {
                formattedValue = value.toFixed(2) + ' sec';
            } else if (typeof value === 'boolean') {
                formattedValue = value ? 'YES' : 'NO';
            } else {
                formattedValue = value;
            }
            
            console.log('    - ' + varName + ': ' + formattedValue);
        } catch (e) {
            console.log('    - ' + varName + ': <error>');
        }
    }
}

When we run this script on a device while interacting with Voice Memo, we can see the internal state variables updating in real-time. The visualization below demonstrates how these variables change during a complete recording cycle:

Voice Memo Variable Tracking

Console output showing variable changes during state transitions in Voice Memo

💡
Notice how the currentState and targetState values change during transitions, and how the boolean flags (recording, paused) reflect the user-facing state.

6. Conclusion: Putting It All Together 🧩

Our analysis has revealed Voice Memo's elegant state machine implementation. Let's examine the core state transitions that occur during a recording session:

Voice Memo State Transition Flow

1
Idle → Preparing

When recording begins, the app first transitions to a "Preparing" state to initialize audio hardware and set up buffers. At this point, boolean flags remain unchanged (recording = NO).

2
Preparing → Recording

Once hardware setup is complete, the app transitions to "Recording" state. This change updates the boolean flag (recording = YES) and starts capturing audio data.

3
Recording → Paused (Optional)

When paused, the app maintains its resources but stops writing to the audio buffer. The flags update to reflect this state (recording = YES, paused = YES).

4
Recording → Stopping

When recording ends, the app enters a "Stopping" state to finalize the audio file. Resources remain allocated (recording = YES) while writing completes.

5
Stopping → Idle

Finally, the app returns to "Idle" state after finalizing the recording, updating timing information (currentDuration) and releasing hardware resources.

7. Resources for Further Learning 📖

To learn more about iOS audio development, check out these resources: