How to earn money by hacking a "walking for money" app.

Or how to reverse engineer React Native apps, and abuse API.

Disclaimer: The article you're about to read, talks about the abuse of a private API, with an humor that probably as not been received has intended. My goal has never been to cause trouble to WeWard, but only to demonstrate skills applied to a funny goal "for fun and profit", fun here for sure, but profit absolutely not. No intrusion on the backend has been attempted, and this article does not contain any "vulnerability" or "0days". No money has been stolen using this method, as every attempts to cashout has been denied by human verification. Every tokens and API keys has been redacted in place of the original ones. WeWard has been notified about this article. I'm still waiting for their answer.

It work basically by monitoring the amount of steps your doing over your day, then it credit your account with points, points that you can trade with rewards. Most of the time it's a discount code from their partners, but sometime it's a brand new phone, money or travels. The success of these apps are directly correlated to the reward they offer, or more accurate the promise of reward you will get.

The leader of this concept in France, is an app called WeWard. Which is our target for this article.

I'm not sure this app is really worthy for users (probably a scam ah ?), but there's one thing I'm sure:

I don't like to walk.

In our digging, will find how to reverse engineer React Native android apps, firebase based APIs and bad authentications, in the only goal of making the much money we can. For fun and profit... No, just for profit this time.

First tour

So first step is to obviously download the app, and install it. After creating my account I can see that it only ask for an email, and ask also to pair a Google account to access watch data, etc.

Once you're authenticated, you can directly access reward, and your step count. Originality of this application is the fact that you need to validate your steps every day to get your points, here called wards.

How to earn wards ?

Wards are can be earned by few ways, the first one obviously is to walk. For every steps level, you validate and then you get a wards amount, with a maximum of 15 wards per day.

You can also earn wards by visiting place of interest then validate your position and earn according wards. But theses are very rare, as most of theses places in fact, are stores, and WeWard force you to buy into them, as you need to provide a cashier ticket in order to claim your wards.

Also, you an earn 1 ward per hour by watching an ad.

Let's take it appart

First thing is to decompress the apk file. You can do it with unzip utility as apk files are literally renamed zip files.

First things we can see is the file named index.android.bundle which is code for the front of a React Native apps.

It's pretty great for me, as I'm way more comfortable with obfuscated Javascript analysis than some compiled Dart or obfuscated Kotlin. I'll extract it and clean indentation for more readability.

I'm going to use a really handy tool called apktool to uncompress the AndroidManifest.xml to give me more information about permissions, APIs, etc. I know that apktool can also de-compile dex files, but it only does as smali bytecode, which is pretty difficult to read. So I prefer to de-compile it sideways to get Java / Kotlin instead. Next steps is to de-compile dex files that contain our backend (if I can say so) code.

AndroidManifest.xml didn't told us more than the usage of Location or GPS in the app (which was obvious anyway).

So let's dig .dex and Javascript.

Dex files

I used JADX for this decompilation. I can't post everything here as there's a lot of data. Among all this data there's only one that interest us, the com.weward package under the classes4.dex file.

Most of these are just Java wrappers with the React Native frontend to receive intent get GPS data, etc. Their are not even obfuscated.

Here is for example the code of WewardPedometer.java. Also if you think WewardRequest.java is interesting, that's what I tough too, and it's not. It's just a utility class for storing and retrieve the firebase_id.

Now we understand that the real subject is this famous index.android.bundle.

Javascript love React

First thing I do when I'm in front of new Javascript obfusated file, is to search every occurence for API or backend. And this time again it worked perfectly.

From this I discovered the apiGet, apiPost, sendStepsData and sendGeoData implementations as well as a settings object.

From this we get the API backend which is https://backend.prod.weward.fr/api/v1.0, how much a ward is worth in euro (.007€ at 15 wards per day, do your math), a slack webhook and finally a Google API Key, etc.

Please do not store that kind of info in a app.

I could literally ruin the company by reusing the Google API Key, or overflow their Slack channels, or even try to hack it with the Slack webhooks phishing attack. These kinds of operation should be done on the backend API side. (Please don't do it, that's gonna be way less fun for me)

Here:

  • x(t, n, o, u, c) validate the response of the API and is called after each API request.
  • v(t) construct the complete URL from the BACK_END string of settings object and the endpoint.
  • w(t) append a lot of HTTP header for authentication and statistics.
  • sendGeoData & sendStepsData are just apiPost wrappers.

That's all we need to start interacting with the API and try to abuse the data sent to it. With more regex trick we can then find all the calls to apiGet & apiPost, and with context it's easy to understand the effects and the requirements of each endpoints. After analyzing the w(t), I'm now able to call endpoint with cURL and in the future with Python requests.

Here is example with the request_signin_with_email:

curl "https://backend.prod.weward.fr/api/v1.0/customer/request_signin_with_email" --data '{ "email": "mm.didelot@deadf00d.com" }' -H "User-Agent: Mozilla/5.0 (Linux; Android 5.1.1; Nexus 5 Build/LMY48B; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/43.0.2357.65 Mobile Safari/537.36" -H"Content-Type: application/json"

All your firebase are belong to us

WeWard has a firebase based authentication. It works with a firebase_id that has to be specified for each authenticated request along with the weward_token. Theses two tokens are given by the call of signin_with_id_token.

Also, a bunch of other metrics are sent within each request as described in this part of the w(t) function.

Here is the corresponding curl request:

curl "https://backend.prod.weward.fr/api/v1.0/endpoint"
-H"ww_app_version: 6.1.1"
-H"ww_os: android"
-H"ww_os_version: 28"
-H"ww_build_version: 152"
-H"ww_codepush_version: base"
-H"Ww-Unique-Device-id: 3fe16fb02c6baea7"
-H"ww_device_ts: 1622293912536"
-H"ww_device_timezone: Europe/Paris"
-H"ww_local_datetime: Sat May 29 2021 15:11:52 GMT+0200 (CEST)"
-H"ww_device_country: FR" -H"firebase-id: 1823308"
-H"Authorization: YwWZXH8lC8WuxpUL31lnpLf47IXd254_jaPtP_X-lck"
-H"User-Agent: Mozilla/5.0 (Linux; Android 5.1.1; Nexus 5 Build/LMY48B; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/43.0.2357.65 Mobile Safari/537.36"
-H"Content-Type: application/json" -vv

In order to get theses two tokens you need to provide a firebase custom_token to the endpoint signin_with_id_token. This firebase custom token is provided in a the mail activation link that is sent after calling the request_signin_with_email provided with an email.

This is easily handled in python with the Firebase package:

Our first ward

Now that we can call authenticated endpoints, we should be able to get our first ward. Here is a quick list of theses endpoints:

Every of theses endpoints have very few arguments, but the one that interest us is /ads_reward_v2.

But why this one ?

Cause it has no arguments.

No arguments ?

Yes. You heard me right, this endpoint is used to credit wards after the view of an ad and has no validation token, no challenge, nothing. Here is code for it:

It is literally called right after the event of finished ad is called. And it directly gives you ward right after.

At this point I hoped a lot that it could be called infinity without timeout. But It's not. Once claimed it block you for an hour.

But hey ! We got our first ward.

Yummy wards

About phone side data

Because of the nature of the step count, which is a data produced by the phone, and therefore, cannot be validated by the backend. In this context it is possible to report a false data, and make the API trust it. These data can be GPS position, steps count, etc. The only way to reduce (but not eliminate) this is to make several validation per minutes or hours of the data and to validate only coherent data.

Abuse GPS data

The next thing I've been focusing on is the possibility to earn wards by going to a special spot and then validate my GPS position. This will credit me a small amount of wards (between 10-30) but hey ! It's still better than having to walk.

As said earlier because of the data is produced by the phone, and validation mechanism can exist, it's easily manipulable. One way, could be to use an emulator and to manually set the GPS position. But again, I'm lazy so I want to work a lot more to get something really fast and that I can use thousands of times, to only use it once.

Anyway theses spot can be found on an interactive map, as an optimization, they only display the spot around the point you're looking at on a certain zoom level.

In order to do this, the app call the API endpoint location_campaign with the available GET arguments:

  • latitude
  • longitude
  • recenter
  • per_page

By providing setting the coordinates of the center of France, and then providing a big number to per_page, I'm able to list every spot directly. Here is a typical response:

Among all these locations only few are free, to detect them we just need to test for the presence of the receipt_campaign_id key in each spot object. If it's not there, then it's free.

To validate your GPS coordinate the app call the reward_visit_campaign provided with the correct latitude, longitude as well with the campaign_id, which is stored under the id key in our spot object.

So after that I developed a quick script that loop over the free spots and validate each of them on my account. I run it, wait a bit, and then:

Abuse steps data

Less interesting than the spots, but still can represent 25 wards per day, steps are also very easy to abuse. Steps amount is reported to the push_step_record API endpoint with theses json POST arguments:

  • weward_steps
  • googlefit_steps
  • steps_source (=wewardpedometer)
  • uptime_score
  • device_uptime_ms
  • data_sources
  • amount
  • android_amount

The validation and conversion of the steps into wards is done by calling the valid_step endpoint without any argument. You can only validate a maximum of 20k steps per day which will give you 25 wards.

Cashout

By exploiting every techniques you can get about 20k wards in few minutes, the equivalent of about 138€.
With this you'll be able to get the 70€ reward, then the 50€ one.

And if you're willing to wait, you're about 30k wards away from the Rome holidays reward.

So what did we learn ?

In order to build more secured application, it's important to perform the more actions you can on the backend side. It seems like a simple tip, and pretty obvious, but even today there's still a lot of application out there that don't follow it.

The worst thing in this WeWard app, is the amount of secret and confidential data packed in the app like this slack webhook that could allow total takeover of their slack or this Google secret key that can be reused and potentially ruin the company.

Fortunately I'm not a malicious attacker, just someone snooping around, but that can happen, especially in high concurrence markets.

If you need advising on such subject, I'm available as a consultant, and would be happy to do so.

Thanks for reading.


You enjoyed?

Contact me

Post a comment

Comments (2)

g

10 Jun 2022

great writeup!

Anonymous

14 Oct 2022

j'ai eu cette idée alors j'ai cherché si ça avait déjà été fait et oui, c'est vraiment impressionnant !!!! Bien joué à toi

Author

Didelot Maurice-Michel Freelance
Didelot
Maurice-Michel

I'm a dedicated cybersecurity professional with a passion for helping organizations protect their digital assets. I've worked hard to create a company that focuses on proactive security, continuous improvement, and collaboration.

Contact me

Last articles