So far you've learned how to configure your computer and device with the necessary tools to decrypt iOS apps and copy them to your computer. In this module you'll learn how to analyze an iOS application by inspecting all its files, frameworks (dependencies) and lastly the application binary. It's called static analysis
because you're not going to execute the binary, you'll be reviewing all the files contained in the .ipa
archive. This is intended to be an interactive module, meaning I'll point you in the hopefully right direction and you are going to find the issues yourself. But don't worry, if you feel lost or cannot find any issues, all the solutions are at the end of the module (along with explanations on why they are considered issues and some recommended solutions).
After you decrypt an iOS application you'll end up with a .ipa
file. This is an application archive, basically a zip archive. It includes the application binary, 3rd-party frameworks, configuration files, media files (like images and videos), UI elements (like storyboards and nibs), custom fonts and any other file the developers embed within the application.
To illustrate the most common vulnerabilities in iOS applications I've created a very insecure application called CoinZa
[^1], I wrote it in Objective-C
(aka Objc) to make it simpler to explain some reversing steps. Applications written in Swift
still prove a bit difficult for some tools, though I plan to add support to some modules for Swift
applications in the future. For now you can download the Objc version form here.
- Extracting the
.ipa
contents is as simple as changing its extension to.zip
and unzipping it.
mv CoinZa.ipa CoinZa.zip
unzip CoinZa.zip
- After unzipping the contents you'll have a folder named
Payload
and inside you'll find the application bundle namedCoinZa.app
. Note: On an application downloaded from the App Store you'll find 2 more files along with thePayload
folder, aiTunesArtwork
file which is the app icon and aiTunesMetadata.plist
file that contains information like the developer's name and ID, the bundle identifier, copyrights, the name of the application, your email and the date you purchased it, among other information. - Right-click (or Control ⌃ + Left-click) the
CoinZa.app
and selectShow Package Contents
. - Finally, move all the files within the
.app
bundle to a new folder. This is to have an easier access to them, instead of right-clicking it and selectingShow Package Contents
all the time.
mkdir CoinZaFiles
mv Payload/CoinZa.app/* CoinZaFiles/
Your end goal is to understand as much as possible what the developers are shipping with every application. It's a good idea to start by looking for low-hanging fruit kind of issues. In iOS reversing these come as configuration files, example data files, database connection files or embedded private keys for SSH connections. Yes, as I've said before, I've seen all of these cases in real applications.
- The two most common configuration files I've encountered in iOS applications are
.plist
and.json
. Start your research by reading through all the files you can find with these extensions and see if you can find some information that should not be there. - A very important file is the
Info.plist
in the root directory of an iOS application. This file contains a lot of configuration data like if the application enables weak TLS settings on some domains (search for theNSAppTransportSecurity
key), or if the application accepts customScheme URLs
(search for theCFBundleURLTypes
key). - Compiled CoreData models (
.mom
,.momd
) can be decompiled intoxcdatamodel
files using tool calledmomdec
. These files can later be inspected in Xcode.
Almost every single iOS application uses at least one 3rd party framework. As a security researcher this is very important because this increases the attack surface and more often than not the developers forget to update their dependencies and the bigger the list of dependencies, the harder it is to keep track of updated versions. This means that as long as an application "still works" there's no incentive to update these 3rd party frameworks. This leaves users with outdated, and potentially vulnerable, code on their devices. All the 3rd party framework within an iOS bundle live in a folder called Frameworks
.
- Open the
Frameworks
folder, take a look at which frameworksCoinZa
is using and pay attention to the frameworks' versions. - Tip 1: The framework's version is disclosed in its
info.plist
file. - Tip 2: Google those framework versions and search for known vulnerabilities.
An essential part of the static analysis of any application is to gather information about what methods and classes are contained in the application. This step gives you very important information because, as many developers know, declaring very descriptive methods help the development of good products. Thus the names of some of the methods will give an insight of what the application features are. I'll show you how to use class-dump-z
to dump the application's classes and methods. There's not going to be an exercise for this section, but you can then spend some time reading through the output and taking notes on interesting classes or methods.
- Dumping the classes is extremely easy with
class-dump-z
, navigate to the folder where you extracted theCoinZa.app
files and runclass-dump-z
with the binary name as its first parameter and save the ouput on adump.txt
file:cd ~/Downloads/CoinZaFiles class-dump-z CoinZa > dump.txt
- If you open the
dump.txt
file, you have now all the classes, methods and some instance variable names of the application binary. As you can see there are some interesting classes likeWallet
,KeyPair
,AddFundsViewController
,CreateWalletViewController
. Even without installing the application we can see that this probably is a cryptocurrency application. - Finally, if you run
class-dump-z
with no parameters it will show you all the options it has for dumping classes.
After the initial reconnaissance work, you've reached (IMO) the most exciting part of this module, understanding the actual behaviour of the application methods. After searching through the classes and methods in the class-dump-z
output, you could see that this application is very small; but most of the applications are significantly bigger and have far more classes and methods. Because of this, it's important that you can prioritize your work and focus on the more interesting cases.
- To disassemble and decompile the binary open Hopper and drag-n-drop the CoinZa binary in Hopper's active window. Note: You'll see that this binary is a
FAT
binary, which means that it contains code for more than one architecture. In this case it contains code for theARMv7
andARM64
architectures because this application targets a minimum version of iOS 10 and the minimum supported devices on iOS 10 are the iPhone 5, iPod Touch 6th Gen and iPad 4th Gen, which areARMv7
devices.
- Hopper will ask you which architecture you want to disassemble. You can choose which ever you want though I'd recommend the
ARMv7
since it has a smaller and simpler instruction set but Hopper has some trouble disassembling some parts of the application onARMv7
and to explain better I'll be using theARM64
disassembled code. - After selecting the architecture, it will ask you to set some options for the Mach-o file. The defaults should suffice.
- Hopper will then begin to disassemble the binary. It shouldn't take too long since, again, this is a small app. But I've had some instances where it took about 45min to finish, and this was on a MacPro 6-core Xeon with 64GB RAM.
- Once Hopper finishes disassembling, select the
Procedures
tab on the left panel and you'll be able to see the list of method names that hopper was able to find.
-
If you select the
Str
tab (next to theProcedures
one) as you probably guessed is the list of all the String-looking or printable characters within the binary. This is another favourite of mine since you can start searching for words likesecret
,private
,test
ordebug
and trace their usage. More often than not, developers leave test classes that provide a good insight. Sometimes there are even developer modes that we can enable to get extra functionality out of the application. -
To trace the usage of a string:
- Search for a string, for example search for
isProVersion
. - On the main window, select the string and right-click on it.
- On the menu select
References to aIsproversion
. - Hopper will take you to a the
cfstring
section, which is where the c-string literals are listed. - Select the
cfstring_isProVersion
and right-click on it and selectReferences to cfstring_isProVersion
. - Hopper will now show you a window with a list of methods. As you probably guessed, this is the list of methods that use the
isProVersion
string. - Select the first instance of
[AddFundsViewController viewDidAppear:]
and click Go. - On the main window you'll now see the assembly code of the
viewDidAppear
method of theAddFundsViewController
class. If this is a bit confusing for you, Hopper has also a decompiler function. - In the middle of the top options bar select the
Pseudo-code Mode
tab (the one with theif(b)
text).
- You'll be able to see that the string
isProVersion
is actually a key of an object stored in theNSUserDefaults
shared settings. I'll explain more on this in a bit.
- Search for a string, for example search for
-
With the string search exercise you found evidence of features potentially guarded by a
ProVersion
state. You also saw in the previous exercise that you can load thepseudo-code
of a method. -
To analyze a method with its
pseudo-code
: Note: use theARM64
disassembly for this exercise.- Click on the
Procedures
tab and search forWalletDetailViewController
and select thedidUpdateWalletBalance:
method. - Uncheck the
Remove potentially dead code
checkbox. Sometimes Hopper tries to optimize the decompiled code or just gets it wrong and thepseudo-code
has some missing information. I usually uncheck this checkbox in case that happened. - I want to bring your attention to this section of the
pseudo-code
:
r2 = @"isProVersion"; if (objc_msgSend(r0, @selector(boolForKey:)) != 0x0) { r8 = 0x1001f0000; r2 = @"isProVersion"; r1 = @selector(stringWithFormat:); var_60 = d8 * 0x1001ad2e0; r2 = @"Since you are a pro user we added an extra 20%% and it's on us!\nYour balance will actually increase by US$%f."; r0 = objc_msgSend(@class(NSString), r1); r29 = r29; } else { r8 = 0x1001f0000; r2 = @"isProVersion"; var_60 = d8; r2 = @"Funds purchased successfully, your balance will increase by US$ %f."; r0 = objc_msgSend(@class(NSString), @selector(stringWithFormat:)); r29 = r29; }
- What you can see is that enabling the
ProVersion
state will be beneficial to an attacker since it will grant them an extra 20% of something. You don't know what that something is yet, but looks like you should take a note about this finding. 😉 Specially since it looks like the check is done on the client side. - I'll leave it to you to keep digging around and take notes of interesting methods and classes. Analyze as many classes and methods as you can because they will help on the next module.
- Click on the
-
Tip 1: Ignore all classes with the
FIR
prefix, they are part of the Firebase framework and are outside of the scope of this analysis. -
Tip 2: If you are using the trial version of
Hopper
take into account that it will self-close every 30min.
On March 5th, 2019 the NSA released a free and open source reversing tool called Ghidra
. Ghidra
supports Windows, Linux and macOS. Even though it's a very new tool and I haven't been using it as long as Hopper, I wanted to add it to the course so that we all could learn from it. Note: Like I said, I haven't used Ghidra
much so please bear with me while I show you how to use it.
- You can launch Ghidra by running the
ghidraRun
bash script at the root of theghidra_9.0.1/
directory. Note:Ghidra
requires the Java JDK, if you don't have it on your machine you can download it from here.
./ghidraRun
- If this is the first time you're running
Ghidra
you'll have to create a project. Click onFile
and thenNew Project...
(or⌘ + N
). - Choose if you want a
Shared
orNon-Shared
project.Shared
project can be accessed by other users. - Select a directory to save your project and give it a name.
- Drag-n-drop the
CoinZa
binary intoGhidra
. Ghidra
will display a dialog saying that the file contains nested files.This is the same as Hopper telling you that it was aFAT
binary and you need to choose an architecture. Select theBatch
option.- You'll be presented with a window showing you the two architectures in the binary.
AARCH64:LE:64:v8A
isARM64
andARM:LE:32:v8
isARMv7
. You can keep both selected, but since they are the same I'd suggest to just keep one selected. Ghidra
will show a toast saying the file was imported. Super fast eh? Not so fast, imported doesn't mean disassembled.- Expand your project folder and the
CoinZa
folder and you'll see a file called eitherARM-32-cpu0x9
orAARCH64-64-cpu0x0
depending on the file you previously selected. - Drag-n-drop the
ARM-32-cpu0x9
/AARCH64-64-cpu0x0
file on top of theCodeBrowser
button (the one with the dragon icon). Ghidra
will tell you that the file hasn't been analyzed and if you want to do it. ClickYes
.- Leave the default selected Analyzers selected and click
Analyze
. Note: To be honest I haven't played around too much withGhidra
to know the different analyzers, that's why I suggested to leave the defaults. - In my computer
Ghidra
took significantly longer thanHopper
. - On the
Symbol Tree
window (on the far left) selectClasses
and scroll down toWallet
and select theWalletDetailViewController
class. - Within the
WalletDetailViewController
functions search, again, fordidUpdateWalletBalance
. - On the
Decompiler
window (on right side of the split windows) you'll see the decompiled code of the method. Note: If you don't see theDecompiler
window press⌘ + E
.
_objc_msgSend(&OBJC_CLASS__NSUserDefaults,"standardUserDefaults");
uVar2 = objc_retainAutoreleasedReturnValue();
iVar1 = objc_msgSend(uVar2,"boolForKey:",&cf_isProVersion);
if (iVar1 == 0) {
_objc_msgSend(&OBJC_CLASS__NSString,"stringWithFormat:",
&cf_Fundspurchasedsuccessfully,yourbalancewillincreasebyUS$%f.);
} else {
_objc_msgSend(&OBJC_CLASS__NSString,"stringWithFormat:",
&
cf_Sinceyouareaprouserweaddedanextra20%%andit'sonus!YourbalancewillactuallyincreasebyUS$%f.
);
}
- As you can see this looks very similar to what
Hopper
showed on itspseudo-code
mode. - A huge advantage is that
Ghidra
is free!
- A static analysis on an iOS application can take you as little or as long as you want. You can go as deep as you can. Specially because the same techniques used to inspect the main application binary can be used to reverse engineer the 3rd party frameworks' binaries. I personally spend many days, and sometimes even many weeks, performing static analysis on iOS applications. Note: The first mobile bug I was ever rewarded for on HackerOne was a weak encryption vulnerability, specifically an insecure encryption key generation, basically I was able to predict past and future encryption keys. This was possible because I spent a lot of time understanding their key generation algorithm and was finally able to understand its behaviour without even running the application, all via static analysis.
- Many developers don't realize that any file they embed in their application will be very easy to extract and analyze.
- As researchers is very good idea to check the 3rd party frameworks bundled with the application.
- Gather as much information as you can on this step because you'll use it in the dynamic analysis step.
Find the solutions here.