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: A jailbroken device. Installing Burp’s CA Certificate in an iOS Device Configuring an iOS Device to Work With Burp Hopper Disassembler Setup LLDB environment 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 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. HTTP history If you don’t know what is SSL Pinning, I suggest referring this detailed explanation article about . In short, SSL Pinning is a way for the client-side to verify whether the app communicates only with the designated server itself. how SSL Pinning works 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 tab. HTTP history To make app requests appear on this tab, we need to find a way to downgrade requests to . 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. ^_^ https http Static Analysis Inspect .ipa resources With the help of or , we can easily pull out file of REDACTED app on a jailbroken device, unzip and navigate to Payload/REDACTED.app folder. All of the app resources and binary files are inside this folder. Frida iOS Dump CrackerXI .ipa .ipa We know that there are 2 ways of doing SSL Pinning: or . 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. Pin the certificate pin the public key Normally the certificate files in iOS app will have or extensions. This is encoding, you can refer this for more details. Open Terminal and navigate to inside of REDACTED.app then run this command to search for certificate files: .cer .der DER article 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 , , … just to name as a few. NSURLSession AlamoFire AFNetworking 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 tab. There are some places most developers prefer to put the base URLs of the app for reuse over the entire app: HTTP history 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 string in that order using command - , we can find some outcomes in the REDACTED Mach-O file. https strings strings looks for ASCII strings in a binary file or standard input. strings is useful for identifying random object files and many other things 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 and 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. https://redacted-backend.redacted.com/ https://private-redacted-api.redacted.com 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 tab on the left panel and search for string, it will show exactly results as the one we used command. Let click on the on the left panel, it will navigate to this address on the right: Str https:// strings https://redacted-backend.redacted.com/ d9d db , ; DATA XREF=cfstring_https___redacted_backend_redacted_com_ 0000000101522 "https://redacted-backend.redacted.com/" 0 Figure 3: Find base HTTPS endpoints in Hopper Disassembler Double click on to find when it is referenced, it would navigate to new location with showing methods referenced to this string. DATA XREF=cfstring_https___redacted_backend_redacted_com_ cfstring_https___redacted_backend_redacted_com_: c5170 dq ; , DATA XREF=-[BackendController init]+ 00000001019 0x00000000000007c8 "https://redacted-backend.redacted.com/" 112 Figure 4: HTTPS string references in Hopper Disassembler We can see that there is a method using this string: . 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 . Let double click on this to navigate to where exactly it is used. -[BackendController init] BackendController.init() -[BackendController init] -[BackendController init]: ... fdc adrp , f fe ldr , [ , ] fe adrp , eb fe ldr , [ , xb ] fec mov , ff bl imp___stubs__objc_msgSend ff adrp , eba ff ldr , [ , ] ffc adrp , , , bl imp___stubs__objc_msgSend mov , adrp , eba ldr , [ , ] mov , mov , bl imp___stubs__objc_msgSend mov , ... 100037 x 8 #0 x 101 09000 100037 0 x 0 x 8 #0 x 3 c 8 ; objc_cls_ref_BackendHttpClient,__objc_class_BackendHttpClient_class 100037 4 x 8 #0 x 101 7000 100037 8 x 20 x 8 #0 88 ; "alloc",@selector(alloc) 100037 x 1 x 20 100037 0 ; objc_msgSend 100037 4 x 8 #0 x 101 000 100037 8 x 1 x 8 #0 x 50 ; "initWithBaseURL:",@selector(initWithBaseURL:) 100037 x 2 #0 x 1019 c 5000 100038000 add x 2 x 2 #0 x 170 ; @"https://redacted-backend.redacted.com/" 100038004 ; objc_msgSend 100038008 x 21 x 0 10003800 c x 8 #0 x 101 000 100038010 x 1 x 8 #0 x 58 ; "setHttpClient:",@selector(setHttpClient:) 100038014 x 0 x 19 100038018 x 2 x 21 10003801 c ; objc_msgSend 100038020 x 0 x 21 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 with assembly instruction and nicely generated comment . Please note down this address , we will use this when debugging the app in the next sections. 100038000 add x2, x2, #0x170 ; @"https://redacted-backend.redacted.com/" 100038000 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 : Let me break down there terms: Wiki In 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 . : Objective-C or Swift are high-level languages. Your Objective-C or Swift code is compiled into assembly language, which is low-level. This assembly is then assembled by an assembler into machine code so that the CPU can read (0s and 1s). You can open REDACTED file in any text editor, you will see machine code instructions: Machine code instructions cffa edfe c00 c00 f5f a45 f f5f c0 c0 c00 f5f f5f b455 c484 b455 f5f f5f da a da c00 ... 0 0001 0000 0000 0200 0000 5 0000 2026 0000 8580 2100 0000 0000 1900 0000 4800 0000 5 5041 4745 5 524 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 5 5445 5854 0000 0000 0000 0000 0000 0000 0000 0100 0000 00 9201 0000 0000 0000 0000 0000 0000 00 9201 0000 0000 0500 0000 0500 0000 0 0000 0000 0000 5 7465 7874 0000 0000 0000 0000 0000 5 5445 5854 0000 0000 0000 0000 0000 0000 0100 0000 3101 0000 0000 0000 0200 0000 0000 0000 0000 0000 0004 0080 0000 0000 0000 0000 0000 0000 5 7374 7562 7300 0000 0000 0000 0000 5 5445 5854 0000 0000 0000 0000 0000 78 3101 0100 0000 983 0000 0000 0000 78 3101 0200 0000 0000 0000 0000 0000 0804 0080 0000 0000 0 0000 0000 0000 Figure 6: Machine code in Hexadecimal form : It’s CPU Architecture, for ex. (is the current 64-bit ARM CPU architecture, as used since the iPhone 5S and later), (being used in Apple’s A6 and A6X chips on iPhone 5, iPhone 5C and iPad 4), (32-bit ARM CPU, as used in the A5 and earlier), (64-bit Intel - Simulator), (32-bit Intel - Simulator) iOS devices use CPU based on . In my opinion, this is easy to read compare to instructions set. Computer architecture arm64 armv7s armv7 x86_64 i386 ARM architecture x86_64 : There is series of levels of memory. Those are close to the CPU are fastest. In a modern computer, these are typically something like: CPU registers > L1 cache > L2 cache > L3 cache > Main memory > HDD. The processor will use some internal memory storage locations, a.k.a registers. For ARM64, there are 32 general purpose registers and some special registers, please have a look below for more details and when it can be used. Registers Figure 7: Registers Figure 8: Registers purpose : There are many commands (operation code — opcode), it’s a single instruction that can be executed by the CPU. The general format can be considered as the , followed by the : where is a command (MOV, SUB, ADD…), is destination register, is the register that is operated on, might be a register or immediate value (for ex. #0xff). Assembly instruction instruction operands Instruction Rd, Rn, Operand2 Instruction Rd Rn Operand2 I think that would be enough, for now, I will explain more when we reach those instructions in the next section, debug app with LLDB. Dynamic Analysis LLDB is fully integrated with XCode since XCode 5, so if you are an iOS developer you’ve already get familiar with it as a daily job. We can attach to the running process from XCode or Terminal, for this post I will run LLDB from the terminal and attach to running app on the jailbroken device. Assume you already setup debugserver on jailbroken device in section. Prerequisites Start debugserver Let SSH to the jailbroken device, for my case, it would be like this: , for your case it would be . You might need to key in when password prompts (default password is alpine). If you would like to debug on jailbroken device often, you can create an SSH shortcut in your file like this (more details you can reference this for how to setup) then you can SSH via shortcut instead. You also can if you manage to SSH more often. ssh ipse_home ssh root@your_device_ip_address ~/.ssh/config post SSH Passwordless login using SSH Keygen Host ipse_home HostName 192.168.1.113 root User Figure 9: SSH Shortcut After SSH into the jailbroken device, we will use to attach to the app and listen for connections from other machines then from our laptop we will launch to connect to for remote debugging. Let’s do step by step. debugserver LLDB debugserver From Terminal SSH into the device, then run this command: debugserver 0.0.0.0:1234 -w "Executable file" is the process name we want to attach (this value you need to get from file). Executable file Info.plist is the IP range allows for other connections to connect (if you know the IP address of remote debugging machine then please specify that for secure). 0.0.0.0 is port number (you can define any if you want). 1234 argument is abbreviation of which will wait for process to launch (if not launched yet) or attach immediately if it’s been already launched. -w --waitfor iPhone-SE:~ root# debugserver : -w REDACTED debugserver-@(#)PROGRAM:LLDB PROJECT:lldb . arm64. Waiting to attach to process REDACTED... 0.0 .0 .0 1234 -900.3 .57 .2 for Figure 10: Start debugserver Let assume you already configured the jailbroken device using Burp Suite proxy, the app was not started yet then start above command. Whenever you see the output , it’s time to launch our app from the jailbroken device and you can see from Terminal it will print out new message which means that it attached successfully and waiting for a connection to debug. debugserver Waiting to attach to process Listening to port 1234 for a connection from 0.0.0.0... iPhone-SE:~ root# debugserver 0.0.0.0:1234 -w REDACTED debugserver-@(#)PROGRAM:LLDB PROJECT:lldb-900.3.57 2 arm64. Waiting attach process REDACTED . Listening 1234 a 0.0.0.0 . .. for to to .. to port for connection from .. Figure 11: debugserver attached process Launch LLDB We will use LLDB as a standalone debugger for this post instead of LLDB debugger through XCode debugging feature (XCode console pane). So let have some fun with LLDB command line, why not? Now let the fun begin. Open another tab of Terminal and run lldb command. MBP# lldb ( ) lldb Figure 12: Start LLDB standalone From lldb prompt, type then to attach into our app (process) via (just to remember is the IP address of the jailbroken device). If we can attach to the process successfully, you will see this log in Terminal: platform select remote-ios process connect connect://192.168.1.113:1234 debugserver 192.168.1.113 (lldb) platform select remote-ios Platform: remote-ios Connected: no SDK Path: SDK Roots: [ ] SDK Roots: [ ] SDK Roots: [ ] SDK Roots: [ ] Process stopped * thread # , queue = , stop reason = signal SIGSTOP frame # : libsystem_c.dylib`strlen + libsystem_c.dylib`strlen: -> <+ >: ldr q0, [x1, # ]! <+ >: uminv b b1, v0 <+ >: fmov w2, s1 <+ >: cbnz w2, ; <+ > Target : (REDACTED) stopped. "/Users/xyz/Library/Developer/Xcode/iOS DeviceSupport/13.3.1 (17D50) arm64e" 0 "/Users/xyz/Library/Developer/Xcode/iOS DeviceSupport/13.3.1 (17D50) arm64e" 1 "/Users/xyz/Library/Developer/Xcode/iOS DeviceSupport/11.3.1 (15E302)" 2 "/Users/xyz/Library/Developer/Xcode/iOS DeviceSupport/11.0 (15A372)" 3 "/Users/xyz/Library/Developer/Xcode/iOS DeviceSupport/12.4 (16G77)" 19596 1 'com.apple.main-thread' 0 0x000000018104e3b0 48 0x18104e3b0 48 0x10 0x18104e3b4 52 .16 0x18104e3b8 56 0x18104e3bc 60 0x18104e3b0 48 0 Figure 13: LLDB attached to process In case shows the SDK Path error or something, you might double-check if your device iOS version exists in XCode iOS DeviceSupport or not. You can refer this great to troubleshoot if issues. lldb post You need to type these 2 above commands everytime you want to debug application in prompt, to save time, LLDB allow to config commands can be loaded when start. You just need to copy those 2 commands and put in will do. Here is my sample file: lldb lldb ~/.lldbinit ~/.lldbinit rr register read rw register write mr memory read mw memory write il image list -o -f eoc expression -l objc -O -- platform select remote-ios process : ## .lldbinit start ### command alias command alias command alias command alias command alias command alias connect connect //192.168.1.113 :1234 Figure 14: ~/.lldbinit You can define command alias here also, and it will take effect in prompt. But there will be a problem here if you are an iOS developer because this file will also be applied for LLDB in XCode, which means when you debug an application in XCode it also load this init file and will cost you some more time to launch the application. To separate the configuration between LLDB standalone and LLDB of XCode, you can create a new file and put your commands there just for XCode usage. Whenever you debug application via XCode, it will check if file if exists then XCode will load this file instead of . lldb lldb ~/.lldbinit-Xcode ~/.lldbinit-Xcode ~/.lldbinit Set breakpoints Now, attached to the app process and waiting for us to debug, you might notice that the app is hanging on the jailbroken device because the process is being interrupted. The debug process would be the same as we debug in XCode except in XCode you debug through each line of code, here you debug each line of assembly instruction, and you examine registers instead of variables. Before examining registers, let set a breakpoint first. Go back to Hopper Disassembler, go to address of method, we need to set the breakpoint at this address to see if application will invoke this method or not and examine value of URL string passed to the registers (in Hopper Disassembler, press G then put in address value to navigate to). In LLDB, to set breakpoint at an address we can type: , or for short then enter. lldb 0x100038000 -[BackendController init] breakpoint set --address 0x100038000 br s -a 0x100038000 (lldb) warning: failed site : error: sending request Breakpoint : address: breakpoint set --address 0x100038000 to set breakpoint at 0x100038000 for breakpoint 1.1 0 the breakpoint 1 0x100038000 Figure 15: LLDB set breakpoint failed But look at that warning, it failed to set breakpoint at . Why??? The reason it’s failed because of . . We need to calculate the real address when the process is running to set a breakpoint (we need to re-calculate real address every time we start the app). , let find out missing part . 0x100038000 ASLR (Address Space Layout Randomization) ASLR is a memory-protection process for operating systems (OSes) that guards against buffer-overflow attacks by randomizing the location where system executables are loaded into memory. Real Address = ASLR shift + Hopper Address ASLR Shift From prompt, type then enter, the result in the console is the (please note that the value might not the same for you as it’s a random number). lldb image list -o processName ASLR shift (lldb) image -o REDACTED [ ] (lldb) list 0 0x00000000045bc000 Figure 16: ASLR Shift My case , so . Let set breakpoint again: or then enter. ASLR Shift = 0x00000000045bc000 Real Address = ASLR shift + Hopper Address = 0x00000000045bc000+0x100038000 = 0x1045F4000 br s -a 0x00000000045bc000+0x100038000 br s -a 0x1045F4000 (lldb) br s -a + Breakpoint : where = REDACTED`___lldb_unnamed_symbol1321$$REDACTED + , address: 0x00000000045bc000 0x100038000 2 112 0x00000001045F4000 Figure 17: Set breakpoint successfully We just set breakpoint successfully, now let the process continue to run by typing: or then enter. It will run the app for a second then you will see it will stop again in console, continue c IT HITS THE BREAKPOINT!!!! (lldb) c Process resuming Process stpped * thread # , queue = , stop reason = breakpoint frame # : REDACTED`___lldb_unnamed_symbol1321$$REDACTED + REDACTED`___lldb_unnamed_symbol1321$$REDACTED -> <+ >: add x2, x2, # ; = <+ >: bl ; symbol stub : objc_msgSend <+ >: mov x21, x0 <+ >: adrp x8, Target : (REDACTED) stopped. (lldb) 19596 19596 1 'com.apple.main-thread' 2.1 0 0x00000001045f4000 112 0x1045f4000 112 0x170 0x170 0x1045f4004 116 0x1058dcae4 for 0x1045f4008 120 0x1045f400c 124 7810 0 Figure 18: LLDB hits breakpoint Examine registers You can see the process stopped at our breakpoint (at address ) at the instruction . Let have a look again in Hopper Disassembler for these instructions: 0x1045F4000 add x2, x2, #0x170 -[BackendController init]: ... fdc adrp , f fe ldr , [ , ] fe adrp , eb fe ldr , [ , xb ] fec mov , ff bl imp___stubs__objc_msgSend ff adrp , eba ff ldr , [ , ] ffc adrp , , , bl imp___stubs__objc_msgSend mov , adrp , eba ldr , [ , ] mov , mov , bl imp___stubs__objc_msgSend mov , ... 100037 x 8 #0 x 101 09000 100037 0 x 0 x 8 #0 x 3 c 8 ; objc_cls_ref_BackendHttpClient,__objc_class_BackendHttpClient_class 100037 4 x 8 #0 x 101 7000 100037 8 x 20 x 8 #0 88 ; "alloc",@selector(alloc) 100037 x 1 x 20 100037 0 ; objc_msgSend 100037 4 x 8 #0 x 101 000 100037 8 x 1 x 8 #0 x 50 ; "initWithBaseURL:",@selector(initWithBaseURL:) 100037 x 2 #0 x 1019 c 5000 100038000 add x 2 x 2 #0 x 170 ; @"https://redacted-backend.redacted.com/" 100038004 ; objc_msgSend 100038008 x 21 x 0 10003800 c x 8 #0 x 101 000 100038010 x 1 x 8 #0 x 58 ; "setHttpClient:",@selector(setHttpClient:) 100038014 x 0 x 19 100038018 x 2 x 21 10003801 c ; objc_msgSend 100038020 x 0 x 21 Figure 19: Instructions explanation I put comment for those instructions, the can be translated as , this is the address of (you can look back above ) and Hopper Disassembler is smart enough to find out and put comment at the end of this instruction. So after executing this instruction, we expect value of x2 is . Let type or then enter to execute this instruction. add x2, x2, #0x170 x2 = x2 + #0x170 = #0x1019c5000 + #0x170 = 0x1019c5170 https://redacted-backend.redacted.com/ Figure 4 https://redacted-backend.redacted.com/ next n Now to check value of register , you can type (po is print object) or , you will see register now holding address that contains string x2 po $x2 register read x2 x2 https://redacted-backend.redacted.com/ (lldb) next Process stopped * thread # , queue = , stop reason = breakpoint frame # : REDACTED`___lldb_unnamed_symbol1321$$REDACTED + REDACTED`___lldb_unnamed_symbol1321$$REDACTED -> <+ >: bl ; symbol stub : objc_msgSend <+ >: mov x21, x0 <+ >: adrp x8, <+ >: ldr x1, [x8, # ] Target : (REDACTED) stopped. (lldb) po $x2 https: (lldb) register read x2 x2 = @ 19596 1 'com.apple.main-thread' 2.1 0 0x00000001045f4004 116 0x1045f4004 116 0x1058dcae4 for 0x1045f4008 120 0x1045f400c 124 7810 0x1045f4010 128 0x58 0 //redacted-backend.redacted.com/ 0x0000000105f81170 "https://redacted-backend.redacted.com/" Figure 20: Examine register x2 Change registers value As we are in debug mode, to change value of register is trivial. We want to downgrade to protocol so we need to change value of register x2 from to , let do it: https http https://redacted-backend.redacted.com/ http://redacted-backend.redacted.com/ First, let create new string by: or , it will spit out new string created with address of that string in memory. As below, is address of new string for my case: http://redacted-backend.redacted.com/ expression @"http://redacted-backend.redacted.com/" e @"http://redacted-backend.redacted.com/" 0x00000001c0c63740 (lldb) @"http: (__NSCFString *) $5 = @"http: (lldb) expression //redacted-backend.redacted.com/" 0x00000001c0c63740 //redacted-backend.redacted.com/" Figure 21: Create a new string Next, we need to set register to hold new value by: and examine its value again, we can see x2 now reflected new value. x2 register write x2 0x00000001c0c63740 register write x x c c po $x http: (lldb) 2 0 00000001 0 63740 (lldb) 2 //redacted-backend.redacted.com/ (lldb) Figure 22: Change register x2 to new value Finally, just type to resume application so it will continue to run with new URL endpoint, the process no longer hit the breakpoint and look into Hopper Disassembler we can see all of http://redacted-backend.redacted.com/ endpoints are shown in tab with requests and responses details, c HTTP History MISSION COMPLETED!!! Figure 23: Hopper Disassembler can see applications requests & responses Final thought SSL Pinning only work with protocol, so to downgrade to requests we can bypass easily https http REDACTED server supports both and protocol so the app works normally when downgraded to http https http For servers that do not support protocol, this kind SSL Pinning bypass will not work, but we can have our proxy to redirect to so it will work (client send http request to proxy -> proxy rewrite to request and send to the server) http http https http https LLDB is very powerful!! Play with registers is an advanced skill we can learn. You can do anything as long as you can attach a debugger into the running app. This app doesn’t have jailbreak and anti-debug detection, so we can do whatever we want without limitation. If you want to secure the app, you need to think about employing this detection in your codes. Further readings Dancing in the Debugger — A Waltz with LLDB SSL Pinning in iOS app Assembly language wiki ARM architecture My original post was on , feel free to clap and share your thoughts if you find it helpful :) Medium I hope you find this post helpful. Please follow and connect me on Twitter ( ) to be notified on my upcoming posts ^_^ @ReverseThatApp