Blog: Internet Of Things

Reverse Engineering BLE from Android apps with Frida

David Lodge 23 Feb 2018

One of the problems with looking at BLE devices is trying to work out what should be sent to the device. There are a few of ways of doing this:

  1. Use a Bluetooth sniffer, such as Ubertooth to attempt to catch the transmissions. This is quite irritating as there’s a 1 in 3 chance of actually getting a valid data stream
  2. Use bleno to emulate the device and try and catch the data
  3. Reverse the mobile app to try and work out the protocol

I generally use option three and try and read through the decompiled Android code to work out the protocol. This can become a real pain if the app has been obfuscated, which is becoming more and more common nowadays.

I got thinking as to whether we could hook into the method call to just dump out the call to write to the characteristic. It turns out that there’s a few frameworks to allow similar, out of these I started messing with the first one to show up on a Google search: Frida.

This is a framework for injecting into binaries which supports a number of platforms including Linux, Windows, iOS and Android. You can code programs in JavaScript which has an API for hooking into Java calls and, just to really confuse things, there’s Python binding to automate stuff.

The problem with Frida is that the documentation is written in a way that assumes you know how to use Frida already. Fortunately, there are a few tutorials out there which go some of the way; but not enough to be useful. Hence this article.

Installation

This is the easiest part – you can just use pip to install it like you would any Python (I’m using Python 2.7) application:

pip install frida

I first tried this on my Linux VM that I use for a lot of hardware stuff and I got random crashes and timeouts, these went away when I ran it on my Windows base OS. The route cause for this seems to have been the version of adb that was running on my VM was a tad flaky, so make sure that you have a decent connection with your Android device first.

Now we have Frida on the base OS, we need to install a server on the Android device. I’m using my much battered Nexus 5, which is running Android 6.0.1 (rooted of course). The safest place to push it is in /data/local/tmp, which the shell user has write access to and is persistent across reboots.

C:\Users\dave>adb push frida-server /data/local/tmp
C:\Users\dave>adb shell
[email protected]:/ $ su
[email protected]:/ # cd /data/local/tmp
[email protected]:/data/local/tmp # ./frida-server &

And… that’s it; it’s all running and we can start using it!

Setting up the app

Because I can’t show the app I’m actually testing (client privilege et al), I’m using the Marshall amp app which one of my colleagues is looking at and I’m sure we’ll find some exciting blog posts coming about in a bit…

The first step is to get the apk and process name for the app. Without programming, the quickest way of getting the app name is to look at the URL for the app on the Play store:

To get the apk it is easier to install it on a device and then pull the apk out. There are services out there that will decrypt Google Play’s apk file and present it for download, but I really don’t trust them.

To do this we need the filename of where the apk is stored, we can do this by calling the package manager (pm) command with the -f option (for list filenames):

D:\dave>adb shell pm list packages -f | findstr marshall
package:/data/app/air.com.marshall.gateway-1/base.apk=air.com.marshall.gateway

The bit before the = is the path to the app, so we can extract this with adb pull (you don’t need to be root to do this):

D:\dave>adb pull /data/app/air.com.marshall.gateway-1/base.apk marshall.apk
7157 KB/s (15674806 bytes in 2.138s)

We also need the name of the app. This should be the name of the package (com.marshall.tone) but may not be. It’s safer to check this on a process listing. We can use adb shell ps for this, or we could use frida-ps: (-U means use a USB connection to the Frida server rather than try and contact the local host)

D:\dave>frida-ps -U | findstr marshall
16182  air.com.marshall.gateway

Now to try and hook into the method, we need to find out what we need to hook into. Searching Google will tell us that Android’s API call to write a characteristic via BLE is android.bluetooth.BluetoothGatt.writeCharacteristic.

For my first few attempts I tried to hook onto this, but couldn’t get it to work, so I went for a slightly easier one and hooked where it was called in the application.

I used jadx-gui to search the decompiled code for writeSettingValue and chased down the instances of android.bluetooth.BluetoothGatt to find a reference to the class net.transpose.igniteaneandroid.IgniteANEAndroidExt

So we can see that the method net.transpose.igniteaneandroid.IgniteANEAndroidExt.writeSettingValue takes an array of byte and a boolean and returns nothing (i.e. void) and sets up the parameter to call the API call writeCharacteristic.

So, theoretically, this means if we intercept this we can see everything written via Bluetooth.

Using Frida

We could run all this on the Frida command line, but it is easier to script this up.

The Frida console is basically a Node session, which uses JavaScript to programmatically hook into calls. There is a Java API to map Java stuff to JavaScript, although the type data is a bit confusing.

This is where I ran into problems – as writeCharacteristic takes an array of byte, which is translated into a JavaScript object, how was this represented once I’d hooked it. The default logging prompt just stated [Object object], which was no help. So I wrote a small program to do a hook and dump out the metadata:

function enumerateObject(obj)
{
     for (key in obj)
     {
     console.log(key + ": " + obj[key]);
     }
}

function newWriteCharacteristic(data)
{
     enumerateObject(data);
     this.writeCharacteristic(data);
}

function hookIt()
{
     var ble=Java.use("net.transpose.igniteaneandroid.IgniteANEAndroidExt ");
     ble. writeSettingValue.implementation=newWriteCharacteristic;
}

Java.perform(hookIt);

This will attach to the class definition of net.transpose.igniteaneandroid.IgniteANEAndroidExt and replace the implementation of the writeSettingValue method with my version, which enumerates the passed object before passing control back to the original function.

frida -U -l c:\users\dave\desktop\marshall-write.js air.com.marshall.gateway

Where the -l option supplies the script and com.marshall.tone is the process name.

I then used the app enough to cause a BLE characteristic write attempt (by selecting a preset) and got back the following on the console:

As we can see the object has three attributes:

  • $handle – the handle to the object’s instance
  • type – which tells us that it’s a byte array
  • length – the length of the object

As it’s a byte array we can just loop through it by index. So if we replace newWriteCharacteristic with:

function newWriteCharacteristic(data)
{
    hexstr="";
    for (i=0;i<data.length;i++)
    {
        b=(data[i]>>>0)&0xff;
        n=b.toString(16);
        hexstr += ("00" + n).slice(-2)+" ";
    }
    console.log("Output: " + hexstr);
    this.writeCharacteristic(data);
}

This should hex dump the values sent. Frida does actually have a builtin hex dump function, but I couldn’t get it to work in my messing time.

Reloading Frida and pressing a few presets on the app shows us what it is sending:

For this to work, the device had to be pre-paired to the Marshall amp, but it looks like it’s working!

We can test this for certain by attempting to replay one of the dumped writes through gatttool to see whether it works on the device:

And… it worked, the preset changed!

Conclusion

Frida is a powerful tool to intercept data flying between the internal gubbins of (amongst others) Android apps which could make some tasks easier.

It’s worthwhile getting a basic understanding of it as it can make reverse engineering of BLE a lot easier.

If you want the code I hacked together it can be found on our github account. Note, I really dislike JavaScripts’s nesting of anonymous functions, so I tend to expand them out where possible to make the code more readable, so the code may be overly verbose compared to other tutorials out there.

For more guitar amp research, with videos and recommendations have a read of my colleague Chris’s post.