. 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.
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.
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.
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/teamblind.app/teamblind
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
0x1006df366: https://api.linkedin.com/v1/people/~:(id,email-address,first-name,last-name,headline,num-connections,industry,summary,specialties,positions:(id,title,summary,start-date,end-date,is-current,company:(id,name,universal-name,type,size,industry,ticker,email-domains)),educations:(id,school-name,field-of-study,start-date,end-date,degree,activities,notes),associations,interests,num-recommenders,date-of-birth,publications:(id,title,publisher:(name),authors:(id,name),date,url,summary),patents:(id,title,summary,number,status:(id,name),office:(name),inventors:(id,name),date,url),languages:(id,language:(name),proficiency:(level,name)),skills:(id,skill:(name)),certifications:(id,name,authority:(name),number,start-date,end-date),courses:(id,name,number),recommendations-received:(id,recommendation-type,recommendation-text,recommender),honors-awards,three-current-positions,three-past-positions,volunteer)?format=json
0x1006df80e: http://us.teamblind.com
0x1006e19ad: https://api.linkedin.com/v1/people/~:(id,email-address)?format=json
0x1006e75df: https://m.facebook.com/settings/email
0x1006e760c: https://www.linkedin.com/m/settings/email
0x1006ea5ec: https://docs.google.com/forms/d/e/1FAIpQLSc_J26TtkDL7HXcLeFXC2jy6lb1PmJSPnh51_ng7fr1638p_Q/viewform
0x1006ee9c3: https://www.linkedin.com/uas/oauth2/authorization?response_type=code&client_id=%@&scope=%@&state=%@&redirect_uri=%@
0x1006f4865: https://krapi.teamblind.com
0x1006f4881: https://usapi.teamblind.com
0x1006f489d: http://kr.stage.teamblind.com:8080
0x1006f48c0: http://us.stage.teamblind.com:8080
0x1006f48e3: http://dev.teamblind.com:8080
0x1006f4901: http://us.dev.teamblind.com:8080
0x1006f4922: https://kr.teamblind.com
0x1006f493b: https://us.teamblind.com
0x1006f4954: https://krnotifier.teamblind.com
0x1006f4975: https://usnotifier.teamblind.com
----
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()
5486b4af453c7830dcea12f347137b07
>>>
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/teamblind.app/teamblind
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>"
cy#
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 |
Frida
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 http://www.frida.re/docs/home/
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
payload. I 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 |
encRequestWithParams
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 |
makeKeyAndIvForEnc
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: