Introduction

There are some barcode scanner apps offered on the app stores, which are designed to quickly and easily obtain important product properties of food, cosmetics, etc. while shopping. A well-known app in the German-speaking countries is “barcoo”. Since I wanted to have a fixed scanner for my refrigerator, which recognizes the EAN of the product and stores the product in a database, I needed an API from which this data comes. There are large datasets out there in which EANs and products are collected, but these cost money. That’s why I had the idea to use the API endpoint of a classic barcode scanner app for my purpose.

Note: I have already analyzed the endpoints of this app in a previous experiment. I used a jailbroken iPad Air and SSL Kill Switch 2 with an MITM proxy for this. However, I wanted to try again with a decompiled Android version of the app.

App description from barcoo - QR & Barcode Scanner on Google Play

With the barcoo app, you can easily find out which substances a product contains while shopping. All you have to do is scan the barcode on the packaging. This way you get information about the product, the price, where you can buy the product nearby and sometimes even test reports. barcoo scanner recognizes any QR code, barcode as well as Datamatrix, EAN and ISBN that your camera has captured.

Reverse Engineering

An APK file holds all of that program’s code, resources, assets, certificates, and manifest file. The app is provided as split APK, a packaging format which bundles hardware dependend features in seperate APK files. This is a common method these days to shrink the overall app installation size.

Extracted Barcoo Split APK

In this case de.barcoo.android.apk is the Base APK.

It contains code and resources that all other split APKs can access and provides the apps basic functionality. Therefore, we will examine this APK in more detail.

Android programs are commonly written in Java and compiled to bytecode. They are then converted from Java Virtual Machine-compatible .class files to Dalvik-compatible .dex (Dalvik Executable) files before installation on a device. The compact Dalvik Executable format is designed to be suitable for systems that are constrained in terms of memory and processor speed.

Extracted Barcoo Base APK

The extracted Base APK contains three .dex files. We need to decompile them to understand how the program’s code is working. I achieved this using skylot/jadx, a CLI and GUI tool for producing Java source code from Android Dex and APK files.

To make a long story short: The file classes2.dex contains all relevant API logic. The source folder structure follows the reverse DNS scheme. Since the company behind the barcoo app is called Offerista, it was easy to find the right folder.

Following screenshot shows decompiled Java code in sources/com/offerista/android:

Decompiled classes2.dex

We can assume that the barcoo app talks to a server at barcoo.com. A simple grep -Ri barcoo.com has proven to be very effective at this point.

# grep -Ri barcoo.com
misc/AppSettings.java:    public static final String BASE_URL_PRODUCTION = "https://www.barcoo.com";
misc/Settings.java:        arrayMap.put(GEOSERVER_DEFINITIONS_BASE, "https://geoserver.barcoo.com");
activity/SearchActivity.java:                                AutoCompleteAdapter.this.conn = new URL("http://www.barcoo.com/search/suggest?spellcheck.q=" + URLEncoder.encode(charSequence.toString(), "UTF-8")).openConnection();

Voila, we have already found the BASE_URL_PRODUCTION and our first API endpoint.

  • BASE_URL_PRODUCTION = "https://www.barcoo.com"
  • Endpoint 1 (returns JSON): http://www.barcoo.com/search/suggest?spellcheck.q=

This suggestion endpoint is apparently used for free text search in the app for search suggestions and autocorrections.

Here is an example request with the keyword “milram”:

# curl -Ls http://www.barcoo.com/search/suggest?spellcheck.q=milram | jq
{
  "responseHeader": {
    "status": 0,
    "QTime": 2
  },
  "spellcheck": {
    "suggestions": [
      "milram",
      {
        "numFound": 352,
        "startOffset": 0,
        "endOffset": 6,
        "suggestion": [
          "milram",
          "milram bu",
          "milram burlander",
          "milram fruchtbumi",
          "milram fruchtbumi drink"
        ]
      }
    ]
  }
}

Now we examine the AppSettings class in the misc/AppSettings.java file.

public final class AppSettings {
    public static final String BASE_URL_PRODUCTION = "https://www.barcoo.com";
    private static final String EAN_PATH = "/product/psp?m=android#!product/__EAN__/EAN/DE/source=__SOURCE__";
    private static final String PRODUCT_BROWSER_SEARCH_PATH = "/produkt-suche#!suche/";

And besides the base URL we find here another hint to the API endpoint we are actually looking for. An endpoint to which we can pass an EAN and which gives us back information about the product.

  • Endpoint 2 (returns HTML): https://www.barcoo.com/product/psp?m=android#!product/__EAN__/EAN/DE/source=__SOURCE__
  • __EAN__ is a string transmitted by the barcode reader
  • __SOURCE__ is still unknown (wanted!)

Further down in the file there is a routine that uses the EAN_PATH variable. It’s name is getRelativeSearchUrl(), a function that can be passed two strings. This parameters are the __EAN__ string and the wanted __SOURCE__ string.

public String getRelativeSearchUrl(String str, String str2) {
        if (str2 == null) {
            str2 = com.appsflyer.oaid.BuildConfig.FLAVOR;
        }
        if (str == null) {
            str = com.appsflyer.oaid.BuildConfig.FLAVOR;
        }
        if (BarcodeUtil.checkBarcodeType(str) != 0) {
            try {
                return EAN_PATH.replace("__EAN__", URLEncoder.encode(str, "UTF-8")).replace("__SOURCE__", URLEncoder.encode(str2, "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
                return com.appsflyer.oaid.BuildConfig.FLAVOR;
            }
        } else {
            try {
                return PRODUCT_BROWSER_SEARCH_PATH + URLEncoder.encode(str, "UTF-8").replace("+", "%20");
            } catch (UnsupportedEncodingException e2) {
                e2.printStackTrace();
                return com.appsflyer.oaid.BuildConfig.FLAVOR;
            }
        }
    }

Then let’s see where this function is called and where these two parameters come from.

# grep -Ri getRelativeSearchUrl
startup/ActivityLauncher.java:        String relativeSearchUrl = this.appSettings.getRelativeSearchUrl(str, "textsearch");
misc/AppSettings.java:    public String getRelativeSearchUrl(String str, String str2) {

Looks like the secret is hiding in the startup/ActivityLauncher.java file.

    public Launch barcooWebViewActivityForSearchResult(String str) {
        String relativeSearchUrl = this.appSettings.getRelativeSearchUrl(str, "textsearch");
        if (relativeSearchUrl == null || relativeSearchUrl.length() == 0) {
            return null;
        }

The mystery is solved! __SOURCE__ = "textsearch"

Now we can assemble an example request.

Our __EAN__ will be 4036300107756 some cheesy example product.

https://www.barcoo.com/product/psp?m=android#!product/4036300107756/EAN/DE/source=textsearch

Notice: source parameter seems to be optional.

EAN Endpoint (returns HTML): https://www.barcoo.com/product/psp?m=android#!product/__EAN__/EAN/DE

The iOS version directly accesses the JSON endpoint and renders the GUI elements natively. On Android, the software developers were a bit lazy and seem to display the product information in a WebView. Luckily, the underlying webpage accesses exactly this JSON endpoint using javascript.

The network requests can be conveniently tracked via the Developer Tools in Firefox:

Firefox DevTools API Response

EAN API Endpoint

Our final API request looks like this:

https://www.barcoo.com/product_api/DE/EAN/__EAN__

and returns valid JSON… reversing our wanted API endpoint is done! 🎉

# curl -s https://www.barcoo.com/product_api/DE/EAN/4036300107756 | jq '.product'
{
  "pi": "4036300107756",
  "pins": "EAN",
  "c": "DE",
  "non_unique": false,
  "title": "Milram Burlander",
  "sub_title": "DMK Deutsches Milchkontor GmbH",
  "fallback_title": "Milram Burlander 150 gr",
  "category_name": "Schnittkäse",
  "category_key": "CUT_CHEESE",
...