Skip to content

iOS Loopback VPN Local MITM proxy

Antonio Cheong edited this page Sep 19, 2025 · 2 revisions

To do a user study on system or app services, MITM is required to record traffic, especially on a platform as restrictive as iOS. However, most existing MITM methods require sending data off device, both a privacy concern and inconvenience due to slower speed. There are also developer tools, such as Charles Proxy, Fiddler, and Proxyman. However, all of them are commercial with no way to fork & adapt for user studies.

Therefore, I decided to investigate how these local MITM apps worked and write up something similar for completely local data collection.

Referenced material:

None of the solutions found were complete, but they paint a clear architecture.

graph TD
    A[iOS App Traffic] -->|HTTPS/HTTP Requests| B[iOS Network Stack]
    B --> C[PacketTunnelProvider<br/>Network Extension]
    
    C -->|Proxy Configuration<br/>127.0.0.1:8080| D[GoMITMProxy.swift<br/>Swift Wrapper]
    D -->|CGO Bridge<br/>gomitm_* functions| E[Go MITM Library<br/>main.go]
    
    E --> F[Proxy Server<br/>proxy.go]
    F --> G[HTTP/HTTPS Handler<br/>goproxy library]
    
    G -->|CONNECT for HTTPS| H{Host Regex Match?}
    H -->|Yes - Match| I[MITM Mode<br/>TLS Termination]
    H -->|No - No Match| J[Transparent Proxy<br/>Pass Through]
    
    I --> K[Certificate Generation<br/>cert.go]
    K --> L[Dynamic TLS Certificate<br/>Per Target Host]
    L --> M[Decrypt & Inspect Traffic]
    
    M --> N[Request Logger<br/>database.go]
    N --> O[SQLite Database<br/>Captured Requests]
    
    M -->|Re-encrypt & Forward| P[Target Server]
    J -->|Direct Forward| P
    
    P -->|Response| Q[Response Handler]
    Q -->|Log Response| N
    Q -->|Back to App| A
    
    subgraph "Network Extension Process"
        C
        D
    end
    
    subgraph "Go Library (C Archive)"
        E
        F
        G
        K
        N
    end
    
    subgraph "MITM Decision Logic"
        H
        I
        J
    end
    
    subgraph "Certificate Management"
        K
        L
        R[CA Certificate Storage<br/>UserDefaults]
    end
    
    K --> R
    R --> K
    
    subgraph "Data Persistence"
        N
        O
    end
    
    style A fill:#e1f5fe
    style C fill:#f3e5f5
    style E fill:#e8f5e8
    style I fill:#fff3e0
    style N fill:#fce4ec
    style O fill:#fce4ec
Loading

Some things to note:

  • All apps on iOS are required to be proxy aware, hence the ability to use NEProxyServer rather than TCP/UDP packet tunnels.
  • Do not write the proxy server in Swift. There is a distinct lack of libraries & you'll be forced to handle a lot of parsing, certificate generation, and SNI sniffing yourself
  • Network extensions are not allowed to spawn subprocesses, so embedding mitmproxy itself is not viable. Instead use something like Rust or Golang and compile to a C archive
  • You must use App Groups to share preferences and data between the main app and network extension

Code: https://git.duti.dev/luddite.dev/LocationCollector

Clone this wiki locally