18 KiB
Module 3 - Static Analysis
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 application files
- Extracting the
.ipacontents is as simple as changing its extension to.zipand unzipping it.
mv CoinZa.ipa CoinZa.zip
unzip CoinZa.zip
- After unzipping the contents you'll have a folder named
Payloadand 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 thePayloadfolder, aiTunesArtworkfile which is the app icon and aiTunesMetadata.plistfile 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.appand selectShow Package Contents. - Finally, move all the files within the
.appbundle to a new folder. This is to have an easier access to them, instead of right-clicking it and selectingShow Package Contentsall the time.
mkdir CoinZaFiles
mv Payload/CoinZa.app/* CoinZaFiles/
Analyzing embedded files
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
.plistand.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.plistin 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 theNSAppTransportSecuritykey), or if the application accepts customScheme URLs(search for theCFBundleURLTypeskey). - Compiled CoreData models (
.mom,.momd) can be decompiled intoxcdatamodelfiles using tool calledmomdec. These files can later be inspected in Xcode.
Analyzing 3rd party frameworks
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
Frameworksfolder, take a look at which frameworksCoinZais using and pay attention to the frameworks' versions. - Tip 1: The framework's version is disclosed in its
info.plistfile. - Tip 2: Google those framework versions and search for known vulnerabilities.
Dumping the application classes
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.appfiles and runclass-dump-zwith the binary name as its first parameter and save the ouput on adump.txtfile:cd ~/Downloads/CoinZaFiles class-dump-z CoinZa > dump.txt - If you open the
dump.txtfile, 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-zwith no parameters it will show you all the options it has for dumping classes.
Disassembling and decompiling the binary - Hopper
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
FATbinary, which means that it contains code for more than one architecture. In this case it contains code for theARMv7andARM64architectures 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 areARMv7devices.
- Hopper will ask you which architecture you want to disassemble. You can choose which ever you want though I'd recommend the
ARMv7since it has a smaller and simpler instruction set but Hopper has some trouble disassembling some parts of the application onARMv7and to explain better I'll be using theARM64disassembled 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
Procedurestab 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
Strtab (next to theProceduresone) 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,testordebugand 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
cfstringsection, which is where the c-string literals are listed. - Select the
cfstring_isProVersionand 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
isProVersionstring. - Select the first instance of
[AddFundsViewController viewDidAppear:]and click Go. - On the main window you'll now see the assembly code of the
viewDidAppearmethod of theAddFundsViewControllerclass. 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 Modetab (the one with theif(b)text).
- You'll be able to see that the string
isProVersionis actually a key of an object stored in theNSUserDefaultsshared 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
ProVersionstate. You also saw in the previous exercise that you can load thepseudo-codeof a method. -
To analyze a method with its
pseudo-code: Note: use theARM64disassembly for this exercise.- Click on the
Procedurestab and search forWalletDetailViewControllerand select thedidUpdateWalletBalance:method. - Uncheck the
Remove potentially dead codecheckbox. Sometimes Hopper tries to optimize the decompiled code or just gets it wrong and thepseudo-codehas 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
ProVersionstate 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
FIRprefix, 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
Hoppertake into account that it will self-close every 30min.
Disassembling and decompiling the binary - Ghidra
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
ghidraRunbash script at the root of theghidra_9.0.1/directory. Note:Ghidrarequires 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
Ghidrayou'll have to create a project. Click onFileand thenNew Project...(or⌘ + N). - Choose if you want a
SharedorNon-Sharedproject.Sharedproject can be accessed by other users. - Select a directory to save your project and give it a name.
- Drag-n-drop the
CoinZabinary intoGhidra. Ghidrawill display a dialog saying that the file contains nested files.This is the same as Hopper telling you that it was aFATbinary and you need to choose an architecture. Select theBatchoption.- You'll be presented with a window showing you the two architectures in the binary.
AARCH64:LE:64:v8AisARM64andARM:LE:32:v8isARMv7. You can keep both selected, but since they are the same I'd suggest to just keep one selected. Ghidrawill 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
CoinZafolder and you'll see a file called eitherARM-32-cpu0x9orAARCH64-64-cpu0x0depending on the file you previously selected. - Drag-n-drop the
ARM-32-cpu0x9/AARCH64-64-cpu0x0file on top of theCodeBrowserbutton (the one with the dragon icon). Ghidrawill 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 withGhidrato know the different analyzers, that's why I suggested to leave the defaults. - In my computer
Ghidratook significantly longer thanHopper. - On the
Symbol Treewindow (on the far left) selectClassesand scroll down toWalletand select theWalletDetailViewControllerclass. - Within the
WalletDetailViewControllerfunctions search, again, fordidUpdateWalletBalance. - On the
Decompilerwindow (on right side of the split windows) you'll see the decompiled code of the method. Note: If you don't see theDecompilerwindow 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
Hoppershowed on itspseudo-codemode. - A huge advantage is that
Ghidrais free!
Conclusions
- 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.
Solutions
Find the solutions here.



