During COVID-19, my university (UTAR) debuted an attendance tracking called Hi-Hive Community. A lecturer would present a QR code, and the students will scan it to take attendance.

After using the application for a few years, I noticed that it was too bloated with unnecessary features, it’s not very responsive, and important items are buried in unending levels of menus.

Thus, I have the idea of rebuilding the application using Kotlin, Jetpack Compose, and more modern UI frameworks.

To do so, I first needed to figure how the API works.

Preliminary Analysis

Through regular usage, I suspected that it uses some kind of web/mobile hybrid framework.

Some symptoms I’ve noticed are:

  1. Slow and slightly off user interface - There will be slight delays when tapping on elements, animation is slow, and low frame rates at times.
  2. Lack of uniformity in the components - A lot of the components seems custom made. There are a mix of native elements with custom ones.
  3. Existence of web-view pages - Some of the functions are loaded through webview.

All in all it feels like using a website. My gut feelings tell me that it’s a web/mobile hybrid framework. Most likely using React Native.

Dissecting the Application

The only method to really confirm this is by cracking it open and have a look at the internals. Here, I will be using version 2.3.1 of the application downloaded from ApkPure.

Unpacking the .apk

I unpacked the .apk using jadx and immediately there are telltale signs of React Native.

For example, this image below shows configuration for a react native push notification library.

Image of configuration for a React Native library

Which upon further investigation, it appears that this was the library used: https://github.com/zo0r/react-native-push-notification. It’s a React Native library.

Checking for presence of index.android.bundle

Furthermore, there is also presence of index.android.bundle as seen below.

Image of index.android.bundle in file structure

The index.android.bundle contains compiled and bundled JavaScript code for the application. Only React Native bundles this.

Disassembling Hermes Bytecode

Immediately, I tried to open the bundle using a text editor but it showed an error.

Image of cannot open bundle

After a google search, I found out that Hermes VM is the default compilation target for more recent versions of React Native. Which is confirmed using the file command:

Image of file tool showing result

Fortunately, P1 Security released a tool to disassemble, and decompile Hermes bytecodes.

Using the tool (hermes-dec), I disassembled the Hermes bytecode into somewhat readable pseudo-javascript.

Image of not so readable pseudocode

However this is still super unreadable, due to it being converted from javascript to obfuscated javascript to hermes bytecode to decompiled WASM to javascript pseudocode.

This meant that it’s a “readable” version of the WASM instructions, not how the original code is structured.

Ideally, I want to get the version of the application that converted javascript to obfuscated javascript which should be more readable, and actually represent the original code.

Back to the square one and cracking an older version

The hermes-dec project README states that React Native only started targetting the Hermes VM by default after React Native v0.70.

I thought, what if the targetting of Hermes VM isn’t actually on purpose by the developer? So I grabbed version 1.0.2 of the application which was released before React Native v0.70, and cracked it open.

As predicted! The index.android.bundle file is only obfuscated Javascript, not Hermes bytecode!

Image of more readable bundle

This is a more readable version of the index.android.bundle file compared to the one from version 2.3.1, not by much but it’s still better than nothing.

What this means is that I now have a base from which to reverse engineer the API used by the application.

Reverse engineering and mapping the API

After a bit of searching, I found the following parts. It seems to be an object containing the API domain, and all the endpoints.

Image of API constants

NOTE: Through comparing v1.0.2 and v2.3.1, I noticed that API_DOMAIN was pointing to www.silverlakemobility.com in v1.0.2 but is now pointing to www.hi-hive.com in v2.3.1.

To implement the application, I only need to figure out some of the endpoints.

The API endpoints relevant to us are:

  • Authentication
  • Listing classes, and attendance
  • Scanning QR codes

With these, I can start mapping the needed endpoints and figure out what to pass to it.

Cracking the authentication method

Looking through the rest, I found a code snippet that shows how the API is being used. This snippet is a callback function for when the login button is pressed.

Image of firebase restriction

It seems to be a bunch of nested async functions, and what it does is:

  1. Loads a Firebase Cloud Messaging (FCM) token from local storage.
  2. Then use the token to encrypt the password using the convertPBEWithMD5AndDES function.
  3. Then pass the resulting encrypted password as part of the payload.

The Login API Call

The application then calls the login endpoint with the following JSON payload structure:

POST https://www.hi-hive.com/chat/api/preLogin/login

{
    "userId": "your user email",
    "password": "encrypted version of your password",
    "os": "Ios or Android",
    "token": "The FCM token used to encrypt the password."
}

I suspect what happens here is that there was a requirement to not send passwords over the internet in plain text even when secured by SSL.

The token is also sent alongside the password, which I assume is used to decrypt it, and/or used to verify that the password was encrypted with a valid token, possibly ones related to the developer to prevent a third party client.

Roadblock to putting together a library

This puts a big roadblock on our goal. What effectively happens in the end is that the login method is made a lot harder to reverse engineer since you have to somehow generated the FCM token.

One method to solve this is to reverse engineer the FCM SDK, then figure out if it’s possible to generate legit FCM tokens, and then check if authentication would work with that generated token.

Until then, listing classes, listing attendance, and scanning QR codes is essentially unusable. It’ll probably stay that way since I’m not very interested in reverse engineering the FCM SDK, especially when I’m graduating and won’t be able to use the results myself.

Other method (Network Monitoring)

Apart from inspecting the unobfuscated code, I’ve used network monitoring to figure out the various endpoints and their needed payload which brought similar results.

This was achieved by:

  1. Preparing the application for network monitoring by:
    • Cracking it open.
    • Editing the AndroidManifest.xml file so that it trust user added SSL Certificates.
    • Repacking, and installing it on a device.
  2. Preparing the device by installing a third party SSL Certificate from CharlesProxy.
  3. Monitoring the traffic of the device (and by proxy the application) using CharlesProxy.

I was able to actually login using the tokens and encrypted password intercepted here which returned a session token, but I wasn’t able to capitalize on it due to the same problems stated before.

Conclusion

So in summary, logging in returns a session token that you can use to access the other authenticated endpoints but since we need the token to encrypt the password, it’s not feasible to create a third party client unless we also reverse engineer Google’s FCM SDK.

All in all, a fun adventure, learnt quite a bit about Android and React Native + reverse engineering android applications.