Reversing API Endpoints from Barcoo App
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.
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.
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
:
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:
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",
...