Tuesday, February 20, 2018

Creating Your Own iOS 11.1.2 Jailbreak With The QiLin Toolkit

Introduction
Jonathan Levin recently released the QiLin, a toolkit, whose aim is to "alleviate the
jailbreak enthusiast or security researcher from the nooks and crannies of post-exploitation, and
to standardize jailbreaking in a way which will be as forward compatible as possible." The toolkit is used in his LiberiOS and LiberTv jailbreak - the former of which is based on Ian Beer's aysnc_wake.

At the time of writing this post, I couldn't find a step-by-step tutorial on how to get started with QiLin. So I decided to quickly add some notes on doing just that.

Environment Setup
Here are some steps to get started:
  1. Download/open Xcode
  2. Download Ian Beer's async_wake project(see previous link) 
  3. Download the QiLin object files(see previous link). You need qilin.o as well as sha1.o/sha256.o
  4. Save the .h file from the QiLin wiki to a file.(eg QiLin.h)
  5. Open the async_wake project downloaded in step 2 and add the QiLin object files downloaded in step 3 to the project.  
    Adding QiLin object files
  6. Drag and drop the QiLin.h file saved in step 4 to the project. Now is also a good time to add the iOSBinpack and tar utilities. Note the tar utility is apart of the binpack. At this point, your project should look like the following: 
QiLin header and utilities
Fixing Errors
Trying to build the project at this point will fail. Let's fix that. Before proceeding, ensure you are signed into Xcode and then update the Build Identifier by navigating to General -> Identity -> Build Identifier.

You most likely will end up with an error indicating that sha256.o does not contain bitcode. Navigate to Build Settings and toggle off Enable Bitcode.
Disable bitcode
One other likely error is that of duplicate symbols. Looking at QiLin's exported functions, reveals that one of the methods is called _go.
Duplicate methods
However, the async_wake project also has a go method(see didFinishLaunchingWithOptions). To fix this, highlight the method(Xcode) then right click and select Refactor -> Rename. I updated mine to jbme(). After that the project should build without errors.

LiberiOS
With the environment up and running, we can now delve into the toolkit. First off, the writeup(linked earlier) does provide an idea of which methods to call, but let's have a look at LiberiOS to see how it uses the toolkit. We will then use that as a guide for our purposes. 

LiberiOS first calls doIt() which initializes the toolkit with a call to initQiLin(). The method takes the both the kernel task port and kernel base as arguments. 
init QiLin
Once initialized, the _go() method is called and this is where the magic happens:

 jtool -d _go LiberiOS | grep BL  
 Disassembling from file offset 0xe0e4, Address 0x10000e0e4 to next function, mmapped 0x120f46000  
   10000e0f4     BL   _rootifyMe     ; 0x10000d544  
   10000e118     BL   libSystem.B.dylib::_printf     ; 0x100013aec  
   10000e120     BL   libSystem.B.dylib::_getpid     ; 0x1000138b8  
   10000e124     BL   _getProcStructForPid     ; 0x10000b234  
   10000e128     BL   _ShaiHulud2ProcessAtAddr     ; 0x10000b854  
   10000e14c     BL   libSystem.B.dylib::_fopen     ; 0x100013870  
   10000e170     BL   libSystem.B.dylib::_fprintf     ; 0x10001387c  
   10000e184     BL   Foundation::_NSLog     ; 0x100013738  
   10000e188     BL   _platformizeMe     ; 0x10000bfb0  
   10000e1b4     BL   _borrowEntitlementsFromDonor     ; 0x10000de0c  
   10000e1c4     BL   libSystem.B.dylib::_sleep     ; 0x100013b70  
   10000e1d8     BL   _entitleMe     ; 0x10000d28c  
   10000e1e0     BL   libSystem.B.dylib::_getpid     ; 0x1000138b8  
   10000e1f4     BL   _entitlePidWithKernelEnts     ; 0x10000c904  
   10000e200     BL   libSystem.B.dylib::_sleep     ; 0x100013b70  
   10000e208     BL   _remountRootFS     ; 0x100009c64  
   10000e210     BL   _castrateAmfid     ; 0x10000ac24  
   10000e234     BLR   X8                      ; 0x24107ff7e64028f0  
   10000e25c     BL   _unpackBinariesToPath     ; 0x10000da98  
   10000e264     BL   _disableAutoUpdates     ; 0x100009f10  
   10000e288     BL   libSystem.B.dylib::_copyfile     ; 0x100013840  
   10000e2a4     BL   libSystem.B.dylib::_mkdir     ; 0x100013a2c  
   10000e2e4     BL   _execCommand     ; 0x10000d7cc  
   10000e310     BL   _spawnAndPlatformize     ; 0x10000dfa8  
   10000e344     BL   _spawnAndPlatformize     ; 0x10000dfa8  
   10000e370     BLR   X8                      ; 0x24107ff7e6402910  
   10000e374     BL   libSystem.B.dylib::_getpid     ; 0x1000138b8  
   10000e378     BL   _getProcStructForPid     ; 0x10000b234  
   10000e380     BL   _ShaiHuludProcessAtAddr     ; 0x10000b99c  

The QiLin writeup already has an explanation for what most of the methods do. But even so, just looking at the above list and the IDA disassembly, it's pretty straightforward what is going on. Keep in mind, you might not be interested in including all of the advertised functionality e.g. disableAutoUpdates()/sendStatToJ() in your jailbreak.

Ok, so all we need to do is call those methods - from the toolkit - in our project and we are done. Post exploitation has never been easier!

QiLin Toolkit
As mentioned earlier, the first step in using the toolkit is calling the initQiLin (mach_port_t TFP0, uint64_t KernelBase) method. The method takes tfp0(task_for_pid0) i.e. SEND right to the kernel task as well as the kernel base. async_wake already provides tfp0 via the get_kernel_memory_rw() method. So all we need to determine is the kernel base. Determining the kernel base involves searching kernel memory for the magic bytes 0xfeedfacf.
Determining kernel base
One frequently asked question is how do I determine the kernel base. I used the following code - taken from the Coalfire-Research Labs project:

 // Code lifted from https://github.com/Coalfire-Research/iOS-11.1.2-15B202-Jailbreak  
 extern uint64_t find_port_via_kmem_read(mach_port_name_t port);  
 uint64_t dump_kernel(mach_port_t tfp0)  
 {  
   // ok, where the f*ck is the kernel  
   // uint64_t kernel_base = 0xfffffff00760a0a0; //15B202 on iPhone 6s  
   mach_port_t self = mach_host_self();  
   uint64_t port_addr = find_port_via_kmem_read(self);  
   uint64_t search_addr = rk64(port_addr + 0x68); //KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT  
   search_addr &= 0xFFFFFFFFFFFFF000;  
   printf("[+]\tGoing backwards until magic seen....\n");  
   while (1)  
   {  
     if (rk32(search_addr) == 0xfeedfacf)  
     {  
       printf("[+]\tOk, looks like we've found the beginning of the kernel!\n");  
       printf("[+]\tKERNEL IS AT 0x%llx\n", search_addr);  
       return search_addr;  
     } else {  
       search_addr-=0x1000;  
     }  
   }  
 }  
 // End lift  

iOS kernelcache
As an aside, you can obtain the kernel by navigating to ipsw and selecting your device. Once downloaded, change the extension to .zip and unzip it. The kernelcache will be labeled kernelcache.release.iphone<x>. This file has been compressed and must therefore be decompressed before any analysis can occur.  You can use lzssdec for this, but the easier option is using joker.
Decompressing kernelcache
Show Me Code
Before we actually write any code, let's look at the overall flow:
Jailbreak flow
And with that, the actual code(jbme() snippet) then looks like:
 mach_port_t tfp0 = get_kernel_memory_rw();  
  printf("tfp0: %x\n", tfp0);  
  /*- Determine kernel_base -*/  
  uint64_t kernel_base = 0;  
  kernel_base = dump_kernel(tfp0);  
  /*- Initialize QiLin toolkit -*/  
  qInit = initQiLin(tfp0, kernel_base);  
  /*- Get root privs -*/  
  printf("[+]\tCalling rootifyMe.....!\n");  
  rootMe = rootifyMe();  
  /*- Unsandbox -*/  
  printf("[+]\tUnsandboxing.....!\n");  
  uint64_t origCreds = ShaiHuludMe(0);  
  printf("[+]\tPlatformizing.....!\n");  
  /*- PlatformizeMe -*/  
  platformizeMe();  
  /*- Get Entitlements -*/  
  printf("[+]\tBorrowing Creds.....!\n");  
  uint64_t origEnt = borrowEntitlementsFromDonor("/usr/bin/sysdiagnose","-u");  
  sleep(3);  
  sleep(1);  
  /*- Remount / -rw -*/  
  printf("[+]\tRemount Root file system.....!\n");  
  remountRootFS();  
  /*- Kill amfid-*/  
  printf("[+]\tCastrate AMFID.....!\n");  
  amfid = castrateAmfid();  
   if(amfid){  
     printf("[+]\tFAILED to Castrate AMFID.....!\n");  
   }else{  
     /*- Unpack tools -*/  
     unpackBinariesToPath("binpack64-256.tar", "/jb", "tar");  
     /*- Get your shell -*/  
     copyfile("/jailbreak/etc/motd", "/etc/motd", 0, 0xA);  
     mkdir("/etc/dropbear", 0755);  
     execCommand("/jb/usr/local/bin/dropbear", "-R", "--shell", "/jb/bin/bash", 0, 0, 0);  
   /*- Go back into the sandbox-*/  
     pid_t myPid = getpid();  
     uint64_t myProcStruct = getProcStructForPid(myPid);  
     ShaiHuludProcessAtAddr(myProcStruct,origEnt);  
   }  

Once executed you should end up with your shell :
shell access
The good thing about the toolkit, is that you can view what is happening during each method call - which is very helpful to say the least.

QiLin debug info
QiLin Notes
While using the toolkit, you may notice that not all exported functions are included in the .h file. To fix that, simply determine the arguments of the function you are interested in - using your favorite disassembler - and then update the .h file. For example, to include the unpackBinariesToPath method, determine the arguments:

unpackBinariesToPath function arguments
And then update the QiLin.h file:
Update QiLin header file
The astute reader would have also noticed that _getProcStructForPid and _ShaiHulud2ProcessAtAddr were replaced with a call to ShaiHuludMe(0), additionally recent versions include an unShaiHuludMe method. However, the latter has not yet been included in the latest qilin.o object file.

Conclusion
With the advent of the QiLin toolkit, post exploitation has never been easier. Thanks to Jonathan Levin for this awesome kit. This should be enough to get you up and running. 

Have fun!!!!

UPDATE: Just thought I would add that the following calls can be replaced by unShaiHuludMe (as soon as the object file is updated)

 /*- Go back into the sandbox-*/  
     pid_t myPid = getpid();  
     uint64_t myProcStruct = getProcStructForPid(myPid);  
     ShaiHuludProcessAtAddr(myProcStruct,origEnt);  

The method has already been added to the header file:
unShaiHuludMe in current QiLin.h
Additionally, prior to calling the above block that puts you back in the sandbox, you should check the return value from the call to borrowEntitlementsFromDonor.

Oh and its NOT async_wait it's async_wake. Don't ask how I missed that :) Thanks for pointing that. out.

Sunday, January 28, 2018

A Look Into Why macOS High Sierra's App Store Preferences Can Be Unlocked With Wrong Credentials

Introduction
macOS High Sierra, AAPL's latest OS offering, seems to be "High" on something ever since it's release. Earlier in November of last year, High Sierra users were greeted with the news that anyone could login as root after clicking on the login button several times. Following on the heels of that announcement, a couple weeks ago, users were again alerted to another issue, that saw a user with administrative privileges being able to unlock the App Store menu in System preferences with any password. This latter issue piqued my interest, and I decided to take a look to see if I could understand the underlying flaw. This blog post chronicles that journey.

App Store Preferences
Authentication
In order to understand the bug, it is important that we first examine how the OS handles authentication. For this, we first turn our attention to the Pluggable Authentication Module(PAM). This is a standard UN*X library which aims to abstract and modularize the UN*X authentication API's.

The main PAM module used by macOS is /usr/lib/pam_opendirectory.so.2. The library links with the OpenDirectory.framework, which interfaces with the /usr/libexec/opendirectoryd daemon. The daemon serves as a focal point for all directory requests in the system. The configuration for this daemon is maintained in the /System/Library/OpenDirectory/. This directory, contains a Modules sub-directory which further contains plugins for various types of directory implementations.

Of importance to us is the /System/Library/OpenDirectory/Modules/PlistFile.bundle, which enables access to directory data stored in AAPL's format of property lists(.plist). The corresponding binary is found in /System/Library/OpenDirectory/Modules/PlistFile.bundle/Contents/MacOS and is aptly named PlistFile.

Updating App Store Preferences
With the groundwork out of the way, let's look at a normal authentication flow. The bug was not present on High Sierra 10.3, and so I started my investigations there.

 macho-reverser:opend macho-reverser$ jtool -l opendirectoryd_10_3  
 LC 09: LC_VERSION_MIN_MACOSX    Minimum OS X version:  10.13.0  
 LC 10: LC_SOURCE_VERSION        Source Version:     483.1.4.0.0  

Unlocking App Store preferences and setting a break point on Password resulted in the following hit:

Password Breakpoint hit
Here, opendirectoryd called the  odm_RecordVerifyPassword method from the PlistFile. This is in keeping with what we discussed earlier, given we are authenticating against a local directory(plist format). A quick look at the function, and we see it calls two other methods - sub_18F1 and sub_826B:
Helper Methods

sub_18F1
Based on the above, the method takes three parameters. Note, on x86_x64 architectures, the first six parameters are passed in registers RDI, RSI, RDX, RCX, R8 and R9 respectively. If there are more than six, the program's stack is used for any additional ones. Setting a breakpoint at the function's entry point and examining the parameters reveals the following:

sub_18F1 arg 3
Ok, so this seems to be a dictionary containing data about the user that is trying to authenticate. Keep in mind, that the attributes shown in the above image, can also be read using the dscl tool.

dscl queries

sub_826B
Moving on, this method, seems to be where the action is, and we again start the analysis by examining its arguments. To set the breakpoint, we determine the ASLR slide as follows:

ASLR Slide
And then add that value to the function offset in IDA:

sub_826B offset
Another way of determining the breakpoint address is to rebase the segment in IDA. Using the ASLR slide determined earlier, from IDA, navigate to Edit -> Segments > Rebase -> Image base and enter the value. With the breakpoint set, the function arguments are:

Function parameters
Tracing through the method, we see a call to function sub_591C


sub_591C function call
And printing the arguments:
sub_591C paramter
The method then calls the CCKeyDerivationPBKDF method to derive a key from the password, and returns the resulting key in parameter v29 :

Generate key from CCKeyDerivationPBKDF
 This value is then compared against the stored value with a call to bcmp :

Comparing user supplied value with stored credentials

That is basically the flow for unlocking the App Store Preferences Pane. Also, to kick start this entire process, it seems that opendirectoryd registers a number of handlers for various requests:
API handlers
Of interest to us would be the odm_handle_api_authentication handler, as this handles calls to ODRecordVerifyPassword.

The Bug on High Sierra(10.3.2)
With all that said, on High Sierra(10.3.2) none of this happens. In other words, setting a breakpoint on odm_handle_api_authentication or even VerifyPassword produces not a single hit. Simply put, no validation takes place. It's almost as if the call was removed altogether. However, on High Sierra(10.3.3) - where the bug has been fixed - things go back to normal and once again the odm_RecordVerifyPassword method is called. Go figure!

Conclusion
In the end it seems somewhere along the line there was a breakdown.This resulted in the password verification code block not being called at all. I am not sure of where the exact call into opendirectoryd occurs and that may be worth a further look.....

Hope you found the post interesting. As always, comments/feedback welcome.

Sunday, November 26, 2017

Exposing Your "Privates!"

Introduction
The following tweet popped up in my feed recently and for some reason it piqued my interest. Hey don't judge me :)

Tweet Behind This Post
The article basically provides a guide to sexting securely. If you are so inclined then you should check it out. The article suggests several apps for this type of activity including Privates! which is the focus of this post. Up until this article I had not heard of this app and so I decided to check it out. Note I didn't spend a lot of time of on it. I had some spare time and decided to give it a quick look. I really should be working on finishing up the AMFI series, but I digress. Let's get on with it then....

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

Jailbreak Detection
I installed the app and launched it on my jailbroken device at which point I was presented with the following screen:

Jailbreak Detection
I guess that's that then....review done, right? Far from it. I first tried bypassing the detection using the usual suspects like tsProtector 8+ and xCon, both of which failed. I don't usually use these tweaks by the way I just decided to give them a quick run. The next step then was to obtain the binary, and as discussed in the "BLIND" Reversing - A Look At The Blind iOS  post I use dumpdecrypted for this:
Obtaining The Binary via Dumpdecrypted
With binary in hand I fired up IDA Pro to see what was up. The first thing I searched for was occurrences of the strings shown in the jailbreak detection notification. When that came up short I looked for references to common jailbreak artifacts like "Cydia", "/usr/bin/ssh","/usr/bin/sshd" etc. This led to the -[BMDevice files] method:

Jailbreak artefacts
And this method was referenced by the -[BMDevice isValid]  method as shown:

isValid Snippet
The method also checks for things like whether or not the call to fork was successful - this call should fail on a non-jailbroken device. So essentially this detection is your typical run of the mill routine. I only went into this level of detail to explain the approach and give you some tips on what to look for.

Jailbreak Bypass
Bypassing the check is trivial. The function returns a boolean value - bool __cdecl -[BMDevice isValid](BMDevice *self, SEL) - so whatever it returns we just need to flip it. For this we turn to Frida. The next step is to determine the app identifier and for that you issue frida-ps -Uai

Process List via Frida
We then hook the -[BMDevice isValid] method and check it's return value:

Determining Return Value
Where the private.js script is defined as:

 if(ObjC.available){  
   var auth = ObjC.classes.BMDevice["- isValid"];  
     Interceptor.attach(auth.implementation, {  
       onLeave: function onLeave(retval) {  
       console.log("\t[-] Type of return value: " + typeof retval);  
       console.log("\t[-] Original Return Value: " + retval);  
      }  
   });  
 }  

The bypass then is simply to flip the return value from 0 to 1:

 if(ObjC.available){  
   var auth = ObjC.classes.BMDevice["- isValid"];  
     Interceptor.attach(auth.implementation, {  
       onLeave: function onLeave(retval) {  
       console.log("\t[-] Type of return value: " + typeof retval);  
       console.log("\t[-] Original Return Value: " + retval);  
       newretval = ptr("0x1")  
       retval.replace(newretval)  
       console.log("\t[-] New Return Value: " + newretval)  
      }  
   });  
 }  

And with that the jailbreak detection is bypassed.

Authorization
With the jailbreak detection bypassed the next step was to examine the traffic. The app presents the following login screen:

Login
And upon successful login, sends requests similar to the following:

Sample Requests
Now this is where things get interesting. Let's take a look at the request to the retrieve messages endpoint - /messages:


Basic Authorization
You are indeed seeing correctly, Privates! is using Basic Authorization. For those that may not be aware this is just a base64 encoded value usually consisting of a username:password pair. In the case of Privates! the value is the user's phonenumber:password. Generally this approach is not recommended.

This brings up an interesting observation, apart from redirecting you to the login screen, the logout functionality shown below really doesn't do much, does it?

Logout
Confirmation Bypass
Another interesting thing was that if the app didn't recognize the device you were logging in from you would be sent a confirmation a code. Fair enough. Now let's say I somehow captured someone's credentials and attempted to log in from my own device. The victim would receive an SMS message with the code and since I as the attacker don't have access to their device, the victim would simply ignore it and carry on, because after all whoever is trying to access their account doesn't have the 6 digit pin.

Confirm Flow
An incorrect confirmation attempt results in a 400 Bad Request response from the server:
Server Response Incorrect Code
Can you see where this is going yet? All the attacker has to do is forge a successful response from the server and they have bypassed the confirmation check. Figuring out a successful response is easy. Simply copy the response from a successful request to the /login endpoint.

Forged Success

Connected Device Bypass
Another feature of Privates! is that it doesn't allow you to view messages if the device is connected(read connected via usb). If the device is connected, trying to read a message results in the following message:

Device Connected Error
Incidentally, take note of the Viewing Period as we will get back to it. Ok, so to track this down I again searched for the displayed message and found it in the -[BMMessageConnectionViewController updateWithUSBMessage] method.

updateWithUSBMessage Responsible For Displaying Message
I then searched for occurrences of updateWithUSBMessage which led to the [BMMessageConnectionViewController handleConnectionChange] method:

handleConnectionChange Method
This method then called the isDeviceDiconnected(yes without the s, I guess as an anti-debugging feature right....) method at offset 0x00000001000C3E40. As it turns out this method returns a boolean value. If you are wondering how it determines if the device is connected it simply checks the battery state(using UIDevice) after all if the device is connected the battery will be charging right:

Checking Battery State
Bypassing this check is therefore similar to the jailbreak detection bypass in that we determine the original return value and then flip it. We end up with the following:

 var device = ObjC.classes.BMMessageConnectionViewController["isDeviceDiconnected"];  
     Interceptor.attach(device.implementation, {  
       onLeave: function onLeave(retval) {  
       console.log("\t[-] DeviceConnected Type of return value: " + typeof retval);  
       console.log("\t[-] DeviceConnected Original Return Value: " + retval);  
       newretval = ptr("0x1")  
       retval.replace(newretval)  
       console.log("\t[-] DeviceConnected New Return Value: " + newretval)  
      }  
   });  

And with that the check is bypassed and we can carry on. One thing to note here is that the isDeviceDiconnected method is a class method(+ class method, - instance method).

Viewing Timer Bypass
So I hinted at this earlier. The sender of the message has the option of specifying the Viewing Period of the message:
Setting Viewing Period
From the above settings you only get a maximum of 30s. In fact there are three settings you can choose from mild, wild and insane with Viewing Periods being 30, 15 and 10 seconds respectively. But let's suppose I want to errrrrmmmmm how do I say this....."enjoy the image" a bit longer. Well the astute reader would have noticed that communication between the app and the server have the following Content-Type: application/json. Do you see where this going? No way what I am about to do should work right? Well, when you retrieve a message one of the parameters included in the response is duration - highlighted in red:

Setting Viewing Duration
To bypass the check just change this to a value of your choosing and enjoy those images...Here I changed it to 60 seconds:
Extended Viewing
Security Options Bypass
Even more interesting is that the sender can specify the actions the recipient has to take before being able to view the message. These actions are defined as Security Options - security_options": ["touch", "motion", "camera"]. Camera means a picture is taken, motion means you have to hold the device a particular way and touch is exactly that - you have to tap the screen. Again you can edit this list and remove the actions you don't want to perform and the sender would be none the wiser.

Unhandled Exceptions
In some instances, the app returned some rather, dare I say "helpful" error messages:

Verbose Errors
This is kind of information is often useful to attackers as it can be used to plan further attacks. At the very least, it shows that some "conditions" were not being properly handled. Let me hasten to point out that I didn't hack the server. The above error message was generated when I attempted to retrieve a message that had been recalled by the sender.

Conclusion
In the end you use these apps at your own risk. The app definitely has areas that it needs to improve on. I didn't even bother getting into the crypto because this was just some weekend fun(the real reason is crypto is hard). I am sure there are other areas to look at - as I said earlier this was by no means an in-depth review - and if you are so inclined this post should have provided enough information for you to go further. At the very least it should have provided some food for thought.

Until next time....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.

Sunday, October 29, 2017

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


Intro
"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/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
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 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
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:

 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:
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.

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.