Skip to main content

Intercepting Mobile Application Traffic with Caido and Frida

Brown Fine Security
Author
Brown Fine Security
Hardware Security Researcher & Cyber Security Content Creator
Table of Contents

I recently started a research project on the iHealth Nexus Pro Body Composition Scale. This device only communicates via Bluetooth Low Energy (BLE) to a mobile application. It is the mobile application itself that performs all communication with the company’s cloud servers.

HTTP Proxy Tooling
#

As a security researcher, I often need to see inside of the HTTP communications of certain devices and applications in order to perform security testing. To do this, an HTTP proxy is an indispensable tool that allows security testers to read, modify and resend various HTTP requests. Caido is the latest HTTP proxy tool on the market, and has a few key features that have won me over. It’s written in Rust and designed from the ground up to not consume large amounts of memory and compute resources. It also runs well on Linux which is important for me. So for this post, we are going to kick the tires and use Caido to intercept the traffic of the mobile application for our IoT device.

Device Prep
#

Some will ask why I’m using a physical device for this testing instead of an android emulator. Emulators are great, but in this case I need to use a real device that can communicate over BLE to the IoT device.

We will need to connect our android device to our Linux computer via a USB data cord and enable developer mode on the phone. We also must ensure that ADB is enabled in the develop options. Another pro tip is to enable the developer option to disable the phone display from turning off while plugged in via USB.

To check that we are able to connect to our phone over ADB, run the following command:

adb devices
List of devices attached
25281JEGR02143	device

It is also useful noting that everything in this post was performed on a non-rooted android device.

Caido Setup
#

After Downloading and Installing Caido we need to create an instance. We will setup our instance to run locally and listen on localhost and TCP port 8080.

caido1
Setup Caido instance

Then we click the Start button on the new instance we just created.

caido2
Start instance

After that simply create and select a Project.

caido3
Create and select a project

Finally, we can select the HTTP History tab on the left side and it will tell us it doesn’t have any traffic intercepted yet.

caido4
HTTP History tab

Proxying Android Mobile Applications
#

Now we need to download our mobile application to our computer and then install it on our device. You can always download the app from the Google Play Store, but I like to download apps from a third party website like apkpure.

First let’s download the iHealth MyVitals application and save it to a file named ihealth-myvitals-4.8.0.apk.

Now we can use adb to install the application to our mobile device:

adb install ihealth-myvitals-4.8.0

Now we need to configure our Wi-Fi network to use a manual HTTP proxy. To do this tap the Modify button on the Network details Wi-Fi screen. It’s the pencil icon in the upper right side.

wifi1
Modify button in upper right side of Network details screen

On this next screen select Manual under the Proxy dropdown and then fill in 127.0.0.1 for the Proxy hostname and 8080 for the Proxy port.

wifi2
Manual proxy settings

But wait! Why did we set 127.0.0.1 for the proxy hostname? Caido isn’t running on our phone!! This is true, but we can use a cool feature in adb to reverse proxy localhost:8080 on the phone to port 8080 on our computer.

Next, we need to add Caido’s CA certificate to the phone trust store. To do this first navigate to http://localhost:8080/ca.crt in a mobile web browser to download the CA certificate. On newer versions of android, you can’t directly install a CA certificate from the browser screen since they see that as a security risk. You will need to navigate in your settings to Encryption and Credentials, select Install a certificate and then follow the prompts to trust Caido’s CA certificate that should be in your phone’s download folder.

cert1
Install Caido CA certificate

adb reverse tcp:8080 tcp:8080

Now when android wants to sent traffic to localhost:8080 it will forward it, over the USB cord, to our computer’s port 8080 where Caido is running!

Hitting a Wall: Certificate Pinning
#

Now that everything is setup, we should be able to receive traffic through our Caido proxy, but when firing up the iHealth mobile app something goes wrong.

e1
App network error

Let’s check the android device’s application logs to see what could be going wrong:

adb logcat
[ ------- TRUNCATED ------- ]
08-23 00:06:56.005  9896 10088 W System.err: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
[ ------- TRUNCATED ------- ]

Trust anchor for certification path not found.

This means that even though the android operating system trusts our root CA certificate, the application for some reason does not. This is because the android application is implementing CA certificate pinning. CA certificate pinning is the process of a TLS client performing a stricter check on the server certificate that verifies its authenticity using a smaller set of root CA’s or even checking the server certificate itself against an expected copy of it stored in the application code.

Adding Frida Native Library to Mobile Application
#

To bypass certificate pinning we are going to use Frida. Frida is a dynamic instrumentation toolkit. Our end goal is to run a script from Frida’s Codeshare Platform that will modify the running iHealth application to not perform certificate pinning. As I mentioned before, I’m doing all of this analysis on a non-rooted android phone. If our phone was rooted, we could use frida to attach to a running process on our mobile device. Instead, we will need to add Frida’s Gadget library to the iHealth mobile application and install that modified app to our device.

I perform all of these modification steps alot, so I documented it all here in a GitHub Repo.

First, we need to unpack the APK file using apktool:

apktool d -o unpack ihealth-myvitals-4.8.0.apk

Then, we need to download frida’s gadget library into the proper native libs directory:

cd unpack/lib/arm64-v8a
wget https://github.com/frida/frida/releases/download/16.1.5/frida-gadget-16.1.5-android-arm64.so.xz
unxz frida-gadget-16.1.5-android-arm64.so.xz
mv frida-gadget-16.1.5-android-arm64.so libfrida-gadget.so

Now we need to open the most important file in an android APK: AndroidManifest.xml.

First, there are a few settings we want to make sure are set correctly in the file. Let’s look at the <application> element:

<application android:allowBackup="false" android:appComponentFactory="androidx.core.app.CoreComponentFactory" android:extractNativeLibs="false" android:icon="@mipmap/mv_icon_app_launcher" android:label="@string/app_name" android:largeHeap="true" android:name="com.ihealth.base.MyApplication" android:networkSecurityConfig="@xml/network_security_config" android:roundIcon="@mipmap/mv_icon_app_launcher" android:supportsRtl="true" android:theme="@style/Pro.Theme.NoActionBar" android:usesCleartextTraffic="true">

android:extractNativeLibs=“false”

Notice that android:extractNativeLibs is set to false. We need that to be true.

Next we want to search for the activity name with the LAUNCHER category element.

<activity android:exported="true" android:launchMode="standard" android:name="com.ihealth.business.common.welcome.WelcomeActivity"

This gives us a launcher activity of com.ihealth.business.common.welcome.WelcomeActivity. This is effectively the entry point for the android application. Now let’s look for that WelcomeActivity in the rest of the unpacked application directory.

grep -r 'com.ihealth.business.common.welcome.WelcomeActivity'
smali_classes2/com/ihealth/broadcastReceiver/LanguageReceiver.smali:    const-class v0, Lcom/ihealth/business/common/welcome/WelcomeActivity;
smali_classes2/com/ihealth/base/BaseActivity.smali:    const-class v1, Lcom/ihealth/business/common/welcome/WelcomeActivity;
smali_classes2/com/ihealth/business/common/guide/device/bpm1/e.smali:    check-cast v1, Lcom/ihealth/business/common/welcome/WelcomeActivity;
smali_classes2/com/ihealth/business/common/guide/device/bpm1/e.smali:    sget v3, Lcom/ihealth/business/common/welcome/WelcomeActivity;->e:I
smali_classes2/com/ihealth/business/common/guide/device/bpm1/e.smali:    iget-object v3, v1, Lcom/ihealth/business/common/welcome/WelcomeActivity;->a:Ljava/lang/String;
smali_classes2/com/ihealth/business/common/guide/device/bpm1/e.smali:    iget-object v5, v1, Lcom/ihealth/business/common/welcome/WelcomeActivity;->b:Ljava/lang/String;
smali_classes2/com/ihealth/business/common/guide/device/bpm1/e.smali:    iget-object v6, v1, Lcom/ihealth/business/common/welcome/WelcomeActivity;->c:Ljava/lang/String;
smali_classes2/com/ihealth/business/common/guide/device/bpm1/e.smali:    iget v7, v1, Lcom/ihealth/business/common/welcome/WelcomeActivity;->d:I
smali_classes2/com/ihealth/business/common/trampoline/TrampolineActivity.smali:    const-class v2, Lcom/ihealth/business/common/welcome/WelcomeActivity;
smali_classes2/com/ihealth/business/common/welcome/WelcomeActivity.smali:.class public Lcom/ihealth/business/common/welcome/WelcomeActivity;S
smali_classes2/com/ihealth/business/common/welcome/WelcomeActivity.smali:    iput-object v2, p0, Lcom/ihealth/business/common/welcome/WelcomeActivity;->a:Ljava/lang/String;
smali_classes2/com/ihealth/business/common/welcome/WelcomeActivity.smali:    iput-object v2, p0, Lcom/ihealth/business/common/welcome/WelcomeActivity;->c:Ljava/lang/String;
smali_classes2/com/ihealth/business/common/welcome/WelcomeActivity.smali:    iput-object v2, p0, Lcom/ihealth/business/common/welcome/WelcomeActivity;->b:Ljava/lang/String;
smali_classes2/com/ihealth/business/common/welcome/WelcomeActivity.smali:    iput v0, p0, Lcom/ihealth/business/common/welcome/WelcomeActivity;->d:I
smali_classes2/com/ihealth/business/common/welcome/WelcomeActivity.smali:    const-class v4, Lcom/ihealth/business/common/welcome/WelcomeActivity;
AndroidManifest.xml:        <activity android:exported="true" android:launchMode="standard" android:name="com.ihealth.business.common.welcome.WelcomeActivity" android:screenOrientation="portrait" android:theme="@style/AppStart">
grep: build/apk/classes2.dex: binary file matches

There are some false positives, but the smali_classes2/com/ihealth/business/common/welcome/WelcomeActivity.smali stands out as a file that probably defines the WelcomeActivity class. We want to modify this class to add a bit of code that will call our Frida Gadget library as soon as the application starts.

We search inside this file for .method public constructor <init>()V to find the method we want to modify:

# direct methods
.method public constructor <init>()V
    .locals 0

    .line 1
    invoke-direct {p0}, Lcom/trello/rxlifecycle3/components/support/RxAppCompatActivity;-><init>()V

    .line 2
    .line 3
    .line 4
    return-void
.end method

We will change that file to look like the file below:

# direct methods
.method public constructor <init>()V
    .locals 1

    const-string v0, "frida-gadget"
    invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V

    .line 1
    invoke-direct {p0}, Lcom/trello/rxlifecycle3/components/support/RxAppCompatActivity;-><init>()V

    .line 2
    .line 3
    .line 4
    return-void
.end method

Notice that we change the number of local variables from 0 to 1. We also added two lines of code:

const-string v0, "frida-gadget"
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V

This is the code that will execute our Frida Gadget library. Next, we navigate back up to where we unpacked the application and prepare to repack the APK file.

We run the following command to repack our APK file:

apktool b --use-aapt2 -o mod-app.apk unpack/

Next we ZIP align the apk file:

zipalign 4 mod-app.apk final-mod-app.apk

All APK files need to be signed by a key. We run the following command to create a java key store (JKS):

keytool -genkey -v -keystore custom.keystore -alias mykeyaliasname -keyalg RSA -keysize 2048 -validity 10000

Now sign the APK file with our newly minted JKS:

apksigner sign -ks custom.keystore final-mod-app.apk

Next let’s manually uninstall the original iHealth mobile application and then use adb to install our newly modified application:

adb install final-mod-app.apk

Intercepting Traffic with Caido
#

Now when we launch the modified android application it will pause on the startup screen:

app1
Application paused waiting for Frida to attach

The application has successful executed the Frida Gadget library and has paused waiting for frida to connect to it over ADB.

Next we will run a Frida Codeshare script to bypass certificate pinning:

frida -U gadget --codeshare sowdust/universal-android-ssl-pinning-bypass-2

Now the application resumes and we can now see data appear in Caido!

caido5
Application data is showing up in Caido

This is awesome, but there is a bunch of data to random google services that we don’t care about. Let’s filter this out.

In Caido, you can create scopes that define what requests should appear in your HTTP History window.

I will create a scope called ihealth with an In Scope entry of *.ihealthlabs.com.

caido6
Caido scope menu

Now when we navigate back to the HTTP History window, we can select our ihealth scope in the upper left hand corner of the screen. Now we just see what we want to see!

caido7
HTTP History with scope selection

Now I can use the mobile application to discover all the API endpoints that exist to find vulnerabilities in the mobile APIs and understand the IoT system as a whole.

Next Steps
#

Next, I plan to dig into the BLE communication protocol that is used to send fitness data between the scale device and the mobile application.

Need an IoT Pentest or other IoT consulting services? Check out all of the IoT Security Services we offer at Brown Fine Security.