Sunday, October 29, 2017

"BLIND" Reversing - A Look At The Blind iOS App

"Blind is an anonymous community app for the workplace". In other words, if you as an employee have ever wanted to "speak freely" aka bash your employer/coworkers anonymously - because let's face it that's really what it comes down to - then BLIND is maybe the app you are looking for. This looked like a rather interesting app to "poke" at as I wanted to know just exactly what was happening under the hood.

Scope and Environment
My focus was on the app itself. And I registered with linkedin as opposed to my work email. This had some limitations chief among which was restricted access to the members area. I also did not look at all the functionality. Instead I chose to look at what I considered to be the core components. And even then I did not cover everything.

My environment was as follows:
- Jailbroken iPhone 5S running iOS 9.3.3
- jtool
- IDA Pro
- Hopper
- BurpSuite Pro
- Frida

Jailbreak Detection
The first thing I noticed is that the app did not have any jailbreak detection routine. Now before you go crazy and be like "well that's not a security issue.." I tend to agree with you. I don't think lack of these type checks is a security issue per se. I think more in terms of it being a part of a broader defense in depth approach.

Certificate Pinning
The second observation is that the app did not check the authenticity of the remote endpoint by verifying its SSL certificate(Certificate Pinning); therefore its feasible to perform Man-in-the-Middle (MiTM) attacks to eavesdrop on and tamper with data. Let me hasten to say however that this is not as bad as it sounds because first the attacker would have to trick the user into installing a malicious certificate on the device and second - as you will see later on - the app encrypts the data it sends to the back end.

On a side note the argument that the attacker needs to first trick the user into installing a rogue CA is a common one. And yet while I agree there, there are tools that aim to simplify the process. This blog post from Sensepost is one such example.

One final thing to note here regarding Certificate Pinning, is that the app provides two options for logging in: work email and LinkedIn Verification(see below). As I mentioned before I opted for the latter. A side effect of this choice was that because there was no pinning an attacker could capture the linkedin credentials. Provided of course you installed the malicious cert.

Login options
Obtaining The Binary
Ok so with those "issues" out of the way let's obtain the binary and start reversing. I use the dumpdecrypted dylib. So I simply ssh'd into the device and ran the following:

 root@Jekyl (/var/root)# su mobile  
 mobile@Jekyl (/var/mobile/Documents)# DYLD_INSERT_LIBRARIES=/var/root/dumpdecrypted.dylib /private/var/containers/Bundle/Application/3C411AB3-6018-4604-97D2-DC2A546EAB85/  
 mach-o decryption dumper  
 DISCLAIMER: This tool is only meant for security research purposes, not for application crackers.  
 [+] detected 64bit ARM binary in memory.  
 [+] offset to cryptid found: @0x1000d4f28(from 0x1000d4000) = f28  
 [+] Found encrypted data at address 00004000 of length 7995392 bytes - type 1.  
 [+] Opening /private/var/containers/Bundle/Application/3C411AB3-6018-4604-97D2-DC2A546EAB85/ for reading.  
 [+] Reading header  
 [+] Detecting header type  
 [+] Executable is a plain MACH-O image  
 [+] Opening teamblind.decrypted for writing.  
 [+] Copying the not encrypted start of the file  
 [+] Dumping the decrypted data into the file  
 [+] Copying the not encrypted remainder of the file  
 [+] Setting the LC_ENCRYPTION_INFO->cryptid to 0 at offset f28  
 [+] Closing original file  
 [+] Closing dump file  

Note that on iOS 9.3.3 running DYLD_INSERT_LIBRARIES=dumpdecrypted.dylib from root results in the process you are injecting into being killed:

 root@Jekyl (/var/root)# DYLD_INSERT_LIBRARIES=dumpdecrypted.dylib /private/var/containers/Bundle/Application/3C411AB3-6018-4604-97D2-DC2A546EAB85/  
 zsh: killed   DYLD_INSERT_LIBRARIES=dumpdecrypted.dylib   
 root@Jekyl (/var/root)#   

The workaround is to first switch to mobile and then cd into /var/mobile/Documents as shown above. Note also that we are able to inject our own dylib because the BLIND app does not have the __RESTRICT Segment.

 LC_SEGMENT_64     Mem: 0x100008000-0x100008000     __RESTRICT  
      Mem: 0x100008000-0x100008000          __RESTRICT.__restrict  

This is a null segment (size 0) which serves to notify DLYD not to trust any DLYD* environment variables.

Identifying Endpoints
One of the things I usually do when looking at a binary is to dump the strings and search for URL endpoints. I then use that list to corroborate Burpsuite traffic.

 macho-reverser:BLIND macho-reverser$ jtool -d __TEXT.__cstring teamblind.decrypted | grep "http"  
 Address : 0x1006dcfd0 = Offset 0x6dcfd0  

You can also use it to get a list of other potential targets as well. That leads us to Burpsuite. Let's fire it up and examine the traffic. Recall earlier I said that the BLIND app did not implement Certificate Pinning and that it wasn't all that of an issue because the app encrypted the data it sent to the backend. This is what a request looks like.

Sample request
The only thing we can confirm are some of the url's we identified earlier. So we are in fact BLIND. But if this data is in fact encrypted, then how and where is/are the encryption key(s) stored/generated? And can we see the plaintext data the app is sending to the server?

Extracting Classes 
To answer those questions, let's first dump the classes and see if anything looks interesting. We start by listing the segments and there we see references to Objective-C:

 macho-reverser:BLIND macho-reverser$ jtool -l teamblind.decrypted   
 LC 00: LC_SEGMENT_64     Mem: 0x000000000-0x100000000     __PAGEZERO  
 LC 01: LC_SEGMENT_64     Mem: 0x100000000-0x1007a4000     __TEXT  
      Mem: 0x100007a90-0x100663f18          __TEXT.__text     (Normal)  
      Mem: 0x100663f18-0x10066723c          __TEXT.__stubs     (Symbol Stubs)  
      Mem: 0x10066723c-0x10066a560          __TEXT.__stub_helper     (Normal)  
      Mem: 0x10066a560-0x100671ec0          __TEXT.__const       
      Mem: 0x100671ec0-0x1006dcfc9          __TEXT.__objc_methname     (C-String Literals)  
      Mem: 0x1006dcfd0-0x10074ca58          __TEXT.__cstring     (C-String Literals)  
      Mem: 0x10074ca58-0x100754bb2          __TEXT.__objc_classname     (C-String Literals)  
      Mem: 0x100754bb2-0x100767daa          __TEXT.__objc_methtype     (C-String Literals)  
      Mem: 0x100767daa-0x100768e18          __TEXT.__ustring       
      Mem: 0x100768e18-0x100788c4c          __TEXT.__gcc_except_tab       
      Mem: 0x100788c50-0x10078b967          __TEXT.__swift3_typeref       
      Mem: 0x10078b968-0x10078c6a0          __TEXT.__swift3_capture       
      Mem: 0x10078c6a0-0x10078d720          __TEXT.__swift3_fieldmd       
      Mem: 0x10078d720-0x10078e67d          __TEXT.__swift3_reflstr       
      Mem: 0x10078e680-0x10078edc8          __TEXT.__swift3_assocty       
      Mem: 0x10078edc8-0x10078f3c8          __TEXT.__swift2_proto       
      Mem: 0x10078f3c8-0x10078f478          __TEXT.__swift2_types       
      Mem: 0x10078f478-0x10078f4dc          __TEXT.__swift3_builtin       
      Mem: 0x10078f4dc-0x1007a3d20          __TEXT.__unwind_info       
      Mem: 0x1007a3d20-0x1007a4000          __TEXT.__eh_frame       
 LC 02: LC_SEGMENT_64     Mem: 0x1007a4000-0x100980000     __DATA  
      Mem: 0x1007a4000-0x1007a4ba8          __DATA.__got     (Non-Lazy Symbol Ptrs)  
      Mem: 0x1007a4ba8-0x1007a6dc0          __DATA.__la_symbol_ptr     (Lazy Symbol Ptrs)  
      Mem: 0x1007a6dc0-0x1007a6e00          __DATA.__mod_init_func     (Module Init Function Ptrs)  
      Mem: 0x1007a6e00-0x1007cfd20          __DATA.__const       
      Mem: 0x1007cfd20-0x10080f300          __DATA.__cfstring       
      Mem: 0x10080f300-0x100811498          __DATA.__objc_classlist     (Normal)  
      Mem: 0x100811498-0x1008114d0          __DATA.__objc_nlclslist     (Normal)  
      Mem: 0x1008114d0-0x100811890          __DATA.__objc_catlist     (Normal)  
      Mem: 0x100811890-0x1008118e8          __DATA.__objc_nlcatlist     (Normal)  
      Mem: 0x1008118e8-0x1008123b0          __DATA.__objc_protolist       
      Mem: 0x1008123b0-0x1008123b8          __DATA.__objc_imageinfo       
      Mem: 0x1008123b8-0x10092bf38          __DATA.__objc_const       
      Mem: 0x10092bf38-0x100944b20          __DATA.__objc_selrefs     (Literal Pointers)  
      Mem: 0x100944b20-0x100944c88          __DATA.__objc_protorefs       
      Mem: 0x100944c88-0x100946ee0          __DATA.__objc_classrefs     (Normal)  
      Mem: 0x100946ee0-0x100948918          __DATA.__objc_superrefs     (Normal)  
      Mem: 0x100948918-0x10094ee80          __DATA.__objc_ivar       
      Mem: 0x10094ee80-0x100965188          __DATA.__objc_data  

We can therefore dump the classes and their methods using the objc option of jtool - JCOLOR=1 jtool -v -d objc teamblind.decrypted:

Extract class information with jtool
Note that while I used IDA for this post there is nothing stopping you from using jtool for your disassembly needs. For example if you wanted to zero in on a particular class you could use jtool  like so jtool -d UserControl:getSecretUserDefaultString: teamblind.decrypted

Disassembling class info
Deciphering Encrypted Values
Ok so at this point we can intercept traffic, but alas, it's encrypted. And that's a bummer. Let's proceed with figuring out how the encryption is implemented. As you saw earlier, BLIND allows you to sign up using either your work email or linkedin. Past that screen that, you are presented with the Create Account option:
Sign Up
The settings for the app can be found in com.teamblind.blind.plist and is located at /private/var/mobile/Containers/Data/Application/<app_id>/Library/Preferences/com.teamblind.blind.plist. If you check the file at this point you will notice that it contains the plaintext email - and associated company - you signed up with. You can use the plutil utility to read the file.

plist snippet
Once you select your password and username and hit GET STARTED things change.

Now your email is no longer stored in plaintext but is encrypted(?), and your password, along with several other values get added. Keep in mind it's never a good idea to store passwords or anything sensitive for that matter in plist files.  The astute reader would have noticed that I did not black out the password_enc value. The name of the key suggests the value might be encrypted since it ends with _enc, but is it? One other thing I want to point out is that this value is integral to the encryption process. But we will get to that later. For now let's dig into this value some more.

As it turns out the "encrypted" password is nothing more than an md5 hash, and you can see this in the requestPassword method of the AuthCompleteViewController class.

Create password hash
At 0x000000010004EB50 we get the user supplied value and then calculate the md5 hash at 0x000000010004EB8C. To verify this, we fire up python where you see it is the same value from earlier(plist). Now you know my super secret password.

 >>> import hashlib  
 >>> m = hashlib.md5()  
 >>> m.update("password#1")  
 >>> print m.hexdigest()  

Identifying ViewControllers
If you are wondering how I determined what class to check, I first navigated to the Create Account page and then using cycript determined the ViewController like so:

 root@Jekyl (/var/root)# ps aux | grep blind  
 mobile  4136  0.1 5.8  815696 59532  ?? Ss  4:10PM  0:06.85 /var/containers/Bundle/Application/3C411AB3-6018-4604-97D2-DC2A546EAB85/  
 root   4139  0.0 0.0  657104  212 s000 R+  4:11PM  0:00.01 grep blind  
 root@Jekyl (/var/root)# cycript -p 4136  
 cy# [[[UIWindow keyWindow] rootViewController] _printHierarchy].toString()  
 "<UINavigationController 0x15615d000>, state: appeared, view: <UILayoutContainerView 0x157415a30>\n  | <RootViewController 0x1570db260>, state: disappeared, view: <UIView 0x157337ab0> not in the window\n  | <AuthCompleteViewController 0x155f587c0>, state: appeared, view: <UIView 0x155da07a0>"  

Recall as well that your was email encrypted. The encryption routine is also in the requestPassword method. Your email is first retrieved from the plist(NSUserDefaults) and then passed to the encryptHES256 method of NSString ([NSString encryptHES256:])

Get user email
The encryptHES256 method then generates an encryption key with a simple XOR of your password and some "random" value before handing off to the AES256EncryptWithKey method where the actual encryption takes place - technically this method calls another function but you get the picture. Can you spot the "randomness"?
Encrypt user email
With a little help from Frida we can see this in action. If you are not yet familiar with Frida I HIGHLY recommend that you familiarize yourself with it and add it to your arsenal. I cannot say enough about this tool. I also recommend checking out Frida CodeShare for ready to go scripts. Of course it goes without saying that you should review any code before running it.

Using Frida and the ObjC method observer script from CodeShare we can observe the AES256EncryptWithKey method in action:

 macho-reverser:BLIND macho-reverser$ frida -U --codeshare mrmacete/objc-method-observer -f com.teamblind.blind  
   / _ |  Frida 10.6.15 - A world-class dynamic instrumentation framework  
   | (_| |  
   > _ |  Commands:  
   /_/ |_|    help   -> Displays the help system  
   . . . .    object?  -> Display information about 'object'  
   . . . .    exit/quit -> Exit  
   . . . .  
   . . . .  More info at  
 Spawned `com.teamblind.blind`. Use %resume to let the main thread start executing!  
 [iPhone::com.teamblind.blind]-> %resume  
 [iPhone::com.teamblind.blind]-> observeSomething('*[* *AES256EncryptWithKey:*]');  
 (0x125fcdca0) -[NSData AES256EncryptWithKey:]  
 AES256EncryptWithKey: password#1^0123456789abcdefghijk  
 0x1001b25cc teamblind!0x11e5cc  
 0x1000e2c7c teamblind!0x4ec7c  

Ok so we know how our password and email are encrypted. And by the way figuring out the other encrypted values is basically repeating the above steps. Let's now move to the actual traffic.

As we saw earlier - while monitoring the traffic sent from the device - all requests included a payloadI searched IDA for this string, and after going through the results, found that most of the request parameters were set in the -[NetworkControl encRequestWithParams:showAlert:completionBlock:failBlock:] method.

Payload string search
The method first tries to retrieve a previously generated encryption key and initialization vector (iv), and if that fails it calls out to the makeKeyAndIvForEnc (-[EncriptControl makeKeyAndIvForEnc]) method of the EncriptControl class, and yes that is Encript with i. Security through obscurity maybe..... :)

Generating the encrypted payload
This is where things get somewhat interesting, as it appears the encryption key is generated using a combination of the users password and some hardcoded value. Remember the encrypted password(password_enc) from earlier? The method will first try to retrieve it:

Retrieve encrypted password(password_enc)
The method will then generate another md5 hash based on a hardcoded value:

Generate some static value
If their was an issue retrieving the user password, another hash is generated:

Generate second md5 hash
Finally the key is set and this ends up being either a combo of hash1+hash2 or hash1+password_enc

Generate the actual key
So in our case the encryption key should be md5("QkdEhdk") + md5("password#1"), which gives us "c07bcdc2 3522ed81 fb76db0c 0c4387cf 5486b4af 453c7830 dcea12f3 47137b07".

The remainder of the method just sets the initialization vector (iv):

Generate IV
Giving Sight To The Blind
The encRequestWithParams method of the NetworkControl class calls makeKeyAndIvForEnc of the EncriptControl class to setup encryption. Once that is done the encRequestWithParams method goes on to call makePayloadDataWithJsonString of the EncriptControl class. This method then calls aesEncrypt from CocoaSecurity - using the encryption keys and IV from earlier - and returns the base64 encoded cipher-text which is what you see in Burp.
Encrypt the payload
Go back to the jtool -d objc dump for a second, and take note of the instances variables for the EncriptControl class:

Encript instance variables
We now have all the pieces of the puzzle and can therefore craft a Frida script to give us the instance variables i.e. encryption keys etc as well as the plaintext data and corresponding ciphertext:

   var makeKandIv = ObjC.classes.EncriptControl["- makePayloadDataWithJsonString:"];  
     Interceptor.attach(makeKandIv.implementation, {  
      onEnter: function(args) {  
        /* Get Class/Params */  
        var obj = ObjC.Object(args[0]);  
        var params = ObjC.Object(args[2]);  
        /* Get ivars */  
        var ivar = obj.$ivars;  
        // Print ivars values   
        console.log("_encKey: " + ivar["_encKey"] + "\n");  
        console.log("_encIv: " + ivar["_encIv"] + "\n");  
        console.log("_encIvStr: " + ivar["_encIvStr"] + "\n");   
        console.log("_encKeyForDM: " + ivar["_encKeyForDM"] + "\n");   
        console.log("_encKeyForDM: " + ivar["_encIvForDM"] + "\n");   
        console.log("PARAMS: " + params);  
      onLeave: function onLeave(retval) {  
         console.log("Encrypted Payload: " + new ObjC.Object(retval).toString() + "\n");  

And so our once encrypted burp traffic:
Encrypted burpsuite traffic
Now becomes:
Plaintext data w/ encryption keys

Additionally, hooking the convertDictionaryEncWithResultStr: method of the EncriptControl class prints the plaintext server response. At this point you could consider using the Brida Burpsuite plugin for other options.

Well that's all I had for today. As I pointed out earlier I didn't register a BLIND account and so I didn't have access to the member's only section or other member only functionality. I didn't care to, either, I was just interested in the blob of data I saw going across the wires in the Burp. Given the nature and claims of the app I wanted to know more about what was happening under the hood. As such no malicious traffic was sent to the BLIND servers.

Happy hacking!!!

Disclaimer: This blog is intended for educational purposes only. Any actions and or activities related to the material contained within this Website is solely your responsibility.The misuse of the information in this website can result in criminal charges brought against the persons in question. This author will not be held responsible in the event any criminal charges be brought against any individuals misusing the information in this website to break the law.


  1. This comment has been removed by the author.

  2. It's so nice article thank you for sharing a valuable content. ios App development online course

  3. We are iPhone and iPad mobile app development experts with experience in catering to enterprises belonging to all domains.

  4. Thanks for sharing the knowledgeable stuff to enlighten us no words for this amazing blog.. learnt so many things I recommend everyone to learn something from this blogger and blog.. I am sharing it with others also..
    We are a professional training institute providing training under the following courses, if any one is looking for them click on the blink below:
    Summer training in Lucknow
    SAP training in Lucknow
    SAP FICO training in Lucknow
    Python Training in Lucknow
    Advance Java Training in Lucknow
    SEO Training in Lucknow
    Digital marketing training in Lucknow
    PHP Training in Lucknow
    Android Training in Lucknow
    am going to share it with others also, it is a nice article

  5. Thanks for the great post on your blog, it really gives me an insight on this topic.
    android app development services provider in pakistan