Reverse Engineering an Android App to Get Gas Price

January 18, 2023

Background

I am currently trying to finish 15 car rentals this year in order to get the President's Circle membership from Hertz. Since I frequently rent the car, I need to find a gas station with the cheapest gas before I return it everytime. Searching gas prices on Google Maps takes time, so why not get the gas price from each gas station's mobile app and write an API by myself? I could also learn something about API security along the way.

Initial Analysis

The app I will be working with looks like this.

App UI

App UI

Sniffing Web Traffic

Setting up the environment

It's time to set up the tools. Personnaly, I prefer to use mitmproxy over other tools like Charles or etc because it's not only cross-platform but also has the WireGuard mode which be used to set up the Android device easily.

mitmweb --mode wireguard

By running the command above, mitmproxy will start and open up a browser window like the following one:

Start Page

Start Page

Finding the right request

After connect to the mitmproxy, we can now open the app and watch the network requests it sends.

Initial Result

Initial Result

Based on the domain name and HTTP method used, it seems that the app first sets up the analytics and crash report collection services and then make several GET request to retrive the gas station data.

The following requests stands out as it looks suspicious and it may contain our gas price data.

Suspicious Request

Suspicious Request

If we look its response content, we could even find something called <REDUCTED>Stores.json.

Suspicious Content

Suspicious Content

Since the response content type is listed as application/octet-stream, we can't know its file type directly. To detect what kind of file it is, I searched the first few bytes on Wikipedia.

Oh look, it's a zip file!

File Type

File Type

It looks like the App is downloading a zip archive that include a file called <REDUCTED>Stores.json. To figure out its content, I donwloaded that archive and unzipped it.

Here is what I've got in that JSON file:

[
  {
    "latitude": <REDUCTED>,
    "longitude": <REDUCTED>,
    "fuelItems": [
      {
        "description": "Unleaded",
        "price": 3.559,
        "guaranteePrice": 0,
        "displayName": "Unleaded",
        "cashPrice": 3.559
      },
      ...
    ],
    ...
  },
  ...
]

By now, we can get the data we need.

However, it I try to download multiple times in a short period of time, I get the following response:

<html>
<head>
<META NAME="robots" CONTENT="noindex,nofollow">
<script src="/_Incapsula_Resource?SWJIYLWA=5074a744e2e3d891814e9a2dace20bd4,719d34d31c8e3a6e6fffd425f7e032f3">
</script>
<body>
</body></html>

Ahh, it looks like I have triggered some request rate-limit mechanism and the server refused my request.

Next Steps

There is just one question left: what does 3210XXXX-XXXX-XXXX-XXXX-XXXXXXXXC2F7 mean in that GET request?

Based on its format, it looks like an UUID. However, according to common API design patterns, it could be the ID of my device, the signature of the request or just the fixed API key.

To figure out what it really means, I need to see the source of the app.

Checking the Source Code

To check out the source code, I need to use a tool called jadx to open the classes.dex file stored inside the app's APK file. After some search, I've found an interesting method signature within a class:

private final native String getApiKey(char[] cArr);

jadx Search Result

jadx Search Result

Within that class, it contains the following code:

public final class <REDUCTED>Native {
  static {
    System.loadLibrary("<REDUCTED>Native");
  }
}

There is also few shared object files in the following path:

/lib/${ARCH}/lib<REDUCTED>Native.so

It looks like the app uses Android NDK (Native Development Kit) to hide the implementation of this sensitive method. To figure out its implementation, we now need to use IDA software to examinine the shared object files.

Assembly View

Assembly View

After opening the shared object file in IDA, we can now know how does that method implemented by checking its assembly code.

Pseudo-code View

Pseudo-code View

However, our tool can also generate pseudo-code based on the assembly.

If we also check the data section of the shared object file, we can also find some interesting strings stored in it. It has some URLs and also some really long string.

Hex View

Hex View

By cross referencing the Hex View and the pseudo-code, it looks like the API key has been hardcoded in that shard object file. Based on the name of other methods, the getApiKey() method seems only checks the current app environment type by comparing the current app package signature with some hard-coded one and then returns the appropriate API key.

Thus, the function can be rewritten as the following:

public String getApiKey(JNIEnv *env, jobject obj, jcharArray signature) {
  if (getDevelopmentModeInt(env, obj, signature) <= 0) {
    return "";
  } else {
    return "3210XXXX-XXXX-XXXX-XXXX-XXXXXXXXC2F7";
  }
}

Summary

As of now, we have figured out what endpoint to request to and what parameters we need to make the request in order to get our gas price data.

Happy Hacking!