Sitting all day in front of a computer really isn’t healthy. Therefore everyday I try to be active, for example riding a bike, taking a dog for a stroll or by going to the gym/fitness in my neighborhood. I like this place, because every day they provide different kinds of 1-hour group training with a trainer. Anyone who is a member of the fitness club can sign up for one or two activities each day. It can be done by website or by mobile application. I’ve been using Android version and last time I noticed that it didn’t work properly. I could log into the application, but couldn’t sign up for activities anymore. There were no updates in Google Play, so I decided to take a technical look.
Debugging
First of all I connected my smartphone to a computer, enabled USB-debugging mode, and ran DDMS (Android Device Monitor).
I started the application and in console there was an error:
javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:361)
...
at se.brpsystems.api.json.JSONParser2.fetchData(JSONParser2.java:124)
...
at se.brpsystems.domain.Person.authenticate(Person.java:40)
Ok, it looks like there is a some kind of problem with certificate during HTTPS connection.
Let’s look into it. First we should download apk file.
Is the smartphone connected?
$ adb devices
List of devices attached
19874e6d device
What is the application’s package name?
$ adb shell pm list packages
...
package:com.ubercab.eats
package:com.endomondo.android
package:se.brpsystems.f24s
package:taxi.android.client
...
Where is an apk file?
$ adb shell pm path se.brpsystems.f24s
package:/data/app/se.brpsystems.f24s-1/base.apk
Download file:
$ adb pull /data/app/se.brpsystems.f24s-1/base.apk f24s.apk
5788 KB/s (2595323 bytes in 0.437s)
Unzip apk:
$ unzip -q f24s.apk -d f24s
$ ls -l f24s
-rw-rw-rw-. 1 p0n3 p0n3 6292 1979-12-31 AndroidManifest.xml
drwxrwxr-x. 3 p0n3 p0n3 19 09-19 12:09 assets
-rw-rw-r--. 1 p0n3 p0n3 2428152 1979-12-31 classes.dex
drwxrwxr-x. 2 p0n3 p0n3 56 09-19 12:09 META-INF
drwxrwxr-x. 3 p0n3 p0n3 21 09-19 12:09 net
drwxrwxr-x. 4 p0n3 p0n3 32 09-19 12:09 org
drwxrwxr-x. 32 p0n3 p0n3 4096 09-19 12:09 res
-rw-rw-rw-. 1 p0n3 p0n3 343564 1979-12-31 resources.arsc
drwxrwxr-x. 12 p0n3 p0n3 155 09-19 12:09 zoneinfo
Ok, you can see classes.dex which is a file with compiled Android application code. Similarly, all the compiled resource files you can find in a file named resources.arsc.
Decompiling
Let’s use dex2jar and Java Decompiler to decompile this file.
$ d2j-dex2jar f24s.apk
dex2jar f24s.apk -> .\f24s-dex2jar.jar
After that, you can open f24s-dex2jar.jar
using Java Decompiler.
Quick look on codes and I found something interesting. Method which is executed when the application starts: getSettingsFromWorld
from se.brpsystems.api.WorldApi
:
public static String getSettingsFromWorld(Context paramContext, String paramString)
{
Object localObject1 = null;
Object localObject2 = null;
JSONParser2 localJSONParser2 = new JSONParser2("http://prod.brpsystems.se/BrpWorld/api/installationInfo?licenceid=" + paramString, null);
Object localObject4 = null;
paramString = (String)localObject2;
for (;;)
{
JsonReader localJsonReader;
String str2;
String str1;
int i;
try
{
localJsonReader = localJSONParser2.getData();
paramString = (String)localObject2;
localJsonReader.beginObject();
paramString = (String)localObject2;
if (!localJsonReader.nextName().equals("settings"))
{
paramString = (String)localObject2;
localJsonReader.skipValue();
continue;
}
...
To obtain a whole url (line 5) we must find a value of a parameter paramString
. Where the method is invoked? Let’s look on package se.brpsystems.ui.BaseActivity
:
...
public Boolean loadInBackground()
{
String str3 = BaseActivity.this.getString(2131165351);
if (str3.equals("-1")) {
return Boolean.valueOf(true);
}
String str1 = BaseActivity.getApiUrl();
String str2 = str1;
if (str1.endsWith("/")) {
str2 = str1.substring(0, str1.length() - 1);
}
str1 = null;
int i = 0;
if (Utils.isEmpty(Utils.getPreference(BaseActivity.this, "webUrl", "")))
{
str1 = WorldApi.getSettingsFromWorld(BaseActivity.this, str3);
i = 1;
}
if (!testApiUrl(str2))
{
if (i == 0) {
str1 = WorldApi.getSettingsFromWorld(BaseActivity.this, str3);
}
...
Not everything is completely decompiled – method getString
is unavaliable, so we don’t know what is a result of BaseActivity.this.getString(2131165351);
(line 18, 5). We can use a different program to decompile APK such as APKTool.
$ apktool.bat d f24s.apk
I: Using Apktool 2.3.4 on f24s.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
[...]
I: Loading resource table from file: [...]
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Baksmaling classes.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...
Now we have:
$ less f24s/smali/se/brpsystems/ui/BaseActivity$3$1.smali
# virtual methods
.method public loadInBackground()Ljava/lang/Boolean;
.locals 10
[...]
.line 562
.local v2, "licenseId":Ljava/lang/String;
const-string v5, "-1"
invoke-virtual {v2, v5}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
[...]
.line 573
iget-object v5, p0, Lse/brpsystems/ui/BaseActivity$3$1;->this$1:Lse/brpsystems/ui/BaseActivity$3;
iget-object v5, v5, Lse/brpsystems/ui/BaseActivity$3;->this$0:Lse/brpsystems/ui/BaseActivity;
invoke-static {v5, v2}, Lse/brpsystems/api/WorldApi;->getSettingsFromWorld(Landroid/content/Context;Ljava/lang/String;)Ljava/lang/String;
So the method invocation is getString("licenseId")
. Maybe we find licenseId in application resources?
$ grep -r "licenceid" f24s/res
res/values/strings.xml: <string name="licenceid">45</string>
Now we know the full url: http://prod.brpsystems.se/BrpWorld/api/installationInfo?licenceid=45
Server responses with JSON:
{
"licenceid":45,
"name":"Fitness 24 Seven",
"settings":[
{
"name":"webServer",
"value":"https://boka.fitness24seven.com/brp/mesh/"
},
{
"name":"grailsServer",
"value":"https://boka.fitness24seven.com/grails"
}
],
"shortName":"f24s",
"version":"2018.4003"
}
So the application try to log in to https://boka.fitness24seven.com/brp/mesh/ and get an information about available workouts.
Maybe the SSL for a domain boka.fitness24seven.com
hasn’t been correctly set?
SSL test
SSL configuration could be tested on ssllabs.com or simple by using openssl
command
$ openssl s_client -connect boka.fitness24seven.com:443
Certificate chain
0 s:/OU=Domain Control Validated/OU=PositiveSSL/CN=boka.fitness24seven.com
i:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Domain Validation Secure Server CA
...
SSL handshake has read 2328 bytes and written 415 bytes
...
Verify return code: 21 (unable to verify the first certificate)
It looks like the certification chain cannot be verified due to missing intermediate certifications. How to fix it? The quickest way is to add certs into server configuration, but I don’t have an access 🙁 . Maybe there is a way to add it manually on smartphone? This helps to understand the way how security/certs can be configure in android application.
First of all I found a certification file on comodo’s official page. Then I installed it on my Android phone simple by clicking on it.
Modification
Sadly it’s not enough. I needed to add a network security configuration to the application.
New file: res/xml/network_security_config.xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
</network-security-config>
Manifest file also must be changed:
<application android:allowBackup="true" android:icon="@drawable/icon" android:label="@string/app_name" android:networkSecurityConfig="@xml/network_security_config">
Now let’s build it!
apktool.bat b f24s -o f24s_mod.apk
I: Using Apktool 2.3.4
I: Checking whether sources has changed...
I: Smaling smali folder into classes.dex...
I: Checking whether resources has changed...
I: Building resources...
S: WARNING: Could not write to (C:\Users\Lukasz\AppData\Local\apktool\framework), using C:\Users\Lukasz\AppData\Local\Temp\ instead...
S: Please be aware this is a volatile directory and frameworks could go missing, please utilize --frame-path if the default storage directory is unavailable
I: Building apk file...
I: Copying unknown files/dir...
I: Built apk...
Uninstall an old version:
$ adb uninstall se.brpsystems.f24s
* daemon not running. starting it now on port 5037 *
* daemon started successfully *
Success
Install modified version:
$ adb install f24s_mod.apk
4174 KB/s (2496079 bytes in 0.583s)
Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES: Failed to collect certificates from /data/app/vmdl1007681516.tmp/base.apk: Attempt to get length of null array]
Whoops, I forgot to sign the application. It could be done from a command line or with program called APK Signer.
Generate new keys in APK Signer:
Sign the application:
And install it:
$ adb install f24s_mod_SIGNED_UNALIGNED.apk
4638 KB/s (2595281 bytes in 0.546s)
Success
Does it work?
Yupi!
Epilogue [updated: 2018-09-20]
I wrote an email to the fitness club. Next day I got a response: “Thanks, but we already release a new application under a different name in Google Play.” Ouch… Anyway It was fun 🙂 . I enjoy a bit of reverse engineering from time to time 😉 .
Leave a Reply