With mitmproxy
you can look inside HTTPS traffic, although it depends on how exactly the app communicates with the real server (if it uses client certificates, I don’t think you can make it work). The website should provide information on how to set it up on your phone.
General decompilation instructions are:
- Find an APK (Android app) for your app. To do this, it works best to find the app identifier, which can be found in the URL of its Google Play page (in this case, the identifier is
com.attopartners.keylite
). Google for the identifier plus “apk” and you’ll find plenty of sites that will offer the APK for download (like this one).
- Since an APK is basically a ZIP file, unzip it first. Sometimes there are additional APK files that get extracted, unzip those too.
- To decompile the Java app itself, I use
jadx
. However, since the Keylite Connect app is mostly written in Javascript, this isn’t really necessary since most of the code is readable(-ish) JS code anyway and contained in the file assets/index.android.bundle
.
- To make the JS code more readable, I use
prettier
.
From there, experience starts playing a role, because even though the JS code is formatted, it’s still quite unreadable because it was obfuscated. So you start looking for tell-tale strings that point to the app making web requests, like auth
, oauth
, http:
, https:
, etc.
In this case, it looks there are two different login API’s, one “simple” (called “v1”) and one based on OAuth.
The simple one seems to perform a login like this:
POST https://app.keyliteroofwindows.com/v1/login
With data:
{ "username" : "XXX", "password" : "YYY", "grant_type" : "password", "scope" : "" }
(not sure if it’s JSON or URL-encoded)
This should return an access token, which should be sent with each additional API request, in the form of a header:
Authorization: Bearer TOKEN
The endpoints that I can find in the code:
GET('/user')
POST('/user/', { user: t })
PUT('/user/', { user: t })
POST('/user/validate', { user: t })
POST('/user/update_password', t)
POST('/user/send_reset_password_link', { email: t });
POST('/user/reset_password_by_token', t)
GET('/user/validate_token?token=' + t)
POST('/user/email_link', { email: t })
POST('/user/confirm_email', { token: t })
GET('/gateways')
POST('/gateways/' + u + '/claim', { name: l, mac_address: f, timezone: s })
PUT('/gateways/' + t.id, { gateway: t })
POST('/gateways/' + t.id + '/windows_close')
GET('/gateways/' + t + '/groups')
POST('/gateways/' + u.id + '/groups', { group: c })
PUT('/gateways/' + u.id + '/groups/' + c.id, { group: c })
POST('/gateways/' + n + '/groups/' + u + '/windows_open', { override: l })
POST('/gateways/' + n + '/groups/' + u + '/windows_close')
POST('/gateways/' + n + '/groups/' + u + '/windows_vent')
POST('/gateways/' + n + '/groups/' + u + '/windows_move_to_percentage', {
POST('/gateways/' + n + '/groups/' + u + '/blinds_move_to_percentage', {
POST('/gateways/' + n + '/groups/' + u + '/blinds_open')
POST('/gateways/' + n + '/groups/' + u + '/blinds_close')
DELETE('/gateways/' + u + '/groups/' + c.id)
GET('/gateways/' + t + '/windows')
POST('/gateways/' + c + '/windows/' + w + '/window_move_to_percentage', {
POST('/gateways/' + c + '/windows/' + w + '/window_vent')
POST('/gateways/' + c + '/windows/' + w + '/window_notice')
POST('/gateways/' + c + '/windows/' + w + '/window_close')
POST('/gateways/' + c + '/windows/' + w + '/blind_move_to_percentage', {
PUT('/gateways/' + c + '/windows/' + w.id, { window: w })
POST('/gateways/' + c + '/windows/' + w.id + '/machine_learn_mode', { window: w })
DELETE('/gateways/' + c + '/windows/' + w.id)
I don’t know what the variables are, but I assume in most cases an index or identifier that should become apparent when you retrieve the /gateways
endpoint.