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.
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/teamblind.app/teamblind
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/teamblind.app/teamblind 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/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 |
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 |
Disassembling class info |
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 |
plist snippet |
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 |
>>> 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 |
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 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 |
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) |
Generate some static value |
Generate second md5 hash |
Generate the actual key |
The remainder of the method just sets the initialization vector (iv):
Generate IV |
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 |
Encript instance variables |
if(ObjC.available){
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("-----------------------------------------------------------\n");
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("-----------------------------------------------------------\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:
Now becomes:
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.
Conclusion
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.
Encrypted burpsuite traffic |
Plaintext data w/ encryption keys |
Conclusion
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.