Home / Reverse Engineering the Renpho App

Reverse Engineering the Renpho App

Saturday, September 18th 2021, 1:00 pm

Background

I use Home Assistant to automate my home. This mostly involves turning lights and music on and off but I really like the idea of the dashboard being a sort of hub for my abode.

I am also getting fat. I mean, lockdown fat.

I have a set of Renpho scales to keep track of this and connect over WiFi, but I never remember to weigh myself. The app works well enough, but really I want the data to feed into my Home Assistant, so I can see my current weight and send myself reminders to weigh in - I will probably do something similar with the Pure Gym app to check when I last visited and remind me to pay a visit, but that's a different story.

The Plan

Being a tubby Thomas, I intend to do the following:

  • Figure out the API calls, endpoints and data the app is sending in order to login and retrieve my latest weight and last weighed time.
  • Integrate this with Home Assistant to display on Dashboard

Sniffing Packets

This was a right chew on.

Basically I have very little knowledge of programming for Android, bar some React Native experiments, and my Wireshark isn't great. I had a brief look into sniffing via Wireshark, as the quickest in and out operation, but this fell flat pretty quickly as a) I have a 2018 Macbook pro (the one with the pathetic, broken keyboard) with the wifi chipset of 2 midgies, so that was out the window and b) there's a 99.9% chance the data will be sent over ssl, so I wouldn't be able to read any traffic between the app and the server. I had assumed at this point the data would be sent over a fairly simple web rest api, which thankfully it sort of is.

My two options were:

  1. Fin a way to sniff packets
  2. Decompile the apk and make sense of the code and it's requests.

I'm not clued up on Java enough to really get into decompiling, and a quick look at some of the methods were a little bit more than spinning back to 2001 and using a cracked version of WDasm so I went for packet sniffing.

After multiple trial and errors, rooting an old Pixel3a, installing security certificates and general failing and fannying about, I have had success with the following method.

Android Studios' AVD

PCAPDroid

mitmproxy

The amount of nerds out there creating tools for free is amazing, I salute each and every one of you.

Basic Setup

I went with using an Android Virtual Device.

I'm not explaining how I set all these up, but the method was along the lines of:

  • spin up an AVD in Android Studio - I used a Nexus 6 and Android 6 I believe, but it was important to use an Android version below 7, in order for user installed web certificates to be trusted
  • copy my mitm proxy certificate from my mac into my virtual android device and install it
  • config PCAPdroid on my virtual device to filter only for Renpho app traffic, and filter it over socks5 proxy to my mitmproxy.

Obviously I installed the Renpho app on my AVD and checked data was logging.

Huzzah, we have decrypted SSL traffic from the app proxied through mitmproxy. I am very aware one of the endpoints is called girth_goals, I'm not aware wtf it actually returns data wise. Maybe ask your mam. I have cut out any potentially identifiable data on this screenshot.

Next up, I logged out the app, logged in again and, again, huzzah! I have a post request containing the following information:

  • email address
  • password
  • some fields I'm not 100% on such as secure_flag and version

Replicating a login call

So my next task is to replicate this call in postman. I will mention now the above screenshot has resolved ips, and when using in postman the requests seemed to break, possibly due to how virtual hosts are set up or equivalent, but looking at my PCAPDroid logs I have the endpoint renpho.qnclouds.com. Sadly this doesn't take you to a browser login, but it was worth a shot.

In postman I've created a request with the bare minimum, to sort of feel it out.

  • URL: https://renpho.qnclouds.com/api/v3/users/sign_in.json?app_id=Renpho - it seems to like the app_id here, my theory is qnclouds is a generic api service and Renpho identifies who to check logins against or otherwise identify the app. I did manage to remove every other query var and have it return success however.
  • Body param: email - I'm not posting these details here as it seems a security faux paus, so accept it
  • Body param: password - this looks to be a string encrypted client side and sent over. I will figure out the encryption first but the important thing is I have been able to use this hashed version of my password multiple times to log myself in.
  • Body Param: secure_flag - if I don't set this as 1 it goes bandy, so I set it as 1

Body params are sent as x-www-form-urlencoded and everything is grand. It returns something similar to the following:

{
    "status_code": "20000",
    "status_message": "ok",
    "terminal_user_session_key": <hidden this>,
    "device_binds_ary": [
        {
            "id": <hidden this>,
            "mac": <hidden this>,
            "scale_name": "QN-Scale",
            "demo": "",
            "hw_ble_version": 0,
            "device_type": 0,
            "hw_software_version": 0,
            "created_at": "2020-12-26 07:51:54",
            "uuid": "",
            "b_user_id": <hidden this>,
            "internal_model": "02D3",
            "wifi_name": <hidden this>,
            "product_category": 0
        }
    ],
    "id": <hidden this>,
    "email": <hidden this>,
    "account_name": "Neil",
    "gender": 1,
    "height": <hidden this>,
    "height_unit": 1,
    "waistline": 0,
    "hip": 0,
    "person_type": 0,
    "category_type": 0,
    "weight_unit": 4,
    "current_goal_weight": 0.0,
    "weight_goal_unit": 1,
    "weight_goal": 82.6,
    "locale": "en",
    "birthday": <hidden this>,
    "weight_goal_date": "",
    "avatar_url": "",
    "weight": <hidden this>,
    "facebook_account": "",
    "twitter_account": "",
    "line_account": "",
    "sport_goal": 0,
    "sleep_goal": 0,
    "bodyfat_goal": <hidden this>,
    "initial_weight": <hidden this>,
    "initial_bodyfat": <hidden this>,
    "area_code": "GB",
    "method": 2,
    "user_code": "",
    "agree_flag": 1,
    "reach_goal_weight_flag": 0,
    "reach_goal_bodyfat_flag": 0,
    "set_goal_at": <hidden this>,
    "sell_flag": 1,
    "allow_notification_flag": 1,
    "phone": "",
    "region_code": "",
    "dump_flag": 0,
    "weighing_mode": 0,
    "password_present_flag": 1,
    "stature": 67.0,
    "custom": "",
    "index_extension": -1,
    "person_body_shape": 0,
    "person_goal": 0
}

I've hidden a few things there as a matter of privacy, but you get the gist.

From the returned information, the most important I believe and a key to figuring this out is the terminal_user_session_key. This looks to be sent with every following request in order to authenticate the user, and in testing I have confirmed this. This key also seems to expire, I guess it is literally a session token.

My Next Puzzle

My next puzzle, as I seem to have an encoded version of my key which provides session access, is to either filter through my requests to find what I want and build a very simple library around it, or, and this is my next puzzle, figure out how the client is encrypting the password before sending for verification.

From what I can tell after preliminary investigation:

  • the password is hashed and uses a combination of upper and lower case letters, numbers and ends in an =
  • The password hash seems to change everytime, despite sending the same password

I have checked it straight up against base64 encoding, and all default supported android hashes listed here with no luck as of yet.

I have a feeling in this part it may be easiest to dump and decompile the Renpho Apk in order to check what calls it is making to hash the password, as it may be using a combination of email + password strings, or a changing salt. This isn't my strong suit, so I find it odd that you can hash something with a different outcome each time and have it verify, but I'm pretty sure I'll learn something which is encryption 101 in my next delve into this.