Debugging iOS Simulator network calls (for free)
When your app’s HTTP API calls fail for the unknown reason, the last resort and often fastest to troubleshoot is to listen to actual network traffic.
Even when the code looks right, object structure matches API spec, sometimes it still fails. After long research, you may eventually learn why your request is not authenticated, even the token is not null:
Authorization: Bearer Optional(“384923840102139012390124434445354”)
Swift happens. In this article I am going to walk through available tools, and how to enable proxy debugging easily and hassle-free.
Use a proxy/network monitor
Simple and effective. Many developers choose Charles,
or BurpSuite. The former one is paid with a free trial; latter can be free, but with a limited set of features. For the proxy to work, you need to enter System Preferences and set proxy configuration:
When set properly, you should start observing all your Mac’s HTTP network traffic in proxy log window. You can inspect, replay and even intercept requests and send modified.
if you are happy with this setup, you may stop reading. Otherwise, follow along.
I do not want to pay for proxy server
Use mitmproxy then. It’s free, open-source, powerful and has the priceless charm of hackish look
I need to sniff encrypted HTTPS traffic
Proxy or any man-in-the-middle circumvents the idea of HTTPS, which besides encrypting transport, also ensures integrity and host name identity.
Every HTTPS host carries a certificate which identifies it and ensures the client it is really connecting to, for example, GitHub:
Of course, for a certificate to have any value, it has to be trusted; GitHub’s certificate is signed by the issuer, which is either certified by other issuer or is one of trusted root certificates embedded into browser or OS.
Proxy servers can only “decrypt” traffic by replacing certificates on the fly, by its own:
The invalid or expired certificate is obvious security smell, and modern operating systems and web browsers deny connecting to such host (with good reason). Our next step is to make proxy software’s certification authority recognized as valid on a device. Every product should provide instructions to do this, in a case of mitm, it is a matter of visiting mitm.it. You will be prompted to install configuration profile, and when complete, mitm becomes a legit man in the middle!
Less intrusive proxying
Okay, here was generic manual how to setup HTTPS proxy to listen to the traffic of any app on your device or simulator. Well, not exactly any app, SSL certificate pinning is the way to prevent man in the middle attacks (even the legitimate ones), but it is outside the scope.
I want to proxy just my app without all the proxy configuration hassle
One downside of general HTTPS proxying is that by setting proxy settings in network preferences of operating system you listen for all traffic, which generates a lot of noise. It is an all-or-nothing choice: either you proxy everything or disable it.
Step 1: install proxy certificate in simulator
If you want to skip the easy way of setting proxy globally, you can do this with ADVTOOLS python script. Make a clone:
git clone https://github.com/ADVTOOLS/ADVTrustStore.git
cd ADVTrustStore
Then execute a script which installs mitm certificate:
python iosCertTrustManager.py -a ~/.mitmproxy/mitmproxy-ca-cert.pem
Note: make sure you are running Python 2.x
The script will interactively ask to install certificate in each simulator version you have installed, answering yes to all should make no harm.
Step 2: Programmatically enable proxy for NSURLSession
Here you need to find a way to tap into a process of creating NSURLSessionConfiguration. If you use a framework which hides session configuration under abstraction, you have the challenge to take. Once you get to bare metal, find the connectionProxyDictionary property.
This is a dictionary of proxy properties. After some research, you would find three essential keys
kCFNetworkProxiesHTTPEnable — nonzero value enables proxy
kCFNetworkProxiesHTTPPort — NSNumber value of port
kCFNetworkProxiesHTTPProxy — NSString proxy hostname
let configuration = URLSessionConfiguration.defaultconfiguration.connectionProxyDictionary = [
kCFNetworkProxiesHTTPEnable as String: true,
kCFNetworkProxiesHTTPPort as String: 8000,
kCFNetworkProxiesHTTPProxy as String: "localhost"
]
So this is it? Not really. Read carefully above constant names and notice missing “S” by HTTP. We have 2017, App Transport Security policies are well established, and nobody should be using plain HTTP. But, no worries, there are sibling properties for HTTPS protocol, like kCFNetworkProxiesHTTPSEnable, and so on.
Let’s add all the missing S-es, and we’re done? Not really. The code will not compile, because HTTPS constants are defined only for macOS, and not present in iOS SDK.
Step 2a: hack HTTPS proxy configuration
Since constants are just convenient way to store dictionary keys, which are strings, we may cheat and use plain strings:
let configuration = URLSessionConfiguration.defaultconfiguration.connectionProxyDictionary = [
"HTTPSEnable" : 1,
"HTTPSProxy" : "localhost",
"HTTPSPort" : 8000
]
Be warned, however: this is undocumented way but working at the time of writing this article.
Step 3: Enable proxy only when needed
We do not want, of course, to use the proxy in production code. Commenting out or deleting a piece of code is also out of the question. Since we are lazy hackers, let’s create a dedicated scheme, call it “MyApp with proxy” and add environment variable when running:
The environment variable can be checked at runtime via the getEnv()
function which is a part of standard C library. Accessing bronze-age APIs from shiny new language like Swift seems awkward, but apparently is possible. Integration is pretty seamless:
if (getenv("EnableProxy") != nil) {
configuration.connectionProxyDictionary = [
"HTTPSEnable" : 1,
"HTTPSProxy" : "localhost",
"HTTPSPort" : 8000
]
}
For simplicity, we check just for existence of the variable and avoid messing with UnsafePointer
s.
Step 3a: Mind the SSL pinning
In case you have enabled SSL certificate pinning, which is a good idea when you transmit sensitive data, don’t forget to disable it programmatically if a proxy is in use.
Debugging network requests like a boss
Now your toolbox includes a simple process of debugging HTTP calls from your app. For the purpose of this tutorial, I have used mitmproxy
, mostly because its hack-ish console look and low price tag (zero US Dollars), but any other similar product will work.