Imagine that you want to inspect the app to see what’s information exchange between mobile app and server, you can think about using simple proxy tools to sniff requests and responses or more advanced techniques such as a reverse binary file to see what are endpoints, parameters, and response payloads…

To starting SSL Pinning bypass series, this post will introduce how to leverage LLDB tools to disable SSL pinning in iOS apps and reverse engineering process.

Disclaimer

This post is for educational purposes only, please use it at your discretion and contact the app’s author if you find issues. We will inspect an app name REDACTED. The figures during the post just for demonstrations, might not relevant to REDACTED app.

Prerequisites

Below tools are used during this post:

Overview

We want to know how REDACTED app exchange info with server, so let pick up Burp Suite as proxy tool to sniff requests. Launch Burp Suite and do some necessary setup as Prerequisites section.
Observe HTTP history tab in Burp Suite while launching the app, we don’t see any requests that exchange player info or configuration, something looks wrong here. Normally when we launch the app, we should see some kind of requests to get app configuration or send player state (game apps)… An idea comes in our minds that this may be due to SSL Pinning employed in the app.
If you don’t know what is SSL Pinning, I suggest referring this detailed explanation article about how SSL Pinning works. In short, SSL Pinning is a way for the client-side to verify whether the app communicates only with the designated server itself.
When the app has SSL Pinning implemented, connect it via proxy server will not work because the client will think that proxy as a server, not a real one and it won’t establish a connection to that proxy server (due to certificate/public key of that proxy server not match with the one bundled in the app), so no requests will be made and no records will be shown in HTTP history tab.
To make app requests appear on this tab, we need to find a way to downgrade https requests to http. And when the app communicates by HTTP protocol, there no need for SSL Pinning evaluation and our Burp Suite proxy will easily Catch ’Em All. To double confirm this, let’s do some analysis. ^_^

Static Analysis

Inspect .ipa resources

With the help of Frida iOS Dump or CrackerXI, we can easily pull out .ipa file of REDACTED app on a jailbroken device, unzip .ipa and navigate to Payload/REDACTED.app folder. All of the app resources and binary files are inside this folder.
We know that there are 2 ways of doing SSL Pinning: Pin the certificate or pin the public key. Pin the certificate is the easier way of implementing SSL Pinning as the developer just needs to download the server’s certificate and bundle them in the app and at run time, the app will compare server-side certificate with the one bundled.
Normally the certificate files in iOS app will have .cer or .der extensions. This is 
DER
 encoding, you can refer this article for more details. Open Terminal and navigate to inside of REDACTED.app then run this command to search for certificate files: 
find . -name "*.cer"
MBP# find . -name "*.cer"
./redacted_test_ca.cer
./redacted.cer
./certificate.cer
./redacted_certificate.cer
./server_ranking.cer
./mqttServer.cer
Figure 1: Find certificate files
As we can see, the certificate files are bundled in the app, so we can say mostly this app has SSL Pinning implemented — pin the certificate. In case you inspect the app and don’t find any certificate files with this command, it might be the developer pinned the public key instead, you need to inspect the binary file. I will share it in another post.
That’s fine now. Let’s hunt look for the URLs that the app is using because this is the parameter that network frameworks will use to make requests like
NSURLSession
,
AlamoFire
,
AFNetworking
… just to name as a few.

Hunt for base URL (https)

We are sure that the REDACTED app is using secure https endpoints to communicate with the server because if just HTTP then it will be shown in HTTP history tab. There are some places most developers prefer to put the base URLs of the app for reuse over the entire app:
Info.plist in host app (REDACTED.app/Info.plist)Mach-O binary file in the host app (REDACTED.app/REDACTED)Info.plist or Mach-O file in the framework (the app has dedicated network frameworks, for example REDACTED.app/Frameworks/NetworkXYZ.framework/Info.plist, REDACTED.app/Frameworks/NetworkXYZ.framework/NetworkXYZ …)
By starting search https string in that order using 
strings
 command - strings looks for ASCII strings in a binary file or standard input. strings is useful for identifying random object files and many other things, we can find some outcomes in the REDACTED Mach-O file.
MBP# strings -a REDACTED | grep https
https://redacted-backend.redacted.com/
https://www.redacted.com/games/redacted/privacy
https://graph.facebook.com/
https://private-redacted-api.redacted.com/
Figure 2: Find base HTTPS endpoints
It looks like 
https://redacted-backend.redacted.com/
 and 
https://private-redacted-api.redacted.com
 are the ones suspicious. These might be the base URLs that the app will use and append with other URLs’ path to make the requests. It’s time to trace where these URLs string are used in the app. We might need the help of Hopper Disassembler.

Trace references of base URLs — Hopper Disassembler

Now let level up our skillset of static analysis with Hopper Disassembler tool — the reverse engineering tool that lets you disassemble, decompile our app. In this case, we use Hopper to disassemble Mach-O file to assembly instructions (arm64 for this case) and find down the reference of https string.
Launch Hopper Disassembler app then drag and drop our REDACTED.app/REDACTED into Hopper and wait for a while for it to disassemble. When it finishes, switch to Str tab on the left panel and search for 
https://
 string, it will show exactly results as the one we used 
strings
 command. Let click on the 
https://redacted-backend.redacted.com/
 on the left panel, it will navigate to this address on the right:
0000000101522d9d  db  "https://redacted-backend.redacted.com/", 0  ; DATA XREF=cfstring_https___redacted_backend_redacted_com_
Figure 3: Find base HTTPS endpoints in Hopper Disassembler
Double click on 
DATA XREF=cfstring_https___redacted_backend_redacted_com_
 to find when it is referenced, it would navigate to new location with showing methods referenced to this string.
cfstring_https___redacted_backend_redacted_com_:
00000001019c5170  dq  0x00000000000007c8 ; "https://redacted-backend.redacted.com/", DATA XREF=-[BackendController init]+112
Figure 4: HTTPS string references in Hopper Disassembler
We can see that there is a method using this string: 
-[BackendController init]
. If you ever developed Objective-C applications before you should be familiar with this syntax. For those who did not, let me make it short. This is objective-C class initialzers (a.k.a constructors). So in Swift language it would be same like this 
BackendController.init()
. Let double click on this 
-[BackendController init]
 to navigate to where exactly it is used.
    -[BackendController init]:
...
100037fdc  adrp       x8, #0x101f09000
100037fe0  ldr        x0, [x8, #0x3c8]            ; objc_cls_ref_BackendHttpClient,__objc_class_BackendHttpClient_class
100037fe4  adrp       x8, #0x101eb7000
100037fe8  ldr        x20, [x8, #0xb88]           ; "alloc",@selector(alloc)
100037fec  mov        x1, x20
100037ff0  bl         imp___stubs__objc_msgSend   ; objc_msgSend
100037ff4  adrp       x8, #0x101eba000                        
100037ff8  ldr        x1, [x8, #0x50]             ; "initWithBaseURL:",@selector(initWithBaseURL:)
100037ffc  adrp       x2, #0x1019c5000                            
100038000  add        x2, x2, #0x170              ; @"https://redacted-backend.redacted.com/"
100038004  bl         imp___stubs__objc_msgSend   ; objc_msgSend
100038008  mov        x21, x0
10003800c  adrp       x8, #0x101eba000            
100038010  ldr        x1, [x8, #0x58]             ; "setHttpClient:",@selector(setHttpClient:)
100038014  mov        x0, x19
100038018  mov        x2, x21
10003801c  bl         imp___stubs__objc_msgSend   ; objc_msgSend
100038020  mov        x0, x21
...
Figure 5: -[BackendController init]
Please don’t panic with these assembly instructions and give up, I promise I will explain right now what are they. Just look into it again you will see that the string is being referenced at address 
100038000
 with assembly instruction 
add x2, x2, #0x170
 and nicely generated comment 
; @"https://redacted-backend.redacted.com/"
. Please note down this address 
100038000
, we will use this when debugging the app in the next sections.

What is ARM64 instructions?

I won’t explain everything about ARM64 instructions here but I will try to explain some basic instructions for the sake of this post so that you can continue to follow through (to be honest even now I still need to learn a lot about this powerful language, I recently got addicted to this and found it like a puzzle game to play).
First, what is assembly language? Let me quote this short explanation from WikiIn computer programming, assembly language (or assembler language), often abbreviated asm, is any low-level programming language in which there is a very strong correspondence between the instructions in the language and the architecture’s machine code instructions. Because assembly depends on the machine code instructions, every assembler has its own assembly language which is designed for exactly one specific computer architecture. Let me break down there terms:
cffa edfe 0c00 0001 0000 0000 0200 0000
5c00 0000 2026 0000 8580 2100 0000 0000
1900 0000 4800 0000 5f5f 5041 4745 5a45
524f 0000 0000 0000 0000 0000 0000 0000
0000 0000 0100 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 1900 0000 0804 0000
5f5f 5445 5854 0000 0000 0000 0000 0000
0000 0000 0100 0000 00c0 9201 0000 0000
0000 0000 0000 0000 00c0 9201 0000 0000
0500 0000 0500 0000 0c00 0000 0000 0000
5f5f 7465 7874 0000 0000 0000 0000 0000
5f5f 5445 5854 0000 0000 0000 0000 0000
b455 0000 0100 0000 c484 3101 0000 0000
b455 0000 0200 0000 0000 0000 0000 0000
0004 0080 0000 0000 0000 0000 0000 0000
5f5f 7374 7562 7300 0000 0000 0000 0000
5f5f 5445 5854 0000 0000 0000 0000 0000
78da 3101 0100 0000 983a 0000 0000 0000
78da 3101 0200 0000 0000 0000 0000 0000
0804 0080 0000 0000 0c00 0000 0000 0000
...
Figure 6: Machine code in Hexadecimal form