Reverse Engineering the Drexel One API

Tomer Shemesh
11 min readDec 20, 2016

--

As many Drexel programming students do, at one point I wanted to use the Drexel One API that the official mobile app uses for personal projects. The API has tons of information about students that is extremely useful and can be used in many amazing ways. Unfortunately Drexel has always been hush hush about the API and has no information about it available to the public. I managed to find the creator of the mobile app, so I tried to reach out to him, but I never got a response. This led me to spend time reverse engineering the API in my sophomore year, and I wanted to share what I learned. This is not only to show you how to use Drexel One’s API, but to show you how to go about reversing engineering other apps API’s as well. Use this at your own discretion though, as private APIs like this can change at any point with-out notice.

Where To Start

Reverse engineering can be a daunting task and takes a lot of time. In this post I will go through what I found and how I went about it. Please note that finding out how this works took many weeks of digging and searching, because I knew almost nothing about this topic when I started.

Intercepting Traffic

The first thing to try when trying to reverse a web API is intercepting the traffic from a mobile device. This is done to see where it is sending requests as well as what data it sends and receives. The general idea behind most APIs is that it will send your username and password to receive a token. This token is then sent with each subsequent request to authenticate it. See more info about this topic here.

Most APIs these days use SSL though which means any old packet sniffer like Wireshark won’t do the job. You need something that will allow you to set up a Man in the Middle attack on your phone that decrypts SSL traffic.

There are a few tools that will do this on mobile devices, but the one I recommend by FAR is Charles Proxy. It is pretty simple to set up; works on Windows, Mac, and Linux; has a great set of tools built in for everything you can imagine; and most importantly, includes SSL support. You can get a free trial and see how to set it up on their website. Basically, you install a custom certificate on your phone and send all traffic from your phone through your computer allowing the program to intercept and decrypt all SSL traffic. Once you have that all setup it’s time to start poking around. I used an iPhone with the Drexel One App, but the process will be almost the same on Android and any other mobile app. First, I reset the app in settings, so I could see the entire flow of API calls. Then, I started recording traffic and re- logged into the app to see what I could find…

Bingo! On the bottom of the list you can see my phone made some requests to the URL https://d1m.drexel.edu which I would bet is the Drexel One API URL.

After expanding the URL calls you can see all the routes it has made calls to:

After clicking on the calls you can see what was sent to the server and what was returned for each call. The call that you will find is the most useful is MeDelta, which returns:

  • Account announcements
  • Courses you’re taking
  • Holds on your account
  • Coop interview dates
  • Dragon card balances
  • Meal Plan data, etc.

After clicking around in the app, you will see that it makes more calls to other routes such as:

  • Your past grades
  • Advisor information
  • Drexel building information and locations
  • Drexel events
  • Sport events
  • Candid picture/question etc.

An absolute treasure trove of information for programmers who want to make apps for Drexel students to make their lives easier. The next step now is seeing how to replicate these calls yourself.

The place to start looking is the first call to /api/v2.0/Authentication where we will most likely be getting our token for the rest of the calls. After clicking on that call in Charles Proxy, you will see something similar to this:

You can see the request is a POST request to https://d1m.drexel.edu/api/v2.0/Authentication/ with a X-Http-Method-Override set to PUT in the header and returns an auth key, an expiration, the username, and what role I am.

After clicking on JSON Text on the middle, you can see what was sent to the server to get this response. Looks like it’s just a JSON with the username, password, and a bool option of whether or not you want to make sure you get a new AuthKey.

In a perfect world, this AuthKey would just be sent in the header of every subsequent request, making it easy to get any information you need. Unfortunately, once you look, you will realize they have added more security to this API which makes it harder to reverse engineer. Looking at the headers of the API calls shows it is doing something else for authentication. It even has a different header for requests going to the same route.

As you can see under the Authorization header, it sends: tjs326:sYdJ7U1tvBzdm0fkCbuMCJDCiIQ=:20161220053432+00

but looking at another call to MeDelta it sends this: tjs326:jxFqfzZqU2oEib6ogZTwMKb8ysA=:20161220054021+00

It looks like it’s some sort of signature or hash for each request. This can be used for a few reasons. First, it means if someone somehow gets access to your network and looks at your traffic, they won’t be able to make other requests for you. This is because they don’t have the original auth key, which is probably used to create the string. I also think that the time is used because the exact same requests have a different header, and it appends what i assume to be a timestamp to each request: 20161220054021+00

the request was sent at 12/20/16 00:40:21 according to Charles so

2016 12 20 05 40 21 +00 makes sense (05 is the hour in UTC which is 5 hours ahead of ET). Unfortunately, this is as far as we can go without diving deeper. The Authorization hash could be made in any way and there is no way to know how without the code. Fortunately, there are ways to do this, such as decompilation.

Decompiling The App

Decompiling apps is a way to see what’s going on inside. You can find decompilers for most languages with varying results. In my experience, Android is the best option to go with if you can, because it is written in Java. There are some great decompilers for Java that will give you back somewhat readable source code to look through, which can’t quite be said for others. In addition, if you do it on iOS, you need a jailbroken phone to pull the decrypted IPA app off your phone to look through. The best decompiler I have found for iOS is Hopper, which is pretty good but more difficult.

But Drexel One has an Android app so we will be going down that road instead. For this section, Android experience will be very helpful because you will recognize the project structure and have a basic idea where to look in the project. The first thing you will need to do is download the app’s APK. I use https://apkpure.com for this. Just paste in the apps URL in the search bar and click “download”.

Next you will need to decompile the APK. There is a great online website that will actually do this for you and give you a zip file with the decompiled project in it. It uses the apktool decompiler but does everything for you so no need to download a program and run the command line. Go to http://www.javadecompilers.com/apk and upload the APK we just got and save the zip to your computer. Once you unzip it you will see something like this:

If you have done Android development before then this may look familiar to you. If not, that’s fine. The most useful stuff will most likely be in the assets folder, the res/values folder, and any com/*/* folder and edu/*/* folders. The trick is knowing which parts can be ignored, such as default android classes and libraries that have been packaged in the app. For instance, the com folder doesn’t seem to contain anything interesting, only libraries:

The edu folder, however, contains this:

All the Java code we could ever want!

Now, here comes the boring part. It will take a while to find what you’re looking for. You will have to look around to find where things are and how classes call each other. Some first places to consider first would be helpers, such as Util classes, Network handler classes, and dashboard classes. After looking around for a while I found this in UserDashboard.java

 private class AuthGetRequest extends AsyncTask<String, Void, String> {
.........
protected String doInBackground(String... urls) {
String responseMessage = BuildConfig.FLAVOR;
String eTag = BuildConfig.FLAVOR;
for (String url : urls) {
try {

HttpClient httpclient = new DefaultHttpClient();
HttpParams params = httpclient.getParams();
HttpConnectionParams.setConnectionTimeout(params, REGISTRATION_TIMEOUT);
HttpConnectionParams.setSoTimeout(params, WAIT_TIMEOUT);
ConnManagerParams.setTimeout(params, 30000);
SharedPreferences appSettings = UserDashboard.this.getSharedPreferences("d1ma3", 0);
String user = appSettings.getString("user", BuildConfig.FLAVOR);
String authKey = appSettings.getString("authKey", BuildConfig.FLAVOR);
String time = Utils.getUTCdatetimeAsString();
String auth = new StringBuilder(String.valueOf(user)).append(":").append(Utils.generateHash(authKey, new StringBuilder(String.valueOf(url)).append(time).toString()).replace("\n", BuildConfig.FLAVOR)).append(":").append(time).toString();

HttpGet httpGet = new HttpGet(url);
httpGet.setHeader(HttpHeaders.ACCEPT, HttpRequest.CONTENT_TYPE_JSON);
httpGet.setHeader(AUTH.WWW_AUTH_RESP, auth);
HttpResponse httpResponse = httpclient.execute(httpGet);
Header responseEtag = httpResponse.getFirstHeader(HttpHeaders.ETAG);
if (responseEtag != null) {
eTag = responseEtag.getValue();
}
StatusLine statusLine = httpResponse.getStatusLine();
if (statusLine.getStatusCode() == 200) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
httpResponse.getEntity().writeTo(out);
out.close();
responseMessage = out.toString();
} else {
Log.e("User Dashboard.authGetRequest", "Could not retreive data: " + statusLine.getReasonPhrase());
httpResponse.getEntity().getContent().close();
Log.e("User Dashboard.authGetRequest", "Closing connection: " + statusLine.getReasonPhrase());
}
} catch (Exception e) {
e.printStackTrace();
}
}
return responseMessage;
}


..........
}

Now this looks like what we are looking for!

String user = appSettings.getString("user", BuildConfig.FLAVOR); String authKey = appSettings.getString("authKey", BuildConfig.FLAVOR); 
String time = Utils.getUTCdatetimeAsString();
String auth = new StringBuilder(String.valueOf(user)).append(":").append(Utils.generateHash(authKey, new StringBuilder(String.valueOf(url)).append(time).toString()).replace("\n", BuildConfig.FLAVOR)).append(":").append(time).toString();

I’m going to assume the “user” String is my username, tjs326, the “authkey” String is the authkey we got from the first API call, and the “time” String is that timestamp we saw in the request.

Now all we need is to figure out what’s happening in the “auth” String. It looks like it’s just “{my username}:{Utils.generateHash method}:{timestamp}”. Hey, that looks exactly like what our API call header looked like:

tjs326:jxFqfzZqU2oEib6ogZTwMKb8ysA=:20161220054021+00

Now we just need to figure out what’s happening in the generateHash call. It looks like it’s passing in the authkey as the first parameter, and the URL of the call with the time string appended as the second parameter. Let’s go take a look.

We go into the utils.java file and search for the function. Ah, here we go:

public static String generateHash(String key, String data) {
String hash = BuildConfig.FLAVOR;
try {
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(new SecretKeySpec(key.getBytes(HTTP.UTF_8), mac.getAlgorithm()));
hash = Base64.encodeToString(mac.doFinal(data.getBytes()), 0);
Log.v("D1M", hash);
return hash;
} catch (Exception e) {
System.out.println(e.getMessage());
return hash;
}
}

Ok so it looks like its just doing a HmacSHA1 hash of the URL and time using the AuthKey as the secret key and converting it into base64. Lets try this out. Go to https://www.liavaag.org/English/SHA-Generator/HMAC/

and paste in the URL and time as the input and the AuthKey you got before as the key. set the SHA variant to SHA1 and the output type as base64 and presto!

The result is the same as the header from before:

tjs326:jxFqfzZqU2oEib6ogZTwMKb8ysA=:20161220054021+00

We now have everything we need to make all the requests we could ever want. The rest of the poking around I will leave to you. Look around in Charles to see what different API calls return, and what you need to send to get it. If you look back to the decompiled folder and look in /res/values/strings.xml, you will see all the URLs theoretically supported by the API. Here are the first few.

Wait so how do I use this?

So this has some complicated concepts in it, and after reading all this it may still not be clear how any of this is used. I will give a smaller TLDR here as an overview though which may clear things up.

The first call you make will be to https://d1m.drexel.edu/api/v2.0/Authentication/ with the username and password which returns an AuthKey.

Then for each of the following requests you make to the API, you need to make a hash to put in the header. I will show you how to get this hash using MeDelta as an example:

https://d1m.drexel.edu/api/v2.0/ViewModel/MeDelta

Start by getting the current time in the format: yyyyMMddhhmmss+00

20161220054021+00

Then take them and append them together:

https://d1m.drexel.edu/api/v2.0/ViewModel/MeDelta20161220054021+00

Now take the HmacSHA1 of that using your AuthKey you got before as the secret and convert it to base64 which will be something like:

jxFqfzZqU2oEib6ogZTwMKb8ysA=

Then you just take {the username}:{That hash}:{that time string}

tjs326:jxFqfzZqU2oEib6ogZTwMKb8ysA=:20161220054021+00

and send it in the header with the key Authorization and the value as the hash.

Many of the API calls are POSTs ,not GETs, and require sending a JSON to specify exactly what you want. This can all be seen using Charles though.

Here is an example Python script, which will get the classes a user is currently registered for:

import requests
import time
import json

def sign_request(username, url, authkey):
from hashlib import sha1
import hmac

time.ctime()
timestring = time.strftime('%Y%m%d%H%M%S') + '+00'
raw = url + timestring

hashed = hmac.new(authkey, raw, sha1)

return username + ':' + hashed.digest().encode("base64").rstrip('\n') + ':' + timestring


username = 'XXXXXX'
password = 'XXXXXXXXX'

url = 'https://d1m.drexel.edu/api/v2.0'

print 'Signing in...'
data = {}
data['UserId'] = username
data['Password'] = password
data['ForceNewKey'] = 'false'
res = requests.post(url + '/Authentication/', json=data, headers={'X-Http-Method-Override':'PUT'})

print 'Received Result:\n' + json.dumps(res.json())
authkey = str(res.json()['AuthKey'])


print('Getting User Classes...')
authorization = sign_request(username,url + '/Student/CourseSections',authkey)
courses = requests.get(url + '/Student/CourseSections', headers={'Authorization':authorization})


print('User Classes:\n' + json.dumps(courses.json()))

Follow up

The first thing I would like to reiterate is that this process is slow. Sure it’s quick to read the code on this post and see how it works, but personally reading through everything and figuring out how it works took a few weeks.

Additionally, this is not a publicly supported API, so I don’t know how much Drexel wants you using this. This post shows how the API works now, but it could be changed at any time, so use it at your own risk.

--

--