Reverse Engineering iOS Apps

October 10, 2015


This short guide to reverse engineering (“Reverse Engineering 101”) goes over some free and inexpensive tools for reverse engineering. Example uses of the basic but essential tools are given.

A brief guide to the iOS app reverse engineering process for the unfamiliar:

  1. Make sure your device is jailbroken
  2. Ensure you have changed your root and mobile passwords from the default of “alpine”
  3. Ensure you have Clutch installed for “cracking” and know how to add more repositories to Cydia
  4. It’s useful to have MobileTerminal installed. Search for Mobile… in Cydia packages, so you can run commands from a terminal on the iPad or iPhone without having to SSH in all the time
  5. Run Clutch on the binary. The capital ‘C’ is necessary. Run it without arguments to set it up, then a command might be “Clutch Aerogram” (find mobile apps that are included in Bug Bounties, such as those on HackerOne and BugCrowd)
  6. Transfer the new IPA package in /User/Documents/Cracked/ to a development machine
  7. Rename the IPA package to have the file extension .zip
  8. Run unzip on the package, which produces usually three files and the directory /Payload/ with all other files being in here
  9. Run tools like otool (examples below) to check if the binary still looks good and gather information about it
  10. Use class-dump-z
  11. Steps 9 and 10 are good, but not strictly necessary to start with. class-dump-z has binaries available for Linux as well as Mac (see Github)
  12. Start with simple tools like strings
  13. Use the Hopper Disassembler (example use below), buy a personal licence if you can, otherwise it will quit after 30 minutes of use
  14. Use Charles to MITM app traffic (example configuration and use below); again buy a licence if you can, otherwise it stops after 30 minutes of use
  15. See the reference to iOS Reverse Engineering at the end of this article for more
  16. Check back here at WHITEHACK for more coming examples using Hopper Disassembler to analyse apps, change them, and write out new apps, and other somewhat more advanced material
  17. Also check back here at WHITEHACK for a decent guide to hacking and securing iOS apps; useful if you’re an iOS developer as well as a general hacker/programmer

Start with strings

You don’t need expensive, sophisticated tools to reverse engineer iOS apps. If you have a machine running Linux or Windows, but not a Mac, that will do, along with a decent UNIX-like environment (in the case of Windows, e.g. Cygwin). It’s also not necessary to have a Mac for Hopper Disassembler (available for Linux but not Windows) or Charles Proxy (available for Linux and Windows as well as Mac OS X). You should know how to share your Internet connection to “middle” traffic between an app and the Internet.


The strings program perhaps unsurprisingly dumps strings. You can pipe the output to grep or your own script to run regular expression matching on the output. Strings is simple but an essential tool for finding readable text in binaries.


Most of the time in binaries, even if they are stripped of symbols, there are sections within segments that give up class and function names, error messages and global static strings used for various purposes, debugging information (so long as it’s text), and much more. It’s well worth piping the output to, say, a Perl script to run a collection of regular expressions against the output. In mobile apps, you might find a lot of XML, URLs, certain file and repository information (where an iOS app checks for a jailbreak), SQL (for local sqlite storage or to send to a back-end), base64-encoded text to unravel, and who knows what else. It’s definitely worthwhile thinking about what you might like to find, creating that script with some patterns you’re interested in, and seeing what pops out.


Since Apple’s FairPlay DRM gets in our way, we want that gone to begin with, so the app will have to be “cracked” with a tool like Clutch.  So Clutch the app, and if all goes well, you should have something like




on the iPad or iPhone ready for transfer to a machine for analysis. Assuming you’re familiar with the jailbreak process and transfer of files back and forth, something like this works to find it, if you’re not exactly sure (Clutch should tell you):


$ ssh [email protected]

$ password <ENTER>


# cd /User/Documents/Cracked

# ls

# …

# logout


A quick way to transfer a cracked app:


$ mkdir crackedapps

$ cd crackedapps

$ ssh [email protected] ‘cat /User/Documents/Cracked/appname-version-\(Clutch-1.4.7\).ipa’ | dd of=appname.ipa


And you now have the cracked app appname.ipa ready to extract and inspect.  The ipa files are just zip files, so rename and unzip (it’s best to do this in their own directories):


$ mkdir appname

$ mv appname.ipa appname/

$ cd appname

$ mv appname.ipa

$ unzip

$ …

$ ls

$ …


You will now see some files and a directory which is always called Payload. Inside the Payload directory is a directory named for the app with “.app” appended.


$ cd Payload

$ ls

$ cd

$ ls -la

$ …


If Clutch fails and you have output similar to “Can’t crack 64bit on 32bit device! skipping” and an error message such as “This app contains Framework, crack failed” you might not be out of luck entirely.  Try checking out the directory /tmp/clutch_ABCDWXYZ/ where clutch_ABCDWXYZ is the recently created Clutch output directory and is the name of the directory of the app you were trying to crack.  Clutch may have successfully written a cracked 32 bit version of the app there, which you can analyse, disassemble with Hopper, or place back in it’s proper app bundle directory to run on your iPad or iPhone.


Now you have the the resources and files that come with the app, as well as the app binary itself.  It’s time to run strings, which is as easy as:


$ strings appname


You may want to extend the number of characters output by default (which is 4) with the -n switch, and pipe it through less.


$ strings -n 8 appname | less


Some versions of strings assume you want armv7 or some other functionality and will fail with a message such as “unknown load command 10” or similar on a Mac; you should not have that problem on Linux or Windows.


Before writing a Perl script with some regular expressions, which will accept output from strings through a pipe, there are always a few interesting and simple snippets of text you may like to try with grep first. For instance:


$ strings appname | grep “<?xml”

$ strings appname | grep “SELECT” (or select, insert, update… etc)

$ strings appname | grep “http”

$ strings appname | grep “cydia” (and others, to check for jailbreak tests)

$ strings appname | grep “.com” (you never know, email addresses might be interesting)

$ strings appname | grep “Crypt” (this will also show AESCrypt, for example)


and “man grep” for an overview of grep’s pattern matching.


The screenshot below (excuse the sloppy job of brushing out some information) shows the curious string “MyPassword1234” as the first line of output, which can be searched for in Hopper later to figure out what it might be used for.


MITM with Charles Proxy and/or BurpSuite


There are a few ways to capture traffic and use an intermediate CA to “middle” SSL/TLS traffic, but by far the easiest way is to use a tool like Portswigger’s BurpSuite or Charles Proxy. Before starting the app on an iPad or iPhone, have a MacBook handy to share an internet connection. What I’ve found works very well is to open the sharing settings on the MacBook and set it to share its Internet connection over ethernet with other devices connecting through Wifi. You may need to go to System Preferences, Network, and under WiFi, select Create New Network for Network Name.  There, you can set the security and password, as shown below.


Hit “Create” then “Apply” and you should be ready to connect and share the connection. If you have problems, you may need to adjust your firewall and/or restart the Mac.


Install Charles Proxy or BurpSuite or your favourite proxy, then configure the iPad or iPhone.  You will have to configure the iPad or iPhone to use the new shared Internet connection. If you’ve named the shared Internet connection, e.g. macbook.local, “forget” any connection the iDevice might be using, select the new shared connection on your iDevice and enter the WEP or WPA2 key for the connection (if any). Run “ifconfig” on the MacBook to find the IP address of the wireless adapter, this should be the same as the default gateway/router.  Under HTTP Proxy settings on the iDevice, select manual, and enter the IP address and port number the proxy will listen on. The default port numbers are 8080 for BurpSuite and 8888 for Charles Proxy. Below is an example configuration on an iPad.


Now install the proxy’s CA certificate with Safari.  You can also email it to yourself if you use email on the iDevice, open and install the CA certificate. For BurpSuite, fetch http://burp and click the link for the CA certificate; for Charles Proxy, fetch There’s one last step, and that is the “scope” of what you would like to capture with the proxy.  This is straight forward with both BurpSuite and Charles Proxy, see the documentation for each proxy for more details on including addresses in scope and excluding others as you may want. Charles Proxy is the easiest tool to use for MITM’ing traffic; that’s what it’s mostly for.  BurpSuite is a proxy as well as a sophisticated tool for “hacking” or auditing Web apps.


It’s easier to start with Charles Proxy, so some examples are shown below. The first is Charles as it starts, then how to add domains you want to proxy SSL/TLS traffic for.  Wildcards are particularly useful here.  The final screen capture shows an example of traffic in Charles.


Charles on Startup


Configuring Charles Proxy for SSL/TLS Capture


Adding Hosts to Enable Proxying


On the left pane, select what you would like to see when MITM’ing the traffic. The Raw request, and response headers, is shown below for one of the domains when starting Microsoft Word on an iPad.



A good proxy will reveal all traffic including SSL/TLS traffic for most addresses, but occasionally fails when key or certificate pinning is used by the mobile app.  Most of the time it’s not much trouble; for instance it’s easier to capture the traffic of major bank apps than it is to capture the traffic from Twitter’s app.  Sometimes to get an idea of the traffic flow, an equivalent of the app will be available on the Web, so you can always try the Web app in a browser if you’re having difficulty examining the traffic from a mobile app.




When you find yourself without any source code to an iOS app, class-dump-z will extract a lot of useful information.  There’s even a convenient switch to output a collection of Objective-C/C++ header files.  These show the class declarations, methods, and properties (internal data) of classes.  To find where the login box of an app is, you might find LoginBox.h and examine that file.  Inside you will likely find the class and parent classes, if any, properties and methods that can be invoked on objects of that class.  If you find any properties that are pointers to class variables, these are sometimes singleton classes, or shared instances of a class. One of these is created for the entire app, and usually persists for the lifetime of the app from when it’s run to when it’s closed down.  These are particularly easy to spot and examined with tools like Cycript (by Saurik, aka Jay Freeman).


Example of class-dump-z on Yahoo Mail! (Aerogram) app:


macbook:mac_x86 whitehack$ class-dump-z -p -u armv7 -a -A -k -k -I -z -H -o ~/Downloads/yahoomail-classdump/ ~/Downloads/Aerogram


Browsing to the directory ~/Downloads/yahoomail-classdump/ and opening FlurryUtil.h, which contains an example function to “NOP out” later in this article with the Hopper Disassembler, reveals a lot more class methods such as +(BOOL) deviceIsJailbroken, another function to search for and take a look at, and the header reveals that potentially a lot of information is being collected.  Looking through all the headers will be very instructive, helping to direct your analysis and study along with Cycript and Hopper.




Lipo is a tool that operates on universal, so-called ‘fat’ files.  These files are apps that contain binaries for multiple architectures.


$ lipo

lipo: Usage: lipo [input_file] … [-arch <arch_type> input_file] … [-info] [-detailed_info] [-output output_file] [-create] [-arch_blank <arch_type>] [-thin <arch_type] [-remove <arch_type>] … [-extract <arch_type>] … [-extract_family <arch_type>] … [-verify_arch <arch_type> …] [-replace <arch_type> <file_name>] …


For instance, when you have a universal ‘fat’ binary with multiple architectures and want to extract a 32-bit ARMv7 binary (it’s not strictly necessary to backup or make a copy, since lipo is never supposed to alter the input file, but for sanity’s sake):


$ cp Appname Appname.bak

$ lipo Appname -output AppnameArmv7 -extract armv7


$ otool -f -a -h -v AppnameArmv7

$ lipo -detailed_info AppnameArmv7


$ mv AppnameArmv7 Appname

[Remember we have a backup at Appname.bak]


Although, this is probably just going to be a useful exercise for later.  If you try to Clutch an app and figure on extracting the architecture you want, for instance a 32-bit binary, and placing that back for Clutch to work on, you’re probably not going to have much luck.  If Clutch fails on an app, check /tmp/ and look for clutch_xyz with the most recent date/time stamp, which should contain Check it with


$ otool -l Appname | grep -A 4 LC_ENCRYPTION


and if cryptid=0, you’re in luck.  At least it dumped the 32-bit binary instead of leaving us with a weird universal ‘fat’ binary containing a cracked 32-bit version and an uncracked 64-bit version.  Now you can analyse the binary in Hopper, and stick it back in it’s bundle directory where it came from in the first place.




Cycript is a dynamic analysis tool for looking at apps running on the iOS platform.  It’s not the traditional debugger, but offers many of the same features, aside from single stepping through code and setting breakpoints wherever you wish.  It’s still very powerful and an excellent tool to understand the app and how it works.


Run ps ax | grep appname on the iDevice over SSH or on the mobile terminal to find the process ID of the app you’re interested in after starting it, then attach to the process with Cycript with cycript -p PID.  If this is successful and it is almost all the time, you will be dropped into a shell-like environment.




It’s at that prompt you type cycript commands.  Cycrypt uses a blend of JavaScript and Objective-C notation, with the JavaScript being the scripting and left hand side of most code, and Objective-C on the right.  For instance:


cy# var vvc = UIApp.keyWindow.rootViewController.visibleViewController


cy# *vvc


Dumps the current visible view controller of the app, along with any references to other objects inside the view controller.  This does not always work, but most of the time it does.


To get a singleton object (you can search for anything called sharedInstance or sharedManager or other terms in strings, or look through what class_dump_z has given) you just assign it to a variable.


cy# var shared = [DataAnalyticsClass sharedInstance];


There is no need to allocate memory for the new object or call any initialization routines, since presumably the app has already created it. Although for the usual classes that are allocated and have an initialisation routine for instance, you can:


cy# var obj = [DataStore alloc];

cy# [obj init];


You can then just dump the contents of the objects:


cy# *shared

cy# *obj


Or inspect properties and change them (see output from class_dump_z for reference), e.g.


cy# var url = [sharedInstance url];

cy# *url


If you go through this with Cycript (see also the many good code snippets for use with Cycript, such as printing method names) you will often find references to objects with the 0x12345678 notation.  You can create instances of these objects by using their numeric handles.


cy# var textbox = new Instance(0x12345678)


And print or change values as well (you will probably want to check to make sure that object has been created first, or this can crash the app):


cy# testbox.text = @”The App with Cycript”;


The official manual for Cycript is sparse and difficult to follow, so most of the time to begin with, Google searches for cycript and how to use it are instructive. You can find some good Cycript routines to type in that turn up in Google Books.


Disassembling with Hopper Disassembler


Hopper is an excellent tool for static code analysis and disassembly, as well as modifying and reassembling code, and writing out new executables.  Hopper typically works best on Mac OS X, although with a personal licence you can download a version for popular Linux distributions as well.


To get started with Hopper, go to File -> Read Executable to Disassemble and point it to your cracked binary.  It will detect the type of file and with armv7 Mach-O files, you have the option to resolve lazy symbols too.


This process may take some time on older Macs.  It will first gather all the strings then work through the code of the app, and leave you at EntryPoint/main.  Wait for the messages at the bottom of the screen to say “Background analysis ended” then you’re ready to start searching for procedures and class names under the tab “Labels”, or anything you like under the “Strings” tab.


A great way to get started with reverse engineering is to look for bug bounties which include mobile apps in their scope.  In these examples, I’ll be using Yahoo! Mail for iOS (Aerogram).


Here’s what’s immediately available in Hopper on disassembling Aerogram:


Hopper drops you off at EntryPoint or _main, in this case. If you can see well, the machine code bytes are listed after the hexadecimal addresses where the disassembled code resides, before relocation in memory (also listed on the right under Instruction Encoding, above CPU Mode). The Thumb mode machine language bytes for push { r4, r5, r6, r7, lr } is F0B5. We’ll soon see a few methods for verifying this is little-endian. If you can see on Hopper’s bottom application bar, the address at the highlighted location (at least as a simple binary on-disk) is 0xde00, file offset 0x9e00. We will soon see that the first text or code section is at 0x4000 in the binary, and so 0x4000 + 0x9e00 = 0xde00.  In other words, the file offset is the offset from the location of a particular section in a segment, in this case segment __TEXT, section __text (which is typically 0x4000).


Hopper also helps by checking out the machine code to determine whether you are in ARM processor mode or ARM’s Thumb processor mode.  ARM is more capable, and Thumb originally came out as a power saving mode for mobile devices (ARM mode is 32 bit, and now of course there are 64 bit ARM processors). This explains why some machine code above is encoded in 32 bits or 4 bytes, while other operations are 16 bits, or 2 bytes. As Thumb mode became more popular, more capability was added to Thumb mode and typically now, the app only goes out of Thumb mode to call an external procedure, execute instructions not available in Thumb mode or which do not fit in 16 bits, or branch elsewhere in the app.


Hopper has a great feature for those who aren’t particularly fond of assembly language or don’t yet know enough about it.  It features a button (top right, labelled “if(b) f(x);”) which when clicked, will disassemble as much as possible into pseudocode. Place the mouse cursor on a function/subroutine and hit the pseudo-code button. An example from Aerogram:


For those who know a little about programming, you can search for a procedure that sounds like it returns a Boolean value, such as isJailbroken.  The start of the function prologue will look similar to this (fewer registers might be saved) in Hopper’s middle disassembly window (minus preparation of the stack and saving other registers if necessary):


push { r4, r5, r6, r7, lr }


and the function epilogue popping the saved registers corresponding to the start of the function will be:


pop { r4, r5, r6, r7, pc }


If the code looks like this, for example (note the push of lr is popped back into lr, rather than the program counter register pc):


pop.w { r4, r5, r6, r7, lr }

b… (branch to somewhere)


This means the function is not returning immediately after popping the registers at the end of the function; it pops the link register back for returning to some address, and continues branching to another location, often a call to objc_send.


In Hopper, you can select all the code between the start of function prologue and epilogue (the push and pop lines respectively) and select Modify -> NOP out region.  Hopper will place the correct number of No-Operation instructions so the function does absolutely nothing. Within reason, you can assemble in any code you wish, replacing the NOPs.


Finding a Function to NOP-out


You might like to search for “Is” and other words such as “not”, “will”, “can” and so on to find candidate functions that return a Boolean value.


For example, looking for Cracked:


Begin your selection of code just after the function prologue begins (after the push):


Keep selecting until you hit the pop registers for the function:


Select Modify, NOP Region.


Selecting the last two nop bytes before a Boolean function returns, you can select Modify -> Assemble region, and add movs r0, #0x0 to make the function always return false, or movs r0, #0x01 (or some non-zero value) to make the function always return true. The movs instruction is the mov (more correctly, a copy) that sets processor flags, such as the zero flag (the last instruction resulted in zero).  The default register for returning a simple type/value is r0 on ARM processors.


After selecting NOP Region as above:


It’s interesting that there would be Thumb mode nops and ARM mode “service call on signed less than” – svclt – then do nothing, i.e. NOP.  The machine code bytes underneath are shown to be exactly the same, sequences of 00BF, for both the Thumb mode NOP and the ARM mode NOP.


You are now ready to search for the machine code for movs r0, #0x0.  If that instruction doesn’t exist in the app, there is a way to figure out the machine code for simple instructions (which is good practice reading assembler and machine code instead of doing a Google search). Notice some mov with set processor flag instructions as below.


There are three consecutive movs instructions using destination registers r0, r1 and r2. Which byte contains the immediate operand is easy to see on the left: 0D20, 0121, 0122. Given the consecutive hexadecimal values 20, 21 and 22, it’s reasonable to assume they correspond to movs into register r0, r1, and r2 respectively.  So it appears the instruction we want is 0020 hexadecimal.  To assemble in the movs r0, #0x0 before the return of the NOP’d out function:


Select Modify -> Assemble Instruction.


Choose CPU Mode Thumb in the selection box, and type in the assembler code as below.


After clicking Assemble and Go Next:


Hopper assembles in the 0020 hexadecimal, which was our guess, only touching the 16 bits selected to assemble.  Click somewhere in the window to dismiss the Assemble and Go Next box.  If you make any mistakes, Hopper has a convenient undo function.


Now it’s done, a function has been made completely useless and just returns false all the time.  Whether it really works – there may be other ways and methods of checking for a “cracked app” – remains to be seen.  If we can MITM the analytics traffic, we might get an idea of whether it worked if it sends this data over the network to the analytics platform.


In Hopper you can select the option to write new executable, and run your modified app.  With large functions, you can explore the possibilities of replacing function code with your own as well as perhaps jumping over bits of code, or assembling in return values that are supposed to have come from external library functions as well as return values from methods inside the app.


Just select File -> Produce New Executable, and click Save (perhaps saving it somewhere else so you don’t overwrite the original on your development machine).


It’s ready to be transferred: A nice, easy way to transfer files, directories or anything else is a program like FileZilla or Cyberduck. Make sure the permissions are correct and change the owner and group back to mobile if you’ve logged in as root.  Tap app icon on your device and enjoy.  Explore functions in Hopper, download ARM manuals, MITM app traffic (which may include in-app analytics and other services), and see what you can come up with!


Hopper does a lot more that’s not covered in this guide.  For instance, you can live debug if the binary will run on your development machine (most of the time, it will not, for iOS apps; there are no decent emulators for ARMv7 code either).  Check the documentation and don’t forget the hex editor option under the Window menu in Hopper.


Once you have a good overview of an app by running it with Cycript, MITM’ing any traffic and analyzing it with Hopper, you can try logging data, stealing crypto keys and passwords, examining the quality of the app, how it behaves when you disable input validation (if any); the possibilities are almost endless once you have a good idea of what the app is and how it does it.  If all you want to do is disable an advertisement feed inside your app, then that’s fine too, although two other worthwhile tasks you can accomplish with reverse engineering is to find out exactly how apps work and build your own, or join a bug bounty by signing up with BugCrowd or HackerOne that includes mobile apps within the bounty program’s scope, for cash rewards, kudos or recognition; it could be impressive to have a nice list of resolved bugs on a CV when looking for that next IT job or a significant career change, e.g. from help desk to information security.


Check Directories


Interesting files to look for are sqlite database files on the device, property list (plist) files and db files.  There are often a few of these in an app’s bundle directory, although apps have another directory to stash files in and cache information; it varies between applications.  To find these, look under /private/var/mobile/Containers/Data/Application/{bundleID-type-string}/. You might have to work through a few directories to find what you’re looking for.  On a mobile terminal, you might change directory into /private/var/mobile/Data/Application and run, for instance, $ find | grep sqlite.


Under these directories, in Preferences and Cache an app might also store cookies and other information, keep logs, and store anything the app might have downloaded.  Before reverse engineering, it’s sometimes useful run the app a few times, and check out its directories to see what it leaves behind, caches and stores.


Other Useful Tools




Mach Object Viewer – this tool shows you all sorts of very useful information you could want in an object file, and has a neat GUI. From


MachOView is a visual Mach-O file browser. It provides a complete solution for exploring and in-place editing Intel and ARM binaries.




Property List Utility – this command can take binary format plist files and output XML, json and other formats. Much more useful than running strings with a binary plist file as input, but if you can’t use plutil, strings is better than nothing.


$ plutil -convert xml1 -o Info.xml Info.plist

$ cat Info.xml | less


$ plutil -convert json -r -o Info.json Info.plist

$ cat Info.json | less




Object Tool – this command is very versatile and will probably be one of the first commands you run on a cracked app once you transfer it to a Mac for analysis.  Tools like this, specific to Mac OS X and often with Xcode, are not available for Windows or Linux.  It pays to have a Mac to analyse your cracked apps.  otool can dump particular segment, section structures, data areas, list load commands, print FAT headers, shared libraries used, relocation entries, disassemble verbosely, and more.


$ otool -l App | grep -A 4 LC_ENCRYPTION

[If cryptid=1, you need to crack it. If cryptid=0, you’re good to go]


$ otool -l App

[All load commands, very useful]


$ otool -f -a -h -v App

[Fat Headers, Archive Header, Mach-O Header, -v is for verbose]




This is a handy tool you can find on SourceForge. As it suggests, it opens SQLite files for inspecting table layouts, SQL, browsing data and more.


Any Good Hex Editor


A good hex editor will go a long way to help understand what’s in a binary. Searching for error messages and replacing them with your own strings is fun and instructive to begin with. You can also change machine code. It’s simple and effective. Hex Fiend is a good editor for the Mac, there are several good ones for Windows and Linux. A Google search or checking out a list of Hex Editors on Wikipedia or elsewhere should reveal a nice selection.


Further Reading


WHITEHACK will continue publishing more material on iOS app testing and reverse engineering “all the things,” so please bookmark and check back soon.


In the mean time, see this excellent guide:


Fields marked with an * are required
Find out more
Recent Posts

Not-For-Profit & Education Discounts

18 July 2016

a team of highly skilled ethical hackers

Read More

Whitehack Information Security Consultant Finds Critical Vulnerability within AVG Owned Domain

30 June 2016

a team of highly skilled ethical hackers

Read More

ABC Four Corners Films Segment at WHITEHACK

03 June 2016

information security auditors, interview, four corners

Read More

ABC Radio Chat: TrainLink & Myspace Hack

31 May 2016

a team of highly skilled ethical hackers

Read More